Compare commits

..

320 Commits

Author SHA1 Message Date
Mary Hipp
0acf800015 update URL for bulk download placeholder 2023-11-15 08:49:15 -05:00
Mary Hipp
fa031156ab Merge branch 'main' of https://github.com/invoke-ai/InvokeAI 2023-11-15 08:48:33 -05:00
Millun Atluri
b6f356f067 Change stylecheck name from "black" to "ruff" (#5090)
## What type of PR is this? (check all applicable)

- [ ] Refactor
- [X] Feature
- [ ] Bug Fix
- [ ] Optimization
- [ ] Documentation Update
- [ ] Community Node Submission


## Have you discussed this change with the InvokeAI team?
- [ ] Yes
- [X] No, because: it is trivial

      
## Have you updated all relevant documentation?
- [ ] Yes
- [X] No


## Description

After the switch to the "ruff" linter, I noticed that the stylecheck
workflow is still described as "black" in the action logs. This small PR
should fix the issue.
2023-11-15 08:29:41 +11:00
Lincoln Stein
a4f1db7c02 change stylecheck name from "black" to "ruff" 2023-11-14 11:06:10 -05:00
psychedelicious
21206bafcf chore: bump pydantic and fastapi
No breaking changes for us.

Pydantic is working on its own faster JSON parser, `jiter`, and 2.5.0 starts bringing this in. See https://github.com/pydantic/jiter

There are a number of other bugfixes and minor changes in this version of pydantic.

The FastAPI update is mostly internal but let's stay up to date.
2023-11-14 14:34:14 +11:00
Millun Atluri
a047bad391 Revert torch to use cu121 (#5091)
## What type of PR is this? (check all applicable)

- [ ] Refactor
- [ ] Feature
- [ ] Bug Fix
- [X] Optimization
- [ ] Documentation Update
- [ ] Community Node Submission


## Have you discussed this change with the InvokeAI team?
- [X] Yes
- [ ] No, because:

      
## Have you updated all relevant documentation?
- [x] Yes
- [ ] No


## Description


## Related Tickets & Documents

<!--
For pull requests that relate or close an issue, please include them
below. 

For example having the text: "closes #1234" would connect the current
pull
request to issue 1234.  And when we merge the pull request, Github will
automatically close the issue.
-->

- Related Issue #
- Closes #

## QA Instructions, Screenshots, Recordings

<!-- 
Please provide steps on how to test changes, any hardware or 
software specifications as well as any other pertinent information. 
-->

## Added/updated tests?

- [ ] Yes
- [ ] No : _please replace this line with details on why tests
      have not been included_

## [optional] Are there any post deployment tasks we need to perform?
2023-11-14 13:47:51 +11:00
Millun Atluri
909afc266e Update 010_INSTALL_AUTOMATED.md 2023-11-13 20:28:00 -05:00
Millun Atluri
4039dd148d Update 030_INSTALL_CUDA_AND_ROCM.md 2023-11-13 20:28:00 -05:00
Millun Atluri
ea0f8b8791 Update 020_INSTALL_MANUAL.md 2023-11-13 20:28:00 -05:00
Millun Atluri
f412582d60 Update README.md to cu121 2023-11-13 20:28:00 -05:00
Millun Atluri
c5672adb6b Update 070_INSTALL_XFORMERS.md 2023-11-13 20:28:00 -05:00
Millun Atluri
0e5c3a641a Revert torch to use cu121 2023-11-13 20:28:00 -05:00
Millun Atluri
9015e72e1e Update README.md to include M3 (#5092)
## What type of PR is this? (check all applicable)

- [ ] Refactor
- [ ] Feature
- [ ] Bug Fix
- [ ] Optimization
- [x] Documentation Update
- [ ] Community Node Submission


## Have you discussed this change with the InvokeAI team?
- [ ] Yes
- [x] No, because:

      
## Have you updated all relevant documentation?
- [x] Yes
- [ ] No


## Description


## Related Tickets & Documents

<!--
For pull requests that relate or close an issue, please include them
below. 

For example having the text: "closes #1234" would connect the current
pull
request to issue 1234.  And when we merge the pull request, Github will
automatically close the issue.
-->

- Related Issue #
- Closes #

## QA Instructions, Screenshots, Recordings

<!-- 
Please provide steps on how to test changes, any hardware or 
software specifications as well as any other pertinent information. 
-->

## Added/updated tests?

- [ ] Yes
- [x] No : _please replace this line with details on why tests
      have not been included_

## [optional] Are there any post deployment tasks we need to perform?
2023-11-14 12:24:57 +11:00
Millun Atluri
6b05d27c7a Update 040_INSTALL_DOCKER.md 2023-11-14 12:22:46 +11:00
Millun Atluri
19d0673085 Update 010_INSTALL_AUTOMATED.md 2023-11-14 12:22:08 +11:00
Kieran Klaassen
048b4fe7e8 Update README.md to include M3 2023-11-13 19:11:31 -06:00
psychedelicious
e8b83fecff fix(backend): apply clip skip after lora
This handles LoRAs that attempt to modify layers skipped by CLIP Skip.
2023-11-14 11:30:15 +11:00
Lincoln Stein
8883ecb2bf Model Manager Refactor Phase 1 - SQL-based config storage (#5039)
## What type of PR is this? (check all applicable)

- [X] Refactor


## Have you discussed this change with the InvokeAI team?
- [X] Extensively
- [ ] No, because:

      
## Have you updated all relevant documentation?
- [X] Yes
- [ ] No


## Description

As discussed with @psychedelicious and @RyanJDick, this is the first
phase of the model manager refactor. In this phase, I've added support
for storing model configuration information the `invokeai.db` SQL3
database. All the code is separate from the original model manager, so
for the time being the frontend is still using the original YAML-based
configuration, so the web app still works.

To keep things clean, I've added a new FastAPI route called
`model_records` which can add, update, retrieve and delete model
records.

The architecture is described in the first section of
`docs/contributing/MODEL_MANAGER.md`.

## QA Instructions, Screenshots, Recordings

There is a pytest for the model sql storage backend in
`tests/backend/model_manager_2/test_model_storage_sql.py`.

To populate `invokeai.db` with models from your current `models.yaml`,
do the following:

1. Stop the running server
2. Back up `invokeai.db`
3. Run `pip install -e .` to install the command used in the next step.
4. Run `invokeai-migrate-models-to-db`

This will iterate through `models.yaml` and create equivalent database
entries in the `model_config` table of `invokeai.db`. Only the models
named in the yaml file will be migrated, so anything that is autoloaded
will be ignored.

Note that in order to get the `model_records` router to be recognized by
the swagger API, I had to rebuild the frontend. Not sure why this was
necessary and would appreciate a pointer on a less radical way to do
this.

## Added/updated tests?

- [X] Yes
- [ ] No
2023-11-13 18:59:25 -05:00
Lincoln Stein
2f97f1d6d5 Merge branch 'main' into refactor/model-manager-2 2023-11-13 18:21:16 -05:00
Lincoln Stein
73d6cc824b Update Pytorch to ~2.1.0 in the installer script (#5089)
## What type of PR is this? (check all applicable)

- [ ] Refactor
- [ ] Feature
- [X] Bug Fix
- [X] Optimization
- [ ] Documentation Update
- [ ] Community Node Submission


## Have you discussed this change with the InvokeAI team?
- [ ] Yes
- [X] No, because it's required

      
## Have you updated all relevant documentation?
- [ ] Yes
- [X] No, not necessary


## Description

We use Pytorch ~2.1.0 as a dependency for InvokeAI, but the installer
still installs 2.0.1 first until Invoke AIs dependencies kick in which
causes it to get deleted anyway and replaced with 2.1.0. This is
unnecessary and probably not wanted.

Fixed the dependencies for the installation script to install Pytorch
~2.1.0 to begin with.

P.s. Is there any reason why "torchmetrics==0.11.4" is pinned? What is
the reason for that? Does that change with Pytorch 2.1? It seems to work
since we use it already. It would be nice to know the reason.

Greetings

## Related Tickets & Documents

<!--
For pull requests that relate or close an issue, please include them
below. 

For example having the text: "closes #1234" would connect the current
pull
request to issue 1234.  And when we merge the pull request, Github will
automatically close the issue.
-->

- Related Issue #
- Closes #

## QA Instructions, Screenshots, Recordings

<!-- 
Please provide steps on how to test changes, any hardware or 
software specifications as well as any other pertinent information. 
-->

## Added/updated tests?

- [ ] Yes
- [ ] No : _please replace this line with details on why tests
      have not been included_

## [optional] Are there any post deployment tasks we need to perform?
2023-11-13 18:20:36 -05:00
Lincoln Stein
acc0a29dca fixed ruff formatting issues 2023-11-13 18:15:17 -05:00
Lincoln Stein
38c1436f02 resolve conflicts; blackify 2023-11-13 18:12:45 -05:00
Lincoln Stein
efbdb75568 implement psychedelicious recommendations as of 13 November 2023-11-13 17:05:01 -05:00
psychedelicious
8929495aeb fix(test): remove unused assignment to value 2023-11-14 08:08:23 +11:00
psychedelicious
428f0b265f feat(api): add log stmt to update_model_record route 2023-11-14 08:06:35 +11:00
psychedelicious
7daee41ad2 fix(api): remove unused ModelsListValidator 2023-11-14 08:01:44 +11:00
psychedelicious
7cdd7b6ad7 feat(api): simplifiy list_model_records handler 2023-11-14 08:00:21 +11:00
psychedelicious
bc64cde6f9 chore: ruff lint 2023-11-14 07:57:07 +11:00
psychedelicious
4465f97cdf Merge branch 'main' into refactor/model-manager-2 2023-11-14 07:51:57 +11:00
Wubbbi
fface2cda7 Update torch to ~2.1.0 in the installer 2023-11-13 17:30:51 +01:00
blessedcoolant
7fcb8959fb chore(ui): cleanup (#5084)
## What type of PR is this? (check all applicable)

- [ ] Refactor
- [ ] Feature
- [ ] Bug Fix
- [x] Optimization
- [ ] Documentation Update
- [ ] Community Node Submission

## Description

Bit of a cleanup. 

[chore(ui): delete unused
files](5eaea9dd64)

[feat(ui): add eslint rule
react/jsx-no-bind](3a0ec635c9)

This rule enforces no arrow functions in component props. In practice,
it means all functions passed as component props must be wrapped in
`useCallback()`.

This is a performance optimization to prevent unnecessary rerenders.

The rule is added and all violations have been fixed, whew!

[chore(ui): move useCopyImageToClipboard to
common/hooks/](f2d26a3a3c)

[chore(ui): move MM components & store to
features/](bb52861896)

Somehow they had ended up in `features/ui/tabs` which isn't right

## QA Instructions, Screenshots, Recordings

UI should still work.

It builds successfully, and I tested things out - looks good to me.
2023-11-13 13:22:41 +05:30
psychedelicious
dcf0dc4274 Merge branch 'main' into chore/ui/cleanup 2023-11-13 16:33:08 +11:00
psychedelicious
bb52861896 chore(ui): move MM components & store to features/
Somehow they had ended up in `features/ui/tabs` which isn't right
2023-11-13 16:32:03 +11:00
psychedelicious
f2d26a3a3c chore(ui): move useCopyImageToClipboard to common/hooks/ 2023-11-13 16:23:46 +11:00
psychedelicious
04d8f2dfea fix(backend): fix controlnet zip len
Do not use `strict=True` when scaling controlnet conditioning.

When using `guess_mode` (e.g. `more_control` or `more_prompt`), `down_block_res_samples` and `scales` are zipped.

These two objects are of different lengths, so using zip's strict mode raises an error.

In testing, `len(scales) === len(down_block_res_samples) + 1`.

It appears this behaviour is intentional, as the final "extra" item in `scales` is used immediately afterwards.
2023-11-13 15:45:03 +11:00
Millun Atluri
355d4cf4e2 Update Accelerate to 0.24.X (#5075)
## What type of PR is this? (check all applicable)

- [ ] Refactor
- [ ] Feature
- [ ] Bug Fix
- [X] Optimization
- [ ] Documentation Update
- [ ] Community Node Submission


## Have you discussed this change with the InvokeAI team?
- [ ] Yes
- [X] No, because: This is just housekeeping

      
## Have you updated all relevant documentation?
- [ ] Yes
- [X] No, not needed


## Description

Update Accelerate to the most recent version. No breaking changes.
Tested for 1 week in productive use now.

## Related Tickets & Documents

<!--
For pull requests that relate or close an issue, please include them
below. 

For example having the text: "closes #1234" would connect the current
pull
request to issue 1234.  And when we merge the pull request, Github will
automatically close the issue.
-->

- Related Issue #
- Closes #

## QA Instructions, Screenshots, Recordings

<!-- 
Please provide steps on how to test changes, any hardware or 
software specifications as well as any other pertinent information. 
-->

## Added/updated tests?

- [ ] Yes
- [ ] No : _please replace this line with details on why tests
      have not been included_

## [optional] Are there any post deployment tasks we need to perform?
2023-11-13 14:20:05 +11:00
Millun Atluri
a3a828779a Merge branch 'main' into update-accelerate 2023-11-13 14:10:53 +11:00
Lincoln Stein
8c71ff37ae Update config.py
Co-authored-by: psychedelicious <4822129+psychedelicious@users.noreply.github.com>
2023-11-12 19:03:39 -05:00
psychedelicious
ddb65e6034 Merge branch 'main' into chore/ui/cleanup 2023-11-13 10:53:04 +11:00
Millun Atluri
8366cd2a00 feat: use ruff for lint & format (#5070)
## What type of PR is this? (check all applicable)

- [ ] Refactor
- [x] Feature
- [x] Bug Fix
- [x] Optimization
- [ ] Documentation Update
- [ ] Community Node Submission

## Description

This PR introduces [`ruff`](https://github.com/astral-sh/ruff) as the
only linter and formatter needed for the project. It is really fast.
Like, alarmingly fast.

It is a drop-in replacement for flake8, isort, black, and much more.
I've configured it similarly to our existing config.

Note: we had enabled a number of flake8 plugins but didn't have the
packages themselves installed, so they did nothing. Ruff used the
existing config, and found a good number of changes needed to adhere to
those flake8 plugins. I've resolved all violations.

### Code changes

- many
[flake8-comprehensions](https://docs.astral.sh/ruff/rules/#flake8-comprehensions-c4)
violations, almost all auto-fixed
- a good handful of
[flake8-bugbear](https://docs.astral.sh/ruff/rules/#flake8-bugbear-b)
violations
- handful of
[pycodestyle](https://docs.astral.sh/ruff/rules/#pycodestyle-e-w)
violations
- some formatting

### Developer Experience

[Ruff integrates with most
editors](https://docs.astral.sh/ruff/integrations/):
- Official VSCode extension
- `ruff-lsp` python package allows it to integrate with any LSP-capable
editor (vim, emacs, etc)
- Can be configured as an external tool in PyCharm

### Github Actions

I've updated the `style-checks` action to use ruff, and deleted the
`pyflakes` action.

## Related Tickets & Documents

<!--
For pull requests that relate or close an issue, please include them
below. 

For example having the text: "closes #1234" would connect the current
pull
request to issue 1234.  And when we merge the pull request, Github will
automatically close the issue.
-->

- Closes #5066 

## QA Instructions, Screenshots, Recordings

Have a poke around, and run the app. There were some logic changes but
it was all pretty straightforward.

~~Not sure how to best test the changed github action.~~ Looks like it
just used the action from this PR, that's kinda unexpected but OK.

<!-- 
Please provide steps on how to test changes, any hardware or 
software specifications as well as any other pertinent information. 
-->

## Added/updated tests?

- [ ] Yes
- [ ] No : _please replace this line with details on why tests
      have not been included_

## [optional] Are there any post deployment tasks we need to perform?
2023-11-13 10:41:43 +11:00
Millun Atluri
ab1ec3720a Merge branch 'main' into feat/ruff 2023-11-13 10:32:23 +11:00
psychedelicious
3a0ec635c9 feat(ui): add eslint rule react/jsx-no-bind
This rule enforces no arrow functions in component props. In practice, it means all functions passed as component props must be wrapped in `useCallback()`.

This is a performance optimization to prevent unnecessary rerenders.

The rule is added and all violations have been fixed, whew!
2023-11-13 10:01:14 +11:00
Lincoln Stein
8afe517204 add note about discriminated union and Body() issue; blackified 2023-11-12 16:50:05 -05:00
psychedelicious
5eaea9dd64 chore(ui): delete unused files 2023-11-13 08:43:27 +11:00
Stefan Tobler
71e298b722 Feat (ui): Add VAE Model to Recall Parameters (#5073)
* adding VAE recall when using all parameters

* adding VAE to the RecallParameters tab in ImageMetadataActions

* checking for nil vae and casting to null if undefined

* adding default VAE to recall actions list if VAE is nullish

* fix(ui): use `lodash-es` for tree-shakeable imports

---------

Co-authored-by: psychedelicious <4822129+psychedelicious@users.noreply.github.com>
2023-11-12 21:19:12 +00:00
Rohinish
89a039460d feat(ui): add number inputs for canvas brush color picker (#5067)
* drop-down for the color picker

* fixed the bug in alpha value

* designing done

---------

Co-authored-by: psychedelicious <4822129+psychedelicious@users.noreply.github.com>
2023-11-12 21:07:26 +00:00
psychedelicious
a342e64772 Merge branch 'main' into feat/ruff 2023-11-13 07:54:06 +11:00
Lincoln Stein
ef8dcf5fae blackify 2023-11-12 14:20:32 -05:00
Riccardo Giovanetti
90a038c685 translationBot(ui): update translation (Italian)
Currently translated at 97.7% (1200 of 1228 strings)

Co-authored-by: Riccardo Giovanetti <riccardo.giovanetti@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/invokeai/web-ui/it/
Translation: InvokeAI/Web UI
2023-11-12 20:24:04 +11:00
Lincoln Stein
024a156114 isort 2023-11-11 13:58:36 -05:00
Lincoln Stein
7ea2a135f1 remove dangling import 2023-11-11 12:24:58 -05:00
Lincoln Stein
af2264b6eb implement workaround for FastAPI and discriminated unions in Body parameter 2023-11-11 12:22:38 -05:00
Wubbbi
41bf9ec4a3 Update Accelerate to 0.24.X 2023-11-11 09:46:23 +01:00
psychedelicious
520ccdb0a9 Merge branch 'main' into feat/ruff 2023-11-11 15:07:35 +11:00
Lincoln Stein
2b36565e9e awkward workaround for double-Annotated in model_record route 2023-11-10 21:32:44 -05:00
Lincoln Stein
f2c3b7c317 Merge branch 'refactor/model-manager-2' of github.com:invoke-ai/InvokeAI into refactor/model-manager-2 2023-11-10 19:47:01 -05:00
Lincoln Stein
67751a01ab remove unused import 2023-11-10 19:25:05 -05:00
Lincoln Stein
cb8cdefd59 Merge branch 'main' into refactor/model-manager-2 2023-11-10 19:24:19 -05:00
Lincoln Stein
f1c846ba5c blackify 2023-11-10 19:14:29 -05:00
Lincoln Stein
3a6ba236f5 replace _class_map in ModelConfigFactory with a nested discriminated union 2023-11-10 19:14:15 -05:00
Paul Curry
1c7ea57492 feat (ui, generation): High Resolution Fix- added automatic resolution toggle and replaced latent upscale with two improved methods (#4905)
* working

* added selector for method

* refactoring graph

* added ersgan method

* fixing yarn build

* add tooltips

* a conjuction

* rephrase

* removed manual sliders, set HRF to calculate dimensions automatically to match 512^2 pixels

* working

* working

* working

* fixed tooltip

* add hrf to use all parameters

* adding hrf method to parameters

* working on parameter recall

* working on parameter recall

* cleaning

* fix(ui): fix unnecessary casts in addHrfToGraph

* chore(ui): use camelCase in addHrfToGraph

* fix(ui): do not add HRF metadata unless HRF is added to graph

* fix(ui): remove unused imports in addHrfToGraph

* feat(ui): do not hide HRF params when disabled, only disable them

* fix(ui): remove unused vars in addHrfToGraph

* feat(ui): default HRF str to 0.35, method ESRGAN

* fix(ui): use isValidBoolean to check hrfEnabled param

* fix(nodes): update CoreMetadataInvocation fields for HRF

* feat(ui): set hrf strength default to 0.45

* fix(ui): set default hrf strength in configSlice

* feat(ui): use translations for HRF features

---------

Co-authored-by: psychedelicious <4822129+psychedelicious@users.noreply.github.com>
2023-11-11 00:11:46 +00:00
psychedelicious
6494e8e551 chore: ruff format 2023-11-11 10:55:40 +11:00
psychedelicious
513fceac82 chore: ruff check - fix pycodestyle 2023-11-11 10:55:33 +11:00
psychedelicious
99a8ebe3a0 chore: ruff check - fix flake8-bugbear 2023-11-11 10:55:28 +11:00
psychedelicious
3a136420d5 chore: ruff check - fix flake8-comprensions 2023-11-11 10:55:23 +11:00
Lincoln Stein
bd56e9bc81 remove cruft code from router 2023-11-10 18:49:25 -05:00
psychedelicious
43f2398e14 feat: use ruff's github output format for action 2023-11-11 10:42:27 +11:00
psychedelicious
d0cf98d7f6 feat: add ruff-lsp to support most editors 2023-11-11 10:42:27 +11:00
psychedelicious
8111dd6cc5 feat: remove pyflakes gh action
ruff supersedes it
2023-11-11 10:42:27 +11:00
psychedelicious
99e4b87fae feat: use ruff in GH style-checks action 2023-11-11 10:42:27 +11:00
psychedelicious
884ec0b5df feat: replace isort, flake8 & black with ruff 2023-11-11 10:42:27 +11:00
Millun Atluri
9ccfa34e04 Update installer.py to use cu118 instead of 121 (#5069)
## What type of PR is this? (check all applicable)

- [ ] Refactor
- [ ] Feature
- [X] Bug Fix
- [ ] Optimization
- [ ] Documentation Update
- [ ] Community Node Submission


## Have you discussed this change with the InvokeAI team?
- [ ] Yes
- [ ] No, because:

      
## Have you updated all relevant documentation?
- [ ] Yes
- [ ] No


## Description


## Related Tickets & Documents

<!--
For pull requests that relate or close an issue, please include them
below. 

For example having the text: "closes #1234" would connect the current
pull
request to issue 1234.  And when we merge the pull request, Github will
automatically close the issue.
-->

- Related Issue #
- Closes #

## QA Instructions, Screenshots, Recordings

<!-- 
Please provide steps on how to test changes, any hardware or 
software specifications as well as any other pertinent information. 
-->

## Added/updated tests?

- [ ] Yes
- [ ] No : _please replace this line with details on why tests
      have not been included_

## [optional] Are there any post deployment tasks we need to perform?
2023-11-11 10:40:47 +11:00
Millun Atluri
d5aa74623d Merge branch 'main' into Millu-patch-1 2023-11-11 10:39:06 +11:00
Millun Atluri
d63a614b8b Update Transformers to 4.35 and fix pad_to_multiple_of (#4817)
## What type of PR is this? (check all applicable)

- [ ] Refactor
- [ ] Feature
- [X] Bug Fix
- [X] Optimization
- [ ] Documentation Update
- [ ] Community Node Submission


## Have you discussed this change with the InvokeAI team?
- [X] Yes, with @blessedcoolant 
- [ ] No, because:

      
## Have you updated all relevant documentation?
- [ ] Yes
- [ ] No


## Description

This PR updates Transformers to the most recent version and fixes the
value `pad_to_multiple_of` for `text_encoder.resize_token_embeddings`
which was introduced with
https://github.com/huggingface/transformers/pull/25088 in Transformers
4.32.0.

According to the [Nvidia
Documentation](https://docs.nvidia.com/deeplearning/performance/dl-performance-matrix-multiplication/index.html#requirements-tc),
`Performance is better when equivalent matrix dimensions M, N, and K are
aligned to multiples of 8 bytes (or 64 bytes on A100) for FP16`
This fixes the following error that was popping up before every
invocation starting with Transformers 4.32.0
`You are resizing the embedding layer without providing a
pad_to_multiple_of parameter. This means that the new embedding
dimension will be None. This might induce some performance reduction as
Tensor Cores will not be available. For more details about this, or help
on choosing the correct value for resizing, refer to this guide:
https://docs.nvidia.com/deeplearning/performance/dl-performance-matrix-multiplication/index.html#requirements-tc`

This is my first "real" fix PR, so I hope this is fine. Please inform me
if there is anything wrong with this. I am glad to help.

Have a nice day and thank you!


## Related Tickets & Documents

<!--
For pull requests that relate or close an issue, please include them
below. 

For example having the text: "closes #1234" would connect the current
pull
request to issue 1234.  And when we merge the pull request, Github will
automatically close the issue.
-->

- Related Issue:
https://github.com/huggingface/transformers/issues/26303
- Related Discord discussion:
https://discord.com/channels/1020123559063990373/1154152783579197571
- Closes #

## QA Instructions, Screenshots, Recordings

<!-- 
Please provide steps on how to test changes, any hardware or 
software specifications as well as any other pertinent information. 
-->

## Added/updated tests?

- [ ] Yes
- [ ] No : _please replace this line with details on why tests
      have not been included_

## [optional] Are there any post deployment tasks we need to perform?
2023-11-11 10:38:33 +11:00
Millun Atluri
cbc905a4d6 Update installer.py to use cu118 instead of 121 2023-11-11 10:36:07 +11:00
Lincoln Stein
b55fc2935e resolve conflicts with commits done on github 2023-11-10 18:26:48 -05:00
Lincoln Stein
0544917161 multiple small fixes suggested in reviews from psychedelicious and ryan 2023-11-10 18:25:37 -05:00
Lincoln Stein
1161dfe055 Update invokeai/app/api/routers/model_records.py
Co-authored-by: Ryan Dick <ryanjdick3@gmail.com>
2023-11-10 18:24:55 -05:00
Lincoln Stein
433f347d7e Update invokeai/app/api/routers/model_records.py
Co-authored-by: Ryan Dick <ryanjdick3@gmail.com>
2023-11-10 18:22:54 -05:00
Lincoln Stein
33a412a24f Update invokeai/backend/model_manager/config.py
Co-authored-by: psychedelicious <4822129+psychedelicious@users.noreply.github.com>
2023-11-10 18:21:38 -05:00
Lincoln Stein
9316534d97 Update invokeai/app/services/model_records/model_records_sql.py
Co-authored-by: psychedelicious <4822129+psychedelicious@users.noreply.github.com>
2023-11-10 17:58:15 -05:00
Lincoln Stein
fdaa661245 revert frontend dist files to main 2023-11-10 17:57:18 -05:00
Lincoln Stein
f1c195afb7 Merge branch 'main' into refactor/model-manager-2 2023-11-10 17:54:28 -05:00
Wubbbi
6001d3d71d Change pad_to_multiple_of to be 8 for all cases. Add comment about it's temporary status 2023-11-10 17:51:59 -05:00
Wubbbi
b9f607be56 Update to 4.35.X 2023-11-10 17:51:59 -05:00
Wubbbi
8831d1ee41 Update Documentation 2023-11-10 17:51:59 -05:00
Wubbbi
a0be83e370 Update Transformers to 4.34 and fix pad_to_multiple_of 2023-11-10 17:51:59 -05:00
Lincoln Stein
8702a63197 add support for downloading and installing LCM lora diffusers models 2023-11-10 17:51:30 -05:00
psychedelicious
d7f0a7919f chore(ui): manually update vite to fix security issue in hoisted dep
`postcss` is a hoisted dependency of `vite`.
2023-11-10 06:58:22 -08:00
blessedcoolant
356b5a41a9 wip: Add LCMScheduler 2023-11-10 06:54:36 -08:00
Millun Atluri
e56a6d85a9 Update diffusers to ~=0.23 (#5063)
## What type of PR is this? (check all applicable)

- [ ] Refactor
- [X] Feature
- [ ] Bug Fix
- [ ] Optimization
- [ ] Documentation Update
- [ ] Community Node Submission


## Have you discussed this change with the InvokeAI team?
- [X] Yes
- [ ] No, because:

      
## Have you updated all relevant documentation?
- [X] Yes
- [ ] No


## Description


## Related Tickets & Documents

<!--
For pull requests that relate or close an issue, please include them
below. 

For example having the text: "closes #1234" would connect the current
pull
request to issue 1234.  And when we merge the pull request, Github will
automatically close the issue.
-->

- Related Issue #
- Closes #

## QA Instructions, Screenshots, Recordings

<!-- 
Please provide steps on how to test changes, any hardware or 
software specifications as well as any other pertinent information. 
-->

## Added/updated tests?

- [ ] Yes
- [ ] No : _please replace this line with details on why tests
      have not been included_

## [optional] Are there any post deployment tasks we need to perform?
2023-11-10 12:44:28 +11:00
Millun Atluri
e22a091d76 Update diffusers to ~=0.23 2023-11-10 11:50:50 +11:00
Millun Atluri
141d02939a Upstream diffusers PR was merged, this no longer seems necessary (#5060)
## What type of PR is this? (check all applicable)

- [ ] Refactor
- [ ] Feature
- [x] Bug Fix
- [ ] Optimization
- [ ] Documentation Update
- [ ] Community Node Submission


## Have you discussed this change with the InvokeAI team?
- [x] Yes
- [ ] No, because:

      
## Have you updated all relevant documentation?
- [ ] Yes
- [x] No


## Description


## Related Tickets & Documents

<!--
For pull requests that relate or close an issue, please include them
below. 

For example having the text: "closes #1234" would connect the current
pull
request to issue 1234.  And when we merge the pull request, Github will
automatically close the issue.
-->

- Related Issue #
- Closes #

## QA Instructions, Screenshots, Recordings

<!-- 
Please provide steps on how to test changes, any hardware or 
software specifications as well as any other pertinent information. 
-->

## Added/updated tests?

- [ ] Yes
- [ ] No : _please replace this line with details on why tests
      have not been included_

## [optional] Are there any post deployment tasks we need to perform?
2023-11-10 11:47:21 +11:00
Millun Atluri
5cb372e9d0 Merge branch 'main' into remove-deprecated-sdxl-t2i-hack 2023-11-10 11:33:32 +11:00
psychedelicious
f95fe68753 chore(ui): manually bump deps with security issues 2023-11-10 09:50:00 +11:00
psychedelicious
6d33893844 chore(ui): update all deps 2023-11-10 09:50:00 +11:00
psychedelicious
fc53112d8e chore(ui): remove unused deps 2023-11-10 09:50:00 +11:00
Brandon Rising
41f7aa6ab4 Remove unused import: 2023-11-09 15:06:01 -05:00
Brandon Rising
9bec755198 Upstream diffusers PR was merged, this no longer seems necessary 2023-11-09 15:02:24 -05:00
psychedelicious
2570497d83 fix(installer): fix import of ValidationError
It was being imported from a deprecated module
2023-11-10 06:11:15 +11:00
Surisen
5d735a714d translationBot(ui): update translation (Chinese (Simplified))
Currently translated at 100.0% (1219 of 1219 strings)

Co-authored-by: Surisen <zhonghx0804@outlook.com>
Translate-URL: https://hosted.weblate.org/projects/invokeai/web-ui/zh_Hans/
Translation: InvokeAI/Web UI
2023-11-09 10:54:56 -08:00
psychedelicious
6aa87f973e fix(nodes): create app/shared/ module to prevent circular imports
We have a number of shared classes, objects, and functions that are used in multiple places. This causes circular import issues.

This commit creates a new `app/shared/` module to hold these shared classes, objects, and functions.

Initially, only `FreeUConfig` and `FieldDescriptions` are moved here. This resolves a circular import issue with custom nodes.

Other shared classes, objects, and functions will be moved here in future commits.
2023-11-09 16:41:55 +11:00
Kevin Turner
f793fdf3d4 fix(socketio): leave room on unsubscribe
https://discord.com/channels/1020123559063990373/1049495067846524939/1171976251704086559
2023-11-09 12:12:32 +11:00
Lincoln Stein
3b363d0258 fix flake8 lint check failures 2023-11-08 16:52:46 -05:00
Lincoln Stein
36e0faea6b blackify 2023-11-08 16:47:03 -05:00
Lincoln Stein
927f8a66e6 Merge branch 'main' into refactor/model-manager-2 2023-11-08 16:46:08 -05:00
Lincoln Stein
eebc0e7315 Merge branch 'refactor/model-manager-2' of github.com:invoke-ai/InvokeAI into refactor/model-manager-2 2023-11-08 16:45:29 -05:00
Lincoln Stein
6b173cc66f multiple small stylistic changes requested by reviewers 2023-11-08 16:45:26 -05:00
Lincoln Stein
b4732a7308 Update invokeai/app/services/model_records/model_records_base.py
Co-authored-by: Ryan Dick <ryanjdick3@gmail.com>
2023-11-08 13:50:40 -05:00
Lincoln Stein
344a56327a Update invokeai/app/services/model_records/model_records_base.py
Co-authored-by: Ryan Dick <ryanjdick3@gmail.com>
2023-11-08 13:50:01 -05:00
Fattire
2e404b7cca Fix updater option list numbering
Fix updater option list numbering in invokeai_update.py so that they don't go 1, 2, 2, 3.  The options themselves work fine.
2023-11-07 19:11:25 -08:00
Kent Keirsey
a760bdae9f (fix) update freeU config to be compatible with custom nodes (#5050)
## What type of PR is this? (check all applicable)

- [ ] Refactor
- [ ] Feature
- [X] Bug Fix
- [ ] Optimization
- [ ] Documentation Update
- [ ] Community Node Submission


## Have you discussed this change with the InvokeAI team?
- [X] Yes: @psychedelicious told me to do this :) 
- [ ] No, because:

      
## Have you updated all relevant documentation?
- [ ] Yes
- [ ] No


## Description


## Related Tickets & Documents

<!--
For pull requests that relate or close an issue, please include them
below. 

For example having the text: "closes #1234" would connect the current
pull
request to issue 1234.  And when we merge the pull request, Github will
automatically close the issue.
-->

- Related Issue #
- Closes #

## QA Instructions, Screenshots, Recordings

<!-- 
Please provide steps on how to test changes, any hardware or 
software specifications as well as any other pertinent information. 
-->

## Added/updated tests?

- [ ] Yes
- [ ] No : _please replace this line with details on why tests
      have not been included_

## [optional] Are there any post deployment tasks we need to perform?
2023-11-06 21:43:18 -08:00
Millun Atluri
4cfd55936c run black formatting 2023-11-07 16:06:18 +11:00
Millun Atluri
5c3a27aac6 fixed sorts 2023-11-07 16:03:06 +11:00
Millun Atluri
d573a23090 Moved FreeU Config Import 2023-11-07 15:48:53 +11:00
Millun Atluri
351abd2ca2 Merge branch 'invoke-ai:main' into main 2023-11-07 15:43:04 +11:00
Millun Atluri
9733cd4199 Update xformers to 0.0.22post7 2023-11-06 17:17:03 -08:00
Millun Atluri
9976bc6908 Update installer.py to cu121 2023-11-06 17:17:03 -08:00
Millun Atluri
c68db6e40f Update xformers to ~0.0.22 2023-11-06 17:17:03 -08:00
Millun Atluri
3a50798a52 Update xformers to 0.0.22post7 2023-11-07 12:00:39 +11:00
Millun Atluri
a98426d2c6 Update installer.py to cu121 2023-11-07 11:57:02 +11:00
Millun Atluri
504f426f0a Update xformers to ~0.0.22 2023-11-07 11:53:39 +11:00
Kent Keirsey
840cbc1d39 xformers==0.0.20 (#4881)
I'm not sure if it's correct way of handling things, but correcting this
string to '==0.0.20' fixes xformers install for me - and maybe for
others it will too. Sorry for absolutely incorrect PR.

Please see [this
thread](https://github.com/facebookresearch/xformers/issues/740), this
is the issue I had (trying to install InvokeAI with
Automatic/Manual/StableMatrix way).

With ~=0.0.19 (0.0.22):
```
(InvokeAI) pip install torch torchvision xformers~=0.0.19
Collecting torch
  Obtaining dependency information for torch from edce54779f/torch-2.1.0-cp311-cp311-win_amd64.whl.metadata
  Using cached torch-2.1.0-cp311-cp311-win_amd64.whl.metadata (25 kB)
Collecting torchvision
  Obtaining dependency information for torchvision from ab6f42af83/torchvision-0.16.0-cp311-cp311-win_amd64.whl.metadata
  Using cached torchvision-0.16.0-cp311-cp311-win_amd64.whl.metadata (6.6 kB)
Collecting xformers
  Using cached xformers-0.0.22.post3.tar.gz (3.9 MB)
  Installing build dependencies ... done
  Getting requirements to build wheel ... error
  error: subprocess-exited-with-error

  × Getting requirements to build wheel did not run successfully.
  │ exit code: 1
  ╰─> [20 lines of output]
      Traceback (most recent call last):
        File "C:\Users\Drun\invokeai\.venv\Lib\site-packages\pip\_vendor\pyproject_hooks\_in_process\_in_process.py", line 353, in <module>
          main()
        File "C:\Users\Drun\invokeai\.venv\Lib\site-packages\pip\_vendor\pyproject_hooks\_in_process\_in_process.py", line 335, in main
          json_out['return_val'] = hook(**hook_input['kwargs'])
                                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        File "C:\Users\Drun\invokeai\.venv\Lib\site-packages\pip\_vendor\pyproject_hooks\_in_process\_in_process.py", line 118, in get_requires_for_build_wheel
          return hook(config_settings)
                 ^^^^^^^^^^^^^^^^^^^^^
        File "C:\Users\Drun\AppData\Local\Temp\pip-build-env-rmhvraqj\overlay\Lib\site-packages\setuptools\build_meta.py", line 355, in get_requires_for_build_wheel
          return self._get_build_requires(config_settings, requirements=['wheel'])
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        File "C:\Users\Drun\AppData\Local\Temp\pip-build-env-rmhvraqj\overlay\Lib\site-packages\setuptools\build_meta.py", line 325, in _get_build_requires
          self.run_setup()
        File "C:\Users\Drun\AppData\Local\Temp\pip-build-env-rmhvraqj\overlay\Lib\site-packages\setuptools\build_meta.py", line 507, in run_setup
          super(_BuildMetaLegacyBackend, self).run_setup(setup_script=setup_script)
        File "C:\Users\Drun\AppData\Local\Temp\pip-build-env-rmhvraqj\overlay\Lib\site-packages\setuptools\build_meta.py", line 341, in run_setup
          exec(code, locals())
        File "<string>", line 23, in <module>
      ModuleNotFoundError: No module named 'torch'
```

With 0.0.20:

```
(InvokeAI) pip install torch torchvision xformers==0.0.20
Collecting torch
  Obtaining dependency information for torch from edce54779f/torch-2.1.0-cp311-cp311-win_amd64.whl.metadata
  Using cached torch-2.1.0-cp311-cp311-win_amd64.whl.metadata (25 kB)
Collecting torchvision
  Obtaining dependency information for torchvision from ab6f42af83/torchvision-0.16.0-cp311-cp311-win_amd64.whl.metadata
  Using cached torchvision-0.16.0-cp311-cp311-win_amd64.whl.metadata (6.6 kB)
Collecting xformers==0.0.20
  Obtaining dependency information for xformers==0.0.20 from d4a42f582a/xformers-0.0.20-cp311-cp311-win_amd64.whl.metadata
  Using cached xformers-0.0.20-cp311-cp311-win_amd64.whl.metadata (1.1 kB)
Collecting numpy (from xformers==0.0.20)
  Obtaining dependency information for numpy from 3f826c6d15/numpy-1.26.0-cp311-cp311-win_amd64.whl.metadata
  Using cached numpy-1.26.0-cp311-cp311-win_amd64.whl.metadata (61 kB)
Collecting pyre-extensions==0.0.29 (from xformers==0.0.20)
  Using cached pyre_extensions-0.0.29-py3-none-any.whl (12 kB)
Collecting torch
  Using cached torch-2.0.1-cp311-cp311-win_amd64.whl (172.3 MB)
Collecting filelock (from torch)
  Obtaining dependency information for filelock from 97afbafd9d/filelock-3.12.4-py3-none-any.whl.metadata
  Using cached filelock-3.12.4-py3-none-any.whl.metadata (2.8 kB)
Requirement already satisfied: typing-extensions in c:\users\drun\invokeai\.venv\lib\site-packages (from torch) (4.8.0)
Requirement already satisfied: sympy in c:\users\drun\invokeai\.venv\lib\site-packages (from torch) (1.12)
Collecting networkx (from torch)
  Using cached networkx-3.1-py3-none-any.whl (2.1 MB)
Collecting jinja2 (from torch)
  Using cached Jinja2-3.1.2-py3-none-any.whl (133 kB)
Collecting typing-inspect (from pyre-extensions==0.0.29->xformers==0.0.20)
  Obtaining dependency information for typing-inspect from 107a22063b/typing_inspect-0.9.0-py3-none-any.whl.metadata
  Using cached typing_inspect-0.9.0-py3-none-any.whl.metadata (1.5 kB)
Collecting requests (from torchvision)
  Obtaining dependency information for requests from 0e2d847013/requests-2.31.0-py3-none-any.whl.metadata
  Using cached requests-2.31.0-py3-none-any.whl.metadata (4.6 kB)
INFO: pip is looking at multiple versions of torchvision to determine which version is compatible with other requirements. This could take a while.
Collecting torchvision
  Using cached torchvision-0.15.2-cp311-cp311-win_amd64.whl (1.2 MB)
Collecting pillow!=8.3.*,>=5.3.0 (from torchvision)
  Obtaining dependency information for pillow!=8.3.*,>=5.3.0 from debe992677/Pillow-10.0.1-cp311-cp311-win_amd64.whl.metadata
  Using cached Pillow-10.0.1-cp311-cp311-win_amd64.whl.metadata (9.6 kB)
Collecting MarkupSafe>=2.0 (from jinja2->torch)
  Obtaining dependency information for MarkupSafe>=2.0 from 08b85bc194/MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl.metadata
  Using cached MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl.metadata (3.1 kB)
Collecting charset-normalizer<4,>=2 (from requests->torchvision)
  Obtaining dependency information for charset-normalizer<4,>=2 from 50028bbb26/charset_normalizer-3.3.0-cp311-cp311-win_amd64.whl.metadata
  Using cached charset_normalizer-3.3.0-cp311-cp311-win_amd64.whl.metadata (33 kB)
Collecting idna<4,>=2.5 (from requests->torchvision)
  Using cached idna-3.4-py3-none-any.whl (61 kB)
Collecting urllib3<3,>=1.21.1 (from requests->torchvision)
  Obtaining dependency information for urllib3<3,>=1.21.1 from 9957270221/urllib3-2.0.6-py3-none-any.whl.metadata
  Using cached urllib3-2.0.6-py3-none-any.whl.metadata (6.6 kB)
Collecting certifi>=2017.4.17 (from requests->torchvision)
  Obtaining dependency information for certifi>=2017.4.17 from 2234eab223/certifi-2023.7.22-py3-none-any.whl.metadata
  Using cached certifi-2023.7.22-py3-none-any.whl.metadata (2.2 kB)
Requirement already satisfied: mpmath>=0.19 in c:\users\drun\invokeai\.venv\lib\site-packages (from sympy->torch) (1.3.0)
Collecting mypy-extensions>=0.3.0 (from typing-inspect->pyre-extensions==0.0.29->xformers==0.0.20)
  Using cached mypy_extensions-1.0.0-py3-none-any.whl (4.7 kB)
Using cached xformers-0.0.20-cp311-cp311-win_amd64.whl (97.6 MB)
Using cached Pillow-10.0.1-cp311-cp311-win_amd64.whl (2.5 MB)
Using cached filelock-3.12.4-py3-none-any.whl (11 kB)
Using cached numpy-1.26.0-cp311-cp311-win_amd64.whl (15.8 MB)
Using cached requests-2.31.0-py3-none-any.whl (62 kB)
Using cached certifi-2023.7.22-py3-none-any.whl (158 kB)
Using cached charset_normalizer-3.3.0-cp311-cp311-win_amd64.whl (97 kB)
Using cached MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl (17 kB)
Using cached urllib3-2.0.6-py3-none-any.whl (123 kB)
Using cached typing_inspect-0.9.0-py3-none-any.whl (8.8 kB)
Installing collected packages: urllib3, pillow, numpy, networkx, mypy-extensions, MarkupSafe, idna, filelock, charset-normalizer, certifi, typing-inspect, requests, jinja2, torch, pyre-extensions, xformers, torchvision
Successfully installed MarkupSafe-2.1.3 certifi-2023.7.22 charset-normalizer-3.3.0 filelock-3.12.4 idna-3.4 jinja2-3.1.2 mypy-extensions-1.0.0 networkx-3.1 numpy-1.26.0 pillow-10.0.1 pyre-extensions-0.0.29 requests-2.31.0 torch-2.0.1 torchvision-0.15.2 typing-inspect-0.9.0 urllib3-2.0.6 xformers-0.0.20
```

## What type of PR is this? (check all applicable)

- [ ] Refactor
- [ ] Feature
- [x] Bug Fix
- [ ] Optimization
- [ ] Documentation Update
- [ ] Community Node Submission


## Have you discussed this change with the InvokeAI team?
- [ ] Yes
- [x] No, because: I'm no-brainer. It fixed issue for me, so I did PR.
Who knows?

## Technical details:
Windows 11, Standalone clean and freshly-installed Python 3.11
2023-11-06 16:07:03 -08:00
Millun Atluri
014d6187ab Update pyproject.toml 2023-11-07 10:22:20 +11:00
Millun Atluri
9fb15fae87 Update pyproject.toml 2023-11-07 10:20:16 +11:00
Millun Atluri
a07336a020 Merge branch 'main' into patch-1 2023-11-07 10:17:46 +11:00
Millun Atluri
0718cc2392 Update xformers to 0.0.21 2023-11-07 10:16:44 +11:00
Lincoln Stein
ce22c0fbaa sync pydantic and sql field names; merge routes 2023-11-06 18:08:57 -05:00
Kent Keirsey
935e4632c2 feat(nodes): add freeu support (#4846)
### What type of PR is this? (check all applicable)

- [ ] Refactor
- [x] Feature
- [ ] Bug Fix
- [ ] Optimization
- [ ] Documentation Update
- [ ] Community Node Submission


## Have you discussed this change with the InvokeAI team?
- [ ] Yes
- [x] No, because:

      
## Have you updated all relevant documentation?
- [ ] Yes
- [x] No


## Description

**Note: FreeU is not in the current release of diffusers. Looks like it
will be in release 0.22. This PR needs to wait until that is released.**

[feat(nodes): add freeu
support](15b33ad501)

Add support for FreeU. See:
- https://huggingface.co/docs/diffusers/main/en/using-diffusers/freeu
- https://github.com/ChenyangSi/FreeU

Implementation:
- `ModelPatcher.apply_freeu()` handles the enabling freeu (which is very
simple with diffusers).
- `FreeUConfig` model added to hold the hyperparameters.
- `freeu_config` added as optional sub-field on `UNetField`.
- `FreeUInvocation` added, works like LoRA - chain it to add the FreeU
config to the UNet
- No support for model-dependent presets, this will be a future workflow
editor enhancement

Closes https://github.com/invoke-ai/InvokeAI/issues/4845

## Related Tickets & Documents

<!--
For pull requests that relate or close an issue, please include them
below. 

For example having the text: "closes #1234" would connect the current
pull
request to issue 1234.  And when we merge the pull request, Github will
automatically close the issue.
-->

- Closes #4845 

## QA Instructions, Screenshots, Recordings

You'll need to install diffusers from their github repo before testing
this:
`pip install git+https://github.com/huggingface/diffusers`

1. Create a graph like this:

![image](https://github.com/invoke-ai/InvokeAI/assets/4822129/af17719b-b001-4534-8c4e-883484fd7465)
2. Get a free lunch!

No FreeU:

![image](https://github.com/invoke-ai/InvokeAI/assets/4822129/279d1a69-1577-4c31-ab82-ebf67f65920d)
With FreeU:

![image](https://github.com/invoke-ai/InvokeAI/assets/4822129/c332c778-0b87-4215-8a36-d4822e06f4de)

No FreeU:

![image](https://github.com/invoke-ai/InvokeAI/assets/4822129/ebec097b-ad54-4295-b734-33656738a2cf)
With FreeU:

![image](https://github.com/invoke-ai/InvokeAI/assets/4822129/3423140d-c9ce-4697-9993-d2bb0d0f5634)

No FreeU:

![image](https://github.com/invoke-ai/InvokeAI/assets/4822129/7cb0e39d-aa87-4a48-a3af-b9f47a866814)
With FreeU:

![image](https://github.com/invoke-ai/InvokeAI/assets/4822129/9113d2fe-5bd3-474f-8f33-82cdeb7abf82)
2023-11-06 13:58:32 -08:00
Kent Keirsey
a83d8810c4 Merge branch 'main' into feat/nodes/freeu 2023-11-06 13:47:56 -08:00
Wilson E. Alvarez
76b3f8956b Fix ROCm support in Docker container 2023-11-06 13:47:08 -08:00
Kent Keirsey
ff8a8a1963 Merge branch 'main' into feat/nodes/freeu 2023-11-06 09:04:54 -08:00
blessedcoolant
cb6d0c8851 Re-add feat/mix cnet t2iadapter (#4929)
Reverts invoke-ai/InvokeAI#4923, which was a revert on the premature
merge.

slide to the left. revert, revert.
2023-11-06 22:29:13 +05:30
Kent Keirsey
67f2616d5a Merge branch 'main' into revert-4923-revert-4914-feat/mix-cnet-t2iadapter 2023-11-06 07:34:51 -08:00
Kent Keirsey
f8f1740668 Set Defaults to 1 2023-11-06 07:11:16 -08:00
Kent Keirsey
e66d0f7372 Merge branch 'main' into feat/nodes/freeu 2023-11-06 05:39:58 -08:00
Kent Keirsey
546aaedbe4 Update pyproject.toml 2023-11-06 05:29:17 -08:00
Lincoln Stein
55f8865524 Merge branch 'main' into refactor/model-manager-2 2023-11-05 21:45:26 -05:00
Lincoln Stein
2d051559d1 fix flake8 complaints 2023-11-05 21:45:08 -05:00
Riccardo Giovanetti
7f650d00de translationBot(ui): update translation (Italian)
Currently translated at 97.7% (1191 of 1219 strings)

Co-authored-by: Riccardo Giovanetti <riccardo.giovanetti@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/invokeai/web-ui/it/
Translation: InvokeAI/Web UI
2023-11-05 11:12:33 -08:00
Lincoln Stein
db9cef0092 re-run isort 2023-11-04 23:50:07 -04:00
Lincoln Stein
72c34aea75 added add_model_record and get_model_record to router api 2023-11-04 23:42:44 -04:00
Lincoln Stein
edeea5237b add sql-based model config store and api 2023-11-04 23:03:26 -04:00
Riccardo Giovanetti
4e6b579526 translationBot(ui): update translation (Italian)
Currently translated at 97.6% (1190 of 1219 strings)

Co-authored-by: Riccardo Giovanetti <riccardo.giovanetti@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/invokeai/web-ui/it/
Translation: InvokeAI/Web UI
2023-11-05 12:09:20 +11:00
Alexander Eichhorn
6334c4adf5 translationBot(ui): update translation (German)
Currently translated at 53.8% (657 of 1219 strings)

Co-authored-by: Alexander Eichhorn <pfannkuchensack@einfach-doof.de>
Translate-URL: https://hosted.weblate.org/projects/invokeai/web-ui/de/
Translation: InvokeAI/Web UI
2023-11-05 12:09:20 +11:00
Lincoln Stein
66b2366efc Remove LowRA from Initial Models (#5016)
## What type of PR is this? (check all applicable)

- [ ] Refactor
- [ ] Feature
- [X] Bug Fix
- [ ] Optimization
- [ ] Documentation Update
- [ ] Community Node Submission


## Have you discussed this change with the InvokeAI team?
- [ ] Yes
- [X] No, because:

      
## Have you updated all relevant documentation?
- [ ] Yes
- [X] No


## Description
Removing LowRA from the initial models as it's been deleted from
CivitAI.

## Related Tickets & Documents

https://discord.com/channels/1020123559063990373/1168415065205112872


- Related Issue #
- Closes #

## QA Instructions, Screenshots, Recordings

<!-- 
Please provide steps on how to test changes, any hardware or 
software specifications as well as any other pertinent information. 
-->

## Added/updated tests?

- [ ] Yes
- [ ] No : _please replace this line with details on why tests
      have not been included_

## [optional] Are there any post deployment tasks we need to perform?
2023-11-04 17:14:24 -04:00
Lincoln Stein
e147379aa7 Merge branch 'main' into main 2023-11-04 17:05:01 -04:00
Lincoln Stein
5a821384d3 fix model-not-found error 2023-11-04 08:24:01 -07:00
Ryan Dick
2bbba323c6 Fix model cache gc.collect() condition. (#5036)
See
https://github.com/invoke-ai/InvokeAI/pull/5034#discussion_r1382381733
2023-11-04 09:09:46 -04:00
Ryan Dick
aa02ebf8f5 Fix model cache gc.collect() condition. 2023-11-04 08:52:10 -04:00
Ryan Dick
fb3d0c4b12 Fix bug in model cache reference count checking. 2023-11-03 13:50:40 -07:00
Ryan Dick
8488ab0134 Reduce frequency that we call gc.collect() in the model cache. 2023-11-03 13:50:40 -07:00
Ryan Dick
875231ed3d Add reminder to clean up our model cache clearing logic. 2023-11-03 13:50:40 -07:00
Ryan Dick
43b300498f Remove explicit gc.collect() after transferring models from device to CPU. I'm not sure why this was there in the first place, but it was taking a significant amount of time (up to ~1sec in my tests). 2023-11-03 13:50:40 -07:00
psychedelicious
5b420653f9 feat(ui): show placeholder in refiner collapse instead of hiding it, if no refiner models installed 2023-11-03 14:15:24 +11:00
psychedelicious
3d32ce2b58 fix(ui): hide refiner collapse if refiner not installed 2023-11-03 14:15:24 +11:00
Ryan Dick
e391f3c9a8 Skip torch.nn.Embedding.reset_parameters(...) when loading a text encoder model. 2023-11-02 19:41:33 -07:00
Ryan Dick
6e7a3f0546 (minor) Fix static checks and typo. 2023-11-02 19:20:37 -07:00
Ryan Dick
4a683cc669 Add a app config parameter to control the ModelCache logging behavior. 2023-11-02 19:20:37 -07:00
Ryan Dick
3781e56e57 Add log_memory_usage param to ModelCache. 2023-11-02 19:20:37 -07:00
Ryan Dick
267e709ba2 (minor) Fix int literal typing error. 2023-11-02 19:20:37 -07:00
Ryan Dick
8ff49109a8 Update get_pretty_snapshot_diff(...) to handle None-snapshots. 2023-11-02 19:20:37 -07:00
Ryan Dick
bac2a757e8 Replace deepcopy with a pickle roundtrip in apply_ti(...) to improve speed. 2023-11-02 19:05:24 -07:00
Ryan Dick
a4a7b601a1 Improve LoRA patching speed (#5017)
## What type of PR is this? (check all applicable)
- [ ] Refactor
- [ ] Feature
- [ ] Bug Fix
- [x] Optimization
- [ ] Documentation Update
- [ ] Community Node Submission

## Have you discussed this change with the InvokeAI team?
- [x] Yes
- [ ] No, because:

## Have you updated all relevant documentation?
- [x] Yes
- [ ] No

## Description
Improve LoRA patching speed with the following changes:
- Calculate LoRA layer weights on the same device as the target model.
Prior to this change, weights were always calculated on the CPU. If the
target model is on the GPU, this significantly improves performance.
- Move models to their target devices _before_ applying LoRA patches.
- Improve the ordering of Tensor copy / cast operations.

## QA Instructions, Screenshots, Recordings

Tests:

- [x] Tested with a CUDA GPU, saw savings of ~10secs with 1 LoRA applied
to an SDXL model.
- [x] No regression in CPU-only environment
- [ ] No regression (and possible improvement?) on Mac with MPS.
- [x] Weights get restored correctly after using a LoRA
- [x] Stacking multiple LoRAs

Please hammer away with a variety of LoRAs in case there is some edge
case that I've missed.

## Added/updated tests?

- [x] Yes (Added some minimal unit tests. Definitely would benefit from
more, but it's a step in the right direction.)
- [ ] No
2023-11-02 13:34:10 -04:00
Ryan Dick
fa7f6a6a10 Further tidying of LoRA patching. Revert some changes that didn't end up being important under the constraint that calculations are done on the same device as the model. 2023-11-02 10:03:17 -07:00
Ryan Dick
e92b84955c Add minimal unit tests for ModelPatcher.apply_lora(...) 2023-11-02 10:03:17 -07:00
Ryan Dick
61b17c475a Add TODO note about improving _resolve_lora_key(...). 2023-11-02 10:03:17 -07:00
Ryan Dick
379d68f595 Patch LoRA on device when model is already on device. 2023-11-02 10:03:17 -07:00
Ryan Dick
545c811bf1 Remove device and dtype members from LoRAModelRaw, they can too easily get out-of-sync with the underlying layer states. 2023-11-02 10:03:17 -07:00
Ryan Dick
2ba5b44ec4 Remove unused _lora_forward_hook(...). 2023-11-02 10:03:17 -07:00
Ryan Dick
7f4ce518b7 auto-format lora.py 2023-11-02 10:03:17 -07:00
psychedelicious
6c66adcd90 fix(ui): show collapse labels only if not default value 2023-11-01 14:41:13 +11:00
Millun Atluri
584b513038 Remove LowRA from Initial Models 2023-11-01 08:55:06 +11:00
nemuruibai
94055ae54a translationBot(ui): update translation (Chinese (Simplified))
Currently translated at 99.8% (1215 of 1217 strings)

Co-authored-by: nemuruibai <nemuruibai@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/invokeai/web-ui/zh_Hans/
Translation: InvokeAI/Web UI
2023-11-01 05:31:16 +11:00
Alexander Eichhorn
a79c86b901 translationBot(ui): update translation (German)
Currently translated at 51.7% (630 of 1217 strings)

Co-authored-by: Alexander Eichhorn <pfannkuchensack@einfach-doof.de>
Translate-URL: https://hosted.weblate.org/projects/invokeai/web-ui/de/
Translation: InvokeAI/Web UI
2023-11-01 05:31:16 +11:00
Millun Atluri
ed81d6d533 Update contributingNodes.md 2023-10-31 07:17:14 -07:00
Millun Atluri
63548c5ea7 Update community node installation instructions 2023-10-31 07:17:14 -07:00
Millun Atluri
8481db96ed Updated workflows 2023-10-31 07:17:14 -07:00
Kent Keirsey
bb68175fd0 Add negative IP Adapter support 2023-10-31 14:30:24 +11:00
Millun Atluri
316131f69b Add option to invokeai update script to install latest prerelease (#5008)
## What type of PR is this? (check all applicable)

- [ ] Refactor
- [X] Feature
- [ ] Bug Fix
- [ ] Optimization
- [ ] Documentation Update
- [ ] Community Node Submission


## Have you discussed this change with the InvokeAI team?
- [X] Yes
- [ ] No, because:

      
## Have you updated all relevant documentation?
- [X] Yes
- [ ] No


## Description

This PR gives the user the option of upgrading to the latest PRE-RELEASE
in addition to the default of updating to the latest release. This will
allow users to conveniently try out the latest pre-release for a while
and then back out to the official release if it doesn't work for them.
2023-10-31 08:45:25 +11:00
Lincoln Stein
9721e1382d add option to install latest prerelease 2023-10-30 15:49:27 -04:00
psychedelicious
03a64275c6 fix(db): fix deprecated pydantic .json() method 2023-10-31 04:34:51 +11:00
psychedelicious
55bfadfd0b fix(nodes): fix DenoiseMaskField.masked_latents_name
This optional field needs to have a default of `None`.
2023-10-31 04:18:09 +11:00
Eugene Brodsky
224b09f8fd Enforce Unix line endings in container (#4990)
* (fix) enforce Unix (LF) line endings in docker/ directory

* (fix) update docker docs wrt line endings on Windows

* (fix) static check fixes
2023-10-30 12:34:30 -04:00
Mary Hipp
d19460bda3 Merge branch 'main' of https://github.com/invoke-ai/InvokeAI 2023-10-30 12:22:55 -04:00
Millun Atluri
8dca194e2c Update communityNodes.md (#4999)
Added Average Images node

## What type of PR is this? (check all applicable)

- [ ] Refactor
- [ ] Feature
- [ ] Bug Fix
- [ ] Optimization
- [ ] Documentation Update
- [X] Community Node Submission


## Have you discussed this change with the InvokeAI team?
- [ ] Yes
- [X] No, because:

      
## Have you updated all relevant documentation?
- [X] Yes
- [ ] No


## Description
Added a new community node that averages input images.
2023-10-30 11:15:11 +11:00
Millun Atluri
3a33bd7881 Merge branch 'main' into JPPhoto-average-images 2023-10-30 11:12:40 +11:00
Millun Atluri
d6d0fd313b Prevent prereleases from showing up in updater (#4997)
## What type of PR is this? (check all applicable)

- [ ] Refactor
- [ ] Feature
- [X] Bug Fix
- [ ] Optimization
- [ ] Documentation Update
- [ ] Community Node Submission


## Have you discussed this change with the InvokeAI team?
- [X] Yes
- [ ] No, because:

      
## Have you updated all relevant documentation?
- [X] Yes
- [ ] No


## Description

This PR prevents the invokeai update script from offering prereleases.
2023-10-30 10:21:53 +11:00
psychedelicious
95b90d22b5 Merge branch 'main' into bugfix/dont-release-prereleases 2023-10-30 06:04:24 +11:00
Alexander Eichhorn
249618f6b4 translationBot(ui): update translation (German)
Currently translated at 40.3% (491 of 1217 strings)

Co-authored-by: Alexander Eichhorn <pfannkuchensack@einfach-doof.de>
Translate-URL: https://hosted.weblate.org/projects/invokeai/web-ui/de/
Translation: InvokeAI/Web UI
2023-10-30 05:26:20 +11:00
Fabian Bahl
8109bc5316 translationBot(ui): update translation (German)
Currently translated at 40.3% (491 of 1217 strings)

Co-authored-by: Fabian Bahl <fabian98@bahl-netz.de>
Translate-URL: https://hosted.weblate.org/projects/invokeai/web-ui/de/
Translation: InvokeAI/Web UI
2023-10-30 05:26:20 +11:00
Alexander Eichhorn
015cec197b translationBot(ui): update translation (German)
Currently translated at 37.7% (460 of 1217 strings)

translationBot(ui): update translation (German)

Currently translated at 36.4% (444 of 1217 strings)

translationBot(ui): update translation (German)

Currently translated at 36.0% (439 of 1217 strings)

Co-authored-by: Alexander Eichhorn <pfannkuchensack@einfach-doof.de>
Translate-URL: https://hosted.weblate.org/projects/invokeai/web-ui/de/
Translation: InvokeAI/Web UI
2023-10-30 05:15:49 +11:00
Fabian Bahl
54b0c4f3c9 translationBot(ui): update translation (German)
Currently translated at 37.7% (460 of 1217 strings)

translationBot(ui): update translation (German)

Currently translated at 36.4% (444 of 1217 strings)

translationBot(ui): update translation (German)

Currently translated at 36.4% (443 of 1217 strings)

translationBot(ui): update translation (German)

Currently translated at 36.0% (439 of 1217 strings)

translationBot(ui): update translation (German)

Currently translated at 35.5% (433 of 1217 strings)

Co-authored-by: Fabian Bahl <fabian98@bahl-netz.de>
Translate-URL: https://hosted.weblate.org/projects/invokeai/web-ui/de/
Translation: InvokeAI/Web UI
2023-10-30 05:15:49 +11:00
Jaulustus
60a105103b translationBot(ui): update translation (German)
Currently translated at 36.0% (439 of 1217 strings)

translationBot(ui): update translation (German)

Currently translated at 35.5% (433 of 1217 strings)

Co-authored-by: Jaulustus <jaulustus@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/invokeai/web-ui/de/
Translation: InvokeAI/Web UI
2023-10-30 05:15:49 +11:00
Alexander Eichhorn
67fb2c8129 translationBot(ui): update translation (German)
Currently translated at 35.5% (433 of 1217 strings)

Co-authored-by: Alexander Eichhorn <pfannkuchensack@einfach-doof.de>
Translate-URL: https://hosted.weblate.org/projects/invokeai/web-ui/de/
Translation: InvokeAI/Web UI
2023-10-30 05:15:49 +11:00
Fabian Bahl
09bb61f630 translationBot(ui): update translation (English)
Currently translated at 100.0% (1217 of 1217 strings)

Co-authored-by: Fabian Bahl <fabian98@bahl-netz.de>
Translate-URL: https://hosted.weblate.org/projects/invokeai/web-ui/en/
Translation: InvokeAI/Web UI
2023-10-30 05:15:49 +11:00
Gohsuke Shimada
69ba3a7278 translationBot(ui): update translation (Japanese)
Currently translated at 56.1% (683 of 1217 strings)

translationBot(ui): update translation (Japanese)

Currently translated at 40.3% (491 of 1217 strings)

Co-authored-by: Gohsuke Shimada <ghoskay@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/invokeai/web-ui/ja/
Translation: InvokeAI/Web UI
2023-10-30 05:15:49 +11:00
Riccardo Giovanetti
6e05292813 translationBot(ui): update translation (Italian)
Currently translated at 97.6% (1188 of 1217 strings)

Co-authored-by: Riccardo Giovanetti <riccardo.giovanetti@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/invokeai/web-ui/it/
Translation: InvokeAI/Web UI
2023-10-30 05:15:49 +11:00
psychedelicious
859e3d5a61 chore: flake8 2023-10-30 01:49:10 +11:00
Jonathan
fe5d2c023b Update communityNodes.md
Added Average Images node
2023-10-28 08:13:51 -05:00
Millun Atluri
b6c259ab92 Update communityNodes.md (#4981)
Update to Load Video Frame node to reflect changes made in link
locations... a.k.a. fixing broken links.

## What type of PR is this? (check all applicable)

- [ ] Refactor
- [ ] Feature
- [ ] Bug Fix
- [ ] Optimization
- [x ] Documentation Update
- [x ] Community Node Submission


## Have you discussed this change with the InvokeAI team?
- [ ] Yes
- [x ] No, because: Its just a doc change to fix links I made for
resources that the page depends on from my github.

      
## Have you updated all relevant documentation?
- [? ] Yes
- [ ] No


## Description
load vid frame community node layout and link change.

## Related Tickets & Documents

<!--
For pull requests that relate or close an issue, please include them
below. 

For example having the text: "closes #1234" would connect the current
pull
request to issue 1234.  And when we merge the pull request, Github will
automatically close the issue.
-->

- Related Issue #
- Closes #

## QA Instructions, Screenshots, Recordings

<!-- 
Please provide steps on how to test changes, any hardware or 
software specifications as well as any other pertinent information. 
-->

## Added/updated tests?

- [ ] Yes
- [ ] No : _please replace this line with details on why tests
      have not been included_

## [optional] Are there any post deployment tasks we need to perform?
2023-10-28 20:37:48 +11:00
Millun Atluri
0fde82a24b Merge branch 'main' into main 2023-10-28 20:35:41 +11:00
Lincoln Stein
4f74549f17 prevent prereleases from showing up in updater 2023-10-27 19:12:48 -04:00
Mary Hipp
cc901e5ace Merge branch 'main' of https://github.com/invoke-ai/InvokeAI 2023-10-27 09:51:59 -04:00
Lincoln Stein
c95c6c5374 Make the merge script work again (#4979)
## What type of PR is this? (check all applicable)

- [ ] Refactor
- [ ] Feature
- [X] Bug Fix
- [ ] Optimization
- [ ] Documentation Update
- [ ] Community Node Submission


## Have you discussed this change with the InvokeAI team?
- [ ] Yes
- [X] No, because n/a

      
## Have you updated all relevant documentation?
- [X] Yes
- [ ] No


## Description

The introduction of `BaseModelType.Any` broke the code in the merge
script which relied on sd-1 coming first in the BaseModelType enum. This
assumption has been removed and the code should be less brittle now.

## Related Tickets & Documents

<!--
For pull requests that relate or close an issue, please include them
below. 

For example having the text: "closes #1234" would connect the current
pull
request to issue 1234.  And when we merge the pull request, Github will
automatically close the issue.
-->

- Related Issue #
- Closes #

## QA Instructions, Screenshots, Recordings

<!-- 
Please provide steps on how to test changes, any hardware or 
software specifications as well as any other pertinent information. 
-->

## Added/updated tests?

- [ ] Yes
- [ ] No : _please replace this line with details on why tests
      have not been included_

## [optional] Are there any post deployment tasks we need to perform?
2023-10-24 17:28:39 -04:00
Lincoln Stein
d946cb78e6 Merge branch 'main' into bugfix/merge-script-display-correct-model-bases 2023-10-24 17:20:36 -04:00
Lincoln Stein
48fc07e049 Make textual inversion script work again (#4978)
## What type of PR is this? (check all applicable)

- [ ] Refactor
- [ ] Feature
- [X] Bug Fix
- [ ] Optimization
- [ ] Documentation Update
- [ ] Community Node Submission


## Have you discussed this change with the InvokeAI team?
- [X] Yes
- [ ] No, because:

      
## Have you updated all relevant documentation?
- [X] Yes
- [ ] No


## Description

Fix textual inversion training script crash caused by reorg of services.

## Related Tickets & Documents

- closes #4975

<!--
For pull requests that relate or close an issue, please include them
below. 

For example having the text: "closes #1234" would connect the current
pull
request to issue 1234.  And when we merge the pull request, Github will
automatically close the issue.
-->

- Related Issue #
- Closes #

## QA Instructions, Screenshots, Recordings

<!-- 
Please provide steps on how to test changes, any hardware or 
software specifications as well as any other pertinent information. 
-->

## Added/updated tests?

- [ ] Yes
- [ ] No : _please replace this line with details on why tests
      have not been included_

## [optional] Are there any post deployment tasks we need to perform?
2023-10-24 14:24:53 -04:00
Lincoln Stein
5c9046580f Merge branch 'main' into bugfix/textual-inversion 2023-10-24 14:17:43 -04:00
Lincoln Stein
d397e80e0d Merge branch 'main' into bugfix/merge-script-display-correct-model-bases 2023-10-24 14:17:19 -04:00
Lincoln Stein
c04099a869 Support conversion of controlnets from safetensors to diffusers format (#4980)
## What type of PR is this? (check all applicable)

- [ ] Refactor
- [X] Feature
- [ ] Bug Fix
- [ ] Optimization
- [ ] Documentation Update
- [ ] Community Node Submission


## Have you discussed this change with the InvokeAI team?
- [X] Yes
- [ ] No, because:

      
## Have you updated all relevant documentation?
- [X] Yes
- [ ] No


## Description

This PR allows users to install checkpoint (safetensors) versions of
controlnet models. The models will be converted into diffusers format
and cached on the fly.

This only works for sd-1 and sd-2 controlnets, as I was unable to find
controlnet sdxl checkpoint models or their corresponding .yaml config
files.

After updating, please run `invokeai-configure --yes --default-only` to
install the missing config files. Users should be instructed to select
option [7] from the launcher "Re-run the configure script to fix a
broken install or to complete a major upgrade".

## Related Tickets & Documents

User request at
https://discord.com/channels/1020123559063990373/1160318627631870092/1160318627631870092

<!--
For pull requests that relate or close an issue, please include them
below. 

For example having the text: "closes #1234" would connect the current
pull
request to issue 1234.  And when we merge the pull request, Github will
automatically close the issue.
-->

- Related Issue #4743
- Closes #

## QA Instructions, Screenshots, Recordings

<!-- 
Please provide steps on how to test changes, any hardware or 
software specifications as well as any other pertinent information. 
-->

See above for instructions on updating the config files after checking
out the PR.
2023-10-24 14:16:52 -04:00
Gille
3b7e17c0cc Update communityNodes.md
Update to Load Video Frame node to reflect changes made in link locations... a.k.a. fixing broken links.
2023-10-23 21:46:51 -06:00
Lincoln Stein
6cbc69f3b7 support conversion of controlnets from safetensors to diffusers 2023-10-23 22:06:10 -04:00
Lincoln Stein
c14aa30956 fix the merge script to correctly display models sorted by base 2023-10-23 20:37:33 -04:00
Lincoln Stein
3546c41f4a close #4975 2023-10-23 18:48:14 -04:00
Eugene Brodsky
8e948d3f17 fix(assets): re-add missing caution image 2023-10-20 16:50:16 +11:00
blessedcoolant
02928298d9 fix(nodes): fix missing generation modes (#4960)
## What type of PR is this? (check all applicable)

- [ ] Refactor
- [ ] Feature
- [x] Bug Fix
- [ ] Optimization
- [ ] Documentation Update
- [ ] Community Node Submission


## Have you discussed this change with the InvokeAI team?
- [x] Yes
- [ ] No, because:

## Description

[fix(nodes): fix missing generation
modes](8615d53e65)

Lax typing on the metadata util functions allowed a typing issue to slip
through. Fixed the lax typing, updated core metadata node.

## Related Tickets & Documents

<!--
For pull requests that relate or close an issue, please include them
below. 

For example having the text: "closes #1234" would connect the current
pull
request to issue 1234.  And when we merge the pull request, Github will
automatically close the issue.
-->

- Related Issue #
- Closes #4959 (thanks @coder543)
2023-10-20 11:04:34 +05:30
psychedelicious
df4dab53a8 Merge remote-tracking branch 'origin/main' into fix/nodes/fix-generation-mode 2023-10-20 16:23:13 +11:00
psychedelicious
8615d53e65 fix(nodes): fix missing generation modes
Lax typing on the metadata util functions allowed a typing issue to slip through. Fixed the lax typing, updated core metadata node.
2023-10-20 16:22:56 +11:00
blessedcoolant
c8481d29eb fix(nodes): explicitly include custom nodes files (#4958)
## What type of PR is this? (check all applicable)

- [ ] Refactor
- [ ] Feature
- [x] Bug Fix
- [ ] Optimization
- [ ] Documentation Update
- [ ] Community Node Submission

## Description

fix(nodes): explicitly include custom nodes files

setuptools ignores markdown files - explicitly include all files in
`"invokeai.app.invocations"` to ensure all custom node files are
included
2023-10-20 10:16:55 +05:30
psychedelicious
b7a05734bb Merge branch 'main' into fix/noodes/include-custom-nodes-files 2023-10-20 15:19:39 +11:00
Dennis
eeeb5dc451 translationBot(ui): update translation (Dutch)
Currently translated at 99.9% (1216 of 1217 strings)

Co-authored-by: Dennis <dennis@vanzoerlandt.nl>
Translate-URL: https://hosted.weblate.org/projects/invokeai/web-ui/nl/
Translation: InvokeAI/Web UI
2023-10-20 15:18:51 +11:00
psychedelicious
3d33b3e1f5 fix(nodes): explicitly include custom nodes files
setuptools ignores markdown files - explicitly include all files in `"invokeai.app.invocations"` to ensure all custom node files are included
2023-10-20 15:18:29 +11:00
Millun Atluri
7b066681f0 Docker image update: ubuntu23.04+python3.11 (#4953)
## What type of PR is this? (check all applicable)

- [ ] Refactor
- [x] Feature
- [ ] Bug Fix
- [x] Optimization
- [x] Documentation Update
- [ ] Community Node Submission


## Have you discussed this change with the InvokeAI team?
- [x] Yes
- [ ] No, because:

      
## Have you updated all relevant documentation?
- [x] Yes
- [ ] No


## Description

- updates the Docker image with ubuntu23.04 base, python3.11
- use the newer pytorch wheel with cuda12.1 support
- corrects `docker compose` CLI in shell script wrappers and docs
- update / overhaul Docker docs
- clean up obsolete lines in `.gitignore`

## QA Instructions, Screenshots, Recordings

Follow the documentation changes, or simply:

```bash
cd docker
cp .env.sample .env
# Set your INVOKEAI_ROOT in .env
docker compose up
```

## Added/updated tests?

- [ ] Yes
- [x] No : N/A
2023-10-20 14:30:53 +11:00
Millun Atluri
1177234931 Merge branch 'main' into ebr/docker-py311 2023-10-20 14:28:40 +11:00
psychedelicious
824702de99 feat(nodes): change expected structure for custom nodes 2023-10-20 14:28:16 +11:00
psychedelicious
8604943e89 feat(nodes): simple custom nodes
Custom nodes may be places in `$INVOKEAI_ROOT/nodes/` (configurable with `custom_nodes_dir` option).

On app startup, an `__init__.py` is copied into the custom nodes dir, which recursively loads all python files in the directory as modules (files starting with `_` are ignored). The custom nodes dir is now a python module itself.

When we `from invocations import *` to load init all invocations, we load the custom nodes dir, registering all custom nodes.
2023-10-20 14:28:16 +11:00
psychedelicious
b7f63a4065 fix(ui): fix canvas color picker when value is zero
good ol' zero is false-y
2023-10-19 23:13:35 -04:00
psychedelicious
dcd11327c1 fix(db): remove unused, commented out methods 2023-10-20 12:05:13 +11:00
psychedelicious
c071262c20 fix(ui): remove getMetadataFromFile query & util
This will all be handled by python going forward
2023-10-20 12:05:13 +11:00
psychedelicious
2f4f83280b fix(db): remove extraneous conflict handling in workflow image records 2023-10-20 12:05:13 +11:00
psychedelicious
301a8fef92 fix(ui): fix batch metadata logic when graph has no metadata
On canvas, images have no metadata yet, so this needs to be handled
2023-10-20 12:05:13 +11:00
psychedelicious
52fbd1b222 fix(ui): remove errant comment 2023-10-20 12:05:13 +11:00
psychedelicious
16dacb5f43 fix(nodes): remove constraints on ip adapter metadata fields 2023-10-20 12:05:13 +11:00
psychedelicious
b5940039f3 chore: lint 2023-10-20 12:05:13 +11:00
psychedelicious
9104979943 chore(ui): regen types 2023-10-20 12:05:13 +11:00
psychedelicious
f04462973b feat(ui): create debounced metadata/workflow query hooks
Also added config options for metadata and workflow debounce times (`metadataFetchDebounce` & `workflowFetchDebounce`).

Falls back to 0 if not provided.

In OSS, because we have no major latency concerns, the debounce is 0. But in other environments, it may be desirable to set this to something like 300ms.
2023-10-20 12:05:13 +11:00
psychedelicious
2faed653d7 fix(api): deduplicate metadata/workflow extraction logic 2023-10-20 12:05:13 +11:00
psychedelicious
23fa2e560a fix: fix tests 2023-10-20 12:05:13 +11:00
psychedelicious
0cda7943fa feat(api): add workflow_images junction table
similar to boards, images and workflows may be associated via junction table
2023-10-20 12:05:13 +11:00
psychedelicious
6d776bad7e fix(nodes): remove errant print 2023-10-20 12:05:13 +11:00
psychedelicious
86c3acf184 fix(nodes): revert optional graph 2023-10-20 12:05:13 +11:00
psychedelicious
d32caf7cb1 fix(ui): remove references to metadata accumulator 2023-10-20 12:05:13 +11:00
psychedelicious
e3e8d8af02 fix(ui): fix log message 2023-10-20 12:05:13 +11:00
psychedelicious
7b6e2bc37f feat(nodes): add field name validation
Protect against using reserved field names
2023-10-20 12:05:13 +11:00
psychedelicious
bbae4045c9 fix(nodes): GraphInvocation should use InputField 2023-10-20 12:05:13 +11:00
psychedelicious
8910e912c7 chore(ui): regen types 2023-10-20 12:05:13 +11:00
psychedelicious
4012388f0a feat: use ModelValidator naming convention for pydantic type adapters
This is the naming convention in the docs and is also clear.
2023-10-20 12:05:13 +11:00
psychedelicious
3c4f43314c feat: move workflow/metadata models to baseinvocation.py
needed to prevent circular imports
2023-10-20 12:05:13 +11:00
psychedelicious
5a163f02a6 fix(nodes): fix metadata/workflow serialization 2023-10-20 12:05:13 +11:00
psychedelicious
f0db4d36e4 feat: metadata refactor
- Refactor how metadata is handled to support a user-defined metadata in graphs
- Update workflow embed handling
- Update UI to work with these changes
- Update tests to support metadata/workflow changes
2023-10-20 12:05:13 +11:00
psychedelicious
c2da74c587 feat: add workflows table & service 2023-10-20 12:05:13 +11:00
Eugene Brodsky
575c7bbfd8 feat(docker): update docker documentation 2023-10-19 11:26:36 -04:00
Eugene Brodsky
f102e38076 feat(docker): update docker image, etc. to python3.11+ubuntu23.04 2023-10-19 11:26:16 -04:00
psychedelicious
9195c8c957 feat: dedicated route to get intermediates count
This fixes a weird issue where the list images method needed to handle `None` for its `limit` and `offset` arguments, in order to get a count of all intermediates.
2023-10-19 16:58:51 +11:00
Millun Atluri
677918df61 Docs Update (python version & T2I (#4867)
* Updated Control Adapter Docs

* fixed typo

* Update docs for 3.10

* Update diffusers language

Co-authored-by: psychedelicious <4822129+psychedelicious@users.noreply.github.com>

* Diffusers format

Co-authored-by: psychedelicious <4822129+psychedelicious@users.noreply.github.com>

* Current T2I Adapter usage

Co-authored-by: psychedelicious <4822129+psychedelicious@users.noreply.github.com>

* Update test-invoke-pip.yml

---------

Co-authored-by: psychedelicious <4822129+psychedelicious@users.noreply.github.com>
2023-10-18 23:38:31 -04:00
psychedelicious
96e80c71fb chore: lint 2023-10-19 08:52:02 +11:00
psychedelicious
da403ba04c fix(api): flesh out types for api_app.py 2023-10-19 08:52:02 +11:00
psychedelicious
e4c45012f4 feat(api): add gzip middleware
On our local installs this will be a very minor change. For those running on remote servers, load times should be slightly improved.

It's a small change but I think correct.
2023-10-19 08:52:02 +11:00
psychedelicious
ef14ba1713 fix(api): fix uvicorn config loop arg
We were providing the loop itself, not the kind of loop. This didn't appear to cause any issues whatsoever, but now it's correct.
2023-10-19 08:52:02 +11:00
psychedelicious
9e06371178 feat(api): serve app via route & add cache-control: no-store
This should prevent `index.html` from *ever* being cached, so UIs will never be out of date.

Minor organisation to accomodate this.

Deleting old unused files from the early days
2023-10-19 08:52:02 +11:00
psychedelicious
a459786d73 fix(nodes): enable number to string coercion 2023-10-19 08:43:08 +11:00
d8ahazard
fdf02c33d0 Catch generic model errors
Prevent the app from dying on invalid models.
2023-10-19 07:28:33 +11:00
psychedelicious
0a01d86ab1 fix(ui): fix multiple control adapters on canvas
We were making an edges for each adapter where we should isntead have one from the adapter's collect node into the denoising node
2023-10-19 07:15:27 +11:00
psychedelicious
5e6df975fd fix(nodes): fix math node validation
Update field_validator api for pydantic v2
2023-10-19 06:50:00 +11:00
Ryan Dick
967a2dad54 Multi-Image IP-Adapter (#4882)
## What type of PR is this? (check all applicable)

- [ ] Refactor
- [x] Feature
- [ ] Bug Fix
- [ ] Optimization
- [ ] Documentation Update
- [ ] Community Node Submission

## Description

This PR adds the ability to pass multiple images to a single IP-Adapter
(note the difference from using _multiple IP-Adapters at once_, which is
already supported.). The image embeddings are combined in the IP-Adapter
attention layers. This is the same strategy for combining multiple
images as used in Insta-LoRA workflows
(https://civitai.com/articles/2345).

This PR only adds multi-image support in the backend and the node
editor. The Linear UI still needs to be updated.

## QA Instructions, Screenshots, Recordings

I have manually tested the following via the workflow editor:
- Multiple images with a single IP-Adapter
- Multiple images per IP-Adapter, and multiple IP-Adapters
- Both standard and sequential conditioning
- IP-Adapters still work in the Linear UI.

Please hammer at this feature some more with manual testing.

## Added/updated tests?

- [x] Yes
- [ ] No

I updated the existing IP-Adapter smoke test, but it provides pretty
limited coverage of this feature. This feature would probably be best
tested by an end-to-end workflow test, which is not currently supported.
(I'm hoping to put some effort into workflow-level testing soon.)
2023-10-18 10:17:34 -04:00
Ryan Dick
a078efc0f2 Merge branch 'main' into ryan/multi-image-ip 2023-10-18 08:59:12 -04:00
psychedelicious
024aa5eb90 fix(ui): fix field sorting
closes #4934
2023-10-18 15:35:26 +11:00
psychedelicious
67a343b3e4 Update pyproject.toml 2023-10-18 11:28:26 +11:00
Lincoln Stein
d27392cc2d remove all references to CLI 2023-10-18 11:28:26 +11:00
blessedcoolant
9fa8e38163 fix(ui): use pidi processor for sketch (#4931)
## What type of PR is this? (check all applicable)

- [ ] Refactor
- [ ] Feature
- [x] Bug Fix
- [ ] Optimization
- [ ] Documentation Update
- [ ] Community Node Submission


## Have you discussed this change with the InvokeAI team?
- [x] Yes
- [ ] No, because:

## Description

fix(ui): use pidi processor for sketch control adapters
2023-10-18 04:04:42 +05:30
blessedcoolant
4b197cb6d4 Merge branch 'main' into fix/ui/sketch-pidi-processor 2023-10-18 04:02:30 +05:30
psychedelicious
252c9a5f5a fix(backend): fix nsfw/watermarker util types 2023-10-18 09:08:13 +11:00
psychedelicious
975ba6b74f fix(ui): use pidi processor for sketch 2023-10-18 08:43:56 +11:00
psychedelicious
284a257c25 feat: remove enqueue_graph routes/methods (#4922)
This is totally extraneous - it's almost identical to `enqueue_batch`.
2023-10-17 18:00:40 +00:00
Mary Hipp
c8d6bec9b3 Merge branch 'main' of https://github.com/invoke-ai/InvokeAI 2023-10-17 12:47:21 -04:00
Kent Keirsey
55ad4feb5c Revert "Revert "feat(ui): remove special handling for t2i vs controlnet""
This reverts commit bdf4c4944c.
2023-10-17 11:59:19 -04:00
Kent Keirsey
b7555ddae8 Revert "Revert "chore: lint""
This reverts commit 38e7eb8878.
2023-10-17 11:59:19 -04:00
Kent Keirsey
8afc47018b Revert "Revert "Cleaning up (removing diagnostic prints)""
This reverts commit 6e697b7b6f.
2023-10-17 11:59:19 -04:00
Kent Keirsey
a97ec88e06 Revert "Revert "Changes to _apply_standard_conditioning_sequentially() and _apply_cross_attention_controlled_conditioning() to reflect changes to T2I-Adapter implementation to allow usage of T2I-Adapter and ControlNet at the same time.""
This reverts commit c04fb451ee.
2023-10-17 11:59:19 -04:00
Kent Keirsey
282d36b640 Revert "Revert "Fixing some var and arg names.""
This reverts commit 58a0709c1e.
2023-10-17 11:59:19 -04:00
psychedelicious
58a0709c1e Revert "Fixing some var and arg names."
This reverts commit f11ba81a8d.
2023-10-17 11:59:11 -04:00
psychedelicious
c04fb451ee Revert "Changes to _apply_standard_conditioning_sequentially() and _apply_cross_attention_controlled_conditioning() to reflect changes to T2I-Adapter implementation to allow usage of T2I-Adapter and ControlNet at the same time."
This reverts commit 378689a519.
2023-10-17 11:59:11 -04:00
psychedelicious
6e697b7b6f Revert "Cleaning up (removing diagnostic prints)"
This reverts commit 06f8a3276d.
2023-10-17 11:59:11 -04:00
psychedelicious
38e7eb8878 Revert "chore: lint"
This reverts commit fff29d663d.
2023-10-17 11:59:11 -04:00
psychedelicious
bdf4c4944c Revert "feat(ui): remove special handling for t2i vs controlnet"
This reverts commit b146993553.
2023-10-17 11:59:11 -04:00
Mary Hipp
3c044bb245 Merge branch 'main' of https://github.com/invoke-ai/InvokeAI 2023-10-17 11:06:43 -04:00
psychedelicious
b146993553 feat(ui): remove special handling for t2i vs controlnet 2023-10-17 19:42:06 +11:00
psychedelicious
fff29d663d chore: lint 2023-10-17 19:42:06 +11:00
user1
06f8a3276d Cleaning up (removing diagnostic prints) 2023-10-17 19:42:06 +11:00
user1
378689a519 Changes to _apply_standard_conditioning_sequentially() and _apply_cross_attention_controlled_conditioning() to reflect changes to T2I-Adapter implementation to allow usage of T2I-Adapter and ControlNet at the same time.
Also, the PREVIOUS commit (@8d3885d, which was already pushed to github repo) was wrongly commented, but too late to fix without a force push or other mucking that I'm reluctant to do. That commit is actually the one that has all the changes to diffusers_pipeline.py to use additional arg down_intrablock_additional_residuals (introduced in diffusers PR https://github.com/huggingface/diffusers/pull/5362) to detangle T2I-Adapter from ControlNet inputs to main UNet.
2023-10-17 19:42:06 +11:00
user1
f11ba81a8d Fixing some var and arg names. 2023-10-17 19:42:06 +11:00
Lincoln Stein
9542883bb5 update requirements to python 3.10-11 2023-10-17 19:30:31 +11:00
Millun Atluri
14e25bf277 Merge branch 'main' into feat/nodes/freeu 2023-10-17 16:42:59 +11:00
Surisen
c69715636d translationBot(ui): update translation (Chinese (Simplified))
Currently translated at 100.0% (1217 of 1217 strings)

Co-authored-by: Surisen <zhonghx0804@outlook.com>
Translate-URL: https://hosted.weblate.org/projects/invokeai/web-ui/zh_Hans/
Translation: InvokeAI/Web UI
2023-10-17 16:42:37 +11:00
Millun Atluri
001bba1719 Merge branch 'main' into feat/nodes/freeu 2023-10-17 15:58:00 +11:00
psychedelicious
a094f4ca2b fix: pin python-socketio~=5.10.0 2023-10-17 14:59:25 +11:00
psychedelicious
9d9592230a chore: lint 2023-10-17 14:59:25 +11:00
psychedelicious
685cda89ff feat(api): restore get_session route 2023-10-17 14:59:25 +11:00
psychedelicious
2c39557dc9 fix(nodes): fix metadata validation error 2023-10-17 14:59:25 +11:00
psychedelicious
c238a7f18b feat(api): chore: pydantic & fastapi upgrade
Upgrade pydantic and fastapi to latest.

- pydantic~=2.4.2
- fastapi~=103.2
- fastapi-events~=0.9.1

**Big Changes**

There are a number of logic changes needed to support pydantic v2. Most changes are very simple, like using the new methods to serialized and deserialize models, but there are a few more complex changes.

**Invocations**

The biggest change relates to invocation creation, instantiation and validation.

Because pydantic v2 moves all validation logic into the rust pydantic-core, we may no longer directly stick our fingers into the validation pie.

Previously, we (ab)used models and fields to allow invocation fields to be optional at instantiation, but required when `invoke()` is called. We directly manipulated the fields and invocation models when calling `invoke()`.

With pydantic v2, this is much more involved. Changes to the python wrapper do not propagate down to the rust validation logic - you have to rebuild the model. This causes problem with concurrent access to the invocation classes and is not a free operation.

This logic has been totally refactored and we do not need to change the model any more. The details are in `baseinvocation.py`, in the `InputField` function and `BaseInvocation.invoke_internal()` method.

In the end, this implementation is cleaner.

**Invocation Fields**

In pydantic v2, you can no longer directly add or remove fields from a model.

Previously, we did this to add the `type` field to invocations.

**Invocation Decorators**

With pydantic v2, we instead use the imperative `create_model()` API to create a new model with the additional field. This is done in `baseinvocation.py` in the `invocation()` wrapper.

A similar technique is used for `invocation_output()`.

**Minor Changes**

There are a number of minor changes around the pydantic v2 models API.

**Protected `model_` Namespace**

All models' pydantic-provided methods and attributes are prefixed with `model_` and this is considered a protected namespace. This causes some conflict, because "model" means something to us, and we have a ton of pydantic models with attributes starting with "model_".

Forunately, there are no direct conflicts. However, in any pydantic model where we define an attribute or method that starts with "model_", we must tell set the protected namespaces to an empty tuple.

```py
class IPAdapterModelField(BaseModel):
    model_name: str = Field(description="Name of the IP-Adapter model")
    base_model: BaseModelType = Field(description="Base model")

    model_config = ConfigDict(protected_namespaces=())
```

**Model Serialization**

Pydantic models no longer have `Model.dict()` or `Model.json()`.

Instead, we use `Model.model_dump()` or `Model.model_dump_json()`.

**Model Deserialization**

Pydantic models no longer have `Model.parse_obj()` or `Model.parse_raw()`, and there are no `parse_raw_as()` or `parse_obj_as()` functions.

Instead, you need to create a `TypeAdapter` object to parse python objects or JSON into a model.

```py
adapter_graph = TypeAdapter(Graph)
deserialized_graph_from_json = adapter_graph.validate_json(graph_json)
deserialized_graph_from_dict = adapter_graph.validate_python(graph_dict)
```

**Field Customisation**

Pydantic `Field`s no longer accept arbitrary args.

Now, you must put all additional arbitrary args in a `json_schema_extra` arg on the field.

**Schema Customisation**

FastAPI and pydantic schema generation now follows the OpenAPI version 3.1 spec.

This necessitates two changes:
- Our schema customization logic has been revised
- Schema parsing to build node templates has been revised

The specific aren't important, but this does present additional surface area for bugs.

**Performance Improvements**

Pydantic v2 is a full rewrite with a rust backend. This offers a substantial performance improvement (pydantic claims 5x to 50x depending on the task). We'll notice this the most during serialization and deserialization of sessions/graphs, which happens very very often - a couple times per node.

I haven't done any benchmarks, but anecdotally, graph execution is much faster. Also, very larges graphs - like with massive iterators - are much, much faster.
2023-10-17 14:59:25 +11:00
psychedelicious
19c5435332 fix(ui): copy image via img onload to blob
There's a bug in chrome that screws with headers on fetch requests and 307 responses. This causes images to fail to copy in the commercial environment.

This change attempts to get around this by copying images in a different way (similar to how the canvas works). When the user requests a copy we:
- create an `<img />` element
- set `crossOrigin` if needed
- add an onload handler:
  - create a canvas element
  - draw image onto it
  - export canvas to blob

This is wrapped in a promise which resolves to the blob, which can then be copied to clipboard.

---

A customized version of Konva's `useImage` hook is also included, which returns the image blob in addition to the `<img />` element. Unfortunately, this hook is not suitable for use across the app, because it does all the image fetching up front, regardless of whether we actually want to copy the image.

In other words, we'd have to fetch the whole image file even if the user is just skipping through image metadata, in order to have the blob to copy. The callback approach means we only fetch the image when the user clicks copy. The hook is thus currently unused.
2023-10-17 06:43:19 +11:00
Ryan Dick
3079c75a60 (minor) Make it more clear that shape annotations are just comments and not commented lines of code. 2023-10-16 08:35:32 -04:00
psychedelicious
53b6f0dc73 Merge branch 'main' into ryan/multi-image-ip 2023-10-16 17:16:10 +11:00
Ryan Dick
35ebc9e18d Bump invocation versions for the multi-image IP feature. 2023-10-14 13:28:50 -04:00
Ryan Dick
49279bbe74 Update IP-Adapter unit test for multi-image. 2023-10-14 13:00:52 -04:00
Ryan Dick
8464450a53 Add support for multi-image IP-Adapter. 2023-10-14 12:50:33 -04:00
Drun555
9db152bf75 xformers==0.0.20
I'm not sure if it's correct way of handling things, but correcting this string to '==0.0.20' fixes xformers install for me - and maybe for others too. 

Please see this thread, this is the issue I had (trying to install InvokeAI):
https://github.com/facebookresearch/xformers/issues/740
2023-10-14 14:59:55 +04:00
Mary Hipp
af5341b39d Merge branch 'main' of https://github.com/invoke-ai/InvokeAI 2023-10-13 10:07:42 -04:00
psychedelicious
0e60f3dac1 chore: typegen 2023-10-12 09:23:10 -04:00
psychedelicious
ce9183113b fix: fix test imports 2023-10-12 09:23:10 -04:00
psychedelicious
ff4a57f883 chore(ui): regen types 2023-10-12 09:23:10 -04:00
psychedelicious
0f361cc3f1 fix(app): remove errant logger line 2023-10-12 09:23:10 -04:00
psychedelicious
028fe7eb04 chore: rebase conflicts 2023-10-12 09:23:10 -04:00
psychedelicious
efda2ded7a fix: merge conflicts 2023-10-12 09:23:10 -04:00
psychedelicious
899935a861 fix(backend): remove logic to create workflows column
Snuck in there while I was organising
2023-10-12 09:23:10 -04:00
psychedelicious
94c00cee4c feat: refactor services folder/module structure
Refactor services folder/module structure.

**Motivation**

While working on our services I've repeatedly encountered circular imports and a general lack of clarity regarding where to put things. The structure introduced goes a long way towards resolving those issues, setting us up for a clean structure going forward.

**Services**

Services are now in their own folder with a few files:

- `services/{service_name}/__init__.py`: init as needed, mostly empty now
- `services/{service_name}/{service_name}_base.py`: the base class for the service
- `services/{service_name}/{service_name}_{impl_type}.py`: the default concrete implementation of the service - typically one of `sqlite`, `default`, or `memory`
- `services/{service_name}/{service_name}_common.py`: any common items - models, exceptions, utilities, etc

Though it's a bit verbose to have the service name both as the folder name and the prefix for files, I found it is _extremely_ confusing to have all of the base classes just be named `base.py`. So, at the cost of some verbosity when importing things, I've included the service name in the filename.

There are some minor logic changes. For example, in `InvocationProcessor`, instead of assigning the model manager service to a variable to be used later in the file, the service is used directly via the `Invoker`.

**Shared**

Things that are used across disparate services are in `services/shared/`:

- `default_graphs.py`: previously in `services/`
- `graphs.py`: previously in `services/`
- `paginatation`: generic pagination models used in a few services
- `sqlite`: the `SqliteDatabase` class, other sqlite-specific things
2023-10-12 09:23:10 -04:00
psychedelicious
021f92fadc feat(backend): rename db.py to sqlite.py 2023-10-12 09:23:10 -04:00
psychedelicious
fe4c5a68c4 feat(backend): move pagination models to own file 2023-10-12 09:23:10 -04:00
psychedelicious
4f2318571d feat(backend): organise service dependencies
**Service Dependencies**

Services that depend on other services now access those services via the `Invoker` object. This object is provided to the service as a kwarg to its `start()` method.

Until now, most services did not utilize this feature, and several services required their dependencies to be initialized and passed in on init.

Additionally, _all_ services are now registered as invocation services - including the low-level services. This obviates issues with inter-dependent services we would otherwise experience as we add workflow storage.

**Database Access**

Previously, we were passing in a separate sqlite connection and corresponding lock as args to services in their init. A good amount of posturing was done in each service that uses the db.

These objects, along with the sqlite startup and cleanup logic, is now abstracted into a simple `SqliteDatabase` class. This creates the shared connection and lock objects, enables foreign keys, and provides a `clean()` method to do startup db maintenance.

This is not a service as it's only used by sqlite services.
2023-10-12 09:23:10 -04:00
psychedelicious
15b33ad501 feat(nodes): add freeu support
Add support for FreeU. See:
- https://huggingface.co/docs/diffusers/main/en/using-diffusers/freeu
- https://github.com/ChenyangSi/FreeU

Implementation:
- `ModelPatcher.apply_freeu()` handles the enabling freeu (which is very simple with diffusers).
- `FreeUConfig` model added to hold the hyperparameters.
- `freeu_config` added as optional sub-field on `UNetField`.
- `FreeUInvocation` added, works like LoRA - chain it to add the FreeU config to the UNet
- No support for model-dependent presets, this will be a future workflow editor enhancement

Closes #4845
2023-10-11 13:49:28 +11:00
468 changed files with 18859 additions and 14584 deletions

1
.gitattributes vendored
View File

@@ -2,3 +2,4 @@
# Only affects text files and ignores other file types. # Only affects text files and ignores other file types.
# For more info see: https://www.aleksandrhovhannisyan.com/blog/crlf-vs-lf-normalizing-line-endings-in-git/ # For more info see: https://www.aleksandrhovhannisyan.com/blog/crlf-vs-lf-normalizing-line-endings-in-git/
* text=auto * text=auto
docker/** text eol=lf

View File

@@ -1,20 +0,0 @@
on:
pull_request:
push:
branches:
- main
- development
- 'release-candidate-*'
jobs:
pyflakes:
name: runner / pyflakes
if: github.event.pull_request.draft == false
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: pyflakes
uses: reviewdog/action-pyflakes@v1
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
reporter: github-pr-review

View File

@@ -6,7 +6,7 @@ on:
branches: main branches: main
jobs: jobs:
black: ruff:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
@@ -18,8 +18,7 @@ jobs:
- name: Install dependencies with pip - name: Install dependencies with pip
run: | run: |
pip install black flake8 Flake8-pyproject isort pip install ruff
- run: isort --check-only . - run: ruff check --output-format=github .
- run: black --check . - run: ruff format --check .
- run: flake8

12
.gitignore vendored
View File

@@ -1,8 +1,5 @@
.idea/ .idea/
# ignore the Anaconda/Miniconda installer used while building Docker image
anaconda.sh
# Byte-compiled / optimized / DLL files # Byte-compiled / optimized / DLL files
__pycache__/ __pycache__/
*.py[cod] *.py[cod]
@@ -136,12 +133,10 @@ celerybeat.pid
# Environments # Environments
.env .env
.venv .venv*
env/ env/
venv/ venv/
ENV/ ENV/
env.bak/
venv.bak/
# Spyder project settings # Spyder project settings
.spyderproject .spyderproject
@@ -186,11 +181,6 @@ cython_debug/
.scratch/ .scratch/
.vscode/ .vscode/
# ignore environment.yml and requirements.txt
# these are links to the real files in environments-and-requirements
environment.yml
requirements.txt
# source installer files # source installer files
installer/*zip installer/*zip
installer/install.bat installer/install.bat

View File

@@ -123,7 +123,7 @@ and go to http://localhost:9090.
### Command-Line Installation (for developers and users familiar with Terminals) ### Command-Line Installation (for developers and users familiar with Terminals)
You must have Python 3.9 through 3.11 installed on your machine. Earlier or You must have Python 3.10 through 3.11 installed on your machine. Earlier or
later versions are not supported. later versions are not supported.
Node.js also needs to be installed along with yarn (can be installed with Node.js also needs to be installed along with yarn (can be installed with
the command `npm install -g yarn` if needed) the command `npm install -g yarn` if needed)
@@ -161,7 +161,7 @@ the command `npm install -g yarn` if needed)
_For Windows/Linux with an NVIDIA GPU:_ _For Windows/Linux with an NVIDIA GPU:_
```terminal ```terminal
pip install "InvokeAI[xformers]" --use-pep517 --extra-index-url https://download.pytorch.org/whl/cu118 pip install "InvokeAI[xformers]" --use-pep517 --extra-index-url https://download.pytorch.org/whl/cu121
``` ```
_For Linux with an AMD GPU:_ _For Linux with an AMD GPU:_
@@ -175,7 +175,7 @@ the command `npm install -g yarn` if needed)
pip install InvokeAI --use-pep517 --extra-index-url https://download.pytorch.org/whl/cpu pip install InvokeAI --use-pep517 --extra-index-url https://download.pytorch.org/whl/cpu
``` ```
_For Macintoshes, either Intel or M1/M2:_ _For Macintoshes, either Intel or M1/M2/M3:_
```sh ```sh
pip install InvokeAI --use-pep517 pip install InvokeAI --use-pep517

View File

@@ -1,13 +1,15 @@
## Make a copy of this file named `.env` and fill in the values below. ## Make a copy of this file named `.env` and fill in the values below.
## Any environment variables supported by InvokeAI can be specified here. ## Any environment variables supported by InvokeAI can be specified here,
## in addition to the examples below.
# INVOKEAI_ROOT is the path to a path on the local filesystem where InvokeAI will store data. # INVOKEAI_ROOT is the path to a path on the local filesystem where InvokeAI will store data.
# Outputs will also be stored here by default. # Outputs will also be stored here by default.
# This **must** be an absolute path. # This **must** be an absolute path.
INVOKEAI_ROOT= INVOKEAI_ROOT=
HUGGINGFACE_TOKEN= # Get this value from your HuggingFace account settings page.
# HUGGING_FACE_HUB_TOKEN=
## optional variables specific to the docker setup ## optional variables specific to the docker setup.
# GPU_DRIVER=cuda # GPU_DRIVER=cuda # or rocm
# CONTAINER_UID=1000 # CONTAINER_UID=1000

View File

@@ -2,7 +2,7 @@
## Builder stage ## Builder stage
FROM library/ubuntu:22.04 AS builder FROM library/ubuntu:23.04 AS builder
ARG DEBIAN_FRONTEND=noninteractive ARG DEBIAN_FRONTEND=noninteractive
RUN rm -f /etc/apt/apt.conf.d/docker-clean; echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' > /etc/apt/apt.conf.d/keep-cache RUN rm -f /etc/apt/apt.conf.d/docker-clean; echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' > /etc/apt/apt.conf.d/keep-cache
@@ -10,7 +10,7 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
--mount=type=cache,target=/var/lib/apt,sharing=locked \ --mount=type=cache,target=/var/lib/apt,sharing=locked \
apt update && apt-get install -y \ apt update && apt-get install -y \
git \ git \
python3.10-venv \ python3-venv \
python3-pip \ python3-pip \
build-essential build-essential
@@ -18,8 +18,8 @@ ENV INVOKEAI_SRC=/opt/invokeai
ENV VIRTUAL_ENV=/opt/venv/invokeai ENV VIRTUAL_ENV=/opt/venv/invokeai
ENV PATH="$VIRTUAL_ENV/bin:$PATH" ENV PATH="$VIRTUAL_ENV/bin:$PATH"
ARG TORCH_VERSION=2.0.1 ARG TORCH_VERSION=2.1.0
ARG TORCHVISION_VERSION=0.15.2 ARG TORCHVISION_VERSION=0.16
ARG GPU_DRIVER=cuda ARG GPU_DRIVER=cuda
ARG TARGETPLATFORM="linux/amd64" ARG TARGETPLATFORM="linux/amd64"
# unused but available # unused but available
@@ -35,9 +35,9 @@ RUN --mount=type=cache,target=/root/.cache/pip \
if [ "$TARGETPLATFORM" = "linux/arm64" ] || [ "$GPU_DRIVER" = "cpu" ]; then \ if [ "$TARGETPLATFORM" = "linux/arm64" ] || [ "$GPU_DRIVER" = "cpu" ]; then \
extra_index_url_arg="--extra-index-url https://download.pytorch.org/whl/cpu"; \ extra_index_url_arg="--extra-index-url https://download.pytorch.org/whl/cpu"; \
elif [ "$GPU_DRIVER" = "rocm" ]; then \ elif [ "$GPU_DRIVER" = "rocm" ]; then \
extra_index_url_arg="--extra-index-url https://download.pytorch.org/whl/rocm5.4.2"; \ extra_index_url_arg="--index-url https://download.pytorch.org/whl/rocm5.6"; \
else \ else \
extra_index_url_arg="--extra-index-url https://download.pytorch.org/whl/cu118"; \ extra_index_url_arg="--extra-index-url https://download.pytorch.org/whl/cu121"; \
fi &&\ fi &&\
pip install $extra_index_url_arg \ pip install $extra_index_url_arg \
torch==$TORCH_VERSION \ torch==$TORCH_VERSION \
@@ -70,7 +70,7 @@ RUN --mount=type=cache,target=/usr/lib/node_modules \
#### Runtime stage --------------------------------------- #### Runtime stage ---------------------------------------
FROM library/ubuntu:22.04 AS runtime FROM library/ubuntu:23.04 AS runtime
ARG DEBIAN_FRONTEND=noninteractive ARG DEBIAN_FRONTEND=noninteractive
ENV PYTHONUNBUFFERED=1 ENV PYTHONUNBUFFERED=1
@@ -85,6 +85,7 @@ RUN apt update && apt install -y --no-install-recommends \
iotop \ iotop \
bzip2 \ bzip2 \
gosu \ gosu \
magic-wormhole \
libglib2.0-0 \ libglib2.0-0 \
libgl1-mesa-glx \ libgl1-mesa-glx \
python3-venv \ python3-venv \
@@ -94,10 +95,6 @@ RUN apt update && apt install -y --no-install-recommends \
libstdc++-10-dev &&\ libstdc++-10-dev &&\
apt-get clean && apt-get autoclean apt-get clean && apt-get autoclean
# globally add magic-wormhole
# for ease of transferring data to and from the container
# when running in sandboxed cloud environments; e.g. Runpod etc.
RUN pip install magic-wormhole
ENV INVOKEAI_SRC=/opt/invokeai ENV INVOKEAI_SRC=/opt/invokeai
ENV VIRTUAL_ENV=/opt/venv/invokeai ENV VIRTUAL_ENV=/opt/venv/invokeai
@@ -120,9 +117,7 @@ WORKDIR ${INVOKEAI_SRC}
RUN cd /usr/lib/$(uname -p)-linux-gnu/pkgconfig/ && ln -sf opencv4.pc opencv.pc RUN cd /usr/lib/$(uname -p)-linux-gnu/pkgconfig/ && ln -sf opencv4.pc opencv.pc
RUN python3 -c "from patchmatch import patch_match" RUN python3 -c "from patchmatch import patch_match"
# Create unprivileged user and make the local dir RUN mkdir -p ${INVOKEAI_ROOT} && chown -R 1000:1000 ${INVOKEAI_ROOT}
RUN useradd --create-home --shell /bin/bash -u 1000 --comment "container local user" invoke
RUN mkdir -p ${INVOKEAI_ROOT} && chown -R invoke:invoke ${INVOKEAI_ROOT}
COPY docker/docker-entrypoint.sh ./ COPY docker/docker-entrypoint.sh ./
ENTRYPOINT ["/opt/invokeai/docker-entrypoint.sh"] ENTRYPOINT ["/opt/invokeai/docker-entrypoint.sh"]

View File

@@ -5,7 +5,7 @@ All commands are to be run from the `docker` directory: `cd docker`
#### Linux #### Linux
1. Ensure builkit is enabled in the Docker daemon settings (`/etc/docker/daemon.json`) 1. Ensure builkit is enabled in the Docker daemon settings (`/etc/docker/daemon.json`)
2. Install the `docker compose` plugin using your package manager, or follow a [tutorial](https://www.digitalocean.com/community/tutorials/how-to-install-and-use-docker-compose-on-ubuntu-22-04). 2. Install the `docker compose` plugin using your package manager, or follow a [tutorial](https://docs.docker.com/compose/install/linux/#install-using-the-repository).
- The deprecated `docker-compose` (hyphenated) CLI continues to work for now. - The deprecated `docker-compose` (hyphenated) CLI continues to work for now.
3. Ensure docker daemon is able to access the GPU. 3. Ensure docker daemon is able to access the GPU.
- You may need to install [nvidia-container-toolkit](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html) - You may need to install [nvidia-container-toolkit](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html)
@@ -20,7 +20,6 @@ This is done via Docker Desktop preferences
## Quickstart ## Quickstart
1. Make a copy of `env.sample` and name it `.env` (`cp env.sample .env` (Mac/Linux) or `copy example.env .env` (Windows)). Make changes as necessary. Set `INVOKEAI_ROOT` to an absolute path to: 1. Make a copy of `env.sample` and name it `.env` (`cp env.sample .env` (Mac/Linux) or `copy example.env .env` (Windows)). Make changes as necessary. Set `INVOKEAI_ROOT` to an absolute path to:
a. the desired location of the InvokeAI runtime directory, or a. the desired location of the InvokeAI runtime directory, or
b. an existing, v3.0.0 compatible runtime directory. b. an existing, v3.0.0 compatible runtime directory.
@@ -42,20 +41,22 @@ The Docker daemon on the system must be already set up to use the GPU. In case o
Check the `.env.sample` file. It contains some environment variables for running in Docker. Copy it, name it `.env`, and fill it in with your own values. Next time you run `docker compose up`, your custom values will be used. Check the `.env.sample` file. It contains some environment variables for running in Docker. Copy it, name it `.env`, and fill it in with your own values. Next time you run `docker compose up`, your custom values will be used.
You can also set these values in `docker compose.yml` directly, but `.env` will help avoid conflicts when code is updated. You can also set these values in `docker-compose.yml` directly, but `.env` will help avoid conflicts when code is updated.
Example (most values are optional): Example (values are optional, but setting `INVOKEAI_ROOT` is highly recommended):
``` ```bash
INVOKEAI_ROOT=/Volumes/WorkDrive/invokeai INVOKEAI_ROOT=/Volumes/WorkDrive/invokeai
HUGGINGFACE_TOKEN=the_actual_token HUGGINGFACE_TOKEN=the_actual_token
CONTAINER_UID=1000 CONTAINER_UID=1000
GPU_DRIVER=cuda GPU_DRIVER=cuda
``` ```
Any environment variables supported by InvokeAI can be set here - please see the [Configuration docs](https://invoke-ai.github.io/InvokeAI/features/CONFIGURATION/) for further detail.
## Even Moar Customizing! ## Even Moar Customizing!
See the `docker compose.yaml` file. The `command` instruction can be uncommented and used to run arbitrary startup commands. Some examples below. See the `docker-compose.yml` file. The `command` instruction can be uncommented and used to run arbitrary startup commands. Some examples below.
### Reconfigure the runtime directory ### Reconfigure the runtime directory
@@ -63,7 +64,7 @@ Can be used to download additional models from the supported model list
In conjunction with `INVOKEAI_ROOT` can be also used to initialize a runtime directory In conjunction with `INVOKEAI_ROOT` can be also used to initialize a runtime directory
``` ```yaml
command: command:
- invokeai-configure - invokeai-configure
- --yes - --yes
@@ -71,7 +72,7 @@ command:
Or install models: Or install models:
``` ```yaml
command: command:
- invokeai-model-install - invokeai-model-install
``` ```

View File

@@ -5,7 +5,7 @@ build_args=""
[[ -f ".env" ]] && build_args=$(awk '$1 ~ /\=[^$]/ {print "--build-arg " $0 " "}' .env) [[ -f ".env" ]] && build_args=$(awk '$1 ~ /\=[^$]/ {print "--build-arg " $0 " "}' .env)
echo "docker-compose build args:" echo "docker compose build args:"
echo $build_args echo $build_args
docker-compose build $build_args docker compose build $build_args

View File

@@ -15,6 +15,10 @@ services:
- driver: nvidia - driver: nvidia
count: 1 count: 1
capabilities: [gpu] capabilities: [gpu]
# For AMD support, comment out the deploy section above and uncomment the devices section below:
#devices:
# - /dev/kfd:/dev/kfd
# - /dev/dri:/dev/dri
build: build:
context: .. context: ..
dockerfile: docker/Dockerfile dockerfile: docker/Dockerfile

View File

@@ -19,7 +19,7 @@ set -e -o pipefail
# Default UID: 1000 chosen due to popularity on Linux systems. Possibly 501 on MacOS. # Default UID: 1000 chosen due to popularity on Linux systems. Possibly 501 on MacOS.
USER_ID=${CONTAINER_UID:-1000} USER_ID=${CONTAINER_UID:-1000}
USER=invoke USER=ubuntu
usermod -u ${USER_ID} ${USER} 1>/dev/null usermod -u ${USER_ID} ${USER} 1>/dev/null
configure() { configure() {

View File

@@ -1,8 +1,11 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -e set -e
# This script is provided for backwards compatibility with the old docker setup.
# it doesn't do much aside from wrapping the usual docker compose CLI.
SCRIPTDIR=$(dirname "${BASH_SOURCE[0]}") SCRIPTDIR=$(dirname "${BASH_SOURCE[0]}")
cd "$SCRIPTDIR" || exit 1 cd "$SCRIPTDIR" || exit 1
docker-compose up --build -d docker compose up -d
docker-compose logs -f docker compose logs -f

View File

@@ -488,7 +488,7 @@ sections describe what's new for InvokeAI.
- A choice of installer scripts that automate installation and configuration. - A choice of installer scripts that automate installation and configuration.
See See
[Installation](installation/index.md). [Installation](installation/INSTALLATION.md).
- A streamlined manual installation process that works for both Conda and - A streamlined manual installation process that works for both Conda and
PIP-only installs. See PIP-only installs. See
[Manual Installation](installation/020_INSTALL_MANUAL.md). [Manual Installation](installation/020_INSTALL_MANUAL.md).
@@ -657,7 +657,7 @@ sections describe what's new for InvokeAI.
## v1.13 <small>(3 September 2022)</small> ## v1.13 <small>(3 September 2022)</small>
- Support image variations (see [VARIATIONS](features/VARIATIONS.md) - Support image variations (see [VARIATIONS](deprecated/VARIATIONS.md)
([Kevin Gibbons](https://github.com/bakkot) and many contributors and ([Kevin Gibbons](https://github.com/bakkot) and many contributors and
reviewers) reviewers)
- Supports a Google Colab notebook for a standalone server running on Google - Supports a Google Colab notebook for a standalone server running on Google

File diff suppressed because it is too large Load Diff

View File

@@ -45,5 +45,5 @@ For backend related work, please reach out to **@blessedcoolant**, **@lstein**,
## **What does the Code of Conduct mean for me?** ## **What does the Code of Conduct mean for me?**
Our [Code of Conduct](CODE_OF_CONDUCT.md) means that you are responsible for treating everyone on the project with respect and courtesy regardless of their identity. If you are the victim of any inappropriate behavior or comments as described in our Code of Conduct, we are here for you and will do the best to ensure that the abuser is reprimanded appropriately, per our code. Our [Code of Conduct](../../CODE_OF_CONDUCT.md) means that you are responsible for treating everyone on the project with respect and courtesy regardless of their identity. If you are the victim of any inappropriate behavior or comments as described in our Code of Conduct, we are here for you and will do the best to ensure that the abuser is reprimanded appropriately, per our code.

View File

@@ -211,8 +211,8 @@ Here are the invoke> command that apply to txt2img:
| `--facetool <name>` | `-ft <name>` | `-ft gfpgan` | Select face restoration algorithm to use: gfpgan, codeformer | | `--facetool <name>` | `-ft <name>` | `-ft gfpgan` | Select face restoration algorithm to use: gfpgan, codeformer |
| `--codeformer_fidelity` | `-cf <float>` | `0.75` | Used along with CodeFormer. Takes values between 0 and 1. 0 produces high quality but low accuracy. 1 produces high accuracy but low quality | | `--codeformer_fidelity` | `-cf <float>` | `0.75` | Used along with CodeFormer. Takes values between 0 and 1. 0 produces high quality but low accuracy. 1 produces high accuracy but low quality |
| `--save_original` | `-save_orig` | `False` | When upscaling or fixing faces, this will cause the original image to be saved rather than replaced. | | `--save_original` | `-save_orig` | `False` | When upscaling or fixing faces, this will cause the original image to be saved rather than replaced. |
| `--variation <float>` | `-v<float>` | `0.0` | Add a bit of noise (0.0=none, 1.0=high) to the image in order to generate a series of variations. Usually used in combination with `-S<seed>` and `-n<int>` to generate a series a riffs on a starting image. See [Variations](../features/VARIATIONS.md). | | `--variation <float>` | `-v<float>` | `0.0` | Add a bit of noise (0.0=none, 1.0=high) to the image in order to generate a series of variations. Usually used in combination with `-S<seed>` and `-n<int>` to generate a series a riffs on a starting image. See [Variations](VARIATIONS.md). |
| `--with_variations <pattern>` | | `None` | Combine two or more variations. See [Variations](../features/VARIATIONS.md) for now to use this. | | `--with_variations <pattern>` | | `None` | Combine two or more variations. See [Variations](VARIATIONS.md) for now to use this. |
| `--save_intermediates <n>` | | `None` | Save the image from every nth step into an "intermediates" folder inside the output directory | | `--save_intermediates <n>` | | `None` | Save the image from every nth step into an "intermediates" folder inside the output directory |
| `--h_symmetry_time_pct <float>` | | `None` | Create symmetry along the X axis at the desired percent complete of the generation process. (Must be between 0.0 and 1.0; set to a very small number like 0.0001 for just after the first step of generation.) | | `--h_symmetry_time_pct <float>` | | `None` | Create symmetry along the X axis at the desired percent complete of the generation process. (Must be between 0.0 and 1.0; set to a very small number like 0.0001 for just after the first step of generation.) |
| `--v_symmetry_time_pct <float>` | | `None` | Create symmetry along the Y axis at the desired percent complete of the generation process. (Must be between 0.0 and 1.0; set to a very small number like 0.0001 for just after the first step of generation.) | | `--v_symmetry_time_pct <float>` | | `None` | Create symmetry along the Y axis at the desired percent complete of the generation process. (Must be between 0.0 and 1.0; set to a very small number like 0.0001 for just after the first step of generation.) |

View File

@@ -126,6 +126,6 @@ amounts of image-to-image variation even when the seed is fixed and the
`-v` argument is very low. Others are more deterministic. Feel free to `-v` argument is very low. Others are more deterministic. Feel free to
experiment until you find the combination that you like. experiment until you find the combination that you like.
Also be aware of the [Perlin Noise](OTHER.md#thresholding-and-perlin-noise-initialization-options) Also be aware of the [Perlin Noise](../features/OTHER.md#thresholding-and-perlin-noise-initialization-options)
feature, which provides another way of introducing variability into your feature, which provides another way of introducing variability into your
image generation requests. image generation requests.

View File

@@ -28,8 +28,9 @@ by placing them in the designated directory for the compatible model type
### An Example ### An Example
Here are a few examples to illustrate how it works. All these images were Here are a few examples to illustrate how it works. All these images
generated using the command-line client and the Stable Diffusion 1.5 model: were generated using the legacy command-line client and the Stable
Diffusion 1.5 model:
| Japanese gardener | Japanese gardener &lt;ghibli-face&gt; | Japanese gardener &lt;hoi4-leaders&gt; | Japanese gardener &lt;cartoona-animals&gt; | | Japanese gardener | Japanese gardener &lt;ghibli-face&gt; | Japanese gardener &lt;hoi4-leaders&gt; | Japanese gardener &lt;cartoona-animals&gt; |
| :--------------------------------: | :-----------------------------------: | :------------------------------------: | :----------------------------------------: | | :--------------------------------: | :-----------------------------------: | :------------------------------------: | :----------------------------------------: |

View File

@@ -82,7 +82,7 @@ format of YAML files can be found
[here](https://circleci.com/blog/what-is-yaml-a-beginner-s-guide/). [here](https://circleci.com/blog/what-is-yaml-a-beginner-s-guide/).
You can fix a broken `invokeai.yaml` by deleting it and running the You can fix a broken `invokeai.yaml` by deleting it and running the
configuration script again -- option [7] in the launcher, "Re-run the configuration script again -- option [6] in the launcher, "Re-run the
configure script". configure script".
#### Reading Environment Variables #### Reading Environment Variables

View File

@@ -17,9 +17,6 @@ image generation, providing you with a way to direct the network
towards generating images that better fit your desired style or towards generating images that better fit your desired style or
outcome. outcome.
#### How it works
ControlNet works by analyzing an input image, pre-processing that ControlNet works by analyzing an input image, pre-processing that
image to identify relevant information that can be interpreted by each image to identify relevant information that can be interpreted by each
specific ControlNet model, and then inserting that control information specific ControlNet model, and then inserting that control information
@@ -27,35 +24,21 @@ into the generation process. This can be used to adjust the style,
composition, or other aspects of the image to better achieve a composition, or other aspects of the image to better achieve a
specific result. specific result.
#### Installation
#### Models
InvokeAI provides access to a series of ControlNet models that provide InvokeAI provides access to a series of ControlNet models that provide
different effects or styles in your generated images. Currently different effects or styles in your generated images.
InvokeAI only supports "diffuser" style ControlNet models. These are
folders that contain the files `config.json` and/or
`diffusion_pytorch_model.safetensors` and
`diffusion_pytorch_model.fp16.safetensors`. The name of the folder is
the name of the model.
***InvokeAI does not currently support checkpoint-format To install ControlNet Models:
ControlNets. These come in the form of a single file with the
extension `.safetensors`.***
Diffuser-style ControlNet models are available at HuggingFace 1. The easiest way to install them is
(http://huggingface.co) and accessed via their repo IDs (identifiers
in the format "author/modelname"). The easiest way to install them is
to use the InvokeAI model installer application. Use the to use the InvokeAI model installer application. Use the
`invoke.sh`/`invoke.bat` launcher to select item [5] and then navigate `invoke.sh`/`invoke.bat` launcher to select item [4] and then navigate
to the CONTROLNETS section. Select the models you wish to install and to the CONTROLNETS section. Select the models you wish to install and
press "APPLY CHANGES". You may also enter additional HuggingFace press "APPLY CHANGES". You may also enter additional HuggingFace
repo_ids in the "Additional models" textbox: repo_ids in the "Additional models" textbox.
2. Using the "Add Model" function of the model manager, enter the HuggingFace Repo ID of the ControlNet. The ID is in the format "author/repoName"
![Model Installer -
Controlnetl](../assets/installing-models/model-installer-controlnet.png){:width="640px"}
Command-line users can launch the model installer using the command
`invokeai-model-install`.
_Be aware that some ControlNet models require additional code _Be aware that some ControlNet models require additional code
functionality in order to work properly, so just installing a functionality in order to work properly, so just installing a
@@ -63,6 +46,17 @@ third-party ControlNet model may not have the desired effect._ Please
read and follow the documentation for installing a third party model read and follow the documentation for installing a third party model
not currently included among InvokeAI's default list. not currently included among InvokeAI's default list.
Currently InvokeAI **only** supports 🤗 Diffusers-format ControlNet models. These are
folders that contain the files `config.json` and/or
`diffusion_pytorch_model.safetensors` and
`diffusion_pytorch_model.fp16.safetensors`. The name of the folder is
the name of the model.
🤗 Diffusers-format ControlNet models are available at HuggingFace
(http://huggingface.co) and accessed via their repo IDs (identifiers
in the format "author/modelname").
#### ControlNet Models
The models currently supported include: The models currently supported include:
**Canny**: **Canny**:
@@ -133,6 +127,29 @@ Start/End - 0 represents the start of the generation, 1 represents the end. The
Additionally, each ControlNet section can be expanded in order to manipulate settings for the image pre-processor that adjusts your uploaded image before using it in when you Invoke. Additionally, each ControlNet section can be expanded in order to manipulate settings for the image pre-processor that adjusts your uploaded image before using it in when you Invoke.
## T2I-Adapter
[T2I-Adapter](https://github.com/TencentARC/T2I-Adapter) is a tool similar to ControlNet that allows for control over the generation process by providing control information during the generation process. T2I-Adapter models tend to be smaller and more efficient than ControlNets.
##### Installation
To install T2I-Adapter Models:
1. The easiest way to install models is
to use the InvokeAI model installer application. Use the
`invoke.sh`/`invoke.bat` launcher to select item [5] and then navigate
to the T2I-Adapters section. Select the models you wish to install and
press "APPLY CHANGES". You may also enter additional HuggingFace
repo_ids in the "Additional models" textbox.
2. Using the "Add Model" function of the model manager, enter the HuggingFace Repo ID of the T2I-Adapter. The ID is in the format "author/repoName"
#### Usage
Each T2I Adapter has two settings that are applied.
Weight - Strength of the model applied to the generation for the section, defined by start/end.
Start/End - 0 represents the start of the generation, 1 represents the end. The Start/end setting controls what steps during the generation process have the ControlNet applied.
Additionally, each section can be expanded with the "Show Advanced" button in order to manipulate settings for the image pre-processor that adjusts your uploaded image before using it in during the generation process.
## IP-Adapter ## IP-Adapter
@@ -140,13 +157,13 @@ Additionally, each ControlNet section can be expanded in order to manipulate set
![IP-Adapter + T2I](https://github.com/tencent-ailab/IP-Adapter/raw/main/assets/demo/ip_adpter_plus_multi.jpg) ![IP-Adapter + T2I](https://github.com/tencent-ailab/IP-Adapter/raw/main/assets/demo/ip_adpter_plus_multi.jpg)
![IP-Adapter + IMG2IMG](https://github.com/tencent-ailab/IP-Adapter/blob/main/assets/demo/image-to-image.jpg) ![IP-Adapter + IMG2IMG](https://raw.githubusercontent.com/tencent-ailab/IP-Adapter/main/assets/demo/image-to-image.jpg)
#### Installation #### Installation
There are several ways to install IP-Adapter models with an existing InvokeAI installation: There are several ways to install IP-Adapter models with an existing InvokeAI installation:
1. Through the command line interface launched from the invoke.sh / invoke.bat scripts, option [5] to download models. 1. Through the command line interface launched from the invoke.sh / invoke.bat scripts, option [4] to download models.
2. Through the Model Manager UI with models from the *Tools* section of [www.models.invoke.ai](www.models.invoke.ai). To do this, copy the repo ID from the desired model page, and paste it in the Add Model field of the model manager. **Note** Both the IP-Adapter and the Image Encoder must be installed for IP-Adapter to work. For example, the [SD 1.5 IP-Adapter](https://models.invoke.ai/InvokeAI/ip_adapter_plus_sd15) and [SD1.5 Image Encoder](https://models.invoke.ai/InvokeAI/ip_adapter_sd_image_encoder) must be installed to use IP-Adapter with SD1.5 based models. 2. Through the Model Manager UI with models from the *Tools* section of [www.models.invoke.ai](https://www.models.invoke.ai). To do this, copy the repo ID from the desired model page, and paste it in the Add Model field of the model manager. **Note** Both the IP-Adapter and the Image Encoder must be installed for IP-Adapter to work. For example, the [SD 1.5 IP-Adapter](https://models.invoke.ai/InvokeAI/ip_adapter_plus_sd15) and [SD1.5 Image Encoder](https://models.invoke.ai/InvokeAI/ip_adapter_sd_image_encoder) must be installed to use IP-Adapter with SD1.5 based models.
3. **Advanced -- Not recommended ** Manually downloading the IP-Adapter and Image Encoder files - Image Encoder folders shouid be placed in the `models\any\clip_vision` folders. IP Adapter Model folders should be placed in the relevant `ip-adapter` folder of relevant base model folder of Invoke root directory. For example, for the SDXL IP-Adapter, files should be added to the `model/sdxl/ip_adapter/` folder. 3. **Advanced -- Not recommended ** Manually downloading the IP-Adapter and Image Encoder files - Image Encoder folders shouid be placed in the `models\any\clip_vision` folders. IP Adapter Model folders should be placed in the relevant `ip-adapter` folder of relevant base model folder of Invoke root directory. For example, for the SDXL IP-Adapter, files should be added to the `model/sdxl/ip_adapter/` folder.
#### Using IP-Adapter #### Using IP-Adapter

View File

@@ -16,9 +16,10 @@ Model Merging can be be done by navigating to the Model Manager and clicking the
display all the diffusers-style models that InvokeAI knows about. display all the diffusers-style models that InvokeAI knows about.
If you do not see the model you are looking for, then it is probably If you do not see the model you are looking for, then it is probably
a legacy checkpoint model and needs to be converted using the a legacy checkpoint model and needs to be converted using the
`invoke` command-line client and its `!optimize` command. You "Convert" option in the Web-based Model Manager tab.
must select at least two models to merge. The third can be left at
"None" if you desire. You must select at least two models to merge. The third can be left
at "None" if you desire.
* Alpha: This is the ratio to use when combining models. It ranges * Alpha: This is the ratio to use when combining models. It ranges
from 0 to 1. The higher the value, the more weight is given to the from 0 to 1. The higher the value, the more weight is given to the

View File

@@ -8,7 +8,7 @@ title: Command-line Utilities
InvokeAI comes with several scripts that are accessible via the InvokeAI comes with several scripts that are accessible via the
command line. To access these commands, start the "developer's command line. To access these commands, start the "developer's
console" from the launcher (`invoke.bat` menu item [8]). Users who are console" from the launcher (`invoke.bat` menu item [7]). Users who are
familiar with Python can alternatively activate InvokeAI's virtual familiar with Python can alternatively activate InvokeAI's virtual
environment (typically, but not necessarily `invokeai/.venv`). environment (typically, but not necessarily `invokeai/.venv`).
@@ -34,7 +34,7 @@ invokeai-web --ram 7
## **invokeai-merge** ## **invokeai-merge**
This is the model merge script, the same as launcher option [4]. Call This is the model merge script, the same as launcher option [3]. Call
it with the `--gui` command-line argument to start the interactive it with the `--gui` command-line argument to start the interactive
console-based GUI. Alternatively, you can run it non-interactively console-based GUI. Alternatively, you can run it non-interactively
using command-line arguments as illustrated in the example below which using command-line arguments as illustrated in the example below which
@@ -48,7 +48,7 @@ invokeai-merge --force --base-model sd-1 --models stable-diffusion-1.5 inkdiffus
## **invokeai-ti** ## **invokeai-ti**
This is the textual inversion training script that is run by launcher This is the textual inversion training script that is run by launcher
option [3]. Call it with `--gui` to run the interactive console-based option [2]. Call it with `--gui` to run the interactive console-based
front end. It can also be run non-interactively. It has about a front end. It can also be run non-interactively. It has about a
zillion arguments, but a typical training session can be launched zillion arguments, but a typical training session can be launched
with: with:
@@ -68,7 +68,7 @@ in Windows).
## **invokeai-install** ## **invokeai-install**
This is the console-based model install script that is run by launcher This is the console-based model install script that is run by launcher
option [5]. If called without arguments, it will launch the option [4]. If called without arguments, it will launch the
interactive console-based interface. It can also be used interactive console-based interface. It can also be used
non-interactively to list, add and remove models as shown by these non-interactively to list, add and remove models as shown by these
examples: examples:
@@ -148,7 +148,7 @@ launch the web server against it with `invokeai-web --root InvokeAI-New`.
## **invokeai-update** ## **invokeai-update**
This is the interactive console-based script that is run by launcher This is the interactive console-based script that is run by launcher
menu item [9] to update to a new version of InvokeAI. It takes no menu item [8] to update to a new version of InvokeAI. It takes no
command-line arguments. command-line arguments.
## **invokeai-metadata** ## **invokeai-metadata**

View File

@@ -28,7 +28,7 @@ Learn how to install and use ControlNet models for fine control over
image output. image output.
### * [Image-to-Image Guide](IMG2IMG.md) ### * [Image-to-Image Guide](IMG2IMG.md)
Use a seed image to build new creations in the CLI. Use a seed image to build new creations.
## Model Management ## Model Management

View File

@@ -57,7 +57,9 @@ Prompts provide the models directions on what to generate. As a general rule of
Models are the magic that power InvokeAI. These files represent the output of training a machine on understanding massive amounts of images - providing them with the capability to generate new images using just a text description of what youd like to see. (Like Stable Diffusion!) Models are the magic that power InvokeAI. These files represent the output of training a machine on understanding massive amounts of images - providing them with the capability to generate new images using just a text description of what youd like to see. (Like Stable Diffusion!)
Invoke offers a simple way to download several different models upon installation, but many more can be discovered online, including at ****. Each model can produce a unique style of output, based on the images it was trained on - Try out different models to see which best fits your creative vision! Invoke offers a simple way to download several different models upon installation, but many more can be discovered online, including at https://models.invoke.ai
Each model can produce a unique style of output, based on the images it was trained on - Try out different models to see which best fits your creative vision!
- *Models that contain “inpainting” in the name are designed for use with the inpainting feature of the Unified Canvas* - *Models that contain “inpainting” in the name are designed for use with the inpainting feature of the Unified Canvas*

View File

@@ -143,7 +143,6 @@ Mac and Linux machines, and runs on GPU cards with as little as 4 GB of RAM.
<!-- seperator --> <!-- seperator -->
### Prompt Engineering ### Prompt Engineering
- [Prompt Syntax](features/PROMPTS.md) - [Prompt Syntax](features/PROMPTS.md)
- [Generating Variations](features/VARIATIONS.md)
### InvokeAI Configuration ### InvokeAI Configuration
- [Guide to InvokeAI Runtime Settings](features/CONFIGURATION.md) - [Guide to InvokeAI Runtime Settings](features/CONFIGURATION.md)
@@ -166,10 +165,8 @@ still a work in progress, but coming soon.
### Command-Line Interface Retired ### Command-Line Interface Retired
The original "invokeai" command-line interface has been retired. The All "invokeai" command-line interfaces have been retired as of version
`invokeai` command will now launch a new command-line client that can 3.4.
be used by developers to create and test nodes. It is not intended to
be used for routine image generation or manipulation.
To launch the Web GUI from the command-line, use the command To launch the Web GUI from the command-line, use the command
`invokeai-web` rather than the traditional `invokeai --web`. `invokeai-web` rather than the traditional `invokeai --web`.
@@ -201,6 +198,7 @@ The list of schedulers has been completely revamped and brought up to date:
| **dpmpp_2m** | DPMSolverMultistepScheduler | original noise scnedule | | **dpmpp_2m** | DPMSolverMultistepScheduler | original noise scnedule |
| **dpmpp_2m_k** | DPMSolverMultistepScheduler | using karras noise schedule | | **dpmpp_2m_k** | DPMSolverMultistepScheduler | using karras noise schedule |
| **unipc** | UniPCMultistepScheduler | CPU only | | **unipc** | UniPCMultistepScheduler | CPU only |
| **lcm** | LCMScheduler | |
Please see [3.0.0 Release Notes](https://github.com/invoke-ai/InvokeAI/releases/tag/v3.0.0) for further details. Please see [3.0.0 Release Notes](https://github.com/invoke-ai/InvokeAI/releases/tag/v3.0.0) for further details.

View File

@@ -40,7 +40,7 @@ experimental versions later.
this, open up a command-line window ("Terminal" on Linux and this, open up a command-line window ("Terminal" on Linux and
Macintosh, "Command" or "Powershell" on Windows) and type `python Macintosh, "Command" or "Powershell" on Windows) and type `python
--version`. If Python is installed, it will print out the version --version`. If Python is installed, it will print out the version
number. If it is version `3.9.*`, `3.10.*` or `3.11.*` you meet number. If it is version `3.10.*` or `3.11.*` you meet
requirements. requirements.
!!! warning "What to do if you have an unsupported version" !!! warning "What to do if you have an unsupported version"
@@ -48,7 +48,7 @@ experimental versions later.
Go to [Python Downloads](https://www.python.org/downloads/) Go to [Python Downloads](https://www.python.org/downloads/)
and download the appropriate installer package for your and download the appropriate installer package for your
platform. We recommend [Version platform. We recommend [Version
3.10.9](https://www.python.org/downloads/release/python-3109/), 3.10.12](https://www.python.org/downloads/release/python-3109/),
which has been extensively tested with InvokeAI. which has been extensively tested with InvokeAI.
_Please select your platform in the section below for platform-specific _Please select your platform in the section below for platform-specific
@@ -179,7 +179,7 @@ experimental versions later.
you will have the choice of CUDA (NVidia cards), ROCm (AMD cards), you will have the choice of CUDA (NVidia cards), ROCm (AMD cards),
or CPU (no graphics acceleration). On Windows, you'll have the or CPU (no graphics acceleration). On Windows, you'll have the
choice of CUDA vs CPU, and on Macs you'll be offered CPU only. When choice of CUDA vs CPU, and on Macs you'll be offered CPU only. When
you select CPU on M1 or M2 Macintoshes, you will get MPS-based you select CPU on M1/M2/M3 Macintoshes, you will get MPS-based
graphics acceleration without installing additional drivers. If you graphics acceleration without installing additional drivers. If you
are unsure what GPU you are using, you can ask the installer to are unsure what GPU you are using, you can ask the installer to
guess. guess.
@@ -471,7 +471,7 @@ Then type the following commands:
=== "NVIDIA System" === "NVIDIA System"
```bash ```bash
pip install torch torchvision --force-reinstall --extra-index-url https://download.pytorch.org/whl/cu118 pip install torch torchvision --force-reinstall --extra-index-url https://download.pytorch.org/whl/cu121
pip install xformers pip install xformers
``` ```

View File

@@ -32,7 +32,7 @@ gaming):
* **Python** * **Python**
version 3.9 through 3.11 version 3.10 through 3.11
* **CUDA Tools** * **CUDA Tools**
@@ -65,7 +65,7 @@ gaming):
To install InvokeAI with virtual environments and the PIP package To install InvokeAI with virtual environments and the PIP package
manager, please follow these steps: manager, please follow these steps:
1. Please make sure you are using Python 3.9 through 3.11. The rest of the install 1. Please make sure you are using Python 3.10 through 3.11. The rest of the install
procedure depends on this and will not work with other versions: procedure depends on this and will not work with other versions:
```bash ```bash
@@ -148,7 +148,7 @@ manager, please follow these steps:
=== "CUDA (NVidia)" === "CUDA (NVidia)"
```bash ```bash
pip install "InvokeAI[xformers]" --use-pep517 --extra-index-url https://download.pytorch.org/whl/cu118 pip install "InvokeAI[xformers]" --use-pep517 --extra-index-url https://download.pytorch.org/whl/cu121
``` ```
=== "ROCm (AMD)" === "ROCm (AMD)"
@@ -327,7 +327,7 @@ installation protocol (important!)
=== "CUDA (NVidia)" === "CUDA (NVidia)"
```bash ```bash
pip install -e .[xformers] --use-pep517 --extra-index-url https://download.pytorch.org/whl/cu118 pip install -e .[xformers] --use-pep517 --extra-index-url https://download.pytorch.org/whl/cu121
``` ```
=== "ROCm (AMD)" === "ROCm (AMD)"
@@ -375,7 +375,7 @@ you can do so using this unsupported recipe:
mkdir ~/invokeai mkdir ~/invokeai
conda create -n invokeai python=3.10 conda create -n invokeai python=3.10
conda activate invokeai conda activate invokeai
pip install InvokeAI[xformers] --use-pep517 --extra-index-url https://download.pytorch.org/whl/cu118 pip install InvokeAI[xformers] --use-pep517 --extra-index-url https://download.pytorch.org/whl/cu121
invokeai-configure --root ~/invokeai invokeai-configure --root ~/invokeai
invokeai --root ~/invokeai --web invokeai --root ~/invokeai --web
``` ```

View File

@@ -85,7 +85,7 @@ You can find which version you should download from [this link](https://docs.nvi
When installing torch and torchvision manually with `pip`, remember to provide When installing torch and torchvision manually with `pip`, remember to provide
the argument `--extra-index-url the argument `--extra-index-url
https://download.pytorch.org/whl/cu118` as described in the [Manual https://download.pytorch.org/whl/cu121` as described in the [Manual
Installation Guide](020_INSTALL_MANUAL.md). Installation Guide](020_INSTALL_MANUAL.md).
## :simple-amd: ROCm ## :simple-amd: ROCm

View File

@@ -4,38 +4,49 @@ title: Installing with Docker
# :fontawesome-brands-docker: Docker # :fontawesome-brands-docker: Docker
!!! warning "For most users" !!! warning "macOS and AMD GPU Users"
We highly recommend to Install InvokeAI locally using [these instructions](INSTALLATION.md) We highly recommend to Install InvokeAI locally using [these instructions](INSTALLATION.md),
because Docker containers can not access the GPU on macOS.
!!! tip "For developers" !!! warning "AMD GPU Users"
For container-related development tasks or for enabling easy Container support for AMD GPUs has been reported to work by the community, but has not received
deployment to other environments (on-premises or cloud), follow these extensive testing. Please make sure to set the `GPU_DRIVER=rocm` environment variable (see below), and
instructions. use the `build.sh` script to build the image for this to take effect at build time.
For general use, install locally to leverage your machine's GPU. !!! tip "Linux and Windows Users"
For optimal performance, configure your Docker daemon to access your machine's GPU.
Docker Desktop on Windows [includes GPU support](https://www.docker.com/blog/wsl-2-gpu-support-for-docker-desktop-on-nvidia-gpus/).
Linux users should install and configure the [NVIDIA Container Toolkit](https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html)
## Why containers? ## Why containers?
They provide a flexible, reliable way to build and deploy InvokeAI. You'll also They provide a flexible, reliable way to build and deploy InvokeAI.
use a Docker volume to store the largest model files and image outputs as a See [Processes](https://12factor.net/processes) under the Twelve-Factor App
first step in decoupling storage and compute. Future enhancements can do this methodology for details on why running applications in such a stateless fashion is important.
for other assets. See [Processes](https://12factor.net/processes) under the
Twelve-Factor App methodology for details on why running applications in such a
stateless fashion is important.
You can specify the target platform when building the image and running the The container is configured for CUDA by default, but can be built to support AMD GPUs
container. You'll also need to specify the InvokeAI requirements file that by setting the `GPU_DRIVER=rocm` environment variable at Docker image build time.
matches the container's OS and the architecture it will run on.
Developers on Apple silicon (M1/M2): You Developers on Apple silicon (M1/M2/M3): You
[can't access your GPU cores from Docker containers](https://github.com/pytorch/pytorch/issues/81224) [can't access your GPU cores from Docker containers](https://github.com/pytorch/pytorch/issues/81224)
and performance is reduced compared with running it directly on macOS but for and performance is reduced compared with running it directly on macOS but for
development purposes it's fine. Once you're done with development tasks on your development purposes it's fine. Once you're done with development tasks on your
laptop you can build for the target platform and architecture and deploy to laptop you can build for the target platform and architecture and deploy to
another environment with NVIDIA GPUs on-premises or in the cloud. another environment with NVIDIA GPUs on-premises or in the cloud.
## TL;DR
This assumes properly configured Docker on Linux or Windows/WSL2. Read on for detailed customization options.
```bash
# docker compose commands should be run from the `docker` directory
cd docker
docker compose up
```
## Installation in a Linux container (desktop) ## Installation in a Linux container (desktop)
### Prerequisites ### Prerequisites
@@ -58,222 +69,44 @@ a token and copy it, since you will need in for the next step.
### Setup ### Setup
Set the fork you want to use and other variables. Set up your environmnent variables. In the `docker` directory, make a copy of `env.sample` and name it `.env`. Make changes as necessary.
!!! tip Any environment variables supported by InvokeAI can be set here - please see the [CONFIGURATION](../features/CONFIGURATION.md) for further detail.
I preffer to save my env vars At a minimum, you might want to set the `INVOKEAI_ROOT` environment variable
in the repository root in a `.env` (or `.envrc`) file to automatically re-apply to point to the location where you wish to store your InvokeAI models, configuration, and outputs.
them when I come back.
The build- and run- scripts contain default values for almost everything,
besides the [Hugging Face Token](https://huggingface.co/settings/tokens) you
created in the last step.
Some Suggestions of variables you may want to change besides the Token:
<figure markdown> <figure markdown>
| Environment-Variable <img width="220" align="right"/> | Default value <img width="360" align="right"/> | Description | | Environment-Variable <img width="220" align="right"/> | Default value <img width="360" align="right"/> | Description |
| ----------------------------------------------------- | ---------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ----------------------------------------------------- | ---------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `HUGGING_FACE_HUB_TOKEN` | No default, but **required**! | This is the only **required** variable, without it you can't download the huggingface models | | `INVOKEAI_ROOT` | `~/invokeai` | **Required** - the location of your InvokeAI root directory. It will be created if it does not exist.
| `REPOSITORY_NAME` | The Basename of the Repo folder | This name will used as the container repository/image name | | `HUGGING_FACE_HUB_TOKEN` | | InvokeAI will work without it, but some of the integrations with HuggingFace (like downloading from models from private repositories) may not work|
| `VOLUMENAME` | `${REPOSITORY_NAME,,}_data` | Name of the Docker Volume where model files will be stored | | `GPU_DRIVER` | `cuda` | Optionally change this to `rocm` to build the image for AMD GPUs. NOTE: Use the `build.sh` script to build the image for this to take effect.
| `ARCH` | arch of the build machine | Can be changed if you want to build the image for another arch |
| `CONTAINER_REGISTRY` | ghcr.io | Name of the Container Registry to use for the full tag |
| `CONTAINER_REPOSITORY` | `$(whoami)/${REPOSITORY_NAME}` | Name of the Container Repository |
| `CONTAINER_FLAVOR` | `cuda` | The flavor of the image to built, available options are `cuda`, `rocm` and `cpu`. If you choose `rocm` or `cpu`, the extra-index-url will be selected automatically, unless you set one yourself. |
| `CONTAINER_TAG` | `${INVOKEAI_BRANCH##*/}-${CONTAINER_FLAVOR}` | The Container Repository / Tag which will be used |
| `INVOKE_DOCKERFILE` | `Dockerfile` | The Dockerfile which should be built, handy for development |
| `PIP_EXTRA_INDEX_URL` | | If you want to use a custom pip-extra-index-url |
</figure> </figure>
#### Build the Image #### Build the Image
I provided a build script, which is located next to the Dockerfile in Use the standard `docker compose build` command from within the `docker` directory.
`docker/build.sh`. It can be executed from repository root like this:
```bash If using an AMD GPU:
./docker/build.sh a: set the `GPU_DRIVER=rocm` environment variable in `docker-compose.yml` and continue using `docker compose build` as usual, or
``` b: set `GPU_DRIVER=rocm` in the `.env` file and use the `build.sh` script, provided for convenience
The build Script not only builds the container, but also creates the docker
volume if not existing yet.
#### Run the Container #### Run the Container
After the build process is done, you can run the container via the provided Use the standard `docker compose up` command, and generally the `docker compose` [CLI](https://docs.docker.com/compose/reference/) as usual.
`docker/run.sh` script
```bash Once the container starts up (and configures the InvokeAI root directory if this is a new installation), you can access InvokeAI at [http://localhost:9090](http://localhost:9090)
./docker/run.sh
```
When used without arguments, the container will start the webserver and provide ## Troubleshooting / FAQ
you the link to open it. But if you want to use some other parameters you can
also do so.
!!! example "run script example" - Q: I am running on Windows under WSL2, and am seeing a "no such file or directory" error.
- A: Your `docker-entrypoint.sh` file likely has Windows (CRLF) as opposed to Unix (LF) line endings,
```bash and you may have cloned this repository before the issue was fixed. To solve this, please change
./docker/run.sh "banana sushi" -Ak_lms -S42 -s10 the line endings in the `docker-entrypoint.sh` file to `LF`. You can do this in VSCode
``` (`Ctrl+P` and search for "line endings"), or by using the `dos2unix` utility in WSL.
Finally, you may delete `docker-entrypoint.sh` followed by `git pull; git checkout docker/docker-entrypoint.sh`
This would generate the legendary "banana sushi" with Seed 42, k_lms Sampler and 10 steps. to reset the file to its most recent version.
For more information on this issue, please see the [Docker Desktop documentation](https://docs.docker.com/desktop/troubleshoot/topics/#avoid-unexpected-syntax-errors-use-unix-style-line-endings-for-files-in-containers)
Find out more about available CLI-Parameters at [features/CLI.md](../../features/CLI/#arguments)
---
## Running the container on your GPU
If you have an Nvidia GPU, you can enable InvokeAI to run on the GPU by running
the container with an extra environment variable to enable GPU usage and have
the process run much faster:
```bash
GPU_FLAGS=all ./docker/run.sh
```
This passes the `--gpus all` to docker and uses the GPU.
If you don't have a GPU (or your host is not yet setup to use it) you will see a
message like this:
`docker: Error response from daemon: could not select device driver "" with capabilities: [[gpu]].`
You can use the full set of GPU combinations documented here:
https://docs.docker.com/config/containers/resource_constraints/#gpu
For example, use `GPU_FLAGS=device=GPU-3a23c669-1f69-c64e-cf85-44e9b07e7a2a` to
choose a specific device identified by a UUID.
---
!!! warning "Deprecated"
From here on you will find the the previous Docker-Docs, which will still
provide some usefull informations.
## Usage (time to have fun)
### Startup
If you're on a **Linux container** the `invoke` script is **automatically
started** and the output dir set to the Docker volume you created earlier.
If you're **directly on macOS follow these startup instructions**. With the
Conda environment activated (`conda activate ldm`), run the interactive
interface that combines the functionality of the original scripts `txt2img` and
`img2img`: Use the more accurate but VRAM-intensive full precision math because
half-precision requires autocast and won't work. By default the images are saved
in `outputs/img-samples/`.
```Shell
python3 scripts/invoke.py --full_precision
```
You'll get the script's prompt. You can see available options or quit.
```Shell
invoke> -h
invoke> q
```
### Text to Image
For quick (but bad) image results test with 5 steps (default 50) and 1 sample
image. This will let you know that everything is set up correctly. Then increase
steps to 100 or more for good (but slower) results. The prompt can be in quotes
or not.
```Shell
invoke> The hulk fighting with sheldon cooper -s5 -n1
invoke> "woman closeup highly detailed" -s 150
# Reuse previous seed and apply face restoration
invoke> "woman closeup highly detailed" --steps 150 --seed -1 -G 0.75
```
You'll need to experiment to see if face restoration is making it better or
worse for your specific prompt.
If you're on a container the output is set to the Docker volume. You can copy it
wherever you want. You can download it from the Docker Desktop app, Volumes,
my-vol, data. Or you can copy it from your Mac terminal. Keep in mind
`docker cp` can't expand `*.png` so you'll need to specify the image file name.
On your host Mac (you can use the name of any container that mounted the
volume):
```Shell
docker cp dummy:/data/000001.928403745.png /Users/<your-user>/Pictures
```
### Image to Image
You can also do text-guided image-to-image translation. For example, turning a
sketch into a detailed drawing.
`strength` is a value between 0.0 and 1.0 that controls the amount of noise that
is added to the input image. Values that approach 1.0 allow for lots of
variations but will also produce images that are not semantically consistent
with the input. 0.0 preserves image exactly, 1.0 replaces it completely.
Make sure your input image size dimensions are multiples of 64 e.g. 512x512.
Otherwise you'll get `Error: product of dimension sizes > 2**31'`. If you still
get the error
[try a different size](https://support.apple.com/guide/preview/resize-rotate-or-flip-an-image-prvw2015/mac#:~:text=image's%20file%20size-,In%20the%20Preview%20app%20on%20your%20Mac%2C%20open%20the%20file,is%20shown%20at%20the%20bottom.)
like 512x256.
If you're on a Docker container, copy your input image into the Docker volume
```Shell
docker cp /Users/<your-user>/Pictures/sketch-mountains-input.jpg dummy:/data/
```
Try it out generating an image (or more). The `invoke` script needs absolute
paths to find the image so don't use `~`.
If you're on your Mac
```Shell
invoke> "A fantasy landscape, trending on artstation" -I /Users/<your-user>/Pictures/sketch-mountains-input.jpg --strength 0.75 --steps 100 -n4
```
If you're on a Linux container on your Mac
```Shell
invoke> "A fantasy landscape, trending on artstation" -I /data/sketch-mountains-input.jpg --strength 0.75 --steps 50 -n1
```
### Web Interface
You can use the `invoke` script with a graphical web interface. Start the web
server with:
```Shell
python3 scripts/invoke.py --full_precision --web
```
If it's running on your Mac point your Mac web browser to
<http://127.0.0.1:9090>
Press Control-C at the command line to stop the web server.
### Notes
Some text you can add at the end of the prompt to make it very pretty:
```Shell
cinematic photo, highly detailed, cinematic lighting, ultra-detailed, ultrarealistic, photorealism, Octane Rendering, cyberpunk lights, Hyper Detail, 8K, HD, Unreal Engine, V-Ray, full hd, cyberpunk, abstract, 3d octane render + 4k UHD + immense detail + dramatic lighting + well lit + black, purple, blue, pink, cerulean, teal, metallic colours, + fine details, ultra photoreal, photographic, concept art, cinematic composition, rule of thirds, mysterious, eerie, photorealism, breathtaking detailed, painting art deco pattern, by hsiao, ron cheng, john james audubon, bizarre compositions, exquisite detail, extremely moody lighting, painted by greg rutkowski makoto shinkai takashi takeuchi studio ghibli, akihiko yoshida
```
The original scripts should work as well.
```Shell
python3 scripts/orig_scripts/txt2img.py --help
python3 scripts/orig_scripts/txt2img.py --ddim_steps 100 --n_iter 1 --n_samples 1 --plms --prompt "new born baby kitten. Hyper Detail, Octane Rendering, Unreal Engine, V-Ray"
python3 scripts/orig_scripts/txt2img.py --ddim_steps 5 --n_iter 1 --n_samples 1 --plms --prompt "ocean" # or --klms
```

View File

@@ -84,7 +84,7 @@ InvokeAI root directory's `autoimport` folder.
### Installation via `invokeai-model-install` ### Installation via `invokeai-model-install`
From the `invoke` launcher, choose option [5] "Download and install From the `invoke` launcher, choose option [4] "Download and install
models." This will launch the same script that prompted you to select models." This will launch the same script that prompted you to select
models at install time. You can use this to add models that you models at install time. You can use this to add models that you
skipped the first time around. It is all right to specify a model that skipped the first time around. It is all right to specify a model that

View File

@@ -59,8 +59,7 @@ Prior to installing PyPatchMatch, you need to take the following steps:
`from patchmatch import patch_match`: It should look like the following: `from patchmatch import patch_match`: It should look like the following:
```py ```py
Python 3.9.5 (default, Nov 23 2021, 15:27:38) Python 3.10.12 (main, Jun 11 2023, 05:26:28) [GCC 11.4.0] on linux
[GCC 9.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information. Type "help", "copyright", "credits" or "license" for more information.
>>> from patchmatch import patch_match >>> from patchmatch import patch_match
Compiling and loading c extensions from "/home/lstein/Projects/InvokeAI/.invokeai-env/src/pypatchmatch/patchmatch". Compiling and loading c extensions from "/home/lstein/Projects/InvokeAI/.invokeai-env/src/pypatchmatch/patchmatch".

View File

@@ -28,7 +28,7 @@ command line, then just be sure to activate it's virtual environment.
Then run the following three commands: Then run the following three commands:
```sh ```sh
pip install xformers~=0.0.19 pip install xformers~=0.0.22
pip install triton # WON'T WORK ON WINDOWS pip install triton # WON'T WORK ON WINDOWS
python -m xformers.info output python -m xformers.info output
``` ```
@@ -42,7 +42,7 @@ If all goes well, you'll see a report like the
following: following:
```sh ```sh
xFormers 0.0.20 xFormers 0.0.22
memory_efficient_attention.cutlassF: available memory_efficient_attention.cutlassF: available
memory_efficient_attention.cutlassB: available memory_efficient_attention.cutlassB: available
memory_efficient_attention.flshattF: available memory_efficient_attention.flshattF: available
@@ -59,14 +59,14 @@ swiglu.gemm_fused_operand_sum: available
swiglu.fused.p.cpp: available swiglu.fused.p.cpp: available
is_triton_available: True is_triton_available: True
is_functorch_available: False is_functorch_available: False
pytorch.version: 2.0.1+cu118 pytorch.version: 2.1.0+cu121
pytorch.cuda: available pytorch.cuda: available
gpu.compute_capability: 8.9 gpu.compute_capability: 8.9
gpu.name: NVIDIA GeForce RTX 4070 gpu.name: NVIDIA GeForce RTX 4070
build.info: available build.info: available
build.cuda_version: 1108 build.cuda_version: 1108
build.python_version: 3.10.11 build.python_version: 3.10.11
build.torch_version: 2.0.1+cu118 build.torch_version: 2.1.0+cu121
build.env.TORCH_CUDA_ARCH_LIST: 5.0+PTX 6.0 6.1 7.0 7.5 8.0 8.6 build.env.TORCH_CUDA_ARCH_LIST: 5.0+PTX 6.0 6.1 7.0 7.5 8.0 8.6
build.env.XFORMERS_BUILD_TYPE: Release build.env.XFORMERS_BUILD_TYPE: Release
build.env.XFORMERS_ENABLE_DEBUG_ASSERTIONS: None build.env.XFORMERS_ENABLE_DEBUG_ASSERTIONS: None
@@ -92,33 +92,22 @@ installed from source. These instructions were written for a system
running Ubuntu 22.04, but other Linux distributions should be able to running Ubuntu 22.04, but other Linux distributions should be able to
adapt this recipe. adapt this recipe.
#### 1. Install CUDA Toolkit 11.8 #### 1. Install CUDA Toolkit 12.1
You will need the CUDA developer's toolkit in order to compile and You will need the CUDA developer's toolkit in order to compile and
install xFormers. **Do not try to install Ubuntu's nvidia-cuda-toolkit install xFormers. **Do not try to install Ubuntu's nvidia-cuda-toolkit
package.** It is out of date and will cause conflicts among the NVIDIA package.** It is out of date and will cause conflicts among the NVIDIA
driver and binaries. Instead install the CUDA Toolkit package provided driver and binaries. Instead install the CUDA Toolkit package provided
by NVIDIA itself. Go to [CUDA Toolkit 11.8 by NVIDIA itself. Go to [CUDA Toolkit 12.1
Downloads](https://developer.nvidia.com/cuda-11-8-0-download-archive) Downloads](https://developer.nvidia.com/cuda-12-1-0-download-archive)
and use the target selection wizard to choose your platform and Linux and use the target selection wizard to choose your platform and Linux
distribution. Select an installer type of "runfile (local)" at the distribution. Select an installer type of "runfile (local)" at the
last step. last step.
This will provide you with a recipe for downloading and running a This will provide you with a recipe for downloading and running a
install shell script that will install the toolkit and drivers. For install shell script that will install the toolkit and drivers.
example, the install script recipe for Ubuntu 22.04 running on a
x86_64 system is:
``` #### 2. Confirm/Install pyTorch 2.1.0 with CUDA 12.1 support
wget https://developer.download.nvidia.com/compute/cuda/11.8.0/local_installers/cuda_11.8.0_520.61.05_linux.run
sudo sh cuda_11.8.0_520.61.05_linux.run
```
Rather than cut-and-paste this example, We recommend that you walk
through the toolkit wizard in order to get the most up to date
installer for your system.
#### 2. Confirm/Install pyTorch 2.01 with CUDA 11.8 support
If you are using InvokeAI 3.0.2 or higher, these will already be If you are using InvokeAI 3.0.2 or higher, these will already be
installed. If not, you can check whether you have the needed libraries installed. If not, you can check whether you have the needed libraries
@@ -133,7 +122,7 @@ Then run the command:
python -c 'exec("import torch\nprint(torch.__version__)")' python -c 'exec("import torch\nprint(torch.__version__)")'
``` ```
If it prints __1.13.1+cu118__ you're good. If not, you can install the If it prints __2.1.0+cu121__ you're good. If not, you can install the
most up to date libraries with this command: most up to date libraries with this command:
```sh ```sh

View File

@@ -79,7 +79,7 @@ title: Manual Installation, Linux
and obtaining an access token for downloading. It will then download and and obtaining an access token for downloading. It will then download and
install the weights files for you. install the weights files for you.
Please look [here](../INSTALL_MANUAL.md) for a manual process for doing Please look [here](../020_INSTALL_MANUAL.md) for a manual process for doing
the same thing. the same thing.
7. Start generating images! 7. Start generating images!
@@ -112,7 +112,7 @@ title: Manual Installation, Linux
To use an alternative model you may invoke the `!switch` command in To use an alternative model you may invoke the `!switch` command in
the CLI, or pass `--model <model_name>` during `invoke.py` launch for the CLI, or pass `--model <model_name>` during `invoke.py` launch for
either the CLI or the Web UI. See [Command Line either the CLI or the Web UI. See [Command Line
Client](../../features/CLI.md#model-selection-and-importation). The Client](../../deprecated/CLI.md#model-selection-and-importation). The
model names are defined in `configs/models.yaml`. model names are defined in `configs/models.yaml`.
8. Subsequently, to relaunch the script, be sure to run "conda activate 8. Subsequently, to relaunch the script, be sure to run "conda activate

View File

@@ -150,7 +150,7 @@ will do our best to help.
To use an alternative model you may invoke the `!switch` command in To use an alternative model you may invoke the `!switch` command in
the CLI, or pass `--model <model_name>` during `invoke.py` launch for the CLI, or pass `--model <model_name>` during `invoke.py` launch for
either the CLI or the Web UI. See [Command Line either the CLI or the Web UI. See [Command Line
Client](../../features/CLI.md#model-selection-and-importation). The Client](../../deprecated/CLI.md#model-selection-and-importation). The
model names are defined in `configs/models.yaml`. model names are defined in `configs/models.yaml`.
--- ---

View File

@@ -128,7 +128,7 @@ python scripts/invoke.py --web --max_load_models=3 \
``` ```
These options are described in detail in the These options are described in detail in the
[Command-Line Interface](../../features/CLI.md) documentation. [Command-Line Interface](../../deprecated/CLI.md) documentation.
## Troubleshooting ## Troubleshooting

View File

@@ -75,7 +75,7 @@ Note that you will need NVIDIA drivers, Python 3.10, and Git installed beforehan
obtaining an access token for downloading. It will then download and install the obtaining an access token for downloading. It will then download and install the
weights files for you. weights files for you.
Please look [here](../INSTALL_MANUAL.md) for a manual process for doing the Please look [here](../020_INSTALL_MANUAL.md) for a manual process for doing the
same thing. same thing.
8. Start generating images! 8. Start generating images!
@@ -108,7 +108,7 @@ Note that you will need NVIDIA drivers, Python 3.10, and Git installed beforehan
To use an alternative model you may invoke the `!switch` command in To use an alternative model you may invoke the `!switch` command in
the CLI, or pass `--model <model_name>` during `invoke.py` launch for the CLI, or pass `--model <model_name>` during `invoke.py` launch for
either the CLI or the Web UI. See [Command Line either the CLI or the Web UI. See [Command Line
Client](../../features/CLI.md#model-selection-and-importation). The Client](../../deprecated/CLI.md#model-selection-and-importation). The
model names are defined in `configs/models.yaml`. model names are defined in `configs/models.yaml`.
9. Subsequently, to relaunch the script, first activate the Anaconda 9. Subsequently, to relaunch the script, first activate the Anaconda

View File

@@ -4,11 +4,16 @@ These are nodes that have been developed by the community, for the community. If
If you'd like to submit a node for the community, please refer to the [node creation overview](contributingNodes.md). If you'd like to submit a node for the community, please refer to the [node creation overview](contributingNodes.md).
To download a node, simply download the `.py` node file from the link and add it to the `invokeai/app/invocations` folder in your Invoke AI install location. If you used the automated installation, this can be found inside the `.venv` folder. Along with the node, an example node graph should be provided to help you get started with the node. To use a node, add the node to the `nodes` folder found in your InvokeAI install location.
The suggested method is to use `git clone` to clone the repository the node is found in. This allows for easy updates of the node in the future.
If you'd prefer, you can also just download the `.py` file from the linked repository and add it to the `nodes` folder.
To use a community workflow, download the the `.json` node graph file and load it into Invoke AI via the **Load Workflow** button in the Workflow Editor. To use a community workflow, download the the `.json` node graph file and load it into Invoke AI via the **Load Workflow** button in the Workflow Editor.
- Community Nodes - Community Nodes
+ [Average Images](#average-images)
+ [Depth Map from Wavefront OBJ](#depth-map-from-wavefront-obj) + [Depth Map from Wavefront OBJ](#depth-map-from-wavefront-obj)
+ [Film Grain](#film-grain) + [Film Grain](#film-grain)
+ [Generative Grammar-Based Prompt Nodes](#generative-grammar-based-prompt-nodes) + [Generative Grammar-Based Prompt Nodes](#generative-grammar-based-prompt-nodes)
@@ -33,6 +38,13 @@ To use a community workflow, download the the `.json` node graph file and load i
- [Help](#help) - [Help](#help)
--------------------------------
### Average Images
**Description:** This node takes in a collection of images of the same size and averages them as output. It converts everything to RGB mode first.
**Node Link:** https://github.com/JPPhoto/average-images-node
-------------------------------- --------------------------------
### Depth Map from Wavefront OBJ ### Depth Map from Wavefront OBJ
@@ -177,12 +189,8 @@ This includes 15 Nodes:
**Node Link:** https://github.com/helix4u/load_video_frame **Node Link:** https://github.com/helix4u/load_video_frame
**Example Node Graph:** https://github.com/helix4u/load_video_frame/blob/main/Example_Workflow.json
**Output Example:** **Output Example:**
<img src="https://raw.githubusercontent.com/helix4u/load_video_frame/main/_git_assets/testmp4_embed_converted.gif" width="500" />
<img src="https://github.com/helix4u/load_video_frame/blob/main/testmp4_embed_converted.gif" width="500" />
[Full mp4 of Example Output test.mp4](https://github.com/helix4u/load_video_frame/blob/main/test.mp4)
-------------------------------- --------------------------------
### Make 3D ### Make 3D
@@ -325,9 +333,9 @@ See full docs here: https://github.com/skunkworxdark/XYGrid_nodes/edit/main/READ
**Description:** This node allows you to do super cool things with InvokeAI. **Description:** This node allows you to do super cool things with InvokeAI.
**Node Link:** https://github.com/invoke-ai/InvokeAI/fake_node.py **Node Link:** https://github.com/invoke-ai/InvokeAI/blob/main/invokeai/app/invocations/prompt.py
**Example Node Graph:** https://github.com/invoke-ai/InvokeAI/fake_node_graph.json **Example Workflow:** https://github.com/invoke-ai/InvokeAI/blob/docs/main/docs/workflows/Prompt_from_File.json
**Output Examples** **Output Examples**

View File

@@ -4,7 +4,7 @@ To learn about the specifics of creating a new node, please visit our [Node crea
Once youve created a node and confirmed that it behaves as expected locally, follow these steps: Once youve created a node and confirmed that it behaves as expected locally, follow these steps:
- Make sure the node is contained in a new Python (.py) file. Preferrably, the node is in a repo with a README detaling the nodes usage & examples to help others more easily use your node. - Make sure the node is contained in a new Python (.py) file. Preferably, the node is in a repo with a README detailing the nodes usage & examples to help others more easily use your node. Including the tag "invokeai-node" in your repository's README can also help other users find it more easily.
- Submit a pull request with a link to your node(s) repo in GitHub against the `main` branch to add the node to the [Community Nodes](communityNodes.md) list - Submit a pull request with a link to your node(s) repo in GitHub against the `main` branch to add the node to the [Community Nodes](communityNodes.md) list
- Make sure you are following the template below and have provided all relevant details about the node and what it does. Example output images and workflows are very helpful for other users looking to use your node. - Make sure you are following the template below and have provided all relevant details about the node and what it does. Example output images and workflows are very helpful for other users looking to use your node.
- A maintainer will review the pull request and node. If the node is aligned with the direction of the project, you may be asked for permission to include it in the core project. - A maintainer will review the pull request and node. If the node is aligned with the direction of the project, you may be asked for permission to include it in the core project.

View File

@@ -2,13 +2,17 @@
We've curated some example workflows for you to get started with Workflows in InvokeAI We've curated some example workflows for you to get started with Workflows in InvokeAI
To use them, right click on your desired workflow, press "Download Linked File". You can then use the "Load Workflow" functionality in InvokeAI to load the workflow and start generating images! To use them, right click on your desired workflow, follow the link to GitHub and click the "⬇" button to download the raw file. You can then use the "Load Workflow" functionality in InvokeAI to load the workflow and start generating images!
If you're interested in finding more workflows, checkout the [#share-your-workflows](https://discord.com/channels/1020123559063990373/1130291608097661000) channel in the InvokeAI Discord. If you're interested in finding more workflows, checkout the [#share-your-workflows](https://discord.com/channels/1020123559063990373/1130291608097661000) channel in the InvokeAI Discord.
* [SD1.5 / SD2 Text to Image](https://github.com/invoke-ai/InvokeAI/blob/main/docs/workflows/Text_to_Image.json) * [SD1.5 / SD2 Text to Image](https://github.com/invoke-ai/InvokeAI/blob/main/docs/workflows/Text_to_Image.json)
* [SDXL Text to Image](https://github.com/invoke-ai/InvokeAI/blob/main/docs/workflows/SDXL_Text_to_Image.json) * [SDXL Text to Image](https://github.com/invoke-ai/InvokeAI/blob/docs/main/docs/workflows/SDXL_Text_to_Image.json)
* [SDXL (with Refiner) Text to Image](https://github.com/invoke-ai/InvokeAI/blob/main/docs/workflows/SDXL_Text_to_Image.json) * [SDXL Text to Image with Refiner](https://github.com/invoke-ai/InvokeAI/blob/docs/main/docs/workflows/SDXL_w_Refiner_Text_to_Image.json)
* [Tiled Upscaling with ControlNet](https://github.com/invoke-ai/InvokeAI/blob/main/docs/workflows/ESRGAN_img2img_upscale w_Canny_ControlNet.json) * [Multi ControlNet (Canny & Depth)](https://github.com/invoke-ai/InvokeAI/blob/docs/main/docs/workflows/Multi_ControlNet_Canny_and_Depth.json)
* [Tiled Upscaling with ControlNet](https://github.com/invoke-ai/InvokeAI/blob/main/docs/workflows/ESRGAN_img2img_upscale_w_Canny_ControlNet.json)
* [Prompt From File](https://github.com/invoke-ai/InvokeAI/blob/docs/main/docs/workflows/Prompt_from_File.json)
* [Face Detailer with IP-Adapter & ControlNet](https://github.com/invoke-ai/InvokeAI/blob/docs/main/docs/workflows/Face_Detailer_with_IP-Adapter_and_Canny.json.json)
* [FaceMask](https://github.com/invoke-ai/InvokeAI/blob/main/docs/workflows/FaceMask.json) * [FaceMask](https://github.com/invoke-ai/InvokeAI/blob/main/docs/workflows/FaceMask.json)
* [FaceOff with 2x Face Scaling](https://github.com/invoke-ai/InvokeAI/blob/main/docs/workflows/FaceOff_FaceScale2x.json) * [FaceOff with 2x Face Scaling](https://github.com/invoke-ai/InvokeAI/blob/main/docs/workflows/FaceOff_FaceScale2x.json)
* [QR Code Monster](https://github.com/invoke-ai/InvokeAI/blob/docs/main/docs/workflows/QR_Code_Monster.json)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,985 @@
{
"name": "Multi ControlNet (Canny & Depth)",
"author": "Millu",
"description": "A sample workflow using canny & depth ControlNets to guide the generation process. ",
"version": "0.1.0",
"contact": "millun@invoke.ai",
"tags": "ControlNet, canny, depth",
"notes": "",
"exposedFields": [
{
"nodeId": "54486974-835b-4d81-8f82-05f9f32ce9e9",
"fieldName": "model"
},
{
"nodeId": "7ce68934-3419-42d4-ac70-82cfc9397306",
"fieldName": "prompt"
},
{
"nodeId": "273e3f96-49ea-4dc5-9d5b-9660390f14e1",
"fieldName": "prompt"
},
{
"nodeId": "c4b23e64-7986-40c4-9cad-46327b12e204",
"fieldName": "image"
},
{
"nodeId": "8e860e51-5045-456e-bf04-9a62a2a5c49e",
"fieldName": "image"
}
],
"meta": {
"version": "1.0.0"
},
"nodes": [
{
"id": "8e860e51-5045-456e-bf04-9a62a2a5c49e",
"type": "invocation",
"data": {
"id": "8e860e51-5045-456e-bf04-9a62a2a5c49e",
"type": "image",
"inputs": {
"image": {
"id": "189c8adf-68cc-4774-a729-49da89f6fdf1",
"name": "image",
"type": "ImageField",
"fieldKind": "input",
"label": "Depth Input Image"
}
},
"outputs": {
"image": {
"id": "1a31cacd-9d19-4f32-b558-c5e4aa39ce73",
"name": "image",
"type": "ImageField",
"fieldKind": "output"
},
"width": {
"id": "12f298fd-1d11-4cca-9426-01240f7ec7cf",
"name": "width",
"type": "integer",
"fieldKind": "output"
},
"height": {
"id": "c47dabcb-44e8-40c9-992d-81dca59f598e",
"name": "height",
"type": "integer",
"fieldKind": "output"
}
},
"label": "",
"isOpen": true,
"notes": "",
"embedWorkflow": false,
"isIntermediate": true,
"useCache": true,
"version": "1.0.0"
},
"width": 320,
"height": 225,
"position": {
"x": 3617.163483500202,
"y": 40.5529847930888
}
},
{
"id": "a33199c2-8340-401e-b8a2-42ffa875fc1c",
"type": "invocation",
"data": {
"id": "a33199c2-8340-401e-b8a2-42ffa875fc1c",
"type": "controlnet",
"inputs": {
"image": {
"id": "4e0a3172-d3c2-4005-a84c-fa12a404f8a0",
"name": "image",
"type": "ImageField",
"fieldKind": "input",
"label": ""
},
"control_model": {
"id": "8cb2d998-4086-430a-8b13-94cbc81e3ca3",
"name": "control_model",
"type": "ControlNetModelField",
"fieldKind": "input",
"label": "",
"value": {
"model_name": "sd-controlnet-depth",
"base_model": "sd-1"
}
},
"control_weight": {
"id": "5e32bd8a-9dc8-42d8-9bcc-c2b0460c0b0f",
"name": "control_weight",
"type": "FloatPolymorphic",
"fieldKind": "input",
"label": "",
"value": 1
},
"begin_step_percent": {
"id": "c258a276-352a-416c-8358-152f11005c0c",
"name": "begin_step_percent",
"type": "float",
"fieldKind": "input",
"label": "",
"value": 0
},
"end_step_percent": {
"id": "43001125-0d70-4f87-8e79-da6603ad6c33",
"name": "end_step_percent",
"type": "float",
"fieldKind": "input",
"label": "",
"value": 1
},
"control_mode": {
"id": "d2f14561-9443-4374-9270-e2f05007944e",
"name": "control_mode",
"type": "enum",
"fieldKind": "input",
"label": "",
"value": "balanced"
},
"resize_mode": {
"id": "727ee7d3-8bf6-4c7d-8b8a-43546b3b59cd",
"name": "resize_mode",
"type": "enum",
"fieldKind": "input",
"label": "",
"value": "just_resize"
}
},
"outputs": {
"control": {
"id": "b034aa0f-4d0d-46e4-b5e3-e25a9588d087",
"name": "control",
"type": "ControlField",
"fieldKind": "output"
}
},
"label": "",
"isOpen": true,
"notes": "",
"embedWorkflow": false,
"isIntermediate": true,
"useCache": true,
"version": "1.0.0"
},
"width": 320,
"height": 508,
"position": {
"x": 4477.604342844504,
"y": -49.39005411272677
}
},
{
"id": "273e3f96-49ea-4dc5-9d5b-9660390f14e1",
"type": "invocation",
"data": {
"id": "273e3f96-49ea-4dc5-9d5b-9660390f14e1",
"type": "compel",
"inputs": {
"prompt": {
"id": "7c2c4771-2161-4d77-aced-ff8c4b3f1c15",
"name": "prompt",
"type": "string",
"fieldKind": "input",
"label": "Negative Prompt",
"value": ""
},
"clip": {
"id": "06d59e91-9cca-411d-bf05-86b099b3e8f7",
"name": "clip",
"type": "ClipField",
"fieldKind": "input",
"label": ""
}
},
"outputs": {
"conditioning": {
"id": "858bc33c-134c-4bf6-8855-f943e1d26f14",
"name": "conditioning",
"type": "ConditioningField",
"fieldKind": "output"
}
},
"label": "",
"isOpen": true,
"notes": "",
"embedWorkflow": false,
"isIntermediate": true,
"useCache": true,
"version": "1.0.0"
},
"width": 320,
"height": 261,
"position": {
"x": 4444.706437017514,
"y": -924.0715320874991
}
},
{
"id": "54486974-835b-4d81-8f82-05f9f32ce9e9",
"type": "invocation",
"data": {
"id": "54486974-835b-4d81-8f82-05f9f32ce9e9",
"type": "main_model_loader",
"inputs": {
"model": {
"id": "f4a915a5-593e-4b6d-9198-c78eb5cefaed",
"name": "model",
"type": "MainModelField",
"fieldKind": "input",
"label": "",
"value": {
"model_name": "stable-diffusion-v1-5",
"base_model": "sd-1",
"model_type": "main"
}
}
},
"outputs": {
"unet": {
"id": "ee24fb16-da38-4c66-9fbc-e8f296ed40d2",
"name": "unet",
"type": "UNetField",
"fieldKind": "output"
},
"clip": {
"id": "f3fb0524-8803-41c1-86db-a61a13ee6a33",
"name": "clip",
"type": "ClipField",
"fieldKind": "output"
},
"vae": {
"id": "5c4878a8-b40f-44ab-b146-1c1f42c860b3",
"name": "vae",
"type": "VaeField",
"fieldKind": "output"
}
},
"label": "",
"isOpen": true,
"notes": "",
"embedWorkflow": false,
"isIntermediate": true,
"useCache": true,
"version": "1.0.0"
},
"width": 320,
"height": 226,
"position": {
"x": 3837.096149678291,
"y": -1050.015351148365
}
},
{
"id": "7ce68934-3419-42d4-ac70-82cfc9397306",
"type": "invocation",
"data": {
"id": "7ce68934-3419-42d4-ac70-82cfc9397306",
"type": "compel",
"inputs": {
"prompt": {
"id": "7c2c4771-2161-4d77-aced-ff8c4b3f1c15",
"name": "prompt",
"type": "string",
"fieldKind": "input",
"label": "Positive Prompt",
"value": ""
},
"clip": {
"id": "06d59e91-9cca-411d-bf05-86b099b3e8f7",
"name": "clip",
"type": "ClipField",
"fieldKind": "input",
"label": ""
}
},
"outputs": {
"conditioning": {
"id": "858bc33c-134c-4bf6-8855-f943e1d26f14",
"name": "conditioning",
"type": "ConditioningField",
"fieldKind": "output"
}
},
"label": "",
"isOpen": true,
"notes": "",
"embedWorkflow": false,
"isIntermediate": true,
"useCache": true,
"version": "1.0.0"
},
"width": 320,
"height": 261,
"position": {
"x": 4449.356038911986,
"y": -1201.659695420063
}
},
{
"id": "d204d184-f209-4fae-a0a1-d152800844e1",
"type": "invocation",
"data": {
"id": "d204d184-f209-4fae-a0a1-d152800844e1",
"type": "controlnet",
"inputs": {
"image": {
"id": "4e0a3172-d3c2-4005-a84c-fa12a404f8a0",
"name": "image",
"type": "ImageField",
"fieldKind": "input",
"label": ""
},
"control_model": {
"id": "8cb2d998-4086-430a-8b13-94cbc81e3ca3",
"name": "control_model",
"type": "ControlNetModelField",
"fieldKind": "input",
"label": "",
"value": {
"model_name": "sd-controlnet-canny",
"base_model": "sd-1"
}
},
"control_weight": {
"id": "5e32bd8a-9dc8-42d8-9bcc-c2b0460c0b0f",
"name": "control_weight",
"type": "FloatPolymorphic",
"fieldKind": "input",
"label": "",
"value": 1
},
"begin_step_percent": {
"id": "c258a276-352a-416c-8358-152f11005c0c",
"name": "begin_step_percent",
"type": "float",
"fieldKind": "input",
"label": "",
"value": 0
},
"end_step_percent": {
"id": "43001125-0d70-4f87-8e79-da6603ad6c33",
"name": "end_step_percent",
"type": "float",
"fieldKind": "input",
"label": "",
"value": 1
},
"control_mode": {
"id": "d2f14561-9443-4374-9270-e2f05007944e",
"name": "control_mode",
"type": "enum",
"fieldKind": "input",
"label": "",
"value": "balanced"
},
"resize_mode": {
"id": "727ee7d3-8bf6-4c7d-8b8a-43546b3b59cd",
"name": "resize_mode",
"type": "enum",
"fieldKind": "input",
"label": "",
"value": "just_resize"
}
},
"outputs": {
"control": {
"id": "b034aa0f-4d0d-46e4-b5e3-e25a9588d087",
"name": "control",
"type": "ControlField",
"fieldKind": "output"
}
},
"label": "",
"isOpen": true,
"notes": "",
"embedWorkflow": false,
"isIntermediate": true,
"useCache": true,
"version": "1.0.0"
},
"width": 320,
"height": 508,
"position": {
"x": 4479.68542130465,
"y": -618.4221638099414
}
},
{
"id": "c4b23e64-7986-40c4-9cad-46327b12e204",
"type": "invocation",
"data": {
"id": "c4b23e64-7986-40c4-9cad-46327b12e204",
"type": "image",
"inputs": {
"image": {
"id": "189c8adf-68cc-4774-a729-49da89f6fdf1",
"name": "image",
"type": "ImageField",
"fieldKind": "input",
"label": "Canny Input Image"
}
},
"outputs": {
"image": {
"id": "1a31cacd-9d19-4f32-b558-c5e4aa39ce73",
"name": "image",
"type": "ImageField",
"fieldKind": "output"
},
"width": {
"id": "12f298fd-1d11-4cca-9426-01240f7ec7cf",
"name": "width",
"type": "integer",
"fieldKind": "output"
},
"height": {
"id": "c47dabcb-44e8-40c9-992d-81dca59f598e",
"name": "height",
"type": "integer",
"fieldKind": "output"
}
},
"label": "",
"isOpen": true,
"notes": "",
"embedWorkflow": false,
"isIntermediate": true,
"useCache": true,
"version": "1.0.0"
},
"width": 320,
"height": 225,
"position": {
"x": 3593.7474460420153,
"y": -538.1200472386865
}
},
{
"id": "ca4d5059-8bfb-447f-b415-da0faba5a143",
"type": "invocation",
"data": {
"id": "ca4d5059-8bfb-447f-b415-da0faba5a143",
"type": "collect",
"inputs": {
"item": {
"id": "b16ae602-8708-4b1b-8d4f-9e0808d429ab",
"name": "item",
"type": "CollectionItem",
"fieldKind": "input",
"label": ""
}
},
"outputs": {
"collection": {
"id": "d8987dd8-dec8-4d94-816a-3e356af29884",
"name": "collection",
"type": "Collection",
"fieldKind": "output"
}
},
"label": "ControlNet Collection",
"isOpen": true,
"notes": "",
"embedWorkflow": false,
"isIntermediate": true,
"useCache": true,
"version": "1.0.0"
},
"width": 320,
"height": 104,
"position": {
"x": 4866.191497139488,
"y": -299.0538619537037
}
},
{
"id": "018b1214-c2af-43a7-9910-fb687c6726d7",
"type": "invocation",
"data": {
"id": "018b1214-c2af-43a7-9910-fb687c6726d7",
"type": "midas_depth_image_processor",
"inputs": {
"metadata": {
"id": "77f91980-c696-4a18-a9ea-6e2fc329a747",
"name": "metadata",
"type": "MetadataField",
"fieldKind": "input",
"label": ""
},
"image": {
"id": "50710a20-2af5-424d-9d17-aa08167829c6",
"name": "image",
"type": "ImageField",
"fieldKind": "input",
"label": ""
},
"a_mult": {
"id": "f3b26f9d-2498-415e-9c01-197a8d06c0a5",
"name": "a_mult",
"type": "float",
"fieldKind": "input",
"label": "",
"value": 2
},
"bg_th": {
"id": "4b1eb3ae-9d4a-47d6-b0ed-da62501e007f",
"name": "bg_th",
"type": "float",
"fieldKind": "input",
"label": "",
"value": 0.1
}
},
"outputs": {
"image": {
"id": "b4ed637c-c4a0-4fdd-a24e-36d6412e4ccf",
"name": "image",
"type": "ImageField",
"fieldKind": "output"
},
"width": {
"id": "6bf9b609-d72c-4239-99bd-390a73cc3a9c",
"name": "width",
"type": "integer",
"fieldKind": "output"
},
"height": {
"id": "3e8aef09-cf44-4e3e-a490-d3c9e7b23119",
"name": "height",
"type": "integer",
"fieldKind": "output"
}
},
"label": "",
"isOpen": true,
"notes": "",
"embedWorkflow": false,
"isIntermediate": true,
"useCache": true,
"version": "1.0.0"
},
"width": 320,
"height": 339,
"position": {
"x": 4054.229311491893,
"y": -31.611411056365725
}
},
{
"id": "c826ba5e-9676-4475-b260-07b85e88753c",
"type": "invocation",
"data": {
"id": "c826ba5e-9676-4475-b260-07b85e88753c",
"type": "canny_image_processor",
"inputs": {
"metadata": {
"id": "08331ea6-99df-4e61-a919-204d9bfa8fb2",
"name": "metadata",
"type": "MetadataField",
"fieldKind": "input",
"label": ""
},
"image": {
"id": "33a37284-06ac-459c-ba93-1655e4f69b2d",
"name": "image",
"type": "ImageField",
"fieldKind": "input",
"label": ""
},
"low_threshold": {
"id": "21ec18a3-50c5-4ba1-9642-f921744d594f",
"name": "low_threshold",
"type": "integer",
"fieldKind": "input",
"label": "",
"value": 100
},
"high_threshold": {
"id": "ebeab271-a5ff-4c88-acfd-1d0271ab6ed4",
"name": "high_threshold",
"type": "integer",
"fieldKind": "input",
"label": "",
"value": 200
}
},
"outputs": {
"image": {
"id": "c0caadbf-883f-4cb4-a62d-626b9c81fc4e",
"name": "image",
"type": "ImageField",
"fieldKind": "output"
},
"width": {
"id": "df225843-8098-49c0-99d1-3b0b6600559f",
"name": "width",
"type": "integer",
"fieldKind": "output"
},
"height": {
"id": "e4abe0de-aa16-41f3-9cd7-968b49db5da3",
"name": "height",
"type": "integer",
"fieldKind": "output"
}
},
"label": "",
"isOpen": true,
"notes": "",
"embedWorkflow": false,
"isIntermediate": true,
"useCache": true,
"version": "1.0.0"
},
"width": 320,
"height": 339,
"position": {
"x": 4095.757337055795,
"y": -455.63440891935863
}
},
{
"id": "9db25398-c869-4a63-8815-c6559341ef12",
"type": "invocation",
"data": {
"id": "9db25398-c869-4a63-8815-c6559341ef12",
"type": "l2i",
"inputs": {
"metadata": {
"id": "2f269793-72e5-4ff3-b76c-fab4f93e983f",
"name": "metadata",
"type": "MetadataField",
"fieldKind": "input",
"label": ""
},
"latents": {
"id": "4aaedd3b-cc77-420c-806e-c7fa74ec4cdf",
"name": "latents",
"type": "LatentsField",
"fieldKind": "input",
"label": ""
},
"vae": {
"id": "432b066a-2462-4d18-83d9-64620b72df45",
"name": "vae",
"type": "VaeField",
"fieldKind": "input",
"label": ""
},
"tiled": {
"id": "61f86e0f-7c46-40f8-b3f5-fe2f693595ca",
"name": "tiled",
"type": "boolean",
"fieldKind": "input",
"label": "",
"value": false
},
"fp32": {
"id": "39b6c89a-37ef-4a7e-9509-daeca49d5092",
"name": "fp32",
"type": "boolean",
"fieldKind": "input",
"label": "",
"value": false
}
},
"outputs": {
"image": {
"id": "6204e9b0-61dd-4250-b685-2092ba0e28e6",
"name": "image",
"type": "ImageField",
"fieldKind": "output"
},
"width": {
"id": "b4140649-8d5d-4d2d-bfa6-09e389ede5f9",
"name": "width",
"type": "integer",
"fieldKind": "output"
},
"height": {
"id": "f3a0c0c8-fc24-4646-8be1-ed8cdd140828",
"name": "height",
"type": "integer",
"fieldKind": "output"
}
},
"label": "",
"isOpen": true,
"notes": "",
"embedWorkflow": false,
"isIntermediate": false,
"useCache": true,
"version": "1.0.0"
},
"width": 320,
"height": 267,
"position": {
"x": 5678.726701377887,
"y": -351.6792416734579
}
},
{
"id": "ac481b7f-08bf-4a9d-9e0c-3a82ea5243ce",
"type": "invocation",
"data": {
"id": "ac481b7f-08bf-4a9d-9e0c-3a82ea5243ce",
"type": "denoise_latents",
"inputs": {
"positive_conditioning": {
"id": "869cd309-c238-444b-a1a0-5021f99785ba",
"name": "positive_conditioning",
"type": "ConditioningField",
"fieldKind": "input",
"label": ""
},
"negative_conditioning": {
"id": "343447b4-1e37-4e9e-8ac7-4d04864066af",
"name": "negative_conditioning",
"type": "ConditioningField",
"fieldKind": "input",
"label": ""
},
"noise": {
"id": "b556571e-0cf9-4e03-8cfc-5caad937d957",
"name": "noise",
"type": "LatentsField",
"fieldKind": "input",
"label": ""
},
"steps": {
"id": "a3b3d2de-9308-423e-b00d-c209c3e6e808",
"name": "steps",
"type": "integer",
"fieldKind": "input",
"label": "",
"value": 10
},
"cfg_scale": {
"id": "b13c50a4-ec7e-4579-b0ef-2fe5df2605ea",
"name": "cfg_scale",
"type": "FloatPolymorphic",
"fieldKind": "input",
"label": "",
"value": 7.5
},
"denoising_start": {
"id": "57d5d755-f58f-4347-b991-f0bca4a0ab29",
"name": "denoising_start",
"type": "float",
"fieldKind": "input",
"label": "",
"value": 0
},
"denoising_end": {
"id": "323e78a6-880a-4d73-a62c-70faff965aa6",
"name": "denoising_end",
"type": "float",
"fieldKind": "input",
"label": "",
"value": 1
},
"scheduler": {
"id": "c25fdc17-a089-43ac-953e-067c45d5c76b",
"name": "scheduler",
"type": "Scheduler",
"fieldKind": "input",
"label": "",
"value": "euler"
},
"unet": {
"id": "6cde662b-e633-4569-b6b4-ec87c52c9c11",
"name": "unet",
"type": "UNetField",
"fieldKind": "input",
"label": ""
},
"control": {
"id": "276a4df9-bb26-4505-a4d3-a94e18c7b541",
"name": "control",
"type": "ControlPolymorphic",
"fieldKind": "input",
"label": ""
},
"ip_adapter": {
"id": "48d40c51-b5e2-4457-a428-eef0696695e8",
"name": "ip_adapter",
"type": "IPAdapterPolymorphic",
"fieldKind": "input",
"label": ""
},
"t2i_adapter": {
"id": "75dd8af2-e7d7-48b4-a574-edd9f6e686ad",
"name": "t2i_adapter",
"type": "T2IAdapterPolymorphic",
"fieldKind": "input",
"label": ""
},
"latents": {
"id": "9223d67b-1dd7-4b34-a45f-ed0a725d9702",
"name": "latents",
"type": "LatentsField",
"fieldKind": "input",
"label": ""
},
"denoise_mask": {
"id": "4ee99177-6923-4b7f-8fe0-d721dd7cb05b",
"name": "denoise_mask",
"type": "DenoiseMaskField",
"fieldKind": "input",
"label": ""
}
},
"outputs": {
"latents": {
"id": "7fb4e326-a974-43e8-9ee7-2e3ab235819d",
"name": "latents",
"type": "LatentsField",
"fieldKind": "output"
},
"width": {
"id": "6bb8acd0-8973-4195-a095-e376385dc705",
"name": "width",
"type": "integer",
"fieldKind": "output"
},
"height": {
"id": "795dea52-1c7d-4e64-99f7-2f60ec6e3ab9",
"name": "height",
"type": "integer",
"fieldKind": "output"
}
},
"label": "",
"isOpen": true,
"notes": "",
"embedWorkflow": false,
"isIntermediate": true,
"useCache": true,
"version": "1.4.0"
},
"width": 320,
"height": 646,
"position": {
"x": 5274.672987098195,
"y": -823.0752416664332
}
}
],
"edges": [
{
"source": "54486974-835b-4d81-8f82-05f9f32ce9e9",
"sourceHandle": "clip",
"target": "7ce68934-3419-42d4-ac70-82cfc9397306",
"targetHandle": "clip",
"id": "reactflow__edge-54486974-835b-4d81-8f82-05f9f32ce9e9clip-7ce68934-3419-42d4-ac70-82cfc9397306clip",
"type": "default"
},
{
"source": "54486974-835b-4d81-8f82-05f9f32ce9e9",
"sourceHandle": "clip",
"target": "273e3f96-49ea-4dc5-9d5b-9660390f14e1",
"targetHandle": "clip",
"id": "reactflow__edge-54486974-835b-4d81-8f82-05f9f32ce9e9clip-273e3f96-49ea-4dc5-9d5b-9660390f14e1clip",
"type": "default"
},
{
"source": "a33199c2-8340-401e-b8a2-42ffa875fc1c",
"sourceHandle": "control",
"target": "ca4d5059-8bfb-447f-b415-da0faba5a143",
"targetHandle": "item",
"id": "reactflow__edge-a33199c2-8340-401e-b8a2-42ffa875fc1ccontrol-ca4d5059-8bfb-447f-b415-da0faba5a143item",
"type": "default"
},
{
"source": "d204d184-f209-4fae-a0a1-d152800844e1",
"sourceHandle": "control",
"target": "ca4d5059-8bfb-447f-b415-da0faba5a143",
"targetHandle": "item",
"id": "reactflow__edge-d204d184-f209-4fae-a0a1-d152800844e1control-ca4d5059-8bfb-447f-b415-da0faba5a143item",
"type": "default"
},
{
"source": "8e860e51-5045-456e-bf04-9a62a2a5c49e",
"sourceHandle": "image",
"target": "018b1214-c2af-43a7-9910-fb687c6726d7",
"targetHandle": "image",
"id": "reactflow__edge-8e860e51-5045-456e-bf04-9a62a2a5c49eimage-018b1214-c2af-43a7-9910-fb687c6726d7image",
"type": "default"
},
{
"source": "018b1214-c2af-43a7-9910-fb687c6726d7",
"sourceHandle": "image",
"target": "a33199c2-8340-401e-b8a2-42ffa875fc1c",
"targetHandle": "image",
"id": "reactflow__edge-018b1214-c2af-43a7-9910-fb687c6726d7image-a33199c2-8340-401e-b8a2-42ffa875fc1cimage",
"type": "default"
},
{
"source": "c4b23e64-7986-40c4-9cad-46327b12e204",
"sourceHandle": "image",
"target": "c826ba5e-9676-4475-b260-07b85e88753c",
"targetHandle": "image",
"id": "reactflow__edge-c4b23e64-7986-40c4-9cad-46327b12e204image-c826ba5e-9676-4475-b260-07b85e88753cimage",
"type": "default"
},
{
"source": "c826ba5e-9676-4475-b260-07b85e88753c",
"sourceHandle": "image",
"target": "d204d184-f209-4fae-a0a1-d152800844e1",
"targetHandle": "image",
"id": "reactflow__edge-c826ba5e-9676-4475-b260-07b85e88753cimage-d204d184-f209-4fae-a0a1-d152800844e1image",
"type": "default"
},
{
"source": "54486974-835b-4d81-8f82-05f9f32ce9e9",
"sourceHandle": "vae",
"target": "9db25398-c869-4a63-8815-c6559341ef12",
"targetHandle": "vae",
"id": "reactflow__edge-54486974-835b-4d81-8f82-05f9f32ce9e9vae-9db25398-c869-4a63-8815-c6559341ef12vae",
"type": "default"
},
{
"source": "ac481b7f-08bf-4a9d-9e0c-3a82ea5243ce",
"sourceHandle": "latents",
"target": "9db25398-c869-4a63-8815-c6559341ef12",
"targetHandle": "latents",
"id": "reactflow__edge-ac481b7f-08bf-4a9d-9e0c-3a82ea5243celatents-9db25398-c869-4a63-8815-c6559341ef12latents",
"type": "default"
},
{
"source": "ca4d5059-8bfb-447f-b415-da0faba5a143",
"sourceHandle": "collection",
"target": "ac481b7f-08bf-4a9d-9e0c-3a82ea5243ce",
"targetHandle": "control",
"id": "reactflow__edge-ca4d5059-8bfb-447f-b415-da0faba5a143collection-ac481b7f-08bf-4a9d-9e0c-3a82ea5243cecontrol",
"type": "default"
},
{
"source": "54486974-835b-4d81-8f82-05f9f32ce9e9",
"sourceHandle": "unet",
"target": "ac481b7f-08bf-4a9d-9e0c-3a82ea5243ce",
"targetHandle": "unet",
"id": "reactflow__edge-54486974-835b-4d81-8f82-05f9f32ce9e9unet-ac481b7f-08bf-4a9d-9e0c-3a82ea5243ceunet",
"type": "default"
},
{
"source": "273e3f96-49ea-4dc5-9d5b-9660390f14e1",
"sourceHandle": "conditioning",
"target": "ac481b7f-08bf-4a9d-9e0c-3a82ea5243ce",
"targetHandle": "negative_conditioning",
"id": "reactflow__edge-273e3f96-49ea-4dc5-9d5b-9660390f14e1conditioning-ac481b7f-08bf-4a9d-9e0c-3a82ea5243cenegative_conditioning",
"type": "default"
},
{
"source": "7ce68934-3419-42d4-ac70-82cfc9397306",
"sourceHandle": "conditioning",
"target": "ac481b7f-08bf-4a9d-9e0c-3a82ea5243ce",
"targetHandle": "positive_conditioning",
"id": "reactflow__edge-7ce68934-3419-42d4-ac70-82cfc9397306conditioning-ac481b7f-08bf-4a9d-9e0c-3a82ea5243cepositive_conditioning",
"type": "default"
}
]
}

View File

@@ -0,0 +1,719 @@
{
"name": "Prompt from File",
"author": "InvokeAI",
"description": "Sample workflow using prompt from file capabilities of InvokeAI ",
"version": "0.1.0",
"contact": "millun@invoke.ai",
"tags": "text2image, prompt from file, default",
"notes": "",
"exposedFields": [
{
"nodeId": "d6353b7f-b447-4e17-8f2e-80a88c91d426",
"fieldName": "model"
},
{
"nodeId": "1b7e0df8-8589-4915-a4ea-c0088f15d642",
"fieldName": "file_path"
}
],
"meta": {
"version": "1.0.0"
},
"nodes": [
{
"id": "c2eaf1ba-5708-4679-9e15-945b8b432692",
"type": "invocation",
"data": {
"id": "c2eaf1ba-5708-4679-9e15-945b8b432692",
"type": "compel",
"inputs": {
"prompt": {
"id": "dcdf3f6d-9b96-4bcd-9b8d-f992fefe4f62",
"name": "prompt",
"type": "string",
"fieldKind": "input",
"label": "",
"value": ""
},
"clip": {
"id": "3f1981c9-d8a9-42eb-a739-4f120eb80745",
"name": "clip",
"type": "ClipField",
"fieldKind": "input",
"label": ""
}
},
"outputs": {
"conditioning": {
"id": "46205e6c-c5e2-44cb-9c82-1cd20b95674a",
"name": "conditioning",
"type": "ConditioningField",
"fieldKind": "output"
}
},
"label": "",
"isOpen": true,
"notes": "",
"embedWorkflow": false,
"isIntermediate": true,
"useCache": true,
"version": "1.0.0"
},
"width": 320,
"height": 261,
"position": {
"x": 1177.3417789657444,
"y": -102.0924766641035
}
},
{
"id": "1b7e0df8-8589-4915-a4ea-c0088f15d642",
"type": "invocation",
"data": {
"id": "1b7e0df8-8589-4915-a4ea-c0088f15d642",
"type": "prompt_from_file",
"inputs": {
"file_path": {
"id": "37e37684-4f30-4ec8-beae-b333e550f904",
"name": "file_path",
"type": "string",
"fieldKind": "input",
"label": "Prompts File Path",
"value": ""
},
"pre_prompt": {
"id": "7de02feb-819a-4992-bad3-72a30920ddea",
"name": "pre_prompt",
"type": "string",
"fieldKind": "input",
"label": "",
"value": ""
},
"post_prompt": {
"id": "95f191d8-a282-428e-bd65-de8cb9b7513a",
"name": "post_prompt",
"type": "string",
"fieldKind": "input",
"label": "",
"value": ""
},
"start_line": {
"id": "efee9a48-05ab-4829-8429-becfa64a0782",
"name": "start_line",
"type": "integer",
"fieldKind": "input",
"label": "",
"value": 1
},
"max_prompts": {
"id": "abebb428-3d3d-49fd-a482-4e96a16fff08",
"name": "max_prompts",
"type": "integer",
"fieldKind": "input",
"label": "",
"value": 1
}
},
"outputs": {
"collection": {
"id": "77d5d7f1-9877-4ab1-9a8c-33e9ffa9abf3",
"name": "collection",
"type": "StringCollection",
"fieldKind": "output"
}
},
"label": "Prompts from File",
"isOpen": true,
"notes": "",
"embedWorkflow": false,
"isIntermediate": true,
"useCache": true,
"version": "1.0.0"
},
"width": 320,
"height": 589,
"position": {
"x": 394.181884547075,
"y": -423.5345157864633
}
},
{
"id": "1b89067c-3f6b-42c8-991f-e3055789b251",
"type": "invocation",
"data": {
"id": "1b89067c-3f6b-42c8-991f-e3055789b251",
"type": "iterate",
"inputs": {
"collection": {
"id": "4c564bf8-5ed6-441e-ad2c-dda265d5785f",
"name": "collection",
"type": "Collection",
"fieldKind": "input",
"label": "",
"value": []
}
},
"outputs": {
"item": {
"id": "36340f9a-e7a5-4afa-b4b5-313f4e292380",
"name": "item",
"type": "CollectionItem",
"fieldKind": "output"
}
},
"label": "",
"isOpen": true,
"notes": "",
"embedWorkflow": false,
"isIntermediate": true,
"useCache": true,
"version": "1.0.0"
},
"width": 320,
"height": 104,
"position": {
"x": 792.8735298060233,
"y": -432.6964953027252
}
},
{
"id": "d6353b7f-b447-4e17-8f2e-80a88c91d426",
"type": "invocation",
"data": {
"id": "d6353b7f-b447-4e17-8f2e-80a88c91d426",
"type": "main_model_loader",
"inputs": {
"model": {
"id": "3f264259-3418-47d5-b90d-b6600e36ae46",
"name": "model",
"type": "MainModelField",
"fieldKind": "input",
"label": "",
"value": {
"model_name": "stable-diffusion-v1-5",
"base_model": "sd-1",
"model_type": "main"
}
}
},
"outputs": {
"unet": {
"id": "8e182ea2-9d0a-4c02-9407-27819288d4b5",
"name": "unet",
"type": "UNetField",
"fieldKind": "output"
},
"clip": {
"id": "d67d9d30-058c-46d5-bded-3d09d6d1aa39",
"name": "clip",
"type": "ClipField",
"fieldKind": "output"
},
"vae": {
"id": "89641601-0429-4448-98d5-190822d920d8",
"name": "vae",
"type": "VaeField",
"fieldKind": "output"
}
},
"label": "",
"isOpen": true,
"notes": "",
"embedWorkflow": false,
"isIntermediate": true,
"useCache": true,
"version": "1.0.0"
},
"width": 320,
"height": 226,
"position": {
"x": -47.66201354137797,
"y": -299.218193067033
}
},
{
"id": "fc9d0e35-a6de-4a19-84e1-c72497c823f6",
"type": "invocation",
"data": {
"id": "fc9d0e35-a6de-4a19-84e1-c72497c823f6",
"type": "compel",
"inputs": {
"prompt": {
"id": "dcdf3f6d-9b96-4bcd-9b8d-f992fefe4f62",
"name": "prompt",
"type": "string",
"fieldKind": "input",
"label": "",
"value": ""
},
"clip": {
"id": "3f1981c9-d8a9-42eb-a739-4f120eb80745",
"name": "clip",
"type": "ClipField",
"fieldKind": "input",
"label": ""
}
},
"outputs": {
"conditioning": {
"id": "46205e6c-c5e2-44cb-9c82-1cd20b95674a",
"name": "conditioning",
"type": "ConditioningField",
"fieldKind": "output"
}
},
"label": "",
"isOpen": true,
"notes": "",
"embedWorkflow": false,
"isIntermediate": true,
"useCache": true,
"version": "1.0.0"
},
"width": 320,
"height": 261,
"position": {
"x": 1175.0187896425462,
"y": -420.64289413577114
}
},
{
"id": "0eb5f3f5-1b91-49eb-9ef0-41d67c7eae77",
"type": "invocation",
"data": {
"id": "0eb5f3f5-1b91-49eb-9ef0-41d67c7eae77",
"type": "noise",
"inputs": {
"seed": {
"id": "b722d84a-eeee-484f-bef2-0250c027cb67",
"name": "seed",
"type": "integer",
"fieldKind": "input",
"label": "",
"value": 0
},
"width": {
"id": "d5f8ce11-0502-4bfc-9a30-5757dddf1f94",
"name": "width",
"type": "integer",
"fieldKind": "input",
"label": "",
"value": 512
},
"height": {
"id": "f187d5ff-38a5-4c3f-b780-fc5801ef34af",
"name": "height",
"type": "integer",
"fieldKind": "input",
"label": "",
"value": 512
},
"use_cpu": {
"id": "12f112b8-8b76-4816-b79e-662edc9f9aa5",
"name": "use_cpu",
"type": "boolean",
"fieldKind": "input",
"label": "",
"value": true
}
},
"outputs": {
"noise": {
"id": "08576ad1-96d9-42d2-96ef-6f5c1961933f",
"name": "noise",
"type": "LatentsField",
"fieldKind": "output"
},
"width": {
"id": "f3e1f94a-258d-41ff-9789-bd999bd9f40d",
"name": "width",
"type": "integer",
"fieldKind": "output"
},
"height": {
"id": "6cefc357-4339-415e-a951-49b9c2be32f4",
"name": "height",
"type": "integer",
"fieldKind": "output"
}
},
"label": "",
"isOpen": true,
"notes": "",
"embedWorkflow": false,
"isIntermediate": true,
"useCache": true,
"version": "1.0.0"
},
"width": 320,
"height": 389,
"position": {
"x": 809.1964864135837,
"y": 183.2735123359796
}
},
{
"id": "dfc20e07-7aef-4fc0-a3a1-7bf68ec6a4e5",
"type": "invocation",
"data": {
"id": "dfc20e07-7aef-4fc0-a3a1-7bf68ec6a4e5",
"type": "rand_int",
"inputs": {
"low": {
"id": "b9fc6cf1-469c-4037-9bf0-04836965826f",
"name": "low",
"type": "integer",
"fieldKind": "input",
"label": "",
"value": 0
},
"high": {
"id": "06eac725-0f60-4ba2-b8cd-7ad9f757488c",
"name": "high",
"type": "integer",
"fieldKind": "input",
"label": "",
"value": 2147483647
}
},
"outputs": {
"value": {
"id": "df08c84e-7346-4e92-9042-9e5cb773aaff",
"name": "value",
"type": "integer",
"fieldKind": "output"
}
},
"label": "",
"isOpen": true,
"notes": "",
"embedWorkflow": false,
"isIntermediate": true,
"useCache": false,
"version": "1.0.0"
},
"width": 320,
"height": 218,
"position": {
"x": 354.19913145404166,
"y": 301.86324846905165
}
},
{
"id": "491ec988-3c77-4c37-af8a-39a0c4e7a2a1",
"type": "invocation",
"data": {
"id": "491ec988-3c77-4c37-af8a-39a0c4e7a2a1",
"type": "l2i",
"inputs": {
"metadata": {
"id": "022e4b33-562b-438d-b7df-41c3fd931f40",
"name": "metadata",
"type": "MetadataField",
"fieldKind": "input",
"label": ""
},
"latents": {
"id": "67cb6c77-a394-4a66-a6a9-a0a7dcca69ec",
"name": "latents",
"type": "LatentsField",
"fieldKind": "input",
"label": ""
},
"vae": {
"id": "7b3fd9ad-a4ef-4e04-89fa-3832a9902dbd",
"name": "vae",
"type": "VaeField",
"fieldKind": "input",
"label": ""
},
"tiled": {
"id": "5ac5680d-3add-4115-8ec0-9ef5bb87493b",
"name": "tiled",
"type": "boolean",
"fieldKind": "input",
"label": "",
"value": false
},
"fp32": {
"id": "db8297f5-55f8-452f-98cf-6572c2582152",
"name": "fp32",
"type": "boolean",
"fieldKind": "input",
"label": "",
"value": false
}
},
"outputs": {
"image": {
"id": "d8778d0c-592a-4960-9280-4e77e00a7f33",
"name": "image",
"type": "ImageField",
"fieldKind": "output"
},
"width": {
"id": "c8b0a75a-f5de-4ff2-9227-f25bb2b97bec",
"name": "width",
"type": "integer",
"fieldKind": "output"
},
"height": {
"id": "83c05fbf-76b9-49ab-93c4-fa4b10e793e4",
"name": "height",
"type": "integer",
"fieldKind": "output"
}
},
"label": "",
"isOpen": true,
"notes": "",
"embedWorkflow": false,
"isIntermediate": true,
"useCache": true,
"version": "1.0.0"
},
"width": 320,
"height": 267,
"position": {
"x": 2037.861329274915,
"y": -329.8393457509562
}
},
{
"id": "2fb1577f-0a56-4f12-8711-8afcaaaf1d5e",
"type": "invocation",
"data": {
"id": "2fb1577f-0a56-4f12-8711-8afcaaaf1d5e",
"type": "denoise_latents",
"inputs": {
"positive_conditioning": {
"id": "751fb35b-3f23-45ce-af1c-053e74251337",
"name": "positive_conditioning",
"type": "ConditioningField",
"fieldKind": "input",
"label": ""
},
"negative_conditioning": {
"id": "b9dc06b6-7481-4db1-a8c2-39d22a5eacff",
"name": "negative_conditioning",
"type": "ConditioningField",
"fieldKind": "input",
"label": ""
},
"noise": {
"id": "6e15e439-3390-48a4-8031-01e0e19f0e1d",
"name": "noise",
"type": "LatentsField",
"fieldKind": "input",
"label": ""
},
"steps": {
"id": "bfdfb3df-760b-4d51-b17b-0abb38b976c2",
"name": "steps",
"type": "integer",
"fieldKind": "input",
"label": "",
"value": 10
},
"cfg_scale": {
"id": "47770858-322e-41af-8494-d8b63ed735f3",
"name": "cfg_scale",
"type": "FloatPolymorphic",
"fieldKind": "input",
"label": "",
"value": 7.5
},
"denoising_start": {
"id": "2ba78720-ee02-4130-a348-7bc3531f790b",
"name": "denoising_start",
"type": "float",
"fieldKind": "input",
"label": "",
"value": 0
},
"denoising_end": {
"id": "a874dffb-d433-4d1a-9f59-af4367bb05e4",
"name": "denoising_end",
"type": "float",
"fieldKind": "input",
"label": "",
"value": 1
},
"scheduler": {
"id": "36e021ad-b762-4fe4-ad4d-17f0291c40b2",
"name": "scheduler",
"type": "Scheduler",
"fieldKind": "input",
"label": "",
"value": "euler"
},
"unet": {
"id": "98d3282d-f9f6-4b5e-b9e8-58658f1cac78",
"name": "unet",
"type": "UNetField",
"fieldKind": "input",
"label": ""
},
"control": {
"id": "f2ea3216-43d5-42b4-887f-36e8f7166d53",
"name": "control",
"type": "ControlPolymorphic",
"fieldKind": "input",
"label": ""
},
"ip_adapter": {
"id": "d0780610-a298-47c8-a54e-70e769e0dfe2",
"name": "ip_adapter",
"type": "IPAdapterPolymorphic",
"fieldKind": "input",
"label": ""
},
"t2i_adapter": {
"id": "fdb40970-185e-4ea8-8bb5-88f06f91f46a",
"name": "t2i_adapter",
"type": "T2IAdapterPolymorphic",
"fieldKind": "input",
"label": ""
},
"latents": {
"id": "e05b538a-1b5a-4aa5-84b1-fd2361289a81",
"name": "latents",
"type": "LatentsField",
"fieldKind": "input",
"label": ""
},
"denoise_mask": {
"id": "463a419e-df30-4382-8ffb-b25b25abe425",
"name": "denoise_mask",
"type": "DenoiseMaskField",
"fieldKind": "input",
"label": ""
}
},
"outputs": {
"latents": {
"id": "559ee688-66cf-4139-8b82-3d3aa69995ce",
"name": "latents",
"type": "LatentsField",
"fieldKind": "output"
},
"width": {
"id": "0b4285c2-e8b9-48e5-98f6-0a49d3f98fd2",
"name": "width",
"type": "integer",
"fieldKind": "output"
},
"height": {
"id": "8b0881b9-45e5-47d5-b526-24b6661de0ee",
"name": "height",
"type": "integer",
"fieldKind": "output"
}
},
"label": "",
"isOpen": true,
"notes": "",
"embedWorkflow": false,
"isIntermediate": true,
"useCache": true,
"version": "1.4.0"
},
"width": 320,
"height": 646,
"position": {
"x": 1570.9941088179146,
"y": -407.6505491604564
}
}
],
"edges": [
{
"source": "1b7e0df8-8589-4915-a4ea-c0088f15d642",
"sourceHandle": "collection",
"target": "1b89067c-3f6b-42c8-991f-e3055789b251",
"targetHandle": "collection",
"id": "reactflow__edge-1b7e0df8-8589-4915-a4ea-c0088f15d642collection-1b89067c-3f6b-42c8-991f-e3055789b251collection",
"type": "default"
},
{
"source": "d6353b7f-b447-4e17-8f2e-80a88c91d426",
"sourceHandle": "clip",
"target": "fc9d0e35-a6de-4a19-84e1-c72497c823f6",
"targetHandle": "clip",
"id": "reactflow__edge-d6353b7f-b447-4e17-8f2e-80a88c91d426clip-fc9d0e35-a6de-4a19-84e1-c72497c823f6clip",
"type": "default"
},
{
"source": "1b89067c-3f6b-42c8-991f-e3055789b251",
"sourceHandle": "item",
"target": "fc9d0e35-a6de-4a19-84e1-c72497c823f6",
"targetHandle": "prompt",
"id": "reactflow__edge-1b89067c-3f6b-42c8-991f-e3055789b251item-fc9d0e35-a6de-4a19-84e1-c72497c823f6prompt",
"type": "default"
},
{
"source": "d6353b7f-b447-4e17-8f2e-80a88c91d426",
"sourceHandle": "clip",
"target": "c2eaf1ba-5708-4679-9e15-945b8b432692",
"targetHandle": "clip",
"id": "reactflow__edge-d6353b7f-b447-4e17-8f2e-80a88c91d426clip-c2eaf1ba-5708-4679-9e15-945b8b432692clip",
"type": "default"
},
{
"source": "dfc20e07-7aef-4fc0-a3a1-7bf68ec6a4e5",
"sourceHandle": "value",
"target": "0eb5f3f5-1b91-49eb-9ef0-41d67c7eae77",
"targetHandle": "seed",
"id": "reactflow__edge-dfc20e07-7aef-4fc0-a3a1-7bf68ec6a4e5value-0eb5f3f5-1b91-49eb-9ef0-41d67c7eae77seed",
"type": "default"
},
{
"source": "fc9d0e35-a6de-4a19-84e1-c72497c823f6",
"sourceHandle": "conditioning",
"target": "2fb1577f-0a56-4f12-8711-8afcaaaf1d5e",
"targetHandle": "positive_conditioning",
"id": "reactflow__edge-fc9d0e35-a6de-4a19-84e1-c72497c823f6conditioning-2fb1577f-0a56-4f12-8711-8afcaaaf1d5epositive_conditioning",
"type": "default"
},
{
"source": "c2eaf1ba-5708-4679-9e15-945b8b432692",
"sourceHandle": "conditioning",
"target": "2fb1577f-0a56-4f12-8711-8afcaaaf1d5e",
"targetHandle": "negative_conditioning",
"id": "reactflow__edge-c2eaf1ba-5708-4679-9e15-945b8b432692conditioning-2fb1577f-0a56-4f12-8711-8afcaaaf1d5enegative_conditioning",
"type": "default"
},
{
"source": "0eb5f3f5-1b91-49eb-9ef0-41d67c7eae77",
"sourceHandle": "noise",
"target": "2fb1577f-0a56-4f12-8711-8afcaaaf1d5e",
"targetHandle": "noise",
"id": "reactflow__edge-0eb5f3f5-1b91-49eb-9ef0-41d67c7eae77noise-2fb1577f-0a56-4f12-8711-8afcaaaf1d5enoise",
"type": "default"
},
{
"source": "d6353b7f-b447-4e17-8f2e-80a88c91d426",
"sourceHandle": "unet",
"target": "2fb1577f-0a56-4f12-8711-8afcaaaf1d5e",
"targetHandle": "unet",
"id": "reactflow__edge-d6353b7f-b447-4e17-8f2e-80a88c91d426unet-2fb1577f-0a56-4f12-8711-8afcaaaf1d5eunet",
"type": "default"
},
{
"source": "2fb1577f-0a56-4f12-8711-8afcaaaf1d5e",
"sourceHandle": "latents",
"target": "491ec988-3c77-4c37-af8a-39a0c4e7a2a1",
"targetHandle": "latents",
"id": "reactflow__edge-2fb1577f-0a56-4f12-8711-8afcaaaf1d5elatents-491ec988-3c77-4c37-af8a-39a0c4e7a2a1latents",
"type": "default"
},
{
"source": "d6353b7f-b447-4e17-8f2e-80a88c91d426",
"sourceHandle": "vae",
"target": "491ec988-3c77-4c37-af8a-39a0c4e7a2a1",
"targetHandle": "vae",
"id": "reactflow__edge-d6353b7f-b447-4e17-8f2e-80a88c91d426vae-491ec988-3c77-4c37-af8a-39a0c4e7a2a1vae",
"type": "default"
}
]
}

View File

@@ -0,0 +1,758 @@
{
"name": "QR Code Monster",
"author": "InvokeAI",
"description": "Sample workflow for create images with QR code Monster ControlNet",
"version": "1.0.1",
"contact": "invoke@invoke.ai",
"tags": "qrcode, controlnet, default",
"notes": "",
"exposedFields": [
{
"nodeId": "a6cc0986-f928-4a7e-8d44-ba2d4b36f54a",
"fieldName": "image"
},
{
"nodeId": "aca3b054-bfba-4392-bd20-6476f59504df",
"fieldName": "prompt"
},
{
"nodeId": "3db7cee0-31e2-4a3d-94a1-268cb16177dd",
"fieldName": "prompt"
}
],
"meta": {
"version": "1.0.0"
},
"nodes": [
{
"id": "3db7cee0-31e2-4a3d-94a1-268cb16177dd",
"type": "invocation",
"data": {
"id": "3db7cee0-31e2-4a3d-94a1-268cb16177dd",
"type": "compel",
"inputs": {
"prompt": {
"id": "6a1fe244-5656-4f8c-91d1-1fb474e28807",
"name": "prompt",
"type": "string",
"fieldKind": "input",
"label": "Negative Prompt",
"value": ""
},
"clip": {
"id": "f24688f3-29b8-4a2d-8603-046e5a5c7250",
"name": "clip",
"type": "ClipField",
"fieldKind": "input",
"label": ""
}
},
"outputs": {
"conditioning": {
"id": "700528eb-3f8b-4745-b540-34f919b5b228",
"name": "conditioning",
"type": "ConditioningField",
"fieldKind": "output"
}
},
"label": "Prompt",
"isOpen": true,
"notes": "",
"embedWorkflow": false,
"isIntermediate": true,
"useCache": true,
"version": "1.0.0"
},
"width": 320,
"height": 261,
"position": {
"x": 773.0502679628016,
"y": 1622.4836086770556
}
},
{
"id": "610384f1-6f0c-4847-a9a2-37ce7f456ed1",
"type": "invocation",
"data": {
"id": "610384f1-6f0c-4847-a9a2-37ce7f456ed1",
"type": "main_model_loader",
"inputs": {
"model": {
"id": "cb36b6d3-6c1f-4911-a200-646745b0ff74",
"name": "model",
"type": "MainModelField",
"fieldKind": "input",
"label": "",
"value": {
"model_name": "stable-diffusion-v1-5",
"base_model": "sd-1",
"model_type": "main"
}
}
},
"outputs": {
"unet": {
"id": "7246895b-b252-49bc-b952-8d801b4672f7",
"name": "unet",
"type": "UNetField",
"fieldKind": "output"
},
"clip": {
"id": "3c2aedb8-30d5-4d4b-99df-d06a0d7bedc6",
"name": "clip",
"type": "ClipField",
"fieldKind": "output"
},
"vae": {
"id": "b9743815-5501-4bbb-8bde-8bd6ba298a4e",
"name": "vae",
"type": "VaeField",
"fieldKind": "output"
}
},
"label": "",
"isOpen": true,
"notes": "",
"embedWorkflow": false,
"isIntermediate": true,
"useCache": true,
"version": "1.0.0"
},
"width": 320,
"height": 226,
"position": {
"x": 211.58866462619744,
"y": 1376.0542388105248
}
},
{
"id": "aca3b054-bfba-4392-bd20-6476f59504df",
"type": "invocation",
"data": {
"id": "aca3b054-bfba-4392-bd20-6476f59504df",
"type": "compel",
"inputs": {
"prompt": {
"id": "6a1fe244-5656-4f8c-91d1-1fb474e28807",
"name": "prompt",
"type": "string",
"fieldKind": "input",
"label": "Positive Prompt",
"value": ""
},
"clip": {
"id": "f24688f3-29b8-4a2d-8603-046e5a5c7250",
"name": "clip",
"type": "ClipField",
"fieldKind": "input",
"label": ""
}
},
"outputs": {
"conditioning": {
"id": "700528eb-3f8b-4745-b540-34f919b5b228",
"name": "conditioning",
"type": "ConditioningField",
"fieldKind": "output"
}
},
"label": "",
"isOpen": true,
"notes": "",
"embedWorkflow": false,
"isIntermediate": true,
"useCache": true,
"version": "1.0.0"
},
"width": 320,
"height": 261,
"position": {
"x": 770.6491131680111,
"y": 1316.379247112241
}
},
{
"id": "a6cc0986-f928-4a7e-8d44-ba2d4b36f54a",
"type": "invocation",
"data": {
"id": "a6cc0986-f928-4a7e-8d44-ba2d4b36f54a",
"type": "image",
"inputs": {
"image": {
"id": "89ba5d58-28c9-4e04-a5df-79fb7a6f3531",
"name": "image",
"type": "ImageField",
"fieldKind": "input",
"label": "QR Code / Hidden Image"
}
},
"outputs": {
"image": {
"id": "54335653-0e17-42da-b9e8-83c5fb5af670",
"name": "image",
"type": "ImageField",
"fieldKind": "output"
},
"width": {
"id": "a3c65953-39ea-4d97-8858-d65154ff9d11",
"name": "width",
"type": "integer",
"fieldKind": "output"
},
"height": {
"id": "2c7db511-ebc9-4286-a46b-bc11e0fd779f",
"name": "height",
"type": "integer",
"fieldKind": "output"
}
},
"label": "",
"isOpen": true,
"notes": "",
"embedWorkflow": false,
"isIntermediate": true,
"useCache": true,
"version": "1.0.0"
},
"width": 320,
"height": 225,
"position": {
"x": 700.5034176864369,
"y": 1981.749600549388
}
},
{
"id": "280fd8a7-3b0c-49fe-8be4-6246e08b6c9a",
"type": "invocation",
"data": {
"id": "280fd8a7-3b0c-49fe-8be4-6246e08b6c9a",
"type": "noise",
"inputs": {
"seed": {
"id": "7c6c76dd-127b-4829-b1ec-430790cb7ed7",
"name": "seed",
"type": "integer",
"fieldKind": "input",
"label": "",
"value": 0
},
"width": {
"id": "8ec6a525-a421-40d8-a17e-39e7b6836438",
"name": "width",
"type": "integer",
"fieldKind": "input",
"label": "",
"value": 512
},
"height": {
"id": "6af1e58a-e2ee-4ec4-9f06-d8d0412922ca",
"name": "height",
"type": "integer",
"fieldKind": "input",
"label": "",
"value": 512
},
"use_cpu": {
"id": "26662e99-5720-43a6-a5d8-06c9dab0e261",
"name": "use_cpu",
"type": "boolean",
"fieldKind": "input",
"label": "",
"value": true
}
},
"outputs": {
"noise": {
"id": "cb4c4dfc-a744-49eb-af4f-677448e28407",
"name": "noise",
"type": "LatentsField",
"fieldKind": "output"
},
"width": {
"id": "97e87be6-e81f-40a3-a522-28ebe4aad0ac",
"name": "width",
"type": "integer",
"fieldKind": "output"
},
"height": {
"id": "80784420-f1e1-47b0-bd1d-1d381a15e22d",
"name": "height",
"type": "integer",
"fieldKind": "output"
}
},
"label": "",
"isOpen": false,
"notes": "",
"embedWorkflow": false,
"isIntermediate": true,
"useCache": true,
"version": "1.0.0"
},
"width": 320,
"height": 32,
"position": {
"x": 1182.460291960481,
"y": 1759.592972960265
}
},
{
"id": "2ac03cf6-0326-454a-bed0-d8baef2bf30d",
"type": "invocation",
"data": {
"id": "2ac03cf6-0326-454a-bed0-d8baef2bf30d",
"type": "controlnet",
"inputs": {
"image": {
"id": "1f683889-9f14-40c8-af29-4b991b211a3a",
"name": "image",
"type": "ImageField",
"fieldKind": "input",
"label": ""
},
"control_model": {
"id": "a933b21d-22c1-4e06-818f-15416b971282",
"name": "control_model",
"type": "ControlNetModelField",
"fieldKind": "input",
"label": "",
"value": {
"model_name": "qrcode_monster",
"base_model": "sd-1"
}
},
"control_weight": {
"id": "198a0825-e55e-4496-bc54-c3d7b02f3d75",
"name": "control_weight",
"type": "FloatPolymorphic",
"fieldKind": "input",
"label": "",
"value": 1.4
},
"begin_step_percent": {
"id": "c85ce42f-22af-42a0-8993-676002fb275e",
"name": "begin_step_percent",
"type": "float",
"fieldKind": "input",
"label": "",
"value": 0
},
"end_step_percent": {
"id": "a61a65c4-9e6f-4fe2-96a5-1294d17ec6e4",
"name": "end_step_percent",
"type": "float",
"fieldKind": "input",
"label": "",
"value": 1
},
"control_mode": {
"id": "1aa45cfa-0249-46b7-bf24-3e38e92f5fa0",
"name": "control_mode",
"type": "enum",
"fieldKind": "input",
"label": "",
"value": "balanced"
},
"resize_mode": {
"id": "a89d3cb9-a141-4cea-bb49-977bf267377b",
"name": "resize_mode",
"type": "enum",
"fieldKind": "input",
"label": "",
"value": "just_resize"
}
},
"outputs": {
"control": {
"id": "c9a1fc7e-cb25-45a9-adff-1a97c9ff04d6",
"name": "control",
"type": "ControlField",
"fieldKind": "output"
}
},
"label": "",
"isOpen": true,
"notes": "",
"embedWorkflow": false,
"isIntermediate": true,
"useCache": true,
"version": "1.0.0"
},
"width": 320,
"height": 508,
"position": {
"x": 1165.434407461108,
"y": 1862.916856351665
}
},
{
"id": "28542b66-5a00-4780-a318-0a036d2df914",
"type": "invocation",
"data": {
"id": "28542b66-5a00-4780-a318-0a036d2df914",
"type": "l2i",
"inputs": {
"metadata": {
"id": "a38e8f55-7f2c-4fcc-a71f-d51e2eb0374a",
"name": "metadata",
"type": "MetadataField",
"fieldKind": "input",
"label": ""
},
"latents": {
"id": "80e97bc8-e716-4175-9115-5b58495aa30c",
"name": "latents",
"type": "LatentsField",
"fieldKind": "input",
"label": ""
},
"vae": {
"id": "5641bce6-ac2b-47eb-bb32-2f290026b7e1",
"name": "vae",
"type": "VaeField",
"fieldKind": "input",
"label": ""
},
"tiled": {
"id": "9e75eb16-ae48-47ed-b180-e0409d377436",
"name": "tiled",
"type": "boolean",
"fieldKind": "input",
"label": "",
"value": false
},
"fp32": {
"id": "0518b0ce-ee37-437b-8437-cc2976a3279f",
"name": "fp32",
"type": "boolean",
"fieldKind": "input",
"label": "",
"value": false
}
},
"outputs": {
"image": {
"id": "ec2ff985-a7eb-401f-92c4-1217cddad6a2",
"name": "image",
"type": "ImageField",
"fieldKind": "output"
},
"width": {
"id": "ba1d1720-6d67-4eca-9e9d-b97d08636774",
"name": "width",
"type": "integer",
"fieldKind": "output"
},
"height": {
"id": "10bcf8f4-6394-422f-b0c0-51680f3bfb25",
"name": "height",
"type": "integer",
"fieldKind": "output"
}
},
"label": "",
"isOpen": true,
"notes": "",
"embedWorkflow": false,
"isIntermediate": true,
"useCache": true,
"version": "1.0.0"
},
"width": 320,
"height": 267,
"position": {
"x": 2110.8415693683014,
"y": 1487.253341116115
}
},
{
"id": "9755ae4c-ef30-4db3-80f6-a31f98979a11",
"type": "invocation",
"data": {
"id": "9755ae4c-ef30-4db3-80f6-a31f98979a11",
"type": "denoise_latents",
"inputs": {
"positive_conditioning": {
"id": "8e6aceaa-a986-4ab2-9c04-5b1027b3daf6",
"name": "positive_conditioning",
"type": "ConditioningField",
"fieldKind": "input",
"label": ""
},
"negative_conditioning": {
"id": "fbbaa712-ca1a-420b-9016-763f2a29d68c",
"name": "negative_conditioning",
"type": "ConditioningField",
"fieldKind": "input",
"label": ""
},
"noise": {
"id": "a3b3d5d2-c0f9-4b89-a9b3-8de9418f7bb5",
"name": "noise",
"type": "LatentsField",
"fieldKind": "input",
"label": ""
},
"steps": {
"id": "e491e664-2f8c-4f49-b3e4-57b051fbb9c5",
"name": "steps",
"type": "integer",
"fieldKind": "input",
"label": "",
"value": 10
},
"cfg_scale": {
"id": "f0318abd-ed65-4cad-86a7-48d1c19a6d14",
"name": "cfg_scale",
"type": "FloatPolymorphic",
"fieldKind": "input",
"label": "",
"value": 7.5
},
"denoising_start": {
"id": "f7c24c51-496f-44c4-836a-c734e529fec0",
"name": "denoising_start",
"type": "float",
"fieldKind": "input",
"label": "",
"value": 0
},
"denoising_end": {
"id": "54f7656a-fb0d-4d9e-a459-f700f7dccd2e",
"name": "denoising_end",
"type": "float",
"fieldKind": "input",
"label": "",
"value": 1
},
"scheduler": {
"id": "363ee440-040d-499b-bf84-bf5391b08681",
"name": "scheduler",
"type": "Scheduler",
"fieldKind": "input",
"label": "",
"value": "euler"
},
"unet": {
"id": "5c93d4e5-1064-4700-ab1d-d12e1e9b5ba7",
"name": "unet",
"type": "UNetField",
"fieldKind": "input",
"label": ""
},
"control": {
"id": "e1948eb3-7407-43b0-93e3-139470f186b7",
"name": "control",
"type": "ControlPolymorphic",
"fieldKind": "input",
"label": ""
},
"ip_adapter": {
"id": "5675b2c3-adfb-49ee-b33c-26bdbfab1fed",
"name": "ip_adapter",
"type": "IPAdapterPolymorphic",
"fieldKind": "input",
"label": ""
},
"t2i_adapter": {
"id": "89cd4ab3-3bfc-4063-9de5-91d42305c651",
"name": "t2i_adapter",
"type": "T2IAdapterPolymorphic",
"fieldKind": "input",
"label": ""
},
"latents": {
"id": "ec01df90-5042-418d-b6d6-86b251c13770",
"name": "latents",
"type": "LatentsField",
"fieldKind": "input",
"label": ""
},
"denoise_mask": {
"id": "561cde00-cb20-42ae-9bd3-4f477f73fbe1",
"name": "denoise_mask",
"type": "DenoiseMaskField",
"fieldKind": "input",
"label": ""
}
},
"outputs": {
"latents": {
"id": "f9addefe-efcc-4e01-8945-6ebbc934b002",
"name": "latents",
"type": "LatentsField",
"fieldKind": "output"
},
"width": {
"id": "6d48f78b-d681-422a-8677-0111bd0625f1",
"name": "width",
"type": "integer",
"fieldKind": "output"
},
"height": {
"id": "f25997b8-6316-44ce-b696-b82e4ed51ae5",
"name": "height",
"type": "integer",
"fieldKind": "output"
}
},
"label": "",
"isOpen": true,
"notes": "",
"embedWorkflow": false,
"isIntermediate": true,
"useCache": true,
"version": "1.4.0"
},
"width": 320,
"height": 646,
"position": {
"x": 1597.9598293300219,
"y": 1420.4637727891632
}
},
{
"id": "59349822-af20-4e0e-a53f-3ba135d00c3f",
"type": "invocation",
"data": {
"id": "59349822-af20-4e0e-a53f-3ba135d00c3f",
"type": "rand_int",
"inputs": {
"low": {
"id": "051f22f9-2d4f-414f-bc51-84af2d626efa",
"name": "low",
"type": "integer",
"fieldKind": "input",
"label": "",
"value": 0
},
"high": {
"id": "77206186-f264-4224-9589-f925cf903dc9",
"name": "high",
"type": "integer",
"fieldKind": "input",
"label": "",
"value": 2147483647
}
},
"outputs": {
"value": {
"id": "a7ed9387-3a24-4d34-b7c5-f713bd544ab1",
"name": "value",
"type": "integer",
"fieldKind": "output"
}
},
"label": "",
"isOpen": false,
"notes": "",
"embedWorkflow": false,
"isIntermediate": true,
"useCache": false,
"version": "1.0.0"
},
"width": 320,
"height": 32,
"position": {
"x": 1178.16746986153,
"y": 1663.9433412808876
}
}
],
"edges": [
{
"source": "59349822-af20-4e0e-a53f-3ba135d00c3f",
"target": "280fd8a7-3b0c-49fe-8be4-6246e08b6c9a",
"id": "59349822-af20-4e0e-a53f-3ba135d00c3f-280fd8a7-3b0c-49fe-8be4-6246e08b6c9a-collapsed",
"type": "collapsed"
},
{
"source": "610384f1-6f0c-4847-a9a2-37ce7f456ed1",
"sourceHandle": "clip",
"target": "aca3b054-bfba-4392-bd20-6476f59504df",
"targetHandle": "clip",
"id": "reactflow__edge-610384f1-6f0c-4847-a9a2-37ce7f456ed1clip-aca3b054-bfba-4392-bd20-6476f59504dfclip",
"type": "default"
},
{
"source": "610384f1-6f0c-4847-a9a2-37ce7f456ed1",
"sourceHandle": "clip",
"target": "3db7cee0-31e2-4a3d-94a1-268cb16177dd",
"targetHandle": "clip",
"id": "reactflow__edge-610384f1-6f0c-4847-a9a2-37ce7f456ed1clip-3db7cee0-31e2-4a3d-94a1-268cb16177ddclip",
"type": "default"
},
{
"source": "a6cc0986-f928-4a7e-8d44-ba2d4b36f54a",
"sourceHandle": "image",
"target": "2ac03cf6-0326-454a-bed0-d8baef2bf30d",
"targetHandle": "image",
"id": "reactflow__edge-a6cc0986-f928-4a7e-8d44-ba2d4b36f54aimage-2ac03cf6-0326-454a-bed0-d8baef2bf30dimage",
"type": "default"
},
{
"source": "610384f1-6f0c-4847-a9a2-37ce7f456ed1",
"sourceHandle": "vae",
"target": "28542b66-5a00-4780-a318-0a036d2df914",
"targetHandle": "vae",
"id": "reactflow__edge-610384f1-6f0c-4847-a9a2-37ce7f456ed1vae-28542b66-5a00-4780-a318-0a036d2df914vae",
"type": "default"
},
{
"source": "280fd8a7-3b0c-49fe-8be4-6246e08b6c9a",
"sourceHandle": "noise",
"target": "9755ae4c-ef30-4db3-80f6-a31f98979a11",
"targetHandle": "noise",
"id": "reactflow__edge-280fd8a7-3b0c-49fe-8be4-6246e08b6c9anoise-9755ae4c-ef30-4db3-80f6-a31f98979a11noise",
"type": "default"
},
{
"source": "3db7cee0-31e2-4a3d-94a1-268cb16177dd",
"sourceHandle": "conditioning",
"target": "9755ae4c-ef30-4db3-80f6-a31f98979a11",
"targetHandle": "negative_conditioning",
"id": "reactflow__edge-3db7cee0-31e2-4a3d-94a1-268cb16177ddconditioning-9755ae4c-ef30-4db3-80f6-a31f98979a11negative_conditioning",
"type": "default"
},
{
"source": "aca3b054-bfba-4392-bd20-6476f59504df",
"sourceHandle": "conditioning",
"target": "9755ae4c-ef30-4db3-80f6-a31f98979a11",
"targetHandle": "positive_conditioning",
"id": "reactflow__edge-aca3b054-bfba-4392-bd20-6476f59504dfconditioning-9755ae4c-ef30-4db3-80f6-a31f98979a11positive_conditioning",
"type": "default"
},
{
"source": "610384f1-6f0c-4847-a9a2-37ce7f456ed1",
"sourceHandle": "unet",
"target": "9755ae4c-ef30-4db3-80f6-a31f98979a11",
"targetHandle": "unet",
"id": "reactflow__edge-610384f1-6f0c-4847-a9a2-37ce7f456ed1unet-9755ae4c-ef30-4db3-80f6-a31f98979a11unet",
"type": "default"
},
{
"source": "2ac03cf6-0326-454a-bed0-d8baef2bf30d",
"sourceHandle": "control",
"target": "9755ae4c-ef30-4db3-80f6-a31f98979a11",
"targetHandle": "control",
"id": "reactflow__edge-2ac03cf6-0326-454a-bed0-d8baef2bf30dcontrol-9755ae4c-ef30-4db3-80f6-a31f98979a11control",
"type": "default"
},
{
"source": "9755ae4c-ef30-4db3-80f6-a31f98979a11",
"sourceHandle": "latents",
"target": "28542b66-5a00-4780-a318-0a036d2df914",
"targetHandle": "latents",
"id": "reactflow__edge-9755ae4c-ef30-4db3-80f6-a31f98979a11latents-28542b66-5a00-4780-a318-0a036d2df914latents",
"type": "default"
},
{
"source": "59349822-af20-4e0e-a53f-3ba135d00c3f",
"sourceHandle": "value",
"target": "280fd8a7-3b0c-49fe-8be4-6246e08b6c9a",
"targetHandle": "seed",
"id": "reactflow__edge-59349822-af20-4e0e-a53f-3ba135d00c3fvalue-280fd8a7-3b0c-49fe-8be4-6246e08b6c9aseed",
"type": "default"
}
]
}

View File

@@ -26,10 +26,6 @@
{ {
"nodeId": "3193ad09-a7c2-4bf4-a3a9-1c61cc33a204", "nodeId": "3193ad09-a7c2-4bf4-a3a9-1c61cc33a204",
"fieldName": "style" "fieldName": "style"
},
{
"nodeId": "87ee6243-fb0d-4f77-ad5f-56591659339e",
"fieldName": "steps"
} }
], ],
"meta": { "meta": {
@@ -40,7 +36,6 @@
"id": "3193ad09-a7c2-4bf4-a3a9-1c61cc33a204", "id": "3193ad09-a7c2-4bf4-a3a9-1c61cc33a204",
"type": "invocation", "type": "invocation",
"data": { "data": {
"version": "1.0.0",
"id": "3193ad09-a7c2-4bf4-a3a9-1c61cc33a204", "id": "3193ad09-a7c2-4bf4-a3a9-1c61cc33a204",
"type": "sdxl_compel_prompt", "type": "sdxl_compel_prompt",
"inputs": { "inputs": {
@@ -135,10 +130,12 @@
"isOpen": true, "isOpen": true,
"notes": "", "notes": "",
"embedWorkflow": false, "embedWorkflow": false,
"isIntermediate": true "isIntermediate": true,
"useCache": true,
"version": "1.0.0"
}, },
"width": 320, "width": 320,
"height": 764, "height": 793,
"position": { "position": {
"x": 1275, "x": 1275,
"y": -350 "y": -350
@@ -148,7 +145,6 @@
"id": "55705012-79b9-4aac-9f26-c0b10309785b", "id": "55705012-79b9-4aac-9f26-c0b10309785b",
"type": "invocation", "type": "invocation",
"data": { "data": {
"version": "1.0.0",
"id": "55705012-79b9-4aac-9f26-c0b10309785b", "id": "55705012-79b9-4aac-9f26-c0b10309785b",
"type": "noise", "type": "noise",
"inputs": { "inputs": {
@@ -209,7 +205,9 @@
"isOpen": false, "isOpen": false,
"notes": "", "notes": "",
"embedWorkflow": false, "embedWorkflow": false,
"isIntermediate": true "isIntermediate": true,
"useCache": true,
"version": "1.0.0"
}, },
"width": 320, "width": 320,
"height": 32, "height": 32,
@@ -218,83 +216,10 @@
"y": -300 "y": -300
} }
}, },
{
"id": "dbcd2f98-d809-48c8-bf64-2635f88a2fe9",
"type": "invocation",
"data": {
"version": "1.0.0",
"id": "dbcd2f98-d809-48c8-bf64-2635f88a2fe9",
"type": "l2i",
"inputs": {
"tiled": {
"id": "24f5bc7b-f6a1-425d-8ab1-f50b4db5d0df",
"name": "tiled",
"type": "boolean",
"fieldKind": "input",
"label": "",
"value": false
},
"fp32": {
"id": "b146d873-ffb9-4767-986a-5360504841a2",
"name": "fp32",
"type": "boolean",
"fieldKind": "input",
"label": "",
"value": true
},
"latents": {
"id": "65441abd-7713-4b00-9d8d-3771404002e8",
"name": "latents",
"type": "LatentsField",
"fieldKind": "input",
"label": ""
},
"vae": {
"id": "a478b833-6e13-4611-9a10-842c89603c74",
"name": "vae",
"type": "VaeField",
"fieldKind": "input",
"label": ""
}
},
"outputs": {
"image": {
"id": "c87ae925-f858-417a-8940-8708ba9b4b53",
"name": "image",
"type": "ImageField",
"fieldKind": "output"
},
"width": {
"id": "4bcb8512-b5a1-45f1-9e52-6e92849f9d6c",
"name": "width",
"type": "integer",
"fieldKind": "output"
},
"height": {
"id": "23e41c00-a354-48e8-8f59-5875679c27ab",
"name": "height",
"type": "integer",
"fieldKind": "output"
}
},
"label": "",
"isOpen": true,
"notes": "",
"embedWorkflow": true,
"isIntermediate": false
},
"width": 320,
"height": 224,
"position": {
"x": 2025,
"y": -250
}
},
{ {
"id": "ea94bc37-d995-4a83-aa99-4af42479f2f2", "id": "ea94bc37-d995-4a83-aa99-4af42479f2f2",
"type": "invocation", "type": "invocation",
"data": { "data": {
"version": "1.0.0",
"id": "ea94bc37-d995-4a83-aa99-4af42479f2f2", "id": "ea94bc37-d995-4a83-aa99-4af42479f2f2",
"type": "rand_int", "type": "rand_int",
"inputs": { "inputs": {
@@ -327,7 +252,9 @@
"isOpen": false, "isOpen": false,
"notes": "", "notes": "",
"embedWorkflow": false, "embedWorkflow": false,
"isIntermediate": true "isIntermediate": true,
"useCache": false,
"version": "1.0.0"
}, },
"width": 320, "width": 320,
"height": 32, "height": 32,
@@ -340,7 +267,6 @@
"id": "30d3289c-773c-4152-a9d2-bd8a99c8fd22", "id": "30d3289c-773c-4152-a9d2-bd8a99c8fd22",
"type": "invocation", "type": "invocation",
"data": { "data": {
"version": "1.0.0",
"id": "30d3289c-773c-4152-a9d2-bd8a99c8fd22", "id": "30d3289c-773c-4152-a9d2-bd8a99c8fd22",
"type": "sdxl_model_loader", "type": "sdxl_model_loader",
"inputs": { "inputs": {
@@ -351,7 +277,7 @@
"fieldKind": "input", "fieldKind": "input",
"label": "", "label": "",
"value": { "value": {
"model_name": "stable-diffusion-xl-base-1.0", "model_name": "stable-diffusion-xl-base-1-0",
"base_model": "sdxl", "base_model": "sdxl",
"model_type": "main" "model_type": "main"
} }
@@ -387,10 +313,12 @@
"isOpen": true, "isOpen": true,
"notes": "", "notes": "",
"embedWorkflow": false, "embedWorkflow": false,
"isIntermediate": true "isIntermediate": true,
"useCache": true,
"version": "1.0.0"
}, },
"width": 320, "width": 320,
"height": 234, "height": 258,
"position": { "position": {
"x": 475, "x": 475,
"y": 25 "y": 25
@@ -400,7 +328,6 @@
"id": "faf965a4-7530-427b-b1f3-4ba6505c2a08", "id": "faf965a4-7530-427b-b1f3-4ba6505c2a08",
"type": "invocation", "type": "invocation",
"data": { "data": {
"version": "1.0.0",
"id": "faf965a4-7530-427b-b1f3-4ba6505c2a08", "id": "faf965a4-7530-427b-b1f3-4ba6505c2a08",
"type": "sdxl_compel_prompt", "type": "sdxl_compel_prompt",
"inputs": { "inputs": {
@@ -495,128 +422,77 @@
"isOpen": true, "isOpen": true,
"notes": "", "notes": "",
"embedWorkflow": false, "embedWorkflow": false,
"isIntermediate": true "isIntermediate": true,
"useCache": true,
"version": "1.0.0"
}, },
"width": 320, "width": 320,
"height": 764, "height": 793,
"position": { "position": {
"x": 900, "x": 900,
"y": -350 "y": -350
} }
}, },
{ {
"id": "87ee6243-fb0d-4f77-ad5f-56591659339e", "id": "63e91020-83b2-4f35-b174-ad9692aabb48",
"type": "invocation", "type": "invocation",
"data": { "data": {
"version": "1.0.0", "id": "63e91020-83b2-4f35-b174-ad9692aabb48",
"id": "87ee6243-fb0d-4f77-ad5f-56591659339e", "type": "l2i",
"type": "denoise_latents",
"inputs": { "inputs": {
"noise": { "metadata": {
"id": "4884a4b7-cc19-4fea-83c7-1f940e6edd24", "id": "88971324-3fdb-442d-b8b7-7612478a8622",
"name": "noise", "name": "metadata",
"type": "LatentsField", "type": "MetadataField",
"fieldKind": "input",
"label": ""
},
"steps": {
"id": "4c61675c-b6b9-41ac-b187-b5c13b587039",
"name": "steps",
"type": "integer",
"fieldKind": "input",
"label": "",
"value": 36
},
"cfg_scale": {
"id": "f8213f35-4637-4a1a-83f4-1f8cfb9ccd2c",
"name": "cfg_scale",
"type": "float",
"fieldKind": "input",
"label": "",
"value": 7.5
},
"denoising_start": {
"id": "01e2f30d-0acd-4e21-98b9-a9b8e24c6db2",
"name": "denoising_start",
"type": "float",
"fieldKind": "input",
"label": "",
"value": 0
},
"denoising_end": {
"id": "3db95479-a73b-4c75-9b44-08daec16b224",
"name": "denoising_end",
"type": "float",
"fieldKind": "input",
"label": "",
"value": 1
},
"scheduler": {
"id": "db8430a9-64c3-4c54-ae38-9f597cf7b6d5",
"name": "scheduler",
"type": "Scheduler",
"fieldKind": "input",
"label": "",
"value": "euler"
},
"control": {
"id": "599b49e8-6435-4576-be41-a5155f3a17e3",
"name": "control",
"type": "ControlField",
"fieldKind": "input", "fieldKind": "input",
"label": "" "label": ""
}, },
"latents": { "latents": {
"id": "226f9e91-454e-4159-9fa6-019c0cf29277", "id": "da0e40cb-c49f-4fa5-9856-338b91a65f6b",
"name": "latents", "name": "latents",
"type": "LatentsField", "type": "LatentsField",
"fieldKind": "input", "fieldKind": "input",
"label": "" "label": ""
}, },
"denoise_mask": { "vae": {
"id": "de019cb6-7fb5-45bf-a266-22e20889893f", "id": "ae5164ce-1710-4ec5-a83a-6113a0d1b5c0",
"name": "denoise_mask", "name": "vae",
"type": "DenoiseMaskField", "type": "VaeField",
"fieldKind": "input", "fieldKind": "input",
"label": "" "label": ""
}, },
"positive_conditioning": { "tiled": {
"id": "02fc400a-110d-470e-8411-f404f966a949", "id": "2ccfd535-1a7b-4ecf-84db-9430a64fb3d7",
"name": "positive_conditioning", "name": "tiled",
"type": "ConditioningField", "type": "boolean",
"fieldKind": "input", "fieldKind": "input",
"label": "" "label": "",
"value": false
}, },
"negative_conditioning": { "fp32": {
"id": "4bd3bdfa-fcf4-42be-8e47-1e314255798f", "id": "64f07d5a-54a2-429c-8c5b-0c2a3a8e5cd5",
"name": "negative_conditioning", "name": "fp32",
"type": "ConditioningField", "type": "boolean",
"fieldKind": "input", "fieldKind": "input",
"label": "" "label": "",
}, "value": false
"unet": {
"id": "7c2d58a8-b5f1-4e63-8ffd-8ada52c35832",
"name": "unet",
"type": "UNetField",
"fieldKind": "input",
"label": ""
} }
}, },
"outputs": { "outputs": {
"latents": { "image": {
"id": "6a6fa492-de26-4e95-b1d9-a322fe37eb13", "id": "9b281eaa-6504-407d-a5ca-1e5e8020a4bf",
"name": "latents", "name": "image",
"type": "LatentsField", "type": "ImageField",
"fieldKind": "output" "fieldKind": "output"
}, },
"width": { "width": {
"id": "a9790729-7d6c-4418-903d-4da961fccf56", "id": "98e545f3-b53b-490d-b94d-bed9418ccc75",
"name": "width", "name": "width",
"type": "integer", "type": "integer",
"fieldKind": "output" "fieldKind": "output"
}, },
"height": { "height": {
"id": "fa74efe5-7330-4a3c-b256-c82a544585b4", "id": "4a74bd43-d7f7-4c7f-bb3b-d09bb2992c46",
"name": "height", "name": "height",
"type": "integer", "type": "integer",
"fieldKind": "output" "fieldKind": "output"
@@ -626,13 +502,161 @@
"isOpen": true, "isOpen": true,
"notes": "", "notes": "",
"embedWorkflow": false, "embedWorkflow": false,
"isIntermediate": true "isIntermediate": false,
"useCache": true,
"version": "1.0.0"
}, },
"width": 320, "width": 320,
"height": 558, "height": 267,
"position": { "position": {
"x": 1650, "x": 2112.5626808057173,
"y": -250 "y": -174.24042139280238
}
},
{
"id": "50a36525-3c0a-4cc5-977c-e4bfc3fd6dfb",
"type": "invocation",
"data": {
"id": "50a36525-3c0a-4cc5-977c-e4bfc3fd6dfb",
"type": "denoise_latents",
"inputs": {
"positive_conditioning": {
"id": "29b73dfa-a06e-4b4a-a844-515b9eb93a81",
"name": "positive_conditioning",
"type": "ConditioningField",
"fieldKind": "input",
"label": ""
},
"negative_conditioning": {
"id": "a81e6f5b-f4de-4919-b483-b6e2f067465a",
"name": "negative_conditioning",
"type": "ConditioningField",
"fieldKind": "input",
"label": ""
},
"noise": {
"id": "4ba06bb7-eb45-4fb9-9984-31001b545587",
"name": "noise",
"type": "LatentsField",
"fieldKind": "input",
"label": ""
},
"steps": {
"id": "36ee8a45-ca69-44bc-9bc3-aa881e6045c0",
"name": "steps",
"type": "integer",
"fieldKind": "input",
"label": "",
"value": 10
},
"cfg_scale": {
"id": "2a2024e0-a736-46ec-933c-c1c1ebe96943",
"name": "cfg_scale",
"type": "FloatPolymorphic",
"fieldKind": "input",
"label": "",
"value": 7.5
},
"denoising_start": {
"id": "be219d5e-41b7-430a-8fb5-bc21a31ad219",
"name": "denoising_start",
"type": "float",
"fieldKind": "input",
"label": "",
"value": 0
},
"denoising_end": {
"id": "3adfb7ae-c9f7-4a40-b6e0-4c2050bd1a99",
"name": "denoising_end",
"type": "float",
"fieldKind": "input",
"label": "",
"value": 1
},
"scheduler": {
"id": "14423e0d-7215-4ee0-b065-f9e95eaa8d7d",
"name": "scheduler",
"type": "Scheduler",
"fieldKind": "input",
"label": "",
"value": "euler"
},
"unet": {
"id": "e73bbf98-6489-492b-b83c-faed215febac",
"name": "unet",
"type": "UNetField",
"fieldKind": "input",
"label": ""
},
"control": {
"id": "dab351b3-0c86-4ea5-9782-4e8edbfb0607",
"name": "control",
"type": "ControlPolymorphic",
"fieldKind": "input",
"label": ""
},
"ip_adapter": {
"id": "192daea0-a90a-43cc-a2ee-0114a8e90318",
"name": "ip_adapter",
"type": "IPAdapterPolymorphic",
"fieldKind": "input",
"label": ""
},
"t2i_adapter": {
"id": "ee386a55-d4c7-48c1-ac57-7bc4e3aada7a",
"name": "t2i_adapter",
"type": "T2IAdapterPolymorphic",
"fieldKind": "input",
"label": ""
},
"latents": {
"id": "3a922c6a-3d8c-4c9e-b3ec-2f4d81cda077",
"name": "latents",
"type": "LatentsField",
"fieldKind": "input",
"label": ""
},
"denoise_mask": {
"id": "cd7ce032-835f-495f-8b45-d57272f33132",
"name": "denoise_mask",
"type": "DenoiseMaskField",
"fieldKind": "input",
"label": ""
}
},
"outputs": {
"latents": {
"id": "6260b84f-8361-470a-98d8-5b22a45c2d8c",
"name": "latents",
"type": "LatentsField",
"fieldKind": "output"
},
"width": {
"id": "aede0ecf-25b6-46be-aa30-b77f79715deb",
"name": "width",
"type": "integer",
"fieldKind": "output"
},
"height": {
"id": "519abf62-d475-48ef-ab8f-66136bc0e499",
"name": "height",
"type": "integer",
"fieldKind": "output"
}
},
"label": "",
"isOpen": true,
"notes": "",
"embedWorkflow": false,
"isIntermediate": true,
"useCache": true,
"version": "1.4.0"
},
"width": 320,
"height": 646,
"position": {
"x": 1642.955772577545,
"y": -230.2485847594651
} }
} }
], ],
@@ -686,50 +710,42 @@
{ {
"source": "30d3289c-773c-4152-a9d2-bd8a99c8fd22", "source": "30d3289c-773c-4152-a9d2-bd8a99c8fd22",
"sourceHandle": "vae", "sourceHandle": "vae",
"target": "dbcd2f98-d809-48c8-bf64-2635f88a2fe9", "target": "63e91020-83b2-4f35-b174-ad9692aabb48",
"targetHandle": "vae", "targetHandle": "vae",
"id": "reactflow__edge-30d3289c-773c-4152-a9d2-bd8a99c8fd22vae-dbcd2f98-d809-48c8-bf64-2635f88a2fe9vae", "id": "reactflow__edge-30d3289c-773c-4152-a9d2-bd8a99c8fd22vae-63e91020-83b2-4f35-b174-ad9692aabb48vae",
"type": "default"
},
{
"source": "87ee6243-fb0d-4f77-ad5f-56591659339e",
"sourceHandle": "latents",
"target": "dbcd2f98-d809-48c8-bf64-2635f88a2fe9",
"targetHandle": "latents",
"id": "reactflow__edge-87ee6243-fb0d-4f77-ad5f-56591659339elatents-dbcd2f98-d809-48c8-bf64-2635f88a2fe9latents",
"type": "default"
},
{
"source": "faf965a4-7530-427b-b1f3-4ba6505c2a08",
"sourceHandle": "conditioning",
"target": "87ee6243-fb0d-4f77-ad5f-56591659339e",
"targetHandle": "positive_conditioning",
"id": "reactflow__edge-faf965a4-7530-427b-b1f3-4ba6505c2a08conditioning-87ee6243-fb0d-4f77-ad5f-56591659339epositive_conditioning",
"type": "default"
},
{
"source": "3193ad09-a7c2-4bf4-a3a9-1c61cc33a204",
"sourceHandle": "conditioning",
"target": "87ee6243-fb0d-4f77-ad5f-56591659339e",
"targetHandle": "negative_conditioning",
"id": "reactflow__edge-3193ad09-a7c2-4bf4-a3a9-1c61cc33a204conditioning-87ee6243-fb0d-4f77-ad5f-56591659339enegative_conditioning",
"type": "default" "type": "default"
}, },
{ {
"source": "30d3289c-773c-4152-a9d2-bd8a99c8fd22", "source": "30d3289c-773c-4152-a9d2-bd8a99c8fd22",
"sourceHandle": "unet", "sourceHandle": "unet",
"target": "87ee6243-fb0d-4f77-ad5f-56591659339e", "target": "50a36525-3c0a-4cc5-977c-e4bfc3fd6dfb",
"targetHandle": "unet", "targetHandle": "unet",
"id": "reactflow__edge-30d3289c-773c-4152-a9d2-bd8a99c8fd22unet-87ee6243-fb0d-4f77-ad5f-56591659339eunet", "id": "reactflow__edge-30d3289c-773c-4152-a9d2-bd8a99c8fd22unet-50a36525-3c0a-4cc5-977c-e4bfc3fd6dfbunet",
"type": "default"
},
{
"source": "faf965a4-7530-427b-b1f3-4ba6505c2a08",
"sourceHandle": "conditioning",
"target": "50a36525-3c0a-4cc5-977c-e4bfc3fd6dfb",
"targetHandle": "positive_conditioning",
"id": "reactflow__edge-faf965a4-7530-427b-b1f3-4ba6505c2a08conditioning-50a36525-3c0a-4cc5-977c-e4bfc3fd6dfbpositive_conditioning",
"type": "default"
},
{
"source": "3193ad09-a7c2-4bf4-a3a9-1c61cc33a204",
"sourceHandle": "conditioning",
"target": "50a36525-3c0a-4cc5-977c-e4bfc3fd6dfb",
"targetHandle": "negative_conditioning",
"id": "reactflow__edge-3193ad09-a7c2-4bf4-a3a9-1c61cc33a204conditioning-50a36525-3c0a-4cc5-977c-e4bfc3fd6dfbnegative_conditioning",
"type": "default" "type": "default"
}, },
{ {
"source": "55705012-79b9-4aac-9f26-c0b10309785b", "source": "55705012-79b9-4aac-9f26-c0b10309785b",
"sourceHandle": "noise", "sourceHandle": "noise",
"target": "87ee6243-fb0d-4f77-ad5f-56591659339e", "target": "50a36525-3c0a-4cc5-977c-e4bfc3fd6dfb",
"targetHandle": "noise", "targetHandle": "noise",
"id": "reactflow__edge-55705012-79b9-4aac-9f26-c0b10309785bnoise-87ee6243-fb0d-4f77-ad5f-56591659339enoise", "id": "reactflow__edge-55705012-79b9-4aac-9f26-c0b10309785bnoise-50a36525-3c0a-4cc5-977c-e4bfc3fd6dfbnoise",
"type": "default" "type": "default"
} }
] ]
} }

File diff suppressed because it is too large Load Diff

View File

@@ -18,10 +18,6 @@
{ {
"nodeId": "93dc02a4-d05b-48ed-b99c-c9b616af3402", "nodeId": "93dc02a4-d05b-48ed-b99c-c9b616af3402",
"fieldName": "prompt" "fieldName": "prompt"
},
{
"nodeId": "75899702-fa44-46d2-b2d5-3e17f234c3e7",
"fieldName": "steps"
} }
], ],
"meta": { "meta": {
@@ -32,7 +28,6 @@
"id": "93dc02a4-d05b-48ed-b99c-c9b616af3402", "id": "93dc02a4-d05b-48ed-b99c-c9b616af3402",
"type": "invocation", "type": "invocation",
"data": { "data": {
"version": "1.0.0",
"id": "93dc02a4-d05b-48ed-b99c-c9b616af3402", "id": "93dc02a4-d05b-48ed-b99c-c9b616af3402",
"type": "compel", "type": "compel",
"inputs": { "inputs": {
@@ -64,20 +59,21 @@
"isOpen": true, "isOpen": true,
"notes": "", "notes": "",
"embedWorkflow": false, "embedWorkflow": false,
"isIntermediate": true "isIntermediate": true,
"useCache": true,
"version": "1.0.0"
}, },
"width": 320, "width": 320,
"height": 235, "height": 261,
"position": { "position": {
"x": 1400, "x": 995.7263915923627,
"y": -75 "y": 239.67783573351227
} }
}, },
{ {
"id": "55705012-79b9-4aac-9f26-c0b10309785b", "id": "55705012-79b9-4aac-9f26-c0b10309785b",
"type": "invocation", "type": "invocation",
"data": { "data": {
"version": "1.0.0",
"id": "55705012-79b9-4aac-9f26-c0b10309785b", "id": "55705012-79b9-4aac-9f26-c0b10309785b",
"type": "noise", "type": "noise",
"inputs": { "inputs": {
@@ -138,92 +134,21 @@
"isOpen": true, "isOpen": true,
"notes": "", "notes": "",
"embedWorkflow": false, "embedWorkflow": false,
"isIntermediate": true "isIntermediate": true,
"useCache": true,
"version": "1.0.0"
}, },
"width": 320, "width": 320,
"height": 364, "height": 389,
"position": { "position": {
"x": 1000, "x": 993.4442117555518,
"y": 350 "y": 605.6757415334787
}
},
{
"id": "dbcd2f98-d809-48c8-bf64-2635f88a2fe9",
"type": "invocation",
"data": {
"version": "1.0.0",
"id": "dbcd2f98-d809-48c8-bf64-2635f88a2fe9",
"type": "l2i",
"inputs": {
"tiled": {
"id": "24f5bc7b-f6a1-425d-8ab1-f50b4db5d0df",
"name": "tiled",
"type": "boolean",
"fieldKind": "input",
"label": "",
"value": false
},
"fp32": {
"id": "b146d873-ffb9-4767-986a-5360504841a2",
"name": "fp32",
"type": "boolean",
"fieldKind": "input",
"label": "",
"value": false
},
"latents": {
"id": "65441abd-7713-4b00-9d8d-3771404002e8",
"name": "latents",
"type": "LatentsField",
"fieldKind": "input",
"label": ""
},
"vae": {
"id": "a478b833-6e13-4611-9a10-842c89603c74",
"name": "vae",
"type": "VaeField",
"fieldKind": "input",
"label": ""
}
},
"outputs": {
"image": {
"id": "c87ae925-f858-417a-8940-8708ba9b4b53",
"name": "image",
"type": "ImageField",
"fieldKind": "output"
},
"width": {
"id": "4bcb8512-b5a1-45f1-9e52-6e92849f9d6c",
"name": "width",
"type": "integer",
"fieldKind": "output"
},
"height": {
"id": "23e41c00-a354-48e8-8f59-5875679c27ab",
"name": "height",
"type": "integer",
"fieldKind": "output"
}
},
"label": "",
"isOpen": true,
"notes": "",
"embedWorkflow": true,
"isIntermediate": false
},
"width": 320,
"height": 266,
"position": {
"x": 1800,
"y": 200
} }
}, },
{ {
"id": "c8d55139-f380-4695-b7f2-8b3d1e1e3db8", "id": "c8d55139-f380-4695-b7f2-8b3d1e1e3db8",
"type": "invocation", "type": "invocation",
"data": { "data": {
"version": "1.0.0",
"id": "c8d55139-f380-4695-b7f2-8b3d1e1e3db8", "id": "c8d55139-f380-4695-b7f2-8b3d1e1e3db8",
"type": "main_model_loader", "type": "main_model_loader",
"inputs": { "inputs": {
@@ -261,23 +186,24 @@
} }
}, },
"label": "", "label": "",
"isOpen": false, "isOpen": true,
"notes": "", "notes": "",
"embedWorkflow": false, "embedWorkflow": false,
"isIntermediate": true "isIntermediate": true,
"useCache": true,
"version": "1.0.0"
}, },
"width": 320, "width": 320,
"height": 32, "height": 226,
"position": { "position": {
"x": 1000, "x": 163.04436745878343,
"y": 200 "y": 254.63156870373479
} }
}, },
{ {
"id": "7d8bf987-284f-413a-b2fd-d825445a5d6c", "id": "7d8bf987-284f-413a-b2fd-d825445a5d6c",
"type": "invocation", "type": "invocation",
"data": { "data": {
"version": "1.0.0",
"id": "7d8bf987-284f-413a-b2fd-d825445a5d6c", "id": "7d8bf987-284f-413a-b2fd-d825445a5d6c",
"type": "compel", "type": "compel",
"inputs": { "inputs": {
@@ -309,20 +235,21 @@
"isOpen": true, "isOpen": true,
"notes": "", "notes": "",
"embedWorkflow": false, "embedWorkflow": false,
"isIntermediate": true "isIntermediate": true,
"useCache": true,
"version": "1.0.0"
}, },
"width": 320, "width": 320,
"height": 235, "height": 261,
"position": { "position": {
"x": 1000, "x": 595.7263915923627,
"y": -75 "y": 239.67783573351227
} }
}, },
{ {
"id": "ea94bc37-d995-4a83-aa99-4af42479f2f2", "id": "ea94bc37-d995-4a83-aa99-4af42479f2f2",
"type": "invocation", "type": "invocation",
"data": { "data": {
"version": "1.0.0",
"id": "ea94bc37-d995-4a83-aa99-4af42479f2f2", "id": "ea94bc37-d995-4a83-aa99-4af42479f2f2",
"type": "rand_int", "type": "rand_int",
"inputs": { "inputs": {
@@ -352,51 +279,66 @@
} }
}, },
"label": "Random Seed", "label": "Random Seed",
"isOpen": false, "isOpen": true,
"notes": "", "notes": "",
"embedWorkflow": false, "embedWorkflow": false,
"isIntermediate": true "isIntermediate": true,
"useCache": false,
"version": "1.0.0"
}, },
"width": 320, "width": 320,
"height": 32, "height": 218,
"position": { "position": {
"x": 1000, "x": 541.094822888628,
"y": 275 "y": 694.5704476446829
} }
}, },
{ {
"id": "75899702-fa44-46d2-b2d5-3e17f234c3e7", "id": "eea2702a-19fb-45b5-9d75-56b4211ec03c",
"type": "invocation", "type": "invocation",
"data": { "data": {
"version": "1.0.0", "id": "eea2702a-19fb-45b5-9d75-56b4211ec03c",
"id": "75899702-fa44-46d2-b2d5-3e17f234c3e7",
"type": "denoise_latents", "type": "denoise_latents",
"inputs": { "inputs": {
"positive_conditioning": {
"id": "90b7f4f8-ada7-4028-8100-d2e54f192052",
"name": "positive_conditioning",
"type": "ConditioningField",
"fieldKind": "input",
"label": ""
},
"negative_conditioning": {
"id": "9393779e-796c-4f64-b740-902a1177bf53",
"name": "negative_conditioning",
"type": "ConditioningField",
"fieldKind": "input",
"label": ""
},
"noise": { "noise": {
"id": "8b18f3eb-40d2-45c1-9a9d-28d6af0dce2b", "id": "8e17f1e5-4f98-40b1-b7f4-86aeeb4554c1",
"name": "noise", "name": "noise",
"type": "LatentsField", "type": "LatentsField",
"fieldKind": "input", "fieldKind": "input",
"label": "" "label": ""
}, },
"steps": { "steps": {
"id": "0be4373c-46f3-441c-80a7-a4bb6ceb498c", "id": "9b63302d-6bd2-42c9-ac13-9b1afb51af88",
"name": "steps", "name": "steps",
"type": "integer", "type": "integer",
"fieldKind": "input", "fieldKind": "input",
"label": "", "label": "",
"value": 36 "value": 10
}, },
"cfg_scale": { "cfg_scale": {
"id": "107267ce-4666-4cd7-94b3-7476b7973ae9", "id": "87dd04d3-870e-49e1-98bf-af003a810109",
"name": "cfg_scale", "name": "cfg_scale",
"type": "float", "type": "FloatPolymorphic",
"fieldKind": "input", "fieldKind": "input",
"label": "", "label": "",
"value": 7.5 "value": 7.5
}, },
"denoising_start": { "denoising_start": {
"id": "d2ce9f0f-5fc2-48b2-b917-53442941e9a1", "id": "f369d80f-4931-4740-9bcd-9f0620719fab",
"name": "denoising_start", "name": "denoising_start",
"type": "float", "type": "float",
"fieldKind": "input", "fieldKind": "input",
@@ -404,7 +346,7 @@
"value": 0 "value": 0
}, },
"denoising_end": { "denoising_end": {
"id": "8ad51505-b8d0-422a-beb8-96fc6fc6b65f", "id": "747d10e5-6f02-445c-994c-0604d814de8c",
"name": "denoising_end", "name": "denoising_end",
"type": "float", "type": "float",
"fieldKind": "input", "fieldKind": "input",
@@ -412,71 +354,71 @@
"value": 1 "value": 1
}, },
"scheduler": { "scheduler": {
"id": "53092874-a43b-4623-91a2-76e62fdb1f2e", "id": "1de84a4e-3a24-4ec8-862b-16ce49633b9b",
"name": "scheduler", "name": "scheduler",
"type": "Scheduler", "type": "Scheduler",
"fieldKind": "input", "fieldKind": "input",
"label": "", "label": "",
"value": "euler" "value": "euler"
}, },
"unet": {
"id": "ffa6fef4-3ce2-4bdb-9296-9a834849489b",
"name": "unet",
"type": "UNetField",
"fieldKind": "input",
"label": ""
},
"control": { "control": {
"id": "7abe57cc-469d-437e-ad72-a18efa28215f", "id": "077b64cb-34be-4fcc-83f2-e399807a02bd",
"name": "control", "name": "control",
"type": "ControlField", "type": "ControlPolymorphic",
"fieldKind": "input",
"label": ""
},
"ip_adapter": {
"id": "1d6948f7-3a65-4a65-a20c-768b287251aa",
"name": "ip_adapter",
"type": "IPAdapterPolymorphic",
"fieldKind": "input",
"label": ""
},
"t2i_adapter": {
"id": "75e67b09-952f-4083-aaf4-6b804d690412",
"name": "t2i_adapter",
"type": "T2IAdapterPolymorphic",
"fieldKind": "input", "fieldKind": "input",
"label": "" "label": ""
}, },
"latents": { "latents": {
"id": "add8bbe5-14d0-42d4-a867-9c65ab8dd129", "id": "334d4ba3-5a99-4195-82c5-86fb3f4f7d43",
"name": "latents", "name": "latents",
"type": "LatentsField", "type": "LatentsField",
"fieldKind": "input", "fieldKind": "input",
"label": "" "label": ""
}, },
"denoise_mask": { "denoise_mask": {
"id": "f373a190-0fc8-45b7-ae62-c4aa8e9687e1", "id": "0d3dbdbf-b014-4e95-8b18-ff2ff9cb0bfa",
"name": "denoise_mask", "name": "denoise_mask",
"type": "DenoiseMaskField", "type": "DenoiseMaskField",
"fieldKind": "input", "fieldKind": "input",
"label": "" "label": ""
},
"positive_conditioning": {
"id": "c7160303-8a23-4f15-9197-855d48802a7f",
"name": "positive_conditioning",
"type": "ConditioningField",
"fieldKind": "input",
"label": ""
},
"negative_conditioning": {
"id": "fd750efa-1dfc-4d0b-accb-828e905ba320",
"name": "negative_conditioning",
"type": "ConditioningField",
"fieldKind": "input",
"label": ""
},
"unet": {
"id": "af1f41ba-ce2a-4314-8d7f-494bb5800381",
"name": "unet",
"type": "UNetField",
"fieldKind": "input",
"label": ""
} }
}, },
"outputs": { "outputs": {
"latents": { "latents": {
"id": "8508d04d-f999-4a44-94d0-388ab1401d27", "id": "70fa5bbc-0c38-41bb-861a-74d6d78d2f38",
"name": "latents", "name": "latents",
"type": "LatentsField", "type": "LatentsField",
"fieldKind": "output" "fieldKind": "output"
}, },
"width": { "width": {
"id": "93dc8287-0a2a-4320-83a4-5e994b7ba23e", "id": "98ee0e6c-82aa-4e8f-8be5-dc5f00ee47f0",
"name": "width", "name": "width",
"type": "integer", "type": "integer",
"fieldKind": "output" "fieldKind": "output"
}, },
"height": { "height": {
"id": "d9862f5c-0ab5-46fa-8c29-5059bb581d96", "id": "e8cb184a-5e1a-47c8-9695-4b8979564f5d",
"name": "height", "name": "height",
"type": "integer", "type": "integer",
"fieldKind": "output" "fieldKind": "output"
@@ -486,13 +428,95 @@
"isOpen": true, "isOpen": true,
"notes": "", "notes": "",
"embedWorkflow": false, "embedWorkflow": false,
"isIntermediate": true "isIntermediate": true,
"useCache": true,
"version": "1.4.0"
}, },
"width": 320, "width": 320,
"height": 558, "height": 646,
"position": { "position": {
"x": 1400, "x": 1476.5794704734735,
"y": 200 "y": 256.80174342731783
}
},
{
"id": "58c957f5-0d01-41fc-a803-b2bbf0413d4f",
"type": "invocation",
"data": {
"id": "58c957f5-0d01-41fc-a803-b2bbf0413d4f",
"type": "l2i",
"inputs": {
"metadata": {
"id": "ab375f12-0042-4410-9182-29e30db82c85",
"name": "metadata",
"type": "MetadataField",
"fieldKind": "input",
"label": ""
},
"latents": {
"id": "3a7e7efd-bff5-47d7-9d48-615127afee78",
"name": "latents",
"type": "LatentsField",
"fieldKind": "input",
"label": ""
},
"vae": {
"id": "a1f5f7a1-0795-4d58-b036-7820c0b0ef2b",
"name": "vae",
"type": "VaeField",
"fieldKind": "input",
"label": ""
},
"tiled": {
"id": "da52059a-0cee-4668-942f-519aa794d739",
"name": "tiled",
"type": "boolean",
"fieldKind": "input",
"label": "",
"value": false
},
"fp32": {
"id": "c4841df3-b24e-4140-be3b-ccd454c2522c",
"name": "fp32",
"type": "boolean",
"fieldKind": "input",
"label": "",
"value": false
}
},
"outputs": {
"image": {
"id": "72d667d0-cf85-459d-abf2-28bd8b823fe7",
"name": "image",
"type": "ImageField",
"fieldKind": "output"
},
"width": {
"id": "c8c907d8-1066-49d1-b9a6-83bdcd53addc",
"name": "width",
"type": "integer",
"fieldKind": "output"
},
"height": {
"id": "230f359c-b4ea-436c-b372-332d7dcdca85",
"name": "height",
"type": "integer",
"fieldKind": "output"
}
},
"label": "",
"isOpen": true,
"notes": "",
"embedWorkflow": false,
"isIntermediate": false,
"useCache": true,
"version": "1.0.0"
},
"width": 320,
"height": 267,
"position": {
"x": 2037.9648469717395,
"y": 426.10844427600136
} }
} }
], ],
@@ -522,52 +546,52 @@
"type": "default" "type": "default"
}, },
{ {
"source": "c8d55139-f380-4695-b7f2-8b3d1e1e3db8", "source": "55705012-79b9-4aac-9f26-c0b10309785b",
"sourceHandle": "vae", "sourceHandle": "noise",
"target": "dbcd2f98-d809-48c8-bf64-2635f88a2fe9", "target": "eea2702a-19fb-45b5-9d75-56b4211ec03c",
"targetHandle": "vae", "targetHandle": "noise",
"id": "reactflow__edge-c8d55139-f380-4695-b7f2-8b3d1e1e3db8vae-dbcd2f98-d809-48c8-bf64-2635f88a2fe9vae", "id": "reactflow__edge-55705012-79b9-4aac-9f26-c0b10309785bnoise-eea2702a-19fb-45b5-9d75-56b4211ec03cnoise",
"type": "default"
},
{
"source": "75899702-fa44-46d2-b2d5-3e17f234c3e7",
"sourceHandle": "latents",
"target": "dbcd2f98-d809-48c8-bf64-2635f88a2fe9",
"targetHandle": "latents",
"id": "reactflow__edge-75899702-fa44-46d2-b2d5-3e17f234c3e7latents-dbcd2f98-d809-48c8-bf64-2635f88a2fe9latents",
"type": "default" "type": "default"
}, },
{ {
"source": "7d8bf987-284f-413a-b2fd-d825445a5d6c", "source": "7d8bf987-284f-413a-b2fd-d825445a5d6c",
"sourceHandle": "conditioning", "sourceHandle": "conditioning",
"target": "75899702-fa44-46d2-b2d5-3e17f234c3e7", "target": "eea2702a-19fb-45b5-9d75-56b4211ec03c",
"targetHandle": "positive_conditioning", "targetHandle": "positive_conditioning",
"id": "reactflow__edge-7d8bf987-284f-413a-b2fd-d825445a5d6cconditioning-75899702-fa44-46d2-b2d5-3e17f234c3e7positive_conditioning", "id": "reactflow__edge-7d8bf987-284f-413a-b2fd-d825445a5d6cconditioning-eea2702a-19fb-45b5-9d75-56b4211ec03cpositive_conditioning",
"type": "default" "type": "default"
}, },
{ {
"source": "93dc02a4-d05b-48ed-b99c-c9b616af3402", "source": "93dc02a4-d05b-48ed-b99c-c9b616af3402",
"sourceHandle": "conditioning", "sourceHandle": "conditioning",
"target": "75899702-fa44-46d2-b2d5-3e17f234c3e7", "target": "eea2702a-19fb-45b5-9d75-56b4211ec03c",
"targetHandle": "negative_conditioning", "targetHandle": "negative_conditioning",
"id": "reactflow__edge-93dc02a4-d05b-48ed-b99c-c9b616af3402conditioning-75899702-fa44-46d2-b2d5-3e17f234c3e7negative_conditioning", "id": "reactflow__edge-93dc02a4-d05b-48ed-b99c-c9b616af3402conditioning-eea2702a-19fb-45b5-9d75-56b4211ec03cnegative_conditioning",
"type": "default" "type": "default"
}, },
{ {
"source": "c8d55139-f380-4695-b7f2-8b3d1e1e3db8", "source": "c8d55139-f380-4695-b7f2-8b3d1e1e3db8",
"sourceHandle": "unet", "sourceHandle": "unet",
"target": "75899702-fa44-46d2-b2d5-3e17f234c3e7", "target": "eea2702a-19fb-45b5-9d75-56b4211ec03c",
"targetHandle": "unet", "targetHandle": "unet",
"id": "reactflow__edge-c8d55139-f380-4695-b7f2-8b3d1e1e3db8unet-75899702-fa44-46d2-b2d5-3e17f234c3e7unet", "id": "reactflow__edge-c8d55139-f380-4695-b7f2-8b3d1e1e3db8unet-eea2702a-19fb-45b5-9d75-56b4211ec03cunet",
"type": "default" "type": "default"
}, },
{ {
"source": "55705012-79b9-4aac-9f26-c0b10309785b", "source": "eea2702a-19fb-45b5-9d75-56b4211ec03c",
"sourceHandle": "noise", "sourceHandle": "latents",
"target": "75899702-fa44-46d2-b2d5-3e17f234c3e7", "target": "58c957f5-0d01-41fc-a803-b2bbf0413d4f",
"targetHandle": "noise", "targetHandle": "latents",
"id": "reactflow__edge-55705012-79b9-4aac-9f26-c0b10309785bnoise-75899702-fa44-46d2-b2d5-3e17f234c3e7noise", "id": "reactflow__edge-eea2702a-19fb-45b5-9d75-56b4211ec03clatents-58c957f5-0d01-41fc-a803-b2bbf0413d4flatents",
"type": "default"
},
{
"source": "c8d55139-f380-4695-b7f2-8b3d1e1e3db8",
"sourceHandle": "vae",
"target": "58c957f5-0d01-41fc-a803-b2bbf0413d4f",
"targetHandle": "vae",
"id": "reactflow__edge-c8d55139-f380-4695-b7f2-8b3d1e1e3db8vae-58c957f5-0d01-41fc-a803-b2bbf0413d4fvae",
"type": "default" "type": "default"
} }
] ]
} }

View File

@@ -1,7 +1,7 @@
@echo off @echo off
setlocal EnableExtensions EnableDelayedExpansion setlocal EnableExtensions EnableDelayedExpansion
@rem This script requires the user to install Python 3.9 or higher. All other @rem This script requires the user to install Python 3.10 or higher. All other
@rem requirements are downloaded as needed. @rem requirements are downloaded as needed.
@rem change to the script's directory @rem change to the script's directory
@@ -19,7 +19,7 @@ set INVOKEAI_VERSION=latest
set INSTRUCTIONS=https://invoke-ai.github.io/InvokeAI/installation/INSTALL_AUTOMATED/ set INSTRUCTIONS=https://invoke-ai.github.io/InvokeAI/installation/INSTALL_AUTOMATED/
set TROUBLESHOOTING=https://invoke-ai.github.io/InvokeAI/installation/INSTALL_AUTOMATED/#troubleshooting set TROUBLESHOOTING=https://invoke-ai.github.io/InvokeAI/installation/INSTALL_AUTOMATED/#troubleshooting
set PYTHON_URL=https://www.python.org/downloads/windows/ set PYTHON_URL=https://www.python.org/downloads/windows/
set MINIMUM_PYTHON_VERSION=3.9.0 set MINIMUM_PYTHON_VERSION=3.10.0
set PYTHON_URL=https://www.python.org/downloads/release/python-3109/ set PYTHON_URL=https://www.python.org/downloads/release/python-3109/
set err_msg=An error has occurred and the script could not continue. set err_msg=An error has occurred and the script could not continue.
@@ -28,8 +28,7 @@ set err_msg=An error has occurred and the script could not continue.
echo This script will install InvokeAI and its dependencies. echo This script will install InvokeAI and its dependencies.
echo. echo.
echo BEFORE YOU START PLEASE MAKE SURE TO DO THE FOLLOWING echo BEFORE YOU START PLEASE MAKE SURE TO DO THE FOLLOWING
echo 1. Install python 3.9 or 3.10. Python version 3.11 and above are echo 1. Install python 3.10 or 3.11. Python version 3.9 is no longer supported.
echo not supported at the moment.
echo 2. Double-click on the file WinLongPathsEnabled.reg in order to echo 2. Double-click on the file WinLongPathsEnabled.reg in order to
echo enable long path support on your system. echo enable long path support on your system.
echo 3. Install the Visual C++ core libraries. echo 3. Install the Visual C++ core libraries.
@@ -46,19 +45,19 @@ echo ***** Checking and Updating Python *****
call python --version >.tmp1 2>.tmp2 call python --version >.tmp1 2>.tmp2
if %errorlevel% == 1 ( if %errorlevel% == 1 (
set err_msg=Please install Python 3.10. See %INSTRUCTIONS% for details. set err_msg=Please install Python 3.10-11. See %INSTRUCTIONS% for details.
goto err_exit goto err_exit
) )
for /f "tokens=2" %%i in (.tmp1) do set python_version=%%i for /f "tokens=2" %%i in (.tmp1) do set python_version=%%i
if "%python_version%" == "" ( if "%python_version%" == "" (
set err_msg=No python was detected on your system. Please install Python version %MINIMUM_PYTHON_VERSION% or higher. We recommend Python 3.10.9 from %PYTHON_URL% set err_msg=No python was detected on your system. Please install Python version %MINIMUM_PYTHON_VERSION% or higher. We recommend Python 3.10.12 from %PYTHON_URL%
goto err_exit goto err_exit
) )
call :compareVersions %MINIMUM_PYTHON_VERSION% %python_version% call :compareVersions %MINIMUM_PYTHON_VERSION% %python_version%
if %errorlevel% == 1 ( if %errorlevel% == 1 (
set err_msg=Your version of Python is too low. You need at least %MINIMUM_PYTHON_VERSION% but you have %python_version%. We recommend Python 3.10.9 from %PYTHON_URL% set err_msg=Your version of Python is too low. You need at least %MINIMUM_PYTHON_VERSION% but you have %python_version%. We recommend Python 3.10.12 from %PYTHON_URL%
goto err_exit goto err_exit
) )

View File

@@ -8,10 +8,10 @@ cd $scriptdir
function version { echo "$@" | awk -F. '{ printf("%d%03d%03d%03d\n", $1,$2,$3,$4); }'; } function version { echo "$@" | awk -F. '{ printf("%d%03d%03d%03d\n", $1,$2,$3,$4); }'; }
MINIMUM_PYTHON_VERSION=3.9.0 MINIMUM_PYTHON_VERSION=3.10.0
MAXIMUM_PYTHON_VERSION=3.11.100 MAXIMUM_PYTHON_VERSION=3.11.100
PYTHON="" PYTHON=""
for candidate in python3.11 python3.10 python3.9 python3 python ; do for candidate in python3.11 python3.10 python3 python ; do
if ppath=`which $candidate`; then if ppath=`which $candidate`; then
# when using `pyenv`, the executable for an inactive Python version will exist but will not be operational # when using `pyenv`, the executable for an inactive Python version will exist but will not be operational
# we check that this found executable can actually run # we check that this found executable can actually run

View File

@@ -13,7 +13,7 @@ from pathlib import Path
from tempfile import TemporaryDirectory from tempfile import TemporaryDirectory
from typing import Union from typing import Union
SUPPORTED_PYTHON = ">=3.9.0,<=3.11.100" SUPPORTED_PYTHON = ">=3.10.0,<=3.11.100"
INSTALLER_REQS = ["rich", "semver", "requests", "plumbum", "prompt-toolkit"] INSTALLER_REQS = ["rich", "semver", "requests", "plumbum", "prompt-toolkit"]
BOOTSTRAP_VENV_PREFIX = "invokeai-installer-tmp" BOOTSTRAP_VENV_PREFIX = "invokeai-installer-tmp"
@@ -67,7 +67,6 @@ class Installer:
# Cleaning up temporary directories on Windows results in a race condition # Cleaning up temporary directories on Windows results in a race condition
# and a stack trace. # and a stack trace.
# `ignore_cleanup_errors` was only added in Python 3.10 # `ignore_cleanup_errors` was only added in Python 3.10
# users of Python 3.9 will see a gnarly stack trace on installer exit
if OS == "Windows" and int(platform.python_version_tuple()[1]) >= 10: if OS == "Windows" and int(platform.python_version_tuple()[1]) >= 10:
venv_dir = TemporaryDirectory(prefix=BOOTSTRAP_VENV_PREFIX, ignore_cleanup_errors=True) venv_dir = TemporaryDirectory(prefix=BOOTSTRAP_VENV_PREFIX, ignore_cleanup_errors=True)
else: else:
@@ -139,13 +138,6 @@ class Installer:
except shutil.SameFileError: except shutil.SameFileError:
venv.create(venv_dir, with_pip=True, symlinks=True) venv.create(venv_dir, with_pip=True, symlinks=True)
# upgrade pip in Python 3.9 environments
if int(platform.python_version_tuple()[1]) == 9:
from plumbum import FG, local
pip = local[get_pip_from_venv(venv_dir)]
pip["install", "--upgrade", "pip"] & FG
return venv_dir return venv_dir
def install( def install(
@@ -252,7 +244,7 @@ class InvokeAiInstance:
"numpy~=1.24.0", # choose versions that won't be uninstalled during phase 2 "numpy~=1.24.0", # choose versions that won't be uninstalled during phase 2
"urllib3~=1.26.0", "urllib3~=1.26.0",
"requests~=2.28.0", "requests~=2.28.0",
"torch~=2.0.0", "torch~=2.1.0",
"torchmetrics==0.11.4", "torchmetrics==0.11.4",
"torchvision>=0.14.1", "torchvision>=0.14.1",
"--force-reinstall", "--force-reinstall",
@@ -468,10 +460,10 @@ def get_torch_source() -> (Union[str, None], str):
url = "https://download.pytorch.org/whl/cpu" url = "https://download.pytorch.org/whl/cpu"
if device == "cuda": if device == "cuda":
url = "https://download.pytorch.org/whl/cu118" url = "https://download.pytorch.org/whl/cu121"
optional_modules = "[xformers,onnx-cuda]" optional_modules = "[xformers,onnx-cuda]"
if device == "cuda_and_dml": if device == "cuda_and_dml":
url = "https://download.pytorch.org/whl/cu118" url = "https://download.pytorch.org/whl/cu121"
optional_modules = "[xformers,onnx-directml]" optional_modules = "[xformers,onnx-directml]"
# in all other cases, Torch wheels should be coming from PyPi as of Torch 1.13 # in all other cases, Torch wheels should be coming from PyPi as of Torch 1.13

View File

@@ -137,7 +137,7 @@ def dest_path(dest=None) -> Path:
path_completer = PathCompleter( path_completer = PathCompleter(
only_directories=True, only_directories=True,
expanduser=True, expanduser=True,
get_paths=lambda: [browse_start], get_paths=lambda: [browse_start], # noqa: B023
# get_paths=lambda: [".."].extend(list(browse_start.iterdir())) # get_paths=lambda: [".."].extend(list(browse_start.iterdir()))
) )
@@ -149,7 +149,7 @@ def dest_path(dest=None) -> Path:
completer=path_completer, completer=path_completer,
default=str(browse_start) + os.sep, default=str(browse_start) + os.sep,
vi_mode=True, vi_mode=True,
complete_while_typing=True complete_while_typing=True,
# Test that this is not needed on Windows # Test that this is not needed on Windows
# complete_style=CompleteStyle.READLINE_LIKE, # complete_style=CompleteStyle.READLINE_LIKE,
) )

View File

@@ -4,7 +4,7 @@ Project homepage: https://github.com/invoke-ai/InvokeAI
Preparations: Preparations:
You will need to install Python 3.9 or higher for this installer You will need to install Python 3.10 or higher for this installer
to work. Instructions are given here: to work. Instructions are given here:
https://invoke-ai.github.io/InvokeAI/installation/INSTALL_AUTOMATED/ https://invoke-ai.github.io/InvokeAI/installation/INSTALL_AUTOMATED/
@@ -14,15 +14,15 @@ Preparations:
python --version python --version
If all is well, it will print "Python 3.X.X", where the version number If all is well, it will print "Python 3.X.X", where the version number
is at least 3.9.*, and not higher than 3.11.*. is at least 3.10.*, and not higher than 3.11.*.
If this works, check the version of the Python package manager, pip: If this works, check the version of the Python package manager, pip:
pip --version pip --version
You should get a message that indicates that the pip package You should get a message that indicates that the pip package
installer was derived from Python 3.9 or 3.10. For example: installer was derived from Python 3.10 or 3.11. For example:
"pip 22.3.1 from /usr/bin/pip (python 3.9)" "pip 22.0.1 from /usr/bin/pip (python 3.10)"
Long Paths on Windows: Long Paths on Windows:

View File

@@ -9,41 +9,37 @@ set INVOKEAI_ROOT=.
:start :start
echo Desired action: echo Desired action:
echo 1. Generate images with the browser-based interface echo 1. Generate images with the browser-based interface
echo 2. Explore InvokeAI nodes using a command-line interface echo 2. Run textual inversion training
echo 3. Run textual inversion training echo 3. Merge models (diffusers type only)
echo 4. Merge models (diffusers type only) echo 4. Download and install models
echo 5. Download and install models echo 5. Change InvokeAI startup options
echo 6. Change InvokeAI startup options echo 6. Re-run the configure script to fix a broken install or to complete a major upgrade
echo 7. Re-run the configure script to fix a broken install or to complete a major upgrade echo 7. Open the developer console
echo 8. Open the developer console echo 8. Update InvokeAI
echo 9. Update InvokeAI echo 9. Run the InvokeAI image database maintenance script
echo 10. Run the InvokeAI image database maintenance script echo 10. Command-line help
echo 11. Command-line help
echo Q - Quit echo Q - Quit
set /P choice="Please enter 1-11, Q: [1] " set /P choice="Please enter 1-10, Q: [1] "
if not defined choice set choice=1 if not defined choice set choice=1
IF /I "%choice%" == "1" ( IF /I "%choice%" == "1" (
echo Starting the InvokeAI browser-based UI.. echo Starting the InvokeAI browser-based UI..
python .venv\Scripts\invokeai-web.exe %* python .venv\Scripts\invokeai-web.exe %*
) ELSE IF /I "%choice%" == "2" ( ) ELSE IF /I "%choice%" == "2" (
echo Starting the InvokeAI command-line..
python .venv\Scripts\invokeai.exe %*
) ELSE IF /I "%choice%" == "3" (
echo Starting textual inversion training.. echo Starting textual inversion training..
python .venv\Scripts\invokeai-ti.exe --gui python .venv\Scripts\invokeai-ti.exe --gui
) ELSE IF /I "%choice%" == "4" ( ) ELSE IF /I "%choice%" == "3" (
echo Starting model merging script.. echo Starting model merging script..
python .venv\Scripts\invokeai-merge.exe --gui python .venv\Scripts\invokeai-merge.exe --gui
) ELSE IF /I "%choice%" == "5" ( ) ELSE IF /I "%choice%" == "4" (
echo Running invokeai-model-install... echo Running invokeai-model-install...
python .venv\Scripts\invokeai-model-install.exe python .venv\Scripts\invokeai-model-install.exe
) ELSE IF /I "%choice%" == "6" ( ) ELSE IF /I "%choice%" == "5" (
echo Running invokeai-configure... echo Running invokeai-configure...
python .venv\Scripts\invokeai-configure.exe --skip-sd-weight --skip-support-models python .venv\Scripts\invokeai-configure.exe --skip-sd-weight --skip-support-models
) ELSE IF /I "%choice%" == "7" ( ) ELSE IF /I "%choice%" == "6" (
echo Running invokeai-configure... echo Running invokeai-configure...
python .venv\Scripts\invokeai-configure.exe --yes --skip-sd-weight python .venv\Scripts\invokeai-configure.exe --yes --skip-sd-weight
) ELSE IF /I "%choice%" == "8" ( ) ELSE IF /I "%choice%" == "7" (
echo Developer Console echo Developer Console
echo Python command is: echo Python command is:
where python where python
@@ -55,13 +51,13 @@ IF /I "%choice%" == "1" (
echo ************************* echo *************************
echo *** Type `exit` to quit this shell and deactivate the Python virtual environment *** echo *** Type `exit` to quit this shell and deactivate the Python virtual environment ***
call cmd /k call cmd /k
) ELSE IF /I "%choice%" == "9" ( ) ELSE IF /I "%choice%" == "8" (
echo Running invokeai-update... echo Running invokeai-update...
python -m invokeai.frontend.install.invokeai_update python -m invokeai.frontend.install.invokeai_update
) ELSE IF /I "%choice%" == "10" ( ) ELSE IF /I "%choice%" == "9" (
echo Running the db maintenance script... echo Running the db maintenance script...
python .venv\Scripts\invokeai-db-maintenance.exe python .venv\Scripts\invokeai-db-maintenance.exe
) ELSE IF /I "%choice%" == "11" ( ) ELSE IF /I "%choice%" == "10" (
echo Displaying command line help... echo Displaying command line help...
python .venv\Scripts\invokeai-web.exe --help %* python .venv\Scripts\invokeai-web.exe --help %*
pause pause

View File

@@ -58,52 +58,47 @@ do_choice() {
invokeai-web $PARAMS invokeai-web $PARAMS
;; ;;
2) 2)
clear
printf "Explore InvokeAI nodes using a command-line interface\n"
invokeai $PARAMS
;;
3)
clear clear
printf "Textual inversion training\n" printf "Textual inversion training\n"
invokeai-ti --gui $PARAMS invokeai-ti --gui $PARAMS
;; ;;
4) 3)
clear clear
printf "Merge models (diffusers type only)\n" printf "Merge models (diffusers type only)\n"
invokeai-merge --gui $PARAMS invokeai-merge --gui $PARAMS
;; ;;
5) 4)
clear clear
printf "Download and install models\n" printf "Download and install models\n"
invokeai-model-install --root ${INVOKEAI_ROOT} invokeai-model-install --root ${INVOKEAI_ROOT}
;; ;;
6) 5)
clear clear
printf "Change InvokeAI startup options\n" printf "Change InvokeAI startup options\n"
invokeai-configure --root ${INVOKEAI_ROOT} --skip-sd-weights --skip-support-models invokeai-configure --root ${INVOKEAI_ROOT} --skip-sd-weights --skip-support-models
;; ;;
7) 6)
clear clear
printf "Re-run the configure script to fix a broken install or to complete a major upgrade\n" printf "Re-run the configure script to fix a broken install or to complete a major upgrade\n"
invokeai-configure --root ${INVOKEAI_ROOT} --yes --default_only --skip-sd-weights invokeai-configure --root ${INVOKEAI_ROOT} --yes --default_only --skip-sd-weights
;; ;;
8) 7)
clear clear
printf "Open the developer console\n" printf "Open the developer console\n"
file_name=$(basename "${BASH_SOURCE[0]}") file_name=$(basename "${BASH_SOURCE[0]}")
bash --init-file "$file_name" bash --init-file "$file_name"
;; ;;
9) 8)
clear clear
printf "Update InvokeAI\n" printf "Update InvokeAI\n"
python -m invokeai.frontend.install.invokeai_update python -m invokeai.frontend.install.invokeai_update
;; ;;
10) 9)
clear clear
printf "Running the db maintenance script\n" printf "Running the db maintenance script\n"
invokeai-db-maintenance --root ${INVOKEAI_ROOT} invokeai-db-maintenance --root ${INVOKEAI_ROOT}
;; ;;
11) 10)
clear clear
printf "Command-line help\n" printf "Command-line help\n"
invokeai-web --help invokeai-web --help
@@ -121,16 +116,15 @@ do_choice() {
do_dialog() { do_dialog() {
options=( options=(
1 "Generate images with a browser-based interface" 1 "Generate images with a browser-based interface"
2 "Explore InvokeAI nodes using a command-line interface" 2 "Textual inversion training"
3 "Textual inversion training" 3 "Merge models (diffusers type only)"
4 "Merge models (diffusers type only)" 4 "Download and install models"
5 "Download and install models" 5 "Change InvokeAI startup options"
6 "Change InvokeAI startup options" 6 "Re-run the configure script to fix a broken install or to complete a major upgrade"
7 "Re-run the configure script to fix a broken install or to complete a major upgrade" 7 "Open the developer console"
8 "Open the developer console" 8 "Update InvokeAI"
9 "Update InvokeAI" 9 "Run the InvokeAI image database maintenance script"
10 "Run the InvokeAI image database maintenance script" 10 "Command-line help"
11 "Command-line help"
) )
choice=$(dialog --clear \ choice=$(dialog --clear \
@@ -155,18 +149,17 @@ do_line_input() {
printf " ** For a more attractive experience, please install the 'dialog' utility using your package manager. **\n\n" printf " ** For a more attractive experience, please install the 'dialog' utility using your package manager. **\n\n"
printf "What would you like to do?\n" printf "What would you like to do?\n"
printf "1: Generate images using the browser-based interface\n" printf "1: Generate images using the browser-based interface\n"
printf "2: Explore InvokeAI nodes using the command-line interface\n" printf "2: Run textual inversion training\n"
printf "3: Run textual inversion training\n" printf "3: Merge models (diffusers type only)\n"
printf "4: Merge models (diffusers type only)\n" printf "4: Download and install models\n"
printf "5: Download and install models\n" printf "5: Change InvokeAI startup options\n"
printf "6: Change InvokeAI startup options\n" printf "6: Re-run the configure script to fix a broken install\n"
printf "7: Re-run the configure script to fix a broken install\n" printf "7: Open the developer console\n"
printf "8: Open the developer console\n" printf "8: Update InvokeAI\n"
printf "9: Update InvokeAI\n" printf "9: Run the InvokeAI image database maintenance script\n"
printf "10: Run the InvokeAI image database maintenance script\n" printf "10: Command-line help\n"
printf "11: Command-line help\n"
printf "Q: Quit\n\n" printf "Q: Quit\n\n"
read -p "Please enter 1-11, Q: [1] " yn read -p "Please enter 1-10, Q: [1] " yn
choice=${yn:='1'} choice=${yn:='1'}
do_choice $choice do_choice $choice
clear clear

View File

@@ -2,6 +2,7 @@
from logging import Logger from logging import Logger
from invokeai.app.services.workflow_image_records.workflow_image_records_sqlite import SqliteWorkflowImageRecordsStorage
from invokeai.backend.util.logging import InvokeAILogger from invokeai.backend.util.logging import InvokeAILogger
from invokeai.version.invokeai_version import __version__ from invokeai.version.invokeai_version import __version__
@@ -23,6 +24,7 @@ from ..services.item_storage.item_storage_sqlite import SqliteItemStorage
from ..services.latents_storage.latents_storage_disk import DiskLatentsStorage from ..services.latents_storage.latents_storage_disk import DiskLatentsStorage
from ..services.latents_storage.latents_storage_forward_cache import ForwardCacheLatentsStorage from ..services.latents_storage.latents_storage_forward_cache import ForwardCacheLatentsStorage
from ..services.model_manager.model_manager_default import ModelManagerService from ..services.model_manager.model_manager_default import ModelManagerService
from ..services.model_records import ModelRecordServiceSQL
from ..services.names.names_default import SimpleNameService from ..services.names.names_default import SimpleNameService
from ..services.session_processor.session_processor_default import DefaultSessionProcessor from ..services.session_processor.session_processor_default import DefaultSessionProcessor
from ..services.session_queue.session_queue_sqlite import SqliteSessionQueue from ..services.session_queue.session_queue_sqlite import SqliteSessionQueue
@@ -30,6 +32,7 @@ from ..services.shared.default_graphs import create_system_graphs
from ..services.shared.graph import GraphExecutionState, LibraryGraph from ..services.shared.graph import GraphExecutionState, LibraryGraph
from ..services.shared.sqlite import SqliteDatabase from ..services.shared.sqlite import SqliteDatabase
from ..services.urls.urls_default import LocalUrlService from ..services.urls.urls_default import LocalUrlService
from ..services.workflow_records.workflow_records_sqlite import SqliteWorkflowRecordsStorage
from .events import FastAPIEventService from .events import FastAPIEventService
@@ -83,6 +86,7 @@ class ApiDependencies:
invocation_cache = MemoryInvocationCache(max_cache_size=config.node_cache_size) invocation_cache = MemoryInvocationCache(max_cache_size=config.node_cache_size)
latents = ForwardCacheLatentsStorage(DiskLatentsStorage(f"{output_folder}/latents")) latents = ForwardCacheLatentsStorage(DiskLatentsStorage(f"{output_folder}/latents"))
model_manager = ModelManagerService(config, logger) model_manager = ModelManagerService(config, logger)
model_record_service = ModelRecordServiceSQL(db=db)
names = SimpleNameService() names = SimpleNameService()
performance_statistics = InvocationStatsService() performance_statistics = InvocationStatsService()
processor = DefaultInvocationProcessor() processor = DefaultInvocationProcessor()
@@ -90,6 +94,8 @@ class ApiDependencies:
session_processor = DefaultSessionProcessor() session_processor = DefaultSessionProcessor()
session_queue = SqliteSessionQueue(db=db) session_queue = SqliteSessionQueue(db=db)
urls = LocalUrlService() urls = LocalUrlService()
workflow_image_records = SqliteWorkflowImageRecordsStorage(db=db)
workflow_records = SqliteWorkflowRecordsStorage(db=db)
services = InvocationServices( services = InvocationServices(
board_image_records=board_image_records, board_image_records=board_image_records,
@@ -107,6 +113,7 @@ class ApiDependencies:
latents=latents, latents=latents,
logger=logger, logger=logger,
model_manager=model_manager, model_manager=model_manager,
model_records=model_record_service,
names=names, names=names,
performance_statistics=performance_statistics, performance_statistics=performance_statistics,
processor=processor, processor=processor,
@@ -114,6 +121,8 @@ class ApiDependencies:
session_processor=session_processor, session_processor=session_processor,
session_queue=session_queue, session_queue=session_queue,
urls=urls, urls=urls,
workflow_image_records=workflow_image_records,
workflow_records=workflow_records,
) )
create_system_graphs(services.graph_library) create_system_graphs(services.graph_library)

View File

@@ -28,7 +28,7 @@ class FastAPIEventService(EventServiceBase):
self.__queue.put(None) self.__queue.put(None)
def dispatch(self, event_name: str, payload: Any) -> None: def dispatch(self, event_name: str, payload: Any) -> None:
self.__queue.put(dict(event_name=event_name, payload=payload)) self.__queue.put({"event_name": event_name, "payload": payload})
async def __dispatch_from_queue(self, stop_event: threading.Event): async def __dispatch_from_queue(self, stop_event: threading.Event):
"""Get events on from the queue and dispatch them, from the correct thread""" """Get events on from the queue and dispatch them, from the correct thread"""

View File

@@ -1,13 +1,14 @@
import io import io
import traceback
from typing import Optional from typing import Optional
from fastapi import Body, HTTPException, Path, Query, Request, Response, UploadFile from fastapi import Body, HTTPException, Path, Query, Request, Response, UploadFile
from fastapi.responses import FileResponse from fastapi.responses import FileResponse
from fastapi.routing import APIRouter from fastapi.routing import APIRouter
from PIL import Image from PIL import Image
from pydantic import BaseModel, Field from pydantic import BaseModel, Field, ValidationError
from invokeai.app.invocations.metadata import ImageMetadata from invokeai.app.invocations.baseinvocation import MetadataField, MetadataFieldValidator, WorkflowFieldValidator
from invokeai.app.services.image_records.image_records_common import ImageCategory, ImageRecordChanges, ResourceOrigin from invokeai.app.services.image_records.image_records_common import ImageCategory, ImageRecordChanges, ResourceOrigin
from invokeai.app.services.images.images_common import ImageDTO, ImageUrlsDTO from invokeai.app.services.images.images_common import ImageDTO, ImageUrlsDTO
from invokeai.app.services.shared.pagination import OffsetPaginatedResults from invokeai.app.services.shared.pagination import OffsetPaginatedResults
@@ -45,17 +46,38 @@ async def upload_image(
if not file.content_type or not file.content_type.startswith("image"): if not file.content_type or not file.content_type.startswith("image"):
raise HTTPException(status_code=415, detail="Not an image") raise HTTPException(status_code=415, detail="Not an image")
contents = await file.read() metadata = None
workflow = None
contents = await file.read()
try: try:
pil_image = Image.open(io.BytesIO(contents)) pil_image = Image.open(io.BytesIO(contents))
if crop_visible: if crop_visible:
bbox = pil_image.getbbox() bbox = pil_image.getbbox()
pil_image = pil_image.crop(bbox) pil_image = pil_image.crop(bbox)
except Exception: except Exception:
# Error opening the image ApiDependencies.invoker.services.logger.error(traceback.format_exc())
raise HTTPException(status_code=415, detail="Failed to read image") raise HTTPException(status_code=415, detail="Failed to read image")
# TODO: retain non-invokeai metadata on upload?
# attempt to parse metadata from image
metadata_raw = pil_image.info.get("invokeai_metadata", None)
if metadata_raw:
try:
metadata = MetadataFieldValidator.validate_json(metadata_raw)
except ValidationError:
ApiDependencies.invoker.services.logger.warn("Failed to parse metadata for uploaded image")
pass
# attempt to parse workflow from image
workflow_raw = pil_image.info.get("invokeai_workflow", None)
if workflow_raw is not None:
try:
workflow = WorkflowFieldValidator.validate_json(workflow_raw)
except ValidationError:
ApiDependencies.invoker.services.logger.warn("Failed to parse metadata for uploaded image")
pass
try: try:
image_dto = ApiDependencies.invoker.services.images.create( image_dto = ApiDependencies.invoker.services.images.create(
image=pil_image, image=pil_image,
@@ -63,6 +85,8 @@ async def upload_image(
image_category=image_category, image_category=image_category,
session_id=session_id, session_id=session_id,
board_id=board_id, board_id=board_id,
metadata=metadata,
workflow=workflow,
is_intermediate=is_intermediate, is_intermediate=is_intermediate,
) )
@@ -71,6 +95,7 @@ async def upload_image(
return image_dto return image_dto
except Exception: except Exception:
ApiDependencies.invoker.services.logger.error(traceback.format_exc())
raise HTTPException(status_code=500, detail="Failed to create image") raise HTTPException(status_code=500, detail="Failed to create image")
@@ -87,7 +112,7 @@ async def delete_image(
pass pass
@images_router.post("/clear-intermediates", operation_id="clear_intermediates") @images_router.delete("/intermediates", operation_id="clear_intermediates")
async def clear_intermediates() -> int: async def clear_intermediates() -> int:
"""Clears all intermediates""" """Clears all intermediates"""
@@ -99,6 +124,17 @@ async def clear_intermediates() -> int:
pass pass
@images_router.get("/intermediates", operation_id="get_intermediates_count")
async def get_intermediates_count() -> int:
"""Gets the count of intermediate images"""
try:
return ApiDependencies.invoker.services.images.get_intermediates_count()
except Exception:
raise HTTPException(status_code=500, detail="Failed to get intermediates")
pass
@images_router.patch( @images_router.patch(
"/i/{image_name}", "/i/{image_name}",
operation_id="update_image", operation_id="update_image",
@@ -135,11 +171,11 @@ async def get_image_dto(
@images_router.get( @images_router.get(
"/i/{image_name}/metadata", "/i/{image_name}/metadata",
operation_id="get_image_metadata", operation_id="get_image_metadata",
response_model=ImageMetadata, response_model=Optional[MetadataField],
) )
async def get_image_metadata( async def get_image_metadata(
image_name: str = Path(description="The name of image to get"), image_name: str = Path(description="The name of image to get"),
) -> ImageMetadata: ) -> Optional[MetadataField]:
"""Gets an image's metadata""" """Gets an image's metadata"""
try: try:
@@ -330,7 +366,7 @@ class ImagesDownloaded(BaseModel):
) )
@images_router.post("/download", operation_id="download_images_from_list", response_model=ImagesDownloaded) @images_router.post("/export", operation_id="download_images_from_list", response_model=ImagesDownloaded)
async def download_images_from_list( async def download_images_from_list(
image_names: list[str] = Body(description="The list of names of images to download", embed=True), image_names: list[str] = Body(description="The list of names of images to download", embed=True),
board_id: Optional[str] = Body( board_id: Optional[str] = Body(

View File

@@ -0,0 +1,164 @@
# Copyright (c) 2023 Lincoln D. Stein
"""FastAPI route for model configuration records."""
from hashlib import sha1
from random import randbytes
from typing import List, Optional
from fastapi import Body, Path, Query, Response
from fastapi.routing import APIRouter
from pydantic import BaseModel, ConfigDict
from starlette.exceptions import HTTPException
from typing_extensions import Annotated
from invokeai.app.services.model_records import (
DuplicateModelException,
InvalidModelException,
UnknownModelException,
)
from invokeai.backend.model_manager.config import (
AnyModelConfig,
BaseModelType,
ModelType,
)
from ..dependencies import ApiDependencies
model_records_router = APIRouter(prefix="/v1/model/record", tags=["models"])
class ModelsList(BaseModel):
"""Return list of configs."""
models: list[AnyModelConfig]
model_config = ConfigDict(use_enum_values=True)
@model_records_router.get(
"/",
operation_id="list_model_records",
)
async def list_model_records(
base_models: Optional[List[BaseModelType]] = Query(default=None, description="Base models to include"),
model_type: Optional[ModelType] = Query(default=None, description="The type of model to get"),
) -> ModelsList:
"""Get a list of models."""
record_store = ApiDependencies.invoker.services.model_records
found_models: list[AnyModelConfig] = []
if base_models:
for base_model in base_models:
found_models.extend(record_store.search_by_attr(base_model=base_model, model_type=model_type))
else:
found_models.extend(record_store.search_by_attr(model_type=model_type))
return ModelsList(models=found_models)
@model_records_router.get(
"/i/{key}",
operation_id="get_model_record",
responses={
200: {"description": "Success"},
400: {"description": "Bad request"},
404: {"description": "The model could not be found"},
},
)
async def get_model_record(
key: str = Path(description="Key of the model record to fetch."),
) -> AnyModelConfig:
"""Get a model record"""
record_store = ApiDependencies.invoker.services.model_records
try:
return record_store.get_model(key)
except UnknownModelException as e:
raise HTTPException(status_code=404, detail=str(e))
@model_records_router.patch(
"/i/{key}",
operation_id="update_model_record",
responses={
200: {"description": "The model was updated successfully"},
400: {"description": "Bad request"},
404: {"description": "The model could not be found"},
409: {"description": "There is already a model corresponding to the new name"},
},
status_code=200,
response_model=AnyModelConfig,
)
async def update_model_record(
key: Annotated[str, Path(description="Unique key of model")],
info: Annotated[AnyModelConfig, Body(description="Model config", discriminator="type")],
) -> AnyModelConfig:
"""Update model contents with a new config. If the model name or base fields are changed, then the model is renamed."""
logger = ApiDependencies.invoker.services.logger
record_store = ApiDependencies.invoker.services.model_records
try:
model_response = record_store.update_model(key, config=info)
logger.info(f"Updated model: {key}")
except UnknownModelException as e:
raise HTTPException(status_code=404, detail=str(e))
except ValueError as e:
logger.error(str(e))
raise HTTPException(status_code=409, detail=str(e))
return model_response
@model_records_router.delete(
"/i/{key}",
operation_id="del_model_record",
responses={
204: {"description": "Model deleted successfully"},
404: {"description": "Model not found"},
},
status_code=204,
)
async def del_model_record(
key: str = Path(description="Unique key of model to remove from model registry."),
) -> Response:
"""Delete Model"""
logger = ApiDependencies.invoker.services.logger
try:
record_store = ApiDependencies.invoker.services.model_records
record_store.del_model(key)
logger.info(f"Deleted model: {key}")
return Response(status_code=204)
except UnknownModelException as e:
logger.error(str(e))
raise HTTPException(status_code=404, detail=str(e))
@model_records_router.post(
"/i/",
operation_id="add_model_record",
responses={
201: {"description": "The model added successfully"},
409: {"description": "There is already a model corresponding to this path or repo_id"},
415: {"description": "Unrecognized file/folder format"},
},
status_code=201,
)
async def add_model_record(
config: Annotated[AnyModelConfig, Body(description="Model config", discriminator="type")]
) -> AnyModelConfig:
"""
Add a model using the configuration information appropriate for its type.
"""
logger = ApiDependencies.invoker.services.logger
record_store = ApiDependencies.invoker.services.model_records
if config.key == "<NOKEY>":
config.key = sha1(randbytes(100)).hexdigest()
logger.info(f"Created model {config.key} for {config.name}")
try:
record_store.add_model(config.key, config)
except DuplicateModelException as e:
logger.error(str(e))
raise HTTPException(status_code=409, detail=str(e))
except InvalidModelException as e:
logger.error(str(e))
raise HTTPException(status_code=415)
# now fetch it out
return record_store.get_model(config.key)

View File

@@ -1,6 +1,5 @@
# Copyright (c) 2023 Kyle Schouviller (https://github.com/kyle0654), 2023 Kent Keirsey (https://github.com/hipsterusername), 2023 Lincoln D. Stein # Copyright (c) 2023 Kyle Schouviller (https://github.com/kyle0654), 2023 Kent Keirsey (https://github.com/hipsterusername), 2023 Lincoln D. Stein
import pathlib import pathlib
from typing import Annotated, List, Literal, Optional, Union from typing import Annotated, List, Literal, Optional, Union
@@ -23,13 +22,13 @@ from ..dependencies import ApiDependencies
models_router = APIRouter(prefix="/v1/models", tags=["models"]) models_router = APIRouter(prefix="/v1/models", tags=["models"])
UpdateModelResponse = Union[tuple(OPENAPI_MODEL_CONFIGS)] UpdateModelResponse = Union[tuple(OPENAPI_MODEL_CONFIGS)]
update_models_response_adapter = TypeAdapter(UpdateModelResponse) UpdateModelResponseValidator = TypeAdapter(UpdateModelResponse)
ImportModelResponse = Union[tuple(OPENAPI_MODEL_CONFIGS)] ImportModelResponse = Union[tuple(OPENAPI_MODEL_CONFIGS)]
import_models_response_adapter = TypeAdapter(ImportModelResponse) ImportModelResponseValidator = TypeAdapter(ImportModelResponse)
ConvertModelResponse = Union[tuple(OPENAPI_MODEL_CONFIGS)] ConvertModelResponse = Union[tuple(OPENAPI_MODEL_CONFIGS)]
convert_models_response_adapter = TypeAdapter(ConvertModelResponse) ConvertModelResponseValidator = TypeAdapter(ConvertModelResponse)
MergeModelResponse = Union[tuple(OPENAPI_MODEL_CONFIGS)] MergeModelResponse = Union[tuple(OPENAPI_MODEL_CONFIGS)]
ImportModelAttributes = Union[tuple(OPENAPI_MODEL_CONFIGS)] ImportModelAttributes = Union[tuple(OPENAPI_MODEL_CONFIGS)]
@@ -41,7 +40,7 @@ class ModelsList(BaseModel):
model_config = ConfigDict(use_enum_values=True) model_config = ConfigDict(use_enum_values=True)
models_list_adapter = TypeAdapter(ModelsList) ModelsListValidator = TypeAdapter(ModelsList)
@models_router.get( @models_router.get(
@@ -55,12 +54,12 @@ async def list_models(
) -> ModelsList: ) -> ModelsList:
"""Gets a list of models""" """Gets a list of models"""
if base_models and len(base_models) > 0: if base_models and len(base_models) > 0:
models_raw = list() models_raw = []
for base_model in base_models: for base_model in base_models:
models_raw.extend(ApiDependencies.invoker.services.model_manager.list_models(base_model, model_type)) models_raw.extend(ApiDependencies.invoker.services.model_manager.list_models(base_model, model_type))
else: else:
models_raw = ApiDependencies.invoker.services.model_manager.list_models(None, model_type) models_raw = ApiDependencies.invoker.services.model_manager.list_models(None, model_type)
models = models_list_adapter.validate_python({"models": models_raw}) models = ModelsListValidator.validate_python({"models": models_raw})
return models return models
@@ -131,7 +130,7 @@ async def update_model(
base_model=base_model, base_model=base_model,
model_type=model_type, model_type=model_type,
) )
model_response = update_models_response_adapter.validate_python(model_raw) model_response = UpdateModelResponseValidator.validate_python(model_raw)
except ModelNotFoundException as e: except ModelNotFoundException as e:
raise HTTPException(status_code=404, detail=str(e)) raise HTTPException(status_code=404, detail=str(e))
except ValueError as e: except ValueError as e:
@@ -186,7 +185,7 @@ async def import_model(
model_raw = ApiDependencies.invoker.services.model_manager.list_model( model_raw = ApiDependencies.invoker.services.model_manager.list_model(
model_name=info.name, base_model=info.base_model, model_type=info.model_type model_name=info.name, base_model=info.base_model, model_type=info.model_type
) )
return import_models_response_adapter.validate_python(model_raw) return ImportModelResponseValidator.validate_python(model_raw)
except ModelNotFoundException as e: except ModelNotFoundException as e:
logger.error(str(e)) logger.error(str(e))
@@ -231,7 +230,7 @@ async def add_model(
base_model=info.base_model, base_model=info.base_model,
model_type=info.model_type, model_type=info.model_type,
) )
return import_models_response_adapter.validate_python(model_raw) return ImportModelResponseValidator.validate_python(model_raw)
except ModelNotFoundException as e: except ModelNotFoundException as e:
logger.error(str(e)) logger.error(str(e))
raise HTTPException(status_code=404, detail=str(e)) raise HTTPException(status_code=404, detail=str(e))
@@ -302,7 +301,7 @@ async def convert_model(
model_raw = ApiDependencies.invoker.services.model_manager.list_model( model_raw = ApiDependencies.invoker.services.model_manager.list_model(
model_name, base_model=base_model, model_type=model_type model_name, base_model=base_model, model_type=model_type
) )
response = convert_models_response_adapter.validate_python(model_raw) response = ConvertModelResponseValidator.validate_python(model_raw)
except ModelNotFoundException as e: except ModelNotFoundException as e:
raise HTTPException(status_code=404, detail=f"Model '{model_name}' not found: {str(e)}") raise HTTPException(status_code=404, detail=f"Model '{model_name}' not found: {str(e)}")
except ValueError as e: except ValueError as e:
@@ -417,7 +416,7 @@ async def merge_models(
base_model=base_model, base_model=base_model,
model_type=ModelType.Main, model_type=ModelType.Main,
) )
response = convert_models_response_adapter.validate_python(model_raw) response = ConvertModelResponseValidator.validate_python(model_raw)
except ModelNotFoundException: except ModelNotFoundException:
raise HTTPException( raise HTTPException(
status_code=404, status_code=404,

View File

@@ -12,13 +12,11 @@ from invokeai.app.services.session_queue.session_queue_common import (
CancelByBatchIDsResult, CancelByBatchIDsResult,
ClearResult, ClearResult,
EnqueueBatchResult, EnqueueBatchResult,
EnqueueGraphResult,
PruneResult, PruneResult,
SessionQueueItem, SessionQueueItem,
SessionQueueItemDTO, SessionQueueItemDTO,
SessionQueueStatus, SessionQueueStatus,
) )
from invokeai.app.services.shared.graph import Graph
from invokeai.app.services.shared.pagination import CursorPaginatedResults from invokeai.app.services.shared.pagination import CursorPaginatedResults
from ..dependencies import ApiDependencies from ..dependencies import ApiDependencies
@@ -33,23 +31,6 @@ class SessionQueueAndProcessorStatus(BaseModel):
processor: SessionProcessorStatus processor: SessionProcessorStatus
@session_queue_router.post(
"/{queue_id}/enqueue_graph",
operation_id="enqueue_graph",
responses={
201: {"model": EnqueueGraphResult},
},
)
async def enqueue_graph(
queue_id: str = Path(description="The queue id to perform this operation on"),
graph: Graph = Body(description="The graph to enqueue"),
prepend: bool = Body(default=False, description="Whether or not to prepend this batch in the queue"),
) -> EnqueueGraphResult:
"""Enqueues a graph for single execution."""
return ApiDependencies.invoker.services.session_queue.enqueue_graph(queue_id=queue_id, graph=graph, prepend=prepend)
@session_queue_router.post( @session_queue_router.post(
"/{queue_id}/enqueue_batch", "/{queue_id}/enqueue_batch",
operation_id="enqueue_batch", operation_id="enqueue_batch",

View File

@@ -1,57 +1,50 @@
# Copyright (c) 2022 Kyle Schouviller (https://github.com/kyle0654) # Copyright (c) 2022 Kyle Schouviller (https://github.com/kyle0654)
from typing import Annotated, Optional, Union
from fastapi import Body, HTTPException, Path, Query, Response from fastapi import HTTPException, Path
from fastapi.routing import APIRouter from fastapi.routing import APIRouter
from pydantic.fields import Field
from invokeai.app.services.shared.pagination import PaginatedResults from ...services.shared.graph import GraphExecutionState
# Importing * is bad karma but needed here for node detection
from ...invocations import * # noqa: F401 F403
from ...invocations.baseinvocation import BaseInvocation
from ...services.shared.graph import Edge, EdgeConnection, Graph, GraphExecutionState, NodeAlreadyExecutedError
from ..dependencies import ApiDependencies from ..dependencies import ApiDependencies
session_router = APIRouter(prefix="/v1/sessions", tags=["sessions"]) session_router = APIRouter(prefix="/v1/sessions", tags=["sessions"])
@session_router.post( # @session_router.post(
"/", # "/",
operation_id="create_session", # operation_id="create_session",
responses={ # responses={
200: {"model": GraphExecutionState}, # 200: {"model": GraphExecutionState},
400: {"description": "Invalid json"}, # 400: {"description": "Invalid json"},
}, # },
deprecated=True, # deprecated=True,
) # )
async def create_session( # async def create_session(
queue_id: str = Query(default="", description="The id of the queue to associate the session with"), # queue_id: str = Query(default="", description="The id of the queue to associate the session with"),
graph: Optional[Graph] = Body(default=None, description="The graph to initialize the session with"), # graph: Optional[Graph] = Body(default=None, description="The graph to initialize the session with"),
) -> GraphExecutionState: # ) -> GraphExecutionState:
"""Creates a new session, optionally initializing it with an invocation graph""" # """Creates a new session, optionally initializing it with an invocation graph"""
session = ApiDependencies.invoker.create_execution_state(queue_id=queue_id, graph=graph) # session = ApiDependencies.invoker.create_execution_state(queue_id=queue_id, graph=graph)
return session # return session
@session_router.get( # @session_router.get(
"/", # "/",
operation_id="list_sessions", # operation_id="list_sessions",
responses={200: {"model": PaginatedResults[GraphExecutionState]}}, # responses={200: {"model": PaginatedResults[GraphExecutionState]}},
deprecated=True, # deprecated=True,
) # )
async def list_sessions( # async def list_sessions(
page: int = Query(default=0, description="The page of results to get"), # page: int = Query(default=0, description="The page of results to get"),
per_page: int = Query(default=10, description="The number of results per page"), # per_page: int = Query(default=10, description="The number of results per page"),
query: str = Query(default="", description="The query string to search for"), # query: str = Query(default="", description="The query string to search for"),
) -> PaginatedResults[GraphExecutionState]: # ) -> PaginatedResults[GraphExecutionState]:
"""Gets a list of sessions, optionally searching""" # """Gets a list of sessions, optionally searching"""
if query == "": # if query == "":
result = ApiDependencies.invoker.services.graph_execution_manager.list(page, per_page) # result = ApiDependencies.invoker.services.graph_execution_manager.list(page, per_page)
else: # else:
result = ApiDependencies.invoker.services.graph_execution_manager.search(query, page, per_page) # result = ApiDependencies.invoker.services.graph_execution_manager.search(query, page, per_page)
return result # return result
@session_router.get( @session_router.get(
@@ -61,7 +54,6 @@ async def list_sessions(
200: {"model": GraphExecutionState}, 200: {"model": GraphExecutionState},
404: {"description": "Session not found"}, 404: {"description": "Session not found"},
}, },
deprecated=True,
) )
async def get_session( async def get_session(
session_id: str = Path(description="The id of the session to get"), session_id: str = Path(description="The id of the session to get"),
@@ -74,211 +66,211 @@ async def get_session(
return session return session
@session_router.post( # @session_router.post(
"/{session_id}/nodes", # "/{session_id}/nodes",
operation_id="add_node", # operation_id="add_node",
responses={ # responses={
200: {"model": str}, # 200: {"model": str},
400: {"description": "Invalid node or link"}, # 400: {"description": "Invalid node or link"},
404: {"description": "Session not found"}, # 404: {"description": "Session not found"},
}, # },
deprecated=True, # deprecated=True,
) # )
async def add_node( # async def add_node(
session_id: str = Path(description="The id of the session"), # session_id: str = Path(description="The id of the session"),
node: Annotated[Union[BaseInvocation.get_invocations()], Field(discriminator="type")] = Body( # type: ignore # node: Annotated[Union[BaseInvocation.get_invocations()], Field(discriminator="type")] = Body( # type: ignore
description="The node to add" # description="The node to add"
), # ),
) -> str: # ) -> str:
"""Adds a node to the graph""" # """Adds a node to the graph"""
session = ApiDependencies.invoker.services.graph_execution_manager.get(session_id) # session = ApiDependencies.invoker.services.graph_execution_manager.get(session_id)
if session is None: # if session is None:
raise HTTPException(status_code=404) # raise HTTPException(status_code=404)
try: # try:
session.add_node(node) # session.add_node(node)
ApiDependencies.invoker.services.graph_execution_manager.set( # ApiDependencies.invoker.services.graph_execution_manager.set(
session # session
) # TODO: can this be done automatically, or add node through an API? # ) # TODO: can this be done automatically, or add node through an API?
return session.id # return session.id
except NodeAlreadyExecutedError: # except NodeAlreadyExecutedError:
raise HTTPException(status_code=400) # raise HTTPException(status_code=400)
except IndexError: # except IndexError:
raise HTTPException(status_code=400) # raise HTTPException(status_code=400)
@session_router.put( # @session_router.put(
"/{session_id}/nodes/{node_path}", # "/{session_id}/nodes/{node_path}",
operation_id="update_node", # operation_id="update_node",
responses={ # responses={
200: {"model": GraphExecutionState}, # 200: {"model": GraphExecutionState},
400: {"description": "Invalid node or link"}, # 400: {"description": "Invalid node or link"},
404: {"description": "Session not found"}, # 404: {"description": "Session not found"},
}, # },
deprecated=True, # deprecated=True,
) # )
async def update_node( # async def update_node(
session_id: str = Path(description="The id of the session"), # session_id: str = Path(description="The id of the session"),
node_path: str = Path(description="The path to the node in the graph"), # node_path: str = Path(description="The path to the node in the graph"),
node: Annotated[Union[BaseInvocation.get_invocations()], Field(discriminator="type")] = Body( # type: ignore # node: Annotated[Union[BaseInvocation.get_invocations()], Field(discriminator="type")] = Body( # type: ignore
description="The new node" # description="The new node"
), # ),
) -> GraphExecutionState: # ) -> GraphExecutionState:
"""Updates a node in the graph and removes all linked edges""" # """Updates a node in the graph and removes all linked edges"""
session = ApiDependencies.invoker.services.graph_execution_manager.get(session_id) # session = ApiDependencies.invoker.services.graph_execution_manager.get(session_id)
if session is None: # if session is None:
raise HTTPException(status_code=404) # raise HTTPException(status_code=404)
try: # try:
session.update_node(node_path, node) # session.update_node(node_path, node)
ApiDependencies.invoker.services.graph_execution_manager.set( # ApiDependencies.invoker.services.graph_execution_manager.set(
session # session
) # TODO: can this be done automatically, or add node through an API? # ) # TODO: can this be done automatically, or add node through an API?
return session # return session
except NodeAlreadyExecutedError: # except NodeAlreadyExecutedError:
raise HTTPException(status_code=400) # raise HTTPException(status_code=400)
except IndexError: # except IndexError:
raise HTTPException(status_code=400) # raise HTTPException(status_code=400)
@session_router.delete( # @session_router.delete(
"/{session_id}/nodes/{node_path}", # "/{session_id}/nodes/{node_path}",
operation_id="delete_node", # operation_id="delete_node",
responses={ # responses={
200: {"model": GraphExecutionState}, # 200: {"model": GraphExecutionState},
400: {"description": "Invalid node or link"}, # 400: {"description": "Invalid node or link"},
404: {"description": "Session not found"}, # 404: {"description": "Session not found"},
}, # },
deprecated=True, # deprecated=True,
) # )
async def delete_node( # async def delete_node(
session_id: str = Path(description="The id of the session"), # session_id: str = Path(description="The id of the session"),
node_path: str = Path(description="The path to the node to delete"), # node_path: str = Path(description="The path to the node to delete"),
) -> GraphExecutionState: # ) -> GraphExecutionState:
"""Deletes a node in the graph and removes all linked edges""" # """Deletes a node in the graph and removes all linked edges"""
session = ApiDependencies.invoker.services.graph_execution_manager.get(session_id) # session = ApiDependencies.invoker.services.graph_execution_manager.get(session_id)
if session is None: # if session is None:
raise HTTPException(status_code=404) # raise HTTPException(status_code=404)
try: # try:
session.delete_node(node_path) # session.delete_node(node_path)
ApiDependencies.invoker.services.graph_execution_manager.set( # ApiDependencies.invoker.services.graph_execution_manager.set(
session # session
) # TODO: can this be done automatically, or add node through an API? # ) # TODO: can this be done automatically, or add node through an API?
return session # return session
except NodeAlreadyExecutedError: # except NodeAlreadyExecutedError:
raise HTTPException(status_code=400) # raise HTTPException(status_code=400)
except IndexError: # except IndexError:
raise HTTPException(status_code=400) # raise HTTPException(status_code=400)
@session_router.post( # @session_router.post(
"/{session_id}/edges", # "/{session_id}/edges",
operation_id="add_edge", # operation_id="add_edge",
responses={ # responses={
200: {"model": GraphExecutionState}, # 200: {"model": GraphExecutionState},
400: {"description": "Invalid node or link"}, # 400: {"description": "Invalid node or link"},
404: {"description": "Session not found"}, # 404: {"description": "Session not found"},
}, # },
deprecated=True, # deprecated=True,
) # )
async def add_edge( # async def add_edge(
session_id: str = Path(description="The id of the session"), # session_id: str = Path(description="The id of the session"),
edge: Edge = Body(description="The edge to add"), # edge: Edge = Body(description="The edge to add"),
) -> GraphExecutionState: # ) -> GraphExecutionState:
"""Adds an edge to the graph""" # """Adds an edge to the graph"""
session = ApiDependencies.invoker.services.graph_execution_manager.get(session_id) # session = ApiDependencies.invoker.services.graph_execution_manager.get(session_id)
if session is None: # if session is None:
raise HTTPException(status_code=404) # raise HTTPException(status_code=404)
try: # try:
session.add_edge(edge) # session.add_edge(edge)
ApiDependencies.invoker.services.graph_execution_manager.set( # ApiDependencies.invoker.services.graph_execution_manager.set(
session # session
) # TODO: can this be done automatically, or add node through an API? # ) # TODO: can this be done automatically, or add node through an API?
return session # return session
except NodeAlreadyExecutedError: # except NodeAlreadyExecutedError:
raise HTTPException(status_code=400) # raise HTTPException(status_code=400)
except IndexError: # except IndexError:
raise HTTPException(status_code=400) # raise HTTPException(status_code=400)
# TODO: the edge being in the path here is really ugly, find a better solution # # TODO: the edge being in the path here is really ugly, find a better solution
@session_router.delete( # @session_router.delete(
"/{session_id}/edges/{from_node_id}/{from_field}/{to_node_id}/{to_field}", # "/{session_id}/edges/{from_node_id}/{from_field}/{to_node_id}/{to_field}",
operation_id="delete_edge", # operation_id="delete_edge",
responses={ # responses={
200: {"model": GraphExecutionState}, # 200: {"model": GraphExecutionState},
400: {"description": "Invalid node or link"}, # 400: {"description": "Invalid node or link"},
404: {"description": "Session not found"}, # 404: {"description": "Session not found"},
}, # },
deprecated=True, # deprecated=True,
) # )
async def delete_edge( # async def delete_edge(
session_id: str = Path(description="The id of the session"), # session_id: str = Path(description="The id of the session"),
from_node_id: str = Path(description="The id of the node the edge is coming from"), # from_node_id: str = Path(description="The id of the node the edge is coming from"),
from_field: str = Path(description="The field of the node the edge is coming from"), # from_field: str = Path(description="The field of the node the edge is coming from"),
to_node_id: str = Path(description="The id of the node the edge is going to"), # to_node_id: str = Path(description="The id of the node the edge is going to"),
to_field: str = Path(description="The field of the node the edge is going to"), # to_field: str = Path(description="The field of the node the edge is going to"),
) -> GraphExecutionState: # ) -> GraphExecutionState:
"""Deletes an edge from the graph""" # """Deletes an edge from the graph"""
session = ApiDependencies.invoker.services.graph_execution_manager.get(session_id) # session = ApiDependencies.invoker.services.graph_execution_manager.get(session_id)
if session is None: # if session is None:
raise HTTPException(status_code=404) # raise HTTPException(status_code=404)
try: # try:
edge = Edge( # edge = Edge(
source=EdgeConnection(node_id=from_node_id, field=from_field), # source=EdgeConnection(node_id=from_node_id, field=from_field),
destination=EdgeConnection(node_id=to_node_id, field=to_field), # destination=EdgeConnection(node_id=to_node_id, field=to_field),
) # )
session.delete_edge(edge) # session.delete_edge(edge)
ApiDependencies.invoker.services.graph_execution_manager.set( # ApiDependencies.invoker.services.graph_execution_manager.set(
session # session
) # TODO: can this be done automatically, or add node through an API? # ) # TODO: can this be done automatically, or add node through an API?
return session # return session
except NodeAlreadyExecutedError: # except NodeAlreadyExecutedError:
raise HTTPException(status_code=400) # raise HTTPException(status_code=400)
except IndexError: # except IndexError:
raise HTTPException(status_code=400) # raise HTTPException(status_code=400)
@session_router.put( # @session_router.put(
"/{session_id}/invoke", # "/{session_id}/invoke",
operation_id="invoke_session", # operation_id="invoke_session",
responses={ # responses={
200: {"model": None}, # 200: {"model": None},
202: {"description": "The invocation is queued"}, # 202: {"description": "The invocation is queued"},
400: {"description": "The session has no invocations ready to invoke"}, # 400: {"description": "The session has no invocations ready to invoke"},
404: {"description": "Session not found"}, # 404: {"description": "Session not found"},
}, # },
deprecated=True, # deprecated=True,
) # )
async def invoke_session( # async def invoke_session(
queue_id: str = Query(description="The id of the queue to associate the session with"), # queue_id: str = Query(description="The id of the queue to associate the session with"),
session_id: str = Path(description="The id of the session to invoke"), # session_id: str = Path(description="The id of the session to invoke"),
all: bool = Query(default=False, description="Whether or not to invoke all remaining invocations"), # all: bool = Query(default=False, description="Whether or not to invoke all remaining invocations"),
) -> Response: # ) -> Response:
"""Invokes a session""" # """Invokes a session"""
session = ApiDependencies.invoker.services.graph_execution_manager.get(session_id) # session = ApiDependencies.invoker.services.graph_execution_manager.get(session_id)
if session is None: # if session is None:
raise HTTPException(status_code=404) # raise HTTPException(status_code=404)
if session.is_complete(): # if session.is_complete():
raise HTTPException(status_code=400) # raise HTTPException(status_code=400)
ApiDependencies.invoker.invoke(queue_id, session, invoke_all=all) # ApiDependencies.invoker.invoke(queue_id, session, invoke_all=all)
return Response(status_code=202) # return Response(status_code=202)
@session_router.delete( # @session_router.delete(
"/{session_id}/invoke", # "/{session_id}/invoke",
operation_id="cancel_session_invoke", # operation_id="cancel_session_invoke",
responses={202: {"description": "The invocation is canceled"}}, # responses={202: {"description": "The invocation is canceled"}},
deprecated=True, # deprecated=True,
) # )
async def cancel_session_invoke( # async def cancel_session_invoke(
session_id: str = Path(description="The id of the session to cancel"), # session_id: str = Path(description="The id of the session to cancel"),
) -> Response: # ) -> Response:
"""Invokes a session""" # """Invokes a session"""
ApiDependencies.invoker.cancel(session_id) # ApiDependencies.invoker.cancel(session_id)
return Response(status_code=202) # return Response(status_code=202)

View File

@@ -0,0 +1,20 @@
from fastapi import APIRouter, Path
from invokeai.app.api.dependencies import ApiDependencies
from invokeai.app.invocations.baseinvocation import WorkflowField
workflows_router = APIRouter(prefix="/v1/workflows", tags=["workflows"])
@workflows_router.get(
"/i/{workflow_id}",
operation_id="get_workflow",
responses={
200: {"model": WorkflowField},
},
)
async def get_workflow(
workflow_id: str = Path(description="The workflow to get"),
) -> WorkflowField:
"""Gets a workflow"""
return ApiDependencies.invoker.services.workflow_records.get(workflow_id)

View File

@@ -34,4 +34,4 @@ class SocketIO:
async def _handle_unsub_queue(self, sid, data, *args, **kwargs): async def _handle_unsub_queue(self, sid, data, *args, **kwargs):
if "queue_id" in data: if "queue_id" in data:
await self.__sio.enter_room(sid, data["queue_id"]) await self.__sio.leave_room(sid, data["queue_id"])

View File

@@ -1,3 +1,7 @@
from typing import Any
from fastapi.responses import HTMLResponse
from .services.config import InvokeAIAppConfig from .services.config import InvokeAIAppConfig
# parse_args() must be called before any other imports. if it is not called first, consumers of the config # parse_args() must be called before any other imports. if it is not called first, consumers of the config
@@ -13,17 +17,20 @@ if True: # hack to make flake8 happy with imports coming after setting up the c
from inspect import signature from inspect import signature
from pathlib import Path from pathlib import Path
import torch
import uvicorn import uvicorn
from fastapi import FastAPI from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from fastapi.middleware.gzip import GZipMiddleware
from fastapi.openapi.docs import get_redoc_html, get_swagger_ui_html from fastapi.openapi.docs import get_redoc_html, get_swagger_ui_html
from fastapi.openapi.utils import get_openapi from fastapi.openapi.utils import get_openapi
from fastapi.responses import FileResponse
from fastapi.staticfiles import StaticFiles from fastapi.staticfiles import StaticFiles
from fastapi_events.handlers.local import local_handler from fastapi_events.handlers.local import local_handler
from fastapi_events.middleware import EventHandlerASGIMiddleware from fastapi_events.middleware import EventHandlerASGIMiddleware
from pydantic.json_schema import models_json_schema from pydantic.json_schema import models_json_schema
from torch.backends.mps import is_available as is_mps_available
# for PyCharm:
# noinspection PyUnresolvedReferences # noinspection PyUnresolvedReferences
import invokeai.backend.util.hotfixes # noqa: F401 (monkeypatching on import) import invokeai.backend.util.hotfixes # noqa: F401 (monkeypatching on import)
import invokeai.frontend.web as web_dir import invokeai.frontend.web as web_dir
@@ -31,19 +38,28 @@ if True: # hack to make flake8 happy with imports coming after setting up the c
from ..backend.util.logging import InvokeAILogger from ..backend.util.logging import InvokeAILogger
from .api.dependencies import ApiDependencies from .api.dependencies import ApiDependencies
from .api.routers import app_info, board_images, boards, images, models, session_queue, utilities from .api.routers import (
app_info,
board_images,
boards,
images,
model_records,
models,
session_queue,
sessions,
utilities,
workflows,
)
from .api.sockets import SocketIO from .api.sockets import SocketIO
from .invocations.baseinvocation import BaseInvocation, UIConfigBase, _InputField, _OutputField from .invocations.baseinvocation import BaseInvocation, UIConfigBase, _InputField, _OutputField
if torch.backends.mps.is_available(): if is_mps_available():
# noinspection PyUnresolvedReferences
import invokeai.backend.util.mps_fixes # noqa: F401 (monkeypatching on import) import invokeai.backend.util.mps_fixes # noqa: F401 (monkeypatching on import)
app_config = InvokeAIAppConfig.get_config() app_config = InvokeAIAppConfig.get_config()
app_config.parse_args() app_config.parse_args()
logger = InvokeAILogger.get_logger(config=app_config) logger = InvokeAILogger.get_logger(config=app_config)
# fix for windows mimetypes registry entries being borked # fix for windows mimetypes registry entries being borked
# see https://github.com/invoke-ai/InvokeAI/discussions/3684#discussioncomment-6391352 # see https://github.com/invoke-ai/InvokeAI/discussions/3684#discussioncomment-6391352
mimetypes.add_type("application/javascript", ".js") mimetypes.add_type("application/javascript", ".js")
@@ -71,40 +87,38 @@ app.add_middleware(
allow_headers=app_config.allow_headers, allow_headers=app_config.allow_headers,
) )
app.add_middleware(GZipMiddleware, minimum_size=1000)
# Add startup event to load dependencies # Add startup event to load dependencies
@app.on_event("startup") @app.on_event("startup")
async def startup_event(): async def startup_event() -> None:
ApiDependencies.initialize(config=app_config, event_handler_id=event_handler_id, logger=logger) ApiDependencies.initialize(config=app_config, event_handler_id=event_handler_id, logger=logger)
# Shut down threads # Shut down threads
@app.on_event("shutdown") @app.on_event("shutdown")
async def shutdown_event(): async def shutdown_event() -> None:
ApiDependencies.shutdown() ApiDependencies.shutdown()
# Include all routers # Include all routers
# app.include_router(sessions.session_router, prefix="/api") app.include_router(sessions.session_router, prefix="/api")
app.include_router(utilities.utilities_router, prefix="/api") app.include_router(utilities.utilities_router, prefix="/api")
app.include_router(models.models_router, prefix="/api") app.include_router(models.models_router, prefix="/api")
app.include_router(model_records.model_records_router, prefix="/api")
app.include_router(images.images_router, prefix="/api") app.include_router(images.images_router, prefix="/api")
app.include_router(boards.boards_router, prefix="/api") app.include_router(boards.boards_router, prefix="/api")
app.include_router(board_images.board_images_router, prefix="/api") app.include_router(board_images.board_images_router, prefix="/api")
app.include_router(app_info.app_router, prefix="/api") app.include_router(app_info.app_router, prefix="/api")
app.include_router(session_queue.session_queue_router, prefix="/api") app.include_router(session_queue.session_queue_router, prefix="/api")
app.include_router(workflows.workflows_router, prefix="/api")
# Build a custom OpenAPI to include all outputs # Build a custom OpenAPI to include all outputs
# TODO: can outputs be included on metadata of invocation schemas somehow? # TODO: can outputs be included on metadata of invocation schemas somehow?
def custom_openapi(): def custom_openapi() -> dict[str, Any]:
if app.openapi_schema: if app.openapi_schema:
return app.openapi_schema return app.openapi_schema
openapi_schema = get_openapi( openapi_schema = get_openapi(
@@ -118,7 +132,7 @@ def custom_openapi():
# Add all outputs # Add all outputs
all_invocations = BaseInvocation.get_invocations() all_invocations = BaseInvocation.get_invocations()
output_types = set() output_types = set()
output_type_titles = dict() output_type_titles = {}
for invoker in all_invocations: for invoker in all_invocations:
output_type = signature(invoker.invoke).return_annotation output_type = signature(invoker.invoke).return_annotation
output_types.add(output_type) output_types.add(output_type)
@@ -159,13 +173,12 @@ def custom_openapi():
# print(f"Config with name {name} already defined") # print(f"Config with name {name} already defined")
continue continue
# "BaseModelType":{"title":"BaseModelType","description":"An enumeration.","enum":["sd-1","sd-2"],"type":"string"} openapi_schema["components"]["schemas"][name] = {
openapi_schema["components"]["schemas"][name] = dict( "title": name,
title=name, "description": "An enumeration.",
description="An enumeration.", "type": "string",
type="string", "enum": [v.value for v in model_config_format_enum],
enum=list(v.value for v in model_config_format_enum), }
)
app.openapi_schema = openapi_schema app.openapi_schema = openapi_schema
return app.openapi_schema return app.openapi_schema
@@ -173,34 +186,43 @@ def custom_openapi():
app.openapi = custom_openapi # type: ignore [method-assign] # this is a valid assignment app.openapi = custom_openapi # type: ignore [method-assign] # this is a valid assignment
# Override API doc favicons
app.mount("/static", StaticFiles(directory=Path(web_dir.__path__[0], "static/dream_web")), name="static")
@app.get("/docs", include_in_schema=False) @app.get("/docs", include_in_schema=False)
def overridden_swagger(): def overridden_swagger() -> HTMLResponse:
return get_swagger_ui_html( return get_swagger_ui_html(
openapi_url=app.openapi_url, openapi_url=app.openapi_url, # type: ignore [arg-type] # this is always a string
title=app.title, title=app.title,
swagger_favicon_url="/static/favicon.ico", swagger_favicon_url="/static/docs/favicon.ico",
) )
@app.get("/redoc", include_in_schema=False) @app.get("/redoc", include_in_schema=False)
def overridden_redoc(): def overridden_redoc() -> HTMLResponse:
return get_redoc_html( return get_redoc_html(
openapi_url=app.openapi_url, openapi_url=app.openapi_url, # type: ignore [arg-type] # this is always a string
title=app.title, title=app.title,
redoc_favicon_url="/static/favicon.ico", redoc_favicon_url="/static/docs/favicon.ico",
) )
# Must mount *after* the other routes else it borks em web_root_path = Path(list(web_dir.__path__)[0])
app.mount("/", StaticFiles(directory=Path(web_dir.__path__[0], "dist"), html=True), name="ui")
def invoke_api(): # Cannot add headers to StaticFiles, so we must serve index.html with a custom route
def find_port(port: int): # Add cache-control: no-store header to prevent caching of index.html, which leads to broken UIs at release
@app.get("/", include_in_schema=False, name="ui_root")
def get_index() -> FileResponse:
return FileResponse(Path(web_root_path, "dist/index.html"), headers={"Cache-Control": "no-store"})
# # Must mount *after* the other routes else it borks em
app.mount("/static", StaticFiles(directory=Path(web_root_path, "static/")), name="static") # docs favicon is in here
app.mount("/assets", StaticFiles(directory=Path(web_root_path, "dist/assets/")), name="assets")
app.mount("/locales", StaticFiles(directory=Path(web_root_path, "dist/locales/")), name="locales")
def invoke_api() -> None:
def find_port(port: int) -> int:
"""Find a port not in use starting at given port""" """Find a port not in use starting at given port"""
# Taken from https://waylonwalker.com/python-find-available-port/, thanks Waylon! # Taken from https://waylonwalker.com/python-find-available-port/, thanks Waylon!
# https://github.com/WaylonWalker # https://github.com/WaylonWalker
@@ -235,7 +257,7 @@ def invoke_api():
app=app, app=app,
host=app_config.host, host=app_config.host,
port=port, port=port,
loop=loop, loop="asyncio",
log_level=app_config.log_level, log_level=app_config.log_level,
) )
server = uvicorn.Server(config) server = uvicorn.Server(config)

View File

@@ -1,312 +0,0 @@
# Copyright (c) 2023 Kyle Schouviller (https://github.com/kyle0654)
import argparse
from abc import ABC, abstractmethod
from typing import Any, Callable, Iterable, Literal, Union, get_args, get_origin, get_type_hints
import matplotlib.pyplot as plt
import networkx as nx
from pydantic import BaseModel, Field
import invokeai.backend.util.logging as logger
from ..invocations.baseinvocation import BaseInvocation
from ..invocations.image import ImageField
from ..services.graph import Edge, GraphExecutionState, LibraryGraph
from ..services.invoker import Invoker
def add_field_argument(command_parser, name: str, field, default_override=None):
default = (
default_override
if default_override is not None
else field.default
if field.default_factory is None
else field.default_factory()
)
if get_origin(field.annotation) == Literal:
allowed_values = get_args(field.annotation)
allowed_types = set()
for val in allowed_values:
allowed_types.add(type(val))
allowed_types_list = list(allowed_types)
field_type = allowed_types_list[0] if len(allowed_types) == 1 else Union[allowed_types_list] # type: ignore
command_parser.add_argument(
f"--{name}",
dest=name,
type=field_type,
default=default,
choices=allowed_values,
help=field.description,
)
else:
command_parser.add_argument(
f"--{name}",
dest=name,
type=field.annotation,
default=default,
help=field.description,
)
def add_parsers(
subparsers,
commands: list[type],
command_field: str = "type",
exclude_fields: list[str] = ["id", "type"],
add_arguments: Union[Callable[[argparse.ArgumentParser], None], None] = None,
):
"""Adds parsers for each command to the subparsers"""
# Create subparsers for each command
for command in commands:
hints = get_type_hints(command)
cmd_name = get_args(hints[command_field])[0]
command_parser = subparsers.add_parser(cmd_name, help=command.__doc__)
if add_arguments is not None:
add_arguments(command_parser)
# Convert all fields to arguments
fields = command.__fields__ # type: ignore
for name, field in fields.items():
if name in exclude_fields:
continue
add_field_argument(command_parser, name, field)
def add_graph_parsers(
subparsers, graphs: list[LibraryGraph], add_arguments: Union[Callable[[argparse.ArgumentParser], None], None] = None
):
for graph in graphs:
command_parser = subparsers.add_parser(graph.name, help=graph.description)
if add_arguments is not None:
add_arguments(command_parser)
# Add arguments for inputs
for exposed_input in graph.exposed_inputs:
node = graph.graph.get_node(exposed_input.node_path)
field = node.__fields__[exposed_input.field]
default_override = getattr(node, exposed_input.field)
add_field_argument(command_parser, exposed_input.alias, field, default_override)
class CliContext:
invoker: Invoker
session: GraphExecutionState
parser: argparse.ArgumentParser
defaults: dict[str, Any]
graph_nodes: dict[str, str]
nodes_added: list[str]
def __init__(self, invoker: Invoker, session: GraphExecutionState, parser: argparse.ArgumentParser):
self.invoker = invoker
self.session = session
self.parser = parser
self.defaults = dict()
self.graph_nodes = dict()
self.nodes_added = list()
def get_session(self):
self.session = self.invoker.services.graph_execution_manager.get(self.session.id)
return self.session
def reset(self):
self.session = self.invoker.create_execution_state()
self.graph_nodes = dict()
self.nodes_added = list()
# Leave defaults unchanged
def add_node(self, node: BaseInvocation):
self.get_session()
self.session.graph.add_node(node)
self.nodes_added.append(node.id)
self.invoker.services.graph_execution_manager.set(self.session)
def add_edge(self, edge: Edge):
self.get_session()
self.session.add_edge(edge)
self.invoker.services.graph_execution_manager.set(self.session)
class ExitCli(Exception):
"""Exception to exit the CLI"""
pass
class BaseCommand(ABC, BaseModel):
"""A CLI command"""
# All commands must include a type name like this:
@classmethod
def get_all_subclasses(cls):
subclasses = []
toprocess = [cls]
while len(toprocess) > 0:
next = toprocess.pop(0)
next_subclasses = next.__subclasses__()
subclasses.extend(next_subclasses)
toprocess.extend(next_subclasses)
return subclasses
@classmethod
def get_commands(cls):
return tuple(BaseCommand.get_all_subclasses())
@classmethod
def get_commands_map(cls):
# Get the type strings out of the literals and into a dictionary
return dict(map(lambda t: (get_args(get_type_hints(t)["type"])[0], t), BaseCommand.get_all_subclasses()))
@abstractmethod
def run(self, context: CliContext) -> None:
"""Run the command. Raise ExitCli to exit."""
pass
class ExitCommand(BaseCommand):
"""Exits the CLI"""
type: Literal["exit"] = "exit"
def run(self, context: CliContext) -> None:
raise ExitCli()
class HelpCommand(BaseCommand):
"""Shows help"""
type: Literal["help"] = "help"
def run(self, context: CliContext) -> None:
context.parser.print_help()
def get_graph_execution_history(
graph_execution_state: GraphExecutionState,
) -> Iterable[str]:
"""Gets the history of fully-executed invocations for a graph execution"""
return (n for n in reversed(graph_execution_state.executed_history) if n in graph_execution_state.graph.nodes)
def get_invocation_command(invocation) -> str:
fields = invocation.__fields__.items()
type_hints = get_type_hints(type(invocation))
command = [invocation.type]
for name, field in fields:
if name in ["id", "type"]:
continue
# TODO: add links
# Skip image fields when serializing command
type_hint = type_hints.get(name) or None
if type_hint is ImageField or ImageField in get_args(type_hint):
continue
field_value = getattr(invocation, name)
field_default = field.default
if field_value != field_default:
if type_hint is str or str in get_args(type_hint):
command.append(f'--{name} "{field_value}"')
else:
command.append(f"--{name} {field_value}")
return " ".join(command)
class HistoryCommand(BaseCommand):
"""Shows the invocation history"""
type: Literal["history"] = "history"
# Inputs
# fmt: off
count: int = Field(default=5, gt=0, description="The number of history entries to show")
# fmt: on
def run(self, context: CliContext) -> None:
history = list(get_graph_execution_history(context.get_session()))
for i in range(min(self.count, len(history))):
entry_id = history[-1 - i]
entry = context.get_session().graph.get_node(entry_id)
logger.info(f"{entry_id}: {get_invocation_command(entry)}")
class SetDefaultCommand(BaseCommand):
"""Sets a default value for a field"""
type: Literal["default"] = "default"
# Inputs
# fmt: off
field: str = Field(description="The field to set the default for")
value: str = Field(description="The value to set the default to, or None to clear the default")
# fmt: on
def run(self, context: CliContext) -> None:
if self.value is None:
if self.field in context.defaults:
del context.defaults[self.field]
else:
context.defaults[self.field] = self.value
class DrawGraphCommand(BaseCommand):
"""Debugs a graph"""
type: Literal["draw_graph"] = "draw_graph"
def run(self, context: CliContext) -> None:
session: GraphExecutionState = context.invoker.services.graph_execution_manager.get(context.session.id)
nxgraph = session.graph.nx_graph_flat()
# Draw the networkx graph
plt.figure(figsize=(20, 20))
pos = nx.spectral_layout(nxgraph)
nx.draw_networkx_nodes(nxgraph, pos, node_size=1000)
nx.draw_networkx_edges(nxgraph, pos, width=2)
nx.draw_networkx_labels(nxgraph, pos, font_size=20, font_family="sans-serif")
plt.axis("off")
plt.show()
class DrawExecutionGraphCommand(BaseCommand):
"""Debugs an execution graph"""
type: Literal["draw_xgraph"] = "draw_xgraph"
def run(self, context: CliContext) -> None:
session: GraphExecutionState = context.invoker.services.graph_execution_manager.get(context.session.id)
nxgraph = session.execution_graph.nx_graph_flat()
# Draw the networkx graph
plt.figure(figsize=(20, 20))
pos = nx.spectral_layout(nxgraph)
nx.draw_networkx_nodes(nxgraph, pos, node_size=1000)
nx.draw_networkx_edges(nxgraph, pos, width=2)
nx.draw_networkx_labels(nxgraph, pos, font_size=20, font_family="sans-serif")
plt.axis("off")
plt.show()
class SortedHelpFormatter(argparse.HelpFormatter):
def _iter_indented_subactions(self, action):
try:
get_subactions = action._get_subactions
except AttributeError:
pass
else:
self._indent()
if isinstance(action, argparse._SubParsersAction):
for subaction in sorted(get_subactions(), key=lambda x: x.dest):
yield subaction
else:
for subaction in get_subactions():
yield subaction
self._dedent()

View File

@@ -1,171 +0,0 @@
"""
Readline helper functions for cli_app.py
You may import the global singleton `completer` to get access to the
completer object.
"""
import atexit
import readline
import shlex
from pathlib import Path
from typing import Dict, List, Literal, get_args, get_origin, get_type_hints
import invokeai.backend.util.logging as logger
from ...backend import ModelManager
from ..invocations.baseinvocation import BaseInvocation
from ..services.invocation_services import InvocationServices
from .commands import BaseCommand
# singleton object, class variable
completer = None
class Completer(object):
def __init__(self, model_manager: ModelManager):
self.commands = self.get_commands()
self.matches = None
self.linebuffer = None
self.manager = model_manager
return
def complete(self, text, state):
"""
Complete commands and switches fromm the node CLI command line.
Switches are determined in a context-specific manner.
"""
buffer = readline.get_line_buffer()
if state == 0:
options = None
try:
current_command, current_switch = self.get_current_command(buffer)
options = self.get_command_options(current_command, current_switch)
except IndexError:
pass
options = options or list(self.parse_commands().keys())
if not text: # first time
self.matches = options
else:
self.matches = [s for s in options if s and s.startswith(text)]
try:
match = self.matches[state]
except IndexError:
match = None
return match
@classmethod
def get_commands(self) -> List[object]:
"""
Return a list of all the client commands and invocations.
"""
return BaseCommand.get_commands() + BaseInvocation.get_invocations()
def get_current_command(self, buffer: str) -> tuple[str, str]:
"""
Parse the readline buffer to find the most recent command and its switch.
"""
if len(buffer) == 0:
return None, None
tokens = shlex.split(buffer)
command = None
switch = None
for t in tokens:
if t[0].isalpha():
if switch is None:
command = t
else:
switch = t
# don't try to autocomplete switches that are already complete
if switch and buffer.endswith(" "):
switch = None
return command or "", switch or ""
def parse_commands(self) -> Dict[str, List[str]]:
"""
Return a dict in which the keys are the command name
and the values are the parameters the command takes.
"""
result = dict()
for command in self.commands:
hints = get_type_hints(command)
name = get_args(hints["type"])[0]
result.update({name: hints})
return result
def get_command_options(self, command: str, switch: str) -> List[str]:
"""
Return all the parameters that can be passed to the command as
command-line switches. Returns None if the command is unrecognized.
"""
parsed_commands = self.parse_commands()
if command not in parsed_commands:
return None
# handle switches in the format "-foo=bar"
argument = None
if switch and "=" in switch:
switch, argument = switch.split("=")
parameter = switch.strip("-")
if parameter in parsed_commands[command]:
if argument is None:
return self.get_parameter_options(parameter, parsed_commands[command][parameter])
else:
return [
f"--{parameter}={x}"
for x in self.get_parameter_options(parameter, parsed_commands[command][parameter])
]
else:
return [f"--{x}" for x in parsed_commands[command].keys()]
def get_parameter_options(self, parameter: str, typehint) -> List[str]:
"""
Given a parameter type (such as Literal), offers autocompletions.
"""
if get_origin(typehint) == Literal:
return get_args(typehint)
if parameter == "model":
return self.manager.model_names()
def _pre_input_hook(self):
if self.linebuffer:
readline.insert_text(self.linebuffer)
readline.redisplay()
self.linebuffer = None
def set_autocompleter(services: InvocationServices) -> Completer:
global completer
if completer:
return completer
completer = Completer(services.model_manager)
readline.set_completer(completer.complete)
try:
readline.set_auto_history(True)
except AttributeError:
# pyreadline3 does not have a set_auto_history() method
pass
readline.set_pre_input_hook(completer._pre_input_hook)
readline.set_completer_delims(" ")
readline.parse_and_bind("tab: complete")
readline.parse_and_bind("set print-completions-horizontally off")
readline.parse_and_bind("set page-completions on")
readline.parse_and_bind("set skip-completed-text on")
readline.parse_and_bind("set show-all-if-ambiguous on")
histfile = Path(services.configuration.root_dir / ".invoke_history")
try:
readline.read_history_file(histfile)
readline.set_history_length(1000)
except FileNotFoundError:
pass
except OSError: # file likely corrupted
newname = f"{histfile}.old"
logger.error(f"Your history file {histfile} couldn't be loaded and may be corrupted. Renaming it to {newname}")
histfile.replace(Path(newname))
atexit.register(readline.write_history_file, histfile)

View File

@@ -1,484 +0,0 @@
# Copyright (c) 2022 Kyle Schouviller (https://github.com/kyle0654) and the InvokeAI Team
from invokeai.app.services.invocation_cache.invocation_cache_memory import MemoryInvocationCache
from .services.config import InvokeAIAppConfig
# parse_args() must be called before any other imports. if it is not called first, consumers of the config
# which are imported/used before parse_args() is called will get the default config values instead of the
# values from the command line or config file.
if True: # hack to make flake8 happy with imports coming after setting up the config
import argparse
import re
import shlex
import sqlite3
import sys
import time
from typing import Optional, Union, get_type_hints
import torch
from pydantic import BaseModel, ValidationError
from pydantic.fields import Field
import invokeai.backend.util.hotfixes # noqa: F401 (monkeypatching on import)
from invokeai.app.services.board_image_record_storage import SqliteBoardImageRecordStorage
from invokeai.app.services.board_images import BoardImagesService, BoardImagesServiceDependencies
from invokeai.app.services.board_record_storage import SqliteBoardRecordStorage
from invokeai.app.services.boards import BoardService, BoardServiceDependencies
from invokeai.app.services.image_record_storage import SqliteImageRecordStorage
from invokeai.app.services.images import ImageService, ImageServiceDependencies
from invokeai.app.services.invocation_stats import InvocationStatsService
from invokeai.app.services.resource_name import SimpleNameService
from invokeai.app.services.urls import LocalUrlService
from invokeai.backend.util.logging import InvokeAILogger
from invokeai.version.invokeai_version import __version__
from .cli.commands import BaseCommand, CliContext, ExitCli, SortedHelpFormatter, add_graph_parsers, add_parsers
from .cli.completer import set_autocompleter
from .invocations.baseinvocation import BaseInvocation
from .services.default_graphs import create_system_graphs, default_text_to_image_graph_id
from .services.events import EventServiceBase
from .services.graph import (
Edge,
EdgeConnection,
GraphExecutionState,
GraphInvocation,
LibraryGraph,
are_connection_types_compatible,
)
from .services.image_file_storage import DiskImageFileStorage
from .services.invocation_queue import MemoryInvocationQueue
from .services.invocation_services import InvocationServices
from .services.invoker import Invoker
from .services.latent_storage import DiskLatentsStorage, ForwardCacheLatentsStorage
from .services.model_manager_service import ModelManagerService
from .services.processor import DefaultInvocationProcessor
from .services.sqlite import SqliteItemStorage
if torch.backends.mps.is_available():
import invokeai.backend.util.mps_fixes # noqa: F401 (monkeypatching on import)
config = InvokeAIAppConfig.get_config()
config.parse_args()
logger = InvokeAILogger().get_logger(config=config)
class CliCommand(BaseModel):
command: Union[BaseCommand.get_commands() + BaseInvocation.get_invocations()] = Field(discriminator="type") # type: ignore
class InvalidArgs(Exception):
pass
def add_invocation_args(command_parser):
# Add linking capability
command_parser.add_argument(
"--link",
"-l",
action="append",
nargs=3,
help="A link in the format 'source_node source_field dest_field'. source_node can be relative to history (e.g. -1)",
)
command_parser.add_argument(
"--link_node",
"-ln",
action="append",
help="A link from all fields in the specified node. Node can be relative to history (e.g. -1)",
)
def get_command_parser(services: InvocationServices) -> argparse.ArgumentParser:
# Create invocation parser
parser = argparse.ArgumentParser(formatter_class=SortedHelpFormatter)
def exit(*args, **kwargs):
raise InvalidArgs
parser.exit = exit
subparsers = parser.add_subparsers(dest="type")
# Create subparsers for each invocation
invocations = BaseInvocation.get_all_subclasses()
add_parsers(subparsers, invocations, add_arguments=add_invocation_args)
# Create subparsers for each command
commands = BaseCommand.get_all_subclasses()
add_parsers(subparsers, commands, exclude_fields=["type"])
# Create subparsers for exposed CLI graphs
# TODO: add a way to identify these graphs
text_to_image = services.graph_library.get(default_text_to_image_graph_id)
add_graph_parsers(subparsers, [text_to_image], add_arguments=add_invocation_args)
return parser
class NodeField:
alias: str
node_path: str
field: str
field_type: type
def __init__(self, alias: str, node_path: str, field: str, field_type: type):
self.alias = alias
self.node_path = node_path
self.field = field
self.field_type = field_type
def fields_from_type_hints(hints: dict[str, type], node_path: str) -> dict[str, NodeField]:
return {k: NodeField(alias=k, node_path=node_path, field=k, field_type=v) for k, v in hints.items()}
def get_node_input_field(graph: LibraryGraph, field_alias: str, node_id: str) -> NodeField:
"""Gets the node field for the specified field alias"""
exposed_input = next(e for e in graph.exposed_inputs if e.alias == field_alias)
node_type = type(graph.graph.get_node(exposed_input.node_path))
return NodeField(
alias=exposed_input.alias,
node_path=f"{node_id}.{exposed_input.node_path}",
field=exposed_input.field,
field_type=get_type_hints(node_type)[exposed_input.field],
)
def get_node_output_field(graph: LibraryGraph, field_alias: str, node_id: str) -> NodeField:
"""Gets the node field for the specified field alias"""
exposed_output = next(e for e in graph.exposed_outputs if e.alias == field_alias)
node_type = type(graph.graph.get_node(exposed_output.node_path))
node_output_type = node_type.get_output_type()
return NodeField(
alias=exposed_output.alias,
node_path=f"{node_id}.{exposed_output.node_path}",
field=exposed_output.field,
field_type=get_type_hints(node_output_type)[exposed_output.field],
)
def get_node_inputs(invocation: BaseInvocation, context: CliContext) -> dict[str, NodeField]:
"""Gets the inputs for the specified invocation from the context"""
node_type = type(invocation)
if node_type is not GraphInvocation:
return fields_from_type_hints(get_type_hints(node_type), invocation.id)
else:
graph: LibraryGraph = context.invoker.services.graph_library.get(context.graph_nodes[invocation.id])
return {e.alias: get_node_input_field(graph, e.alias, invocation.id) for e in graph.exposed_inputs}
def get_node_outputs(invocation: BaseInvocation, context: CliContext) -> dict[str, NodeField]:
"""Gets the outputs for the specified invocation from the context"""
node_type = type(invocation)
if node_type is not GraphInvocation:
return fields_from_type_hints(get_type_hints(node_type.get_output_type()), invocation.id)
else:
graph: LibraryGraph = context.invoker.services.graph_library.get(context.graph_nodes[invocation.id])
return {e.alias: get_node_output_field(graph, e.alias, invocation.id) for e in graph.exposed_outputs}
def generate_matching_edges(a: BaseInvocation, b: BaseInvocation, context: CliContext) -> list[Edge]:
"""Generates all possible edges between two invocations"""
afields = get_node_outputs(a, context)
bfields = get_node_inputs(b, context)
matching_fields = set(afields.keys()).intersection(bfields.keys())
# Remove invalid fields
invalid_fields = set(["type", "id"])
matching_fields = matching_fields.difference(invalid_fields)
# Validate types
matching_fields = [
f for f in matching_fields if are_connection_types_compatible(afields[f].field_type, bfields[f].field_type)
]
edges = [
Edge(
source=EdgeConnection(node_id=afields[alias].node_path, field=afields[alias].field),
destination=EdgeConnection(node_id=bfields[alias].node_path, field=bfields[alias].field),
)
for alias in matching_fields
]
return edges
class SessionError(Exception):
"""Raised when a session error has occurred"""
pass
def invoke_all(context: CliContext):
"""Runs all invocations in the specified session"""
context.invoker.invoke(context.session, invoke_all=True)
while not context.get_session().is_complete():
# Wait some time
time.sleep(0.1)
# Print any errors
if context.session.has_error():
for n in context.session.errors:
context.invoker.services.logger.error(
f"Error in node {n} (source node {context.session.prepared_source_mapping[n]}): {context.session.errors[n]}"
)
raise SessionError()
def invoke_cli():
logger.info(f"InvokeAI version {__version__}")
# get the optional list of invocations to execute on the command line
parser = config.get_parser()
parser.add_argument("commands", nargs="*")
invocation_commands = parser.parse_args().commands
# get the optional file to read commands from.
# Simplest is to use it for STDIN
if infile := config.from_file:
sys.stdin = open(infile, "r")
model_manager = ModelManagerService(config, logger)
events = EventServiceBase()
output_folder = config.output_path
# TODO: build a file/path manager?
if config.use_memory_db:
db_location = ":memory:"
else:
db_location = config.db_path
db_location.parent.mkdir(parents=True, exist_ok=True)
db_conn = sqlite3.connect(db_location, check_same_thread=False) # TODO: figure out a better threading solution
logger.info(f'InvokeAI database location is "{db_location}"')
graph_execution_manager = SqliteItemStorage[GraphExecutionState](conn=db_conn, table_name="graph_executions")
urls = LocalUrlService()
image_record_storage = SqliteImageRecordStorage(conn=db_conn)
image_file_storage = DiskImageFileStorage(f"{output_folder}/images")
names = SimpleNameService()
board_record_storage = SqliteBoardRecordStorage(conn=db_conn)
board_image_record_storage = SqliteBoardImageRecordStorage(conn=db_conn)
boards = BoardService(
services=BoardServiceDependencies(
board_image_record_storage=board_image_record_storage,
board_record_storage=board_record_storage,
image_record_storage=image_record_storage,
url=urls,
logger=logger,
)
)
board_images = BoardImagesService(
services=BoardImagesServiceDependencies(
board_image_record_storage=board_image_record_storage,
board_record_storage=board_record_storage,
image_record_storage=image_record_storage,
url=urls,
logger=logger,
)
)
images = ImageService(
services=ImageServiceDependencies(
board_image_record_storage=board_image_record_storage,
image_record_storage=image_record_storage,
image_file_storage=image_file_storage,
url=urls,
logger=logger,
names=names,
graph_execution_manager=graph_execution_manager,
)
)
services = InvocationServices(
model_manager=model_manager,
events=events,
latents=ForwardCacheLatentsStorage(DiskLatentsStorage(f"{output_folder}/latents")),
images=images,
boards=boards,
board_images=board_images,
queue=MemoryInvocationQueue(),
graph_library=SqliteItemStorage[LibraryGraph](conn=db_conn, table_name="graphs"),
graph_execution_manager=graph_execution_manager,
processor=DefaultInvocationProcessor(),
performance_statistics=InvocationStatsService(graph_execution_manager),
logger=logger,
configuration=config,
invocation_cache=MemoryInvocationCache(max_cache_size=config.node_cache_size),
)
system_graphs = create_system_graphs(services.graph_library)
system_graph_names = set([g.name for g in system_graphs])
set_autocompleter(services)
invoker = Invoker(services)
session: GraphExecutionState = invoker.create_execution_state()
parser = get_command_parser(services)
re_negid = re.compile("^-[0-9]+$")
# Uncomment to print out previous sessions at startup
# print(services.session_manager.list())
context = CliContext(invoker, session, parser)
set_autocompleter(services)
command_line_args_exist = len(invocation_commands) > 0
done = False
while not done:
try:
if command_line_args_exist:
cmd_input = invocation_commands.pop(0)
done = len(invocation_commands) == 0
else:
cmd_input = input("invoke> ")
except (KeyboardInterrupt, EOFError):
# Ctrl-c exits
break
try:
# Refresh the state of the session
# history = list(get_graph_execution_history(context.session))
history = list(reversed(context.nodes_added))
# Split the command for piping
cmds = cmd_input.split("|")
start_id = len(context.nodes_added)
current_id = start_id
new_invocations = list()
for cmd in cmds:
if cmd is None or cmd.strip() == "":
raise InvalidArgs("Empty command")
# Parse args to create invocation
args = vars(context.parser.parse_args(shlex.split(cmd.strip())))
# Override defaults
for field_name, field_default in context.defaults.items():
if field_name in args:
args[field_name] = field_default
# Parse invocation
command: CliCommand = None # type:ignore
system_graph: Optional[LibraryGraph] = None
if args["type"] in system_graph_names:
system_graph = next(filter(lambda g: g.name == args["type"], system_graphs))
invocation = GraphInvocation(graph=system_graph.graph, id=str(current_id))
for exposed_input in system_graph.exposed_inputs:
if exposed_input.alias in args:
node = invocation.graph.get_node(exposed_input.node_path)
field = exposed_input.field
setattr(node, field, args[exposed_input.alias])
command = CliCommand(command=invocation)
context.graph_nodes[invocation.id] = system_graph.id
else:
args["id"] = current_id
command = CliCommand(command=args)
if command is None:
continue
# Run any CLI commands immediately
if isinstance(command.command, BaseCommand):
# Invoke all current nodes to preserve operation order
invoke_all(context)
# Run the command
command.command.run(context)
continue
# TODO: handle linking with library graphs
# Pipe previous command output (if there was a previous command)
edges: list[Edge] = list()
if len(history) > 0 or current_id != start_id:
from_id = history[0] if current_id == start_id else str(current_id - 1)
from_node = (
next(filter(lambda n: n[0].id == from_id, new_invocations))[0]
if current_id != start_id
else context.session.graph.get_node(from_id)
)
matching_edges = generate_matching_edges(from_node, command.command, context)
edges.extend(matching_edges)
# Parse provided links
if "link_node" in args and args["link_node"]:
for link in args["link_node"]:
node_id = link
if re_negid.match(node_id):
node_id = str(current_id + int(node_id))
link_node = context.session.graph.get_node(node_id)
matching_edges = generate_matching_edges(link_node, command.command, context)
matching_destinations = [e.destination for e in matching_edges]
edges = [e for e in edges if e.destination not in matching_destinations]
edges.extend(matching_edges)
if "link" in args and args["link"]:
for link in args["link"]:
edges = [
e
for e in edges
if e.destination.node_id != command.command.id or e.destination.field != link[2]
]
node_id = link[0]
if re_negid.match(node_id):
node_id = str(current_id + int(node_id))
# TODO: handle missing input/output
node_output = get_node_outputs(context.session.graph.get_node(node_id), context)[link[1]]
node_input = get_node_inputs(command.command, context)[link[2]]
edges.append(
Edge(
source=EdgeConnection(node_id=node_output.node_path, field=node_output.field),
destination=EdgeConnection(node_id=node_input.node_path, field=node_input.field),
)
)
new_invocations.append((command.command, edges))
current_id = current_id + 1
# Add the node to the session
context.add_node(command.command)
for edge in edges:
print(edge)
context.add_edge(edge)
# Execute all remaining nodes
invoke_all(context)
except InvalidArgs:
invoker.services.logger.warning('Invalid command, use "help" to list commands')
continue
except ValidationError:
invoker.services.logger.warning('Invalid command arguments, run "<command> --help" for summary')
except SessionError:
# Start a new session
invoker.services.logger.warning("Session error: creating a new session")
context.reset()
except ExitCli:
break
except SystemExit:
continue
invoker.stop()
if __name__ == "__main__":
if config.version:
print(f"InvokeAI version {__version__}")
else:
invoke_cli()

View File

@@ -1,8 +1,28 @@
import os import shutil
import sys
from importlib.util import module_from_spec, spec_from_file_location
from pathlib import Path
__all__ = [] from invokeai.app.services.config.config_default import InvokeAIAppConfig
dirname = os.path.dirname(os.path.abspath(__file__)) custom_nodes_path = Path(InvokeAIAppConfig.get_config().custom_nodes_path.absolute())
for f in os.listdir(dirname): custom_nodes_path.mkdir(parents=True, exist_ok=True)
if f != "__init__.py" and os.path.isfile("%s/%s" % (dirname, f)) and f[-3:] == ".py":
__all__.append(f[:-3]) custom_nodes_init_path = str(custom_nodes_path / "__init__.py")
custom_nodes_readme_path = str(custom_nodes_path / "README.md")
# copy our custom nodes __init__.py to the custom nodes directory
shutil.copy(Path(__file__).parent / "custom_nodes/init.py", custom_nodes_init_path)
shutil.copy(Path(__file__).parent / "custom_nodes/README.md", custom_nodes_readme_path)
# Import custom nodes, see https://docs.python.org/3/library/importlib.html#importing-programmatically
spec = spec_from_file_location("custom_nodes", custom_nodes_init_path)
if spec is None or spec.loader is None:
raise RuntimeError(f"Could not load custom nodes from {custom_nodes_init_path}")
module = module_from_spec(spec)
sys.modules[spec.name] = module
spec.loader.exec_module(module)
# add core nodes to __all__
python_files = filter(lambda f: not f.name.startswith("_"), Path(__file__).parent.glob("*.py"))
__all__ = [f.stem for f in python_files] # type: ignore

View File

@@ -2,27 +2,22 @@
from __future__ import annotations from __future__ import annotations
import json import inspect
import re import re
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from enum import Enum from enum import Enum
from inspect import signature from inspect import signature
from types import UnionType from types import UnionType
from typing import TYPE_CHECKING, Any, Callable, ClassVar, Iterable, Literal, Optional, Protocol, Type, TypeVar, Union from typing import TYPE_CHECKING, Any, Callable, ClassVar, Iterable, Literal, Optional, Type, TypeVar, Union
import semver import semver
from PIL.Image import Image as ImageType from pydantic import BaseModel, ConfigDict, Field, RootModel, TypeAdapter, create_model
from pydantic import BaseModel, ConfigDict, Field, create_model, field_validator from pydantic.fields import FieldInfo, _Unset
from pydantic.fields import _Unset
from pydantic_core import PydanticUndefined from pydantic_core import PydanticUndefined
import torch
from invokeai.app.services.config.config_default import InvokeAIAppConfig from invokeai.app.services.config.config_default import InvokeAIAppConfig
from invokeai.app.services.image_records.image_records_common import ImageCategory, ResourceOrigin from invokeai.app.shared.fields import FieldDescriptions
from invokeai.app.services.invocation_processor.invocation_processor_common import ProgressImage
from invokeai.app.util.misc import uuid_string from invokeai.app.util.misc import uuid_string
from invokeai.backend.model_management.model_manager import ModelInfo
from invokeai.backend.model_management.models.base import BaseModelType, ModelType, SubModelType
if TYPE_CHECKING: if TYPE_CHECKING:
from ..services.invocation_services import InvocationServices from ..services.invocation_services import InvocationServices
@@ -32,63 +27,8 @@ class InvalidVersionError(ValueError):
pass pass
class FieldDescriptions: class InvalidFieldError(TypeError):
denoising_start = "When to start denoising, expressed a percentage of total steps" pass
denoising_end = "When to stop denoising, expressed a percentage of total steps"
cfg_scale = "Classifier-Free Guidance scale"
scheduler = "Scheduler to use during inference"
positive_cond = "Positive conditioning tensor"
negative_cond = "Negative conditioning tensor"
noise = "Noise tensor"
clip = "CLIP (tokenizer, text encoder, LoRAs) and skipped layer count"
unet = "UNet (scheduler, LoRAs)"
vae = "VAE"
cond = "Conditioning tensor"
controlnet_model = "ControlNet model to load"
vae_model = "VAE model to load"
lora_model = "LoRA model to load"
main_model = "Main model (UNet, VAE, CLIP) to load"
sdxl_main_model = "SDXL Main model (UNet, VAE, CLIP1, CLIP2) to load"
sdxl_refiner_model = "SDXL Refiner Main Modde (UNet, VAE, CLIP2) to load"
onnx_main_model = "ONNX Main model (UNet, VAE, CLIP) to load"
lora_weight = "The weight at which the LoRA is applied to each model"
compel_prompt = "Prompt to be parsed by Compel to create a conditioning tensor"
raw_prompt = "Raw prompt text (no parsing)"
sdxl_aesthetic = "The aesthetic score to apply to the conditioning tensor"
skipped_layers = "Number of layers to skip in text encoder"
seed = "Seed for random number generation"
steps = "Number of steps to run"
width = "Width of output (px)"
height = "Height of output (px)"
control = "ControlNet(s) to apply"
ip_adapter = "IP-Adapter to apply"
t2i_adapter = "T2I-Adapter(s) to apply"
denoised_latents = "Denoised latents tensor"
latents = "Latents tensor"
strength = "Strength of denoising (proportional to steps)"
core_metadata = "Optional core metadata to be written to image"
interp_mode = "Interpolation mode"
torch_antialias = "Whether or not to apply antialiasing (bilinear or bicubic only)"
fp32 = "Whether or not to use full float32 precision"
precision = "Precision to use"
tiled = "Processing using overlapping tiles (reduce memory consumption)"
detect_res = "Pixel resolution for detection"
image_res = "Pixel resolution for output image"
safe_mode = "Whether or not to use safe mode"
scribble_mode = "Whether or not to use scribble mode"
scale_factor = "The factor by which to scale"
blend_alpha = (
"Blending factor. 0.0 = use input A only, 1.0 = use input B only, 0.5 = 50% mix of input A and input B."
)
num_1 = "The first number"
num_2 = "The second number"
mask = "The mask to use for the operation"
board = "The board to save the image to"
image = "The image to process"
tile_size = "Tile size"
inclusive_low = "The inclusive low value"
exclusive_high = "The exclusive high value"
decimal_places = "The number of decimal places to round to"
class Input(str, Enum): class Input(str, Enum):
@@ -173,8 +113,12 @@ class UIType(str, Enum):
Scheduler = "Scheduler" Scheduler = "Scheduler"
WorkflowField = "WorkflowField" WorkflowField = "WorkflowField"
IsIntermediate = "IsIntermediate" IsIntermediate = "IsIntermediate"
MetadataField = "MetadataField"
BoardField = "BoardField" BoardField = "BoardField"
Any = "Any"
MetadataItem = "MetadataItem"
MetadataItemCollection = "MetadataItemCollection"
MetadataItemPolymorphic = "MetadataItemPolymorphic"
MetadataDict = "MetadataDict"
# endregion # endregion
@@ -292,34 +236,35 @@ def InputField(
Ignored for non-collection fields. Ignored for non-collection fields.
""" """
json_schema_extra_: dict[str, Any] = dict( json_schema_extra_: dict[str, Any] = {
input=input, "input": input,
ui_type=ui_type, "ui_type": ui_type,
ui_component=ui_component, "ui_component": ui_component,
ui_hidden=ui_hidden, "ui_hidden": ui_hidden,
ui_order=ui_order, "ui_order": ui_order,
item_default=item_default, "item_default": item_default,
ui_choice_labels=ui_choice_labels, "ui_choice_labels": ui_choice_labels,
) "_field_kind": "input",
}
field_args = dict( field_args = {
default=default, "default": default,
default_factory=default_factory, "default_factory": default_factory,
title=title, "title": title,
description=description, "description": description,
pattern=pattern, "pattern": pattern,
strict=strict, "strict": strict,
gt=gt, "gt": gt,
ge=ge, "ge": ge,
lt=lt, "lt": lt,
le=le, "le": le,
multiple_of=multiple_of, "multiple_of": multiple_of,
allow_inf_nan=allow_inf_nan, "allow_inf_nan": allow_inf_nan,
max_digits=max_digits, "max_digits": max_digits,
decimal_places=decimal_places, "decimal_places": decimal_places,
min_length=min_length, "min_length": min_length,
max_length=max_length, "max_length": max_length,
) }
""" """
Invocation definitions have their fields typed correctly for their `invoke()` functions. Invocation definitions have their fields typed correctly for their `invoke()` functions.
@@ -354,24 +299,24 @@ def InputField(
# because we are manually making fields optional, we need to store the original required bool for reference later # because we are manually making fields optional, we need to store the original required bool for reference later
if default is PydanticUndefined and default_factory is PydanticUndefined: if default is PydanticUndefined and default_factory is PydanticUndefined:
json_schema_extra_.update(dict(orig_required=True)) json_schema_extra_.update({"orig_required": True})
else: else:
json_schema_extra_.update(dict(orig_required=False)) json_schema_extra_.update({"orig_required": False})
# make Input.Any and Input.Connection fields optional, providing None as a default if the field doesn't already have one # make Input.Any and Input.Connection fields optional, providing None as a default if the field doesn't already have one
if (input is Input.Any or input is Input.Connection) and default_factory is PydanticUndefined: if (input is Input.Any or input is Input.Connection) and default_factory is PydanticUndefined:
default_ = None if default is PydanticUndefined else default default_ = None if default is PydanticUndefined else default
provided_args.update(dict(default=default_)) provided_args.update({"default": default_})
if default is not PydanticUndefined: if default is not PydanticUndefined:
# before invoking, we'll grab the original default value and set it on the field if the field wasn't provided a value # before invoking, we'll grab the original default value and set it on the field if the field wasn't provided a value
json_schema_extra_.update(dict(default=default)) json_schema_extra_.update({"default": default})
json_schema_extra_.update(dict(orig_default=default)) json_schema_extra_.update({"orig_default": default})
elif default is not PydanticUndefined and default_factory is PydanticUndefined: elif default is not PydanticUndefined and default_factory is PydanticUndefined:
default_ = default default_ = default
provided_args.update(dict(default=default_)) provided_args.update({"default": default_})
json_schema_extra_.update(dict(orig_default=default_)) json_schema_extra_.update({"orig_default": default_})
elif default_factory is not PydanticUndefined: elif default_factory is not PydanticUndefined:
provided_args.update(dict(default_factory=default_factory)) provided_args.update({"default_factory": default_factory})
# TODO: cannot serialize default_factory... # TODO: cannot serialize default_factory...
# json_schema_extra_.update(dict(orig_default_factory=default_factory)) # json_schema_extra_.update(dict(orig_default_factory=default_factory))
@@ -438,11 +383,12 @@ def OutputField(
decimal_places=decimal_places, decimal_places=decimal_places,
min_length=min_length, min_length=min_length,
max_length=max_length, max_length=max_length,
json_schema_extra=dict( json_schema_extra={
ui_type=ui_type, "ui_type": ui_type,
ui_hidden=ui_hidden, "ui_hidden": ui_hidden,
ui_order=ui_order, "ui_order": ui_order,
), "_field_kind": "output",
},
) )
@@ -466,123 +412,7 @@ class UIConfigBase(BaseModel):
) )
class GetImage(Protocol):
def __call__(self, name: str) -> ImageType:
...
class SaveImage(Protocol):
def __call__(self, image: ImageType, category: ImageCategory = ImageCategory.GENERAL) -> str:
...
class GetLatents(Protocol):
def __call__(self, name: str) -> torch.Tensor:
...
class SaveLatents(Protocol):
def __call__(self, latents: torch.Tensor) -> str:
...
class GetConditioning(Protocol):
def __call__(self, name: str) -> torch.Tensor:
...
class SaveConditioning(Protocol):
def __call__(self, conditioning: torch.Tensor) -> str:
...
class IsCanceled(Protocol):
def __call__(self) -> bool:
...
class EmitDenoisingProgress(Protocol):
def __call__(self, progress_image: ProgressImage, step: int, order: int, total_steps: int) -> None:
...
class GetModel(Protocol):
def __call__(
self,
model_name: str,
base_model: BaseModelType,
model_type: ModelType,
submodel: Optional[SubModelType] = None,
) -> ModelInfo:
...
class ModelExists(Protocol):
def __call__(
self,
model_name: str,
base_model: BaseModelType,
model_type: ModelType,
) -> bool:
...
class InvocationContext: class InvocationContext:
def __init__(
self,
# context
queue_id: str,
queue_item_id: int,
queue_batch_id: str,
graph_execution_state_id: str,
source_node_id: str,
# methods
get_image: GetImage,
save_image: SaveImage,
get_latents: GetLatents,
save_latents: SaveLatents,
get_conditioning: GetConditioning,
save_conditioning: SaveConditioning,
is_canceled: IsCanceled,
get_model: GetModel,
emit_denoising_progress: EmitDenoisingProgress,
model_exists: ModelExists,
# services
config: InvokeAIAppConfig,
) -> None:
# context
self.queue_id = queue_id
self.queue_item_id = queue_item_id
self.queue_batch_id = queue_batch_id
self.graph_execution_state_id = graph_execution_state_id
self.source_node_id = source_node_id
# resource methods
self.get_image = get_image
self.save_image = save_image
self.get_latents = get_latents
self.save_latents = save_latents
self.get_conditioning = get_conditioning
self.save_conditioning = save_conditioning
# execution state
self.is_canceled = is_canceled
# models
self.get_model = get_model
self.model_exists = model_exists
# events
self.emit_denoising_progress = emit_denoising_progress
# services
self.config = config
# misc
self.categories = ImageCategory
class AppInvocationContext:
"""Initialized and provided to on execution of invocations.""" """Initialized and provided to on execution of invocations."""
services: InvocationServices services: InvocationServices
@@ -590,7 +420,6 @@ class AppInvocationContext:
queue_id: str queue_id: str
queue_item_id: int queue_item_id: int
queue_batch_id: str queue_batch_id: str
source_node_id: str
def __init__( def __init__(
self, self,
@@ -599,113 +428,12 @@ class AppInvocationContext:
queue_item_id: int, queue_item_id: int,
queue_batch_id: str, queue_batch_id: str,
graph_execution_state_id: str, graph_execution_state_id: str,
source_node_id: str,
): ):
self.services = services self.services = services
self.graph_execution_state_id = graph_execution_state_id self.graph_execution_state_id = graph_execution_state_id
self.queue_id = queue_id self.queue_id = queue_id
self.queue_item_id = queue_item_id self.queue_item_id = queue_item_id
self.queue_batch_id = queue_batch_id self.queue_batch_id = queue_batch_id
self.source_node_id = source_node_id
def get_restricted_context(self, invocation: BaseInvocation) -> InvocationContext:
def get_image(name: str) -> ImageType:
return self.services.images.get_pil_image(name)
def save_image(image: ImageType, category: ImageCategory = ImageCategory.GENERAL) -> str:
metadata = getattr(invocation, "metadata")
workflow = getattr(invocation, "workflow")
image_dto = self.services.images.create(
image=image,
image_origin=ResourceOrigin.INTERNAL,
image_category=category,
session_id=self.graph_execution_state_id,
node_id=invocation.id,
is_intermediate=invocation.is_intermediate,
metadata=metadata.model_dump() if metadata else None,
workflow=workflow,
)
return image_dto.image_name
def get_latents(name: str) -> torch.Tensor:
return self.services.latents.get(name)
def save_latents(latents: torch.Tensor) -> str:
name = f"{self.graph_execution_state_id}__{invocation.id}"
self.services.latents.save(name=name, data=latents)
return name
def get_conditioning(name: str) -> torch.Tensor:
return self.services.latents.get(name)
def save_conditioning(conditioning: torch.Tensor) -> str:
name = f"{self.graph_execution_state_id}__{invocation.id}_conditioning"
self.services.latents.save(name=name, data=conditioning)
return name
def is_canceled() -> bool:
return self.services.queue.is_canceled(self.graph_execution_state_id)
def get_model(
model_name: str,
base_model: BaseModelType,
model_type: ModelType,
submodel: Optional[SubModelType] = None,
) -> ModelInfo:
return self.services.model_manager.get_model(
model_name=model_name,
base_model=base_model,
model_type=model_type,
submodel=submodel,
queue_id=self.queue_id,
queue_item_id=self.queue_item_id,
queue_batch_id=self.queue_batch_id,
graph_execution_state_id=self.graph_execution_state_id,
)
def model_exists(
model_name: str,
base_model: BaseModelType,
model_type: ModelType,
) -> bool:
return self.services.model_manager.model_exists(model_name, base_model, model_type)
def emit_denoising_progress(progress_image: ProgressImage, step: int, order: int, total_steps: int) -> None:
self.services.events.emit_generator_progress(
queue_id=self.queue_id,
queue_item_id=self.queue_item_id,
queue_batch_id=self.queue_batch_id,
graph_execution_state_id=self.graph_execution_state_id,
node=invocation.model_dump(),
source_node_id=self.source_node_id,
progress_image=progress_image,
step=step,
order=order,
total_steps=total_steps,
)
return InvocationContext(
# context
queue_id=self.queue_id,
queue_item_id=self.queue_item_id,
queue_batch_id=self.queue_batch_id,
graph_execution_state_id=self.graph_execution_state_id,
source_node_id=self.source_node_id,
# methods
get_image=get_image,
save_image=save_image,
get_latents=get_latents,
save_latents=save_latents,
get_conditioning=get_conditioning,
save_conditioning=save_conditioning,
is_canceled=is_canceled,
emit_denoising_progress=emit_denoising_progress,
get_model=get_model,
model_exists=model_exists,
# services
config=self.services.configuration,
)
class BaseInvocationOutput(BaseModel): class BaseInvocationOutput(BaseModel):
@@ -732,17 +460,18 @@ class BaseInvocationOutput(BaseModel):
@classmethod @classmethod
def get_output_types(cls) -> Iterable[str]: def get_output_types(cls) -> Iterable[str]:
return map(lambda i: get_type(i), BaseInvocationOutput.get_outputs()) return (get_type(i) for i in BaseInvocationOutput.get_outputs())
@staticmethod @staticmethod
def json_schema_extra(schema: dict[str, Any], model_class: Type[BaseModel]) -> None: def json_schema_extra(schema: dict[str, Any], model_class: Type[BaseModel]) -> None:
# Because we use a pydantic Literal field with default value for the invocation type, # Because we use a pydantic Literal field with default value for the invocation type,
# it will be typed as optional in the OpenAPI schema. Make it required manually. # it will be typed as optional in the OpenAPI schema. Make it required manually.
if "required" not in schema or not isinstance(schema["required"], list): if "required" not in schema or not isinstance(schema["required"], list):
schema["required"] = list() schema["required"] = []
schema["required"].extend(["type"]) schema["required"].extend(["type"])
model_config = ConfigDict( model_config = ConfigDict(
protected_namespaces=(),
validate_assignment=True, validate_assignment=True,
json_schema_serialization_defaults_required=True, json_schema_serialization_defaults_required=True,
json_schema_extra=json_schema_extra, json_schema_extra=json_schema_extra,
@@ -765,9 +494,6 @@ class MissingInputException(Exception):
class BaseInvocation(ABC, BaseModel): class BaseInvocation(ABC, BaseModel):
""" """
A node to process inputs and produce outputs.
May use dependency injection in __init__ to receive providers.
All invocations must use the `@invocation` decorator to provide their unique type. All invocations must use the `@invocation` decorator to provide their unique type.
""" """
@@ -801,16 +527,11 @@ class BaseInvocation(ABC, BaseModel):
@classmethod @classmethod
def get_invocations_map(cls) -> dict[str, BaseInvocation]: def get_invocations_map(cls) -> dict[str, BaseInvocation]:
# Get the type strings out of the literals and into a dictionary # Get the type strings out of the literals and into a dictionary
return dict( return {get_type(i): i for i in BaseInvocation.get_invocations()}
map(
lambda i: (get_type(i), i),
BaseInvocation.get_invocations(),
)
)
@classmethod @classmethod
def get_invocation_types(cls) -> Iterable[str]: def get_invocation_types(cls) -> Iterable[str]:
return map(lambda i: get_type(i), BaseInvocation.get_invocations()) return (get_type(i) for i in BaseInvocation.get_invocations())
@classmethod @classmethod
def get_output_type(cls) -> BaseInvocationOutput: def get_output_type(cls) -> BaseInvocationOutput:
@@ -829,7 +550,7 @@ class BaseInvocation(ABC, BaseModel):
if uiconfig and hasattr(uiconfig, "version"): if uiconfig and hasattr(uiconfig, "version"):
schema["version"] = uiconfig.version schema["version"] = uiconfig.version
if "required" not in schema or not isinstance(schema["required"], list): if "required" not in schema or not isinstance(schema["required"], list):
schema["required"] = list() schema["required"] = []
schema["required"].extend(["type", "id"]) schema["required"].extend(["type", "id"])
@abstractmethod @abstractmethod
@@ -837,7 +558,7 @@ class BaseInvocation(ABC, BaseModel):
"""Invoke with provided context and return outputs.""" """Invoke with provided context and return outputs."""
pass pass
def invoke_internal(self, context: AppInvocationContext) -> BaseInvocationOutput: def invoke_internal(self, context: InvocationContext) -> BaseInvocationOutput:
for field_name, field in self.model_fields.items(): for field_name, field in self.model_fields.items():
if not field.json_schema_extra or callable(field.json_schema_extra): if not field.json_schema_extra or callable(field.json_schema_extra):
# something has gone terribly awry, we should always have this and it should be a dict # something has gone terribly awry, we should always have this and it should be a dict
@@ -859,7 +580,7 @@ class BaseInvocation(ABC, BaseModel):
# skip node cache codepath if it's disabled # skip node cache codepath if it's disabled
if context.services.configuration.node_cache_size == 0: if context.services.configuration.node_cache_size == 0:
return self.invoke(context.get_restricted_context(invocation=self)) return self.invoke(context)
output: BaseInvocationOutput output: BaseInvocationOutput
if self.use_cache: if self.use_cache:
@@ -867,7 +588,7 @@ class BaseInvocation(ABC, BaseModel):
cached_value = context.services.invocation_cache.get(key) cached_value = context.services.invocation_cache.get(key)
if cached_value is None: if cached_value is None:
context.services.logger.debug(f'Invocation cache miss for type "{self.get_type()}": {self.id}') context.services.logger.debug(f'Invocation cache miss for type "{self.get_type()}": {self.id}')
output = self.invoke(context.get_restricted_context(invocation=self)) output = self.invoke(context)
context.services.invocation_cache.save(key, output) context.services.invocation_cache.save(key, output)
return output return output
else: else:
@@ -875,7 +596,7 @@ class BaseInvocation(ABC, BaseModel):
return cached_value return cached_value
else: else:
context.services.logger.debug(f'Skipping invocation cache for "{self.get_type()}": {self.id}') context.services.logger.debug(f'Skipping invocation cache for "{self.get_type()}": {self.id}')
return self.invoke(context.get_restricted_context(invocation=self)) return self.invoke(context)
def get_type(self) -> str: def get_type(self) -> str:
return self.model_fields["type"].default return self.model_fields["type"].default
@@ -883,46 +604,91 @@ class BaseInvocation(ABC, BaseModel):
id: str = Field( id: str = Field(
default_factory=uuid_string, default_factory=uuid_string,
description="The id of this instance of an invocation. Must be unique among all instances of invocations.", description="The id of this instance of an invocation. Must be unique among all instances of invocations.",
json_schema_extra={"_field_kind": "internal"},
) )
is_intermediate: Optional[bool] = Field( is_intermediate: bool = Field(
default=False, default=False,
description="Whether or not this is an intermediate invocation.", description="Whether or not this is an intermediate invocation.",
json_schema_extra=dict(ui_type=UIType.IsIntermediate), json_schema_extra={"ui_type": UIType.IsIntermediate, "_field_kind": "internal"},
) )
workflow: Optional[str] = Field( use_cache: bool = Field(
default=None, default=True, description="Whether or not to use the cache", json_schema_extra={"_field_kind": "internal"}
description="The workflow to save with the image",
json_schema_extra=dict(ui_type=UIType.WorkflowField),
) )
use_cache: Optional[bool] = Field(
default=True,
description="Whether or not to use the cache",
)
@field_validator("workflow", mode="before")
@classmethod
def validate_workflow_is_json(cls, v):
"""We don't have a workflow schema in the backend, so we just check that it's valid JSON"""
if v is None:
return None
try:
json.loads(v)
except json.decoder.JSONDecodeError:
raise ValueError("Workflow must be valid JSON")
return v
UIConfig: ClassVar[Type[UIConfigBase]] UIConfig: ClassVar[Type[UIConfigBase]]
model_config = ConfigDict( model_config = ConfigDict(
protected_namespaces=(),
validate_assignment=True, validate_assignment=True,
json_schema_extra=json_schema_extra, json_schema_extra=json_schema_extra,
json_schema_serialization_defaults_required=True, json_schema_serialization_defaults_required=True,
coerce_numbers_to_str=True,
) )
TBaseInvocation = TypeVar("TBaseInvocation", bound=BaseInvocation) TBaseInvocation = TypeVar("TBaseInvocation", bound=BaseInvocation)
RESERVED_INPUT_FIELD_NAMES = {
"id",
"is_intermediate",
"use_cache",
"type",
"workflow",
"metadata",
}
RESERVED_OUTPUT_FIELD_NAMES = {"type"}
class _Model(BaseModel):
pass
# Get all pydantic model attrs, methods, etc
RESERVED_PYDANTIC_FIELD_NAMES = {m[0] for m in inspect.getmembers(_Model())}
def validate_fields(model_fields: dict[str, FieldInfo], model_type: str) -> None:
"""
Validates the fields of an invocation or invocation output:
- must not override any pydantic reserved fields
- must be created via `InputField`, `OutputField`, or be an internal field defined in this file
"""
for name, field in model_fields.items():
if name in RESERVED_PYDANTIC_FIELD_NAMES:
raise InvalidFieldError(f'Invalid field name "{name}" on "{model_type}" (reserved by pydantic)')
field_kind = (
# _field_kind is defined via InputField(), OutputField() or by one of the internal fields defined in this file
field.json_schema_extra.get("_field_kind", None) if field.json_schema_extra else None
)
# must have a field_kind
if field_kind is None or field_kind not in {"input", "output", "internal"}:
raise InvalidFieldError(
f'Invalid field definition for "{name}" on "{model_type}" (maybe it\'s not an InputField or OutputField?)'
)
if field_kind == "input" and name in RESERVED_INPUT_FIELD_NAMES:
raise InvalidFieldError(f'Invalid field name "{name}" on "{model_type}" (reserved input field name)')
if field_kind == "output" and name in RESERVED_OUTPUT_FIELD_NAMES:
raise InvalidFieldError(f'Invalid field name "{name}" on "{model_type}" (reserved output field name)')
# internal fields *must* be in the reserved list
if (
field_kind == "internal"
and name not in RESERVED_INPUT_FIELD_NAMES
and name not in RESERVED_OUTPUT_FIELD_NAMES
):
raise InvalidFieldError(
f'Invalid field name "{name}" on "{model_type}" (internal field without reserved name)'
)
return None
def invocation( def invocation(
invocation_type: str, invocation_type: str,
title: Optional[str] = None, title: Optional[str] = None,
@@ -932,7 +698,7 @@ def invocation(
use_cache: Optional[bool] = True, use_cache: Optional[bool] = True,
) -> Callable[[Type[TBaseInvocation]], Type[TBaseInvocation]]: ) -> Callable[[Type[TBaseInvocation]], Type[TBaseInvocation]]:
""" """
Adds metadata to an invocation. Registers an invocation.
:param str invocation_type: The type of the invocation. Must be unique among all invocations. :param str invocation_type: The type of the invocation. Must be unique among all invocations.
:param Optional[str] title: Adds a title to the invocation. Use if the auto-generated title isn't quite right. Defaults to None. :param Optional[str] title: Adds a title to the invocation. Use if the auto-generated title isn't quite right. Defaults to None.
@@ -951,10 +717,12 @@ def invocation(
if invocation_type in BaseInvocation.get_invocation_types(): if invocation_type in BaseInvocation.get_invocation_types():
raise ValueError(f'Invocation type "{invocation_type}" already exists') raise ValueError(f'Invocation type "{invocation_type}" already exists')
validate_fields(cls.model_fields, invocation_type)
# Add OpenAPI schema extras # Add OpenAPI schema extras
uiconf_name = cls.__qualname__ + ".UIConfig" uiconf_name = cls.__qualname__ + ".UIConfig"
if not hasattr(cls, "UIConfig") or cls.UIConfig.__qualname__ != uiconf_name: if not hasattr(cls, "UIConfig") or cls.UIConfig.__qualname__ != uiconf_name:
cls.UIConfig = type(uiconf_name, (UIConfigBase,), dict()) cls.UIConfig = type(uiconf_name, (UIConfigBase,), {})
if title is not None: if title is not None:
cls.UIConfig.title = title cls.UIConfig.title = title
if tags is not None: if tags is not None:
@@ -981,8 +749,7 @@ def invocation(
invocation_type_annotation = Literal[invocation_type] # type: ignore invocation_type_annotation = Literal[invocation_type] # type: ignore
invocation_type_field = Field( invocation_type_field = Field(
title="type", title="type", default=invocation_type, json_schema_extra={"_field_kind": "internal"}
default=invocation_type,
) )
docstring = cls.__doc__ docstring = cls.__doc__
@@ -1023,13 +790,12 @@ def invocation_output(
if output_type in BaseInvocationOutput.get_output_types(): if output_type in BaseInvocationOutput.get_output_types():
raise ValueError(f'Invocation type "{output_type}" already exists') raise ValueError(f'Invocation type "{output_type}" already exists')
validate_fields(cls.model_fields, output_type)
# Add the output type to the model. # Add the output type to the model.
output_type_annotation = Literal[output_type] # type: ignore output_type_annotation = Literal[output_type] # type: ignore
output_type_field = Field( output_type_field = Field(title="type", default=output_type, json_schema_extra={"_field_kind": "internal"})
title="type",
default=output_type,
)
docstring = cls.__doc__ docstring = cls.__doc__
cls = create_model( cls = create_model(
@@ -1047,4 +813,37 @@ def invocation_output(
return wrapper return wrapper
GenericBaseModel = TypeVar("GenericBaseModel", bound=BaseModel) class WorkflowField(RootModel):
"""
Pydantic model for workflows with custom root of type dict[str, Any].
Workflows are stored without a strict schema.
"""
root: dict[str, Any] = Field(description="The workflow")
WorkflowFieldValidator = TypeAdapter(WorkflowField)
class WithWorkflow(BaseModel):
workflow: Optional[WorkflowField] = Field(
default=None, description=FieldDescriptions.workflow, json_schema_extra={"_field_kind": "internal"}
)
class MetadataField(RootModel):
"""
Pydantic model for metadata with custom root of type dict[str, Any].
Metadata is stored without a strict schema.
"""
root: dict[str, Any] = Field(description="The metadata")
MetadataFieldValidator = TypeAdapter(MetadataField)
class WithMetadata(BaseModel):
metadata: Optional[MetadataField] = Field(
default=None, description=FieldDescriptions.metadata, json_schema_extra={"_field_kind": "internal"}
)

View File

@@ -7,6 +7,7 @@ from compel import Compel, ReturnedEmbeddingsType
from compel.prompt_parser import Blend, Conjunction, CrossAttentionControlSubstitute, FlattenedPrompt, Fragment from compel.prompt_parser import Blend, Conjunction, CrossAttentionControlSubstitute, FlattenedPrompt, Fragment
from invokeai.app.invocations.primitives import ConditioningField, ConditioningOutput from invokeai.app.invocations.primitives import ConditioningField, ConditioningOutput
from invokeai.app.shared.fields import FieldDescriptions
from invokeai.backend.stable_diffusion.diffusion.conditioning_data import ( from invokeai.backend.stable_diffusion.diffusion.conditioning_data import (
BasicConditioningInfo, BasicConditioningInfo,
ExtraConditioningInfo, ExtraConditioningInfo,
@@ -19,7 +20,6 @@ from ...backend.util.devices import torch_dtype
from .baseinvocation import ( from .baseinvocation import (
BaseInvocation, BaseInvocation,
BaseInvocationOutput, BaseInvocationOutput,
FieldDescriptions,
Input, Input,
InputField, InputField,
InvocationContext, InvocationContext,
@@ -66,21 +66,25 @@ class CompelInvocation(BaseInvocation):
@torch.no_grad() @torch.no_grad()
def invoke(self, context: InvocationContext) -> ConditioningOutput: def invoke(self, context: InvocationContext) -> ConditioningOutput:
tokenizer_info = context.get_model( tokenizer_info = context.services.model_manager.get_model(
**self.clip.tokenizer.model_dump(), **self.clip.tokenizer.model_dump(),
context=context,
) )
text_encoder_info = context.get_model( text_encoder_info = context.services.model_manager.get_model(
**self.clip.text_encoder.model_dump(), **self.clip.text_encoder.model_dump(),
context=context,
) )
def _lora_loader(): def _lora_loader():
for lora in self.clip.loras: for lora in self.clip.loras:
lora_info = context.get_model(**lora.model_dump(exclude={"weight"})) lora_info = context.services.model_manager.get_model(
**lora.model_dump(exclude={"weight"}), context=context
)
yield (lora_info.context.model, lora.weight) yield (lora_info.context.model, lora.weight)
del lora_info del lora_info
return return
# loras = [(context.get_model(**lora.dict(exclude={"weight"})).context.model, lora.weight) for lora in self.clip.loras] # loras = [(context.services.model_manager.get_model(**lora.dict(exclude={"weight"})).context.model, lora.weight) for lora in self.clip.loras]
ti_list = [] ti_list = []
for trigger in re.findall(r"<[a-zA-Z0-9., _-]+>", self.prompt): for trigger in re.findall(r"<[a-zA-Z0-9., _-]+>", self.prompt):
@@ -89,10 +93,11 @@ class CompelInvocation(BaseInvocation):
ti_list.append( ti_list.append(
( (
name, name,
context.get_model( context.services.model_manager.get_model(
model_name=name, model_name=name,
base_model=self.clip.text_encoder.base_model, base_model=self.clip.text_encoder.base_model,
model_type=ModelType.TextualInversion, model_type=ModelType.TextualInversion,
context=context,
).context.model, ).context.model,
) )
) )
@@ -103,13 +108,15 @@ class CompelInvocation(BaseInvocation):
print(f'Warn: trigger: "{trigger}" not found') print(f'Warn: trigger: "{trigger}" not found')
with ( with (
ModelPatcher.apply_lora_text_encoder(text_encoder_info.context.model, _lora_loader()),
ModelPatcher.apply_ti(tokenizer_info.context.model, text_encoder_info.context.model, ti_list) as ( ModelPatcher.apply_ti(tokenizer_info.context.model, text_encoder_info.context.model, ti_list) as (
tokenizer, tokenizer,
ti_manager, ti_manager,
), ),
ModelPatcher.apply_clip_skip(text_encoder_info.context.model, self.clip.skipped_layers),
text_encoder_info as text_encoder, text_encoder_info as text_encoder,
# Apply the LoRA after text_encoder has been moved to its target device for faster patching.
ModelPatcher.apply_lora_text_encoder(text_encoder, _lora_loader()),
# Apply CLIP Skip after LoRA to prevent LoRA application from failing on skipped layers.
ModelPatcher.apply_clip_skip(text_encoder_info.context.model, self.clip.skipped_layers),
): ):
compel = Compel( compel = Compel(
tokenizer=tokenizer, tokenizer=tokenizer,
@@ -121,7 +128,7 @@ class CompelInvocation(BaseInvocation):
conjunction = Compel.parse_prompt_string(self.prompt) conjunction = Compel.parse_prompt_string(self.prompt)
if context.config.log_tokenization: if context.services.configuration.log_tokenization:
log_tokenization_for_conjunction(conjunction, tokenizer) log_tokenization_for_conjunction(conjunction, tokenizer)
c, options = compel.build_conditioning_tensor_for_conjunction(conjunction) c, options = compel.build_conditioning_tensor_for_conjunction(conjunction)
@@ -142,7 +149,8 @@ class CompelInvocation(BaseInvocation):
] ]
) )
conditioning_name = context.save_conditioning(conditioning_data) conditioning_name = f"{context.graph_execution_state_id}_{self.id}_conditioning"
context.services.latents.save(conditioning_name, conditioning_data)
return ConditioningOutput( return ConditioningOutput(
conditioning=ConditioningField( conditioning=ConditioningField(
@@ -223,13 +231,15 @@ class SDXLPromptInvocationBase:
print(f'Warn: trigger: "{trigger}" not found') print(f'Warn: trigger: "{trigger}" not found')
with ( with (
ModelPatcher.apply_lora(text_encoder_info.context.model, _lora_loader(), lora_prefix),
ModelPatcher.apply_ti(tokenizer_info.context.model, text_encoder_info.context.model, ti_list) as ( ModelPatcher.apply_ti(tokenizer_info.context.model, text_encoder_info.context.model, ti_list) as (
tokenizer, tokenizer,
ti_manager, ti_manager,
), ),
ModelPatcher.apply_clip_skip(text_encoder_info.context.model, clip_field.skipped_layers),
text_encoder_info as text_encoder, text_encoder_info as text_encoder,
# Apply the LoRA after text_encoder has been moved to its target device for faster patching.
ModelPatcher.apply_lora(text_encoder, _lora_loader(), lora_prefix),
# Apply CLIP Skip after LoRA to prevent LoRA application from failing on skipped layers.
ModelPatcher.apply_clip_skip(text_encoder_info.context.model, clip_field.skipped_layers),
): ):
compel = Compel( compel = Compel(
tokenizer=tokenizer, tokenizer=tokenizer,

View File

@@ -28,16 +28,18 @@ from pydantic import BaseModel, ConfigDict, Field, field_validator
from invokeai.app.invocations.primitives import ImageField, ImageOutput from invokeai.app.invocations.primitives import ImageField, ImageOutput
from invokeai.app.services.image_records.image_records_common import ImageCategory, ResourceOrigin from invokeai.app.services.image_records.image_records_common import ImageCategory, ResourceOrigin
from invokeai.app.shared.fields import FieldDescriptions
from ...backend.model_management import BaseModelType from ...backend.model_management import BaseModelType
from .baseinvocation import ( from .baseinvocation import (
BaseInvocation, BaseInvocation,
BaseInvocationOutput, BaseInvocationOutput,
FieldDescriptions,
Input, Input,
InputField, InputField,
InvocationContext, InvocationContext,
OutputField, OutputField,
WithMetadata,
WithWorkflow,
invocation, invocation,
invocation_output, invocation_output,
) )
@@ -127,12 +129,12 @@ class ControlNetInvocation(BaseInvocation):
# This invocation exists for other invocations to subclass it - do not register with @invocation! # This invocation exists for other invocations to subclass it - do not register with @invocation!
class ImageProcessorInvocation(BaseInvocation): class ImageProcessorInvocation(BaseInvocation, WithMetadata, WithWorkflow):
"""Base class for invocations that preprocess images for ControlNet""" """Base class for invocations that preprocess images for ControlNet"""
image: ImageField = InputField(description="The image to process") image: ImageField = InputField(description="The image to process")
def run_processor(self, image): def run_processor(self, image: Image.Image) -> Image.Image:
# superclass just passes through image without processing # superclass just passes through image without processing
return image return image
@@ -150,6 +152,7 @@ class ImageProcessorInvocation(BaseInvocation):
session_id=context.graph_execution_state_id, session_id=context.graph_execution_state_id,
node_id=self.id, node_id=self.id,
is_intermediate=self.is_intermediate, is_intermediate=self.is_intermediate,
metadata=self.metadata,
workflow=self.workflow, workflow=self.workflow,
) )

View File

@@ -0,0 +1,51 @@
# Custom Nodes / Node Packs
Copy your node packs to this directory.
When nodes are added or changed, you must restart the app to see the changes.
## Directory Structure
For a node pack to be loaded, it must be placed in a directory alongside this
file. Here's an example structure:
```py
.
__init__.py # Invoke-managed custom node loader
cool_node
__init__.py # see example below
cool_node.py
my_node_pack
__init__.py # see example below
tasty_node.py
bodacious_node.py
utils.py
extra_nodes
fancy_node.py
```
## Node Pack `__init__.py`
Each node pack must have an `__init__.py` file that imports its nodes.
The structure of each node or node pack is otherwise not important.
Here are examples, based on the example directory structure.
### `cool_node/__init__.py`
```py
from .cool_node import CoolInvocation
```
### `my_node_pack/__init__.py`
```py
from .tasty_node import TastyInvocation
from .bodacious_node import BodaciousInvocation
from .extra_nodes.fancy_node import FancyInvocation
```
Only nodes imported in the `__init__.py` file are loaded.

View File

@@ -0,0 +1,51 @@
"""
Invoke-managed custom node loader. See README.md for more information.
"""
import sys
from importlib.util import module_from_spec, spec_from_file_location
from pathlib import Path
from invokeai.backend.util.logging import InvokeAILogger
logger = InvokeAILogger.get_logger()
loaded_count = 0
for d in Path(__file__).parent.iterdir():
# skip files
if not d.is_dir():
continue
# skip hidden directories
if d.name.startswith("_") or d.name.startswith("."):
continue
# skip directories without an `__init__.py`
init = d / "__init__.py"
if not init.exists():
continue
module_name = init.parent.stem
# skip if already imported
if module_name in globals():
continue
# we have a legit module to import
spec = spec_from_file_location(module_name, init.absolute())
if spec is None or spec.loader is None:
logger.warn(f"Could not load {init}")
continue
module = module_from_spec(spec)
sys.modules[spec.name] = module
spec.loader.exec_module(module)
loaded_count += 1
del init, module_name
logger.info(f"Loaded {loaded_count} modules from {Path(__file__).parent}")

View File

@@ -8,11 +8,11 @@ from PIL import Image, ImageOps
from invokeai.app.invocations.primitives import ImageField, ImageOutput from invokeai.app.invocations.primitives import ImageField, ImageOutput
from invokeai.app.services.image_records.image_records_common import ImageCategory, ResourceOrigin from invokeai.app.services.image_records.image_records_common import ImageCategory, ResourceOrigin
from .baseinvocation import BaseInvocation, InputField, InvocationContext, invocation from .baseinvocation import BaseInvocation, InputField, InvocationContext, WithMetadata, WithWorkflow, invocation
@invocation("cv_inpaint", title="OpenCV Inpaint", tags=["opencv", "inpaint"], category="inpaint", version="1.0.0") @invocation("cv_inpaint", title="OpenCV Inpaint", tags=["opencv", "inpaint"], category="inpaint", version="1.0.0")
class CvInpaintInvocation(BaseInvocation): class CvInpaintInvocation(BaseInvocation, WithMetadata, WithWorkflow):
"""Simple inpaint using opencv.""" """Simple inpaint using opencv."""
image: ImageField = InputField(description="The image to inpaint") image: ImageField = InputField(description="The image to inpaint")

View File

@@ -16,6 +16,8 @@ from invokeai.app.invocations.baseinvocation import (
InputField, InputField,
InvocationContext, InvocationContext,
OutputField, OutputField,
WithMetadata,
WithWorkflow,
invocation, invocation,
invocation_output, invocation_output,
) )
@@ -129,7 +131,7 @@ def prepare_faces_list(
deduped_faces: list[FaceResultData] = [] deduped_faces: list[FaceResultData] = []
if len(face_result_list) == 0: if len(face_result_list) == 0:
return list() return []
for candidate in face_result_list: for candidate in face_result_list:
should_add = True should_add = True
@@ -208,7 +210,7 @@ def generate_face_box_mask(
# Check if any face is detected. # Check if any face is detected.
if results.multi_face_landmarks: # type: ignore # this are via protobuf and not typed if results.multi_face_landmarks: # type: ignore # this are via protobuf and not typed
# Search for the face_id in the detected faces. # Search for the face_id in the detected faces.
for face_id, face_landmarks in enumerate(results.multi_face_landmarks): # type: ignore #this are via protobuf and not typed for _face_id, face_landmarks in enumerate(results.multi_face_landmarks): # type: ignore #this are via protobuf and not typed
# Get the bounding box of the face mesh. # Get the bounding box of the face mesh.
x_coordinates = [landmark.x for landmark in face_landmarks.landmark] x_coordinates = [landmark.x for landmark in face_landmarks.landmark]
y_coordinates = [landmark.y for landmark in face_landmarks.landmark] y_coordinates = [landmark.y for landmark in face_landmarks.landmark]
@@ -437,7 +439,7 @@ def get_faces_list(
@invocation("face_off", title="FaceOff", tags=["image", "faceoff", "face", "mask"], category="image", version="1.0.2") @invocation("face_off", title="FaceOff", tags=["image", "faceoff", "face", "mask"], category="image", version="1.0.2")
class FaceOffInvocation(BaseInvocation): class FaceOffInvocation(BaseInvocation, WithWorkflow, WithMetadata):
"""Bound, extract, and mask a face from an image using MediaPipe detection""" """Bound, extract, and mask a face from an image using MediaPipe detection"""
image: ImageField = InputField(description="Image for face detection") image: ImageField = InputField(description="Image for face detection")
@@ -531,7 +533,7 @@ class FaceOffInvocation(BaseInvocation):
@invocation("face_mask_detection", title="FaceMask", tags=["image", "face", "mask"], category="image", version="1.0.2") @invocation("face_mask_detection", title="FaceMask", tags=["image", "face", "mask"], category="image", version="1.0.2")
class FaceMaskInvocation(BaseInvocation): class FaceMaskInvocation(BaseInvocation, WithWorkflow, WithMetadata):
"""Face mask creation using mediapipe face detection""" """Face mask creation using mediapipe face detection"""
image: ImageField = InputField(description="Image to face detect") image: ImageField = InputField(description="Image to face detect")
@@ -650,7 +652,7 @@ class FaceMaskInvocation(BaseInvocation):
@invocation( @invocation(
"face_identifier", title="FaceIdentifier", tags=["image", "face", "identifier"], category="image", version="1.0.2" "face_identifier", title="FaceIdentifier", tags=["image", "face", "identifier"], category="image", version="1.0.2"
) )
class FaceIdentifierInvocation(BaseInvocation): class FaceIdentifierInvocation(BaseInvocation, WithWorkflow, WithMetadata):
"""Outputs an image with detected face IDs printed on each face. For use with other FaceTools.""" """Outputs an image with detected face IDs printed on each face. For use with other FaceTools."""
image: ImageField = InputField(description="Image to face detect") image: ImageField = InputField(description="Image to face detect")

View File

@@ -7,13 +7,13 @@ import cv2
import numpy import numpy
from PIL import Image, ImageChops, ImageFilter, ImageOps from PIL import Image, ImageChops, ImageFilter, ImageOps
from invokeai.app.invocations.metadata import CoreMetadata
from invokeai.app.invocations.primitives import BoardField, ColorField, ImageField, ImageOutput from invokeai.app.invocations.primitives import BoardField, ColorField, ImageField, ImageOutput
from invokeai.app.services.image_records.image_records_common import ImageCategory, ResourceOrigin from invokeai.app.services.image_records.image_records_common import ImageCategory, ResourceOrigin
from invokeai.app.shared.fields import FieldDescriptions
from invokeai.backend.image_util.invisible_watermark import InvisibleWatermark from invokeai.backend.image_util.invisible_watermark import InvisibleWatermark
from invokeai.backend.image_util.safety_checker import SafetyChecker from invokeai.backend.image_util.safety_checker import SafetyChecker
from .baseinvocation import BaseInvocation, FieldDescriptions, Input, InputField, InvocationContext, invocation from .baseinvocation import BaseInvocation, Input, InputField, InvocationContext, WithMetadata, WithWorkflow, invocation
@invocation("show_image", title="Show Image", tags=["image"], category="image", version="1.0.0") @invocation("show_image", title="Show Image", tags=["image"], category="image", version="1.0.0")
@@ -36,14 +36,8 @@ class ShowImageInvocation(BaseInvocation):
) )
@invocation( @invocation("blank_image", title="Blank Image", tags=["image"], category="image", version="1.0.0")
"blank_image", class BlankImageInvocation(BaseInvocation, WithMetadata, WithWorkflow):
title="Blank Image",
tags=["image"],
category="image",
version="1.0.0",
)
class BlankImageInvocation(BaseInvocation):
"""Creates a blank image and forwards it to the pipeline""" """Creates a blank image and forwards it to the pipeline"""
width: int = InputField(default=512, description="The width of the image") width: int = InputField(default=512, description="The width of the image")
@@ -61,6 +55,7 @@ class BlankImageInvocation(BaseInvocation):
node_id=self.id, node_id=self.id,
session_id=context.graph_execution_state_id, session_id=context.graph_execution_state_id,
is_intermediate=self.is_intermediate, is_intermediate=self.is_intermediate,
metadata=self.metadata,
workflow=self.workflow, workflow=self.workflow,
) )
@@ -71,14 +66,8 @@ class BlankImageInvocation(BaseInvocation):
) )
@invocation( @invocation("img_crop", title="Crop Image", tags=["image", "crop"], category="image", version="1.0.0")
"img_crop", class ImageCropInvocation(BaseInvocation, WithWorkflow, WithMetadata):
title="Crop Image",
tags=["image", "crop"],
category="image",
version="1.0.0",
)
class ImageCropInvocation(BaseInvocation):
"""Crops an image to a specified box. The box can be outside of the image.""" """Crops an image to a specified box. The box can be outside of the image."""
image: ImageField = InputField(description="The image to crop") image: ImageField = InputField(description="The image to crop")
@@ -100,6 +89,7 @@ class ImageCropInvocation(BaseInvocation):
node_id=self.id, node_id=self.id,
session_id=context.graph_execution_state_id, session_id=context.graph_execution_state_id,
is_intermediate=self.is_intermediate, is_intermediate=self.is_intermediate,
metadata=self.metadata,
workflow=self.workflow, workflow=self.workflow,
) )
@@ -110,14 +100,8 @@ class ImageCropInvocation(BaseInvocation):
) )
@invocation( @invocation("img_paste", title="Paste Image", tags=["image", "paste"], category="image", version="1.0.1")
"img_paste", class ImagePasteInvocation(BaseInvocation, WithWorkflow, WithMetadata):
title="Paste Image",
tags=["image", "paste"],
category="image",
version="1.0.1",
)
class ImagePasteInvocation(BaseInvocation):
"""Pastes an image into another image.""" """Pastes an image into another image."""
base_image: ImageField = InputField(description="The base image") base_image: ImageField = InputField(description="The base image")
@@ -159,6 +143,7 @@ class ImagePasteInvocation(BaseInvocation):
node_id=self.id, node_id=self.id,
session_id=context.graph_execution_state_id, session_id=context.graph_execution_state_id,
is_intermediate=self.is_intermediate, is_intermediate=self.is_intermediate,
metadata=self.metadata,
workflow=self.workflow, workflow=self.workflow,
) )
@@ -169,14 +154,8 @@ class ImagePasteInvocation(BaseInvocation):
) )
@invocation( @invocation("tomask", title="Mask from Alpha", tags=["image", "mask"], category="image", version="1.0.0")
"tomask", class MaskFromAlphaInvocation(BaseInvocation, WithWorkflow, WithMetadata):
title="Mask from Alpha",
tags=["image", "mask"],
category="image",
version="1.0.0",
)
class MaskFromAlphaInvocation(BaseInvocation):
"""Extracts the alpha channel of an image as a mask.""" """Extracts the alpha channel of an image as a mask."""
image: ImageField = InputField(description="The image to create the mask from") image: ImageField = InputField(description="The image to create the mask from")
@@ -196,6 +175,7 @@ class MaskFromAlphaInvocation(BaseInvocation):
node_id=self.id, node_id=self.id,
session_id=context.graph_execution_state_id, session_id=context.graph_execution_state_id,
is_intermediate=self.is_intermediate, is_intermediate=self.is_intermediate,
metadata=self.metadata,
workflow=self.workflow, workflow=self.workflow,
) )
@@ -206,14 +186,8 @@ class MaskFromAlphaInvocation(BaseInvocation):
) )
@invocation( @invocation("img_mul", title="Multiply Images", tags=["image", "multiply"], category="image", version="1.0.0")
"img_mul", class ImageMultiplyInvocation(BaseInvocation, WithWorkflow, WithMetadata):
title="Multiply Images",
tags=["image", "multiply"],
category="image",
version="1.0.0",
)
class ImageMultiplyInvocation(BaseInvocation):
"""Multiplies two images together using `PIL.ImageChops.multiply()`.""" """Multiplies two images together using `PIL.ImageChops.multiply()`."""
image1: ImageField = InputField(description="The first image to multiply") image1: ImageField = InputField(description="The first image to multiply")
@@ -232,6 +206,7 @@ class ImageMultiplyInvocation(BaseInvocation):
node_id=self.id, node_id=self.id,
session_id=context.graph_execution_state_id, session_id=context.graph_execution_state_id,
is_intermediate=self.is_intermediate, is_intermediate=self.is_intermediate,
metadata=self.metadata,
workflow=self.workflow, workflow=self.workflow,
) )
@@ -245,14 +220,8 @@ class ImageMultiplyInvocation(BaseInvocation):
IMAGE_CHANNELS = Literal["A", "R", "G", "B"] IMAGE_CHANNELS = Literal["A", "R", "G", "B"]
@invocation( @invocation("img_chan", title="Extract Image Channel", tags=["image", "channel"], category="image", version="1.0.0")
"img_chan", class ImageChannelInvocation(BaseInvocation, WithWorkflow, WithMetadata):
title="Extract Image Channel",
tags=["image", "channel"],
category="image",
version="1.0.0",
)
class ImageChannelInvocation(BaseInvocation):
"""Gets a channel from an image.""" """Gets a channel from an image."""
image: ImageField = InputField(description="The image to get the channel from") image: ImageField = InputField(description="The image to get the channel from")
@@ -270,6 +239,7 @@ class ImageChannelInvocation(BaseInvocation):
node_id=self.id, node_id=self.id,
session_id=context.graph_execution_state_id, session_id=context.graph_execution_state_id,
is_intermediate=self.is_intermediate, is_intermediate=self.is_intermediate,
metadata=self.metadata,
workflow=self.workflow, workflow=self.workflow,
) )
@@ -283,14 +253,8 @@ class ImageChannelInvocation(BaseInvocation):
IMAGE_MODES = Literal["L", "RGB", "RGBA", "CMYK", "YCbCr", "LAB", "HSV", "I", "F"] IMAGE_MODES = Literal["L", "RGB", "RGBA", "CMYK", "YCbCr", "LAB", "HSV", "I", "F"]
@invocation( @invocation("img_conv", title="Convert Image Mode", tags=["image", "convert"], category="image", version="1.0.0")
"img_conv", class ImageConvertInvocation(BaseInvocation, WithWorkflow, WithMetadata):
title="Convert Image Mode",
tags=["image", "convert"],
category="image",
version="1.0.0",
)
class ImageConvertInvocation(BaseInvocation):
"""Converts an image to a different mode.""" """Converts an image to a different mode."""
image: ImageField = InputField(description="The image to convert") image: ImageField = InputField(description="The image to convert")
@@ -308,6 +272,7 @@ class ImageConvertInvocation(BaseInvocation):
node_id=self.id, node_id=self.id,
session_id=context.graph_execution_state_id, session_id=context.graph_execution_state_id,
is_intermediate=self.is_intermediate, is_intermediate=self.is_intermediate,
metadata=self.metadata,
workflow=self.workflow, workflow=self.workflow,
) )
@@ -318,14 +283,8 @@ class ImageConvertInvocation(BaseInvocation):
) )
@invocation( @invocation("img_blur", title="Blur Image", tags=["image", "blur"], category="image", version="1.0.0")
"img_blur", class ImageBlurInvocation(BaseInvocation, WithWorkflow, WithMetadata):
title="Blur Image",
tags=["image", "blur"],
category="image",
version="1.0.0",
)
class ImageBlurInvocation(BaseInvocation):
"""Blurs an image""" """Blurs an image"""
image: ImageField = InputField(description="The image to blur") image: ImageField = InputField(description="The image to blur")
@@ -348,6 +307,7 @@ class ImageBlurInvocation(BaseInvocation):
node_id=self.id, node_id=self.id,
session_id=context.graph_execution_state_id, session_id=context.graph_execution_state_id,
is_intermediate=self.is_intermediate, is_intermediate=self.is_intermediate,
metadata=self.metadata,
workflow=self.workflow, workflow=self.workflow,
) )
@@ -378,26 +338,17 @@ PIL_RESAMPLING_MAP = {
} }
@invocation( @invocation("img_resize", title="Resize Image", tags=["image", "resize"], category="image", version="1.0.0")
"img_resize", class ImageResizeInvocation(BaseInvocation, WithMetadata, WithWorkflow):
title="Resize Image",
tags=["image", "resize"],
category="image",
version="1.0.0",
)
class ImageResizeInvocation(BaseInvocation):
"""Resizes an image to specific dimensions""" """Resizes an image to specific dimensions"""
image: ImageField = InputField(description="The image to resize") image: ImageField = InputField(description="The image to resize")
width: int = InputField(default=512, gt=0, description="The width to resize to (px)") width: int = InputField(default=512, gt=0, description="The width to resize to (px)")
height: int = InputField(default=512, gt=0, description="The height to resize to (px)") height: int = InputField(default=512, gt=0, description="The height to resize to (px)")
resample_mode: PIL_RESAMPLING_MODES = InputField(default="bicubic", description="The resampling mode") resample_mode: PIL_RESAMPLING_MODES = InputField(default="bicubic", description="The resampling mode")
metadata: Optional[CoreMetadata] = InputField(
default=None, description=FieldDescriptions.core_metadata, ui_hidden=True
)
def invoke(self, context: InvocationContext) -> ImageOutput: def invoke(self, context: InvocationContext) -> ImageOutput:
image = context.get_image(self.image.image_name) image = context.services.images.get_pil_image(self.image.image_name)
resample_mode = PIL_RESAMPLING_MAP[self.resample_mode] resample_mode = PIL_RESAMPLING_MAP[self.resample_mode]
@@ -406,23 +357,26 @@ class ImageResizeInvocation(BaseInvocation):
resample=resample_mode, resample=resample_mode,
) )
image_name = context.save_image(image=resize_image) image_dto = context.services.images.create(
image=resize_image,
image_origin=ResourceOrigin.INTERNAL,
image_category=ImageCategory.GENERAL,
node_id=self.id,
session_id=context.graph_execution_state_id,
is_intermediate=self.is_intermediate,
metadata=self.metadata,
workflow=self.workflow,
)
return ImageOutput( return ImageOutput(
image=ImageField(image_name=image_name), image=ImageField(image_name=image_dto.image_name),
width=resize_image.width, width=image_dto.width,
height=resize_image.height, height=image_dto.height,
) )
@invocation( @invocation("img_scale", title="Scale Image", tags=["image", "scale"], category="image", version="1.0.0")
"img_scale", class ImageScaleInvocation(BaseInvocation, WithMetadata, WithWorkflow):
title="Scale Image",
tags=["image", "scale"],
category="image",
version="1.0.0",
)
class ImageScaleInvocation(BaseInvocation):
"""Scales an image by a factor""" """Scales an image by a factor"""
image: ImageField = InputField(description="The image to scale") image: ImageField = InputField(description="The image to scale")
@@ -452,6 +406,7 @@ class ImageScaleInvocation(BaseInvocation):
node_id=self.id, node_id=self.id,
session_id=context.graph_execution_state_id, session_id=context.graph_execution_state_id,
is_intermediate=self.is_intermediate, is_intermediate=self.is_intermediate,
metadata=self.metadata,
workflow=self.workflow, workflow=self.workflow,
) )
@@ -462,14 +417,8 @@ class ImageScaleInvocation(BaseInvocation):
) )
@invocation( @invocation("img_lerp", title="Lerp Image", tags=["image", "lerp"], category="image", version="1.0.0")
"img_lerp", class ImageLerpInvocation(BaseInvocation, WithWorkflow, WithMetadata):
title="Lerp Image",
tags=["image", "lerp"],
category="image",
version="1.0.0",
)
class ImageLerpInvocation(BaseInvocation):
"""Linear interpolation of all pixels of an image""" """Linear interpolation of all pixels of an image"""
image: ImageField = InputField(description="The image to lerp") image: ImageField = InputField(description="The image to lerp")
@@ -491,6 +440,7 @@ class ImageLerpInvocation(BaseInvocation):
node_id=self.id, node_id=self.id,
session_id=context.graph_execution_state_id, session_id=context.graph_execution_state_id,
is_intermediate=self.is_intermediate, is_intermediate=self.is_intermediate,
metadata=self.metadata,
workflow=self.workflow, workflow=self.workflow,
) )
@@ -501,14 +451,8 @@ class ImageLerpInvocation(BaseInvocation):
) )
@invocation( @invocation("img_ilerp", title="Inverse Lerp Image", tags=["image", "ilerp"], category="image", version="1.0.0")
"img_ilerp", class ImageInverseLerpInvocation(BaseInvocation, WithWorkflow, WithMetadata):
title="Inverse Lerp Image",
tags=["image", "ilerp"],
category="image",
version="1.0.0",
)
class ImageInverseLerpInvocation(BaseInvocation):
"""Inverse linear interpolation of all pixels of an image""" """Inverse linear interpolation of all pixels of an image"""
image: ImageField = InputField(description="The image to lerp") image: ImageField = InputField(description="The image to lerp")
@@ -530,6 +474,7 @@ class ImageInverseLerpInvocation(BaseInvocation):
node_id=self.id, node_id=self.id,
session_id=context.graph_execution_state_id, session_id=context.graph_execution_state_id,
is_intermediate=self.is_intermediate, is_intermediate=self.is_intermediate,
metadata=self.metadata,
workflow=self.workflow, workflow=self.workflow,
) )
@@ -540,20 +485,11 @@ class ImageInverseLerpInvocation(BaseInvocation):
) )
@invocation( @invocation("img_nsfw", title="Blur NSFW Image", tags=["image", "nsfw"], category="image", version="1.0.0")
"img_nsfw", class ImageNSFWBlurInvocation(BaseInvocation, WithMetadata, WithWorkflow):
title="Blur NSFW Image",
tags=["image", "nsfw"],
category="image",
version="1.0.0",
)
class ImageNSFWBlurInvocation(BaseInvocation):
"""Add blur to NSFW-flagged images""" """Add blur to NSFW-flagged images"""
image: ImageField = InputField(description="The image to check") image: ImageField = InputField(description="The image to check")
metadata: Optional[CoreMetadata] = InputField(
default=None, description=FieldDescriptions.core_metadata, ui_hidden=True
)
def invoke(self, context: InvocationContext) -> ImageOutput: def invoke(self, context: InvocationContext) -> ImageOutput:
image = context.services.images.get_pil_image(self.image.image_name) image = context.services.images.get_pil_image(self.image.image_name)
@@ -574,7 +510,7 @@ class ImageNSFWBlurInvocation(BaseInvocation):
node_id=self.id, node_id=self.id,
session_id=context.graph_execution_state_id, session_id=context.graph_execution_state_id,
is_intermediate=self.is_intermediate, is_intermediate=self.is_intermediate,
metadata=self.metadata.model_dump() if self.metadata else None, metadata=self.metadata,
workflow=self.workflow, workflow=self.workflow,
) )
@@ -598,14 +534,11 @@ class ImageNSFWBlurInvocation(BaseInvocation):
category="image", category="image",
version="1.0.0", version="1.0.0",
) )
class ImageWatermarkInvocation(BaseInvocation): class ImageWatermarkInvocation(BaseInvocation, WithMetadata, WithWorkflow):
"""Add an invisible watermark to an image""" """Add an invisible watermark to an image"""
image: ImageField = InputField(description="The image to check") image: ImageField = InputField(description="The image to check")
text: str = InputField(default="InvokeAI", description="Watermark text") text: str = InputField(default="InvokeAI", description="Watermark text")
metadata: Optional[CoreMetadata] = InputField(
default=None, description=FieldDescriptions.core_metadata, ui_hidden=True
)
def invoke(self, context: InvocationContext) -> ImageOutput: def invoke(self, context: InvocationContext) -> ImageOutput:
image = context.services.images.get_pil_image(self.image.image_name) image = context.services.images.get_pil_image(self.image.image_name)
@@ -617,7 +550,7 @@ class ImageWatermarkInvocation(BaseInvocation):
node_id=self.id, node_id=self.id,
session_id=context.graph_execution_state_id, session_id=context.graph_execution_state_id,
is_intermediate=self.is_intermediate, is_intermediate=self.is_intermediate,
metadata=self.metadata.model_dump() if self.metadata else None, metadata=self.metadata,
workflow=self.workflow, workflow=self.workflow,
) )
@@ -628,14 +561,8 @@ class ImageWatermarkInvocation(BaseInvocation):
) )
@invocation( @invocation("mask_edge", title="Mask Edge", tags=["image", "mask", "inpaint"], category="image", version="1.0.0")
"mask_edge", class MaskEdgeInvocation(BaseInvocation, WithWorkflow, WithMetadata):
title="Mask Edge",
tags=["image", "mask", "inpaint"],
category="image",
version="1.0.0",
)
class MaskEdgeInvocation(BaseInvocation):
"""Applies an edge mask to an image""" """Applies an edge mask to an image"""
image: ImageField = InputField(description="The image to apply the mask to") image: ImageField = InputField(description="The image to apply the mask to")
@@ -669,6 +596,7 @@ class MaskEdgeInvocation(BaseInvocation):
node_id=self.id, node_id=self.id,
session_id=context.graph_execution_state_id, session_id=context.graph_execution_state_id,
is_intermediate=self.is_intermediate, is_intermediate=self.is_intermediate,
metadata=self.metadata,
workflow=self.workflow, workflow=self.workflow,
) )
@@ -686,7 +614,7 @@ class MaskEdgeInvocation(BaseInvocation):
category="image", category="image",
version="1.0.0", version="1.0.0",
) )
class MaskCombineInvocation(BaseInvocation): class MaskCombineInvocation(BaseInvocation, WithWorkflow, WithMetadata):
"""Combine two masks together by multiplying them using `PIL.ImageChops.multiply()`.""" """Combine two masks together by multiplying them using `PIL.ImageChops.multiply()`."""
mask1: ImageField = InputField(description="The first mask to combine") mask1: ImageField = InputField(description="The first mask to combine")
@@ -705,6 +633,7 @@ class MaskCombineInvocation(BaseInvocation):
node_id=self.id, node_id=self.id,
session_id=context.graph_execution_state_id, session_id=context.graph_execution_state_id,
is_intermediate=self.is_intermediate, is_intermediate=self.is_intermediate,
metadata=self.metadata,
workflow=self.workflow, workflow=self.workflow,
) )
@@ -715,14 +644,8 @@ class MaskCombineInvocation(BaseInvocation):
) )
@invocation( @invocation("color_correct", title="Color Correct", tags=["image", "color"], category="image", version="1.0.0")
"color_correct", class ColorCorrectInvocation(BaseInvocation, WithWorkflow, WithMetadata):
title="Color Correct",
tags=["image", "color"],
category="image",
version="1.0.0",
)
class ColorCorrectInvocation(BaseInvocation):
""" """
Shifts the colors of a target image to match the reference image, optionally Shifts the colors of a target image to match the reference image, optionally
using a mask to only color-correct certain regions of the target image. using a mask to only color-correct certain regions of the target image.
@@ -821,6 +744,7 @@ class ColorCorrectInvocation(BaseInvocation):
node_id=self.id, node_id=self.id,
session_id=context.graph_execution_state_id, session_id=context.graph_execution_state_id,
is_intermediate=self.is_intermediate, is_intermediate=self.is_intermediate,
metadata=self.metadata,
workflow=self.workflow, workflow=self.workflow,
) )
@@ -831,14 +755,8 @@ class ColorCorrectInvocation(BaseInvocation):
) )
@invocation( @invocation("img_hue_adjust", title="Adjust Image Hue", tags=["image", "hue"], category="image", version="1.0.0")
"img_hue_adjust", class ImageHueAdjustmentInvocation(BaseInvocation, WithWorkflow, WithMetadata):
title="Adjust Image Hue",
tags=["image", "hue"],
category="image",
version="1.0.0",
)
class ImageHueAdjustmentInvocation(BaseInvocation):
"""Adjusts the Hue of an image.""" """Adjusts the Hue of an image."""
image: ImageField = InputField(description="The image to adjust") image: ImageField = InputField(description="The image to adjust")
@@ -866,6 +784,7 @@ class ImageHueAdjustmentInvocation(BaseInvocation):
node_id=self.id, node_id=self.id,
is_intermediate=self.is_intermediate, is_intermediate=self.is_intermediate,
session_id=context.graph_execution_state_id, session_id=context.graph_execution_state_id,
metadata=self.metadata,
workflow=self.workflow, workflow=self.workflow,
) )
@@ -941,7 +860,7 @@ CHANNEL_FORMATS = {
category="image", category="image",
version="1.0.0", version="1.0.0",
) )
class ImageChannelOffsetInvocation(BaseInvocation): class ImageChannelOffsetInvocation(BaseInvocation, WithWorkflow, WithMetadata):
"""Add or subtract a value from a specific color channel of an image.""" """Add or subtract a value from a specific color channel of an image."""
image: ImageField = InputField(description="The image to adjust") image: ImageField = InputField(description="The image to adjust")
@@ -975,6 +894,7 @@ class ImageChannelOffsetInvocation(BaseInvocation):
node_id=self.id, node_id=self.id,
is_intermediate=self.is_intermediate, is_intermediate=self.is_intermediate,
session_id=context.graph_execution_state_id, session_id=context.graph_execution_state_id,
metadata=self.metadata,
workflow=self.workflow, workflow=self.workflow,
) )
@@ -1011,7 +931,7 @@ class ImageChannelOffsetInvocation(BaseInvocation):
category="image", category="image",
version="1.0.0", version="1.0.0",
) )
class ImageChannelMultiplyInvocation(BaseInvocation): class ImageChannelMultiplyInvocation(BaseInvocation, WithWorkflow, WithMetadata):
"""Scale a specific color channel of an image.""" """Scale a specific color channel of an image."""
image: ImageField = InputField(description="The image to adjust") image: ImageField = InputField(description="The image to adjust")
@@ -1051,6 +971,7 @@ class ImageChannelMultiplyInvocation(BaseInvocation):
is_intermediate=self.is_intermediate, is_intermediate=self.is_intermediate,
session_id=context.graph_execution_state_id, session_id=context.graph_execution_state_id,
workflow=self.workflow, workflow=self.workflow,
metadata=self.metadata,
) )
return ImageOutput( return ImageOutput(
@@ -1070,16 +991,11 @@ class ImageChannelMultiplyInvocation(BaseInvocation):
version="1.0.1", version="1.0.1",
use_cache=False, use_cache=False,
) )
class SaveImageInvocation(BaseInvocation): class SaveImageInvocation(BaseInvocation, WithWorkflow, WithMetadata):
"""Saves an image. Unlike an image primitive, this invocation stores a copy of the image.""" """Saves an image. Unlike an image primitive, this invocation stores a copy of the image."""
image: ImageField = InputField(description=FieldDescriptions.image) image: ImageField = InputField(description=FieldDescriptions.image)
board: Optional[BoardField] = InputField(default=None, description=FieldDescriptions.board, input=Input.Direct) board: BoardField = InputField(default=None, description=FieldDescriptions.board, input=Input.Direct)
metadata: Optional[CoreMetadata] = InputField(
default=None,
description=FieldDescriptions.core_metadata,
ui_hidden=True,
)
def invoke(self, context: InvocationContext) -> ImageOutput: def invoke(self, context: InvocationContext) -> ImageOutput:
image = context.services.images.get_pil_image(self.image.image_name) image = context.services.images.get_pil_image(self.image.image_name)
@@ -1092,7 +1008,7 @@ class SaveImageInvocation(BaseInvocation):
node_id=self.id, node_id=self.id,
session_id=context.graph_execution_state_id, session_id=context.graph_execution_state_id,
is_intermediate=self.is_intermediate, is_intermediate=self.is_intermediate,
metadata=self.metadata.model_dump() if self.metadata else None, metadata=self.metadata,
workflow=self.workflow, workflow=self.workflow,
) )

View File

@@ -13,7 +13,7 @@ from invokeai.backend.image_util.cv2_inpaint import cv2_inpaint
from invokeai.backend.image_util.lama import LaMA from invokeai.backend.image_util.lama import LaMA
from invokeai.backend.image_util.patchmatch import PatchMatch from invokeai.backend.image_util.patchmatch import PatchMatch
from .baseinvocation import BaseInvocation, InputField, InvocationContext, invocation from .baseinvocation import BaseInvocation, InputField, InvocationContext, WithMetadata, WithWorkflow, invocation
from .image import PIL_RESAMPLING_MAP, PIL_RESAMPLING_MODES from .image import PIL_RESAMPLING_MAP, PIL_RESAMPLING_MODES
@@ -119,7 +119,7 @@ def tile_fill_missing(im: Image.Image, tile_size: int = 16, seed: Optional[int]
@invocation("infill_rgba", title="Solid Color Infill", tags=["image", "inpaint"], category="inpaint", version="1.0.0") @invocation("infill_rgba", title="Solid Color Infill", tags=["image", "inpaint"], category="inpaint", version="1.0.0")
class InfillColorInvocation(BaseInvocation): class InfillColorInvocation(BaseInvocation, WithWorkflow, WithMetadata):
"""Infills transparent areas of an image with a solid color""" """Infills transparent areas of an image with a solid color"""
image: ImageField = InputField(description="The image to infill") image: ImageField = InputField(description="The image to infill")
@@ -143,6 +143,7 @@ class InfillColorInvocation(BaseInvocation):
node_id=self.id, node_id=self.id,
session_id=context.graph_execution_state_id, session_id=context.graph_execution_state_id,
is_intermediate=self.is_intermediate, is_intermediate=self.is_intermediate,
metadata=self.metadata,
workflow=self.workflow, workflow=self.workflow,
) )
@@ -154,7 +155,7 @@ class InfillColorInvocation(BaseInvocation):
@invocation("infill_tile", title="Tile Infill", tags=["image", "inpaint"], category="inpaint", version="1.0.0") @invocation("infill_tile", title="Tile Infill", tags=["image", "inpaint"], category="inpaint", version="1.0.0")
class InfillTileInvocation(BaseInvocation): class InfillTileInvocation(BaseInvocation, WithWorkflow, WithMetadata):
"""Infills transparent areas of an image with tiles of the image""" """Infills transparent areas of an image with tiles of the image"""
image: ImageField = InputField(description="The image to infill") image: ImageField = InputField(description="The image to infill")
@@ -179,6 +180,7 @@ class InfillTileInvocation(BaseInvocation):
node_id=self.id, node_id=self.id,
session_id=context.graph_execution_state_id, session_id=context.graph_execution_state_id,
is_intermediate=self.is_intermediate, is_intermediate=self.is_intermediate,
metadata=self.metadata,
workflow=self.workflow, workflow=self.workflow,
) )
@@ -192,7 +194,7 @@ class InfillTileInvocation(BaseInvocation):
@invocation( @invocation(
"infill_patchmatch", title="PatchMatch Infill", tags=["image", "inpaint"], category="inpaint", version="1.0.0" "infill_patchmatch", title="PatchMatch Infill", tags=["image", "inpaint"], category="inpaint", version="1.0.0"
) )
class InfillPatchMatchInvocation(BaseInvocation): class InfillPatchMatchInvocation(BaseInvocation, WithWorkflow, WithMetadata):
"""Infills transparent areas of an image using the PatchMatch algorithm""" """Infills transparent areas of an image using the PatchMatch algorithm"""
image: ImageField = InputField(description="The image to infill") image: ImageField = InputField(description="The image to infill")
@@ -232,6 +234,7 @@ class InfillPatchMatchInvocation(BaseInvocation):
node_id=self.id, node_id=self.id,
session_id=context.graph_execution_state_id, session_id=context.graph_execution_state_id,
is_intermediate=self.is_intermediate, is_intermediate=self.is_intermediate,
metadata=self.metadata,
workflow=self.workflow, workflow=self.workflow,
) )
@@ -243,7 +246,7 @@ class InfillPatchMatchInvocation(BaseInvocation):
@invocation("infill_lama", title="LaMa Infill", tags=["image", "inpaint"], category="inpaint", version="1.0.0") @invocation("infill_lama", title="LaMa Infill", tags=["image", "inpaint"], category="inpaint", version="1.0.0")
class LaMaInfillInvocation(BaseInvocation): class LaMaInfillInvocation(BaseInvocation, WithWorkflow, WithMetadata):
"""Infills transparent areas of an image using the LaMa model""" """Infills transparent areas of an image using the LaMa model"""
image: ImageField = InputField(description="The image to infill") image: ImageField = InputField(description="The image to infill")
@@ -260,6 +263,8 @@ class LaMaInfillInvocation(BaseInvocation):
node_id=self.id, node_id=self.id,
session_id=context.graph_execution_state_id, session_id=context.graph_execution_state_id,
is_intermediate=self.is_intermediate, is_intermediate=self.is_intermediate,
metadata=self.metadata,
workflow=self.workflow,
) )
return ImageOutput( return ImageOutput(
@@ -269,8 +274,8 @@ class LaMaInfillInvocation(BaseInvocation):
) )
@invocation("infill_cv2", title="CV2 Infill", tags=["image", "inpaint"], category="inpaint", version="1.0.0") @invocation("infill_cv2", title="CV2 Infill", tags=["image", "inpaint"], category="inpaint")
class CV2InfillInvocation(BaseInvocation): class CV2InfillInvocation(BaseInvocation, WithWorkflow, WithMetadata):
"""Infills transparent areas of an image using OpenCV Inpainting""" """Infills transparent areas of an image using OpenCV Inpainting"""
image: ImageField = InputField(description="The image to infill") image: ImageField = InputField(description="The image to infill")
@@ -287,6 +292,8 @@ class CV2InfillInvocation(BaseInvocation):
node_id=self.id, node_id=self.id,
session_id=context.graph_execution_state_id, session_id=context.graph_execution_state_id,
is_intermediate=self.is_intermediate, is_intermediate=self.is_intermediate,
metadata=self.metadata,
workflow=self.workflow,
) )
return ImageOutput( return ImageOutput(

View File

@@ -7,7 +7,6 @@ from pydantic import BaseModel, ConfigDict, Field
from invokeai.app.invocations.baseinvocation import ( from invokeai.app.invocations.baseinvocation import (
BaseInvocation, BaseInvocation,
BaseInvocationOutput, BaseInvocationOutput,
FieldDescriptions,
Input, Input,
InputField, InputField,
InvocationContext, InvocationContext,
@@ -17,6 +16,7 @@ from invokeai.app.invocations.baseinvocation import (
invocation_output, invocation_output,
) )
from invokeai.app.invocations.primitives import ImageField from invokeai.app.invocations.primitives import ImageField
from invokeai.app.shared.fields import FieldDescriptions
from invokeai.backend.model_management.models.base import BaseModelType, ModelType from invokeai.backend.model_management.models.base import BaseModelType, ModelType
from invokeai.backend.model_management.models.ip_adapter import get_ip_adapter_image_encoder_model_id from invokeai.backend.model_management.models.ip_adapter import get_ip_adapter_image_encoder_model_id
@@ -36,7 +36,7 @@ class CLIPVisionModelField(BaseModel):
class IPAdapterField(BaseModel): class IPAdapterField(BaseModel):
image: ImageField = Field(description="The IP-Adapter image prompt.") image: Union[ImageField, List[ImageField]] = Field(description="The IP-Adapter image prompt(s).")
ip_adapter_model: IPAdapterModelField = Field(description="The IP-Adapter model to use.") ip_adapter_model: IPAdapterModelField = Field(description="The IP-Adapter model to use.")
image_encoder_model: CLIPVisionModelField = Field(description="The name of the CLIP image encoder model.") image_encoder_model: CLIPVisionModelField = Field(description="The name of the CLIP image encoder model.")
weight: Union[float, List[float]] = Field(default=1, description="The weight given to the ControlNet") weight: Union[float, List[float]] = Field(default=1, description="The weight given to the ControlNet")
@@ -55,19 +55,19 @@ class IPAdapterOutput(BaseInvocationOutput):
ip_adapter: IPAdapterField = OutputField(description=FieldDescriptions.ip_adapter, title="IP-Adapter") ip_adapter: IPAdapterField = OutputField(description=FieldDescriptions.ip_adapter, title="IP-Adapter")
@invocation("ip_adapter", title="IP-Adapter", tags=["ip_adapter", "control"], category="ip_adapter", version="1.0.0") @invocation("ip_adapter", title="IP-Adapter", tags=["ip_adapter", "control"], category="ip_adapter", version="1.1.0")
class IPAdapterInvocation(BaseInvocation): class IPAdapterInvocation(BaseInvocation):
"""Collects IP-Adapter info to pass to other nodes.""" """Collects IP-Adapter info to pass to other nodes."""
# Inputs # Inputs
image: ImageField = InputField(description="The IP-Adapter image prompt.") image: Union[ImageField, List[ImageField]] = InputField(description="The IP-Adapter image prompt(s).")
ip_adapter_model: IPAdapterModelField = InputField( ip_adapter_model: IPAdapterModelField = InputField(
description="The IP-Adapter model.", title="IP-Adapter Model", input=Input.Direct, ui_order=-1 description="The IP-Adapter model.", title="IP-Adapter Model", input=Input.Direct, ui_order=-1
) )
# weight: float = InputField(default=1.0, description="The weight of the IP-Adapter.", ui_type=UIType.Float) # weight: float = InputField(default=1.0, description="The weight of the IP-Adapter.", ui_type=UIType.Float)
weight: Union[float, List[float]] = InputField( weight: Union[float, List[float]] = InputField(
default=1, ge=0, description="The weight given to the IP-Adapter", ui_type=UIType.Float, title="Weight" default=1, ge=-1, description="The weight given to the IP-Adapter", ui_type=UIType.Float, title="Weight"
) )
begin_step_percent: float = InputField( begin_step_percent: float = InputField(

View File

@@ -10,7 +10,7 @@ import torch
import torchvision.transforms as T import torchvision.transforms as T
from diffusers import AutoencoderKL, AutoencoderTiny from diffusers import AutoencoderKL, AutoencoderTiny
from diffusers.image_processor import VaeImageProcessor from diffusers.image_processor import VaeImageProcessor
from diffusers.models.adapter import FullAdapterXL, T2IAdapter from diffusers.models.adapter import T2IAdapter
from diffusers.models.attention_processor import ( from diffusers.models.attention_processor import (
AttnProcessor2_0, AttnProcessor2_0,
LoRAAttnProcessor2_0, LoRAAttnProcessor2_0,
@@ -23,7 +23,6 @@ from pydantic import field_validator
from torchvision.transforms.functional import resize as tv_resize from torchvision.transforms.functional import resize as tv_resize
from invokeai.app.invocations.ip_adapter import IPAdapterField from invokeai.app.invocations.ip_adapter import IPAdapterField
from invokeai.app.invocations.metadata import CoreMetadata
from invokeai.app.invocations.primitives import ( from invokeai.app.invocations.primitives import (
DenoiseMaskField, DenoiseMaskField,
DenoiseMaskOutput, DenoiseMaskOutput,
@@ -35,6 +34,7 @@ from invokeai.app.invocations.primitives import (
) )
from invokeai.app.invocations.t2i_adapter import T2IAdapterField from invokeai.app.invocations.t2i_adapter import T2IAdapterField
from invokeai.app.services.image_records.image_records_common import ImageCategory, ResourceOrigin from invokeai.app.services.image_records.image_records_common import ImageCategory, ResourceOrigin
from invokeai.app.shared.fields import FieldDescriptions
from invokeai.app.util.controlnet_utils import prepare_control_image from invokeai.app.util.controlnet_utils import prepare_control_image
from invokeai.app.util.step_callback import stable_diffusion_step_callback from invokeai.app.util.step_callback import stable_diffusion_step_callback
from invokeai.backend.ip_adapter.ip_adapter import IPAdapter, IPAdapterPlus from invokeai.backend.ip_adapter.ip_adapter import IPAdapter, IPAdapterPlus
@@ -58,12 +58,13 @@ from ...backend.util.devices import choose_precision, choose_torch_device
from .baseinvocation import ( from .baseinvocation import (
BaseInvocation, BaseInvocation,
BaseInvocationOutput, BaseInvocationOutput,
FieldDescriptions,
Input, Input,
InputField, InputField,
InvocationContext, InvocationContext,
OutputField, OutputField,
UIType, UIType,
WithMetadata,
WithWorkflow,
invocation, invocation,
invocation_output, invocation_output,
) )
@@ -76,7 +77,7 @@ if choose_torch_device() == torch.device("mps"):
DEFAULT_PRECISION = choose_precision(choose_torch_device()) DEFAULT_PRECISION = choose_precision(choose_torch_device())
SAMPLER_NAME_VALUES = Literal[tuple(list(SCHEDULER_MAP.keys()))] SAMPLER_NAME_VALUES = Literal[tuple(SCHEDULER_MAP.keys())]
@invocation_output("scheduler_output") @invocation_output("scheduler_output")
@@ -182,8 +183,9 @@ def get_scheduler(
seed: int, seed: int,
) -> Scheduler: ) -> Scheduler:
scheduler_class, scheduler_extra_config = SCHEDULER_MAP.get(scheduler_name, SCHEDULER_MAP["ddim"]) scheduler_class, scheduler_extra_config = SCHEDULER_MAP.get(scheduler_name, SCHEDULER_MAP["ddim"])
orig_scheduler_info = context.get_model( orig_scheduler_info = context.services.model_manager.get_model(
**scheduler_info.model_dump(), **scheduler_info.model_dump(),
context=context,
) )
with orig_scheduler_info as orig_scheduler: with orig_scheduler_info as orig_scheduler:
scheduler_config = orig_scheduler.config scheduler_config = orig_scheduler.config
@@ -213,7 +215,7 @@ def get_scheduler(
title="Denoise Latents", title="Denoise Latents",
tags=["latents", "denoise", "txt2img", "t2i", "t2l", "img2img", "i2i", "l2l"], tags=["latents", "denoise", "txt2img", "t2i", "t2l", "img2img", "i2i", "l2l"],
category="latents", category="latents",
version="1.3.0", version="1.4.0",
) )
class DenoiseLatentsInvocation(BaseInvocation): class DenoiseLatentsInvocation(BaseInvocation):
"""Denoises noisy latents to decodable images""" """Denoises noisy latents to decodable images"""
@@ -297,12 +299,15 @@ class DenoiseLatentsInvocation(BaseInvocation):
def dispatch_progress( def dispatch_progress(
self, self,
context: InvocationContext, context: InvocationContext,
source_node_id: str,
intermediate_state: PipelineIntermediateState, intermediate_state: PipelineIntermediateState,
base_model: BaseModelType, base_model: BaseModelType,
) -> None: ) -> None:
stable_diffusion_step_callback( stable_diffusion_step_callback(
context=context, context=context,
intermediate_state=intermediate_state, intermediate_state=intermediate_state,
node=self.model_dump(),
source_node_id=source_node_id,
base_model=base_model, base_model=base_model,
) )
@@ -313,11 +318,11 @@ class DenoiseLatentsInvocation(BaseInvocation):
unet, unet,
seed, seed,
) -> ConditioningData: ) -> ConditioningData:
positive_cond_data = context.get_conditioning(self.positive_conditioning.conditioning_name) positive_cond_data = context.services.latents.get(self.positive_conditioning.conditioning_name)
c = positive_cond_data.conditionings[0].to(device=unet.device, dtype=unet.dtype) c = positive_cond_data.conditionings[0].to(device=unet.device, dtype=unet.dtype)
extra_conditioning_info = c.extra_conditioning extra_conditioning_info = c.extra_conditioning
negative_cond_data = context.get_conditioning(self.negative_conditioning.conditioning_name) negative_cond_data = context.services.latents.get(self.negative_conditioning.conditioning_name)
uc = negative_cond_data.conditionings[0].to(device=unet.device, dtype=unet.dtype) uc = negative_cond_data.conditionings[0].to(device=unet.device, dtype=unet.dtype)
conditioning_data = ConditioningData( conditioning_data = ConditioningData(
@@ -404,16 +409,17 @@ class DenoiseLatentsInvocation(BaseInvocation):
controlnet_data = [] controlnet_data = []
for control_info in control_list: for control_info in control_list:
control_model = exit_stack.enter_context( control_model = exit_stack.enter_context(
context.get_model( context.services.model_manager.get_model(
model_name=control_info.control_model.model_name, model_name=control_info.control_model.model_name,
model_type=ModelType.ControlNet, model_type=ModelType.ControlNet,
base_model=control_info.control_model.base_model, base_model=control_info.control_model.base_model,
context=context,
) )
) )
# control_models.append(control_model) # control_models.append(control_model)
control_image_field = control_info.image control_image_field = control_info.image
input_image = context.get_image(control_image_field.image_name) input_image = context.services.images.get_pil_image(control_image_field.image_name)
# self.image.image_type, self.image.image_name # self.image.image_type, self.image.image_name
# FIXME: still need to test with different widths, heights, devices, dtypes # FIXME: still need to test with different widths, heights, devices, dtypes
# and add in batch_size, num_images_per_prompt? # and add in batch_size, num_images_per_prompt?
@@ -471,29 +477,36 @@ class DenoiseLatentsInvocation(BaseInvocation):
conditioning_data.ip_adapter_conditioning = [] conditioning_data.ip_adapter_conditioning = []
for single_ip_adapter in ip_adapter: for single_ip_adapter in ip_adapter:
ip_adapter_model: Union[IPAdapter, IPAdapterPlus] = exit_stack.enter_context( ip_adapter_model: Union[IPAdapter, IPAdapterPlus] = exit_stack.enter_context(
context.get_model( context.services.model_manager.get_model(
model_name=single_ip_adapter.ip_adapter_model.model_name, model_name=single_ip_adapter.ip_adapter_model.model_name,
model_type=ModelType.IPAdapter, model_type=ModelType.IPAdapter,
base_model=single_ip_adapter.ip_adapter_model.base_model, base_model=single_ip_adapter.ip_adapter_model.base_model,
context=context,
) )
) )
image_encoder_model_info = context.get_model( image_encoder_model_info = context.services.model_manager.get_model(
model_name=single_ip_adapter.image_encoder_model.model_name, model_name=single_ip_adapter.image_encoder_model.model_name,
model_type=ModelType.CLIPVision, model_type=ModelType.CLIPVision,
base_model=single_ip_adapter.image_encoder_model.base_model, base_model=single_ip_adapter.image_encoder_model.base_model,
context=context,
) )
input_image = context.get_image(single_ip_adapter.image.image_name) # `single_ip_adapter.image` could be a list or a single ImageField. Normalize to a list here.
single_ipa_images = single_ip_adapter.image
if not isinstance(single_ipa_images, list):
single_ipa_images = [single_ipa_images]
single_ipa_images = [context.services.images.get_pil_image(image.image_name) for image in single_ipa_images]
# TODO(ryand): With some effort, the step of running the CLIP Vision encoder could be done before any other # TODO(ryand): With some effort, the step of running the CLIP Vision encoder could be done before any other
# models are needed in memory. This would help to reduce peak memory utilization in low-memory environments. # models are needed in memory. This would help to reduce peak memory utilization in low-memory environments.
with image_encoder_model_info as image_encoder_model: with image_encoder_model_info as image_encoder_model:
# Get image embeddings from CLIP and ImageProjModel. # Get image embeddings from CLIP and ImageProjModel.
( image_prompt_embeds, uncond_image_prompt_embeds = ip_adapter_model.get_image_embeds(
image_prompt_embeds, single_ipa_images, image_encoder_model
uncond_image_prompt_embeds, )
) = ip_adapter_model.get_image_embeds(input_image, image_encoder_model)
conditioning_data.ip_adapter_conditioning.append( conditioning_data.ip_adapter_conditioning.append(
IPAdapterConditioningInfo(image_prompt_embeds, uncond_image_prompt_embeds) IPAdapterConditioningInfo(image_prompt_embeds, uncond_image_prompt_embeds)
) )
@@ -528,12 +541,13 @@ class DenoiseLatentsInvocation(BaseInvocation):
t2i_adapter_data = [] t2i_adapter_data = []
for t2i_adapter_field in t2i_adapter: for t2i_adapter_field in t2i_adapter:
t2i_adapter_model_info = context.get_model( t2i_adapter_model_info = context.services.model_manager.get_model(
model_name=t2i_adapter_field.t2i_adapter_model.model_name, model_name=t2i_adapter_field.t2i_adapter_model.model_name,
model_type=ModelType.T2IAdapter, model_type=ModelType.T2IAdapter,
base_model=t2i_adapter_field.t2i_adapter_model.base_model, base_model=t2i_adapter_field.t2i_adapter_model.base_model,
context=context,
) )
image = context.get_image(t2i_adapter_field.image.image_name) image = context.services.images.get_pil_image(t2i_adapter_field.image.image_name)
# The max_unet_downscale is the maximum amount that the UNet model downscales the latent image internally. # The max_unet_downscale is the maximum amount that the UNet model downscales the latent image internally.
if t2i_adapter_field.t2i_adapter_model.base_model == BaseModelType.StableDiffusion1: if t2i_adapter_field.t2i_adapter_model.base_model == BaseModelType.StableDiffusion1:
@@ -548,10 +562,6 @@ class DenoiseLatentsInvocation(BaseInvocation):
t2i_adapter_model: T2IAdapter t2i_adapter_model: T2IAdapter
with t2i_adapter_model_info as t2i_adapter_model: with t2i_adapter_model_info as t2i_adapter_model:
total_downscale_factor = t2i_adapter_model.total_downscale_factor total_downscale_factor = t2i_adapter_model.total_downscale_factor
if isinstance(t2i_adapter_model.adapter, FullAdapterXL):
# HACK(ryand): Work around a bug in FullAdapterXL. This is being addressed upstream in diffusers by
# this PR: https://github.com/huggingface/diffusers/pull/5134.
total_downscale_factor = total_downscale_factor // 2
# Resize the T2I-Adapter input image. # Resize the T2I-Adapter input image.
# We select the resize dimensions so that after the T2I-Adapter's total_downscale_factor is applied, the # We select the resize dimensions so that after the T2I-Adapter's total_downscale_factor is applied, the
@@ -643,11 +653,11 @@ class DenoiseLatentsInvocation(BaseInvocation):
seed = None seed = None
noise = None noise = None
if self.noise is not None: if self.noise is not None:
noise = context.get_latents(self.noise.latents_name) noise = context.services.latents.get(self.noise.latents_name)
seed = self.noise.seed seed = self.noise.seed
if self.latents is not None: if self.latents is not None:
latents = context.get_latents(self.latents.latents_name) latents = context.services.latents.get(self.latents.latents_name)
if seed is None: if seed is None:
seed = self.latents.seed seed = self.latents.seed
@@ -673,26 +683,35 @@ class DenoiseLatentsInvocation(BaseInvocation):
do_classifier_free_guidance=True, do_classifier_free_guidance=True,
) )
# Get the source node id (we are invoking the prepared node)
graph_execution_state = context.services.graph_execution_manager.get(context.graph_execution_state_id)
source_node_id = graph_execution_state.prepared_source_mapping[self.id]
def step_callback(state: PipelineIntermediateState): def step_callback(state: PipelineIntermediateState):
self.dispatch_progress(context, state, self.unet.unet.base_model) self.dispatch_progress(context, source_node_id, state, self.unet.unet.base_model)
def _lora_loader(): def _lora_loader():
for lora in self.unet.loras: for lora in self.unet.loras:
lora_info = context.get_model( lora_info = context.services.model_manager.get_model(
**lora.model_dump(exclude={"weight"}), **lora.model_dump(exclude={"weight"}),
context=context,
) )
yield (lora_info.context.model, lora.weight) yield (lora_info.context.model, lora.weight)
del lora_info del lora_info
return return
unet_info = context.get_model( unet_info = context.services.model_manager.get_model(
**self.unet.unet.model_dump(), **self.unet.unet.model_dump(),
context=context,
) )
with ( with (
ExitStack() as exit_stack, ExitStack() as exit_stack,
ModelPatcher.apply_lora_unet(unet_info.context.model, _lora_loader()), ModelPatcher.apply_lora_unet(unet_info.context.model, _lora_loader()),
ModelPatcher.apply_freeu(unet_info.context.model, self.unet.freeu_config),
set_seamless(unet_info.context.model, self.unet.seamless_axes), set_seamless(unet_info.context.model, self.unet.seamless_axes),
unet_info as unet, unet_info as unet,
# Apply the LoRA after unet has been moved to its target device for faster patching.
ModelPatcher.apply_lora_unet(unet, _lora_loader()),
): ):
latents = latents.to(device=unet.device, dtype=unet.dtype) latents = latents.to(device=unet.device, dtype=unet.dtype)
if noise is not None: if noise is not None:
@@ -761,8 +780,9 @@ class DenoiseLatentsInvocation(BaseInvocation):
if choose_torch_device() == torch.device("mps"): if choose_torch_device() == torch.device("mps"):
mps.empty_cache() mps.empty_cache()
latents_name = context.save_latents(result_latents) name = f"{context.graph_execution_state_id}__{self.id}"
return build_latents_output(latents_name=latents_name, latents=result_latents, seed=seed) context.services.latents.save(name, result_latents)
return build_latents_output(latents_name=name, latents=result_latents, seed=seed)
@invocation( @invocation(
@@ -772,7 +792,7 @@ class DenoiseLatentsInvocation(BaseInvocation):
category="latents", category="latents",
version="1.0.0", version="1.0.0",
) )
class LatentsToImageInvocation(BaseInvocation): class LatentsToImageInvocation(BaseInvocation, WithMetadata, WithWorkflow):
"""Generates an image from latents.""" """Generates an image from latents."""
latents: LatentsField = InputField( latents: LatentsField = InputField(
@@ -785,18 +805,14 @@ class LatentsToImageInvocation(BaseInvocation):
) )
tiled: bool = InputField(default=False, description=FieldDescriptions.tiled) tiled: bool = InputField(default=False, description=FieldDescriptions.tiled)
fp32: bool = InputField(default=DEFAULT_PRECISION == "float32", description=FieldDescriptions.fp32) fp32: bool = InputField(default=DEFAULT_PRECISION == "float32", description=FieldDescriptions.fp32)
metadata: Optional[CoreMetadata] = InputField(
default=None,
description=FieldDescriptions.core_metadata,
ui_hidden=True,
)
@torch.no_grad() @torch.no_grad()
def invoke(self, context: InvocationContext) -> ImageOutput: def invoke(self, context: InvocationContext) -> ImageOutput:
latents = context.get_latents(self.latents.latents_name) latents = context.services.latents.get(self.latents.latents_name)
vae_info = context.get_model( vae_info = context.services.model_manager.get_model(
**self.vae.vae.model_dump(), **self.vae.vae.model_dump(),
context=context,
) )
with set_seamless(vae_info.context.model, self.vae.seamless_axes), vae_info as vae: with set_seamless(vae_info.context.model, self.vae.seamless_axes), vae_info as vae:
@@ -826,7 +842,7 @@ class LatentsToImageInvocation(BaseInvocation):
vae.to(dtype=torch.float16) vae.to(dtype=torch.float16)
latents = latents.half() latents = latents.half()
if self.tiled or context.config.tiled_decode: if self.tiled or context.services.configuration.tiled_decode:
vae.enable_tiling() vae.enable_tiling()
else: else:
vae.disable_tiling() vae.disable_tiling()
@@ -850,12 +866,21 @@ class LatentsToImageInvocation(BaseInvocation):
if choose_torch_device() == torch.device("mps"): if choose_torch_device() == torch.device("mps"):
mps.empty_cache() mps.empty_cache()
image_name = context.save_image(image, category=context.categories.GENERAL) image_dto = context.services.images.create(
image=image,
image_origin=ResourceOrigin.INTERNAL,
image_category=ImageCategory.GENERAL,
node_id=self.id,
session_id=context.graph_execution_state_id,
is_intermediate=self.is_intermediate,
metadata=self.metadata,
workflow=self.workflow,
)
return ImageOutput( return ImageOutput(
image=ImageField(image_name=image_name), image=ImageField(image_name=image_dto.image_name),
width=image.width, width=image_dto.width,
height=image.height, height=image_dto.height,
) )
@@ -1080,7 +1105,7 @@ class BlendLatentsInvocation(BaseInvocation):
latents_b = context.services.latents.get(self.latents_b.latents_name) latents_b = context.services.latents.get(self.latents_b.latents_name)
if latents_a.shape != latents_b.shape: if latents_a.shape != latents_b.shape:
raise "Latents to blend must be the same size." raise Exception("Latents to blend must be the same size.")
# TODO: # TODO:
device = choose_torch_device() device = choose_torch_device()

View File

@@ -3,11 +3,12 @@
from typing import Literal from typing import Literal
import numpy as np import numpy as np
from pydantic import field_validator from pydantic import ValidationInfo, field_validator
from invokeai.app.invocations.primitives import FloatOutput, IntegerOutput from invokeai.app.invocations.primitives import FloatOutput, IntegerOutput
from invokeai.app.shared.fields import FieldDescriptions
from .baseinvocation import BaseInvocation, FieldDescriptions, InputField, InvocationContext, invocation from .baseinvocation import BaseInvocation, InputField, InvocationContext, invocation
@invocation("add", title="Add Integers", tags=["math", "add"], category="math", version="1.0.0") @invocation("add", title="Add Integers", tags=["math", "add"], category="math", version="1.0.0")
@@ -144,17 +145,17 @@ INTEGER_OPERATIONS = Literal[
] ]
INTEGER_OPERATIONS_LABELS = dict( INTEGER_OPERATIONS_LABELS = {
ADD="Add A+B", "ADD": "Add A+B",
SUB="Subtract A-B", "SUB": "Subtract A-B",
MUL="Multiply A*B", "MUL": "Multiply A*B",
DIV="Divide A/B", "DIV": "Divide A/B",
EXP="Exponentiate A^B", "EXP": "Exponentiate A^B",
MOD="Modulus A%B", "MOD": "Modulus A%B",
ABS="Absolute Value of A", "ABS": "Absolute Value of A",
MIN="Minimum(A,B)", "MIN": "Minimum(A,B)",
MAX="Maximum(A,B)", "MAX": "Maximum(A,B)",
) }
@invocation( @invocation(
@@ -182,16 +183,16 @@ class IntegerMathInvocation(BaseInvocation):
operation: INTEGER_OPERATIONS = InputField( operation: INTEGER_OPERATIONS = InputField(
default="ADD", description="The operation to perform", ui_choice_labels=INTEGER_OPERATIONS_LABELS default="ADD", description="The operation to perform", ui_choice_labels=INTEGER_OPERATIONS_LABELS
) )
a: int = InputField(default=0, description=FieldDescriptions.num_1) a: int = InputField(default=1, description=FieldDescriptions.num_1)
b: int = InputField(default=0, description=FieldDescriptions.num_2) b: int = InputField(default=1, description=FieldDescriptions.num_2)
@field_validator("b") @field_validator("b")
def no_unrepresentable_results(cls, v, values): def no_unrepresentable_results(cls, v: int, info: ValidationInfo):
if values["operation"] == "DIV" and v == 0: if info.data["operation"] == "DIV" and v == 0:
raise ValueError("Cannot divide by zero") raise ValueError("Cannot divide by zero")
elif values["operation"] == "MOD" and v == 0: elif info.data["operation"] == "MOD" and v == 0:
raise ValueError("Cannot divide by zero") raise ValueError("Cannot divide by zero")
elif values["operation"] == "EXP" and v < 0: elif info.data["operation"] == "EXP" and v < 0:
raise ValueError("Result of exponentiation is not an integer") raise ValueError("Result of exponentiation is not an integer")
return v return v
@@ -230,17 +231,17 @@ FLOAT_OPERATIONS = Literal[
] ]
FLOAT_OPERATIONS_LABELS = dict( FLOAT_OPERATIONS_LABELS = {
ADD="Add A+B", "ADD": "Add A+B",
SUB="Subtract A-B", "SUB": "Subtract A-B",
MUL="Multiply A*B", "MUL": "Multiply A*B",
DIV="Divide A/B", "DIV": "Divide A/B",
EXP="Exponentiate A^B", "EXP": "Exponentiate A^B",
ABS="Absolute Value of A", "ABS": "Absolute Value of A",
SQRT="Square Root of A", "SQRT": "Square Root of A",
MIN="Minimum(A,B)", "MIN": "Minimum(A,B)",
MAX="Maximum(A,B)", "MAX": "Maximum(A,B)",
) }
@invocation( @invocation(
@@ -256,16 +257,16 @@ class FloatMathInvocation(BaseInvocation):
operation: FLOAT_OPERATIONS = InputField( operation: FLOAT_OPERATIONS = InputField(
default="ADD", description="The operation to perform", ui_choice_labels=FLOAT_OPERATIONS_LABELS default="ADD", description="The operation to perform", ui_choice_labels=FLOAT_OPERATIONS_LABELS
) )
a: float = InputField(default=0, description=FieldDescriptions.num_1) a: float = InputField(default=1, description=FieldDescriptions.num_1)
b: float = InputField(default=0, description=FieldDescriptions.num_2) b: float = InputField(default=1, description=FieldDescriptions.num_2)
@field_validator("b") @field_validator("b")
def no_unrepresentable_results(cls, v, values): def no_unrepresentable_results(cls, v: float, info: ValidationInfo):
if values["operation"] == "DIV" and v == 0: if info.data["operation"] == "DIV" and v == 0:
raise ValueError("Cannot divide by zero") raise ValueError("Cannot divide by zero")
elif values["operation"] == "EXP" and values["a"] == 0 and v < 0: elif info.data["operation"] == "EXP" and info.data["a"] == 0 and v < 0:
raise ValueError("Cannot raise zero to a negative power") raise ValueError("Cannot raise zero to a negative power")
elif values["operation"] == "EXP" and type(values["a"] ** v) is complex: elif info.data["operation"] == "EXP" and isinstance(info.data["a"] ** v, complex):
raise ValueError("Root operation resulted in a complex number") raise ValueError("Root operation resulted in a complex number")
return v return v

View File

@@ -1,13 +1,15 @@
from typing import Optional from typing import Any, Literal, Optional, Union
from pydantic import Field from pydantic import BaseModel, ConfigDict, Field
from invokeai.app.invocations.baseinvocation import ( from invokeai.app.invocations.baseinvocation import (
BaseInvocation, BaseInvocation,
BaseInvocationOutput, BaseInvocationOutput,
InputField, InputField,
InvocationContext, InvocationContext,
MetadataField,
OutputField, OutputField,
UIType,
invocation, invocation,
invocation_output, invocation_output,
) )
@@ -16,116 +18,105 @@ from invokeai.app.invocations.ip_adapter import IPAdapterModelField
from invokeai.app.invocations.model import LoRAModelField, MainModelField, VAEModelField from invokeai.app.invocations.model import LoRAModelField, MainModelField, VAEModelField
from invokeai.app.invocations.primitives import ImageField from invokeai.app.invocations.primitives import ImageField
from invokeai.app.invocations.t2i_adapter import T2IAdapterField from invokeai.app.invocations.t2i_adapter import T2IAdapterField
from invokeai.app.util.model_exclude_null import BaseModelExcludeNull from invokeai.app.shared.fields import FieldDescriptions
from ...version import __version__ from ...version import __version__
class LoRAMetadataField(BaseModelExcludeNull): class MetadataItemField(BaseModel):
"""LoRA metadata for an image generated in InvokeAI.""" label: str = Field(description=FieldDescriptions.metadata_item_label)
value: Any = Field(description=FieldDescriptions.metadata_item_value)
lora: LoRAModelField = Field(description="The LoRA model")
weight: float = Field(description="The weight of the LoRA model")
class IPAdapterMetadataField(BaseModelExcludeNull): class LoRAMetadataField(BaseModel):
"""LoRA Metadata Field"""
lora: LoRAModelField = Field(description=FieldDescriptions.lora_model)
weight: float = Field(description=FieldDescriptions.lora_weight)
class IPAdapterMetadataField(BaseModel):
"""IP Adapter Field, minus the CLIP Vision Encoder model"""
image: ImageField = Field(description="The IP-Adapter image prompt.") image: ImageField = Field(description="The IP-Adapter image prompt.")
ip_adapter_model: IPAdapterModelField = Field(description="The IP-Adapter model to use.") ip_adapter_model: IPAdapterModelField = Field(
weight: float = Field(description="The weight of the IP-Adapter model") description="The IP-Adapter model.",
begin_step_percent: float = Field(
default=0, ge=0, le=1, description="When the IP-Adapter is first applied (% of total steps)"
) )
end_step_percent: float = Field( weight: Union[float, list[float]] = Field(
default=1, ge=0, le=1, description="When the IP-Adapter is last applied (% of total steps)" description="The weight given to the IP-Adapter",
)
begin_step_percent: float = Field(description="When the IP-Adapter is first applied (% of total steps)")
end_step_percent: float = Field(description="When the IP-Adapter is last applied (% of total steps)")
@invocation_output("metadata_item_output")
class MetadataItemOutput(BaseInvocationOutput):
"""Metadata Item Output"""
item: MetadataItemField = OutputField(description="Metadata Item")
@invocation("metadata_item", title="Metadata Item", tags=["metadata"], category="metadata", version="1.0.0")
class MetadataItemInvocation(BaseInvocation):
"""Used to create an arbitrary metadata item. Provide "label" and make a connection to "value" to store that data as the value."""
label: str = InputField(description=FieldDescriptions.metadata_item_label)
value: Any = InputField(description=FieldDescriptions.metadata_item_value, ui_type=UIType.Any)
def invoke(self, context: InvocationContext) -> MetadataItemOutput:
return MetadataItemOutput(item=MetadataItemField(label=self.label, value=self.value))
@invocation_output("metadata_output")
class MetadataOutput(BaseInvocationOutput):
metadata: MetadataField = OutputField(description="Metadata Dict")
@invocation("metadata", title="Metadata", tags=["metadata"], category="metadata", version="1.0.0")
class MetadataInvocation(BaseInvocation):
"""Takes a MetadataItem or collection of MetadataItems and outputs a MetadataDict."""
items: Union[list[MetadataItemField], MetadataItemField] = InputField(
description=FieldDescriptions.metadata_item_polymorphic
) )
def invoke(self, context: InvocationContext) -> MetadataOutput:
if isinstance(self.items, MetadataItemField):
# single metadata item
data = {self.items.label: self.items.value}
else:
# collection of metadata items
data = {item.label: item.value for item in self.items}
class CoreMetadata(BaseModelExcludeNull): # add app version
"""Core generation metadata for an image generated in InvokeAI.""" data.update({"app_version": __version__})
return MetadataOutput(metadata=MetadataField.model_validate(data))
app_version: str = Field(default=__version__, description="The version of InvokeAI used to generate this image")
generation_mode: Optional[str] = Field(
default=None,
description="The generation mode that output this image",
)
created_by: Optional[str] = Field(description="The name of the creator of the image")
positive_prompt: Optional[str] = Field(default=None, description="The positive prompt parameter")
negative_prompt: Optional[str] = Field(default=None, description="The negative prompt parameter")
width: Optional[int] = Field(default=None, description="The width parameter")
height: Optional[int] = Field(default=None, description="The height parameter")
seed: Optional[int] = Field(default=None, description="The seed used for noise generation")
rand_device: Optional[str] = Field(default=None, description="The device used for random number generation")
cfg_scale: Optional[float] = Field(default=None, description="The classifier-free guidance scale parameter")
steps: Optional[int] = Field(default=None, description="The number of steps used for inference")
scheduler: Optional[str] = Field(default=None, description="The scheduler used for inference")
clip_skip: Optional[int] = Field(
default=None,
description="The number of skipped CLIP layers",
)
model: Optional[MainModelField] = Field(default=None, description="The main model used for inference")
controlnets: Optional[list[ControlField]] = Field(default=None, description="The ControlNets used for inference")
ipAdapters: Optional[list[IPAdapterMetadataField]] = Field(
default=None, description="The IP Adapters used for inference"
)
t2iAdapters: Optional[list[T2IAdapterField]] = Field(default=None, description="The IP Adapters used for inference")
loras: Optional[list[LoRAMetadataField]] = Field(default=None, description="The LoRAs used for inference")
vae: Optional[VAEModelField] = Field(
default=None,
description="The VAE used for decoding, if the main model's default was not used",
)
# Latents-to-Latents
strength: Optional[float] = Field(
default=None,
description="The strength used for latents-to-latents",
)
init_image: Optional[str] = Field(default=None, description="The name of the initial image")
# SDXL
positive_style_prompt: Optional[str] = Field(default=None, description="The positive style prompt parameter")
negative_style_prompt: Optional[str] = Field(default=None, description="The negative style prompt parameter")
# SDXL Refiner
refiner_model: Optional[MainModelField] = Field(default=None, description="The SDXL Refiner model used")
refiner_cfg_scale: Optional[float] = Field(
default=None,
description="The classifier-free guidance scale parameter used for the refiner",
)
refiner_steps: Optional[int] = Field(default=None, description="The number of steps used for the refiner")
refiner_scheduler: Optional[str] = Field(default=None, description="The scheduler used for the refiner")
refiner_positive_aesthetic_score: Optional[float] = Field(
default=None, description="The aesthetic score used for the refiner"
)
refiner_negative_aesthetic_score: Optional[float] = Field(
default=None, description="The aesthetic score used for the refiner"
)
refiner_start: Optional[float] = Field(default=None, description="The start value used for refiner denoising")
class ImageMetadata(BaseModelExcludeNull): @invocation("merge_metadata", title="Metadata Merge", tags=["metadata"], category="metadata", version="1.0.0")
"""An image's generation metadata""" class MergeMetadataInvocation(BaseInvocation):
"""Merged a collection of MetadataDict into a single MetadataDict."""
metadata: Optional[dict] = Field( collection: list[MetadataField] = InputField(description=FieldDescriptions.metadata_collection)
default=None,
description="The image's core metadata, if it was created in the Linear or Canvas UI", def invoke(self, context: InvocationContext) -> MetadataOutput:
) data = {}
graph: Optional[dict] = Field(default=None, description="The graph that created the image") for item in self.collection:
data.update(item.model_dump())
return MetadataOutput(metadata=MetadataField.model_validate(data))
@invocation_output("metadata_accumulator_output") GENERATION_MODES = Literal[
class MetadataAccumulatorOutput(BaseInvocationOutput): "txt2img", "img2img", "inpaint", "outpaint", "sdxl_txt2img", "sdxl_img2img", "sdxl_inpaint", "sdxl_outpaint"
"""The output of the MetadataAccumulator node""" ]
metadata: CoreMetadata = OutputField(description="The core metadata for the image")
@invocation( @invocation("core_metadata", title="Core Metadata", tags=["metadata"], category="metadata", version="1.0.0")
"metadata_accumulator", title="Metadata Accumulator", tags=["metadata"], category="metadata", version="1.0.0" class CoreMetadataInvocation(BaseInvocation):
) """Collects core generation metadata into a MetadataField"""
class MetadataAccumulatorInvocation(BaseInvocation):
"""Outputs a Core Metadata Object"""
generation_mode: Optional[str] = InputField( generation_mode: Optional[GENERATION_MODES] = InputField(
default=None, default=None,
description="The generation mode that output this image", description="The generation mode that output this image",
) )
@@ -138,6 +129,8 @@ class MetadataAccumulatorInvocation(BaseInvocation):
cfg_scale: Optional[float] = InputField(default=None, description="The classifier-free guidance scale parameter") cfg_scale: Optional[float] = InputField(default=None, description="The classifier-free guidance scale parameter")
steps: Optional[int] = InputField(default=None, description="The number of steps used for inference") steps: Optional[int] = InputField(default=None, description="The number of steps used for inference")
scheduler: Optional[str] = InputField(default=None, description="The scheduler used for inference") scheduler: Optional[str] = InputField(default=None, description="The scheduler used for inference")
seamless_x: Optional[bool] = InputField(default=None, description="Whether seamless tiling was used on the X axis")
seamless_y: Optional[bool] = InputField(default=None, description="Whether seamless tiling was used on the Y axis")
clip_skip: Optional[int] = InputField( clip_skip: Optional[int] = InputField(
default=None, default=None,
description="The number of skipped CLIP layers", description="The number of skipped CLIP layers",
@@ -167,13 +160,14 @@ class MetadataAccumulatorInvocation(BaseInvocation):
) )
# High resolution fix metadata. # High resolution fix metadata.
hrf_width: Optional[int] = InputField( hrf_enabled: Optional[float] = InputField(
default=None, default=None,
description="The high resolution fix height and width multipler.", description="Whether or not high resolution fix was enabled.",
) )
hrf_height: Optional[int] = InputField( # TODO: should this be stricter or do we just let the UI handle it?
hrf_method: Optional[str] = InputField(
default=None, default=None,
description="The high resolution fix height and width multipler.", description="The high resolution fix upscale method.",
) )
hrf_strength: Optional[float] = InputField( hrf_strength: Optional[float] = InputField(
default=None, default=None,
@@ -220,7 +214,13 @@ class MetadataAccumulatorInvocation(BaseInvocation):
description="The start value used for refiner denoising", description="The start value used for refiner denoising",
) )
def invoke(self, context: InvocationContext) -> MetadataAccumulatorOutput: def invoke(self, context: InvocationContext) -> MetadataOutput:
"""Collects and outputs a CoreMetadata object""" """Collects and outputs a CoreMetadata object"""
return MetadataAccumulatorOutput(metadata=CoreMetadata(**self.model_dump())) return MetadataOutput(
metadata=MetadataField.model_validate(
self.model_dump(exclude_none=True, exclude={"id", "type", "is_intermediate", "use_cache"})
)
)
model_config = ConfigDict(extra="allow")

View File

@@ -3,11 +3,13 @@ from typing import List, Optional
from pydantic import BaseModel, ConfigDict, Field from pydantic import BaseModel, ConfigDict, Field
from invokeai.app.shared.fields import FieldDescriptions
from invokeai.app.shared.models import FreeUConfig
from ...backend.model_management import BaseModelType, ModelType, SubModelType from ...backend.model_management import BaseModelType, ModelType, SubModelType
from .baseinvocation import ( from .baseinvocation import (
BaseInvocation, BaseInvocation,
BaseInvocationOutput, BaseInvocationOutput,
FieldDescriptions,
Input, Input,
InputField, InputField,
InvocationContext, InvocationContext,
@@ -36,6 +38,7 @@ class UNetField(BaseModel):
scheduler: ModelInfo = Field(description="Info to load scheduler submodel") scheduler: ModelInfo = Field(description="Info to load scheduler submodel")
loras: List[LoraInfo] = Field(description="Loras to apply on model loading") loras: List[LoraInfo] = Field(description="Loras to apply on model loading")
seamless_axes: List[str] = Field(default_factory=list, description='Axes("x" and "y") to which apply seamless') seamless_axes: List[str] = Field(default_factory=list, description='Axes("x" and "y") to which apply seamless')
freeu_config: Optional[FreeUConfig] = Field(default=None, description="FreeU configuration")
class ClipField(BaseModel): class ClipField(BaseModel):
@@ -51,15 +54,34 @@ class VaeField(BaseModel):
seamless_axes: List[str] = Field(default_factory=list, description='Axes("x" and "y") to which apply seamless') seamless_axes: List[str] = Field(default_factory=list, description='Axes("x" and "y") to which apply seamless')
@invocation_output("model_loader_output") @invocation_output("unet_output")
class ModelLoaderOutput(BaseInvocationOutput): class UNetOutput(BaseInvocationOutput):
"""Model loader output""" """Base class for invocations that output a UNet field"""
unet: UNetField = OutputField(description=FieldDescriptions.unet, title="UNet") unet: UNetField = OutputField(description=FieldDescriptions.unet, title="UNet")
clip: ClipField = OutputField(description=FieldDescriptions.clip, title="CLIP")
@invocation_output("vae_output")
class VAEOutput(BaseInvocationOutput):
"""Base class for invocations that output a VAE field"""
vae: VaeField = OutputField(description=FieldDescriptions.vae, title="VAE") vae: VaeField = OutputField(description=FieldDescriptions.vae, title="VAE")
@invocation_output("clip_output")
class CLIPOutput(BaseInvocationOutput):
"""Base class for invocations that output a CLIP field"""
clip: ClipField = OutputField(description=FieldDescriptions.clip, title="CLIP")
@invocation_output("model_loader_output")
class ModelLoaderOutput(UNetOutput, CLIPOutput, VAEOutput):
"""Model loader output"""
pass
class MainModelField(BaseModel): class MainModelField(BaseModel):
"""Main model field""" """Main model field"""
@@ -98,7 +120,7 @@ class MainModelLoaderInvocation(BaseInvocation):
model_type = ModelType.Main model_type = ModelType.Main
# TODO: not found exceptions # TODO: not found exceptions
if not context.model_exists( if not context.services.model_manager.model_exists(
model_name=model_name, model_name=model_name,
base_model=base_model, base_model=base_model,
model_type=model_type, model_type=model_type,
@@ -366,13 +388,6 @@ class VAEModelField(BaseModel):
model_config = ConfigDict(protected_namespaces=()) model_config = ConfigDict(protected_namespaces=())
@invocation_output("vae_loader_output")
class VaeLoaderOutput(BaseInvocationOutput):
"""VAE output"""
vae: VaeField = OutputField(description=FieldDescriptions.vae, title="VAE")
@invocation("vae_loader", title="VAE", tags=["vae", "model"], category="model", version="1.0.0") @invocation("vae_loader", title="VAE", tags=["vae", "model"], category="model", version="1.0.0")
class VaeLoaderInvocation(BaseInvocation): class VaeLoaderInvocation(BaseInvocation):
"""Loads a VAE model, outputting a VaeLoaderOutput""" """Loads a VAE model, outputting a VaeLoaderOutput"""
@@ -384,7 +399,7 @@ class VaeLoaderInvocation(BaseInvocation):
title="VAE", title="VAE",
) )
def invoke(self, context: InvocationContext) -> VaeLoaderOutput: def invoke(self, context: InvocationContext) -> VAEOutput:
base_model = self.vae_model.base_model base_model = self.vae_model.base_model
model_name = self.vae_model.model_name model_name = self.vae_model.model_name
model_type = ModelType.Vae model_type = ModelType.Vae
@@ -395,7 +410,7 @@ class VaeLoaderInvocation(BaseInvocation):
model_type=model_type, model_type=model_type,
): ):
raise Exception(f"Unkown vae name: {model_name}!") raise Exception(f"Unkown vae name: {model_name}!")
return VaeLoaderOutput( return VAEOutput(
vae=VaeField( vae=VaeField(
vae=ModelInfo( vae=ModelInfo(
model_name=model_name, model_name=model_name,
@@ -457,3 +472,24 @@ class SeamlessModeInvocation(BaseInvocation):
vae.seamless_axes = seamless_axes_list vae.seamless_axes = seamless_axes_list
return SeamlessModeOutput(unet=unet, vae=vae) return SeamlessModeOutput(unet=unet, vae=vae)
@invocation("freeu", title="FreeU", tags=["freeu"], category="unet", version="1.0.0")
class FreeUInvocation(BaseInvocation):
"""
Applies FreeU to the UNet. Suggested values (b1/b2/s1/s2):
SD1.5: 1.2/1.4/0.9/0.2,
SD2: 1.1/1.2/0.9/0.2,
SDXL: 1.1/1.2/0.6/0.4,
"""
unet: UNetField = InputField(description=FieldDescriptions.unet, input=Input.Connection, title="UNet")
b1: float = InputField(default=1.2, ge=-1, le=3, description=FieldDescriptions.freeu_b1)
b2: float = InputField(default=1.4, ge=-1, le=3, description=FieldDescriptions.freeu_b2)
s1: float = InputField(default=0.9, ge=-1, le=3, description=FieldDescriptions.freeu_s1)
s2: float = InputField(default=0.2, ge=-1, le=3, description=FieldDescriptions.freeu_s2)
def invoke(self, context: InvocationContext) -> UNetOutput:
self.unet.freeu_config = FreeUConfig(s1=self.s1, s2=self.s2, b1=self.b1, b2=self.b2)
return UNetOutput(unet=self.unet)

View File

@@ -5,13 +5,13 @@ import torch
from pydantic import field_validator from pydantic import field_validator
from invokeai.app.invocations.latent import LatentsField from invokeai.app.invocations.latent import LatentsField
from invokeai.app.shared.fields import FieldDescriptions
from invokeai.app.util.misc import SEED_MAX, get_random_seed from invokeai.app.util.misc import SEED_MAX, get_random_seed
from ...backend.util.devices import choose_torch_device, torch_dtype from ...backend.util.devices import choose_torch_device, torch_dtype
from .baseinvocation import ( from .baseinvocation import (
BaseInvocation, BaseInvocation,
BaseInvocationOutput, BaseInvocationOutput,
FieldDescriptions,
InputField, InputField,
InvocationContext, InvocationContext,
OutputField, OutputField,
@@ -124,5 +124,6 @@ class NoiseInvocation(BaseInvocation):
seed=self.seed, seed=self.seed,
use_cpu=self.use_cpu, use_cpu=self.use_cpu,
) )
latents_name = context.save_latents(noise) name = f"{context.graph_execution_state_id}__{self.id}"
return build_noise_output(latents_name=latents_name, latents=noise, seed=self.seed) context.services.latents.save(name, noise)
return build_noise_output(latents_name=name, latents=noise, seed=self.seed)

View File

@@ -4,7 +4,7 @@ import inspect
import re import re
# from contextlib import ExitStack # from contextlib import ExitStack
from typing import List, Literal, Optional, Union from typing import List, Literal, Union
import numpy as np import numpy as np
import torch import torch
@@ -12,9 +12,9 @@ from diffusers.image_processor import VaeImageProcessor
from pydantic import BaseModel, ConfigDict, Field, field_validator from pydantic import BaseModel, ConfigDict, Field, field_validator
from tqdm import tqdm from tqdm import tqdm
from invokeai.app.invocations.metadata import CoreMetadata
from invokeai.app.invocations.primitives import ConditioningField, ConditioningOutput, ImageField, ImageOutput from invokeai.app.invocations.primitives import ConditioningField, ConditioningOutput, ImageField, ImageOutput
from invokeai.app.services.image_records.image_records_common import ImageCategory, ResourceOrigin from invokeai.app.services.image_records.image_records_common import ImageCategory, ResourceOrigin
from invokeai.app.shared.fields import FieldDescriptions
from invokeai.app.util.step_callback import stable_diffusion_step_callback from invokeai.app.util.step_callback import stable_diffusion_step_callback
from invokeai.backend import BaseModelType, ModelType, SubModelType from invokeai.backend import BaseModelType, ModelType, SubModelType
@@ -24,13 +24,14 @@ from ...backend.util import choose_torch_device
from .baseinvocation import ( from .baseinvocation import (
BaseInvocation, BaseInvocation,
BaseInvocationOutput, BaseInvocationOutput,
FieldDescriptions,
Input, Input,
InputField, InputField,
InvocationContext, InvocationContext,
OutputField, OutputField,
UIComponent, UIComponent,
UIType, UIType,
WithMetadata,
WithWorkflow,
invocation, invocation,
invocation_output, invocation_output,
) )
@@ -53,7 +54,7 @@ ORT_TO_NP_TYPE = {
"tensor(double)": np.float64, "tensor(double)": np.float64,
} }
PRECISION_VALUES = Literal[tuple(list(ORT_TO_NP_TYPE.keys()))] PRECISION_VALUES = Literal[tuple(ORT_TO_NP_TYPE.keys())]
@invocation("prompt_onnx", title="ONNX Prompt (Raw)", tags=["prompt", "onnx"], category="conditioning", version="1.0.0") @invocation("prompt_onnx", title="ONNX Prompt (Raw)", tags=["prompt", "onnx"], category="conditioning", version="1.0.0")
@@ -251,7 +252,7 @@ class ONNXTextToLatentsInvocation(BaseInvocation):
scheduler.set_timesteps(self.steps) scheduler.set_timesteps(self.steps)
latents = latents * np.float64(scheduler.init_noise_sigma) latents = latents * np.float64(scheduler.init_noise_sigma)
extra_step_kwargs = dict() extra_step_kwargs = {}
if "eta" in set(inspect.signature(scheduler.step).parameters.keys()): if "eta" in set(inspect.signature(scheduler.step).parameters.keys()):
extra_step_kwargs.update( extra_step_kwargs.update(
eta=0.0, eta=0.0,
@@ -327,7 +328,7 @@ class ONNXTextToLatentsInvocation(BaseInvocation):
category="image", category="image",
version="1.0.0", version="1.0.0",
) )
class ONNXLatentsToImageInvocation(BaseInvocation): class ONNXLatentsToImageInvocation(BaseInvocation, WithMetadata, WithWorkflow):
"""Generates an image from latents.""" """Generates an image from latents."""
latents: LatentsField = InputField( latents: LatentsField = InputField(
@@ -338,11 +339,6 @@ class ONNXLatentsToImageInvocation(BaseInvocation):
description=FieldDescriptions.vae, description=FieldDescriptions.vae,
input=Input.Connection, input=Input.Connection,
) )
metadata: Optional[CoreMetadata] = InputField(
default=None,
description=FieldDescriptions.core_metadata,
ui_hidden=True,
)
# tiled: bool = InputField(default=False, description="Decode latents by overlaping tiles(less memory consumption)") # tiled: bool = InputField(default=False, description="Decode latents by overlaping tiles(less memory consumption)")
def invoke(self, context: InvocationContext) -> ImageOutput: def invoke(self, context: InvocationContext) -> ImageOutput:
@@ -381,7 +377,7 @@ class ONNXLatentsToImageInvocation(BaseInvocation):
node_id=self.id, node_id=self.id,
session_id=context.graph_execution_state_id, session_id=context.graph_execution_state_id,
is_intermediate=self.is_intermediate, is_intermediate=self.is_intermediate,
metadata=self.metadata.model_dump() if self.metadata else None, metadata=self.metadata,
workflow=self.workflow, workflow=self.workflow,
) )

View File

@@ -100,7 +100,7 @@ EASING_FUNCTIONS_MAP = {
"BounceInOut": BounceEaseInOut, "BounceInOut": BounceEaseInOut,
} }
EASING_FUNCTION_KEYS = Literal[tuple(list(EASING_FUNCTIONS_MAP.keys()))] EASING_FUNCTION_KEYS = Literal[tuple(EASING_FUNCTIONS_MAP.keys())]
# actually I think for now could just use CollectionOutput (which is list[Any] # actually I think for now could just use CollectionOutput (which is list[Any]
@@ -161,7 +161,7 @@ class StepParamEasingInvocation(BaseInvocation):
easing_class = EASING_FUNCTIONS_MAP[self.easing] easing_class = EASING_FUNCTIONS_MAP[self.easing]
if log_diagnostics: if log_diagnostics:
context.services.logger.debug("easing class: " + str(easing_class)) context.services.logger.debug("easing class: " + str(easing_class))
easing_list = list() easing_list = []
if self.mirror: # "expected" mirroring if self.mirror: # "expected" mirroring
# if number of steps is even, squeeze duration down to (number_of_steps)/2 # if number of steps is even, squeeze duration down to (number_of_steps)/2
# and create reverse copy of list to append # and create reverse copy of list to append
@@ -178,7 +178,7 @@ class StepParamEasingInvocation(BaseInvocation):
end=self.end_value, end=self.end_value,
duration=base_easing_duration - 1, duration=base_easing_duration - 1,
) )
base_easing_vals = list() base_easing_vals = []
for step_index in range(base_easing_duration): for step_index in range(base_easing_duration):
easing_val = easing_function.ease(step_index) easing_val = easing_function.ease(step_index)
base_easing_vals.append(easing_val) base_easing_vals.append(easing_val)

View File

@@ -5,10 +5,11 @@ from typing import Optional, Tuple
import torch import torch
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
from invokeai.app.shared.fields import FieldDescriptions
from .baseinvocation import ( from .baseinvocation import (
BaseInvocation, BaseInvocation,
BaseInvocationOutput, BaseInvocationOutput,
FieldDescriptions,
Input, Input,
InputField, InputField,
InvocationContext, InvocationContext,
@@ -251,7 +252,9 @@ class ImageCollectionOutput(BaseInvocationOutput):
@invocation("image", title="Image Primitive", tags=["primitives", "image"], category="primitives", version="1.0.0") @invocation("image", title="Image Primitive", tags=["primitives", "image"], category="primitives", version="1.0.0")
class ImageInvocation(BaseInvocation): class ImageInvocation(
BaseInvocation,
):
"""An image primitive value""" """An image primitive value"""
image: ImageField = InputField(description="The image to load") image: ImageField = InputField(description="The image to load")
@@ -291,7 +294,7 @@ class DenoiseMaskField(BaseModel):
"""An inpaint mask field""" """An inpaint mask field"""
mask_name: str = Field(description="The name of the mask image") mask_name: str = Field(description="The name of the mask image")
masked_latents_name: Optional[str] = Field(description="The name of the masked image latents") masked_latents_name: Optional[str] = Field(default=None, description="The name of the masked image latents")
@invocation_output("denoise_mask_output") @invocation_output("denoise_mask_output")

View File

@@ -1,8 +1,9 @@
from invokeai.app.shared.fields import FieldDescriptions
from ...backend.model_management import ModelType, SubModelType from ...backend.model_management import ModelType, SubModelType
from .baseinvocation import ( from .baseinvocation import (
BaseInvocation, BaseInvocation,
BaseInvocationOutput, BaseInvocationOutput,
FieldDescriptions,
Input, Input,
InputField, InputField,
InvocationContext, InvocationContext,

View File

@@ -5,7 +5,6 @@ from pydantic import BaseModel, ConfigDict, Field
from invokeai.app.invocations.baseinvocation import ( from invokeai.app.invocations.baseinvocation import (
BaseInvocation, BaseInvocation,
BaseInvocationOutput, BaseInvocationOutput,
FieldDescriptions,
Input, Input,
InputField, InputField,
InvocationContext, InvocationContext,
@@ -16,6 +15,7 @@ from invokeai.app.invocations.baseinvocation import (
) )
from invokeai.app.invocations.controlnet_image_processors import CONTROLNET_RESIZE_VALUES from invokeai.app.invocations.controlnet_image_processors import CONTROLNET_RESIZE_VALUES
from invokeai.app.invocations.primitives import ImageField from invokeai.app.invocations.primitives import ImageField
from invokeai.app.shared.fields import FieldDescriptions
from invokeai.backend.model_management.models.base import BaseModelType from invokeai.backend.model_management.models.base import BaseModelType

View File

@@ -14,7 +14,7 @@ from invokeai.app.invocations.primitives import ImageField, ImageOutput
from invokeai.app.services.image_records.image_records_common import ImageCategory, ResourceOrigin from invokeai.app.services.image_records.image_records_common import ImageCategory, ResourceOrigin
from invokeai.backend.util.devices import choose_torch_device from invokeai.backend.util.devices import choose_torch_device
from .baseinvocation import BaseInvocation, InputField, InvocationContext, invocation from .baseinvocation import BaseInvocation, InputField, InvocationContext, WithMetadata, WithWorkflow, invocation
# TODO: Populate this from disk? # TODO: Populate this from disk?
# TODO: Use model manager to load? # TODO: Use model manager to load?
@@ -30,7 +30,7 @@ if choose_torch_device() == torch.device("mps"):
@invocation("esrgan", title="Upscale (RealESRGAN)", tags=["esrgan", "upscale"], category="esrgan", version="1.1.0") @invocation("esrgan", title="Upscale (RealESRGAN)", tags=["esrgan", "upscale"], category="esrgan", version="1.1.0")
class ESRGANInvocation(BaseInvocation): class ESRGANInvocation(BaseInvocation, WithWorkflow, WithMetadata):
"""Upscales an image using RealESRGAN.""" """Upscales an image using RealESRGAN."""
image: ImageField = InputField(description="The input image") image: ImageField = InputField(description="The input image")
@@ -123,6 +123,7 @@ class ESRGANInvocation(BaseInvocation):
node_id=self.id, node_id=self.id,
session_id=context.graph_execution_state_id, session_id=context.graph_execution_state_id,
is_intermediate=self.is_intermediate, is_intermediate=self.is_intermediate,
metadata=self.metadata,
workflow=self.workflow, workflow=self.workflow,
) )

View File

@@ -139,7 +139,7 @@ class SqliteBoardImageRecordStorage(BoardImageRecordStorageBase):
(board_id,), (board_id,),
) )
result = cast(list[sqlite3.Row], self._cursor.fetchall()) result = cast(list[sqlite3.Row], self._cursor.fetchall())
images = list(map(lambda r: deserialize_image_record(dict(r)), result)) images = [deserialize_image_record(dict(r)) for r in result]
self._cursor.execute( self._cursor.execute(
"""--sql """--sql
@@ -167,7 +167,7 @@ class SqliteBoardImageRecordStorage(BoardImageRecordStorageBase):
(board_id,), (board_id,),
) )
result = cast(list[sqlite3.Row], self._cursor.fetchall()) result = cast(list[sqlite3.Row], self._cursor.fetchall())
image_names = list(map(lambda r: r[0], result)) image_names = [r[0] for r in result]
return image_names return image_names
except sqlite3.Error as e: except sqlite3.Error as e:
self._conn.rollback() self._conn.rollback()

View File

@@ -199,7 +199,7 @@ class SqliteBoardRecordStorage(BoardRecordStorageBase):
) )
result = cast(list[sqlite3.Row], self._cursor.fetchall()) result = cast(list[sqlite3.Row], self._cursor.fetchall())
boards = list(map(lambda r: deserialize_board_record(dict(r)), result)) boards = [deserialize_board_record(dict(r)) for r in result]
# Get the total number of boards # Get the total number of boards
self._cursor.execute( self._cursor.execute(
@@ -236,7 +236,7 @@ class SqliteBoardRecordStorage(BoardRecordStorageBase):
) )
result = cast(list[sqlite3.Row], self._cursor.fetchall()) result = cast(list[sqlite3.Row], self._cursor.fetchall())
boards = list(map(lambda r: deserialize_board_record(dict(r)), result)) boards = [deserialize_board_record(dict(r)) for r in result]
return boards return boards

View File

@@ -55,7 +55,7 @@ class InvokeAISettings(BaseSettings):
""" """
cls = self.__class__ cls = self.__class__
type = get_args(get_type_hints(cls)["type"])[0] type = get_args(get_type_hints(cls)["type"])[0]
field_dict = dict({type: dict()}) field_dict = {type: {}}
for name, field in self.model_fields.items(): for name, field in self.model_fields.items():
if name in cls._excluded_from_yaml(): if name in cls._excluded_from_yaml():
continue continue
@@ -64,7 +64,7 @@ class InvokeAISettings(BaseSettings):
) )
value = getattr(self, name) value = getattr(self, name)
if category not in field_dict[type]: if category not in field_dict[type]:
field_dict[type][category] = dict() field_dict[type][category] = {}
# keep paths as strings to make it easier to read # keep paths as strings to make it easier to read
field_dict[type][category][name] = str(value) if isinstance(value, Path) else value field_dict[type][category][name] = str(value) if isinstance(value, Path) else value
conf = OmegaConf.create(field_dict) conf = OmegaConf.create(field_dict)
@@ -89,7 +89,7 @@ class InvokeAISettings(BaseSettings):
# create an upcase version of the environment in # create an upcase version of the environment in
# order to achieve case-insensitive environment # order to achieve case-insensitive environment
# variables (the way Windows does) # variables (the way Windows does)
upcase_environ = dict() upcase_environ = {}
for key, value in os.environ.items(): for key, value in os.environ.items():
upcase_environ[key.upper()] = value upcase_environ[key.upper()] = value

View File

@@ -45,6 +45,7 @@ InvokeAI:
ram: 13.5 ram: 13.5
vram: 0.25 vram: 0.25
lazy_offload: true lazy_offload: true
log_memory_usage: false
Device: Device:
device: auto device: auto
precision: auto precision: auto
@@ -187,18 +188,18 @@ DEFAULT_MAX_VRAM = 0.5
class Categories(object): class Categories(object):
WebServer = dict(category="Web Server") WebServer = {"category": "Web Server"}
Features = dict(category="Features") Features = {"category": "Features"}
Paths = dict(category="Paths") Paths = {"category": "Paths"}
Logging = dict(category="Logging") Logging = {"category": "Logging"}
Development = dict(category="Development") Development = {"category": "Development"}
Other = dict(category="Other") Other = {"category": "Other"}
ModelCache = dict(category="Model Cache") ModelCache = {"category": "Model Cache"}
Device = dict(category="Device") Device = {"category": "Device"}
Generation = dict(category="Generation") Generation = {"category": "Generation"}
Queue = dict(category="Queue") Queue = {"category": "Queue"}
Nodes = dict(category="Nodes") Nodes = {"category": "Nodes"}
MemoryPerformance = dict(category="Memory/Performance") MemoryPerformance = {"category": "Memory/Performance"}
class InvokeAIAppConfig(InvokeAISettings): class InvokeAIAppConfig(InvokeAISettings):
@@ -243,6 +244,7 @@ class InvokeAIAppConfig(InvokeAISettings):
db_dir : Optional[Path] = Field(default=Path('databases'), description='Path to InvokeAI databases directory', json_schema_extra=Categories.Paths) db_dir : Optional[Path] = Field(default=Path('databases'), description='Path to InvokeAI databases directory', json_schema_extra=Categories.Paths)
outdir : Optional[Path] = Field(default=Path('outputs'), description='Default folder for output images', json_schema_extra=Categories.Paths) outdir : Optional[Path] = Field(default=Path('outputs'), description='Default folder for output images', json_schema_extra=Categories.Paths)
use_memory_db : bool = Field(default=False, description='Use in-memory database for storing image metadata', json_schema_extra=Categories.Paths) use_memory_db : bool = Field(default=False, description='Use in-memory database for storing image metadata', json_schema_extra=Categories.Paths)
custom_nodes_dir : Path = Field(default=Path('nodes'), description='Path to directory for custom nodes', json_schema_extra=Categories.Paths)
from_file : Optional[Path] = Field(default=None, description='Take command input from the indicated file (command-line client only)', json_schema_extra=Categories.Paths) from_file : Optional[Path] = Field(default=None, description='Take command input from the indicated file (command-line client only)', json_schema_extra=Categories.Paths)
# LOGGING # LOGGING
@@ -260,6 +262,7 @@ class InvokeAIAppConfig(InvokeAISettings):
ram : float = Field(default=7.5, gt=0, description="Maximum memory amount used by model cache for rapid switching (floating point number, GB)", json_schema_extra=Categories.ModelCache, ) ram : float = Field(default=7.5, gt=0, description="Maximum memory amount used by model cache for rapid switching (floating point number, GB)", json_schema_extra=Categories.ModelCache, )
vram : float = Field(default=0.25, ge=0, description="Amount of VRAM reserved for model storage (floating point number, GB)", json_schema_extra=Categories.ModelCache, ) vram : float = Field(default=0.25, ge=0, description="Amount of VRAM reserved for model storage (floating point number, GB)", json_schema_extra=Categories.ModelCache, )
lazy_offload : bool = Field(default=True, description="Keep models in VRAM until their space is needed", json_schema_extra=Categories.ModelCache, ) lazy_offload : bool = Field(default=True, description="Keep models in VRAM until their space is needed", json_schema_extra=Categories.ModelCache, )
log_memory_usage : bool = Field(default=False, description="If True, a memory snapshot will be captured before and after every model cache operation, and the result will be logged (at debug level). There is a time cost to capturing the memory snapshots, so it is recommended to only enable this feature if you are actively inspecting the model cache's behaviour.", json_schema_extra=Categories.ModelCache)
# DEVICE # DEVICE
device : Literal["auto", "cpu", "cuda", "cuda:1", "mps"] = Field(default="auto", description="Generation device", json_schema_extra=Categories.Device) device : Literal["auto", "cpu", "cuda", "cuda:1", "mps"] = Field(default="auto", description="Generation device", json_schema_extra=Categories.Device)
@@ -410,6 +413,13 @@ class InvokeAIAppConfig(InvokeAISettings):
""" """
return self._resolve(self.models_dir) return self._resolve(self.models_dir)
@property
def custom_nodes_path(self) -> Path:
"""
Path to the custom nodes directory
"""
return self._resolve(self.custom_nodes_dir)
# the following methods support legacy calls leftover from the Globals era # the following methods support legacy calls leftover from the Globals era
@property @property
def full_precision(self) -> bool: def full_precision(self) -> bool:
@@ -472,7 +482,7 @@ def _find_root() -> Path:
venv = Path(os.environ.get("VIRTUAL_ENV") or ".") venv = Path(os.environ.get("VIRTUAL_ENV") or ".")
if os.environ.get("INVOKEAI_ROOT"): if os.environ.get("INVOKEAI_ROOT"):
root = Path(os.environ["INVOKEAI_ROOT"]) root = Path(os.environ["INVOKEAI_ROOT"])
elif any([(venv.parent / x).exists() for x in [INIT_FILE, LEGACY_INIT_FILE]]): elif any((venv.parent / x).exists() for x in [INIT_FILE, LEGACY_INIT_FILE]):
root = (venv.parent).resolve() root = (venv.parent).resolve()
else: else:
root = Path("~/invokeai").expanduser().resolve() root = Path("~/invokeai").expanduser().resolve()

View File

@@ -27,7 +27,7 @@ class EventServiceBase:
payload["timestamp"] = get_timestamp() payload["timestamp"] = get_timestamp()
self.dispatch( self.dispatch(
event_name=EventServiceBase.queue_event, event_name=EventServiceBase.queue_event,
payload=dict(event=event_name, data=payload), payload={"event": event_name, "data": payload},
) )
# Define events here for every event in the system. # Define events here for every event in the system.
@@ -48,18 +48,18 @@ class EventServiceBase:
"""Emitted when there is generation progress""" """Emitted when there is generation progress"""
self.__emit_queue_event( self.__emit_queue_event(
event_name="generator_progress", event_name="generator_progress",
payload=dict( payload={
queue_id=queue_id, "queue_id": queue_id,
queue_item_id=queue_item_id, "queue_item_id": queue_item_id,
queue_batch_id=queue_batch_id, "queue_batch_id": queue_batch_id,
graph_execution_state_id=graph_execution_state_id, "graph_execution_state_id": graph_execution_state_id,
node_id=node.get("id"), "node_id": node.get("id"),
source_node_id=source_node_id, "source_node_id": source_node_id,
progress_image=progress_image.model_dump() if progress_image is not None else None, "progress_image": progress_image.model_dump() if progress_image is not None else None,
step=step, "step": step,
order=order, "order": order,
total_steps=total_steps, "total_steps": total_steps,
), },
) )
def emit_invocation_complete( def emit_invocation_complete(
@@ -75,15 +75,15 @@ class EventServiceBase:
"""Emitted when an invocation has completed""" """Emitted when an invocation has completed"""
self.__emit_queue_event( self.__emit_queue_event(
event_name="invocation_complete", event_name="invocation_complete",
payload=dict( payload={
queue_id=queue_id, "queue_id": queue_id,
queue_item_id=queue_item_id, "queue_item_id": queue_item_id,
queue_batch_id=queue_batch_id, "queue_batch_id": queue_batch_id,
graph_execution_state_id=graph_execution_state_id, "graph_execution_state_id": graph_execution_state_id,
node=node, "node": node,
source_node_id=source_node_id, "source_node_id": source_node_id,
result=result, "result": result,
), },
) )
def emit_invocation_error( def emit_invocation_error(
@@ -100,16 +100,16 @@ class EventServiceBase:
"""Emitted when an invocation has completed""" """Emitted when an invocation has completed"""
self.__emit_queue_event( self.__emit_queue_event(
event_name="invocation_error", event_name="invocation_error",
payload=dict( payload={
queue_id=queue_id, "queue_id": queue_id,
queue_item_id=queue_item_id, "queue_item_id": queue_item_id,
queue_batch_id=queue_batch_id, "queue_batch_id": queue_batch_id,
graph_execution_state_id=graph_execution_state_id, "graph_execution_state_id": graph_execution_state_id,
node=node, "node": node,
source_node_id=source_node_id, "source_node_id": source_node_id,
error_type=error_type, "error_type": error_type,
error=error, "error": error,
), },
) )
def emit_invocation_started( def emit_invocation_started(
@@ -124,14 +124,14 @@ class EventServiceBase:
"""Emitted when an invocation has started""" """Emitted when an invocation has started"""
self.__emit_queue_event( self.__emit_queue_event(
event_name="invocation_started", event_name="invocation_started",
payload=dict( payload={
queue_id=queue_id, "queue_id": queue_id,
queue_item_id=queue_item_id, "queue_item_id": queue_item_id,
queue_batch_id=queue_batch_id, "queue_batch_id": queue_batch_id,
graph_execution_state_id=graph_execution_state_id, "graph_execution_state_id": graph_execution_state_id,
node=node, "node": node,
source_node_id=source_node_id, "source_node_id": source_node_id,
), },
) )
def emit_graph_execution_complete( def emit_graph_execution_complete(
@@ -140,12 +140,12 @@ class EventServiceBase:
"""Emitted when a session has completed all invocations""" """Emitted when a session has completed all invocations"""
self.__emit_queue_event( self.__emit_queue_event(
event_name="graph_execution_state_complete", event_name="graph_execution_state_complete",
payload=dict( payload={
queue_id=queue_id, "queue_id": queue_id,
queue_item_id=queue_item_id, "queue_item_id": queue_item_id,
queue_batch_id=queue_batch_id, "queue_batch_id": queue_batch_id,
graph_execution_state_id=graph_execution_state_id, "graph_execution_state_id": graph_execution_state_id,
), },
) )
def emit_model_load_started( def emit_model_load_started(
@@ -162,16 +162,16 @@ class EventServiceBase:
"""Emitted when a model is requested""" """Emitted when a model is requested"""
self.__emit_queue_event( self.__emit_queue_event(
event_name="model_load_started", event_name="model_load_started",
payload=dict( payload={
queue_id=queue_id, "queue_id": queue_id,
queue_item_id=queue_item_id, "queue_item_id": queue_item_id,
queue_batch_id=queue_batch_id, "queue_batch_id": queue_batch_id,
graph_execution_state_id=graph_execution_state_id, "graph_execution_state_id": graph_execution_state_id,
model_name=model_name, "model_name": model_name,
base_model=base_model, "base_model": base_model,
model_type=model_type, "model_type": model_type,
submodel=submodel, "submodel": submodel,
), },
) )
def emit_model_load_completed( def emit_model_load_completed(
@@ -189,19 +189,19 @@ class EventServiceBase:
"""Emitted when a model is correctly loaded (returns model info)""" """Emitted when a model is correctly loaded (returns model info)"""
self.__emit_queue_event( self.__emit_queue_event(
event_name="model_load_completed", event_name="model_load_completed",
payload=dict( payload={
queue_id=queue_id, "queue_id": queue_id,
queue_item_id=queue_item_id, "queue_item_id": queue_item_id,
queue_batch_id=queue_batch_id, "queue_batch_id": queue_batch_id,
graph_execution_state_id=graph_execution_state_id, "graph_execution_state_id": graph_execution_state_id,
model_name=model_name, "model_name": model_name,
base_model=base_model, "base_model": base_model,
model_type=model_type, "model_type": model_type,
submodel=submodel, "submodel": submodel,
hash=model_info.hash, "hash": model_info.hash,
location=str(model_info.location), "location": str(model_info.location),
precision=str(model_info.precision), "precision": str(model_info.precision),
), },
) )
def emit_session_retrieval_error( def emit_session_retrieval_error(
@@ -216,14 +216,14 @@ class EventServiceBase:
"""Emitted when session retrieval fails""" """Emitted when session retrieval fails"""
self.__emit_queue_event( self.__emit_queue_event(
event_name="session_retrieval_error", event_name="session_retrieval_error",
payload=dict( payload={
queue_id=queue_id, "queue_id": queue_id,
queue_item_id=queue_item_id, "queue_item_id": queue_item_id,
queue_batch_id=queue_batch_id, "queue_batch_id": queue_batch_id,
graph_execution_state_id=graph_execution_state_id, "graph_execution_state_id": graph_execution_state_id,
error_type=error_type, "error_type": error_type,
error=error, "error": error,
), },
) )
def emit_invocation_retrieval_error( def emit_invocation_retrieval_error(
@@ -239,15 +239,15 @@ class EventServiceBase:
"""Emitted when invocation retrieval fails""" """Emitted when invocation retrieval fails"""
self.__emit_queue_event( self.__emit_queue_event(
event_name="invocation_retrieval_error", event_name="invocation_retrieval_error",
payload=dict( payload={
queue_id=queue_id, "queue_id": queue_id,
queue_item_id=queue_item_id, "queue_item_id": queue_item_id,
queue_batch_id=queue_batch_id, "queue_batch_id": queue_batch_id,
graph_execution_state_id=graph_execution_state_id, "graph_execution_state_id": graph_execution_state_id,
node_id=node_id, "node_id": node_id,
error_type=error_type, "error_type": error_type,
error=error, "error": error,
), },
) )
def emit_session_canceled( def emit_session_canceled(
@@ -260,12 +260,12 @@ class EventServiceBase:
"""Emitted when a session is canceled""" """Emitted when a session is canceled"""
self.__emit_queue_event( self.__emit_queue_event(
event_name="session_canceled", event_name="session_canceled",
payload=dict( payload={
queue_id=queue_id, "queue_id": queue_id,
queue_item_id=queue_item_id, "queue_item_id": queue_item_id,
queue_batch_id=queue_batch_id, "queue_batch_id": queue_batch_id,
graph_execution_state_id=graph_execution_state_id, "graph_execution_state_id": graph_execution_state_id,
), },
) )
def emit_queue_item_status_changed( def emit_queue_item_status_changed(
@@ -277,39 +277,39 @@ class EventServiceBase:
"""Emitted when a queue item's status changes""" """Emitted when a queue item's status changes"""
self.__emit_queue_event( self.__emit_queue_event(
event_name="queue_item_status_changed", event_name="queue_item_status_changed",
payload=dict( payload={
queue_id=queue_status.queue_id, "queue_id": queue_status.queue_id,
queue_item=dict( "queue_item": {
queue_id=session_queue_item.queue_id, "queue_id": session_queue_item.queue_id,
item_id=session_queue_item.item_id, "item_id": session_queue_item.item_id,
status=session_queue_item.status, "status": session_queue_item.status,
batch_id=session_queue_item.batch_id, "batch_id": session_queue_item.batch_id,
session_id=session_queue_item.session_id, "session_id": session_queue_item.session_id,
error=session_queue_item.error, "error": session_queue_item.error,
created_at=str(session_queue_item.created_at) if session_queue_item.created_at else None, "created_at": str(session_queue_item.created_at) if session_queue_item.created_at else None,
updated_at=str(session_queue_item.updated_at) if session_queue_item.updated_at else None, "updated_at": str(session_queue_item.updated_at) if session_queue_item.updated_at else None,
started_at=str(session_queue_item.started_at) if session_queue_item.started_at else None, "started_at": str(session_queue_item.started_at) if session_queue_item.started_at else None,
completed_at=str(session_queue_item.completed_at) if session_queue_item.completed_at else None, "completed_at": str(session_queue_item.completed_at) if session_queue_item.completed_at else None,
), },
batch_status=batch_status.model_dump(), "batch_status": batch_status.model_dump(),
queue_status=queue_status.model_dump(), "queue_status": queue_status.model_dump(),
), },
) )
def emit_batch_enqueued(self, enqueue_result: EnqueueBatchResult) -> None: def emit_batch_enqueued(self, enqueue_result: EnqueueBatchResult) -> None:
"""Emitted when a batch is enqueued""" """Emitted when a batch is enqueued"""
self.__emit_queue_event( self.__emit_queue_event(
event_name="batch_enqueued", event_name="batch_enqueued",
payload=dict( payload={
queue_id=enqueue_result.queue_id, "queue_id": enqueue_result.queue_id,
batch_id=enqueue_result.batch.batch_id, "batch_id": enqueue_result.batch.batch_id,
enqueued=enqueue_result.enqueued, "enqueued": enqueue_result.enqueued,
), },
) )
def emit_queue_cleared(self, queue_id: str) -> None: def emit_queue_cleared(self, queue_id: str) -> None:
"""Emitted when the queue is cleared""" """Emitted when the queue is cleared"""
self.__emit_queue_event( self.__emit_queue_event(
event_name="queue_cleared", event_name="queue_cleared",
payload=dict(queue_id=queue_id), payload={"queue_id": queue_id},
) )

View File

@@ -4,6 +4,8 @@ from typing import Optional
from PIL.Image import Image as PILImageType from PIL.Image import Image as PILImageType
from invokeai.app.invocations.baseinvocation import MetadataField, WorkflowField
class ImageFileStorageBase(ABC): class ImageFileStorageBase(ABC):
"""Low-level service responsible for storing and retrieving image files.""" """Low-level service responsible for storing and retrieving image files."""
@@ -30,8 +32,8 @@ class ImageFileStorageBase(ABC):
self, self,
image: PILImageType, image: PILImageType,
image_name: str, image_name: str,
metadata: Optional[dict] = None, metadata: Optional[MetadataField] = None,
workflow: Optional[str] = None, workflow: Optional[WorkflowField] = None,
thumbnail_size: int = 256, thumbnail_size: int = 256,
) -> None: ) -> None:
"""Saves an image and a 256x256 WEBP thumbnail. Returns a tuple of the image name, thumbnail name, and created timestamp.""" """Saves an image and a 256x256 WEBP thumbnail. Returns a tuple of the image name, thumbnail name, and created timestamp."""

View File

@@ -1,5 +1,4 @@
# Copyright (c) 2022 Kyle Schouviller (https://github.com/kyle0654) and the InvokeAI Team # Copyright (c) 2022 Kyle Schouviller (https://github.com/kyle0654) and the InvokeAI Team
import json
from pathlib import Path from pathlib import Path
from queue import Queue from queue import Queue
from typing import Dict, Optional, Union from typing import Dict, Optional, Union
@@ -8,6 +7,7 @@ from PIL import Image, PngImagePlugin
from PIL.Image import Image as PILImageType from PIL.Image import Image as PILImageType
from send2trash import send2trash from send2trash import send2trash
from invokeai.app.invocations.baseinvocation import MetadataField, WorkflowField
from invokeai.app.services.invoker import Invoker from invokeai.app.services.invoker import Invoker
from invokeai.app.util.thumbnails import get_thumbnail_name, make_thumbnail from invokeai.app.util.thumbnails import get_thumbnail_name, make_thumbnail
@@ -25,7 +25,7 @@ class DiskImageFileStorage(ImageFileStorageBase):
__invoker: Invoker __invoker: Invoker
def __init__(self, output_folder: Union[str, Path]): def __init__(self, output_folder: Union[str, Path]):
self.__cache = dict() self.__cache = {}
self.__cache_ids = Queue() self.__cache_ids = Queue()
self.__max_cache_size = 10 # TODO: get this from config self.__max_cache_size = 10 # TODO: get this from config
@@ -55,8 +55,8 @@ class DiskImageFileStorage(ImageFileStorageBase):
self, self,
image: PILImageType, image: PILImageType,
image_name: str, image_name: str,
metadata: Optional[dict] = None, metadata: Optional[MetadataField] = None,
workflow: Optional[str] = None, workflow: Optional[WorkflowField] = None,
thumbnail_size: int = 256, thumbnail_size: int = 256,
) -> None: ) -> None:
try: try:
@@ -65,20 +65,10 @@ class DiskImageFileStorage(ImageFileStorageBase):
pnginfo = PngImagePlugin.PngInfo() pnginfo = PngImagePlugin.PngInfo()
if metadata is not None or workflow is not None: if metadata is not None:
if metadata is not None: pnginfo.add_text("invokeai_metadata", metadata.model_dump_json())
pnginfo.add_text("invokeai_metadata", json.dumps(metadata)) if workflow is not None:
if workflow is not None: pnginfo.add_text("invokeai_workflow", workflow.model_dump_json())
pnginfo.add_text("invokeai_workflow", workflow)
else:
# For uploaded images, we want to retain metadata. PIL strips it on save; manually add it back
# TODO: retain non-invokeai metadata on save...
original_metadata = image.info.get("invokeai_metadata", None)
if original_metadata is not None:
pnginfo.add_text("invokeai_metadata", original_metadata)
original_workflow = image.info.get("invokeai_workflow", None)
if original_workflow is not None:
pnginfo.add_text("invokeai_workflow", original_workflow)
image.save( image.save(
image_path, image_path,

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