Compare commits
1148 Commits
v3.4.0
...
feat/invoc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c76a6bd65f | ||
|
|
6c4eeaa569 | ||
|
|
1bbd13ead7 | ||
|
|
321b939d0e | ||
|
|
8fb77e431e | ||
|
|
083a4f3faa | ||
|
|
2005411f7e | ||
|
|
ba7b1b2665 | ||
|
|
b7ffd36cc6 | ||
|
|
199ddd6623 | ||
|
|
a7207ed8cf | ||
|
|
6bb2dda3f1 | ||
|
|
c1e5cd5893 | ||
|
|
ff249a2315 | ||
|
|
c58f8c3269 | ||
|
|
ed772a7107 | ||
|
|
cb0b389b4b | ||
|
|
8892df1d97 | ||
|
|
bc5f356390 | ||
|
|
bcb85e100d | ||
|
|
1f27ddc07d | ||
|
|
7a2b606001 | ||
|
|
83ddcc5f3a | ||
|
|
55fa785561 | ||
|
|
06429028c8 | ||
|
|
8b6e322697 | ||
|
|
54a67459bf | ||
|
|
7fe5283e74 | ||
|
|
fe0391c86b | ||
|
|
25386a76ef | ||
|
|
fd30cb4d90 | ||
|
|
0266946d3d | ||
|
|
a7f91b3e01 | ||
|
|
de0b72528c | ||
|
|
2932652787 | ||
|
|
db6bc7305a | ||
|
|
a5db204629 | ||
|
|
8e2b61e19f | ||
|
|
a3faa3792a | ||
|
|
c16eba78ab | ||
|
|
1a191c4655 | ||
|
|
e36d925bce | ||
|
|
b1ba18b3d1 | ||
|
|
aff46759f9 | ||
|
|
d7b7dcc7fe | ||
|
|
889a26c5b6 | ||
|
|
b4c774896a | ||
|
|
afbe889d35 | ||
|
|
9c1e52b1ef | ||
|
|
3f5ab02da9 | ||
|
|
bf48e8a03a | ||
|
|
e52434cb99 | ||
|
|
483bdbcb9f | ||
|
|
ae421fb4ab | ||
|
|
cc295a9f0a | ||
|
|
a7e23af9c6 | ||
|
|
3de4390711 | ||
|
|
3ceee2b2b2 | ||
|
|
5c7ed24aab | ||
|
|
183c9c4799 | ||
|
|
8baf3f78a2 | ||
|
|
ac2eb16a65 | ||
|
|
4aa7bee4b9 | ||
|
|
7e5ba2795e | ||
|
|
97a6c6eea7 | ||
|
|
f0e60a4ba2 | ||
|
|
aa089e8108 | ||
|
|
c5aeb36230 | ||
|
|
5e77f0d93b | ||
|
|
d3acb81743 | ||
|
|
e0f2404c00 | ||
|
|
5ed7972e5f | ||
|
|
792131be01 | ||
|
|
fc278c5cb1 | ||
|
|
d7f6af1f07 | ||
|
|
ff9bd040cc | ||
|
|
17d5f7bebd | ||
|
|
30dae0f5aa | ||
|
|
161000cde6 | ||
|
|
de832f6862 | ||
|
|
21ba3c63de | ||
|
|
a948bd1310 | ||
|
|
2071972a8c | ||
|
|
5ed2f6e6c1 | ||
|
|
b77f6bd0ad | ||
|
|
34cc26a4ed | ||
|
|
9d6e4ff1fb | ||
|
|
85bbf65967 | ||
|
|
3726293258 | ||
|
|
8bd65be8c8 | ||
|
|
783442c40d | ||
|
|
8a147bd6e6 | ||
|
|
273994b742 | ||
|
|
3339ad4df8 | ||
|
|
c3b2a8cb27 | ||
|
|
daa780940b | ||
|
|
2289680ae1 | ||
|
|
cda85a0637 | ||
|
|
1d9801e7be | ||
|
|
3ecb1e580f | ||
|
|
6301e58a2e | ||
|
|
5dd552effa | ||
|
|
25ce505628 | ||
|
|
1dd07fb1eb | ||
|
|
e82c21b5ba | ||
|
|
50b93992cf | ||
|
|
f8e566d62a | ||
|
|
f588b95c7f | ||
|
|
67daf1751c | ||
|
|
7d80261d47 | ||
|
|
67cbfeb33d | ||
|
|
f7998b4be0 | ||
|
|
675c73c94f | ||
|
|
0a27b0379f | ||
|
|
0ef18b6477 | ||
|
|
6539ef7c9f | ||
|
|
14c9a1e4f3 | ||
|
|
64b0feca31 | ||
|
|
0be9a2d906 | ||
|
|
d925f721b9 | ||
|
|
4e5be1891a | ||
|
|
156d4ec3b2 | ||
|
|
c45a43519a | ||
|
|
763816ca0c | ||
|
|
83a7c9059f | ||
|
|
c5f069a255 | ||
|
|
cd169ee082 | ||
|
|
66b106f107 | ||
|
|
b10d745dae | ||
|
|
d20f98fb4f | ||
|
|
c9c150f850 | ||
|
|
a60e2b7c77 | ||
|
|
da6e5b2ba1 | ||
|
|
c65d497cbc | ||
|
|
a68d8fe203 | ||
|
|
5de2288cfa | ||
|
|
2ce70b4457 | ||
|
|
6c5f743e2b | ||
|
|
bb242c4e1e | ||
|
|
c9e246ed1b | ||
|
|
2175fe3823 | ||
|
|
f64fc2c8b7 | ||
|
|
3d1b5c57ea | ||
|
|
31b9538976 | ||
|
|
97c1545cca | ||
|
|
6a8a3b50bc | ||
|
|
5a816818dc | ||
|
|
1cb866d1fc | ||
|
|
29bcc4b595 | ||
|
|
ca2bb6f0cc | ||
|
|
1c8fc908b2 | ||
|
|
d397beaa47 | ||
|
|
60eea09629 | ||
|
|
5b7b1122cb | ||
|
|
dfc8d1bb10 | ||
|
|
f9fa62164e | ||
|
|
d47905d2fb | ||
|
|
03b1cde97d | ||
|
|
7162ff04df | ||
|
|
32b1e974ca | ||
|
|
82c3c7fc38 | ||
|
|
3dcbb79ef7 | ||
|
|
3b41104427 | ||
|
|
35bf7ee66d | ||
|
|
430e17a5d2 | ||
|
|
400d66fa5d | ||
|
|
800c481515 | ||
|
|
79ae9c4e64 | ||
|
|
0dc6cb0535 | ||
|
|
810fc19e43 | ||
|
|
e0e106367d | ||
|
|
14472dc09d | ||
|
|
e8095b73ae | ||
|
|
c979cf5ecc | ||
|
|
1b4dbd283e | ||
|
|
fb50a221f8 | ||
|
|
52e07db06b | ||
|
|
6643b5cec4 | ||
|
|
e8bf9ea058 | ||
|
|
ce3d37e829 | ||
|
|
8a61063e84 | ||
|
|
87ff96553a | ||
|
|
209bf105bc | ||
|
|
804dbeba34 | ||
|
|
067cd4dc2e | ||
|
|
feb4a3f242 | ||
|
|
4a886c0a4a | ||
|
|
8e500283b6 | ||
|
|
3205371654 | ||
|
|
d713620d9e | ||
|
|
c1300fa8b1 | ||
|
|
0976ddba23 | ||
|
|
3ebb806410 | ||
|
|
9f274c79dc | ||
|
|
88c08bbfc7 | ||
|
|
c2af124622 | ||
|
|
f972fe9836 | ||
|
|
dcfc883ab3 | ||
|
|
1d2bd6b8f7 | ||
|
|
f2777f5096 | ||
|
|
d3320dc4ee | ||
|
|
72db2ee352 | ||
|
|
60c3a4ad5e | ||
|
|
cf7a7928af | ||
|
|
1057314508 | ||
|
|
73a077956b | ||
|
|
5e1e50bd47 | ||
|
|
413fe566b8 | ||
|
|
c9b5f06c42 | ||
|
|
b53e432b0f | ||
|
|
88164447e9 | ||
|
|
1ac85fd049 | ||
|
|
ee6fc4ab1d | ||
|
|
9f793bdae8 | ||
|
|
a0eecaecd0 | ||
|
|
d532073f5b | ||
|
|
198e8c9d55 | ||
|
|
30367deeca | ||
|
|
e73298aea2 | ||
|
|
59279851a3 | ||
|
|
2965357d99 | ||
|
|
8bd32ee142 | ||
|
|
a4f892dcfb | ||
|
|
e675983e20 | ||
|
|
e9558f97c4 | ||
|
|
a1a611f8cb | ||
|
|
182dc859a0 | ||
|
|
c0240a8568 | ||
|
|
02bcff29e8 | ||
|
|
d4ed64df7d | ||
|
|
701f14c1e3 | ||
|
|
45bf2c7da6 | ||
|
|
67ada70a26 | ||
|
|
06bcc07f65 | ||
|
|
4410ecf62c | ||
|
|
9f6b9d4d23 | ||
|
|
b24e8dd829 | ||
|
|
25291a2e01 | ||
|
|
332f3930a5 | ||
|
|
ed466a99ec | ||
|
|
f68f8898c0 | ||
|
|
a0996b1c0a | ||
|
|
522ff4a042 | ||
|
|
a769f93be0 | ||
|
|
2c5ef92979 | ||
|
|
5d773dc94c | ||
|
|
088e3420e6 | ||
|
|
14efc95707 | ||
|
|
f48a2c5fd2 | ||
|
|
74ae4d7774 | ||
|
|
191203ea0c | ||
|
|
6aceae5c22 | ||
|
|
8c6b3efd39 | ||
|
|
4602efd598 | ||
|
|
f70c0936ca | ||
|
|
0d4de4cc63 | ||
|
|
1e855f8290 | ||
|
|
bb2787584d | ||
|
|
a04981b418 | ||
|
|
d7f16b7c87 | ||
|
|
4477e04d59 | ||
|
|
30e11b4b42 | ||
|
|
b93695b78f | ||
|
|
b01311813b | ||
|
|
5ae80fab87 | ||
|
|
c4291f2136 | ||
|
|
287d3c2b04 | ||
|
|
7fde19730e | ||
|
|
13575642d8 | ||
|
|
3f5370b284 | ||
|
|
d048eb5b20 | ||
|
|
dd7031a472 | ||
|
|
4160d5ef26 | ||
|
|
51bdf2fd19 | ||
|
|
6a44697911 | ||
|
|
7a1d0ec228 | ||
|
|
b5928fd411 | ||
|
|
2f345d1976 | ||
|
|
f5d0721fa8 | ||
|
|
c3b36cb61d | ||
|
|
189c430e46 | ||
|
|
b922ee566a | ||
|
|
89da69f647 | ||
|
|
138caa34de | ||
|
|
26c3378ede | ||
|
|
aa134a2db8 | ||
|
|
d0391cb430 | ||
|
|
c955ea9de0 | ||
|
|
fc29a5d439 | ||
|
|
7e9942dbab | ||
|
|
c003967eaa | ||
|
|
b28fcc6be5 | ||
|
|
418cdbabb7 | ||
|
|
18e61e92d9 | ||
|
|
de20711637 | ||
|
|
55e91b97be | ||
|
|
f79bbd2d6e | ||
|
|
e1c2c3905d | ||
|
|
03ac93bfc7 | ||
|
|
89da976949 | ||
|
|
57dafd294d | ||
|
|
e611baa4b4 | ||
|
|
fc448d5b6d | ||
|
|
e59954f956 | ||
|
|
e160cbb1e9 | ||
|
|
86c857b9c2 | ||
|
|
0a13d7d2c7 | ||
|
|
68da5c6d22 | ||
|
|
f82744b95e | ||
|
|
5a67bc68a1 | ||
|
|
61cf4d4c70 | ||
|
|
9d20a2d5a3 | ||
|
|
8b0ac451e3 | ||
|
|
470dbe75a2 | ||
|
|
b7d19b8130 | ||
|
|
3dc13221d8 | ||
|
|
35184dbd86 | ||
|
|
0868fc2558 | ||
|
|
92fb09c4df | ||
|
|
b4cf5496b6 | ||
|
|
a0e68705dd | ||
|
|
7cb49e65bd | ||
|
|
39fedb090b | ||
|
|
f36a691219 | ||
|
|
6a2eb1d2e4 | ||
|
|
13123daa3f | ||
|
|
c859eb865e | ||
|
|
8f5e2cbcc7 | ||
|
|
2aed6e2dba | ||
|
|
52b51a6088 | ||
|
|
52b24e01e2 | ||
|
|
1178fd8bd3 | ||
|
|
a0187cc9df | ||
|
|
2f656cc357 | ||
|
|
71f9ac9985 | ||
|
|
8bbdfc45fa | ||
|
|
3cbb1a7671 | ||
|
|
b74e0de74a | ||
|
|
e7e7793896 | ||
|
|
504bdac14a | ||
|
|
b76d2cd716 | ||
|
|
022b32c724 | ||
|
|
653b820da1 | ||
|
|
68232e642f | ||
|
|
4ba0bf4dcf | ||
|
|
5e4daf4bc6 | ||
|
|
7e0713c869 | ||
|
|
099d516ac0 | ||
|
|
b94f6a4a29 | ||
|
|
4caf63d53d | ||
|
|
6057229ceb | ||
|
|
6a2856e46f | ||
|
|
4dedd63b74 | ||
|
|
db74837eb1 | ||
|
|
892fe62264 | ||
|
|
3c79476785 | ||
|
|
dad364da17 | ||
|
|
37bc4f78d0 | ||
|
|
de0b43c81d | ||
|
|
ea1d2d6a4c | ||
|
|
fafe8ccc59 | ||
|
|
4b88cfac19 | ||
|
|
5fa13fba36 | ||
|
|
f28f761436 | ||
|
|
27d7889780 | ||
|
|
a1cf153097 | ||
|
|
d121eefa12 | ||
|
|
c92e25a6a7 | ||
|
|
8be03dead5 | ||
|
|
1197133d06 | ||
|
|
850458a554 | ||
|
|
e96ad41729 | ||
|
|
53cf518390 | ||
|
|
b00ace852d | ||
|
|
be72765d02 | ||
|
|
580d29257c | ||
|
|
5d068c1da1 | ||
|
|
8e2ccab1f0 | ||
|
|
6f478eef62 | ||
|
|
1ff1c370df | ||
|
|
5ef87ef2a6 | ||
|
|
d0709d4f4e | ||
|
|
2a081b0a27 | ||
|
|
d902533387 | ||
|
|
1174713223 | ||
|
|
4b1740ad19 | ||
|
|
e03c88ce32 | ||
|
|
b917ffecbe | ||
|
|
2967a78c5a | ||
|
|
aa25ea62a5 | ||
|
|
1ab0e86085 | ||
|
|
c9ddbb4241 | ||
|
|
415a1c7a4f | ||
|
|
84a4836ab7 | ||
|
|
dbd6c9c6ed | ||
|
|
4f95c077d4 | ||
|
|
0a4cbc4e16 | ||
|
|
d45b76fab4 | ||
|
|
9722135cda | ||
|
|
7366913a31 | ||
|
|
bd31b5606c | ||
|
|
2953dea4a0 | ||
|
|
f3fed0b10f | ||
|
|
db57d426d9 | ||
|
|
4536e4a8b6 | ||
|
|
426a7b900f | ||
|
|
cc571d9ab2 | ||
|
|
296c861e7d | ||
|
|
aa45d21fd2 | ||
|
|
ac42513da9 | ||
|
|
e2387546fe | ||
|
|
c8929b35f0 | ||
|
|
c000e270a0 | ||
|
|
8ff28da3b4 | ||
|
|
b7b376103c | ||
|
|
08d379bb29 | ||
|
|
74e644c4ba | ||
|
|
d4c36da3ee | ||
|
|
dfe0b73890 | ||
|
|
c0c8fa9a89 | ||
|
|
ad7139829c | ||
|
|
a24e63d440 | ||
|
|
59437a02c3 | ||
|
|
98a44d7fa1 | ||
|
|
07416753be | ||
|
|
630854ce26 | ||
|
|
b55c2b99a7 | ||
|
|
f81d36c95f | ||
|
|
26b7aadd32 | ||
|
|
8e7e3c2b4a | ||
|
|
f2e8b66be4 | ||
|
|
ff09fd30dc | ||
|
|
9fcc30c3d6 | ||
|
|
b29a6522ef | ||
|
|
936d19cd60 | ||
|
|
f25b6ee5d1 | ||
|
|
7dea079220 | ||
|
|
7fc08962fb | ||
|
|
71155d9e72 | ||
|
|
6ccd72349d | ||
|
|
30e12376d3 | ||
|
|
23c8a893e1 | ||
|
|
7d93329401 | ||
|
|
968fb655a4 | ||
|
|
80ec9f4131 | ||
|
|
f19def5f7b | ||
|
|
9e1dd8ac9c | ||
|
|
ebd68b7a6c | ||
|
|
68a231afea | ||
|
|
21ab650ac0 | ||
|
|
b501bd709f | ||
|
|
4082f25062 | ||
|
|
63d74b4ba6 | ||
|
|
da5907613b | ||
|
|
3a9201bd31 | ||
|
|
d6e2cb7cef | ||
|
|
0809e832d4 | ||
|
|
7269c9f02e | ||
|
|
d86d7e5c33 | ||
|
|
5d87578746 | ||
|
|
04aef021fc | ||
|
|
0fc08bb384 | ||
|
|
5779542084 | ||
|
|
ebda81e96e | ||
|
|
3fe332e85f | ||
|
|
3428ea1b3c | ||
|
|
6024fc7baf | ||
|
|
75c1c4ce5a | ||
|
|
ffa05a0bb3 | ||
|
|
a20e17330b | ||
|
|
4e83644433 | ||
|
|
604f0083f2 | ||
|
|
2a8a158823 | ||
|
|
f8c3db72e9 | ||
|
|
60815807f9 | ||
|
|
196fb0e014 | ||
|
|
eba668956d | ||
|
|
ee5ec023f4 | ||
|
|
d59661e0af | ||
|
|
f51e8eeae1 | ||
|
|
6e06935e75 | ||
|
|
f7f697849c | ||
|
|
8e17e29a5c | ||
|
|
12e9f17f7a | ||
|
|
cb7e56a9a3 | ||
|
|
1a710a4c12 | ||
|
|
d8d266d3be | ||
|
|
4716632c23 | ||
|
|
3c4150d153 | ||
|
|
b71b14d582 | ||
|
|
73481d4aec | ||
|
|
2c049a3b94 | ||
|
|
367de44a8b | ||
|
|
f5f378d04b | ||
|
|
823edbfdef | ||
|
|
29bbb27289 | ||
|
|
a23502f7ff | ||
|
|
ce64dbefce | ||
|
|
b47afdc3b5 | ||
|
|
cde9c3090f | ||
|
|
6924b04d7c | ||
|
|
83fbd4bdf2 | ||
|
|
6460dcc7e0 | ||
|
|
59aa009c93 | ||
|
|
59d2a012cd | ||
|
|
7e3b620830 | ||
|
|
e16b55816f | ||
|
|
895cb8637e | ||
|
|
fe5bceb1ed | ||
|
|
5d475a40f5 | ||
|
|
bca7ea1674 | ||
|
|
f27bb402fb | ||
|
|
dd32c632cd | ||
|
|
9e2e740033 | ||
|
|
d6362ce0bd | ||
|
|
2347a00a70 | ||
|
|
0b7dc721cf | ||
|
|
ac04a834ef | ||
|
|
bbca053b48 | ||
|
|
fcf2006502 | ||
|
|
ac0d0019bd | ||
|
|
2d922a0a65 | ||
|
|
8db14911d7 | ||
|
|
01bab58b20 | ||
|
|
7a57bc99cf | ||
|
|
d3b6d86e74 | ||
|
|
360b6cb286 | ||
|
|
8f9e9e639e | ||
|
|
6930d8ba41 | ||
|
|
7ad74e680d | ||
|
|
c56a6a4ddd | ||
|
|
afad764a00 | ||
|
|
49a72bd714 | ||
|
|
8cf14287b6 | ||
|
|
0db47dd5e7 | ||
|
|
71f6f77ae8 | ||
|
|
6f16229c41 | ||
|
|
0cc0d794d1 | ||
|
|
535639cb95 | ||
|
|
2250bca8d9 | ||
|
|
4ce39a5974 | ||
|
|
644e9287f0 | ||
|
|
6a5e0be022 | ||
|
|
707f0f7091 | ||
|
|
8e709fe05a | ||
|
|
154da609cb | ||
|
|
21975d6268 | ||
|
|
31035b3e63 | ||
|
|
6c05818887 | ||
|
|
77c5b051f0 | ||
|
|
4fdc4c15f9 | ||
|
|
1a4be78013 | ||
|
|
eb16ad3d6f | ||
|
|
1fee08639d | ||
|
|
7caaf40835 | ||
|
|
6bfe994622 | ||
|
|
8a6f03cd46 | ||
|
|
4ce9f9dc36 | ||
|
|
00297716d6 | ||
|
|
50c0dc71eb | ||
|
|
29ccc6a3d8 | ||
|
|
f92a5cbabc | ||
|
|
acbf10f7ba | ||
|
|
46d830b9fa | ||
|
|
db17ec7a4b | ||
|
|
6320d18846 | ||
|
|
37c8b9d06a | ||
|
|
7ba2108eb0 | ||
|
|
8aeeee4752 | ||
|
|
930de51910 | ||
|
|
b1b5c0d3b2 | ||
|
|
ebe717099e | ||
|
|
06245bc761 | ||
|
|
b4c0dafdc8 | ||
|
|
0cefacb3a2 | ||
|
|
baa5f75976 | ||
|
|
989aaedc7f | ||
|
|
93e08df849 | ||
|
|
4a43e1c1b8 | ||
|
|
2bbab9d94e | ||
|
|
a456f6e6f0 | ||
|
|
a408f562d6 | ||
|
|
cefdf9ed00 | ||
|
|
5413bf07e2 | ||
|
|
4cffe282bd | ||
|
|
ae8ffe9d51 | ||
|
|
870cc5b733 | ||
|
|
0b4eb888c5 | ||
|
|
11f1cb5391 | ||
|
|
1e2e26cfc2 | ||
|
|
e9bce6e1c3 | ||
|
|
799ef0e7c1 | ||
|
|
61c10a7ca8 | ||
|
|
93880223e6 | ||
|
|
271456b745 | ||
|
|
cecee33bc0 | ||
|
|
4f43eda09b | ||
|
|
011757c497 | ||
|
|
2700d0e769 | ||
|
|
d256d93a2a | ||
|
|
f3c8e986a5 | ||
|
|
48f5e4f313 | ||
|
|
5950ffe064 | ||
|
|
49ca949cd6 | ||
|
|
5d69f1cbf5 | ||
|
|
9169006171 | ||
|
|
28b74523d0 | ||
|
|
9359c03c3c | ||
|
|
598241e0f2 | ||
|
|
e698a8006c | ||
|
|
34e7b5a7fb | ||
|
|
5c3dd62ae0 | ||
|
|
7e2eeec1f3 | ||
|
|
7eb79266c4 | ||
|
|
5d4610d981 | ||
|
|
7c548c5bf3 | ||
|
|
2a38606342 | ||
|
|
793cf39964 | ||
|
|
ab3e689ee0 | ||
|
|
20f497054f | ||
|
|
6209fef63d | ||
|
|
5168415999 | ||
|
|
b490c8ae27 | ||
|
|
6f354f16ba | ||
|
|
e108a2302e | ||
|
|
2ffecef792 | ||
|
|
2663a07e94 | ||
|
|
8d2ef5afc3 | ||
|
|
539887b215 | ||
|
|
2ba505cce9 | ||
|
|
bd92a31d15 | ||
|
|
ee2529f3fd | ||
|
|
89b7082bc0 | ||
|
|
55dfabb892 | ||
|
|
2a41fd0b29 | ||
|
|
966919ea4a | ||
|
|
d3acdcf12f | ||
|
|
52f9749bf5 | ||
|
|
2a661450c3 | ||
|
|
2d96c62fdb | ||
|
|
3e6173ee8c | ||
|
|
4e9841c924 | ||
|
|
f4ea495d23 | ||
|
|
43a4b815e8 | ||
|
|
4134f18319 | ||
|
|
cd292f6c1c | ||
|
|
3ce8f3d6fe | ||
|
|
10fd4f6a61 | ||
|
|
47b1fd4bce | ||
|
|
300805a25a | ||
|
|
56527da73e | ||
|
|
ca4b8e65c1 | ||
|
|
f5194f9e2d | ||
|
|
ccbbb417f9 | ||
|
|
37786a26a5 | ||
|
|
4f2930412e | ||
|
|
83049a3a5b | ||
|
|
38256f97b3 | ||
|
|
77f2aabda4 | ||
|
|
e32eb2a649 | ||
|
|
f4cdfa3b9c | ||
|
|
e99b715e9e | ||
|
|
ed96c40239 | ||
|
|
1b3bb932b9 | ||
|
|
f0b102d830 | ||
|
|
a47d91f0e7 | ||
|
|
358c1f5791 | ||
|
|
faec320d48 | ||
|
|
fd074abdc4 | ||
|
|
d8eb58cd58 | ||
|
|
8937d66412 | ||
|
|
a6935ae7fb | ||
|
|
69968eb67b | ||
|
|
e57f5f129c | ||
|
|
1b8651fa26 | ||
|
|
f6664960ca | ||
|
|
84a001720c | ||
|
|
c9951cd86b | ||
|
|
83a9e26cd8 | ||
|
|
80812cf7cd | ||
|
|
2a6c940047 | ||
|
|
78fe9b642d | ||
|
|
53b835945f | ||
|
|
acba51c888 | ||
|
|
daa9d50d95 | ||
|
|
e38d0e39b7 | ||
|
|
2c632a811b | ||
|
|
6afeb37ce5 | ||
|
|
85726c164b | ||
|
|
17e1ef0140 | ||
|
|
cdfc01d938 | ||
|
|
dc632a787a | ||
|
|
4e04ea0c0d | ||
|
|
f51bb00b5e | ||
|
|
12f2357e70 | ||
|
|
60629cba3c | ||
|
|
5196e4bc38 | ||
|
|
89e7848079 | ||
|
|
5b38b5ea7f | ||
|
|
88c1af969f | ||
|
|
fbede84405 | ||
|
|
756cb9c27e | ||
|
|
78b29db458 | ||
|
|
1225c3fb47 | ||
|
|
4957a360ff | ||
|
|
32ad742f3e | ||
|
|
41cd40541a | ||
|
|
2d11d97dad | ||
|
|
64858b2523 | ||
|
|
d5134325f6 | ||
|
|
702d0f68af | ||
|
|
a0d0e9f474 | ||
|
|
475823835f | ||
|
|
b95d547ccc | ||
|
|
9b4758f02f | ||
|
|
8d2952695d | ||
|
|
8dd55cc45e | ||
|
|
562fb1f3a1 | ||
|
|
21ed2d42cd | ||
|
|
79cf3ec9a5 | ||
|
|
37b76caccf | ||
|
|
a4f9bfc8f7 | ||
|
|
9afdd0f4a8 | ||
|
|
bee6ad1547 | ||
|
|
fa3f1b6e41 | ||
|
|
d0fa131010 | ||
|
|
2f438431bd | ||
|
|
bbeb5cb477 | ||
|
|
cd3111c324 | ||
|
|
16b7246412 | ||
|
|
42be78d328 | ||
|
|
e469e24a58 | ||
|
|
cb698ff1fb | ||
|
|
45470a3ac8 | ||
|
|
0e738c4290 | ||
|
|
09d1bc513d | ||
|
|
b6ed4ba559 | ||
|
|
aefa828237 | ||
|
|
74ea592d02 | ||
|
|
457b0dfac0 | ||
|
|
96a717c4ba | ||
|
|
77b74264a8 | ||
|
|
351078e8aa | ||
|
|
b8354bd1a4 | ||
|
|
3b944b8af6 | ||
|
|
b811c037bd | ||
|
|
5bf61382a4 | ||
|
|
0f1c5f382a | ||
|
|
4af1695c60 | ||
|
|
df9a903a50 | ||
|
|
311be8f97d | ||
|
|
3f970c8326 | ||
|
|
fc150acde5 | ||
|
|
1615df3aa1 | ||
|
|
b2a8c45553 | ||
|
|
212dbaf9a2 | ||
|
|
ac3cf48d7f | ||
|
|
454f01e0c1 | ||
|
|
72dca55e44 | ||
|
|
264ea6d94d | ||
|
|
60e3e653fa | ||
|
|
082894c377 | ||
|
|
4b00f8fc82 | ||
|
|
6ea09ba0b6 | ||
|
|
296060db63 | ||
|
|
d1d8ee71fc | ||
|
|
42c04db167 | ||
|
|
b935768eeb | ||
|
|
ea4ef042f3 | ||
|
|
18b2bcbbee | ||
|
|
5ad88c7f86 | ||
|
|
3b04fef31d | ||
|
|
bec888923a | ||
|
|
c6235049c7 | ||
|
|
e10f6e8962 | ||
|
|
77f04ff8d6 | ||
|
|
461e474394 | ||
|
|
f0c70fe3f1 | ||
|
|
442ac2b828 | ||
|
|
bb986b97f3 | ||
|
|
98655db57b | ||
|
|
8845894e83 | ||
|
|
937c7e957d | ||
|
|
569ae7c482 | ||
|
|
340957f920 | ||
|
|
076d9b05ea | ||
|
|
2b54e240d4 | ||
|
|
5127e9df2d | ||
|
|
42329a1849 | ||
|
|
42bc6ef154 | ||
|
|
6c6c45c3da | ||
|
|
f76b04a3b8 | ||
|
|
821e0326c9 | ||
|
|
cc18d86f29 | ||
|
|
ed1583383e | ||
|
|
c50a49719b | ||
|
|
ebf5f5d418 | ||
|
|
386b656530 | ||
|
|
d7cede6c28 | ||
|
|
15de7c21d9 | ||
|
|
9620f9336c | ||
|
|
a64ced7b29 | ||
|
|
dd7deff1a3 | ||
|
|
612912a6c9 | ||
|
|
bca2372280 | ||
|
|
0b860582f0 | ||
|
|
87ff380fe4 | ||
|
|
2cdda1fda2 | ||
|
|
6caa70123d | ||
|
|
7e831c8a96 | ||
|
|
3d64bc886d | ||
|
|
1a136d6167 | ||
|
|
43f2837117 | ||
|
|
5f77ef7e99 | ||
|
|
22ccaa4e9a | ||
|
|
d277bd3c38 | ||
|
|
fd4e041e7c | ||
|
|
15a3e8076f | ||
|
|
2fbe3a3104 | ||
|
|
b0cfa58526 | ||
|
|
285ed26edd | ||
|
|
02565b9a00 | ||
|
|
78a6024d6c | ||
|
|
95198da645 | ||
|
|
ee1f1f3363 | ||
|
|
18ba7feca1 | ||
|
|
55b0c7cdc9 | ||
|
|
713a83e7da | ||
|
|
f3a97e06ec | ||
|
|
50815d36c6 | ||
|
|
a69f518c76 | ||
|
|
18093c4f1d | ||
|
|
0cf7fe43af | ||
|
|
6063760ce2 | ||
|
|
c5ba4f2ea5 | ||
|
|
3414437eea | ||
|
|
417db71471 | ||
|
|
afe4e55bf9 | ||
|
|
55acc16b2d | ||
|
|
535ce10e99 | ||
|
|
11f4a48144 | ||
|
|
67ed4a0245 | ||
|
|
fbbc1037cd | ||
|
|
0852fd4e88 | ||
|
|
c84526fae5 | ||
|
|
f762940335 | ||
|
|
fefb78795f | ||
|
|
ef8284f009 | ||
|
|
290851016e | ||
|
|
fa7d002175 | ||
|
|
f1b6f78319 | ||
|
|
26ab917021 | ||
|
|
4f3c32a2ee | ||
|
|
77065b1ce1 | ||
|
|
41db92b9e8 | ||
|
|
c823f5667b | ||
|
|
3227b30430 | ||
|
|
567f107a81 | ||
|
|
b3d5955bc7 | ||
|
|
8726b203d4 | ||
|
|
b3f92e0547 | ||
|
|
72c9a7663f | ||
|
|
fcb9e89bd7 | ||
|
|
56966d6d05 | ||
|
|
e46dc9b34e | ||
|
|
e461f9925e | ||
|
|
abeb1bd3b3 | ||
|
|
83e820d721 | ||
|
|
f8e4b93a74 | ||
|
|
0710ec30cf | ||
|
|
c382329e8c | ||
|
|
a2dc780188 | ||
|
|
abc9dc4d17 | ||
|
|
3c692018cd | ||
|
|
3ba3c1918c | ||
|
|
f2c6819d68 | ||
|
|
ef807cf63a | ||
|
|
bbcd58e681 | ||
|
|
36043bf38b | ||
|
|
fd68c47920 | ||
|
|
c5c975c7a9 | ||
|
|
41ad13c282 | ||
|
|
e9d7e6bdd5 | ||
|
|
49b74d189e | ||
|
|
179bc64490 | ||
|
|
1feab3da37 | ||
|
|
0a15f3fc35 | ||
|
|
daf00efa4d | ||
|
|
55cfb879d0 | ||
|
|
de2879f602 | ||
|
|
3b1ff4a7f4 | ||
|
|
d7f7fbc8c2 | ||
|
|
e2567a7e31 | ||
|
|
2f3457c02a | ||
|
|
aab6369ffe | ||
|
|
4c97b619fb | ||
|
|
abdd840fb9 | ||
|
|
e656768eb2 | ||
|
|
494c2a9b05 | ||
|
|
40d4c7c8e1 | ||
|
|
076284c26f | ||
|
|
1af4260ab6 | ||
|
|
08ef71a74e | ||
|
|
8f6e2c0c85 | ||
|
|
0ac33f36ef | ||
|
|
9661fa5f76 | ||
|
|
ca07449fb4 | ||
|
|
fb39f621c6 | ||
|
|
977d309692 | ||
|
|
72cb8b83fe | ||
|
|
99f14b1dfe | ||
|
|
95a3c89a56 | ||
|
|
b271474812 | ||
|
|
2272925607 | ||
|
|
5902a52e40 | ||
|
|
5140056b59 | ||
|
|
f17b3d0068 | ||
|
|
5b9d25f57e | ||
|
|
73dbb8792e | ||
|
|
fc6cebb975 | ||
|
|
06104f3851 | ||
|
|
6e028d691a | ||
|
|
6d176601cc | ||
|
|
4627a7c75f | ||
|
|
d9a0efb20b | ||
|
|
7436aa8e3a | ||
|
|
d75d3885c3 | ||
|
|
db4763a742 | ||
|
|
13c9f8ffb7 | ||
|
|
e4f67628c0 | ||
|
|
283bb73418 | ||
|
|
5b5a71d40c | ||
|
|
61060f032a | ||
|
|
3423b5848f | ||
|
|
fd8d1e13a0 | ||
|
|
c42d692ea6 | ||
|
|
5f37176938 | ||
|
|
375a91db32 | ||
|
|
b7ba426249 | ||
|
|
d3ad356c6a | ||
|
|
fdb97c1d02 | ||
|
|
8cda42ab0a | ||
|
|
fed2bdafeb | ||
|
|
9ba5752770 | ||
|
|
8648c2c42e | ||
|
|
b519b6e1e0 | ||
|
|
913c68982a | ||
|
|
6e1e67aa72 | ||
|
|
ee6fbabbfb | ||
|
|
db58efbe65 | ||
|
|
cd15d8b7a9 | ||
|
|
3b4b4ba40a | ||
|
|
eecee472b1 | ||
|
|
7b314116be | ||
|
|
bc6d4111a2 | ||
|
|
674d9796d0 | ||
|
|
5816320645 | ||
|
|
14254e8be8 | ||
|
|
e990235d32 | ||
|
|
5f122186bd | ||
|
|
3bfaee9c57 | ||
|
|
1ca0901cbe | ||
|
|
2d7555b7b8 | ||
|
|
3c7d1fcd32 | ||
|
|
c7fa2db556 | ||
|
|
3b06cc6782 | ||
|
|
7c9f48b84d | ||
|
|
fed2bf6dab | ||
|
|
2b583ffcdf | ||
|
|
6f46d15c05 | ||
|
|
018ccebd6f | ||
|
|
620b2d477a | ||
|
|
f73b678aae | ||
|
|
0463541d99 | ||
|
|
e45704833e | ||
|
|
0fdcc0af65 | ||
|
|
4fc2ed7195 | ||
|
|
d0464a5793 | ||
|
|
bdb0d13a2d | ||
|
|
2d2ef5d72c | ||
|
|
fb9b471150 | ||
|
|
3f0e0af177 | ||
|
|
0228aba06f | ||
|
|
1fd6666682 | ||
|
|
cff6600ded | ||
|
|
04ddcf53f3 | ||
|
|
e46ac45741 | ||
|
|
75089b7a9d | ||
|
|
0539a64569 | ||
|
|
778fd55f0d | ||
|
|
5a3f1f2b22 | ||
|
|
f95ce1870c | ||
|
|
0719a46372 | ||
|
|
a8ef4e5be8 | ||
|
|
e6fe2540b8 | ||
|
|
aadcde3edd | ||
|
|
984e609c61 | ||
|
|
57e70aaf50 | ||
|
|
bfdef120d1 | ||
|
|
32da359ba5 | ||
|
|
b19ed36b43 | ||
|
|
e5a212b5c8 | ||
|
|
9b863fb9bc | ||
|
|
7cab51745b | ||
|
|
18c6ff427e | ||
|
|
843f2d71d6 | ||
|
|
67540c9ee0 | ||
|
|
7f816c9243 | ||
|
|
76b888de17 | ||
|
|
65a16be299 | ||
|
|
1c8ff0ae66 | ||
|
|
29eade4880 | ||
|
|
86fd1d5b22 | ||
|
|
909b78a1cb | ||
|
|
2f81f9fb22 | ||
|
|
a6d4e4ed57 | ||
|
|
3e01c396e1 | ||
|
|
0beb08686c | ||
|
|
693c6cf5e4 | ||
|
|
bb87c988cb | ||
|
|
049b0239da | ||
|
|
932de08fc0 | ||
|
|
303791d5c6 | ||
|
|
7e4a689370 | ||
|
|
04e0fefdee | ||
|
|
9b4e6da226 | ||
|
|
e1c53a2465 | ||
|
|
121b930abf | ||
|
|
436560da39 | ||
|
|
3980f79ed5 | ||
|
|
1d0dc7eeab | ||
|
|
1f63fa8236 | ||
|
|
caf47dee09 | ||
|
|
d742479810 | ||
|
|
77933a0a85 | ||
|
|
2a087bf161 | ||
|
|
b0fe57ec80 | ||
|
|
09cb40786f | ||
|
|
18ecfc0521 | ||
|
|
59d932e9c1 | ||
|
|
578c8ce5dd | ||
|
|
3d4874dc34 | ||
|
|
5aaf2e8873 | ||
|
|
f3fd0f6d73 | ||
|
|
4468581d2e | ||
|
|
da642b7aad | ||
|
|
b379e3d187 | ||
|
|
6867c79185 | ||
|
|
a1705dc6b3 | ||
|
|
4af4486dd9 | ||
|
|
282a7f32d3 | ||
|
|
4c6a88a642 | ||
|
|
e41d0b9a76 | ||
|
|
a02090b06b | ||
|
|
0d9a546d74 | ||
|
|
8d99113bef | ||
|
|
4309f3bd58 | ||
|
|
42370939a8 | ||
|
|
654591cbf3 | ||
|
|
ad9c954a58 | ||
|
|
a703e1b3d3 | ||
|
|
e85f2254f0 | ||
|
|
8f2cf30191 | ||
|
|
296741306c | ||
|
|
5386a286fd | ||
|
|
803fb393bb | ||
|
|
ab944bd13a | ||
|
|
514c49d946 | ||
|
|
858bcdd3ff | ||
|
|
ed79980dd4 | ||
|
|
86a74e929a | ||
|
|
0d52430481 | ||
|
|
4eca802cdd | ||
|
|
ff0a25bd9c | ||
|
|
ace0eb366b | ||
|
|
ecd3dcd5df | ||
|
|
d971c5fa64 | ||
|
|
ae82df0fda | ||
|
|
e28262ebd9 | ||
|
|
250ee4b11c | ||
|
|
b7293d638b | ||
|
|
eee863e380 | ||
|
|
e509d719ee | ||
|
|
a79e814c8d | ||
|
|
1d8f44d356 | ||
|
|
7653d21cf5 | ||
|
|
46a2d83b84 | ||
|
|
79efc6789e | ||
|
|
2192210910 | ||
|
|
3fe1bef5cd | ||
|
|
84629df49c | ||
|
|
dbd0151c0e | ||
|
|
6da508f147 | ||
|
|
ef6b27ab35 | ||
|
|
8ef596eac7 | ||
|
|
8f4f4d48d5 | ||
|
|
60eae7443a | ||
|
|
8695ad6f59 | ||
|
|
dc5c452ef9 | ||
|
|
8aefe2cefe | ||
|
|
17420f76b3 | ||
|
|
ec510d34b5 | ||
|
|
45213aa631 | ||
|
|
4381dabbd9 | ||
|
|
b4a03fcf42 | ||
|
|
714be33850 | ||
|
|
5f23fc493d | ||
|
|
4fe93e521e | ||
|
|
6e6d903f99 | ||
|
|
667a2a3d84 | ||
|
|
f57b277d5a | ||
|
|
e62991c54d | ||
|
|
785d584603 | ||
|
|
da4aab9233 | ||
|
|
591b601fd3 | ||
|
|
19baea1883 | ||
|
|
80bc9be3ab | ||
|
|
8c7a7bc897 | ||
|
|
4aab728590 | ||
|
|
9cf060115d | ||
|
|
317b5ebae1 | ||
|
|
98a4930a52 | ||
|
|
1a596a5684 | ||
|
|
84a0a0fa14 | ||
|
|
da443973cb | ||
|
|
d073d10f9f | ||
|
|
2b7e7496f7 | ||
|
|
50ab677ea4 | ||
|
|
cb81558302 | ||
|
|
9259483081 | ||
|
|
4ece322f82 | ||
|
|
13e8fa733e | ||
|
|
3e473ae008 | ||
|
|
9ea3126118 | ||
|
|
6c56233edc | ||
|
|
487fda0226 | ||
|
|
74d3b22533 | ||
|
|
b5e018972f | ||
|
|
2af844385f | ||
|
|
540047e26e | ||
|
|
4d8b8a2db8 | ||
|
|
d581a3289b | ||
|
|
d756c9b10a | ||
|
|
63d3212bec | ||
|
|
136ff011b2 | ||
|
|
3bc15a96d5 | ||
|
|
43d5bb2038 | ||
|
|
8d39eab3a9 |
8
.github/CODEOWNERS
vendored
@@ -1,5 +1,5 @@
|
|||||||
# continuous integration
|
# continuous integration
|
||||||
/.github/workflows/ @lstein @blessedcoolant @hipsterusername
|
/.github/workflows/ @lstein @blessedcoolant @hipsterusername @ebr
|
||||||
|
|
||||||
# documentation
|
# documentation
|
||||||
/docs/ @lstein @blessedcoolant @hipsterusername @Millu
|
/docs/ @lstein @blessedcoolant @hipsterusername @Millu
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
# installation and configuration
|
# installation and configuration
|
||||||
/pyproject.toml @lstein @blessedcoolant @hipsterusername
|
/pyproject.toml @lstein @blessedcoolant @hipsterusername
|
||||||
/docker/ @lstein @blessedcoolant @hipsterusername
|
/docker/ @lstein @blessedcoolant @hipsterusername @ebr
|
||||||
/scripts/ @ebr @lstein @hipsterusername
|
/scripts/ @ebr @lstein @hipsterusername
|
||||||
/installer/ @lstein @ebr @hipsterusername
|
/installer/ @lstein @ebr @hipsterusername
|
||||||
/invokeai/assets @lstein @ebr @hipsterusername
|
/invokeai/assets @lstein @ebr @hipsterusername
|
||||||
@@ -26,9 +26,7 @@
|
|||||||
|
|
||||||
# front ends
|
# front ends
|
||||||
/invokeai/frontend/CLI @lstein @hipsterusername
|
/invokeai/frontend/CLI @lstein @hipsterusername
|
||||||
/invokeai/frontend/install @lstein @ebr @hipsterusername
|
/invokeai/frontend/install @lstein @ebr @hipsterusername
|
||||||
/invokeai/frontend/merge @lstein @blessedcoolant @hipsterusername
|
/invokeai/frontend/merge @lstein @blessedcoolant @hipsterusername
|
||||||
/invokeai/frontend/training @lstein @blessedcoolant @hipsterusername
|
/invokeai/frontend/training @lstein @blessedcoolant @hipsterusername
|
||||||
/invokeai/frontend/web @psychedelicious @blessedcoolant @maryhipp @hipsterusername
|
/invokeai/frontend/web @psychedelicious @blessedcoolant @maryhipp @hipsterusername
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
98
.github/ISSUE_TEMPLATE/BUG_REPORT.yml
vendored
@@ -6,10 +6,6 @@ title: '[bug]: '
|
|||||||
|
|
||||||
labels: ['bug']
|
labels: ['bug']
|
||||||
|
|
||||||
# assignees:
|
|
||||||
# - moderator_bot
|
|
||||||
# - lstein
|
|
||||||
|
|
||||||
body:
|
body:
|
||||||
- type: markdown
|
- type: markdown
|
||||||
attributes:
|
attributes:
|
||||||
@@ -18,10 +14,9 @@ body:
|
|||||||
|
|
||||||
- type: checkboxes
|
- type: checkboxes
|
||||||
attributes:
|
attributes:
|
||||||
label: Is there an existing issue for this?
|
label: Is there an existing issue for this problem?
|
||||||
description: |
|
description: |
|
||||||
Please use the [search function](https://github.com/invoke-ai/InvokeAI/issues?q=is%3Aissue+is%3Aopen+label%3Abug)
|
Please [search](https://github.com/invoke-ai/InvokeAI/issues) first to see if an issue already exists for the problem.
|
||||||
irst to see if an issue already exists for the bug you encountered.
|
|
||||||
options:
|
options:
|
||||||
- label: I have searched the existing issues
|
- label: I have searched the existing issues
|
||||||
required: true
|
required: true
|
||||||
@@ -33,80 +28,119 @@ body:
|
|||||||
- type: dropdown
|
- type: dropdown
|
||||||
id: os_dropdown
|
id: os_dropdown
|
||||||
attributes:
|
attributes:
|
||||||
label: OS
|
label: Operating system
|
||||||
description: Which operating System did you use when the bug occured
|
description: Your computer's operating system.
|
||||||
multiple: false
|
multiple: false
|
||||||
options:
|
options:
|
||||||
- 'Linux'
|
- 'Linux'
|
||||||
- 'Windows'
|
- 'Windows'
|
||||||
- 'macOS'
|
- 'macOS'
|
||||||
|
- 'other'
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
- type: dropdown
|
- type: dropdown
|
||||||
id: gpu_dropdown
|
id: gpu_dropdown
|
||||||
attributes:
|
attributes:
|
||||||
label: GPU
|
label: GPU vendor
|
||||||
description: Which kind of Graphic-Adapter is your System using
|
description: Your GPU's vendor.
|
||||||
multiple: false
|
multiple: false
|
||||||
options:
|
options:
|
||||||
- 'cuda'
|
- 'Nvidia (CUDA)'
|
||||||
- 'amd'
|
- 'AMD (ROCm)'
|
||||||
- 'mps'
|
- 'Apple Silicon (MPS)'
|
||||||
- 'cpu'
|
- 'None (CPU)'
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
id: gpu_model
|
||||||
|
attributes:
|
||||||
|
label: GPU model
|
||||||
|
description: Your GPU's model. If on Apple Silicon, this is your Mac's chip. Leave blank if on CPU.
|
||||||
|
placeholder: ex. RTX 2080 Ti, Mac M1 Pro
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
|
||||||
- type: input
|
- type: input
|
||||||
id: vram
|
id: vram
|
||||||
attributes:
|
attributes:
|
||||||
label: VRAM
|
label: GPU VRAM
|
||||||
description: Size of the VRAM if known
|
description: Your GPU's VRAM. If on Apple Silicon, this is your Mac's unified memory. Leave blank if on CPU.
|
||||||
placeholder: 8GB
|
placeholder: 8GB
|
||||||
validations:
|
validations:
|
||||||
required: false
|
required: false
|
||||||
|
|
||||||
- type: input
|
- type: input
|
||||||
id: version-number
|
id: version-number
|
||||||
attributes:
|
attributes:
|
||||||
label: What version did you experience this issue on?
|
label: Version number
|
||||||
description: |
|
description: |
|
||||||
Please share the version of Invoke AI that you experienced the issue on. If this is not the latest version, please update first to confirm the issue still exists. If you are testing main, please include the commit hash instead.
|
The version of Invoke you have installed. If it is not the latest version, please update and try again to confirm the issue still exists. If you are testing main, please include the commit hash instead.
|
||||||
placeholder: X.X.X
|
placeholder: ex. 3.6.1
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
|
- type: input
|
||||||
|
id: browser-version
|
||||||
|
attributes:
|
||||||
|
label: Browser
|
||||||
|
description: Your web browser and version.
|
||||||
|
placeholder: ex. Firefox 123.0b3
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: python-deps
|
||||||
|
attributes:
|
||||||
|
label: Python dependencies
|
||||||
|
description: |
|
||||||
|
If the problem occurred during image generation, click the gear icon at the bottom left corner, click "About", click the copy button and then paste here.
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
|
||||||
- type: textarea
|
- type: textarea
|
||||||
id: what-happened
|
id: what-happened
|
||||||
attributes:
|
attributes:
|
||||||
label: What happened?
|
label: What happened
|
||||||
description: |
|
description: |
|
||||||
Briefly describe what happened, what you expected to happen and how to reproduce this bug.
|
Describe what happened. Include any relevant error messages, stack traces and screenshots here.
|
||||||
placeholder: When using the webinterface and right-clicking on button X instead of the popup-menu there error Y appears
|
placeholder: I clicked button X and then Y happened.
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|
||||||
- type: textarea
|
- type: textarea
|
||||||
|
id: what-you-expected
|
||||||
attributes:
|
attributes:
|
||||||
label: Screenshots
|
label: What you expected to happen
|
||||||
description: If applicable, add screenshots to help explain your problem
|
description: Describe what you expected to happen.
|
||||||
placeholder: this is what the result looked like <screenshot>
|
placeholder: I expected Z to happen.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
|
||||||
|
- type: textarea
|
||||||
|
id: how-to-repro
|
||||||
|
attributes:
|
||||||
|
label: How to reproduce the problem
|
||||||
|
description: List steps to reproduce the problem.
|
||||||
|
placeholder: Start the app, generate an image with these settings, then click button X.
|
||||||
validations:
|
validations:
|
||||||
required: false
|
required: false
|
||||||
|
|
||||||
- type: textarea
|
- type: textarea
|
||||||
|
id: additional-context
|
||||||
attributes:
|
attributes:
|
||||||
label: Additional context
|
label: Additional context
|
||||||
description: Add any other context about the problem here
|
description: Any other context that might help us to understand the problem.
|
||||||
placeholder: Only happens when there is full moon and Friday the 13th on Christmas Eve 🎅🏻
|
placeholder: Only happens when there is full moon and Friday the 13th on Christmas Eve 🎅🏻
|
||||||
validations:
|
validations:
|
||||||
required: false
|
required: false
|
||||||
|
|
||||||
- type: input
|
- type: input
|
||||||
id: contact
|
id: discord-username
|
||||||
attributes:
|
attributes:
|
||||||
label: Contact Details
|
label: Discord username
|
||||||
description: __OPTIONAL__ How can we get in touch with you if we need more info (besides this issue)?
|
description: If you are on the Invoke discord and would prefer to be contacted there, please provide your username.
|
||||||
placeholder: ex. email@example.com, discordname, twitter, ...
|
placeholder: supercoolusername123
|
||||||
validations:
|
validations:
|
||||||
required: false
|
required: false
|
||||||
|
|||||||
59
.github/pr_labels.yml
vendored
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
Root:
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file: '*'
|
||||||
|
|
||||||
|
PythonDeps:
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file: 'pyproject.toml'
|
||||||
|
|
||||||
|
Python:
|
||||||
|
- changed-files:
|
||||||
|
- all-globs-to-any-file:
|
||||||
|
- 'invokeai/**'
|
||||||
|
- '!invokeai/frontend/web/**'
|
||||||
|
|
||||||
|
PythonTests:
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file: 'tests/**'
|
||||||
|
|
||||||
|
CICD:
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file: .github/**
|
||||||
|
|
||||||
|
Docker:
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file: docker/**
|
||||||
|
|
||||||
|
Installer:
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file: installer/**
|
||||||
|
|
||||||
|
Documentation:
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file: docs/**
|
||||||
|
|
||||||
|
Invocations:
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file: 'invokeai/app/invocations/**'
|
||||||
|
|
||||||
|
Backend:
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file: 'invokeai/backend/**'
|
||||||
|
|
||||||
|
Api:
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file: 'invokeai/app/api/**'
|
||||||
|
|
||||||
|
Services:
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file: 'invokeai/app/services/**'
|
||||||
|
|
||||||
|
FrontendDeps:
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file:
|
||||||
|
- '**/*/package.json'
|
||||||
|
- '**/*/pnpm-lock.yaml'
|
||||||
|
|
||||||
|
Frontend:
|
||||||
|
- changed-files:
|
||||||
|
- any-glob-to-any-file: 'invokeai/frontend/web/**'
|
||||||
15
.github/pull_request_template.md
vendored
@@ -42,6 +42,21 @@ Please provide steps on how to test changes, any hardware or
|
|||||||
software specifications as well as any other pertinent information.
|
software specifications as well as any other pertinent information.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
|
## Merge Plan
|
||||||
|
|
||||||
|
<!--
|
||||||
|
A merge plan describes how this PR should be handled after it is approved.
|
||||||
|
|
||||||
|
Example merge plans:
|
||||||
|
- "This PR can be merged when approved"
|
||||||
|
- "This must be squash-merged when approved"
|
||||||
|
- "DO NOT MERGE - I will rebase and tidy commits before merging"
|
||||||
|
- "#dev-chat on discord needs to be advised of this change when it is merged"
|
||||||
|
|
||||||
|
A merge plan is particularly important for large PRs or PRs that touch the
|
||||||
|
database in any way.
|
||||||
|
-->
|
||||||
|
|
||||||
## Added/updated tests?
|
## Added/updated tests?
|
||||||
|
|
||||||
- [ ] Yes
|
- [ ] Yes
|
||||||
|
|||||||
5
.github/workflows/build-container.yml
vendored
@@ -40,10 +40,14 @@ jobs:
|
|||||||
- name: Free up more disk space on the runner
|
- name: Free up more disk space on the runner
|
||||||
# https://github.com/actions/runner-images/issues/2840#issuecomment-1284059930
|
# https://github.com/actions/runner-images/issues/2840#issuecomment-1284059930
|
||||||
run: |
|
run: |
|
||||||
|
echo "----- Free space before cleanup"
|
||||||
|
df -h
|
||||||
sudo rm -rf /usr/share/dotnet
|
sudo rm -rf /usr/share/dotnet
|
||||||
sudo rm -rf "$AGENT_TOOLSDIRECTORY"
|
sudo rm -rf "$AGENT_TOOLSDIRECTORY"
|
||||||
sudo swapoff /mnt/swapfile
|
sudo swapoff /mnt/swapfile
|
||||||
sudo rm -rf /mnt/swapfile
|
sudo rm -rf /mnt/swapfile
|
||||||
|
echo "----- Free space after cleanup"
|
||||||
|
df -h
|
||||||
|
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
@@ -91,6 +95,7 @@ jobs:
|
|||||||
# password: ${{ secrets.DOCKERHUB_TOKEN }}
|
# password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Build container
|
- name: Build container
|
||||||
|
timeout-minutes: 40
|
||||||
id: docker_build
|
id: docker_build
|
||||||
uses: docker/build-push-action@v4
|
uses: docker/build-push-action@v4
|
||||||
with:
|
with:
|
||||||
|
|||||||
16
.github/workflows/label-pr.yml
vendored
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
name: "Pull Request Labeler"
|
||||||
|
on:
|
||||||
|
- pull_request_target
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
labeler:
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pull-requests: write
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- uses: actions/labeler@v5
|
||||||
|
with:
|
||||||
|
configuration-path: .github/pr_labels.yml
|
||||||
24
.github/workflows/lint-frontend.yml
vendored
@@ -22,12 +22,22 @@ jobs:
|
|||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
- name: Setup Node 18
|
- name: Setup Node 18
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: '18'
|
node-version: '18'
|
||||||
- uses: actions/checkout@v3
|
- name: Checkout
|
||||||
- run: 'yarn install --frozen-lockfile'
|
uses: actions/checkout@v4
|
||||||
- run: 'yarn run lint:tsc'
|
- name: Setup pnpm
|
||||||
- run: 'yarn run lint:madge'
|
uses: pnpm/action-setup@v2
|
||||||
- run: 'yarn run lint:eslint'
|
with:
|
||||||
- run: 'yarn run lint:prettier'
|
version: '8.12.1'
|
||||||
|
- name: Install dependencies
|
||||||
|
run: 'pnpm install --prefer-frozen-lockfile'
|
||||||
|
- name: Typescript
|
||||||
|
run: 'pnpm run lint:tsc'
|
||||||
|
- name: Madge
|
||||||
|
run: 'pnpm run lint:madge'
|
||||||
|
- name: ESLint
|
||||||
|
run: 'pnpm run lint:eslint'
|
||||||
|
- name: Prettier
|
||||||
|
run: 'pnpm run lint:prettier'
|
||||||
|
|||||||
50
.github/workflows/pypi-release.yml
vendored
@@ -1,13 +1,15 @@
|
|||||||
name: PyPI Release
|
name: PyPI Release
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
|
||||||
paths:
|
|
||||||
- 'invokeai/version/invokeai_version.py'
|
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
publish_package:
|
||||||
|
description: 'Publish build on PyPi? [true/false]'
|
||||||
|
required: true
|
||||||
|
default: 'false'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release:
|
build-and-release:
|
||||||
if: github.repository == 'invoke-ai/InvokeAI'
|
if: github.repository == 'invoke-ai/InvokeAI'
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
env:
|
env:
|
||||||
@@ -15,19 +17,43 @@ jobs:
|
|||||||
TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
|
TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
|
||||||
TWINE_NON_INTERACTIVE: 1
|
TWINE_NON_INTERACTIVE: 1
|
||||||
steps:
|
steps:
|
||||||
- name: checkout sources
|
- name: Checkout
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: install deps
|
- name: Setup Node 18
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '18'
|
||||||
|
|
||||||
|
- name: Setup pnpm
|
||||||
|
uses: pnpm/action-setup@v2
|
||||||
|
with:
|
||||||
|
version: '8.12.1'
|
||||||
|
|
||||||
|
- name: Install frontend dependencies
|
||||||
|
run: pnpm install --prefer-frozen-lockfile
|
||||||
|
working-directory: invokeai/frontend/web
|
||||||
|
|
||||||
|
- name: Build frontend
|
||||||
|
run: pnpm run build
|
||||||
|
working-directory: invokeai/frontend/web
|
||||||
|
|
||||||
|
- name: Install python dependencies
|
||||||
run: pip install --upgrade build twine
|
run: pip install --upgrade build twine
|
||||||
|
|
||||||
- name: build package
|
- name: Build python package
|
||||||
run: python3 -m build
|
run: python3 -m build
|
||||||
|
|
||||||
- name: check distribution
|
- name: Upload build as workflow artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: dist
|
||||||
|
path: dist
|
||||||
|
|
||||||
|
- name: Check distribution
|
||||||
run: twine check dist/*
|
run: twine check dist/*
|
||||||
|
|
||||||
- name: check PyPI versions
|
- name: Check PyPI versions
|
||||||
if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/release/')
|
if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/release/')
|
||||||
run: |
|
run: |
|
||||||
pip install --upgrade requests
|
pip install --upgrade requests
|
||||||
@@ -36,6 +62,6 @@ jobs:
|
|||||||
EXISTS=scripts.pypi_helper.local_on_pypi(); \
|
EXISTS=scripts.pypi_helper.local_on_pypi(); \
|
||||||
print(f'PACKAGE_EXISTS={EXISTS}')" >> $GITHUB_ENV
|
print(f'PACKAGE_EXISTS={EXISTS}')" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: upload package
|
- name: Publish build on PyPi
|
||||||
if: env.PACKAGE_EXISTS == 'False' && env.TWINE_PASSWORD != ''
|
if: env.PACKAGE_EXISTS == 'False' && env.TWINE_PASSWORD != '' && github.event.inputs.publish_package == 'true'
|
||||||
run: twine upload dist/*
|
run: twine upload dist/*
|
||||||
|
|||||||
2
.github/workflows/test-invoke-pip.yml
vendored
@@ -58,7 +58,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Check for changed python files
|
- name: Check for changed python files
|
||||||
id: changed-files
|
id: changed-files
|
||||||
uses: tj-actions/changed-files@v37
|
uses: tj-actions/changed-files@v41
|
||||||
with:
|
with:
|
||||||
files_yaml: |
|
files_yaml: |
|
||||||
python:
|
python:
|
||||||
|
|||||||
3
.gitignore
vendored
@@ -16,7 +16,7 @@ __pycache__/
|
|||||||
.Python
|
.Python
|
||||||
build/
|
build/
|
||||||
develop-eggs/
|
develop-eggs/
|
||||||
# dist/
|
dist/
|
||||||
downloads/
|
downloads/
|
||||||
eggs/
|
eggs/
|
||||||
.eggs/
|
.eggs/
|
||||||
@@ -187,3 +187,4 @@ installer/install.bat
|
|||||||
installer/install.sh
|
installer/install.sh
|
||||||
installer/update.bat
|
installer/update.bat
|
||||||
installer/update.sh
|
installer/update.sh
|
||||||
|
installer/InvokeAI-Installer/
|
||||||
|
|||||||
52
Makefile
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
# simple Makefile with scripts that are otherwise hard to remember
|
||||||
|
# to use, run from the repo root `make <command>`
|
||||||
|
|
||||||
|
default: help
|
||||||
|
|
||||||
|
help:
|
||||||
|
@echo Developer commands:
|
||||||
|
@echo
|
||||||
|
@echo "ruff Run ruff, fixing any safely-fixable errors and formatting"
|
||||||
|
@echo "ruff-unsafe Run ruff, fixing all fixable errors and formatting"
|
||||||
|
@echo "mypy Run mypy using the config in pyproject.toml to identify type mismatches and other coding errors"
|
||||||
|
@echo "mypy-all Run mypy ignoring the config in pyproject.tom but still ignoring missing imports"
|
||||||
|
@echo "frontend-build Build the frontend in order to run on localhost:9090"
|
||||||
|
@echo "frontend-dev Run the frontend in developer mode on localhost:5173"
|
||||||
|
@echo "installer-zip Build the installer .zip file for the current version"
|
||||||
|
@echo "tag-release Tag the GitHub repository with the current version (use at release time only!)"
|
||||||
|
|
||||||
|
# Runs ruff, fixing any safely-fixable errors and formatting
|
||||||
|
ruff:
|
||||||
|
ruff check . --fix
|
||||||
|
ruff format .
|
||||||
|
|
||||||
|
# Runs ruff, fixing all errors it can fix and formatting
|
||||||
|
ruff-unsafe:
|
||||||
|
ruff check . --fix --unsafe-fixes
|
||||||
|
ruff format .
|
||||||
|
|
||||||
|
# Runs mypy, using the config in pyproject.toml
|
||||||
|
mypy:
|
||||||
|
mypy scripts/invokeai-web.py
|
||||||
|
|
||||||
|
# Runs mypy, ignoring the config in pyproject.toml but still ignoring missing (untyped) imports
|
||||||
|
# (many files are ignored by the config, so this is useful for checking all files)
|
||||||
|
mypy-all:
|
||||||
|
mypy scripts/invokeai-web.py --config-file= --ignore-missing-imports
|
||||||
|
|
||||||
|
# Build the frontend
|
||||||
|
frontend-build:
|
||||||
|
cd invokeai/frontend/web && pnpm build
|
||||||
|
|
||||||
|
# Run the frontend in dev mode
|
||||||
|
frontend-dev:
|
||||||
|
cd invokeai/frontend/web && pnpm dev
|
||||||
|
|
||||||
|
# Installer zip file
|
||||||
|
installer-zip:
|
||||||
|
cd installer && ./create_installer.sh
|
||||||
|
|
||||||
|
# Tag the release
|
||||||
|
tag-release:
|
||||||
|
cd installer && ./tag_release.sh
|
||||||
|
|
||||||
22
README.md
@@ -1,10 +1,10 @@
|
|||||||
<div align="center">
|
<div align="center">
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
# Invoke AI - Generative AI for Professional Creatives
|
# Invoke - Professional Creative AI Tools for Visual Media
|
||||||
## Professional Creative Tools for Stable Diffusion, Custom-Trained Models, and more.
|
## To learn more about Invoke, or implement our Business solutions, visit [invoke.com](https://www.invoke.com/about)
|
||||||
To learn more about Invoke AI, get started instantly, or implement our Business solutions, visit [invoke.ai](https://invoke.ai)
|
|
||||||
|
|
||||||
|
|
||||||
[![discord badge]][discord link]
|
[![discord badge]][discord link]
|
||||||
@@ -56,7 +56,9 @@ the foundation for multiple commercial products.
|
|||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
|
|
||||||

|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -125,8 +127,8 @@ and go to http://localhost:9090.
|
|||||||
|
|
||||||
You must have Python 3.10 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 `pnpm` (can be installed with
|
||||||
the command `npm install -g yarn` if needed)
|
the command `npm install -g pnpm` if needed)
|
||||||
|
|
||||||
1. Open a command-line window on your machine. The PowerShell is recommended for Windows.
|
1. Open a command-line window on your machine. The PowerShell is recommended for Windows.
|
||||||
2. Create a directory to install InvokeAI into. You'll need at least 15 GB of free space:
|
2. Create a directory to install InvokeAI into. You'll need at least 15 GB of free space:
|
||||||
@@ -167,7 +169,7 @@ the command `npm install -g yarn` if needed)
|
|||||||
_For Linux with an AMD GPU:_
|
_For Linux with an AMD GPU:_
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
pip install InvokeAI --use-pep517 --extra-index-url https://download.pytorch.org/whl/rocm5.4.2
|
pip install InvokeAI --use-pep517 --extra-index-url https://download.pytorch.org/whl/rocm5.6
|
||||||
```
|
```
|
||||||
|
|
||||||
_For non-GPU systems:_
|
_For non-GPU systems:_
|
||||||
@@ -270,7 +272,7 @@ upgrade script.** See the next section for a Windows recipe.
|
|||||||
3. Select option [1] to upgrade to the latest release.
|
3. Select option [1] to upgrade to the latest release.
|
||||||
|
|
||||||
4. Once the upgrade is finished you will be returned to the launcher
|
4. Once the upgrade is finished you will be returned to the launcher
|
||||||
menu. Select option [7] "Re-run the configure script to fix a broken
|
menu. Select option [6] "Re-run the configure script to fix a broken
|
||||||
install or to complete a major upgrade".
|
install or to complete a major upgrade".
|
||||||
|
|
||||||
This will run the configure script against the v2.3 directory and
|
This will run the configure script against the v2.3 directory and
|
||||||
@@ -395,7 +397,7 @@ Notes](https://github.com/invoke-ai/InvokeAI/releases) and the
|
|||||||
|
|
||||||
### Troubleshooting
|
### Troubleshooting
|
||||||
|
|
||||||
Please check out our **[Q&A](https://invoke-ai.github.io/InvokeAI/help/TROUBLESHOOT/#faq)** to get solutions for common installation
|
Please check out our **[Troubleshooting Guide](https://invoke-ai.github.io/InvokeAI/installation/010_INSTALL_AUTOMATED/#troubleshooting)** to get solutions for common installation
|
||||||
problems and other issues. For more help, please join our [Discord][discord link]
|
problems and other issues. For more help, please join our [Discord][discord link]
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|||||||
@@ -2,14 +2,17 @@
|
|||||||
## 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.
|
## in addition to the examples below.
|
||||||
|
|
||||||
# INVOKEAI_ROOT is the path to a path on the local filesystem where InvokeAI will store data.
|
# HOST_INVOKEAI_ROOT is the path on the docker host's 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.
|
# If relative, it will be relative to the docker directory in which the docker-compose.yml file is located
|
||||||
INVOKEAI_ROOT=
|
#HOST_INVOKEAI_ROOT=../../invokeai-data
|
||||||
|
|
||||||
|
# INVOKEAI_ROOT is the path to the root of the InvokeAI repository within the container.
|
||||||
|
# INVOKEAI_ROOT=~/invokeai
|
||||||
|
|
||||||
# Get this value from your HuggingFace account settings page.
|
# Get this value from your HuggingFace account settings page.
|
||||||
# HUGGING_FACE_HUB_TOKEN=
|
# HUGGING_FACE_HUB_TOKEN=
|
||||||
|
|
||||||
## optional variables specific to the docker setup.
|
## optional variables specific to the docker setup.
|
||||||
# GPU_DRIVER=cuda # or rocm
|
# GPU_DRIVER=nvidia #| rocm
|
||||||
# CONTAINER_UID=1000
|
# CONTAINER_UID=1000
|
||||||
|
|||||||
@@ -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.1.0
|
ARG TORCH_VERSION=2.1.2
|
||||||
ARG TORCHVISION_VERSION=0.16
|
ARG TORCHVISION_VERSION=0.16.2
|
||||||
ARG GPU_DRIVER=cuda
|
ARG GPU_DRIVER=cuda
|
||||||
ARG TARGETPLATFORM="linux/amd64"
|
ARG TARGETPLATFORM="linux/amd64"
|
||||||
# unused but available
|
# unused but available
|
||||||
@@ -35,7 +35,7 @@ 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="--index-url https://download.pytorch.org/whl/rocm5.6"; \
|
extra_index_url_arg="--extra-index-url https://download.pytorch.org/whl/rocm5.6"; \
|
||||||
else \
|
else \
|
||||||
extra_index_url_arg="--extra-index-url https://download.pytorch.org/whl/cu121"; \
|
extra_index_url_arg="--extra-index-url https://download.pytorch.org/whl/cu121"; \
|
||||||
fi &&\
|
fi &&\
|
||||||
@@ -54,19 +54,21 @@ RUN --mount=type=cache,target=/root/.cache/pip \
|
|||||||
if [ "$GPU_DRIVER" = "cuda" ] && [ "$TARGETPLATFORM" = "linux/amd64" ]; then \
|
if [ "$GPU_DRIVER" = "cuda" ] && [ "$TARGETPLATFORM" = "linux/amd64" ]; then \
|
||||||
pip install -e ".[xformers]"; \
|
pip install -e ".[xformers]"; \
|
||||||
else \
|
else \
|
||||||
pip install -e "."; \
|
pip install $extra_index_url_arg -e "."; \
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# #### Build the Web UI ------------------------------------
|
# #### Build the Web UI ------------------------------------
|
||||||
|
|
||||||
FROM node:18 AS web-builder
|
FROM node:20-slim AS web-builder
|
||||||
|
ENV PNPM_HOME="/pnpm"
|
||||||
|
ENV PATH="$PNPM_HOME:$PATH"
|
||||||
|
RUN corepack enable
|
||||||
|
|
||||||
WORKDIR /build
|
WORKDIR /build
|
||||||
COPY invokeai/frontend/web/ ./
|
COPY invokeai/frontend/web/ ./
|
||||||
RUN --mount=type=cache,target=/usr/lib/node_modules \
|
RUN --mount=type=cache,target=/pnpm/store \
|
||||||
npm install --include dev
|
pnpm install --frozen-lockfile
|
||||||
RUN --mount=type=cache,target=/usr/lib/node_modules \
|
RUN npx vite build
|
||||||
yarn vite build
|
|
||||||
|
|
||||||
|
|
||||||
#### Runtime stage ---------------------------------------
|
#### Runtime stage ---------------------------------------
|
||||||
|
|
||||||
@@ -100,6 +102,8 @@ ENV INVOKEAI_SRC=/opt/invokeai
|
|||||||
ENV VIRTUAL_ENV=/opt/venv/invokeai
|
ENV VIRTUAL_ENV=/opt/venv/invokeai
|
||||||
ENV INVOKEAI_ROOT=/invokeai
|
ENV INVOKEAI_ROOT=/invokeai
|
||||||
ENV PATH="$VIRTUAL_ENV/bin:$INVOKEAI_SRC:$PATH"
|
ENV PATH="$VIRTUAL_ENV/bin:$INVOKEAI_SRC:$PATH"
|
||||||
|
ENV CONTAINER_UID=${CONTAINER_UID:-1000}
|
||||||
|
ENV CONTAINER_GID=${CONTAINER_GID:-1000}
|
||||||
|
|
||||||
# --link requires buldkit w/ dockerfile syntax 1.4
|
# --link requires buldkit w/ dockerfile syntax 1.4
|
||||||
COPY --link --from=builder ${INVOKEAI_SRC} ${INVOKEAI_SRC}
|
COPY --link --from=builder ${INVOKEAI_SRC} ${INVOKEAI_SRC}
|
||||||
@@ -117,7 +121,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"
|
||||||
|
|
||||||
RUN mkdir -p ${INVOKEAI_ROOT} && chown -R 1000:1000 ${INVOKEAI_ROOT}
|
RUN mkdir -p ${INVOKEAI_ROOT} && chown -R ${CONTAINER_UID}:${CONTAINER_GID} ${INVOKEAI_ROOT}
|
||||||
|
|
||||||
COPY docker/docker-entrypoint.sh ./
|
COPY docker/docker-entrypoint.sh ./
|
||||||
ENTRYPOINT ["/opt/invokeai/docker-entrypoint.sh"]
|
ENTRYPOINT ["/opt/invokeai/docker-entrypoint.sh"]
|
||||||
|
|||||||
@@ -1,6 +1,14 @@
|
|||||||
# InvokeAI Containerized
|
# InvokeAI Containerized
|
||||||
|
|
||||||
All commands are to be run from the `docker` directory: `cd docker`
|
All commands should be run within the `docker` directory: `cd docker`
|
||||||
|
|
||||||
|
## Quickstart :rocket:
|
||||||
|
|
||||||
|
On a known working Linux+Docker+CUDA (Nvidia) system, execute `./run.sh` in this directory. It will take a few minutes - depending on your internet speed - to install the core models. Once the application starts up, open `http://localhost:9090` in your browser to Invoke!
|
||||||
|
|
||||||
|
For more configuration options (using an AMD GPU, custom root directory location, etc): read on.
|
||||||
|
|
||||||
|
## Detailed setup
|
||||||
|
|
||||||
#### Linux
|
#### Linux
|
||||||
|
|
||||||
@@ -18,12 +26,12 @@ All commands are to be run from the `docker` directory: `cd docker`
|
|||||||
|
|
||||||
This is done via Docker Desktop preferences
|
This is done via Docker Desktop preferences
|
||||||
|
|
||||||
## Quickstart
|
### Configure Invoke environment
|
||||||
|
|
||||||
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.
|
||||||
1. `docker compose up`
|
1. Execute `run.sh`
|
||||||
|
|
||||||
The image will be built automatically if needed.
|
The image will be built automatically if needed.
|
||||||
|
|
||||||
@@ -37,19 +45,21 @@ The runtime directory (holding models and outputs) will be created in the locati
|
|||||||
|
|
||||||
The Docker daemon on the system must be already set up to use the GPU. In case of Linux, this involves installing `nvidia-docker-runtime` and configuring the `nvidia` runtime as default. Steps will be different for AMD. Please see Docker documentation for the most up-to-date instructions for using your GPU with Docker.
|
The Docker daemon on the system must be already set up to use the GPU. In case of Linux, this involves installing `nvidia-docker-runtime` and configuring the `nvidia` runtime as default. Steps will be different for AMD. Please see Docker documentation for the most up-to-date instructions for using your GPU with Docker.
|
||||||
|
|
||||||
|
To use an AMD GPU, set `GPU_DRIVER=rocm` in your `.env` file.
|
||||||
|
|
||||||
## Customize
|
## Customize
|
||||||
|
|
||||||
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 `run.sh`, 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 (values are optional, but setting `INVOKEAI_ROOT` is highly recommended):
|
Values are optional, but setting `INVOKEAI_ROOT` is highly recommended. The default is `~/invokeai`. Example:
|
||||||
|
|
||||||
```bash
|
```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=nvidia
|
||||||
```
|
```
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -e
|
|
||||||
|
|
||||||
build_args=""
|
|
||||||
|
|
||||||
[[ -f ".env" ]] && build_args=$(awk '$1 ~ /\=[^$]/ {print "--build-arg " $0 " "}' .env)
|
|
||||||
|
|
||||||
echo "docker compose build args:"
|
|
||||||
echo $build_args
|
|
||||||
|
|
||||||
docker compose build $build_args
|
|
||||||
@@ -2,23 +2,8 @@
|
|||||||
|
|
||||||
version: '3.8'
|
version: '3.8'
|
||||||
|
|
||||||
services:
|
x-invokeai: &invokeai
|
||||||
invokeai:
|
|
||||||
image: "local/invokeai:latest"
|
image: "local/invokeai:latest"
|
||||||
# edit below to run on a container runtime other than nvidia-container-runtime.
|
|
||||||
# not yet tested with rocm/AMD GPUs
|
|
||||||
# Comment out the "deploy" section to run on CPU only
|
|
||||||
deploy:
|
|
||||||
resources:
|
|
||||||
reservations:
|
|
||||||
devices:
|
|
||||||
- driver: nvidia
|
|
||||||
count: 1
|
|
||||||
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
|
||||||
@@ -36,7 +21,9 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- "${INVOKEAI_PORT:-9090}:9090"
|
- "${INVOKEAI_PORT:-9090}:9090"
|
||||||
volumes:
|
volumes:
|
||||||
- ${INVOKEAI_ROOT:-~/invokeai}:${INVOKEAI_ROOT:-/invokeai}
|
- type: bind
|
||||||
|
source: ${HOST_INVOKEAI_ROOT:-${INVOKEAI_ROOT:-~/invokeai}}
|
||||||
|
target: ${INVOKEAI_ROOT:-/invokeai}
|
||||||
- ${HF_HOME:-~/.cache/huggingface}:${HF_HOME:-/invokeai/.cache/huggingface}
|
- ${HF_HOME:-~/.cache/huggingface}:${HF_HOME:-/invokeai/.cache/huggingface}
|
||||||
# - ${INVOKEAI_MODELS_DIR:-${INVOKEAI_ROOT:-/invokeai/models}}
|
# - ${INVOKEAI_MODELS_DIR:-${INVOKEAI_ROOT:-/invokeai/models}}
|
||||||
# - ${INVOKEAI_MODELS_CONFIG_PATH:-${INVOKEAI_ROOT:-/invokeai/configs/models.yaml}}
|
# - ${INVOKEAI_MODELS_CONFIG_PATH:-${INVOKEAI_ROOT:-/invokeai/configs/models.yaml}}
|
||||||
@@ -50,3 +37,27 @@ services:
|
|||||||
# - |
|
# - |
|
||||||
# invokeai-model-install --yes --default-only --config_file ${INVOKEAI_ROOT}/config_custom.yaml
|
# invokeai-model-install --yes --default-only --config_file ${INVOKEAI_ROOT}/config_custom.yaml
|
||||||
# invokeai-nodes-web --host 0.0.0.0
|
# invokeai-nodes-web --host 0.0.0.0
|
||||||
|
|
||||||
|
services:
|
||||||
|
invokeai-nvidia:
|
||||||
|
<<: *invokeai
|
||||||
|
deploy:
|
||||||
|
resources:
|
||||||
|
reservations:
|
||||||
|
devices:
|
||||||
|
- driver: nvidia
|
||||||
|
count: 1
|
||||||
|
capabilities: [gpu]
|
||||||
|
|
||||||
|
invokeai-cpu:
|
||||||
|
<<: *invokeai
|
||||||
|
profiles:
|
||||||
|
- cpu
|
||||||
|
|
||||||
|
invokeai-rocm:
|
||||||
|
<<: *invokeai
|
||||||
|
devices:
|
||||||
|
- /dev/kfd:/dev/kfd
|
||||||
|
- /dev/dri:/dev/dri
|
||||||
|
profiles:
|
||||||
|
- rocm
|
||||||
|
|||||||
@@ -1,11 +1,32 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -e
|
set -e -o pipefail
|
||||||
|
|
||||||
# This script is provided for backwards compatibility with the old docker setup.
|
run() {
|
||||||
# it doesn't do much aside from wrapping the usual docker compose CLI.
|
local scriptdir=$(dirname "${BASH_SOURCE[0]}")
|
||||||
|
cd "$scriptdir" || exit 1
|
||||||
|
|
||||||
SCRIPTDIR=$(dirname "${BASH_SOURCE[0]}")
|
local build_args=""
|
||||||
cd "$SCRIPTDIR" || exit 1
|
local profile=""
|
||||||
|
|
||||||
docker compose up -d
|
touch .env
|
||||||
docker compose logs -f
|
build_args=$(awk '$1 ~ /=[^$]/ && $0 !~ /^#/ {print "--build-arg " $0 " "}' .env) &&
|
||||||
|
profile="$(awk -F '=' '/GPU_DRIVER/ {print $2}' .env)"
|
||||||
|
|
||||||
|
[[ -z "$profile" ]] && profile="nvidia"
|
||||||
|
|
||||||
|
local service_name="invokeai-$profile"
|
||||||
|
|
||||||
|
if [[ ! -z "$build_args" ]]; then
|
||||||
|
printf "%s\n" "docker compose build args:"
|
||||||
|
printf "%s\n" "$build_args"
|
||||||
|
fi
|
||||||
|
|
||||||
|
docker compose build $build_args $service_name
|
||||||
|
unset build_args
|
||||||
|
|
||||||
|
printf "%s\n" "starting service $service_name"
|
||||||
|
docker compose --profile "$profile" up -d "$service_name"
|
||||||
|
docker compose logs -f
|
||||||
|
}
|
||||||
|
|
||||||
|
run
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 297 KiB After Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 4.9 MiB |
|
Before Width: | Height: | Size: 169 KiB After Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 194 KiB After Width: | Height: | Size: 131 KiB |
|
Before Width: | Height: | Size: 209 KiB After Width: | Height: | Size: 122 KiB |
|
Before Width: | Height: | Size: 114 KiB After Width: | Height: | Size: 95 KiB |
|
Before Width: | Height: | Size: 187 KiB After Width: | Height: | Size: 123 KiB |
|
Before Width: | Height: | Size: 112 KiB After Width: | Height: | Size: 107 KiB |
|
Before Width: | Height: | Size: 132 KiB After Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 167 KiB After Width: | Height: | Size: 119 KiB |
|
Before Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 60 KiB |
BIN
docs/assets/nodes/workflow_library.png
Normal file
|
After Width: | Height: | Size: 129 KiB |
277
docs/contributing/DOWNLOAD_QUEUE.md
Normal file
@@ -0,0 +1,277 @@
|
|||||||
|
# The InvokeAI Download Queue
|
||||||
|
|
||||||
|
The DownloadQueueService provides a multithreaded parallel download
|
||||||
|
queue for arbitrary URLs, with queue prioritization, event handling,
|
||||||
|
and restart capabilities.
|
||||||
|
|
||||||
|
## Simple Example
|
||||||
|
|
||||||
|
```
|
||||||
|
from invokeai.app.services.download import DownloadQueueService, TqdmProgress
|
||||||
|
|
||||||
|
download_queue = DownloadQueueService()
|
||||||
|
for url in ['https://github.com/invoke-ai/InvokeAI/blob/main/invokeai/assets/a-painting-of-a-fire.png?raw=true',
|
||||||
|
'https://github.com/invoke-ai/InvokeAI/blob/main/invokeai/assets/birdhouse.png?raw=true',
|
||||||
|
'https://github.com/invoke-ai/InvokeAI/blob/main/invokeai/assets/missing.png',
|
||||||
|
'https://civitai.com/api/download/models/152309?type=Model&format=SafeTensor',
|
||||||
|
]:
|
||||||
|
|
||||||
|
# urls start downloading as soon as download() is called
|
||||||
|
download_queue.download(source=url,
|
||||||
|
dest='/tmp/downloads',
|
||||||
|
on_progress=TqdmProgress().update
|
||||||
|
)
|
||||||
|
|
||||||
|
download_queue.join() # wait for all downloads to finish
|
||||||
|
for job in download_queue.list_jobs():
|
||||||
|
print(job.model_dump_json(exclude_none=True, indent=4),"\n")
|
||||||
|
```
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"source": "https://github.com/invoke-ai/InvokeAI/blob/main/invokeai/assets/a-painting-of-a-fire.png?raw=true",
|
||||||
|
"dest": "/tmp/downloads",
|
||||||
|
"id": 0,
|
||||||
|
"priority": 10,
|
||||||
|
"status": "completed",
|
||||||
|
"download_path": "/tmp/downloads/a-painting-of-a-fire.png",
|
||||||
|
"job_started": "2023-12-04T05:34:41.742174",
|
||||||
|
"job_ended": "2023-12-04T05:34:42.592035",
|
||||||
|
"bytes": 666734,
|
||||||
|
"total_bytes": 666734
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
"source": "https://github.com/invoke-ai/InvokeAI/blob/main/invokeai/assets/birdhouse.png?raw=true",
|
||||||
|
"dest": "/tmp/downloads",
|
||||||
|
"id": 1,
|
||||||
|
"priority": 10,
|
||||||
|
"status": "completed",
|
||||||
|
"download_path": "/tmp/downloads/birdhouse.png",
|
||||||
|
"job_started": "2023-12-04T05:34:41.741975",
|
||||||
|
"job_ended": "2023-12-04T05:34:42.652841",
|
||||||
|
"bytes": 774949,
|
||||||
|
"total_bytes": 774949
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
"source": "https://github.com/invoke-ai/InvokeAI/blob/main/invokeai/assets/missing.png",
|
||||||
|
"dest": "/tmp/downloads",
|
||||||
|
"id": 2,
|
||||||
|
"priority": 10,
|
||||||
|
"status": "error",
|
||||||
|
"job_started": "2023-12-04T05:34:41.742079",
|
||||||
|
"job_ended": "2023-12-04T05:34:42.147625",
|
||||||
|
"bytes": 0,
|
||||||
|
"total_bytes": 0,
|
||||||
|
"error_type": "HTTPError(Not Found)",
|
||||||
|
"error": "Traceback (most recent call last):\n File \"/home/lstein/Projects/InvokeAI/invokeai/app/services/download/download_default.py\", line 182, in _download_next_item\n self._do_download(job)\n File \"/home/lstein/Projects/InvokeAI/invokeai/app/services/download/download_default.py\", line 206, in _do_download\n raise HTTPError(resp.reason)\nrequests.exceptions.HTTPError: Not Found\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
"source": "https://civitai.com/api/download/models/152309?type=Model&format=SafeTensor",
|
||||||
|
"dest": "/tmp/downloads",
|
||||||
|
"id": 3,
|
||||||
|
"priority": 10,
|
||||||
|
"status": "completed",
|
||||||
|
"download_path": "/tmp/downloads/xl_more_art-full_v1.safetensors",
|
||||||
|
"job_started": "2023-12-04T05:34:42.147645",
|
||||||
|
"job_ended": "2023-12-04T05:34:43.735990",
|
||||||
|
"bytes": 719020768,
|
||||||
|
"total_bytes": 719020768
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## The API
|
||||||
|
|
||||||
|
The default download queue is `DownloadQueueService`, an
|
||||||
|
implementation of ABC `DownloadQueueServiceBase`. It juggles multiple
|
||||||
|
background download requests and provides facilities for interrogating
|
||||||
|
and cancelling the requests. Access to a current or past download task
|
||||||
|
is mediated via `DownloadJob` objects which report the current status
|
||||||
|
of a job request
|
||||||
|
|
||||||
|
### The Queue Object
|
||||||
|
|
||||||
|
A default download queue is located in
|
||||||
|
`ApiDependencies.invoker.services.download_queue`. However, you can
|
||||||
|
create additional instances if you need to isolate your queue from the
|
||||||
|
main one.
|
||||||
|
|
||||||
|
```
|
||||||
|
queue = DownloadQueueService(event_bus=events)
|
||||||
|
```
|
||||||
|
|
||||||
|
`DownloadQueueService()` takes three optional arguments:
|
||||||
|
|
||||||
|
| **Argument** | **Type** | **Default** | **Description** |
|
||||||
|
|----------------|-----------------|---------------|-----------------|
|
||||||
|
| `max_parallel_dl` | int | 5 | Maximum number of simultaneous downloads allowed |
|
||||||
|
| `event_bus` | EventServiceBase | None | System-wide FastAPI event bus for reporting download events |
|
||||||
|
| `requests_session` | requests.sessions.Session | None | An alternative requests Session object to use for the download |
|
||||||
|
|
||||||
|
`max_parallel_dl` specifies how many download jobs are allowed to run
|
||||||
|
simultaneously. Each will run in a different thread of execution.
|
||||||
|
|
||||||
|
`event_bus` is an EventServiceBase, typically the one created at
|
||||||
|
InvokeAI startup. If present, download events are periodically emitted
|
||||||
|
on this bus to allow clients to follow download progress.
|
||||||
|
|
||||||
|
`requests_session` is a url library requests Session object. It is
|
||||||
|
used for testing.
|
||||||
|
|
||||||
|
### The Job object
|
||||||
|
|
||||||
|
The queue operates on a series of download job objects. These objects
|
||||||
|
specify the source and destination of the download, and keep track of
|
||||||
|
the progress of the download.
|
||||||
|
|
||||||
|
The only job type currently implemented is `DownloadJob`, a pydantic object with the
|
||||||
|
following fields:
|
||||||
|
|
||||||
|
| **Field** | **Type** | **Default** | **Description** |
|
||||||
|
|----------------|-----------------|---------------|-----------------|
|
||||||
|
| _Fields passed in at job creation time_ |
|
||||||
|
| `source` | AnyHttpUrl | | Where to download from |
|
||||||
|
| `dest` | Path | | Where to download to |
|
||||||
|
| `access_token` | str | | [optional] string containing authentication token for access |
|
||||||
|
| `on_start` | Callable | | [optional] callback when the download starts |
|
||||||
|
| `on_progress` | Callable | | [optional] callback called at intervals during download progress |
|
||||||
|
| `on_complete` | Callable | | [optional] callback called after successful download completion |
|
||||||
|
| `on_error` | Callable | | [optional] callback called after an error occurs |
|
||||||
|
| `id` | int | auto assigned | Job ID, an integer >= 0 |
|
||||||
|
| `priority` | int | 10 | Job priority. Lower priorities run before higher priorities |
|
||||||
|
| |
|
||||||
|
| _Fields updated over the course of the download task_
|
||||||
|
| `status` | DownloadJobStatus| | Status code |
|
||||||
|
| `download_path` | Path | | Path to the location of the downloaded file |
|
||||||
|
| `job_started` | float | | Timestamp for when the job started running |
|
||||||
|
| `job_ended` | float | | Timestamp for when the job completed or errored out |
|
||||||
|
| `job_sequence` | int | | A counter that is incremented each time a model is dequeued |
|
||||||
|
| `bytes` | int | 0 | Bytes downloaded so far |
|
||||||
|
| `total_bytes` | int | 0 | Total size of the file at the remote site |
|
||||||
|
| `error_type` | str | | String version of the exception that caused an error during download |
|
||||||
|
| `error` | str | | String version of the traceback associated with an error |
|
||||||
|
| `cancelled` | bool | False | Set to true if the job was cancelled by the caller|
|
||||||
|
|
||||||
|
When you create a job, you can assign it a `priority`. If multiple
|
||||||
|
jobs are queued, the job with the lowest priority runs first.
|
||||||
|
|
||||||
|
Every job has a `source` and a `dest`. `source` is a pydantic.networks AnyHttpUrl object.
|
||||||
|
The `dest` is a path on the local filesystem that specifies the
|
||||||
|
destination for the downloaded object. Its semantics are
|
||||||
|
described below.
|
||||||
|
|
||||||
|
When the job is submitted, it is assigned a numeric `id`. The id can
|
||||||
|
then be used to fetch the job object from the queue.
|
||||||
|
|
||||||
|
The `status` field is updated by the queue to indicate where the job
|
||||||
|
is in its lifecycle. Values are defined in the string enum
|
||||||
|
`DownloadJobStatus`, a symbol available from
|
||||||
|
`invokeai.app.services.download_manager`. Possible values are:
|
||||||
|
|
||||||
|
| **Value** | **String Value** | ** Description ** |
|
||||||
|
|--------------|---------------------|-------------------|
|
||||||
|
| `WAITING` | waiting | Job is on the queue but not yet running|
|
||||||
|
| `RUNNING` | running | The download is started |
|
||||||
|
| `COMPLETED` | completed | Job has finished its work without an error |
|
||||||
|
| `ERROR` | error | Job encountered an error and will not run again|
|
||||||
|
|
||||||
|
`job_started` and `job_ended` indicate when the job
|
||||||
|
was started (using a python timestamp) and when it completed.
|
||||||
|
|
||||||
|
In case of an error, the job's status will be set to `DownloadJobStatus.ERROR`, the text of the
|
||||||
|
Exception that caused the error will be placed in the `error_type`
|
||||||
|
field and the traceback that led to the error will be in `error`.
|
||||||
|
|
||||||
|
A cancelled job will have status `DownloadJobStatus.ERROR` and an
|
||||||
|
`error_type` field of "DownloadJobCancelledException". In addition,
|
||||||
|
the job's `cancelled` property will be set to True.
|
||||||
|
|
||||||
|
### Callbacks
|
||||||
|
|
||||||
|
Download jobs can be associated with a series of callbacks, each with
|
||||||
|
the signature `Callable[["DownloadJob"], None]`. The callbacks are assigned
|
||||||
|
using optional arguments `on_start`, `on_progress`, `on_complete` and
|
||||||
|
`on_error`. When the corresponding event occurs, the callback wil be
|
||||||
|
invoked and passed the job. The callback will be run in a `try:`
|
||||||
|
context in the same thread as the download job. Any exceptions that
|
||||||
|
occur during execution of the callback will be caught and converted
|
||||||
|
into a log error message, thereby allowing the download to continue.
|
||||||
|
|
||||||
|
#### `TqdmProgress`
|
||||||
|
|
||||||
|
The `invokeai.app.services.download.download_default` module defines a
|
||||||
|
class named `TqdmProgress` which can be used as an `on_progress`
|
||||||
|
handler to display a completion bar in the console. Use as follows:
|
||||||
|
|
||||||
|
```
|
||||||
|
from invokeai.app.services.download import TqdmProgress
|
||||||
|
|
||||||
|
download_queue.download(source='http://some.server.somewhere/some_file',
|
||||||
|
dest='/tmp/downloads',
|
||||||
|
on_progress=TqdmProgress().update
|
||||||
|
)
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### Events
|
||||||
|
|
||||||
|
If the queue was initialized with the InvokeAI event bus (the case
|
||||||
|
when using `ApiDependencies.invoker.services.download_queue`), then
|
||||||
|
download events will also be issued on the bus. The events are:
|
||||||
|
|
||||||
|
* `download_started` -- This is issued when a job is taken off the
|
||||||
|
queue and a request is made to the remote server for the URL headers, but before any data
|
||||||
|
has been downloaded. The event payload will contain the keys `source`
|
||||||
|
and `download_path`. The latter contains the path that the URL will be
|
||||||
|
downloaded to.
|
||||||
|
|
||||||
|
* `download_progress -- This is issued periodically as the download
|
||||||
|
runs. The payload contains the keys `source`, `download_path`,
|
||||||
|
`current_bytes` and `total_bytes`. The latter two fields can be
|
||||||
|
used to display the percent complete.
|
||||||
|
|
||||||
|
* `download_complete` -- This is issued when the download completes
|
||||||
|
successfully. The payload contains the keys `source`, `download_path`
|
||||||
|
and `total_bytes`.
|
||||||
|
|
||||||
|
* `download_error` -- This is issued when the download stops because
|
||||||
|
of an error condition. The payload contains the fields `error_type`
|
||||||
|
and `error`. The former is the text representation of the exception,
|
||||||
|
and the latter is a traceback showing where the error occurred.
|
||||||
|
|
||||||
|
### Job control
|
||||||
|
|
||||||
|
To create a job call the queue's `download()` method. You can list all
|
||||||
|
jobs using `list_jobs()`, fetch a single job by its with
|
||||||
|
`id_to_job()`, cancel a running job with `cancel_job()`, cancel all
|
||||||
|
running jobs with `cancel_all_jobs()`, and wait for all jobs to finish
|
||||||
|
with `join()`.
|
||||||
|
|
||||||
|
#### job = queue.download(source, dest, priority, access_token)
|
||||||
|
|
||||||
|
Create a new download job and put it on the queue, returning the
|
||||||
|
DownloadJob object.
|
||||||
|
|
||||||
|
#### jobs = queue.list_jobs()
|
||||||
|
|
||||||
|
Return a list of all active and inactive `DownloadJob`s.
|
||||||
|
|
||||||
|
#### job = queue.id_to_job(id)
|
||||||
|
|
||||||
|
Return the job corresponding to given ID.
|
||||||
|
|
||||||
|
Return a list of all active and inactive `DownloadJob`s.
|
||||||
|
|
||||||
|
#### queue.prune_jobs()
|
||||||
|
|
||||||
|
Remove inactive (complete or errored) jobs from the listing returned
|
||||||
|
by `list_jobs()`.
|
||||||
|
|
||||||
|
#### queue.join()
|
||||||
|
|
||||||
|
Block until all pending jobs have run to completion or errored out.
|
||||||
|
|
||||||
@@ -9,11 +9,15 @@ complex functionality.
|
|||||||
|
|
||||||
## Invocations Directory
|
## Invocations Directory
|
||||||
|
|
||||||
InvokeAI Nodes can be found in the `invokeai/app/invocations` directory. These can be used as examples to create your own nodes.
|
InvokeAI Nodes can be found in the `invokeai/app/invocations` directory. These
|
||||||
|
can be used as examples to create your own nodes.
|
||||||
|
|
||||||
New nodes should be added to a subfolder in `nodes` direction found at the root level of the InvokeAI installation location. Nodes added to this folder will be able to be used upon application startup.
|
New nodes should be added to a subfolder in `nodes` direction found at the root
|
||||||
|
level of the InvokeAI installation location. Nodes added to this folder will be
|
||||||
|
able to be used upon application startup.
|
||||||
|
|
||||||
|
Example `nodes` subfolder structure:
|
||||||
|
|
||||||
Example `nodes` subfolder structure:
|
|
||||||
```py
|
```py
|
||||||
├── __init__.py # Invoke-managed custom node loader
|
├── __init__.py # Invoke-managed custom node loader
|
||||||
│
|
│
|
||||||
@@ -30,14 +34,14 @@ Example `nodes` subfolder structure:
|
|||||||
└── fancy_node.py
|
└── fancy_node.py
|
||||||
```
|
```
|
||||||
|
|
||||||
Each node folder must have an `__init__.py` file that imports its nodes. Only nodes imported in the `__init__.py` file are loaded.
|
Each node folder must have an `__init__.py` file that imports its nodes. Only
|
||||||
See the README in the nodes folder for more examples:
|
nodes imported in the `__init__.py` file are loaded. See the README in the nodes
|
||||||
|
folder for more examples:
|
||||||
|
|
||||||
```py
|
```py
|
||||||
from .cool_node import CoolInvocation
|
from .cool_node import CoolInvocation
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
## Creating A New Invocation
|
## Creating A New Invocation
|
||||||
|
|
||||||
In order to understand the process of creating a new Invocation, let us actually
|
In order to understand the process of creating a new Invocation, let us actually
|
||||||
@@ -65,7 +69,7 @@ The first set of things we need to do when creating a new Invocation are -
|
|||||||
So let us do that.
|
So let us do that.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from .baseinvocation import BaseInvocation, invocation
|
from invokeai.app.invocations.baseinvocation import BaseInvocation, invocation
|
||||||
|
|
||||||
@invocation('resize')
|
@invocation('resize')
|
||||||
class ResizeInvocation(BaseInvocation):
|
class ResizeInvocation(BaseInvocation):
|
||||||
@@ -99,8 +103,8 @@ create your own custom field types later in this guide. For now, let's go ahead
|
|||||||
and use it.
|
and use it.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from .baseinvocation import BaseInvocation, InputField, invocation
|
from invokeai.app.invocations.baseinvocation import BaseInvocation, InputField, invocation
|
||||||
from .primitives import ImageField
|
from invokeai.app.invocations.primitives import ImageField
|
||||||
|
|
||||||
@invocation('resize')
|
@invocation('resize')
|
||||||
class ResizeInvocation(BaseInvocation):
|
class ResizeInvocation(BaseInvocation):
|
||||||
@@ -124,14 +128,13 @@ image: ImageField = InputField(description="The input image")
|
|||||||
Great. Now let us create our other inputs for `width` and `height`
|
Great. Now let us create our other inputs for `width` and `height`
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from .baseinvocation import BaseInvocation, InputField, invocation
|
from invokeai.app.invocations.baseinvocation import BaseInvocation, InputField, invocation
|
||||||
from .primitives import ImageField
|
from invokeai.app.invocations.primitives import ImageField
|
||||||
|
|
||||||
@invocation('resize')
|
@invocation('resize')
|
||||||
class ResizeInvocation(BaseInvocation):
|
class ResizeInvocation(BaseInvocation):
|
||||||
'''Resizes an image'''
|
'''Resizes an image'''
|
||||||
|
|
||||||
# Inputs
|
|
||||||
image: ImageField = InputField(description="The input image")
|
image: ImageField = InputField(description="The input image")
|
||||||
width: int = InputField(default=512, ge=64, le=2048, description="Width of the new image")
|
width: int = InputField(default=512, ge=64, le=2048, description="Width of the new image")
|
||||||
height: int = InputField(default=512, ge=64, le=2048, description="Height of the new image")
|
height: int = InputField(default=512, ge=64, le=2048, description="Height of the new image")
|
||||||
@@ -160,14 +163,13 @@ that are provided by it by InvokeAI.
|
|||||||
Let us create this function first.
|
Let us create this function first.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from .baseinvocation import BaseInvocation, InputField, invocation
|
from invokeai.app.invocations.baseinvocation import BaseInvocation, InputField, invocation, InvocationContext
|
||||||
from .primitives import ImageField
|
from invokeai.app.invocations.primitives import ImageField
|
||||||
|
|
||||||
@invocation('resize')
|
@invocation('resize')
|
||||||
class ResizeInvocation(BaseInvocation):
|
class ResizeInvocation(BaseInvocation):
|
||||||
'''Resizes an image'''
|
'''Resizes an image'''
|
||||||
|
|
||||||
# Inputs
|
|
||||||
image: ImageField = InputField(description="The input image")
|
image: ImageField = InputField(description="The input image")
|
||||||
width: int = InputField(default=512, ge=64, le=2048, description="Width of the new image")
|
width: int = InputField(default=512, ge=64, le=2048, description="Width of the new image")
|
||||||
height: int = InputField(default=512, ge=64, le=2048, description="Height of the new image")
|
height: int = InputField(default=512, ge=64, le=2048, description="Height of the new image")
|
||||||
@@ -189,15 +191,14 @@ all the necessary info related to image outputs. So let us use that.
|
|||||||
We will cover how to create your own output types later in this guide.
|
We will cover how to create your own output types later in this guide.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from .baseinvocation import BaseInvocation, InputField, invocation
|
from invokeai.app.invocations.baseinvocation import BaseInvocation, InputField, invocation, InvocationContext
|
||||||
from .primitives import ImageField
|
from invokeai.app.invocations.primitives import ImageField
|
||||||
from .image import ImageOutput
|
from invokeai.app.invocations.image import ImageOutput
|
||||||
|
|
||||||
@invocation('resize')
|
@invocation('resize')
|
||||||
class ResizeInvocation(BaseInvocation):
|
class ResizeInvocation(BaseInvocation):
|
||||||
'''Resizes an image'''
|
'''Resizes an image'''
|
||||||
|
|
||||||
# Inputs
|
|
||||||
image: ImageField = InputField(description="The input image")
|
image: ImageField = InputField(description="The input image")
|
||||||
width: int = InputField(default=512, ge=64, le=2048, description="Width of the new image")
|
width: int = InputField(default=512, ge=64, le=2048, description="Width of the new image")
|
||||||
height: int = InputField(default=512, ge=64, le=2048, description="Height of the new image")
|
height: int = InputField(default=512, ge=64, le=2048, description="Height of the new image")
|
||||||
@@ -216,9 +217,9 @@ Perfect. Now that we have our Invocation setup, let us do what we want to do.
|
|||||||
So let's do that.
|
So let's do that.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from .baseinvocation import BaseInvocation, InputField, invocation
|
from invokeai.app.invocations.baseinvocation import BaseInvocation, InputField, invocation, InvocationContext
|
||||||
from .primitives import ImageField
|
from invokeai.app.invocations.primitives import ImageField
|
||||||
from .image import ImageOutput
|
from invokeai.app.invocations.image import ImageOutput, ResourceOrigin, ImageCategory
|
||||||
|
|
||||||
@invocation("resize")
|
@invocation("resize")
|
||||||
class ResizeInvocation(BaseInvocation):
|
class ResizeInvocation(BaseInvocation):
|
||||||
@@ -229,30 +230,17 @@ class ResizeInvocation(BaseInvocation):
|
|||||||
height: int = InputField(default=512, ge=64, le=2048, description="Height of the new image")
|
height: int = InputField(default=512, ge=64, le=2048, description="Height of the new image")
|
||||||
|
|
||||||
def invoke(self, context: InvocationContext) -> ImageOutput:
|
def invoke(self, context: InvocationContext) -> ImageOutput:
|
||||||
# Load the image using InvokeAI's predefined Image Service. Returns the PIL image.
|
# Load the input image as a PIL image
|
||||||
image = context.services.images.get_pil_image(self.image.image_name)
|
image = context.images.get_pil(self.image.image_name)
|
||||||
|
|
||||||
# Resizing the image
|
# Resize the image
|
||||||
resized_image = image.resize((self.width, self.height))
|
resized_image = image.resize((self.width, self.height))
|
||||||
|
|
||||||
# Save the image using InvokeAI's predefined Image Service. Returns the prepared PIL image.
|
# Save the image
|
||||||
output_image = context.services.images.create(
|
image_dto = context.images.save(image=resized_image)
|
||||||
image=resized_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,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Returning the Image
|
# Return an ImageOutput
|
||||||
return ImageOutput(
|
return ImageOutput.build(image_dto)
|
||||||
image=ImageField(
|
|
||||||
image_name=output_image.image_name,
|
|
||||||
),
|
|
||||||
width=output_image.width,
|
|
||||||
height=output_image.height,
|
|
||||||
)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**Note:** Do not be overwhelmed by the `ImageOutput` process. InvokeAI has a
|
**Note:** Do not be overwhelmed by the `ImageOutput` process. InvokeAI has a
|
||||||
@@ -343,27 +331,25 @@ class ImageColorStringOutput(BaseInvocationOutput):
|
|||||||
|
|
||||||
That's all there is to it.
|
That's all there is to it.
|
||||||
|
|
||||||
<!-- TODO: DANGER - we probably do not want people to create their own field types, because this requires a lot of work on the frontend to accomodate.
|
|
||||||
|
|
||||||
### Custom Input Fields
|
### Custom Input Fields
|
||||||
|
|
||||||
Now that you know how to create your own Invocations, let us dive into slightly
|
Now that you know how to create your own Invocations, let us dive into slightly
|
||||||
more advanced topics.
|
more advanced topics.
|
||||||
|
|
||||||
While creating your own Invocations, you might run into a scenario where the
|
While creating your own Invocations, you might run into a scenario where the
|
||||||
existing input types in InvokeAI do not meet your requirements. In such cases,
|
existing fields in InvokeAI do not meet your requirements. In such cases, you
|
||||||
you can create your own input types.
|
can create your own fields.
|
||||||
|
|
||||||
Let us create one as an example. Let us say we want to create a color input
|
Let us create one as an example. Let us say we want to create a color input
|
||||||
field that represents a color code. But before we start on that here are some
|
field that represents a color code. But before we start on that here are some
|
||||||
general good practices to keep in mind.
|
general good practices to keep in mind.
|
||||||
|
|
||||||
**Good Practices**
|
### Best Practices
|
||||||
|
|
||||||
- There is no naming convention for input fields but we highly recommend that
|
- There is no naming convention for input fields but we highly recommend that
|
||||||
you name it something appropriate like `ColorField`.
|
you name it something appropriate like `ColorField`.
|
||||||
- It is not mandatory but it is heavily recommended to add a relevant
|
- It is not mandatory but it is heavily recommended to add a relevant
|
||||||
`docstring` to describe your input field.
|
`docstring` to describe your field.
|
||||||
- Keep your field in the same file as the Invocation that it is made for or in
|
- Keep your field in the same file as the Invocation that it is made for or in
|
||||||
another file where it is relevant.
|
another file where it is relevant.
|
||||||
|
|
||||||
@@ -378,10 +364,13 @@ class ColorField(BaseModel):
|
|||||||
pass
|
pass
|
||||||
```
|
```
|
||||||
|
|
||||||
Perfect. Now let us create our custom inputs for our field. This is exactly
|
Perfect. Now let us create the properties for our field. This is similar to how
|
||||||
similar how you created input fields for your Invocation. All the same rules
|
you created input fields for your Invocation. All the same rules apply. Let us
|
||||||
apply. Let us create four fields representing the _red(r)_, _blue(b)_,
|
create four fields representing the _red(r)_, _blue(b)_, _green(g)_ and
|
||||||
_green(g)_ and _alpha(a)_ channel of the color.
|
_alpha(a)_ channel of the color.
|
||||||
|
|
||||||
|
> Technically, the properties are _also_ called fields - but in this case, it
|
||||||
|
> refers to a `pydantic` field.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
class ColorField(BaseModel):
|
class ColorField(BaseModel):
|
||||||
@@ -396,25 +385,11 @@ That's it. We now have a new input field type that we can use in our Invocations
|
|||||||
like this.
|
like this.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
color: ColorField = Field(default=ColorField(r=0, g=0, b=0, a=0), description='Background color of an image')
|
color: ColorField = InputField(default=ColorField(r=0, g=0, b=0, a=0), description='Background color of an image')
|
||||||
```
|
```
|
||||||
|
|
||||||
### Custom Components For Frontend
|
### Using the custom field
|
||||||
|
|
||||||
Every backend input type should have a corresponding frontend component so the
|
When you start the UI, your custom field will be automatically recognized.
|
||||||
UI knows what to render when you use a particular field type.
|
|
||||||
|
|
||||||
If you are using existing field types, we already have components for those. So
|
Custom fields only support connection inputs in the Workflow Editor.
|
||||||
you don't have to worry about creating anything new. But this might not always
|
|
||||||
be the case. Sometimes you might want to create new field types and have the
|
|
||||||
frontend UI deal with it in a different way.
|
|
||||||
|
|
||||||
This is where we venture into the world of React and Javascript and create our
|
|
||||||
own new components for our Invocations. Do not fear the world of JS. It's
|
|
||||||
actually pretty straightforward.
|
|
||||||
|
|
||||||
Let us create a new component for our custom color field we created above. When
|
|
||||||
we use a color field, let us say we want the UI to display a color picker for
|
|
||||||
the user to pick from rather than entering values. That is what we will build
|
|
||||||
now.
|
|
||||||
-->
|
|
||||||
|
|||||||
@@ -1,75 +0,0 @@
|
|||||||
# Contributing to the Frontend
|
|
||||||
|
|
||||||
# InvokeAI Web UI
|
|
||||||
|
|
||||||
- [InvokeAI Web UI](https://github.com/invoke-ai/InvokeAI/tree/main/invokeai/frontend/web/docs#invokeai-web-ui)
|
|
||||||
- [Stack](https://github.com/invoke-ai/InvokeAI/tree/main/invokeai/frontend/web/docs#stack)
|
|
||||||
- [Contributing](https://github.com/invoke-ai/InvokeAI/tree/main/invokeai/frontend/web/docs#contributing)
|
|
||||||
- [Dev Environment](https://github.com/invoke-ai/InvokeAI/tree/main/invokeai/frontend/web/docs#dev-environment)
|
|
||||||
- [Production builds](https://github.com/invoke-ai/InvokeAI/tree/main/invokeai/frontend/web/docs#production-builds)
|
|
||||||
|
|
||||||
The UI is a fairly straightforward Typescript React app, with the Unified Canvas being more complex.
|
|
||||||
|
|
||||||
Code is located in `invokeai/frontend/web/` for review.
|
|
||||||
|
|
||||||
## Stack
|
|
||||||
|
|
||||||
State management is Redux via [Redux Toolkit](https://github.com/reduxjs/redux-toolkit). We lean heavily on RTK:
|
|
||||||
|
|
||||||
- `createAsyncThunk` for HTTP requests
|
|
||||||
- `createEntityAdapter` for fetching images and models
|
|
||||||
- `createListenerMiddleware` for workflows
|
|
||||||
|
|
||||||
The API client and associated types are generated from the OpenAPI schema. See API_CLIENT.md.
|
|
||||||
|
|
||||||
Communication with server is a mix of HTTP and [socket.io](https://github.com/socketio/socket.io-client) (with a simple socket.io redux middleware to help).
|
|
||||||
|
|
||||||
[Chakra-UI](https://github.com/chakra-ui/chakra-ui) & [Mantine](https://github.com/mantinedev/mantine) for components and styling.
|
|
||||||
|
|
||||||
[Konva](https://github.com/konvajs/react-konva) for the canvas, but we are pushing the limits of what is feasible with it (and HTML canvas in general). We plan to rebuild it with [PixiJS](https://github.com/pixijs/pixijs) to take advantage of WebGL's improved raster handling.
|
|
||||||
|
|
||||||
[Vite](https://vitejs.dev/) for bundling.
|
|
||||||
|
|
||||||
Localisation is via [i18next](https://github.com/i18next/react-i18next), but translation happens on our [Weblate](https://hosted.weblate.org/engage/invokeai/) project. Only the English source strings should be changed on this repo.
|
|
||||||
|
|
||||||
## Contributing
|
|
||||||
|
|
||||||
Thanks for your interest in contributing to the InvokeAI Web UI!
|
|
||||||
|
|
||||||
We encourage you to ping @psychedelicious and @blessedcoolant on [Discord](https://discord.gg/ZmtBAhwWhy) if you want to contribute, just to touch base and ensure your work doesn't conflict with anything else going on. The project is very active.
|
|
||||||
|
|
||||||
### Dev Environment
|
|
||||||
|
|
||||||
**Setup**
|
|
||||||
|
|
||||||
1. Install [node](https://nodejs.org/en/download/). You can confirm node is installed with:
|
|
||||||
```bash
|
|
||||||
node --version
|
|
||||||
```
|
|
||||||
2. Install [yarn classic](https://classic.yarnpkg.com/lang/en/) and confirm it is installed by running this:
|
|
||||||
```bash
|
|
||||||
npm install --global yarn
|
|
||||||
yarn --version
|
|
||||||
```
|
|
||||||
|
|
||||||
From `invokeai/frontend/web/` run `yarn install` to get everything set up.
|
|
||||||
|
|
||||||
Start everything in dev mode:
|
|
||||||
1. Ensure your virtual environment is running
|
|
||||||
2. Start the dev server: `yarn dev`
|
|
||||||
3. Start the InvokeAI Nodes backend: `python scripts/invokeai-web.py # run from the repo root`
|
|
||||||
4. Point your browser to the dev server address e.g. [http://localhost:5173/](http://localhost:5173/)
|
|
||||||
|
|
||||||
### VSCode Remote Dev
|
|
||||||
|
|
||||||
We've noticed an intermittent issue with the VSCode Remote Dev port forwarding. If you use this feature of VSCode, you may intermittently click the Invoke button and then get nothing until the request times out. Suggest disabling the IDE's port forwarding feature and doing it manually via SSH:
|
|
||||||
|
|
||||||
`ssh -L 9090:localhost:9090 -L 5173:localhost:5173 user@host`
|
|
||||||
|
|
||||||
### Production builds
|
|
||||||
|
|
||||||
For a number of technical and logistical reasons, we need to commit UI build artefacts to the repo.
|
|
||||||
|
|
||||||
If you submit a PR, there is a good chance we will ask you to include a separate commit with a build of the app.
|
|
||||||
|
|
||||||
To build for production, run `yarn build`.
|
|
||||||
@@ -12,7 +12,7 @@ To get started, take a look at our [new contributors checklist](newContributorCh
|
|||||||
Once you're setup, for more information, you can review the documentation specific to your area of interest:
|
Once you're setup, for more information, you can review the documentation specific to your area of interest:
|
||||||
|
|
||||||
* #### [InvokeAI Architecure](../ARCHITECTURE.md)
|
* #### [InvokeAI Architecure](../ARCHITECTURE.md)
|
||||||
* #### [Frontend Documentation](./contributingToFrontend.md)
|
* #### [Frontend Documentation](https://github.com/invoke-ai/InvokeAI/tree/main/invokeai/frontend/web)
|
||||||
* #### [Node Documentation](../INVOCATIONS.md)
|
* #### [Node Documentation](../INVOCATIONS.md)
|
||||||
* #### [Local Development](../LOCAL_DEVELOPMENT.md)
|
* #### [Local Development](../LOCAL_DEVELOPMENT.md)
|
||||||
|
|
||||||
|
|||||||
53
docs/deprecated/2to3.md
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
## :octicons-log-16: Important Changes Since Version 2.3
|
||||||
|
|
||||||
|
### Nodes
|
||||||
|
|
||||||
|
Behind the scenes, InvokeAI has been completely rewritten to support
|
||||||
|
"nodes," small unitary operations that can be combined into graphs to
|
||||||
|
form arbitrary workflows. For example, there is a prompt node that
|
||||||
|
processes the prompt string and feeds it to a text2latent node that
|
||||||
|
generates a latent image. The latents are then fed to a latent2image
|
||||||
|
node that translates the latent image into a PNG.
|
||||||
|
|
||||||
|
The WebGUI has a node editor that allows you to graphically design and
|
||||||
|
execute custom node graphs. The ability to save and load graphs is
|
||||||
|
still a work in progress, but coming soon.
|
||||||
|
|
||||||
|
### Command-Line Interface Retired
|
||||||
|
|
||||||
|
All "invokeai" command-line interfaces have been retired as of version
|
||||||
|
3.4.
|
||||||
|
|
||||||
|
To launch the Web GUI from the command-line, use the command
|
||||||
|
`invokeai-web` rather than the traditional `invokeai --web`.
|
||||||
|
|
||||||
|
### ControlNet
|
||||||
|
|
||||||
|
This version of InvokeAI features ControlNet, a system that allows you
|
||||||
|
to achieve exact poses for human and animal figures by providing a
|
||||||
|
model to follow. Full details are found in [ControlNet](features/CONTROLNET.md)
|
||||||
|
|
||||||
|
### New Schedulers
|
||||||
|
|
||||||
|
The list of schedulers has been completely revamped and brought up to date:
|
||||||
|
|
||||||
|
| **Short Name** | **Scheduler** | **Notes** |
|
||||||
|
|----------------|---------------------------------|-----------------------------|
|
||||||
|
| **ddim** | DDIMScheduler | |
|
||||||
|
| **ddpm** | DDPMScheduler | |
|
||||||
|
| **deis** | DEISMultistepScheduler | |
|
||||||
|
| **lms** | LMSDiscreteScheduler | |
|
||||||
|
| **pndm** | PNDMScheduler | |
|
||||||
|
| **heun** | HeunDiscreteScheduler | original noise schedule |
|
||||||
|
| **heun_k** | HeunDiscreteScheduler | using karras noise schedule |
|
||||||
|
| **euler** | EulerDiscreteScheduler | original noise schedule |
|
||||||
|
| **euler_k** | EulerDiscreteScheduler | using karras noise schedule |
|
||||||
|
| **kdpm_2** | KDPM2DiscreteScheduler | |
|
||||||
|
| **kdpm_2_a** | KDPM2AncestralDiscreteScheduler | |
|
||||||
|
| **dpmpp_2s** | DPMSolverSinglestepScheduler | |
|
||||||
|
| **dpmpp_2m** | DPMSolverMultistepScheduler | original noise scnedule |
|
||||||
|
| **dpmpp_2m_k** | DPMSolverMultistepScheduler | using karras noise schedule |
|
||||||
|
| **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.
|
||||||
@@ -154,14 +154,16 @@ groups in `invokeia.yaml`:
|
|||||||
|
|
||||||
### Web Server
|
### Web Server
|
||||||
|
|
||||||
| Setting | Default Value | Description |
|
| Setting | Default Value | Description |
|
||||||
|----------|----------------|--------------|
|
|---------------------|---------------|----------------------------------------------------------------------------------------------------------------------------|
|
||||||
| `host` | `localhost` | Name or IP address of the network interface that the web server will listen on |
|
| `host` | `localhost` | Name or IP address of the network interface that the web server will listen on |
|
||||||
| `port` | `9090` | Network port number that the web server will listen on |
|
| `port` | `9090` | Network port number that the web server will listen on |
|
||||||
| `allow_origins` | `[]` | A list of host names or IP addresses that are allowed to connect to the InvokeAI API in the format `['host1','host2',...]` |
|
| `allow_origins` | `[]` | A list of host names or IP addresses that are allowed to connect to the InvokeAI API in the format `['host1','host2',...]` |
|
||||||
| `allow_credentials` | `true` | Require credentials for a foreign host to access the InvokeAI API (don't change this) |
|
| `allow_credentials` | `true` | Require credentials for a foreign host to access the InvokeAI API (don't change this) |
|
||||||
| `allow_methods` | `*` | List of HTTP methods ("GET", "POST") that the web server is allowed to use when accessing the API |
|
| `allow_methods` | `*` | List of HTTP methods ("GET", "POST") that the web server is allowed to use when accessing the API |
|
||||||
| `allow_headers` | `*` | List of HTTP headers that the web server will accept when accessing the API |
|
| `allow_headers` | `*` | List of HTTP headers that the web server will accept when accessing the API |
|
||||||
|
| `ssl_certfile` | null | Path to an SSL certificate file, used to enable HTTPS. |
|
||||||
|
| `ssl_keyfile` | null | Path to an SSL keyfile, if the key is not included in the certificate file. |
|
||||||
|
|
||||||
The documentation for InvokeAI's API can be accessed by browsing to the following URL: [http://localhost:9090/docs].
|
The documentation for InvokeAI's API can be accessed by browsing to the following URL: [http://localhost:9090/docs].
|
||||||
|
|
||||||
|
|||||||
@@ -94,6 +94,8 @@ A model that helps generate creative QR codes that still scan. Can also be used
|
|||||||
**Openpose**:
|
**Openpose**:
|
||||||
The OpenPose control model allows for the identification of the general pose of a character by pre-processing an existing image with a clear human structure. With advanced options, Openpose can also detect the face or hands in the image.
|
The OpenPose control model allows for the identification of the general pose of a character by pre-processing an existing image with a clear human structure. With advanced options, Openpose can also detect the face or hands in the image.
|
||||||
|
|
||||||
|
*Note:* The DWPose Processor has replaced the OpenPose processor in Invoke. Workflows and generations that relied on the OpenPose Processor will need to be updated to use the DWPose Processor instead.
|
||||||
|
|
||||||
**Mediapipe Face**:
|
**Mediapipe Face**:
|
||||||
|
|
||||||
The MediaPipe Face identification processor is able to clearly identify facial features in order to capture vivid expressions of human faces.
|
The MediaPipe Face identification processor is able to clearly identify facial features in order to capture vivid expressions of human faces.
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ Generate an image with a given prompt, record the seed of the image, and then
|
|||||||
use the `prompt2prompt` syntax to substitute words in the original prompt for
|
use the `prompt2prompt` syntax to substitute words in the original prompt for
|
||||||
words in a new prompt. This works for `img2img` as well.
|
words in a new prompt. This works for `img2img` as well.
|
||||||
|
|
||||||
For example, consider the prompt `a cat.swap(dog) playing with a ball in the forest`. Normally, because of the word words interact with each other when doing a stable diffusion image generation, these two prompts would generate different compositions:
|
For example, consider the prompt `a cat.swap(dog) playing with a ball in the forest`. Normally, because the words interact with each other when doing a stable diffusion image generation, these two prompts would generate different compositions:
|
||||||
- `a cat playing with a ball in the forest`
|
- `a cat playing with a ball in the forest`
|
||||||
- `a dog playing with a ball in the forest`
|
- `a dog playing with a ball in the forest`
|
||||||
|
|
||||||
|
|||||||
@@ -229,29 +229,28 @@ clarity on the intent and common use cases we expect for utilizing them.
|
|||||||
currently being rendered by your browser into a merged copy of the image. This
|
currently being rendered by your browser into a merged copy of the image. This
|
||||||
lowers the resource requirements and should improve performance.
|
lowers the resource requirements and should improve performance.
|
||||||
|
|
||||||
### Seam Correction
|
### Compositing / Seam Correction
|
||||||
|
|
||||||
When doing Inpainting or Outpainting, Invoke needs to merge the pixels generated
|
When doing Inpainting or Outpainting, Invoke needs to merge the pixels generated
|
||||||
by Stable Diffusion into your existing image. To do this, the area around the
|
by Stable Diffusion into your existing image. This is achieved through compositing - the area around the the boundary between your image and the new generation is
|
||||||
`seam` at the boundary between your image and the new generation is
|
|
||||||
automatically blended to produce a seamless output. In a fully automatic
|
automatically blended to produce a seamless output. In a fully automatic
|
||||||
process, a mask is generated to cover the seam, and then the area of the seam is
|
process, a mask is generated to cover the boundary, and then the area of the boundary is
|
||||||
Inpainted.
|
Inpainted.
|
||||||
|
|
||||||
Although the default options should work well most of the time, sometimes it can
|
Although the default options should work well most of the time, sometimes it can
|
||||||
help to alter the parameters that control the seam Inpainting. A wider seam and
|
help to alter the parameters that control the Compositing. A larger blur and
|
||||||
a blur setting of about 1/3 of the seam have been noted as producing
|
a blur setting have been noted as producing
|
||||||
consistently strong results (e.g. 96 wide and 16 blur - adds up to 32 blur with
|
consistently strong results . Strength of 0.7 is best for reducing hard seams.
|
||||||
both sides). Seam strength of 0.7 is best for reducing hard seams.
|
|
||||||
|
- **Mode** - What part of the image will have the the Compositing applied to it.
|
||||||
|
- **Mask edge** will apply Compositing to the edge of the masked area
|
||||||
|
- **Mask** will apply Compositing to the entire masked area
|
||||||
|
- **Unmasked** will apply Compositing to the entire image
|
||||||
|
- **Steps** - Number of generation steps that will occur during the Coherence Pass, similar to Denoising Steps. Higher step counts will generally have better results.
|
||||||
|
- **Strength** - How much noise is added for the Coherence Pass, similar to Denoising Strength. A strength of 0 will result in an unchanged image, while a strength of 1 will result in an image with a completely new area as defined by the Mode setting.
|
||||||
|
- **Blur** - Adjusts the pixel radius of the the mask. A larger blur radius will cause the mask to extend past the visibly masked area, while too small of a blur radius will result in a mask that is smaller than the visibly masked area.
|
||||||
|
- **Blur Method** - The method of blur applied to the masked area.
|
||||||
|
|
||||||
- **Seam Size** - The size of the seam masked area. Set higher to make a larger
|
|
||||||
mask around the seam.
|
|
||||||
- **Seam Blur** - The size of the blur that is applied on _each_ side of the
|
|
||||||
masked area.
|
|
||||||
- **Seam Strength** - The Image To Image Strength parameter used for the
|
|
||||||
Inpainting generation that is applied to the seam area.
|
|
||||||
- **Seam Steps** - The number of generation steps that should be used to Inpaint
|
|
||||||
the seam.
|
|
||||||
|
|
||||||
### Infill & Scaling
|
### Infill & Scaling
|
||||||
|
|
||||||
|
|||||||
BIN
docs/img/favicon.ico
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
@@ -18,7 +18,7 @@ title: Home
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
height: 50px;
|
height: 50px;
|
||||||
background-color: #448AFF;
|
background-color: #35A4DB;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
border: none;
|
border: none;
|
||||||
@@ -43,7 +43,7 @@ title: Home
|
|||||||
<div align="center" markdown>
|
<div align="center" markdown>
|
||||||
|
|
||||||
|
|
||||||
[](https://github.com/invoke-ai/InvokeAI)
|
[](https://github.com/invoke-ai/InvokeAI)
|
||||||
|
|
||||||
[![discord badge]][discord link]
|
[![discord badge]][discord link]
|
||||||
|
|
||||||
@@ -117,6 +117,11 @@ Mac and Linux machines, and runs on GPU cards with as little as 4 GB of RAM.
|
|||||||
|
|
||||||
## :octicons-gift-24: InvokeAI Features
|
## :octicons-gift-24: InvokeAI Features
|
||||||
|
|
||||||
|
### Installation
|
||||||
|
- [Automated Installer](installation/010_INSTALL_AUTOMATED.md)
|
||||||
|
- [Manual Installation](installation/020_INSTALL_MANUAL.md)
|
||||||
|
- [Docker Installation](installation/040_INSTALL_DOCKER.md)
|
||||||
|
|
||||||
### The InvokeAI Web Interface
|
### The InvokeAI Web Interface
|
||||||
- [WebUI overview](features/WEB.md)
|
- [WebUI overview](features/WEB.md)
|
||||||
- [WebUI hotkey reference guide](features/WEBUIHOTKEYS.md)
|
- [WebUI hotkey reference guide](features/WEBUIHOTKEYS.md)
|
||||||
@@ -145,60 +150,6 @@ Mac and Linux machines, and runs on GPU cards with as little as 4 GB of RAM.
|
|||||||
- [Guide to InvokeAI Runtime Settings](features/CONFIGURATION.md)
|
- [Guide to InvokeAI Runtime Settings](features/CONFIGURATION.md)
|
||||||
- [Database Maintenance and other Command Line Utilities](features/UTILITIES.md)
|
- [Database Maintenance and other Command Line Utilities](features/UTILITIES.md)
|
||||||
|
|
||||||
## :octicons-log-16: Important Changes Since Version 2.3
|
|
||||||
|
|
||||||
### Nodes
|
|
||||||
|
|
||||||
Behind the scenes, InvokeAI has been completely rewritten to support
|
|
||||||
"nodes," small unitary operations that can be combined into graphs to
|
|
||||||
form arbitrary workflows. For example, there is a prompt node that
|
|
||||||
processes the prompt string and feeds it to a text2latent node that
|
|
||||||
generates a latent image. The latents are then fed to a latent2image
|
|
||||||
node that translates the latent image into a PNG.
|
|
||||||
|
|
||||||
The WebGUI has a node editor that allows you to graphically design and
|
|
||||||
execute custom node graphs. The ability to save and load graphs is
|
|
||||||
still a work in progress, but coming soon.
|
|
||||||
|
|
||||||
### Command-Line Interface Retired
|
|
||||||
|
|
||||||
All "invokeai" command-line interfaces have been retired as of version
|
|
||||||
3.4.
|
|
||||||
|
|
||||||
To launch the Web GUI from the command-line, use the command
|
|
||||||
`invokeai-web` rather than the traditional `invokeai --web`.
|
|
||||||
|
|
||||||
### ControlNet
|
|
||||||
|
|
||||||
This version of InvokeAI features ControlNet, a system that allows you
|
|
||||||
to achieve exact poses for human and animal figures by providing a
|
|
||||||
model to follow. Full details are found in [ControlNet](features/CONTROLNET.md)
|
|
||||||
|
|
||||||
### New Schedulers
|
|
||||||
|
|
||||||
The list of schedulers has been completely revamped and brought up to date:
|
|
||||||
|
|
||||||
| **Short Name** | **Scheduler** | **Notes** |
|
|
||||||
|----------------|---------------------------------|-----------------------------|
|
|
||||||
| **ddim** | DDIMScheduler | |
|
|
||||||
| **ddpm** | DDPMScheduler | |
|
|
||||||
| **deis** | DEISMultistepScheduler | |
|
|
||||||
| **lms** | LMSDiscreteScheduler | |
|
|
||||||
| **pndm** | PNDMScheduler | |
|
|
||||||
| **heun** | HeunDiscreteScheduler | original noise schedule |
|
|
||||||
| **heun_k** | HeunDiscreteScheduler | using karras noise schedule |
|
|
||||||
| **euler** | EulerDiscreteScheduler | original noise schedule |
|
|
||||||
| **euler_k** | EulerDiscreteScheduler | using karras noise schedule |
|
|
||||||
| **kdpm_2** | KDPM2DiscreteScheduler | |
|
|
||||||
| **kdpm_2_a** | KDPM2AncestralDiscreteScheduler | |
|
|
||||||
| **dpmpp_2s** | DPMSolverSinglestepScheduler | |
|
|
||||||
| **dpmpp_2m** | DPMSolverMultistepScheduler | original noise scnedule |
|
|
||||||
| **dpmpp_2m_k** | DPMSolverMultistepScheduler | using karras noise schedule |
|
|
||||||
| **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.
|
|
||||||
|
|
||||||
## :material-target: Troubleshooting
|
## :material-target: Troubleshooting
|
||||||
|
|
||||||
Please check out our **[:material-frequently-asked-questions:
|
Please check out our **[:material-frequently-asked-questions:
|
||||||
|
|||||||
@@ -477,7 +477,7 @@ Then type the following commands:
|
|||||||
|
|
||||||
=== "AMD System"
|
=== "AMD System"
|
||||||
```bash
|
```bash
|
||||||
pip install torch torchvision --force-reinstall --extra-index-url https://download.pytorch.org/whl/rocm5.4.2
|
pip install torch torchvision --force-reinstall --extra-index-url https://download.pytorch.org/whl/rocm5.6
|
||||||
```
|
```
|
||||||
|
|
||||||
### Corrupted configuration file
|
### Corrupted configuration file
|
||||||
|
|||||||
@@ -154,7 +154,7 @@ manager, please follow these steps:
|
|||||||
=== "ROCm (AMD)"
|
=== "ROCm (AMD)"
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pip install InvokeAI --use-pep517 --extra-index-url https://download.pytorch.org/whl/rocm5.4.2
|
pip install InvokeAI --use-pep517 --extra-index-url https://download.pytorch.org/whl/rocm5.6
|
||||||
```
|
```
|
||||||
|
|
||||||
=== "CPU (Intel Macs & non-GPU systems)"
|
=== "CPU (Intel Macs & non-GPU systems)"
|
||||||
@@ -230,13 +230,13 @@ manager, please follow these steps:
|
|||||||
=== "local Webserver"
|
=== "local Webserver"
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
invokeai --web
|
invokeai-web
|
||||||
```
|
```
|
||||||
|
|
||||||
=== "Public Webserver"
|
=== "Public Webserver"
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
invokeai --web --host 0.0.0.0
|
invokeai-web --host 0.0.0.0
|
||||||
```
|
```
|
||||||
|
|
||||||
=== "CLI"
|
=== "CLI"
|
||||||
@@ -293,6 +293,19 @@ manager, please follow these steps:
|
|||||||
|
|
||||||
## Developer Install
|
## Developer Install
|
||||||
|
|
||||||
|
!!! warning
|
||||||
|
|
||||||
|
InvokeAI uses a SQLite database. By running on `main`, you accept responsibility for your database. This
|
||||||
|
means making regular backups (especially before pulling) and/or fixing it yourself in the event that a
|
||||||
|
PR introduces a schema change.
|
||||||
|
|
||||||
|
If you don't need persistent backend storage, you can use an ephemeral in-memory database by setting
|
||||||
|
`use_memory_db: true` under `Path:` in your `invokeai.yaml` file.
|
||||||
|
|
||||||
|
If this is untenable, you should run the application via the official installer or a manual install of the
|
||||||
|
python package from pypi. These releases will not break your database.
|
||||||
|
|
||||||
|
|
||||||
If you have an interest in how InvokeAI works, or you would like to
|
If you have an interest in how InvokeAI works, or you would like to
|
||||||
add features or bugfixes, you are encouraged to install the source
|
add features or bugfixes, you are encouraged to install the source
|
||||||
code for InvokeAI. For this to work, you will need to install the
|
code for InvokeAI. For this to work, you will need to install the
|
||||||
@@ -300,7 +313,7 @@ code for InvokeAI. For this to work, you will need to install the
|
|||||||
on your system, please see the [Git Installation
|
on your system, please see the [Git Installation
|
||||||
Guide](https://github.com/git-guides/install-git)
|
Guide](https://github.com/git-guides/install-git)
|
||||||
|
|
||||||
You will also need to install the [frontend development toolchain](https://github.com/invoke-ai/InvokeAI/blob/main/docs/contributing/contribution_guides/contributingToFrontend.md).
|
You will also need to install the [frontend development toolchain](https://github.com/invoke-ai/InvokeAI/blob/main/invokeai/frontend/web/README.md).
|
||||||
|
|
||||||
If you have a "normal" installation, you should create a totally separate virtual environment for the git-based installation, else the two may interfere.
|
If you have a "normal" installation, you should create a totally separate virtual environment for the git-based installation, else the two may interfere.
|
||||||
|
|
||||||
@@ -332,7 +345,7 @@ installation protocol (important!)
|
|||||||
|
|
||||||
=== "ROCm (AMD)"
|
=== "ROCm (AMD)"
|
||||||
```bash
|
```bash
|
||||||
pip install -e . --use-pep517 --extra-index-url https://download.pytorch.org/whl/rocm5.4.2
|
pip install -e . --use-pep517 --extra-index-url https://download.pytorch.org/whl/rocm5.6
|
||||||
```
|
```
|
||||||
|
|
||||||
=== "CPU (Intel Macs & non-GPU systems)"
|
=== "CPU (Intel Macs & non-GPU systems)"
|
||||||
@@ -348,7 +361,7 @@ installation protocol (important!)
|
|||||||
Be sure to pass `-e` (for an editable install) and don't forget the
|
Be sure to pass `-e` (for an editable install) and don't forget the
|
||||||
dot ("."). It is part of the command.
|
dot ("."). It is part of the command.
|
||||||
|
|
||||||
5. Install the [frontend toolchain](https://github.com/invoke-ai/InvokeAI/blob/main/docs/contributing/contribution_guides/contributingToFrontend.md) and do a production build of the UI as described.
|
5. Install the [frontend toolchain](https://github.com/invoke-ai/InvokeAI/blob/main/invokeai/frontend/web/README.md) and do a production build of the UI as described.
|
||||||
|
|
||||||
6. You can now run `invokeai` and its related commands. The code will be
|
6. You can now run `invokeai` and its related commands. The code will be
|
||||||
read from the repository, so that you can edit the .py source files
|
read from the repository, so that you can edit the .py source files
|
||||||
@@ -388,3 +401,5 @@ environment variable INVOKEAI_ROOT to point to the installation directory.
|
|||||||
|
|
||||||
Note that if you run into problems with the Conda installation, the InvokeAI
|
Note that if you run into problems with the Conda installation, the InvokeAI
|
||||||
staff will **not** be able to help you out. Caveat Emptor!
|
staff will **not** be able to help you out. Caveat Emptor!
|
||||||
|
|
||||||
|
[dev-chat]: https://discord.com/channels/1020123559063990373/1049495067846524939
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ recipes are available
|
|||||||
|
|
||||||
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/rocm5.4.2` as described in the [Manual
|
https://download.pytorch.org/whl/rocm5.6` as described in the [Manual
|
||||||
Installation Guide](020_INSTALL_MANUAL.md).
|
Installation Guide](020_INSTALL_MANUAL.md).
|
||||||
|
|
||||||
This will be done automatically for you if you use the installer
|
This will be done automatically for you if you use the installer
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ a token and copy it, since you will need in for the next step.
|
|||||||
|
|
||||||
### Setup
|
### Setup
|
||||||
|
|
||||||
Set up your environmnent variables. In the `docker` directory, make a copy of `env.sample` and name it `.env`. Make changes as necessary.
|
Set up your environmnent variables. In the `docker` directory, make a copy of `.env.sample` and name it `.env`. Make changes as necessary.
|
||||||
|
|
||||||
Any environment variables supported by InvokeAI can be set here - please see the [CONFIGURATION](../features/CONFIGURATION.md) for further detail.
|
Any environment variables supported by InvokeAI can be set here - please see the [CONFIGURATION](../features/CONFIGURATION.md) for further detail.
|
||||||
|
|
||||||
|
|||||||
@@ -18,13 +18,18 @@ either an Nvidia-based card (with CUDA support) or an AMD card (using the ROCm
|
|||||||
driver).
|
driver).
|
||||||
|
|
||||||
|
|
||||||
## **[Automated Installer](010_INSTALL_AUTOMATED.md)**
|
## **[Automated Installer (Recommended)](010_INSTALL_AUTOMATED.md)**
|
||||||
✅ This is the recommended installation method for first-time users.
|
✅ This is the recommended installation method for first-time users.
|
||||||
|
|
||||||
This is a script that will install all of InvokeAI's essential
|
This is a script that will install all of InvokeAI's essential
|
||||||
third party libraries and InvokeAI itself. It includes access to a
|
third party libraries and InvokeAI itself.
|
||||||
"developer console" which will help us debug problems with you and
|
|
||||||
give you to access experimental features.
|
🖥️ **Download the latest installer .zip file here** : https://github.com/invoke-ai/InvokeAI/releases/latest
|
||||||
|
|
||||||
|
- *Look for the file labelled "InvokeAI-installer-v3.X.X.zip" at the bottom of the page*
|
||||||
|
- If you experience issues, read through the full [installation instructions](010_INSTALL_AUTOMATED.md) to make sure you have met all of the installation requirements. If you need more help, join the [Discord](discord.gg/invoke-ai) or create an issue on [Github](https://github.com/invoke-ai/InvokeAI).
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## **[Manual Installation](020_INSTALL_MANUAL.md)**
|
## **[Manual Installation](020_INSTALL_MANUAL.md)**
|
||||||
This method is recommended for experienced users and developers.
|
This method is recommended for experienced users and developers.
|
||||||
|
|||||||
10
docs/javascripts/init_kapa_widget.js
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
|
var script = document.createElement("script");
|
||||||
|
script.src = "https://widget.kapa.ai/kapa-widget.bundle.js";
|
||||||
|
script.setAttribute("data-website-id", "b5973bb1-476b-451e-8cf4-98de86745a10");
|
||||||
|
script.setAttribute("data-project-name", "Invoke.AI");
|
||||||
|
script.setAttribute("data-project-color", "#11213C");
|
||||||
|
script.setAttribute("data-project-logo", "https://avatars.githubusercontent.com/u/113954515?s=280&v=4");
|
||||||
|
script.async = true;
|
||||||
|
document.head.appendChild(script);
|
||||||
|
});
|
||||||
@@ -6,10 +6,17 @@ If you're not familiar with Diffusion, take a look at our [Diffusion Overview.](
|
|||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
|
### Workflow Library
|
||||||
|
The Workflow Library enables you to save workflows to the Invoke database, allowing you to easily creating, modify and share workflows as needed.
|
||||||
|
|
||||||
|
A curated set of workflows are provided by default - these are designed to help explain important nodes' usage in the Workflow Editor.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
### Linear View
|
### Linear View
|
||||||
The Workflow Editor allows you to create a UI for your workflow, to make it easier to iterate on your generations.
|
The Workflow Editor allows you to create a UI for your workflow, to make it easier to iterate on your generations.
|
||||||
|
|
||||||
To add an input to the Linear UI, right click on the input label and select "Add to Linear View".
|
To add an input to the Linear UI, right click on the **input label** and select "Add to Linear View".
|
||||||
|
|
||||||
The Linear UI View will also be part of the saved workflow, allowing you share workflows and enable other to use them, regardless of complexity.
|
The Linear UI View will also be part of the saved workflow, allowing you share workflows and enable other to use them, regardless of complexity.
|
||||||
|
|
||||||
@@ -30,7 +37,7 @@ Any node or input field can be renamed in the workflow editor. If the input fiel
|
|||||||
Nodes have a "Use Cache" option in their footer. This allows for performance improvements by using the previously cached values during the workflow processing.
|
Nodes have a "Use Cache" option in their footer. This allows for performance improvements by using the previously cached values during the workflow processing.
|
||||||
|
|
||||||
|
|
||||||
## Important Concepts
|
## Important Nodes & Concepts
|
||||||
|
|
||||||
There are several node grouping concepts that can be examined with a narrow focus. These (and other) groupings can be pieced together to make up functional graph setups, and are important to understanding how groups of nodes work together as part of a whole. Note that the screenshots below aren't examples of complete functioning node graphs (see Examples).
|
There are several node grouping concepts that can be examined with a narrow focus. These (and other) groupings can be pieced together to make up functional graph setups, and are important to understanding how groups of nodes work together as part of a whole. Note that the screenshots below aren't examples of complete functioning node graphs (see Examples).
|
||||||
|
|
||||||
@@ -56,7 +63,7 @@ The ImageToLatents node takes in a pixel image and a VAE and outputs a latents.
|
|||||||
|
|
||||||
It is common to want to use both the same seed (for continuity) and random seeds (for variety). To define a seed, simply enter it into the 'Seed' field on a noise node. Conversely, the RandomInt node generates a random integer between 'Low' and 'High', and can be used as input to the 'Seed' edge point on a noise node to randomize your seed.
|
It is common to want to use both the same seed (for continuity) and random seeds (for variety). To define a seed, simply enter it into the 'Seed' field on a noise node. Conversely, the RandomInt node generates a random integer between 'Low' and 'High', and can be used as input to the 'Seed' edge point on a noise node to randomize your seed.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
### ControlNet
|
### ControlNet
|
||||||
|
|
||||||
|
|||||||
@@ -8,28 +8,45 @@ To use a node, add the node to the `nodes` folder found in your InvokeAI install
|
|||||||
|
|
||||||
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.
|
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.
|
If you'd prefer, you can also just download the whole node folder 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
|
||||||
|
+ [Adapters-Linked](#adapters-linked-nodes)
|
||||||
|
+ [Autostereogram](#autostereogram-nodes)
|
||||||
+ [Average Images](#average-images)
|
+ [Average Images](#average-images)
|
||||||
|
+ [Clean Image Artifacts After Cut](#clean-image-artifacts-after-cut)
|
||||||
|
+ [Close Color Mask](#close-color-mask)
|
||||||
|
+ [Clothing Mask](#clothing-mask)
|
||||||
|
+ [Contrast Limited Adaptive Histogram Equalization](#contrast-limited-adaptive-histogram-equalization)
|
||||||
+ [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)
|
||||||
+ [GPT2RandomPromptMaker](#gpt2randompromptmaker)
|
+ [GPT2RandomPromptMaker](#gpt2randompromptmaker)
|
||||||
+ [Grid to Gif](#grid-to-gif)
|
+ [Grid to Gif](#grid-to-gif)
|
||||||
+ [Halftone](#halftone)
|
+ [Halftone](#halftone)
|
||||||
+ [Ideal Size](#ideal-size)
|
+ [Hand Refiner with MeshGraphormer](#hand-refiner-with-meshgraphormer)
|
||||||
+ [Image and Mask Composition Pack](#image-and-mask-composition-pack)
|
+ [Image and Mask Composition Pack](#image-and-mask-composition-pack)
|
||||||
|
+ [Image Dominant Color](#image-dominant-color)
|
||||||
+ [Image to Character Art Image Nodes](#image-to-character-art-image-nodes)
|
+ [Image to Character Art Image Nodes](#image-to-character-art-image-nodes)
|
||||||
+ [Image Picker](#image-picker)
|
+ [Image Picker](#image-picker)
|
||||||
|
+ [Image Resize Plus](#image-resize-plus)
|
||||||
+ [Load Video Frame](#load-video-frame)
|
+ [Load Video Frame](#load-video-frame)
|
||||||
+ [Make 3D](#make-3d)
|
+ [Make 3D](#make-3d)
|
||||||
|
+ [Mask Operations](#mask-operations)
|
||||||
|
+ [Match Histogram](#match-histogram)
|
||||||
|
+ [Metadata-Linked](#metadata-linked-nodes)
|
||||||
|
+ [Negative Image](#negative-image)
|
||||||
|
+ [Nightmare Promptgen](#nightmare-promptgen)
|
||||||
+ [Oobabooga](#oobabooga)
|
+ [Oobabooga](#oobabooga)
|
||||||
+ [Prompt Tools](#prompt-tools)
|
+ [Prompt Tools](#prompt-tools)
|
||||||
|
+ [Remote Image](#remote-image)
|
||||||
|
+ [BriaAI Background Remove](#briaai-remove-background)
|
||||||
|
+ [Remove Background](#remove-background)
|
||||||
+ [Retroize](#retroize)
|
+ [Retroize](#retroize)
|
||||||
+ [Size Stepper Nodes](#size-stepper-nodes)
|
+ [Size Stepper Nodes](#size-stepper-nodes)
|
||||||
|
+ [Simple Skin Detection](#simple-skin-detection)
|
||||||
+ [Text font to Image](#text-font-to-image)
|
+ [Text font to Image](#text-font-to-image)
|
||||||
+ [Thresholding](#thresholding)
|
+ [Thresholding](#thresholding)
|
||||||
+ [Unsharp Mask](#unsharp-mask)
|
+ [Unsharp Mask](#unsharp-mask)
|
||||||
@@ -39,6 +56,30 @@ To use a community workflow, download the the `.json` node graph file and load i
|
|||||||
- [Help](#help)
|
- [Help](#help)
|
||||||
|
|
||||||
|
|
||||||
|
--------------------------------
|
||||||
|
### Adapters Linked Nodes
|
||||||
|
|
||||||
|
**Description:** A set of nodes for linked adapters (ControlNet, IP-Adaptor & T2I-Adapter). This allows multiple adapters to be chained together without using a `collect` node which means it can be used inside an `iterate` node without any collecting on every iteration issues.
|
||||||
|
|
||||||
|
- `ControlNet-Linked` - Collects ControlNet info to pass to other nodes.
|
||||||
|
- `IP-Adapter-Linked` - Collects IP-Adapter info to pass to other nodes.
|
||||||
|
- `T2I-Adapter-Linked` - Collects T2I-Adapter info to pass to other nodes.
|
||||||
|
|
||||||
|
Note: These are inherited from the core nodes so any update to the core nodes should be reflected in these.
|
||||||
|
|
||||||
|
**Node Link:** https://github.com/skunkworxdark/adapters-linked-nodes
|
||||||
|
|
||||||
|
--------------------------------
|
||||||
|
### Autostereogram Nodes
|
||||||
|
|
||||||
|
**Description:** Generate autostereogram images from a depth map. This is not a very practically useful node but more a 90s nostalgic indulgence as I used to love these images as a kid.
|
||||||
|
|
||||||
|
**Node Link:** https://github.com/skunkworxdark/autostereogram_nodes
|
||||||
|
|
||||||
|
**Example Usage:**
|
||||||
|
</br>
|
||||||
|
<img src="https://github.com/skunkworxdark/autostereogram_nodes/blob/main/images/spider.png" width="200" /> -> <img src="https://github.com/skunkworxdark/autostereogram_nodes/blob/main/images/spider-depth.png" width="200" /> -> <img src="https://github.com/skunkworxdark/autostereogram_nodes/raw/main/images/spider-dots.png" width="200" /> <img src="https://github.com/skunkworxdark/autostereogram_nodes/raw/main/images/spider-pattern.png" width="200" />
|
||||||
|
|
||||||
--------------------------------
|
--------------------------------
|
||||||
### Average Images
|
### Average Images
|
||||||
|
|
||||||
@@ -46,6 +87,46 @@ To use a community workflow, download the the `.json` node graph file and load i
|
|||||||
|
|
||||||
**Node Link:** https://github.com/JPPhoto/average-images-node
|
**Node Link:** https://github.com/JPPhoto/average-images-node
|
||||||
|
|
||||||
|
--------------------------------
|
||||||
|
### Clean Image Artifacts After Cut
|
||||||
|
|
||||||
|
Description: Removes residual artifacts after an image is separated from its background.
|
||||||
|
|
||||||
|
Node Link: https://github.com/VeyDlin/clean-artifact-after-cut-node
|
||||||
|
|
||||||
|
View:
|
||||||
|
</br><img src="https://raw.githubusercontent.com/VeyDlin/clean-artifact-after-cut-node/master/.readme/node.png" width="500" />
|
||||||
|
|
||||||
|
--------------------------------
|
||||||
|
### Close Color Mask
|
||||||
|
|
||||||
|
Description: Generates a mask for images based on a closely matching color, useful for color-based selections.
|
||||||
|
|
||||||
|
Node Link: https://github.com/VeyDlin/close-color-mask-node
|
||||||
|
|
||||||
|
View:
|
||||||
|
</br><img src="https://raw.githubusercontent.com/VeyDlin/close-color-mask-node/master/.readme/node.png" width="500" />
|
||||||
|
|
||||||
|
--------------------------------
|
||||||
|
### Clothing Mask
|
||||||
|
|
||||||
|
Description: Employs a U2NET neural network trained for the segmentation of clothing items in images.
|
||||||
|
|
||||||
|
Node Link: https://github.com/VeyDlin/clothing-mask-node
|
||||||
|
|
||||||
|
View:
|
||||||
|
</br><img src="https://raw.githubusercontent.com/VeyDlin/clothing-mask-node/master/.readme/node.png" width="500" />
|
||||||
|
|
||||||
|
--------------------------------
|
||||||
|
### Contrast Limited Adaptive Histogram Equalization
|
||||||
|
|
||||||
|
Description: Enhances local image contrast using adaptive histogram equalization with contrast limiting.
|
||||||
|
|
||||||
|
Node Link: https://github.com/VeyDlin/clahe-node
|
||||||
|
|
||||||
|
View:
|
||||||
|
</br><img src="https://raw.githubusercontent.com/VeyDlin/clahe-node/master/.readme/node.png" width="500" />
|
||||||
|
|
||||||
--------------------------------
|
--------------------------------
|
||||||
### Depth Map from Wavefront OBJ
|
### Depth Map from Wavefront OBJ
|
||||||
|
|
||||||
@@ -129,13 +210,18 @@ CMYK Halftone Output:
|
|||||||
<img src="https://github.com/invoke-ai/InvokeAI/assets/34005131/c59c578f-db8e-4d66-8c66-2851752d75ea" width="300" />
|
<img src="https://github.com/invoke-ai/InvokeAI/assets/34005131/c59c578f-db8e-4d66-8c66-2851752d75ea" width="300" />
|
||||||
|
|
||||||
--------------------------------
|
--------------------------------
|
||||||
### Ideal Size
|
|
||||||
|
|
||||||
**Description:** This node calculates an ideal image size for a first pass of a multi-pass upscaling. The aim is to avoid duplication that results from choosing a size larger than the model is capable of.
|
### Hand Refiner with MeshGraphormer
|
||||||
|
|
||||||
**Node Link:** https://github.com/JPPhoto/ideal-size-node
|
**Description**: Hand Refiner takes in your image and automatically generates a fixed depth map for the hands along with a mask of the hands region that will conveniently allow you to use them along with ControlNet to fix the wonky hands generated by Stable Diffusion
|
||||||
|
|
||||||
|
**Node Link:** https://github.com/blessedcoolant/invoke_meshgraphormer
|
||||||
|
|
||||||
|
**View**
|
||||||
|
<img src="https://raw.githubusercontent.com/blessedcoolant/invoke_meshgraphormer/main/assets/preview.jpg" />
|
||||||
|
|
||||||
--------------------------------
|
--------------------------------
|
||||||
|
|
||||||
### Image and Mask Composition Pack
|
### Image and Mask Composition Pack
|
||||||
|
|
||||||
**Description:** This is a pack of nodes for composing masks and images, including a simple text mask creator and both image and latent offset nodes. The offsets wrap around, so these can be used in conjunction with the Seamless node to progressively generate centered on different parts of the seamless tiling.
|
**Description:** This is a pack of nodes for composing masks and images, including a simple text mask creator and both image and latent offset nodes. The offsets wrap around, so these can be used in conjunction with the Seamless node to progressively generate centered on different parts of the seamless tiling.
|
||||||
@@ -162,6 +248,16 @@ This includes 15 Nodes:
|
|||||||
|
|
||||||
</br><img src="https://raw.githubusercontent.com/dwringer/composition-nodes/main/composition_pack_overview.jpg" width="500" />
|
</br><img src="https://raw.githubusercontent.com/dwringer/composition-nodes/main/composition_pack_overview.jpg" width="500" />
|
||||||
|
|
||||||
|
--------------------------------
|
||||||
|
### Image Dominant Color
|
||||||
|
|
||||||
|
Description: Identifies and extracts the dominant color from an image using k-means clustering.
|
||||||
|
|
||||||
|
Node Link: https://github.com/VeyDlin/image-dominant-color-node
|
||||||
|
|
||||||
|
View:
|
||||||
|
</br><img src="https://raw.githubusercontent.com/VeyDlin/image-dominant-color-node/master/.readme/node.png" width="500" />
|
||||||
|
|
||||||
--------------------------------
|
--------------------------------
|
||||||
### Image to Character Art Image Nodes
|
### Image to Character Art Image Nodes
|
||||||
|
|
||||||
@@ -183,6 +279,17 @@ This includes 15 Nodes:
|
|||||||
|
|
||||||
**Node Link:** https://github.com/JPPhoto/image-picker-node
|
**Node Link:** https://github.com/JPPhoto/image-picker-node
|
||||||
|
|
||||||
|
--------------------------------
|
||||||
|
### Image Resize Plus
|
||||||
|
|
||||||
|
Description: Provides various image resizing options such as fill, stretch, fit, center, and crop.
|
||||||
|
|
||||||
|
Node Link: https://github.com/VeyDlin/image-resize-plus-node
|
||||||
|
|
||||||
|
View:
|
||||||
|
</br><img src="https://raw.githubusercontent.com/VeyDlin/image-resize-plus-node/master/.readme/node.png" width="500" />
|
||||||
|
|
||||||
|
|
||||||
--------------------------------
|
--------------------------------
|
||||||
### Load Video Frame
|
### Load Video Frame
|
||||||
|
|
||||||
@@ -207,6 +314,64 @@ This includes 15 Nodes:
|
|||||||
<img src="https://gitlab.com/srcrr/shift3d/-/raw/main/example-1.png" width="300" />
|
<img src="https://gitlab.com/srcrr/shift3d/-/raw/main/example-1.png" width="300" />
|
||||||
<img src="https://gitlab.com/srcrr/shift3d/-/raw/main/example-2.png" width="300" />
|
<img src="https://gitlab.com/srcrr/shift3d/-/raw/main/example-2.png" width="300" />
|
||||||
|
|
||||||
|
--------------------------------
|
||||||
|
### Mask Operations
|
||||||
|
|
||||||
|
Description: Offers logical operations (OR, SUB, AND) for combining and manipulating image masks.
|
||||||
|
|
||||||
|
Node Link: https://github.com/VeyDlin/mask-operations-node
|
||||||
|
|
||||||
|
View:
|
||||||
|
</br><img src="https://raw.githubusercontent.com/VeyDlin/mask-operations-node/master/.readme/node.png" width="500" />
|
||||||
|
|
||||||
|
--------------------------------
|
||||||
|
### Match Histogram
|
||||||
|
|
||||||
|
**Description:** An InvokeAI node to match a histogram from one image to another. This is a bit like the `color correct` node in the main InvokeAI but this works in the YCbCr colourspace and can handle images of different sizes. Also does not require a mask input.
|
||||||
|
- Option to only transfer luminance channel.
|
||||||
|
- Option to save output as grayscale
|
||||||
|
|
||||||
|
A good use case for this node is to normalize the colors of an image that has been through the tiled scaling workflow of my XYGrid Nodes.
|
||||||
|
|
||||||
|
See full docs here: https://github.com/skunkworxdark/Prompt-tools-nodes/edit/main/README.md
|
||||||
|
|
||||||
|
**Node Link:** https://github.com/skunkworxdark/match_histogram
|
||||||
|
|
||||||
|
**Output Examples**
|
||||||
|
|
||||||
|
<img src="https://github.com/skunkworxdark/match_histogram/assets/21961335/ed12f329-a0ef-444a-9bae-129ed60d6097" width="300" />
|
||||||
|
|
||||||
|
--------------------------------
|
||||||
|
### Metadata Linked Nodes
|
||||||
|
|
||||||
|
**Description:** A set of nodes for Metadata. Collect Metadata from within an `iterate` node & extract metadata from an image.
|
||||||
|
|
||||||
|
- `Metadata Item Linked` - Allows collecting of metadata while within an iterate node with no need for a collect node or conversion to metadata node.
|
||||||
|
- `Metadata From Image` - Provides Metadata from an image.
|
||||||
|
- `Metadata To String` - Extracts a String value of a label from metadata.
|
||||||
|
- `Metadata To Integer` - Extracts an Integer value of a label from metadata.
|
||||||
|
- `Metadata To Float` - Extracts a Float value of a label from metadata.
|
||||||
|
- `Metadata To Scheduler` - Extracts a Scheduler value of a label from metadata.
|
||||||
|
|
||||||
|
**Node Link:** https://github.com/skunkworxdark/metadata-linked-nodes
|
||||||
|
|
||||||
|
--------------------------------
|
||||||
|
### Negative Image
|
||||||
|
|
||||||
|
Description: Creates a negative version of an image, effective for visual effects and mask inversion.
|
||||||
|
|
||||||
|
Node Link: https://github.com/VeyDlin/negative-image-node
|
||||||
|
|
||||||
|
View:
|
||||||
|
</br><img src="https://raw.githubusercontent.com/VeyDlin/negative-image-node/master/.readme/node.png" width="500" />
|
||||||
|
|
||||||
|
--------------------------------
|
||||||
|
### Nightmare Promptgen
|
||||||
|
|
||||||
|
**Description:** Nightmare Prompt Generator - Uses a local text generation model to create unique imaginative (but usually nightmarish) prompts for InvokeAI. By default, it allows you to choose from some gpt-neo models I finetuned on over 2500 of my own InvokeAI prompts in Compel format, but you're able to add your own, as well. Offers support for replacing any troublesome words with a random choice from list you can also define.
|
||||||
|
|
||||||
|
**Node Link:** [https://github.com/gogurtenjoyer/nightmare-promptgen](https://github.com/gogurtenjoyer/nightmare-promptgen)
|
||||||
|
|
||||||
--------------------------------
|
--------------------------------
|
||||||
### Oobabooga
|
### Oobabooga
|
||||||
|
|
||||||
@@ -236,22 +401,61 @@ This node works best with SDXL models, especially as the style can be described
|
|||||||
--------------------------------
|
--------------------------------
|
||||||
### Prompt Tools
|
### Prompt Tools
|
||||||
|
|
||||||
**Description:** A set of InvokeAI nodes that add general prompt manipulation tools. These were written to accompany the PromptsFromFile node and other prompt generation nodes.
|
**Description:** A set of InvokeAI nodes that add general prompt (string) manipulation tools. Designed to accompany the `Prompts From File` node and other prompt generation nodes.
|
||||||
|
|
||||||
|
1. `Prompt To File` - saves a prompt or collection of prompts to a file. one per line. There is an append/overwrite option.
|
||||||
|
2. `PTFields Collect` - Converts image generation fields into a Json format string that can be passed to Prompt to file.
|
||||||
|
3. `PTFields Expand` - Takes Json string and converts it to individual generation parameters. This can be fed from the Prompts to file node.
|
||||||
|
4. `Prompt Strength` - Formats prompt with strength like the weighted format of compel
|
||||||
|
5. `Prompt Strength Combine` - Combines weighted prompts for .and()/.blend()
|
||||||
|
6. `CSV To Index String` - Gets a string from a CSV by index. Includes a Random index option
|
||||||
|
|
||||||
|
The following Nodes are now included in v3.2 of Invoke and are nolonger in this set of tools.<br>
|
||||||
|
- `Prompt Join` -> `String Join`
|
||||||
|
- `Prompt Join Three` -> `String Join Three`
|
||||||
|
- `Prompt Replace` -> `String Replace`
|
||||||
|
- `Prompt Split Neg` -> `String Split Neg`
|
||||||
|
|
||||||
1. PromptJoin - Joins to prompts into one.
|
|
||||||
2. PromptReplace - performs a search and replace on a prompt. With the option of using regex.
|
|
||||||
3. PromptSplitNeg - splits a prompt into positive and negative using the old V2 method of [] for negative.
|
|
||||||
4. PromptToFile - saves a prompt or collection of prompts to a file. one per line. There is an append/overwrite option.
|
|
||||||
5. PTFieldsCollect - Converts image generation fields into a Json format string that can be passed to Prompt to file.
|
|
||||||
6. PTFieldsExpand - Takes Json string and converts it to individual generation parameters This can be fed from the Prompts to file node.
|
|
||||||
7. PromptJoinThree - Joins 3 prompt together.
|
|
||||||
8. PromptStrength - This take a string and float and outputs another string in the format of (string)strength like the weighted format of compel.
|
|
||||||
9. PromptStrengthCombine - This takes a collection of prompt strength strings and outputs a string in the .and() or .blend() format that can be fed into a proper prompt node.
|
|
||||||
|
|
||||||
See full docs here: https://github.com/skunkworxdark/Prompt-tools-nodes/edit/main/README.md
|
See full docs here: https://github.com/skunkworxdark/Prompt-tools-nodes/edit/main/README.md
|
||||||
|
|
||||||
**Node Link:** https://github.com/skunkworxdark/Prompt-tools-nodes
|
**Node Link:** https://github.com/skunkworxdark/Prompt-tools-nodes
|
||||||
|
|
||||||
|
**Workflow Examples**
|
||||||
|
|
||||||
|
<img src="https://github.com/skunkworxdark/prompt-tools/blob/main/images/CSVToIndexStringNode.png" width="300" />
|
||||||
|
|
||||||
|
--------------------------------
|
||||||
|
### Remote Image
|
||||||
|
|
||||||
|
**Description:** This is a pack of nodes to interoperate with other services, be they public websites or bespoke local servers. The pack consists of these nodes:
|
||||||
|
|
||||||
|
- *Load Remote Image* - Lets you load remote images such as a realtime webcam image, an image of the day, or dynamically created images.
|
||||||
|
- *Post Image to Remote Server* - Lets you upload an image to a remote server using an HTTP POST request, eg for storage, display or further processing.
|
||||||
|
|
||||||
|
**Node Link:** https://github.com/fieldOfView/InvokeAI-remote_image
|
||||||
|
|
||||||
|
--------------------------------
|
||||||
|
|
||||||
|
### BriaAI Remove Background
|
||||||
|
|
||||||
|
**Description**: Implements one click background removal with BriaAI's new version 1.4 model which seems to be be producing better results than any other previous background removal tool.
|
||||||
|
|
||||||
|
**Node Link:** https://github.com/blessedcoolant/invoke_bria_rmbg
|
||||||
|
|
||||||
|
**View**
|
||||||
|
<img src="https://raw.githubusercontent.com/blessedcoolant/invoke_bria_rmbg/main/assets/preview.jpg" />
|
||||||
|
|
||||||
|
--------------------------------
|
||||||
|
### Remove Background
|
||||||
|
|
||||||
|
Description: An integration of the rembg package to remove backgrounds from images using multiple U2NET models.
|
||||||
|
|
||||||
|
Node Link: https://github.com/VeyDlin/remove-background-node
|
||||||
|
|
||||||
|
View:
|
||||||
|
</br><img src="https://raw.githubusercontent.com/VeyDlin/remove-background-node/master/.readme/node.png" width="500" />
|
||||||
|
|
||||||
--------------------------------
|
--------------------------------
|
||||||
### Retroize
|
### Retroize
|
||||||
|
|
||||||
@@ -263,6 +467,17 @@ See full docs here: https://github.com/skunkworxdark/Prompt-tools-nodes/edit/mai
|
|||||||
|
|
||||||
<img src="https://github.com/Ar7ific1al/InvokeAI_nodes_retroize/assets/2306586/de8b4fa6-324c-4c2d-b36c-297600c73974" width="500" />
|
<img src="https://github.com/Ar7ific1al/InvokeAI_nodes_retroize/assets/2306586/de8b4fa6-324c-4c2d-b36c-297600c73974" width="500" />
|
||||||
|
|
||||||
|
--------------------------------
|
||||||
|
### Simple Skin Detection
|
||||||
|
|
||||||
|
Description: Detects skin in images based on predefined color thresholds.
|
||||||
|
|
||||||
|
Node Link: https://github.com/VeyDlin/simple-skin-detection-node
|
||||||
|
|
||||||
|
View:
|
||||||
|
</br><img src="https://raw.githubusercontent.com/VeyDlin/simple-skin-detection-node/master/.readme/node.png" width="500" />
|
||||||
|
|
||||||
|
|
||||||
--------------------------------
|
--------------------------------
|
||||||
### Size Stepper Nodes
|
### Size Stepper Nodes
|
||||||
|
|
||||||
@@ -327,15 +542,28 @@ Highlights/Midtones/Shadows (with LUT blur enabled):
|
|||||||
--------------------------------
|
--------------------------------
|
||||||
### XY Image to Grid and Images to Grids nodes
|
### XY Image to Grid and Images to Grids nodes
|
||||||
|
|
||||||
**Description:** Image to grid nodes and supporting tools.
|
**Description:** These nodes add the following to InvokeAI:
|
||||||
|
- Generate grids of images from multiple input images
|
||||||
|
- Create XY grid images with labels from parameters
|
||||||
|
- Split images into overlapping tiles for processing (for super-resolution workflows)
|
||||||
|
- Recombine image tiles into a single output image blending the seams
|
||||||
|
|
||||||
1. "Images To Grids" node - Takes a collection of images and creates a grid(s) of images. If there are more images than the size of a single grid then multiple grids will be created until it runs out of images.
|
The nodes include:
|
||||||
2. "XYImage To Grid" node - Converts a collection of XYImages into a labeled Grid of images. The XYImages collection has to be built using the supporting nodes. See example node setups for more details.
|
1. `Images To Grids` - Combine multiple images into a grid of images
|
||||||
|
2. `XYImage To Grid` - Take X & Y params and creates a labeled image grid.
|
||||||
|
3. `XYImage Tiles` - Super-resolution (embiggen) style tiled resizing
|
||||||
|
4. `Image Tot XYImages` - Takes an image and cuts it up into a number of columns and rows.
|
||||||
|
5. Multiple supporting nodes - Helper nodes for data wrangling and building `XYImage` collections
|
||||||
|
|
||||||
See full docs here: https://github.com/skunkworxdark/XYGrid_nodes/edit/main/README.md
|
See full docs here: https://github.com/skunkworxdark/XYGrid_nodes/edit/main/README.md
|
||||||
|
|
||||||
**Node Link:** https://github.com/skunkworxdark/XYGrid_nodes
|
**Node Link:** https://github.com/skunkworxdark/XYGrid_nodes
|
||||||
|
|
||||||
|
**Output Examples**
|
||||||
|
|
||||||
|
<img src="https://github.com/skunkworxdark/XYGrid_nodes/blob/main/images/collage.png" width="300" />
|
||||||
|
|
||||||
|
|
||||||
--------------------------------
|
--------------------------------
|
||||||
### Example Node Template
|
### Example Node Template
|
||||||
|
|
||||||
|
|||||||
@@ -1,104 +1,107 @@
|
|||||||
# List of Default Nodes
|
# List of Default Nodes
|
||||||
|
|
||||||
The table below contains a list of the default nodes shipped with InvokeAI and their descriptions.
|
The table below contains a list of the default nodes shipped with InvokeAI and
|
||||||
|
their descriptions.
|
||||||
|
|
||||||
| Node <img width=160 align="right"> | Function |
|
| Node <img width=160 align="right"> | Function |
|
||||||
|: ---------------------------------- | :--------------------------------------------------------------------------------------|
|
| :------------------------------------------------------------ | :--------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
|Add Integers | Adds two numbers|
|
| Add Integers | Adds two numbers |
|
||||||
|Boolean Primitive Collection | A collection of boolean primitive values|
|
| Boolean Primitive Collection | A collection of boolean primitive values |
|
||||||
|Boolean Primitive | A boolean primitive value|
|
| Boolean Primitive | A boolean primitive value |
|
||||||
|Canny Processor | Canny edge detection for ControlNet|
|
| Canny Processor | Canny edge detection for ControlNet |
|
||||||
|CLIP Skip | Skip layers in clip text_encoder model.|
|
| CenterPadCrop | Pad or crop an image's sides from the center by specified pixels. Positive values are outside of the image. |
|
||||||
|Collect | Collects values into a collection|
|
| CLIP Skip | Skip layers in clip text_encoder model. |
|
||||||
|Color Correct | 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.|
|
| Collect | Collects values into a collection |
|
||||||
|Color Primitive | A color primitive value|
|
| Color Correct | 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. |
|
||||||
|Compel Prompt | Parse prompt using compel package to conditioning.|
|
| Color Primitive | A color primitive value |
|
||||||
|Conditioning Primitive Collection | A collection of conditioning tensor primitive values|
|
| Compel Prompt | Parse prompt using compel package to conditioning. |
|
||||||
|Conditioning Primitive | A conditioning tensor primitive value|
|
| Conditioning Primitive Collection | A collection of conditioning tensor primitive values |
|
||||||
|Content Shuffle Processor | Applies content shuffle processing to image|
|
| Conditioning Primitive | A conditioning tensor primitive value |
|
||||||
|ControlNet | Collects ControlNet info to pass to other nodes|
|
| Content Shuffle Processor | Applies content shuffle processing to image |
|
||||||
|Denoise Latents | Denoises noisy latents to decodable images|
|
| ControlNet | Collects ControlNet info to pass to other nodes |
|
||||||
|Divide Integers | Divides two numbers|
|
| Denoise Latents | Denoises noisy latents to decodable images |
|
||||||
|Dynamic Prompt | Parses a prompt using adieyal/dynamicprompts' random or combinatorial generator|
|
| Divide Integers | Divides two numbers |
|
||||||
|[FaceMask](./detailedNodes/faceTools.md#facemask) | Generates masks for faces in an image to use with Inpainting|
|
| Dynamic Prompt | Parses a prompt using adieyal/dynamicprompts' random or combinatorial generator |
|
||||||
|[FaceIdentifier](./detailedNodes/faceTools.md#faceidentifier) | Identifies and labels faces in an image|
|
| [FaceMask](./detailedNodes/faceTools.md#facemask) | Generates masks for faces in an image to use with Inpainting |
|
||||||
|[FaceOff](./detailedNodes/faceTools.md#faceoff) | Creates a new image that is a scaled bounding box with a mask on the face for Inpainting|
|
| [FaceIdentifier](./detailedNodes/faceTools.md#faceidentifier) | Identifies and labels faces in an image |
|
||||||
|Float Math | Perform basic math operations on two floats|
|
| [FaceOff](./detailedNodes/faceTools.md#faceoff) | Creates a new image that is a scaled bounding box with a mask on the face for Inpainting |
|
||||||
|Float Primitive Collection | A collection of float primitive values|
|
| Float Math | Perform basic math operations on two floats |
|
||||||
|Float Primitive | A float primitive value|
|
| Float Primitive Collection | A collection of float primitive values |
|
||||||
|Float Range | Creates a range|
|
| Float Primitive | A float primitive value |
|
||||||
|HED (softedge) Processor | Applies HED edge detection to image|
|
| Float Range | Creates a range |
|
||||||
|Blur Image | Blurs an image|
|
| HED (softedge) Processor | Applies HED edge detection to image |
|
||||||
|Extract Image Channel | Gets a channel from an image.|
|
| Blur Image | Blurs an image |
|
||||||
|Image Primitive Collection | A collection of image primitive values|
|
| Extract Image Channel | Gets a channel from an image. |
|
||||||
|Integer Math | Perform basic math operations on two integers|
|
| Image Primitive Collection | A collection of image primitive values |
|
||||||
|Convert Image Mode | Converts an image to a different mode.|
|
| Integer Math | Perform basic math operations on two integers |
|
||||||
|Crop Image | Crops an image to a specified box. The box can be outside of the image.|
|
| Convert Image Mode | Converts an image to a different mode. |
|
||||||
|Image Hue Adjustment | Adjusts the Hue of an image.|
|
| Crop Image | Crops an image to a specified box. The box can be outside of the image. |
|
||||||
|Inverse Lerp Image | Inverse linear interpolation of all pixels of an image|
|
| Ideal Size | Calculates an ideal image size for latents for a first pass of a multi-pass upscaling to avoid duplication and other artifacts |
|
||||||
|Image Primitive | An image primitive value|
|
| Image Hue Adjustment | Adjusts the Hue of an image. |
|
||||||
|Lerp Image | Linear interpolation of all pixels of an image|
|
| Inverse Lerp Image | Inverse linear interpolation of all pixels of an image |
|
||||||
|Offset Image Channel | Add to or subtract from an image color channel by a uniform value.|
|
| Image Primitive | An image primitive value |
|
||||||
|Multiply Image Channel | Multiply or Invert an image color channel by a scalar value.|
|
| Lerp Image | Linear interpolation of all pixels of an image |
|
||||||
|Multiply Images | Multiplies two images together using `PIL.ImageChops.multiply()`.|
|
| Offset Image Channel | Add to or subtract from an image color channel by a uniform value. |
|
||||||
|Blur NSFW Image | Add blur to NSFW-flagged images|
|
| Multiply Image Channel | Multiply or Invert an image color channel by a scalar value. |
|
||||||
|Paste Image | Pastes an image into another image.|
|
| Multiply Images | Multiplies two images together using `PIL.ImageChops.multiply()`. |
|
||||||
|ImageProcessor | Base class for invocations that preprocess images for ControlNet|
|
| Blur NSFW Image | Add blur to NSFW-flagged images |
|
||||||
|Resize Image | Resizes an image to specific dimensions|
|
| Paste Image | Pastes an image into another image. |
|
||||||
|Round Float | Rounds a float to a specified number of decimal places|
|
| ImageProcessor | Base class for invocations that preprocess images for ControlNet |
|
||||||
|Float to Integer | Converts a float to an integer. Optionally rounds to an even multiple of a input number.|
|
| Resize Image | Resizes an image to specific dimensions |
|
||||||
|Scale Image | Scales an image by a factor|
|
| Round Float | Rounds a float to a specified number of decimal places |
|
||||||
|Image to Latents | Encodes an image into latents.|
|
| Float to Integer | Converts a float to an integer. Optionally rounds to an even multiple of a input number. |
|
||||||
|Add Invisible Watermark | Add an invisible watermark to an image|
|
| Scale Image | Scales an image by a factor |
|
||||||
|Solid Color Infill | Infills transparent areas of an image with a solid color|
|
| Image to Latents | Encodes an image into latents. |
|
||||||
|PatchMatch Infill | Infills transparent areas of an image using the PatchMatch algorithm|
|
| Add Invisible Watermark | Add an invisible watermark to an image |
|
||||||
|Tile Infill | Infills transparent areas of an image with tiles of the image|
|
| Solid Color Infill | Infills transparent areas of an image with a solid color |
|
||||||
|Integer Primitive Collection | A collection of integer primitive values|
|
| PatchMatch Infill | Infills transparent areas of an image using the PatchMatch algorithm |
|
||||||
|Integer Primitive | An integer primitive value|
|
| Tile Infill | Infills transparent areas of an image with tiles of the image |
|
||||||
|Iterate | Iterates over a list of items|
|
| Integer Primitive Collection | A collection of integer primitive values |
|
||||||
|Latents Primitive Collection | A collection of latents tensor primitive values|
|
| Integer Primitive | An integer primitive value |
|
||||||
|Latents Primitive | A latents tensor primitive value|
|
| Iterate | Iterates over a list of items |
|
||||||
|Latents to Image | Generates an image from latents.|
|
| Latents Primitive Collection | A collection of latents tensor primitive values |
|
||||||
|Leres (Depth) Processor | Applies leres processing to image|
|
| Latents Primitive | A latents tensor primitive value |
|
||||||
|Lineart Anime Processor | Applies line art anime processing to image|
|
| Latents to Image | Generates an image from latents. |
|
||||||
|Lineart Processor | Applies line art processing to image|
|
| Leres (Depth) Processor | Applies leres processing to image |
|
||||||
|LoRA Loader | Apply selected lora to unet and text_encoder.|
|
| Lineart Anime Processor | Applies line art anime processing to image |
|
||||||
|Main Model Loader | Loads a main model, outputting its submodels.|
|
| Lineart Processor | Applies line art processing to image |
|
||||||
|Combine Mask | Combine two masks together by multiplying them using `PIL.ImageChops.multiply()`.|
|
| LoRA Loader | Apply selected lora to unet and text_encoder. |
|
||||||
|Mask Edge | Applies an edge mask to an image|
|
| Main Model Loader | Loads a main model, outputting its submodels. |
|
||||||
|Mask from Alpha | Extracts the alpha channel of an image as a mask.|
|
| Combine Mask | Combine two masks together by multiplying them using `PIL.ImageChops.multiply()`. |
|
||||||
|Mediapipe Face Processor | Applies mediapipe face processing to image|
|
| Mask Edge | Applies an edge mask to an image |
|
||||||
|Midas (Depth) Processor | Applies Midas depth processing to image|
|
| Mask from Alpha | Extracts the alpha channel of an image as a mask. |
|
||||||
|MLSD Processor | Applies MLSD processing to image|
|
| Mediapipe Face Processor | Applies mediapipe face processing to image |
|
||||||
|Multiply Integers | Multiplies two numbers|
|
| Midas (Depth) Processor | Applies Midas depth processing to image |
|
||||||
|Noise | Generates latent noise.|
|
| MLSD Processor | Applies MLSD processing to image |
|
||||||
|Normal BAE Processor | Applies NormalBae processing to image|
|
| Multiply Integers | Multiplies two numbers |
|
||||||
|ONNX Latents to Image | Generates an image from latents.|
|
| Noise | Generates latent noise. |
|
||||||
|ONNX Prompt (Raw) | A node to process inputs and produce outputs. May use dependency injection in __init__ to receive providers.|
|
| Normal BAE Processor | Applies NormalBae processing to image |
|
||||||
|ONNX Text to Latents | Generates latents from conditionings.|
|
| ONNX Latents to Image | Generates an image from latents. |
|
||||||
|ONNX Model Loader | Loads a main model, outputting its submodels.|
|
| ONNX Prompt (Raw) | A node to process inputs and produce outputs. May use dependency injection in **init** to receive providers. |
|
||||||
|OpenCV Inpaint | Simple inpaint using opencv.|
|
| ONNX Text to Latents | Generates latents from conditionings. |
|
||||||
|Openpose Processor | Applies Openpose processing to image|
|
| ONNX Model Loader | Loads a main model, outputting its submodels. |
|
||||||
|PIDI Processor | Applies PIDI processing to image|
|
| OpenCV Inpaint | Simple inpaint using opencv. |
|
||||||
|Prompts from File | Loads prompts from a text file|
|
| DW Openpose Processor | Applies Openpose processing to image |
|
||||||
|Random Integer | Outputs a single random integer.|
|
| PIDI Processor | Applies PIDI processing to image |
|
||||||
|Random Range | Creates a collection of random numbers|
|
| Prompts from File | Loads prompts from a text file |
|
||||||
|Integer Range | Creates a range of numbers from start to stop with step|
|
| Random Integer | Outputs a single random integer. |
|
||||||
|Integer Range of Size | Creates a range from start to start + size with step|
|
| Random Range | Creates a collection of random numbers |
|
||||||
|Resize Latents | Resizes latents to explicit width/height (in pixels). Provided dimensions are floor-divided by 8.|
|
| Integer Range | Creates a range of numbers from start to stop with step |
|
||||||
|SDXL Compel Prompt | Parse prompt using compel package to conditioning.|
|
| Integer Range of Size | Creates a range from start to start + size with step |
|
||||||
|SDXL LoRA Loader | Apply selected lora to unet and text_encoder.|
|
| Resize Latents | Resizes latents to explicit width/height (in pixels). Provided dimensions are floor-divided by 8. |
|
||||||
|SDXL Main Model Loader | Loads an sdxl base model, outputting its submodels.|
|
| SDXL Compel Prompt | Parse prompt using compel package to conditioning. |
|
||||||
|SDXL Refiner Compel Prompt | Parse prompt using compel package to conditioning.|
|
| SDXL LoRA Loader | Apply selected lora to unet and text_encoder. |
|
||||||
|SDXL Refiner Model Loader | Loads an sdxl refiner model, outputting its submodels.|
|
| SDXL Main Model Loader | Loads an sdxl base model, outputting its submodels. |
|
||||||
|Scale Latents | Scales latents by a given factor.|
|
| SDXL Refiner Compel Prompt | Parse prompt using compel package to conditioning. |
|
||||||
|Segment Anything Processor | Applies segment anything processing to image|
|
| SDXL Refiner Model Loader | Loads an sdxl refiner model, outputting its submodels. |
|
||||||
|Show Image | Displays a provided image, and passes it forward in the pipeline.|
|
| Scale Latents | Scales latents by a given factor. |
|
||||||
|Step Param Easing | Experimental per-step parameter easing for denoising steps|
|
| Segment Anything Processor | Applies segment anything processing to image |
|
||||||
|String Primitive Collection | A collection of string primitive values|
|
| Show Image | Displays a provided image, and passes it forward in the pipeline. |
|
||||||
|String Primitive | A string primitive value|
|
| Step Param Easing | Experimental per-step parameter easing for denoising steps |
|
||||||
|Subtract Integers | Subtracts two numbers|
|
| String Primitive Collection | A collection of string primitive values |
|
||||||
|Tile Resample Processor | Tile resampler processor|
|
| String Primitive | A string primitive value |
|
||||||
|Upscale (RealESRGAN) | Upscales an image using RealESRGAN.|
|
| Subtract Integers | Subtracts two numbers |
|
||||||
|VAE Loader | Loads a VAE model, outputting a VaeLoaderOutput|
|
| Tile Resample Processor | Tile resampler processor |
|
||||||
|Zoe (Depth) Processor | Applies Zoe depth processing to image|
|
| Upscale (RealESRGAN) | Upscales an image using RealESRGAN. |
|
||||||
|
| VAE Loader | Loads a VAE model, outputting a VaeLoaderOutput |
|
||||||
|
| Zoe (Depth) Processor | Applies Zoe depth processing to image |
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
# Example Workflows
|
# Example Workflows
|
||||||
|
|
||||||
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! These can also be found in the Workflow Library, located in the Workflow Editor of Invoke.
|
||||||
|
|
||||||
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!
|
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/docs/main/docs/workflows/SDXL_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 with Refiner](https://github.com/invoke-ai/InvokeAI/blob/docs/main/docs/workflows/SDXL_w_Refiner_Text_to_Image.json)
|
* [SDXL Text to Image with Refiner](https://github.com/invoke-ai/InvokeAI/blob/main/docs/workflows/SDXL_w_Refiner_Text_to_Image.json)
|
||||||
* [Multi ControlNet (Canny & Depth)](https://github.com/invoke-ai/InvokeAI/blob/docs/main/docs/workflows/Multi_ControlNet_Canny_and_Depth.json)
|
* [Multi ControlNet (Canny & Depth)](https://github.com/invoke-ai/InvokeAI/blob/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)
|
* [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)
|
* [Prompt From File](https://github.com/invoke-ai/InvokeAI/blob/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)
|
* [Face Detailer with IP-Adapter & ControlNet](https://github.com/invoke-ai/InvokeAI/blob/main/docs/workflows/Face_Detailer_with_IP-Adapter_and_Canny.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)
|
* [QR Code Monster](https://github.com/invoke-ai/InvokeAI/blob/main/docs/workflows/QR_Code_Monster.json)
|
||||||
|
|||||||
@@ -13,46 +13,69 @@ We thank them for all of their time and hard work.
|
|||||||
|
|
||||||
- [Lincoln D. Stein](mailto:lincoln.stein@gmail.com)
|
- [Lincoln D. Stein](mailto:lincoln.stein@gmail.com)
|
||||||
|
|
||||||
## **Current core team**
|
## **Current Core Team**
|
||||||
|
|
||||||
* @lstein (Lincoln Stein) - Co-maintainer
|
* @lstein (Lincoln Stein) - Co-maintainer
|
||||||
* @blessedcoolant - Co-maintainer
|
* @blessedcoolant - Co-maintainer
|
||||||
* @hipsterusername (Kent Keirsey) - Co-maintainer, CEO, Positive Vibes
|
* @hipsterusername (Kent Keirsey) - Co-maintainer, CEO, Positive Vibes
|
||||||
* @psychedelicious (Spencer Mabrito) - Web Team Leader
|
* @psychedelicious (Spencer Mabrito) - Web Team Leader
|
||||||
* @Kyle0654 (Kyle Schouviller) - Node Architect and General Backend Wizard
|
* @chainchompa (Jennifer Player) - Web Development & Chain-Chomping
|
||||||
* @damian0815 - Attention Systems and Compel Maintainer
|
* @josh is toast (Josh Corbett) - Web Development
|
||||||
* @ebr (Eugene Brodsky) - Cloud/DevOps/Sofware engineer; your friendly neighbourhood cluster-autoscaler
|
|
||||||
* @genomancer (Gregg Helt) - Controlnet support
|
|
||||||
* @StAlKeR7779 (Sergey Borisov) - Torch stack, ONNX, model management, optimization
|
|
||||||
* @cheerio (Mary Rogers) - Lead Engineer & Web App Development
|
* @cheerio (Mary Rogers) - Lead Engineer & Web App Development
|
||||||
|
* @ebr (Eugene Brodsky) - Cloud/DevOps/Sofware engineer; your friendly neighbourhood cluster-autoscaler
|
||||||
|
* @sunija - Standalone version
|
||||||
|
* @genomancer (Gregg Helt) - Controlnet support
|
||||||
* @brandon (Brandon Rising) - Platform, Infrastructure, Backend Systems
|
* @brandon (Brandon Rising) - Platform, Infrastructure, Backend Systems
|
||||||
* @ryanjdick (Ryan Dick) - Machine Learning & Training
|
* @ryanjdick (Ryan Dick) - Machine Learning & Training
|
||||||
* @millu (Millun Atluri) - Community Manager, Documentation, Node-wrangler
|
* @JPPhoto - Core image generation nodes
|
||||||
* @chainchompa (Jennifer Player) - Web Development & Chain-Chomping
|
* @dunkeroni - Image generation backend
|
||||||
|
* @SkunkWorxDark - Image generation backend
|
||||||
* @keturn (Kevin Turner) - Diffusers
|
* @keturn (Kevin Turner) - Diffusers
|
||||||
|
* @millu (Millun Atluri) - Community Wizard, Documentation, Node-wrangler,
|
||||||
|
* @glimmerleaf (Devon Hopkins) - Community Wizard
|
||||||
* @gogurt enjoyer - Discord moderator and end user support
|
* @gogurt enjoyer - Discord moderator and end user support
|
||||||
* @whosawhatsis - Discord moderator and end user support
|
* @whosawhatsis - Discord moderator and end user support
|
||||||
* @dwinrger - Discord moderator and end user support
|
* @dwinrger - Discord moderator and end user support
|
||||||
* @526christian - Discord moderator and end user support
|
* @526christian - Discord moderator and end user support
|
||||||
|
* @harvester62 - Discord moderator and end user support
|
||||||
|
|
||||||
|
|
||||||
|
## **Honored Team Alumni**
|
||||||
|
|
||||||
|
* @StAlKeR7779 (Sergey Borisov) - Torch stack, ONNX, model management, optimization
|
||||||
|
* @damian0815 - Attention Systems and Compel Maintainer
|
||||||
|
* @netsvetaev (Artur) - Localization support
|
||||||
|
* @Kyle0654 (Kyle Schouviller) - Node Architect and General Backend Wizard
|
||||||
|
* @tildebyte - Installation and configuration
|
||||||
|
* @mauwii (Matthias Wilde) - Installation, release, continuous integration
|
||||||
|
|
||||||
|
|
||||||
## **Full List of Contributors by Commit Name**
|
## **Full List of Contributors by Commit Name**
|
||||||
|
|
||||||
|
- 이승석
|
||||||
- AbdBarho
|
- AbdBarho
|
||||||
- ablattmann
|
- ablattmann
|
||||||
- AdamOStark
|
- AdamOStark
|
||||||
- Adam Rice
|
- Adam Rice
|
||||||
- Airton Silva
|
- Airton Silva
|
||||||
|
- Aldo Hoeben
|
||||||
- Alexander Eichhorn
|
- Alexander Eichhorn
|
||||||
- Alexandre D. Roberge
|
- Alexandre D. Roberge
|
||||||
|
- Alexandre Macabies
|
||||||
|
- Alfie John
|
||||||
- Andreas Rozek
|
- Andreas Rozek
|
||||||
- Andre LaBranche
|
- Andre LaBranche
|
||||||
- Andy Bearman
|
- Andy Bearman
|
||||||
- Andy Luhrs
|
- Andy Luhrs
|
||||||
- Andy Pilate
|
- Andy Pilate
|
||||||
|
- Anonymous
|
||||||
|
- Anthony Monthe
|
||||||
- Any-Winter-4079
|
- Any-Winter-4079
|
||||||
- apolinario
|
- apolinario
|
||||||
|
- Ar7ific1al
|
||||||
- ArDiouscuros
|
- ArDiouscuros
|
||||||
- Armando C. Santisbon
|
- Armando C. Santisbon
|
||||||
|
- Arnold Cordewiner
|
||||||
- Arthur Holstvoogd
|
- Arthur Holstvoogd
|
||||||
- artmen1516
|
- artmen1516
|
||||||
- Artur
|
- Artur
|
||||||
@@ -64,13 +87,16 @@ We thank them for all of their time and hard work.
|
|||||||
- blhook
|
- blhook
|
||||||
- BlueAmulet
|
- BlueAmulet
|
||||||
- Bouncyknighter
|
- Bouncyknighter
|
||||||
|
- Brandon
|
||||||
- Brandon Rising
|
- Brandon Rising
|
||||||
- Brent Ozar
|
- Brent Ozar
|
||||||
- Brian Racer
|
- Brian Racer
|
||||||
- bsilvereagle
|
- bsilvereagle
|
||||||
- c67e708d
|
- c67e708d
|
||||||
|
- camenduru
|
||||||
- CapableWeb
|
- CapableWeb
|
||||||
- Carson Katri
|
- Carson Katri
|
||||||
|
- chainchompa
|
||||||
- Chloe
|
- Chloe
|
||||||
- Chris Dawson
|
- Chris Dawson
|
||||||
- Chris Hayes
|
- Chris Hayes
|
||||||
@@ -86,30 +112,45 @@ We thank them for all of their time and hard work.
|
|||||||
- cpacker
|
- cpacker
|
||||||
- Cragin Godley
|
- Cragin Godley
|
||||||
- creachec
|
- creachec
|
||||||
|
- CrypticWit
|
||||||
|
- d8ahazard
|
||||||
|
- damian
|
||||||
|
- damian0815
|
||||||
|
- Damian at mba
|
||||||
- Damian Stewart
|
- Damian Stewart
|
||||||
- Daniel Manzke
|
- Daniel Manzke
|
||||||
- Danny Beer
|
- Danny Beer
|
||||||
- Dan Sully
|
- Dan Sully
|
||||||
|
- Darren Ringer
|
||||||
- David Burnett
|
- David Burnett
|
||||||
- David Ford
|
- David Ford
|
||||||
- David Regla
|
- David Regla
|
||||||
|
- David Sisco
|
||||||
- David Wager
|
- David Wager
|
||||||
- Daya Adianto
|
- Daya Adianto
|
||||||
- db3000
|
- db3000
|
||||||
|
- DekitaRPG
|
||||||
- Denis Olshin
|
- Denis Olshin
|
||||||
- Dennis
|
- Dennis
|
||||||
|
- dependabot[bot]
|
||||||
|
- Dmitry Parnas
|
||||||
|
- Dobrynia100
|
||||||
- Dominic Letz
|
- Dominic Letz
|
||||||
- DrGunnarMallon
|
- DrGunnarMallon
|
||||||
|
- Drun555
|
||||||
|
- dunkeroni
|
||||||
- Edward Johan
|
- Edward Johan
|
||||||
- elliotsayes
|
- elliotsayes
|
||||||
- Elrik
|
- Elrik
|
||||||
- ElrikUnderlake
|
- ElrikUnderlake
|
||||||
- Eric Khun
|
- Eric Khun
|
||||||
- Eric Wolf
|
- Eric Wolf
|
||||||
|
- Eugene
|
||||||
- Eugene Brodsky
|
- Eugene Brodsky
|
||||||
- ExperimentalCyborg
|
- ExperimentalCyborg
|
||||||
- Fabian Bahl
|
- Fabian Bahl
|
||||||
- Fabio 'MrWHO' Torchetti
|
- Fabio 'MrWHO' Torchetti
|
||||||
|
- Fattire
|
||||||
- fattire
|
- fattire
|
||||||
- Felipe Nogueira
|
- Felipe Nogueira
|
||||||
- Félix Sanz
|
- Félix Sanz
|
||||||
@@ -118,8 +159,12 @@ We thank them for all of their time and hard work.
|
|||||||
- gabrielrotbart
|
- gabrielrotbart
|
||||||
- gallegonovato
|
- gallegonovato
|
||||||
- Gérald LONLAS
|
- Gérald LONLAS
|
||||||
|
- Gille
|
||||||
- GitHub Actions Bot
|
- GitHub Actions Bot
|
||||||
|
- glibesyck
|
||||||
- gogurtenjoyer
|
- gogurtenjoyer
|
||||||
|
- Gohsuke Shimada
|
||||||
|
- greatwolf
|
||||||
- greentext2
|
- greentext2
|
||||||
- Gregg Helt
|
- Gregg Helt
|
||||||
- H4rk
|
- H4rk
|
||||||
@@ -131,6 +176,7 @@ We thank them for all of their time and hard work.
|
|||||||
- Hosted Weblate
|
- Hosted Weblate
|
||||||
- Iman Karim
|
- Iman Karim
|
||||||
- ismail ihsan bülbül
|
- ismail ihsan bülbül
|
||||||
|
- ItzAttila
|
||||||
- Ivan Efimov
|
- Ivan Efimov
|
||||||
- jakehl
|
- jakehl
|
||||||
- Jakub Kolčář
|
- Jakub Kolčář
|
||||||
@@ -141,6 +187,7 @@ We thank them for all of their time and hard work.
|
|||||||
- Jason Toffaletti
|
- Jason Toffaletti
|
||||||
- Jaulustus
|
- Jaulustus
|
||||||
- Jeff Mahoney
|
- Jeff Mahoney
|
||||||
|
- Jennifer Player
|
||||||
- jeremy
|
- jeremy
|
||||||
- Jeremy Clark
|
- Jeremy Clark
|
||||||
- JigenD
|
- JigenD
|
||||||
@@ -148,19 +195,26 @@ We thank them for all of their time and hard work.
|
|||||||
- Johan Roxendal
|
- Johan Roxendal
|
||||||
- Johnathon Selstad
|
- Johnathon Selstad
|
||||||
- Jonathan
|
- Jonathan
|
||||||
|
- Jordan Hewitt
|
||||||
- Joseph Dries III
|
- Joseph Dries III
|
||||||
|
- Josh Corbett
|
||||||
- JPPhoto
|
- JPPhoto
|
||||||
- jspraul
|
- jspraul
|
||||||
|
- junzi
|
||||||
- Justin Wong
|
- Justin Wong
|
||||||
- Juuso V
|
- Juuso V
|
||||||
- Kaspar Emanuel
|
- Kaspar Emanuel
|
||||||
- Katsuyuki-Karasawa
|
- Katsuyuki-Karasawa
|
||||||
|
- Keerigan45
|
||||||
- Kent Keirsey
|
- Kent Keirsey
|
||||||
|
- Kevin Brack
|
||||||
- Kevin Coakley
|
- Kevin Coakley
|
||||||
- Kevin Gibbons
|
- Kevin Gibbons
|
||||||
- Kevin Schaul
|
- Kevin Schaul
|
||||||
- Kevin Turner
|
- Kevin Turner
|
||||||
|
- Kieran Klaassen
|
||||||
- krummrey
|
- krummrey
|
||||||
|
- Kyle
|
||||||
- Kyle Lacy
|
- Kyle Lacy
|
||||||
- Kyle Schouviller
|
- Kyle Schouviller
|
||||||
- Lawrence Norton
|
- Lawrence Norton
|
||||||
@@ -171,10 +225,15 @@ We thank them for all of their time and hard work.
|
|||||||
- Lynne Whitehorn
|
- Lynne Whitehorn
|
||||||
- majick
|
- majick
|
||||||
- Marco Labarile
|
- Marco Labarile
|
||||||
|
- Marta Nahorniuk
|
||||||
- Martin Kristiansen
|
- Martin Kristiansen
|
||||||
|
- Mary Hipp
|
||||||
|
- maryhipp
|
||||||
- Mary Hipp Rogers
|
- Mary Hipp Rogers
|
||||||
|
- mastercaster
|
||||||
- mastercaster9000
|
- mastercaster9000
|
||||||
- Matthias Wild
|
- Matthias Wild
|
||||||
|
- mauwii
|
||||||
- michaelk71
|
- michaelk71
|
||||||
- mickr777
|
- mickr777
|
||||||
- Mihai
|
- Mihai
|
||||||
@@ -182,11 +241,15 @@ We thank them for all of their time and hard work.
|
|||||||
- Mikhail Tishin
|
- Mikhail Tishin
|
||||||
- Millun Atluri
|
- Millun Atluri
|
||||||
- Minjune Song
|
- Minjune Song
|
||||||
|
- Mitchell Allain
|
||||||
- mitien
|
- mitien
|
||||||
- mofuzz
|
- mofuzz
|
||||||
- Muhammad Usama
|
- Muhammad Usama
|
||||||
- Name
|
- Name
|
||||||
- _nderscore
|
- _nderscore
|
||||||
|
- Neil Wang
|
||||||
|
- nekowaiz
|
||||||
|
- nemuruibai
|
||||||
- Netzer R
|
- Netzer R
|
||||||
- Nicholas Koh
|
- Nicholas Koh
|
||||||
- Nicholas Körfer
|
- Nicholas Körfer
|
||||||
@@ -197,9 +260,11 @@ We thank them for all of their time and hard work.
|
|||||||
- ofirkris
|
- ofirkris
|
||||||
- Olivier Louvignes
|
- Olivier Louvignes
|
||||||
- owenvincent
|
- owenvincent
|
||||||
|
- pand4z31
|
||||||
- Patrick Esser
|
- Patrick Esser
|
||||||
- Patrick Tien
|
- Patrick Tien
|
||||||
- Patrick von Platen
|
- Patrick von Platen
|
||||||
|
- Paul Curry
|
||||||
- Paul Sajna
|
- Paul Sajna
|
||||||
- pejotr
|
- pejotr
|
||||||
- Peter Baylies
|
- Peter Baylies
|
||||||
@@ -207,6 +272,7 @@ We thank them for all of their time and hard work.
|
|||||||
- plucked
|
- plucked
|
||||||
- prixt
|
- prixt
|
||||||
- psychedelicious
|
- psychedelicious
|
||||||
|
- psychedelicious@windows
|
||||||
- Rainer Bernhardt
|
- Rainer Bernhardt
|
||||||
- Riccardo Giovanetti
|
- Riccardo Giovanetti
|
||||||
- Rich Jones
|
- Rich Jones
|
||||||
@@ -215,16 +281,22 @@ We thank them for all of their time and hard work.
|
|||||||
- Robert Bolender
|
- Robert Bolender
|
||||||
- Robin Rombach
|
- Robin Rombach
|
||||||
- Rohan Barar
|
- Rohan Barar
|
||||||
|
- Rohinish
|
||||||
- rpagliuca
|
- rpagliuca
|
||||||
- rromb
|
- rromb
|
||||||
- Rupesh Sreeraman
|
- Rupesh Sreeraman
|
||||||
|
- Ryan
|
||||||
- Ryan Cao
|
- Ryan Cao
|
||||||
|
- Ryan Dick
|
||||||
- Saifeddine
|
- Saifeddine
|
||||||
- Saifeddine ALOUI
|
- Saifeddine ALOUI
|
||||||
|
- Sam
|
||||||
- SammCheese
|
- SammCheese
|
||||||
|
- Sam McLeod
|
||||||
- Sammy
|
- Sammy
|
||||||
- sammyf
|
- sammyf
|
||||||
- Samuel Husso
|
- Samuel Husso
|
||||||
|
- Saurav Maheshkar
|
||||||
- Scott Lahteine
|
- Scott Lahteine
|
||||||
- Sean McLellan
|
- Sean McLellan
|
||||||
- Sebastian Aigner
|
- Sebastian Aigner
|
||||||
@@ -232,16 +304,21 @@ We thank them for all of their time and hard work.
|
|||||||
- Sergey Krashevich
|
- Sergey Krashevich
|
||||||
- Shapor Naghibzadeh
|
- Shapor Naghibzadeh
|
||||||
- Shawn Zhong
|
- Shawn Zhong
|
||||||
|
- Simona Liliac
|
||||||
- Simon Vans-Colina
|
- Simon Vans-Colina
|
||||||
- skunkworxdark
|
- skunkworxdark
|
||||||
- slashtechno
|
- slashtechno
|
||||||
|
- SoheilRezaei
|
||||||
|
- Song, Pengcheng
|
||||||
- spezialspezial
|
- spezialspezial
|
||||||
- ssantos
|
- ssantos
|
||||||
- StAlKeR7779
|
- StAlKeR7779
|
||||||
|
- Stefan Tobler
|
||||||
- Stephan Koglin-Fischer
|
- Stephan Koglin-Fischer
|
||||||
- SteveCaruso
|
- SteveCaruso
|
||||||
- Steve Martinelli
|
- Steve Martinelli
|
||||||
- Steven Frank
|
- Steven Frank
|
||||||
|
- Surisen
|
||||||
- System X - Files
|
- System X - Files
|
||||||
- Taylor Kems
|
- Taylor Kems
|
||||||
- techicode
|
- techicode
|
||||||
@@ -260,26 +337,34 @@ We thank them for all of their time and hard work.
|
|||||||
- tyler
|
- tyler
|
||||||
- unknown
|
- unknown
|
||||||
- user1
|
- user1
|
||||||
|
- vedant-3010
|
||||||
- Vedant Madane
|
- Vedant Madane
|
||||||
- veprogames
|
- veprogames
|
||||||
- wa.code
|
- wa.code
|
||||||
- wfng92
|
- wfng92
|
||||||
|
- whjms
|
||||||
- whosawhatsis
|
- whosawhatsis
|
||||||
- Will
|
- Will
|
||||||
- William Becher
|
- William Becher
|
||||||
- William Chong
|
- William Chong
|
||||||
|
- Wilson E. Alvarez
|
||||||
|
- woweenie
|
||||||
|
- Wubbbi
|
||||||
- xra
|
- xra
|
||||||
- Yeung Yiu Hung
|
- Yeung Yiu Hung
|
||||||
- ymgenesis
|
- ymgenesis
|
||||||
- Yorzaren
|
- Yorzaren
|
||||||
- Yosuke Shinya
|
- Yosuke Shinya
|
||||||
- yun saki
|
- yun saki
|
||||||
|
- ZachNagengast
|
||||||
- Zadagu
|
- Zadagu
|
||||||
- zeptofine
|
- zeptofine
|
||||||
|
- Zerdoumi
|
||||||
|
- Васянатор
|
||||||
- 冯不游
|
- 冯不游
|
||||||
- 唐澤 克幸
|
- 唐澤 克幸
|
||||||
|
|
||||||
## **Original CompVis Authors**
|
## **Original CompVis (Stable Diffusion) Authors**
|
||||||
|
|
||||||
- [Robin Rombach](https://github.com/rromb)
|
- [Robin Rombach](https://github.com/rromb)
|
||||||
- [Patrick von Platen](https://github.com/patrickvonplaten)
|
- [Patrick von Platen](https://github.com/patrickvonplaten)
|
||||||
|
|||||||
5
docs/stylesheets/extra.css
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
:root {
|
||||||
|
--md-primary-fg-color: #35A4DB;
|
||||||
|
--md-primary-fg-color--light: #35A4DB;
|
||||||
|
--md-primary-fg-color--dark: #35A4DB;
|
||||||
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"name": "Text to Image",
|
"name": "Text to Image - SD1.5",
|
||||||
"author": "InvokeAI",
|
"author": "InvokeAI",
|
||||||
"description": "Sample text to image workflow for Stable Diffusion 1.5/2",
|
"description": "Sample text to image workflow for Stable Diffusion 1.5/2",
|
||||||
"version": "1.0.1",
|
"version": "1.1.0",
|
||||||
"contact": "invoke@invoke.ai",
|
"contact": "invoke@invoke.ai",
|
||||||
"tags": "text2image, SD1.5, SD2, default",
|
"tags": "text2image, SD1.5, SD2, default",
|
||||||
"notes": "",
|
"notes": "",
|
||||||
@@ -18,10 +18,19 @@
|
|||||||
{
|
{
|
||||||
"nodeId": "93dc02a4-d05b-48ed-b99c-c9b616af3402",
|
"nodeId": "93dc02a4-d05b-48ed-b99c-c9b616af3402",
|
||||||
"fieldName": "prompt"
|
"fieldName": "prompt"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"nodeId": "55705012-79b9-4aac-9f26-c0b10309785b",
|
||||||
|
"fieldName": "width"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"nodeId": "55705012-79b9-4aac-9f26-c0b10309785b",
|
||||||
|
"fieldName": "height"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"meta": {
|
"meta": {
|
||||||
"version": "1.0.0"
|
"category": "default",
|
||||||
|
"version": "2.0.0"
|
||||||
},
|
},
|
||||||
"nodes": [
|
"nodes": [
|
||||||
{
|
{
|
||||||
@@ -30,44 +39,56 @@
|
|||||||
"data": {
|
"data": {
|
||||||
"id": "93dc02a4-d05b-48ed-b99c-c9b616af3402",
|
"id": "93dc02a4-d05b-48ed-b99c-c9b616af3402",
|
||||||
"type": "compel",
|
"type": "compel",
|
||||||
|
"label": "Negative Compel Prompt",
|
||||||
|
"isOpen": true,
|
||||||
|
"notes": "",
|
||||||
|
"isIntermediate": true,
|
||||||
|
"useCache": true,
|
||||||
|
"version": "1.0.0",
|
||||||
|
"nodePack": "invokeai",
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"prompt": {
|
"prompt": {
|
||||||
"id": "7739aff6-26cb-4016-8897-5a1fb2305e4e",
|
"id": "7739aff6-26cb-4016-8897-5a1fb2305e4e",
|
||||||
"name": "prompt",
|
"name": "prompt",
|
||||||
"type": "string",
|
|
||||||
"fieldKind": "input",
|
"fieldKind": "input",
|
||||||
"label": "Negative Prompt",
|
"label": "Negative Prompt",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "StringField"
|
||||||
|
},
|
||||||
"value": ""
|
"value": ""
|
||||||
},
|
},
|
||||||
"clip": {
|
"clip": {
|
||||||
"id": "48d23dce-a6ae-472a-9f8c-22a714ea5ce0",
|
"id": "48d23dce-a6ae-472a-9f8c-22a714ea5ce0",
|
||||||
"name": "clip",
|
"name": "clip",
|
||||||
"type": "ClipField",
|
|
||||||
"fieldKind": "input",
|
"fieldKind": "input",
|
||||||
"label": ""
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "ClipField"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"outputs": {
|
"outputs": {
|
||||||
"conditioning": {
|
"conditioning": {
|
||||||
"id": "37cf3a9d-f6b7-4b64-8ff6-2558c5ecc447",
|
"id": "37cf3a9d-f6b7-4b64-8ff6-2558c5ecc447",
|
||||||
"name": "conditioning",
|
"name": "conditioning",
|
||||||
"type": "ConditioningField",
|
"fieldKind": "output",
|
||||||
"fieldKind": "output"
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "ConditioningField"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
"label": "Negative Compel Prompt",
|
|
||||||
"isOpen": true,
|
|
||||||
"notes": "",
|
|
||||||
"embedWorkflow": false,
|
|
||||||
"isIntermediate": true,
|
|
||||||
"useCache": true,
|
|
||||||
"version": "1.0.0"
|
|
||||||
},
|
},
|
||||||
"width": 320,
|
"width": 320,
|
||||||
"height": 261,
|
"height": 259,
|
||||||
"position": {
|
"position": {
|
||||||
"x": 995.7263915923627,
|
"x": 1000,
|
||||||
"y": 239.67783573351227
|
"y": 350
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -76,37 +97,60 @@
|
|||||||
"data": {
|
"data": {
|
||||||
"id": "55705012-79b9-4aac-9f26-c0b10309785b",
|
"id": "55705012-79b9-4aac-9f26-c0b10309785b",
|
||||||
"type": "noise",
|
"type": "noise",
|
||||||
|
"label": "",
|
||||||
|
"isOpen": true,
|
||||||
|
"notes": "",
|
||||||
|
"isIntermediate": true,
|
||||||
|
"useCache": true,
|
||||||
|
"version": "1.0.1",
|
||||||
|
"nodePack": "invokeai",
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"seed": {
|
"seed": {
|
||||||
"id": "6431737c-918a-425d-a3b4-5d57e2f35d4d",
|
"id": "6431737c-918a-425d-a3b4-5d57e2f35d4d",
|
||||||
"name": "seed",
|
"name": "seed",
|
||||||
"type": "integer",
|
|
||||||
"fieldKind": "input",
|
"fieldKind": "input",
|
||||||
"label": "",
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "IntegerField"
|
||||||
|
},
|
||||||
"value": 0
|
"value": 0
|
||||||
},
|
},
|
||||||
"width": {
|
"width": {
|
||||||
"id": "38fc5b66-fe6e-47c8-bba9-daf58e454ed7",
|
"id": "38fc5b66-fe6e-47c8-bba9-daf58e454ed7",
|
||||||
"name": "width",
|
"name": "width",
|
||||||
"type": "integer",
|
|
||||||
"fieldKind": "input",
|
"fieldKind": "input",
|
||||||
"label": "",
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "IntegerField"
|
||||||
|
},
|
||||||
"value": 512
|
"value": 512
|
||||||
},
|
},
|
||||||
"height": {
|
"height": {
|
||||||
"id": "16298330-e2bf-4872-a514-d6923df53cbb",
|
"id": "16298330-e2bf-4872-a514-d6923df53cbb",
|
||||||
"name": "height",
|
"name": "height",
|
||||||
"type": "integer",
|
|
||||||
"fieldKind": "input",
|
"fieldKind": "input",
|
||||||
"label": "",
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "IntegerField"
|
||||||
|
},
|
||||||
"value": 512
|
"value": 512
|
||||||
},
|
},
|
||||||
"use_cpu": {
|
"use_cpu": {
|
||||||
"id": "c7c436d3-7a7a-4e76-91e4-c6deb271623c",
|
"id": "c7c436d3-7a7a-4e76-91e4-c6deb271623c",
|
||||||
"name": "use_cpu",
|
"name": "use_cpu",
|
||||||
"type": "boolean",
|
|
||||||
"fieldKind": "input",
|
"fieldKind": "input",
|
||||||
"label": "",
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "BooleanField"
|
||||||
|
},
|
||||||
"value": true
|
"value": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -114,35 +158,40 @@
|
|||||||
"noise": {
|
"noise": {
|
||||||
"id": "50f650dc-0184-4e23-a927-0497a96fe954",
|
"id": "50f650dc-0184-4e23-a927-0497a96fe954",
|
||||||
"name": "noise",
|
"name": "noise",
|
||||||
"type": "LatentsField",
|
"fieldKind": "output",
|
||||||
"fieldKind": "output"
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "LatentsField"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"width": {
|
"width": {
|
||||||
"id": "bb8a452b-133d-42d1-ae4a-3843d7e4109a",
|
"id": "bb8a452b-133d-42d1-ae4a-3843d7e4109a",
|
||||||
"name": "width",
|
"name": "width",
|
||||||
"type": "integer",
|
"fieldKind": "output",
|
||||||
"fieldKind": "output"
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "IntegerField"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"height": {
|
"height": {
|
||||||
"id": "35cfaa12-3b8b-4b7a-a884-327ff3abddd9",
|
"id": "35cfaa12-3b8b-4b7a-a884-327ff3abddd9",
|
||||||
"name": "height",
|
"name": "height",
|
||||||
"type": "integer",
|
"fieldKind": "output",
|
||||||
"fieldKind": "output"
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "IntegerField"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
"label": "",
|
|
||||||
"isOpen": true,
|
|
||||||
"notes": "",
|
|
||||||
"embedWorkflow": false,
|
|
||||||
"isIntermediate": true,
|
|
||||||
"useCache": true,
|
|
||||||
"version": "1.0.0"
|
|
||||||
},
|
},
|
||||||
"width": 320,
|
"width": 320,
|
||||||
"height": 389,
|
"height": 388,
|
||||||
"position": {
|
"position": {
|
||||||
"x": 993.4442117555518,
|
"x": 600,
|
||||||
"y": 605.6757415334787
|
"y": 325
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -151,13 +200,24 @@
|
|||||||
"data": {
|
"data": {
|
||||||
"id": "c8d55139-f380-4695-b7f2-8b3d1e1e3db8",
|
"id": "c8d55139-f380-4695-b7f2-8b3d1e1e3db8",
|
||||||
"type": "main_model_loader",
|
"type": "main_model_loader",
|
||||||
|
"label": "",
|
||||||
|
"isOpen": true,
|
||||||
|
"notes": "",
|
||||||
|
"isIntermediate": true,
|
||||||
|
"useCache": true,
|
||||||
|
"version": "1.0.0",
|
||||||
|
"nodePack": "invokeai",
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"model": {
|
"model": {
|
||||||
"id": "993eabd2-40fd-44fe-bce7-5d0c7075ddab",
|
"id": "993eabd2-40fd-44fe-bce7-5d0c7075ddab",
|
||||||
"name": "model",
|
"name": "model",
|
||||||
"type": "MainModelField",
|
|
||||||
"fieldKind": "input",
|
"fieldKind": "input",
|
||||||
"label": "",
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "MainModelField"
|
||||||
|
},
|
||||||
"value": {
|
"value": {
|
||||||
"model_name": "stable-diffusion-v1-5",
|
"model_name": "stable-diffusion-v1-5",
|
||||||
"base_model": "sd-1",
|
"base_model": "sd-1",
|
||||||
@@ -169,35 +229,40 @@
|
|||||||
"unet": {
|
"unet": {
|
||||||
"id": "5c18c9db-328d-46d0-8cb9-143391c410be",
|
"id": "5c18c9db-328d-46d0-8cb9-143391c410be",
|
||||||
"name": "unet",
|
"name": "unet",
|
||||||
"type": "UNetField",
|
"fieldKind": "output",
|
||||||
"fieldKind": "output"
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "UNetField"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"clip": {
|
"clip": {
|
||||||
"id": "6effcac0-ec2f-4bf5-a49e-a2c29cf921f4",
|
"id": "6effcac0-ec2f-4bf5-a49e-a2c29cf921f4",
|
||||||
"name": "clip",
|
"name": "clip",
|
||||||
"type": "ClipField",
|
"fieldKind": "output",
|
||||||
"fieldKind": "output"
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "ClipField"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"vae": {
|
"vae": {
|
||||||
"id": "57683ba3-f5f5-4f58-b9a2-4b83dacad4a1",
|
"id": "57683ba3-f5f5-4f58-b9a2-4b83dacad4a1",
|
||||||
"name": "vae",
|
"name": "vae",
|
||||||
"type": "VaeField",
|
"fieldKind": "output",
|
||||||
"fieldKind": "output"
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "VaeField"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
"label": "",
|
|
||||||
"isOpen": true,
|
|
||||||
"notes": "",
|
|
||||||
"embedWorkflow": false,
|
|
||||||
"isIntermediate": true,
|
|
||||||
"useCache": true,
|
|
||||||
"version": "1.0.0"
|
|
||||||
},
|
},
|
||||||
"width": 320,
|
"width": 320,
|
||||||
"height": 226,
|
"height": 226,
|
||||||
"position": {
|
"position": {
|
||||||
"x": 163.04436745878343,
|
"x": 600,
|
||||||
"y": 254.63156870373479
|
"y": 25
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -206,44 +271,56 @@
|
|||||||
"data": {
|
"data": {
|
||||||
"id": "7d8bf987-284f-413a-b2fd-d825445a5d6c",
|
"id": "7d8bf987-284f-413a-b2fd-d825445a5d6c",
|
||||||
"type": "compel",
|
"type": "compel",
|
||||||
|
"label": "Positive Compel Prompt",
|
||||||
|
"isOpen": true,
|
||||||
|
"notes": "",
|
||||||
|
"isIntermediate": true,
|
||||||
|
"useCache": true,
|
||||||
|
"version": "1.0.0",
|
||||||
|
"nodePack": "invokeai",
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"prompt": {
|
"prompt": {
|
||||||
"id": "7739aff6-26cb-4016-8897-5a1fb2305e4e",
|
"id": "7739aff6-26cb-4016-8897-5a1fb2305e4e",
|
||||||
"name": "prompt",
|
"name": "prompt",
|
||||||
"type": "string",
|
|
||||||
"fieldKind": "input",
|
"fieldKind": "input",
|
||||||
"label": "Positive Prompt",
|
"label": "Positive Prompt",
|
||||||
"value": ""
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "StringField"
|
||||||
|
},
|
||||||
|
"value": "Super cute tiger cub, national geographic award-winning photograph"
|
||||||
},
|
},
|
||||||
"clip": {
|
"clip": {
|
||||||
"id": "48d23dce-a6ae-472a-9f8c-22a714ea5ce0",
|
"id": "48d23dce-a6ae-472a-9f8c-22a714ea5ce0",
|
||||||
"name": "clip",
|
"name": "clip",
|
||||||
"type": "ClipField",
|
|
||||||
"fieldKind": "input",
|
"fieldKind": "input",
|
||||||
"label": ""
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "ClipField"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"outputs": {
|
"outputs": {
|
||||||
"conditioning": {
|
"conditioning": {
|
||||||
"id": "37cf3a9d-f6b7-4b64-8ff6-2558c5ecc447",
|
"id": "37cf3a9d-f6b7-4b64-8ff6-2558c5ecc447",
|
||||||
"name": "conditioning",
|
"name": "conditioning",
|
||||||
"type": "ConditioningField",
|
"fieldKind": "output",
|
||||||
"fieldKind": "output"
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "ConditioningField"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
"label": "Positive Compel Prompt",
|
|
||||||
"isOpen": true,
|
|
||||||
"notes": "",
|
|
||||||
"embedWorkflow": false,
|
|
||||||
"isIntermediate": true,
|
|
||||||
"useCache": true,
|
|
||||||
"version": "1.0.0"
|
|
||||||
},
|
},
|
||||||
"width": 320,
|
"width": 320,
|
||||||
"height": 261,
|
"height": 259,
|
||||||
"position": {
|
"position": {
|
||||||
"x": 595.7263915923627,
|
"x": 1000,
|
||||||
"y": 239.67783573351227
|
"y": 25
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -252,21 +329,36 @@
|
|||||||
"data": {
|
"data": {
|
||||||
"id": "ea94bc37-d995-4a83-aa99-4af42479f2f2",
|
"id": "ea94bc37-d995-4a83-aa99-4af42479f2f2",
|
||||||
"type": "rand_int",
|
"type": "rand_int",
|
||||||
|
"label": "Random Seed",
|
||||||
|
"isOpen": false,
|
||||||
|
"notes": "",
|
||||||
|
"isIntermediate": true,
|
||||||
|
"useCache": false,
|
||||||
|
"version": "1.0.0",
|
||||||
|
"nodePack": "invokeai",
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"low": {
|
"low": {
|
||||||
"id": "3ec65a37-60ba-4b6c-a0b2-553dd7a84b84",
|
"id": "3ec65a37-60ba-4b6c-a0b2-553dd7a84b84",
|
||||||
"name": "low",
|
"name": "low",
|
||||||
"type": "integer",
|
|
||||||
"fieldKind": "input",
|
"fieldKind": "input",
|
||||||
"label": "",
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "IntegerField"
|
||||||
|
},
|
||||||
"value": 0
|
"value": 0
|
||||||
},
|
},
|
||||||
"high": {
|
"high": {
|
||||||
"id": "085f853a-1a5f-494d-8bec-e4ba29a3f2d1",
|
"id": "085f853a-1a5f-494d-8bec-e4ba29a3f2d1",
|
||||||
"name": "high",
|
"name": "high",
|
||||||
"type": "integer",
|
|
||||||
"fieldKind": "input",
|
"fieldKind": "input",
|
||||||
"label": "",
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "IntegerField"
|
||||||
|
},
|
||||||
"value": 2147483647
|
"value": 2147483647
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -274,23 +366,20 @@
|
|||||||
"value": {
|
"value": {
|
||||||
"id": "812ade4d-7699-4261-b9fc-a6c9d2ab55ee",
|
"id": "812ade4d-7699-4261-b9fc-a6c9d2ab55ee",
|
||||||
"name": "value",
|
"name": "value",
|
||||||
"type": "integer",
|
"fieldKind": "output",
|
||||||
"fieldKind": "output"
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "IntegerField"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
"label": "Random Seed",
|
|
||||||
"isOpen": true,
|
|
||||||
"notes": "",
|
|
||||||
"embedWorkflow": false,
|
|
||||||
"isIntermediate": true,
|
|
||||||
"useCache": false,
|
|
||||||
"version": "1.0.0"
|
|
||||||
},
|
},
|
||||||
"width": 320,
|
"width": 320,
|
||||||
"height": 218,
|
"height": 32,
|
||||||
"position": {
|
"position": {
|
||||||
"x": 541.094822888628,
|
"x": 600,
|
||||||
"y": 694.5704476446829
|
"y": 275
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -299,144 +388,224 @@
|
|||||||
"data": {
|
"data": {
|
||||||
"id": "eea2702a-19fb-45b5-9d75-56b4211ec03c",
|
"id": "eea2702a-19fb-45b5-9d75-56b4211ec03c",
|
||||||
"type": "denoise_latents",
|
"type": "denoise_latents",
|
||||||
|
"label": "",
|
||||||
|
"isOpen": true,
|
||||||
|
"notes": "",
|
||||||
|
"isIntermediate": true,
|
||||||
|
"useCache": true,
|
||||||
|
"version": "1.5.0",
|
||||||
|
"nodePack": "invokeai",
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"positive_conditioning": {
|
"positive_conditioning": {
|
||||||
"id": "90b7f4f8-ada7-4028-8100-d2e54f192052",
|
"id": "90b7f4f8-ada7-4028-8100-d2e54f192052",
|
||||||
"name": "positive_conditioning",
|
"name": "positive_conditioning",
|
||||||
"type": "ConditioningField",
|
|
||||||
"fieldKind": "input",
|
"fieldKind": "input",
|
||||||
"label": ""
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "ConditioningField"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"negative_conditioning": {
|
"negative_conditioning": {
|
||||||
"id": "9393779e-796c-4f64-b740-902a1177bf53",
|
"id": "9393779e-796c-4f64-b740-902a1177bf53",
|
||||||
"name": "negative_conditioning",
|
"name": "negative_conditioning",
|
||||||
"type": "ConditioningField",
|
|
||||||
"fieldKind": "input",
|
"fieldKind": "input",
|
||||||
"label": ""
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "ConditioningField"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"noise": {
|
"noise": {
|
||||||
"id": "8e17f1e5-4f98-40b1-b7f4-86aeeb4554c1",
|
"id": "8e17f1e5-4f98-40b1-b7f4-86aeeb4554c1",
|
||||||
"name": "noise",
|
"name": "noise",
|
||||||
"type": "LatentsField",
|
|
||||||
"fieldKind": "input",
|
"fieldKind": "input",
|
||||||
"label": ""
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "LatentsField"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"steps": {
|
"steps": {
|
||||||
"id": "9b63302d-6bd2-42c9-ac13-9b1afb51af88",
|
"id": "9b63302d-6bd2-42c9-ac13-9b1afb51af88",
|
||||||
"name": "steps",
|
"name": "steps",
|
||||||
"type": "integer",
|
|
||||||
"fieldKind": "input",
|
"fieldKind": "input",
|
||||||
"label": "",
|
"label": "",
|
||||||
"value": 10
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "IntegerField"
|
||||||
|
},
|
||||||
|
"value": 50
|
||||||
},
|
},
|
||||||
"cfg_scale": {
|
"cfg_scale": {
|
||||||
"id": "87dd04d3-870e-49e1-98bf-af003a810109",
|
"id": "87dd04d3-870e-49e1-98bf-af003a810109",
|
||||||
"name": "cfg_scale",
|
"name": "cfg_scale",
|
||||||
"type": "FloatPolymorphic",
|
|
||||||
"fieldKind": "input",
|
"fieldKind": "input",
|
||||||
"label": "",
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": true,
|
||||||
|
"name": "FloatField"
|
||||||
|
},
|
||||||
"value": 7.5
|
"value": 7.5
|
||||||
},
|
},
|
||||||
"denoising_start": {
|
"denoising_start": {
|
||||||
"id": "f369d80f-4931-4740-9bcd-9f0620719fab",
|
"id": "f369d80f-4931-4740-9bcd-9f0620719fab",
|
||||||
"name": "denoising_start",
|
"name": "denoising_start",
|
||||||
"type": "float",
|
|
||||||
"fieldKind": "input",
|
"fieldKind": "input",
|
||||||
"label": "",
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "FloatField"
|
||||||
|
},
|
||||||
"value": 0
|
"value": 0
|
||||||
},
|
},
|
||||||
"denoising_end": {
|
"denoising_end": {
|
||||||
"id": "747d10e5-6f02-445c-994c-0604d814de8c",
|
"id": "747d10e5-6f02-445c-994c-0604d814de8c",
|
||||||
"name": "denoising_end",
|
"name": "denoising_end",
|
||||||
"type": "float",
|
|
||||||
"fieldKind": "input",
|
"fieldKind": "input",
|
||||||
"label": "",
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "FloatField"
|
||||||
|
},
|
||||||
"value": 1
|
"value": 1
|
||||||
},
|
},
|
||||||
"scheduler": {
|
"scheduler": {
|
||||||
"id": "1de84a4e-3a24-4ec8-862b-16ce49633b9b",
|
"id": "1de84a4e-3a24-4ec8-862b-16ce49633b9b",
|
||||||
"name": "scheduler",
|
"name": "scheduler",
|
||||||
"type": "Scheduler",
|
|
||||||
"fieldKind": "input",
|
"fieldKind": "input",
|
||||||
"label": "",
|
"label": "",
|
||||||
"value": "euler"
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "SchedulerField"
|
||||||
|
},
|
||||||
|
"value": "unipc"
|
||||||
},
|
},
|
||||||
"unet": {
|
"unet": {
|
||||||
"id": "ffa6fef4-3ce2-4bdb-9296-9a834849489b",
|
"id": "ffa6fef4-3ce2-4bdb-9296-9a834849489b",
|
||||||
"name": "unet",
|
"name": "unet",
|
||||||
"type": "UNetField",
|
|
||||||
"fieldKind": "input",
|
"fieldKind": "input",
|
||||||
"label": ""
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "UNetField"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"control": {
|
"control": {
|
||||||
"id": "077b64cb-34be-4fcc-83f2-e399807a02bd",
|
"id": "077b64cb-34be-4fcc-83f2-e399807a02bd",
|
||||||
"name": "control",
|
"name": "control",
|
||||||
"type": "ControlPolymorphic",
|
|
||||||
"fieldKind": "input",
|
"fieldKind": "input",
|
||||||
"label": ""
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": true,
|
||||||
|
"name": "ControlField"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"ip_adapter": {
|
"ip_adapter": {
|
||||||
"id": "1d6948f7-3a65-4a65-a20c-768b287251aa",
|
"id": "1d6948f7-3a65-4a65-a20c-768b287251aa",
|
||||||
"name": "ip_adapter",
|
"name": "ip_adapter",
|
||||||
"type": "IPAdapterPolymorphic",
|
|
||||||
"fieldKind": "input",
|
"fieldKind": "input",
|
||||||
"label": ""
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": true,
|
||||||
|
"name": "IPAdapterField"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"t2i_adapter": {
|
"t2i_adapter": {
|
||||||
"id": "75e67b09-952f-4083-aaf4-6b804d690412",
|
"id": "75e67b09-952f-4083-aaf4-6b804d690412",
|
||||||
"name": "t2i_adapter",
|
"name": "t2i_adapter",
|
||||||
"type": "T2IAdapterPolymorphic",
|
|
||||||
"fieldKind": "input",
|
"fieldKind": "input",
|
||||||
"label": ""
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": true,
|
||||||
|
"name": "T2IAdapterField"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"cfg_rescale_multiplier": {
|
||||||
|
"id": "9101f0a6-5fe0-4826-b7b3-47e5d506826c",
|
||||||
|
"name": "cfg_rescale_multiplier",
|
||||||
|
"fieldKind": "input",
|
||||||
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "FloatField"
|
||||||
|
},
|
||||||
|
"value": 0
|
||||||
},
|
},
|
||||||
"latents": {
|
"latents": {
|
||||||
"id": "334d4ba3-5a99-4195-82c5-86fb3f4f7d43",
|
"id": "334d4ba3-5a99-4195-82c5-86fb3f4f7d43",
|
||||||
"name": "latents",
|
"name": "latents",
|
||||||
"type": "LatentsField",
|
|
||||||
"fieldKind": "input",
|
"fieldKind": "input",
|
||||||
"label": ""
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "LatentsField"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"denoise_mask": {
|
"denoise_mask": {
|
||||||
"id": "0d3dbdbf-b014-4e95-8b18-ff2ff9cb0bfa",
|
"id": "0d3dbdbf-b014-4e95-8b18-ff2ff9cb0bfa",
|
||||||
"name": "denoise_mask",
|
"name": "denoise_mask",
|
||||||
"type": "DenoiseMaskField",
|
|
||||||
"fieldKind": "input",
|
"fieldKind": "input",
|
||||||
"label": ""
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "DenoiseMaskField"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"outputs": {
|
"outputs": {
|
||||||
"latents": {
|
"latents": {
|
||||||
"id": "70fa5bbc-0c38-41bb-861a-74d6d78d2f38",
|
"id": "70fa5bbc-0c38-41bb-861a-74d6d78d2f38",
|
||||||
"name": "latents",
|
"name": "latents",
|
||||||
"type": "LatentsField",
|
"fieldKind": "output",
|
||||||
"fieldKind": "output"
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "LatentsField"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"width": {
|
"width": {
|
||||||
"id": "98ee0e6c-82aa-4e8f-8be5-dc5f00ee47f0",
|
"id": "98ee0e6c-82aa-4e8f-8be5-dc5f00ee47f0",
|
||||||
"name": "width",
|
"name": "width",
|
||||||
"type": "integer",
|
"fieldKind": "output",
|
||||||
"fieldKind": "output"
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "IntegerField"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"height": {
|
"height": {
|
||||||
"id": "e8cb184a-5e1a-47c8-9695-4b8979564f5d",
|
"id": "e8cb184a-5e1a-47c8-9695-4b8979564f5d",
|
||||||
"name": "height",
|
"name": "height",
|
||||||
"type": "integer",
|
"fieldKind": "output",
|
||||||
"fieldKind": "output"
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "IntegerField"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
"label": "",
|
|
||||||
"isOpen": true,
|
|
||||||
"notes": "",
|
|
||||||
"embedWorkflow": false,
|
|
||||||
"isIntermediate": true,
|
|
||||||
"useCache": true,
|
|
||||||
"version": "1.4.0"
|
|
||||||
},
|
},
|
||||||
"width": 320,
|
"width": 320,
|
||||||
"height": 646,
|
"height": 703,
|
||||||
"position": {
|
"position": {
|
||||||
"x": 1476.5794704734735,
|
"x": 1400,
|
||||||
"y": 256.80174342731783
|
"y": 25
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -445,153 +614,185 @@
|
|||||||
"data": {
|
"data": {
|
||||||
"id": "58c957f5-0d01-41fc-a803-b2bbf0413d4f",
|
"id": "58c957f5-0d01-41fc-a803-b2bbf0413d4f",
|
||||||
"type": "l2i",
|
"type": "l2i",
|
||||||
|
"label": "",
|
||||||
|
"isOpen": true,
|
||||||
|
"notes": "",
|
||||||
|
"isIntermediate": false,
|
||||||
|
"useCache": true,
|
||||||
|
"version": "1.2.0",
|
||||||
|
"nodePack": "invokeai",
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"id": "ab375f12-0042-4410-9182-29e30db82c85",
|
"id": "ab375f12-0042-4410-9182-29e30db82c85",
|
||||||
"name": "metadata",
|
"name": "metadata",
|
||||||
"type": "MetadataField",
|
|
||||||
"fieldKind": "input",
|
"fieldKind": "input",
|
||||||
"label": ""
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "MetadataField"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"latents": {
|
"latents": {
|
||||||
"id": "3a7e7efd-bff5-47d7-9d48-615127afee78",
|
"id": "3a7e7efd-bff5-47d7-9d48-615127afee78",
|
||||||
"name": "latents",
|
"name": "latents",
|
||||||
"type": "LatentsField",
|
|
||||||
"fieldKind": "input",
|
"fieldKind": "input",
|
||||||
"label": ""
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "LatentsField"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"vae": {
|
"vae": {
|
||||||
"id": "a1f5f7a1-0795-4d58-b036-7820c0b0ef2b",
|
"id": "a1f5f7a1-0795-4d58-b036-7820c0b0ef2b",
|
||||||
"name": "vae",
|
"name": "vae",
|
||||||
"type": "VaeField",
|
|
||||||
"fieldKind": "input",
|
"fieldKind": "input",
|
||||||
"label": ""
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "VaeField"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"tiled": {
|
"tiled": {
|
||||||
"id": "da52059a-0cee-4668-942f-519aa794d739",
|
"id": "da52059a-0cee-4668-942f-519aa794d739",
|
||||||
"name": "tiled",
|
"name": "tiled",
|
||||||
"type": "boolean",
|
|
||||||
"fieldKind": "input",
|
"fieldKind": "input",
|
||||||
"label": "",
|
"label": "",
|
||||||
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "BooleanField"
|
||||||
|
},
|
||||||
"value": false
|
"value": false
|
||||||
},
|
},
|
||||||
"fp32": {
|
"fp32": {
|
||||||
"id": "c4841df3-b24e-4140-be3b-ccd454c2522c",
|
"id": "c4841df3-b24e-4140-be3b-ccd454c2522c",
|
||||||
"name": "fp32",
|
"name": "fp32",
|
||||||
"type": "boolean",
|
|
||||||
"fieldKind": "input",
|
"fieldKind": "input",
|
||||||
"label": "",
|
"label": "",
|
||||||
"value": false
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "BooleanField"
|
||||||
|
},
|
||||||
|
"value": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"outputs": {
|
"outputs": {
|
||||||
"image": {
|
"image": {
|
||||||
"id": "72d667d0-cf85-459d-abf2-28bd8b823fe7",
|
"id": "72d667d0-cf85-459d-abf2-28bd8b823fe7",
|
||||||
"name": "image",
|
"name": "image",
|
||||||
"type": "ImageField",
|
"fieldKind": "output",
|
||||||
"fieldKind": "output"
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "ImageField"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"width": {
|
"width": {
|
||||||
"id": "c8c907d8-1066-49d1-b9a6-83bdcd53addc",
|
"id": "c8c907d8-1066-49d1-b9a6-83bdcd53addc",
|
||||||
"name": "width",
|
"name": "width",
|
||||||
"type": "integer",
|
"fieldKind": "output",
|
||||||
"fieldKind": "output"
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "IntegerField"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"height": {
|
"height": {
|
||||||
"id": "230f359c-b4ea-436c-b372-332d7dcdca85",
|
"id": "230f359c-b4ea-436c-b372-332d7dcdca85",
|
||||||
"name": "height",
|
"name": "height",
|
||||||
"type": "integer",
|
"fieldKind": "output",
|
||||||
"fieldKind": "output"
|
"type": {
|
||||||
|
"isCollection": false,
|
||||||
|
"isCollectionOrScalar": false,
|
||||||
|
"name": "IntegerField"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
"label": "",
|
|
||||||
"isOpen": true,
|
|
||||||
"notes": "",
|
|
||||||
"embedWorkflow": false,
|
|
||||||
"isIntermediate": false,
|
|
||||||
"useCache": true,
|
|
||||||
"version": "1.0.0"
|
|
||||||
},
|
},
|
||||||
"width": 320,
|
"width": 320,
|
||||||
"height": 267,
|
"height": 266,
|
||||||
"position": {
|
"position": {
|
||||||
"x": 2037.9648469717395,
|
"x": 1800,
|
||||||
"y": 426.10844427600136
|
"y": 25
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"edges": [
|
"edges": [
|
||||||
{
|
{
|
||||||
"source": "ea94bc37-d995-4a83-aa99-4af42479f2f2",
|
|
||||||
"sourceHandle": "value",
|
|
||||||
"target": "55705012-79b9-4aac-9f26-c0b10309785b",
|
|
||||||
"targetHandle": "seed",
|
|
||||||
"id": "reactflow__edge-ea94bc37-d995-4a83-aa99-4af42479f2f2value-55705012-79b9-4aac-9f26-c0b10309785bseed",
|
"id": "reactflow__edge-ea94bc37-d995-4a83-aa99-4af42479f2f2value-55705012-79b9-4aac-9f26-c0b10309785bseed",
|
||||||
"type": "default"
|
"source": "ea94bc37-d995-4a83-aa99-4af42479f2f2",
|
||||||
|
"target": "55705012-79b9-4aac-9f26-c0b10309785b",
|
||||||
|
"type": "default",
|
||||||
|
"sourceHandle": "value",
|
||||||
|
"targetHandle": "seed"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"source": "c8d55139-f380-4695-b7f2-8b3d1e1e3db8",
|
|
||||||
"sourceHandle": "clip",
|
|
||||||
"target": "7d8bf987-284f-413a-b2fd-d825445a5d6c",
|
|
||||||
"targetHandle": "clip",
|
|
||||||
"id": "reactflow__edge-c8d55139-f380-4695-b7f2-8b3d1e1e3db8clip-7d8bf987-284f-413a-b2fd-d825445a5d6cclip",
|
"id": "reactflow__edge-c8d55139-f380-4695-b7f2-8b3d1e1e3db8clip-7d8bf987-284f-413a-b2fd-d825445a5d6cclip",
|
||||||
"type": "default"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"source": "c8d55139-f380-4695-b7f2-8b3d1e1e3db8",
|
"source": "c8d55139-f380-4695-b7f2-8b3d1e1e3db8",
|
||||||
|
"target": "7d8bf987-284f-413a-b2fd-d825445a5d6c",
|
||||||
|
"type": "default",
|
||||||
"sourceHandle": "clip",
|
"sourceHandle": "clip",
|
||||||
"target": "93dc02a4-d05b-48ed-b99c-c9b616af3402",
|
"targetHandle": "clip"
|
||||||
"targetHandle": "clip",
|
},
|
||||||
|
{
|
||||||
"id": "reactflow__edge-c8d55139-f380-4695-b7f2-8b3d1e1e3db8clip-93dc02a4-d05b-48ed-b99c-c9b616af3402clip",
|
"id": "reactflow__edge-c8d55139-f380-4695-b7f2-8b3d1e1e3db8clip-93dc02a4-d05b-48ed-b99c-c9b616af3402clip",
|
||||||
"type": "default"
|
"source": "c8d55139-f380-4695-b7f2-8b3d1e1e3db8",
|
||||||
|
"target": "93dc02a4-d05b-48ed-b99c-c9b616af3402",
|
||||||
|
"type": "default",
|
||||||
|
"sourceHandle": "clip",
|
||||||
|
"targetHandle": "clip"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"source": "55705012-79b9-4aac-9f26-c0b10309785b",
|
|
||||||
"sourceHandle": "noise",
|
|
||||||
"target": "eea2702a-19fb-45b5-9d75-56b4211ec03c",
|
|
||||||
"targetHandle": "noise",
|
|
||||||
"id": "reactflow__edge-55705012-79b9-4aac-9f26-c0b10309785bnoise-eea2702a-19fb-45b5-9d75-56b4211ec03cnoise",
|
"id": "reactflow__edge-55705012-79b9-4aac-9f26-c0b10309785bnoise-eea2702a-19fb-45b5-9d75-56b4211ec03cnoise",
|
||||||
"type": "default"
|
"source": "55705012-79b9-4aac-9f26-c0b10309785b",
|
||||||
|
"target": "eea2702a-19fb-45b5-9d75-56b4211ec03c",
|
||||||
|
"type": "default",
|
||||||
|
"sourceHandle": "noise",
|
||||||
|
"targetHandle": "noise"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"source": "7d8bf987-284f-413a-b2fd-d825445a5d6c",
|
|
||||||
"sourceHandle": "conditioning",
|
|
||||||
"target": "eea2702a-19fb-45b5-9d75-56b4211ec03c",
|
|
||||||
"targetHandle": "positive_conditioning",
|
|
||||||
"id": "reactflow__edge-7d8bf987-284f-413a-b2fd-d825445a5d6cconditioning-eea2702a-19fb-45b5-9d75-56b4211ec03cpositive_conditioning",
|
"id": "reactflow__edge-7d8bf987-284f-413a-b2fd-d825445a5d6cconditioning-eea2702a-19fb-45b5-9d75-56b4211ec03cpositive_conditioning",
|
||||||
"type": "default"
|
"source": "7d8bf987-284f-413a-b2fd-d825445a5d6c",
|
||||||
},
|
"target": "eea2702a-19fb-45b5-9d75-56b4211ec03c",
|
||||||
{
|
"type": "default",
|
||||||
"source": "93dc02a4-d05b-48ed-b99c-c9b616af3402",
|
|
||||||
"sourceHandle": "conditioning",
|
"sourceHandle": "conditioning",
|
||||||
"target": "eea2702a-19fb-45b5-9d75-56b4211ec03c",
|
"targetHandle": "positive_conditioning"
|
||||||
"targetHandle": "negative_conditioning",
|
},
|
||||||
|
{
|
||||||
"id": "reactflow__edge-93dc02a4-d05b-48ed-b99c-c9b616af3402conditioning-eea2702a-19fb-45b5-9d75-56b4211ec03cnegative_conditioning",
|
"id": "reactflow__edge-93dc02a4-d05b-48ed-b99c-c9b616af3402conditioning-eea2702a-19fb-45b5-9d75-56b4211ec03cnegative_conditioning",
|
||||||
"type": "default"
|
"source": "93dc02a4-d05b-48ed-b99c-c9b616af3402",
|
||||||
},
|
|
||||||
{
|
|
||||||
"source": "c8d55139-f380-4695-b7f2-8b3d1e1e3db8",
|
|
||||||
"sourceHandle": "unet",
|
|
||||||
"target": "eea2702a-19fb-45b5-9d75-56b4211ec03c",
|
"target": "eea2702a-19fb-45b5-9d75-56b4211ec03c",
|
||||||
"targetHandle": "unet",
|
"type": "default",
|
||||||
|
"sourceHandle": "conditioning",
|
||||||
|
"targetHandle": "negative_conditioning"
|
||||||
|
},
|
||||||
|
{
|
||||||
"id": "reactflow__edge-c8d55139-f380-4695-b7f2-8b3d1e1e3db8unet-eea2702a-19fb-45b5-9d75-56b4211ec03cunet",
|
"id": "reactflow__edge-c8d55139-f380-4695-b7f2-8b3d1e1e3db8unet-eea2702a-19fb-45b5-9d75-56b4211ec03cunet",
|
||||||
"type": "default"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"source": "eea2702a-19fb-45b5-9d75-56b4211ec03c",
|
|
||||||
"sourceHandle": "latents",
|
|
||||||
"target": "58c957f5-0d01-41fc-a803-b2bbf0413d4f",
|
|
||||||
"targetHandle": "latents",
|
|
||||||
"id": "reactflow__edge-eea2702a-19fb-45b5-9d75-56b4211ec03clatents-58c957f5-0d01-41fc-a803-b2bbf0413d4flatents",
|
|
||||||
"type": "default"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"source": "c8d55139-f380-4695-b7f2-8b3d1e1e3db8",
|
"source": "c8d55139-f380-4695-b7f2-8b3d1e1e3db8",
|
||||||
"sourceHandle": "vae",
|
"target": "eea2702a-19fb-45b5-9d75-56b4211ec03c",
|
||||||
|
"type": "default",
|
||||||
|
"sourceHandle": "unet",
|
||||||
|
"targetHandle": "unet"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "reactflow__edge-eea2702a-19fb-45b5-9d75-56b4211ec03clatents-58c957f5-0d01-41fc-a803-b2bbf0413d4flatents",
|
||||||
|
"source": "eea2702a-19fb-45b5-9d75-56b4211ec03c",
|
||||||
"target": "58c957f5-0d01-41fc-a803-b2bbf0413d4f",
|
"target": "58c957f5-0d01-41fc-a803-b2bbf0413d4f",
|
||||||
"targetHandle": "vae",
|
"type": "default",
|
||||||
|
"sourceHandle": "latents",
|
||||||
|
"targetHandle": "latents"
|
||||||
|
},
|
||||||
|
{
|
||||||
"id": "reactflow__edge-c8d55139-f380-4695-b7f2-8b3d1e1e3db8vae-58c957f5-0d01-41fc-a803-b2bbf0413d4fvae",
|
"id": "reactflow__edge-c8d55139-f380-4695-b7f2-8b3d1e1e3db8vae-58c957f5-0d01-41fc-a803-b2bbf0413d4fvae",
|
||||||
"type": "default"
|
"source": "c8d55139-f380-4695-b7f2-8b3d1e1e3db8",
|
||||||
|
"target": "58c957f5-0d01-41fc-a803-b2bbf0413d4f",
|
||||||
|
"type": "default",
|
||||||
|
"sourceHandle": "vae",
|
||||||
|
"targetHandle": "vae"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,56 +2,60 @@
|
|||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
cd "$(dirname "$0")"
|
BCYAN="\e[1;36m"
|
||||||
|
BYELLOW="\e[1;33m"
|
||||||
|
BGREEN="\e[1;32m"
|
||||||
|
BRED="\e[1;31m"
|
||||||
|
RED="\e[31m"
|
||||||
|
RESET="\e[0m"
|
||||||
|
|
||||||
|
function is_bin_in_path {
|
||||||
|
builtin type -P "$1" &>/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
function git_show {
|
||||||
|
git show -s --format=oneline --abbrev-commit "$1" | cat
|
||||||
|
}
|
||||||
|
|
||||||
if [[ -v "VIRTUAL_ENV" ]]; then
|
if [[ -v "VIRTUAL_ENV" ]]; then
|
||||||
# we can't just call 'deactivate' because this function is not exported
|
# we can't just call 'deactivate' because this function is not exported
|
||||||
# to the environment of this script from the bash process that runs the script
|
# to the environment of this script from the bash process that runs the script
|
||||||
echo "A virtual environment is activated. Please deactivate it before proceeding".
|
echo -e "${BRED}A virtual environment is activated. Please deactivate it before proceeding.${RESET}"
|
||||||
exit -1
|
exit -1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
VERSION=$(cd ..; python -c "from invokeai.version import __version__ as version; print(version)")
|
cd "$(dirname "$0")"
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo -e "${BYELLOW}This script must be run from the installer directory!${RESET}"
|
||||||
|
echo "The current working directory is $(pwd)"
|
||||||
|
read -p "If that looks right, press any key to proceed, or CTRL-C to exit..."
|
||||||
|
echo
|
||||||
|
|
||||||
|
# Some machines only have `python3` in PATH, others have `python` - make an alias.
|
||||||
|
# We can use a function to approximate an alias within a non-interactive shell.
|
||||||
|
if ! is_bin_in_path python && is_bin_in_path python3; then
|
||||||
|
function python {
|
||||||
|
python3 "$@"
|
||||||
|
}
|
||||||
|
fi
|
||||||
|
|
||||||
|
VERSION=$(
|
||||||
|
cd ..
|
||||||
|
python -c "from invokeai.version import __version__ as version; print(version)"
|
||||||
|
)
|
||||||
PATCH=""
|
PATCH=""
|
||||||
VERSION="v${VERSION}${PATCH}"
|
VERSION="v${VERSION}${PATCH}"
|
||||||
LATEST_TAG="v3-latest"
|
|
||||||
|
|
||||||
echo Building installer for version $VERSION
|
echo -e "${BGREEN}HEAD${RESET}:"
|
||||||
echo "Be certain that you're in the 'installer' directory before continuing."
|
git_show HEAD
|
||||||
read -p "Press any key to continue, or CTRL-C to exit..."
|
echo
|
||||||
|
|
||||||
read -e -p "Tag this repo with '${VERSION}' and '${LATEST_TAG}'? [n]: " input
|
|
||||||
RESPONSE=${input:='n'}
|
|
||||||
if [ "$RESPONSE" == 'y' ]; then
|
|
||||||
|
|
||||||
git push origin :refs/tags/$VERSION
|
|
||||||
if ! git tag -fa $VERSION ; then
|
|
||||||
echo "Existing/invalid tag"
|
|
||||||
exit -1
|
|
||||||
fi
|
|
||||||
|
|
||||||
git push origin :refs/tags/$LATEST_TAG
|
|
||||||
git tag -fa $LATEST_TAG
|
|
||||||
|
|
||||||
echo "remember to push --tags!"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# ----------------------
|
# ----------------------
|
||||||
|
|
||||||
echo Building the wheel
|
echo
|
||||||
|
echo "Building installer zip files for InvokeAI ${VERSION}..."
|
||||||
# install the 'build' package in the user site packages, if needed
|
echo
|
||||||
# could be improved by using a temporary venv, but it's tiny and harmless
|
|
||||||
if [[ $(python -c 'from importlib.util import find_spec; print(find_spec("build") is None)') == "True" ]]; then
|
|
||||||
pip install --user build
|
|
||||||
fi
|
|
||||||
|
|
||||||
rm -r ../build
|
|
||||||
python -m build --wheel --outdir dist/ ../.
|
|
||||||
|
|
||||||
# ----------------------
|
|
||||||
|
|
||||||
echo Building installer zip fles for InvokeAI $VERSION
|
|
||||||
|
|
||||||
# get rid of any old ones
|
# get rid of any old ones
|
||||||
rm -f *.zip
|
rm -f *.zip
|
||||||
@@ -59,12 +63,11 @@ rm -rf InvokeAI-Installer
|
|||||||
|
|
||||||
# copy content
|
# copy content
|
||||||
mkdir InvokeAI-Installer
|
mkdir InvokeAI-Installer
|
||||||
for f in templates lib *.txt *.reg; do
|
for f in templates *.txt *.reg; do
|
||||||
cp -r ${f} InvokeAI-Installer/
|
cp -r ${f} InvokeAI-Installer/
|
||||||
done
|
done
|
||||||
|
mkdir InvokeAI-Installer/lib
|
||||||
# Move the wheel
|
cp lib/*.py InvokeAI-Installer/lib
|
||||||
mv dist/*.whl InvokeAI-Installer/lib/
|
|
||||||
|
|
||||||
# Install scripts
|
# Install scripts
|
||||||
# Mac/Linux
|
# Mac/Linux
|
||||||
@@ -72,13 +75,13 @@ cp install.sh.in InvokeAI-Installer/install.sh
|
|||||||
chmod a+x InvokeAI-Installer/install.sh
|
chmod a+x InvokeAI-Installer/install.sh
|
||||||
|
|
||||||
# Windows
|
# Windows
|
||||||
perl -p -e "s/^set INVOKEAI_VERSION=.*/set INVOKEAI_VERSION=$VERSION/" install.bat.in > InvokeAI-Installer/install.bat
|
cp install.bat.in InvokeAI-Installer/install.bat
|
||||||
cp WinLongPathsEnabled.reg InvokeAI-Installer/
|
cp WinLongPathsEnabled.reg InvokeAI-Installer/
|
||||||
|
|
||||||
# Zip everything up
|
# Zip everything up
|
||||||
zip -r InvokeAI-installer-$VERSION.zip InvokeAI-Installer
|
zip -r InvokeAI-installer-$VERSION.zip InvokeAI-Installer
|
||||||
|
|
||||||
# clean up
|
# clean up
|
||||||
rm -rf InvokeAI-Installer tmp dist
|
rm -rf InvokeAI-Installer tmp dist ../invokeai/frontend/web/dist/
|
||||||
|
|
||||||
exit 0
|
exit 0
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ if "%1" == "use-cache" (
|
|||||||
@rem Config
|
@rem Config
|
||||||
@rem The version in the next line is replaced by an up to date release number
|
@rem The version in the next line is replaced by an up to date release number
|
||||||
@rem when create_installer.sh is run. Change the release number there.
|
@rem when create_installer.sh is run. Change the release number there.
|
||||||
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/
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import sys
|
|||||||
import venv
|
import venv
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from tempfile import TemporaryDirectory
|
from tempfile import TemporaryDirectory
|
||||||
from typing import Union
|
from typing import Optional, Tuple
|
||||||
|
|
||||||
SUPPORTED_PYTHON = ">=3.10.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"]
|
||||||
@@ -21,40 +21,20 @@ OS = platform.uname().system
|
|||||||
ARCH = platform.uname().machine
|
ARCH = platform.uname().machine
|
||||||
VERSION = "latest"
|
VERSION = "latest"
|
||||||
|
|
||||||
### Feature flags
|
|
||||||
# Install the virtualenv into the runtime dir
|
|
||||||
FF_VENV_IN_RUNTIME = True
|
|
||||||
|
|
||||||
# Install the wheel packaged with the installer
|
|
||||||
FF_USE_LOCAL_WHEEL = True
|
|
||||||
|
|
||||||
|
|
||||||
class Installer:
|
class Installer:
|
||||||
"""
|
"""
|
||||||
Deploys an InvokeAI installation into a given path
|
Deploys an InvokeAI installation into a given path
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
reqs: list[str] = INSTALLER_REQS
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.reqs = INSTALLER_REQS
|
|
||||||
self.preflight()
|
|
||||||
if os.getenv("VIRTUAL_ENV") is not None:
|
if os.getenv("VIRTUAL_ENV") is not None:
|
||||||
print("A virtual environment is already activated. Please 'deactivate' before installation.")
|
print("A virtual environment is already activated. Please 'deactivate' before installation.")
|
||||||
sys.exit(-1)
|
sys.exit(-1)
|
||||||
self.bootstrap()
|
self.bootstrap()
|
||||||
|
self.available_releases = get_github_releases()
|
||||||
def preflight(self) -> None:
|
|
||||||
"""
|
|
||||||
Preflight checks
|
|
||||||
"""
|
|
||||||
|
|
||||||
# TODO
|
|
||||||
# verify python version
|
|
||||||
# on macOS verify XCode tools are present
|
|
||||||
# verify libmesa, libglx on linux
|
|
||||||
# check that the system arch is not i386 (?)
|
|
||||||
# check that the system has a GPU, and the type of GPU
|
|
||||||
|
|
||||||
pass
|
|
||||||
|
|
||||||
def mktemp_venv(self) -> TemporaryDirectory:
|
def mktemp_venv(self) -> TemporaryDirectory:
|
||||||
"""
|
"""
|
||||||
@@ -78,12 +58,9 @@ class Installer:
|
|||||||
|
|
||||||
return venv_dir
|
return venv_dir
|
||||||
|
|
||||||
def bootstrap(self, verbose: bool = False) -> TemporaryDirectory:
|
def bootstrap(self, verbose: bool = False) -> TemporaryDirectory | None:
|
||||||
"""
|
"""
|
||||||
Bootstrap the installer venv with packages required at install time
|
Bootstrap the installer venv with packages required at install time
|
||||||
|
|
||||||
:return: path to the virtual environment directory that was bootstrapped
|
|
||||||
:rtype: TemporaryDirectory
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
print("Initializing the installer. This may take a minute - please wait...")
|
print("Initializing the installer. This may take a minute - please wait...")
|
||||||
@@ -95,39 +72,27 @@ class Installer:
|
|||||||
cmd.extend(self.reqs)
|
cmd.extend(self.reqs)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
res = subprocess.check_output(cmd).decode()
|
# upgrade pip to the latest version to avoid a confusing message
|
||||||
|
res = upgrade_pip(Path(venv_dir.name))
|
||||||
if verbose:
|
if verbose:
|
||||||
print(res)
|
print(res)
|
||||||
|
|
||||||
|
# run the install prerequisites installation
|
||||||
|
res = subprocess.check_output(cmd).decode()
|
||||||
|
|
||||||
|
if verbose:
|
||||||
|
print(res)
|
||||||
|
|
||||||
return venv_dir
|
return venv_dir
|
||||||
except subprocess.CalledProcessError as e:
|
except subprocess.CalledProcessError as e:
|
||||||
print(e)
|
print(e)
|
||||||
|
|
||||||
def app_venv(self, path: str = None):
|
def app_venv(self, venv_parent) -> Path:
|
||||||
"""
|
"""
|
||||||
Create a virtualenv for the InvokeAI installation
|
Create a virtualenv for the InvokeAI installation
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# explicit venv location
|
venv_dir = venv_parent / ".venv"
|
||||||
# currently unused in normal operation
|
|
||||||
# useful for testing or special cases
|
|
||||||
if path is not None:
|
|
||||||
venv_dir = Path(path)
|
|
||||||
|
|
||||||
# experimental / testing
|
|
||||||
elif not FF_VENV_IN_RUNTIME:
|
|
||||||
if OS == "Windows":
|
|
||||||
venv_dir_parent = os.getenv("APPDATA", "~/AppData/Roaming")
|
|
||||||
elif OS == "Darwin":
|
|
||||||
# there is no environment variable on macOS to find this
|
|
||||||
# TODO: confirm this is working as expected
|
|
||||||
venv_dir_parent = "~/Library/Application Support"
|
|
||||||
elif OS == "Linux":
|
|
||||||
venv_dir_parent = os.getenv("XDG_DATA_DIR", "~/.local/share")
|
|
||||||
venv_dir = Path(venv_dir_parent).expanduser().resolve() / f"InvokeAI/{VERSION}/venv"
|
|
||||||
|
|
||||||
# stable / current
|
|
||||||
else:
|
|
||||||
venv_dir = self.dest / ".venv"
|
|
||||||
|
|
||||||
# Prefer to copy python executables
|
# Prefer to copy python executables
|
||||||
# so that updates to system python don't break InvokeAI
|
# so that updates to system python don't break InvokeAI
|
||||||
@@ -141,7 +106,7 @@ class Installer:
|
|||||||
return venv_dir
|
return venv_dir
|
||||||
|
|
||||||
def install(
|
def install(
|
||||||
self, root: str = "~/invokeai", version: str = "latest", yes_to_all=False, find_links: Path = None
|
self, version=None, root: str = "~/invokeai", yes_to_all=False, find_links: Optional[Path] = None
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Install the InvokeAI application into the given runtime path
|
Install the InvokeAI application into the given runtime path
|
||||||
@@ -158,15 +123,20 @@ class Installer:
|
|||||||
|
|
||||||
import messages
|
import messages
|
||||||
|
|
||||||
messages.welcome()
|
messages.welcome(self.available_releases)
|
||||||
|
|
||||||
default_path = os.environ.get("INVOKEAI_ROOT") or Path(root).expanduser().resolve()
|
version = messages.choose_version(self.available_releases)
|
||||||
self.dest = default_path if yes_to_all else messages.dest_path(root)
|
|
||||||
|
auto_dest = Path(os.environ.get("INVOKEAI_ROOT", root)).expanduser().resolve()
|
||||||
|
destination = auto_dest if yes_to_all else messages.dest_path(root)
|
||||||
|
if destination is None:
|
||||||
|
print("Could not find or create the destination directory. Installation cancelled.")
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
# create the venv for the app
|
# create the venv for the app
|
||||||
self.venv = self.app_venv()
|
self.venv = self.app_venv(venv_parent=destination)
|
||||||
|
|
||||||
self.instance = InvokeAiInstance(runtime=self.dest, venv=self.venv, version=version)
|
self.instance = InvokeAiInstance(runtime=destination, venv=self.venv, version=version)
|
||||||
|
|
||||||
# install dependencies and the InvokeAI application
|
# install dependencies and the InvokeAI application
|
||||||
(extra_index_url, optional_modules) = get_torch_source() if not yes_to_all else (None, None)
|
(extra_index_url, optional_modules) = get_torch_source() if not yes_to_all else (None, None)
|
||||||
@@ -190,7 +160,7 @@ class InvokeAiInstance:
|
|||||||
A single runtime directory *may* be shared by multiple virtual environments, though this isn't currently tested or supported.
|
A single runtime directory *may* be shared by multiple virtual environments, though this isn't currently tested or supported.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, runtime: Path, venv: Path, version: str) -> None:
|
def __init__(self, runtime: Path, venv: Path, version: str = "stable") -> None:
|
||||||
self.runtime = runtime
|
self.runtime = runtime
|
||||||
self.venv = venv
|
self.venv = venv
|
||||||
self.pip = get_pip_from_venv(venv)
|
self.pip = get_pip_from_venv(venv)
|
||||||
@@ -199,6 +169,7 @@ class InvokeAiInstance:
|
|||||||
set_sys_path(venv)
|
set_sys_path(venv)
|
||||||
os.environ["INVOKEAI_ROOT"] = str(self.runtime.expanduser().resolve())
|
os.environ["INVOKEAI_ROOT"] = str(self.runtime.expanduser().resolve())
|
||||||
os.environ["VIRTUAL_ENV"] = str(self.venv.expanduser().resolve())
|
os.environ["VIRTUAL_ENV"] = str(self.venv.expanduser().resolve())
|
||||||
|
upgrade_pip(venv)
|
||||||
|
|
||||||
def get(self) -> tuple[Path, Path]:
|
def get(self) -> tuple[Path, Path]:
|
||||||
"""
|
"""
|
||||||
@@ -212,54 +183,7 @@ class InvokeAiInstance:
|
|||||||
|
|
||||||
def install(self, extra_index_url=None, optional_modules=None, find_links=None):
|
def install(self, extra_index_url=None, optional_modules=None, find_links=None):
|
||||||
"""
|
"""
|
||||||
Install this instance, including dependencies and the app itself
|
Install the package from PyPi.
|
||||||
|
|
||||||
:param extra_index_url: the "--extra-index-url ..." line for pip to look in extra indexes.
|
|
||||||
:type extra_index_url: str
|
|
||||||
"""
|
|
||||||
|
|
||||||
import messages
|
|
||||||
|
|
||||||
# install torch first to ensure the correct version gets installed.
|
|
||||||
# works with either source or wheel install with negligible impact on installation times.
|
|
||||||
messages.simple_banner("Installing PyTorch :fire:")
|
|
||||||
self.install_torch(extra_index_url, find_links)
|
|
||||||
|
|
||||||
messages.simple_banner("Installing the InvokeAI Application :art:")
|
|
||||||
self.install_app(extra_index_url, optional_modules, find_links)
|
|
||||||
|
|
||||||
def install_torch(self, extra_index_url=None, find_links=None):
|
|
||||||
"""
|
|
||||||
Install PyTorch
|
|
||||||
"""
|
|
||||||
|
|
||||||
from plumbum import FG, local
|
|
||||||
|
|
||||||
pip = local[self.pip]
|
|
||||||
|
|
||||||
(
|
|
||||||
pip[
|
|
||||||
"install",
|
|
||||||
"--require-virtualenv",
|
|
||||||
"numpy~=1.24.0", # choose versions that won't be uninstalled during phase 2
|
|
||||||
"urllib3~=1.26.0",
|
|
||||||
"requests~=2.28.0",
|
|
||||||
"torch~=2.1.0",
|
|
||||||
"torchmetrics==0.11.4",
|
|
||||||
"torchvision>=0.14.1",
|
|
||||||
"--force-reinstall",
|
|
||||||
"--find-links" if find_links is not None else None,
|
|
||||||
find_links,
|
|
||||||
"--extra-index-url" if extra_index_url is not None else None,
|
|
||||||
extra_index_url,
|
|
||||||
]
|
|
||||||
& FG
|
|
||||||
)
|
|
||||||
|
|
||||||
def install_app(self, extra_index_url=None, optional_modules=None, find_links=None):
|
|
||||||
"""
|
|
||||||
Install the application with pip.
|
|
||||||
Supports installation from PyPi or from a local source directory.
|
|
||||||
|
|
||||||
:param extra_index_url: the "--extra-index-url ..." line for pip to look in extra indexes.
|
:param extra_index_url: the "--extra-index-url ..." line for pip to look in extra indexes.
|
||||||
:type extra_index_url: str
|
:type extra_index_url: str
|
||||||
@@ -271,53 +195,52 @@ class InvokeAiInstance:
|
|||||||
:type find_links: Path
|
:type find_links: Path
|
||||||
"""
|
"""
|
||||||
|
|
||||||
## this only applies to pypi installs; TODO actually use this
|
import messages
|
||||||
if self.version == "pre":
|
|
||||||
|
# not currently used, but may be useful for "install most recent version" option
|
||||||
|
if self.version == "prerelease":
|
||||||
version = None
|
version = None
|
||||||
pre = "--pre"
|
pre_flag = "--pre"
|
||||||
|
elif self.version == "stable":
|
||||||
|
version = None
|
||||||
|
pre_flag = None
|
||||||
else:
|
else:
|
||||||
version = self.version
|
version = self.version
|
||||||
pre = None
|
pre_flag = None
|
||||||
|
|
||||||
## TODO: only local wheel will be installed as of now; support for --version arg is TODO
|
src = "invokeai"
|
||||||
if FF_USE_LOCAL_WHEEL:
|
if optional_modules:
|
||||||
# if no wheel, try to do a source install before giving up
|
src += optional_modules
|
||||||
try:
|
if version:
|
||||||
src = str(next(Path(__file__).parent.glob("InvokeAI-*.whl")))
|
src += f"=={version}"
|
||||||
except StopIteration:
|
|
||||||
try:
|
|
||||||
src = Path(__file__).parents[1].expanduser().resolve()
|
|
||||||
# if the above directory contains one of these files, we'll do a source install
|
|
||||||
next(src.glob("pyproject.toml"))
|
|
||||||
next(src.glob("invokeai"))
|
|
||||||
except StopIteration:
|
|
||||||
print("Unable to find a wheel or perform a source install. Giving up.")
|
|
||||||
|
|
||||||
elif version == "source":
|
messages.simple_banner("Installing the InvokeAI Application :art:")
|
||||||
# this makes an assumption about the location of the installer package in the source tree
|
|
||||||
src = Path(__file__).parents[1].expanduser().resolve()
|
|
||||||
else:
|
|
||||||
# will install from PyPi
|
|
||||||
src = f"invokeai=={version}" if version is not None else "invokeai"
|
|
||||||
|
|
||||||
from plumbum import FG, local
|
from plumbum import FG, ProcessExecutionError, local # type: ignore
|
||||||
|
|
||||||
pip = local[self.pip]
|
pip = local[self.pip]
|
||||||
|
|
||||||
(
|
pipeline = pip[
|
||||||
pip[
|
"install",
|
||||||
"install",
|
"--require-virtualenv",
|
||||||
"--require-virtualenv",
|
"--force-reinstall",
|
||||||
"--use-pep517",
|
"--use-pep517",
|
||||||
str(src) + (optional_modules if optional_modules else ""),
|
str(src),
|
||||||
"--find-links" if find_links is not None else None,
|
"--find-links" if find_links is not None else None,
|
||||||
find_links,
|
find_links,
|
||||||
"--extra-index-url" if extra_index_url is not None else None,
|
"--extra-index-url" if extra_index_url is not None else None,
|
||||||
extra_index_url,
|
extra_index_url,
|
||||||
pre,
|
pre_flag,
|
||||||
]
|
]
|
||||||
& FG
|
|
||||||
)
|
try:
|
||||||
|
_ = pipeline & FG
|
||||||
|
except ProcessExecutionError as e:
|
||||||
|
print(f"Error: {e}")
|
||||||
|
print(
|
||||||
|
"Could not install InvokeAI. Please try downloading the latest version of the installer and install again."
|
||||||
|
)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
def configure(self):
|
def configure(self):
|
||||||
"""
|
"""
|
||||||
@@ -373,7 +296,6 @@ class InvokeAiInstance:
|
|||||||
|
|
||||||
ext = "bat" if OS == "Windows" else "sh"
|
ext = "bat" if OS == "Windows" else "sh"
|
||||||
|
|
||||||
# scripts = ['invoke', 'update']
|
|
||||||
scripts = ["invoke"]
|
scripts = ["invoke"]
|
||||||
|
|
||||||
for script in scripts:
|
for script in scripts:
|
||||||
@@ -408,6 +330,23 @@ def get_pip_from_venv(venv_path: Path) -> str:
|
|||||||
return str(venv_path.expanduser().resolve() / pip)
|
return str(venv_path.expanduser().resolve() / pip)
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade_pip(venv_path: Path) -> str | None:
|
||||||
|
"""
|
||||||
|
Upgrade the pip executable in the given virtual environment
|
||||||
|
"""
|
||||||
|
|
||||||
|
python = "Scripts\\python.exe" if OS == "Windows" else "bin/python"
|
||||||
|
python = str(venv_path.expanduser().resolve() / python)
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = subprocess.check_output([python, "-m", "pip", "install", "--upgrade", "pip"]).decode()
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
print(e)
|
||||||
|
result = None
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
def set_sys_path(venv_path: Path) -> None:
|
def set_sys_path(venv_path: Path) -> None:
|
||||||
"""
|
"""
|
||||||
Given a path to a virtual environment, set the sys.path, in a cross-platform fashion,
|
Given a path to a virtual environment, set the sys.path, in a cross-platform fashion,
|
||||||
@@ -431,7 +370,43 @@ def set_sys_path(venv_path: Path) -> None:
|
|||||||
sys.path.append(str(Path(venv_path, lib, "site-packages").expanduser().resolve()))
|
sys.path.append(str(Path(venv_path, lib, "site-packages").expanduser().resolve()))
|
||||||
|
|
||||||
|
|
||||||
def get_torch_source() -> (Union[str, None], str):
|
def get_github_releases() -> tuple[list, list] | None:
|
||||||
|
"""
|
||||||
|
Query Github for published (pre-)release versions.
|
||||||
|
Return a tuple where the first element is a list of stable releases and the second element is a list of pre-releases.
|
||||||
|
Return None if the query fails for any reason.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
## get latest releases using github api
|
||||||
|
url = "https://api.github.com/repos/invoke-ai/InvokeAI/releases"
|
||||||
|
releases, pre_releases = [], []
|
||||||
|
try:
|
||||||
|
res = requests.get(url)
|
||||||
|
res.raise_for_status()
|
||||||
|
tag_info = res.json()
|
||||||
|
for tag in tag_info:
|
||||||
|
if not tag["prerelease"]:
|
||||||
|
releases.append(tag["tag_name"].lstrip("v"))
|
||||||
|
else:
|
||||||
|
pre_releases.append(tag["tag_name"].lstrip("v"))
|
||||||
|
except requests.HTTPError as e:
|
||||||
|
print(f"Error: {e}")
|
||||||
|
print("Could not fetch version information from GitHub. Please check your network connection and try again.")
|
||||||
|
return
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error: {e}")
|
||||||
|
print("An unexpected error occurred while trying to fetch version information from GitHub. Please try again.")
|
||||||
|
return
|
||||||
|
|
||||||
|
releases.sort(reverse=True)
|
||||||
|
pre_releases.sort(reverse=True)
|
||||||
|
|
||||||
|
return releases, pre_releases
|
||||||
|
|
||||||
|
|
||||||
|
def get_torch_source() -> Tuple[str | None, str | None]:
|
||||||
"""
|
"""
|
||||||
Determine the extra index URL for pip to use for torch installation.
|
Determine the extra index URL for pip to use for torch installation.
|
||||||
This depends on the OS and the graphics accelerator in use.
|
This depends on the OS and the graphics accelerator in use.
|
||||||
@@ -446,25 +421,26 @@ def get_torch_source() -> (Union[str, None], str):
|
|||||||
:rtype: list
|
:rtype: list
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from messages import graphical_accelerator
|
from messages import select_gpu
|
||||||
|
|
||||||
# device can be one of: "cuda", "rocm", "cpu", "idk"
|
# device can be one of: "cuda", "rocm", "cpu", "cuda_and_dml, autodetect"
|
||||||
device = graphical_accelerator()
|
device = select_gpu()
|
||||||
|
|
||||||
url = None
|
url = None
|
||||||
optional_modules = "[onnx]"
|
optional_modules = "[onnx]"
|
||||||
if OS == "Linux":
|
if OS == "Linux":
|
||||||
if device == "rocm":
|
if device.value == "rocm":
|
||||||
url = "https://download.pytorch.org/whl/rocm5.4.2"
|
url = "https://download.pytorch.org/whl/rocm5.6"
|
||||||
elif device == "cpu":
|
elif device.value == "cpu":
|
||||||
url = "https://download.pytorch.org/whl/cpu"
|
url = "https://download.pytorch.org/whl/cpu"
|
||||||
|
|
||||||
if device == "cuda":
|
elif OS == "Windows":
|
||||||
url = "https://download.pytorch.org/whl/cu121"
|
if device.value == "cuda":
|
||||||
optional_modules = "[xformers,onnx-cuda]"
|
url = "https://download.pytorch.org/whl/cu121"
|
||||||
if device == "cuda_and_dml":
|
optional_modules = "[xformers,onnx-cuda]"
|
||||||
url = "https://download.pytorch.org/whl/cu121"
|
if device.value == "cuda_and_dml":
|
||||||
optional_modules = "[xformers,onnx-directml]"
|
url = "https://download.pytorch.org/whl/cu121"
|
||||||
|
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
|
||||||
|
|
||||||
|
|||||||
@@ -5,10 +5,11 @@ Installer user interaction
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import platform
|
import platform
|
||||||
|
from enum import Enum
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from prompt_toolkit import HTML, prompt
|
from prompt_toolkit import HTML, prompt
|
||||||
from prompt_toolkit.completion import PathCompleter
|
from prompt_toolkit.completion import FuzzyWordCompleter, PathCompleter
|
||||||
from prompt_toolkit.validation import Validator
|
from prompt_toolkit.validation import Validator
|
||||||
from rich import box, print
|
from rich import box, print
|
||||||
from rich.console import Console, Group, group
|
from rich.console import Console, Group, group
|
||||||
@@ -35,16 +36,26 @@ else:
|
|||||||
console = Console(style=Style(color="grey74", bgcolor="grey19"))
|
console = Console(style=Style(color="grey74", bgcolor="grey19"))
|
||||||
|
|
||||||
|
|
||||||
def welcome():
|
def welcome(available_releases: tuple | None = None) -> None:
|
||||||
@group()
|
@group()
|
||||||
def text():
|
def text():
|
||||||
if (platform_specific := _platform_specific_help()) != "":
|
if (platform_specific := _platform_specific_help()) is not None:
|
||||||
yield platform_specific
|
yield platform_specific
|
||||||
yield ""
|
yield ""
|
||||||
yield Text.from_markup(
|
yield Text.from_markup(
|
||||||
"Some of the installation steps take a long time to run. Please be patient. If the script appears to hang for more than 10 minutes, please interrupt with [i]Control-C[/] and retry.",
|
"Some of the installation steps take a long time to run. Please be patient. If the script appears to hang for more than 10 minutes, please interrupt with [i]Control-C[/] and retry.",
|
||||||
justify="center",
|
justify="center",
|
||||||
)
|
)
|
||||||
|
if available_releases is not None:
|
||||||
|
latest_stable = available_releases[0][0]
|
||||||
|
last_pre = available_releases[1][0]
|
||||||
|
yield ""
|
||||||
|
yield Text.from_markup(
|
||||||
|
f"[red3]🠶[/] Latest stable release (recommended): [b bright_white]{latest_stable}", justify="center"
|
||||||
|
)
|
||||||
|
yield Text.from_markup(
|
||||||
|
f"[red3]🠶[/] Last published pre-release version: [b bright_white]{last_pre}", justify="center"
|
||||||
|
)
|
||||||
|
|
||||||
console.rule()
|
console.rule()
|
||||||
print(
|
print(
|
||||||
@@ -61,19 +72,30 @@ def welcome():
|
|||||||
console.line()
|
console.line()
|
||||||
|
|
||||||
|
|
||||||
def confirm_install(dest: Path) -> bool:
|
def choose_version(available_releases: tuple | None = None) -> str:
|
||||||
if dest.exists():
|
"""
|
||||||
print(f":exclamation: Directory {dest} already exists :exclamation:")
|
Prompt the user to choose an Invoke version to install
|
||||||
dest_confirmed = Confirm.ask(
|
"""
|
||||||
":stop_sign: (re)install in this location?",
|
|
||||||
default=False,
|
# short circuit if we couldn't get a version list
|
||||||
)
|
# still try to install the latest stable version
|
||||||
else:
|
if available_releases is None:
|
||||||
print(f"InvokeAI will be installed in {dest}")
|
return "stable"
|
||||||
dest_confirmed = Confirm.ask("Use this location?", default=True)
|
|
||||||
|
console.print(":grey_question: [orange3]Please choose an Invoke version to install.")
|
||||||
|
|
||||||
|
choices = available_releases[0] + available_releases[1]
|
||||||
|
|
||||||
|
response = prompt(
|
||||||
|
message=f" <Enter> to install the recommended release ({choices[0]}). <Tab> or type to pick a version: ",
|
||||||
|
complete_while_typing=True,
|
||||||
|
completer=FuzzyWordCompleter(choices),
|
||||||
|
)
|
||||||
|
console.print(f" Version {choices[0] if response == '' else response} will be installed.")
|
||||||
|
|
||||||
console.line()
|
console.line()
|
||||||
|
|
||||||
return dest_confirmed
|
return "stable" if response == "" else response
|
||||||
|
|
||||||
|
|
||||||
def user_wants_auto_configuration() -> bool:
|
def user_wants_auto_configuration() -> bool:
|
||||||
@@ -109,7 +131,23 @@ def user_wants_auto_configuration() -> bool:
|
|||||||
return choice.lower().startswith("a")
|
return choice.lower().startswith("a")
|
||||||
|
|
||||||
|
|
||||||
def dest_path(dest=None) -> Path:
|
def confirm_install(dest: Path) -> bool:
|
||||||
|
if dest.exists():
|
||||||
|
print(f":stop_sign: Directory {dest} already exists!")
|
||||||
|
print(" Is this location correct?")
|
||||||
|
default = False
|
||||||
|
else:
|
||||||
|
print(f":file_folder: InvokeAI will be installed in {dest}")
|
||||||
|
default = True
|
||||||
|
|
||||||
|
dest_confirmed = Confirm.ask(" Please confirm:", default=default)
|
||||||
|
|
||||||
|
console.line()
|
||||||
|
|
||||||
|
return dest_confirmed
|
||||||
|
|
||||||
|
|
||||||
|
def dest_path(dest=None) -> Path | None:
|
||||||
"""
|
"""
|
||||||
Prompt the user for the destination path and create the path
|
Prompt the user for the destination path and create the path
|
||||||
|
|
||||||
@@ -124,25 +162,21 @@ def dest_path(dest=None) -> Path:
|
|||||||
else:
|
else:
|
||||||
dest = Path.cwd().expanduser().resolve()
|
dest = Path.cwd().expanduser().resolve()
|
||||||
prev_dest = init_path = dest
|
prev_dest = init_path = dest
|
||||||
|
dest_confirmed = False
|
||||||
dest_confirmed = confirm_install(dest)
|
|
||||||
|
|
||||||
while not dest_confirmed:
|
while not dest_confirmed:
|
||||||
# if the given destination already exists, the starting point for browsing is its parent directory.
|
browse_start = (dest or Path.cwd()).expanduser().resolve()
|
||||||
# the user may have made a typo, or otherwise wants to place the root dir next to an existing one.
|
|
||||||
# if the destination dir does NOT exist, then the user must have changed their mind about the selection.
|
|
||||||
# since we can't read their mind, start browsing at Path.cwd().
|
|
||||||
browse_start = (prev_dest.parent if prev_dest.exists() else Path.cwd()).expanduser().resolve()
|
|
||||||
|
|
||||||
path_completer = PathCompleter(
|
path_completer = PathCompleter(
|
||||||
only_directories=True,
|
only_directories=True,
|
||||||
expanduser=True,
|
expanduser=True,
|
||||||
get_paths=lambda: [browse_start], # noqa: B023
|
get_paths=lambda: [str(browse_start)], # noqa: B023
|
||||||
# get_paths=lambda: [".."].extend(list(browse_start.iterdir()))
|
# get_paths=lambda: [".."].extend(list(browse_start.iterdir()))
|
||||||
)
|
)
|
||||||
|
|
||||||
console.line()
|
console.line()
|
||||||
console.print(f"[orange3]Please select the destination directory for the installation:[/] \\[{browse_start}]: ")
|
|
||||||
|
console.print(f":grey_question: [orange3]Please select the install destination:[/] \\[{browse_start}]: ")
|
||||||
selected = prompt(
|
selected = prompt(
|
||||||
">>> ",
|
">>> ",
|
||||||
complete_in_thread=True,
|
complete_in_thread=True,
|
||||||
@@ -155,6 +189,7 @@ def dest_path(dest=None) -> Path:
|
|||||||
)
|
)
|
||||||
prev_dest = dest
|
prev_dest = dest
|
||||||
dest = Path(selected)
|
dest = Path(selected)
|
||||||
|
|
||||||
console.line()
|
console.line()
|
||||||
|
|
||||||
dest_confirmed = confirm_install(dest.expanduser().resolve())
|
dest_confirmed = confirm_install(dest.expanduser().resolve())
|
||||||
@@ -182,41 +217,45 @@ def dest_path(dest=None) -> Path:
|
|||||||
console.rule("Goodbye!")
|
console.rule("Goodbye!")
|
||||||
|
|
||||||
|
|
||||||
def graphical_accelerator():
|
class GpuType(Enum):
|
||||||
|
CUDA = "cuda"
|
||||||
|
CUDA_AND_DML = "cuda_and_dml"
|
||||||
|
ROCM = "rocm"
|
||||||
|
CPU = "cpu"
|
||||||
|
AUTODETECT = "autodetect"
|
||||||
|
|
||||||
|
|
||||||
|
def select_gpu() -> GpuType:
|
||||||
"""
|
"""
|
||||||
Prompt the user to select the graphical accelerator in their system
|
Prompt the user to select the GPU driver
|
||||||
This does not validate user's choices (yet), but only offers choices
|
|
||||||
valid for the platform.
|
|
||||||
CUDA is the fallback.
|
|
||||||
We may be able to detect the GPU driver by shelling out to `modprobe` or `lspci`,
|
|
||||||
but this is not yet supported or reliable. Also, some users may have exotic preferences.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if ARCH == "arm64" and OS != "Darwin":
|
if ARCH == "arm64" and OS != "Darwin":
|
||||||
print(f"Only CPU acceleration is available on {ARCH} architecture. Proceeding with that.")
|
print(f"Only CPU acceleration is available on {ARCH} architecture. Proceeding with that.")
|
||||||
return "cpu"
|
return GpuType.CPU
|
||||||
|
|
||||||
nvidia = (
|
nvidia = (
|
||||||
"an [gold1 b]NVIDIA[/] GPU (using CUDA™)",
|
"an [gold1 b]NVIDIA[/] GPU (using CUDA™)",
|
||||||
"cuda",
|
GpuType.CUDA,
|
||||||
)
|
)
|
||||||
nvidia_with_dml = (
|
nvidia_with_dml = (
|
||||||
"an [gold1 b]NVIDIA[/] GPU (using CUDA™, and DirectML™ for ONNX) -- ALPHA",
|
"an [gold1 b]NVIDIA[/] GPU (using CUDA™, and DirectML™ for ONNX) -- ALPHA",
|
||||||
"cuda_and_dml",
|
GpuType.CUDA_AND_DML,
|
||||||
)
|
)
|
||||||
amd = (
|
amd = (
|
||||||
"an [gold1 b]AMD[/] GPU (using ROCm™)",
|
"an [gold1 b]AMD[/] GPU (using ROCm™)",
|
||||||
"rocm",
|
GpuType.ROCM,
|
||||||
)
|
)
|
||||||
cpu = (
|
cpu = (
|
||||||
"no compatible GPU, or specifically prefer to use the CPU",
|
"Do not install any GPU support, use CPU for generation (slow)",
|
||||||
"cpu",
|
GpuType.CPU,
|
||||||
)
|
)
|
||||||
idk = (
|
autodetect = (
|
||||||
"I'm not sure what to choose",
|
"I'm not sure what to choose",
|
||||||
"idk",
|
GpuType.AUTODETECT,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
options = []
|
||||||
if OS == "Windows":
|
if OS == "Windows":
|
||||||
options = [nvidia, nvidia_with_dml, cpu]
|
options = [nvidia, nvidia_with_dml, cpu]
|
||||||
if OS == "Linux":
|
if OS == "Linux":
|
||||||
@@ -230,7 +269,7 @@ def graphical_accelerator():
|
|||||||
return options[0][1]
|
return options[0][1]
|
||||||
|
|
||||||
# "I don't know" is always added the last option
|
# "I don't know" is always added the last option
|
||||||
options.append(idk)
|
options.append(autodetect) # type: ignore
|
||||||
|
|
||||||
options = {str(i): opt for i, opt in enumerate(options, 1)}
|
options = {str(i): opt for i, opt in enumerate(options, 1)}
|
||||||
|
|
||||||
@@ -265,9 +304,9 @@ def graphical_accelerator():
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
if options[choice][1] == "idk":
|
if options[choice][1] is GpuType.AUTODETECT:
|
||||||
console.print(
|
console.print(
|
||||||
"No problem. We will try to install a version that [i]should[/i] be compatible. :crossed_fingers:"
|
"No problem. We will install CUDA support first :crossed_fingers: If Invoke does not detect a GPU, please re-run the installer and select one of the other GPU types."
|
||||||
)
|
)
|
||||||
|
|
||||||
return options[choice][1]
|
return options[choice][1]
|
||||||
@@ -291,7 +330,7 @@ def windows_long_paths_registry() -> None:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
with open(str(Path(__file__).parent / "WinLongPathsEnabled.reg"), "r", encoding="utf-16le") as code:
|
with open(str(Path(__file__).parent / "WinLongPathsEnabled.reg"), "r", encoding="utf-16le") as code:
|
||||||
syntax = Syntax(code.read(), line_numbers=True)
|
syntax = Syntax(code.read(), line_numbers=True, lexer="regedit")
|
||||||
|
|
||||||
console.print(
|
console.print(
|
||||||
Panel(
|
Panel(
|
||||||
@@ -301,7 +340,7 @@ def windows_long_paths_registry() -> None:
|
|||||||
"We will now apply a registry fix to enable long paths on Windows. InvokeAI needs this to function correctly. We are asking your permission to modify the Windows Registry on your behalf.",
|
"We will now apply a registry fix to enable long paths on Windows. InvokeAI needs this to function correctly. We are asking your permission to modify the Windows Registry on your behalf.",
|
||||||
"",
|
"",
|
||||||
"This is the change that will be applied:",
|
"This is the change that will be applied:",
|
||||||
syntax,
|
str(syntax),
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
@@ -340,7 +379,7 @@ def introduction() -> None:
|
|||||||
console.line(2)
|
console.line(2)
|
||||||
|
|
||||||
|
|
||||||
def _platform_specific_help() -> str:
|
def _platform_specific_help() -> Text | None:
|
||||||
if OS == "Darwin":
|
if OS == "Darwin":
|
||||||
text = Text.from_markup(
|
text = Text.from_markup(
|
||||||
"""[b wheat1]macOS Users![/]\n\nPlease be sure you have the [b wheat1]Xcode command-line tools[/] installed before continuing.\nIf not, cancel with [i]Control-C[/] and follow the Xcode install instructions at [deep_sky_blue1]https://www.freecodecamp.org/news/install-xcode-command-line-tools/[/]."""
|
"""[b wheat1]macOS Users![/]\n\nPlease be sure you have the [b wheat1]Xcode command-line tools[/] installed before continuing.\nIf not, cancel with [i]Control-C[/] and follow the Xcode install instructions at [deep_sky_blue1]https://www.freecodecamp.org/news/install-xcode-command-line-tools/[/]."""
|
||||||
@@ -354,5 +393,5 @@ def _platform_specific_help() -> str:
|
|||||||
[deep_sky_blue1]https://learn.microsoft.com/en-US/cpp/windows/latest-supported-vc-redist?view=msvc-170[/]"""
|
[deep_sky_blue1]https://learn.microsoft.com/en-US/cpp/windows/latest-supported-vc-redist?view=msvc-170[/]"""
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
text = ""
|
return
|
||||||
return text
|
return text
|
||||||
|
|||||||
71
installer/tag_release.sh
Executable file
@@ -0,0 +1,71 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
BCYAN="\e[1;36m"
|
||||||
|
BYELLOW="\e[1;33m"
|
||||||
|
BGREEN="\e[1;32m"
|
||||||
|
BRED="\e[1;31m"
|
||||||
|
RED="\e[31m"
|
||||||
|
RESET="\e[0m"
|
||||||
|
|
||||||
|
function does_tag_exist {
|
||||||
|
git rev-parse --quiet --verify "refs/tags/$1" >/dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
function git_show_ref {
|
||||||
|
git show-ref --dereference $1 --abbrev 7
|
||||||
|
}
|
||||||
|
|
||||||
|
function git_show {
|
||||||
|
git show -s --format='%h %s' $1
|
||||||
|
}
|
||||||
|
|
||||||
|
VERSION=$(
|
||||||
|
cd ..
|
||||||
|
python -c "from invokeai.version import __version__ as version; print(version)"
|
||||||
|
)
|
||||||
|
PATCH=""
|
||||||
|
MAJOR_VERSION=$(echo $VERSION | sed 's/\..*$//')
|
||||||
|
VERSION="v${VERSION}${PATCH}"
|
||||||
|
LATEST_TAG="v${MAJOR_VERSION}-latest"
|
||||||
|
|
||||||
|
if does_tag_exist $VERSION; then
|
||||||
|
echo -e "${BCYAN}${VERSION}${RESET} already exists:"
|
||||||
|
git_show_ref tags/$VERSION
|
||||||
|
echo
|
||||||
|
fi
|
||||||
|
if does_tag_exist $LATEST_TAG; then
|
||||||
|
echo -e "${BCYAN}${LATEST_TAG}${RESET} already exists:"
|
||||||
|
git_show_ref tags/$LATEST_TAG
|
||||||
|
echo
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "${BGREEN}HEAD${RESET}:"
|
||||||
|
git_show
|
||||||
|
echo
|
||||||
|
|
||||||
|
echo -e -n "Create tags ${BCYAN}${VERSION}${RESET} and ${BCYAN}${LATEST_TAG}${RESET} @ ${BGREEN}HEAD${RESET}, ${RED}deleting existing tags on remote${RESET}? "
|
||||||
|
read -e -p 'y/n [n]: ' input
|
||||||
|
RESPONSE=${input:='n'}
|
||||||
|
if [ "$RESPONSE" == 'y' ]; then
|
||||||
|
echo
|
||||||
|
echo -e "Deleting ${BCYAN}${VERSION}${RESET} tag on remote..."
|
||||||
|
git push --delete origin $VERSION
|
||||||
|
|
||||||
|
echo -e "Tagging ${BGREEN}HEAD${RESET} with ${BCYAN}${VERSION}${RESET} locally..."
|
||||||
|
if ! git tag -fa $VERSION; then
|
||||||
|
echo "Existing/invalid tag"
|
||||||
|
exit -1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "Deleting ${BCYAN}${LATEST_TAG}${RESET} tag on remote..."
|
||||||
|
git push --delete origin $LATEST_TAG
|
||||||
|
|
||||||
|
echo -e "Tagging ${BGREEN}HEAD${RESET} with ${BCYAN}${LATEST_TAG}${RESET} locally..."
|
||||||
|
git tag -fa $LATEST_TAG
|
||||||
|
|
||||||
|
echo -e "Pushing updated tags to remote..."
|
||||||
|
git push origin --tags
|
||||||
|
fi
|
||||||
|
exit 0
|
||||||
@@ -15,7 +15,7 @@ echo 4. Download and install models
|
|||||||
echo 5. Change InvokeAI startup options
|
echo 5. Change InvokeAI startup options
|
||||||
echo 6. Re-run the configure script to fix a broken install or to complete a major upgrade
|
echo 6. Re-run the configure script to fix a broken install or to complete a major upgrade
|
||||||
echo 7. Open the developer console
|
echo 7. Open the developer console
|
||||||
echo 8. Update InvokeAI
|
echo 8. Update InvokeAI (DEPRECATED - please use the installer)
|
||||||
echo 9. Run the InvokeAI image database maintenance script
|
echo 9. Run the InvokeAI image database maintenance script
|
||||||
echo 10. Command-line help
|
echo 10. Command-line help
|
||||||
echo Q - Quit
|
echo Q - Quit
|
||||||
@@ -52,8 +52,10 @@ IF /I "%choice%" == "1" (
|
|||||||
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%" == "8" (
|
) ELSE IF /I "%choice%" == "8" (
|
||||||
echo Running invokeai-update...
|
echo UPDATING FROM WITHIN THE APP IS BEING DEPRECATED.
|
||||||
python -m invokeai.frontend.install.invokeai_update
|
echo Please download the installer from https://github.com/invoke-ai/InvokeAI/releases/latest and run it to update your installation.
|
||||||
|
timeout 4
|
||||||
|
python -m invokeai.frontend.install.invokeai_update
|
||||||
) ELSE IF /I "%choice%" == "9" (
|
) 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
|
||||||
@@ -77,4 +79,3 @@ pause
|
|||||||
|
|
||||||
:ending
|
:ending
|
||||||
exit /b
|
exit /b
|
||||||
|
|
||||||
|
|||||||
@@ -90,7 +90,9 @@ do_choice() {
|
|||||||
;;
|
;;
|
||||||
8)
|
8)
|
||||||
clear
|
clear
|
||||||
printf "Update InvokeAI\n"
|
printf "UPDATING FROM WITHIN THE APP IS BEING DEPRECATED\n"
|
||||||
|
printf "Please download the installer from https://github.com/invoke-ai/InvokeAI/releases/latest and run it to update your installation.\n"
|
||||||
|
sleep 4
|
||||||
python -m invokeai.frontend.install.invokeai_update
|
python -m invokeai.frontend.install.invokeai_update
|
||||||
;;
|
;;
|
||||||
9)
|
9)
|
||||||
@@ -122,7 +124,7 @@ do_dialog() {
|
|||||||
5 "Change InvokeAI startup options"
|
5 "Change InvokeAI startup options"
|
||||||
6 "Re-run the configure script to fix a broken install or to complete a major upgrade"
|
6 "Re-run the configure script to fix a broken install or to complete a major upgrade"
|
||||||
7 "Open the developer console"
|
7 "Open the developer console"
|
||||||
8 "Update InvokeAI"
|
8 "Update InvokeAI (DEPRECATED - please use the installer)"
|
||||||
9 "Run the InvokeAI image database maintenance script"
|
9 "Run the InvokeAI image database maintenance script"
|
||||||
10 "Command-line help"
|
10 "Command-line help"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,72 +0,0 @@
|
|||||||
@echo off
|
|
||||||
setlocal EnableExtensions EnableDelayedExpansion
|
|
||||||
|
|
||||||
PUSHD "%~dp0"
|
|
||||||
|
|
||||||
set INVOKE_AI_VERSION=latest
|
|
||||||
set arg=%1
|
|
||||||
if "%arg%" neq "" (
|
|
||||||
if "%arg:~0,2%" equ "/?" (
|
|
||||||
echo Usage: update.bat ^<release name or branch^>
|
|
||||||
echo Updates InvokeAI to use the indicated version of the code base.
|
|
||||||
echo Find the version or branch for the release you want, and pass it as the argument.
|
|
||||||
echo For example '.\update.bat v2.2.5' for release 2.2.5.
|
|
||||||
echo '.\update.bat main' for the latest development version
|
|
||||||
echo.
|
|
||||||
echo If no argument provided then will install the most recent release, equivalent to
|
|
||||||
echo '.\update.bat latest'
|
|
||||||
exit /b
|
|
||||||
) else (
|
|
||||||
set INVOKE_AI_VERSION=%arg%
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
set INVOKE_AI_SRC="https://github.com/invoke-ai/InvokeAI/archive/!INVOKE_AI_VERSION!.zip"
|
|
||||||
set INVOKE_AI_DEP=https://raw.githubusercontent.com/invoke-ai/InvokeAI/!INVOKE_AI_VERSION!/environments-and-requirements/requirements-base.txt
|
|
||||||
set INVOKE_AI_MODELS=https://raw.githubusercontent.com/invoke-ai/InvokeAI/$INVOKE_AI_VERSION/configs/INITIAL_MODELS.yaml
|
|
||||||
|
|
||||||
call curl -I "%INVOKE_AI_DEP%" -fs >.tmp.out
|
|
||||||
if %errorlevel% neq 0 (
|
|
||||||
echo '!INVOKE_AI_VERSION!' is not a known branch name or tag. Please check the version and try again.
|
|
||||||
echo "Press any key to continue"
|
|
||||||
pause
|
|
||||||
exit /b
|
|
||||||
)
|
|
||||||
del .tmp.out
|
|
||||||
|
|
||||||
echo This script will update InvokeAI and all its dependencies to !INVOKE_AI_SRC!.
|
|
||||||
echo If you do not want to do this, press control-C now!
|
|
||||||
pause
|
|
||||||
|
|
||||||
call curl -L "%INVOKE_AI_DEP%" > environments-and-requirements/requirements-base.txt
|
|
||||||
call curl -L "%INVOKE_AI_MODELS%" > configs/INITIAL_MODELS.yaml
|
|
||||||
|
|
||||||
|
|
||||||
call .venv\Scripts\activate.bat
|
|
||||||
call .venv\Scripts\python -mpip install -r requirements.txt
|
|
||||||
if %errorlevel% neq 0 (
|
|
||||||
echo Installation of requirements failed. See https://invoke-ai.github.io/InvokeAI/installation/INSTALL_AUTOMATED/#troubleshooting for suggestions.
|
|
||||||
pause
|
|
||||||
exit /b
|
|
||||||
)
|
|
||||||
|
|
||||||
call .venv\Scripts\python -mpip install !INVOKE_AI_SRC!
|
|
||||||
if %errorlevel% neq 0 (
|
|
||||||
echo Installation of InvokeAI failed. See https://invoke-ai.github.io/InvokeAI/installation/INSTALL_AUTOMATED/#troubleshooting for suggestions.
|
|
||||||
pause
|
|
||||||
exit /b
|
|
||||||
)
|
|
||||||
|
|
||||||
@rem call .venv\Scripts\invokeai-configure --root=.
|
|
||||||
|
|
||||||
@rem if %errorlevel% neq 0 (
|
|
||||||
@rem echo Configuration InvokeAI failed. See https://invoke-ai.github.io/InvokeAI/installation/INSTALL_AUTOMATED/#troubleshooting for suggestions.
|
|
||||||
@rem pause
|
|
||||||
@rem exit /b
|
|
||||||
@rem )
|
|
||||||
|
|
||||||
echo InvokeAI has been updated to '%INVOKE_AI_VERSION%'
|
|
||||||
|
|
||||||
echo "Press any key to continue"
|
|
||||||
pause
|
|
||||||
endlocal
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
set -eu
|
|
||||||
|
|
||||||
if [ $# -ge 1 ] && [ "${1:0:2}" == "-h" ]; then
|
|
||||||
echo "Usage: update.sh <release>"
|
|
||||||
echo "Updates InvokeAI to use the indicated version of the code base."
|
|
||||||
echo "Find the version or branch for the release you want, and pass it as the argument."
|
|
||||||
echo "For example: update.sh v2.2.5 for release 2.2.5."
|
|
||||||
echo " update.sh main for the current development version."
|
|
||||||
echo ""
|
|
||||||
echo "If no argument provided then will install the version tagged with 'latest', equivalent to"
|
|
||||||
echo "update.sh latest"
|
|
||||||
exit -1
|
|
||||||
fi
|
|
||||||
|
|
||||||
INVOKE_AI_VERSION=${1:-latest}
|
|
||||||
|
|
||||||
INVOKE_AI_SRC="https://github.com/invoke-ai/InvokeAI/archive/$INVOKE_AI_VERSION.zip"
|
|
||||||
INVOKE_AI_DEP=https://raw.githubusercontent.com/invoke-ai/InvokeAI/$INVOKE_AI_VERSION/environments-and-requirements/requirements-base.txt
|
|
||||||
INVOKE_AI_MODELS=https://raw.githubusercontent.com/invoke-ai/InvokeAI/$INVOKE_AI_VERSION/configs/INITIAL_MODELS.yaml
|
|
||||||
|
|
||||||
# ensure we're in the correct folder in case user's CWD is somewhere else
|
|
||||||
scriptdir=$(dirname "$0")
|
|
||||||
cd "$scriptdir"
|
|
||||||
|
|
||||||
function _err_exit {
|
|
||||||
if test "$1" -ne 0
|
|
||||||
then
|
|
||||||
echo "Something went wrong while installing InvokeAI and/or its requirements."
|
|
||||||
echo "Update cannot continue. Please report this error to https://github.com/invoke-ai/InvokeAI/issues"
|
|
||||||
echo -e "Error code $1; Error caught was '$2'"
|
|
||||||
read -p "Press any key to exit..."
|
|
||||||
exit
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
if ! curl -I "$INVOKE_AI_DEP" -fs >/dev/null; then
|
|
||||||
echo \'$INVOKE_AI_VERSION\' is not a known branch name or tag. Please check the version and try again.
|
|
||||||
exit
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo This script will update InvokeAI and all its dependencies to version \'$INVOKE_AI_VERSION\'.
|
|
||||||
echo If you do not want to do this, press control-C now!
|
|
||||||
read -p "Press any key to continue, or CTRL-C to exit..."
|
|
||||||
|
|
||||||
curl -L "$INVOKE_AI_DEP" > environments-and-requirements/requirements-base.txt
|
|
||||||
curl -L "$INVOKE_AI_MODELS" > configs/INITIAL_MODELS.yaml
|
|
||||||
|
|
||||||
. .venv/bin/activate
|
|
||||||
|
|
||||||
./.venv/bin/python -mpip install -r requirements.txt
|
|
||||||
_err_exit $? "The pip program failed to install InvokeAI's requirements."
|
|
||||||
|
|
||||||
./.venv/bin/python -mpip install $INVOKE_AI_SRC
|
|
||||||
_err_exit $? "The pip program failed to install InvokeAI."
|
|
||||||
|
|
||||||
echo InvokeAI updated to \'$INVOKE_AI_VERSION\'
|
|
||||||
@@ -2,7 +2,14 @@
|
|||||||
|
|
||||||
from logging import Logger
|
from logging import Logger
|
||||||
|
|
||||||
from invokeai.app.services.workflow_image_records.workflow_image_records_sqlite import SqliteWorkflowImageRecordsStorage
|
import torch
|
||||||
|
|
||||||
|
from invokeai.app.services.item_storage.item_storage_memory import ItemStorageMemory
|
||||||
|
from invokeai.app.services.object_serializer.object_serializer_disk import ObjectSerializerDisk
|
||||||
|
from invokeai.app.services.object_serializer.object_serializer_forward_cache import ObjectSerializerForwardCache
|
||||||
|
from invokeai.app.services.shared.sqlite.sqlite_util import init_db
|
||||||
|
from invokeai.backend.model_manager.metadata import ModelMetadataStore
|
||||||
|
from invokeai.backend.stable_diffusion.diffusion.conditioning_data import ConditioningFieldData
|
||||||
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__
|
||||||
|
|
||||||
@@ -11,6 +18,7 @@ from ..services.board_images.board_images_default import BoardImagesService
|
|||||||
from ..services.board_records.board_records_sqlite import SqliteBoardRecordStorage
|
from ..services.board_records.board_records_sqlite import SqliteBoardRecordStorage
|
||||||
from ..services.boards.boards_default import BoardService
|
from ..services.boards.boards_default import BoardService
|
||||||
from ..services.config import InvokeAIAppConfig
|
from ..services.config import InvokeAIAppConfig
|
||||||
|
from ..services.download import DownloadQueueService
|
||||||
from ..services.image_files.image_files_disk import DiskImageFileStorage
|
from ..services.image_files.image_files_disk import DiskImageFileStorage
|
||||||
from ..services.image_records.image_records_sqlite import SqliteImageRecordStorage
|
from ..services.image_records.image_records_sqlite import SqliteImageRecordStorage
|
||||||
from ..services.images.images_default import ImageService
|
from ..services.images.images_default import ImageService
|
||||||
@@ -20,17 +28,13 @@ from ..services.invocation_queue.invocation_queue_memory import MemoryInvocation
|
|||||||
from ..services.invocation_services import InvocationServices
|
from ..services.invocation_services import InvocationServices
|
||||||
from ..services.invocation_stats.invocation_stats_default import InvocationStatsService
|
from ..services.invocation_stats.invocation_stats_default import InvocationStatsService
|
||||||
from ..services.invoker import Invoker
|
from ..services.invoker import Invoker
|
||||||
from ..services.item_storage.item_storage_sqlite import SqliteItemStorage
|
from ..services.model_install import ModelInstallService
|
||||||
from ..services.latents_storage.latents_storage_disk import DiskLatentsStorage
|
|
||||||
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.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
|
||||||
from ..services.shared.default_graphs import create_system_graphs
|
from ..services.shared.graph import GraphExecutionState
|
||||||
from ..services.shared.graph import GraphExecutionState, LibraryGraph
|
|
||||||
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 ..services.workflow_records.workflow_records_sqlite import SqliteWorkflowRecordsStorage
|
||||||
from .events import FastAPIEventService
|
from .events import FastAPIEventService
|
||||||
@@ -61,14 +65,18 @@ class ApiDependencies:
|
|||||||
invoker: Invoker
|
invoker: Invoker
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def initialize(config: InvokeAIAppConfig, event_handler_id: int, logger: Logger = logger):
|
def initialize(config: InvokeAIAppConfig, event_handler_id: int, logger: Logger = logger) -> None:
|
||||||
logger.info(f"InvokeAI version {__version__}")
|
logger.info(f"InvokeAI version {__version__}")
|
||||||
logger.info(f"Root directory = {str(config.root_path)}")
|
logger.info(f"Root directory = {str(config.root_path)}")
|
||||||
logger.debug(f"Internet connectivity is {config.internet_available}")
|
logger.debug(f"Internet connectivity is {config.internet_available}")
|
||||||
|
|
||||||
output_folder = config.output_path
|
output_folder = config.output_path
|
||||||
|
if output_folder is None:
|
||||||
|
raise ValueError("Output folder is not set")
|
||||||
|
|
||||||
db = SqliteDatabase(config, logger)
|
image_files = DiskImageFileStorage(f"{output_folder}/images")
|
||||||
|
|
||||||
|
db = init_db(config=config, logger=logger, image_files=image_files)
|
||||||
|
|
||||||
configuration = config
|
configuration = config
|
||||||
logger = logger
|
logger = logger
|
||||||
@@ -78,15 +86,27 @@ class ApiDependencies:
|
|||||||
board_records = SqliteBoardRecordStorage(db=db)
|
board_records = SqliteBoardRecordStorage(db=db)
|
||||||
boards = BoardService()
|
boards = BoardService()
|
||||||
events = FastAPIEventService(event_handler_id)
|
events = FastAPIEventService(event_handler_id)
|
||||||
graph_execution_manager = SqliteItemStorage[GraphExecutionState](db=db, table_name="graph_executions")
|
graph_execution_manager = ItemStorageMemory[GraphExecutionState]()
|
||||||
graph_library = SqliteItemStorage[LibraryGraph](db=db, table_name="graphs")
|
|
||||||
image_files = DiskImageFileStorage(f"{output_folder}/images")
|
|
||||||
image_records = SqliteImageRecordStorage(db=db)
|
image_records = SqliteImageRecordStorage(db=db)
|
||||||
images = ImageService()
|
images = ImageService()
|
||||||
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"))
|
tensors = ObjectSerializerForwardCache(
|
||||||
|
ObjectSerializerDisk[torch.Tensor](output_folder / "tensors", ephemeral=True)
|
||||||
|
)
|
||||||
|
conditioning = ObjectSerializerForwardCache(
|
||||||
|
ObjectSerializerDisk[ConditioningFieldData](output_folder / "conditioning", ephemeral=True)
|
||||||
|
)
|
||||||
model_manager = ModelManagerService(config, logger)
|
model_manager = ModelManagerService(config, logger)
|
||||||
model_record_service = ModelRecordServiceSQL(db=db)
|
model_record_service = ModelRecordServiceSQL(db=db)
|
||||||
|
download_queue_service = DownloadQueueService(event_bus=events)
|
||||||
|
metadata_store = ModelMetadataStore(db=db)
|
||||||
|
model_install_service = ModelInstallService(
|
||||||
|
app_config=config,
|
||||||
|
record_store=model_record_service,
|
||||||
|
download_queue=download_queue_service,
|
||||||
|
metadata_store=metadata_store,
|
||||||
|
event_bus=events,
|
||||||
|
)
|
||||||
names = SimpleNameService()
|
names = SimpleNameService()
|
||||||
performance_statistics = InvocationStatsService()
|
performance_statistics = InvocationStatsService()
|
||||||
processor = DefaultInvocationProcessor()
|
processor = DefaultInvocationProcessor()
|
||||||
@@ -94,7 +114,6 @@ 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)
|
workflow_records = SqliteWorkflowRecordsStorage(db=db)
|
||||||
|
|
||||||
services = InvocationServices(
|
services = InvocationServices(
|
||||||
@@ -105,15 +124,15 @@ class ApiDependencies:
|
|||||||
configuration=configuration,
|
configuration=configuration,
|
||||||
events=events,
|
events=events,
|
||||||
graph_execution_manager=graph_execution_manager,
|
graph_execution_manager=graph_execution_manager,
|
||||||
graph_library=graph_library,
|
|
||||||
image_files=image_files,
|
image_files=image_files,
|
||||||
image_records=image_records,
|
image_records=image_records,
|
||||||
images=images,
|
images=images,
|
||||||
invocation_cache=invocation_cache,
|
invocation_cache=invocation_cache,
|
||||||
latents=latents,
|
|
||||||
logger=logger,
|
logger=logger,
|
||||||
model_manager=model_manager,
|
model_manager=model_manager,
|
||||||
model_records=model_record_service,
|
model_records=model_record_service,
|
||||||
|
download_queue=download_queue_service,
|
||||||
|
model_install=model_install_service,
|
||||||
names=names,
|
names=names,
|
||||||
performance_statistics=performance_statistics,
|
performance_statistics=performance_statistics,
|
||||||
processor=processor,
|
processor=processor,
|
||||||
@@ -121,17 +140,15 @@ 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,
|
workflow_records=workflow_records,
|
||||||
|
tensors=tensors,
|
||||||
|
conditioning=conditioning,
|
||||||
)
|
)
|
||||||
|
|
||||||
create_system_graphs(services.graph_library)
|
|
||||||
|
|
||||||
ApiDependencies.invoker = Invoker(services)
|
ApiDependencies.invoker = Invoker(services)
|
||||||
|
|
||||||
db.clean()
|
db.clean()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def shutdown():
|
def shutdown() -> None:
|
||||||
if ApiDependencies.invoker:
|
if ApiDependencies.invoker:
|
||||||
ApiDependencies.invoker.stop()
|
ApiDependencies.invoker.stop()
|
||||||
|
|||||||
28
invokeai/app/api/no_cache_staticfiles.py
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from starlette.responses import Response
|
||||||
|
from starlette.staticfiles import StaticFiles
|
||||||
|
|
||||||
|
|
||||||
|
class NoCacheStaticFiles(StaticFiles):
|
||||||
|
"""
|
||||||
|
This class is used to override the default caching behavior of starlette for static files,
|
||||||
|
ensuring we *never* cache static files. It modifies the file response headers to strictly
|
||||||
|
never cache the files.
|
||||||
|
|
||||||
|
Static files include the javascript bundles, fonts, locales, and some images. Generated
|
||||||
|
images are not included, as they are served by a router.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, *args: Any, **kwargs: Any):
|
||||||
|
self.cachecontrol = "max-age=0, no-cache, no-store, , must-revalidate"
|
||||||
|
self.pragma = "no-cache"
|
||||||
|
self.expires = "0"
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
def file_response(self, *args: Any, **kwargs: Any) -> Response:
|
||||||
|
resp = super().file_response(*args, **kwargs)
|
||||||
|
resp.headers.setdefault("Cache-Control", self.cachecontrol)
|
||||||
|
resp.headers.setdefault("Pragma", self.pragma)
|
||||||
|
resp.headers.setdefault("Expires", self.expires)
|
||||||
|
return resp
|
||||||
@@ -1,7 +1,11 @@
|
|||||||
import typing
|
import typing
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
from importlib.metadata import PackageNotFoundError, version
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from platform import python_version
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
import torch
|
||||||
from fastapi import Body
|
from fastapi import Body
|
||||||
from fastapi.routing import APIRouter
|
from fastapi.routing import APIRouter
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
@@ -40,6 +44,24 @@ class AppVersion(BaseModel):
|
|||||||
version: str = Field(description="App version")
|
version: str = Field(description="App version")
|
||||||
|
|
||||||
|
|
||||||
|
class AppDependencyVersions(BaseModel):
|
||||||
|
"""App depencency Versions Response"""
|
||||||
|
|
||||||
|
accelerate: str = Field(description="accelerate version")
|
||||||
|
compel: str = Field(description="compel version")
|
||||||
|
cuda: Optional[str] = Field(description="CUDA version")
|
||||||
|
diffusers: str = Field(description="diffusers version")
|
||||||
|
numpy: str = Field(description="Numpy version")
|
||||||
|
opencv: str = Field(description="OpenCV version")
|
||||||
|
onnx: str = Field(description="ONNX version")
|
||||||
|
pillow: str = Field(description="Pillow (PIL) version")
|
||||||
|
python: str = Field(description="Python version")
|
||||||
|
torch: str = Field(description="PyTorch version")
|
||||||
|
torchvision: str = Field(description="PyTorch Vision version")
|
||||||
|
transformers: str = Field(description="transformers version")
|
||||||
|
xformers: Optional[str] = Field(description="xformers version")
|
||||||
|
|
||||||
|
|
||||||
class AppConfig(BaseModel):
|
class AppConfig(BaseModel):
|
||||||
"""App Config Response"""
|
"""App Config Response"""
|
||||||
|
|
||||||
@@ -54,6 +76,29 @@ async def get_version() -> AppVersion:
|
|||||||
return AppVersion(version=__version__)
|
return AppVersion(version=__version__)
|
||||||
|
|
||||||
|
|
||||||
|
@app_router.get("/app_deps", operation_id="get_app_deps", status_code=200, response_model=AppDependencyVersions)
|
||||||
|
async def get_app_deps() -> AppDependencyVersions:
|
||||||
|
try:
|
||||||
|
xformers = version("xformers")
|
||||||
|
except PackageNotFoundError:
|
||||||
|
xformers = None
|
||||||
|
return AppDependencyVersions(
|
||||||
|
accelerate=version("accelerate"),
|
||||||
|
compel=version("compel"),
|
||||||
|
cuda=torch.version.cuda,
|
||||||
|
diffusers=version("diffusers"),
|
||||||
|
numpy=version("numpy"),
|
||||||
|
opencv=version("opencv-python"),
|
||||||
|
onnx=version("onnx"),
|
||||||
|
pillow=version("pillow"),
|
||||||
|
python=python_version(),
|
||||||
|
torch=torch.version.__version__,
|
||||||
|
torchvision=version("torchvision"),
|
||||||
|
transformers=version("transformers"),
|
||||||
|
xformers=xformers,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@app_router.get("/config", operation_id="get_config", status_code=200, response_model=AppConfig)
|
@app_router.get("/config", operation_id="get_config", status_code=200, response_model=AppConfig)
|
||||||
async def get_config() -> AppConfig:
|
async def get_config() -> AppConfig:
|
||||||
infill_methods = ["tile", "lama", "cv2"]
|
infill_methods = ["tile", "lama", "cv2"]
|
||||||
|
|||||||
111
invokeai/app/api/routers/download_queue.py
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
# Copyright (c) 2023 Lincoln D. Stein
|
||||||
|
"""FastAPI route for the download queue."""
|
||||||
|
|
||||||
|
from typing import List, Optional
|
||||||
|
|
||||||
|
from fastapi import Body, Path, Response
|
||||||
|
from fastapi.routing import APIRouter
|
||||||
|
from pydantic.networks import AnyHttpUrl
|
||||||
|
from starlette.exceptions import HTTPException
|
||||||
|
|
||||||
|
from invokeai.app.services.download import (
|
||||||
|
DownloadJob,
|
||||||
|
UnknownJobIDException,
|
||||||
|
)
|
||||||
|
|
||||||
|
from ..dependencies import ApiDependencies
|
||||||
|
|
||||||
|
download_queue_router = APIRouter(prefix="/v1/download_queue", tags=["download_queue"])
|
||||||
|
|
||||||
|
|
||||||
|
@download_queue_router.get(
|
||||||
|
"/",
|
||||||
|
operation_id="list_downloads",
|
||||||
|
)
|
||||||
|
async def list_downloads() -> List[DownloadJob]:
|
||||||
|
"""Get a list of active and inactive jobs."""
|
||||||
|
queue = ApiDependencies.invoker.services.download_queue
|
||||||
|
return queue.list_jobs()
|
||||||
|
|
||||||
|
|
||||||
|
@download_queue_router.patch(
|
||||||
|
"/",
|
||||||
|
operation_id="prune_downloads",
|
||||||
|
responses={
|
||||||
|
204: {"description": "All completed jobs have been pruned"},
|
||||||
|
400: {"description": "Bad request"},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
async def prune_downloads():
|
||||||
|
"""Prune completed and errored jobs."""
|
||||||
|
queue = ApiDependencies.invoker.services.download_queue
|
||||||
|
queue.prune_jobs()
|
||||||
|
return Response(status_code=204)
|
||||||
|
|
||||||
|
|
||||||
|
@download_queue_router.post(
|
||||||
|
"/i/",
|
||||||
|
operation_id="download",
|
||||||
|
)
|
||||||
|
async def download(
|
||||||
|
source: AnyHttpUrl = Body(description="download source"),
|
||||||
|
dest: str = Body(description="download destination"),
|
||||||
|
priority: int = Body(default=10, description="queue priority"),
|
||||||
|
access_token: Optional[str] = Body(default=None, description="token for authorization to download"),
|
||||||
|
) -> DownloadJob:
|
||||||
|
"""Download the source URL to the file or directory indicted in dest."""
|
||||||
|
queue = ApiDependencies.invoker.services.download_queue
|
||||||
|
return queue.download(source, dest, priority, access_token)
|
||||||
|
|
||||||
|
|
||||||
|
@download_queue_router.get(
|
||||||
|
"/i/{id}",
|
||||||
|
operation_id="get_download_job",
|
||||||
|
responses={
|
||||||
|
200: {"description": "Success"},
|
||||||
|
404: {"description": "The requested download JobID could not be found"},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
async def get_download_job(
|
||||||
|
id: int = Path(description="ID of the download job to fetch."),
|
||||||
|
) -> DownloadJob:
|
||||||
|
"""Get a download job using its ID."""
|
||||||
|
try:
|
||||||
|
job = ApiDependencies.invoker.services.download_queue.id_to_job(id)
|
||||||
|
return job
|
||||||
|
except UnknownJobIDException as e:
|
||||||
|
raise HTTPException(status_code=404, detail=str(e))
|
||||||
|
|
||||||
|
|
||||||
|
@download_queue_router.delete(
|
||||||
|
"/i/{id}",
|
||||||
|
operation_id="cancel_download_job",
|
||||||
|
responses={
|
||||||
|
204: {"description": "Job has been cancelled"},
|
||||||
|
404: {"description": "The requested download JobID could not be found"},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
async def cancel_download_job(
|
||||||
|
id: int = Path(description="ID of the download job to cancel."),
|
||||||
|
):
|
||||||
|
"""Cancel a download job using its ID."""
|
||||||
|
try:
|
||||||
|
queue = ApiDependencies.invoker.services.download_queue
|
||||||
|
job = queue.id_to_job(id)
|
||||||
|
queue.cancel_job(job)
|
||||||
|
return Response(status_code=204)
|
||||||
|
except UnknownJobIDException as e:
|
||||||
|
raise HTTPException(status_code=404, detail=str(e))
|
||||||
|
|
||||||
|
|
||||||
|
@download_queue_router.delete(
|
||||||
|
"/i",
|
||||||
|
operation_id="cancel_all_download_jobs",
|
||||||
|
responses={
|
||||||
|
204: {"description": "Download jobs have been cancelled"},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
async def cancel_all_download_jobs():
|
||||||
|
"""Cancel all download jobs."""
|
||||||
|
ApiDependencies.invoker.services.download_queue.cancel_all_jobs()
|
||||||
|
return Response(status_code=204)
|
||||||
@@ -8,10 +8,11 @@ from fastapi.routing import APIRouter
|
|||||||
from PIL import Image
|
from PIL import Image
|
||||||
from pydantic import BaseModel, Field, ValidationError
|
from pydantic import BaseModel, Field, ValidationError
|
||||||
|
|
||||||
from invokeai.app.invocations.baseinvocation import MetadataField, MetadataFieldValidator, WorkflowFieldValidator
|
from invokeai.app.invocations.fields import MetadataField, MetadataFieldValidator
|
||||||
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
|
||||||
|
from invokeai.app.services.workflow_records.workflow_records_common import WorkflowWithoutID, WorkflowWithoutIDValidator
|
||||||
|
|
||||||
from ..dependencies import ApiDependencies
|
from ..dependencies import ApiDependencies
|
||||||
|
|
||||||
@@ -73,7 +74,7 @@ async def upload_image(
|
|||||||
workflow_raw = pil_image.info.get("invokeai_workflow", None)
|
workflow_raw = pil_image.info.get("invokeai_workflow", None)
|
||||||
if workflow_raw is not None:
|
if workflow_raw is not None:
|
||||||
try:
|
try:
|
||||||
workflow = WorkflowFieldValidator.validate_json(workflow_raw)
|
workflow = WorkflowWithoutIDValidator.validate_json(workflow_raw)
|
||||||
except ValidationError:
|
except ValidationError:
|
||||||
ApiDependencies.invoker.services.logger.warn("Failed to parse metadata for uploaded image")
|
ApiDependencies.invoker.services.logger.warn("Failed to parse metadata for uploaded image")
|
||||||
pass
|
pass
|
||||||
@@ -184,6 +185,18 @@ async def get_image_metadata(
|
|||||||
raise HTTPException(status_code=404)
|
raise HTTPException(status_code=404)
|
||||||
|
|
||||||
|
|
||||||
|
@images_router.get(
|
||||||
|
"/i/{image_name}/workflow", operation_id="get_image_workflow", response_model=Optional[WorkflowWithoutID]
|
||||||
|
)
|
||||||
|
async def get_image_workflow(
|
||||||
|
image_name: str = Path(description="The name of image whose workflow to get"),
|
||||||
|
) -> Optional[WorkflowWithoutID]:
|
||||||
|
try:
|
||||||
|
return ApiDependencies.invoker.services.images.get_workflow(image_name)
|
||||||
|
except Exception:
|
||||||
|
raise HTTPException(status_code=404)
|
||||||
|
|
||||||
|
|
||||||
@images_router.api_route(
|
@images_router.api_route(
|
||||||
"/i/{image_name}/full",
|
"/i/{image_name}/full",
|
||||||
methods=["GET", "HEAD"],
|
methods=["GET", "HEAD"],
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
# Copyright (c) 2023 Lincoln D. Stein
|
# Copyright (c) 2023 Lincoln D. Stein
|
||||||
"""FastAPI route for model configuration records."""
|
"""FastAPI route for model configuration records."""
|
||||||
|
|
||||||
|
import pathlib
|
||||||
from hashlib import sha1
|
from hashlib import sha1
|
||||||
from random import randbytes
|
from random import randbytes
|
||||||
from typing import List, Optional
|
from typing import Any, Dict, List, Optional, Set
|
||||||
|
|
||||||
from fastapi import Body, Path, Query, Response
|
from fastapi import Body, Path, Query, Response
|
||||||
from fastapi.routing import APIRouter
|
from fastapi.routing import APIRouter
|
||||||
@@ -12,30 +12,46 @@ from pydantic import BaseModel, ConfigDict
|
|||||||
from starlette.exceptions import HTTPException
|
from starlette.exceptions import HTTPException
|
||||||
from typing_extensions import Annotated
|
from typing_extensions import Annotated
|
||||||
|
|
||||||
|
from invokeai.app.services.model_install import ModelInstallJob, ModelSource
|
||||||
from invokeai.app.services.model_records import (
|
from invokeai.app.services.model_records import (
|
||||||
DuplicateModelException,
|
DuplicateModelException,
|
||||||
InvalidModelException,
|
InvalidModelException,
|
||||||
|
ModelRecordOrderBy,
|
||||||
|
ModelSummary,
|
||||||
UnknownModelException,
|
UnknownModelException,
|
||||||
)
|
)
|
||||||
|
from invokeai.app.services.shared.pagination import PaginatedResults
|
||||||
from invokeai.backend.model_manager.config import (
|
from invokeai.backend.model_manager.config import (
|
||||||
AnyModelConfig,
|
AnyModelConfig,
|
||||||
BaseModelType,
|
BaseModelType,
|
||||||
|
ModelFormat,
|
||||||
ModelType,
|
ModelType,
|
||||||
)
|
)
|
||||||
|
from invokeai.backend.model_manager.merge import MergeInterpolationMethod, ModelMerger
|
||||||
|
from invokeai.backend.model_manager.metadata import AnyModelRepoMetadata
|
||||||
|
|
||||||
from ..dependencies import ApiDependencies
|
from ..dependencies import ApiDependencies
|
||||||
|
|
||||||
model_records_router = APIRouter(prefix="/v1/model/record", tags=["models"])
|
model_records_router = APIRouter(prefix="/v1/model/record", tags=["model_manager_v2_unstable"])
|
||||||
|
|
||||||
|
|
||||||
class ModelsList(BaseModel):
|
class ModelsList(BaseModel):
|
||||||
"""Return list of configs."""
|
"""Return list of configs."""
|
||||||
|
|
||||||
models: list[AnyModelConfig]
|
models: List[AnyModelConfig]
|
||||||
|
|
||||||
model_config = ConfigDict(use_enum_values=True)
|
model_config = ConfigDict(use_enum_values=True)
|
||||||
|
|
||||||
|
|
||||||
|
class ModelTagSet(BaseModel):
|
||||||
|
"""Return tags for a set of models."""
|
||||||
|
|
||||||
|
key: str
|
||||||
|
name: str
|
||||||
|
author: str
|
||||||
|
tags: Set[str]
|
||||||
|
|
||||||
|
|
||||||
@model_records_router.get(
|
@model_records_router.get(
|
||||||
"/",
|
"/",
|
||||||
operation_id="list_model_records",
|
operation_id="list_model_records",
|
||||||
@@ -43,15 +59,25 @@ class ModelsList(BaseModel):
|
|||||||
async def list_model_records(
|
async def list_model_records(
|
||||||
base_models: Optional[List[BaseModelType]] = Query(default=None, description="Base models to include"),
|
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"),
|
model_type: Optional[ModelType] = Query(default=None, description="The type of model to get"),
|
||||||
|
model_name: Optional[str] = Query(default=None, description="Exact match on the name of the model"),
|
||||||
|
model_format: Optional[ModelFormat] = Query(
|
||||||
|
default=None, description="Exact match on the format of the model (e.g. 'diffusers')"
|
||||||
|
),
|
||||||
) -> ModelsList:
|
) -> ModelsList:
|
||||||
"""Get a list of models."""
|
"""Get a list of models."""
|
||||||
record_store = ApiDependencies.invoker.services.model_records
|
record_store = ApiDependencies.invoker.services.model_records
|
||||||
found_models: list[AnyModelConfig] = []
|
found_models: list[AnyModelConfig] = []
|
||||||
if base_models:
|
if base_models:
|
||||||
for base_model in base_models:
|
for base_model in base_models:
|
||||||
found_models.extend(record_store.search_by_attr(base_model=base_model, model_type=model_type))
|
found_models.extend(
|
||||||
|
record_store.search_by_attr(
|
||||||
|
base_model=base_model, model_type=model_type, model_name=model_name, model_format=model_format
|
||||||
|
)
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
found_models.extend(record_store.search_by_attr(model_type=model_type))
|
found_models.extend(
|
||||||
|
record_store.search_by_attr(model_type=model_type, model_name=model_name, model_format=model_format)
|
||||||
|
)
|
||||||
return ModelsList(models=found_models)
|
return ModelsList(models=found_models)
|
||||||
|
|
||||||
|
|
||||||
@@ -75,6 +101,59 @@ async def get_model_record(
|
|||||||
raise HTTPException(status_code=404, detail=str(e))
|
raise HTTPException(status_code=404, detail=str(e))
|
||||||
|
|
||||||
|
|
||||||
|
@model_records_router.get("/meta", operation_id="list_model_summary")
|
||||||
|
async def list_model_summary(
|
||||||
|
page: int = Query(default=0, description="The page to get"),
|
||||||
|
per_page: int = Query(default=10, description="The number of models per page"),
|
||||||
|
order_by: ModelRecordOrderBy = Query(default=ModelRecordOrderBy.Default, description="The attribute to order by"),
|
||||||
|
) -> PaginatedResults[ModelSummary]:
|
||||||
|
"""Gets a page of model summary data."""
|
||||||
|
return ApiDependencies.invoker.services.model_records.list_models(page=page, per_page=per_page, order_by=order_by)
|
||||||
|
|
||||||
|
|
||||||
|
@model_records_router.get(
|
||||||
|
"/meta/i/{key}",
|
||||||
|
operation_id="get_model_metadata",
|
||||||
|
responses={
|
||||||
|
200: {"description": "Success"},
|
||||||
|
400: {"description": "Bad request"},
|
||||||
|
404: {"description": "No metadata available"},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
async def get_model_metadata(
|
||||||
|
key: str = Path(description="Key of the model repo metadata to fetch."),
|
||||||
|
) -> Optional[AnyModelRepoMetadata]:
|
||||||
|
"""Get a model metadata object."""
|
||||||
|
record_store = ApiDependencies.invoker.services.model_records
|
||||||
|
result = record_store.get_metadata(key)
|
||||||
|
if not result:
|
||||||
|
raise HTTPException(status_code=404, detail="No metadata for a model with this key")
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
@model_records_router.get(
|
||||||
|
"/tags",
|
||||||
|
operation_id="list_tags",
|
||||||
|
)
|
||||||
|
async def list_tags() -> Set[str]:
|
||||||
|
"""Get a unique set of all the model tags."""
|
||||||
|
record_store = ApiDependencies.invoker.services.model_records
|
||||||
|
return record_store.list_tags()
|
||||||
|
|
||||||
|
|
||||||
|
@model_records_router.get(
|
||||||
|
"/tags/search",
|
||||||
|
operation_id="search_by_metadata_tags",
|
||||||
|
)
|
||||||
|
async def search_by_metadata_tags(
|
||||||
|
tags: Set[str] = Query(default=None, description="Tags to search for"),
|
||||||
|
) -> ModelsList:
|
||||||
|
"""Get a list of models."""
|
||||||
|
record_store = ApiDependencies.invoker.services.model_records
|
||||||
|
results = record_store.search_by_metadata_tag(tags)
|
||||||
|
return ModelsList(models=results)
|
||||||
|
|
||||||
|
|
||||||
@model_records_router.patch(
|
@model_records_router.patch(
|
||||||
"/i/{key}",
|
"/i/{key}",
|
||||||
operation_id="update_model_record",
|
operation_id="update_model_record",
|
||||||
@@ -117,12 +196,17 @@ async def update_model_record(
|
|||||||
async def del_model_record(
|
async def del_model_record(
|
||||||
key: str = Path(description="Unique key of model to remove from model registry."),
|
key: str = Path(description="Unique key of model to remove from model registry."),
|
||||||
) -> Response:
|
) -> Response:
|
||||||
"""Delete Model"""
|
"""
|
||||||
|
Delete model record from database.
|
||||||
|
|
||||||
|
The configuration record will be removed. The corresponding weights files will be
|
||||||
|
deleted as well if they reside within the InvokeAI "models" directory.
|
||||||
|
"""
|
||||||
logger = ApiDependencies.invoker.services.logger
|
logger = ApiDependencies.invoker.services.logger
|
||||||
|
|
||||||
try:
|
try:
|
||||||
record_store = ApiDependencies.invoker.services.model_records
|
installer = ApiDependencies.invoker.services.model_install
|
||||||
record_store.del_model(key)
|
installer.delete(key)
|
||||||
logger.info(f"Deleted model: {key}")
|
logger.info(f"Deleted model: {key}")
|
||||||
return Response(status_code=204)
|
return Response(status_code=204)
|
||||||
except UnknownModelException as e:
|
except UnknownModelException as e:
|
||||||
@@ -141,11 +225,9 @@ async def del_model_record(
|
|||||||
status_code=201,
|
status_code=201,
|
||||||
)
|
)
|
||||||
async def add_model_record(
|
async def add_model_record(
|
||||||
config: Annotated[AnyModelConfig, Body(description="Model config", discriminator="type")]
|
config: Annotated[AnyModelConfig, Body(description="Model config", discriminator="type")],
|
||||||
) -> AnyModelConfig:
|
) -> AnyModelConfig:
|
||||||
"""
|
"""Add a model using the configuration information appropriate for its type."""
|
||||||
Add a model using the configuration information appropriate for its type.
|
|
||||||
"""
|
|
||||||
logger = ApiDependencies.invoker.services.logger
|
logger = ApiDependencies.invoker.services.logger
|
||||||
record_store = ApiDependencies.invoker.services.model_records
|
record_store = ApiDependencies.invoker.services.model_records
|
||||||
if config.key == "<NOKEY>":
|
if config.key == "<NOKEY>":
|
||||||
@@ -162,3 +244,229 @@ async def add_model_record(
|
|||||||
|
|
||||||
# now fetch it out
|
# now fetch it out
|
||||||
return record_store.get_model(config.key)
|
return record_store.get_model(config.key)
|
||||||
|
|
||||||
|
|
||||||
|
@model_records_router.post(
|
||||||
|
"/import",
|
||||||
|
operation_id="import_model_record",
|
||||||
|
responses={
|
||||||
|
201: {"description": "The model imported successfully"},
|
||||||
|
415: {"description": "Unrecognized file/folder format"},
|
||||||
|
424: {"description": "The model appeared to import successfully, but could not be found in the model manager"},
|
||||||
|
409: {"description": "There is already a model corresponding to this path or repo_id"},
|
||||||
|
},
|
||||||
|
status_code=201,
|
||||||
|
)
|
||||||
|
async def import_model(
|
||||||
|
source: ModelSource,
|
||||||
|
config: Optional[Dict[str, Any]] = Body(
|
||||||
|
description="Dict of fields that override auto-probed values in the model config record, such as name, description and prediction_type ",
|
||||||
|
default=None,
|
||||||
|
),
|
||||||
|
) -> ModelInstallJob:
|
||||||
|
"""Add a model using its local path, repo_id, or remote URL.
|
||||||
|
|
||||||
|
Models will be downloaded, probed, configured and installed in a
|
||||||
|
series of background threads. The return object has `status` attribute
|
||||||
|
that can be used to monitor progress.
|
||||||
|
|
||||||
|
The source object is a discriminated Union of LocalModelSource,
|
||||||
|
HFModelSource and URLModelSource. Set the "type" field to the
|
||||||
|
appropriate value:
|
||||||
|
|
||||||
|
* To install a local path using LocalModelSource, pass a source of form:
|
||||||
|
`{
|
||||||
|
"type": "local",
|
||||||
|
"path": "/path/to/model",
|
||||||
|
"inplace": false
|
||||||
|
}`
|
||||||
|
The "inplace" flag, if true, will register the model in place in its
|
||||||
|
current filesystem location. Otherwise, the model will be copied
|
||||||
|
into the InvokeAI models directory.
|
||||||
|
|
||||||
|
* To install a HuggingFace repo_id using HFModelSource, pass a source of form:
|
||||||
|
`{
|
||||||
|
"type": "hf",
|
||||||
|
"repo_id": "stabilityai/stable-diffusion-2.0",
|
||||||
|
"variant": "fp16",
|
||||||
|
"subfolder": "vae",
|
||||||
|
"access_token": "f5820a918aaf01"
|
||||||
|
}`
|
||||||
|
The `variant`, `subfolder` and `access_token` fields are optional.
|
||||||
|
|
||||||
|
* To install a remote model using an arbitrary URL, pass:
|
||||||
|
`{
|
||||||
|
"type": "url",
|
||||||
|
"url": "http://www.civitai.com/models/123456",
|
||||||
|
"access_token": "f5820a918aaf01"
|
||||||
|
}`
|
||||||
|
The `access_token` field is optonal
|
||||||
|
|
||||||
|
The model's configuration record will be probed and filled in
|
||||||
|
automatically. To override the default guesses, pass "metadata"
|
||||||
|
with a Dict containing the attributes you wish to override.
|
||||||
|
|
||||||
|
Installation occurs in the background. Either use list_model_install_jobs()
|
||||||
|
to poll for completion, or listen on the event bus for the following events:
|
||||||
|
|
||||||
|
"model_install_running"
|
||||||
|
"model_install_completed"
|
||||||
|
"model_install_error"
|
||||||
|
|
||||||
|
On successful completion, the event's payload will contain the field "key"
|
||||||
|
containing the installed ID of the model. On an error, the event's payload
|
||||||
|
will contain the fields "error_type" and "error" describing the nature of the
|
||||||
|
error and its traceback, respectively.
|
||||||
|
|
||||||
|
"""
|
||||||
|
logger = ApiDependencies.invoker.services.logger
|
||||||
|
|
||||||
|
try:
|
||||||
|
installer = ApiDependencies.invoker.services.model_install
|
||||||
|
result: ModelInstallJob = installer.import_model(
|
||||||
|
source=source,
|
||||||
|
config=config,
|
||||||
|
)
|
||||||
|
logger.info(f"Started installation of {source}")
|
||||||
|
except UnknownModelException as e:
|
||||||
|
logger.error(str(e))
|
||||||
|
raise HTTPException(status_code=424, detail=str(e))
|
||||||
|
except InvalidModelException as e:
|
||||||
|
logger.error(str(e))
|
||||||
|
raise HTTPException(status_code=415)
|
||||||
|
except ValueError as e:
|
||||||
|
logger.error(str(e))
|
||||||
|
raise HTTPException(status_code=409, detail=str(e))
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
@model_records_router.get(
|
||||||
|
"/import",
|
||||||
|
operation_id="list_model_install_jobs",
|
||||||
|
)
|
||||||
|
async def list_model_install_jobs() -> List[ModelInstallJob]:
|
||||||
|
"""Return list of model install jobs."""
|
||||||
|
jobs: List[ModelInstallJob] = ApiDependencies.invoker.services.model_install.list_jobs()
|
||||||
|
return jobs
|
||||||
|
|
||||||
|
|
||||||
|
@model_records_router.get(
|
||||||
|
"/import/{id}",
|
||||||
|
operation_id="get_model_install_job",
|
||||||
|
responses={
|
||||||
|
200: {"description": "Success"},
|
||||||
|
404: {"description": "No such job"},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
async def get_model_install_job(id: int = Path(description="Model install id")) -> ModelInstallJob:
|
||||||
|
"""Return model install job corresponding to the given source."""
|
||||||
|
try:
|
||||||
|
return ApiDependencies.invoker.services.model_install.get_job_by_id(id)
|
||||||
|
except ValueError as e:
|
||||||
|
raise HTTPException(status_code=404, detail=str(e))
|
||||||
|
|
||||||
|
|
||||||
|
@model_records_router.delete(
|
||||||
|
"/import/{id}",
|
||||||
|
operation_id="cancel_model_install_job",
|
||||||
|
responses={
|
||||||
|
201: {"description": "The job was cancelled successfully"},
|
||||||
|
415: {"description": "No such job"},
|
||||||
|
},
|
||||||
|
status_code=201,
|
||||||
|
)
|
||||||
|
async def cancel_model_install_job(id: int = Path(description="Model install job ID")) -> None:
|
||||||
|
"""Cancel the model install job(s) corresponding to the given job ID."""
|
||||||
|
installer = ApiDependencies.invoker.services.model_install
|
||||||
|
try:
|
||||||
|
job = installer.get_job_by_id(id)
|
||||||
|
except ValueError as e:
|
||||||
|
raise HTTPException(status_code=415, detail=str(e))
|
||||||
|
installer.cancel_job(job)
|
||||||
|
|
||||||
|
|
||||||
|
@model_records_router.patch(
|
||||||
|
"/import",
|
||||||
|
operation_id="prune_model_install_jobs",
|
||||||
|
responses={
|
||||||
|
204: {"description": "All completed and errored jobs have been pruned"},
|
||||||
|
400: {"description": "Bad request"},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
async def prune_model_install_jobs() -> Response:
|
||||||
|
"""Prune all completed and errored jobs from the install job list."""
|
||||||
|
ApiDependencies.invoker.services.model_install.prune_jobs()
|
||||||
|
return Response(status_code=204)
|
||||||
|
|
||||||
|
|
||||||
|
@model_records_router.patch(
|
||||||
|
"/sync",
|
||||||
|
operation_id="sync_models_to_config",
|
||||||
|
responses={
|
||||||
|
204: {"description": "Model config record database resynced with files on disk"},
|
||||||
|
400: {"description": "Bad request"},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
async def sync_models_to_config() -> Response:
|
||||||
|
"""
|
||||||
|
Traverse the models and autoimport directories.
|
||||||
|
|
||||||
|
Model files without a corresponding
|
||||||
|
record in the database are added. Orphan records without a models file are deleted.
|
||||||
|
"""
|
||||||
|
ApiDependencies.invoker.services.model_install.sync_to_config()
|
||||||
|
return Response(status_code=204)
|
||||||
|
|
||||||
|
|
||||||
|
@model_records_router.put(
|
||||||
|
"/merge",
|
||||||
|
operation_id="merge",
|
||||||
|
)
|
||||||
|
async def merge(
|
||||||
|
keys: List[str] = Body(description="Keys for two to three models to merge", min_length=2, max_length=3),
|
||||||
|
merged_model_name: Optional[str] = Body(description="Name of destination model", default=None),
|
||||||
|
alpha: float = Body(description="Alpha weighting strength to apply to 2d and 3d models", default=0.5),
|
||||||
|
force: bool = Body(
|
||||||
|
description="Force merging of models created with different versions of diffusers",
|
||||||
|
default=False,
|
||||||
|
),
|
||||||
|
interp: Optional[MergeInterpolationMethod] = Body(description="Interpolation method", default=None),
|
||||||
|
merge_dest_directory: Optional[str] = Body(
|
||||||
|
description="Save the merged model to the designated directory (with 'merged_model_name' appended)",
|
||||||
|
default=None,
|
||||||
|
),
|
||||||
|
) -> AnyModelConfig:
|
||||||
|
"""
|
||||||
|
Merge diffusers models.
|
||||||
|
|
||||||
|
keys: List of 2-3 model keys to merge together. All models must use the same base type.
|
||||||
|
merged_model_name: Name for the merged model [Concat model names]
|
||||||
|
alpha: Alpha value (0.0-1.0). Higher values give more weight to the second model [0.5]
|
||||||
|
force: If true, force the merge even if the models were generated by different versions of the diffusers library [False]
|
||||||
|
interp: Interpolation method. One of "weighted_sum", "sigmoid", "inv_sigmoid" or "add_difference" [weighted_sum]
|
||||||
|
merge_dest_directory: Specify a directory to store the merged model in [models directory]
|
||||||
|
"""
|
||||||
|
print(f"here i am, keys={keys}")
|
||||||
|
logger = ApiDependencies.invoker.services.logger
|
||||||
|
try:
|
||||||
|
logger.info(f"Merging models: {keys} into {merge_dest_directory or '<MODELS>'}/{merged_model_name}")
|
||||||
|
dest = pathlib.Path(merge_dest_directory) if merge_dest_directory else None
|
||||||
|
installer = ApiDependencies.invoker.services.model_install
|
||||||
|
merger = ModelMerger(installer)
|
||||||
|
model_names = [installer.record_store.get_model(x).name for x in keys]
|
||||||
|
response = merger.merge_diffusion_models_and_save(
|
||||||
|
model_keys=keys,
|
||||||
|
merged_model_name=merged_model_name or "+".join(model_names),
|
||||||
|
alpha=alpha,
|
||||||
|
interp=interp,
|
||||||
|
force=force,
|
||||||
|
merge_dest_directory=dest,
|
||||||
|
)
|
||||||
|
except UnknownModelException:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=404,
|
||||||
|
detail=f"One or more of the models '{keys}' not found",
|
||||||
|
)
|
||||||
|
except ValueError as e:
|
||||||
|
raise HTTPException(status_code=400, detail=str(e))
|
||||||
|
return response
|
||||||
|
|||||||
@@ -23,10 +23,11 @@ class DynamicPromptsResponse(BaseModel):
|
|||||||
)
|
)
|
||||||
async def parse_dynamicprompts(
|
async def parse_dynamicprompts(
|
||||||
prompt: str = Body(description="The prompt to parse with dynamicprompts"),
|
prompt: str = Body(description="The prompt to parse with dynamicprompts"),
|
||||||
max_prompts: int = Body(default=1000, description="The max number of prompts to generate"),
|
max_prompts: int = Body(ge=1, le=10000, default=1000, description="The max number of prompts to generate"),
|
||||||
combinatorial: bool = Body(default=True, description="Whether to use the combinatorial generator"),
|
combinatorial: bool = Body(default=True, description="Whether to use the combinatorial generator"),
|
||||||
) -> DynamicPromptsResponse:
|
) -> DynamicPromptsResponse:
|
||||||
"""Creates a batch process"""
|
"""Creates a batch process"""
|
||||||
|
max_prompts = min(max_prompts, 10000)
|
||||||
generator: Union[RandomPromptGenerator, CombinatorialPromptGenerator]
|
generator: Union[RandomPromptGenerator, CombinatorialPromptGenerator]
|
||||||
try:
|
try:
|
||||||
error: Optional[str] = None
|
error: Optional[str] = None
|
||||||
|
|||||||
@@ -1,7 +1,19 @@
|
|||||||
from fastapi import APIRouter, Path
|
from typing import Optional
|
||||||
|
|
||||||
|
from fastapi import APIRouter, Body, HTTPException, Path, Query
|
||||||
|
|
||||||
from invokeai.app.api.dependencies import ApiDependencies
|
from invokeai.app.api.dependencies import ApiDependencies
|
||||||
from invokeai.app.invocations.baseinvocation import WorkflowField
|
from invokeai.app.services.shared.pagination import PaginatedResults
|
||||||
|
from invokeai.app.services.shared.sqlite.sqlite_common import SQLiteDirection
|
||||||
|
from invokeai.app.services.workflow_records.workflow_records_common import (
|
||||||
|
Workflow,
|
||||||
|
WorkflowCategory,
|
||||||
|
WorkflowNotFoundError,
|
||||||
|
WorkflowRecordDTO,
|
||||||
|
WorkflowRecordListItemDTO,
|
||||||
|
WorkflowRecordOrderBy,
|
||||||
|
WorkflowWithoutID,
|
||||||
|
)
|
||||||
|
|
||||||
workflows_router = APIRouter(prefix="/v1/workflows", tags=["workflows"])
|
workflows_router = APIRouter(prefix="/v1/workflows", tags=["workflows"])
|
||||||
|
|
||||||
@@ -10,11 +22,76 @@ workflows_router = APIRouter(prefix="/v1/workflows", tags=["workflows"])
|
|||||||
"/i/{workflow_id}",
|
"/i/{workflow_id}",
|
||||||
operation_id="get_workflow",
|
operation_id="get_workflow",
|
||||||
responses={
|
responses={
|
||||||
200: {"model": WorkflowField},
|
200: {"model": WorkflowRecordDTO},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
async def get_workflow(
|
async def get_workflow(
|
||||||
workflow_id: str = Path(description="The workflow to get"),
|
workflow_id: str = Path(description="The workflow to get"),
|
||||||
) -> WorkflowField:
|
) -> WorkflowRecordDTO:
|
||||||
"""Gets a workflow"""
|
"""Gets a workflow"""
|
||||||
return ApiDependencies.invoker.services.workflow_records.get(workflow_id)
|
try:
|
||||||
|
return ApiDependencies.invoker.services.workflow_records.get(workflow_id)
|
||||||
|
except WorkflowNotFoundError:
|
||||||
|
raise HTTPException(status_code=404, detail="Workflow not found")
|
||||||
|
|
||||||
|
|
||||||
|
@workflows_router.patch(
|
||||||
|
"/i/{workflow_id}",
|
||||||
|
operation_id="update_workflow",
|
||||||
|
responses={
|
||||||
|
200: {"model": WorkflowRecordDTO},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
async def update_workflow(
|
||||||
|
workflow: Workflow = Body(description="The updated workflow", embed=True),
|
||||||
|
) -> WorkflowRecordDTO:
|
||||||
|
"""Updates a workflow"""
|
||||||
|
return ApiDependencies.invoker.services.workflow_records.update(workflow=workflow)
|
||||||
|
|
||||||
|
|
||||||
|
@workflows_router.delete(
|
||||||
|
"/i/{workflow_id}",
|
||||||
|
operation_id="delete_workflow",
|
||||||
|
)
|
||||||
|
async def delete_workflow(
|
||||||
|
workflow_id: str = Path(description="The workflow to delete"),
|
||||||
|
) -> None:
|
||||||
|
"""Deletes a workflow"""
|
||||||
|
ApiDependencies.invoker.services.workflow_records.delete(workflow_id)
|
||||||
|
|
||||||
|
|
||||||
|
@workflows_router.post(
|
||||||
|
"/",
|
||||||
|
operation_id="create_workflow",
|
||||||
|
responses={
|
||||||
|
200: {"model": WorkflowRecordDTO},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
async def create_workflow(
|
||||||
|
workflow: WorkflowWithoutID = Body(description="The workflow to create", embed=True),
|
||||||
|
) -> WorkflowRecordDTO:
|
||||||
|
"""Creates a workflow"""
|
||||||
|
return ApiDependencies.invoker.services.workflow_records.create(workflow=workflow)
|
||||||
|
|
||||||
|
|
||||||
|
@workflows_router.get(
|
||||||
|
"/",
|
||||||
|
operation_id="list_workflows",
|
||||||
|
responses={
|
||||||
|
200: {"model": PaginatedResults[WorkflowRecordListItemDTO]},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
async def list_workflows(
|
||||||
|
page: int = Query(default=0, description="The page to get"),
|
||||||
|
per_page: int = Query(default=10, description="The number of workflows per page"),
|
||||||
|
order_by: WorkflowRecordOrderBy = Query(
|
||||||
|
default=WorkflowRecordOrderBy.Name, description="The attribute to order by"
|
||||||
|
),
|
||||||
|
direction: SQLiteDirection = Query(default=SQLiteDirection.Ascending, description="The direction to order by"),
|
||||||
|
category: WorkflowCategory = Query(default=WorkflowCategory.User, description="The category of workflow to get"),
|
||||||
|
query: Optional[str] = Query(default=None, description="The text to query by (matches name and description)"),
|
||||||
|
) -> PaginatedResults[WorkflowRecordListItemDTO]:
|
||||||
|
"""Gets a page of workflows"""
|
||||||
|
return ApiDependencies.invoker.services.workflow_records.get_many(
|
||||||
|
page=page, per_page=per_page, order_by=order_by, direction=direction, query=query, category=category
|
||||||
|
)
|
||||||
|
|||||||
@@ -14,12 +14,13 @@ class SocketIO:
|
|||||||
|
|
||||||
def __init__(self, app: FastAPI):
|
def __init__(self, app: FastAPI):
|
||||||
self.__sio = AsyncServer(async_mode="asgi", cors_allowed_origins="*")
|
self.__sio = AsyncServer(async_mode="asgi", cors_allowed_origins="*")
|
||||||
self.__app = ASGIApp(socketio_server=self.__sio, socketio_path="socket.io")
|
self.__app = ASGIApp(socketio_server=self.__sio, socketio_path="/ws/socket.io")
|
||||||
app.mount("/ws", self.__app)
|
app.mount("/ws", self.__app)
|
||||||
|
|
||||||
self.__sio.on("subscribe_queue", handler=self._handle_sub_queue)
|
self.__sio.on("subscribe_queue", handler=self._handle_sub_queue)
|
||||||
self.__sio.on("unsubscribe_queue", handler=self._handle_unsub_queue)
|
self.__sio.on("unsubscribe_queue", handler=self._handle_unsub_queue)
|
||||||
local_handler.register(event_name=EventServiceBase.queue_event, _func=self._handle_queue_event)
|
local_handler.register(event_name=EventServiceBase.queue_event, _func=self._handle_queue_event)
|
||||||
|
local_handler.register(event_name=EventServiceBase.model_event, _func=self._handle_model_event)
|
||||||
|
|
||||||
async def _handle_queue_event(self, event: Event):
|
async def _handle_queue_event(self, event: Event):
|
||||||
await self.__sio.emit(
|
await self.__sio.emit(
|
||||||
@@ -28,10 +29,13 @@ class SocketIO:
|
|||||||
room=event[1]["data"]["queue_id"],
|
room=event[1]["data"]["queue_id"],
|
||||||
)
|
)
|
||||||
|
|
||||||
async def _handle_sub_queue(self, sid, data, *args, **kwargs):
|
async def _handle_sub_queue(self, sid, data, *args, **kwargs) -> None:
|
||||||
if "queue_id" in data:
|
if "queue_id" in data:
|
||||||
await self.__sio.enter_room(sid, data["queue_id"])
|
await self.__sio.enter_room(sid, data["queue_id"])
|
||||||
|
|
||||||
async def _handle_unsub_queue(self, sid, data, *args, **kwargs):
|
async def _handle_unsub_queue(self, sid, data, *args, **kwargs) -> None:
|
||||||
if "queue_id" in data:
|
if "queue_id" in data:
|
||||||
await self.__sio.leave_room(sid, data["queue_id"])
|
await self.__sio.leave_room(sid, data["queue_id"])
|
||||||
|
|
||||||
|
async def _handle_model_event(self, event: Event) -> None:
|
||||||
|
await self.__sio.emit(event=event[1]["event"], data=event[1]["data"])
|
||||||
|
|||||||
@@ -1,14 +1,19 @@
|
|||||||
from typing import Any
|
|
||||||
|
|
||||||
from fastapi.responses import HTMLResponse
|
|
||||||
|
|
||||||
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
|
||||||
# which are imported/used before parse_args() is called will get the default config values instead of the
|
# 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.
|
# values from the command line or config file.
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from invokeai.app.api.no_cache_staticfiles import NoCacheStaticFiles
|
||||||
|
from invokeai.version.invokeai_version import __version__
|
||||||
|
|
||||||
|
from .invocations.fields import InputFieldJSONSchemaExtra, OutputFieldJSONSchemaExtra
|
||||||
|
from .services.config import InvokeAIAppConfig
|
||||||
|
|
||||||
app_config = InvokeAIAppConfig.get_config()
|
app_config = InvokeAIAppConfig.get_config()
|
||||||
app_config.parse_args()
|
app_config.parse_args()
|
||||||
|
if app_config.version:
|
||||||
|
print(f"InvokeAI version {__version__}")
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
if True: # hack to make flake8 happy with imports coming after setting up the config
|
if True: # hack to make flake8 happy with imports coming after setting up the config
|
||||||
import asyncio
|
import asyncio
|
||||||
@@ -16,6 +21,7 @@ if True: # hack to make flake8 happy with imports coming after setting up the c
|
|||||||
import socket
|
import socket
|
||||||
from inspect import signature
|
from inspect import signature
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
import uvicorn
|
import uvicorn
|
||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
@@ -23,8 +29,7 @@ if True: # hack to make flake8 happy with imports coming after setting up the c
|
|||||||
from fastapi.middleware.gzip import GZipMiddleware
|
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.responses import HTMLResponse
|
||||||
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
|
||||||
@@ -34,7 +39,6 @@ if True: # hack to make flake8 happy with imports coming after setting up the c
|
|||||||
# 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
|
||||||
from invokeai.version.invokeai_version import __version__
|
|
||||||
|
|
||||||
from ..backend.util.logging import InvokeAILogger
|
from ..backend.util.logging import InvokeAILogger
|
||||||
from .api.dependencies import ApiDependencies
|
from .api.dependencies import ApiDependencies
|
||||||
@@ -42,6 +46,7 @@ if True: # hack to make flake8 happy with imports coming after setting up the c
|
|||||||
app_info,
|
app_info,
|
||||||
board_images,
|
board_images,
|
||||||
boards,
|
boards,
|
||||||
|
download_queue,
|
||||||
images,
|
images,
|
||||||
model_records,
|
model_records,
|
||||||
models,
|
models,
|
||||||
@@ -51,7 +56,10 @@ if True: # hack to make flake8 happy with imports coming after setting up the c
|
|||||||
workflows,
|
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,
|
||||||
|
)
|
||||||
|
|
||||||
if is_mps_available():
|
if is_mps_available():
|
||||||
import invokeai.backend.util.mps_fixes # noqa: F401 (monkeypatching on import)
|
import invokeai.backend.util.mps_fixes # noqa: F401 (monkeypatching on import)
|
||||||
@@ -67,7 +75,7 @@ mimetypes.add_type("text/css", ".css")
|
|||||||
|
|
||||||
# Create the app
|
# Create the app
|
||||||
# TODO: create this all in a method so configuration/etc. can be passed in?
|
# TODO: create this all in a method so configuration/etc. can be passed in?
|
||||||
app = FastAPI(title="Invoke AI", docs_url=None, redoc_url=None, separate_input_output_schemas=False)
|
app = FastAPI(title="Invoke - Community Edition", docs_url=None, redoc_url=None, separate_input_output_schemas=False)
|
||||||
|
|
||||||
# Add event handler
|
# Add event handler
|
||||||
event_handler_id: int = id(app)
|
event_handler_id: int = id(app)
|
||||||
@@ -108,6 +116,7 @@ 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(model_records.model_records_router, prefix="/api")
|
||||||
|
app.include_router(download_queue.download_queue_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")
|
||||||
@@ -147,7 +156,11 @@ def custom_openapi() -> dict[str, Any]:
|
|||||||
|
|
||||||
# Add Node Editor UI helper schemas
|
# Add Node Editor UI helper schemas
|
||||||
ui_config_schemas = models_json_schema(
|
ui_config_schemas = models_json_schema(
|
||||||
[(UIConfigBase, "serialization"), (_InputField, "serialization"), (_OutputField, "serialization")],
|
[
|
||||||
|
(UIConfigBase, "serialization"),
|
||||||
|
(InputFieldJSONSchemaExtra, "serialization"),
|
||||||
|
(OutputFieldJSONSchemaExtra, "serialization"),
|
||||||
|
],
|
||||||
ref_template="#/components/schemas/{model}",
|
ref_template="#/components/schemas/{model}",
|
||||||
)
|
)
|
||||||
for schema_key, ui_config_schema in ui_config_schemas[1]["$defs"].items():
|
for schema_key, ui_config_schema in ui_config_schemas[1]["$defs"].items():
|
||||||
@@ -155,7 +168,7 @@ def custom_openapi() -> dict[str, Any]:
|
|||||||
|
|
||||||
# Add a reference to the output type to additionalProperties of the invoker schema
|
# Add a reference to the output type to additionalProperties of the invoker schema
|
||||||
for invoker in all_invocations:
|
for invoker in all_invocations:
|
||||||
invoker_name = invoker.__name__
|
invoker_name = invoker.__name__ # type: ignore [attr-defined] # this is a valid attribute
|
||||||
output_type = signature(obj=invoker.invoke).return_annotation
|
output_type = signature(obj=invoker.invoke).return_annotation
|
||||||
output_type_title = output_type_titles[output_type.__name__]
|
output_type_title = output_type_titles[output_type.__name__]
|
||||||
invoker_schema = openapi_schema["components"]["schemas"][f"{invoker_name}"]
|
invoker_schema = openapi_schema["components"]["schemas"][f"{invoker_name}"]
|
||||||
@@ -191,8 +204,8 @@ app.openapi = custom_openapi # type: ignore [method-assign] # this is a valid a
|
|||||||
def overridden_swagger() -> HTMLResponse:
|
def overridden_swagger() -> HTMLResponse:
|
||||||
return get_swagger_ui_html(
|
return get_swagger_ui_html(
|
||||||
openapi_url=app.openapi_url, # type: ignore [arg-type] # this is always a string
|
openapi_url=app.openapi_url, # type: ignore [arg-type] # this is always a string
|
||||||
title=app.title,
|
title=f"{app.title} - Swagger UI",
|
||||||
swagger_favicon_url="/static/docs/favicon.ico",
|
swagger_favicon_url="static/docs/invoke-favicon-docs.svg",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -200,25 +213,20 @@ def overridden_swagger() -> HTMLResponse:
|
|||||||
def overridden_redoc() -> HTMLResponse:
|
def overridden_redoc() -> HTMLResponse:
|
||||||
return get_redoc_html(
|
return get_redoc_html(
|
||||||
openapi_url=app.openapi_url, # type: ignore [arg-type] # this is always a string
|
openapi_url=app.openapi_url, # type: ignore [arg-type] # this is always a string
|
||||||
title=app.title,
|
title=f"{app.title} - Redoc",
|
||||||
redoc_favicon_url="/static/docs/favicon.ico",
|
redoc_favicon_url="static/docs/invoke-favicon-docs.svg",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
web_root_path = Path(list(web_dir.__path__)[0])
|
web_root_path = Path(list(web_dir.__path__)[0])
|
||||||
|
|
||||||
|
try:
|
||||||
# Cannot add headers to StaticFiles, so we must serve index.html with a custom route
|
app.mount("/", NoCacheStaticFiles(directory=Path(web_root_path, "dist"), html=True), name="ui")
|
||||||
# Add cache-control: no-store header to prevent caching of index.html, which leads to broken UIs at release
|
except RuntimeError:
|
||||||
@app.get("/", include_in_schema=False, name="ui_root")
|
logger.warn(f"No UI found at {web_root_path}/dist, skipping UI mount")
|
||||||
def get_index() -> FileResponse:
|
app.mount(
|
||||||
return FileResponse(Path(web_root_path, "dist/index.html"), headers={"Cache-Control": "no-store"})
|
"/static", NoCacheStaticFiles(directory=Path(web_root_path, "static/")), name="static"
|
||||||
|
) # docs favicon is in here
|
||||||
|
|
||||||
# # 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 invoke_api() -> None:
|
||||||
@@ -259,6 +267,8 @@ def invoke_api() -> None:
|
|||||||
port=port,
|
port=port,
|
||||||
loop="asyncio",
|
loop="asyncio",
|
||||||
log_level=app_config.log_level,
|
log_level=app_config.log_level,
|
||||||
|
ssl_certfile=app_config.ssl_certfile,
|
||||||
|
ssl_keyfile=app_config.ssl_keyfile,
|
||||||
)
|
)
|
||||||
server = uvicorn.Server(config)
|
server = uvicorn.Server(config)
|
||||||
|
|
||||||
@@ -273,7 +283,4 @@ def invoke_api() -> None:
|
|||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
if app_config.version:
|
invoke_api()
|
||||||
print(f"InvokeAI version {__version__}")
|
|
||||||
else:
|
|
||||||
invoke_api()
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ from pathlib import Path
|
|||||||
|
|
||||||
from invokeai.app.services.config.config_default import InvokeAIAppConfig
|
from invokeai.app.services.config.config_default import InvokeAIAppConfig
|
||||||
|
|
||||||
custom_nodes_path = Path(InvokeAIAppConfig.get_config().custom_nodes_path.absolute())
|
custom_nodes_path = Path(InvokeAIAppConfig.get_config().custom_nodes_path.resolve())
|
||||||
custom_nodes_path.mkdir(parents=True, exist_ok=True)
|
custom_nodes_path.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
custom_nodes_init_path = str(custom_nodes_path / "__init__.py")
|
custom_nodes_init_path = str(custom_nodes_path / "__init__.py")
|
||||||
|
|||||||
@@ -1,27 +1,38 @@
|
|||||||
# Copyright (c) 2022 Kyle Schouviller (https://github.com/kyle0654)
|
# Copyright (c) 2022 Kyle Schouviller (https://github.com/kyle0654) and the InvokeAI team
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import inspect
|
import inspect
|
||||||
import re
|
import re
|
||||||
|
import warnings
|
||||||
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, Type, TypeVar, Union
|
from typing import TYPE_CHECKING, Any, Callable, ClassVar, Iterable, Literal, Optional, Type, TypeVar, Union, cast
|
||||||
|
|
||||||
import semver
|
import semver
|
||||||
from pydantic import BaseModel, ConfigDict, Field, RootModel, TypeAdapter, create_model
|
from pydantic import BaseModel, ConfigDict, Field, create_model
|
||||||
from pydantic.fields import FieldInfo, _Unset
|
from pydantic.fields import FieldInfo
|
||||||
from pydantic_core import PydanticUndefined
|
from pydantic_core import PydanticUndefined
|
||||||
|
|
||||||
|
from invokeai.app.invocations.fields import (
|
||||||
|
FieldKind,
|
||||||
|
Input,
|
||||||
|
)
|
||||||
from invokeai.app.services.config.config_default import InvokeAIAppConfig
|
from invokeai.app.services.config.config_default import InvokeAIAppConfig
|
||||||
from invokeai.app.shared.fields import FieldDescriptions
|
from invokeai.app.services.shared.invocation_context import InvocationContext
|
||||||
|
from invokeai.app.util.metaenum import MetaEnum
|
||||||
from invokeai.app.util.misc import uuid_string
|
from invokeai.app.util.misc import uuid_string
|
||||||
|
from invokeai.backend.util.logging import InvokeAILogger
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from ..services.invocation_services import InvocationServices
|
from ..services.invocation_services import InvocationServices
|
||||||
|
|
||||||
|
logger = InvokeAILogger.get_logger()
|
||||||
|
|
||||||
|
CUSTOM_NODE_PACK_SUFFIX = "__invokeai-custom-node"
|
||||||
|
|
||||||
|
|
||||||
class InvalidVersionError(ValueError):
|
class InvalidVersionError(ValueError):
|
||||||
pass
|
pass
|
||||||
@@ -31,365 +42,17 @@ class InvalidFieldError(TypeError):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class Input(str, Enum):
|
class Classification(str, Enum, metaclass=MetaEnum):
|
||||||
"""
|
"""
|
||||||
The type of input a field accepts.
|
The classification of an Invocation.
|
||||||
- `Input.Direct`: The field must have its value provided directly, when the invocation and field \
|
- `Stable`: The invocation, including its inputs/outputs and internal logic, is stable. You may build workflows with it, having confidence that they will not break because of a change in this invocation.
|
||||||
are instantiated.
|
- `Beta`: The invocation is not yet stable, but is planned to be stable in the future. Workflows built around this invocation may break, but we are committed to supporting this invocation long-term.
|
||||||
- `Input.Connection`: The field must have its value provided by a connection.
|
- `Prototype`: The invocation is not yet stable and may be removed from the application at any time. Workflows built around this invocation may break, and we are *not* committed to supporting this invocation.
|
||||||
- `Input.Any`: The field may have its value provided either directly or by a connection.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
Connection = "connection"
|
Stable = "stable"
|
||||||
Direct = "direct"
|
Beta = "beta"
|
||||||
Any = "any"
|
Prototype = "prototype"
|
||||||
|
|
||||||
|
|
||||||
class UIType(str, Enum):
|
|
||||||
"""
|
|
||||||
Type hints for the UI.
|
|
||||||
If a field should be provided a data type that does not exactly match the python type of the field, \
|
|
||||||
use this to provide the type that should be used instead. See the node development docs for detail \
|
|
||||||
on adding a new field type, which involves client-side changes.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# region Primitives
|
|
||||||
Boolean = "boolean"
|
|
||||||
Color = "ColorField"
|
|
||||||
Conditioning = "ConditioningField"
|
|
||||||
Control = "ControlField"
|
|
||||||
Float = "float"
|
|
||||||
Image = "ImageField"
|
|
||||||
Integer = "integer"
|
|
||||||
Latents = "LatentsField"
|
|
||||||
String = "string"
|
|
||||||
# endregion
|
|
||||||
|
|
||||||
# region Collection Primitives
|
|
||||||
BooleanCollection = "BooleanCollection"
|
|
||||||
ColorCollection = "ColorCollection"
|
|
||||||
ConditioningCollection = "ConditioningCollection"
|
|
||||||
ControlCollection = "ControlCollection"
|
|
||||||
FloatCollection = "FloatCollection"
|
|
||||||
ImageCollection = "ImageCollection"
|
|
||||||
IntegerCollection = "IntegerCollection"
|
|
||||||
LatentsCollection = "LatentsCollection"
|
|
||||||
StringCollection = "StringCollection"
|
|
||||||
# endregion
|
|
||||||
|
|
||||||
# region Polymorphic Primitives
|
|
||||||
BooleanPolymorphic = "BooleanPolymorphic"
|
|
||||||
ColorPolymorphic = "ColorPolymorphic"
|
|
||||||
ConditioningPolymorphic = "ConditioningPolymorphic"
|
|
||||||
ControlPolymorphic = "ControlPolymorphic"
|
|
||||||
FloatPolymorphic = "FloatPolymorphic"
|
|
||||||
ImagePolymorphic = "ImagePolymorphic"
|
|
||||||
IntegerPolymorphic = "IntegerPolymorphic"
|
|
||||||
LatentsPolymorphic = "LatentsPolymorphic"
|
|
||||||
StringPolymorphic = "StringPolymorphic"
|
|
||||||
# endregion
|
|
||||||
|
|
||||||
# region Models
|
|
||||||
MainModel = "MainModelField"
|
|
||||||
SDXLMainModel = "SDXLMainModelField"
|
|
||||||
SDXLRefinerModel = "SDXLRefinerModelField"
|
|
||||||
ONNXModel = "ONNXModelField"
|
|
||||||
VaeModel = "VaeModelField"
|
|
||||||
LoRAModel = "LoRAModelField"
|
|
||||||
ControlNetModel = "ControlNetModelField"
|
|
||||||
IPAdapterModel = "IPAdapterModelField"
|
|
||||||
UNet = "UNetField"
|
|
||||||
Vae = "VaeField"
|
|
||||||
CLIP = "ClipField"
|
|
||||||
# endregion
|
|
||||||
|
|
||||||
# region Iterate/Collect
|
|
||||||
Collection = "Collection"
|
|
||||||
CollectionItem = "CollectionItem"
|
|
||||||
# endregion
|
|
||||||
|
|
||||||
# region Misc
|
|
||||||
Enum = "enum"
|
|
||||||
Scheduler = "Scheduler"
|
|
||||||
WorkflowField = "WorkflowField"
|
|
||||||
IsIntermediate = "IsIntermediate"
|
|
||||||
BoardField = "BoardField"
|
|
||||||
Any = "Any"
|
|
||||||
MetadataItem = "MetadataItem"
|
|
||||||
MetadataItemCollection = "MetadataItemCollection"
|
|
||||||
MetadataItemPolymorphic = "MetadataItemPolymorphic"
|
|
||||||
MetadataDict = "MetadataDict"
|
|
||||||
# endregion
|
|
||||||
|
|
||||||
|
|
||||||
class UIComponent(str, Enum):
|
|
||||||
"""
|
|
||||||
The type of UI component to use for a field, used to override the default components, which are \
|
|
||||||
inferred from the field type.
|
|
||||||
"""
|
|
||||||
|
|
||||||
None_ = "none"
|
|
||||||
Textarea = "textarea"
|
|
||||||
Slider = "slider"
|
|
||||||
|
|
||||||
|
|
||||||
class _InputField(BaseModel):
|
|
||||||
"""
|
|
||||||
*DO NOT USE*
|
|
||||||
This helper class is used to tell the client about our custom field attributes via OpenAPI
|
|
||||||
schema generation, and Typescript type generation from that schema. It serves no functional
|
|
||||||
purpose in the backend.
|
|
||||||
"""
|
|
||||||
|
|
||||||
input: Input
|
|
||||||
ui_hidden: bool
|
|
||||||
ui_type: Optional[UIType]
|
|
||||||
ui_component: Optional[UIComponent]
|
|
||||||
ui_order: Optional[int]
|
|
||||||
ui_choice_labels: Optional[dict[str, str]]
|
|
||||||
item_default: Optional[Any]
|
|
||||||
|
|
||||||
model_config = ConfigDict(
|
|
||||||
validate_assignment=True,
|
|
||||||
json_schema_serialization_defaults_required=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class _OutputField(BaseModel):
|
|
||||||
"""
|
|
||||||
*DO NOT USE*
|
|
||||||
This helper class is used to tell the client about our custom field attributes via OpenAPI
|
|
||||||
schema generation, and Typescript type generation from that schema. It serves no functional
|
|
||||||
purpose in the backend.
|
|
||||||
"""
|
|
||||||
|
|
||||||
ui_hidden: bool
|
|
||||||
ui_type: Optional[UIType]
|
|
||||||
ui_order: Optional[int]
|
|
||||||
|
|
||||||
model_config = ConfigDict(
|
|
||||||
validate_assignment=True,
|
|
||||||
json_schema_serialization_defaults_required=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def get_type(klass: BaseModel) -> str:
|
|
||||||
"""Helper function to get an invocation or invocation output's type. This is the default value of the `type` field."""
|
|
||||||
return klass.model_fields["type"].default
|
|
||||||
|
|
||||||
|
|
||||||
def InputField(
|
|
||||||
# copied from pydantic's Field
|
|
||||||
default: Any = _Unset,
|
|
||||||
default_factory: Callable[[], Any] | None = _Unset,
|
|
||||||
title: str | None = _Unset,
|
|
||||||
description: str | None = _Unset,
|
|
||||||
pattern: str | None = _Unset,
|
|
||||||
strict: bool | None = _Unset,
|
|
||||||
gt: float | None = _Unset,
|
|
||||||
ge: float | None = _Unset,
|
|
||||||
lt: float | None = _Unset,
|
|
||||||
le: float | None = _Unset,
|
|
||||||
multiple_of: float | None = _Unset,
|
|
||||||
allow_inf_nan: bool | None = _Unset,
|
|
||||||
max_digits: int | None = _Unset,
|
|
||||||
decimal_places: int | None = _Unset,
|
|
||||||
min_length: int | None = _Unset,
|
|
||||||
max_length: int | None = _Unset,
|
|
||||||
# custom
|
|
||||||
input: Input = Input.Any,
|
|
||||||
ui_type: Optional[UIType] = None,
|
|
||||||
ui_component: Optional[UIComponent] = None,
|
|
||||||
ui_hidden: bool = False,
|
|
||||||
ui_order: Optional[int] = None,
|
|
||||||
ui_choice_labels: Optional[dict[str, str]] = None,
|
|
||||||
item_default: Optional[Any] = None,
|
|
||||||
) -> Any:
|
|
||||||
"""
|
|
||||||
Creates an input field for an invocation.
|
|
||||||
|
|
||||||
This is a wrapper for Pydantic's [Field](https://docs.pydantic.dev/1.10/usage/schema/#field-customization) \
|
|
||||||
that adds a few extra parameters to support graph execution and the node editor UI.
|
|
||||||
|
|
||||||
:param Input input: [Input.Any] The kind of input this field requires. \
|
|
||||||
`Input.Direct` means a value must be provided on instantiation. \
|
|
||||||
`Input.Connection` means the value must be provided by a connection. \
|
|
||||||
`Input.Any` means either will do.
|
|
||||||
|
|
||||||
:param UIType ui_type: [None] Optionally provides an extra type hint for the UI. \
|
|
||||||
In some situations, the field's type is not enough to infer the correct UI type. \
|
|
||||||
For example, model selection fields should render a dropdown UI component to select a model. \
|
|
||||||
Internally, there is no difference between SD-1, SD-2 and SDXL model fields, they all use \
|
|
||||||
`MainModelField`. So to ensure the base-model-specific UI is rendered, you can use \
|
|
||||||
`UIType.SDXLMainModelField` to indicate that the field is an SDXL main model field.
|
|
||||||
|
|
||||||
:param UIComponent ui_component: [None] Optionally specifies a specific component to use in the UI. \
|
|
||||||
The UI will always render a suitable component, but sometimes you want something different than the default. \
|
|
||||||
For example, a `string` field will default to a single-line input, but you may want a multi-line textarea instead. \
|
|
||||||
For this case, you could provide `UIComponent.Textarea`.
|
|
||||||
|
|
||||||
: param bool ui_hidden: [False] Specifies whether or not this field should be hidden in the UI.
|
|
||||||
|
|
||||||
: param int ui_order: [None] Specifies the order in which this field should be rendered in the UI. \
|
|
||||||
|
|
||||||
: param bool item_default: [None] Specifies the default item value, if this is a collection input. \
|
|
||||||
Ignored for non-collection fields.
|
|
||||||
"""
|
|
||||||
|
|
||||||
json_schema_extra_: dict[str, Any] = {
|
|
||||||
"input": input,
|
|
||||||
"ui_type": ui_type,
|
|
||||||
"ui_component": ui_component,
|
|
||||||
"ui_hidden": ui_hidden,
|
|
||||||
"ui_order": ui_order,
|
|
||||||
"item_default": item_default,
|
|
||||||
"ui_choice_labels": ui_choice_labels,
|
|
||||||
"_field_kind": "input",
|
|
||||||
}
|
|
||||||
|
|
||||||
field_args = {
|
|
||||||
"default": default,
|
|
||||||
"default_factory": default_factory,
|
|
||||||
"title": title,
|
|
||||||
"description": description,
|
|
||||||
"pattern": pattern,
|
|
||||||
"strict": strict,
|
|
||||||
"gt": gt,
|
|
||||||
"ge": ge,
|
|
||||||
"lt": lt,
|
|
||||||
"le": le,
|
|
||||||
"multiple_of": multiple_of,
|
|
||||||
"allow_inf_nan": allow_inf_nan,
|
|
||||||
"max_digits": max_digits,
|
|
||||||
"decimal_places": decimal_places,
|
|
||||||
"min_length": min_length,
|
|
||||||
"max_length": max_length,
|
|
||||||
}
|
|
||||||
|
|
||||||
"""
|
|
||||||
Invocation definitions have their fields typed correctly for their `invoke()` functions.
|
|
||||||
This typing is often more specific than the actual invocation definition requires, because
|
|
||||||
fields may have values provided only by connections.
|
|
||||||
|
|
||||||
For example, consider an ResizeImageInvocation with an `image: ImageField` field.
|
|
||||||
|
|
||||||
`image` is required during the call to `invoke()`, but when the python class is instantiated,
|
|
||||||
the field may not be present. This is fine, because that image field will be provided by a
|
|
||||||
an ancestor node that outputs the image.
|
|
||||||
|
|
||||||
So we'd like to type that `image` field as `Optional[ImageField]`. If we do that, however, then
|
|
||||||
we need to handle a lot of extra logic in the `invoke()` function to check if the field has a
|
|
||||||
value or not. This is very tedious.
|
|
||||||
|
|
||||||
Ideally, the invocation definition would be able to specify that the field is required during
|
|
||||||
invocation, but optional during instantiation. So the field would be typed as `image: ImageField`,
|
|
||||||
but when calling the `invoke()` function, we raise an error if the field is not present.
|
|
||||||
|
|
||||||
To do this, we need to do a bit of fanagling to make the pydantic field optional, and then do
|
|
||||||
extra validation when calling `invoke()`.
|
|
||||||
|
|
||||||
There is some additional logic here to cleaning create the pydantic field via the wrapper.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Filter out field args not provided
|
|
||||||
provided_args = {k: v for (k, v) in field_args.items() if v is not PydanticUndefined}
|
|
||||||
|
|
||||||
if (default is not PydanticUndefined) and (default_factory is not PydanticUndefined):
|
|
||||||
raise ValueError("Cannot specify both default and default_factory")
|
|
||||||
|
|
||||||
# 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:
|
|
||||||
json_schema_extra_.update({"orig_required": True})
|
|
||||||
else:
|
|
||||||
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
|
|
||||||
if (input is Input.Any or input is Input.Connection) and default_factory is PydanticUndefined:
|
|
||||||
default_ = None if default is PydanticUndefined else default
|
|
||||||
provided_args.update({"default": default_})
|
|
||||||
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
|
|
||||||
json_schema_extra_.update({"default": default})
|
|
||||||
json_schema_extra_.update({"orig_default": default})
|
|
||||||
elif default is not PydanticUndefined and default_factory is PydanticUndefined:
|
|
||||||
default_ = default
|
|
||||||
provided_args.update({"default": default_})
|
|
||||||
json_schema_extra_.update({"orig_default": default_})
|
|
||||||
elif default_factory is not PydanticUndefined:
|
|
||||||
provided_args.update({"default_factory": default_factory})
|
|
||||||
# TODO: cannot serialize default_factory...
|
|
||||||
# json_schema_extra_.update(dict(orig_default_factory=default_factory))
|
|
||||||
|
|
||||||
return Field(
|
|
||||||
**provided_args,
|
|
||||||
json_schema_extra=json_schema_extra_,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def OutputField(
|
|
||||||
# copied from pydantic's Field
|
|
||||||
default: Any = _Unset,
|
|
||||||
default_factory: Callable[[], Any] | None = _Unset,
|
|
||||||
title: str | None = _Unset,
|
|
||||||
description: str | None = _Unset,
|
|
||||||
pattern: str | None = _Unset,
|
|
||||||
strict: bool | None = _Unset,
|
|
||||||
gt: float | None = _Unset,
|
|
||||||
ge: float | None = _Unset,
|
|
||||||
lt: float | None = _Unset,
|
|
||||||
le: float | None = _Unset,
|
|
||||||
multiple_of: float | None = _Unset,
|
|
||||||
allow_inf_nan: bool | None = _Unset,
|
|
||||||
max_digits: int | None = _Unset,
|
|
||||||
decimal_places: int | None = _Unset,
|
|
||||||
min_length: int | None = _Unset,
|
|
||||||
max_length: int | None = _Unset,
|
|
||||||
# custom
|
|
||||||
ui_type: Optional[UIType] = None,
|
|
||||||
ui_hidden: bool = False,
|
|
||||||
ui_order: Optional[int] = None,
|
|
||||||
) -> Any:
|
|
||||||
"""
|
|
||||||
Creates an output field for an invocation output.
|
|
||||||
|
|
||||||
This is a wrapper for Pydantic's [Field](https://docs.pydantic.dev/1.10/usage/schema/#field-customization) \
|
|
||||||
that adds a few extra parameters to support graph execution and the node editor UI.
|
|
||||||
|
|
||||||
:param UIType ui_type: [None] Optionally provides an extra type hint for the UI. \
|
|
||||||
In some situations, the field's type is not enough to infer the correct UI type. \
|
|
||||||
For example, model selection fields should render a dropdown UI component to select a model. \
|
|
||||||
Internally, there is no difference between SD-1, SD-2 and SDXL model fields, they all use \
|
|
||||||
`MainModelField`. So to ensure the base-model-specific UI is rendered, you can use \
|
|
||||||
`UIType.SDXLMainModelField` to indicate that the field is an SDXL main model field.
|
|
||||||
|
|
||||||
: param bool ui_hidden: [False] Specifies whether or not this field should be hidden in the UI. \
|
|
||||||
|
|
||||||
: param int ui_order: [None] Specifies the order in which this field should be rendered in the UI. \
|
|
||||||
"""
|
|
||||||
return Field(
|
|
||||||
default=default,
|
|
||||||
default_factory=default_factory,
|
|
||||||
title=title,
|
|
||||||
description=description,
|
|
||||||
pattern=pattern,
|
|
||||||
strict=strict,
|
|
||||||
gt=gt,
|
|
||||||
ge=ge,
|
|
||||||
lt=lt,
|
|
||||||
le=le,
|
|
||||||
multiple_of=multiple_of,
|
|
||||||
allow_inf_nan=allow_inf_nan,
|
|
||||||
max_digits=max_digits,
|
|
||||||
decimal_places=decimal_places,
|
|
||||||
min_length=min_length,
|
|
||||||
max_length=max_length,
|
|
||||||
json_schema_extra={
|
|
||||||
"ui_type": ui_type,
|
|
||||||
"ui_hidden": ui_hidden,
|
|
||||||
"ui_order": ui_order,
|
|
||||||
"_field_kind": "output",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class UIConfigBase(BaseModel):
|
class UIConfigBase(BaseModel):
|
||||||
@@ -401,10 +64,11 @@ class UIConfigBase(BaseModel):
|
|||||||
tags: Optional[list[str]] = Field(default_factory=None, description="The node's tags")
|
tags: Optional[list[str]] = Field(default_factory=None, description="The node's tags")
|
||||||
title: Optional[str] = Field(default=None, description="The node's display name")
|
title: Optional[str] = Field(default=None, description="The node's display name")
|
||||||
category: Optional[str] = Field(default=None, description="The node's category")
|
category: Optional[str] = Field(default=None, description="The node's category")
|
||||||
version: Optional[str] = Field(
|
version: str = Field(
|
||||||
default=None,
|
|
||||||
description='The node\'s version. Should be a valid semver string e.g. "1.0.0" or "3.8.13".',
|
description='The node\'s version. Should be a valid semver string e.g. "1.0.0" or "3.8.13".',
|
||||||
)
|
)
|
||||||
|
node_pack: Optional[str] = Field(default=None, description="Whether or not this is a custom node")
|
||||||
|
classification: Classification = Field(default=Classification.Stable, description="The node's classification")
|
||||||
|
|
||||||
model_config = ConfigDict(
|
model_config = ConfigDict(
|
||||||
validate_assignment=True,
|
validate_assignment=True,
|
||||||
@@ -412,30 +76,6 @@ class UIConfigBase(BaseModel):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class InvocationContext:
|
|
||||||
"""Initialized and provided to on execution of invocations."""
|
|
||||||
|
|
||||||
services: InvocationServices
|
|
||||||
graph_execution_state_id: str
|
|
||||||
queue_id: str
|
|
||||||
queue_item_id: int
|
|
||||||
queue_batch_id: str
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
services: InvocationServices,
|
|
||||||
queue_id: str,
|
|
||||||
queue_item_id: int,
|
|
||||||
queue_batch_id: str,
|
|
||||||
graph_execution_state_id: str,
|
|
||||||
):
|
|
||||||
self.services = services
|
|
||||||
self.graph_execution_state_id = graph_execution_state_id
|
|
||||||
self.queue_id = queue_id
|
|
||||||
self.queue_item_id = queue_item_id
|
|
||||||
self.queue_batch_id = queue_batch_id
|
|
||||||
|
|
||||||
|
|
||||||
class BaseInvocationOutput(BaseModel):
|
class BaseInvocationOutput(BaseModel):
|
||||||
"""
|
"""
|
||||||
Base class for all invocation outputs.
|
Base class for all invocation outputs.
|
||||||
@@ -447,29 +87,39 @@ class BaseInvocationOutput(BaseModel):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def register_output(cls, output: BaseInvocationOutput) -> None:
|
def register_output(cls, output: BaseInvocationOutput) -> None:
|
||||||
|
"""Registers an invocation output."""
|
||||||
cls._output_classes.add(output)
|
cls._output_classes.add(output)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_outputs(cls) -> Iterable[BaseInvocationOutput]:
|
def get_outputs(cls) -> Iterable[BaseInvocationOutput]:
|
||||||
|
"""Gets all invocation outputs."""
|
||||||
return cls._output_classes
|
return cls._output_classes
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_outputs_union(cls) -> UnionType:
|
def get_outputs_union(cls) -> UnionType:
|
||||||
|
"""Gets a union of all invocation outputs."""
|
||||||
outputs_union = Union[tuple(cls._output_classes)] # type: ignore [valid-type]
|
outputs_union = Union[tuple(cls._output_classes)] # type: ignore [valid-type]
|
||||||
return outputs_union # type: ignore [return-value]
|
return outputs_union # type: ignore [return-value]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_output_types(cls) -> Iterable[str]:
|
def get_output_types(cls) -> Iterable[str]:
|
||||||
return (get_type(i) for i in BaseInvocationOutput.get_outputs())
|
"""Gets all invocation output types."""
|
||||||
|
return (i.get_type() 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:
|
||||||
|
"""Adds various UI-facing attributes to the invocation output's OpenAPI schema."""
|
||||||
# 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"] = []
|
schema["required"] = []
|
||||||
schema["required"].extend(["type"])
|
schema["required"].extend(["type"])
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_type(cls) -> str:
|
||||||
|
"""Gets the invocation output's type, as provided by the `@invocation_output` decorator."""
|
||||||
|
return cls.model_fields["type"].default
|
||||||
|
|
||||||
model_config = ConfigDict(
|
model_config = ConfigDict(
|
||||||
protected_namespaces=(),
|
protected_namespaces=(),
|
||||||
validate_assignment=True,
|
validate_assignment=True,
|
||||||
@@ -499,21 +149,29 @@ class BaseInvocation(ABC, BaseModel):
|
|||||||
|
|
||||||
_invocation_classes: ClassVar[set[BaseInvocation]] = set()
|
_invocation_classes: ClassVar[set[BaseInvocation]] = set()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_type(cls) -> str:
|
||||||
|
"""Gets the invocation's type, as provided by the `@invocation` decorator."""
|
||||||
|
return cls.model_fields["type"].default
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def register_invocation(cls, invocation: BaseInvocation) -> None:
|
def register_invocation(cls, invocation: BaseInvocation) -> None:
|
||||||
|
"""Registers an invocation."""
|
||||||
cls._invocation_classes.add(invocation)
|
cls._invocation_classes.add(invocation)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_invocations_union(cls) -> UnionType:
|
def get_invocations_union(cls) -> UnionType:
|
||||||
|
"""Gets a union of all invocation types."""
|
||||||
invocations_union = Union[tuple(cls._invocation_classes)] # type: ignore [valid-type]
|
invocations_union = Union[tuple(cls._invocation_classes)] # type: ignore [valid-type]
|
||||||
return invocations_union # type: ignore [return-value]
|
return invocations_union # type: ignore [return-value]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_invocations(cls) -> Iterable[BaseInvocation]:
|
def get_invocations(cls) -> Iterable[BaseInvocation]:
|
||||||
|
"""Gets all invocations, respecting the allowlist and denylist."""
|
||||||
app_config = InvokeAIAppConfig.get_config()
|
app_config = InvokeAIAppConfig.get_config()
|
||||||
allowed_invocations: set[BaseInvocation] = set()
|
allowed_invocations: set[BaseInvocation] = set()
|
||||||
for sc in cls._invocation_classes:
|
for sc in cls._invocation_classes:
|
||||||
invocation_type = get_type(sc)
|
invocation_type = sc.get_type()
|
||||||
is_in_allowlist = (
|
is_in_allowlist = (
|
||||||
invocation_type in app_config.allow_nodes if isinstance(app_config.allow_nodes, list) else True
|
invocation_type in app_config.allow_nodes if isinstance(app_config.allow_nodes, list) else True
|
||||||
)
|
)
|
||||||
@@ -526,28 +184,33 @@ 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
|
"""Gets a map of all invocation types to their invocation classes."""
|
||||||
return {get_type(i): i for i in BaseInvocation.get_invocations()}
|
return {i.get_type(): i for i in BaseInvocation.get_invocations()}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_invocation_types(cls) -> Iterable[str]:
|
def get_invocation_types(cls) -> Iterable[str]:
|
||||||
return (get_type(i) for i in BaseInvocation.get_invocations())
|
"""Gets all invocation types."""
|
||||||
|
return (i.get_type() for i in BaseInvocation.get_invocations())
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_output_type(cls) -> BaseInvocationOutput:
|
def get_output_annotation(cls) -> BaseInvocationOutput:
|
||||||
|
"""Gets the invocation's output annotation (i.e. the return annotation of its `invoke()` method)."""
|
||||||
return signature(cls.invoke).return_annotation
|
return signature(cls.invoke).return_annotation
|
||||||
|
|
||||||
@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], *args, **kwargs) -> None:
|
||||||
# Add the various UI-facing attributes to the schema. These are used to build the invocation templates.
|
"""Adds various UI-facing attributes to the invocation's OpenAPI schema."""
|
||||||
uiconfig = getattr(model_class, "UIConfig", None)
|
uiconfig = cast(UIConfigBase | None, getattr(model_class, "UIConfig", None))
|
||||||
if uiconfig and hasattr(uiconfig, "title"):
|
if uiconfig is not None:
|
||||||
schema["title"] = uiconfig.title
|
if uiconfig.title is not None:
|
||||||
if uiconfig and hasattr(uiconfig, "tags"):
|
schema["title"] = uiconfig.title
|
||||||
schema["tags"] = uiconfig.tags
|
if uiconfig.tags is not None:
|
||||||
if uiconfig and hasattr(uiconfig, "category"):
|
schema["tags"] = uiconfig.tags
|
||||||
schema["category"] = uiconfig.category
|
if uiconfig.category is not None:
|
||||||
if uiconfig and hasattr(uiconfig, "version"):
|
schema["category"] = uiconfig.category
|
||||||
|
if uiconfig.node_pack is not None:
|
||||||
|
schema["node_pack"] = uiconfig.node_pack
|
||||||
|
schema["classification"] = uiconfig.classification
|
||||||
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"] = []
|
schema["required"] = []
|
||||||
@@ -558,7 +221,11 @@ 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: InvocationContext) -> BaseInvocationOutput:
|
def invoke_internal(self, context: InvocationContext, services: "InvocationServices") -> BaseInvocationOutput:
|
||||||
|
"""
|
||||||
|
Internal invoke method, calls `invoke()` after some prep.
|
||||||
|
Handles optional fields that are required to call `invoke()` and invocation cache.
|
||||||
|
"""
|
||||||
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
|
||||||
@@ -579,40 +246,39 @@ class BaseInvocation(ABC, BaseModel):
|
|||||||
raise MissingInputException(self.model_fields["type"].default, field_name)
|
raise MissingInputException(self.model_fields["type"].default, field_name)
|
||||||
|
|
||||||
# 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 services.configuration.node_cache_size == 0:
|
||||||
return self.invoke(context)
|
return self.invoke(context)
|
||||||
|
|
||||||
output: BaseInvocationOutput
|
output: BaseInvocationOutput
|
||||||
if self.use_cache:
|
if self.use_cache:
|
||||||
key = context.services.invocation_cache.create_key(self)
|
key = services.invocation_cache.create_key(self)
|
||||||
cached_value = context.services.invocation_cache.get(key)
|
cached_value = 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}')
|
services.logger.debug(f'Invocation cache miss for type "{self.get_type()}": {self.id}')
|
||||||
output = self.invoke(context)
|
output = self.invoke(context)
|
||||||
context.services.invocation_cache.save(key, output)
|
services.invocation_cache.save(key, output)
|
||||||
return output
|
return output
|
||||||
else:
|
else:
|
||||||
context.services.logger.debug(f'Invocation cache hit for type "{self.get_type()}": {self.id}')
|
services.logger.debug(f'Invocation cache hit for type "{self.get_type()}": {self.id}')
|
||||||
return cached_value
|
return cached_value
|
||||||
else:
|
else:
|
||||||
context.services.logger.debug(f'Skipping invocation cache for "{self.get_type()}": {self.id}')
|
services.logger.debug(f'Skipping invocation cache for "{self.get_type()}": {self.id}')
|
||||||
return self.invoke(context)
|
return self.invoke(context)
|
||||||
|
|
||||||
def get_type(self) -> str:
|
|
||||||
return self.model_fields["type"].default
|
|
||||||
|
|
||||||
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"},
|
json_schema_extra={"field_kind": FieldKind.NodeAttribute},
|
||||||
)
|
)
|
||||||
is_intermediate: 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={"ui_type": UIType.IsIntermediate, "_field_kind": "internal"},
|
json_schema_extra={"ui_type": "IsIntermediate", "field_kind": FieldKind.NodeAttribute},
|
||||||
)
|
)
|
||||||
use_cache: bool = Field(
|
use_cache: bool = Field(
|
||||||
default=True, description="Whether or not to use the cache", json_schema_extra={"_field_kind": "internal"}
|
default=True,
|
||||||
|
description="Whether or not to use the cache",
|
||||||
|
json_schema_extra={"field_kind": FieldKind.NodeAttribute},
|
||||||
)
|
)
|
||||||
|
|
||||||
UIConfig: ClassVar[Type[UIConfigBase]]
|
UIConfig: ClassVar[Type[UIConfigBase]]
|
||||||
@@ -629,15 +295,16 @@ class BaseInvocation(ABC, BaseModel):
|
|||||||
TBaseInvocation = TypeVar("TBaseInvocation", bound=BaseInvocation)
|
TBaseInvocation = TypeVar("TBaseInvocation", bound=BaseInvocation)
|
||||||
|
|
||||||
|
|
||||||
RESERVED_INPUT_FIELD_NAMES = {
|
RESERVED_NODE_ATTRIBUTE_FIELD_NAMES = {
|
||||||
"id",
|
"id",
|
||||||
"is_intermediate",
|
"is_intermediate",
|
||||||
"use_cache",
|
"use_cache",
|
||||||
"type",
|
"type",
|
||||||
"workflow",
|
"workflow",
|
||||||
"metadata",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RESERVED_INPUT_FIELD_NAMES = {"metadata", "board"}
|
||||||
|
|
||||||
RESERVED_OUTPUT_FIELD_NAMES = {"type"}
|
RESERVED_OUTPUT_FIELD_NAMES = {"type"}
|
||||||
|
|
||||||
|
|
||||||
@@ -645,47 +312,68 @@ class _Model(BaseModel):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
# Get all pydantic model attrs, methods, etc
|
with warnings.catch_warnings():
|
||||||
RESERVED_PYDANTIC_FIELD_NAMES = {m[0] for m in inspect.getmembers(_Model())}
|
warnings.simplefilter("ignore", category=DeprecationWarning)
|
||||||
|
# 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:
|
def validate_fields(model_fields: dict[str, FieldInfo], model_type: str) -> None:
|
||||||
"""
|
"""
|
||||||
Validates the fields of an invocation or invocation output:
|
Validates the fields of an invocation or invocation output:
|
||||||
- must not override any pydantic reserved fields
|
- Must not override any pydantic reserved fields
|
||||||
- must be created via `InputField`, `OutputField`, or be an internal field defined in this file
|
- Must have a type annotation
|
||||||
|
- Must have a json_schema_extra dict
|
||||||
|
- Must have field_kind in json_schema_extra
|
||||||
|
- Field name must not be reserved, according to its field_kind
|
||||||
"""
|
"""
|
||||||
for name, field in model_fields.items():
|
for name, field in model_fields.items():
|
||||||
if name in RESERVED_PYDANTIC_FIELD_NAMES:
|
if name in RESERVED_PYDANTIC_FIELD_NAMES:
|
||||||
raise InvalidFieldError(f'Invalid field name "{name}" on "{model_type}" (reserved by pydantic)')
|
raise InvalidFieldError(f'Invalid field name "{name}" on "{model_type}" (reserved by pydantic)')
|
||||||
|
|
||||||
field_kind = (
|
if not field.annotation:
|
||||||
# _field_kind is defined via InputField(), OutputField() or by one of the internal fields defined in this file
|
raise InvalidFieldError(f'Invalid field type "{name}" on "{model_type}" (missing annotation)')
|
||||||
field.json_schema_extra.get("_field_kind", None) if field.json_schema_extra else None
|
|
||||||
)
|
if not isinstance(field.json_schema_extra, dict):
|
||||||
|
raise InvalidFieldError(
|
||||||
|
f'Invalid field definition for "{name}" on "{model_type}" (missing json_schema_extra dict)'
|
||||||
|
)
|
||||||
|
|
||||||
|
field_kind = field.json_schema_extra.get("field_kind", None)
|
||||||
|
|
||||||
# must have a field_kind
|
# must have a field_kind
|
||||||
if field_kind is None or field_kind not in {"input", "output", "internal"}:
|
if not isinstance(field_kind, FieldKind):
|
||||||
raise InvalidFieldError(
|
raise InvalidFieldError(
|
||||||
f'Invalid field definition for "{name}" on "{model_type}" (maybe it\'s not an InputField or OutputField?)'
|
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:
|
if field_kind is FieldKind.Input and (
|
||||||
|
name in RESERVED_NODE_ATTRIBUTE_FIELD_NAMES or name in RESERVED_INPUT_FIELD_NAMES
|
||||||
|
):
|
||||||
raise InvalidFieldError(f'Invalid field name "{name}" on "{model_type}" (reserved input field name)')
|
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:
|
if field_kind is FieldKind.Output and name in RESERVED_OUTPUT_FIELD_NAMES:
|
||||||
raise InvalidFieldError(f'Invalid field name "{name}" on "{model_type}" (reserved output field name)')
|
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 is FieldKind.Internal) and name not in RESERVED_INPUT_FIELD_NAMES:
|
||||||
if (
|
|
||||||
field_kind == "internal"
|
|
||||||
and name not in RESERVED_INPUT_FIELD_NAMES
|
|
||||||
and name not in RESERVED_OUTPUT_FIELD_NAMES
|
|
||||||
):
|
|
||||||
raise InvalidFieldError(
|
raise InvalidFieldError(
|
||||||
f'Invalid field name "{name}" on "{model_type}" (internal field without reserved name)'
|
f'Invalid field name "{name}" on "{model_type}" (internal field without reserved name)'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# node attribute fields *must* be in the reserved list
|
||||||
|
if (
|
||||||
|
field_kind is FieldKind.NodeAttribute
|
||||||
|
and name not in RESERVED_NODE_ATTRIBUTE_FIELD_NAMES
|
||||||
|
and name not in RESERVED_OUTPUT_FIELD_NAMES
|
||||||
|
):
|
||||||
|
raise InvalidFieldError(
|
||||||
|
f'Invalid field name "{name}" on "{model_type}" (node attribute field without reserved name)'
|
||||||
|
)
|
||||||
|
|
||||||
|
ui_type = field.json_schema_extra.get("ui_type", None)
|
||||||
|
if isinstance(ui_type, str) and ui_type.startswith("DEPRECATED_"):
|
||||||
|
logger.warn(f"\"UIType.{ui_type.split('_')[-1]}\" is deprecated, ignoring")
|
||||||
|
field.json_schema_extra.pop("ui_type")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
@@ -696,6 +384,7 @@ def invocation(
|
|||||||
category: Optional[str] = None,
|
category: Optional[str] = None,
|
||||||
version: Optional[str] = None,
|
version: Optional[str] = None,
|
||||||
use_cache: Optional[bool] = True,
|
use_cache: Optional[bool] = True,
|
||||||
|
classification: Classification = Classification.Stable,
|
||||||
) -> Callable[[Type[TBaseInvocation]], Type[TBaseInvocation]]:
|
) -> Callable[[Type[TBaseInvocation]], Type[TBaseInvocation]]:
|
||||||
"""
|
"""
|
||||||
Registers an invocation.
|
Registers an invocation.
|
||||||
@@ -706,6 +395,7 @@ def invocation(
|
|||||||
:param Optional[str] category: Adds a category to the invocation. Used to group the invocations in the UI. Defaults to None.
|
:param Optional[str] category: Adds a category to the invocation. Used to group the invocations in the UI. Defaults to None.
|
||||||
:param Optional[str] version: Adds a version to the invocation. Must be a valid semver string. Defaults to None.
|
:param Optional[str] version: Adds a version to the invocation. Must be a valid semver string. Defaults to None.
|
||||||
:param Optional[bool] use_cache: Whether or not to use the invocation cache. Defaults to True. The user may override this in the workflow editor.
|
:param Optional[bool] use_cache: Whether or not to use the invocation cache. Defaults to True. The user may override this in the workflow editor.
|
||||||
|
:param Classification classification: The classification of the invocation. Defaults to FeatureClassification.Stable. Use Beta or Prototype if the invocation is unstable.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def wrapper(cls: Type[TBaseInvocation]) -> Type[TBaseInvocation]:
|
def wrapper(cls: Type[TBaseInvocation]) -> Type[TBaseInvocation]:
|
||||||
@@ -720,21 +410,31 @@ def invocation(
|
|||||||
validate_fields(cls.model_fields, invocation_type)
|
validate_fields(cls.model_fields, invocation_type)
|
||||||
|
|
||||||
# Add OpenAPI schema extras
|
# Add OpenAPI schema extras
|
||||||
uiconf_name = cls.__qualname__ + ".UIConfig"
|
uiconfig_name = cls.__qualname__ + ".UIConfig"
|
||||||
if not hasattr(cls, "UIConfig") or cls.UIConfig.__qualname__ != uiconf_name:
|
if not hasattr(cls, "UIConfig") or cls.UIConfig.__qualname__ != uiconfig_name:
|
||||||
cls.UIConfig = type(uiconf_name, (UIConfigBase,), {})
|
cls.UIConfig = type(uiconfig_name, (UIConfigBase,), {})
|
||||||
if title is not None:
|
cls.UIConfig.title = title
|
||||||
cls.UIConfig.title = title
|
cls.UIConfig.tags = tags
|
||||||
if tags is not None:
|
cls.UIConfig.category = category
|
||||||
cls.UIConfig.tags = tags
|
cls.UIConfig.classification = classification
|
||||||
if category is not None:
|
|
||||||
cls.UIConfig.category = category
|
# Grab the node pack's name from the module name, if it's a custom node
|
||||||
|
is_custom_node = cls.__module__.rsplit(".", 1)[0] == "invokeai.app.invocations"
|
||||||
|
if is_custom_node:
|
||||||
|
cls.UIConfig.node_pack = cls.__module__.split(".")[0]
|
||||||
|
else:
|
||||||
|
cls.UIConfig.node_pack = None
|
||||||
|
|
||||||
if version is not None:
|
if version is not None:
|
||||||
try:
|
try:
|
||||||
semver.Version.parse(version)
|
semver.Version.parse(version)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
raise InvalidVersionError(f'Invalid version string for node "{invocation_type}": "{version}"') from e
|
raise InvalidVersionError(f'Invalid version string for node "{invocation_type}": "{version}"') from e
|
||||||
cls.UIConfig.version = version
|
cls.UIConfig.version = version
|
||||||
|
else:
|
||||||
|
logger.warn(f'No version specified for node "{invocation_type}", using "1.0.0"')
|
||||||
|
cls.UIConfig.version = "1.0.0"
|
||||||
|
|
||||||
if use_cache is not None:
|
if use_cache is not None:
|
||||||
cls.model_fields["use_cache"].default = use_cache
|
cls.model_fields["use_cache"].default = use_cache
|
||||||
|
|
||||||
@@ -749,7 +449,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", default=invocation_type, json_schema_extra={"_field_kind": "internal"}
|
title="type", default=invocation_type, json_schema_extra={"field_kind": FieldKind.NodeAttribute}
|
||||||
)
|
)
|
||||||
|
|
||||||
docstring = cls.__doc__
|
docstring = cls.__doc__
|
||||||
@@ -795,7 +495,9 @@ def invocation_output(
|
|||||||
# 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(title="type", default=output_type, json_schema_extra={"_field_kind": "internal"})
|
output_type_field = Field(
|
||||||
|
title="type", default=output_type, json_schema_extra={"field_kind": FieldKind.NodeAttribute}
|
||||||
|
)
|
||||||
|
|
||||||
docstring = cls.__doc__
|
docstring = cls.__doc__
|
||||||
cls = create_model(
|
cls = create_model(
|
||||||
@@ -811,39 +513,3 @@ def invocation_output(
|
|||||||
return cls
|
return cls
|
||||||
|
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
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"}
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -5,9 +5,11 @@ import numpy as np
|
|||||||
from pydantic import ValidationInfo, field_validator
|
from pydantic import ValidationInfo, field_validator
|
||||||
|
|
||||||
from invokeai.app.invocations.primitives import IntegerCollectionOutput
|
from invokeai.app.invocations.primitives import IntegerCollectionOutput
|
||||||
from invokeai.app.util.misc import SEED_MAX, get_random_seed
|
from invokeai.app.services.shared.invocation_context import InvocationContext
|
||||||
|
from invokeai.app.util.misc import SEED_MAX
|
||||||
|
|
||||||
from .baseinvocation import BaseInvocation, InputField, InvocationContext, invocation
|
from .baseinvocation import BaseInvocation, invocation
|
||||||
|
from .fields import InputField
|
||||||
|
|
||||||
|
|
||||||
@invocation(
|
@invocation(
|
||||||
@@ -55,7 +57,7 @@ class RangeOfSizeInvocation(BaseInvocation):
|
|||||||
title="Random Range",
|
title="Random Range",
|
||||||
tags=["range", "integer", "random", "collection"],
|
tags=["range", "integer", "random", "collection"],
|
||||||
category="collections",
|
category="collections",
|
||||||
version="1.0.0",
|
version="1.0.1",
|
||||||
use_cache=False,
|
use_cache=False,
|
||||||
)
|
)
|
||||||
class RandomRangeInvocation(BaseInvocation):
|
class RandomRangeInvocation(BaseInvocation):
|
||||||
@@ -65,10 +67,10 @@ class RandomRangeInvocation(BaseInvocation):
|
|||||||
high: int = InputField(default=np.iinfo(np.int32).max, description="The exclusive high value")
|
high: int = InputField(default=np.iinfo(np.int32).max, description="The exclusive high value")
|
||||||
size: int = InputField(default=1, description="The number of values to generate")
|
size: int = InputField(default=1, description="The number of values to generate")
|
||||||
seed: int = InputField(
|
seed: int = InputField(
|
||||||
|
default=0,
|
||||||
ge=0,
|
ge=0,
|
||||||
le=SEED_MAX,
|
le=SEED_MAX,
|
||||||
description="The seed for the RNG (omit for random)",
|
description="The seed for the RNG (omit for random)",
|
||||||
default_factory=get_random_seed,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def invoke(self, context: InvocationContext) -> IntegerCollectionOutput:
|
def invoke(self, context: InvocationContext) -> IntegerCollectionOutput:
|
||||||
|
|||||||
@@ -1,15 +1,21 @@
|
|||||||
import re
|
|
||||||
from dataclasses import dataclass
|
|
||||||
from typing import List, Optional, Union
|
from typing import List, Optional, Union
|
||||||
|
|
||||||
import torch
|
import torch
|
||||||
from compel import Compel, ReturnedEmbeddingsType
|
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.fields import (
|
||||||
from invokeai.app.shared.fields import FieldDescriptions
|
FieldDescriptions,
|
||||||
|
Input,
|
||||||
|
InputField,
|
||||||
|
OutputField,
|
||||||
|
UIComponent,
|
||||||
|
)
|
||||||
|
from invokeai.app.invocations.primitives import ConditioningOutput
|
||||||
|
from invokeai.app.services.shared.invocation_context import InvocationContext
|
||||||
from invokeai.backend.stable_diffusion.diffusion.conditioning_data import (
|
from invokeai.backend.stable_diffusion.diffusion.conditioning_data import (
|
||||||
BasicConditioningInfo,
|
BasicConditioningInfo,
|
||||||
|
ConditioningFieldData,
|
||||||
ExtraConditioningInfo,
|
ExtraConditioningInfo,
|
||||||
SDXLConditioningInfo,
|
SDXLConditioningInfo,
|
||||||
)
|
)
|
||||||
@@ -17,24 +23,16 @@ from invokeai.backend.stable_diffusion.diffusion.conditioning_data import (
|
|||||||
from ...backend.model_management.lora import ModelPatcher
|
from ...backend.model_management.lora import ModelPatcher
|
||||||
from ...backend.model_management.models import ModelNotFoundException, ModelType
|
from ...backend.model_management.models import ModelNotFoundException, ModelType
|
||||||
from ...backend.util.devices import torch_dtype
|
from ...backend.util.devices import torch_dtype
|
||||||
|
from ..util.ti_utils import extract_ti_triggers_from_prompt
|
||||||
from .baseinvocation import (
|
from .baseinvocation import (
|
||||||
BaseInvocation,
|
BaseInvocation,
|
||||||
BaseInvocationOutput,
|
BaseInvocationOutput,
|
||||||
Input,
|
|
||||||
InputField,
|
|
||||||
InvocationContext,
|
|
||||||
OutputField,
|
|
||||||
UIComponent,
|
|
||||||
invocation,
|
invocation,
|
||||||
invocation_output,
|
invocation_output,
|
||||||
)
|
)
|
||||||
from .model import ClipField
|
from .model import ClipField
|
||||||
|
|
||||||
|
# unconditioned: Optional[torch.Tensor]
|
||||||
@dataclass
|
|
||||||
class ConditioningFieldData:
|
|
||||||
conditionings: List[BasicConditioningInfo]
|
|
||||||
# unconditioned: Optional[torch.Tensor]
|
|
||||||
|
|
||||||
|
|
||||||
# class ConditioningAlgo(str, Enum):
|
# class ConditioningAlgo(str, Enum):
|
||||||
@@ -48,7 +46,7 @@ class ConditioningFieldData:
|
|||||||
title="Prompt",
|
title="Prompt",
|
||||||
tags=["prompt", "compel"],
|
tags=["prompt", "compel"],
|
||||||
category="conditioning",
|
category="conditioning",
|
||||||
version="1.0.0",
|
version="1.0.1",
|
||||||
)
|
)
|
||||||
class CompelInvocation(BaseInvocation):
|
class CompelInvocation(BaseInvocation):
|
||||||
"""Parse prompt using compel package to conditioning."""
|
"""Parse prompt using compel package to conditioning."""
|
||||||
@@ -66,38 +64,29 @@ class CompelInvocation(BaseInvocation):
|
|||||||
|
|
||||||
@torch.no_grad()
|
@torch.no_grad()
|
||||||
def invoke(self, context: InvocationContext) -> ConditioningOutput:
|
def invoke(self, context: InvocationContext) -> ConditioningOutput:
|
||||||
tokenizer_info = context.services.model_manager.get_model(
|
tokenizer_info = context.models.load(**self.clip.tokenizer.model_dump())
|
||||||
**self.clip.tokenizer.model_dump(),
|
text_encoder_info = context.models.load(**self.clip.text_encoder.model_dump())
|
||||||
context=context,
|
|
||||||
)
|
|
||||||
text_encoder_info = context.services.model_manager.get_model(
|
|
||||||
**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.services.model_manager.get_model(
|
lora_info = context.models.load(**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
|
||||||
|
|
||||||
# loras = [(context.services.model_manager.get_model(**lora.dict(exclude={"weight"})).context.model, lora.weight) for lora in self.clip.loras]
|
# loras = [(context.models.get(**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 extract_ti_triggers_from_prompt(self.prompt):
|
||||||
name = trigger[1:-1]
|
name = trigger[1:-1]
|
||||||
try:
|
try:
|
||||||
ti_list.append(
|
ti_list.append(
|
||||||
(
|
(
|
||||||
name,
|
name,
|
||||||
context.services.model_manager.get_model(
|
context.models.load(
|
||||||
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,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -128,7 +117,7 @@ class CompelInvocation(BaseInvocation):
|
|||||||
|
|
||||||
conjunction = Compel.parse_prompt_string(self.prompt)
|
conjunction = Compel.parse_prompt_string(self.prompt)
|
||||||
|
|
||||||
if context.services.configuration.log_tokenization:
|
if context.config.get().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)
|
||||||
@@ -149,14 +138,9 @@ class CompelInvocation(BaseInvocation):
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
conditioning_name = f"{context.graph_execution_state_id}_{self.id}_conditioning"
|
conditioning_name = context.conditioning.save(conditioning_data)
|
||||||
context.services.latents.save(conditioning_name, conditioning_data)
|
|
||||||
|
|
||||||
return ConditioningOutput(
|
return ConditioningOutput.build(conditioning_name)
|
||||||
conditioning=ConditioningField(
|
|
||||||
conditioning_name=conditioning_name,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class SDXLPromptInvocationBase:
|
class SDXLPromptInvocationBase:
|
||||||
@@ -169,14 +153,8 @@ class SDXLPromptInvocationBase:
|
|||||||
lora_prefix: str,
|
lora_prefix: str,
|
||||||
zero_on_empty: bool,
|
zero_on_empty: bool,
|
||||||
):
|
):
|
||||||
tokenizer_info = context.services.model_manager.get_model(
|
tokenizer_info = context.models.load(**clip_field.tokenizer.model_dump())
|
||||||
**clip_field.tokenizer.model_dump(),
|
text_encoder_info = context.models.load(**clip_field.text_encoder.model_dump())
|
||||||
context=context,
|
|
||||||
)
|
|
||||||
text_encoder_info = context.services.model_manager.get_model(
|
|
||||||
**clip_field.text_encoder.model_dump(),
|
|
||||||
context=context,
|
|
||||||
)
|
|
||||||
|
|
||||||
# return zero on empty
|
# return zero on empty
|
||||||
if prompt == "" and zero_on_empty:
|
if prompt == "" and zero_on_empty:
|
||||||
@@ -200,27 +178,24 @@ class SDXLPromptInvocationBase:
|
|||||||
|
|
||||||
def _lora_loader():
|
def _lora_loader():
|
||||||
for lora in clip_field.loras:
|
for lora in clip_field.loras:
|
||||||
lora_info = context.services.model_manager.get_model(
|
lora_info = context.models.load(**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
|
||||||
|
|
||||||
# loras = [(context.services.model_manager.get_model(**lora.dict(exclude={"weight"})).context.model, lora.weight) for lora in self.clip.loras]
|
# loras = [(context.models.get(**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., _-]+>", prompt):
|
for trigger in extract_ti_triggers_from_prompt(prompt):
|
||||||
name = trigger[1:-1]
|
name = trigger[1:-1]
|
||||||
try:
|
try:
|
||||||
ti_list.append(
|
ti_list.append(
|
||||||
(
|
(
|
||||||
name,
|
name,
|
||||||
context.services.model_manager.get_model(
|
context.models.load(
|
||||||
model_name=name,
|
model_name=name,
|
||||||
base_model=clip_field.text_encoder.base_model,
|
base_model=clip_field.text_encoder.base_model,
|
||||||
model_type=ModelType.TextualInversion,
|
model_type=ModelType.TextualInversion,
|
||||||
context=context,
|
|
||||||
).context.model,
|
).context.model,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -253,7 +228,7 @@ class SDXLPromptInvocationBase:
|
|||||||
|
|
||||||
conjunction = Compel.parse_prompt_string(prompt)
|
conjunction = Compel.parse_prompt_string(prompt)
|
||||||
|
|
||||||
if context.services.configuration.log_tokenization:
|
if context.config.get().log_tokenization:
|
||||||
# TODO: better logging for and syntax
|
# TODO: better logging for and syntax
|
||||||
log_tokenization_for_conjunction(conjunction, tokenizer)
|
log_tokenization_for_conjunction(conjunction, tokenizer)
|
||||||
|
|
||||||
@@ -286,7 +261,7 @@ class SDXLPromptInvocationBase:
|
|||||||
title="SDXL Prompt",
|
title="SDXL Prompt",
|
||||||
tags=["sdxl", "compel", "prompt"],
|
tags=["sdxl", "compel", "prompt"],
|
||||||
category="conditioning",
|
category="conditioning",
|
||||||
version="1.0.0",
|
version="1.0.1",
|
||||||
)
|
)
|
||||||
class SDXLCompelPromptInvocation(BaseInvocation, SDXLPromptInvocationBase):
|
class SDXLCompelPromptInvocation(BaseInvocation, SDXLPromptInvocationBase):
|
||||||
"""Parse prompt using compel package to conditioning."""
|
"""Parse prompt using compel package to conditioning."""
|
||||||
@@ -368,14 +343,9 @@ class SDXLCompelPromptInvocation(BaseInvocation, SDXLPromptInvocationBase):
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
conditioning_name = f"{context.graph_execution_state_id}_{self.id}_conditioning"
|
conditioning_name = context.conditioning.save(conditioning_data)
|
||||||
context.services.latents.save(conditioning_name, conditioning_data)
|
|
||||||
|
|
||||||
return ConditioningOutput(
|
return ConditioningOutput.build(conditioning_name)
|
||||||
conditioning=ConditioningField(
|
|
||||||
conditioning_name=conditioning_name,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@invocation(
|
@invocation(
|
||||||
@@ -383,7 +353,7 @@ class SDXLCompelPromptInvocation(BaseInvocation, SDXLPromptInvocationBase):
|
|||||||
title="SDXL Refiner Prompt",
|
title="SDXL Refiner Prompt",
|
||||||
tags=["sdxl", "compel", "prompt"],
|
tags=["sdxl", "compel", "prompt"],
|
||||||
category="conditioning",
|
category="conditioning",
|
||||||
version="1.0.0",
|
version="1.0.1",
|
||||||
)
|
)
|
||||||
class SDXLRefinerCompelPromptInvocation(BaseInvocation, SDXLPromptInvocationBase):
|
class SDXLRefinerCompelPromptInvocation(BaseInvocation, SDXLPromptInvocationBase):
|
||||||
"""Parse prompt using compel package to conditioning."""
|
"""Parse prompt using compel package to conditioning."""
|
||||||
@@ -421,14 +391,9 @@ class SDXLRefinerCompelPromptInvocation(BaseInvocation, SDXLPromptInvocationBase
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
conditioning_name = f"{context.graph_execution_state_id}_{self.id}_conditioning"
|
conditioning_name = context.conditioning.save(conditioning_data)
|
||||||
context.services.latents.save(conditioning_name, conditioning_data)
|
|
||||||
|
|
||||||
return ConditioningOutput(
|
return ConditioningOutput.build(conditioning_name)
|
||||||
conditioning=ConditioningField(
|
|
||||||
conditioning_name=conditioning_name,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@invocation_output("clip_skip_output")
|
@invocation_output("clip_skip_output")
|
||||||
|
|||||||
14
invokeai/app/invocations/constants.py
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
from typing import Literal
|
||||||
|
|
||||||
|
from invokeai.backend.stable_diffusion.schedulers import SCHEDULER_MAP
|
||||||
|
|
||||||
|
LATENT_SCALE_FACTOR = 8
|
||||||
|
"""
|
||||||
|
HACK: Many nodes are currently hard-coded to use a fixed latent scale factor of 8. This is fragile, and will need to
|
||||||
|
be addressed if future models use a different latent scale factor. Also, note that there may be places where the scale
|
||||||
|
factor is hard-coded to a literal '8' rather than using this constant.
|
||||||
|
The ratio of image:latent dimensions is LATENT_SCALE_FACTOR:1, or 8:1.
|
||||||
|
"""
|
||||||
|
|
||||||
|
SCHEDULER_NAME_VALUES = Literal[tuple(SCHEDULER_MAP.keys())]
|
||||||
|
"""A literal type representing the valid scheduler names."""
|
||||||
@@ -17,29 +17,33 @@ from controlnet_aux import (
|
|||||||
MidasDetector,
|
MidasDetector,
|
||||||
MLSDdetector,
|
MLSDdetector,
|
||||||
NormalBaeDetector,
|
NormalBaeDetector,
|
||||||
OpenposeDetector,
|
|
||||||
PidiNetDetector,
|
PidiNetDetector,
|
||||||
SamDetector,
|
SamDetector,
|
||||||
ZoeDetector,
|
ZoeDetector,
|
||||||
)
|
)
|
||||||
from controlnet_aux.util import HWC3, ade_palette
|
from controlnet_aux.util import HWC3, ade_palette
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
|
||||||
|
|
||||||
from invokeai.app.invocations.primitives import ImageField, ImageOutput
|
from invokeai.app.invocations.fields import (
|
||||||
from invokeai.app.services.image_records.image_records_common import ImageCategory, ResourceOrigin
|
FieldDescriptions,
|
||||||
from invokeai.app.shared.fields import FieldDescriptions
|
ImageField,
|
||||||
|
Input,
|
||||||
|
InputField,
|
||||||
|
OutputField,
|
||||||
|
WithBoard,
|
||||||
|
WithMetadata,
|
||||||
|
)
|
||||||
|
from invokeai.app.invocations.primitives import ImageOutput
|
||||||
|
from invokeai.app.invocations.util import validate_begin_end_step, validate_weights
|
||||||
|
from invokeai.app.services.shared.invocation_context import InvocationContext
|
||||||
|
from invokeai.backend.image_util.depth_anything import DepthAnythingDetector
|
||||||
|
from invokeai.backend.image_util.dw_openpose import DWOpenposeDetector
|
||||||
|
from invokeai.backend.model_management.models.base import BaseModelType
|
||||||
|
|
||||||
from ...backend.model_management import BaseModelType
|
|
||||||
from .baseinvocation import (
|
from .baseinvocation import (
|
||||||
BaseInvocation,
|
BaseInvocation,
|
||||||
BaseInvocationOutput,
|
BaseInvocationOutput,
|
||||||
Input,
|
|
||||||
InputField,
|
|
||||||
InvocationContext,
|
|
||||||
OutputField,
|
|
||||||
WithMetadata,
|
|
||||||
WithWorkflow,
|
|
||||||
invocation,
|
invocation,
|
||||||
invocation_output,
|
invocation_output,
|
||||||
)
|
)
|
||||||
@@ -76,17 +80,16 @@ class ControlField(BaseModel):
|
|||||||
resize_mode: CONTROLNET_RESIZE_VALUES = Field(default="just_resize", description="The resize mode to use")
|
resize_mode: CONTROLNET_RESIZE_VALUES = Field(default="just_resize", description="The resize mode to use")
|
||||||
|
|
||||||
@field_validator("control_weight")
|
@field_validator("control_weight")
|
||||||
|
@classmethod
|
||||||
def validate_control_weight(cls, v):
|
def validate_control_weight(cls, v):
|
||||||
"""Validate that all control weights in the valid range"""
|
validate_weights(v)
|
||||||
if isinstance(v, list):
|
|
||||||
for i in v:
|
|
||||||
if i < -1 or i > 2:
|
|
||||||
raise ValueError("Control weights must be within -1 to 2 range")
|
|
||||||
else:
|
|
||||||
if v < -1 or v > 2:
|
|
||||||
raise ValueError("Control weights must be within -1 to 2 range")
|
|
||||||
return v
|
return v
|
||||||
|
|
||||||
|
@model_validator(mode="after")
|
||||||
|
def validate_begin_end_step_percent(self):
|
||||||
|
validate_begin_end_step(self.begin_step_percent, self.end_step_percent)
|
||||||
|
return self
|
||||||
|
|
||||||
|
|
||||||
@invocation_output("control_output")
|
@invocation_output("control_output")
|
||||||
class ControlOutput(BaseInvocationOutput):
|
class ControlOutput(BaseInvocationOutput):
|
||||||
@@ -96,17 +99,17 @@ class ControlOutput(BaseInvocationOutput):
|
|||||||
control: ControlField = OutputField(description=FieldDescriptions.control)
|
control: ControlField = OutputField(description=FieldDescriptions.control)
|
||||||
|
|
||||||
|
|
||||||
@invocation("controlnet", title="ControlNet", tags=["controlnet"], category="controlnet", version="1.1.0")
|
@invocation("controlnet", title="ControlNet", tags=["controlnet"], category="controlnet", version="1.1.1")
|
||||||
class ControlNetInvocation(BaseInvocation):
|
class ControlNetInvocation(BaseInvocation):
|
||||||
"""Collects ControlNet info to pass to other nodes"""
|
"""Collects ControlNet info to pass to other nodes"""
|
||||||
|
|
||||||
image: ImageField = InputField(description="The control image")
|
image: ImageField = InputField(description="The control image")
|
||||||
control_model: ControlNetModelField = InputField(description=FieldDescriptions.controlnet_model, input=Input.Direct)
|
control_model: ControlNetModelField = InputField(description=FieldDescriptions.controlnet_model, input=Input.Direct)
|
||||||
control_weight: Union[float, List[float]] = InputField(
|
control_weight: Union[float, List[float]] = InputField(
|
||||||
default=1.0, description="The weight given to the ControlNet"
|
default=1.0, ge=-1, le=2, description="The weight given to the ControlNet"
|
||||||
)
|
)
|
||||||
begin_step_percent: float = InputField(
|
begin_step_percent: float = InputField(
|
||||||
default=0, ge=-1, le=2, description="When the ControlNet is first applied (% of total steps)"
|
default=0, ge=0, le=1, description="When the ControlNet is first applied (% of total steps)"
|
||||||
)
|
)
|
||||||
end_step_percent: float = InputField(
|
end_step_percent: float = InputField(
|
||||||
default=1, ge=0, le=1, description="When the ControlNet is last applied (% of total steps)"
|
default=1, ge=0, le=1, description="When the ControlNet is last applied (% of total steps)"
|
||||||
@@ -114,6 +117,17 @@ class ControlNetInvocation(BaseInvocation):
|
|||||||
control_mode: CONTROLNET_MODE_VALUES = InputField(default="balanced", description="The control mode used")
|
control_mode: CONTROLNET_MODE_VALUES = InputField(default="balanced", description="The control mode used")
|
||||||
resize_mode: CONTROLNET_RESIZE_VALUES = InputField(default="just_resize", description="The resize mode used")
|
resize_mode: CONTROLNET_RESIZE_VALUES = InputField(default="just_resize", description="The resize mode used")
|
||||||
|
|
||||||
|
@field_validator("control_weight")
|
||||||
|
@classmethod
|
||||||
|
def validate_control_weight(cls, v):
|
||||||
|
validate_weights(v)
|
||||||
|
return v
|
||||||
|
|
||||||
|
@model_validator(mode="after")
|
||||||
|
def validate_begin_end_step_percent(self) -> "ControlNetInvocation":
|
||||||
|
validate_begin_end_step(self.begin_step_percent, self.end_step_percent)
|
||||||
|
return self
|
||||||
|
|
||||||
def invoke(self, context: InvocationContext) -> ControlOutput:
|
def invoke(self, context: InvocationContext) -> ControlOutput:
|
||||||
return ControlOutput(
|
return ControlOutput(
|
||||||
control=ControlField(
|
control=ControlField(
|
||||||
@@ -129,7 +143,7 @@ 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, WithMetadata, WithWorkflow):
|
class ImageProcessorInvocation(BaseInvocation, WithMetadata, WithBoard):
|
||||||
"""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")
|
||||||
@@ -139,22 +153,13 @@ class ImageProcessorInvocation(BaseInvocation, WithMetadata, WithWorkflow):
|
|||||||
return image
|
return image
|
||||||
|
|
||||||
def invoke(self, context: InvocationContext) -> ImageOutput:
|
def invoke(self, context: InvocationContext) -> ImageOutput:
|
||||||
raw_image = context.services.images.get_pil_image(self.image.image_name)
|
raw_image = context.images.get_pil(self.image.image_name)
|
||||||
# image type should be PIL.PngImagePlugin.PngImageFile ?
|
# image type should be PIL.PngImagePlugin.PngImageFile ?
|
||||||
processed_image = self.run_processor(raw_image)
|
processed_image = self.run_processor(raw_image)
|
||||||
|
|
||||||
# currently can't see processed image in node UI without a showImage node,
|
# currently can't see processed image in node UI without a showImage node,
|
||||||
# so for now setting image_type to RESULT instead of INTERMEDIATE so will get saved in gallery
|
# so for now setting image_type to RESULT instead of INTERMEDIATE so will get saved in gallery
|
||||||
image_dto = context.services.images.create(
|
image_dto = context.images.save(image=processed_image)
|
||||||
image=processed_image,
|
|
||||||
image_origin=ResourceOrigin.INTERNAL,
|
|
||||||
image_category=ImageCategory.CONTROL,
|
|
||||||
session_id=context.graph_execution_state_id,
|
|
||||||
node_id=self.id,
|
|
||||||
is_intermediate=self.is_intermediate,
|
|
||||||
metadata=self.metadata,
|
|
||||||
workflow=self.workflow,
|
|
||||||
)
|
|
||||||
|
|
||||||
"""Builds an ImageOutput and its ImageField"""
|
"""Builds an ImageOutput and its ImageField"""
|
||||||
processed_image_field = ImageField(image_name=image_dto.image_name)
|
processed_image_field = ImageField(image_name=image_dto.image_name)
|
||||||
@@ -173,7 +178,7 @@ class ImageProcessorInvocation(BaseInvocation, WithMetadata, WithWorkflow):
|
|||||||
title="Canny Processor",
|
title="Canny Processor",
|
||||||
tags=["controlnet", "canny"],
|
tags=["controlnet", "canny"],
|
||||||
category="controlnet",
|
category="controlnet",
|
||||||
version="1.1.0",
|
version="1.2.1",
|
||||||
)
|
)
|
||||||
class CannyImageProcessorInvocation(ImageProcessorInvocation):
|
class CannyImageProcessorInvocation(ImageProcessorInvocation):
|
||||||
"""Canny edge detection for ControlNet"""
|
"""Canny edge detection for ControlNet"""
|
||||||
@@ -196,7 +201,7 @@ class CannyImageProcessorInvocation(ImageProcessorInvocation):
|
|||||||
title="HED (softedge) Processor",
|
title="HED (softedge) Processor",
|
||||||
tags=["controlnet", "hed", "softedge"],
|
tags=["controlnet", "hed", "softedge"],
|
||||||
category="controlnet",
|
category="controlnet",
|
||||||
version="1.1.0",
|
version="1.2.1",
|
||||||
)
|
)
|
||||||
class HedImageProcessorInvocation(ImageProcessorInvocation):
|
class HedImageProcessorInvocation(ImageProcessorInvocation):
|
||||||
"""Applies HED edge detection to image"""
|
"""Applies HED edge detection to image"""
|
||||||
@@ -225,7 +230,7 @@ class HedImageProcessorInvocation(ImageProcessorInvocation):
|
|||||||
title="Lineart Processor",
|
title="Lineart Processor",
|
||||||
tags=["controlnet", "lineart"],
|
tags=["controlnet", "lineart"],
|
||||||
category="controlnet",
|
category="controlnet",
|
||||||
version="1.1.0",
|
version="1.2.1",
|
||||||
)
|
)
|
||||||
class LineartImageProcessorInvocation(ImageProcessorInvocation):
|
class LineartImageProcessorInvocation(ImageProcessorInvocation):
|
||||||
"""Applies line art processing to image"""
|
"""Applies line art processing to image"""
|
||||||
@@ -247,7 +252,7 @@ class LineartImageProcessorInvocation(ImageProcessorInvocation):
|
|||||||
title="Lineart Anime Processor",
|
title="Lineart Anime Processor",
|
||||||
tags=["controlnet", "lineart", "anime"],
|
tags=["controlnet", "lineart", "anime"],
|
||||||
category="controlnet",
|
category="controlnet",
|
||||||
version="1.1.0",
|
version="1.2.1",
|
||||||
)
|
)
|
||||||
class LineartAnimeImageProcessorInvocation(ImageProcessorInvocation):
|
class LineartAnimeImageProcessorInvocation(ImageProcessorInvocation):
|
||||||
"""Applies line art anime processing to image"""
|
"""Applies line art anime processing to image"""
|
||||||
@@ -265,37 +270,12 @@ class LineartAnimeImageProcessorInvocation(ImageProcessorInvocation):
|
|||||||
return processed_image
|
return processed_image
|
||||||
|
|
||||||
|
|
||||||
@invocation(
|
|
||||||
"openpose_image_processor",
|
|
||||||
title="Openpose Processor",
|
|
||||||
tags=["controlnet", "openpose", "pose"],
|
|
||||||
category="controlnet",
|
|
||||||
version="1.1.0",
|
|
||||||
)
|
|
||||||
class OpenposeImageProcessorInvocation(ImageProcessorInvocation):
|
|
||||||
"""Applies Openpose processing to image"""
|
|
||||||
|
|
||||||
hand_and_face: bool = InputField(default=False, description="Whether to use hands and face mode")
|
|
||||||
detect_resolution: int = InputField(default=512, ge=0, description=FieldDescriptions.detect_res)
|
|
||||||
image_resolution: int = InputField(default=512, ge=0, description=FieldDescriptions.image_res)
|
|
||||||
|
|
||||||
def run_processor(self, image):
|
|
||||||
openpose_processor = OpenposeDetector.from_pretrained("lllyasviel/Annotators")
|
|
||||||
processed_image = openpose_processor(
|
|
||||||
image,
|
|
||||||
detect_resolution=self.detect_resolution,
|
|
||||||
image_resolution=self.image_resolution,
|
|
||||||
hand_and_face=self.hand_and_face,
|
|
||||||
)
|
|
||||||
return processed_image
|
|
||||||
|
|
||||||
|
|
||||||
@invocation(
|
@invocation(
|
||||||
"midas_depth_image_processor",
|
"midas_depth_image_processor",
|
||||||
title="Midas Depth Processor",
|
title="Midas Depth Processor",
|
||||||
tags=["controlnet", "midas"],
|
tags=["controlnet", "midas"],
|
||||||
category="controlnet",
|
category="controlnet",
|
||||||
version="1.1.0",
|
version="1.2.1",
|
||||||
)
|
)
|
||||||
class MidasDepthImageProcessorInvocation(ImageProcessorInvocation):
|
class MidasDepthImageProcessorInvocation(ImageProcessorInvocation):
|
||||||
"""Applies Midas depth processing to image"""
|
"""Applies Midas depth processing to image"""
|
||||||
@@ -322,7 +302,7 @@ class MidasDepthImageProcessorInvocation(ImageProcessorInvocation):
|
|||||||
title="Normal BAE Processor",
|
title="Normal BAE Processor",
|
||||||
tags=["controlnet"],
|
tags=["controlnet"],
|
||||||
category="controlnet",
|
category="controlnet",
|
||||||
version="1.1.0",
|
version="1.2.1",
|
||||||
)
|
)
|
||||||
class NormalbaeImageProcessorInvocation(ImageProcessorInvocation):
|
class NormalbaeImageProcessorInvocation(ImageProcessorInvocation):
|
||||||
"""Applies NormalBae processing to image"""
|
"""Applies NormalBae processing to image"""
|
||||||
@@ -339,7 +319,7 @@ class NormalbaeImageProcessorInvocation(ImageProcessorInvocation):
|
|||||||
|
|
||||||
|
|
||||||
@invocation(
|
@invocation(
|
||||||
"mlsd_image_processor", title="MLSD Processor", tags=["controlnet", "mlsd"], category="controlnet", version="1.1.0"
|
"mlsd_image_processor", title="MLSD Processor", tags=["controlnet", "mlsd"], category="controlnet", version="1.2.1"
|
||||||
)
|
)
|
||||||
class MlsdImageProcessorInvocation(ImageProcessorInvocation):
|
class MlsdImageProcessorInvocation(ImageProcessorInvocation):
|
||||||
"""Applies MLSD processing to image"""
|
"""Applies MLSD processing to image"""
|
||||||
@@ -362,7 +342,7 @@ class MlsdImageProcessorInvocation(ImageProcessorInvocation):
|
|||||||
|
|
||||||
|
|
||||||
@invocation(
|
@invocation(
|
||||||
"pidi_image_processor", title="PIDI Processor", tags=["controlnet", "pidi"], category="controlnet", version="1.1.0"
|
"pidi_image_processor", title="PIDI Processor", tags=["controlnet", "pidi"], category="controlnet", version="1.2.1"
|
||||||
)
|
)
|
||||||
class PidiImageProcessorInvocation(ImageProcessorInvocation):
|
class PidiImageProcessorInvocation(ImageProcessorInvocation):
|
||||||
"""Applies PIDI processing to image"""
|
"""Applies PIDI processing to image"""
|
||||||
@@ -389,7 +369,7 @@ class PidiImageProcessorInvocation(ImageProcessorInvocation):
|
|||||||
title="Content Shuffle Processor",
|
title="Content Shuffle Processor",
|
||||||
tags=["controlnet", "contentshuffle"],
|
tags=["controlnet", "contentshuffle"],
|
||||||
category="controlnet",
|
category="controlnet",
|
||||||
version="1.1.0",
|
version="1.2.1",
|
||||||
)
|
)
|
||||||
class ContentShuffleImageProcessorInvocation(ImageProcessorInvocation):
|
class ContentShuffleImageProcessorInvocation(ImageProcessorInvocation):
|
||||||
"""Applies content shuffle processing to image"""
|
"""Applies content shuffle processing to image"""
|
||||||
@@ -419,7 +399,7 @@ class ContentShuffleImageProcessorInvocation(ImageProcessorInvocation):
|
|||||||
title="Zoe (Depth) Processor",
|
title="Zoe (Depth) Processor",
|
||||||
tags=["controlnet", "zoe", "depth"],
|
tags=["controlnet", "zoe", "depth"],
|
||||||
category="controlnet",
|
category="controlnet",
|
||||||
version="1.1.0",
|
version="1.2.1",
|
||||||
)
|
)
|
||||||
class ZoeDepthImageProcessorInvocation(ImageProcessorInvocation):
|
class ZoeDepthImageProcessorInvocation(ImageProcessorInvocation):
|
||||||
"""Applies Zoe depth processing to image"""
|
"""Applies Zoe depth processing to image"""
|
||||||
@@ -435,7 +415,7 @@ class ZoeDepthImageProcessorInvocation(ImageProcessorInvocation):
|
|||||||
title="Mediapipe Face Processor",
|
title="Mediapipe Face Processor",
|
||||||
tags=["controlnet", "mediapipe", "face"],
|
tags=["controlnet", "mediapipe", "face"],
|
||||||
category="controlnet",
|
category="controlnet",
|
||||||
version="1.1.0",
|
version="1.2.1",
|
||||||
)
|
)
|
||||||
class MediapipeFaceProcessorInvocation(ImageProcessorInvocation):
|
class MediapipeFaceProcessorInvocation(ImageProcessorInvocation):
|
||||||
"""Applies mediapipe face processing to image"""
|
"""Applies mediapipe face processing to image"""
|
||||||
@@ -458,7 +438,7 @@ class MediapipeFaceProcessorInvocation(ImageProcessorInvocation):
|
|||||||
title="Leres (Depth) Processor",
|
title="Leres (Depth) Processor",
|
||||||
tags=["controlnet", "leres", "depth"],
|
tags=["controlnet", "leres", "depth"],
|
||||||
category="controlnet",
|
category="controlnet",
|
||||||
version="1.1.0",
|
version="1.2.1",
|
||||||
)
|
)
|
||||||
class LeresImageProcessorInvocation(ImageProcessorInvocation):
|
class LeresImageProcessorInvocation(ImageProcessorInvocation):
|
||||||
"""Applies leres processing to image"""
|
"""Applies leres processing to image"""
|
||||||
@@ -487,7 +467,7 @@ class LeresImageProcessorInvocation(ImageProcessorInvocation):
|
|||||||
title="Tile Resample Processor",
|
title="Tile Resample Processor",
|
||||||
tags=["controlnet", "tile"],
|
tags=["controlnet", "tile"],
|
||||||
category="controlnet",
|
category="controlnet",
|
||||||
version="1.1.0",
|
version="1.2.1",
|
||||||
)
|
)
|
||||||
class TileResamplerProcessorInvocation(ImageProcessorInvocation):
|
class TileResamplerProcessorInvocation(ImageProcessorInvocation):
|
||||||
"""Tile resampler processor"""
|
"""Tile resampler processor"""
|
||||||
@@ -527,7 +507,7 @@ class TileResamplerProcessorInvocation(ImageProcessorInvocation):
|
|||||||
title="Segment Anything Processor",
|
title="Segment Anything Processor",
|
||||||
tags=["controlnet", "segmentanything"],
|
tags=["controlnet", "segmentanything"],
|
||||||
category="controlnet",
|
category="controlnet",
|
||||||
version="1.1.0",
|
version="1.2.1",
|
||||||
)
|
)
|
||||||
class SegmentAnythingProcessorInvocation(ImageProcessorInvocation):
|
class SegmentAnythingProcessorInvocation(ImageProcessorInvocation):
|
||||||
"""Applies segment anything processing to image"""
|
"""Applies segment anything processing to image"""
|
||||||
@@ -569,7 +549,7 @@ class SamDetectorReproducibleColors(SamDetector):
|
|||||||
title="Color Map Processor",
|
title="Color Map Processor",
|
||||||
tags=["controlnet"],
|
tags=["controlnet"],
|
||||||
category="controlnet",
|
category="controlnet",
|
||||||
version="1.1.0",
|
version="1.2.1",
|
||||||
)
|
)
|
||||||
class ColorMapImageProcessorInvocation(ImageProcessorInvocation):
|
class ColorMapImageProcessorInvocation(ImageProcessorInvocation):
|
||||||
"""Generates a color map from the provided image"""
|
"""Generates a color map from the provided image"""
|
||||||
@@ -592,3 +572,60 @@ class ColorMapImageProcessorInvocation(ImageProcessorInvocation):
|
|||||||
color_map = cv2.resize(color_map, (width, height), interpolation=cv2.INTER_NEAREST)
|
color_map = cv2.resize(color_map, (width, height), interpolation=cv2.INTER_NEAREST)
|
||||||
color_map = Image.fromarray(color_map)
|
color_map = Image.fromarray(color_map)
|
||||||
return color_map
|
return color_map
|
||||||
|
|
||||||
|
|
||||||
|
DEPTH_ANYTHING_MODEL_SIZES = Literal["large", "base", "small"]
|
||||||
|
|
||||||
|
|
||||||
|
@invocation(
|
||||||
|
"depth_anything_image_processor",
|
||||||
|
title="Depth Anything Processor",
|
||||||
|
tags=["controlnet", "depth", "depth anything"],
|
||||||
|
category="controlnet",
|
||||||
|
version="1.0.0",
|
||||||
|
)
|
||||||
|
class DepthAnythingImageProcessorInvocation(ImageProcessorInvocation):
|
||||||
|
"""Generates a depth map based on the Depth Anything algorithm"""
|
||||||
|
|
||||||
|
model_size: DEPTH_ANYTHING_MODEL_SIZES = InputField(
|
||||||
|
default="small", description="The size of the depth model to use"
|
||||||
|
)
|
||||||
|
resolution: int = InputField(default=512, ge=64, multiple_of=64, description=FieldDescriptions.image_res)
|
||||||
|
offload: bool = InputField(default=False)
|
||||||
|
|
||||||
|
def run_processor(self, image: Image.Image):
|
||||||
|
depth_anything_detector = DepthAnythingDetector()
|
||||||
|
depth_anything_detector.load_model(model_size=self.model_size)
|
||||||
|
|
||||||
|
if image.mode == "RGBA":
|
||||||
|
image = image.convert("RGB")
|
||||||
|
|
||||||
|
processed_image = depth_anything_detector(image=image, resolution=self.resolution, offload=self.offload)
|
||||||
|
return processed_image
|
||||||
|
|
||||||
|
|
||||||
|
@invocation(
|
||||||
|
"dw_openpose_image_processor",
|
||||||
|
title="DW Openpose Image Processor",
|
||||||
|
tags=["controlnet", "dwpose", "openpose"],
|
||||||
|
category="controlnet",
|
||||||
|
version="1.0.0",
|
||||||
|
)
|
||||||
|
class DWOpenposeImageProcessorInvocation(ImageProcessorInvocation):
|
||||||
|
"""Generates an openpose pose from an image using DWPose"""
|
||||||
|
|
||||||
|
draw_body: bool = InputField(default=True)
|
||||||
|
draw_face: bool = InputField(default=False)
|
||||||
|
draw_hands: bool = InputField(default=False)
|
||||||
|
image_resolution: int = InputField(default=512, ge=0, description=FieldDescriptions.image_res)
|
||||||
|
|
||||||
|
def run_processor(self, image):
|
||||||
|
dw_openpose = DWOpenposeDetector()
|
||||||
|
processed_image = dw_openpose(
|
||||||
|
image,
|
||||||
|
draw_face=self.draw_face,
|
||||||
|
draw_hands=self.draw_hands,
|
||||||
|
draw_body=self.draw_body,
|
||||||
|
resolution=self.image_resolution,
|
||||||
|
)
|
||||||
|
return processed_image
|
||||||
|
|||||||
@@ -32,13 +32,15 @@ for d in Path(__file__).parent.iterdir():
|
|||||||
if module_name in globals():
|
if module_name in globals():
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# we have a legit module to import
|
# load the module, appending adding a suffix to identify it as a custom node pack
|
||||||
spec = spec_from_file_location(module_name, init.absolute())
|
spec = spec_from_file_location(module_name, init.absolute())
|
||||||
|
|
||||||
if spec is None or spec.loader is None:
|
if spec is None or spec.loader is None:
|
||||||
logger.warn(f"Could not load {init}")
|
logger.warn(f"Could not load {init}")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
logger.info(f"Loading node pack {module_name}")
|
||||||
|
|
||||||
module = module_from_spec(spec)
|
module = module_from_spec(spec)
|
||||||
sys.modules[spec.name] = module
|
sys.modules[spec.name] = module
|
||||||
spec.loader.exec_module(module)
|
spec.loader.exec_module(module)
|
||||||
@@ -47,5 +49,5 @@ for d in Path(__file__).parent.iterdir():
|
|||||||
|
|
||||||
del init, module_name
|
del init, module_name
|
||||||
|
|
||||||
|
if loaded_count > 0:
|
||||||
logger.info(f"Loaded {loaded_count} modules from {Path(__file__).parent}")
|
logger.info(f"Loaded {loaded_count} node packs from {Path(__file__).parent}")
|
||||||
|
|||||||
@@ -5,22 +5,24 @@ import cv2 as cv
|
|||||||
import numpy
|
import numpy
|
||||||
from PIL import Image, ImageOps
|
from PIL import Image, ImageOps
|
||||||
|
|
||||||
from invokeai.app.invocations.primitives import ImageField, ImageOutput
|
from invokeai.app.invocations.fields import ImageField
|
||||||
from invokeai.app.services.image_records.image_records_common import ImageCategory, ResourceOrigin
|
from invokeai.app.invocations.primitives import ImageOutput
|
||||||
|
from invokeai.app.services.shared.invocation_context import InvocationContext
|
||||||
|
|
||||||
from .baseinvocation import BaseInvocation, InputField, InvocationContext, WithMetadata, WithWorkflow, invocation
|
from .baseinvocation import BaseInvocation, invocation
|
||||||
|
from .fields import InputField, WithBoard, WithMetadata
|
||||||
|
|
||||||
|
|
||||||
@invocation("cv_inpaint", title="OpenCV Inpaint", tags=["opencv", "inpaint"], category="inpaint", version="1.1.0")
|
@invocation("cv_inpaint", title="OpenCV Inpaint", tags=["opencv", "inpaint"], category="inpaint", version="1.2.1")
|
||||||
class CvInpaintInvocation(BaseInvocation, WithMetadata, WithWorkflow):
|
class CvInpaintInvocation(BaseInvocation, WithMetadata, WithBoard):
|
||||||
"""Simple inpaint using opencv."""
|
"""Simple inpaint using opencv."""
|
||||||
|
|
||||||
image: ImageField = InputField(description="The image to inpaint")
|
image: ImageField = InputField(description="The image to inpaint")
|
||||||
mask: ImageField = InputField(description="The mask to use when inpainting")
|
mask: ImageField = InputField(description="The mask to use when inpainting")
|
||||||
|
|
||||||
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.images.get_pil(self.image.image_name)
|
||||||
mask = context.services.images.get_pil_image(self.mask.image_name)
|
mask = context.images.get_pil(self.mask.image_name)
|
||||||
|
|
||||||
# Convert to cv image/mask
|
# Convert to cv image/mask
|
||||||
# TODO: consider making these utility functions
|
# TODO: consider making these utility functions
|
||||||
@@ -34,18 +36,6 @@ class CvInpaintInvocation(BaseInvocation, WithMetadata, WithWorkflow):
|
|||||||
# TODO: consider making a utility function
|
# TODO: consider making a utility function
|
||||||
image_inpainted = Image.fromarray(cv.cvtColor(cv_inpainted, cv.COLOR_BGR2RGB))
|
image_inpainted = Image.fromarray(cv.cvtColor(cv_inpainted, cv.COLOR_BGR2RGB))
|
||||||
|
|
||||||
image_dto = context.services.images.create(
|
image_dto = context.images.save(image=image_inpainted)
|
||||||
image=image_inpainted,
|
|
||||||
image_origin=ResourceOrigin.INTERNAL,
|
|
||||||
image_category=ImageCategory.GENERAL,
|
|
||||||
node_id=self.id,
|
|
||||||
session_id=context.graph_execution_state_id,
|
|
||||||
is_intermediate=self.is_intermediate,
|
|
||||||
workflow=self.workflow,
|
|
||||||
)
|
|
||||||
|
|
||||||
return ImageOutput(
|
return ImageOutput.build(image_dto)
|
||||||
image=ImageField(image_name=image_dto.image_name),
|
|
||||||
width=image_dto.width,
|
|
||||||
height=image_dto.height,
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -13,16 +13,13 @@ from pydantic import field_validator
|
|||||||
import invokeai.assets.fonts as font_assets
|
import invokeai.assets.fonts as font_assets
|
||||||
from invokeai.app.invocations.baseinvocation import (
|
from invokeai.app.invocations.baseinvocation import (
|
||||||
BaseInvocation,
|
BaseInvocation,
|
||||||
InputField,
|
|
||||||
InvocationContext,
|
|
||||||
OutputField,
|
|
||||||
WithMetadata,
|
|
||||||
WithWorkflow,
|
|
||||||
invocation,
|
invocation,
|
||||||
invocation_output,
|
invocation_output,
|
||||||
)
|
)
|
||||||
from invokeai.app.invocations.primitives import ImageField, ImageOutput
|
from invokeai.app.invocations.fields import ImageField, InputField, OutputField, WithBoard, WithMetadata
|
||||||
from invokeai.app.services.image_records.image_records_common import ImageCategory, ResourceOrigin
|
from invokeai.app.invocations.primitives import ImageOutput
|
||||||
|
from invokeai.app.services.image_records.image_records_common import ImageCategory
|
||||||
|
from invokeai.app.services.shared.invocation_context import InvocationContext
|
||||||
|
|
||||||
|
|
||||||
@invocation_output("face_mask_output")
|
@invocation_output("face_mask_output")
|
||||||
@@ -307,37 +304,37 @@ def extract_face(
|
|||||||
|
|
||||||
# Adjust the crop boundaries to stay within the original image's dimensions
|
# Adjust the crop boundaries to stay within the original image's dimensions
|
||||||
if x_min < 0:
|
if x_min < 0:
|
||||||
context.services.logger.warning("FaceTools --> -X-axis padding reached image edge.")
|
context.logger.warning("FaceTools --> -X-axis padding reached image edge.")
|
||||||
x_max -= x_min
|
x_max -= x_min
|
||||||
x_min = 0
|
x_min = 0
|
||||||
elif x_max > mask.width:
|
elif x_max > mask.width:
|
||||||
context.services.logger.warning("FaceTools --> +X-axis padding reached image edge.")
|
context.logger.warning("FaceTools --> +X-axis padding reached image edge.")
|
||||||
x_min -= x_max - mask.width
|
x_min -= x_max - mask.width
|
||||||
x_max = mask.width
|
x_max = mask.width
|
||||||
|
|
||||||
if y_min < 0:
|
if y_min < 0:
|
||||||
context.services.logger.warning("FaceTools --> +Y-axis padding reached image edge.")
|
context.logger.warning("FaceTools --> +Y-axis padding reached image edge.")
|
||||||
y_max -= y_min
|
y_max -= y_min
|
||||||
y_min = 0
|
y_min = 0
|
||||||
elif y_max > mask.height:
|
elif y_max > mask.height:
|
||||||
context.services.logger.warning("FaceTools --> -Y-axis padding reached image edge.")
|
context.logger.warning("FaceTools --> -Y-axis padding reached image edge.")
|
||||||
y_min -= y_max - mask.height
|
y_min -= y_max - mask.height
|
||||||
y_max = mask.height
|
y_max = mask.height
|
||||||
|
|
||||||
# Ensure the crop is square and adjust the boundaries if needed
|
# Ensure the crop is square and adjust the boundaries if needed
|
||||||
if x_max - x_min != crop_size:
|
if x_max - x_min != crop_size:
|
||||||
context.services.logger.warning("FaceTools --> Limiting x-axis padding to constrain bounding box to a square.")
|
context.logger.warning("FaceTools --> Limiting x-axis padding to constrain bounding box to a square.")
|
||||||
diff = crop_size - (x_max - x_min)
|
diff = crop_size - (x_max - x_min)
|
||||||
x_min -= diff // 2
|
x_min -= diff // 2
|
||||||
x_max += diff - diff // 2
|
x_max += diff - diff // 2
|
||||||
|
|
||||||
if y_max - y_min != crop_size:
|
if y_max - y_min != crop_size:
|
||||||
context.services.logger.warning("FaceTools --> Limiting y-axis padding to constrain bounding box to a square.")
|
context.logger.warning("FaceTools --> Limiting y-axis padding to constrain bounding box to a square.")
|
||||||
diff = crop_size - (y_max - y_min)
|
diff = crop_size - (y_max - y_min)
|
||||||
y_min -= diff // 2
|
y_min -= diff // 2
|
||||||
y_max += diff - diff // 2
|
y_max += diff - diff // 2
|
||||||
|
|
||||||
context.services.logger.info(f"FaceTools --> Calculated bounding box (8 multiple): {crop_size}")
|
context.logger.info(f"FaceTools --> Calculated bounding box (8 multiple): {crop_size}")
|
||||||
|
|
||||||
# Crop the output image to the specified size with the center of the face mesh as the center.
|
# Crop the output image to the specified size with the center of the face mesh as the center.
|
||||||
mask = mask.crop((x_min, y_min, x_max, y_max))
|
mask = mask.crop((x_min, y_min, x_max, y_max))
|
||||||
@@ -369,7 +366,7 @@ def get_faces_list(
|
|||||||
|
|
||||||
# Generate the face box mask and get the center of the face.
|
# Generate the face box mask and get the center of the face.
|
||||||
if not should_chunk:
|
if not should_chunk:
|
||||||
context.services.logger.info("FaceTools --> Attempting full image face detection.")
|
context.logger.info("FaceTools --> Attempting full image face detection.")
|
||||||
result = generate_face_box_mask(
|
result = generate_face_box_mask(
|
||||||
context=context,
|
context=context,
|
||||||
minimum_confidence=minimum_confidence,
|
minimum_confidence=minimum_confidence,
|
||||||
@@ -381,7 +378,7 @@ def get_faces_list(
|
|||||||
draw_mesh=draw_mesh,
|
draw_mesh=draw_mesh,
|
||||||
)
|
)
|
||||||
if should_chunk or len(result) == 0:
|
if should_chunk or len(result) == 0:
|
||||||
context.services.logger.info("FaceTools --> Chunking image (chunk toggled on, or no face found in full image).")
|
context.logger.info("FaceTools --> Chunking image (chunk toggled on, or no face found in full image).")
|
||||||
width, height = image.size
|
width, height = image.size
|
||||||
image_chunks = []
|
image_chunks = []
|
||||||
x_offsets = []
|
x_offsets = []
|
||||||
@@ -400,7 +397,7 @@ def get_faces_list(
|
|||||||
x_offsets.append(x)
|
x_offsets.append(x)
|
||||||
y_offsets.append(0)
|
y_offsets.append(0)
|
||||||
fx += increment
|
fx += increment
|
||||||
context.services.logger.info(f"FaceTools --> Chunk starting at x = {x}")
|
context.logger.info(f"FaceTools --> Chunk starting at x = {x}")
|
||||||
elif height > width:
|
elif height > width:
|
||||||
# Portrait - slice the image vertically
|
# Portrait - slice the image vertically
|
||||||
fy = 0.0
|
fy = 0.0
|
||||||
@@ -412,10 +409,10 @@ def get_faces_list(
|
|||||||
x_offsets.append(0)
|
x_offsets.append(0)
|
||||||
y_offsets.append(y)
|
y_offsets.append(y)
|
||||||
fy += increment
|
fy += increment
|
||||||
context.services.logger.info(f"FaceTools --> Chunk starting at y = {y}")
|
context.logger.info(f"FaceTools --> Chunk starting at y = {y}")
|
||||||
|
|
||||||
for idx in range(len(image_chunks)):
|
for idx in range(len(image_chunks)):
|
||||||
context.services.logger.info(f"FaceTools --> Evaluating faces in chunk {idx}")
|
context.logger.info(f"FaceTools --> Evaluating faces in chunk {idx}")
|
||||||
result = result + generate_face_box_mask(
|
result = result + generate_face_box_mask(
|
||||||
context=context,
|
context=context,
|
||||||
minimum_confidence=minimum_confidence,
|
minimum_confidence=minimum_confidence,
|
||||||
@@ -429,7 +426,7 @@ def get_faces_list(
|
|||||||
|
|
||||||
if len(result) == 0:
|
if len(result) == 0:
|
||||||
# Give up
|
# Give up
|
||||||
context.services.logger.warning(
|
context.logger.warning(
|
||||||
"FaceTools --> No face detected in chunked input image. Passing through original image."
|
"FaceTools --> No face detected in chunked input image. Passing through original image."
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -438,8 +435,8 @@ def get_faces_list(
|
|||||||
return all_faces
|
return all_faces
|
||||||
|
|
||||||
|
|
||||||
@invocation("face_off", title="FaceOff", tags=["image", "faceoff", "face", "mask"], category="image", version="1.1.0")
|
@invocation("face_off", title="FaceOff", tags=["image", "faceoff", "face", "mask"], category="image", version="1.2.1")
|
||||||
class FaceOffInvocation(BaseInvocation, WithWorkflow, WithMetadata):
|
class FaceOffInvocation(BaseInvocation, 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")
|
||||||
@@ -471,11 +468,11 @@ class FaceOffInvocation(BaseInvocation, WithWorkflow, WithMetadata):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if len(all_faces) == 0:
|
if len(all_faces) == 0:
|
||||||
context.services.logger.warning("FaceOff --> No faces detected. Passing through original image.")
|
context.logger.warning("FaceOff --> No faces detected. Passing through original image.")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if self.face_id > len(all_faces) - 1:
|
if self.face_id > len(all_faces) - 1:
|
||||||
context.services.logger.warning(
|
context.logger.warning(
|
||||||
f"FaceOff --> Face ID {self.face_id} is outside of the number of faces detected ({len(all_faces)}). Passing through original image."
|
f"FaceOff --> Face ID {self.face_id} is outside of the number of faces detected ({len(all_faces)}). Passing through original image."
|
||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
@@ -487,7 +484,7 @@ class FaceOffInvocation(BaseInvocation, WithWorkflow, WithMetadata):
|
|||||||
return face_data
|
return face_data
|
||||||
|
|
||||||
def invoke(self, context: InvocationContext) -> FaceOffOutput:
|
def invoke(self, context: InvocationContext) -> FaceOffOutput:
|
||||||
image = context.services.images.get_pil_image(self.image.image_name)
|
image = context.images.get_pil(self.image.image_name)
|
||||||
result = self.faceoff(context=context, image=image)
|
result = self.faceoff(context=context, image=image)
|
||||||
|
|
||||||
if result is None:
|
if result is None:
|
||||||
@@ -501,24 +498,9 @@ class FaceOffInvocation(BaseInvocation, WithWorkflow, WithMetadata):
|
|||||||
x = result["x_min"]
|
x = result["x_min"]
|
||||||
y = result["y_min"]
|
y = result["y_min"]
|
||||||
|
|
||||||
image_dto = context.services.images.create(
|
image_dto = context.images.save(image=result_image)
|
||||||
image=result_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,
|
|
||||||
workflow=self.workflow,
|
|
||||||
)
|
|
||||||
|
|
||||||
mask_dto = context.services.images.create(
|
mask_dto = context.images.save(image=result_mask, image_category=ImageCategory.MASK)
|
||||||
image=result_mask,
|
|
||||||
image_origin=ResourceOrigin.INTERNAL,
|
|
||||||
image_category=ImageCategory.MASK,
|
|
||||||
node_id=self.id,
|
|
||||||
session_id=context.graph_execution_state_id,
|
|
||||||
is_intermediate=self.is_intermediate,
|
|
||||||
)
|
|
||||||
|
|
||||||
output = FaceOffOutput(
|
output = FaceOffOutput(
|
||||||
image=ImageField(image_name=image_dto.image_name),
|
image=ImageField(image_name=image_dto.image_name),
|
||||||
@@ -532,8 +514,8 @@ class FaceOffInvocation(BaseInvocation, WithWorkflow, WithMetadata):
|
|||||||
return output
|
return output
|
||||||
|
|
||||||
|
|
||||||
@invocation("face_mask_detection", title="FaceMask", tags=["image", "face", "mask"], category="image", version="1.1.0")
|
@invocation("face_mask_detection", title="FaceMask", tags=["image", "face", "mask"], category="image", version="1.2.1")
|
||||||
class FaceMaskInvocation(BaseInvocation, WithWorkflow, WithMetadata):
|
class FaceMaskInvocation(BaseInvocation, 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")
|
||||||
@@ -581,7 +563,7 @@ class FaceMaskInvocation(BaseInvocation, WithWorkflow, WithMetadata):
|
|||||||
|
|
||||||
if len(intersected_face_ids) == 0:
|
if len(intersected_face_ids) == 0:
|
||||||
id_range_str = ",".join([str(id) for id in id_range])
|
id_range_str = ",".join([str(id) for id in id_range])
|
||||||
context.services.logger.warning(
|
context.logger.warning(
|
||||||
f"Face IDs must be in range of detected faces - requested {self.face_ids}, detected {id_range_str}. Passing through original image."
|
f"Face IDs must be in range of detected faces - requested {self.face_ids}, detected {id_range_str}. Passing through original image."
|
||||||
)
|
)
|
||||||
return FaceMaskResult(
|
return FaceMaskResult(
|
||||||
@@ -617,27 +599,12 @@ class FaceMaskInvocation(BaseInvocation, WithWorkflow, WithMetadata):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def invoke(self, context: InvocationContext) -> FaceMaskOutput:
|
def invoke(self, context: InvocationContext) -> FaceMaskOutput:
|
||||||
image = context.services.images.get_pil_image(self.image.image_name)
|
image = context.images.get_pil(self.image.image_name)
|
||||||
result = self.facemask(context=context, image=image)
|
result = self.facemask(context=context, image=image)
|
||||||
|
|
||||||
image_dto = context.services.images.create(
|
image_dto = context.images.save(image=result["image"])
|
||||||
image=result["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,
|
|
||||||
workflow=self.workflow,
|
|
||||||
)
|
|
||||||
|
|
||||||
mask_dto = context.services.images.create(
|
mask_dto = context.images.save(image=result["mask"], image_category=ImageCategory.MASK)
|
||||||
image=result["mask"],
|
|
||||||
image_origin=ResourceOrigin.INTERNAL,
|
|
||||||
image_category=ImageCategory.MASK,
|
|
||||||
node_id=self.id,
|
|
||||||
session_id=context.graph_execution_state_id,
|
|
||||||
is_intermediate=self.is_intermediate,
|
|
||||||
)
|
|
||||||
|
|
||||||
output = FaceMaskOutput(
|
output = FaceMaskOutput(
|
||||||
image=ImageField(image_name=image_dto.image_name),
|
image=ImageField(image_name=image_dto.image_name),
|
||||||
@@ -650,9 +617,9 @@ class FaceMaskInvocation(BaseInvocation, WithWorkflow, WithMetadata):
|
|||||||
|
|
||||||
|
|
||||||
@invocation(
|
@invocation(
|
||||||
"face_identifier", title="FaceIdentifier", tags=["image", "face", "identifier"], category="image", version="1.1.0"
|
"face_identifier", title="FaceIdentifier", tags=["image", "face", "identifier"], category="image", version="1.2.1"
|
||||||
)
|
)
|
||||||
class FaceIdentifierInvocation(BaseInvocation, WithWorkflow, WithMetadata):
|
class FaceIdentifierInvocation(BaseInvocation, WithMetadata, WithBoard):
|
||||||
"""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")
|
||||||
@@ -706,21 +673,9 @@ class FaceIdentifierInvocation(BaseInvocation, WithWorkflow, WithMetadata):
|
|||||||
return image
|
return image
|
||||||
|
|
||||||
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.images.get_pil(self.image.image_name)
|
||||||
result_image = self.faceidentifier(context=context, image=image)
|
result_image = self.faceidentifier(context=context, image=image)
|
||||||
|
|
||||||
image_dto = context.services.images.create(
|
image_dto = context.images.save(image=result_image)
|
||||||
image=result_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,
|
|
||||||
workflow=self.workflow,
|
|
||||||
)
|
|
||||||
|
|
||||||
return ImageOutput(
|
return ImageOutput.build(image_dto)
|
||||||
image=ImageField(image_name=image_dto.image_name),
|
|
||||||
width=image_dto.width,
|
|
||||||
height=image_dto.height,
|
|
||||||
)
|
|
||||||
|
|||||||
565
invokeai/app/invocations/fields.py
Normal file
@@ -0,0 +1,565 @@
|
|||||||
|
from enum import Enum
|
||||||
|
from typing import Any, Callable, Optional, Tuple
|
||||||
|
|
||||||
|
from pydantic import BaseModel, ConfigDict, Field, RootModel, TypeAdapter
|
||||||
|
from pydantic.fields import _Unset
|
||||||
|
from pydantic_core import PydanticUndefined
|
||||||
|
|
||||||
|
from invokeai.app.util.metaenum import MetaEnum
|
||||||
|
from invokeai.backend.util.logging import InvokeAILogger
|
||||||
|
|
||||||
|
logger = InvokeAILogger.get_logger()
|
||||||
|
|
||||||
|
|
||||||
|
class UIType(str, Enum, metaclass=MetaEnum):
|
||||||
|
"""
|
||||||
|
Type hints for the UI for situations in which the field type is not enough to infer the correct UI type.
|
||||||
|
|
||||||
|
- Model Fields
|
||||||
|
The most common node-author-facing use will be for model fields. Internally, there is no difference
|
||||||
|
between SD-1, SD-2 and SDXL model fields - they all use the class `MainModelField`. To ensure the
|
||||||
|
base-model-specific UI is rendered, use e.g. `ui_type=UIType.SDXLMainModelField` to indicate that
|
||||||
|
the field is an SDXL main model field.
|
||||||
|
|
||||||
|
- Any Field
|
||||||
|
We cannot infer the usage of `typing.Any` via schema parsing, so you *must* use `ui_type=UIType.Any` to
|
||||||
|
indicate that the field accepts any type. Use with caution. This cannot be used on outputs.
|
||||||
|
|
||||||
|
- Scheduler Field
|
||||||
|
Special handling in the UI is needed for this field, which otherwise would be parsed as a plain enum field.
|
||||||
|
|
||||||
|
- Internal Fields
|
||||||
|
Similar to the Any Field, the `collect` and `iterate` nodes make use of `typing.Any`. To facilitate
|
||||||
|
handling these types in the client, we use `UIType._Collection` and `UIType._CollectionItem`. These
|
||||||
|
should not be used by node authors.
|
||||||
|
|
||||||
|
- DEPRECATED Fields
|
||||||
|
These types are deprecated and should not be used by node authors. A warning will be logged if one is
|
||||||
|
used, and the type will be ignored. They are included here for backwards compatibility.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# region Model Field Types
|
||||||
|
SDXLMainModel = "SDXLMainModelField"
|
||||||
|
SDXLRefinerModel = "SDXLRefinerModelField"
|
||||||
|
ONNXModel = "ONNXModelField"
|
||||||
|
VaeModel = "VAEModelField"
|
||||||
|
LoRAModel = "LoRAModelField"
|
||||||
|
ControlNetModel = "ControlNetModelField"
|
||||||
|
IPAdapterModel = "IPAdapterModelField"
|
||||||
|
# endregion
|
||||||
|
|
||||||
|
# region Misc Field Types
|
||||||
|
Scheduler = "SchedulerField"
|
||||||
|
Any = "AnyField"
|
||||||
|
# endregion
|
||||||
|
|
||||||
|
# region Internal Field Types
|
||||||
|
_Collection = "CollectionField"
|
||||||
|
_CollectionItem = "CollectionItemField"
|
||||||
|
# endregion
|
||||||
|
|
||||||
|
# region DEPRECATED
|
||||||
|
Boolean = "DEPRECATED_Boolean"
|
||||||
|
Color = "DEPRECATED_Color"
|
||||||
|
Conditioning = "DEPRECATED_Conditioning"
|
||||||
|
Control = "DEPRECATED_Control"
|
||||||
|
Float = "DEPRECATED_Float"
|
||||||
|
Image = "DEPRECATED_Image"
|
||||||
|
Integer = "DEPRECATED_Integer"
|
||||||
|
Latents = "DEPRECATED_Latents"
|
||||||
|
String = "DEPRECATED_String"
|
||||||
|
BooleanCollection = "DEPRECATED_BooleanCollection"
|
||||||
|
ColorCollection = "DEPRECATED_ColorCollection"
|
||||||
|
ConditioningCollection = "DEPRECATED_ConditioningCollection"
|
||||||
|
ControlCollection = "DEPRECATED_ControlCollection"
|
||||||
|
FloatCollection = "DEPRECATED_FloatCollection"
|
||||||
|
ImageCollection = "DEPRECATED_ImageCollection"
|
||||||
|
IntegerCollection = "DEPRECATED_IntegerCollection"
|
||||||
|
LatentsCollection = "DEPRECATED_LatentsCollection"
|
||||||
|
StringCollection = "DEPRECATED_StringCollection"
|
||||||
|
BooleanPolymorphic = "DEPRECATED_BooleanPolymorphic"
|
||||||
|
ColorPolymorphic = "DEPRECATED_ColorPolymorphic"
|
||||||
|
ConditioningPolymorphic = "DEPRECATED_ConditioningPolymorphic"
|
||||||
|
ControlPolymorphic = "DEPRECATED_ControlPolymorphic"
|
||||||
|
FloatPolymorphic = "DEPRECATED_FloatPolymorphic"
|
||||||
|
ImagePolymorphic = "DEPRECATED_ImagePolymorphic"
|
||||||
|
IntegerPolymorphic = "DEPRECATED_IntegerPolymorphic"
|
||||||
|
LatentsPolymorphic = "DEPRECATED_LatentsPolymorphic"
|
||||||
|
StringPolymorphic = "DEPRECATED_StringPolymorphic"
|
||||||
|
MainModel = "DEPRECATED_MainModel"
|
||||||
|
UNet = "DEPRECATED_UNet"
|
||||||
|
Vae = "DEPRECATED_Vae"
|
||||||
|
CLIP = "DEPRECATED_CLIP"
|
||||||
|
Collection = "DEPRECATED_Collection"
|
||||||
|
CollectionItem = "DEPRECATED_CollectionItem"
|
||||||
|
Enum = "DEPRECATED_Enum"
|
||||||
|
WorkflowField = "DEPRECATED_WorkflowField"
|
||||||
|
IsIntermediate = "DEPRECATED_IsIntermediate"
|
||||||
|
BoardField = "DEPRECATED_BoardField"
|
||||||
|
MetadataItem = "DEPRECATED_MetadataItem"
|
||||||
|
MetadataItemCollection = "DEPRECATED_MetadataItemCollection"
|
||||||
|
MetadataItemPolymorphic = "DEPRECATED_MetadataItemPolymorphic"
|
||||||
|
MetadataDict = "DEPRECATED_MetadataDict"
|
||||||
|
|
||||||
|
|
||||||
|
class UIComponent(str, Enum, metaclass=MetaEnum):
|
||||||
|
"""
|
||||||
|
The type of UI component to use for a field, used to override the default components, which are
|
||||||
|
inferred from the field type.
|
||||||
|
"""
|
||||||
|
|
||||||
|
None_ = "none"
|
||||||
|
Textarea = "textarea"
|
||||||
|
Slider = "slider"
|
||||||
|
|
||||||
|
|
||||||
|
class FieldDescriptions:
|
||||||
|
denoising_start = "When to start denoising, expressed a percentage of total steps"
|
||||||
|
denoising_end = "When to stop denoising, expressed a percentage of total steps"
|
||||||
|
cfg_scale = "Classifier-Free Guidance scale"
|
||||||
|
cfg_rescale_multiplier = "Rescale multiplier for CFG guidance, used for models trained with zero-terminal SNR"
|
||||||
|
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)"
|
||||||
|
metadata = "Optional metadata to be saved with the image"
|
||||||
|
metadata_collection = "Collection of Metadata"
|
||||||
|
metadata_item_polymorphic = "A single metadata item or collection of metadata items"
|
||||||
|
metadata_item_label = "Label for this metadata item"
|
||||||
|
metadata_item_value = "The value for this metadata item (may be any type)"
|
||||||
|
workflow = "Optional workflow to be saved with the 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"
|
||||||
|
freeu_s1 = 'Scaling factor for stage 1 to attenuate the contributions of the skip features. This is done to mitigate the "oversmoothing effect" in the enhanced denoising process.'
|
||||||
|
freeu_s2 = 'Scaling factor for stage 2 to attenuate the contributions of the skip features. This is done to mitigate the "oversmoothing effect" in the enhanced denoising process.'
|
||||||
|
freeu_b1 = "Scaling factor for stage 1 to amplify the contributions of backbone features."
|
||||||
|
freeu_b2 = "Scaling factor for stage 2 to amplify the contributions of backbone features."
|
||||||
|
|
||||||
|
|
||||||
|
class ImageField(BaseModel):
|
||||||
|
"""An image primitive field"""
|
||||||
|
|
||||||
|
image_name: str = Field(description="The name of the image")
|
||||||
|
|
||||||
|
|
||||||
|
class BoardField(BaseModel):
|
||||||
|
"""A board primitive field"""
|
||||||
|
|
||||||
|
board_id: str = Field(description="The id of the board")
|
||||||
|
|
||||||
|
|
||||||
|
class DenoiseMaskField(BaseModel):
|
||||||
|
"""An inpaint mask field"""
|
||||||
|
|
||||||
|
mask_name: str = Field(description="The name of the mask image")
|
||||||
|
masked_latents_name: Optional[str] = Field(default=None, description="The name of the masked image latents")
|
||||||
|
|
||||||
|
|
||||||
|
class LatentsField(BaseModel):
|
||||||
|
"""A latents tensor primitive field"""
|
||||||
|
|
||||||
|
latents_name: str = Field(description="The name of the latents")
|
||||||
|
seed: Optional[int] = Field(default=None, description="Seed used to generate this latents")
|
||||||
|
|
||||||
|
|
||||||
|
class ColorField(BaseModel):
|
||||||
|
"""A color primitive field"""
|
||||||
|
|
||||||
|
r: int = Field(ge=0, le=255, description="The red component")
|
||||||
|
g: int = Field(ge=0, le=255, description="The green component")
|
||||||
|
b: int = Field(ge=0, le=255, description="The blue component")
|
||||||
|
a: int = Field(ge=0, le=255, description="The alpha component")
|
||||||
|
|
||||||
|
def tuple(self) -> Tuple[int, int, int, int]:
|
||||||
|
return (self.r, self.g, self.b, self.a)
|
||||||
|
|
||||||
|
|
||||||
|
class ConditioningField(BaseModel):
|
||||||
|
"""A conditioning tensor primitive value"""
|
||||||
|
|
||||||
|
conditioning_name: str = Field(description="The name of conditioning tensor")
|
||||||
|
# endregion
|
||||||
|
|
||||||
|
|
||||||
|
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 Input(str, Enum, metaclass=MetaEnum):
|
||||||
|
"""
|
||||||
|
The type of input a field accepts.
|
||||||
|
- `Input.Direct`: The field must have its value provided directly, when the invocation and field \
|
||||||
|
are instantiated.
|
||||||
|
- `Input.Connection`: The field must have its value provided by a connection.
|
||||||
|
- `Input.Any`: The field may have its value provided either directly or by a connection.
|
||||||
|
"""
|
||||||
|
|
||||||
|
Connection = "connection"
|
||||||
|
Direct = "direct"
|
||||||
|
Any = "any"
|
||||||
|
|
||||||
|
|
||||||
|
class FieldKind(str, Enum, metaclass=MetaEnum):
|
||||||
|
"""
|
||||||
|
The kind of field.
|
||||||
|
- `Input`: An input field on a node.
|
||||||
|
- `Output`: An output field on a node.
|
||||||
|
- `Internal`: A field which is treated as an input, but cannot be used in node definitions. Metadata is
|
||||||
|
one example. It is provided to nodes via the WithMetadata class, and we want to reserve the field name
|
||||||
|
"metadata" for this on all nodes. `FieldKind` is used to short-circuit the field name validation logic,
|
||||||
|
allowing "metadata" for that field.
|
||||||
|
- `NodeAttribute`: The field is a node attribute. These are fields which are not inputs or outputs,
|
||||||
|
but which are used to store information about the node. For example, the `id` and `type` fields are node
|
||||||
|
attributes.
|
||||||
|
|
||||||
|
The presence of this in `json_schema_extra["field_kind"]` is used when initializing node schemas on app
|
||||||
|
startup, and when generating the OpenAPI schema for the workflow editor.
|
||||||
|
"""
|
||||||
|
|
||||||
|
Input = "input"
|
||||||
|
Output = "output"
|
||||||
|
Internal = "internal"
|
||||||
|
NodeAttribute = "node_attribute"
|
||||||
|
|
||||||
|
|
||||||
|
class InputFieldJSONSchemaExtra(BaseModel):
|
||||||
|
"""
|
||||||
|
Extra attributes to be added to input fields and their OpenAPI schema. Used during graph execution,
|
||||||
|
and by the workflow editor during schema parsing and UI rendering.
|
||||||
|
"""
|
||||||
|
|
||||||
|
input: Input
|
||||||
|
orig_required: bool
|
||||||
|
field_kind: FieldKind
|
||||||
|
default: Optional[Any] = None
|
||||||
|
orig_default: Optional[Any] = None
|
||||||
|
ui_hidden: bool = False
|
||||||
|
ui_type: Optional[UIType] = None
|
||||||
|
ui_component: Optional[UIComponent] = None
|
||||||
|
ui_order: Optional[int] = None
|
||||||
|
ui_choice_labels: Optional[dict[str, str]] = None
|
||||||
|
|
||||||
|
model_config = ConfigDict(
|
||||||
|
validate_assignment=True,
|
||||||
|
json_schema_serialization_defaults_required=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class WithMetadata(BaseModel):
|
||||||
|
"""
|
||||||
|
Inherit from this class if your node needs a metadata input field.
|
||||||
|
"""
|
||||||
|
|
||||||
|
metadata: Optional[MetadataField] = Field(
|
||||||
|
default=None,
|
||||||
|
description=FieldDescriptions.metadata,
|
||||||
|
json_schema_extra=InputFieldJSONSchemaExtra(
|
||||||
|
field_kind=FieldKind.Internal,
|
||||||
|
input=Input.Connection,
|
||||||
|
orig_required=False,
|
||||||
|
).model_dump(exclude_none=True),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class WithWorkflow:
|
||||||
|
workflow = None
|
||||||
|
|
||||||
|
def __init_subclass__(cls) -> None:
|
||||||
|
logger.warn(
|
||||||
|
f"{cls.__module__.split('.')[0]}.{cls.__name__}: WithWorkflow is deprecated. Use `context.workflow` to access the workflow."
|
||||||
|
)
|
||||||
|
super().__init_subclass__()
|
||||||
|
|
||||||
|
|
||||||
|
class WithBoard(BaseModel):
|
||||||
|
"""
|
||||||
|
Inherit from this class if your node needs a board input field.
|
||||||
|
"""
|
||||||
|
|
||||||
|
board: Optional[BoardField] = Field(
|
||||||
|
default=None,
|
||||||
|
description=FieldDescriptions.board,
|
||||||
|
json_schema_extra=InputFieldJSONSchemaExtra(
|
||||||
|
field_kind=FieldKind.Internal,
|
||||||
|
input=Input.Direct,
|
||||||
|
orig_required=False,
|
||||||
|
).model_dump(exclude_none=True),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class OutputFieldJSONSchemaExtra(BaseModel):
|
||||||
|
"""
|
||||||
|
Extra attributes to be added to input fields and their OpenAPI schema. Used by the workflow editor
|
||||||
|
during schema parsing and UI rendering.
|
||||||
|
"""
|
||||||
|
|
||||||
|
field_kind: FieldKind
|
||||||
|
ui_hidden: bool
|
||||||
|
ui_type: Optional[UIType]
|
||||||
|
ui_order: Optional[int]
|
||||||
|
|
||||||
|
model_config = ConfigDict(
|
||||||
|
validate_assignment=True,
|
||||||
|
json_schema_serialization_defaults_required=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def InputField(
|
||||||
|
# copied from pydantic's Field
|
||||||
|
# TODO: Can we support default_factory?
|
||||||
|
default: Any = _Unset,
|
||||||
|
default_factory: Callable[[], Any] | None = _Unset,
|
||||||
|
title: str | None = _Unset,
|
||||||
|
description: str | None = _Unset,
|
||||||
|
pattern: str | None = _Unset,
|
||||||
|
strict: bool | None = _Unset,
|
||||||
|
gt: float | None = _Unset,
|
||||||
|
ge: float | None = _Unset,
|
||||||
|
lt: float | None = _Unset,
|
||||||
|
le: float | None = _Unset,
|
||||||
|
multiple_of: float | None = _Unset,
|
||||||
|
allow_inf_nan: bool | None = _Unset,
|
||||||
|
max_digits: int | None = _Unset,
|
||||||
|
decimal_places: int | None = _Unset,
|
||||||
|
min_length: int | None = _Unset,
|
||||||
|
max_length: int | None = _Unset,
|
||||||
|
# custom
|
||||||
|
input: Input = Input.Any,
|
||||||
|
ui_type: Optional[UIType] = None,
|
||||||
|
ui_component: Optional[UIComponent] = None,
|
||||||
|
ui_hidden: bool = False,
|
||||||
|
ui_order: Optional[int] = None,
|
||||||
|
ui_choice_labels: Optional[dict[str, str]] = None,
|
||||||
|
) -> Any:
|
||||||
|
"""
|
||||||
|
Creates an input field for an invocation.
|
||||||
|
|
||||||
|
This is a wrapper for Pydantic's [Field](https://docs.pydantic.dev/latest/api/fields/#pydantic.fields.Field) \
|
||||||
|
that adds a few extra parameters to support graph execution and the node editor UI.
|
||||||
|
|
||||||
|
:param Input input: [Input.Any] The kind of input this field requires. \
|
||||||
|
`Input.Direct` means a value must be provided on instantiation. \
|
||||||
|
`Input.Connection` means the value must be provided by a connection. \
|
||||||
|
`Input.Any` means either will do.
|
||||||
|
|
||||||
|
:param UIType ui_type: [None] Optionally provides an extra type hint for the UI. \
|
||||||
|
In some situations, the field's type is not enough to infer the correct UI type. \
|
||||||
|
For example, model selection fields should render a dropdown UI component to select a model. \
|
||||||
|
Internally, there is no difference between SD-1, SD-2 and SDXL model fields, they all use \
|
||||||
|
`MainModelField`. So to ensure the base-model-specific UI is rendered, you can use \
|
||||||
|
`UIType.SDXLMainModelField` to indicate that the field is an SDXL main model field.
|
||||||
|
|
||||||
|
:param UIComponent ui_component: [None] Optionally specifies a specific component to use in the UI. \
|
||||||
|
The UI will always render a suitable component, but sometimes you want something different than the default. \
|
||||||
|
For example, a `string` field will default to a single-line input, but you may want a multi-line textarea instead. \
|
||||||
|
For this case, you could provide `UIComponent.Textarea`.
|
||||||
|
|
||||||
|
:param bool ui_hidden: [False] Specifies whether or not this field should be hidden in the UI.
|
||||||
|
|
||||||
|
:param int ui_order: [None] Specifies the order in which this field should be rendered in the UI.
|
||||||
|
|
||||||
|
:param dict[str, str] ui_choice_labels: [None] Specifies the labels to use for the choices in an enum field.
|
||||||
|
"""
|
||||||
|
|
||||||
|
json_schema_extra_ = InputFieldJSONSchemaExtra(
|
||||||
|
input=input,
|
||||||
|
ui_type=ui_type,
|
||||||
|
ui_component=ui_component,
|
||||||
|
ui_hidden=ui_hidden,
|
||||||
|
ui_order=ui_order,
|
||||||
|
ui_choice_labels=ui_choice_labels,
|
||||||
|
field_kind=FieldKind.Input,
|
||||||
|
orig_required=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
"""
|
||||||
|
There is a conflict between the typing of invocation definitions and the typing of an invocation's
|
||||||
|
`invoke()` function.
|
||||||
|
|
||||||
|
On instantiation of a node, the invocation definition is used to create the python class. At this time,
|
||||||
|
any number of fields may be optional, because they may be provided by connections.
|
||||||
|
|
||||||
|
On calling of `invoke()`, however, those fields may be required.
|
||||||
|
|
||||||
|
For example, consider an ResizeImageInvocation with an `image: ImageField` field.
|
||||||
|
|
||||||
|
`image` is required during the call to `invoke()`, but when the python class is instantiated,
|
||||||
|
the field may not be present. This is fine, because that image field will be provided by a
|
||||||
|
connection from an ancestor node, which outputs an image.
|
||||||
|
|
||||||
|
This means we want to type the `image` field as optional for the node class definition, but required
|
||||||
|
for the `invoke()` function.
|
||||||
|
|
||||||
|
If we use `typing.Optional` in the node class definition, the field will be typed as optional in the
|
||||||
|
`invoke()` method, and we'll have to do a lot of runtime checks to ensure the field is present - or
|
||||||
|
any static type analysis tools will complain.
|
||||||
|
|
||||||
|
To get around this, in node class definitions, we type all fields correctly for the `invoke()` function,
|
||||||
|
but secretly make them optional in `InputField()`. We also store the original required bool and/or default
|
||||||
|
value. When we call `invoke()`, we use this stored information to do an additional check on the class.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if default_factory is not _Unset and default_factory is not None:
|
||||||
|
default = default_factory()
|
||||||
|
logger.warn('"default_factory" is not supported, calling it now to set "default"')
|
||||||
|
|
||||||
|
# These are the args we may wish pass to the pydantic `Field()` function
|
||||||
|
field_args = {
|
||||||
|
"default": default,
|
||||||
|
"title": title,
|
||||||
|
"description": description,
|
||||||
|
"pattern": pattern,
|
||||||
|
"strict": strict,
|
||||||
|
"gt": gt,
|
||||||
|
"ge": ge,
|
||||||
|
"lt": lt,
|
||||||
|
"le": le,
|
||||||
|
"multiple_of": multiple_of,
|
||||||
|
"allow_inf_nan": allow_inf_nan,
|
||||||
|
"max_digits": max_digits,
|
||||||
|
"decimal_places": decimal_places,
|
||||||
|
"min_length": min_length,
|
||||||
|
"max_length": max_length,
|
||||||
|
}
|
||||||
|
|
||||||
|
# We only want to pass the args that were provided, otherwise the `Field()`` function won't work as expected
|
||||||
|
provided_args = {k: v for (k, v) in field_args.items() if v is not PydanticUndefined}
|
||||||
|
|
||||||
|
# Because we are manually making fields optional, we need to store the original required bool for reference later
|
||||||
|
json_schema_extra_.orig_required = default is PydanticUndefined
|
||||||
|
|
||||||
|
# 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:
|
||||||
|
default_ = None if default is PydanticUndefined else default
|
||||||
|
provided_args.update({"default": default_})
|
||||||
|
if default is not PydanticUndefined:
|
||||||
|
# Before invoking, we'll check for the original default value and set it on the field if the field has no value
|
||||||
|
json_schema_extra_.default = default
|
||||||
|
json_schema_extra_.orig_default = default
|
||||||
|
elif default is not PydanticUndefined:
|
||||||
|
default_ = default
|
||||||
|
provided_args.update({"default": default_})
|
||||||
|
json_schema_extra_.orig_default = default_
|
||||||
|
|
||||||
|
return Field(
|
||||||
|
**provided_args,
|
||||||
|
json_schema_extra=json_schema_extra_.model_dump(exclude_none=True),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def OutputField(
|
||||||
|
# copied from pydantic's Field
|
||||||
|
default: Any = _Unset,
|
||||||
|
title: str | None = _Unset,
|
||||||
|
description: str | None = _Unset,
|
||||||
|
pattern: str | None = _Unset,
|
||||||
|
strict: bool | None = _Unset,
|
||||||
|
gt: float | None = _Unset,
|
||||||
|
ge: float | None = _Unset,
|
||||||
|
lt: float | None = _Unset,
|
||||||
|
le: float | None = _Unset,
|
||||||
|
multiple_of: float | None = _Unset,
|
||||||
|
allow_inf_nan: bool | None = _Unset,
|
||||||
|
max_digits: int | None = _Unset,
|
||||||
|
decimal_places: int | None = _Unset,
|
||||||
|
min_length: int | None = _Unset,
|
||||||
|
max_length: int | None = _Unset,
|
||||||
|
# custom
|
||||||
|
ui_type: Optional[UIType] = None,
|
||||||
|
ui_hidden: bool = False,
|
||||||
|
ui_order: Optional[int] = None,
|
||||||
|
) -> Any:
|
||||||
|
"""
|
||||||
|
Creates an output field for an invocation output.
|
||||||
|
|
||||||
|
This is a wrapper for Pydantic's [Field](https://docs.pydantic.dev/1.10/usage/schema/#field-customization) \
|
||||||
|
that adds a few extra parameters to support graph execution and the node editor UI.
|
||||||
|
|
||||||
|
:param UIType ui_type: [None] Optionally provides an extra type hint for the UI. \
|
||||||
|
In some situations, the field's type is not enough to infer the correct UI type. \
|
||||||
|
For example, model selection fields should render a dropdown UI component to select a model. \
|
||||||
|
Internally, there is no difference between SD-1, SD-2 and SDXL model fields, they all use \
|
||||||
|
`MainModelField`. So to ensure the base-model-specific UI is rendered, you can use \
|
||||||
|
`UIType.SDXLMainModelField` to indicate that the field is an SDXL main model field.
|
||||||
|
|
||||||
|
:param bool ui_hidden: [False] Specifies whether or not this field should be hidden in the UI. \
|
||||||
|
|
||||||
|
:param int ui_order: [None] Specifies the order in which this field should be rendered in the UI. \
|
||||||
|
"""
|
||||||
|
return Field(
|
||||||
|
default=default,
|
||||||
|
title=title,
|
||||||
|
description=description,
|
||||||
|
pattern=pattern,
|
||||||
|
strict=strict,
|
||||||
|
gt=gt,
|
||||||
|
ge=ge,
|
||||||
|
lt=lt,
|
||||||
|
le=le,
|
||||||
|
multiple_of=multiple_of,
|
||||||
|
allow_inf_nan=allow_inf_nan,
|
||||||
|
max_digits=max_digits,
|
||||||
|
decimal_places=decimal_places,
|
||||||
|
min_length=min_length,
|
||||||
|
max_length=max_length,
|
||||||
|
json_schema_extra=OutputFieldJSONSchemaExtra(
|
||||||
|
ui_type=ui_type,
|
||||||
|
ui_hidden=ui_hidden,
|
||||||
|
ui_order=ui_order,
|
||||||
|
field_kind=FieldKind.Output,
|
||||||
|
).model_dump(exclude_none=True),
|
||||||
|
)
|
||||||
@@ -6,14 +6,16 @@ from typing import Literal, Optional, get_args
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
from PIL import Image, ImageOps
|
from PIL import Image, ImageOps
|
||||||
|
|
||||||
from invokeai.app.invocations.primitives import ColorField, ImageField, ImageOutput
|
from invokeai.app.invocations.fields import ColorField, ImageField
|
||||||
from invokeai.app.services.image_records.image_records_common import ImageCategory, ResourceOrigin
|
from invokeai.app.invocations.primitives import ImageOutput
|
||||||
from invokeai.app.util.misc import SEED_MAX, get_random_seed
|
from invokeai.app.services.shared.invocation_context import InvocationContext
|
||||||
|
from invokeai.app.util.misc import SEED_MAX
|
||||||
from invokeai.backend.image_util.cv2_inpaint import cv2_inpaint
|
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, WithMetadata, WithWorkflow, invocation
|
from .baseinvocation import BaseInvocation, invocation
|
||||||
|
from .fields import InputField, WithBoard, WithMetadata
|
||||||
from .image import PIL_RESAMPLING_MAP, PIL_RESAMPLING_MODES
|
from .image import PIL_RESAMPLING_MAP, PIL_RESAMPLING_MODES
|
||||||
|
|
||||||
|
|
||||||
@@ -118,8 +120,8 @@ def tile_fill_missing(im: Image.Image, tile_size: int = 16, seed: Optional[int]
|
|||||||
return si
|
return si
|
||||||
|
|
||||||
|
|
||||||
@invocation("infill_rgba", title="Solid Color Infill", tags=["image", "inpaint"], category="inpaint", version="1.1.0")
|
@invocation("infill_rgba", title="Solid Color Infill", tags=["image", "inpaint"], category="inpaint", version="1.2.1")
|
||||||
class InfillColorInvocation(BaseInvocation, WithWorkflow, WithMetadata):
|
class InfillColorInvocation(BaseInvocation, WithMetadata, WithBoard):
|
||||||
"""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")
|
||||||
@@ -129,72 +131,46 @@ class InfillColorInvocation(BaseInvocation, WithWorkflow, WithMetadata):
|
|||||||
)
|
)
|
||||||
|
|
||||||
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.images.get_pil(self.image.image_name)
|
||||||
|
|
||||||
solid_bg = Image.new("RGBA", image.size, self.color.tuple())
|
solid_bg = Image.new("RGBA", image.size, self.color.tuple())
|
||||||
infilled = Image.alpha_composite(solid_bg, image.convert("RGBA"))
|
infilled = Image.alpha_composite(solid_bg, image.convert("RGBA"))
|
||||||
|
|
||||||
infilled.paste(image, (0, 0), image.split()[-1])
|
infilled.paste(image, (0, 0), image.split()[-1])
|
||||||
|
|
||||||
image_dto = context.services.images.create(
|
image_dto = context.images.save(image=infilled)
|
||||||
image=infilled,
|
|
||||||
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.build(image_dto)
|
||||||
image=ImageField(image_name=image_dto.image_name),
|
|
||||||
width=image_dto.width,
|
|
||||||
height=image_dto.height,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@invocation("infill_tile", title="Tile Infill", tags=["image", "inpaint"], category="inpaint", version="1.1.0")
|
@invocation("infill_tile", title="Tile Infill", tags=["image", "inpaint"], category="inpaint", version="1.2.2")
|
||||||
class InfillTileInvocation(BaseInvocation, WithWorkflow, WithMetadata):
|
class InfillTileInvocation(BaseInvocation, WithMetadata, WithBoard):
|
||||||
"""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")
|
||||||
tile_size: int = InputField(default=32, ge=1, description="The tile size (px)")
|
tile_size: int = InputField(default=32, ge=1, description="The tile size (px)")
|
||||||
seed: int = InputField(
|
seed: int = InputField(
|
||||||
|
default=0,
|
||||||
ge=0,
|
ge=0,
|
||||||
le=SEED_MAX,
|
le=SEED_MAX,
|
||||||
description="The seed to use for tile generation (omit for random)",
|
description="The seed to use for tile generation (omit for random)",
|
||||||
default_factory=get_random_seed,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
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.images.get_pil(self.image.image_name)
|
||||||
|
|
||||||
infilled = tile_fill_missing(image.copy(), seed=self.seed, tile_size=self.tile_size)
|
infilled = tile_fill_missing(image.copy(), seed=self.seed, tile_size=self.tile_size)
|
||||||
infilled.paste(image, (0, 0), image.split()[-1])
|
infilled.paste(image, (0, 0), image.split()[-1])
|
||||||
|
|
||||||
image_dto = context.services.images.create(
|
image_dto = context.images.save(image=infilled)
|
||||||
image=infilled,
|
|
||||||
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.build(image_dto)
|
||||||
image=ImageField(image_name=image_dto.image_name),
|
|
||||||
width=image_dto.width,
|
|
||||||
height=image_dto.height,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@invocation(
|
@invocation(
|
||||||
"infill_patchmatch", title="PatchMatch Infill", tags=["image", "inpaint"], category="inpaint", version="1.1.0"
|
"infill_patchmatch", title="PatchMatch Infill", tags=["image", "inpaint"], category="inpaint", version="1.2.1"
|
||||||
)
|
)
|
||||||
class InfillPatchMatchInvocation(BaseInvocation, WithWorkflow, WithMetadata):
|
class InfillPatchMatchInvocation(BaseInvocation, WithMetadata, WithBoard):
|
||||||
"""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")
|
||||||
@@ -202,7 +178,7 @@ class InfillPatchMatchInvocation(BaseInvocation, WithWorkflow, WithMetadata):
|
|||||||
resample_mode: PIL_RESAMPLING_MODES = InputField(default="bicubic", description="The resampling mode")
|
resample_mode: PIL_RESAMPLING_MODES = InputField(default="bicubic", description="The resampling mode")
|
||||||
|
|
||||||
def invoke(self, context: InvocationContext) -> ImageOutput:
|
def invoke(self, context: InvocationContext) -> ImageOutput:
|
||||||
image = context.services.images.get_pil_image(self.image.image_name).convert("RGBA")
|
image = context.images.get_pil(self.image.image_name).convert("RGBA")
|
||||||
|
|
||||||
resample_mode = PIL_RESAMPLING_MAP[self.resample_mode]
|
resample_mode = PIL_RESAMPLING_MAP[self.resample_mode]
|
||||||
|
|
||||||
@@ -227,77 +203,38 @@ class InfillPatchMatchInvocation(BaseInvocation, WithWorkflow, WithMetadata):
|
|||||||
infilled.paste(image, (0, 0), mask=image.split()[-1])
|
infilled.paste(image, (0, 0), mask=image.split()[-1])
|
||||||
# image.paste(infilled, (0, 0), mask=image.split()[-1])
|
# image.paste(infilled, (0, 0), mask=image.split()[-1])
|
||||||
|
|
||||||
image_dto = context.services.images.create(
|
image_dto = context.images.save(image=infilled)
|
||||||
image=infilled,
|
|
||||||
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.build(image_dto)
|
||||||
image=ImageField(image_name=image_dto.image_name),
|
|
||||||
width=image_dto.width,
|
|
||||||
height=image_dto.height,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@invocation("infill_lama", title="LaMa Infill", tags=["image", "inpaint"], category="inpaint", version="1.1.0")
|
@invocation("infill_lama", title="LaMa Infill", tags=["image", "inpaint"], category="inpaint", version="1.2.1")
|
||||||
class LaMaInfillInvocation(BaseInvocation, WithWorkflow, WithMetadata):
|
class LaMaInfillInvocation(BaseInvocation, WithMetadata, WithBoard):
|
||||||
"""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")
|
||||||
|
|
||||||
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.images.get_pil(self.image.image_name)
|
||||||
|
|
||||||
infilled = infill_lama(image.copy())
|
infilled = infill_lama(image.copy())
|
||||||
|
|
||||||
image_dto = context.services.images.create(
|
image_dto = context.images.save(image=infilled)
|
||||||
image=infilled,
|
|
||||||
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.build(image_dto)
|
||||||
image=ImageField(image_name=image_dto.image_name),
|
|
||||||
width=image_dto.width,
|
|
||||||
height=image_dto.height,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@invocation("infill_cv2", title="CV2 Infill", tags=["image", "inpaint"], category="inpaint", version="1.1.0")
|
@invocation("infill_cv2", title="CV2 Infill", tags=["image", "inpaint"], category="inpaint", version="1.2.1")
|
||||||
class CV2InfillInvocation(BaseInvocation, WithWorkflow, WithMetadata):
|
class CV2InfillInvocation(BaseInvocation, WithMetadata, WithBoard):
|
||||||
"""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")
|
||||||
|
|
||||||
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.images.get_pil(self.image.image_name)
|
||||||
|
|
||||||
infilled = infill_cv2(image.copy())
|
infilled = infill_cv2(image.copy())
|
||||||
|
|
||||||
image_dto = context.services.images.create(
|
image_dto = context.images.save(image=infilled)
|
||||||
image=infilled,
|
|
||||||
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.build(image_dto)
|
||||||
image=ImageField(image_name=image_dto.image_name),
|
|
||||||
width=image_dto.width,
|
|
||||||
height=image_dto.height,
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -2,21 +2,18 @@ import os
|
|||||||
from builtins import float
|
from builtins import float
|
||||||
from typing import List, Union
|
from typing import List, Union
|
||||||
|
|
||||||
from pydantic import BaseModel, ConfigDict, Field
|
from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator
|
||||||
|
|
||||||
from invokeai.app.invocations.baseinvocation import (
|
from invokeai.app.invocations.baseinvocation import (
|
||||||
BaseInvocation,
|
BaseInvocation,
|
||||||
BaseInvocationOutput,
|
BaseInvocationOutput,
|
||||||
Input,
|
|
||||||
InputField,
|
|
||||||
InvocationContext,
|
|
||||||
OutputField,
|
|
||||||
UIType,
|
|
||||||
invocation,
|
invocation,
|
||||||
invocation_output,
|
invocation_output,
|
||||||
)
|
)
|
||||||
|
from invokeai.app.invocations.fields import FieldDescriptions, Input, InputField, OutputField
|
||||||
from invokeai.app.invocations.primitives import ImageField
|
from invokeai.app.invocations.primitives import ImageField
|
||||||
from invokeai.app.shared.fields import FieldDescriptions
|
from invokeai.app.invocations.util import validate_begin_end_step, validate_weights
|
||||||
|
from invokeai.app.services.shared.invocation_context import InvocationContext
|
||||||
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
|
||||||
|
|
||||||
@@ -40,7 +37,6 @@ class IPAdapterField(BaseModel):
|
|||||||
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")
|
||||||
# weight: float = Field(default=1.0, ge=0, description="The weight of the IP-Adapter.")
|
|
||||||
begin_step_percent: float = Field(
|
begin_step_percent: float = Field(
|
||||||
default=0, ge=0, le=1, description="When the IP-Adapter is first applied (% of total steps)"
|
default=0, ge=0, le=1, description="When the IP-Adapter is first applied (% of total steps)"
|
||||||
)
|
)
|
||||||
@@ -48,6 +44,17 @@ class IPAdapterField(BaseModel):
|
|||||||
default=1, ge=0, le=1, description="When the IP-Adapter is last applied (% of total steps)"
|
default=1, ge=0, le=1, description="When the IP-Adapter is last applied (% of total steps)"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@field_validator("weight")
|
||||||
|
@classmethod
|
||||||
|
def validate_ip_adapter_weight(cls, v):
|
||||||
|
validate_weights(v)
|
||||||
|
return v
|
||||||
|
|
||||||
|
@model_validator(mode="after")
|
||||||
|
def validate_begin_end_step_percent(self):
|
||||||
|
validate_begin_end_step(self.begin_step_percent, self.end_step_percent)
|
||||||
|
return self
|
||||||
|
|
||||||
|
|
||||||
@invocation_output("ip_adapter_output")
|
@invocation_output("ip_adapter_output")
|
||||||
class IPAdapterOutput(BaseInvocationOutput):
|
class IPAdapterOutput(BaseInvocationOutput):
|
||||||
@@ -55,7 +62,7 @@ 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.1.0")
|
@invocation("ip_adapter", title="IP-Adapter", tags=["ip_adapter", "control"], category="ip_adapter", version="1.1.2")
|
||||||
class IPAdapterInvocation(BaseInvocation):
|
class IPAdapterInvocation(BaseInvocation):
|
||||||
"""Collects IP-Adapter info to pass to other nodes."""
|
"""Collects IP-Adapter info to pass to other nodes."""
|
||||||
|
|
||||||
@@ -65,21 +72,30 @@ class IPAdapterInvocation(BaseInvocation):
|
|||||||
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: Union[float, List[float]] = InputField(
|
weight: Union[float, List[float]] = InputField(
|
||||||
default=1, ge=-1, description="The weight given to the IP-Adapter", ui_type=UIType.Float, title="Weight"
|
default=1, description="The weight given to the IP-Adapter", title="Weight"
|
||||||
)
|
)
|
||||||
|
|
||||||
begin_step_percent: float = InputField(
|
begin_step_percent: float = InputField(
|
||||||
default=0, ge=-1, le=2, description="When the IP-Adapter is first applied (% of total steps)"
|
default=0, ge=0, le=1, description="When the IP-Adapter is first applied (% of total steps)"
|
||||||
)
|
)
|
||||||
end_step_percent: float = InputField(
|
end_step_percent: float = InputField(
|
||||||
default=1, ge=0, le=1, description="When the IP-Adapter is last applied (% of total steps)"
|
default=1, ge=0, le=1, description="When the IP-Adapter is last applied (% of total steps)"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@field_validator("weight")
|
||||||
|
@classmethod
|
||||||
|
def validate_ip_adapter_weight(cls, v):
|
||||||
|
validate_weights(v)
|
||||||
|
return v
|
||||||
|
|
||||||
|
@model_validator(mode="after")
|
||||||
|
def validate_begin_end_step_percent(self):
|
||||||
|
validate_begin_end_step(self.begin_step_percent, self.end_step_percent)
|
||||||
|
return self
|
||||||
|
|
||||||
def invoke(self, context: InvocationContext) -> IPAdapterOutput:
|
def invoke(self, context: InvocationContext) -> IPAdapterOutput:
|
||||||
# Lookup the CLIP Vision encoder that is intended to be used with the IP-Adapter model.
|
# Lookup the CLIP Vision encoder that is intended to be used with the IP-Adapter model.
|
||||||
ip_adapter_info = context.services.model_manager.model_info(
|
ip_adapter_info = context.models.get_info(
|
||||||
self.ip_adapter_model.model_name, self.ip_adapter_model.base_model, ModelType.IPAdapter
|
self.ip_adapter_model.model_name, self.ip_adapter_model.base_model, ModelType.IPAdapter
|
||||||
)
|
)
|
||||||
# HACK(ryand): This is bad for a couple of reasons: 1) we are bypassing the model manager to read the model
|
# HACK(ryand): This is bad for a couple of reasons: 1) we are bypassing the model manager to read the model
|
||||||
@@ -88,7 +104,7 @@ class IPAdapterInvocation(BaseInvocation):
|
|||||||
# is currently messy due to differences between how the model info is generated when installing a model from
|
# is currently messy due to differences between how the model info is generated when installing a model from
|
||||||
# disk vs. downloading the model.
|
# disk vs. downloading the model.
|
||||||
image_encoder_model_id = get_ip_adapter_image_encoder_model_id(
|
image_encoder_model_id = get_ip_adapter_image_encoder_model_id(
|
||||||
os.path.join(context.services.configuration.get_config().models_path, ip_adapter_info["path"])
|
os.path.join(context.config.get().models_path, ip_adapter_info["path"])
|
||||||
)
|
)
|
||||||
image_encoder_model_name = image_encoder_model_id.split("/")[-1].strip()
|
image_encoder_model_name = image_encoder_model_id.split("/")[-1].strip()
|
||||||
image_encoder_model = CLIPVisionModelField(
|
image_encoder_model = CLIPVisionModelField(
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
# Copyright (c) 2023 Kyle Schouviller (https://github.com/kyle0654)
|
# Copyright (c) 2023 Kyle Schouviller (https://github.com/kyle0654)
|
||||||
|
|
||||||
|
import math
|
||||||
from contextlib import ExitStack
|
from contextlib import ExitStack
|
||||||
from functools import singledispatchmethod
|
from functools import singledispatchmethod
|
||||||
from typing import List, Literal, Optional, Union
|
from typing import List, Literal, Optional, Union
|
||||||
@@ -22,21 +23,29 @@ from diffusers.schedulers import SchedulerMixin as Scheduler
|
|||||||
from pydantic import field_validator
|
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.constants import LATENT_SCALE_FACTOR, SCHEDULER_NAME_VALUES
|
||||||
|
from invokeai.app.invocations.fields import (
|
||||||
|
ConditioningField,
|
||||||
|
DenoiseMaskField,
|
||||||
|
FieldDescriptions,
|
||||||
|
ImageField,
|
||||||
|
Input,
|
||||||
|
InputField,
|
||||||
|
LatentsField,
|
||||||
|
OutputField,
|
||||||
|
UIType,
|
||||||
|
WithBoard,
|
||||||
|
WithMetadata,
|
||||||
|
)
|
||||||
from invokeai.app.invocations.ip_adapter import IPAdapterField
|
from invokeai.app.invocations.ip_adapter import IPAdapterField
|
||||||
from invokeai.app.invocations.primitives import (
|
from invokeai.app.invocations.primitives import (
|
||||||
DenoiseMaskField,
|
|
||||||
DenoiseMaskOutput,
|
DenoiseMaskOutput,
|
||||||
ImageField,
|
|
||||||
ImageOutput,
|
ImageOutput,
|
||||||
LatentsField,
|
|
||||||
LatentsOutput,
|
LatentsOutput,
|
||||||
build_latents_output,
|
|
||||||
)
|
)
|
||||||
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.shared.invocation_context import InvocationContext
|
||||||
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.backend.ip_adapter.ip_adapter import IPAdapter, IPAdapterPlus
|
from invokeai.backend.ip_adapter.ip_adapter import IPAdapter, IPAdapterPlus
|
||||||
from invokeai.backend.model_management.models import ModelType, SilenceWarnings
|
from invokeai.backend.model_management.models import ModelType, SilenceWarnings
|
||||||
from invokeai.backend.stable_diffusion.diffusion.conditioning_data import ConditioningData, IPAdapterConditioningInfo
|
from invokeai.backend.stable_diffusion.diffusion.conditioning_data import ConditioningData, IPAdapterConditioningInfo
|
||||||
@@ -58,17 +67,9 @@ from ...backend.util.devices import choose_precision, choose_torch_device
|
|||||||
from .baseinvocation import (
|
from .baseinvocation import (
|
||||||
BaseInvocation,
|
BaseInvocation,
|
||||||
BaseInvocationOutput,
|
BaseInvocationOutput,
|
||||||
Input,
|
|
||||||
InputField,
|
|
||||||
InvocationContext,
|
|
||||||
OutputField,
|
|
||||||
UIType,
|
|
||||||
WithMetadata,
|
|
||||||
WithWorkflow,
|
|
||||||
invocation,
|
invocation,
|
||||||
invocation_output,
|
invocation_output,
|
||||||
)
|
)
|
||||||
from .compel import ConditioningField
|
|
||||||
from .controlnet_image_processors import ControlField
|
from .controlnet_image_processors import ControlField
|
||||||
from .model import ModelInfo, UNetField, VaeField
|
from .model import ModelInfo, UNetField, VaeField
|
||||||
|
|
||||||
@@ -77,12 +78,10 @@ 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(SCHEDULER_MAP.keys())]
|
|
||||||
|
|
||||||
|
|
||||||
@invocation_output("scheduler_output")
|
@invocation_output("scheduler_output")
|
||||||
class SchedulerOutput(BaseInvocationOutput):
|
class SchedulerOutput(BaseInvocationOutput):
|
||||||
scheduler: SAMPLER_NAME_VALUES = OutputField(description=FieldDescriptions.scheduler, ui_type=UIType.Scheduler)
|
scheduler: SCHEDULER_NAME_VALUES = OutputField(description=FieldDescriptions.scheduler, ui_type=UIType.Scheduler)
|
||||||
|
|
||||||
|
|
||||||
@invocation(
|
@invocation(
|
||||||
@@ -95,7 +94,7 @@ class SchedulerOutput(BaseInvocationOutput):
|
|||||||
class SchedulerInvocation(BaseInvocation):
|
class SchedulerInvocation(BaseInvocation):
|
||||||
"""Selects a scheduler."""
|
"""Selects a scheduler."""
|
||||||
|
|
||||||
scheduler: SAMPLER_NAME_VALUES = InputField(
|
scheduler: SCHEDULER_NAME_VALUES = InputField(
|
||||||
default="euler",
|
default="euler",
|
||||||
description=FieldDescriptions.scheduler,
|
description=FieldDescriptions.scheduler,
|
||||||
ui_type=UIType.Scheduler,
|
ui_type=UIType.Scheduler,
|
||||||
@@ -110,7 +109,7 @@ class SchedulerInvocation(BaseInvocation):
|
|||||||
title="Create Denoise Mask",
|
title="Create Denoise Mask",
|
||||||
tags=["mask", "denoise"],
|
tags=["mask", "denoise"],
|
||||||
category="latents",
|
category="latents",
|
||||||
version="1.0.0",
|
version="1.0.1",
|
||||||
)
|
)
|
||||||
class CreateDenoiseMaskInvocation(BaseInvocation):
|
class CreateDenoiseMaskInvocation(BaseInvocation):
|
||||||
"""Creates mask for denoising model run."""
|
"""Creates mask for denoising model run."""
|
||||||
@@ -138,7 +137,7 @@ class CreateDenoiseMaskInvocation(BaseInvocation):
|
|||||||
@torch.no_grad()
|
@torch.no_grad()
|
||||||
def invoke(self, context: InvocationContext) -> DenoiseMaskOutput:
|
def invoke(self, context: InvocationContext) -> DenoiseMaskOutput:
|
||||||
if self.image is not None:
|
if self.image is not None:
|
||||||
image = context.services.images.get_pil_image(self.image.image_name)
|
image = context.images.get_pil(self.image.image_name)
|
||||||
image = image_resized_to_grid_as_tensor(image.convert("RGB"))
|
image = image_resized_to_grid_as_tensor(image.convert("RGB"))
|
||||||
if image.dim() == 3:
|
if image.dim() == 3:
|
||||||
image = image.unsqueeze(0)
|
image = image.unsqueeze(0)
|
||||||
@@ -146,33 +145,26 @@ class CreateDenoiseMaskInvocation(BaseInvocation):
|
|||||||
image = None
|
image = None
|
||||||
|
|
||||||
mask = self.prep_mask_tensor(
|
mask = self.prep_mask_tensor(
|
||||||
context.services.images.get_pil_image(self.mask.image_name),
|
context.images.get_pil(self.mask.image_name),
|
||||||
)
|
)
|
||||||
|
|
||||||
if image is not None:
|
if image is not None:
|
||||||
vae_info = context.services.model_manager.get_model(
|
vae_info = context.models.load(**self.vae.vae.model_dump())
|
||||||
**self.vae.vae.model_dump(),
|
|
||||||
context=context,
|
|
||||||
)
|
|
||||||
|
|
||||||
img_mask = tv_resize(mask, image.shape[-2:], T.InterpolationMode.BILINEAR, antialias=False)
|
img_mask = tv_resize(mask, image.shape[-2:], T.InterpolationMode.BILINEAR, antialias=False)
|
||||||
masked_image = image * torch.where(img_mask < 0.5, 0.0, 1.0)
|
masked_image = image * torch.where(img_mask < 0.5, 0.0, 1.0)
|
||||||
# TODO:
|
# TODO:
|
||||||
masked_latents = ImageToLatentsInvocation.vae_encode(vae_info, self.fp32, self.tiled, masked_image.clone())
|
masked_latents = ImageToLatentsInvocation.vae_encode(vae_info, self.fp32, self.tiled, masked_image.clone())
|
||||||
|
|
||||||
masked_latents_name = f"{context.graph_execution_state_id}__{self.id}_masked_latents"
|
masked_latents_name = context.tensors.save(tensor=masked_latents)
|
||||||
context.services.latents.save(masked_latents_name, masked_latents)
|
|
||||||
else:
|
else:
|
||||||
masked_latents_name = None
|
masked_latents_name = None
|
||||||
|
|
||||||
mask_name = f"{context.graph_execution_state_id}__{self.id}_mask"
|
mask_name = context.tensors.save(tensor=mask)
|
||||||
context.services.latents.save(mask_name, mask)
|
|
||||||
|
|
||||||
return DenoiseMaskOutput(
|
return DenoiseMaskOutput.build(
|
||||||
denoise_mask=DenoiseMaskField(
|
mask_name=mask_name,
|
||||||
mask_name=mask_name,
|
masked_latents_name=masked_latents_name,
|
||||||
masked_latents_name=masked_latents_name,
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -183,10 +175,7 @@ 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.services.model_manager.get_model(
|
orig_scheduler_info = context.models.load(**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
|
||||||
|
|
||||||
@@ -215,7 +204,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.4.0",
|
version="1.5.2",
|
||||||
)
|
)
|
||||||
class DenoiseLatentsInvocation(BaseInvocation):
|
class DenoiseLatentsInvocation(BaseInvocation):
|
||||||
"""Denoises noisy latents to decodable images"""
|
"""Denoises noisy latents to decodable images"""
|
||||||
@@ -243,7 +232,7 @@ class DenoiseLatentsInvocation(BaseInvocation):
|
|||||||
description=FieldDescriptions.denoising_start,
|
description=FieldDescriptions.denoising_start,
|
||||||
)
|
)
|
||||||
denoising_end: float = InputField(default=1.0, ge=0, le=1, description=FieldDescriptions.denoising_end)
|
denoising_end: float = InputField(default=1.0, ge=0, le=1, description=FieldDescriptions.denoising_end)
|
||||||
scheduler: SAMPLER_NAME_VALUES = InputField(
|
scheduler: SCHEDULER_NAME_VALUES = InputField(
|
||||||
default="euler",
|
default="euler",
|
||||||
description=FieldDescriptions.scheduler,
|
description=FieldDescriptions.scheduler,
|
||||||
ui_type=UIType.Scheduler,
|
ui_type=UIType.Scheduler,
|
||||||
@@ -273,8 +262,14 @@ class DenoiseLatentsInvocation(BaseInvocation):
|
|||||||
input=Input.Connection,
|
input=Input.Connection,
|
||||||
ui_order=7,
|
ui_order=7,
|
||||||
)
|
)
|
||||||
|
cfg_rescale_multiplier: float = InputField(
|
||||||
|
title="CFG Rescale Multiplier", default=0, ge=0, lt=1, description=FieldDescriptions.cfg_rescale_multiplier
|
||||||
|
)
|
||||||
latents: Optional[LatentsField] = InputField(
|
latents: Optional[LatentsField] = InputField(
|
||||||
default=None, description=FieldDescriptions.latents, input=Input.Connection
|
default=None,
|
||||||
|
description=FieldDescriptions.latents,
|
||||||
|
input=Input.Connection,
|
||||||
|
ui_order=4,
|
||||||
)
|
)
|
||||||
denoise_mask: Optional[DenoiseMaskField] = InputField(
|
denoise_mask: Optional[DenoiseMaskField] = InputField(
|
||||||
default=None,
|
default=None,
|
||||||
@@ -295,22 +290,6 @@ class DenoiseLatentsInvocation(BaseInvocation):
|
|||||||
raise ValueError("cfg_scale must be greater than 1")
|
raise ValueError("cfg_scale must be greater than 1")
|
||||||
return v
|
return v
|
||||||
|
|
||||||
# TODO: pass this an emitter method or something? or a session for dispatching?
|
|
||||||
def dispatch_progress(
|
|
||||||
self,
|
|
||||||
context: InvocationContext,
|
|
||||||
source_node_id: str,
|
|
||||||
intermediate_state: PipelineIntermediateState,
|
|
||||||
base_model: BaseModelType,
|
|
||||||
) -> None:
|
|
||||||
stable_diffusion_step_callback(
|
|
||||||
context=context,
|
|
||||||
intermediate_state=intermediate_state,
|
|
||||||
node=self.model_dump(),
|
|
||||||
source_node_id=source_node_id,
|
|
||||||
base_model=base_model,
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_conditioning_data(
|
def get_conditioning_data(
|
||||||
self,
|
self,
|
||||||
context: InvocationContext,
|
context: InvocationContext,
|
||||||
@@ -318,17 +297,18 @@ class DenoiseLatentsInvocation(BaseInvocation):
|
|||||||
unet,
|
unet,
|
||||||
seed,
|
seed,
|
||||||
) -> ConditioningData:
|
) -> ConditioningData:
|
||||||
positive_cond_data = context.services.latents.get(self.positive_conditioning.conditioning_name)
|
positive_cond_data = context.conditioning.load(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.services.latents.get(self.negative_conditioning.conditioning_name)
|
negative_cond_data = context.conditioning.load(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(
|
||||||
unconditioned_embeddings=uc,
|
unconditioned_embeddings=uc,
|
||||||
text_embeddings=c,
|
text_embeddings=c,
|
||||||
guidance_scale=self.cfg_scale,
|
guidance_scale=self.cfg_scale,
|
||||||
|
guidance_rescale_multiplier=self.cfg_rescale_multiplier,
|
||||||
extra=extra_conditioning_info,
|
extra=extra_conditioning_info,
|
||||||
postprocessing_settings=PostprocessingSettings(
|
postprocessing_settings=PostprocessingSettings(
|
||||||
threshold=0.0, # threshold,
|
threshold=0.0, # threshold,
|
||||||
@@ -387,9 +367,9 @@ class DenoiseLatentsInvocation(BaseInvocation):
|
|||||||
exit_stack: ExitStack,
|
exit_stack: ExitStack,
|
||||||
do_classifier_free_guidance: bool = True,
|
do_classifier_free_guidance: bool = True,
|
||||||
) -> List[ControlNetData]:
|
) -> List[ControlNetData]:
|
||||||
# assuming fixed dimensional scaling of 8:1 for image:latents
|
# Assuming fixed dimensional scaling of LATENT_SCALE_FACTOR.
|
||||||
control_height_resize = latents_shape[2] * 8
|
control_height_resize = latents_shape[2] * LATENT_SCALE_FACTOR
|
||||||
control_width_resize = latents_shape[3] * 8
|
control_width_resize = latents_shape[3] * LATENT_SCALE_FACTOR
|
||||||
if control_input is None:
|
if control_input is None:
|
||||||
control_list = None
|
control_list = None
|
||||||
elif isinstance(control_input, list) and len(control_input) == 0:
|
elif isinstance(control_input, list) and len(control_input) == 0:
|
||||||
@@ -409,17 +389,16 @@ 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.services.model_manager.get_model(
|
context.models.load(
|
||||||
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.services.images.get_pil_image(control_image_field.image_name)
|
input_image = context.images.get_pil(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?
|
||||||
@@ -477,19 +456,17 @@ 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.services.model_manager.get_model(
|
context.models.load(
|
||||||
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.services.model_manager.get_model(
|
image_encoder_model_info = context.models.load(
|
||||||
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,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# `single_ip_adapter.image` could be a list or a single ImageField. Normalize to a list here.
|
# `single_ip_adapter.image` could be a list or a single ImageField. Normalize to a list here.
|
||||||
@@ -497,7 +474,7 @@ class DenoiseLatentsInvocation(BaseInvocation):
|
|||||||
if not isinstance(single_ipa_images, list):
|
if not isinstance(single_ipa_images, list):
|
||||||
single_ipa_images = [single_ipa_images]
|
single_ipa_images = [single_ipa_images]
|
||||||
|
|
||||||
single_ipa_images = [context.services.images.get_pil_image(image.image_name) for image in single_ipa_images]
|
single_ipa_images = [context.images.get_pil(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.
|
||||||
@@ -541,13 +518,12 @@ 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.services.model_manager.get_model(
|
t2i_adapter_model_info = context.models.load(
|
||||||
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.services.images.get_pil_image(t2i_adapter_field.image.image_name)
|
image = context.images.get_pil(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:
|
||||||
@@ -634,14 +610,14 @@ class DenoiseLatentsInvocation(BaseInvocation):
|
|||||||
|
|
||||||
return num_inference_steps, timesteps, init_timestep
|
return num_inference_steps, timesteps, init_timestep
|
||||||
|
|
||||||
def prep_inpaint_mask(self, context, latents):
|
def prep_inpaint_mask(self, context: InvocationContext, latents):
|
||||||
if self.denoise_mask is None:
|
if self.denoise_mask is None:
|
||||||
return None, None
|
return None, None
|
||||||
|
|
||||||
mask = context.services.latents.get(self.denoise_mask.mask_name)
|
mask = context.tensors.load(self.denoise_mask.mask_name)
|
||||||
mask = tv_resize(mask, latents.shape[-2:], T.InterpolationMode.BILINEAR, antialias=False)
|
mask = tv_resize(mask, latents.shape[-2:], T.InterpolationMode.BILINEAR, antialias=False)
|
||||||
if self.denoise_mask.masked_latents_name is not None:
|
if self.denoise_mask.masked_latents_name is not None:
|
||||||
masked_latents = context.services.latents.get(self.denoise_mask.masked_latents_name)
|
masked_latents = context.tensors.load(self.denoise_mask.masked_latents_name)
|
||||||
else:
|
else:
|
||||||
masked_latents = None
|
masked_latents = None
|
||||||
|
|
||||||
@@ -653,11 +629,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.services.latents.get(self.noise.latents_name)
|
noise = context.tensors.load(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.services.latents.get(self.latents.latents_name)
|
latents = context.tensors.load(self.latents.latents_name)
|
||||||
if seed is None:
|
if seed is None:
|
||||||
seed = self.latents.seed
|
seed = self.latents.seed
|
||||||
|
|
||||||
@@ -683,30 +659,19 @@ 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, source_node_id, state, self.unet.unet.base_model)
|
context.util.sd_step_callback(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.services.model_manager.get_model(
|
lora_info = context.models.load(**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.services.model_manager.get_model(
|
unet_info = context.models.load(**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_freeu(unet_info.context.model, self.unet.freeu_config),
|
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,
|
||||||
@@ -780,9 +745,8 @@ class DenoiseLatentsInvocation(BaseInvocation):
|
|||||||
if choose_torch_device() == torch.device("mps"):
|
if choose_torch_device() == torch.device("mps"):
|
||||||
mps.empty_cache()
|
mps.empty_cache()
|
||||||
|
|
||||||
name = f"{context.graph_execution_state_id}__{self.id}"
|
name = context.tensors.save(tensor=result_latents)
|
||||||
context.services.latents.save(name, result_latents)
|
return LatentsOutput.build(latents_name=name, latents=result_latents, seed=seed)
|
||||||
return build_latents_output(latents_name=name, latents=result_latents, seed=seed)
|
|
||||||
|
|
||||||
|
|
||||||
@invocation(
|
@invocation(
|
||||||
@@ -790,9 +754,9 @@ class DenoiseLatentsInvocation(BaseInvocation):
|
|||||||
title="Latents to Image",
|
title="Latents to Image",
|
||||||
tags=["latents", "image", "vae", "l2i"],
|
tags=["latents", "image", "vae", "l2i"],
|
||||||
category="latents",
|
category="latents",
|
||||||
version="1.1.0",
|
version="1.2.1",
|
||||||
)
|
)
|
||||||
class LatentsToImageInvocation(BaseInvocation, WithMetadata, WithWorkflow):
|
class LatentsToImageInvocation(BaseInvocation, WithMetadata, WithBoard):
|
||||||
"""Generates an image from latents."""
|
"""Generates an image from latents."""
|
||||||
|
|
||||||
latents: LatentsField = InputField(
|
latents: LatentsField = InputField(
|
||||||
@@ -808,12 +772,9 @@ class LatentsToImageInvocation(BaseInvocation, WithMetadata, WithWorkflow):
|
|||||||
|
|
||||||
@torch.no_grad()
|
@torch.no_grad()
|
||||||
def invoke(self, context: InvocationContext) -> ImageOutput:
|
def invoke(self, context: InvocationContext) -> ImageOutput:
|
||||||
latents = context.services.latents.get(self.latents.latents_name)
|
latents = context.tensors.load(self.latents.latents_name)
|
||||||
|
|
||||||
vae_info = context.services.model_manager.get_model(
|
vae_info = context.models.load(**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:
|
||||||
latents = latents.to(vae.device)
|
latents = latents.to(vae.device)
|
||||||
@@ -842,7 +803,7 @@ class LatentsToImageInvocation(BaseInvocation, WithMetadata, WithWorkflow):
|
|||||||
vae.to(dtype=torch.float16)
|
vae.to(dtype=torch.float16)
|
||||||
latents = latents.half()
|
latents = latents.half()
|
||||||
|
|
||||||
if self.tiled or context.services.configuration.tiled_decode:
|
if self.tiled or context.config.get().tiled_decode:
|
||||||
vae.enable_tiling()
|
vae.enable_tiling()
|
||||||
else:
|
else:
|
||||||
vae.disable_tiling()
|
vae.disable_tiling()
|
||||||
@@ -866,22 +827,9 @@ class LatentsToImageInvocation(BaseInvocation, WithMetadata, WithWorkflow):
|
|||||||
if choose_torch_device() == torch.device("mps"):
|
if choose_torch_device() == torch.device("mps"):
|
||||||
mps.empty_cache()
|
mps.empty_cache()
|
||||||
|
|
||||||
image_dto = context.services.images.create(
|
image_dto = context.images.save(image=image)
|
||||||
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.build(image_dto)
|
||||||
image=ImageField(image_name=image_dto.image_name),
|
|
||||||
width=image_dto.width,
|
|
||||||
height=image_dto.height,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
LATENTS_INTERPOLATION_MODE = Literal["nearest", "linear", "bilinear", "bicubic", "trilinear", "area", "nearest-exact"]
|
LATENTS_INTERPOLATION_MODE = Literal["nearest", "linear", "bilinear", "bicubic", "trilinear", "area", "nearest-exact"]
|
||||||
@@ -892,7 +840,7 @@ LATENTS_INTERPOLATION_MODE = Literal["nearest", "linear", "bilinear", "bicubic",
|
|||||||
title="Resize Latents",
|
title="Resize Latents",
|
||||||
tags=["latents", "resize"],
|
tags=["latents", "resize"],
|
||||||
category="latents",
|
category="latents",
|
||||||
version="1.0.0",
|
version="1.0.1",
|
||||||
)
|
)
|
||||||
class ResizeLatentsInvocation(BaseInvocation):
|
class ResizeLatentsInvocation(BaseInvocation):
|
||||||
"""Resizes latents to explicit width/height (in pixels). Provided dimensions are floor-divided by 8."""
|
"""Resizes latents to explicit width/height (in pixels). Provided dimensions are floor-divided by 8."""
|
||||||
@@ -903,26 +851,26 @@ class ResizeLatentsInvocation(BaseInvocation):
|
|||||||
)
|
)
|
||||||
width: int = InputField(
|
width: int = InputField(
|
||||||
ge=64,
|
ge=64,
|
||||||
multiple_of=8,
|
multiple_of=LATENT_SCALE_FACTOR,
|
||||||
description=FieldDescriptions.width,
|
description=FieldDescriptions.width,
|
||||||
)
|
)
|
||||||
height: int = InputField(
|
height: int = InputField(
|
||||||
ge=64,
|
ge=64,
|
||||||
multiple_of=8,
|
multiple_of=LATENT_SCALE_FACTOR,
|
||||||
description=FieldDescriptions.width,
|
description=FieldDescriptions.width,
|
||||||
)
|
)
|
||||||
mode: LATENTS_INTERPOLATION_MODE = InputField(default="bilinear", description=FieldDescriptions.interp_mode)
|
mode: LATENTS_INTERPOLATION_MODE = InputField(default="bilinear", description=FieldDescriptions.interp_mode)
|
||||||
antialias: bool = InputField(default=False, description=FieldDescriptions.torch_antialias)
|
antialias: bool = InputField(default=False, description=FieldDescriptions.torch_antialias)
|
||||||
|
|
||||||
def invoke(self, context: InvocationContext) -> LatentsOutput:
|
def invoke(self, context: InvocationContext) -> LatentsOutput:
|
||||||
latents = context.services.latents.get(self.latents.latents_name)
|
latents = context.tensors.load(self.latents.latents_name)
|
||||||
|
|
||||||
# TODO:
|
# TODO:
|
||||||
device = choose_torch_device()
|
device = choose_torch_device()
|
||||||
|
|
||||||
resized_latents = torch.nn.functional.interpolate(
|
resized_latents = torch.nn.functional.interpolate(
|
||||||
latents.to(device),
|
latents.to(device),
|
||||||
size=(self.height // 8, self.width // 8),
|
size=(self.height // LATENT_SCALE_FACTOR, self.width // LATENT_SCALE_FACTOR),
|
||||||
mode=self.mode,
|
mode=self.mode,
|
||||||
antialias=self.antialias if self.mode in ["bilinear", "bicubic"] else False,
|
antialias=self.antialias if self.mode in ["bilinear", "bicubic"] else False,
|
||||||
)
|
)
|
||||||
@@ -933,10 +881,8 @@ class ResizeLatentsInvocation(BaseInvocation):
|
|||||||
if device == torch.device("mps"):
|
if device == torch.device("mps"):
|
||||||
mps.empty_cache()
|
mps.empty_cache()
|
||||||
|
|
||||||
name = f"{context.graph_execution_state_id}__{self.id}"
|
name = context.tensors.save(tensor=resized_latents)
|
||||||
# context.services.latents.set(name, resized_latents)
|
return LatentsOutput.build(latents_name=name, latents=resized_latents, seed=self.latents.seed)
|
||||||
context.services.latents.save(name, resized_latents)
|
|
||||||
return build_latents_output(latents_name=name, latents=resized_latents, seed=self.latents.seed)
|
|
||||||
|
|
||||||
|
|
||||||
@invocation(
|
@invocation(
|
||||||
@@ -944,7 +890,7 @@ class ResizeLatentsInvocation(BaseInvocation):
|
|||||||
title="Scale Latents",
|
title="Scale Latents",
|
||||||
tags=["latents", "resize"],
|
tags=["latents", "resize"],
|
||||||
category="latents",
|
category="latents",
|
||||||
version="1.0.0",
|
version="1.0.1",
|
||||||
)
|
)
|
||||||
class ScaleLatentsInvocation(BaseInvocation):
|
class ScaleLatentsInvocation(BaseInvocation):
|
||||||
"""Scales latents by a given factor."""
|
"""Scales latents by a given factor."""
|
||||||
@@ -958,7 +904,7 @@ class ScaleLatentsInvocation(BaseInvocation):
|
|||||||
antialias: bool = InputField(default=False, description=FieldDescriptions.torch_antialias)
|
antialias: bool = InputField(default=False, description=FieldDescriptions.torch_antialias)
|
||||||
|
|
||||||
def invoke(self, context: InvocationContext) -> LatentsOutput:
|
def invoke(self, context: InvocationContext) -> LatentsOutput:
|
||||||
latents = context.services.latents.get(self.latents.latents_name)
|
latents = context.tensors.load(self.latents.latents_name)
|
||||||
|
|
||||||
# TODO:
|
# TODO:
|
||||||
device = choose_torch_device()
|
device = choose_torch_device()
|
||||||
@@ -977,10 +923,8 @@ class ScaleLatentsInvocation(BaseInvocation):
|
|||||||
if device == torch.device("mps"):
|
if device == torch.device("mps"):
|
||||||
mps.empty_cache()
|
mps.empty_cache()
|
||||||
|
|
||||||
name = f"{context.graph_execution_state_id}__{self.id}"
|
name = context.tensors.save(tensor=resized_latents)
|
||||||
# context.services.latents.set(name, resized_latents)
|
return LatentsOutput.build(latents_name=name, latents=resized_latents, seed=self.latents.seed)
|
||||||
context.services.latents.save(name, resized_latents)
|
|
||||||
return build_latents_output(latents_name=name, latents=resized_latents, seed=self.latents.seed)
|
|
||||||
|
|
||||||
|
|
||||||
@invocation(
|
@invocation(
|
||||||
@@ -988,7 +932,7 @@ class ScaleLatentsInvocation(BaseInvocation):
|
|||||||
title="Image to Latents",
|
title="Image to Latents",
|
||||||
tags=["latents", "image", "vae", "i2l"],
|
tags=["latents", "image", "vae", "i2l"],
|
||||||
category="latents",
|
category="latents",
|
||||||
version="1.0.0",
|
version="1.0.1",
|
||||||
)
|
)
|
||||||
class ImageToLatentsInvocation(BaseInvocation):
|
class ImageToLatentsInvocation(BaseInvocation):
|
||||||
"""Encodes an image into latents."""
|
"""Encodes an image into latents."""
|
||||||
@@ -1049,12 +993,9 @@ class ImageToLatentsInvocation(BaseInvocation):
|
|||||||
|
|
||||||
@torch.no_grad()
|
@torch.no_grad()
|
||||||
def invoke(self, context: InvocationContext) -> LatentsOutput:
|
def invoke(self, context: InvocationContext) -> LatentsOutput:
|
||||||
image = context.services.images.get_pil_image(self.image.image_name)
|
image = context.images.get_pil(self.image.image_name)
|
||||||
|
|
||||||
vae_info = context.services.model_manager.get_model(
|
vae_info = context.models.load(**self.vae.vae.model_dump())
|
||||||
**self.vae.vae.model_dump(),
|
|
||||||
context=context,
|
|
||||||
)
|
|
||||||
|
|
||||||
image_tensor = image_resized_to_grid_as_tensor(image.convert("RGB"))
|
image_tensor = image_resized_to_grid_as_tensor(image.convert("RGB"))
|
||||||
if image_tensor.dim() == 3:
|
if image_tensor.dim() == 3:
|
||||||
@@ -1062,10 +1003,9 @@ class ImageToLatentsInvocation(BaseInvocation):
|
|||||||
|
|
||||||
latents = self.vae_encode(vae_info, self.fp32, self.tiled, image_tensor)
|
latents = self.vae_encode(vae_info, self.fp32, self.tiled, image_tensor)
|
||||||
|
|
||||||
name = f"{context.graph_execution_state_id}__{self.id}"
|
|
||||||
latents = latents.to("cpu")
|
latents = latents.to("cpu")
|
||||||
context.services.latents.save(name, latents)
|
name = context.tensors.save(tensor=latents)
|
||||||
return build_latents_output(latents_name=name, latents=latents, seed=None)
|
return LatentsOutput.build(latents_name=name, latents=latents, seed=None)
|
||||||
|
|
||||||
@singledispatchmethod
|
@singledispatchmethod
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -1085,7 +1025,7 @@ class ImageToLatentsInvocation(BaseInvocation):
|
|||||||
title="Blend Latents",
|
title="Blend Latents",
|
||||||
tags=["latents", "blend"],
|
tags=["latents", "blend"],
|
||||||
category="latents",
|
category="latents",
|
||||||
version="1.0.0",
|
version="1.0.1",
|
||||||
)
|
)
|
||||||
class BlendLatentsInvocation(BaseInvocation):
|
class BlendLatentsInvocation(BaseInvocation):
|
||||||
"""Blend two latents using a given alpha. Latents must have same size."""
|
"""Blend two latents using a given alpha. Latents must have same size."""
|
||||||
@@ -1101,8 +1041,8 @@ class BlendLatentsInvocation(BaseInvocation):
|
|||||||
alpha: float = InputField(default=0.5, description=FieldDescriptions.blend_alpha)
|
alpha: float = InputField(default=0.5, description=FieldDescriptions.blend_alpha)
|
||||||
|
|
||||||
def invoke(self, context: InvocationContext) -> LatentsOutput:
|
def invoke(self, context: InvocationContext) -> LatentsOutput:
|
||||||
latents_a = context.services.latents.get(self.latents_a.latents_name)
|
latents_a = context.tensors.load(self.latents_a.latents_name)
|
||||||
latents_b = context.services.latents.get(self.latents_b.latents_name)
|
latents_b = context.tensors.load(self.latents_b.latents_name)
|
||||||
|
|
||||||
if latents_a.shape != latents_b.shape:
|
if latents_a.shape != latents_b.shape:
|
||||||
raise Exception("Latents to blend must be the same size.")
|
raise Exception("Latents to blend must be the same size.")
|
||||||
@@ -1156,7 +1096,115 @@ class BlendLatentsInvocation(BaseInvocation):
|
|||||||
if device == torch.device("mps"):
|
if device == torch.device("mps"):
|
||||||
mps.empty_cache()
|
mps.empty_cache()
|
||||||
|
|
||||||
name = f"{context.graph_execution_state_id}__{self.id}"
|
name = context.tensors.save(tensor=blended_latents)
|
||||||
# context.services.latents.set(name, resized_latents)
|
return LatentsOutput.build(latents_name=name, latents=blended_latents)
|
||||||
context.services.latents.save(name, blended_latents)
|
|
||||||
return build_latents_output(latents_name=name, latents=blended_latents)
|
|
||||||
|
# The Crop Latents node was copied from @skunkworxdark's implementation here:
|
||||||
|
# https://github.com/skunkworxdark/XYGrid_nodes/blob/74647fa9c1fa57d317a94bd43ca689af7f0aae5e/images_to_grids.py#L1117C1-L1167C80
|
||||||
|
@invocation(
|
||||||
|
"crop_latents",
|
||||||
|
title="Crop Latents",
|
||||||
|
tags=["latents", "crop"],
|
||||||
|
category="latents",
|
||||||
|
version="1.0.1",
|
||||||
|
)
|
||||||
|
# TODO(ryand): Named `CropLatentsCoreInvocation` to prevent a conflict with custom node `CropLatentsInvocation`.
|
||||||
|
# Currently, if the class names conflict then 'GET /openapi.json' fails.
|
||||||
|
class CropLatentsCoreInvocation(BaseInvocation):
|
||||||
|
"""Crops a latent-space tensor to a box specified in image-space. The box dimensions and coordinates must be
|
||||||
|
divisible by the latent scale factor of 8.
|
||||||
|
"""
|
||||||
|
|
||||||
|
latents: LatentsField = InputField(
|
||||||
|
description=FieldDescriptions.latents,
|
||||||
|
input=Input.Connection,
|
||||||
|
)
|
||||||
|
x: int = InputField(
|
||||||
|
ge=0,
|
||||||
|
multiple_of=LATENT_SCALE_FACTOR,
|
||||||
|
description="The left x coordinate (in px) of the crop rectangle in image space. This value will be converted to a dimension in latent space.",
|
||||||
|
)
|
||||||
|
y: int = InputField(
|
||||||
|
ge=0,
|
||||||
|
multiple_of=LATENT_SCALE_FACTOR,
|
||||||
|
description="The top y coordinate (in px) of the crop rectangle in image space. This value will be converted to a dimension in latent space.",
|
||||||
|
)
|
||||||
|
width: int = InputField(
|
||||||
|
ge=1,
|
||||||
|
multiple_of=LATENT_SCALE_FACTOR,
|
||||||
|
description="The width (in px) of the crop rectangle in image space. This value will be converted to a dimension in latent space.",
|
||||||
|
)
|
||||||
|
height: int = InputField(
|
||||||
|
ge=1,
|
||||||
|
multiple_of=LATENT_SCALE_FACTOR,
|
||||||
|
description="The height (in px) of the crop rectangle in image space. This value will be converted to a dimension in latent space.",
|
||||||
|
)
|
||||||
|
|
||||||
|
def invoke(self, context: InvocationContext) -> LatentsOutput:
|
||||||
|
latents = context.tensors.load(self.latents.latents_name)
|
||||||
|
|
||||||
|
x1 = self.x // LATENT_SCALE_FACTOR
|
||||||
|
y1 = self.y // LATENT_SCALE_FACTOR
|
||||||
|
x2 = x1 + (self.width // LATENT_SCALE_FACTOR)
|
||||||
|
y2 = y1 + (self.height // LATENT_SCALE_FACTOR)
|
||||||
|
|
||||||
|
cropped_latents = latents[..., y1:y2, x1:x2]
|
||||||
|
|
||||||
|
name = context.tensors.save(tensor=cropped_latents)
|
||||||
|
|
||||||
|
return LatentsOutput.build(latents_name=name, latents=cropped_latents)
|
||||||
|
|
||||||
|
|
||||||
|
@invocation_output("ideal_size_output")
|
||||||
|
class IdealSizeOutput(BaseInvocationOutput):
|
||||||
|
"""Base class for invocations that output an image"""
|
||||||
|
|
||||||
|
width: int = OutputField(description="The ideal width of the image (in pixels)")
|
||||||
|
height: int = OutputField(description="The ideal height of the image (in pixels)")
|
||||||
|
|
||||||
|
|
||||||
|
@invocation(
|
||||||
|
"ideal_size",
|
||||||
|
title="Ideal Size",
|
||||||
|
tags=["latents", "math", "ideal_size"],
|
||||||
|
version="1.0.2",
|
||||||
|
)
|
||||||
|
class IdealSizeInvocation(BaseInvocation):
|
||||||
|
"""Calculates the ideal size for generation to avoid duplication"""
|
||||||
|
|
||||||
|
width: int = InputField(default=1024, description="Final image width")
|
||||||
|
height: int = InputField(default=576, description="Final image height")
|
||||||
|
unet: UNetField = InputField(default=None, description=FieldDescriptions.unet)
|
||||||
|
multiplier: float = InputField(
|
||||||
|
default=1.0,
|
||||||
|
description="Amount to multiply the model's dimensions by when calculating the ideal size (may result in initial generation artifacts if too large)",
|
||||||
|
)
|
||||||
|
|
||||||
|
def trim_to_multiple_of(self, *args, multiple_of=LATENT_SCALE_FACTOR):
|
||||||
|
return tuple((x - x % multiple_of) for x in args)
|
||||||
|
|
||||||
|
def invoke(self, context: InvocationContext) -> IdealSizeOutput:
|
||||||
|
aspect = self.width / self.height
|
||||||
|
dimension = 512
|
||||||
|
if self.unet.unet.base_model == BaseModelType.StableDiffusion2:
|
||||||
|
dimension = 768
|
||||||
|
elif self.unet.unet.base_model == BaseModelType.StableDiffusionXL:
|
||||||
|
dimension = 1024
|
||||||
|
dimension = dimension * self.multiplier
|
||||||
|
min_dimension = math.floor(dimension * 0.5)
|
||||||
|
model_area = dimension * dimension # hardcoded for now since all models are trained on square images
|
||||||
|
|
||||||
|
if aspect > 1.0:
|
||||||
|
init_height = max(min_dimension, math.sqrt(model_area / aspect))
|
||||||
|
init_width = init_height * aspect
|
||||||
|
else:
|
||||||
|
init_width = max(min_dimension, math.sqrt(model_area * aspect))
|
||||||
|
init_height = init_width / aspect
|
||||||
|
|
||||||
|
scaled_width, scaled_height = self.trim_to_multiple_of(
|
||||||
|
math.floor(init_width),
|
||||||
|
math.floor(init_height),
|
||||||
|
)
|
||||||
|
|
||||||
|
return IdealSizeOutput(width=scaled_width, height=scaled_height)
|
||||||
|
|||||||
@@ -5,10 +5,11 @@ from typing import Literal
|
|||||||
import numpy as np
|
import numpy as np
|
||||||
from pydantic import ValidationInfo, field_validator
|
from pydantic import ValidationInfo, field_validator
|
||||||
|
|
||||||
|
from invokeai.app.invocations.fields import FieldDescriptions, InputField
|
||||||
from invokeai.app.invocations.primitives import FloatOutput, IntegerOutput
|
from invokeai.app.invocations.primitives import FloatOutput, IntegerOutput
|
||||||
from invokeai.app.shared.fields import FieldDescriptions
|
from invokeai.app.services.shared.invocation_context import InvocationContext
|
||||||
|
|
||||||
from .baseinvocation import BaseInvocation, InputField, InvocationContext, invocation
|
from .baseinvocation import BaseInvocation, 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")
|
||||||
|
|||||||
@@ -5,20 +5,22 @@ from pydantic import BaseModel, ConfigDict, Field
|
|||||||
from invokeai.app.invocations.baseinvocation import (
|
from invokeai.app.invocations.baseinvocation import (
|
||||||
BaseInvocation,
|
BaseInvocation,
|
||||||
BaseInvocationOutput,
|
BaseInvocationOutput,
|
||||||
InputField,
|
|
||||||
InvocationContext,
|
|
||||||
MetadataField,
|
|
||||||
OutputField,
|
|
||||||
UIType,
|
|
||||||
invocation,
|
invocation,
|
||||||
invocation_output,
|
invocation_output,
|
||||||
)
|
)
|
||||||
from invokeai.app.invocations.controlnet_image_processors import ControlField
|
from invokeai.app.invocations.controlnet_image_processors import ControlField
|
||||||
|
from invokeai.app.invocations.fields import (
|
||||||
|
FieldDescriptions,
|
||||||
|
ImageField,
|
||||||
|
InputField,
|
||||||
|
MetadataField,
|
||||||
|
OutputField,
|
||||||
|
UIType,
|
||||||
|
)
|
||||||
from invokeai.app.invocations.ip_adapter import IPAdapterModelField
|
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.t2i_adapter import T2IAdapterField
|
from invokeai.app.invocations.t2i_adapter import T2IAdapterField
|
||||||
from invokeai.app.shared.fields import FieldDescriptions
|
from invokeai.app.services.shared.invocation_context import InvocationContext
|
||||||
|
|
||||||
from ...version import __version__
|
from ...version import __version__
|
||||||
|
|
||||||
@@ -127,6 +129,9 @@ class CoreMetadataInvocation(BaseInvocation):
|
|||||||
seed: Optional[int] = InputField(default=None, description="The seed used for noise generation")
|
seed: Optional[int] = InputField(default=None, description="The seed used for noise generation")
|
||||||
rand_device: Optional[str] = InputField(default=None, description="The device used for random number generation")
|
rand_device: Optional[str] = InputField(default=None, description="The device used for random number generation")
|
||||||
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")
|
||||||
|
cfg_rescale_multiplier: Optional[float] = InputField(
|
||||||
|
default=None, description=FieldDescriptions.cfg_rescale_multiplier
|
||||||
|
)
|
||||||
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_x: Optional[bool] = InputField(default=None, description="Whether seamless tiling was used on the X axis")
|
||||||
|
|||||||
@@ -3,18 +3,14 @@ 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.invocations.fields import FieldDescriptions, Input, InputField, OutputField
|
||||||
|
from invokeai.app.services.shared.invocation_context import InvocationContext
|
||||||
from invokeai.app.shared.models import FreeUConfig
|
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,
|
||||||
Input,
|
|
||||||
InputField,
|
|
||||||
InvocationContext,
|
|
||||||
OutputField,
|
|
||||||
UIType,
|
|
||||||
invocation,
|
invocation,
|
||||||
invocation_output,
|
invocation_output,
|
||||||
)
|
)
|
||||||
@@ -106,7 +102,7 @@ class LoRAModelField(BaseModel):
|
|||||||
title="Main Model",
|
title="Main Model",
|
||||||
tags=["model"],
|
tags=["model"],
|
||||||
category="model",
|
category="model",
|
||||||
version="1.0.0",
|
version="1.0.1",
|
||||||
)
|
)
|
||||||
class MainModelLoaderInvocation(BaseInvocation):
|
class MainModelLoaderInvocation(BaseInvocation):
|
||||||
"""Loads a main model, outputting its submodels."""
|
"""Loads a main model, outputting its submodels."""
|
||||||
@@ -120,7 +116,7 @@ class MainModelLoaderInvocation(BaseInvocation):
|
|||||||
model_type = ModelType.Main
|
model_type = ModelType.Main
|
||||||
|
|
||||||
# TODO: not found exceptions
|
# TODO: not found exceptions
|
||||||
if not context.services.model_manager.model_exists(
|
if not context.models.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,
|
||||||
@@ -207,7 +203,7 @@ class LoraLoaderOutput(BaseInvocationOutput):
|
|||||||
clip: Optional[ClipField] = OutputField(default=None, description=FieldDescriptions.clip, title="CLIP")
|
clip: Optional[ClipField] = OutputField(default=None, description=FieldDescriptions.clip, title="CLIP")
|
||||||
|
|
||||||
|
|
||||||
@invocation("lora_loader", title="LoRA", tags=["model"], category="model", version="1.0.0")
|
@invocation("lora_loader", title="LoRA", tags=["model"], category="model", version="1.0.1")
|
||||||
class LoraLoaderInvocation(BaseInvocation):
|
class LoraLoaderInvocation(BaseInvocation):
|
||||||
"""Apply selected lora to unet and text_encoder."""
|
"""Apply selected lora to unet and text_encoder."""
|
||||||
|
|
||||||
@@ -233,7 +229,7 @@ class LoraLoaderInvocation(BaseInvocation):
|
|||||||
base_model = self.lora.base_model
|
base_model = self.lora.base_model
|
||||||
lora_name = self.lora.model_name
|
lora_name = self.lora.model_name
|
||||||
|
|
||||||
if not context.services.model_manager.model_exists(
|
if not context.models.exists(
|
||||||
base_model=base_model,
|
base_model=base_model,
|
||||||
model_name=lora_name,
|
model_name=lora_name,
|
||||||
model_type=ModelType.Lora,
|
model_type=ModelType.Lora,
|
||||||
@@ -289,7 +285,7 @@ class SDXLLoraLoaderOutput(BaseInvocationOutput):
|
|||||||
title="SDXL LoRA",
|
title="SDXL LoRA",
|
||||||
tags=["lora", "model"],
|
tags=["lora", "model"],
|
||||||
category="model",
|
category="model",
|
||||||
version="1.0.0",
|
version="1.0.1",
|
||||||
)
|
)
|
||||||
class SDXLLoraLoaderInvocation(BaseInvocation):
|
class SDXLLoraLoaderInvocation(BaseInvocation):
|
||||||
"""Apply selected lora to unet and text_encoder."""
|
"""Apply selected lora to unet and text_encoder."""
|
||||||
@@ -322,7 +318,7 @@ class SDXLLoraLoaderInvocation(BaseInvocation):
|
|||||||
base_model = self.lora.base_model
|
base_model = self.lora.base_model
|
||||||
lora_name = self.lora.model_name
|
lora_name = self.lora.model_name
|
||||||
|
|
||||||
if not context.services.model_manager.model_exists(
|
if not context.models.exists(
|
||||||
base_model=base_model,
|
base_model=base_model,
|
||||||
model_name=lora_name,
|
model_name=lora_name,
|
||||||
model_type=ModelType.Lora,
|
model_type=ModelType.Lora,
|
||||||
@@ -388,14 +384,13 @@ class VAEModelField(BaseModel):
|
|||||||
model_config = ConfigDict(protected_namespaces=())
|
model_config = ConfigDict(protected_namespaces=())
|
||||||
|
|
||||||
|
|
||||||
@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.1")
|
||||||
class VaeLoaderInvocation(BaseInvocation):
|
class VaeLoaderInvocation(BaseInvocation):
|
||||||
"""Loads a VAE model, outputting a VaeLoaderOutput"""
|
"""Loads a VAE model, outputting a VaeLoaderOutput"""
|
||||||
|
|
||||||
vae_model: VAEModelField = InputField(
|
vae_model: VAEModelField = InputField(
|
||||||
description=FieldDescriptions.vae_model,
|
description=FieldDescriptions.vae_model,
|
||||||
input=Input.Direct,
|
input=Input.Direct,
|
||||||
ui_type=UIType.VaeModel,
|
|
||||||
title="VAE",
|
title="VAE",
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -404,7 +399,7 @@ class VaeLoaderInvocation(BaseInvocation):
|
|||||||
model_name = self.vae_model.model_name
|
model_name = self.vae_model.model_name
|
||||||
model_type = ModelType.Vae
|
model_type = ModelType.Vae
|
||||||
|
|
||||||
if not context.services.model_manager.model_exists(
|
if not context.models.exists(
|
||||||
base_model=base_model,
|
base_model=base_model,
|
||||||
model_name=model_name,
|
model_name=model_name,
|
||||||
model_type=model_type,
|
model_type=model_type,
|
||||||
|
|||||||
@@ -4,17 +4,15 @@
|
|||||||
import torch
|
import torch
|
||||||
from pydantic import field_validator
|
from pydantic import field_validator
|
||||||
|
|
||||||
from invokeai.app.invocations.latent import LatentsField
|
from invokeai.app.invocations.constants import LATENT_SCALE_FACTOR
|
||||||
from invokeai.app.shared.fields import FieldDescriptions
|
from invokeai.app.invocations.fields import FieldDescriptions, InputField, LatentsField, OutputField
|
||||||
from invokeai.app.util.misc import SEED_MAX, get_random_seed
|
from invokeai.app.services.shared.invocation_context import InvocationContext
|
||||||
|
from invokeai.app.util.misc import SEED_MAX
|
||||||
|
|
||||||
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,
|
||||||
InputField,
|
|
||||||
InvocationContext,
|
|
||||||
OutputField,
|
|
||||||
invocation,
|
invocation,
|
||||||
invocation_output,
|
invocation_output,
|
||||||
)
|
)
|
||||||
@@ -69,13 +67,13 @@ class NoiseOutput(BaseInvocationOutput):
|
|||||||
width: int = OutputField(description=FieldDescriptions.width)
|
width: int = OutputField(description=FieldDescriptions.width)
|
||||||
height: int = OutputField(description=FieldDescriptions.height)
|
height: int = OutputField(description=FieldDescriptions.height)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
def build_noise_output(latents_name: str, latents: torch.Tensor, seed: int):
|
def build(cls, latents_name: str, latents: torch.Tensor, seed: int) -> "NoiseOutput":
|
||||||
return NoiseOutput(
|
return cls(
|
||||||
noise=LatentsField(latents_name=latents_name, seed=seed),
|
noise=LatentsField(latents_name=latents_name, seed=seed),
|
||||||
width=latents.size()[3] * 8,
|
width=latents.size()[3] * LATENT_SCALE_FACTOR,
|
||||||
height=latents.size()[2] * 8,
|
height=latents.size()[2] * LATENT_SCALE_FACTOR,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@invocation(
|
@invocation(
|
||||||
@@ -83,26 +81,26 @@ def build_noise_output(latents_name: str, latents: torch.Tensor, seed: int):
|
|||||||
title="Noise",
|
title="Noise",
|
||||||
tags=["latents", "noise"],
|
tags=["latents", "noise"],
|
||||||
category="latents",
|
category="latents",
|
||||||
version="1.0.0",
|
version="1.0.1",
|
||||||
)
|
)
|
||||||
class NoiseInvocation(BaseInvocation):
|
class NoiseInvocation(BaseInvocation):
|
||||||
"""Generates latent noise."""
|
"""Generates latent noise."""
|
||||||
|
|
||||||
seed: int = InputField(
|
seed: int = InputField(
|
||||||
|
default=0,
|
||||||
ge=0,
|
ge=0,
|
||||||
le=SEED_MAX,
|
le=SEED_MAX,
|
||||||
description=FieldDescriptions.seed,
|
description=FieldDescriptions.seed,
|
||||||
default_factory=get_random_seed,
|
|
||||||
)
|
)
|
||||||
width: int = InputField(
|
width: int = InputField(
|
||||||
default=512,
|
default=512,
|
||||||
multiple_of=8,
|
multiple_of=LATENT_SCALE_FACTOR,
|
||||||
gt=0,
|
gt=0,
|
||||||
description=FieldDescriptions.width,
|
description=FieldDescriptions.width,
|
||||||
)
|
)
|
||||||
height: int = InputField(
|
height: int = InputField(
|
||||||
default=512,
|
default=512,
|
||||||
multiple_of=8,
|
multiple_of=LATENT_SCALE_FACTOR,
|
||||||
gt=0,
|
gt=0,
|
||||||
description=FieldDescriptions.height,
|
description=FieldDescriptions.height,
|
||||||
)
|
)
|
||||||
@@ -124,6 +122,5 @@ class NoiseInvocation(BaseInvocation):
|
|||||||
seed=self.seed,
|
seed=self.seed,
|
||||||
use_cpu=self.use_cpu,
|
use_cpu=self.use_cpu,
|
||||||
)
|
)
|
||||||
name = f"{context.graph_execution_state_id}__{self.id}"
|
name = context.tensors.save(tensor=noise)
|
||||||
context.services.latents.save(name, noise)
|
return NoiseOutput.build(latents_name=name, latents=noise, seed=self.seed)
|
||||||
return build_noise_output(latents_name=name, latents=noise, seed=self.seed)
|
|
||||||
|
|||||||
@@ -1,509 +0,0 @@
|
|||||||
# Copyright (c) 2023 Borisov Sergey (https://github.com/StAlKeR7779)
|
|
||||||
|
|
||||||
import inspect
|
|
||||||
import re
|
|
||||||
|
|
||||||
# from contextlib import ExitStack
|
|
||||||
from typing import List, Literal, Union
|
|
||||||
|
|
||||||
import numpy as np
|
|
||||||
import torch
|
|
||||||
from diffusers.image_processor import VaeImageProcessor
|
|
||||||
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
|
||||||
from tqdm import tqdm
|
|
||||||
|
|
||||||
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.shared.fields import FieldDescriptions
|
|
||||||
from invokeai.app.util.step_callback import stable_diffusion_step_callback
|
|
||||||
from invokeai.backend import BaseModelType, ModelType, SubModelType
|
|
||||||
|
|
||||||
from ...backend.model_management import ONNXModelPatcher
|
|
||||||
from ...backend.stable_diffusion import PipelineIntermediateState
|
|
||||||
from ...backend.util import choose_torch_device
|
|
||||||
from .baseinvocation import (
|
|
||||||
BaseInvocation,
|
|
||||||
BaseInvocationOutput,
|
|
||||||
Input,
|
|
||||||
InputField,
|
|
||||||
InvocationContext,
|
|
||||||
OutputField,
|
|
||||||
UIComponent,
|
|
||||||
UIType,
|
|
||||||
WithMetadata,
|
|
||||||
WithWorkflow,
|
|
||||||
invocation,
|
|
||||||
invocation_output,
|
|
||||||
)
|
|
||||||
from .controlnet_image_processors import ControlField
|
|
||||||
from .latent import SAMPLER_NAME_VALUES, LatentsField, LatentsOutput, build_latents_output, get_scheduler
|
|
||||||
from .model import ClipField, ModelInfo, UNetField, VaeField
|
|
||||||
|
|
||||||
ORT_TO_NP_TYPE = {
|
|
||||||
"tensor(bool)": np.bool_,
|
|
||||||
"tensor(int8)": np.int8,
|
|
||||||
"tensor(uint8)": np.uint8,
|
|
||||||
"tensor(int16)": np.int16,
|
|
||||||
"tensor(uint16)": np.uint16,
|
|
||||||
"tensor(int32)": np.int32,
|
|
||||||
"tensor(uint32)": np.uint32,
|
|
||||||
"tensor(int64)": np.int64,
|
|
||||||
"tensor(uint64)": np.uint64,
|
|
||||||
"tensor(float16)": np.float16,
|
|
||||||
"tensor(float)": np.float32,
|
|
||||||
"tensor(double)": np.float64,
|
|
||||||
}
|
|
||||||
|
|
||||||
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")
|
|
||||||
class ONNXPromptInvocation(BaseInvocation):
|
|
||||||
prompt: str = InputField(default="", description=FieldDescriptions.raw_prompt, ui_component=UIComponent.Textarea)
|
|
||||||
clip: ClipField = InputField(description=FieldDescriptions.clip, input=Input.Connection)
|
|
||||||
|
|
||||||
def invoke(self, context: InvocationContext) -> ConditioningOutput:
|
|
||||||
tokenizer_info = context.services.model_manager.get_model(
|
|
||||||
**self.clip.tokenizer.model_dump(),
|
|
||||||
)
|
|
||||||
text_encoder_info = context.services.model_manager.get_model(
|
|
||||||
**self.clip.text_encoder.model_dump(),
|
|
||||||
)
|
|
||||||
with tokenizer_info as orig_tokenizer, text_encoder_info as text_encoder: # , ExitStack() as stack:
|
|
||||||
loras = [
|
|
||||||
(
|
|
||||||
context.services.model_manager.get_model(**lora.model_dump(exclude={"weight"})).context.model,
|
|
||||||
lora.weight,
|
|
||||||
)
|
|
||||||
for lora in self.clip.loras
|
|
||||||
]
|
|
||||||
|
|
||||||
ti_list = []
|
|
||||||
for trigger in re.findall(r"<[a-zA-Z0-9., _-]+>", self.prompt):
|
|
||||||
name = trigger[1:-1]
|
|
||||||
try:
|
|
||||||
ti_list.append(
|
|
||||||
(
|
|
||||||
name,
|
|
||||||
context.services.model_manager.get_model(
|
|
||||||
model_name=name,
|
|
||||||
base_model=self.clip.text_encoder.base_model,
|
|
||||||
model_type=ModelType.TextualInversion,
|
|
||||||
).context.model,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
except Exception:
|
|
||||||
# print(e)
|
|
||||||
# import traceback
|
|
||||||
# print(traceback.format_exc())
|
|
||||||
print(f'Warn: trigger: "{trigger}" not found')
|
|
||||||
if loras or ti_list:
|
|
||||||
text_encoder.release_session()
|
|
||||||
with (
|
|
||||||
ONNXModelPatcher.apply_lora_text_encoder(text_encoder, loras),
|
|
||||||
ONNXModelPatcher.apply_ti(orig_tokenizer, text_encoder, ti_list) as (tokenizer, ti_manager),
|
|
||||||
):
|
|
||||||
text_encoder.create_session()
|
|
||||||
|
|
||||||
# copy from
|
|
||||||
# https://github.com/huggingface/diffusers/blob/3ebbaf7c96801271f9e6c21400033b6aa5ffcf29/src/diffusers/pipelines/stable_diffusion/pipeline_onnx_stable_diffusion.py#L153
|
|
||||||
text_inputs = tokenizer(
|
|
||||||
self.prompt,
|
|
||||||
padding="max_length",
|
|
||||||
max_length=tokenizer.model_max_length,
|
|
||||||
truncation=True,
|
|
||||||
return_tensors="np",
|
|
||||||
)
|
|
||||||
text_input_ids = text_inputs.input_ids
|
|
||||||
"""
|
|
||||||
untruncated_ids = tokenizer(prompt, padding="max_length", return_tensors="np").input_ids
|
|
||||||
|
|
||||||
if not np.array_equal(text_input_ids, untruncated_ids):
|
|
||||||
removed_text = self.tokenizer.batch_decode(
|
|
||||||
untruncated_ids[:, self.tokenizer.model_max_length - 1 : -1]
|
|
||||||
)
|
|
||||||
logger.warning(
|
|
||||||
"The following part of your input was truncated because CLIP can only handle sequences up to"
|
|
||||||
f" {self.tokenizer.model_max_length} tokens: {removed_text}"
|
|
||||||
)
|
|
||||||
"""
|
|
||||||
|
|
||||||
prompt_embeds = text_encoder(input_ids=text_input_ids.astype(np.int32))[0]
|
|
||||||
|
|
||||||
conditioning_name = f"{context.graph_execution_state_id}_{self.id}_conditioning"
|
|
||||||
|
|
||||||
# TODO: hacky but works ;D maybe rename latents somehow?
|
|
||||||
context.services.latents.save(conditioning_name, (prompt_embeds, None))
|
|
||||||
|
|
||||||
return ConditioningOutput(
|
|
||||||
conditioning=ConditioningField(
|
|
||||||
conditioning_name=conditioning_name,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# Text to image
|
|
||||||
@invocation(
|
|
||||||
"t2l_onnx",
|
|
||||||
title="ONNX Text to Latents",
|
|
||||||
tags=["latents", "inference", "txt2img", "onnx"],
|
|
||||||
category="latents",
|
|
||||||
version="1.0.0",
|
|
||||||
)
|
|
||||||
class ONNXTextToLatentsInvocation(BaseInvocation):
|
|
||||||
"""Generates latents from conditionings."""
|
|
||||||
|
|
||||||
positive_conditioning: ConditioningField = InputField(
|
|
||||||
description=FieldDescriptions.positive_cond,
|
|
||||||
input=Input.Connection,
|
|
||||||
)
|
|
||||||
negative_conditioning: ConditioningField = InputField(
|
|
||||||
description=FieldDescriptions.negative_cond,
|
|
||||||
input=Input.Connection,
|
|
||||||
)
|
|
||||||
noise: LatentsField = InputField(
|
|
||||||
description=FieldDescriptions.noise,
|
|
||||||
input=Input.Connection,
|
|
||||||
)
|
|
||||||
steps: int = InputField(default=10, gt=0, description=FieldDescriptions.steps)
|
|
||||||
cfg_scale: Union[float, List[float]] = InputField(
|
|
||||||
default=7.5,
|
|
||||||
ge=1,
|
|
||||||
description=FieldDescriptions.cfg_scale,
|
|
||||||
)
|
|
||||||
scheduler: SAMPLER_NAME_VALUES = InputField(
|
|
||||||
default="euler", description=FieldDescriptions.scheduler, input=Input.Direct, ui_type=UIType.Scheduler
|
|
||||||
)
|
|
||||||
precision: PRECISION_VALUES = InputField(default="tensor(float16)", description=FieldDescriptions.precision)
|
|
||||||
unet: UNetField = InputField(
|
|
||||||
description=FieldDescriptions.unet,
|
|
||||||
input=Input.Connection,
|
|
||||||
)
|
|
||||||
control: Union[ControlField, list[ControlField]] = InputField(
|
|
||||||
default=None,
|
|
||||||
description=FieldDescriptions.control,
|
|
||||||
)
|
|
||||||
# seamless: bool = InputField(default=False, description="Whether or not to generate an image that can tile without seams", )
|
|
||||||
# seamless_axes: str = InputField(default="", description="The axes to tile the image on, 'x' and/or 'y'")
|
|
||||||
|
|
||||||
@field_validator("cfg_scale")
|
|
||||||
def ge_one(cls, v):
|
|
||||||
"""validate that all cfg_scale values are >= 1"""
|
|
||||||
if isinstance(v, list):
|
|
||||||
for i in v:
|
|
||||||
if i < 1:
|
|
||||||
raise ValueError("cfg_scale must be greater than 1")
|
|
||||||
else:
|
|
||||||
if v < 1:
|
|
||||||
raise ValueError("cfg_scale must be greater than 1")
|
|
||||||
return v
|
|
||||||
|
|
||||||
# based on
|
|
||||||
# https://github.com/huggingface/diffusers/blob/3ebbaf7c96801271f9e6c21400033b6aa5ffcf29/src/diffusers/pipelines/stable_diffusion/pipeline_onnx_stable_diffusion.py#L375
|
|
||||||
def invoke(self, context: InvocationContext) -> LatentsOutput:
|
|
||||||
c, _ = context.services.latents.get(self.positive_conditioning.conditioning_name)
|
|
||||||
uc, _ = context.services.latents.get(self.negative_conditioning.conditioning_name)
|
|
||||||
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]
|
|
||||||
if isinstance(c, torch.Tensor):
|
|
||||||
c = c.cpu().numpy()
|
|
||||||
if isinstance(uc, torch.Tensor):
|
|
||||||
uc = uc.cpu().numpy()
|
|
||||||
device = torch.device(choose_torch_device())
|
|
||||||
prompt_embeds = np.concatenate([uc, c])
|
|
||||||
|
|
||||||
latents = context.services.latents.get(self.noise.latents_name)
|
|
||||||
if isinstance(latents, torch.Tensor):
|
|
||||||
latents = latents.cpu().numpy()
|
|
||||||
|
|
||||||
# TODO: better execution device handling
|
|
||||||
latents = latents.astype(ORT_TO_NP_TYPE[self.precision])
|
|
||||||
|
|
||||||
# get the initial random noise unless the user supplied it
|
|
||||||
do_classifier_free_guidance = True
|
|
||||||
# latents_dtype = prompt_embeds.dtype
|
|
||||||
# latents_shape = (batch_size * num_images_per_prompt, 4, height // 8, width // 8)
|
|
||||||
# if latents.shape != latents_shape:
|
|
||||||
# raise ValueError(f"Unexpected latents shape, got {latents.shape}, expected {latents_shape}")
|
|
||||||
|
|
||||||
scheduler = get_scheduler(
|
|
||||||
context=context,
|
|
||||||
scheduler_info=self.unet.scheduler,
|
|
||||||
scheduler_name=self.scheduler,
|
|
||||||
seed=0, # TODO: refactor this node
|
|
||||||
)
|
|
||||||
|
|
||||||
def torch2numpy(latent: torch.Tensor):
|
|
||||||
return latent.cpu().numpy()
|
|
||||||
|
|
||||||
def numpy2torch(latent, device):
|
|
||||||
return torch.from_numpy(latent).to(device)
|
|
||||||
|
|
||||||
def dispatch_progress(
|
|
||||||
self, context: InvocationContext, source_node_id: str, intermediate_state: PipelineIntermediateState
|
|
||||||
) -> None:
|
|
||||||
stable_diffusion_step_callback(
|
|
||||||
context=context,
|
|
||||||
intermediate_state=intermediate_state,
|
|
||||||
node=self.model_dump(),
|
|
||||||
source_node_id=source_node_id,
|
|
||||||
)
|
|
||||||
|
|
||||||
scheduler.set_timesteps(self.steps)
|
|
||||||
latents = latents * np.float64(scheduler.init_noise_sigma)
|
|
||||||
|
|
||||||
extra_step_kwargs = {}
|
|
||||||
if "eta" in set(inspect.signature(scheduler.step).parameters.keys()):
|
|
||||||
extra_step_kwargs.update(
|
|
||||||
eta=0.0,
|
|
||||||
)
|
|
||||||
|
|
||||||
unet_info = context.services.model_manager.get_model(**self.unet.unet.model_dump())
|
|
||||||
|
|
||||||
with unet_info as unet: # , ExitStack() as stack:
|
|
||||||
# loras = [(stack.enter_context(context.services.model_manager.get_model(**lora.dict(exclude={"weight"}))), lora.weight) for lora in self.unet.loras]
|
|
||||||
loras = [
|
|
||||||
(
|
|
||||||
context.services.model_manager.get_model(**lora.model_dump(exclude={"weight"})).context.model,
|
|
||||||
lora.weight,
|
|
||||||
)
|
|
||||||
for lora in self.unet.loras
|
|
||||||
]
|
|
||||||
|
|
||||||
if loras:
|
|
||||||
unet.release_session()
|
|
||||||
with ONNXModelPatcher.apply_lora_unet(unet, loras):
|
|
||||||
# TODO:
|
|
||||||
_, _, h, w = latents.shape
|
|
||||||
unet.create_session(h, w)
|
|
||||||
|
|
||||||
timestep_dtype = next(
|
|
||||||
(input.type for input in unet.session.get_inputs() if input.name == "timestep"), "tensor(float16)"
|
|
||||||
)
|
|
||||||
timestep_dtype = ORT_TO_NP_TYPE[timestep_dtype]
|
|
||||||
for i in tqdm(range(len(scheduler.timesteps))):
|
|
||||||
t = scheduler.timesteps[i]
|
|
||||||
# expand the latents if we are doing classifier free guidance
|
|
||||||
latent_model_input = np.concatenate([latents] * 2) if do_classifier_free_guidance else latents
|
|
||||||
latent_model_input = scheduler.scale_model_input(numpy2torch(latent_model_input, device), t)
|
|
||||||
latent_model_input = latent_model_input.cpu().numpy()
|
|
||||||
|
|
||||||
# predict the noise residual
|
|
||||||
timestep = np.array([t], dtype=timestep_dtype)
|
|
||||||
noise_pred = unet(sample=latent_model_input, timestep=timestep, encoder_hidden_states=prompt_embeds)
|
|
||||||
noise_pred = noise_pred[0]
|
|
||||||
|
|
||||||
# perform guidance
|
|
||||||
if do_classifier_free_guidance:
|
|
||||||
noise_pred_uncond, noise_pred_text = np.split(noise_pred, 2)
|
|
||||||
noise_pred = noise_pred_uncond + self.cfg_scale * (noise_pred_text - noise_pred_uncond)
|
|
||||||
|
|
||||||
# compute the previous noisy sample x_t -> x_t-1
|
|
||||||
scheduler_output = scheduler.step(
|
|
||||||
numpy2torch(noise_pred, device), t, numpy2torch(latents, device), **extra_step_kwargs
|
|
||||||
)
|
|
||||||
latents = torch2numpy(scheduler_output.prev_sample)
|
|
||||||
|
|
||||||
state = PipelineIntermediateState(
|
|
||||||
run_id="test", step=i, timestep=timestep, latents=scheduler_output.prev_sample
|
|
||||||
)
|
|
||||||
dispatch_progress(self, context=context, source_node_id=source_node_id, intermediate_state=state)
|
|
||||||
|
|
||||||
# call the callback, if provided
|
|
||||||
# if callback is not None and i % callback_steps == 0:
|
|
||||||
# callback(i, t, latents)
|
|
||||||
|
|
||||||
torch.cuda.empty_cache()
|
|
||||||
|
|
||||||
name = f"{context.graph_execution_state_id}__{self.id}"
|
|
||||||
context.services.latents.save(name, latents)
|
|
||||||
return build_latents_output(latents_name=name, latents=torch.from_numpy(latents))
|
|
||||||
|
|
||||||
|
|
||||||
# Latent to image
|
|
||||||
@invocation(
|
|
||||||
"l2i_onnx",
|
|
||||||
title="ONNX Latents to Image",
|
|
||||||
tags=["latents", "image", "vae", "onnx"],
|
|
||||||
category="image",
|
|
||||||
version="1.1.0",
|
|
||||||
)
|
|
||||||
class ONNXLatentsToImageInvocation(BaseInvocation, WithMetadata, WithWorkflow):
|
|
||||||
"""Generates an image from latents."""
|
|
||||||
|
|
||||||
latents: LatentsField = InputField(
|
|
||||||
description=FieldDescriptions.denoised_latents,
|
|
||||||
input=Input.Connection,
|
|
||||||
)
|
|
||||||
vae: VaeField = InputField(
|
|
||||||
description=FieldDescriptions.vae,
|
|
||||||
input=Input.Connection,
|
|
||||||
)
|
|
||||||
# tiled: bool = InputField(default=False, description="Decode latents by overlaping tiles(less memory consumption)")
|
|
||||||
|
|
||||||
def invoke(self, context: InvocationContext) -> ImageOutput:
|
|
||||||
latents = context.services.latents.get(self.latents.latents_name)
|
|
||||||
|
|
||||||
if self.vae.vae.submodel != SubModelType.VaeDecoder:
|
|
||||||
raise Exception(f"Expected vae_decoder, found: {self.vae.vae.model_type}")
|
|
||||||
|
|
||||||
vae_info = context.services.model_manager.get_model(
|
|
||||||
**self.vae.vae.model_dump(),
|
|
||||||
)
|
|
||||||
|
|
||||||
# clear memory as vae decode can request a lot
|
|
||||||
torch.cuda.empty_cache()
|
|
||||||
|
|
||||||
with vae_info as vae:
|
|
||||||
vae.create_session()
|
|
||||||
|
|
||||||
# copied from
|
|
||||||
# https://github.com/huggingface/diffusers/blob/3ebbaf7c96801271f9e6c21400033b6aa5ffcf29/src/diffusers/pipelines/stable_diffusion/pipeline_onnx_stable_diffusion.py#L427
|
|
||||||
latents = 1 / 0.18215 * latents
|
|
||||||
# image = self.vae_decoder(latent_sample=latents)[0]
|
|
||||||
# it seems likes there is a strange result for using half-precision vae decoder if batchsize>1
|
|
||||||
image = np.concatenate([vae(latent_sample=latents[i : i + 1])[0] for i in range(latents.shape[0])])
|
|
||||||
|
|
||||||
image = np.clip(image / 2 + 0.5, 0, 1)
|
|
||||||
image = image.transpose((0, 2, 3, 1))
|
|
||||||
image = VaeImageProcessor.numpy_to_pil(image)[0]
|
|
||||||
|
|
||||||
torch.cuda.empty_cache()
|
|
||||||
|
|
||||||
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(
|
|
||||||
image=ImageField(image_name=image_dto.image_name),
|
|
||||||
width=image_dto.width,
|
|
||||||
height=image_dto.height,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@invocation_output("model_loader_output_onnx")
|
|
||||||
class ONNXModelLoaderOutput(BaseInvocationOutput):
|
|
||||||
"""Model loader output"""
|
|
||||||
|
|
||||||
unet: UNetField = OutputField(default=None, description=FieldDescriptions.unet, title="UNet")
|
|
||||||
clip: ClipField = OutputField(default=None, description=FieldDescriptions.clip, title="CLIP")
|
|
||||||
vae_decoder: VaeField = OutputField(default=None, description=FieldDescriptions.vae, title="VAE Decoder")
|
|
||||||
vae_encoder: VaeField = OutputField(default=None, description=FieldDescriptions.vae, title="VAE Encoder")
|
|
||||||
|
|
||||||
|
|
||||||
class OnnxModelField(BaseModel):
|
|
||||||
"""Onnx model field"""
|
|
||||||
|
|
||||||
model_name: str = Field(description="Name of the model")
|
|
||||||
base_model: BaseModelType = Field(description="Base model")
|
|
||||||
model_type: ModelType = Field(description="Model Type")
|
|
||||||
|
|
||||||
model_config = ConfigDict(protected_namespaces=())
|
|
||||||
|
|
||||||
|
|
||||||
@invocation("onnx_model_loader", title="ONNX Main Model", tags=["onnx", "model"], category="model", version="1.0.0")
|
|
||||||
class OnnxModelLoaderInvocation(BaseInvocation):
|
|
||||||
"""Loads a main model, outputting its submodels."""
|
|
||||||
|
|
||||||
model: OnnxModelField = InputField(
|
|
||||||
description=FieldDescriptions.onnx_main_model, input=Input.Direct, ui_type=UIType.ONNXModel
|
|
||||||
)
|
|
||||||
|
|
||||||
def invoke(self, context: InvocationContext) -> ONNXModelLoaderOutput:
|
|
||||||
base_model = self.model.base_model
|
|
||||||
model_name = self.model.model_name
|
|
||||||
model_type = ModelType.ONNX
|
|
||||||
|
|
||||||
# TODO: not found exceptions
|
|
||||||
if not context.services.model_manager.model_exists(
|
|
||||||
model_name=model_name,
|
|
||||||
base_model=base_model,
|
|
||||||
model_type=model_type,
|
|
||||||
):
|
|
||||||
raise Exception(f"Unknown {base_model} {model_type} model: {model_name}")
|
|
||||||
|
|
||||||
"""
|
|
||||||
if not context.services.model_manager.model_exists(
|
|
||||||
model_name=self.model_name,
|
|
||||||
model_type=SDModelType.Diffusers,
|
|
||||||
submodel=SDModelType.Tokenizer,
|
|
||||||
):
|
|
||||||
raise Exception(
|
|
||||||
f"Failed to find tokenizer submodel in {self.model_name}! Check if model corrupted"
|
|
||||||
)
|
|
||||||
|
|
||||||
if not context.services.model_manager.model_exists(
|
|
||||||
model_name=self.model_name,
|
|
||||||
model_type=SDModelType.Diffusers,
|
|
||||||
submodel=SDModelType.TextEncoder,
|
|
||||||
):
|
|
||||||
raise Exception(
|
|
||||||
f"Failed to find text_encoder submodel in {self.model_name}! Check if model corrupted"
|
|
||||||
)
|
|
||||||
|
|
||||||
if not context.services.model_manager.model_exists(
|
|
||||||
model_name=self.model_name,
|
|
||||||
model_type=SDModelType.Diffusers,
|
|
||||||
submodel=SDModelType.UNet,
|
|
||||||
):
|
|
||||||
raise Exception(
|
|
||||||
f"Failed to find unet submodel from {self.model_name}! Check if model corrupted"
|
|
||||||
)
|
|
||||||
"""
|
|
||||||
|
|
||||||
return ONNXModelLoaderOutput(
|
|
||||||
unet=UNetField(
|
|
||||||
unet=ModelInfo(
|
|
||||||
model_name=model_name,
|
|
||||||
base_model=base_model,
|
|
||||||
model_type=model_type,
|
|
||||||
submodel=SubModelType.UNet,
|
|
||||||
),
|
|
||||||
scheduler=ModelInfo(
|
|
||||||
model_name=model_name,
|
|
||||||
base_model=base_model,
|
|
||||||
model_type=model_type,
|
|
||||||
submodel=SubModelType.Scheduler,
|
|
||||||
),
|
|
||||||
loras=[],
|
|
||||||
),
|
|
||||||
clip=ClipField(
|
|
||||||
tokenizer=ModelInfo(
|
|
||||||
model_name=model_name,
|
|
||||||
base_model=base_model,
|
|
||||||
model_type=model_type,
|
|
||||||
submodel=SubModelType.Tokenizer,
|
|
||||||
),
|
|
||||||
text_encoder=ModelInfo(
|
|
||||||
model_name=model_name,
|
|
||||||
base_model=base_model,
|
|
||||||
model_type=model_type,
|
|
||||||
submodel=SubModelType.TextEncoder,
|
|
||||||
),
|
|
||||||
loras=[],
|
|
||||||
skipped_layers=0,
|
|
||||||
),
|
|
||||||
vae_decoder=VaeField(
|
|
||||||
vae=ModelInfo(
|
|
||||||
model_name=model_name,
|
|
||||||
base_model=base_model,
|
|
||||||
model_type=model_type,
|
|
||||||
submodel=SubModelType.VaeDecoder,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
vae_encoder=VaeField(
|
|
||||||
vae=ModelInfo(
|
|
||||||
model_name=model_name,
|
|
||||||
base_model=base_model,
|
|
||||||
model_type=model_type,
|
|
||||||
submodel=SubModelType.VaeEncoder,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||