Compare commits

...

325 Commits

Author SHA1 Message Date
AUTOMATIC1111 adadb4e3c7 Merge branch 'release_candidate' 2024-04-13 06:37:28 +03:00
AUTOMATIC1111 d282d24800 update changelog 2024-04-13 06:37:03 +03:00
AUTOMATIC1111 a196319edf Merge pull request #15492 from w-e-w/update-restricted_opts
update restricted_opts
2024-04-11 19:34:10 +03:00
AUTOMATIC1111 88f70ce63c Merge pull request #15470 from AUTOMATIC1111/read-infotext-Script-not-found
error handling paste_field callables
2024-04-09 16:01:13 +03:00
AUTOMATIC1111 7f691612ca Merge pull request #15460 from AUTOMATIC1111/create_infotext-index-and-callable
create_infotext allow index and callable, re-work Hires prompt infotext
2024-04-09 12:05:15 +03:00
AUTOMATIC1111 696d6813e0 Merge pull request #15465 from jordenyt/fix-extras-api-upscale-enabled
Fix extra-single-image API not doing upscale failed
2024-04-09 11:00:49 +03:00
AUTOMATIC1111 3786f3742f fix limited file write (thanks, Sylwia) 2024-04-08 16:15:55 +03:00
AUTOMATIC1111 e1640314df 1.9.0 changelog 2024-04-06 21:46:56 +03:00
AUTOMATIC1111 c16a27caa9
Merge pull request #15446 from AUTOMATIC1111/re-add-update_file_entry
re-add update_file_entry
2024-04-06 10:02:45 +03:00
w-e-w 2ad17a6100 re-add update_file_entry
MassFileLister.update_file_entry was accidentally removed in https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15205/files#diff-c39b942d8f8620d46d314db8301189b8d6195fc97aedbeb124a33694b738d69cL151-R173
2024-04-06 15:56:57 +09:00
AUTOMATIC1111 23c06a51cc use 'scripts.' prefix for names of dynamically loaded modules 2024-04-06 09:05:04 +03:00
AUTOMATIC1111 badb70da48
Merge pull request #15423 from storyicon/master
feat: ensure the indexability of dynamically imported packages
2024-04-06 09:00:35 +03:00
AUTOMATIC1111 447198f21b
Merge pull request #15442 from AUTOMATIC1111/open_folder-as-util
open_folder as util
2024-04-06 08:54:20 +03:00
AUTOMATIC1111 acb20338b1 put HF_ENDPOINT into shared for #15443 2024-04-06 08:53:21 +03:00
AUTOMATIC1111 73f7812045
Merge pull request #15443 from Satariall/add-hf_endpoint-variable
Use HF_ENDPOINT variable for HuggingFace domain with default
2024-04-06 08:48:55 +03:00
Marsel Markhabulin 989b89b12a
Use HF_ENDPOINT variable for HuggingFace domain with default
Modified the list_models function to dynamically construct the model URL
by using an environment variable for the HuggingFace domain. This allows
for greater flexibility in specifying the domain and ensures that the
modification is also compatible with the Hub client library. By
supporting different environments or requirements without hardcoding the
domain name, this change facilitates the use of custom HuggingFace
domains not only within our code but also when interacting with the Hub
client library.
2024-04-05 13:02:49 +03:00
w-e-w 20123d427b open_folder docstring 2024-04-05 16:19:20 +09:00
w-e-w a05d89b1e5
Merge branch 'dev' into open_folder-as-util 2024-04-05 15:14:38 +08:00
w-e-w 92e6aa3653 open_folder as util 2024-04-05 16:08:45 +09:00
AUTOMATIC1111 b372fb6165 fix API upscale 2024-04-01 23:33:45 +03:00
AUTOMATIC1111 0cb2bbd01a
Merge pull request #15428 from v0xie/fix/remove-callbacks
Fix: Remove script callbacks in ordered_callbacks_map
2024-04-01 23:25:03 +03:00
v0xie a669b8a6bc fix: remove script callbacks in ordered_callbacks_map 2024-04-01 12:51:09 -07:00
AUTOMATIC1111 719296133d
Merge pull request #15425 from light-and-ray/fix_upscaler_2_images_do_not_match
fix upscaler 2 images do not match
2024-04-01 13:40:09 +03:00
Andray 86861f8379 fix upscaler 2 images do not match 2024-04-01 13:58:45 +04:00
storyicon e73a7e4006 feat: ensure the indexability of dynamically imported packages
Signed-off-by: storyicon <storyicon@foxmail.com>
2024-04-01 09:13:07 +00:00
AUTOMATIC1111 aa4a45187e
Merge pull request #15417 from light-and-ray/fix_upscaler_2
Fix upscaler 2: add missed max_side_length
2024-03-31 18:49:21 +03:00
Andray 0a7d1e756f fix upscaler 2 2024-03-31 19:34:58 +04:00
AUTOMATIC1111 859f0f6b19
Merge pull request #15415 from light-and-ray/fix_dcd4f880a86e500ec88ddf7eafe65894a24b85a3
fix dcd4f880a8
2024-03-31 16:55:47 +03:00
AUTOMATIC1111 23ef5027c6
Merge pull request #15414 from DrBiggusDickus/dev2
Fix CodeFormer weight
2024-03-31 16:53:23 +03:00
Andray 4ccbae320e fix dcd4f880a8 2024-03-31 17:05:15 +04:00
DrBiggusDickus ea83180761 fix CodeFormer weight 2024-03-31 14:41:06 +02:00
AUTOMATIC1111 f1a6c5fe17 add an option to hide postprocessing options in Extras tab 2024-03-31 08:30:00 +03:00
AUTOMATIC1111 bfa20d2758 resize Max side length field 2024-03-31 08:20:19 +03:00
AUTOMATIC1111 dcd4f880a8 rework code/UI for #15293 2024-03-31 08:17:22 +03:00
AUTOMATIC1111 7f3ce06de9
Merge pull request #15293 from light-and-ray/extras_upscaler_limit_target_resolution
Extras upscaler: option limit target resolution
2024-03-31 08:02:35 +03:00
AUTOMATIC1111 8bebfde701
Merge pull request #15350 from baseco/memory-bug-fix
minor bug fix of sd model memory management
2024-03-30 07:37:10 +03:00
AUTOMATIC1111 98096195dd
Merge pull request #15382 from huaizong/fix/whz/model-loaded-remove-v3
fix: when find already_loaded model, remove loaded by array index
2024-03-30 07:36:33 +03:00
AUTOMATIC1111 642bca4c3d
Merge pull request #15380 from light-and-ray/interrupt_upscale
interrupt upscale
2024-03-30 07:34:34 +03:00
AUTOMATIC1111 80b87107de
Merge pull request #15386 from eltociear/patch-3
fix typo in call_queue.py
2024-03-30 07:34:10 +03:00
AUTOMATIC1111 c4c8a64111 restore the line lost in the merge 2024-03-30 07:33:39 +03:00
AUTOMATIC1111 470d402b17
Merge pull request #15390 from ochen1/patch-1
fix: Python version check for PyTorch installation compatibility
2024-03-30 07:32:29 +03:00
AUTOMATIC1111 1dc8cc1bce
Merge branch 'dev' into patch-1 2024-03-30 07:31:08 +03:00
AUTOMATIC1111 8687163f7f
Merge pull request #15394 from light-and-ray/fix_ui_config_for_hires_sampler_and_scheduler
fix ui_config for hires sampler and scheduler
2024-03-27 16:42:09 +03:00
Andray 4e2bb7250f fix_ui_config_for_hires_sampler_and_scheduler 2024-03-27 15:35:06 +04:00
ochen1 5461b00e89
fix: Python version check for PyTorch installation compatibility 2024-03-26 21:22:09 -06:00
Ikko Eltociear Ashimine 16522cb0e3
fix typo in call_queue.py
amout -> amount
2024-03-27 03:01:06 +09:00
Andray c321680b3d interrupt upscale 2024-03-26 14:53:38 +04:00
王怀宗 f4633cb9c0 fix: when find already_loaded model, remove loaded by array index 2024-03-26 18:29:51 +08:00
Boning f62217b65d minor bug fix of sd model memory management 2024-03-25 10:38:15 -07:00
AUTOMATIC1111 dfbdb5a135 put request: gr.Request at start of img2img function similar to txt2img 2024-03-25 18:00:58 +03:00
AUTOMATIC1111 b0b90dc0d7
Merge pull request #15319 from catboxanon/feat/ssmd_cover_images
Support cover images embedded in safetensors metadata
2024-03-24 13:43:37 +03:00
AUTOMATIC1111 9aa9e980a9 support scheduler selection in hires fix 2024-03-24 11:00:16 +03:00
catboxanon c4402500c7 Support `ssmd_cover_images` 2024-03-24 02:33:10 -04:00
AUTOMATIC1111 755d2cb2e5
Merge pull request #15343 from light-and-ray/escape_brackets_in_lora_random_prompt
escape brackets in lora random prompt generator
2024-03-24 05:31:33 +03:00
AUTOMATIC1111 0affa24ce2
Merge pull request #15354 from akx/xyz-script-size
Add Size as an XYZ Grid option
2024-03-24 05:27:00 +03:00
AUTOMATIC1111 bf2f7b3af4
Merge pull request #15333 from AUTOMATIC1111/scheduler_selection
Scheduler selection in main UI
2024-03-24 05:21:56 +03:00
AUTOMATIC1111 db61b876d6
Merge pull request #15361 from kaalibro/fix/scheduler_selection
Fix for "Scheduler selection" #15333
2024-03-24 05:21:40 +03:00
kaalibro f3ca6a92ad
Fix for #15333
- Fix "X/Y/Z plot" not working with "Schedule type"
- Fix "Schedule type" not being saved to "params.txt"
2024-03-23 00:50:37 +05:00
Aarni Koskela 2941e1f1f3 Add Size as an XYZ Grid option 2024-03-22 12:04:03 +02:00
Andray 721c4309c2 escape brackets in lora random prompt generator 2024-03-21 16:29:51 +04:00
AUTOMATIC1111 57727e554d make #15334 work without making copies of images 2024-03-21 07:22:27 +03:00
AUTOMATIC1111 b80b1cf92c
Merge pull request #15334 from Gourieff/extras--allow-png-rgba--dev
Allow PNG-RGBA for Extras Tab
2024-03-21 07:21:15 +03:00
AUTOMATIC1111 5c5594ff16 linter 2024-03-21 07:09:40 +03:00
AUTOMATIC1111 65075896f2
Merge pull request #15310 from Dalton-Murray/update-pytorch-lightning-utilities
Update pytorch lightning utilities
2024-03-21 07:08:43 +03:00
Dalton 32ba757501
Re-add import but after if check 2024-03-20 23:55:04 -04:00
Dalton 4e6e2574ab
Cleanup ddpm_edit.py
Fully reverts this time
2024-03-20 23:36:35 -04:00
Dalton 41907b25f0
Cleanup sd_hijack_ddpm_v1.py
Forgot some things to revert
2024-03-20 23:35:32 -04:00
Dalton 4bc2963320
Remove unnecessary import 2024-03-20 23:33:15 -04:00
Dalton 4eb5e09873
Update initialize_util.py 2024-03-20 23:28:40 -04:00
Dalton b5b04912b5
Include running pytorch lightning check 2024-03-20 23:06:00 -04:00
Dalton f010dfffb9
Revert ddpm_edit.py 2024-03-20 23:02:30 -04:00
Dalton 5fd9a40b92
Revert sd_hijack_ddpm_v1.py 2024-03-20 23:01:50 -04:00
Art Gourieff e0cad0f87a Merge remote-tracking branch 'upstream/dev' into extras--allow-png-rgba--dev 2024-03-20 15:28:17 +07:00
Art Gourieff 8ec8901921 FIX: No specific type for 'image' arg
Roll back
2024-03-20 15:20:29 +07:00
Art Gourieff 702edb288e FIX: initial_pp RGBA right way 2024-03-20 15:14:28 +07:00
AUTOMATIC1111 31306ce672 change the behavior of discard_next_to_last_sigma for sgm_uniform to match other schedulers 2024-03-20 10:29:52 +03:00
AUTOMATIC1111 ac9aa44cb8 do not add 'Automatic' to infotext 2024-03-20 10:27:53 +03:00
AUTOMATIC1111 76f8436bfa add Uniform scheduler 2024-03-20 10:27:32 +03:00
Art Gourieff 61f488302f FIX: Allow PNG-RGBA for Extras Tab 2024-03-20 13:28:32 +07:00
AUTOMATIC1111 25cd53d775 scheduler selection in main UI 2024-03-20 09:17:11 +03:00
AUTOMATIC1111 060e55dfe3
Merge pull request #15331 from AUTOMATIC1111/extra-networks-buttons
Fix extra networks buttons when filename contains an apostrophe
2024-03-20 06:53:55 +03:00
missionfloyd b5c33341a1
Don't use quote_js on filename 2024-03-19 19:06:56 -06:00
missionfloyd 6e420c7be2
Merge branch 'dev' into extra-networks-buttons 2024-03-19 19:03:53 -06:00
missionfloyd d7f48472cc Fix extra networks buttons when filename contains an apostrophe 2024-03-19 18:50:25 -06:00
Dalton 49779413aa
Formatting sd_hijack_ddpm_v1.py 2024-03-19 14:54:06 -04:00
Dalton 8f450321fe
Formatting ddpm_edit 2024-03-19 14:53:30 -04:00
Dalton 86276832e0
Update sd_hijack_ddpm_v1.py 2024-03-19 14:45:07 -04:00
Dalton 61f321756f
Update ddpm_edit.py 2024-03-19 14:44:31 -04:00
AUTOMATIC1111 d44b8aa8c1
Merge pull request #15325 from AUTOMATIC1111/sgm_uniform
Sgm uniform scheduler for SDXL-Lightning models
2024-03-19 21:37:16 +03:00
Kohaku-Blueleaf a6b5a513f9 Implementation for sgm_uniform branch 2024-03-19 20:05:54 +08:00
AUTOMATIC1111 c4a00affc5 use existing quote_js function for #15316 2024-03-19 08:10:27 +03:00
AUTOMATIC1111 522121be7e
Merge pull request #15316 from AUTOMATIC1111/escape-filename
Escape btn_copy_path filename
2024-03-19 08:02:36 +03:00
missionfloyd 3fa1ebed62 Escape btn_copy_path filename 2024-03-18 21:47:52 -06:00
AUTOMATIC1111 7ac7600dc3
Merge pull request #15307 from AUTOMATIC1111/restore-outputs-path
restore outputs path
2024-03-18 19:33:48 +03:00
w-e-w e9d4da7b56 restore outputs path
output -> outputs
2024-03-19 00:54:56 +09:00
AUTOMATIC1111 c4664b5a9c fix for listing wrong requirements for extensions 2024-03-18 08:00:42 +03:00
Andray 203afa39c4 update tooltip 2024-03-18 06:52:46 +04:00
Dalton 51cb20ec39
Update ddpm_edit.py 2024-03-17 22:45:31 -04:00
Dalton 2a6054f836
Update sd_hijack_ddpm_v1.py 2024-03-17 22:37:19 -04:00
AUTOMATIC1111 8ac4a207f3
Merge pull request #15299 from AUTOMATIC1111/diskcache-bett
Tweak diskcache limits
2024-03-17 23:59:12 +03:00
Aarni Koskela df4da02ab0 Tweak diskcache limits 2024-03-17 20:25:25 +00:00
AUTOMATIC1111 f1b090e9e0
Merge pull request #15287 from AUTOMATIC1111/diskcache
use diskcache library for caching
2024-03-17 23:20:00 +03:00
AUTOMATIC1111 611faaddef change the default name for callback from None to "unnamed" 2024-03-17 23:19:24 +03:00
AUTOMATIC1111 daa1b33247 make reloading UI scripts optional when doing Reload UI, and off by default 2024-03-17 18:16:12 +03:00
Andray fd83d4eec3 add .needs_reload_ui() 2024-03-17 18:19:13 +04:00
Andray 81be357925 hide limit target resolution under option 2024-03-17 14:51:19 +04:00
AUTOMATIC1111 79cbc92abf change code for variant requirements in metadata.ini 2024-03-17 13:30:20 +03:00
Andray 06c5dd0907 maybe fix tests 2024-03-17 14:28:26 +04:00
AUTOMATIC1111 908d522057 update ruff to 0.3.3 2024-03-17 13:19:44 +03:00
AUTOMATIC1111 4ce2e25c0b
Merge pull request #15290 from light-and-ray/allow_variants_for_extension_name_in_metadata.ini
allow variants for extension name in metadata.ini
2024-03-17 13:19:23 +03:00
Andray ef35619325 Extras upscaler: option limit target resolution 2024-03-17 14:14:12 +04:00
Andray b1cd0189bc allow variants for extension name in metadata.ini 2024-03-17 13:05:35 +04:00
AUTOMATIC1111 c95c46004a
Merge pull request #15288 from light-and-ray/allow_use_zoom.js_outside_webui_context
little fixes zoom.js
2024-03-17 09:48:33 +03:00
Andray c3f75d1d85 little fixes zoom.js 2024-03-17 10:30:11 +04:00
AUTOMATIC1111 c12ba58433
Merge pull request #15286 from light-and-ray/allow_use_zoom.js_outside_webui_context
allow use zoom.js outside webui context [for extensions]
2024-03-17 09:20:51 +03:00
AUTOMATIC1111 66355b4775 use diskcache library for caching 2024-03-17 09:18:32 +03:00
Andray e9b8a89b3c allow use zoom.js outside webui context 2024-03-17 09:29:11 +04:00
AUTOMATIC1111 93c7b9d7fc linter for #15262 2024-03-17 07:02:31 +03:00
AUTOMATIC1111 6d8b7ec188
Merge pull request #15262 from catboxanon/feat/dragdrop-urls
Support dragdrop for URLs to read infotext
2024-03-17 07:02:08 +03:00
catboxanon 446cd5a58b
dragdrop: add error handling for URLs 2024-03-16 20:19:12 -04:00
missionfloyd 83a9dd82db Download image client-side 2024-03-16 17:10:26 -06:00
missionfloyd 3da13f0cc9 Fix dragging to/from firefox 2024-03-16 15:46:29 -06:00
AUTOMATIC1111 df8c09bcb3
Merge pull request #15283 from AUTOMATIC1111/dora-weight-decompose
Use correct DoRA implementation
2024-03-16 20:20:08 +03:00
AUTOMATIC1111 8dcb8faf5d
Merge branch 'dev' into dora-weight-decompose 2024-03-16 20:20:02 +03:00
Kohaku-Blueleaf 199c51d688 linter 2024-03-17 00:00:07 +08:00
Kohaku-Blueleaf 1792e193b1 Use correct implementation, fix device error 2024-03-16 23:52:29 +08:00
AUTOMATIC1111 bf35c66183 fix for #15179 2024-03-16 18:45:19 +03:00
AUTOMATIC1111 cb09e1ef7d
Merge pull request #15179 from llnancy/master
fix: fix syntax errors
2024-03-16 18:45:01 +03:00
AUTOMATIC1111 0283826179 prevent make alt key from opening main menu if it's used for brush size also 2024-03-16 18:44:36 +03:00
AUTOMATIC1111 2f9d1c33e2
Merge pull request #15267 from light-and-ray/prevent_alt_menu_on_firefox
prevent alt menu for firefox
2024-03-16 18:31:55 +03:00
AUTOMATIC1111 874809e0ca
Merge pull request #15268 from light-and-ray/handle_0_wheel_deltaX
handle 0 wheel deltaY
2024-03-16 18:25:00 +03:00
Andray c364b60776 handle 0 wheel deltaX 2024-03-16 18:08:02 +04:00
Andray 7598a92436 use e.key instead of e.code 2024-03-16 17:49:05 +04:00
Andray eb2ea8df1d check e.key in up event 2024-03-16 17:42:25 +04:00
Andray 9142ce8188 fix linter and do not require reload page if option was changed 2024-03-16 16:14:57 +04:00
Andray 79514e5b8e prevent defaults for alt only if mouse inside image 2024-03-16 16:06:21 +04:00
AUTOMATIC1111 bb9df5cdc9
Merge pull request #15276 from AUTOMATIC1111/v180_hr_styles-actual-version-number
v180_hr_styles actual version number
2024-03-16 12:40:24 +03:00
AUTOMATIC1111 e8613dbc93
Merge pull request #15231 from light-and-ray/fix_ui-config_for_InputAccordion
fix ui-config for InputAccordion [custom_script_source]
2024-03-16 12:35:43 +03:00
Andray cc8ea32501 fix ui-config for InputAccordion 2024-03-16 12:32:39 +04:00
w-e-w 38a7dc5488 v180_hr_styles actual version number 2024-03-16 17:19:38 +09:00
AUTOMATIC1111 5bd2724765
Merge pull request #15205 from AUTOMATIC1111/callback_order
Callback order
2024-03-16 09:45:41 +03:00
AUTOMATIC1111 9fd693272f
Merge pull request #15211 from light-and-ray/type_hintinh_in_shared.py
type hinting in shared.py
2024-03-16 09:45:30 +03:00
AUTOMATIC1111 f7bad19e00
Merge pull request #15221 from AUTOMATIC1111/fix-Restore-progress
fix "Restore progress" button
2024-03-16 09:44:50 +03:00
AUTOMATIC1111 03ea0f3bfc
Merge pull request #15222 from light-and-ray/move_postprocessing-for-training_into_builtin_extensions
move postprocessing-for-training into builtin extensions
2024-03-16 09:43:01 +03:00
AUTOMATIC1111 2fc47b44c2
Merge pull request #15223 from light-and-ray/move_upscale_postprocessing_under_input_accordion
move upscale postprocessing under input accordion
2024-03-16 09:41:40 +03:00
AUTOMATIC1111 446e49d6db
Merge branch 'dev' into move_upscale_postprocessing_under_input_accordion 2024-03-16 09:41:16 +03:00
AUTOMATIC1111 8bc9978909
Merge pull request #15228 from wangshuai09/ascend_npu_readme
Ascend NPU wiki page
2024-03-16 09:39:34 +03:00
AUTOMATIC1111 1282bceeba
Merge pull request #15233 from light-and-ray/featch_only_active_branch_updates_for_extensions
featch only active branch updates for extensions
2024-03-16 09:06:33 +03:00
AUTOMATIC1111 d38b390ed4
Merge pull request #15239 from AUTOMATIC1111/Fix-lora-bugs
Add missing .mean() back
2024-03-16 09:06:05 +03:00
AUTOMATIC1111 63c3c4dbc3 simplify code for #15244 2024-03-16 09:04:08 +03:00
AUTOMATIC1111 afb9296e0d
Merge pull request #15244 from Haoming02/auto-scale-by
Automatically Set the Scale by value when user selects an Upscale Model
2024-03-16 08:49:32 +03:00
AUTOMATIC1111 c9244ef83a
Merge pull request #15224 from DGdev91/dev
Better workaround for Navi1, removing --pre for Navi3
2024-03-16 08:45:02 +03:00
AUTOMATIC1111 a072c1997d
Merge pull request #15259 from AUTOMATIC1111/PEP-604-annotations
PEP 604 annotations
2024-03-16 08:43:02 +03:00
AUTOMATIC1111 3cb698ac15
Merge pull request #15260 from v0xie/fix-OFT-MhA-AttributeError
Fix AttributeError in OFT when trying to get MultiheadAttention weight
2024-03-16 08:42:45 +03:00
AUTOMATIC1111 0cc3647c1c
Merge pull request #15261 from catboxanon/fix/imageviewer-click
Make imageviewer event listeners browser consistent
2024-03-16 08:41:11 +03:00
AUTOMATIC1111 3ffe47c6b7
Merge pull request #15263 from AUTOMATIC1111/fix-hr-comments
Strip comments from hires fix prompt
2024-03-16 08:30:16 +03:00
AUTOMATIC1111 c5aa7b65f7
Merge pull request #15269 from AUTOMATIC1111/fix-Hires-prompt-Styles
fix issue with Styles when Hires prompt is used
2024-03-16 08:25:39 +03:00
AUTOMATIC1111 01ba5ad213
Merge pull request #15272 from AUTOMATIC1111/bump-action-version
bump action version
2024-03-16 08:22:48 +03:00
w-e-w a3a648bf6b bump action version 2024-03-16 05:57:23 +09:00
w-e-w 887a512208 fix issue with Styles when Hires prompt is used 2024-03-15 21:06:54 +09:00
Andray 6f51e05553 prevent alt menu for firefox 2024-03-15 12:12:37 +04:00
missionfloyd 5f4203bf9b Strip comments from hires fix prompt 2024-03-14 22:23:06 -06:00
catboxanon 8eaa7e9f04 Support dragdrop for URLs 2024-03-15 04:06:17 +00:00
catboxanon 76fd487818
Make imageviewer event listeners browser consistent 2024-03-14 21:59:53 -04:00
v0xie 07805cbeee fix: AttributeError when attempting to reshape rescale by org_module weight 2024-03-14 17:05:14 -07:00
w-e-w c40f33ca04 PEP 604 annotations 2024-03-15 08:22:36 +09:00
Haoming 4e17fc36d8 add user setting
Now this is disabled by default
2024-03-14 10:04:09 +08:00
Haoming fd71b761ff use re instead of hardcoding
Now supports all natively provided upscaler as well
2024-03-14 09:55:14 +08:00
Haoming d18eb10ecd add hook 2024-03-13 21:15:52 +08:00
KohakuBlueleaf 9f2ae1cb85 Add missing .mean 2024-03-13 11:47:33 +08:00
DGdev91 32f0b5dbaf Merge branch 'dev' of https://github.com/DGdev91/stable-diffusion-webui into dev 2024-03-13 00:56:42 +01:00
DGdev91 2efc7c1b05 Better workaround for Navi1, removing --pre for Navi3 2024-03-13 00:54:32 +01:00
DGdev91 9fbfb8ad32 Better workaround for Navi1 - fix if 2024-03-13 00:43:01 +01:00
DGdev91 74e2e5279c Workaround for Navi1: pytorch nightly whl for 3.8 and 3.9 2024-03-13 00:17:24 +01:00
Andray b980c8140b featch only active branch updates for extensions 2024-03-12 22:21:59 +04:00
wangshuai09 994e08aac1 ascend npu readme 2024-03-12 18:45:26 +08:00
DGdev91 8262cd71c4 Better workaround for Navi1, removing --pre for Navi3 2024-03-12 00:09:07 +01:00
Andray 2e3a0f39f6 move upscale postprocessing under input accordion 2024-03-12 02:28:15 +04:00
Andray 4079b17dd9 move postprocessing-for-training into builtin extensions 2024-03-12 01:50:57 +04:00
w-e-w 1a1205f601 fix Restore progress 2024-03-12 03:26:50 +09:00
Andray 2d57a2df66
Update modules/shared.py
Co-authored-by: catboxanon <122327233+catboxanon@users.noreply.github.com>
2024-03-11 07:40:15 +04:00
Andray eb10da8bb7 type hinting in shared.py 2024-03-11 05:15:09 +04:00
AUTOMATIC1111 3e0146f9bd restore the lost Uncategorized options section 2024-03-10 22:40:35 +03:00
AUTOMATIC1111 1bbc8a153b Merge branch 'dev' into callback_order 2024-03-10 16:15:09 +03:00
AUTOMATIC1111 3670b4f49e lint 2024-03-10 15:16:12 +03:00
AUTOMATIC1111 2f55d669a2 add support for specifying callback order in metadata 2024-03-10 15:14:04 +03:00
AUTOMATIC1111 edc56202c1
Merge pull request #15201 from AUTOMATIC1111/update-preview-on-Replace-Preview
update preview on Replace Preview
2024-03-10 14:11:26 +03:00
AUTOMATIC1111 7e5e67330b add UI for reordering callbacks 2024-03-10 14:09:48 +03:00
w-e-w 9fd0cd6a80 update preview on Replace Preview 2024-03-10 18:24:52 +09:00
SunChaser 9b842e9ec7
fix: resolve type annotation warnings 2024-03-10 16:19:59 +08:00
AUTOMATIC1111 0411eced89 add names to callbacks 2024-03-10 07:52:57 +03:00
AUTOMATIC1111 2e93bdce0c
Merge pull request #15198 from zopieux/search-desc
Add model description to searched terms
2024-03-10 07:03:16 +03:00
AUTOMATIC1111 8076100e14
Merge pull request #15199 from AUTOMATIC1111/add-entry-to-MassFileLister-after-writing-metadata
Add entry to MassFileLister  after writing metadata
2024-03-10 07:01:46 +03:00
w-e-w fb62f1fb40 add entry to MassFileLister after writing metadata
fix #15184
2024-03-10 06:07:16 +09:00
Alexandre Macabies 0085e719a9 Add model description to searched terms.
This adds the model description to the searchable terms.
This is particularly useful since the description can be used to store
arbitrary tags, independently from the filename, which is imposed by the
model publisher.
2024-03-09 21:53:38 +01:00
AUTOMATIC1111 6136db1409 linter 2024-03-09 12:21:46 +03:00
AUTOMATIC1111 110e3d7033
Merge pull request #15191 from AUTOMATIC1111/fix-default-in-get_learned_conditioning
Avoid error from None in get_learned_conditioning
2024-03-09 12:21:23 +03:00
Kohaku-Blueleaf 0dc179ee72 Avoid error from None 2024-03-09 17:12:54 +08:00
AUTOMATIC1111 4c9a7b8a75
Merge pull request #15190 from AUTOMATIC1111/dora-weight-decompose
Fix built-in lora system bugs caused by torch.nn.MultiheadAttention
2024-03-09 08:29:51 +03:00
AUTOMATIC1111 1770b887ec
Merge pull request #15189 from 10sa/dev
Add '--no-prompt-history' cmd args for disable last generation prompt history
2024-03-09 08:28:56 +03:00
AUTOMATIC1111 18d801a13d stylistic changes for extra network sorting/search controls 2024-03-09 08:25:01 +03:00
Kohaku-Blueleaf 851c3d51ed Fix bugs for torch.nn.MultiheadAttention 2024-03-09 12:31:32 +08:00
AUTOMATIC1111 5251733c0d use natural sort in extra networks when ordering by path 2024-03-09 07:24:51 +03:00
10sa c50b7e4eff Add '--no-prompt-history' cmd args for disable last generation prompt history 2024-03-09 11:43:49 +09:00
AUTOMATIC1111 d318f1b5e1
Merge pull request #15183 from jim60105/master
chore: fix font not loaded
2024-03-08 21:58:41 +03:00
陳鈞 02a4ceabdd
chore: fix font not loaded
fix #15182
2024-03-09 02:13:35 +08:00
AUTOMATIC1111 7d1368c51c lint 2024-03-08 17:11:56 +03:00
AUTOMATIC1111 758e8d7b41 undo unwanted change for extra networks 2024-03-08 17:11:42 +03:00
AUTOMATIC1111 530fea2bc4 optimization for extra networks sorting 2024-03-08 17:09:11 +03:00
AUTOMATIC1111 3bd75adb1c optimization for extra networks filtering 2024-03-08 16:54:39 +03:00
SunChaser 01f531e9b1
fix: fix syntax errors 2024-03-08 17:25:28 +08:00
AUTOMATIC1111 a551a43164 add an option to have old-style directory view instead of tree view 2024-03-08 09:52:25 +03:00
AUTOMATIC1111 a43ce7eabb fix broken resize handle on the train tab 2024-03-08 08:13:08 +03:00
AUTOMATIC1111 9409419afb
Merge pull request #15160 from AUTOMATIC1111/dora-weight-decompose
Add DoRA (weight-decompose) support for LoRA/LoHa/LoKr
2024-03-08 08:02:17 +03:00
AUTOMATIC1111 e0c9361b7d performance optimization for extra networks 2024-03-08 07:51:31 +03:00
AUTOMATIC1111 8b96f3d036
Merge pull request #15178 from catboxanon/feat/edit-attention-whitespace-trim
edit-attention: deselect surrounding whitespace
2024-03-08 06:21:24 +03:00
catboxanon 5ab5405b6f
Simpler comparison
Co-authored-by: missionfloyd <missionfloyd@users.noreply.github.com>
2024-03-07 21:30:05 -05:00
catboxanon 766f6e3eca
edit-attention: deselect surrounding whitespace 2024-03-07 18:30:36 -05:00
Kohaku-Blueleaf 12bcacf413 Initial implementation 2024-03-07 13:29:40 +08:00
AUTOMATIC1111 58f7410c9d
Merge pull request #14820 from alexhegit/master
Update to ROCm5.7 and PyTorch
2024-03-06 15:45:39 +03:00
AUTOMATIC1111 ea3aae9c39
Merge branch 'dev' into master 2024-03-06 15:44:55 +03:00
AUTOMATIC1111 8904e00842
Merge pull request #15148 from continue-revolution/conrevo/fix-soft-inpaint
Fix Soft Inpaint for AnimateDiff
2024-03-06 15:36:01 +03:00
continue-revolution 7d59b3b564 rm comment 2024-03-06 05:39:17 -06:00
continue-revolution 7f766cd762 Merge branch 'dev' into conrevo/fix-soft-inpaint 2024-03-06 05:33:30 -06:00
continue-revolution 73e635ce6e fix 2024-03-06 05:32:59 -06:00
AUTOMATIC1111 ecd5fa9c42
Merge pull request #15131 from catboxanon/feat/extra-network-metadata
Re-use profiler visualization for extra networks
2024-03-06 13:08:43 +03:00
AUTOMATIC1111 14215beb48
Merge pull request #15135 from AUTOMATIC1111/fix-extract_style_text_from_prompt
fix extract_style_text_from_prompt #15132
2024-03-06 13:07:43 +03:00
AUTOMATIC1111 11ef1a9302
Merge pull request #15142 from catboxanon/fix/emphasis-prompt-txt-write-order
Fix emphasis infotext missing from `params.txt`
2024-03-06 13:07:02 +03:00
AUTOMATIC1111 c1deec64cb lint 2024-03-06 13:06:13 +03:00
AUTOMATIC1111 2bb296531d
Merge pull request #15141 from catboxanon/feat/emphasis-infotext-parse
Only override emphasis if actually used in prompt
2024-03-06 13:05:56 +03:00
catboxanon ed386c84b6
Fix emphasis infotext missing from `params.txt` 2024-03-05 11:53:36 -05:00
catboxanon 7785d484ae
Only override emphasis if actually used in prompt 2024-03-05 11:50:53 -05:00
w-e-w 706f63adfa fix extract_style_text_from_prompt #15132 2024-03-05 12:35:46 +09:00
catboxanon ecffe8513e
Lint 2024-03-04 18:46:25 -05:00
catboxanon 801461eea2 Re-use profiler visualization for extra networks 2024-03-04 18:33:22 -05:00
AUTOMATIC1111 eee46a5094
Merge pull request #14981 from wangshuai09/gpu_info_for_ascend
Add training support and change lspci for Ascend NPU
2024-03-04 20:06:54 +03:00
AUTOMATIC1111 09b5ce68a9 add images.read to automatically fix all jpeg/png weirdness 2024-03-04 19:14:53 +03:00
AUTOMATIC1111 5625ce1b1a
Merge pull request #14958 from HTYISABUG/dev
Error handling for unsupported transparency
2024-03-04 18:40:16 +03:00
AUTOMATIC1111 58278aa71c
Merge pull request #15121 from AUTOMATIC1111/fix-settings-in-ui
[alternative fix] can't load webui if selected wrong extra option in ui
2024-03-04 18:24:09 +03:00
AUTOMATIC1111 33fbe943e2
Merge pull request #15062 from astriaai/fix-exif-orientation-api
Fix EXIF orientation in API image loading
2024-03-04 16:26:53 +03:00
AUTOMATIC1111 0dc12861ef 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 2024-03-04 15:30:46 +03:00
Alon Burg 67d8dafe44 Fix EXIF orientation in API image loading 2024-03-04 12:23:14 +02:00
wangshuai09 3fb1c2e58d fix npu-smi command 2024-03-04 17:19:37 +08:00
AUTOMATIC1111 92d77e3fa8
Merge pull request #15102 from light-and-ray/fix_jpeg_live_preview
fix_jpeg_live_preview
2024-03-04 10:30:24 +03:00
AUTOMATIC1111 48a677c4ac
Merge pull request #15116 from akx/typos
Fix various typos with crate-ci/typos
2024-03-04 10:28:42 +03:00
Aarni Koskela e3fa46f26f Fix various typos with crate-ci/typos 2024-03-04 08:42:07 +02:00
AUTOMATIC1111 e2a8745abc
Merge pull request #15084 from clayne/1709395892-upscale-logging-reduce
upscaler_utils: Reduce logging
2024-03-04 06:50:03 +03:00
Christopher Layne 3c0177a24b upscaler_utils: Reduce logging
* upscale_with_model: Remove debugging logging occurring in loop as
  it's an excessive amount of noise when running w/ DEBUG log levels.
2024-03-03 11:41:32 -08:00
Andray 0103365697 fix_jpeg_live_preview 2024-03-03 16:54:58 +04:00
AUTOMATIC1111 45b8a499a7 fix wrong condition 2024-03-02 10:36:48 +03:00
AUTOMATIC1111 bb24c13ed7 infotext support for #14978 2024-03-02 07:39:59 +03:00
AUTOMATIC1111 aabedcbcc7
Merge pull request #14978 from drhead/refiner_fix
Make refiner switchover based on model timesteps instead of sampling steps
2024-03-02 07:24:44 +03:00
AUTOMATIC1111 f4cb21bb8a
Merge pull request #15031 from light-and-ray/unix_filenames
cmd args: `--unix-filenames-sanitization` and `--filenames-max-length`
2024-03-02 07:16:06 +03:00
AUTOMATIC1111 6044a9723a
Merge pull request #15059 from Dalton-Murray/direct-binary-release-link
Add a direct link to the binary release
2024-03-02 07:15:32 +03:00
AUTOMATIC1111 9189ea20b0
Merge pull request #15041 from light-and-ray/resize_handle_for_extra_networks
resize handle for extra networks
2024-03-02 07:13:00 +03:00
AUTOMATIC1111 95d143eafe Merge branch 'master' into dev 2024-03-02 07:04:33 +03:00
AUTOMATIC1111 ee470cc6a3 style changes for #14979 2024-03-02 06:54:11 +03:00
AUTOMATIC1111 1a51b166a0 call apply_alpha_schedule_override in load_model_weights for #14979 2024-03-02 06:53:53 +03:00
AUTOMATIC1111 06b9200e91
Merge pull request #14979 from drhead/refiner_cumprod_fix
Protect alphas_cumprod during refiner switchover
2024-03-02 06:40:32 +03:00
AUTOMATIC1111 f04e76811a
Merge pull request #15039 from light-and-ray/dat_cmd_flag
dat cmd flag
2024-03-02 06:38:50 +03:00
AUTOMATIC1111 817d9b15f7
Merge pull request #15065 from light-and-ray/resizeHandle_handle_double_tap
resizeHandle handle double tap
2024-03-02 06:37:19 +03:00
AUTOMATIC1111 150b603770
Merge pull request #15035 from AUTOMATIC1111/fix/normalized-filepath-absolute
Use `absolute` path for normalized filepath
2024-03-02 06:35:46 +03:00
Andray eb0b84c564 make minimal width 2 times smaller then default 2024-02-29 16:02:21 +04:00
Andray bb99f52712 resizeHandle handle double tap 2024-02-29 15:40:15 +04:00
Dalton bce09eb987
Add a direct link to the binary release 2024-02-29 01:04:46 -05:00
Andray 51cc1ff2c9 fix for mobile and allow collapse right column 2024-02-27 23:31:47 +04:00
Andray b4c44e659b fix on reload with changed show all loras setting 2024-02-27 23:17:52 +04:00
Andray de7604fa77 lint 2024-02-27 18:38:38 +04:00
Andray 44bce3c74e resize handle for extra networks 2024-02-27 18:31:36 +04:00
Andray 3ba575216a dat cmd flag 2024-02-27 15:10:51 +04:00
drhead 4dae91a1fe
remove alphas cumprod fix from samplers_common 2024-02-26 23:46:10 -05:00
drhead 94f23d00a7
move alphas cumprod override out of processing 2024-02-26 23:44:58 -05:00
drhead e2cd92ea23
move refiner fix to sd_models.py 2024-02-26 23:43:27 -05:00
catboxanon 3a618e3d24
Fix normalized filepath, resolve -> absolute
https://github.com/lllyasviel/stable-diffusion-webui-forge/issues/313
https://github.com/AUTOMATIC1111/stable-diffusion-webui/discussions/14942#discussioncomment-8550050
2024-02-26 12:44:57 -05:00
Andray dd4b0b95d5 cmd args: allow unix filenames and filenames max length 2024-02-26 16:30:15 +04:00
AUTOMATIC1111 c8a5322d1f
Merge pull request #15012 from light-and-ray/register_tmp_file-also-with-mtime
register_tmp_file also for mtime
2024-02-26 12:53:21 +03:00
AUTOMATIC1111 ca0308b60d
Merge pull request #15010 from light-and-ray/fix_resize-handle_for_vertical_layout
Fix resize-handle visability for vertical layout (mobile)
2024-02-26 12:52:34 +03:00
Andray 6e6cc2922d fix resize handle 2024-02-26 13:37:29 +04:00
drhead 648f6a8e0c
dont need to preserve alphas_cumprod_original 2024-02-25 23:28:36 -05:00
AUTOMATIC1111 2b7ddcbb5c
Merge pull request #15006 from imnodb/master
fix: the `split_threshold` parameter does not work when running Split oversized images
2024-02-26 07:16:42 +03:00
AUTOMATIC1111 e3a8dc6e23
Merge pull request #15004 from light-and-ray/ResizeHandleRow_-_allow_overriden_column_scale_parametr
ResizeHandleRow - allow overriden column scale parametr
2024-02-26 07:16:24 +03:00
AUTOMATIC1111 ca8dc2bde2
Merge pull request #14995 from dtlnor/14591-bug-the-categories-layout-is-different-when-localization-is-on
Fix #14591 using translated content to do categories mapping
2024-02-26 07:12:31 +03:00
AUTOMATIC1111 900419e85e
Merge pull request #14973 from AUTOMATIC1111/Fix-new-oft-boft
Fix the OFT/BOFT bugs when using new LyCORIS implementation
2024-02-26 07:12:12 +03:00
Andray 3a99824638 register_tmp_file also with mtime 2024-02-23 20:26:56 +04:00
Andray bab918f049 fix resize-handle for vertical layout 2024-02-23 18:34:24 +04:00
DB Eriospermum ed594d7ba6
fix: the `split_threshold` parameter does not work when running Split oversized images 2024-02-23 13:37:37 +08:00
Andray 9211febbfc ResizeHandleRow - allow overriden column scale parametr 2024-02-23 02:32:12 +04:00
AUTOMATIC1111 18819723c1
Merge pull request #15002 from light-and-ray/support_resizable_columns_for_touch_(tablets)
support resizable columns for touch (tablets)
2024-02-22 22:59:26 +03:00
AUTOMATIC1111 3f18a09c86 make extra network card description plaintext by default, with an option to re-enable HTML as it was 2024-02-22 21:27:10 +03:00
Andray 58985e6b37 fix lint 2 2024-02-22 17:22:00 +04:00
Andray ab1e0fa9bf fix lint and console warning 2024-02-22 17:16:16 +04:00
Andray 85abbbb8fa support resizable columns for touch (tablets) 2024-02-22 17:04:56 +04:00
wangshuai09 ba66cf8d69 update 2024-02-22 20:17:10 +08:00
AUTOMATIC1111 1da05297ea possible fix for reload button not appearing in some cases for extra networks. 2024-02-22 10:28:03 +03:00
dtlnor f537e5a519 fix #14591 - using translated content to do categories mapping 2024-02-22 12:26:57 +09:00
Kohaku-Blueleaf c4afdb7895
For no constraint 2024-02-22 00:43:32 +08:00
Kohaku-Blueleaf 64179c3221
Update network_oft.py 2024-02-21 22:50:43 +08:00
wangshuai09 b7aa425344 del gpu_info for npu 2024-02-21 11:49:06 +08:00
drhead 9c1ece8978
Protect alphas_cumprod during refiner switchover 2024-02-20 19:23:21 -05:00
drhead bf348032bc
fix missing arg 2024-02-20 16:59:28 -05:00
drhead 25eeeaa65f
Allow refiner to be triggered by model timestep instead of sampling 2024-02-20 16:37:29 -05:00
drhead 09d2e58811
Pass sigma to apply_refiner 2024-02-20 16:22:40 -05:00
drhead f4869f8de3
Add compatibility option for refiner switching 2024-02-20 16:18:13 -05:00
Kohaku-Blueleaf 591470d86d linting 2024-02-20 17:21:34 +08:00
Kohaku-Blueleaf a5436a3ac0 Update network_oft.py 2024-02-20 17:20:14 +08:00
AUTOMATIC1111 0a271938d8
Merge pull request #14966 from light-and-ray/avoid_doble_upscaling_in_inpaint
[bug] avoid doble upscaling in inpaint
2024-02-19 18:06:05 +03:00
Andray 33c8fe1221 avoid doble upscaling in inpaint 2024-02-19 16:57:49 +04:00
AUTOMATIC1111 6e4fc5e1a8
Merge pull request #14871 from v0xie/boft
Support inference with LyCORIS BOFT networks
2024-02-19 10:05:30 +03:00
Kohaku-Blueleaf 4eb949625c
prevent undefined variable 2024-02-19 14:43:07 +08:00
HSIEH TSUNGYU 9d5dc582be Error handling for unsupported transparency
When input images (palette mode) have transparency (bytes) in info,
the output images (RGB mode) will inherit it,
causing ValueError in Pillow:PIL/PngImagePlugin.py#1364
when trying to unpack this bytes.

This commit check the PNG mode and transparency info,
removing transparency if it's RGB mode and transparency is bytes
2024-02-18 19:27:33 +08:00
Kohaku-Blueleaf 5a8dd0c549
Fix rescale 2024-02-18 14:58:41 +08:00
AUTOMATIC1111 9d5becb4de
Merge pull request #14947 from AUTOMATIC1111/open-button
option "open image button" open the actual dir
2024-02-17 21:30:21 +03:00
w-e-w 71072f5620 re-work open image button settings 2024-02-18 02:47:44 +09:00
w-e-w a18e54ecd7 option "open image button" open the actual dir 2024-02-18 00:38:05 +09:00
AUTOMATIC1111 4ff1fabc86 Update comment for Pad prompt/negative prompt v0 to add a warning about truncation, make it override the v1 implementation 2024-02-17 13:21:08 +03:00
AUTOMATIC1111 4573195894 prevent escape button causing an interrupt when no generation has been made yet 2024-02-17 11:40:53 +03:00
Kohaku-Blueleaf 90441294db
Add rescale mechanism
LyCORIS will support save oft_blocks instead of oft_diag in the near future (for both OFT and BOFT)

But this means we need to store the rescale if user enable it.
2024-02-12 14:25:09 +08:00
v0xie eb6f2df826 Revert "fix: add butterfly_factor fn"
This reverts commit 81c16c965e.
2024-02-08 22:00:15 -08:00
v0xie 613b0d9548 doc: add boft comment 2024-02-08 21:58:59 -08:00
v0xie 325eaeb584 fix: get boft params from weight shape 2024-02-08 11:55:05 -08:00
v0xie 2f1073dc6e style: fix lint 2024-02-07 04:55:11 -08:00
v0xie 81c16c965e fix: add butterfly_factor fn 2024-02-07 04:54:14 -08:00
v0xie a4668a16b6 fix: calculate butterfly factor 2024-02-07 04:51:22 -08:00
v0xie 9588721197 feat: support LyCORIS BOFT 2024-02-07 04:49:17 -08:00
Alex He db4632f4ba Update to ROCm5.7 and PyTorch
The webui.sh installs ROCm5.4.2 as default. The webui run failed with AMD
Radeon Pro W7900 with **Segmentation Fault** at Ubuntu22.04 maybe the ABI
compatibility issue.

ROCm5.7 is the latest version supported by PyTorch (https://pytorch.org/)
at now. I test it with AMD Radeon Pro W7900 by PyTorch+ROCm5.7 with PASS.

Signed-off-by: Alex He <heye_dev@163.com>
2024-02-02 13:48:42 +08:00
109 changed files with 1952 additions and 776 deletions

View File

@ -78,6 +78,8 @@ module.exports = {
//extraNetworks.js
requestGet: "readonly",
popup: "readonly",
// profilerVisualization.js
createVisualizationTable: "readonly",
// from python
localization: "readonly",
// progrssbar.js

View File

@ -11,8 +11,8 @@ jobs:
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
uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: 3.11
# NB: there's no cache: pip here since we're not installing anything
@ -20,7 +20,7 @@ jobs:
# 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
run: pip install ruff==0.3.3
- name: Run Ruff
run: ruff .
lint-js:
@ -29,9 +29,9 @@ jobs:
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/checkout@v4
- name: Install Node.js
uses: actions/setup-node@v3
uses: actions/setup-node@v4
with:
node-version: 18
- run: npm i --ci

View File

@ -11,9 +11,9 @@ jobs:
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/checkout@v4
- name: Set up Python 3.10
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: 3.10.6
cache: pip
@ -22,7 +22,7 @@ jobs:
launch.py
- name: Cache models
id: cache-models
uses: actions/cache@v3
uses: actions/cache@v4
with:
path: models
key: "2023-12-30"
@ -68,13 +68,13 @@ jobs:
python -m coverage report -i
python -m coverage html -i
- name: Upload main app output
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
if: always()
with:
name: output
path: output.txt
- name: Upload coverage HTML
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
if: always()
with:
name: htmlcov

1
.gitignore vendored
View File

@ -38,3 +38,4 @@ notification.mp3
/package-lock.json
/.coverage*
/test/test_outputs
/cache

View File

@ -1,4 +1,126 @@
## 1.8.0-RC
## 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.<filename>` ([#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
@ -14,7 +136,7 @@
* 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))
* Propmpt comments support
* Prompt comments support
### Minor:
* Allow pasting in WIDTHxHEIGHT strings into the width/height fields ([#14296](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14296))
@ -59,9 +181,9 @@
* 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 overriden column scale parameter ([#15004](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15004))
* ResizeHandleRow - allow overridden column scale parameter ([#15004](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/15004))
### Performance
### 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))
@ -101,7 +223,7 @@
* 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 doble upscaling in inpaint ([#14966](https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/14966))
* 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))
@ -171,7 +293,7 @@
* 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:
@ -308,7 +430,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))
@ -484,7 +606,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
@ -703,7 +825,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)
@ -733,7 +855,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
@ -751,7 +873,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

View File

@ -98,6 +98,7 @@ Make sure the required [dependencies](https://github.com/AUTOMATIC1111/stable-di
- [NVidia](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Install-and-Run-on-NVidia-GPUs) (recommended)
- [AMD](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Install-and-Run-on-AMD-GPUs) GPUs.
- [Intel CPUs, Intel GPUs (both integrated and discrete)](https://github.com/openvinotoolkit/stable-diffusion-webui/wiki/Installation-on-Intel-Silicon) (external wiki page)
- [Ascend NPUs](https://github.com/wangshuai09/stable-diffusion-webui/wiki/Install-and-run-on-Ascend-NPUs) (external wiki page)
Alternatively, use online services (like Google Colab):

5
_typos.toml Normal file
View File

@ -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"

View File

@ -301,7 +301,7 @@ class DDPMV1(pl.LightningModule):
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])
@ -880,7 +880,7 @@ class LatentDiffusionV1(DDPMV1):
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 @@ class LatentDiffusionV1(DDPMV1):
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 @@ class LatentDiffusionV1(DDPMV1):
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)

View File

@ -30,7 +30,7 @@ def factorization(dimension: int, factor:int=-1) -> tuple[int, int]:
In LoRA with Kroneckor Product, first value is a value for weight scale.
secon value is a value for weight.
Becuase of non-commutative property, AB BA. Meaning of two matrices is slightly different.
Because of non-commutative property, AB BA. Meaning of two matrices is slightly different.
examples)
factor

View File

@ -29,7 +29,6 @@ class NetworkOnDisk:
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
@ -117,6 +116,12 @@ class NetworkModule:
if 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 +151,9 @@ class NetworkModule:
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 +168,27 @@ class NetworkModule:
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,6 +204,9 @@ class NetworkModule:
if ex_bias is not None:
ex_bias = ex_bias * self.multiplier()
if self.dora_scale is not None:
updown = self.apply_weight_decompose(updown, orig_weight)
return updown * self.calc_scale() * self.multiplier(), ex_bias
def calc_updown(self, target):

View File

@ -36,13 +36,6 @@ class NetworkModuleOFT(network.NetworkModule):
# self.alpha is unused
self.dim = self.oft_blocks.shape[1] # (num_blocks, block_size, block_size)
# LyCORIS BOFT
if self.oft_blocks.dim() == 4:
self.is_boft = True
self.rescale = weights.w.get('rescale', None)
if self.rescale is not None:
self.rescale = self.rescale.reshape(-1, *[1]*(self.org_module[0].weight.dim() - 1))
is_linear = type(self.sd_module) in [torch.nn.Linear, torch.nn.modules.linear.NonDynamicallyQuantizableLinear]
is_conv = type(self.sd_module) in [torch.nn.Conv2d]
is_other_linear = type(self.sd_module) in [torch.nn.MultiheadAttention] # unsupported
@ -54,6 +47,13 @@ class NetworkModuleOFT(network.NetworkModule):
elif is_other_linear:
self.out_dim = self.sd_module.embed_dim
# LyCORIS BOFT
if self.oft_blocks.dim() == 4:
self.is_boft = True
self.rescale = weights.w.get('rescale', None)
if self.rescale is not None and not is_other_linear:
self.rescale = self.rescale.reshape(-1, *[1]*(self.org_module[0].weight.dim() - 1))
self.num_blocks = self.dim
self.block_size = self.out_dim // self.dim
self.constraint = (0 if self.alpha is None else self.alpha) * self.out_dim

View File

@ -355,7 +355,7 @@ def network_apply_weights(self: Union[torch.nn.Conv2d, torch.nn.Linear, torch.nn
"""
Applies the currently selected set of networks to the weights of torch layer self.
If weights already have this particular set of networks applied, does nothing.
If not, restores orginal weights from backup and alters weights according to networks.
If not, restores original weights from backup and alters weights according to networks.
"""
network_layer_name = getattr(self, 'network_layer_name', None)
@ -429,9 +429,12 @@ def network_apply_weights(self: Union[torch.nn.Conv2d, torch.nn.Linear, torch.nn
if isinstance(self, torch.nn.MultiheadAttention) and module_q and module_k and module_v and module_out:
try:
with torch.no_grad():
updown_q, _ = module_q.calc_updown(self.in_proj_weight)
updown_k, _ = module_k.calc_updown(self.in_proj_weight)
updown_v, _ = module_v.calc_updown(self.in_proj_weight)
# Send "real" orig_weight into MHA's lora module
qw, kw, vw = self.in_proj_weight.chunk(3, 0)
updown_q, _ = module_q.calc_updown(qw)
updown_k, _ = module_k.calc_updown(kw)
updown_v, _ = module_v.calc_updown(vw)
del qw, kw, vw
updown_qkv = torch.vstack([updown_q, updown_k, updown_v])
updown_out, ex_bias = module_out.calc_updown(self.out_proj.weight)

View File

@ -149,6 +149,8 @@ class LoraUserMetadataEditor(ui_extra_networks_user_metadata.UserMetadataEditor)
v = random.random() * max_count
if count > v:
for x in "({[]})":
tag = tag.replace(x, '\\' + x)
res.append(tag)
return ", ".join(sorted(res))

View File

@ -31,7 +31,7 @@ class ExtraNetworksPageLora(ui_extra_networks.ExtraNetworksPage):
"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}",

View File

@ -29,6 +29,7 @@ onUiLoaded(async() => {
});
function getActiveTab(elements, all = false) {
if (!elements.img2imgTabs) return null;
const tabs = elements.img2imgTabs.querySelectorAll("button");
if (all) return tabs;
@ -43,6 +44,7 @@ onUiLoaded(async() => {
// Get tab ID
function getTabId(elements) {
const activeTab = getActiveTab(elements);
if (!activeTab) return null;
return tabNameToElementId[activeTab.innerText];
}
@ -252,6 +254,7 @@ onUiLoaded(async() => {
let isMoving = false;
let mouseX, mouseY;
let activeElement;
let interactedWithAltKey = false;
const elements = Object.fromEntries(
Object.keys(elementIDs).map(id => [
@ -277,7 +280,7 @@ onUiLoaded(async() => {
const targetElement = gradioApp().querySelector(elemId);
if (!targetElement) {
console.log("Element not found");
console.log("Element not found", elemId);
return;
}
@ -292,7 +295,7 @@ onUiLoaded(async() => {
// Create tooltip
function createTooltip() {
const toolTipElemnt =
const toolTipElement =
targetElement.querySelector(".image-container");
const tooltip = document.createElement("div");
tooltip.className = "canvas-tooltip";
@ -355,7 +358,7 @@ onUiLoaded(async() => {
tooltip.appendChild(tooltipContent);
// Add a hint element to the target element
toolTipElemnt.appendChild(tooltip);
toolTipElement.appendChild(tooltip);
}
//Show tool tip if setting enable
@ -365,9 +368,9 @@ onUiLoaded(async() => {
// 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();
const activeTab = getActiveTab(elements)?.textContent.trim();
if (activeTab !== "img2img") {
if (activeTab && activeTab !== "img2img") {
const img = targetElement.querySelector(`${elemId} img`);
if (img && img.style.display !== "none") {
@ -508,6 +511,10 @@ onUiLoaded(async() => {
if (isModifierKey(e, hotkeysConfig.canvas_hotkey_zoom)) {
e.preventDefault();
if (hotkeysConfig.canvas_hotkey_zoom === "Alt") {
interactedWithAltKey = true;
}
let zoomPosX, zoomPosY;
let delta = 0.2;
if (elemData[elemId].zoomLevel > 7) {
@ -783,23 +790,29 @@ onUiLoaded(async() => {
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);
}
});
if (elements.img2imgTabs) {
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 ? "-" : "+";
const operation = (e.deltaY || -e.wheelDelta) > 0 ? "-" : "+";
changeZoomLevel(operation, e);
// Handle brush size adjustment with ctrl key pressed
if (isModifierKey(e, hotkeysConfig.canvas_hotkey_adjust)) {
e.preventDefault();
if (hotkeysConfig.canvas_hotkey_adjust === "Alt") {
interactedWithAltKey = true;
}
// Increase or decrease brush size based on scroll direction
adjustBrushSize(elemId, e.deltaY);
}
@ -839,6 +852,20 @@ onUiLoaded(async() => {
document.addEventListener("keydown", handleMoveKeyDown);
document.addEventListener("keyup", handleMoveKeyUp);
// Prevent firefox from opening main menu when alt is used as a hotkey for zoom or brush size
function handleAltKeyUp(e) {
if (e.key !== "Alt" || !interactedWithAltKey) {
return;
}
e.preventDefault();
interactedWithAltKey = false;
}
document.addEventListener("keyup", handleAltKeyUp);
// Detect zoom level and update the pan speed.
function updatePanPosition(movementX, movementY) {
let panSpeed = 2;

View File

@ -8,8 +8,8 @@ shared.options_templates.update(shared.options_section(('canvas_hotkey', "Canvas
"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_hotkey_reset": shared.OptionInfo("R", "Reset zoom and canvas position"),
"canvas_hotkey_overlap": shared.OptionInfo("O", "Toggle overlap").info("Technical button, needed 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"),

View File

@ -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 @@ class ExtraOptionsSection(scripts.Script):
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)

View File

@ -57,10 +57,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
@ -104,7 +108,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 +139,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 +164,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)

View File

@ -1,5 +1,5 @@
<div class="copy-path-button card-button"
title="Copy path to clipboard"
onclick="extraNetworksCopyCardPath(event, '{filename}')"
onclick="extraNetworksCopyCardPath(event)"
data-clipboard-text="{filename}">
</div>

View File

@ -1,4 +1,4 @@
<div class="edit-button card-button"
title="Edit metadata"
onclick="extraNetworksEditUserMetadata(event, '{tabname}', '{extra_networks_tabname}', '{name}')">
onclick="extraNetworksEditUserMetadata(event, '{tabname}', '{extra_networks_tabname}')">
</div>

View File

@ -1,4 +1,4 @@
<div class="metadata-button card-button"
title="Show internal metadata"
onclick="extraNetworksRequestMetadata(event, '{extra_networks_tabname}', '{name}')">
onclick="extraNetworksRequestMetadata(event, '{extra_networks_tabname}')">
</div>

View File

@ -0,0 +1,8 @@
<div class="extra-network-pane-content-dirs">
<div id='{tabname}_{extra_networks_tabname}_dirs' class='extra-network-dirs'>
{dirs_html}
</div>
<div id='{tabname}_{extra_networks_tabname}_cards' class='extra-network-cards'>
{items_html}
</div>
</div>

View File

@ -0,0 +1,8 @@
<div class="extra-network-pane-content-tree resize-handle-row">
<div id='{tabname}_{extra_networks_tabname}_tree' class='extra-network-tree' style='flex-basis: {extra_networks_tree_view_default_width}px'>
{tree_html}
</div>
<div id='{tabname}_{extra_networks_tabname}_cards' class='extra-network-cards' style='flex-grow: 1;'>
{items_html}
</div>
</div>

View File

@ -1,23 +1,53 @@
<div id='{tabname}_{extra_networks_tabname}_pane' class='extra-network-pane'>
<div id='{tabname}_{extra_networks_tabname}_pane' class='extra-network-pane {tree_view_div_default_display_class}'>
<div class="extra-network-control" id="{tabname}_{extra_networks_tabname}_controls" style="display:none" >
<div class="extra-network-control--search">
<input
id="{tabname}_{extra_networks_tabname}_extra_search"
class="extra-network-control--search-text"
type="search"
placeholder="Filter files"
placeholder="Search"
>
</div>
<small>Sort: </small>
<div
id="{tabname}_{extra_networks_tabname}_extra_sort"
class="extra-network-control--sort"
data-sortmode="{data_sortmode}"
data-sortkey="{data_sortkey}"
id="{tabname}_{extra_networks_tabname}_extra_sort_path"
class="extra-network-control--sort{sort_path_active}"
data-sortkey="default"
title="Sort by path"
onclick="extraNetworksControlSortOnClick(event, '{tabname}', '{extra_networks_tabname}');"
>
<i class="extra-network-control--sort-icon"></i>
<i class="extra-network-control--icon extra-network-control--sort-icon"></i>
</div>
<div
id="{tabname}_{extra_networks_tabname}_extra_sort_name"
class="extra-network-control--sort{sort_name_active}"
data-sortkey="name"
title="Sort by name"
onclick="extraNetworksControlSortOnClick(event, '{tabname}', '{extra_networks_tabname}');"
>
<i class="extra-network-control--icon extra-network-control--sort-icon"></i>
</div>
<div
id="{tabname}_{extra_networks_tabname}_extra_sort_date_created"
class="extra-network-control--sort{sort_date_created_active}"
data-sortkey="date_created"
title="Sort by date created"
onclick="extraNetworksControlSortOnClick(event, '{tabname}', '{extra_networks_tabname}');"
>
<i class="extra-network-control--icon extra-network-control--sort-icon"></i>
</div>
<div
id="{tabname}_{extra_networks_tabname}_extra_sort_date_modified"
class="extra-network-control--sort{sort_date_modified_active}"
data-sortkey="date_modified"
title="Sort by date modified"
onclick="extraNetworksControlSortOnClick(event, '{tabname}', '{extra_networks_tabname}');"
>
<i class="extra-network-control--icon extra-network-control--sort-icon"></i>
</div>
<small> </small>
<div
id="{tabname}_{extra_networks_tabname}_extra_sort_dir"
class="extra-network-control--sort-dir"
@ -25,15 +55,18 @@
title="Sort ascending"
onclick="extraNetworksControlSortDirOnClick(event, '{tabname}', '{extra_networks_tabname}');"
>
<i class="extra-network-control--sort-dir-icon"></i>
<i class="extra-network-control--icon extra-network-control--sort-dir-icon"></i>
</div>
<small> </small>
<div
id="{tabname}_{extra_networks_tabname}_extra_tree_view"
class="extra-network-control--tree-view {tree_view_btn_extra_class}"
title="Enable Tree View"
onclick="extraNetworksControlTreeViewOnClick(event, '{tabname}', '{extra_networks_tabname}');"
>
<i class="extra-network-control--tree-view-icon"></i>
<i class="extra-network-control--icon extra-network-control--tree-view-icon"></i>
</div>
<div
id="{tabname}_{extra_networks_tabname}_extra_refresh"
@ -41,15 +74,8 @@
title="Refresh page"
onclick="extraNetworksControlRefreshOnClick(event, '{tabname}', '{extra_networks_tabname}');"
>
<i class="extra-network-control--refresh-icon"></i>
<i class="extra-network-control--icon extra-network-control--refresh-icon"></i>
</div>
</div>
<div class="extra-network-pane-content">
<div id='{tabname}_{extra_networks_tabname}_tree' class='extra-network-tree {tree_view_div_extra_class}'>
{tree_html}
</div>
<div id='{tabname}_{extra_networks_tabname}_cards' class='extra-network-cards'>
{items_html}
</div>
</div>
</div>
{pane_content}
</div>

View File

@ -50,17 +50,17 @@ function dimensionChange(e, is_width, is_height) {
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;

View File

@ -74,22 +74,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) && !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;
}
}
}

View File

@ -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;
}

View File

@ -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.eror(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);

View File

@ -131,19 +131,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);

View File

@ -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';
function createVisualizationTable(data, cutoff = 0, sort = "") {
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];
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 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(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 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");
}
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);
}
});
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");
}
childrenRows.push(tr);
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';
}
return childrenRows;
};
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");
});
};
addLevel(0, []);
cell.title = othersList.join(", ");
cell.classList.add("link");
cell.addEventListener("click", onclick);
if (hide) {
tr.classList.add("hidden");
}
childrenRows.push(tr);
}
return childrenRows;
};
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);
});
}

View File

@ -22,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";
@ -41,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;
@ -64,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;
@ -132,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);
}
});
@ -171,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);

View File

@ -136,8 +136,7 @@ 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() {
@ -209,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);
@ -223,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);
@ -411,7 +412,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];

View File

@ -23,7 +23,7 @@ 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
@ -85,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
@ -93,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
@ -360,7 +360,7 @@ class Api:
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.
@ -409,8 +409,8 @@ class Api:
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

View File

@ -2,48 +2,55 @@ import json
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 = {}
if not os.path.exists(cache_dir) and os.path.isfile(cache_filename):
convert_old_cached_data()
s = cache_data.get(subsection, {})
cache_data[subsection] = s
cache_obj = caches.get(subsection)
if not cache_obj:
cache_obj = make_cache(subsection)
caches[subsection] = cache_obj
return s
return cache_obj
def cached_data_for_file(subsection, title, filename, func):

View File

@ -100,8 +100,8 @@ def wrap_gradio_call(func, extra_outputs=None, add_stats=False):
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"<abbr title='{toltip_a}'>A</abbr>: <span class='measurement'>{active_peak/1024:.2f} GB</span>"
text_r = f"<abbr title='{toltip_r}'>R</abbr>: <span class='measurement'>{reserved_peak/1024:.2f} GB</span>"

View File

@ -121,4 +121,7 @@ parser.add_argument('--api-server-stop', action='store_true', help='enable serve
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")

View File

@ -50,7 +50,7 @@ class FaceRestorerCodeFormer(face_restoration_utils.CommonFaceRestoration):
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)

View File

@ -259,7 +259,7 @@ def test_for_nans(x, where):
def first_time_calculation():
"""
just do any calculation with pytorch layers - the first time this is done it allocaltes about 700MB of memory and
spends about 2.7 seconds doing that, at least wih NVidia.
spends about 2.7 seconds doing that, at least with NVidia.
"""
x = torch.zeros((1, 1)).to(device, dtype)

View File

@ -1,6 +1,7 @@
from __future__ import annotations
import configparser
import dataclasses
import os
import threading
import re
@ -9,6 +10,10 @@ from modules import shared, errors, cache, scripts
from modules.gitpython_hack import Repo
from modules.paths_internal import extensions_dir, extensions_builtin_dir, script_path # noqa: F401
extensions: list[Extension] = []
extension_paths: dict[str, Extension] = {}
loaded_extensions: dict[str, Exception] = {}
os.makedirs(extensions_dir, exist_ok=True)
@ -22,6 +27,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
@ -42,7 +54,7 @@ class ExtensionMetadata:
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,
@ -54,7 +66,15 @@ class ExtensionMetadata:
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"])"""
@ -65,6 +85,22 @@ class ExtensionMetadata:
# 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,6 +192,8 @@ class Extension:
def check_updates(self):
repo = Repo(self.path)
for fetch in repo.remote().fetch(dry_run=True):
if self.branch and fetch.name != f'{repo.remote().name}/{self.branch}':
continue
if fetch.flags != fetch.HEAD_UPTODATE:
self.can_update = True
self.status = "new commits"
@ -186,6 +224,8 @@ class Extension:
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 ***")
@ -196,7 +236,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]:
@ -220,8 +259,12 @@ def list_extensions():
is_builtin = dirname == extensions_builtin_dir
extension = Extension(name=extension_dirname, path=path, enabled=extension_dirname not in shared.opts.disabled_extensions, is_builtin=is_builtin, metadata=metadata)
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:
@ -238,4 +281,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

View File

@ -60,7 +60,7 @@ class ExtraNetwork:
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.

View File

@ -95,6 +95,7 @@ class HypernetworkModule(torch.nn.Module):
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):

View File

@ -12,7 +12,7 @@ import re
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
import string
import json
import hashlib
@ -321,13 +321,16 @@ def resize_image(resize_mode, im, width, height, upscaler_name=None):
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()
@ -770,7 +773,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 +800,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

View File

@ -6,7 +6,7 @@ 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
@ -21,7 +21,7 @@ def process_batch(p, input_dir, output_dir, inpaint_mask_dir, args, to_scale=Fal
output_dir = output_dir.strip()
processing.fix_seed(p)
images = list(shared.walk_files(input_dir, allowed_extensions=(".png", ".jpg", ".jpeg", ".webp", ".tif", ".tiff")))
batch_images = list(shared.walk_files(input_dir, allowed_extensions=(".png", ".jpg", ".jpeg", ".webp", ".tif", ".tiff")))
is_inpaint_batch = False
if inpaint_mask_dir:
@ -31,9 +31,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
@ -46,8 +46,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
@ -55,7 +55,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
@ -86,7 +86,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:
@ -94,8 +94,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:
@ -146,7 +146,7 @@ def process_batch(p, input_dir, output_dir, inpaint_mask_dir, args, to_scale=Fal
return batch_results
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):
def img2img(id_task: str, request: gr.Request, 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, 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, *args):
override_settings = create_override_settings_dict(override_settings_texts)
is_batch = mode == 5
@ -175,9 +175,8 @@ def img2img(id_task: str, mode: int, prompt: str, negative_prompt: str, prompt_s
image = None
mask = None
# Use the EXIF orientation of photos taken by smartphones.
if image is not None:
image = ImageOps.exif_transpose(image)
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"
@ -194,10 +193,8 @@ def img2img(id_task: str, mode: int, prompt: str, negative_prompt: str, prompt_s
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,

View File

@ -8,7 +8,7 @@ import sys
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
@ -83,7 +83,7 @@ def image_from_url_text(filedata):
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:
@ -95,7 +95,7 @@ def image_from_url_text(filedata):
filedata = filedata[len("data:image/png;base64,"):]
filedata = base64.decodebytes(filedata.encode('utf-8'))
image = Image.open(io.BytesIO(filedata))
image = images.read(io.BytesIO(filedata))
return image
@ -265,17 +265,6 @@ Steps: 20, Sampler: Euler a, CFG scale: 7, Seed: 965400086, Size: 512x512, Model
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] == '"':
@ -290,6 +279,26 @@ Steps: 20, Sampler: Euler a, CFG scale: 7, Seed: 965400086, Size: 512x512, Model
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"
@ -305,6 +314,9 @@ Steps: 20, Sampler: Euler a, CFG scale: 7, Seed: 965400086, Size: 512x512, Model
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"
@ -356,9 +368,15 @@ Steps: 20, Sampler: Euler a, CFG scale: 7, Seed: 965400086, Size: 512x512, Model
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:
@ -456,7 +474,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:
@ -470,7 +488,11 @@ def connect_paste(button, paste_fields, input_comp, override_settings_component,
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)

View File

@ -5,6 +5,8 @@ import re
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

View File

@ -51,6 +51,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()
@ -109,7 +110,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")
@ -139,7 +140,7 @@ def initialize_rest(*, reload_script_modules=False):
"""
Accesses shared.sd_model property to load model.
After it's available, if it has been loaded before this access by some extension,
its optimization may be None because the list of optimizaers has neet been filled
its optimization may be None because the list of optimizers has not been filled
by that time, so we apply optimization again.
"""
from modules import devices

View File

@ -24,6 +24,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():
"""

View File

@ -55,7 +55,7 @@ and delete current Python and "venv" folder in WebUI's directory.
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.
""")

View File

@ -12,7 +12,7 @@ log = logging.getLogger(__name__)
# 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"):

View File

@ -110,7 +110,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,10 +129,10 @@ 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 ""
)

View File

@ -341,7 +341,7 @@ class DDPM(pl.LightningModule):
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 @@ class LatentDiffusion(DDPM):
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 @@ class LatentDiffusion(DDPM):
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 @@ class LatentDiffusion(DDPM):
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)

View File

@ -240,6 +240,9 @@ class Options:
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:

View File

@ -32,6 +32,6 @@ models_path = 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')

View File

@ -17,10 +17,10 @@ def run_postprocessing(extras_mode, image, image_folder, input_dir, output_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:
@ -56,7 +56,7 @@ def run_postprocessing(extras_mode, image, image_folder, input_dir, output_dir,
if isinstance(image_placeholder, str):
try:
image_data = Image.open(image_placeholder)
image_data = images.read(image_placeholder)
except Exception:
continue
else:
@ -66,7 +66,7 @@ def run_postprocessing(extras_mode, image, image_folder, input_dir, output_dir,
if parameters:
existing_pnginfo["parameters"] = parameters
initial_pp = scripts_postprocessing.PostprocessedImage(image_data.convert("RGB"))
initial_pp = scripts_postprocessing.PostprocessedImage(image_data if image_data.mode in ("RGBA", "RGB") else image_data.convert("RGB"))
scripts.scripts_postproc.run(initial_pp, args)
@ -122,8 +122,6 @@ def run_postprocessing(extras_mode, image, image_folder, input_dir, output_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 +131,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,

View File

@ -152,6 +152,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
@ -607,7 +608,7 @@ class Processed:
"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)
@ -703,7 +704,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:
@ -714,6 +761,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)
@ -721,6 +771,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],
@ -750,10 +801,19 @@ def create_infotext(p, all_prompts, all_seeds, all_subseeds, comments=None, iter
"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()
@ -896,22 +956,22 @@ 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}"
@ -1106,6 +1166,7 @@ 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
@ -1194,11 +1255,21 @@ class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing):
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
if tuple(self.hr_negative_prompt) != tuple(self.negative_prompt):
self.extra_generation_params["Hires negative prompt"] = self.hr_negative_prompt
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 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:

View File

@ -26,6 +26,13 @@ class ScriptStripComments(scripts.Script):
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:

View File

@ -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

View File

@ -34,7 +34,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."""
@ -48,7 +48,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."""

View File

@ -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=[],
@ -141,14 +240,18 @@ callback_map = dict(
callbacks_before_token_counter=[],
)
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))
@ -157,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:
@ -165,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:
@ -175,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:
@ -185,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:
@ -193,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:
@ -201,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:
@ -209,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:
@ -217,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:
@ -225,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:
@ -233,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:
@ -241,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:
@ -249,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:
@ -257,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:
@ -265,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:
@ -273,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:
@ -281,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:
@ -289,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:
@ -299,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:
@ -311,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:
@ -321,20 +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 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'
@ -343,6 +439,9 @@ 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):
@ -351,24 +450,24 @@ def remove_callbacks_for_function(callback_func):
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:
@ -378,71 +477,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.
@ -451,61 +550,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)
add_callback(callback_map['callbacks_before_token_counter'], callback, name=name, category='before_token_counter')

View File

@ -2,6 +2,10 @@ import os
import importlib.util
from modules import errors
import sys
loaded_scripts = {}
def load_module(path):
@ -9,6 +13,11 @@ def load_module(path):
module = importlib.util.module_from_spec(module_spec)
module_spec.loader.exec_module(module)
loaded_scripts[path] = module
module_name, _ = os.path.splitext(os.path.basename(path))
sys.modules["scripts." + module_name] = module
return module

View File

@ -7,7 +7,9 @@ from dataclasses import dataclass
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()
@ -92,7 +94,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()."""
def title(self):
"""this function should return the title of the script. This is what will be displayed in the dropdown menu."""
@ -109,7 +111,7 @@ class Script:
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
@ -138,7 +140,6 @@ class Script:
"""
pass
def before_process(self, p, *args):
"""
This function is called very early during processing begins for AlwaysVisible scripts.
@ -351,6 +352,9 @@ class ScriptBuiltinUI(Script):
return f'{tabname}{item_id}'
def show(self, is_img2img):
return AlwaysVisible
current_basedir = paths.script_path
@ -369,29 +373,6 @@ scripts_data = []
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:
@ -562,6 +543,25 @@ class ScriptRunner:
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"""
@ -600,6 +600,8 @@ class ScriptRunner:
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):
@ -737,12 +739,17 @@ class ScriptRunner:
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])
@ -769,8 +776,42 @@ class ScriptRunner:
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)
@ -778,7 +819,7 @@ class ScriptRunner:
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)
@ -786,7 +827,7 @@ class ScriptRunner:
errors.report(f"Error running process: {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)
@ -794,7 +835,7 @@ class ScriptRunner:
errors.report(f"Error running before_process_batch: {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)
@ -802,7 +843,7 @@ class ScriptRunner:
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)
@ -810,7 +851,7 @@ class ScriptRunner:
errors.report(f"Error running process_batch: {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)
@ -818,7 +859,7 @@ class ScriptRunner:
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)
@ -826,7 +867,7 @@ class ScriptRunner:
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)
@ -834,7 +875,7 @@ class ScriptRunner:
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)
@ -842,7 +883,7 @@ class ScriptRunner:
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)
@ -850,7 +891,7 @@ class ScriptRunner:
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)
@ -858,7 +899,7 @@ class ScriptRunner:
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)
@ -866,7 +907,7 @@ class ScriptRunner:
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)
@ -880,7 +921,7 @@ class ScriptRunner:
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:
@ -893,7 +934,7 @@ class ScriptRunner:
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:
@ -921,7 +962,7 @@ class ScriptRunner:
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)
@ -929,7 +970,7 @@ class ScriptRunner:
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

View File

@ -143,6 +143,7 @@ class ScriptPostprocessingRunner:
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 @@ class ScriptPostprocessingRunner:
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 = []

View File

@ -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 @@ class EmphasisOriginal(Emphasis):
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)

View File

@ -23,7 +23,7 @@ class PromptChunk:
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."""
@ -66,7 +66,7 @@ class FrozenCLIPEmbedderWithCustomWordsBase(torch.nn.Module):
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 +136,7 @@ class FrozenCLIPEmbedderWithCustomWordsBase(torch.nn.Module):
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,7 +206,7 @@ class FrozenCLIPEmbedderWithCustomWordsBase(torch.nn.Module):
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"
"""
@ -230,7 +230,7 @@ class FrozenCLIPEmbedderWithCustomWordsBase(torch.nn.Module):
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)

View File

@ -1,5 +1,5 @@
import collections
import os.path
import os
import sys
import threading
@ -7,7 +7,6 @@ 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
@ -151,7 +150,7 @@ def list_models():
if shared.cmd_opts.no_download_sd_model or cmd_ckpt != shared.sd_model_file or os.path.exists(cmd_ckpt):
model_url = None
else:
model_url = "https://huggingface.co/runwayml/stable-diffusion-v1-5/resolve/main/v1-5-pruned-emaonly.safetensors"
model_url = f"{shared.hf_endpoint}/runwayml/stable-diffusion-v1-5/resolve/main/v1-5-pruned-emaonly.safetensors"
model_list = modelloader.load_models(model_path=model_path, model_url=model_url, command_path=shared.cmd_opts.ckpt_dir, ext_filter=[".ckpt", ".safetensors"], download_name="v1-5-pruned-emaonly.safetensors", ext_blacklist=[".vae.ckpt", ".vae.safetensors"])
@ -508,7 +507,7 @@ def enable_midas_autodownload():
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)
@ -784,9 +783,16 @@ def reuse_model_from_already_loaded(sd_model, checkpoint_info, timer):
If it is loaded, returns that (moving it to GPU if necessary, and moving the currently loadded model to CPU if necessary).
If not, returns the model that can be used to load weights from checkpoint_info's file.
If no such model exists, returns None.
Additionaly deletes loaded models that are over the limit set in settings (sd_checkpoints_limit).
Additionally deletes loaded models that are over the limit set in settings (sd_checkpoints_limit).
"""
if sd_model is not None and sd_model.sd_checkpoint_info.filename == checkpoint_info.filename:
return sd_model
if shared.opts.sd_checkpoints_keep_in_cpu:
send_model_to_cpu(sd_model)
timer.record("send model to cpu")
already_loaded = None
for i in reversed(range(len(model_data.loaded_sd_models))):
loaded_model = model_data.loaded_sd_models[i]
@ -796,14 +802,10 @@ def reuse_model_from_already_loaded(sd_model, checkpoint_info, timer):
if len(model_data.loaded_sd_models) > shared.opts.sd_checkpoints_limit > 0:
print(f"Unloading model {len(model_data.loaded_sd_models)} over the limit of {shared.opts.sd_checkpoints_limit}: {loaded_model.sd_checkpoint_info.title}")
model_data.loaded_sd_models.pop()
del model_data.loaded_sd_models[i]
send_model_to_trash(loaded_model)
timer.record("send model to trash")
if shared.opts.sd_checkpoints_keep_in_cpu:
send_model_to_cpu(sd_model)
timer.record("send model to cpu")
if already_loaded is not None:
send_model_to_device(already_loaded)
timer.record("send model to device")

View File

@ -13,8 +13,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

View File

@ -1,7 +1,12 @@
from modules import sd_samplers_kdiffusion, sd_samplers_timesteps, sd_samplers_lcm, shared
from __future__ import annotations
import functools
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
samples_to_image_grid = sd_samplers_common.samples_to_image_grid
sample_to_image = sd_samplers_common.sample_to_image
all_samplers = [
*sd_samplers_kdiffusion.samplers_data_k_diffusion,
@ -10,8 +15,8 @@ all_samplers = [
]
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 = {}
@ -57,4 +62,64 @@ 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):
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 sampler.options.get('scheduler', None) == found_scheduler.name:
found_scheduler = sd_schedulers.schedulers[0]
return sampler.name, found_scheduler.label
set_samplers()

View File

@ -152,7 +152,7 @@ class CFGDenoiser(torch.nn.Module):
if state.interrupted or state.skipped:
raise sd_samplers_common.InterruptedException
if sd_samplers_common.apply_refiner(self):
if sd_samplers_common.apply_refiner(self, sigma):
cond = self.sampler.sampler_extra_args['cond']
uncond = self.sampler.sampler_extra_args['uncond']

View File

@ -155,8 +155,19 @@ def replace_torchsde_browinan():
replace_torchsde_browinan()
def apply_refiner(cfg_denoiser):
completed_ratio = cfg_denoiser.step / cfg_denoiser.total_steps
def apply_refiner(cfg_denoiser, sigma=None):
if opts.refiner_switch_by_sample_steps or sigma is None:
completed_ratio = cfg_denoiser.step / cfg_denoiser.total_steps
cfg_denoiser.p.extra_generation_params["Refiner switch by sampling steps"] = True
else:
# torch.max(sigma) only to handle rare case where we might have different sigmas in the same batch
try:
timestep = torch.argmin(torch.abs(cfg_denoiser.inner_model.sigmas - torch.max(sigma)))
except AttributeError: # for samplers that don't use sigmas (DDIM) sigma is actually the timestep
timestep = torch.max(sigma).to(dtype=int)
completed_ratio = (999 - timestep) / 1000
refiner_switch_at = cfg_denoiser.p.refiner_switch_at
refiner_checkpoint_info = cfg_denoiser.p.refiner_checkpoint_info

View File

@ -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
from modules.sd_samplers_cfg_denoiser import CFGDenoiser # noqa: F401
from modules.script_callbacks import ExtraNoiseParams, extra_noise_callback
@ -9,32 +9,20 @@ from modules.shared import opts
import modules.shared as shared
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}),
]
@ -58,12 +46,7 @@ sampler_extra_params = {
}
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):
@ -96,42 +79,43 @@ class KDiffusionSampler(sd_samplers_common.Sampler):
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,
}
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}
sigmas_func = k_diffusion_scheduler[opts.k_sched_type]
p.extra_generation_params["Schedule type"] = opts.k_sched_type
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 != m_sigma_min and opts.sigma_min != 0:
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)
sigmas = scheduler.function(n=steps, **sigmas_kwargs, device=shared.device)
if discard_next_to_last_sigma:
sigmas = torch.cat([sigmas[:-2], sigmas[-1:]])

43
modules/sd_schedulers.py Normal file
View File

@ -0,0 +1,43 @@
import dataclasses
import torch
import k_diffusion
@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)
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)
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"]),
]
schedulers_map = {**{x.name: x for x in schedulers}, **{x.label: x for x in schedulers}}

View File

@ -6,6 +6,10 @@ import gradio as gr
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 @@ styles_filename = cmd_opts.styles_file = cmd_opts.styles_file if len(cmd_opts.st
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 @@ hypernetworks = {}
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 repsponsible for those settings"""
tab_names = []
@ -65,9 +69,9 @@ progress_print_out = sys.stdout
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 @@ list_checkpoint_tiles = shared_items.list_checkpoint_tiles
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')

View File

@ -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("<div class='info'>Default order: <ol>" + "".join(f"<li>{html.escape(x.name)}</li>\n" for x in callbacks) + "</ol></div>")
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

View File

@ -19,7 +19,9 @@ restricted_opts = {
"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")
@ -101,6 +103,7 @@ options_templates.update(options_section(('upscaling', "Upscaling", "postprocess
"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"), {
@ -213,7 +216,7 @@ options_templates.update(options_section(('optimizations', "Optimizations", "sd"
"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."),
}))
@ -227,7 +230,8 @@ options_templates.update(options_section(('compatibility', "Compatibility", "sd"
"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 +261,9 @@ options_templates.update(options_section(('extra_networks', "Extra Networks", "s
"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 +317,8 @@ options_templates.update(options_section(('ui', "User interface", "ui"), {
"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."),
}))
@ -362,13 +370,12 @@ options_templates.update(options_section(('sampler-params', "Sampler parameters"
'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"),
@ -378,6 +385,7 @@ options_templates.update(options_section(('sampler-params', "Sampler parameters"
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"),

View File

@ -157,10 +157,12 @@ class State:
self.current_image_sampling_step = self.sampling_step
except Exception:
# when switching models during genration, VAE would be on CPU, so creating an image will fail.
# when switching models during generation, VAE would be on CPU, so creating an image will fail.
# we silently ignore this error
errors.record_exception()
def assign_current_image(self, image):
if shared.opts.live_previews_image_format == 'jpeg' and image.mode == 'RGBA':
image = image.convert('RGB')
self.current_image = image
self.id_live_preview += 1

View File

@ -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

View File

@ -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 @@ class Settings:
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

View File

@ -2,7 +2,6 @@ import os
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 @@ from random import shuffle, choices
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 @@ class PersonalizedBase(Dataset):
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():

View File

@ -193,11 +193,11 @@ if __name__ == '__main__':
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

View File

@ -172,7 +172,7 @@ class EmbeddingDatabase:
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")

View File

@ -11,7 +11,7 @@ from PIL import Image
import gradio as gr
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:
@ -24,10 +24,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,
@ -40,6 +38,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,

View File

@ -12,7 +12,7 @@ import numpy as np
from PIL import Image, PngImagePlugin # noqa: F401
from modules.call_queue import wrap_gradio_gpu_call, wrap_queued_call, wrap_gradio_call
from modules import gradio_extensons # noqa: F401
from modules import gradio_extensons, 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
@ -229,19 +229,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)}
@ -269,6 +256,9 @@ 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)
@ -292,9 +282,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):
@ -335,10 +322,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):
@ -393,8 +381,6 @@ def create_ui():
toprow.prompt,
toprow.negative_prompt,
toprow.ui_styles.dropdown,
steps,
sampler_name,
batch_count,
batch_size,
cfg_scale,
@ -409,6 +395,7 @@ def create_ui():
hr_resize_y,
hr_checkpoint_name,
hr_sampler_name,
hr_scheduler,
hr_prompt,
hr_negative_prompt,
override_settings,
@ -458,8 +445,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"),
@ -473,8 +458,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()),
@ -485,11 +471,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,
@ -620,9 +608,6 @@ def create_ui():
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):
@ -751,8 +736,6 @@ def create_ui():
inpaint_color_sketch_orig,
init_img_inpaint,
init_mask_inpaint,
steps,
sampler_name,
mask_blur,
mask_alpha,
inpainting_fill,
@ -837,6 +820,8 @@ def create_ui():
**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])
@ -845,8 +830,6 @@ def create_ui():
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"),
@ -1116,7 +1099,6 @@ def create_ui():
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 = [

View File

@ -3,13 +3,10 @@ import dataclasses
import json
import html
import os
import platform
import sys
import gradio as gr
import subprocess as sp
from modules import call_queue, shared, ui_tempdir
from modules import call_queue, shared, ui_tempdir, util
from modules.infotext_utils import image_from_url_text
import modules.images
from modules.ui_components import ToolButton
@ -105,7 +102,7 @@ def save_files(js_data, images, do_make_zip, index):
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
# updating headers and padding with delimiters where needed
if os.path.exists(logfile_path):
update_logfile(logfile_path, fields)
@ -176,31 +173,7 @@ def create_output_panel(tabname, outdir, toprow=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:

View File

@ -88,7 +88,7 @@ class DropdownEditable(FormComponent, gr.Dropdown):
class InputAccordion(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.
"""
global_index = 0

View File

@ -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.

View File

@ -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 gradio as gr
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 @@ class ExtraNetworksPage:
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 @@ class ExtraNetworksPage:
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 @@ class ExtraNetworksPage:
return f"<ul class='tree-list tree-list--tree'>{res}</ul>"
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"""
<button class='lg secondary gradio-button custom-button{" search-all" if subdir == "" else ""}' onclick='extraNetworksSearchButton("{tabname}", "{self.extra_networks_tabname}", event)'>
{html.escape(subdir if subdir != "" else "all")}
</button>
""" 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 @@ class ExtraNetworksPage:
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"<li>{x}</li>" 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 @@ class ExtraNetworksPage:
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 @@ class ExtraNetworksPage:
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).
@ -693,7 +777,7 @@ def create_ui(interface: gr.Blocks, unrelated_tabs, tabname):
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 +787,7 @@ def create_ui(interface: gr.Blocks, unrelated_tabs, tabname):
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

View File

@ -133,8 +133,10 @@ class UserMetadataEditor:
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,7 +187,8 @@ class UserMetadataEditor:
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):
@ -200,6 +203,3 @@ class UserMetadataEditor:
inputs=[self.edit_name_input],
outputs=[]
)

View File

@ -104,6 +104,8 @@ class UiLoadsave:
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')

View File

@ -12,7 +12,7 @@ def create_ui():
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 = gr.Image(label="Source", source="upload", interactive=True, type="pil", elem_id="extras_image", image_mode="RGBA")
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")

View File

@ -67,7 +67,7 @@ class UiPromptStyles:
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():

View File

@ -1,7 +1,8 @@
import gradio as gr
from modules import ui_common, shared, script_callbacks, scripts, sd_models, sysinfo, timer
from modules import ui_common, shared, script_callbacks, scripts, sd_models, sysinfo, timer, shared_items
from modules.call_queue import wrap_gradio_call
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 @@ class UiSettings:
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 @@ class UiSettings:
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:

View File

@ -20,7 +20,7 @@ class Upscaler:
filter = None
model = None
user_path = None
scalers: []
scalers: list
tile = True
def __init__(self, create_dirs=False):
@ -60,6 +60,9 @@ class Upscaler:
if img.width >= dest_w and img.height >= dest_h:
break
if shared.state.interrupted:
break
shape = (img.width, img.height)
img = self.do_upscale(img, selected_model)

View File

@ -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])

View File

@ -81,6 +81,17 @@ class MassFileListerCachedDir:
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 @@ class MassFileLister:
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 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
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(["wsl-open", path])
else:
subprocess.Popen(["xdg-open", path])

View File

@ -2,6 +2,8 @@
target-version = "py39"
[tool.ruff.lint]
extend-select = [
"B",
"C",
@ -25,10 +27,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"]

Some files were not shown because too many files have changed in this diff Show More