Compare commits

...
Author SHA1 Message Date
Connum 1495a7460b refactored image paste handling to fill unset images successively, then replace last existing image (fixes #981) 2022-09-26 18:12:55 +02:00
tateisuandAUTOMATIC1111 78d6aef302 use strftime. update hints.js 2022-09-26 16:24:21 +03:00
tateisuandAUTOMATIC1111 b39f06cea7 add [datetime] to image file name pattern 2022-09-26 16:24:21 +03:00
DepFAandAUTOMATIC1111 e61da308ba Disable sentry logging 2022-09-26 16:19:47 +03:00
DepFAandAUTOMATIC1111 b8a8f749ef Update webui.bat 2022-09-26 16:19:47 +03:00
LiamandAUTOMATIC1111 db4ab47f09 copy over fields when sending to img2img: https://github.com/AUTOMATIC1111/stable-diffusion-webui/issues/1029 2022-09-26 08:33:02 +03:00
AUTOMATIC ca3e5519e8 Merge remote-tracking branch 'origin/master' 2022-09-25 19:45:22 +03:00
AUTOMATIC ff107845b4 added hiding items to ui-config.json 2022-09-25 19:43:42 +03:00
EyrieandAUTOMATIC1111 1877a3767e Changed job_id to timestamp 2022-09-25 17:11:07 +03:00
EyrieandAUTOMATIC1111 4c3d4aad60 fix formatting 2022-09-25 17:11:07 +03:00
EyrieandAUTOMATIC1111 e13912dfde fix formatting 2022-09-25 17:11:07 +03:00
EyrieandAUTOMATIC1111 ba34b790d3 Added hints for file & dir patterns 2022-09-25 17:11:07 +03:00
EyrieandAUTOMATIC1111 40166dbf08 Added job_id pattern for directories 2022-09-25 17:11:07 +03:00
AUTOMATIC 073f6eac22 potential fix for embeddings no loading on AMD cards 2022-09-25 15:04:39 +03:00
DepFAandAUTOMATIC1111 50ae19acf6 add custom script source to config path keys 2022-09-25 09:59:31 +03:00
DepFAandAUTOMATIC1111 31a0fbf1ad add attribute to custom script controls 2022-09-25 09:59:31 +03:00
guaneecandAUTOMATIC1111 615b2fc9ce Fix token max length 2022-09-25 09:30:02 +03:00
AUTOMATIC b8eae5de93 Merge remote-tracking branch 'origin/master' 2022-09-25 09:25:47 +03:00
AUTOMATIC 600cc0342d added support for setting hires fix from pasted prompts
added more robust detection of last line with parameters for pasted prompts
2022-09-25 09:25:28 +03:00
DepFAandAUTOMATIC1111 72e2a96284 Correct indent 2022-09-25 08:46:09 +03:00
DepFAandAUTOMATIC1111 ab5a472556 Persist default Number values 2022-09-25 08:46:09 +03:00
DepFAandAUTOMATIC1111 ca13264609 Add attr to skip 2022-09-25 08:46:09 +03:00
DepFAandAUTOMATIC1111 b1320d79b5 Apply to all checkboxes and textboxes 2022-09-25 08:46:09 +03:00
DepFAandAUTOMATIC1111 714dcda068 Add attributes to checkboxes for persisting 2022-09-25 08:46:09 +03:00
achaiahandAUTOMATIC1111 5194a58278 removing duplicates
removing duplicated requirements (transformers, diffusers) and reorganizing alphabetically to be able to spot duplicates in the future.
2022-09-25 08:44:54 +03:00
AUTOMATIC 53be15c204 by popular request, moves width/height sliders higher 2022-09-25 08:40:29 +03:00
EyeDeckandAUTOMATIC1111 b5d705e263 Disable 'batch img2img' when launched with --hide-ui-dir-config
Prevent writing to arbitrary directories, as usual
2022-09-24 19:06:36 +03:00
RnDMonkeyandAUTOMATIC1111 a2bea2f97a added [styles] filename pattern 2022-09-24 11:12:58 +03:00
AUTOMATIC 410bef0853 do not hide preview image too early 2022-09-24 09:16:54 +03:00
AUTOMATIC 31ac0baba0 Reformat some changes from the previous commit. 2022-09-24 08:23:01 +03:00
RnDMonkeyandGitHub ed8b8b3b8c Fix to XY_Grid script console progress bar and other progress bar improvements (#890)
Fix to XY_Grid script console progress bar and other progress bar improvements #890
2022-09-24 08:09:59 +03:00
jciancaandAUTOMATIC1111 7aec029423 Update README.md 2022-09-24 07:58:30 +03:00
jciancaandAUTOMATIC1111 e94ec98102 Update README.md 2022-09-24 07:58:30 +03:00
jciancaandAUTOMATIC1111 db341bd0d8 Add files via upload 2022-09-24 07:58:30 +03:00
innovacionesandAUTOMATIC1111 3e4b7fb869 Remove double loading message 2022-09-24 07:58:07 +03:00
ConnumandAUTOMATIC1111 910a3d9427 make callback queue handling reusable and implement onUiTabChange() 2022-09-24 07:44:05 +03:00
Jacob TyndallandAUTOMATIC1111 0f77e1852f Clear no longer needed VRAM during a 'highres fix' generation created during the first sampling before doing the second (img2img) sampling. Makes it possible to use the feature on an 8GB card. 2022-09-24 07:41:59 +03:00
AUTOMATIC 74f940e818 Merge remote-tracking branch 'origin/master' 2022-09-24 00:13:47 +03:00
AUTOMATIC 71cfb9ebac fix for settings sometimes not applying on javascript side 2022-09-24 00:13:32 +03:00
innovacionesandAUTOMATIC1111 b04a36be37 Tweak margin 2022-09-23 23:41:10 +03:00
innovacionesandAUTOMATIC1111 e1d49c5262 Fix border 2022-09-23 23:41:10 +03:00
innovacionesandAUTOMATIC1111 6cd613811b Fix preview images position 2022-09-23 23:41:10 +03:00
DepFAandAUTOMATIC1111 e560eb8b60 Only switch between visible gallery entries
Since the other tabs are kept in the dom now filtering is needed.
2022-09-23 23:38:45 +03:00
AUTOMATIC e12793e937 delete unwanted console.log 2022-09-23 23:34:33 +03:00
AUTOMATIC c8c662901b fix for inpaint at full resolution breaking if you have an NN upscaler. 2022-09-23 23:29:53 +03:00
AUTOMATIC 39ce23f42d add the bitton to paste parameters into UI for txt2img, img2img, and pnginfo tabs
fixed some [send to..] buttons to work properly with all tabs
2022-09-23 22:49:21 +03:00
AUTOMATIC 9c92a1a9aa removed some information that its owner decided he did not want to share 2022-09-23 20:55:54 +03:00
AUTOMATIC 8ffc07b7b1 one change didn't make it into the previous commit 2022-09-23 20:54:17 +03:00
AUTOMATIC 0065327726 upgrade to gradio==3.4b3 t fixthe inpain bugs
rework progressbar/preview to work with new gradio
remove unnecessary create style button
added link to alternative colab
2022-09-23 20:46:02 +03:00
AUTOMATIC 1a0353675d Option to use advanced upscalers with normal img2img 2022-09-23 17:37:47 +03:00
AUTOMATIC 6fa20d51dc prevent saving settings with bogus values 2022-09-23 17:27:30 +03:00
AUTOMATIC1111andGitHub 79e7c39298 Merge pull request #847 from rewbs/rewbs-optionally-save-before-color-correction
Add option to save before color correction. This helps with some posterisation issues in img2img loopback.
2022-09-23 14:41:06 +03:00
SekiandAUTOMATIC1111 03faf20251 add extrastab send to inpaint 2022-09-23 14:19:33 +03:00
SekiandAUTOMATIC1111 8708386609 add extrastab send to img2img 2022-09-23 14:19:33 +03:00
AUTOMATIC d4205e66fa gfpgan: just download the damn model 2022-09-23 10:26:00 +03:00
William MoorehouseandAUTOMATIC1111 d6fd71f36f Update .gitignore to ignore downloaded GFPGAN models 2022-09-23 09:23:16 +03:00
AUTOMATIC 7ef361dfc3 add warning for when user's settings are broken 2022-09-23 09:15:00 +03:00
AUTOMATIC c82e32652e prevent weird behavior when pressing interrupt just as image finishes 2022-09-23 08:48:19 +03:00
AUTOMATIC 02f58cde8d Merge remote-tracking branch 'origin/dfaker-patch-1' 2022-09-23 08:37:40 +03:00
AUTOMATIC 8e8d64199a Merge remote-tracking branch 'origin/dfaker-patch-2' 2022-09-23 08:37:32 +03:00
innovacionesandAUTOMATIC1111 0ce9e05a42 Fix typo 2022-09-23 08:33:08 +03:00
innovacionesandAUTOMATIC1111 6e86fc9fd0 Show interrupt button without progress bar 2022-09-23 08:33:08 +03:00
innovacionesandAUTOMATIC1111 ebf19c1145 Show interrupt button without progress bar 2022-09-23 08:33:08 +03:00
DepFAandAUTOMATIC1111 a2d084a07c Change default bug report template label to bug-report
for later confirmation or denial as an actual bug
2022-09-23 08:30:54 +03:00
DepFAandAUTOMATIC1111 42b7902922 sort JavaScript includes to assure script sequence
Who knows what filesystem they're on
2022-09-23 08:30:32 +03:00
DepFAandGitHub 08e27c3252 sort JavaScript includes to assure script sequence
Who knows what filesystem they're on
2022-09-23 03:05:42 +01:00
DepFAandGitHub ea2426fa61 Change default bug report template label to bug-report
for later confirmation or denial as an actual bug
2022-09-23 02:19:00 +01:00
Robin Fernandes d26d89377b Remove unnecessary duplication 2022-09-23 00:57:42 +00:00
Robin Fernandes 03738668c0 Merge from master 2022-09-23 00:54:32 +00:00
C43H66N12O12S2andAUTOMATIC1111 19fc3e8279 credit parlance-zz as they requested it 2022-09-23 01:42:55 +03:00
ConnumandAUTOMATIC1111 e16b9dc819 resize mask canvases to fit underlying image (fixes #668) 2022-09-22 22:36:47 +03:00
AUTOMATIC 77cf346d93 settings categories split to columns, remade categories 2022-09-22 21:32:44 +03:00
AUTOMATIC 75b90a5e40 emergency fix for the settings screen breaking the program 2022-09-22 20:41:22 +03:00
DepFAandAUTOMATIC1111 6d1c01c955 Add section splitting to settings ui 2022-09-22 20:26:21 +03:00
DepFAandAUTOMATIC1111 dec7584903 Add Section keys and headers to settings 2022-09-22 20:26:21 +03:00
Prof-CheeseandAUTOMATIC1111 ae32b8a53f Fixed directory name generation process.
'Max prompt words' has been added to config and modified to be used in the directory name generation process.
2022-09-22 20:15:37 +03:00
DepFAandAUTOMATIC1111 cae09e1651 Update selectors after ui and core overhaul 2022-09-22 19:29:20 +03:00
Johan Aires RasténandAUTOMATIC1111 a213d3a21c Add option to always log generation info 2022-09-22 16:34:54 +03:00
Robin Fernandes 25eb1e3d90 Add option to save before color correction. Add suffix param when saving files, used for special saves without color correction and face restoration. 2022-09-22 20:54:50 +10:00
AUTOMATIC1111andGitHub 34d5a31ea2 Merge pull request #834 from AUTOMATIC1111/dylancl-patch-2
Update README to link to wiki page for Apple Silicon installs
2022-09-22 13:45:25 +03:00
AUTOMATIC 3582befdcd move Notification.requestPermission() entirely to javascript to possibly fix problem with secure context people are having on non-localhost 2022-09-22 13:15:33 +03:00
Dylan CathelijnandGitHub 699aecd4ae Update README to link to wiki page for Apple Silicon installs 2022-09-22 11:35:12 +02:00
AUTOMATIC b0765a6788 remove labels from output gallery 2022-09-22 12:30:11 +03:00
AUTOMATIC 91bfc71261 A big rework, just what you were secretly hoping for!
SD upscale moved to scripts
Batch processing script removed
Batch processing added to main img2img and now works with scripts
img2img page UI reworked to use tabs
2022-09-22 12:11:48 +03:00
AUTOMATIC1111andGitHub e235d4e691 Merge pull request #824 from dfaker/patch-6
Aspect ratio overlay on img2img
2022-09-22 08:15:35 +03:00
AUTOMATIC1111andGitHub 013e9a4bda Merge branch 'master' into patch-6 2022-09-22 08:15:22 +03:00
innovacionesandAUTOMATIC1111 aa1d1bf44e New position and interrupt integration 2022-09-22 08:12:10 +03:00
innovacionesandAUTOMATIC1111 c9ee98440b New position and interrupt integration 2022-09-22 08:12:10 +03:00
innovacionesandAUTOMATIC1111 8af8fa2055 New position and interrupt integration 2022-09-22 08:12:10 +03:00
innovacionesandAUTOMATIC1111 70e39f6cfe Use generate button as progress bar 2022-09-22 08:12:10 +03:00
innovacionesandAUTOMATIC1111 12622105dc Use generate button as progress bar 2022-09-22 08:12:10 +03:00
innovacionesandAUTOMATIC1111 057206cb73 Update progressbar.js 2022-09-22 08:12:10 +03:00
innovacionesandAUTOMATIC1111 b9aed0c692 Fix progress bar position 2022-09-22 08:12:10 +03:00
innovacionesandAUTOMATIC1111 f183e09fcc Fix progress bar position 2022-09-22 08:12:10 +03:00
AUTOMATIC 3eaae8ae65 reworking #775 2022-09-22 07:59:27 +03:00
AUTOMATIC 81c7961c25 revert transformers version change that prevents the program from starting. 2022-09-22 07:28:19 +03:00
d8ahazardandAUTOMATIC1111 c214c428bc Basic settings for realesrgan model selection.
I don't like that you have to restart the app, but it works.
2022-09-22 07:21:54 +03:00
d8ahazardandAUTOMATIC1111 4f434c8aea Fix/Revert opts name from GAN to ESRGAN 2022-09-22 07:21:54 +03:00
d8ahazardandAUTOMATIC1111 9a93a82b82 Cleanup Imports 2022-09-22 07:21:54 +03:00
d8ahazardandAUTOMATIC1111 3cc68300cb Typo Fix 2022-09-22 07:21:54 +03:00
d8ahazardandAUTOMATIC1111 dd5566814a Add new models, fix shared opts issues
Add General x4x3, GeneralWDN x4x3, and AnimeVideo models from newer ESRGAN releases.

Fix issues caused by renaming ESRGAN_tille values to GAN_tile without using an IDE...
2022-09-22 07:21:54 +03:00
d8ahazardandAUTOMATIC1111 cc287d53a5 Bump Versions 2022-09-22 07:21:54 +03:00
ClashSANandAUTOMATIC1111 ef744b96d6 Update README.md 2022-09-22 07:18:00 +03:00
ClashSANandAUTOMATIC1111 ca22b820f4 Update README.md 2022-09-22 07:18:00 +03:00
ClashSANandAUTOMATIC1111 76c9d1a991 Update README.md 2022-09-22 07:18:00 +03:00
ClashSANandAUTOMATIC1111 be3f080909 Update README.md
Added SwinIR and new features to readme
2022-09-22 07:18:00 +03:00
DepFAandGitHub 2d271c0e62 Add aspect ratio overlay js 2022-09-22 01:51:49 +01:00
DepFAandGitHub 4985d7cd04 Add aspect ratio overlay styles 2022-09-22 01:50:42 +01:00
d8ahazardandAUTOMATIC1111 5a1951f175 Update launch.py 2022-09-21 16:50:30 +03:00
d8ahazardandGitHub 1a1f7e85c7 Add LDSR and "GoLatent?" Upscaling (#763)
* Add LDSR Upscaling
2022-09-21 16:06:37 +03:00
benbotandAUTOMATIC1111 d6e8d85e30 removed TORCH_COMMAND section from webui.sh and
added correct torchvision version to default torch install command
2022-09-21 16:04:13 +03:00
AUTOMATIC 06186582ad prevent outpainting mk2 from breaking on different image resolutions. 2022-09-21 15:40:31 +03:00
AUTOMATIC 1ebb5cc669 update tiling button for imageviewer.js to use an icon in same style as others, add mouseover hints 2022-09-21 14:22:16 +03:00
Adam SnodgrassandAUTOMATIC1111 390ead187c add button to toggle tiling images in the imageviewer modal 2022-09-21 14:09:25 +03:00
Dement242andAUTOMATIC1111 5fb6f39607 Added CodeFormer to Credits
request from Shangchen Zhou
2022-09-21 13:35:35 +03:00
AUTOMATIC 75dd274dd6 prevent seed extras from having effect when extras checkbox is not checked 2022-09-21 13:34:10 +03:00
AUTOMATIC 45c46f4cb3 oops 2022-09-21 10:32:58 +03:00
AUTOMATIC a8ff013963 added --skip-torch-cuda-test to launcher for #746 2022-09-21 10:25:45 +03:00
AUTOMATIC 4b26b50df0 Merge remote-tracking branch 'origin/master' 2022-09-21 09:49:07 +03:00
AUTOMATIC 254da5d127 --opt-split-attention now on by default for torch.cuda, off for others (cpu and MPS; because the option does not work there according to reports) 2022-09-21 09:49:02 +03:00
ConnumandAUTOMATIC1111 b5693699b8 Revert "Reset image input when dragover new image"
This reverts commit 9035afbab5.
2022-09-21 09:13:13 +03:00
ConnumandAUTOMATIC1111 e9ba2d42d6 fix image replacement via clipboard paste or drag and drop on PNG Info tab 2022-09-21 09:13:13 +03:00
C43H66N12O12S2andAUTOMATIC1111 29d6084f47 disallow no tiling as we don't support it 2022-09-21 09:09:39 +03:00
C43H66N12O12S2andAUTOMATIC1111 a5313351b3 Update swinir.py 2022-09-21 09:09:39 +03:00
C43H66N12O12S2andAUTOMATIC1111 403bb02948 breakout tile options 2022-09-21 09:09:39 +03:00
AUTOMATIC 6785fabefb Revert "fix for swininr PR breaking ESRGAN for new users"
This reverts commit 8d1d64f9a2.
2022-09-21 09:09:20 +03:00
AUTOMATIC 8d1d64f9a2 fix for swininr PR breaking ESRGAN for new users 2022-09-21 08:39:47 +03:00
AUTOMATIC 1578859305 fix for too large embeddings causing an error 2022-09-21 00:20:11 +03:00
C43H66N12O12S2andAUTOMATIC1111 e452c961ed Create put_swinir_models_here.txt 2022-09-20 23:31:06 +03:00
C43H66N12O12S2andAUTOMATIC1111 d8ed699839 Update swinir.py 2022-09-20 23:31:06 +03:00
C43H66N12O12S2andAUTOMATIC1111 5f71ecfe6f Update webui.py 2022-09-20 23:31:06 +03:00
C43H66N12O12S2andAUTOMATIC1111 55dc847c48 Update shared.py 2022-09-20 23:31:06 +03:00
C43H66N12O12S2andAUTOMATIC1111 62ca23ac5b add swinir import 2022-09-20 23:31:06 +03:00
C43H66N12O12S2andAUTOMATIC1111 ae8e21abdd hack 2022-09-20 23:31:06 +03:00
C43H66N12O12S2andAUTOMATIC1111 efab42ff66 Rename swinir_arch.py to modules/swinir_arch.py 2022-09-20 23:31:06 +03:00
C43H66N12O12S2andAUTOMATIC1111 948eff4b3c make swinir actually useful 2022-09-20 23:31:06 +03:00
C43H66N12O12S2andAUTOMATIC1111 7267b7d2d9 extremely basic and incomplete swinir implementation 2022-09-20 23:31:06 +03:00
truftyandAUTOMATIC1111 9035afbab5 Reset image input when dragover new image 2022-09-20 23:30:36 +03:00
AUTOMATIC 19a75d38d7 added --use-textbox-seed option to make long seeds possible from web ui 2022-09-20 20:10:01 +03:00
AUTOMATIC 06cd206107 Enable neural network upscalers for highres. fix 2022-09-20 19:32:26 +03:00
AUTOMATIC 2c9777fcc7 Merge remote-tracking branch 'origin/master' 2022-09-20 19:07:18 +03:00
AUTOMATIC b9674e2900 add extra generation params 2022-09-20 19:07:09 +03:00
Rahul KolasseriandAUTOMATIC1111 c2775528c9 added __name__ == __main__ to launch.py so it can be imported without launching the webUI 2022-09-20 17:23:26 +03:00
orionaskatuandAUTOMATIC1111 f2ad00bc8e Remove default git variables to prevent issues 2022-09-20 17:23:01 +03:00
orionaskatuandAUTOMATIC1111 d0a12763fe Comment variables with default values 2022-09-20 17:23:01 +03:00
AUTOMATIC 90401d96a6 fix a off by one error with embedding at the start of the sentence 2022-09-20 12:12:31 +03:00
AUTOMATIC 54a097a818 Merge remote-tracking branch 'origin/master' 2022-09-20 09:53:37 +03:00
AUTOMATIC ab38392119 add the part that was missing for word textual inversion checksums 2022-09-20 09:53:29 +03:00
Dave SescleiferandAUTOMATIC1111 d7f36dac21 Send a browser notification when the images are ready 2022-09-20 09:52:52 +03:00
MillyandAUTOMATIC1111 a8a75ec43a Fixed filename part sanitizing
The following file patterns are invalid on Windows.
- Begin or end with the ASCII Space (0x20)
- End with the ASCII Period (0x2E)

For example, if `[prompt_spaces]` is specified as the directory name pattern, sometimes file saving may fail.
2022-09-20 09:46:44 +03:00
olivierandAUTOMATIC1111 06e076e48a Keep legacy behaviour wrt reuse of filename during batch processes 2022-09-20 07:44:19 +03:00
olivierandAUTOMATIC1111 5fe2c75da0 Support reusing filename stems during batch processing in Extras tab 2022-09-20 07:44:19 +03:00
C43H66N12O12S2andAUTOMATIC1111 53651696dc avoid exception if empty 2022-09-19 22:49:53 +03:00
C43H66N12O12S2andAUTOMATIC1111 942acd4e12 Update README.md 2022-09-19 22:49:53 +03:00
C43H66N12O12S2andAUTOMATIC1111 acbe44b835 automatically match any version gfpgan 2022-09-19 22:49:53 +03:00
truftyandAUTOMATIC1111 6486ec9a56 Automatically show PNGinfo when uploading image 2022-09-19 22:49:05 +03:00
DepFAandAUTOMATIC1111 dd911a47b3 lightbox image scaling fix 2022-09-19 19:14:33 +03:00
AUTOMATIC 01c93a8400 use model's dtype for #707 2022-09-19 18:39:21 +03:00
C43H66N12O12S2andAUTOMATIC1111 cf28445f10 fix cpu usage 2022-09-19 18:36:32 +03:00
AUTOMATIC 422d17487f [BUG] HighRes fix Feature Breaks Scripts #706 2022-09-19 18:23:51 +03:00
AUTOMATIC 5b6dea5698 do not show denoising strength when highres fix is not enabled 2022-09-19 17:41:46 +03:00
AUTOMATIC 028fc25f28 fix the issue with incorrect js options on page load
add a setting for lightbox max size images
use setting for lightbox max size images late to let user change it and see results
2022-09-19 17:16:04 +03:00
44 changed files with 2679 additions and 671 deletions
+1 -1
View File
@@ -2,7 +2,7 @@
name: Bug report
about: Create a report to help us improve
title: ''
labels: bug
labels: bug-report
assignees: ''
---
+2
View File
@@ -6,6 +6,7 @@ __pycache__
/model.ckpt
/models/*.ckpt
/GFPGANv1.3.pth
/gfpgan/weights/*.pth
/ui-config.json
/outputs
/config.json
@@ -18,3 +19,4 @@ __pycache__
/webui-user.sh
/interrogate
/user.css
/.idea
+19 -3
View File
@@ -1,7 +1,7 @@
# Stable Diffusion web UI
A browser interface based on Gradio library for Stable Diffusion.
![](screenshot.png)
![](txt2img_Screenshot.png)
## Features
[Detailed feature showcase with images](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Features):
@@ -20,6 +20,8 @@ A browser interface based on Gradio library for Stable Diffusion.
- CodeFormer, face restoration tool as an alternative to GFPGAN
- RealESRGAN, neural network upscaler
- ESRGAN, neural network with a lot of third party models
- SwinIR, neural network upscaler
- LDSR, Latent diffusion super resolution upscaling
- Resizing aspect ratio options
- Sampling method selection
- Interrupt processing at any time
@@ -41,18 +43,25 @@ A browser interface based on Gradio library for Stable Diffusion.
- Seed resizing
- CLIP interrogator
- Prompt Editing
- Batch Processing
- Img2img Alternative
- Highres Fix
- LDSR Upscaling
## Installation and Running
Make sure the required [dependencies](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Dependencies) are met and follow the instructions available for both [NVidia](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Install-and-Run-on-NVidia-GPUs) (recommended) and [AMD](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Install-and-Run-on-AMD-GPUs) GPUs.
Alternatively, use [Google Colab](https://colab.research.google.com/drive/1Iy-xW9t1-OQWhb0hNxueGij8phCyluOh).
Alternatively, use Google Colab:
- [Colab, maintained by Akaibu](https://colab.research.google.com/drive/1kw3egmSn-KgWsikYvOMjJkVDsPLjEMzl)
- [Colab, original by me, outdated](https://colab.research.google.com/drive/1Iy-xW9t1-OQWhb0hNxueGij8phCyluOh).
### Automatic Installation on Windows
1. Install [Python 3.10.6](https://www.python.org/downloads/windows/), checking "Add Python to PATH"
2. Install [git](https://git-scm.com/download/win).
3. Download the stable-diffusion-webui repository, for example by running `git clone https://github.com/AUTOMATIC1111/stable-diffusion-webui.git`.
4. Place `model.ckpt` in the `models` directory (see [dependencies](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Dependencies) for where to get it).
5. _*(Optional)*_ Place `GFPGANv1.3.pth` in the base directory, alongside `webui.py` (see [dependencies](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Dependencies) for where to get it).
5. _*(Optional)*_ Place `GFPGANv1.4.pth` in the base directory, alongside `webui.py` (see [dependencies](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Dependencies) for where to get it).
6. Run `webui-user.bat` from Windows Explorer as normal, non-administrator, user.
### Automatic Installation on Linux
@@ -70,6 +79,10 @@ sudo pacman -S wget git python3
bash <(wget -qO- https://raw.githubusercontent.com/AUTOMATIC1111/stable-diffusion-webui/master/webui.sh)
```
### Installation on Apple Silicon
Find the instructions [here](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Installation-on-Apple-Silicon).
## Documentation
The documentation was moved from this README over to the project's [wiki](https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki).
@@ -77,7 +90,10 @@ The documentation was moved from this README over to the project's [wiki](https:
- Stable Diffusion - https://github.com/CompVis/stable-diffusion, https://github.com/CompVis/taming-transformers
- k-diffusion - https://github.com/crowsonkb/k-diffusion.git
- GFPGAN - https://github.com/TencentARC/GFPGAN.git
- CodeFormer - https://github.com/sczhou/CodeFormer
- ESRGAN - https://github.com/xinntao/ESRGAN
- SwinIR - https://github.com/JingyunLiang/SwinIR
- LDSR - https://github.com/Hafiidz/latent-diffusion
- Ideas for optimizations - https://github.com/basujindal/stable-diffusion
- Doggettx - Cross Attention layer optimization - https://github.com/Doggettx/stable-diffusion, original idea for prompt editing.
- Idea for SD upscale - https://github.com/jquesnelle/txt2imghd
+1
View File
@@ -0,0 +1 @@
+119
View File
@@ -0,0 +1,119 @@
let currentWidth = null;
let currentHeight = null;
let arFrameTimeout = setTimeout(function(){},0);
function dimensionChange(e,dimname){
if(dimname == 'Width'){
currentWidth = e.target.value*1.0
}
if(dimname == 'Height'){
currentHeight = e.target.value*1.0
}
var inImg2img = Boolean(gradioApp().querySelector("button.rounded-t-lg.border-gray-200"))
if(!inImg2img){
return;
}
var img2imgMode = gradioApp().querySelector('#mode_img2img.tabs > div > button.rounded-t-lg.border-gray-200')
if(img2imgMode){
img2imgMode=img2imgMode.innerText
}else{
return;
}
var redrawImage = gradioApp().querySelector('div[data-testid=image] img');
var inpaintImage = gradioApp().querySelector('#img2maskimg div[data-testid=image] img')
var targetElement = null;
if(img2imgMode=='img2img' && redrawImage){
targetElement = redrawImage;
}else if(img2imgMode=='Inpaint' && inpaintImage){
targetElement = inpaintImage;
}
if(targetElement){
var arPreviewRect = gradioApp().querySelector('#imageARPreview');
if(!arPreviewRect){
arPreviewRect = document.createElement('div')
arPreviewRect.id = "imageARPreview";
gradioApp().getRootNode().appendChild(arPreviewRect)
}
var viewportOffset = targetElement.getBoundingClientRect();
viewportscale = Math.min( targetElement.clientWidth/targetElement.naturalWidth, targetElement.clientHeight/targetElement.naturalHeight )
scaledx = targetElement.naturalWidth*viewportscale
scaledy = targetElement.naturalHeight*viewportscale
cleintRectTop = (viewportOffset.top+window.scrollY)
cleintRectLeft = (viewportOffset.left+window.scrollX)
cleintRectCentreY = cleintRectTop + (targetElement.clientHeight/2)
cleintRectCentreX = cleintRectLeft + (targetElement.clientWidth/2)
viewRectTop = cleintRectCentreY-(scaledy/2)
viewRectLeft = cleintRectCentreX-(scaledx/2)
arRectWidth = scaledx
arRectHeight = scaledy
arscale = Math.min( arRectWidth/currentWidth, arRectHeight/currentHeight )
arscaledx = currentWidth*arscale
arscaledy = currentHeight*arscale
arRectTop = cleintRectCentreY-(arscaledy/2)
arRectLeft = cleintRectCentreX-(arscaledx/2)
arRectWidth = arscaledx
arRectHeight = arscaledy
arPreviewRect.style.top = arRectTop+'px';
arPreviewRect.style.left = arRectLeft+'px';
arPreviewRect.style.width = arRectWidth+'px';
arPreviewRect.style.height = arRectHeight+'px';
clearTimeout(arFrameTimeout);
arFrameTimeout = setTimeout(function(){
arPreviewRect.style.display = 'none';
},2000);
arPreviewRect.style.display = 'block';
}
}
onUiUpdate(function(){
var arPreviewRect = gradioApp().querySelector('#imageARPreview');
if(arPreviewRect){
arPreviewRect.style.display = 'none';
}
var inImg2img = Boolean(gradioApp().querySelector("button.rounded-t-lg.border-gray-200"))
if(inImg2img){
let inputs = gradioApp().querySelectorAll('input');
inputs.forEach(function(e){
let parentLabel = e.parentElement.querySelector('label')
if(parentLabel && parentLabel.innerText){
if(!e.classList.contains('scrollwatch')){
if(parentLabel.innerText == 'Width' || parentLabel.innerText == 'Height'){
e.addEventListener('input', function(e){dimensionChange(e,parentLabel.innerText)} )
e.classList.add('scrollwatch')
}
if(parentLabel.innerText == 'Width'){
currentWidth = e.value*1.0
}
if(parentLabel.innerText == 'Height'){
currentHeight = e.value*1.0
}
}
}
})
}
});
+38 -11
View File
@@ -10,13 +10,34 @@ function dropReplaceImage( imgWrap, files ) {
}
imgWrap.querySelector('.modify-upload button + button, .touch-none + div button + button')?.click();
window.requestAnimationFrame( () => {
const callback = () => {
const fileInput = imgWrap.querySelector('input[type="file"]');
if ( fileInput ) {
fileInput.files = files;
fileInput.dispatchEvent(new Event('change'));
}
});
};
if ( imgWrap.closest('#pnginfo_image') ) {
// special treatment for PNG Info tab, wait for fetch request to finish
const oldFetch = window.fetch;
window.fetch = async (input, options) => {
const response = await oldFetch(input, options);
if ( 'api/predict/' === input ) {
const content = await response.text();
window.fetch = oldFetch;
window.requestAnimationFrame( () => callback() );
return new Response(content, {
status: response.status,
statusText: response.statusText,
headers: response.headers
})
}
return response;
};
} else {
window.requestAnimationFrame( () => callback() );
}
}
window.document.addEventListener('dragover', e => {
@@ -47,13 +68,19 @@ window.addEventListener('paste', e => {
if ( ! isValidImageList( files ) ) {
return;
}
[...gradioApp().querySelectorAll('input[type=file][accept="image/x-png,image/gif,image/jpeg"]')]
.filter(input => !input.matches('.\\!hidden input[type=file]'))
.forEach(input => {
input.files = files;
input.dispatchEvent(new Event('change'))
});
[...gradioApp().querySelectorAll('[data-testid="image"]')]
.filter(imgWrap => !imgWrap.closest('.\\!hidden'))
.forEach(imgWrap => dropReplaceImage( imgWrap, files ));
const visibleImageFields = [...gradioApp().querySelectorAll('[data-testid="image"]')]
.filter(el => uiElementIsVisible(el));
if ( ! visibleImageFields.length ) {
return;
}
const firstFreeImageField = visibleImageFields
.filter(el => el.querySelector('input[type=file]'))?.[0];
dropReplaceImage(
firstFreeImageField ?
firstFreeImageField :
visibleImageFields[visibleImageFields.length - 1]
, files );
});
+5 -5
View File
@@ -13,6 +13,8 @@ titles = {
"Seed": "A value that determines the output of random number generator - if you create an image with same parameters and seed as another image, you'll get the same result",
"\u{1f3b2}\ufe0f": "Set seed to -1, which will cause a new random number to be used every time",
"\u267b\ufe0f": "Reuse seed from last generation, mostly useful if it was randomed",
"\u{1f3a8}": "Add a random artist to the prompt.",
"\u2199\ufe0f": "Read generation parameters from prompt into user interface.",
"Inpaint a part of image": "Draw a mask over an image, and the script will regenerate the masked area with content according to prompt",
"SD upscale": "Upscale image normally, split result into tiles, improve each tile using img2img, merge whole image back",
@@ -48,8 +50,6 @@ titles = {
"Tiling": "Produce an image that can be tiled.",
"Tile overlap": "For SD upscale, how much overlap in pixels should there be between tiles. Tiles overlap so that when they are merged back into one picture, there is no clearly visible seam.",
"Roll": "Add a random artist to the prompt.",
"Variation seed": "Seed of a different picture to be mixed into the generation.",
"Variation strength": "How strong of a variation to produce. At 0, there will be no effect. At 1, you will get the complete picture with variation seed (except for ancestral samplers, where you will just get something).",
"Resize seed from height": "Make an attempt to produce a picture similar to what would have been produced with same seed at specified resolution",
@@ -57,13 +57,13 @@ titles = {
"Interrogate": "Reconstruct prompt from existing image and put it into the prompt field.",
"Images filename pattern": "Use following tags to define how filenames for images are chosen: [steps], [cfg], [prompt], [prompt_spaces], [width], [height], [sampler], [seed], [model_hash], [prompt_words], [date]; leave empty for default.",
"Directory name pattern": "Use following tags to define how subdirectories for images and grids are chosen: [steps], [cfg], [prompt], [prompt_spaces], [width], [height], [sampler], [seed], [model_hash], [prompt_words], [date]; leave empty for default.",
"Images filename pattern": "Use following tags to define how filenames for images are chosen: [steps], [cfg], [prompt], [prompt_spaces], [width], [height], [styles], [sampler], [seed], [model_hash], [prompt_words], [date], [datetime], [job_timestamp]; leave empty for default.",
"Directory name pattern": "Use following tags to define how subdirectories for images and grids are chosen: [steps], [cfg], [prompt], [prompt_spaces], [width], [height], [styles], [sampler], [seed], [model_hash], [prompt_words], [date], [datetime], [job_timestamp]; leave empty for default.",
"Max prompt words": "Set the maximum number of words to be used in the [prompt_words] option; ATTENTION: If the words are too long, they may exceed the maximum length of the file path that the system can handle",
"Loopback": "Process an image, use it as an input, repeat.",
"Loops": "How many times to repeat processing an image and using it as input for the next iteration",
"Style 1": "Style to apply; styles have components for both positive and negative prompts and apply to both",
"Style 2": "Style to apply; styles have components for both positive and negative prompts and apply to both",
"Apply style": "Insert selected styles into prompt fields",
+45
View File
@@ -0,0 +1,45 @@
/**
* temporary fix for https://github.com/AUTOMATIC1111/stable-diffusion-webui/issues/668
* @see https://github.com/gradio-app/gradio/issues/1721
*/
window.addEventListener( 'resize', () => imageMaskResize());
function imageMaskResize() {
const canvases = gradioApp().querySelectorAll('#img2maskimg .touch-none canvas');
if ( ! canvases.length ) {
canvases_fixed = false;
window.removeEventListener( 'resize', imageMaskResize );
return;
}
const wrapper = canvases[0].closest('.touch-none');
const previewImage = wrapper.previousElementSibling;
if ( ! previewImage.complete ) {
previewImage.addEventListener( 'load', () => imageMaskResize());
return;
}
const w = previewImage.width;
const h = previewImage.height;
const nw = previewImage.naturalWidth;
const nh = previewImage.naturalHeight;
const portrait = nh > nw;
const factor = portrait;
const wW = Math.min(w, portrait ? h/nh*nw : w/nw*nw);
const wH = Math.min(h, portrait ? h/nh*nh : w/nw*nh);
wrapper.style.width = `${wW}px`;
wrapper.style.height = `${wH}px`;
wrapper.style.left = `${(w-wW)/2}px`;
wrapper.style.top = `${(h-wH)/2}px`;
canvases.forEach( c => {
c.style.width = c.style.height = '';
c.style.maxWidth = '100%';
c.style.maxHeight = '100%';
c.style.objectFit = 'contain';
});
}
onUiUpdate(() => imageMaskResize());
+72 -30
View File
@@ -5,9 +5,13 @@ function closeModal() {
}
function showModal(event) {
var source = event.target || event.srcElement;
gradioApp().getElementById("modalImage").src = source.src
var lb = gradioApp().getElementById("lightboxModal")
const source = event.target || event.srcElement;
const modalImage = gradioApp().getElementById("modalImage")
const lb = gradioApp().getElementById("lightboxModal")
modalImage.src = source.src
if (modalImage.style.display === 'none') {
lb.style.setProperty('background-image', 'url(' + source.src + ')');
}
lb.style.display = "block";
lb.focus()
event.stopPropagation()
@@ -18,10 +22,22 @@ function negmod(n, m) {
}
function modalImageSwitch(offset){
var galleryButtons = gradioApp().querySelectorAll(".gallery-item.transition-all")
var allgalleryButtons = gradioApp().querySelectorAll(".gallery-item.transition-all")
var galleryButtons = []
allgalleryButtons.forEach(function(elem){
if(elem.parentElement.offsetParent){
galleryButtons.push(elem);
}
})
if(galleryButtons.length>1){
var currentButton = gradioApp().querySelector(".gallery-item.transition-all.\\!ring-2")
var allcurrentButtons = gradioApp().querySelectorAll(".gallery-item.transition-all.\\!ring-2")
var currentButton = null
allcurrentButtons.forEach(function(elem){
if(elem.parentElement.offsetParent){
currentButton = elem;
}
})
var result = -1
galleryButtons.forEach(function(v, i){ if(v==currentButton) { result = i } })
@@ -29,8 +45,13 @@ function modalImageSwitch(offset){
if(result != -1){
nextButton = galleryButtons[negmod((result+offset),galleryButtons.length)]
nextButton.click()
gradioApp().getElementById("modalImage").src = nextButton.children[0].src
setTimeout( function(){gradioApp().getElementById("lightboxModal").focus()},10)
const modalImage = gradioApp().getElementById("modalImage");
const modal = gradioApp().getElementById("lightboxModal");
modalImage.src = nextButton.children[0].src;
if (modalImage.style.display === 'none') {
modal.style.setProperty('background-image', `url(${modalImage.src})`)
}
setTimeout( function(){modal.focus()},10)
}
}
}
@@ -71,6 +92,7 @@ function showGalleryImage(){
e.addEventListener('click', function (evt) {
if(!opts.js_modal_lightbox) return;
modalZoomSet(gradioApp().getElementById('modalImage'), opts.js_modal_lightbox_initialy_zoomed)
showModal(evt)
},true);
}
@@ -80,13 +102,32 @@ function showGalleryImage(){
}, 100);
}
function modalZoomToggle(event){
modalImage = gradioApp().getElementById("modalImage");
if( !modalImage.classList.contains('modalImageFullscreen') ){
function modalZoomSet(modalImage, enable){
if( enable ){
modalImage.classList.add('modalImageFullscreen');
}else{
} else{
modalImage.classList.remove('modalImageFullscreen');
}
}
function modalZoomToggle(event){
modalImage = gradioApp().getElementById("modalImage");
modalZoomSet(modalImage, !modalImage.classList.contains('modalImageFullscreen'))
event.stopPropagation()
}
function modalTileImageToggle(event){
const modalImage = gradioApp().getElementById("modalImage");
const modal = gradioApp().getElementById("lightboxModal");
const isTiling = modalImage.style.display === 'none';
if (isTiling) {
modalImage.style.display = 'block';
modal.style.setProperty('background-image', 'none')
} else {
modalImage.style.display = 'none';
modal.style.setProperty('background-image', `url(${modalImage.src})`)
}
event.stopPropagation()
}
@@ -101,18 +142,6 @@ onUiUpdate(function(){
if(fullImg_preview != null){
fullImg_preview.forEach(galleryImageHandler);
}
if(Object.keys(opts).length == 0) return;
if(!window.lightbox_settings_applied){
window.lightbox_settings_applied = true;
if(opts.js_modal_lightbox_initialy_zoomed){
var imageModal = gradioApp().getElementById('modalImage');
if(imageModal){
imageModal.className = 'modalImageFullscreen';
}
}
}
})
document.addEventListener("DOMContentLoaded", function() {
@@ -123,17 +152,30 @@ document.addEventListener("DOMContentLoaded", function() {
modal.tabIndex=0
modal.addEventListener('keydown', modalKeyHandler, true)
const modalClose = document.createElement('span')
modalClose.className = 'modalClose cursor';
modalClose.innerHTML = '&times;'
modalClose.onclick = closeModal;
modal.appendChild(modalClose)
const modalControls = document.createElement('div')
modalControls.className = 'modalControls gradio-container';
modal.append(modalControls);
const modalZoom = document.createElement('span')
modalZoom.className = 'modalZoom cursor';
modalZoom.innerHTML = '&#10529;'
modalZoom.addEventListener('click', modalZoomToggle, true)
modal.appendChild(modalZoom)
modalZoom.title = "Toggle zoomed view";
modalControls.appendChild(modalZoom)
const modalTileImage = document.createElement('span')
modalTileImage.className = 'modalTileImage cursor';
modalTileImage.innerHTML = '&#8862;'
modalTileImage.addEventListener('click', modalTileImageToggle, true)
modalTileImage.title = "Preview tiling";
modalControls.appendChild(modalTileImage)
const modalClose = document.createElement('span')
modalClose.className = 'modalClose cursor';
modalClose.innerHTML = '&times;'
modalClose.onclick = closeModal;
modalClose.title = "Close image viewer";
modalControls.appendChild(modalClose)
const modalImage = document.createElement('img')
modalImage.id = 'modalImage';
+46
View File
@@ -0,0 +1,46 @@
// Monitors the gallery and sends a browser notification when the leading image is new.
let lastHeadImg = null;
notificationButton = null
onUiUpdate(function(){
if(notificationButton == null){
notificationButton = gradioApp().getElementById('request_notifications')
if(notificationButton != null){
notificationButton.addEventListener('click', function (evt) {
Notification.requestPermission();
},true);
}
}
const galleryPreviews = gradioApp().querySelectorAll('img.h-full.w-full.overflow-hidden');
if (galleryPreviews == null) return;
const headImg = galleryPreviews[0]?.src;
if (headImg == null || headImg == lastHeadImg) return;
lastHeadImg = headImg;
if (document.hasFocus()) return;
// Multiple copies of the images are in the DOM when one is selected. Dedup with a Set to get the real number generated.
const imgs = new Set(Array.from(galleryPreviews).map(img => img.src));
const notification = new Notification(
'Stable Diffusion',
{
body: `Generated ${imgs.size > 1 ? imgs.size - 1 : 1} image${imgs.size > 1 ? 's' : ''}`,
icon: headImg,
image: headImg,
}
);
notification.onclick = function(_){
parent.focus();
this.close();
};
});
+35 -29
View File
@@ -1,46 +1,52 @@
// code related to showing and updating progressbar shown as the image is being made
global_progressbars = {}
global_progressbar = null
onUiUpdate(function(){
progressbar = gradioApp().getElementById('progressbar')
if(progressbar!= null && progressbar != global_progressbar){
global_progressbar = progressbar
function check_progressbar(id_part, id_progressbar, id_progressbar_span, id_interrupt, id_preview, id_gallery){
var progressbar = gradioApp().getElementById(id_progressbar)
var interrupt = gradioApp().getElementById(id_interrupt)
if(progressbar!= null && progressbar != global_progressbars[id_progressbar]){
global_progressbars[id_progressbar] = progressbar
var mutationObserver = new MutationObserver(function(m){
txt2img_preview = gradioApp().getElementById('txt2img_preview')
txt2img_gallery = gradioApp().getElementById('txt2img_gallery')
preview = gradioApp().getElementById(id_preview)
gallery = gradioApp().getElementById(id_gallery)
img2img_preview = gradioApp().getElementById('img2img_preview')
img2img_gallery = gradioApp().getElementById('img2img_gallery')
if(preview != null && gallery != null){
preview.style.width = gallery.clientWidth + "px"
preview.style.height = gallery.clientHeight + "px"
if(txt2img_preview != null && txt2img_gallery != null){
txt2img_preview.style.width = txt2img_gallery.clientWidth + "px"
txt2img_preview.style.height = txt2img_gallery.clientHeight + "px"
var progressDiv = gradioApp().querySelectorAll('#' + id_progressbar_span).length > 0;
if(!progressDiv){
interrupt.style.display = "none"
}
}
if(img2img_preview != null && img2img_gallery != null){
img2img_preview.style.width = img2img_gallery.clientWidth + "px"
img2img_preview.style.height = img2img_gallery.clientHeight + "px"
}
window.setTimeout(requestMoreProgress, 500)
window.setTimeout(function(){ requestMoreProgress(id_part, id_progressbar_span, id_interrupt) }, 500)
});
mutationObserver.observe( progressbar, { childList:true, subtree:true })
}
}
onUiUpdate(function(){
check_progressbar('txt2img', 'txt2img_progressbar', 'txt2img_progress_span', 'txt2img_interrupt', 'txt2img_preview', 'txt2img_gallery')
check_progressbar('img2img', 'img2img_progressbar', 'img2img_progress_span', 'img2img_interrupt', 'img2img_preview', 'img2img_gallery')
})
function requestMoreProgress(){
btn = gradioApp().getElementById("check_progress");
function requestMoreProgress(id_part, id_progressbar_span, id_interrupt){
btn = gradioApp().getElementById(id_part+"_check_progress");
if(btn==null) return;
btn.click();
var progressDiv = gradioApp().querySelectorAll('#' + id_progressbar_span).length > 0;
var interrupt = gradioApp().getElementById(id_interrupt)
if(progressDiv && interrupt){
interrupt.style.display = "block"
}
}
function requestProgress(id_part){
btn = gradioApp().getElementById(id_part+"_check_progress_initial");
if(btn==null) return;
btn.click();
}
function requestProgress(){
btn = gradioApp().getElementById("check_progress_initial");
if(btn==null) return;
btn.click();
}
+93 -7
View File
@@ -25,23 +25,90 @@ function extract_image_from_gallery(gallery){
return gallery[index];
}
function extract_image_from_gallery_img2img(gallery){
function args_to_array(args){
res = []
for(var i=0;i<args.length;i++){
res.push(args[i])
}
return res
}
function switch_to_txt2img(){
gradioApp().querySelectorAll('button')[0].click();
return args_to_array(arguments);
}
function switch_to_img2img_img2img(){
gradioApp().querySelectorAll('button')[1].click();
gradioApp().getElementById('mode_img2img').querySelectorAll('button')[0].click();
return args_to_array(arguments);
}
function switch_to_img2img_inpaint(){
gradioApp().querySelectorAll('button')[1].click();
gradioApp().getElementById('mode_img2img').querySelectorAll('button')[1].click();
return args_to_array(arguments);
}
function switch_to_extras(){
gradioApp().querySelectorAll('button')[2].click();
return args_to_array(arguments);
}
function extract_image_from_gallery_txt2img(gallery){
switch_to_txt2img()
return extract_image_from_gallery(gallery);
}
function extract_image_from_gallery_img2img(gallery){
switch_to_img2img_img2img()
return extract_image_from_gallery(gallery);
}
function extract_image_from_gallery_inpaint(gallery){
switch_to_img2img_inpaint()
return extract_image_from_gallery(gallery);
}
function extract_image_from_gallery_extras(gallery){
gradioApp().querySelectorAll('button')[2].click();
switch_to_extras()
return extract_image_from_gallery(gallery);
}
function submit(){
// this calls a function from progressbar.js
requestProgress()
function get_tab_index(tabId){
var res = 0
gradioApp().getElementById(tabId).querySelector('div').querySelectorAll('button').forEach(function(button, i){
if(button.className.indexOf('bg-white') != -1)
res = i
})
return res
}
function create_tab_index_args(tabId, args){
var res = []
for(var i=0; i<args.length; i++){
res.push(args[i])
}
res[0] = get_tab_index(tabId)
return res
}
function get_extras_tab_index(){
return create_tab_index_args('mode_extras', arguments)
}
function create_submit_args(args){
res = []
for(var i=0;i<arguments.length;i++){
res.push(arguments[i])
for(var i=0;i<args.length;i++){
res.push(args[i])
}
// As it is currently, txt2img and img2img send back the previous output args (txt2img_gallery, generation_info, html_info) whenever you generate a new image.
@@ -55,11 +122,30 @@ function submit(){
return res
}
function submit(){
requestProgress('txt2img')
return create_submit_args(arguments)
}
function submit_img2img(){
requestProgress('img2img')
res = create_submit_args(arguments)
res[0] = get_tab_index('mode_img2img')
return res
}
function ask_for_style_name(_, prompt_text, negative_prompt_text) {
name_ = prompt('Style name:')
return name_ === null ? [null, null, null]: [name_, prompt_text, negative_prompt_text]
}
opts = {}
function apply_settings(jsdata){
console.log(jsdata)
+22 -7
View File
@@ -11,7 +11,7 @@ dir_tmp = "tmp"
python = sys.executable
git = os.environ.get('GIT', "git")
torch_command = os.environ.get('TORCH_COMMAND', "pip install torch==1.12.1+cu113 --extra-index-url https://download.pytorch.org/whl/cu113")
torch_command = os.environ.get('TORCH_COMMAND', "pip install torch==1.12.1+cu113 torchvision==0.13.1+cu113 --extra-index-url https://download.pytorch.org/whl/cu113")
requirements_file = os.environ.get('REQS_FILE', "requirements_versions.txt")
commandline_args = os.environ.get('COMMANDLINE_ARGS', "")
@@ -22,6 +22,17 @@ stable_diffusion_commit_hash = os.environ.get('STABLE_DIFFUSION_COMMIT_HASH', "6
taming_transformers_commit_hash = os.environ.get('TAMING_TRANSFORMERS_COMMIT_HASH', "24268930bf1dce879235a7fddd0b2355b84d7ea6")
codeformer_commit_hash = os.environ.get('CODEFORMER_COMMIT_HASH', "c5b4593074ba6214284d6acd5f1719b6c5d739af")
blip_commit_hash = os.environ.get('BLIP_COMMIT_HASH', "48211a1594f1321b00f14c9f7a5b4813144b2fb9")
ldsr_commit_hash = os.environ.get('LDSR_COMMIT_HASH',"abf33e7002d59d9085081bce93ec798dcabd49af")
args = shlex.split(commandline_args)
def extract_arg(args, name):
return [x for x in args if x != name], name in args
args, skip_torch_cuda_test = extract_arg(args, '--skip-torch-cuda-test')
def repo_dir(name):
return os.path.join(dir_repos, name)
@@ -92,10 +103,12 @@ except Exception:
print(f"Python {sys.version}")
print(f"Commit hash: {commit}")
if not is_installed("torch"):
run(f'"{python}" -m {torch_command}', "Installing torch", "Couldn't install torch")
run_python("import torch; assert torch.cuda.is_available(), 'Torch is not able to use GPU'")
if not is_installed("torch") or not is_installed("torchvision"):
run(f'"{python}" -m {torch_command}', "Installing torch and torchvision", "Couldn't install torch")
if not skip_torch_cuda_test:
run_python("import torch; assert torch.cuda.is_available(), 'Torch is not able to use GPU; add --skip-torch-cuda-test to COMMANDINE_ARGS variable to disable this check'")
if not is_installed("k_diffusion.sampling"):
run_pip(f"install {k_diffusion_package}", "k-diffusion")
@@ -109,13 +122,15 @@ git_clone("https://github.com/CompVis/stable-diffusion.git", repo_dir('stable-di
git_clone("https://github.com/CompVis/taming-transformers.git", repo_dir('taming-transformers'), "Taming Transformers", taming_transformers_commit_hash)
git_clone("https://github.com/sczhou/CodeFormer.git", repo_dir('CodeFormer'), "CodeFormer", codeformer_commit_hash)
git_clone("https://github.com/salesforce/BLIP.git", repo_dir('BLIP'), "BLIP", blip_commit_hash)
# Using my repo until my changes are merged, as this makes interfacing with our version of SD-web a lot easier
git_clone("https://github.com/Hafiidz/latent-diffusion", repo_dir('latent-diffusion'), "LDSR", ldsr_commit_hash)
if not is_installed("lpips"):
run_pip(f"install -r {os.path.join(repo_dir('CodeFormer'), 'requirements.txt')}", "requirements for CodeFormer")
run_pip(f"install -r {requirements_file}", "requirements for Web UI")
sys.argv += shlex.split(commandline_args)
sys.argv += args
def start_webui():
@@ -123,5 +138,5 @@ def start_webui():
import webui
webui.webui()
start_webui()
if __name__ == "__main__":
start_webui()
+21 -16
View File
@@ -1,3 +1,5 @@
import os
import numpy as np
from PIL import Image
@@ -13,31 +15,27 @@ import piexif.helper
cached_images = {}
def run_extras(image, image_folder, gfpgan_visibility, codeformer_visibility, codeformer_weight, upscaling_resize, extras_upscaler_1, extras_upscaler_2, extras_upscaler_2_visibility):
def run_extras(extras_mode, image, image_folder, gfpgan_visibility, codeformer_visibility, codeformer_weight, upscaling_resize, extras_upscaler_1, extras_upscaler_2, extras_upscaler_2_visibility):
devices.torch_gc()
imageArr = []
# Also keep track of original file names
imageNameArr = []
if image_folder != None:
if image != None:
print("Batch detected and single image detected, please only use one of the two. Aborting.")
return None
if extras_mode == 1:
#convert file to pillow image
for img in image_folder:
image = Image.fromarray(np.array(Image.open(img)))
imageArr.append(image)
elif image != None:
if image_folder != None:
print("Batch detected and single image detected, please only use one of the two. Aborting.")
return None
else:
imageArr.append(image)
imageNameArr.append(os.path.splitext(img.orig_name)[0])
else:
imageArr.append(image)
imageNameArr.append(None)
outpath = opts.outdir_samples or opts.outdir_extras_samples
outputs = []
for image in imageArr:
for image, image_name in zip(imageArr, imageNameArr):
existing_pnginfo = image.info or {}
image = image.convert("RGB")
@@ -90,7 +88,9 @@ def run_extras(image, image_folder, gfpgan_visibility, codeformer_visibility, co
while len(cached_images) > 2:
del cached_images[next(iter(cached_images.keys()))]
images.save_image(image, path=outpath, basename="", seed=None, prompt=None, extension=opts.samples_format, info=info, short_filename=True, no_prompt=True, grid=False, pnginfo_section_name="extras", existing_info=existing_pnginfo)
images.save_image(image, path=outpath, basename="", seed=None, prompt=None, extension=opts.samples_format, info=info, short_filename=True,
no_prompt=True, grid=False, pnginfo_section_name="extras", existing_info=existing_pnginfo,
forced_filename=image_name if opts.use_original_name_batch else None)
outputs.append(image)
@@ -98,7 +98,11 @@ def run_extras(image, image_folder, gfpgan_visibility, codeformer_visibility, co
def run_pnginfo(image):
if image is None:
return '', '', ''
items = image.info
geninfo = ''
if "exif" in image.info:
exif = piexif.load(image.info["exif"])
@@ -108,13 +112,14 @@ def run_pnginfo(image):
except ValueError:
exif_comment = exif_comment.decode('utf8', errors="ignore")
items['exif comment'] = exif_comment
geninfo = exif_comment
for field in ['jfif', 'jfif_version', 'jfif_unit', 'jfif_density', 'dpi', 'exif',
'loop', 'background', 'timestamp', 'duration']:
items.pop(field, None)
geninfo = items.get('parameters', geninfo)
info = ''
for key, text in items.items():
@@ -129,4 +134,4 @@ def run_pnginfo(image):
message = "Nothing found in the image."
info = f"<div><p>{message}<p></div>"
return '', '', info
return '', geninfo, info
@@ -0,0 +1,92 @@
import re
import gradio as gr
re_param_code = r"\s*([\w ]+):\s*([^,]+)(?:,|$)"
re_param = re.compile(re_param_code)
re_params = re.compile(r"^(?:" + re_param_code + "){3,}$")
re_imagesize = re.compile(r"^(\d+)x(\d+)$")
type_of_gr_update = type(gr.update())
def parse_generation_parameters(x: str):
"""parses generation parameters string, the one you see in text field under the picture in UI:
```
girl with an artist's beret, determined, blue eyes, desert scene, computer monitors, heavy makeup, by Alphonse Mucha and Charlie Bowater, ((eyeshadow)), (coquettish), detailed, intricate
Negative prompt: ugly, fat, obese, chubby, (((deformed))), [blurry], bad anatomy, disfigured, poorly drawn face, mutation, mutated, (extra_limb), (ugly), (poorly drawn hands), messy drawing
Steps: 20, Sampler: Euler a, CFG scale: 7, Seed: 965400086, Size: 512x512, Model hash: 45dee52b
```
returns a dict with field values
"""
res = {}
prompt = ""
negative_prompt = ""
done_with_prompt = False
*lines, lastline = x.strip().split("\n")
if not re_params.match(lastline):
lines.append(lastline)
lastline = ''
for i, line in enumerate(lines):
line = line.strip()
if line.startswith("Negative prompt:"):
done_with_prompt = True
line = line[16:].strip()
if done_with_prompt:
negative_prompt += ("" if negative_prompt == "" else "\n") + line
else:
prompt += ("" if prompt == "" else "\n") + line
if len(prompt) > 0:
res["Prompt"] = prompt
if len(negative_prompt) > 0:
res["Negative prompt"] = negative_prompt
for k, v in re_param.findall(lastline):
m = re_imagesize.match(v)
if m is not None:
res[k+"-1"] = m.group(1)
res[k+"-2"] = m.group(2)
else:
res[k] = v
return res
def connect_paste(button, paste_fields, input_comp, js=None):
def paste_func(prompt):
params = parse_generation_parameters(prompt)
res = []
for output, key in paste_fields:
if callable(key):
v = key(params)
else:
v = params.get(key, None)
if v is None:
res.append(gr.update())
elif isinstance(v, type_of_gr_update):
res.append(v)
else:
try:
valtype = type(output.value)
val = valtype(v)
res.append(gr.update(value=val))
except Exception:
res.append(gr.update())
return res
button.click(
fn=paste_func,
_js=js,
inputs=[input_comp],
outputs=[x[0] for x in paste_fields],
)
+13 -6
View File
@@ -1,6 +1,7 @@
import os
import sys
import traceback
from glob import glob
from modules import shared, devices
from modules.shared import cmd_opts
@@ -11,14 +12,20 @@ import modules.face_restoration
def gfpgan_model_path():
from modules.shared import cmd_opts
filemask = 'GFPGAN*.pth'
if cmd_opts.gfpgan_model is not None:
return cmd_opts.gfpgan_model
places = [script_path, '.', os.path.join(cmd_opts.gfpgan_dir, 'experiments/pretrained_models')]
files = [cmd_opts.gfpgan_model] + [os.path.join(dirname, cmd_opts.gfpgan_model) for dirname in places]
found = [x for x in files if os.path.exists(x)]
if len(found) == 0:
raise Exception("GFPGAN model not found in paths: " + ", ".join(files))
filename = None
for place in places:
filename = next(iter(glob(os.path.join(place, filemask))), None)
if filename is not None:
break
return found[0]
return filename
loaded_gfpgan_model = None
@@ -34,7 +41,7 @@ def gfpgan():
if gfpgan_constructor is None:
return None
model = gfpgan_constructor(model_path=gfpgan_model_path(), upscale=1, arch='clean', channel_multiplier=2, bg_upsampler=None)
model = gfpgan_constructor(model_path=gfpgan_model_path() or 'https://github.com/TencentARC/GFPGAN/releases/download/v1.3.0/GFPGANv1.4.pth', upscale=1, arch='clean', channel_multiplier=2, bg_upsampler=None)
model.gfpgan.to(shared.device)
loaded_gfpgan_model = model
+44 -19
View File
@@ -209,8 +209,16 @@ def draw_prompt_matrix(im, width, height, all_prompts):
def resize_image(resize_mode, im, width, height):
def resize(im, w, h):
if opts.upscaler_for_img2img is None or opts.upscaler_for_img2img == "None" or im.mode == 'L':
return im.resize((w, h), resample=LANCZOS)
upscaler = [x for x in shared.sd_upscalers if x.name == opts.upscaler_for_img2img][0]
return upscaler.upscale(im, w, h)
if resize_mode == 0:
res = im.resize((width, height), resample=LANCZOS)
res = resize(im, width, height)
elif resize_mode == 1:
ratio = width / height
src_ratio = im.width / im.height
@@ -218,9 +226,10 @@ def resize_image(resize_mode, im, width, height):
src_w = width if ratio > src_ratio else im.width * height // im.height
src_h = height if ratio <= src_ratio else im.height * width // im.width
resized = im.resize((src_w, src_h), resample=LANCZOS)
resized = resize(im, src_w, src_h)
res = Image.new("RGB", (width, height))
res.paste(resized, box=(width // 2 - src_w // 2, height // 2 - src_h // 2))
else:
ratio = width / height
src_ratio = im.width / im.height
@@ -228,7 +237,7 @@ def resize_image(resize_mode, im, width, height):
src_w = width if ratio < src_ratio else im.width * height // im.height
src_h = height if ratio >= src_ratio else im.height * width // im.width
resized = im.resize((src_w, src_h), resample=LANCZOS)
resized = resize(im, src_w, src_h)
res = Image.new("RGB", (width, height))
res.paste(resized, box=(width // 2 - src_w // 2, height // 2 - src_h // 2))
@@ -245,37 +254,49 @@ def resize_image(resize_mode, im, width, height):
invalid_filename_chars = '<>:"/\\|?*\n'
invalid_filename_prefix = ' '
invalid_filename_postfix = ' .'
re_nonletters = re.compile(r'[\s'+string.punctuation+']+')
max_filename_part_length = 128
def sanitize_filename_part(text, replace_spaces=True):
if replace_spaces:
text = text.replace(' ', '_')
return text.translate({ord(x): '_' for x in invalid_filename_chars})[:128]
text = text.translate({ord(x): '_' for x in invalid_filename_chars})
text = text.lstrip(invalid_filename_prefix)[:max_filename_part_length]
text = text.rstrip(invalid_filename_postfix)
return text
def apply_filename_pattern(x, p, seed, prompt):
max_prompt_words = opts.directories_max_prompt_words
if seed is not None:
x = x.replace("[seed]", str(seed))
if prompt is not None:
x = x.replace("[prompt]", sanitize_filename_part(prompt)[:128])
x = x.replace("[prompt_spaces]", sanitize_filename_part(prompt, replace_spaces=False)[:128])
x = x.replace("[prompt]", sanitize_filename_part(prompt))
x = x.replace("[prompt_spaces]", sanitize_filename_part(prompt, replace_spaces=False))
if "[prompt_words]" in x:
words = [x for x in re_nonletters.split(prompt or "") if len(x) > 0]
if len(words) == 0:
words = ["empty"]
x = x.replace("[prompt_words]", sanitize_filename_part(" ".join(words[0:max_prompt_words]), replace_spaces=False))
x = x.replace("[prompt_words]", " ".join(words[0:8]).strip())
if p is not None:
x = x.replace("[steps]", str(p.steps))
x = x.replace("[cfg]", str(p.cfg_scale))
x = x.replace("[width]", str(p.width))
x = x.replace("[height]", str(p.height))
x = x.replace("[sampler]", sd_samplers.samplers[p.sampler_index].name)
x = x.replace("[styles]", sanitize_filename_part(", ".join(p.styles), replace_spaces=False))
x = x.replace("[sampler]", sanitize_filename_part(sd_samplers.samplers[p.sampler_index].name, replace_spaces=False))
x = x.replace("[model_hash]", shared.sd_model.sd_model_hash)
x = x.replace("[date]", datetime.date.today().isoformat())
x = x.replace("[datetime]", datetime.datetime.now().strftime("%Y%m%d%H%M%S"))
x = x.replace("[job_timestamp]", shared.state.job_timestamp)
if cmd_opts.hide_ui_dir_config:
x = re.sub(r'^[\\/]+|\.{2,}[\\/]+|[\\/]+\.{2,}', '', x)
@@ -303,7 +324,7 @@ def get_next_sequence_number(path, basename):
return result + 1
def save_image(image, path, basename, seed=None, prompt=None, extension='png', info=None, short_filename=False, no_prompt=False, grid=False, pnginfo_section_name='parameters', p=None, existing_info=None):
def save_image(image, path, basename, seed=None, prompt=None, extension='png', info=None, short_filename=False, no_prompt=False, grid=False, pnginfo_section_name='parameters', p=None, existing_info=None, forced_filename=None, suffix=""):
if short_filename or prompt is None or seed is None:
file_decoration = ""
elif opts.save_to_dirs:
@@ -314,7 +335,7 @@ def save_image(image, path, basename, seed=None, prompt=None, extension='png', i
if file_decoration != "":
file_decoration = "-" + file_decoration.lower()
file_decoration = apply_filename_pattern(file_decoration, p, seed, prompt)
file_decoration = apply_filename_pattern(file_decoration, p, seed, prompt) + suffix
if extension == 'png' and opts.enable_pnginfo and info is not None:
pnginfo = PngImagePlugin.PngInfo()
@@ -335,15 +356,19 @@ def save_image(image, path, basename, seed=None, prompt=None, extension='png', i
os.makedirs(path, exist_ok=True)
basecount = get_next_sequence_number(path, basename)
fullfn = "a.png"
fullfn_without_extension = "a"
for i in range(500):
fn = f"{basecount+i:05}" if basename == '' else f"{basename}-{basecount+i:04}"
fullfn = os.path.join(path, f"{fn}{file_decoration}.{extension}")
fullfn_without_extension = os.path.join(path, f"{fn}{file_decoration}")
if not os.path.exists(fullfn):
break
if forced_filename is None:
basecount = get_next_sequence_number(path, basename)
fullfn = "a.png"
fullfn_without_extension = "a"
for i in range(500):
fn = f"{basecount+i:05}" if basename == '' else f"{basename}-{basecount+i:04}"
fullfn = os.path.join(path, f"{fn}{file_decoration}.{extension}")
fullfn_without_extension = os.path.join(path, f"{fn}{file_decoration}")
if not os.path.exists(fullfn):
break
else:
fullfn = os.path.join(path, f"{forced_filename}.{extension}")
fullfn_without_extension = os.path.join(path, forced_filename)
def exif_bytes():
return piexif.dump({
+56 -67
View File
@@ -1,4 +1,8 @@
import math
import os
import sys
import traceback
import numpy as np
from PIL import Image, ImageOps, ImageChops
@@ -11,9 +15,45 @@ from modules.ui import plaintext_to_html
import modules.images as images
import modules.scripts
def img2img(prompt: str, negative_prompt: str, prompt_style: str, prompt_style2: str, init_img, init_img_with_mask, init_mask, mask_mode, steps: int, sampler_index: int, mask_blur: int, inpainting_fill: int, restore_faces: bool, tiling: bool, mode: int, n_iter: int, batch_size: int, cfg_scale: float, denoising_strength: float, seed: int, subseed: int, subseed_strength: float, seed_resize_from_h: int, seed_resize_from_w: int, height: int, width: int, resize_mode: int, upscaler_index: str, upscale_overlap: int, inpaint_full_res: bool, inpainting_mask_invert: int, *args):
def process_batch(p, input_dir, output_dir, args):
processing.fix_seed(p)
images = [file for file in [os.path.join(input_dir, x) for x in os.listdir(input_dir)] if os.path.isfile(file)]
print(f"Will process {len(images)} images, creating {p.n_iter * p.batch_size} new images for each.")
p.do_not_save_grid = True
p.do_not_save_samples = True
state.job_count = len(images) * p.n_iter
for i, image in enumerate(images):
state.job = f"{i+1} out of {len(images)}"
if state.interrupted:
break
img = Image.open(image)
p.init_images = [img] * p.batch_size
proc = modules.scripts.scripts_img2img.run(p, *args)
if proc is None:
proc = process_images(p)
for n, processed_image in enumerate(proc.images):
filename = os.path.basename(image)
if n > 0:
left, right = os.path.splitext(filename)
filename = f"{left}-{n}{right}"
processed_image.save(os.path.join(output_dir, filename))
def img2img(mode: int, prompt: str, negative_prompt: str, prompt_style: str, prompt_style2: str, init_img, init_img_with_mask, init_img_inpaint, init_mask_inpaint, mask_mode, steps: int, sampler_index: int, mask_blur: int, inpainting_fill: int, restore_faces: bool, tiling: bool, n_iter: int, batch_size: int, cfg_scale: float, denoising_strength: float, seed: int, subseed: int, subseed_strength: float, seed_resize_from_h: int, seed_resize_from_w: int, seed_enable_extras: bool, height: int, width: int, 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, *args):
is_inpaint = mode == 1
is_upscale = mode == 2
is_batch = mode == 2
if is_inpaint:
if mask_mode == 0:
@@ -23,8 +63,8 @@ def img2img(prompt: str, negative_prompt: str, prompt_style: str, prompt_style2:
mask = ImageChops.lighter(alpha_mask, mask.convert('L')).convert('L')
image = image.convert('RGB')
else:
image = init_img
mask = init_mask
image = init_img_inpaint
mask = init_mask_inpaint
else:
image = init_img
mask = None
@@ -43,6 +83,7 @@ def img2img(prompt: str, negative_prompt: str, prompt_style: str, prompt_style2:
subseed_strength=subseed_strength,
seed_resize_from_h=seed_resize_from_h,
seed_resize_from_w=seed_resize_from_w,
seed_enable_extras=seed_enable_extras,
sampler_index=sampler_index,
batch_size=batch_size,
n_iter=n_iter,
@@ -59,80 +100,28 @@ def img2img(prompt: str, negative_prompt: str, prompt_style: str, prompt_style2:
resize_mode=resize_mode,
denoising_strength=denoising_strength,
inpaint_full_res=inpaint_full_res,
inpaint_full_res_padding=inpaint_full_res_padding,
inpainting_mask_invert=inpainting_mask_invert,
)
print(f"\nimg2img: {prompt}", file=shared.progress_print_out)
if is_upscale:
initial_info = None
p.extra_generation_params["Mask blur"] = mask_blur
processing.fix_seed(p)
seed = p.seed
if is_batch:
assert not shared.cmd_opts.hide_ui_dir_config, "Launched with --hide-ui-dir-config, batch img2img disabled"
upscaler = shared.sd_upscalers[upscaler_index]
img = upscaler.upscale(init_img, init_img.width * 2, init_img.height * 2)
devices.torch_gc()
grid = images.split_grid(img, tile_w=width, tile_h=height, overlap=upscale_overlap)
batch_size = p.batch_size
upscale_count = p.n_iter
p.n_iter = 1
p.do_not_save_grid = True
p.do_not_save_samples = True
work = []
for y, h, row in grid.tiles:
for tiledata in row:
work.append(tiledata[2])
batch_count = math.ceil(len(work) / batch_size)
state.job_count = batch_count * upscale_count
print(f"SD upscaling will process a total of {len(work)} images tiled as {len(grid.tiles[0][2])}x{len(grid.tiles)} per upscale in a total of {state.job_count} batches.")
result_images = []
for n in range(upscale_count):
start_seed = seed + n
p.seed = start_seed
work_results = []
for i in range(batch_count):
p.batch_size = batch_size
p.init_images = work[i*batch_size:(i+1)*batch_size]
state.job = f"Batch {i + 1 + n * batch_count} out of {state.job_count}"
processed = process_images(p)
if initial_info is None:
initial_info = processed.info
p.seed = processed.seed + 1
work_results += processed.images
image_index = 0
for y, h, row in grid.tiles:
for tiledata in row:
tiledata[2] = work_results[image_index] if image_index < len(work_results) else Image.new("RGB", (p.width, p.height))
image_index += 1
combined_image = images.combine_grid(grid)
result_images.append(combined_image)
if opts.samples_save:
images.save_image(combined_image, p.outpath_samples, "", start_seed, prompt, opts.samples_format, info=initial_info, p=p)
processed = Processed(p, result_images, seed, initial_info)
process_batch(p, img2img_batch_input_dir, img2img_batch_output_dir, args)
processed = Processed(p, [], p.seed, "")
else:
processed = modules.scripts.scripts_img2img.run(p, *args)
if processed is None:
processed = process_images(p)
shared.total_tqdm.clear()
return processed.images, processed.js(), plaintext_to_html(processed.info)
generation_info_js = processed.js()
if opts.samples_log_stdout:
print(generation_info_js)
return processed.images, generation_info_js, plaintext_to_html(processed.info)
+67
View File
@@ -0,0 +1,67 @@
import os
import sys
import traceback
from collections import namedtuple
from basicsr.utils.download_util import load_file_from_url
import modules.images
from modules import shared
from modules.paths import script_path
LDSRModelInfo = namedtuple("LDSRModelInfo", ["name", "location", "model", "netscale"])
ldsr_models = []
have_ldsr = False
LDSR_obj = None
class UpscalerLDSR(modules.images.Upscaler):
def __init__(self, steps):
self.steps = steps
self.name = "LDSR"
def do_upscale(self, img):
return upscale_with_ldsr(img)
def add_lsdr():
modules.shared.sd_upscalers.append(UpscalerLDSR(100))
def setup_ldsr():
path = modules.paths.paths.get("LDSR", None)
if path is None:
return
global have_ldsr
global LDSR_obj
try:
from LDSR import LDSR
model_url = "https://heibox.uni-heidelberg.de/f/578df07c8fc04ffbadf3/?dl=1"
yaml_url = "https://heibox.uni-heidelberg.de/f/31a76b13ea27482981b4/?dl=1"
repo_path = 'latent-diffusion/experiments/pretrained_models/'
model_path = load_file_from_url(url=model_url, model_dir=os.path.join("repositories", repo_path),
progress=True, file_name="model.chkpt")
yaml_path = load_file_from_url(url=yaml_url, model_dir=os.path.join("repositories", repo_path),
progress=True, file_name="project.yaml")
have_ldsr = True
LDSR_obj = LDSR(model_path, yaml_path)
except Exception:
print("Error importing LDSR:", file=sys.stderr)
print(traceback.format_exc(), file=sys.stderr)
have_ldsr = False
def upscale_with_ldsr(image):
setup_ldsr()
if not have_ldsr or LDSR_obj is None:
return image
ddim_steps = shared.opts.ldsr_steps
pre_scale = shared.opts.ldsr_pre_down
post_scale = shared.opts.ldsr_post_down
image = LDSR_obj.super_resolution(image, ddim_steps, pre_scale, post_scale)
return image
+1
View File
@@ -19,6 +19,7 @@ path_dirs = [
(os.path.join(sd_path, '../taming-transformers'), 'taming', 'Taming Transformers'),
(os.path.join(sd_path, '../CodeFormer'), 'inference_codeformer.py', 'CodeFormer'),
(os.path.join(sd_path, '../BLIP'), 'models/blip.py', 'BLIP'),
(os.path.join(sd_path, '../latent-diffusion'), 'LDSR.py', 'LDSR'),
]
paths = {}
+48 -12
View File
@@ -20,6 +20,7 @@ import modules.shared as shared
import modules.face_restoration
import modules.images as images
import modules.styles
import logging
# some of those options should not be changed at all because they would break the model, so I removed them from options.
@@ -28,11 +29,13 @@ opt_f = 8
def setup_color_correction(image):
logging.info("Calibrating color correction.")
correction_target = cv2.cvtColor(np.asarray(image.copy()), cv2.COLOR_RGB2LAB)
return correction_target
def apply_color_correction(correction, image):
logging.info("Applying color correction.")
image = Image.fromarray(cv2.cvtColor(exposure.match_histograms(
cv2.cvtColor(
np.asarray(image),
@@ -46,7 +49,7 @@ def apply_color_correction(correction, image):
class StableDiffusionProcessing:
def __init__(self, sd_model=None, outpath_samples=None, outpath_grids=None, prompt="", styles=None, seed=-1, subseed=-1, subseed_strength=0, seed_resize_from_h=-1, seed_resize_from_w=-1, sampler_index=0, batch_size=1, n_iter=1, steps=50, cfg_scale=7.0, width=512, height=512, restore_faces=False, tiling=False, do_not_save_samples=False, do_not_save_grid=False, extra_generation_params=None, overlay_images=None, negative_prompt=None):
def __init__(self, sd_model=None, outpath_samples=None, outpath_grids=None, prompt="", styles=None, seed=-1, subseed=-1, subseed_strength=0, seed_resize_from_h=-1, seed_resize_from_w=-1, seed_enable_extras=True, sampler_index=0, batch_size=1, n_iter=1, steps=50, cfg_scale=7.0, width=512, height=512, restore_faces=False, tiling=False, do_not_save_samples=False, do_not_save_grid=False, extra_generation_params=None, overlay_images=None, negative_prompt=None):
self.sd_model = sd_model
self.outpath_samples: str = outpath_samples
self.outpath_grids: str = outpath_grids
@@ -70,12 +73,18 @@ class StableDiffusionProcessing:
self.tiling: bool = tiling
self.do_not_save_samples: bool = do_not_save_samples
self.do_not_save_grid: bool = do_not_save_grid
self.extra_generation_params: dict = extra_generation_params
self.extra_generation_params: dict = extra_generation_params or {}
self.overlay_images = overlay_images
self.paste_to = None
self.color_corrections = None
self.denoising_strength: float = 0
if not seed_enable_extras:
self.subseed = -1
self.subseed_strength = 0
self.seed_resize_from_h = 0
self.seed_resize_from_w = 0
def init(self, all_prompts, all_seeds, all_subseeds):
pass
@@ -225,8 +234,8 @@ def create_random_tensors(shape, seeds, subseeds=None, subseed_strength=0.0, see
def fix_seed(p):
p.seed = int(random.randrange(4294967294)) if p.seed is None or p.seed == -1 else p.seed
p.subseed = int(random.randrange(4294967294)) if p.subseed is None or p.subseed == -1 else p.subseed
p.seed = int(random.randrange(4294967294)) if p.seed is None or p.seed == '' or p.seed == -1 else p.seed
p.subseed = int(random.randrange(4294967294)) if p.subseed is None or p.subseed == '' or p.subseed == -1 else p.subseed
def create_infotext(p, all_prompts, all_seeds, all_subseeds, comments, iteration=0, position_in_batch=0):
@@ -248,8 +257,7 @@ def create_infotext(p, all_prompts, all_seeds, all_subseeds, comments, iteration
"Denoising strength": getattr(p, 'denoising_strength', None),
}
if p.extra_generation_params is not None:
generation_params.update(p.extra_generation_params)
generation_params.update(p.extra_generation_params)
generation_params_text = ", ".join([k if k == v else f'{k}: {v}' for k, v in generation_params.items() if v is not None])
@@ -287,12 +295,12 @@ def process_images(p: StableDiffusionProcessing) -> Processed:
if type(p.seed) == list:
all_seeds = p.seed
else:
all_seeds = [int(p.seed + (x if p.subseed_strength == 0 else 0)) for x in range(len(all_prompts))]
all_seeds = [int(p.seed) + (x if p.subseed_strength == 0 else 0) for x in range(len(all_prompts))]
if type(p.subseed) == list:
all_subseeds = p.subseed
else:
all_subseeds = [int(p.subseed + x) for x in range(len(all_prompts))]
all_subseeds = [int(p.subseed) + x for x in range(len(all_prompts))]
def infotext(iteration=0, position_in_batch=0):
return create_infotext(p, all_prompts, all_seeds, all_subseeds, comments, iteration, position_in_batch)
@@ -352,7 +360,7 @@ def process_images(p: StableDiffusionProcessing) -> Processed:
if p.restore_faces:
if opts.save and not p.do_not_save_samples and opts.save_images_before_face_restoration:
images.save_image(Image.fromarray(x_sample), p.outpath_samples, "", seeds[i], prompts[i], opts.samples_format, info=infotext(n, i), p=p)
images.save_image(Image.fromarray(x_sample), p.outpath_samples, "", seeds[i], prompts[i], opts.samples_format, info=infotext(n, i), p=p, suffix="-before-face-restoration")
devices.torch_gc()
@@ -361,6 +369,8 @@ def process_images(p: StableDiffusionProcessing) -> Processed:
image = Image.fromarray(x_sample)
if p.color_corrections is not None and i < len(p.color_corrections):
if opts.save and not p.do_not_save_samples and opts.save_images_before_color_correction:
images.save_image(image, p.outpath_samples, "", seeds[i], prompts[i], opts.samples_format, info=infotext(n, i), p=p, suffix="-before-color-correction")
image = apply_color_correction(p.color_corrections[i], image)
if p.overlay_images is not None and i < len(p.overlay_images):
@@ -451,13 +461,38 @@ class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing):
samples = torch.nn.functional.interpolate(samples, size=(self.height // opt_f, self.width // opt_f), mode="bilinear")
else:
decoded_samples = self.sd_model.decode_first_stage(samples)
decoded_samples = torch.nn.functional.interpolate(decoded_samples, size=(self.height, self.width), mode="bilinear")
if opts.upscaler_for_img2img is None or opts.upscaler_for_img2img == "None":
decoded_samples = torch.nn.functional.interpolate(decoded_samples, size=(self.height, self.width), mode="bilinear")
else:
lowres_samples = torch.clamp((decoded_samples + 1.0) / 2.0, min=0.0, max=1.0)
batch_images = []
for i, x_sample in enumerate(lowres_samples):
x_sample = 255. * np.moveaxis(x_sample.cpu().numpy(), 0, 2)
x_sample = x_sample.astype(np.uint8)
image = Image.fromarray(x_sample)
upscaler = [x for x in shared.sd_upscalers if x.name == opts.upscaler_for_img2img][0]
image = upscaler.upscale(image, self.width, self.height)
image = np.array(image).astype(np.float32) / 255.0
image = np.moveaxis(image, 2, 0)
batch_images.append(image)
decoded_samples = torch.from_numpy(np.array(batch_images))
decoded_samples = decoded_samples.to(shared.device)
decoded_samples = 2. * decoded_samples - 1.
samples = self.sd_model.get_first_stage_encoding(self.sd_model.encode_first_stage(decoded_samples))
shared.state.nextjob()
self.sampler = samplers[self.sampler_index].constructor(self.sd_model)
noise = create_random_tensors(samples.shape[1:], seeds=seeds, subseeds=subseeds, subseed_strength=subseed_strength, seed_resize_from_h=self.seed_resize_from_h, seed_resize_from_w=self.seed_resize_from_w, p=self)
# GC now before running the next img2img to prevent running out of memory
x = None
devices.torch_gc()
samples = self.sampler.sample_img2img(self, samples, noise, conditioning, unconditional_conditioning, steps=self.steps)
return samples
@@ -466,7 +501,7 @@ class StableDiffusionProcessingTxt2Img(StableDiffusionProcessing):
class StableDiffusionProcessingImg2Img(StableDiffusionProcessing):
sampler = None
def __init__(self, init_images=None, resize_mode=0, denoising_strength=0.75, mask=None, mask_blur=4, inpainting_fill=0, inpaint_full_res=True, inpainting_mask_invert=0, **kwargs):
def __init__(self, init_images=None, resize_mode=0, denoising_strength=0.75, mask=None, mask_blur=4, inpainting_fill=0, inpaint_full_res=True, inpaint_full_res_padding=0, inpainting_mask_invert=0, **kwargs):
super().__init__(**kwargs)
self.init_images = init_images
@@ -480,6 +515,7 @@ class StableDiffusionProcessingImg2Img(StableDiffusionProcessing):
self.mask_blur = mask_blur
self.inpainting_fill = inpainting_fill
self.inpaint_full_res = inpaint_full_res
self.inpaint_full_res_padding = inpaint_full_res_padding
self.inpainting_mask_invert = inpainting_mask_invert
self.mask = None
self.nmask = None
@@ -502,7 +538,7 @@ class StableDiffusionProcessingImg2Img(StableDiffusionProcessing):
if self.inpaint_full_res:
self.mask_for_overlay = self.image_mask
mask = self.image_mask.convert('L')
crop_region = masking.get_crop_region(np.array(mask), opts.upscale_at_full_resolution_padding)
crop_region = masking.get_crop_region(np.array(mask), self.inpaint_full_res_padding)
crop_region = masking.expand_crop_region(crop_region, self.width, self.height, mask.width, mask.height)
x1, y1, x2, y2 = crop_region
+2 -2
View File
@@ -114,7 +114,7 @@ def get_learned_conditioning(prompts, steps):
def reconstruct_cond_batch(c: ScheduledPromptBatch, current_step):
res = torch.zeros(c.shape)
res = torch.zeros(c.shape, device=shared.device, dtype=next(shared.sd_model.parameters()).dtype)
for i, cond_schedule in enumerate(c.schedules):
target_index = 0
for curret_index, (end_at, cond) in enumerate(cond_schedule):
@@ -123,7 +123,7 @@ def reconstruct_cond_batch(c: ScheduledPromptBatch, current_step):
break
res[i] = cond_schedule[target_index].cond
return res.to(shared.device)
return res
+56 -24
View File
@@ -1,17 +1,66 @@
import sys
import traceback
from collections import namedtuple
import numpy as np
from PIL import Image
from realesrgan import RealESRGANer
import modules.images
from modules.shared import cmd_opts, opts
RealesrganModelInfo = namedtuple("RealesrganModelInfo", ["name", "location", "model", "netscale"])
realesrgan_models = []
have_realesrgan = False
RealESRGANer_constructor = None
def get_realesrgan_models():
try:
from basicsr.archs.rrdbnet_arch import RRDBNet
from realesrgan import RealESRGANer
from realesrgan.archs.srvgg_arch import SRVGGNetCompact
models = [
RealesrganModelInfo(
name="Real-ESRGAN General x4x3",
location="https://github.com/xinntao/Real-ESRGAN/releases/download/v0.2.5.0/realesr-general-x4v3.pth",
netscale=4,
model=lambda: SRVGGNetCompact(num_in_ch=3, num_out_ch=3, num_feat=64, num_conv=32, upscale=4, act_type='prelu')
),
RealesrganModelInfo(
name="Real-ESRGAN General WDN x4x3",
location="https://github.com/xinntao/Real-ESRGAN/releases/download/v0.2.5.0/realesr-general-wdn-x4v3.pth",
netscale=4,
model=lambda: SRVGGNetCompact(num_in_ch=3, num_out_ch=3, num_feat=64, num_conv=32, upscale=4, act_type='prelu')
),
RealesrganModelInfo(
name="Real-ESRGAN AnimeVideo",
location="https://github.com/xinntao/Real-ESRGAN/releases/download/v0.2.5.0/realesr-animevideov3.pth",
netscale=4,
model=lambda: SRVGGNetCompact(num_in_ch=3, num_out_ch=3, num_feat=64, num_conv=16, upscale=4, act_type='prelu')
),
RealesrganModelInfo(
name="Real-ESRGAN 4x plus",
location="https://github.com/xinntao/Real-ESRGAN/releases/download/v0.1.0/RealESRGAN_x4plus.pth",
netscale=4,
model=lambda: RRDBNet(num_in_ch=3, num_out_ch=3, num_feat=64, num_block=23, num_grow_ch=32, scale=4)
),
RealesrganModelInfo(
name="Real-ESRGAN 4x plus anime 6B",
location="https://github.com/xinntao/Real-ESRGAN/releases/download/v0.2.2.4/RealESRGAN_x4plus_anime_6B.pth",
netscale=4,
model=lambda: RRDBNet(num_in_ch=3, num_out_ch=3, num_feat=64, num_block=6, num_grow_ch=32, scale=4)
),
RealesrganModelInfo(
name="Real-ESRGAN 2x plus",
location="https://github.com/xinntao/Real-ESRGAN/releases/download/v0.2.1/RealESRGAN_x2plus.pth",
netscale=2,
model=lambda: RRDBNet(num_in_ch=3, num_out_ch=3, num_feat=64, num_block=23, num_grow_ch=32, scale=2)
),
]
return models
except Exception as e:
print("Error makeing Real-ESRGAN midels list:", file=sys.stderr)
print(traceback.format_exc(), file=sys.stderr)
class UpscalerRealESRGAN(modules.images.Upscaler):
@@ -27,35 +76,18 @@ class UpscalerRealESRGAN(modules.images.Upscaler):
def setup_realesrgan():
global realesrgan_models
global have_realesrgan
global RealESRGANer_constructor
try:
from basicsr.archs.rrdbnet_arch import RRDBNet
from realesrgan import RealESRGANer
from realesrgan.archs.srvgg_arch import SRVGGNetCompact
realesrgan_models = [
RealesrganModelInfo(
name="Real-ESRGAN 4x plus",
location="https://github.com/xinntao/Real-ESRGAN/releases/download/v0.1.0/RealESRGAN_x4plus.pth",
netscale=4, model=lambda: RRDBNet(num_in_ch=3, num_out_ch=3, num_feat=64, num_block=23, num_grow_ch=32, scale=4)
),
RealesrganModelInfo(
name="Real-ESRGAN 4x plus anime 6B",
location="https://github.com/xinntao/Real-ESRGAN/releases/download/v0.2.2.4/RealESRGAN_x4plus_anime_6B.pth",
netscale=4, model=lambda: RRDBNet(num_in_ch=3, num_out_ch=3, num_feat=64, num_block=6, num_grow_ch=32, scale=4)
),
RealesrganModelInfo(
name="Real-ESRGAN 2x plus",
location="https://github.com/xinntao/Real-ESRGAN/releases/download/v0.2.1/RealESRGAN_x2plus.pth",
netscale=2, model=lambda: RRDBNet(num_in_ch=3, num_out_ch=3, num_feat=64, num_block=23, num_grow_ch=32, scale=2)
),
]
realesrgan_models = get_realesrgan_models()
have_realesrgan = True
RealESRGANer_constructor = RealESRGANer
for i, model in enumerate(realesrgan_models):
modules.shared.sd_upscalers.append(UpscalerRealESRGAN(model.netscale, i))
if model.name in opts.realesrgan_enabled_models:
modules.shared.sd_upscalers.append(UpscalerRealESRGAN(model.netscale, i))
except Exception:
print("Error importing Real-ESRGAN:", file=sys.stderr)
@@ -66,13 +98,13 @@ def setup_realesrgan():
def upscale_with_realesrgan(image, RealESRGAN_upscaling, RealESRGAN_model_index):
if not have_realesrgan or RealESRGANer_constructor is None:
if not have_realesrgan:
return image
info = realesrgan_models[RealESRGAN_model_index]
model = info.model()
upsampler = RealESRGANer_constructor(
upsampler = RealESRGANer(
scale=info.netscale,
model_path=info.location,
model=model,
+1 -1
View File
@@ -119,6 +119,7 @@ class ScriptRunner:
continue
for control in controls:
control.custom_script_source = os.path.basename(script.filename)
control.visible = False
inputs += controls
@@ -143,7 +144,6 @@ class ScriptRunner:
return inputs
def run(self, p: StableDiffusionProcessing, *args):
script_index = args[0]
+7 -7
View File
@@ -201,7 +201,7 @@ class StableDiffusionModelHijack:
def process_file(path, filename):
name = os.path.splitext(filename)[0]
data = torch.load(path)
data = torch.load(path, map_location="cpu")
# textual inversion embeddings
if 'string_to_param' in data:
@@ -217,8 +217,8 @@ class StableDiffusionModelHijack:
if len(emb.shape) == 1:
emb = emb.unsqueeze(0)
self.word_embeddings[name] = emb.detach()
self.word_embeddings_checksums[name] = f'{const_hash(emb.reshape(-1))&0xffff:04x}'
self.word_embeddings[name] = emb.detach().to(device)
self.word_embeddings_checksums[name] = f'{const_hash(emb.reshape(-1)*100)&0xffff:04x}'
ids = tokenizer([name], add_special_tokens=False)['input_ids'][0]
@@ -245,7 +245,7 @@ class StableDiffusionModelHijack:
if cmd_opts.opt_split_attention_v1:
ldm.modules.attention.CrossAttention.forward = split_cross_attention_forward_v1
elif not cmd_opts.disable_opt_split_attention:
elif not cmd_opts.disable_opt_split_attention and (cmd_opts.opt_split_attention or torch.cuda.is_available()):
ldm.modules.attention.CrossAttention.forward = split_cross_attention_forward
ldm.modules.diffusionmodules.model.nonlinearity = nonlinearity_hijack
ldm.modules.diffusionmodules.model.AttnBlock.forward = cross_attention_attnblock_forward
@@ -300,7 +300,7 @@ class FrozenCLIPEmbedderWithCustomWords(torch.nn.Module):
remade_batch_tokens = []
id_start = self.wrapped.tokenizer.bos_token_id
id_end = self.wrapped.tokenizer.eos_token_id
maxlen = self.wrapped.max_length - 2
maxlen = self.wrapped.max_length
used_custom_terms = []
cache = {}
@@ -400,8 +400,8 @@ class EmbeddingsWithFixes(torch.nn.Module):
for fixes, tensor in zip(batch_fixes, inputs_embeds):
for offset, word in fixes:
emb = self.embeddings.word_embeddings[word]
emb_len = min(tensor.shape[0]-offset, emb.shape[0])
tensor[offset:offset+emb_len] = self.embeddings.word_embeddings[word][0:emb_len]
emb_len = min(tensor.shape[0]-offset-1, emb.shape[0])
tensor[offset+1:offset+1+emb_len] = self.embeddings.word_embeddings[word][0:emb_len]
return inputs_embeds
+160 -71
View File
@@ -2,9 +2,9 @@ import sys
import argparse
import json
import os
import gradio as gr
import tqdm
import datetime
import modules.artists
from modules.paths import script_path, sd_path
@@ -22,7 +22,7 @@ parser.add_argument("--config", type=str, default=os.path.join(sd_path, "configs
parser.add_argument("--ckpt", type=str, default=sd_model_file, help="path to checkpoint of stable diffusion model; this checkpoint will be added to the list of checkpoints and loaded by default if you don't have a checkpoint selected in settings",)
parser.add_argument("--ckpt-dir", type=str, default=os.path.join(script_path, 'models'), help="path to directory with stable diffusion checkpoints",)
parser.add_argument("--gfpgan-dir", type=str, help="GFPGAN directory", default=('./src/gfpgan' if os.path.exists('./src/gfpgan') else './GFPGAN'))
parser.add_argument("--gfpgan-model", type=str, help="GFPGAN model file name", default='GFPGANv1.3.pth')
parser.add_argument("--gfpgan-model", type=str, help="GFPGAN model file name", default=None)
parser.add_argument("--no-half", action='store_true', help="do not switch the model to 16-bit floats")
parser.add_argument("--no-progressbar-hiding", action='store_true', help="do not hide progressbar in gradio UI (we hide it because it slows down ML if you have hardware acceleration in browser)")
parser.add_argument("--max-batch-count", type=int, default=16, help="maximum batch count value for the UI")
@@ -35,8 +35,9 @@ parser.add_argument("--unload-gfpgan", action='store_true', help="does not do an
parser.add_argument("--precision", type=str, help="evaluate at this precision", choices=["full", "autocast"], default="autocast")
parser.add_argument("--share", action='store_true', help="use share=True for gradio and make the UI accessible through their site (doesn't work for me but you might have better luck)")
parser.add_argument("--esrgan-models-path", type=str, help="path to directory with ESRGAN models", default=os.path.join(script_path, 'ESRGAN'))
parser.add_argument("--opt-split-attention", action='store_true', help="does not do anything")
parser.add_argument("--disable-opt-split-attention", action='store_true', help="disable an optimization that reduces vram usage by a lot")
parser.add_argument("--swinir-models-path", type=str, help="path to directory with SwinIR models", default=os.path.join(script_path, 'SwinIR'))
parser.add_argument("--opt-split-attention", action='store_true', help="force-enables cross-attention layer optimization. By default, it's on for torch.cuda and off for other torch devices.")
parser.add_argument("--disable-opt-split-attention", action='store_true', help="force-disables cross-attention layer optimization")
parser.add_argument("--opt-split-attention-v1", action='store_true', help="enable older version of split attention optimization that does not consume all the VRAM it can find")
parser.add_argument("--listen", action='store_true', help="launch gradio with 0.0.0.0 as server name, allowing to respond to network requests")
parser.add_argument("--port", type=int, help="launch gradio with given server port, you need root/admin rights for ports < 1024, defaults to 7860 if available", default=None)
@@ -49,10 +50,9 @@ parser.add_argument("--gradio-auth", type=str, help='set gradio authentication l
parser.add_argument("--opt-channelslast", action='store_true', help="change memory type for stable diffusion to channels last")
parser.add_argument("--styles-file", type=str, help="filename to use for styles", default=os.path.join(script_path, 'styles.csv'))
parser.add_argument("--autolaunch", action='store_true', help="open the webui URL in the system's default browser upon launch", default=False)
cmd_opts = parser.parse_args()
parser.add_argument("--use-textbox-seed", action='store_true', help="use textbox for seeds in UI (no up/down, but possible to input long seeds)", default=False)
if cmd_opts.opt_split_attention:
print("Information: --opt-split-attention is now the default. To remove this message, remove --opt-split-attention from command line arguments. To disable the optimization, use --disable-opt-split-attention")
cmd_opts = parser.parse_args()
device = get_optimal_device()
@@ -66,6 +66,7 @@ class State:
job = ""
job_no = 0
job_count = 0
job_timestamp = 0
sampling_step = 0
sampling_steps = 0
current_latent = None
@@ -79,6 +80,8 @@ class State:
self.job_no += 1
self.sampling_step = 0
self.current_image_sampling_step = 0
def get_job_timestamp(self):
return datetime.datetime.now().strftime("%Y%m%d%H%M%S")
state = State()
@@ -95,72 +98,132 @@ face_restorers = []
modules.sd_models.list_models()
class Options:
class OptionInfo:
def __init__(self, default=None, label="", component=None, component_args=None, onchange=None):
self.default = default
self.label = label
self.component = component
self.component_args = component_args
self.onchange = onchange
def realesrgan_models_names():
import modules.realesrgan_model
return [x.name for x in modules.realesrgan_model.get_realesrgan_models()]
class OptionInfo:
def __init__(self, default=None, label="", component=None, component_args=None, onchange=None):
self.default = default
self.label = label
self.component = component
self.component_args = component_args
self.onchange = onchange
self.section = None
def options_section(section_identifer, options_dict):
for k, v in options_dict.items():
v.section = section_identifer
return options_dict
hide_dirs = {"visible": not cmd_opts.hide_ui_dir_config}
options_templates = {}
options_templates.update(options_section(('saving-images', "Saving images/grids"), {
"samples_save": OptionInfo(True, "Always save all generated images"),
"samples_format": OptionInfo('png', 'File format for images'),
"samples_filename_pattern": OptionInfo("", "Images filename pattern"),
"grid_save": OptionInfo(True, "Always save all generated image grids"),
"grid_format": OptionInfo('png', 'File format for grids'),
"grid_extended_filename": OptionInfo(False, "Add extended info (seed, prompt) to filename when saving grid"),
"grid_only_if_multiple": OptionInfo(True, "Do not save grids consisting of one picture"),
"n_rows": OptionInfo(-1, "Grid row count; use -1 for autodetect and 0 for it to be same as batch size", gr.Slider, {"minimum": -1, "maximum": 16, "step": 1}),
"enable_pnginfo": OptionInfo(True, "Save text information about generation parameters as chunks to png files"),
"save_txt": OptionInfo(False, "Create a text file next to every image with generation parameters."),
"save_images_before_face_restoration": OptionInfo(False, "Save a copy of image before doing face restoration."),
"jpeg_quality": OptionInfo(80, "Quality for saved jpeg images", gr.Slider, {"minimum": 1, "maximum": 100, "step": 1}),
"export_for_4chan": OptionInfo(True, "If PNG image is larger than 4MB or any dimension is larger than 4000, downscale and save copy as JPG"),
"use_original_name_batch": OptionInfo(False, "Use original name for output filename during batch process in extras tab"),
}))
options_templates.update(options_section(('saving-paths', "Paths for saving"), {
"outdir_samples": OptionInfo("", "Output directory for images; if empty, defaults to three directories below", component_args=hide_dirs),
"outdir_txt2img_samples": OptionInfo("outputs/txt2img-images", 'Output directory for txt2img images', component_args=hide_dirs),
"outdir_img2img_samples": OptionInfo("outputs/img2img-images", 'Output directory for img2img images', component_args=hide_dirs),
"outdir_extras_samples": OptionInfo("outputs/extras-images", 'Output directory for images from extras tab', component_args=hide_dirs),
"outdir_grids": OptionInfo("", "Output directory for grids; if empty, defaults to two directories below", component_args=hide_dirs),
"outdir_txt2img_grids": OptionInfo("outputs/txt2img-grids", 'Output directory for txt2img grids', component_args=hide_dirs),
"outdir_img2img_grids": OptionInfo("outputs/img2img-grids", 'Output directory for img2img grids', component_args=hide_dirs),
"outdir_save": OptionInfo("log/images", "Directory for saving images using the Save button", component_args=hide_dirs),
}))
options_templates.update(options_section(('saving-to-dirs', "Saving to a directory"), {
"save_to_dirs": OptionInfo(False, "Save images to a subdirectory"),
"grid_save_to_dirs": OptionInfo(False, "Save grids to subdirectory"),
"directories_filename_pattern": OptionInfo("", "Directory name pattern"),
"directories_max_prompt_words": OptionInfo(8, "Max prompt words", gr.Slider, {"minimum": 1, "maximum": 20, "step": 1}),
}))
options_templates.update(options_section(('upscaling', "Upscaling"), {
"ESRGAN_tile": OptionInfo(192, "Tile size for ESRGAN upscalers. 0 = no tiling.", gr.Slider, {"minimum": 0, "maximum": 512, "step": 16}),
"ESRGAN_tile_overlap": OptionInfo(8, "Tile overlap, in pixels for ESRGAN upscalers. Low values = visible seam.", gr.Slider, {"minimum": 0, "maximum": 48, "step": 1}),
"realesrgan_enabled_models": OptionInfo(["Real-ESRGAN 4x plus", "Real-ESRGAN 4x plus anime 6B"], "Select which RealESRGAN models to show in the web UI. (Requires restart)", gr.CheckboxGroup, lambda: {"choices": realesrgan_models_names()}),
"SWIN_tile": OptionInfo(192, "Tile size for all SwinIR.", gr.Slider, {"minimum": 16, "maximum": 512, "step": 16}),
"SWIN_tile_overlap": OptionInfo(8, "Tile overlap, in pixels for SwinIR. Low values = visible seam.", gr.Slider, {"minimum": 0, "maximum": 48, "step": 1}),
"ldsr_steps": OptionInfo(100, "LDSR processing steps. Lower = faster", gr.Slider, {"minimum": 1, "maximum": 200, "step": 1}),
"ldsr_pre_down": OptionInfo(1, "LDSR Pre-process downssample scale. 1 = no down-sampling, 4 = 1/4 scale.", gr.Slider, {"minimum": 1, "maximum": 4, "step": 1}),
"ldsr_post_down": OptionInfo(1, "LDSR Post-process down-sample scale. 1 = no down-sampling, 4 = 1/4 scale.", gr.Slider, {"minimum": 1, "maximum": 4, "step": 1}),
"upscaler_for_img2img": OptionInfo(None, "Upscaler for img2img", gr.Radio, lambda: {"choices": [x.name for x in sd_upscalers]}),
}))
options_templates.update(options_section(('face-restoration', "Face restoration"), {
"face_restoration_model": OptionInfo(None, "Face restoration model", gr.Radio, lambda: {"choices": [x.name() for x in face_restorers]}),
"code_former_weight": OptionInfo(0.5, "CodeFormer weight parameter; 0 = maximum effect; 1 = minimum effect", gr.Slider, {"minimum": 0, "maximum": 1, "step": 0.01}),
"face_restoration_unload": OptionInfo(False, "Move face restoration model from VRAM into RAM after processing"),
"save_selected_only": OptionInfo(False, "When using 'Save' button, only save a single selected image"),
}))
options_templates.update(options_section(('system', "System"), {
"memmon_poll_rate": OptionInfo(8, "VRAM usage polls per second during generation. Set to 0 to disable.", gr.Slider, {"minimum": 0, "maximum": 40, "step": 1}),
"samples_log_stdout": OptionInfo(False, "Always print all generation info to standard output"),
"multiple_tqdm": OptionInfo(True, "Add a second progress bar to the console that shows progress for an entire job. Broken in PyCharm console."),
}))
options_templates.update(options_section(('sd', "Stable Diffusion"), {
"sd_model_checkpoint": OptionInfo(None, "Stable Diffusion checkpoint", gr.Radio, lambda: {"choices": [x.title for x in modules.sd_models.checkpoints_list.values()]}),
"img2img_color_correction": OptionInfo(False, "Apply color correction to img2img results to match original colors."),
"save_images_before_color_correction": OptionInfo(False, "Save a copy of image before applying color correction to img2img results"),
"img2img_fix_steps": OptionInfo(False, "With img2img, do exactly the amount of steps the slider specifies (normally you'd do less with less denoising)."),
"enable_quantization": OptionInfo(False, "Enable quantization in K samplers for sharper and cleaner results. This may change existing seeds. Requires restart to apply."),
"enable_emphasis": OptionInfo(True, "Use (text) to make model pay more attention to text and [text] to make it pay less attention"),
"enable_batch_seeds": OptionInfo(True, "Make K-diffusion samplers produce same images in a batch as when making a single image"),
"filter_nsfw": OptionInfo(False, "Filter NSFW content"),
"random_artist_categories": OptionInfo([], "Allowed categories for random artists selection when using the Roll button", gr.CheckboxGroup, {"choices": artist_db.categories()}),
}))
options_templates.update(options_section(('interrogate', "Interrogate Options"), {
"interrogate_keep_models_in_memory": OptionInfo(False, "Interrogate: keep models in VRAM"),
"interrogate_use_builtin_artists": OptionInfo(True, "Interrogate: use artists from artists.csv"),
"interrogate_clip_num_beams": OptionInfo(1, "Interrogate: num_beams for BLIP", gr.Slider, {"minimum": 1, "maximum": 16, "step": 1}),
"interrogate_clip_min_length": OptionInfo(24, "Interrogate: minimum description length (excluding artists, etc..)", gr.Slider, {"minimum": 1, "maximum": 128, "step": 1}),
"interrogate_clip_max_length": OptionInfo(48, "Interrogate: maximum description length", gr.Slider, {"minimum": 1, "maximum": 256, "step": 1}),
"interrogate_clip_dict_limit": OptionInfo(1500, "Interrogate: maximum number of lines in text file (0 = No limit)"),
}))
options_templates.update(options_section(('ui', "User interface"), {
"show_progressbar": OptionInfo(True, "Show progressbar"),
"show_progress_every_n_steps": OptionInfo(0, "Show show image creation progress every N sampling steps. Set 0 to disable.", gr.Slider, {"minimum": 0, "maximum": 32, "step": 1}),
"return_grid": OptionInfo(True, "Show grid in results for web"),
"add_model_hash_to_info": OptionInfo(True, "Add model hash to generation information"),
"font": OptionInfo("", "Font for image grids that have text"),
"js_modal_lightbox": OptionInfo(True, "Enable full page image viewer"),
"js_modal_lightbox_initialy_zoomed": OptionInfo(True, "Show images zoomed in by default in full page image viewer"),
}))
class Options:
data = None
hide_dirs = {"visible": False} if cmd_opts.hide_ui_dir_config else None
data_labels = {
"samples_filename_pattern": OptionInfo("", "Images filename pattern"),
"save_to_dirs": OptionInfo(False, "Save images to a subdirectory"),
"grid_save_to_dirs": OptionInfo(False, "Save grids to subdirectory"),
"directories_filename_pattern": OptionInfo("", "Directory name pattern"),
"outdir_samples": OptionInfo("", "Output directory for images; if empty, defaults to two directories below", component_args=hide_dirs),
"outdir_txt2img_samples": OptionInfo("outputs/txt2img-images", 'Output directory for txt2img images', component_args=hide_dirs),
"outdir_img2img_samples": OptionInfo("outputs/img2img-images", 'Output directory for img2img images', component_args=hide_dirs),
"outdir_extras_samples": OptionInfo("outputs/extras-images", 'Output directory for images from extras tab', component_args=hide_dirs),
"outdir_grids": OptionInfo("", "Output directory for grids; if empty, defaults to two directories below", component_args=hide_dirs),
"outdir_txt2img_grids": OptionInfo("outputs/txt2img-grids", 'Output directory for txt2img grids', component_args=hide_dirs),
"outdir_img2img_grids": OptionInfo("outputs/img2img-grids", 'Output directory for img2img grids', component_args=hide_dirs),
"outdir_save": OptionInfo("log/images", "Directory for saving images using the Save button", component_args=hide_dirs),
"samples_save": OptionInfo(True, "Always save all generated images"),
"save_selected_only": OptionInfo(False, "When using 'Save' button, only save a single selected image"),
"samples_format": OptionInfo('png', 'File format for individual samples'),
"filter_nsfw": OptionInfo(False, "Filter NSFW content"),
"grid_save": OptionInfo(True, "Always save all generated image grids"),
"return_grid": OptionInfo(True, "Show grid in results for web"),
"grid_format": OptionInfo('png', 'File format for grids'),
"grid_extended_filename": OptionInfo(False, "Add extended info (seed, prompt) to filename when saving grid"),
"grid_only_if_multiple": OptionInfo(True, "Do not save grids consisting of one picture"),
"n_rows": OptionInfo(-1, "Grid row count; use -1 for autodetect and 0 for it to be same as batch size", gr.Slider, {"minimum": -1, "maximum": 16, "step": 1}),
"jpeg_quality": OptionInfo(80, "Quality for saved jpeg images", gr.Slider, {"minimum": 1, "maximum": 100, "step": 1}),
"export_for_4chan": OptionInfo(True, "If PNG image is larger than 4MB or any dimension is larger than 4000, downscale and save copy as JPG"),
"enable_pnginfo": OptionInfo(True, "Save text information about generation parameters as chunks to png files"),
"add_model_hash_to_info": OptionInfo(False, "Add model hash to generation information"),
"img2img_color_correction": OptionInfo(False, "Apply color correction to img2img results to match original colors."),
"img2img_fix_steps": OptionInfo(False, "With img2img, do exactly the amount of steps the slider specifies (normally you'd do less with less denoising)."),
"enable_quantization": OptionInfo(False, "Enable quantization in K samplers for sharper and cleaner results. This may change existing seeds. Requires restart to apply."),
"font": OptionInfo("", "Font for image grids that have text"),
"enable_emphasis": OptionInfo(True, "Use (text) to make model pay more attention to text and [text] to make it pay less attention"),
"enable_batch_seeds": OptionInfo(True, "Make K-diffusion samplers produce same images in a batch as when making a single image"),
"save_txt": OptionInfo(False, "Create a text file next to every image with generation parameters."),
"ESRGAN_tile": OptionInfo(192, "Tile size for upscaling. 0 = no tiling.", gr.Slider, {"minimum": 0, "maximum": 512, "step": 16}),
"ESRGAN_tile_overlap": OptionInfo(8, "Tile overlap, in pixels for upscaling. Low values = visible seam.", gr.Slider, {"minimum": 0, "maximum": 48, "step": 1}),
"random_artist_categories": OptionInfo([], "Allowed categories for random artists selection when using the Roll button", gr.CheckboxGroup, {"choices": artist_db.categories()}),
"upscale_at_full_resolution_padding": OptionInfo(16, "Inpainting at full resolution: padding, in pixels, for the masked region.", gr.Slider, {"minimum": 0, "maximum": 128, "step": 4}),
"show_progressbar": OptionInfo(True, "Show progressbar"),
"show_progress_every_n_steps": OptionInfo(0, "Show show image creation progress every N sampling steps. Set 0 to disable.", gr.Slider, {"minimum": 0, "maximum": 32, "step": 1}),
"multiple_tqdm": OptionInfo(True, "Add a second progress bar to the console that shows progress for an entire job. Broken in PyCharm console."),
"memmon_poll_rate": OptionInfo(8, "VRAM usage polls per second during generation. Set to 0 to disable.", gr.Slider, {"minimum": 0, "maximum": 40, "step":1}),
"face_restoration_model": OptionInfo(None, "Face restoration model", gr.Radio, lambda: {"choices": [x.name() for x in face_restorers]}),
"code_former_weight": OptionInfo(0.5, "CodeFormer weight parameter; 0 = maximum effect; 1 = minimum effect", gr.Slider, {"minimum": 0, "maximum": 1, "step": 0.01}),
"save_images_before_face_restoration": OptionInfo(False, "Save a copy of image before doing face restoration."),
"face_restoration_unload": OptionInfo(False, "Move face restoration model from VRAM into RAM after processing"),
"interrogate_keep_models_in_memory": OptionInfo(False, "Interrogate: keep models in VRAM"),
"interrogate_use_builtin_artists": OptionInfo(True, "Interrogate: use artists from artists.csv"),
"interrogate_clip_num_beams": OptionInfo(1, "Interrogate: num_beams for BLIP", gr.Slider, {"minimum": 1, "maximum": 16, "step": 1}),
"interrogate_clip_min_length": OptionInfo(24, "Interrogate: minimum description length (excluding artists, etc..)", gr.Slider, {"minimum": 1, "maximum": 128, "step": 1}),
"interrogate_clip_max_length": OptionInfo(48, "Interrogate: maximum description length", gr.Slider, {"minimum": 1, "maximum": 256, "step": 1}),
"interrogate_clip_dict_limit": OptionInfo(1500, "Interrogate: maximum number of lines in text file (0 = No limit)"),
"sd_model_checkpoint": OptionInfo(None, "Stable Diffusion checkpoint", gr.Radio, lambda: {"choices": [x.title for x in modules.sd_models.checkpoints_list.values()]}),
"js_modal_lightbox": OptionInfo(True, "Enable full page image viewer"),
}
data_labels = options_templates
typemap = {int: float}
def __init__(self):
self.data = {k: v.default for k, v in self.data_labels.items()}
@@ -186,10 +249,29 @@ class Options:
with open(filename, "w", encoding="utf8") as file:
json.dump(self.data, file)
def same_type(self, x, y):
if x is None or y is None:
return True
type_x = self.typemap.get(type(x), type(x))
type_y = self.typemap.get(type(y), type(y))
return type_x == type_y
def load(self, filename):
with open(filename, "r", encoding="utf8") as file:
self.data = json.load(file)
bad_settings = 0
for k, v in self.data.items():
info = self.data_labels.get(k, None)
if info is not None and not self.same_type(info.default, v):
print(f"Warning: bad setting value: {k}: {v} ({type(v).__name__}; expected {type(info.default).__name__})", file=sys.stderr)
bad_settings += 1
if bad_settings > 0:
print(f"The program is likely to not work with bad settings.\nSettings file: {filename}\nEither fix the file, or delete it and restart.", file=sys.stderr)
def onchange(self, key, func):
item = self.data_labels.get(key)
item.onchange = func
@@ -229,6 +311,13 @@ class TotalTQDM:
self.reset()
self._tqdm.update()
def updateTotal(self, new_total):
if not opts.multiple_tqdm:
return
if self._tqdm is None:
self.reset()
self._tqdm.total=new_total
def clear(self):
if self._tqdm is not None:
self._tqdm.close()
+123
View File
@@ -0,0 +1,123 @@
import sys
import traceback
import cv2
import os
import contextlib
import numpy as np
from PIL import Image
import torch
import modules.images
from modules.shared import cmd_opts, opts, device
from modules.swinir_arch import SwinIR as net
precision_scope = (
torch.autocast if cmd_opts.precision == "autocast" else contextlib.nullcontext
)
def load_model(filename, scale=4):
model = net(
upscale=scale,
in_chans=3,
img_size=64,
window_size=8,
img_range=1.0,
depths=[6, 6, 6, 6, 6, 6, 6, 6, 6],
embed_dim=240,
num_heads=[8, 8, 8, 8, 8, 8, 8, 8, 8],
mlp_ratio=2,
upsampler="nearest+conv",
resi_connection="3conv",
)
pretrained_model = torch.load(filename)
model.load_state_dict(pretrained_model["params_ema"], strict=True)
if not cmd_opts.no_half:
model = model.half()
return model
def load_models(dirname):
for file in os.listdir(dirname):
path = os.path.join(dirname, file)
model_name, extension = os.path.splitext(file)
if extension != ".pt" and extension != ".pth":
continue
try:
modules.shared.sd_upscalers.append(UpscalerSwin(path, model_name))
except Exception:
print(f"Error loading SwinIR model: {path}", file=sys.stderr)
print(traceback.format_exc(), file=sys.stderr)
def upscale(
img,
model,
tile=opts.SWIN_tile,
tile_overlap=opts.SWIN_tile_overlap,
window_size=8,
scale=4,
):
img = np.array(img)
img = img[:, :, ::-1]
img = np.moveaxis(img, 2, 0) / 255
img = torch.from_numpy(img).float()
img = img.unsqueeze(0).to(device)
with torch.no_grad(), precision_scope("cuda"):
_, _, h_old, w_old = img.size()
h_pad = (h_old // window_size + 1) * window_size - h_old
w_pad = (w_old // window_size + 1) * window_size - w_old
img = torch.cat([img, torch.flip(img, [2])], 2)[:, :, : h_old + h_pad, :]
img = torch.cat([img, torch.flip(img, [3])], 3)[:, :, :, : w_old + w_pad]
output = inference(img, model, tile, tile_overlap, window_size, scale)
output = output[..., : h_old * scale, : w_old * scale]
output = output.data.squeeze().float().cpu().clamp_(0, 1).numpy()
if output.ndim == 3:
output = np.transpose(
output[[2, 1, 0], :, :], (1, 2, 0)
) # CHW-RGB to HCW-BGR
output = (output * 255.0).round().astype(np.uint8) # float32 to uint8
return Image.fromarray(output, "RGB")
def inference(img, model, tile, tile_overlap, window_size, scale):
# test the image tile by tile
b, c, h, w = img.size()
tile = min(tile, h, w)
assert tile % window_size == 0, "tile size should be a multiple of window_size"
sf = scale
stride = tile - tile_overlap
h_idx_list = list(range(0, h - tile, stride)) + [h - tile]
w_idx_list = list(range(0, w - tile, stride)) + [w - tile]
E = torch.zeros(b, c, h * sf, w * sf, dtype=torch.half, device=device).type_as(img)
W = torch.zeros_like(E, dtype=torch.half, device=device)
for h_idx in h_idx_list:
for w_idx in w_idx_list:
in_patch = img[..., h_idx : h_idx + tile, w_idx : w_idx + tile]
out_patch = model(in_patch)
out_patch_mask = torch.ones_like(out_patch)
E[
..., h_idx * sf : (h_idx + tile) * sf, w_idx * sf : (w_idx + tile) * sf
].add_(out_patch)
W[
..., h_idx * sf : (h_idx + tile) * sf, w_idx * sf : (w_idx + tile) * sf
].add_(out_patch_mask)
output = E.div_(W)
return output
class UpscalerSwin(modules.images.Upscaler):
def __init__(self, filename, title):
self.name = title
self.model = load_model(filename)
def do_upscale(self, img):
model = self.model.to(device)
img = upscale(img, model)
return img
+867
View File
@@ -0,0 +1,867 @@
# -----------------------------------------------------------------------------------
# SwinIR: Image Restoration Using Swin Transformer, https://arxiv.org/abs/2108.10257
# Originally Written by Ze Liu, Modified by Jingyun Liang.
# -----------------------------------------------------------------------------------
import math
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.utils.checkpoint as checkpoint
from timm.models.layers import DropPath, to_2tuple, trunc_normal_
class Mlp(nn.Module):
def __init__(self, in_features, hidden_features=None, out_features=None, act_layer=nn.GELU, drop=0.):
super().__init__()
out_features = out_features or in_features
hidden_features = hidden_features or in_features
self.fc1 = nn.Linear(in_features, hidden_features)
self.act = act_layer()
self.fc2 = nn.Linear(hidden_features, out_features)
self.drop = nn.Dropout(drop)
def forward(self, x):
x = self.fc1(x)
x = self.act(x)
x = self.drop(x)
x = self.fc2(x)
x = self.drop(x)
return x
def window_partition(x, window_size):
"""
Args:
x: (B, H, W, C)
window_size (int): window size
Returns:
windows: (num_windows*B, window_size, window_size, C)
"""
B, H, W, C = x.shape
x = x.view(B, H // window_size, window_size, W // window_size, window_size, C)
windows = x.permute(0, 1, 3, 2, 4, 5).contiguous().view(-1, window_size, window_size, C)
return windows
def window_reverse(windows, window_size, H, W):
"""
Args:
windows: (num_windows*B, window_size, window_size, C)
window_size (int): Window size
H (int): Height of image
W (int): Width of image
Returns:
x: (B, H, W, C)
"""
B = int(windows.shape[0] / (H * W / window_size / window_size))
x = windows.view(B, H // window_size, W // window_size, window_size, window_size, -1)
x = x.permute(0, 1, 3, 2, 4, 5).contiguous().view(B, H, W, -1)
return x
class WindowAttention(nn.Module):
r""" Window based multi-head self attention (W-MSA) module with relative position bias.
It supports both of shifted and non-shifted window.
Args:
dim (int): Number of input channels.
window_size (tuple[int]): The height and width of the window.
num_heads (int): Number of attention heads.
qkv_bias (bool, optional): If True, add a learnable bias to query, key, value. Default: True
qk_scale (float | None, optional): Override default qk scale of head_dim ** -0.5 if set
attn_drop (float, optional): Dropout ratio of attention weight. Default: 0.0
proj_drop (float, optional): Dropout ratio of output. Default: 0.0
"""
def __init__(self, dim, window_size, num_heads, qkv_bias=True, qk_scale=None, attn_drop=0., proj_drop=0.):
super().__init__()
self.dim = dim
self.window_size = window_size # Wh, Ww
self.num_heads = num_heads
head_dim = dim // num_heads
self.scale = qk_scale or head_dim ** -0.5
# define a parameter table of relative position bias
self.relative_position_bias_table = nn.Parameter(
torch.zeros((2 * window_size[0] - 1) * (2 * window_size[1] - 1), num_heads)) # 2*Wh-1 * 2*Ww-1, nH
# get pair-wise relative position index for each token inside the window
coords_h = torch.arange(self.window_size[0])
coords_w = torch.arange(self.window_size[1])
coords = torch.stack(torch.meshgrid([coords_h, coords_w])) # 2, Wh, Ww
coords_flatten = torch.flatten(coords, 1) # 2, Wh*Ww
relative_coords = coords_flatten[:, :, None] - coords_flatten[:, None, :] # 2, Wh*Ww, Wh*Ww
relative_coords = relative_coords.permute(1, 2, 0).contiguous() # Wh*Ww, Wh*Ww, 2
relative_coords[:, :, 0] += self.window_size[0] - 1 # shift to start from 0
relative_coords[:, :, 1] += self.window_size[1] - 1
relative_coords[:, :, 0] *= 2 * self.window_size[1] - 1
relative_position_index = relative_coords.sum(-1) # Wh*Ww, Wh*Ww
self.register_buffer("relative_position_index", relative_position_index)
self.qkv = nn.Linear(dim, dim * 3, bias=qkv_bias)
self.attn_drop = nn.Dropout(attn_drop)
self.proj = nn.Linear(dim, dim)
self.proj_drop = nn.Dropout(proj_drop)
trunc_normal_(self.relative_position_bias_table, std=.02)
self.softmax = nn.Softmax(dim=-1)
def forward(self, x, mask=None):
"""
Args:
x: input features with shape of (num_windows*B, N, C)
mask: (0/-inf) mask with shape of (num_windows, Wh*Ww, Wh*Ww) or None
"""
B_, N, C = x.shape
qkv = self.qkv(x).reshape(B_, N, 3, self.num_heads, C // self.num_heads).permute(2, 0, 3, 1, 4)
q, k, v = qkv[0], qkv[1], qkv[2] # make torchscript happy (cannot use tensor as tuple)
q = q * self.scale
attn = (q @ k.transpose(-2, -1))
relative_position_bias = self.relative_position_bias_table[self.relative_position_index.view(-1)].view(
self.window_size[0] * self.window_size[1], self.window_size[0] * self.window_size[1], -1) # Wh*Ww,Wh*Ww,nH
relative_position_bias = relative_position_bias.permute(2, 0, 1).contiguous() # nH, Wh*Ww, Wh*Ww
attn = attn + relative_position_bias.unsqueeze(0)
if mask is not None:
nW = mask.shape[0]
attn = attn.view(B_ // nW, nW, self.num_heads, N, N) + mask.unsqueeze(1).unsqueeze(0)
attn = attn.view(-1, self.num_heads, N, N)
attn = self.softmax(attn)
else:
attn = self.softmax(attn)
attn = self.attn_drop(attn)
x = (attn @ v).transpose(1, 2).reshape(B_, N, C)
x = self.proj(x)
x = self.proj_drop(x)
return x
def extra_repr(self) -> str:
return f'dim={self.dim}, window_size={self.window_size}, num_heads={self.num_heads}'
def flops(self, N):
# calculate flops for 1 window with token length of N
flops = 0
# qkv = self.qkv(x)
flops += N * self.dim * 3 * self.dim
# attn = (q @ k.transpose(-2, -1))
flops += self.num_heads * N * (self.dim // self.num_heads) * N
# x = (attn @ v)
flops += self.num_heads * N * N * (self.dim // self.num_heads)
# x = self.proj(x)
flops += N * self.dim * self.dim
return flops
class SwinTransformerBlock(nn.Module):
r""" Swin Transformer Block.
Args:
dim (int): Number of input channels.
input_resolution (tuple[int]): Input resulotion.
num_heads (int): Number of attention heads.
window_size (int): Window size.
shift_size (int): Shift size for SW-MSA.
mlp_ratio (float): Ratio of mlp hidden dim to embedding dim.
qkv_bias (bool, optional): If True, add a learnable bias to query, key, value. Default: True
qk_scale (float | None, optional): Override default qk scale of head_dim ** -0.5 if set.
drop (float, optional): Dropout rate. Default: 0.0
attn_drop (float, optional): Attention dropout rate. Default: 0.0
drop_path (float, optional): Stochastic depth rate. Default: 0.0
act_layer (nn.Module, optional): Activation layer. Default: nn.GELU
norm_layer (nn.Module, optional): Normalization layer. Default: nn.LayerNorm
"""
def __init__(self, dim, input_resolution, num_heads, window_size=7, shift_size=0,
mlp_ratio=4., qkv_bias=True, qk_scale=None, drop=0., attn_drop=0., drop_path=0.,
act_layer=nn.GELU, norm_layer=nn.LayerNorm):
super().__init__()
self.dim = dim
self.input_resolution = input_resolution
self.num_heads = num_heads
self.window_size = window_size
self.shift_size = shift_size
self.mlp_ratio = mlp_ratio
if min(self.input_resolution) <= self.window_size:
# if window size is larger than input resolution, we don't partition windows
self.shift_size = 0
self.window_size = min(self.input_resolution)
assert 0 <= self.shift_size < self.window_size, "shift_size must in 0-window_size"
self.norm1 = norm_layer(dim)
self.attn = WindowAttention(
dim, window_size=to_2tuple(self.window_size), num_heads=num_heads,
qkv_bias=qkv_bias, qk_scale=qk_scale, attn_drop=attn_drop, proj_drop=drop)
self.drop_path = DropPath(drop_path) if drop_path > 0. else nn.Identity()
self.norm2 = norm_layer(dim)
mlp_hidden_dim = int(dim * mlp_ratio)
self.mlp = Mlp(in_features=dim, hidden_features=mlp_hidden_dim, act_layer=act_layer, drop=drop)
if self.shift_size > 0:
attn_mask = self.calculate_mask(self.input_resolution)
else:
attn_mask = None
self.register_buffer("attn_mask", attn_mask)
def calculate_mask(self, x_size):
# calculate attention mask for SW-MSA
H, W = x_size
img_mask = torch.zeros((1, H, W, 1)) # 1 H W 1
h_slices = (slice(0, -self.window_size),
slice(-self.window_size, -self.shift_size),
slice(-self.shift_size, None))
w_slices = (slice(0, -self.window_size),
slice(-self.window_size, -self.shift_size),
slice(-self.shift_size, None))
cnt = 0
for h in h_slices:
for w in w_slices:
img_mask[:, h, w, :] = cnt
cnt += 1
mask_windows = window_partition(img_mask, self.window_size) # nW, window_size, window_size, 1
mask_windows = mask_windows.view(-1, self.window_size * self.window_size)
attn_mask = mask_windows.unsqueeze(1) - mask_windows.unsqueeze(2)
attn_mask = attn_mask.masked_fill(attn_mask != 0, float(-100.0)).masked_fill(attn_mask == 0, float(0.0))
return attn_mask
def forward(self, x, x_size):
H, W = x_size
B, L, C = x.shape
# assert L == H * W, "input feature has wrong size"
shortcut = x
x = self.norm1(x)
x = x.view(B, H, W, C)
# cyclic shift
if self.shift_size > 0:
shifted_x = torch.roll(x, shifts=(-self.shift_size, -self.shift_size), dims=(1, 2))
else:
shifted_x = x
# partition windows
x_windows = window_partition(shifted_x, self.window_size) # nW*B, window_size, window_size, C
x_windows = x_windows.view(-1, self.window_size * self.window_size, C) # nW*B, window_size*window_size, C
# W-MSA/SW-MSA (to be compatible for testing on images whose shapes are the multiple of window size
if self.input_resolution == x_size:
attn_windows = self.attn(x_windows, mask=self.attn_mask) # nW*B, window_size*window_size, C
else:
attn_windows = self.attn(x_windows, mask=self.calculate_mask(x_size).to(x.device))
# merge windows
attn_windows = attn_windows.view(-1, self.window_size, self.window_size, C)
shifted_x = window_reverse(attn_windows, self.window_size, H, W) # B H' W' C
# reverse cyclic shift
if self.shift_size > 0:
x = torch.roll(shifted_x, shifts=(self.shift_size, self.shift_size), dims=(1, 2))
else:
x = shifted_x
x = x.view(B, H * W, C)
# FFN
x = shortcut + self.drop_path(x)
x = x + self.drop_path(self.mlp(self.norm2(x)))
return x
def extra_repr(self) -> str:
return f"dim={self.dim}, input_resolution={self.input_resolution}, num_heads={self.num_heads}, " \
f"window_size={self.window_size}, shift_size={self.shift_size}, mlp_ratio={self.mlp_ratio}"
def flops(self):
flops = 0
H, W = self.input_resolution
# norm1
flops += self.dim * H * W
# W-MSA/SW-MSA
nW = H * W / self.window_size / self.window_size
flops += nW * self.attn.flops(self.window_size * self.window_size)
# mlp
flops += 2 * H * W * self.dim * self.dim * self.mlp_ratio
# norm2
flops += self.dim * H * W
return flops
class PatchMerging(nn.Module):
r""" Patch Merging Layer.
Args:
input_resolution (tuple[int]): Resolution of input feature.
dim (int): Number of input channels.
norm_layer (nn.Module, optional): Normalization layer. Default: nn.LayerNorm
"""
def __init__(self, input_resolution, dim, norm_layer=nn.LayerNorm):
super().__init__()
self.input_resolution = input_resolution
self.dim = dim
self.reduction = nn.Linear(4 * dim, 2 * dim, bias=False)
self.norm = norm_layer(4 * dim)
def forward(self, x):
"""
x: B, H*W, C
"""
H, W = self.input_resolution
B, L, C = x.shape
assert L == H * W, "input feature has wrong size"
assert H % 2 == 0 and W % 2 == 0, f"x size ({H}*{W}) are not even."
x = x.view(B, H, W, C)
x0 = x[:, 0::2, 0::2, :] # B H/2 W/2 C
x1 = x[:, 1::2, 0::2, :] # B H/2 W/2 C
x2 = x[:, 0::2, 1::2, :] # B H/2 W/2 C
x3 = x[:, 1::2, 1::2, :] # B H/2 W/2 C
x = torch.cat([x0, x1, x2, x3], -1) # B H/2 W/2 4*C
x = x.view(B, -1, 4 * C) # B H/2*W/2 4*C
x = self.norm(x)
x = self.reduction(x)
return x
def extra_repr(self) -> str:
return f"input_resolution={self.input_resolution}, dim={self.dim}"
def flops(self):
H, W = self.input_resolution
flops = H * W * self.dim
flops += (H // 2) * (W // 2) * 4 * self.dim * 2 * self.dim
return flops
class BasicLayer(nn.Module):
""" A basic Swin Transformer layer for one stage.
Args:
dim (int): Number of input channels.
input_resolution (tuple[int]): Input resolution.
depth (int): Number of blocks.
num_heads (int): Number of attention heads.
window_size (int): Local window size.
mlp_ratio (float): Ratio of mlp hidden dim to embedding dim.
qkv_bias (bool, optional): If True, add a learnable bias to query, key, value. Default: True
qk_scale (float | None, optional): Override default qk scale of head_dim ** -0.5 if set.
drop (float, optional): Dropout rate. Default: 0.0
attn_drop (float, optional): Attention dropout rate. Default: 0.0
drop_path (float | tuple[float], optional): Stochastic depth rate. Default: 0.0
norm_layer (nn.Module, optional): Normalization layer. Default: nn.LayerNorm
downsample (nn.Module | None, optional): Downsample layer at the end of the layer. Default: None
use_checkpoint (bool): Whether to use checkpointing to save memory. Default: False.
"""
def __init__(self, dim, input_resolution, depth, num_heads, window_size,
mlp_ratio=4., qkv_bias=True, qk_scale=None, drop=0., attn_drop=0.,
drop_path=0., norm_layer=nn.LayerNorm, downsample=None, use_checkpoint=False):
super().__init__()
self.dim = dim
self.input_resolution = input_resolution
self.depth = depth
self.use_checkpoint = use_checkpoint
# build blocks
self.blocks = nn.ModuleList([
SwinTransformerBlock(dim=dim, input_resolution=input_resolution,
num_heads=num_heads, window_size=window_size,
shift_size=0 if (i % 2 == 0) else window_size // 2,
mlp_ratio=mlp_ratio,
qkv_bias=qkv_bias, qk_scale=qk_scale,
drop=drop, attn_drop=attn_drop,
drop_path=drop_path[i] if isinstance(drop_path, list) else drop_path,
norm_layer=norm_layer)
for i in range(depth)])
# patch merging layer
if downsample is not None:
self.downsample = downsample(input_resolution, dim=dim, norm_layer=norm_layer)
else:
self.downsample = None
def forward(self, x, x_size):
for blk in self.blocks:
if self.use_checkpoint:
x = checkpoint.checkpoint(blk, x, x_size)
else:
x = blk(x, x_size)
if self.downsample is not None:
x = self.downsample(x)
return x
def extra_repr(self) -> str:
return f"dim={self.dim}, input_resolution={self.input_resolution}, depth={self.depth}"
def flops(self):
flops = 0
for blk in self.blocks:
flops += blk.flops()
if self.downsample is not None:
flops += self.downsample.flops()
return flops
class RSTB(nn.Module):
"""Residual Swin Transformer Block (RSTB).
Args:
dim (int): Number of input channels.
input_resolution (tuple[int]): Input resolution.
depth (int): Number of blocks.
num_heads (int): Number of attention heads.
window_size (int): Local window size.
mlp_ratio (float): Ratio of mlp hidden dim to embedding dim.
qkv_bias (bool, optional): If True, add a learnable bias to query, key, value. Default: True
qk_scale (float | None, optional): Override default qk scale of head_dim ** -0.5 if set.
drop (float, optional): Dropout rate. Default: 0.0
attn_drop (float, optional): Attention dropout rate. Default: 0.0
drop_path (float | tuple[float], optional): Stochastic depth rate. Default: 0.0
norm_layer (nn.Module, optional): Normalization layer. Default: nn.LayerNorm
downsample (nn.Module | None, optional): Downsample layer at the end of the layer. Default: None
use_checkpoint (bool): Whether to use checkpointing to save memory. Default: False.
img_size: Input image size.
patch_size: Patch size.
resi_connection: The convolutional block before residual connection.
"""
def __init__(self, dim, input_resolution, depth, num_heads, window_size,
mlp_ratio=4., qkv_bias=True, qk_scale=None, drop=0., attn_drop=0.,
drop_path=0., norm_layer=nn.LayerNorm, downsample=None, use_checkpoint=False,
img_size=224, patch_size=4, resi_connection='1conv'):
super(RSTB, self).__init__()
self.dim = dim
self.input_resolution = input_resolution
self.residual_group = BasicLayer(dim=dim,
input_resolution=input_resolution,
depth=depth,
num_heads=num_heads,
window_size=window_size,
mlp_ratio=mlp_ratio,
qkv_bias=qkv_bias, qk_scale=qk_scale,
drop=drop, attn_drop=attn_drop,
drop_path=drop_path,
norm_layer=norm_layer,
downsample=downsample,
use_checkpoint=use_checkpoint)
if resi_connection == '1conv':
self.conv = nn.Conv2d(dim, dim, 3, 1, 1)
elif resi_connection == '3conv':
# to save parameters and memory
self.conv = nn.Sequential(nn.Conv2d(dim, dim // 4, 3, 1, 1), nn.LeakyReLU(negative_slope=0.2, inplace=True),
nn.Conv2d(dim // 4, dim // 4, 1, 1, 0),
nn.LeakyReLU(negative_slope=0.2, inplace=True),
nn.Conv2d(dim // 4, dim, 3, 1, 1))
self.patch_embed = PatchEmbed(
img_size=img_size, patch_size=patch_size, in_chans=0, embed_dim=dim,
norm_layer=None)
self.patch_unembed = PatchUnEmbed(
img_size=img_size, patch_size=patch_size, in_chans=0, embed_dim=dim,
norm_layer=None)
def forward(self, x, x_size):
return self.patch_embed(self.conv(self.patch_unembed(self.residual_group(x, x_size), x_size))) + x
def flops(self):
flops = 0
flops += self.residual_group.flops()
H, W = self.input_resolution
flops += H * W * self.dim * self.dim * 9
flops += self.patch_embed.flops()
flops += self.patch_unembed.flops()
return flops
class PatchEmbed(nn.Module):
r""" Image to Patch Embedding
Args:
img_size (int): Image size. Default: 224.
patch_size (int): Patch token size. Default: 4.
in_chans (int): Number of input image channels. Default: 3.
embed_dim (int): Number of linear projection output channels. Default: 96.
norm_layer (nn.Module, optional): Normalization layer. Default: None
"""
def __init__(self, img_size=224, patch_size=4, in_chans=3, embed_dim=96, norm_layer=None):
super().__init__()
img_size = to_2tuple(img_size)
patch_size = to_2tuple(patch_size)
patches_resolution = [img_size[0] // patch_size[0], img_size[1] // patch_size[1]]
self.img_size = img_size
self.patch_size = patch_size
self.patches_resolution = patches_resolution
self.num_patches = patches_resolution[0] * patches_resolution[1]
self.in_chans = in_chans
self.embed_dim = embed_dim
if norm_layer is not None:
self.norm = norm_layer(embed_dim)
else:
self.norm = None
def forward(self, x):
x = x.flatten(2).transpose(1, 2) # B Ph*Pw C
if self.norm is not None:
x = self.norm(x)
return x
def flops(self):
flops = 0
H, W = self.img_size
if self.norm is not None:
flops += H * W * self.embed_dim
return flops
class PatchUnEmbed(nn.Module):
r""" Image to Patch Unembedding
Args:
img_size (int): Image size. Default: 224.
patch_size (int): Patch token size. Default: 4.
in_chans (int): Number of input image channels. Default: 3.
embed_dim (int): Number of linear projection output channels. Default: 96.
norm_layer (nn.Module, optional): Normalization layer. Default: None
"""
def __init__(self, img_size=224, patch_size=4, in_chans=3, embed_dim=96, norm_layer=None):
super().__init__()
img_size = to_2tuple(img_size)
patch_size = to_2tuple(patch_size)
patches_resolution = [img_size[0] // patch_size[0], img_size[1] // patch_size[1]]
self.img_size = img_size
self.patch_size = patch_size
self.patches_resolution = patches_resolution
self.num_patches = patches_resolution[0] * patches_resolution[1]
self.in_chans = in_chans
self.embed_dim = embed_dim
def forward(self, x, x_size):
B, HW, C = x.shape
x = x.transpose(1, 2).view(B, self.embed_dim, x_size[0], x_size[1]) # B Ph*Pw C
return x
def flops(self):
flops = 0
return flops
class Upsample(nn.Sequential):
"""Upsample module.
Args:
scale (int): Scale factor. Supported scales: 2^n and 3.
num_feat (int): Channel number of intermediate features.
"""
def __init__(self, scale, num_feat):
m = []
if (scale & (scale - 1)) == 0: # scale = 2^n
for _ in range(int(math.log(scale, 2))):
m.append(nn.Conv2d(num_feat, 4 * num_feat, 3, 1, 1))
m.append(nn.PixelShuffle(2))
elif scale == 3:
m.append(nn.Conv2d(num_feat, 9 * num_feat, 3, 1, 1))
m.append(nn.PixelShuffle(3))
else:
raise ValueError(f'scale {scale} is not supported. ' 'Supported scales: 2^n and 3.')
super(Upsample, self).__init__(*m)
class UpsampleOneStep(nn.Sequential):
"""UpsampleOneStep module (the difference with Upsample is that it always only has 1conv + 1pixelshuffle)
Used in lightweight SR to save parameters.
Args:
scale (int): Scale factor. Supported scales: 2^n and 3.
num_feat (int): Channel number of intermediate features.
"""
def __init__(self, scale, num_feat, num_out_ch, input_resolution=None):
self.num_feat = num_feat
self.input_resolution = input_resolution
m = []
m.append(nn.Conv2d(num_feat, (scale ** 2) * num_out_ch, 3, 1, 1))
m.append(nn.PixelShuffle(scale))
super(UpsampleOneStep, self).__init__(*m)
def flops(self):
H, W = self.input_resolution
flops = H * W * self.num_feat * 3 * 9
return flops
class SwinIR(nn.Module):
r""" SwinIR
A PyTorch impl of : `SwinIR: Image Restoration Using Swin Transformer`, based on Swin Transformer.
Args:
img_size (int | tuple(int)): Input image size. Default 64
patch_size (int | tuple(int)): Patch size. Default: 1
in_chans (int): Number of input image channels. Default: 3
embed_dim (int): Patch embedding dimension. Default: 96
depths (tuple(int)): Depth of each Swin Transformer layer.
num_heads (tuple(int)): Number of attention heads in different layers.
window_size (int): Window size. Default: 7
mlp_ratio (float): Ratio of mlp hidden dim to embedding dim. Default: 4
qkv_bias (bool): If True, add a learnable bias to query, key, value. Default: True
qk_scale (float): Override default qk scale of head_dim ** -0.5 if set. Default: None
drop_rate (float): Dropout rate. Default: 0
attn_drop_rate (float): Attention dropout rate. Default: 0
drop_path_rate (float): Stochastic depth rate. Default: 0.1
norm_layer (nn.Module): Normalization layer. Default: nn.LayerNorm.
ape (bool): If True, add absolute position embedding to the patch embedding. Default: False
patch_norm (bool): If True, add normalization after patch embedding. Default: True
use_checkpoint (bool): Whether to use checkpointing to save memory. Default: False
upscale: Upscale factor. 2/3/4/8 for image SR, 1 for denoising and compress artifact reduction
img_range: Image range. 1. or 255.
upsampler: The reconstruction reconstruction module. 'pixelshuffle'/'pixelshuffledirect'/'nearest+conv'/None
resi_connection: The convolutional block before residual connection. '1conv'/'3conv'
"""
def __init__(self, img_size=64, patch_size=1, in_chans=3,
embed_dim=96, depths=[6, 6, 6, 6], num_heads=[6, 6, 6, 6],
window_size=7, mlp_ratio=4., qkv_bias=True, qk_scale=None,
drop_rate=0., attn_drop_rate=0., drop_path_rate=0.1,
norm_layer=nn.LayerNorm, ape=False, patch_norm=True,
use_checkpoint=False, upscale=2, img_range=1., upsampler='', resi_connection='1conv',
**kwargs):
super(SwinIR, self).__init__()
num_in_ch = in_chans
num_out_ch = in_chans
num_feat = 64
self.img_range = img_range
if in_chans == 3:
rgb_mean = (0.4488, 0.4371, 0.4040)
self.mean = torch.Tensor(rgb_mean).view(1, 3, 1, 1)
else:
self.mean = torch.zeros(1, 1, 1, 1)
self.upscale = upscale
self.upsampler = upsampler
self.window_size = window_size
#####################################################################################################
################################### 1, shallow feature extraction ###################################
self.conv_first = nn.Conv2d(num_in_ch, embed_dim, 3, 1, 1)
#####################################################################################################
################################### 2, deep feature extraction ######################################
self.num_layers = len(depths)
self.embed_dim = embed_dim
self.ape = ape
self.patch_norm = patch_norm
self.num_features = embed_dim
self.mlp_ratio = mlp_ratio
# split image into non-overlapping patches
self.patch_embed = PatchEmbed(
img_size=img_size, patch_size=patch_size, in_chans=embed_dim, embed_dim=embed_dim,
norm_layer=norm_layer if self.patch_norm else None)
num_patches = self.patch_embed.num_patches
patches_resolution = self.patch_embed.patches_resolution
self.patches_resolution = patches_resolution
# merge non-overlapping patches into image
self.patch_unembed = PatchUnEmbed(
img_size=img_size, patch_size=patch_size, in_chans=embed_dim, embed_dim=embed_dim,
norm_layer=norm_layer if self.patch_norm else None)
# absolute position embedding
if self.ape:
self.absolute_pos_embed = nn.Parameter(torch.zeros(1, num_patches, embed_dim))
trunc_normal_(self.absolute_pos_embed, std=.02)
self.pos_drop = nn.Dropout(p=drop_rate)
# stochastic depth
dpr = [x.item() for x in torch.linspace(0, drop_path_rate, sum(depths))] # stochastic depth decay rule
# build Residual Swin Transformer blocks (RSTB)
self.layers = nn.ModuleList()
for i_layer in range(self.num_layers):
layer = RSTB(dim=embed_dim,
input_resolution=(patches_resolution[0],
patches_resolution[1]),
depth=depths[i_layer],
num_heads=num_heads[i_layer],
window_size=window_size,
mlp_ratio=self.mlp_ratio,
qkv_bias=qkv_bias, qk_scale=qk_scale,
drop=drop_rate, attn_drop=attn_drop_rate,
drop_path=dpr[sum(depths[:i_layer]):sum(depths[:i_layer + 1])], # no impact on SR results
norm_layer=norm_layer,
downsample=None,
use_checkpoint=use_checkpoint,
img_size=img_size,
patch_size=patch_size,
resi_connection=resi_connection
)
self.layers.append(layer)
self.norm = norm_layer(self.num_features)
# build the last conv layer in deep feature extraction
if resi_connection == '1conv':
self.conv_after_body = nn.Conv2d(embed_dim, embed_dim, 3, 1, 1)
elif resi_connection == '3conv':
# to save parameters and memory
self.conv_after_body = nn.Sequential(nn.Conv2d(embed_dim, embed_dim // 4, 3, 1, 1),
nn.LeakyReLU(negative_slope=0.2, inplace=True),
nn.Conv2d(embed_dim // 4, embed_dim // 4, 1, 1, 0),
nn.LeakyReLU(negative_slope=0.2, inplace=True),
nn.Conv2d(embed_dim // 4, embed_dim, 3, 1, 1))
#####################################################################################################
################################ 3, high quality image reconstruction ################################
if self.upsampler == 'pixelshuffle':
# for classical SR
self.conv_before_upsample = nn.Sequential(nn.Conv2d(embed_dim, num_feat, 3, 1, 1),
nn.LeakyReLU(inplace=True))
self.upsample = Upsample(upscale, num_feat)
self.conv_last = nn.Conv2d(num_feat, num_out_ch, 3, 1, 1)
elif self.upsampler == 'pixelshuffledirect':
# for lightweight SR (to save parameters)
self.upsample = UpsampleOneStep(upscale, embed_dim, num_out_ch,
(patches_resolution[0], patches_resolution[1]))
elif self.upsampler == 'nearest+conv':
# for real-world SR (less artifacts)
self.conv_before_upsample = nn.Sequential(nn.Conv2d(embed_dim, num_feat, 3, 1, 1),
nn.LeakyReLU(inplace=True))
self.conv_up1 = nn.Conv2d(num_feat, num_feat, 3, 1, 1)
if self.upscale == 4:
self.conv_up2 = nn.Conv2d(num_feat, num_feat, 3, 1, 1)
self.conv_hr = nn.Conv2d(num_feat, num_feat, 3, 1, 1)
self.conv_last = nn.Conv2d(num_feat, num_out_ch, 3, 1, 1)
self.lrelu = nn.LeakyReLU(negative_slope=0.2, inplace=True)
else:
# for image denoising and JPEG compression artifact reduction
self.conv_last = nn.Conv2d(embed_dim, num_out_ch, 3, 1, 1)
self.apply(self._init_weights)
def _init_weights(self, m):
if isinstance(m, nn.Linear):
trunc_normal_(m.weight, std=.02)
if isinstance(m, nn.Linear) and m.bias is not None:
nn.init.constant_(m.bias, 0)
elif isinstance(m, nn.LayerNorm):
nn.init.constant_(m.bias, 0)
nn.init.constant_(m.weight, 1.0)
@torch.jit.ignore
def no_weight_decay(self):
return {'absolute_pos_embed'}
@torch.jit.ignore
def no_weight_decay_keywords(self):
return {'relative_position_bias_table'}
def check_image_size(self, x):
_, _, h, w = x.size()
mod_pad_h = (self.window_size - h % self.window_size) % self.window_size
mod_pad_w = (self.window_size - w % self.window_size) % self.window_size
x = F.pad(x, (0, mod_pad_w, 0, mod_pad_h), 'reflect')
return x
def forward_features(self, x):
x_size = (x.shape[2], x.shape[3])
x = self.patch_embed(x)
if self.ape:
x = x + self.absolute_pos_embed
x = self.pos_drop(x)
for layer in self.layers:
x = layer(x, x_size)
x = self.norm(x) # B L C
x = self.patch_unembed(x, x_size)
return x
def forward(self, x):
H, W = x.shape[2:]
x = self.check_image_size(x)
self.mean = self.mean.type_as(x)
x = (x - self.mean) * self.img_range
if self.upsampler == 'pixelshuffle':
# for classical SR
x = self.conv_first(x)
x = self.conv_after_body(self.forward_features(x)) + x
x = self.conv_before_upsample(x)
x = self.conv_last(self.upsample(x))
elif self.upsampler == 'pixelshuffledirect':
# for lightweight SR
x = self.conv_first(x)
x = self.conv_after_body(self.forward_features(x)) + x
x = self.upsample(x)
elif self.upsampler == 'nearest+conv':
# for real-world SR
x = self.conv_first(x)
x = self.conv_after_body(self.forward_features(x)) + x
x = self.conv_before_upsample(x)
x = self.lrelu(self.conv_up1(torch.nn.functional.interpolate(x, scale_factor=2, mode='nearest')))
if self.upscale == 4:
x = self.lrelu(self.conv_up2(torch.nn.functional.interpolate(x, scale_factor=2, mode='nearest')))
x = self.conv_last(self.lrelu(self.conv_hr(x)))
else:
# for image denoising and JPEG compression artifact reduction
x_first = self.conv_first(x)
res = self.conv_after_body(self.forward_features(x_first)) + x_first
x = x + self.conv_last(res)
x = x / self.img_range + self.mean
return x[:, :, :H*self.upscale, :W*self.upscale]
def flops(self):
flops = 0
H, W = self.patches_resolution
flops += H * W * 3 * self.embed_dim * 9
flops += self.patch_embed.flops()
for i, layer in enumerate(self.layers):
flops += layer.flops()
flops += H * W * 3 * self.embed_dim * self.embed_dim
flops += self.upsample.flops()
return flops
if __name__ == '__main__':
upscale = 4
window_size = 8
height = (1024 // upscale // window_size + 1) * window_size
width = (720 // upscale // window_size + 1) * window_size
model = SwinIR(upscale=2, img_size=(height, width),
window_size=window_size, img_range=1., depths=[6, 6, 6, 6],
embed_dim=60, num_heads=[6, 6, 6, 6], mlp_ratio=2, upsampler='pixelshuffledirect')
print(model)
print(height, width, model.flops() / 1e9)
x = torch.randn((1, 3, height, width))
x = model(x)
print(x.shape)
+10 -7
View File
@@ -6,7 +6,7 @@ import modules.processing as processing
from modules.ui import plaintext_to_html
def txt2img(prompt: str, negative_prompt: str, prompt_style: str, prompt_style2: str, steps: int, sampler_index: int, restore_faces: bool, tiling: bool, n_iter: int, batch_size: int, cfg_scale: float, seed: int, subseed: int, subseed_strength: float, seed_resize_from_h: int, seed_resize_from_w: int, height: int, width: int, enable_hr: bool, scale_latent: bool, denoising_strength: float, *args):
def txt2img(prompt: str, negative_prompt: str, prompt_style: str, prompt_style2: str, steps: int, sampler_index: int, restore_faces: bool, tiling: bool, n_iter: int, batch_size: int, cfg_scale: float, seed: int, subseed: int, subseed_strength: float, seed_resize_from_h: int, seed_resize_from_w: int, seed_enable_extras: bool, height: int, width: int, enable_hr: bool, scale_latent: bool, denoising_strength: float, *args):
p = StableDiffusionProcessingTxt2Img(
sd_model=shared.sd_model,
outpath_samples=opts.outdir_samples or opts.outdir_txt2img_samples,
@@ -19,6 +19,7 @@ def txt2img(prompt: str, negative_prompt: str, prompt_style: str, prompt_style2:
subseed_strength=subseed_strength,
seed_resize_from_h=seed_resize_from_h,
seed_resize_from_w=seed_resize_from_w,
seed_enable_extras=seed_enable_extras,
sampler_index=sampler_index,
batch_size=batch_size,
n_iter=n_iter,
@@ -29,19 +30,21 @@ def txt2img(prompt: str, negative_prompt: str, prompt_style: str, prompt_style2:
restore_faces=restore_faces,
tiling=tiling,
enable_hr=enable_hr,
scale_latent=scale_latent,
denoising_strength=denoising_strength,
scale_latent=scale_latent if enable_hr else None,
denoising_strength=denoising_strength if enable_hr else None,
)
print(f"\ntxt2img: {prompt}", file=shared.progress_print_out)
processed = modules.scripts.scripts_txt2img.run(p, *args)
if processed is not None:
pass
else:
if processed is None:
processed = process_images(p)
shared.total_tqdm.clear()
return processed.images, processed.js(), plaintext_to_html(processed.info)
generation_info_js = processed.js()
if opts.samples_log_stdout:
print(generation_info_js)
return processed.images, generation_info_js, plaintext_to_html(processed.info)
+278 -190
View File
@@ -22,11 +22,12 @@ from modules.paths import script_path
from modules.shared import opts, cmd_opts
import modules.shared as shared
from modules.sd_samplers import samplers, samplers_for_img2img
import modules.realesrgan_model as realesrgan
import modules.ldsr_model
import modules.scripts
import modules.gfpgan_model
import modules.codeformer_model
import modules.styles
import modules.generation_parameters_copypaste
# this is a fix for Windows users. Without it, javascript files will be served with text/html content-type and the bowser will not show any UI
mimetypes.init()
@@ -48,7 +49,6 @@ sample_img2img = sample_img2img if os.path.exists(sample_img2img) else None
css_hide_progressbar = """
.wrap .m-12 svg { display:none!important; }
.wrap .m-12::before { content:"Loading..." }
.progress-bar { display:none!important; }
.meta-text { display:none!important; }
"""
@@ -57,6 +57,8 @@ css_hide_progressbar = """
# Important that they exactly match script.js for tooltip to work.
random_symbol = '\U0001f3b2\ufe0f' # 🎲️
reuse_symbol = '\u267b\ufe0f' # ♻️
art_symbol = '\U0001f3a8' # 🎨
paste_symbol = '\u2199\ufe0f' # ↙
def plaintext_to_html(text):
@@ -166,7 +168,7 @@ def wrap_gradio_call(func):
return f
def check_progress_call():
def check_progress_call(id_part):
if shared.state.job_count == 0:
return "", gr_show(False), gr_show(False)
@@ -195,20 +197,20 @@ def check_progress_call():
image = shared.state.current_image
if image is None or progress >= 1:
if image is None:
image = gr.update(value=None)
else:
preview_visibility = gr_show(True)
return f"<span style='display: none'>{time.time()}</span><p>{progressbar}</p>", preview_visibility, image
return f"<span id='{id_part}_progress_span' style='display: none'>{time.time()}</span><p>{progressbar}</p>", preview_visibility, image
def check_progress_call_initial():
def check_progress_call_initial(id_part):
shared.state.job_count = -1
shared.state.current_latent = None
shared.state.current_image = None
return check_progress_call()
return check_progress_call(id_part)
def roll_artist(prompt):
@@ -236,8 +238,7 @@ def add_style(name: str, prompt: str, negative_prompt: str):
# reserialize all styles every time we save them
shared.prompt_styles.save_styles(shared.styles_filename)
update = {"visible": True, "choices": list(shared.prompt_styles.styles), "__type__": "update"}
return [update, update, update, update]
return [gr.Dropdown.update(visible=True, choices=list(shared.prompt_styles.styles)) for _ in range(4)]
def apply_styles(prompt, prompt_neg, style1_name, style2_name):
@@ -257,7 +258,7 @@ def create_seed_inputs():
with gr.Row():
with gr.Box():
with gr.Row(elem_id='seed_row'):
seed = gr.Number(label='Seed', value=-1)
seed = (gr.Textbox if cmd_opts.use_textbox_seed else gr.Number)(label='Seed', value=-1)
seed.style(container=False)
random_seed = gr.Button(random_symbol, elem_id='random_seed')
reuse_seed = gr.Button(reuse_symbol, elem_id='reuse_seed')
@@ -291,7 +292,7 @@ def create_seed_inputs():
seed_checkbox.change(change_visibility, show_progress=False, inputs=[seed_checkbox], outputs=seed_extras)
return seed, reuse_seed, subseed, reuse_subseed, subseed_strength, seed_resize_from_h, seed_resize_from_w
return seed, reuse_seed, subseed, reuse_subseed, subseed_strength, seed_resize_from_h, seed_resize_from_w, seed_checkbox
def connect_reuse_seed(seed: gr.Number, reuse_seed: gr.Button, generation_info: gr.Textbox, dummy_component, is_subseed):
@@ -329,28 +330,40 @@ def connect_reuse_seed(seed: gr.Number, reuse_seed: gr.Button, generation_info:
def create_toprow(is_img2img):
id_part = "img2img" if is_img2img else "txt2img"
with gr.Row(elem_id="toprow"):
with gr.Column(scale=4):
with gr.Row():
with gr.Column(scale=8):
with gr.Column(scale=80):
with gr.Row():
prompt = gr.Textbox(label="Prompt", elem_id="prompt", show_label=False, placeholder="Prompt", lines=2)
roll = gr.Button('Roll', elem_id="roll", visible=len(shared.artist_db.artists) > 0)
with gr.Column(scale=1, elem_id="style_pos_col"):
prompt_style = gr.Dropdown(label="Style 1", elem_id="style_index", choices=[k for k, v in shared.prompt_styles.styles.items()], value=next(iter(shared.prompt_styles.styles.keys())), visible=len(shared.prompt_styles.styles) > 1)
with gr.Column(scale=1, elem_id="roll_col"):
roll = gr.Button(value=art_symbol, elem_id="roll", visible=len(shared.artist_db.artists) > 0)
paste = gr.Button(value=paste_symbol, elem_id="paste")
with gr.Column(scale=10, elem_id="style_pos_col"):
prompt_style = gr.Dropdown(label="Style 1", elem_id=f"{id_part}_style_index", choices=[k for k, v in shared.prompt_styles.styles.items()], value=next(iter(shared.prompt_styles.styles.keys())), visible=len(shared.prompt_styles.styles) > 1)
with gr.Row():
with gr.Column(scale=8):
negative_prompt = gr.Textbox(label="Negative prompt", elem_id="negative_prompt", show_label=False, placeholder="Negative prompt", lines=2)
with gr.Column(scale=1, elem_id="style_neg_col"):
prompt_style2 = gr.Dropdown(label="Style 2", elem_id="style2_index", choices=[k for k, v in shared.prompt_styles.styles.items()], value=next(iter(shared.prompt_styles.styles.keys())), visible=len(shared.prompt_styles.styles) > 1)
prompt_style2 = gr.Dropdown(label="Style 2", elem_id=f"{id_part}_style2_index", choices=[k for k, v in shared.prompt_styles.styles.items()], value=next(iter(shared.prompt_styles.styles.keys())), visible=len(shared.prompt_styles.styles) > 1)
with gr.Column(scale=1):
with gr.Row():
interrupt = gr.Button('Interrupt', elem_id=f"{id_part}_interrupt")
submit = gr.Button('Generate', elem_id="generate", variant='primary')
interrupt.click(
fn=lambda: shared.state.interrupt(),
inputs=[],
outputs=[],
)
with gr.Row():
if is_img2img:
interrogate = gr.Button('Interrogate', elem_id="interrogate")
@@ -359,21 +372,21 @@ def create_toprow(is_img2img):
prompt_style_apply = gr.Button('Apply style', elem_id="style_apply")
save_style = gr.Button('Create style', elem_id="style_create")
return prompt, roll, prompt_style, negative_prompt, prompt_style2, submit, interrogate, prompt_style_apply, save_style
return prompt, roll, prompt_style, negative_prompt, prompt_style2, submit, interrogate, prompt_style_apply, save_style, paste
def setup_progressbar(progressbar, preview):
check_progress = gr.Button('Check progress', elem_id="check_progress", visible=False)
def setup_progressbar(progressbar, preview, id_part):
check_progress = gr.Button('Check progress', elem_id=f"{id_part}_check_progress", visible=False)
check_progress.click(
fn=check_progress_call,
fn=lambda: check_progress_call(id_part),
show_progress=False,
inputs=[],
outputs=[progressbar, preview, preview],
)
check_progress_initial = gr.Button('Check progress (first)', elem_id="check_progress_initial", visible=False)
check_progress_initial = gr.Button('Check progress (first)', elem_id=f"{id_part}_check_progress_initial", visible=False)
check_progress_initial.click(
fn=check_progress_call_initial,
fn=lambda: check_progress_call_initial(id_part),
show_progress=False,
inputs=[],
outputs=[progressbar, preview, preview],
@@ -382,21 +395,34 @@ def setup_progressbar(progressbar, preview):
def create_ui(txt2img, img2img, run_extras, run_pnginfo):
with gr.Blocks(analytics_enabled=False) as txt2img_interface:
txt2img_prompt, roll, txt2img_prompt_style, txt2img_negative_prompt, txt2img_prompt_style2, submit, _, txt2img_prompt_style_apply, txt2img_save_style = create_toprow(is_img2img=False)
txt2img_prompt, roll, txt2img_prompt_style, txt2img_negative_prompt, txt2img_prompt_style2, submit, _, txt2img_prompt_style_apply, txt2img_save_style, paste = create_toprow(is_img2img=False)
dummy_component = gr.Label(visible=False)
with gr.Row(elem_id='txt2img_progress_row'):
with gr.Column(scale=1):
pass
with gr.Column(scale=1):
progressbar = gr.HTML(elem_id="txt2img_progressbar")
txt2img_preview = gr.Image(elem_id='txt2img_preview', visible=False)
setup_progressbar(progressbar, txt2img_preview, 'txt2img')
with gr.Row().style(equal_height=False):
with gr.Column(variant='panel'):
steps = gr.Slider(minimum=1, maximum=150, step=1, label="Sampling Steps", value=20)
sampler_index = gr.Radio(label='Sampling method', elem_id="txt2img_sampling", choices=[x.name for x in samplers], value=samplers[0].name, type="index")
with gr.Group():
width = gr.Slider(minimum=64, maximum=2048, step=64, label="Width", value=512)
height = gr.Slider(minimum=64, maximum=2048, step=64, label="Height", value=512)
with gr.Row():
restore_faces = gr.Checkbox(label='Restore faces', value=False, visible=len(shared.face_restorers) > 1)
tiling = gr.Checkbox(label='Tiling', value=False)
enable_hr = gr.Checkbox(label='Highres. fix', value=False)
with gr.Row(visible=False) as hr_options:
scale_latent = gr.Checkbox(label='Scale latent', value=True)
scale_latent = gr.Checkbox(label='Scale latent', value=False)
denoising_strength = gr.Slider(minimum=0.0, maximum=1.0, step=0.01, label='Denoising strength', value=0.7)
with gr.Row():
@@ -405,23 +431,16 @@ def create_ui(txt2img, img2img, run_extras, run_pnginfo):
cfg_scale = gr.Slider(minimum=1.0, maximum=30.0, step=0.5, label='CFG Scale', value=7.0)
with gr.Group():
width = gr.Slider(minimum=64, maximum=2048, step=64, label="Width", value=512)
height = gr.Slider(minimum=64, maximum=2048, step=64, label="Height", value=512)
seed, reuse_seed, subseed, reuse_subseed, subseed_strength, seed_resize_from_h, seed_resize_from_w = create_seed_inputs()
seed, reuse_seed, subseed, reuse_subseed, subseed_strength, seed_resize_from_h, seed_resize_from_w, seed_checkbox = create_seed_inputs()
with gr.Group():
custom_inputs = modules.scripts.scripts_txt2img.setup_ui(is_img2img=False)
with gr.Column(variant='panel'):
progressbar = gr.HTML(elem_id="progressbar")
with gr.Group():
txt2img_preview = gr.Image(elem_id='txt2img_preview', visible=False)
txt2img_gallery = gr.Gallery(label='Output', elem_id='txt2img_gallery').style(grid=4)
setup_progressbar(progressbar, txt2img_preview)
txt2img_gallery = gr.Gallery(label='Output', show_label=False, elem_id='txt2img_gallery').style(grid=4)
with gr.Group():
with gr.Row():
@@ -429,7 +448,6 @@ def create_ui(txt2img, img2img, run_extras, run_pnginfo):
send_to_img2img = gr.Button('Send to img2img')
send_to_inpaint = gr.Button('Send to inpaint')
send_to_extras = gr.Button('Send to extras')
interrupt = gr.Button('Interrupt')
with gr.Group():
html_info = gr.HTML()
@@ -454,7 +472,7 @@ def create_ui(txt2img, img2img, run_extras, run_pnginfo):
batch_size,
cfg_scale,
seed,
subseed, subseed_strength, seed_resize_from_h, seed_resize_from_w,
subseed, subseed_strength, seed_resize_from_h, seed_resize_from_w, seed_checkbox,
height,
width,
enable_hr,
@@ -478,12 +496,6 @@ def create_ui(txt2img, img2img, run_extras, run_pnginfo):
outputs=[hr_options],
)
interrupt.click(
fn=lambda: shared.state.interrupt(),
inputs=[],
outputs=[],
)
save.click(
fn=wrap_gradio_call(save_files),
_js="(x, y, z) => [x, y, selected_gallery_index()]",
@@ -509,38 +521,83 @@ def create_ui(txt2img, img2img, run_extras, run_pnginfo):
]
)
txt2img_paste_fields = [
(txt2img_prompt, "Prompt"),
(txt2img_negative_prompt, "Negative prompt"),
(steps, "Steps"),
(sampler_index, "Sampler"),
(restore_faces, "Face restoration"),
(cfg_scale, "CFG scale"),
(seed, "Seed"),
(width, "Size-1"),
(height, "Size-2"),
(batch_size, "Batch size"),
(subseed, "Variation seed"),
(subseed_strength, "Variation seed strength"),
(seed_resize_from_w, "Seed resize from-1"),
(seed_resize_from_h, "Seed resize from-2"),
(denoising_strength, "Denoising strength"),
(enable_hr, lambda d: "Denoising strength" in d),
(hr_options, lambda d: gr.Row.update(visible="Denoising strength" in d)),
]
modules.generation_parameters_copypaste.connect_paste(paste, txt2img_paste_fields, txt2img_prompt)
with gr.Blocks(analytics_enabled=False) as img2img_interface:
img2img_prompt, roll, img2img_prompt_style, img2img_negative_prompt, img2img_prompt_style2, submit, img2img_interrogate, img2img_prompt_style_apply, img2img_save_style = create_toprow(is_img2img=True)
img2img_prompt, roll, img2img_prompt_style, img2img_negative_prompt, img2img_prompt_style2, submit, img2img_interrogate, img2img_prompt_style_apply, img2img_save_style, paste = create_toprow(is_img2img=True)
with gr.Row(elem_id='img2img_progress_row'):
with gr.Column(scale=1):
pass
with gr.Column(scale=1):
progressbar = gr.HTML(elem_id="img2img_progressbar")
img2img_preview = gr.Image(elem_id='img2img_preview', visible=False)
setup_progressbar(progressbar, img2img_preview, 'img2img')
with gr.Row().style(equal_height=False):
with gr.Column(variant='panel'):
with gr.Group():
switch_mode = gr.Radio(label='Mode', elem_id="img2img_mode", choices=['Redraw whole image', 'Inpaint a part of image', 'SD upscale'], value='Redraw whole image', type="index", show_label=False)
init_img = gr.Image(label="Image for img2img", source="upload", interactive=True, type="pil")
init_img_with_mask = gr.Image(label="Image for inpainting with mask", elem_id="img2maskimg", source="upload", interactive=True, type="pil", tool="sketch", visible=False, image_mode="RGBA")
init_mask = gr.Image(label="Mask", source="upload", interactive=True, type="pil", visible=False)
init_img_with_mask_comment = gr.HTML(elem_id="mask_bug_info", value="<small>if the editor shows ERROR, switch to another tab and back, then to another img2img mode above and back</small>", visible=False)
with gr.Row():
resize_mode = gr.Radio(label="Resize mode", elem_id="resize_mode", show_label=False, choices=["Just resize", "Crop and resize", "Resize and fill"], type="index", value="Just resize")
mask_mode = gr.Radio(label="Mask mode", show_label=False, choices=["Draw mask", "Upload mask"], type="index", value="Draw mask")
with gr.Tabs(elem_id="mode_img2img") as tabs_img2img_mode:
with gr.TabItem('img2img', id='img2img'):
init_img = gr.Image(label="Image for img2img", show_label=False, source="upload", interactive=True, type="pil")
with gr.TabItem('Inpaint', id='inpaint'):
init_img_with_mask = gr.Image(label="Image for inpainting with mask", show_label=False, elem_id="img2maskimg", source="upload", interactive=True, type="pil", tool="sketch", image_mode="RGBA")
init_img_inpaint = gr.Image(label="Image for img2img", show_label=False, source="upload", interactive=True, type="pil", visible=False)
init_mask_inpaint = gr.Image(label="Mask", source="upload", interactive=True, type="pil", visible=False)
mask_blur = gr.Slider(label='Mask blur', minimum=0, maximum=64, step=1, value=4)
with gr.Row():
mask_mode = gr.Radio(label="Mask mode", show_label=False, choices=["Draw mask", "Upload mask"], type="index", value="Draw mask")
inpainting_mask_invert = gr.Radio(label='Masking mode', show_label=False, choices=['Inpaint masked', 'Inpaint not masked'], value='Inpaint masked', type="index")
inpainting_fill = gr.Radio(label='Masked content', choices=['fill', 'original', 'latent noise', 'latent nothing'], value='fill', type="index")
with gr.Row():
inpaint_full_res = gr.Checkbox(label='Inpaint at full resolution', value=False)
inpaint_full_res_padding = gr.Slider(label='Inpaint at full resolution padding, pixels', minimum=0, maximum=256, step=4, value=32)
with gr.TabItem('Batch img2img', id='batch'):
hidden = '<br>Disabled when launched with --hide-ui-dir-config.' if shared.cmd_opts.hide_ui_dir_config else ''
gr.HTML(f"<p class=\"text-gray-500\">Process images in a directory on the same machine where the server is running.{hidden}</p>")
img2img_batch_input_dir = gr.Textbox(label="Input directory", **shared.hide_dirs)
img2img_batch_output_dir = gr.Textbox(label="Output directory", **shared.hide_dirs)
with gr.Row():
resize_mode = gr.Radio(label="Resize mode", elem_id="resize_mode", show_label=False, choices=["Just resize", "Crop and resize", "Resize and fill"], type="index", value="Just resize")
steps = gr.Slider(minimum=1, maximum=150, step=1, label="Sampling Steps", value=20)
sampler_index = gr.Radio(label='Sampling method', choices=[x.name for x in samplers_for_img2img], value=samplers_for_img2img[0].name, type="index")
mask_blur = gr.Slider(label='Mask blur', minimum=0, maximum=64, step=1, value=4, visible=False)
inpainting_fill = gr.Radio(label='Masked content', choices=['fill', 'original', 'latent noise', 'latent nothing'], value='fill', type="index", visible=False)
with gr.Row():
inpaint_full_res = gr.Checkbox(label='Inpaint at full resolution', value=False, visible=False)
inpainting_mask_invert = gr.Radio(label='Masking mode', choices=['Inpaint masked', 'Inpaint not masked'], value='Inpaint masked', type="index", visible=False)
with gr.Group():
width = gr.Slider(minimum=64, maximum=2048, step=64, label="Width", value=512)
height = gr.Slider(minimum=64, maximum=2048, step=64, label="Height", value=512)
with gr.Row():
restore_faces = gr.Checkbox(label='Restore faces', value=False, visible=len(shared.face_restorers) > 1)
tiling = gr.Checkbox(label='Tiling', value=False)
sd_upscale_overlap = gr.Slider(minimum=0, maximum=256, step=16, label='Tile overlap', value=64, visible=False)
with gr.Row():
sd_upscale_upscaler_name = gr.Radio(label='Upscaler', choices=[x.name for x in shared.sd_upscalers], value=shared.sd_upscalers[0].name, type="index", visible=False)
with gr.Row():
batch_count = gr.Slider(minimum=1, maximum=cmd_opts.max_batch_count, step=1, label='Batch count', value=1)
@@ -550,23 +607,16 @@ def create_ui(txt2img, img2img, run_extras, run_pnginfo):
cfg_scale = gr.Slider(minimum=1.0, maximum=30.0, step=0.5, label='CFG Scale', value=7.0)
denoising_strength = gr.Slider(minimum=0.0, maximum=1.0, step=0.01, label='Denoising strength', value=0.75)
with gr.Group():
width = gr.Slider(minimum=64, maximum=2048, step=64, label="Width", value=512)
height = gr.Slider(minimum=64, maximum=2048, step=64, label="Height", value=512)
seed, reuse_seed, subseed, reuse_subseed, subseed_strength, seed_resize_from_h, seed_resize_from_w = create_seed_inputs()
seed, reuse_seed, subseed, reuse_subseed, subseed_strength, seed_resize_from_h, seed_resize_from_w, seed_checkbox = create_seed_inputs()
with gr.Group():
custom_inputs = modules.scripts.scripts_img2img.setup_ui(is_img2img=True)
with gr.Column(variant='panel'):
progressbar = gr.HTML(elem_id="progressbar")
with gr.Group():
img2img_preview = gr.Image(elem_id='img2img_preview', visible=False)
img2img_gallery = gr.Gallery(label='Output', elem_id='img2img_gallery').style(grid=4)
setup_progressbar(progressbar, img2img_preview)
img2img_gallery = gr.Gallery(label='Output', show_label=False, elem_id='img2img_gallery').style(grid=4)
with gr.Group():
with gr.Row():
@@ -574,9 +624,6 @@ def create_ui(txt2img, img2img, run_extras, run_pnginfo):
img2img_send_to_img2img = gr.Button('Send to img2img')
img2img_send_to_inpaint = gr.Button('Send to inpaint')
img2img_send_to_extras = gr.Button('Send to extras')
interrupt = gr.Button('Interrupt')
img2img_save_style = gr.Button('Save prompt as style')
with gr.Group():
html_info = gr.HTML()
@@ -585,70 +632,33 @@ def create_ui(txt2img, img2img, run_extras, run_pnginfo):
connect_reuse_seed(seed, reuse_seed, generation_info, dummy_component, is_subseed=False)
connect_reuse_seed(subseed, reuse_subseed, generation_info, dummy_component, is_subseed=True)
def apply_mode(mode, uploadmask):
is_classic = mode == 0
is_inpaint = mode == 1
is_upscale = mode == 2
return {
init_img: gr_show(not is_inpaint or (is_inpaint and uploadmask == 1)),
init_img_with_mask: gr_show(is_inpaint and uploadmask == 0),
init_img_with_mask_comment: gr_show(is_inpaint and uploadmask == 0),
init_mask: gr_show(is_inpaint and uploadmask == 1),
mask_mode: gr_show(is_inpaint),
mask_blur: gr_show(is_inpaint),
inpainting_fill: gr_show(is_inpaint),
sd_upscale_upscaler_name: gr_show(is_upscale),
sd_upscale_overlap: gr_show(is_upscale),
inpaint_full_res: gr_show(is_inpaint),
inpainting_mask_invert: gr_show(is_inpaint),
img2img_interrogate: gr_show(not is_inpaint),
}
switch_mode.change(
apply_mode,
inputs=[switch_mode, mask_mode],
outputs=[
init_img,
init_img_with_mask,
init_img_with_mask_comment,
init_mask,
mask_mode,
mask_blur,
inpainting_fill,
sd_upscale_upscaler_name,
sd_upscale_overlap,
inpaint_full_res,
inpainting_mask_invert,
img2img_interrogate,
]
)
mask_mode.change(
lambda mode: {
init_img: gr_show(mode == 1),
lambda mode, img: {
init_img_with_mask: gr_show(mode == 0),
init_mask: gr_show(mode == 1),
init_img_inpaint: gr_show(mode == 1),
init_mask_inpaint: gr_show(mode == 1),
},
inputs=[mask_mode],
inputs=[mask_mode, init_img_with_mask],
outputs=[
init_img,
init_img_with_mask,
init_mask,
init_img_inpaint,
init_mask_inpaint,
],
)
img2img_args = dict(
fn=img2img,
_js="submit",
_js="submit_img2img",
inputs=[
dummy_component,
img2img_prompt,
img2img_negative_prompt,
img2img_prompt_style,
img2img_prompt_style2,
init_img,
init_img_with_mask,
init_mask,
init_img_inpaint,
init_mask_inpaint,
mask_mode,
steps,
sampler_index,
@@ -656,20 +666,20 @@ def create_ui(txt2img, img2img, run_extras, run_pnginfo):
inpainting_fill,
restore_faces,
tiling,
switch_mode,
batch_count,
batch_size,
cfg_scale,
denoising_strength,
seed,
subseed, subseed_strength, seed_resize_from_h, seed_resize_from_w,
subseed, subseed_strength, seed_resize_from_h, seed_resize_from_w, seed_checkbox,
height,
width,
resize_mode,
sd_upscale_upscaler_name,
sd_upscale_overlap,
inpaint_full_res,
inpaint_full_res_padding,
inpainting_mask_invert,
img2img_batch_input_dir,
img2img_batch_output_dir,
] + custom_inputs,
outputs=[
img2img_gallery,
@@ -688,12 +698,6 @@ def create_ui(txt2img, img2img, run_extras, run_pnginfo):
outputs=[img2img_prompt],
)
interrupt.click(
fn=lambda: shared.state.interrupt(),
inputs=[],
outputs=[],
)
save.click(
fn=wrap_gradio_call(save_files),
_js="(x, y, z) => [x, y, selected_gallery_index()]",
@@ -739,12 +743,31 @@ def create_ui(txt2img, img2img, run_extras, run_pnginfo):
outputs=[prompt, negative_prompt, style1, style2],
)
img2img_paste_fields = [
(img2img_prompt, "Prompt"),
(img2img_negative_prompt, "Negative prompt"),
(steps, "Steps"),
(sampler_index, "Sampler"),
(restore_faces, "Face restoration"),
(cfg_scale, "CFG scale"),
(seed, "Seed"),
(width, "Size-1"),
(height, "Size-2"),
(batch_size, "Batch size"),
(subseed, "Variation seed"),
(subseed_strength, "Variation seed strength"),
(seed_resize_from_w, "Seed resize from-1"),
(seed_resize_from_h, "Seed resize from-2"),
(denoising_strength, "Denoising strength"),
]
modules.generation_parameters_copypaste.connect_paste(paste, img2img_paste_fields, img2img_prompt)
with gr.Blocks(analytics_enabled=False) as extras_interface:
with gr.Row().style(equal_height=False):
with gr.Column(variant='panel'):
with gr.Tabs():
with gr.Tabs(elem_id="mode_extras"):
with gr.TabItem('Single Image'):
image = gr.Image(label="Source", source="upload", interactive=True, type="pil")
extras_image = gr.Image(label="Source", source="upload", interactive=True, type="pil")
with gr.TabItem('Batch Process'):
image_batch = gr.File(label="Batch Process", file_count="multiple", interactive=True, type="file")
@@ -768,14 +791,18 @@ def create_ui(txt2img, img2img, run_extras, run_pnginfo):
submit = gr.Button('Generate', elem_id="extras_generate", variant='primary')
with gr.Column(variant='panel'):
result_images = gr.Gallery(label="Result")
result_images = gr.Gallery(label="Result", show_label=False)
html_info_x = gr.HTML()
html_info = gr.HTML()
extras_send_to_img2img = gr.Button('Send to img2img')
extras_send_to_inpaint = gr.Button('Send to inpaint')
extras_args = dict(
submit.click(
fn=run_extras,
_js="get_extras_tab_index",
inputs=[
image,
dummy_component,
extras_image,
image_batch,
gfpgan_visibility,
codeformer_visibility,
@@ -791,22 +818,40 @@ def create_ui(txt2img, img2img, run_extras, run_pnginfo):
html_info,
]
)
extras_send_to_img2img.click(
fn=lambda x: image_from_url_text(x),
_js="extract_image_from_gallery_img2img",
inputs=[result_images],
outputs=[init_img],
)
extras_send_to_inpaint.click(
fn=lambda x: image_from_url_text(x),
_js="extract_image_from_gallery_img2img",
inputs=[result_images],
outputs=[init_img_with_mask],
)
submit.click(**extras_args)
with gr.Blocks(analytics_enabled=False) as pnginfo_interface:
with gr.Row().style(equal_height=False):
with gr.Column(variant='panel'):
image = gr.Image(elem_id="pnginfo_image", label="Source", source="upload", interactive=True, type="pil")
pnginfo_interface = gr.Interface(
wrap_gradio_call(run_pnginfo),
inputs=[
gr.Image(label="Source", source="upload", interactive=True, type="pil"),
],
outputs=[
gr.HTML(),
gr.HTML(),
gr.HTML(),
],
allow_flagging="never",
analytics_enabled=False,
)
with gr.Column(variant='panel'):
html = gr.HTML()
generation_info = gr.Textbox(visible=False)
html2 = gr.HTML()
with gr.Row():
pnginfo_send_to_txt2img = gr.Button('Send to txt2img')
pnginfo_send_to_img2img = gr.Button('Send to img2img')
image.change(
fn=wrap_gradio_call(run_pnginfo),
inputs=[image],
outputs=[html, generation_info, html2],
)
def create_setting_component(key):
def fun():
@@ -831,12 +876,13 @@ def create_ui(txt2img, img2img, run_extras, run_pnginfo):
return comp(label=info.label, value=fun, **(args or {}))
components = []
keys = list(opts.data_labels.keys())
settings_cols = 3
items_per_col = math.ceil(len(keys) / settings_cols)
def run_settings(*args):
up = []
changed = 0
for key, value, comp in zip(opts.data_labels.keys(), args, components):
if not opts.same_type(value, opts.data_labels[key].default):
return f"Bad value for setting {key}: {value}; expecting {type(opts.data_labels[key].default).__name__}"
for key, value, comp in zip(opts.data_labels.keys(), args, components):
comp_args = opts.data_labels[key].component_args
@@ -846,34 +892,59 @@ def create_ui(txt2img, img2img, run_extras, run_pnginfo):
oldval = opts.data.get(key, None)
opts.data[key] = value
if oldval != value and opts.data_labels[key].onchange is not None:
opts.data_labels[key].onchange()
if oldval != value:
if opts.data_labels[key].onchange is not None:
opts.data_labels[key].onchange()
up.append(comp.update(value=value))
changed += 1
opts.save(shared.config_filename)
return 'Settings applied.'
return f'{changed} settings changed.', opts.dumpjson()
with gr.Blocks(analytics_enabled=False) as settings_interface:
settings_submit = gr.Button(value="Apply settings", variant='primary')
result = gr.HTML()
settings_cols = 3
items_per_col = int(len(opts.data_labels) * 0.9 / settings_cols)
cols_displayed = 0
items_displayed = 0
previous_section = None
column = None
with gr.Row(elem_id="settings").style(equal_height=False):
for colno in range(settings_cols):
with gr.Column(variant='panel'):
for rowno in range(items_per_col):
index = rowno + colno * items_per_col
for i, (k, item) in enumerate(opts.data_labels.items()):
if index < len(keys):
components.append(create_setting_component(keys[index]))
if previous_section != item.section:
if cols_displayed < settings_cols and (items_displayed >= items_per_col or previous_section is None):
if column is not None:
column.__exit__()
settings_submit.click(
fn=run_settings,
inputs=components,
outputs=[result]
column = gr.Column(variant='panel')
column.__enter__()
items_displayed = 0
cols_displayed += 1
previous_section = item.section
gr.HTML(elem_id="settings_header_text_{}".format(item.section[0]), value='<h1 class="gr-button-lg">{}</h1>'.format(item.section[1]))
components.append(create_setting_component(k))
items_displayed += 1
request_notifications = gr.Button(value='Request browser notifications', elem_id="request_notifications")
request_notifications.click(
fn=lambda: None,
inputs=[],
outputs=[],
_js='function(){}'
)
if column is not None:
column.__exit__()
interfaces = [
(txt2img_interface, "txt2img", "txt2img"),
(img2img_interface, "img2img", "img2img"),
@@ -900,44 +971,40 @@ def create_ui(txt2img, img2img, run_extras, run_pnginfo):
with gr.TabItem(label, id=ifid):
interface.render()
text_settings = gr.Textbox(elem_id="settings_json", value=opts.dumpjson(), visible=False)
text_settings = gr.Textbox(elem_id="settings_json", value=lambda: opts.dumpjson(), visible=False)
settings_submit.click(
fn=lambda: opts.dumpjson(),
inputs=[],
outputs=[text_settings],
)
tabs.change(
fn=lambda x: x,
inputs=[init_img_with_mask],
outputs=[init_img_with_mask],
fn=run_settings,
inputs=components,
outputs=[result, text_settings],
)
paste_field_names = ['Prompt', 'Negative prompt', 'Steps', 'Face restoration', 'Seed', 'Size-1', 'Size-2']
txt2img_fields = [field for field,name in txt2img_paste_fields if name in paste_field_names]
img2img_fields = [field for field,name in img2img_paste_fields if name in paste_field_names]
send_to_img2img.click(
fn=lambda x: image_from_url_text(x),
_js="extract_image_from_gallery_img2img",
inputs=[txt2img_gallery],
outputs=[init_img],
fn=lambda img, *args: (image_from_url_text(img),*args),
_js="(gallery, ...args) => [extract_image_from_gallery_img2img(gallery), ...args]",
inputs=[txt2img_gallery] + txt2img_fields,
outputs=[init_img] + img2img_fields,
)
send_to_inpaint.click(
fn=lambda x: image_from_url_text(x),
_js="extract_image_from_gallery_img2img",
inputs=[txt2img_gallery],
outputs=[init_img_with_mask],
fn=lambda x, *args: (image_from_url_text(x), *args),
_js="(gallery, ...args) => [extract_image_from_gallery_inpaint(gallery), ...args]",
inputs=[txt2img_gallery] + txt2img_fields,
outputs=[init_img_with_mask] + img2img_fields,
)
img2img_send_to_img2img.click(
fn=lambda x: image_from_url_text(x),
_js="extract_image_from_gallery",
_js="extract_image_from_gallery_img2img",
inputs=[img2img_gallery],
outputs=[init_img],
)
img2img_send_to_inpaint.click(
fn=lambda x: image_from_url_text(x),
_js="extract_image_from_gallery",
_js="extract_image_from_gallery_inpaint",
inputs=[img2img_gallery],
outputs=[init_img_with_mask],
)
@@ -946,16 +1013,19 @@ def create_ui(txt2img, img2img, run_extras, run_pnginfo):
fn=lambda x: image_from_url_text(x),
_js="extract_image_from_gallery_extras",
inputs=[txt2img_gallery],
outputs=[image],
outputs=[extras_image],
)
img2img_send_to_extras.click(
fn=lambda x: image_from_url_text(x),
_js="extract_image_from_gallery_extras",
inputs=[img2img_gallery],
outputs=[image],
outputs=[extras_image],
)
modules.generation_parameters_copypaste.connect_paste(pnginfo_send_to_txt2img, txt2img_paste_fields, generation_info, 'switch_to_txt2img')
modules.generation_parameters_copypaste.connect_paste(pnginfo_send_to_img2img, img2img_paste_fields, generation_info, 'switch_to_img2img_img2img')
ui_config_file = cmd_opts.ui_config_file
ui_settings = {}
settings_count = len(ui_settings)
@@ -974,12 +1044,21 @@ def create_ui(txt2img, img2img, run_extras, run_pnginfo):
def apply_field(obj, field, condition=None):
key = path + "/" + field
if getattr(obj,'custom_script_source',None) is not None:
key = 'customscript/' + obj.custom_script_source + '/' + key
if getattr(obj, 'do_not_save_to_config', False):
return
saved_value = ui_settings.get(key, None)
if saved_value is None:
ui_settings[key] = getattr(obj, field)
elif condition is None or condition(saved_value):
setattr(obj, field, saved_value)
if type(x) in [gr.Slider, gr.Radio, gr.Checkbox, gr.Textbox, gr.Number] and x.visible:
apply_field(x, 'visible')
if type(x) == gr.Slider:
apply_field(x, 'value')
apply_field(x, 'minimum')
@@ -989,6 +1068,15 @@ def create_ui(txt2img, img2img, run_extras, run_pnginfo):
if type(x) == gr.Radio:
apply_field(x, 'value', lambda val: val in x.choices)
if type(x) == gr.Checkbox:
apply_field(x, 'value')
if type(x) == gr.Textbox:
apply_field(x, 'value')
if type(x) == gr.Number:
apply_field(x, 'value')
visit(txt2img_interface, loadsave, "txt2img")
visit(img2img_interface, loadsave, "img2img")
visit(extras_interface, loadsave, "extras")
@@ -1004,7 +1092,7 @@ with open(os.path.join(script_path, "script.js"), "r", encoding="utf8") as jsfil
javascript = f'<script>{jsfile.read()}</script>'
jsdir = os.path.join(script_path, "javascript")
for filename in os.listdir(jsdir):
for filename in sorted(os.listdir(jsdir)):
with open(os.path.join(jsdir, filename), "r", encoding="utf8") as jsfile:
javascript += f"\n<script>{jsfile.read()}</script>"
+13 -15
View File
@@ -1,22 +1,20 @@
transformers
diffusers
basicsr
gfpgan
gradio
numpy
Pillow
realesrgan
torch
transformers
omegaconf
pytorch_lightning
diffusers
invisible-watermark
scikit-image>=0.19
fairscale==0.4.4
fonts
font-roboto
gfpgan
gradio
invisible-watermark
git+https://github.com/crowsonkb/k-diffusion.git
numpy
omegaconf
piexif
Pillow
pytorch_lightning
realesrgan
scikit-image>=0.19
git+https://github.com/TencentARC/GFPGAN.git@8d2447a2d918f8eba5a4a01463fd48e45126a379
timm==0.4.12
fairscale==0.4.4
piexif
transformers
torch
+9 -10
View File
@@ -1,18 +1,17 @@
transformers==4.19.2
diffusers==0.2.4
basicsr==1.3.5
gfpgan
gradio==3.3.1
diffusers==0.3.0
basicsr==1.4.2
gfpgan==1.3.8
gradio==3.4b3
numpy==1.23.3
Pillow==9.2.0
realesrgan==0.2.5.0
realesrgan==0.3.0
torch
transformers==4.19.2
omegaconf==2.1.1
pytorch_lightning==1.7.2
omegaconf==2.2.3
pytorch_lightning==1.7.6
scikit-image==0.19.2
fonts
font-roboto
timm==0.4.12
fairscale==0.4.4
timm==0.6.7
fairscale==0.4.9
piexif==1.1.3
+46 -9
View File
@@ -2,24 +2,61 @@ function gradioApp(){
return document.getElementsByTagName('gradio-app')[0].shadowRoot;
}
function get_uiCurrentTab() {
return gradioApp().querySelector('.tabs button:not(.border-transparent)')
}
uiUpdateCallbacks = []
uiTabChangeCallbacks = []
let uiCurrentTab = null
function onUiUpdate(callback){
uiUpdateCallbacks.push(callback)
}
function onUiTabChange(callback){
uiTabChangeCallbacks.push(callback)
}
function uiUpdate(root){
uiUpdateCallbacks.forEach(function(x){
try {
x()
} catch (e) {
(console.error || console.log).call(console, e.message, e);
}
})
function runCallback(x){
try {
x()
} catch (e) {
(console.error || console.log).call(console, e.message, e);
}
}
function executeCallbacks(queue) {
queue.forEach(runCallback)
}
document.addEventListener("DOMContentLoaded", function() {
var mutationObserver = new MutationObserver(function(m){
uiUpdate(gradioApp());
executeCallbacks(uiUpdateCallbacks);
const newTab = get_uiCurrentTab();
if ( newTab && ( newTab !== uiCurrentTab ) ) {
uiCurrentTab = newTab;
executeCallbacks(uiTabChangeCallbacks);
}
});
mutationObserver.observe( gradioApp(), { childList:true, subtree:true })
});
/**
* checks that a UI element is not in another hidden element or tab content
*/
function uiElementIsVisible(el) {
let isVisible = !el.closest('.\\!hidden');
if ( ! isVisible ) {
return false;
}
while( isVisible = el.closest('.tabitem')?.style.display !== 'none' ) {
if ( ! isVisible ) {
return false;
} else if ( el.parentElement ) {
el = el.parentElement
} else {
break;
}
}
return isVisible;
}
-59
View File
@@ -1,59 +0,0 @@
import math
import os
import sys
import traceback
import modules.scripts as scripts
import gradio as gr
from modules.processing import Processed, process_images
from PIL import Image
from modules.shared import opts, cmd_opts, state
class Script(scripts.Script):
def title(self):
return "Batch processing"
def show(self, is_img2img):
return is_img2img
def ui(self, is_img2img):
input_dir = gr.Textbox(label="Input directory", lines=1)
output_dir = gr.Textbox(label="Output directory", lines=1)
return [input_dir, output_dir]
def run(self, p, input_dir, output_dir):
images = [file for file in [os.path.join(input_dir, x) for x in os.listdir(input_dir)] if os.path.isfile(file)]
batch_count = math.ceil(len(images) / p.batch_size)
print(f"Will process {len(images)} images in {batch_count} batches.")
p.batch_count = 1
p.do_not_save_grid = True
p.do_not_save_samples = True
state.job_count = batch_count
for batch_no in range(batch_count):
batch_images = []
for path in images[batch_no*p.batch_size:(batch_no+1)*p.batch_size]:
try:
img = Image.open(path)
batch_images.append((img, path))
except:
print(f"Error processing {path}:", file=sys.stderr)
print(traceback.format_exc(), file=sys.stderr)
if len(batch_images) == 0:
continue
state.job = f"{batch_no} out of {batch_count}: {batch_images[0][1]}"
p.init_images = [x[0] for x in batch_images]
proc = process_images(p)
for image, (_, path) in zip(proc.images, batch_images):
filename = os.path.basename(path)
image.save(os.path.join(output_dir, filename))
return Processed(p, [], p.seed, "")
+9 -9
View File
@@ -77,14 +77,15 @@ class Script(scripts.Script):
original_negative_prompt = gr.Textbox(label="Original negative prompt", lines=1)
cfg = gr.Slider(label="Decode CFG scale", minimum=0.0, maximum=15.0, step=0.1, value=1.0)
st = gr.Slider(label="Decode steps", minimum=1, maximum=150, step=1, value=50)
randomness = gr.Slider(label="randomness", minimum=0.0, maximum=1.0, step=0.01, value=0.0)
randomness = gr.Slider(label="Randomness", minimum=0.0, maximum=1.0, step=0.01, value=0.0)
return [original_prompt, original_negative_prompt, cfg, st, randomness]
def run(self, p, original_prompt, original_negative_prompt, cfg, st, randomness):
p.batch_size = 1
p.batch_count = 1
def sample_extra(x, conditioning, unconditional_conditioning):
def sample_extra(conditioning, unconditional_conditioning, seeds, subseeds, subseed_strength):
lat = (p.init_latent.cpu().numpy() * 10).astype(int)
same_params = self.cache is not None and self.cache.cfg_scale == cfg and self.cache.steps == st and self.cache.original_prompt == original_prompt and self.cache.original_negative_prompt == original_negative_prompt
@@ -107,20 +108,19 @@ class Script(scripts.Script):
sigmas = sampler.model_wrap.get_sigmas(p.steps)
noise_dt = combined_noise - ( p.init_latent / sigmas[0] )
noise_dt = combined_noise - (p.init_latent / sigmas[0])
p.seed = p.seed + 1
return sampler.sample_img2img(p, p.init_latent, noise_dt, conditioning, unconditional_conditioning)
p.sample = sample_extra
p.extra_generation_params = {
"Decode prompt": original_prompt,
"Decode CFG scale": cfg,
"Decode steps": st,
}
p.extra_generation_params["Decode prompt"] = original_prompt
p.extra_generation_params["Decode negative prompt"] = original_negative_prompt
p.extra_generation_params["Decode CFG scale"] = cfg
p.extra_generation_params["Decode steps"] = st
p.extra_generation_params["Randomness"] = randomness
processed = processing.process_images(p)
+15 -8
View File
@@ -11,7 +11,7 @@ from modules import images, processing, devices
from modules.processing import Processed, process_images
from modules.shared import opts, cmd_opts, state
# https://github.com/parlance-zz/g-diffuser-bot
def expand(x, dir, amount, power=0.75):
is_left = dir == 3
is_right = dir == 1
@@ -197,6 +197,7 @@ class Script(scripts.Script):
if left > 0:
left = left * (target_w - init_img.width) // (left + right)
if right > 0:
right = target_w - init_img.width - left
@@ -208,7 +209,7 @@ class Script(scripts.Script):
init_image = p.init_images[0]
state.job_count = (1 if left > 0 else 0) + (1 if right > 0 else 0)+ (1 if up > 0 else 0)+ (1 if down > 0 else 0)
state.job_count = (1 if left > 0 else 0) + (1 if right > 0 else 0) + (1 if up > 0 else 0) + (1 if down > 0 else 0)
def expand(init, expand_pixels, is_left=False, is_right=False, is_top=False, is_bottom=False):
is_horiz = is_left or is_right
@@ -216,15 +217,20 @@ class Script(scripts.Script):
pixels_horiz = expand_pixels if is_horiz else 0
pixels_vert = expand_pixels if is_vert else 0
img = Image.new("RGB", (init.width + pixels_horiz, init.height + pixels_vert))
res_w = init.width + pixels_horiz
res_h = init.height + pixels_vert
process_res_w = math.ceil(res_w / 64) * 64
process_res_h = math.ceil(res_h / 64) * 64
img = Image.new("RGB", (process_res_w, process_res_h))
img.paste(init, (pixels_horiz if is_left else 0, pixels_vert if is_top else 0))
mask = Image.new("RGB", (init.width + pixels_horiz, init.height + pixels_vert), "white")
mask = Image.new("RGB", (process_res_w, process_res_h), "white")
draw = ImageDraw.Draw(mask)
draw.rectangle((
expand_pixels + mask_blur if is_left else 0,
expand_pixels + mask_blur if is_top else 0,
mask.width - expand_pixels - mask_blur if is_right else mask.width,
mask.height - expand_pixels - mask_blur if is_bottom else mask.height,
mask.width - expand_pixels - mask_blur if is_right else res_w,
mask.height - expand_pixels - mask_blur if is_bottom else res_h,
), fill="black")
np_image = (np.asarray(img) / 255.0).astype(np.float64)
@@ -255,8 +261,8 @@ class Script(scripts.Script):
draw.rectangle((
expand_pixels + mask_blur * 2 if is_left else 0,
expand_pixels + mask_blur * 2 if is_top else 0,
mask.width - expand_pixels - mask_blur * 2 if is_right else mask.width,
mask.height - expand_pixels - mask_blur * 2 if is_bottom else mask.height,
mask.width - expand_pixels - mask_blur * 2 if is_right else res_w,
mask.height - expand_pixels - mask_blur * 2 if is_bottom else res_h,
), fill="black")
p.latent_mask = latent_mask
@@ -268,6 +274,7 @@ class Script(scripts.Script):
initial_seed_and_info[1] = proc.info
out.paste(proc_img, (0 if is_left else out.width - proc_img.width, 0 if is_top else out.height - proc_img.height))
out = out.crop((0, 0, res_w, res_h))
return out
img = init_image
+93
View File
@@ -0,0 +1,93 @@
import math
import modules.scripts as scripts
import gradio as gr
from PIL import Image
from modules import processing, shared, sd_samplers, images, devices
from modules.processing import Processed
from modules.shared import opts, cmd_opts, state
class Script(scripts.Script):
def title(self):
return "SD upscale"
def show(self, is_img2img):
return is_img2img
def ui(self, is_img2img):
info = gr.HTML("<p style=\"margin-bottom:0.75em\">Will upscale the image to twice the dimensions; use width and height sliders to set tile size</p>")
overlap = gr.Slider(minimum=0, maximum=256, step=16, label='Tile overlap', value=64, visible=False)
upscaler_index = gr.Radio(label='Upscaler', choices=[x.name for x in shared.sd_upscalers], value=shared.sd_upscalers[0].name, type="index", visible=False)
return [info, overlap, upscaler_index]
def run(self, p, _, overlap, upscaler_index):
processing.fix_seed(p)
upscaler = shared.sd_upscalers[upscaler_index]
p.extra_generation_params["SD upscale overlap"] = overlap
p.extra_generation_params["SD upscale upscaler"] = upscaler.name
initial_info = None
seed = p.seed
init_img = p.init_images[0]
img = upscaler.upscale(init_img, init_img.width * 2, init_img.height * 2)
devices.torch_gc()
grid = images.split_grid(img, tile_w=p.width, tile_h=p.height, overlap=overlap)
batch_size = p.batch_size
upscale_count = p.n_iter
p.n_iter = 1
p.do_not_save_grid = True
p.do_not_save_samples = True
work = []
for y, h, row in grid.tiles:
for tiledata in row:
work.append(tiledata[2])
batch_count = math.ceil(len(work) / batch_size)
state.job_count = batch_count * upscale_count
print(f"SD upscaling will process a total of {len(work)} images tiled as {len(grid.tiles[0][2])}x{len(grid.tiles)} per upscale in a total of {state.job_count} batches.")
result_images = []
for n in range(upscale_count):
start_seed = seed + n
p.seed = start_seed
work_results = []
for i in range(batch_count):
p.batch_size = batch_size
p.init_images = work[i*batch_size:(i+1)*batch_size]
state.job = f"Batch {i + 1 + n * batch_count} out of {state.job_count}"
processed = processing.process_images(p)
if initial_info is None:
initial_info = processed.info
p.seed = processed.seed + 1
work_results += processed.images
image_index = 0
for y, h, row in grid.tiles:
for tiledata in row:
tiledata[2] = work_results[image_index] if image_index < len(work_results) else Image.new("RGB", (p.width, p.height))
image_index += 1
combined_image = images.combine_grid(grid)
result_images.append(combined_image)
if opts.samples_save:
images.save_image(combined_image, p.outpath_samples, "", start_seed, p.prompt, opts.samples_format, info=initial_info, p=p)
processed = Processed(p, result_images, seed, initial_info)
return processed
+26 -5
View File
@@ -141,10 +141,11 @@ class Script(scripts.Script):
y_values = gr.Textbox(label="Y values", visible=False, lines=1)
draw_legend = gr.Checkbox(label='Draw legend', value=True)
return [x_type, x_values, y_type, y_values, draw_legend]
no_fixed_seeds = gr.Checkbox(label='Keep -1 for seeds', value=False)
def run(self, p, x_type, x_values, y_type, y_values, draw_legend):
return [x_type, x_values, y_type, y_values, draw_legend, no_fixed_seeds]
def run(self, p, x_type, x_values, y_type, y_values, draw_legend, no_fixed_seeds):
modules.processing.fix_seed(p)
p.batch_size = 1
@@ -169,7 +170,7 @@ class Script(scripts.Script):
end = int(mc.group(2))
num = int(mc.group(3)) if mc.group(3) is not None else 1
valslist_ext += [int(x) for x in np.linspace(start = start, stop = end, num = num).tolist()]
valslist_ext += [int(x) for x in np.linspace(start=start, stop=end, num=num).tolist()]
else:
valslist_ext.append(val)
@@ -191,7 +192,7 @@ class Script(scripts.Script):
end = float(mc.group(2))
num = int(mc.group(3)) if mc.group(3) is not None else 1
valslist_ext += np.linspace(start = start, stop = end, num = num).tolist()
valslist_ext += np.linspace(start=start, stop=end, num=num).tolist()
else:
valslist_ext.append(val)
@@ -207,6 +208,26 @@ class Script(scripts.Script):
y_opt = axis_options[y_type]
ys = process_axis(y_opt, y_values)
def fix_axis_seeds(axis_opt, axis_list):
if axis_opt.label == 'Seed':
return [int(random.randrange(4294967294)) if val is None or val == '' or val == -1 else val for val in axis_list]
else:
return axis_list
if not no_fixed_seeds:
xs = fix_axis_seeds(x_opt, xs)
ys = fix_axis_seeds(y_opt, ys)
if x_opt.label == 'Steps':
total_steps = sum(xs) * len(ys)
elif y_opt.label == 'Steps':
total_steps = sum(ys) * len(xs)
else:
total_steps = p.steps * len(xs) * len(ys)
print(f"X/Y plot will create {len(xs) * len(ys) * p.n_iter} images on a {len(xs)}x{len(ys)} grid. (Total steps to process: {total_steps * p.n_iter})")
shared.total_tqdm.updateTotal(total_steps * p.n_iter)
def cell(x, y):
pc = copy(p)
x_opt.apply(pc, x, xs)
+102 -23
View File
@@ -74,10 +74,21 @@
height: 100%;
}
#roll{
min-width: 1em;
max-width: 4em;
margin: 0.5em;
#roll_col{
min-width: unset !important;
flex-grow: 0 !important;
padding: 0.4em 0;
}
#roll, #paste{
min-width: 2em;
min-height: 2em;
max-width: 2em;
max-height: 2em;
flex-grow: 0;
padding-left: 0.25em;
padding-right: 0.25em;
margin: 0.1em 0;
}
#style_apply, #style_create, #interrogate{
@@ -86,10 +97,10 @@
}
#style_pos_col, #style_neg_col{
min-width: 4em !important;
min-width: 8em !important;
}
#style_index, #style2_index{
#txt2img_style_index, #txt2img_style2_index, #img2img_style_index, #img2img_style2_index{
margin-top: 1em;
}
@@ -97,6 +108,11 @@
background: transparent;
}
.my-4{
margin-top: 0;
margin-bottom: 0;
}
#toprow div{
border: none;
gap: 0;
@@ -138,7 +154,23 @@ button{
right: 0;
margin-left: auto;
margin-right: auto;
margin-top: 34px;
z-index: 100;
border: none;
border-top-left-radius: 0;
border-top-right-radius: 0;
}
@media screen and (min-width: 768px) {
#txt2img_preview, #img2img_preview {
position: absolute;
}
}
@media screen and (max-width: 767px) {
#txt2img_preview, #img2img_preview {
position: relative;
}
}
#txt2img_preview div.left-0.top-0, #img2img_preview div.left-0.top-0{
@@ -198,21 +230,30 @@ input[type="range"]{
#mask_bug_info {
text-align: center;
display: block;
margin-bottom: 0.5em;
margin-top: -0.75em;
margin-bottom: -0.75em;
}
#txt2img_negative_prompt, #img2img_negative_prompt{
}
#progressbar{
#txt2img_progressbar, #img2img_progressbar{
position: absolute;
z-index: 1000;
right: 0;
padding-left: 5px;
padding-right: 5px;
display: block;
}
#txt2img_progress_row, #img2img_progress_row{
margin-bottom: 10px;
margin-top: -18px;
}
.progressDiv{
width: 100%;
height: 30px;
height: 20px;
background: #b4c0cc;
border-radius: 8px;
}
@@ -223,11 +264,11 @@ input[type="range"]{
.progressDiv .progress{
width: 0%;
height: 30px;
height: 20px;
background: #0060df;
color: white;
font-weight: bold;
line-height: 30px;
line-height: 20px;
padding: 0 8px 0 0;
text-align: right;
border-radius: 8px;
@@ -246,20 +287,38 @@ input[type="range"]{
background-color: rgba(20, 20, 20, 0.95);
}
.modalClose,
.modalZoom {
color: white;
position: absolute;
top: 10px;
right: 25px;
font-size: 35px;
font-weight: bold;
cursor: pointer;
.modalControls {
display: grid;
grid-template-columns: 32px auto 1fr 32px;
grid-template-areas: "zoom tile space close";
position: absolute;
top: 0;
left: 0;
right: 0;
padding: 16px;
gap: 16px;
background-color: rgba(0,0,0,0.2);
}
.modalClose {
grid-area: close;
}
.modalZoom {
left: 10px;
right: auto;
grid-area: zoom;
}
.modalTileImage {
grid-area: tile;
}
.modalClose,
.modalZoom,
.modalTileImage {
color: white;
font-size: 35px;
font-weight: bold;
cursor: pointer;
}
.modalClose:hover,
@@ -280,7 +339,7 @@ input[type="range"]{
}
.modalImageFullscreen {
max-width: max-content;
object-fit: contain;
height: 90%;
}
@@ -310,3 +369,23 @@ input[type="range"]{
.modalNext:hover {
background-color: rgba(0, 0, 0, 0.8);
}
#imageARPreview{
position:absolute;
top:0px;
left:0px;
border:2px solid red;
background:rgba(255, 0, 0, 0.3);
z-index: 900;
pointer-events:none;
display:none
}
#txt2img_interrupt, #img2img_interrupt{
position: absolute;
width: 100%;
height: 72px;
background: #b4c0cc;
border-radius: 8px;
display: none;
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 526 KiB

+12 -12
View File
@@ -1,31 +1,31 @@
#!/bin/bash
###########################################
# Change the variables below to your need:#
###########################################
#########################################################
# Uncomment and change the variables below to your need:#
#########################################################
# Install directory without trailing slash
install_dir="/home/$(whoami)"
#install_dir="/home/$(whoami)"
# Name of the subdirectory (defaults to stable-diffusion-webui)
clone_dir="stable-diffusion-webui"
# Name of the subdirectory
#clone_dir="stable-diffusion-webui"
# Commandline arguments for webui.py, for example: export COMMANDLINE_ARGS=(--medvram --opt-split-attention)
# Commandline arguments for webui.py, for example: export COMMANDLINE_ARGS="--medvram --opt-split-attention"
export COMMANDLINE_ARGS=""
# python3 executable
python_cmd="python3"
#python_cmd="python3"
# git executable
#export GIT=""
#export GIT="git"
# python3 venv without trailing slash (defaults to ${install_dir}/${clone_dir}/venv)
venv_dir="venv"
#venv_dir="venv"
# install command for torch
export TORCH_COMMAND="pip install torch==1.12.1+cu113 --extra-index-url https://download.pytorch.org/whl/cu113"
#export TORCH_COMMAND="pip install torch==1.12.1+cu113 --extra-index-url https://download.pytorch.org/whl/cu113"
# Requirements file to use for stable-diffusion-webui
#export REQS_FILE=""
#export REQS_FILE="requirements_versions.txt"
# Fixed git repos
#export K_DIFFUSION_PACKAGE=""
+2
View File
@@ -3,6 +3,8 @@
if not defined PYTHON (set PYTHON=python)
if not defined VENV_DIR (set VENV_DIR=venv)
set ERROR_REPORTING=FALSE
mkdir tmp 2>NUL
%PYTHON% -c "" >tmp/stdout.txt 2>tmp/stderr.txt
+6 -1
View File
@@ -15,10 +15,12 @@ import modules.gfpgan_model
import modules.face_restoration
import modules.realesrgan_model as realesrgan
import modules.esrgan_model as esrgan
import modules.ldsr_model as ldsr
import modules.extras
import modules.lowvram
import modules.txt2img
import modules.img2img
import modules.swinir as swinir
import modules.sd_models
@@ -27,8 +29,9 @@ modules.gfpgan_model.setup_gfpgan()
shared.face_restorers.append(modules.face_restoration.FaceRestoration())
esrgan.load_models(cmd_opts.esrgan_models_path)
swinir.load_models(cmd_opts.swinir_models_path)
realesrgan.setup_realesrgan()
ldsr.add_lsdr()
queue_lock = threading.Lock()
@@ -47,9 +50,11 @@ def wrap_gradio_gpu_call(func):
shared.state.sampling_step = 0
shared.state.job_count = -1
shared.state.job_no = 0
shared.state.job_timestamp = shared.state.get_job_timestamp()
shared.state.current_latent = None
shared.state.current_image = None
shared.state.current_image_sampling_step = 0
shared.state.interrupted = False
with queue_lock:
res = func(*args, **kwargs)
Regular → Executable
+2 -5
View File
@@ -41,11 +41,8 @@ then
venv_dir="venv"
fi
# install command for torch
if [[ -z "${TORCH_COMMAND}" ]]
then
export TORCH_COMMAND="pip install torch==1.12.1+cu113 --extra-index-url https://download.pytorch.org/whl/cu113"
fi
# Disable sentry logging
export ERROR_REPORTING=FALSE
# Do not reinstall existing pip packages on Debian/Ubuntu
export PIP_IGNORE_INSTALLED=0