Compare commits

..
86 Commits
Author SHA1 Message Date
37274c652a feat: improve anime preset by adding style Fooocus Semi Realistic (#2492)
* Add files via upload

In anime.json, at Line 36,
replace "Fooocus Negative" with "Fooocus Semi Realistic"

* Add files via upload

In sdxl_styles_fooocus.json, insert this text at Line 6:

    {
        "name": "Fooocus Semi Realistic",
        "negative_prompt": "(worst quality, low quality, normal quality, lowres, low details, oversaturated, undersaturated, overexposed, underexposed, grayscale, bw, bad photo, bad photography, bad art:1.4), (watermark, signature, text font, username, error, logo, words, letters, digits, autograph, trademark, name:1.2), (blur, blurry, grainy), morbid, ugly, asymmetrical, mutated malformed, mutilated, poorly lit, bad shadow, draft, cropped, out of frame, cut off, censored, jpeg artifacts, out of focus, glitch, duplicate, (bad hands, bad anatomy, bad body, bad face, bad teeth, bad arms, bad legs, deformities:1.3)"
    },

* Add files via upload

Popup image for the new "Fooocus Semi Realistic" style

* Update sdxl_styles_fooocus.json

Removed "grayscale, bw" from the proposed Fooocus Realistic entry at Line 6 of sdxl_styles_fooocus.json

* refactor: cleanup files

* feat: use default model to create thumbnail

juggernautv8, seed 0, 1024x1024, no LoRAs, only this style, positive prompt "cat"

---------

Co-authored-by: Manuel Schmid <manuel.schmid@odt.net>
Co-authored-by: Manuel Schmid <dev@mash1t.de>
2024-03-15 22:52:27 +01:00
Spencer Hayes-LaverdiereandManuel Schmid 55e23a9374 fix: add error output for unsupported images (#2537)
* Raise Error on bad decode

* Move task arg pop to try block

* fix: prevent empty task from getting queued

---------

Co-authored-by: Manuel Schmid <dev@mash1t.de>
2024-03-15 22:30:29 +01:00
Manuel Schmid 4a44be36fd feat: add preset selection to Gradio UI (session based) (#1570)
* add preset selection

uses meta parsing to set presets in user session (UI elements only)

* add LoRA handling

* use default config as fallback value

* add preset refresh on "Refresh All Files" click

* add special handling for default_styles and default_aspect_ratio

* sort styles after preset change

* code cleanup

* download missing models from preset

* set default refiner to "None" in preset realistic

* use state_is_generating for preset selection change

* DRY output parameter handling

* feat: add argument --disable-preset-selection

useful for cloud provisioning to prevent model switches and keep models loaded

* feat: keep prompt when not set in preset, use more robust syntax

* fix: add default return values when preset download is disabled

https://github.com/mashb1t/Fooocus/issues/20

* feat: add translation for preset label

* refactor: unify preset loading methods in config

* refactor: code cleanup
2024-03-15 22:04:27 +01:00
Manuel Schmid 8baafcd79c Merge branch 'main_upstream' into develop 2024-03-15 20:52:06 +01:00
Zxilly 0da614f7e1 feat: allow users to add custom preset without blocking automatic update (#2520) 2024-03-15 20:51:10 +01:00
Manuel Schmid 9cd0366d30 fix: parse seed as string to display correctly in metadata preview (#2536) 2024-03-15 20:38:21 +01:00
josephrocca f51e0138e6 feat: update xformers to 0.0.23 in Dockerfile (#2519) 2024-03-13 15:12:06 +01:00
Manuel Schmid 4363dbc303 fix: revert testing change to default lora activation 2024-03-13 00:32:54 +01:00
Manuel Schmid f7f0b51bab Merge branch 'main_upstream' into develop 2024-03-13 00:31:41 +01:00
Manuel Schmid 6da0441cc7 fix: update xformers to 0.0.23 (#2517)
WARNING[XFORMERS]: xFormers can't load C++/CUDA extensions. xFormers was built for:
    PyTorch 2.0.1+cu118 with CUDA 1108 (you have 2.1.0+cu121)
    Python  3.10.11 (you have 3.10.9)
2024-03-12 23:13:38 +01:00
Manuel Schmid 57a01865b9 refactor: only use LoRA activate on handover to async worker, extract method 2024-03-11 23:49:45 +01:00
Giuseppe SperanzaandManuel Schmid 532401df76 fix: prioritize VRAM over RAM in Colab, preventing out of memory issues (#1710)
* colab: balance the use of RAM

enables the use of VRAM memory so as not to saturate the system RAM

* feat: use --always-high-vram by default for Colab, adjust readme

---------

Co-authored-by: Manuel Schmid <manuel.schmid@odt.net>
2024-03-11 19:58:25 +01:00
Manuel Schmid d57afc88a4 feat: merge webui css into one file 2024-03-11 18:26:04 +01:00
Manuel Schmid 39669453cd feat: allow to add disabled LoRAs in config on application start (#2507)
add LoRA checkbox enable/disable handling to all necessary occurrences
2024-03-11 17:59:58 +01:00
2831dc70a7 feat: use scrollable 2 column layout for styles (#1883)
* Styles Grouping/Sorting #1770

* Update css/style.css

Co-authored-by: Manuel Schmid <9307310+mashb1t@users.noreply.github.com>

* Update javascript/script.js

Co-authored-by: Manuel Schmid <9307310+mashb1t@users.noreply.github.com>

* feat: use standard padding again

---------

Co-authored-by: Manuel Schmid <9307310+mashb1t@users.noreply.github.com>
Co-authored-by: Manuel Schmid <manuel.schmid@odt.net>
2024-03-11 16:35:03 +01:00
Manuel Schmid 84e3124c37 i18n: add translation for lightning 2024-03-11 00:47:43 +01:00
xhoxyeandManuel Schmid ead24c9361 feat: read wildcards in order 通配符增强,切换顺序读取。(#1761)
* 通配符增强,切换顺序读取

通配符增强,通过勾选切换通配符读取方法,默认不勾选为随机读取一行,勾选后为按顺序读取,并使用相同的种子。

* 代码来自刁璐璐

* update

* Update async_worker.py

* refactor: rename read_wildcard_in_order_checkbox to read_wildcard_in_order

* fix: use correct method call for interrupt_current_processing

actually achieves the same result, stopping the task

* refactor: move checkbox to developer debug mode, rename to plural

below disable seed increment

* refactor: code cleanup, separate code for disable_seed_increment

* i18n: add translation for checkbox text

---------

Co-authored-by: Manuel Schmid <manuel.schmid@odt.net>
2024-03-10 23:18:36 +01:00
Manuel Schmid 5c7dc12470 Merge branch 'main_upstream' into develop 2024-03-10 23:14:52 +01:00
Manuel Schmid bc9c586082 fix: use correct method call for interrupt_current_processing (#2506)
actually achieves the same result, stopping the task
2024-03-10 23:13:09 +01:00
CruxialandManuel Schmid f6117180d4 feat: scan wildcard subdirectories (#2466)
* Fix typo

* Scan wildcards recursively

Adds a method for getting the top-most occurrence of a given file in a directory tree

* Use already existing method for locating files

* Fix issue with incorrect files being loaded

When using the `name-filter` parameter in `get_model_filenames`, it doesn't guarantee the best match to be in the first index. This change adds a step to ensure the correct wildcard is being loaded.

* feat: make path for wildcards configurable, cache filenames on refresh files, rename button variable

* Fix formatting

---------

Co-authored-by: Manuel Schmid <manuel.schmid@odt.net>
2024-03-10 21:35:41 +01:00
400471f7af feat: add config for temp path and temp path cleanup on launch (#1992)
* Added options to set the Gradio cache path and clear cache on launch.

* Renamed cache to temp

* clear temp

* feat: do not delete temp folder but only clean content

also use fallback to system temp dir
see https://github.com/gradio-app/gradio/blob/6683ab2589f9d8658e1f51acc1b7526edce988d3/gradio/utils.py#L1151

* refactor: code cleanup

* feat: unify arg --temp-path and new temp_path config value

* feat: change default temp dir from gradio to fooocus

* refactor: move temp path method definition and configs

* feat: rename get_temp_path to init_temp_path

---------

Co-authored-by: Magee <koshms3@gmail.com>
Co-authored-by: steveyourcreativepeople <steve@yourcreativepeople.com>
Co-authored-by: Manuel Schmid <manuel.schmid@odt.net>
2024-03-10 21:11:41 +01:00
Manuel Schmid 5409bfdb26 Revert "feat: add config for temp path and temp path cleanup on launch (#1992)" (#2502)
This reverts commit 85e8aa8ce2.
2024-03-10 21:08:55 +01:00
85e8aa8ce2 feat: add config for temp path and temp path cleanup on launch (#1992)
* Added options to set the Gradio cache path and  clear cache on launch.

* Renamed cache to temp

* clear temp

* feat: do not delete temp folder but only clean content

also use fallback to system temp dir
see https://github.com/gradio-app/gradio/blob/6683ab2589f9d8658e1f51acc1b7526edce988d3/gradio/utils.py#L1151

* refactor: code cleanup

* feat: unify arg --temp-path and new temp_path config value

* feat: change default temp dir from gradio to fooocus

* refactor: move temp path method definition and configs

* feat: rename get_temp_path to init_temp_path

---------

Co-authored-by: steveyourcreativepeople <steve@yourcreativepeople.com>
Co-authored-by: Manuel Schmid <manuel.schmid@odt.net>
2024-03-10 21:06:08 +01:00
db7d2018ca fix: change synthetic refiner switch from 0.5 to 0.8 (#2165)
* fix problem

1. In partial redrawing, when refiner is empty, enable use_synthetic_refiner. The default switching timing of 0.5 is too early, which is now modified to SDXL default of 0.8.
2. When using custom steps, the calculation of switching timing is wrong. Now it is modified to calculate "steps x timing" after custom steps are used.

* fix: parse width and height as int when applying metadata (#2452)

fixes an issue with A1111 metadata scheme where width and height are strings after splitting resolution

* fix: do not attempt to remove non-existing image grid file (#2456)

image grid is actually not an image here but a numpy array, as the grid isn't saved by default

* feat: add troubleshooting guide to bug report template again (#2489)

---------

Co-authored-by: Manuel Schmid <9307310+mashb1t@users.noreply.github.com>
Co-authored-by: Manuel Schmid <manuel.schmid@odt.net>
2024-03-10 14:42:03 +01:00
Manuel Schmid 4701b4f8f3 Merge branch 'main_upstream' into develop 2024-03-10 14:40:58 +01:00
Manuel Schmid 25650b4bc4 feat: add performance lightning with 4 step LoRA (#2415)
* feat: add performance sdxl lightning

based on https://huggingface.co/ByteDance/SDXL-Lightning/blob/main/sdxl_lightning_4step_lora.safetensors

* feat: add method for centralized restriction of features for specific performance modes

* feat: add lightning preset
2024-03-10 14:34:48 +01:00
Manuel Schmid b6e4bb86f4 feat: use jpeg instead of jpg, use enums instead of strings (#2453)
* fix: parse width and height as int when applying metadata (#2452)

fixes an issue with A1111 metadata scheme where width and height are strings after splitting resolution

* feat: use jpeg instead of jpg, use enums instead of strings
2024-03-09 16:00:25 +01:00
Manuel Schmid 831c6b93cc feat: add troubleshooting guide to bug report template again (#2489) 2024-03-09 14:13:16 +01:00
Manuel Schmid 3a64fe3eb3 fix: do not attempt to remove non-existing image grid file (#2456)
image grid is actually not an image here but a numpy array, as the grid isn't saved by default
2024-03-05 21:16:21 +01:00
Manuel Schmid 6cfcc62000 fix: parse width and height as int when applying metadata (#2452)
fixes an issue with A1111 metadata scheme where width and height are strings after splitting resolution
2024-03-05 18:18:47 +01:00
Manuel Schmid 28cdc2f104 Merge pull request #2439 from lllyasviel/develop
release 2.2.1
2024-03-04 11:37:41 +01:00
Manuel Schmid ee96b854d9 docs: update version and changelog 2024-03-04 11:33:49 +01:00
Manuel Schmid 9155d94067 feat: match anything in array syntax, not only words and whitespace (#2438)
allows e.g. [[ (red:1.1), (blue:1.2) ]] and enables same seed checks for different prompt weight
2024-03-04 11:22:24 +01:00
nbs e54fb54f91 fix: typo in wildcards/animal.txt (#2433)
* Fix typo in animal wildcards

* Update animal.txt
2024-03-04 10:19:49 +01:00
eddyizm e965bfc39c fix: add hint for png to metadata scheme selection (#2434) 2024-03-04 00:22:47 +01:00
Manuel Schmid e241c53f0e feat: adjust width of lora_weight for firefox (#2431) 2024-03-03 21:15:42 +01:00
Manuel Schmid c3fd57acb9 feat: add metadata flag and steps override to history log (#2425)
* feat: add metadata hint to history log

* feat: add actual metadata_scheme to log instead of only boolean

* feat: add steps to log if they were overridden

* fix: pass copy of metadata

prevents LoRA file extension removal in history log caused by passing reference to meta_parser fooocus scheme
2024-03-03 19:34:38 +01:00
Manuel Schmid fb94394b10 fix: add fallback value for default_max_lora_number when default_loras is empty (#2430) 2024-03-03 18:46:26 +01:00
Manuel Schmid 4ea3baff50 fix: add handling for filepaths to image grid (#2414)
previously skipped due to not being in np.ndarray format but string
2024-03-03 00:21:59 +01:00
Manuel Schmid 90839430da fix: adjust parameters for upscale fast 2x (#2411) 2024-03-02 19:05:11 +01:00
Manuel Schmid 4945fc9962 Merge pull request #2406 from lllyasviel/develop
release 2.2.0
2024-03-02 16:27:54 +01:00
Manuel Schmid 6db14acf8e docs: update version and changelog 2024-03-02 16:25:31 +01:00
Gianluca Teti 41e88a4e8d docs: fix typo in readme (#2368) 2024-02-29 16:10:34 +01:00
Manuel Schmid 4f4d23f4e3 fix: use filename instead of download function call for lcm lora
do not require lcm lora to be downloaded for metadata parsing
2024-02-26 21:14:44 +01:00
Manuel Schmid 9c30961efd fix: add missing return statement in model_refresh_clicked 2024-02-26 21:12:27 +01:00
Manuel Schmid 692beadbdc docs: bump version number to 2.2.0-rc1
easier debugging and issue handling
2024-02-26 17:41:29 +01:00
Manuel Schmid 4e526e255e docs: add missing release notes for 2.1.865 2024-02-26 17:39:29 +01:00
f4a6350300 feat: add docker files (#1418)
* Add docker files

* Add python precompiled cache file in the image

* Add Notes in docker.md

* Create docker-publish.yml

* Modify docker-compose.yml not to use the bind mount

* Update torch version

* Change --share to --listen

* Update torch version

* Change '--share' to '--listen`

* adjust code comments

* Update requirements-docker.txt

* chore: code cleanup

- default_model env var isn't necessary as model is included in default preset, same for speed
- ENV CMDARGS --listen is now synched with docker-compose.yml file
- remove

* Change entry_with_update.py to launch.py in entrypoint.sh

* Change CMD in Dockerfile

* Change default CMDARGS to --listen in Dockerfile

* Modify CMD in Dockerfile

* Fix docker-compose.yml

* Import files from models,outputs

* docs: change wording in docker.md, change git clone URL, add quotes to port mapping

* docs: remove docker publish github action, remove pre-built image from docs

* Modify modules versions for linux/arm64

* docs: update docker readme

---------

Co-authored-by: Manuel Schmid <9307310+mashb1t@users.noreply.github.com>
Co-authored-by: Manuel Schmid <dev@mash1t.de>
Co-authored-by: Manuel Schmid <manuel.schmid@odt.net>
2024-02-26 17:30:05 +01:00
Manuel Schmid b6d23670d8 feat: add jpg and webp support, add exif data handling for metadata (#1863)
* feature: added flag, config and ui update for image extension change #1789

* moved function to config module

* moved image extension to webui via async worker. Passing as parameter to log and get_current_html_path functions per feedback

* check flag before displaying image extension radio button

* disabled if image log flag is passed in

* fix: add missing image_extension parameter to log call

* refactor: change label

* feat: add webp to image_extensions

supported image extemsions: see https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html

* feat: use consistent file name in gradio

returns and uses filepaths instead of numpy image by saving to temp dir
uses double the temp dir file storage on disk as it saves to temp dir and gradio temp dir when displaying the image, but reuses logged output image

* feat: delete temp images after yielding to gradio

* feat: use args temp path if given

* chore: code cleanup, remove redundant if statement

* feat: always show image_extension element

this is now possible due to image extension support in gradio via https://github.com/lllyasviel/Fooocus/pull/1932

* refactor: rename image_extension to image_file_extension

* feat: use optimized jpg parameters when saving the image

quality=95
optimize=True
progressive=True

* refactor: rename image_file_extension to output_format

* feat: add exif handling

* refactor: code cleanup, remove items from metadata output

---------

Co-authored-by: Manuel Schmid <dev@mash1t.de>
Co-authored-by: Manuel Schmid <9307310+mashb1t@users.noreply.github.com>
Co-authored-by: Manuel Schmid <manuel.schmid@odt.net>
Co-authored by: eddyizm <wtfisup@hotmail.com>
2024-02-26 15:31:32 +01:00
Manuel Schmid ba9eadbcda feat: add metadata to images (#1940)
* feat: add metadata logging for images

inspired by https://github.com/MoonRide303/Fooocus-MRE

* feat: add config and checkbox for save_metadata_to_images

* feat: add argument disable_metadata

* feat: add support for A1111 metadata schema

https://github.com/AUTOMATIC1111/stable-diffusion-webui/blob/cf2772fab0af5573da775e7437e6acdca424f26e/modules/processing.py#L672

* feat: add model hash support for a1111

* feat: use resolved prompts with included expansion and styles for a1111 metadata

* fix: code cleanup and resolved prompt fixes

* feat: add config metadata_created_by

* fix: use stting isntead of quote wrap for A1111 created_by

* fix: correctlyy hide/show metadata schema on app start

* fix: do not generate hashes when arg --disable-metadata is used

* refactor: rename metadata_schema to metadata_scheme

* fix: use pnginfo "parameters" insteadf of "Comments"

see https://github.com/RupertAvery/DiffusionToolkit/issues/202 and https://github.com/AUTOMATIC1111/stable-diffusion-webui/blob/cf2772fab0af5573da775e7437e6acdca424f26e/modules/processing.py#L939

* feat: add resolved prompts to metadata

* fix: use correct default value in metadata check for created_by

* wip: add metadata mapping, reading and writing

applying data after reading currently not functional for A1111

* feat: rename metadata tab and import button label

* feat: map basic information for scheme A1111

* wip: optimize handling for metadata in Gradio calls

* feat: add enums for Performance, Steps and StepsUOV

also move MetadataSchema enum to prevent circular dependency

* fix: correctly map resolution, use empty styles for A1111

* chore: code cleanup

* feat: add A1111 prompt style detection

only detects one style as Fooocus doesn't wrap {prompt} with the whole style, but has a separate prompt string for each style

* wip: add prompt style extraction for A1111 scheme

* feat: sort styles after metadata import

* refactor: use central flag for LoRA count

* refactor: use central flag for ControlNet image count

* fix: use correct LoRA mapping, add fallback for backwards compatibility

* feat: add created_by again

* feat: add prefix "Fooocus" to version

* wip: code cleanup, update todos

* fix: use correct order to read LoRA in meta parser

* wip: code cleanup, update todos

* feat: make sha256 with length 10 default

* feat: add lora handling to A1111 scheme

* feat: override existing LoRA values when importing, would cause images to differ

* fix: correctly extract prompt style when only prompt expansion is selected

* feat: allow model / LoRA loading from subfolders

* feat: code cleanup, do not queue metadata preview on image upload

* refactor: add flag for refiner_swap_method

* feat: add metadata handling for all non-img2img parameters

* refactor: code cleanup

* chore: use str as return type in calculate_sha256

* feat: add hash cache to metadata

* chore: code cleanup

* feat: add method get_scheme to Metadata

* fix: align handling for scheme Fooocus by removing lcm lora from json parsing

* refactor: add step before parsing to set data in parser

- add constructor for MetadataSchema class
- remove showable and copyable from log output
- add functional hash cache (model hashing takes about 5 seconds, only required once per model, using hash lazy loading)

* feat: sort metadata attributes before writing to image

* feat: add translations and hint for image prompt parameters

* chore: check and remove ToDo's

* refactor: merge metadata.py into meta_parser.py

* fix: add missing refiner in A1111 parse_json

* wip: add TODO for ultiline prompt style resolution

* fix: remove sorting for A1111, change performance key position

fixes https://github.com/lllyasviel/Fooocus/pull/1940#issuecomment-1924444633

* fix: add workaround for multiline prompts

* feat: add sampler mapping

* feat: prevent config reset by renaming metadata_scheme to match config options

* chore: remove remaining todos after analysis

refiner is added when set
restoring multiline prompts has been resolved by using separate parameters "raw_prompt" and "raw_negative_prompt"

* chore: specify too broad exception types

* feat: add mapping for _gpu samplers to cpu samplers

gpu samplers are less deterministic than cpu but in general similar, see https://www.reddit.com/r/comfyui/comments/15hayzo/comment/juqcpep/

* feat: add better handling for image import with empty metadata

* fix: parse adaptive_cfg as float instead of string

* chore: loosen strict type for parse_json, fix indent

* chore: make steps enums more strict

* feat: only override steps if metadata value is not in steps enum or in steps enum and performance is not the same

* fix: handle empty strings in metadata

e.g. raw negative prompt when none is set
2024-02-26 14:27:57 +01:00
Manuel Schmid d3113f5c3f feat: use consistent file name in gradio (#1932)
* feat: use consistent file name in gradio

returns and uses filepaths instead of numpy image by saving to temp dir
uses double the temp dir file storage on disk as it saves to temp dir and gradio temp dir when displaying the image, but reuses logged output image

* feat: delete temp images after yielding to gradio

* feat: use args temp path if given

* chore: code cleanup, remove redundant if statement
2024-02-25 22:56:38 +01:00
Brian FlanneryandManuel Schmid c898e6a4dc feat: add array support on main prompt (#1503)
* prompt array support

* update change log

* update change log

* docs: remove 2.1.847 change log

* refactor: rename freeze_seed to disable_seed_increment, move to developer debug mode

* feat: add translation for new labels

* fix: use task_rng based on task_seed, not initial seed

---------

Co-authored-by: Manuel Schmid <manuel.schmid@odt.net>
2024-02-25 22:22:49 +01:00
MindOfMatterandManuel Schmid 3be76ef8a3 feat: make lora min max weight editable in config (#2216)
* Initial commit

* Update README.md

* sync with original main Fooocus repo

* update with my gitignore setup

* add min max weight configs feature

* add max lora config feature

* Revert "add max lora config feature"

This reverts commit cfe7463fe2.

* Update README.md

* Update .gitignore

* update

* merge

* revert

---------

Co-authored-by: Manuel Schmid <manuel.schmid@odt.net>
2024-02-25 21:36:25 +01:00
MindOfMatterandManuel Schmid 18f9f7dc31 feat: make lora number editable in config (#2215)
* Initial commit

* Update README.md

* sync with original main Fooocus repo

* update with my gitignore setup

* add max lora config feature

* Revert "add max lora config feature"

This reverts commit cfe7463fe2.

* add max loras config feature

* Update README.md

* Update .gitignore

* update

* merge

* revert

* refactor: rename default_loras_max_number to default_max_lora_number, validate config for int

* fix: add missing patch_all call and imports again

---------

Co-authored-by: Manuel Schmid <manuel.schmid@odt.net>
2024-02-25 21:12:26 +01:00
MindOfMatterandManuel Schmid 468d704b29 feat: add button to enable LoRAs (#2210)
* Initial commit

* Update README.md

* sync with original main Fooocus repo

* update with my gitignore setup

* add max lora config feature

* Revert "add max lora config feature"

This reverts commit cfe7463fe2.

* add lora enabler feature

* Update README.md

* Update .gitignore

* update

* merge

* revert changes

* revert

* feat: change width of LoRA columns

* refactor: rename lora_enable to lora_enabled, optimize code

---------

Co-authored-by: Manuel Schmid <manuel.schmid@odt.net>
2024-02-25 19:59:28 +01:00
Manuel Schmid eebd7752ab fix: allow path_outputs to be outside of root dir (#2332)
allows Gradio to serve outputs when folder has been changed in the config
2024-02-25 18:44:28 +01:00
Manuel Schmid b5f019fb62 fix: correctly create directory for path_outputs if not existing (#1668)
* correctly create directory for outputs if not existing

* feat: add make_directory parameter checks for list, extract make_directory to util
2024-02-25 18:41:43 +01:00
Manuel Schmid 9c19300a3e feat: improve bug report and feature request issue templates (#1631)
* refactor and improve bug report and feature request issue templates

* update operating system placeholder to Windows 10

most common usage i assume

* use already existing label "enhancement" instead of "feature"

* feat: add checkbox for latest version check, add triage to feature requests

* feat: add link to ask a question

* feat: use templates of stable-diffusion-webui-forge as basis

* feat: add optional hosting and operating system inputs
2024-02-25 18:04:46 +01:00
Maxim SaplinandManuel Schmid 4d34f31a72 feat: allow users to specify the number of threads when running on CPU (#1601)
* CPU_NUM_THREADS

* refactor: optimize code, type is already strict

---------

Co-authored-by: Manuel Schmid <manuel.schmid@odt.net>
2024-02-25 17:14:17 +01:00
dooglewoogleandManuel Schmid ef1999c52c feat: add ability to load checkpoints and loras from multiple locations (#1256)
* Add ability to load checkpoints and loras from multiple locations

* Found another location a default path is required

* feat: use array as default

---------

Co-authored-by: Manuel Schmid <manuel.schmid@odt.net>
2024-02-25 12:47:14 +01:00
Manuel Schmid 7cfb5e742d feat: add advanced parameter for disable_intermediate_results (progress_gallery) (#1013)
* add advanced parameter for disable_intermediate_results

prevents gradio frontend process from clogging image output and updates in high throughput scenarios such as LCM with image number >= 4

* update disable_intermediate_results correctly

based on default and selected performance

* chore: add missing translations
2024-02-25 11:31:00 +01:00
Manuel Schmid 965364cd80 feat: add list of 100 most popular animals to wildcards (#985) 2024-02-24 19:03:46 +01:00
Manuel Schmid 5b7ddf8b22 feat: advanced params refactoring + prevent users from skipping/stopping other users tasks in queue (#981)
* only make stop_button and skip_button interactive when rendering process starts

fix inconsistency in behaviour of stop_button and skip_button as it was possible to skip or stop other users processes while still being in queue

* use AsyncTask for last_stop handling instead of shared

* Revert "only make stop_button and skip_button interactive when rendering process starts"

This reverts commit d3f9156854.

* introduce state for task skipping/stopping

* fix return parameters of stop_clicked

* code cleanup, do not disable skip/stop on stop_clicked

* reset last_stop when skipping for further processing

* fix: replace fcbh with ldm_patched

* fix: use currentTask instead of ctrls after merging upstream

* feat: extract attribute disable_preview

* feat: extract attribute adm_scaler_positive

* feat: extract attribute adm_scaler_negative

* feat: extract attribute adm_scaler_end

* feat: extract attribute adaptive_cfg

* feat: extract attribute sampler_name

* feat: extract attribute scheduler_name

* feat: extract attribute generate_image_grid

* feat: extract attribute overwrite_step

* feat: extract attribute overwrite_switch

* feat: extract attribute overwrite_width

* feat: extract attribute overwrite_height

* feat: extract attribute overwrite_vary_strength

* feat: extract attribute overwrite_upscale_strength

* feat: extract attribute mixing_image_prompt_and_vary_upscale

* feat: extract attribute mixing_image_prompt_and_inpaint

* feat: extract attribute debugging_cn_preprocessor

* feat: extract attribute skipping_cn_preprocessor

* feat: extract attribute canny_low_threshold

* feat: extract attribute canny_high_threshold

* feat: extract attribute refiner_swap_method

* feat: extract freeu_ctrls attributes

freeu_enabled, freeu_b1, freeu_b2, freeu_s1, freeu_s2

* feat: extract inpaint_ctrls attributes

debugging_inpaint_preprocessor, inpaint_disable_initial_latent, inpaint_engine, inpaint_strength, inpaint_respective_field, inpaint_mask_upload_checkbox, invert_mask_checkbox, inpaint_erode_or_dilate

* wip: add TODOs

* chore: cleanup code

* feat: extract attribute controlnet_softness

* feat: extract remaining attributes, do not use globals in patch

* fix: resolve circular import, patch_all now in async_worker

* chore: cleanup pid code
2024-02-24 19:01:06 +01:00
Manuel Schmid 0ed01da4e4 Merge pull request #2313 from charliewilco/patch-1
chore: add .DS_Store to .gitignore
2024-02-22 21:53:36 +01:00
Charlie ⚡️ 187f4a76c6 Remove mac generated invisible files 2024-02-20 21:51:01 -05:00
Manuel Schmid f8ca04a406 feat: add early return for prompt expansion when no new tokens should be added
closes https://github.com/lllyasviel/Fooocus/issues/2278, also removes comma at the end added before tokenizer
2024-02-19 15:22:10 +01:00
Manuel Schmid a78f66ffb5 fix: sort with casefold, case insensitive
https://docs.python.org/3/library/stdtypes.html#str.casefold
2024-02-12 21:59:22 +01:00
Manuel Schmid 1c999be8c8 Merge pull request #2229 from lllyasviel/develop
Release 2.1.865
2024-02-11 15:20:27 +01:00
Manuel Schmid f4a8bf24cf fix: correctly calculate refiner switch when overwrite_switch is > 0 (#2165)
When using custom steps, the calculation of switching timing is wrong. Now it is modified to calculate "steps x timing" after custom steps are used.
By @xhoxye
2024-02-11 15:13:20 +01:00
074b655dff fix: implement output path argument (#2074)
* added function to check output path arg and override, other wise, use temp or fallback to config

* added function to check output path arg and override, other wise, use temp or fallback to config #2065

* Revert to 1bcbd650

* moved path output arg handling inside config start up

* Revert "added function to check output path arg and override, other wise, use temp or fallback to config"

This reverts commit fecb97b59c.

* Updated tag to uppercase

* updated docstring to standard double quotes.

Co-authored-by: Manuel Schmid <9307310+mashb1t@users.noreply.github.com>

* removed extra check on image log flag per feedback

* feat: update config_dict value when overriding path_outputs, change message

---------

Co-authored-by: Manuel Schmid <9307310+mashb1t@users.noreply.github.com>
Co-authored-by: Manuel Schmid <manuel.schmid@odt.net>
2024-02-11 13:04:06 +01:00
Manuel Schmid ee3ce95566 docs: update version 2024-02-10 21:59:13 +01:00
Manuel Schmid 2037de3fcb chore: fix typos and adjust wording (#1521, #1644, #1691, #1772) 2024-02-10 21:54:50 +01:00
eb3f4d745c feat: add suffix ordinals (#845)
* add suffix ordinals with lambda

* delay importing of modules.config (#2195)

* refactor: use easier to read version to find matching ordinal suffix

---------

Co-authored-by: rsl8 <138326583+rsl8@users.noreply.github.com>
Co-authored-by: Manuel Schmid <manuel.schmid@odt.net>
Co-authored-by: Manuel Schmid <9307310+mashb1t@users.noreply.github.com>
2024-02-10 21:49:23 +01:00
Praveen Kumar SridharandManuel Schmid b9d7e77b0d replaced the custom lcm function with math.lcm (#1122)
Co-authored-by: Manuel Schmid <manuel.schmid@odt.net>
2024-02-10 19:28:10 +01:00
Evgenii c32b9bdc44 fix: replace regexp to support unicode chars (#1424) 2024-02-10 19:15:57 +01:00
Manuel Schmid 98ba1d5d47 fix: correctly sort files, display deepest dir level first (#1784) 2024-02-10 19:03:26 +01:00
Dr. Christoph Mittendorf 231956065f Removing unnecessary comments / old code (#1905) 2024-02-10 18:51:03 +01:00
rsl8 e4929a9ed7 fix: do not overwrite $GRADIO_SERVER_PORT if it is already set (#1921) 2024-02-10 18:44:20 +01:00
Manuel Schmidandrsl8 b7715b0a0c fix: prevents outdated history log link after midnight (#1979)
* feat: update history link date after each generation

prevents outdated date in link after midnight

* delay importing of modules.config (#2195)

* fix: disable queue for initial queue loading

---------

Co-authored-by: rsl8 <138326583+rsl8@users.noreply.github.com>
2024-02-10 18:33:28 +01:00
ac10e51364 add auth to --listen and readme (#2127)
* Update webui.py

* Update readme.md

* Update webui.py

Only enable AuthN for --listen and --share

Co-authored-by: Manuel Schmid <9307310+mashb1t@users.noreply.github.com>

* docs: rephrase documentation changes for auth

---------

Co-authored-by: Manuel Schmid <9307310+mashb1t@users.noreply.github.com>
Co-authored-by: Manuel Schmid <manuel.schmid@odt.net>
2024-02-10 18:15:51 +01:00
rsl8 95f93a1f4b delay importing of modules.config (#2195) 2024-02-10 17:51:38 +01:00
V1sionVerse d1a450c581 Fixed mistakes in HTML generation (#2187)
Added <!DOCTYPE html> declaration
<img/> instead of <img></img>
<br/> instead of </br>
2024-02-10 17:50:41 +01:00
rsl8 fdc4dc1d87 delay importing of modules.config (#2195) 2024-02-10 17:42:30 +01:00
Justin Dhillon 71eb040afc Fix broken links (#2217)
* https://github.com/rlaphoenix/VSGAN/blob/master/vsgan/archs/esrgan.py

* https://github.com/huggingface/pytorch-image-models/blob/main/timm/layers/drop.py

* https://kornia.readthedocs.io/en/latest/
2024-02-10 17:36:56 +01:00
lllyasviel 1bcbd6501b fix config 2024-01-27 16:18:26 -08:00
lllyasviel 31fc99d2bc fix (#2069) 2024-01-27 09:07:30 -08:00
57 changed files with 2477 additions and 729 deletions
+1
View File
@@ -0,0 +1 @@
.idea
-18
View File
@@ -1,18 +0,0 @@
---
name: Bug report
about: Describe a problem
title: ''
labels: ''
assignees: ''
---
**Read Troubleshoot**
[x] I admit that I have read the [Troubleshoot](https://github.com/lllyasviel/Fooocus/blob/main/troubleshoot.md) before making this issue.
**Describe the problem**
A clear and concise description of what the bug is.
**Full Console Log**
Paste **full** console log here. You will make our job easier if you give a **full** log.
+107
View File
@@ -0,0 +1,107 @@
name: Bug Report
description: You think something is broken in Fooocus
title: "[Bug]: "
labels: ["bug", "triage"]
body:
- type: markdown
attributes:
value: |
> The title of the bug report should be short and descriptive.
> Use relevant keywords for searchability.
> Do not leave it blank, but also do not put an entire error log in it.
- type: checkboxes
attributes:
label: Checklist
description: |
Please perform basic debugging to see if your configuration is the cause of the issue.
Basic debug procedure
 2. Update Fooocus - sometimes things just need to be updated
 3. Backup and remove your config.txt - check if the issue is caused by bad configuration
 5. Try a fresh installation of Fooocus in a different directory - see if a clean installation solves the issue
Before making a issue report please, check that the issue hasn't been reported recently.
options:
- label: The issue has not been resolved by following the [troubleshooting guide](https://github.com/lllyasviel/Fooocus/blob/main/troubleshoot.md)
- label: The issue exists on a clean installation of Fooocus
- label: The issue exists in the current version of Fooocus
- label: The issue has not been reported before recently
- label: The issue has been reported before but has not been fixed yet
- type: markdown
attributes:
value: |
> Please fill this form with as much information as possible. Don't forget to add information about "What browsers" and provide screenshots if possible
- type: textarea
id: what-did
attributes:
label: What happened?
description: Tell us what happened in a very clear and simple way
placeholder: |
image generation is not working as intended.
validations:
required: true
- type: textarea
id: steps
attributes:
label: Steps to reproduce the problem
description: Please provide us with precise step by step instructions on how to reproduce the bug
placeholder: |
1. Go to ...
2. Press ...
3. ...
validations:
required: true
- type: textarea
id: what-should
attributes:
label: What should have happened?
description: Tell us what you think the normal behavior should be
placeholder: |
Fooocus should ...
validations:
required: true
- type: dropdown
id: browsers
attributes:
label: What browsers do you use to access Fooocus?
multiple: true
options:
- Mozilla Firefox
- Google Chrome
- Brave
- Apple Safari
- Microsoft Edge
- Android
- iOS
- Other
- type: dropdown
id: hosting
attributes:
label: Where are you running Fooocus?
multiple: false
options:
- Locally
- Locally with virtualization (e.g. Docker)
- Cloud (Google Colab)
- Cloud (other)
- type: input
id: operating-system
attributes:
label: What operating system are you using?
placeholder: |
Windows 10
- type: textarea
id: logs
attributes:
label: Console logs
description: Please provide **full** cmd/terminal logs from the moment you started UI to the end of it, after the bug occured. If it's very long, provide a link to pastebin or similar service.
render: Shell
validations:
required: true
- type: textarea
id: misc
attributes:
label: Additional information
description: |
Please provide us with any relevant additional info or context.
Examples:
 I have updated my GPU driver recently.
+5
View File
@@ -0,0 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: Ask a question
url: https://github.com/lllyasviel/Fooocus/discussions/new?category=q-a
about: Ask the community for help
-14
View File
@@ -1,14 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the idea you'd like**
A clear and concise description of what you want to happen.
@@ -0,0 +1,40 @@
name: Feature request
description: Suggest an idea for this project
title: "[Feature Request]: "
labels: ["enhancement", "triage"]
body:
- type: checkboxes
attributes:
label: Is there an existing issue for this?
description: Please search to see if an issue already exists for the feature you want, and that it's not implemented in a recent build/commit.
options:
- label: I have searched the existing issues and checked the recent builds/commits
required: true
- type: markdown
attributes:
value: |
*Please fill this form with as much information as possible, provide screenshots and/or illustrations of the feature if possible*
- type: textarea
id: feature
attributes:
label: What would your feature do?
description: Tell us about your feature in a very clear and simple way, and what problem it would solve
validations:
required: true
- type: textarea
id: workflow
attributes:
label: Proposed workflow
description: Please provide us with step by step information on how you'd like the feature to be accessed and used
value: |
1. Go to ....
2. Press ....
3. ...
validations:
required: true
- type: textarea
id: misc
attributes:
label: Additional information
description: Add any other context or screenshots about the feature request here.
+1
View File
@@ -51,3 +51,4 @@ user_path_config-deprecated.txt
/package-lock.json /package-lock.json
/.coverage* /.coverage*
/auth.json /auth.json
.DS_Store
+29
View File
@@ -0,0 +1,29 @@
FROM nvidia/cuda:12.3.1-base-ubuntu22.04
ENV DEBIAN_FRONTEND noninteractive
ENV CMDARGS --listen
RUN apt-get update -y && \
apt-get install -y curl libgl1 libglib2.0-0 python3-pip python-is-python3 git && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
COPY requirements_docker.txt requirements_versions.txt /tmp/
RUN pip install --no-cache-dir -r /tmp/requirements_docker.txt -r /tmp/requirements_versions.txt && \
rm -f /tmp/requirements_docker.txt /tmp/requirements_versions.txt
RUN pip install --no-cache-dir xformers==0.0.23 --no-dependencies
RUN curl -fsL -o /usr/local/lib/python3.10/dist-packages/gradio/frpc_linux_amd64_v0.2 https://cdn-media.huggingface.co/frpc-gradio-0.2/frpc_linux_amd64 && \
chmod +x /usr/local/lib/python3.10/dist-packages/gradio/frpc_linux_amd64_v0.2
RUN adduser --disabled-password --gecos '' user && \
mkdir -p /content/app /content/data
COPY entrypoint.sh /content/
RUN chown -R user:user /content
WORKDIR /content
USER user
RUN git clone https://github.com/lllyasviel/Fooocus /content/app
RUN mv /content/app/models /content/app/models.org
CMD [ "sh", "-c", "/content/entrypoint.sh ${CMDARGS}" ]
+10 -1
View File
@@ -1,8 +1,13 @@
import ldm_patched.modules.args_parser as args_parser import ldm_patched.modules.args_parser as args_parser
import os
from tempfile import gettempdir
args_parser.parser.add_argument("--share", action='store_true', help="Set whether to share on Gradio.") args_parser.parser.add_argument("--share", action='store_true', help="Set whether to share on Gradio.")
args_parser.parser.add_argument("--preset", type=str, default=None, help="Apply specified UI preset.") args_parser.parser.add_argument("--preset", type=str, default=None, help="Apply specified UI preset.")
args_parser.parser.add_argument("--disable-preset-selection", action='store_true',
help="Disables preset selection in Gradio.")
args_parser.parser.add_argument("--language", type=str, default='default', args_parser.parser.add_argument("--language", type=str, default='default',
help="Translate UI using json files in [language] folder. " help="Translate UI using json files in [language] folder. "
@@ -18,7 +23,10 @@ args_parser.parser.add_argument("--disable-image-log", action='store_true',
help="Prevent writing images and logs to hard drive.") help="Prevent writing images and logs to hard drive.")
args_parser.parser.add_argument("--disable-analytics", action='store_true', args_parser.parser.add_argument("--disable-analytics", action='store_true',
help="Disables analytics for Gradio", default=False) help="Disables analytics for Gradio.")
args_parser.parser.add_argument("--disable-metadata", action='store_true',
help="Disables saving metadata to images.")
args_parser.parser.add_argument("--disable-preset-download", action='store_true', args_parser.parser.add_argument("--disable-preset-download", action='store_true',
help="Disables downloading models for presets", default=False) help="Disables downloading models for presets", default=False)
@@ -40,6 +48,7 @@ args_parser.args.always_offload_from_vram = not args_parser.args.disable_offload
if args_parser.args.disable_analytics: if args_parser.args.disable_analytics:
import os import os
os.environ["GRADIO_ANALYTICS_ENABLED"] = "False" os.environ["GRADIO_ANALYTICS_ENABLED"] = "False"
if args_parser.args.disable_in_browser: if args_parser.args.disable_in_browser:
args_parser.args.in_browser = False args_parser.args.in_browser = False
+176
View File
@@ -1,5 +1,136 @@
/* based on https://github.com/AUTOMATIC1111/stable-diffusion-webui/blob/v1.6.0/style.css */ /* based on https://github.com/AUTOMATIC1111/stable-diffusion-webui/blob/v1.6.0/style.css */
.loader-container {
display: flex; /* Use flex to align items horizontally */
align-items: center; /* Center items vertically within the container */
white-space: nowrap; /* Prevent line breaks within the container */
}
.loader {
border: 8px solid #f3f3f3; /* Light grey */
border-top: 8px solid #3498db; /* Blue */
border-radius: 50%;
width: 30px;
height: 30px;
animation: spin 2s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* Style the progress bar */
progress {
appearance: none; /* Remove default styling */
height: 20px; /* Set the height of the progress bar */
border-radius: 5px; /* Round the corners of the progress bar */
background-color: #f3f3f3; /* Light grey background */
width: 100%;
}
/* Style the progress bar container */
.progress-container {
margin-left: 20px;
margin-right: 20px;
flex-grow: 1; /* Allow the progress container to take up remaining space */
}
/* Set the color of the progress bar fill */
progress::-webkit-progress-value {
background-color: #3498db; /* Blue color for the fill */
}
progress::-moz-progress-bar {
background-color: #3498db; /* Blue color for the fill in Firefox */
}
/* Style the text on the progress bar */
progress::after {
content: attr(value '%'); /* Display the progress value followed by '%' */
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: white; /* Set text color */
font-size: 14px; /* Set font size */
}
/* Style other texts */
.loader-container > span {
margin-left: 5px; /* Add spacing between the progress bar and the text */
}
.progress-bar > .generating {
display: none !important;
}
.progress-bar{
height: 30px !important;
}
.type_row{
height: 80px !important;
}
.type_row_half{
height: 32px !important;
}
.scroll-hide{
resize: none !important;
}
.refresh_button{
border: none !important;
background: none !important;
font-size: none !important;
box-shadow: none !important;
}
.advanced_check_row{
width: 250px !important;
}
.min_check{
min-width: min(1px, 100%) !important;
}
.resizable_area {
resize: vertical;
overflow: auto !important;
}
.aspect_ratios label {
width: 140px !important;
}
.aspect_ratios label span {
white-space: nowrap !important;
}
.aspect_ratios label input {
margin-left: -5px !important;
}
.lora_enable label {
height: 100%;
}
.lora_enable label input {
margin: auto;
}
.lora_enable label span {
display: none;
}
@-moz-document url-prefix() {
.lora_weight input[type=number] {
width: 80px;
}
}
#context-menu{ #context-menu{
z-index:9999; z-index:9999;
position:absolute; position:absolute;
@@ -218,3 +349,48 @@
#stylePreviewOverlay.lower-half { #stylePreviewOverlay.lower-half {
transform: translate(-140px, -140px); transform: translate(-140px, -140px);
} }
/* scrollable box for style selections */
.contain .tabs {
height: 100%;
}
.contain .tabs .tabitem.style_selections_tab {
height: 100%;
}
.contain .tabs .tabitem.style_selections_tab > div:first-child {
height: 100%;
}
.contain .tabs .tabitem.style_selections_tab .style_selections {
min-height: 200px;
height: 100%;
}
.contain .tabs .tabitem.style_selections_tab .style_selections .wrap[data-testid="checkbox-group"] {
position: absolute; /* remove this to disable scrolling within the checkbox-group */
overflow: auto;
padding-right: 2px;
max-height: 100%;
}
.contain .tabs .tabitem.style_selections_tab .style_selections .wrap[data-testid="checkbox-group"] label {
/* max-width: calc(35% - 15px) !important; */ /* add this to enable 3 columns layout */
flex: calc(50% - 5px) !important;
}
.contain .tabs .tabitem.style_selections_tab .style_selections .wrap[data-testid="checkbox-group"] label span {
/* white-space:nowrap; */ /* add this to disable text wrapping (better choice for 3 columns layout) */
overflow: hidden;
text-overflow: ellipsis;
}
/* styles preview tooltip */
.preview-tooltip {
background-color: #fff8;
font-family: monospace;
text-align: center;
border-radius-top: 5px;
display: none; /* remove this to enable tooltip in preview image */
}
+38
View File
@@ -0,0 +1,38 @@
version: '3.9'
volumes:
fooocus-data:
services:
app:
build: .
image: fooocus
ports:
- "7865:7865"
environment:
- CMDARGS=--listen # Arguments for launch.py.
- DATADIR=/content/data # Directory which stores models, outputs dir
- config_path=/content/data/config.txt
- config_example_path=/content/data/config_modification_tutorial.txt
- path_checkpoints=/content/data/models/checkpoints/
- path_loras=/content/data/models/loras/
- path_embeddings=/content/data/models/embeddings/
- path_vae_approx=/content/data/models/vae_approx/
- path_upscale_models=/content/data/models/upscale_models/
- path_inpaint=/content/data/models/inpaint/
- path_controlnet=/content/data/models/controlnet/
- path_clip_vision=/content/data/models/clip_vision/
- path_fooocus_expansion=/content/data/models/prompt_expansion/fooocus_expansion/
- path_outputs=/content/app/outputs/ # Warning: If it is not located under '/content/app', you can't see history log!
volumes:
- fooocus-data:/content/data
#- ./models:/import/models # Once you import files, you don't need to mount again.
#- ./outputs:/import/outputs # Once you import files, you don't need to mount again.
tty: true
deploy:
resources:
reservations:
devices:
- driver: nvidia
device_ids: ['0']
capabilities: [compute, utility]
+66
View File
@@ -0,0 +1,66 @@
# Fooocus on Docker
The docker image is based on NVIDIA CUDA 12.3 and PyTorch 2.0, see [Dockerfile](Dockerfile) and [requirements_docker.txt](requirements_docker.txt) for details.
## Quick start
**This is just an easy way for testing. Please find more information in the [notes](#notes).**
1. Clone this repository
2. Build the image with `docker compose build`
3. Run the docker container with `docker compose up`. Building the image takes some time.
When you see the message `Use the app with http://0.0.0.0:7865/` in the console, you can access the URL in your browser.
Your models and outputs are stored in the `fooocus-data` volume, which, depending on OS, is stored in `/var/lib/docker/volumes`.
## Details
### Update the container manually
When you are using `docker compose up` continuously, the container is not updated to the latest version of Fooocus automatically.
Run `git pull` before executing `docker compose build --no-cache` to build an image with the latest Fooocus version.
You can then start it with `docker compose up`
### Import models, outputs
If you want to import files from models or the outputs folder, you can uncomment the following settings in the [docker-compose.yml](docker-compose.yml):
```
#- ./models:/import/models # Once you import files, you don't need to mount again.
#- ./outputs:/import/outputs # Once you import files, you don't need to mount again.
```
After running `docker compose up`, your files will be copied into `/content/data/models` and `/content/data/outputs`
Since `/content/data` is a persistent volume folder, your files will be persisted even when you re-run `docker compose up --build` without above volume settings.
### Paths inside the container
|Path|Details|
|-|-|
|/content/app|The application stored folder|
|/content/app/models.org|Original 'models' folder.<br> Files are copied to the '/content/app/models' which is symlinked to '/content/data/models' every time the container boots. (Existing files will not be overwritten.) |
|/content/data|Persistent volume mount point|
|/content/data/models|The folder is symlinked to '/content/app/models'|
|/content/data/outputs|The folder is symlinked to '/content/app/outputs'|
### Environments
You can change `config.txt` parameters by using environment variables.
**The priority of using the environments is higher than the values defined in `config.txt`, and they will be saved to the `config_modification_tutorial.txt`**
Docker specified environments are there. They are used by 'entrypoint.sh'
|Environment|Details|
|-|-|
|DATADIR|'/content/data' location.|
|CMDARGS|Arguments for [entry_with_update.py](entry_with_update.py) which is called by [entrypoint.sh](entrypoint.sh)|
|config_path|'config.txt' location|
|config_example_path|'config_modification_tutorial.txt' location|
You can also use the same json key names and values explained in the 'config_modification_tutorial.txt' as the environments.
See examples in the [docker-compose.yml](docker-compose.yml)
## Notes
- Please keep 'path_outputs' under '/content/app'. Otherwise, you may get an error when you open the history log.
- Docker on Mac/Windows still has issues in the form of slow volume access when you use "bind mount" volumes. Please refer to [this article](https://docs.docker.com/storage/volumes/#use-a-volume-with-docker-compose) for not using "bind mount".
- The MPS backend (Metal Performance Shaders, Apple Silicon M1/M2/etc.) is not yet supported in Docker, see https://github.com/pytorch/pytorch/issues/81224
- You can also use `docker compose up -d` to start the container detached and connect to the logs with `docker compose logs -f`. This way you can also close the terminal and keep the container running.
Executable
+33
View File
@@ -0,0 +1,33 @@
#!/bin/bash
ORIGINALDIR=/content/app
# Use predefined DATADIR if it is defined
[[ x"${DATADIR}" == "x" ]] && DATADIR=/content/data
# Make persistent dir from original dir
function mklink () {
mkdir -p $DATADIR/$1
ln -s $DATADIR/$1 $ORIGINALDIR
}
# Copy old files from import dir
function import () {
(test -d /import/$1 && cd /import/$1 && cp -Rpn . $DATADIR/$1/)
}
cd $ORIGINALDIR
# models
mklink models
# Copy original files
(cd $ORIGINALDIR/models.org && cp -Rpn . $ORIGINALDIR/models/)
# Import old files
import models
# outputs
mklink outputs
# Import old files
import outputs
# Start application
python launch.py $*
+3
View File
@@ -112,6 +112,9 @@ class FooocusExpansion:
max_token_length = 75 * int(math.ceil(float(current_token_length) / 75.0)) max_token_length = 75 * int(math.ceil(float(current_token_length) / 75.0))
max_new_tokens = max_token_length - current_token_length max_new_tokens = max_token_length - current_token_length
if max_new_tokens == 0:
return prompt[:-1]
# https://huggingface.co/blog/introducing-csearch # https://huggingface.co/blog/introducing-csearch
# https://huggingface.co/docs/transformers/generation_strategies # https://huggingface.co/docs/transformers/generation_strategies
features = self.model.generate(**tokenized_kwargs, features = self.model.generate(**tokenized_kwargs,
+8 -9
View File
@@ -1,27 +1,26 @@
import cv2 import cv2
import numpy as np import numpy as np
import modules.advanced_parameters as advanced_parameters
def centered_canny(x: np.ndarray): def centered_canny(x: np.ndarray, canny_low_threshold, canny_high_threshold):
assert isinstance(x, np.ndarray) assert isinstance(x, np.ndarray)
assert x.ndim == 2 and x.dtype == np.uint8 assert x.ndim == 2 and x.dtype == np.uint8
y = cv2.Canny(x, int(advanced_parameters.canny_low_threshold), int(advanced_parameters.canny_high_threshold)) y = cv2.Canny(x, int(canny_low_threshold), int(canny_high_threshold))
y = y.astype(np.float32) / 255.0 y = y.astype(np.float32) / 255.0
return y return y
def centered_canny_color(x: np.ndarray): def centered_canny_color(x: np.ndarray, canny_low_threshold, canny_high_threshold):
assert isinstance(x, np.ndarray) assert isinstance(x, np.ndarray)
assert x.ndim == 3 and x.shape[2] == 3 assert x.ndim == 3 and x.shape[2] == 3
result = [centered_canny(x[..., i]) for i in range(3)] result = [centered_canny(x[..., i], canny_low_threshold, canny_high_threshold) for i in range(3)]
result = np.stack(result, axis=2) result = np.stack(result, axis=2)
return result return result
def pyramid_canny_color(x: np.ndarray): def pyramid_canny_color(x: np.ndarray, canny_low_threshold, canny_high_threshold):
assert isinstance(x, np.ndarray) assert isinstance(x, np.ndarray)
assert x.ndim == 3 and x.shape[2] == 3 assert x.ndim == 3 and x.shape[2] == 3
@@ -31,7 +30,7 @@ def pyramid_canny_color(x: np.ndarray):
for k in [0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]: for k in [0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]:
Hs, Ws = int(H * k), int(W * k) Hs, Ws = int(H * k), int(W * k)
small = cv2.resize(x, (Ws, Hs), interpolation=cv2.INTER_AREA) small = cv2.resize(x, (Ws, Hs), interpolation=cv2.INTER_AREA)
edge = centered_canny_color(small) edge = centered_canny_color(small, canny_low_threshold, canny_high_threshold)
if acc_edge is None: if acc_edge is None:
acc_edge = edge acc_edge = edge
else: else:
@@ -54,11 +53,11 @@ def norm255(x, low=4, high=96):
return x * 255.0 return x * 255.0
def canny_pyramid(x): def canny_pyramid(x, canny_low_threshold, canny_high_threshold):
# For some reasons, SAI's Control-lora Canny seems to be trained on canny maps with non-standard resolutions. # For some reasons, SAI's Control-lora Canny seems to be trained on canny maps with non-standard resolutions.
# Then we use pyramid to use all resolutions to avoid missing any structure in specific resolutions. # Then we use pyramid to use all resolutions to avoid missing any structure in specific resolutions.
color_canny = pyramid_canny_color(x) color_canny = pyramid_canny_color(x, canny_low_threshold, canny_high_threshold)
result = np.sum(color_canny, axis=2) result = np.sum(color_canny, axis=2)
return norm255(result, low=1, high=99).clip(0, 255).astype(np.uint8) return norm255(result, low=1, high=99).clip(0, 255).astype(np.uint8)
+1 -1
View File
@@ -12,7 +12,7 @@
"%cd /content\n", "%cd /content\n",
"!git clone https://github.com/lllyasviel/Fooocus.git\n", "!git clone https://github.com/lllyasviel/Fooocus.git\n",
"%cd /content/Fooocus\n", "%cd /content/Fooocus\n",
"!python entry_with_update.py --share\n" "!python entry_with_update.py --share --always-high-vram\n"
] ]
} }
], ],
+1 -1
View File
@@ -1 +1 @@
version = '2.1.864' version = '2.2.1'
-4
View File
@@ -154,12 +154,8 @@ let cancelGenerateForever = function() {
let generateOnRepeatForButtons = function() { let generateOnRepeatForButtons = function() {
generateOnRepeat('#generate_button', '#stop_button'); generateOnRepeat('#generate_button', '#stop_button');
}; };
appendContextMenuOption('#generate_button', 'Generate forever', generateOnRepeatForButtons); appendContextMenuOption('#generate_button', 'Generate forever', generateOnRepeatForButtons);
// appendContextMenuOption('#stop_button', 'Generate forever', generateOnRepeatForButtons);
// appendContextMenuOption('#stop_button', 'Cancel generate forever', cancelGenerateForever);
// appendContextMenuOption('#generate_button', 'Cancel generate forever', cancelGenerateForever);
})(); })();
//End example Context Menu Items //End example Context Menu Items
+9 -3
View File
@@ -150,9 +150,12 @@ function initStylePreviewOverlay() {
let overlayVisible = false; let overlayVisible = false;
const samplesPath = document.querySelector("meta[name='samples-path']").getAttribute("content") const samplesPath = document.querySelector("meta[name='samples-path']").getAttribute("content")
const overlay = document.createElement('div'); const overlay = document.createElement('div');
const tooltip = document.createElement('div');
tooltip.className = 'preview-tooltip';
overlay.appendChild(tooltip);
overlay.id = 'stylePreviewOverlay'; overlay.id = 'stylePreviewOverlay';
document.body.appendChild(overlay); document.body.appendChild(overlay);
document.addEventListener('mouseover', function(e) { document.addEventListener('mouseover', function (e) {
const label = e.target.closest('.style_selections label'); const label = e.target.closest('.style_selections label');
if (!label) return; if (!label) return;
label.removeEventListener("mouseout", onMouseLeave); label.removeEventListener("mouseout", onMouseLeave);
@@ -165,6 +168,9 @@ function initStylePreviewOverlay() {
"fooocus_v2", "fooocus_v2",
name.toLowerCase().replaceAll(" ", "_") name.toLowerCase().replaceAll(" ", "_")
).replaceAll("\\", "\\\\")}")`; ).replaceAll("\\", "\\\\")}")`;
tooltip.textContent = name;
function onMouseLeave() { function onMouseLeave() {
overlayVisible = false; overlayVisible = false;
overlay.style.opacity = "0"; overlay.style.opacity = "0";
@@ -172,8 +178,8 @@ function initStylePreviewOverlay() {
label.removeEventListener("mouseout", onMouseLeave); label.removeEventListener("mouseout", onMouseLeave);
} }
}); });
document.addEventListener('mousemove', function(e) { document.addEventListener('mousemove', function (e) {
if(!overlayVisible) return; if (!overlayVisible) return;
overlay.style.left = `${e.clientX}px`; overlay.style.left = `${e.clientX}px`;
overlay.style.top = `${e.clientY}px`; overlay.style.top = `${e.clientY}px`;
overlay.className = e.clientY > window.innerHeight / 2 ? "lower-half" : "upper-half"; overlay.className = e.clientY > window.innerHeight / 2 ? "lower-half" : "upper-half";
+18 -2
View File
@@ -38,9 +38,12 @@
"* \"Inpaint or Outpaint\" is powered by the sampler \"DPMPP Fooocus Seamless 2M SDE Karras Inpaint Sampler\" (beta)": "* \"Inpaint or Outpaint\" is powered by the sampler \"DPMPP Fooocus Seamless 2M SDE Karras Inpaint Sampler\" (beta)", "* \"Inpaint or Outpaint\" is powered by the sampler \"DPMPP Fooocus Seamless 2M SDE Karras Inpaint Sampler\" (beta)": "* \"Inpaint or Outpaint\" is powered by the sampler \"DPMPP Fooocus Seamless 2M SDE Karras Inpaint Sampler\" (beta)",
"Setting": "Setting", "Setting": "Setting",
"Style": "Style", "Style": "Style",
"Preset": "Preset",
"Performance": "Performance", "Performance": "Performance",
"Speed": "Speed", "Speed": "Speed",
"Quality": "Quality", "Quality": "Quality",
"Extreme Speed": "Extreme Speed",
"Lightning": "Lightning",
"Aspect Ratios": "Aspect Ratios", "Aspect Ratios": "Aspect Ratios",
"width \u00d7 height": "width \u00d7 height", "width \u00d7 height": "width \u00d7 height",
"Image Number": "Image Number", "Image Number": "Image Number",
@@ -48,6 +51,9 @@
"Describing what you do not want to see.": "Describing what you do not want to see.", "Describing what you do not want to see.": "Describing what you do not want to see.",
"Random": "Random", "Random": "Random",
"Seed": "Seed", "Seed": "Seed",
"Disable seed increment": "Disable seed increment",
"Disable automatic seed increment when image number is > 1.": "Disable automatic seed increment when image number is > 1.",
"Read wildcards in order": "Read wildcards in order",
"\ud83d\udcda History Log": "\uD83D\uDCDA History Log", "\ud83d\udcda History Log": "\uD83D\uDCDA History Log",
"Image Style": "Image Style", "Image Style": "Image Style",
"Fooocus V2": "Fooocus V2", "Fooocus V2": "Fooocus V2",
@@ -342,6 +348,10 @@
"Forced Overwrite of Denoising Strength of \"Vary\"": "Forced Overwrite of Denoising Strength of \"Vary\"", "Forced Overwrite of Denoising Strength of \"Vary\"": "Forced Overwrite of Denoising Strength of \"Vary\"",
"Set as negative number to disable. For developer debugging.": "Set as negative number to disable. For developer debugging.", "Set as negative number to disable. For developer debugging.": "Set as negative number to disable. For developer debugging.",
"Forced Overwrite of Denoising Strength of \"Upscale\"": "Forced Overwrite of Denoising Strength of \"Upscale\"", "Forced Overwrite of Denoising Strength of \"Upscale\"": "Forced Overwrite of Denoising Strength of \"Upscale\"",
"Disable Preview": "Disable Preview",
"Disable preview during generation.": "Disable preview during generation.",
"Disable Intermediate Results": "Disable Intermediate Results",
"Disable intermediate results during generation, only show final gallery.": "Disable intermediate results during generation, only show final gallery.",
"Inpaint Engine": "Inpaint Engine", "Inpaint Engine": "Inpaint Engine",
"v1": "v1", "v1": "v1",
"Version of Fooocus inpaint model": "Version of Fooocus inpaint model", "Version of Fooocus inpaint model": "Version of Fooocus inpaint model",
@@ -361,12 +371,18 @@
"B2": "B2", "B2": "B2",
"S1": "S1", "S1": "S1",
"S2": "S2", "S2": "S2",
"Extreme Speed": "Extreme Speed",
"\uD83D\uDD0E Type here to search styles ...": "\uD83D\uDD0E Type here to search styles ...", "\uD83D\uDD0E Type here to search styles ...": "\uD83D\uDD0E Type here to search styles ...",
"Type prompt here.": "Type prompt here.", "Type prompt here.": "Type prompt here.",
"Outpaint Expansion Direction:": "Outpaint Expansion Direction:", "Outpaint Expansion Direction:": "Outpaint Expansion Direction:",
"* Powered by Fooocus Inpaint Engine (beta)": "* Powered by Fooocus Inpaint Engine (beta)", "* Powered by Fooocus Inpaint Engine (beta)": "* Powered by Fooocus Inpaint Engine (beta)",
"Fooocus Enhance": "Fooocus Enhance", "Fooocus Enhance": "Fooocus Enhance",
"Fooocus Cinematic": "Fooocus Cinematic", "Fooocus Cinematic": "Fooocus Cinematic",
"Fooocus Sharp": "Fooocus Sharp" "Fooocus Sharp": "Fooocus Sharp",
"Drag any image generated by Fooocus here": "Drag any image generated by Fooocus here",
"Metadata": "Metadata",
"Apply Metadata": "Apply Metadata",
"Metadata Scheme": "Metadata Scheme",
"Image Prompt parameters are not included. Use png and a1111 for compatibility with Civitai.": "Image Prompt parameters are not included. Use png and a1111 for compatibility with Civitai.",
"fooocus (json)": "fooocus (json)",
"a1111 (plain text)": "a1111 (plain text)"
} }
+35 -25
View File
@@ -1,6 +1,6 @@
import os import os
import sys
import ssl import ssl
import sys
print('[System ARGV] ' + str(sys.argv)) print('[System ARGV] ' + str(sys.argv))
@@ -10,19 +10,17 @@ os.chdir(root)
os.environ["PYTORCH_ENABLE_MPS_FALLBACK"] = "1" os.environ["PYTORCH_ENABLE_MPS_FALLBACK"] = "1"
os.environ["PYTORCH_MPS_HIGH_WATERMARK_RATIO"] = "0.0" os.environ["PYTORCH_MPS_HIGH_WATERMARK_RATIO"] = "0.0"
os.environ["GRADIO_SERVER_PORT"] = "7865" if "GRADIO_SERVER_PORT" not in os.environ:
os.environ["GRADIO_SERVER_PORT"] = "7865"
ssl._create_default_https_context = ssl._create_unverified_context ssl._create_default_https_context = ssl._create_unverified_context
import platform import platform
import fooocus_version import fooocus_version
from build_launcher import build_launcher from build_launcher import build_launcher
from modules.launch_util import is_installed, run, python, run_pip, requirements_met from modules.launch_util import is_installed, run, python, run_pip, requirements_met, delete_folder_content
from modules.model_loader import load_file_from_url from modules.model_loader import load_file_from_url
from modules import config
REINSTALL_ALL = False REINSTALL_ALL = False
TRY_INSTALL_XFORMERS = False TRY_INSTALL_XFORMERS = False
@@ -42,7 +40,7 @@ def prepare_environment():
if TRY_INSTALL_XFORMERS: if TRY_INSTALL_XFORMERS:
if REINSTALL_ALL or not is_installed("xformers"): if REINSTALL_ALL or not is_installed("xformers"):
xformers_package = os.environ.get('XFORMERS_PACKAGE', 'xformers==0.0.20') xformers_package = os.environ.get('XFORMERS_PACKAGE', 'xformers==0.0.23')
if platform.system() == "Windows": if platform.system() == "Windows":
if platform.python_version().startswith("3.10"): if platform.python_version().startswith("3.10"):
run_pip(f"install -U -I --no-deps {xformers_package}", "xformers", live=True) run_pip(f"install -U -I --no-deps {xformers_package}", "xformers", live=True)
@@ -78,13 +76,24 @@ prepare_environment()
build_launcher() build_launcher()
args = ini_args() args = ini_args()
if args.gpu_device_id is not None: if args.gpu_device_id is not None:
os.environ['CUDA_VISIBLE_DEVICES'] = str(args.gpu_device_id) os.environ['CUDA_VISIBLE_DEVICES'] = str(args.gpu_device_id)
print("Set device to:", args.gpu_device_id) print("Set device to:", args.gpu_device_id)
from modules import config
def download_models(): os.environ['GRADIO_TEMP_DIR'] = config.temp_path
if config.temp_path_cleanup_on_launch:
print(f'[Cleanup] Attempting to delete content of temp dir {config.temp_path}')
result = delete_folder_content(config.temp_path, '[Cleanup] ')
if result:
print("[Cleanup] Cleanup successful")
else:
print(f"[Cleanup] Failed to delete content of temp dir.")
def download_models(default_model, previous_default_models, checkpoint_downloads, embeddings_downloads, lora_downloads):
for file_name, url in vae_approx_filenames: for file_name, url in vae_approx_filenames:
load_file_from_url(url=url, model_dir=config.path_vae_approx, file_name=file_name) load_file_from_url(url=url, model_dir=config.path_vae_approx, file_name=file_name)
@@ -96,31 +105,32 @@ def download_models():
if args.disable_preset_download: if args.disable_preset_download:
print('Skipped model download.') print('Skipped model download.')
return return default_model, checkpoint_downloads
if not args.always_download_new_model: if not args.always_download_new_model:
if not os.path.exists(os.path.join(config.path_checkpoints, config.default_base_model_name)): if not os.path.exists(os.path.join(config.paths_checkpoints[0], default_model)):
for alternative_model_name in config.previous_default_models: for alternative_model_name in previous_default_models:
if os.path.exists(os.path.join(config.path_checkpoints, alternative_model_name)): if os.path.exists(os.path.join(config.paths_checkpoints[0], alternative_model_name)):
print(f'You do not have [{config.default_base_model_name}] but you have [{alternative_model_name}].') print(f'You do not have [{default_model}] but you have [{alternative_model_name}].')
print(f'Fooocus will use [{alternative_model_name}] to avoid downloading new models, ' print(f'Fooocus will use [{alternative_model_name}] to avoid downloading new models, '
f'but you are not using latest models.') f'but you are not using the latest models.')
print('Use --always-download-new-model to avoid fallback and always get new models.') print('Use --always-download-new-model to avoid fallback and always get new models.')
config.checkpoint_downloads = {} checkpoint_downloads = {}
config.default_base_model_name = alternative_model_name default_model = alternative_model_name
break break
for file_name, url in config.checkpoint_downloads.items(): for file_name, url in checkpoint_downloads.items():
load_file_from_url(url=url, model_dir=config.path_checkpoints, file_name=file_name) load_file_from_url(url=url, model_dir=config.paths_checkpoints[0], file_name=file_name)
for file_name, url in config.embeddings_downloads.items(): for file_name, url in embeddings_downloads.items():
load_file_from_url(url=url, model_dir=config.path_embeddings, file_name=file_name) load_file_from_url(url=url, model_dir=config.path_embeddings, file_name=file_name)
for file_name, url in config.lora_downloads.items(): for file_name, url in lora_downloads.items():
load_file_from_url(url=url, model_dir=config.path_loras, file_name=file_name) load_file_from_url(url=url, model_dir=config.paths_loras[0], file_name=file_name)
return return default_model, checkpoint_downloads
download_models() config.default_base_model_name, config.checkpoint_downloads = download_models(
config.default_base_model_name, config.previous_default_models, config.checkpoint_downloads,
config.embeddings_downloads, config.lora_downloads)
from webui import * from webui import *
+3 -3
View File
@@ -78,7 +78,7 @@ def spatial_gradient(input, normalized: bool = True):
Return: Return:
the derivatives of the input feature map. with shape :math:`(B, C, 2, H, W)`. the derivatives of the input feature map. with shape :math:`(B, C, 2, H, W)`.
.. note:: .. note::
See a working example `here <https://kornia-tutorials.readthedocs.io/en/latest/ See a working example `here <https://kornia.readthedocs.io/en/latest/
filtering_edges.html>`__. filtering_edges.html>`__.
Examples: Examples:
>>> input = torch.rand(1, 3, 4, 4) >>> input = torch.rand(1, 3, 4, 4)
@@ -120,7 +120,7 @@ def rgb_to_grayscale(image, rgb_weights = None):
grayscale version of the image with shape :math:`(*,1,H,W)`. grayscale version of the image with shape :math:`(*,1,H,W)`.
.. note:: .. note::
See a working example `here <https://kornia-tutorials.readthedocs.io/en/latest/ See a working example `here <https://kornia.readthedocs.io/en/latest/
color_conversions.html>`__. color_conversions.html>`__.
Example: Example:
@@ -176,7 +176,7 @@ def canny(
- the canny edge magnitudes map, shape of :math:`(B,1,H,W)`. - the canny edge magnitudes map, shape of :math:`(B,1,H,W)`.
- the canny edge detection filtered by thresholds and hysteresis, shape of :math:`(B,1,H,W)`. - the canny edge detection filtered by thresholds and hysteresis, shape of :math:`(B,1,H,W)`.
.. note:: .. note::
See a working example `here <https://kornia-tutorials.readthedocs.io/en/latest/ See a working example `here <https://kornia.readthedocs.io/en/latest/
canny.html>`__. canny.html>`__.
Example: Example:
>>> input = torch.rand(5, 3, 4, 4) >>> input = torch.rand(5, 3, 4, 4)
+1 -2
View File
@@ -100,8 +100,7 @@ vram_group.add_argument("--always-high-vram", action="store_true")
vram_group.add_argument("--always-normal-vram", action="store_true") vram_group.add_argument("--always-normal-vram", action="store_true")
vram_group.add_argument("--always-low-vram", action="store_true") vram_group.add_argument("--always-low-vram", action="store_true")
vram_group.add_argument("--always-no-vram", action="store_true") vram_group.add_argument("--always-no-vram", action="store_true")
vram_group.add_argument("--always-cpu", action="store_true") vram_group.add_argument("--always-cpu", type=int, nargs="?", metavar="CPU_NUM_THREADS", const=-1)
parser.add_argument("--always-offload-from-vram", action="store_true") parser.add_argument("--always-offload-from-vram", action="store_true")
parser.add_argument("--pytorch-deterministic", action="store_true") parser.add_argument("--pytorch-deterministic", action="store_true")
+2 -4
View File
@@ -3,8 +3,6 @@ import math
import ldm_patched.modules.utils import ldm_patched.modules.utils
def lcm(a, b): #TODO: eventually replace by math.lcm (added in python3.9)
return abs(a*b) // math.gcd(a, b)
class CONDRegular: class CONDRegular:
def __init__(self, cond): def __init__(self, cond):
@@ -41,7 +39,7 @@ class CONDCrossAttn(CONDRegular):
if s1[0] != s2[0] or s1[2] != s2[2]: #these 2 cases should not happen if s1[0] != s2[0] or s1[2] != s2[2]: #these 2 cases should not happen
return False return False
mult_min = lcm(s1[1], s2[1]) mult_min = math.lcm(s1[1], s2[1])
diff = mult_min // min(s1[1], s2[1]) diff = mult_min // min(s1[1], s2[1])
if diff > 4: #arbitrary limit on the padding because it's probably going to impact performance negatively if it's too much if diff > 4: #arbitrary limit on the padding because it's probably going to impact performance negatively if it's too much
return False return False
@@ -52,7 +50,7 @@ class CONDCrossAttn(CONDRegular):
crossattn_max_len = self.cond.shape[1] crossattn_max_len = self.cond.shape[1]
for x in others: for x in others:
c = x.cond c = x.cond
crossattn_max_len = lcm(crossattn_max_len, c.shape[1]) crossattn_max_len = math.lcm(crossattn_max_len, c.shape[1])
conds.append(c) conds.append(c)
out = [] out = []
+3
View File
@@ -60,6 +60,9 @@ except:
pass pass
if args.always_cpu: if args.always_cpu:
if args.always_cpu > 0:
torch.set_num_threads(args.always_cpu)
print(f"Running on {torch.get_num_threads()} CPU threads")
cpu_state = CPUState.CPU cpu_state = CPUState.CPU
def is_intel_xpu(): def is_intel_xpu():
+2 -2
View File
@@ -14,7 +14,7 @@ from .timm.weight_init import trunc_normal_
def drop_path(x, drop_prob: float = 0.0, training: bool = False): def drop_path(x, drop_prob: float = 0.0, training: bool = False):
"""Drop paths (Stochastic Depth) per sample (when applied in main path of residual blocks). """Drop paths (Stochastic Depth) per sample (when applied in main path of residual blocks).
From: https://github.com/rwightman/pytorch-image-models/blob/master/timm/models/layers/drop.py From: https://github.com/huggingface/pytorch-image-models/blob/main/timm/layers/drop.py
""" """
if drop_prob == 0.0 or not training: if drop_prob == 0.0 or not training:
return x return x
@@ -30,7 +30,7 @@ def drop_path(x, drop_prob: float = 0.0, training: bool = False):
class DropPath(nn.Module): class DropPath(nn.Module):
"""Drop paths (Stochastic Depth) per sample (when applied in main path of residual blocks). """Drop paths (Stochastic Depth) per sample (when applied in main path of residual blocks).
From: https://github.com/rwightman/pytorch-image-models/blob/master/timm/models/layers/drop.py From: https://github.com/huggingface/pytorch-image-models/blob/main/timm/layers/drop.py
""" """
def __init__(self, drop_prob=None): def __init__(self, drop_prob=None):
+1 -1
View File
@@ -13,7 +13,7 @@ import torch.nn.functional as F
from . import block as B from . import block as B
# Borrowed from https://github.com/rlaphoenix/VSGAN/blob/master/vsgan/archs/ESRGAN.py # Borrowed from https://github.com/rlaphoenix/VSGAN/blob/master/vsgan/archs/esrgan.py
# Which enhanced stuff that was already here # Which enhanced stuff that was already here
class RRDBNet(nn.Module): class RRDBNet(nn.Module):
def __init__( def __init__(
@@ -2,7 +2,7 @@
Modified from https://github.com/sczhou/CodeFormer Modified from https://github.com/sczhou/CodeFormer
VQGAN code, adapted from the original created by the Unleashing Transformers authors: VQGAN code, adapted from the original created by the Unleashing Transformers authors:
https://github.com/samb-t/unleashing-transformers/blob/master/models/vqgan.py https://github.com/samb-t/unleashing-transformers/blob/master/models/vqgan.py
This verison of the arch specifically was gathered from an old version of GFPGAN. If this is a problem, please contact me. This version of the arch specifically was gathered from an old version of GFPGAN. If this is a problem, please contact me.
""" """
import math import math
from typing import Optional from typing import Optional
-33
View File
@@ -1,33 +0,0 @@
disable_preview, adm_scaler_positive, adm_scaler_negative, adm_scaler_end, adaptive_cfg, sampler_name, \
scheduler_name, generate_image_grid, overwrite_step, overwrite_switch, overwrite_width, overwrite_height, \
overwrite_vary_strength, overwrite_upscale_strength, \
mixing_image_prompt_and_vary_upscale, mixing_image_prompt_and_inpaint, \
debugging_cn_preprocessor, skipping_cn_preprocessor, controlnet_softness, canny_low_threshold, canny_high_threshold, \
refiner_swap_method, \
freeu_enabled, freeu_b1, freeu_b2, freeu_s1, freeu_s2, \
debugging_inpaint_preprocessor, inpaint_disable_initial_latent, inpaint_engine, inpaint_strength, inpaint_respective_field, \
inpaint_mask_upload_checkbox, invert_mask_checkbox, inpaint_erode_or_dilate = [None] * 35
def set_all_advanced_parameters(*args):
global disable_preview, adm_scaler_positive, adm_scaler_negative, adm_scaler_end, adaptive_cfg, sampler_name, \
scheduler_name, generate_image_grid, overwrite_step, overwrite_switch, overwrite_width, overwrite_height, \
overwrite_vary_strength, overwrite_upscale_strength, \
mixing_image_prompt_and_vary_upscale, mixing_image_prompt_and_inpaint, \
debugging_cn_preprocessor, skipping_cn_preprocessor, controlnet_softness, canny_low_threshold, canny_high_threshold, \
refiner_swap_method, \
freeu_enabled, freeu_b1, freeu_b2, freeu_s1, freeu_s2, \
debugging_inpaint_preprocessor, inpaint_disable_initial_latent, inpaint_engine, inpaint_strength, inpaint_respective_field, \
inpaint_mask_upload_checkbox, invert_mask_checkbox, inpaint_erode_or_dilate
disable_preview, adm_scaler_positive, adm_scaler_negative, adm_scaler_end, adaptive_cfg, sampler_name, \
scheduler_name, generate_image_grid, overwrite_step, overwrite_switch, overwrite_width, overwrite_height, \
overwrite_vary_strength, overwrite_upscale_strength, \
mixing_image_prompt_and_vary_upscale, mixing_image_prompt_and_inpaint, \
debugging_cn_preprocessor, skipping_cn_preprocessor, controlnet_softness, canny_low_threshold, canny_high_threshold, \
refiner_swap_method, \
freeu_enabled, freeu_b1, freeu_b2, freeu_s1, freeu_s2, \
debugging_inpaint_preprocessor, inpaint_disable_initial_latent, inpaint_engine, inpaint_strength, inpaint_respective_field, \
inpaint_mask_upload_checkbox, invert_mask_checkbox, inpaint_erode_or_dilate = args
return
+233 -139
View File
@@ -1,11 +1,16 @@
import threading import threading
import re
from modules.patch import PatchSettings, patch_settings, patch_all
patch_all()
class AsyncTask: class AsyncTask:
def __init__(self, args): def __init__(self, args):
self.args = args self.args = args
self.yields = [] self.yields = []
self.results = [] self.results = []
self.last_stop = False
self.processing = False
async_tasks = [] async_tasks = []
@@ -14,9 +19,11 @@ async_tasks = []
def worker(): def worker():
global async_tasks global async_tasks
import os
import traceback import traceback
import math import math
import numpy as np import numpy as np
import cv2
import torch import torch
import time import time
import shared import shared
@@ -31,17 +38,22 @@ def worker():
import extras.preprocessors as preprocessors import extras.preprocessors as preprocessors
import modules.inpaint_worker as inpaint_worker import modules.inpaint_worker as inpaint_worker
import modules.constants as constants import modules.constants as constants
import modules.advanced_parameters as advanced_parameters
import extras.ip_adapter as ip_adapter import extras.ip_adapter as ip_adapter
import extras.face_crop import extras.face_crop
import fooocus_version import fooocus_version
import args_manager
from modules.sdxl_styles import apply_style, apply_wildcards, fooocus_expansion from modules.sdxl_styles import apply_style, apply_wildcards, fooocus_expansion, apply_arrays
from modules.private_logger import log from modules.private_logger import log
from extras.expansion import safe_str from extras.expansion import safe_str
from modules.util import remove_empty_str, HWC3, resize_image, \ from modules.util import remove_empty_str, HWC3, resize_image, get_image_shape_ceil, set_image_shape_ceil, \
get_image_shape_ceil, set_image_shape_ceil, get_shape_ceil, resample_image, erode_or_dilate get_shape_ceil, resample_image, erode_or_dilate, ordinal_suffix, get_enabled_loras
from modules.upscaler import perform_upscale from modules.upscaler import perform_upscale
from modules.flags import Performance
from modules.meta_parser import get_metadata_parser, MetadataScheme
pid = os.getpid()
print(f'Started worker with PID {pid}')
try: try:
async_gradio_app = shared.gradio_root async_gradio_app = shared.gradio_root
@@ -69,19 +81,20 @@ def worker():
return return
def build_image_wall(async_task): def build_image_wall(async_task):
if not advanced_parameters.generate_image_grid: results = []
if len(async_task.results) < 2:
return return
results = async_task.results for img in async_task.results:
if isinstance(img, str) and os.path.exists(img):
if len(results) < 2: img = cv2.imread(img)
return img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
for img in results:
if not isinstance(img, np.ndarray): if not isinstance(img, np.ndarray):
return return
if img.ndim != 3: if img.ndim != 3:
return return
results.append(img)
H, W, C = results[0].shape H, W, C = results[0].shape
@@ -115,6 +128,7 @@ def worker():
@torch.inference_mode() @torch.inference_mode()
def handler(async_task): def handler(async_task):
execution_start_time = time.perf_counter() execution_start_time = time.perf_counter()
async_task.processing = True
args = async_task.args args = async_task.args
args.reverse() args.reverse()
@@ -122,16 +136,18 @@ def worker():
prompt = args.pop() prompt = args.pop()
negative_prompt = args.pop() negative_prompt = args.pop()
style_selections = args.pop() style_selections = args.pop()
performance_selection = args.pop() performance_selection = Performance(args.pop())
aspect_ratios_selection = args.pop() aspect_ratios_selection = args.pop()
image_number = args.pop() image_number = args.pop()
output_format = args.pop()
image_seed = args.pop() image_seed = args.pop()
read_wildcards_in_order = args.pop()
sharpness = args.pop() sharpness = args.pop()
guidance_scale = args.pop() guidance_scale = args.pop()
base_model_name = args.pop() base_model_name = args.pop()
refiner_model_name = args.pop() refiner_model_name = args.pop()
refiner_switch = args.pop() refiner_switch = args.pop()
loras = [[str(args.pop()), float(args.pop())] for _ in range(5)] loras = get_enabled_loras([[bool(args.pop()), str(args.pop()), float(args.pop())] for _ in range(modules.config.default_max_lora_number)])
input_image_checkbox = args.pop() input_image_checkbox = args.pop()
current_tab = args.pop() current_tab = args.pop()
uov_method = args.pop() uov_method = args.pop()
@@ -141,8 +157,48 @@ def worker():
inpaint_additional_prompt = args.pop() inpaint_additional_prompt = args.pop()
inpaint_mask_image_upload = args.pop() inpaint_mask_image_upload = args.pop()
disable_preview = args.pop()
disable_intermediate_results = args.pop()
disable_seed_increment = args.pop()
adm_scaler_positive = args.pop()
adm_scaler_negative = args.pop()
adm_scaler_end = args.pop()
adaptive_cfg = args.pop()
sampler_name = args.pop()
scheduler_name = args.pop()
overwrite_step = args.pop()
overwrite_switch = args.pop()
overwrite_width = args.pop()
overwrite_height = args.pop()
overwrite_vary_strength = args.pop()
overwrite_upscale_strength = args.pop()
mixing_image_prompt_and_vary_upscale = args.pop()
mixing_image_prompt_and_inpaint = args.pop()
debugging_cn_preprocessor = args.pop()
skipping_cn_preprocessor = args.pop()
canny_low_threshold = args.pop()
canny_high_threshold = args.pop()
refiner_swap_method = args.pop()
controlnet_softness = args.pop()
freeu_enabled = args.pop()
freeu_b1 = args.pop()
freeu_b2 = args.pop()
freeu_s1 = args.pop()
freeu_s2 = args.pop()
debugging_inpaint_preprocessor = args.pop()
inpaint_disable_initial_latent = args.pop()
inpaint_engine = args.pop()
inpaint_strength = args.pop()
inpaint_respective_field = args.pop()
inpaint_mask_upload_checkbox = args.pop()
invert_mask_checkbox = args.pop()
inpaint_erode_or_dilate = args.pop()
save_metadata_to_images = args.pop() if not args_manager.args.disable_metadata else False
metadata_scheme = MetadataScheme(args.pop()) if not args_manager.args.disable_metadata else MetadataScheme.FOOOCUS
cn_tasks = {x: [] for x in flags.ip_list} cn_tasks = {x: [] for x in flags.ip_list}
for _ in range(4): for _ in range(flags.controlnet_image_count):
cn_img = args.pop() cn_img = args.pop()
cn_stop = args.pop() cn_stop = args.pop()
cn_weight = args.pop() cn_weight = args.pop()
@@ -167,17 +223,9 @@ def worker():
print(f'Refiner disabled because base model and refiner are same.') print(f'Refiner disabled because base model and refiner are same.')
refiner_model_name = 'None' refiner_model_name = 'None'
assert performance_selection in ['Speed', 'Quality', 'Extreme Speed'] steps = performance_selection.steps()
steps = 30 if performance_selection == Performance.EXTREME_SPEED:
if performance_selection == 'Speed':
steps = 30
if performance_selection == 'Quality':
steps = 60
if performance_selection == 'Extreme Speed':
print('Enter LCM mode.') print('Enter LCM mode.')
progressbar(async_task, 1, 'Downloading LCM components ...') progressbar(async_task, 1, 'Downloading LCM components ...')
loras += [(modules.config.downloading_sdxl_lcm_lora(), 1.0)] loras += [(modules.config.downloading_sdxl_lcm_lora(), 1.0)]
@@ -186,30 +234,51 @@ def worker():
print(f'Refiner disabled in LCM mode.') print(f'Refiner disabled in LCM mode.')
refiner_model_name = 'None' refiner_model_name = 'None'
sampler_name = advanced_parameters.sampler_name = 'lcm' sampler_name = 'lcm'
scheduler_name = advanced_parameters.scheduler_name = 'lcm' scheduler_name = 'lcm'
modules.patch.sharpness = sharpness = 0.0 sharpness = 0.0
cfg_scale = guidance_scale = 1.0 guidance_scale = 1.0
modules.patch.adaptive_cfg = advanced_parameters.adaptive_cfg = 1.0 adaptive_cfg = 1.0
refiner_switch = 1.0 refiner_switch = 1.0
modules.patch.positive_adm_scale = advanced_parameters.adm_scaler_positive = 1.0 adm_scaler_positive = 1.0
modules.patch.negative_adm_scale = advanced_parameters.adm_scaler_negative = 1.0 adm_scaler_negative = 1.0
modules.patch.adm_scaler_end = advanced_parameters.adm_scaler_end = 0.0 adm_scaler_end = 0.0
steps = 8
modules.patch.adaptive_cfg = advanced_parameters.adaptive_cfg elif performance_selection == Performance.LIGHTNING:
print(f'[Parameters] Adaptive CFG = {modules.patch.adaptive_cfg}') print('Enter Lightning mode.')
progressbar(async_task, 1, 'Downloading Lightning components ...')
loras += [(modules.config.downloading_sdxl_lightning_lora(), 1.0)]
modules.patch.sharpness = sharpness if refiner_model_name != 'None':
print(f'[Parameters] Sharpness = {modules.patch.sharpness}') print(f'Refiner disabled in Lightning mode.')
modules.patch.positive_adm_scale = advanced_parameters.adm_scaler_positive refiner_model_name = 'None'
modules.patch.negative_adm_scale = advanced_parameters.adm_scaler_negative sampler_name = 'euler'
modules.patch.adm_scaler_end = advanced_parameters.adm_scaler_end scheduler_name = 'sgm_uniform'
sharpness = 0.0
guidance_scale = 1.0
adaptive_cfg = 1.0
refiner_switch = 1.0
adm_scaler_positive = 1.0
adm_scaler_negative = 1.0
adm_scaler_end = 0.0
print(f'[Parameters] Adaptive CFG = {adaptive_cfg}')
print(f'[Parameters] Sharpness = {sharpness}')
print(f'[Parameters] ControlNet Softness = {controlnet_softness}')
print(f'[Parameters] ADM Scale = ' print(f'[Parameters] ADM Scale = '
f'{modules.patch.positive_adm_scale} : ' f'{adm_scaler_positive} : '
f'{modules.patch.negative_adm_scale} : ' f'{adm_scaler_negative} : '
f'{modules.patch.adm_scaler_end}') f'{adm_scaler_end}')
patch_settings[pid] = PatchSettings(
sharpness,
adm_scaler_end,
adm_scaler_positive,
adm_scaler_negative,
controlnet_softness,
adaptive_cfg
)
cfg_scale = float(guidance_scale) cfg_scale = float(guidance_scale)
print(f'[Parameters] CFG = {cfg_scale}') print(f'[Parameters] CFG = {cfg_scale}')
@@ -222,10 +291,9 @@ def worker():
width, height = int(width), int(height) width, height = int(width), int(height)
skip_prompt_processing = False skip_prompt_processing = False
refiner_swap_method = advanced_parameters.refiner_swap_method
inpaint_worker.current_task = None inpaint_worker.current_task = None
inpaint_parameterized = advanced_parameters.inpaint_engine != 'None' inpaint_parameterized = inpaint_engine != 'None'
inpaint_image = None inpaint_image = None
inpaint_mask = None inpaint_mask = None
inpaint_head_model_path = None inpaint_head_model_path = None
@@ -239,15 +307,12 @@ def worker():
seed = int(image_seed) seed = int(image_seed)
print(f'[Parameters] Seed = {seed}') print(f'[Parameters] Seed = {seed}')
sampler_name = advanced_parameters.sampler_name
scheduler_name = advanced_parameters.scheduler_name
goals = [] goals = []
tasks = [] tasks = []
if input_image_checkbox: if input_image_checkbox:
if (current_tab == 'uov' or ( if (current_tab == 'uov' or (
current_tab == 'ip' and advanced_parameters.mixing_image_prompt_and_vary_upscale)) \ current_tab == 'ip' and mixing_image_prompt_and_vary_upscale)) \
and uov_method != flags.disabled and uov_input_image is not None: and uov_method != flags.disabled and uov_input_image is not None:
uov_input_image = HWC3(uov_input_image) uov_input_image = HWC3(uov_input_image)
if 'vary' in uov_method: if 'vary' in uov_method:
@@ -257,26 +322,17 @@ def worker():
if 'fast' in uov_method: if 'fast' in uov_method:
skip_prompt_processing = True skip_prompt_processing = True
else: else:
steps = 18 steps = performance_selection.steps_uov()
if performance_selection == 'Speed':
steps = 18
if performance_selection == 'Quality':
steps = 36
if performance_selection == 'Extreme Speed':
steps = 8
progressbar(async_task, 1, 'Downloading upscale models ...') progressbar(async_task, 1, 'Downloading upscale models ...')
modules.config.downloading_upscale_model() modules.config.downloading_upscale_model()
if (current_tab == 'inpaint' or ( if (current_tab == 'inpaint' or (
current_tab == 'ip' and advanced_parameters.mixing_image_prompt_and_inpaint)) \ current_tab == 'ip' and mixing_image_prompt_and_inpaint)) \
and isinstance(inpaint_input_image, dict): and isinstance(inpaint_input_image, dict):
inpaint_image = inpaint_input_image['image'] inpaint_image = inpaint_input_image['image']
inpaint_mask = inpaint_input_image['mask'][:, :, 0] inpaint_mask = inpaint_input_image['mask'][:, :, 0]
if advanced_parameters.inpaint_mask_upload_checkbox: if inpaint_mask_upload_checkbox:
if isinstance(inpaint_mask_image_upload, np.ndarray): if isinstance(inpaint_mask_image_upload, np.ndarray):
if inpaint_mask_image_upload.ndim == 3: if inpaint_mask_image_upload.ndim == 3:
H, W, C = inpaint_image.shape H, W, C = inpaint_image.shape
@@ -285,10 +341,10 @@ def worker():
inpaint_mask_image_upload = (inpaint_mask_image_upload > 127).astype(np.uint8) * 255 inpaint_mask_image_upload = (inpaint_mask_image_upload > 127).astype(np.uint8) * 255
inpaint_mask = np.maximum(inpaint_mask, inpaint_mask_image_upload) inpaint_mask = np.maximum(inpaint_mask, inpaint_mask_image_upload)
if int(advanced_parameters.inpaint_erode_or_dilate) != 0: if int(inpaint_erode_or_dilate) != 0:
inpaint_mask = erode_or_dilate(inpaint_mask, advanced_parameters.inpaint_erode_or_dilate) inpaint_mask = erode_or_dilate(inpaint_mask, inpaint_erode_or_dilate)
if advanced_parameters.invert_mask_checkbox: if invert_mask_checkbox:
inpaint_mask = 255 - inpaint_mask inpaint_mask = 255 - inpaint_mask
inpaint_image = HWC3(inpaint_image) inpaint_image = HWC3(inpaint_image)
@@ -299,12 +355,12 @@ def worker():
if inpaint_parameterized: if inpaint_parameterized:
progressbar(async_task, 1, 'Downloading inpainter ...') progressbar(async_task, 1, 'Downloading inpainter ...')
inpaint_head_model_path, inpaint_patch_model_path = modules.config.downloading_inpaint_models( inpaint_head_model_path, inpaint_patch_model_path = modules.config.downloading_inpaint_models(
advanced_parameters.inpaint_engine) inpaint_engine)
base_model_additional_loras += [(inpaint_patch_model_path, 1.0)] base_model_additional_loras += [(inpaint_patch_model_path, 1.0)]
print(f'[Inpaint] Current inpaint model is {inpaint_patch_model_path}') print(f'[Inpaint] Current inpaint model is {inpaint_patch_model_path}')
if refiner_model_name == 'None': if refiner_model_name == 'None':
use_synthetic_refiner = True use_synthetic_refiner = True
refiner_switch = 0.5 refiner_switch = 0.8
else: else:
inpaint_head_model_path, inpaint_patch_model_path = None, None inpaint_head_model_path, inpaint_patch_model_path = None, None
print(f'[Inpaint] Parameterized inpaint is disabled.') print(f'[Inpaint] Parameterized inpaint is disabled.')
@@ -315,8 +371,8 @@ def worker():
prompt = inpaint_additional_prompt + '\n' + prompt prompt = inpaint_additional_prompt + '\n' + prompt
goals.append('inpaint') goals.append('inpaint')
if current_tab == 'ip' or \ if current_tab == 'ip' or \
advanced_parameters.mixing_image_prompt_and_inpaint or \ mixing_image_prompt_and_vary_upscale or \
advanced_parameters.mixing_image_prompt_and_vary_upscale: mixing_image_prompt_and_inpaint:
goals.append('cn') goals.append('cn')
progressbar(async_task, 1, 'Downloading control models ...') progressbar(async_task, 1, 'Downloading control models ...')
if len(cn_tasks[flags.cn_canny]) > 0: if len(cn_tasks[flags.cn_canny]) > 0:
@@ -335,19 +391,19 @@ def worker():
ip_adapter.load_ip_adapter(clip_vision_path, ip_negative_path, ip_adapter_path) ip_adapter.load_ip_adapter(clip_vision_path, ip_negative_path, ip_adapter_path)
ip_adapter.load_ip_adapter(clip_vision_path, ip_negative_path, ip_adapter_face_path) ip_adapter.load_ip_adapter(clip_vision_path, ip_negative_path, ip_adapter_face_path)
if overwrite_step > 0:
steps = overwrite_step
switch = int(round(steps * refiner_switch)) switch = int(round(steps * refiner_switch))
if advanced_parameters.overwrite_step > 0: if overwrite_switch > 0:
steps = advanced_parameters.overwrite_step switch = overwrite_switch
if advanced_parameters.overwrite_switch > 0: if overwrite_width > 0:
switch = advanced_parameters.overwrite_switch width = overwrite_width
if advanced_parameters.overwrite_width > 0: if overwrite_height > 0:
width = advanced_parameters.overwrite_width height = overwrite_height
if advanced_parameters.overwrite_height > 0:
height = advanced_parameters.overwrite_height
print(f'[Parameters] Sampler = {sampler_name} - {scheduler_name}') print(f'[Parameters] Sampler = {sampler_name} - {scheduler_name}')
print(f'[Parameters] Steps = {steps} - {switch}') print(f'[Parameters] Steps = {steps} - {switch}')
@@ -376,14 +432,19 @@ def worker():
progressbar(async_task, 3, 'Processing prompts ...') progressbar(async_task, 3, 'Processing prompts ...')
tasks = [] tasks = []
for i in range(image_number):
task_seed = (seed + i) % (constants.MAX_SEED + 1) # randint is inclusive, % is not
task_rng = random.Random(task_seed) # may bind to inpaint noise in the future
task_prompt = apply_wildcards(prompt, task_rng) for i in range(image_number):
task_negative_prompt = apply_wildcards(negative_prompt, task_rng) if disable_seed_increment:
task_extra_positive_prompts = [apply_wildcards(pmt, task_rng) for pmt in extra_positive_prompts] task_seed = seed % (constants.MAX_SEED + 1)
task_extra_negative_prompts = [apply_wildcards(pmt, task_rng) for pmt in extra_negative_prompts] else:
task_seed = (seed + i) % (constants.MAX_SEED + 1) # randint is inclusive, % is not
task_rng = random.Random(task_seed) # may bind to inpaint noise in the future
task_prompt = apply_wildcards(prompt, task_rng, i, read_wildcards_in_order)
task_prompt = apply_arrays(task_prompt, i)
task_negative_prompt = apply_wildcards(negative_prompt, task_rng, i, read_wildcards_in_order)
task_extra_positive_prompts = [apply_wildcards(pmt, task_rng, i, read_wildcards_in_order) for pmt in extra_positive_prompts]
task_extra_negative_prompts = [apply_wildcards(pmt, task_rng, i, read_wildcards_in_order) for pmt in extra_negative_prompts]
positive_basic_workloads = [] positive_basic_workloads = []
negative_basic_workloads = [] negative_basic_workloads = []
@@ -446,8 +507,8 @@ def worker():
denoising_strength = 0.5 denoising_strength = 0.5
if 'strong' in uov_method: if 'strong' in uov_method:
denoising_strength = 0.85 denoising_strength = 0.85
if advanced_parameters.overwrite_vary_strength > 0: if overwrite_vary_strength > 0:
denoising_strength = advanced_parameters.overwrite_vary_strength denoising_strength = overwrite_vary_strength
shape_ceil = get_image_shape_ceil(uov_input_image) shape_ceil = get_image_shape_ceil(uov_input_image)
if shape_ceil < 1024: if shape_ceil < 1024:
@@ -510,16 +571,16 @@ def worker():
direct_return = False direct_return = False
if direct_return: if direct_return:
d = [('Upscale (Fast)', '2x')] d = [('Upscale (Fast)', 'upscale_fast', '2x')]
log(uov_input_image, d) uov_input_image_path = log(uov_input_image, d, output_format=output_format)
yield_result(async_task, uov_input_image, do_not_show_finished_images=True) yield_result(async_task, uov_input_image_path, do_not_show_finished_images=True)
return return
tiled = True tiled = True
denoising_strength = 0.382 denoising_strength = 0.382
if advanced_parameters.overwrite_upscale_strength > 0: if overwrite_upscale_strength > 0:
denoising_strength = advanced_parameters.overwrite_upscale_strength denoising_strength = overwrite_upscale_strength
initial_pixels = core.numpy_to_pytorch(uov_input_image) initial_pixels = core.numpy_to_pytorch(uov_input_image)
progressbar(async_task, 13, 'VAE encoding ...') progressbar(async_task, 13, 'VAE encoding ...')
@@ -563,19 +624,19 @@ def worker():
inpaint_image = np.ascontiguousarray(inpaint_image.copy()) inpaint_image = np.ascontiguousarray(inpaint_image.copy())
inpaint_mask = np.ascontiguousarray(inpaint_mask.copy()) inpaint_mask = np.ascontiguousarray(inpaint_mask.copy())
advanced_parameters.inpaint_strength = 1.0 inpaint_strength = 1.0
advanced_parameters.inpaint_respective_field = 1.0 inpaint_respective_field = 1.0
denoising_strength = advanced_parameters.inpaint_strength denoising_strength = inpaint_strength
inpaint_worker.current_task = inpaint_worker.InpaintWorker( inpaint_worker.current_task = inpaint_worker.InpaintWorker(
image=inpaint_image, image=inpaint_image,
mask=inpaint_mask, mask=inpaint_mask,
use_fill=denoising_strength > 0.99, use_fill=denoising_strength > 0.99,
k=advanced_parameters.inpaint_respective_field k=inpaint_respective_field
) )
if advanced_parameters.debugging_inpaint_preprocessor: if debugging_inpaint_preprocessor:
yield_result(async_task, inpaint_worker.current_task.visualize_mask_processing(), yield_result(async_task, inpaint_worker.current_task.visualize_mask_processing(),
do_not_show_finished_images=True) do_not_show_finished_images=True)
return return
@@ -621,7 +682,7 @@ def worker():
model=pipeline.final_unet model=pipeline.final_unet
) )
if not advanced_parameters.inpaint_disable_initial_latent: if not inpaint_disable_initial_latent:
initial_latent = {'samples': latent_fill} initial_latent = {'samples': latent_fill}
B, C, H, W = latent_fill.shape B, C, H, W = latent_fill.shape
@@ -634,24 +695,24 @@ def worker():
cn_img, cn_stop, cn_weight = task cn_img, cn_stop, cn_weight = task
cn_img = resize_image(HWC3(cn_img), width=width, height=height) cn_img = resize_image(HWC3(cn_img), width=width, height=height)
if not advanced_parameters.skipping_cn_preprocessor: if not skipping_cn_preprocessor:
cn_img = preprocessors.canny_pyramid(cn_img) cn_img = preprocessors.canny_pyramid(cn_img, canny_low_threshold, canny_high_threshold)
cn_img = HWC3(cn_img) cn_img = HWC3(cn_img)
task[0] = core.numpy_to_pytorch(cn_img) task[0] = core.numpy_to_pytorch(cn_img)
if advanced_parameters.debugging_cn_preprocessor: if debugging_cn_preprocessor:
yield_result(async_task, cn_img, do_not_show_finished_images=True) yield_result(async_task, cn_img, do_not_show_finished_images=True)
return return
for task in cn_tasks[flags.cn_cpds]: for task in cn_tasks[flags.cn_cpds]:
cn_img, cn_stop, cn_weight = task cn_img, cn_stop, cn_weight = task
cn_img = resize_image(HWC3(cn_img), width=width, height=height) cn_img = resize_image(HWC3(cn_img), width=width, height=height)
if not advanced_parameters.skipping_cn_preprocessor: if not skipping_cn_preprocessor:
cn_img = preprocessors.cpds(cn_img) cn_img = preprocessors.cpds(cn_img)
cn_img = HWC3(cn_img) cn_img = HWC3(cn_img)
task[0] = core.numpy_to_pytorch(cn_img) task[0] = core.numpy_to_pytorch(cn_img)
if advanced_parameters.debugging_cn_preprocessor: if debugging_cn_preprocessor:
yield_result(async_task, cn_img, do_not_show_finished_images=True) yield_result(async_task, cn_img, do_not_show_finished_images=True)
return return
for task in cn_tasks[flags.cn_ip]: for task in cn_tasks[flags.cn_ip]:
@@ -662,21 +723,21 @@ def worker():
cn_img = resize_image(cn_img, width=224, height=224, resize_mode=0) cn_img = resize_image(cn_img, width=224, height=224, resize_mode=0)
task[0] = ip_adapter.preprocess(cn_img, ip_adapter_path=ip_adapter_path) task[0] = ip_adapter.preprocess(cn_img, ip_adapter_path=ip_adapter_path)
if advanced_parameters.debugging_cn_preprocessor: if debugging_cn_preprocessor:
yield_result(async_task, cn_img, do_not_show_finished_images=True) yield_result(async_task, cn_img, do_not_show_finished_images=True)
return return
for task in cn_tasks[flags.cn_ip_face]: for task in cn_tasks[flags.cn_ip_face]:
cn_img, cn_stop, cn_weight = task cn_img, cn_stop, cn_weight = task
cn_img = HWC3(cn_img) cn_img = HWC3(cn_img)
if not advanced_parameters.skipping_cn_preprocessor: if not skipping_cn_preprocessor:
cn_img = extras.face_crop.crop_image(cn_img) cn_img = extras.face_crop.crop_image(cn_img)
# https://github.com/tencent-ailab/IP-Adapter/blob/d580c50a291566bbf9fc7ac0f760506607297e6d/README.md?plain=1#L75 # https://github.com/tencent-ailab/IP-Adapter/blob/d580c50a291566bbf9fc7ac0f760506607297e6d/README.md?plain=1#L75
cn_img = resize_image(cn_img, width=224, height=224, resize_mode=0) cn_img = resize_image(cn_img, width=224, height=224, resize_mode=0)
task[0] = ip_adapter.preprocess(cn_img, ip_adapter_path=ip_adapter_face_path) task[0] = ip_adapter.preprocess(cn_img, ip_adapter_path=ip_adapter_face_path)
if advanced_parameters.debugging_cn_preprocessor: if debugging_cn_preprocessor:
yield_result(async_task, cn_img, do_not_show_finished_images=True) yield_result(async_task, cn_img, do_not_show_finished_images=True)
return return
@@ -685,14 +746,14 @@ def worker():
if len(all_ip_tasks) > 0: if len(all_ip_tasks) > 0:
pipeline.final_unet = ip_adapter.patch_model(pipeline.final_unet, all_ip_tasks) pipeline.final_unet = ip_adapter.patch_model(pipeline.final_unet, all_ip_tasks)
if advanced_parameters.freeu_enabled: if freeu_enabled:
print(f'FreeU is enabled!') print(f'FreeU is enabled!')
pipeline.final_unet = core.apply_freeu( pipeline.final_unet = core.apply_freeu(
pipeline.final_unet, pipeline.final_unet,
advanced_parameters.freeu_b1, freeu_b1,
advanced_parameters.freeu_b2, freeu_b2,
advanced_parameters.freeu_s1, freeu_s1,
advanced_parameters.freeu_s2 freeu_s2
) )
all_steps = steps * image_number all_steps = steps * image_number
@@ -732,13 +793,14 @@ def worker():
done_steps = current_task_id * steps + step done_steps = current_task_id * steps + step
async_task.yields.append(['preview', ( async_task.yields.append(['preview', (
int(15.0 + 85.0 * float(done_steps) / float(all_steps)), int(15.0 + 85.0 * float(done_steps) / float(all_steps)),
f'Step {step}/{total_steps} in the {current_task_id + 1}-th Sampling', f'Step {step}/{total_steps} in the {current_task_id + 1}{ordinal_suffix(current_task_id + 1)} Sampling', y)])
y)])
for current_task_id, task in enumerate(tasks): for current_task_id, task in enumerate(tasks):
execution_start_time = time.perf_counter() execution_start_time = time.perf_counter()
try: try:
if async_task.last_stop is not False:
ldm_patched.modules.model_management.interrupt_current_processing()
positive_cond, negative_cond = task['c'], task['uc'] positive_cond, negative_cond = task['c'], task['uc']
if 'cn' in goals: if 'cn' in goals:
@@ -766,7 +828,8 @@ def worker():
denoise=denoising_strength, denoise=denoising_strength,
tiled=tiled, tiled=tiled,
cfg_scale=cfg_scale, cfg_scale=cfg_scale,
refiner_swap_method=refiner_swap_method refiner_swap_method=refiner_swap_method,
disable_preview=disable_preview
) )
del task['c'], task['uc'], positive_cond, negative_cond # Save memory del task['c'], task['uc'], positive_cond, negative_cond # Save memory
@@ -774,37 +837,62 @@ def worker():
if inpaint_worker.current_task is not None: if inpaint_worker.current_task is not None:
imgs = [inpaint_worker.current_task.post_process(x) for x in imgs] imgs = [inpaint_worker.current_task.post_process(x) for x in imgs]
img_paths = []
for x in imgs: for x in imgs:
d = [ d = [('Prompt', 'prompt', task['log_positive_prompt']),
('Prompt', task['log_positive_prompt']), ('Negative Prompt', 'negative_prompt', task['log_negative_prompt']),
('Negative Prompt', task['log_negative_prompt']), ('Fooocus V2 Expansion', 'prompt_expansion', task['expansion']),
('Fooocus V2 Expansion', task['expansion']), ('Styles', 'styles', str(raw_style_selections)),
('Styles', str(raw_style_selections)), ('Performance', 'performance', performance_selection.value)]
('Performance', performance_selection),
('Resolution', str((width, height))), if performance_selection.steps() != steps:
('Sharpness', sharpness), d.append(('Steps', 'steps', steps))
('Guidance Scale', guidance_scale),
('ADM Guidance', str(( d += [('Resolution', 'resolution', str((width, height))),
modules.patch.positive_adm_scale, ('Guidance Scale', 'guidance_scale', guidance_scale),
modules.patch.negative_adm_scale, ('Sharpness', 'sharpness', sharpness),
modules.patch.adm_scaler_end))), ('ADM Guidance', 'adm_guidance', str((
('Base Model', base_model_name), modules.patch.patch_settings[pid].positive_adm_scale,
('Refiner Model', refiner_model_name), modules.patch.patch_settings[pid].negative_adm_scale,
('Refiner Switch', refiner_switch), modules.patch.patch_settings[pid].adm_scaler_end))),
('Sampler', sampler_name), ('Base Model', 'base_model', base_model_name),
('Scheduler', scheduler_name), ('Refiner Model', 'refiner_model', refiner_model_name),
('Seed', task['task_seed']), ('Refiner Switch', 'refiner_switch', refiner_switch)]
]
if refiner_model_name != 'None':
if overwrite_switch > 0:
d.append(('Overwrite Switch', 'overwrite_switch', overwrite_switch))
if refiner_swap_method != flags.refiner_swap_method:
d.append(('Refiner Swap Method', 'refiner_swap_method', refiner_swap_method))
if modules.patch.patch_settings[pid].adaptive_cfg != modules.config.default_cfg_tsnr:
d.append(('CFG Mimicking from TSNR', 'adaptive_cfg', modules.patch.patch_settings[pid].adaptive_cfg))
d.append(('Sampler', 'sampler', sampler_name))
d.append(('Scheduler', 'scheduler', scheduler_name))
d.append(('Seed', 'seed', str(task['task_seed'])))
if freeu_enabled:
d.append(('FreeU', 'freeu', str((freeu_b1, freeu_b2, freeu_s1, freeu_s2))))
for li, (n, w) in enumerate(loras): for li, (n, w) in enumerate(loras):
if n != 'None': if n != 'None':
d.append((f'LoRA {li + 1}', f'{n} : {w}')) d.append((f'LoRA {li + 1}', f'lora_combined_{li + 1}', f'{n} : {w}'))
d.append(('Version', 'v' + fooocus_version.version))
log(x, d)
yield_result(async_task, imgs, do_not_show_finished_images=len(tasks) == 1) metadata_parser = None
if save_metadata_to_images:
metadata_parser = modules.meta_parser.get_metadata_parser(metadata_scheme)
metadata_parser.set_data(task['log_positive_prompt'], task['positive'],
task['log_negative_prompt'], task['negative'],
steps, base_model_name, refiner_model_name, loras)
d.append(('Metadata Scheme', 'metadata_scheme', metadata_scheme.value if save_metadata_to_images else save_metadata_to_images))
d.append(('Version', 'version', 'Fooocus v' + fooocus_version.version))
img_paths.append(log(x, d, metadata_parser, output_format))
yield_result(async_task, img_paths, do_not_show_finished_images=len(tasks) == 1 or disable_intermediate_results)
except ldm_patched.modules.model_management.InterruptProcessingException as e: except ldm_patched.modules.model_management.InterruptProcessingException as e:
if shared.last_stop == 'skip': if async_task.last_stop == 'skip':
print('User skipped') print('User skipped')
async_task.last_stop = False
continue continue
else: else:
print('User stopped') print('User stopped')
@@ -812,21 +900,27 @@ def worker():
execution_time = time.perf_counter() - execution_start_time execution_time = time.perf_counter() - execution_start_time
print(f'Generating and saving time: {execution_time:.2f} seconds') print(f'Generating and saving time: {execution_time:.2f} seconds')
async_task.processing = False
return return
while True: while True:
time.sleep(0.01) time.sleep(0.01)
if len(async_tasks) > 0: if len(async_tasks) > 0:
task = async_tasks.pop(0) task = async_tasks.pop(0)
generate_image_grid = task.args.pop(0)
try: try:
handler(task) handler(task)
if generate_image_grid:
build_image_wall(task) build_image_wall(task)
task.yields.append(['finish', task.results]) task.yields.append(['finish', task.results])
pipeline.prepare_text_encoder(async_call=True) pipeline.prepare_text_encoder(async_call=True)
except: except:
traceback.print_exc() traceback.print_exc()
task.yields.append(['finish', task.results]) task.yields.append(['finish', task.results])
finally:
if pid in modules.patch.patch_settings:
del modules.patch.patch_settings[pid]
pass pass
+228 -61
View File
@@ -3,23 +3,41 @@ import json
import math import math
import numbers import numbers
import args_manager import args_manager
import tempfile
import modules.flags import modules.flags
import modules.sdxl_styles import modules.sdxl_styles
from modules.model_loader import load_file_from_url from modules.model_loader import load_file_from_url
from modules.util import get_files_from_folder from modules.util import get_files_from_folder, makedirs_with_log
from modules.flags import OutputFormat, Performance, MetadataScheme
config_path = os.path.abspath("./config.txt") def get_config_path(key, default_value):
config_example_path = os.path.abspath("config_modification_tutorial.txt") env = os.getenv(key)
if env is not None and isinstance(env, str):
print(f"Environment: {key} = {env}")
return env
else:
return os.path.abspath(default_value)
config_path = get_config_path('config_path', "./config.txt")
config_example_path = get_config_path('config_example_path', "config_modification_tutorial.txt")
config_dict = {} config_dict = {}
always_save_keys = [] always_save_keys = []
visited_keys = [] visited_keys = []
try:
with open(os.path.abspath(f'./presets/default.json'), "r", encoding="utf-8") as json_file:
config_dict.update(json.load(json_file))
except Exception as e:
print(f'Load default preset failed.')
print(e)
try: try:
if os.path.exists(config_path): if os.path.exists(config_path):
with open(config_path, "r", encoding="utf-8") as json_file: with open(config_path, "r", encoding="utf-8") as json_file:
config_dict = json.load(json_file) config_dict.update(json.load(json_file))
always_save_keys = list(config_dict.keys()) always_save_keys = list(config_dict.keys())
except Exception as e: except Exception as e:
print(f'Failed to load config file "{config_path}" . The reason is: {str(e)}') print(f'Failed to load config file "{config_path}" . The reason is: {str(e)}')
@@ -79,6 +97,34 @@ def try_load_deprecated_user_path_config():
try_load_deprecated_user_path_config() try_load_deprecated_user_path_config()
def get_presets():
preset_folder = 'presets'
presets = ['initial']
if not os.path.exists(preset_folder):
print('No presets found.')
return presets
return presets + [f[:f.index('.json')] for f in os.listdir(preset_folder) if f.endswith('.json')]
def try_get_preset_content(preset):
if isinstance(preset, str):
preset_path = os.path.abspath(f'./presets/{preset}.json')
try:
if os.path.exists(preset_path):
with open(preset_path, "r", encoding="utf-8") as json_file:
json_content = json.load(json_file)
print(f'Loaded preset: {preset_path}')
return json_content
else:
raise FileNotFoundError
except Exception as e:
print(f'Load preset [{preset_path}] failed')
print(e)
return {}
try: try:
with open(os.path.abspath(f'./presets/default.json'), "r", encoding="utf-8") as json_file: with open(os.path.abspath(f'./presets/default.json'), "r", encoding="utf-8") as json_file:
config_dict.update(json.load(json_file)) config_dict.update(json.load(json_file))
@@ -86,23 +132,23 @@ except Exception as e:
print(f'Load default preset failed.') print(f'Load default preset failed.')
print(e) print(e)
available_presets = get_presets()
preset = args_manager.args.preset preset = args_manager.args.preset
config_dict.update(try_get_preset_content(preset))
if isinstance(preset, str): def get_path_output() -> str:
preset_path = os.path.abspath(f'./presets/{preset}.json') """
try: Checking output path argument and overriding default path.
if os.path.exists(preset_path): """
with open(preset_path, "r", encoding="utf-8") as json_file: global config_dict
config_dict.update(json.load(json_file)) path_output = get_dir_or_set_default('path_outputs', '../outputs/', make_directory=True)
print(f'Loaded preset: {preset_path}') if args_manager.args.output_path:
else: print(f'Overriding config value path_outputs with {args_manager.args.output_path}')
raise FileNotFoundError config_dict['path_outputs'] = path_output = args_manager.args.output_path
except Exception as e: return path_output
print(f'Load preset [{preset_path}] failed')
print(e)
def get_dir_or_set_default(key, default_value): def get_dir_or_set_default(key, default_value, as_array=False, make_directory=False):
global config_dict, visited_keys, always_save_keys global config_dict, visited_keys, always_save_keys
if key not in visited_keys: if key not in visited_keys:
@@ -111,20 +157,44 @@ def get_dir_or_set_default(key, default_value):
if key not in always_save_keys: if key not in always_save_keys:
always_save_keys.append(key) always_save_keys.append(key)
v = config_dict.get(key, None) v = os.getenv(key)
if isinstance(v, str) and os.path.exists(v) and os.path.isdir(v): if v is not None:
return v print(f"Environment: {key} = {v}")
config_dict[key] = v
else: else:
v = config_dict.get(key, None)
if isinstance(v, str):
if make_directory:
makedirs_with_log(v)
if os.path.exists(v) and os.path.isdir(v):
return v if not as_array else [v]
elif isinstance(v, list):
if make_directory:
for d in v:
makedirs_with_log(d)
if all([os.path.exists(d) and os.path.isdir(d) for d in v]):
return v
if v is not None: if v is not None:
print(f'Failed to load config key: {json.dumps({key:v})} is invalid or does not exist; will use {json.dumps({key:default_value})} instead.') print(f'Failed to load config key: {json.dumps({key:v})} is invalid or does not exist; will use {json.dumps({key:default_value})} instead.')
if isinstance(default_value, list):
dp = []
for path in default_value:
abs_path = os.path.abspath(os.path.join(os.path.dirname(__file__), path))
dp.append(abs_path)
os.makedirs(abs_path, exist_ok=True)
else:
dp = os.path.abspath(os.path.join(os.path.dirname(__file__), default_value)) dp = os.path.abspath(os.path.join(os.path.dirname(__file__), default_value))
os.makedirs(dp, exist_ok=True) os.makedirs(dp, exist_ok=True)
if as_array:
dp = [dp]
config_dict[key] = dp config_dict[key] = dp
return dp return dp
path_checkpoints = get_dir_or_set_default('path_checkpoints', '../models/checkpoints/') paths_checkpoints = get_dir_or_set_default('path_checkpoints', ['../models/checkpoints/'], True)
path_loras = get_dir_or_set_default('path_loras', '../models/loras/') paths_loras = get_dir_or_set_default('path_loras', ['../models/loras/'], True)
path_embeddings = get_dir_or_set_default('path_embeddings', '../models/embeddings/') path_embeddings = get_dir_or_set_default('path_embeddings', '../models/embeddings/')
path_vae_approx = get_dir_or_set_default('path_vae_approx', '../models/vae_approx/') path_vae_approx = get_dir_or_set_default('path_vae_approx', '../models/vae_approx/')
path_upscale_models = get_dir_or_set_default('path_upscale_models', '../models/upscale_models/') path_upscale_models = get_dir_or_set_default('path_upscale_models', '../models/upscale_models/')
@@ -132,7 +202,8 @@ path_inpaint = get_dir_or_set_default('path_inpaint', '../models/inpaint/')
path_controlnet = get_dir_or_set_default('path_controlnet', '../models/controlnet/') path_controlnet = get_dir_or_set_default('path_controlnet', '../models/controlnet/')
path_clip_vision = get_dir_or_set_default('path_clip_vision', '../models/clip_vision/') path_clip_vision = get_dir_or_set_default('path_clip_vision', '../models/clip_vision/')
path_fooocus_expansion = get_dir_or_set_default('path_fooocus_expansion', '../models/prompt_expansion/fooocus_expansion') path_fooocus_expansion = get_dir_or_set_default('path_fooocus_expansion', '../models/prompt_expansion/fooocus_expansion')
path_outputs = get_dir_or_set_default('path_outputs', '../outputs/') path_wildcards = get_dir_or_set_default('path_wildcards', '../wildcards/')
path_outputs = get_path_output()
def get_config_item_or_set_default(key, default_value, validator, disable_empty_as_none=False): def get_config_item_or_set_default(key, default_value, validator, disable_empty_as_none=False):
@@ -141,6 +212,11 @@ def get_config_item_or_set_default(key, default_value, validator, disable_empty_
if key not in visited_keys: if key not in visited_keys:
visited_keys.append(key) visited_keys.append(key)
v = os.getenv(key)
if v is not None:
print(f"Environment: {key} = {v}")
config_dict[key] = v
if key not in config_dict: if key not in config_dict:
config_dict[key] = default_value config_dict[key] = default_value
return default_value return default_value
@@ -158,7 +234,37 @@ def get_config_item_or_set_default(key, default_value, validator, disable_empty_
return default_value return default_value
default_base_model_name = get_config_item_or_set_default( def init_temp_path(path: str | None, default_path: str) -> str:
if args_manager.args.temp_path:
path = args_manager.args.temp_path
if path != '' and path != default_path:
try:
if not os.path.isabs(path):
path = os.path.abspath(path)
os.makedirs(path, exist_ok=True)
print(f'Using temp path {path}')
return path
except Exception as e:
print(f'Could not create temp path {path}. Reason: {e}')
print(f'Using default temp path {default_path} instead.')
os.makedirs(default_path, exist_ok=True)
return default_path
default_temp_path = os.path.join(tempfile.gettempdir(), 'fooocus')
temp_path = init_temp_path(get_config_item_or_set_default(
key='temp_path',
default_value=default_temp_path,
validator=lambda x: isinstance(x, str),
), default_temp_path)
temp_path_cleanup_on_launch = get_config_item_or_set_default(
key='temp_path_cleanup_on_launch',
default_value=True,
validator=lambda x: isinstance(x, bool)
)
default_base_model_name = default_model = get_config_item_or_set_default(
key='default_model', key='default_model',
default_value='model.safetensors', default_value='model.safetensors',
validator=lambda x: isinstance(x, str) validator=lambda x: isinstance(x, str)
@@ -168,7 +274,7 @@ previous_default_models = get_config_item_or_set_default(
default_value=[], default_value=[],
validator=lambda x: isinstance(x, list) and all(isinstance(k, str) for k in x) validator=lambda x: isinstance(x, list) and all(isinstance(k, str) for k in x)
) )
default_refiner_model_name = get_config_item_or_set_default( default_refiner_model_name = default_refiner = get_config_item_or_set_default(
key='default_refiner', key='default_refiner',
default_value='None', default_value='None',
validator=lambda x: isinstance(x, str) validator=lambda x: isinstance(x, str)
@@ -178,31 +284,51 @@ default_refiner_switch = get_config_item_or_set_default(
default_value=0.8, default_value=0.8,
validator=lambda x: isinstance(x, numbers.Number) and 0 <= x <= 1 validator=lambda x: isinstance(x, numbers.Number) and 0 <= x <= 1
) )
default_loras_min_weight = get_config_item_or_set_default(
key='default_loras_min_weight',
default_value=-2,
validator=lambda x: isinstance(x, numbers.Number) and -10 <= x <= 10
)
default_loras_max_weight = get_config_item_or_set_default(
key='default_loras_max_weight',
default_value=2,
validator=lambda x: isinstance(x, numbers.Number) and -10 <= x <= 10
)
default_loras = get_config_item_or_set_default( default_loras = get_config_item_or_set_default(
key='default_loras', key='default_loras',
default_value=[ default_value=[
[ [
True,
"None", "None",
1.0 1.0
], ],
[ [
True,
"None", "None",
1.0 1.0
], ],
[ [
True,
"None", "None",
1.0 1.0
], ],
[ [
True,
"None", "None",
1.0 1.0
], ],
[ [
True,
"None", "None",
1.0 1.0
] ]
], ],
validator=lambda x: isinstance(x, list) and all(len(y) == 2 and isinstance(y[0], str) and isinstance(y[1], numbers.Number) for y in x) validator=lambda x: isinstance(x, list) and all(len(y) == 3 and isinstance(y[0], bool) and isinstance(y[1], str) and isinstance(y[2], numbers.Number) for y in x)
)
default_max_lora_number = get_config_item_or_set_default(
key='default_max_lora_number',
default_value=len(default_loras) if isinstance(default_loras, list) and len(default_loras) > 0 else 5,
validator=lambda x: isinstance(x, int) and x >= 1
) )
default_cfg_scale = get_config_item_or_set_default( default_cfg_scale = get_config_item_or_set_default(
key='default_cfg_scale', key='default_cfg_scale',
@@ -247,8 +373,8 @@ default_prompt = get_config_item_or_set_default(
) )
default_performance = get_config_item_or_set_default( default_performance = get_config_item_or_set_default(
key='default_performance', key='default_performance',
default_value='Speed', default_value=Performance.SPEED.value,
validator=lambda x: x in modules.flags.performance_selections validator=lambda x: x in Performance.list()
) )
default_advanced_checkbox = get_config_item_or_set_default( default_advanced_checkbox = get_config_item_or_set_default(
key='default_advanced_checkbox', key='default_advanced_checkbox',
@@ -260,6 +386,11 @@ default_max_image_number = get_config_item_or_set_default(
default_value=32, default_value=32,
validator=lambda x: isinstance(x, int) and x >= 1 validator=lambda x: isinstance(x, int) and x >= 1
) )
default_output_format = get_config_item_or_set_default(
key='default_output_format',
default_value='png',
validator=lambda x: x in OutputFormat.list()
)
default_image_number = get_config_item_or_set_default( default_image_number = get_config_item_or_set_default(
key='default_image_number', key='default_image_number',
default_value=2, default_value=2,
@@ -323,30 +454,50 @@ example_inpaint_prompts = get_config_item_or_set_default(
], ],
validator=lambda x: isinstance(x, list) and all(isinstance(v, str) for v in x) validator=lambda x: isinstance(x, list) and all(isinstance(v, str) for v in x)
) )
default_save_metadata_to_images = get_config_item_or_set_default(
key='default_save_metadata_to_images',
default_value=False,
validator=lambda x: isinstance(x, bool)
)
default_metadata_scheme = get_config_item_or_set_default(
key='default_metadata_scheme',
default_value=MetadataScheme.FOOOCUS.value,
validator=lambda x: x in [y[1] for y in modules.flags.metadata_scheme if y[1] == x]
)
metadata_created_by = get_config_item_or_set_default(
key='metadata_created_by',
default_value='',
validator=lambda x: isinstance(x, str)
)
example_inpaint_prompts = [[x] for x in example_inpaint_prompts] example_inpaint_prompts = [[x] for x in example_inpaint_prompts]
config_dict["default_loras"] = default_loras = default_loras[:5] + [['None', 1.0] for _ in range(5 - len(default_loras))] config_dict["default_loras"] = default_loras = default_loras[:default_max_lora_number] + [['None', 1.0] for _ in range(default_max_lora_number - len(default_loras))]
possible_preset_keys = [
"default_model",
"default_refiner",
"default_refiner_switch",
"default_loras",
"default_cfg_scale",
"default_sample_sharpness",
"default_sampler",
"default_scheduler",
"default_performance",
"default_prompt",
"default_prompt_negative",
"default_styles",
"default_aspect_ratio",
"checkpoint_downloads",
"embeddings_downloads",
"lora_downloads",
]
# mapping config to meta parameter
possible_preset_keys = {
"default_model": "base_model",
"default_refiner": "refiner_model",
"default_refiner_switch": "refiner_switch",
"previous_default_models": "previous_default_models",
"default_loras_min_weight": "default_loras_min_weight",
"default_loras_max_weight": "default_loras_max_weight",
"default_loras": "<processed>",
"default_cfg_scale": "guidance_scale",
"default_sample_sharpness": "sharpness",
"default_sampler": "sampler",
"default_scheduler": "scheduler",
"default_overwrite_step": "steps",
"default_performance": "performance",
"default_prompt": "prompt",
"default_prompt_negative": "negative_prompt",
"default_styles": "styles",
"default_aspect_ratio": "resolution",
"default_save_metadata_to_images": "default_save_metadata_to_images",
"checkpoint_downloads": "checkpoint_downloads",
"embeddings_downloads": "embeddings_downloads",
"lora_downloads": "lora_downloads"
}
REWRITE_PRESET = False REWRITE_PRESET = False
@@ -385,21 +536,29 @@ with open(config_example_path, "w", encoding="utf-8") as json_file:
'and there is no "," before the last "}". \n\n\n') 'and there is no "," before the last "}". \n\n\n')
json.dump({k: config_dict[k] for k in visited_keys}, json_file, indent=4) json.dump({k: config_dict[k] for k in visited_keys}, json_file, indent=4)
os.makedirs(path_outputs, exist_ok=True)
model_filenames = [] model_filenames = []
lora_filenames = [] lora_filenames = []
wildcard_filenames = []
sdxl_lcm_lora = 'sdxl_lcm_lora.safetensors'
sdxl_lightning_lora = 'sdxl_lightning_4step_lora.safetensors'
def get_model_filenames(folder_path, name_filter=None): def get_model_filenames(folder_paths, extensions=None, name_filter=None):
return get_files_from_folder(folder_path, ['.pth', '.ckpt', '.bin', '.safetensors', '.fooocus.patch'], name_filter) if extensions is None:
extensions = ['.pth', '.ckpt', '.bin', '.safetensors', '.fooocus.patch']
files = []
for folder in folder_paths:
files += get_files_from_folder(folder, extensions, name_filter)
return files
def update_all_model_names(): def update_files():
global model_filenames, lora_filenames global model_filenames, lora_filenames, wildcard_filenames, available_presets
model_filenames = get_model_filenames(path_checkpoints) model_filenames = get_model_filenames(paths_checkpoints)
lora_filenames = get_model_filenames(path_loras) lora_filenames = get_model_filenames(paths_loras)
wildcard_filenames = get_files_from_folder(path_wildcards, ['.txt'])
available_presets = get_presets()
return return
@@ -444,10 +603,18 @@ def downloading_inpaint_models(v):
def downloading_sdxl_lcm_lora(): def downloading_sdxl_lcm_lora():
load_file_from_url( load_file_from_url(
url='https://huggingface.co/lllyasviel/misc/resolve/main/sdxl_lcm_lora.safetensors', url='https://huggingface.co/lllyasviel/misc/resolve/main/sdxl_lcm_lora.safetensors',
model_dir=path_loras, model_dir=paths_loras[0],
file_name='sdxl_lcm_lora.safetensors' file_name=sdxl_lcm_lora
) )
return 'sdxl_lcm_lora.safetensors' return sdxl_lcm_lora
def downloading_sdxl_lightning_lora():
load_file_from_url(
url='https://huggingface.co/ByteDance/SDXL-Lightning/resolve/main/sdxl_lightning_4step_lora.safetensors',
model_dir=paths_loras[0],
file_name=sdxl_lightning_lora
)
return sdxl_lightning_lora
def downloading_controlnet_canny(): def downloading_controlnet_canny():
@@ -515,4 +682,4 @@ def downloading_upscale_model():
return os.path.join(path_upscale_models, 'fooocus_upscaler_s409985e5.bin') return os.path.join(path_upscale_models, 'fooocus_upscaler_s409985e5.bin')
update_all_model_names() update_files()
+8 -13
View File
@@ -1,8 +1,3 @@
from modules.patch import patch_all
patch_all()
import os import os
import einops import einops
import torch import torch
@@ -16,7 +11,6 @@ import ldm_patched.modules.controlnet
import modules.sample_hijack import modules.sample_hijack
import ldm_patched.modules.samplers import ldm_patched.modules.samplers
import ldm_patched.modules.latent_formats import ldm_patched.modules.latent_formats
import modules.advanced_parameters
from ldm_patched.modules.sd import load_checkpoint_guess_config from ldm_patched.modules.sd import load_checkpoint_guess_config
from ldm_patched.contrib.external import VAEDecode, EmptyLatentImage, VAEEncode, VAEEncodeTiled, VAEDecodeTiled, \ from ldm_patched.contrib.external import VAEDecode, EmptyLatentImage, VAEEncode, VAEEncodeTiled, VAEDecodeTiled, \
@@ -24,6 +18,7 @@ from ldm_patched.contrib.external import VAEDecode, EmptyLatentImage, VAEEncode,
from ldm_patched.contrib.external_freelunch import FreeU_V2 from ldm_patched.contrib.external_freelunch import FreeU_V2
from ldm_patched.modules.sample import prepare_mask from ldm_patched.modules.sample import prepare_mask
from modules.lora import match_lora from modules.lora import match_lora
from modules.util import get_file_from_folder_list
from ldm_patched.modules.lora import model_lora_keys_unet, model_lora_keys_clip from ldm_patched.modules.lora import model_lora_keys_unet, model_lora_keys_clip
from modules.config import path_embeddings from modules.config import path_embeddings
from ldm_patched.contrib.external_model_advanced import ModelSamplingDiscrete from ldm_patched.contrib.external_model_advanced import ModelSamplingDiscrete
@@ -78,14 +73,14 @@ class StableDiffusionModel:
loras_to_load = [] loras_to_load = []
for name, weight in loras: for filename, weight in loras:
if name == 'None': if filename == 'None':
continue continue
if os.path.exists(name): if os.path.exists(filename):
lora_filename = name lora_filename = filename
else: else:
lora_filename = os.path.join(modules.config.path_loras, name) lora_filename = get_file_from_folder_list(filename, modules.config.paths_loras)
if not os.path.exists(lora_filename): if not os.path.exists(lora_filename):
print(f'Lora file not found: {lora_filename}') print(f'Lora file not found: {lora_filename}')
@@ -268,7 +263,7 @@ def get_previewer(model):
def ksampler(model, positive, negative, latent, seed=None, steps=30, cfg=7.0, sampler_name='dpmpp_2m_sde_gpu', def ksampler(model, positive, negative, latent, seed=None, steps=30, cfg=7.0, sampler_name='dpmpp_2m_sde_gpu',
scheduler='karras', denoise=1.0, disable_noise=False, start_step=None, last_step=None, scheduler='karras', denoise=1.0, disable_noise=False, start_step=None, last_step=None,
force_full_denoise=False, callback_function=None, refiner=None, refiner_switch=-1, force_full_denoise=False, callback_function=None, refiner=None, refiner_switch=-1,
previewer_start=None, previewer_end=None, sigmas=None, noise_mean=None): previewer_start=None, previewer_end=None, sigmas=None, noise_mean=None, disable_preview=False):
if sigmas is not None: if sigmas is not None:
sigmas = sigmas.clone().to(ldm_patched.modules.model_management.get_torch_device()) sigmas = sigmas.clone().to(ldm_patched.modules.model_management.get_torch_device())
@@ -299,7 +294,7 @@ def ksampler(model, positive, negative, latent, seed=None, steps=30, cfg=7.0, sa
def callback(step, x0, x, total_steps): def callback(step, x0, x, total_steps):
ldm_patched.modules.model_management.throw_exception_if_processing_interrupted() ldm_patched.modules.model_management.throw_exception_if_processing_interrupted()
y = None y = None
if previewer is not None and not modules.advanced_parameters.disable_preview: if previewer is not None and not disable_preview:
y = previewer(x0, previewer_start + step, previewer_end) y = previewer(x0, previewer_start + step, previewer_end)
if callback_function is not None: if callback_function is not None:
callback_function(previewer_start + step, x0, x, previewer_end, y) callback_function(previewer_start + step, x0, x, previewer_end, y)
+15 -9
View File
@@ -11,6 +11,7 @@ from extras.expansion import FooocusExpansion
from ldm_patched.modules.model_base import SDXL, SDXLRefiner from ldm_patched.modules.model_base import SDXL, SDXLRefiner
from modules.sample_hijack import clip_separate from modules.sample_hijack import clip_separate
from modules.util import get_file_from_folder_list, get_enabled_loras
model_base = core.StableDiffusionModel() model_base = core.StableDiffusionModel()
@@ -60,7 +61,7 @@ def assert_model_integrity():
def refresh_base_model(name): def refresh_base_model(name):
global model_base global model_base
filename = os.path.abspath(os.path.realpath(os.path.join(modules.config.path_checkpoints, name))) filename = get_file_from_folder_list(name, modules.config.paths_checkpoints)
if model_base.filename == filename: if model_base.filename == filename:
return return
@@ -76,7 +77,7 @@ def refresh_base_model(name):
def refresh_refiner_model(name): def refresh_refiner_model(name):
global model_refiner global model_refiner
filename = os.path.abspath(os.path.realpath(os.path.join(modules.config.path_checkpoints, name))) filename = get_file_from_folder_list(name, modules.config.paths_checkpoints)
if model_refiner.filename == filename: if model_refiner.filename == filename:
return return
@@ -253,7 +254,7 @@ def refresh_everything(refiner_model_name, base_model_name, loras,
refresh_everything( refresh_everything(
refiner_model_name=modules.config.default_refiner_model_name, refiner_model_name=modules.config.default_refiner_model_name,
base_model_name=modules.config.default_base_model_name, base_model_name=modules.config.default_base_model_name,
loras=modules.config.default_loras loras=get_enabled_loras(modules.config.default_loras)
) )
@@ -315,7 +316,7 @@ def get_candidate_vae(steps, switch, denoise=1.0, refiner_swap_method='joint'):
@torch.no_grad() @torch.no_grad()
@torch.inference_mode() @torch.inference_mode()
def process_diffusion(positive_cond, negative_cond, steps, switch, width, height, image_seed, callback, sampler_name, scheduler_name, latent=None, denoise=1.0, tiled=False, cfg_scale=7.0, refiner_swap_method='joint'): def process_diffusion(positive_cond, negative_cond, steps, switch, width, height, image_seed, callback, sampler_name, scheduler_name, latent=None, denoise=1.0, tiled=False, cfg_scale=7.0, refiner_swap_method='joint', disable_preview=False):
target_unet, target_vae, target_refiner_unet, target_refiner_vae, target_clip \ target_unet, target_vae, target_refiner_unet, target_refiner_vae, target_clip \
= final_unet, final_vae, final_refiner_unet, final_refiner_vae, final_clip = final_unet, final_vae, final_refiner_unet, final_refiner_vae, final_clip
@@ -374,6 +375,7 @@ def process_diffusion(positive_cond, negative_cond, steps, switch, width, height
refiner_switch=switch, refiner_switch=switch,
previewer_start=0, previewer_start=0,
previewer_end=steps, previewer_end=steps,
disable_preview=disable_preview
) )
decoded_latent = core.decode_vae(vae=target_vae, latent_image=sampled_latent, tiled=tiled) decoded_latent = core.decode_vae(vae=target_vae, latent_image=sampled_latent, tiled=tiled)
@@ -392,6 +394,7 @@ def process_diffusion(positive_cond, negative_cond, steps, switch, width, height
scheduler=scheduler_name, scheduler=scheduler_name,
previewer_start=0, previewer_start=0,
previewer_end=steps, previewer_end=steps,
disable_preview=disable_preview
) )
print('Refiner swapped by changing ksampler. Noise preserved.') print('Refiner swapped by changing ksampler. Noise preserved.')
@@ -414,6 +417,7 @@ def process_diffusion(positive_cond, negative_cond, steps, switch, width, height
scheduler=scheduler_name, scheduler=scheduler_name,
previewer_start=switch, previewer_start=switch,
previewer_end=steps, previewer_end=steps,
disable_preview=disable_preview
) )
target_model = target_refiner_vae target_model = target_refiner_vae
@@ -422,7 +426,7 @@ def process_diffusion(positive_cond, negative_cond, steps, switch, width, height
decoded_latent = core.decode_vae(vae=target_model, latent_image=sampled_latent, tiled=tiled) decoded_latent = core.decode_vae(vae=target_model, latent_image=sampled_latent, tiled=tiled)
if refiner_swap_method == 'vae': if refiner_swap_method == 'vae':
modules.patch.eps_record = 'vae' modules.patch.patch_settings[os.getpid()].eps_record = 'vae'
if modules.inpaint_worker.current_task is not None: if modules.inpaint_worker.current_task is not None:
modules.inpaint_worker.current_task.unswap() modules.inpaint_worker.current_task.unswap()
@@ -440,7 +444,8 @@ def process_diffusion(positive_cond, negative_cond, steps, switch, width, height
sampler_name=sampler_name, sampler_name=sampler_name,
scheduler=scheduler_name, scheduler=scheduler_name,
previewer_start=0, previewer_start=0,
previewer_end=steps previewer_end=steps,
disable_preview=disable_preview
) )
print('Fooocus VAE-based swap.') print('Fooocus VAE-based swap.')
@@ -459,7 +464,7 @@ def process_diffusion(positive_cond, negative_cond, steps, switch, width, height
denoise=denoise)[switch:] * k_sigmas denoise=denoise)[switch:] * k_sigmas
len_sigmas = len(sigmas) - 1 len_sigmas = len(sigmas) - 1
noise_mean = torch.mean(modules.patch.eps_record, dim=1, keepdim=True) noise_mean = torch.mean(modules.patch.patch_settings[os.getpid()].eps_record, dim=1, keepdim=True)
if modules.inpaint_worker.current_task is not None: if modules.inpaint_worker.current_task is not None:
modules.inpaint_worker.current_task.swap() modules.inpaint_worker.current_task.swap()
@@ -479,7 +484,8 @@ def process_diffusion(positive_cond, negative_cond, steps, switch, width, height
previewer_start=switch, previewer_start=switch,
previewer_end=steps, previewer_end=steps,
sigmas=sigmas, sigmas=sigmas,
noise_mean=noise_mean noise_mean=noise_mean,
disable_preview=disable_preview
) )
target_model = target_refiner_vae target_model = target_refiner_vae
@@ -488,5 +494,5 @@ def process_diffusion(positive_cond, negative_cond, steps, switch, width, height
decoded_latent = core.decode_vae(vae=target_model, latent_image=sampled_latent, tiled=tiled) decoded_latent = core.decode_vae(vae=target_model, latent_image=sampled_latent, tiled=tiled)
images = core.pytorch_to_numpy(decoded_latent) images = core.pytorch_to_numpy(decoded_latent)
modules.patch.eps_record = None modules.patch.patch_settings[os.getpid()].eps_record = None
return images return images
+101 -6
View File
@@ -1,3 +1,5 @@
from enum import IntEnum, Enum
disabled = 'Disabled' disabled = 'Disabled'
enabled = 'Enabled' enabled = 'Enabled'
subtle_variation = 'Vary (Subtle)' subtle_variation = 'Vary (Subtle)'
@@ -10,16 +12,49 @@ uov_list = [
disabled, subtle_variation, strong_variation, upscale_15, upscale_2, upscale_fast disabled, subtle_variation, strong_variation, upscale_15, upscale_2, upscale_fast
] ]
KSAMPLER_NAMES = ["euler", "euler_ancestral", "heun", "heunpp2","dpm_2", "dpm_2_ancestral", CIVITAI_NO_KARRAS = ["euler", "euler_ancestral", "heun", "dpm_fast", "dpm_adaptive", "ddim", "uni_pc"]
"lms", "dpm_fast", "dpm_adaptive", "dpmpp_2s_ancestral", "dpmpp_sde", "dpmpp_sde_gpu",
"dpmpp_2m", "dpmpp_2m_sde", "dpmpp_2m_sde_gpu", "dpmpp_3m_sde", "dpmpp_3m_sde_gpu", "ddpm", "lcm"] # fooocus: a1111 (Civitai)
KSAMPLER = {
"euler": "Euler",
"euler_ancestral": "Euler a",
"heun": "Heun",
"heunpp2": "",
"dpm_2": "DPM2",
"dpm_2_ancestral": "DPM2 a",
"lms": "LMS",
"dpm_fast": "DPM fast",
"dpm_adaptive": "DPM adaptive",
"dpmpp_2s_ancestral": "DPM++ 2S a",
"dpmpp_sde": "DPM++ SDE",
"dpmpp_sde_gpu": "DPM++ SDE",
"dpmpp_2m": "DPM++ 2M",
"dpmpp_2m_sde": "DPM++ 2M SDE",
"dpmpp_2m_sde_gpu": "DPM++ 2M SDE",
"dpmpp_3m_sde": "",
"dpmpp_3m_sde_gpu": "",
"ddpm": "",
"lcm": "LCM"
}
SAMPLER_EXTRA = {
"ddim": "DDIM",
"uni_pc": "UniPC",
"uni_pc_bh2": ""
}
SAMPLERS = KSAMPLER | SAMPLER_EXTRA
KSAMPLER_NAMES = list(KSAMPLER.keys())
SCHEDULER_NAMES = ["normal", "karras", "exponential", "sgm_uniform", "simple", "ddim_uniform", "lcm", "turbo"] SCHEDULER_NAMES = ["normal", "karras", "exponential", "sgm_uniform", "simple", "ddim_uniform", "lcm", "turbo"]
SAMPLER_NAMES = KSAMPLER_NAMES + ["ddim", "uni_pc", "uni_pc_bh2"] SAMPLER_NAMES = KSAMPLER_NAMES + list(SAMPLER_EXTRA.keys())
sampler_list = SAMPLER_NAMES sampler_list = SAMPLER_NAMES
scheduler_list = SCHEDULER_NAMES scheduler_list = SCHEDULER_NAMES
refiner_swap_method = 'joint'
cn_ip = "ImagePrompt" cn_ip = "ImagePrompt"
cn_ip_face = "FaceSwap" cn_ip_face = "FaceSwap"
cn_canny = "PyraCanny" cn_canny = "PyraCanny"
@@ -32,9 +67,9 @@ default_parameters = {
cn_ip: (0.5, 0.6), cn_ip_face: (0.9, 0.75), cn_canny: (0.5, 1.0), cn_cpds: (0.5, 1.0) cn_ip: (0.5, 0.6), cn_ip_face: (0.9, 0.75), cn_canny: (0.5, 1.0), cn_cpds: (0.5, 1.0)
} # stop, weight } # stop, weight
inpaint_engine_versions = ['None', 'v1', 'v2.5', 'v2.6'] output_formats = ['png', 'jpeg', 'webp']
performance_selections = ['Speed', 'Quality', 'Extreme Speed']
inpaint_engine_versions = ['None', 'v1', 'v2.5', 'v2.6']
inpaint_option_default = 'Inpaint or Outpaint (default)' inpaint_option_default = 'Inpaint or Outpaint (default)'
inpaint_option_detail = 'Improve Detail (face, hand, eyes, etc.)' inpaint_option_detail = 'Improve Detail (face, hand, eyes, etc.)'
inpaint_option_modify = 'Modify Content (add objects, change background, etc.)' inpaint_option_modify = 'Modify Content (add objects, change background, etc.)'
@@ -42,3 +77,63 @@ inpaint_options = [inpaint_option_default, inpaint_option_detail, inpaint_option
desc_type_photo = 'Photograph' desc_type_photo = 'Photograph'
desc_type_anime = 'Art/Anime' desc_type_anime = 'Art/Anime'
class MetadataScheme(Enum):
FOOOCUS = 'fooocus'
A1111 = 'a1111'
metadata_scheme = [
(f'{MetadataScheme.FOOOCUS.value} (json)', MetadataScheme.FOOOCUS.value),
(f'{MetadataScheme.A1111.value} (plain text)', MetadataScheme.A1111.value),
]
controlnet_image_count = 4
class OutputFormat(Enum):
PNG = 'png'
JPEG = 'jpeg'
WEBP = 'webp'
@classmethod
def list(cls) -> list:
return list(map(lambda c: c.value, cls))
class Steps(IntEnum):
QUALITY = 60
SPEED = 30
EXTREME_SPEED = 8
LIGHTNING = 4
class StepsUOV(IntEnum):
QUALITY = 36
SPEED = 18
EXTREME_SPEED = 8
LIGHTNING = 4
class Performance(Enum):
QUALITY = 'Quality'
SPEED = 'Speed'
EXTREME_SPEED = 'Extreme Speed'
LIGHTNING = 'Lightning'
@classmethod
def list(cls) -> list:
return list(map(lambda c: c.value, cls))
@classmethod
def has_restricted_features(cls, x) -> bool:
if isinstance(x, Performance):
x = x.value
return x in [cls.EXTREME_SPEED.value, cls.LIGHTNING.value]
def steps(self) -> int | None:
return Steps[self.name].value if Steps[self.name] else None
def steps_uov(self) -> int | None:
return StepsUOV[self.name].value if Steps[self.name] else None
+4 -1
View File
@@ -17,7 +17,7 @@ from gradio_client.documentation import document, set_documentation_group
from gradio_client.serializing import ImgSerializable from gradio_client.serializing import ImgSerializable
from PIL import Image as _Image # using _ to minimize namespace pollution from PIL import Image as _Image # using _ to minimize namespace pollution
from gradio import processing_utils, utils from gradio import processing_utils, utils, Error
from gradio.components.base import IOComponent, _Keywords, Block from gradio.components.base import IOComponent, _Keywords, Block
from gradio.deprecation import warn_style_method_deprecation from gradio.deprecation import warn_style_method_deprecation
from gradio.events import ( from gradio.events import (
@@ -275,7 +275,10 @@ class Image(
x, mask = x["image"], x["mask"] x, mask = x["image"], x["mask"]
assert isinstance(x, str) assert isinstance(x, str)
try:
im = processing_utils.decode_base64_to_image(x) im = processing_utils.decode_base64_to_image(x)
except PIL.UnidentifiedImageError:
raise Error("Unsupported image type in input")
with warnings.catch_warnings(): with warnings.catch_warnings():
warnings.simplefilter("ignore") warnings.simplefilter("ignore")
im = im.convert(self.image_mode) im = im.convert(self.image_mode)
-115
View File
@@ -1,118 +1,3 @@
css = '''
.loader-container {
display: flex; /* Use flex to align items horizontally */
align-items: center; /* Center items vertically within the container */
white-space: nowrap; /* Prevent line breaks within the container */
}
.loader {
border: 8px solid #f3f3f3; /* Light grey */
border-top: 8px solid #3498db; /* Blue */
border-radius: 50%;
width: 30px;
height: 30px;
animation: spin 2s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* Style the progress bar */
progress {
appearance: none; /* Remove default styling */
height: 20px; /* Set the height of the progress bar */
border-radius: 5px; /* Round the corners of the progress bar */
background-color: #f3f3f3; /* Light grey background */
width: 100%;
}
/* Style the progress bar container */
.progress-container {
margin-left: 20px;
margin-right: 20px;
flex-grow: 1; /* Allow the progress container to take up remaining space */
}
/* Set the color of the progress bar fill */
progress::-webkit-progress-value {
background-color: #3498db; /* Blue color for the fill */
}
progress::-moz-progress-bar {
background-color: #3498db; /* Blue color for the fill in Firefox */
}
/* Style the text on the progress bar */
progress::after {
content: attr(value '%'); /* Display the progress value followed by '%' */
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: white; /* Set text color */
font-size: 14px; /* Set font size */
}
/* Style other texts */
.loader-container > span {
margin-left: 5px; /* Add spacing between the progress bar and the text */
}
.progress-bar > .generating {
display: none !important;
}
.progress-bar{
height: 30px !important;
}
.type_row{
height: 80px !important;
}
.type_row_half{
height: 32px !important;
}
.scroll-hide{
resize: none !important;
}
.refresh_button{
border: none !important;
background: none !important;
font-size: none !important;
box-shadow: none !important;
}
.advanced_check_row{
width: 250px !important;
}
.min_check{
min-width: min(1px, 100%) !important;
}
.resizable_area {
resize: vertical;
overflow: auto !important;
}
.aspect_ratios label {
width: 140px !important;
}
.aspect_ratios label span {
white-space: nowrap !important;
}
.aspect_ratios label input {
margin-left: -5px !important;
}
'''
progress_html = ''' progress_html = '''
<div class="loader-container"> <div class="loader-container">
<div class="loader"></div> <div class="loader"></div>
+18 -4
View File
@@ -1,6 +1,7 @@
import os import os
import importlib import importlib
import importlib.util import importlib.util
import shutil
import subprocess import subprocess
import sys import sys
import re import re
@@ -9,13 +10,10 @@ import importlib.metadata
import packaging.version import packaging.version
from packaging.requirements import Requirement from packaging.requirements import Requirement
logging.getLogger("torch.distributed.nn").setLevel(logging.ERROR) # sshh... logging.getLogger("torch.distributed.nn").setLevel(logging.ERROR) # sshh...
logging.getLogger("xformers").addFilter(lambda record: 'A matching Triton is not available' not in record.getMessage()) logging.getLogger("xformers").addFilter(lambda record: 'A matching Triton is not available' not in record.getMessage())
re_requirement = re.compile(r"\s*([-_a-zA-Z0-9]+)\s*(?:==\s*([-+_.a-zA-Z0-9]+))?\s*") re_requirement = re.compile(r"\s*([-\w]+)\s*(?:==\s*([-+.\w]+))?\s*")
python = sys.executable python = sys.executable
default_command_live = (os.environ.get('LAUNCH_LIVE_OUTPUT') == "1") default_command_live = (os.environ.get('LAUNCH_LIVE_OUTPUT') == "1")
@@ -101,3 +99,19 @@ def requirements_met(requirements_file):
return True return True
def delete_folder_content(folder, prefix=None):
result = True
for filename in os.listdir(folder):
file_path = os.path.join(folder, filename)
try:
if os.path.isfile(file_path) or os.path.islink(file_path):
os.unlink(file_path)
elif os.path.isdir(file_path):
shutil.rmtree(file_path)
except Exception as e:
print(f'{prefix}Failed to delete {file_path}. Reason: {e}')
result = False
return result
+500 -76
View File
@@ -1,45 +1,114 @@
import json import json
import os
import re
from abc import ABC, abstractmethod
from pathlib import Path
import gradio as gr import gradio as gr
from PIL import Image
import fooocus_version
import modules.config import modules.config
import modules.sdxl_styles
from modules.flags import MetadataScheme, Performance, Steps
from modules.flags import SAMPLERS, CIVITAI_NO_KARRAS
from modules.util import quote, unquote, extract_styles_from_prompt, is_json, get_file_from_folder_list, calculate_sha256
re_param_code = r'\s*(\w[\w \-/]+):\s*("(?:\\.|[^\\"])+"|[^,]*)(?:,|$)'
re_param = re.compile(re_param_code)
re_imagesize = re.compile(r"^(\d+)x(\d+)$")
hash_cache = {}
def load_parameter_button_click(raw_prompt_txt, is_generating): def load_parameter_button_click(raw_metadata: dict | str, is_generating: bool):
loaded_parameter_dict = json.loads(raw_prompt_txt) loaded_parameter_dict = raw_metadata
if isinstance(raw_metadata, str):
loaded_parameter_dict = json.loads(raw_metadata)
assert isinstance(loaded_parameter_dict, dict) assert isinstance(loaded_parameter_dict, dict)
results = [True, 1] results = [len(loaded_parameter_dict) > 0, 1]
get_str('prompt', 'Prompt', loaded_parameter_dict, results)
get_str('negative_prompt', 'Negative Prompt', loaded_parameter_dict, results)
get_list('styles', 'Styles', loaded_parameter_dict, results)
get_str('performance', 'Performance', loaded_parameter_dict, results)
get_steps('steps', 'Steps', loaded_parameter_dict, results)
get_float('overwrite_switch', 'Overwrite Switch', loaded_parameter_dict, results)
get_resolution('resolution', 'Resolution', loaded_parameter_dict, results)
get_float('guidance_scale', 'Guidance Scale', loaded_parameter_dict, results)
get_float('sharpness', 'Sharpness', loaded_parameter_dict, results)
get_adm_guidance('adm_guidance', 'ADM Guidance', loaded_parameter_dict, results)
get_str('refiner_swap_method', 'Refiner Swap Method', loaded_parameter_dict, results)
get_float('adaptive_cfg', 'CFG Mimicking from TSNR', loaded_parameter_dict, results)
get_str('base_model', 'Base Model', loaded_parameter_dict, results)
get_str('refiner_model', 'Refiner Model', loaded_parameter_dict, results)
get_float('refiner_switch', 'Refiner Switch', loaded_parameter_dict, results)
get_str('sampler', 'Sampler', loaded_parameter_dict, results)
get_str('scheduler', 'Scheduler', loaded_parameter_dict, results)
get_seed('seed', 'Seed', loaded_parameter_dict, results)
if is_generating:
results.append(gr.update())
else:
results.append(gr.update(visible=True))
results.append(gr.update(visible=False))
get_freeu('freeu', 'FreeU', loaded_parameter_dict, results)
for i in range(modules.config.default_max_lora_number):
get_lora(f'lora_combined_{i + 1}', f'LoRA {i + 1}', loaded_parameter_dict, results)
return results
def get_str(key: str, fallback: str | None, source_dict: dict, results: list, default=None):
try: try:
h = loaded_parameter_dict.get('Prompt', None) h = source_dict.get(key, source_dict.get(fallback, default))
assert isinstance(h, str) assert isinstance(h, str)
results.append(h) results.append(h)
except: except:
results.append(gr.update()) results.append(gr.update())
try:
h = loaded_parameter_dict.get('Negative Prompt', None)
assert isinstance(h, str)
results.append(h)
except:
results.append(gr.update())
def get_list(key: str, fallback: str | None, source_dict: dict, results: list, default=None):
try: try:
h = loaded_parameter_dict.get('Styles', None) h = source_dict.get(key, source_dict.get(fallback, default))
h = eval(h) h = eval(h)
assert isinstance(h, list) assert isinstance(h, list)
results.append(h) results.append(h)
except: except:
results.append(gr.update()) results.append(gr.update())
def get_float(key: str, fallback: str | None, source_dict: dict, results: list, default=None):
try: try:
h = loaded_parameter_dict.get('Performance', None) h = source_dict.get(key, source_dict.get(fallback, default))
assert isinstance(h, str) assert h is not None
h = float(h)
results.append(h) results.append(h)
except: except:
results.append(gr.update()) results.append(gr.update())
def get_steps(key: str, fallback: str | None, source_dict: dict, results: list, default=None):
try: try:
h = loaded_parameter_dict.get('Resolution', None) h = source_dict.get(key, source_dict.get(fallback, default))
assert h is not None
h = int(h)
# if not in steps or in steps and performance is not the same
if h not in iter(Steps) or Steps(h).name.casefold() != source_dict.get('performance', '').replace(' ', '_').casefold():
results.append(h)
return
results.append(-1)
except:
results.append(-1)
def get_resolution(key: str, fallback: str | None, source_dict: dict, results: list, default=None):
try:
h = source_dict.get(key, source_dict.get(fallback, default))
width, height = eval(h) width, height = eval(h)
formatted = modules.config.add_ratio(f'{width}*{height}') formatted = modules.config.add_ratio(f'{width}*{height}')
if formatted in modules.config.available_aspect_ratios: if formatted in modules.config.available_aspect_ratios:
@@ -48,31 +117,29 @@ def load_parameter_button_click(raw_prompt_txt, is_generating):
results.append(-1) results.append(-1)
else: else:
results.append(gr.update()) results.append(gr.update())
results.append(width) results.append(int(width))
results.append(height) results.append(int(height))
except: except:
results.append(gr.update()) results.append(gr.update())
results.append(gr.update()) results.append(gr.update())
results.append(gr.update()) results.append(gr.update())
def get_seed(key: str, fallback: str | None, source_dict: dict, results: list, default=None):
try: try:
h = loaded_parameter_dict.get('Sharpness', None) h = source_dict.get(key, source_dict.get(fallback, default))
assert h is not None assert h is not None
h = float(h) h = int(h)
results.append(False)
results.append(h) results.append(h)
except: except:
results.append(gr.update()) results.append(gr.update())
try:
h = loaded_parameter_dict.get('Guidance Scale', None)
assert h is not None
h = float(h)
results.append(h)
except:
results.append(gr.update()) results.append(gr.update())
def get_adm_guidance(key: str, fallback: str | None, source_dict: dict, results: list, default=None):
try: try:
h = loaded_parameter_dict.get('ADM Guidance', None) h = source_dict.get(key, source_dict.get(fallback, default))
p, n, e = eval(h) p, n, e = eval(h)
results.append(float(p)) results.append(float(p))
results.append(float(n)) results.append(float(n))
@@ -82,67 +149,424 @@ def load_parameter_button_click(raw_prompt_txt, is_generating):
results.append(gr.update()) results.append(gr.update())
results.append(gr.update()) results.append(gr.update())
try:
h = loaded_parameter_dict.get('Base Model', None)
assert isinstance(h, str)
results.append(h)
except:
results.append(gr.update())
def get_freeu(key: str, fallback: str | None, source_dict: dict, results: list, default=None):
try: try:
h = loaded_parameter_dict.get('Refiner Model', None) h = source_dict.get(key, source_dict.get(fallback, default))
assert isinstance(h, str) b1, b2, s1, s2 = eval(h)
results.append(h) results.append(True)
results.append(float(b1))
results.append(float(b2))
results.append(float(s1))
results.append(float(s2))
except: except:
results.append(gr.update())
try:
h = loaded_parameter_dict.get('Refiner Switch', None)
assert h is not None
h = float(h)
results.append(h)
except:
results.append(gr.update())
try:
h = loaded_parameter_dict.get('Sampler', None)
assert isinstance(h, str)
results.append(h)
except:
results.append(gr.update())
try:
h = loaded_parameter_dict.get('Scheduler', None)
assert isinstance(h, str)
results.append(h)
except:
results.append(gr.update())
try:
h = loaded_parameter_dict.get('Seed', None)
assert h is not None
h = int(h)
results.append(False) results.append(False)
results.append(h) results.append(gr.update())
except: results.append(gr.update())
results.append(gr.update()) results.append(gr.update())
results.append(gr.update()) results.append(gr.update())
if is_generating:
results.append(gr.update())
else:
results.append(gr.update(visible=True))
results.append(gr.update(visible=False)) def get_lora(key: str, fallback: str | None, source_dict: dict, results: list):
for i in range(1, 6):
try: try:
n, w = loaded_parameter_dict.get(f'LoRA {i}').split(' : ') n, w = source_dict.get(key, source_dict.get(fallback)).split(' : ')
w = float(w) w = float(w)
results.append(True)
results.append(n) results.append(n)
results.append(w) results.append(w)
except: except:
results.append(gr.update()) results.append(True)
results.append(gr.update()) results.append('None')
results.append(1)
return results
def get_sha256(filepath):
global hash_cache
if filepath not in hash_cache:
hash_cache[filepath] = calculate_sha256(filepath)
return hash_cache[filepath]
def parse_meta_from_preset(preset_content):
assert isinstance(preset_content, dict)
preset_prepared = {}
items = preset_content
for settings_key, meta_key in modules.config.possible_preset_keys.items():
if settings_key == "default_loras":
loras = getattr(modules.config, settings_key)
if settings_key in items:
loras = items[settings_key]
for index, lora in enumerate(loras[:5]):
preset_prepared[f'lora_combined_{index + 1}'] = ' : '.join(map(str, lora))
elif settings_key == "default_aspect_ratio":
if settings_key in items and items[settings_key] is not None:
default_aspect_ratio = items[settings_key]
width, height = default_aspect_ratio.split('*')
else:
default_aspect_ratio = getattr(modules.config, settings_key)
width, height = default_aspect_ratio.split('×')
height = height[:height.index(" ")]
preset_prepared[meta_key] = (width, height)
else:
preset_prepared[meta_key] = items[settings_key] if settings_key in items and items[settings_key] is not None else getattr(modules.config, settings_key)
if settings_key == "default_styles" or settings_key == "default_aspect_ratio":
preset_prepared[meta_key] = str(preset_prepared[meta_key])
return preset_prepared
class MetadataParser(ABC):
def __init__(self):
self.raw_prompt: str = ''
self.full_prompt: str = ''
self.raw_negative_prompt: str = ''
self.full_negative_prompt: str = ''
self.steps: int = 30
self.base_model_name: str = ''
self.base_model_hash: str = ''
self.refiner_model_name: str = ''
self.refiner_model_hash: str = ''
self.loras: list = []
@abstractmethod
def get_scheme(self) -> MetadataScheme:
raise NotImplementedError
@abstractmethod
def parse_json(self, metadata: dict | str) -> dict:
raise NotImplementedError
@abstractmethod
def parse_string(self, metadata: dict) -> str:
raise NotImplementedError
def set_data(self, raw_prompt, full_prompt, raw_negative_prompt, full_negative_prompt, steps, base_model_name,
refiner_model_name, loras):
self.raw_prompt = raw_prompt
self.full_prompt = full_prompt
self.raw_negative_prompt = raw_negative_prompt
self.full_negative_prompt = full_negative_prompt
self.steps = steps
self.base_model_name = Path(base_model_name).stem
base_model_path = get_file_from_folder_list(base_model_name, modules.config.paths_checkpoints)
self.base_model_hash = get_sha256(base_model_path)
if refiner_model_name not in ['', 'None']:
self.refiner_model_name = Path(refiner_model_name).stem
refiner_model_path = get_file_from_folder_list(refiner_model_name, modules.config.paths_checkpoints)
self.refiner_model_hash = get_sha256(refiner_model_path)
self.loras = []
for (lora_name, lora_weight) in loras:
if lora_name != 'None':
lora_path = get_file_from_folder_list(lora_name, modules.config.paths_loras)
lora_hash = get_sha256(lora_path)
self.loras.append((Path(lora_name).stem, lora_weight, lora_hash))
class A1111MetadataParser(MetadataParser):
def get_scheme(self) -> MetadataScheme:
return MetadataScheme.A1111
fooocus_to_a1111 = {
'raw_prompt': 'Raw prompt',
'raw_negative_prompt': 'Raw negative prompt',
'negative_prompt': 'Negative prompt',
'styles': 'Styles',
'performance': 'Performance',
'steps': 'Steps',
'sampler': 'Sampler',
'scheduler': 'Scheduler',
'guidance_scale': 'CFG scale',
'seed': 'Seed',
'resolution': 'Size',
'sharpness': 'Sharpness',
'adm_guidance': 'ADM Guidance',
'refiner_swap_method': 'Refiner Swap Method',
'adaptive_cfg': 'Adaptive CFG',
'overwrite_switch': 'Overwrite Switch',
'freeu': 'FreeU',
'base_model': 'Model',
'base_model_hash': 'Model hash',
'refiner_model': 'Refiner',
'refiner_model_hash': 'Refiner hash',
'lora_hashes': 'Lora hashes',
'lora_weights': 'Lora weights',
'created_by': 'User',
'version': 'Version'
}
def parse_json(self, metadata: str) -> dict:
metadata_prompt = ''
metadata_negative_prompt = ''
done_with_prompt = False
*lines, lastline = metadata.strip().split("\n")
if len(re_param.findall(lastline)) < 3:
lines.append(lastline)
lastline = ''
for line in lines:
line = line.strip()
if line.startswith(f"{self.fooocus_to_a1111['negative_prompt']}:"):
done_with_prompt = True
line = line[len(f"{self.fooocus_to_a1111['negative_prompt']}:"):].strip()
if done_with_prompt:
metadata_negative_prompt += ('' if metadata_negative_prompt == '' else "\n") + line
else:
metadata_prompt += ('' if metadata_prompt == '' else "\n") + line
found_styles, prompt, negative_prompt = extract_styles_from_prompt(metadata_prompt, metadata_negative_prompt)
data = {
'prompt': prompt,
'negative_prompt': negative_prompt
}
for k, v in re_param.findall(lastline):
try:
if v != '' and v[0] == '"' and v[-1] == '"':
v = unquote(v)
m = re_imagesize.match(v)
if m is not None:
data['resolution'] = str((m.group(1), m.group(2)))
else:
data[list(self.fooocus_to_a1111.keys())[list(self.fooocus_to_a1111.values()).index(k)]] = v
except Exception:
print(f"Error parsing \"{k}: {v}\"")
# workaround for multiline prompts
if 'raw_prompt' in data:
data['prompt'] = data['raw_prompt']
raw_prompt = data['raw_prompt'].replace("\n", ', ')
if metadata_prompt != raw_prompt and modules.sdxl_styles.fooocus_expansion not in found_styles:
found_styles.append(modules.sdxl_styles.fooocus_expansion)
if 'raw_negative_prompt' in data:
data['negative_prompt'] = data['raw_negative_prompt']
data['styles'] = str(found_styles)
# try to load performance based on steps, fallback for direct A1111 imports
if 'steps' in data and 'performance' not in data:
try:
data['performance'] = Performance[Steps(int(data['steps'])).name].value
except ValueError | KeyError:
pass
if 'sampler' in data:
data['sampler'] = data['sampler'].replace(' Karras', '')
# get key
for k, v in SAMPLERS.items():
if v == data['sampler']:
data['sampler'] = k
break
for key in ['base_model', 'refiner_model']:
if key in data:
for filename in modules.config.model_filenames:
path = Path(filename)
if data[key] == path.stem:
data[key] = filename
break
if 'lora_hashes' in data:
lora_filenames = modules.config.lora_filenames.copy()
if modules.config.sdxl_lcm_lora in lora_filenames:
lora_filenames.remove(modules.config.sdxl_lcm_lora)
for li, lora in enumerate(data['lora_hashes'].split(', ')):
lora_name, lora_hash, lora_weight = lora.split(': ')
for filename in lora_filenames:
path = Path(filename)
if lora_name == path.stem:
data[f'lora_combined_{li + 1}'] = f'{filename} : {lora_weight}'
break
return data
def parse_string(self, metadata: dict) -> str:
data = {k: v for _, k, v in metadata}
width, height = eval(data['resolution'])
sampler = data['sampler']
scheduler = data['scheduler']
if sampler in SAMPLERS and SAMPLERS[sampler] != '':
sampler = SAMPLERS[sampler]
if sampler not in CIVITAI_NO_KARRAS and scheduler == 'karras':
sampler += f' Karras'
generation_params = {
self.fooocus_to_a1111['steps']: self.steps,
self.fooocus_to_a1111['sampler']: sampler,
self.fooocus_to_a1111['seed']: data['seed'],
self.fooocus_to_a1111['resolution']: f'{width}x{height}',
self.fooocus_to_a1111['guidance_scale']: data['guidance_scale'],
self.fooocus_to_a1111['sharpness']: data['sharpness'],
self.fooocus_to_a1111['adm_guidance']: data['adm_guidance'],
self.fooocus_to_a1111['base_model']: Path(data['base_model']).stem,
self.fooocus_to_a1111['base_model_hash']: self.base_model_hash,
self.fooocus_to_a1111['performance']: data['performance'],
self.fooocus_to_a1111['scheduler']: scheduler,
# workaround for multiline prompts
self.fooocus_to_a1111['raw_prompt']: self.raw_prompt,
self.fooocus_to_a1111['raw_negative_prompt']: self.raw_negative_prompt,
}
if self.refiner_model_name not in ['', 'None']:
generation_params |= {
self.fooocus_to_a1111['refiner_model']: self.refiner_model_name,
self.fooocus_to_a1111['refiner_model_hash']: self.refiner_model_hash
}
for key in ['adaptive_cfg', 'overwrite_switch', 'refiner_swap_method', 'freeu']:
if key in data:
generation_params[self.fooocus_to_a1111[key]] = data[key]
lora_hashes = []
for index, (lora_name, lora_weight, lora_hash) in enumerate(self.loras):
# workaround for Fooocus not knowing LoRA name in LoRA metadata
lora_hashes.append(f'{lora_name}: {lora_hash}: {lora_weight}')
lora_hashes_string = ', '.join(lora_hashes)
generation_params |= {
self.fooocus_to_a1111['lora_hashes']: lora_hashes_string,
self.fooocus_to_a1111['version']: data['version']
}
if modules.config.metadata_created_by != '':
generation_params[self.fooocus_to_a1111['created_by']] = modules.config.metadata_created_by
generation_params_text = ", ".join(
[k if k == v else f'{k}: {quote(v)}' for k, v in generation_params.items() if
v is not None])
positive_prompt_resolved = ', '.join(self.full_prompt)
negative_prompt_resolved = ', '.join(self.full_negative_prompt)
negative_prompt_text = f"\nNegative prompt: {negative_prompt_resolved}" if negative_prompt_resolved else ""
return f"{positive_prompt_resolved}{negative_prompt_text}\n{generation_params_text}".strip()
class FooocusMetadataParser(MetadataParser):
def get_scheme(self) -> MetadataScheme:
return MetadataScheme.FOOOCUS
def parse_json(self, metadata: dict) -> dict:
model_filenames = modules.config.model_filenames.copy()
lora_filenames = modules.config.lora_filenames.copy()
if modules.config.sdxl_lcm_lora in lora_filenames:
lora_filenames.remove(modules.config.sdxl_lcm_lora)
for key, value in metadata.items():
if value in ['', 'None']:
continue
if key in ['base_model', 'refiner_model']:
metadata[key] = self.replace_value_with_filename(key, value, model_filenames)
elif key.startswith('lora_combined_'):
metadata[key] = self.replace_value_with_filename(key, value, lora_filenames)
else:
continue
return metadata
def parse_string(self, metadata: list) -> str:
for li, (label, key, value) in enumerate(metadata):
# remove model folder paths from metadata
if key.startswith('lora_combined_'):
name, weight = value.split(' : ')
name = Path(name).stem
value = f'{name} : {weight}'
metadata[li] = (label, key, value)
res = {k: v for _, k, v in metadata}
res['full_prompt'] = self.full_prompt
res['full_negative_prompt'] = self.full_negative_prompt
res['steps'] = self.steps
res['base_model'] = self.base_model_name
res['base_model_hash'] = self.base_model_hash
if self.refiner_model_name not in ['', 'None']:
res['refiner_model'] = self.refiner_model_name
res['refiner_model_hash'] = self.refiner_model_hash
res['loras'] = self.loras
if modules.config.metadata_created_by != '':
res['created_by'] = modules.config.metadata_created_by
return json.dumps(dict(sorted(res.items())))
@staticmethod
def replace_value_with_filename(key, value, filenames):
for filename in filenames:
path = Path(filename)
if key.startswith('lora_combined_'):
name, weight = value.split(' : ')
if name == path.stem:
return f'{filename} : {weight}'
elif value == path.stem:
return filename
def get_metadata_parser(metadata_scheme: MetadataScheme) -> MetadataParser:
match metadata_scheme:
case MetadataScheme.FOOOCUS:
return FooocusMetadataParser()
case MetadataScheme.A1111:
return A1111MetadataParser()
case _:
raise NotImplementedError
def read_info_from_image(filepath) -> tuple[str | None, MetadataScheme | None]:
with Image.open(filepath) as image:
items = (image.info or {}).copy()
parameters = items.pop('parameters', None)
metadata_scheme = items.pop('fooocus_scheme', None)
exif = items.pop('exif', None)
if parameters is not None and is_json(parameters):
parameters = json.loads(parameters)
elif exif is not None:
exif = image.getexif()
# 0x9286 = UserComment
parameters = exif.get(0x9286, None)
# 0x927C = MakerNote
metadata_scheme = exif.get(0x927C, None)
if is_json(parameters):
parameters = json.loads(parameters)
try:
metadata_scheme = MetadataScheme(metadata_scheme)
except ValueError:
metadata_scheme = None
# broad fallback
if isinstance(parameters, dict):
metadata_scheme = MetadataScheme.FOOOCUS
if isinstance(parameters, str):
metadata_scheme = MetadataScheme.A1111
return parameters, metadata_scheme
def get_exif(metadata: str | None, metadata_scheme: str):
exif = Image.Exif()
# tags see see https://github.com/python-pillow/Pillow/blob/9.2.x/src/PIL/ExifTags.py
# 0x9286 = UserComment
exif[0x9286] = metadata
# 0x0131 = Software
exif[0x0131] = 'Fooocus v' + fooocus_version.version
# 0x927C = MakerNote
exif[0x927C] = metadata_scheme
return exif
+37 -31
View File
@@ -17,7 +17,6 @@ import ldm_patched.controlnet.cldm
import ldm_patched.modules.model_patcher import ldm_patched.modules.model_patcher
import ldm_patched.modules.samplers import ldm_patched.modules.samplers
import ldm_patched.modules.args_parser import ldm_patched.modules.args_parser
import modules.advanced_parameters as advanced_parameters
import warnings import warnings
import safetensors.torch import safetensors.torch
import modules.constants as constants import modules.constants as constants
@@ -29,15 +28,25 @@ from modules.patch_precision import patch_all_precision
from modules.patch_clip import patch_all_clip from modules.patch_clip import patch_all_clip
sharpness = 2.0 class PatchSettings:
def __init__(self,
sharpness=2.0,
adm_scaler_end=0.3,
positive_adm_scale=1.5,
negative_adm_scale=0.8,
controlnet_softness=0.25,
adaptive_cfg=7.0):
self.sharpness = sharpness
self.adm_scaler_end = adm_scaler_end
self.positive_adm_scale = positive_adm_scale
self.negative_adm_scale = negative_adm_scale
self.controlnet_softness = controlnet_softness
self.adaptive_cfg = adaptive_cfg
self.global_diffusion_progress = 0
self.eps_record = None
adm_scaler_end = 0.3
positive_adm_scale = 1.5
negative_adm_scale = 0.8
adaptive_cfg = 7.0 patch_settings = {}
global_diffusion_progress = 0
eps_record = None
def calculate_weight_patched(self, patches, weight, key): def calculate_weight_patched(self, patches, weight, key):
@@ -201,14 +210,13 @@ class BrownianTreeNoiseSamplerPatched:
def compute_cfg(uncond, cond, cfg_scale, t): def compute_cfg(uncond, cond, cfg_scale, t):
global adaptive_cfg pid = os.getpid()
mimic_cfg = float(patch_settings[pid].adaptive_cfg)
mimic_cfg = float(adaptive_cfg)
real_cfg = float(cfg_scale) real_cfg = float(cfg_scale)
real_eps = uncond + real_cfg * (cond - uncond) real_eps = uncond + real_cfg * (cond - uncond)
if cfg_scale > adaptive_cfg: if cfg_scale > patch_settings[pid].adaptive_cfg:
mimicked_eps = uncond + mimic_cfg * (cond - uncond) mimicked_eps = uncond + mimic_cfg * (cond - uncond)
return real_eps * t + mimicked_eps * (1 - t) return real_eps * t + mimicked_eps * (1 - t)
else: else:
@@ -216,13 +224,13 @@ def compute_cfg(uncond, cond, cfg_scale, t):
def patched_sampling_function(model, x, timestep, uncond, cond, cond_scale, model_options=None, seed=None): def patched_sampling_function(model, x, timestep, uncond, cond, cond_scale, model_options=None, seed=None):
global eps_record pid = os.getpid()
if math.isclose(cond_scale, 1.0) and not model_options.get("disable_cfg1_optimization", False): if math.isclose(cond_scale, 1.0) and not model_options.get("disable_cfg1_optimization", False):
final_x0 = calc_cond_uncond_batch(model, cond, None, x, timestep, model_options)[0] final_x0 = calc_cond_uncond_batch(model, cond, None, x, timestep, model_options)[0]
if eps_record is not None: if patch_settings[pid].eps_record is not None:
eps_record = ((x - final_x0) / timestep).cpu() patch_settings[pid].eps_record = ((x - final_x0) / timestep).cpu()
return final_x0 return final_x0
@@ -231,16 +239,16 @@ def patched_sampling_function(model, x, timestep, uncond, cond, cond_scale, mode
positive_eps = x - positive_x0 positive_eps = x - positive_x0
negative_eps = x - negative_x0 negative_eps = x - negative_x0
alpha = 0.001 * sharpness * global_diffusion_progress alpha = 0.001 * patch_settings[pid].sharpness * patch_settings[pid].global_diffusion_progress
positive_eps_degraded = anisotropic.adaptive_anisotropic_filter(x=positive_eps, g=positive_x0) positive_eps_degraded = anisotropic.adaptive_anisotropic_filter(x=positive_eps, g=positive_x0)
positive_eps_degraded_weighted = positive_eps_degraded * alpha + positive_eps * (1.0 - alpha) positive_eps_degraded_weighted = positive_eps_degraded * alpha + positive_eps * (1.0 - alpha)
final_eps = compute_cfg(uncond=negative_eps, cond=positive_eps_degraded_weighted, final_eps = compute_cfg(uncond=negative_eps, cond=positive_eps_degraded_weighted,
cfg_scale=cond_scale, t=global_diffusion_progress) cfg_scale=cond_scale, t=patch_settings[pid].global_diffusion_progress)
if eps_record is not None: if patch_settings[pid].eps_record is not None:
eps_record = (final_eps / timestep).cpu() patch_settings[pid].eps_record = (final_eps / timestep).cpu()
return x - final_eps return x - final_eps
@@ -255,20 +263,19 @@ def round_to_64(x):
def sdxl_encode_adm_patched(self, **kwargs): def sdxl_encode_adm_patched(self, **kwargs):
global positive_adm_scale, negative_adm_scale
clip_pooled = ldm_patched.modules.model_base.sdxl_pooled(kwargs, self.noise_augmentor) clip_pooled = ldm_patched.modules.model_base.sdxl_pooled(kwargs, self.noise_augmentor)
width = kwargs.get("width", 1024) width = kwargs.get("width", 1024)
height = kwargs.get("height", 1024) height = kwargs.get("height", 1024)
target_width = width target_width = width
target_height = height target_height = height
pid = os.getpid()
if kwargs.get("prompt_type", "") == "negative": if kwargs.get("prompt_type", "") == "negative":
width = float(width) * negative_adm_scale width = float(width) * patch_settings[pid].negative_adm_scale
height = float(height) * negative_adm_scale height = float(height) * patch_settings[pid].negative_adm_scale
elif kwargs.get("prompt_type", "") == "positive": elif kwargs.get("prompt_type", "") == "positive":
width = float(width) * positive_adm_scale width = float(width) * patch_settings[pid].positive_adm_scale
height = float(height) * positive_adm_scale height = float(height) * patch_settings[pid].positive_adm_scale
def embedder(number_list): def embedder(number_list):
h = self.embedder(torch.tensor(number_list, dtype=torch.float32)) h = self.embedder(torch.tensor(number_list, dtype=torch.float32))
@@ -322,7 +329,7 @@ def patched_KSamplerX0Inpaint_forward(self, x, sigma, uncond, cond, cond_scale,
def timed_adm(y, timesteps): def timed_adm(y, timesteps):
if isinstance(y, torch.Tensor) and int(y.dim()) == 2 and int(y.shape[1]) == 5632: if isinstance(y, torch.Tensor) and int(y.dim()) == 2 and int(y.shape[1]) == 5632:
y_mask = (timesteps > 999.0 * (1.0 - float(adm_scaler_end))).to(y)[..., None] y_mask = (timesteps > 999.0 * (1.0 - float(patch_settings[os.getpid()].adm_scaler_end))).to(y)[..., None]
y_with_adm = y[..., :2816].clone() y_with_adm = y[..., :2816].clone()
y_without_adm = y[..., 2816:].clone() y_without_adm = y[..., 2816:].clone()
return y_with_adm * y_mask + y_without_adm * (1.0 - y_mask) return y_with_adm * y_mask + y_without_adm * (1.0 - y_mask)
@@ -332,6 +339,7 @@ def timed_adm(y, timesteps):
def patched_cldm_forward(self, x, hint, timesteps, context, y=None, **kwargs): def patched_cldm_forward(self, x, hint, timesteps, context, y=None, **kwargs):
t_emb = ldm_patched.ldm.modules.diffusionmodules.openaimodel.timestep_embedding(timesteps, self.model_channels, repeat_only=False).to(x.dtype) t_emb = ldm_patched.ldm.modules.diffusionmodules.openaimodel.timestep_embedding(timesteps, self.model_channels, repeat_only=False).to(x.dtype)
emb = self.time_embed(t_emb) emb = self.time_embed(t_emb)
pid = os.getpid()
guided_hint = self.input_hint_block(hint, emb, context) guided_hint = self.input_hint_block(hint, emb, context)
@@ -357,19 +365,17 @@ def patched_cldm_forward(self, x, hint, timesteps, context, y=None, **kwargs):
h = self.middle_block(h, emb, context) h = self.middle_block(h, emb, context)
outs.append(self.middle_block_out(h, emb, context)) outs.append(self.middle_block_out(h, emb, context))
if advanced_parameters.controlnet_softness > 0: if patch_settings[pid].controlnet_softness > 0:
for i in range(10): for i in range(10):
k = 1.0 - float(i) / 9.0 k = 1.0 - float(i) / 9.0
outs[i] = outs[i] * (1.0 - advanced_parameters.controlnet_softness * k) outs[i] = outs[i] * (1.0 - patch_settings[pid].controlnet_softness * k)
return outs return outs
def patched_unet_forward(self, x, timesteps=None, context=None, y=None, control=None, transformer_options={}, **kwargs): def patched_unet_forward(self, x, timesteps=None, context=None, y=None, control=None, transformer_options={}, **kwargs):
global global_diffusion_progress
self.current_step = 1.0 - timesteps.to(x) / 999.0 self.current_step = 1.0 - timesteps.to(x) / 999.0
global_diffusion_progress = float(self.current_step.detach().cpu().numpy().tolist()[0]) patch_settings[os.getpid()].global_diffusion_progress = float(self.current_step.detach().cpu().numpy().tolist()[0])
y = timed_adm(y, timesteps) y = timed_adm(y, timesteps)
+39 -16
View File
@@ -5,26 +5,49 @@ import json
import urllib.parse import urllib.parse
from PIL import Image from PIL import Image
from PIL.PngImagePlugin import PngInfo
from modules.flags import OutputFormat
from modules.meta_parser import MetadataParser, get_exif
from modules.util import generate_temp_filename from modules.util import generate_temp_filename
log_cache = {} log_cache = {}
def get_current_html_path(): def get_current_html_path(output_format=None):
output_format = output_format if output_format else modules.config.default_output_format
date_string, local_temp_filename, only_name = generate_temp_filename(folder=modules.config.path_outputs, date_string, local_temp_filename, only_name = generate_temp_filename(folder=modules.config.path_outputs,
extension='png') extension=output_format)
html_name = os.path.join(os.path.dirname(local_temp_filename), 'log.html') html_name = os.path.join(os.path.dirname(local_temp_filename), 'log.html')
return html_name return html_name
def log(img, dic): def log(img, metadata, metadata_parser: MetadataParser | None = None, output_format=None) -> str:
if args_manager.args.disable_image_log: path_outputs = modules.config.temp_path if args_manager.args.disable_image_log else modules.config.path_outputs
return output_format = output_format if output_format else modules.config.default_output_format
date_string, local_temp_filename, only_name = generate_temp_filename(folder=path_outputs, extension=output_format)
date_string, local_temp_filename, only_name = generate_temp_filename(folder=modules.config.path_outputs, extension='png')
os.makedirs(os.path.dirname(local_temp_filename), exist_ok=True) os.makedirs(os.path.dirname(local_temp_filename), exist_ok=True)
Image.fromarray(img).save(local_temp_filename)
parsed_parameters = metadata_parser.parse_string(metadata.copy()) if metadata_parser is not None else ''
image = Image.fromarray(img)
if output_format == OutputFormat.PNG.value:
if parsed_parameters != '':
pnginfo = PngInfo()
pnginfo.add_text('parameters', parsed_parameters)
pnginfo.add_text('fooocus_scheme', metadata_parser.get_scheme().value)
else:
pnginfo = None
image.save(local_temp_filename, pnginfo=pnginfo)
elif output_format == OutputFormat.JPEG.value:
image.save(local_temp_filename, quality=95, optimize=True, progressive=True, exif=get_exif(parsed_parameters, metadata_parser.get_scheme().value) if metadata_parser else Image.Exif())
elif output_format == OutputFormat.WEBP.value:
image.save(local_temp_filename, quality=95, lossless=False, exif=get_exif(parsed_parameters, metadata_parser.get_scheme().value) if metadata_parser else Image.Exif())
else:
image.save(local_temp_filename)
if args_manager.args.disable_image_log:
return local_temp_filename
html_name = os.path.join(os.path.dirname(local_temp_filename), 'log.html') html_name = os.path.join(os.path.dirname(local_temp_filename), 'log.html')
css_styles = ( css_styles = (
@@ -32,7 +55,7 @@ def log(img, dic):
"body { background-color: #121212; color: #E0E0E0; } " "body { background-color: #121212; color: #E0E0E0; } "
"a { color: #BB86FC; } " "a { color: #BB86FC; } "
".metadata { border-collapse: collapse; width: 100%; } " ".metadata { border-collapse: collapse; width: 100%; } "
".metadata .key { width: 15%; } " ".metadata .label { width: 15%; } "
".metadata .value { width: 85%; font-weight: bold; } " ".metadata .value { width: 85%; font-weight: bold; } "
".metadata th, .metadata td { border: 1px solid #4d4d4d; padding: 4px; } " ".metadata th, .metadata td { border: 1px solid #4d4d4d; padding: 4px; } "
".image-container img { height: auto; max-width: 512px; display: block; padding-right:10px; } " ".image-container img { height: auto; max-width: 512px; display: block; padding-right:10px; } "
@@ -68,7 +91,7 @@ def log(img, dic):
</script>""" </script>"""
) )
begin_part = f"<html><head><title>Fooocus Log {date_string}</title>{css_styles}</head><body>{js}<p>Fooocus Log {date_string} (private)</p>\n<p>All images are clean, without any hidden data/meta, and safe to share with others.</p><!--fooocus-log-split-->\n\n" begin_part = f"<!DOCTYPE html><html><head><title>Fooocus Log {date_string}</title>{css_styles}</head><body>{js}<p>Fooocus Log {date_string} (private)</p>\n<p>Metadata is embedded if enabled in the config or developer debug mode. You can find the information for each image in line Metadata Scheme.</p><!--fooocus-log-split-->\n\n"
end_part = f'\n<!--fooocus-log-split--></body></html>' end_part = f'\n<!--fooocus-log-split--></body></html>'
middle_part = log_cache.get(html_name, "") middle_part = log_cache.get(html_name, "")
@@ -83,14 +106,14 @@ def log(img, dic):
div_name = only_name.replace('.', '_') div_name = only_name.replace('.', '_')
item = f"<div id=\"{div_name}\" class=\"image-container\"><hr><table><tr>\n" item = f"<div id=\"{div_name}\" class=\"image-container\"><hr><table><tr>\n"
item += f"<td><a href=\"{only_name}\" target=\"_blank\"><img src='{only_name}' onerror=\"this.closest('.image-container').style.display='none';\" loading='lazy'></img></a><div>{only_name}</div></td>" item += f"<td><a href=\"{only_name}\" target=\"_blank\"><img src='{only_name}' onerror=\"this.closest('.image-container').style.display='none';\" loading='lazy'/></a><div>{only_name}</div></td>"
item += "<td><table class='metadata'>" item += "<td><table class='metadata'>"
for key, value in dic: for label, key, value in metadata:
value_txt = str(value).replace('\n', ' </br> ') value_txt = str(value).replace('\n', ' </br> ')
item += f"<tr><td class='key'>{key}</td><td class='value'>{value_txt}</td></tr>\n" item += f"<tr><td class='label'>{label}</td><td class='value'>{value_txt}</td></tr>\n"
item += "</table>" item += "</table>"
js_txt = urllib.parse.quote(json.dumps({k: v for k, v in dic}, indent=0), safe='') js_txt = urllib.parse.quote(json.dumps({k: v for _, k, v in metadata}, indent=0), safe='')
item += f"</br><button onclick=\"to_clipboard('{js_txt}')\">Copy to Clipboard</button>" item += f"</br><button onclick=\"to_clipboard('{js_txt}')\">Copy to Clipboard</button>"
item += "</td>" item += "</td>"
@@ -105,4 +128,4 @@ def log(img, dic):
log_cache[html_name] = middle_part log_cache[html_name] = middle_part
return return local_temp_filename
+43 -4
View File
@@ -1,13 +1,13 @@
import os import os
import re import re
import json import json
import math
import modules.config
from modules.util import get_files_from_folder from modules.util import get_files_from_folder
# cannot use modules.config - validators causing circular imports # cannot use modules.config - validators causing circular imports
styles_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '../sdxl_styles/')) styles_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '../sdxl_styles/'))
wildcards_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '../wildcards/'))
wildcards_max_bfs_depth = 64 wildcards_max_bfs_depth = 64
@@ -59,7 +59,7 @@ def apply_style(style, positive):
return p.replace('{prompt}', positive).splitlines(), n.splitlines() return p.replace('{prompt}', positive).splitlines(), n.splitlines()
def apply_wildcards(wildcard_text, rng, directory=wildcards_path): def apply_wildcards(wildcard_text, rng, i, read_wildcards_in_order):
for _ in range(wildcards_max_bfs_depth): for _ in range(wildcards_max_bfs_depth):
placeholders = re.findall(r'__([\w-]+)__', wildcard_text) placeholders = re.findall(r'__([\w-]+)__', wildcard_text)
if len(placeholders) == 0: if len(placeholders) == 0:
@@ -68,9 +68,13 @@ def apply_wildcards(wildcard_text, rng, directory=wildcards_path):
print(f'[Wildcards] processing: {wildcard_text}') print(f'[Wildcards] processing: {wildcard_text}')
for placeholder in placeholders: for placeholder in placeholders:
try: try:
words = open(os.path.join(directory, f'{placeholder}.txt'), encoding='utf-8').read().splitlines() matches = [x for x in modules.config.wildcard_filenames if os.path.splitext(os.path.basename(x))[0] == placeholder]
words = open(os.path.join(modules.config.path_wildcards, matches[0]), encoding='utf-8').read().splitlines()
words = [x for x in words if x != ''] words = [x for x in words if x != '']
assert len(words) > 0 assert len(words) > 0
if read_wildcards_in_order:
wildcard_text = wildcard_text.replace(f'__{placeholder}__', words[i % len(words)], 1)
else:
wildcard_text = wildcard_text.replace(f'__{placeholder}__', rng.choice(words), 1) wildcard_text = wildcard_text.replace(f'__{placeholder}__', rng.choice(words), 1)
except: except:
print(f'[Wildcards] Warning: {placeholder}.txt missing or empty. ' print(f'[Wildcards] Warning: {placeholder}.txt missing or empty. '
@@ -80,3 +84,38 @@ def apply_wildcards(wildcard_text, rng, directory=wildcards_path):
print(f'[Wildcards] BFS stack overflow. Current text: {wildcard_text}') print(f'[Wildcards] BFS stack overflow. Current text: {wildcard_text}')
return wildcard_text return wildcard_text
def get_words(arrays, totalMult, index):
if len(arrays) == 1:
return [arrays[0].split(',')[index]]
else:
words = arrays[0].split(',')
word = words[index % len(words)]
index -= index % len(words)
index /= len(words)
index = math.floor(index)
return [word] + get_words(arrays[1:], math.floor(totalMult/len(words)), index)
def apply_arrays(text, index):
arrays = re.findall(r'\[\[(.*?)\]\]', text)
if len(arrays) == 0:
return text
print(f'[Arrays] processing: {text}')
mult = 1
for arr in arrays:
words = arr.split(',')
mult *= len(words)
index %= mult
chosen_words = get_words(arrays, mult, index)
i = 0
for arr in arrays:
text = text.replace(f'[[{arr}]]', chosen_words[i], 1)
i = i+1
return text
+196 -7
View File
@@ -1,15 +1,20 @@
import typing
import numpy as np import numpy as np
import datetime import datetime
import random import random
import math import math
import os import os
import cv2 import cv2
import json
from PIL import Image from PIL import Image
from hashlib import sha256
import modules.sdxl_styles
LANCZOS = (Image.Resampling.LANCZOS if hasattr(Image, 'Resampling') else Image.LANCZOS) LANCZOS = (Image.Resampling.LANCZOS if hasattr(Image, 'Resampling') else Image.LANCZOS)
HASH_SHA256_LENGTH = 10
def erode_or_dilate(x, k): def erode_or_dilate(x, k):
k = int(k) k = int(k)
@@ -155,23 +160,207 @@ def generate_temp_filename(folder='./outputs/', extension='png'):
random_number = random.randint(1000, 9999) random_number = random.randint(1000, 9999)
filename = f"{time_string}_{random_number}.{extension}" filename = f"{time_string}_{random_number}.{extension}"
result = os.path.join(folder, date_string, filename) result = os.path.join(folder, date_string, filename)
return date_string, os.path.abspath(os.path.realpath(result)), filename return date_string, os.path.abspath(result), filename
def get_files_from_folder(folder_path, exensions=None, name_filter=None): def get_files_from_folder(folder_path, extensions=None, name_filter=None):
if not os.path.isdir(folder_path): if not os.path.isdir(folder_path):
raise ValueError("Folder path is not a valid directory.") raise ValueError("Folder path is not a valid directory.")
filenames = [] filenames = []
for root, dirs, files in os.walk(folder_path): for root, dirs, files in os.walk(folder_path, topdown=False):
relative_path = os.path.relpath(root, folder_path) relative_path = os.path.relpath(root, folder_path)
if relative_path == ".": if relative_path == ".":
relative_path = "" relative_path = ""
for filename in files: for filename in sorted(files, key=lambda s: s.casefold()):
_, file_extension = os.path.splitext(filename) _, file_extension = os.path.splitext(filename)
if (exensions == None or file_extension.lower() in exensions) and (name_filter == None or name_filter in _): if (extensions is None or file_extension.lower() in extensions) and (name_filter is None or name_filter in _):
path = os.path.join(relative_path, filename) path = os.path.join(relative_path, filename)
filenames.append(path) filenames.append(path)
return sorted(filenames, key=lambda x: -1 if os.sep in x else 1) return filenames
def calculate_sha256(filename, length=HASH_SHA256_LENGTH) -> str:
hash_sha256 = sha256()
blksize = 1024 * 1024
with open(filename, "rb") as f:
for chunk in iter(lambda: f.read(blksize), b""):
hash_sha256.update(chunk)
res = hash_sha256.hexdigest()
return res[:length] if length else res
def quote(text):
if ',' not in str(text) and '\n' not in str(text) and ':' not in str(text):
return text
return json.dumps(text, ensure_ascii=False)
def unquote(text):
if len(text) == 0 or text[0] != '"' or text[-1] != '"':
return text
try:
return json.loads(text)
except Exception:
return text
def unwrap_style_text_from_prompt(style_text, prompt):
"""
Checks the prompt to see if the style text is wrapped around it. If so,
returns True plus the prompt text without the style text. Otherwise, returns
False with the original prompt.
Note that the "cleaned" version of the style text is only used for matching
purposes here. It isn't returned; the original style text is not modified.
"""
stripped_prompt = prompt
stripped_style_text = style_text
if "{prompt}" in stripped_style_text:
# Work out whether the prompt is wrapped in the style text. If so, we
# return True and the "inner" prompt text that isn't part of the style.
try:
left, right = stripped_style_text.split("{prompt}", 2)
except ValueError as e:
# If the style text has multple "{prompt}"s, we can't split it into
# two parts. This is an error, but we can't do anything about it.
print(f"Unable to compare style text to prompt:\n{style_text}")
print(f"Error: {e}")
return False, prompt, ''
left_pos = stripped_prompt.find(left)
right_pos = stripped_prompt.find(right)
if 0 <= left_pos < right_pos:
real_prompt = stripped_prompt[left_pos + len(left):right_pos]
prompt = stripped_prompt.replace(left + real_prompt + right, '', 1)
if prompt.startswith(", "):
prompt = prompt[2:]
if prompt.endswith(", "):
prompt = prompt[:-2]
return True, prompt, real_prompt
else:
# Work out whether the given prompt starts with the style text. If so, we
# return True and the prompt text up to where the style text starts.
if stripped_prompt.endswith(stripped_style_text):
prompt = stripped_prompt[: len(stripped_prompt) - len(stripped_style_text)]
if prompt.endswith(", "):
prompt = prompt[:-2]
return True, prompt, prompt
return False, prompt, ''
def extract_original_prompts(style, prompt, negative_prompt):
"""
Takes a style and compares it to the prompt and negative prompt. If the style
matches, returns True plus the prompt and negative prompt with the style text
removed. Otherwise, returns False with the original prompt and negative prompt.
"""
if not style.prompt and not style.negative_prompt:
return False, prompt, negative_prompt
match_positive, extracted_positive, real_prompt = unwrap_style_text_from_prompt(
style.prompt, prompt
)
if not match_positive:
return False, prompt, negative_prompt, ''
match_negative, extracted_negative, _ = unwrap_style_text_from_prompt(
style.negative_prompt, negative_prompt
)
if not match_negative:
return False, prompt, negative_prompt, ''
return True, extracted_positive, extracted_negative, real_prompt
def extract_styles_from_prompt(prompt, negative_prompt):
extracted = []
applicable_styles = []
for style_name, (style_prompt, style_negative_prompt) in modules.sdxl_styles.styles.items():
applicable_styles.append(PromptStyle(name=style_name, prompt=style_prompt, negative_prompt=style_negative_prompt))
real_prompt = ''
while True:
found_style = None
for style in applicable_styles:
is_match, new_prompt, new_neg_prompt, new_real_prompt = extract_original_prompts(
style, prompt, negative_prompt
)
if is_match:
found_style = style
prompt = new_prompt
negative_prompt = new_neg_prompt
if real_prompt == '' and new_real_prompt != '' and new_real_prompt != prompt:
real_prompt = new_real_prompt
break
if not found_style:
break
applicable_styles.remove(found_style)
extracted.append(found_style.name)
# add prompt expansion if not all styles could be resolved
if prompt != '':
if real_prompt != '':
extracted.append(modules.sdxl_styles.fooocus_expansion)
else:
# find real_prompt when only prompt expansion is selected
first_word = prompt.split(', ')[0]
first_word_positions = [i for i in range(len(prompt)) if prompt.startswith(first_word, i)]
if len(first_word_positions) > 1:
real_prompt = prompt[:first_word_positions[-1]]
extracted.append(modules.sdxl_styles.fooocus_expansion)
if real_prompt.endswith(', '):
real_prompt = real_prompt[:-2]
return list(reversed(extracted)), real_prompt, negative_prompt
class PromptStyle(typing.NamedTuple):
name: str
prompt: str
negative_prompt: str
def is_json(data: str) -> bool:
try:
loaded_json = json.loads(data)
assert isinstance(loaded_json, dict)
except (ValueError, AssertionError):
return False
return True
def get_file_from_folder_list(name, folders):
for folder in folders:
filename = os.path.abspath(os.path.realpath(os.path.join(folder, name)))
if os.path.isfile(filename):
return filename
return os.path.abspath(os.path.realpath(os.path.join(folders[0], name)))
def ordinal_suffix(number: int) -> str:
return 'th' if 10 <= number % 100 <= 20 else {1: 'st', 2: 'nd', 3: 'rd'}.get(number % 10, 'th')
def makedirs_with_log(path):
try:
os.makedirs(path, exist_ok=True)
except OSError as error:
print(f'Directory {path} could not be created, reason: {error}')
def get_enabled_loras(loras: list) -> list:
return [[lora[1], lora[2]] for lora in loras if lora[0]]
+6
View File
@@ -0,0 +1,6 @@
*.json
!anime.json
!default.json
!lcm.json
!realistic.json
!sai.json
+8 -2
View File
@@ -4,22 +4,27 @@
"default_refiner_switch": 0.5, "default_refiner_switch": 0.5,
"default_loras": [ "default_loras": [
[ [
true,
"None", "None",
1.0 1.0
], ],
[ [
true,
"None", "None",
1.0 1.0
], ],
[ [
true,
"None", "None",
1.0 1.0
], ],
[ [
true,
"None", "None",
1.0 1.0
], ],
[ [
true,
"None", "None",
1.0 1.0
] ]
@@ -33,7 +38,7 @@
"default_prompt_negative": "", "default_prompt_negative": "",
"default_styles": [ "default_styles": [
"Fooocus V2", "Fooocus V2",
"Fooocus Negative", "Fooocus Semi Realistic",
"Fooocus Masterpiece" "Fooocus Masterpiece"
], ],
"default_aspect_ratio": "896*1152", "default_aspect_ratio": "896*1152",
@@ -41,5 +46,6 @@
"animaPencilXL_v100.safetensors": "https://huggingface.co/lllyasviel/fav_models/resolve/main/fav/animaPencilXL_v100.safetensors" "animaPencilXL_v100.safetensors": "https://huggingface.co/lllyasviel/fav_models/resolve/main/fav/animaPencilXL_v100.safetensors"
}, },
"embeddings_downloads": {}, "embeddings_downloads": {},
"lora_downloads": {} "lora_downloads": {},
"previous_default_models": []
} }
+5
View File
@@ -4,22 +4,27 @@
"default_refiner_switch": 0.5, "default_refiner_switch": 0.5,
"default_loras": [ "default_loras": [
[ [
true,
"sd_xl_offset_example-lora_1.0.safetensors", "sd_xl_offset_example-lora_1.0.safetensors",
0.1 0.1
], ],
[ [
true,
"None", "None",
1.0 1.0
], ],
[ [
true,
"None", "None",
1.0 1.0
], ],
[ [
true,
"None", "None",
1.0 1.0
], ],
[ [
true,
"None", "None",
1.0 1.0
] ]
+15 -3
View File
@@ -1,25 +1,30 @@
{ {
"default_model": "juggernautXL_version6Rundiffusion.safetensors", "default_model": "juggernautXL_v8Rundiffusion.safetensors",
"default_refiner": "None", "default_refiner": "None",
"default_refiner_switch": 0.5, "default_refiner_switch": 0.5,
"default_loras": [ "default_loras": [
[ [
true,
"None", "None",
1.0 1.0
], ],
[ [
true,
"None", "None",
1.0 1.0
], ],
[ [
true,
"None", "None",
1.0 1.0
], ],
[ [
true,
"None", "None",
1.0 1.0
], ],
[ [
true,
"None", "None",
1.0 1.0
] ]
@@ -38,8 +43,15 @@
], ],
"default_aspect_ratio": "1152*896", "default_aspect_ratio": "1152*896",
"checkpoint_downloads": { "checkpoint_downloads": {
"juggernautXL_version6Rundiffusion.safetensors": "https://huggingface.co/lllyasviel/fav_models/resolve/main/fav/juggernautXL_version6Rundiffusion.safetensors" "juggernautXL_v8Rundiffusion.safetensors": "https://huggingface.co/lllyasviel/fav_models/resolve/main/fav/juggernautXL_v8Rundiffusion.safetensors"
}, },
"embeddings_downloads": {}, "embeddings_downloads": {},
"lora_downloads": {} "lora_downloads": {},
"previous_default_models": [
"juggernautXL_version8Rundiffusion.safetensors",
"juggernautXL_version7Rundiffusion.safetensors",
"juggernautXL_v7Rundiffusion.safetensors",
"juggernautXL_version6Rundiffusion.safetensors",
"juggernautXL_v6Rundiffusion.safetensors"
]
} }
+57
View File
@@ -0,0 +1,57 @@
{
"default_model": "juggernautXL_v8Rundiffusion.safetensors",
"default_refiner": "None",
"default_refiner_switch": 0.5,
"default_loras": [
[
true,
"None",
1.0
],
[
true,
"None",
1.0
],
[
true,
"None",
1.0
],
[
true,
"None",
1.0
],
[
true,
"None",
1.0
]
],
"default_cfg_scale": 4.0,
"default_sample_sharpness": 2.0,
"default_sampler": "dpmpp_2m_sde_gpu",
"default_scheduler": "karras",
"default_performance": "Lightning",
"default_prompt": "",
"default_prompt_negative": "",
"default_styles": [
"Fooocus V2",
"Fooocus Enhance",
"Fooocus Sharp"
],
"default_aspect_ratio": "1152*896",
"checkpoint_downloads": {
"juggernautXL_v8Rundiffusion.safetensors": "https://huggingface.co/lllyasviel/fav_models/resolve/main/fav/juggernautXL_v8Rundiffusion.safetensors"
},
"embeddings_downloads": {},
"lora_downloads": {},
"previous_default_models": [
"juggernautXL_version8Rundiffusion.safetensors",
"juggernautXL_version7Rundiffusion.safetensors",
"juggernautXL_v7Rundiffusion.safetensors",
"juggernautXL_version6Rundiffusion.safetensors",
"juggernautXL_v6Rundiffusion.safetensors"
]
}
+6 -1
View File
@@ -1,25 +1,30 @@
{ {
"default_model": "realisticStockPhoto_v20.safetensors", "default_model": "realisticStockPhoto_v20.safetensors",
"default_refiner": "", "default_refiner": "None",
"default_refiner_switch": 0.5, "default_refiner_switch": 0.5,
"default_loras": [ "default_loras": [
[ [
true,
"SDXL_FILM_PHOTOGRAPHY_STYLE_BetaV0.4.safetensors", "SDXL_FILM_PHOTOGRAPHY_STYLE_BetaV0.4.safetensors",
0.25 0.25
], ],
[ [
true,
"None", "None",
1.0 1.0
], ],
[ [
true,
"None", "None",
1.0 1.0
], ],
[ [
true,
"None", "None",
1.0 1.0
], ],
[ [
true,
"None", "None",
1.0 1.0
] ]
+7 -1
View File
@@ -4,22 +4,27 @@
"default_refiner_switch": 0.75, "default_refiner_switch": 0.75,
"default_loras": [ "default_loras": [
[ [
true,
"sd_xl_offset_example-lora_1.0.safetensors", "sd_xl_offset_example-lora_1.0.safetensors",
0.5 0.5
], ],
[ [
true,
"None", "None",
1.0 1.0
], ],
[ [
true,
"None", "None",
1.0 1.0
], ],
[ [
true,
"None", "None",
1.0 1.0
], ],
[ [
true,
"None", "None",
1.0 1.0
] ]
@@ -43,5 +48,6 @@
"embeddings_downloads": {}, "embeddings_downloads": {},
"lora_downloads": { "lora_downloads": {
"sd_xl_offset_example-lora_1.0.safetensors": "https://huggingface.co/stabilityai/stable-diffusion-xl-base-1.0/resolve/main/sd_xl_offset_example-lora_1.0.safetensors" "sd_xl_offset_example-lora_1.0.safetensors": "https://huggingface.co/stabilityai/stable-diffusion-xl-base-1.0/resolve/main/sd_xl_offset_example-lora_1.0.safetensors"
} },
"previous_default_models": []
} }
+19 -6
View File
@@ -115,16 +115,18 @@ See also the common problems and troubleshoots [here](troubleshoot.md).
### Colab ### Colab
(Last tested - 2023 Dec 12) (Last tested - 2024 Mar 11)
| Colab | Info | Colab | Info
| --- | --- | | --- | --- |
[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/lllyasviel/Fooocus/blob/main/fooocus_colab.ipynb) | Fooocus Official [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/lllyasviel/Fooocus/blob/main/fooocus_colab.ipynb) | Fooocus Official
In Colab, you can modify the last line to `!python entry_with_update.py --share` or `!python entry_with_update.py --preset anime --share` or `!python entry_with_update.py --preset realistic --share` for Fooocus Default/Anime/Realistic Edition. In Colab, you can modify the last line to `!python entry_with_update.py --share --always-high-vram` or `!python entry_with_update.py --share --always-high-vram --preset anime` or `!python entry_with_update.py --share --always-high-vram --preset realistic` for Fooocus Default/Anime/Realistic Edition.
Note that this Colab will disable refiner by default because Colab free's resources are relatively limited (and some "big" features like image prompt may cause free-tier Colab to disconnect). We make sure that basic text-to-image is always working on free-tier Colab. Note that this Colab will disable refiner by default because Colab free's resources are relatively limited (and some "big" features like image prompt may cause free-tier Colab to disconnect). We make sure that basic text-to-image is always working on free-tier Colab.
Using `--always-high-vram` shifts resource allocation from RAM to VRAM and achieves the overall best balance between performance, flexibility and stability on the default T4 instance.
Thanks to [camenduru](https://github.com/camenduru)! Thanks to [camenduru](https://github.com/camenduru)!
### Linux (Using Anaconda) ### Linux (Using Anaconda)
@@ -202,7 +204,7 @@ AMD is not intensively tested, however. The AMD support is in beta.
Use `python entry_with_update.py --preset anime` or `python entry_with_update.py --preset realistic` for Fooocus Anime/Realistic Edition. Use `python entry_with_update.py --preset anime` or `python entry_with_update.py --preset realistic` for Fooocus Anime/Realistic Edition.
### Windows(AMD GPUs) ### Windows (AMD GPUs)
Note that the [minimal requirement](#minimal-requirement) for different platforms is different. Note that the [minimal requirement](#minimal-requirement) for different platforms is different.
@@ -237,6 +239,10 @@ You can install Fooocus on Apple Mac silicon (M1 or M2) with macOS 'Catalina' or
Use `python entry_with_update.py --preset anime` or `python entry_with_update.py --preset realistic` for Fooocus Anime/Realistic Edition. Use `python entry_with_update.py --preset anime` or `python entry_with_update.py --preset realistic` for Fooocus Anime/Realistic Edition.
### Docker
See [docker.md](docker.md)
### Download Previous Version ### Download Previous Version
See the guidelines [here](https://github.com/lllyasviel/Fooocus/discussions/1405). See the guidelines [here](https://github.com/lllyasviel/Fooocus/discussions/1405).
@@ -281,14 +287,21 @@ Given different goals, the default models and configs of Fooocus are different:
Note that the download is **automatic** - you do not need to do anything if the internet connection is okay. However, you can download them manually if you (or move them from somewhere else) have your own preparation. Note that the download is **automatic** - you do not need to do anything if the internet connection is okay. However, you can download them manually if you (or move them from somewhere else) have your own preparation.
## UI Access and Authentication
In addition to running on localhost, Fooocus can also expose its UI in two ways:
* Local UI listener: use `--listen` (specify port e.g. with `--port 8888`).
* API access: use `--share` (registers an endpoint at `.gradio.live`).
In both ways the access is unauthenticated by default. You can add basic authentication by creating a file called `auth.json` in the main directory, which contains a list of JSON objects with the keys `user` and `pass` (see example in [auth-example.json](./auth-example.json)).
## List of "Hidden" Tricks ## List of "Hidden" Tricks
<a name="tech_list"></a> <a name="tech_list"></a>
The below things are already inside the software, and **users do not need to do anything about these**. The below things are already inside the software, and **users do not need to do anything about these**.
1. GPT2-based [prompt expansion as a dynamic style "Fooocus V2".](https://github.com/lllyasviel/Fooocus/discussions/117#raw) (similar to Midjourney's hidden pre-processsing and "raw" mode, or the LeonardoAI's Prompt Magic). 1. GPT2-based [prompt expansion as a dynamic style "Fooocus V2".](https://github.com/lllyasviel/Fooocus/discussions/117#raw) (similar to Midjourney's hidden pre-processing and "raw" mode, or the LeonardoAI's Prompt Magic).
2. Native refiner swap inside one single k-sampler. The advantage is that the refiner model can now reuse the base model's momentum (or ODE's history parameters) collected from k-sampling to achieve more coherent sampling. In Automatic1111's high-res fix and ComfyUI's node system, the base model and refiner use two independent k-samplers, which means the momentum is largely wasted, and the sampling continuity is broken. Fooocus uses its own advanced k-diffusion sampling that ensures seamless, native, and continuous swap in a refiner setup. (Update Aug 13: Actually, I discussed this with Automatic1111 several days ago, and it seems that the “native refiner swap inside one single k-sampler” is [merged]( https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/12371) into the dev branch of webui. Great!) 2. Native refiner swap inside one single k-sampler. The advantage is that the refiner model can now reuse the base model's momentum (or ODE's history parameters) collected from k-sampling to achieve more coherent sampling. In Automatic1111's high-res fix and ComfyUI's node system, the base model and refiner use two independent k-samplers, which means the momentum is largely wasted, and the sampling continuity is broken. Fooocus uses its own advanced k-diffusion sampling that ensures seamless, native, and continuous swap in a refiner setup. (Update Aug 13: Actually, I discussed this with Automatic1111 several days ago, and it seems that the “native refiner swap inside one single k-sampler” is [merged]( https://github.com/AUTOMATIC1111/stable-diffusion-webui/pull/12371) into the dev branch of webui. Great!)
3. Negative ADM guidance. Because the highest resolution level of XL Base does not have cross attentions, the positive and negative signals for XL's highest resolution level cannot receive enough contrasts during the CFG sampling, causing the results to look a bit plastic or overly smooth in certain cases. Fortunately, since the XL's highest resolution level is still conditioned on image aspect ratios (ADM), we can modify the adm on the positive/negative side to compensate for the lack of CFG contrast in the highest resolution level. (Update Aug 16, the IOS App [Drawing Things](https://apps.apple.com/us/app/draw-things-ai-generation/id6444050820) will support Negative ADM Guidance. Great!) 3. Negative ADM guidance. Because the highest resolution level of XL Base does not have cross attentions, the positive and negative signals for XL's highest resolution level cannot receive enough contrasts during the CFG sampling, causing the results to look a bit plastic or overly smooth in certain cases. Fortunately, since the XL's highest resolution level is still conditioned on image aspect ratios (ADM), we can modify the adm on the positive/negative side to compensate for the lack of CFG contrast in the highest resolution level. (Update Aug 16, the IOS App [Draw Things](https://apps.apple.com/us/app/draw-things-ai-generation/id6444050820) will support Negative ADM Guidance. Great!)
4. We implemented a carefully tuned variation of Section 5.1 of ["Improving Sample Quality of Diffusion Models Using Self-Attention Guidance"](https://arxiv.org/pdf/2210.00939.pdf). The weight is set to very low, but this is Fooocus's final guarantee to make sure that the XL will never yield an overly smooth or plastic appearance (examples [here](https://github.com/lllyasviel/Fooocus/discussions/117#sharpness)). This can almost eliminate all cases for which XL still occasionally produces overly smooth results, even with negative ADM guidance. (Update 2023 Aug 18, the Gaussian kernel of SAG is changed to an anisotropic kernel for better structure preservation and fewer artifacts.) 4. We implemented a carefully tuned variation of Section 5.1 of ["Improving Sample Quality of Diffusion Models Using Self-Attention Guidance"](https://arxiv.org/pdf/2210.00939.pdf). The weight is set to very low, but this is Fooocus's final guarantee to make sure that the XL will never yield an overly smooth or plastic appearance (examples [here](https://github.com/lllyasviel/Fooocus/discussions/117#sharpness)). This can almost eliminate all cases for which XL still occasionally produces overly smooth results, even with negative ADM guidance. (Update 2023 Aug 18, the Gaussian kernel of SAG is changed to an anisotropic kernel for better structure preservation and fewer artifacts.)
5. We modified the style templates a bit and added the "cinematic-default". 5. We modified the style templates a bit and added the "cinematic-default".
6. We tested the "sd_xl_offset_example-lora_1.0.safetensors" and it seems that when the lora weight is below 0.5, the results are always better than XL without lora. 6. We tested the "sd_xl_offset_example-lora_1.0.safetensors" and it seems that when the lora weight is below 0.5, the results are always better than XL without lora.
@@ -363,7 +376,7 @@ entry_with_update.py [-h] [--listen [IP]] [--port PORT]
[--attention-split | --attention-quad | --attention-pytorch] [--attention-split | --attention-quad | --attention-pytorch]
[--disable-xformers] [--disable-xformers]
[--always-gpu | --always-high-vram | --always-normal-vram | [--always-gpu | --always-high-vram | --always-normal-vram |
--always-low-vram | --always-no-vram | --always-cpu] --always-low-vram | --always-no-vram | --always-cpu [CPU_NUM_THREADS]]
[--always-offload-from-vram] [--disable-server-log] [--always-offload-from-vram] [--disable-server-log]
[--debug-mode] [--is-windows-embedded-python] [--debug-mode] [--is-windows-embedded-python]
[--disable-server-info] [--share] [--preset PRESET] [--disable-server-info] [--share] [--preset PRESET]
+5
View File
@@ -0,0 +1,5 @@
torch==2.0.1
torchvision==0.15.2
torchaudio==2.0.2
torchtext==0.15.2
torchdata==0.6.1
Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

+4
View File
@@ -3,6 +3,10 @@
"name": "Fooocus Enhance", "name": "Fooocus Enhance",
"negative_prompt": "(worst quality, low quality, normal quality, lowres, low details, oversaturated, undersaturated, overexposed, underexposed, grayscale, bw, bad photo, bad photography, bad art:1.4), (watermark, signature, text font, username, error, logo, words, letters, digits, autograph, trademark, name:1.2), (blur, blurry, grainy), morbid, ugly, asymmetrical, mutated malformed, mutilated, poorly lit, bad shadow, draft, cropped, out of frame, cut off, censored, jpeg artifacts, out of focus, glitch, duplicate, (airbrushed, cartoon, anime, semi-realistic, cgi, render, blender, digital art, manga, amateur:1.3), (3D ,3D Game, 3D Game Scene, 3D Character:1.1), (bad hands, bad anatomy, bad body, bad face, bad teeth, bad arms, bad legs, deformities:1.3)" "negative_prompt": "(worst quality, low quality, normal quality, lowres, low details, oversaturated, undersaturated, overexposed, underexposed, grayscale, bw, bad photo, bad photography, bad art:1.4), (watermark, signature, text font, username, error, logo, words, letters, digits, autograph, trademark, name:1.2), (blur, blurry, grainy), morbid, ugly, asymmetrical, mutated malformed, mutilated, poorly lit, bad shadow, draft, cropped, out of frame, cut off, censored, jpeg artifacts, out of focus, glitch, duplicate, (airbrushed, cartoon, anime, semi-realistic, cgi, render, blender, digital art, manga, amateur:1.3), (3D ,3D Game, 3D Game Scene, 3D Character:1.1), (bad hands, bad anatomy, bad body, bad face, bad teeth, bad arms, bad legs, deformities:1.3)"
}, },
{
"name": "Fooocus Semi Realistic",
"negative_prompt": "(worst quality, low quality, normal quality, lowres, low details, oversaturated, undersaturated, overexposed, underexposed, bad photo, bad photography, bad art:1.4), (watermark, signature, text font, username, error, logo, words, letters, digits, autograph, trademark, name:1.2), (blur, blurry, grainy), morbid, ugly, asymmetrical, mutated malformed, mutilated, poorly lit, bad shadow, draft, cropped, out of frame, cut off, censored, jpeg artifacts, out of focus, glitch, duplicate, (bad hands, bad anatomy, bad body, bad face, bad teeth, bad arms, bad legs, deformities:1.3)"
},
{ {
"name": "Fooocus Sharp", "name": "Fooocus Sharp",
"prompt": "cinematic still {prompt} . emotional, harmonious, vignette, 4k epic detailed, shot on kodak, 35mm photo, sharp focus, high budget, cinemascope, moody, epic, gorgeous, film grain, grainy", "prompt": "cinematic still {prompt} . emotional, harmonious, vignette, 4k epic detailed, shot on kodak, 35mm photo, sharp focus, high budget, cinemascope, moody, epic, gorgeous, film grain, grainy",
-1
View File
@@ -1,2 +1 @@
gradio_root = None gradio_root = None
last_stop = None
+19
View File
@@ -1,3 +1,22 @@
# [2.2.1](https://github.com/lllyasviel/Fooocus/releases/tag/2.2.1)
* Fix some small bugs (e.g. image grid, upscale fast 2x, LoRA weight width in Firefox)
* Allow prompt weights in array syntax
* Add steps override and metadata scheme to history log
# [2.2.0](https://github.com/lllyasviel/Fooocus/releases/tag/2.2.0)
* Isolate every image generation to truly allow multi-user usage
* Add array support, changes the main prompt when increasing the image number. Syntax: `[[red, green, blue]] flower`
* Add optional metadata to images, allowing you to regenerate and modify them later with the same parameters
* Now supports native PNG, JPG and WEBP image generation
* Add Docker support
# [2.1.865](https://github.com/lllyasviel/Fooocus/releases/tag/2.1.865)
* Various bugfixes
* Add authentication to --listen
# 2.1.864 # 2.1.864
* New model list. See also discussions. * New model list. See also discussions.
+188 -87
View File
@@ -11,28 +11,35 @@ import modules.async_worker as worker
import modules.constants as constants import modules.constants as constants
import modules.flags as flags import modules.flags as flags
import modules.gradio_hijack as grh import modules.gradio_hijack as grh
import modules.advanced_parameters as advanced_parameters
import modules.style_sorter as style_sorter import modules.style_sorter as style_sorter
import modules.meta_parser import modules.meta_parser
import args_manager import args_manager
import copy import copy
import launch
from modules.sdxl_styles import legal_style_names from modules.sdxl_styles import legal_style_names
from modules.private_logger import get_current_html_path from modules.private_logger import get_current_html_path
from modules.ui_gradio_extensions import reload_javascript from modules.ui_gradio_extensions import reload_javascript
from modules.auth import auth_enabled, check_auth from modules.auth import auth_enabled, check_auth
from modules.util import is_json
def get_task(*args):
args = list(args)
args.pop(0)
def generate_clicked(*args): return worker.AsyncTask(args=args)
def generate_clicked(task: worker.AsyncTask):
import ldm_patched.modules.model_management as model_management import ldm_patched.modules.model_management as model_management
with model_management.interrupt_processing_mutex: with model_management.interrupt_processing_mutex:
model_management.interrupt_processing = False model_management.interrupt_processing = False
# outputs=[progress_html, progress_window, progress_gallery, gallery] # outputs=[progress_html, progress_window, progress_gallery, gallery]
if len(task.args) == 0:
return
execution_start_time = time.perf_counter() execution_start_time = time.perf_counter()
task = worker.AsyncTask(args=list(args))
finished = False finished = False
yield gr.update(visible=True, value=modules.html.make_progress_html(1, 'Waiting for task to start ...')), \ yield gr.update(visible=True, value=modules.html.make_progress_html(1, 'Waiting for task to start ...')), \
@@ -71,6 +78,12 @@ def generate_clicked(*args):
gr.update(visible=True, value=product) gr.update(visible=True, value=product)
finished = True finished = True
# delete Fooocus temp images, only keep gradio temp images
if args_manager.args.disable_image_log:
for filepath in product:
if isinstance(filepath, str) and os.path.exists(filepath):
os.remove(filepath)
execution_time = time.perf_counter() - execution_start_time execution_time = time.perf_counter() - execution_start_time
print(f'Total time: {execution_time:.2f} seconds') print(f'Total time: {execution_time:.2f} seconds')
return return
@@ -83,11 +96,10 @@ title = f'Fooocus {fooocus_version.version}'
if isinstance(args_manager.args.preset, str): if isinstance(args_manager.args.preset, str):
title += ' ' + args_manager.args.preset title += ' ' + args_manager.args.preset
shared.gradio_root = gr.Blocks( shared.gradio_root = gr.Blocks(title=title).queue()
title=title,
css=modules.html.css).queue()
with shared.gradio_root: with shared.gradio_root:
currentTask = gr.State(worker.AsyncTask(args=[]))
with gr.Row(): with gr.Row():
with gr.Column(scale=2): with gr.Column(scale=2):
with gr.Row(): with gr.Row():
@@ -115,21 +127,22 @@ with shared.gradio_root:
skip_button = gr.Button(label="Skip", value="Skip", elem_classes='type_row_half', visible=False) skip_button = gr.Button(label="Skip", value="Skip", elem_classes='type_row_half', visible=False)
stop_button = gr.Button(label="Stop", value="Stop", elem_classes='type_row_half', elem_id='stop_button', visible=False) stop_button = gr.Button(label="Stop", value="Stop", elem_classes='type_row_half', elem_id='stop_button', visible=False)
def stop_clicked(): def stop_clicked(currentTask):
import ldm_patched.modules.model_management as model_management import ldm_patched.modules.model_management as model_management
shared.last_stop = 'stop' currentTask.last_stop = 'stop'
if (currentTask.processing):
model_management.interrupt_current_processing() model_management.interrupt_current_processing()
return [gr.update(interactive=False)] * 2 return currentTask
def skip_clicked(): def skip_clicked(currentTask):
import ldm_patched.modules.model_management as model_management import ldm_patched.modules.model_management as model_management
shared.last_stop = 'skip' currentTask.last_stop = 'skip'
if (currentTask.processing):
model_management.interrupt_current_processing() model_management.interrupt_current_processing()
return return currentTask
stop_button.click(stop_clicked, outputs=[skip_button, stop_button], stop_button.click(stop_clicked, inputs=currentTask, outputs=currentTask, queue=False, show_progress=False, _js='cancelGenerateForever')
queue=False, show_progress=False, _js='cancelGenerateForever') skip_button.click(skip_clicked, inputs=currentTask, outputs=currentTask, queue=False, show_progress=False)
skip_button.click(skip_clicked, queue=False, show_progress=False)
with gr.Row(elem_classes='advanced_check_row'): with gr.Row(elem_classes='advanced_check_row'):
input_image_checkbox = gr.Checkbox(label='Input Image', value=False, container=False, elem_classes='min_check') input_image_checkbox = gr.Checkbox(label='Input Image', value=False, container=False, elem_classes='min_check')
advanced_checkbox = gr.Checkbox(label='Advanced', value=modules.config.default_advanced_checkbox, container=False, elem_classes='min_check') advanced_checkbox = gr.Checkbox(label='Advanced', value=modules.config.default_advanced_checkbox, container=False, elem_classes='min_check')
@@ -150,7 +163,7 @@ with shared.gradio_root:
ip_weights = [] ip_weights = []
ip_ctrls = [] ip_ctrls = []
ip_ad_cols = [] ip_ad_cols = []
for _ in range(4): for _ in range(flags.controlnet_image_count):
with gr.Column(): with gr.Column():
ip_image = grh.Image(label='Image', source='upload', type='numpy', show_label=False, height=300) ip_image = grh.Image(label='Image', source='upload', type='numpy', show_label=False, height=300)
ip_images.append(ip_image) ip_images.append(ip_image)
@@ -208,6 +221,27 @@ with shared.gradio_root:
value=flags.desc_type_photo) value=flags.desc_type_photo)
desc_btn = gr.Button(value='Describe this Image into Prompt') desc_btn = gr.Button(value='Describe this Image into Prompt')
gr.HTML('<a href="https://github.com/lllyasviel/Fooocus/discussions/1363" target="_blank">\U0001F4D4 Document</a>') gr.HTML('<a href="https://github.com/lllyasviel/Fooocus/discussions/1363" target="_blank">\U0001F4D4 Document</a>')
with gr.TabItem(label='Metadata') as load_tab:
with gr.Column():
metadata_input_image = grh.Image(label='Drag any image generated by Fooocus here', source='upload', type='filepath')
metadata_json = gr.JSON(label='Metadata')
metadata_import_button = gr.Button(value='Apply Metadata')
def trigger_metadata_preview(filepath):
parameters, metadata_scheme = modules.meta_parser.read_info_from_image(filepath)
results = {}
if parameters is not None:
results['parameters'] = parameters
if isinstance(metadata_scheme, flags.MetadataScheme):
results['metadata_scheme'] = metadata_scheme.value
return results
metadata_input_image.upload(trigger_metadata_preview, inputs=metadata_input_image,
outputs=metadata_json, queue=False, show_progress=True)
switch_js = "(x) => {if(x){viewer_to_bottom(100);viewer_to_bottom(500);}else{viewer_to_top();} return x;}" switch_js = "(x) => {if(x){viewer_to_bottom(100);viewer_to_bottom(500);}else{viewer_to_top();} return x;}"
down_js = "() => {viewer_to_bottom();}" down_js = "() => {viewer_to_bottom();}"
@@ -223,13 +257,23 @@ with shared.gradio_root:
with gr.Column(scale=1, visible=modules.config.default_advanced_checkbox) as advanced_column: with gr.Column(scale=1, visible=modules.config.default_advanced_checkbox) as advanced_column:
with gr.Tab(label='Setting'): with gr.Tab(label='Setting'):
if not args_manager.args.disable_preset_selection:
preset_selection = gr.Radio(label='Preset',
choices=modules.config.available_presets,
value=args_manager.args.preset if args_manager.args.preset else "initial",
interactive=True)
performance_selection = gr.Radio(label='Performance', performance_selection = gr.Radio(label='Performance',
choices=modules.flags.performance_selections, choices=flags.Performance.list(),
value=modules.config.default_performance) value=modules.config.default_performance)
aspect_ratios_selection = gr.Radio(label='Aspect Ratios', choices=modules.config.available_aspect_ratios, aspect_ratios_selection = gr.Radio(label='Aspect Ratios', choices=modules.config.available_aspect_ratios,
value=modules.config.default_aspect_ratio, info='width × height', value=modules.config.default_aspect_ratio, info='width × height',
elem_classes='aspect_ratios') elem_classes='aspect_ratios')
image_number = gr.Slider(label='Image Number', minimum=1, maximum=modules.config.default_max_image_number, step=1, value=modules.config.default_image_number) image_number = gr.Slider(label='Image Number', minimum=1, maximum=modules.config.default_max_image_number, step=1, value=modules.config.default_image_number)
output_format = gr.Radio(label='Output Format',
choices=flags.OutputFormat.list(),
value=modules.config.default_output_format)
negative_prompt = gr.Textbox(label='Negative Prompt', show_label=True, placeholder="Type prompt here.", negative_prompt = gr.Textbox(label='Negative Prompt', show_label=True, placeholder="Type prompt here.",
info='Describing what you do not want to see.', lines=2, info='Describing what you do not want to see.', lines=2,
elem_id='negative_prompt', elem_id='negative_prompt',
@@ -255,10 +299,16 @@ with shared.gradio_root:
seed_random.change(random_checked, inputs=[seed_random], outputs=[image_seed], seed_random.change(random_checked, inputs=[seed_random], outputs=[image_seed],
queue=False, show_progress=False) queue=False, show_progress=False)
if not args_manager.args.disable_image_log: def update_history_link():
gr.HTML(f'<a href="file={get_current_html_path()}" target="_blank">\U0001F4DA History Log</a>') if args_manager.args.disable_image_log:
return gr.update(value='')
with gr.Tab(label='Style'): return gr.update(value=f'<a href="file={get_current_html_path(output_format)}" target="_blank">\U0001F4DA History Log</a>')
history_link = gr.HTML()
shared.gradio_root.load(update_history_link, outputs=history_link, queue=False, show_progress=False)
with gr.Tab(label='Style', elem_classes=['style_selections_tab']):
style_sorter.try_load_sorted_styles( style_sorter.try_load_sorted_styles(
style_names=legal_style_names, style_names=legal_style_names,
default_selected=modules.config.default_styles) default_selected=modules.config.default_styles)
@@ -311,16 +361,20 @@ with shared.gradio_root:
with gr.Group(): with gr.Group():
lora_ctrls = [] lora_ctrls = []
for i, (n, v) in enumerate(modules.config.default_loras): for i, (enabled, filename, weight) in enumerate(modules.config.default_loras):
with gr.Row(): with gr.Row():
lora_enabled = gr.Checkbox(label='Enable', value=enabled,
elem_classes=['lora_enable', 'min_check'], scale=1)
lora_model = gr.Dropdown(label=f'LoRA {i + 1}', lora_model = gr.Dropdown(label=f'LoRA {i + 1}',
choices=['None'] + modules.config.lora_filenames, value=n) choices=['None'] + modules.config.lora_filenames, value=filename,
lora_weight = gr.Slider(label='Weight', minimum=-2, maximum=2, step=0.01, value=v, elem_classes='lora_model', scale=5)
elem_classes='lora_weight') lora_weight = gr.Slider(label='Weight', minimum=modules.config.default_loras_min_weight,
lora_ctrls += [lora_model, lora_weight] maximum=modules.config.default_loras_max_weight, step=0.01, value=weight,
elem_classes='lora_weight', scale=5)
lora_ctrls += [lora_enabled, lora_model, lora_weight]
with gr.Row(): with gr.Row():
model_refresh = gr.Button(label='Refresh', value='\U0001f504 Refresh All Files', variant='secondary', elem_classes='refresh_button') refresh_files = gr.Button(label='Refresh', value='\U0001f504 Refresh All Files', variant='secondary', elem_classes='refresh_button')
with gr.Tab(label='Advanced'): with gr.Tab(label='Advanced'):
guidance_scale = gr.Slider(label='Guidance Scale', minimum=1.0, maximum=30.0, step=0.01, guidance_scale = gr.Slider(label='Guidance Scale', minimum=1.0, maximum=30.0, step=0.01,
value=modules.config.default_cfg_scale, value=modules.config.default_cfg_scale,
@@ -341,7 +395,7 @@ with shared.gradio_root:
step=0.001, value=0.3, step=0.001, value=0.3,
info='When to end the guidance from positive/negative ADM. ') info='When to end the guidance from positive/negative ADM. ')
refiner_swap_method = gr.Dropdown(label='Refiner swap method', value='joint', refiner_swap_method = gr.Dropdown(label='Refiner swap method', value=flags.refiner_swap_method,
choices=['joint', 'separate', 'vae']) choices=['joint', 'separate', 'vae'])
adaptive_cfg = gr.Slider(label='CFG Mimicking from TSNR', minimum=1.0, maximum=30.0, step=0.01, adaptive_cfg = gr.Slider(label='CFG Mimicking from TSNR', minimum=1.0, maximum=30.0, step=0.01,
@@ -381,6 +435,24 @@ with shared.gradio_root:
info='Set as negative number to disable. For developer debugging.') info='Set as negative number to disable. For developer debugging.')
disable_preview = gr.Checkbox(label='Disable Preview', value=False, disable_preview = gr.Checkbox(label='Disable Preview', value=False,
info='Disable preview during generation.') info='Disable preview during generation.')
disable_intermediate_results = gr.Checkbox(label='Disable Intermediate Results',
value=modules.config.default_performance == flags.Performance.EXTREME_SPEED.value,
interactive=modules.config.default_performance != flags.Performance.EXTREME_SPEED.value,
info='Disable intermediate results during generation, only show final gallery.')
disable_seed_increment = gr.Checkbox(label='Disable seed increment',
info='Disable automatic seed increment when image number is > 1.',
value=False)
read_wildcards_in_order = gr.Checkbox(label="Read wildcards in order", value=False)
if not args_manager.args.disable_metadata:
save_metadata_to_images = gr.Checkbox(label='Save Metadata to Images', value=modules.config.default_save_metadata_to_images,
info='Adds parameters to generated images allowing manual regeneration.')
metadata_scheme = gr.Radio(label='Metadata Scheme', choices=flags.metadata_scheme, value=modules.config.default_metadata_scheme,
info='Image Prompt parameters are not included. Use png and a1111 for compatibility with Civitai.',
visible=modules.config.default_save_metadata_to_images)
save_metadata_to_images.change(lambda x: gr.update(visible=x), inputs=[save_metadata_to_images], outputs=[metadata_scheme],
queue=False, show_progress=False)
with gr.Tab(label='Control'): with gr.Tab(label='Control'):
debugging_cn_preprocessor = gr.Checkbox(label='Debug Preprocessors', value=False, debugging_cn_preprocessor = gr.Checkbox(label='Debug Preprocessors', value=False,
@@ -446,42 +518,72 @@ with shared.gradio_root:
freeu_s2 = gr.Slider(label='S2', minimum=0, maximum=4, step=0.01, value=0.95) freeu_s2 = gr.Slider(label='S2', minimum=0, maximum=4, step=0.01, value=0.95)
freeu_ctrls = [freeu_enabled, freeu_b1, freeu_b2, freeu_s1, freeu_s2] freeu_ctrls = [freeu_enabled, freeu_b1, freeu_b2, freeu_s1, freeu_s2]
adps = [disable_preview, adm_scaler_positive, adm_scaler_negative, adm_scaler_end, adaptive_cfg, sampler_name,
scheduler_name, generate_image_grid, overwrite_step, overwrite_switch, overwrite_width, overwrite_height,
overwrite_vary_strength, overwrite_upscale_strength,
mixing_image_prompt_and_vary_upscale, mixing_image_prompt_and_inpaint,
debugging_cn_preprocessor, skipping_cn_preprocessor, controlnet_softness,
canny_low_threshold, canny_high_threshold, refiner_swap_method]
adps += freeu_ctrls
adps += inpaint_ctrls
def dev_mode_checked(r): def dev_mode_checked(r):
return gr.update(visible=r) return gr.update(visible=r)
dev_mode.change(dev_mode_checked, inputs=[dev_mode], outputs=[dev_tools], dev_mode.change(dev_mode_checked, inputs=[dev_mode], outputs=[dev_tools],
queue=False, show_progress=False) queue=False, show_progress=False)
def model_refresh_clicked(): def refresh_files_clicked():
modules.config.update_all_model_names() modules.config.update_files()
results = [] results = [gr.update(choices=modules.config.model_filenames)]
results += [gr.update(choices=modules.config.model_filenames), gr.update(choices=['None'] + modules.config.model_filenames)] results += [gr.update(choices=['None'] + modules.config.model_filenames)]
for i in range(5): if not args_manager.args.disable_preset_selection:
results += [gr.update(choices=['None'] + modules.config.lora_filenames), gr.update()] results += [gr.update(choices=modules.config.available_presets)]
for i in range(modules.config.default_max_lora_number):
results += [gr.update(interactive=True),
gr.update(choices=['None'] + modules.config.lora_filenames), gr.update()]
return results return results
model_refresh.click(model_refresh_clicked, [], [base_model, refiner_model] + lora_ctrls, refresh_files_output = [base_model, refiner_model]
if not args_manager.args.disable_preset_selection:
refresh_files_output += [preset_selection]
refresh_files.click(refresh_files_clicked, [], refresh_files_output + lora_ctrls,
queue=False, show_progress=False) queue=False, show_progress=False)
performance_selection.change(lambda x: [gr.update(interactive=x != 'Extreme Speed')] * 11 + state_is_generating = gr.State(False)
[gr.update(visible=x != 'Extreme Speed')] * 1,
load_data_outputs = [advanced_checkbox, image_number, prompt, negative_prompt, style_selections,
performance_selection, overwrite_step, overwrite_switch, aspect_ratios_selection,
overwrite_width, overwrite_height, guidance_scale, sharpness, adm_scaler_positive,
adm_scaler_negative, adm_scaler_end, refiner_swap_method, adaptive_cfg, base_model,
refiner_model, refiner_switch, sampler_name, scheduler_name, seed_random, image_seed,
generate_button, load_parameter_button] + freeu_ctrls + lora_ctrls
if not args_manager.args.disable_preset_selection:
def preset_selection_change(preset, is_generating):
preset_content = modules.config.try_get_preset_content(preset) if preset != 'initial' else {}
preset_prepared = modules.meta_parser.parse_meta_from_preset(preset_content)
default_model = preset_prepared.get('base_model')
previous_default_models = preset_prepared.get('previous_default_models', [])
checkpoint_downloads = preset_prepared.get('checkpoint_downloads', {})
embeddings_downloads = preset_prepared.get('embeddings_downloads', {})
lora_downloads = preset_prepared.get('lora_downloads', {})
preset_prepared['base_model'], preset_prepared['lora_downloads'] = launch.download_models(
default_model, previous_default_models, checkpoint_downloads, embeddings_downloads, lora_downloads)
if 'prompt' in preset_prepared and preset_prepared.get('prompt') == '':
del preset_prepared['prompt']
return modules.meta_parser.load_parameter_button_click(json.dumps(preset_prepared), is_generating)
preset_selection.change(preset_selection_change, inputs=[preset_selection, state_is_generating], outputs=load_data_outputs, queue=False, show_progress=True) \
.then(fn=style_sorter.sort_styles, inputs=style_selections, outputs=style_selections, queue=False, show_progress=False) \
performance_selection.change(lambda x: [gr.update(interactive=not flags.Performance.has_restricted_features(x))] * 11 +
[gr.update(visible=not flags.Performance.has_restricted_features(x))] * 1 +
[gr.update(interactive=not flags.Performance.has_restricted_features(x), value=flags.Performance.has_restricted_features(x))] * 1,
inputs=performance_selection, inputs=performance_selection,
outputs=[ outputs=[
guidance_scale, sharpness, adm_scaler_end, adm_scaler_positive, guidance_scale, sharpness, adm_scaler_end, adm_scaler_positive,
adm_scaler_negative, refiner_switch, refiner_model, sampler_name, adm_scaler_negative, refiner_switch, refiner_model, sampler_name,
scheduler_name, adaptive_cfg, refiner_swap_method, negative_prompt scheduler_name, adaptive_cfg, refiner_swap_method, negative_prompt, disable_intermediate_results
], queue=False, show_progress=False) ], queue=False, show_progress=False)
output_format.input(lambda x: gr.update(output_format=x), inputs=output_format)
advanced_checkbox.change(lambda x: gr.update(visible=x), advanced_checkbox, advanced_column, advanced_checkbox.change(lambda x: gr.update(visible=x), advanced_checkbox, advanced_column,
queue=False, show_progress=False) \ queue=False, show_progress=False) \
.then(fn=lambda: None, _js='refresh_grid_delayed', queue=False, show_progress=False) .then(fn=lambda: None, _js='refresh_grid_delayed', queue=False, show_progress=False)
@@ -519,29 +621,36 @@ with shared.gradio_root:
inpaint_strength, inpaint_respective_field inpaint_strength, inpaint_respective_field
], show_progress=False, queue=False) ], show_progress=False, queue=False)
ctrls = [ ctrls = [currentTask, generate_image_grid]
ctrls += [
prompt, negative_prompt, style_selections, prompt, negative_prompt, style_selections,
performance_selection, aspect_ratios_selection, image_number, image_seed, sharpness, guidance_scale performance_selection, aspect_ratios_selection, image_number, output_format, image_seed,
read_wildcards_in_order, sharpness, guidance_scale
] ]
ctrls += [base_model, refiner_model, refiner_switch] + lora_ctrls ctrls += [base_model, refiner_model, refiner_switch] + lora_ctrls
ctrls += [input_image_checkbox, current_tab] ctrls += [input_image_checkbox, current_tab]
ctrls += [uov_method, uov_input_image] ctrls += [uov_method, uov_input_image]
ctrls += [outpaint_selections, inpaint_input_image, inpaint_additional_prompt, inpaint_mask_image] ctrls += [outpaint_selections, inpaint_input_image, inpaint_additional_prompt, inpaint_mask_image]
ctrls += ip_ctrls ctrls += [disable_preview, disable_intermediate_results, disable_seed_increment]
ctrls += [adm_scaler_positive, adm_scaler_negative, adm_scaler_end, adaptive_cfg]
ctrls += [sampler_name, scheduler_name]
ctrls += [overwrite_step, overwrite_switch, overwrite_width, overwrite_height, overwrite_vary_strength]
ctrls += [overwrite_upscale_strength, mixing_image_prompt_and_vary_upscale, mixing_image_prompt_and_inpaint]
ctrls += [debugging_cn_preprocessor, skipping_cn_preprocessor, canny_low_threshold, canny_high_threshold]
ctrls += [refiner_swap_method, controlnet_softness]
ctrls += freeu_ctrls
ctrls += inpaint_ctrls
state_is_generating = gr.State(False) if not args_manager.args.disable_metadata:
ctrls += [save_metadata_to_images, metadata_scheme]
ctrls += ip_ctrls
def parse_meta(raw_prompt_txt, is_generating): def parse_meta(raw_prompt_txt, is_generating):
loaded_json = None loaded_json = None
try: if is_json(raw_prompt_txt):
if '{' in raw_prompt_txt:
if '}' in raw_prompt_txt:
if ':' in raw_prompt_txt:
loaded_json = json.loads(raw_prompt_txt) loaded_json = json.loads(raw_prompt_txt)
assert isinstance(loaded_json, dict)
except:
loaded_json = None
if loaded_json is None: if loaded_json is None:
if is_generating: if is_generating:
@@ -553,39 +662,30 @@ with shared.gradio_root:
prompt.input(parse_meta, inputs=[prompt, state_is_generating], outputs=[prompt, generate_button, load_parameter_button], queue=False, show_progress=False) prompt.input(parse_meta, inputs=[prompt, state_is_generating], outputs=[prompt, generate_button, load_parameter_button], queue=False, show_progress=False)
load_parameter_button.click(modules.meta_parser.load_parameter_button_click, inputs=[prompt, state_is_generating], outputs=[ load_parameter_button.click(modules.meta_parser.load_parameter_button_click, inputs=[prompt, state_is_generating], outputs=load_data_outputs, queue=False, show_progress=False)
advanced_checkbox,
image_number, def trigger_metadata_import(filepath, state_is_generating):
prompt, parameters, metadata_scheme = modules.meta_parser.read_info_from_image(filepath)
negative_prompt, if parameters is None:
style_selections, print('Could not find metadata in the image!')
performance_selection, parsed_parameters = {}
aspect_ratios_selection, else:
overwrite_width, metadata_parser = modules.meta_parser.get_metadata_parser(metadata_scheme)
overwrite_height, parsed_parameters = metadata_parser.parse_json(parameters)
sharpness,
guidance_scale, return modules.meta_parser.load_parameter_button_click(parsed_parameters, state_is_generating)
adm_scaler_positive,
adm_scaler_negative, metadata_import_button.click(trigger_metadata_import, inputs=[metadata_input_image, state_is_generating], outputs=load_data_outputs, queue=False, show_progress=True) \
adm_scaler_end, .then(style_sorter.sort_styles, inputs=style_selections, outputs=style_selections, queue=False, show_progress=False)
base_model,
refiner_model,
refiner_switch,
sampler_name,
scheduler_name,
seed_random,
image_seed,
generate_button,
load_parameter_button
] + lora_ctrls, queue=False, show_progress=False)
generate_button.click(lambda: (gr.update(visible=True, interactive=True), gr.update(visible=True, interactive=True), gr.update(visible=False, interactive=False), [], True), generate_button.click(lambda: (gr.update(visible=True, interactive=True), gr.update(visible=True, interactive=True), gr.update(visible=False, interactive=False), [], True),
outputs=[stop_button, skip_button, generate_button, gallery, state_is_generating]) \ outputs=[stop_button, skip_button, generate_button, gallery, state_is_generating]) \
.then(fn=refresh_seed, inputs=[seed_random, image_seed], outputs=image_seed) \ .then(fn=refresh_seed, inputs=[seed_random, image_seed], outputs=image_seed) \
.then(advanced_parameters.set_all_advanced_parameters, inputs=adps) \ .then(fn=get_task, inputs=ctrls, outputs=currentTask) \
.then(fn=generate_clicked, inputs=ctrls, outputs=[progress_html, progress_window, progress_gallery, gallery]) \ .then(fn=generate_clicked, inputs=currentTask, outputs=[progress_html, progress_window, progress_gallery, gallery]) \
.then(lambda: (gr.update(visible=True, interactive=True), gr.update(visible=False, interactive=False), gr.update(visible=False, interactive=False), False), .then(lambda: (gr.update(visible=True, interactive=True), gr.update(visible=False, interactive=False), gr.update(visible=False, interactive=False), False),
outputs=[generate_button, stop_button, skip_button, state_is_generating]) \ outputs=[generate_button, stop_button, skip_button, state_is_generating]) \
.then(fn=update_history_link, outputs=history_link) \
.then(fn=lambda: None, _js='playNotification').then(fn=lambda: None, _js='refresh_grid_delayed') .then(fn=lambda: None, _js='playNotification').then(fn=lambda: None, _js='refresh_grid_delayed')
for notification_file in ['notification.ogg', 'notification.mp3']: for notification_file in ['notification.ogg', 'notification.mp3']:
@@ -618,6 +718,7 @@ shared.gradio_root.launch(
server_name=args_manager.args.listen, server_name=args_manager.args.listen,
server_port=args_manager.args.port, server_port=args_manager.args.port,
share=args_manager.args.share, share=args_manager.args.share,
auth=check_auth if args_manager.args.share and auth_enabled else None, auth=check_auth if (args_manager.args.share or args_manager.args.listen) and auth_enabled else None,
allowed_paths=[modules.config.path_outputs],
blocked_paths=[constants.AUTH_FILENAME] blocked_paths=[constants.AUTH_FILENAME]
) )
+100
View File
@@ -0,0 +1,100 @@
Alligator
Ant
Antelope
Armadillo
Badger
Bat
Bear
Beaver
Bison
Boar
Bobcat
Bull
Camel
Chameleon
Cheetah
Chicken
Chihuahua
Chimpanzee
Chinchilla
Chipmunk
Komodo Dragon
Cow
Coyote
Crocodile
Crow
Deer
Dinosaur
Dolphin
Donkey
Duck
Eagle
Eel
Elephant
Elk
Emu
Falcon
Ferret
Flamingo
Flying Squirrel
Giraffe
Goose
Guinea pig
Hawk
Hedgehog
Hippopotamus
Horse
Hummingbird
Hyena
Jackal
Jaguar
Jellyfish
Kangaroo
King Cobra
Koala bear
Leopard
Lion
Lizard
Magpie
Marten
Meerkat
Mole
Monkey
Moose
Mouse
Octopus
Okapi
Orangutan
Ostrich
Otter
Owl
Panda
Pangolin
Panther
Penguin
Pig
Porcupine
Possum
Puma
Quokka
Rabbit
Raccoon
Raven
Reindeer
Rhinoceros
Seal
Shark
Sheep
Snail
Snake
Sparrow
Spider
Squirrel
Swallow
Tiger
Walrus
Whale
Wolf
Wombat
Yak
Zebra