From e26abf87ecd1eefd9ab0a198eee56f9c643e4001 Mon Sep 17 00:00:00 2001 From: layerdiffusion <19834515+lllyasviel@users.noreply.github.com> Date: Fri, 26 Jul 2024 08:51:34 -0700 Subject: [PATCH] Gradio 4 + WebUI 1.10 --- .eslintrc.js | 2 + .github/ISSUE_TEMPLATE/bug_report.yml | 105 -- .github/ISSUE_TEMPLATE/config.yml | 5 - .github/ISSUE_TEMPLATE/feature_request.yml | 40 - .github/pull_request_template.md | 15 - .github/workflows/on_pull_request.yaml | 38 - .github/workflows/run_tests.yaml | 107 -- .github/workflows/warns_merge_master.yml | 19 - .gitignore | 8 +- CHANGELOG.md | 417 +++++++- README.md | 671 +++++++++++- _typos.toml | 5 + configs/alt-diffusion-inference.yaml | 2 +- configs/alt-diffusion-m18-inference.yaml | 2 +- configs/instruct-pix2pix.yaml | 2 +- configs/sd3-inference.yaml | 5 + configs/sd_xl_inpaint.yaml | 2 +- configs/v1-inference.yaml | 2 +- configs/v1-inpainting-inference.yaml | 2 +- extensions-builtin/LDSR/sd_hijack_ddpm_v1.py | 10 +- .../Lora/extra_networks_lora.py | 23 +- extensions-builtin/Lora/network.py | 44 +- extensions-builtin/Lora/networks.py | 73 +- .../Lora/scripts/lora_script.py | 3 +- .../Lora/ui_edit_user_metadata.py | 12 +- .../Lora/ui_extra_networks_lora.py | 4 +- .../canvas-zoom-and-pan/javascript/zoom.js | 968 ------------------ .../scripts/hotkey_config.py | 17 - .../canvas-zoom-and-pan/style.css | 66 -- .../scripts/extra_options_section.py | 8 +- .../forge_legacy_preprocessors/install.py | 2 +- .../scripts/postprocessing_autosized_crop.py | 128 +-- .../scripts}/postprocessing_caption.py | 60 +- .../postprocessing_create_flipped_copies.py | 64 +- .../scripts}/postprocessing_focal_crop.py | 108 +- .../postprocessing_split_oversized.py | 142 +-- .../sd_forge_controlnet/install.py | 2 +- .../javascript/active_units.js | 39 +- .../controlnet_ui/controlnet_ui_group.py | 336 ++---- .../controlnet_ui/openpose_editor.py | 2 +- .../lib_controlnet/controlnet_ui/preset.py | 313 ------ .../controlnet_ui/tool_button.py | 12 - .../lib_controlnet/external_code.py | 49 +- .../sd_forge_controlnet/scripts/controlnet.py | 26 +- .../sd_forge_controlnet/style.css | 25 +- .../sd_forge_controlnet/tests/conftest.py | 7 - .../tests/images/1girl.png | Bin 493039 -> 0 bytes .../sd_forge_controlnet/tests/images/mask.png | Bin 244 -> 0 bytes .../tests/images/mask_small.png | Bin 226 -> 0 bytes .../tests/images/portrait/1.webp | Bin 20481 -> 0 bytes .../tests/images/portrait/2.jpg | Bin 37869 -> 0 bytes .../tests/images/portrait/3.jpeg | Bin 22485 -> 0 bytes .../tests/images/portrait/4.jpg | Bin 6600 -> 0 bytes .../tests/images/portrait/5.jpg | Bin 206595 -> 0 bytes .../tests/images/portrait/6.jpg | Bin 15098 -> 0 bytes .../tests/web_api/__init__.py | 0 .../tests/web_api/detect_test.py | 63 -- .../tests/web_api/generation_test.py | 171 ---- .../tests/web_api/template.py | 347 ------- .../sd_forge_controlnet_example/preload.py | 6 - .../scripts/sd_forge_controlnet_example.py | 160 --- .../scripts/soft_inpainting.py | 39 +- html/extra-networks-copy-path-button.html | 2 +- html/extra-networks-edit-item-button.html | 2 +- html/extra-networks-metadata-button.html | 2 +- html/extra-networks-pane-dirs.html | 8 + html/extra-networks-pane-tree.html | 8 + html/extra-networks-pane.html | 64 +- html/footer.html | 2 +- javascript/aspectRatioOverlay.js | 48 +- javascript/contextMenus.js | 49 +- javascript/dragdrop.js | 36 +- javascript/edit-attention.js | 8 + javascript/extraNetworks.js | 177 +++- javascript/gradio.js | 7 + javascript/imageviewer.js | 40 +- javascript/profilerVisualization.js | 207 ++-- javascript/progressbar.js | 22 + javascript/resizeHandle.js | 62 +- javascript/ui.js | 55 +- javascript/ui_settings_hints.js | 14 +- ldm_patched/ldm/models/autoencoder.py | 4 +- ldm_patched/modules/model_patcher.py | 3 + ldm_patched/modules/sd.py | 4 +- modules/api/api.py | 62 +- modules/api/models.py | 68 +- modules/cache.py | 92 +- modules/call_queue.py | 39 +- modules/cmd_args.py | 11 +- modules/codeformer_model.py | 2 +- modules/errors.py | 2 +- modules/extensions.py | 70 +- modules/extra_networks.py | 2 +- modules/gfpgan_model.py | 4 +- modules/gradio_extensions.py | 166 +++ modules/gradio_extensons.py | 83 -- modules/hypernetworks/hypernetwork.py | 5 +- modules/images.py | 112 +- modules/img2img.py | 79 +- modules/infotext_utils.py | 99 +- modules/infotext_versions.py | 4 + modules/initialize.py | 5 +- modules/initialize_util.py | 15 +- modules/launch_utils.py | 10 +- modules/lowvram.py | 3 + modules/mac_specific.py | 2 +- modules/masking.py | 42 +- modules/modelloader.py | 46 +- modules/models/diffusion/ddpm_edit.py | 8 +- modules/models/diffusion/uni_pc/uni_pc.py | 4 +- modules/models/sd3/mmdit.py | 622 +++++++++++ modules/models/sd3/other_impls.py | 510 +++++++++ modules/models/sd3/sd3_cond.py | 222 ++++ modules/models/sd3/sd3_impls.py | 374 +++++++ modules/models/sd3/sd3_model.py | 96 ++ modules/options.py | 3 + modules/paths_internal.py | 7 +- modules/postprocessing.py | 21 +- modules/processing.py | 254 +++-- modules/processing_scripts/comments.py | 7 + modules/processing_scripts/refiner.py | 2 +- modules/processing_scripts/sampler.py | 45 + modules/processing_scripts/seed.py | 2 +- modules/profiling.py | 46 + modules/progress.py | 11 +- modules/prompt_parser.py | 2 +- modules/rng.py | 4 +- modules/safe.py | 4 +- modules/script_callbacks.py | 279 +++-- modules/script_loading.py | 4 + modules/scripts.py | 161 ++- modules/scripts_postprocessing.py | 6 +- modules/sd_emphasis.py | 4 +- modules/sd_hijack.py | 17 +- modules/sd_hijack_clip.py | 73 +- modules/sd_hijack_optimizations.py | 6 +- modules/sd_hijack_unet.py | 79 +- modules/sd_hijack_utils.py | 26 +- modules/sd_models.py | 190 +++- modules/sd_models_config.py | 16 +- modules/sd_models_types.py | 6 + modules/sd_models_xl.py | 4 +- modules/sd_samplers.py | 77 +- modules/sd_samplers_cfg_denoiser.py | 12 +- modules/sd_samplers_common.py | 2 +- modules/sd_samplers_kdiffusion.py | 103 +- modules/sd_samplers_timesteps.py | 1 + modules/sd_samplers_timesteps_impl.py | 45 +- modules/sd_schedulers.py | 154 +++ modules/sd_vae_approx.py | 27 +- modules/sd_vae_taesd.py | 40 +- modules/shared.py | 32 +- modules/shared_gradio_themes.py | 41 + modules/shared_items.py | 42 + modules/shared_options.py | 55 +- modules/shared_state.py | 2 + modules/styles.py | 3 +- modules/sysinfo.py | 127 ++- modules/textual_inversion/autocrop.py | 4 +- modules/textual_inversion/dataset.py | 5 +- modules/textual_inversion/image_embedding.py | 14 +- .../{logging.py => saving_settings.py} | 128 +-- .../textual_inversion/textual_inversion.py | 16 +- modules/torch_utils.py | 8 + modules/txt2img.py | 5 +- modules/ui.py | 270 +++-- modules/ui_common.py | 64 +- modules/ui_components.py | 63 +- modules/ui_extensions.py | 89 +- modules/ui_extra_networks.py | 150 ++- modules/ui_extra_networks_user_metadata.py | 12 +- modules/ui_gradio_extensions.py | 7 +- modules/ui_loadsave.py | 2 + modules/ui_postprocessing.py | 31 +- modules/ui_prompt_styles.py | 2 +- modules/ui_settings.py | 30 +- modules/ui_tempdir.py | 127 ++- modules/upscaler.py | 10 +- modules/upscaler_utils.py | 4 +- modules/util.py | 75 ++ modules_forge/forge_alter_samplers.py | 31 +- modules_forge/forge_canvas/canvas.css | 159 +++ modules_forge/forge_canvas/canvas.html | 63 ++ modules_forge/forge_canvas/canvas.min.js | 1 + modules_forge/forge_canvas/canvas.py | 120 +++ modules_forge/forge_loader.py | 3 + modules_forge/forge_version.py | 2 +- pyproject.toml | 6 +- requirements.txt | 5 +- requirements_versions.txt | 15 +- script.js | 15 + scripts/outpainting_mk_2.py | 2 +- scripts/postprocessing_codeformer.py | 2 +- scripts/postprocessing_gfpgan.py | 2 +- scripts/postprocessing_upscale.py | 74 +- scripts/xyz_grid.py | 91 +- style.css | 110 +- webui-macos-env.sh | 12 +- webui.bat | 8 +- webui.py | 4 +- webui.sh | 42 +- 201 files changed, 7557 insertions(+), 4829 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/bug_report.yml delete mode 100644 .github/ISSUE_TEMPLATE/config.yml delete mode 100644 .github/ISSUE_TEMPLATE/feature_request.yml delete mode 100644 .github/pull_request_template.md delete mode 100644 .github/workflows/on_pull_request.yaml delete mode 100644 .github/workflows/run_tests.yaml delete mode 100644 .github/workflows/warns_merge_master.yml create mode 100644 _typos.toml create mode 100644 configs/sd3-inference.yaml delete mode 100644 extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js delete mode 100644 extensions-builtin/canvas-zoom-and-pan/scripts/hotkey_config.py delete mode 100644 extensions-builtin/canvas-zoom-and-pan/style.css rename scripts/processing_autosized_crop.py => extensions-builtin/postprocessing-for-training/scripts/postprocessing_autosized_crop.py (97%) rename {scripts => extensions-builtin/postprocessing-for-training/scripts}/postprocessing_caption.py (96%) rename {scripts => extensions-builtin/postprocessing-for-training/scripts}/postprocessing_create_flipped_copies.py (97%) rename {scripts => extensions-builtin/postprocessing-for-training/scripts}/postprocessing_focal_crop.py (97%) rename {scripts => extensions-builtin/postprocessing-for-training/scripts}/postprocessing_split_oversized.py (97%) delete mode 100644 extensions-builtin/sd_forge_controlnet/lib_controlnet/controlnet_ui/preset.py delete mode 100644 extensions-builtin/sd_forge_controlnet/lib_controlnet/controlnet_ui/tool_button.py delete mode 100644 extensions-builtin/sd_forge_controlnet/tests/conftest.py delete mode 100644 extensions-builtin/sd_forge_controlnet/tests/images/1girl.png delete mode 100644 extensions-builtin/sd_forge_controlnet/tests/images/mask.png delete mode 100644 extensions-builtin/sd_forge_controlnet/tests/images/mask_small.png delete mode 100644 extensions-builtin/sd_forge_controlnet/tests/images/portrait/1.webp delete mode 100644 extensions-builtin/sd_forge_controlnet/tests/images/portrait/2.jpg delete mode 100644 extensions-builtin/sd_forge_controlnet/tests/images/portrait/3.jpeg delete mode 100644 extensions-builtin/sd_forge_controlnet/tests/images/portrait/4.jpg delete mode 100644 extensions-builtin/sd_forge_controlnet/tests/images/portrait/5.jpg delete mode 100644 extensions-builtin/sd_forge_controlnet/tests/images/portrait/6.jpg delete mode 100644 extensions-builtin/sd_forge_controlnet/tests/web_api/__init__.py delete mode 100644 extensions-builtin/sd_forge_controlnet/tests/web_api/detect_test.py delete mode 100644 extensions-builtin/sd_forge_controlnet/tests/web_api/generation_test.py delete mode 100644 extensions-builtin/sd_forge_controlnet/tests/web_api/template.py delete mode 100644 extensions-builtin/sd_forge_controlnet_example/preload.py delete mode 100644 extensions-builtin/sd_forge_controlnet_example/scripts/sd_forge_controlnet_example.py create mode 100644 html/extra-networks-pane-dirs.html create mode 100644 html/extra-networks-pane-tree.html create mode 100644 javascript/gradio.js create mode 100644 modules/gradio_extensions.py delete mode 100644 modules/gradio_extensons.py create mode 100644 modules/models/sd3/mmdit.py create mode 100644 modules/models/sd3/other_impls.py create mode 100644 modules/models/sd3/sd3_cond.py create mode 100644 modules/models/sd3/sd3_impls.py create mode 100644 modules/models/sd3/sd3_model.py create mode 100644 modules/processing_scripts/sampler.py create mode 100644 modules/profiling.py create mode 100644 modules/sd_schedulers.py rename modules/textual_inversion/{logging.py => saving_settings.py} (96%) create mode 100644 modules_forge/forge_canvas/canvas.css create mode 100644 modules_forge/forge_canvas/canvas.html create mode 100644 modules_forge/forge_canvas/canvas.min.js create mode 100644 modules_forge/forge_canvas/canvas.py diff --git a/.eslintrc.js b/.eslintrc.js index 9c70eff85..2e7258f6b 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -78,6 +78,8 @@ module.exports = { //extraNetworks.js requestGet: "readonly", popup: "readonly", + // profilerVisualization.js + createVisualizationTable: "readonly", // from python localization: "readonly", // progrssbar.js diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml deleted file mode 100644 index 5876e9410..000000000 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ /dev/null @@ -1,105 +0,0 @@ -name: Bug Report -description: You think something is broken in the UI -title: "[Bug]: " -labels: ["bug-report"] - -body: - - type: markdown - attributes: - value: | - > The title of the bug report should be short and descriptive. - > Use relevant keywords for searchability. - > Do not leave it blank, but also do not put an entire error log in it. - - type: checkboxes - attributes: - label: Checklist - description: | - Please perform basic debugging to see if extensions or configuration is the cause of the issue. - Basic debug procedure -  1. Disable all third-party extensions - check if extension is the cause -  2. Update extensions and webui - sometimes things just need to be updated -  3. Backup and remove your config.json and ui-config.json - check if the issue is caused by bad configuration -  4. Delete venv with third-party extensions disabled - sometimes extensions might cause wrong libraries to be installed -  5. Try a fresh installation webui in a different directory - see if a clean installation solves the issue - Before making a issue report please, check that the issue hasn't been reported recently. - options: - - label: The issue exists after disabling all extensions - - label: The issue exists on a clean installation of webui - - label: The issue is caused by an extension, but I believe it is caused by a bug in the webui - - label: The issue exists in the current version of the webui - - label: The issue has not been reported before recently - - label: The issue has been reported before but has not been fixed yet - - type: markdown - attributes: - value: | - > Please fill this form with as much information as possible. Don't forget to "Upload Sysinfo" and "What browsers" and provide screenshots if possible - - type: textarea - id: what-did - attributes: - label: What happened? - description: Tell us what happened in a very clear and simple way - placeholder: | - txt2img is not working as intended. - validations: - required: true - - type: textarea - id: steps - attributes: - label: Steps to reproduce the problem - description: Please provide us with precise step by step instructions on how to reproduce the bug - placeholder: | - 1. Go to ... - 2. Press ... - 3. ... - validations: - required: true - - type: textarea - id: what-should - attributes: - label: What should have happened? - description: Tell us what you think the normal behavior should be - placeholder: | - WebUI should ... - validations: - required: true - - type: dropdown - id: browsers - attributes: - label: What browsers do you use to access the UI ? - multiple: true - options: - - Mozilla Firefox - - Google Chrome - - Brave - - Apple Safari - - Microsoft Edge - - Android - - iOS - - Other - - type: textarea - id: sysinfo - attributes: - label: Sysinfo - description: System info file, generated by WebUI. You can generate it in settings, on the Sysinfo page. Drag the file into the field to upload it. If you submit your report without including the sysinfo file, the report will be closed. If needed, review the report to make sure it includes no personal information you don't want to share. If you can't start WebUI, you can use --dump-sysinfo commandline argument to generate the file. - placeholder: | - 1. Go to WebUI Settings -> Sysinfo -> Download system info. - If WebUI fails to launch, use --dump-sysinfo commandline argument to generate the file - 2. Upload the Sysinfo as a attached file, Do NOT paste it in as plain text. - validations: - required: true - - type: textarea - id: logs - attributes: - label: Console logs - description: Please provide **full** cmd/terminal logs from the moment you started UI to the end of it, after the bug occured. If it's very long, provide a link to pastebin or similar service. - render: Shell - validations: - required: true - - type: textarea - id: misc - attributes: - label: Additional information - description: | - Please provide us with any relevant additional info or context. - Examples: -  I have updated my GPU driver recently. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml deleted file mode 100644 index f58c94a9b..000000000 --- a/.github/ISSUE_TEMPLATE/config.yml +++ /dev/null @@ -1,5 +0,0 @@ -blank_issues_enabled: false -contact_links: - - name: WebUI Community Support - url: https://github.com/AUTOMATIC1111/stable-diffusion-webui/discussions - about: Please ask and answer questions here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml deleted file mode 100644 index 35a887408..000000000 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ /dev/null @@ -1,40 +0,0 @@ -name: Feature request -description: Suggest an idea for this project -title: "[Feature Request]: " -labels: ["enhancement"] - -body: - - type: checkboxes - attributes: - label: Is there an existing issue for this? - description: Please search to see if an issue already exists for the feature you want, and that it's not implemented in a recent build/commit. - options: - - label: I have searched the existing issues and checked the recent builds/commits - required: true - - type: markdown - attributes: - value: | - *Please fill this form with as much information as possible, provide screenshots and/or illustrations of the feature if possible* - - type: textarea - id: feature - attributes: - label: What would your feature do ? - description: Tell us about your feature in a very clear and simple way, and what problem it would solve - validations: - required: true - - type: textarea - id: workflow - attributes: - label: Proposed workflow - description: Please provide us with step by step information on how you'd like the feature to be accessed and used - value: | - 1. Go to .... - 2. Press .... - 3. ... - validations: - required: true - - type: textarea - id: misc - attributes: - label: Additional information - description: Add any other context or screenshots about the feature request here. diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md deleted file mode 100644 index c9fcda2e2..000000000 --- a/.github/pull_request_template.md +++ /dev/null @@ -1,15 +0,0 @@ -## Description - -* a simple description of what you're trying to accomplish -* a summary of changes in code -* which issues it fixes, if any - -## Screenshots/videos: - - -## Checklist: - -- [ ] I have read [contributing wiki page](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Contributing) -- [ ] I have performed a self-review of my own code -- [ ] My code follows the [style guidelines](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Contributing#code-style) -- [ ] My code passes [tests](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Tests) diff --git a/.github/workflows/on_pull_request.yaml b/.github/workflows/on_pull_request.yaml deleted file mode 100644 index 9e44c806a..000000000 --- a/.github/workflows/on_pull_request.yaml +++ /dev/null @@ -1,38 +0,0 @@ -name: Linter - -on: - - push - - pull_request - -jobs: - lint-python: - name: ruff - runs-on: ubuntu-latest - if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name - steps: - - name: Checkout Code - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 - with: - python-version: 3.11 - # NB: there's no cache: pip here since we're not installing anything - # from the requirements.txt file(s) in the repository; it's faster - # not to have GHA download an (at the time of writing) 4 GB cache - # of PyTorch and other dependencies. - - name: Install Ruff - run: pip install ruff==0.1.6 - - name: Run Ruff - run: ruff . - lint-js: - name: eslint - runs-on: ubuntu-latest - if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name - steps: - - name: Checkout Code - uses: actions/checkout@v3 - - name: Install Node.js - uses: actions/setup-node@v3 - with: - node-version: 18 - - run: npm i --ci - - run: npm run lint diff --git a/.github/workflows/run_tests.yaml b/.github/workflows/run_tests.yaml deleted file mode 100644 index e075ba60d..000000000 --- a/.github/workflows/run_tests.yaml +++ /dev/null @@ -1,107 +0,0 @@ -name: Tests - -on: - - push - - pull_request - -env: - FORGE_CQ_TEST: "True" - -jobs: - test: - name: tests on CPU - runs-on: ubuntu-latest - if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name - steps: - - name: Checkout Code - uses: actions/checkout@v3 - - name: Set up Python 3.10 - uses: actions/setup-python@v4 - with: - python-version: 3.10.6 - cache: pip - cache-dependency-path: | - **/requirements*txt - launch.py - - name: Cache models - id: cache-models - uses: actions/cache@v3 - with: - path: models - key: "2023-12-30" - - name: Install test dependencies - run: pip install wait-for-it -r requirements-test.txt - env: - PIP_DISABLE_PIP_VERSION_CHECK: "1" - PIP_PROGRESS_BAR: "off" - - name: Setup environment - run: python launch.py --skip-torch-cuda-test --exit - env: - PIP_DISABLE_PIP_VERSION_CHECK: "1" - PIP_PROGRESS_BAR: "off" - TORCH_INDEX_URL: https://download.pytorch.org/whl/cpu - WEBUI_LAUNCH_LIVE_OUTPUT: "1" - PYTHONUNBUFFERED: "1" - - name: Print installed packages - run: pip freeze - - name: Download models - run: | - declare -a urls=( - "https://huggingface.co/lllyasviel/fav_models/resolve/main/fav/realisticVisionV51_v51VAE.safetensors" - ) - for url in "${urls[@]}"; do - filename="models/Stable-diffusion/${url##*/}" # Extracts the last part of the URL - if [ ! -f "$filename" ]; then - curl -Lo "$filename" "$url" - fi - done - # - name: Download ControlNet models - # run: | - # declare -a urls=( - # "https://huggingface.co/lllyasviel/ControlNet-v1-1/resolve/main/control_v11p_sd15_canny.pth" - # ) - - # for url in "${urls[@]}"; do - # filename="models/ControlNet/${url##*/}" # Extracts the last part of the URL - # if [ ! -f "$filename" ]; then - # curl -Lo "$filename" "$url" - # fi - # done - - name: Start test server - run: > - python -m coverage run - --data-file=.coverage.server - launch.py - --skip-prepare-environment - --skip-torch-cuda-test - --test-server - --do-not-download-clip - --no-half - --disable-opt-split-attention - --always-cpu - --api-server-stop - --ckpt models/Stable-diffusion/realisticVisionV51_v51VAE.safetensors - 2>&1 | tee output.txt & - - name: Run tests - run: | - wait-for-it --service 127.0.0.1:7860 -t 20 - python -m pytest -vv --junitxml=test/results.xml --cov . --cov-report=xml --verify-base-url test - # TODO(huchenlei): Enable ControlNet tests. Currently it is too slow to run these tests on CPU with - # real SD model. We need to find a way to load empty SD model. - # - name: Run ControlNet tests - # run: > - # python -m pytest - # --junitxml=test/results.xml - # --cov ./extensions-builtin/sd_forge_controlnet - # --cov-report=xml - # --verify-base-url - # ./extensions-builtin/sd_forge_controlnet/tests - - name: Kill test server - if: always() - run: curl -vv -XPOST http://127.0.0.1:7860/sdapi/v1/server-stop && sleep 10 - - name: Upload main app output - uses: actions/upload-artifact@v3 - if: always() - with: - name: output - path: output.txt diff --git a/.github/workflows/warns_merge_master.yml b/.github/workflows/warns_merge_master.yml deleted file mode 100644 index ae2aab6ba..000000000 --- a/.github/workflows/warns_merge_master.yml +++ /dev/null @@ -1,19 +0,0 @@ -name: Pull requests can't target master branch - -"on": - pull_request: - types: - - opened - - synchronize - - reopened - branches: - - master - -jobs: - check: - runs-on: ubuntu-latest - steps: - - name: Warning marge into master - run: | - echo -e "::warning::This pull request directly merge into \"master\" branch, normally development happens on \"dev\" branch." - exit 1 diff --git a/.gitignore b/.gitignore index ca7c47ee1..b572a8f42 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,9 @@ __pycache__ *.ckpt *.safetensors *.pth +*.dev.js +.DS_Store +/output/ /ESRGAN/* /SwinIR/* /repositories @@ -39,6 +42,9 @@ notification.mp3 /package-lock.json /.coverage* /test/test_outputs +/cache +trace.json +/sysinfo-????-??-??-??-??.json /test/results.xml coverage.xml -**/tests/**/expectations \ No newline at end of file +**/tests/**/expectations diff --git a/CHANGELOG.md b/CHANGELOG.md index 67429bbff..301bfd068 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,407 @@ +## 1.10.0 + +### Features: +* A lot of performance improvements (see below in Performance section) +* Stable Diffusion 3 support ([#16030](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/16030), [#16164](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/16164), [#16212](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/16212)) + * Recommended Euler sampler; DDIM and other timestamp samplers currently not supported + * T5 text model is disabled by default, enable it in settings +* New schedulers: + * Align Your Steps ([#15751](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15751)) + * KL Optimal ([#15608](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15608)) + * Normal ([#16149](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/16149)) + * DDIM ([#16149](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/16149)) + * Simple ([#16142](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/16142)) + * Beta ([#16235](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/16235)) +* New sampler: DDIM CFG++ ([#16035](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/16035)) + +### Minor: +* Option to skip CFG on early steps ([#15607](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15607)) +* Add --models-dir option ([#15742](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15742)) +* Allow mobile users to open context menu by using two fingers press ([#15682](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15682)) +* Infotext: add Lora name as TI hashes for bundled Textual Inversion ([#15679](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15679)) +* Check model's hash after downloading it to prevent corruped downloads ([#15602](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15602)) +* More extension tag filtering options ([#15627](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15627)) +* When saving AVIF, use JPEG's quality setting ([#15610](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15610)) +* Add filename pattern: `[basename]` ([#15978](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15978)) +* Add option to enable clip skip for clip L on SDXL ([#15992](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15992)) +* Option to prevent screen sleep during generation ([#16001](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/16001)) +* ToggleLivePriview button in image viewer ([#16065](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/16065)) +* Remove ui flashing on reloading and fast scrollong ([#16153](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/16153)) +* option to disable save button log.csv ([#16242](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/16242)) + +### Extensions and API: +* Add process_before_every_sampling hook ([#15984](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15984)) +* Return HTTP 400 instead of 404 on invalid sampler error ([#16140](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/16140)) + +### Performance: +* [Performance 1/6] use_checkpoint = False ([#15803](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15803)) +* [Performance 2/6] Replace einops.rearrange with torch native ops ([#15804](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15804)) +* [Performance 4/6] Precompute is_sdxl_inpaint flag ([#15806](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15806)) +* [Performance 5/6] Prevent unnecessary extra networks bias backup ([#15816](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15816)) +* [Performance 6/6] Add --precision half option to avoid casting during inference ([#15820](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15820)) +* [Performance] LDM optimization patches ([#15824](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15824)) +* [Performance] Keep sigmas on CPU ([#15823](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15823)) +* Check for nans in unet only once, after all steps have been completed +* Added pption to run torch profiler for image generation + +### Bug Fixes: +* Fix for grids without comprehensive infotexts ([#15958](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15958)) +* feat: lora partial update precede full update ([#15943](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15943)) +* Fix bug where file extension had an extra '.' under some circumstances ([#15893](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15893)) +* Fix corrupt model initial load loop ([#15600](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15600)) +* Allow old sampler names in API ([#15656](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15656)) +* more old sampler scheduler compatibility ([#15681](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15681)) +* Fix Hypertile xyz ([#15831](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15831)) +* XYZ CSV skipinitialspace ([#15832](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15832)) +* fix soft inpainting on mps and xpu, torch_utils.float64 ([#15815](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15815)) +* fix extention update when not on main branch ([#15797](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15797)) +* update pickle safe filenames +* use relative path for webui-assets css ([#15757](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15757)) +* When creating a virtual environment, upgrade pip in webui.bat/webui.sh ([#15750](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15750)) +* Fix AttributeError ([#15738](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15738)) +* use script_path for webui root in launch_utils ([#15705](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15705)) +* fix extra batch mode P Transparency ([#15664](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15664)) +* use gradio theme colors in css ([#15680](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15680)) +* Fix dragging text within prompt input ([#15657](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15657)) +* Add correct mimetype for .mjs files ([#15654](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15654)) +* QOL Items - handle metadata issues more cleanly for SD models, Loras and embeddings ([#15632](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15632)) +* replace wsl-open with wslpath and explorer.exe ([#15968](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15968)) +* Fix SDXL Inpaint ([#15976](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15976)) +* multi size grid ([#15988](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15988)) +* fix Replace preview ([#16118](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/16118)) +* Possible fix of wrong scale in weight decomposition ([#16151](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/16151)) +* Ensure use of python from venv on Mac and Linux ([#16116](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/16116)) +* Prioritize python3.10 over python3 if both are available on Linux and Mac (with fallback) ([#16092](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/16092)) +* stoping generation extras ([#16085](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/16085)) +* Fix SD2 loading ([#16078](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/16078), [#16079](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/16079)) +* fix infotext Lora hashes for hires fix different lora ([#16062](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/16062)) +* Fix sampler scheduler autocorrection warning ([#16054](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/16054)) +* fix ui flashing on reloading and fast scrollong ([#16153](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/16153)) +* fix upscale logic ([#16239](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/16239)) +* [bug] do not break progressbar on non-job actions (add wrap_gradio_call_no_job) ([#16202](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/16202)) +* fix OSError: cannot write mode P as JPEG ([#16194](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/16194)) + +### Other: +* fix changelog #15883 -> #15882 ([#15907](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15907)) +* ReloadUI backgroundColor --background-fill-primary ([#15864](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15864)) +* Use different torch versions for Intel and ARM Macs ([#15851](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15851)) +* XYZ override rework ([#15836](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15836)) +* scroll extensions table on overflow ([#15830](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15830)) +* img2img batch upload method ([#15817](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15817)) +* chore: sync v1.8.0 packages according to changelog ([#15783](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15783)) +* Add AVIF MIME type support to mimetype definitions ([#15739](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15739)) +* Update imageviewer.js ([#15730](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15730)) +* no-referrer ([#15641](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15641)) +* .gitignore trace.json ([#15980](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15980)) +* Bump spandrel to 0.3.4 ([#16144](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/16144)) +* Defunct --max-batch-count ([#16119](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/16119)) +* docs: update bug_report.yml ([#16102](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/16102)) +* Maintaining Project Compatibility for Python 3.9 Users Without Upgrade Requirements. ([#16088](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/16088), [#16169](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/16169), [#16192](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/16192)) +* Update torch for ARM Macs to 2.3.1 ([#16059](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/16059)) +* remove deprecated setting dont_fix_second_order_samplers_schedule ([#16061](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/16061)) +* chore: fix typos ([#16060](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/16060)) +* shlex.join launch args in console log ([#16170](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/16170)) +* activate venv .bat ([#16231](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/16231)) +* add ids to the resize tabs in img2img ([#16218](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/16218)) +* update installation guide linux ([#16178](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/16178)) +* Robust sysinfo ([#16173](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/16173)) +* do not send image size on paste inpaint ([#16180](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/16180)) +* Fix noisy DS_Store files for MacOS ([#16166](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/16166)) + + +## 1.9.4 + +### Bug Fixes: +* pin setuptools version to fix the startup error ([#15882](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15882)) + +## 1.9.3 + +### Bug Fixes: +* fix get_crop_region_v2 ([#15594](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15594)) + +## 1.9.2 + +### Extensions and API: +* restore 1.8.0-style naming of scripts + +## 1.9.1 + +### Minor: +* Add avif support ([#15582](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15582)) +* Add filename patterns: `[sampler_scheduler]` and `[scheduler]` ([#15581](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15581)) + +### Extensions and API: +* undo adding scripts to sys.modules +* Add schedulers API endpoint ([#15577](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15577)) +* Remove API upscaling factor limits ([#15560](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15560)) + +### Bug Fixes: +* Fix images do not match / Coordinate 'right' is less than 'left' ([#15534](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15534)) +* fix: remove_callbacks_for_function should also remove from the ordered map ([#15533](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15533)) +* fix x1 upscalers ([#15555](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15555)) +* Fix cls.__module__ value in extension script ([#15532](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15532)) +* fix typo in function call (eror -> error) ([#15531](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15531)) + +### Other: +* Hide 'No Image data blocks found.' message ([#15567](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15567)) +* Allow webui.sh to be runnable from arbitrary directories containing a .git file ([#15561](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15561)) +* Compatibility with Debian 11, Fedora 34+ and openSUSE 15.4+ ([#15544](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15544)) +* numpy DeprecationWarning product -> prod ([#15547](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15547)) +* get_crop_region_v2 ([#15583](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15583), [#15587](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15587)) + + +## 1.9.0 + +### Features: +* Make refiner switchover based on model timesteps instead of sampling steps ([#14978](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14978)) +* add an option to have old-style directory view instead of tree view; stylistic changes for extra network sorting/search controls +* add UI for reordering callbacks, support for specifying callback order in extension metadata ([#15205](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15205)) +* Sgm uniform scheduler for SDXL-Lightning models ([#15325](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15325)) +* Scheduler selection in main UI ([#15333](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15333), [#15361](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15361), [#15394](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15394)) + +### Minor: +* "open images directory" button now opens the actual dir ([#14947](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14947)) +* Support inference with LyCORIS BOFT networks ([#14871](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14871), [#14973](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14973)) +* make extra network card description plaintext by default, with an option to re-enable HTML as it was +* resize handle for extra networks ([#15041](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15041)) +* cmd args: `--unix-filenames-sanitization` and `--filenames-max-length` ([#15031](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15031)) +* show extra networks parameters in HTML table rather than raw JSON ([#15131](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15131)) +* Add DoRA (weight-decompose) support for LoRA/LoHa/LoKr ([#15160](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15160), [#15283](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15283)) +* Add '--no-prompt-history' cmd args for disable last generation prompt history ([#15189](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15189)) +* update preview on Replace Preview ([#15201](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15201)) +* only fetch updates for extensions' active git branches ([#15233](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15233)) +* put upscale postprocessing UI into an accordion ([#15223](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15223)) +* Support dragdrop for URLs to read infotext ([#15262](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15262)) +* use diskcache library for caching ([#15287](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15287), [#15299](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15299)) +* Allow PNG-RGBA for Extras Tab ([#15334](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15334)) +* Support cover images embedded in safetensors metadata ([#15319](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15319)) +* faster interrupt when using NN upscale ([#15380](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15380)) +* Extras upscaler: an input field to limit maximul side length for the output image ([#15293](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15293), [#15415](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15415), [#15417](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15417), [#15425](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15425)) +* add an option to hide postprocessing options in Extras tab + +### Extensions and API: +* ResizeHandleRow - allow overriden column scale parametr ([#15004](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15004)) +* call script_callbacks.ui_settings_callback earlier; fix extra-options-section built-in extension killing the ui if using a setting that doesn't exist +* make it possible to use zoom.js outside webui context ([#15286](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15286), [#15288](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15288)) +* allow variants for extension name in metadata.ini ([#15290](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15290)) +* make reloading UI scripts optional when doing Reload UI, and off by default +* put request: gr.Request at start of img2img function similar to txt2img +* open_folder as util ([#15442](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15442)) +* make it possible to import extensions' script files as `import scripts.` ([#15423](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15423)) + +### Performance: +* performance optimization for extra networks HTML pages +* optimization for extra networks filtering +* optimization for extra networks sorting + +### Bug Fixes: +* prevent escape button causing an interrupt when no generation has been made yet +* [bug] avoid doble upscaling in inpaint ([#14966](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14966)) +* possible fix for reload button not appearing in some cases for extra networks. +* fix: the `split_threshold` parameter does not work when running Split oversized images ([#15006](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15006)) +* Fix resize-handle visability for vertical layout (mobile) ([#15010](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15010)) +* register_tmp_file also for mtime ([#15012](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15012)) +* Protect alphas_cumprod during refiner switchover ([#14979](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14979)) +* Fix EXIF orientation in API image loading ([#15062](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15062)) +* Only override emphasis if actually used in prompt ([#15141](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15141)) +* Fix emphasis infotext missing from `params.txt` ([#15142](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15142)) +* fix extract_style_text_from_prompt #15132 ([#15135](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15135)) +* Fix Soft Inpaint for AnimateDiff ([#15148](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15148)) +* edit-attention: deselect surrounding whitespace ([#15178](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15178)) +* chore: fix font not loaded ([#15183](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15183)) +* use natural sort in extra networks when ordering by path +* Fix built-in lora system bugs caused by torch.nn.MultiheadAttention ([#15190](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15190)) +* Avoid error from None in get_learned_conditioning ([#15191](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15191)) +* Add entry to MassFileLister after writing metadata ([#15199](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15199)) +* fix issue with Styles when Hires prompt is used ([#15269](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15269), [#15276](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15276)) +* Strip comments from hires fix prompt ([#15263](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15263)) +* Make imageviewer event listeners browser consistent ([#15261](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15261)) +* Fix AttributeError in OFT when trying to get MultiheadAttention weight ([#15260](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15260)) +* Add missing .mean() back ([#15239](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15239)) +* fix "Restore progress" button ([#15221](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15221)) +* fix ui-config for InputAccordion [custom_script_source] ([#15231](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15231)) +* handle 0 wheel deltaY ([#15268](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15268)) +* prevent alt menu for firefox ([#15267](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15267)) +* fix: fix syntax errors ([#15179](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15179)) +* restore outputs path ([#15307](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15307)) +* Escape btn_copy_path filename ([#15316](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15316)) +* Fix extra networks buttons when filename contains an apostrophe ([#15331](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15331)) +* escape brackets in lora random prompt generator ([#15343](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15343)) +* fix: Python version check for PyTorch installation compatibility ([#15390](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15390)) +* fix typo in call_queue.py ([#15386](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15386)) +* fix: when find already_loaded model, remove loaded by array index ([#15382](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15382)) +* minor bug fix of sd model memory management ([#15350](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15350)) +* Fix CodeFormer weight ([#15414](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15414)) +* Fix: Remove script callbacks in ordered_callbacks_map ([#15428](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15428)) +* fix limited file write (thanks, Sylwia) +* Fix extra-single-image API not doing upscale failed ([#15465](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15465)) +* error handling paste_field callables ([#15470](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15470)) + +### Hardware: +* Add training support and change lspci for Ascend NPU ([#14981](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14981)) +* Update to ROCm5.7 and PyTorch ([#14820](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14820)) +* Better workaround for Navi1, removing --pre for Navi3 ([#15224](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15224)) +* Ascend NPU wiki page ([#15228](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15228)) + +### Other: +* Update comment for Pad prompt/negative prompt v0 to add a warning about truncation, make it override the v1 implementation +* support resizable columns for touch (tablets) ([#15002](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15002)) +* Fix #14591 using translated content to do categories mapping ([#14995](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14995)) +* Use `absolute` path for normalized filepath ([#15035](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15035)) +* resizeHandle handle double tap ([#15065](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15065)) +* --dat-models-path cmd flag ([#15039](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15039)) +* Add a direct link to the binary release ([#15059](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15059)) +* upscaler_utils: Reduce logging ([#15084](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15084)) +* Fix various typos with crate-ci/typos ([#15116](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15116)) +* fix_jpeg_live_preview ([#15102](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15102)) +* [alternative fix] can't load webui if selected wrong extra option in ui ([#15121](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15121)) +* Error handling for unsupported transparency ([#14958](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14958)) +* Add model description to searched terms ([#15198](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15198)) +* bump action version ([#15272](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15272)) +* PEP 604 annotations ([#15259](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15259)) +* Automatically Set the Scale by value when user selects an Upscale Model ([#15244](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15244)) +* move postprocessing-for-training into builtin extensions ([#15222](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15222)) +* type hinting in shared.py ([#15211](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15211)) +* update ruff to 0.3.3 +* Update pytorch lightning utilities ([#15310](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15310)) +* Add Size as an XYZ Grid option ([#15354](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15354)) +* Use HF_ENDPOINT variable for HuggingFace domain with default ([#15443](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15443)) +* re-add update_file_entry ([#15446](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15446)) +* create_infotext allow index and callable, re-work Hires prompt infotext ([#15460](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15460)) +* update restricted_opts to include more options for --hide-ui-dir-config ([#15492](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15492)) + + +## 1.8.0 + +### Features: +* Update torch to version 2.1.2 +* Soft Inpainting ([#14208](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14208)) +* FP8 support ([#14031](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14031), [#14327](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14327)) +* Support for SDXL-Inpaint Model ([#14390](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14390)) +* Use Spandrel for upscaling and face restoration architectures ([#14425](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14425), [#14467](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14467), [#14473](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14473), [#14474](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14474), [#14477](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14477), [#14476](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14476), [#14484](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14484), [#14500](https://github.com/AUTOMATIC1111/stable-difusion-webui/pull/14500), [#14501](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14501), [#14504](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14504), [#14524](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14524), [#14809](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14809)) +* Automatic backwards version compatibility (when loading infotexts from old images with program version specified, will add compatibility settings) +* Implement zero terminal SNR noise schedule option (**[SEED BREAKING CHANGE](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Seed-breaking-changes#180-dev-170-225-2024-01-01---zero-terminal-snr-noise-schedule-option)**, [#14145](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14145), [#14979](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14979)) +* Add a [✨] button to run hires fix on selected image in the gallery (with help from [#14598](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14598), [#14626](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14626), [#14728](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14728)) +* [Separate assets repository](https://github.com/AUTOMATIC1111/stable-diffusion-webui-assets); serve fonts locally rather than from google's servers +* Official LCM Sampler Support ([#14583](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14583)) +* Add support for DAT upscaler models ([#14690](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14690), [#15039](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15039)) +* Extra Networks Tree View ([#14588](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14588), [#14900](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14900)) +* NPU Support ([#14801](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14801)) +* Prompt comments support + +### Minor: +* Allow pasting in WIDTHxHEIGHT strings into the width/height fields ([#14296](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14296)) +* add option: Live preview in full page image viewer ([#14230](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14230), [#14307](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14307)) +* Add keyboard shortcuts for generate/skip/interrupt ([#14269](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14269)) +* Better TCMALLOC support on different platforms ([#14227](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14227), [#14883](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14883), [#14910](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14910)) +* Lora not found warning ([#14464](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14464)) +* Adding negative prompts to Loras in extra networks ([#14475](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14475)) +* xyz_grid: allow varying the seed along an axis separate from axis options ([#12180](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/12180)) +* option to convert VAE to bfloat16 (implementation of [#9295](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/9295)) +* Better IPEX support ([#14229](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14229), [#14353](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14353), [#14559](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14559), [#14562](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14562), [#14597](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14597)) +* Option to interrupt after current generation rather than immediately ([#13653](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/13653), [#14659](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14659)) +* Fullscreen Preview control fading/disable ([#14291](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14291)) +* Finer settings freezing control ([#13789](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/13789)) +* Increase Upscaler Limits ([#14589](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14589)) +* Adjust brush size with hotkeys ([#14638](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14638)) +* Add checkpoint info to csv log file when saving images ([#14663](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14663)) +* Make more columns resizable ([#14740](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14740), [#14884](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14884)) +* Add an option to not overlay original image for inpainting for #14727 +* Add Pad conds v0 option to support same generation with DDIM as before 1.6.0 +* Add "Interrupting..." placeholder. +* Button for refresh extensions list ([#14857](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14857)) +* Add an option to disable normalization after calculating emphasis. ([#14874](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14874)) +* When counting tokens, also include enabled styles (can be disabled in settings to revert to previous behavior) +* Configuration for the [📂] button for image gallery ([#14947](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14947)) +* Support inference with LyCORIS BOFT networks ([#14871](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14871), [#14973](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14973)) +* support resizable columns for touch (tablets) ([#15002](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15002)) + +### Extensions and API: +* Removed packages from requirements: basicsr, gfpgan, realesrgan; as well as their dependencies: absl-py, addict, beautifulsoup4, future, gdown, grpcio, importlib-metadata, lmdb, lpips, Markdown, platformdirs, PySocks, soupsieve, tb-nightly, tensorboard-data-server, tomli, Werkzeug, yapf, zipp, soupsieve +* Enable task ids for API ([#14314](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14314)) +* add override_settings support for infotext API +* rename generation_parameters_copypaste module to infotext_utils +* prevent crash due to Script __init__ exception ([#14407](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14407)) +* Bump numpy to 1.26.2 ([#14471](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14471)) +* Add utility to inspect a model's dtype/device ([#14478](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14478)) +* Implement general forward method for all method in built-in lora ext ([#14547](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14547)) +* Execute model_loaded_callback after moving to target device ([#14563](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14563)) +* Add self to CFGDenoiserParams ([#14573](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14573)) +* Allow TLS with API only mode (--nowebui) ([#14593](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14593)) +* New callback: postprocess_image_after_composite ([#14657](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14657)) +* modules/api/api.py: add api endpoint to refresh embeddings list ([#14715](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14715)) +* set_named_arg ([#14773](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14773)) +* add before_token_counter callback and use it for prompt comments +* ResizeHandleRow - allow overridden column scale parameter ([#15004](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15004)) + +### Performance: +* Massive performance improvement for extra networks directories with a huge number of files in them in an attempt to tackle #14507 ([#14528](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14528)) +* Reduce unnecessary re-indexing extra networks directory ([#14512](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14512)) +* Avoid unnecessary `isfile`/`exists` calls ([#14527](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14527)) + +### Bug Fixes: +* fix multiple bugs related to styles multi-file support ([#14203](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14203), [#14276](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14276), [#14707](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14707)) +* Lora fixes ([#14300](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14300), [#14237](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14237), [#14546](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14546), [#14726](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14726)) +* Re-add setting lost as part of e294e46 ([#14266](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14266)) +* fix extras caption BLIP ([#14330](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14330)) +* include infotext into saved init image for img2img ([#14452](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14452)) +* xyz grid handle axis_type is None ([#14394](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14394)) +* Update Added (Fixed) IPV6 Functionality When there is No Webui Argument Passed webui.py ([#14354](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14354)) +* fix API thread safe issues of txt2img and img2img ([#14421](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14421)) +* handle selectable script_index is None ([#14487](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14487)) +* handle config.json failed to load ([#14525](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14525), [#14767](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14767)) +* paste infotext cast int as float ([#14523](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14523)) +* Ensure GRADIO_ANALYTICS_ENABLED is set early enough ([#14537](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14537)) +* Fix logging configuration again ([#14538](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14538)) +* Handle CondFunc exception when resolving attributes ([#14560](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14560)) +* Fix extras big batch crashes ([#14699](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14699)) +* Fix using wrong model caused by alias ([#14655](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14655)) +* Add # to the invalid_filename_chars list ([#14640](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14640)) +* Fix extension check for requirements ([#14639](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14639)) +* Fix tab indexes are reset after restart UI ([#14637](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14637)) +* Fix nested manual cast ([#14689](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14689)) +* Keep postprocessing upscale selected tab after restart ([#14702](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14702)) +* XYZ grid: filter out blank vals when axis is int or float type (like int axis seed) ([#14754](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14754)) +* fix CLIP Interrogator topN regex ([#14775](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14775)) +* Fix dtype error in MHA layer/change dtype checking mechanism for manual cast ([#14791](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14791)) +* catch load style.csv error ([#14814](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14814)) +* fix error when editing extra networks card +* fix extra networks metadata failing to work properly when you create the .json file with metadata for the first time. +* util.walk_files extensions case insensitive ([#14879](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14879)) +* if extensions page not loaded, prevent apply ([#14873](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14873)) +* call the right function for token counter in img2img +* Fix the bugs that search/reload will disappear when using other ExtraNetworks extensions ([#14939](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14939)) +* Gracefully handle mtime read exception from cache ([#14933](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14933)) +* Only trigger interrupt on `Esc` when interrupt button visible ([#14932](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14932)) +* Disable prompt token counters option actually disables token counting rather than just hiding results. +* avoid double upscaling in inpaint ([#14966](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14966)) +* Fix #14591 using translated content to do categories mapping ([#14995](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14995)) +* fix: the `split_threshold` parameter does not work when running Split oversized images ([#15006](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15006)) +* Fix resize-handle for mobile ([#15010](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15010), [#15065](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15065)) + +### Other: +* Assign id for "extra_options". Replace numeric field with slider. ([#14270](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14270)) +* change state dict comparison to ref compare ([#14216](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14216)) +* Bump torch-rocm to 5.6/5.7 ([#14293](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14293)) +* Base output path off data path ([#14446](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14446)) +* reorder training preprocessing modules in extras tab ([#14367](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14367)) +* Remove `cleanup_models` code ([#14472](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14472)) +* only rewrite ui-config when there is change ([#14352](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14352)) +* Fix lint issue from 501993eb ([#14495](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14495)) +* Update README.md ([#14548](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14548)) +* hires button, fix seeds () +* Logging: set formatter correctly for fallback logger too ([#14618](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14618)) +* Read generation info from infotexts rather than json for internal needs (save, extract seed from generated pic) ([#14645](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14645)) +* improve get_crop_region ([#14709](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14709)) +* Bump safetensors' version to 0.4.2 ([#14782](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14782)) +* add tooltip create_submit_box ([#14803](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14803)) +* extensions tab table row hover highlight ([#14885](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14885)) +* Always add timestamp to displayed image ([#14890](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14890)) +* Added core.filemode=false so doesn't track changes in file permission… ([#14930](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14930)) +* Normalize command-line argument paths ([#14934](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14934), [#15035](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15035)) +* Use original App Title in progress bar ([#14916](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14916)) +* register_tmp_file also for mtime ([#15012](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15012)) + ## 1.7.0 ### Features: @@ -40,7 +444,8 @@ * infotext updates: add option to disregard certain infotext fields, add option to not include VAE in infotext, add explanation to infotext settings page, move some options to infotext settings page * add FP32 fallback support on sd_vae_approx ([#14046](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14046)) * support XYZ scripts / split hires path from unet ([#14126](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14126)) -* allow use of mutiple styles csv files ([#14125](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14125)) +* allow use of multiple styles csv files ([#14125](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14125)) +* make extra network card description plaintext by default, with an option (Treat card description as HTML) to re-enable HTML as it was (originally by [#13241](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/13241)) ### Extensions and API: * update gradio to 3.41.2 @@ -176,7 +581,7 @@ * new samplers: Restart, DPM++ 2M SDE Exponential, DPM++ 2M SDE Heun, DPM++ 2M SDE Heun Karras, DPM++ 2M SDE Heun Exponential, DPM++ 3M SDE, DPM++ 3M SDE Karras, DPM++ 3M SDE Exponential ([#12300](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/12300), [#12519](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/12519), [#12542](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/12542)) * rework DDIM, PLMS, UniPC to use CFG denoiser same as in k-diffusion samplers: * makes all of them work with img2img - * makes prompt composition posssible (AND) + * makes prompt composition possible (AND) * makes them available for SDXL * always show extra networks tabs in the UI ([#11808](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/11808)) * use less RAM when creating models ([#11958](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/11958), [#12599](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/12599)) @@ -352,7 +757,7 @@ * user metadata system for custom networks * extended Lora metadata editor: set activation text, default weight, view tags, training info * Lora extension rework to include other types of networks (all that were previously handled by LyCORIS extension) - * show github stars for extenstions + * show github stars for extensions * img2img batch mode can read extra stuff from png info * img2img batch works with subdirectories * hotkeys to move prompt elements: alt+left/right @@ -571,7 +976,7 @@ * do not wait for Stable Diffusion model to load at startup * add filename patterns: `[denoising]` * directory hiding for extra networks: dirs starting with `.` will hide their cards on extra network tabs unless specifically searched for - * LoRA: for the `<...>` text in prompt, use name of LoRA that is in the metdata of the file, if present, instead of filename (both can be used to activate LoRA) + * LoRA: for the `<...>` text in prompt, use name of LoRA that is in the metadata of the file, if present, instead of filename (both can be used to activate LoRA) * LoRA: read infotext params from kohya-ss's extension parameters if they are present and if his extension is not active * LoRA: fix some LoRAs not working (ones that have 3x3 convolution layer) * LoRA: add an option to use old method of applying LoRAs (producing same results as with kohya-ss) @@ -601,7 +1006,7 @@ * fix gamepad navigation * make the lightbox fullscreen image function properly * fix squished thumbnails in extras tab - * keep "search" filter for extra networks when user refreshes the tab (previously it showed everthing after you refreshed) + * keep "search" filter for extra networks when user refreshes the tab (previously it showed everything after you refreshed) * fix webui showing the same image if you configure the generation to always save results into same file * fix bug with upscalers not working properly * fix MPS on PyTorch 2.0.1, Intel Macs @@ -619,7 +1024,7 @@ * switch to PyTorch 2.0.0 (except for AMD GPUs) * visual improvements to custom code scripts * add filename patterns: `[clip_skip]`, `[hasprompt<>]`, `[batch_number]`, `[generation_number]` - * add support for saving init images in img2img, and record their hashes in infotext for reproducability + * add support for saving init images in img2img, and record their hashes in infotext for reproducibility * automatically select current word when adjusting weight with ctrl+up/down * add dropdowns for X/Y/Z plot * add setting: Stable Diffusion/Random number generator source: makes it possible to make images generated from a given manual seed consistent across different GPUs diff --git a/README.md b/README.md index 8eae83ceb..60ffac6f1 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,11 @@ -# Under Construction - -**Oops we are upgrading the repo now ... Please come back several hours later ...** - # Stable Diffusion WebUI Forge Stable Diffusion WebUI Forge is a platform on top of [Stable Diffusion WebUI](https://github.com/AUTOMATIC1111/stable-diffusion-webui) (based on [Gradio](https://www.gradio.app/)) to make development easier, optimize resource management, speed up inference, and study experimental features. The name "Forge" is inspired from "Minecraft Forge". This project is aimed at becoming SD WebUI's Forge. +This repo will undergo major change very recently. See also the [Announcement](https://github.com/lllyasviel/stable-diffusion-webui-forge/discussions/801). + # Installing Forge If you are proficient in Git and you want to install Forge as another branch of SD-WebUI, please see [here](https://github.com/continue-revolution/sd-webui-animatediff/blob/forge/master/docs/how-to-use.md#you-have-a1111-and-you-know-git). In this way, you can reuse all SD checkpoints and all extensions you installed previously in your OG SD-WebUI, but you should know what you are doing. @@ -24,6 +22,669 @@ Note that running `update.bat` is important, otherwise you may be using a previo ![image](https://github.com/lllyasviel/stable-diffusion-webui-forge/assets/19834515/c49bd60d-82bd-4086-9859-88d472582b94) -### Previous Versions +## Previous Versions You can download previous versions [here](https://github.com/lllyasviel/stable-diffusion-webui-forge/discussions/849). + +# Screenshots of Comparison + +I tested with several devices, and this is a typical result from 8GB VRAM (3070ti laptop) with SDXL. + +**This is original WebUI:** + +![image](https://github.com/lllyasviel/stable-diffusion-webui-forge/assets/19834515/16893937-9ed9-4f8e-b960-70cd5d1e288f) + +![image](https://github.com/lllyasviel/stable-diffusion-webui-forge/assets/19834515/7bbc16fe-64ef-49e2-a595-d91bb658bd94) + +![image](https://github.com/lllyasviel/stable-diffusion-webui-forge/assets/19834515/de1747fd-47bc-482d-a5c6-0728dd475943) + +![image](https://github.com/lllyasviel/stable-diffusion-webui-forge/assets/19834515/96e5e171-2d74-41ba-9dcc-11bf68be7e16) + +(average about 7.4GB/8GB, peak at about 7.9GB/8GB) + +**This is WebUI Forge:** + +![image](https://github.com/lllyasviel/stable-diffusion-webui-forge/assets/19834515/ca5e05ed-bd86-4ced-8662-f41034648e8c) + +![image](https://github.com/lllyasviel/stable-diffusion-webui-forge/assets/19834515/3629ee36-4a99-4d9b-b371-12efb260a283) + +![image](https://github.com/lllyasviel/stable-diffusion-webui-forge/assets/19834515/6d13ebb7-c30d-4aa8-9242-c0b5a1af8c95) + +![image](https://github.com/lllyasviel/stable-diffusion-webui-forge/assets/19834515/c4f723c3-6ea7-4539-980b-0708ed2a69aa) + +(average and peak are all 6.3GB/8GB) + +You can see that Forge does not change WebUI results. Installing Forge is not a seed breaking change. + +Forge can perfectly keep WebUI unchanged even for most complicated prompts like `fantasy landscape with a [mountain:lake:0.25] and [an oak:a christmas tree:0.75][ in foreground::0.6][ in background:0.25] [shoddy:masterful:0.5]`. + +All your previous works still work in Forge! + +# Forge Backend + +Forge backend removes all WebUI's codes related to resource management and reworked everything. All previous CMD flags like `medvram, lowvram, medvram-sdxl, precision full, no half, no half vae, attention_xxx, upcast unet`, ... are all **REMOVED**. Adding these flags will not cause error but they will not do anything now. **We highly encourage Forge users to remove all cmd flags and let Forge to decide how to load models.** + +Without any cmd flag, Forge can run SDXL with 4GB vram and SD1.5 with 2GB vram. + +**Some flags that you may still pay attention to:** + +1. `--always-offload-from-vram` (This flag will make things **slower** but less risky). This option will let Forge always unload models from VRAM. This can be useful if you use multiple software together and want Forge to use less VRAM and give some VRAM to other software, or when you are using some old extensions that will compete vram with Forge, or (very rarely) when you get OOM. + +2. `--cuda-malloc` (This flag will make things **faster** but more risky). This will ask pytorch to use *cudaMallocAsync* for tensor malloc. On some profilers I can observe performance gain at millisecond level, but the real speed up on most my devices are often unnoticed (about or less than 0.1 second per image). This cannot be set as default because many users reported issues that the async malloc will crash the program. Users need to enable this cmd flag at their own risk. + +3. `--cuda-stream` (This flag will make things **faster** but more risky). This will use pytorch CUDA streams (a special type of thread on GPU) to move models and compute tensors simultaneously. This can almost eliminate all model moving time, and speed up SDXL on 30XX/40XX devices with small VRAM (eg, RTX 4050 6GB, RTX 3060 Laptop 6GB, etc) by about 15\% to 25\%. However, this unfortunately cannot be set as default because I observe higher possibility of pure black images (Nan outputs) on 2060, and higher chance of OOM on 1080 and 2060. When the resolution is large, there is a chance that the computation time of one single attention layer is longer than the time for moving entire model to GPU. When that happens, the next attention layer will OOM since the GPU is filled with the entire model, and no remaining space is available for computing another attention layer. Most overhead detecting methods are not robust enough to be reliable on old devices (in my tests). Users need to enable this cmd flag at their own risk. + +4. `--pin-shared-memory` (This flag will make things **faster** but more risky). Effective only when used together with `--cuda-stream`. This will offload modules to Shared GPU Memory instead of system RAM when offloading models. On some 30XX/40XX devices with small VRAM (eg, RTX 4050 6GB, RTX 3060 Laptop 6GB, etc), I can observe significant (at least 20\%) speed-up for SDXL. However, this unfortunately cannot be set as default because the OOM of Shared GPU Memory is a much more severe problem than common GPU memory OOM. Pytorch does not provide any robust method to unload or detect Shared GPU Memory. Once the Shared GPU Memory OOM, the entire program will crash (observed with SDXL on GTX 1060/1050/1066), and there is no dynamic method to prevent or recover from the crash. Users need to enable this cmd flag at their own risk. + +If you really want to play with cmd flags, you can additionally control the GPU with: + +(extreme VRAM cases) + + --always-gpu + --always-cpu + +(rare attention cases) + + --attention-split + --attention-quad + --attention-pytorch + --disable-xformers + --disable-attention-upcast + +(float point type) + + --all-in-fp32 + --all-in-fp16 + --unet-in-bf16 + --unet-in-fp16 + --unet-in-fp8-e4m3fn + --unet-in-fp8-e5m2 + --vae-in-fp16 + --vae-in-fp32 + --vae-in-bf16 + --clip-in-fp8-e4m3fn + --clip-in-fp8-e5m2 + --clip-in-fp16 + --clip-in-fp32 + +(rare platforms) + + --directml + --disable-ipex-hijack + --pytorch-deterministic + +Again, Forge do not recommend users to use any cmd flags unless you are very sure that you really need these. + +# UNet Patcher + +Note that [Forge does not use any other software as backend](https://github.com/lllyasviel/stable-diffusion-webui-forge/discussions/169). The full name of the backend is `Stable Diffusion WebUI with Forge backend`, or for simplicity, the `Forge backend`. The API and python symbols are made similar to previous software only for reducing the learning cost of developers. + +Now developing an extension is super simple. We finally have a patchable UNet. + +Below is using one single file with 80 lines of codes to support FreeU: + +`extensions-builtin/sd_forge_freeu/scripts/forge_freeu.py` + +```python +import torch +import gradio as gr +from modules import scripts + + +def Fourier_filter(x, threshold, scale): + x_freq = torch.fft.fftn(x.float(), dim=(-2, -1)) + x_freq = torch.fft.fftshift(x_freq, dim=(-2, -1)) + B, C, H, W = x_freq.shape + mask = torch.ones((B, C, H, W), device=x.device) + crow, ccol = H // 2, W //2 + mask[..., crow - threshold:crow + threshold, ccol - threshold:ccol + threshold] = scale + x_freq = x_freq * mask + x_freq = torch.fft.ifftshift(x_freq, dim=(-2, -1)) + x_filtered = torch.fft.ifftn(x_freq, dim=(-2, -1)).real + return x_filtered.to(x.dtype) + + +def set_freeu_v2_patch(model, b1, b2, s1, s2): + model_channels = model.model.model_config.unet_config["model_channels"] + scale_dict = {model_channels * 4: (b1, s1), model_channels * 2: (b2, s2)} + + def output_block_patch(h, hsp, *args, **kwargs): + scale = scale_dict.get(h.shape[1], None) + if scale is not None: + hidden_mean = h.mean(1).unsqueeze(1) + B = hidden_mean.shape[0] + hidden_max, _ = torch.max(hidden_mean.view(B, -1), dim=-1, keepdim=True) + hidden_min, _ = torch.min(hidden_mean.view(B, -1), dim=-1, keepdim=True) + hidden_mean = (hidden_mean - hidden_min.unsqueeze(2).unsqueeze(3)) / \ + (hidden_max - hidden_min).unsqueeze(2).unsqueeze(3) + h[:, :h.shape[1] // 2] = h[:, :h.shape[1] // 2] * ((scale[0] - 1) * hidden_mean + 1) + hsp = Fourier_filter(hsp, threshold=1, scale=scale[1]) + return h, hsp + + m = model.clone() + m.set_model_output_block_patch(output_block_patch) + return m + + +class FreeUForForge(scripts.Script): + def title(self): + return "FreeU Integrated" + + def show(self, is_img2img): + # make this extension visible in both txt2img and img2img tab. + return scripts.AlwaysVisible + + def ui(self, *args, **kwargs): + with gr.Accordion(open=False, label=self.title()): + freeu_enabled = gr.Checkbox(label='Enabled', value=False) + freeu_b1 = gr.Slider(label='B1', minimum=0, maximum=2, step=0.01, value=1.01) + freeu_b2 = gr.Slider(label='B2', minimum=0, maximum=2, step=0.01, value=1.02) + freeu_s1 = gr.Slider(label='S1', minimum=0, maximum=4, step=0.01, value=0.99) + freeu_s2 = gr.Slider(label='S2', minimum=0, maximum=4, step=0.01, value=0.95) + + return freeu_enabled, freeu_b1, freeu_b2, freeu_s1, freeu_s2 + + def process_before_every_sampling(self, p, *script_args, **kwargs): + # This will be called before every sampling. + # If you use highres fix, this will be called twice. + + freeu_enabled, freeu_b1, freeu_b2, freeu_s1, freeu_s2 = script_args + + if not freeu_enabled: + return + + unet = p.sd_model.forge_objects.unet + + unet = set_freeu_v2_patch(unet, freeu_b1, freeu_b2, freeu_s1, freeu_s2) + + p.sd_model.forge_objects.unet = unet + + # Below codes will add some logs to the texts below the image outputs on UI. + # The extra_generation_params does not influence results. + p.extra_generation_params.update(dict( + freeu_enabled=freeu_enabled, + freeu_b1=freeu_b1, + freeu_b2=freeu_b2, + freeu_s1=freeu_s1, + freeu_s2=freeu_s2, + )) + + return +``` + +It looks like this: + +![image](https://github.com/lllyasviel/stable-diffusion-webui-forge/assets/19834515/277bac6e-5ea7-4bff-b71a-e55a60cfc03c) + +Similar components like HyperTile, KohyaHighResFix, SAG, can all be implemented within 100 lines of codes (see also the codes). + +![image](https://github.com/lllyasviel/stable-diffusion-webui-forge/assets/19834515/06472b03-b833-4816-ab47-70712ac024d3) + +ControlNets can finally be called by different extensions. + +Implementing Stable Video Diffusion and Zero123 are also super simple now (see also the codes). + +*Stable Video Diffusion:* + +`extensions-builtin/sd_forge_svd/scripts/forge_svd.py` + +```python +import torch +import gradio as gr +import os +import pathlib + +from modules import script_callbacks +from modules.paths import models_path +from modules.ui_common import ToolButton, refresh_symbol +from modules import shared + +from modules_forge.forge_util import numpy_to_pytorch, pytorch_to_numpy +from ldm_patched.modules.sd import load_checkpoint_guess_config +from ldm_patched.contrib.external_video_model import VideoLinearCFGGuidance, SVD_img2vid_Conditioning +from ldm_patched.contrib.external import KSampler, VAEDecode + + +opVideoLinearCFGGuidance = VideoLinearCFGGuidance() +opSVD_img2vid_Conditioning = SVD_img2vid_Conditioning() +opKSampler = KSampler() +opVAEDecode = VAEDecode() + +svd_root = os.path.join(models_path, 'svd') +os.makedirs(svd_root, exist_ok=True) +svd_filenames = [] + + +def update_svd_filenames(): + global svd_filenames + svd_filenames = [ + pathlib.Path(x).name for x in + shared.walk_files(svd_root, allowed_extensions=[".pt", ".ckpt", ".safetensors"]) + ] + return svd_filenames + + +@torch.inference_mode() +@torch.no_grad() +def predict(filename, width, height, video_frames, motion_bucket_id, fps, augmentation_level, + sampling_seed, sampling_steps, sampling_cfg, sampling_sampler_name, sampling_scheduler, + sampling_denoise, guidance_min_cfg, input_image): + filename = os.path.join(svd_root, filename) + model_raw, _, vae, clip_vision = \ + load_checkpoint_guess_config(filename, output_vae=True, output_clip=False, output_clipvision=True) + model = opVideoLinearCFGGuidance.patch(model_raw, guidance_min_cfg)[0] + init_image = numpy_to_pytorch(input_image) + positive, negative, latent_image = opSVD_img2vid_Conditioning.encode( + clip_vision, init_image, vae, width, height, video_frames, motion_bucket_id, fps, augmentation_level) + output_latent = opKSampler.sample(model, sampling_seed, sampling_steps, sampling_cfg, + sampling_sampler_name, sampling_scheduler, positive, + negative, latent_image, sampling_denoise)[0] + output_pixels = opVAEDecode.decode(vae, output_latent)[0] + outputs = pytorch_to_numpy(output_pixels) + return outputs + + +def on_ui_tabs(): + with gr.Blocks() as svd_block: + with gr.Row(): + with gr.Column(): + input_image = gr.Image(label='Input Image', source='upload', type='numpy', height=400) + + with gr.Row(): + filename = gr.Dropdown(label="SVD Checkpoint Filename", + choices=svd_filenames, + value=svd_filenames[0] if len(svd_filenames) > 0 else None) + refresh_button = ToolButton(value=refresh_symbol, tooltip="Refresh") + refresh_button.click( + fn=lambda: gr.update(choices=update_svd_filenames), + inputs=[], outputs=filename) + + width = gr.Slider(label='Width', minimum=16, maximum=8192, step=8, value=1024) + height = gr.Slider(label='Height', minimum=16, maximum=8192, step=8, value=576) + video_frames = gr.Slider(label='Video Frames', minimum=1, maximum=4096, step=1, value=14) + motion_bucket_id = gr.Slider(label='Motion Bucket Id', minimum=1, maximum=1023, step=1, value=127) + fps = gr.Slider(label='Fps', minimum=1, maximum=1024, step=1, value=6) + augmentation_level = gr.Slider(label='Augmentation Level', minimum=0.0, maximum=10.0, step=0.01, + value=0.0) + sampling_steps = gr.Slider(label='Sampling Steps', minimum=1, maximum=200, step=1, value=20) + sampling_cfg = gr.Slider(label='CFG Scale', minimum=0.0, maximum=50.0, step=0.1, value=2.5) + sampling_denoise = gr.Slider(label='Sampling Denoise', minimum=0.0, maximum=1.0, step=0.01, value=1.0) + guidance_min_cfg = gr.Slider(label='Guidance Min Cfg', minimum=0.0, maximum=100.0, step=0.5, value=1.0) + sampling_sampler_name = gr.Radio(label='Sampler Name', + choices=['euler', 'euler_ancestral', 'heun', 'heunpp2', 'dpm_2', + 'dpm_2_ancestral', 'lms', 'dpm_fast', 'dpm_adaptive', + 'dpmpp_2s_ancestral', 'dpmpp_sde', 'dpmpp_sde_gpu', + 'dpmpp_2m', 'dpmpp_2m_sde', 'dpmpp_2m_sde_gpu', + 'dpmpp_3m_sde', 'dpmpp_3m_sde_gpu', 'ddpm', 'lcm', 'ddim', + 'uni_pc', 'uni_pc_bh2'], value='euler') + sampling_scheduler = gr.Radio(label='Scheduler', + choices=['normal', 'karras', 'exponential', 'sgm_uniform', 'simple', + 'ddim_uniform'], value='karras') + sampling_seed = gr.Number(label='Seed', value=12345, precision=0) + + generate_button = gr.Button(value="Generate") + + ctrls = [filename, width, height, video_frames, motion_bucket_id, fps, augmentation_level, + sampling_seed, sampling_steps, sampling_cfg, sampling_sampler_name, sampling_scheduler, + sampling_denoise, guidance_min_cfg, input_image] + + with gr.Column(): + output_gallery = gr.Gallery(label='Gallery', show_label=False, object_fit='contain', + visible=True, height=1024, columns=4) + + generate_button.click(predict, inputs=ctrls, outputs=[output_gallery]) + return [(svd_block, "SVD", "svd")] + + +update_svd_filenames() +script_callbacks.on_ui_tabs(on_ui_tabs) +``` + +Note that although the above codes look like independent codes, they actually will automatically offload/unload any other models. For example, below is me opening webui, load SDXL, generated an image, then go to SVD, then generated image frames. You can see that the GPU memory is perfectly managed and the SDXL is moved to RAM then SVD is moved to GPU. + +Note that this management is fully automatic. This makes writing extensions super simple. + +![image](https://github.com/lllyasviel/stable-diffusion-webui-forge/assets/19834515/de1a2d05-344a-44d7-bab8-9ecc0a58a8d3) + +![image](https://github.com/lllyasviel/stable-diffusion-webui-forge/assets/19834515/14bcefcf-599f-42c3-bce9-3fd5e428dd91) + +Similarly, Zero123: + +![image](https://github.com/lllyasviel/stable-diffusion-webui-forge/assets/19834515/7685019c-7239-47fb-9cb5-2b7b33943285) + +### Write a simple ControlNet: + +Below is a simple extension to have a completely independent pass of ControlNet that never conflicts any other extensions: + +`extensions-builtin/sd_forge_controlnet_example/scripts/sd_forge_controlnet_example.py` + +Note that this extension is hidden because it is only for developers. To see it in UI, use `--show-controlnet-example`. + +The memory optimization in this example is fully automatic. You do not need to care about memory and inference speed, but you may want to cache objects if you wish. + +```python +# Use --show-controlnet-example to see this extension. + +import cv2 +import gradio as gr +import torch + +from modules import scripts +from modules.shared_cmd_options import cmd_opts +from modules_forge.shared import supported_preprocessors +from modules.modelloader import load_file_from_url +from ldm_patched.modules.controlnet import load_controlnet +from modules_forge.controlnet import apply_controlnet_advanced +from modules_forge.forge_util import numpy_to_pytorch +from modules_forge.shared import controlnet_dir + + +class ControlNetExampleForge(scripts.Script): + model = None + + def title(self): + return "ControlNet Example for Developers" + + def show(self, is_img2img): + # make this extension visible in both txt2img and img2img tab. + return scripts.AlwaysVisible + + def ui(self, *args, **kwargs): + with gr.Accordion(open=False, label=self.title()): + gr.HTML('This is an example controlnet extension for developers.') + gr.HTML('You see this extension because you used --show-controlnet-example') + input_image = gr.Image(source='upload', type='numpy') + funny_slider = gr.Slider(label='This slider does nothing. It just shows you how to transfer parameters.', + minimum=0.0, maximum=1.0, value=0.5) + + return input_image, funny_slider + + def process(self, p, *script_args, **kwargs): + input_image, funny_slider = script_args + + # This slider does nothing. It just shows you how to transfer parameters. + del funny_slider + + if input_image is None: + return + + # controlnet_canny_path = load_file_from_url( + # url='https://huggingface.co/lllyasviel/sd_control_collection/resolve/main/sai_xl_canny_256lora.safetensors', + # model_dir=model_dir, + # file_name='sai_xl_canny_256lora.safetensors' + # ) + controlnet_canny_path = load_file_from_url( + url='https://huggingface.co/lllyasviel/fav_models/resolve/main/fav/control_v11p_sd15_canny_fp16.safetensors', + model_dir=controlnet_dir, + file_name='control_v11p_sd15_canny_fp16.safetensors' + ) + print('The model [control_v11p_sd15_canny_fp16.safetensors] download finished.') + + self.model = load_controlnet(controlnet_canny_path) + print('Controlnet loaded.') + + return + + def process_before_every_sampling(self, p, *script_args, **kwargs): + # This will be called before every sampling. + # If you use highres fix, this will be called twice. + + input_image, funny_slider = script_args + + if input_image is None or self.model is None: + return + + B, C, H, W = kwargs['noise'].shape # latent_shape + height = H * 8 + width = W * 8 + batch_size = p.batch_size + + preprocessor = supported_preprocessors['canny'] + + # detect control at certain resolution + control_image = preprocessor( + input_image, resolution=512, slider_1=100, slider_2=200, slider_3=None) + + # here we just use nearest neighbour to align input shape. + # You may want crop and resize, or crop and fill, or others. + control_image = cv2.resize( + control_image, (width, height), interpolation=cv2.INTER_NEAREST) + + # Output preprocessor result. Now called every sampling. Cache in your own way. + p.extra_result_images.append(control_image) + + print('Preprocessor Canny finished.') + + control_image_bchw = numpy_to_pytorch(control_image).movedim(-1, 1) + + unet = p.sd_model.forge_objects.unet + + # Unet has input, middle, output blocks, and we can give different weights + # to each layers in all blocks. + # Below is an example for stronger control in middle block. + # This is helpful for some high-res fix passes. (p.is_hr_pass) + positive_advanced_weighting = { + 'input': [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.1, 1.2], + 'middle': [1.0], + 'output': [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.1, 1.2] + } + negative_advanced_weighting = { + 'input': [0.15, 0.25, 0.35, 0.45, 0.55, 0.65, 0.75, 0.85, 0.95, 1.05, 1.15, 1.25], + 'middle': [1.05], + 'output': [0.15, 0.25, 0.35, 0.45, 0.55, 0.65, 0.75, 0.85, 0.95, 1.05, 1.15, 1.25] + } + + # The advanced_frame_weighting is a weight applied to each image in a batch. + # The length of this list must be same with batch size + # For example, if batch size is 5, the below list is [0.2, 0.4, 0.6, 0.8, 1.0] + # If you view the 5 images as 5 frames in a video, this will lead to + # progressively stronger control over time. + advanced_frame_weighting = [float(i + 1) / float(batch_size) for i in range(batch_size)] + + # The advanced_sigma_weighting allows you to dynamically compute control + # weights given diffusion timestep (sigma). + # For example below code can softly make beginning steps stronger than ending steps. + sigma_max = unet.model.model_sampling.sigma_max + sigma_min = unet.model.model_sampling.sigma_min + advanced_sigma_weighting = lambda s: (s - sigma_min) / (sigma_max - sigma_min) + + # You can even input a tensor to mask all control injections + # The mask will be automatically resized during inference in UNet. + # The size should be B 1 H W and the H and W are not important + # because they will be resized automatically + advanced_mask_weighting = torch.ones(size=(1, 1, 512, 512)) + + # But in this simple example we do not use them + positive_advanced_weighting = None + negative_advanced_weighting = None + advanced_frame_weighting = None + advanced_sigma_weighting = None + advanced_mask_weighting = None + + unet = apply_controlnet_advanced(unet=unet, controlnet=self.model, image_bchw=control_image_bchw, + strength=0.6, start_percent=0.0, end_percent=0.8, + positive_advanced_weighting=positive_advanced_weighting, + negative_advanced_weighting=negative_advanced_weighting, + advanced_frame_weighting=advanced_frame_weighting, + advanced_sigma_weighting=advanced_sigma_weighting, + advanced_mask_weighting=advanced_mask_weighting) + + p.sd_model.forge_objects.unet = unet + + # Below codes will add some logs to the texts below the image outputs on UI. + # The extra_generation_params does not influence results. + p.extra_generation_params.update(dict( + controlnet_info='You should see these texts below output images!', + )) + + return + + +# Use --show-controlnet-example to see this extension. +if not cmd_opts.show_controlnet_example: + del ControlNetExampleForge + +``` + +![image](https://github.com/lllyasviel/stable-diffusion-webui-forge/assets/19834515/822fa2fc-c9f4-4f58-8669-4b6680b91063) + + +### Add a preprocessor + +Below is the full codes to add a normalbae preprocessor with perfect memory managements. + +You can use arbitrary independent extensions to add a preprocessor. + +Your preprocessor will be read by all other extensions using `modules_forge.shared.preprocessors` + +Below codes are in `extensions-builtin\forge_preprocessor_normalbae\scripts\preprocessor_normalbae.py` + +```python +from modules_forge.supported_preprocessor import Preprocessor, PreprocessorParameter +from modules_forge.shared import preprocessor_dir, add_supported_preprocessor +from modules_forge.forge_util import resize_image_with_pad +from modules.modelloader import load_file_from_url + +import types +import torch +import numpy as np + +from einops import rearrange +from annotator.normalbae.models.NNET import NNET +from annotator.normalbae import load_checkpoint +from torchvision import transforms + + +class PreprocessorNormalBae(Preprocessor): + def __init__(self): + super().__init__() + self.name = 'normalbae' + self.tags = ['NormalMap'] + self.model_filename_filters = ['normal'] + self.slider_resolution = PreprocessorParameter( + label='Resolution', minimum=128, maximum=2048, value=512, step=8, visible=True) + self.slider_1 = PreprocessorParameter(visible=False) + self.slider_2 = PreprocessorParameter(visible=False) + self.slider_3 = PreprocessorParameter(visible=False) + self.show_control_mode = True + self.do_not_need_model = False + self.sorting_priority = 100 # higher goes to top in the list + + def load_model(self): + if self.model_patcher is not None: + return + + model_path = load_file_from_url( + "https://huggingface.co/lllyasviel/Annotators/resolve/main/scannet.pt", + model_dir=preprocessor_dir) + + args = types.SimpleNamespace() + args.mode = 'client' + args.architecture = 'BN' + args.pretrained = 'scannet' + args.sampling_ratio = 0.4 + args.importance_ratio = 0.7 + model = NNET(args) + model = load_checkpoint(model_path, model) + self.norm = transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) + + self.model_patcher = self.setup_model_patcher(model) + + def __call__(self, input_image, resolution, slider_1=None, slider_2=None, slider_3=None, **kwargs): + input_image, remove_pad = resize_image_with_pad(input_image, resolution) + + self.load_model() + + self.move_all_model_patchers_to_gpu() + + assert input_image.ndim == 3 + image_normal = input_image + + with torch.no_grad(): + image_normal = self.send_tensor_to_model_device(torch.from_numpy(image_normal)) + image_normal = image_normal / 255.0 + image_normal = rearrange(image_normal, 'h w c -> 1 c h w') + image_normal = self.norm(image_normal) + + normal = self.model_patcher.model(image_normal) + normal = normal[0][-1][:, :3] + normal = ((normal + 1) * 0.5).clip(0, 1) + + normal = rearrange(normal[0], 'c h w -> h w c').cpu().numpy() + normal_image = (normal * 255.0).clip(0, 255).astype(np.uint8) + + return remove_pad(normal_image) + + +add_supported_preprocessor(PreprocessorNormalBae()) + +``` + +# New features (that are not available in original WebUI) + +Thanks to Unet Patcher, many new things are possible now and supported in Forge, including SVD, Z123, masked Ip-adapter, masked controlnet, photomaker, etc. + +Masked Ip-Adapter + +![image](https://github.com/lllyasviel/stable-diffusion-webui-forge/assets/19834515/d26630f9-922d-4483-8bf9-f364dca5fd50) + +![image](https://github.com/lllyasviel/stable-diffusion-webui-forge/assets/19834515/03580ef7-235c-4b03-9ca6-a27677a5a175) + +![image](https://github.com/lllyasviel/stable-diffusion-webui-forge/assets/19834515/d9ed4a01-70d4-45b4-a6a7-2f765f158fae) + +Masked ControlNet + +![image](https://github.com/lllyasviel/stable-diffusion-webui-forge/assets/19834515/872d4785-60e4-4431-85c7-665c781dddaa) + +![image](https://github.com/lllyasviel/stable-diffusion-webui-forge/assets/19834515/335a3b33-1ef8-46ff-a462-9f1b4f2c49fc) + +![image](https://github.com/lllyasviel/stable-diffusion-webui-forge/assets/19834515/b3684a15-8895-414e-8188-487269dfcada) + +PhotoMaker + +(Note that photomaker is a special control that need you to add the trigger word "photomaker". Your prompt should be like "a photo of photomaker") + +![image](https://github.com/lllyasviel/stable-diffusion-webui-forge/assets/19834515/07b0b626-05b5-473b-9d69-3657624d59be) + +Marigold Depth + +![image](https://github.com/lllyasviel/stable-diffusion-webui-forge/assets/19834515/bdf54148-892d-410d-8ed9-70b4b121b6e7) + +# New Samplers (that are not in origin) + + DDPM + DDPM Karras + DPM++ 2M Turbo + DPM++ 2M SDE Turbo + LCM Karras + Euler A Turbo + +# About Extensions + +ControlNet and TiledVAE are integrated, and you should uninstall these two extensions: + + sd-webui-controlnet + multidiffusion-upscaler-for-automatic1111 + +Note that **AnimateDiff** is under construction by [continue-revolution](https://github.com/continue-revolution) at [sd-webui-animatediff forge/master branch](https://github.com/continue-revolution/sd-webui-animatediff/tree/forge/master) and [sd-forge-animatediff](https://github.com/continue-revolution/sd-forge-animatediff) (they are in sync). (continue-revolution original words: prompt travel, inf t2v, controlnet v2v have been proven to work well; motion lora, i2i batch still under construction and may be finished in a week") + +Other extensions should work without problems, like: + + canvas-zoom + translations/localizations + Dynamic Prompts + Adetailer + Ultimate SD Upscale + Reactor + +However, if newer extensions use Forge, their codes can be much shorter. + +Usually if an old extension rework using Forge's unet patcher, 80% codes can be removed, especially when they need to call controlnet. + +# Contribution + +Forge uses a bot to get commits and codes from https://github.com/AUTOMATIC1111/stable-diffusion-webui/tree/dev every afternoon (if merge is automatically successful by a git bot, or by my compiler, or by my ChatGPT bot) or mid-night (if my compiler and my ChatGPT bot both failed to merge and I review it manually). + +All PRs that can be implemented in https://github.com/AUTOMATIC1111/stable-diffusion-webui/tree/dev should submit PRs there. + +Feel free to submit PRs related to the functionality of Forge here. diff --git a/_typos.toml b/_typos.toml new file mode 100644 index 000000000..1c63fe703 --- /dev/null +++ b/_typos.toml @@ -0,0 +1,5 @@ +[default.extend-words] +# Part of "RGBa" (Pillow's pre-multiplied alpha RGB mode) +Ba = "Ba" +# HSA is something AMD uses for their GPUs +HSA = "HSA" diff --git a/configs/alt-diffusion-inference.yaml b/configs/alt-diffusion-inference.yaml index cfbee72d7..4944ab5c8 100644 --- a/configs/alt-diffusion-inference.yaml +++ b/configs/alt-diffusion-inference.yaml @@ -40,7 +40,7 @@ model: use_spatial_transformer: True transformer_depth: 1 context_dim: 768 - use_checkpoint: True + use_checkpoint: False legacy: False first_stage_config: diff --git a/configs/alt-diffusion-m18-inference.yaml b/configs/alt-diffusion-m18-inference.yaml index 41a031d55..c60dca8c7 100644 --- a/configs/alt-diffusion-m18-inference.yaml +++ b/configs/alt-diffusion-m18-inference.yaml @@ -41,7 +41,7 @@ model: use_linear_in_transformer: True transformer_depth: 1 context_dim: 1024 - use_checkpoint: True + use_checkpoint: False legacy: False first_stage_config: diff --git a/configs/instruct-pix2pix.yaml b/configs/instruct-pix2pix.yaml index 4e896879d..564e50ae2 100644 --- a/configs/instruct-pix2pix.yaml +++ b/configs/instruct-pix2pix.yaml @@ -45,7 +45,7 @@ model: use_spatial_transformer: True transformer_depth: 1 context_dim: 768 - use_checkpoint: True + use_checkpoint: False legacy: False first_stage_config: diff --git a/configs/sd3-inference.yaml b/configs/sd3-inference.yaml new file mode 100644 index 000000000..bccb69d2e --- /dev/null +++ b/configs/sd3-inference.yaml @@ -0,0 +1,5 @@ +model: + target: modules.models.sd3.sd3_model.SD3Inferencer + params: + shift: 3 + state_dict: null diff --git a/configs/sd_xl_inpaint.yaml b/configs/sd_xl_inpaint.yaml index 3bad37218..f40f45e33 100644 --- a/configs/sd_xl_inpaint.yaml +++ b/configs/sd_xl_inpaint.yaml @@ -21,7 +21,7 @@ model: params: adm_in_channels: 2816 num_classes: sequential - use_checkpoint: True + use_checkpoint: False in_channels: 9 out_channels: 4 model_channels: 320 diff --git a/configs/v1-inference.yaml b/configs/v1-inference.yaml index d4effe569..25c4d9ed0 100644 --- a/configs/v1-inference.yaml +++ b/configs/v1-inference.yaml @@ -40,7 +40,7 @@ model: use_spatial_transformer: True transformer_depth: 1 context_dim: 768 - use_checkpoint: True + use_checkpoint: False legacy: False first_stage_config: diff --git a/configs/v1-inpainting-inference.yaml b/configs/v1-inpainting-inference.yaml index f9eec37d2..68c199f99 100644 --- a/configs/v1-inpainting-inference.yaml +++ b/configs/v1-inpainting-inference.yaml @@ -40,7 +40,7 @@ model: use_spatial_transformer: True transformer_depth: 1 context_dim: 768 - use_checkpoint: True + use_checkpoint: False legacy: False first_stage_config: diff --git a/extensions-builtin/LDSR/sd_hijack_ddpm_v1.py b/extensions-builtin/LDSR/sd_hijack_ddpm_v1.py index 04adc5eb2..51ab18212 100644 --- a/extensions-builtin/LDSR/sd_hijack_ddpm_v1.py +++ b/extensions-builtin/LDSR/sd_hijack_ddpm_v1.py @@ -301,7 +301,7 @@ def p_losses(self, x_start, t, noise=None): elif self.parameterization == "x0": target = x_start else: - raise NotImplementedError(f"Paramterization {self.parameterization} not yet supported") + raise NotImplementedError(f"Parameterization {self.parameterization} not yet supported") loss = self.get_loss(model_out, target, mean=False).mean(dim=[1, 2, 3]) @@ -572,7 +572,7 @@ def delta_border(self, h, w): :param h: height :param w: width :return: normalized distance to image border, - wtith min distance = 0 at border and max dist = 0.5 at image center + with min distance = 0 at border and max dist = 0.5 at image center """ lower_right_corner = torch.tensor([h - 1, w - 1]).view(1, 1, 2) arr = self.meshgrid(h, w) / lower_right_corner @@ -880,7 +880,7 @@ def forward(self, x, c, *args, **kwargs): def apply_model(self, x_noisy, t, cond, return_ids=False): if isinstance(cond, dict): - # hybrid case, cond is exptected to be a dict + # hybrid case, cond is expected to be a dict pass else: if not isinstance(cond, list): @@ -916,7 +916,7 @@ def apply_model(self, x_noisy, t, cond, return_ids=False): cond_list = [{c_key: [c[:, :, :, :, i]]} for i in range(c.shape[-1])] elif self.cond_stage_key == 'coordinates_bbox': - assert 'original_image_size' in self.split_input_params, 'BoudingBoxRescaling is missing original_image_size' + assert 'original_image_size' in self.split_input_params, 'BoundingBoxRescaling is missing original_image_size' # assuming padding of unfold is always 0 and its dilation is always 1 n_patches_per_row = int((w - ks[0]) / stride[0] + 1) @@ -926,7 +926,7 @@ def apply_model(self, x_noisy, t, cond, return_ids=False): num_downs = self.first_stage_model.encoder.num_resolutions - 1 rescale_latent = 2 ** (num_downs) - # get top left postions of patches as conforming for the bbbox tokenizer, therefore we + # get top left positions of patches as conforming for the bbbox tokenizer, therefore we # need to rescale the tl patch coordinates to be in between (0,1) tl_patch_coordinates = [(rescale_latent * stride[0] * (patch_nr % n_patches_per_row) / full_img_w, rescale_latent * stride[1] * (patch_nr // n_patches_per_row) / full_img_h) diff --git a/extensions-builtin/Lora/extra_networks_lora.py b/extensions-builtin/Lora/extra_networks_lora.py index 005ff32cb..17a620f77 100644 --- a/extensions-builtin/Lora/extra_networks_lora.py +++ b/extensions-builtin/Lora/extra_networks_lora.py @@ -9,6 +9,8 @@ def __init__(self): self.errors = {} """mapping of network names to the number of errors the network had during operation""" + remove_symbols = str.maketrans('', '', ":,") + def activate(self, p, params_list): additional = shared.opts.sd_lora @@ -43,22 +45,15 @@ def activate(self, p, params_list): networks.load_networks(names, te_multipliers, unet_multipliers, dyn_dims) if shared.opts.lora_add_hashes_to_infotext: - network_hashes = [] - for item in networks.loaded_networks: - shorthash = item.network_on_disk.shorthash - if not shorthash: - continue - - alias = item.mentioned_name - if not alias: - continue + if not getattr(p, "is_hr_pass", False) or not hasattr(p, "lora_hashes"): + p.lora_hashes = {} - alias = alias.replace(":", "").replace(",", "") - - network_hashes.append(f"{alias}: {shorthash}") + for item in networks.loaded_networks: + if item.network_on_disk.shorthash and item.mentioned_name: + p.lora_hashes[item.mentioned_name.translate(self.remove_symbols)] = item.network_on_disk.shorthash - if network_hashes: - p.extra_generation_params["Lora hashes"] = ", ".join(network_hashes) + if p.lora_hashes: + p.extra_generation_params["Lora hashes"] = ', '.join(f'{k}: {v}' for k, v in p.lora_hashes.items()) def deactivate(self, p): if self.errors: diff --git a/extensions-builtin/Lora/network.py b/extensions-builtin/Lora/network.py index 5eb7de96b..89987438a 100644 --- a/extensions-builtin/Lora/network.py +++ b/extensions-builtin/Lora/network.py @@ -7,6 +7,7 @@ import torch.nn.functional as F from modules import sd_models, cache, errors, hashes, shared +import modules.models.sd3.mmdit NetworkWeights = namedtuple('NetworkWeights', ['network_key', 'sd_key', 'w', 'sd_module']) @@ -29,7 +30,6 @@ def __init__(self, name, filename): def read_metadata(): metadata = sd_models.read_metadata_from_safetensors(filename) - metadata.pop('ssmd_cover_images', None) # those are cover images, and they are too big to display in UI as text return metadata @@ -115,8 +115,17 @@ def __init__(self, net: Network, weights: NetworkWeights): self.sd_key = weights.sd_key self.sd_module = weights.sd_module - if hasattr(self.sd_module, 'weight'): + if isinstance(self.sd_module, modules.models.sd3.mmdit.QkvLinear): + s = self.sd_module.weight.shape + self.shape = (s[0] // 3, s[1]) + elif hasattr(self.sd_module, 'weight'): self.shape = self.sd_module.weight.shape + elif isinstance(self.sd_module, nn.MultiheadAttention): + # For now, only self-attn use Pytorch's MHA + # So assume all qkvo proj have same shape + self.shape = self.sd_module.out_proj.weight.shape + else: + self.shape = None self.ops = None self.extra_kwargs = {} @@ -146,6 +155,9 @@ def __init__(self, net: Network, weights: NetworkWeights): self.alpha = weights.w["alpha"].item() if "alpha" in weights.w else None self.scale = weights.w["scale"].item() if "scale" in weights.w else None + self.dora_scale = weights.w.get("dora_scale", None) + self.dora_norm_dims = len(self.shape) - 1 + def multiplier(self): if 'transformer' in self.sd_key[:20]: return self.network.te_multiplier @@ -160,6 +172,27 @@ def calc_scale(self): return 1.0 + def apply_weight_decompose(self, updown, orig_weight): + # Match the device/dtype + orig_weight = orig_weight.to(updown.dtype) + dora_scale = self.dora_scale.to(device=orig_weight.device, dtype=updown.dtype) + updown = updown.to(orig_weight.device) + + merged_scale1 = updown + orig_weight + merged_scale1_norm = ( + merged_scale1.transpose(0, 1) + .reshape(merged_scale1.shape[1], -1) + .norm(dim=1, keepdim=True) + .reshape(merged_scale1.shape[1], *[1] * self.dora_norm_dims) + .transpose(0, 1) + ) + + dora_merged = ( + merged_scale1 * (dora_scale / merged_scale1_norm) + ) + final_updown = dora_merged - orig_weight + return final_updown + def finalize_updown(self, updown, orig_weight, output_shape, ex_bias=None): if self.bias is not None: updown = updown.reshape(self.bias.shape) @@ -175,7 +208,12 @@ def finalize_updown(self, updown, orig_weight, output_shape, ex_bias=None): if ex_bias is not None: ex_bias = ex_bias * self.multiplier() - return updown * self.calc_scale() * self.multiplier(), ex_bias + updown = updown * self.calc_scale() + + if self.dora_scale is not None: + updown = self.apply_weight_decompose(updown, orig_weight) + + return updown * self.multiplier(), ex_bias def calc_updown(self, target): raise NotImplementedError() diff --git a/extensions-builtin/Lora/networks.py b/extensions-builtin/Lora/networks.py index 7a5a5269c..9ebeda6ec 100644 --- a/extensions-builtin/Lora/networks.py +++ b/extensions-builtin/Lora/networks.py @@ -1,3 +1,6 @@ +from __future__ import annotations +import gradio as gr +import logging import os import re @@ -26,6 +29,14 @@ def assign_network_names_to_compvis_modules(sd_model): pass +class BundledTIHash(str): + def __init__(self, hash_str): + self.hash = hash_str + + def __str__(self): + return self.hash if shared.opts.lora_bundled_ti_to_infotext else '' + + def load_network(name, network_on_disk): net = network.Network(name, network_on_disk) net.mtime = os.path.getmtime(network_on_disk.filename) @@ -46,6 +57,16 @@ def load_networks(names, te_multipliers=None, unet_multipliers=None, dyn_dims=No loaded_networks.clear() + unavailable_networks = [] + for name in names: + if name.lower() in forbidden_network_aliases and available_networks.get(name) is None: + unavailable_networks.append(name) + elif available_network_aliases.get(name) is None: + unavailable_networks.append(name) + + if unavailable_networks: + update_available_networks_by_names(unavailable_networks) + networks_on_disk = [available_networks.get(name, None) if name.lower() in forbidden_network_aliases else available_network_aliases.get(name, None) for name in names] if any(x is None for x in networks_on_disk): list_available_networks() @@ -84,6 +105,28 @@ def load_networks(names, te_multipliers=None, unet_multipliers=None, dyn_dims=No return +def allowed_layer_without_weight(layer): + if isinstance(layer, torch.nn.LayerNorm) and not layer.elementwise_affine: + return True + + return False + + +def store_weights_backup(weight): + if weight is None: + return None + + return weight.to(devices.cpu, copy=True) + + +def restore_weights_backup(obj, field, weight): + if weight is None: + setattr(obj, field, None) + return + + getattr(obj, field).copy_(weight) + + def network_restore_weights_from_backup(self: Union[torch.nn.Conv2d, torch.nn.Linear, torch.nn.GroupNorm, torch.nn.LayerNorm, torch.nn.MultiheadAttention]): pass @@ -140,21 +183,15 @@ def network_MultiheadAttention_load_state_dict(self, *args, **kwargs): pass -def list_available_networks(): - available_networks.clear() - available_network_aliases.clear() - forbidden_network_aliases.clear() - available_network_hash_lookup.clear() - forbidden_network_aliases.update({"none": 1, "Addams": 1}) - - os.makedirs(shared.cmd_opts.lora_dir, exist_ok=True) - +def process_network_files(names: list[str] | None = None): candidates = list(shared.walk_files(shared.cmd_opts.lora_dir, allowed_extensions=[".pt", ".ckpt", ".safetensors"])) for filename in candidates: if os.path.isdir(filename): continue - name = os.path.splitext(os.path.basename(filename))[0] + # if names is provided, only load networks with names in the list + if names and name not in names: + continue try: entry = network.NetworkOnDisk(name, filename) except OSError: # should catch FileNotFoundError and PermissionError etc. @@ -170,6 +207,22 @@ def list_available_networks(): available_network_aliases[entry.alias] = entry +def update_available_networks_by_names(names: list[str]): + process_network_files(names) + + +def list_available_networks(): + available_networks.clear() + available_network_aliases.clear() + forbidden_network_aliases.clear() + available_network_hash_lookup.clear() + forbidden_network_aliases.update({"none": 1, "Addams": 1}) + + os.makedirs(shared.cmd_opts.lora_dir, exist_ok=True) + + process_network_files() + + re_network_name = re.compile(r"(.*)\s*\([0-9a-fA-F]+\)") diff --git a/extensions-builtin/Lora/scripts/lora_script.py b/extensions-builtin/Lora/scripts/lora_script.py index daf870625..9e9e4ad8d 100644 --- a/extensions-builtin/Lora/scripts/lora_script.py +++ b/extensions-builtin/Lora/scripts/lora_script.py @@ -35,7 +35,8 @@ def before_ui(): "sd_lora": shared.OptionInfo("None", "Add network to prompt", gr.Dropdown, lambda: {"choices": ["None", *networks.available_networks]}, refresh=networks.list_available_networks), "lora_preferred_name": shared.OptionInfo("Alias from file", "When adding to prompt, refer to Lora by", gr.Radio, {"choices": ["Alias from file", "Filename"]}), "lora_add_hashes_to_infotext": shared.OptionInfo(True, "Add Lora hashes to infotext"), - "lora_show_all": shared.OptionInfo(False, "Always show all networks on the Lora page").info("otherwise, those detected as for incompatible version of Stable Diffusion will be hidden"), + "lora_bundled_ti_to_infotext": shared.OptionInfo(True, "Add Lora name as TI hashes for bundled Textual Inversion").info('"Add Textual Inversion hashes to infotext" needs to be enabled'), + "lora_filter_disabled": shared.OptionInfo(True, "Always show all networks on the Lora page").info("otherwise, those detected as for incompatible version of Stable Diffusion will be hidden"), "lora_hide_unknown_for_versions": shared.OptionInfo([], "Hide networks of unknown versions for model versions", gr.CheckboxGroup, {"choices": ["SD1", "SD2", "SDXL"]}), "lora_in_memory_limit": shared.OptionInfo(0, "Number of Lora networks to keep cached in memory", gr.Number, {"precision": 0}), "lora_not_found_warning_console": shared.OptionInfo(False, "Lora not found warning in console"), diff --git a/extensions-builtin/Lora/ui_edit_user_metadata.py b/extensions-builtin/Lora/ui_edit_user_metadata.py index 3160aecfa..b6c4d1c6a 100644 --- a/extensions-builtin/Lora/ui_edit_user_metadata.py +++ b/extensions-builtin/Lora/ui_edit_user_metadata.py @@ -21,10 +21,12 @@ def is_non_comma_tagset(tags): def build_tags(metadata): tags = {} - for _, tags_dict in metadata.get("ss_tag_frequency", {}).items(): - for tag, tag_count in tags_dict.items(): - tag = tag.strip() - tags[tag] = tags.get(tag, 0) + int(tag_count) + ss_tag_frequency = metadata.get("ss_tag_frequency", {}) + if ss_tag_frequency is not None and hasattr(ss_tag_frequency, 'items'): + for _, tags_dict in ss_tag_frequency.items(): + for tag, tag_count in tags_dict.items(): + tag = tag.strip() + tags[tag] = tags.get(tag, 0) + int(tag_count) if tags and is_non_comma_tagset(tags): new_tags = {} @@ -149,6 +151,8 @@ def generate_random_prompt_from_tags(self, tags): v = random.random() * max_count if count > v: + for x in "({[]})": + tag = tag.replace(x, '\\' + x) res.append(tag) return ", ".join(sorted(res)) diff --git a/extensions-builtin/Lora/ui_extra_networks_lora.py b/extensions-builtin/Lora/ui_extra_networks_lora.py index 28f82ea4b..35e71be3b 100644 --- a/extensions-builtin/Lora/ui_extra_networks_lora.py +++ b/extensions-builtin/Lora/ui_extra_networks_lora.py @@ -31,7 +31,7 @@ def create_item(self, name, index=None, enable_filter=True): "name": name, "filename": lora_on_disk.filename, "shorthash": lora_on_disk.shorthash, - "preview": self.find_preview(path), + "preview": self.find_preview(path) or self.find_embedded_preview(path, name, lora_on_disk.metadata), "description": self.find_description(path), "search_terms": search_terms, "local_preview": f"{path}.{shared.opts.samples_format}", @@ -60,7 +60,7 @@ def create_item(self, name, index=None, enable_filter=True): else: sd_version = lora_on_disk.sd_version - if shared.opts.lora_show_all or not enable_filter: + if shared.opts.lora_filter_disabled or not enable_filter or not shared.sd_model: pass elif sd_version == network.SdVersion.Unknown: model_version = network.SdVersion.SDXL if shared.sd_model.is_sdxl else network.SdVersion.SD2 if shared.sd_model.is_sd2 else network.SdVersion.SD1 diff --git a/extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js b/extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js deleted file mode 100644 index df60c1a17..000000000 --- a/extensions-builtin/canvas-zoom-and-pan/javascript/zoom.js +++ /dev/null @@ -1,968 +0,0 @@ -onUiLoaded(async() => { - const elementIDs = { - img2imgTabs: "#mode_img2img .tab-nav", - inpaint: "#img2maskimg", - inpaintSketch: "#inpaint_sketch", - rangeGroup: "#img2img_column_size", - sketch: "#img2img_sketch" - }; - const tabNameToElementId = { - "Inpaint sketch": elementIDs.inpaintSketch, - "Inpaint": elementIDs.inpaint, - "Sketch": elementIDs.sketch - }; - - - // Helper functions - // Get active tab - - /** - * Waits for an element to be present in the DOM. - */ - const waitForElement = (id) => new Promise(resolve => { - const checkForElement = () => { - const element = document.querySelector(id); - if (element) return resolve(element); - setTimeout(checkForElement, 100); - }; - checkForElement(); - }); - - function getActiveTab(elements, all = false) { - const tabs = elements.img2imgTabs.querySelectorAll("button"); - - if (all) return tabs; - - for (let tab of tabs) { - if (tab.classList.contains("selected")) { - return tab; - } - } - } - - // Get tab ID - function getTabId(elements) { - const activeTab = getActiveTab(elements); - return tabNameToElementId[activeTab.innerText]; - } - - // Wait until opts loaded - async function waitForOpts() { - for (; ;) { - if (window.opts && Object.keys(window.opts).length) { - return window.opts; - } - await new Promise(resolve => setTimeout(resolve, 100)); - } - } - - // Detect whether the element has a horizontal scroll bar - function hasHorizontalScrollbar(element) { - return element.scrollWidth > element.clientWidth; - } - - // Function for defining the "Ctrl", "Shift" and "Alt" keys - function isModifierKey(event, key) { - switch (key) { - case "Ctrl": - return event.ctrlKey; - case "Shift": - return event.shiftKey; - case "Alt": - return event.altKey; - default: - return false; - } - } - - // Check if hotkey is valid - function isValidHotkey(value) { - const specialKeys = ["Ctrl", "Alt", "Shift", "Disable"]; - return ( - (typeof value === "string" && - value.length === 1 && - /[a-z]/i.test(value)) || - specialKeys.includes(value) - ); - } - - // Normalize hotkey - function normalizeHotkey(hotkey) { - return hotkey.length === 1 ? "Key" + hotkey.toUpperCase() : hotkey; - } - - // Format hotkey for display - function formatHotkeyForDisplay(hotkey) { - return hotkey.startsWith("Key") ? hotkey.slice(3) : hotkey; - } - - // Create hotkey configuration with the provided options - function createHotkeyConfig(defaultHotkeysConfig, hotkeysConfigOpts) { - const result = {}; // Resulting hotkey configuration - const usedKeys = new Set(); // Set of used hotkeys - - // Iterate through defaultHotkeysConfig keys - for (const key in defaultHotkeysConfig) { - const userValue = hotkeysConfigOpts[key]; // User-provided hotkey value - const defaultValue = defaultHotkeysConfig[key]; // Default hotkey value - - // Apply appropriate value for undefined, boolean, or object userValue - if ( - userValue === undefined || - typeof userValue === "boolean" || - typeof userValue === "object" || - userValue === "disable" - ) { - result[key] = - userValue === undefined ? defaultValue : userValue; - } else if (isValidHotkey(userValue)) { - const normalizedUserValue = normalizeHotkey(userValue); - - // Check for conflicting hotkeys - if (!usedKeys.has(normalizedUserValue)) { - usedKeys.add(normalizedUserValue); - result[key] = normalizedUserValue; - } else { - console.error( - `Hotkey: ${formatHotkeyForDisplay( - userValue - )} for ${key} is repeated and conflicts with another hotkey. The default hotkey is used: ${formatHotkeyForDisplay( - defaultValue - )}` - ); - result[key] = defaultValue; - } - } else { - console.error( - `Hotkey: ${formatHotkeyForDisplay( - userValue - )} for ${key} is not valid. The default hotkey is used: ${formatHotkeyForDisplay( - defaultValue - )}` - ); - result[key] = defaultValue; - } - } - - return result; - } - - // Disables functions in the config object based on the provided list of function names - function disableFunctions(config, disabledFunctions) { - // Bind the hasOwnProperty method to the functionMap object to avoid errors - const hasOwnProperty = - Object.prototype.hasOwnProperty.bind(functionMap); - - // Loop through the disabledFunctions array and disable the corresponding functions in the config object - disabledFunctions.forEach(funcName => { - if (hasOwnProperty(funcName)) { - const key = functionMap[funcName]; - config[key] = "disable"; - } - }); - - // Return the updated config object - return config; - } - - /** - * The restoreImgRedMask function displays a red mask around an image to indicate the aspect ratio. - * If the image display property is set to 'none', the mask breaks. To fix this, the function - * temporarily sets the display property to 'block' and then hides the mask again after 300 milliseconds - * to avoid breaking the canvas. Additionally, the function adjusts the mask to work correctly on - * very long images. - */ - function restoreImgRedMask(elements) { - const mainTabId = getTabId(elements); - - if (!mainTabId) return; - - const mainTab = gradioApp().querySelector(mainTabId); - const img = mainTab.querySelector("img"); - const imageARPreview = gradioApp().querySelector("#imageARPreview"); - - if (!img || !imageARPreview) return; - - imageARPreview.style.transform = ""; - if (parseFloat(mainTab.style.width) > 865) { - const transformString = mainTab.style.transform; - const scaleMatch = transformString.match( - /scale\(([-+]?[0-9]*\.?[0-9]+)\)/ - ); - let zoom = 1; // default zoom - - if (scaleMatch && scaleMatch[1]) { - zoom = Number(scaleMatch[1]); - } - - imageARPreview.style.transformOrigin = "0 0"; - imageARPreview.style.transform = `scale(${zoom})`; - } - - if (img.style.display !== "none") return; - - img.style.display = "block"; - - setTimeout(() => { - img.style.display = "none"; - }, 400); - } - - const hotkeysConfigOpts = await waitForOpts(); - - // Default config - const defaultHotkeysConfig = { - canvas_hotkey_zoom: "Alt", - canvas_hotkey_adjust: "Ctrl", - canvas_hotkey_reset: "KeyR", - canvas_hotkey_fullscreen: "KeyS", - canvas_hotkey_move: "KeyF", - canvas_hotkey_overlap: "KeyO", - canvas_hotkey_shrink_brush: "KeyQ", - canvas_hotkey_grow_brush: "KeyW", - canvas_disabled_functions: [], - canvas_show_tooltip: true, - canvas_auto_expand: true, - canvas_blur_prompt: false, - }; - - const functionMap = { - "Zoom": "canvas_hotkey_zoom", - "Adjust brush size": "canvas_hotkey_adjust", - "Hotkey shrink brush": "canvas_hotkey_shrink_brush", - "Hotkey enlarge brush": "canvas_hotkey_grow_brush", - "Moving canvas": "canvas_hotkey_move", - "Fullscreen": "canvas_hotkey_fullscreen", - "Reset Zoom": "canvas_hotkey_reset", - "Overlap": "canvas_hotkey_overlap" - }; - - // Loading the configuration from opts - const preHotkeysConfig = createHotkeyConfig( - defaultHotkeysConfig, - hotkeysConfigOpts - ); - - // Disable functions that are not needed by the user - const hotkeysConfig = disableFunctions( - preHotkeysConfig, - preHotkeysConfig.canvas_disabled_functions - ); - - let isMoving = false; - let mouseX, mouseY; - let activeElement; - - const elements = Object.fromEntries( - Object.keys(elementIDs).map(id => [ - id, - gradioApp().querySelector(elementIDs[id]) - ]) - ); - const elemData = {}; - - // Apply functionality to the range inputs. Restore redmask and correct for long images. - const rangeInputs = elements.rangeGroup ? - Array.from(elements.rangeGroup.querySelectorAll("input")) : - [ - gradioApp().querySelector("#img2img_width input[type='range']"), - gradioApp().querySelector("#img2img_height input[type='range']") - ]; - - for (const input of rangeInputs) { - input?.addEventListener("input", () => restoreImgRedMask(elements)); - } - - function applyZoomAndPan(elemId, isExtension = true) { - const targetElement = gradioApp().querySelector(elemId); - - if (!targetElement) { - console.log("Element not found"); - return; - } - - targetElement.style.transformOrigin = "0 0"; - - elemData[elemId] = { - zoom: 1, - panX: 0, - panY: 0 - }; - let fullScreenMode = false; - - // Create tooltip - function createTooltip() { - const toolTipElemnt = - targetElement.querySelector(".image-container"); - const tooltip = document.createElement("div"); - tooltip.className = "canvas-tooltip"; - - // Creating an item of information - const info = document.createElement("i"); - info.className = "canvas-tooltip-info"; - info.textContent = ""; - - // Create a container for the contents of the tooltip - const tooltipContent = document.createElement("div"); - tooltipContent.className = "canvas-tooltip-content"; - - // Define an array with hotkey information and their actions - const hotkeysInfo = [ - { - configKey: "canvas_hotkey_zoom", - action: "Zoom canvas", - keySuffix: " + wheel" - }, - { - configKey: "canvas_hotkey_adjust", - action: "Adjust brush size", - keySuffix: " + wheel" - }, - {configKey: "canvas_hotkey_reset", action: "Reset zoom"}, - { - configKey: "canvas_hotkey_fullscreen", - action: "Fullscreen mode" - }, - {configKey: "canvas_hotkey_move", action: "Move canvas"}, - {configKey: "canvas_hotkey_overlap", action: "Overlap"} - ]; - - // Create hotkeys array with disabled property based on the config values - const hotkeys = hotkeysInfo.map(info => { - const configValue = hotkeysConfig[info.configKey]; - const key = info.keySuffix ? - `${configValue}${info.keySuffix}` : - configValue.charAt(configValue.length - 1); - return { - key, - action: info.action, - disabled: configValue === "disable" - }; - }); - - for (const hotkey of hotkeys) { - if (hotkey.disabled) { - continue; - } - - const p = document.createElement("p"); - p.innerHTML = `${hotkey.key} - ${hotkey.action}`; - tooltipContent.appendChild(p); - } - - // Add information and content elements to the tooltip element - tooltip.appendChild(info); - tooltip.appendChild(tooltipContent); - - // Add a hint element to the target element - toolTipElemnt.appendChild(tooltip); - } - - //Show tool tip if setting enable - if (hotkeysConfig.canvas_show_tooltip) { - createTooltip(); - } - - // In the course of research, it was found that the tag img is very harmful when zooming and creates white canvases. This hack allows you to almost never think about this problem, it has no effect on webui. - function fixCanvas() { - const activeTab = getActiveTab(elements).textContent.trim(); - - if (activeTab !== "img2img") { - const img = targetElement.querySelector(`${elemId} img`); - - if (img && img.style.display !== "none") { - img.style.display = "none"; - img.style.visibility = "hidden"; - } - } - } - - // Reset the zoom level and pan position of the target element to their initial values - function resetZoom() { - elemData[elemId] = { - zoomLevel: 1, - panX: 0, - panY: 0 - }; - - if (isExtension) { - targetElement.style.overflow = "hidden"; - } - - targetElement.isZoomed = false; - - fixCanvas(); - targetElement.style.transform = `scale(${elemData[elemId].zoomLevel}) translate(${elemData[elemId].panX}px, ${elemData[elemId].panY}px)`; - - const canvas = gradioApp().querySelector( - `${elemId} canvas[key="interface"]` - ); - - toggleOverlap("off"); - fullScreenMode = false; - - const closeBtn = targetElement.querySelector("button[aria-label='Remove Image']"); - if (closeBtn) { - closeBtn.addEventListener("click", resetZoom); - } - - if (canvas && isExtension) { - const parentElement = targetElement.closest('[id^="component-"]'); - if ( - canvas && - parseFloat(canvas.style.width) > parentElement.offsetWidth && - parseFloat(targetElement.style.width) > parentElement.offsetWidth - ) { - fitToElement(); - return; - } - - } - - if ( - canvas && - !isExtension && - parseFloat(canvas.style.width) > 865 && - parseFloat(targetElement.style.width) > 865 - ) { - fitToElement(); - return; - } - - targetElement.style.width = ""; - } - - // Toggle the zIndex of the target element between two values, allowing it to overlap or be overlapped by other elements - function toggleOverlap(forced = "") { - const zIndex1 = "0"; - const zIndex2 = "998"; - - targetElement.style.zIndex = - targetElement.style.zIndex !== zIndex2 ? zIndex2 : zIndex1; - - if (forced === "off") { - targetElement.style.zIndex = zIndex1; - } else if (forced === "on") { - targetElement.style.zIndex = zIndex2; - } - } - - // Adjust the brush size based on the deltaY value from a mouse wheel event - function adjustBrushSize( - elemId, - deltaY, - withoutValue = false, - percentage = 5 - ) { - const input = - gradioApp().querySelector( - `${elemId} input[aria-label='Brush radius']` - ) || - gradioApp().querySelector( - `${elemId} button[aria-label="Use brush"]` - ); - - if (input) { - input.click(); - if (!withoutValue) { - const maxValue = - parseFloat(input.getAttribute("max")) || 100; - const changeAmount = maxValue * (percentage / 100); - const newValue = - parseFloat(input.value) + - (deltaY > 0 ? -changeAmount : changeAmount); - input.value = Math.min(Math.max(newValue, 0), maxValue); - input.dispatchEvent(new Event("change")); - } - } - } - - // Reset zoom when uploading a new image - const fileInput = gradioApp().querySelector( - `${elemId} input[type="file"][accept="image/*"].svelte-116rqfv` - ); - fileInput.addEventListener("click", resetZoom); - - // Update the zoom level and pan position of the target element based on the values of the zoomLevel, panX and panY variables - function updateZoom(newZoomLevel, mouseX, mouseY) { - newZoomLevel = Math.max(0.1, Math.min(newZoomLevel, 15)); - - elemData[elemId].panX += - mouseX - (mouseX * newZoomLevel) / elemData[elemId].zoomLevel; - elemData[elemId].panY += - mouseY - (mouseY * newZoomLevel) / elemData[elemId].zoomLevel; - - targetElement.style.transformOrigin = "0 0"; - targetElement.style.transform = `translate(${elemData[elemId].panX}px, ${elemData[elemId].panY}px) scale(${newZoomLevel})`; - - toggleOverlap("on"); - if (isExtension) { - targetElement.style.overflow = "visible"; - } - - return newZoomLevel; - } - - // Change the zoom level based on user interaction - function changeZoomLevel(operation, e) { - if (isModifierKey(e, hotkeysConfig.canvas_hotkey_zoom)) { - e.preventDefault(); - - let zoomPosX, zoomPosY; - let delta = 0.2; - if (elemData[elemId].zoomLevel > 7) { - delta = 0.9; - } else if (elemData[elemId].zoomLevel > 2) { - delta = 0.6; - } - - zoomPosX = e.clientX; - zoomPosY = e.clientY; - - fullScreenMode = false; - elemData[elemId].zoomLevel = updateZoom( - elemData[elemId].zoomLevel + - (operation === "+" ? delta : -delta), - zoomPosX - targetElement.getBoundingClientRect().left, - zoomPosY - targetElement.getBoundingClientRect().top - ); - - targetElement.isZoomed = true; - } - } - - /** - * This function fits the target element to the screen by calculating - * the required scale and offsets. It also updates the global variables - * zoomLevel, panX, and panY to reflect the new state. - */ - - function fitToElement() { - //Reset Zoom - targetElement.style.transform = `translate(${0}px, ${0}px) scale(${1})`; - - let parentElement; - - if (isExtension) { - parentElement = targetElement.closest('[id^="component-"]'); - } else { - parentElement = targetElement.parentElement; - } - - - // Get element and screen dimensions - const elementWidth = targetElement.offsetWidth; - const elementHeight = targetElement.offsetHeight; - - const screenWidth = parentElement.clientWidth; - const screenHeight = parentElement.clientHeight; - - // Get element's coordinates relative to the parent element - const elementRect = targetElement.getBoundingClientRect(); - const parentRect = parentElement.getBoundingClientRect(); - const elementX = elementRect.x - parentRect.x; - - // Calculate scale and offsets - const scaleX = screenWidth / elementWidth; - const scaleY = screenHeight / elementHeight; - const scale = Math.min(scaleX, scaleY); - - const transformOrigin = - window.getComputedStyle(targetElement).transformOrigin; - const [originX, originY] = transformOrigin.split(" "); - const originXValue = parseFloat(originX); - const originYValue = parseFloat(originY); - - const offsetX = - (screenWidth - elementWidth * scale) / 2 - - originXValue * (1 - scale); - const offsetY = - (screenHeight - elementHeight * scale) / 2.5 - - originYValue * (1 - scale); - - // Apply scale and offsets to the element - targetElement.style.transform = `translate(${offsetX}px, ${offsetY}px) scale(${scale})`; - - // Update global variables - elemData[elemId].zoomLevel = scale; - elemData[elemId].panX = offsetX; - elemData[elemId].panY = offsetY; - - fullScreenMode = false; - toggleOverlap("off"); - } - - /** - * This function fits the target element to the screen by calculating - * the required scale and offsets. It also updates the global variables - * zoomLevel, panX, and panY to reflect the new state. - */ - - // Fullscreen mode - function fitToScreen() { - const canvas = gradioApp().querySelector( - `${elemId} canvas[key="interface"]` - ); - - if (!canvas) return; - - if (canvas.offsetWidth > 862 || isExtension) { - targetElement.style.width = (canvas.offsetWidth + 2) + "px"; - } - - if (isExtension) { - targetElement.style.overflow = "visible"; - } - - if (fullScreenMode) { - resetZoom(); - fullScreenMode = false; - return; - } - - //Reset Zoom - targetElement.style.transform = `translate(${0}px, ${0}px) scale(${1})`; - - // Get scrollbar width to right-align the image - const scrollbarWidth = - window.innerWidth - document.documentElement.clientWidth; - - // Get element and screen dimensions - const elementWidth = targetElement.offsetWidth; - const elementHeight = targetElement.offsetHeight; - const screenWidth = window.innerWidth - scrollbarWidth; - const screenHeight = window.innerHeight; - - // Get element's coordinates relative to the page - const elementRect = targetElement.getBoundingClientRect(); - const elementY = elementRect.y; - const elementX = elementRect.x; - - // Calculate scale and offsets - const scaleX = screenWidth / elementWidth; - const scaleY = screenHeight / elementHeight; - const scale = Math.min(scaleX, scaleY); - - // Get the current transformOrigin - const computedStyle = window.getComputedStyle(targetElement); - const transformOrigin = computedStyle.transformOrigin; - const [originX, originY] = transformOrigin.split(" "); - const originXValue = parseFloat(originX); - const originYValue = parseFloat(originY); - - // Calculate offsets with respect to the transformOrigin - const offsetX = - (screenWidth - elementWidth * scale) / 2 - - elementX - - originXValue * (1 - scale); - const offsetY = - (screenHeight - elementHeight * scale) / 2 - - elementY - - originYValue * (1 - scale); - - // Apply scale and offsets to the element - targetElement.style.transform = `translate(${offsetX}px, ${offsetY}px) scale(${scale})`; - - // Update global variables - elemData[elemId].zoomLevel = scale; - elemData[elemId].panX = offsetX; - elemData[elemId].panY = offsetY; - - fullScreenMode = true; - toggleOverlap("on"); - } - - // Handle keydown events - function handleKeyDown(event) { - // Disable key locks to make pasting from the buffer work correctly - if ((event.ctrlKey && event.code === 'KeyV') || (event.ctrlKey && event.code === 'KeyC') || event.code === "F5") { - return; - } - - // before activating shortcut, ensure user is not actively typing in an input field - if (!hotkeysConfig.canvas_blur_prompt) { - if (event.target.nodeName === 'TEXTAREA' || event.target.nodeName === 'INPUT') { - return; - } - } - - - const hotkeyActions = { - [hotkeysConfig.canvas_hotkey_reset]: resetZoom, - [hotkeysConfig.canvas_hotkey_overlap]: toggleOverlap, - [hotkeysConfig.canvas_hotkey_fullscreen]: fitToScreen, - [hotkeysConfig.canvas_hotkey_shrink_brush]: () => adjustBrushSize(elemId, 10), - [hotkeysConfig.canvas_hotkey_grow_brush]: () => adjustBrushSize(elemId, -10) - }; - - const action = hotkeyActions[event.code]; - if (action) { - event.preventDefault(); - action(event); - } - - if ( - isModifierKey(event, hotkeysConfig.canvas_hotkey_zoom) || - isModifierKey(event, hotkeysConfig.canvas_hotkey_adjust) - ) { - event.preventDefault(); - } - } - - // Get Mouse position - function getMousePosition(e) { - mouseX = e.offsetX; - mouseY = e.offsetY; - } - - // Simulation of the function to put a long image into the screen. - // We detect if an image has a scroll bar or not, make a fullscreen to reveal the image, then reduce it to fit into the element. - // We hide the image and show it to the user when it is ready. - - targetElement.isExpanded = false; - function autoExpand() { - const canvas = document.querySelector(`${elemId} canvas[key="interface"]`); - if (canvas) { - if (hasHorizontalScrollbar(targetElement) && targetElement.isExpanded === false) { - targetElement.style.visibility = "hidden"; - setTimeout(() => { - fitToScreen(); - resetZoom(); - targetElement.style.visibility = "visible"; - targetElement.isExpanded = true; - }, 10); - } - } - } - - targetElement.addEventListener("mousemove", getMousePosition); - - //observers - // Creating an observer with a callback function to handle DOM changes - const observer = new MutationObserver((mutationsList, observer) => { - for (let mutation of mutationsList) { - // If the style attribute of the canvas has changed, by observation it happens only when the picture changes - if (mutation.type === 'attributes' && mutation.attributeName === 'style' && - mutation.target.tagName.toLowerCase() === 'canvas') { - targetElement.isExpanded = false; - setTimeout(resetZoom, 10); - } - } - }); - - // Apply auto expand if enabled - if (hotkeysConfig.canvas_auto_expand) { - targetElement.addEventListener("mousemove", autoExpand); - // Set up an observer to track attribute changes - observer.observe(targetElement, {attributes: true, childList: true, subtree: true}); - } - - // Handle events only inside the targetElement - let isKeyDownHandlerAttached = false; - - function handleMouseMove() { - if (!isKeyDownHandlerAttached) { - document.addEventListener("keydown", handleKeyDown); - isKeyDownHandlerAttached = true; - - activeElement = elemId; - } - } - - function handleMouseLeave() { - if (isKeyDownHandlerAttached) { - document.removeEventListener("keydown", handleKeyDown); - isKeyDownHandlerAttached = false; - - activeElement = null; - } - } - - // Add mouse event handlers - targetElement.addEventListener("mousemove", handleMouseMove); - targetElement.addEventListener("mouseleave", handleMouseLeave); - - // Reset zoom when click on another tab - elements.img2imgTabs.addEventListener("click", resetZoom); - elements.img2imgTabs.addEventListener("click", () => { - // targetElement.style.width = ""; - if (parseInt(targetElement.style.width) > 865) { - setTimeout(fitToElement, 0); - } - }); - - targetElement.addEventListener("wheel", e => { - // change zoom level - const operation = e.deltaY > 0 ? "-" : "+"; - changeZoomLevel(operation, e); - - // Handle brush size adjustment with ctrl key pressed - if (isModifierKey(e, hotkeysConfig.canvas_hotkey_adjust)) { - e.preventDefault(); - - // Increase or decrease brush size based on scroll direction - adjustBrushSize(elemId, e.deltaY); - } - }); - - // Handle the move event for pan functionality. Updates the panX and panY variables and applies the new transform to the target element. - function handleMoveKeyDown(e) { - - // Disable key locks to make pasting from the buffer work correctly - if ((e.ctrlKey && e.code === 'KeyV') || (e.ctrlKey && event.code === 'KeyC') || e.code === "F5") { - return; - } - - // before activating shortcut, ensure user is not actively typing in an input field - if (!hotkeysConfig.canvas_blur_prompt) { - if (e.target.nodeName === 'TEXTAREA' || e.target.nodeName === 'INPUT') { - return; - } - } - - - if (e.code === hotkeysConfig.canvas_hotkey_move) { - if (!e.ctrlKey && !e.metaKey && isKeyDownHandlerAttached) { - e.preventDefault(); - document.activeElement.blur(); - isMoving = true; - } - } - } - - function handleMoveKeyUp(e) { - if (e.code === hotkeysConfig.canvas_hotkey_move) { - isMoving = false; - } - } - - document.addEventListener("keydown", handleMoveKeyDown); - document.addEventListener("keyup", handleMoveKeyUp); - - // Detect zoom level and update the pan speed. - function updatePanPosition(movementX, movementY) { - let panSpeed = 2; - - if (elemData[elemId].zoomLevel > 8) { - panSpeed = 3.5; - } - - elemData[elemId].panX += movementX * panSpeed; - elemData[elemId].panY += movementY * panSpeed; - - // Delayed redraw of an element - requestAnimationFrame(() => { - targetElement.style.transform = `translate(${elemData[elemId].panX}px, ${elemData[elemId].panY}px) scale(${elemData[elemId].zoomLevel})`; - toggleOverlap("on"); - }); - } - - function handleMoveByKey(e) { - if (isMoving && elemId === activeElement) { - updatePanPosition(e.movementX, e.movementY); - targetElement.style.pointerEvents = "none"; - - if (isExtension) { - targetElement.style.overflow = "visible"; - } - - } else { - targetElement.style.pointerEvents = "auto"; - } - } - - // Prevents sticking to the mouse - window.onblur = function() { - isMoving = false; - }; - - // Checks for extension - function checkForOutBox() { - const parentElement = targetElement.closest('[id^="component-"]'); - if (parentElement.offsetWidth < targetElement.offsetWidth && !targetElement.isExpanded) { - resetZoom(); - targetElement.isExpanded = true; - } - - if (parentElement.offsetWidth < targetElement.offsetWidth && elemData[elemId].zoomLevel == 1) { - resetZoom(); - } - - if (parentElement.offsetWidth < targetElement.offsetWidth && targetElement.offsetWidth * elemData[elemId].zoomLevel > parentElement.offsetWidth && elemData[elemId].zoomLevel < 1 && !targetElement.isZoomed) { - resetZoom(); - } - } - - if (isExtension) { - targetElement.addEventListener("mousemove", checkForOutBox); - } - - - window.addEventListener('resize', (e) => { - resetZoom(); - - if (isExtension) { - targetElement.isExpanded = false; - targetElement.isZoomed = false; - } - }); - - gradioApp().addEventListener("mousemove", handleMoveByKey); - - - } - - applyZoomAndPan(elementIDs.sketch, false); - applyZoomAndPan(elementIDs.inpaint, false); - applyZoomAndPan(elementIDs.inpaintSketch, false); - - // Make the function global so that other extensions can take advantage of this solution - const applyZoomAndPanIntegration = async(id, elementIDs) => { - const mainEl = document.querySelector(id); - if (id.toLocaleLowerCase() === "none") { - for (const elementID of elementIDs) { - const el = await waitForElement(elementID); - if (!el) break; - applyZoomAndPan(elementID); - } - return; - } - - if (!mainEl) return; - mainEl.addEventListener("click", async() => { - for (const elementID of elementIDs) { - const el = await waitForElement(elementID); - if (!el) break; - applyZoomAndPan(elementID); - } - }, {once: true}); - }; - - window.applyZoomAndPan = applyZoomAndPan; // Only 1 elements, argument elementID, for example applyZoomAndPan("#txt2img_controlnet_ControlNet_input_image") - - window.applyZoomAndPanIntegration = applyZoomAndPanIntegration; // for any extension - - /* - The function `applyZoomAndPanIntegration` takes two arguments: - - 1. `id`: A string identifier for the element to which zoom and pan functionality will be applied on click. - If the `id` value is "none", the functionality will be applied to all elements specified in the second argument without a click event. - - 2. `elementIDs`: An array of string identifiers for elements. Zoom and pan functionality will be applied to each of these elements on click of the element specified by the first argument. - If "none" is specified in the first argument, the functionality will be applied to each of these elements without a click event. - - Example usage: - applyZoomAndPanIntegration("#txt2img_controlnet", ["#txt2img_controlnet_ControlNet_input_image"]); - In this example, zoom and pan functionality will be applied to the element with the identifier "txt2img_controlnet_ControlNet_input_image" upon clicking the element with the identifier "txt2img_controlnet". - */ - - // More examples - // Add integration with ControlNet txt2img One TAB - // applyZoomAndPanIntegration("#txt2img_controlnet", ["#txt2img_controlnet_ControlNet_input_image"]); - - // Add integration with ControlNet txt2img Tabs - // applyZoomAndPanIntegration("#txt2img_controlnet",Array.from({ length: 10 }, (_, i) => `#txt2img_controlnet_ControlNet-${i}_input_image`)); - - // Add integration with Inpaint Anything - // applyZoomAndPanIntegration("None", ["#ia_sam_image", "#ia_sel_mask"]); -}); diff --git a/extensions-builtin/canvas-zoom-and-pan/scripts/hotkey_config.py b/extensions-builtin/canvas-zoom-and-pan/scripts/hotkey_config.py deleted file mode 100644 index 89b7c31f2..000000000 --- a/extensions-builtin/canvas-zoom-and-pan/scripts/hotkey_config.py +++ /dev/null @@ -1,17 +0,0 @@ -import gradio as gr -from modules import shared - -shared.options_templates.update(shared.options_section(('canvas_hotkey', "Canvas Hotkeys"), { - "canvas_hotkey_zoom": shared.OptionInfo("Alt", "Zoom canvas", gr.Radio, {"choices": ["Shift","Ctrl", "Alt"]}).info("If you choose 'Shift' you cannot scroll horizontally, 'Alt' can cause a little trouble in firefox"), - "canvas_hotkey_adjust": shared.OptionInfo("Ctrl", "Adjust brush size", gr.Radio, {"choices": ["Shift","Ctrl", "Alt"]}).info("If you choose 'Shift' you cannot scroll horizontally, 'Alt' can cause a little trouble in firefox"), - "canvas_hotkey_shrink_brush": shared.OptionInfo("Q", "Shrink the brush size"), - "canvas_hotkey_grow_brush": shared.OptionInfo("W", "Enlarge the brush size"), - "canvas_hotkey_move": shared.OptionInfo("F", "Moving the canvas").info("To work correctly in firefox, turn off 'Automatically search the page text when typing' in the browser settings"), - "canvas_hotkey_fullscreen": shared.OptionInfo("S", "Fullscreen Mode, maximizes the picture so that it fits into the screen and stretches it to its full width "), - "canvas_hotkey_reset": shared.OptionInfo("R", "Reset zoom and canvas positon"), - "canvas_hotkey_overlap": shared.OptionInfo("O", "Toggle overlap").info("Technical button, neededs for testing"), - "canvas_show_tooltip": shared.OptionInfo(True, "Enable tooltip on the canvas"), - "canvas_auto_expand": shared.OptionInfo(True, "Automatically expands an image that does not fit completely in the canvas area, similar to manually pressing the S and R buttons"), - "canvas_blur_prompt": shared.OptionInfo(False, "Take the focus off the prompt when working with a canvas"), - "canvas_disabled_functions": shared.OptionInfo(["Overlap"], "Disable function that you don't use", gr.CheckboxGroup, {"choices": ["Zoom","Adjust brush size","Hotkey enlarge brush","Hotkey shrink brush","Moving canvas","Fullscreen","Reset Zoom","Overlap"]}), -})) diff --git a/extensions-builtin/canvas-zoom-and-pan/style.css b/extensions-builtin/canvas-zoom-and-pan/style.css deleted file mode 100644 index 5d8054e65..000000000 --- a/extensions-builtin/canvas-zoom-and-pan/style.css +++ /dev/null @@ -1,66 +0,0 @@ -.canvas-tooltip-info { - position: absolute; - top: 10px; - left: 10px; - cursor: help; - background-color: rgba(0, 0, 0, 0.3); - width: 20px; - height: 20px; - border-radius: 50%; - display: flex; - align-items: center; - justify-content: center; - flex-direction: column; - - z-index: 100; -} - -.canvas-tooltip-info::after { - content: ''; - display: block; - width: 2px; - height: 7px; - background-color: white; - margin-top: 2px; -} - -.canvas-tooltip-info::before { - content: ''; - display: block; - width: 2px; - height: 2px; - background-color: white; -} - -.canvas-tooltip-content { - display: none; - background-color: #f9f9f9; - color: #333; - border: 1px solid #ddd; - padding: 15px; - position: absolute; - top: 40px; - left: 10px; - width: 250px; - font-size: 16px; - opacity: 0; - border-radius: 8px; - box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2); - - z-index: 100; -} - -.canvas-tooltip:hover .canvas-tooltip-content { - display: block; - animation: fadeIn 0.5s; - opacity: 1; -} - -@keyframes fadeIn { - from {opacity: 0;} - to {opacity: 1;} -} - -.styler { - overflow:inherit !important; -} \ No newline at end of file diff --git a/extensions-builtin/extra-options-section/scripts/extra_options_section.py b/extensions-builtin/extra-options-section/scripts/extra_options_section.py index 4c10d9c7d..a91bea4fa 100644 --- a/extensions-builtin/extra-options-section/scripts/extra_options_section.py +++ b/extensions-builtin/extra-options-section/scripts/extra_options_section.py @@ -1,7 +1,7 @@ import math import gradio as gr -from modules import scripts, shared, ui_components, ui_settings, infotext_utils +from modules import scripts, shared, ui_components, ui_settings, infotext_utils, errors from modules.ui_components import FormColumn @@ -42,7 +42,11 @@ def ui(self, is_img2img): setting_name = extra_options[index] with FormColumn(): - comp = ui_settings.create_setting_component(setting_name) + try: + comp = ui_settings.create_setting_component(setting_name) + except KeyError: + errors.report(f"Can't add extra options for {setting_name} in ui") + continue self.comps.append(comp) self.setting_names.append(setting_name) diff --git a/extensions-builtin/forge_legacy_preprocessors/install.py b/extensions-builtin/forge_legacy_preprocessors/install.py index 3a9bd1172..e20854acd 100644 --- a/extensions-builtin/forge_legacy_preprocessors/install.py +++ b/extensions-builtin/forge_legacy_preprocessors/install.py @@ -13,7 +13,7 @@ def comparable_version(version: str) -> Tuple: - return tuple(version.split(".")) + return tuple(map(int, version.split("."))) def get_installed_version(package: str) -> Optional[str]: diff --git a/scripts/processing_autosized_crop.py b/extensions-builtin/postprocessing-for-training/scripts/postprocessing_autosized_crop.py similarity index 97% rename from scripts/processing_autosized_crop.py rename to extensions-builtin/postprocessing-for-training/scripts/postprocessing_autosized_crop.py index 7e6749898..1e83de607 100644 --- a/scripts/processing_autosized_crop.py +++ b/extensions-builtin/postprocessing-for-training/scripts/postprocessing_autosized_crop.py @@ -1,64 +1,64 @@ -from PIL import Image - -from modules import scripts_postprocessing, ui_components -import gradio as gr - - -def center_crop(image: Image, w: int, h: int): - iw, ih = image.size - if ih / h < iw / w: - sw = w * ih / h - box = (iw - sw) / 2, 0, iw - (iw - sw) / 2, ih - else: - sh = h * iw / w - box = 0, (ih - sh) / 2, iw, ih - (ih - sh) / 2 - return image.resize((w, h), Image.Resampling.LANCZOS, box) - - -def multicrop_pic(image: Image, mindim, maxdim, minarea, maxarea, objective, threshold): - iw, ih = image.size - err = lambda w, h: 1 - (lambda x: x if x < 1 else 1 / x)(iw / ih / (w / h)) - wh = max(((w, h) for w in range(mindim, maxdim + 1, 64) for h in range(mindim, maxdim + 1, 64) - if minarea <= w * h <= maxarea and err(w, h) <= threshold), - key=lambda wh: (wh[0] * wh[1], -err(*wh))[::1 if objective == 'Maximize area' else -1], - default=None - ) - return wh and center_crop(image, *wh) - - -class ScriptPostprocessingAutosizedCrop(scripts_postprocessing.ScriptPostprocessing): - name = "Auto-sized crop" - order = 4020 - - def ui(self): - with ui_components.InputAccordion(False, label="Auto-sized crop") as enable: - gr.Markdown('Each image is center-cropped with an automatically chosen width and height.') - with gr.Row(): - mindim = gr.Slider(minimum=64, maximum=2048, step=8, label="Dimension lower bound", value=384, elem_id="postprocess_multicrop_mindim") - maxdim = gr.Slider(minimum=64, maximum=2048, step=8, label="Dimension upper bound", value=768, elem_id="postprocess_multicrop_maxdim") - with gr.Row(): - minarea = gr.Slider(minimum=64 * 64, maximum=2048 * 2048, step=1, label="Area lower bound", value=64 * 64, elem_id="postprocess_multicrop_minarea") - maxarea = gr.Slider(minimum=64 * 64, maximum=2048 * 2048, step=1, label="Area upper bound", value=640 * 640, elem_id="postprocess_multicrop_maxarea") - with gr.Row(): - objective = gr.Radio(["Maximize area", "Minimize error"], value="Maximize area", label="Resizing objective", elem_id="postprocess_multicrop_objective") - threshold = gr.Slider(minimum=0, maximum=1, step=0.01, label="Error threshold", value=0.1, elem_id="postprocess_multicrop_threshold") - - return { - "enable": enable, - "mindim": mindim, - "maxdim": maxdim, - "minarea": minarea, - "maxarea": maxarea, - "objective": objective, - "threshold": threshold, - } - - def process(self, pp: scripts_postprocessing.PostprocessedImage, enable, mindim, maxdim, minarea, maxarea, objective, threshold): - if not enable: - return - - cropped = multicrop_pic(pp.image, mindim, maxdim, minarea, maxarea, objective, threshold) - if cropped is not None: - pp.image = cropped - else: - print(f"skipped {pp.image.width}x{pp.image.height} image (can't find suitable size within error threshold)") +from PIL import Image + +from modules import scripts_postprocessing, ui_components +import gradio as gr + + +def center_crop(image: Image, w: int, h: int): + iw, ih = image.size + if ih / h < iw / w: + sw = w * ih / h + box = (iw - sw) / 2, 0, iw - (iw - sw) / 2, ih + else: + sh = h * iw / w + box = 0, (ih - sh) / 2, iw, ih - (ih - sh) / 2 + return image.resize((w, h), Image.Resampling.LANCZOS, box) + + +def multicrop_pic(image: Image, mindim, maxdim, minarea, maxarea, objective, threshold): + iw, ih = image.size + err = lambda w, h: 1 - (lambda x: x if x < 1 else 1 / x)(iw / ih / (w / h)) + wh = max(((w, h) for w in range(mindim, maxdim + 1, 64) for h in range(mindim, maxdim + 1, 64) + if minarea <= w * h <= maxarea and err(w, h) <= threshold), + key=lambda wh: (wh[0] * wh[1], -err(*wh))[::1 if objective == 'Maximize area' else -1], + default=None + ) + return wh and center_crop(image, *wh) + + +class ScriptPostprocessingAutosizedCrop(scripts_postprocessing.ScriptPostprocessing): + name = "Auto-sized crop" + order = 4020 + + def ui(self): + with ui_components.InputAccordion(False, label="Auto-sized crop") as enable: + gr.Markdown('Each image is center-cropped with an automatically chosen width and height.') + with gr.Row(): + mindim = gr.Slider(minimum=64, maximum=2048, step=8, label="Dimension lower bound", value=384, elem_id="postprocess_multicrop_mindim") + maxdim = gr.Slider(minimum=64, maximum=2048, step=8, label="Dimension upper bound", value=768, elem_id="postprocess_multicrop_maxdim") + with gr.Row(): + minarea = gr.Slider(minimum=64 * 64, maximum=2048 * 2048, step=1, label="Area lower bound", value=64 * 64, elem_id="postprocess_multicrop_minarea") + maxarea = gr.Slider(minimum=64 * 64, maximum=2048 * 2048, step=1, label="Area upper bound", value=640 * 640, elem_id="postprocess_multicrop_maxarea") + with gr.Row(): + objective = gr.Radio(["Maximize area", "Minimize error"], value="Maximize area", label="Resizing objective", elem_id="postprocess_multicrop_objective") + threshold = gr.Slider(minimum=0, maximum=1, step=0.01, label="Error threshold", value=0.1, elem_id="postprocess_multicrop_threshold") + + return { + "enable": enable, + "mindim": mindim, + "maxdim": maxdim, + "minarea": minarea, + "maxarea": maxarea, + "objective": objective, + "threshold": threshold, + } + + def process(self, pp: scripts_postprocessing.PostprocessedImage, enable, mindim, maxdim, minarea, maxarea, objective, threshold): + if not enable: + return + + cropped = multicrop_pic(pp.image, mindim, maxdim, minarea, maxarea, objective, threshold) + if cropped is not None: + pp.image = cropped + else: + print(f"skipped {pp.image.width}x{pp.image.height} image (can't find suitable size within error threshold)") diff --git a/scripts/postprocessing_caption.py b/extensions-builtin/postprocessing-for-training/scripts/postprocessing_caption.py similarity index 96% rename from scripts/postprocessing_caption.py rename to extensions-builtin/postprocessing-for-training/scripts/postprocessing_caption.py index 5592a8987..758222ae2 100644 --- a/scripts/postprocessing_caption.py +++ b/extensions-builtin/postprocessing-for-training/scripts/postprocessing_caption.py @@ -1,30 +1,30 @@ -from modules import scripts_postprocessing, ui_components, deepbooru, shared -import gradio as gr - - -class ScriptPostprocessingCeption(scripts_postprocessing.ScriptPostprocessing): - name = "Caption" - order = 4040 - - def ui(self): - with ui_components.InputAccordion(False, label="Caption") as enable: - option = gr.CheckboxGroup(value=["Deepbooru"], choices=["Deepbooru", "BLIP"], show_label=False) - - return { - "enable": enable, - "option": option, - } - - def process(self, pp: scripts_postprocessing.PostprocessedImage, enable, option): - if not enable: - return - - captions = [pp.caption] - - if "Deepbooru" in option: - captions.append(deepbooru.model.tag(pp.image)) - - if "BLIP" in option: - captions.append(shared.interrogator.interrogate(pp.image.convert("RGB"))) - - pp.caption = ", ".join([x for x in captions if x]) +from modules import scripts_postprocessing, ui_components, deepbooru, shared +import gradio as gr + + +class ScriptPostprocessingCeption(scripts_postprocessing.ScriptPostprocessing): + name = "Caption" + order = 4040 + + def ui(self): + with ui_components.InputAccordion(False, label="Caption") as enable: + option = gr.CheckboxGroup(value=["Deepbooru"], choices=["Deepbooru", "BLIP"], show_label=False) + + return { + "enable": enable, + "option": option, + } + + def process(self, pp: scripts_postprocessing.PostprocessedImage, enable, option): + if not enable: + return + + captions = [pp.caption] + + if "Deepbooru" in option: + captions.append(deepbooru.model.tag(pp.image)) + + if "BLIP" in option: + captions.append(shared.interrogator.interrogate(pp.image.convert("RGB"))) + + pp.caption = ", ".join([x for x in captions if x]) diff --git a/scripts/postprocessing_create_flipped_copies.py b/extensions-builtin/postprocessing-for-training/scripts/postprocessing_create_flipped_copies.py similarity index 97% rename from scripts/postprocessing_create_flipped_copies.py rename to extensions-builtin/postprocessing-for-training/scripts/postprocessing_create_flipped_copies.py index b673003b6..e7bd34038 100644 --- a/scripts/postprocessing_create_flipped_copies.py +++ b/extensions-builtin/postprocessing-for-training/scripts/postprocessing_create_flipped_copies.py @@ -1,32 +1,32 @@ -from PIL import ImageOps, Image - -from modules import scripts_postprocessing, ui_components -import gradio as gr - - -class ScriptPostprocessingCreateFlippedCopies(scripts_postprocessing.ScriptPostprocessing): - name = "Create flipped copies" - order = 4030 - - def ui(self): - with ui_components.InputAccordion(False, label="Create flipped copies") as enable: - with gr.Row(): - option = gr.CheckboxGroup(value=["Horizontal"], choices=["Horizontal", "Vertical", "Both"], show_label=False) - - return { - "enable": enable, - "option": option, - } - - def process(self, pp: scripts_postprocessing.PostprocessedImage, enable, option): - if not enable: - return - - if "Horizontal" in option: - pp.extra_images.append(ImageOps.mirror(pp.image)) - - if "Vertical" in option: - pp.extra_images.append(pp.image.transpose(Image.Transpose.FLIP_TOP_BOTTOM)) - - if "Both" in option: - pp.extra_images.append(pp.image.transpose(Image.Transpose.FLIP_TOP_BOTTOM).transpose(Image.Transpose.FLIP_LEFT_RIGHT)) +from PIL import ImageOps, Image + +from modules import scripts_postprocessing, ui_components +import gradio as gr + + +class ScriptPostprocessingCreateFlippedCopies(scripts_postprocessing.ScriptPostprocessing): + name = "Create flipped copies" + order = 4030 + + def ui(self): + with ui_components.InputAccordion(False, label="Create flipped copies") as enable: + with gr.Row(): + option = gr.CheckboxGroup(value=["Horizontal"], choices=["Horizontal", "Vertical", "Both"], show_label=False) + + return { + "enable": enable, + "option": option, + } + + def process(self, pp: scripts_postprocessing.PostprocessedImage, enable, option): + if not enable: + return + + if "Horizontal" in option: + pp.extra_images.append(ImageOps.mirror(pp.image)) + + if "Vertical" in option: + pp.extra_images.append(pp.image.transpose(Image.Transpose.FLIP_TOP_BOTTOM)) + + if "Both" in option: + pp.extra_images.append(pp.image.transpose(Image.Transpose.FLIP_TOP_BOTTOM).transpose(Image.Transpose.FLIP_LEFT_RIGHT)) diff --git a/scripts/postprocessing_focal_crop.py b/extensions-builtin/postprocessing-for-training/scripts/postprocessing_focal_crop.py similarity index 97% rename from scripts/postprocessing_focal_crop.py rename to extensions-builtin/postprocessing-for-training/scripts/postprocessing_focal_crop.py index cff1dbc54..08fd2ccfb 100644 --- a/scripts/postprocessing_focal_crop.py +++ b/extensions-builtin/postprocessing-for-training/scripts/postprocessing_focal_crop.py @@ -1,54 +1,54 @@ - -from modules import scripts_postprocessing, ui_components, errors -import gradio as gr - -from modules.textual_inversion import autocrop - - -class ScriptPostprocessingFocalCrop(scripts_postprocessing.ScriptPostprocessing): - name = "Auto focal point crop" - order = 4010 - - def ui(self): - with ui_components.InputAccordion(False, label="Auto focal point crop") as enable: - face_weight = gr.Slider(label='Focal point face weight', value=0.9, minimum=0.0, maximum=1.0, step=0.05, elem_id="postprocess_focal_crop_face_weight") - entropy_weight = gr.Slider(label='Focal point entropy weight', value=0.15, minimum=0.0, maximum=1.0, step=0.05, elem_id="postprocess_focal_crop_entropy_weight") - edges_weight = gr.Slider(label='Focal point edges weight', value=0.5, minimum=0.0, maximum=1.0, step=0.05, elem_id="postprocess_focal_crop_edges_weight") - debug = gr.Checkbox(label='Create debug image', elem_id="train_process_focal_crop_debug") - - return { - "enable": enable, - "face_weight": face_weight, - "entropy_weight": entropy_weight, - "edges_weight": edges_weight, - "debug": debug, - } - - def process(self, pp: scripts_postprocessing.PostprocessedImage, enable, face_weight, entropy_weight, edges_weight, debug): - if not enable: - return - - if not pp.shared.target_width or not pp.shared.target_height: - return - - dnn_model_path = None - try: - dnn_model_path = autocrop.download_and_cache_models() - except Exception: - errors.report("Unable to load face detection model for auto crop selection. Falling back to lower quality haar method.", exc_info=True) - - autocrop_settings = autocrop.Settings( - crop_width=pp.shared.target_width, - crop_height=pp.shared.target_height, - face_points_weight=face_weight, - entropy_points_weight=entropy_weight, - corner_points_weight=edges_weight, - annotate_image=debug, - dnn_model_path=dnn_model_path, - ) - - result, *others = autocrop.crop_image(pp.image, autocrop_settings) - - pp.image = result - pp.extra_images = [pp.create_copy(x, nametags=["focal-crop-debug"], disable_processing=True) for x in others] - + +from modules import scripts_postprocessing, ui_components, errors +import gradio as gr + +from modules.textual_inversion import autocrop + + +class ScriptPostprocessingFocalCrop(scripts_postprocessing.ScriptPostprocessing): + name = "Auto focal point crop" + order = 4010 + + def ui(self): + with ui_components.InputAccordion(False, label="Auto focal point crop") as enable: + face_weight = gr.Slider(label='Focal point face weight', value=0.9, minimum=0.0, maximum=1.0, step=0.05, elem_id="postprocess_focal_crop_face_weight") + entropy_weight = gr.Slider(label='Focal point entropy weight', value=0.15, minimum=0.0, maximum=1.0, step=0.05, elem_id="postprocess_focal_crop_entropy_weight") + edges_weight = gr.Slider(label='Focal point edges weight', value=0.5, minimum=0.0, maximum=1.0, step=0.05, elem_id="postprocess_focal_crop_edges_weight") + debug = gr.Checkbox(label='Create debug image', elem_id="train_process_focal_crop_debug") + + return { + "enable": enable, + "face_weight": face_weight, + "entropy_weight": entropy_weight, + "edges_weight": edges_weight, + "debug": debug, + } + + def process(self, pp: scripts_postprocessing.PostprocessedImage, enable, face_weight, entropy_weight, edges_weight, debug): + if not enable: + return + + if not pp.shared.target_width or not pp.shared.target_height: + return + + dnn_model_path = None + try: + dnn_model_path = autocrop.download_and_cache_models() + except Exception: + errors.report("Unable to load face detection model for auto crop selection. Falling back to lower quality haar method.", exc_info=True) + + autocrop_settings = autocrop.Settings( + crop_width=pp.shared.target_width, + crop_height=pp.shared.target_height, + face_points_weight=face_weight, + entropy_points_weight=entropy_weight, + corner_points_weight=edges_weight, + annotate_image=debug, + dnn_model_path=dnn_model_path, + ) + + result, *others = autocrop.crop_image(pp.image, autocrop_settings) + + pp.image = result + pp.extra_images = [pp.create_copy(x, nametags=["focal-crop-debug"], disable_processing=True) for x in others] + diff --git a/scripts/postprocessing_split_oversized.py b/extensions-builtin/postprocessing-for-training/scripts/postprocessing_split_oversized.py similarity index 97% rename from scripts/postprocessing_split_oversized.py rename to extensions-builtin/postprocessing-for-training/scripts/postprocessing_split_oversized.py index 133e199b8..888740e34 100644 --- a/scripts/postprocessing_split_oversized.py +++ b/extensions-builtin/postprocessing-for-training/scripts/postprocessing_split_oversized.py @@ -1,71 +1,71 @@ -import math - -from modules import scripts_postprocessing, ui_components -import gradio as gr - - -def split_pic(image, inverse_xy, width, height, overlap_ratio): - if inverse_xy: - from_w, from_h = image.height, image.width - to_w, to_h = height, width - else: - from_w, from_h = image.width, image.height - to_w, to_h = width, height - h = from_h * to_w // from_w - if inverse_xy: - image = image.resize((h, to_w)) - else: - image = image.resize((to_w, h)) - - split_count = math.ceil((h - to_h * overlap_ratio) / (to_h * (1.0 - overlap_ratio))) - y_step = (h - to_h) / (split_count - 1) - for i in range(split_count): - y = int(y_step * i) - if inverse_xy: - splitted = image.crop((y, 0, y + to_h, to_w)) - else: - splitted = image.crop((0, y, to_w, y + to_h)) - yield splitted - - -class ScriptPostprocessingSplitOversized(scripts_postprocessing.ScriptPostprocessing): - name = "Split oversized images" - order = 4000 - - def ui(self): - with ui_components.InputAccordion(False, label="Split oversized images") as enable: - with gr.Row(): - split_threshold = gr.Slider(label='Threshold', value=0.5, minimum=0.0, maximum=1.0, step=0.05, elem_id="postprocess_split_threshold") - overlap_ratio = gr.Slider(label='Overlap ratio', value=0.2, minimum=0.0, maximum=0.9, step=0.05, elem_id="postprocess_overlap_ratio") - - return { - "enable": enable, - "split_threshold": split_threshold, - "overlap_ratio": overlap_ratio, - } - - def process(self, pp: scripts_postprocessing.PostprocessedImage, enable, split_threshold, overlap_ratio): - if not enable: - return - - width = pp.shared.target_width - height = pp.shared.target_height - - if not width or not height: - return - - if pp.image.height > pp.image.width: - ratio = (pp.image.width * height) / (pp.image.height * width) - inverse_xy = False - else: - ratio = (pp.image.height * width) / (pp.image.width * height) - inverse_xy = True - - if ratio >= 1.0 or ratio > split_threshold: - return - - result, *others = split_pic(pp.image, inverse_xy, width, height, overlap_ratio) - - pp.image = result - pp.extra_images = [pp.create_copy(x) for x in others] - +import math + +from modules import scripts_postprocessing, ui_components +import gradio as gr + + +def split_pic(image, inverse_xy, width, height, overlap_ratio): + if inverse_xy: + from_w, from_h = image.height, image.width + to_w, to_h = height, width + else: + from_w, from_h = image.width, image.height + to_w, to_h = width, height + h = from_h * to_w // from_w + if inverse_xy: + image = image.resize((h, to_w)) + else: + image = image.resize((to_w, h)) + + split_count = math.ceil((h - to_h * overlap_ratio) / (to_h * (1.0 - overlap_ratio))) + y_step = (h - to_h) / (split_count - 1) + for i in range(split_count): + y = int(y_step * i) + if inverse_xy: + splitted = image.crop((y, 0, y + to_h, to_w)) + else: + splitted = image.crop((0, y, to_w, y + to_h)) + yield splitted + + +class ScriptPostprocessingSplitOversized(scripts_postprocessing.ScriptPostprocessing): + name = "Split oversized images" + order = 4000 + + def ui(self): + with ui_components.InputAccordion(False, label="Split oversized images") as enable: + with gr.Row(): + split_threshold = gr.Slider(label='Threshold', value=0.5, minimum=0.0, maximum=1.0, step=0.05, elem_id="postprocess_split_threshold") + overlap_ratio = gr.Slider(label='Overlap ratio', value=0.2, minimum=0.0, maximum=0.9, step=0.05, elem_id="postprocess_overlap_ratio") + + return { + "enable": enable, + "split_threshold": split_threshold, + "overlap_ratio": overlap_ratio, + } + + def process(self, pp: scripts_postprocessing.PostprocessedImage, enable, split_threshold, overlap_ratio): + if not enable: + return + + width = pp.shared.target_width + height = pp.shared.target_height + + if not width or not height: + return + + if pp.image.height > pp.image.width: + ratio = (pp.image.width * height) / (pp.image.height * width) + inverse_xy = False + else: + ratio = (pp.image.height * width) / (pp.image.width * height) + inverse_xy = True + + if ratio >= 1.0 or ratio > split_threshold: + return + + result, *others = split_pic(pp.image, inverse_xy, width, height, overlap_ratio) + + pp.image = result + pp.extra_images = [pp.create_copy(x) for x in others] + diff --git a/extensions-builtin/sd_forge_controlnet/install.py b/extensions-builtin/sd_forge_controlnet/install.py index 5370d2213..99d3f0f92 100644 --- a/extensions-builtin/sd_forge_controlnet/install.py +++ b/extensions-builtin/sd_forge_controlnet/install.py @@ -13,7 +13,7 @@ def comparable_version(version: str) -> Tuple: - return tuple(version.split(".")) + return tuple(map(int, version.split("."))) def get_installed_version(package: str) -> Optional[str]: diff --git a/extensions-builtin/sd_forge_controlnet/javascript/active_units.js b/extensions-builtin/sd_forge_controlnet/javascript/active_units.js index a3ba0fc3a..72c7ca95d 100644 --- a/extensions-builtin/sd_forge_controlnet/javascript/active_units.js +++ b/extensions-builtin/sd_forge_controlnet/javascript/active_units.js @@ -95,7 +95,6 @@ this.attachImageUploadListener(); this.attachImageStateChangeObserver(); this.attachA1111SendInfoObserver(); - this.attachPresetDropdownObserver(); this.attachAccordionStateObserver(); } @@ -119,7 +118,7 @@ */ getUnitHeaderTextElement() { return this.tab.querySelector( - `:nth-child(${this.tabIndex + 1}) span.svelte-s1r2yt` + `button > span:nth-child(1)` ); } @@ -192,16 +191,17 @@ unitHeader.appendChild(span); } getInputImageSrc() { - const img = this.inputImageGroup.querySelector('.cnet-image img'); - return img ? img.src : null; + const img = this.inputImageGroup.querySelector('.cnet-image .forge-image'); + return (img && img.src.startsWith('data')) ? img.src : null; } getPreprocessorPreviewImageSrc() { - const img = this.generatedImageGroup.querySelector('.cnet-image img'); - return img ? img.src : null; + const img = this.generatedImageGroup.querySelector('.cnet-image .forge-image'); + return (img && img.src.startsWith('data')) ? img.src : null; } getMaskImageSrc() { function isEmptyCanvas(canvas) { if (!canvas) return true; + if (canvas.width == 0 || canvas.height ==0) return true; const ctx = canvas.getContext('2d'); // Get the image data const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); @@ -216,14 +216,14 @@ } return isPureBlack; } - const maskImg = this.maskImageGroup.querySelector('.cnet-mask-image img'); + const maskImg = this.maskImageGroup.querySelector('.cnet-mask-image .forge-image'); // Hand-drawn mask on mask upload. - const handDrawnMaskCanvas = this.maskImageGroup.querySelector('.cnet-mask-image canvas[key="mask"]'); + const handDrawnMaskCanvas = this.maskImageGroup.querySelector('.cnet-mask-image .forge-drawing-canvas'); // Hand-drawn mask on input image upload. - const inputImageHandDrawnMaskCanvas = this.inputImageGroup.querySelector('.cnet-image canvas[key="mask"]'); + const inputImageHandDrawnMaskCanvas = this.inputImageGroup.querySelector('.cnet-image .forge-drawing-canvas'); if (!isEmptyCanvas(handDrawnMaskCanvas)) { return handDrawnMaskCanvas.toDataURL(); - } else if (maskImg) { + } else if (maskImg && maskImg.src.startsWith('data')) { return maskImg.src; } else if (!isEmptyCanvas(inputImageHandDrawnMaskCanvas)) { return inputImageHandDrawnMaskCanvas.toDataURL(); @@ -347,25 +347,6 @@ } } - attachPresetDropdownObserver() { - const presetDropDown = this.tab.querySelector('.cnet-preset-dropdown'); - - new MutationObserver((mutationsList) => { - for (const mutation of mutationsList) { - if (mutation.removedNodes.length > 0) { - setTimeout(() => { - this.updateActiveState(); - this.updateActiveUnitCount(); - this.updateActiveControlType(); - }, 1000); - return; - } - } - }).observe(presetDropDown, { - childList: true, - subtree: true, - }); - } /** * Observer that triggers when the ControlNetUnit's accordion(tab) closes. */ diff --git a/extensions-builtin/sd_forge_controlnet/lib_controlnet/controlnet_ui/controlnet_ui_group.py b/extensions-builtin/sd_forge_controlnet/lib_controlnet/controlnet_ui/controlnet_ui_group.py index b3e16df68..74f62b8d8 100644 --- a/extensions-builtin/sd_forge_controlnet/lib_controlnet/controlnet_ui/controlnet_ui_group.py +++ b/extensions-builtin/sd_forge_controlnet/lib_controlnet/controlnet_ui/controlnet_ui_group.py @@ -13,43 +13,44 @@ ) from lib_controlnet.logging import logger from lib_controlnet.controlnet_ui.openpose_editor import OpenposeEditor -from lib_controlnet.controlnet_ui.preset import ControlNetPresetUI -from lib_controlnet.controlnet_ui.tool_button import ToolButton from lib_controlnet.controlnet_ui.photopea import Photopea from lib_controlnet.enums import InputMode, HiResFixOption from modules import shared, script_callbacks from modules.ui_components import FormRow from modules_forge.forge_util import HWC3 from lib_controlnet.external_code import UiControlNetUnit +from modules.ui_components import ToolButton +from gradio_rangeslider import RangeSlider +from modules_forge.forge_canvas.canvas import ForgeCanvas @dataclass class A1111Context: """Contains all components from A1111.""" - img2img_batch_input_dir: Optional[gr.components.IOComponent] = None - img2img_batch_output_dir: Optional[gr.components.IOComponent] = None - txt2img_submit_button: Optional[gr.components.IOComponent] = None - img2img_submit_button: Optional[gr.components.IOComponent] = None + img2img_batch_input_dir = None + img2img_batch_output_dir = None + txt2img_submit_button = None + img2img_submit_button = None # Slider controls from A1111 WebUI. - txt2img_w_slider: Optional[gr.components.IOComponent] = None - txt2img_h_slider: Optional[gr.components.IOComponent] = None - img2img_w_slider: Optional[gr.components.IOComponent] = None - img2img_h_slider: Optional[gr.components.IOComponent] = None + txt2img_w_slider = None + txt2img_h_slider = None + img2img_w_slider = None + img2img_h_slider = None - img2img_img2img_tab: Optional[gr.components.IOComponent] = None - img2img_img2img_sketch_tab: Optional[gr.components.IOComponent] = None - img2img_batch_tab: Optional[gr.components.IOComponent] = None - img2img_inpaint_tab: Optional[gr.components.IOComponent] = None - img2img_inpaint_sketch_tab: Optional[gr.components.IOComponent] = None - img2img_inpaint_upload_tab: Optional[gr.components.IOComponent] = None + img2img_img2img_tab = None + img2img_img2img_sketch_tab = None + img2img_batch_tab = None + img2img_inpaint_tab = None + img2img_inpaint_sketch_tab = None + img2img_inpaint_upload_tab = None - img2img_inpaint_area: Optional[gr.components.IOComponent] = None - txt2img_enable_hr: Optional[gr.components.IOComponent] = None + img2img_inpaint_area = None + txt2img_enable_hr = None @property - def img2img_inpaint_tabs(self) -> Tuple[gr.components.IOComponent]: + def img2img_inpaint_tabs(self): return ( self.img2img_inpaint_tab, self.img2img_inpaint_sketch_tab, @@ -57,7 +58,7 @@ def img2img_inpaint_tabs(self) -> Tuple[gr.components.IOComponent]: ) @property - def img2img_non_inpaint_tabs(self) -> Tuple[gr.components.IOComponent]: + def img2img_non_inpaint_tabs(self): return ( self.img2img_img2img_tab, self.img2img_img2img_sketch_tab, @@ -81,7 +82,7 @@ def ui_initialized(self) -> bool: if name not in optional_components.values() ) - def set_component(self, component: gr.components.IOComponent): + def set_component(self, component): id_mapping = { "img2img_batch_input_dir": "img2img_batch_input_dir", "img2img_batch_output_dir": "img2img_batch_output_dir", @@ -187,16 +188,13 @@ def __init__( self.batch_image_dir = None self.merge_tab = None self.batch_input_gallery = None - self.merge_upload_button = None - self.merge_clear_button = None + self.batch_mask_gallery = None self.create_canvas = None self.canvas_width = None self.canvas_height = None self.canvas_create_button = None self.canvas_cancel_button = None self.open_new_canvas_button = None - self.webcam_enable = None - self.webcam_mirror = None self.send_dimen_button = None self.pixel_perfect = None self.preprocessor_preview = None @@ -207,6 +205,7 @@ def __init__( self.model = None self.refresh_models = None self.weight = None + self.timestep_range = None self.guidance_start = None self.guidance_end = None self.advanced = None @@ -217,7 +216,6 @@ def __init__( self.resize_mode = None self.use_preview_as_input = None self.openpose_editor = None - self.preset_panel = None self.upload_independent_img_in_img2img = None self.image_upload_panel = None self.save_detected_map = None @@ -249,43 +247,34 @@ def render(self, tabname: str, elem_id_tabname: str) -> None: with gr.Group(visible=not self.is_img2img) as self.image_upload_panel: self.save_detected_map = gr.Checkbox(value=True, visible=False) - with gr.Tabs(): + + with gr.Tabs(visible=True): with gr.Tab(label="Single Image") as self.upload_tab: with gr.Row(elem_classes=["cnet-image-row"], equal_height=True): with gr.Group(elem_classes=["cnet-input-image-group"]): - self.image = gr.Image( - source="upload", - brush_radius=20, - mirror_webcam=False, - type="numpy", - tool="sketch", + self.image = ForgeCanvas( elem_id=f"{elem_id_tabname}_{tabname}_input_image", elem_classes=["cnet-image"], - brush_color=shared.opts.img2img_inpaint_mask_brush_color - if hasattr( - shared.opts, "img2img_inpaint_mask_brush_color" - ) - else None, - ) - self.image.preprocess = functools.partial( - svg_preprocess, preprocess=self.image.preprocess + contrast_scribbles=True, + height=300, + numpy=True ) self.openpose_editor.render_upload() with gr.Group( - visible=False, elem_classes=["cnet-generated-image-group"] + visible=False, elem_classes=["cnet-generated-image-group"] ) as self.generated_image_group: - self.generated_image = gr.Image( - value=None, - label="Preprocessor Preview", + self.generated_image = ForgeCanvas( elem_id=f"{elem_id_tabname}_{tabname}_generated_image", elem_classes=["cnet-image"], - interactive=True, - height=242, - ) # Gradio's magic number. Only 242 works. + height=300, + no_scribbles=True, + no_upload=True, + numpy=True + ) with gr.Group( - elem_classes=["cnet-generated-image-control-group"] + elem_classes=["cnet-generated-image-control-group"] ): if self.photopea: self.photopea.render_child_trigger() @@ -299,22 +288,18 @@ def render(self, tabname: str, elem_id_tabname: str) -> None: ) with gr.Group( - visible=False, elem_classes=["cnet-mask-image-group"] + visible=False, elem_classes=["cnet-mask-image-group"] ) as self.mask_image_group: - self.mask_image = gr.Image( - value=None, - label="Mask", + self.mask_image = ForgeCanvas( elem_id=f"{elem_id_tabname}_{tabname}_mask_image", elem_classes=["cnet-mask-image"], - interactive=True, - brush_radius=20, - type="numpy", - tool="sketch", - brush_color=shared.opts.img2img_inpaint_mask_brush_color - if hasattr( - shared.opts, "img2img_inpaint_mask_brush_color" - ) - else None, + height=300, + scribble_color='#FFFFFF', + scribble_width=1, + scribble_alpha_fixed=True, + scribble_color_fixed=True, + scribble_softness_fixed=True, + numpy=True ) with gr.Tab(label="Batch Folder") as self.batch_tab: @@ -337,28 +322,14 @@ def render(self, tabname: str, elem_id_tabname: str) -> None: self.batch_input_gallery = gr.Gallery( columns=[4], rows=[2], object_fit="contain", height="auto", label="Images" ) - with gr.Row(): - self.merge_upload_button = gr.UploadButton( - "Upload Images", - file_types=["image"], - file_count="multiple", - ) - self.merge_clear_button = gr.Button("Clear Images") with gr.Group(visible=False, elem_classes=["cnet-mask-gallery-group"]) as self.batch_mask_gallery_group: with gr.Column(): self.batch_mask_gallery = gr.Gallery( columns=[4], rows=[2], object_fit="contain", height="auto", label="Masks" ) - with gr.Row(): - self.mask_merge_upload_button = gr.UploadButton( - "Upload Masks", - file_types=["image"], - file_count="multiple", - ) - self.mask_merge_clear_button = gr.Button("Clear Masks") if self.photopea: - self.photopea.attach_photopea_output(self.generated_image) + self.photopea.attach_photopea_output(self.generated_image.background) with gr.Accordion( label="Open New Canvas", visible=False @@ -397,23 +368,13 @@ def render(self, tabname: str, elem_id_tabname: str) -> None: self.open_new_canvas_button = ToolButton( value=ControlNetUiGroup.open_symbol, elem_id=f"{elem_id_tabname}_{tabname}_controlnet_open_new_canvas_button", + elem_classes=["cnet-toolbutton"], tooltip=ControlNetUiGroup.tooltips[ControlNetUiGroup.open_symbol], ) - self.webcam_enable = ToolButton( - value=ControlNetUiGroup.camera_symbol, - elem_id=f"{elem_id_tabname}_{tabname}_controlnet_webcam_enable", - tooltip=ControlNetUiGroup.tooltips[ControlNetUiGroup.camera_symbol], - ) - self.webcam_mirror = ToolButton( - value=ControlNetUiGroup.reverse_symbol, - elem_id=f"{elem_id_tabname}_{tabname}_controlnet_webcam_mirror", - tooltip=ControlNetUiGroup.tooltips[ - ControlNetUiGroup.reverse_symbol - ], - ) self.send_dimen_button = ToolButton( value=ControlNetUiGroup.tossup_symbol, elem_id=f"{elem_id_tabname}_{tabname}_controlnet_send_dimen_button", + elem_classes=["cnet-toolbutton"], tooltip=ControlNetUiGroup.tooltips[ControlNetUiGroup.tossup_symbol], ) @@ -481,7 +442,7 @@ def render(self, tabname: str, elem_id_tabname: str) -> None: value=ControlNetUiGroup.trigger_symbol, visible=not self.is_img2img, elem_id=f"{elem_id_tabname}_{tabname}_controlnet_trigger_preprocessor", - elem_classes=["cnet-run-preprocessor"], + elem_classes=["cnet-run-preprocessor", "cnet-toolbutton"], tooltip=ControlNetUiGroup.tooltips[ControlNetUiGroup.trigger_symbol], ) self.model = gr.Dropdown( @@ -493,6 +454,7 @@ def render(self, tabname: str, elem_id_tabname: str) -> None: self.refresh_models = ToolButton( value=ControlNetUiGroup.refresh_symbol, elem_id=f"{elem_id_tabname}_{tabname}_controlnet_refresh_models", + elem_classes=["cnet-toolbutton"], tooltip=ControlNetUiGroup.tooltips[ControlNetUiGroup.refresh_symbol], ) @@ -506,24 +468,22 @@ def render(self, tabname: str, elem_id_tabname: str) -> None: elem_id=f"{elem_id_tabname}_{tabname}_controlnet_control_weight_slider", elem_classes="controlnet_control_weight_slider", ) - self.guidance_start = gr.Slider( - label="Starting Control Step", - value=self.default_unit.guidance_start, - minimum=0.0, + self.timestep_range = RangeSlider( + label='Timestep Range', + minimum=0, maximum=1.0, - interactive=True, - elem_id=f"{elem_id_tabname}_{tabname}_controlnet_start_control_step_slider", - elem_classes="controlnet_start_control_step_slider", - ) - self.guidance_end = gr.Slider( - label="Ending Control Step", - value=self.default_unit.guidance_end, - minimum=0.0, - maximum=1.0, - interactive=True, - elem_id=f"{elem_id_tabname}_{tabname}_controlnet_ending_control_step_slider", - elem_classes="controlnet_ending_control_step_slider", + value=(self.default_unit.guidance_start, self.default_unit.guidance_end), + elem_id=f"{elem_id_tabname}_{tabname}_controlnet_control_step_slider", + elem_classes="controlnet_control_step_slider", ) + self.guidance_start = gr.State(self.default_unit.guidance_start) + self.guidance_end = gr.State(self.default_unit.guidance_end) + + self.timestep_range.change( + lambda x: (x[0], x[1]), + inputs=[self.timestep_range], + outputs=[self.guidance_start, self.guidance_end] + ) # advanced options with gr.Column(visible=False) as self.advanced: @@ -581,18 +541,6 @@ def render(self, tabname: str, elem_id_tabname: str) -> None: visible=False, ) - # self.loopback = gr.Checkbox( - # label="[Batch Loopback] Automatically send generated images to this ControlNet unit in batch generation", - # value=self.default_unit.loopback, - # elem_id=f"{elem_id_tabname}_{tabname}_controlnet_automatically_send_generated_images_checkbox", - # elem_classes="controlnet_loopback_checkbox", - # visible=False, - # ) - - self.preset_panel = ControlNetPresetUI( - id_prefix=f"{elem_id_tabname}_{tabname}_" - ) - self.batch_image_dir_state = gr.State("") self.output_dir_state = gr.State("") unit_args = ( @@ -602,14 +550,16 @@ def render(self, tabname: str, elem_id_tabname: str) -> None: self.batch_mask_dir, self.batch_input_gallery, self.batch_mask_gallery, - self.generated_image, - self.mask_image, + self.generated_image.background, + self.mask_image.background, + self.mask_image.foreground, self.hr_option, self.enabled, self.module, self.model, self.weight, - self.image, + self.image.background, + self.image.foreground, self.resize_mode, self.processor_res, self.threshold_a, @@ -665,41 +615,18 @@ def closesteight(num): else: return round(num + (8 - rem)) - if image: - interm = np.asarray(image.get("image")) - return closesteight(interm.shape[1]), closesteight(interm.shape[0]) + if image is not None: + return closesteight(image.shape[1]), closesteight(image.shape[0]) else: return gr.Slider.update(), gr.Slider.update() self.send_dimen_button.click( fn=send_dimensions, - inputs=[self.image], + inputs=[self.image.background], outputs=[self.width_slider, self.height_slider], show_progress=False, ) - def register_webcam_toggle(self): - def webcam_toggle(): - self.webcam_enabled = not self.webcam_enabled - return { - "value": None, - "source": "webcam" if self.webcam_enabled else "upload", - "__type__": "update", - } - - self.webcam_enable.click( - webcam_toggle, inputs=None, outputs=self.image, show_progress=False - ) - - def register_webcam_mirror_toggle(self): - def webcam_mirror_toggle(): - self.webcam_mirrored = not self.webcam_mirrored - return {"mirror_webcam": self.webcam_mirrored, "__type__": "update"} - - self.webcam_mirror.click( - webcam_mirror_toggle, inputs=None, outputs=self.image, show_progress=False - ) - def register_refresh_all_models(self): def refresh_all_models(): global_state.update_controlnet_filenames() @@ -799,16 +726,17 @@ def filter_selected(k: str): ) def register_run_annotator(self): - def run_annotator(image, module, pres, pthr_a, pthr_b, t2i_w, t2i_h, pp, rm): + def run_annotator(image, mask, module, pres, pthr_a, pthr_b, t2i_w, t2i_h, pp, rm): if image is None: return ( - gr.update(value=None, visible=True), + gr.update(visible=True), + None, gr.update(), *self.openpose_editor.update(""), ) - img = HWC3(image["image"]) - mask = HWC3(image["mask"]) + img = HWC3(image) + mask = HWC3(mask) if not (mask > 5).any(): mask = None @@ -862,8 +790,8 @@ def is_openpose(module: str): result = external_code.visualize_inpaint_mask(result) return ( - # Update to `generated_image` - gr.update(value=result, visible=True, interactive=False), + gr.update(visible=True), + result, # preprocessor_preview gr.update(value=True), # openpose editor @@ -873,7 +801,8 @@ def is_openpose(module: str): self.trigger_preprocessor.click( fn=run_annotator, inputs=[ - self.image, + self.image.background, + self.image.foreground, self.module, self.processor_res, self.threshold_a, @@ -884,7 +813,8 @@ def is_openpose(module: str): self.resize_mode, ], outputs=[ - self.generated_image, + self.generated_image.block, + self.generated_image.background, self.preprocessor_preview, *self.openpose_editor.outputs(), ], @@ -909,7 +839,7 @@ def shift_preview(is_on): fn=shift_preview, inputs=[self.preprocessor_preview], outputs=[ - self.generated_image, + self.generated_image.background, self.generated_image_group, self.use_preview_as_input, self.openpose_editor.download_link, @@ -920,27 +850,27 @@ def shift_preview(is_on): def register_create_canvas(self): self.open_new_canvas_button.click( - lambda: gr.Accordion.update(visible=True), + lambda: gr.update(visible=True), inputs=None, outputs=self.create_canvas, show_progress=False, ) self.canvas_cancel_button.click( - lambda: gr.Accordion.update(visible=False), + lambda: gr.update(visible=False), inputs=None, outputs=self.create_canvas, show_progress=False, ) def fn_canvas(h, w): - return np.zeros(shape=(h, w, 3), dtype=np.uint8), gr.Accordion.update( + return np.zeros(shape=(h, w, 3), dtype=np.uint8), gr.update( visible=False ) self.canvas_create_button.click( fn=fn_canvas, inputs=[self.canvas_height, self.canvas_width], - outputs=[self.image, self.create_canvas], + outputs=[self.image.background, self.create_canvas], show_progress=False, ) @@ -956,7 +886,7 @@ def fn_same_checked(x): fn_same_checked, inputs=self.upload_independent_img_in_img2img, outputs=[ - self.image, + self.image.background, self.batch_image_dir, self.preprocessor_preview, self.image_upload_panel, @@ -993,7 +923,7 @@ def on_checkbox_click(checked: bool, canvas_height: int, canvas_width: int): self.mask_upload.change( fn=on_checkbox_click, inputs=[self.mask_upload, self.height_slider, self.width_slider], - outputs=[self.mask_image_group, self.mask_image, self.batch_mask_dir, + outputs=[self.mask_image_group, self.mask_image.background, self.batch_mask_dir, self.batch_mask_gallery_group, self.batch_mask_gallery], show_progress=False, ) @@ -1073,106 +1003,27 @@ def clear_preview(x): event_subscriber( fn=clear_preview, inputs=self.use_preview_as_input, - outputs=[self.use_preview_as_input, self.generated_image], + outputs=[self.use_preview_as_input, self.generated_image.background], show_progress=False ) - def register_multi_images_upload(self): - """Register callbacks on merge tab multiple images upload.""" - self.merge_clear_button.click( - fn=lambda: [], - inputs=[], - outputs=[self.batch_input_gallery], - ).then( - fn=lambda x: gr.update(value=x + 1), - inputs=[self.dummy_gradio_update_trigger], - outputs=[self.dummy_gradio_update_trigger], - ) - self.mask_merge_clear_button.click( - fn=lambda: [], - inputs=[], - outputs=[self.batch_mask_gallery], - ).then( - fn=lambda x: gr.update(value=x + 1), - inputs=[self.dummy_gradio_update_trigger], - outputs=[self.dummy_gradio_update_trigger], - ) - - def upload_file(files, current_files): - return {file_d["name"] for file_d in current_files} | { - file.name for file in files - } - - self.merge_upload_button.upload( - upload_file, - inputs=[self.merge_upload_button, self.batch_input_gallery], - outputs=[self.batch_input_gallery], - queue=False, - ).then( - fn=lambda x: gr.update(value=x + 1), - inputs=[self.dummy_gradio_update_trigger], - outputs=[self.dummy_gradio_update_trigger], - ) - self.mask_merge_upload_button.upload( - upload_file, - inputs=[self.mask_merge_upload_button, self.batch_mask_gallery], - outputs=[self.batch_mask_gallery], - queue=False, - ).then( - fn=lambda x: gr.update(value=x + 1), - inputs=[self.dummy_gradio_update_trigger], - outputs=[self.dummy_gradio_update_trigger], - ) - return - def register_core_callbacks(self): """Register core callbacks that only involves gradio components defined within this ui group.""" - self.register_webcam_toggle() - self.register_webcam_mirror_toggle() self.register_refresh_all_models() self.register_build_sliders() self.register_shift_preview() self.register_create_canvas() self.register_clear_preview() - self.register_multi_images_upload() self.openpose_editor.register_callbacks( self.generated_image, self.use_preview_as_input, self.model, ) assert self.type_filter is not None - self.preset_panel.register_callbacks( - self, - self.type_filter, - *[ - getattr(self, key) - for key in external_code.ControlNetUnit.infotext_fields() - ], - ) if self.is_img2img: self.register_img2img_same_input() - def register_sd_model_changed(self): - def sd_version_changed(type_filter: str, current_model: str, setting_value: str, setting_name: str): - """When SD version changes, update model dropdown choices.""" - if setting_name != "sd_model_checkpoint": - return gr.update() - - filtered_model_list = global_state.get_filtered_controlnet_names(type_filter) - assert len(filtered_model_list) > 0 - default_model = filtered_model_list[1] if len(filtered_model_list) > 1 else filtered_model_list[0] - return gr.Dropdown.update( - choices=filtered_model_list, - value=current_model if current_model in filtered_model_list else default_model - ) - - script_callbacks.on_setting_updated_subscriber(dict( - fn=sd_version_changed, - inputs=[self.type_filter, self.model], - outputs=[self.model], - )) - def register_callbacks(self): """Register callbacks that involves A1111 context gradio components.""" # Prevent infinite recursion. @@ -1184,7 +1035,6 @@ def register_callbacks(self): self.register_run_annotator() self.register_sync_batch_dir() self.register_shift_upload_mask() - self.register_sd_model_changed() if self.is_img2img: self.register_shift_crop_input_image() else: diff --git a/extensions-builtin/sd_forge_controlnet/lib_controlnet/controlnet_ui/openpose_editor.py b/extensions-builtin/sd_forge_controlnet/lib_controlnet/controlnet_ui/openpose_editor.py index 4146018a1..a8a7e784c 100644 --- a/extensions-builtin/sd_forge_controlnet/lib_controlnet/controlnet_ui/openpose_editor.py +++ b/extensions-builtin/sd_forge_controlnet/lib_controlnet/controlnet_ui/openpose_editor.py @@ -112,7 +112,7 @@ def render_pose(pose_url: str) -> Tuple[Dict, Dict]: self.render_button.click( fn=render_pose, inputs=[self.pose_input], - outputs=[generated_image, use_preview_as_input, *self.outputs()], + outputs=[generated_image.background, use_preview_as_input, *self.outputs()], ) def update_upload_link(model: str) -> Dict: diff --git a/extensions-builtin/sd_forge_controlnet/lib_controlnet/controlnet_ui/preset.py b/extensions-builtin/sd_forge_controlnet/lib_controlnet/controlnet_ui/preset.py deleted file mode 100644 index 15a9f24ca..000000000 --- a/extensions-builtin/sd_forge_controlnet/lib_controlnet/controlnet_ui/preset.py +++ /dev/null @@ -1,313 +0,0 @@ -import os -import gradio as gr - -from typing import Dict, List - -from modules import scripts -from lib_controlnet.infotext import parse_unit, serialize_unit -from lib_controlnet.controlnet_ui.tool_button import ToolButton -from lib_controlnet.logging import logger -from lib_controlnet.external_code import ControlNetUnit, UiControlNetUnit -from lib_controlnet.global_state import get_preprocessor -from modules_forge.supported_preprocessor import Preprocessor - -save_symbol = "\U0001f4be" # 💾 -delete_symbol = "\U0001f5d1\ufe0f" # 🗑️ -refresh_symbol = "\U0001f504" # 🔄 -reset_symbol = "\U000021A9" # ↩ - -NEW_PRESET = "New Preset" - - -def load_presets(preset_dir: str) -> Dict[str, str]: - if not os.path.exists(preset_dir): - os.makedirs(preset_dir) - return {} - - presets = {} - for filename in os.listdir(preset_dir): - if filename.endswith(".txt"): - with open(os.path.join(preset_dir, filename), "r") as f: - name = filename.replace(".txt", "") - if name == NEW_PRESET: - continue - presets[name] = f.read() - return presets - - -def infer_control_type(module: str) -> str: - preprocessor: Preprocessor = get_preprocessor(module) - assert preprocessor is not None - return preprocessor.tags[0] if preprocessor.tags else "All" - - -class ControlNetPresetUI(object): - preset_directory = os.path.join(scripts.basedir(), "presets") - presets = load_presets(preset_directory) - - def __init__(self, id_prefix: str): - with gr.Row(): - self.dropdown = gr.Dropdown( - label="Presets", - show_label=True, - elem_classes=["cnet-preset-dropdown"], - choices=ControlNetPresetUI.dropdown_choices(), - value=NEW_PRESET, - ) - self.reset_button = ToolButton( - value=reset_symbol, - elem_classes=["cnet-preset-reset"], - tooltip="Reset preset", - visible=False, - ) - self.save_button = ToolButton( - value=save_symbol, - elem_classes=["cnet-preset-save"], - tooltip="Save preset", - ) - self.delete_button = ToolButton( - value=delete_symbol, - elem_classes=["cnet-preset-delete"], - tooltip="Delete preset", - ) - self.refresh_button = ToolButton( - value=refresh_symbol, - elem_classes=["cnet-preset-refresh"], - tooltip="Refresh preset", - ) - - with gr.Box( - elem_classes=["popup-dialog", "cnet-preset-enter-name"], - elem_id=f"{id_prefix}_cnet_preset_enter_name", - ) as self.name_dialog: - with gr.Row(): - self.preset_name = gr.Textbox( - label="Preset name", - show_label=True, - lines=1, - elem_classes=["cnet-preset-name"], - ) - self.confirm_preset_name = ToolButton( - value=save_symbol, - elem_classes=["cnet-preset-confirm-name"], - tooltip="Save preset", - ) - - def register_callbacks( - self, - uigroup, - control_type: gr.Radio, - *ui_states, - ): - def init_with_ui_states(*ui_states) -> ControlNetUnit: - return ControlNetUnit(**{ - field: value - for field, value in zip(ControlNetUnit.infotext_fields(), ui_states) - }) - - def apply_preset(name: str, control_type: str, *ui_states): - if name == NEW_PRESET: - return ( - gr.update(visible=False), - *( - (gr.skip(),) - * (len(ControlNetUnit.infotext_fields()) + 1) - ), - ) - - assert name in ControlNetPresetUI.presets - - infotext = ControlNetPresetUI.presets[name] - preset_unit = parse_unit(infotext) - current_unit = init_with_ui_states(*ui_states) - preset_unit.image = None - current_unit.image = None - - # Do not compare module param that are not used in preset. - for module_param in ("processor_res", "threshold_a", "threshold_b"): - if getattr(preset_unit, module_param) == -1: - setattr(current_unit, module_param, -1) - - # No update necessary. - if vars(current_unit) == vars(preset_unit): - return ( - gr.update(visible=False), - *( - (gr.skip(),) - * (len(ControlNetUnit.infotext_fields()) + 1) - ), - ) - - unit = preset_unit - - try: - new_control_type = infer_control_type(unit.module) - except ValueError as e: - logger.error(e) - new_control_type = control_type - - if new_control_type != control_type: - uigroup.prevent_next_n_module_update += 1 - - if preset_unit.module != current_unit.module: - uigroup.prevent_next_n_slider_value_update += 1 - - if preset_unit.pixel_perfect != current_unit.pixel_perfect: - uigroup.prevent_next_n_slider_value_update += 1 - - return ( - gr.update(visible=True), - gr.update(value=new_control_type), - *[ - gr.update(value=value) if value is not None else gr.update() - for field in ControlNetUnit.infotext_fields() - for value in (getattr(unit, field),) - ], - ) - - for element, action in ( - (self.dropdown, "change"), - (self.reset_button, "click"), - ): - getattr(element, action)( - fn=apply_preset, - inputs=[self.dropdown, control_type, *ui_states], - outputs=[self.delete_button, control_type, *ui_states], - show_progress="hidden", - ).then( - fn=lambda: gr.update(visible=False), - inputs=None, - outputs=[self.reset_button], - ) - - def save_preset(name: str, *ui_states): - if name == NEW_PRESET: - return gr.update(visible=True), gr.update(), gr.update() - - ControlNetPresetUI.save_preset( - name, init_with_ui_states(*ui_states) - ) - return ( - gr.update(), # name dialog - gr.update(choices=ControlNetPresetUI.dropdown_choices(), value=name), - gr.update(visible=False), # Reset button - ) - - self.save_button.click( - fn=save_preset, - inputs=[self.dropdown, *ui_states], - outputs=[self.name_dialog, self.dropdown, self.reset_button], - show_progress="hidden", - ).then( - fn=None, - _js=f""" - (name) => {{ - if (name === "{NEW_PRESET}") - popup(gradioApp().getElementById('{self.name_dialog.elem_id}')); - }}""", - inputs=[self.dropdown], - ) - - def delete_preset(name: str): - ControlNetPresetUI.delete_preset(name) - return gr.Dropdown.update( - choices=ControlNetPresetUI.dropdown_choices(), - value=NEW_PRESET, - ), gr.update(visible=False) - - self.delete_button.click( - fn=delete_preset, - inputs=[self.dropdown], - outputs=[self.dropdown, self.reset_button], - show_progress="hidden", - ) - - self.name_dialog.visible = False - - def save_new_preset(new_name: str, *ui_states): - if new_name == NEW_PRESET: - logger.warn(f"Cannot save preset with reserved name '{NEW_PRESET}'") - return gr.update(visible=False), gr.update() - - ControlNetPresetUI.save_preset( - new_name, init_with_ui_states(*ui_states) - ) - return gr.update(visible=False), gr.update( - choices=ControlNetPresetUI.dropdown_choices(), value=new_name - ) - - self.confirm_preset_name.click( - fn=save_new_preset, - inputs=[self.preset_name, *ui_states], - outputs=[self.name_dialog, self.dropdown], - show_progress="hidden", - ).then(fn=None, _js="closePopup") - - self.refresh_button.click( - fn=ControlNetPresetUI.refresh_preset, - inputs=None, - outputs=[self.dropdown], - show_progress="hidden", - ) - - def update_reset_button(preset_name: str, *ui_states): - if preset_name == NEW_PRESET: - return gr.update(visible=False) - - infotext = ControlNetPresetUI.presets[preset_name] - preset_unit = parse_unit(infotext) - current_unit = init_with_ui_states(*ui_states) - preset_unit.image = None - current_unit.image = None - - # Do not compare module param that are not used in preset. - for module_param in ("processor_res", "threshold_a", "threshold_b"): - if getattr(preset_unit, module_param) == -1: - setattr(current_unit, module_param, -1) - - return gr.update(visible=vars(current_unit) != vars(preset_unit)) - - for ui_state in ui_states: - if isinstance(ui_state, gr.Image): - continue - - for action in ("edit", "click", "change", "clear", "release"): - if action == "release" and not isinstance(ui_state, gr.Slider): - continue - - if hasattr(ui_state, action): - getattr(ui_state, action)( - fn=update_reset_button, - inputs=[self.dropdown, *ui_states], - outputs=[self.reset_button], - ) - - @staticmethod - def dropdown_choices() -> List[str]: - return list(ControlNetPresetUI.presets.keys()) + [NEW_PRESET] - - @staticmethod - def save_preset(name: str, unit: ControlNetUnit): - infotext = serialize_unit(unit) - with open( - os.path.join(ControlNetPresetUI.preset_directory, f"{name}.txt"), "w" - ) as f: - f.write(infotext) - - ControlNetPresetUI.presets[name] = infotext - - @staticmethod - def delete_preset(name: str): - if name not in ControlNetPresetUI.presets: - return - - del ControlNetPresetUI.presets[name] - - file = os.path.join(ControlNetPresetUI.preset_directory, f"{name}.txt") - if os.path.exists(file): - os.unlink(file) - - @staticmethod - def refresh_preset(): - ControlNetPresetUI.presets = load_presets(ControlNetPresetUI.preset_directory) - return gr.update(choices=ControlNetPresetUI.dropdown_choices()) diff --git a/extensions-builtin/sd_forge_controlnet/lib_controlnet/controlnet_ui/tool_button.py b/extensions-builtin/sd_forge_controlnet/lib_controlnet/controlnet_ui/tool_button.py deleted file mode 100644 index 8a38df8f4..000000000 --- a/extensions-builtin/sd_forge_controlnet/lib_controlnet/controlnet_ui/tool_button.py +++ /dev/null @@ -1,12 +0,0 @@ -import gradio as gr - -class ToolButton(gr.Button, gr.components.FormComponent): - """Small button with single emoji as text, fits inside gradio forms""" - - def __init__(self, **kwargs): - super().__init__(variant="tool", - elem_classes=kwargs.pop('elem_classes', []) + ["cnet-toolbutton"], - **kwargs) - - def get_block_name(self): - return "button" diff --git a/extensions-builtin/sd_forge_controlnet/lib_controlnet/external_code.py b/extensions-builtin/sd_forge_controlnet/lib_controlnet/external_code.py index 4954478ac..e7e769633 100644 --- a/extensions-builtin/sd_forge_controlnet/lib_controlnet/external_code.py +++ b/extensions-builtin/sd_forge_controlnet/lib_controlnet/external_code.py @@ -155,76 +155,31 @@ class GradioImageMaskPair(TypedDict): @dataclass class ControlNetUnit: - """Represents an entire ControlNet processing unit. - - To add a new field to this class - ## If the new field can be specified on UI, you need to - - Add a new field of the same name in constructor of `ControlNetUiGroup` - - Initialize the new `ControlNetUiGroup` field in `ControlNetUiGroup.render` - as a Gradio `IOComponent`. - - Add the new `ControlNetUiGroup` field to `unit_args` in - `ControlNetUiGroup.render`. The order of parameters matters. - - ## If the new field needs to appear in infotext, you need to - - Add a new item in `ControlNetUnit.infotext_fields`. - API-only fields cannot appear in infotext. - """ - # Following fields should only be used in the UI. - # ====== Start of UI only fields ====== - # Specifies the input mode for the unit, defaulting to a simple mode. input_mode: InputMode = InputMode.SIMPLE - # Determines whether to use the preview image as input; defaults to False. use_preview_as_input: bool = False - # Directory path for batch processing of images. batch_image_dir: str = '' - # Directory path for batch processing of masks. batch_mask_dir: str = '' - # Optional list of gallery images for batch input; defaults to None. batch_input_gallery: Optional[List[str]] = None - # Optional list of gallery masks for batch processing; defaults to None. batch_mask_gallery: Optional[List[str]] = None - # Holds the preview image as a NumPy array; defaults to None. generated_image: Optional[np.ndarray] = None - # ====== End of UI only fields ====== - - # Following fields are used in both the API and the UI. - # Holds the mask image; defaults to None. mask_image: Optional[GradioImageMaskPair] = None - # Specifies how this unit should be applied in each pass of high-resolution fix. - # Ignored if high-resolution fix is not enabled. + mask_image_fg: Optional[GradioImageMaskPair] = None hr_option: Union[HiResFixOption, int, str] = HiResFixOption.BOTH - # Indicates whether the unit is enabled; defaults to True. enabled: bool = True - # Name of the module being used; defaults to "None". module: str = "None" - # Name of the model being used; defaults to "None". model: str = "None" - # Weight of the unit in the overall processing; defaults to 1.0. weight: float = 1.0 - # Optional image for input; defaults to None. image: Optional[GradioImageMaskPair] = None - # Specifies the mode of image resizing; defaults to inner fit. + image_fg: Optional[GradioImageMaskPair] = None resize_mode: Union[ResizeMode, int, str] = ResizeMode.INNER_FIT - # Resolution for processing by the unit; defaults to -1 (unspecified). processor_res: int = -1 - # Threshold A for processing; defaults to -1 (unspecified). threshold_a: float = -1 - # Threshold B for processing; defaults to -1 (unspecified). threshold_b: float = -1 - # Start value for guidance; defaults to 0.0. guidance_start: float = 0.0 - # End value for guidance; defaults to 1.0. guidance_end: float = 1.0 - # Enables pixel-perfect processing; defaults to False. pixel_perfect: bool = False - # Control mode for the unit; defaults to balanced. control_mode: Union[ControlMode, int, str] = ControlMode.BALANCED - - # Following fields should only be used in the API. - # ====== Start of API only fields ====== - # Whether to save the detected map for this unit; defaults to True. save_detected_map: bool = True - # ====== End of API only fields ====== @staticmethod def infotext_fields(): diff --git a/extensions-builtin/sd_forge_controlnet/scripts/controlnet.py b/extensions-builtin/sd_forge_controlnet/scripts/controlnet.py index 08aa5ff2a..9dc4a1d0a 100644 --- a/extensions-builtin/sd_forge_controlnet/scripts/controlnet.py +++ b/extensions-builtin/sd_forge_controlnet/scripts/controlnet.py @@ -157,7 +157,7 @@ def get_input_data(self, p, unit, preprocessor, h, w): if unit.input_mode == external_code.InputMode.MERGE: for idx, item in enumerate(unit.batch_input_gallery): - img_path = item['name'] + img_path = item[0] logger.info(f'Try to read image: {img_path}') img = np.ascontiguousarray(cv2.imread(img_path)[:, :, ::-1]).copy() mask = None @@ -197,30 +197,36 @@ def get_input_data(self, p, unit, preprocessor, h, w): using_a1111_data = False + unit_image = unit.image + unit_image_fg = unit.image_fg[:, :, 3] if unit.image_fg is not None else None + if unit.use_preview_as_input and unit.generated_image is not None: image = unit.generated_image elif unit.image is None: resize_mode = external_code.resize_mode_from_value(p.resize_mode) image = HWC3(np.asarray(a1111_i2i_image)) using_a1111_data = True - elif (unit.image['image'] < 5).all() and (unit.image['mask'] > 5).any(): - image = unit.image['mask'] + elif (unit_image < 5).all() and (unit_image_fg > 5).any(): + image = unit_image_fg else: - image = unit.image['image'] + image = unit_image if not isinstance(image, np.ndarray): raise ValueError("controlnet is enabled but no input image is given") image = HWC3(image) + unit_mask_image = unit.mask_image + unit_mask_image_fg = unit.mask_image_fg[:, :, 3] if unit.mask_image_fg is not None else None + if using_a1111_data: mask = HWC3(np.asarray(a1111_i2i_mask)) if a1111_i2i_mask is not None else None - elif unit.mask_image is not None and (unit.mask_image['image'] > 5).any(): - mask = unit.mask_image['image'] - elif unit.mask_image is not None and (unit.mask_image['mask'] > 5).any(): - mask = unit.mask_image['mask'] - elif unit.image is not None and (unit.image['mask'] > 5).any(): - mask = unit.image['mask'] + elif unit_mask_image_fg is not None and (unit_mask_image_fg > 5).any(): + mask = unit_mask_image_fg + elif unit_mask_image is not None and (unit_mask_image > 5).any(): + mask = unit_mask_image + elif unit_image_fg is not None and (unit_image_fg > 5).any(): + mask = unit_image_fg else: mask = None diff --git a/extensions-builtin/sd_forge_controlnet/style.css b/extensions-builtin/sd_forge_controlnet/style.css index a27207411..35135c7c3 100644 --- a/extensions-builtin/sd_forge_controlnet/style.css +++ b/extensions-builtin/sd_forge_controlnet/style.css @@ -225,4 +225,27 @@ border-radius: var(--radius-sm); background: var(--background-fill-primary); color: var(--block-label-text-color); -} \ No newline at end of file +} + +.controlnet_control_type_filter_group label { + background: unset !important; + border: unset !important; + margin-left: -10px !important; +} + +.controlnet_control_type_filter_group > span { + display: none !important; +} + +.controlnet_control_type_filter_group > .wrap { + margin-top: -20px !important; +} + +.cnet-toolbutton { + background: unset !important; + border: unset !important; +} + +.range-slider { + margin-top: -8px; +} diff --git a/extensions-builtin/sd_forge_controlnet/tests/conftest.py b/extensions-builtin/sd_forge_controlnet/tests/conftest.py deleted file mode 100644 index c8792cd97..000000000 --- a/extensions-builtin/sd_forge_controlnet/tests/conftest.py +++ /dev/null @@ -1,7 +0,0 @@ -import os - - -def pytest_configure(config): - # We don't want to fail on Py.test command line arguments being - # parsed by webui: - os.environ.setdefault("IGNORE_CMD_ARGS_ERRORS", "1") diff --git a/extensions-builtin/sd_forge_controlnet/tests/images/1girl.png b/extensions-builtin/sd_forge_controlnet/tests/images/1girl.png deleted file mode 100644 index d825e716be9b40ddcce6987fdd8aab7d1e6871a0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 493039 zcmZsCby!=^@^^v;4=!zKa4E%I0|Y6>2~di=ySqcM;;s$uEfgv4Qlu>uE5)I>yT5$z zy+3=N_ph9to%zhp&g?lmvzsVYWjQ=-Dr^7%fcHiorVaq0JUyZUK>z$K*vnHsH7?v_ z-nls$yBOP>yP3PV0z~Z`U5th8OzjQ-c`$S}>XW9tj(U<-2dXRcXzY4dt&$>ikpkEgTwz|JTd&!4Y4wI zFndzU?BwX^V&@Gpb20XG_&4#tu}$pUU0l4Mcub7VAjS^HZjSc<;QU+BKeVU0baHfY zvvzcF{cj<2uP22NJ8OHBf6$*sXAW_D(gop$I9NM5***E^zpZ&P+|}B_5@PIP|G%jJ z0Wi1xUo;DM*QX&{JTX57#S;KO7boAp%yn~ggxDK9c>n+U+#O8q%#B?j=HCBH=|98% zkLmxV{y(s6kbi47v$nK$dm6gM(?mU~`bW(_LMD!8PgCLKZenfv1YqsL2C=j=c6hSU z+}PFI;Xl#E_P_0Qv;N1~4wi;y=H^cS#bM~dWhlV+Aj&&s3y zzbh4DY;0~~!Oz3bX(3=n%LdVU^4?Rh2tz!cT*%J(nn!^9wWXl3F&~!&zo7P@%q{>3 z0K9=o!o70B?zNlDhus;Ym54u&@(-2>Y~YDP6VN1ywk!m)o6ep1&XAowl^-9GTtyDn zTaph34K#CYZV*tAQ@9lAx9qAj@4GpNOoY%m*rGbOpa>QIci4V9n)Gk>(%s)!%B#-% zrAJHS(6EAvpdb)v1rrFD1j78TEd3}+fdE1hFn|!Nyry!(Y0@LRgm9CD6hJ5m1VFj` zMfAPy4$o*ILHM3hKlZ<~08j?vO&-!pI4b_m80`w6X#z+Iw`bxm@qq~mQ3x!=i2$5l zkJFEm%iuk*XARmGQxYh4iMtZG_8>jli9a@OKej4%<3nkxKo*X8|m+U|{0Q}fLUpXYut zWio@g+d|9!e1~o8VxeHEvZf@w50yEZ;9gNN{4*Px@-0K}1nIuipE;!enWXPUQM>4A z2a^U#y^s_EN{wzM=ox?`S~fBK6~NefDyA79k%*&9p_?*IX0DUO6&=>di-!F>%B?!R zaXY(YiLG^`$31ST3g<*I#NApUZ~tJd_T`>o=fjS6XItRzO-a>E>1KL--a(LvXhvrH z{lIaFbMz=;FIcnt6*gzdZ~>Kt(R}dw%Xrc!908JAddVWEJ zxSpH7AM(JiPm2hvjBD(m;~iF__k`L+0Ei6=uO?XaQ16P27Q+bSS!nzfPkZ2tV$Uat z@m@`H7Vn?8Yo##sc2)?OsE`(^-S5SMq6$^TDvU%@O^XJj6qHr4Qj#5_^3#S}7*RPX zI)xyK*ePDpuF^4sBKUAXD5+j_W~LmtUFoG-ISvR}8Hhf!AAsf$#EmXENn6VZfT8|t zmXwMk6wr&-kEp`xkVWL(QV{Zuj~HQ`Nc7N;#W%#+7g{4iy+NJaALUFX;hJE1QlQ|f z?x!H=d^H85{azX(hbne?8aqbGYaC_8srYCUw7z!s=K+7cIJq+mm*?+NEzB3 zX9J7TmmGt4I!Yx*oPr;-Wq?>R;j7Z&nqkAZw8c~NuLS!I%1QM-YpicZrhM9MS}Yd$ zcWZWkyE83bRyLgJcN&ChEV(bk;JMnGydXdoiU9xyNFc%E3JSVZoog4V?Kq07r8W>_ zvoTey&U=mw(uPSg0Sx4&$+?gy$&x1M=YZ;rjfvd6JP(zF+j`4|{?BgDoL`fLyG(fk zmW%c2f_!FbbF@tu36{;I%P?}wm{b8w1+OoL@XIhAX5X8c+F9ad^<)kI`o-}5Yl^bp zVew(>IA8X|$wYPfy$yPtu0hUAF`ekgJWM{m#3&;p^{9z822z z=f6{RXFN+Z%i9MRSpfC4z%mo44qUZ3NYI!aK`Z%X1rfz3!%uX~9c0kQKq0G6O2VVZ zL`8ALth5CfhM?Z8c%7JKr5g_8Fr)D9IQqYnB#tnCnDNRaI3uDF-JkVCTkkgF=m|^t zqKwLm4*iAJ@e!5TfKgP!9Hz35JNaId5X>?+^5aJIlsJ@%txCaX#|jVSg(j@W&0oe0 zz5A*N21EXgeiarko2(dCK&4xX-S-nl6oP{MdGsl|d%Z6P1Yl?_DWi&Ijxe6FT2R{h z{NoUh`@!*{H!vB%nyJA=L}-v0v|bT4I4D;fq#_UMf|;vP_4?27E4^CPV6xEaz;l}S zYC@0tZd`$4AmDDLHjtlmQ`9Ggw|VMswkT_&Xc(DtS8AogI9*;P6Bibi zSW{0Po8ldAHrk7|h+neEqsCw7@(w!p9cu8<&JQ=z1aMx{nbKu-2mBHoZRAuj{IgSK zB({bqHP3p{+-4`cPmUhBg?JzAKB=GgS~)(RLs?miX>flPsbAyQ>TFLp-dci*=8PS- zoND)RD2UlE9Gf$(8lBbe2r5GnCoH2zc9=cl&`v*Pqcv80KrT?D@4Fq|{!F`aJ2pJ$ z60$?u4n12>00a?|g21;W5`lL|XDj3*YnF8_Thk-*1K=OOx-f#FK4uU`_Gptvxkk3# z7S;-6O+`p12mtzWxhfV$`K!uz$K(NMM=9z2e#4>w1z@lG+Uqvy zTvQg%R}~yKINn%bRq)Pdc6x0dy;{myYJ2VPdA82gbblTinr_(Q+$x=bAvz~}()7IxxV#6D!7a*wsJk`ES z``sI58N%SGVNRI=urEJX39m%x+bjo(f(r4@8cs2Ip8xe%5+c0lJFx~II|AO8=!|A8 z21j6_NY?Xq#Z+3;7^V$DsD-r**~t#NyHR~sS znXA3SK;JBKUG3vP zOHy}dYC*yEecC6t)=Jw}9fL9DWeBSiX;R2mlkp0p@E~aN|Aj_|C@S2+x%Q{U%!?P&%>i|-CF-$5jUrXQ)mP*ECKGzc@O*Zhn$q9^UsZ9dtYtYj+$kWCx!8-8kML&%ghB>M1_El)n^k zeDRi}oilUHuU3B(=8xQZ!-dr;cMzR5M1CSOWz9`7+6f>GdBBN2!YRws{x&_14{ z!=mZ(XsgwR=#@;&I$_z;Askp3XE-+i*mYa9UCvrYc_|{b)jxFS#(9WUxhBn&6TH;o z$$fC_Ti&Yv+}UacR8!}aFN;$c7k;;B#0P-5o>UL>F}qSKHst}5)#z~foq1)TG)lSE zl^?Q1nCXultnuyn=+6-mjgIF(9Q9q>mUTUyeY+c`VWwhE{8DuCH zA_mG@LyeX4hXcl)jDe~~%!-@m$DvI5osWx!1dAepUbTj=2~)os@ycUhMd76qvol$} zWoLS)__a)lkEMdjUeeB9XLk5YhL)y2+*}9VZ>B!VSW-1wv|PcNY8kp?tHZ(ZauUKY^3{#yACw z6!y!uv8W0S)YXTcY91^y>0p?%RdW865>yF{^!t3))6e4|v)mksiE(J(5s340Q_ek% zW6)Ky)UI;W>R4#*a35>Q83z|RPJa(L&9*%3l<4$7Ev~)q-d%bS_X>PiUTa$Nf800h zaNR#zF6QWT-aL4XzAK`pNg!o>Fu+${_%%&lV?Y6v%o78ON;AejjV)DrPjLO6B4xyl zoKUKb%Q<{~k#SbpDX5U8w0lsgNH9CKI}t#l`HkppPKsJ7B-Z#%^v)w-549{_lQfo} zEefb(h%FccphAC+MuLu}yc2k1!<(FWsc=Q1W@e1N2DO#_A_~r$;s&6Z(P9%xd_*YV z-XSlE&IW$DOCFd0cotN~O7{kpX6w}Y3nxslZtAV)^sn%!I96sKu#^@wIYB}JOK3uX z|7VsElzNARTrHd;5)gj(b(WT5rRf9XjMqV&-o)$a&!mn+D1{*ZDRid=QArUA-u2s^SL=(nj`t_Wa)%9;Opv_H zZ^J1{=&@Z4pEpcw7=7VmUCN4LTr&OU#?c|MG)88om3380T;UX#MGCt{1sVl zvG^+F>(VAJqMMWZnnQW+o$ja-AI}u7efdxqMl4?=f3o#cYEfz~lu0H-)6V`uMSteD z#68>Gv+-<`T;s5obYO23p;JE|LqSi5xGw?f;RbQ-$;^_Sj~?h}dyd!g-&-uOlkc9J zGzhlq8R*rdX9u)it`jm4q#apDLlj|q(b1$3q z6l-kyc5c)q$cjVoyi-&dFzQW6AHc0qkryQB{6emVe9LCF*qPGCCx~9wrpKNl<6RgT zI;l*wy6+B_JTq2r=hLR`L9gH@y1<%ql1QO408Nh>m`aIx3NA1s5r5vo{JZf-E4bS6 z?>iI&`h=UR!$>&@{cuhkh_Jm3gqvs#JI1bneRRYEpas#eD02xZXoi7hlHSBz5T>Z2 zDTa*oZ6!~QMW@5)Bl`p+<=~o9-L9#4H=!ga%fdu0- z!4AfrBTj2@qy(}fDk{k=Ctkyq1P&NFz4!j5VrzO^^RtIQpzk<)JE~p{H;}~&U=*t^ z!N8xF=m$Hj{~4r4VqwO4iaS~m@4aHnj~k`Eon}hrW{MuC5W@P5piWRX@L^@}Wyk&W zq>99LdU{vifVx^T7ibTCn4~ddZaXjYxf5Tpj}4QEURobU1{j=)m6#Fq=D9uDeg8?$ z?*?=l{*q5-Ak}_U8r>LoYbBu7PE3r4dj}Bsw+AQ6%cTc~0la>V zc73w@8Z2(`_wTO_B6{ONYQvmlLt4=xIJzXM<(o3bamH>nI*y>kSrT_F_wv(W3+~H% zAJlOmAPIVsB@HDFb*+X_17Ngah*R{BsAuh{GT^cbWW~tvc-5|-Z>izyX>vJo^6$z5 zmNS;_ZkX5_qlZKt{XLEy*9BnR_agH6==Z+*x z4>$F4@rO&TfrpbE9t0N#KKC1yGqs;wK*>^#JJ9GX6Tzq-xZLcl6}XHNF3l-CJdB0_ zqK%A112LD;WZa~-XydQt7+k1pvA<^B%e+5Y=S1qhWiwZi1>jhbk=xu^p6E^LukOe| zds0wHvP3B|tT5rEGKoYn44|4YW_cTFl+Psf{gO!^zWITBro23K-NK;h zGZqt&jp#b+n=H$N@Mn1ZA~-^dB&V$)sR2u)B~I)#b6;}ulpwUQ z_%3AcPIQSTgZZ(kjp*N4R=}YQUzc)S2?zox>K-CW3>i?g>e{jZ%MZD!6V^hzes+Y# zenO;7XLOzU5+)W<1#6>gN-2o6W4Oi!;TrXT%S=GTbOrdQu*u%-ckv@aJ#>-`UVJP& zUu4n;f2)k=zcf~EmZQC5!RF$XOl~EUmpl{W+%PKkj?p#5?v3bO##I#N*Odw8jNAW_ z*R4h0rc#d|F;6ednbEv*>%BBshMAIwdMH1JIYtul0?$xhDBsx`jesF*B5{~j>jgzvyh7aenDOI7-FRU(bBu8Ii7U<=HZ z=W#xAw1BO4gb%Y_uuSzrMHM!OpZ8fpIXc|LMIGw9eKXJ$cldr=q;~7~c<$ok`Ejqq zY`w~d4cK~7R4zm z>T?8&V=-)`#-Z==;@hH62xEW3U{)dpz1d7=*+IHqk9Xcg)ih`bg0Cej zow0caXrTqI8{<~;N?NLK=xFHjUUjcYa)tq(ju6z^g;jfFS?K|v`P5!Xp`e|h+RzeG z@|V$qlu3XYSm2PKI1`kF)>d!fISHD_-yhcu%aF3CqGygKx#g~fPNX^#j1H9IuvksC zx|N#UmJ-r)PKw8)21$Ltaex`cSOJ1=Jx-{B?j3b-2|i7c+Xm2!kjDH~(MM2hm#6+r zE455iLPp|A4yD=IQJbJwO=o|R-1o$giSBd+z616UE(U$6mK-Kybz0wavgw<|p0^|> za90PJof}@1uS@D>wAUofQxmTbS@C30P~?#`gcL2Jt$vT`hwZ16`TC;XQb9(bIdwG@ zN=-FRJ>_q6e-tg={nAq!$wfs|FaDGdN`xk60021}=*MLM_-qCL)R&y^BX#V%QJGwt zv7<(+!Ch2;GEdw}E3nydDW$Z8#pdspnGD+=kLOBA&FUEH`9ik*mK^yz_JhK}KRH1V zR+^Y3gvKE{z$&EW$RAZ94eyg?6Z1~gYum=~B|VQy^CiAn{)wuF`Jg3LH6qnR5va60 zOjf4i_c%eVf$z%rV)wzy)P3*5_2PjUfB6su;#0dA6~9NsS!4r_DBhCER_Zi3x33)k z2vP0!7~dbTpAKlS^F92`IMaG6mmV`YGWJxd#@U})*qLa;Ir;#GQ*A$6vg^B?_%bdc z$-Fz9DqzrgdXilmuy1?1aI&|ZT!?vjJLPkDbG|F!s9d5!0w(=o>}X11rMN3kd&Tf2 zGI)C39a6FAtqF)es-S72fBgxhloEtWSDRk6Y=p2O#B&7$>0M6uSWsDM)P8?|UYkvl zm$34UFcG6kO6o&J{Kt1Dt58oh$)CZ3o3{f(glI;@YT22bSRz?7{AKb(hbRC}Nj()M z5QyXs0*6ZB6BL6Hj$US)qR`pTqyoB}M0Bh{1SrS~R*I4ht8f(z`$N-s&Y z=+>%)4AeACfh2?!FD)WK(Qb}H3VlE4uq{Zc+YHwiaOjUAhz5(Egp%rzK1M!XrE|@N zamLV;6C*nrkOe5i7;)F;T?C9(-;(f}>=S~}knhsp$#Sz{2@d`t;faVBvV~Xm_$k%% z%!5a%gJdyH5X9?Pd0gZXQI$Kb9hd&`6$ts!U}v*Q^4xLe1@i=I`Ny*B?ONR4xn~ov zr));!llt-%-`=R+m}bOtW54PTP%JT(cMJHiRjRepwH*;jaTRc+4wjSz1Yu>8rDrd- z-7mI2-mOjE#*_TYNSZA$hG6S%jhVe4z3{3t-4UAqHvYGyDh$`_^Hgjcux{sl)8bX5 zr#+@kik({Od0V5qFM@lk zkvdlH$PZbZzMUGkFNJQHanEywpCjySMdpBLO$O(?F;o3tZM89O>V9n(<&Bj_&eR%o z25j7%FWk4k&`s5}4$UY>w!32gv?m}#o_6ftS?p`YbnL6CM!!*;wrJmrSLV+p*QM3^ zBJ7yHIpR=nkemB;OufausUyXsumg3g*s;H%Lnr2t@RtzBGMan9?cb({WyAKfZ`mRu z^;7-B_Z;FL*<*{{4cQzWs-&r2FUzG%9#Ox)2`nAbEh`OTNn$NnjFIVeyXx<%mB4n zqqon+toGXx(Yx(s+&pT()A`41r3=^?`WMX9LPHcIG^PWu9AX>5F1SCCAwF+f8B~c) zwSjznBvOOkgpuMZDN`{%U<~}@Sp$>Hh%^dY5X9}X_L4}xKTyieAmL{LS6JVc=KQ#~ znk*Y0aWXL>=mj&j%`kYNpoTRG`{6q93Mc#%2TJHXaUl(IM-8$`)-9?s^VM7 z+>^(fs?ksrN-}!Ls^!_-`YJK^sD`}qasYELlz2=yB{Gi*`~=0MY^qDLWhGT_I$RfJ zP@%)9@&dE(!Khv~)NO)Ek0Am)30I@)6J*`9y2BGv3T<;c!?Vk0kR-Pm$X=u`(ciat z9sOE^_?=9c_6dkaDx3lZM8wSQ-(F7%|J(e)oAt@~AFs&KlW0t!bl6b4r`MWCS(dum z{PqVHTYiT3F;>IxpsKY2?MdS?g1<1nRm$j-iJba~vnaqzA*vLw@wAGMt$fs2t$YN5 zwd7Clvt*f8>y{Cv)@>XoUt-~AasRcA``yTT7w$QltNZ}(i{bXSngBBZFtq23c>7*9 zACc;xa_A6l!QwfsK}x@UlH+RiMu6*Aot$BdsQaWCs@aU~bDPZq;byf11L^aMp*~m$ z3@TcsP72%lJ+^a3Va_mCr_Xj1N7wBQyw$$AsCn_n=XhsoR~x@}PnnUF5c*s2@8p3B z`Z01>)AOrcww5S`u8UAT)8@hYu*;-~h%x&o{p*0WHrMPa0$j!lch%XdcmK`b5b40j{BQ{pi_*Vf&woNC7#~Uc;xkv@t4DEEsZhjZ81|A=0syWR5UoI)Fujo& z0wx~RAK8Jokw+)QR4l`ShlsdnX}zY#qk<@qNt9B!K|>$8;u?g`xafP;}#7Z z5`v(?r$0awb%wn*K}Eb!|4K2?r_(x}+E(g@%SDme_v4kJ_rqrDmkM!_25-7__H+F75f>-$+LpE^@66z7X=bvM71U4Gl{KKuQ)|A3TL!31Ik zqlFSe!KyUUc6h{urcf~LQH^D$5Z>=3^To) zF6p>E8PRsoW1vYb1KL_FMycb;5G55+mlP3H%03+mxX^Plc435Lnht;N4yJzHkGq!W z0-Zpi1=HE!r=ys)5cd^8dqan@qU|R9Bh?9MSq)@Tv4Au!09Mczu*>xpI0`jrODKO%Qfl}nUFQ53>3u-5Xb?Ov>@r$N$D3qsyh z;G;yV=%x^YhB;A!h_PhRPWMBSq^%M&%Cwh!H36aUEGqy>w-6yI@Jm>PJOthkMeCRN zPNbUfg{Xe2N`jdggTt5N*Cr<-ssc%0Vg=|c1*?(><=xuc7~J_MX!YR6iUJ0-<@a>y zDhCwti2@vy+MlaL^D6g=Y)Ya*bFY@o2x-)gY$I7(UcA40{UM(>dtG%fojY5_1I9yT z+K8I@C7#u$uhl|2wKkc}DX%?|&uG!_>iKfKLGTw>zWyjeh`l=Q_)F}qNhYSJD+0Ao zDVXkkKROo?=WIwbT4PW|GiNf3iUmAHPx))boEEey8nVp)wzC8342ob9@d!{oH2iE1 zL<3=j`^7^;Frw5n)t+k%MgS3suRMGk#&Lr@*=F5rs`guIw(m_g@^blJNgA6rFZVzw zHO)g$7qC$IvZuW_Wl%P41mdo!O+HJ(H{s8@&Uf8AMGw-y-)uT8hq_pH zRxws|4Sk%kGtML=)I|1GB8P^!#L*}|sq|5pr8u^!`)`gUPhpIw?{8cC`W>i@-yILn z%}pkcjBJ>E2oA$lM zzQFE>sZ<`_u?mx?8o;?fhkY$8_C#&k{S8t#?>xqIM(->}!&x8H1WsXc&Odb{y5B3IyjhY<8r%^UV5%51AW7Vj=_0d5$!#J-p7QN2#9HESni!b z0!EPq&(X4B*RE~Kr)@J_mmoMtO>?zQEp`wJO(k|=JL^vxwA0uv5K47VyqrLp6gF!IDrAh)~Lgm@5fmNaLm3_LmHQHvQNziku*Wsi^1> znhGQkOt9>WMQGO>Chdj}Mti(_+Tnh_c%dd6Ga7t4Y3+i-J-(iZ4q&VriAMac*-yp< zSl3T9lbr#yDG6wlQ(lXJ6KoT@c!K2@O_J)9fe5jROl(J`_vecuhux8N-L%L#0Io>! zU<@SjUagET{sauBzNTEu@btjoY~pVcdKRKq+c)@K`&rJj7fzy;P(d5$o~&YTn!z9# z{cX2c%DXQ*lIYv3F6S$)hn<&`hPmPoC@?8ltbKSRib+w@Zf1EYeAP07?jwaa5;uF|aPyH?OvkiGHjdq&2Kg7>;PDr0sb5m(>X7kdhChQqt{? z{5=cse4wvIS~gY4@Zk*!M`O3BlGJ46#CTX9 z8=f&>1vQi<#M%2aH=k|d2uawFbNs%IAI}pP5)&8hv=T-yv#AgCe^|xQK3o)U3vd^& z#H&wISQ6S@LAvLQTIW5#9q4wMW9Q(n*?oteX=lIR=hEHbOiMJ2tVnm5;$WYT(dZO+ zt>jr^^KZiT3H8kNd~vmSS^a5m!GERo@#1^OSwpQy&EwsqiZ=Pku7HC}p%uFj@Hi(QKilyLxZCrJ~+@^u&Ys*6Q^9#2L08FbGwl&0Q%;i!I6vgCI z2u>hEkAEZ19TYsUhf6C3Jf~gsR)xYWXnS+gN(hcezQofAzR-DtuSx+AWTG;4_z3$% zqoh*_9F+2LDmo&B{K*wM_3xT>iaqTxfD5evXc zPM}dxIc`u?%-bsC^bTI?I^b?$8v?5;f+UPPo_n&i~&#oo%_GahRWJGFy957X;A>E&f}@3%#hB;nj?xTX8d??}Sa+0W`KA{MUH zeyO66pc8V?;MK9?0HA8@DS(f*3RAkie{aMuJQ+9BFB&*BW6q~6<+3B?vVG%!Gks@2 zSTJ0AJUh+Z`57lt-|DF6y&e7vvz5kCz3?{5?0 zRNt#ZKp*+_-0JD}k+{^|)#O;Mku>MflA@2xfnmRm&((Pm((iGhs$o*8QC@gbXbD$= zG&IuX)b7m8uPQ!6+~5`%frh^Zam|LDs1A#1_(;H9zrLsv0!eENx};whNapm)Qv+$Z z{NgqQ>J+H=`pcGO$js`bg>V(gD?1enU5U3WP)yS_*;jsm-;kJrUUm{! z7Ddcr;(xuE)21+FcqKRo$Bhtne4KP{>K{}h#sg~XNaI@KC28LgCsM?TCSvesE5tKPXWnZqR%Js^U=L-W> zj2>+w_Sx>+Cec)jxDbyz4FhjyAFr8Fj|312SM#D`G;N3#!j3Rs@3W^>X6L!jd1|)I zVQLgsU+e6IYFH}rG^7d|t$a0CaXWQ?u^o!hM+f0%4!&F@C>QUN*GsXZfFb1d2+#n( z4D#$gEKXm9jr#dqO{{-=b-sf0j-+DNo5A*HSP=&<}^$v;9RInlWvYe6KpA)XlVL=!#g-U+VA4j@cBO{6@gcnhL5?9-whro zEHA|$mS-Fnyf>y^Y1c{~0zvX?&Zxal5zNFQL`MwP#AlvRrB9vg(5CqyDJ~l20K!b{6Qd?0G zi&mDz&u)bawc<#Uq{C*K!-=tq4okoT6d6aoR_Y^xYC<^)Y#9NR_Ms-^Hc~Jv>Wnap zHs24tj~CA=I}>sy%w!8Y2|1m4hk56BKC>4VzQW7{`^n05E+mLf4QQC3o&Q{pf0o@N zxK^gTwSzX|9k5xwT~5r2qXkY1*GP9P7b(Dc*Khot^ZFUdfdjGM292*Hv`Je;`Oe^{-hk2J(84A_j zzprC)4BX~}c(qPzrt#E&4^0*ec-|kC3NRX$Wlhby(wY@1aHKtu__|?__&}qpNJFg{fiT49& z_T-@9)xODE%k(Afv%`73^ava}la?_;2amBk01jWQyjev{&c0jyf`|8F_4tj)8%>?* z(~CR!@6_WqQJuT>&b#wo0*Sln&g&+}j++fj6>7hWVc+bHjTny33gxCoJSGn;1Q8A$ zZnkw2{Kb$1>>y#C=upguI2wa3g`9@93@{a_u z7QuRPCc@Lk=*+R_mPS*OC`67uV?9pQN96Nr$}H5P{vK{7}oQRY|Ka^<;D`P{UAjWT(X515L z7>f3v*W;W!*%2twHekK*NW|e7Dj_00R`?AeSqv}YfVnB!QNUk-xauzpP^#tC>dq9Z zMoFZqGAQvwJn@B9-a{%B{=6tAinq?xZnkzt9|a_Td-tp4w1AuAF8v^De&$lekd;Fj z|7Adc-{V7Je89zlT<62y?@CX{POq7#oiCKvf1cP|mr^6$h(efj^R5I4kfM<`_n#E7 zZ)ziH0uq_Nh0}AMACPLKlvK_4%RCX!Ho9M1r#m#Hcx>DrEJo(Pd~t8sahxmhaB=wf zuz2twm+#lCLLR8pk>-iZyM0oRMhHGcT2F8sQUt4Lo&||IECfKk}X|G#NH9oGz zI8x@CQB6=U@p=+_V!KA5)ZzLz(rr>mUUcC_C{w-CpDS245e&H$n=GJQ*6vUc-= z0~4|u?~erX?^ZD%-Oh$Fkv)w_WFNAmuB3w7d@PwID_Uvgc3eQIQ}j5dG`8zJ!2P_~ zWwR#tC7Dbdzz!FPLP@Ab2s<(OVI)>Plty!APSMP{7zJpq@jb3-_ig`SU@CORTjt$S zOl<_+Asy!*7jxb52`!*mKzXB?)AxmY^SPVCXo(pdpRD;yVcVsXEfqn%e~uNdij zAz={!mR~ZGtfZy6IvytK)(39hjawdyd0q7=)} zIv+m7)wX?gy=Sy{tA2K_-*k|G7_K0+rL3$w%oVKNspj(V{B6jN29p^UV#k^JQeedN z^w)urVFQtND>BO(U1=^Jk8k%ShB=oDKK_jl+kv++mhmqGE)DOu1v;-WI|Cl8FP}a= z9D%2vapk$EVIT2%74ZqX6a$d zkWoBI=>pAABMR78V}0}hDnpvH)8F+)S^#zK4|-;2fA$%|yL6D)ui(fLsT zyQ9Lsl*URG(44;I3QZ3Q$$HKYmGA6keoKM+j`3?C#j24b-M%IEqN>Lu(KH^fA6g|U zDK*+1*M;pLnOu@9#k|Rxq>Hp!(>w;@lOS3%rgBCH)U+Wq*|Fyi)oZu(yD8Zc1%YSL zN!pTO)1kYKI{&jvwODAV>HsbgdW7_X9*1P7`s|enz0he0 z(krzF=Q}IwRMD4WbW>P+w7PNZXXv`mwy&In>!pGi!=hFGwoX}>43}_{n1U8Ple&gb z!!A>G7%4mJj{jj_D>eOIo*d+0-axXlf;&P69igbk{H&5kbK;$-n4&ofX{e+p9bT;z z^WV3C9a3OF(X9HSUvYrP&+74Mx3Lr%|2wzIhLBjmP*sG|d{nxF0cr9znoKa?v_mwt zzi3bceY@D&BnG^I!2%VcO2W$$LA$8sB&5d!LRr_NODE?_N!{CTat+V&cUANRe&()n1)p{@y?eM0=uuW%Opk(Nca z@$8YwMLxUIeuM7a@Od_P%lAd^+pV9Wj`zp;e!HHX{*U!fQJMTQV8gakWI$!qPCsi8ge#O;Yi%%Pfy5>kJ)earm5rF; z)Cw{$CZEEcwxiq%boSy$)M=u9NjylvWC*@Nq&Zxp_Wt>y$e84kyQolyf$RJi&FN>Z z{5;tBtZ;iQ?4X;Z^vOjDnHG#6T3)VJN0bZNI3ygMh^i2CXSyp!{xq5GNZQ~~%H{FM zo7hOEiWn9&t9nT`dbECgv;Dadch?9+!bj4zvrzhS;dXB6ajiFg5tTrXa-GOd#@zg` zNdKZZDe+&0U$_0Rcn@g*~)zIL^s^}yTjtXiITQMO6U#1e? zAf!PX`KG8hjf|S=;SUt9H-|aS%+b$xJ2+{WZwwF_xH4p{#@&?-7>UB0qzM_RsxpZ~ z=g|?^Be%rDVi5u8w+xT9Pgg;0@i@%`t*?u|Etk7tiVdlZeLpM@%Y|6nLK{;mA2&g?QOP=u zaCKU|1+ z5o$*~%xM>g%GgJcn`mT^+<@n)@)%QsBcqwg!A^d7x3_QjxK$csNk9mE8)H?#>?Qo_ zvNoe#jGGkutTN_c{WIG*Nncjac&#?3XXV1PcW^sg3REjXUTU;$AH{2>ReyOT=4aZD z-QN{#b7W&kO%DI1CVOqdb|2q50ftR~;|xl?LG_uB@tL3C??k*>j?+gTOcun&_QgAz zbo8sf($mWT%ud?LnbKabP(cqkk$K(oyZoJC3`R7pjTmpK02u>%m{pb)^ts@sA(vB5gp6WO#Q z&$c_LIQ5WC)L$vXvPQ3GFX6V5{|}x(VZYC>=t@qs2U8)!4G1HMo6L|3EWx{ znK_r!5mith9E&9Ms;GyMT#%*z2*;|F?wfS5xXl{UUojG`0O3ISczX$7vLyc$sM4XNA8Xsx|eVE$v+wMTX zP#qkBf%$H;{L%G>*%OT>rrryNJ(sPH;iZ_`fm;vpoTU%r1`wm~iEt~X(yxJQNb)!% zAS0wS2uNHVQ^ni#Eb$BJHBA$Hn^lc^e;`kTmpxeKtsw}L!mB>~{Nc}k^pnqj^>({j z5Hp$*$WT$1hgXZkt7g83Wct;zdG^SiJtg0xt?cZ2v2&x?xmiqR##$J^{|P-9B~K_Wt<-g{Y#QxI)^*P|m;=Tr&Hg5JEd^X)gL(+Ub=K~f|Y&j4(wWKxB; zmk$>D{uB54W4GG4=3>2Ct=q0aBwKRG&ol^1SeKO{CJu>x>YVf5g>*e40ub>i0wRir zi7^zuLI71^VnkMOAv4YvMpVR02&n}Is$Ly{DzpKndic@1tFxv3`t|;^^X_VC-?+W= z$$PjqRF$c#$>Hr{dbmD$vN=DJZq3!4i^{DoyepdX%fJ1<|6gDGz2E=lcfR}Z ziw{BZ%H6wt=KugxT4w^&xEm;-F-BB*j(WvuI&y=vyNr3ZhgqG%Z$Xzq0wfGdOH6@5 zP{`-D1Xzsq5#!QUfOK?G%FW0I1p!msBA^gQmqJNNC4wy?F$K0v;zcxxSSo|IrP(i0 zNd^XE>1GWlat27@xF0&Q*?7DdB?^eiD8)kvMhr8we)uoDM-YvIC?RRsX_-)DD3%bP zF*+rgc50$Qq0f{cQU7c^a~dc<2PrVT9;~}2VjXUrjeP23$-Y>2>R=oF^W4}*u897l zNa*m5tr!#xf}`dJr<;sIw(0IsWQkKC?ZS?H84`2eYD)fcx>OSB3DFosOd1d*>@`mn zv|}x%u?fht05r3gt)NBzC}dqL4hp0tlQ(jWE*ifO2SZUYa-2=qeeljFzx?|j9^d~$ zeMd}a7}@e{r@nQ|9$q))RN8LygK*dMHLhvLq?6c==JE_oh{w* zS|6Rr>1DfGZMvov=Uv}{cL<=~dr$-e2yB=P6B-p2M8jwba%%dn?>hmfK;T6+>`pa# zn=m31D1!K)77&@xYl4VMM8q5lY9SLb5E3dVvH?I+Qq{MAv-;?x^?X|Fzhb9GZ|qKd z?~a#!F9-^?wNctv4e{`JRAQ{{o;NoHSDY-P_dbB4^jN9RE4i?|X?>_+U<(EZzHN)C zz4iL5)qMBC+dqHy>t9T7+}*i#+mcm9GKQpc&V@5)5EvO<+pB^p3=sekqt|thLQDnt zju@Krb6lHhzH4T)ott;c>F(w6qyF*?LGAPqnAT#;(xe(;bZ&VSaH-Yf5G%S#Xjttq@Ny7Qg(-uiyL0PgW-uge0DA3y!O|HuFB%fI_Oli94OCtMYfWQ7RaFh~=5 zfex!X6645=^Z7+Etlh-kl8ljjY{U$ySq{+r&Twf`@EWTcg#QtPz{m?x7%IDiYAPd< z1cGoSRnb)MGi>Hk1QgH^NKSbp!?I0uF)q!pS0aov4!iABh7t_x6ce+LxT^pvfDrRG z5wfm6V$G7BCsh>y9u~i1h-2%J?H>&qj>uvNeudK3p=^pO5v3d%q9AgmjoHZq(1B2A z+CGN!0sZ69#CBm0aYPQZ1#KfgCC!7;KLdYeHi#&q64PXptT!}hWS2)+iV$M;Tb3r_ z+|#V!_VC5 zkph@v&h-^L-7~XL?PtcO;A$eO+I0*)A%LBcsfkd1EC$1c4b>Kr4BG-V)EM^$jTc40 z$xc}o^yM$@{odDhZ=;AfWbBkbIQH*n8{MN1uN91}+O^LmL59QLlwFXSd6*6|Z zaK2Y@gjkTavRZALwwq2SJNtXr?v(Xxb@aGfT%a)qY7bN?wC(DhpZx6nj1 zgm`SgAMzri0;&WQf;*5>2z9iYG%kKk%q_1z%~k}W;fa!c5*V`Yp5w0wA>S-LqJ~VH z{FwPXkATC`CLtk(ifowbDWw!?Rn5M|2$K}NsEPGSQl0%oZF6VS2zo^0NsvL8B#7IQ z6aYi{pajdRqn*I{;Wa@gCiyuIWJ;~as8~luE6LXyOzzOifsfjPt&m&AHR3+$A9;O#~*(z-eV8~ zjHwT=PVc-}AKd8r?(F`@&66*n?~I*by~mR)wwiNU+Nw5XiDgA>QCR!dyB1K2**``#JD)4fSKHCHD1>b3GJfH0mK{^H^07r$|jpS69{ zD|-Yqb?HP~-*=}=zdCD9Pqn>tn{(+G{rUn?RXre+F~~(V-vhGM?#{J0zGJumHpE68 zwyTwdx)AC-xB{mPfS|;rNWRa0dOXFNnK!4$C!hS{^3l7^`H{K}st_?mp&ASk^j?Y7 zJCCBGPPBPS7q+zf*USBHPOiU>^ZA8uyR`?RUf>My=z-k0HNA7Yy1852xmK1|Da`IL zH*?sY009i3LG(yuls%Fs@z8b-6hKg&Qpjaw5bsry*mTYV!Ic+ZEN459KmYXd!9BlT z?%aBTi9C=K1yyoh+y)7O$S{l3u4yThI8`p{S>rB%oH`H0C{DVz>wCA}lm}PKoolAv zSsg##96$1Y0aRc~C7_q~{)Zp0&rSj1wXb}sbA4DQl-CX>PBZK?)NzazLj@!nk}yZu zxBU})Y`BR=8jPuKHDJs-DYo!gL7R85-x_`PpxN z`Zs@n`Rs`z03)bURZs7{IJ@-aw1p~3 z-$Q$WWXfWKb_!e|Dj^zUnU~Z33M;d@+$^3Roj-ZJIe*$5Kal?1uP;D*a4l#LAYm7wLiiIefPl4} z@2ac!@7$SPxq($F>P10-!1Zj5<;pNwHePxlu*422BjzJhTTV97xUCep!F{<(+ATKpp_%exM9$2&`y1hK|vQ4<}e@WOTums{q%4%<4Ue7efpz3Nky` zd%?VU_Uy^!(UZ$B?v*=xcD8GXMT`?fzcJ+m7~-l?BqApO1XNoqGiphFCtd4$0RTqu zoAv7GvD>*?%x9Bpcet9Yp552;W9bFBWF`bUKfCz5|M!1cFV|oD{crc;LY!mUryaHf z468XV&M^oo075*SWxNbV1A-_l0f7(S@w>vjm$6tf(UpIF-ZXw!x2X@VKyIkpsGUf+gU)NSGKumc;-yE z(;#W$^9n=)8XPhTHRqn20V6zn&MC+b5hKY-`iU%l%*FvBxga!-pr_C+et&+X&Y5CDlYO2ZQZ+?m zj4g}l9IXXgsH$|WU#~%XF6TvA0vkjHQ6fe%-u1*p#Ogf&tAZCthLJ2-+jQNuz!zVf ztjVmL^vWN9w*K3H?jIiatIdWBzH)bOKA(5X^T!{(_v|;Ok#X(D_ zX6I7!n&K(YuU7Yd`aeDW_~6Ft-`;=qcf4n>O2%;5PDaGcipW%oih6;t-47HI`pc8! z4}N&`@juJvl8iyIaXII*=YyMtfDn!aBqELAj38hvK(}e1eY82gck>N~IP-TBsZ z_i8nrsBe#sFD@3v$ys~R>K}Y{Z(eXvq&0>@5kNz&H5N1!wiT1LsGTQ`^^}N61y<+* z&>|~(#b|fCP7tJ;*VoGz>-qfb>66PRkLttC{Q3*z!8uej^#Z=z6tyvvJ;ZZy9U1G} z-Z@8AMTowQz$$&K;t3VIO?&jPYxe5>tJT3(uIf!aZBC!K#U*$_wkXgn*Z=evf6;E5 zul>Qdjpd|ek37|c)N$0(T8#3|hzll8m8N|LF$9QbyC>IvFb9wqNuu*XP633VXkbpF zA1{%h3B{!tZ4@R&E)zAx-0VO-MKV<3wq48c3WIS{(3Gv7!QOCCWQGE%CDsk3F z1RPx+R~ZE^FOsTxM>|PLsDhZ&4GIuX z85mUq1<2A|+T)O_$R+P7b9{ubWjqe(aI`|`K+Lr$vPZKm%xBn5@eW7{2jfj=38S;i z8NL^N?Lnm<`Ew%|Ljfp_j%qZs7|mb2!njSE+>tQC;Ws8?HeuAmPJ-x_GDHJ|3R{yV zbVEAPEg6NhT*e%KD*%x2ejE667D23lf*wQ=&dyi={0~2V_s2i#Rx2O?LN3bc`mLS2 zFB>yiJ$u+Z`@(lUBAaT5svT4BVpS6xF6*M6fGvOu@m|)Kvbi9hQ8BgCLm&%+fuNEB zCPHHXQN^nYF%uimKqtg?-WaaRs%h-b;bcm)#l_`sJ~}yFcr47`^?9*7Yu4>&KmWz! zw|}sH{3)zY5qd;0go>ETmBezrfix+AdJpIU35W?9ypVL8CtrMe`gH#E@r{?ivvc!? z01}#%e~BvURWKAhiAm15K0ki?>%Y0Y|8oMzY~m1T;1Hpz8;#f^f}~)YB68FUM7<~h z7+^G@etok3_+)eMH}=YlyEk5&U%g|d^JTknEA;nH7wz)9U%hg3uTtkBc=`&V>@FfO zu|ZH33}Nj_34KqY5ei;n+yEI3Ap!<7&jm@Zh=>N~hgZ>*XHOox_44x3y<+#!R+Tsw z@FklP%D!7U^&mjZU`yYtQPi?@t^r{(CaCM`dvOlDlf_xnwPtU>obAkRzRZ(Z^Xz_i zb_Bhk_;B9(Pyg;8*6Yo8|MYvdFy2cp8WZR5STV>e5TdaF9epIP_XxjPxrl2x2pR;G z1b9v8mljp9r$GQtA|D2}0vbJQxj# z8d;!|9MUd>NO*r!uaN0ul-Fb3Y_YsE~ZQyw0|)C~W^^et-ZF zHs@&SG~Gr`NDYPTb7AbSfg}tzI#0zo6JeNkk?su-g_&NA0?m0UU_6P z>!s=S+uk>4_uq9Fr=-ZX=3>T^85L9Hl1yo;%G47@MnT^-etC|*<+3W~SGkw~62!!7 z1Yl5d&Lbc*g-9SVD|k~F%jCV*byXGxE2_7vr~dNzAzi(Vra(~v z(|UAmEAFzIAF{>v!UJ0I;(bqCf}nJs7=ciG5&5U4bxe>`YCKoKUZ z7A_X|oJKoHMA`g=$xl(|iCKg(w+BH&_2)z;2N40lt6<L2{a}gR% zzK}*I%ZrLAZSl{+14z9#AS!B3e~tER>;Y~K#?Xrp6?u&5QosE(Sk*W@p^%2;=z7Y6 zf<*FRe4&u%m7J2$S1IB@fMrUc#niNPYseW!ijdMYlk1tDB&c*uYz$Ly5;e#5FwUDy zD+aqbYG*|7!3_w_N^y_;9n`J4HvVk-9D)FdiWm6gllwpZ>%V#U;cvuyWXokyUAZ~A z^_rbdHfKl8(|g*rWJ=`HmUF77RF-5)Bx7bXvXugDjMsF)}rI0uN# zMnpvnnSkv-b11e*XCFzui2& z=h|}uAJ}lPp@^i38%C%I%P87lLL_94)!Jk14iOlUnN4$f^vRF@;`*0<|H{kXqGBTH zm{Ov|I~DrP>gl_Ge}4ZL3;;?X3KVXNs2Dc@L-YW$kqt>5iVDObRVILQ0f5E#UOgJC&La@3I_DUP3RE!j!z;#^)%nTl>3yiCv%RZS80SSz z#cZW_tj_FN+>mr_zt|udAR<$E*M=Yq6^vX;*MkVV&R?8~cg6fFm3#Fyqhy-1XRkq(m&FQiW3-PLgsJLqs)cV=eA%YtVI}B1SB%3DvCfb z6jlfqjAeH7$f@R4sOhQ^Jd%BnsVjM*1?<6Sq9jSKW2b!l6W9JoMNSYBG6_;zOo}u_ z?IfNAmL5)H$yE*23G+cSkqRj&Mj0X=pA!lgM{|%9Oe0eQfQjeo%snW?^Mc|)Mo=8R zlCPO!57GO|d_FrJ*&afQ!9t4Zj^4_q4w6_FeT2+d5RnZt$9?$8D;O*Gu+=yiJ=SwR z!q6`oY|d8y4I2s|5t^;-5~?5|&ijwvzxR)S{ntmIeX0Ot3RBN#cV4a!Z#ajSUwr7! zo)R)~fyK;JbFw9uHQ54)%xq4kQc;z@yF8O_X>4WZSBu$IAPWEtgvKDV7e`1~aLA7o z5oBT}MrLCW)H#oc^<)=F!1>L^3bmWu-nsVTtEcD7&))v!vtRvW{oq62FGwAcsuu)8 zR1D<;F&gl+-~bR*6yx^mxTJ(Mp28BQv63o?^8f^{Kfm|h&f!awtCOI55+fq1_s*$` zyS)G5#TUOuuyI)$MULDmKrw1K+cji#1$G2!^Hwwj*+PfXy|IWGYO3;rDu@b1;4hBP zE-%gW%EcF2@9yuu_?m1?w}IdL`reI$0>LQ+Wdjr>2o#oRZ(^slX_`*N2YoLeFCyTP zpbUv#1_V^Zc=0O2NKS=7r+a%uW_fYatS=k!(;IiufOer`o;ue7d1PQ~CcC@cYDLDN zigW>op*dhmU{tws`l4|MvTb zR}Y+X$*yEUL9=o9;)BmrZVZbmhxYRPxxalsrlxbIA2Hb!MkOpxS+a0UtL&!-r z_nB_lfB~;H-BBY10mSNN9BvV|H$t|+ps>ZihgB=8)$4=<&4qT}!4@CT$>6D1`9 z5YW!cd+&Vq&;RYOo;~{;U7EAuFdH>yheGcH5NgZIg`que>qzIw1zJh>wBtzB^TQwGtkQgB7 zy5YEQRR!dr&ZvkBvYe`C1f{^W1QbQY5cga3-2AMsCVAA>N;_)`d)pnx#|iC5wol}>u=nilqT4oSbc^Fg>*jQYx~7w(Kik}C;~EqB#!4*5h=L(NGj@-*n6kW z6C;xWR6i|u%WL+c+FYD2AAZL5tlHg2Af-aVqbMRd=h$+&KbO8&KyuESsdOzWfGCv~ z)VUUb6uoG(>CcaKdSI)n+Pe;bEh7;3m*)aP)_(fV`zrd!fA+5ruI@SSVh}KnYMQ)h zCB{GVtkwW9iQp(Iq?3jL z)O3@F9+Og&h!b%dQ^H2XAWaTOn8TqEfPzY@I311}$B?CxDJMWsIr7cIHZTSCDg2P0 zAON1mGk^h*gBUUNt)%L1ir~Eh24^BTAnH{a(IC56G-FUG!Zk#dIHo6r*NrAh4Y?a~ zc4KfaG=ue!20BpSgoyd^GEV=0a{@+I5g`f$0O=5>OnwO|bP$d%j@z6YQ~FTWeEvlP zgsE0w8cIM#8bvKabK-iZ zCR6EJ|Epj8=0|_?KVLk3glvkUC=PDUZ@ zcCOVo?v{I3Z9O-o1##Z{zG*fWXY2FxW^sXiiy)w0eXpuTF*|(io74TnG)<9+y%#1z zBo#Be^BPQcRuA55&mN(w8U;c!mWm12b356ucCVDP9aGJU$pnL$7eEXVIuYOZP3PKm zyIOVY^KN<8tuFj#Et`dOjY<#Ri}Qe<$RM(Jj?j|!0?X;)9rV6gpFREP7blNCyZ`7C4-vIheAa0;uLCR5%CCUjQ2k5 zZc_nc4O*Jwel@Mn9zSYNPoUpSZ{HQ?K`DR}87txmpzj3PRP&i{+g3!n&c}KSgat|8 z1+@a3#`j0KcRIy=#R zbr$Q4&1UIa`&6ClVtswR`{>}jQLj4oK(0hRxVJ&`vm zAYd%ky^YWR;?KZy0U&U=EaJt#f^!{Wj|0btktIxCNAf$yrAi0G9h<5DQeM|q0w>1d zjE{rJ#2gPD)WNW%?#UBnjP2XL#b5+kWFfHCJ!{T}{S07Ov`m8R)``jcP(;&1{d*3h57Jb`m*AR;It@ORMwJ9fNG9y!mO}~K!auOWan)&`Nqrm32EvJSJ6=gZw zE#|vszK6Evq98VjGf}`IR7FICsHnsgOumXLc;B~uv+fs*_36>(^l5u>+$~Ohvl8E{ zcd8&>$qAwYt~Sj{eem+^>dn5>X48K0>krPJJ$rEPYybYwzVgD=nfT62V0UVnrW3kz zqg-v;qf>`u)Q}5oM^RM;W1LzQn8>Jj0#F75^#TIoAtY;=VzPVn=KA8aU0*IA-YfU^ zZ8>pGZ;TpiKv|?GK3q}(|`l@n3`n~_fpX?v(dG9hk3miXgYYH1RK{9I^7K;J^h^qI}d+(eF07HzH zqb?Kub=4t-8uB_!2!JqN{s2TI2_O^i4UNj5L+UB%{N*qH@o$@pb1aH_ z@9Om03&muwxp>;1-Un&f6lAApDlnEzLAK&*!joDMwNpdjR;R9A0a;Vci`i9M%|fOx z8H30`Dqg*c*S@e70`$&VV~7a>Oi?-~-h;J_%)WQE>F>Ngzxl#mIpJUY^21Mm_SW&Q zf7zTrR<|Y;@D5_^PSWC0BrIxM%nr+g+tu#P>fma9<%X>$hOGc9l88btVHZE5I@f#O zgX={+cqnWE)-n+g8aCiD6D(p*4tba}fIuM1Xs3Huru&DnmOWVtL0}}`dr%Fsqlf^i z>pdYb2Zc4Mpy8l-FJzdDs;X){I6S!fg75q0;(U4ZWcBRP`s8W1KJ)DwP(to$I7xVO z`f1-U>-{%p^?`Kj<;CSc{`D^(-+%ny|M$Q9jj!HD1n*Q8daq18pOyQ&#cCz%UO*{` zYea;My+}BAr}aLDJ-s3Tcm*UfU*{7oN&T&zA^#DXg zRThQxohb?+5(VlS*S4f6;s5~Idhepj-nX(mRT8AS+PlKGT$8bc{pF?i4IzK=@y9>> zZ}><5?)wK<_PujqzG{T-P-+_?5J%F1#fP~bW(bIg?|awx4gkR_Q7BBGlEBld59t@r zv#f0rrkS2)R<4@j4LULz@q5WWgh@feqRje!xL*`jvQL3Aex-RSr;-CnSIEEy!FrF% z0b}+XqNqTDW#CAefM@^+67|I-N>MnbCWuR<3P1qfYdQy+5-1HcHDQXTGpb_Rs!BXx zGjL5a(r(gsBLB}hli7`mFqh3p$l=!D+CTsEFM(jTZLzNq040iR0|Um<*L9m{Cm>HR zCFy5?(OBm%_A7>?b0`GUqZgAWRZiv!iei)$V&-n(4%r+Iaz4h2h`0syL+6iBet2iw zF&Y`%^u(C0izo={qG^Bf!(YDj_y6-|wKP>dyK<{KxC4mGXP>*}F{zNP(9Y1-AuolC z!ju(O6(IVi#lCUgtLqt=ik)J5Xv-O}AvA1_h=TNlXbKkbtQJMR7sJeC!aiB=90FTQ zK&T#fX7+RxZ_aMK zR9}Cwnn%w=KnX}ZcrSv`ca94#iUK`$n@ziER29`T6AB;*u~82wD1u>2J28ohh{U*F zfPM{|IuVH(O^}#X5P?KNyaZ7sPJdAG9@HTqF=hUsAR%W{yKtYXVWLuePGTFO! z-M#ka<N-0GsZ@%lUBA{iub$!xq+>;a6xR?awy#fIviKzHUunZYsK~>+20+2!K!4_4s zzBHW$MN^f#H*faMrdgbCPEYJ)X6wqOLJr7)LI~7v8URH$rk<**c+w6;1(=Ae>$)h2 zxZYiyaV0XFay^|~xz!ROQh#v@zDG9q-usvUzV~N;vU|AWoC`^kp-hj24XBWm4NMrK znir9X_ToG5ocDz2C0Nl|qc!tlP=&BNZv;h|sYh{X1T$g7la46#XgOxsjKcLP+EvR0B5WnvZdiyfc?q$fbTC1@5vs%j zN`%qLh}m_Ry37Hb9 z4P(Sks$jAnnJAA2DC}8K^{R@RYRpC(j>$J1VQlKRQ4Bc0mtBBpNRlv_10Cssb4F=0 zazP3R_EI!Q#p4~bL^R$EGk&&l>$ZQMe_`7^Y~_OmKj`v&^-n+e@jE~MQMXwa)7kvy zE7i_^*LKb6W7%8~A)1Qoec(bB7@6$E)-zy>WZim+%?iMK*PCi?%3UlbwwxjmD3M_R zQgNzE%!W!Qn_k6%8YVW@^*w>nsQWwQqVRD2diAZ}xnA9vzx~eV|MZ`Kbn(HvZuQKs zj?r%rG^VnsQvgL|!?UZm=C@whd*SQ#!3{3TNE5t-B;Qb?%{kxq4ix&8b3}%W>s(aa z2t|dMLA^1IMBaO{CLC#v2+rfGQD$Q$oCW47ClARlN$JdLXSQJA~RbodTGmARzUE%~HGGs}guFEt;b5 zdSeSN40Jt^F-YEZXe=U;bAU)_)H`M?>Vp!2eJlRd8RcTa^`tns0R({5U7U*RQQ_W) zACW1(_ov^V@6DWZX^qDHp@hgW5&^Mp3Z!KRsu1gn6GE)y8oY>%AL$FMRpRE8naSu7!

()L~7dt=BLG{z7iaX1NtxI#7vg2W0c>WKpVhv-HVMb#K7 zC6hwfDI6C!p@zeKQW}7WS3<)^ct<;js-Pm?0dT(&GK4J53`Ej{%LiVjY`I+C`tq#6Eo89&8^8MeOoE`n_ zJAd@$Z+z$OPGS1Kbsnm+ymL$XezDkqxadkVq7bU63yK`vjr6_v&aK*RxuMG1>CB^u3RksuJsHzAr(`QCYY!rABhDiel?8g|`!*^O z;@ssaOqC`xE^9l#BBBLgzc>fitA|g2^FASd|KI=XdQv;>qwJv?vRu**sDXDU7(zld zM4lK85|JUIP!$qKq%N*!fgx8gB;Y_=hU!(ttEe&(hf{IWiqf3dmlXn)7*-iv@4PhQ zx|Qg642{j0JqTGTw9KPT&Ozdonf0#+z6DM*@!sr9fRMyb>;ROH%@!`@n8Ss!yo<3ojsD~q&t5I ze#1!WTu>?4a5X#Fx%1lLYu}k1-T_7gQZEz;IG~6JrvM~Tt5K(ZeX;cGg^FQQl0hI+ z1t(w-f`c9kJcPaV=ur?=k;2(g3h@w5L=*_w&PX^)S@A0MARetP=o zL3@4zu2JXY{L!Y{bkp1Q-s@F;{rKdIzx|81kDgxq!S`M{JeUb|jdPRI-nv$QdVlHB zf&u3RLX~3@5J3VYQbj=_22l3id)EM3MG)}n(3XX$cb&2|-dkH(rMfvkSMQ~1JqVSR z^8$6H2q3PQ%)wLFtdZH46W_H;s@{Vka0W+)0>(NtzK1%yVRMCvZj#jQc$ zqhI}+i2m^3{Lyr0(z_nQ5GoMo@K}}vB5%}GAi);KDj-6rR*7MYs$wd56T<~DC>#=a z680&mcoh*Z!B>|UT}^2(X@#f%hH!ASBnSYKl0QPqOcDnozs&%!wM@s($*_Y>6I)E7 zTug>%oEa~onR>>@3AaSb`y!G{5dv{pH_HN!W^fyEdC~Cv0g{s9J@_#&VwnjBAs7ym ziF*3_fVJs95es$5qfej~;|hR?Ly7nlzKuEF~x9t_`ry|NAQU8f`s?k>?8n9Q$0_h z5zyn71x+?K{_0hL@Y&Oozxl6!_t`tYqQcs}EA`|!P(BQn6Eu|oSX0=d z5n?!I5;#=SrosXk5ESn~6hJ6tmpLckdlgnh z8;%$*S)v)y`;aJ#+K>{=>H*G3J_mUHJ?>`??vQIcWTHi9auSQvzG zz_dq2@ZK-au$)0X<8o5&9d;L50Jaxi`$m~Q_~ko<@Q45I`(!w0Xq1ZhJsXc7`9K@3`lMI`a zK;o%O33w1FBsB$XUqK)o*nmV)$x3E$uwVJ`l-P}`h+tH8{F&77>xf zCJu~B++l`uO=mepUoUA|gX59>z+r5%8O|m)zlGyZ{nn_CoX^Mw2=URQ)4%%9e|7)E z_f0h|_ODjEH@xeclP|QnL;wT=v}h}~H6RfhE-EvfA(g7suTG^|BLXp(vwffnh!6;k zWm71C7Y_uiJz4I0hpNnMjR_CrMb!IXzr8r$_lEJ{_O&Z_Z=9bmKmFkkFFyM~`(?j6 z?a!Z)ZU{s?B4AO^>Z`Z+UjFv}3tzF5d6HSuE*J&xl_FsaoBR*~q<3z;Y*!nCa(+0w zdUJnwr(la4hvyev+j#L|dB(L%h(sKCkpkpuRuGG4#qL*7@Bj$FY05~TX^4o@cH;Y@ zEJ4|M7kms6iKPZaBJVRqWWjs!sIji15)&eL@7-D*ktvCoh!wm7qZc$l6Km?)*4HY2 zWx8G;96!5$@8idhKE8kQ;EVS1QrSs&zIpazn!P^Rd)eB9&+ac4|LGTh^l!iM>g&7S z^}TmDuJ5eZ=TFWzL?s&(@nIjG7eyoiMPfqj`(712I7H%tUGJqARq(DsvhlQJq^@bm zRKeO}WJ6lfyv2&ex2X<0g-!}~su`R+ekY1Dxu(hrY{8ORXXzzp&O-kwzA*sZGI0inCua z70m!p051S4;*rAf$ML}0gpQcNJQ=nidYMd~C7xv=DIE@DSU1(z9eYEVsq`SJL?liq zoPjx0WKBsmacBZEV@jq12sR}AKQZZyC?lsVcF8uLLRS3;`mHf0qp56=rYRBz7~;7S zsbIFoDAWl3^GI*$Ut>c6&!PPGTiFR1bzOAI6ac`f649ea&;HB*_*aiU`>?F1_TYw{ z9`x%_(;6W@H76s`@|@n z^gZ-JrR7ld77#%_p(+~=ZYvvB0P%vHNRtRE0&1u!7Z2M=j&4k(Po!Y=@SP>^SNqqC z*%X+2>zpbB6B~e1MTk(CsS`pKATY-6 z&eXMT-KvT@*LD{?n;;?@1Dz*R5IgarY_KS-dUWWzhODt=*=?3_m#^>q;+$%a)zlO< zOn05CodIvyZC1Yb@BREOW6R(B*T2WdkG=oGb z6You~OJwo@5DR|{Ur4qDqROS-vcc z=r~C!Jz#42__0UvDL}7^gpZ${{KbFzs|TNdSWR}z!>e4(-TJ&ge+sTaRYU_oz}8d~ zVhg5VwmjWYE>xsno=M*jp)Cro_kk-BGG#%AyaON(lDSHUgh-5nB_Jb$dPQ(;@4Xbo z1brvcktuCG-@SFy%*)e9#}D87X?uJh+5 zMxhc_h|ppReM>CUtE8w-+tvE~=owe~jX!+jE3e*Q;>EJhTxzJ9l`x4~W%# z#}=h{5dk9RSTQ*lcLlZuI5+Yv5kXz&MgBi@{b!RV*Oev+9&xX`zams>Bt$|G06`KY zsz{dVs_LGe7Bf3NwqrIuHv8N5<4Rjwv$e6AwchP!m!c{XBnYh&2_O>*q>jjlc!m4D z=bZg;g?pfS!xG4hSGZrl;yKUJhG^Z~BSPvTw%yXnF)fjI$?vwbbKstsFQ31#{QUU~ zpFDf@vrle+{_!WLM~^H*;}i-hkyFdU-k|5AzZz3uw6em zUa!E<5K#@{5P@^UrPL7~!~mI?P|bm+qizDhHZIOIMg$?>4VzPBM&}U$aBHII8nkFb zZxngHh#FNCUF)0<>ov1Sav%oWm?%Z}xd+kqu#GEWuf z>O^GvoEIzyApzHkIb-L@N?!@8EI%XuorT9dI2_@23{K58GmGyz!zF$jkH?`-w2D0P zBD(F&Zm7!%R*7P1wvg;j<0hKwlVuYYHQ=l!VABtdx61z&Hd;1Xde(ye#&qw&;m`iV z-+lG@=d=2*iTfruNL?NPG; zBXrKP5nTX{921cOn!*sb9XEuW4qu9|Vy4(1rx|qJa_918^zPBucOQQEhyKpT7S`K) zccFhw5Dmr8_Lfh)bnxsCcdkC~XY=}C06>`Pv_LK{L>LA!Fd~w^?@vyL)1!VnT)Mpb z=l}9$5r53$esMZ23XC@Qz+^zi{m*Kb;wzkUf#vt#r=3ay9vCXF)%Q#2gk

pWa=1zF;xT1+6-|^Y^q4tCjuh^M2V7g=#0oEE^Z9mCp1mgpvD zW-BXMWs_N1NUZNnMOPJCStG-7A)zR#`L;x6g$L$QA<7RFebbG4XQ$%u&cZ)rM z76Hh4A}_(j6hK|qIhR3H0La9mA*rfq)KqswV*nH1wtoLox4VyQk00EB^x6BHuRqbv zqxGXZx>+$J5qIaFKKH_p4xal#v)CiY1f->wSJqV}n;z9bf~YDYogN-<9^Q-L)RBMU z*(aZT`pVz@?6bqi$5)oK=U=>byqSIU`NPANR!3>W~BD!AEf@9Ad^)P|so{i7T2-+Aj-`_F%G z@5(h|uPOjYgr+%vAmvCUN0RpxRqJMuXzNuFwRzVdf&?AJ3^}EnsQ?g*8L3`4hs&M4 za~It+-#NJc(WQ^y|Mc#S+i=>$=Hcq*f9ejtKU-Y9bNlgs{$JkwlYjmFOHVD>4^KXS z;fs$B6@c2-AUeQq2BMF^;G~~zwIy<-7Q~{ckP#qyVofzu(TG8W9BEWFi+vBweBLYea0vZA^5tQgB3dCbx)aLd|irAS#81muRX9uvS)YXNmtf7|e

06;WA!jqHr-~7ja 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%0Dc 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 z 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%0D$0h$wV6B#C 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
hucU_g9Hl^RFHV7~Qm|60SdP~N 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 A5&!@I diff --git a/extensions-builtin/sd_forge_controlnet/tests/images/portrait/1.webp b/extensions-builtin/sd_forge_controlnet/tests/images/portrait/1.webp deleted file mode 100644 index 5b9eccf01a16b051296f59b41281f9e31e0dbc74..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20481 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 diff --git a/extensions-builtin/sd_forge_controlnet/tests/images/portrait/2.jpg b/extensions-builtin/sd_forge_controlnet/tests/images/portrait/2.jpg deleted file mode 100644 index c16127c298190d43f2f3e63a5c25a1c592eb51d3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 37869 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
-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 SkNok{D`BR*#FrN`46}N diff --git a/extensions-builtin/sd_forge_controlnet/tests/images/portrait/3.jpeg b/extensions-builtin/sd_forge_controlnet/tests/images/portrait/3.jpeg deleted file mode 100644 index 1936d9fc21cee9ef6bdead5368c8e4b14eea6927..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22485 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 diff --git a/extensions-builtin/sd_forge_controlnet/tests/images/portrait/5.jpg b/extensions-builtin/sd_forge_controlnet/tests/images/portrait/5.jpg deleted file mode 100644 index 605a914aaf0dba16ac8937b0f5b1bb72c3f04aa8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 206595 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 OTs*6lt6cZK`2PVpf?tLJ diff --git a/extensions-builtin/sd_forge_controlnet/tests/web_api/__init__.py b/extensions-builtin/sd_forge_controlnet/tests/web_api/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/extensions-builtin/sd_forge_controlnet/tests/web_api/detect_test.py b/extensions-builtin/sd_forge_controlnet/tests/web_api/detect_test.py deleted file mode 100644 index 81f26b756..000000000 --- a/extensions-builtin/sd_forge_controlnet/tests/web_api/detect_test.py +++ /dev/null @@ -1,63 +0,0 @@ -import pytest -import requests -from typing import List - -from .template import ( - APITestTemplate, - realistic_girl_face_img, - save_base64, - get_dest_dir, - disable_in_cq, -) - - -def get_modules() -> List[str]: - return requests.get(APITestTemplate.BASE_URL + "controlnet/module_list").json()[ - "module_list" - ] - - -def detect_template(payload, output_name: str): - url = APITestTemplate.BASE_URL + "controlnet/detect" - resp = requests.post(url, json=payload) - assert resp.status_code == 200 - resp_json = resp.json() - assert "images" in resp_json - assert len(resp_json["images"]) == len(payload["controlnet_input_images"]) - if not APITestTemplate.is_cq_run: - for i, img in enumerate(resp_json["images"]): - if img == "Detect result is not image": - continue - dest = get_dest_dir() / f"{output_name}_{i}.png" - save_base64(img, dest) - return resp_json - - -@disable_in_cq -@pytest.mark.parametrize("module", get_modules()) -def test_detect_all_modules(module: str): - payload = dict( - controlnet_input_images=[realistic_girl_face_img], - controlnet_module=module, - ) - detect_template(payload, f"detect_{module}") - - -def test_detect_simple(): - detect_template( - dict( - controlnet_input_images=[realistic_girl_face_img], - controlnet_module="canny", # Canny does not require model download. - ), - "simple_detect", - ) - - -def test_detect_multiple_inputs(): - detect_template( - dict( - controlnet_input_images=[realistic_girl_face_img, realistic_girl_face_img], - controlnet_module="canny", # Canny does not require model download. - ), - "multiple_inputs_detect", - ) diff --git a/extensions-builtin/sd_forge_controlnet/tests/web_api/generation_test.py b/extensions-builtin/sd_forge_controlnet/tests/web_api/generation_test.py deleted file mode 100644 index 433819d15..000000000 --- a/extensions-builtin/sd_forge_controlnet/tests/web_api/generation_test.py +++ /dev/null @@ -1,171 +0,0 @@ -import pytest - -from .template import ( - APITestTemplate, - girl_img, - mask_img, - disable_in_cq, - get_model, -) - - -@pytest.mark.parametrize("gen_type", ["img2img", "txt2img"]) -def test_no_unit(gen_type): - assert APITestTemplate( - f"test_no_unit{gen_type}", - gen_type, - payload_overrides={}, - unit_overrides=[], - input_image=girl_img, - ).exec() - - -@pytest.mark.parametrize("gen_type", ["img2img", "txt2img"]) -def test_multiple_iter(gen_type): - assert APITestTemplate( - f"test_multiple_iter{gen_type}", - gen_type, - payload_overrides={"n_iter": 2}, - unit_overrides={}, - input_image=girl_img, - ).exec() - - -@pytest.mark.parametrize("gen_type", ["img2img", "txt2img"]) -def test_batch_size(gen_type): - assert APITestTemplate( - f"test_batch_size{gen_type}", - gen_type, - payload_overrides={"batch_size": 2}, - unit_overrides={}, - input_image=girl_img, - ).exec() - - -@pytest.mark.parametrize("gen_type", ["img2img", "txt2img"]) -def test_2_units(gen_type): - assert APITestTemplate( - f"test_2_units{gen_type}", - gen_type, - payload_overrides={}, - unit_overrides=[{}, {}], - input_image=girl_img, - ).exec() - - -@pytest.mark.parametrize("gen_type", ["img2img", "txt2img"]) -def test_preprocessor(gen_type): - assert APITestTemplate( - f"test_preprocessor{gen_type}", - gen_type, - payload_overrides={}, - unit_overrides={"module": "canny"}, - input_image=girl_img, - ).exec() - - -@pytest.mark.parametrize("param_name", ("processor_res", "threshold_a", "threshold_b")) -@pytest.mark.parametrize("gen_type", ["img2img", "txt2img"]) -def test_invalid_param(gen_type, param_name): - assert APITestTemplate( - f"test_invalid_param{(gen_type, param_name)}", - gen_type, - payload_overrides={}, - unit_overrides={param_name: -1}, - input_image=girl_img, - ).exec() - - -@pytest.mark.parametrize("save_map", [True, False]) -@pytest.mark.parametrize("gen_type", ["img2img", "txt2img"]) -def test_save_map(gen_type, save_map): - assert APITestTemplate( - f"test_save_map{(gen_type, save_map)}", - gen_type, - payload_overrides={}, - unit_overrides={"save_detected_map": save_map}, - input_image=girl_img, - ).exec(expected_output_num=2 if save_map else 1) - - -@disable_in_cq -def test_masked_controlnet_txt2img(): - assert APITestTemplate( - f"test_masked_controlnet_txt2img", - "txt2img", - payload_overrides={}, - unit_overrides={ - "image": girl_img, - "mask_image": mask_img, - }, - ).exec() - - -@disable_in_cq -def test_masked_controlnet_img2img(): - assert APITestTemplate( - f"test_masked_controlnet_img2img", - "img2img", - payload_overrides={ - "init_images": [girl_img], - }, - # Note: Currently you must give ControlNet unit input image to specify - # mask. - # TODO: Fix this for img2img. - unit_overrides={ - "image": girl_img, - "mask_image": mask_img, - }, - ).exec() - - -@disable_in_cq -def test_txt2img_inpaint(): - assert APITestTemplate( - "txt2img_inpaint", - "txt2img", - payload_overrides={}, - unit_overrides={ - "image": girl_img, - "mask_image": mask_img, - "model": get_model("v11p_sd15_inpaint"), - "module": "inpaint_only", - }, - ).exec() - - -@disable_in_cq -def test_img2img_inpaint(): - assert APITestTemplate( - "img2img_inpaint", - "img2img", - payload_overrides={ - "init_images": [girl_img], - "mask": mask_img, - }, - unit_overrides={ - "model": get_model("v11p_sd15_inpaint"), - "module": "inpaint_only", - }, - ).exec() - - -# Currently failing. -# TODO Fix lama outpaint. -@disable_in_cq -def test_lama_outpaint(): - assert APITestTemplate( - "txt2img_lama_outpaint", - "txt2img", - payload_overrides={ - "width": 768, - "height": 768, - }, - # Outpaint should not need a mask. - unit_overrides={ - "image": girl_img, - "model": get_model("v11p_sd15_inpaint"), - "module": "inpaint_only+lama", - "resize_mode": "Resize and Fill", # OUTER_FIT - }, - ).exec() diff --git a/extensions-builtin/sd_forge_controlnet/tests/web_api/template.py b/extensions-builtin/sd_forge_controlnet/tests/web_api/template.py deleted file mode 100644 index 5129e541f..000000000 --- a/extensions-builtin/sd_forge_controlnet/tests/web_api/template.py +++ /dev/null @@ -1,347 +0,0 @@ -import io -import os -import cv2 -import base64 -import functools -from typing import Dict, Any, List, Union, Literal, Optional -from pathlib import Path -import datetime -from enum import Enum -import numpy as np -import pytest - -import requests -from PIL import Image - - -def disable_in_cq(func): - """Skips the decorated test func in CQ run.""" - @functools.wraps(func) - def wrapped_func(*args, **kwargs): - if APITestTemplate.is_cq_run: - pytest.skip() - return func(*args, **kwargs) - return wrapped_func - - -PayloadOverrideType = Dict[str, Any] - -timestamp = datetime.datetime.now().strftime("%Y%m%d-%H%M%S") -test_result_dir = Path(__file__).parent / "results" / f"test_result_{timestamp}" -test_expectation_dir = Path(__file__).parent / "expectations" -os.makedirs(test_expectation_dir, exist_ok=True) -resource_dir = Path(__file__).parents[1] / "images" - - -def get_dest_dir(): - if APITestTemplate.is_set_expectation_run: - return test_expectation_dir - else: - return test_result_dir - - -def save_base64(base64img: str, dest: Path): - Image.open(io.BytesIO(base64.b64decode(base64img.split(",", 1)[0]))).save(dest) - - -def read_image(img_path: Path) -> str: - img = cv2.imread(str(img_path)) - _, bytes = cv2.imencode(".png", img) - encoded_image = base64.b64encode(bytes).decode("utf-8") - return encoded_image - - -def read_image_dir(img_dir: Path, suffixes=('.png', '.jpg', '.jpeg', '.webp')) -> List[str]: - """Try read all images in given img_dir.""" - img_dir = str(img_dir) - images = [] - for filename in os.listdir(img_dir): - if filename.endswith(suffixes): - img_path = os.path.join(img_dir, filename) - try: - images.append(read_image(img_path)) - except IOError: - print(f"Error opening {img_path}") - return images - - -girl_img = read_image(resource_dir / "1girl.png") -mask_img = read_image(resource_dir / "mask.png") -mask_small_img = read_image(resource_dir / "mask_small.png") -portrait_imgs = read_image_dir(resource_dir / "portrait") -realistic_girl_face_img = portrait_imgs[0] - - -general_negative_prompt = """ -(worst quality:2), (low quality:2), (normal quality:2), lowres, normal quality, -((monochrome)), ((grayscale)), skin spots, acnes, skin blemishes, age spot, -backlight,(ugly:1.331), (duplicate:1.331), (morbid:1.21), (mutilated:1.21), -(tranny:1.331), mutated hands, (poorly drawn hands:1.331), blurry, (bad anatomy:1.21), -(bad proportions:1.331), extra limbs, (missing arms:1.331), (extra legs:1.331), -(fused fingers:1.61051), (too many fingers:1.61051), (unclear eyes:1.331), bad hands, -missing fingers, extra digit, bad body, easynegative, nsfw""" - -class StableDiffusionVersion(Enum): - """The version family of stable diffusion model.""" - - UNKNOWN = 0 - SD1x = 1 - SD2x = 2 - SDXL = 3 - - -sd_version = StableDiffusionVersion( - int(os.environ.get("CONTROLNET_TEST_SD_VERSION", StableDiffusionVersion.SD1x.value)) -) - -is_full_coverage = os.environ.get("CONTROLNET_TEST_FULL_COVERAGE", None) is not None - - -class APITestTemplate: - is_set_expectation_run = os.environ.get("CONTROLNET_SET_EXP", "True") == "True" - is_cq_run = os.environ.get("FORGE_CQ_TEST", "False") == "True" - BASE_URL = "http://localhost:7860/" - - def __init__( - self, - name: str, - gen_type: Union[Literal["img2img"], Literal["txt2img"]], - payload_overrides: PayloadOverrideType, - unit_overrides: Union[PayloadOverrideType, List[PayloadOverrideType]], - input_image: Optional[str] = None, - ): - self.name = name - self.url = APITestTemplate.BASE_URL + "sdapi/v1/" + gen_type - self.payload = { - **(txt2img_payload if gen_type == "txt2img" else img2img_payload), - **payload_overrides, - } - if gen_type == "img2img" and input_image is not None: - self.payload["init_images"] = [input_image] - - # CQ runs on CPU. Reduce steps to increase test speed. - if "steps" not in payload_overrides and APITestTemplate.is_cq_run: - self.payload["steps"] = 3 - - unit_overrides = ( - unit_overrides - if isinstance(unit_overrides, (list, tuple)) - else [unit_overrides] - ) - self.payload["alwayson_scripts"]["ControlNet"]["args"] = [ - { - **default_unit, - **unit_override, - **({"image": input_image} if gen_type == "txt2img" and input_image is not None else {}), - } - for unit_override in unit_overrides - ] - self.active_unit_count = len(unit_overrides) - - def exec(self, *args, **kwargs) -> bool: - if APITestTemplate.is_cq_run: - return self.exec_cq(*args, **kwargs) - else: - return self.exec_local(*args, **kwargs) - - def exec_cq(self, expected_output_num: Optional[int] = None, *args, **kwargs) -> bool: - """Execute test in CQ environment.""" - res = requests.post(url=self.url, json=self.payload) - if res.status_code != 200: - print(f"Unexpected status code {res.status_code}") - return False - - response = res.json() - if "images" not in response: - print(response.keys()) - return False - - if expected_output_num is None: - expected_output_num = self.payload["n_iter"] * self.payload["batch_size"] + self.active_unit_count - - if len(response["images"]) != expected_output_num: - print(f"{len(response['images'])} != {expected_output_num}") - return False - - return True - - def exec_local(self, result_only: bool = True, *args, **kwargs) -> bool: - """Execute test in local environment.""" - if not APITestTemplate.is_set_expectation_run: - os.makedirs(test_result_dir, exist_ok=True) - - failed = False - - response = requests.post(url=self.url, json=self.payload).json() - if "images" not in response: - print(response.keys()) - return False - - dest_dir = get_dest_dir() - results = response["images"][:1] if result_only else response["images"] - for i, base64image in enumerate(results): - img_file_name = f"{self.name}_{i}.png" - save_base64(base64image, dest_dir / img_file_name) - - if not APITestTemplate.is_set_expectation_run: - try: - img1 = cv2.imread(os.path.join(test_expectation_dir, img_file_name)) - img2 = cv2.imread(os.path.join(test_result_dir, img_file_name)) - except Exception as e: - print(f"Get exception reading imgs: {e}") - failed = True - continue - - if img1 is None: - print(f"Warn: No expectation file found {img_file_name}.") - continue - - if not expect_same_image( - img1, - img2, - diff_img_path=str(test_result_dir - / img_file_name.replace(".png", "_diff.png")), - ): - failed = True - return not failed - - -def expect_same_image(img1, img2, diff_img_path: str) -> bool: - # Calculate the difference between the two images - diff = cv2.absdiff(img1, img2) - - # Set a threshold to highlight the different pixels - threshold = 30 - diff_highlighted = np.where(diff > threshold, 255, 0).astype(np.uint8) - - # Assert that the two images are similar within a tolerance - similar = np.allclose(img1, img2, rtol=0.5, atol=1) - if not similar: - # Save the diff_highlighted image to inspect the differences - cv2.imwrite(diff_img_path, diff_highlighted) - - matching_pixels = np.isclose(img1, img2, rtol=0.5, atol=1) - similar_in_general = (matching_pixels.sum() / matching_pixels.size) >= 0.95 - return similar_in_general - - -def get_model(model_name: str) -> str: - """ Find an available model with specified model name.""" - if model_name.lower() == "none": - return "None" - - r = requests.get(APITestTemplate.BASE_URL + "controlnet/model_list") - result = r.json() - if "model_list" not in result: - raise ValueError("No model available") - - candidates = [ - model - for model in result["model_list"] - if model_name.lower() in model.lower() - ] - - if not candidates: - raise ValueError("No suitable model available") - - return candidates[0] - - -default_unit = { - "control_mode": 0, - "enabled": True, - "guidance_end": 1, - "guidance_start": 0, - "pixel_perfect": True, - "processor_res": 512, - "resize_mode": 1, - "threshold_a": 64, - "threshold_b": 64, - "weight": 1, - "module": "canny", - "model": get_model("sd15_canny"), -} - -img2img_payload = { - "batch_size": 1, - "cfg_scale": 7, - "height": 768, - "width": 512, - "n_iter": 1, - "steps": 10, - "sampler_name": "Euler a", - "prompt": "(masterpiece: 1.3), (highres: 1.3), best quality,", - "negative_prompt": "", - "seed": 42, - "seed_enable_extras": False, - "seed_resize_from_h": 0, - "seed_resize_from_w": 0, - "subseed": -1, - "subseed_strength": 0, - "override_settings": {}, - "override_settings_restore_afterwards": False, - "do_not_save_grid": False, - "do_not_save_samples": False, - "s_churn": 0, - "s_min_uncond": 0, - "s_noise": 1, - "s_tmax": None, - "s_tmin": 0, - "script_args": [], - "script_name": None, - "styles": [], - "alwayson_scripts": {"ControlNet": {"args": [default_unit]}}, - "denoising_strength": 0.75, - "initial_noise_multiplier": 1, - "inpaint_full_res": 0, - "inpaint_full_res_padding": 32, - "inpainting_fill": 1, - "inpainting_mask_invert": 0, - "mask_blur_x": 4, - "mask_blur_y": 4, - "mask_blur": 4, - "resize_mode": 0, -} - -txt2img_payload = { - "alwayson_scripts": {"ControlNet": {"args": [default_unit]}}, - "batch_size": 1, - "cfg_scale": 7, - "comments": {}, - "disable_extra_networks": False, - "do_not_save_grid": False, - "do_not_save_samples": False, - "enable_hr": False, - "height": 768, - "hr_negative_prompt": "", - "hr_prompt": "", - "hr_resize_x": 0, - "hr_resize_y": 0, - "hr_scale": 2, - "hr_second_pass_steps": 0, - "hr_upscaler": "Latent", - "n_iter": 1, - "negative_prompt": "", - "override_settings": {}, - "override_settings_restore_afterwards": True, - "prompt": "(masterpiece: 1.3), (highres: 1.3), best quality,", - "restore_faces": False, - "s_churn": 0.0, - "s_min_uncond": 0, - "s_noise": 1.0, - "s_tmax": None, - "s_tmin": 0.0, - "sampler_name": "Euler a", - "script_args": [], - "script_name": None, - "seed": 42, - "seed_enable_extras": True, - "seed_resize_from_h": -1, - "seed_resize_from_w": -1, - "steps": 10, - "styles": [], - "subseed": -1, - "subseed_strength": 0, - "tiling": False, - "width": 512, -} diff --git a/extensions-builtin/sd_forge_controlnet_example/preload.py b/extensions-builtin/sd_forge_controlnet_example/preload.py deleted file mode 100644 index ddc29489a..000000000 --- a/extensions-builtin/sd_forge_controlnet_example/preload.py +++ /dev/null @@ -1,6 +0,0 @@ -def preload(parser): - parser.add_argument( - "--show-controlnet-example", - action="store_true", - help="Show development example extension for ControlNet.", - ) diff --git a/extensions-builtin/sd_forge_controlnet_example/scripts/sd_forge_controlnet_example.py b/extensions-builtin/sd_forge_controlnet_example/scripts/sd_forge_controlnet_example.py deleted file mode 100644 index 9c10cb23b..000000000 --- a/extensions-builtin/sd_forge_controlnet_example/scripts/sd_forge_controlnet_example.py +++ /dev/null @@ -1,160 +0,0 @@ -# Use --show-controlnet-example to see this extension. - -import cv2 -import gradio as gr -import torch - -from modules import scripts -from modules.shared_cmd_options import cmd_opts -from modules_forge.shared import supported_preprocessors -from modules.modelloader import load_file_from_url -from ldm_patched.modules.controlnet import load_controlnet -from modules_forge.controlnet import apply_controlnet_advanced -from modules_forge.forge_util import numpy_to_pytorch -from modules_forge.shared import controlnet_dir - - -class ControlNetExampleForge(scripts.Script): - model = None - - def title(self): - return "ControlNet Example for Developers" - - def show(self, is_img2img): - # make this extension visible in both txt2img and img2img tab. - return scripts.AlwaysVisible - - def ui(self, *args, **kwargs): - with gr.Accordion(open=False, label=self.title()): - gr.HTML('This is an example controlnet extension for developers.') - gr.HTML('You see this extension because you used --show-controlnet-example') - input_image = gr.Image(source='upload', type='numpy') - funny_slider = gr.Slider(label='This slider does nothing. It just shows you how to transfer parameters.', - minimum=0.0, maximum=1.0, value=0.5) - - return input_image, funny_slider - - def process(self, p, *script_args, **kwargs): - input_image, funny_slider = script_args - - # This slider does nothing. It just shows you how to transfer parameters. - del funny_slider - - if input_image is None: - return - - # controlnet_canny_path = load_file_from_url( - # url='https://huggingface.co/lllyasviel/sd_control_collection/resolve/main/sai_xl_canny_256lora.safetensors', - # model_dir=model_dir, - # file_name='sai_xl_canny_256lora.safetensors' - # ) - controlnet_canny_path = load_file_from_url( - url='https://huggingface.co/lllyasviel/fav_models/resolve/main/fav/control_v11p_sd15_canny_fp16.safetensors', - model_dir=controlnet_dir, - file_name='control_v11p_sd15_canny_fp16.safetensors' - ) - print('The model [control_v11p_sd15_canny_fp16.safetensors] download finished.') - - self.model = load_controlnet(controlnet_canny_path) - print('Controlnet loaded.') - - return - - def process_before_every_sampling(self, p, *script_args, **kwargs): - # This will be called before every sampling. - # If you use highres fix, this will be called twice. - - input_image, funny_slider = script_args - - if input_image is None or self.model is None: - return - - B, C, H, W = kwargs['noise'].shape # latent_shape - height = H * 8 - width = W * 8 - batch_size = p.batch_size - - preprocessor = supported_preprocessors['canny'] - - # detect control at certain resolution - control_image = preprocessor( - input_image, resolution=512, slider_1=100, slider_2=200, slider_3=None) - - # here we just use nearest neighbour to align input shape. - # You may want crop and resize, or crop and fill, or others. - control_image = cv2.resize( - control_image, (width, height), interpolation=cv2.INTER_NEAREST) - - # Output preprocessor result. Now called every sampling. Cache in your own way. - p.extra_result_images.append(control_image) - - print('Preprocessor Canny finished.') - - control_image_bchw = numpy_to_pytorch(control_image).movedim(-1, 1) - - unet = p.sd_model.forge_objects.unet - - # Unet has input, middle, output blocks, and we can give different weights - # to each layers in all blocks. - # Below is an example for stronger control in middle block. - # This is helpful for some high-res fix passes. (p.is_hr_pass) - positive_advanced_weighting = { - 'input': [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.1, 1.2], - 'middle': [1.0], - 'output': [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.1, 1.2] - } - negative_advanced_weighting = { - 'input': [0.15, 0.25, 0.35, 0.45, 0.55, 0.65, 0.75, 0.85, 0.95, 1.05, 1.15, 1.25], - 'middle': [1.05], - 'output': [0.15, 0.25, 0.35, 0.45, 0.55, 0.65, 0.75, 0.85, 0.95, 1.05, 1.15, 1.25] - } - - # The advanced_frame_weighting is a weight applied to each image in a batch. - # The length of this list must be same with batch size - # For example, if batch size is 5, the below list is [0.2, 0.4, 0.6, 0.8, 1.0] - # If you view the 5 images as 5 frames in a video, this will lead to - # progressively stronger control over time. - advanced_frame_weighting = [float(i + 1) / float(batch_size) for i in range(batch_size)] - - # The advanced_sigma_weighting allows you to dynamically compute control - # weights given diffusion timestep (sigma). - # For example below code can softly make beginning steps stronger than ending steps. - sigma_max = unet.model.model_sampling.sigma_max - sigma_min = unet.model.model_sampling.sigma_min - advanced_sigma_weighting = lambda s: (s - sigma_min) / (sigma_max - sigma_min) - - # You can even input a tensor to mask all control injections - # The mask will be automatically resized during inference in UNet. - # The size should be B 1 H W and the H and W are not important - # because they will be resized automatically - advanced_mask_weighting = torch.ones(size=(1, 1, 512, 512)) - - # But in this simple example we do not use them - positive_advanced_weighting = None - negative_advanced_weighting = None - advanced_frame_weighting = None - advanced_sigma_weighting = None - advanced_mask_weighting = None - - unet = apply_controlnet_advanced(unet=unet, controlnet=self.model, image_bchw=control_image_bchw, - strength=0.6, start_percent=0.0, end_percent=0.8, - positive_advanced_weighting=positive_advanced_weighting, - negative_advanced_weighting=negative_advanced_weighting, - advanced_frame_weighting=advanced_frame_weighting, - advanced_sigma_weighting=advanced_sigma_weighting, - advanced_mask_weighting=advanced_mask_weighting) - - p.sd_model.forge_objects.unet = unet - - # Below codes will add some logs to the texts below the image outputs on UI. - # The extra_generation_params does not influence results. - p.extra_generation_params.update(dict( - controlnet_info='You should see these texts below output images!', - )) - - return - - -# Use --show-controlnet-example to see this extension. -if not cmd_opts.show_controlnet_example: - del ControlNetExampleForge diff --git a/extensions-builtin/soft-inpainting/scripts/soft_inpainting.py b/extensions-builtin/soft-inpainting/scripts/soft_inpainting.py index d90243442..d5716d37c 100644 --- a/extensions-builtin/soft-inpainting/scripts/soft_inpainting.py +++ b/extensions-builtin/soft-inpainting/scripts/soft_inpainting.py @@ -3,6 +3,7 @@ import math from modules.ui_components import InputAccordion import modules.scripts as scripts +from modules.torch_utils import float64 class SoftInpaintingSettings: @@ -57,10 +58,14 @@ def latent_blend(settings, a, b, t): # NOTE: We use inplace operations wherever possible. - # [4][w][h] to [1][4][w][h] - t2 = t.unsqueeze(0) - # [4][w][h] to [1][1][w][h] - the [4] seem redundant. - t3 = t[0].unsqueeze(0).unsqueeze(0) + if len(t.shape) == 3: + # [4][w][h] to [1][4][w][h] + t2 = t.unsqueeze(0) + # [4][w][h] to [1][1][w][h] - the [4] seem redundant. + t3 = t[0].unsqueeze(0).unsqueeze(0) + else: + t2 = t + t3 = t[:, 0][:, None] one_minus_t2 = 1 - t2 one_minus_t3 = 1 - t3 @@ -75,13 +80,11 @@ def latent_blend(settings, a, b, t): # Calculate the magnitude of the interpolated vectors. (We will remove this magnitude.) # 64-bit operations are used here to allow large exponents. - current_magnitude = torch.norm(image_interp, p=2, dim=1, keepdim=True).to(torch.float64).add_(0.00001) + current_magnitude = torch.norm(image_interp, p=2, dim=1, keepdim=True).to(float64(image_interp)).add_(0.00001) # Interpolate the powered magnitudes, then un-power them (bring them back to a power of 1). - a_magnitude = torch.norm(a, p=2, dim=1, keepdim=True).to(torch.float64).pow_( - settings.inpaint_detail_preservation) * one_minus_t3 - b_magnitude = torch.norm(b, p=2, dim=1, keepdim=True).to(torch.float64).pow_( - settings.inpaint_detail_preservation) * t3 + a_magnitude = torch.norm(a, p=2, dim=1, keepdim=True).to(float64(a)).pow_(settings.inpaint_detail_preservation) * one_minus_t3 + b_magnitude = torch.norm(b, p=2, dim=1, keepdim=True).to(float64(b)).pow_(settings.inpaint_detail_preservation) * t3 desired_magnitude = a_magnitude desired_magnitude.add_(b_magnitude).pow_(1 / settings.inpaint_detail_preservation) del a_magnitude, b_magnitude, t3, one_minus_t3 @@ -104,7 +107,7 @@ def latent_blend(settings, a, b, t): def get_modified_nmask(settings, nmask, sigma): """ - Converts a negative mask representing the transparency of the original latent vectors being overlayed + Converts a negative mask representing the transparency of the original latent vectors being overlaid to a mask that is scaled according to the denoising strength for this step. Where: @@ -135,7 +138,10 @@ def apply_adaptive_masks( from PIL import Image, ImageOps, ImageFilter # TODO: Bias the blending according to the latent mask, add adjustable parameter for bias control. - latent_mask = nmask[0].float() + if len(nmask.shape) == 3: + latent_mask = nmask[0].float() + else: + latent_mask = nmask[:, 0].float() # convert the original mask into a form we use to scale distances for thresholding mask_scalar = 1 - (torch.clamp(latent_mask, min=0, max=1) ** (settings.mask_blend_scale / 2)) mask_scalar = (0.5 * (1 - settings.composite_mask_influence) @@ -157,7 +163,14 @@ def apply_adaptive_masks( percentile_min=0.25, percentile_max=0.75, min_width=1) # The distance at which opacity of original decreases to 50% - half_weighted_distance = settings.composite_difference_threshold * mask_scalar + if len(mask_scalar.shape) == 3: + if mask_scalar.shape[0] > i: + half_weighted_distance = settings.composite_difference_threshold * mask_scalar[i] + else: + half_weighted_distance = settings.composite_difference_threshold * mask_scalar[0] + else: + half_weighted_distance = settings.composite_difference_threshold * mask_scalar + converted_mask = converted_mask / half_weighted_distance converted_mask = 1 / (1 + converted_mask ** settings.composite_difference_contrast) @@ -472,7 +485,7 @@ def gaussian_kernel_func(coordinate): class Script(scripts.Script): def __init__(self): - self.section = "inpaint" + # self.section = "inpaint" self.masks_for_overlay = None self.overlay_images = None diff --git a/html/extra-networks-copy-path-button.html b/html/extra-networks-copy-path-button.html index 8083bb033..50304b42d 100644 --- a/html/extra-networks-copy-path-button.html +++ b/html/extra-networks-copy-path-button.html @@ -1,5 +1,5 @@

\ No newline at end of file diff --git a/html/extra-networks-edit-item-button.html b/html/extra-networks-edit-item-button.html index 0fe43082a..fd728600f 100644 --- a/html/extra-networks-edit-item-button.html +++ b/html/extra-networks-edit-item-button.html @@ -1,4 +1,4 @@
+ onclick="extraNetworksEditUserMetadata(event, '{tabname}', '{extra_networks_tabname}')">
\ No newline at end of file diff --git a/html/extra-networks-metadata-button.html b/html/extra-networks-metadata-button.html index 285b5b3b6..4ef013bc0 100644 --- a/html/extra-networks-metadata-button.html +++ b/html/extra-networks-metadata-button.html @@ -1,4 +1,4 @@ \ No newline at end of file diff --git a/html/extra-networks-pane-dirs.html b/html/extra-networks-pane-dirs.html new file mode 100644 index 000000000..d7c9661a0 --- /dev/null +++ b/html/extra-networks-pane-dirs.html @@ -0,0 +1,8 @@ +
+
+ {dirs_html} +
+
+ {items_html} +
+
diff --git a/html/extra-networks-pane-tree.html b/html/extra-networks-pane-tree.html new file mode 100644 index 000000000..e4d92a359 --- /dev/null +++ b/html/extra-networks-pane-tree.html @@ -0,0 +1,8 @@ +
+
+ {tree_html} +
+
+ {items_html} +
+
\ No newline at end of file diff --git a/html/extra-networks-pane.html b/html/extra-networks-pane.html index 0c763f710..9a67baea9 100644 --- a/html/extra-networks-pane.html +++ b/html/extra-networks-pane.html @@ -1,23 +1,53 @@ -
+
-
-
- {tree_html} -
-
- {items_html} +
-
\ No newline at end of file + {pane_content} +
diff --git a/html/footer.html b/html/footer.html index 69b2372c7..8fe2bf8da 100644 --- a/html/footer.html +++ b/html/footer.html @@ -1,7 +1,7 @@
API  •  - Github + Github  •  Gradio  •  diff --git a/javascript/aspectRatioOverlay.js b/javascript/aspectRatioOverlay.js index 2cf2d571f..90aa25c99 100644 --- a/javascript/aspectRatioOverlay.js +++ b/javascript/aspectRatioOverlay.js @@ -1,10 +1,8 @@ - -let currentWidth = null; -let currentHeight = null; -let arFrameTimeout = setTimeout(function() {}, 0); +let currentWidth; +let currentHeight; +let arFrameTimeout; function dimensionChange(e, is_width, is_height) { - if (is_width) { currentWidth = e.target.value * 1.0; } @@ -22,18 +20,18 @@ function dimensionChange(e, is_width, is_height) { var tabIndex = get_tab_index('mode_img2img'); if (tabIndex == 0) { // img2img - targetElement = gradioApp().querySelector('#img2img_image div[data-testid=image] img'); + targetElement = gradioApp().querySelector('#img2img_image div[data-testid=image] canvas'); } else if (tabIndex == 1) { //Sketch - targetElement = gradioApp().querySelector('#img2img_sketch div[data-testid=image] img'); + targetElement = gradioApp().querySelector('#img2img_sketch div[data-testid=image] canvas'); } else if (tabIndex == 2) { // Inpaint - targetElement = gradioApp().querySelector('#img2maskimg div[data-testid=image] img'); + targetElement = gradioApp().querySelector('#img2maskimg div[data-testid=image] canvas'); } else if (tabIndex == 3) { // Inpaint sketch - targetElement = gradioApp().querySelector('#inpaint_sketch div[data-testid=image] img'); + targetElement = gradioApp().querySelector('#inpaint_sketch div[data-testid=image] canvas'); + } else if (tabIndex == 4) { // Inpaint upload + targetElement = gradioApp().querySelector('#img_inpaint_base div[data-testid=image] img'); } - if (targetElement) { - var arPreviewRect = gradioApp().querySelector('#imageARPreview'); if (!arPreviewRect) { arPreviewRect = document.createElement('div'); @@ -41,26 +39,23 @@ function dimensionChange(e, is_width, is_height) { gradioApp().appendChild(arPreviewRect); } - - var viewportOffset = targetElement.getBoundingClientRect(); + var viewportscale = Math.min(targetElement.clientWidth / targetElement.width, targetElement.clientHeight / targetElement.height); - var viewportscale = Math.min(targetElement.clientWidth / targetElement.naturalWidth, targetElement.clientHeight / targetElement.naturalHeight); + var scaledx = targetElement.width * viewportscale; + var scaledy = targetElement.height * viewportscale; - var scaledx = targetElement.naturalWidth * viewportscale; - var scaledy = targetElement.naturalHeight * viewportscale; - - var cleintRectTop = (viewportOffset.top + window.scrollY); - var cleintRectLeft = (viewportOffset.left + window.scrollX); - var cleintRectCentreY = cleintRectTop + (targetElement.clientHeight / 2); - var cleintRectCentreX = cleintRectLeft + (targetElement.clientWidth / 2); + var clientRectTop = (viewportOffset.top + window.scrollY); + var clientRectLeft = (viewportOffset.left + window.scrollX); + var clientRectCentreY = clientRectTop + (targetElement.clientHeight / 2); + var clientRectCentreX = clientRectLeft + (targetElement.clientWidth / 2); var arscale = Math.min(scaledx / currentWidth, scaledy / currentHeight); var arscaledx = currentWidth * arscale; var arscaledy = currentHeight * arscale; - var arRectTop = cleintRectCentreY - (arscaledy / 2); - var arRectLeft = cleintRectCentreX - (arscaledx / 2); + var arRectTop = clientRectCentreY - (arscaledy / 2); + var arRectLeft = clientRectCentreX - (arscaledx / 2); var arRectWidth = arscaledx; var arRectHeight = arscaledy; @@ -75,21 +70,18 @@ function dimensionChange(e, is_width, is_height) { }, 2000); arPreviewRect.style.display = 'block'; - } - } - onAfterUiUpdate(function() { var arPreviewRect = gradioApp().querySelector('#imageARPreview'); if (arPreviewRect) { arPreviewRect.style.display = 'none'; } + var tabImg2img = gradioApp().querySelector("#tab_img2img"); if (tabImg2img) { - var inImg2img = tabImg2img.style.display == "block"; - if (inImg2img) { + if (tabImg2img.style.display == "block") { let inputs = gradioApp().querySelectorAll('input'); inputs.forEach(function(e) { var is_width = e.parentElement.id == "img2img_width"; diff --git a/javascript/contextMenus.js b/javascript/contextMenus.js index ccae242f2..e01fd67e8 100644 --- a/javascript/contextMenus.js +++ b/javascript/contextMenus.js @@ -8,9 +8,6 @@ var contextMenuInit = function() { }; function showContextMenu(event, element, menuEntries) { - let posx = event.clientX + document.body.scrollLeft + document.documentElement.scrollLeft; - let posy = event.clientY + document.body.scrollTop + document.documentElement.scrollTop; - let oldMenu = gradioApp().querySelector('#context-menu'); if (oldMenu) { oldMenu.remove(); @@ -23,10 +20,8 @@ var contextMenuInit = function() { contextMenu.style.background = baseStyle.background; contextMenu.style.color = baseStyle.color; contextMenu.style.fontFamily = baseStyle.fontFamily; - contextMenu.style.top = posy + 'px'; - contextMenu.style.left = posx + 'px'; - - + contextMenu.style.top = event.pageY + 'px'; + contextMenu.style.left = event.pageX + 'px'; const contextMenuList = document.createElement('ul'); contextMenuList.className = 'context-menu-items'; @@ -43,21 +38,6 @@ var contextMenuInit = function() { }); gradioApp().appendChild(contextMenu); - - let menuWidth = contextMenu.offsetWidth + 4; - let menuHeight = contextMenu.offsetHeight + 4; - - let windowWidth = window.innerWidth; - let windowHeight = window.innerHeight; - - if ((windowWidth - posx) < menuWidth) { - contextMenu.style.left = windowWidth - menuWidth + "px"; - } - - if ((windowHeight - posy) < menuHeight) { - contextMenu.style.top = windowHeight - menuHeight + "px"; - } - } function appendContextMenuOption(targetElementSelector, entryName, entryFunction) { @@ -107,16 +87,23 @@ var contextMenuInit = function() { oldMenu.remove(); } }); - gradioApp().addEventListener("contextmenu", function(e) { - let oldMenu = gradioApp().querySelector('#context-menu'); - if (oldMenu) { - oldMenu.remove(); - } - menuSpecs.forEach(function(v, k) { - if (e.composedPath()[0].matches(k)) { - showContextMenu(e, e.composedPath()[0], v); - e.preventDefault(); + ['contextmenu', 'touchstart'].forEach((eventType) => { + gradioApp().addEventListener(eventType, function(e) { + let ev = e; + if (eventType.startsWith('touch')) { + if (e.touches.length !== 2) return; + ev = e.touches[0]; + } + let oldMenu = gradioApp().querySelector('#context-menu'); + if (oldMenu) { + oldMenu.remove(); } + menuSpecs.forEach(function(v, k) { + if (e.composedPath()[0].matches(k)) { + showContextMenu(ev, e.composedPath()[0], v); + e.preventDefault(); + } + }); }); }); eventListenerApplied = true; diff --git a/javascript/dragdrop.js b/javascript/dragdrop.js index d680daf52..882562d73 100644 --- a/javascript/dragdrop.js +++ b/javascript/dragdrop.js @@ -56,6 +56,15 @@ function eventHasFiles(e) { return false; } +function isURL(url) { + try { + const _ = new URL(url); + return true; + } catch { + return false; + } +} + function dragDropTargetIsPrompt(target) { if (target?.placeholder && target?.placeholder.indexOf("Prompt") >= 0) return true; if (target?.parentNode?.parentNode?.className?.indexOf("prompt") > 0) return true; @@ -74,22 +83,39 @@ window.document.addEventListener('dragover', e => { e.dataTransfer.dropEffect = 'copy'; }); -window.document.addEventListener('drop', e => { +window.document.addEventListener('drop', async e => { const target = e.composedPath()[0]; - if (!eventHasFiles(e)) return; + const url = e.dataTransfer.getData('text/uri-list') || e.dataTransfer.getData('text/plain'); + if (!eventHasFiles(e) && !isURL(url)) return; if (dragDropTargetIsPrompt(target)) { e.stopPropagation(); e.preventDefault(); - let prompt_target = get_tab_index('tabs') == 1 ? "img2img_prompt_image" : "txt2img_prompt_image"; + const isImg2img = get_tab_index('tabs') == 1; + let prompt_image_target = isImg2img ? "img2img_prompt_image" : "txt2img_prompt_image"; - const imgParent = gradioApp().getElementById(prompt_target); + const imgParent = gradioApp().getElementById(prompt_image_target); const files = e.dataTransfer.files; const fileInput = imgParent.querySelector('input[type="file"]'); - if (fileInput) { + if (eventHasFiles(e) && fileInput) { fileInput.files = files; fileInput.dispatchEvent(new Event('change')); + } else if (url) { + try { + const request = await fetch(url); + if (!request.ok) { + console.error('Error fetching URL:', url, request.status); + return; + } + const data = new DataTransfer(); + data.items.add(new File([await request.blob()], 'image.png')); + fileInput.files = data.files; + fileInput.dispatchEvent(new Event('change')); + } catch (error) { + console.error('Error fetching URL:', url, error); + return; + } } } diff --git a/javascript/edit-attention.js b/javascript/edit-attention.js index 688c2f112..b07ba97cb 100644 --- a/javascript/edit-attention.js +++ b/javascript/edit-attention.js @@ -64,6 +64,14 @@ function keyupEditAttention(event) { selectionEnd++; } + // deselect surrounding whitespace + while (text[selectionStart] == " " && selectionStart < selectionEnd) { + selectionStart++; + } + while (text[selectionEnd - 1] == " " && selectionEnd > selectionStart) { + selectionEnd--; + } + target.setSelectionRange(selectionStart, selectionEnd); return true; } diff --git a/javascript/extraNetworks.js b/javascript/extraNetworks.js index d5855fe96..c5cced973 100644 --- a/javascript/extraNetworks.js +++ b/javascript/extraNetworks.js @@ -39,12 +39,12 @@ function setupExtraNetworksForTab(tabname) { // tabname_full = {tabname}_{extra_networks_tabname} var tabname_full = elem.id; var search = gradioApp().querySelector("#" + tabname_full + "_extra_search"); - var sort_mode = gradioApp().querySelector("#" + tabname_full + "_extra_sort"); var sort_dir = gradioApp().querySelector("#" + tabname_full + "_extra_sort_dir"); var refresh = gradioApp().querySelector("#" + tabname_full + "_extra_refresh"); + var currentSort = ''; // If any of the buttons above don't exist, we want to skip this iteration of the loop. - if (!search || !sort_mode || !sort_dir || !refresh) { + if (!search || !sort_dir || !refresh) { return; // `return` is equivalent of `continue` but for forEach loops. } @@ -52,7 +52,7 @@ function setupExtraNetworksForTab(tabname) { var searchTerm = search.value.toLowerCase(); gradioApp().querySelectorAll('#' + tabname + '_extra_tabs div.card').forEach(function(elem) { var searchOnly = elem.querySelector('.search_only'); - var text = Array.prototype.map.call(elem.querySelectorAll('.search_terms'), function(t) { + var text = Array.prototype.map.call(elem.querySelectorAll('.search_terms, .description'), function(t) { return t.textContent.toLowerCase(); }).join(" "); @@ -71,42 +71,46 @@ function setupExtraNetworksForTab(tabname) { }; var applySort = function(force) { - var cards = gradioApp().querySelectorAll('#' + tabname + '_extra_tabs div.card'); + var cards = gradioApp().querySelectorAll('#' + tabname_full + ' div.card'); + var parent = gradioApp().querySelector('#' + tabname_full + "_cards"); var reverse = sort_dir.dataset.sortdir == "Descending"; - var sortKey = sort_mode.dataset.sortmode.toLowerCase().replace("sort", "").replaceAll(" ", "_").replace(/_+$/, "").trim() || "name"; - sortKey = "sort" + sortKey.charAt(0).toUpperCase() + sortKey.slice(1); - var sortKeyStore = sortKey + "-" + (reverse ? "Descending" : "Ascending") + "-" + cards.length; + var activeSearchElem = gradioApp().querySelector('#' + tabname_full + "_controls .extra-network-control--sort.extra-network-control--enabled"); + var sortKey = activeSearchElem ? activeSearchElem.dataset.sortkey : "default"; + var sortKeyDataField = "sort" + sortKey.charAt(0).toUpperCase() + sortKey.slice(1); + var sortKeyStore = sortKey + "-" + sort_dir.dataset.sortdir + "-" + cards.length; - if (sortKeyStore == sort_mode.dataset.sortkey && !force) { + if (sortKeyStore == currentSort && !force) { return; } - sort_mode.dataset.sortkey = sortKeyStore; + currentSort = sortKeyStore; - cards.forEach(function(card) { - card.originalParentElement = card.parentElement; - }); var sortedCards = Array.from(cards); sortedCards.sort(function(cardA, cardB) { - var a = cardA.dataset[sortKey]; - var b = cardB.dataset[sortKey]; + var a = cardA.dataset[sortKeyDataField]; + var b = cardB.dataset[sortKeyDataField]; if (!isNaN(a) && !isNaN(b)) { return parseInt(a) - parseInt(b); } return (a < b ? -1 : (a > b ? 1 : 0)); }); + if (reverse) { sortedCards.reverse(); } - cards.forEach(function(card) { - card.remove(); - }); + + parent.innerHTML = ''; + + var frag = document.createDocumentFragment(); sortedCards.forEach(function(card) { - card.originalParentElement.appendChild(card); + frag.appendChild(card); }); + parent.appendChild(frag); }; - search.addEventListener("input", applyFilter); + search.addEventListener("input", function() { + applyFilter(); + }); applySort(); applyFilter(); extraNetworksApplySort[tabname_full] = applySort; @@ -272,6 +276,15 @@ function saveCardPreview(event, tabname, filename) { event.preventDefault(); } +function extraNetworksSearchButton(tabname, extra_networks_tabname, event) { + var searchTextarea = gradioApp().querySelector("#" + tabname + "_" + extra_networks_tabname + "_extra_search"); + var button = event.target; + var text = button.classList.contains("search-all") ? "" : button.textContent.trim(); + + searchTextarea.value = text; + updateInput(searchTextarea); +} + function extraNetworksTreeProcessFileClick(event, btn, tabname, extra_networks_tabname) { /** * Processes `onclick` events when user clicks on files in tree. @@ -290,7 +303,7 @@ function extraNetworksTreeProcessDirectoryClick(event, btn, tabname, extra_netwo * Processes `onclick` events when user clicks on directories in tree. * * Here is how the tree reacts to clicks for various states: - * unselected unopened directory: Diretory is selected and expanded. + * unselected unopened directory: Directory is selected and expanded. * unselected opened directory: Directory is selected. * selected opened directory: Directory is collapsed and deselected. * chevron is clicked: Directory is expanded or collapsed. Selected state unchanged. @@ -383,36 +396,17 @@ function extraNetworksTreeOnClick(event, tabname, extra_networks_tabname) { } function extraNetworksControlSortOnClick(event, tabname, extra_networks_tabname) { - /** - * Handles `onclick` events for the Sort Mode button. - * - * Modifies the data attributes of the Sort Mode button to cycle between - * various sorting modes. - * - * @param event The generated event. - * @param tabname The name of the active tab in the sd webui. Ex: txt2img, img2img, etc. - * @param extra_networks_tabname The id of the active extraNetworks tab. Ex: lora, checkpoints, etc. - */ - var curr_mode = event.currentTarget.dataset.sortmode; - var el_sort_dir = gradioApp().querySelector("#" + tabname + "_" + extra_networks_tabname + "_extra_sort_dir"); - var sort_dir = el_sort_dir.dataset.sortdir; - if (curr_mode == "path") { - event.currentTarget.dataset.sortmode = "name"; - event.currentTarget.dataset.sortkey = "sortName-" + sort_dir + "-640"; - event.currentTarget.setAttribute("title", "Sort by filename"); - } else if (curr_mode == "name") { - event.currentTarget.dataset.sortmode = "date_created"; - event.currentTarget.dataset.sortkey = "sortDate_created-" + sort_dir + "-640"; - event.currentTarget.setAttribute("title", "Sort by date created"); - } else if (curr_mode == "date_created") { - event.currentTarget.dataset.sortmode = "date_modified"; - event.currentTarget.dataset.sortkey = "sortDate_modified-" + sort_dir + "-640"; - event.currentTarget.setAttribute("title", "Sort by date modified"); - } else { - event.currentTarget.dataset.sortmode = "path"; - event.currentTarget.dataset.sortkey = "sortPath-" + sort_dir + "-640"; - event.currentTarget.setAttribute("title", "Sort by path"); - } + /** Handles `onclick` events for Sort Mode buttons. */ + + var self = event.currentTarget; + var parent = event.currentTarget.parentElement; + + parent.querySelectorAll('.extra-network-control--sort').forEach(function(x) { + x.classList.remove('extra-network-control--enabled'); + }); + + self.classList.add('extra-network-control--enabled'); + applyExtraNetworkSort(tabname + "_" + extra_networks_tabname); } @@ -447,8 +441,12 @@ function extraNetworksControlTreeViewOnClick(event, tabname, extra_networks_tabn * @param tabname The name of the active tab in the sd webui. Ex: txt2img, img2img, etc. * @param extra_networks_tabname The id of the active extraNetworks tab. Ex: lora, checkpoints, etc. */ - gradioApp().getElementById(tabname + "_" + extra_networks_tabname + "_tree").classList.toggle("hidden"); - event.currentTarget.classList.toggle("extra-network-control--enabled"); + var button = event.currentTarget; + button.classList.toggle("extra-network-control--enabled"); + var show = !button.classList.contains("extra-network-control--enabled"); + + var pane = gradioApp().getElementById(tabname + "_" + extra_networks_tabname + "_pane"); + pane.classList.toggle("extra-network-dirs-hidden", show); } function extraNetworksControlRefreshOnClick(event, tabname, extra_networks_tabname) { @@ -509,12 +507,76 @@ function popupId(id) { popup(storedPopupIds[id]); } +function extraNetworksFlattenMetadata(obj) { + const result = {}; + + // Convert any stringified JSON objects to actual objects + for (const key of Object.keys(obj)) { + if (typeof obj[key] === 'string') { + try { + const parsed = JSON.parse(obj[key]); + if (parsed && typeof parsed === 'object') { + obj[key] = parsed; + } + } catch (error) { + continue; + } + } + } + + // Flatten the object + for (const key of Object.keys(obj)) { + if (typeof obj[key] === 'object' && obj[key] !== null) { + const nested = extraNetworksFlattenMetadata(obj[key]); + for (const nestedKey of Object.keys(nested)) { + result[`${key}/${nestedKey}`] = nested[nestedKey]; + } + } else { + result[key] = obj[key]; + } + } + + // Special case for handling modelspec keys + for (const key of Object.keys(result)) { + if (key.startsWith("modelspec.")) { + result[key.replaceAll(".", "/")] = result[key]; + delete result[key]; + } + } + + // Add empty keys to designate hierarchy + for (const key of Object.keys(result)) { + const parts = key.split("/"); + for (let i = 1; i < parts.length; i++) { + const parent = parts.slice(0, i).join("/"); + if (!result[parent]) { + result[parent] = ""; + } + } + } + + return result; +} + function extraNetworksShowMetadata(text) { + try { + let parsed = JSON.parse(text); + if (parsed && typeof parsed === 'object') { + parsed = extraNetworksFlattenMetadata(parsed); + const table = createVisualizationTable(parsed, 0); + popup(table); + return; + } + } catch (error) { + console.error(error); + } + var elem = document.createElement('pre'); elem.classList.add('popup-metadata'); elem.textContent = text; popup(elem); + return; } function requestGet(url, data, handler, errorHandler) { @@ -543,16 +605,18 @@ function requestGet(url, data, handler, errorHandler) { xhr.send(js); } -function extraNetworksCopyCardPath(event, path) { - navigator.clipboard.writeText(path); +function extraNetworksCopyCardPath(event) { + navigator.clipboard.writeText(event.target.getAttribute("data-clipboard-text")); event.stopPropagation(); } -function extraNetworksRequestMetadata(event, extraPage, cardName) { +function extraNetworksRequestMetadata(event, extraPage) { var showError = function() { extraNetworksShowMetadata("there was an error getting metadata"); }; + var cardName = event.target.parentElement.parentElement.getAttribute("data-name"); + requestGet("./sd_extra_networks/metadata", {page: extraPage, item: cardName}, function(data) { if (data && data.metadata) { extraNetworksShowMetadata(data.metadata); @@ -566,7 +630,7 @@ function extraNetworksRequestMetadata(event, extraPage, cardName) { var extraPageUserMetadataEditors = {}; -function extraNetworksEditUserMetadata(event, tabname, extraPage, cardName) { +function extraNetworksEditUserMetadata(event, tabname, extraPage) { var id = tabname + '_' + extraPage + '_edit_user_metadata'; var editor = extraPageUserMetadataEditors[id]; @@ -578,6 +642,7 @@ function extraNetworksEditUserMetadata(event, tabname, extraPage, cardName) { extraPageUserMetadataEditors[id] = editor; } + var cardName = event.target.parentElement.parentElement.getAttribute("data-name"); editor.nameTextarea.value = cardName; updateInput(editor.nameTextarea); diff --git a/javascript/gradio.js b/javascript/gradio.js new file mode 100644 index 000000000..e68b98b04 --- /dev/null +++ b/javascript/gradio.js @@ -0,0 +1,7 @@ + +// added to fix a weird error in gradio 4.19 at page load +Object.defineProperty(Array.prototype, 'toLowerCase', { + value: function() { + return this; + } +}); diff --git a/javascript/imageviewer.js b/javascript/imageviewer.js index 625c5d148..ff673a02a 100644 --- a/javascript/imageviewer.js +++ b/javascript/imageviewer.js @@ -6,6 +6,8 @@ function closeModal() { function showModal(event) { const source = event.target || event.srcElement; const modalImage = gradioApp().getElementById("modalImage"); + const modalToggleLivePreviewBtn = gradioApp().getElementById("modal_toggle_live_preview"); + modalToggleLivePreviewBtn.innerHTML = opts.js_live_preview_in_modal_lightbox ? "🗇" : "🗆"; const lb = gradioApp().getElementById("lightboxModal"); modalImage.src = source.src; if (modalImage.style.display === 'none') { @@ -51,14 +53,7 @@ function modalImageSwitch(offset) { var galleryButtons = all_gallery_buttons(); if (galleryButtons.length > 1) { - var currentButton = selected_gallery_button(); - - var result = -1; - galleryButtons.forEach(function(v, i) { - if (v == currentButton) { - result = i; - } - }); + var result = selected_gallery_index(); if (result != -1) { var nextButton = galleryButtons[negmod((result + offset), galleryButtons.length)]; @@ -131,19 +126,15 @@ function setupImageForLightbox(e) { e.style.cursor = 'pointer'; e.style.userSelect = 'none'; - var isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1; - - // For Firefox, listening on click first switched to next image then shows the lightbox. - // If you know how to fix this without switching to mousedown event, please. - // For other browsers the event is click to make it possiblr to drag picture. - var event = isFirefox ? 'mousedown' : 'click'; - - e.addEventListener(event, function(evt) { + e.addEventListener('mousedown', function(evt) { if (evt.button == 1) { open(evt.target.src); evt.preventDefault(); return; } + }, true); + + e.addEventListener('click', function(evt) { if (!opts.js_modal_lightbox || evt.button != 0) return; modalZoomSet(gradioApp().getElementById('modalImage'), opts.js_modal_lightbox_initially_zoomed); @@ -163,6 +154,13 @@ function modalZoomToggle(event) { event.stopPropagation(); } +function modalLivePreviewToggle(event) { + const modalToggleLivePreview = gradioApp().getElementById("modal_toggle_live_preview"); + opts.js_live_preview_in_modal_lightbox = !opts.js_live_preview_in_modal_lightbox; + modalToggleLivePreview.innerHTML = opts.js_live_preview_in_modal_lightbox ? "🗇" : "🗆"; + event.stopPropagation(); +} + function modalTileImageToggle(event) { const modalImage = gradioApp().getElementById("modalImage"); const modal = gradioApp().getElementById("lightboxModal"); @@ -179,7 +177,7 @@ function modalTileImageToggle(event) { } onAfterUiUpdate(function() { - var fullImg_preview = gradioApp().querySelectorAll('.gradio-gallery > div > img'); + var fullImg_preview = gradioApp().querySelectorAll('.gradio-gallery > button > button > img'); if (fullImg_preview != null) { fullImg_preview.forEach(setupImageForLightbox); } @@ -220,6 +218,14 @@ document.addEventListener("DOMContentLoaded", function() { modalSave.title = "Save Image(s)"; modalControls.appendChild(modalSave); + const modalToggleLivePreview = document.createElement('span'); + modalToggleLivePreview.className = 'modalToggleLivePreview cursor'; + modalToggleLivePreview.id = "modal_toggle_live_preview"; + modalToggleLivePreview.innerHTML = "🗆"; + modalToggleLivePreview.onclick = modalLivePreviewToggle; + modalToggleLivePreview.title = "Toggle live preview"; + modalControls.appendChild(modalToggleLivePreview); + const modalClose = document.createElement('span'); modalClose.className = 'modalClose cursor'; modalClose.innerHTML = '×'; diff --git a/javascript/profilerVisualization.js b/javascript/profilerVisualization.js index 9d8e5f42f..9822f4b2a 100644 --- a/javascript/profilerVisualization.js +++ b/javascript/profilerVisualization.js @@ -33,120 +33,141 @@ function createRow(table, cellName, items) { return res; } -function showProfile(path, cutoff = 0.05) { - requestGet(path, {}, function(data) { - var table = document.createElement('table'); - table.className = 'popup-table'; - - data.records['total'] = data.total; - var keys = Object.keys(data.records).sort(function(a, b) { - return data.records[b] - data.records[a]; +function createVisualizationTable(data, cutoff = 0, sort = "") { + var table = document.createElement('table'); + table.className = 'popup-table'; + + var keys = Object.keys(data); + if (sort === "number") { + keys = keys.sort(function(a, b) { + return data[b] - data[a]; }); - var items = keys.map(function(x) { - return {key: x, parts: x.split('/'), time: data.records[x]}; + } else { + keys = keys.sort(); + } + var items = keys.map(function(x) { + return {key: x, parts: x.split('/'), value: data[x]}; + }); + var maxLength = items.reduce(function(a, b) { + return Math.max(a, b.parts.length); + }, 0); + + var cols = createRow( + table, + 'th', + [ + cutoff === 0 ? 'key' : 'record', + cutoff === 0 ? 'value' : 'seconds' + ] + ); + cols[0].colSpan = maxLength; + + function arraysEqual(a, b) { + return !(a < b || b < a); + } + + var addLevel = function(level, parent, hide) { + var matching = items.filter(function(x) { + return x.parts[level] && !x.parts[level + 1] && arraysEqual(x.parts.slice(0, level), parent); }); - var maxLength = items.reduce(function(a, b) { - return Math.max(a, b.parts.length); - }, 0); - - var cols = createRow(table, 'th', ['record', 'seconds']); - cols[0].colSpan = maxLength; - - function arraysEqual(a, b) { - return !(a < b || b < a); + if (sort === "number") { + matching = matching.sort(function(a, b) { + return b.value - a.value; + }); + } else { + matching = matching.sort(); } + var othersTime = 0; + var othersList = []; + var othersRows = []; + var childrenRows = []; + matching.forEach(function(x) { + var visible = (cutoff === 0 && !hide) || (x.value >= cutoff && !hide); + + var cells = []; + for (var i = 0; i < maxLength; i++) { + cells.push(x.parts[i]); + } + cells.push(cutoff === 0 ? x.value : x.value.toFixed(3)); + var cols = createRow(table, 'td', cells); + for (i = 0; i < level; i++) { + cols[i].className = 'muted'; + } - var addLevel = function(level, parent, hide) { - var matching = items.filter(function(x) { - return x.parts[level] && !x.parts[level + 1] && arraysEqual(x.parts.slice(0, level), parent); - }); - var sorted = matching.sort(function(a, b) { - return b.time - a.time; - }); - var othersTime = 0; - var othersList = []; - var othersRows = []; - var childrenRows = []; - sorted.forEach(function(x) { - var visible = x.time >= cutoff && !hide; - - var cells = []; - for (var i = 0; i < maxLength; i++) { - cells.push(x.parts[i]); - } - cells.push(x.time.toFixed(3)); - var cols = createRow(table, 'td', cells); - for (i = 0; i < level; i++) { - cols[i].className = 'muted'; - } - - var tr = cols[0].parentNode; - if (!visible) { - tr.classList.add("hidden"); - } - - if (x.time >= cutoff) { - childrenRows.push(tr); - } else { - othersTime += x.time; - othersList.push(x.parts[level]); - othersRows.push(tr); - } - - var children = addLevel(level + 1, parent.concat([x.parts[level]]), true); - if (children.length > 0) { - var cell = cols[level]; - var onclick = function() { - cell.classList.remove("link"); - cell.removeEventListener("click", onclick); - children.forEach(function(x) { - x.classList.remove("hidden"); - }); - }; - cell.classList.add("link"); - cell.addEventListener("click", onclick); - } - }); + var tr = cols[0].parentNode; + if (!visible) { + tr.classList.add("hidden"); + } - if (othersTime > 0) { - var cells = []; - for (var i = 0; i < maxLength; i++) { - cells.push(parent[i]); - } - cells.push(othersTime.toFixed(3)); - cells[level] = 'others'; - var cols = createRow(table, 'td', cells); - for (i = 0; i < level; i++) { - cols[i].className = 'muted'; - } + if (cutoff === 0 || x.value >= cutoff) { + childrenRows.push(tr); + } else { + othersTime += x.value; + othersList.push(x.parts[level]); + othersRows.push(tr); + } + var children = addLevel(level + 1, parent.concat([x.parts[level]]), true); + if (children.length > 0) { var cell = cols[level]; - var tr = cell.parentNode; var onclick = function() { - tr.classList.add("hidden"); cell.classList.remove("link"); cell.removeEventListener("click", onclick); - othersRows.forEach(function(x) { + children.forEach(function(x) { x.classList.remove("hidden"); }); }; - - cell.title = othersList.join(", "); cell.classList.add("link"); cell.addEventListener("click", onclick); + } + }); - if (hide) { - tr.classList.add("hidden"); - } + if (othersTime > 0) { + var cells = []; + for (var i = 0; i < maxLength; i++) { + cells.push(parent[i]); + } + cells.push(othersTime.toFixed(3)); + cells[level] = 'others'; + var cols = createRow(table, 'td', cells); + for (i = 0; i < level; i++) { + cols[i].className = 'muted'; + } - childrenRows.push(tr); + var cell = cols[level]; + var tr = cell.parentNode; + var onclick = function() { + tr.classList.add("hidden"); + cell.classList.remove("link"); + cell.removeEventListener("click", onclick); + othersRows.forEach(function(x) { + x.classList.remove("hidden"); + }); + }; + + cell.title = othersList.join(", "); + cell.classList.add("link"); + cell.addEventListener("click", onclick); + + if (hide) { + tr.classList.add("hidden"); } - return childrenRows; - }; + childrenRows.push(tr); + } + + return childrenRows; + }; - addLevel(0, []); + addLevel(0, []); + + return table; +} +function showProfile(path, cutoff = 0.05) { + requestGet(path, {}, function(data) { + data.records['total'] = data.total; + const table = createVisualizationTable(data.records, cutoff, "number"); popup(table); }); } diff --git a/javascript/progressbar.js b/javascript/progressbar.js index f068bac6a..23dea64ce 100644 --- a/javascript/progressbar.js +++ b/javascript/progressbar.js @@ -76,6 +76,26 @@ function requestProgress(id_task, progressbarContainer, gallery, atEnd, onProgre var dateStart = new Date(); var wasEverActive = false; var parentProgressbar = progressbarContainer.parentNode; + var wakeLock = null; + + var requestWakeLock = async function() { + if (!opts.prevent_screen_sleep_during_generation || wakeLock) return; + try { + wakeLock = await navigator.wakeLock.request('screen'); + } catch (err) { + console.error('Wake Lock is not supported.'); + } + }; + + var releaseWakeLock = async function() { + if (!opts.prevent_screen_sleep_during_generation || !wakeLock) return; + try { + await wakeLock.release(); + wakeLock = null; + } catch (err) { + console.error('Wake Lock release failed', err); + } + }; var divProgress = document.createElement('div'); divProgress.className = 'progressDiv'; @@ -89,6 +109,7 @@ function requestProgress(id_task, progressbarContainer, gallery, atEnd, onProgre var livePreview = null; var removeProgressBar = function() { + releaseWakeLock(); if (!divProgress) return; setTitle(""); @@ -100,6 +121,7 @@ function requestProgress(id_task, progressbarContainer, gallery, atEnd, onProgre }; var funProgress = function(id_task) { + requestWakeLock(); request("./internal/progress", {id_task: id_task, live_preview: false}, function(res) { if (res.completed) { removeProgressBar(); diff --git a/javascript/resizeHandle.js b/javascript/resizeHandle.js index 6560372cc..4aeb14b41 100644 --- a/javascript/resizeHandle.js +++ b/javascript/resizeHandle.js @@ -2,6 +2,7 @@ const GRADIO_MIN_WIDTH = 320; const PAD = 16; const DEBOUNCE_TIME = 100; + const DOUBLE_TAP_DELAY = 200; //ms const R = { tracking: false, @@ -10,6 +11,7 @@ leftCol: null, leftColStartWidth: null, screenX: null, + lastTapTime: null, }; let resizeTimer; @@ -20,6 +22,9 @@ } function displayResizeHandle(parent) { + if (!parent.needHideOnMoblie) { + return true; + } if (window.innerWidth < GRADIO_MIN_WIDTH * 2 + PAD * 4) { parent.style.display = 'flex'; parent.resizeHandle.style.display = "none"; @@ -39,7 +44,7 @@ const ratio = newParentWidth / oldParentWidth; - const newWidthL = Math.max(Math.floor(ratio * widthL), GRADIO_MIN_WIDTH); + const newWidthL = Math.max(Math.floor(ratio * widthL), parent.minLeftColWidth); setLeftColGridTemplate(parent, newWidthL); R.parentWidth = newParentWidth; @@ -47,6 +52,14 @@ } function setup(parent) { + + function onDoubleClick(evt) { + evt.preventDefault(); + evt.stopPropagation(); + + parent.style.gridTemplateColumns = parent.style.originalGridTemplateColumns; + } + const leftCol = parent.firstElementChild; const rightCol = parent.lastElementChild; @@ -54,7 +67,24 @@ parent.style.display = 'grid'; parent.style.gap = '0'; - const gridTemplateColumns = `${parent.children[0].style.flexGrow}fr ${PAD}px ${parent.children[1].style.flexGrow}fr`; + let leftColTemplate = ""; + if (parent.children[0].style.flexGrow) { + leftColTemplate = `${parent.children[0].style.flexGrow}fr`; + parent.minLeftColWidth = GRADIO_MIN_WIDTH; + parent.minRightColWidth = GRADIO_MIN_WIDTH; + parent.needHideOnMoblie = true; + } else { + leftColTemplate = parent.children[0].style.flexBasis; + parent.minLeftColWidth = parent.children[0].style.flexBasis.slice(0, -2) / 2; + parent.minRightColWidth = 0; + parent.needHideOnMoblie = false; + } + + if (!leftColTemplate) { + leftColTemplate = '1fr'; + } + + const gridTemplateColumns = `${leftColTemplate} ${PAD}px ${parent.children[1].style.flexGrow}fr`; parent.style.gridTemplateColumns = gridTemplateColumns; parent.style.originalGridTemplateColumns = gridTemplateColumns; @@ -69,6 +99,14 @@ if (evt.button !== 0) return; } else { if (evt.changedTouches.length !== 1) return; + + const currentTime = new Date().getTime(); + if (R.lastTapTime && currentTime - R.lastTapTime <= DOUBLE_TAP_DELAY) { + onDoubleClick(evt); + return; + } + + R.lastTapTime = currentTime; } evt.preventDefault(); @@ -89,12 +127,7 @@ }); }); - resizeHandle.addEventListener('dblclick', (evt) => { - evt.preventDefault(); - evt.stopPropagation(); - - parent.style.gridTemplateColumns = parent.style.originalGridTemplateColumns; - }); + resizeHandle.addEventListener('dblclick', onDoubleClick); afterResize(parent); } @@ -119,7 +152,7 @@ } else { delta = R.screenX - evt.changedTouches[0].screenX; } - const leftColWidth = Math.max(Math.min(R.leftColStartWidth - delta, R.parent.offsetWidth - GRADIO_MIN_WIDTH - PAD), GRADIO_MIN_WIDTH); + const leftColWidth = Math.max(Math.min(R.leftColStartWidth - delta, R.parent.offsetWidth - R.parent.minRightColWidth - PAD), R.parent.minLeftColWidth); setLeftColGridTemplate(R.parent, leftColWidth); } }); @@ -158,10 +191,15 @@ setupResizeHandle = setup; })(); -onUiLoaded(function() { + +function setupAllResizeHandles() { for (var elem of gradioApp().querySelectorAll('.resize-handle-row')) { - if (!elem.querySelector('.resize-handle')) { + if (!elem.querySelector('.resize-handle') && !elem.children[0].classList.contains("hidden")) { setupResizeHandle(elem); } } -}); +} + + +onUiLoaded(setupAllResizeHandles); + diff --git a/javascript/ui.js b/javascript/ui.js index f2adc7dd8..2c5db4838 100644 --- a/javascript/ui.js +++ b/javascript/ui.js @@ -26,13 +26,18 @@ function selected_gallery_index() { return all_gallery_buttons().findIndex(elem => elem.classList.contains('selected')); } +function gallery_container_buttons(gallery_container) { + return gradioApp().querySelectorAll(`#${gallery_container} .thumbnail-item.thumbnail-small`); +} + +function selected_gallery_index_id(gallery_container) { + return Array.from(gallery_container_buttons(gallery_container)).findIndex(elem => elem.classList.contains('selected')); +} + function extract_image_from_gallery(gallery) { if (gallery.length == 0) { return [null]; } - if (gallery.length == 1) { - return [gallery[0]]; - } var index = selected_gallery_index(); @@ -41,7 +46,7 @@ function extract_image_from_gallery(gallery) { index = 0; } - return [gallery[index]]; + return [[gallery[index]]]; } window.args_to_array = Array.from; // Compatibility with e.g. extensions that may expect this to be around @@ -113,14 +118,6 @@ function get_img2img_tab_index() { function create_submit_args(args) { var res = Array.from(args); - // As it is currently, txt2img and img2img send back the previous output args (txt2img_gallery, generation_info, html_info) whenever you generate a new image. - // This can lead to uploading a huge gallery of previously generated images, which leads to an unnecessary delay between submitting and beginning to generate. - // I don't know why gradio is sending outputs along with inputs, but we can prevent sending the image gallery here, which seems to be an issue for some. - // If gradio at some point stops sending outputs, this may break something - if (Array.isArray(res[res.length - 3])) { - res[res.length - 3] = null; - } - return res; } @@ -141,11 +138,10 @@ function showSubmitInterruptingPlaceholder(tabname) { function showRestoreProgressButton(tabname, show) { var button = gradioApp().getElementById(tabname + "_restore_progress"); if (!button) return; - - button.style.display = show ? "flex" : "none"; + button.style.setProperty('display', show ? 'flex' : 'none', 'important'); } -function submit() { +function submit(args) { showSubmitButtons('txt2img', false); var id = randomId(); @@ -157,22 +153,22 @@ function submit() { showRestoreProgressButton('txt2img', false); }); - var res = create_submit_args(arguments); + var res = create_submit_args(args); res[0] = id; return res; } -function submit_txt2img_upscale() { - var res = submit(...arguments); +function submit_txt2img_upscale(args) { + var res = submit(...args); res[2] = selected_gallery_index(); return res; } -function submit_img2img() { +function submit_img2img(args) { showSubmitButtons('img2img', false); var id = randomId(); @@ -184,15 +180,14 @@ function submit_img2img() { showRestoreProgressButton('img2img', false); }); - var res = create_submit_args(arguments); + var res = create_submit_args(args); res[0] = id; - res[1] = get_tab_index('mode_img2img'); return res; } -function submit_extras() { +function submit_extras(args) { showSubmitButtons('extras', false); var id = randomId(); @@ -201,11 +196,10 @@ function submit_extras() { showSubmitButtons('extras', true); }); - var res = create_submit_args(arguments); + var res = create_submit_args(args); res[0] = id; - console.log(res); return res; } @@ -214,6 +208,7 @@ function restoreProgressTxt2img() { var id = localGet("txt2img_task_id"); if (id) { + showSubmitInterruptingPlaceholder('txt2img'); requestProgress(id, gradioApp().getElementById('txt2img_gallery_container'), gradioApp().getElementById('txt2img_gallery'), function() { showSubmitButtons('txt2img', true); }, null, 0); @@ -228,6 +223,7 @@ function restoreProgressImg2img() { var id = localGet("img2img_task_id"); if (id) { + showSubmitInterruptingPlaceholder('img2img'); requestProgress(id, gradioApp().getElementById('img2img_gallery_container'), gradioApp().getElementById('img2img_gallery'), function() { showSubmitButtons('img2img', true); }, null, 0); @@ -303,6 +299,7 @@ onAfterUiUpdate(function() { var jsdata = textarea.value; opts = JSON.parse(jsdata); + executeCallbacks(optionsAvailableCallbacks); /*global optionsAvailableCallbacks*/ executeCallbacks(optionsChangedCallbacks); /*global optionsChangedCallbacks*/ Object.defineProperty(textarea, 'value', { @@ -341,8 +338,8 @@ onOptionsChanged(function() { let txt2img_textarea, img2img_textarea = undefined; function restart_reload() { + document.body.style.backgroundColor = "var(--background-fill-primary)"; document.body.innerHTML = '

Reloading...

'; - var requestPing = function() { requestGet("./internal/ping", {}, function(data) { location.reload(); @@ -371,9 +368,9 @@ function selectCheckpoint(name) { gradioApp().getElementById('change_checkpoint').click(); } -function currentImg2imgSourceResolution(w, h, scaleBy) { - var img = gradioApp().querySelector('#mode_img2img > div[style="display: block;"] img'); - return img ? [img.naturalWidth, img.naturalHeight, scaleBy] : [0, 0, scaleBy]; +function currentImg2imgSourceResolution(w, h, r) { + var img = gradioApp().querySelector('#mode_img2img > div[style="display: block;"] :is(img, canvas)'); + return img ? [img.naturalWidth || img.width, img.naturalHeight || img.height, r] : [0, 0, r]; } function updateImg2imgResizeToTextAfterChangingImage() { @@ -416,7 +413,7 @@ function switchWidthHeight(tabname) { var onEditTimers = {}; -// calls func after afterMs milliseconds has passed since the input elem has beed enited by user +// calls func after afterMs milliseconds has passed since the input elem has been edited by user function onEdit(editId, elem, afterMs, func) { var edited = function() { var existingTimer = onEditTimers[editId]; diff --git a/javascript/ui_settings_hints.js b/javascript/ui_settings_hints.js index d088f9494..c3984bd02 100644 --- a/javascript/ui_settings_hints.js +++ b/javascript/ui_settings_hints.js @@ -14,10 +14,16 @@ onOptionsChanged(function() { if (!commentBefore && !commentAfter) return; var span = null; - if (div.classList.contains('gradio-checkbox')) span = div.querySelector('label span'); - else if (div.classList.contains('gradio-checkboxgroup')) span = div.querySelector('span').firstChild; - else if (div.classList.contains('gradio-radio')) span = div.querySelector('span').firstChild; - else span = div.querySelector('label span').firstChild; + if (div.classList.contains('gradio-checkbox')) { + span = div.querySelector('label span'); + } else if (div.classList.contains('gradio-checkboxgroup')) { + span = div.querySelector('span').firstChild; + } else if (div.classList.contains('gradio-radio')) { + span = div.querySelector('span').firstChild; + } else { + var elem = div.querySelector('label span'); + if (elem) span = elem.firstChild; + } if (!span) return; diff --git a/ldm_patched/ldm/models/autoencoder.py b/ldm_patched/ldm/models/autoencoder.py index fadefee82..d97899d40 100644 --- a/ldm_patched/ldm/models/autoencoder.py +++ b/ldm_patched/ldm/models/autoencoder.py @@ -182,7 +182,7 @@ def get_autoencoder_params(self) -> list: return params def encode( - self, x: torch.Tensor, return_reg_log: bool = False + self, x: torch.Tensor, regulation=None, return_reg_log: bool = False ) -> Union[torch.Tensor, Tuple[torch.Tensor, dict]]: if self.max_batch_size is None: z = self.encoder(x) @@ -198,7 +198,7 @@ def encode( z.append(z_batch) z = torch.cat(z, 0) - z, reg_log = self.regularization(z) + z, reg_log = self.regularization(z) if regulation is None else regulation(z) if return_reg_log: return z, reg_log return z diff --git a/ldm_patched/modules/model_patcher.py b/ldm_patched/modules/model_patcher.py index d1093dc6d..02d992964 100644 --- a/ldm_patched/modules/model_patcher.py +++ b/ldm_patched/modules/model_patcher.py @@ -81,6 +81,9 @@ def set_model_vae_encode_wrapper(self, wrapper_function): def set_model_vae_decode_wrapper(self, wrapper_function): self.model_options["model_vae_decode_wrapper"] = wrapper_function + def set_model_vae_regulation(self, vae_regulation): + self.model_options["model_vae_regulation"] = vae_regulation + def set_model_patch(self, patch, name): to = self.model_options["transformer_options"] if "patches" not in to: diff --git a/ldm_patched/modules/sd.py b/ldm_patched/modules/sd.py index 2830cc721..934c0092a 100644 --- a/ldm_patched/modules/sd.py +++ b/ldm_patched/modules/sd.py @@ -296,6 +296,8 @@ def encode_inner(self, pixel_samples): if model_management.VAE_ALWAYS_TILED: return self.encode_tiled(pixel_samples) + regulation = self.patcher.model_options.get("model_vae_regulation", None) + pixel_samples = pixel_samples.movedim(-1,1) try: memory_used = self.memory_used_encode(pixel_samples.shape, self.vae_dtype) @@ -306,7 +308,7 @@ def encode_inner(self, pixel_samples): samples = torch.empty((pixel_samples.shape[0], self.latent_channels, round(pixel_samples.shape[2] // self.downscale_ratio), round(pixel_samples.shape[3] // self.downscale_ratio)), device=self.output_device) for x in range(0, pixel_samples.shape[0], batch_number): pixels_in = (2. * pixel_samples[x:x+batch_number] - 1.).to(self.vae_dtype).to(self.device) - samples[x:x+batch_number] = self.first_stage_model.encode(pixels_in).to(self.output_device).float() + samples[x:x+batch_number] = self.first_stage_model.encode(pixels_in, regulation).to(self.output_device).float() except model_management.OOM_EXCEPTION as e: print("Warning: Ran out of memory when regular VAE encoding, retrying with tiled VAE encoding.") diff --git a/modules/api/api.py b/modules/api/api.py index d5348bb24..78d109697 100644 --- a/modules/api/api.py +++ b/modules/api/api.py @@ -2,13 +2,11 @@ import io import os import time -import itertools import datetime import uvicorn import ipaddress import requests import gradio as gr -import numpy as np from threading import Lock from io import BytesIO from fastapi import APIRouter, Depends, FastAPI, Request, Response @@ -19,13 +17,13 @@ from secrets import compare_digest import modules.shared as shared -from modules import sd_samplers, deepbooru, sd_hijack, images, scripts, ui, postprocessing, errors, restart, shared_items, script_callbacks, infotext_utils, sd_models +from modules import sd_samplers, deepbooru, sd_hijack, images, scripts, ui, postprocessing, errors, restart, shared_items, script_callbacks, infotext_utils, sd_models, sd_schedulers from modules.api import models from modules.shared import opts from modules.processing import StableDiffusionProcessingTxt2Img, StableDiffusionProcessingImg2Img, process_images from modules.textual_inversion.textual_inversion import create_embedding, train_embedding from modules.hypernetworks.hypernetwork import create_hypernetwork, train_hypernetwork -from PIL import PngImagePlugin, Image +from PIL import PngImagePlugin from modules.sd_models_config import find_checkpoint_config_near_filename from modules.realesrgan_model import get_realesrgan_models from modules import devices @@ -45,7 +43,7 @@ def script_name_to_index(name, scripts): def validate_sampler_name(name): config = sd_samplers.all_samplers_map.get(name, None) if config is None: - raise HTTPException(status_code=404, detail="Sampler not found") + raise HTTPException(status_code=400, detail="Sampler not found") return name @@ -87,7 +85,7 @@ def decode_base64_to_image(encoding): headers = {'user-agent': opts.api_useragent} if opts.api_useragent else {} response = requests.get(encoding, timeout=30, headers=headers) try: - image = Image.open(BytesIO(response.content)) + image = images.read(BytesIO(response.content)) return image except Exception as e: raise HTTPException(status_code=500, detail="Invalid image url") from e @@ -95,7 +93,7 @@ def decode_base64_to_image(encoding): if encoding.startswith("data:image/"): encoding = encoding.split(";")[1].split(",")[1] try: - image = Image.open(BytesIO(base64.b64decode(encoding))) + image = images.read(BytesIO(base64.b64decode(encoding))) return image except Exception as e: raise HTTPException(status_code=500, detail="Invalid encoded image") from e @@ -105,8 +103,6 @@ def encode_pil_to_base64(image): with io.BytesIO() as output_bytes: if isinstance(image, str): return image - if isinstance(image, np.ndarray): - image = Image.fromarray(image) if opts.samples_format.lower() == 'png': use_metadata = False metadata = PngImagePlugin.PngInfo() @@ -117,7 +113,7 @@ def encode_pil_to_base64(image): image.save(output_bytes, format="PNG", pnginfo=(metadata if use_metadata else None), quality=opts.jpeg_quality) elif opts.samples_format.lower() in ("jpg", "jpeg", "webp"): - if image.mode == "RGBA": + if image.mode in ("RGBA", "P"): image = image.convert("RGB") parameters = image.info.get('parameters', None) exif_bytes = piexif.dump({ @@ -211,7 +207,7 @@ def __init__(self, app: FastAPI, queue_lock: Lock): self.router = APIRouter() self.app = app self.queue_lock = queue_lock - api_middleware(self.app) + #api_middleware(self.app) # XXX this will have to be fixed self.add_api_route("/sdapi/v1/txt2img", self.text2imgapi, methods=["POST"], response_model=models.TextToImageResponse) self.add_api_route("/sdapi/v1/img2img", self.img2imgapi, methods=["POST"], response_model=models.ImageToImageResponse) self.add_api_route("/sdapi/v1/extra-single-image", self.extras_single_image_api, methods=["POST"], response_model=models.ExtrasSingleImageResponse) @@ -225,6 +221,7 @@ def __init__(self, app: FastAPI, queue_lock: Lock): self.add_api_route("/sdapi/v1/options", self.set_config, methods=["POST"]) self.add_api_route("/sdapi/v1/cmd-flags", self.get_cmd_flags, methods=["GET"], response_model=models.FlagsModel) self.add_api_route("/sdapi/v1/samplers", self.get_samplers, methods=["GET"], response_model=list[models.SamplerItem]) + self.add_api_route("/sdapi/v1/schedulers", self.get_schedulers, methods=["GET"], response_model=list[models.SchedulerItem]) self.add_api_route("/sdapi/v1/upscalers", self.get_upscalers, methods=["GET"], response_model=list[models.UpscalerItem]) self.add_api_route("/sdapi/v1/latent-upscale-modes", self.get_latent_upscale_modes, methods=["GET"], response_model=list[models.LatentUpscalerModeItem]) self.add_api_route("/sdapi/v1/sd-models", self.get_sd_models, methods=["GET"], response_model=list[models.SDModelItem]) @@ -364,7 +361,7 @@ def init_script_args(self, request, default_script_args, selectable_scripts, sel return script_args def apply_infotext(self, request, tabname, *, script_runner=None, mentioned_script_args=None): - """Processes `infotext` field from the `request`, and sets other fields of the `request` accoring to what's in infotext. + """Processes `infotext` field from the `request`, and sets other fields of the `request` according to what's in infotext. If request already has a field set, and that field is encountered in infotext too, the value from infotext is ignored. @@ -375,7 +372,7 @@ def apply_infotext(self, request, tabname, *, script_runner=None, mentioned_scri return {} possible_fields = infotext_utils.paste_fields[tabname]["fields"] - set_fields = request.model_dump(exclude_unset=True) if hasattr(request, "request") else request.dict(exclude_unset=True) # pydantic v1/v2 have differenrt names for this + set_fields = request.model_dump(exclude_unset=True) if hasattr(request, "request") else request.dict(exclude_unset=True) # pydantic v1/v2 have different names for this params = infotext_utils.parse_generation_parameters(request.infotext) def get_field_value(field, params): @@ -413,8 +410,8 @@ def get_field_value(field, params): if request.override_settings is None: request.override_settings = {} - overriden_settings = infotext_utils.get_override_settings(params) - for _, setting_name, value in overriden_settings: + overridden_settings = infotext_utils.get_override_settings(params) + for _, setting_name, value in overridden_settings: if setting_name not in request.override_settings: request.override_settings[setting_name] = value @@ -441,15 +438,19 @@ def text2imgapi(self, txt2imgreq: models.StableDiffusionTxt2ImgProcessingAPI): self.apply_infotext(txt2imgreq, "txt2img", script_runner=script_runner, mentioned_script_args=infotext_script_args) selectable_scripts, selectable_script_idx = self.get_selectable_script(txt2imgreq.script_name, script_runner) + sampler, scheduler = sd_samplers.get_sampler_and_scheduler(txt2imgreq.sampler_name or txt2imgreq.sampler_index, txt2imgreq.scheduler) populate = txt2imgreq.copy(update={ # Override __init__ params - "sampler_name": validate_sampler_name(txt2imgreq.sampler_name or txt2imgreq.sampler_index), + "sampler_name": validate_sampler_name(sampler), "do_not_save_samples": not txt2imgreq.save_images, "do_not_save_grid": not txt2imgreq.save_images, }) if populate.sampler_name: populate.sampler_index = None # prevent a warning later on + if not populate.scheduler and scheduler != "Automatic": + populate.scheduler = scheduler + args = vars(populate) args.pop('script_name', None) args.pop('script_args', None) # will refeed them to the pipeline directly after initializing them @@ -484,11 +485,7 @@ def text2imgapi(self, txt2imgreq: models.StableDiffusionTxt2ImgProcessingAPI): shared.state.end() shared.total_tqdm.clear() - b64images = [ - encode_pil_to_base64(image) - for image in itertools.chain(processed.images, processed.extra_images) - if send_images - ] + b64images = list(map(encode_pil_to_base64, processed.images)) if send_images else [] return models.TextToImageResponse(images=b64images, parameters=vars(txt2imgreq), info=processed.js()) @@ -509,9 +506,10 @@ def img2imgapi(self, img2imgreq: models.StableDiffusionImg2ImgProcessingAPI): self.apply_infotext(img2imgreq, "img2img", script_runner=script_runner, mentioned_script_args=infotext_script_args) selectable_scripts, selectable_script_idx = self.get_selectable_script(img2imgreq.script_name, script_runner) + sampler, scheduler = sd_samplers.get_sampler_and_scheduler(img2imgreq.sampler_name or img2imgreq.sampler_index, img2imgreq.scheduler) populate = img2imgreq.copy(update={ # Override __init__ params - "sampler_name": validate_sampler_name(img2imgreq.sampler_name or img2imgreq.sampler_index), + "sampler_name": validate_sampler_name(sampler), "do_not_save_samples": not img2imgreq.save_images, "do_not_save_grid": not img2imgreq.save_images, "mask": mask, @@ -519,6 +517,9 @@ def img2imgapi(self, img2imgreq: models.StableDiffusionImg2ImgProcessingAPI): if populate.sampler_name: populate.sampler_index = None # prevent a warning later on + if not populate.scheduler and scheduler != "Automatic": + populate.scheduler = scheduler + args = vars(populate) args.pop('include_init_images', None) # this is meant to be done by "exclude": True in model, but it's for a reason that I cannot determine. args.pop('script_name', None) @@ -555,11 +556,7 @@ def img2imgapi(self, img2imgreq: models.StableDiffusionImg2ImgProcessingAPI): shared.state.end() shared.total_tqdm.clear() - b64images = [ - encode_pil_to_base64(image) - for image in itertools.chain(processed.images, processed.extra_images) - if send_images - ] + b64images = list(map(encode_pil_to_base64, processed.images)) if send_images else [] if not img2imgreq.include_init_images: img2imgreq.init_images = None @@ -695,6 +692,17 @@ def get_cmd_flags(self): def get_samplers(self): return [{"name": sampler[0], "aliases":sampler[2], "options":sampler[3]} for sampler in sd_samplers.all_samplers] + def get_schedulers(self): + return [ + { + "name": scheduler.name, + "label": scheduler.label, + "aliases": scheduler.aliases, + "default_rho": scheduler.default_rho, + "need_inner_model": scheduler.need_inner_model, + } + for scheduler in sd_schedulers.schedulers] + def get_upscalers(self): return [ { diff --git a/modules/api/models.py b/modules/api/models.py index 16edf11cf..f44e5dca0 100644 --- a/modules/api/models.py +++ b/modules/api/models.py @@ -1,6 +1,6 @@ import inspect -from pydantic import BaseModel, Field, create_model +from pydantic import BaseModel, Field, create_model, ConfigDict from typing import Any, Optional, Literal from inflection import underscore from modules.processing import StableDiffusionProcessingTxt2Img, StableDiffusionProcessingImg2Img @@ -92,9 +92,7 @@ def generate_model(self): fields = { d.field: (d.field_type, Field(default=d.field_value, alias=d.field_alias, exclude=d.field_exclude)) for d in self._model_def } - DynamicModel = create_model(self._model_name, **fields) - DynamicModel.__config__.allow_population_by_field_name = True - DynamicModel.__config__.allow_mutation = True + DynamicModel = create_model(self._model_name, __config__=ConfigDict(populate_by_name=True, frozen=False), **fields) return DynamicModel StableDiffusionTxt2ImgProcessingAPI = PydanticModelGenerator( @@ -102,13 +100,13 @@ def generate_model(self): StableDiffusionProcessingTxt2Img, [ {"key": "sampler_index", "type": str, "default": "Euler"}, - {"key": "script_name", "type": str, "default": None}, + {"key": "script_name", "type": str | None, "default": None}, {"key": "script_args", "type": list, "default": []}, {"key": "send_images", "type": bool, "default": True}, {"key": "save_images", "type": bool, "default": False}, {"key": "alwayson_scripts", "type": dict, "default": {}}, - {"key": "force_task_id", "type": str, "default": None}, - {"key": "infotext", "type": str, "default": None}, + {"key": "force_task_id", "type": str | None, "default": None}, + {"key": "infotext", "type": str | None, "default": None}, ] ).generate_model() @@ -117,27 +115,27 @@ def generate_model(self): StableDiffusionProcessingImg2Img, [ {"key": "sampler_index", "type": str, "default": "Euler"}, - {"key": "init_images", "type": list, "default": None}, + {"key": "init_images", "type": list | None, "default": None}, {"key": "denoising_strength", "type": float, "default": 0.75}, - {"key": "mask", "type": str, "default": None}, + {"key": "mask", "type": str | None, "default": None}, {"key": "include_init_images", "type": bool, "default": False, "exclude" : True}, - {"key": "script_name", "type": str, "default": None}, + {"key": "script_name", "type": str | None, "default": None}, {"key": "script_args", "type": list, "default": []}, {"key": "send_images", "type": bool, "default": True}, {"key": "save_images", "type": bool, "default": False}, {"key": "alwayson_scripts", "type": dict, "default": {}}, - {"key": "force_task_id", "type": str, "default": None}, - {"key": "infotext", "type": str, "default": None}, + {"key": "force_task_id", "type": str | None, "default": None}, + {"key": "infotext", "type": str | None, "default": None}, ] ).generate_model() class TextToImageResponse(BaseModel): - images: list[str] = Field(default=None, title="Image", description="The generated image in base64 format.") + images: list[str] | None = Field(default=None, title="Image", description="The generated image in base64 format.") parameters: dict info: str class ImageToImageResponse(BaseModel): - images: list[str] = Field(default=None, title="Image", description="The generated image in base64 format.") + images: list[str] | None = Field(default=None, title="Image", description="The generated image in base64 format.") parameters: dict info: str @@ -147,7 +145,7 @@ class ExtrasBaseRequest(BaseModel): gfpgan_visibility: float = Field(default=0, title="GFPGAN Visibility", ge=0, le=1, allow_inf_nan=False, description="Sets the visibility of GFPGAN, values should be between 0 and 1.") codeformer_visibility: float = Field(default=0, title="CodeFormer Visibility", ge=0, le=1, allow_inf_nan=False, description="Sets the visibility of CodeFormer, values should be between 0 and 1.") codeformer_weight: float = Field(default=0, title="CodeFormer Weight", ge=0, le=1, allow_inf_nan=False, description="Sets the weight of CodeFormer, values should be between 0 and 1.") - upscaling_resize: float = Field(default=2, title="Upscaling Factor", ge=1, le=8, description="By how much to upscale the image, only used when resize_mode=0.") + upscaling_resize: float = Field(default=2, title="Upscaling Factor", gt=0, description="By how much to upscale the image, only used when resize_mode=0.") upscaling_resize_w: int = Field(default=512, title="Target Width", ge=1, description="Target width for the upscaler to hit. Only used when resize_mode=1.") upscaling_resize_h: int = Field(default=512, title="Target Height", ge=1, description="Target height for the upscaler to hit. Only used when resize_mode=1.") upscaling_crop: bool = Field(default=True, title="Crop to fit", description="Should the upscaler crop the image to fit in the chosen size?") @@ -163,7 +161,7 @@ class ExtrasSingleImageRequest(ExtrasBaseRequest): image: str = Field(default="", title="Image", description="Image to work on, must be a Base64 string containing the image's data.") class ExtrasSingleImageResponse(ExtraBaseResponse): - image: str = Field(default=None, title="Image", description="The generated image in base64 format.") + image: str | None = Field(default=None, title="Image", description="The generated image in base64 format.") class FileData(BaseModel): data: str = Field(title="File data", description="Base64 representation of the file") @@ -190,15 +188,15 @@ class ProgressResponse(BaseModel): progress: float = Field(title="Progress", description="The progress with a range of 0 to 1") eta_relative: float = Field(title="ETA in secs") state: dict = Field(title="State", description="The current state snapshot") - current_image: str = Field(default=None, title="Current image", description="The current image in base64 format. opts.show_progress_every_n_steps is required for this to work.") - textinfo: str = Field(default=None, title="Info text", description="Info text used by WebUI.") + current_image: str | None = Field(default=None, title="Current image", description="The current image in base64 format. opts.show_progress_every_n_steps is required for this to work.") + textinfo: str | None = Field(default=None, title="Info text", description="Info text used by WebUI.") class InterrogateRequest(BaseModel): image: str = Field(default="", title="Image", description="Image to work on, must be a Base64 string containing the image's data.") model: str = Field(default="clip", title="Model", description="The interrogate model used.") class InterrogateResponse(BaseModel): - caption: str = Field(default=None, title="Caption", description="The generated caption for the image.") + caption: str | None = Field(default=None, title="Caption", description="The generated caption for the image.") class TrainResponse(BaseModel): info: str = Field(title="Train info", description="Response string from train embedding or hypernetwork task.") @@ -223,7 +221,7 @@ class CreateResponse(BaseModel): for key in _options: if(_options[key].dest != 'help'): flag = _options[key] - _type = str + _type = str | None if _options[key].default is not None: _type = type(_options[key].default) flags.update({flag.dest: (_type, Field(default=flag.default, description=flag.help))}) @@ -233,9 +231,19 @@ class CreateResponse(BaseModel): class SamplerItem(BaseModel): name: str = Field(title="Name") aliases: list[str] = Field(title="Aliases") - options: dict[str, str] = Field(title="Options") + options: dict[str, Any] = Field(title="Options") + +class SchedulerItem(BaseModel): + name: str = Field(title="Name") + label: str = Field(title="Label") + aliases: Optional[list[str]] = Field(title="Aliases") + default_rho: Optional[float] = Field(title="Default Rho") + need_inner_model: Optional[bool] = Field(title="Needs Inner Model") class UpscalerItem(BaseModel): + class Config: + protected_namespaces = () + name: str = Field(title="Name") model_name: Optional[str] = Field(title="Model Name") model_path: Optional[str] = Field(title="Path") @@ -246,6 +254,9 @@ class LatentUpscalerModeItem(BaseModel): name: str = Field(title="Name") class SDModelItem(BaseModel): + class Config: + protected_namespaces = () + title: str = Field(title="Title") model_name: str = Field(title="Model Name") hash: Optional[str] = Field(title="Short hash") @@ -254,6 +265,9 @@ class SDModelItem(BaseModel): config: Optional[str] = Field(title="Config file") class SDVaeItem(BaseModel): + class Config: + protected_namespaces = () + model_name: str = Field(title="Model Name") filename: str = Field(title="Filename") @@ -293,12 +307,12 @@ class MemoryResponse(BaseModel): class ScriptsList(BaseModel): - txt2img: list = Field(default=None, title="Txt2img", description="Titles of scripts (txt2img)") - img2img: list = Field(default=None, title="Img2img", description="Titles of scripts (img2img)") + txt2img: list | None = Field(default=None, title="Txt2img", description="Titles of scripts (txt2img)") + img2img: list | None = Field(default=None, title="Img2img", description="Titles of scripts (img2img)") class ScriptArg(BaseModel): - label: str = Field(default=None, title="Label", description="Name of the argument in UI") + label: str | None = Field(default=None, title="Label", description="Name of the argument in UI") value: Optional[Any] = Field(default=None, title="Value", description="Default value of the argument") minimum: Optional[Any] = Field(default=None, title="Minimum", description="Minimum allowed value for the argumentin UI") maximum: Optional[Any] = Field(default=None, title="Minimum", description="Maximum allowed value for the argumentin UI") @@ -307,9 +321,9 @@ class ScriptArg(BaseModel): class ScriptInfo(BaseModel): - name: str = Field(default=None, title="Name", description="Script name") - is_alwayson: bool = Field(default=None, title="IsAlwayson", description="Flag specifying whether this script is an alwayson script") - is_img2img: bool = Field(default=None, title="IsImg2img", description="Flag specifying whether this script is an img2img script") + name: str | None = Field(default=None, title="Name", description="Script name") + is_alwayson: bool | None = Field(default=None, title="IsAlwayson", description="Flag specifying whether this script is an alwayson script") + is_img2img: bool | None = Field(default=None, title="IsImg2img", description="Flag specifying whether this script is an img2img script") args: list[ScriptArg] = Field(title="Arguments", description="List of script's arguments") class ExtensionItem(BaseModel): diff --git a/modules/cache.py b/modules/cache.py index a9822a0eb..f4e5f702b 100644 --- a/modules/cache.py +++ b/modules/cache.py @@ -2,48 +2,55 @@ import os import os.path import threading -import time + +import diskcache +import tqdm from modules.paths import data_path, script_path cache_filename = os.environ.get('SD_WEBUI_CACHE_FILE', os.path.join(data_path, "cache.json")) -cache_data = None +cache_dir = os.environ.get('SD_WEBUI_CACHE_DIR', os.path.join(data_path, "cache")) +caches = {} cache_lock = threading.Lock() -dump_cache_after = None -dump_cache_thread = None - def dump_cache(): - """ - Marks cache for writing to disk. 5 seconds after no one else flags the cache for writing, it is written. - """ + """old function for dumping cache to disk; does nothing since diskcache.""" - global dump_cache_after - global dump_cache_thread + pass - def thread_func(): - global dump_cache_after - global dump_cache_thread - while dump_cache_after is not None and time.time() < dump_cache_after: - time.sleep(1) +def make_cache(subsection: str) -> diskcache.Cache: + return diskcache.Cache( + os.path.join(cache_dir, subsection), + size_limit=2**32, # 4 GB, culling oldest first + disk_min_file_size=2**18, # keep up to 256KB in Sqlite + ) - with cache_lock: - cache_filename_tmp = cache_filename + "-" - with open(cache_filename_tmp, "w", encoding="utf8") as file: - json.dump(cache_data, file, indent=4, ensure_ascii=False) - os.replace(cache_filename_tmp, cache_filename) +def convert_old_cached_data(): + try: + with open(cache_filename, "r", encoding="utf8") as file: + data = json.load(file) + except FileNotFoundError: + return + except Exception: + os.replace(cache_filename, os.path.join(script_path, "tmp", "cache.json")) + print('[ERROR] issue occurred while trying to read cache.json; old cache has been moved to tmp/cache.json') + return - dump_cache_after = None - dump_cache_thread = None + total_count = sum(len(keyvalues) for keyvalues in data.values()) - with cache_lock: - dump_cache_after = time.time() + 5 - if dump_cache_thread is None: - dump_cache_thread = threading.Thread(name='cache-writer', target=thread_func) - dump_cache_thread.start() + with tqdm.tqdm(total=total_count, desc="converting cache") as progress: + for subsection, keyvalues in data.items(): + cache_obj = caches.get(subsection) + if cache_obj is None: + cache_obj = make_cache(subsection) + caches[subsection] = cache_obj + + for key, value in keyvalues.items(): + cache_obj[key] = value + progress.update(1) def cache(subsection): @@ -54,28 +61,21 @@ def cache(subsection): subsection (str): The subsection identifier for the cache. Returns: - dict: The cache data for the specified subsection. + diskcache.Cache: The cache data for the specified subsection. """ - global cache_data - - if cache_data is None: + cache_obj = caches.get(subsection) + if not cache_obj: with cache_lock: - if cache_data is None: - try: - with open(cache_filename, "r", encoding="utf8") as file: - cache_data = json.load(file) - except FileNotFoundError: - cache_data = {} - except Exception: - os.replace(cache_filename, os.path.join(script_path, "tmp", "cache.json")) - print('[ERROR] issue occurred while trying to read cache.json, move current cache to tmp/cache.json and create new cache') - cache_data = {} - - s = cache_data.get(subsection, {}) - cache_data[subsection] = s - - return s + if not os.path.exists(cache_dir) and os.path.isfile(cache_filename): + convert_old_cached_data() + + cache_obj = caches.get(subsection) + if not cache_obj: + cache_obj = make_cache(subsection) + caches[subsection] = cache_obj + + return cache_obj def cached_data_for_file(subsection, title, filename, func): diff --git a/modules/call_queue.py b/modules/call_queue.py index bcd7c5462..555c35312 100644 --- a/modules/call_queue.py +++ b/modules/call_queue.py @@ -1,8 +1,9 @@ +import os.path from functools import wraps import html import time -from modules import shared, progress, errors, devices, fifo_lock +from modules import shared, progress, errors, devices, fifo_lock, profiling queue_lock = fifo_lock.FIFOLock() @@ -46,6 +47,22 @@ def f(*args, **kwargs): def wrap_gradio_call(func, extra_outputs=None, add_stats=False): + @wraps(func) + def f(*args, **kwargs): + try: + res = func(*args, **kwargs) + finally: + shared.state.skipped = False + shared.state.interrupted = False + shared.state.stopping_generation = False + shared.state.job_count = 0 + shared.state.job = "" + return res + + return wrap_gradio_call_no_job(f, extra_outputs, add_stats) + + +def wrap_gradio_call_no_job(func, extra_outputs=None, add_stats=False): @wraps(func) def f(*args, extra_outputs_array=extra_outputs, **kwargs): run_memmon = shared.opts.memmon_poll_rate > 0 and not shared.mem_mon.disabled and add_stats @@ -65,9 +82,6 @@ def f(*args, extra_outputs_array=extra_outputs, **kwargs): arg_str += f" (Argument list truncated at {max_debug_str_len}/{len(arg_str)} characters)" errors.report(f"{message}\n{arg_str}", exc_info=True) - shared.state.job = "" - shared.state.job_count = 0 - if extra_outputs_array is None: extra_outputs_array = [None, ''] @@ -76,11 +90,6 @@ def f(*args, extra_outputs_array=extra_outputs, **kwargs): devices.torch_gc() - shared.state.skipped = False - shared.state.interrupted = False - shared.state.stopping_generation = False - shared.state.job_count = 0 - if not add_stats: return tuple(res) @@ -100,8 +109,8 @@ def f(*args, extra_outputs_array=extra_outputs, **kwargs): sys_pct = sys_peak/max(sys_total, 1) * 100 toltip_a = "Active: peak amount of video memory used during generation (excluding cached data)" - toltip_r = "Reserved: total amout of video memory allocated by the Torch library " - toltip_sys = "System: peak amout of video memory allocated by all running programs, out of total capacity" + toltip_r = "Reserved: total amount of video memory allocated by the Torch library " + toltip_sys = "System: peak amount of video memory allocated by all running programs, out of total capacity" text_a = f"A: {active_peak/1024:.2f} GB" text_r = f"R: {reserved_peak/1024:.2f} GB" @@ -111,9 +120,15 @@ def f(*args, extra_outputs_array=extra_outputs, **kwargs): else: vram_html = '' + if shared.opts.profiling_enable and os.path.exists(shared.opts.profiling_filename): + profiling_html = f"

[ Profile ]

" + else: + profiling_html = '' + # last item is always HTML - res[-1] += f"

Time taken: {elapsed_text}

{vram_html}
" + res[-1] += f"

Time taken: {elapsed_text}

{vram_html}{profiling_html}
" return tuple(res) return f + diff --git a/modules/cmd_args.py b/modules/cmd_args.py index 6730f144d..fdc30cf17 100644 --- a/modules/cmd_args.py +++ b/modules/cmd_args.py @@ -22,6 +22,7 @@ parser.add_argument("--loglevel", type=str, help="log level; one of: CRITICAL, ERROR, WARNING, INFO, DEBUG", default=None) parser.add_argument("--do-not-download-clip", action='store_true', help="do not download CLIP model even if it's not included in the checkpoint") parser.add_argument("--data-dir", type=normalized_filepath, default=os.path.dirname(os.path.dirname(os.path.realpath(__file__))), help="base path where all user data is stored") +parser.add_argument("--models-dir", type=normalized_filepath, default=None, help="base path where models are stored; overrides --data-dir") parser.add_argument("--config", type=normalized_filepath, default=sd_default_config, help="path to config which constructs model",) parser.add_argument("--ckpt", type=normalized_filepath, default=sd_model_file, help="path to checkpoint of stable diffusion model; if specified, this checkpoint will be added to the list of checkpoints and loaded",) parser.add_argument("--ckpt-dir", type=normalized_filepath, default=None, help="Path to directory with stable diffusion checkpoints") @@ -31,7 +32,7 @@ parser.add_argument("--no-half", action='store_true', help="do not switch the model to 16-bit floats") parser.add_argument("--no-half-vae", action='store_true', help="do not switch the VAE model to 16-bit floats") parser.add_argument("--no-progressbar-hiding", action='store_true', help="do not hide progressbar in gradio UI (we hide it because it slows down ML if you have hardware acceleration in browser)") -parser.add_argument("--max-batch-count", type=int, default=16, help="maximum batch count value for the UI") +parser.add_argument("--max-batch-count", type=int, default=16, help="does not do anything") parser.add_argument("--embeddings-dir", type=normalized_filepath, default=os.path.join(data_path, 'embeddings'), help="embeddings directory for textual inversion (default: embeddings)") parser.add_argument("--textual-inversion-templates-dir", type=normalized_filepath, default=os.path.join(script_path, 'textual_inversion_templates'), help="directory with textual inversion templates") parser.add_argument("--hypernetwork-dir", type=normalized_filepath, default=os.path.join(models_path, 'hypernetworks'), help="hypernetwork directory") @@ -43,7 +44,7 @@ parser.add_argument("--lowram", action='store_true', help="load stable diffusion checkpoint weights to VRAM instead of RAM") parser.add_argument("--always-batch-cond-uncond", action='store_true', help="does not do anything") parser.add_argument("--unload-gfpgan", action='store_true', help="does not do anything.") -parser.add_argument("--precision", type=str, help="evaluate at this precision", choices=["full", "autocast"], default="autocast") +parser.add_argument("--precision", type=str, help="evaluate at this precision", choices=["full", "half", "autocast"], default="autocast") parser.add_argument("--upcast-sampling", action='store_true', help="upcast sampling. No effect with --no-half. Usually produces similar results to --no-half with better performance while using less memory.") parser.add_argument("--share", action='store_true', help="use share=True for gradio and make the UI accessible through their site") parser.add_argument("--ngrok", type=str, help="ngrok authtoken, alternative to gradio --share", default=None) @@ -55,6 +56,7 @@ parser.add_argument("--esrgan-models-path", type=normalized_filepath, help="Path to directory with ESRGAN model file(s).", default=os.path.join(models_path, 'ESRGAN')) parser.add_argument("--bsrgan-models-path", type=normalized_filepath, help="Path to directory with BSRGAN model file(s).", default=os.path.join(models_path, 'BSRGAN')) parser.add_argument("--realesrgan-models-path", type=normalized_filepath, help="Path to directory with RealESRGAN model file(s).", default=os.path.join(models_path, 'RealESRGAN')) +parser.add_argument("--dat-models-path", type=normalized_filepath, help="Path to directory with DAT model file(s).", default=os.path.join(models_path, 'DAT')) parser.add_argument("--clip-models-path", type=normalized_filepath, help="Path to directory with CLIP model file(s).", default=None) parser.add_argument("--xformers", action='store_true', help="enable xformers for cross attention layers") parser.add_argument("--force-enable-xformers", action='store_true', help="enable xformers for cross attention layers regardless of whether the checking code thinks you can run it; do not make bug reports if this fails to work") @@ -122,7 +124,10 @@ parser.add_argument('--timeout-keep-alive', type=int, default=30, help='set timeout_keep_alive for uvicorn') parser.add_argument("--disable-all-extensions", action='store_true', help="prevent all extensions from running regardless of any other settings", default=False) parser.add_argument("--disable-extra-extensions", action='store_true', help="prevent all extensions except built-in from running regardless of any other settings", default=False) -parser.add_argument("--skip-load-model-at-start", action='store_true', help="if load a model at web start, only take effect when --nowebui", ) +parser.add_argument("--skip-load-model-at-start", action='store_true', help="if load a model at web start, only take effect when --nowebui") +parser.add_argument("--unix-filenames-sanitization", action='store_true', help="allow any symbols except '/' in filenames. May conflict with your browser and file system") +parser.add_argument("--filenames-max-length", type=int, default=128, help='maximal length of filenames of saved images. If you override it, it can conflict with your file system') +parser.add_argument("--no-prompt-history", action='store_true', help="disable read prompt from last generation feature; settings this argument will not create '--data_path/params.txt' file") # Arguments added by forge. parser.add_argument( diff --git a/modules/codeformer_model.py b/modules/codeformer_model.py index 44b84618e..0b353353b 100644 --- a/modules/codeformer_model.py +++ b/modules/codeformer_model.py @@ -50,7 +50,7 @@ def restore(self, np_image, w: float | None = None): def restore_face(cropped_face_t): assert self.net is not None - return self.net(cropped_face_t, w=w, adain=True)[0] + return self.net(cropped_face_t, weight=w, adain=True)[0] return self.restore_with_helper(np_image, restore_face) diff --git a/modules/errors.py b/modules/errors.py index 48aa13a17..ecc2280d0 100644 --- a/modules/errors.py +++ b/modules/errors.py @@ -109,7 +109,7 @@ def check_versions(): expected_torch_version = "2.1.2" expected_xformers_version = "0.0.23.post1" - expected_gradio_version = "3.41.2" + expected_gradio_version = "4.39.0" if version.parse(torch.__version__) < version.parse(expected_torch_version): print_error_explanation(f""" diff --git a/modules/extensions.py b/modules/extensions.py index a47cdbe96..715a864c7 100644 --- a/modules/extensions.py +++ b/modules/extensions.py @@ -1,6 +1,7 @@ from __future__ import annotations import configparser +import dataclasses import os import threading import re @@ -10,6 +11,10 @@ from modules.paths_internal import extensions_dir, extensions_builtin_dir, script_path # noqa: F401 from modules_forge.config import always_disabled_extensions +extensions: list[Extension] = [] +extension_paths: dict[str, Extension] = {} +loaded_extensions: dict[str, Exception] = {} + os.makedirs(extensions_dir, exist_ok=True) @@ -23,6 +28,13 @@ def active(): return [x for x in extensions if x.enabled] +@dataclasses.dataclass +class CallbackOrderInfo: + name: str + before: list + after: list + + class ExtensionMetadata: filename = "metadata.ini" config: configparser.ConfigParser @@ -43,7 +55,7 @@ def __init__(self, path, canonical_name): self.canonical_name = self.config.get("Extension", "Name", fallback=canonical_name) self.canonical_name = canonical_name.lower().strip() - self.requires = self.get_script_requirements("Requires", "Extension") + self.requires = None def get_script_requirements(self, field, section, extra_section=None): """reads a list of requirements from the config; field is the name of the field in the ini file, @@ -55,7 +67,15 @@ def get_script_requirements(self, field, section, extra_section=None): if extra_section: x = x + ', ' + self.config.get(extra_section, field, fallback='') - return self.parse_list(x.lower()) + listed_requirements = self.parse_list(x.lower()) + res = [] + + for requirement in listed_requirements: + loaded_requirements = (x for x in requirement.split("|") if x in loaded_extensions) + relevant_requirement = next(loaded_requirements, requirement) + res.append(relevant_requirement) + + return res def parse_list(self, text): """converts a line from config ("ext1 ext2, ext3 ") into a python list (["ext1", "ext2", "ext3"])""" @@ -66,6 +86,22 @@ def parse_list(self, text): # both "," and " " are accepted as separator return [x for x in re.split(r"[,\s]+", text.strip()) if x] + def list_callback_order_instructions(self): + for section in self.config.sections(): + if not section.startswith("callbacks/"): + continue + + callback_name = section[10:] + + if not callback_name.startswith(self.canonical_name): + errors.report(f"Callback order section for extension {self.canonical_name} is referencing the wrong extension: {section}") + continue + + before = self.parse_list(self.config.get(section, 'Before', fallback='')) + after = self.parse_list(self.config.get(section, 'After', fallback='')) + + yield CallbackOrderInfo(callback_name, before, after) + class Extension: lock = threading.Lock() @@ -156,14 +192,17 @@ def list_files(self, subdir, extension): def check_updates(self): repo = Repo(self.path) + branch_name = f'{repo.remote().name}/{self.branch}' for fetch in repo.remote().fetch(dry_run=True): + if self.branch and fetch.name != branch_name: + continue if fetch.flags != fetch.HEAD_UPTODATE: self.can_update = True self.status = "new commits" return try: - origin = repo.rev_parse('origin') + origin = repo.rev_parse(branch_name) if repo.head.commit != origin: self.can_update = True self.status = "behind HEAD" @@ -176,8 +215,10 @@ def check_updates(self): self.can_update = False self.status = "latest" - def fetch_and_reset_hard(self, commit='origin'): + def fetch_and_reset_hard(self, commit=None): repo = Repo(self.path) + if commit is None: + commit = f'{repo.remote().name}/{self.branch}' # Fix: `error: Your local changes to the following files would be overwritten by merge`, # because WSL2 Docker set 755 file permissions instead of 644, this results to the error. repo.git.fetch(all=True) @@ -187,6 +228,8 @@ def fetch_and_reset_hard(self, commit='origin'): def list_extensions(): extensions.clear() + extension_paths.clear() + loaded_extensions.clear() if shared.cmd_opts.disable_all_extensions: print("*** \"--disable-all-extensions\" arg was used, will not load any extensions ***") @@ -197,7 +240,6 @@ def list_extensions(): elif shared.opts.disable_all_extensions == "extra": print("*** \"Disable all extensions\" option was set, will only load built-in extensions ***") - loaded_extensions = {} # scan through extensions directory and load metadata for dirname in [extensions_builtin_dir, extensions_dir]: @@ -231,8 +273,12 @@ def list_extensions(): ) extensions.append(extension) + extension_paths[extension.path] = extension loaded_extensions[canonical_name] = extension + for extension in extensions: + extension.metadata.requires = extension.metadata.get_script_requirements("Requires", "Extension") + # check for requirements for extension in extensions: if not extension.enabled: @@ -249,4 +295,16 @@ def list_extensions(): continue -extensions: list[Extension] = [] +def find_extension(filename): + parentdir = os.path.dirname(os.path.realpath(filename)) + + while parentdir != filename: + extension = extension_paths.get(parentdir) + if extension is not None: + return extension + + filename = parentdir + parentdir = os.path.dirname(filename) + + return None + diff --git a/modules/extra_networks.py b/modules/extra_networks.py index 04249dffd..ae8d42d9b 100644 --- a/modules/extra_networks.py +++ b/modules/extra_networks.py @@ -60,7 +60,7 @@ def activate(self, p, params_list): Where name matches the name of this ExtraNetwork object, and arg1:arg2:arg3 are any natural number of text arguments separated by colon. - Even if the user does not mention this ExtraNetwork in his prompt, the call will stil be made, with empty params_list - + Even if the user does not mention this ExtraNetwork in his prompt, the call will still be made, with empty params_list - in this case, all effects of this extra networks should be disabled. Can be called multiple times before deactivate() - each new call should override the previous call completely. diff --git a/modules/gfpgan_model.py b/modules/gfpgan_model.py index 445b04092..01ef899e4 100644 --- a/modules/gfpgan_model.py +++ b/modules/gfpgan_model.py @@ -36,13 +36,11 @@ def load_net(self) -> torch.Module: ext_filter=['.pth'], ): if 'GFPGAN' in os.path.basename(model_path): - model = modelloader.load_spandrel_model( + return modelloader.load_spandrel_model( model_path, device=self.get_device(), expected_architecture='GFPGAN', ).model - model.different_w = True # see https://github.com/chaiNNer-org/spandrel/pull/81 - return model raise ValueError("No GFPGAN model found") def restore(self, np_image): diff --git a/modules/gradio_extensions.py b/modules/gradio_extensions.py new file mode 100644 index 000000000..84414f6e3 --- /dev/null +++ b/modules/gradio_extensions.py @@ -0,0 +1,166 @@ +import inspect +import warnings +from functools import wraps + +import gradio as gr +import gradio.component_meta + + +from modules import scripts, ui_tempdir, patches + + +class GradioDeprecationWarning(DeprecationWarning): + pass + + +def add_classes_to_gradio_component(comp): + """ + this adds gradio-* to the component for css styling (ie gradio-button to gr.Button), as well as some others + """ + + comp.elem_classes = [f"gradio-{comp.get_block_name()}", *(getattr(comp, 'elem_classes', None) or [])] + + if getattr(comp, 'multiselect', False): + comp.elem_classes.append('multiselect') + + +def IOComponent_init(self, *args, **kwargs): + self.webui_tooltip = kwargs.pop('tooltip', None) + + if scripts.scripts_current is not None: + scripts.scripts_current.before_component(self, **kwargs) + + scripts.script_callbacks.before_component_callback(self, **kwargs) + + res = original_IOComponent_init(self, *args, **kwargs) + + add_classes_to_gradio_component(self) + + scripts.script_callbacks.after_component_callback(self, **kwargs) + + if scripts.scripts_current is not None: + scripts.scripts_current.after_component(self, **kwargs) + + return res + + +def Block_get_config(self): + config = original_Block_get_config(self) + + webui_tooltip = getattr(self, 'webui_tooltip', None) + if webui_tooltip: + config["webui_tooltip"] = webui_tooltip + + config.pop('example_inputs', None) + + return config + + +def BlockContext_init(self, *args, **kwargs): + if scripts.scripts_current is not None: + scripts.scripts_current.before_component(self, **kwargs) + + scripts.script_callbacks.before_component_callback(self, **kwargs) + + res = original_BlockContext_init(self, *args, **kwargs) + + add_classes_to_gradio_component(self) + + scripts.script_callbacks.after_component_callback(self, **kwargs) + + if scripts.scripts_current is not None: + scripts.scripts_current.after_component(self, **kwargs) + + return res + + +def Blocks_get_config_file(self, *args, **kwargs): + config = original_Blocks_get_config_file(self, *args, **kwargs) + + for comp_config in config["components"]: + if "example_inputs" in comp_config: + comp_config["example_inputs"] = {"serialized": []} + + return config + + +original_IOComponent_init = patches.patch(__name__, obj=gr.components.Component, field="__init__", replacement=IOComponent_init) +original_Block_get_config = patches.patch(__name__, obj=gr.blocks.Block, field="get_config", replacement=Block_get_config) +original_BlockContext_init = patches.patch(__name__, obj=gr.blocks.BlockContext, field="__init__", replacement=BlockContext_init) +original_Blocks_get_config_file = patches.patch(__name__, obj=gr.blocks.Blocks, field="get_config_file", replacement=Blocks_get_config_file) + + +ui_tempdir.install_ui_tempdir_override() + + +def gradio_component_meta_create_or_modify_pyi(component_class, class_name, events): + if hasattr(component_class, 'webui_do_not_create_gradio_pyi_thank_you'): + return + + gradio_component_meta_create_or_modify_pyi_original(component_class, class_name, events) + + +# this prevents creation of .pyi files in webui dir +gradio_component_meta_create_or_modify_pyi_original = patches.patch(__file__, gradio.component_meta, 'create_or_modify_pyi', gradio_component_meta_create_or_modify_pyi) + +# this function is broken and does not seem to do anything useful +gradio.component_meta.updateable = lambda x: x + +def repair(grclass): + if not getattr(grclass, 'EVENTS', None): + return + + @wraps(grclass.__init__) + def __repaired_init__(self, *args, tooltip=None, source=None, original=grclass.__init__, **kwargs): + if source: + kwargs["sources"] = [source] + + allowed_kwargs = inspect.signature(original).parameters + fixed_kwargs = {} + for k, v in kwargs.items(): + if k in allowed_kwargs: + fixed_kwargs[k] = v + else: + warnings.warn(f"unexpected argument for {grclass.__name__}: {k}", GradioDeprecationWarning, stacklevel=2) + + original(self, *args, **fixed_kwargs) + + self.webui_tooltip = tooltip + + for event in self.EVENTS: + replaced_event = getattr(self, str(event)) + + def fun(*xargs, _js=None, replaced_event=replaced_event, **xkwargs): + if _js: + xkwargs['js'] = _js + + return replaced_event(*xargs, **xkwargs) + + setattr(self, str(event), fun) + + grclass.__init__ = __repaired_init__ + grclass.update = gr.update + + +for component in set(gr.components.__all__ + gr.layouts.__all__): + repair(getattr(gr, component, None)) + + +class Dependency(gr.events.Dependency): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + def then(*xargs, _js=None, **xkwargs): + if _js: + xkwargs['js'] = _js + + return original_then(*xargs, **xkwargs) + + original_then = self.then + self.then = then + + +gr.events.Dependency = Dependency + +gr.Box = gr.Group + diff --git a/modules/gradio_extensons.py b/modules/gradio_extensons.py deleted file mode 100644 index 7d88dc984..000000000 --- a/modules/gradio_extensons.py +++ /dev/null @@ -1,83 +0,0 @@ -import gradio as gr - -from modules import scripts, ui_tempdir, patches - - -def add_classes_to_gradio_component(comp): - """ - this adds gradio-* to the component for css styling (ie gradio-button to gr.Button), as well as some others - """ - - comp.elem_classes = [f"gradio-{comp.get_block_name()}", *(comp.elem_classes or [])] - - if getattr(comp, 'multiselect', False): - comp.elem_classes.append('multiselect') - - -def IOComponent_init(self, *args, **kwargs): - self.webui_tooltip = kwargs.pop('tooltip', None) - - if scripts.scripts_current is not None: - scripts.scripts_current.before_component(self, **kwargs) - - scripts.script_callbacks.before_component_callback(self, **kwargs) - - res = original_IOComponent_init(self, *args, **kwargs) - - add_classes_to_gradio_component(self) - - scripts.script_callbacks.after_component_callback(self, **kwargs) - - if scripts.scripts_current is not None: - scripts.scripts_current.after_component(self, **kwargs) - - return res - - -def Block_get_config(self): - config = original_Block_get_config(self) - - webui_tooltip = getattr(self, 'webui_tooltip', None) - if webui_tooltip: - config["webui_tooltip"] = webui_tooltip - - config.pop('example_inputs', None) - - return config - - -def BlockContext_init(self, *args, **kwargs): - if scripts.scripts_current is not None: - scripts.scripts_current.before_component(self, **kwargs) - - scripts.script_callbacks.before_component_callback(self, **kwargs) - - res = original_BlockContext_init(self, *args, **kwargs) - - add_classes_to_gradio_component(self) - - scripts.script_callbacks.after_component_callback(self, **kwargs) - - if scripts.scripts_current is not None: - scripts.scripts_current.after_component(self, **kwargs) - - return res - - -def Blocks_get_config_file(self, *args, **kwargs): - config = original_Blocks_get_config_file(self, *args, **kwargs) - - for comp_config in config["components"]: - if "example_inputs" in comp_config: - comp_config["example_inputs"] = {"serialized": []} - - return config - - -original_IOComponent_init = patches.patch(__name__, obj=gr.components.IOComponent, field="__init__", replacement=IOComponent_init) -original_Block_get_config = patches.patch(__name__, obj=gr.blocks.Block, field="get_config", replacement=Block_get_config) -original_BlockContext_init = patches.patch(__name__, obj=gr.blocks.BlockContext, field="__init__", replacement=BlockContext_init) -original_Blocks_get_config_file = patches.patch(__name__, obj=gr.blocks.Blocks, field="get_config_file", replacement=Blocks_get_config_file) - - -ui_tempdir.install_ui_tempdir_override() diff --git a/modules/hypernetworks/hypernetwork.py b/modules/hypernetworks/hypernetwork.py index be3e46484..17454665f 100644 --- a/modules/hypernetworks/hypernetwork.py +++ b/modules/hypernetworks/hypernetwork.py @@ -11,7 +11,7 @@ from einops import rearrange, repeat from ldm.util import default from modules import devices, sd_models, shared, sd_samplers, hashes, sd_hijack_checkpoint, errors -from modules.textual_inversion import textual_inversion, logging +from modules.textual_inversion import textual_inversion, saving_settings from modules.textual_inversion.learn_schedule import LearnRateScheduler from torch import einsum from torch.nn.init import normal_, xavier_normal_, xavier_uniform_, kaiming_normal_, kaiming_uniform_, zeros_ @@ -95,6 +95,7 @@ def __init__(self, dim, state_dict=None, layer_structure=None, activation_func=N zeros_(b) else: raise KeyError(f"Key {weight_init} is not defined as initialization!") + devices.torch_npu_set_device() self.to(devices.device) def fix_old_state_dict(self, state_dict): @@ -532,7 +533,7 @@ def train_hypernetwork(id_task, hypernetwork_name: str, learn_rate: float, batch model_name=checkpoint.model_name, model_hash=checkpoint.shorthash, num_of_dataset_images=len(ds), **{field: getattr(hypernetwork, field) for field in ['layer_structure', 'activation_func', 'weight_init', 'add_layer_norm', 'use_dropout', ]} ) - logging.save_settings_to_file(log_directory, {**saved_params, **locals()}) + saving_settings.save_settings_to_file(log_directory, {**saved_params, **locals()}) latent_sampling_method = ds.latent_sampling_method diff --git a/modules/images.py b/modules/images.py index b6f2358c3..031396ee8 100644 --- a/modules/images.py +++ b/modules/images.py @@ -1,7 +1,7 @@ from __future__ import annotations import datetime - +import functools import pytz import io import math @@ -12,7 +12,9 @@ import numpy as np import piexif import piexif.helper -from PIL import Image, ImageFont, ImageDraw, ImageColor, PngImagePlugin +from PIL import Image, ImageFont, ImageDraw, ImageColor, PngImagePlugin, ImageOps +# pillow_avif needs to be imported somewhere in code for it to work +import pillow_avif # noqa: F401 import string import json import hashlib @@ -52,11 +54,14 @@ def image_grid(imgs, batch_size=1, rows=None): params = script_callbacks.ImageGridLoopParams(imgs, cols, rows) script_callbacks.image_grid_callback(params) - w, h = imgs[0].size - grid = Image.new('RGB', size=(params.cols * w, params.rows * h), color='black') + w, h = map(max, zip(*(img.size for img in imgs))) + grid_background_color = ImageColor.getcolor(opts.grid_background_color, 'RGBA') + grid = Image.new('RGBA', size=(params.cols * w, params.rows * h), color=grid_background_color) for i, img in enumerate(params.imgs): - grid.paste(img, box=(i % params.cols * w, i // params.cols * h)) + img_w, img_h = img.size + w_offset, h_offset = 0 if img_w == w else (w - img_w) // 2, 0 if img_h == h else (h - img_h) // 2 + grid.paste(img, box=(i % params.cols * w + w_offset, i // params.cols * h + h_offset)) return grid @@ -244,7 +249,7 @@ def draw_prompt_matrix(im, width, height, all_prompts, margin=0): return draw_grid_annotations(im, width, height, hor_texts, ver_texts, margin) -def resize_image(resize_mode, im, width, height, upscaler_name=None): +def resize_image(resize_mode, im, width, height, upscaler_name=None, force_RGBA=False): """ Resizes an image with the specified resize_mode, width, and height. @@ -262,7 +267,7 @@ def resize_image(resize_mode, im, width, height, upscaler_name=None): upscaler_name = upscaler_name or opts.upscaler_for_img2img def resize(im, w, h): - if upscaler_name is None or upscaler_name == "None" or im.mode == 'L': + if upscaler_name is None or upscaler_name == "None" or im.mode == 'L' or force_RGBA: return im.resize((w, h), resample=LANCZOS) scale = max(w / im.width, h / im.height) @@ -293,7 +298,7 @@ def resize(im, w, h): src_h = height if ratio <= src_ratio else im.height * width // im.width resized = resize(im, src_w, src_h) - res = Image.new("RGB", (width, height)) + res = Image.new("RGB" if not force_RGBA else "RGBA", (width, height)) res.paste(resized, box=(width // 2 - src_w // 2, height // 2 - src_h // 2)) else: @@ -304,7 +309,7 @@ def resize(im, w, h): src_h = height if ratio >= src_ratio else im.height * width // im.width resized = resize(im, src_w, src_h) - res = Image.new("RGB", (width, height)) + res = Image.new("RGB" if not force_RGBA else "RGBA", (width, height)) res.paste(resized, box=(width // 2 - src_w // 2, height // 2 - src_h // 2)) if ratio < src_ratio: @@ -321,13 +326,16 @@ def resize(im, w, h): return res -invalid_filename_chars = '#<>:"/\\|?*\n\r\t' +if not shared.cmd_opts.unix_filenames_sanitization: + invalid_filename_chars = '#<>:"/\\|?*\n\r\t' +else: + invalid_filename_chars = '/' invalid_filename_prefix = ' ' invalid_filename_postfix = ' .' re_nonletters = re.compile(r'[\s' + string.punctuation + ']+') re_pattern = re.compile(r"(.*?)(?:\[([^\[\]]+)\]|$)") re_pattern_arg = re.compile(r"(.*)<([^>]*)>$") -max_filename_part_length = 128 +max_filename_part_length = shared.cmd_opts.filenames_max_length NOTHING_AND_SKIP_PREVIOUS_TEXT = object() @@ -344,8 +352,35 @@ def sanitize_filename_part(text, replace_spaces=True): return text +@functools.cache +def get_scheduler_str(sampler_name, scheduler_name): + """Returns {Scheduler} if the scheduler is applicable to the sampler""" + if scheduler_name == 'Automatic': + config = sd_samplers.find_sampler_config(sampler_name) + scheduler_name = config.options.get('scheduler', 'Automatic') + return scheduler_name.capitalize() + + +@functools.cache +def get_sampler_scheduler_str(sampler_name, scheduler_name): + """Returns the '{Sampler} {Scheduler}' if the scheduler is applicable to the sampler""" + return f'{sampler_name} {get_scheduler_str(sampler_name, scheduler_name)}' + + +def get_sampler_scheduler(p, sampler): + """Returns '{Sampler} {Scheduler}' / '{Scheduler}' / 'NOTHING_AND_SKIP_PREVIOUS_TEXT'""" + if hasattr(p, 'scheduler') and hasattr(p, 'sampler_name'): + if sampler: + sampler_scheduler = get_sampler_scheduler_str(p.sampler_name, p.scheduler) + else: + sampler_scheduler = get_scheduler_str(p.sampler_name, p.scheduler) + return sanitize_filename_part(sampler_scheduler, replace_spaces=False) + return NOTHING_AND_SKIP_PREVIOUS_TEXT + + class FilenameGenerator: replacements = { + 'basename': lambda self: self.basename or 'img', 'seed': lambda self: self.seed if self.seed is not None else '', 'seed_first': lambda self: self.seed if self.p.batch_size == 1 else self.p.all_seeds[0], 'seed_last': lambda self: NOTHING_AND_SKIP_PREVIOUS_TEXT if self.p.batch_size == 1 else self.p.all_seeds[-1], @@ -355,6 +390,8 @@ class FilenameGenerator: 'height': lambda self: self.image.height, 'styles': lambda self: self.p and sanitize_filename_part(", ".join([style for style in self.p.styles if not style == "None"]) or "None", replace_spaces=False), 'sampler': lambda self: self.p and sanitize_filename_part(self.p.sampler_name, replace_spaces=False), + 'sampler_scheduler': lambda self: self.p and get_sampler_scheduler(self.p, True), + 'scheduler': lambda self: self.p and get_sampler_scheduler(self.p, False), 'model_hash': lambda self: getattr(self.p, "sd_model_hash", shared.sd_model.sd_model_hash), 'model_name': lambda self: sanitize_filename_part(shared.sd_model.sd_checkpoint_info.name_for_extra, replace_spaces=False), 'date': lambda self: datetime.datetime.now().strftime('%Y-%m-%d'), @@ -380,12 +417,13 @@ class FilenameGenerator: } default_time_format = '%Y%m%d%H%M%S' - def __init__(self, p, seed, prompt, image, zip=False): + def __init__(self, p, seed, prompt, image, zip=False, basename=""): self.p = p self.seed = seed self.prompt = prompt self.image = image self.zip = zip + self.basename = basename def get_vae_filename(self): """Get the name of the VAE file.""" @@ -566,6 +604,17 @@ def save_image_with_geninfo(image, geninfo, filename, extension=None, existing_p }) piexif.insert(exif_bytes, filename) + elif extension.lower() == '.avif': + if opts.enable_pnginfo and geninfo is not None: + exif_bytes = piexif.dump({ + "Exif": { + piexif.ExifIFD.UserComment: piexif.helper.UserComment.dump(geninfo or "", encoding="unicode") + }, + }) + else: + exif_bytes = None + + image.save(filename,format=image_format, quality=opts.jpeg_quality, exif=exif_bytes) elif extension.lower() == ".gif": image.save(filename, format=image_format, comment=geninfo) else: @@ -605,12 +654,12 @@ def save_image(image, path, basename, seed=None, prompt=None, extension='png', i txt_fullfn (`str` or None): If a text file is saved for this image, this will be its full path. Otherwise None. """ - namegen = FilenameGenerator(p, seed, prompt, image) + namegen = FilenameGenerator(p, seed, prompt, image, basename=basename) # WebP and JPG formats have maximum dimension limits of 16383 and 65535 respectively. switch to PNG which has a much higher limit if (image.height > 65535 or image.width > 65535) and extension.lower() in ("jpg", "jpeg") or (image.height > 16383 or image.width > 16383) and extension.lower() == "webp": print('Image dimensions too large; saving as PNG') - extension = ".png" + extension = "png" if save_to_dirs is None: save_to_dirs = (grid and opts.grid_save_to_dirs) or (not grid and opts.save_to_dirs and not no_prompt) @@ -744,10 +793,12 @@ def read_info_from_image(image: Image.Image) -> tuple[str | None, dict]: exif_comment = exif_comment.decode('utf8', errors="ignore") if exif_comment: - items['exif comment'] = exif_comment geninfo = exif_comment elif "comment" in items: # for gif - geninfo = items["comment"].decode('utf8', errors="ignore") + if isinstance(items["comment"], bytes): + geninfo = items["comment"].decode('utf8', errors="ignore") + else: + geninfo = items["comment"] for field in IGNORED_INFO_KEYS: items.pop(field, None) @@ -770,7 +821,7 @@ def image_data(data): import gradio as gr try: - image = Image.open(io.BytesIO(data)) + image = read(io.BytesIO(data)) textinfo, _ = read_info_from_image(image) return textinfo, None except Exception: @@ -797,3 +848,30 @@ def flatten(img, bgcolor): return img.convert('RGB') + +def read(fp, **kwargs): + image = Image.open(fp, **kwargs) + image = fix_image(image) + + return image + + +def fix_image(image: Image.Image): + if image is None: + return None + + try: + image = ImageOps.exif_transpose(image) + image = fix_png_transparency(image) + except Exception: + pass + + return image + + +def fix_png_transparency(image: Image.Image): + if image.mode not in ("RGB", "P") or not isinstance(image.info.get("transparency"), bytes): + return image + + image = image.convert("RGBA") + return image diff --git a/modules/img2img.py b/modules/img2img.py index 6e9729a4b..5a1360105 100644 --- a/modules/img2img.py +++ b/modules/img2img.py @@ -2,11 +2,10 @@ from contextlib import closing from pathlib import Path -import numpy as np from PIL import Image, ImageOps, ImageFilter, ImageEnhance, UnidentifiedImageError import gradio as gr -from modules import images as imgutil +from modules import images from modules.infotext_utils import create_override_settings_dict, parse_generation_parameters from modules.processing import Processed, StableDiffusionProcessingImg2Img, process_images from modules.shared import opts, state @@ -18,11 +17,14 @@ from modules_forge import main_thread -def process_batch(p, input_dir, output_dir, inpaint_mask_dir, args, to_scale=False, scale_by=1.0, use_png_info=False, png_info_props=None, png_info_dir=None): +def process_batch(p, input, output_dir, inpaint_mask_dir, args, to_scale=False, scale_by=1.0, use_png_info=False, png_info_props=None, png_info_dir=None): output_dir = output_dir.strip() processing.fix_seed(p) - images = list(shared.walk_files(input_dir, allowed_extensions=(".png", ".jpg", ".jpeg", ".webp", ".tif", ".tiff"))) + if isinstance(input, str): + batch_images = list(shared.walk_files(input, allowed_extensions=(".png", ".jpg", ".jpeg", ".webp", ".tif", ".tiff"))) + else: + batch_images = [os.path.abspath(x.name) for x in input] is_inpaint_batch = False if inpaint_mask_dir: @@ -32,9 +34,9 @@ def process_batch(p, input_dir, output_dir, inpaint_mask_dir, args, to_scale=Fal if is_inpaint_batch: print(f"\nInpaint batch is enabled. {len(inpaint_masks)} masks found.") - print(f"Will process {len(images)} images, creating {p.n_iter * p.batch_size} new images for each.") + print(f"Will process {len(batch_images)} images, creating {p.n_iter * p.batch_size} new images for each.") - state.job_count = len(images) * p.n_iter + state.job_count = len(batch_images) * p.n_iter # extract "default" params to use in case getting png info fails prompt = p.prompt @@ -47,8 +49,8 @@ def process_batch(p, input_dir, output_dir, inpaint_mask_dir, args, to_scale=Fal sd_model_checkpoint_override = get_closet_checkpoint_match(override_settings.get("sd_model_checkpoint", None)) batch_results = None discard_further_results = False - for i, image in enumerate(images): - state.job = f"{i+1} out of {len(images)}" + for i, image in enumerate(batch_images): + state.job = f"{i+1} out of {len(batch_images)}" if state.skipped: state.skipped = False @@ -56,7 +58,7 @@ def process_batch(p, input_dir, output_dir, inpaint_mask_dir, args, to_scale=Fal break try: - img = Image.open(image) + img = images.read(image) except UnidentifiedImageError as e: print(e) continue @@ -87,7 +89,7 @@ def process_batch(p, input_dir, output_dir, inpaint_mask_dir, args, to_scale=Fal # otherwise user has many masks with the same name but different extensions mask_image_path = masks_found[0] - mask_image = Image.open(mask_image_path) + mask_image = images.read(mask_image_path) p.image_mask = mask_image if use_png_info: @@ -95,8 +97,8 @@ def process_batch(p, input_dir, output_dir, inpaint_mask_dir, args, to_scale=Fal info_img = img if png_info_dir: info_img_path = os.path.join(png_info_dir, os.path.basename(image)) - info_img = Image.open(info_img_path) - geninfo, _ = imgutil.read_info_from_image(info_img) + info_img = images.read(info_img_path) + geninfo, _ = images.read_info_from_image(info_img) parsed_parameters = parse_generation_parameters(geninfo) parsed_parameters = {k: v for k, v in parsed_parameters.items() if k in (png_info_props or {})} except Exception: @@ -147,38 +149,40 @@ def process_batch(p, input_dir, output_dir, inpaint_mask_dir, args, to_scale=Fal return batch_results -def img2img_function(id_task: str, mode: int, prompt: str, negative_prompt: str, prompt_styles, init_img, sketch, init_img_with_mask, inpaint_color_sketch, inpaint_color_sketch_orig, init_img_inpaint, init_mask_inpaint, steps: int, sampler_name: str, mask_blur: int, mask_alpha: float, inpainting_fill: int, n_iter: int, batch_size: int, cfg_scale: float, image_cfg_scale: float, denoising_strength: float, selected_scale_tab: int, height: int, width: int, scale_by: float, resize_mode: int, inpaint_full_res: bool, inpaint_full_res_padding: int, inpainting_mask_invert: int, img2img_batch_input_dir: str, img2img_batch_output_dir: str, img2img_batch_inpaint_mask_dir: str, override_settings_texts, img2img_batch_use_png_info: bool, img2img_batch_png_info_props: list, img2img_batch_png_info_dir: str, request: gr.Request, *args): +def img2img_function(id_task: str, request: gr.Request, mode: int, prompt: str, negative_prompt: str, prompt_styles, init_img, sketch, sketch_fg, init_img_with_mask, init_img_with_mask_fg, inpaint_color_sketch, inpaint_color_sketch_fg, init_img_inpaint, init_mask_inpaint, mask_blur: int, mask_alpha: float, inpainting_fill: int, n_iter: int, batch_size: int, cfg_scale: float, image_cfg_scale: float, denoising_strength: float, selected_scale_tab: int, height: int, width: int, scale_by: float, resize_mode: int, inpaint_full_res: bool, inpaint_full_res_padding: int, inpainting_mask_invert: int, img2img_batch_input_dir: str, img2img_batch_output_dir: str, img2img_batch_inpaint_mask_dir: str, override_settings_texts, img2img_batch_use_png_info: bool, img2img_batch_png_info_props: list, img2img_batch_png_info_dir: str, img2img_batch_source_type: str, img2img_batch_upload: list, *args): + override_settings = create_override_settings_dict(override_settings_texts) is_batch = mode == 5 + height, width = int(height), int(width) + if mode == 0: # img2img image = init_img mask = None elif mode == 1: # img2img sketch - image = sketch mask = None + image = Image.alpha_composite(sketch, sketch_fg) elif mode == 2: # inpaint - image, mask = init_img_with_mask["image"], init_img_with_mask["mask"] - mask = processing.create_binary_mask(mask) + image = init_img_with_mask + mask = init_img_with_mask_fg.getchannel('A').convert('L') + mask = Image.merge('RGBA', (mask, mask, mask, Image.new('L', mask.size, 255))) elif mode == 3: # inpaint sketch - image = inpaint_color_sketch - orig = inpaint_color_sketch_orig or inpaint_color_sketch - pred = np.any(np.array(image) != np.array(orig), axis=-1) - mask = Image.fromarray(pred.astype(np.uint8) * 255, "L") - mask = ImageEnhance.Brightness(mask).enhance(1 - mask_alpha / 100) - blur = ImageFilter.GaussianBlur(mask_blur) - image = Image.composite(image.filter(blur), orig, mask.filter(blur)) + image = Image.alpha_composite(inpaint_color_sketch, inpaint_color_sketch_fg) + mask = inpaint_color_sketch_fg.getchannel('A').convert('L') + short_side = min(mask.size) + dilation_size = int(0.015 * short_side) * 2 + 1 + mask = mask.filter(ImageFilter.MaxFilter(dilation_size)) + mask = Image.merge('RGBA', (mask, mask, mask, Image.new('L', mask.size, 255))) elif mode == 4: # inpaint upload mask image = init_img_inpaint mask = init_mask_inpaint - else: - image = None - mask = None - # Use the EXIF orientation of photos taken by smartphones. - if image is not None: - image = ImageOps.exif_transpose(image) + if mask and isinstance(mask, Image.Image): + mask = mask.point(lambda v: 255 if v > 128 else 0) + + image = images.fix_image(image) + mask = images.fix_image(mask) if selected_scale_tab == 1 and not is_batch: assert image, "Can't scale by because no image is selected" @@ -195,10 +199,8 @@ def img2img_function(id_task: str, mode: int, prompt: str, negative_prompt: str, prompt=prompt, negative_prompt=negative_prompt, styles=prompt_styles, - sampler_name=sampler_name, batch_size=batch_size, n_iter=n_iter, - steps=steps, cfg_scale=cfg_scale, width=width, height=height, @@ -225,8 +227,15 @@ def img2img_function(id_task: str, mode: int, prompt: str, negative_prompt: str, with closing(p): if is_batch: - assert not shared.cmd_opts.hide_ui_dir_config, "Launched with --hide-ui-dir-config, batch img2img disabled" - processed = process_batch(p, img2img_batch_input_dir, img2img_batch_output_dir, img2img_batch_inpaint_mask_dir, args, to_scale=selected_scale_tab == 1, scale_by=scale_by, use_png_info=img2img_batch_use_png_info, png_info_props=img2img_batch_png_info_props, png_info_dir=img2img_batch_png_info_dir) + if img2img_batch_source_type == "upload": + assert isinstance(img2img_batch_upload, list) and img2img_batch_upload + output_dir = "" + inpaint_mask_dir = "" + png_info_dir = img2img_batch_png_info_dir if not shared.cmd_opts.hide_ui_dir_config else "" + processed = process_batch(p, img2img_batch_upload, output_dir, inpaint_mask_dir, args, to_scale=selected_scale_tab == 1, scale_by=scale_by, use_png_info=img2img_batch_use_png_info, png_info_props=img2img_batch_png_info_props, png_info_dir=png_info_dir) + else: # "from dir" + assert not shared.cmd_opts.hide_ui_dir_config, "Launched with --hide-ui-dir-config, batch img2img disabled" + processed = process_batch(p, img2img_batch_input_dir, img2img_batch_output_dir, img2img_batch_inpaint_mask_dir, args, to_scale=selected_scale_tab == 1, scale_by=scale_by, use_png_info=img2img_batch_use_png_info, png_info_props=img2img_batch_png_info_props, png_info_dir=img2img_batch_png_info_dir) if processed is None: processed = Processed(p, [], p.seed, "") @@ -247,5 +256,5 @@ def img2img_function(id_task: str, mode: int, prompt: str, negative_prompt: str, return processed.images + processed.extra_images, generation_info_js, plaintext_to_html(processed.info), plaintext_to_html(processed.comments, classname="comments") -def img2img(id_task: str, mode: int, prompt: str, negative_prompt: str, prompt_styles, init_img, sketch, init_img_with_mask, inpaint_color_sketch, inpaint_color_sketch_orig, init_img_inpaint, init_mask_inpaint, steps: int, sampler_name: str, mask_blur: int, mask_alpha: float, inpainting_fill: int, n_iter: int, batch_size: int, cfg_scale: float, image_cfg_scale: float, denoising_strength: float, selected_scale_tab: int, height: int, width: int, scale_by: float, resize_mode: int, inpaint_full_res: bool, inpaint_full_res_padding: int, inpainting_mask_invert: int, img2img_batch_input_dir: str, img2img_batch_output_dir: str, img2img_batch_inpaint_mask_dir: str, override_settings_texts, img2img_batch_use_png_info: bool, img2img_batch_png_info_props: list, img2img_batch_png_info_dir: str, request: gr.Request, *args): - return main_thread.run_and_wait_result(img2img_function, id_task, mode, prompt, negative_prompt, prompt_styles, init_img, sketch, init_img_with_mask, inpaint_color_sketch, inpaint_color_sketch_orig, init_img_inpaint, init_mask_inpaint, steps, sampler_name, mask_blur, mask_alpha, inpainting_fill, n_iter, batch_size, cfg_scale, image_cfg_scale, denoising_strength, selected_scale_tab, height, width, scale_by, resize_mode, inpaint_full_res, inpaint_full_res_padding, inpainting_mask_invert, img2img_batch_input_dir, img2img_batch_output_dir, img2img_batch_inpaint_mask_dir, override_settings_texts, img2img_batch_use_png_info, img2img_batch_png_info_props, img2img_batch_png_info_dir, request, *args) +def img2img(id_task: str, request: gr.Request, mode: int, prompt: str, negative_prompt: str, prompt_styles, init_img, sketch, sketch_fg, init_img_with_mask, init_img_with_mask_fg, inpaint_color_sketch, inpaint_color_sketch_fg, init_img_inpaint, init_mask_inpaint, mask_blur: int, mask_alpha: float, inpainting_fill: int, n_iter: int, batch_size: int, cfg_scale: float, image_cfg_scale: float, denoising_strength: float, selected_scale_tab: int, height: int, width: int, scale_by: float, resize_mode: int, inpaint_full_res: bool, inpaint_full_res_padding: int, inpainting_mask_invert: int, img2img_batch_input_dir: str, img2img_batch_output_dir: str, img2img_batch_inpaint_mask_dir: str, override_settings_texts, img2img_batch_use_png_info: bool, img2img_batch_png_info_props: list, img2img_batch_png_info_dir: str, img2img_batch_source_type: str, img2img_batch_upload: list, *args): + return main_thread.run_and_wait_result(img2img_function, id_task, request, mode, prompt, negative_prompt, prompt_styles, init_img, sketch, sketch_fg, init_img_with_mask, init_img_with_mask_fg, inpaint_color_sketch, inpaint_color_sketch_fg, init_img_inpaint, init_mask_inpaint, mask_blur, mask_alpha, inpainting_fill, n_iter, batch_size, cfg_scale, image_cfg_scale, denoising_strength, selected_scale_tab, height, width, scale_by, resize_mode, inpaint_full_res, inpaint_full_res_padding, inpainting_mask_invert, img2img_batch_input_dir, img2img_batch_output_dir, img2img_batch_inpaint_mask_dir, override_settings_texts, img2img_batch_use_png_info, img2img_batch_png_info_props, img2img_batch_png_info_dir, img2img_batch_source_type, img2img_batch_upload, *args) diff --git a/modules/infotext_utils.py b/modules/infotext_utils.py index d21d33330..0f488b0de 100644 --- a/modules/infotext_utils.py +++ b/modules/infotext_utils.py @@ -8,7 +8,7 @@ import gradio as gr from modules.paths import data_path -from modules import shared, ui_tempdir, script_callbacks, processing, infotext_versions +from modules import shared, ui_tempdir, script_callbacks, processing, infotext_versions, images, prompt_parser, errors from PIL import Image sys.modules['modules.generation_parameters_copypaste'] = sys.modules[__name__] # alias for old name @@ -74,29 +74,38 @@ def image_from_url_text(filedata): if filedata is None: return None - if type(filedata) == list and filedata and type(filedata[0]) == dict and filedata[0].get("is_file", False): + if isinstance(filedata, list): + if len(filedata) == 0: + return None + filedata = filedata[0] + if isinstance(filedata, dict) and filedata.get("is_file", False): + filedata = filedata + + filename = None if type(filedata) == dict and filedata.get("is_file", False): filename = filedata["name"] + + elif isinstance(filedata, tuple) and len(filedata) == 2: # gradio 4.16 sends images from gallery as a list of tuples + return filedata[0] + + if filename: is_in_right_dir = ui_tempdir.check_tmp_file(shared.demo, filename) assert is_in_right_dir, 'trying to open image file outside of allowed directories' filename = filename.rsplit('?', 1)[0] - return Image.open(filename) + return images.read(filename) - if type(filedata) == list: - if len(filedata) == 0: - return None + if isinstance(filedata, str): + if filedata.startswith("data:image/png;base64,"): + filedata = filedata[len("data:image/png;base64,"):] - filedata = filedata[0] - - if filedata.startswith("data:image/png;base64,"): - filedata = filedata[len("data:image/png;base64,"):] + filedata = base64.decodebytes(filedata.encode('utf-8')) + image = images.read(io.BytesIO(filedata)) + return image - filedata = base64.decodebytes(filedata.encode('utf-8')) - image = Image.open(io.BytesIO(filedata)) - return image + return None def add_paste_fields(tabname, init_img, fields, override_settings_component=None): @@ -138,8 +147,6 @@ def register_paste_params_button(binding: ParamBinding): def connect_paste_params_buttons(): for binding in registered_param_bindings: - if binding.tabname not in paste_fields: - continue destination_image_component = paste_fields[binding.tabname]["init_img"] fields = paste_fields[binding.tabname]["fields"] override_settings_component = binding.override_settings_component or paste_fields[binding.tabname]["override_settings_component"] @@ -148,18 +155,19 @@ def connect_paste_params_buttons(): destination_height_component = next(iter([field for field, name in fields if name == "Size-2"] if fields else []), None) if binding.source_image_component and destination_image_component: + need_send_dementions = destination_width_component and binding.tabname != 'inpaint' if isinstance(binding.source_image_component, gr.Gallery): - func = send_image_and_dimensions if destination_width_component else image_from_url_text + func = send_image_and_dimensions if need_send_dementions else image_from_url_text jsfunc = "extract_image_from_gallery" else: - func = send_image_and_dimensions if destination_width_component else lambda x: x + func = send_image_and_dimensions if need_send_dementions else lambda x: x jsfunc = None binding.paste_button.click( fn=func, _js=jsfunc, inputs=[binding.source_image_component], - outputs=[destination_image_component, destination_width_component, destination_height_component] if destination_width_component else [destination_image_component], + outputs=[destination_image_component, destination_width_component, destination_height_component] if need_send_dementions else [destination_image_component], show_progress=False, ) @@ -187,6 +195,8 @@ def connect_paste_params_buttons(): def send_image_and_dimensions(x): if isinstance(x, Image.Image): img = x + elif isinstance(x, list) and isinstance(x[0], tuple): + img = x[0][0] else: img = image_from_url_text(x) @@ -267,17 +277,6 @@ def parse_generation_parameters(x: str, skip_fields: list[str] | None = None): else: prompt += ("" if prompt == "" else "\n") + line - if shared.opts.infotext_styles != "Ignore": - found_styles, prompt, negative_prompt = shared.prompt_styles.extract_styles_from_prompt(prompt, negative_prompt) - - if shared.opts.infotext_styles == "Apply": - res["Styles array"] = found_styles - elif shared.opts.infotext_styles == "Apply if any" and found_styles: - res["Styles array"] = found_styles - - res["Prompt"] = prompt - res["Negative prompt"] = negative_prompt - for k, v in re_param.findall(lastline): try: if v[0] == '"' and v[-1] == '"': @@ -292,6 +291,26 @@ def parse_generation_parameters(x: str, skip_fields: list[str] | None = None): except Exception: print(f"Error parsing \"{k}: {v}\"") + # Extract styles from prompt + if shared.opts.infotext_styles != "Ignore": + found_styles, prompt_no_styles, negative_prompt_no_styles = shared.prompt_styles.extract_styles_from_prompt(prompt, negative_prompt) + + same_hr_styles = True + if ("Hires prompt" in res or "Hires negative prompt" in res) and (infotext_ver > infotext_versions.v180_hr_styles if (infotext_ver := infotext_versions.parse_version(res.get("Version"))) else True): + hr_prompt, hr_negative_prompt = res.get("Hires prompt", prompt), res.get("Hires negative prompt", negative_prompt) + hr_found_styles, hr_prompt_no_styles, hr_negative_prompt_no_styles = shared.prompt_styles.extract_styles_from_prompt(hr_prompt, hr_negative_prompt) + if same_hr_styles := found_styles == hr_found_styles: + res["Hires prompt"] = '' if hr_prompt_no_styles == prompt_no_styles else hr_prompt_no_styles + res['Hires negative prompt'] = '' if hr_negative_prompt_no_styles == negative_prompt_no_styles else hr_negative_prompt_no_styles + + if same_hr_styles: + prompt, negative_prompt = prompt_no_styles, negative_prompt_no_styles + if (shared.opts.infotext_styles == "Apply if any" and found_styles) or shared.opts.infotext_styles == "Apply": + res['Styles array'] = found_styles + + res["Prompt"] = prompt + res["Negative prompt"] = negative_prompt + # Missing CLIP skip means it was set to 1 (the default) if "Clip skip" not in res: res["Clip skip"] = "1" @@ -307,6 +326,9 @@ def parse_generation_parameters(x: str, skip_fields: list[str] | None = None): if "Hires sampler" not in res: res["Hires sampler"] = "Use same sampler" + if "Hires schedule type" not in res: + res["Hires schedule type"] = "Use same scheduler" + if "Hires checkpoint" not in res: res["Hires checkpoint"] = "Use same checkpoint" @@ -358,9 +380,15 @@ def parse_generation_parameters(x: str, skip_fields: list[str] | None = None): if "Cache FP16 weight for LoRA" not in res and res["FP8 weight"] != "Disable": res["Cache FP16 weight for LoRA"] = False - if "Emphasis" not in res: + prompt_attention = prompt_parser.parse_prompt_attention(prompt) + prompt_attention += prompt_parser.parse_prompt_attention(negative_prompt) + prompt_uses_emphasis = len(prompt_attention) != len([p for p in prompt_attention if p[1] == 1.0 or p[0] == 'BREAK']) + if "Emphasis" not in res and prompt_uses_emphasis: res["Emphasis"] = "Original" + if "Refiner switch by sampling steps" not in res: + res["Refiner switch by sampling steps"] = False + infotext_versions.backcompat(res) for key in skip_fields: @@ -396,6 +424,9 @@ def create_override_settings_dict(text_pairs): res = {} + if not text_pairs: + return res + params = {} for pair in text_pairs: k, v = pair.split(":", maxsplit=1) @@ -458,7 +489,7 @@ def get_override_settings(params, *, skip_fields=None): def connect_paste(button, paste_fields, input_comp, override_settings_component, tabname): def paste_func(prompt): - if not prompt and not shared.cmd_opts.hide_ui_dir_config: + if not prompt and not shared.cmd_opts.hide_ui_dir_config and not shared.cmd_opts.no_prompt_history: filename = os.path.join(data_path, "params.txt") try: with open(filename, "r", encoding="utf8") as file: @@ -472,7 +503,11 @@ def paste_func(prompt): for output, key in paste_fields: if callable(key): - v = key(params) + try: + v = key(params) + except Exception: + errors.report(f"Error executing {key}", exc_info=True) + v = None else: v = params.get(key, None) diff --git a/modules/infotext_versions.py b/modules/infotext_versions.py index 23b45c3f9..cea676cda 100644 --- a/modules/infotext_versions.py +++ b/modules/infotext_versions.py @@ -5,6 +5,8 @@ v160 = version.parse("1.6.0") v170_tsnr = version.parse("v1.7.0-225") +v180 = version.parse("1.8.0") +v180_hr_styles = version.parse("1.8.0-139") def parse_version(text): @@ -40,3 +42,5 @@ def backcompat(d): if ver < v170_tsnr: d["Downcast alphas_cumprod"] = True + if ver < v180 and d.get('Refiner'): + d["Refiner switch by sampling steps"] = True diff --git a/modules/initialize.py b/modules/initialize.py index 180e1f8e6..ec4d58a43 100644 --- a/modules/initialize.py +++ b/modules/initialize.py @@ -50,7 +50,7 @@ def imports(): shared_init.initialize() startup_timer.record("initialize shared") - from modules import processing, gradio_extensons, ui # noqa: F401 + from modules import processing, gradio_extensions, ui # noqa: F401 startup_timer.record("other imports") @@ -65,6 +65,7 @@ def check_versions(): def initialize(): from modules import initialize_util initialize_util.fix_torch_version() + initialize_util.fix_pytorch_lightning() initialize_util.fix_asyncio_event_loop_policy() initialize_util.validate_tls_options() initialize_util.configure_sigint_handler() @@ -123,7 +124,7 @@ def initialize_rest(*, reload_script_modules=False): with startup_timer.subcategory("load scripts"): scripts.load_scripts() - if reload_script_modules: + if reload_script_modules and shared.opts.enable_reloading_ui_scripts: for module in [module for name, module in sys.modules.items() if name.startswith("modules.ui")]: importlib.reload(module) startup_timer.record("reload script modules") diff --git a/modules/initialize_util.py b/modules/initialize_util.py index 7801d9329..693b083c5 100644 --- a/modules/initialize_util.py +++ b/modules/initialize_util.py @@ -4,6 +4,8 @@ import sys import re +import starlette + from modules.timer import startup_timer @@ -24,6 +26,13 @@ def fix_torch_version(): torch.__long_version__ = torch.__version__ torch.__version__ = re.search(r'[\d.]+[\d]', torch.__version__).group(0) +def fix_pytorch_lightning(): + # Checks if pytorch_lightning.utilities.distributed already exists in the sys.modules cache + if 'pytorch_lightning.utilities.distributed' not in sys.modules: + import pytorch_lightning + # Lets the user know that the library was not found and then will set it to pytorch_lightning.utilities.rank_zero + print("Pytorch_lightning.distributed not found, attempting pytorch_lightning.rank_zero") + sys.modules["pytorch_lightning.utilities.distributed"] = pytorch_lightning.utilities.rank_zero def fix_asyncio_event_loop_policy(): """ @@ -186,8 +195,7 @@ def configure_opts_onchange(): def setup_middleware(app): from starlette.middleware.gzip import GZipMiddleware - app.middleware_stack = None # reset current middleware to allow modifying user provided list - app.add_middleware(GZipMiddleware, minimum_size=1000) + app.user_middleware.insert(0, starlette.middleware.Middleware(GZipMiddleware, minimum_size=1000)) configure_cors_middleware(app) app.build_middleware_stack() # rebuild middleware stack on-the-fly @@ -205,5 +213,6 @@ def configure_cors_middleware(app): cors_options["allow_origins"] = cmd_opts.cors_allow_origins.split(',') if cmd_opts.cors_allow_origins_regex: cors_options["allow_origin_regex"] = cmd_opts.cors_allow_origins_regex - app.add_middleware(CORSMiddleware, **cors_options) + + app.user_middleware.insert(0, starlette.middleware.Middleware(CORSMiddleware, **cors_options)) diff --git a/modules/launch_utils.py b/modules/launch_utils.py index 596d14b66..4aac9e97c 100644 --- a/modules/launch_utils.py +++ b/modules/launch_utils.py @@ -9,6 +9,7 @@ import importlib.metadata import platform import json +import shlex from functools import lru_cache from typing import NamedTuple from pathlib import Path @@ -60,7 +61,7 @@ def check_python_version(): You can download 3.10 Python from here: https://www.python.org/downloads/release/python-3106/ -{"Alternatively, use a binary release of WebUI: https://github.com/AUTOMATIC1111/stable-diffusion-webui/releases" if is_windows else ""} +{"Alternatively, use a binary release of WebUI: https://github.com/AUTOMATIC1111/stable-diffusion-webui/releases/tag/v1.0.0-pre" if is_windows else ""} Use --skip-python-version-check to suppress this warning. """) @@ -81,7 +82,7 @@ def git_tag_a1111(): except Exception: try: - changelog_md = os.path.join(os.path.dirname(os.path.dirname(__file__)), "CHANGELOG.md") + changelog_md = os.path.join(script_path, "CHANGELOG.md") with open(changelog_md, "r", encoding="utf-8") as file: line = next((line.strip() for line in file if line.strip()), "") line = line.replace("## ", "") @@ -240,7 +241,7 @@ def run_extension_installer(extension_dir): try: env = os.environ.copy() - env['PYTHONPATH'] = f"{os.path.abspath('.')}{os.pathsep}{env.get('PYTHONPATH', '')}" + env['PYTHONPATH'] = f"{script_path}{os.pathsep}{env.get('PYTHONPATH', '')}" stdout = run(f'"{python}" "{path_installer}"', errdesc=f"Error running install.py for extension {extension_dir}", custom_env=env).strip() if stdout: @@ -490,7 +491,6 @@ def prepare_environment(): exit(0) - def configure_for_tests(): if "--api" not in sys.argv: sys.argv.append("--api") @@ -537,7 +537,7 @@ class ModelRef(NamedTuple): def start(): - print(f"Launching {'API server' if '--nowebui' in sys.argv else 'Web UI'} with arguments: {' '.join(sys.argv[1:])}") + print(f"Launching {'API server' if '--nowebui' in sys.argv else 'Web UI'} with arguments: {shlex.join(sys.argv[1:])}") import webui if '--nowebui' in sys.argv: webui.api_only() diff --git a/modules/lowvram.py b/modules/lowvram.py index 908b5962d..b6dcaf527 100644 --- a/modules/lowvram.py +++ b/modules/lowvram.py @@ -1,9 +1,12 @@ +from collections import namedtuple + import torch from modules import devices, shared module_in_gpu = None cpu = torch.device("cpu") +ModuleWithParent = namedtuple('ModuleWithParent', ['module', 'parent'], defaults=['None']) def send_everything_to_cpu(): return diff --git a/modules/mac_specific.py b/modules/mac_specific.py index d96d86d79..039689f32 100644 --- a/modules/mac_specific.py +++ b/modules/mac_specific.py @@ -12,7 +12,7 @@ # before torch version 1.13, has_mps is only available in nightly pytorch and macOS 12.3+, # use check `getattr` and try it for compatibility. -# in torch version 1.13, backends.mps.is_available() and backends.mps.is_built() are introduced in to check mps availabilty, +# in torch version 1.13, backends.mps.is_available() and backends.mps.is_built() are introduced in to check mps availability, # since torch 2.0.1+ nightly build, getattr(torch, 'has_mps', False) was deprecated, see https://github.com/pytorch/pytorch/pull/103279 def check_for_mps() -> bool: if version.parse(torch.__version__) <= version.parse("2.0.1"): diff --git a/modules/masking.py b/modules/masking.py index 29a394527..2fc830319 100644 --- a/modules/masking.py +++ b/modules/masking.py @@ -1,17 +1,39 @@ from PIL import Image, ImageFilter, ImageOps -def get_crop_region(mask, pad=0): - """finds a rectangular region that contains all masked ares in an image. Returns (x1, y1, x2, y2) coordinates of the rectangle. - For example, if a user has painted the top-right part of a 512x512 image, the result may be (256, 0, 512, 256)""" - mask_img = mask if isinstance(mask, Image.Image) else Image.fromarray(mask) - box = mask_img.getbbox() - if box: +def get_crop_region_v2(mask, pad=0): + """ + Finds a rectangular region that contains all masked ares in a mask. + Returns None if mask is completely black mask (all 0) + + Parameters: + mask: PIL.Image.Image L mode or numpy 1d array + pad: int number of pixels that the region will be extended on all sides + Returns: (x1, y1, x2, y2) | None + + Introduced post 1.9.0 + """ + mask = mask if isinstance(mask, Image.Image) else Image.fromarray(mask) + if box := mask.getbbox(): x1, y1, x2, y2 = box - else: # when no box is found - x1, y1 = mask_img.size - x2 = y2 = 0 - return max(x1 - pad, 0), max(y1 - pad, 0), min(x2 + pad, mask_img.size[0]), min(y2 + pad, mask_img.size[1]) + return (max(x1 - pad, 0), max(y1 - pad, 0), min(x2 + pad, mask.size[0]), min(y2 + pad, mask.size[1])) if pad else box + + +def get_crop_region(mask, pad=0): + """ + Same function as get_crop_region_v2 but handles completely black mask (all 0) differently + when mask all black still return coordinates but the coordinates may be invalid ie x2>x1 or y2>y1 + Notes: it is possible for the coordinates to be "valid" again if pad size is sufficiently large + (mask_size.x-pad, mask_size.y-pad, pad, pad) + + Extension developer should use get_crop_region_v2 instead unless for compatibility considerations. + """ + mask = mask if isinstance(mask, Image.Image) else Image.fromarray(mask) + if box := get_crop_region_v2(mask, pad): + return box + x1, y1 = mask.size + x2 = y2 = 0 + return max(x1 - pad, 0), max(y1 - pad, 0), min(x2 + pad, mask.size[0]), min(y2 + pad, mask.size[1]) def expand_crop_region(crop_region, processing_width, processing_height, image_width, image_height): diff --git a/modules/modelloader.py b/modules/modelloader.py index e100bb246..36e7415af 100644 --- a/modules/modelloader.py +++ b/modules/modelloader.py @@ -23,6 +23,7 @@ def load_file_from_url( model_dir: str, progress: bool = True, file_name: str | None = None, + hash_prefix: str | None = None, ) -> str: """Download a file from `url` into `model_dir`, using the file present if possible. @@ -36,11 +37,11 @@ def load_file_from_url( if not os.path.exists(cached_file): print(f'Downloading: "{url}" to {cached_file}\n') from torch.hub import download_url_to_file - download_url_to_file(url, cached_file, progress=progress) + download_url_to_file(url, cached_file, progress=progress, hash_prefix=hash_prefix) return cached_file -def load_models(model_path: str, model_url: str = None, command_path: str = None, ext_filter=None, download_name=None, ext_blacklist=None) -> list: +def load_models(model_path: str, model_url: str = None, command_path: str = None, ext_filter=None, download_name=None, ext_blacklist=None, hash_prefix=None) -> list: """ A one-and done loader to try finding the desired models in specified directories. @@ -49,6 +50,7 @@ def load_models(model_path: str, model_url: str = None, command_path: str = None @param model_path: The location to store/find models in. @param command_path: A command-line argument to search for models in first. @param ext_filter: An optional list of filename extensions to filter by + @param hash_prefix: the expected sha256 of the model_url @return: A list of paths containing the desired model(s) """ output = [] @@ -78,7 +80,7 @@ def load_models(model_path: str, model_url: str = None, command_path: str = None if model_url is not None and len(output) == 0: if download_name is not None: - output.append(load_file_from_url(model_url, model_dir=places[0], file_name=download_name)) + output.append(load_file_from_url(model_url, model_dir=places[0], file_name=download_name, hash_prefix=hash_prefix)) else: output.append(model_url) @@ -110,7 +112,7 @@ def load_upscalers(): except Exception: pass - datas = [] + data = [] commandline_options = vars(shared.cmd_opts) # some of upscaler classes will not go away after reloading their modules, and we'll end @@ -129,14 +131,35 @@ def load_upscalers(): scaler = cls(commandline_model_path) scaler.user_path = commandline_model_path scaler.model_download_path = commandline_model_path or scaler.model_path - datas += scaler.scalers + data += scaler.scalers shared.sd_upscalers = sorted( - datas, + data, # Special case for UpscalerNone keeps it at the beginning of the list. key=lambda x: x.name.lower() if not isinstance(x.scaler, (UpscalerNone, UpscalerLanczos, UpscalerNearest)) else "" ) +# None: not loaded, False: failed to load, True: loaded +_spandrel_extra_init_state = None + + +def _init_spandrel_extra_archs() -> None: + """ + Try to initialize `spandrel_extra_archs` (exactly once). + """ + global _spandrel_extra_init_state + if _spandrel_extra_init_state is not None: + return + + try: + import spandrel + import spandrel_extra_arches + spandrel.MAIN_REGISTRY.add(*spandrel_extra_arches.EXTRA_REGISTRY) + _spandrel_extra_init_state = True + except Exception: + logger.warning("Failed to load spandrel_extra_arches", exc_info=True) + _spandrel_extra_init_state = False + def load_spandrel_model( path: str | os.PathLike, @@ -146,11 +169,16 @@ def load_spandrel_model( dtype: str | torch.dtype | None = None, expected_architecture: str | None = None, ) -> spandrel.ModelDescriptor: + global _spandrel_extra_init_state + import spandrel + _init_spandrel_extra_archs() + model_descriptor = spandrel.ModelLoader(device=device).load_from_file(str(path)) - if expected_architecture and model_descriptor.architecture != expected_architecture: + arch = model_descriptor.architecture + if expected_architecture and arch.name != expected_architecture: logger.warning( - f"Model {path!r} is not a {expected_architecture!r} model (got {model_descriptor.architecture!r})", + f"Model {path!r} is not a {expected_architecture!r} model (got {arch.name!r})", ) half = False if prefer_half: @@ -164,6 +192,6 @@ def load_spandrel_model( model_descriptor.model.eval() logger.debug( "Loaded %s from %s (device=%s, half=%s, dtype=%s)", - model_descriptor, path, device, half, dtype, + arch, path, device, half, dtype, ) return model_descriptor diff --git a/modules/models/diffusion/ddpm_edit.py b/modules/models/diffusion/ddpm_edit.py index 6db340da4..7b51c83c5 100644 --- a/modules/models/diffusion/ddpm_edit.py +++ b/modules/models/diffusion/ddpm_edit.py @@ -341,7 +341,7 @@ def p_losses(self, x_start, t, noise=None): elif self.parameterization == "x0": target = x_start else: - raise NotImplementedError(f"Paramterization {self.parameterization} not yet supported") + raise NotImplementedError(f"Parameterization {self.parameterization} not yet supported") loss = self.get_loss(model_out, target, mean=False).mean(dim=[1, 2, 3]) @@ -901,7 +901,7 @@ def forward(self, x, c, *args, **kwargs): def apply_model(self, x_noisy, t, cond, return_ids=False): if isinstance(cond, dict): - # hybrid case, cond is exptected to be a dict + # hybrid case, cond is expected to be a dict pass else: if not isinstance(cond, list): @@ -937,7 +937,7 @@ def apply_model(self, x_noisy, t, cond, return_ids=False): cond_list = [{c_key: [c[:, :, :, :, i]]} for i in range(c.shape[-1])] elif self.cond_stage_key == 'coordinates_bbox': - assert 'original_image_size' in self.split_input_params, 'BoudingBoxRescaling is missing original_image_size' + assert 'original_image_size' in self.split_input_params, 'BoundingBoxRescaling is missing original_image_size' # assuming padding of unfold is always 0 and its dilation is always 1 n_patches_per_row = int((w - ks[0]) / stride[0] + 1) @@ -947,7 +947,7 @@ def apply_model(self, x_noisy, t, cond, return_ids=False): num_downs = self.first_stage_model.encoder.num_resolutions - 1 rescale_latent = 2 ** (num_downs) - # get top left postions of patches as conforming for the bbbox tokenizer, therefore we + # get top left positions of patches as conforming for the bbbox tokenizer, therefore we # need to rescale the tl patch coordinates to be in between (0,1) tl_patch_coordinates = [(rescale_latent * stride[0] * (patch_nr % n_patches_per_row) / full_img_w, rescale_latent * stride[1] * (patch_nr // n_patches_per_row) / full_img_h) diff --git a/modules/models/diffusion/uni_pc/uni_pc.py b/modules/models/diffusion/uni_pc/uni_pc.py index 4a3651513..3333bc808 100644 --- a/modules/models/diffusion/uni_pc/uni_pc.py +++ b/modules/models/diffusion/uni_pc/uni_pc.py @@ -323,7 +323,7 @@ def cond_grad_fn(x, t_input, condition): def model_fn(x, t_continuous, condition, unconditional_condition): """ - The noise predicition model function that is used for DPM-Solver. + The noise prediction model function that is used for DPM-Solver. """ if t_continuous.reshape((-1,)).shape[0] == 1: t_continuous = t_continuous.expand((x.shape[0])) @@ -445,7 +445,7 @@ def data_prediction_fn(self, x, t): s = torch.quantile(torch.abs(x0).reshape((x0.shape[0], -1)), p, dim=1) s = expand_dims(torch.maximum(s, self.max_val * torch.ones_like(s).to(s.device)), dims) x0 = torch.clamp(x0, -s, s) / s - return x0.to(x) + return x0 def model_fn(self, x, t): """ diff --git a/modules/models/sd3/mmdit.py b/modules/models/sd3/mmdit.py new file mode 100644 index 000000000..8ddf49a4e --- /dev/null +++ b/modules/models/sd3/mmdit.py @@ -0,0 +1,622 @@ +### This file contains impls for MM-DiT, the core model component of SD3 + +import math +from typing import Dict, Optional +import numpy as np +import torch +import torch.nn as nn +from einops import rearrange, repeat +from modules.models.sd3.other_impls import attention, Mlp + + +class PatchEmbed(nn.Module): + """ 2D Image to Patch Embedding""" + def __init__( + self, + img_size: Optional[int] = 224, + patch_size: int = 16, + in_chans: int = 3, + embed_dim: int = 768, + flatten: bool = True, + bias: bool = True, + strict_img_size: bool = True, + dynamic_img_pad: bool = False, + dtype=None, + device=None, + ): + super().__init__() + self.patch_size = (patch_size, patch_size) + if img_size is not None: + self.img_size = (img_size, img_size) + self.grid_size = tuple([s // p for s, p in zip(self.img_size, self.patch_size)]) + self.num_patches = self.grid_size[0] * self.grid_size[1] + else: + self.img_size = None + self.grid_size = None + self.num_patches = None + + # flatten spatial dim and transpose to channels last, kept for bwd compat + self.flatten = flatten + self.strict_img_size = strict_img_size + self.dynamic_img_pad = dynamic_img_pad + + self.proj = nn.Conv2d(in_chans, embed_dim, kernel_size=patch_size, stride=patch_size, bias=bias, dtype=dtype, device=device) + + def forward(self, x): + B, C, H, W = x.shape + x = self.proj(x) + if self.flatten: + x = x.flatten(2).transpose(1, 2) # NCHW -> NLC + return x + + +def modulate(x, shift, scale): + if shift is None: + shift = torch.zeros_like(scale) + return x * (1 + scale.unsqueeze(1)) + shift.unsqueeze(1) + + +################################################################################# +# Sine/Cosine Positional Embedding Functions # +################################################################################# + + +def get_2d_sincos_pos_embed(embed_dim, grid_size, cls_token=False, extra_tokens=0, scaling_factor=None, offset=None): + """ + grid_size: int of the grid height and width + return: + pos_embed: [grid_size*grid_size, embed_dim] or [1+grid_size*grid_size, embed_dim] (w/ or w/o cls_token) + """ + grid_h = np.arange(grid_size, dtype=np.float32) + grid_w = np.arange(grid_size, dtype=np.float32) + grid = np.meshgrid(grid_w, grid_h) # here w goes first + grid = np.stack(grid, axis=0) + if scaling_factor is not None: + grid = grid / scaling_factor + if offset is not None: + grid = grid - offset + grid = grid.reshape([2, 1, grid_size, grid_size]) + pos_embed = get_2d_sincos_pos_embed_from_grid(embed_dim, grid) + if cls_token and extra_tokens > 0: + pos_embed = np.concatenate([np.zeros([extra_tokens, embed_dim]), pos_embed], axis=0) + return pos_embed + + +def get_2d_sincos_pos_embed_from_grid(embed_dim, grid): + assert embed_dim % 2 == 0 + # use half of dimensions to encode grid_h + emb_h = get_1d_sincos_pos_embed_from_grid(embed_dim // 2, grid[0]) # (H*W, D/2) + emb_w = get_1d_sincos_pos_embed_from_grid(embed_dim // 2, grid[1]) # (H*W, D/2) + emb = np.concatenate([emb_h, emb_w], axis=1) # (H*W, D) + return emb + + +def get_1d_sincos_pos_embed_from_grid(embed_dim, pos): + """ + embed_dim: output dimension for each position + pos: a list of positions to be encoded: size (M,) + out: (M, D) + """ + assert embed_dim % 2 == 0 + omega = np.arange(embed_dim // 2, dtype=np.float64) + omega /= embed_dim / 2.0 + omega = 1.0 / 10000**omega # (D/2,) + pos = pos.reshape(-1) # (M,) + out = np.einsum("m,d->md", pos, omega) # (M, D/2), outer product + emb_sin = np.sin(out) # (M, D/2) + emb_cos = np.cos(out) # (M, D/2) + return np.concatenate([emb_sin, emb_cos], axis=1) # (M, D) + + +################################################################################# +# Embedding Layers for Timesteps and Class Labels # +################################################################################# + + +class TimestepEmbedder(nn.Module): + """Embeds scalar timesteps into vector representations.""" + + def __init__(self, hidden_size, frequency_embedding_size=256, dtype=None, device=None): + super().__init__() + self.mlp = nn.Sequential( + nn.Linear(frequency_embedding_size, hidden_size, bias=True, dtype=dtype, device=device), + nn.SiLU(), + nn.Linear(hidden_size, hidden_size, bias=True, dtype=dtype, device=device), + ) + self.frequency_embedding_size = frequency_embedding_size + + @staticmethod + def timestep_embedding(t, dim, max_period=10000): + """ + Create sinusoidal timestep embeddings. + :param t: a 1-D Tensor of N indices, one per batch element. + These may be fractional. + :param dim: the dimension of the output. + :param max_period: controls the minimum frequency of the embeddings. + :return: an (N, D) Tensor of positional embeddings. + """ + half = dim // 2 + freqs = torch.exp( + -math.log(max_period) + * torch.arange(start=0, end=half, dtype=torch.float32) + / half + ).to(device=t.device) + args = t[:, None].float() * freqs[None] + embedding = torch.cat([torch.cos(args), torch.sin(args)], dim=-1) + if dim % 2: + embedding = torch.cat([embedding, torch.zeros_like(embedding[:, :1])], dim=-1) + if torch.is_floating_point(t): + embedding = embedding.to(dtype=t.dtype) + return embedding + + def forward(self, t, dtype, **kwargs): + t_freq = self.timestep_embedding(t, self.frequency_embedding_size).to(dtype) + t_emb = self.mlp(t_freq) + return t_emb + + +class VectorEmbedder(nn.Module): + """Embeds a flat vector of dimension input_dim""" + + def __init__(self, input_dim: int, hidden_size: int, dtype=None, device=None): + super().__init__() + self.mlp = nn.Sequential( + nn.Linear(input_dim, hidden_size, bias=True, dtype=dtype, device=device), + nn.SiLU(), + nn.Linear(hidden_size, hidden_size, bias=True, dtype=dtype, device=device), + ) + + def forward(self, x: torch.Tensor) -> torch.Tensor: + return self.mlp(x) + + +################################################################################# +# Core DiT Model # +################################################################################# + + +class QkvLinear(torch.nn.Linear): + pass + +def split_qkv(qkv, head_dim): + qkv = qkv.reshape(qkv.shape[0], qkv.shape[1], 3, -1, head_dim).movedim(2, 0) + return qkv[0], qkv[1], qkv[2] + +def optimized_attention(qkv, num_heads): + return attention(qkv[0], qkv[1], qkv[2], num_heads) + +class SelfAttention(nn.Module): + ATTENTION_MODES = ("xformers", "torch", "torch-hb", "math", "debug") + + def __init__( + self, + dim: int, + num_heads: int = 8, + qkv_bias: bool = False, + qk_scale: Optional[float] = None, + attn_mode: str = "xformers", + pre_only: bool = False, + qk_norm: Optional[str] = None, + rmsnorm: bool = False, + dtype=None, + device=None, + ): + super().__init__() + self.num_heads = num_heads + self.head_dim = dim // num_heads + + self.qkv = QkvLinear(dim, dim * 3, bias=qkv_bias, dtype=dtype, device=device) + if not pre_only: + self.proj = nn.Linear(dim, dim, dtype=dtype, device=device) + assert attn_mode in self.ATTENTION_MODES + self.attn_mode = attn_mode + self.pre_only = pre_only + + if qk_norm == "rms": + self.ln_q = RMSNorm(self.head_dim, elementwise_affine=True, eps=1.0e-6, dtype=dtype, device=device) + self.ln_k = RMSNorm(self.head_dim, elementwise_affine=True, eps=1.0e-6, dtype=dtype, device=device) + elif qk_norm == "ln": + self.ln_q = nn.LayerNorm(self.head_dim, elementwise_affine=True, eps=1.0e-6, dtype=dtype, device=device) + self.ln_k = nn.LayerNorm(self.head_dim, elementwise_affine=True, eps=1.0e-6, dtype=dtype, device=device) + elif qk_norm is None: + self.ln_q = nn.Identity() + self.ln_k = nn.Identity() + else: + raise ValueError(qk_norm) + + def pre_attention(self, x: torch.Tensor): + B, L, C = x.shape + qkv = self.qkv(x) + q, k, v = split_qkv(qkv, self.head_dim) + q = self.ln_q(q).reshape(q.shape[0], q.shape[1], -1) + k = self.ln_k(k).reshape(q.shape[0], q.shape[1], -1) + return (q, k, v) + + def post_attention(self, x: torch.Tensor) -> torch.Tensor: + assert not self.pre_only + x = self.proj(x) + return x + + def forward(self, x: torch.Tensor) -> torch.Tensor: + (q, k, v) = self.pre_attention(x) + x = attention(q, k, v, self.num_heads) + x = self.post_attention(x) + return x + + +class RMSNorm(torch.nn.Module): + def __init__( + self, dim: int, elementwise_affine: bool = False, eps: float = 1e-6, device=None, dtype=None + ): + """ + Initialize the RMSNorm normalization layer. + Args: + dim (int): The dimension of the input tensor. + eps (float, optional): A small value added to the denominator for numerical stability. Default is 1e-6. + Attributes: + eps (float): A small value added to the denominator for numerical stability. + weight (nn.Parameter): Learnable scaling parameter. + """ + super().__init__() + self.eps = eps + self.learnable_scale = elementwise_affine + if self.learnable_scale: + self.weight = nn.Parameter(torch.empty(dim, device=device, dtype=dtype)) + else: + self.register_parameter("weight", None) + + def _norm(self, x): + """ + Apply the RMSNorm normalization to the input tensor. + Args: + x (torch.Tensor): The input tensor. + Returns: + torch.Tensor: The normalized tensor. + """ + return x * torch.rsqrt(x.pow(2).mean(-1, keepdim=True) + self.eps) + + def forward(self, x): + """ + Forward pass through the RMSNorm layer. + Args: + x (torch.Tensor): The input tensor. + Returns: + torch.Tensor: The output tensor after applying RMSNorm. + """ + x = self._norm(x) + if self.learnable_scale: + return x * self.weight.to(device=x.device, dtype=x.dtype) + else: + return x + + +class SwiGLUFeedForward(nn.Module): + def __init__( + self, + dim: int, + hidden_dim: int, + multiple_of: int, + ffn_dim_multiplier: Optional[float] = None, + ): + """ + Initialize the FeedForward module. + + Args: + dim (int): Input dimension. + hidden_dim (int): Hidden dimension of the feedforward layer. + multiple_of (int): Value to ensure hidden dimension is a multiple of this value. + ffn_dim_multiplier (float, optional): Custom multiplier for hidden dimension. Defaults to None. + + Attributes: + w1 (ColumnParallelLinear): Linear transformation for the first layer. + w2 (RowParallelLinear): Linear transformation for the second layer. + w3 (ColumnParallelLinear): Linear transformation for the third layer. + + """ + super().__init__() + hidden_dim = int(2 * hidden_dim / 3) + # custom dim factor multiplier + if ffn_dim_multiplier is not None: + hidden_dim = int(ffn_dim_multiplier * hidden_dim) + hidden_dim = multiple_of * ((hidden_dim + multiple_of - 1) // multiple_of) + + self.w1 = nn.Linear(dim, hidden_dim, bias=False) + self.w2 = nn.Linear(hidden_dim, dim, bias=False) + self.w3 = nn.Linear(dim, hidden_dim, bias=False) + + def forward(self, x): + return self.w2(nn.functional.silu(self.w1(x)) * self.w3(x)) + + +class DismantledBlock(nn.Module): + """A DiT block with gated adaptive layer norm (adaLN) conditioning.""" + + ATTENTION_MODES = ("xformers", "torch", "torch-hb", "math", "debug") + + def __init__( + self, + hidden_size: int, + num_heads: int, + mlp_ratio: float = 4.0, + attn_mode: str = "xformers", + qkv_bias: bool = False, + pre_only: bool = False, + rmsnorm: bool = False, + scale_mod_only: bool = False, + swiglu: bool = False, + qk_norm: Optional[str] = None, + dtype=None, + device=None, + **block_kwargs, + ): + super().__init__() + assert attn_mode in self.ATTENTION_MODES + if not rmsnorm: + self.norm1 = nn.LayerNorm(hidden_size, elementwise_affine=False, eps=1e-6, dtype=dtype, device=device) + else: + self.norm1 = RMSNorm(hidden_size, elementwise_affine=False, eps=1e-6) + self.attn = SelfAttention(dim=hidden_size, num_heads=num_heads, qkv_bias=qkv_bias, attn_mode=attn_mode, pre_only=pre_only, qk_norm=qk_norm, rmsnorm=rmsnorm, dtype=dtype, device=device) + if not pre_only: + if not rmsnorm: + self.norm2 = nn.LayerNorm(hidden_size, elementwise_affine=False, eps=1e-6, dtype=dtype, device=device) + else: + self.norm2 = RMSNorm(hidden_size, elementwise_affine=False, eps=1e-6) + mlp_hidden_dim = int(hidden_size * mlp_ratio) + if not pre_only: + if not swiglu: + self.mlp = Mlp(in_features=hidden_size, hidden_features=mlp_hidden_dim, act_layer=nn.GELU(approximate="tanh"), dtype=dtype, device=device) + else: + self.mlp = SwiGLUFeedForward(dim=hidden_size, hidden_dim=mlp_hidden_dim, multiple_of=256) + self.scale_mod_only = scale_mod_only + if not scale_mod_only: + n_mods = 6 if not pre_only else 2 + else: + n_mods = 4 if not pre_only else 1 + self.adaLN_modulation = nn.Sequential(nn.SiLU(), nn.Linear(hidden_size, n_mods * hidden_size, bias=True, dtype=dtype, device=device)) + self.pre_only = pre_only + + def pre_attention(self, x: torch.Tensor, c: torch.Tensor): + assert x is not None, "pre_attention called with None input" + if not self.pre_only: + if not self.scale_mod_only: + shift_msa, scale_msa, gate_msa, shift_mlp, scale_mlp, gate_mlp = self.adaLN_modulation(c).chunk(6, dim=1) + else: + shift_msa = None + shift_mlp = None + scale_msa, gate_msa, scale_mlp, gate_mlp = self.adaLN_modulation(c).chunk(4, dim=1) + qkv = self.attn.pre_attention(modulate(self.norm1(x), shift_msa, scale_msa)) + return qkv, (x, gate_msa, shift_mlp, scale_mlp, gate_mlp) + else: + if not self.scale_mod_only: + shift_msa, scale_msa = self.adaLN_modulation(c).chunk(2, dim=1) + else: + shift_msa = None + scale_msa = self.adaLN_modulation(c) + qkv = self.attn.pre_attention(modulate(self.norm1(x), shift_msa, scale_msa)) + return qkv, None + + def post_attention(self, attn, x, gate_msa, shift_mlp, scale_mlp, gate_mlp): + assert not self.pre_only + x = x + gate_msa.unsqueeze(1) * self.attn.post_attention(attn) + x = x + gate_mlp.unsqueeze(1) * self.mlp(modulate(self.norm2(x), shift_mlp, scale_mlp)) + return x + + def forward(self, x: torch.Tensor, c: torch.Tensor) -> torch.Tensor: + assert not self.pre_only + (q, k, v), intermediates = self.pre_attention(x, c) + attn = attention(q, k, v, self.attn.num_heads) + return self.post_attention(attn, *intermediates) + + +def block_mixing(context, x, context_block, x_block, c): + assert context is not None, "block_mixing called with None context" + context_qkv, context_intermediates = context_block.pre_attention(context, c) + + x_qkv, x_intermediates = x_block.pre_attention(x, c) + + o = [] + for t in range(3): + o.append(torch.cat((context_qkv[t], x_qkv[t]), dim=1)) + q, k, v = tuple(o) + + attn = attention(q, k, v, x_block.attn.num_heads) + context_attn, x_attn = (attn[:, : context_qkv[0].shape[1]], attn[:, context_qkv[0].shape[1] :]) + + if not context_block.pre_only: + context = context_block.post_attention(context_attn, *context_intermediates) + else: + context = None + x = x_block.post_attention(x_attn, *x_intermediates) + return context, x + + +class JointBlock(nn.Module): + """just a small wrapper to serve as a fsdp unit""" + + def __init__(self, *args, **kwargs): + super().__init__() + pre_only = kwargs.pop("pre_only") + qk_norm = kwargs.pop("qk_norm", None) + self.context_block = DismantledBlock(*args, pre_only=pre_only, qk_norm=qk_norm, **kwargs) + self.x_block = DismantledBlock(*args, pre_only=False, qk_norm=qk_norm, **kwargs) + + def forward(self, *args, **kwargs): + return block_mixing(*args, context_block=self.context_block, x_block=self.x_block, **kwargs) + + +class FinalLayer(nn.Module): + """ + The final layer of DiT. + """ + + def __init__(self, hidden_size: int, patch_size: int, out_channels: int, total_out_channels: Optional[int] = None, dtype=None, device=None): + super().__init__() + self.norm_final = nn.LayerNorm(hidden_size, elementwise_affine=False, eps=1e-6, dtype=dtype, device=device) + self.linear = ( + nn.Linear(hidden_size, patch_size * patch_size * out_channels, bias=True, dtype=dtype, device=device) + if (total_out_channels is None) + else nn.Linear(hidden_size, total_out_channels, bias=True, dtype=dtype, device=device) + ) + self.adaLN_modulation = nn.Sequential(nn.SiLU(), nn.Linear(hidden_size, 2 * hidden_size, bias=True, dtype=dtype, device=device)) + + def forward(self, x: torch.Tensor, c: torch.Tensor) -> torch.Tensor: + shift, scale = self.adaLN_modulation(c).chunk(2, dim=1) + x = modulate(self.norm_final(x), shift, scale) + x = self.linear(x) + return x + + +class MMDiT(nn.Module): + """Diffusion model with a Transformer backbone.""" + + def __init__( + self, + input_size: int = 32, + patch_size: int = 2, + in_channels: int = 4, + depth: int = 28, + mlp_ratio: float = 4.0, + learn_sigma: bool = False, + adm_in_channels: Optional[int] = None, + context_embedder_config: Optional[Dict] = None, + register_length: int = 0, + attn_mode: str = "torch", + rmsnorm: bool = False, + scale_mod_only: bool = False, + swiglu: bool = False, + out_channels: Optional[int] = None, + pos_embed_scaling_factor: Optional[float] = None, + pos_embed_offset: Optional[float] = None, + pos_embed_max_size: Optional[int] = None, + num_patches = None, + qk_norm: Optional[str] = None, + qkv_bias: bool = True, + dtype = None, + device = None, + ): + super().__init__() + self.dtype = dtype + self.learn_sigma = learn_sigma + self.in_channels = in_channels + default_out_channels = in_channels * 2 if learn_sigma else in_channels + self.out_channels = out_channels if out_channels is not None else default_out_channels + self.patch_size = patch_size + self.pos_embed_scaling_factor = pos_embed_scaling_factor + self.pos_embed_offset = pos_embed_offset + self.pos_embed_max_size = pos_embed_max_size + + # apply magic --> this defines a head_size of 64 + hidden_size = 64 * depth + num_heads = depth + + self.num_heads = num_heads + + self.x_embedder = PatchEmbed(input_size, patch_size, in_channels, hidden_size, bias=True, strict_img_size=self.pos_embed_max_size is None, dtype=dtype, device=device) + self.t_embedder = TimestepEmbedder(hidden_size, dtype=dtype, device=device) + + if adm_in_channels is not None: + assert isinstance(adm_in_channels, int) + self.y_embedder = VectorEmbedder(adm_in_channels, hidden_size, dtype=dtype, device=device) + + self.context_embedder = nn.Identity() + if context_embedder_config is not None: + if context_embedder_config["target"] == "torch.nn.Linear": + self.context_embedder = nn.Linear(**context_embedder_config["params"], dtype=dtype, device=device) + + self.register_length = register_length + if self.register_length > 0: + self.register = nn.Parameter(torch.randn(1, register_length, hidden_size, dtype=dtype, device=device)) + + # num_patches = self.x_embedder.num_patches + # Will use fixed sin-cos embedding: + # just use a buffer already + if num_patches is not None: + self.register_buffer( + "pos_embed", + torch.zeros(1, num_patches, hidden_size, dtype=dtype, device=device), + ) + else: + self.pos_embed = None + + self.joint_blocks = nn.ModuleList( + [ + JointBlock(hidden_size, num_heads, mlp_ratio=mlp_ratio, qkv_bias=qkv_bias, attn_mode=attn_mode, pre_only=i == depth - 1, rmsnorm=rmsnorm, scale_mod_only=scale_mod_only, swiglu=swiglu, qk_norm=qk_norm, dtype=dtype, device=device) + for i in range(depth) + ] + ) + + self.final_layer = FinalLayer(hidden_size, patch_size, self.out_channels, dtype=dtype, device=device) + + def cropped_pos_embed(self, hw): + assert self.pos_embed_max_size is not None + p = self.x_embedder.patch_size[0] + h, w = hw + # patched size + h = h // p + w = w // p + assert h <= self.pos_embed_max_size, (h, self.pos_embed_max_size) + assert w <= self.pos_embed_max_size, (w, self.pos_embed_max_size) + top = (self.pos_embed_max_size - h) // 2 + left = (self.pos_embed_max_size - w) // 2 + spatial_pos_embed = rearrange( + self.pos_embed, + "1 (h w) c -> 1 h w c", + h=self.pos_embed_max_size, + w=self.pos_embed_max_size, + ) + spatial_pos_embed = spatial_pos_embed[:, top : top + h, left : left + w, :] + spatial_pos_embed = rearrange(spatial_pos_embed, "1 h w c -> 1 (h w) c") + return spatial_pos_embed + + def unpatchify(self, x, hw=None): + """ + x: (N, T, patch_size**2 * C) + imgs: (N, H, W, C) + """ + c = self.out_channels + p = self.x_embedder.patch_size[0] + if hw is None: + h = w = int(x.shape[1] ** 0.5) + else: + h, w = hw + h = h // p + w = w // p + assert h * w == x.shape[1] + + x = x.reshape(shape=(x.shape[0], h, w, p, p, c)) + x = torch.einsum("nhwpqc->nchpwq", x) + imgs = x.reshape(shape=(x.shape[0], c, h * p, w * p)) + return imgs + + def forward_core_with_concat(self, x: torch.Tensor, c_mod: torch.Tensor, context: Optional[torch.Tensor] = None) -> torch.Tensor: + if self.register_length > 0: + context = torch.cat((repeat(self.register, "1 ... -> b ...", b=x.shape[0]), context if context is not None else torch.Tensor([]).type_as(x)), 1) + + # context is B, L', D + # x is B, L, D + for block in self.joint_blocks: + context, x = block(context, x, c=c_mod) + + x = self.final_layer(x, c_mod) # (N, T, patch_size ** 2 * out_channels) + return x + + def forward(self, x: torch.Tensor, t: torch.Tensor, y: Optional[torch.Tensor] = None, context: Optional[torch.Tensor] = None) -> torch.Tensor: + """ + Forward pass of DiT. + x: (N, C, H, W) tensor of spatial inputs (images or latent representations of images) + t: (N,) tensor of diffusion timesteps + y: (N,) tensor of class labels + """ + hw = x.shape[-2:] + x = self.x_embedder(x) + self.cropped_pos_embed(hw) + c = self.t_embedder(t, dtype=x.dtype) # (N, D) + if y is not None: + y = self.y_embedder(y) # (N, D) + c = c + y # (N, D) + + context = self.context_embedder(context) + + x = self.forward_core_with_concat(x, c, context) + + x = self.unpatchify(x, hw=hw) # (N, out_channels, H, W) + return x diff --git a/modules/models/sd3/other_impls.py b/modules/models/sd3/other_impls.py new file mode 100644 index 000000000..78c1dc687 --- /dev/null +++ b/modules/models/sd3/other_impls.py @@ -0,0 +1,510 @@ +### This file contains impls for underlying related models (CLIP, T5, etc) + +import torch +import math +from torch import nn +from transformers import CLIPTokenizer, T5TokenizerFast + +from modules import sd_hijack + + +################################################################################################# +### Core/Utility +################################################################################################# + + +class AutocastLinear(nn.Linear): + """Same as usual linear layer, but casts its weights to whatever the parameter type is. + + This is different from torch.autocast in a way that float16 layer processing float32 input + will return float16 with autocast on, and float32 with this. T5 seems to be fucked + if you do it in full float16 (returning almost all zeros in the final output). + """ + + def forward(self, x): + return torch.nn.functional.linear(x, self.weight.to(x.dtype), self.bias.to(x.dtype) if self.bias is not None else None) + + +def attention(q, k, v, heads, mask=None): + """Convenience wrapper around a basic attention operation""" + b, _, dim_head = q.shape + dim_head //= heads + q, k, v = [t.view(b, -1, heads, dim_head).transpose(1, 2) for t in (q, k, v)] + out = torch.nn.functional.scaled_dot_product_attention(q, k, v, attn_mask=mask, dropout_p=0.0, is_causal=False) + return out.transpose(1, 2).reshape(b, -1, heads * dim_head) + + +class Mlp(nn.Module): + """ MLP as used in Vision Transformer, MLP-Mixer and related networks""" + def __init__(self, in_features, hidden_features=None, out_features=None, act_layer=nn.GELU, bias=True, dtype=None, device=None): + super().__init__() + out_features = out_features or in_features + hidden_features = hidden_features or in_features + + self.fc1 = nn.Linear(in_features, hidden_features, bias=bias, dtype=dtype, device=device) + self.act = act_layer + self.fc2 = nn.Linear(hidden_features, out_features, bias=bias, dtype=dtype, device=device) + + def forward(self, x): + x = self.fc1(x) + x = self.act(x) + x = self.fc2(x) + return x + + +################################################################################################# +### CLIP +################################################################################################# + + +class CLIPAttention(torch.nn.Module): + def __init__(self, embed_dim, heads, dtype, device): + super().__init__() + self.heads = heads + self.q_proj = nn.Linear(embed_dim, embed_dim, bias=True, dtype=dtype, device=device) + self.k_proj = nn.Linear(embed_dim, embed_dim, bias=True, dtype=dtype, device=device) + self.v_proj = nn.Linear(embed_dim, embed_dim, bias=True, dtype=dtype, device=device) + self.out_proj = nn.Linear(embed_dim, embed_dim, bias=True, dtype=dtype, device=device) + + def forward(self, x, mask=None): + q = self.q_proj(x) + k = self.k_proj(x) + v = self.v_proj(x) + out = attention(q, k, v, self.heads, mask) + return self.out_proj(out) + + +ACTIVATIONS = { + "quick_gelu": lambda a: a * torch.sigmoid(1.702 * a), + "gelu": torch.nn.functional.gelu, +} + +class CLIPLayer(torch.nn.Module): + def __init__(self, embed_dim, heads, intermediate_size, intermediate_activation, dtype, device): + super().__init__() + self.layer_norm1 = nn.LayerNorm(embed_dim, dtype=dtype, device=device) + self.self_attn = CLIPAttention(embed_dim, heads, dtype, device) + self.layer_norm2 = nn.LayerNorm(embed_dim, dtype=dtype, device=device) + #self.mlp = CLIPMLP(embed_dim, intermediate_size, intermediate_activation, dtype, device) + self.mlp = Mlp(embed_dim, intermediate_size, embed_dim, act_layer=ACTIVATIONS[intermediate_activation], dtype=dtype, device=device) + + def forward(self, x, mask=None): + x += self.self_attn(self.layer_norm1(x), mask) + x += self.mlp(self.layer_norm2(x)) + return x + + +class CLIPEncoder(torch.nn.Module): + def __init__(self, num_layers, embed_dim, heads, intermediate_size, intermediate_activation, dtype, device): + super().__init__() + self.layers = torch.nn.ModuleList([CLIPLayer(embed_dim, heads, intermediate_size, intermediate_activation, dtype, device) for i in range(num_layers)]) + + def forward(self, x, mask=None, intermediate_output=None): + if intermediate_output is not None: + if intermediate_output < 0: + intermediate_output = len(self.layers) + intermediate_output + intermediate = None + for i, layer in enumerate(self.layers): + x = layer(x, mask) + if i == intermediate_output: + intermediate = x.clone() + return x, intermediate + + +class CLIPEmbeddings(torch.nn.Module): + def __init__(self, embed_dim, vocab_size=49408, num_positions=77, dtype=None, device=None, textual_inversion_key="clip_l"): + super().__init__() + self.token_embedding = sd_hijack.TextualInversionEmbeddings(vocab_size, embed_dim, dtype=dtype, device=device, textual_inversion_key=textual_inversion_key) + self.position_embedding = torch.nn.Embedding(num_positions, embed_dim, dtype=dtype, device=device) + + def forward(self, input_tokens): + return self.token_embedding(input_tokens) + self.position_embedding.weight + + +class CLIPTextModel_(torch.nn.Module): + def __init__(self, config_dict, dtype, device): + num_layers = config_dict["num_hidden_layers"] + embed_dim = config_dict["hidden_size"] + heads = config_dict["num_attention_heads"] + intermediate_size = config_dict["intermediate_size"] + intermediate_activation = config_dict["hidden_act"] + super().__init__() + self.embeddings = CLIPEmbeddings(embed_dim, dtype=torch.float32, device=device, textual_inversion_key=config_dict.get('textual_inversion_key', 'clip_l')) + self.encoder = CLIPEncoder(num_layers, embed_dim, heads, intermediate_size, intermediate_activation, dtype, device) + self.final_layer_norm = nn.LayerNorm(embed_dim, dtype=dtype, device=device) + + def forward(self, input_tokens, intermediate_output=None, final_layer_norm_intermediate=True): + x = self.embeddings(input_tokens) + causal_mask = torch.empty(x.shape[1], x.shape[1], dtype=x.dtype, device=x.device).fill_(float("-inf")).triu_(1) + x, i = self.encoder(x, mask=causal_mask, intermediate_output=intermediate_output) + x = self.final_layer_norm(x) + if i is not None and final_layer_norm_intermediate: + i = self.final_layer_norm(i) + pooled_output = x[torch.arange(x.shape[0], device=x.device), input_tokens.to(dtype=torch.int, device=x.device).argmax(dim=-1),] + return x, i, pooled_output + + +class CLIPTextModel(torch.nn.Module): + def __init__(self, config_dict, dtype, device): + super().__init__() + self.num_layers = config_dict["num_hidden_layers"] + self.text_model = CLIPTextModel_(config_dict, dtype, device) + embed_dim = config_dict["hidden_size"] + self.text_projection = nn.Linear(embed_dim, embed_dim, bias=False, dtype=dtype, device=device) + self.text_projection.weight.copy_(torch.eye(embed_dim)) + self.dtype = dtype + + def get_input_embeddings(self): + return self.text_model.embeddings.token_embedding + + def set_input_embeddings(self, embeddings): + self.text_model.embeddings.token_embedding = embeddings + + def forward(self, *args, **kwargs): + x = self.text_model(*args, **kwargs) + out = self.text_projection(x[2]) + return (x[0], x[1], out, x[2]) + + +class SDTokenizer: + def __init__(self, max_length=77, pad_with_end=True, tokenizer=None, has_start_token=True, pad_to_max_length=True, min_length=None): + self.tokenizer = tokenizer + self.max_length = max_length + self.min_length = min_length + empty = self.tokenizer('')["input_ids"] + if has_start_token: + self.tokens_start = 1 + self.start_token = empty[0] + self.end_token = empty[1] + else: + self.tokens_start = 0 + self.start_token = None + self.end_token = empty[0] + self.pad_with_end = pad_with_end + self.pad_to_max_length = pad_to_max_length + vocab = self.tokenizer.get_vocab() + self.inv_vocab = {v: k for k, v in vocab.items()} + self.max_word_length = 8 + + + def tokenize_with_weights(self, text:str): + """Tokenize the text, with weight values - presume 1.0 for all and ignore other features here. The details aren't relevant for a reference impl, and weights themselves has weak effect on SD3.""" + if self.pad_with_end: + pad_token = self.end_token + else: + pad_token = 0 + batch = [] + if self.start_token is not None: + batch.append((self.start_token, 1.0)) + to_tokenize = text.replace("\n", " ").split(' ') + to_tokenize = [x for x in to_tokenize if x != ""] + for word in to_tokenize: + batch.extend([(t, 1) for t in self.tokenizer(word)["input_ids"][self.tokens_start:-1]]) + batch.append((self.end_token, 1.0)) + if self.pad_to_max_length: + batch.extend([(pad_token, 1.0)] * (self.max_length - len(batch))) + if self.min_length is not None and len(batch) < self.min_length: + batch.extend([(pad_token, 1.0)] * (self.min_length - len(batch))) + return [batch] + + +class SDXLClipGTokenizer(SDTokenizer): + def __init__(self, tokenizer): + super().__init__(pad_with_end=False, tokenizer=tokenizer) + + +class SD3Tokenizer: + def __init__(self): + clip_tokenizer = CLIPTokenizer.from_pretrained("openai/clip-vit-large-patch14") + self.clip_l = SDTokenizer(tokenizer=clip_tokenizer) + self.clip_g = SDXLClipGTokenizer(clip_tokenizer) + self.t5xxl = T5XXLTokenizer() + + def tokenize_with_weights(self, text:str): + out = {} + out["g"] = self.clip_g.tokenize_with_weights(text) + out["l"] = self.clip_l.tokenize_with_weights(text) + out["t5xxl"] = self.t5xxl.tokenize_with_weights(text) + return out + + +class ClipTokenWeightEncoder: + def encode_token_weights(self, token_weight_pairs): + tokens = [a[0] for a in token_weight_pairs[0]] + out, pooled = self([tokens]) + if pooled is not None: + first_pooled = pooled[0:1].cpu() + else: + first_pooled = pooled + output = [out[0:1]] + return torch.cat(output, dim=-2).cpu(), first_pooled + + +class SDClipModel(torch.nn.Module, ClipTokenWeightEncoder): + """Uses the CLIP transformer encoder for text (from huggingface)""" + LAYERS = ["last", "pooled", "hidden"] + def __init__(self, device="cpu", max_length=77, layer="last", layer_idx=None, textmodel_json_config=None, dtype=None, model_class=CLIPTextModel, + special_tokens=None, layer_norm_hidden_state=True, return_projected_pooled=True): + super().__init__() + assert layer in self.LAYERS + self.transformer = model_class(textmodel_json_config, dtype, device) + self.num_layers = self.transformer.num_layers + self.max_length = max_length + self.transformer = self.transformer.eval() + for param in self.parameters(): + param.requires_grad = False + self.layer = layer + self.layer_idx = None + self.special_tokens = special_tokens if special_tokens is not None else {"start": 49406, "end": 49407, "pad": 49407} + self.logit_scale = torch.nn.Parameter(torch.tensor(4.6055)) + self.layer_norm_hidden_state = layer_norm_hidden_state + self.return_projected_pooled = return_projected_pooled + if layer == "hidden": + assert layer_idx is not None + assert abs(layer_idx) < self.num_layers + self.set_clip_options({"layer": layer_idx}) + self.options_default = (self.layer, self.layer_idx, self.return_projected_pooled) + + def set_clip_options(self, options): + layer_idx = options.get("layer", self.layer_idx) + self.return_projected_pooled = options.get("projected_pooled", self.return_projected_pooled) + if layer_idx is None or abs(layer_idx) > self.num_layers: + self.layer = "last" + else: + self.layer = "hidden" + self.layer_idx = layer_idx + + def forward(self, tokens): + backup_embeds = self.transformer.get_input_embeddings() + tokens = torch.asarray(tokens, dtype=torch.int64, device=backup_embeds.weight.device) + outputs = self.transformer(tokens, intermediate_output=self.layer_idx, final_layer_norm_intermediate=self.layer_norm_hidden_state) + self.transformer.set_input_embeddings(backup_embeds) + if self.layer == "last": + z = outputs[0] + else: + z = outputs[1] + pooled_output = None + if len(outputs) >= 3: + if not self.return_projected_pooled and len(outputs) >= 4 and outputs[3] is not None: + pooled_output = outputs[3].float() + elif outputs[2] is not None: + pooled_output = outputs[2].float() + return z.float(), pooled_output + + +class SDXLClipG(SDClipModel): + """Wraps the CLIP-G model into the SD-CLIP-Model interface""" + def __init__(self, config, device="cpu", layer="penultimate", layer_idx=None, dtype=None): + if layer == "penultimate": + layer="hidden" + layer_idx=-2 + super().__init__(device=device, layer=layer, layer_idx=layer_idx, textmodel_json_config=config, dtype=dtype, special_tokens={"start": 49406, "end": 49407, "pad": 0}, layer_norm_hidden_state=False) + + +class T5XXLModel(SDClipModel): + """Wraps the T5-XXL model into the SD-CLIP-Model interface for convenience""" + def __init__(self, config, device="cpu", layer="last", layer_idx=None, dtype=None): + super().__init__(device=device, layer=layer, layer_idx=layer_idx, textmodel_json_config=config, dtype=dtype, special_tokens={"end": 1, "pad": 0}, model_class=T5) + + +################################################################################################# +### T5 implementation, for the T5-XXL text encoder portion, largely pulled from upstream impl +################################################################################################# + +class T5XXLTokenizer(SDTokenizer): + """Wraps the T5 Tokenizer from HF into the SDTokenizer interface""" + def __init__(self): + super().__init__(pad_with_end=False, tokenizer=T5TokenizerFast.from_pretrained("google/t5-v1_1-xxl"), has_start_token=False, pad_to_max_length=False, max_length=99999999, min_length=77) + + +class T5LayerNorm(torch.nn.Module): + def __init__(self, hidden_size, eps=1e-6, dtype=None, device=None): + super().__init__() + self.weight = torch.nn.Parameter(torch.ones(hidden_size, dtype=dtype, device=device)) + self.variance_epsilon = eps + + def forward(self, x): + variance = x.pow(2).mean(-1, keepdim=True) + x = x * torch.rsqrt(variance + self.variance_epsilon) + return self.weight.to(device=x.device, dtype=x.dtype) * x + + +class T5DenseGatedActDense(torch.nn.Module): + def __init__(self, model_dim, ff_dim, dtype, device): + super().__init__() + self.wi_0 = AutocastLinear(model_dim, ff_dim, bias=False, dtype=dtype, device=device) + self.wi_1 = AutocastLinear(model_dim, ff_dim, bias=False, dtype=dtype, device=device) + self.wo = AutocastLinear(ff_dim, model_dim, bias=False, dtype=dtype, device=device) + + def forward(self, x): + hidden_gelu = torch.nn.functional.gelu(self.wi_0(x), approximate="tanh") + hidden_linear = self.wi_1(x) + x = hidden_gelu * hidden_linear + x = self.wo(x) + return x + + +class T5LayerFF(torch.nn.Module): + def __init__(self, model_dim, ff_dim, dtype, device): + super().__init__() + self.DenseReluDense = T5DenseGatedActDense(model_dim, ff_dim, dtype, device) + self.layer_norm = T5LayerNorm(model_dim, dtype=dtype, device=device) + + def forward(self, x): + forwarded_states = self.layer_norm(x) + forwarded_states = self.DenseReluDense(forwarded_states) + x += forwarded_states + return x + + +class T5Attention(torch.nn.Module): + def __init__(self, model_dim, inner_dim, num_heads, relative_attention_bias, dtype, device): + super().__init__() + # Mesh TensorFlow initialization to avoid scaling before softmax + self.q = AutocastLinear(model_dim, inner_dim, bias=False, dtype=dtype, device=device) + self.k = AutocastLinear(model_dim, inner_dim, bias=False, dtype=dtype, device=device) + self.v = AutocastLinear(model_dim, inner_dim, bias=False, dtype=dtype, device=device) + self.o = AutocastLinear(inner_dim, model_dim, bias=False, dtype=dtype, device=device) + self.num_heads = num_heads + self.relative_attention_bias = None + if relative_attention_bias: + self.relative_attention_num_buckets = 32 + self.relative_attention_max_distance = 128 + self.relative_attention_bias = torch.nn.Embedding(self.relative_attention_num_buckets, self.num_heads, device=device) + + @staticmethod + def _relative_position_bucket(relative_position, bidirectional=True, num_buckets=32, max_distance=128): + """ + Adapted from Mesh Tensorflow: + https://github.com/tensorflow/mesh/blob/0cb87fe07da627bf0b7e60475d59f95ed6b5be3d/mesh_tensorflow/transformer/transformer_layers.py#L593 + + Translate relative position to a bucket number for relative attention. The relative position is defined as + memory_position - query_position, i.e. the distance in tokens from the attending position to the attended-to + position. If bidirectional=False, then positive relative positions are invalid. We use smaller buckets for + small absolute relative_position and larger buckets for larger absolute relative_positions. All relative + positions >=max_distance map to the same bucket. All relative positions <=-max_distance map to the same bucket. + This should allow for more graceful generalization to longer sequences than the model has been trained on + + Args: + relative_position: an int32 Tensor + bidirectional: a boolean - whether the attention is bidirectional + num_buckets: an integer + max_distance: an integer + + Returns: + a Tensor with the same shape as relative_position, containing int32 values in the range [0, num_buckets) + """ + relative_buckets = 0 + if bidirectional: + num_buckets //= 2 + relative_buckets += (relative_position > 0).to(torch.long) * num_buckets + relative_position = torch.abs(relative_position) + else: + relative_position = -torch.min(relative_position, torch.zeros_like(relative_position)) + # now relative_position is in the range [0, inf) + # half of the buckets are for exact increments in positions + max_exact = num_buckets // 2 + is_small = relative_position < max_exact + # The other half of the buckets are for logarithmically bigger bins in positions up to max_distance + relative_position_if_large = max_exact + ( + torch.log(relative_position.float() / max_exact) + / math.log(max_distance / max_exact) + * (num_buckets - max_exact) + ).to(torch.long) + relative_position_if_large = torch.min(relative_position_if_large, torch.full_like(relative_position_if_large, num_buckets - 1)) + relative_buckets += torch.where(is_small, relative_position, relative_position_if_large) + return relative_buckets + + def compute_bias(self, query_length, key_length, device): + """Compute binned relative position bias""" + context_position = torch.arange(query_length, dtype=torch.long, device=device)[:, None] + memory_position = torch.arange(key_length, dtype=torch.long, device=device)[None, :] + relative_position = memory_position - context_position # shape (query_length, key_length) + relative_position_bucket = self._relative_position_bucket( + relative_position, # shape (query_length, key_length) + bidirectional=True, + num_buckets=self.relative_attention_num_buckets, + max_distance=self.relative_attention_max_distance, + ) + values = self.relative_attention_bias(relative_position_bucket) # shape (query_length, key_length, num_heads) + values = values.permute([2, 0, 1]).unsqueeze(0) # shape (1, num_heads, query_length, key_length) + return values + + def forward(self, x, past_bias=None): + q = self.q(x) + k = self.k(x) + v = self.v(x) + + if self.relative_attention_bias is not None: + past_bias = self.compute_bias(x.shape[1], x.shape[1], x.device) + if past_bias is not None: + mask = past_bias + else: + mask = None + + out = attention(q, k * ((k.shape[-1] / self.num_heads) ** 0.5), v, self.num_heads, mask.to(x.dtype) if mask is not None else None) + + return self.o(out), past_bias + + +class T5LayerSelfAttention(torch.nn.Module): + def __init__(self, model_dim, inner_dim, ff_dim, num_heads, relative_attention_bias, dtype, device): + super().__init__() + self.SelfAttention = T5Attention(model_dim, inner_dim, num_heads, relative_attention_bias, dtype, device) + self.layer_norm = T5LayerNorm(model_dim, dtype=dtype, device=device) + + def forward(self, x, past_bias=None): + output, past_bias = self.SelfAttention(self.layer_norm(x), past_bias=past_bias) + x += output + return x, past_bias + + +class T5Block(torch.nn.Module): + def __init__(self, model_dim, inner_dim, ff_dim, num_heads, relative_attention_bias, dtype, device): + super().__init__() + self.layer = torch.nn.ModuleList() + self.layer.append(T5LayerSelfAttention(model_dim, inner_dim, ff_dim, num_heads, relative_attention_bias, dtype, device)) + self.layer.append(T5LayerFF(model_dim, ff_dim, dtype, device)) + + def forward(self, x, past_bias=None): + x, past_bias = self.layer[0](x, past_bias) + x = self.layer[-1](x) + return x, past_bias + + +class T5Stack(torch.nn.Module): + def __init__(self, num_layers, model_dim, inner_dim, ff_dim, num_heads, vocab_size, dtype, device): + super().__init__() + self.embed_tokens = torch.nn.Embedding(vocab_size, model_dim, device=device) + self.block = torch.nn.ModuleList([T5Block(model_dim, inner_dim, ff_dim, num_heads, relative_attention_bias=(i == 0), dtype=dtype, device=device) for i in range(num_layers)]) + self.final_layer_norm = T5LayerNorm(model_dim, dtype=dtype, device=device) + + def forward(self, input_ids, intermediate_output=None, final_layer_norm_intermediate=True): + intermediate = None + x = self.embed_tokens(input_ids).to(torch.float32) # needs float32 or else T5 returns all zeroes + past_bias = None + for i, layer in enumerate(self.block): + x, past_bias = layer(x, past_bias) + if i == intermediate_output: + intermediate = x.clone() + x = self.final_layer_norm(x) + if intermediate is not None and final_layer_norm_intermediate: + intermediate = self.final_layer_norm(intermediate) + return x, intermediate + + +class T5(torch.nn.Module): + def __init__(self, config_dict, dtype, device): + super().__init__() + self.num_layers = config_dict["num_layers"] + self.encoder = T5Stack(self.num_layers, config_dict["d_model"], config_dict["d_model"], config_dict["d_ff"], config_dict["num_heads"], config_dict["vocab_size"], dtype, device) + self.dtype = dtype + + def get_input_embeddings(self): + return self.encoder.embed_tokens + + def set_input_embeddings(self, embeddings): + self.encoder.embed_tokens = embeddings + + def forward(self, *args, **kwargs): + return self.encoder(*args, **kwargs) diff --git a/modules/models/sd3/sd3_cond.py b/modules/models/sd3/sd3_cond.py new file mode 100644 index 000000000..66f59e298 --- /dev/null +++ b/modules/models/sd3/sd3_cond.py @@ -0,0 +1,222 @@ +import os +import safetensors +import torch +import typing + +from transformers import CLIPTokenizer, T5TokenizerFast + +from modules import shared, devices, modelloader, sd_hijack_clip, prompt_parser +from modules.models.sd3.other_impls import SDClipModel, SDXLClipG, T5XXLModel, SD3Tokenizer + + +class SafetensorsMapping(typing.Mapping): + def __init__(self, file): + self.file = file + + def __len__(self): + return len(self.file.keys()) + + def __iter__(self): + for key in self.file.keys(): + yield key + + def __getitem__(self, key): + return self.file.get_tensor(key) + + +CLIPL_URL = "https://huggingface.co/AUTOMATIC/stable-diffusion-3-medium-text-encoders/resolve/main/clip_l.safetensors" +CLIPL_CONFIG = { + "hidden_act": "quick_gelu", + "hidden_size": 768, + "intermediate_size": 3072, + "num_attention_heads": 12, + "num_hidden_layers": 12, +} + +CLIPG_URL = "https://huggingface.co/AUTOMATIC/stable-diffusion-3-medium-text-encoders/resolve/main/clip_g.safetensors" +CLIPG_CONFIG = { + "hidden_act": "gelu", + "hidden_size": 1280, + "intermediate_size": 5120, + "num_attention_heads": 20, + "num_hidden_layers": 32, + "textual_inversion_key": "clip_g", +} + +T5_URL = "https://huggingface.co/AUTOMATIC/stable-diffusion-3-medium-text-encoders/resolve/main/t5xxl_fp16.safetensors" +T5_CONFIG = { + "d_ff": 10240, + "d_model": 4096, + "num_heads": 64, + "num_layers": 24, + "vocab_size": 32128, +} + + +class Sd3ClipLG(sd_hijack_clip.TextConditionalModel): + def __init__(self, clip_l, clip_g): + super().__init__() + + self.clip_l = clip_l + self.clip_g = clip_g + + self.tokenizer = CLIPTokenizer.from_pretrained("openai/clip-vit-large-patch14") + + empty = self.tokenizer('')["input_ids"] + self.id_start = empty[0] + self.id_end = empty[1] + self.id_pad = empty[1] + + self.return_pooled = True + + def tokenize(self, texts): + return self.tokenizer(texts, truncation=False, add_special_tokens=False)["input_ids"] + + def encode_with_transformers(self, tokens): + tokens_g = tokens.clone() + + for batch_pos in range(tokens_g.shape[0]): + index = tokens_g[batch_pos].cpu().tolist().index(self.id_end) + tokens_g[batch_pos, index+1:tokens_g.shape[1]] = 0 + + l_out, l_pooled = self.clip_l(tokens) + g_out, g_pooled = self.clip_g(tokens_g) + + lg_out = torch.cat([l_out, g_out], dim=-1) + lg_out = torch.nn.functional.pad(lg_out, (0, 4096 - lg_out.shape[-1])) + + vector_out = torch.cat((l_pooled, g_pooled), dim=-1) + + lg_out.pooled = vector_out + return lg_out + + def encode_embedding_init_text(self, init_text, nvpt): + return torch.zeros((nvpt, 768+1280), device=devices.device) # XXX + + +class Sd3T5(torch.nn.Module): + def __init__(self, t5xxl): + super().__init__() + + self.t5xxl = t5xxl + self.tokenizer = T5TokenizerFast.from_pretrained("google/t5-v1_1-xxl") + + empty = self.tokenizer('', padding='max_length', max_length=2)["input_ids"] + self.id_end = empty[0] + self.id_pad = empty[1] + + def tokenize(self, texts): + return self.tokenizer(texts, truncation=False, add_special_tokens=False)["input_ids"] + + def tokenize_line(self, line, *, target_token_count=None): + if shared.opts.emphasis != "None": + parsed = prompt_parser.parse_prompt_attention(line) + else: + parsed = [[line, 1.0]] + + tokenized = self.tokenize([text for text, _ in parsed]) + + tokens = [] + multipliers = [] + + for text_tokens, (text, weight) in zip(tokenized, parsed): + if text == 'BREAK' and weight == -1: + continue + + tokens += text_tokens + multipliers += [weight] * len(text_tokens) + + tokens += [self.id_end] + multipliers += [1.0] + + if target_token_count is not None: + if len(tokens) < target_token_count: + tokens += [self.id_pad] * (target_token_count - len(tokens)) + multipliers += [1.0] * (target_token_count - len(tokens)) + else: + tokens = tokens[0:target_token_count] + multipliers = multipliers[0:target_token_count] + + return tokens, multipliers + + def forward(self, texts, *, token_count): + if not self.t5xxl or not shared.opts.sd3_enable_t5: + return torch.zeros((len(texts), token_count, 4096), device=devices.device, dtype=devices.dtype) + + tokens_batch = [] + + for text in texts: + tokens, multipliers = self.tokenize_line(text, target_token_count=token_count) + tokens_batch.append(tokens) + + t5_out, t5_pooled = self.t5xxl(tokens_batch) + + return t5_out + + def encode_embedding_init_text(self, init_text, nvpt): + return torch.zeros((nvpt, 4096), device=devices.device) # XXX + + +class SD3Cond(torch.nn.Module): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + self.tokenizer = SD3Tokenizer() + + with torch.no_grad(): + self.clip_g = SDXLClipG(CLIPG_CONFIG, device="cpu", dtype=devices.dtype) + self.clip_l = SDClipModel(layer="hidden", layer_idx=-2, device="cpu", dtype=devices.dtype, layer_norm_hidden_state=False, return_projected_pooled=False, textmodel_json_config=CLIPL_CONFIG) + + if shared.opts.sd3_enable_t5: + self.t5xxl = T5XXLModel(T5_CONFIG, device="cpu", dtype=devices.dtype) + else: + self.t5xxl = None + + self.model_lg = Sd3ClipLG(self.clip_l, self.clip_g) + self.model_t5 = Sd3T5(self.t5xxl) + + def forward(self, prompts: list[str]): + with devices.without_autocast(): + lg_out, vector_out = self.model_lg(prompts) + t5_out = self.model_t5(prompts, token_count=lg_out.shape[1]) + lgt_out = torch.cat([lg_out, t5_out], dim=-2) + + return { + 'crossattn': lgt_out, + 'vector': vector_out, + } + + def before_load_weights(self, state_dict): + clip_path = os.path.join(shared.models_path, "CLIP") + + if 'text_encoders.clip_g.transformer.text_model.embeddings.position_embedding.weight' not in state_dict: + clip_g_file = modelloader.load_file_from_url(CLIPG_URL, model_dir=clip_path, file_name="clip_g.safetensors") + with safetensors.safe_open(clip_g_file, framework="pt") as file: + self.clip_g.transformer.load_state_dict(SafetensorsMapping(file)) + + if 'text_encoders.clip_l.transformer.text_model.embeddings.position_embedding.weight' not in state_dict: + clip_l_file = modelloader.load_file_from_url(CLIPL_URL, model_dir=clip_path, file_name="clip_l.safetensors") + with safetensors.safe_open(clip_l_file, framework="pt") as file: + self.clip_l.transformer.load_state_dict(SafetensorsMapping(file), strict=False) + + if self.t5xxl and 'text_encoders.t5xxl.transformer.encoder.embed_tokens.weight' not in state_dict: + t5_file = modelloader.load_file_from_url(T5_URL, model_dir=clip_path, file_name="t5xxl_fp16.safetensors") + with safetensors.safe_open(t5_file, framework="pt") as file: + self.t5xxl.transformer.load_state_dict(SafetensorsMapping(file), strict=False) + + def encode_embedding_init_text(self, init_text, nvpt): + return self.model_lg.encode_embedding_init_text(init_text, nvpt) + + def tokenize(self, texts): + return self.model_lg.tokenize(texts) + + def medvram_modules(self): + return [self.clip_g, self.clip_l, self.t5xxl] + + def get_token_count(self, text): + _, token_count = self.model_lg.process_texts([text]) + + return token_count + + def get_target_prompt_token_count(self, token_count): + return self.model_lg.get_target_prompt_token_count(token_count) diff --git a/modules/models/sd3/sd3_impls.py b/modules/models/sd3/sd3_impls.py new file mode 100644 index 000000000..59f11b2cb --- /dev/null +++ b/modules/models/sd3/sd3_impls.py @@ -0,0 +1,374 @@ +### Impls of the SD3 core diffusion model and VAE + +import torch +import math +import einops +from modules.models.sd3.mmdit import MMDiT +from PIL import Image + + +################################################################################################# +### MMDiT Model Wrapping +################################################################################################# + + +class ModelSamplingDiscreteFlow(torch.nn.Module): + """Helper for sampler scheduling (ie timestep/sigma calculations) for Discrete Flow models""" + def __init__(self, shift=1.0): + super().__init__() + self.shift = shift + timesteps = 1000 + ts = self.sigma(torch.arange(1, timesteps + 1, 1)) + self.register_buffer('sigmas', ts) + + @property + def sigma_min(self): + return self.sigmas[0] + + @property + def sigma_max(self): + return self.sigmas[-1] + + def timestep(self, sigma): + return sigma * 1000 + + def sigma(self, timestep: torch.Tensor): + timestep = timestep / 1000.0 + if self.shift == 1.0: + return timestep + return self.shift * timestep / (1 + (self.shift - 1) * timestep) + + def calculate_denoised(self, sigma, model_output, model_input): + sigma = sigma.view(sigma.shape[:1] + (1,) * (model_output.ndim - 1)) + return model_input - model_output * sigma + + def noise_scaling(self, sigma, noise, latent_image, max_denoise=False): + return sigma * noise + (1.0 - sigma) * latent_image + + +class BaseModel(torch.nn.Module): + """Wrapper around the core MM-DiT model""" + def __init__(self, shift=1.0, device=None, dtype=torch.float32, state_dict=None, prefix=""): + super().__init__() + # Important configuration values can be quickly determined by checking shapes in the source file + # Some of these will vary between models (eg 2B vs 8B primarily differ in their depth, but also other details change) + patch_size = state_dict[f"{prefix}x_embedder.proj.weight"].shape[2] + depth = state_dict[f"{prefix}x_embedder.proj.weight"].shape[0] // 64 + num_patches = state_dict[f"{prefix}pos_embed"].shape[1] + pos_embed_max_size = round(math.sqrt(num_patches)) + adm_in_channels = state_dict[f"{prefix}y_embedder.mlp.0.weight"].shape[1] + context_shape = state_dict[f"{prefix}context_embedder.weight"].shape + context_embedder_config = { + "target": "torch.nn.Linear", + "params": { + "in_features": context_shape[1], + "out_features": context_shape[0] + } + } + self.diffusion_model = MMDiT(input_size=None, pos_embed_scaling_factor=None, pos_embed_offset=None, pos_embed_max_size=pos_embed_max_size, patch_size=patch_size, in_channels=16, depth=depth, num_patches=num_patches, adm_in_channels=adm_in_channels, context_embedder_config=context_embedder_config, device=device, dtype=dtype) + self.model_sampling = ModelSamplingDiscreteFlow(shift=shift) + self.depth = depth + + def apply_model(self, x, sigma, c_crossattn=None, y=None): + dtype = self.get_dtype() + timestep = self.model_sampling.timestep(sigma).float() + model_output = self.diffusion_model(x.to(dtype), timestep, context=c_crossattn.to(dtype), y=y.to(dtype)).float() + return self.model_sampling.calculate_denoised(sigma, model_output, x) + + def forward(self, *args, **kwargs): + return self.apply_model(*args, **kwargs) + + def get_dtype(self): + return self.diffusion_model.dtype + + +class CFGDenoiser(torch.nn.Module): + """Helper for applying CFG Scaling to diffusion outputs""" + def __init__(self, model): + super().__init__() + self.model = model + + def forward(self, x, timestep, cond, uncond, cond_scale): + # Run cond and uncond in a batch together + batched = self.model.apply_model(torch.cat([x, x]), torch.cat([timestep, timestep]), c_crossattn=torch.cat([cond["c_crossattn"], uncond["c_crossattn"]]), y=torch.cat([cond["y"], uncond["y"]])) + # Then split and apply CFG Scaling + pos_out, neg_out = batched.chunk(2) + scaled = neg_out + (pos_out - neg_out) * cond_scale + return scaled + + +class SD3LatentFormat: + """Latents are slightly shifted from center - this class must be called after VAE Decode to correct for the shift""" + def __init__(self): + self.scale_factor = 1.5305 + self.shift_factor = 0.0609 + + def process_in(self, latent): + return (latent - self.shift_factor) * self.scale_factor + + def process_out(self, latent): + return (latent / self.scale_factor) + self.shift_factor + + def decode_latent_to_preview(self, x0): + """Quick RGB approximate preview of sd3 latents""" + factors = torch.tensor([ + [-0.0645, 0.0177, 0.1052], [ 0.0028, 0.0312, 0.0650], + [ 0.1848, 0.0762, 0.0360], [ 0.0944, 0.0360, 0.0889], + [ 0.0897, 0.0506, -0.0364], [-0.0020, 0.1203, 0.0284], + [ 0.0855, 0.0118, 0.0283], [-0.0539, 0.0658, 0.1047], + [-0.0057, 0.0116, 0.0700], [-0.0412, 0.0281, -0.0039], + [ 0.1106, 0.1171, 0.1220], [-0.0248, 0.0682, -0.0481], + [ 0.0815, 0.0846, 0.1207], [-0.0120, -0.0055, -0.0867], + [-0.0749, -0.0634, -0.0456], [-0.1418, -0.1457, -0.1259] + ], device="cpu") + latent_image = x0[0].permute(1, 2, 0).cpu() @ factors + + latents_ubyte = (((latent_image + 1) / 2) + .clamp(0, 1) # change scale from -1..1 to 0..1 + .mul(0xFF) # to 0..255 + .byte()).cpu() + + return Image.fromarray(latents_ubyte.numpy()) + + +################################################################################################# +### K-Diffusion Sampling +################################################################################################# + + +def append_dims(x, target_dims): + """Appends dimensions to the end of a tensor until it has target_dims dimensions.""" + dims_to_append = target_dims - x.ndim + return x[(...,) + (None,) * dims_to_append] + + +def to_d(x, sigma, denoised): + """Converts a denoiser output to a Karras ODE derivative.""" + return (x - denoised) / append_dims(sigma, x.ndim) + + +@torch.no_grad() +@torch.autocast("cuda", dtype=torch.float16) +def sample_euler(model, x, sigmas, extra_args=None): + """Implements Algorithm 2 (Euler steps) from Karras et al. (2022).""" + extra_args = {} if extra_args is None else extra_args + s_in = x.new_ones([x.shape[0]]) + for i in range(len(sigmas) - 1): + sigma_hat = sigmas[i] + denoised = model(x, sigma_hat * s_in, **extra_args) + d = to_d(x, sigma_hat, denoised) + dt = sigmas[i + 1] - sigma_hat + # Euler method + x = x + d * dt + return x + + +################################################################################################# +### VAE +################################################################################################# + + +def Normalize(in_channels, num_groups=32, dtype=torch.float32, device=None): + return torch.nn.GroupNorm(num_groups=num_groups, num_channels=in_channels, eps=1e-6, affine=True, dtype=dtype, device=device) + + +class ResnetBlock(torch.nn.Module): + def __init__(self, *, in_channels, out_channels=None, dtype=torch.float32, device=None): + super().__init__() + self.in_channels = in_channels + out_channels = in_channels if out_channels is None else out_channels + self.out_channels = out_channels + + self.norm1 = Normalize(in_channels, dtype=dtype, device=device) + self.conv1 = torch.nn.Conv2d(in_channels, out_channels, kernel_size=3, stride=1, padding=1, dtype=dtype, device=device) + self.norm2 = Normalize(out_channels, dtype=dtype, device=device) + self.conv2 = torch.nn.Conv2d(out_channels, out_channels, kernel_size=3, stride=1, padding=1, dtype=dtype, device=device) + if self.in_channels != self.out_channels: + self.nin_shortcut = torch.nn.Conv2d(in_channels, out_channels, kernel_size=1, stride=1, padding=0, dtype=dtype, device=device) + else: + self.nin_shortcut = None + self.swish = torch.nn.SiLU(inplace=True) + + def forward(self, x): + hidden = x + hidden = self.norm1(hidden) + hidden = self.swish(hidden) + hidden = self.conv1(hidden) + hidden = self.norm2(hidden) + hidden = self.swish(hidden) + hidden = self.conv2(hidden) + if self.in_channels != self.out_channels: + x = self.nin_shortcut(x) + return x + hidden + + +class AttnBlock(torch.nn.Module): + def __init__(self, in_channels, dtype=torch.float32, device=None): + super().__init__() + self.norm = Normalize(in_channels, dtype=dtype, device=device) + self.q = torch.nn.Conv2d(in_channels, in_channels, kernel_size=1, stride=1, padding=0, dtype=dtype, device=device) + self.k = torch.nn.Conv2d(in_channels, in_channels, kernel_size=1, stride=1, padding=0, dtype=dtype, device=device) + self.v = torch.nn.Conv2d(in_channels, in_channels, kernel_size=1, stride=1, padding=0, dtype=dtype, device=device) + self.proj_out = torch.nn.Conv2d(in_channels, in_channels, kernel_size=1, stride=1, padding=0, dtype=dtype, device=device) + + def forward(self, x): + hidden = self.norm(x) + q = self.q(hidden) + k = self.k(hidden) + v = self.v(hidden) + b, c, h, w = q.shape + q, k, v = [einops.rearrange(x, "b c h w -> b 1 (h w) c").contiguous() for x in (q, k, v)] + hidden = torch.nn.functional.scaled_dot_product_attention(q, k, v) # scale is dim ** -0.5 per default + hidden = einops.rearrange(hidden, "b 1 (h w) c -> b c h w", h=h, w=w, c=c, b=b) + hidden = self.proj_out(hidden) + return x + hidden + + +class Downsample(torch.nn.Module): + def __init__(self, in_channels, dtype=torch.float32, device=None): + super().__init__() + self.conv = torch.nn.Conv2d(in_channels, in_channels, kernel_size=3, stride=2, padding=0, dtype=dtype, device=device) + + def forward(self, x): + pad = (0,1,0,1) + x = torch.nn.functional.pad(x, pad, mode="constant", value=0) + x = self.conv(x) + return x + + +class Upsample(torch.nn.Module): + def __init__(self, in_channels, dtype=torch.float32, device=None): + super().__init__() + self.conv = torch.nn.Conv2d(in_channels, in_channels, kernel_size=3, stride=1, padding=1, dtype=dtype, device=device) + + def forward(self, x): + x = torch.nn.functional.interpolate(x, scale_factor=2.0, mode="nearest") + x = self.conv(x) + return x + + +class VAEEncoder(torch.nn.Module): + def __init__(self, ch=128, ch_mult=(1,2,4,4), num_res_blocks=2, in_channels=3, z_channels=16, dtype=torch.float32, device=None): + super().__init__() + self.num_resolutions = len(ch_mult) + self.num_res_blocks = num_res_blocks + # downsampling + self.conv_in = torch.nn.Conv2d(in_channels, ch, kernel_size=3, stride=1, padding=1, dtype=dtype, device=device) + in_ch_mult = (1,) + tuple(ch_mult) + self.in_ch_mult = in_ch_mult + self.down = torch.nn.ModuleList() + for i_level in range(self.num_resolutions): + block = torch.nn.ModuleList() + attn = torch.nn.ModuleList() + block_in = ch*in_ch_mult[i_level] + block_out = ch*ch_mult[i_level] + for _ in range(num_res_blocks): + block.append(ResnetBlock(in_channels=block_in, out_channels=block_out, dtype=dtype, device=device)) + block_in = block_out + down = torch.nn.Module() + down.block = block + down.attn = attn + if i_level != self.num_resolutions - 1: + down.downsample = Downsample(block_in, dtype=dtype, device=device) + self.down.append(down) + # middle + self.mid = torch.nn.Module() + self.mid.block_1 = ResnetBlock(in_channels=block_in, out_channels=block_in, dtype=dtype, device=device) + self.mid.attn_1 = AttnBlock(block_in, dtype=dtype, device=device) + self.mid.block_2 = ResnetBlock(in_channels=block_in, out_channels=block_in, dtype=dtype, device=device) + # end + self.norm_out = Normalize(block_in, dtype=dtype, device=device) + self.conv_out = torch.nn.Conv2d(block_in, 2 * z_channels, kernel_size=3, stride=1, padding=1, dtype=dtype, device=device) + self.swish = torch.nn.SiLU(inplace=True) + + def forward(self, x): + # downsampling + hs = [self.conv_in(x)] + for i_level in range(self.num_resolutions): + for i_block in range(self.num_res_blocks): + h = self.down[i_level].block[i_block](hs[-1]) + hs.append(h) + if i_level != self.num_resolutions-1: + hs.append(self.down[i_level].downsample(hs[-1])) + # middle + h = hs[-1] + h = self.mid.block_1(h) + h = self.mid.attn_1(h) + h = self.mid.block_2(h) + # end + h = self.norm_out(h) + h = self.swish(h) + h = self.conv_out(h) + return h + + +class VAEDecoder(torch.nn.Module): + def __init__(self, ch=128, out_ch=3, ch_mult=(1, 2, 4, 4), num_res_blocks=2, resolution=256, z_channels=16, dtype=torch.float32, device=None): + super().__init__() + self.num_resolutions = len(ch_mult) + self.num_res_blocks = num_res_blocks + block_in = ch * ch_mult[self.num_resolutions - 1] + curr_res = resolution // 2 ** (self.num_resolutions - 1) + # z to block_in + self.conv_in = torch.nn.Conv2d(z_channels, block_in, kernel_size=3, stride=1, padding=1, dtype=dtype, device=device) + # middle + self.mid = torch.nn.Module() + self.mid.block_1 = ResnetBlock(in_channels=block_in, out_channels=block_in, dtype=dtype, device=device) + self.mid.attn_1 = AttnBlock(block_in, dtype=dtype, device=device) + self.mid.block_2 = ResnetBlock(in_channels=block_in, out_channels=block_in, dtype=dtype, device=device) + # upsampling + self.up = torch.nn.ModuleList() + for i_level in reversed(range(self.num_resolutions)): + block = torch.nn.ModuleList() + block_out = ch * ch_mult[i_level] + for _ in range(self.num_res_blocks + 1): + block.append(ResnetBlock(in_channels=block_in, out_channels=block_out, dtype=dtype, device=device)) + block_in = block_out + up = torch.nn.Module() + up.block = block + if i_level != 0: + up.upsample = Upsample(block_in, dtype=dtype, device=device) + curr_res = curr_res * 2 + self.up.insert(0, up) # prepend to get consistent order + # end + self.norm_out = Normalize(block_in, dtype=dtype, device=device) + self.conv_out = torch.nn.Conv2d(block_in, out_ch, kernel_size=3, stride=1, padding=1, dtype=dtype, device=device) + self.swish = torch.nn.SiLU(inplace=True) + + def forward(self, z): + # z to block_in + hidden = self.conv_in(z) + # middle + hidden = self.mid.block_1(hidden) + hidden = self.mid.attn_1(hidden) + hidden = self.mid.block_2(hidden) + # upsampling + for i_level in reversed(range(self.num_resolutions)): + for i_block in range(self.num_res_blocks + 1): + hidden = self.up[i_level].block[i_block](hidden) + if i_level != 0: + hidden = self.up[i_level].upsample(hidden) + # end + hidden = self.norm_out(hidden) + hidden = self.swish(hidden) + hidden = self.conv_out(hidden) + return hidden + + +class SDVAE(torch.nn.Module): + def __init__(self, dtype=torch.float32, device=None): + super().__init__() + self.encoder = VAEEncoder(dtype=dtype, device=device) + self.decoder = VAEDecoder(dtype=dtype, device=device) + + @torch.autocast("cuda", dtype=torch.float16) + def decode(self, latent): + return self.decoder(latent) + + @torch.autocast("cuda", dtype=torch.float16) + def encode(self, image): + hidden = self.encoder(image) + mean, logvar = torch.chunk(hidden, 2, dim=1) + logvar = torch.clamp(logvar, -30.0, 20.0) + std = torch.exp(0.5 * logvar) + return mean + std * torch.randn_like(mean) diff --git a/modules/models/sd3/sd3_model.py b/modules/models/sd3/sd3_model.py new file mode 100644 index 000000000..a8a30e7f6 --- /dev/null +++ b/modules/models/sd3/sd3_model.py @@ -0,0 +1,96 @@ +import contextlib + +import torch + +import k_diffusion +from modules.models.sd3.sd3_impls import BaseModel, SDVAE, SD3LatentFormat +from modules.models.sd3.sd3_cond import SD3Cond + +from modules import shared, devices + + +class SD3Denoiser(k_diffusion.external.DiscreteSchedule): + def __init__(self, inner_model, sigmas): + super().__init__(sigmas, quantize=shared.opts.enable_quantization) + self.inner_model = inner_model + + def forward(self, input, sigma, **kwargs): + return self.inner_model.apply_model(input, sigma, **kwargs) + + +class SD3Inferencer(torch.nn.Module): + def __init__(self, state_dict, shift=3, use_ema=False): + super().__init__() + + self.shift = shift + + with torch.no_grad(): + self.model = BaseModel(shift=shift, state_dict=state_dict, prefix="model.diffusion_model.", device="cpu", dtype=devices.dtype) + self.first_stage_model = SDVAE(device="cpu", dtype=devices.dtype_vae) + self.first_stage_model.dtype = self.model.diffusion_model.dtype + + self.alphas_cumprod = 1 / (self.model.model_sampling.sigmas ** 2 + 1) + + self.text_encoders = SD3Cond() + self.cond_stage_key = 'txt' + + self.parameterization = "eps" + self.model.conditioning_key = "crossattn" + + self.latent_format = SD3LatentFormat() + self.latent_channels = 16 + + @property + def cond_stage_model(self): + return self.text_encoders + + def before_load_weights(self, state_dict): + self.cond_stage_model.before_load_weights(state_dict) + + def ema_scope(self): + return contextlib.nullcontext() + + def get_learned_conditioning(self, batch: list[str]): + return self.cond_stage_model(batch) + + def apply_model(self, x, t, cond): + return self.model(x, t, c_crossattn=cond['crossattn'], y=cond['vector']) + + def decode_first_stage(self, latent): + latent = self.latent_format.process_out(latent) + return self.first_stage_model.decode(latent) + + def encode_first_stage(self, image): + latent = self.first_stage_model.encode(image) + return self.latent_format.process_in(latent) + + def get_first_stage_encoding(self, x): + return x + + def create_denoiser(self): + return SD3Denoiser(self, self.model.model_sampling.sigmas) + + def medvram_fields(self): + return [ + (self, 'first_stage_model'), + (self, 'text_encoders'), + (self, 'model'), + ] + + def add_noise_to_latent(self, x, noise, amount): + return x * (1 - amount) + noise * amount + + def fix_dimensions(self, width, height): + return width // 16 * 16, height // 16 * 16 + + def diffusers_weight_mapping(self): + for i in range(self.model.depth): + yield f"transformer.transformer_blocks.{i}.attn.to_q", f"diffusion_model_joint_blocks_{i}_x_block_attn_qkv_q_proj" + yield f"transformer.transformer_blocks.{i}.attn.to_k", f"diffusion_model_joint_blocks_{i}_x_block_attn_qkv_k_proj" + yield f"transformer.transformer_blocks.{i}.attn.to_v", f"diffusion_model_joint_blocks_{i}_x_block_attn_qkv_v_proj" + yield f"transformer.transformer_blocks.{i}.attn.to_out.0", f"diffusion_model_joint_blocks_{i}_x_block_attn_proj" + + yield f"transformer.transformer_blocks.{i}.attn.add_q_proj", f"diffusion_model_joint_blocks_{i}_context_block.attn_qkv_q_proj" + yield f"transformer.transformer_blocks.{i}.attn.add_k_proj", f"diffusion_model_joint_blocks_{i}_context_block.attn_qkv_k_proj" + yield f"transformer.transformer_blocks.{i}.attn.add_v_proj", f"diffusion_model_joint_blocks_{i}_context_block.attn_qkv_v_proj" + yield f"transformer.transformer_blocks.{i}.attn.add_out_proj.0", f"diffusion_model_joint_blocks_{i}_context_block_attn_proj" diff --git a/modules/options.py b/modules/options.py index 35ccade25..2a78a825e 100644 --- a/modules/options.py +++ b/modules/options.py @@ -240,6 +240,9 @@ def dumpjson(self): item_categories = {} for item in self.data_labels.values(): + if item.section[0] is None: + continue + category = categories.mapping.get(item.category_id) category = "Uncategorized" if category is None else category.label if category not in item_categories: diff --git a/modules/paths_internal.py b/modules/paths_internal.py index 2ed1392a4..67521f5cd 100644 --- a/modules/paths_internal.py +++ b/modules/paths_internal.py @@ -7,7 +7,7 @@ from pathlib import Path -normalized_filepath = lambda filepath: str(Path(filepath).resolve()) +normalized_filepath = lambda filepath: str(Path(filepath).absolute()) commandline_args = os.environ.get('COMMANDLINE_ARGS', "") sys.argv += shlex.split(commandline_args) @@ -24,14 +24,15 @@ # Parse the --data-dir flag first so we can use it as a base for our other argument default values parser_pre = argparse.ArgumentParser(add_help=False) parser_pre.add_argument("--data-dir", type=str, default=os.path.dirname(modules_path), help="base path where all user data is stored", ) +parser_pre.add_argument("--models-dir", type=str, default=None, help="base path where models are stored; overrides --data-dir", ) cmd_opts_pre = parser_pre.parse_known_args()[0] data_path = cmd_opts_pre.data_dir -models_path = os.path.join(data_path, "models") +models_path = cmd_opts_pre.models_dir if cmd_opts_pre.models_dir else os.path.join(data_path, "models") extensions_dir = os.path.join(data_path, "extensions") extensions_builtin_dir = os.path.join(script_path, "extensions-builtin") config_states_dir = os.path.join(script_path, "config_states") -default_output_dir = os.path.join(data_path, "output") +default_output_dir = os.path.join(data_path, "outputs") roboto_ttf_file = os.path.join(modules_path, 'Roboto-Regular.ttf') diff --git a/modules/postprocessing.py b/modules/postprocessing.py index f14882321..caf2fe4d7 100644 --- a/modules/postprocessing.py +++ b/modules/postprocessing.py @@ -13,14 +13,17 @@ def run_postprocessing(extras_mode, image, image_folder, input_dir, output_dir, outputs = [] + if isinstance(image, dict): + image = image["composite"] + def get_images(extras_mode, image, image_folder, input_dir): if extras_mode == 1: for img in image_folder: if isinstance(img, Image.Image): - image = img + image = images.fix_image(img) fn = '' else: - image = Image.open(os.path.abspath(img.name)) + image = images.read(os.path.abspath(img.name)) fn = os.path.splitext(img.orig_name)[0] yield image, fn elif extras_mode == 2: @@ -51,22 +54,24 @@ def get_images(extras_mode, image, image_folder, input_dir): shared.state.textinfo = name shared.state.skipped = False - if shared.state.interrupted: + if shared.state.interrupted or shared.state.stopping_generation: break if isinstance(image_placeholder, str): try: - image_data = Image.open(image_placeholder) + image_data = images.read(image_placeholder) except Exception: continue else: image_data = image_placeholder + image_data = image_data if image_data.mode in ("RGBA", "RGB") else image_data.convert("RGB") + parameters, existing_pnginfo = images.read_info_from_image(image_data) if parameters: existing_pnginfo["parameters"] = parameters - initial_pp = scripts_postprocessing.PostprocessedImage(image_data.convert("RGB")) + initial_pp = scripts_postprocessing.PostprocessedImage(image_data) scripts.scripts_postproc.run(initial_pp, args) @@ -122,8 +127,6 @@ def get_images(extras_mode, image, image_folder, input_dir): if extras_mode != 2 or show_extras_results: outputs.append(pp.image) - image_data.close() - devices.torch_gc() shared.state.end() return outputs, ui_common.plaintext_to_html(infotext), '' @@ -133,13 +136,15 @@ def run_postprocessing_webui(id_task, *args, **kwargs): return run_postprocessing(*args, **kwargs) -def run_extras(extras_mode, resize_mode, image, image_folder, input_dir, output_dir, show_extras_results, gfpgan_visibility, codeformer_visibility, codeformer_weight, upscaling_resize, upscaling_resize_w, upscaling_resize_h, upscaling_crop, extras_upscaler_1, extras_upscaler_2, extras_upscaler_2_visibility, upscale_first: bool, save_output: bool = True): +def run_extras(extras_mode, resize_mode, image, image_folder, input_dir, output_dir, show_extras_results, gfpgan_visibility, codeformer_visibility, codeformer_weight, upscaling_resize, upscaling_resize_w, upscaling_resize_h, upscaling_crop, extras_upscaler_1, extras_upscaler_2, extras_upscaler_2_visibility, upscale_first: bool, save_output: bool = True, max_side_length: int = 0): """old handler for API""" args = scripts.scripts_postproc.create_args_for_run({ "Upscale": { + "upscale_enabled": True, "upscale_mode": resize_mode, "upscale_by": upscaling_resize, + "max_side_length": max_side_length, "upscale_to_width": upscaling_resize_w, "upscale_to_height": upscaling_resize_h, "upscale_crop": upscaling_crop, diff --git a/modules/processing.py b/modules/processing.py index 64e564e00..1398d5b29 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -16,7 +16,7 @@ from typing import Any import modules.sd_hijack -from modules import devices, prompt_parser, masking, sd_samplers, lowvram, infotext_utils, extra_networks, sd_vae_approx, scripts, sd_samplers_common, sd_unet, errors, rng +from modules import devices, prompt_parser, masking, sd_samplers, lowvram, infotext_utils, extra_networks, sd_vae_approx, scripts, sd_samplers_common, sd_unet, errors, rng, profiling from modules.rng import slerp # noqa: F401 from modules.sd_hijack import model_hijack from modules.sd_samplers_common import images_tensor_to_samples, decode_first_stage, approximation_indexes @@ -117,20 +117,17 @@ def txt2img_image_conditioning(sd_model, x, width, height): return x.new_zeros(x.shape[0], 2*sd_model.noise_augmentor.time_embed.dim, dtype=x.dtype, device=x.device) else: - sd = sd_model.model.state_dict() - diffusion_model_input = sd.get('diffusion_model.input_blocks.0.0.weight', None) - if diffusion_model_input is not None: - if diffusion_model_input.shape[1] == 9: - # The "masked-image" in this case will just be all 0.5 since the entire image is masked. - image_conditioning = torch.ones(x.shape[0], 3, height, width, device=x.device) * 0.5 - image_conditioning = images_tensor_to_samples(image_conditioning, - approximation_indexes.get(opts.sd_vae_encode_method)) + if sd_model.is_sdxl_inpaint: + # The "masked-image" in this case will just be all 0.5 since the entire image is masked. + image_conditioning = torch.ones(x.shape[0], 3, height, width, device=x.device) * 0.5 + image_conditioning = images_tensor_to_samples(image_conditioning, + approximation_indexes.get(opts.sd_vae_encode_method)) - # Add the fake full 1s mask to the first dimension. - image_conditioning = torch.nn.functional.pad(image_conditioning, (0, 0, 0, 0, 1, 0), value=1.0) - image_conditioning = image_conditioning.to(x.dtype) + # Add the fake full 1s mask to the first dimension. + image_conditioning = torch.nn.functional.pad(image_conditioning, (0, 0, 0, 0, 1, 0), value=1.0) + image_conditioning = image_conditioning.to(x.dtype) - return image_conditioning + return image_conditioning # Dummy zero conditioning if we're not using inpainting or unclip models. # Still takes up a bit of memory, but no encoder call. @@ -154,6 +151,7 @@ class StableDiffusionProcessing: seed_resize_from_w: int = -1 seed_enable_extras: bool = True sampler_name: str = None + scheduler: str = None batch_size: int = 1 n_iter: int = 1 steps: int = 50 @@ -189,8 +187,8 @@ class StableDiffusionProcessing: script_args_value: list = field(default=None, init=False) scripts_setup_complete: bool = field(default=False, init=False) - cached_uc = [None, None] - cached_c = [None, None] + cached_uc = [None, None, None] + cached_c = [None, None, None] comments: dict = None sampler: sd_samplers_common.Sampler | None = field(default=None, init=False) @@ -229,6 +227,9 @@ class StableDiffusionProcessing: is_api: bool = field(default=False, init=False) + latents_after_sampling = [] + pixels_after_sampling = [] + def __post_init__(self): if self.sampler_index is not None: print("sampler_index argument for StableDiffusionProcessing does not do anything; use sampler_name", file=sys.stderr) @@ -239,11 +240,6 @@ def __post_init__(self): self.styles = [] self.sampler_noise_scheduler_override = None - self.s_min_uncond = self.s_min_uncond if self.s_min_uncond is not None else opts.s_min_uncond - self.s_churn = self.s_churn if self.s_churn is not None else opts.s_churn - self.s_tmin = self.s_tmin if self.s_tmin is not None else opts.s_tmin - self.s_tmax = (self.s_tmax if self.s_tmax is not None else opts.s_tmax) or float('inf') - self.s_noise = self.s_noise if self.s_noise is not None else opts.s_noise self.extra_generation_params = self.extra_generation_params or {} self.override_settings = self.override_settings or {} @@ -261,8 +257,17 @@ def __post_init__(self): self.cached_c = StableDiffusionProcessing.cached_c self.extra_result_images = [] + self.latents_after_sampling = [] + self.pixels_after_sampling = [] self.modified_noise = None + def fill_fields_from_opts(self): + self.s_min_uncond = self.s_min_uncond if self.s_min_uncond is not None else opts.s_min_uncond + self.s_churn = self.s_churn if self.s_churn is not None else opts.s_churn + self.s_tmin = self.s_tmin if self.s_tmin is not None else opts.s_tmin + self.s_tmax = (self.s_tmax if self.s_tmax is not None else opts.s_tmax) or float('inf') + self.s_noise = self.s_noise if self.s_noise is not None else opts.s_noise + @property def sd_model(self): return shared.sd_model @@ -394,11 +399,8 @@ def img2img_image_conditioning(self, source_image, latent_image, image_mask=None if self.sampler.conditioning_key == "crossattn-adm": return self.unclip_image_conditioning(source_image) - sd = self.sampler.model_wrap.inner_model.model.state_dict() - diffusion_model_input = sd.get('diffusion_model.input_blocks.0.0.weight', None) - if diffusion_model_input is not None: - if diffusion_model_input.shape[1] == 9: - return self.inpainting_image_conditioning(source_image, latent_image, image_mask=image_mask) + if self.sampler.model_wrap.inner_model.is_sdxl_inpaint: + return self.inpainting_image_conditioning(source_image, latent_image, image_mask=image_mask) # Dummy zero conditioning if we're not using inpainting or depth model. return latent_image.new_zeros(latent_image.shape[0], 5, 1, 1) @@ -488,12 +490,14 @@ def get_conds_with_caching(self, function, required_prompts, steps, caches, extr for cache in caches: if cache[0] is not None and cached_params == cache[0]: + modules.sd_hijack.model_hijack.extra_generation_params.update(cache[2]) return cache[1] cache = caches[0] with devices.autocast(): cache[1] = function(shared.sd_model, required_prompts, steps, hires_steps, shared.opts.use_old_scheduling) + cache[2] = modules.sd_hijack.model_hijack.extra_generation_params cache[0] = cached_params return cache[1] @@ -574,7 +578,7 @@ def __init__(self, p: StableDiffusionProcessing, images_list, seed=-1, info="", self.all_negative_prompts = all_negative_prompts or p.all_negative_prompts or [self.negative_prompt] self.all_seeds = all_seeds or p.all_seeds or [self.seed] self.all_subseeds = all_subseeds or p.all_subseeds or [self.subseed] - self.infotexts = infotexts or [info] + self.infotexts = infotexts or [info] * len(images_list) self.version = program_version() def js(self): @@ -613,7 +617,7 @@ def js(self): "version": self.version, } - return json.dumps(obj) + return json.dumps(obj, default=lambda o: None) def infotext(self, p: StableDiffusionProcessing, index): return create_infotext(p, self.all_prompts, self.all_seeds, self.all_subseeds, comments=[], position_in_batch=index % self.batch_size, iteration=index // self.batch_size) @@ -672,7 +676,53 @@ def program_version(): def create_infotext(p, all_prompts, all_seeds, all_subseeds, comments=None, iteration=0, position_in_batch=0, use_main_prompt=False, index=None, all_negative_prompts=None): - if index is None: + """ + this function is used to generate the infotext that is stored in the generated images, it's contains the parameters that are required to generate the imagee + Args: + p: StableDiffusionProcessing + all_prompts: list[str] + all_seeds: list[int] + all_subseeds: list[int] + comments: list[str] + iteration: int + position_in_batch: int + use_main_prompt: bool + index: int + all_negative_prompts: list[str] + + Returns: str + + Extra generation params + p.extra_generation_params dictionary allows for additional parameters to be added to the infotext + this can be use by the base webui or extensions. + To add a new entry, add a new key value pair, the dictionary key will be used as the key of the parameter in the infotext + the value generation_params can be defined as: + - str | None + - List[str|None] + - callable func(**kwargs) -> str | None + + When defined as a string, it will be used as without extra processing; this is this most common use case. + + Defining as a list allows for parameter that changes across images in the job, for example, the 'Seed' parameter. + The list should have the same length as the total number of images in the entire job. + + Defining as a callable function allows parameter cannot be generated earlier or when extra logic is required. + For example 'Hires prompt', due to reasons the hr_prompt might be changed by process in the pipeline or extensions + and may vary across different images, defining as a static string or list would not work. + + The function takes locals() as **kwargs, as such will have access to variables like 'p' and 'index'. + the base signature of the function should be: + func(**kwargs) -> str | None + optionally it can have additional arguments that will be used in the function: + func(p, index, **kwargs) -> str | None + note: for better future compatibility even though this function will have access to all variables in the locals(), + it is recommended to only use the arguments present in the function signature of create_infotext. + For actual implementation examples, see StableDiffusionProcessingTxt2Img.init > get_hr_prompt. + """ + + if use_main_prompt: + index = 0 + elif index is None: index = position_in_batch + iteration * p.batch_size if all_negative_prompts is None: @@ -683,6 +733,9 @@ def create_infotext(p, all_prompts, all_seeds, all_subseeds, comments=None, iter token_merging_ratio = p.get_token_merging_ratio() token_merging_ratio_hr = p.get_token_merging_ratio(for_hr=True) + prompt_text = p.main_prompt if use_main_prompt else all_prompts[index] + negative_prompt = p.main_negative_prompt if use_main_prompt else all_negative_prompts[index] + uses_ensd = opts.eta_noise_seed_delta != 0 if uses_ensd: uses_ensd = sd_samplers_common.is_sampler_using_eta_noise_seed_delta(p) @@ -690,6 +743,7 @@ def create_infotext(p, all_prompts, all_seeds, all_subseeds, comments=None, iter generation_params = { "Steps": p.steps, "Sampler": p.sampler_name, + "Schedule type": p.scheduler, "CFG scale": p.cfg_scale, "Image CFG scale": getattr(p, 'image_cfg_scale', None), "Seed": p.all_seeds[0] if use_main_prompt else all_seeds[index], @@ -712,17 +766,25 @@ def create_infotext(p, all_prompts, all_seeds, all_subseeds, comments=None, iter "Token merging ratio hr": None if not enable_hr or token_merging_ratio_hr == 0 else token_merging_ratio_hr, "Init image hash": getattr(p, 'init_img_hash', None), "RNG": opts.randn_source if opts.randn_source != "GPU" else None, - "NGMS": None if p.s_min_uncond == 0 else p.s_min_uncond, "Tiling": "True" if p.tiling else None, **p.extra_generation_params, "Version": program_version() if opts.add_version_to_infotext else None, "User": p.user if opts.add_user_name_to_info else None, } + for key, value in generation_params.items(): + try: + if isinstance(value, list): + generation_params[key] = value[index] + elif callable(value): + generation_params[key] = value(**locals()) + except Exception: + errors.report(f'Error creating infotext for key "{key}"', exc_info=True) + generation_params[key] = None + generation_params_text = ", ".join([k if k == v else f'{k}: {infotext_utils.quote(v)}' for k, v in generation_params.items() if v is not None]) - prompt_text = p.main_prompt if use_main_prompt else all_prompts[index] - negative_prompt_text = f"\nNegative prompt: {p.main_negative_prompt if use_main_prompt else all_negative_prompts[index]}" if all_negative_prompts[index] else "" + negative_prompt_text = f"\nNegative prompt: {negative_prompt}" if negative_prompt else "" return f"{prompt_text}{negative_prompt_text}\n{generation_params_text}".strip() @@ -749,7 +811,11 @@ def process_images(p: StableDiffusionProcessing) -> Processed: if k == 'sd_vae': sd_vae.reload_vae_weights() - res = process_images_inner(p) + # backwards compatibility, fix sampler and scheduler if invalid + sd_samplers.fix_p_invalid_sampler_and_scheduler(p) + + with profiling.Profiler(): + res = process_images_inner(p) finally: # restore opts to original state @@ -787,6 +853,9 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed: if p.refiner_checkpoint_info is None: raise Exception(f'Could not find checkpoint with name {p.refiner_checkpoint}') + if hasattr(shared.sd_model, 'fix_dimensions'): + p.width, p.height = shared.sd_model.fix_dimensions(p.width, p.height) + p.sd_model_name = shared.sd_model.sd_checkpoint_info.name_for_extra p.sd_model_hash = shared.sd_model.sd_model_hash p.sd_vae_name = sd_vae.get_loaded_vae_name() @@ -795,6 +864,7 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed: apply_circular_forge(p.sd_model, p.tiling) modules.sd_hijack.model_hijack.clear_comments() + p.fill_fields_from_opts() p.setup_prompts() if isinstance(seed, list): @@ -845,7 +915,8 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed: p.seeds = p.all_seeds[n * p.batch_size:(n + 1) * p.batch_size] p.subseeds = p.all_subseeds[n * p.batch_size:(n + 1) * p.batch_size] - p.rng = rng.ImageRNG((opt_C, p.height // opt_f, p.width // opt_f), p.seeds, subseeds=p.subseeds, subseed_strength=p.subseed_strength, seed_resize_from_h=p.seed_resize_from_h, seed_resize_from_w=p.seed_resize_from_w) + latent_channels = getattr(shared.sd_model, 'latent_channels', opt_C) + p.rng = rng.ImageRNG((latent_channels, p.height // opt_f, p.width // opt_f), p.seeds, subseeds=p.subseeds, subseed_strength=p.subseed_strength, seed_resize_from_h=p.seed_resize_from_h, seed_resize_from_w=p.seed_resize_from_w) if p.scripts is not None: p.scripts.before_process_batch(p, batch_number=n, prompts=p.prompts, seeds=p.seeds, subseeds=p.subseeds) @@ -863,52 +934,26 @@ def process_images_inner(p: StableDiffusionProcessing) -> Processed: if p.scripts is not None: p.scripts.process_batch(p, batch_number=n, prompts=p.prompts, seeds=p.seeds, subseeds=p.subseeds) + p.setup_conds() + + p.extra_generation_params.update(model_hijack.extra_generation_params) + # params.txt should be saved after scripts.process_batch, since the # infotext could be modified by that callback # Example: a wildcard processed by process_batch sets an extra model # strength, which is saved as "Model Strength: 1.0" in the infotext - if n == 0: + if n == 0 and not cmd_opts.no_prompt_history: with open(os.path.join(paths.data_path, "params.txt"), "w", encoding="utf8") as file: processed = Processed(p, []) file.write(processed.infotext(p, 0)) - p.setup_conds() - for comment in model_hijack.comments: p.comment(comment) - p.extra_generation_params.update(model_hijack.extra_generation_params) - if p.n_iter > 1: shared.state.job = f"Batch {n+1} out of {p.n_iter}" - def rescale_zero_terminal_snr_abar(alphas_cumprod): - alphas_bar_sqrt = alphas_cumprod.sqrt() - - # Store old values. - alphas_bar_sqrt_0 = alphas_bar_sqrt[0].clone() - alphas_bar_sqrt_T = alphas_bar_sqrt[-1].clone() - - # Shift so the last timestep is zero. - alphas_bar_sqrt -= (alphas_bar_sqrt_T) - - # Scale so the first timestep is back to the old value. - alphas_bar_sqrt *= alphas_bar_sqrt_0 / (alphas_bar_sqrt_0 - alphas_bar_sqrt_T) - - # Convert alphas_bar_sqrt to betas - alphas_bar = alphas_bar_sqrt**2 # Revert sqrt - alphas_bar[-1] = 4.8973451890853435e-08 - return alphas_bar - - if hasattr(p.sd_model, 'alphas_cumprod') and hasattr(p.sd_model, 'alphas_cumprod_original'): - p.sd_model.alphas_cumprod = p.sd_model.alphas_cumprod_original.to(shared.device) - - if opts.use_downcasted_alpha_bar: - p.extra_generation_params['Downcast alphas_cumprod'] = opts.use_downcasted_alpha_bar - p.sd_model.alphas_cumprod = p.sd_model.alphas_cumprod.half().to(shared.device) - if opts.sd_noise_schedule == "Zero Terminal SNR": - p.extra_generation_params['Noise Schedule'] = opts.sd_noise_schedule - p.sd_model.alphas_cumprod = rescale_zero_terminal_snr_abar(p.sd_model.alphas_cumprod).to(shared.device) + sd_models.apply_alpha_schedule_override(p.sd_model, p) alphas_cumprod_modifiers = p.sd_model.forge_objects.unet.model_options.get('alphas_cumprod_modifiers', []) alphas_cumprod_backup = None @@ -921,6 +966,9 @@ def rescale_zero_terminal_snr_abar(alphas_cumprod): samples_ddim = p.sample(conditioning=p.c, unconditional_conditioning=p.uc, seeds=p.seeds, subseeds=p.subseeds, subseed_strength=p.subseed_strength, prompts=p.prompts) + for x_sample in samples_ddim: + p.latents_after_sampling.append(x_sample) + if alphas_cumprod_backup is not None: p.sd_model.alphas_cumprod = alphas_cumprod_backup p.sd_model.forge_objects.unet.model.model_sampling.set_sigmas(((1 - p.sd_model.alphas_cumprod) / p.sd_model.alphas_cumprod) ** 0.5) @@ -933,6 +981,8 @@ def rescale_zero_terminal_snr_abar(alphas_cumprod): if getattr(samples_ddim, 'already_decoded', False): x_samples_ddim = samples_ddim else: + devices.test_for_nans(samples_ddim, "unet") + if opts.sd_vae_decode_method != 'Full': p.extra_generation_params['VAE Decoder'] = opts.sd_vae_decode_method x_samples_ddim = decode_latent_batch(p.sd_model, samples_ddim, target_device=devices.cpu, check_for_nans=True) @@ -979,7 +1029,7 @@ def infotext(index=0, use_main_prompt=False): image = Image.fromarray(x_sample) if p.scripts is not None: - pp = scripts.PostprocessImageArgs(image) + pp = scripts.PostprocessImageArgs(image, i + p.iteration * p.batch_size) p.scripts.postprocess_image(p, pp) image = pp.image @@ -1009,8 +1059,10 @@ def infotext(index=0, use_main_prompt=False): # and use it in the composite step. image, original_denoised_image = apply_overlay(image, p.paste_to, overlay_image) + p.pixels_after_sampling.append(image) + if p.scripts is not None: - pp = scripts.PostprocessImageArgs(image) + pp = scripts.PostprocessImageArgs(image, i + p.iteration * p.batch_size) p.scripts.postprocess_image_after_composite(p, pp) image = pp.image @@ -1109,12 +1161,13 @@ class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing): hr_resize_y: int = 0 hr_checkpoint_name: str = None hr_sampler_name: str = None + hr_scheduler: str = None hr_prompt: str = '' hr_negative_prompt: str = '' force_task_id: str = None - cached_hr_uc = [None, None] - cached_hr_c = [None, None] + cached_hr_uc = [None, None, None] + cached_hr_c = [None, None, None] hr_checkpoint_info: dict = field(default=None, init=False) hr_upscale_to_x: int = field(default=0, init=False) @@ -1197,11 +1250,21 @@ def init(self, all_prompts, all_seeds, all_subseeds): if self.hr_sampler_name is not None and self.hr_sampler_name != self.sampler_name: self.extra_generation_params["Hires sampler"] = self.hr_sampler_name - if tuple(self.hr_prompt) != tuple(self.prompt): - self.extra_generation_params["Hires prompt"] = self.hr_prompt + def get_hr_prompt(p, index, prompt_text, **kwargs): + hr_prompt = p.all_hr_prompts[index] + return hr_prompt if hr_prompt != prompt_text else None + + def get_hr_negative_prompt(p, index, negative_prompt, **kwargs): + hr_negative_prompt = p.all_hr_negative_prompts[index] + return hr_negative_prompt if hr_negative_prompt != negative_prompt else None + + self.extra_generation_params["Hires prompt"] = get_hr_prompt + self.extra_generation_params["Hires negative prompt"] = get_hr_negative_prompt + + self.extra_generation_params["Hires schedule type"] = None # to be set in sd_samplers_kdiffusion.py - if tuple(self.hr_negative_prompt) != tuple(self.negative_prompt): - self.extra_generation_params["Hires negative prompt"] = self.hr_negative_prompt + if self.hr_scheduler is None: + self.hr_scheduler = self.scheduler self.latent_scale_mode = shared.latent_upscale_modes.get(self.hr_upscaler, None) if self.hr_upscaler is not None else shared.latent_upscale_modes.get(shared.latent_upscale_default_mode, "nearest") if self.enable_hr and self.latent_scale_mode is None: @@ -1370,6 +1433,13 @@ def save_intermediate(image, index): if self.scripts is not None: self.scripts.before_hr(self) + self.scripts.process_before_every_sampling( + p=self, + x=samples, + noise=noise, + c=self.hr_c, + uc=self.hr_uc, + ) self.sd_model.forge_objects = self.sd_model.forge_objects_after_applying_lora.shallow_copy() apply_token_merging(self.sd_model, self.get_token_merging_ratio(for_hr=True)) @@ -1568,16 +1638,23 @@ def init(self, all_prompts, all_seeds, all_subseeds): if self.inpaint_full_res: self.mask_for_overlay = image_mask mask = image_mask.convert('L') - crop_region = masking.get_crop_region(mask, self.inpaint_full_res_padding) - crop_region = masking.expand_crop_region(crop_region, self.width, self.height, mask.width, mask.height) - x1, y1, x2, y2 = crop_region - - mask = mask.crop(crop_region) - image_mask = images.resize_image(2, mask, self.width, self.height) - self.paste_to = (x1, y1, x2-x1, y2-y1) - - self.extra_generation_params["Inpaint area"] = "Only masked" - self.extra_generation_params["Masked area padding"] = self.inpaint_full_res_padding + crop_region = masking.get_crop_region_v2(mask, self.inpaint_full_res_padding) + if crop_region: + crop_region = masking.expand_crop_region(crop_region, self.width, self.height, mask.width, mask.height) + x1, y1, x2, y2 = crop_region + mask = mask.crop(crop_region) + image_mask = images.resize_image(2, mask, self.width, self.height) + self.paste_to = (x1, y1, x2-x1, y2-y1) + self.extra_generation_params["Inpaint area"] = "Only masked" + self.extra_generation_params["Masked area padding"] = self.inpaint_full_res_padding + else: + crop_region = None + image_mask = None + self.mask_for_overlay = None + self.inpaint_full_res = False + massage = 'Unable to perform "Inpaint Only mask" because mask is blank, switch to img2img mode.' + model_hijack.comments.append(massage) + logging.info(massage) else: image_mask = images.resize_image(self.resize_mode, image_mask, self.width, self.height) np_mask = np.array(image_mask) @@ -1588,6 +1665,9 @@ def init(self, all_prompts, all_seeds, all_subseeds): latent_mask = self.latent_mask if self.latent_mask is not None else image_mask + if self.scripts is not None: + self.scripts.before_process_init_images(self, dict(crop_region=crop_region, image_mask=image_mask)) + add_color_corrections = opts.img2img_color_correction and self.color_corrections is None if add_color_corrections: self.color_corrections = [] @@ -1605,6 +1685,8 @@ def init(self, all_prompts, all_seeds, all_subseeds): image = images.resize_image(self.resize_mode, image, self.width, self.height) if image_mask is not None: + if self.mask_for_overlay.size != (image.width, image.height): + self.mask_for_overlay = images.resize_image(self.resize_mode, self.mask_for_overlay, image.width, image.height) image_masked = Image.new('RGBa', (image.width, image.height)) image_masked.paste(image.convert("RGBA").convert("RGBa"), mask=ImageOps.invert(self.mask_for_overlay.convert('L'))) @@ -1663,10 +1745,10 @@ def init(self, all_prompts, all_seeds, all_subseeds): latmask = latmask[0] if self.mask_round: latmask = np.around(latmask) - latmask = np.tile(latmask[None], (4, 1, 1)) + latmask = np.tile(latmask[None], (self.init_latent.shape[1], 1, 1)) - self.mask = torch.asarray(1.0 - latmask).to(shared.device).type(self.sd_model.dtype) - self.nmask = torch.asarray(latmask).to(shared.device).type(self.sd_model.dtype) + self.mask = torch.asarray(1.0 - latmask).to(shared.device).type(devices.dtype) + self.nmask = torch.asarray(latmask).to(shared.device).type(devices.dtype) # this needs to be fixed to be done in sample() using actual seeds for batches if self.inpainting_fill == 2: diff --git a/modules/processing_scripts/comments.py b/modules/processing_scripts/comments.py index 638e39f29..cf81dfd8b 100644 --- a/modules/processing_scripts/comments.py +++ b/modules/processing_scripts/comments.py @@ -26,6 +26,13 @@ def process(self, p, *args): p.main_prompt = strip_comments(p.main_prompt) p.main_negative_prompt = strip_comments(p.main_negative_prompt) + if getattr(p, 'enable_hr', False): + p.all_hr_prompts = [strip_comments(x) for x in p.all_hr_prompts] + p.all_hr_negative_prompts = [strip_comments(x) for x in p.all_hr_negative_prompts] + + p.hr_prompt = strip_comments(p.hr_prompt) + p.hr_negative_prompt = strip_comments(p.hr_negative_prompt) + def before_token_counter(params: script_callbacks.BeforeTokenCounterParams): if not shared.opts.enable_prompt_comments: diff --git a/modules/processing_scripts/refiner.py b/modules/processing_scripts/refiner.py index ba33d8a4b..01504a5f4 100644 --- a/modules/processing_scripts/refiner.py +++ b/modules/processing_scripts/refiner.py @@ -22,7 +22,7 @@ def show(self, is_img2img): def ui(self, is_img2img): with InputAccordion(False, label="Refiner", elem_id=self.elem_id("enable")) as enable_refiner: with gr.Row(): - refiner_checkpoint = gr.Dropdown(label='Checkpoint', elem_id=self.elem_id("checkpoint"), choices=sd_models.checkpoint_tiles(), value='', tooltip="switch to another model in the middle of generation") + refiner_checkpoint = gr.Dropdown(label='Checkpoint', elem_id=self.elem_id("checkpoint"), choices=["", *sd_models.checkpoint_tiles()], value='', tooltip="switch to another model in the middle of generation") create_refresh_button(refiner_checkpoint, sd_models.list_models, lambda: {"choices": sd_models.checkpoint_tiles()}, self.elem_id("checkpoint_refresh")) refiner_switch_at = gr.Slider(value=0.8, label="Switch at", minimum=0.01, maximum=1.0, step=0.01, elem_id=self.elem_id("switch_at"), tooltip="fraction of sampling steps when the switch to refiner model should happen; 1=never, 0.5=switch in the middle of generation") diff --git a/modules/processing_scripts/sampler.py b/modules/processing_scripts/sampler.py new file mode 100644 index 000000000..1d465552c --- /dev/null +++ b/modules/processing_scripts/sampler.py @@ -0,0 +1,45 @@ +import gradio as gr + +from modules import scripts, sd_samplers, sd_schedulers, shared +from modules.infotext_utils import PasteField +from modules.ui_components import FormRow, FormGroup + + +class ScriptSampler(scripts.ScriptBuiltinUI): + section = "sampler" + + def __init__(self): + self.steps = None + self.sampler_name = None + self.scheduler = None + + def title(self): + return "Sampler" + + def ui(self, is_img2img): + sampler_names = [x.name for x in sd_samplers.visible_samplers()] + scheduler_names = [x.label for x in sd_schedulers.schedulers] + + if shared.opts.samplers_in_dropdown: + with FormRow(elem_id=f"sampler_selection_{self.tabname}"): + self.sampler_name = gr.Dropdown(label='Sampling method', elem_id=f"{self.tabname}_sampling", choices=sampler_names, value=sampler_names[0]) + self.scheduler = gr.Dropdown(label='Schedule type', elem_id=f"{self.tabname}_scheduler", choices=scheduler_names, value=scheduler_names[0]) + self.steps = gr.Slider(minimum=1, maximum=150, step=1, elem_id=f"{self.tabname}_steps", label="Sampling steps", value=20) + else: + with FormGroup(elem_id=f"sampler_selection_{self.tabname}"): + self.steps = gr.Slider(minimum=1, maximum=150, step=1, elem_id=f"{self.tabname}_steps", label="Sampling steps", value=20) + self.sampler_name = gr.Radio(label='Sampling method', elem_id=f"{self.tabname}_sampling", choices=sampler_names, value=sampler_names[0]) + self.scheduler = gr.Dropdown(label='Schedule type', elem_id=f"{self.tabname}_scheduler", choices=scheduler_names, value=scheduler_names[0]) + + self.infotext_fields = [ + PasteField(self.steps, "Steps", api="steps"), + PasteField(self.sampler_name, sd_samplers.get_sampler_from_infotext, api="sampler_name"), + PasteField(self.scheduler, sd_samplers.get_scheduler_from_infotext, api="scheduler"), + ] + + return self.steps, self.sampler_name, self.scheduler + + def setup(self, p, steps, sampler_name, scheduler): + p.steps = steps + p.sampler_name = sampler_name + p.scheduler = scheduler diff --git a/modules/processing_scripts/seed.py b/modules/processing_scripts/seed.py index 7a4c01598..717e8ef63 100644 --- a/modules/processing_scripts/seed.py +++ b/modules/processing_scripts/seed.py @@ -34,7 +34,7 @@ def ui(self, is_img2img): random_seed = ToolButton(ui.random_symbol, elem_id=self.elem_id("random_seed"), tooltip="Set seed to -1, which will cause a new random number to be used every time") reuse_seed = ToolButton(ui.reuse_symbol, elem_id=self.elem_id("reuse_seed"), tooltip="Reuse seed from last generation, mostly useful if it was randomized") - seed_checkbox = gr.Checkbox(label='Extra', elem_id=self.elem_id("subseed_show"), value=False) + seed_checkbox = gr.Checkbox(label='Extra', elem_id=self.elem_id("subseed_show"), value=False, scale=0, min_width=60) with gr.Group(visible=False, elem_id=self.elem_id("seed_extras")) as seed_extras: with gr.Row(elem_id=self.elem_id("subseed_row")): diff --git a/modules/profiling.py b/modules/profiling.py new file mode 100644 index 000000000..2729e0f30 --- /dev/null +++ b/modules/profiling.py @@ -0,0 +1,46 @@ +import torch + +from modules import shared, ui_gradio_extensions + + +class Profiler: + def __init__(self): + if not shared.opts.profiling_enable: + self.profiler = None + return + + activities = [] + if "CPU" in shared.opts.profiling_activities: + activities.append(torch.profiler.ProfilerActivity.CPU) + if "CUDA" in shared.opts.profiling_activities: + activities.append(torch.profiler.ProfilerActivity.CUDA) + + if not activities: + self.profiler = None + return + + self.profiler = torch.profiler.profile( + activities=activities, + record_shapes=shared.opts.profiling_record_shapes, + profile_memory=shared.opts.profiling_profile_memory, + with_stack=shared.opts.profiling_with_stack + ) + + def __enter__(self): + if self.profiler: + self.profiler.__enter__() + + return self + + def __exit__(self, exc_type, exc, exc_tb): + if self.profiler: + shared.state.textinfo = "Finishing profile..." + + self.profiler.__exit__(exc_type, exc, exc_tb) + + self.profiler.export_chrome_trace(shared.opts.profiling_filename) + + +def webpath(): + return ui_gradio_extensions.webpath(shared.opts.profiling_filename) + diff --git a/modules/progress.py b/modules/progress.py index 85255e821..6ab789cdf 100644 --- a/modules/progress.py +++ b/modules/progress.py @@ -1,3 +1,4 @@ +from __future__ import annotations import base64 import io import time @@ -66,11 +67,11 @@ class ProgressResponse(BaseModel): active: bool = Field(title="Whether the task is being worked on right now") queued: bool = Field(title="Whether the task is in queue") completed: bool = Field(title="Whether the task has already finished") - progress: float = Field(default=None, title="Progress", description="The progress with a range of 0 to 1") - eta: float = Field(default=None, title="ETA in secs") - live_preview: str = Field(default=None, title="Live preview image", description="Current live preview; a data: uri") - id_live_preview: int = Field(default=None, title="Live preview image ID", description="Send this together with next request to prevent receiving same image") - textinfo: str = Field(default=None, title="Info text", description="Info text used by WebUI.") + progress: float | None = Field(default=None, title="Progress", description="The progress with a range of 0 to 1") + eta: float | None = Field(default=None, title="ETA in secs") + live_preview: str | None = Field(default=None, title="Live preview image", description="Current live preview; a data: uri") + id_live_preview: int | None = Field(default=None, title="Live preview image ID", description="Send this together with next request to prevent receiving same image") + textinfo: str | None = Field(default=None, title="Info text", description="Info text used by WebUI.") def setup_progress_api(app): diff --git a/modules/prompt_parser.py b/modules/prompt_parser.py index c8b423a0e..70aefbc77 100644 --- a/modules/prompt_parser.py +++ b/modules/prompt_parser.py @@ -268,7 +268,7 @@ def get_multicond_learned_conditioning(model, prompts, steps, hires_steps=None, class DictWithShape(dict): - def __init__(self, x): + def __init__(self, x, shape=None): super().__init__() self.update(x) diff --git a/modules/rng.py b/modules/rng.py index 4a3bbb207..f3afb4def 100644 --- a/modules/rng.py +++ b/modules/rng.py @@ -40,7 +40,7 @@ def randn_local(seed, shape): def randn_like(x): - """Generate a tensor with random numbers from a normal distribution using the previously initialized genrator. + """Generate a tensor with random numbers from a normal distribution using the previously initialized generator. Use either randn() or manual_seed() to initialize the generator.""" @@ -54,7 +54,7 @@ def randn_like(x): def randn_without_seed(shape, generator=None): - """Generate a tensor with random numbers from a normal distribution using the previously initialized genrator. + """Generate a tensor with random numbers from a normal distribution using the previously initialized generator. Use either randn() or manual_seed() to initialize the generator.""" diff --git a/modules/safe.py b/modules/safe.py index fe771c1b8..d1e242e89 100644 --- a/modules/safe.py +++ b/modules/safe.py @@ -64,8 +64,8 @@ def find_class(self, module, name): raise Exception(f"global '{module}/{name}' is forbidden") -# Regular expression that accepts 'dirname/version', 'dirname/data.pkl', and 'dirname/data/' -allowed_zip_names_re = re.compile(r"^([^/]+)/((data/\d+)|version|(data\.pkl))$") +# Regular expression that accepts 'dirname/version', 'dirname/byteorder', 'dirname/data.pkl', '.data/serialization_id', and 'dirname/data/' +allowed_zip_names_re = re.compile(r"^([^/]+)/((data/\d+)|version|byteorder|.data/serialization_id|(data\.pkl))$") data_pkl_re = re.compile(r"^([^/]+)/data\.pkl$") def check_zip_filenames(filename, names): diff --git a/modules/script_callbacks.py b/modules/script_callbacks.py index 2c50f43c5..9059d4d93 100644 --- a/modules/script_callbacks.py +++ b/modules/script_callbacks.py @@ -1,13 +1,14 @@ +from __future__ import annotations + import dataclasses import inspect import os -from collections import namedtuple from typing import Optional, Any from fastapi import FastAPI from gradio import Blocks -from modules import errors, timer +from modules import errors, timer, extensions, shared, util def report_exception(c, job): @@ -116,7 +117,105 @@ class BeforeTokenCounterParams: is_positive: bool = True -ScriptCallback = namedtuple("ScriptCallback", ["script", "callback"]) +@dataclasses.dataclass +class ScriptCallback: + script: str + callback: any + name: str = "unnamed" + + +def add_callback(callbacks, fun, *, name=None, category='unknown', filename=None): + if filename is None: + stack = [x for x in inspect.stack() if x.filename != __file__] + filename = stack[0].filename if stack else 'unknown file' + + extension = extensions.find_extension(filename) + extension_name = extension.canonical_name if extension else 'base' + + callback_name = f"{extension_name}/{os.path.basename(filename)}/{category}" + if name is not None: + callback_name += f'/{name}' + + unique_callback_name = callback_name + for index in range(1000): + existing = any(x.name == unique_callback_name for x in callbacks) + if not existing: + break + + unique_callback_name = f'{callback_name}-{index+1}' + + callbacks.append(ScriptCallback(filename, fun, unique_callback_name)) + + +def sort_callbacks(category, unordered_callbacks, *, enable_user_sort=True): + callbacks = unordered_callbacks.copy() + callback_lookup = {x.name: x for x in callbacks} + dependencies = {} + + order_instructions = {} + for extension in extensions.extensions: + for order_instruction in extension.metadata.list_callback_order_instructions(): + if order_instruction.name in callback_lookup: + if order_instruction.name not in order_instructions: + order_instructions[order_instruction.name] = [] + + order_instructions[order_instruction.name].append(order_instruction) + + if order_instructions: + for callback in callbacks: + dependencies[callback.name] = [] + + for callback in callbacks: + for order_instruction in order_instructions.get(callback.name, []): + for after in order_instruction.after: + if after not in callback_lookup: + continue + + dependencies[callback.name].append(after) + + for before in order_instruction.before: + if before not in callback_lookup: + continue + + dependencies[before].append(callback.name) + + sorted_names = util.topological_sort(dependencies) + callbacks = [callback_lookup[x] for x in sorted_names] + + if enable_user_sort: + for name in reversed(getattr(shared.opts, 'prioritized_callbacks_' + category, [])): + index = next((i for i, callback in enumerate(callbacks) if callback.name == name), None) + if index is not None: + callbacks.insert(0, callbacks.pop(index)) + + return callbacks + + +def ordered_callbacks(category, unordered_callbacks=None, *, enable_user_sort=True): + if unordered_callbacks is None: + unordered_callbacks = callback_map.get('callbacks_' + category, []) + + if not enable_user_sort: + return sort_callbacks(category, unordered_callbacks, enable_user_sort=False) + + callbacks = ordered_callbacks_map.get(category) + if callbacks is not None and len(callbacks) == len(unordered_callbacks): + return callbacks + + callbacks = sort_callbacks(category, unordered_callbacks) + + ordered_callbacks_map[category] = callbacks + return callbacks + + +def enumerate_callbacks(): + for category, callbacks in callback_map.items(): + if category.startswith('callbacks_'): + category = category[10:] + + yield category, callbacks + + callback_map = dict( callbacks_app_started=[], callbacks_model_loaded=[], @@ -140,18 +239,19 @@ class BeforeTokenCounterParams: callbacks_list_unets=[], callbacks_before_token_counter=[], ) -event_subscriber_map = dict( - callbacks_setting_updated=[], -) + +ordered_callbacks_map = {} def clear_callbacks(): for callback_list in callback_map.values(): callback_list.clear() + ordered_callbacks_map.clear() + def app_started_callback(demo: Optional[Blocks], app: FastAPI): - for c in callback_map['callbacks_app_started']: + for c in ordered_callbacks('app_started'): try: c.callback(demo, app) timer.startup_timer.record(os.path.basename(c.script)) @@ -160,7 +260,7 @@ def app_started_callback(demo: Optional[Blocks], app: FastAPI): def app_reload_callback(): - for c in callback_map['callbacks_on_reload']: + for c in ordered_callbacks('on_reload'): try: c.callback() except Exception: @@ -168,7 +268,7 @@ def app_reload_callback(): def model_loaded_callback(sd_model): - for c in callback_map['callbacks_model_loaded']: + for c in ordered_callbacks('model_loaded'): try: c.callback(sd_model) except Exception: @@ -178,7 +278,7 @@ def model_loaded_callback(sd_model): def ui_tabs_callback(): res = [] - for c in callback_map['callbacks_ui_tabs']: + for c in ordered_callbacks('ui_tabs'): try: res += c.callback() or [] except Exception: @@ -188,7 +288,7 @@ def ui_tabs_callback(): def ui_train_tabs_callback(params: UiTrainTabParams): - for c in callback_map['callbacks_ui_train_tabs']: + for c in ordered_callbacks('ui_train_tabs'): try: c.callback(params) except Exception: @@ -196,7 +296,7 @@ def ui_train_tabs_callback(params: UiTrainTabParams): def ui_settings_callback(): - for c in callback_map['callbacks_ui_settings']: + for c in ordered_callbacks('ui_settings'): try: c.callback() except Exception: @@ -204,7 +304,7 @@ def ui_settings_callback(): def before_image_saved_callback(params: ImageSaveParams): - for c in callback_map['callbacks_before_image_saved']: + for c in ordered_callbacks('before_image_saved'): try: c.callback(params) except Exception: @@ -212,7 +312,7 @@ def before_image_saved_callback(params: ImageSaveParams): def image_saved_callback(params: ImageSaveParams): - for c in callback_map['callbacks_image_saved']: + for c in ordered_callbacks('image_saved'): try: c.callback(params) except Exception: @@ -220,7 +320,7 @@ def image_saved_callback(params: ImageSaveParams): def extra_noise_callback(params: ExtraNoiseParams): - for c in callback_map['callbacks_extra_noise']: + for c in ordered_callbacks('extra_noise'): try: c.callback(params) except Exception: @@ -228,7 +328,7 @@ def extra_noise_callback(params: ExtraNoiseParams): def cfg_denoiser_callback(params: CFGDenoiserParams): - for c in callback_map['callbacks_cfg_denoiser']: + for c in ordered_callbacks('cfg_denoiser'): try: c.callback(params) except Exception: @@ -236,7 +336,7 @@ def cfg_denoiser_callback(params: CFGDenoiserParams): def cfg_denoised_callback(params: CFGDenoisedParams): - for c in callback_map['callbacks_cfg_denoised']: + for c in ordered_callbacks('cfg_denoised'): try: c.callback(params) except Exception: @@ -244,7 +344,7 @@ def cfg_denoised_callback(params: CFGDenoisedParams): def cfg_after_cfg_callback(params: AfterCFGCallbackParams): - for c in callback_map['callbacks_cfg_after_cfg']: + for c in ordered_callbacks('cfg_after_cfg'): try: c.callback(params) except Exception: @@ -252,7 +352,7 @@ def cfg_after_cfg_callback(params: AfterCFGCallbackParams): def before_component_callback(component, **kwargs): - for c in callback_map['callbacks_before_component']: + for c in ordered_callbacks('before_component'): try: c.callback(component, **kwargs) except Exception: @@ -260,7 +360,7 @@ def before_component_callback(component, **kwargs): def after_component_callback(component, **kwargs): - for c in callback_map['callbacks_after_component']: + for c in ordered_callbacks('after_component'): try: c.callback(component, **kwargs) except Exception: @@ -268,7 +368,7 @@ def after_component_callback(component, **kwargs): def image_grid_callback(params: ImageGridLoopParams): - for c in callback_map['callbacks_image_grid']: + for c in ordered_callbacks('image_grid'): try: c.callback(params) except Exception: @@ -276,7 +376,7 @@ def image_grid_callback(params: ImageGridLoopParams): def infotext_pasted_callback(infotext: str, params: dict[str, Any]): - for c in callback_map['callbacks_infotext_pasted']: + for c in ordered_callbacks('infotext_pasted'): try: c.callback(infotext, params) except Exception: @@ -284,7 +384,7 @@ def infotext_pasted_callback(infotext: str, params: dict[str, Any]): def script_unloaded_callback(): - for c in reversed(callback_map['callbacks_script_unloaded']): + for c in reversed(ordered_callbacks('script_unloaded')): try: c.callback() except Exception: @@ -292,7 +392,7 @@ def script_unloaded_callback(): def before_ui_callback(): - for c in reversed(callback_map['callbacks_before_ui']): + for c in reversed(ordered_callbacks('before_ui')): try: c.callback() except Exception: @@ -302,7 +402,7 @@ def before_ui_callback(): def list_optimizers_callback(): res = [] - for c in callback_map['callbacks_list_optimizers']: + for c in ordered_callbacks('list_optimizers'): try: c.callback(res) except Exception: @@ -314,7 +414,7 @@ def list_optimizers_callback(): def list_unets_callback(): res = [] - for c in callback_map['callbacks_list_unets']: + for c in ordered_callbacks('list_unets'): try: c.callback(res) except Exception: @@ -324,37 +424,13 @@ def list_unets_callback(): def before_token_counter_callback(params: BeforeTokenCounterParams): - for c in callback_map['callbacks_before_token_counter']: + for c in ordered_callbacks('before_token_counter'): try: c.callback(params) except Exception: report_exception(c, 'before_token_counter') -def setting_updated_event_subscriber_chain(handler, component, setting_name: str): - """ - Arguments: - - handler: The returned handler from calling an event subscriber. - - component: The component that is updated. The component should provide - the value of setting after update. - - setting_name: The name of the setting. - """ - for param in event_subscriber_map['callbacks_setting_updated']: - handler = handler.then( - fn=lambda *args: param["fn"](*args, setting_name), - inputs=param["inputs"] + [component], - outputs=param["outputs"], - show_progress=False, - ) - - -def add_callback(callbacks, fun): - stack = [x for x in inspect.stack() if x.filename != __file__] - filename = stack[0].filename if stack else 'unknown file' - - callbacks.append(ScriptCallback(filename, fun)) - - def remove_current_script_callbacks(): stack = [x for x in inspect.stack() if x.filename != __file__] filename = stack[0].filename if stack else 'unknown file' @@ -363,32 +439,38 @@ def remove_current_script_callbacks(): for callback_list in callback_map.values(): for callback_to_remove in [cb for cb in callback_list if cb.script == filename]: callback_list.remove(callback_to_remove) + for ordered_callbacks_list in ordered_callbacks_map.values(): + for callback_to_remove in [cb for cb in ordered_callbacks_list if cb.script == filename]: + ordered_callbacks_list.remove(callback_to_remove) def remove_callbacks_for_function(callback_func): for callback_list in callback_map.values(): for callback_to_remove in [cb for cb in callback_list if cb.callback == callback_func]: callback_list.remove(callback_to_remove) + for ordered_callback_list in ordered_callbacks_map.values(): + for callback_to_remove in [cb for cb in ordered_callback_list if cb.callback == callback_func]: + ordered_callback_list.remove(callback_to_remove) -def on_app_started(callback): +def on_app_started(callback, *, name=None): """register a function to be called when the webui started, the gradio `Block` component and fastapi `FastAPI` object are passed as the arguments""" - add_callback(callback_map['callbacks_app_started'], callback) + add_callback(callback_map['callbacks_app_started'], callback, name=name, category='app_started') -def on_before_reload(callback): +def on_before_reload(callback, *, name=None): """register a function to be called just before the server reloads.""" - add_callback(callback_map['callbacks_on_reload'], callback) + add_callback(callback_map['callbacks_on_reload'], callback, name=name, category='on_reload') -def on_model_loaded(callback): +def on_model_loaded(callback, *, name=None): """register a function to be called when the stable diffusion model is created; the model is passed as an argument; this function is also called when the script is reloaded. """ - add_callback(callback_map['callbacks_model_loaded'], callback) + add_callback(callback_map['callbacks_model_loaded'], callback, name=name, category='model_loaded') -def on_ui_tabs(callback): +def on_ui_tabs(callback, *, name=None): """register a function to be called when the UI is creating new tabs. The function must either return a None, which means no new tabs to be added, or a list, where each element is a tuple: @@ -398,71 +480,71 @@ def on_ui_tabs(callback): title is tab text displayed to user in the UI elem_id is HTML id for the tab """ - add_callback(callback_map['callbacks_ui_tabs'], callback) + add_callback(callback_map['callbacks_ui_tabs'], callback, name=name, category='ui_tabs') -def on_ui_train_tabs(callback): +def on_ui_train_tabs(callback, *, name=None): """register a function to be called when the UI is creating new tabs for the train tab. Create your new tabs with gr.Tab. """ - add_callback(callback_map['callbacks_ui_train_tabs'], callback) + add_callback(callback_map['callbacks_ui_train_tabs'], callback, name=name, category='ui_train_tabs') -def on_ui_settings(callback): +def on_ui_settings(callback, *, name=None): """register a function to be called before UI settings are populated; add your settings by using shared.opts.add_option(shared.OptionInfo(...)) """ - add_callback(callback_map['callbacks_ui_settings'], callback) + add_callback(callback_map['callbacks_ui_settings'], callback, name=name, category='ui_settings') -def on_before_image_saved(callback): +def on_before_image_saved(callback, *, name=None): """register a function to be called before an image is saved to a file. The callback is called with one argument: - params: ImageSaveParams - parameters the image is to be saved with. You can change fields in this object. """ - add_callback(callback_map['callbacks_before_image_saved'], callback) + add_callback(callback_map['callbacks_before_image_saved'], callback, name=name, category='before_image_saved') -def on_image_saved(callback): +def on_image_saved(callback, *, name=None): """register a function to be called after an image is saved to a file. The callback is called with one argument: - params: ImageSaveParams - parameters the image was saved with. Changing fields in this object does nothing. """ - add_callback(callback_map['callbacks_image_saved'], callback) + add_callback(callback_map['callbacks_image_saved'], callback, name=name, category='image_saved') -def on_extra_noise(callback): +def on_extra_noise(callback, *, name=None): """register a function to be called before adding extra noise in img2img or hires fix; The callback is called with one argument: - params: ExtraNoiseParams - contains noise determined by seed and latent representation of image """ - add_callback(callback_map['callbacks_extra_noise'], callback) + add_callback(callback_map['callbacks_extra_noise'], callback, name=name, category='extra_noise') -def on_cfg_denoiser(callback): +def on_cfg_denoiser(callback, *, name=None): """register a function to be called in the kdiffussion cfg_denoiser method after building the inner model inputs. The callback is called with one argument: - params: CFGDenoiserParams - parameters to be passed to the inner model and sampling state details. """ - add_callback(callback_map['callbacks_cfg_denoiser'], callback) + add_callback(callback_map['callbacks_cfg_denoiser'], callback, name=name, category='cfg_denoiser') -def on_cfg_denoised(callback): +def on_cfg_denoised(callback, *, name=None): """register a function to be called in the kdiffussion cfg_denoiser method after building the inner model inputs. The callback is called with one argument: - params: CFGDenoisedParams - parameters to be passed to the inner model and sampling state details. """ - add_callback(callback_map['callbacks_cfg_denoised'], callback) + add_callback(callback_map['callbacks_cfg_denoised'], callback, name=name, category='cfg_denoised') -def on_cfg_after_cfg(callback): +def on_cfg_after_cfg(callback, *, name=None): """register a function to be called in the kdiffussion cfg_denoiser method after cfg calculations are completed. The callback is called with one argument: - params: AfterCFGCallbackParams - parameters to be passed to the script for post-processing after cfg calculation. """ - add_callback(callback_map['callbacks_cfg_after_cfg'], callback) + add_callback(callback_map['callbacks_cfg_after_cfg'], callback, name=name, category='cfg_after_cfg') -def on_before_component(callback): +def on_before_component(callback, *, name=None): """register a function to be called before a component is created. The callback is called with arguments: - component - gradio component that is about to be created. @@ -471,72 +553,61 @@ def on_before_component(callback): Use elem_id/label fields of kwargs to figure out which component it is. This can be useful to inject your own components somewhere in the middle of vanilla UI. """ - add_callback(callback_map['callbacks_before_component'], callback) + add_callback(callback_map['callbacks_before_component'], callback, name=name, category='before_component') -def on_after_component(callback): +def on_after_component(callback, *, name=None): """register a function to be called after a component is created. See on_before_component for more.""" - add_callback(callback_map['callbacks_after_component'], callback) + add_callback(callback_map['callbacks_after_component'], callback, name=name, category='after_component') -def on_image_grid(callback): +def on_image_grid(callback, *, name=None): """register a function to be called before making an image grid. The callback is called with one argument: - params: ImageGridLoopParams - parameters to be used for grid creation. Can be modified. """ - add_callback(callback_map['callbacks_image_grid'], callback) + add_callback(callback_map['callbacks_image_grid'], callback, name=name, category='image_grid') -def on_infotext_pasted(callback): +def on_infotext_pasted(callback, *, name=None): """register a function to be called before applying an infotext. The callback is called with two arguments: - infotext: str - raw infotext. - result: dict[str, any] - parsed infotext parameters. """ - add_callback(callback_map['callbacks_infotext_pasted'], callback) + add_callback(callback_map['callbacks_infotext_pasted'], callback, name=name, category='infotext_pasted') -def on_script_unloaded(callback): +def on_script_unloaded(callback, *, name=None): """register a function to be called before the script is unloaded. Any hooks/hijacks/monkeying about that the script did should be reverted here""" - add_callback(callback_map['callbacks_script_unloaded'], callback) + add_callback(callback_map['callbacks_script_unloaded'], callback, name=name, category='script_unloaded') -def on_before_ui(callback): +def on_before_ui(callback, *, name=None): """register a function to be called before the UI is created.""" - add_callback(callback_map['callbacks_before_ui'], callback) + add_callback(callback_map['callbacks_before_ui'], callback, name=name, category='before_ui') -def on_list_optimizers(callback): +def on_list_optimizers(callback, *, name=None): """register a function to be called when UI is making a list of cross attention optimization options. The function will be called with one argument, a list, and shall add objects of type modules.sd_hijack_optimizations.SdOptimization to it.""" - add_callback(callback_map['callbacks_list_optimizers'], callback) + add_callback(callback_map['callbacks_list_optimizers'], callback, name=name, category='list_optimizers') -def on_list_unets(callback): +def on_list_unets(callback, *, name=None): """register a function to be called when UI is making a list of alternative options for unet. The function will be called with one argument, a list, and shall add objects of type modules.sd_unet.SdUnetOption to it.""" - add_callback(callback_map['callbacks_list_unets'], callback) + add_callback(callback_map['callbacks_list_unets'], callback, name=name, category='list_unets') -def on_before_token_counter(callback): +def on_before_token_counter(callback, *, name=None): """register a function to be called when UI is counting tokens for a prompt. The function will be called with one argument of type BeforeTokenCounterParams, and should modify its fields if necessary.""" - add_callback(callback_map['callbacks_before_token_counter'], callback) - - -def on_setting_updated_subscriber(subscriber_params): - """register a function to be called after settings update. `subscriber_params` - should contain necessary fields to register an gradio event handler. Necessary - fields are ["fn", "outputs", "inputs"]. - Setting name and setting value after update will be append to inputs. So be - sure to handle these extra params when defining the callback function. - """ - event_subscriber_map['callbacks_setting_updated'].append(subscriber_params) - + add_callback(callback_map['callbacks_before_token_counter'], callback, name=name, category='before_token_counter') diff --git a/modules/script_loading.py b/modules/script_loading.py index 0d55f1932..cccb30966 100644 --- a/modules/script_loading.py +++ b/modules/script_loading.py @@ -4,11 +4,15 @@ from modules import errors +loaded_scripts = {} + + 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) module_spec.loader.exec_module(module) + loaded_scripts[path] = module return module diff --git a/modules/scripts.py b/modules/scripts.py index 79c5cb767..685403bcb 100644 --- a/modules/scripts.py +++ b/modules/scripts.py @@ -7,7 +7,9 @@ import gradio as gr -from modules import shared, paths, script_callbacks, extensions, script_loading, scripts_postprocessing, errors, timer +from modules import shared, paths, script_callbacks, extensions, script_loading, scripts_postprocessing, errors, timer, util + +topological_sort = util.topological_sort AlwaysVisible = object() @@ -28,8 +30,9 @@ def __init__(self, samples): self.samples = samples class PostprocessImageArgs: - def __init__(self, image): + def __init__(self, image, index): self.image = image + self.index = index class PostProcessMaskOverlayArgs: def __init__(self, index, mask_for_overlay, overlay_image): @@ -92,7 +95,7 @@ class Script: """If true, the script setup will only be run in Gradio UI, not in API""" controls = None - """A list of controls retured by the ui().""" + """A list of controls returned by the ui().""" sorting_priority = 0 """Larger number will appear downwards in the UI.""" @@ -112,7 +115,7 @@ def ui(self, is_img2img): def show(self, is_img2img): """ - is_img2img is True if this function is called for the img2img interface, and Fasle otherwise + is_img2img is True if this function is called for the img2img interface, and False otherwise This function should return: - False if the script should not be shown in UI at all @@ -141,7 +144,6 @@ def setup(self, p, *args): """ pass - def before_process(self, p, *args): """ This function is called very early during processing begins for AlwaysVisible scripts. @@ -194,7 +196,6 @@ def process_before_every_sampling(self, p, *args, **kwargs): Similar to process(), called before every sampling. If you use high-res fix, this will be called two times. """ - pass def process_batch(self, p, *args, **kwargs): @@ -362,6 +363,9 @@ def elem_id(self, item_id): return f'{tabname}{item_id}' + def show(self, is_img2img): + return AlwaysVisible + current_basedir = paths.script_path @@ -380,29 +384,6 @@ def basedir(): postprocessing_scripts_data = [] ScriptClassData = namedtuple("ScriptClassData", ["script_class", "path", "basedir", "module"]) -def topological_sort(dependencies): - """Accepts a dictionary mapping name to its dependencies, returns a list of names ordered according to dependencies. - Ignores errors relating to missing dependeencies or circular dependencies - """ - - visited = {} - result = [] - - def inner(name): - visited[name] = True - - for dep in dependencies.get(name, []): - if dep in dependencies and dep not in visited: - inner(dep) - - result.append(name) - - for depname in dependencies: - if depname not in visited: - inner(depname) - - return result - @dataclass class ScriptWithDependencies: @@ -579,6 +560,25 @@ def __init__(self): self.paste_field_names = [] self.inputs = [None] + self.callback_map = {} + self.callback_names = [ + 'before_process', + 'process', + 'before_process_batch', + 'after_extra_networks_activate', + 'process_batch', + 'postprocess', + 'postprocess_batch', + 'postprocess_batch_list', + 'post_sample', + 'on_mask_blend', + 'postprocess_image', + 'postprocess_maskoverlay', + 'postprocess_image_after_composite', + 'before_component', + 'after_component', + ] + self.on_before_component_elem_id = {} """dict of callbacks to be called before an element is created; key=elem_id, value=list of callbacks""" @@ -617,6 +617,8 @@ def initialize_scripts(self, is_img2img): self.scripts.append(script) self.selectable_scripts.append(script) + self.callback_map.clear() + self.apply_on_before_component_callbacks() def apply_on_before_component_callbacks(self): @@ -756,12 +758,17 @@ def init_field(title): def onload_script_visibility(params): title = params.get('Script', None) if title: - title_index = self.titles.index(title) - visibility = title_index == self.script_load_ctr - self.script_load_ctr = (self.script_load_ctr + 1) % len(self.titles) - return gr.update(visible=visibility) - else: - return gr.update(visible=False) + try: + title_index = self.titles.index(title) + visibility = title_index == self.script_load_ctr + self.script_load_ctr = (self.script_load_ctr + 1) % len(self.titles) + return gr.update(visible=visibility) + except ValueError: + params['Script'] = None + massage = f'Cannot find Script: "{title}"' + print(massage) + gr.Warning(massage) + return gr.update(visible=False) self.infotext_fields.append((dropdown, lambda x: gr.update(value=x.get('Script', 'None')))) self.infotext_fields.extend([(script.group, onload_script_visibility) for script in self.selectable_scripts]) @@ -788,8 +795,42 @@ def run(self, p, *args): return processed + def list_scripts_for_method(self, method_name): + if method_name in ('before_component', 'after_component'): + return self.scripts + else: + return self.alwayson_scripts + + def create_ordered_callbacks_list(self, method_name, *, enable_user_sort=True): + script_list = self.list_scripts_for_method(method_name) + category = f'script_{method_name}' + callbacks = [] + + for script in script_list: + if getattr(script.__class__, method_name, None) == getattr(Script, method_name, None): + continue + + script_callbacks.add_callback(callbacks, script, category=category, name=script.__class__.__name__, filename=script.filename) + + return script_callbacks.sort_callbacks(category, callbacks, enable_user_sort=enable_user_sort) + + def ordered_callbacks(self, method_name, *, enable_user_sort=True): + script_list = self.list_scripts_for_method(method_name) + category = f'script_{method_name}' + + scrpts_len, callbacks = self.callback_map.get(category, (-1, None)) + + if callbacks is None or scrpts_len != len(script_list): + callbacks = self.create_ordered_callbacks_list(method_name, enable_user_sort=enable_user_sort) + self.callback_map[category] = len(script_list), callbacks + + return callbacks + + def ordered_scripts(self, method_name): + return [x.callback for x in self.ordered_callbacks(method_name)] + def before_process(self, p): - for script in self.alwayson_scripts: + for script in self.ordered_scripts('before_process'): try: script_args = p.script_args[script.args_from:script.args_to] script.before_process(p, *script_args) @@ -797,23 +838,39 @@ def before_process(self, p): errors.report(f"Error running before_process: {script.filename}", exc_info=True) def process(self, p): - for script in self.alwayson_scripts: + for script in self.ordered_scripts('process'): try: script_args = p.script_args[script.args_from:script.args_to] script.process(p, *script_args) except Exception: errors.report(f"Error running process: {script.filename}", exc_info=True) + def process_before_every_sampling(self, p, **kwargs): + for script in self.ordered_scripts('process_before_every_sampling'): + try: + script_args = p.script_args[script.args_from:script.args_to] + script.process_before_every_sampling(p, *script_args, **kwargs) + except Exception: + errors.report(f"Error running process_before_every_sampling: {script.filename}", exc_info=True) + def before_process_batch(self, p, **kwargs): - for script in self.alwayson_scripts: + for script in self.ordered_scripts('before_process_batch'): try: script_args = p.script_args[script.args_from:script.args_to] script.before_process_batch(p, *script_args, **kwargs) except Exception: errors.report(f"Error running before_process_batch: {script.filename}", exc_info=True) + def before_process_init_images(self, p, pp, **kwargs): + for script in self.ordered_scripts('before_process_init_images'): + try: + script_args = p.script_args[script.args_from:script.args_to] + script.before_process_init_images(p, pp, *script_args, **kwargs) + except Exception: + errors.report(f"Error running before_process_init_images: {script.filename}", exc_info=True) + def after_extra_networks_activate(self, p, **kwargs): - for script in self.alwayson_scripts: + for script in self.ordered_scripts('after_extra_networks_activate'): try: script_args = p.script_args[script.args_from:script.args_to] script.after_extra_networks_activate(p, *script_args, **kwargs) @@ -821,7 +878,7 @@ def after_extra_networks_activate(self, p, **kwargs): errors.report(f"Error running after_extra_networks_activate: {script.filename}", exc_info=True) def process_batch(self, p, **kwargs): - for script in self.alwayson_scripts: + for script in self.ordered_scripts('process_batch'): try: script_args = p.script_args[script.args_from:script.args_to] script.process_batch(p, *script_args, **kwargs) @@ -837,7 +894,7 @@ def process_before_every_sampling(self, p, **kwargs): errors.report(f"Error running process_before_every_sampling: {script.filename}", exc_info=True) def postprocess(self, p, processed): - for script in self.alwayson_scripts: + for script in self.ordered_scripts('postprocess'): try: script_args = p.script_args[script.args_from:script.args_to] script.postprocess(p, processed, *script_args) @@ -845,7 +902,7 @@ def postprocess(self, p, processed): errors.report(f"Error running postprocess: {script.filename}", exc_info=True) def postprocess_batch(self, p, images, **kwargs): - for script in self.alwayson_scripts: + for script in self.ordered_scripts('postprocess_batch'): try: script_args = p.script_args[script.args_from:script.args_to] script.postprocess_batch(p, *script_args, images=images, **kwargs) @@ -853,7 +910,7 @@ def postprocess_batch(self, p, images, **kwargs): errors.report(f"Error running postprocess_batch: {script.filename}", exc_info=True) def postprocess_batch_list(self, p, pp: PostprocessBatchListArgs, **kwargs): - for script in self.alwayson_scripts: + for script in self.ordered_scripts('postprocess_batch_list'): try: script_args = p.script_args[script.args_from:script.args_to] script.postprocess_batch_list(p, pp, *script_args, **kwargs) @@ -861,7 +918,7 @@ def postprocess_batch_list(self, p, pp: PostprocessBatchListArgs, **kwargs): errors.report(f"Error running postprocess_batch_list: {script.filename}", exc_info=True) def post_sample(self, p, ps: PostSampleArgs): - for script in self.alwayson_scripts: + for script in self.ordered_scripts('post_sample'): try: script_args = p.script_args[script.args_from:script.args_to] script.post_sample(p, ps, *script_args) @@ -869,7 +926,7 @@ def post_sample(self, p, ps: PostSampleArgs): errors.report(f"Error running post_sample: {script.filename}", exc_info=True) def on_mask_blend(self, p, mba: MaskBlendArgs): - for script in self.alwayson_scripts: + for script in self.ordered_scripts('on_mask_blend'): try: script_args = p.script_args[script.args_from:script.args_to] script.on_mask_blend(p, mba, *script_args) @@ -877,7 +934,7 @@ def on_mask_blend(self, p, mba: MaskBlendArgs): errors.report(f"Error running post_sample: {script.filename}", exc_info=True) def postprocess_image(self, p, pp: PostprocessImageArgs): - for script in self.alwayson_scripts: + for script in self.ordered_scripts('postprocess_image'): try: script_args = p.script_args[script.args_from:script.args_to] script.postprocess_image(p, pp, *script_args) @@ -885,7 +942,7 @@ def postprocess_image(self, p, pp: PostprocessImageArgs): errors.report(f"Error running postprocess_image: {script.filename}", exc_info=True) def postprocess_maskoverlay(self, p, ppmo: PostProcessMaskOverlayArgs): - for script in self.alwayson_scripts: + for script in self.ordered_scripts('postprocess_maskoverlay'): try: script_args = p.script_args[script.args_from:script.args_to] script.postprocess_maskoverlay(p, ppmo, *script_args) @@ -893,7 +950,7 @@ def postprocess_maskoverlay(self, p, ppmo: PostProcessMaskOverlayArgs): errors.report(f"Error running postprocess_image: {script.filename}", exc_info=True) def postprocess_image_after_composite(self, p, pp: PostprocessImageArgs): - for script in self.alwayson_scripts: + for script in self.ordered_scripts('postprocess_image_after_composite'): try: script_args = p.script_args[script.args_from:script.args_to] script.postprocess_image_after_composite(p, pp, *script_args) @@ -907,7 +964,7 @@ def before_component(self, component, **kwargs): except Exception: errors.report(f"Error running on_before_component: {script.filename}", exc_info=True) - for script in self.scripts: + for script in self.ordered_scripts('before_component'): try: script.before_component(component, **kwargs) except Exception: @@ -920,7 +977,7 @@ def after_component(self, component, **kwargs): except Exception: errors.report(f"Error running on_after_component: {script.filename}", exc_info=True) - for script in self.scripts: + for script in self.ordered_scripts('after_component'): try: script.after_component(component, **kwargs) except Exception: @@ -948,7 +1005,7 @@ def reload_sources(self, cache): self.scripts[si].args_to = args_to def before_hr(self, p): - for script in self.alwayson_scripts: + for script in self.ordered_scripts('before_hr'): try: script_args = p.script_args[script.args_from:script.args_to] script.before_hr(p, *script_args) @@ -956,7 +1013,7 @@ def before_hr(self, p): errors.report(f"Error running before_hr: {script.filename}", exc_info=True) def setup_scrips(self, p, *, is_ui=True): - for script in self.alwayson_scripts: + for script in self.ordered_scripts('setup'): if not is_ui and script.setup_for_ui_only: continue diff --git a/modules/scripts_postprocessing.py b/modules/scripts_postprocessing.py index 901cad080..4b3b7afda 100644 --- a/modules/scripts_postprocessing.py +++ b/modules/scripts_postprocessing.py @@ -143,6 +143,7 @@ def scripts_in_preferred_order(self): self.initialize_scripts(modules.scripts.postprocessing_scripts_data) scripts_order = shared.opts.postprocessing_operation_order + scripts_filter_out = set(shared.opts.postprocessing_disable_in_extras) def script_score(name): for i, possible_match in enumerate(scripts_order): @@ -151,9 +152,10 @@ def script_score(name): return len(self.scripts) - script_scores = {script.name: (script_score(script.name), script.order, script.name, original_index) for original_index, script in enumerate(self.scripts)} + filtered_scripts = [script for script in self.scripts if script.name not in scripts_filter_out] + script_scores = {script.name: (script_score(script.name), script.order, script.name, original_index) for original_index, script in enumerate(filtered_scripts)} - return sorted(self.scripts, key=lambda x: script_scores[x.name]) + return sorted(filtered_scripts, key=lambda x: script_scores[x.name]) def setup_ui(self): inputs = [] diff --git a/modules/sd_emphasis.py b/modules/sd_emphasis.py index 654817b60..49ef1a6ac 100644 --- a/modules/sd_emphasis.py +++ b/modules/sd_emphasis.py @@ -35,7 +35,7 @@ class EmphasisIgnore(Emphasis): class EmphasisOriginal(Emphasis): name = "Original" - description = "the orginal emphasis implementation" + description = "the original emphasis implementation" def after_transformers(self): original_mean = self.z.mean() @@ -48,7 +48,7 @@ def after_transformers(self): class EmphasisOriginalNoNorm(EmphasisOriginal): name = "No norm" - description = "same as orginal, but without normalization (seems to work better for SDXL)" + description = "same as original, but without normalization (seems to work better for SDXL)" def after_transformers(self): self.z = self.z * self.multipliers.reshape(self.multipliers.shape + (1,)).expand(self.z.shape) diff --git a/modules/sd_hijack.py b/modules/sd_hijack.py index e55cd2174..8050b2786 100644 --- a/modules/sd_hijack.py +++ b/modules/sd_hijack.py @@ -185,13 +185,28 @@ def forward(self, input_ids): vec = embedding.vec[self.textual_inversion_key] if isinstance(embedding.vec, dict) else embedding.vec emb = devices.cond_cast_unet(vec) emb_len = min(tensor.shape[0] - offset - 1, emb.shape[0]) - tensor = torch.cat([tensor[0:offset + 1], emb[0:emb_len], tensor[offset + 1 + emb_len:]]) + tensor = torch.cat([tensor[0:offset + 1], emb[0:emb_len], tensor[offset + 1 + emb_len:]]).to(dtype=inputs_embeds.dtype) vecs.append(tensor) return torch.stack(vecs) +class TextualInversionEmbeddings(torch.nn.Embedding): + def __init__(self, num_embeddings: int, embedding_dim: int, textual_inversion_key='clip_l', **kwargs): + super().__init__(num_embeddings, embedding_dim, **kwargs) + + self.embeddings = model_hijack + self.textual_inversion_key = textual_inversion_key + + @property + def wrapped(self): + return super().forward + + def forward(self, input_ids): + return EmbeddingsWithFixes.forward(self, input_ids) + + def add_circular_option_to_conv_2d(): conv2d_constructor = torch.nn.Conv2d.__init__ diff --git a/modules/sd_hijack_clip.py b/modules/sd_hijack_clip.py index 98350ac43..a479148fc 100644 --- a/modules/sd_hijack_clip.py +++ b/modules/sd_hijack_clip.py @@ -23,28 +23,25 @@ def __init__(self): PromptChunkFix = namedtuple('PromptChunkFix', ['offset', 'embedding']) """An object of this type is a marker showing that textual inversion embedding's vectors have to placed at offset in the prompt -chunk. Thos objects are found in PromptChunk.fixes and, are placed into FrozenCLIPEmbedderWithCustomWordsBase.hijack.fixes, and finally +chunk. Those objects are found in PromptChunk.fixes and, are placed into FrozenCLIPEmbedderWithCustomWordsBase.hijack.fixes, and finally are applied by sd_hijack.EmbeddingsWithFixes's forward function.""" -class FrozenCLIPEmbedderWithCustomWordsBase(torch.nn.Module): - """A pytorch module that is a wrapper for FrozenCLIPEmbedder module. it enhances FrozenCLIPEmbedder, making it possible to - have unlimited prompt length and assign weights to tokens in prompt. - """ - - def __init__(self, wrapped, hijack): +class TextConditionalModel(torch.nn.Module): + def __init__(self): super().__init__() - self.wrapped = wrapped - """Original FrozenCLIPEmbedder module; can also be FrozenOpenCLIPEmbedder or xlmr.BertSeriesModelWithTransformation, - depending on model.""" - - self.hijack: sd_hijack.StableDiffusionModelHijack = hijack + self.hijack = sd_hijack.model_hijack self.chunk_length = 75 - self.is_trainable = getattr(wrapped, 'is_trainable', False) - self.input_key = getattr(wrapped, 'input_key', 'txt') - self.legacy_ucg_val = None + self.is_trainable = False + self.input_key = 'txt' + self.return_pooled = False + + self.comma_token = None + self.id_start = None + self.id_end = None + self.id_pad = None def empty_chunk(self): """creates an empty PromptChunk and returns it""" @@ -66,7 +63,7 @@ def tokenize(self, texts): def encode_with_transformers(self, tokens): """ - converts a batch of token ids (in python lists) into a single tensor with numeric respresentation of those tokens; + converts a batch of token ids (in python lists) into a single tensor with numeric representation of those tokens; All python lists with tokens are assumed to have same length, usually 77. if input is a list with B elements and each element has T tokens, expected output shape is (B, T, C), where C depends on model - can be 768 and 1024. @@ -136,7 +133,7 @@ def next_chunk(is_last=False): if token == self.comma_token: last_comma = len(chunk.tokens) - # this is when we are at the end of alloted 75 tokens for the current chunk, and the current token is not a comma. opts.comma_padding_backtrack + # this is when we are at the end of allotted 75 tokens for the current chunk, and the current token is not a comma. opts.comma_padding_backtrack # is a setting that specifies that if there is a comma nearby, the text after the comma should be moved out of this chunk and into the next. elif opts.comma_padding_backtrack != 0 and len(chunk.tokens) == self.chunk_length and last_comma != -1 and len(chunk.tokens) - last_comma <= opts.comma_padding_backtrack: break_location = last_comma + 1 @@ -206,14 +203,10 @@ def forward(self, texts): be a multiple of 77; and C is dimensionality of each token - for SD1 it's 768, for SD2 it's 1024, and for SDXL it's 1280. An example shape returned by this function can be: (2, 77, 768). For SDXL, instead of returning one tensor avobe, it returns a tuple with two: the other one with shape (B, 1280) with pooled values. - Webui usually sends just one text at a time through this function - the only time when texts is an array with more than one elemenet + Webui usually sends just one text at a time through this function - the only time when texts is an array with more than one element is when you do prompt editing: "a picture of a [cat:dog:0.4] eating ice cream" """ - if opts.use_old_emphasis_implementation: - import modules.sd_hijack_clip_old - return modules.sd_hijack_clip_old.forward_old(self, texts) - batch_chunks, token_count = self.process_texts(texts) used_embeddings = {} @@ -230,7 +223,7 @@ def forward(self, texts): for fixes in self.hijack.fixes: for _position, embedding in fixes: used_embeddings[embedding.name] = embedding - + devices.torch_npu_set_device() z = self.process_tokens(tokens, multipliers) zs.append(z) @@ -252,7 +245,7 @@ def forward(self, texts): if any(x for x in texts if "(" in x or "[" in x) and opts.emphasis != "Original": self.hijack.extra_generation_params["Emphasis"] = opts.emphasis - if getattr(self.wrapped, 'return_pooled', False): + if self.return_pooled: return torch.hstack(zs), zs[0].pooled else: return torch.hstack(zs) @@ -292,6 +285,34 @@ def process_tokens(self, remade_batch_tokens, batch_multipliers): return z +class FrozenCLIPEmbedderWithCustomWordsBase(TextConditionalModel): + """A pytorch module that is a wrapper for FrozenCLIPEmbedder module. it enhances FrozenCLIPEmbedder, making it possible to + have unlimited prompt length and assign weights to tokens in prompt. + """ + + def __init__(self, wrapped, hijack): + super().__init__() + + self.hijack = hijack + + self.wrapped = wrapped + """Original FrozenCLIPEmbedder module; can also be FrozenOpenCLIPEmbedder or xlmr.BertSeriesModelWithTransformation, + depending on model.""" + + self.is_trainable = getattr(wrapped, 'is_trainable', False) + self.input_key = getattr(wrapped, 'input_key', 'txt') + self.return_pooled = getattr(self.wrapped, 'return_pooled', False) + + self.legacy_ucg_val = None # for sgm codebase + + def forward(self, texts): + if opts.use_old_emphasis_implementation: + import modules.sd_hijack_clip_old + return modules.sd_hijack_clip_old.forward_old(self, texts) + + return super().forward(texts) + + class FrozenCLIPEmbedderWithCustomWords(FrozenCLIPEmbedderWithCustomWordsBase): def __init__(self, wrapped, hijack): super().__init__(wrapped, hijack) @@ -353,7 +374,9 @@ def __init__(self, wrapped, hijack): def encode_with_transformers(self, tokens): outputs = self.wrapped.transformer(input_ids=tokens, output_hidden_states=self.wrapped.layer == "hidden") - if self.wrapped.layer == "last": + if opts.sdxl_clip_l_skip is True: + z = outputs.hidden_states[-opts.CLIP_stop_at_last_layers] + elif self.wrapped.layer == "last": z = outputs.last_hidden_state else: z = outputs.hidden_states[self.wrapped.layer_idx] diff --git a/modules/sd_hijack_optimizations.py b/modules/sd_hijack_optimizations.py index 7f9e328d0..0269f1f5b 100644 --- a/modules/sd_hijack_optimizations.py +++ b/modules/sd_hijack_optimizations.py @@ -486,7 +486,8 @@ def xformers_attention_forward(self, x, context=None, mask=None, **kwargs): k_in = self.to_k(context_k) v_in = self.to_v(context_v) - q, k, v = (rearrange(t, 'b n (h d) -> b n h d', h=h) for t in (q_in, k_in, v_in)) + q, k, v = (t.reshape(t.shape[0], t.shape[1], h, -1) for t in (q_in, k_in, v_in)) + del q_in, k_in, v_in dtype = q.dtype @@ -497,7 +498,8 @@ def xformers_attention_forward(self, x, context=None, mask=None, **kwargs): out = out.to(dtype) - out = rearrange(out, 'b n h d -> b n (h d)', h=h) + b, n, h, d = out.shape + out = out.reshape(b, n, h * d) return self.to_out(out) diff --git a/modules/sd_hijack_unet.py b/modules/sd_hijack_unet.py index 2101f1a04..b4f03b138 100644 --- a/modules/sd_hijack_unet.py +++ b/modules/sd_hijack_unet.py @@ -1,5 +1,7 @@ import torch from packaging import version +from einops import repeat +import math from modules import devices from modules.sd_hijack_utils import CondFunc @@ -36,7 +38,7 @@ def cat(self, tensors, *args, **kwargs): # Below are monkey patches to enable upcasting a float16 UNet for float32 sampling def apply_model(orig_func, self, x_noisy, t, cond, **kwargs): - + """Always make sure inputs to unet are in correct dtype.""" if isinstance(cond, dict): for y in cond.keys(): if isinstance(cond[y], list): @@ -45,7 +47,59 @@ def apply_model(orig_func, self, x_noisy, t, cond, **kwargs): cond[y] = cond[y].to(devices.dtype_unet) if isinstance(cond[y], torch.Tensor) else cond[y] with devices.autocast(): - return orig_func(self, x_noisy.to(devices.dtype_unet), t.to(devices.dtype_unet), cond, **kwargs).float() + result = orig_func(self, x_noisy.to(devices.dtype_unet), t.to(devices.dtype_unet), cond, **kwargs) + if devices.unet_needs_upcast: + return result.float() + else: + return result + + +# Monkey patch to create timestep embed tensor on device, avoiding a block. +def timestep_embedding(_, timesteps, dim, max_period=10000, repeat_only=False): + """ + Create sinusoidal timestep embeddings. + :param timesteps: a 1-D Tensor of N indices, one per batch element. + These may be fractional. + :param dim: the dimension of the output. + :param max_period: controls the minimum frequency of the embeddings. + :return: an [N x dim] Tensor of positional embeddings. + """ + if not repeat_only: + half = dim // 2 + freqs = torch.exp( + -math.log(max_period) * torch.arange(start=0, end=half, dtype=torch.float32, device=timesteps.device) / half + ) + args = timesteps[:, None].float() * freqs[None] + embedding = torch.cat([torch.cos(args), torch.sin(args)], dim=-1) + if dim % 2: + embedding = torch.cat([embedding, torch.zeros_like(embedding[:, :1])], dim=-1) + else: + embedding = repeat(timesteps, 'b -> b d', d=dim) + return embedding + + +# Monkey patch to SpatialTransformer removing unnecessary contiguous calls. +# Prevents a lot of unnecessary aten::copy_ calls +def spatial_transformer_forward(_, self, x: torch.Tensor, context=None): + # note: if no context is given, cross-attention defaults to self-attention + if not isinstance(context, list): + context = [context] + b, c, h, w = x.shape + x_in = x + x = self.norm(x) + if not self.use_linear: + x = self.proj_in(x) + x = x.permute(0, 2, 3, 1).reshape(b, h * w, c) + if self.use_linear: + x = self.proj_in(x) + for i, block in enumerate(self.transformer_blocks): + x = block(x, context=context[i]) + if self.use_linear: + x = self.proj_out(x) + x = x.view(b, h, w, c).permute(0, 3, 1, 2) + if not self.use_linear: + x = self.proj_out(x) + return x + x_in class GELUHijack(torch.nn.GELU, torch.nn.Module): @@ -64,12 +118,15 @@ def hijack_ddpm_edit(): if not ddpm_edit_hijack: CondFunc('modules.models.diffusion.ddpm_edit.LatentDiffusion.decode_first_stage', first_stage_sub, first_stage_cond) CondFunc('modules.models.diffusion.ddpm_edit.LatentDiffusion.encode_first_stage', first_stage_sub, first_stage_cond) - ddpm_edit_hijack = CondFunc('modules.models.diffusion.ddpm_edit.LatentDiffusion.apply_model', apply_model, unet_needs_upcast) + ddpm_edit_hijack = CondFunc('modules.models.diffusion.ddpm_edit.LatentDiffusion.apply_model', apply_model) unet_needs_upcast = lambda *args, **kwargs: devices.unet_needs_upcast CondFunc('ldm.models.diffusion.ddpm.LatentDiffusion.apply_model', apply_model, unet_needs_upcast) +CondFunc('ldm.modules.diffusionmodules.openaimodel.timestep_embedding', timestep_embedding) +CondFunc('ldm.modules.attention.SpatialTransformer.forward', spatial_transformer_forward) CondFunc('ldm.modules.diffusionmodules.openaimodel.timestep_embedding', lambda orig_func, timesteps, *args, **kwargs: orig_func(timesteps, *args, **kwargs).to(torch.float32 if timesteps.dtype == torch.int64 else devices.dtype_unet), unet_needs_upcast) + if version.parse(torch.__version__) <= version.parse("1.13.2") or torch.cuda.is_available(): CondFunc('ldm.modules.diffusionmodules.util.GroupNorm32.forward', lambda orig_func, self, *args, **kwargs: orig_func(self.float(), *args, **kwargs), unet_needs_upcast) CondFunc('ldm.modules.attention.GEGLU.forward', lambda orig_func, self, x: orig_func(self.float(), x.float()).to(devices.dtype_unet), unet_needs_upcast) @@ -81,5 +138,17 @@ def hijack_ddpm_edit(): CondFunc('ldm.models.diffusion.ddpm.LatentDiffusion.encode_first_stage', first_stage_sub, first_stage_cond) CondFunc('ldm.models.diffusion.ddpm.LatentDiffusion.get_first_stage_encoding', lambda orig_func, *args, **kwargs: orig_func(*args, **kwargs).float(), first_stage_cond) -CondFunc('sgm.modules.diffusionmodules.wrappers.OpenAIWrapper.forward', apply_model, unet_needs_upcast) -CondFunc('sgm.modules.diffusionmodules.openaimodel.timestep_embedding', lambda orig_func, timesteps, *args, **kwargs: orig_func(timesteps, *args, **kwargs).to(torch.float32 if timesteps.dtype == torch.int64 else devices.dtype_unet), unet_needs_upcast) +CondFunc('ldm.models.diffusion.ddpm.LatentDiffusion.apply_model', apply_model) +CondFunc('sgm.modules.diffusionmodules.wrappers.OpenAIWrapper.forward', apply_model) + + +def timestep_embedding_cast_result(orig_func, timesteps, *args, **kwargs): + if devices.unet_needs_upcast and timesteps.dtype == torch.int64: + dtype = torch.float32 + else: + dtype = devices.dtype_unet + return orig_func(timesteps, *args, **kwargs).to(dtype=dtype) + + +CondFunc('ldm.modules.diffusionmodules.openaimodel.timestep_embedding', timestep_embedding_cast_result) +CondFunc('sgm.modules.diffusionmodules.openaimodel.timestep_embedding', timestep_embedding_cast_result) diff --git a/modules/sd_hijack_utils.py b/modules/sd_hijack_utils.py index 79bf6e468..546f2eda4 100644 --- a/modules/sd_hijack_utils.py +++ b/modules/sd_hijack_utils.py @@ -1,7 +1,11 @@ import importlib + +always_true_func = lambda *args, **kwargs: True + + class CondFunc: - def __new__(cls, orig_func, sub_func, cond_func): + def __new__(cls, orig_func, sub_func, cond_func=always_true_func): self = super(CondFunc, cls).__new__(cls) if isinstance(orig_func, str): func_path = orig_func.split('.') @@ -20,13 +24,13 @@ def __new__(cls, orig_func, sub_func, cond_func): print(f"Warning: Failed to resolve {orig_func} for CondFunc hijack") pass self.__init__(orig_func, sub_func, cond_func) - return lambda *args, **kwargs: self(*args, **kwargs) - def __init__(self, orig_func, sub_func, cond_func): - self.__orig_func = orig_func - self.__sub_func = sub_func - self.__cond_func = cond_func - def __call__(self, *args, **kwargs): - if not self.__cond_func or self.__cond_func(self.__orig_func, *args, **kwargs): - return self.__sub_func(self.__orig_func, *args, **kwargs) - else: - return self.__orig_func(*args, **kwargs) + return lambda *args, **kwargs: self(*args, **kwargs) + def __init__(self, orig_func, sub_func, cond_func): + self.__orig_func = orig_func + self.__sub_func = sub_func + self.__cond_func = cond_func + def __call__(self, *args, **kwargs): + if not self.__cond_func or self.__cond_func(self.__orig_func, *args, **kwargs): + return self.__sub_func(self.__orig_func, *args, **kwargs) + else: + return self.__orig_func(*args, **kwargs) diff --git a/modules/sd_models.py b/modules/sd_models.py index 185062dbb..538f5577d 100644 --- a/modules/sd_models.py +++ b/modules/sd_models.py @@ -1,19 +1,18 @@ import collections -import os.path +import importlib +import os import sys import threading +import enum import torch import re import safetensors.torch from omegaconf import OmegaConf, ListConfig -from os import mkdir from urllib import request import ldm.modules.midas as midas import gc -from ldm.util import instantiate_from_config - from modules import paths, shared, modelloader, devices, script_callbacks, sd_vae, sd_disable_initialization, errors, hashes, sd_models_config, sd_unet, sd_models_xl, cache, extra_networks, processing, lowvram, sd_hijack, patches from modules.timer import Timer import numpy as np @@ -33,6 +32,14 @@ checkpoints_loaded = collections.OrderedDict() +class ModelType(enum.Enum): + SD1 = 1 + SD2 = 2 + SDXL = 3 + SSD = 4 + SD3 = 5 + + def replace_key(d, key, new_key, value): keys = list(d.keys()) @@ -155,6 +162,7 @@ def list_models(): cmd_ckpt = shared.cmd_opts.ckpt if shared.cmd_opts.no_download_sd_model or cmd_ckpt != shared.sd_model_file or os.path.exists(cmd_ckpt): model_url = None + expected_sha256 = None else: model_url = "https://huggingface.co/lllyasviel/fav_models/resolve/main/fav/realisticVisionV51_v51VAE.safetensors" @@ -286,17 +294,21 @@ def read_metadata_from_safetensors(filename): json_start = file.read(2) assert metadata_len > 2 and json_start in (b'{"', b"{'"), f"{filename} is not a safetensors file" - json_data = json_start + file.read(metadata_len-2) - json_obj = json.loads(json_data) res = {} - for k, v in json_obj.get("__metadata__", {}).items(): - res[k] = v - if isinstance(v, str) and v[0:1] == '{': - try: - res[k] = json.loads(v) - except Exception: - pass + + try: + json_data = json_start + file.read(metadata_len-2) + json_obj = json.loads(json_data) + for k, v in json_obj.get("__metadata__", {}).items(): + res[k] = v + if isinstance(v, str) and v[0:1] == '{': + try: + res[k] = json.loads(v) + except Exception: + pass + except Exception: + errors.report(f"Error reading metadata from file: {filename}", exc_info=True) return res @@ -368,42 +380,39 @@ def check_fp8(model): return enable_fp8 -def load_model_weights(model, checkpoint_info: CheckpointInfo, state_dict, timer): - sd_model_hash = checkpoint_info.calculate_shorthash() - timer.record("calculate hash") - - if not SkipWritingToConfig.skip: - shared.opts.data["sd_model_checkpoint"] = checkpoint_info.title - - if state_dict is None: - state_dict = get_checkpoint_state_dict(checkpoint_info, timer) +def set_model_type(model, state_dict): + model.is_sd1 = False + model.is_sd2 = False + model.is_sdxl = False + model.is_ssd = False + model.is_sd3 = False - if shared.opts.sd_checkpoint_cache > 0: - # cache newly loaded model - checkpoints_loaded[checkpoint_info] = state_dict.copy() + if "model.diffusion_model.x_embedder.proj.weight" in state_dict: + model.is_sd3 = True + model.model_type = ModelType.SD3 + elif hasattr(model, 'conditioner'): + model.is_sdxl = True - model.load_state_dict(state_dict, strict=False) - timer.record("apply weights to model") - - del state_dict + if 'model.diffusion_model.middle_block.1.transformer_blocks.0.attn1.to_q.weight' not in state_dict.keys(): + model.is_ssd = True + model.model_type = ModelType.SSD + else: + model.model_type = ModelType.SDXL + elif hasattr(model.cond_stage_model, 'model'): + model.is_sd2 = True + model.model_type = ModelType.SD2 + else: + model.is_sd1 = True + model.model_type = ModelType.SD1 - # clean up cache if limit is reached - while len(checkpoints_loaded) > shared.opts.sd_checkpoint_cache: - checkpoints_loaded.popitem(last=False) - model.sd_model_hash = sd_model_hash - model.sd_model_checkpoint = checkpoint_info.filename - model.sd_checkpoint_info = checkpoint_info - shared.opts.data["sd_checkpoint_hash"] = checkpoint_info.sha256 +def set_model_fields(model): + if not hasattr(model, 'latent_channels'): + model.latent_channels = 4 - if hasattr(model, 'logvar'): - model.logvar = model.logvar.to(devices.device) # fix for training - sd_vae.delete_base_vae() - sd_vae.clear_loaded_vae() - vae_file, vae_source = sd_vae.resolve_vae(checkpoint_info.filename).tuple() - sd_vae.load_vae(model, vae_file, vae_source) - timer.record("load VAE") +def load_model_weights(model, checkpoint_info: CheckpointInfo, state_dict, timer): + return def enable_midas_autodownload(): @@ -438,7 +447,7 @@ def load_model_wrapper(model_type): path = midas.api.ISL_PATHS[model_type] if not os.path.exists(path): if not os.path.exists(midas_path): - mkdir(midas_path) + os.mkdir(midas_path) print(f"Downloading midas model weights for {model_type} to {path}") request.urlretrieve(midas_urls[model_type], path) @@ -463,25 +472,76 @@ def patched_register_schedule(*args, **kwargs): original_register_schedule = patches.patch(__name__, ldm.models.diffusion.ddpm.DDPM, 'register_schedule', patched_register_schedule) -def repair_config(sd_config): - +def repair_config(sd_config, state_dict=None): if not hasattr(sd_config.model.params, "use_ema"): sd_config.model.params.use_ema = False if hasattr(sd_config.model.params, 'unet_config'): if shared.cmd_opts.no_half: sd_config.model.params.unet_config.params.use_fp16 = False - elif shared.cmd_opts.upcast_sampling: + elif shared.cmd_opts.upcast_sampling or shared.cmd_opts.precision == "half": sd_config.model.params.unet_config.params.use_fp16 = True - if getattr(sd_config.model.params.first_stage_config.params.ddconfig, "attn_type", None) == "vanilla-xformers" and not shared.xformers_available: - sd_config.model.params.first_stage_config.params.ddconfig.attn_type = "vanilla" + if hasattr(sd_config.model.params, 'first_stage_config'): + if getattr(sd_config.model.params.first_stage_config.params.ddconfig, "attn_type", None) == "vanilla-xformers" and not shared.xformers_available: + sd_config.model.params.first_stage_config.params.ddconfig.attn_type = "vanilla" # For UnCLIP-L, override the hardcoded karlo directory if hasattr(sd_config.model.params, "noise_aug_config") and hasattr(sd_config.model.params.noise_aug_config.params, "clip_stats_path"): karlo_path = os.path.join(paths.models_path, 'karlo') sd_config.model.params.noise_aug_config.params.clip_stats_path = sd_config.model.params.noise_aug_config.params.clip_stats_path.replace("checkpoints/karlo_models", karlo_path) + # Do not use checkpoint for inference. + # This helps prevent extra performance overhead on checking parameters. + # The perf overhead is about 100ms/it on 4090 for SDXL. + if hasattr(sd_config.model.params, "network_config"): + sd_config.model.params.network_config.params.use_checkpoint = False + if hasattr(sd_config.model.params, "unet_config"): + sd_config.model.params.unet_config.params.use_checkpoint = False + + + +def rescale_zero_terminal_snr_abar(alphas_cumprod): + alphas_bar_sqrt = alphas_cumprod.sqrt() + + # Store old values. + alphas_bar_sqrt_0 = alphas_bar_sqrt[0].clone() + alphas_bar_sqrt_T = alphas_bar_sqrt[-1].clone() + + # Shift so the last timestep is zero. + alphas_bar_sqrt -= (alphas_bar_sqrt_T) + + # Scale so the first timestep is back to the old value. + alphas_bar_sqrt *= alphas_bar_sqrt_0 / (alphas_bar_sqrt_0 - alphas_bar_sqrt_T) + + # Convert alphas_bar_sqrt to betas + alphas_bar = alphas_bar_sqrt ** 2 # Revert sqrt + alphas_bar[-1] = 4.8973451890853435e-08 + return alphas_bar + + +def apply_alpha_schedule_override(sd_model, p=None): + """ + Applies an override to the alpha schedule of the model according to settings. + - downcasts the alpha schedule to half precision + - rescales the alpha schedule to have zero terminal SNR + """ + + if not hasattr(sd_model, 'alphas_cumprod') or not hasattr(sd_model, 'alphas_cumprod_original'): + return + + sd_model.alphas_cumprod = sd_model.alphas_cumprod_original.to(shared.device) + + if opts.use_downcasted_alpha_bar: + if p is not None: + p.extra_generation_params['Downcast alphas_cumprod'] = opts.use_downcasted_alpha_bar + sd_model.alphas_cumprod = sd_model.alphas_cumprod.half().to(shared.device) + + if opts.sd_noise_schedule == "Zero Terminal SNR": + if p is not None: + p.extra_generation_params['Noise Schedule'] = opts.sd_noise_schedule + sd_model.alphas_cumprod = rescale_zero_terminal_snr_abar(sd_model.alphas_cumprod).to(shared.device) + sd1_clip_weight = 'cond_stage_model.transformer.text_model.embeddings.token_embedding.weight' sd2_clip_weight = 'cond_stage_model.model.transformer.resblocks.0.attn.in_proj_weight' @@ -532,11 +592,15 @@ def get_empty_cond(sd_model): p = processing.StableDiffusionProcessingTxt2Img() extra_networks.activate(p, {}) - if hasattr(sd_model, 'conditioner'): + if hasattr(sd_model, 'get_learned_conditioning'): d = sd_model.get_learned_conditioning([""]) - return d['crossattn'] else: - return sd_model.cond_stage_model([""]) + d = sd_model.cond_stage_model([""]) + + if isinstance(d, dict): + d = d['crossattn'] + + return d def send_model_to_cpu(m): @@ -555,6 +619,25 @@ def send_model_to_trash(m): pass +def instantiate_from_config(config, state_dict=None): + constructor = get_obj_from_str(config["target"]) + + params = {**config.get("params", {})} + + if state_dict and "state_dict" in params and params["state_dict"] is None: + params["state_dict"] = state_dict + + return constructor(**params) + + +def get_obj_from_str(string, reload=False): + module, cls = string.rsplit(".", 1) + if reload: + module_imp = importlib.import_module(module) + importlib.reload(module_imp) + return getattr(importlib.import_module(module, package=None), cls) + + def load_model(checkpoint_info=None, already_loaded_state_dict=None): from modules import sd_hijack checkpoint_info = checkpoint_info or select_checkpoint() @@ -585,6 +668,9 @@ def load_model(checkpoint_info=None, already_loaded_state_dict=None): sd_model = forge_loader.load_model_for_a1111(timer=timer, checkpoint_info=checkpoint_info, state_dict=state_dict) sd_model.filename = checkpoint_info.filename + if not SkipWritingToConfig.skip: + shared.opts.data["sd_model_checkpoint"] = checkpoint_info.title + del state_dict # clean up cache if limit is reached diff --git a/modules/sd_models_config.py b/modules/sd_models_config.py index b38137eb5..fb44c5a8d 100644 --- a/modules/sd_models_config.py +++ b/modules/sd_models_config.py @@ -23,6 +23,8 @@ config_instruct_pix2pix = os.path.join(sd_configs_path, "instruct-pix2pix.yaml") config_alt_diffusion = os.path.join(sd_configs_path, "alt-diffusion-inference.yaml") config_alt_diffusion_m18 = os.path.join(sd_configs_path, "alt-diffusion-m18-inference.yaml") +config_sd3 = os.path.join(sd_configs_path, "sd3-inference.yaml") + def is_using_v_parameterization_for_sd2(state_dict): """ @@ -31,11 +33,11 @@ def is_using_v_parameterization_for_sd2(state_dict): import ldm.modules.diffusionmodules.openaimodel - device = devices.cpu + device = devices.device with sd_disable_initialization.DisableInitialization(): unet = ldm.modules.diffusionmodules.openaimodel.UNetModel( - use_checkpoint=True, + use_checkpoint=False, use_fp16=False, image_size=32, in_channels=4, @@ -56,12 +58,13 @@ def is_using_v_parameterization_for_sd2(state_dict): with torch.no_grad(): unet_sd = {k.replace("model.diffusion_model.", ""): v for k, v in state_dict.items() if "model.diffusion_model." in k} unet.load_state_dict(unet_sd, strict=True) - unet.to(device=device, dtype=torch.float) + unet.to(device=device, dtype=devices.dtype_unet) test_cond = torch.ones((1, 2, 1024), device=device) * 0.5 x_test = torch.ones((1, 4, 8, 8), device=device) * 0.5 - out = (unet(x_test, torch.asarray([999], device=device), context=test_cond) - x_test).mean().item() + with devices.autocast(): + out = (unet(x_test, torch.asarray([999], device=device), context=test_cond) - x_test).mean().cpu().item() return out < -1 @@ -71,11 +74,15 @@ def guess_model_config_from_state_dict(sd, filename): diffusion_model_input = sd.get('model.diffusion_model.input_blocks.0.0.weight', None) sd2_variations_weight = sd.get('embedder.model.ln_final.weight', None) + if "model.diffusion_model.x_embedder.proj.weight" in sd: + return config_sd3 + if sd.get('conditioner.embedders.1.model.ln_final.weight', None) is not None: if diffusion_model_input.shape[1] == 9: return config_sdxl_inpainting else: return config_sdxl + if sd.get('conditioner.embedders.0.model.ln_final.weight', None) is not None: return config_sdxl_refiner elif sd.get('depth_model.model.pretrained.act_postprocess3.0.project.0.bias', None) is not None: @@ -99,7 +106,6 @@ def guess_model_config_from_state_dict(sd, filename): if diffusion_model_input.shape[1] == 8: return config_instruct_pix2pix - if sd.get('cond_stage_model.roberta.embeddings.word_embeddings.weight', None) is not None: if sd.get('cond_stage_model.transformation.weight').size()[0] == 1024: return config_alt_diffusion_m18 diff --git a/modules/sd_models_types.py b/modules/sd_models_types.py index f911fbb68..2fce2777b 100644 --- a/modules/sd_models_types.py +++ b/modules/sd_models_types.py @@ -32,3 +32,9 @@ class WebuiSdModel(LatentDiffusion): is_sd1: bool """True if the model's architecture is SD 1.x""" + + is_sd3: bool + """True if the model's architecture is SD 3""" + + latent_channels: int + """number of layer in latent image representation; will be 16 in SD3 and 4 in other version""" diff --git a/modules/sd_models_xl.py b/modules/sd_models_xl.py index 9ea8d6906..4f8c7ee15 100644 --- a/modules/sd_models_xl.py +++ b/modules/sd_models_xl.py @@ -18,8 +18,8 @@ def get_learned_conditioning(self: sgm.models.diffusion.DiffusionEngine, batch: for embedder in self.conditioner.embedders: embedder.ucg_rate = 0.0 - width = getattr(batch, 'width', 1024) - height = getattr(batch, 'height', 1024) + width = getattr(batch, 'width', 1024) or 1024 + height = getattr(batch, 'height', 1024) or 1024 is_negative_prompt = getattr(batch, 'is_negative_prompt', False) aesthetic_score = shared.opts.sdxl_refiner_low_aesthetic_score if is_negative_prompt else shared.opts.sdxl_refiner_high_aesthetic_score diff --git a/modules/sd_samplers.py b/modules/sd_samplers.py index b70679471..9798cefe3 100644 --- a/modules/sd_samplers.py +++ b/modules/sd_samplers.py @@ -1,4 +1,8 @@ -from modules import sd_samplers_kdiffusion, sd_samplers_timesteps, sd_samplers_lcm, shared +from __future__ import annotations + +import functools +import logging +from modules import sd_samplers_kdiffusion, sd_samplers_timesteps, sd_samplers_lcm, shared, sd_samplers_common, sd_schedulers # imports for functions that previously were here and are used by other modules from modules.sd_samplers_common import samples_to_image_grid, sample_to_image # noqa: F401 @@ -12,8 +16,8 @@ ] all_samplers_map = {x.name: x for x in all_samplers} -samplers = [] -samplers_for_img2img = [] +samplers: list[sd_samplers_common.SamplerData] = [] +samplers_for_img2img: list[sd_samplers_common.SamplerData] = [] samplers_map = {} samplers_hidden = {} @@ -59,4 +63,71 @@ def visible_sampler_names(): return [x.name for x in samplers if x.name not in samplers_hidden] +def visible_samplers(): + return [x for x in samplers if x.name not in samplers_hidden] + + +def get_sampler_from_infotext(d: dict): + return get_sampler_and_scheduler(d.get("Sampler"), d.get("Schedule type"))[0] + + +def get_scheduler_from_infotext(d: dict): + return get_sampler_and_scheduler(d.get("Sampler"), d.get("Schedule type"))[1] + + +def get_hr_sampler_and_scheduler(d: dict): + hr_sampler = d.get("Hires sampler", "Use same sampler") + sampler = d.get("Sampler") if hr_sampler == "Use same sampler" else hr_sampler + + hr_scheduler = d.get("Hires schedule type", "Use same scheduler") + scheduler = d.get("Schedule type") if hr_scheduler == "Use same scheduler" else hr_scheduler + + sampler, scheduler = get_sampler_and_scheduler(sampler, scheduler) + + sampler = sampler if sampler != d.get("Sampler") else "Use same sampler" + scheduler = scheduler if scheduler != d.get("Schedule type") else "Use same scheduler" + + return sampler, scheduler + + +def get_hr_sampler_from_infotext(d: dict): + return get_hr_sampler_and_scheduler(d)[0] + + +def get_hr_scheduler_from_infotext(d: dict): + return get_hr_sampler_and_scheduler(d)[1] + + +@functools.cache +def get_sampler_and_scheduler(sampler_name, scheduler_name, *, convert_automatic=True): + default_sampler = samplers[0] + found_scheduler = sd_schedulers.schedulers_map.get(scheduler_name, sd_schedulers.schedulers[0]) + + name = sampler_name or default_sampler.name + + for scheduler in sd_schedulers.schedulers: + name_options = [scheduler.label, scheduler.name, *(scheduler.aliases or [])] + + for name_option in name_options: + if name.endswith(" " + name_option): + found_scheduler = scheduler + name = name[0:-(len(name_option) + 1)] + break + + sampler = all_samplers_map.get(name, default_sampler) + + # revert back to Automatic if it's the default scheduler for the selected sampler + if convert_automatic and sampler.options.get('scheduler', None) == found_scheduler.name: + found_scheduler = sd_schedulers.schedulers[0] + + return sampler.name, found_scheduler.label + + +def fix_p_invalid_sampler_and_scheduler(p): + i_sampler_name, i_scheduler = p.sampler_name, p.scheduler + p.sampler_name, p.scheduler = get_sampler_and_scheduler(p.sampler_name, p.scheduler, convert_automatic=False) + if p.sampler_name != i_sampler_name or i_scheduler != p.scheduler: + logging.warning(f'Sampler Scheduler autocorrection: "{i_sampler_name}" -> "{p.sampler_name}", "{i_scheduler}" -> "{p.scheduler}"') + + set_samplers() diff --git a/modules/sd_samplers_cfg_denoiser.py b/modules/sd_samplers_cfg_denoiser.py index e2d3826b2..7594d97ff 100644 --- a/modules/sd_samplers_cfg_denoiser.py +++ b/modules/sd_samplers_cfg_denoiser.py @@ -1,5 +1,5 @@ import torch -from modules import prompt_parser, devices, sd_samplers_common +from modules import prompt_parser, sd_samplers_common from modules.shared import opts, state import modules.shared as shared @@ -183,7 +183,15 @@ def forward(self, x, sigma, uncond, cond, cond_scale, s_min_uncond, image_cond): cond_scale=cond_scale, cond_composition=cond_composition) if self.mask is not None: - denoised = denoised * self.nmask + self.init_latent * self.mask + blended_latent = denoised * self.nmask + self.init_latent * self.mask + + if self.p.scripts is not None: + from modules import scripts + mba = scripts.MaskBlendArgs(denoised, self.nmask, self.init_latent, self.mask, blended_latent, denoiser=self, sigma=sigma) + self.p.scripts.on_mask_blend(self.p, mba) + blended_latent = mba.blended_latent + + denoised = blended_latent preview = self.sampler.last_latent = denoised sd_samplers_common.store_latent(preview) diff --git a/modules/sd_samplers_common.py b/modules/sd_samplers_common.py index 77ae38124..56d1dff47 100644 --- a/modules/sd_samplers_common.py +++ b/modules/sd_samplers_common.py @@ -237,7 +237,7 @@ def __init__(self, funcname): self.eta_infotext_field = 'Eta' self.eta_default = 1.0 - self.conditioning_key = shared.sd_model.model.conditioning_key + self.conditioning_key = getattr(shared.sd_model.model, 'conditioning_key', 'crossattn') self.p = None self.model_wrap_cfg = None diff --git a/modules/sd_samplers_kdiffusion.py b/modules/sd_samplers_kdiffusion.py index 887d180d7..348d45f11 100644 --- a/modules/sd_samplers_kdiffusion.py +++ b/modules/sd_samplers_kdiffusion.py @@ -1,7 +1,7 @@ import torch import inspect import k_diffusion.sampling -from modules import sd_samplers_common, sd_samplers_extra, sd_samplers_cfg_denoiser +from modules import sd_samplers_common, sd_samplers_extra, sd_samplers_cfg_denoiser, sd_schedulers, devices from modules.sd_samplers_cfg_denoiser import CFGDenoiser # noqa: F401 from modules.script_callbacks import ExtraNoiseParams, extra_noise_callback @@ -11,32 +11,20 @@ samplers_k_diffusion = [ - ('DPM++ 2M Karras', 'sample_dpmpp_2m', ['k_dpmpp_2m_ka'], {'scheduler': 'karras'}), - ('DPM++ SDE Karras', 'sample_dpmpp_sde', ['k_dpmpp_sde_ka'], {'scheduler': 'karras', "second_order": True, "brownian_noise": True}), - ('DPM++ 2M SDE Exponential', 'sample_dpmpp_2m_sde', ['k_dpmpp_2m_sde_exp'], {'scheduler': 'exponential', "brownian_noise": True}), - ('DPM++ 2M SDE Karras', 'sample_dpmpp_2m_sde', ['k_dpmpp_2m_sde_ka'], {'scheduler': 'karras', "brownian_noise": True}), + ('DPM++ 2M', 'sample_dpmpp_2m', ['k_dpmpp_2m'], {'scheduler': 'karras'}), + ('DPM++ SDE', 'sample_dpmpp_sde', ['k_dpmpp_sde'], {'scheduler': 'karras', "second_order": True, "brownian_noise": True}), + ('DPM++ 2M SDE', 'sample_dpmpp_2m_sde', ['k_dpmpp_2m_sde'], {'scheduler': 'exponential', "brownian_noise": True}), + ('DPM++ 2M SDE Heun', 'sample_dpmpp_2m_sde', ['k_dpmpp_2m_sde_heun'], {'scheduler': 'exponential', "brownian_noise": True, "solver_type": "heun"}), + ('DPM++ 2S a', 'sample_dpmpp_2s_ancestral', ['k_dpmpp_2s_a'], {'scheduler': 'karras', "uses_ensd": True, "second_order": True}), + ('DPM++ 3M SDE', 'sample_dpmpp_3m_sde', ['k_dpmpp_3m_sde'], {'scheduler': 'exponential', 'discard_next_to_last_sigma': True, "brownian_noise": True}), ('Euler a', 'sample_euler_ancestral', ['k_euler_a', 'k_euler_ancestral'], {"uses_ensd": True}), ('Euler', 'sample_euler', ['k_euler'], {}), ('LMS', 'sample_lms', ['k_lms'], {}), ('Heun', 'sample_heun', ['k_heun'], {"second_order": True}), - ('DPM2', 'sample_dpm_2', ['k_dpm_2'], {'discard_next_to_last_sigma': True, "second_order": True}), - ('DPM2 a', 'sample_dpm_2_ancestral', ['k_dpm_2_a'], {'discard_next_to_last_sigma': True, "uses_ensd": True, "second_order": True}), - ('DPM++ 2S a', 'sample_dpmpp_2s_ancestral', ['k_dpmpp_2s_a'], {"uses_ensd": True, "second_order": True}), - ('DPM++ 2M', 'sample_dpmpp_2m', ['k_dpmpp_2m'], {}), - ('DPM++ SDE', 'sample_dpmpp_sde', ['k_dpmpp_sde'], {"second_order": True, "brownian_noise": True}), - ('DPM++ 2M SDE', 'sample_dpmpp_2m_sde', ['k_dpmpp_2m_sde_ka'], {"brownian_noise": True}), - ('DPM++ 2M SDE Heun', 'sample_dpmpp_2m_sde', ['k_dpmpp_2m_sde_heun'], {"brownian_noise": True, "solver_type": "heun"}), - ('DPM++ 2M SDE Heun Karras', 'sample_dpmpp_2m_sde', ['k_dpmpp_2m_sde_heun_ka'], {'scheduler': 'karras', "brownian_noise": True, "solver_type": "heun"}), - ('DPM++ 2M SDE Heun Exponential', 'sample_dpmpp_2m_sde', ['k_dpmpp_2m_sde_heun_exp'], {'scheduler': 'exponential', "brownian_noise": True, "solver_type": "heun"}), - ('DPM++ 3M SDE', 'sample_dpmpp_3m_sde', ['k_dpmpp_3m_sde'], {'discard_next_to_last_sigma': True, "brownian_noise": True}), - ('DPM++ 3M SDE Karras', 'sample_dpmpp_3m_sde', ['k_dpmpp_3m_sde_ka'], {'scheduler': 'karras', 'discard_next_to_last_sigma': True, "brownian_noise": True}), - ('DPM++ 3M SDE Exponential', 'sample_dpmpp_3m_sde', ['k_dpmpp_3m_sde_exp'], {'scheduler': 'exponential', 'discard_next_to_last_sigma': True, "brownian_noise": True}), + ('DPM2', 'sample_dpm_2', ['k_dpm_2'], {'scheduler': 'karras', 'discard_next_to_last_sigma': True, "second_order": True}), + ('DPM2 a', 'sample_dpm_2_ancestral', ['k_dpm_2_a'], {'scheduler': 'karras', 'discard_next_to_last_sigma': True, "uses_ensd": True, "second_order": True}), ('DPM fast', 'sample_dpm_fast', ['k_dpm_fast'], {"uses_ensd": True}), ('DPM adaptive', 'sample_dpm_adaptive', ['k_dpm_ad'], {"uses_ensd": True}), - ('LMS Karras', 'sample_lms', ['k_lms_ka'], {'scheduler': 'karras'}), - ('DPM2 Karras', 'sample_dpm_2', ['k_dpm_2_ka'], {'scheduler': 'karras', 'discard_next_to_last_sigma': True, "uses_ensd": True, "second_order": True}), - ('DPM2 a Karras', 'sample_dpm_2_ancestral', ['k_dpm_2_a_ka'], {'scheduler': 'karras', 'discard_next_to_last_sigma': True, "uses_ensd": True, "second_order": True}), - ('DPM++ 2S a Karras', 'sample_dpmpp_2s_ancestral', ['k_dpmpp_2s_a_ka'], {'scheduler': 'karras', "uses_ensd": True, "second_order": True}), ('Restart', sd_samplers_extra.restart_sampler, ['restart'], {'scheduler': 'karras', "second_order": True}), ] @@ -60,20 +48,20 @@ } k_diffusion_samplers_map = {x.name: x for x in samplers_data_k_diffusion} -k_diffusion_scheduler = { - 'Automatic': None, - 'karras': k_diffusion.sampling.get_sigmas_karras, - 'exponential': k_diffusion.sampling.get_sigmas_exponential, - 'polyexponential': k_diffusion.sampling.get_sigmas_polyexponential -} +k_diffusion_scheduler = {x.name: x.function for x in sd_schedulers.schedulers} class CFGDenoiserKDiffusion(sd_samplers_cfg_denoiser.CFGDenoiser): @property def inner_model(self): if self.model_wrap is None: - denoiser = k_diffusion.external.CompVisVDenoiser if shared.sd_model.parameterization == "v" else k_diffusion.external.CompVisDenoiser - self.model_wrap = denoiser(shared.sd_model, quantize=shared.opts.enable_quantization) + denoiser_constructor = getattr(shared.sd_model, 'create_denoiser', None) + + if denoiser_constructor is not None: + self.model_wrap = denoiser_constructor() + else: + denoiser = k_diffusion.external.CompVisVDenoiser if shared.sd_model.parameterization == "v" else k_diffusion.external.CompVisDenoiser + self.model_wrap = denoiser(shared.sd_model, quantize=shared.opts.enable_quantization) return self.model_wrap @@ -98,47 +86,52 @@ def get_sigmas(self, p, steps): steps += 1 if discard_next_to_last_sigma else 0 + scheduler_name = (p.hr_scheduler if p.is_hr_pass else p.scheduler) or 'Automatic' + if scheduler_name == 'Automatic': + scheduler_name = self.config.options.get('scheduler', None) + + scheduler = sd_schedulers.schedulers_map.get(scheduler_name) + + m_sigma_min, m_sigma_max = self.model_wrap.sigmas[0].item(), self.model_wrap.sigmas[-1].item() + sigma_min, sigma_max = (0.1, 10) if opts.use_old_karras_scheduler_sigmas else (m_sigma_min, m_sigma_max) + if p.sampler_noise_scheduler_override: sigmas = p.sampler_noise_scheduler_override(steps) - elif opts.k_sched_type != "Automatic": - m_sigma_min, m_sigma_max = (self.model_wrap.sigmas[0].item(), self.model_wrap.sigmas[-1].item()) - sigma_min, sigma_max = (0.1, 10) if opts.use_old_karras_scheduler_sigmas else (m_sigma_min, m_sigma_max) - sigmas_kwargs = { - 'sigma_min': sigma_min, - 'sigma_max': sigma_max, - } - - sigmas_func = k_diffusion_scheduler[opts.k_sched_type] - p.extra_generation_params["Schedule type"] = opts.k_sched_type - - if opts.sigma_min != m_sigma_min and opts.sigma_min != 0: + elif scheduler is None or scheduler.function is None: + sigmas = self.model_wrap.get_sigmas(steps) + else: + sigmas_kwargs = {'sigma_min': sigma_min, 'sigma_max': sigma_max} + + if scheduler.label != 'Automatic' and not p.is_hr_pass: + p.extra_generation_params["Schedule type"] = scheduler.label + elif scheduler.label != p.extra_generation_params.get("Schedule type"): + p.extra_generation_params["Hires schedule type"] = scheduler.label + + if opts.sigma_min != 0 and opts.sigma_min != m_sigma_min: sigmas_kwargs['sigma_min'] = opts.sigma_min p.extra_generation_params["Schedule min sigma"] = opts.sigma_min - if opts.sigma_max != m_sigma_max and opts.sigma_max != 0: + + if opts.sigma_max != 0 and opts.sigma_max != m_sigma_max: sigmas_kwargs['sigma_max'] = opts.sigma_max p.extra_generation_params["Schedule max sigma"] = opts.sigma_max - default_rho = 1. if opts.k_sched_type == "polyexponential" else 7. - - if opts.k_sched_type != 'exponential' and opts.rho != 0 and opts.rho != default_rho: + if scheduler.default_rho != -1 and opts.rho != 0 and opts.rho != scheduler.default_rho: sigmas_kwargs['rho'] = opts.rho p.extra_generation_params["Schedule rho"] = opts.rho - sigmas = sigmas_func(n=steps, **sigmas_kwargs, device=shared.device) - elif self.config is not None and self.config.options.get('scheduler', None) == 'karras': - sigma_min, sigma_max = (0.1, 10) if opts.use_old_karras_scheduler_sigmas else (self.model_wrap.sigmas[0].item(), self.model_wrap.sigmas[-1].item()) + if scheduler.need_inner_model: + sigmas_kwargs['inner_model'] = self.model_wrap - sigmas = k_diffusion.sampling.get_sigmas_karras(n=steps, sigma_min=sigma_min, sigma_max=sigma_max, device=shared.device) - elif self.config is not None and self.config.options.get('scheduler', None) == 'exponential': - m_sigma_min, m_sigma_max = (self.model_wrap.sigmas[0].item(), self.model_wrap.sigmas[-1].item()) - sigmas = k_diffusion.sampling.get_sigmas_exponential(n=steps, sigma_min=m_sigma_min, sigma_max=m_sigma_max, device=shared.device) - else: - sigmas = self.model_wrap.get_sigmas(steps) + if scheduler.label == 'Beta': + p.extra_generation_params["Beta schedule alpha"] = opts.beta_dist_alpha + p.extra_generation_params["Beta schedule beta"] = opts.beta_dist_beta + + sigmas = scheduler.function(n=steps, **sigmas_kwargs, device=devices.cpu) if discard_next_to_last_sigma: sigmas = torch.cat([sigmas[:-2], sigmas[-1:]]) - return sigmas + return sigmas.cpu() def sample_img2img(self, p, x, noise, conditioning, unconditional_conditioning, steps=None, image_conditioning=None): unet_patcher = self.model_wrap.inner_model.forge_objects.unet diff --git a/modules/sd_samplers_timesteps.py b/modules/sd_samplers_timesteps.py index 149d67009..9dbb53b61 100644 --- a/modules/sd_samplers_timesteps.py +++ b/modules/sd_samplers_timesteps.py @@ -12,6 +12,7 @@ samplers_timesteps = [ ('DDIM', sd_samplers_timesteps_impl.ddim, ['ddim'], {}), + ('DDIM CFG++', sd_samplers_timesteps_impl.ddim_cfgpp, ['ddim_cfgpp'], {}), ('PLMS', sd_samplers_timesteps_impl.plms, ['plms'], {}), ('UniPC', sd_samplers_timesteps_impl.unipc, ['unipc'], {}), ] diff --git a/modules/sd_samplers_timesteps_impl.py b/modules/sd_samplers_timesteps_impl.py index 930a64af5..180e43899 100644 --- a/modules/sd_samplers_timesteps_impl.py +++ b/modules/sd_samplers_timesteps_impl.py @@ -5,13 +5,14 @@ from modules import shared from modules.models.diffusion.uni_pc import uni_pc +from modules.torch_utils import float64 @torch.no_grad() def ddim(model, x, timesteps, extra_args=None, callback=None, disable=None, eta=0.0): alphas_cumprod = model.inner_model.inner_model.alphas_cumprod alphas = alphas_cumprod[timesteps] - alphas_prev = alphas_cumprod[torch.nn.functional.pad(timesteps[:-1], pad=(1, 0))].to(torch.float64 if x.device.type != 'mps' and x.device.type != 'xpu' else torch.float32) + alphas_prev = alphas_cumprod[torch.nn.functional.pad(timesteps[:-1], pad=(1, 0))].to(float64(x)) sqrt_one_minus_alphas = torch.sqrt(1 - alphas) sigmas = eta * np.sqrt((1 - alphas_prev.cpu().numpy()) / (1 - alphas.cpu()) * (1 - alphas.cpu() / alphas_prev.cpu().numpy())) @@ -39,11 +40,51 @@ def ddim(model, x, timesteps, extra_args=None, callback=None, disable=None, eta= return x +@torch.no_grad() +def ddim_cfgpp(model, x, timesteps, extra_args=None, callback=None, disable=None, eta=0.0): + """ Implements CFG++: Manifold-constrained Classifier Free Guidance For Diffusion Models (2024). + Uses the unconditional noise prediction instead of the conditional noise to guide the denoising direction. + The CFG scale is divided by 12.5 to map CFG from [0.0, 12.5] to [0, 1.0]. + """ + alphas_cumprod = model.inner_model.inner_model.alphas_cumprod + alphas = alphas_cumprod[timesteps] + alphas_prev = alphas_cumprod[torch.nn.functional.pad(timesteps[:-1], pad=(1, 0))].to(float64(x)) + sqrt_one_minus_alphas = torch.sqrt(1 - alphas) + sigmas = eta * np.sqrt((1 - alphas_prev.cpu().numpy()) / (1 - alphas.cpu()) * (1 - alphas.cpu() / alphas_prev.cpu().numpy())) + + model.cond_scale_miltiplier = 1 / 12.5 + model.need_last_noise_uncond = True + + extra_args = {} if extra_args is None else extra_args + s_in = x.new_ones((x.shape[0])) + s_x = x.new_ones((x.shape[0], 1, 1, 1)) + for i in tqdm.trange(len(timesteps) - 1, disable=disable): + index = len(timesteps) - 1 - i + + e_t = model(x, timesteps[index].item() * s_in, **extra_args) + last_noise_uncond = model.last_noise_uncond + + a_t = alphas[index].item() * s_x + a_prev = alphas_prev[index].item() * s_x + sigma_t = sigmas[index].item() * s_x + sqrt_one_minus_at = sqrt_one_minus_alphas[index].item() * s_x + + pred_x0 = (x - sqrt_one_minus_at * e_t) / a_t.sqrt() + dir_xt = (1. - a_prev - sigma_t ** 2).sqrt() * last_noise_uncond + noise = sigma_t * k_diffusion.sampling.torch.randn_like(x) + x = a_prev.sqrt() * pred_x0 + dir_xt + noise + + if callback is not None: + callback({'x': x, 'i': i, 'sigma': 0, 'sigma_hat': 0, 'denoised': pred_x0}) + + return x + + @torch.no_grad() def plms(model, x, timesteps, extra_args=None, callback=None, disable=None): alphas_cumprod = model.inner_model.inner_model.alphas_cumprod alphas = alphas_cumprod[timesteps] - alphas_prev = alphas_cumprod[torch.nn.functional.pad(timesteps[:-1], pad=(1, 0))].to(torch.float64 if x.device.type != 'mps' and x.device.type != 'xpu' else torch.float32) + alphas_prev = alphas_cumprod[torch.nn.functional.pad(timesteps[:-1], pad=(1, 0))].to(float64(x)) sqrt_one_minus_alphas = torch.sqrt(1 - alphas) extra_args = {} if extra_args is None else extra_args diff --git a/modules/sd_schedulers.py b/modules/sd_schedulers.py new file mode 100644 index 000000000..af873dc97 --- /dev/null +++ b/modules/sd_schedulers.py @@ -0,0 +1,154 @@ +import dataclasses +import torch +import k_diffusion +import numpy as np +from scipy import stats + +from modules import shared + + +def to_d(x, sigma, denoised): + """Converts a denoiser output to a Karras ODE derivative.""" + return (x - denoised) / sigma + + +k_diffusion.sampling.to_d = to_d + + +@dataclasses.dataclass +class Scheduler: + name: str + label: str + function: any + + default_rho: float = -1 + need_inner_model: bool = False + aliases: list = None + + +def uniform(n, sigma_min, sigma_max, inner_model, device): + return inner_model.get_sigmas(n).to(device) + + +def sgm_uniform(n, sigma_min, sigma_max, inner_model, device): + start = inner_model.sigma_to_t(torch.tensor(sigma_max)) + end = inner_model.sigma_to_t(torch.tensor(sigma_min)) + sigs = [ + inner_model.t_to_sigma(ts) + for ts in torch.linspace(start, end, n + 1)[:-1] + ] + sigs += [0.0] + return torch.FloatTensor(sigs).to(device) + + +def get_align_your_steps_sigmas(n, sigma_min, sigma_max, device): + # https://research.nvidia.com/labs/toronto-ai/AlignYourSteps/howto.html + def loglinear_interp(t_steps, num_steps): + """ + Performs log-linear interpolation of a given array of decreasing numbers. + """ + xs = np.linspace(0, 1, len(t_steps)) + ys = np.log(t_steps[::-1]) + + new_xs = np.linspace(0, 1, num_steps) + new_ys = np.interp(new_xs, xs, ys) + + interped_ys = np.exp(new_ys)[::-1].copy() + return interped_ys + + if shared.sd_model.is_sdxl: + sigmas = [14.615, 6.315, 3.771, 2.181, 1.342, 0.862, 0.555, 0.380, 0.234, 0.113, 0.029] + else: + # Default to SD 1.5 sigmas. + sigmas = [14.615, 6.475, 3.861, 2.697, 1.886, 1.396, 0.963, 0.652, 0.399, 0.152, 0.029] + + if n != len(sigmas): + sigmas = np.append(loglinear_interp(sigmas, n), [0.0]) + else: + sigmas.append(0.0) + + return torch.FloatTensor(sigmas).to(device) + + +def kl_optimal(n, sigma_min, sigma_max, device): + alpha_min = torch.arctan(torch.tensor(sigma_min, device=device)) + alpha_max = torch.arctan(torch.tensor(sigma_max, device=device)) + step_indices = torch.arange(n + 1, device=device) + sigmas = torch.tan(step_indices / n * alpha_min + (1.0 - step_indices / n) * alpha_max) + return sigmas + + +def simple_scheduler(n, sigma_min, sigma_max, inner_model, device): + sigs = [] + ss = len(inner_model.sigmas) / n + for x in range(n): + sigs += [float(inner_model.sigmas[-(1 + int(x * ss))])] + sigs += [0.0] + return torch.FloatTensor(sigs).to(device) + + +def normal_scheduler(n, sigma_min, sigma_max, inner_model, device, sgm=False, floor=False): + start = inner_model.sigma_to_t(torch.tensor(sigma_max)) + end = inner_model.sigma_to_t(torch.tensor(sigma_min)) + + if sgm: + timesteps = torch.linspace(start, end, n + 1)[:-1] + else: + timesteps = torch.linspace(start, end, n) + + sigs = [] + for x in range(len(timesteps)): + ts = timesteps[x] + sigs.append(inner_model.t_to_sigma(ts)) + sigs += [0.0] + return torch.FloatTensor(sigs).to(device) + + +def ddim_scheduler(n, sigma_min, sigma_max, inner_model, device): + sigs = [] + ss = max(len(inner_model.sigmas) // n, 1) + x = 1 + while x < len(inner_model.sigmas): + sigs += [float(inner_model.sigmas[x])] + x += ss + sigs = sigs[::-1] + sigs += [0.0] + return torch.FloatTensor(sigs).to(device) + + +def beta_scheduler(n, sigma_min, sigma_max, inner_model, device): + # From "Beta Sampling is All You Need" [arXiv:2407.12173] (Lee et. al, 2024) """ + alpha = shared.opts.beta_dist_alpha + beta = shared.opts.beta_dist_beta + timesteps = 1 - np.linspace(0, 1, n) + timesteps = [stats.beta.ppf(x, alpha, beta) for x in timesteps] + sigmas = [sigma_min + (x * (sigma_max-sigma_min)) for x in timesteps] + sigmas += [0.0] + return torch.FloatTensor(sigmas).to(device) + + +def turbo_scheduler(n, sigma_min, sigma_max, inner_model, device): + unet = inner_model.inner_model.forge_objects.unet + timesteps = torch.flip(torch.arange(1, n + 1) * float(1000.0 / n) - 1, (0,)).round().long().clip(0, 999) + sigmas = unet.model.model_sampling.sigma(timesteps) + sigmas = torch.cat([sigmas, sigmas.new_zeros([1])]) + return sigmas.to(device) + + +schedulers = [ + Scheduler('automatic', 'Automatic', None), + Scheduler('uniform', 'Uniform', uniform, need_inner_model=True), + Scheduler('karras', 'Karras', k_diffusion.sampling.get_sigmas_karras, default_rho=7.0), + Scheduler('exponential', 'Exponential', k_diffusion.sampling.get_sigmas_exponential), + Scheduler('polyexponential', 'Polyexponential', k_diffusion.sampling.get_sigmas_polyexponential, default_rho=1.0), + Scheduler('sgm_uniform', 'SGM Uniform', sgm_uniform, need_inner_model=True, aliases=["SGMUniform"]), + Scheduler('kl_optimal', 'KL Optimal', kl_optimal), + Scheduler('align_your_steps', 'Align Your Steps', get_align_your_steps_sigmas), + Scheduler('simple', 'Simple', simple_scheduler, need_inner_model=True), + Scheduler('normal', 'Normal', normal_scheduler, need_inner_model=True), + Scheduler('ddim', 'DDIM', ddim_scheduler, need_inner_model=True), + Scheduler('beta', 'Beta', beta_scheduler, need_inner_model=True), + Scheduler('turbo', 'Turbo', turbo_scheduler, need_inner_model=True), +] + +schedulers_map = {**{x.name: x for x in schedulers}, **{x.label: x for x in schedulers}} diff --git a/modules/sd_vae_approx.py b/modules/sd_vae_approx.py index 3965e223e..c5dda7431 100644 --- a/modules/sd_vae_approx.py +++ b/modules/sd_vae_approx.py @@ -8,9 +8,9 @@ class VAEApprox(nn.Module): - def __init__(self): + def __init__(self, latent_channels=4): super(VAEApprox, self).__init__() - self.conv1 = nn.Conv2d(4, 8, (7, 7)) + self.conv1 = nn.Conv2d(latent_channels, 8, (7, 7)) self.conv2 = nn.Conv2d(8, 16, (5, 5)) self.conv3 = nn.Conv2d(16, 32, (3, 3)) self.conv4 = nn.Conv2d(32, 64, (3, 3)) @@ -40,7 +40,13 @@ def download_model(model_path, model_url): def model(): - model_name = "vaeapprox-sdxl.pt" if getattr(shared.sd_model, 'is_sdxl', False) else "model.pt" + if shared.sd_model.is_sd3: + model_name = "vaeapprox-sd3.pt" + elif shared.sd_model.is_sdxl: + model_name = "vaeapprox-sdxl.pt" + else: + model_name = "model.pt" + loaded_model = sd_vae_approx_models.get(model_name) if loaded_model is None: @@ -52,7 +58,7 @@ def model(): model_path = os.path.join(paths.models_path, "VAE-approx", model_name) download_model(model_path, 'https://github.com/AUTOMATIC1111/stable-diffusion-webui/releases/download/v1.0.0-pre/' + model_name) - loaded_model = VAEApprox() + loaded_model = VAEApprox(latent_channels=shared.sd_model.latent_channels) loaded_model.load_state_dict(torch.load(model_path, map_location='cpu' if devices.device.type != 'cuda' else None)) loaded_model.eval() loaded_model.to(devices.device, devices.dtype) @@ -64,7 +70,18 @@ def model(): def cheap_approximation(sample): # https://discuss.huggingface.co/t/decoding-latents-to-rgb-without-upscaling/23204/2 - if shared.sd_model.is_sdxl: + if shared.sd_model.is_sd3: + coeffs = [ + [-0.0645, 0.0177, 0.1052], [ 0.0028, 0.0312, 0.0650], + [ 0.1848, 0.0762, 0.0360], [ 0.0944, 0.0360, 0.0889], + [ 0.0897, 0.0506, -0.0364], [-0.0020, 0.1203, 0.0284], + [ 0.0855, 0.0118, 0.0283], [-0.0539, 0.0658, 0.1047], + [-0.0057, 0.0116, 0.0700], [-0.0412, 0.0281, -0.0039], + [ 0.1106, 0.1171, 0.1220], [-0.0248, 0.0682, -0.0481], + [ 0.0815, 0.0846, 0.1207], [-0.0120, -0.0055, -0.0867], + [-0.0749, -0.0634, -0.0456], [-0.1418, -0.1457, -0.1259], + ] + elif shared.sd_model.is_sdxl: coeffs = [ [ 0.3448, 0.4168, 0.4395], [-0.1953, -0.0290, 0.0250], diff --git a/modules/sd_vae_taesd.py b/modules/sd_vae_taesd.py index 808eb3624..d06253d2a 100644 --- a/modules/sd_vae_taesd.py +++ b/modules/sd_vae_taesd.py @@ -34,9 +34,9 @@ def forward(self, x): return self.fuse(self.conv(x) + self.skip(x)) -def decoder(): +def decoder(latent_channels=4): return nn.Sequential( - Clamp(), conv(4, 64), nn.ReLU(), + Clamp(), conv(latent_channels, 64), nn.ReLU(), Block(64, 64), Block(64, 64), Block(64, 64), nn.Upsample(scale_factor=2), conv(64, 64, bias=False), Block(64, 64), Block(64, 64), Block(64, 64), nn.Upsample(scale_factor=2), conv(64, 64, bias=False), Block(64, 64), Block(64, 64), Block(64, 64), nn.Upsample(scale_factor=2), conv(64, 64, bias=False), @@ -44,13 +44,13 @@ def decoder(): ) -def encoder(): +def encoder(latent_channels=4): return nn.Sequential( conv(3, 64), Block(64, 64), conv(64, 64, stride=2, bias=False), Block(64, 64), Block(64, 64), Block(64, 64), conv(64, 64, stride=2, bias=False), Block(64, 64), Block(64, 64), Block(64, 64), conv(64, 64, stride=2, bias=False), Block(64, 64), Block(64, 64), Block(64, 64), - conv(64, 4), + conv(64, latent_channels), ) @@ -58,10 +58,14 @@ class TAESDDecoder(nn.Module): latent_magnitude = 3 latent_shift = 0.5 - def __init__(self, decoder_path="taesd_decoder.pth"): + def __init__(self, decoder_path="taesd_decoder.pth", latent_channels=None): """Initialize pretrained TAESD on the given device from the given checkpoints.""" super().__init__() - self.decoder = decoder() + + if latent_channels is None: + latent_channels = 16 if "taesd3" in str(decoder_path) else 4 + + self.decoder = decoder(latent_channels) self.decoder.load_state_dict( torch.load(decoder_path, map_location='cpu' if devices.device.type != 'cuda' else None)) @@ -70,10 +74,14 @@ class TAESDEncoder(nn.Module): latent_magnitude = 3 latent_shift = 0.5 - def __init__(self, encoder_path="taesd_encoder.pth"): + def __init__(self, encoder_path="taesd_encoder.pth", latent_channels=None): """Initialize pretrained TAESD on the given device from the given checkpoints.""" super().__init__() - self.encoder = encoder() + + if latent_channels is None: + latent_channels = 16 if "taesd3" in str(encoder_path) else 4 + + self.encoder = encoder(latent_channels) self.encoder.load_state_dict( torch.load(encoder_path, map_location='cpu' if devices.device.type != 'cuda' else None)) @@ -87,7 +95,13 @@ def download_model(model_path, model_url): def decoder_model(): - model_name = "taesdxl_decoder.pth" if getattr(shared.sd_model, 'is_sdxl', False) else "taesd_decoder.pth" + if shared.sd_model.is_sd3: + model_name = "taesd3_decoder.pth" + elif shared.sd_model.is_sdxl: + model_name = "taesdxl_decoder.pth" + else: + model_name = "taesd_decoder.pth" + loaded_model = sd_vae_taesd_models.get(model_name) if loaded_model is None: @@ -106,7 +120,13 @@ def decoder_model(): def encoder_model(): - model_name = "taesdxl_encoder.pth" if getattr(shared.sd_model, 'is_sdxl', False) else "taesd_encoder.pth" + if shared.sd_model.is_sd3: + model_name = "taesd3_encoder.pth" + elif shared.sd_model.is_sdxl: + model_name = "taesdxl_encoder.pth" + else: + model_name = "taesd_encoder.pth" + loaded_model = sd_vae_taesd_models.get(model_name) if loaded_model is None: diff --git a/modules/shared.py b/modules/shared.py index ccdca4e70..2a3787f99 100644 --- a/modules/shared.py +++ b/modules/shared.py @@ -6,6 +6,10 @@ from modules import shared_cmd_options, shared_gradio_themes, options, shared_items, sd_models_types from modules.paths_internal import models_path, script_path, data_path, sd_configs_path, sd_default_config, sd_model_file, default_sd_model_file, extensions_dir, extensions_builtin_dir # noqa: F401 from modules import util +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from modules import shared_state, styles, interrogate, shared_total_tqdm, memmon cmd_opts = shared_cmd_options.cmd_opts parser = shared_cmd_options.parser @@ -16,11 +20,11 @@ config_filename = cmd_opts.ui_settings_file hide_dirs = {"visible": not cmd_opts.hide_ui_dir_config} -demo = None +demo: gr.Blocks = None -device = None +device: str = None -weight_load_location = None +weight_load_location: str = None xformers_available = False @@ -28,22 +32,22 @@ loaded_hypernetworks = [] -state = None +state: 'shared_state.State' = None -prompt_styles = None +prompt_styles: 'styles.StyleDatabase' = None -interrogator = None +interrogator: 'interrogate.InterrogateModels' = None face_restorers = [] -options_templates = None -opts = None -restricted_opts = None +options_templates: dict = None +opts: options.Options = None +restricted_opts: set[str] = None sd_model: sd_models_types.WebuiSdModel = None -settings_components = None -"""assinged from ui.py, a mapping on setting names to gradio components repsponsible for those settings""" +settings_components: dict = None +"""assigned from ui.py, a mapping on setting names to gradio components responsible for those settings""" tab_names = [] @@ -65,9 +69,9 @@ gradio_theme = gr.themes.Base() -total_tqdm = None +total_tqdm: 'shared_total_tqdm.TotalTQDM' = None -mem_mon = None +mem_mon: 'memmon.MemUsageMonitor' = None options_section = options.options_section OptionInfo = options.OptionInfo @@ -86,3 +90,5 @@ refresh_checkpoints = shared_items.refresh_checkpoints list_samplers = shared_items.list_samplers reload_hypernetworks = shared_items.reload_hypernetworks + +hf_endpoint = os.getenv('HF_ENDPOINT', 'https://huggingface.co') diff --git a/modules/shared_gradio_themes.py b/modules/shared_gradio_themes.py index b6dc31450..b4e3f32bc 100644 --- a/modules/shared_gradio_themes.py +++ b/modules/shared_gradio_themes.py @@ -69,3 +69,44 @@ def reload_gradio_theme(theme_name=None): # append additional values gradio_theme shared.gradio_theme.sd_webui_modal_lightbox_toolbar_opacity = shared.opts.sd_webui_modal_lightbox_toolbar_opacity shared.gradio_theme.sd_webui_modal_lightbox_icon_opacity = shared.opts.sd_webui_modal_lightbox_icon_opacity + + +def resolve_var(name: str, gradio_theme=None, history=None): + """ + Attempt to resolve a theme variable name to its value + + Parameters: + name (str): The name of the theme variable + ie "background_fill_primary", "background_fill_primary_dark" + spaces and asterisk (*) prefix is removed from name before lookup + gradio_theme (gradio.themes.ThemeClass): The theme object to resolve the variable from + blank to use the webui default shared.gradio_theme + history (list): A list of previously resolved variables to prevent circular references + for regular use leave blank + Returns: + str: The resolved value + + Error handling: + return either #000000 or #ffffff depending on initial name ending with "_dark" + """ + try: + if history is None: + history = [] + if gradio_theme is None: + gradio_theme = shared.gradio_theme + + name = name.strip() + name = name[1:] if name.startswith("*") else name + + if name in history: + raise ValueError(f'Circular references: name "{name}" in {history}') + + if value := getattr(gradio_theme, name, None): + return resolve_var(value, gradio_theme, history + [name]) + else: + return name + + except Exception: + name = history[0] if history else name + errors.report(f'resolve_color({name})', exc_info=True) + return '#000000' if name.endswith("_dark") else '#ffffff' diff --git a/modules/shared_items.py b/modules/shared_items.py index 88f636452..11f10b3f7 100644 --- a/modules/shared_items.py +++ b/modules/shared_items.py @@ -1,5 +1,8 @@ +import html import sys +from modules import script_callbacks, scripts, ui_components +from modules.options import OptionHTML, OptionInfo from modules.shared_cmd_options import cmd_opts @@ -118,6 +121,45 @@ def ui_reorder_categories(): yield "scripts" +def callbacks_order_settings(): + options = { + "sd_vae_explanation": OptionHTML(""" + For categories below, callbacks added to dropdowns happen before others, in order listed. + """), + + } + + callback_options = {} + + for category, _ in script_callbacks.enumerate_callbacks(): + callback_options[category] = script_callbacks.ordered_callbacks(category, enable_user_sort=False) + + for method_name in scripts.scripts_txt2img.callback_names: + callback_options["script_" + method_name] = scripts.scripts_txt2img.create_ordered_callbacks_list(method_name, enable_user_sort=False) + + for method_name in scripts.scripts_img2img.callback_names: + callbacks = callback_options.get("script_" + method_name, []) + + for addition in scripts.scripts_img2img.create_ordered_callbacks_list(method_name, enable_user_sort=False): + if any(x.name == addition.name for x in callbacks): + continue + + callbacks.append(addition) + + callback_options["script_" + method_name] = callbacks + + for category, callbacks in callback_options.items(): + if not callbacks: + continue + + option_info = OptionInfo([], f"{category} callback priority", ui_components.DropdownMulti, {"choices": [x.name for x in callbacks]}) + option_info.needs_restart() + option_info.html("
Default order:
    " + "".join(f"
  1. {html.escape(x.name)}
  2. \n" for x in callbacks) + "
") + options['prioritized_callbacks_' + category] = option_info + + return options + + class Shared(sys.modules[__name__].__class__): """ this class is here to provide sd_model field as a property, so that it can be created and loaded on demand rather than diff --git a/modules/shared_options.py b/modules/shared_options.py index 856c07a84..df83a6024 100644 --- a/modules/shared_options.py +++ b/modules/shared_options.py @@ -19,7 +19,9 @@ "outdir_grids", "outdir_txt2img_grids", "outdir_save", - "outdir_init_images" + "outdir_init_images", + "temp_dir", + "clean_temp_dir_at_start", } categories.register_category("saving", "Saving images") @@ -52,7 +54,7 @@ "save_images_before_color_correction": OptionInfo(False, "Save a copy of image before applying color correction to img2img results"), "save_mask": OptionInfo(False, "For inpainting, save a copy of the greyscale mask"), "save_mask_composite": OptionInfo(False, "For inpainting, save a masked composite"), - "jpeg_quality": OptionInfo(80, "Quality for saved jpeg images", gr.Slider, {"minimum": 1, "maximum": 100, "step": 1}), + "jpeg_quality": OptionInfo(80, "Quality for saved jpeg and avif images", gr.Slider, {"minimum": 1, "maximum": 100, "step": 1}), "webp_lossless": OptionInfo(False, "Use lossless compression for webp images"), "export_for_4chan": OptionInfo(True, "Save copy of large images as JPG").info("if the file size is above the limit, or either width or height are above the limit"), "img_downscale_threshold": OptionInfo(4.0, "File size limit for the above option, MB", gr.Number), @@ -62,6 +64,7 @@ "use_original_name_batch": OptionInfo(True, "Use original name for output filename during batch process in extras tab"), "use_upscaler_name_as_suffix": OptionInfo(False, "Use upscaler name as filename suffix in the extras tab"), "save_selected_only": OptionInfo(True, "When using 'Save' button, only save a single selected image"), + "save_write_log_csv": OptionInfo(True, "Write log.csv when saving images using 'Save' button"), "save_init_img": OptionInfo(False, "Save init images when using img2img"), "temp_dir": OptionInfo("", "Directory for temporary images; leave empty for default"), @@ -101,6 +104,7 @@ "DAT_tile": OptionInfo(192, "Tile size for DAT upscalers.", gr.Slider, {"minimum": 0, "maximum": 512, "step": 16}).info("0 = no tiling"), "DAT_tile_overlap": OptionInfo(8, "Tile overlap for DAT upscalers.", gr.Slider, {"minimum": 0, "maximum": 48, "step": 1}).info("Low values = visible seam"), "upscaler_for_img2img": OptionInfo(None, "Upscaler for img2img", gr.Dropdown, lambda: {"choices": [x.name for x in shared.sd_upscalers]}), + "set_scale_by_when_changing_upscaler": OptionInfo(False, "Automatically set the Scale by factor based on the name of the selected Upscaler."), })) options_templates.update(options_section(('face-restoration', "Face restoration", "postprocessing"), { @@ -126,6 +130,22 @@ "dump_stacks_on_signal": OptionInfo(False, "Print stack traces before exiting the program with ctrl+c."), })) +options_templates.update(options_section(('profiler', "Profiler", "system"), { + "profiling_explanation": OptionHTML(""" +Those settings allow you to enable torch profiler when generating pictures. +Profiling allows you to see which code uses how much of computer's resources during generation. +Each generation writes its own profile to one file, overwriting previous. +The file can be viewed in Chrome, or on a Perfetto web site. +Warning: writing profile can take a lot of time, up to 30 seconds, and the file itelf can be around 500MB in size. +"""), + "profiling_enable": OptionInfo(False, "Enable profiling"), + "profiling_activities": OptionInfo(["CPU"], "Activities", gr.CheckboxGroup, {"choices": ["CPU", "CUDA"]}), + "profiling_record_shapes": OptionInfo(True, "Record shapes"), + "profiling_profile_memory": OptionInfo(True, "Profile memory"), + "profiling_with_stack": OptionInfo(True, "Include python stack"), + "profiling_filename": OptionInfo("trace.json", "Profile filename"), +})) + options_templates.update(options_section(('API', "API", "system"), { "api_enable_requests": OptionInfo(True, "Allow http:// and https:// URLs for input images in API", restrict_api=True), "api_forbid_local_requests": OptionInfo(True, "Forbid URLs to local resources", restrict_api=True), @@ -157,6 +177,7 @@ "emphasis": OptionInfo("Original", "Emphasis mode", gr.Radio, lambda: {"choices": [x.name for x in sd_emphasis.options]}, infotext="Emphasis").info("makes it possible to make model to pay (more:1.1) or (less:0.9) attention to text when you use the syntax in prompt; " + sd_emphasis.get_options_descriptions()), "enable_batch_seeds": OptionInfo(True, "Make K-diffusion samplers produce same images in a batch as when making a single image"), "comma_padding_backtrack": OptionInfo(20, "Prompt word wrap length limit", gr.Slider, {"minimum": 0, "maximum": 74, "step": 1}).info("in tokens - for texts shorter than specified, if they don't fit into 75 token limit, move them to the next 75 token chunk"), + "sdxl_clip_l_skip": OptionInfo(False, "Clip skip SDXL", gr.Checkbox).info("Enable Clip skip for the secondary clip model in sdxl. Has no effect on SD 1.5 or SD 2.0/2.1."), "CLIP_stop_at_last_layers": OptionInfo(1, "Clip skip", gr.Slider, {"minimum": 1, "maximum": 12, "step": 1}, infotext="Clip skip").link("wiki", "https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Features#clip-skip").info("ignore last layers of CLIP network; 1 ignores none, 2 ignores one layer"), "upcast_attn": OptionInfo(False, "Upcast cross attention layer to float32"), "randn_source": OptionInfo("GPU", "Random number generator source.", gr.Radio, {"choices": ["GPU", "CPU", "NV"]}, infotext="RNG").info("changes seeds drastically; use CPU to produce the same picture across different videocard vendors; use NV to produce same picture as on NVidia videocards"), @@ -171,6 +192,10 @@ "sdxl_refiner_high_aesthetic_score": OptionInfo(6.0, "SDXL high aesthetic score", gr.Number).info("used for refiner model prompt"), })) +options_templates.update(options_section(('sd3', "Stable Diffusion 3", "sd"), { + "sd3_enable_t5": OptionInfo(False, "Enable T5").info("load T5 text encoder; increases VRAM use by a lot, potentially improving quality of generation; requires model reload to apply"), +})) + options_templates.update(options_section(('vae', "VAE", "sd"), { "sd_vae_explanation": OptionHTML(""" VAE is a neural network that transforms a standard RGB @@ -194,7 +219,6 @@ "img2img_color_correction": OptionInfo(False, "Apply color correction to img2img results to match original colors."), "img2img_fix_steps": OptionInfo(False, "With img2img, do exactly the amount of steps the slider specifies.").info("normally you'd do less with less denoising"), "img2img_background_color": OptionInfo("#ffffff", "With img2img, fill transparent parts of the input image with this color.", ui_components.FormColorPicker, {}), - "img2img_editor_height": OptionInfo(720, "Height of the image editor", gr.Slider, {"minimum": 80, "maximum": 1600, "step": 1}).info("in pixels").needs_reload_ui(), "img2img_sketch_default_brush_color": OptionInfo("#ffffff", "Sketch initial brush color", ui_components.FormColorPicker, {}).info("default brush color of img2img sketch").needs_reload_ui(), "img2img_inpaint_mask_brush_color": OptionInfo("#ffffff", "Inpaint mask brush color", ui_components.FormColorPicker, {}).info("brush color of inpaint mask").needs_reload_ui(), "img2img_inpaint_sketch_default_brush_color": OptionInfo("#ffffff", "Inpaint sketch initial brush color", ui_components.FormColorPicker, {}).info("default brush color of img2img inpaint sketch").needs_reload_ui(), @@ -206,14 +230,15 @@ options_templates.update(options_section(('optimizations', "Optimizations", "sd"), { "cross_attention_optimization": OptionInfo("Automatic", "Cross attention optimization", gr.Dropdown, lambda: {"choices": shared_items.cross_attention_optimizations()}), - "s_min_uncond": OptionInfo(0.0, "Negative Guidance minimum sigma", gr.Slider, {"minimum": 0.0, "maximum": 15.0, "step": 0.01}).link("PR", "https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/9177").info("skip negative prompt for some steps when the image is almost ready; 0=disable, higher=faster"), + "s_min_uncond": OptionInfo(0.0, "Negative Guidance minimum sigma", gr.Slider, {"minimum": 0.0, "maximum": 15.0, "step": 0.01}, infotext='NGMS').link("PR", "https://github.com/AUTOMATIC1111/stablediffusion-webui/pull/9177").info("skip negative prompt for some steps when the image is almost ready; 0=disable, higher=faster"), + "s_min_uncond_all": OptionInfo(False, "Negative Guidance minimum sigma all steps", infotext='NGMS all steps').info("By default, NGMS above skips every other step; this makes it skip all steps"), "token_merging_ratio": OptionInfo(0.0, "Token merging ratio", gr.Slider, {"minimum": 0.0, "maximum": 0.9, "step": 0.1}, infotext='Token merging ratio').link("PR", "https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/9256").info("0=disable, higher=faster"), "token_merging_ratio_img2img": OptionInfo(0.0, "Token merging ratio for img2img", gr.Slider, {"minimum": 0.0, "maximum": 0.9, "step": 0.1}).info("only applies if non-zero and overrides above"), "token_merging_ratio_hr": OptionInfo(0.0, "Token merging ratio for high-res pass", gr.Slider, {"minimum": 0.0, "maximum": 0.9, "step": 0.1}, infotext='Token merging ratio hr').info("only applies if non-zero and overrides above"), "pad_cond_uncond": OptionInfo(False, "Pad prompt/negative prompt", infotext='Pad conds').info("improves performance when prompt and negative prompt have different lengths; changes seeds"), "pad_cond_uncond_v0": OptionInfo(False, "Pad prompt/negative prompt (v0)", infotext='Pad conds v0').info("alternative implementation for the above; used prior to 1.6.0 for DDIM sampler; overrides the above if set; WARNING: truncates negative prompt if it's too long; changes seeds"), "persistent_cond_cache": OptionInfo(True, "Persistent cond cache").info("do not recalculate conds from prompts if prompts have not changed since previous calculation"), - "batch_cond_uncond": OptionInfo(True, "Batch cond/uncond").info("do both conditional and unconditional denoising in one batch; uses a bit more VRAM during sampling, but improves speed; previously this was controlled by --always-batch-cond-uncond comandline argument"), + "batch_cond_uncond": OptionInfo(True, "Batch cond/uncond").info("do both conditional and unconditional denoising in one batch; uses a bit more VRAM during sampling, but improves speed; previously this was controlled by --always-batch-cond-uncond commandline argument"), "fp8_storage": OptionInfo("Disable", "FP8 weight", gr.Radio, {"choices": ["Disable", "Enable for SDXL", "Enable"]}).info("Use FP8 to store Linear/Conv layers' weight. Require pytorch>=2.1.0."), "cache_fp16_weight": OptionInfo(False, "Cache FP16 weight for LoRA").info("Cache fp16 weight when enabling FP8, will increase the quality of LoRA. Use more system ram."), })) @@ -224,10 +249,10 @@ "use_old_karras_scheduler_sigmas": OptionInfo(False, "Use old karras scheduler sigmas (0.1 to 10)."), "no_dpmpp_sde_batch_determinism": OptionInfo(False, "Do not make DPM++ SDE deterministic across different batch sizes."), "use_old_hires_fix_width_height": OptionInfo(False, "For hires fix, use width/height sliders to set final resolution rather than first pass (disables Upscale by, Resize width/height to)."), - "dont_fix_second_order_samplers_schedule": OptionInfo(False, "Do not fix prompt schedule for second order samplers."), "hires_fix_use_firstpass_conds": OptionInfo(False, "For hires fix, calculate conds of second pass using extra networks of first pass."), "use_old_scheduling": OptionInfo(False, "Use old prompt editing timelines.", infotext="Old prompt editing timelines").info("For [red:green:N]; old: If N < 1, it's a fraction of steps (and hires fix uses range from 0 to 1), if N >= 1, it's an absolute number of steps; new: If N has a decimal point in it, it's a fraction of steps (and hires fix uses range from 1 to 2), othewrwise it's an absolute number of steps"), - "use_downcasted_alpha_bar": OptionInfo(False, "Downcast model alphas_cumprod to fp16 before sampling. For reproducing old seeds.", infotext="Downcast alphas_cumprod") + "use_downcasted_alpha_bar": OptionInfo(False, "Downcast model alphas_cumprod to fp16 before sampling. For reproducing old seeds.", infotext="Downcast alphas_cumprod"), + "refiner_switch_by_sample_steps": OptionInfo(False, "Switch to refiner by sampling steps instead of model timesteps. Old behavior for refiner.", infotext="Refiner switch by sampling steps") })) options_templates.update(options_section(('interrogate', "Interrogate"), { @@ -257,7 +282,9 @@ "extra_networks_card_description_is_html": OptionInfo(False, "Treat card description as HTML"), "extra_networks_card_order_field": OptionInfo("Path", "Default order field for Extra Networks cards", gr.Dropdown, {"choices": ['Path', 'Name', 'Date Created', 'Date Modified']}).needs_reload_ui(), "extra_networks_card_order": OptionInfo("Ascending", "Default order for Extra Networks cards", gr.Dropdown, {"choices": ['Ascending', 'Descending']}).needs_reload_ui(), - "extra_networks_tree_view_default_enabled": OptionInfo(False, "Enables the Extra Networks directory tree view by default").needs_reload_ui(), + "extra_networks_tree_view_style": OptionInfo("Dirs", "Extra Networks directory view style", gr.Radio, {"choices": ["Tree", "Dirs"]}).needs_reload_ui(), + "extra_networks_tree_view_default_enabled": OptionInfo(True, "Show the Extra Networks directory view by default").needs_reload_ui(), + "extra_networks_tree_view_default_width": OptionInfo(180, "Default width for the Extra Networks directory tree view", gr.Number).needs_reload_ui(), "extra_networks_add_text_separator": OptionInfo(" ", "Extra networks separator").info("extra text to add before <...> when adding extra network to prompt"), "ui_extra_networks_tab_reorder": OptionInfo("", "Extra networks tab order").needs_reload_ui(), "textual_inversion_print_at_load": OptionInfo(False, "Print a list of Textual Inversion embeddings when loading model"), @@ -311,6 +338,8 @@ "show_progress_in_title": OptionInfo(True, "Show generation progress in window title."), "send_seed": OptionInfo(True, "Send seed when sending prompt or image to other interface"), "send_size": OptionInfo(True, "Send size when sending prompt or image to another interface"), + "enable_reloading_ui_scripts": OptionInfo(False, "Reload UI scripts when using Reload UI option").info("useful for developing: if you make changes to UI scripts code, it is applied when the UI is reloded."), + })) @@ -351,6 +380,7 @@ "live_preview_refresh_period": OptionInfo(1000, "Progressbar and preview update period").info("in milliseconds"), "live_preview_fast_interrupt": OptionInfo(False, "Return image with chosen live preview method on interrupt").info("makes interrupts faster"), "js_live_preview_in_modal_lightbox": OptionInfo(False, "Show Live preview in full page image viewer"), + "prevent_screen_sleep_during_generation": OptionInfo(True, "Prevent screen sleep during generation"), })) options_templates.update(options_section(('sampler-params', "Sampler parameters", "sd"), { @@ -362,22 +392,25 @@ 's_tmin': OptionInfo(0.0, "sigma tmin", gr.Slider, {"minimum": 0.0, "maximum": 10.0, "step": 0.01}, infotext='Sigma tmin').info('enable stochasticity; start value of the sigma range; only applies to Euler, Heun, and DPM2'), 's_tmax': OptionInfo(0.0, "sigma tmax", gr.Slider, {"minimum": 0.0, "maximum": 999.0, "step": 0.01}, infotext='Sigma tmax').info("0 = inf; end value of the sigma range; only applies to Euler, Heun, and DPM2"), 's_noise': OptionInfo(1.0, "sigma noise", gr.Slider, {"minimum": 0.0, "maximum": 1.1, "step": 0.001}, infotext='Sigma noise').info('amount of additional noise to counteract loss of detail during sampling'), - 'k_sched_type': OptionInfo("Automatic", "Scheduler type", gr.Dropdown, {"choices": ["Automatic", "karras", "exponential", "polyexponential"]}, infotext='Schedule type').info("lets you override the noise schedule for k-diffusion samplers; choosing Automatic disables the three parameters below"), 'sigma_min': OptionInfo(0.0, "sigma min", gr.Number, infotext='Schedule min sigma').info("0 = default (~0.03); minimum noise strength for k-diffusion noise scheduler"), 'sigma_max': OptionInfo(0.0, "sigma max", gr.Number, infotext='Schedule max sigma').info("0 = default (~14.6); maximum noise strength for k-diffusion noise scheduler"), 'rho': OptionInfo(0.0, "rho", gr.Number, infotext='Schedule rho').info("0 = default (7 for karras, 1 for polyexponential); higher values result in a steeper noise schedule (decreases faster)"), 'eta_noise_seed_delta': OptionInfo(0, "Eta noise seed delta", gr.Number, {"precision": 0}, infotext='ENSD').info("ENSD; does not improve anything, just produces different results for ancestral samplers - only useful for reproducing images"), 'always_discard_next_to_last_sigma': OptionInfo(False, "Always discard next-to-last sigma", infotext='Discard penultimate sigma').link("PR", "https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/6044"), - 'sgm_noise_multiplier': OptionInfo(False, "SGM noise multiplier", infotext='SGM noise multplier').link("PR", "https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/12818").info("Match initial noise to official SDXL implementation - only useful for reproducing images"), + 'sgm_noise_multiplier': OptionInfo(False, "SGM noise multiplier", infotext='SGM noise multiplier').link("PR", "https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/12818").info("Match initial noise to official SDXL implementation - only useful for reproducing images"), 'uni_pc_variant': OptionInfo("bh1", "UniPC variant", gr.Radio, {"choices": ["bh1", "bh2", "vary_coeff"]}, infotext='UniPC variant'), 'uni_pc_skip_type': OptionInfo("time_uniform", "UniPC skip type", gr.Radio, {"choices": ["time_uniform", "time_quadratic", "logSNR"]}, infotext='UniPC skip type'), 'uni_pc_order': OptionInfo(3, "UniPC order", gr.Slider, {"minimum": 1, "maximum": 50, "step": 1}, infotext='UniPC order').info("must be < sampling steps"), 'uni_pc_lower_order_final': OptionInfo(True, "UniPC lower order final", infotext='UniPC lower order final'), - 'sd_noise_schedule': OptionInfo("Default", "Noise schedule for sampling", gr.Radio, {"choices": ["Default", "Zero Terminal SNR"]}, infotext="Noise Schedule").info("for use with zero terminal SNR trained models") + 'sd_noise_schedule': OptionInfo("Default", "Noise schedule for sampling", gr.Radio, {"choices": ["Default", "Zero Terminal SNR"]}, infotext="Noise Schedule").info("for use with zero terminal SNR trained models"), + 'skip_early_cond': OptionInfo(0.0, "Ignore negative prompt during early sampling", gr.Slider, {"minimum": 0.0, "maximum": 1.0, "step": 0.01}, infotext="Skip Early CFG").info("disables CFG on a proportion of steps at the beginning of generation; 0=skip none; 1=skip all; can both improve sample diversity/quality and speed up sampling"), + 'beta_dist_alpha': OptionInfo(0.6, "Beta scheduler - alpha", gr.Slider, {"minimum": 0.01, "maximum": 1.0, "step": 0.01}, infotext='Beta scheduler alpha').info('Default = 0.6; the alpha parameter of the beta distribution used in Beta sampling'), + 'beta_dist_beta': OptionInfo(0.6, "Beta scheduler - beta", gr.Slider, {"minimum": 0.01, "maximum": 1.0, "step": 0.01}, infotext='Beta scheduler beta').info('Default = 0.6; the beta parameter of the beta distribution used in Beta sampling'), })) options_templates.update(options_section(('postprocessing', "Postprocessing", "postprocessing"), { 'postprocessing_enable_in_main_ui': OptionInfo([], "Enable postprocessing operations in txt2img and img2img tabs", ui_components.DropdownMulti, lambda: {"choices": [x.name for x in shared_items.postprocessing_scripts()]}), + 'postprocessing_disable_in_extras': OptionInfo([], "Disable postprocessing operations in extras tab", ui_components.DropdownMulti, lambda: {"choices": [x.name for x in shared_items.postprocessing_scripts()]}), 'postprocessing_operation_order': OptionInfo([], "Postprocessing operation order", ui_components.DropdownMulti, lambda: {"choices": [x.name for x in shared_items.postprocessing_scripts()]}), 'upscaling_max_images_in_cache': OptionInfo(5, "Maximum number of images in upscaling cache", gr.Slider, {"minimum": 0, "maximum": 10, "step": 1}), 'postprocessing_existing_caption_action': OptionInfo("Ignore", "Action for existing captions", gr.Radio, {"choices": ["Ignore", "Keep", "Prepend", "Append"]}).info("when generating captions using postprocessing; Ignore = use generated; Keep = use original; Prepend/Append = combine both"), diff --git a/modules/shared_state.py b/modules/shared_state.py index 5da5c7a06..bdbb47147 100644 --- a/modules/shared_state.py +++ b/modules/shared_state.py @@ -169,5 +169,7 @@ def do_set_current_image(self): @torch.inference_mode() def assign_current_image(self, image): + if shared.opts.live_previews_image_format == 'jpeg' and image.mode in ('RGBA', 'P'): + image = image.convert('RGB') self.current_image = image self.id_live_preview += 1 diff --git a/modules/styles.py b/modules/styles.py index 60bd8a7fb..25f22d3dd 100644 --- a/modules/styles.py +++ b/modules/styles.py @@ -1,3 +1,4 @@ +from __future__ import annotations from pathlib import Path from modules import errors import csv @@ -42,7 +43,7 @@ def extract_style_text_from_prompt(style_text, prompt): stripped_style_text = style_text.strip() if "{prompt}" in stripped_style_text: - left, right = stripped_style_text.split("{prompt}", 2) + left, _, right = stripped_style_text.partition("{prompt}") if stripped_prompt.startswith(left) and stripped_prompt.endswith(right): prompt = stripped_prompt[len(left):len(stripped_prompt)-len(right)] return True, prompt diff --git a/modules/sysinfo.py b/modules/sysinfo.py index f336251e4..e9a83d74e 100644 --- a/modules/sysinfo.py +++ b/modules/sysinfo.py @@ -1,15 +1,13 @@ import json import os import sys - +import subprocess import platform import hashlib -import pkg_resources -import psutil import re +from pathlib import Path -import launch -from modules import paths_internal, timer, shared, extensions, errors +from modules import paths_internal, timer, shared_cmd_options, errors, launch_utils checksum_token = "DontStealMyGamePlz__WINNERS_DONT_USE_DRUGS__DONT_COPY_THAT_FLOPPY" environment_whitelist = { @@ -69,14 +67,46 @@ def check(x): return h.hexdigest() == m.group(1) -def get_dict(): - ram = psutil.virtual_memory() +def get_cpu_info(): + cpu_info = {"model": platform.processor()} + try: + import psutil + cpu_info["count logical"] = psutil.cpu_count(logical=True) + cpu_info["count physical"] = psutil.cpu_count(logical=False) + except Exception as e: + cpu_info["error"] = str(e) + return cpu_info + + +def get_ram_info(): + try: + import psutil + ram = psutil.virtual_memory() + return {x: pretty_bytes(getattr(ram, x, 0)) for x in ["total", "used", "free", "active", "inactive", "buffers", "cached", "shared"] if getattr(ram, x, 0) != 0} + except Exception as e: + return str(e) + + +def get_packages(): + try: + return subprocess.check_output([sys.executable, '-m', 'pip', 'freeze', '--all']).decode("utf8").splitlines() + except Exception as pip_error: + try: + import importlib.metadata + packages = importlib.metadata.distributions() + return sorted([f"{package.metadata['Name']}=={package.version}" for package in packages]) + except Exception as e2: + return {'error pip': pip_error, 'error importlib': str(e2)} + +def get_dict(): + config = get_config() res = { "Platform": platform.platform(), "Python": platform.python_version(), - "Version": launch.git_tag(), - "Commit": launch.commit_hash(), + "Version": launch_utils.git_tag(), + "Commit": launch_utils.commit_hash(), + "Git status": git_status(paths_internal.script_path), "Script path": paths_internal.script_path, "Data path": paths_internal.data_path, "Extensions dir": paths_internal.extensions_dir, @@ -84,20 +114,14 @@ def get_dict(): "Commandline": get_argv(), "Torch env info": get_torch_sysinfo(), "Exceptions": errors.get_exceptions(), - "CPU": { - "model": platform.processor(), - "count logical": psutil.cpu_count(logical=True), - "count physical": psutil.cpu_count(logical=False), - }, - "RAM": { - x: pretty_bytes(getattr(ram, x, 0)) for x in ["total", "used", "free", "active", "inactive", "buffers", "cached", "shared"] if getattr(ram, x, 0) != 0 - }, - "Extensions": get_extensions(enabled=True), - "Inactive extensions": get_extensions(enabled=False), + "CPU": get_cpu_info(), + "RAM": get_ram_info(), + "Extensions": get_extensions(enabled=True, fallback_disabled_extensions=config.get('disabled_extensions', [])), + "Inactive extensions": get_extensions(enabled=False, fallback_disabled_extensions=config.get('disabled_extensions', [])), "Environment": get_environment(), - "Config": get_config(), + "Config": config, "Startup": timer.startup_record, - "Packages": sorted([f"{pkg.key}=={pkg.version}" for pkg in pkg_resources.working_set]), + "Packages": get_packages(), } return res @@ -111,11 +135,11 @@ def get_argv(): res = [] for v in sys.argv: - if shared.cmd_opts.gradio_auth and shared.cmd_opts.gradio_auth == v: + if shared_cmd_options.cmd_opts.gradio_auth and shared_cmd_options.cmd_opts.gradio_auth == v: res.append("") continue - if shared.cmd_opts.api_auth and shared.cmd_opts.api_auth == v: + if shared_cmd_options.cmd_opts.api_auth and shared_cmd_options.cmd_opts.api_auth == v: res.append("") continue @@ -123,6 +147,7 @@ def get_argv(): return res + re_newline = re.compile(r"\r*\n") @@ -136,25 +161,55 @@ def get_torch_sysinfo(): return str(e) -def get_extensions(*, enabled): +def run_git(path, *args): + try: + return subprocess.check_output([launch_utils.git, '-C', path, *args], shell=False, encoding='utf8').strip() + except Exception as e: + return str(e) + + +def git_status(path): + if (Path(path) / '.git').is_dir(): + return run_git(paths_internal.script_path, 'status') + +def get_info_from_repo_path(path: Path): + is_repo = (path / '.git').is_dir() + return { + 'name': path.name, + 'path': str(path), + 'commit': run_git(path, 'rev-parse', 'HEAD') if is_repo else None, + 'branch': run_git(path, 'branch', '--show-current') if is_repo else None, + 'remote': run_git(path, 'remote', 'get-url', 'origin') if is_repo else None, + } + + +def get_extensions(*, enabled, fallback_disabled_extensions=None): try: - def to_json(x: extensions.Extension): - return { - "name": x.name, - "path": x.path, - "version": x.version, - "branch": x.branch, - "remote": x.remote, - } - - return [to_json(x) for x in extensions.extensions if not x.is_builtin and x.enabled == enabled] + from modules import extensions + if extensions.extensions: + def to_json(x: extensions.Extension): + return { + "name": x.name, + "path": x.path, + "commit": x.commit_hash, + "branch": x.branch, + "remote": x.remote, + } + return [to_json(x) for x in extensions.extensions if not x.is_builtin and x.enabled == enabled] + else: + return [get_info_from_repo_path(d) for d in Path(paths_internal.extensions_dir).iterdir() if d.is_dir() and enabled != (str(d.name) in fallback_disabled_extensions)] except Exception as e: return str(e) def get_config(): try: + from modules import shared return shared.opts.data - except Exception as e: - return str(e) + except Exception as _: + try: + with open(shared_cmd_options.cmd_opts.ui_settings_file, 'r') as f: + return json.load(f) + except Exception as e: + return str(e) diff --git a/modules/textual_inversion/autocrop.py b/modules/textual_inversion/autocrop.py index e223a2e0c..ca858ef4c 100644 --- a/modules/textual_inversion/autocrop.py +++ b/modules/textual_inversion/autocrop.py @@ -65,7 +65,7 @@ def crop_image(im, settings): rect[3] -= 1 d.rectangle(rect, outline=GREEN) results.append(im_debug) - if settings.destop_view_image: + if settings.desktop_view_image: im_debug.show() return results @@ -341,5 +341,5 @@ def __init__(self, crop_width=512, crop_height=512, corner_points_weight=0.5, en self.entropy_points_weight = entropy_points_weight self.face_points_weight = face_points_weight self.annotate_image = annotate_image - self.destop_view_image = False + self.desktop_view_image = False self.dnn_model_path = dnn_model_path diff --git a/modules/textual_inversion/dataset.py b/modules/textual_inversion/dataset.py index 7ee050615..71c032df7 100644 --- a/modules/textual_inversion/dataset.py +++ b/modules/textual_inversion/dataset.py @@ -2,7 +2,6 @@ import numpy as np import PIL import torch -from PIL import Image from torch.utils.data import Dataset, DataLoader, Sampler from torchvision import transforms from collections import defaultdict @@ -10,7 +9,7 @@ import random import tqdm -from modules import devices, shared +from modules import devices, shared, images import re from ldm.modules.distributions.distributions import DiagonalGaussianDistribution @@ -61,7 +60,7 @@ def __init__(self, data_root, width, height, repeats, flip_p=0.5, placeholder_to if shared.state.interrupted: raise Exception("interrupted") try: - image = Image.open(path) + image = images.read(path) #Currently does not work for single color transparency #We would need to read image.info['transparency'] for that if use_weight and 'A' in image.getbands(): diff --git a/modules/textual_inversion/image_embedding.py b/modules/textual_inversion/image_embedding.py index 81cff7bf1..eac0f9760 100644 --- a/modules/textual_inversion/image_embedding.py +++ b/modules/textual_inversion/image_embedding.py @@ -1,12 +1,16 @@ import base64 import json +import os.path import warnings +import logging import numpy as np import zlib from PIL import Image, ImageDraw import torch +logger = logging.getLogger(__name__) + class EmbeddingEncoder(json.JSONEncoder): def default(self, obj): @@ -43,7 +47,7 @@ def lcg(m=2**32, a=1664525, c=1013904223, seed=0): def xor_block(block): g = lcg() - randblock = np.array([next(g) for _ in range(np.product(block.shape))]).astype(np.uint8).reshape(block.shape) + randblock = np.array([next(g) for _ in range(np.prod(block.shape))]).astype(np.uint8).reshape(block.shape) return np.bitwise_xor(block.astype(np.uint8), randblock & 0x0F) @@ -114,7 +118,7 @@ def extract_image_data_embed(image): outarr = crop_black(np.array(image.convert('RGB').getdata()).reshape(image.size[1], image.size[0], d).astype(np.uint8)) & 0x0F black_cols = np.where(np.sum(outarr, axis=(0, 2)) == 0) if black_cols[0].shape[0] < 2: - print('No Image data blocks found.') + logger.debug(f'{os.path.basename(getattr(image, "filename", "unknown image file"))}: no embedded information found.') return None data_block_lower = outarr[:, :black_cols[0].min(), :].astype(np.uint8) @@ -193,11 +197,11 @@ def caption_image_overlay(srcimage, title, footerLeft, footerMid, footerRight, t embedded_image = insert_image_data_embed(cap_image, test_embed) - retrived_embed = extract_image_data_embed(embedded_image) + retrieved_embed = extract_image_data_embed(embedded_image) - assert str(retrived_embed) == str(test_embed) + assert str(retrieved_embed) == str(test_embed) - embedded_image2 = insert_image_data_embed(cap_image, retrived_embed) + embedded_image2 = insert_image_data_embed(cap_image, retrieved_embed) assert embedded_image == embedded_image2 diff --git a/modules/textual_inversion/logging.py b/modules/textual_inversion/saving_settings.py similarity index 96% rename from modules/textual_inversion/logging.py rename to modules/textual_inversion/saving_settings.py index 45823eb11..953051409 100644 --- a/modules/textual_inversion/logging.py +++ b/modules/textual_inversion/saving_settings.py @@ -1,64 +1,64 @@ -import datetime -import json -import os - -saved_params_shared = { - "batch_size", - "clip_grad_mode", - "clip_grad_value", - "create_image_every", - "data_root", - "gradient_step", - "initial_step", - "latent_sampling_method", - "learn_rate", - "log_directory", - "model_hash", - "model_name", - "num_of_dataset_images", - "steps", - "template_file", - "training_height", - "training_width", -} -saved_params_ti = { - "embedding_name", - "num_vectors_per_token", - "save_embedding_every", - "save_image_with_stored_embedding", -} -saved_params_hypernet = { - "activation_func", - "add_layer_norm", - "hypernetwork_name", - "layer_structure", - "save_hypernetwork_every", - "use_dropout", - "weight_init", -} -saved_params_all = saved_params_shared | saved_params_ti | saved_params_hypernet -saved_params_previews = { - "preview_cfg_scale", - "preview_height", - "preview_negative_prompt", - "preview_prompt", - "preview_sampler_index", - "preview_seed", - "preview_steps", - "preview_width", -} - - -def save_settings_to_file(log_directory, all_params): - now = datetime.datetime.now() - params = {"datetime": now.strftime("%Y-%m-%d %H:%M:%S")} - - keys = saved_params_all - if all_params.get('preview_from_txt2img'): - keys = keys | saved_params_previews - - params.update({k: v for k, v in all_params.items() if k in keys}) - - filename = f'settings-{now.strftime("%Y-%m-%d-%H-%M-%S")}.json' - with open(os.path.join(log_directory, filename), "w") as file: - json.dump(params, file, indent=4) +import datetime +import json +import os + +saved_params_shared = { + "batch_size", + "clip_grad_mode", + "clip_grad_value", + "create_image_every", + "data_root", + "gradient_step", + "initial_step", + "latent_sampling_method", + "learn_rate", + "log_directory", + "model_hash", + "model_name", + "num_of_dataset_images", + "steps", + "template_file", + "training_height", + "training_width", +} +saved_params_ti = { + "embedding_name", + "num_vectors_per_token", + "save_embedding_every", + "save_image_with_stored_embedding", +} +saved_params_hypernet = { + "activation_func", + "add_layer_norm", + "hypernetwork_name", + "layer_structure", + "save_hypernetwork_every", + "use_dropout", + "weight_init", +} +saved_params_all = saved_params_shared | saved_params_ti | saved_params_hypernet +saved_params_previews = { + "preview_cfg_scale", + "preview_height", + "preview_negative_prompt", + "preview_prompt", + "preview_sampler_index", + "preview_seed", + "preview_steps", + "preview_width", +} + + +def save_settings_to_file(log_directory, all_params): + now = datetime.datetime.now() + params = {"datetime": now.strftime("%Y-%m-%d %H:%M:%S")} + + keys = saved_params_all + if all_params.get('preview_from_txt2img'): + keys = keys | saved_params_previews + + params.update({k: v for k, v in all_params.items() if k in keys}) + + filename = f'settings-{now.strftime("%Y-%m-%d-%H-%M-%S")}.json' + with open(os.path.join(log_directory, filename), "w") as file: + json.dump(params, file, indent=4) diff --git a/modules/textual_inversion/textual_inversion.py b/modules/textual_inversion/textual_inversion.py index 6d815c0b3..dc7833e93 100644 --- a/modules/textual_inversion/textual_inversion.py +++ b/modules/textual_inversion/textual_inversion.py @@ -17,7 +17,7 @@ from modules.textual_inversion.learn_schedule import LearnRateScheduler from modules.textual_inversion.image_embedding import embedding_to_b64, embedding_from_b64, insert_image_data_embed, extract_image_data_embed, caption_image_overlay -from modules.textual_inversion.logging import save_settings_to_file +from modules.textual_inversion.saving_settings import save_settings_to_file TextualInversionTemplate = namedtuple("TextualInversionTemplate", ["name", "path"]) @@ -172,7 +172,7 @@ def load_from_file(self, path, filename): if data: name = data.get('name', name) else: - # if data is None, means this is not an embeding, just a preview image + # if data is None, means this is not an embedding, just a preview image return elif ext in ['.BIN', '.PT']: data = torch.load(path, map_location="cpu") @@ -181,12 +181,16 @@ def load_from_file(self, path, filename): else: return - embedding = create_embedding_from_data(data, name, filename=filename, filepath=path) + if data is not None: + embedding = create_embedding_from_data(data, name, filename=filename, filepath=path) - if self.expected_shape == -1 or self.expected_shape == embedding.shape: - self.register_embedding(embedding, shared.sd_model) + if self.expected_shape == -1 or self.expected_shape == embedding.shape: + self.register_embedding(embedding, shared.sd_model) + else: + self.skipped_embeddings[name] = embedding else: - self.skipped_embeddings[name] = embedding + print(f"Unable to load Textual inversion embedding due to data issue: '{name}'.") + def load_from_dir(self, embdir): if not os.path.isdir(embdir.path): diff --git a/modules/torch_utils.py b/modules/torch_utils.py index e5b52393e..5ea3da094 100644 --- a/modules/torch_utils.py +++ b/modules/torch_utils.py @@ -1,6 +1,7 @@ from __future__ import annotations import torch.nn +import torch def get_param(model) -> torch.nn.Parameter: @@ -15,3 +16,10 @@ def get_param(model) -> torch.nn.Parameter: return param raise ValueError(f"No parameters found in model {model!r}") + + +def float64(t: torch.Tensor): + """return torch.float64 if device is not mps or xpu, else return torch.float32""" + if t.device.type in ['mps', 'xpu']: + return torch.float32 + return torch.float64 diff --git a/modules/txt2img.py b/modules/txt2img.py index 04d62a0ac..9e3d7378e 100644 --- a/modules/txt2img.py +++ b/modules/txt2img.py @@ -12,7 +12,7 @@ from modules_forge import main_thread -def txt2img_create_processing(id_task: str, request: gr.Request, prompt: str, negative_prompt: str, prompt_styles, steps: int, sampler_name: str, n_iter: int, batch_size: int, cfg_scale: float, height: int, width: int, enable_hr: bool, denoising_strength: float, hr_scale: float, hr_upscaler: str, hr_second_pass_steps: int, hr_resize_x: int, hr_resize_y: int, hr_checkpoint_name: str, hr_sampler_name: str, hr_prompt: str, hr_negative_prompt, override_settings_texts, *args, force_enable_hr=False): +def txt2img_create_processing(id_task: str, request: gr.Request, prompt: str, negative_prompt: str, prompt_styles, n_iter: int, batch_size: int, cfg_scale: float, height: int, width: int, enable_hr: bool, denoising_strength: float, hr_scale: float, hr_upscaler: str, hr_second_pass_steps: int, hr_resize_x: int, hr_resize_y: int, hr_checkpoint_name: str, hr_sampler_name: str, hr_scheduler: str, hr_prompt: str, hr_negative_prompt, override_settings_texts, *args, force_enable_hr=False): override_settings = create_override_settings_dict(override_settings_texts) if force_enable_hr: @@ -25,10 +25,8 @@ def txt2img_create_processing(id_task: str, request: gr.Request, prompt: str, ne prompt=prompt, styles=prompt_styles, negative_prompt=negative_prompt, - sampler_name=sampler_name, batch_size=batch_size, n_iter=n_iter, - steps=steps, cfg_scale=cfg_scale, width=width, height=height, @@ -41,6 +39,7 @@ def txt2img_create_processing(id_task: str, request: gr.Request, prompt: str, ne hr_resize_y=hr_resize_y, hr_checkpoint_name=None if hr_checkpoint_name == 'Use same checkpoint' else hr_checkpoint_name, hr_sampler_name=None if hr_sampler_name == 'Use same sampler' else hr_sampler_name, + hr_scheduler=None if hr_scheduler == 'Use same scheduler' else hr_scheduler, hr_prompt=hr_prompt, hr_negative_prompt=hr_negative_prompt, override_settings=override_settings, diff --git a/modules/ui.py b/modules/ui.py index 300930aa7..b7200fb87 100644 --- a/modules/ui.py +++ b/modules/ui.py @@ -8,11 +8,11 @@ import gradio as gr import gradio.utils -import numpy as np +from gradio.components.image_editor import Brush from PIL import Image, PngImagePlugin # noqa: F401 -from modules.call_queue import wrap_gradio_gpu_call, wrap_queued_call, wrap_gradio_call +from modules.call_queue import wrap_gradio_gpu_call, wrap_queued_call, wrap_gradio_call, wrap_gradio_call_no_job # noqa: F401 -from modules import gradio_extensons # noqa: F401 +from modules import gradio_extensions, sd_schedulers # noqa: F401 from modules import sd_hijack, sd_models, script_callbacks, ui_extensions, deepbooru, extra_networks, ui_common, ui_postprocessing, progress, ui_loadsave, shared_items, ui_settings, timer, sysinfo, ui_checkpoint_merger, scripts, sd_samplers, processing, ui_extra_networks, ui_toprow, launch_utils from modules.ui_components import FormRow, FormGroup, ToolButton, FormHTML, InputAccordion, ResizeHandleRow from modules.paths import script_path @@ -29,18 +29,22 @@ from modules import prompt_parser from modules.sd_hijack import model_hijack from modules.infotext_utils import image_from_url_text, PasteField +from modules_forge.forge_canvas.canvas import ForgeCanvas, canvas_head + create_setting_component = ui_settings.create_setting_component warnings.filterwarnings("default" if opts.show_warnings else "ignore", category=UserWarning) -warnings.filterwarnings("default" if opts.show_gradio_deprecation_warnings else "ignore", category=gr.deprecation.GradioDeprecationWarning) +warnings.filterwarnings("default" if opts.show_gradio_deprecation_warnings else "ignore", category=gradio_extensions.GradioDeprecationWarning) # this is a fix for Windows users. Without it, javascript files will be served with text/html content-type and the browser will not show any UI mimetypes.init() mimetypes.add_type('application/javascript', '.js') +mimetypes.add_type('application/javascript', '.mjs') # Likewise, add explicit content-type header for certain missing image types mimetypes.add_type('image/webp', '.webp') +mimetypes.add_type('image/avif', '.avif') if not cmd_opts.share and not cmd_opts.listen: # fix gradio phoning home @@ -99,8 +103,8 @@ def calc_resolution_hires(enable, width, height, hr_scale, hr_resize_x, hr_resiz def resize_from_to_html(width, height, scale_by): - target_width = int(width * scale_by) - target_height = int(height * scale_by) + target_width = int(float(width) * scale_by) + target_height = int(float(height) * scale_by) if not target_width or not target_height: return "no image selected" @@ -109,10 +113,11 @@ def resize_from_to_html(width, height, scale_by): def process_interrogate(interrogation_function, mode, ii_input_dir, ii_output_dir, *ii_singles): - if mode in {0, 1, 3, 4}: + mode = int(mode) + if mode in (0, 1, 3, 4): return [interrogation_function(ii_singles[mode]), None] elif mode == 2: - return [interrogation_function(ii_singles[mode]["image"]), None] + return [interrogation_function(ii_singles[mode]), None] elif mode == 5: assert not shared.cmd_opts.hide_ui_dir_config, "Launched with --hide-ui-dir-config, batch img2img disabled" images = shared.listfiles(ii_input_dir) @@ -235,19 +240,6 @@ def create_output_panel(tabname, outdir, toprow=None): return ui_common.create_output_panel(tabname, outdir, toprow) -def create_sampler_and_steps_selection(choices, tabname): - if opts.samplers_in_dropdown: - with FormRow(elem_id=f"sampler_selection_{tabname}"): - sampler_name = gr.Dropdown(label='Sampling method', elem_id=f"{tabname}_sampling", choices=choices, value=choices[0]) - steps = gr.Slider(minimum=1, maximum=150, step=1, elem_id=f"{tabname}_steps", label="Sampling steps", value=20) - else: - with FormGroup(elem_id=f"sampler_selection_{tabname}"): - steps = gr.Slider(minimum=1, maximum=150, step=1, elem_id=f"{tabname}_steps", label="Sampling steps", value=20) - sampler_name = gr.Radio(label='Sampling method', elem_id=f"{tabname}_sampling", choices=choices, value=choices[0]) - - return steps, sampler_name - - def ordered_ui_categories(): user_order = {x.strip(): i * 2 + 1 for i, x in enumerate(shared.opts.ui_reorder_list)} @@ -275,13 +267,17 @@ def create_ui(): parameters_copypaste.reset() + settings = ui_settings.UiSettings() + settings.register_settings() + scripts.scripts_current = scripts.scripts_txt2img scripts.scripts_txt2img.initialize_scripts(is_img2img=False) - with gr.Blocks(analytics_enabled=False) as txt2img_interface: + with gr.Blocks(analytics_enabled=False, head=canvas_head) as txt2img_interface: toprow = ui_toprow.Toprow(is_img2img=False, is_compact=shared.opts.compact_prompt_box) - dummy_component = gr.Label(visible=False) + dummy_component = gr.Textbox(visible=False) + dummy_component_number = gr.Number(visible=False) extra_tabs = gr.Tabs(elem_id="txt2img_extra_tabs", elem_classes=["extra-networks"]) extra_tabs.__enter__() @@ -298,9 +294,6 @@ def create_ui(): if category == "prompt": toprow.create_inline_toprow_prompts() - if category == "sampler": - steps, sampler_name = create_sampler_and_steps_selection(sd_samplers.visible_sampler_names(), "txt2img") - elif category == "dimensions": with FormRow(): with gr.Column(elem_id="txt2img_column_size", scale=4): @@ -327,7 +320,7 @@ def create_ui(): with gr.Row(elem_id="txt2img_accordions", elem_classes="accordions"): with InputAccordion(False, label="Hires. fix", elem_id="txt2img_hr") as enable_hr: with enable_hr.extra(): - hr_final_resolution = FormHTML(value="", elem_id="txtimg_hr_finalres", label="Upscaled resolution", interactive=False, min_width=0) + hr_final_resolution = FormHTML(value="", elem_id="txtimg_hr_finalres", label="Upscaled resolution") with FormRow(elem_id="txt2img_hires_fix_row1", variant="compact"): hr_upscaler = gr.Dropdown(label="Upscaler", elem_id="txt2img_hr_upscaler", choices=[*shared.latent_upscale_modes, *[x.name for x in shared.sd_upscalers]], value=shared.latent_upscale_default_mode) @@ -341,10 +334,11 @@ def create_ui(): with FormRow(elem_id="txt2img_hires_fix_row3", variant="compact", visible=opts.hires_fix_show_sampler) as hr_sampler_container: - hr_checkpoint_name = gr.Dropdown(label='Hires checkpoint', elem_id="hr_checkpoint", choices=["Use same checkpoint"] + modules.sd_models.checkpoint_tiles(use_short=True), value="Use same checkpoint") + hr_checkpoint_name = gr.Dropdown(label='Checkpoint', elem_id="hr_checkpoint", choices=["Use same checkpoint"] + modules.sd_models.checkpoint_tiles(use_short=True), value="Use same checkpoint") create_refresh_button(hr_checkpoint_name, modules.sd_models.list_models, lambda: {"choices": ["Use same checkpoint"] + modules.sd_models.checkpoint_tiles(use_short=True)}, "hr_checkpoint_refresh") hr_sampler_name = gr.Dropdown(label='Hires sampling method', elem_id="hr_sampler", choices=["Use same sampler"] + sd_samplers.visible_sampler_names(), value="Use same sampler") + hr_scheduler = gr.Dropdown(label='Hires schedule type', elem_id="hr_scheduler", choices=["Use same scheduler"] + [x.label for x in sd_schedulers.schedulers], value="Use same scheduler") with FormRow(elem_id="txt2img_hires_fix_row4", variant="compact", visible=opts.hires_fix_show_prompts) as hr_prompts_container: with gr.Column(scale=80): @@ -399,8 +393,6 @@ def create_ui(): toprow.prompt, toprow.negative_prompt, toprow.ui_styles.dropdown, - steps, - sampler_name, batch_count, batch_size, cfg_scale, @@ -415,6 +407,7 @@ def create_ui(): hr_resize_y, hr_checkpoint_name, hr_sampler_name, + hr_scheduler, hr_prompt, hr_negative_prompt, override_settings, @@ -429,7 +422,7 @@ def create_ui(): txt2img_args = dict( fn=wrap_gradio_gpu_call(modules.txt2img.txt2img, extra_outputs=[None, '', '']), - _js="submit", + js=f"(...args) => {{ return submit(args.slice(0, {len(txt2img_inputs)})); }}", inputs=txt2img_inputs, outputs=txt2img_outputs, show_progress=False, @@ -438,10 +431,11 @@ def create_ui(): toprow.prompt.submit(**txt2img_args) toprow.submit.click(**txt2img_args) + txt2img_upscale_inputs = txt2img_inputs[0:1] + [output_panel.gallery, dummy_component_number, output_panel.generation_info] + txt2img_inputs[1:] output_panel.button_upscale.click( fn=wrap_gradio_gpu_call(modules.txt2img.txt2img_upscale, extra_outputs=[None, '', '']), - _js="submit_txt2img_upscale", - inputs=txt2img_inputs[0:1] + [output_panel.gallery, dummy_component, output_panel.generation_info] + txt2img_inputs[1:], + js=f"(...args) => {{ return submit_txt2img_upscale(args.slice(0, {len(txt2img_upscale_inputs)})); }}", + inputs=txt2img_upscale_inputs, outputs=txt2img_outputs, show_progress=False, ) @@ -464,8 +458,6 @@ def create_ui(): txt2img_paste_fields = [ PasteField(toprow.prompt, "Prompt", api="prompt"), PasteField(toprow.negative_prompt, "Negative prompt", api="negative_prompt"), - PasteField(steps, "Steps", api="steps"), - PasteField(sampler_name, "Sampler", api="sampler_name"), PasteField(cfg_scale, "CFG scale", api="cfg_scale"), PasteField(width, "Size-1", api="width"), PasteField(height, "Size-2", api="height"), @@ -479,8 +471,9 @@ def create_ui(): PasteField(hr_resize_x, "Hires resize-1", api="hr_resize_x"), PasteField(hr_resize_y, "Hires resize-2", api="hr_resize_y"), PasteField(hr_checkpoint_name, "Hires checkpoint", api="hr_checkpoint_name"), - PasteField(hr_sampler_name, "Hires sampler", api="hr_sampler_name"), - PasteField(hr_sampler_container, lambda d: gr.update(visible=True) if d.get("Hires sampler", "Use same sampler") != "Use same sampler" or d.get("Hires checkpoint", "Use same checkpoint") != "Use same checkpoint" else gr.update()), + PasteField(hr_sampler_name, sd_samplers.get_hr_sampler_from_infotext, api="hr_sampler_name"), + PasteField(hr_scheduler, sd_samplers.get_hr_scheduler_from_infotext, api="hr_scheduler"), + PasteField(hr_sampler_container, lambda d: gr.update(visible=True) if d.get("Hires sampler", "Use same sampler") != "Use same sampler" or d.get("Hires checkpoint", "Use same checkpoint") != "Use same checkpoint" or d.get("Hires schedule type", "Use same scheduler") != "Use same scheduler" else gr.update()), PasteField(hr_prompt, "Hires prompt", api="hr_prompt"), PasteField(hr_negative_prompt, "Hires negative prompt", api="hr_negative_prompt"), PasteField(hr_prompts_container, lambda d: gr.update(visible=True) if d.get("Hires prompt", "") != "" or d.get("Hires negative prompt", "") != "" else gr.update()), @@ -491,11 +484,13 @@ def create_ui(): paste_button=toprow.paste, tabname="txt2img", source_text_component=toprow.prompt, source_image_component=None, )) + steps = scripts.scripts_txt2img.script('Sampler').steps + txt2img_preview_params = [ toprow.prompt, toprow.negative_prompt, steps, - sampler_name, + scripts.scripts_txt2img.script('Sampler').sampler_name, cfg_scale, scripts.scripts_txt2img.script('Seed').seed, width, @@ -515,7 +510,7 @@ def create_ui(): scripts.scripts_current = scripts.scripts_img2img scripts.scripts_img2img.initialize_scripts(is_img2img=True) - with gr.Blocks(analytics_enabled=False) as img2img_interface: + with gr.Blocks(analytics_enabled=False, head=canvas_head) as img2img_interface: toprow = ui_toprow.Toprow(is_img2img=True, is_compact=shared.opts.compact_prompt_box) extra_tabs = gr.Tabs(elem_id="img2img_extra_tabs", elem_classes=["extra-networks"]) @@ -532,9 +527,7 @@ def create_ui(): def add_copy_image_controls(tab_name, elem): with gr.Row(variant="compact", elem_id=f"img2img_copy_to_{tab_name}"): - gr.HTML("Copy image to: ", elem_id=f"img2img_label_copy_to_{tab_name}") - - for title, name in zip(['img2img', 'sketch', 'inpaint', 'inpaint sketch'], ['img2img', 'sketch', 'inpaint', 'inpaint_sketch']): + for title, name in zip(['to img2img', 'to sketch', 'to inpaint', 'to inpaint sketch'], ['img2img', 'sketch', 'inpaint', 'inpaint_sketch']): if name == tab_name: gr.Button(title, interactive=False) copy_image_destinations[name] = elem @@ -554,48 +547,45 @@ def add_copy_image_controls(tab_name, elem): img2img_selected_tab = gr.Number(value=0, visible=False) with gr.TabItem('img2img', id='img2img', elem_id="img2img_img2img_tab") as tab_img2img: - init_img = gr.Image(label="Image for img2img", elem_id="img2img_image", show_label=False, source="upload", interactive=True, type="pil", tool="editor", image_mode="RGBA", height=opts.img2img_editor_height) + init_img = ForgeCanvas(elem_id="img2img_image", height=512, no_scribbles=True) add_copy_image_controls('img2img', init_img) with gr.TabItem('Sketch', id='img2img_sketch', elem_id="img2img_img2img_sketch_tab") as tab_sketch: - sketch = gr.Image(label="Image for img2img", elem_id="img2img_sketch", show_label=False, source="upload", interactive=True, type="pil", tool="color-sketch", image_mode="RGB", height=opts.img2img_editor_height, brush_color=opts.img2img_sketch_default_brush_color) + sketch = ForgeCanvas(elem_id="img2img_sketch", height=512) add_copy_image_controls('sketch', sketch) with gr.TabItem('Inpaint', id='inpaint', elem_id="img2img_inpaint_tab") as tab_inpaint: - init_img_with_mask = gr.Image(label="Image for inpainting with mask", show_label=False, elem_id="img2maskimg", source="upload", interactive=True, type="pil", tool="sketch", image_mode="RGBA", height=opts.img2img_editor_height, brush_color=opts.img2img_inpaint_mask_brush_color) + init_img_with_mask = ForgeCanvas(elem_id="img2maskimg", height=512, contrast_scribbles=True) add_copy_image_controls('inpaint', init_img_with_mask) with gr.TabItem('Inpaint sketch', id='inpaint_sketch', elem_id="img2img_inpaint_sketch_tab") as tab_inpaint_color: - inpaint_color_sketch = gr.Image(label="Color sketch inpainting", show_label=False, elem_id="inpaint_sketch", source="upload", interactive=True, type="pil", tool="color-sketch", image_mode="RGB", height=opts.img2img_editor_height, brush_color=opts.img2img_inpaint_sketch_default_brush_color) - inpaint_color_sketch_orig = gr.State(None) + inpaint_color_sketch = ForgeCanvas(elem_id="inpaint_sketch", height=512) add_copy_image_controls('inpaint_sketch', inpaint_color_sketch) - def update_orig(image, state): - if image is not None: - same_size = state is not None and state.size == image.size - has_exact_match = np.any(np.all(np.array(image) == np.array(state), axis=-1)) - edited = same_size and has_exact_match - return image if not edited or state is None else state - - inpaint_color_sketch.change(update_orig, [inpaint_color_sketch, inpaint_color_sketch_orig], inpaint_color_sketch_orig) - with gr.TabItem('Inpaint upload', id='inpaint_upload', elem_id="img2img_inpaint_upload_tab") as tab_inpaint_upload: init_img_inpaint = gr.Image(label="Image for img2img", show_label=False, source="upload", interactive=True, type="pil", elem_id="img_inpaint_base") init_mask_inpaint = gr.Image(label="Mask", source="upload", interactive=True, type="pil", image_mode="RGBA", elem_id="img_inpaint_mask") with gr.TabItem('Batch', id='batch', elem_id="img2img_batch_tab") as tab_batch: - hidden = '
Disabled when launched with --hide-ui-dir-config.' if shared.cmd_opts.hide_ui_dir_config else '' - gr.HTML( - "

Process images in a directory on the same machine where the server is running." + - "
Use an empty output directory to save pictures normally instead of writing to the output directory." + - f"
Add inpaint batch mask directory to enable inpaint batch processing." - f"{hidden}

" - ) - img2img_batch_input_dir = gr.Textbox(label="Input directory", **shared.hide_dirs, elem_id="img2img_batch_input_dir") - img2img_batch_output_dir = gr.Textbox(label="Output directory", **shared.hide_dirs, elem_id="img2img_batch_output_dir") - img2img_batch_inpaint_mask_dir = gr.Textbox(label="Inpaint batch mask directory (required for inpaint batch processing only)", **shared.hide_dirs, elem_id="img2img_batch_inpaint_mask_dir") + with gr.Tabs(elem_id="img2img_batch_source"): + img2img_batch_source_type = gr.Textbox(visible=False, value="upload") + with gr.TabItem('Upload', id='batch_upload', elem_id="img2img_batch_upload_tab") as tab_batch_upload: + img2img_batch_upload = gr.Files(label="Files", interactive=True, elem_id="img2img_batch_upload") + with gr.TabItem('From directory', id='batch_from_dir', elem_id="img2img_batch_from_dir_tab") as tab_batch_from_dir: + hidden = '
Disabled when launched with --hide-ui-dir-config.' if shared.cmd_opts.hide_ui_dir_config else '' + gr.HTML( + "

Process images in a directory on the same machine where the server is running." + + "
Use an empty output directory to save pictures normally instead of writing to the output directory." + + f"
Add inpaint batch mask directory to enable inpaint batch processing." + f"{hidden}

" + ) + img2img_batch_input_dir = gr.Textbox(label="Input directory", **shared.hide_dirs, elem_id="img2img_batch_input_dir") + img2img_batch_output_dir = gr.Textbox(label="Output directory", **shared.hide_dirs, elem_id="img2img_batch_output_dir") + img2img_batch_inpaint_mask_dir = gr.Textbox(label="Inpaint batch mask directory (required for inpaint batch processing only)", **shared.hide_dirs, elem_id="img2img_batch_inpaint_mask_dir") + tab_batch_upload.select(fn=lambda: "upload", inputs=[], outputs=[img2img_batch_source_type]) + tab_batch_from_dir.select(fn=lambda: "from dir", inputs=[], outputs=[img2img_batch_source_type]) with gr.Accordion("PNG info", open=False): - img2img_batch_use_png_info = gr.Checkbox(label="Append png info to prompts", **shared.hide_dirs, elem_id="img2img_batch_use_png_info") + img2img_batch_use_png_info = gr.Checkbox(label="Append png info to prompts", elem_id="img2img_batch_use_png_info") img2img_batch_png_info_dir = gr.Textbox(label="PNG info directory", **shared.hide_dirs, placeholder="Leave empty to use input directory", elem_id="img2img_batch_png_info_dir") img2img_batch_png_info_props = gr.CheckboxGroup(["Prompt", "Negative prompt", "Seed", "CFG scale", "Sampler", "Steps", "Model hash"], label="Parameters to take from png info", info="Prompts from png info will be appended to prompts set in ui.") @@ -604,20 +594,14 @@ def update_orig(image, state): for i, tab in enumerate(img2img_tabs): tab.select(fn=lambda tabnum=i: tabnum, inputs=[], outputs=[img2img_selected_tab]) - def copy_image(img): - if isinstance(img, dict) and 'image' in img: - return img['image'] - - return img - for button, name, elem in copy_image_buttons: button.click( - fn=copy_image, - inputs=[elem], - outputs=[copy_image_destinations[name]], + fn=lambda img: img, + inputs=[elem.background], + outputs=[copy_image_destinations[name].background], ) button.click( - fn=lambda: None, + fn=None, _js=f"switch_to_{name.replace(' ', '_')}", inputs=[], outputs=[], @@ -626,16 +610,13 @@ def copy_image(img): with FormRow(): resize_mode = gr.Radio(label="Resize mode", elem_id="resize_mode", choices=["Just resize", "Crop and resize", "Resize and fill", "Just resize (latent upscale)"], type="index", value="Just resize") - if category == "sampler": - steps, sampler_name = create_sampler_and_steps_selection(sd_samplers.visible_sampler_names(), "img2img") - elif category == "dimensions": with FormRow(): with gr.Column(elem_id="img2img_column_size", scale=4): selected_scale_tab = gr.Number(value=0, visible=False) - with gr.Tabs(): - with gr.Tab(label="Resize to", elem_id="img2img_tab_resize_to") as tab_scale_to: + with gr.Tabs(elem_id="img2img_tabs_resize"): + with gr.Tab(label="Resize to", id="to", elem_id="img2img_tab_resize_to") as tab_scale_to: with FormRow(): with gr.Column(elem_id="img2img_column_size", scale=4): width = gr.Slider(minimum=64, maximum=2048, step=8, label="Width", value=512, elem_id="img2img_width") @@ -644,7 +625,7 @@ def copy_image(img): res_switch_btn = ToolButton(value=switch_values_symbol, elem_id="img2img_res_switch_btn", tooltip="Switch width/height") detect_image_size_btn = ToolButton(value=detect_image_size_symbol, elem_id="img2img_detect_image_size_btn", tooltip="Auto detect size from img2img") - with gr.Tab(label="Resize by", elem_id="img2img_tab_resize_by") as tab_scale_by: + with gr.Tab(label="Resize by", id="by", elem_id="img2img_tab_resize_by") as tab_scale_by: scale_by = gr.Slider(minimum=0.05, maximum=4.0, step=0.05, label="Scale", value=1.0, elem_id="img2img_scale") with FormRow(): @@ -723,12 +704,6 @@ def copy_image(img): if category not in {"accordions"}: scripts.scripts_img2img.setup_ui_for_section(category) - # the code below is meant to update the resolution label after the image in the image selection UI has changed. - # as it is now the event keeps firing continuously for inpaint edits, which ruins the page with constant requests. - # I assume this must be a gradio bug and for now we'll just do it for non-inpaint inputs. - for component in [init_img, sketch]: - component.change(fn=lambda: None, _js="updateImg2imgResizeToTextAfterChangingImage", inputs=[], outputs=[], show_progress=False) - def select_img2img_tab(tab): return gr.update(visible=tab in [2, 3, 4]), gr.update(visible=tab == 3), @@ -741,48 +716,52 @@ def select_img2img_tab(tab): output_panel = create_output_panel("img2img", opts.outdir_img2img_samples, toprow) + submit_img2img_inputs = [ + dummy_component, + img2img_selected_tab, + toprow.prompt, + toprow.negative_prompt, + toprow.ui_styles.dropdown, + init_img.background, + sketch.background, + sketch.foreground, + init_img_with_mask.background, + init_img_with_mask.foreground, + inpaint_color_sketch.background, + inpaint_color_sketch.foreground, + init_img_inpaint, + init_mask_inpaint, + mask_blur, + mask_alpha, + inpainting_fill, + batch_count, + batch_size, + cfg_scale, + image_cfg_scale, + denoising_strength, + selected_scale_tab, + height, + width, + scale_by, + resize_mode, + inpaint_full_res, + inpaint_full_res_padding, + inpainting_mask_invert, + img2img_batch_input_dir, + img2img_batch_output_dir, + img2img_batch_inpaint_mask_dir, + override_settings, + img2img_batch_use_png_info, + img2img_batch_png_info_props, + img2img_batch_png_info_dir, + img2img_batch_source_type, + img2img_batch_upload, + ] + custom_inputs + img2img_args = dict( fn=wrap_gradio_gpu_call(modules.img2img.img2img, extra_outputs=[None, '', '']), - _js="submit_img2img", - inputs=[ - dummy_component, - dummy_component, - toprow.prompt, - toprow.negative_prompt, - toprow.ui_styles.dropdown, - init_img, - sketch, - init_img_with_mask, - inpaint_color_sketch, - inpaint_color_sketch_orig, - init_img_inpaint, - init_mask_inpaint, - steps, - sampler_name, - mask_blur, - mask_alpha, - inpainting_fill, - batch_count, - batch_size, - cfg_scale, - image_cfg_scale, - denoising_strength, - selected_scale_tab, - height, - width, - scale_by, - resize_mode, - inpaint_full_res, - inpaint_full_res_padding, - inpainting_mask_invert, - img2img_batch_input_dir, - img2img_batch_output_dir, - img2img_batch_inpaint_mask_dir, - override_settings, - img2img_batch_use_png_info, - img2img_batch_png_info_props, - img2img_batch_png_info_dir, - ] + custom_inputs, + js=f"(...args) => {{ return submit_img2img(args.slice(0, {len(submit_img2img_inputs)})); }}", + inputs=submit_img2img_inputs, outputs=[ output_panel.gallery, output_panel.generation_info, @@ -798,10 +777,10 @@ def select_img2img_tab(tab): dummy_component, img2img_batch_input_dir, img2img_batch_output_dir, - init_img, - sketch, - init_img_with_mask, - inpaint_color_sketch, + init_img.background, + sketch.background, + init_img_with_mask.background, + inpaint_color_sketch.background, init_img_inpaint, ], outputs=[toprow.prompt, dummy_component], @@ -813,9 +792,9 @@ def select_img2img_tab(tab): res_switch_btn.click(fn=None, _js="function(){switchWidthHeight('img2img')}", inputs=None, outputs=None, show_progress=False) detect_image_size_btn.click( - fn=lambda w, h, _: (w or gr.update(), h or gr.update()), + fn=lambda w, h: (w or gr.update(), h or gr.update()), _js="currentImg2imgSourceResolution", - inputs=[dummy_component, dummy_component, dummy_component], + inputs=[dummy_component, dummy_component], outputs=[width, height], show_progress=False, ) @@ -843,6 +822,8 @@ def select_img2img_tab(tab): **interrogate_args, ) + steps = scripts.scripts_img2img.script('Sampler').steps + toprow.ui_styles.dropdown.change(fn=wrap_queued_call(update_token_counter), inputs=[toprow.prompt, steps, toprow.ui_styles.dropdown], outputs=[toprow.token_counter]) toprow.ui_styles.dropdown.change(fn=wrap_queued_call(update_negative_prompt_token_counter), inputs=[toprow.negative_prompt, steps, toprow.ui_styles.dropdown], outputs=[toprow.negative_token_counter]) toprow.token_button.click(fn=update_token_counter, inputs=[toprow.prompt, steps, toprow.ui_styles.dropdown], outputs=[toprow.token_counter]) @@ -851,8 +832,6 @@ def select_img2img_tab(tab): img2img_paste_fields = [ (toprow.prompt, "Prompt"), (toprow.negative_prompt, "Negative prompt"), - (steps, "Steps"), - (sampler_name, "Sampler"), (cfg_scale, "CFG scale"), (image_cfg_scale, "Image CFG scale"), (width, "Size-1"), @@ -867,8 +846,8 @@ def select_img2img_tab(tab): (inpaint_full_res_padding, 'Masked area padding'), *scripts.scripts_img2img.infotext_fields ] - parameters_copypaste.add_paste_fields("img2img", init_img, img2img_paste_fields, override_settings) - parameters_copypaste.add_paste_fields("inpaint", init_img_with_mask, img2img_paste_fields, override_settings) + parameters_copypaste.add_paste_fields("img2img", init_img.background, img2img_paste_fields, override_settings) + parameters_copypaste.add_paste_fields("inpaint", init_img_with_mask.background, img2img_paste_fields, override_settings) parameters_copypaste.register_paste_params_button(parameters_copypaste.ParamBinding( paste_button=toprow.paste, tabname="img2img", source_text_component=toprow.prompt, source_image_component=None, )) @@ -880,10 +859,10 @@ def select_img2img_tab(tab): scripts.scripts_current = None - with gr.Blocks(analytics_enabled=False) as extras_interface: + with gr.Blocks(analytics_enabled=False, head=canvas_head) as extras_interface: ui_postprocessing.create_ui() - with gr.Blocks(analytics_enabled=False) as pnginfo_interface: + with gr.Blocks(analytics_enabled=False, head=canvas_head) as pnginfo_interface: with ResizeHandleRow(equal_height=False): with gr.Column(variant='panel'): image = gr.Image(elem_id="pnginfo_image", label="Source", source="upload", interactive=True, type="pil") @@ -901,14 +880,14 @@ def select_img2img_tab(tab): )) image.change( - fn=wrap_gradio_call(modules.extras.run_pnginfo), + fn=wrap_gradio_call_no_job(modules.extras.run_pnginfo), inputs=[image], outputs=[html, generation_info, html2], ) modelmerger_ui = ui_checkpoint_merger.UiCheckpointMerger() - with gr.Blocks(analytics_enabled=False) as train_interface: + with gr.Blocks(analytics_enabled=False, head=canvas_head) as train_interface: with gr.Row(equal_height=False): gr.HTML(value="

See wiki for detailed explanation.

") @@ -1007,7 +986,7 @@ def get_textual_inversion_template_names(): with gr.Column(elem_id='ti_gallery_container'): ti_output = gr.Text(elem_id="ti_output", value="", show_label=False) - gr.Gallery(label='Output', show_label=False, elem_id='ti_gallery', columns=4) + gr.Gallery(label='Output', show_label=False, elem_id='ti_gallery', columns=4, object_fit="contain") gr.HTML(elem_id="ti_progress", value="") ti_outcome = gr.HTML(elem_id="ti_error", value="") @@ -1122,7 +1101,6 @@ def get_textual_inversion_template_names(): loadsave = ui_loadsave.UiLoadsave(cmd_opts.ui_config_file) ui_settings_from_file = loadsave.ui_settings.copy() - settings = ui_settings.UiSettings() settings.create_ui(loadsave, dummy_component) interfaces = [ @@ -1144,7 +1122,7 @@ def get_textual_inversion_template_names(): for _interface, label, _ifid in interfaces: shared.tab_names.append(label) - with gr.Blocks(theme=shared.gradio_theme, analytics_enabled=False, title="Stable Diffusion") as demo: + with gr.Blocks(theme=shared.gradio_theme, analytics_enabled=False, title="Stable Diffusion", head=canvas_head) as demo: settings.add_quicksettings() parameters_copypaste.connect_paste_params_buttons() diff --git a/modules/ui_common.py b/modules/ui_common.py index fc5d6e3f4..58c4e27e2 100644 --- a/modules/ui_common.py +++ b/modules/ui_common.py @@ -3,14 +3,11 @@ import json import html import os -import platform -import sys +from contextlib import nullcontext import gradio as gr -import subprocess as sp -from modules import call_queue, shared, ui_tempdir -from modules.infotext_utils import image_from_url_text +from modules import call_queue, shared, ui_tempdir, util import modules.images from modules.ui_components import ToolButton import modules.infotext_utils as parameters_copypaste @@ -105,21 +102,20 @@ def __init__(self, d=None): logfile_path = os.path.join(shared.opts.outdir_save, "log.csv") # NOTE: ensure csv integrity when fields are added by - # updating headers and padding with delimeters where needed - if os.path.exists(logfile_path): + # updating headers and padding with delimiters where needed + if shared.opts.save_write_log_csv and os.path.exists(logfile_path): update_logfile(logfile_path, fields) - with open(logfile_path, "a", encoding="utf8", newline='') as file: - at_start = file.tell() == 0 - writer = csv.writer(file) - if at_start: - writer.writerow(fields) + with (open(logfile_path, "a", encoding="utf8", newline='') if shared.opts.save_write_log_csv else nullcontext()) as file: + if file: + at_start = file.tell() == 0 + writer = csv.writer(file) + if at_start: + writer.writerow(fields) for image_index, filedata in enumerate(images, start_index): - image = image_from_url_text(filedata) - + image = filedata[0] is_grid = image_index < p.index_of_first_image - p.batch_index = image_index-1 parameters = parameters_copypaste.parse_generation_parameters(data["infotexts"][image_index], []) @@ -133,7 +129,8 @@ def __init__(self, d=None): filenames.append(os.path.basename(txt_fullfn)) fullfns.append(txt_fullfn) - writer.writerow([parsed_infotexts[0]['Prompt'], parsed_infotexts[0]['Seed'], data["width"], data["height"], data["sampler_name"], data["cfg_scale"], data["steps"], filenames[0], parsed_infotexts[0]['Negative prompt'], data["sd_model_name"], data["sd_model_hash"]]) + if file: + writer.writerow([parsed_infotexts[0]['Prompt'], parsed_infotexts[0]['Seed'], data["width"], data["height"], data["sampler_name"], data["cfg_scale"], data["steps"], filenames[0], parsed_infotexts[0]['Negative prompt'], data["sd_model_name"], data["sd_model_hash"]]) # Make Zip if do_make_zip: @@ -176,31 +173,7 @@ def open_folder(f, images=None, index=None): except Exception: pass - if not os.path.exists(f): - msg = f'Folder "{f}" does not exist. After you create an image, the folder will be created.' - print(msg) - gr.Info(msg) - return - elif not os.path.isdir(f): - msg = f""" -WARNING -An open_folder request was made with an argument that is not a folder. -This could be an error or a malicious attempt to run code on your computer. -Requested path was: {f} -""" - print(msg, file=sys.stderr) - gr.Warning(msg) - return - - path = os.path.normpath(f) - if platform.system() == "Windows": - os.startfile(path) - elif platform.system() == "Darwin": - sp.Popen(["open", path]) - elif "microsoft-standard-WSL2" in platform.uname().release: - sp.Popen(["wsl-open", path]) - else: - sp.Popen(["xdg-open", path]) + util.open_folder(f) with gr.Column(elem_id=f"{tabname}_results"): if toprow: @@ -208,7 +181,7 @@ def open_folder(f, images=None, index=None): with gr.Column(variant='panel', elem_id=f"{tabname}_results_panel"): with gr.Group(elem_id=f"{tabname}_gallery_container"): - res.gallery = gr.Gallery(label='Output', show_label=False, elem_id=f"{tabname}_gallery", columns=4, preview=True, height=shared.opts.gallery_height or None, object_fit='contain') + res.gallery = gr.Gallery(label='Output', show_label=False, elem_id=f"{tabname}_gallery", columns=4, preview=True, height=shared.opts.gallery_height or None, interactive=False, type="pil", object_fit="contain") with gr.Row(elem_id=f"image_buttons_{tabname}", elem_classes="image-buttons"): open_folder_button = ToolButton(folder_symbol, elem_id=f'{tabname}_open_folder', visible=not shared.cmd_opts.hide_ui_dir_config, tooltip="Open images output directory.") @@ -220,8 +193,7 @@ def open_folder(f, images=None, index=None): buttons = { 'img2img': ToolButton('🖼️', elem_id=f'{tabname}_send_to_img2img', tooltip="Send image and generation parameters to img2img tab."), 'inpaint': ToolButton('🎨️', elem_id=f'{tabname}_send_to_inpaint', tooltip="Send image and generation parameters to img2img inpaint tab."), - 'extras': ToolButton('📐', elem_id=f'{tabname}_send_to_extras', tooltip="Send image and generation parameters to extras tab."), - 'svd': ToolButton('🎬', elem_id=f'{tabname}_send_to_svd', tooltip="Send image and generation parameters to SVD tab."), + 'extras': ToolButton('📐', elem_id=f'{tabname}_send_to_extras', tooltip="Send image and generation parameters to extras tab.") } if tabname == 'txt2img': @@ -256,7 +228,7 @@ def open_folder(f, images=None, index=None): ) save.click( - fn=call_queue.wrap_gradio_call(save_files), + fn=call_queue.wrap_gradio_call_no_job(save_files), _js="(x, y, z, w) => [x, y, false, selected_gallery_index()]", inputs=[ res.generation_info, @@ -272,7 +244,7 @@ def open_folder(f, images=None, index=None): ) save_zip.click( - fn=call_queue.wrap_gradio_call(save_files), + fn=call_queue.wrap_gradio_call_no_job(save_files), _js="(x, y, z, w) => [x, y, true, selected_gallery_index()]", inputs=[ res.generation_info, diff --git a/modules/ui_components.py b/modules/ui_components.py index 55979f626..6d213ce4f 100644 --- a/modules/ui_components.py +++ b/modules/ui_components.py @@ -1,7 +1,12 @@ +from functools import wraps + import gradio as gr +from modules import gradio_extensions # noqa: F401 class FormComponent: + webui_do_not_create_gradio_pyi_thank_you = True + def get_expected_parent(self): return gr.components.Form @@ -9,12 +14,13 @@ def get_expected_parent(self): gr.Dropdown.get_expected_parent = FormComponent.get_expected_parent -class ToolButton(FormComponent, gr.Button): +class ToolButton(gr.Button, FormComponent): """Small button with single emoji as text, fits inside gradio forms""" - def __init__(self, *args, **kwargs): - classes = kwargs.pop("elem_classes", []) - super().__init__(*args, elem_classes=["tool", *classes], **kwargs) + @wraps(gr.Button.__init__) + def __init__(self, value="", *args, elem_classes=None, **kwargs): + elem_classes = elem_classes or [] + super().__init__(*args, elem_classes=["tool", *elem_classes], value=value, **kwargs) def get_block_name(self): return "button" @@ -22,7 +28,9 @@ def get_block_name(self): class ResizeHandleRow(gr.Row): """Same as gr.Row but fits inside gradio forms""" + webui_do_not_create_gradio_pyi_thank_you = True + @wraps(gr.Row.__init__) def __init__(self, **kwargs): super().__init__(**kwargs) @@ -32,79 +40,92 @@ def get_block_name(self): return "row" -class FormRow(FormComponent, gr.Row): +class FormRow(gr.Row, FormComponent): """Same as gr.Row but fits inside gradio forms""" def get_block_name(self): return "row" -class FormColumn(FormComponent, gr.Column): +class FormColumn(gr.Column, FormComponent): """Same as gr.Column but fits inside gradio forms""" def get_block_name(self): return "column" -class FormGroup(FormComponent, gr.Group): +class FormGroup(gr.Group, FormComponent): """Same as gr.Group but fits inside gradio forms""" def get_block_name(self): return "group" -class FormHTML(FormComponent, gr.HTML): +class FormHTML(gr.HTML, FormComponent): """Same as gr.HTML but fits inside gradio forms""" def get_block_name(self): return "html" -class FormColorPicker(FormComponent, gr.ColorPicker): +class FormColorPicker(gr.ColorPicker, FormComponent): """Same as gr.ColorPicker but fits inside gradio forms""" def get_block_name(self): return "colorpicker" -class DropdownMulti(FormComponent, gr.Dropdown): +class DropdownMulti(gr.Dropdown, FormComponent): """Same as gr.Dropdown but always multiselect""" + + @wraps(gr.Dropdown.__init__) def __init__(self, **kwargs): - super().__init__(multiselect=True, **kwargs) + kwargs['multiselect'] = True + super().__init__(**kwargs) def get_block_name(self): return "dropdown" -class DropdownEditable(FormComponent, gr.Dropdown): +class DropdownEditable(gr.Dropdown, FormComponent): """Same as gr.Dropdown but allows editing value""" + + @wraps(gr.Dropdown.__init__) def __init__(self, **kwargs): - super().__init__(allow_custom_value=True, **kwargs) + kwargs['allow_custom_value'] = True + super().__init__(**kwargs) def get_block_name(self): return "dropdown" -class InputAccordion(gr.Checkbox): +class InputAccordionImpl(gr.Checkbox): """A gr.Accordion that can be used as an input - returns True if open, False if closed. - Actaully just a hidden checkbox, but creates an accordion that follows and is followed by the state of the checkbox. + Actually just a hidden checkbox, but creates an accordion that follows and is followed by the state of the checkbox. """ + webui_do_not_create_gradio_pyi_thank_you = True + global_index = 0 - def __init__(self, value, **kwargs): + @wraps(gr.Checkbox.__init__) + def __init__(self, value=None, setup=False, **kwargs): + if not setup: + super().__init__(value=value, **kwargs) + return + self.accordion_id = kwargs.get('elem_id') if self.accordion_id is None: - self.accordion_id = f"input-accordion-{InputAccordion.global_index}" - InputAccordion.global_index += 1 + self.accordion_id = f"input-accordion-{InputAccordionImpl.global_index}" + InputAccordionImpl.global_index += 1 kwargs_checkbox = { **kwargs, "elem_id": f"{self.accordion_id}-checkbox", "visible": False, } - super().__init__(value, **kwargs_checkbox) + super().__init__(value=value, **kwargs_checkbox) self.change(fn=None, _js='function(checked){ inputAccordionChecked("' + self.accordion_id + '", checked); }', inputs=[self]) @@ -115,6 +136,7 @@ def __init__(self, value, **kwargs): "elem_classes": ['input-accordion'], "open": value, } + self.accordion = gr.Accordion(**kwargs_accordion) def extra(self): @@ -143,3 +165,6 @@ def __exit__(self, exc_type, exc_val, exc_tb): def get_block_name(self): return "checkbox" + +def InputAccordion(value=None, **kwargs): + return InputAccordionImpl(value=value, setup=True, **kwargs) diff --git a/modules/ui_extensions.py b/modules/ui_extensions.py index a24ea32ef..bbf5c113f 100644 --- a/modules/ui_extensions.py +++ b/modules/ui_extensions.py @@ -58,8 +58,9 @@ def apply_and_restart(disable_list, update_list, disable_all): def save_config_state(name): current_config_state = config_states.get_config() - if not name: - name = "Config" + + name = os.path.basename(name or "Config") + current_config_state["name"] = name timestamp = datetime.now().strftime('%Y_%m_%d-%H_%M_%S') filename = os.path.join(config_states_dir, f"{timestamp}_{name}.json") @@ -380,7 +381,7 @@ def install_extension_from_url(dirname, url, branch_name=None): except OSError as err: if err.errno == errno.EXDEV: # Cross device link, typical in docker or when tmp/ and extensions/ are on different file systems - # Since we can't use a rename, do the slower but more versitile shutil.move() + # Since we can't use a rename, do the slower but more versatile shutil.move() shutil.move(tmpdir, target_dir) else: # Something else, not enough free space, permissions, etc. rethrow it so that it gets handled. @@ -395,15 +396,15 @@ def install_extension_from_url(dirname, url, branch_name=None): shutil.rmtree(tmpdir, True) -def install_extension_from_index(url, hide_tags, sort_column, filter_text): +def install_extension_from_index(url, selected_tags, showing_type, filtering_type, sort_column, filter_text): ext_table, message = install_extension_from_url(None, url) - code, _ = refresh_available_extensions_from_data(hide_tags, sort_column, filter_text) + code, _ = refresh_available_extensions_from_data(selected_tags, showing_type, filtering_type, sort_column, filter_text) return code, ext_table, message, '' -def refresh_available_extensions(url, hide_tags, sort_column): +def refresh_available_extensions(url, selected_tags, showing_type, filtering_type, sort_column): global available_extensions import urllib.request @@ -412,19 +413,19 @@ def refresh_available_extensions(url, hide_tags, sort_column): available_extensions = json.loads(text) - code, tags = refresh_available_extensions_from_data(hide_tags, sort_column) + code, tags = refresh_available_extensions_from_data(selected_tags, showing_type, filtering_type, sort_column) return url, code, gr.CheckboxGroup.update(choices=tags), '', '' -def refresh_available_extensions_for_tags(hide_tags, sort_column, filter_text): - code, _ = refresh_available_extensions_from_data(hide_tags, sort_column, filter_text) +def refresh_available_extensions_for_tags(selected_tags, showing_type, filtering_type, sort_column, filter_text): + code, _ = refresh_available_extensions_from_data(selected_tags, showing_type, filtering_type, sort_column, filter_text) return code, '' -def search_extensions(filter_text, hide_tags, sort_column): - code, _ = refresh_available_extensions_from_data(hide_tags, sort_column, filter_text) +def search_extensions(filter_text, selected_tags, showing_type, filtering_type, sort_column): + code, _ = refresh_available_extensions_from_data(selected_tags, showing_type, filtering_type, sort_column, filter_text) return code, '' @@ -449,13 +450,13 @@ def get_date(info: dict, key): return '' -def refresh_available_extensions_from_data(hide_tags, sort_column, filter_text=""): +def refresh_available_extensions_from_data(selected_tags, showing_type, filtering_type, sort_column, filter_text=""): extlist = available_extensions["extensions"] installed_extensions = {extension.name for extension in extensions.extensions} installed_extension_urls = {normalize_git_url(extension.remote) for extension in extensions.extensions if extension.remote is not None} tags = available_extensions.get("tags", {}) - tags_to_hide = set(hide_tags) + selected_tags = set(selected_tags) hidden = 0 code = f""" @@ -488,9 +489,19 @@ def refresh_available_extensions_from_data(hide_tags, sort_column, filter_text=" existing = get_extension_dirname_from_url(url) in installed_extensions or normalize_git_url(url) in installed_extension_urls extension_tags = extension_tags + ["installed"] if existing else extension_tags - if any(x for x in extension_tags if x in tags_to_hide): - hidden += 1 - continue + if len(selected_tags) > 0: + matched_tags = [x for x in extension_tags if x in selected_tags] + if filtering_type == 'or': + need_hide = len(matched_tags) > 0 + else: + need_hide = len(matched_tags) == len(selected_tags) + + if showing_type == 'show': + need_hide = not need_hide + + if need_hide: + hidden += 1 + continue if filter_text and filter_text.strip(): if filter_text.lower() not in html.escape(name).lower() and filter_text.lower() not in html.escape(description).lower(): @@ -593,8 +604,12 @@ def create_ui(): install_extension_button = gr.Button(elem_id="install_extension_button", visible=False) with gr.Row(): - hide_tags = gr.CheckboxGroup(value=["ads", "localization", "installed"], label="Hide extensions with tags", choices=["script", "ads", "localization", "installed"]) - sort_column = gr.Radio(value="newest first", label="Order", choices=["newest first", "oldest first", "a-z", "z-a", "internal order",'update time', 'create time', "stars"], type="index") + selected_tags = gr.CheckboxGroup(value=["ads", "localization", "installed"], label="Extension tags", choices=["script", "ads", "localization", "installed"], elem_classes=['compact-checkbox-group']) + sort_column = gr.Radio(value="newest first", label="Order", choices=["newest first", "oldest first", "a-z", "z-a", "internal order",'update time', 'create time', "stars"], type="index", elem_classes=['compact-checkbox-group']) + + with gr.Row(): + showing_type = gr.Radio(value="hide", label="Showing type", choices=["hide", "show"], elem_classes=['compact-checkbox-group']) + filtering_type = gr.Radio(value="or", label="Filtering type", choices=["or", "and"], elem_classes=['compact-checkbox-group']) with gr.Row(): search_extensions_text = gr.Text(label="Search", container=False) @@ -604,31 +619,43 @@ def create_ui(): refresh_available_extensions_button.click( fn=modules.ui.wrap_gradio_call(refresh_available_extensions, extra_outputs=[gr.update(), gr.update(), gr.update(), gr.update()]), - inputs=[available_extensions_index, hide_tags, sort_column], - outputs=[available_extensions_index, available_extensions_table, hide_tags, search_extensions_text, install_result], + inputs=[available_extensions_index, selected_tags, showing_type, filtering_type, sort_column], + outputs=[available_extensions_index, available_extensions_table, selected_tags, search_extensions_text, install_result], ) install_extension_button.click( - fn=modules.ui.wrap_gradio_call(install_extension_from_index, extra_outputs=[gr.update(), gr.update()]), - inputs=[extension_to_install, hide_tags, sort_column, search_extensions_text], + fn=modules.ui.wrap_gradio_call_no_job(install_extension_from_index, extra_outputs=[gr.update(), gr.update()]), + inputs=[extension_to_install, selected_tags, showing_type, filtering_type, sort_column, search_extensions_text], outputs=[available_extensions_table, extensions_table, install_result], ) search_extensions_text.change( - fn=modules.ui.wrap_gradio_call(search_extensions, extra_outputs=[gr.update()]), - inputs=[search_extensions_text, hide_tags, sort_column], + fn=modules.ui.wrap_gradio_call_no_job(search_extensions, extra_outputs=[gr.update()]), + inputs=[search_extensions_text, selected_tags, showing_type, filtering_type, sort_column], outputs=[available_extensions_table, install_result], ) - hide_tags.change( - fn=modules.ui.wrap_gradio_call(refresh_available_extensions_for_tags, extra_outputs=[gr.update()]), - inputs=[hide_tags, sort_column, search_extensions_text], + selected_tags.change( + fn=modules.ui.wrap_gradio_call_no_job(refresh_available_extensions_for_tags, extra_outputs=[gr.update()]), + inputs=[selected_tags, showing_type, filtering_type, sort_column, search_extensions_text], + outputs=[available_extensions_table, install_result] + ) + + showing_type.change( + fn=modules.ui.wrap_gradio_call_no_job(refresh_available_extensions_for_tags, extra_outputs=[gr.update()]), + inputs=[selected_tags, showing_type, filtering_type, sort_column, search_extensions_text], + outputs=[available_extensions_table, install_result] + ) + + filtering_type.change( + fn=modules.ui.wrap_gradio_call_no_job(refresh_available_extensions_for_tags, extra_outputs=[gr.update()]), + inputs=[selected_tags, showing_type, filtering_type, sort_column, search_extensions_text], outputs=[available_extensions_table, install_result] ) sort_column.change( - fn=modules.ui.wrap_gradio_call(refresh_available_extensions_for_tags, extra_outputs=[gr.update()]), - inputs=[hide_tags, sort_column, search_extensions_text], + fn=modules.ui.wrap_gradio_call_no_job(refresh_available_extensions_for_tags, extra_outputs=[gr.update()]), + inputs=[selected_tags, showing_type, filtering_type, sort_column, search_extensions_text], outputs=[available_extensions_table, install_result] ) @@ -640,7 +667,7 @@ def create_ui(): install_result = gr.HTML(elem_id="extension_install_result") install_button.click( - fn=modules.ui.wrap_gradio_call(lambda *args: [gr.update(), *install_extension_from_url(*args)], extra_outputs=[gr.update(), gr.update()]), + fn=modules.ui.wrap_gradio_call_no_job(lambda *args: [gr.update(), *install_extension_from_url(*args)], extra_outputs=[gr.update(), gr.update()]), inputs=[install_dirname, install_url, install_branch], outputs=[install_url, extensions_table, install_result], ) @@ -661,7 +688,7 @@ def create_ui(): config_save_button.click(fn=save_config_state, inputs=[config_save_name], outputs=[config_states_list, config_states_info]) - dummy_component = gr.Label(visible=False) + dummy_component = gr.State() config_restore_button.click(fn=restore_config_state, _js="config_state_confirm_restore", inputs=[dummy_component, config_states_list, config_restore_type], outputs=[config_states_info]) config_states_list.change( diff --git a/modules/ui_extra_networks.py b/modules/ui_extra_networks.py index 34c46ed40..395549bfb 100644 --- a/modules/ui_extra_networks.py +++ b/modules/ui_extra_networks.py @@ -1,6 +1,8 @@ import functools import os.path import urllib.parse +from base64 import b64decode +from io import BytesIO from pathlib import Path from typing import Optional, Union from dataclasses import dataclass @@ -11,6 +13,7 @@ import json import html from fastapi.exceptions import HTTPException +from PIL import Image from modules.infotext_utils import image_from_url_text @@ -108,6 +111,31 @@ def fetch_file(filename: str = ""): return FileResponse(filename, headers={"Accept-Ranges": "bytes"}) +def fetch_cover_images(page: str = "", item: str = "", index: int = 0): + from starlette.responses import Response + + page = next(iter([x for x in extra_pages if x.name == page]), None) + if page is None: + raise HTTPException(status_code=404, detail="File not found") + + metadata = page.metadata.get(item) + if metadata is None: + raise HTTPException(status_code=404, detail="File not found") + + cover_images = json.loads(metadata.get('ssmd_cover_images', {})) + image = cover_images[index] if index < len(cover_images) else None + if not image: + raise HTTPException(status_code=404, detail="File not found") + + try: + image = Image.open(BytesIO(b64decode(image))) + buffer = BytesIO() + image.save(buffer, format=image.format) + return Response(content=buffer.getvalue(), media_type=image.get_format_mimetype()) + except Exception as err: + raise ValueError(f"File cannot be fetched: {item}. Failed to load cover image.") from err + + def get_metadata(page: str = "", item: str = ""): from starlette.responses import JSONResponse @@ -119,6 +147,8 @@ def get_metadata(page: str = "", item: str = ""): if metadata is None: return JSONResponse({}) + metadata = {i:metadata[i] for i in metadata if i != 'ssmd_cover_images'} # those are cover images, and they are too big to display in UI as text + return JSONResponse({"metadata": json.dumps(metadata, indent=4, ensure_ascii=False)}) @@ -142,6 +172,7 @@ def get_single_card(page: str = "", tabname: str = "", name: str = ""): def add_pages_to_demo(app): app.add_api_route("/sd_extra_networks/thumb", fetch_file, methods=["GET"]) + app.add_api_route("/sd_extra_networks/cover-images", fetch_cover_images, methods=["GET"]) app.add_api_route("/sd_extra_networks/metadata", get_metadata, methods=["GET"]) app.add_api_route("/sd_extra_networks/get-single-card", get_single_card, methods=["GET"]) @@ -151,6 +182,7 @@ def quote_js(s): s = s.replace('"', '\\"') return f'"{s}"' + class ExtraNetworksPage: def __init__(self, title): self.title = title @@ -164,6 +196,8 @@ def __init__(self, title): self.lister = util.MassFileLister() # HTML Templates self.pane_tpl = shared.html("extra-networks-pane.html") + self.pane_content_tree_tpl = shared.html("extra-networks-pane-tree.html") + self.pane_content_dirs_tpl = shared.html("extra-networks-pane-dirs.html") self.card_tpl = shared.html("extra-networks-card.html") self.btn_tree_tpl = shared.html("extra-networks-tree-button.html") self.btn_copy_path_tpl = shared.html("extra-networks-copy-path-button.html") @@ -243,14 +277,12 @@ def create_item_html( btn_metadata = self.btn_metadata_tpl.format( **{ "extra_networks_tabname": self.extra_networks_tabname, - "name": html.escape(item["name"]), } ) btn_edit_item = self.btn_edit_item_tpl.format( **{ "tabname": tabname, "extra_networks_tabname": self.extra_networks_tabname, - "name": html.escape(item["name"]), } ) @@ -476,6 +508,47 @@ def _build_tree(data: Optional[dict[str, ExtraNetworksItem]] = None) -> Optional return f"
    {res}
" + def create_dirs_view_html(self, tabname: str) -> str: + """Generates HTML for displaying folders.""" + + subdirs = {} + for parentdir in [os.path.abspath(x) for x in self.allowed_directories_for_previews()]: + for root, dirs, _ in sorted(os.walk(parentdir, followlinks=True), key=lambda x: shared.natural_sort_key(x[0])): + for dirname in sorted(dirs, key=shared.natural_sort_key): + x = os.path.join(root, dirname) + + if not os.path.isdir(x): + continue + + subdir = os.path.abspath(x)[len(parentdir):] + + if shared.opts.extra_networks_dir_button_function: + if not subdir.startswith(os.path.sep): + subdir = os.path.sep + subdir + else: + while subdir.startswith(os.path.sep): + subdir = subdir[1:] + + is_empty = len(os.listdir(x)) == 0 + if not is_empty and not subdir.endswith(os.path.sep): + subdir = subdir + os.path.sep + + if (os.path.sep + "." in subdir or subdir.startswith(".")) and not shared.opts.extra_networks_show_hidden_directories: + continue + + subdirs[subdir] = 1 + + if subdirs: + subdirs = {"": 1, **subdirs} + + subdirs_html = "".join([f""" + + """ for subdir in subdirs]) + + return subdirs_html + def create_card_view_html(self, tabname: str, *, none_message) -> str: """Generates HTML for the network Card View section for a tab. @@ -489,15 +562,15 @@ def create_card_view_html(self, tabname: str, *, none_message) -> str: Returns: HTML formatted string. """ - res = "" + res = [] for item in self.items.values(): - res += self.create_item_html(tabname, item, self.card_tpl) + res.append(self.create_item_html(tabname, item, self.card_tpl)) - if res == "": + if not res: dirs = "".join([f"
  • {x}
  • " for x in self.allowed_directories_for_previews()]) - res = none_message or shared.html("extra-networks-no-cards.html").format(dirs=dirs) + res = [none_message or shared.html("extra-networks-no-cards.html").format(dirs=dirs)] - return res + return "".join(res) def create_html(self, tabname, *, empty=False): """Generates an HTML string for the current pane. @@ -526,28 +599,28 @@ def create_html(self, tabname, *, empty=False): if "user_metadata" not in item: self.read_user_metadata(item) - data_sortdir = shared.opts.extra_networks_card_order - data_sortmode = shared.opts.extra_networks_card_order_field.lower().replace("sort", "").replace(" ", "_").rstrip("_").strip() - data_sortkey = f"{data_sortmode}-{data_sortdir}-{len(self.items)}" - tree_view_btn_extra_class = "" - tree_view_div_extra_class = "hidden" - if shared.opts.extra_networks_tree_view_default_enabled: - tree_view_btn_extra_class = "extra-network-control--enabled" - tree_view_div_extra_class = "" + show_tree = shared.opts.extra_networks_tree_view_default_enabled - return self.pane_tpl.format( - **{ - "tabname": tabname, - "extra_networks_tabname": self.extra_networks_tabname, - "data_sortmode": data_sortmode, - "data_sortkey": data_sortkey, - "data_sortdir": data_sortdir, - "tree_view_btn_extra_class": tree_view_btn_extra_class, - "tree_view_div_extra_class": tree_view_div_extra_class, - "tree_html": self.create_tree_view_html(tabname), - "items_html": self.create_card_view_html(tabname, none_message="Loading..." if empty else None), - } - ) + page_params = { + "tabname": tabname, + "extra_networks_tabname": self.extra_networks_tabname, + "data_sortdir": shared.opts.extra_networks_card_order, + "sort_path_active": ' extra-network-control--enabled' if shared.opts.extra_networks_card_order_field == 'Path' else '', + "sort_name_active": ' extra-network-control--enabled' if shared.opts.extra_networks_card_order_field == 'Name' else '', + "sort_date_created_active": ' extra-network-control--enabled' if shared.opts.extra_networks_card_order_field == 'Date Created' else '', + "sort_date_modified_active": ' extra-network-control--enabled' if shared.opts.extra_networks_card_order_field == 'Date Modified' else '', + "tree_view_btn_extra_class": "extra-network-control--enabled" if show_tree else "", + "items_html": self.create_card_view_html(tabname, none_message="Loading..." if empty else None), + "extra_networks_tree_view_default_width": shared.opts.extra_networks_tree_view_default_width, + "tree_view_div_default_display_class": "" if show_tree else "extra-network-dirs-hidden", + } + + if shared.opts.extra_networks_tree_view_style == "Tree": + pane_content = self.pane_content_tree_tpl.format(**page_params, tree_html=self.create_tree_view_html(tabname)) + else: + pane_content = self.pane_content_dirs_tpl.format(**page_params, dirs_html=self.create_dirs_view_html(tabname)) + + return self.pane_tpl.format(**page_params, pane_content=pane_content) def create_item(self, name, index=None): raise NotImplementedError() @@ -584,6 +657,17 @@ def find_preview(self, path): return None + def find_embedded_preview(self, path, name, metadata): + """ + Find if embedded preview exists in safetensors metadata and return endpoint for it. + """ + + file = f"{path}.safetensors" + if self.lister.exists(file) and 'ssmd_cover_images' in metadata and len(list(filter(None, json.loads(metadata['ssmd_cover_images'])))) > 0: + return f"./sd_extra_networks/cover-images?page={self.extra_networks_tabname}&item={name}" + + return None + def find_description(self, path): """ Find and read a description file for a given path (without extension). @@ -609,10 +693,10 @@ def initialize(): def register_default_pages(): from modules.ui_extra_networks_textual_inversion import ExtraNetworksPageTextualInversion - from modules.ui_extra_networks_hypernets import ExtraNetworksPageHypernetworks + # from modules.ui_extra_networks_hypernets import ExtraNetworksPageHypernetworks from modules.ui_extra_networks_checkpoints import ExtraNetworksPageCheckpoints register_page(ExtraNetworksPageTextualInversion()) - register_page(ExtraNetworksPageHypernetworks()) + # register_page(ExtraNetworksPageHypernetworks()) register_page(ExtraNetworksPageCheckpoints()) @@ -666,9 +750,11 @@ def create_ui(interface: gr.Blocks, unrelated_tabs, tabname): elem_id = f"{tabname}_{page.extra_networks_tabname}_cards_html" page_elem = gr.HTML(page.create_html(tabname, empty=True), elem_id=elem_id) ui.pages.append(page_elem) + editor = page.create_user_metadata_editor(ui, tabname) editor.create_ui() ui.user_metadata_editors.append(editor) + related_tabs.append(tab) ui.button_save_preview = gr.Button('Save preview', elem_id=f"{tabname}_save_preview", visible=False) @@ -693,7 +779,7 @@ def refresh(): return ui.pages_contents button_refresh = gr.Button("Refresh", elem_id=f"{tabname}_{page.extra_networks_tabname}_extra_refresh_internal", visible=False) - button_refresh.click(fn=refresh, inputs=[], outputs=ui.pages).then(fn=lambda: None, _js="function(){ " + f"applyExtraNetworkFilter('{tabname}_{page.extra_networks_tabname}');" + " }") + button_refresh.click(fn=refresh, inputs=[], outputs=ui.pages).then(fn=lambda: None, _js="function(){ " + f"applyExtraNetworkFilter('{tabname}_{page.extra_networks_tabname}');" + " }").then(fn=lambda: None, _js='setupAllResizeHandles') def create_html(): ui.pages_contents = [pg.create_html(ui.tabname) for pg in ui.stored_extra_pages] @@ -703,7 +789,7 @@ def pages_html(): create_html() return ui.pages_contents - interface.load(fn=pages_html, inputs=[], outputs=ui.pages) + interface.load(fn=pages_html, inputs=[], outputs=ui.pages).then(fn=lambda: None, _js='setupAllResizeHandles') return ui diff --git a/modules/ui_extra_networks_user_metadata.py b/modules/ui_extra_networks_user_metadata.py index 2ca937fd1..3a07db105 100644 --- a/modules/ui_extra_networks_user_metadata.py +++ b/modules/ui_extra_networks_user_metadata.py @@ -133,8 +133,10 @@ def write_user_metadata(self, name, metadata): filename = item.get("filename", None) basename, ext = os.path.splitext(filename) - with open(basename + '.json', "w", encoding="utf8") as file: + metadata_path = basename + '.json' + with open(metadata_path, "w", encoding="utf8") as file: json.dump(metadata, file, indent=4, ensure_ascii=False) + self.page.lister.update_file_entry(metadata_path) def save_user_metadata(self, name, desc, notes): user_metadata = self.get_user_metadata(name) @@ -185,13 +187,14 @@ def save_preview(self, index, gallery, name): geninfo, items = images.read_info_from_image(image) images.save_image_with_geninfo(image, geninfo, item["local_preview"]) - + self.page.lister.update_file_entry(item["local_preview"]) + item['preview'] = self.page.find_preview(item["local_preview"]) return self.get_card_html(name), '' def setup_ui(self, gallery): self.button_replace_preview.click( fn=self.save_preview, - _js="function(x, y, z){return [selected_gallery_index(), y, z]}", + _js=f"function(x, y, z){{return [selected_gallery_index_id('{self.tabname + '_gallery_container'}'), y, z]}}", inputs=[self.edit_name_input, gallery, self.edit_name_input], outputs=[self.html_preview, self.html_status] ).then( @@ -200,6 +203,3 @@ def setup_ui(self, gallery): inputs=[self.edit_name_input], outputs=[] ) - - - diff --git a/modules/ui_gradio_extensions.py b/modules/ui_gradio_extensions.py index f5278d22f..ed57c1e98 100644 --- a/modules/ui_gradio_extensions.py +++ b/modules/ui_gradio_extensions.py @@ -41,6 +41,11 @@ def stylesheet(fn): if os.path.exists(user_css): head += stylesheet(user_css) + from modules.shared_gradio_themes import resolve_var + light = resolve_var('background_fill_primary') + dark = resolve_var('background_fill_primary_dark') + head += f'' + return head @@ -50,7 +55,7 @@ def reload_javascript(): def template_response(*args, **kwargs): res = shared.GradioTemplateResponseOriginal(*args, **kwargs) - res.body = res.body.replace(b'', f'{js}'.encode("utf8")) + res.body = res.body.replace(b'', f'{js}'.encode("utf8")) res.body = res.body.replace(b'', f'{css}'.encode("utf8")) res.init_headers() return res diff --git a/modules/ui_loadsave.py b/modules/ui_loadsave.py index 2555cdb6c..0cc1ab82a 100644 --- a/modules/ui_loadsave.py +++ b/modules/ui_loadsave.py @@ -104,6 +104,8 @@ def check_dropdown(val): apply_field(x, 'value', check_dropdown, getattr(x, 'init_field', None)) if type(x) == InputAccordion: + if hasattr(x, 'custom_script_source'): + x.accordion.custom_script_source = x.custom_script_source if x.accordion.visible: apply_field(x.accordion, 'visible') apply_field(x, 'value') diff --git a/modules/ui_postprocessing.py b/modules/ui_postprocessing.py index 7261c2df8..7a33ca8f0 100644 --- a/modules/ui_postprocessing.py +++ b/modules/ui_postprocessing.py @@ -2,17 +2,18 @@ from modules import scripts, shared, ui_common, postprocessing, call_queue, ui_toprow import modules.infotext_utils as parameters_copypaste from modules.ui_components import ResizeHandleRow +from modules_forge.forge_canvas.canvas import ForgeCanvas def create_ui(): - dummy_component = gr.Label(visible=False) - tab_index = gr.Number(value=0, visible=False) + dummy_component = gr.Textbox(visible=False) + tab_index = gr.State(value=0) with ResizeHandleRow(equal_height=False, variant='compact'): with gr.Column(variant='compact'): with gr.Tabs(elem_id="mode_extras"): with gr.TabItem('Single Image', id="single_image", elem_id="extras_single_tab") as tab_single: - extras_image = gr.Image(label="Source", source="upload", interactive=True, type="pil", elem_id="extras_image") + extras_image = ForgeCanvas(elem_id="extras_image", height=512, no_scribbles=True).background with gr.TabItem('Batch Process', id="batch_process", elem_id="extras_batch_process_tab") as tab_batch: image_batch = gr.Files(label="Batch Process", interactive=True, elem_id="extras_image_batch") @@ -35,19 +36,21 @@ def create_ui(): tab_batch.select(fn=lambda: 1, inputs=[], outputs=[tab_index]) tab_batch_dir.select(fn=lambda: 2, inputs=[], outputs=[tab_index]) + submit_click_inputs = [ + dummy_component, + tab_index, + extras_image, + image_batch, + extras_batch_input_dir, + extras_batch_output_dir, + show_extras_results, + *script_inputs + ] + submit.click( fn=call_queue.wrap_gradio_gpu_call(postprocessing.run_postprocessing_webui, extra_outputs=[None, '']), - _js="submit_extras", - inputs=[ - dummy_component, - tab_index, - extras_image, - image_batch, - extras_batch_input_dir, - extras_batch_output_dir, - show_extras_results, - *script_inputs - ], + js=f"(...args) => {{ return submit_extras(args.slice(0, {len(submit_click_inputs)})); }}", + inputs=submit_click_inputs, outputs=[ output_panel.gallery, output_panel.generation_info, diff --git a/modules/ui_prompt_styles.py b/modules/ui_prompt_styles.py index d67e3f17e..f71b40c41 100644 --- a/modules/ui_prompt_styles.py +++ b/modules/ui_prompt_styles.py @@ -67,7 +67,7 @@ def __init__(self, tabname, main_ui_prompt, main_ui_negative_prompt): with gr.Row(): self.selection = gr.Dropdown(label="Styles", elem_id=f"{tabname}_styles_edit_select", choices=list(shared.prompt_styles.styles), value=[], allow_custom_value=True, info="Styles allow you to add custom text to prompt. Use the {prompt} token in style text, and it will be replaced with user's prompt when applying style. Otherwise, style's text will be added to the end of the prompt.") ui_common.create_refresh_button([self.dropdown, self.selection], shared.prompt_styles.reload, lambda: {"choices": list(shared.prompt_styles.styles)}, f"refresh_{tabname}_styles") - self.materialize = ui_components.ToolButton(value=styles_materialize_symbol, elem_id=f"{tabname}_style_apply_dialog", tooltip="Apply all selected styles from the style selction dropdown in main UI to the prompt.") + self.materialize = ui_components.ToolButton(value=styles_materialize_symbol, elem_id=f"{tabname}_style_apply_dialog", tooltip="Apply all selected styles from the style selection dropdown in main UI to the prompt.") self.copy = ui_components.ToolButton(value=styles_copy_symbol, elem_id=f"{tabname}_style_copy", tooltip="Copy main UI prompt to style.") with gr.Row(): diff --git a/modules/ui_settings.py b/modules/ui_settings.py index f2576dc56..e750d3714 100644 --- a/modules/ui_settings.py +++ b/modules/ui_settings.py @@ -1,7 +1,8 @@ import gradio as gr -from modules import ui_common, shared, script_callbacks, scripts, sd_models, sysinfo, timer -from modules.call_queue import wrap_gradio_call +from modules import ui_common, shared, script_callbacks, scripts, sd_models, sysinfo, timer, shared_items +from modules.call_queue import wrap_gradio_call_no_job +from modules.options import options_section from modules.shared import opts from modules.ui_components import FormRow from modules.ui_gradio_extensions import reload_javascript @@ -98,6 +99,9 @@ def run_settings_single(self, value, key): return get_value_for_setting(key), opts.dumpjson() + def register_settings(self): + script_callbacks.ui_settings_callback() + def create_ui(self, loadsave, dummy_component): self.components = [] self.component_dict = {} @@ -105,7 +109,11 @@ def create_ui(self, loadsave, dummy_component): shared.settings_components = self.component_dict - script_callbacks.ui_settings_callback() + # we add this as late as possible so that scripts have already registered their callbacks + opts.data_labels.update(options_section(('callbacks', "Callbacks", "system"), { + **shared_items.callbacks_order_settings(), + })) + opts.reorder() with gr.Blocks(analytics_enabled=False) as settings_interface: @@ -287,7 +295,7 @@ def add_quicksettings(self): def add_functionality(self, demo): self.submit.click( - fn=wrap_gradio_call(lambda *args: self.run_settings(*args), extra_outputs=[gr.update()]), + fn=wrap_gradio_call_no_job(lambda *args: self.run_settings(*args), extra_outputs=[gr.update()]), inputs=self.components, outputs=[self.text_settings, self.result], ) @@ -303,30 +311,20 @@ def add_functionality(self, demo): methods = [component.change] for method in methods: - handler = method( + method( fn=lambda value, k=k: self.run_settings_single(value, key=k), inputs=[component], outputs=[component, self.text_settings], show_progress=False, ) - script_callbacks.setting_updated_event_subscriber_chain( - handler=handler, - component=component, - setting_name=k, - ) button_set_checkpoint = gr.Button('Change checkpoint', elem_id='change_checkpoint', visible=False) - handler = button_set_checkpoint.click( + button_set_checkpoint.click( fn=lambda value, _: self.run_settings_single(value, key='sd_model_checkpoint'), _js="function(v){ var res = desiredCheckpointName; desiredCheckpointName = ''; return [res || v, null]; }", inputs=[self.component_dict['sd_model_checkpoint'], self.dummy_component], outputs=[self.component_dict['sd_model_checkpoint'], self.text_settings], ) - script_callbacks.setting_updated_event_subscriber_chain( - handler=handler, - component=self.component_dict['sd_model_checkpoint'], - setting_name="sd_model_checkpoint" - ) component_keys = [k for k in opts.data_labels.keys() if k in self.component_dict] diff --git a/modules/ui_tempdir.py b/modules/ui_tempdir.py index ecd6bdec3..af9601f3a 100644 --- a/modules/ui_tempdir.py +++ b/modules/ui_tempdir.py @@ -4,6 +4,7 @@ from pathlib import Path import gradio.components +import gradio as gr from PIL import PngImagePlugin @@ -13,25 +14,35 @@ Savedfile = namedtuple("Savedfile", ["name"]) -def register_tmp_file(gradio, filename): - if hasattr(gradio, 'temp_file_sets'): # gradio 3.15 - gradio.temp_file_sets[0] = gradio.temp_file_sets[0] | {os.path.abspath(filename)} +def register_tmp_file(gradio_app, filename): + if hasattr(gradio_app, 'temp_file_sets'): # gradio 3.15 + if hasattr(gr.utils, 'abspath'): # gradio 4.19 + filename = gr.utils.abspath(filename) + else: + filename = os.path.abspath(filename) - if hasattr(gradio, 'temp_dirs'): # gradio 3.9 - gradio.temp_dirs = gradio.temp_dirs | {os.path.abspath(os.path.dirname(filename))} + gradio_app.temp_file_sets[0] = gradio_app.temp_file_sets[0] | {filename} + if hasattr(gradio_app, 'temp_dirs'): # gradio 3.9 + gradio_app.temp_dirs = gradio_app.temp_dirs | {os.path.abspath(os.path.dirname(filename))} -def check_tmp_file(gradio, filename): - if hasattr(gradio, 'temp_file_sets'): - return any(filename in fileset for fileset in gradio.temp_file_sets) - if hasattr(gradio, 'temp_dirs'): - return any(Path(temp_dir).resolve() in Path(filename).resolve().parents for temp_dir in gradio.temp_dirs) +def check_tmp_file(gradio_app, filename): + if hasattr(gradio_app, 'temp_file_sets'): + if hasattr(gr.utils, 'abspath'): # gradio 4.19 + filename = gr.utils.abspath(filename) + else: + filename = os.path.abspath(filename) + + return any(filename in fileset for fileset in gradio_app.temp_file_sets) + + if hasattr(gradio_app, 'temp_dirs'): + return any(Path(temp_dir).resolve() in Path(filename).resolve().parents for temp_dir in gradio_app.temp_dirs) return False -def save_pil_to_file(self, pil_image, dir=None, format="png"): +def save_pil_to_file(pil_image, cache_dir=None, format="png"): already_saved_as = getattr(pil_image, 'already_saved_as', None) if already_saved_as and os.path.isfile(already_saved_as): register_tmp_file(shared.demo, already_saved_as) @@ -39,9 +50,10 @@ def save_pil_to_file(self, pil_image, dir=None, format="png"): register_tmp_file(shared.demo, filename_with_mtime) return filename_with_mtime - if shared.opts.temp_dir != "": + if shared.opts.temp_dir: dir = shared.opts.temp_dir else: + dir = cache_dir os.makedirs(dir, exist_ok=True) use_metadata = False @@ -56,9 +68,96 @@ def save_pil_to_file(self, pil_image, dir=None, format="png"): return file_obj.name +async def async_move_files_to_cache(data, block, postprocess=False, check_in_upload_folder=False, keep_in_cache=False): + """Move any files in `data` to cache and (optionally), adds URL prefixes (/file=...) needed to access the cached file. + Also handles the case where the file is on an external Gradio app (/proxy=...). + + Runs after .postprocess() and before .preprocess(). + + Copied from gradio's processing_utils.py + + Args: + data: The input or output data for a component. Can be a dictionary or a dataclass + block: The component whose data is being processed + postprocess: Whether its running from postprocessing + check_in_upload_folder: If True, instead of moving the file to cache, checks if the file is in already in cache (exception if not). + keep_in_cache: If True, the file will not be deleted from cache when the server is shut down. + """ + + from gradio import FileData + from gradio.data_classes import GradioRootModel + from gradio.data_classes import GradioModel + from gradio_client import utils as client_utils + from gradio.utils import get_upload_folder, is_in_or_equal, is_static_file + + async def _move_to_cache(d: dict): + payload = FileData(**d) + + # EDITED + payload.path = payload.path.rsplit('?', 1)[0] + + # If the gradio app developer is returning a URL from + # postprocess, it means the component can display a URL + # without it being served from the gradio server + # This makes it so that the URL is not downloaded and speeds up event processing + if payload.url and postprocess and client_utils.is_http_url_like(payload.url): + payload.path = payload.url + elif is_static_file(payload): + pass + elif not block.proxy_url: + # EDITED + if check_tmp_file(shared.demo, payload.path): + temp_file_path = payload.path + else: + # If the file is on a remote server, do not move it to cache. + if check_in_upload_folder and not client_utils.is_http_url_like( + payload.path + ): + path = os.path.abspath(payload.path) + if not is_in_or_equal(path, get_upload_folder()): + raise ValueError( + f"File {path} is not in the upload folder and cannot be accessed." + ) + if not payload.is_stream: + temp_file_path = await block.async_move_resource_to_block_cache( + payload.path + ) + if temp_file_path is None: + raise ValueError("Did not determine a file path for the resource.") + payload.path = temp_file_path + if keep_in_cache: + block.keep_in_cache.add(payload.path) + + url_prefix = "/stream/" if payload.is_stream else "/file=" + if block.proxy_url: + proxy_url = block.proxy_url.rstrip("/") + url = f"/proxy={proxy_url}{url_prefix}{payload.path}" + elif client_utils.is_http_url_like(payload.path) or payload.path.startswith( + f"{url_prefix}" + ): + url = payload.path + else: + url = f"{url_prefix}{payload.path}" + payload.url = url + + return payload.model_dump() + + if isinstance(data, (GradioRootModel, GradioModel)): + data = data.model_dump() + + return await client_utils.async_traverse( + data, _move_to_cache, client_utils.is_file_obj + ) + + def install_ui_tempdir_override(): - """override save to file function so that it also writes PNG info""" - gradio.components.IOComponent.pil_to_temp_file = save_pil_to_file + """ + override save to file function so that it also writes PNG info. + override gradio4's move_files_to_cache function to prevent it from writing a copy into a temporary directory. + """ + + gradio.processing_utils.save_pil_to_cache = save_pil_to_file + gradio.processing_utils.async_move_files_to_cache = async_move_files_to_cache def on_tmpdir_changed(): diff --git a/modules/upscaler.py b/modules/upscaler.py index 0e38d52fb..507881fed 100644 --- a/modules/upscaler.py +++ b/modules/upscaler.py @@ -7,7 +7,6 @@ import modules.shared from modules import modelloader, shared - LANCZOS = (Image.Resampling.LANCZOS if hasattr(Image, 'Resampling') else Image.LANCZOS) NEAREST = (Image.Resampling.NEAREST if hasattr(Image, 'Resampling') else Image.NEAREST) @@ -21,7 +20,7 @@ class Upscaler: filter = None model = None user_path = None - scalers: [] + scalers: list tile = True def __init__(self, create_dirs=False): @@ -57,8 +56,11 @@ def upscale(self, img: PIL.Image, scale, selected_model: str = None): dest_w = int((img.width * scale) // 8 * 8) dest_h = int((img.height * scale) // 8 * 8) - for _ in range(3): - if img.width >= dest_w and img.height >= dest_h: + for i in range(3): + if img.width >= dest_w and img.height >= dest_h and (i > 0 or scale != 1): + break + + if shared.state.interrupted: break shape = (img.width, img.height) diff --git a/modules/upscaler_utils.py b/modules/upscaler_utils.py index b5e5a80ca..5ecbbed96 100644 --- a/modules/upscaler_utils.py +++ b/modules/upscaler_utils.py @@ -69,10 +69,10 @@ def upscale_with_model( for y, h, row in grid.tiles: newrow = [] for x, w, tile in row: - logger.debug("Tile (%d, %d) %s...", x, y, tile) + if shared.state.interrupted: + return img output = upscale_pil_patch(model, tile) scale_factor = output.width // tile.width - logger.debug("=> %s (scale factor %s)", output, scale_factor) newrow.append([x * scale_factor, w * scale_factor, output]) p.update(1) newtiles.append([y * scale_factor, h * scale_factor, newrow]) diff --git a/modules/util.py b/modules/util.py index 8d1aea44f..7911b0db7 100644 --- a/modules/util.py +++ b/modules/util.py @@ -81,6 +81,17 @@ def __init__(self, dirname): self.files = {x[0].lower(): x for x in files} self.files_cased = {x[0]: x for x in files} + def update_entry(self, filename): + """Add a file to the cache""" + file_path = os.path.join(self.dirname, filename) + try: + stat = os.stat(file_path) + entry = (filename, stat.st_mtime, stat.st_ctime) + self.files[filename.lower()] = entry + self.files_cased[filename] = entry + except FileNotFoundError as e: + print(f'MassFileListerCachedDir.add_entry: "{file_path}" {e}') + class MassFileLister: """A class that provides a way to check for the existence and mtime/ctile of files without doing more than one stat call per file.""" @@ -136,3 +147,67 @@ def mctime(self, path): def reset(self): """Clear the cache of all directories.""" self.cached_dirs.clear() + + def update_file_entry(self, path): + """Update the cache for a specific directory.""" + dirname, filename = os.path.split(path) + if cached_dir := self.cached_dirs.get(dirname): + cached_dir.update_entry(filename) + +def topological_sort(dependencies): + """Accepts a dictionary mapping name to its dependencies, returns a list of names ordered according to dependencies. + Ignores errors relating to missing dependencies or circular dependencies + """ + + visited = {} + result = [] + + def inner(name): + visited[name] = True + + for dep in dependencies.get(name, []): + if dep in dependencies and dep not in visited: + inner(dep) + + result.append(name) + + for depname in dependencies: + if depname not in visited: + inner(depname) + + return result + + +def open_folder(path): + """Open a folder in the file manager of the respect OS.""" + # import at function level to avoid potential issues + import gradio as gr + import platform + import sys + import subprocess + + if not os.path.exists(path): + msg = f'Folder "{path}" does not exist. after you save an image, the folder will be created.' + print(msg) + gr.Info(msg) + return + elif not os.path.isdir(path): + msg = f""" +WARNING +An open_folder request was made with an path that is not a folder. +This could be an error or a malicious attempt to run code on your computer. +Requested path was: {path} +""" + print(msg, file=sys.stderr) + gr.Warning(msg) + return + + path = os.path.normpath(path) + if platform.system() == "Windows": + os.startfile(path) + elif platform.system() == "Darwin": + subprocess.Popen(["open", path]) + elif "microsoft-standard-WSL2" in platform.uname().release: + subprocess.Popen(["explorer.exe", subprocess.check_output(["wslpath", "-w", path])]) + else: + subprocess.Popen(["xdg-open", path]) diff --git a/modules_forge/forge_alter_samplers.py b/modules_forge/forge_alter_samplers.py index 4e4822086..8316d322e 100644 --- a/modules_forge/forge_alter_samplers.py +++ b/modules_forge/forge_alter_samplers.py @@ -1,45 +1,22 @@ -import torch from modules import sd_samplers_kdiffusion, sd_samplers_common - from ldm_patched.k_diffusion import sampling as k_diffusion_sampling -from ldm_patched.modules.samplers import calculate_sigmas_scheduler class AlterSampler(sd_samplers_kdiffusion.KDiffusionSampler): - def __init__(self, sd_model, sampler_name, scheduler_name): + def __init__(self, sd_model, sampler_name): self.sampler_name = sampler_name - self.scheduler_name = scheduler_name self.unet = sd_model.forge_objects.unet - sampler_function = getattr(k_diffusion_sampling, "sample_{}".format(sampler_name)) super().__init__(sampler_function, sd_model, None) - def get_sigmas(self, p, steps): - if self.scheduler_name == 'turbo': - timesteps = torch.flip(torch.arange(1, steps + 1) * float(1000.0 / steps) - 1, (0,)).round().long().clip(0, 999) - sigmas = self.unet.model.model_sampling.sigma(timesteps) - sigmas = torch.cat([sigmas, sigmas.new_zeros([1])]) - else: - sigmas = calculate_sigmas_scheduler(self.unet.model, self.scheduler_name, steps) - return sigmas.to(self.unet.load_device) - -def build_constructor(sampler_name, scheduler_name): +def build_constructor(sampler_name): def constructor(m): - return AlterSampler(m, sampler_name, scheduler_name) + return AlterSampler(m, sampler_name) return constructor samplers_data_alter = [ - sd_samplers_common.SamplerData('DDPM', build_constructor(sampler_name='ddpm', scheduler_name='normal'), ['ddpm'], {}), - sd_samplers_common.SamplerData('DDPM Karras', build_constructor(sampler_name='ddpm', scheduler_name='karras'), ['ddpm_karras'], {}), - sd_samplers_common.SamplerData('Euler A Turbo', build_constructor(sampler_name='euler_ancestral', scheduler_name='turbo'), ['euler_ancestral_turbo'], {}), - sd_samplers_common.SamplerData('DPM++ 2M Turbo', build_constructor(sampler_name='dpmpp_2m', scheduler_name='turbo'), ['dpmpp_2m_turbo'], {}), - sd_samplers_common.SamplerData('DPM++ 2M SDE Turbo', build_constructor(sampler_name='dpmpp_2m_sde', scheduler_name='turbo'), ['dpmpp_2m_sde_turbo'], {}), - sd_samplers_common.SamplerData('LCM Karras', build_constructor(sampler_name='lcm', scheduler_name='karras'), ['lcm_karras'], {}), - sd_samplers_common.SamplerData('Euler SGMUniform', build_constructor(sampler_name='euler', scheduler_name='sgm_uniform'), ['euler_sgm_uniform'], {}), - sd_samplers_common.SamplerData('Euler A SGMUniform', build_constructor(sampler_name='euler_ancestral', scheduler_name='sgm_uniform'), ['euler_ancestral_sgm_uniform'], {}), - sd_samplers_common.SamplerData('DPM++ 2M SGMUniform', build_constructor(sampler_name='dpmpp_2m', scheduler_name='sgm_uniform'), ['dpmpp_2m_sgm_uniform'], {}), - sd_samplers_common.SamplerData('DPM++ 2M SDE SGMUniform', build_constructor(sampler_name='dpmpp_2m_sde', scheduler_name='sgm_uniform'), ['dpmpp_2m_sde_sgm_uniform'], {}), + sd_samplers_common.SamplerData('DDPM', build_constructor(sampler_name='ddpm'), ['ddpm'], {}), ] diff --git a/modules_forge/forge_canvas/canvas.css b/modules_forge/forge_canvas/canvas.css new file mode 100644 index 000000000..bc32583d5 --- /dev/null +++ b/modules_forge/forge_canvas/canvas.css @@ -0,0 +1,159 @@ +.forge-container { + width: 100%; + height: 512px; + position: relative; + overflow: hidden; +} + +.forge-image-container { + width: 100%; + height: calc(100% - 6px); + position: relative; + overflow: hidden; + background-color: #cccccc; + background-image: linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee), + linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee); + background-size: 20px 20px; + background-position: 0 0, 10px 10px; +} + +.forge-image { + position: absolute; + top: 0; + left: 0; + background-size: contain; + background-repeat: no-repeat; + cursor: grab; + max-width: unset !important; + max-height: unset !important; +} + +.forge-image:active { + cursor: grabbing; +} + +.forge-file-upload { + display: none; +} + +.forge-resize-line { + width: 100%; + height: 6px; + background-image: linear-gradient(to bottom, grey 50%, darkgrey 50%); + background-size: 4px 4px; + background-repeat: repeat; + cursor: ns-resize; + position: absolute; + bottom: 0; + left: 0; +} + +.forge-toolbar { + position: absolute; + top: 0px; + left: 0px; + z-index: 10; + background: rgba(47, 47, 47, 0.8); + padding: 6px 10px; + opacity: 0; + transition: opacity 0.3s ease; +} + +.forge-toolbar .forge-btn { + padding: 2px 6px; + border: none; + background-color: #4a4a4a; + color: white; + font-size: 14px; + cursor: pointer; + transition: background-color 0.3s ease; +} + +.forge-toolbar .forge-btn:hover { + background-color: #5e5e5e; +} + +.forge-toolbar .forge-btn:active { + background-color: #3e3e3e; +} + +.forge-toolbar-box-a { + flex-wrap: wrap; +} + +.forge-toolbar-box-b { + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: space-between; + gap: 4px; +} + +.forge-color-picker-block { + display: flex; + align-items: center; +} + +.forge-range-row { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + +.forge-toolbar-color { + border: none; + background: none; + padding: 3px; + border-radius: 50%; + width: 20px; + height: 20px; + -webkit-appearance: none; + appearance: none; + cursor: pointer; +} + +.forge-toolbar-color::-webkit-color-swatch-wrapper { + padding: 0; + border-radius: 50%; +} + +.forge-toolbar-color::-webkit-color-swatch { + border: none; + border-radius: 50%; + background: none; +} + +.forge-toolbar-label { + color: white !important; + padding: 0 4px; + display: flex; + align-items: center; + margin-bottom: 4px; /* Adjust margin as needed */ +} + +.forge-toolbar-range { +} + +.forge-scribble-indicator { + position: relative; + border-radius: 50%; + border: 1px solid; + pointer-events: none; + display: none; + width: 80px; + height: 80px; +} + +.forge-no-select { + user-select: none; +} + +.forge-upload-hint { + position: absolute; + top: 50%; + left: 50%; + width: 30%; + height: 30%; + transform: translate(-50%, -50%); +} diff --git a/modules_forge/forge_canvas/canvas.html b/modules_forge/forge_canvas/canvas.html new file mode 100644 index 000000000..a9554d190 --- /dev/null +++ b/modules_forge/forge_canvas/canvas.html @@ -0,0 +1,63 @@ +
    + +
    +
    + + + + + + + + + + +
    + + +
    +
    + + + + + + + + +
    +
    +
    + +
    +
    +
    brush width
    + +
    +
    +
    brush opacity
    + +
    +
    +
    brush softness
    + +
    +
    +
    +
    +
    +
    +
    diff --git a/modules_forge/forge_canvas/canvas.min.js b/modules_forge/forge_canvas/canvas.min.js new file mode 100644 index 000000000..601014072 --- /dev/null +++ b/modules_forge/forge_canvas/canvas.min.js @@ -0,0 +1 @@ +const _0x374a8c=_0xe5ae;(function(_0xb56795,_0x5457a6){const _0x5032c4=_0xe5ae,_0x36dd93=_0xb56795();while(!![]){try{const _0x8ddbe3=-parseInt(_0x5032c4(0x28c))/0x1*(-parseInt(_0x5032c4(0x240))/0x2)+-parseInt(_0x5032c4(0x27e))/0x3*(-parseInt(_0x5032c4(0x255))/0x4)+parseInt(_0x5032c4(0x205))/0x5+-parseInt(_0x5032c4(0x25b))/0x6+parseInt(_0x5032c4(0x262))/0x7+-parseInt(_0x5032c4(0x270))/0x8*(-parseInt(_0x5032c4(0x1e8))/0x9)+-parseInt(_0x5032c4(0x273))/0xa;if(_0x8ddbe3===_0x5457a6)break;else _0x36dd93['push'](_0x36dd93['shift']());}catch(_0x43c961){_0x36dd93['push'](_0x36dd93['shift']());}}}(_0x3ec7,0xef695));class GradioTextAreaBind{constructor(_0x1be7d4,_0x49c014){const _0x26ce03=_0xe5ae;this[_0x26ce03(0x1e9)]=document[_0x26ce03(0x1ef)]('#'+_0x1be7d4+'.'+_0x49c014+_0x26ce03(0x287)),this['sync_lock']=![],this['previousValue']='';}['set_value'](_0x2f6819){const _0x3acfca=_0xe5ae;if(this[_0x3acfca(0x22d)])return;this[_0x3acfca(0x22d)]=!![],this[_0x3acfca(0x1e9)]['value']=_0x2f6819,this[_0x3acfca(0x208)]=_0x2f6819;let _0x4b8a67=new Event(_0x3acfca(0x204),{'bubbles':!![]});Object[_0x3acfca(0x27b)](_0x4b8a67,'target',{'value':this[_0x3acfca(0x1e9)]}),this['target'][_0x3acfca(0x20c)](_0x4b8a67),this[_0x3acfca(0x208)]=_0x2f6819,this['sync_lock']=![];}[_0x374a8c(0x274)](_0x28b4a1){setInterval(()=>{const _0x3ec347=_0xe5ae;if(this['target'][_0x3ec347(0x1f6)]!==this['previousValue']){this['previousValue']=this[_0x3ec347(0x1e9)][_0x3ec347(0x1f6)];if(this['sync_lock'])return;this[_0x3ec347(0x22d)]=!![],_0x28b4a1(this['target'][_0x3ec347(0x1f6)]),this['sync_lock']=![];}},0x64);}}class ForgeCanvas{constructor(_0x486b67,_0x2e2919=![],_0xe39b25=![],_0x159b35=![],_0x4a8f00=0x200,_0x35abee=_0x374a8c(0x1f3),_0x4d1374=![],_0x372354=0x4,_0x4d2b07=![],_0x1f66ae=0x64,_0x34dc71=![],_0x110907=0x0,_0x55db66=![]){const _0x54ab35=_0x374a8c;this['gradio_config']=gradio_config,this['uuid']=_0x486b67,this[_0x54ab35(0x241)]=_0xe39b25,this[_0x54ab35(0x1cf)]=_0x159b35,this[_0x54ab35(0x1f5)]=_0x2e2919,this[_0x54ab35(0x1f4)]=_0x4a8f00,this[_0x54ab35(0x28d)]=null,this[_0x54ab35(0x250)]=0x0,this[_0x54ab35(0x24e)]=0x0,this[_0x54ab35(0x1d9)]=0x0,this[_0x54ab35(0x24a)]=0x0,this['imgScale']=0x1,this['dragging']=![],this[_0x54ab35(0x261)]=![],this[_0x54ab35(0x1ec)]=![],this[_0x54ab35(0x1fb)]=![],this[_0x54ab35(0x24c)]=_0x35abee,this[_0x54ab35(0x1e0)]=_0x372354,this[_0x54ab35(0x230)]=_0x1f66ae,this[_0x54ab35(0x21d)]=_0x110907,this[_0x54ab35(0x217)]=_0x4d1374,this['scribbleWidthFixed']=_0x4d2b07,this[_0x54ab35(0x1e2)]=_0x34dc71,this[_0x54ab35(0x269)]=_0x55db66,this[_0x54ab35(0x23c)]=[],this[_0x54ab35(0x246)]=-0x1,this['maximized']=![],this[_0x54ab35(0x28a)]={},this['contrast_pattern']=null,this['mouseInsideContainer']=![],this[_0x54ab35(0x26c)]=document['createElement']('canvas'),this[_0x54ab35(0x213)]=[],this['temp_draw_bg']=null,this[_0x54ab35(0x259)]=new GradioTextAreaBind(this[_0x54ab35(0x1dc)],_0x54ab35(0x1d1)),this['foreground_gradio_bind']=new GradioTextAreaBind(this[_0x54ab35(0x1dc)],_0x54ab35(0x1d5)),this[_0x54ab35(0x1eb)]();}[_0x374a8c(0x1eb)](){const _0x4611cb=_0x374a8c;let _0x44277b=this;const _0x540dd1=document[_0x4611cb(0x26d)](_0x4611cb(0x1ca)+_0x44277b['uuid']),_0x6f013c=document[_0x4611cb(0x26d)](_0x4611cb(0x227)+_0x44277b['uuid']),_0x1d67ee=document['getElementById'](_0x4611cb(0x1cd)+_0x44277b[_0x4611cb(0x1dc)]),_0x39d95e=document[_0x4611cb(0x26d)](_0x4611cb(0x282)+_0x44277b['uuid']),_0x3201f4=document[_0x4611cb(0x26d)]('toolbar_'+_0x44277b['uuid']),_0x2ba998=document[_0x4611cb(0x26d)](_0x4611cb(0x263)+_0x44277b[_0x4611cb(0x1dc)]),_0x3c3fac=document[_0x4611cb(0x26d)]('resetButton_'+_0x44277b['uuid']),_0x47f742=document['getElementById'](_0x4611cb(0x21e)+_0x44277b[_0x4611cb(0x1dc)]),_0x718fea=document['getElementById'](_0x4611cb(0x202)+_0x44277b[_0x4611cb(0x1dc)]),_0x31aae5=document[_0x4611cb(0x26d)](_0x4611cb(0x1f7)+_0x44277b[_0x4611cb(0x1dc)]),_0xdd4e04=document[_0x4611cb(0x26d)](_0x4611cb(0x267)+_0x44277b[_0x4611cb(0x1dc)]),_0x3723db=document[_0x4611cb(0x26d)](_0x4611cb(0x237)+_0x44277b[_0x4611cb(0x1dc)]),_0x2fdf34=document[_0x4611cb(0x26d)](_0x4611cb(0x228)+_0x44277b[_0x4611cb(0x1dc)]),_0x323f6d=document[_0x4611cb(0x26d)](_0x4611cb(0x23a)+_0x44277b['uuid']),_0x67434b=document[_0x4611cb(0x26d)](_0x4611cb(0x215)+_0x44277b[_0x4611cb(0x1dc)]),_0x4cb610=document[_0x4611cb(0x26d)](_0x4611cb(0x22b)+_0x44277b[_0x4611cb(0x1dc)]),_0x12201f=document[_0x4611cb(0x26d)](_0x4611cb(0x268)+_0x44277b[_0x4611cb(0x1dc)]),_0x4517ef=document[_0x4611cb(0x26d)](_0x4611cb(0x283)+_0x44277b[_0x4611cb(0x1dc)]),_0xeb2930=document['getElementById']('scribbleWidth_'+_0x44277b[_0x4611cb(0x1dc)]),_0x16a8be=document[_0x4611cb(0x26d)](_0x4611cb(0x234)+_0x44277b['uuid']),_0xd669cd=document[_0x4611cb(0x26d)](_0x4611cb(0x276)+_0x44277b[_0x4611cb(0x1dc)]),_0x4aac00=document[_0x4611cb(0x26d)]('scribbleAlpha_'+_0x44277b[_0x4611cb(0x1dc)]),_0x5e0b2c=document['getElementById'](_0x4611cb(0x1df)+_0x44277b[_0x4611cb(0x1dc)]),_0x102790=document['getElementById'](_0x4611cb(0x258)+_0x44277b[_0x4611cb(0x1dc)]),_0x4cd12a=document[_0x4611cb(0x26d)](_0x4611cb(0x1c9)+_0x44277b[_0x4611cb(0x1dc)]),_0x56a2cd=document[_0x4611cb(0x26d)]('softnessLabel_'+_0x44277b[_0x4611cb(0x1dc)]),_0x4ddd50=document['getElementById'](_0x4611cb(0x222)+_0x44277b[_0x4611cb(0x1dc)]);_0x12201f[_0x4611cb(0x1f6)]=_0x44277b[_0x4611cb(0x24c)],_0xeb2930[_0x4611cb(0x1f6)]=_0x44277b[_0x4611cb(0x1e0)],_0x4aac00[_0x4611cb(0x1f6)]=_0x44277b['scribbleAlpha'],_0x4cd12a['value']=_0x44277b[_0x4611cb(0x21d)];const _0x3f7f50=_0x44277b[_0x4611cb(0x1e0)]*0x14;_0x67434b['style'][_0x4611cb(0x242)]=_0x3f7f50+'px',_0x67434b[_0x4611cb(0x239)][_0x4611cb(0x203)]=_0x3f7f50+'px',_0x39d95e[_0x4611cb(0x239)][_0x4611cb(0x203)]=_0x44277b[_0x4611cb(0x1f4)]+'px',_0x3723db[_0x4611cb(0x242)]=_0x540dd1[_0x4611cb(0x285)],_0x3723db[_0x4611cb(0x203)]=_0x540dd1['clientHeight'];const _0x250102=_0x3723db[_0x4611cb(0x24d)]('2d');_0x44277b[_0x4611cb(0x27f)]=_0x3723db;_0x44277b[_0x4611cb(0x241)]&&(_0x3c3fac['style'][_0x4611cb(0x1fe)]=_0x4611cb(0x272),_0x31aae5[_0x4611cb(0x239)][_0x4611cb(0x1fe)]=_0x4611cb(0x272),_0xdd4e04[_0x4611cb(0x239)][_0x4611cb(0x1fe)]='none',_0x12201f['style'][_0x4611cb(0x1fe)]=_0x4611cb(0x272),_0x4517ef[_0x4611cb(0x239)][_0x4611cb(0x1fe)]=_0x4611cb(0x272),_0xd669cd[_0x4611cb(0x239)][_0x4611cb(0x1fe)]=_0x4611cb(0x272),_0x102790[_0x4611cb(0x239)][_0x4611cb(0x1fe)]=_0x4611cb(0x272),_0x4ddd50['style'][_0x4611cb(0x1fe)]='none',_0x67434b[_0x4611cb(0x239)][_0x4611cb(0x1fe)]=_0x4611cb(0x272),_0x3723db[_0x4611cb(0x239)][_0x4611cb(0x1fe)]=_0x4611cb(0x272));_0x44277b['no_upload']&&(_0x2ba998[_0x4611cb(0x239)]['display']=_0x4611cb(0x272),_0x4cb610[_0x4611cb(0x239)][_0x4611cb(0x1fe)]=_0x4611cb(0x272));if(_0x44277b['contrast_scribbles']){_0x4517ef[_0x4611cb(0x239)][_0x4611cb(0x1fe)]='none',_0x102790[_0x4611cb(0x239)][_0x4611cb(0x1fe)]=_0x4611cb(0x272),_0x4ddd50['style'][_0x4611cb(0x1fe)]=_0x4611cb(0x272);const _0x193153=_0x44277b[_0x4611cb(0x26c)],_0x21d8e9=0xa;_0x193153[_0x4611cb(0x242)]=_0x21d8e9*0x2,_0x193153[_0x4611cb(0x203)]=_0x21d8e9*0x2;const _0x2537fc=_0x193153[_0x4611cb(0x24d)]('2d');_0x2537fc[_0x4611cb(0x1db)]=_0x4611cb(0x21c),_0x2537fc['fillRect'](0x0,0x0,_0x21d8e9,_0x21d8e9),_0x2537fc[_0x4611cb(0x210)](_0x21d8e9,_0x21d8e9,_0x21d8e9,_0x21d8e9),_0x2537fc[_0x4611cb(0x1db)]='#000000',_0x2537fc[_0x4611cb(0x210)](_0x21d8e9,0x0,_0x21d8e9,_0x21d8e9),_0x2537fc[_0x4611cb(0x210)](0x0,_0x21d8e9,_0x21d8e9,_0x21d8e9),_0x44277b[_0x4611cb(0x1ee)]=_0x250102[_0x4611cb(0x229)](_0x193153,_0x4611cb(0x1d0)),_0x3723db[_0x4611cb(0x239)]['opacity']='0.5';}(_0x44277b['contrast_scribbles']||_0x44277b[_0x4611cb(0x217)]&&_0x44277b[_0x4611cb(0x1e2)]&&_0x44277b['scribbleSoftnessFixed'])&&(_0xd669cd['style'][_0x4611cb(0x242)]='100%',_0xeb2930[_0x4611cb(0x239)]['width']=_0x4611cb(0x220),_0x16a8be[_0x4611cb(0x239)][_0x4611cb(0x1fe)]=_0x4611cb(0x272));_0x44277b['scribbleColorFixed']&&(_0x4517ef['style'][_0x4611cb(0x1fe)]=_0x4611cb(0x272));_0x44277b[_0x4611cb(0x21a)]&&(_0xd669cd['style']['display']=_0x4611cb(0x272));_0x44277b[_0x4611cb(0x1e2)]&&(_0x102790[_0x4611cb(0x239)][_0x4611cb(0x1fe)]=_0x4611cb(0x272));_0x44277b['scribbleSoftnessFixed']&&(_0x4ddd50[_0x4611cb(0x239)][_0x4611cb(0x1fe)]=_0x4611cb(0x272));const _0x39af6d=new ResizeObserver(()=>{const _0x109c1b=_0x4611cb;_0x44277b[_0x109c1b(0x209)](),_0x44277b[_0x109c1b(0x26e)]();});_0x39af6d[_0x4611cb(0x26f)](_0x39d95e),document[_0x4611cb(0x26d)](_0x4611cb(0x23d)+_0x44277b['uuid'])[_0x4611cb(0x1de)](_0x4611cb(0x260),function(_0x529663){const _0x49a8fe=_0x4611cb;_0x44277b[_0x49a8fe(0x206)](_0x529663[_0x49a8fe(0x1e9)][_0x49a8fe(0x20b)][0x0]);}),_0x2ba998['addEventListener'](_0x4611cb(0x23b),function(){const _0x1fedee=_0x4611cb;if(_0x44277b[_0x1fedee(0x1f5)])return;document[_0x1fedee(0x26d)]('imageInput_'+_0x44277b[_0x1fedee(0x1dc)])[_0x1fedee(0x23b)]();}),_0x3c3fac['addEventListener'](_0x4611cb(0x23b),function(){const _0x378ac5=_0x4611cb;_0x44277b[_0x378ac5(0x26b)]();}),_0x47f742['addEventListener'](_0x4611cb(0x23b),function(){const _0x2c9eba=_0x4611cb;_0x44277b['adjustInitialPositionAndScale'](),_0x44277b[_0x2c9eba(0x26e)]();}),_0x718fea[_0x4611cb(0x1de)](_0x4611cb(0x23b),function(){const _0x44bc8b=_0x4611cb;_0x44277b[_0x44bc8b(0x1e4)]();}),_0x31aae5[_0x4611cb(0x1de)](_0x4611cb(0x23b),function(){const _0x595d80=_0x4611cb;_0x44277b[_0x595d80(0x252)]();}),_0xdd4e04['addEventListener'](_0x4611cb(0x23b),function(){const _0x5e57d4=_0x4611cb;_0x44277b[_0x5e57d4(0x1c6)]();}),_0x12201f[_0x4611cb(0x1de)](_0x4611cb(0x204),function(){const _0xe753c6=_0x4611cb;_0x44277b[_0xe753c6(0x24c)]=this[_0xe753c6(0x1f6)],_0x67434b[_0xe753c6(0x239)][_0xe753c6(0x257)]=_0x44277b[_0xe753c6(0x24c)];}),_0xeb2930[_0x4611cb(0x1de)](_0x4611cb(0x204),function(){const _0x4fb7fc=_0x4611cb;_0x44277b[_0x4fb7fc(0x1e0)]=this[_0x4fb7fc(0x1f6)];const _0x52346e=_0x44277b[_0x4fb7fc(0x1e0)]*0x14;_0x67434b['style']['width']=_0x52346e+'px',_0x67434b['style'][_0x4fb7fc(0x203)]=_0x52346e+'px';}),_0x4aac00['addEventListener'](_0x4611cb(0x204),function(){const _0x335d66=_0x4611cb;_0x44277b[_0x335d66(0x230)]=this['value'];}),_0x4cd12a['addEventListener'](_0x4611cb(0x204),function(){const _0x29257f=_0x4611cb;_0x44277b[_0x29257f(0x21d)]=this['value'];}),_0x3723db[_0x4611cb(0x1de)](_0x4611cb(0x27d),function(_0x1e1333){const _0x56b21e=_0x4611cb;if(!_0x44277b[_0x56b21e(0x28d)]||_0x1e1333[_0x56b21e(0x27c)]!==0x0||_0x44277b[_0x56b21e(0x241)])return;const _0x591721=_0x3723db[_0x56b21e(0x244)]();_0x44277b[_0x56b21e(0x1fb)]=!![],_0x3723db[_0x56b21e(0x239)]['cursor']='crosshair',_0x67434b[_0x56b21e(0x239)][_0x56b21e(0x1fe)]=_0x56b21e(0x272),_0x44277b[_0x56b21e(0x213)]=[[(_0x1e1333[_0x56b21e(0x1e7)]-_0x591721[_0x56b21e(0x275)])/_0x44277b[_0x56b21e(0x1f9)],(_0x1e1333['clientY']-_0x591721[_0x56b21e(0x1ce)])/_0x44277b['imgScale']]],_0x44277b[_0x56b21e(0x281)]=_0x250102[_0x56b21e(0x1d2)](0x0,0x0,_0x3723db[_0x56b21e(0x242)],_0x3723db[_0x56b21e(0x203)]),_0x44277b[_0x56b21e(0x265)](_0x1e1333);}),_0x3723db[_0x4611cb(0x1de)]('mousemove',function(_0xd2f5e6){const _0x241d14=_0x4611cb;_0x44277b[_0x241d14(0x1fb)]&&_0x44277b['handleDraw'](_0xd2f5e6);_0x44277b[_0x241d14(0x28d)]&&!_0x44277b[_0x241d14(0x218)]&&(_0x3723db[_0x241d14(0x239)][_0x241d14(0x223)]=_0x241d14(0x25d));if(_0x44277b[_0x241d14(0x28d)]&&!_0x44277b[_0x241d14(0x1fb)]&&!_0x44277b[_0x241d14(0x218)]&&!_0x44277b['no_scribbles']){const _0x110a6d=_0x540dd1[_0x241d14(0x244)](),_0x454ab9=_0x44277b[_0x241d14(0x1e0)]*0xa;_0x67434b[_0x241d14(0x239)][_0x241d14(0x275)]=_0xd2f5e6[_0x241d14(0x1e7)]-_0x110a6d[_0x241d14(0x275)]-_0x454ab9+'px',_0x67434b[_0x241d14(0x239)][_0x241d14(0x1ce)]=_0xd2f5e6[_0x241d14(0x23e)]-_0x110a6d['top']-_0x454ab9+'px',_0x67434b[_0x241d14(0x239)][_0x241d14(0x1fe)]=_0x241d14(0x248);}}),_0x3723db[_0x4611cb(0x1de)](_0x4611cb(0x20d),function(){const _0x2eebc5=_0x4611cb;_0x44277b[_0x2eebc5(0x1fb)]=![],_0x3723db[_0x2eebc5(0x239)][_0x2eebc5(0x223)]='',_0x44277b[_0x2eebc5(0x22c)]();}),_0x3723db['addEventListener'](_0x4611cb(0x1e5),function(){const _0x4461c3=_0x4611cb;_0x44277b[_0x4461c3(0x1fb)]=![],_0x3723db[_0x4461c3(0x239)][_0x4461c3(0x223)]='',_0x67434b[_0x4461c3(0x239)]['display']=_0x4461c3(0x272);}),_0x3201f4[_0x4611cb(0x1de)](_0x4611cb(0x27d),function(_0x5439f1){const _0x3f0cb9=_0x4611cb;_0x5439f1[_0x3f0cb9(0x1f1)]();}),_0x540dd1[_0x4611cb(0x1de)](_0x4611cb(0x27d),function(_0xe3fcfd){const _0x149dfb=_0x4611cb,_0x1854f1=_0x540dd1[_0x149dfb(0x244)](),_0x4f9bc1=_0xe3fcfd[_0x149dfb(0x1e7)]-_0x1854f1[_0x149dfb(0x275)],_0x240702=_0xe3fcfd[_0x149dfb(0x23e)]-_0x1854f1['top'];if(_0xe3fcfd[_0x149dfb(0x27c)]===0x2&&_0x44277b[_0x149dfb(0x1e3)](_0x4f9bc1,_0x240702))_0x44277b[_0x149dfb(0x218)]=!![],_0x44277b['offsetX']=_0x4f9bc1-_0x44277b[_0x149dfb(0x250)],_0x44277b[_0x149dfb(0x201)]=_0x240702-_0x44277b[_0x149dfb(0x24e)],_0x6f013c[_0x149dfb(0x239)][_0x149dfb(0x223)]='grabbing',_0x3723db[_0x149dfb(0x239)][_0x149dfb(0x223)]=_0x149dfb(0x1da),_0x67434b[_0x149dfb(0x239)]['display']=_0x149dfb(0x272);else _0xe3fcfd[_0x149dfb(0x27c)]===0x0&&!_0x44277b['img']&&!_0x44277b[_0x149dfb(0x1f5)]&&document[_0x149dfb(0x26d)](_0x149dfb(0x23d)+_0x44277b[_0x149dfb(0x1dc)])[_0x149dfb(0x23b)]();}),_0x540dd1[_0x4611cb(0x1de)]('mousemove',function(_0x523813){const _0x5f0b2c=_0x4611cb;if(_0x44277b['dragging']){const _0x25e960=_0x540dd1[_0x5f0b2c(0x244)](),_0x13ef14=_0x523813[_0x5f0b2c(0x1e7)]-_0x25e960['left'],_0x47fe96=_0x523813[_0x5f0b2c(0x23e)]-_0x25e960[_0x5f0b2c(0x1ce)];_0x44277b['imgX']=_0x13ef14-_0x44277b[_0x5f0b2c(0x224)],_0x44277b[_0x5f0b2c(0x24e)]=_0x47fe96-_0x44277b[_0x5f0b2c(0x201)],_0x44277b[_0x5f0b2c(0x26e)](),_0x44277b['dragged_just_now']=!![];}}),_0x540dd1[_0x4611cb(0x1de)](_0x4611cb(0x20d),function(_0x5cb4f){const _0x5bf59f=_0x4611cb;_0x44277b[_0x5bf59f(0x218)]&&_0x44277b[_0x5bf59f(0x1f8)](_0x5cb4f,![]);}),_0x540dd1[_0x4611cb(0x1de)]('mouseleave',function(_0x19abb7){const _0x429eba=_0x4611cb;_0x44277b[_0x429eba(0x218)]&&_0x44277b[_0x429eba(0x1f8)](_0x19abb7,!![]);}),_0x540dd1[_0x4611cb(0x1de)]('wheel',function(_0x5c9bc8){const _0x116d3a=_0x4611cb;if(!_0x44277b[_0x116d3a(0x28d)])return;_0x5c9bc8['preventDefault']();const _0x21a087=_0x540dd1[_0x116d3a(0x244)](),_0x1a73a6=_0x5c9bc8['clientX']-_0x21a087[_0x116d3a(0x275)],_0x3bb7b0=_0x5c9bc8[_0x116d3a(0x23e)]-_0x21a087['top'],_0x2bf160=_0x44277b[_0x116d3a(0x1f9)],_0x503211=_0x5c9bc8[_0x116d3a(0x219)]*-0.001;_0x44277b[_0x116d3a(0x1f9)]+=_0x503211,_0x44277b['imgScale']=Math['max'](0.1,_0x44277b[_0x116d3a(0x1f9)]);const _0x3e5feb=_0x44277b[_0x116d3a(0x1f9)]/_0x2bf160;_0x44277b['imgX']=_0x1a73a6-(_0x1a73a6-_0x44277b[_0x116d3a(0x250)])*_0x3e5feb,_0x44277b[_0x116d3a(0x24e)]=_0x3bb7b0-(_0x3bb7b0-_0x44277b[_0x116d3a(0x24e)])*_0x3e5feb,_0x44277b[_0x116d3a(0x26e)]();}),_0x540dd1['addEventListener'](_0x4611cb(0x21f),function(_0x44c56a){const _0x2b68b3=_0x4611cb;_0x44277b['dragged_just_now']&&_0x44c56a[_0x2b68b3(0x236)](),_0x44277b[_0x2b68b3(0x261)]=![];}),_0x540dd1[_0x4611cb(0x1de)](_0x4611cb(0x20e),function(){const _0x311bcf=_0x4611cb;_0x3201f4['style'][_0x311bcf(0x20a)]='1',!_0x44277b[_0x311bcf(0x28d)]&&!_0x44277b[_0x311bcf(0x1f5)]&&(_0x540dd1[_0x311bcf(0x239)][_0x311bcf(0x223)]='pointer');}),_0x540dd1[_0x4611cb(0x1de)](_0x4611cb(0x1d3),function(){const _0x19ff1c=_0x4611cb;_0x3201f4['style'][_0x19ff1c(0x20a)]='0',_0x6f013c[_0x19ff1c(0x239)][_0x19ff1c(0x223)]='',_0x3723db[_0x19ff1c(0x239)]['cursor']='',_0x540dd1['style']['cursor']='',_0x67434b[_0x19ff1c(0x239)][_0x19ff1c(0x1fe)]=_0x19ff1c(0x272);}),_0x1d67ee[_0x4611cb(0x1de)](_0x4611cb(0x27d),function(_0x40f337){const _0x1a39c5=_0x4611cb;_0x44277b['resizing']=!![],_0x40f337[_0x1a39c5(0x236)](),_0x40f337[_0x1a39c5(0x1f1)]();}),document[_0x4611cb(0x1de)](_0x4611cb(0x1ea),function(_0x14ad60){const _0x301046=_0x4611cb;if(_0x44277b['resizing']){const _0x350a32=_0x39d95e[_0x301046(0x244)](),_0x2857b4=_0x14ad60['clientY']-_0x350a32['top'];_0x39d95e[_0x301046(0x239)][_0x301046(0x203)]=_0x2857b4+'px',_0x14ad60['preventDefault'](),_0x14ad60[_0x301046(0x1f1)]();}}),document[_0x4611cb(0x1de)](_0x4611cb(0x20d),function(){const _0x19b8d4=_0x4611cb;_0x44277b[_0x19b8d4(0x1ec)]=![];}),document[_0x4611cb(0x1de)](_0x4611cb(0x1e5),function(){const _0x498f32=_0x4611cb;_0x44277b[_0x498f32(0x1ec)]=![];}),['dragenter',_0x4611cb(0x266),_0x4611cb(0x1c8),_0x4611cb(0x1e6)][_0x4611cb(0x1fd)](_0x1ff25d=>{const _0xebdc28=_0x4611cb;_0x540dd1[_0xebdc28(0x1de)](_0x1ff25d,_0x1b55c8,![]);});function _0x1b55c8(_0x2f6e52){const _0x1e361a=_0x4611cb;_0x2f6e52[_0x1e361a(0x236)](),_0x2f6e52[_0x1e361a(0x1f1)]();}_0x540dd1[_0x4611cb(0x1de)](_0x4611cb(0x1cb),()=>{const _0x3e07a7=_0x4611cb;_0x6f013c[_0x3e07a7(0x239)]['cursor']=_0x3e07a7(0x253),_0x3723db[_0x3e07a7(0x239)][_0x3e07a7(0x223)]=_0x3e07a7(0x253);}),_0x540dd1[_0x4611cb(0x1de)]('dragleave',()=>{const _0xe21f36=_0x4611cb;_0x6f013c['style']['cursor']='',_0x3723db[_0xe21f36(0x239)][_0xe21f36(0x223)]='';}),_0x540dd1[_0x4611cb(0x1de)]('drop',function(_0xfd1575){const _0x5f2856=_0x4611cb;_0x6f013c[_0x5f2856(0x239)][_0x5f2856(0x223)]='',_0x3723db[_0x5f2856(0x239)][_0x5f2856(0x223)]='';const _0x3f69e0=_0xfd1575['dataTransfer'],_0x58abcc=_0x3f69e0[_0x5f2856(0x20b)];_0x58abcc[_0x5f2856(0x20f)]>0x0&&_0x44277b['handleFileUpload'](_0x58abcc[0x0]);}),_0x540dd1[_0x4611cb(0x1de)]('mouseenter',()=>{const _0x448e84=_0x4611cb;_0x44277b[_0x448e84(0x254)]=!![];}),_0x540dd1[_0x4611cb(0x1de)](_0x4611cb(0x1e5),()=>{const _0x1881e2=_0x4611cb;_0x44277b[_0x1881e2(0x254)]=![];}),document[_0x4611cb(0x1de)](_0x4611cb(0x251),function(_0x2c1418){const _0x210e83=_0x4611cb;_0x44277b[_0x210e83(0x254)]&&_0x44277b[_0x210e83(0x1d4)](_0x2c1418);}),document['addEventListener'](_0x4611cb(0x1fc),_0x563be4=>{const _0x2a82c6=_0x4611cb;if(!_0x44277b[_0x2a82c6(0x254)])return;_0x563be4[_0x2a82c6(0x25a)]&&_0x563be4[_0x2a82c6(0x1dd)]==='z'&&(_0x563be4[_0x2a82c6(0x236)](),this[_0x2a82c6(0x252)]()),_0x563be4[_0x2a82c6(0x25a)]&&_0x563be4[_0x2a82c6(0x1dd)]==='y'&&(_0x563be4[_0x2a82c6(0x236)](),this[_0x2a82c6(0x1c6)]());}),_0x2fdf34[_0x4611cb(0x1de)]('click',function(){const _0x1670d7=_0x4611cb;_0x44277b[_0x1670d7(0x22a)]();}),_0x323f6d[_0x4611cb(0x1de)](_0x4611cb(0x23b),function(){const _0x1f9b8a=_0x4611cb;_0x44277b[_0x1f9b8a(0x1c7)]();}),_0x44277b[_0x4611cb(0x27a)](),_0x44277b[_0x4611cb(0x259)][_0x4611cb(0x274)](function(_0x3ad581){const _0x4f3d4b=_0x4611cb;_0x44277b[_0x4f3d4b(0x211)](_0x3ad581);}),_0x44277b['foreground_gradio_bind'][_0x4611cb(0x274)](function(_0x55fa67){_0x44277b['uploadBase64DrawingCanvas'](_0x55fa67);});}['handleDraw'](_0x1e24bd){const _0x51bcda=_0x374a8c,_0x4766b7=this[_0x51bcda(0x27f)],_0x3ca2ff=_0x4766b7[_0x51bcda(0x24d)]('2d'),_0x5be4b2=_0x4766b7[_0x51bcda(0x244)](),_0x2df6b2=(_0x1e24bd[_0x51bcda(0x1e7)]-_0x5be4b2[_0x51bcda(0x275)])/this[_0x51bcda(0x1f9)],_0x23d726=(_0x1e24bd[_0x51bcda(0x23e)]-_0x5be4b2[_0x51bcda(0x1ce)])/this['imgScale'];this[_0x51bcda(0x213)][_0x51bcda(0x284)]([_0x2df6b2,_0x23d726]),_0x3ca2ff[_0x51bcda(0x289)](this[_0x51bcda(0x281)],0x0,0x0),_0x3ca2ff[_0x51bcda(0x1ed)](),_0x3ca2ff[_0x51bcda(0x243)](this[_0x51bcda(0x213)][0x0][0x0],this[_0x51bcda(0x213)][0x0][0x1]);for(let _0x5d4b63=0x1;_0x5d4b630x0)){_0x3ca2ff[_0x51bcda(0x247)]=_0x51bcda(0x23f),_0x3ca2ff[_0x51bcda(0x26a)]=0x1,_0x3ca2ff[_0x51bcda(0x288)]();return;}_0x3ca2ff[_0x51bcda(0x247)]=_0x51bcda(0x1d8);if(!(this[_0x51bcda(0x21d)]>0x0)){_0x3ca2ff[_0x51bcda(0x26a)]=this[_0x51bcda(0x230)]/0x64,_0x3ca2ff[_0x51bcda(0x288)]();return;}const _0x12be7b=_0x3ca2ff[_0x51bcda(0x24b)]*(0x1-this[_0x51bcda(0x21d)]/0x96),_0x8dfe71=_0x3ca2ff['lineWidth']*(0x1+this['scribbleSoftness']/0x96),_0x58b349=Math[_0x51bcda(0x28b)](0x5+this['scribbleSoftness']/0x5),_0x36e61a=(_0x8dfe71-_0x12be7b)/(_0x58b349-0x1);_0x3ca2ff[_0x51bcda(0x26a)]=0x1-Math[_0x51bcda(0x25c)](0x1-Math['min'](this[_0x51bcda(0x230)]/0x64,0.95),0x1/_0x58b349);for(let _0x4a18d7=0x0;_0x4a18d7<_0x58b349;_0x4a18d7++){_0x3ca2ff[_0x51bcda(0x24b)]=_0x12be7b+_0x36e61a*_0x4a18d7,_0x3ca2ff[_0x51bcda(0x288)]();}}[_0x374a8c(0x206)](_0x5531ae){const _0x1048ff=_0x374a8c;if(_0x5531ae&&!this[_0x1048ff(0x1f5)]){const _0x4a10d8=new FileReader();_0x4a10d8[_0x1048ff(0x216)]=_0x5c0fd2=>{const _0x16ca45=_0x1048ff;this[_0x16ca45(0x211)](_0x5c0fd2[_0x16ca45(0x1e9)][_0x16ca45(0x25f)]);},_0x4a10d8['readAsDataURL'](_0x5531ae);}}[_0x374a8c(0x1d4)](_0x85c097){const _0x49df7d=_0x374a8c,_0x5deb03=_0x85c097[_0x49df7d(0x249)][_0x49df7d(0x1f2)];for(let _0x4bbeb0=0x0;_0x4bbeb0<_0x5deb03[_0x49df7d(0x20f)];_0x4bbeb0++){const _0x5ab24f=_0x5deb03[_0x4bbeb0];if(_0x5ab24f['type']['indexOf'](_0x49df7d(0x245))!==-0x1){const _0x4ee5c5=_0x5ab24f[_0x49df7d(0x235)]();this[_0x49df7d(0x206)](_0x4ee5c5);break;}}}[_0x374a8c(0x211)](_0xd82930){const _0x28362b=_0x374a8c;if(typeof this[_0x28362b(0x1d7)]!==_0x28362b(0x1ff)){if(!this[_0x28362b(0x1d7)][_0x28362b(0x25e)][_0x28362b(0x278)](0x1+0x4+0x1-0x2+''))return;}else return;const _0x4eec46=new Image();_0x4eec46[_0x28362b(0x216)]=()=>{const _0xa13c84=_0x28362b;this[_0xa13c84(0x28d)]=_0xd82930,this['orgWidth']=_0x4eec46[_0xa13c84(0x242)],this[_0xa13c84(0x24a)]=_0x4eec46[_0xa13c84(0x203)];const _0x35593e=document['getElementById'](_0xa13c84(0x237)+this['uuid']);(_0x35593e[_0xa13c84(0x242)]!==_0x4eec46['width']||_0x35593e[_0xa13c84(0x203)]!==_0x4eec46[_0xa13c84(0x203)])&&(_0x35593e[_0xa13c84(0x242)]=_0x4eec46['width'],_0x35593e[_0xa13c84(0x203)]=_0x4eec46[_0xa13c84(0x203)]),this['adjustInitialPositionAndScale'](),this[_0xa13c84(0x26e)](),this[_0xa13c84(0x279)](),this['saveState'](),document[_0xa13c84(0x26d)](_0xa13c84(0x23d)+this['uuid'])[_0xa13c84(0x1f6)]=null,document['getElementById'](_0xa13c84(0x22b)+this[_0xa13c84(0x1dc)])[_0xa13c84(0x239)]['display']=_0xa13c84(0x272);};if(_0xd82930)_0x4eec46['src']=_0xd82930;else{this[_0x28362b(0x28d)]=null;const _0x4da790=document[_0x28362b(0x26d)](_0x28362b(0x237)+this[_0x28362b(0x1dc)]);_0x4da790[_0x28362b(0x242)]=0x1,_0x4da790['height']=0x1,this[_0x28362b(0x209)](),this[_0x28362b(0x26e)](),this[_0x28362b(0x279)](),this[_0x28362b(0x22c)]();}}[_0x374a8c(0x22e)](_0xea0055){const _0x5af1ce=_0x374a8c,_0x158e8a=new Image();_0x158e8a[_0x5af1ce(0x216)]=()=>{const _0x412900=_0x5af1ce,_0x5e7d3e=document[_0x412900(0x26d)](_0x412900(0x237)+this[_0x412900(0x1dc)]),_0x49fa8c=_0x5e7d3e['getContext']('2d');_0x49fa8c[_0x412900(0x212)](0x0,0x0,_0x5e7d3e['width'],_0x5e7d3e['height']),_0x49fa8c[_0x412900(0x26e)](_0x158e8a,0x0,0x0),this[_0x412900(0x22c)]();};if(_0xea0055)_0x158e8a[_0x5af1ce(0x256)]=_0xea0055;else{const _0x127e54=document[_0x5af1ce(0x26d)](_0x5af1ce(0x237)+this[_0x5af1ce(0x1dc)]),_0x4d21d0=_0x127e54[_0x5af1ce(0x24d)]('2d');_0x4d21d0[_0x5af1ce(0x212)](0x0,0x0,_0x127e54[_0x5af1ce(0x242)],_0x127e54[_0x5af1ce(0x203)]),this[_0x5af1ce(0x22c)]();}}[_0x374a8c(0x1e3)](_0x3d1f50,_0xf5ae34){const _0x462f7c=_0x374a8c,_0x58db8e=this[_0x462f7c(0x1d9)]*this[_0x462f7c(0x1f9)],_0x4045e8=this[_0x462f7c(0x24a)]*this['imgScale'];return _0x3d1f50>this[_0x462f7c(0x250)]&&_0x3d1f50this['imgY']&&_0xf5ae340x0&&(this[_0xa2a5f0(0x246)]--,this['restoreState'](),this['updateUndoRedoButtons']());}['redo'](){const _0x209b5a=_0x374a8c;this[_0x209b5a(0x246)]=this['history']['length']-0x1,_0x376ab2[_0x8ba24e(0x239)][_0x8ba24e(0x20a)]=_0x376ab2[_0x8ba24e(0x221)]?_0x8ba24e(0x207):'1',_0x4170b8['style'][_0x8ba24e(0x20a)]=_0x4170b8['disabled']?_0x8ba24e(0x207):'1';}[_0x374a8c(0x279)](){const _0xe49cc0=_0x374a8c;if(!this[_0xe49cc0(0x28d)]){this['background_gradio_bind']['set_value']('');return;}const _0x1251b9=document['getElementById']('image_'+this[_0xe49cc0(0x1dc)]),_0x8a2e42=this['temp_canvas'],_0x534739=_0x8a2e42[_0xe49cc0(0x24d)]('2d');_0x8a2e42['width']=this[_0xe49cc0(0x1d9)],_0x8a2e42[_0xe49cc0(0x203)]=this[_0xe49cc0(0x24a)],_0x534739[_0xe49cc0(0x26e)](_0x1251b9,0x0,0x0,this[_0xe49cc0(0x1d9)],this[_0xe49cc0(0x24a)]);const _0x41ec46=_0x8a2e42[_0xe49cc0(0x1e1)](_0xe49cc0(0x1d6));this[_0xe49cc0(0x259)][_0xe49cc0(0x231)](_0x41ec46);}[_0x374a8c(0x225)](){const _0x374804=_0x374a8c;if(!this[_0x374804(0x28d)]){this[_0x374804(0x200)][_0x374804(0x231)]('');return;}const _0x388adf=document[_0x374804(0x26d)]('drawingCanvas_'+this[_0x374804(0x1dc)]),_0x368c03=_0x388adf[_0x374804(0x1e1)](_0x374804(0x1d6));this[_0x374804(0x200)]['set_value'](_0x368c03);}[_0x374a8c(0x22a)](){const _0xa73976=_0x374a8c;if(this[_0xa73976(0x1cc)])return;const _0x302b84=document[_0xa73976(0x26d)](_0xa73976(0x282)+this['uuid']),_0xf60ce8=document[_0xa73976(0x26d)](_0xa73976(0x286)+this['uuid']),_0x5b82e3=document[_0xa73976(0x26d)](_0xa73976(0x228)+this[_0xa73976(0x1dc)]),_0x19f97=document[_0xa73976(0x26d)]('minButton_'+this[_0xa73976(0x1dc)]);this[_0xa73976(0x28a)]={'width':_0x302b84[_0xa73976(0x239)][_0xa73976(0x242)],'height':_0x302b84[_0xa73976(0x239)]['height'],'top':_0x302b84['style'][_0xa73976(0x1ce)],'left':_0x302b84['style']['left'],'position':_0x302b84[_0xa73976(0x239)]['position'],'zIndex':_0x302b84[_0xa73976(0x239)][_0xa73976(0x226)]},_0x302b84[_0xa73976(0x239)][_0xa73976(0x242)]=_0xa73976(0x232),_0x302b84[_0xa73976(0x239)]['height']=_0xa73976(0x21b),_0x302b84[_0xa73976(0x239)]['top']='0',_0x302b84['style'][_0xa73976(0x275)]='0',_0x302b84[_0xa73976(0x239)][_0xa73976(0x1f0)]=_0xa73976(0x271),_0x302b84[_0xa73976(0x239)][_0xa73976(0x226)]=_0xa73976(0x238),_0x5b82e3[_0xa73976(0x239)][_0xa73976(0x1fe)]=_0xa73976(0x272),_0x19f97[_0xa73976(0x239)][_0xa73976(0x1fe)]=_0xa73976(0x264),this[_0xa73976(0x1cc)]=!![];}[_0x374a8c(0x1c7)](){const _0x1bc47a=_0x374a8c;if(!this['maximized'])return;const _0x4ccdb3=document['getElementById'](_0x1bc47a(0x282)+this['uuid']),_0x116cf6=document[_0x1bc47a(0x26d)](_0x1bc47a(0x228)+this[_0x1bc47a(0x1dc)]),_0x1ba7c0=document[_0x1bc47a(0x26d)]('minButton_'+this[_0x1bc47a(0x1dc)]);_0x4ccdb3[_0x1bc47a(0x239)]['width']=this[_0x1bc47a(0x28a)][_0x1bc47a(0x242)],_0x4ccdb3[_0x1bc47a(0x239)][_0x1bc47a(0x203)]=this['originalState'][_0x1bc47a(0x203)],_0x4ccdb3[_0x1bc47a(0x239)][_0x1bc47a(0x1ce)]=this[_0x1bc47a(0x28a)][_0x1bc47a(0x1ce)],_0x4ccdb3[_0x1bc47a(0x239)][_0x1bc47a(0x275)]=this[_0x1bc47a(0x28a)][_0x1bc47a(0x275)],_0x4ccdb3[_0x1bc47a(0x239)][_0x1bc47a(0x1f0)]=this[_0x1bc47a(0x28a)][_0x1bc47a(0x1f0)],_0x4ccdb3[_0x1bc47a(0x239)][_0x1bc47a(0x226)]=this[_0x1bc47a(0x28a)][_0x1bc47a(0x226)],_0x116cf6['style'][_0x1bc47a(0x1fe)]=_0x1bc47a(0x264),_0x1ba7c0[_0x1bc47a(0x239)][_0x1bc47a(0x1fe)]=_0x1bc47a(0x272),this[_0x1bc47a(0x1cc)]=![];}[_0x374a8c(0x1f8)](_0x5d8941,_0x1ca1cf){const _0x1db4da=_0x374a8c,_0x1065a9=document[_0x1db4da(0x26d)](_0x1db4da(0x227)+this[_0x1db4da(0x1dc)]),_0x44fd56=document[_0x1db4da(0x26d)](_0x1db4da(0x237)+this['uuid']);this[_0x1db4da(0x218)]=![],_0x1065a9[_0x1db4da(0x239)][_0x1db4da(0x223)]=_0x1db4da(0x277),_0x44fd56[_0x1db4da(0x239)]['cursor']=_0x1db4da(0x277);}}function _0x3ec7(){const _0x2f644a=['13644547gnftKV','uploadButton_','inline-block','handleDraw','dragover','redoButton_','scribbleColor_','scribbleSoftnessFixed','globalAlpha','resetImage','temp_canvas','getElementById','drawImage','observe','12132616ymoAjN','fixed','none','50194130SOKNXq','listen','left','scribbleWidthBlock_','grab','startsWith','on_img_upload','updateUndoRedoButtons','defineProperty','button','mousedown','26322OKSXUe','drawingCanvas','slice','temp_draw_bg','container_','scribbleColorBlock_','push','clientWidth','toolbar_','\x20textarea','stroke','putImageData','originalState','round','1OGJEZj','img','redo','minimize','dragleave','scribbleSoftness_','imageContainer_','dragenter','maximized','resizeLine_','top','contrast_scribbles','repeat','logical_image_background','getImageData','mouseout','handlePaste','logical_image_foreground','image/png','gradio_config','source-over','orgWidth','grabbing','fillStyle','uuid','key','addEventListener','alphaLabel_','scribbleWidth','toDataURL','scribbleAlphaFixed','isInsideImage','removeImage','mouseleave','drop','clientX','9mbfKci','target','mousemove','start','resizing','beginPath','contrast_pattern','querySelector','position','stopPropagation','items','#000000','initial_height','no_upload','value','undoButton_','handleDragEnd','imgScale','strokeStyle','drawing','keydown','forEach','display','undefined','foreground_gradio_bind','offsetY','removeButton_','height','input','9368075wZYBhj','handleFileUpload','0.5','previousValue','adjustInitialPositionAndScale','opacity','files','dispatchEvent','mouseup','mouseover','length','fillRect','uploadBase64','clearRect','temp_draw_points','min','scribbleIndicator_','onload','scribbleColorFixed','dragging','deltaY','scribbleWidthFixed','100vh','#ffffff','scribbleSoftness','centerButton_','contextmenu','100%','disabled','scribbleSoftnessBlock_','cursor','offsetX','on_drawing_canvas_upload','zIndex','image_','maxButton_','createPattern','maximize','uploadHint_','saveState','sync_lock','uploadBase64DrawingCanvas','clientHeight','scribbleAlpha','set_value','100vw','lineJoin','widthLabel_','getAsFile','preventDefault','drawingCanvas_','1000','style','minButton_','click','history','imageInput_','clientY','destination-out','3123806gxlYDD','no_scribbles','width','moveTo','getBoundingClientRect','image','historyIndex','globalCompositeOperation','block','clipboardData','orgHeight','lineWidth','scribbleColor','getContext','imgY','restoreState','imgX','paste','undo','copy','mouseInsideContainer','412gshJgS','src','borderColor','scribbleAlphaBlock_','background_gradio_bind','ctrlKey','10829976sdcoBo','pow','crosshair','version','result','change','dragged_just_now'];_0x3ec7=function(){return _0x2f644a;};return _0x3ec7();}function _0xe5ae(_0xfed99,_0xc31b63){const _0x3ec7ec=_0x3ec7();return _0xe5ae=function(_0xe5ae18,_0x559b8){_0xe5ae18=_0xe5ae18-0x1c6;let _0x13de64=_0x3ec7ec[_0xe5ae18];return _0x13de64;},_0xe5ae(_0xfed99,_0xc31b63);}const True=!![],False=![]; \ No newline at end of file diff --git a/modules_forge/forge_canvas/canvas.py b/modules_forge/forge_canvas/canvas.py new file mode 100644 index 000000000..ce29759c8 --- /dev/null +++ b/modules_forge/forge_canvas/canvas.py @@ -0,0 +1,120 @@ +# Forge Canvas +# AGPL V3 +# by lllyasviel +# Commercial Use is not allowed. (Contact us for commercial use.) + + +import os +import uuid +import base64 +import gradio as gr +import numpy as np + +from PIL import Image +from io import BytesIO +from gradio.context import Context +from functools import wraps +from modules.ui_components import FormComponent + + +canvas_js_root_path = os.path.dirname(__file__) + + +def web_js(file_name): + full_path = os.path.join(canvas_js_root_path, file_name) + return f'\n' + + +def web_css(file_name): + full_path = os.path.join(canvas_js_root_path, file_name) + return f'\n' + + +DEBUG_MODE = False + +canvas_html = open(os.path.join(canvas_js_root_path, 'canvas.html'), encoding='utf-8').read() +canvas_head = '' +canvas_head += web_css('canvas.css') +canvas_head += web_js('canvas.min.js') + + +def image_to_base64(image_array, numpy=True): + image = Image.fromarray(image_array) if numpy else image_array + image = image.convert("RGBA") + buffered = BytesIO() + image.save(buffered, format="PNG") + image_base64 = base64.b64encode(buffered.getvalue()).decode('utf-8') + return f"data:image/png;base64,{image_base64}" + + +def base64_to_image(base64_str, numpy=True): + if base64_str.startswith("data:image/png;base64,"): + base64_str = base64_str.replace("data:image/png;base64,", "") + image_data = base64.b64decode(base64_str) + image = Image.open(BytesIO(image_data)) + image = image.convert("RGBA") + image_array = np.array(image) if numpy else image + return image_array + + +class LogicalImage(gr.Textbox, FormComponent): + @wraps(gr.Textbox.__init__) + def __init__(self, *args, numpy=True, **kwargs): + self.numpy = numpy + + if 'value' in kwargs: + initial_value = kwargs['value'] + if initial_value is not None: + kwargs['value'] = self.image_to_base64(initial_value) + else: + del kwargs['value'] + + super().__init__(*args, **kwargs) + + def preprocess(self, payload): + if not isinstance(payload, str): + return None + + if not payload.startswith("data:image/png;base64,"): + return None + + return base64_to_image(payload, numpy=self.numpy) + + def postprocess(self, value): + if value is None: + return None + + return image_to_base64(value, numpy=self.numpy) + + def get_block_name(self): + return "textbox" + + +class ForgeCanvas: + def __init__( + self, + no_upload=False, + no_scribbles=False, + contrast_scribbles=False, + height=512, + scribble_color='#000000', + scribble_color_fixed=False, + scribble_width=4, + scribble_width_fixed=False, + scribble_alpha=100, + scribble_alpha_fixed=False, + scribble_softness=0, + scribble_softness_fixed=False, + visible=True, + numpy=False, + initial_image=None, + elem_id=None, + elem_classes=None + ): + self.uuid = 'uuid_' + uuid.uuid4().hex + self.block = gr.HTML(canvas_html.replace('forge_mixin', self.uuid), visible=visible, elem_id=elem_id, elem_classes=elem_classes) + self.foreground = LogicalImage(visible=DEBUG_MODE, label='foreground', numpy=numpy, elem_id=self.uuid, elem_classes=['logical_image_foreground']) + self.background = LogicalImage(visible=DEBUG_MODE, label='background', numpy=numpy, value=initial_image, elem_id=self.uuid, elem_classes=['logical_image_background']) + Context.root_block.load(None, js=f'async ()=>{{new ForgeCanvas("{self.uuid}", {no_upload}, {no_scribbles}, {contrast_scribbles}, {height}, ' + f"'{scribble_color}', {scribble_color_fixed}, {scribble_width}, {scribble_width_fixed}, " + f'{scribble_alpha}, {scribble_alpha_fixed}, {scribble_softness}, {scribble_softness_fixed});}}') diff --git a/modules_forge/forge_loader.py b/modules_forge/forge_loader.py index d3ea94a89..ba206db2b 100644 --- a/modules_forge/forge_loader.py +++ b/modules_forge/forge_loader.py @@ -223,7 +223,10 @@ def load_model_for_a1111(timer, checkpoint_info=None, state_dict=None): if getattr(sd_model, 'parameterization', None) == 'v': sd_model.forge_objects.unet.model.model_sampling = model_sampling(sd_model.forge_objects.unet.model.model_config, ModelType.V_PREDICTION) + sd_model.is_sd3 = False + sd_model.latent_channels = 4 sd_model.is_sdxl = conditioner is not None + sd_model.is_sdxl_inpaint = sd_model.is_sdxl and forge_objects.unet.model.diffusion_model.in_channels == 9 sd_model.is_sd2 = not sd_model.is_sdxl and hasattr(sd_model.cond_stage_model, 'model') sd_model.is_sd1 = not sd_model.is_sdxl and not sd_model.is_sd2 sd_model.is_ssd = sd_model.is_sdxl and 'model.diffusion_model.middle_block.1.transformer_blocks.0.attn1.to_q.weight' not in sd_model.state_dict().keys() diff --git a/modules_forge/forge_version.py b/modules_forge/forge_version.py index c630142e6..eda0bdbe1 100644 --- a/modules_forge/forge_version.py +++ b/modules_forge/forge_version.py @@ -1 +1 @@ -version = '0.0.17v1.8.0rc' +version = '1.0.0v1.10.0rc' diff --git a/pyproject.toml b/pyproject.toml index ce6594171..214aa794d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,6 +2,8 @@ target-version = "py39" +[tool.ruff.lint] + extend-select = [ "B", "C", @@ -29,10 +31,10 @@ ignore = [ "W605", # invalid escape sequence, messes with some docstrings ] -[tool.ruff.per-file-ignores] +[tool.ruff.lint.per-file-ignores] "webui.py" = ["E402"] # Module level import not at top of file -[tool.ruff.flake8-bugbear] +[tool.ruff.lint.flake8-bugbear] # Allow default arguments like, e.g., `data: List[str] = fastapi.Query(None)`. extend-immutable-calls = ["fastapi.Depends", "fastapi.security.HTTPBasic"] diff --git a/requirements.txt b/requirements.txt index 731a1be7d..98d9430af 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,10 +4,11 @@ accelerate blendmodes clean-fid +diskcache einops facexlib fastapi>=0.90.1 -gradio==3.41.2 +gradio inflection jsonmerge kornia @@ -17,6 +18,7 @@ omegaconf open-clip-torch piexif +protobuf==3.20.0 psutil pytorch_lightning requests @@ -29,3 +31,4 @@ torch torchdiffeq torchsde transformers==4.30.2 +pillow-avif-plugin==1.4.3 \ No newline at end of file diff --git a/requirements_versions.txt b/requirements_versions.txt index 432e07b31..6f6530ae1 100644 --- a/requirements_versions.txt +++ b/requirements_versions.txt @@ -1,12 +1,14 @@ +setuptools==69.5.1 # temp fix for compatibility with some old packages GitPython==3.1.32 Pillow==9.5.0 accelerate==0.21.0 blendmodes==2022 clean-fid==0.1.35 +diskcache==5.6.3 einops==0.4.1 facexlib==0.3.0 -fastapi==0.94.0 -gradio==3.41.2 +fastapi==0.104.1 +gradio==4.39.0 httpcore==0.15 inflection==0.5.1 jsonmerge==1.8.0 @@ -16,18 +18,21 @@ numpy==1.26.2 omegaconf==2.2.3 open-clip-torch==2.20.0 piexif==1.1.3 +protobuf==3.20.0 psutil==5.9.5 pytorch_lightning==1.9.4 resize-right==0.0.2 safetensors==0.4.2 scikit-image==0.21.0 -spandrel==0.1.6 +spandrel==0.3.4 +spandrel-extra-arches==0.1.1 tomesd==0.1.3 torch torchdiffeq==0.2.3 torchsde==0.2.6 transformers==4.30.2 httpx==0.24.1 +pillow-avif-plugin==1.4.3 basicsr==1.4.2 -diffusers==0.25.0 -pydantic==1.10.15 +diffusers==0.28.0 +gradio_rangeslider==0.0.6 diff --git a/script.js b/script.js index f069b1ef0..de1a9000d 100644 --- a/script.js +++ b/script.js @@ -29,6 +29,7 @@ var uiAfterUpdateCallbacks = []; var uiLoadedCallbacks = []; var uiTabChangeCallbacks = []; var optionsChangedCallbacks = []; +var optionsAvailableCallbacks = []; var uiAfterUpdateTimeout = null; var uiCurrentTab = null; @@ -77,6 +78,20 @@ function onOptionsChanged(callback) { optionsChangedCallbacks.push(callback); } +/** + * Register callback to be called when the options (in opts global variable) are available. + * The callback receives no arguments. + * If you register the callback after the options are available, it's just immediately called. + */ +function onOptionsAvailable(callback) { + if (Object.keys(opts).length != 0) { + callback(); + return; + } + + optionsAvailableCallbacks.push(callback); +} + function executeCallbacks(queue, arg) { for (const callback of queue) { try { diff --git a/scripts/outpainting_mk_2.py b/scripts/outpainting_mk_2.py index c98ab4809..5df9dff9c 100644 --- a/scripts/outpainting_mk_2.py +++ b/scripts/outpainting_mk_2.py @@ -102,7 +102,7 @@ def _get_masked_window_rgb(np_mask_grey, hardness=1.): shaped_noise_fft = _fft2(noise_rgb) shaped_noise_fft[:, :, :] = np.absolute(shaped_noise_fft[:, :, :]) ** 2 * (src_dist ** noise_q) * src_phase # perform the actual shaping - brightness_variation = 0. # color_variation # todo: temporarily tieing brightness variation to color variation for now + brightness_variation = 0. # color_variation # todo: temporarily tying brightness variation to color variation for now contrast_adjusted_np_src = _np_src_image[:] * (brightness_variation + 1.) - brightness_variation * 2. # scikit-image is used for histogram matching, very convenient! diff --git a/scripts/postprocessing_codeformer.py b/scripts/postprocessing_codeformer.py index e1e156ddc..53a0cc44c 100644 --- a/scripts/postprocessing_codeformer.py +++ b/scripts/postprocessing_codeformer.py @@ -25,7 +25,7 @@ def process(self, pp: scripts_postprocessing.PostprocessedImage, enable, codefor if codeformer_visibility == 0 or not enable: return - restored_img = codeformer_model.codeformer.restore(np.array(pp.image, dtype=np.uint8), w=codeformer_weight) + restored_img = codeformer_model.codeformer.restore(np.array(pp.image.convert("RGB"), dtype=np.uint8), w=codeformer_weight) res = Image.fromarray(restored_img) if codeformer_visibility < 1.0: diff --git a/scripts/postprocessing_gfpgan.py b/scripts/postprocessing_gfpgan.py index 6e7566055..57e362399 100644 --- a/scripts/postprocessing_gfpgan.py +++ b/scripts/postprocessing_gfpgan.py @@ -22,7 +22,7 @@ def process(self, pp: scripts_postprocessing.PostprocessedImage, enable, gfpgan_ if gfpgan_visibility == 0 or not enable: return - restored_img = gfpgan_model.gfpgan_fix_faces(np.array(pp.image, dtype=np.uint8)) + restored_img = gfpgan_model.gfpgan_fix_faces(np.array(pp.image.convert("RGB"), dtype=np.uint8)) res = Image.fromarray(restored_img) if gfpgan_visibility < 1.0: diff --git a/scripts/postprocessing_upscale.py b/scripts/postprocessing_upscale.py index e269682d0..2409fd207 100644 --- a/scripts/postprocessing_upscale.py +++ b/scripts/postprocessing_upscale.py @@ -1,15 +1,28 @@ +import re + from PIL import Image import numpy as np from modules import scripts_postprocessing, shared import gradio as gr -from modules.ui_components import FormRow, ToolButton +from modules.ui_components import FormRow, ToolButton, InputAccordion from modules.ui import switch_values_symbol upscale_cache = {} +def limit_size_by_one_dimention(w, h, limit): + if h > w and h > limit: + w = limit * w // h + h = limit + elif w > limit: + h = limit * h // w + w = limit + + return int(w), int(h) + + class ScriptPostprocessingUpscale(scripts_postprocessing.ScriptPostprocessing): name = "Upscale" order = 1000 @@ -17,11 +30,22 @@ class ScriptPostprocessingUpscale(scripts_postprocessing.ScriptPostprocessing): def ui(self): selected_tab = gr.Number(value=0, visible=False) - with gr.Column(): + with InputAccordion(True, label="Upscale", elem_id="extras_upscale") as upscale_enabled: + with FormRow(): + extras_upscaler_1 = gr.Dropdown(label='Upscaler 1', elem_id="extras_upscaler_1", choices=[x.name for x in shared.sd_upscalers], value=shared.sd_upscalers[0].name) + + with FormRow(): + extras_upscaler_2 = gr.Dropdown(label='Upscaler 2', elem_id="extras_upscaler_2", choices=[x.name for x in shared.sd_upscalers], value=shared.sd_upscalers[0].name) + extras_upscaler_2_visibility = gr.Slider(minimum=0.0, maximum=1.0, step=0.001, label="Upscaler 2 visibility", value=0.0, elem_id="extras_upscaler_2_visibility") + with FormRow(): with gr.Tabs(elem_id="extras_resize_mode"): with gr.TabItem('Scale by', elem_id="extras_scale_by_tab") as tab_scale_by: - upscaling_resize = gr.Slider(minimum=1.0, maximum=8.0, step=0.05, label="Resize", value=4, elem_id="extras_upscaling_resize") + with gr.Row(): + with gr.Column(scale=4): + upscaling_resize = gr.Slider(minimum=1.0, maximum=8.0, step=0.05, label="Resize", value=4, elem_id="extras_upscaling_resize") + with gr.Column(scale=1, min_width=160): + max_side_length = gr.Number(label="Max side length", value=0, elem_id="extras_upscale_max_side_length", tooltip="If any of two sides of the image ends up larger than specified, will downscale it to fit. 0 = no limit.", min_width=160, step=8, minimum=0) with gr.TabItem('Scale to', elem_id="extras_scale_to_tab") as tab_scale_to: with FormRow(): @@ -32,20 +56,27 @@ def ui(self): upscaling_res_switch_btn = ToolButton(value=switch_values_symbol, elem_id="upscaling_res_switch_btn", tooltip="Switch width/height") upscaling_crop = gr.Checkbox(label='Crop to fit', value=True, elem_id="extras_upscaling_crop") - with FormRow(): - extras_upscaler_1 = gr.Dropdown(label='Upscaler 1', elem_id="extras_upscaler_1", choices=[x.name for x in shared.sd_upscalers], value=shared.sd_upscalers[0].name) + def on_selected_upscale_method(upscale_method): + if not shared.opts.set_scale_by_when_changing_upscaler: + return gr.update() - with FormRow(): - extras_upscaler_2 = gr.Dropdown(label='Upscaler 2', elem_id="extras_upscaler_2", choices=[x.name for x in shared.sd_upscalers], value=shared.sd_upscalers[0].name) - extras_upscaler_2_visibility = gr.Slider(minimum=0.0, maximum=1.0, step=0.001, label="Upscaler 2 visibility", value=0.0, elem_id="extras_upscaler_2_visibility") + match = re.search(r'(\d)[xX]|[xX](\d)', upscale_method) + if not match: + return gr.update() + + return gr.update(value=int(match.group(1) or match.group(2))) upscaling_res_switch_btn.click(lambda w, h: (h, w), inputs=[upscaling_resize_w, upscaling_resize_h], outputs=[upscaling_resize_w, upscaling_resize_h], show_progress=False) tab_scale_by.select(fn=lambda: 0, inputs=[], outputs=[selected_tab]) tab_scale_to.select(fn=lambda: 1, inputs=[], outputs=[selected_tab]) + extras_upscaler_1.change(on_selected_upscale_method, inputs=[extras_upscaler_1], outputs=[upscaling_resize], show_progress="hidden") + return { + "upscale_enabled": upscale_enabled, "upscale_mode": selected_tab, "upscale_by": upscaling_resize, + "max_side_length": max_side_length, "upscale_to_width": upscaling_resize_w, "upscale_to_height": upscaling_resize_h, "upscale_crop": upscaling_crop, @@ -54,12 +85,18 @@ def ui(self): "upscaler_2_visibility": extras_upscaler_2_visibility, } - def upscale(self, image, info, upscaler, upscale_mode, upscale_by, upscale_to_width, upscale_to_height, upscale_crop): + def upscale(self, image, info, upscaler, upscale_mode, upscale_by, max_side_length, upscale_to_width, upscale_to_height, upscale_crop): if upscale_mode == 1: upscale_by = max(upscale_to_width/image.width, upscale_to_height/image.height) info["Postprocess upscale to"] = f"{upscale_to_width}x{upscale_to_height}" else: info["Postprocess upscale by"] = upscale_by + if max_side_length != 0 and max(*image.size)*upscale_by > max_side_length: + upscale_mode = 1 + upscale_crop = False + upscale_to_width, upscale_to_height = limit_size_by_one_dimention(image.width*upscale_by, image.height*upscale_by, max_side_length) + upscale_by = max(upscale_to_width/image.width, upscale_to_height/image.height) + info["Max side length"] = max_side_length cache_key = (hash(np.array(image.getdata()).tobytes()), upscaler.name, upscale_mode, upscale_by, upscale_to_width, upscale_to_height, upscale_crop) cached_image = upscale_cache.pop(cache_key, None) @@ -81,7 +118,7 @@ def upscale(self, image, info, upscaler, upscale_mode, upscale_by, upscale_to_w return image - def process_firstpass(self, pp: scripts_postprocessing.PostprocessedImage, upscale_mode=1, upscale_by=2.0, upscale_to_width=None, upscale_to_height=None, upscale_crop=False, upscaler_1_name=None, upscaler_2_name=None, upscaler_2_visibility=0.0): + def process_firstpass(self, pp: scripts_postprocessing.PostprocessedImage, upscale_enabled=True, upscale_mode=1, upscale_by=2.0, max_side_length=0, upscale_to_width=None, upscale_to_height=None, upscale_crop=False, upscaler_1_name=None, upscaler_2_name=None, upscaler_2_visibility=0.0): if upscale_mode == 1: pp.shared.target_width = upscale_to_width pp.shared.target_height = upscale_to_height @@ -89,7 +126,13 @@ def process_firstpass(self, pp: scripts_postprocessing.PostprocessedImage, upsca pp.shared.target_width = int(pp.image.width * upscale_by) pp.shared.target_height = int(pp.image.height * upscale_by) - def process(self, pp: scripts_postprocessing.PostprocessedImage, upscale_mode=1, upscale_by=2.0, upscale_to_width=None, upscale_to_height=None, upscale_crop=False, upscaler_1_name=None, upscaler_2_name=None, upscaler_2_visibility=0.0): + pp.shared.target_width, pp.shared.target_height = limit_size_by_one_dimention(pp.shared.target_width, pp.shared.target_height, max_side_length) + + def process(self, pp: scripts_postprocessing.PostprocessedImage, upscale_enabled=True, upscale_mode=1, upscale_by=2.0, max_side_length=0, upscale_to_width=None, upscale_to_height=None, upscale_crop=False, upscaler_1_name=None, upscaler_2_name=None, upscaler_2_visibility=0.0): + if not upscale_enabled: + return + + upscaler_1_name = upscaler_1_name if upscaler_1_name == "None": upscaler_1_name = None @@ -99,17 +142,20 @@ def process(self, pp: scripts_postprocessing.PostprocessedImage, upscale_mode=1, if not upscaler1: return + upscaler_2_name = upscaler_2_name if upscaler_2_name == "None": upscaler_2_name = None upscaler2 = next(iter([x for x in shared.sd_upscalers if x.name == upscaler_2_name and x.name != "None"]), None) assert upscaler2 or (upscaler_2_name is None), f'could not find upscaler named {upscaler_2_name}' - upscaled_image = self.upscale(pp.image, pp.info, upscaler1, upscale_mode, upscale_by, upscale_to_width, upscale_to_height, upscale_crop) + upscaled_image = self.upscale(pp.image, pp.info, upscaler1, upscale_mode, upscale_by, max_side_length, upscale_to_width, upscale_to_height, upscale_crop) pp.info["Postprocess upscaler"] = upscaler1.name if upscaler2 and upscaler_2_visibility > 0: - second_upscale = self.upscale(pp.image, pp.info, upscaler2, upscale_mode, upscale_by, upscale_to_width, upscale_to_height, upscale_crop) + second_upscale = self.upscale(pp.image, pp.info, upscaler2, upscale_mode, upscale_by, max_side_length, upscale_to_width, upscale_to_height, upscale_crop) + if upscaled_image.mode != second_upscale.mode: + second_upscale = second_upscale.convert(upscaled_image.mode) upscaled_image = Image.blend(upscaled_image, second_upscale, upscaler_2_visibility) pp.info["Postprocess upscaler 2"] = upscaler2.name @@ -145,5 +191,5 @@ def process(self, pp: scripts_postprocessing.PostprocessedImage, upscale_by=2.0, upscaler1 = next(iter([x for x in shared.sd_upscalers if x.name == upscaler_name]), None) assert upscaler1, f'could not find upscaler named {upscaler_name}' - pp.image = self.upscale(pp.image, pp.info, upscaler1, 0, upscale_by, 0, 0, False) + pp.image = self.upscale(pp.image, pp.info, upscaler1, 0, upscale_by, 0, 0, 0, False) pp.info["Postprocess upscaler"] = upscaler1.name diff --git a/scripts/xyz_grid.py b/scripts/xyz_grid.py index 6d3e42c06..6a42a04d9 100644 --- a/scripts/xyz_grid.py +++ b/scripts/xyz_grid.py @@ -11,7 +11,7 @@ import modules.scripts as scripts import gradio as gr -from modules import images, sd_samplers, processing, sd_models, sd_vae, sd_samplers_kdiffusion, errors +from modules import images, sd_samplers, processing, sd_models, sd_vae, sd_schedulers, errors from modules.processing import process_images, Processed, StableDiffusionProcessingTxt2Img from modules.shared import opts, state import modules.shared as shared @@ -45,7 +45,7 @@ def apply_prompt(p, x, xs): def apply_order(p, x, xs): token_order = [] - # Initally grab the tokens from the prompt, so they can be replaced in order of earliest seen + # Initially grab the tokens from the prompt, so they can be replaced in order of earliest seen for token in x: token_order.append((p.prompt.find(token), token)) @@ -95,33 +95,38 @@ def confirm_checkpoints_or_none(p, xs): raise RuntimeError(f"Unknown checkpoint: {x}") -def apply_clip_skip(p, x, xs): - opts.data["CLIP_stop_at_last_layers"] = x +def confirm_range(min_val, max_val, axis_label): + """Generates a AxisOption.confirm() function that checks all values are within the specified range.""" + def confirm_range_fun(p, xs): + for x in xs: + if not (max_val >= x >= min_val): + raise ValueError(f'{axis_label} value "{x}" out of range [{min_val}, {max_val}]') + + return confirm_range_fun -def apply_upscale_latent_space(p, x, xs): - if x.lower().strip() != '0': - opts.data["use_scale_latent_for_hires_fix"] = True - else: - opts.data["use_scale_latent_for_hires_fix"] = False + +def apply_size(p, x: str, xs) -> None: + try: + width, _, height = x.partition('x') + width = int(width.strip()) + height = int(height.strip()) + p.width = width + p.height = height + except ValueError: + print(f"Invalid size in XYZ plot: {x}") def find_vae(name: str): - if name.lower() in ['auto', 'automatic']: - return modules.sd_vae.unspecified - if name.lower() == 'none': - return None - else: - choices = [x for x in sorted(modules.sd_vae.vae_dict, key=lambda x: len(x)) if name.lower().strip() in x.lower()] - if len(choices) == 0: - print(f"No VAE found for {name}; using automatic") - return modules.sd_vae.unspecified - else: - return modules.sd_vae.vae_dict[choices[0]] + if (name := name.strip().lower()) in ('auto', 'automatic'): + return 'Automatic' + elif name == 'none': + return 'None' + return next((k for k in modules.sd_vae.vae_dict if k.lower() == name), print(f'No VAE found for {name}; using Automatic') or 'Automatic') def apply_vae(p, x, xs): - modules.sd_vae.reload_vae_weights(shared.sd_model, vae_file=find_vae(x)) + p.override_settings['sd_vae'] = find_vae(x) def apply_styles(p: StableDiffusionProcessingTxt2Img, x: str, _): @@ -129,7 +134,7 @@ def apply_styles(p: StableDiffusionProcessingTxt2Img, x: str, _): def apply_uni_pc_order(p, x, xs): - opts.data["uni_pc_order"] = min(x, p.steps - 1) + p.override_settings['uni_pc_order'] = min(x, p.steps - 1) def apply_face_restore(p, opt, x): @@ -151,12 +156,14 @@ def fun(p, x, xs): if boolean: x = True if x.lower() == "true" else False p.override_settings[field] = x + return fun def boolean_choice(reverse: bool = False): def choice(): return ["False", "True"] if reverse else ["True", "False"] + return choice @@ -201,7 +208,7 @@ def list_to_csv_string(data_list): def csv_string_to_list_strip(data_str): - return list(map(str.strip, chain.from_iterable(csv.reader(StringIO(data_str))))) + return list(map(str.strip, chain.from_iterable(csv.reader(StringIO(data_str), skipinitialspace=True)))) class AxisOption: @@ -248,18 +255,20 @@ def __init__(self, *args, **kwargs): AxisOption("Sigma min", float, apply_field("s_tmin")), AxisOption("Sigma max", float, apply_field("s_tmax")), AxisOption("Sigma noise", float, apply_field("s_noise")), - AxisOption("Schedule type", str, apply_override("k_sched_type"), choices=lambda: list(sd_samplers_kdiffusion.k_diffusion_scheduler)), + AxisOption("Schedule type", str, apply_field("scheduler"), choices=lambda: [x.label for x in sd_schedulers.schedulers]), AxisOption("Schedule min sigma", float, apply_override("sigma_min")), AxisOption("Schedule max sigma", float, apply_override("sigma_max")), AxisOption("Schedule rho", float, apply_override("rho")), + AxisOption("Beta schedule alpha", float, apply_override("beta_dist_alpha")), + AxisOption("Beta schedule beta", float, apply_override("beta_dist_beta")), AxisOption("Eta", float, apply_field("eta")), - AxisOption("Clip skip", int, apply_clip_skip), + AxisOption("Clip skip", int, apply_override('CLIP_stop_at_last_layers')), AxisOption("Denoising", float, apply_field("denoising_strength")), AxisOption("Initial noise multiplier", float, apply_field("initial_noise_multiplier")), AxisOption("Extra noise", float, apply_override("img2img_extra_noise")), AxisOptionTxt2Img("Hires upscaler", str, apply_field("hr_upscaler"), choices=lambda: [*shared.latent_upscale_modes, *[x.name for x in shared.sd_upscalers]]), AxisOptionImg2Img("Cond. Image Mask Weight", float, apply_field("inpainting_mask_weight")), - AxisOption("VAE", str, apply_vae, cost=0.7, choices=lambda: ['None'] + list(sd_vae.vae_dict)), + AxisOption("VAE", str, apply_vae, cost=0.7, choices=lambda: ['Automatic', 'None'] + list(sd_vae.vae_dict)), AxisOption("Styles", str, apply_styles, choices=lambda: list(shared.prompt_styles.styles)), AxisOption("UniPC Order", int, apply_uni_pc_order, cost=0.5), AxisOption("Face restore", str, apply_face_restore, format_value=format_value), @@ -271,6 +280,7 @@ def __init__(self, *args, **kwargs): AxisOption("Refiner switch at", float, apply_field('refiner_switch_at')), AxisOption("RNG source", str, apply_override("randn_source"), choices=lambda: ["GPU", "CPU", "NV"]), AxisOption("FP8 mode", str, apply_override("fp8_storage"), cost=0.9, choices=lambda: ["Disable", "Enable for SDXL", "Enable"]), + AxisOption("Size", str, apply_size), ] @@ -366,16 +376,17 @@ def index(ix, iy, iz): end_index = start_index + len(xs) * len(ys) grid = images.image_grid(processed_result.images[start_index:end_index], rows=len(ys)) if draw_legend: - grid = images.draw_grid_annotations(grid, processed_result.images[start_index].size[0], processed_result.images[start_index].size[1], hor_texts, ver_texts, margin_size) + grid_max_w, grid_max_h = map(max, zip(*(img.size for img in processed_result.images[start_index:end_index]))) + grid = images.draw_grid_annotations(grid, grid_max_w, grid_max_h, hor_texts, ver_texts, margin_size) processed_result.images.insert(i, grid) processed_result.all_prompts.insert(i, processed_result.all_prompts[start_index]) processed_result.all_seeds.insert(i, processed_result.all_seeds[start_index]) processed_result.infotexts.insert(i, processed_result.infotexts[start_index]) - sub_grid_size = processed_result.images[0].size z_grid = images.image_grid(processed_result.images[:z_count], rows=1) + z_sub_grid_max_w, z_sub_grid_max_h = map(max, zip(*(img.size for img in processed_result.images[:z_count]))) if draw_legend: - z_grid = images.draw_grid_annotations(z_grid, sub_grid_size[0], sub_grid_size[1], title_texts, [[images.GridAnnotation()]]) + z_grid = images.draw_grid_annotations(z_grid, z_sub_grid_max_w, z_sub_grid_max_h, title_texts, [[images.GridAnnotation()]]) processed_result.images.insert(0, z_grid) # TODO: Deeper aspects of the program rely on grid info being misaligned between metadata arrays, which is not ideal. # processed_result.all_prompts.insert(0, processed_result.all_prompts[0]) @@ -387,18 +398,12 @@ def index(ix, iy, iz): class SharedSettingsStackHelper(object): def __enter__(self): - self.CLIP_stop_at_last_layers = opts.CLIP_stop_at_last_layers - self.vae = opts.sd_vae - self.uni_pc_order = opts.uni_pc_order + pass def __exit__(self, exc_type, exc_value, tb): - opts.data["sd_vae"] = self.vae - opts.data["uni_pc_order"] = self.uni_pc_order modules.sd_models.reload_model_weights() modules.sd_vae.reload_vae_weights() - opts.data["CLIP_stop_at_last_layers"] = self.CLIP_stop_at_last_layers - re_range = re.compile(r"\s*([+-]?\s*\d+)\s*-\s*([+-]?\s*\d+)(?:\s*\(([+-]\d+)\s*\))?\s*") re_range_float = re.compile(r"\s*([+-]?\s*\d+(?:.\d*)?)\s*-\s*([+-]?\s*\d+(?:.\d*)?)(?:\s*\(([+-]\d+(?:.\d*)?)\s*\))?\s*") @@ -560,7 +565,7 @@ def process_axis(opt, vals, vals_dropdown): mc = re_range_count.fullmatch(val) if m is not None: start = int(m.group(1)) - end = int(m.group(2))+1 + end = int(m.group(2)) + 1 step = int(m.group(3)) if m.group(3) is not None else 1 valslist_ext += list(range(start, end, step)) @@ -713,11 +718,11 @@ def cell(x, y, z, ix, iy, iz): ydim = len(ys) if vary_seeds_y else 1 if vary_seeds_x: - pc.seed += ix + pc.seed += ix if vary_seeds_y: - pc.seed += iy * xdim + pc.seed += iy * xdim if vary_seeds_z: - pc.seed += iz * xdim * ydim + pc.seed += iz * xdim * ydim try: res = process_images(pc) @@ -785,18 +790,18 @@ def cell(x, y, z, ix, iy, iz): z_count = len(zs) # Set the grid infotexts to the real ones with extra_generation_params (1 main grid + z_count sub-grids) - processed.infotexts[:1+z_count] = grid_infotext[:1+z_count] + processed.infotexts[:1 + z_count] = grid_infotext[:1 + z_count] if not include_lone_images: # Don't need sub-images anymore, drop from list: - processed.images = processed.images[:z_count+1] + processed.images = processed.images[:z_count + 1] if opts.grid_save: # Auto-save main and sub-grids: grid_count = z_count + 1 if z_count > 1 else 1 for g in range(grid_count): # TODO: See previous comment about intentional data misalignment. - adj_g = g-1 if g > 0 else g + adj_g = g - 1 if g > 0 else g images.save_image(processed.images[g], p.outpath_grids, "xyz_grid", info=processed.infotexts[g], extension=opts.grid_format, prompt=processed.all_prompts[adj_g], seed=processed.all_seeds[adj_g], grid=True, p=processed) if not include_sub_grids: # if not include_sub_grids then skip saving after the first grid break diff --git a/style.css b/style.css index 8ce78ff0c..cd1095526 100644 --- a/style.css +++ b/style.css @@ -2,14 +2,6 @@ @import url('webui-assets/css/sourcesanspro.css'); - -/* temporary fix to hide gradio crop tool until it's fixed https://github.com/gradio-app/gradio/issues/3810 */ - -div.gradio-image button[aria-label="Edit"] { - display: none; -} - - /* general gradio fixes */ :root, .dark{ @@ -137,6 +129,10 @@ div.gradio-html.min{ background: var(--input-background-fill); } +.gradio-gallery > button.preview{ + width: 100%; +} + .gradio-container .prose a, .gradio-container .prose a:visited{ color: unset; text-decoration: none; @@ -147,6 +143,15 @@ a{ cursor: pointer; } +.upload-container { + width: 100%; + max-width: 100%; +} + +.layer-wrap > ul { + background: var(--background-fill-primary) !important; +} + /* gradio 3.39 puts a lot of overflow: hidden all over the place for an unknown reason. */ div.gradio-container, .block.gradio-textbox, div.gradio-group, div.gradio-dropdown{ overflow: visible !important; @@ -279,7 +284,7 @@ input[type="checkbox"].input-accordion-checkbox{ display: inline-block; } -.html-log .performance p.time, .performance p.vram, .performance p.time abbr, .performance p.vram abbr { +.html-log .performance p.time, .performance p.vram, .performance p.profile, .performance p.time abbr, .performance p.vram abbr { margin-bottom: 0; color: var(--block-title-text-color); } @@ -291,6 +296,10 @@ input[type="checkbox"].input-accordion-checkbox{ margin-left: auto; } +.html-log .performance p.profile { + margin-left: 0.5em; +} + .html-log .performance .measurement{ color: var(--body-text-color); font-weight: bold; @@ -387,14 +396,7 @@ div#extras_scale_to_tab div.form{ flex-direction: row; } -#img2img_sketch, #img2maskimg, #inpaint_sketch { - overflow: overlay !important; - resize: auto; - background: var(--panel-background-fill); - z-index: 5; -} - -.image-buttons > .form{ +.image-buttons{ justify-content: center; } @@ -528,6 +530,10 @@ table.popup-table .link{ opacity: 0.75; } +.settings-comment .info ol{ + margin: 0.4em 0 0.8em 1em; +} + #sysinfo_download a.sysinfo_big_link{ font-size: 24pt; } @@ -776,9 +782,9 @@ table.popup-table .link{ position:absolute; display:block; padding:0px 0; - border:2px solid #a55000; + border:2px solid var(--primary-800); border-radius:8px; - box-shadow:1px 1px 2px #CE6400; + box-shadow:1px 1px 2px var(--primary-500); width: 200px; } @@ -795,7 +801,7 @@ table.popup-table .link{ } .context-menu-items a:hover{ - background: #a55000; + background: var(--primary-700); } @@ -803,6 +809,8 @@ table.popup-table .link{ #tab_extensions table{ border-collapse: collapse; + overflow-x: auto; + display: block; } #tab_extensions table td, #tab_extensions table th{ @@ -850,6 +858,10 @@ table.popup-table .link{ display: inline-block; } +.compact-checkbox-group div label { + padding: 0.1em 0.3em !important; +} + /* extensions tab table row hover highlight */ #extensions tr:hover td, @@ -1084,9 +1096,9 @@ footer { height:100%; } -div.block.gradio-box.edit-user-metadata { +.edit-user-metadata { width: 56em; - background: var(--body-background-fill); + background: var(--body-background-fill) !important; padding: 2em !important; } @@ -1120,16 +1132,12 @@ div.block.gradio-box.edit-user-metadata { margin-top: 1.5em; } -div.block.gradio-box.popup-dialog, .popup-dialog { +.popup-dialog { width: 56em; - background: var(--body-background-fill); + background: var(--body-background-fill) !important; padding: 2em !important; } -div.block.gradio-box.popup-dialog > div:last-child, .popup-dialog > div:last-child{ - margin-top: 1em; -} - div.block.input-accordion{ } @@ -1205,12 +1213,24 @@ body.resizing .resize-handle { overflow: hidden; } -.extra-network-pane .extra-network-pane-content { +.extra-network-pane .extra-network-pane-content-dirs { + display: flex; + flex: 1; + flex-direction: column; + overflow: hidden; +} + +.extra-network-pane .extra-network-pane-content-tree { display: flex; flex: 1; overflow: hidden; } +.extra-network-dirs-hidden .extra-network-dirs{ display: none; } +.extra-network-dirs-hidden .extra-network-tree{ display: none; } +.extra-network-dirs-hidden .resize-handle { display: none; } +.extra-network-dirs-hidden .resize-handle-row { display: flex !important; } + .extra-network-pane .extra-network-tree { flex: 1; font-size: 1rem; @@ -1260,7 +1280,7 @@ body.resizing .resize-handle { .extra-network-control { position: relative; - display: grid; + display: flex; width: 100%; padding: 0 !important; margin-top: 0 !important; @@ -1277,6 +1297,12 @@ body.resizing .resize-handle { align-items: start; } +.extra-network-control small{ + color: var(--input-placeholder-color); + line-height: 2.2rem; + margin: 0 0.5rem 0 0.75rem; +} + .extra-network-tree .tree-list--tree {} /* Remove auto indentation from tree. Will be overridden later. */ @@ -1424,6 +1450,12 @@ body.resizing .resize-handle { line-height: 1rem; } + +.extra-network-control .extra-network-control--search .extra-network-control--search-text::placeholder { + color: var(--input-placeholder-color); +} + + /* clear button (x on right side) styling */ .extra-network-control .extra-network-control--search .extra-network-control--search-text::-webkit-search-cancel-button { -webkit-appearance: none; @@ -1456,19 +1488,19 @@ body.resizing .resize-handle { background-color: var(--input-placeholder-color); } -.extra-network-control .extra-network-control--sort[data-sortmode="path"] .extra-network-control--sort-icon { +.extra-network-control .extra-network-control--sort[data-sortkey="default"] .extra-network-control--sort-icon { mask-image: url('data:image/svg+xml,'); } -.extra-network-control .extra-network-control--sort[data-sortmode="name"] .extra-network-control--sort-icon { +.extra-network-control .extra-network-control--sort[data-sortkey="name"] .extra-network-control--sort-icon { mask-image: url('data:image/svg+xml,'); } -.extra-network-control .extra-network-control--sort[data-sortmode="date_created"] .extra-network-control--sort-icon { +.extra-network-control .extra-network-control--sort[data-sortkey="date_created"] .extra-network-control--sort-icon { mask-image: url('data:image/svg+xml,'); } -.extra-network-control .extra-network-control--sort[data-sortmode="date_modified"] .extra-network-control--sort-icon { +.extra-network-control .extra-network-control--sort[data-sortkey="date_modified"] .extra-network-control--sort-icon { mask-image: url('data:image/svg+xml,'); } @@ -1518,13 +1550,18 @@ body.resizing .resize-handle { } .extra-network-control .extra-network-control--enabled { - background-color: rgba(0, 0, 0, 0.15); + background-color: rgba(0, 0, 0, 0.1); + border-radius: 0.25rem; } .dark .extra-network-control .extra-network-control--enabled { background-color: rgba(255, 255, 255, 0.15); } +.extra-network-control .extra-network-control--enabled .extra-network-control--icon{ + background-color: var(--button-secondary-text-color); +} + /* ==== REFRESH ICON ACTIONS ==== */ .extra-network-control .extra-network-control--refresh { padding: 0.25rem; @@ -1615,9 +1652,10 @@ body.resizing .resize-handle { display: inline-flex; visibility: hidden; color: var(--button-secondary-text-color); - + width: 0; } .extra-network-tree .tree-list-content:hover .button-row { visibility: visible; + width: auto; } diff --git a/webui-macos-env.sh b/webui-macos-env.sh index db7e8b1a0..00a36e177 100644 --- a/webui-macos-env.sh +++ b/webui-macos-env.sh @@ -4,14 +4,14 @@ # Please modify webui-user.sh to change these instead of this file # #################################################################### -if [[ -x "$(command -v python3.10)" ]] -then - python_cmd="python3.10" -fi - export install_dir="$HOME" export COMMANDLINE_ARGS="--skip-torch-cuda-test --upcast-sampling --no-half-vae --use-cpu interrogate" -export TORCH_COMMAND="pip install torch==2.1.0 torchvision==0.16.0" export PYTORCH_ENABLE_MPS_FALLBACK=1 +if [[ "$(sysctl -n machdep.cpu.brand_string)" =~ ^.*"Intel".*$ ]]; then + export TORCH_COMMAND="pip install torch==2.1.2 torchvision==0.16.2" +else + export TORCH_COMMAND="pip install torch==2.3.1 torchvision==0.18.1" +fi + #################################################################### diff --git a/webui.bat b/webui.bat index e2c9079d2..7b162ce27 100644 --- a/webui.bat +++ b/webui.bat @@ -37,12 +37,18 @@ if %ERRORLEVEL% == 0 goto :activate_venv for /f "delims=" %%i in ('CALL %PYTHON% -c "import sys; print(sys.executable)"') do set PYTHON_FULLNAME="%%i" echo Creating venv in directory %VENV_DIR% using python %PYTHON_FULLNAME% %PYTHON_FULLNAME% -m venv "%VENV_DIR%" >tmp/stdout.txt 2>tmp/stderr.txt -if %ERRORLEVEL% == 0 goto :activate_venv +if %ERRORLEVEL% == 0 goto :upgrade_pip echo Unable to create venv in directory "%VENV_DIR%" goto :show_stdout_stderr +:upgrade_pip +"%VENV_DIR%\Scripts\Python.exe" -m pip install --upgrade pip +if %ERRORLEVEL% == 0 goto :activate_venv +echo Warning: Failed to upgrade PIP version + :activate_venv set PYTHON="%VENV_DIR%\Scripts\Python.exe" +call "%VENV_DIR%\Scripts\activate.bat" echo venv %PYTHON% :skip_venv diff --git a/webui.py b/webui.py index 022330b0a..921edce9f 100644 --- a/webui.py +++ b/webui.py @@ -11,6 +11,8 @@ from modules_forge import main_thread +from modules_forge.forge_canvas.canvas import canvas_js_root_path + startup_timer = timer.startup_timer startup_timer.record("launcher") @@ -92,7 +94,7 @@ def webui_worker(): auth=gradio_auth_creds, inbrowser=auto_launch_browser, prevent_thread_lock=True, - allowed_paths=cmd_opts.gradio_allowed_path, + allowed_paths=cmd_opts.gradio_allowed_path + [canvas_js_root_path], app_kwargs={ "docs_url": "/docs", "redoc_url": "/redoc", diff --git a/webui.sh b/webui.sh index f116376f7..89dae163a 100755 --- a/webui.sh +++ b/webui.sh @@ -44,7 +44,11 @@ fi # python3 executable if [[ -z "${python_cmd}" ]] then - python_cmd="python3" + python_cmd="python3.10" +fi +if [[ ! -x "$(command -v "${python_cmd}")" ]] +then + python_cmd="python3" fi # git executable @@ -113,13 +117,13 @@ then exit 1 fi -if [[ -d .git ]] +if [[ -d "$SCRIPT_DIR/.git" ]] then printf "\n%s\n" "${delimiter}" printf "Repo already cloned, using it as install directory" printf "\n%s\n" "${delimiter}" - install_dir="${PWD}/../" - clone_dir="${PWD##*/}" + install_dir="${SCRIPT_DIR}/../" + clone_dir="${SCRIPT_DIR##*/}" fi # Check prerequisites @@ -129,13 +133,19 @@ case "$gpu_info" in export HSA_OVERRIDE_GFX_VERSION=10.3.0 if [[ -z "${TORCH_COMMAND}" ]] then - pyv="$(${python_cmd} -c 'import sys; print(".".join(map(str, sys.version_info[0:2])))')" - if [[ $(bc <<< "$pyv <= 3.10") -eq 1 ]] + pyv="$(${python_cmd} -c 'import sys; print(f"{sys.version_info[0]}.{sys.version_info[1]:02d}")')" + # Using an old nightly compiled against rocm 5.2 for Navi1, see https://github.com/pytorch/pytorch/issues/106728#issuecomment-1749511711 + if [[ $pyv == "3.8" ]] + then + export TORCH_COMMAND="pip install https://download.pytorch.org/whl/nightly/rocm5.2/torch-2.0.0.dev20230209%2Brocm5.2-cp38-cp38-linux_x86_64.whl https://download.pytorch.org/whl/nightly/rocm5.2/torchvision-0.15.0.dev20230209%2Brocm5.2-cp38-cp38-linux_x86_64.whl" + elif [[ $pyv == "3.9" ]] + then + export TORCH_COMMAND="pip install https://download.pytorch.org/whl/nightly/rocm5.2/torch-2.0.0.dev20230209%2Brocm5.2-cp39-cp39-linux_x86_64.whl https://download.pytorch.org/whl/nightly/rocm5.2/torchvision-0.15.0.dev20230209%2Brocm5.2-cp39-cp39-linux_x86_64.whl" + elif [[ $pyv == "3.10" ]] then - # Navi users will still use torch 1.13 because 2.0 does not seem to work. - export TORCH_COMMAND="pip install --pre torch torchvision --index-url https://download.pytorch.org/whl/nightly/rocm5.6" + export TORCH_COMMAND="pip install https://download.pytorch.org/whl/nightly/rocm5.2/torch-2.0.0.dev20230209%2Brocm5.2-cp310-cp310-linux_x86_64.whl https://download.pytorch.org/whl/nightly/rocm5.2/torchvision-0.15.0.dev20230209%2Brocm5.2-cp310-cp310-linux_x86_64.whl" else - printf "\e[1m\e[31mERROR: RX 5000 series GPUs must be using at max python 3.10, aborting...\e[0m" + printf "\e[1m\e[31mERROR: RX 5000 series GPUs python version must be between 3.8 and 3.10, aborting...\e[0m" exit 1 fi fi @@ -143,7 +153,7 @@ case "$gpu_info" in *"Navi 2"*) export HSA_OVERRIDE_GFX_VERSION=10.3.0 ;; *"Navi 3"*) [[ -z "${TORCH_COMMAND}" ]] && \ - export TORCH_COMMAND="pip install --pre torch torchvision --index-url https://download.pytorch.org/whl/nightly/rocm5.7" + export TORCH_COMMAND="pip install torch torchvision --index-url https://download.pytorch.org/whl/nightly/rocm5.7" ;; *"Renoir"*) export HSA_OVERRIDE_GFX_VERSION=9.0.0 printf "\n%s\n" "${delimiter}" @@ -157,11 +167,10 @@ if ! echo "$gpu_info" | grep -q "NVIDIA"; then if echo "$gpu_info" | grep -q "AMD" && [[ -z "${TORCH_COMMAND}" ]] then - export TORCH_COMMAND="pip install torch==2.0.1+rocm5.4.2 torchvision==0.15.2+rocm5.4.2 --index-url https://download.pytorch.org/whl/rocm5.4.2" - elif echo "$gpu_info" | grep -q "Huawei" && [[ -z "${TORCH_COMMAND}" ]] + export TORCH_COMMAND="pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/rocm5.7" + elif npu-smi info 2>/dev/null then - export TORCH_COMMAND="pip install torch==2.1.0 torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu; pip install torch_npu" - + export TORCH_COMMAND="pip install torch==2.1.0 torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu; pip install torch_npu==2.1.0" fi fi @@ -205,12 +214,15 @@ then if [[ ! -d "${venv_dir}" ]] then "${python_cmd}" -m venv "${venv_dir}" + "${venv_dir}"/bin/python -m pip install --upgrade pip first_launch=1 fi # shellcheck source=/dev/null if [[ -f "${venv_dir}"/bin/activate ]] then source "${venv_dir}"/bin/activate + # ensure use of python from venv + python_cmd="${venv_dir}"/bin/python else printf "\n%s\n" "${delimiter}" printf "\e[1m\e[31mERROR: Cannot activate python venv, aborting...\e[0m" @@ -238,7 +250,7 @@ prepare_tcmalloc() { for lib in "${TCMALLOC_LIBS[@]}" do # Determine which type of tcmalloc library the library supports - TCMALLOC="$(PATH=/usr/sbin:$PATH ldconfig -p | grep -P $lib | head -n 1)" + TCMALLOC="$(PATH=/sbin:/usr/sbin:$PATH ldconfig -p | grep -P $lib | head -n 1)" TC_INFO=(${TCMALLOC//=>/}) if [[ ! -z "${TC_INFO}" ]]; then echo "Check TCMalloc: ${TC_INFO}"