mirror of
https://github.com/lllyasviel/Fooocus.git
synced 2026-08-16 13:13:16 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8da1d3ff68 | ||
|
|
710a9fa2c5 | ||
|
|
251a130f06 | ||
|
|
0a87da7dc1 | ||
|
|
1d98d1c760 | ||
|
|
1068d3fde4 | ||
|
|
082a5262b0 | ||
|
|
14895ebb13 | ||
|
|
b0d16a3aa7 | ||
|
|
fd74b57f56 | ||
|
|
8bd9ea1dbf | ||
|
|
ee12d114c1 | ||
|
|
2c78cec01d | ||
|
|
ef0acca9f9 | ||
|
|
60af8d2d84 | ||
|
|
39d07bf0f3 | ||
|
|
f0dcf5a911 | ||
|
|
c4d5b160be | ||
|
|
2f08cb4360 | ||
|
|
da3d4d006f | ||
|
|
c2dc17e883 | ||
|
|
1a53e0676a | ||
|
|
a5040f6218 | ||
|
|
3f25b885a7 | ||
|
|
e36fa0b5f7 | ||
|
|
1be3c504ed | ||
|
|
c4ce2ce600 | ||
|
|
03655fa5ea | ||
|
|
a9248c8e46 | ||
|
|
37360e95fe | ||
|
|
54985596e8 | ||
|
|
3a20e14ca0 | ||
|
|
2262061145 | ||
|
|
56928b769b | ||
|
|
2e8cff296e | ||
|
|
f597bf1ab6 | ||
|
|
f97adafc09 | ||
|
|
97a8475a62 | ||
|
|
033cb90e6e | ||
|
|
aed3240ccd | ||
|
|
4f12bbb02b | ||
|
|
9f93cf6110 | ||
|
|
1f429ffeda | ||
|
|
8d67166dd1 | ||
|
|
3a86fa2f0d | ||
|
|
ef8dd27f91 | ||
|
|
d46e47ab3d | ||
|
|
069bea534b | ||
|
|
e0d3325894 | ||
|
|
5a1003a726 | ||
|
|
5e8110e430 | ||
|
|
ee02643020 | ||
|
|
e1f4b65fc9 | ||
|
|
f2a21900c6 | ||
|
|
5a71495822 | ||
|
|
34f67c01a8 | ||
|
|
9178aa8ebb | ||
|
|
7c1a101c0f | ||
|
|
9d41c9521b | ||
|
|
3e453501f7 | ||
|
|
55ef7608ea | ||
|
|
ba77e7f706 | ||
|
|
5abae220c5 | ||
|
|
04d764820e | ||
|
|
350fdd9021 | ||
|
|
85a8deecee | ||
|
|
b58bc7774e | ||
|
|
2d55a5f257 | ||
|
|
cb24c686b0 | ||
|
|
ab01104d42 | ||
|
|
3d43976e8e | ||
|
|
07c6c89edf | ||
|
|
7899261755 | ||
|
|
64c29a8c43 | ||
|
|
4e658bb63a | ||
|
|
3ef663c5b7 | ||
|
|
bf70815a66 | ||
|
|
725bf05c31 | ||
|
|
4a070a9d61 | ||
|
|
0e621ae34e | ||
|
|
dfff9b7dcf | ||
|
|
989a1ad52b | ||
|
|
de34023c79 | ||
|
|
12dc2396f6 | ||
|
|
c227cf1f56 | ||
|
|
57d2f2a0dd | ||
|
|
67289dd0fe | ||
|
|
cc58fe5270 | ||
|
|
4e5509351f | ||
|
|
1d1a4a3ebd | ||
|
|
d850bca09f | ||
|
|
04f64ab0bc | ||
|
|
7b70d27032 | ||
|
|
4da5a68c10 | ||
|
|
302bfdf855 | ||
|
|
7537612bcc | ||
|
|
ac14d9d03c | ||
|
|
65a8b25129 | ||
|
|
c995511705 | ||
|
|
e94b97604f | ||
|
|
35b74dfa64 | ||
|
|
dad228907e | ||
|
|
0466ff944c | ||
|
|
13599edb9b | ||
|
|
2e2e8f851a | ||
|
|
3bae73e23e | ||
|
|
3a55e7e391 | ||
|
|
00d3d1b4b3 | ||
|
|
33fa175bd4 | ||
|
|
1eb58fa366 | ||
|
|
5e594685e1 | ||
|
|
96bf89f782 | ||
|
|
bdd6b1a9b0 | ||
|
|
052393bb9b | ||
|
|
6308fb8b54 | ||
|
|
f54364fe4e | ||
|
|
c32bc5e199 | ||
|
|
121f1e0a15 | ||
|
|
c36e951781 | ||
|
|
5b2d046b12 | ||
|
|
d16a54edd6 | ||
|
|
dbf49d323e | ||
|
|
e64130323a | ||
|
|
1dff430d4c | ||
|
|
5ada070d88 | ||
|
|
e2f9bcb11d | ||
|
|
523ef5c70e | ||
|
|
9aaa400553 | ||
|
|
7564dd5131 | ||
|
|
978267f461 | ||
|
|
e9bc5e50c6 | ||
|
|
856eb750ab | ||
|
|
6b41af7140 | ||
|
|
532a6e2e67 | ||
|
|
a1bda88aa3 | ||
|
|
3efce581ca | ||
|
|
ee361715af | ||
|
|
c08518abae | ||
|
|
6b44c101db | ||
|
|
5bf96018fe | ||
|
|
d057f2fae9 | ||
|
|
86cba3f223 | ||
|
|
37274c652a | ||
|
|
55e23a9374 | ||
|
|
4a44be36fd | ||
|
|
8baafcd79c | ||
|
|
0da614f7e1 | ||
|
|
9cd0366d30 | ||
|
|
f51e0138e6 | ||
|
|
4363dbc303 | ||
|
|
f7f0b51bab | ||
|
|
6da0441cc7 | ||
|
|
57a01865b9 | ||
|
|
532401df76 | ||
|
|
d57afc88a4 | ||
|
|
39669453cd | ||
|
|
2831dc70a7 | ||
|
|
84e3124c37 | ||
|
|
ead24c9361 | ||
|
|
5c7dc12470 | ||
|
|
bc9c586082 | ||
|
|
f6117180d4 | ||
|
|
400471f7af | ||
|
|
5409bfdb26 | ||
|
|
85e8aa8ce2 | ||
|
|
db7d2018ca | ||
|
|
4701b4f8f3 | ||
|
|
25650b4bc4 | ||
|
|
b6e4bb86f4 | ||
|
|
831c6b93cc | ||
|
|
3a64fe3eb3 | ||
|
|
6cfcc62000 | ||
|
|
28cdc2f104 | ||
|
|
ee96b854d9 | ||
|
|
9155d94067 | ||
|
|
e54fb54f91 | ||
|
|
e965bfc39c | ||
|
|
e241c53f0e | ||
|
|
c3fd57acb9 | ||
|
|
fb94394b10 | ||
|
|
4ea3baff50 | ||
|
|
90839430da | ||
|
|
4945fc9962 | ||
|
|
6db14acf8e | ||
|
|
41e88a4e8d | ||
|
|
4f4d23f4e3 | ||
|
|
9c30961efd | ||
|
|
692beadbdc | ||
|
|
4e526e255e | ||
|
|
f4a6350300 | ||
|
|
b6d23670d8 | ||
|
|
ba9eadbcda | ||
|
|
d3113f5c3f | ||
|
|
c898e6a4dc | ||
|
|
3be76ef8a3 | ||
|
|
18f9f7dc31 | ||
|
|
468d704b29 | ||
|
|
eebd7752ab | ||
|
|
b5f019fb62 | ||
|
|
9c19300a3e | ||
|
|
4d34f31a72 | ||
|
|
ef1999c52c | ||
|
|
7cfb5e742d | ||
|
|
965364cd80 | ||
|
|
5b7ddf8b22 | ||
|
|
0ed01da4e4 | ||
|
|
187f4a76c6 | ||
|
|
f8ca04a406 | ||
|
|
a78f66ffb5 | ||
|
|
1c999be8c8 | ||
|
|
f4a8bf24cf | ||
|
|
074b655dff | ||
|
|
ee3ce95566 | ||
|
|
2037de3fcb | ||
|
|
eb3f4d745c | ||
|
|
b9d7e77b0d | ||
|
|
c32b9bdc44 | ||
|
|
98ba1d5d47 | ||
|
|
231956065f | ||
|
|
e4929a9ed7 | ||
|
|
b7715b0a0c | ||
|
|
ac10e51364 | ||
|
|
95f93a1f4b | ||
|
|
d1a450c581 | ||
|
|
fdc4dc1d87 | ||
|
|
71eb040afc | ||
|
|
1bcbd6501b | ||
|
|
31fc99d2bc | ||
|
|
3b1cd37631 | ||
|
|
0cb2db9c41 | ||
|
|
79afb3a619 | ||
|
|
7b26b29226 | ||
|
|
69ad1b3c24 | ||
|
|
ff2187efed | ||
|
|
80068a0cd7 | ||
|
|
1cc40d24d7 | ||
|
|
0b00aaf897 | ||
|
|
f6d67d7655 | ||
|
|
dc5b5238c8 | ||
|
|
6261f17561 | ||
|
|
fee997d929 | ||
|
|
c48d6e7404 | ||
|
|
888cc08405 | ||
|
|
f298ebca76 | ||
|
|
176faf6f34 | ||
|
|
624f74a1ed | ||
|
|
e5af760db8 | ||
|
|
b5163e057f | ||
|
|
0c4f20a0d2 | ||
|
|
2521af308b | ||
|
|
8e62a72a63 | ||
|
|
c0e11c3451 | ||
|
|
eb0b4d51ef | ||
|
|
cce73d28b4 | ||
|
|
2f6ebbf876 | ||
|
|
ad158450e3 | ||
|
|
48b1324a26 | ||
|
|
986ab451cf | ||
|
|
7b5bced6c2 | ||
|
|
1f9a072d66 | ||
|
|
b0df0d57f6 | ||
|
|
81107298a8 | ||
|
|
f7bb578a14 | ||
|
|
f308489440 | ||
|
|
7a6b46f363 | ||
|
|
0e1aa8d084 | ||
|
|
69a23c4d60 | ||
|
|
1669370d2e | ||
|
|
67808d5ee5 | ||
|
|
efb312d495 | ||
|
|
085bc3aeaa | ||
|
|
ec5dd950a2 | ||
|
|
3a727fd240 | ||
|
|
776e080b3f | ||
|
|
05ef3e6861 | ||
|
|
a7f82b2545 | ||
|
|
26ea508588 | ||
|
|
059037eeb2 | ||
|
|
179bcb2c4e | ||
|
|
a39c1d1349 | ||
|
|
323af5667a | ||
|
|
bac5c882ba | ||
|
|
bc4cbbd9d9 | ||
|
|
f85c10338c | ||
|
|
0a17fb8bc6 | ||
|
|
5b99e3a1e4 | ||
|
|
28b07cd658 | ||
|
|
6eeb90ad4e | ||
|
|
df615d3781 | ||
|
|
bb45d0309f | ||
|
|
0d5f5e1f8b | ||
|
|
2bf2c88bfa | ||
|
|
5660ea203a | ||
|
|
fcc7458d15 | ||
|
|
406133f0fb | ||
|
|
02f3b2be19 | ||
|
|
322aa5a724 | ||
|
|
c175afb394 | ||
|
|
ee06e87a58 | ||
|
|
28b5782d8d | ||
|
|
8b8d926712 | ||
|
|
2e3e90e282 | ||
|
|
f8e651a483 | ||
|
|
d4a062f8e1 | ||
|
|
a321f4c488 | ||
|
|
f1b75ee690 | ||
|
|
9c3fb90df3 | ||
|
|
47441736ae | ||
|
|
9cd3c06c6f | ||
|
|
1ee5d24f33 | ||
|
|
8bbde9bcd0 | ||
|
|
8f52b6d5f3 | ||
|
|
7b72d894c5 | ||
|
|
5f3a87d871 | ||
|
|
85b3e64e60 | ||
|
|
48ca305332 | ||
|
|
a2d4df8510 | ||
|
|
de9950187f | ||
|
|
9a8e9b9266 | ||
|
|
e8d88d3e25 | ||
|
|
0d878b0282 | ||
|
|
de570566e2 | ||
|
|
e3db20b9e5 | ||
|
|
80050bace3 | ||
|
|
b2f391213f | ||
|
|
65229bdf89 | ||
|
|
b54b3698f4 | ||
|
|
e8626fd402 | ||
|
|
f31f3edeec | ||
|
|
cca0ca704a | ||
|
|
ec57c1fde0 | ||
|
|
3bc9ac88fd | ||
|
|
bd4d40203c | ||
|
|
8f98e96d73 | ||
|
|
dececbd060 | ||
|
|
8f9f020e8f | ||
|
|
675805960a | ||
|
|
3b97e49dd8 | ||
|
|
943098f8da | ||
|
|
cf2c89c288 | ||
|
|
28f9342d10 | ||
|
|
166bb98333 | ||
|
|
ab528b78cf | ||
|
|
608fe3962c | ||
|
|
e59fd50787 | ||
|
|
13f476eb36 | ||
|
|
fce145dfac | ||
|
|
eae0b71ff9 | ||
|
|
3a9c3c07d1 | ||
|
|
a662567f6c | ||
|
|
8c49bb1cba | ||
|
|
8f23e2e969 | ||
|
|
cbe66fd5e0 | ||
|
|
a9bd188555 | ||
|
|
861c8d38df | ||
|
|
6769ab0f9b | ||
|
|
97a6e87d18 | ||
|
|
ad1ae0fd48 | ||
|
|
cec0c2a8df | ||
|
|
375b30f375 | ||
|
|
5158463216 | ||
|
|
305c39d49c | ||
|
|
c9a5e729d9 | ||
|
|
f80f159d8f | ||
|
|
6c812b68db | ||
|
|
e10da9de49 | ||
|
|
a8be5d7972 | ||
|
|
ed70c578fa | ||
|
|
7157c1a3ed | ||
|
|
d3d63d5bf6 | ||
|
|
7e222cf3e1 | ||
|
|
ac8002d2a4 | ||
|
|
649f45a6df | ||
|
|
54f4b265e0 | ||
|
|
63b084f846 | ||
|
|
b8a035dc15 | ||
|
|
ffd5eabe08 | ||
|
|
fa86cf4d54 | ||
|
|
e6aeefd2b4 | ||
|
|
e7fe1d443a | ||
|
|
33bf502b47 | ||
|
|
7e0c6d3421 | ||
|
|
38b01230f2 | ||
|
|
448fb6e7ea | ||
|
|
20979fcd1b | ||
|
|
2bef62c545 | ||
|
|
fd4a5b2eaf | ||
|
|
3d180e9eb6 | ||
|
|
64159f0ce3 | ||
|
|
e89dc07485 | ||
|
|
7632d752e0 | ||
|
|
09f70de40e | ||
|
|
316ac6fafa | ||
|
|
e17bbdbf5f | ||
|
|
c35321013a | ||
|
|
4fe08161a5 | ||
|
|
a9bb1079cf | ||
|
|
5e8a77fdac | ||
|
|
2342761fa1 | ||
|
|
10574f1cc2 | ||
|
|
933da40735 | ||
|
|
2165114876 | ||
|
|
1babf969af | ||
|
|
e19e01ae61 | ||
|
|
87de9edb1a | ||
|
|
49f47a6f5e | ||
|
|
b95c7378c9 | ||
|
|
51d87ce00b | ||
|
|
32c282daab | ||
|
|
9ced4fe651 | ||
|
|
c62fc6e1bc | ||
|
|
7d532b0285 | ||
|
|
01bfa11fd1 | ||
|
|
5dc1221c65 | ||
|
|
f07e64b357 | ||
|
|
f30e71ad64 | ||
|
|
1b96d3ba0b | ||
|
|
07b0a37de0 | ||
|
|
34bcfa79c0 | ||
|
|
d8616fe8dc | ||
|
|
c697826f9f | ||
|
|
efcd4837ce | ||
|
|
1d16b942b3 | ||
|
|
51ffcc47e4 | ||
|
|
ce2c53f121 | ||
|
|
6364bb37cf | ||
|
|
867402e3df | ||
|
|
634420ed94 | ||
|
|
769bfc7b6d | ||
|
|
dd8bf62263 | ||
|
|
932f1333e4 | ||
|
|
cb4859fdd8 | ||
|
|
49b94fa26e | ||
|
|
759bfadefa | ||
|
|
b103f88f18 | ||
|
|
48bcc7a5fc | ||
|
|
fa3f1709cf | ||
|
|
809d4a8832 | ||
|
|
76120e045e | ||
|
|
56c8a342ec | ||
|
|
3445c0d7d1 | ||
|
|
f07f7693d3 | ||
|
|
5cbefa821f | ||
|
|
4607316c2f | ||
|
|
f6eee62520 | ||
|
|
9982d34b6c | ||
|
|
a16b451fd7 | ||
|
|
823fa924d3 | ||
|
|
d7ee814579 | ||
|
|
175f3e3040 | ||
|
|
214c2ba04c | ||
|
|
959f965b77 | ||
|
|
37ec436a3e | ||
|
|
01b1e98d37 | ||
|
|
a6ac4604cd | ||
|
|
38e70cebcc | ||
|
|
bb965067e0 | ||
|
|
9bb16d0d76 | ||
|
|
486568a04f | ||
|
|
19a03f1d0a | ||
|
|
5d32c208b2 | ||
|
|
4d4ba87e9d | ||
|
|
fbbaf86232 | ||
|
|
2f58ac2334 | ||
|
|
523aa190db | ||
|
|
10a9f0fc9d | ||
|
|
60cb91c406 | ||
|
|
504e5478b0 | ||
|
|
ee2fe66811 | ||
|
|
0b90fd9e8e | ||
|
|
7a6775acdc | ||
|
|
47281e52c4 | ||
|
|
1272d50d35 | ||
|
|
00c2ba63fa | ||
|
|
81650a4305 | ||
|
|
23a7559db8 | ||
|
|
43bc2df328 | ||
|
|
6f3fa4b670 | ||
|
|
576aa9283f | ||
|
|
4c94f0f590 | ||
|
|
4cf0c778da | ||
|
|
22fc28958f | ||
|
|
566cf08750 | ||
|
|
7d81eeed7e | ||
|
|
3acf1d6494 | ||
|
|
cb950bd247 | ||
|
|
dde311639e | ||
|
|
3cc95314d6 | ||
|
|
736a5aa3ac | ||
|
|
60c05342b2 | ||
|
|
9b0dc625fa | ||
|
|
adf134d446 | ||
|
|
65190b4a68 | ||
|
|
650f32b920 | ||
|
|
4479fbaaef | ||
|
|
591d829f29 | ||
|
|
8ad034937d | ||
|
|
590d7ccdc7 | ||
|
|
df93f738de | ||
|
|
de8d954584 | ||
|
|
7075fd39c3 | ||
|
|
4ce5c51d8e | ||
|
|
4a04d8b54a | ||
|
|
9183cc0c71 | ||
|
|
e4afe5819b | ||
|
|
02282ab57a | ||
|
|
849dafb95b | ||
|
|
d4a9b116e4 | ||
|
|
ff8d649cac | ||
|
|
f1e1d2f7d7 | ||
|
|
04066124c2 | ||
|
|
a0539c37f5 | ||
|
|
37da4ac13a | ||
|
|
3350b564a5 | ||
|
|
48d2fb85f9 | ||
|
|
898fcb752b | ||
|
|
6d76c55964 | ||
|
|
b1cd3fb85b | ||
|
|
59b32e9dec | ||
|
|
6e2126b112 | ||
|
|
7ced4e26a7 | ||
|
|
f553d0fb18 | ||
|
|
6f02c797c6 | ||
|
|
7d003c1cd4 | ||
|
|
bb6addee70 | ||
|
|
b3832e255e | ||
|
|
bb4c7db621 | ||
|
|
296b1da278 | ||
|
|
906dfa1331 | ||
|
|
9660daff94 | ||
|
|
d2c8f16082 | ||
|
|
c0b4e766fb | ||
|
|
30af33a458 | ||
|
|
9532d167f8 | ||
|
|
c751758016 | ||
|
|
e5f614c14e | ||
|
|
78c78cca72 | ||
|
|
02eadff97a | ||
|
|
1374481b59 | ||
|
|
e2832177e2 | ||
|
|
4dc3bd8b67 | ||
|
|
2566ec5da5 | ||
|
|
1eaf7796da | ||
|
|
99435762e6 | ||
|
|
a8803a7724 | ||
|
|
f8d842a52c | ||
|
|
99a4ebf863 | ||
|
|
c79d653e49 | ||
|
|
3d42abb682 | ||
|
|
a5236781fc | ||
|
|
1cdadc37f5 | ||
|
|
6a068d6bad | ||
|
|
d189604233 | ||
|
|
5e3728c839 | ||
|
|
9ec2f66fbe | ||
|
|
e3ac360396 | ||
|
|
ef4e120d0a | ||
|
|
32ff981075 | ||
|
|
6fce823fc4 | ||
|
|
eb72b16da0 | ||
|
|
0823b741a3 | ||
|
|
cf9379763e | ||
|
|
f3b7c2b9b9 | ||
|
|
64163cf174 | ||
|
|
a827396bed | ||
|
|
57773ef80b | ||
|
|
bb5779196a | ||
|
|
919c67fcd9 | ||
|
|
034367ab48 | ||
|
|
e564ba46a6 | ||
|
|
635b6fc1bc | ||
|
|
3dd11cf405 | ||
|
|
79f2f22101 | ||
|
|
9cc2325891 | ||
|
|
4e1441c07e | ||
|
|
20b13b6a8b | ||
|
|
da3f6d5c8b | ||
|
|
589409e3f4 | ||
|
|
11f71c7291 | ||
|
|
1e3b24db43 | ||
|
|
19a345e38a | ||
|
|
050a3bc23a | ||
|
|
10ae78e100 | ||
|
|
9ee64514c8 | ||
|
|
680c35fc18 | ||
|
|
9906b5a58d | ||
|
|
a27ca7eabb | ||
|
|
d042af8a80 | ||
|
|
4b6082bca9 | ||
|
|
138d461982 | ||
|
|
cdf22b8ccc | ||
|
|
f2537e5751 | ||
|
|
a60615de61 | ||
|
|
b3d662054f | ||
|
|
e27383c07f | ||
|
|
79b1a605a7 | ||
|
|
42c7365103 | ||
|
|
5a8157c89a | ||
|
|
96ba906b44 | ||
|
|
0080b25ae7 | ||
|
|
76861de84f | ||
|
|
ed69bea3e3 | ||
|
|
6f24c03826 | ||
|
|
2e082e4ce1 | ||
|
|
d48b81ac37 | ||
|
|
b5e24300ff | ||
|
|
460cac649e | ||
|
|
616ce3d553 | ||
|
|
05d70675be | ||
|
|
0868f61a1c | ||
|
|
f010dc6994 | ||
|
|
9efa4ecd27 | ||
|
|
2f6843da95 | ||
|
|
b3c6624e82 | ||
|
|
88879d133f | ||
|
|
e4113d3231 | ||
|
|
7d4b635460 | ||
|
|
739a8a592a | ||
|
|
63820ff362 | ||
|
|
80d80c5962 | ||
|
|
1d4c42842d | ||
|
|
a428d5190d | ||
|
|
a252f75549 | ||
|
|
10e2db67d6 | ||
|
|
c4118d2418 | ||
|
|
3b9266e848 | ||
|
|
26fe88d8a6 | ||
|
|
e61aac34ca | ||
|
|
4c867c1b8b | ||
|
|
42bb1cda2f | ||
|
|
cac8af0928 | ||
|
|
132afcc2a2 | ||
|
|
5e6b27a680 | ||
|
|
bbdf4bd120 | ||
|
|
777510dc9b | ||
|
|
34a05f021e | ||
|
|
f55b15ab41 | ||
|
|
a0bc991e38 | ||
|
|
df34b640a8 | ||
|
|
5e8f2f96e9 | ||
|
|
db945c8a27 | ||
|
|
ec3d211ab5 | ||
|
|
07a564b779 | ||
|
|
c24be3f886 | ||
|
|
a960d24825 | ||
|
|
1601af43be | ||
|
|
e44e1e5b67 | ||
|
|
53573fa073 | ||
|
|
778a3cf81e | ||
|
|
f92b8038b4 | ||
|
|
c275434c59 | ||
|
|
0723c2fa52 | ||
|
|
8afdd7c7b0 | ||
|
|
8ee81ecd56 | ||
|
|
98792cfecf | ||
|
|
65d7e75d8b | ||
|
|
302b36bc60 | ||
|
|
8747b45d4b | ||
|
|
0a2c1c4cbf | ||
|
|
7cbfb1bc2c | ||
|
|
53967f23d0 | ||
|
|
1f8944b974 | ||
|
|
05d92ca50b | ||
|
|
fc35888757 | ||
|
|
de6b1267e6 | ||
|
|
05ca54e6e0 | ||
|
|
3fc4eb1579 | ||
|
|
76708ce286 | ||
|
|
71e9ebc7a2 | ||
|
|
331dacbacd | ||
|
|
5eb18f3a3b | ||
|
|
f643a63155 | ||
|
|
784161066e | ||
|
|
51b9bf5e7d | ||
|
|
a45e4d011e | ||
|
|
5bb919c055 | ||
|
|
8f370762cd | ||
|
|
f1f400b511 | ||
|
|
df9a0549b0 | ||
|
|
4ff670c7fb | ||
|
|
d631cfe491 | ||
|
|
e00b647eee | ||
|
|
e51f7fea59 | ||
|
|
cf484c5ed9 | ||
|
|
a65b8d470c | ||
|
|
3b8b2750ae | ||
|
|
8ec058bd63 | ||
|
|
7710c30277 | ||
|
|
0879e92ab8 | ||
|
|
a39f033603 | ||
|
|
81ce2bb37f | ||
|
|
e83828b7b2 | ||
|
|
51cdc5e53a | ||
|
|
6c03faf568 | ||
|
|
9506714985 | ||
|
|
f170e1c08e | ||
|
|
6faaac333b | ||
|
|
b42e96a52d | ||
|
|
927462e107 | ||
|
|
0f62757eff | ||
|
|
6189e482bd | ||
|
|
aebfc56641 | ||
|
|
8bfc90202d | ||
|
|
00be37e2b8 | ||
|
|
7fb0996461 | ||
|
|
046c93c0f2 | ||
|
|
4c0a16d9bc | ||
|
|
ba58dbc8bc | ||
|
|
6c56b580d7 | ||
|
|
80632d5eb3 | ||
|
|
7d48ecee32 | ||
|
|
8872b97346 | ||
|
|
480a7222c5 | ||
|
|
2f31d9e5a7 | ||
|
|
bbae307ef2 | ||
|
|
225947ac1a | ||
|
|
da40b0b461 | ||
|
|
93ee4eabe3 | ||
|
|
e5ca884886 | ||
|
|
726f684ff2 | ||
|
|
7e031b1b03 | ||
|
|
cdf642437c | ||
|
|
6597b3df64 | ||
|
|
5456a5784b | ||
|
|
65b9364170 | ||
|
|
c9ec6e22ad | ||
|
|
b89192162d | ||
|
|
098d08cd22 | ||
|
|
b6752c74e3 | ||
|
|
969a54e85b | ||
|
|
e485f7b242 | ||
|
|
32719e3ad1 | ||
|
|
897a56024e | ||
|
|
0927445492 | ||
|
|
6ae7de377d | ||
|
|
92d1b318cf | ||
|
|
7ecdc74844 | ||
|
|
1adeebf3b8 | ||
|
|
699017ab32 | ||
|
|
b61642ecba | ||
|
|
43e59c1676 | ||
|
|
6cfb31d67d | ||
|
|
74b26a3cbb | ||
|
|
7b73152fe3 | ||
|
|
d819ffae56 | ||
|
|
71e99185cb | ||
|
|
ceee6dfd73 | ||
|
|
6adc771888 | ||
|
|
5f86b0c897 | ||
|
|
ec5bbe7f02 | ||
|
|
8ef31d33af | ||
|
|
58c29aed00 | ||
|
|
8ef00d87b4 | ||
|
|
d1b4389098 | ||
|
|
cf7cde08b1 | ||
|
|
b5b4fd27f1 | ||
|
|
bcee7fd273 | ||
|
|
0c1348ffdc | ||
|
|
4e4bab1ff0 | ||
|
|
eab402c77d | ||
|
|
f2c545803c | ||
|
|
917f284db1 | ||
|
|
db8748a34d | ||
|
|
0f58038d85 | ||
|
|
ac1050dd24 | ||
|
|
1964aec7f8 | ||
|
|
1a088db0eb | ||
|
|
f7f548ff35 | ||
|
|
0f09b61ce5 | ||
|
|
328ad8ad2b | ||
|
|
e0138752a5 | ||
|
|
8f279294bf | ||
|
|
7ba85d4190 | ||
|
|
663f7926ab | ||
|
|
462e635ae2 | ||
|
|
80f566d1cc | ||
|
|
b87891404c | ||
|
|
2e543fb93f | ||
|
|
e32f04da34 | ||
|
|
53beede21d | ||
|
|
28ce927586 | ||
|
|
7700276b50 | ||
|
|
e0f97b6fef | ||
|
|
07ea52542e | ||
|
|
a9b7219604 | ||
|
|
eccf32b78c | ||
|
|
edece07e74 | ||
|
|
4f88bad965 | ||
|
|
6caec16607 | ||
|
|
47876aaf99 | ||
|
|
25fed6a4fe | ||
|
|
0f658a97f7 | ||
|
|
e6557a4ded | ||
|
|
9337ad5f5e | ||
|
|
480e89ca03 | ||
|
|
fe766115a8 | ||
|
|
14713eb95e | ||
|
|
afdf47ea79 | ||
|
|
da03ec1e25 | ||
|
|
d50714b261 | ||
|
|
9e9821b3ed | ||
|
|
a19c9ac132 | ||
|
|
c4125c3b6b | ||
|
|
c585ef23c4 | ||
|
|
0d0456e492 | ||
|
|
074b5d5e3e | ||
|
|
b5be8f3918 | ||
|
|
6b444611ea | ||
|
|
efc573a35a | ||
|
|
1d6d25e836 | ||
|
|
5b111f09cc | ||
|
|
eeddd25ece | ||
|
|
7fd6fff123 | ||
|
|
7c1f0bd52d | ||
|
|
13f9daabe0 | ||
|
|
75ebaf2677 | ||
|
|
afc3407a44 | ||
|
|
493421b423 | ||
|
|
74ae79fa2b | ||
|
|
88a5556f3d | ||
|
|
fa3fdeb53c | ||
|
|
6d6437ab7d | ||
|
|
6acb2fb66d | ||
|
|
b10d9d78f6 | ||
|
|
8a6cfbbf15 | ||
|
|
43c443d80e | ||
|
|
024b021eac | ||
|
|
14b8e494f9 | ||
|
|
75b8718ed4 | ||
|
|
fe093c48fd | ||
|
|
97079667df | ||
|
|
496766edd7 | ||
|
|
09e0d1cb3a | ||
|
|
7538b4d17b | ||
|
|
f3b96ef9f2 | ||
|
|
0425ea487c | ||
|
|
ec229d06af | ||
|
|
dcc4463a24 | ||
|
|
46a6cfe090 | ||
|
|
67f00ac391 | ||
|
|
ed0e911276 | ||
|
|
442b8d2b0d | ||
|
|
695784d92a | ||
|
|
fe16e8f050 | ||
|
|
585daa9452 | ||
|
|
93df594b72 | ||
|
|
dd3c62012e | ||
|
|
0c5e78961e | ||
|
|
63545b224e | ||
|
|
4bc5b0e50f | ||
|
|
084f1dfb72 | ||
|
|
d2e7d940ce | ||
|
|
41fcda7336 | ||
|
|
9c97310d92 | ||
|
|
ddf4abbfd4 | ||
|
|
349be5f170 | ||
|
|
4f0777e9b3 | ||
|
|
cb5c4b1b6c | ||
|
|
64a45417d9 | ||
|
|
814aac94aa |
@@ -0,0 +1,54 @@
|
||||
__pycache__
|
||||
*.ckpt
|
||||
*.safetensors
|
||||
*.pth
|
||||
*.pt
|
||||
*.bin
|
||||
*.patch
|
||||
*.backup
|
||||
*.corrupted
|
||||
*.partial
|
||||
*.onnx
|
||||
sorted_styles.json
|
||||
/input
|
||||
/cache
|
||||
/language/default.json
|
||||
/test_imgs
|
||||
config.txt
|
||||
config_modification_tutorial.txt
|
||||
user_path_config.txt
|
||||
user_path_config-deprecated.txt
|
||||
/modules/*.png
|
||||
/repositories
|
||||
/fooocus_env
|
||||
/venv
|
||||
/tmp
|
||||
/ui-config.json
|
||||
/outputs
|
||||
/config.json
|
||||
/log
|
||||
/webui.settings.bat
|
||||
/embeddings
|
||||
/styles.csv
|
||||
/params.txt
|
||||
/styles.csv.bak
|
||||
/webui-user.bat
|
||||
/webui-user.sh
|
||||
/interrogate
|
||||
/user.css
|
||||
/.idea
|
||||
/notification.ogg
|
||||
/notification.mp3
|
||||
/SwinIR
|
||||
/textual_inversion
|
||||
.vscode
|
||||
/extensions
|
||||
/test/stdout.txt
|
||||
/test/stderr.txt
|
||||
/cache.json*
|
||||
/config_states/
|
||||
/node_modules
|
||||
/package-lock.json
|
||||
/.coverage*
|
||||
/auth.json
|
||||
.DS_Store
|
||||
@@ -0,0 +1,3 @@
|
||||
# Ensure that shell scripts always use lf line endings, e.g. entrypoint.sh for docker
|
||||
* text=auto
|
||||
*.sh text eol=lf
|
||||
@@ -0,0 +1 @@
|
||||
* @lllyasviel
|
||||
@@ -0,0 +1,107 @@
|
||||
name: Bug Report
|
||||
description: You think something is broken in Fooocus
|
||||
title: "[Bug]: "
|
||||
labels: ["bug", "triage"]
|
||||
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
> The title of the bug report should be short and descriptive.
|
||||
> Use relevant keywords for searchability.
|
||||
> Do not leave it blank, but also do not put an entire error log in it.
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Checklist
|
||||
description: |
|
||||
Please perform basic debugging to see if your configuration is the cause of the issue.
|
||||
Basic debug procedure
|
||||
1. Update Fooocus - sometimes things just need to be updated
|
||||
2. Backup and remove your config.txt - check if the issue is caused by bad configuration
|
||||
3. Try a fresh installation of Fooocus in a different directory - see if a clean installation solves the issue
|
||||
Before making a issue report please, check that the issue hasn't been reported recently.
|
||||
options:
|
||||
- label: The issue has not been resolved by following the [troubleshooting guide](https://github.com/lllyasviel/Fooocus/blob/main/troubleshoot.md)
|
||||
- label: The issue exists on a clean installation of Fooocus
|
||||
- label: The issue exists in the current version of Fooocus
|
||||
- label: The issue has not been reported before recently
|
||||
- label: The issue has been reported before but has not been fixed yet
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
> Please fill this form with as much information as possible. Don't forget to add information about "What browsers" and provide screenshots if possible
|
||||
- type: textarea
|
||||
id: what-did
|
||||
attributes:
|
||||
label: What happened?
|
||||
description: Tell us what happened in a very clear and simple way
|
||||
placeholder: |
|
||||
image generation is not working as intended.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: steps
|
||||
attributes:
|
||||
label: Steps to reproduce the problem
|
||||
description: Please provide us with precise step by step instructions on how to reproduce the bug
|
||||
placeholder: |
|
||||
1. Go to ...
|
||||
2. Press ...
|
||||
3. ...
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: what-should
|
||||
attributes:
|
||||
label: What should have happened?
|
||||
description: Tell us what you think the normal behavior should be
|
||||
placeholder: |
|
||||
Fooocus should ...
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: browsers
|
||||
attributes:
|
||||
label: What browsers do you use to access Fooocus?
|
||||
multiple: true
|
||||
options:
|
||||
- Mozilla Firefox
|
||||
- Google Chrome
|
||||
- Brave
|
||||
- Apple Safari
|
||||
- Microsoft Edge
|
||||
- Android
|
||||
- iOS
|
||||
- Other
|
||||
- type: dropdown
|
||||
id: hosting
|
||||
attributes:
|
||||
label: Where are you running Fooocus?
|
||||
multiple: false
|
||||
options:
|
||||
- Locally
|
||||
- Locally with virtualization (e.g. Docker)
|
||||
- Cloud (Google Colab)
|
||||
- Cloud (other)
|
||||
- type: input
|
||||
id: operating-system
|
||||
attributes:
|
||||
label: What operating system are you using?
|
||||
placeholder: |
|
||||
Windows 10
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: Console logs
|
||||
description: Please provide **full** cmd/terminal logs from the moment you started UI to the end of it, after the bug occured. If it's very long, provide a link to pastebin or similar service.
|
||||
render: Shell
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: misc
|
||||
attributes:
|
||||
label: Additional information
|
||||
description: |
|
||||
Please provide us with any relevant additional info or context.
|
||||
Examples:
|
||||
I have updated my GPU driver recently.
|
||||
@@ -0,0 +1,5 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Ask a question
|
||||
url: https://github.com/lllyasviel/Fooocus/discussions/new?category=q-a
|
||||
about: Ask the community for help
|
||||
@@ -0,0 +1,40 @@
|
||||
name: Feature request
|
||||
description: Suggest an idea for this project
|
||||
title: "[Feature Request]: "
|
||||
labels: ["enhancement", "triage"]
|
||||
|
||||
body:
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Is there an existing issue for this?
|
||||
description: Please search to see if an issue already exists for the feature you want, and that it's not implemented in a recent build/commit.
|
||||
options:
|
||||
- label: I have searched the existing issues and checked the recent builds/commits
|
||||
required: true
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
*Please fill this form with as much information as possible, provide screenshots and/or illustrations of the feature if possible*
|
||||
- type: textarea
|
||||
id: feature
|
||||
attributes:
|
||||
label: What would your feature do?
|
||||
description: Tell us about your feature in a very clear and simple way, and what problem it would solve
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: workflow
|
||||
attributes:
|
||||
label: Proposed workflow
|
||||
description: Please provide us with step by step information on how you'd like the feature to be accessed and used
|
||||
value: |
|
||||
1. Go to ....
|
||||
2. Press ....
|
||||
3. ...
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: misc
|
||||
attributes:
|
||||
label: Additional information
|
||||
description: Add any other context or screenshots about the feature request here.
|
||||
@@ -0,0 +1,6 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "monthly"
|
||||
@@ -0,0 +1,47 @@
|
||||
name: Docker image build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
tags:
|
||||
- v*
|
||||
|
||||
jobs:
|
||||
build-and-push-image:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Log in to the Container registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: ghcr.io/${{ github.repository_owner }}/${{ github.event.repository.name }}
|
||||
tags: |
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=semver,pattern={{major}}
|
||||
type=edge,branch=main
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
+23
-2
@@ -2,8 +2,26 @@ __pycache__
|
||||
*.ckpt
|
||||
*.safetensors
|
||||
*.pth
|
||||
!taesdxl_decoder.pth
|
||||
*.pt
|
||||
*.bin
|
||||
*.patch
|
||||
*.backup
|
||||
*.corrupted
|
||||
*.partial
|
||||
*.onnx
|
||||
sorted_styles.json
|
||||
hash_cache.txt
|
||||
/input
|
||||
/cache
|
||||
/language/default.json
|
||||
/test_imgs
|
||||
config.txt
|
||||
config_modification_tutorial.txt
|
||||
user_path_config.txt
|
||||
user_path_config-deprecated.txt
|
||||
/modules/*.png
|
||||
/repositories
|
||||
/fooocus_env
|
||||
/venv
|
||||
/tmp
|
||||
/ui-config.json
|
||||
@@ -20,7 +38,8 @@ __pycache__
|
||||
/interrogate
|
||||
/user.css
|
||||
/.idea
|
||||
notification.mp3
|
||||
/notification.ogg
|
||||
/notification.mp3
|
||||
/SwinIR
|
||||
/textual_inversion
|
||||
.vscode
|
||||
@@ -32,3 +51,5 @@ notification.mp3
|
||||
/node_modules
|
||||
/package-lock.json
|
||||
/.coverage*
|
||||
/auth.json
|
||||
.DS_Store
|
||||
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
FROM nvidia/cuda:12.4.1-base-ubuntu22.04
|
||||
ENV DEBIAN_FRONTEND noninteractive
|
||||
ENV CMDARGS --listen
|
||||
|
||||
RUN apt-get update -y && \
|
||||
apt-get install -y curl libgl1 libglib2.0-0 python3-pip python-is-python3 git && \
|
||||
apt-get clean && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY requirements_docker.txt requirements_versions.txt /tmp/
|
||||
RUN pip install --no-cache-dir -r /tmp/requirements_docker.txt -r /tmp/requirements_versions.txt && \
|
||||
rm -f /tmp/requirements_docker.txt /tmp/requirements_versions.txt
|
||||
RUN pip install --no-cache-dir xformers==0.0.23 --no-dependencies
|
||||
RUN curl -fsL -o /usr/local/lib/python3.10/dist-packages/gradio/frpc_linux_amd64_v0.2 https://cdn-media.huggingface.co/frpc-gradio-0.2/frpc_linux_amd64 && \
|
||||
chmod +x /usr/local/lib/python3.10/dist-packages/gradio/frpc_linux_amd64_v0.2
|
||||
|
||||
RUN adduser --disabled-password --gecos '' user && \
|
||||
mkdir -p /content/app /content/data
|
||||
|
||||
COPY entrypoint.sh /content/
|
||||
RUN chown -R user:user /content
|
||||
|
||||
WORKDIR /content
|
||||
USER user
|
||||
|
||||
COPY --chown=user:user . /content/app
|
||||
RUN mv /content/app/models /content/app/models.org
|
||||
|
||||
CMD [ "sh", "-c", "/content/entrypoint.sh ${CMDARGS}" ]
|
||||
@@ -0,0 +1,61 @@
|
||||
import ldm_patched.modules.args_parser as args_parser
|
||||
|
||||
args_parser.parser.add_argument("--share", action='store_true', help="Set whether to share on Gradio.")
|
||||
|
||||
args_parser.parser.add_argument("--preset", type=str, default=None, help="Apply specified UI preset.")
|
||||
args_parser.parser.add_argument("--disable-preset-selection", action='store_true',
|
||||
help="Disables preset selection in Gradio.")
|
||||
|
||||
args_parser.parser.add_argument("--language", type=str, default='default',
|
||||
help="Translate UI using json files in [language] folder. "
|
||||
"For example, [--language example] will use [language/example.json] for translation.")
|
||||
|
||||
# For example, https://github.com/lllyasviel/Fooocus/issues/849
|
||||
args_parser.parser.add_argument("--disable-offload-from-vram", action="store_true",
|
||||
help="Force loading models to vram when the unload can be avoided. "
|
||||
"Some Mac users may need this.")
|
||||
|
||||
args_parser.parser.add_argument("--theme", type=str, help="launches the UI with light or dark theme", default=None)
|
||||
args_parser.parser.add_argument("--disable-image-log", action='store_true',
|
||||
help="Prevent writing images and logs to the outputs folder.")
|
||||
|
||||
args_parser.parser.add_argument("--disable-analytics", action='store_true',
|
||||
help="Disables analytics for Gradio.")
|
||||
|
||||
args_parser.parser.add_argument("--disable-metadata", action='store_true',
|
||||
help="Disables saving metadata to images.")
|
||||
|
||||
args_parser.parser.add_argument("--disable-preset-download", action='store_true',
|
||||
help="Disables downloading models for presets", default=False)
|
||||
|
||||
args_parser.parser.add_argument("--disable-enhance-output-sorting", action='store_true',
|
||||
help="Disables enhance output sorting for final image gallery.")
|
||||
|
||||
args_parser.parser.add_argument("--enable-auto-describe-image", action='store_true',
|
||||
help="Enables automatic description of uov and enhance image when prompt is empty", default=False)
|
||||
|
||||
args_parser.parser.add_argument("--always-download-new-model", action='store_true',
|
||||
help="Always download newer models", default=False)
|
||||
|
||||
args_parser.parser.add_argument("--rebuild-hash-cache", help="Generates missing model and LoRA hashes.",
|
||||
type=int, nargs="?", metavar="CPU_NUM_THREADS", const=-1)
|
||||
|
||||
args_parser.parser.set_defaults(
|
||||
disable_cuda_malloc=True,
|
||||
in_browser=True,
|
||||
port=None
|
||||
)
|
||||
|
||||
args_parser.args = args_parser.parser.parse_args()
|
||||
|
||||
# (Disable by default because of issues like https://github.com/lllyasviel/Fooocus/issues/724)
|
||||
args_parser.args.always_offload_from_vram = not args_parser.args.disable_offload_from_vram
|
||||
|
||||
if args_parser.args.disable_analytics:
|
||||
import os
|
||||
os.environ["GRADIO_ANALYTICS_ENABLED"] = "False"
|
||||
|
||||
if args_parser.args.disable_in_browser:
|
||||
args_parser.args.in_browser = False
|
||||
|
||||
args = args_parser.args
|
||||
@@ -0,0 +1,6 @@
|
||||
[
|
||||
{
|
||||
"user": "sitting-duck-1",
|
||||
"pass": "very-bad-publicly-known-password-change-it"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,26 @@
|
||||
import os
|
||||
|
||||
win32_root = os.path.dirname(os.path.dirname(__file__))
|
||||
python_embeded_path = os.path.join(win32_root, 'python_embeded')
|
||||
|
||||
is_win32_standalone_build = os.path.exists(python_embeded_path) and os.path.isdir(python_embeded_path)
|
||||
|
||||
win32_cmd = '''
|
||||
.\python_embeded\python.exe -s Fooocus\entry_with_update.py {cmds} %*
|
||||
pause
|
||||
'''
|
||||
|
||||
|
||||
def build_launcher():
|
||||
if not is_win32_standalone_build:
|
||||
return
|
||||
|
||||
presets = [None, 'anime', 'realistic']
|
||||
|
||||
for preset in presets:
|
||||
win32_cmd_preset = win32_cmd.replace('{cmds}', '' if preset is None else f'--preset {preset}')
|
||||
bat_path = os.path.join(win32_root, 'run.bat' if preset is None else f'run_{preset}.bat')
|
||||
if not os.path.exists(bat_path):
|
||||
with open(bat_path, "w", encoding="utf-8") as f:
|
||||
f.write(win32_cmd_preset)
|
||||
return
|
||||
-48
@@ -1,48 +0,0 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "VjYy0F2gZIPR"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"%cd /content\n",
|
||||
"!git clone https://github.com/lllyasviel/Fooocus\n",
|
||||
"\n",
|
||||
"!apt -y update -qq\n",
|
||||
"!wget https://github.com/camenduru/gperftools/releases/download/v1.0/libtcmalloc_minimal.so.4 -O /content/libtcmalloc_minimal.so.4\n",
|
||||
"%env LD_PRELOAD=/content/libtcmalloc_minimal.so.4\n",
|
||||
"\n",
|
||||
"!pip install torchsde==0.2.5 einops==0.4.1 transformers==4.30.2 safetensors==0.3.1 accelerate==0.21.0\n",
|
||||
"!pip install pytorch_lightning==1.9.4 omegaconf==2.2.3 gradio==3.39.0 xformers==0.0.20 triton==2.0.0 pygit2==1.12.2\n",
|
||||
"\n",
|
||||
"!apt -y install -qq aria2\n",
|
||||
"!aria2c --console-log-level=error -c -x 16 -s 16 -k 1M https://huggingface.co/ckpt/sd_xl_base_1.0/resolve/main/sd_xl_base_1.0_0.9vae.safetensors -d /content/Fooocus/models/checkpoints -o sd_xl_base_1.0_0.9vae.safetensors\n",
|
||||
"!aria2c --console-log-level=error -c -x 16 -s 16 -k 1M https://huggingface.co/ckpt/sd_xl_refiner_1.0/resolve/main/sd_xl_refiner_1.0_0.9vae.safetensors -d /content/Fooocus/models/checkpoints -o sd_xl_refiner_1.0_0.9vae.safetensors\n",
|
||||
"!aria2c --console-log-level=error -c -x 16 -s 16 -k 1M https://huggingface.co/stabilityai/stable-diffusion-xl-base-1.0/resolve/main/sd_xl_offset_example-lora_1.0.safetensors -d /content/Fooocus/models/loras -o sd_xl_offset_example-lora_1.0.safetensors\n",
|
||||
"\n",
|
||||
"%cd /content/Fooocus\n",
|
||||
"!git pull\n",
|
||||
"!python launch.py --share\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"accelerator": "GPU",
|
||||
"colab": {
|
||||
"gpuType": "T4",
|
||||
"provenance": []
|
||||
},
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"name": "python"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 0
|
||||
}
|
||||
+418
@@ -0,0 +1,418 @@
|
||||
/* based on https://github.com/AUTOMATIC1111/stable-diffusion-webui/blob/v1.6.0/style.css */
|
||||
|
||||
.loader-container {
|
||||
display: flex; /* Use flex to align items horizontally */
|
||||
align-items: center; /* Center items vertically within the container */
|
||||
white-space: nowrap; /* Prevent line breaks within the container */
|
||||
}
|
||||
|
||||
.loader {
|
||||
border: 8px solid #f3f3f3; /* Light grey */
|
||||
border-top: 8px solid #3498db; /* Blue */
|
||||
border-radius: 50%;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
animation: spin 2s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* Style the progress bar */
|
||||
progress {
|
||||
appearance: none; /* Remove default styling */
|
||||
height: 20px; /* Set the height of the progress bar */
|
||||
border-radius: 5px; /* Round the corners of the progress bar */
|
||||
background-color: #f3f3f3; /* Light grey background */
|
||||
width: 100%;
|
||||
vertical-align: middle !important;
|
||||
}
|
||||
|
||||
/* Style the progress bar container */
|
||||
.progress-container {
|
||||
margin-left: 20px;
|
||||
margin-right: 20px;
|
||||
flex-grow: 1; /* Allow the progress container to take up remaining space */
|
||||
}
|
||||
|
||||
/* Set the color of the progress bar fill */
|
||||
progress::-webkit-progress-value {
|
||||
background-color: #3498db; /* Blue color for the fill */
|
||||
}
|
||||
|
||||
progress::-moz-progress-bar {
|
||||
background-color: #3498db; /* Blue color for the fill in Firefox */
|
||||
}
|
||||
|
||||
/* Style the text on the progress bar */
|
||||
progress::after {
|
||||
content: attr(value '%'); /* Display the progress value followed by '%' */
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
color: white; /* Set text color */
|
||||
font-size: 14px; /* Set font size */
|
||||
}
|
||||
|
||||
/* Style other texts */
|
||||
.loader-container > span {
|
||||
margin-left: 5px; /* Add spacing between the progress bar and the text */
|
||||
}
|
||||
|
||||
.progress-bar > .generating {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.progress-bar{
|
||||
height: 30px !important;
|
||||
}
|
||||
|
||||
.progress-bar span {
|
||||
text-align: right;
|
||||
width: 215px;
|
||||
}
|
||||
div:has(> #positive_prompt) {
|
||||
border: none;
|
||||
}
|
||||
|
||||
#positive_prompt {
|
||||
padding: 1px;
|
||||
background: var(--background-fill-primary);
|
||||
}
|
||||
|
||||
.type_row {
|
||||
height: 84px !important;
|
||||
}
|
||||
|
||||
.type_row_half {
|
||||
height: 34px !important;
|
||||
}
|
||||
|
||||
.refresh_button {
|
||||
border: none !important;
|
||||
background: none !important;
|
||||
font-size: none !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
.advanced_check_row {
|
||||
width: 330px !important;
|
||||
}
|
||||
|
||||
.min_check {
|
||||
min-width: min(1px, 100%) !important;
|
||||
}
|
||||
|
||||
.resizable_area {
|
||||
resize: vertical;
|
||||
overflow: auto !important;
|
||||
}
|
||||
|
||||
.performance_selection label {
|
||||
width: 140px !important;
|
||||
}
|
||||
|
||||
.aspect_ratios label {
|
||||
flex: calc(50% - 5px) !important;
|
||||
}
|
||||
|
||||
.aspect_ratios label span {
|
||||
white-space: nowrap !important;
|
||||
}
|
||||
|
||||
.aspect_ratios label input {
|
||||
margin-left: -5px !important;
|
||||
}
|
||||
|
||||
.lora_enable label {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.lora_enable label input {
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.lora_enable label span {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@-moz-document url-prefix() {
|
||||
.lora_weight input[type=number] {
|
||||
width: 80px;
|
||||
}
|
||||
}
|
||||
|
||||
#context-menu{
|
||||
z-index:9999;
|
||||
position:absolute;
|
||||
display:block;
|
||||
padding:0px 0;
|
||||
border:2px solid #a55000;
|
||||
border-radius:8px;
|
||||
box-shadow:1px 1px 2px #CE6400;
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
.context-menu-items{
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.context-menu-items a{
|
||||
display:block;
|
||||
padding:5px;
|
||||
cursor:pointer;
|
||||
}
|
||||
|
||||
.context-menu-items a:hover{
|
||||
background: #a55000;
|
||||
}
|
||||
|
||||
.canvas-tooltip-info {
|
||||
position: absolute;
|
||||
top: 28px;
|
||||
left: 2px;
|
||||
cursor: help;
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.canvas-tooltip-info::after {
|
||||
content: '';
|
||||
display: block;
|
||||
width: 2px;
|
||||
height: 7px;
|
||||
background-color: white;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.canvas-tooltip-info::before {
|
||||
content: '';
|
||||
display: block;
|
||||
width: 2px;
|
||||
height: 2px;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
.canvas-tooltip-content {
|
||||
display: none;
|
||||
background-color: #f9f9f9;
|
||||
color: #333;
|
||||
border: 1px solid #ddd;
|
||||
padding: 15px;
|
||||
position: absolute;
|
||||
top: 40px;
|
||||
left: 10px;
|
||||
width: 250px;
|
||||
font-size: 16px;
|
||||
opacity: 0;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
|
||||
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.canvas-tooltip:hover .canvas-tooltip-content {
|
||||
display: block;
|
||||
animation: fadeIn 0.5s;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {opacity: 0;}
|
||||
to {opacity: 1;}
|
||||
}
|
||||
|
||||
.styler {
|
||||
overflow:inherit !important;
|
||||
}
|
||||
|
||||
.gradio-container{
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
/* fullpage image viewer */
|
||||
|
||||
#lightboxModal{
|
||||
display: none;
|
||||
position: fixed;
|
||||
z-index: 1001;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
background-color: rgba(20, 20, 20, 0.95);
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.modalControls {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
right: 0px;
|
||||
left: 0px;
|
||||
gap: 1em;
|
||||
padding: 1em;
|
||||
background-color:rgba(0,0,0,0);
|
||||
z-index: 1;
|
||||
transition: 0.2s ease background-color;
|
||||
}
|
||||
.modalControls:hover {
|
||||
background-color:rgba(0,0,0,0.9);
|
||||
}
|
||||
.modalClose {
|
||||
margin-left: auto;
|
||||
}
|
||||
.modalControls span{
|
||||
color: white;
|
||||
text-shadow: 0px 0px 0.25em black;
|
||||
font-size: 35px;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
width: 1em;
|
||||
}
|
||||
|
||||
.modalControls span:hover, .modalControls span:focus{
|
||||
color: #999;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
#lightboxModal > img {
|
||||
display: block;
|
||||
margin: auto;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
#lightboxModal > img.modalImageFullscreen{
|
||||
object-fit: contain;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.modalPrev,
|
||||
.modalNext {
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
width: auto;
|
||||
padding: 16px;
|
||||
margin-top: -50px;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
font-size: 20px;
|
||||
transition: 0.6s ease;
|
||||
border-radius: 0 3px 3px 0;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
|
||||
.modalNext {
|
||||
right: 0;
|
||||
border-radius: 3px 0 0 3px;
|
||||
}
|
||||
|
||||
.modalPrev:hover,
|
||||
.modalNext:hover {
|
||||
background-color: rgba(0, 0, 0, 0.8);
|
||||
}
|
||||
|
||||
#imageARPreview {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
border: 2px solid red;
|
||||
background: rgba(255, 0, 0, 0.3);
|
||||
z-index: 900;
|
||||
pointer-events: none;
|
||||
display: none;
|
||||
}
|
||||
|
||||
#stylePreviewOverlay {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
width: 128px;
|
||||
height: 128px;
|
||||
position: fixed;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
border: solid 1px lightgrey;
|
||||
transform: translate(-140px, 20px);
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
border-radius: 5px;
|
||||
z-index: 100;
|
||||
transition: transform 0.1s ease, opacity 0.3s ease;
|
||||
}
|
||||
|
||||
#stylePreviewOverlay.lower-half {
|
||||
transform: translate(-140px, -140px);
|
||||
}
|
||||
|
||||
/* scrollable box for style selections */
|
||||
.contain .tabs {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.contain .tabs .tabitem.style_selections_tab {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.contain .tabs .tabitem.style_selections_tab > div:first-child {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.contain .tabs .tabitem.style_selections_tab .style_selections {
|
||||
min-height: 200px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.contain .tabs .tabitem.style_selections_tab .style_selections .wrap[data-testid="checkbox-group"] {
|
||||
position: absolute; /* remove this to disable scrolling within the checkbox-group */
|
||||
overflow: auto;
|
||||
padding-right: 2px;
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
.contain .tabs .tabitem.style_selections_tab .style_selections .wrap[data-testid="checkbox-group"] label {
|
||||
/* max-width: calc(35% - 15px) !important; */ /* add this to enable 3 columns layout */
|
||||
flex: calc(50% - 5px) !important;
|
||||
}
|
||||
|
||||
.contain .tabs .tabitem.style_selections_tab .style_selections .wrap[data-testid="checkbox-group"] label span {
|
||||
/* white-space:nowrap; */ /* add this to disable text wrapping (better choice for 3 columns layout) */
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
/* styles preview tooltip */
|
||||
.preview-tooltip {
|
||||
background-color: #fff8;
|
||||
font-family: monospace;
|
||||
text-align: center;
|
||||
border-radius: 5px 5px 0px 0px;
|
||||
display: none; /* remove this to enable tooltip in preview image */
|
||||
}
|
||||
|
||||
#inpaint_canvas .canvas-tooltip-info {
|
||||
top: 2px;
|
||||
}
|
||||
|
||||
#inpaint_brush_color input[type=color]{
|
||||
background: none;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
## Running unit tests
|
||||
|
||||
Native python:
|
||||
```
|
||||
python -m unittest tests/
|
||||
```
|
||||
|
||||
Embedded python (Windows zip file installation method):
|
||||
```
|
||||
..\python_embeded\python.exe -m unittest
|
||||
```
|
||||
@@ -0,0 +1,36 @@
|
||||
volumes:
|
||||
fooocus-data:
|
||||
|
||||
services:
|
||||
app:
|
||||
build: .
|
||||
image: ghcr.io/lllyasviel/fooocus
|
||||
ports:
|
||||
- "7865:7865"
|
||||
environment:
|
||||
- CMDARGS=--listen # Arguments for launch.py.
|
||||
- DATADIR=/content/data # Directory which stores models, outputs dir
|
||||
- config_path=/content/data/config.txt
|
||||
- config_example_path=/content/data/config_modification_tutorial.txt
|
||||
- path_checkpoints=/content/data/models/checkpoints/
|
||||
- path_loras=/content/data/models/loras/
|
||||
- path_embeddings=/content/data/models/embeddings/
|
||||
- path_vae_approx=/content/data/models/vae_approx/
|
||||
- path_upscale_models=/content/data/models/upscale_models/
|
||||
- path_inpaint=/content/data/models/inpaint/
|
||||
- path_controlnet=/content/data/models/controlnet/
|
||||
- path_clip_vision=/content/data/models/clip_vision/
|
||||
- path_fooocus_expansion=/content/data/models/prompt_expansion/fooocus_expansion/
|
||||
- path_outputs=/content/app/outputs/ # Warning: If it is not located under '/content/app', you can't see history log!
|
||||
volumes:
|
||||
- fooocus-data:/content/data
|
||||
#- ./models:/import/models # Once you import files, you don't need to mount again.
|
||||
#- ./outputs:/import/outputs # Once you import files, you don't need to mount again.
|
||||
tty: true
|
||||
deploy:
|
||||
resources:
|
||||
reservations:
|
||||
devices:
|
||||
- driver: nvidia
|
||||
device_ids: ['0']
|
||||
capabilities: [compute, utility]
|
||||
@@ -0,0 +1,131 @@
|
||||
# Fooocus on Docker
|
||||
|
||||
The docker image is based on NVIDIA CUDA 12.4 and PyTorch 2.1, see [Dockerfile](Dockerfile) and [requirements_docker.txt](requirements_docker.txt) for details.
|
||||
|
||||
## Requirements
|
||||
|
||||
- A computer with specs good enough to run Fooocus, and proprietary Nvidia drivers
|
||||
- Docker, Docker Compose, or Podman
|
||||
|
||||
## Quick start
|
||||
|
||||
**More information in the [notes](#notes).**
|
||||
|
||||
### Running with Docker Compose
|
||||
|
||||
1. Clone this repository
|
||||
2. Run the docker container with `docker compose up`.
|
||||
|
||||
### Running with Docker
|
||||
|
||||
```sh
|
||||
docker run -p 7865:7865 -v fooocus-data:/content/data -it \
|
||||
--gpus all \
|
||||
-e CMDARGS=--listen \
|
||||
-e DATADIR=/content/data \
|
||||
-e config_path=/content/data/config.txt \
|
||||
-e config_example_path=/content/data/config_modification_tutorial.txt \
|
||||
-e path_checkpoints=/content/data/models/checkpoints/ \
|
||||
-e path_loras=/content/data/models/loras/ \
|
||||
-e path_embeddings=/content/data/models/embeddings/ \
|
||||
-e path_vae_approx=/content/data/models/vae_approx/ \
|
||||
-e path_upscale_models=/content/data/models/upscale_models/ \
|
||||
-e path_inpaint=/content/data/models/inpaint/ \
|
||||
-e path_controlnet=/content/data/models/controlnet/ \
|
||||
-e path_clip_vision=/content/data/models/clip_vision/ \
|
||||
-e path_fooocus_expansion=/content/data/models/prompt_expansion/fooocus_expansion/ \
|
||||
-e path_outputs=/content/app/outputs/ \
|
||||
ghcr.io/lllyasviel/fooocus
|
||||
```
|
||||
### Running with Podman
|
||||
|
||||
```sh
|
||||
podman run -p 7865:7865 -v fooocus-data:/content/data -it \
|
||||
--security-opt=no-new-privileges --cap-drop=ALL --security-opt label=type:nvidia_container_t --device=nvidia.com/gpu=all \
|
||||
-e CMDARGS=--listen \
|
||||
-e DATADIR=/content/data \
|
||||
-e config_path=/content/data/config.txt \
|
||||
-e config_example_path=/content/data/config_modification_tutorial.txt \
|
||||
-e path_checkpoints=/content/data/models/checkpoints/ \
|
||||
-e path_loras=/content/data/models/loras/ \
|
||||
-e path_embeddings=/content/data/models/embeddings/ \
|
||||
-e path_vae_approx=/content/data/models/vae_approx/ \
|
||||
-e path_upscale_models=/content/data/models/upscale_models/ \
|
||||
-e path_inpaint=/content/data/models/inpaint/ \
|
||||
-e path_controlnet=/content/data/models/controlnet/ \
|
||||
-e path_clip_vision=/content/data/models/clip_vision/ \
|
||||
-e path_fooocus_expansion=/content/data/models/prompt_expansion/fooocus_expansion/ \
|
||||
-e path_outputs=/content/app/outputs/ \
|
||||
ghcr.io/lllyasviel/fooocus
|
||||
```
|
||||
|
||||
When you see the message `Use the app with http://0.0.0.0:7865/` in the console, you can access the URL in your browser.
|
||||
|
||||
Your models and outputs are stored in the `fooocus-data` volume, which, depending on OS, is stored in `/var/lib/docker/volumes/` (or `~/.local/share/containers/storage/volumes/` when using `podman`).
|
||||
|
||||
## Building the container locally
|
||||
|
||||
Clone the repository first, and open a terminal in the folder.
|
||||
|
||||
Build with `docker`:
|
||||
```sh
|
||||
docker build . -t fooocus
|
||||
```
|
||||
|
||||
Build with `podman`:
|
||||
```sh
|
||||
podman build . -t fooocus
|
||||
```
|
||||
|
||||
## Details
|
||||
|
||||
### Update the container manually (`docker compose`)
|
||||
|
||||
When you are using `docker compose up` continuously, the container is not updated to the latest version of Fooocus automatically.
|
||||
Run `git pull` before executing `docker compose build --no-cache` to build an image with the latest Fooocus version.
|
||||
You can then start it with `docker compose up`
|
||||
|
||||
### Import models, outputs
|
||||
|
||||
If you want to import files from models or the outputs folder, you can add the following bind mounts in the [docker-compose.yml](docker-compose.yml) or your preferred method of running the container:
|
||||
```
|
||||
#- ./models:/import/models # Once you import files, you don't need to mount again.
|
||||
#- ./outputs:/import/outputs # Once you import files, you don't need to mount again.
|
||||
```
|
||||
After running the container, your files will be copied into `/content/data/models` and `/content/data/outputs`
|
||||
Since `/content/data` is a persistent volume folder, your files will be persisted even when you re-run the container without the above mounts.
|
||||
|
||||
|
||||
### Paths inside the container
|
||||
|
||||
|Path|Details|
|
||||
|-|-|
|
||||
|/content/app|The application stored folder|
|
||||
|/content/app/models.org|Original 'models' folder.<br> Files are copied to the '/content/app/models' which is symlinked to '/content/data/models' every time the container boots. (Existing files will not be overwritten.) |
|
||||
|/content/data|Persistent volume mount point|
|
||||
|/content/data/models|The folder is symlinked to '/content/app/models'|
|
||||
|/content/data/outputs|The folder is symlinked to '/content/app/outputs'|
|
||||
|
||||
### Environments
|
||||
|
||||
You can change `config.txt` parameters by using environment variables.
|
||||
**The priority of using the environments is higher than the values defined in `config.txt`, and they will be saved to the `config_modification_tutorial.txt`**
|
||||
|
||||
Docker specified environments are there. They are used by 'entrypoint.sh'
|
||||
|Environment|Details|
|
||||
|-|-|
|
||||
|DATADIR|'/content/data' location.|
|
||||
|CMDARGS|Arguments for [entry_with_update.py](entry_with_update.py) which is called by [entrypoint.sh](entrypoint.sh)|
|
||||
|config_path|'config.txt' location|
|
||||
|config_example_path|'config_modification_tutorial.txt' location|
|
||||
|HF_MIRROR| huggingface mirror site domain|
|
||||
|
||||
You can also use the same json key names and values explained in the 'config_modification_tutorial.txt' as the environments.
|
||||
See examples in the [docker-compose.yml](docker-compose.yml)
|
||||
|
||||
## Notes
|
||||
|
||||
- Please keep 'path_outputs' under '/content/app'. Otherwise, you may get an error when you open the history log.
|
||||
- Docker on Mac/Windows still has issues in the form of slow volume access when you use "bind mount" volumes. Please refer to [this article](https://docs.docker.com/storage/volumes/#use-a-volume-with-docker-compose) for not using "bind mount".
|
||||
- The MPS backend (Metal Performance Shaders, Apple Silicon M1/M2/etc.) is not yet supported in Docker, see https://github.com/pytorch/pytorch/issues/81224
|
||||
- You can also use `docker compose up -d` to start the container detached and connect to the logs with `docker compose logs -f`. This way you can also close the terminal and keep the container running.
|
||||
@@ -37,7 +37,7 @@ try:
|
||||
repo.reset(local_branch.target, pygit2.GIT_RESET_HARD)
|
||||
print("Fast-forward merge")
|
||||
elif merge_result & pygit2.GIT_MERGE_ANALYSIS_NORMAL:
|
||||
print("Update failed - Did you modified any file?")
|
||||
print("Update failed - Did you modify any file?")
|
||||
except Exception as e:
|
||||
print('Update failed.')
|
||||
print(str(e))
|
||||
|
||||
Executable
+33
@@ -0,0 +1,33 @@
|
||||
#!/bin/bash
|
||||
|
||||
ORIGINALDIR=/content/app
|
||||
# Use predefined DATADIR if it is defined
|
||||
[[ x"${DATADIR}" == "x" ]] && DATADIR=/content/data
|
||||
|
||||
# Make persistent dir from original dir
|
||||
function mklink () {
|
||||
mkdir -p $DATADIR/$1
|
||||
ln -s $DATADIR/$1 $ORIGINALDIR
|
||||
}
|
||||
|
||||
# Copy old files from import dir
|
||||
function import () {
|
||||
(test -d /import/$1 && cd /import/$1 && cp -Rpn . $DATADIR/$1/)
|
||||
}
|
||||
|
||||
cd $ORIGINALDIR
|
||||
|
||||
# models
|
||||
mklink models
|
||||
# Copy original files
|
||||
(cd $ORIGINALDIR/models.org && cp -Rpn . $ORIGINALDIR/models/)
|
||||
# Import old files
|
||||
import models
|
||||
|
||||
# outputs
|
||||
mklink outputs
|
||||
# Import old files
|
||||
import outputs
|
||||
|
||||
# Start application
|
||||
python launch.py $*
|
||||
@@ -0,0 +1,8 @@
|
||||
from modules.expansion import FooocusExpansion
|
||||
|
||||
expansion = FooocusExpansion()
|
||||
|
||||
text = 'a handsome man'
|
||||
|
||||
for i in range(64):
|
||||
print(expansion(text, seed=i))
|
||||
@@ -0,0 +1,7 @@
|
||||
import cv2
|
||||
import extras.face_crop as cropper
|
||||
|
||||
|
||||
img = cv2.imread('lena.png')
|
||||
result = cropper.crop_image(img)
|
||||
cv2.imwrite('lena_result.png', result)
|
||||
@@ -0,0 +1,8 @@
|
||||
import cv2
|
||||
from extras.interrogate import default_interrogator as default_interrogator_photo
|
||||
from extras.wd14tagger import default_interrogator as default_interrogator_anime
|
||||
|
||||
img = cv2.imread('./test_imgs/red_box.jpg')[:, :, ::-1].copy()
|
||||
print(default_interrogator_photo(img))
|
||||
img = cv2.imread('./test_imgs/miku.jpg')[:, :, ::-1].copy()
|
||||
print(default_interrogator_anime(img))
|
||||
@@ -0,0 +1,24 @@
|
||||
# https://github.com/sail-sg/EditAnything/blob/main/sam2groundingdino_edit.py
|
||||
|
||||
import numpy as np
|
||||
from PIL import Image
|
||||
|
||||
from extras.inpaint_mask import SAMOptions, generate_mask_from_image
|
||||
|
||||
original_image = Image.open('cat.webp')
|
||||
image = np.array(original_image, dtype=np.uint8)
|
||||
|
||||
sam_options = SAMOptions(
|
||||
dino_prompt='eye',
|
||||
dino_box_threshold=0.3,
|
||||
dino_text_threshold=0.25,
|
||||
dino_erode_or_dilate=0,
|
||||
dino_debug=False,
|
||||
max_detections=2,
|
||||
model_type='vit_b'
|
||||
)
|
||||
|
||||
mask_image, _, _, _ = generate_mask_from_image(image, sam_options=sam_options)
|
||||
|
||||
merged_masks_img = Image.fromarray(mask_image)
|
||||
merged_masks_img.show()
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"architectures": [
|
||||
"BertModel"
|
||||
],
|
||||
"attention_probs_dropout_prob": 0.1,
|
||||
"hidden_act": "gelu",
|
||||
"hidden_dropout_prob": 0.1,
|
||||
"hidden_size": 768,
|
||||
"initializer_range": 0.02,
|
||||
"intermediate_size": 3072,
|
||||
"layer_norm_eps": 1e-12,
|
||||
"max_position_embeddings": 512,
|
||||
"model_type": "bert",
|
||||
"num_attention_heads": 12,
|
||||
"num_hidden_layers": 12,
|
||||
"pad_token_id": 0,
|
||||
"type_vocab_size": 2,
|
||||
"vocab_size": 30522,
|
||||
"encoder_width": 768,
|
||||
"add_cross_attention": true
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
image_root: '/export/share/datasets/vision/coco/images/'
|
||||
ann_root: 'annotation'
|
||||
coco_gt_root: 'annotation/coco_gt'
|
||||
|
||||
# set pretrained as a file path or an url
|
||||
pretrained: 'https://storage.googleapis.com/sfr-vision-language-research/BLIP/models/model_base_caption_capfilt_large.pth'
|
||||
|
||||
# size of vit model; base or large
|
||||
vit: 'base'
|
||||
vit_grad_ckpt: False
|
||||
vit_ckpt_layer: 0
|
||||
batch_size: 32
|
||||
init_lr: 1e-5
|
||||
|
||||
# vit: 'large'
|
||||
# vit_grad_ckpt: True
|
||||
# vit_ckpt_layer: 5
|
||||
# batch_size: 16
|
||||
# init_lr: 2e-6
|
||||
|
||||
image_size: 384
|
||||
|
||||
# generation configs
|
||||
max_length: 20
|
||||
min_length: 5
|
||||
num_beams: 3
|
||||
prompt: 'a picture of '
|
||||
|
||||
# optimizer
|
||||
weight_decay: 0.05
|
||||
min_lr: 0
|
||||
max_epoch: 5
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"architectures": [
|
||||
"BertModel"
|
||||
],
|
||||
"attention_probs_dropout_prob": 0.1,
|
||||
"hidden_act": "gelu",
|
||||
"hidden_dropout_prob": 0.1,
|
||||
"hidden_size": 768,
|
||||
"initializer_range": 0.02,
|
||||
"intermediate_size": 3072,
|
||||
"layer_norm_eps": 1e-12,
|
||||
"max_position_embeddings": 512,
|
||||
"model_type": "bert",
|
||||
"num_attention_heads": 12,
|
||||
"num_hidden_layers": 12,
|
||||
"pad_token_id": 0,
|
||||
"type_vocab_size": 2,
|
||||
"vocab_size": 30524,
|
||||
"encoder_width": 768,
|
||||
"add_cross_attention": true
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
image_root: '/export/share/datasets/vision/NLVR2/'
|
||||
ann_root: 'annotation'
|
||||
|
||||
# set pretrained as a file path or an url
|
||||
pretrained: 'https://storage.googleapis.com/sfr-vision-language-research/BLIP/models/model_base_nlvr.pth'
|
||||
|
||||
#size of vit model; base or large
|
||||
vit: 'base'
|
||||
batch_size_train: 16
|
||||
batch_size_test: 64
|
||||
vit_grad_ckpt: False
|
||||
vit_ckpt_layer: 0
|
||||
max_epoch: 15
|
||||
|
||||
image_size: 384
|
||||
|
||||
# optimizer
|
||||
weight_decay: 0.05
|
||||
init_lr: 3e-5
|
||||
min_lr: 0
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
image_root: '/export/share/datasets/vision/nocaps/'
|
||||
ann_root: 'annotation'
|
||||
|
||||
# set pretrained as a file path or an url
|
||||
pretrained: 'https://storage.googleapis.com/sfr-vision-language-research/BLIP/models/model_base_caption_capfilt_large.pth'
|
||||
|
||||
vit: 'base'
|
||||
batch_size: 32
|
||||
|
||||
image_size: 384
|
||||
|
||||
max_length: 20
|
||||
min_length: 5
|
||||
num_beams: 3
|
||||
prompt: 'a picture of '
|
||||
@@ -0,0 +1,27 @@
|
||||
train_file: ['/export/share/junnan-li/VL_pretrain/annotation/coco_karpathy_train.json',
|
||||
'/export/share/junnan-li/VL_pretrain/annotation/vg_caption.json',
|
||||
]
|
||||
laion_path: ''
|
||||
|
||||
# size of vit model; base or large
|
||||
vit: 'base'
|
||||
vit_grad_ckpt: False
|
||||
vit_ckpt_layer: 0
|
||||
|
||||
image_size: 224
|
||||
batch_size: 75
|
||||
|
||||
queue_size: 57600
|
||||
alpha: 0.4
|
||||
|
||||
# optimizer
|
||||
weight_decay: 0.05
|
||||
init_lr: 3e-4
|
||||
min_lr: 1e-6
|
||||
warmup_lr: 1e-6
|
||||
lr_decay_rate: 0.9
|
||||
max_epoch: 20
|
||||
warmup_steps: 3000
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
image_root: '/export/share/datasets/vision/coco/images/'
|
||||
ann_root: 'annotation'
|
||||
dataset: 'coco'
|
||||
|
||||
# set pretrained as a file path or an url
|
||||
pretrained: 'https://storage.googleapis.com/sfr-vision-language-research/BLIP/models/model_base_retrieval_coco.pth'
|
||||
|
||||
# size of vit model; base or large
|
||||
|
||||
vit: 'base'
|
||||
batch_size_train: 32
|
||||
batch_size_test: 64
|
||||
vit_grad_ckpt: True
|
||||
vit_ckpt_layer: 4
|
||||
init_lr: 1e-5
|
||||
|
||||
# vit: 'large'
|
||||
# batch_size_train: 16
|
||||
# batch_size_test: 32
|
||||
# vit_grad_ckpt: True
|
||||
# vit_ckpt_layer: 12
|
||||
# init_lr: 5e-6
|
||||
|
||||
image_size: 384
|
||||
queue_size: 57600
|
||||
alpha: 0.4
|
||||
k_test: 256
|
||||
negative_all_rank: True
|
||||
|
||||
# optimizer
|
||||
weight_decay: 0.05
|
||||
min_lr: 0
|
||||
max_epoch: 6
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
image_root: '/export/share/datasets/vision/flickr30k/'
|
||||
ann_root: 'annotation'
|
||||
dataset: 'flickr'
|
||||
|
||||
# set pretrained as a file path or an url
|
||||
pretrained: 'https://storage.googleapis.com/sfr-vision-language-research/BLIP/models/model_base_retrieval_flickr.pth'
|
||||
|
||||
# size of vit model; base or large
|
||||
|
||||
vit: 'base'
|
||||
batch_size_train: 32
|
||||
batch_size_test: 64
|
||||
vit_grad_ckpt: True
|
||||
vit_ckpt_layer: 4
|
||||
init_lr: 1e-5
|
||||
|
||||
# vit: 'large'
|
||||
# batch_size_train: 16
|
||||
# batch_size_test: 32
|
||||
# vit_grad_ckpt: True
|
||||
# vit_ckpt_layer: 10
|
||||
# init_lr: 5e-6
|
||||
|
||||
image_size: 384
|
||||
queue_size: 57600
|
||||
alpha: 0.4
|
||||
k_test: 128
|
||||
negative_all_rank: False
|
||||
|
||||
# optimizer
|
||||
weight_decay: 0.05
|
||||
min_lr: 0
|
||||
max_epoch: 6
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
video_root: '/export/share/dongxuli/data/msrvtt_retrieval/videos'
|
||||
ann_root: 'annotation'
|
||||
|
||||
# set pretrained as a file path or an url
|
||||
pretrained: 'https://storage.googleapis.com/sfr-vision-language-research/BLIP/models/model_base_retrieval_coco.pth'
|
||||
|
||||
# size of vit model; base or large
|
||||
vit: 'base'
|
||||
batch_size: 64
|
||||
k_test: 128
|
||||
image_size: 384
|
||||
num_frm_test: 8
|
||||
@@ -0,0 +1,25 @@
|
||||
vqa_root: '/export/share/datasets/vision/VQA/Images/mscoco/' #followed by train2014/
|
||||
vg_root: '/export/share/datasets/vision/visual-genome/' #followed by image/
|
||||
train_files: ['vqa_train','vqa_val','vg_qa']
|
||||
ann_root: 'annotation'
|
||||
|
||||
# set pretrained as a file path or an url
|
||||
pretrained: 'https://storage.googleapis.com/sfr-vision-language-research/BLIP/models/model_base_vqa_capfilt_large.pth'
|
||||
|
||||
# size of vit model; base or large
|
||||
vit: 'base'
|
||||
batch_size_train: 16
|
||||
batch_size_test: 32
|
||||
vit_grad_ckpt: False
|
||||
vit_ckpt_layer: 0
|
||||
init_lr: 2e-5
|
||||
|
||||
image_size: 480
|
||||
|
||||
k_test: 128
|
||||
inference: 'rank'
|
||||
|
||||
# optimizer
|
||||
weight_decay: 0.05
|
||||
min_lr: 0
|
||||
max_epoch: 10
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"architectures": [
|
||||
"BertForMaskedLM"
|
||||
],
|
||||
"attention_probs_dropout_prob": 0.1,
|
||||
"gradient_checkpointing": false,
|
||||
"hidden_act": "gelu",
|
||||
"hidden_dropout_prob": 0.1,
|
||||
"hidden_size": 768,
|
||||
"initializer_range": 0.02,
|
||||
"intermediate_size": 3072,
|
||||
"layer_norm_eps": 1e-12,
|
||||
"max_position_embeddings": 512,
|
||||
"model_type": "bert",
|
||||
"num_attention_heads": 12,
|
||||
"num_hidden_layers": 12,
|
||||
"pad_token_id": 0,
|
||||
"position_embedding_type": "absolute",
|
||||
"transformers_version": "4.6.0.dev0",
|
||||
"type_vocab_size": 2,
|
||||
"use_cache": true,
|
||||
"vocab_size": 30522
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"do_lower_case": true
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,239 @@
|
||||
'''
|
||||
* Copyright (c) 2022, salesforce.com, inc.
|
||||
* All rights reserved.
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
|
||||
* By Junnan Li
|
||||
'''
|
||||
import warnings
|
||||
warnings.filterwarnings("ignore")
|
||||
|
||||
from extras.BLIP.models.vit import VisionTransformer, interpolate_pos_embed
|
||||
from extras.BLIP.models.med import BertConfig, BertModel, BertLMHeadModel
|
||||
from transformers import BertTokenizer
|
||||
|
||||
import torch
|
||||
from torch import nn
|
||||
import torch.nn.functional as F
|
||||
|
||||
import os
|
||||
from urllib.parse import urlparse
|
||||
from timm.models.hub import download_cached_file
|
||||
|
||||
class BLIP_Base(nn.Module):
|
||||
def __init__(self,
|
||||
med_config = 'configs/med_config.json',
|
||||
image_size = 224,
|
||||
vit = 'base',
|
||||
vit_grad_ckpt = False,
|
||||
vit_ckpt_layer = 0,
|
||||
):
|
||||
"""
|
||||
Args:
|
||||
med_config (str): path for the mixture of encoder-decoder model's configuration file
|
||||
image_size (int): input image size
|
||||
vit (str): model size of vision transformer
|
||||
"""
|
||||
super().__init__()
|
||||
|
||||
self.visual_encoder, vision_width = create_vit(vit,image_size, vit_grad_ckpt, vit_ckpt_layer)
|
||||
self.tokenizer = init_tokenizer()
|
||||
med_config = BertConfig.from_json_file(med_config)
|
||||
med_config.encoder_width = vision_width
|
||||
self.text_encoder = BertModel(config=med_config, add_pooling_layer=False)
|
||||
|
||||
|
||||
def forward(self, image, caption, mode):
|
||||
|
||||
assert mode in ['image', 'text', 'multimodal'], "mode parameter must be image, text, or multimodal"
|
||||
text = self.tokenizer(caption, return_tensors="pt").to(image.device)
|
||||
|
||||
if mode=='image':
|
||||
# return image features
|
||||
image_embeds = self.visual_encoder(image)
|
||||
return image_embeds
|
||||
|
||||
elif mode=='text':
|
||||
# return text features
|
||||
text_output = self.text_encoder(text.input_ids, attention_mask = text.attention_mask,
|
||||
return_dict = True, mode = 'text')
|
||||
return text_output.last_hidden_state
|
||||
|
||||
elif mode=='multimodal':
|
||||
# return multimodel features
|
||||
image_embeds = self.visual_encoder(image)
|
||||
image_atts = torch.ones(image_embeds.size()[:-1],dtype=torch.long).to(image.device)
|
||||
|
||||
text.input_ids[:,0] = self.tokenizer.enc_token_id
|
||||
output = self.text_encoder(text.input_ids,
|
||||
attention_mask = text.attention_mask,
|
||||
encoder_hidden_states = image_embeds,
|
||||
encoder_attention_mask = image_atts,
|
||||
return_dict = True,
|
||||
)
|
||||
return output.last_hidden_state
|
||||
|
||||
|
||||
|
||||
class BLIP_Decoder(nn.Module):
|
||||
def __init__(self,
|
||||
med_config = 'configs/med_config.json',
|
||||
image_size = 384,
|
||||
vit = 'base',
|
||||
vit_grad_ckpt = False,
|
||||
vit_ckpt_layer = 0,
|
||||
prompt = 'a picture of ',
|
||||
):
|
||||
"""
|
||||
Args:
|
||||
med_config (str): path for the mixture of encoder-decoder model's configuration file
|
||||
image_size (int): input image size
|
||||
vit (str): model size of vision transformer
|
||||
"""
|
||||
super().__init__()
|
||||
|
||||
self.visual_encoder, vision_width = create_vit(vit,image_size, vit_grad_ckpt, vit_ckpt_layer)
|
||||
self.tokenizer = init_tokenizer()
|
||||
med_config = BertConfig.from_json_file(med_config)
|
||||
med_config.encoder_width = vision_width
|
||||
self.text_decoder = BertLMHeadModel(config=med_config)
|
||||
|
||||
self.prompt = prompt
|
||||
self.prompt_length = len(self.tokenizer(self.prompt).input_ids)-1
|
||||
|
||||
|
||||
def forward(self, image, caption):
|
||||
|
||||
image_embeds = self.visual_encoder(image)
|
||||
image_atts = torch.ones(image_embeds.size()[:-1],dtype=torch.long).to(image.device)
|
||||
|
||||
text = self.tokenizer(caption, padding='longest', truncation=True, max_length=40, return_tensors="pt").to(image.device)
|
||||
|
||||
text.input_ids[:,0] = self.tokenizer.bos_token_id
|
||||
|
||||
decoder_targets = text.input_ids.masked_fill(text.input_ids == self.tokenizer.pad_token_id, -100)
|
||||
decoder_targets[:,:self.prompt_length] = -100
|
||||
|
||||
decoder_output = self.text_decoder(text.input_ids,
|
||||
attention_mask = text.attention_mask,
|
||||
encoder_hidden_states = image_embeds,
|
||||
encoder_attention_mask = image_atts,
|
||||
labels = decoder_targets,
|
||||
return_dict = True,
|
||||
)
|
||||
loss_lm = decoder_output.loss
|
||||
|
||||
return loss_lm
|
||||
|
||||
def generate(self, image, sample=False, num_beams=3, max_length=30, min_length=10, top_p=0.9, repetition_penalty=1.0):
|
||||
image_embeds = self.visual_encoder(image)
|
||||
|
||||
if not sample:
|
||||
image_embeds = image_embeds.repeat_interleave(num_beams,dim=0)
|
||||
|
||||
image_atts = torch.ones(image_embeds.size()[:-1],dtype=torch.long).to(image.device)
|
||||
model_kwargs = {"encoder_hidden_states": image_embeds, "encoder_attention_mask":image_atts}
|
||||
|
||||
prompt = [self.prompt] * image.size(0)
|
||||
input_ids = self.tokenizer(prompt, return_tensors="pt").input_ids.to(image.device)
|
||||
input_ids[:,0] = self.tokenizer.bos_token_id
|
||||
input_ids = input_ids[:, :-1]
|
||||
|
||||
if sample:
|
||||
#nucleus sampling
|
||||
outputs = self.text_decoder.generate(input_ids=input_ids,
|
||||
max_length=max_length,
|
||||
min_length=min_length,
|
||||
do_sample=True,
|
||||
top_p=top_p,
|
||||
num_return_sequences=1,
|
||||
eos_token_id=self.tokenizer.sep_token_id,
|
||||
pad_token_id=self.tokenizer.pad_token_id,
|
||||
repetition_penalty=1.1,
|
||||
**model_kwargs)
|
||||
else:
|
||||
#beam search
|
||||
outputs = self.text_decoder.generate(input_ids=input_ids,
|
||||
max_length=max_length,
|
||||
min_length=min_length,
|
||||
num_beams=num_beams,
|
||||
eos_token_id=self.tokenizer.sep_token_id,
|
||||
pad_token_id=self.tokenizer.pad_token_id,
|
||||
repetition_penalty=repetition_penalty,
|
||||
**model_kwargs)
|
||||
|
||||
captions = []
|
||||
for output in outputs:
|
||||
caption = self.tokenizer.decode(output, skip_special_tokens=True)
|
||||
captions.append(caption[len(self.prompt):])
|
||||
return captions
|
||||
|
||||
|
||||
def blip_decoder(pretrained='',**kwargs):
|
||||
model = BLIP_Decoder(**kwargs)
|
||||
if pretrained:
|
||||
model,msg = load_checkpoint(model,pretrained)
|
||||
assert(len(msg.missing_keys)==0)
|
||||
return model
|
||||
|
||||
def blip_feature_extractor(pretrained='',**kwargs):
|
||||
model = BLIP_Base(**kwargs)
|
||||
if pretrained:
|
||||
model,msg = load_checkpoint(model,pretrained)
|
||||
assert(len(msg.missing_keys)==0)
|
||||
return model
|
||||
|
||||
def init_tokenizer():
|
||||
tokenizer_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "bert_tokenizer")
|
||||
tokenizer = BertTokenizer.from_pretrained(tokenizer_path)
|
||||
tokenizer.add_special_tokens({'bos_token':'[DEC]'})
|
||||
tokenizer.add_special_tokens({'additional_special_tokens':['[ENC]']})
|
||||
tokenizer.enc_token_id = tokenizer.additional_special_tokens_ids[0]
|
||||
return tokenizer
|
||||
|
||||
|
||||
def create_vit(vit, image_size, use_grad_checkpointing=False, ckpt_layer=0, drop_path_rate=0):
|
||||
|
||||
assert vit in ['base', 'large'], "vit parameter must be base or large"
|
||||
if vit=='base':
|
||||
vision_width = 768
|
||||
visual_encoder = VisionTransformer(img_size=image_size, patch_size=16, embed_dim=vision_width, depth=12,
|
||||
num_heads=12, use_grad_checkpointing=use_grad_checkpointing, ckpt_layer=ckpt_layer,
|
||||
drop_path_rate=0 or drop_path_rate
|
||||
)
|
||||
elif vit=='large':
|
||||
vision_width = 1024
|
||||
visual_encoder = VisionTransformer(img_size=image_size, patch_size=16, embed_dim=vision_width, depth=24,
|
||||
num_heads=16, use_grad_checkpointing=use_grad_checkpointing, ckpt_layer=ckpt_layer,
|
||||
drop_path_rate=0.1 or drop_path_rate
|
||||
)
|
||||
return visual_encoder, vision_width
|
||||
|
||||
def is_url(url_or_filename):
|
||||
parsed = urlparse(url_or_filename)
|
||||
return parsed.scheme in ("http", "https")
|
||||
|
||||
def load_checkpoint(model,url_or_filename):
|
||||
if is_url(url_or_filename):
|
||||
cached_file = download_cached_file(url_or_filename, check_hash=False, progress=True)
|
||||
checkpoint = torch.load(cached_file, map_location='cpu', weights_only=True)
|
||||
elif os.path.isfile(url_or_filename):
|
||||
checkpoint = torch.load(url_or_filename, map_location='cpu', weights_only=True)
|
||||
else:
|
||||
raise RuntimeError('checkpoint url or path is invalid')
|
||||
|
||||
state_dict = checkpoint['model']
|
||||
|
||||
state_dict['visual_encoder.pos_embed'] = interpolate_pos_embed(state_dict['visual_encoder.pos_embed'],model.visual_encoder)
|
||||
if 'visual_encoder_m.pos_embed' in model.state_dict().keys():
|
||||
state_dict['visual_encoder_m.pos_embed'] = interpolate_pos_embed(state_dict['visual_encoder_m.pos_embed'],
|
||||
model.visual_encoder_m)
|
||||
for key in model.state_dict().keys():
|
||||
if key in state_dict.keys():
|
||||
if state_dict[key].shape!=model.state_dict()[key].shape:
|
||||
del state_dict[key]
|
||||
|
||||
msg = model.load_state_dict(state_dict,strict=False)
|
||||
print('load checkpoint from %s'%url_or_filename)
|
||||
return model,msg
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
from extras.BLIP.models.med import BertConfig, BertModel
|
||||
from transformers import BertTokenizer
|
||||
|
||||
import torch
|
||||
from torch import nn
|
||||
import torch.nn.functional as F
|
||||
|
||||
from extras.BLIP.models.blip import create_vit, init_tokenizer, load_checkpoint
|
||||
|
||||
class BLIP_ITM(nn.Module):
|
||||
def __init__(self,
|
||||
med_config = 'configs/med_config.json',
|
||||
image_size = 384,
|
||||
vit = 'base',
|
||||
vit_grad_ckpt = False,
|
||||
vit_ckpt_layer = 0,
|
||||
embed_dim = 256,
|
||||
):
|
||||
"""
|
||||
Args:
|
||||
med_config (str): path for the mixture of encoder-decoder model's configuration file
|
||||
image_size (int): input image size
|
||||
vit (str): model size of vision transformer
|
||||
"""
|
||||
super().__init__()
|
||||
|
||||
self.visual_encoder, vision_width = create_vit(vit,image_size, vit_grad_ckpt, vit_ckpt_layer)
|
||||
self.tokenizer = init_tokenizer()
|
||||
med_config = BertConfig.from_json_file(med_config)
|
||||
med_config.encoder_width = vision_width
|
||||
self.text_encoder = BertModel(config=med_config, add_pooling_layer=False)
|
||||
|
||||
text_width = self.text_encoder.config.hidden_size
|
||||
|
||||
self.vision_proj = nn.Linear(vision_width, embed_dim)
|
||||
self.text_proj = nn.Linear(text_width, embed_dim)
|
||||
|
||||
self.itm_head = nn.Linear(text_width, 2)
|
||||
|
||||
|
||||
def forward(self, image, caption, match_head='itm'):
|
||||
|
||||
image_embeds = self.visual_encoder(image)
|
||||
image_atts = torch.ones(image_embeds.size()[:-1],dtype=torch.long).to(image.device)
|
||||
|
||||
text = self.tokenizer(caption, padding='max_length', truncation=True, max_length=35,
|
||||
return_tensors="pt").to(image.device)
|
||||
|
||||
|
||||
if match_head=='itm':
|
||||
output = self.text_encoder(text.input_ids,
|
||||
attention_mask = text.attention_mask,
|
||||
encoder_hidden_states = image_embeds,
|
||||
encoder_attention_mask = image_atts,
|
||||
return_dict = True,
|
||||
)
|
||||
itm_output = self.itm_head(output.last_hidden_state[:,0,:])
|
||||
return itm_output
|
||||
|
||||
elif match_head=='itc':
|
||||
text_output = self.text_encoder(text.input_ids, attention_mask = text.attention_mask,
|
||||
return_dict = True, mode = 'text')
|
||||
image_feat = F.normalize(self.vision_proj(image_embeds[:,0,:]),dim=-1)
|
||||
text_feat = F.normalize(self.text_proj(text_output.last_hidden_state[:,0,:]),dim=-1)
|
||||
|
||||
sim = image_feat @ text_feat.t()
|
||||
return sim
|
||||
|
||||
|
||||
def blip_itm(pretrained='',**kwargs):
|
||||
model = BLIP_ITM(**kwargs)
|
||||
if pretrained:
|
||||
model,msg = load_checkpoint(model,pretrained)
|
||||
assert(len(msg.missing_keys)==0)
|
||||
return model
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
from extras.BLIP.models.med import BertConfig
|
||||
from extras.BLIP.models.nlvr_encoder import BertModel
|
||||
from extras.BLIP.models.vit import interpolate_pos_embed
|
||||
from extras.BLIP.models.blip import create_vit, init_tokenizer, is_url
|
||||
|
||||
from timm.models.hub import download_cached_file
|
||||
|
||||
import torch
|
||||
from torch import nn
|
||||
import torch.nn.functional as F
|
||||
from transformers import BertTokenizer
|
||||
import numpy as np
|
||||
import os
|
||||
|
||||
|
||||
class BLIP_NLVR(nn.Module):
|
||||
def __init__(self,
|
||||
med_config = 'configs/med_config.json',
|
||||
image_size = 480,
|
||||
vit = 'base',
|
||||
vit_grad_ckpt = False,
|
||||
vit_ckpt_layer = 0,
|
||||
):
|
||||
"""
|
||||
Args:
|
||||
med_config (str): path for the mixture of encoder-decoder model's configuration file
|
||||
image_size (int): input image size
|
||||
vit (str): model size of vision transformer
|
||||
"""
|
||||
super().__init__()
|
||||
|
||||
self.visual_encoder, vision_width = create_vit(vit,image_size, vit_grad_ckpt, vit_ckpt_layer, drop_path_rate=0.1)
|
||||
self.tokenizer = init_tokenizer()
|
||||
med_config = BertConfig.from_json_file(med_config)
|
||||
med_config.encoder_width = vision_width
|
||||
self.text_encoder = BertModel(config=med_config, add_pooling_layer=False)
|
||||
|
||||
self.cls_head = nn.Sequential(
|
||||
nn.Linear(self.text_encoder.config.hidden_size, self.text_encoder.config.hidden_size),
|
||||
nn.ReLU(),
|
||||
nn.Linear(self.text_encoder.config.hidden_size, 2)
|
||||
)
|
||||
|
||||
def forward(self, image, text, targets, train=True):
|
||||
|
||||
image_embeds = self.visual_encoder(image)
|
||||
image_atts = torch.ones(image_embeds.size()[:-1],dtype=torch.long).to(image.device)
|
||||
image0_embeds, image1_embeds = torch.split(image_embeds,targets.size(0))
|
||||
|
||||
text = self.tokenizer(text, padding='longest', return_tensors="pt").to(image.device)
|
||||
text.input_ids[:,0] = self.tokenizer.enc_token_id
|
||||
|
||||
output = self.text_encoder(text.input_ids,
|
||||
attention_mask = text.attention_mask,
|
||||
encoder_hidden_states = [image0_embeds,image1_embeds],
|
||||
encoder_attention_mask = [image_atts[:image0_embeds.size(0)],
|
||||
image_atts[image0_embeds.size(0):]],
|
||||
return_dict = True,
|
||||
)
|
||||
hidden_state = output.last_hidden_state[:,0,:]
|
||||
prediction = self.cls_head(hidden_state)
|
||||
|
||||
if train:
|
||||
loss = F.cross_entropy(prediction, targets)
|
||||
return loss
|
||||
else:
|
||||
return prediction
|
||||
|
||||
def blip_nlvr(pretrained='',**kwargs):
|
||||
model = BLIP_NLVR(**kwargs)
|
||||
if pretrained:
|
||||
model,msg = load_checkpoint(model,pretrained)
|
||||
print("missing keys:")
|
||||
print(msg.missing_keys)
|
||||
return model
|
||||
|
||||
|
||||
def load_checkpoint(model,url_or_filename):
|
||||
if is_url(url_or_filename):
|
||||
cached_file = download_cached_file(url_or_filename, check_hash=False, progress=True)
|
||||
checkpoint = torch.load(cached_file, map_location='cpu', weights_only=True)
|
||||
elif os.path.isfile(url_or_filename):
|
||||
checkpoint = torch.load(url_or_filename, map_location='cpu', weights_only=True)
|
||||
else:
|
||||
raise RuntimeError('checkpoint url or path is invalid')
|
||||
state_dict = checkpoint['model']
|
||||
|
||||
state_dict['visual_encoder.pos_embed'] = interpolate_pos_embed(state_dict['visual_encoder.pos_embed'],model.visual_encoder)
|
||||
|
||||
for key in list(state_dict.keys()):
|
||||
if 'crossattention.self.' in key:
|
||||
new_key0 = key.replace('self','self0')
|
||||
new_key1 = key.replace('self','self1')
|
||||
state_dict[new_key0] = state_dict[key]
|
||||
state_dict[new_key1] = state_dict[key]
|
||||
elif 'crossattention.output.dense.' in key:
|
||||
new_key0 = key.replace('dense','dense0')
|
||||
new_key1 = key.replace('dense','dense1')
|
||||
state_dict[new_key0] = state_dict[key]
|
||||
state_dict[new_key1] = state_dict[key]
|
||||
|
||||
msg = model.load_state_dict(state_dict,strict=False)
|
||||
print('load checkpoint from %s'%url_or_filename)
|
||||
return model,msg
|
||||
|
||||
@@ -0,0 +1,339 @@
|
||||
'''
|
||||
* Copyright (c) 2022, salesforce.com, inc.
|
||||
* All rights reserved.
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
|
||||
* By Junnan Li
|
||||
'''
|
||||
from extras.BLIP.models.med import BertConfig, BertModel, BertLMHeadModel
|
||||
from transformers import BertTokenizer
|
||||
import transformers
|
||||
transformers.logging.set_verbosity_error()
|
||||
|
||||
import torch
|
||||
from torch import nn
|
||||
import torch.nn.functional as F
|
||||
|
||||
from extras.BLIP.models.blip import create_vit, init_tokenizer, load_checkpoint
|
||||
|
||||
class BLIP_Pretrain(nn.Module):
|
||||
def __init__(self,
|
||||
med_config = 'configs/bert_config.json',
|
||||
image_size = 224,
|
||||
vit = 'base',
|
||||
vit_grad_ckpt = False,
|
||||
vit_ckpt_layer = 0,
|
||||
embed_dim = 256,
|
||||
queue_size = 57600,
|
||||
momentum = 0.995,
|
||||
):
|
||||
"""
|
||||
Args:
|
||||
med_config (str): path for the mixture of encoder-decoder model's configuration file
|
||||
image_size (int): input image size
|
||||
vit (str): model size of vision transformer
|
||||
"""
|
||||
super().__init__()
|
||||
|
||||
self.visual_encoder, vision_width = create_vit(vit,image_size, vit_grad_ckpt, vit_ckpt_layer, 0)
|
||||
|
||||
if vit=='base':
|
||||
checkpoint = torch.hub.load_state_dict_from_url(
|
||||
url="https://dl.fbaipublicfiles.com/deit/deit_base_patch16_224-b5f2ef4d.pth",
|
||||
map_location="cpu", check_hash=True)
|
||||
state_dict = checkpoint["model"]
|
||||
msg = self.visual_encoder.load_state_dict(state_dict,strict=False)
|
||||
elif vit=='large':
|
||||
from timm.models.helpers import load_custom_pretrained
|
||||
from timm.models.vision_transformer import default_cfgs
|
||||
load_custom_pretrained(self.visual_encoder,default_cfgs['vit_large_patch16_224_in21k'])
|
||||
|
||||
self.tokenizer = init_tokenizer()
|
||||
encoder_config = BertConfig.from_json_file(med_config)
|
||||
encoder_config.encoder_width = vision_width
|
||||
self.text_encoder = BertModel.from_pretrained('bert-base-uncased',config=encoder_config, add_pooling_layer=False)
|
||||
self.text_encoder.resize_token_embeddings(len(self.tokenizer))
|
||||
|
||||
text_width = self.text_encoder.config.hidden_size
|
||||
|
||||
self.vision_proj = nn.Linear(vision_width, embed_dim)
|
||||
self.text_proj = nn.Linear(text_width, embed_dim)
|
||||
|
||||
self.itm_head = nn.Linear(text_width, 2)
|
||||
|
||||
# create momentum encoders
|
||||
self.visual_encoder_m, vision_width = create_vit(vit,image_size)
|
||||
self.vision_proj_m = nn.Linear(vision_width, embed_dim)
|
||||
self.text_encoder_m = BertModel(config=encoder_config, add_pooling_layer=False)
|
||||
self.text_proj_m = nn.Linear(text_width, embed_dim)
|
||||
|
||||
self.model_pairs = [[self.visual_encoder,self.visual_encoder_m],
|
||||
[self.vision_proj,self.vision_proj_m],
|
||||
[self.text_encoder,self.text_encoder_m],
|
||||
[self.text_proj,self.text_proj_m],
|
||||
]
|
||||
self.copy_params()
|
||||
|
||||
# create the queue
|
||||
self.register_buffer("image_queue", torch.randn(embed_dim, queue_size))
|
||||
self.register_buffer("text_queue", torch.randn(embed_dim, queue_size))
|
||||
self.register_buffer("queue_ptr", torch.zeros(1, dtype=torch.long))
|
||||
|
||||
self.image_queue = nn.functional.normalize(self.image_queue, dim=0)
|
||||
self.text_queue = nn.functional.normalize(self.text_queue, dim=0)
|
||||
|
||||
self.queue_size = queue_size
|
||||
self.momentum = momentum
|
||||
self.temp = nn.Parameter(0.07*torch.ones([]))
|
||||
|
||||
# create the decoder
|
||||
decoder_config = BertConfig.from_json_file(med_config)
|
||||
decoder_config.encoder_width = vision_width
|
||||
self.text_decoder = BertLMHeadModel.from_pretrained('bert-base-uncased',config=decoder_config)
|
||||
self.text_decoder.resize_token_embeddings(len(self.tokenizer))
|
||||
tie_encoder_decoder_weights(self.text_encoder,self.text_decoder.bert,'','/attention')
|
||||
|
||||
|
||||
def forward(self, image, caption, alpha):
|
||||
with torch.no_grad():
|
||||
self.temp.clamp_(0.001,0.5)
|
||||
|
||||
image_embeds = self.visual_encoder(image)
|
||||
image_atts = torch.ones(image_embeds.size()[:-1],dtype=torch.long).to(image.device)
|
||||
image_feat = F.normalize(self.vision_proj(image_embeds[:,0,:]),dim=-1)
|
||||
|
||||
text = self.tokenizer(caption, padding='max_length', truncation=True, max_length=30,
|
||||
return_tensors="pt").to(image.device)
|
||||
text_output = self.text_encoder(text.input_ids, attention_mask = text.attention_mask,
|
||||
return_dict = True, mode = 'text')
|
||||
text_feat = F.normalize(self.text_proj(text_output.last_hidden_state[:,0,:]),dim=-1)
|
||||
|
||||
# get momentum features
|
||||
with torch.no_grad():
|
||||
self._momentum_update()
|
||||
image_embeds_m = self.visual_encoder_m(image)
|
||||
image_feat_m = F.normalize(self.vision_proj_m(image_embeds_m[:,0,:]),dim=-1)
|
||||
image_feat_all = torch.cat([image_feat_m.t(),self.image_queue.clone().detach()],dim=1)
|
||||
|
||||
text_output_m = self.text_encoder_m(text.input_ids, attention_mask = text.attention_mask,
|
||||
return_dict = True, mode = 'text')
|
||||
text_feat_m = F.normalize(self.text_proj_m(text_output_m.last_hidden_state[:,0,:]),dim=-1)
|
||||
text_feat_all = torch.cat([text_feat_m.t(),self.text_queue.clone().detach()],dim=1)
|
||||
|
||||
sim_i2t_m = image_feat_m @ text_feat_all / self.temp
|
||||
sim_t2i_m = text_feat_m @ image_feat_all / self.temp
|
||||
|
||||
sim_targets = torch.zeros(sim_i2t_m.size()).to(image.device)
|
||||
sim_targets.fill_diagonal_(1)
|
||||
|
||||
sim_i2t_targets = alpha * F.softmax(sim_i2t_m, dim=1) + (1 - alpha) * sim_targets
|
||||
sim_t2i_targets = alpha * F.softmax(sim_t2i_m, dim=1) + (1 - alpha) * sim_targets
|
||||
|
||||
sim_i2t = image_feat @ text_feat_all / self.temp
|
||||
sim_t2i = text_feat @ image_feat_all / self.temp
|
||||
|
||||
loss_i2t = -torch.sum(F.log_softmax(sim_i2t, dim=1)*sim_i2t_targets,dim=1).mean()
|
||||
loss_t2i = -torch.sum(F.log_softmax(sim_t2i, dim=1)*sim_t2i_targets,dim=1).mean()
|
||||
|
||||
loss_ita = (loss_i2t+loss_t2i)/2
|
||||
|
||||
self._dequeue_and_enqueue(image_feat_m, text_feat_m)
|
||||
|
||||
###============== Image-text Matching ===================###
|
||||
encoder_input_ids = text.input_ids.clone()
|
||||
encoder_input_ids[:,0] = self.tokenizer.enc_token_id
|
||||
|
||||
# forward the positve image-text pair
|
||||
bs = image.size(0)
|
||||
output_pos = self.text_encoder(encoder_input_ids,
|
||||
attention_mask = text.attention_mask,
|
||||
encoder_hidden_states = image_embeds,
|
||||
encoder_attention_mask = image_atts,
|
||||
return_dict = True,
|
||||
)
|
||||
with torch.no_grad():
|
||||
weights_t2i = F.softmax(sim_t2i[:,:bs],dim=1)+1e-4
|
||||
weights_t2i.fill_diagonal_(0)
|
||||
weights_i2t = F.softmax(sim_i2t[:,:bs],dim=1)+1e-4
|
||||
weights_i2t.fill_diagonal_(0)
|
||||
|
||||
# select a negative image for each text
|
||||
image_embeds_neg = []
|
||||
for b in range(bs):
|
||||
neg_idx = torch.multinomial(weights_t2i[b], 1).item()
|
||||
image_embeds_neg.append(image_embeds[neg_idx])
|
||||
image_embeds_neg = torch.stack(image_embeds_neg,dim=0)
|
||||
|
||||
# select a negative text for each image
|
||||
text_ids_neg = []
|
||||
text_atts_neg = []
|
||||
for b in range(bs):
|
||||
neg_idx = torch.multinomial(weights_i2t[b], 1).item()
|
||||
text_ids_neg.append(encoder_input_ids[neg_idx])
|
||||
text_atts_neg.append(text.attention_mask[neg_idx])
|
||||
|
||||
text_ids_neg = torch.stack(text_ids_neg,dim=0)
|
||||
text_atts_neg = torch.stack(text_atts_neg,dim=0)
|
||||
|
||||
text_ids_all = torch.cat([encoder_input_ids, text_ids_neg],dim=0)
|
||||
text_atts_all = torch.cat([text.attention_mask, text_atts_neg],dim=0)
|
||||
|
||||
image_embeds_all = torch.cat([image_embeds_neg,image_embeds],dim=0)
|
||||
image_atts_all = torch.cat([image_atts,image_atts],dim=0)
|
||||
|
||||
output_neg = self.text_encoder(text_ids_all,
|
||||
attention_mask = text_atts_all,
|
||||
encoder_hidden_states = image_embeds_all,
|
||||
encoder_attention_mask = image_atts_all,
|
||||
return_dict = True,
|
||||
)
|
||||
|
||||
vl_embeddings = torch.cat([output_pos.last_hidden_state[:,0,:], output_neg.last_hidden_state[:,0,:]],dim=0)
|
||||
vl_output = self.itm_head(vl_embeddings)
|
||||
|
||||
itm_labels = torch.cat([torch.ones(bs,dtype=torch.long),torch.zeros(2*bs,dtype=torch.long)],
|
||||
dim=0).to(image.device)
|
||||
loss_itm = F.cross_entropy(vl_output, itm_labels)
|
||||
|
||||
##================= LM ========================##
|
||||
decoder_input_ids = text.input_ids.clone()
|
||||
decoder_input_ids[:,0] = self.tokenizer.bos_token_id
|
||||
decoder_targets = decoder_input_ids.masked_fill(decoder_input_ids == self.tokenizer.pad_token_id, -100)
|
||||
|
||||
decoder_output = self.text_decoder(decoder_input_ids,
|
||||
attention_mask = text.attention_mask,
|
||||
encoder_hidden_states = image_embeds,
|
||||
encoder_attention_mask = image_atts,
|
||||
labels = decoder_targets,
|
||||
return_dict = True,
|
||||
)
|
||||
|
||||
loss_lm = decoder_output.loss
|
||||
return loss_ita, loss_itm, loss_lm
|
||||
|
||||
|
||||
|
||||
@torch.no_grad()
|
||||
def copy_params(self):
|
||||
for model_pair in self.model_pairs:
|
||||
for param, param_m in zip(model_pair[0].parameters(), model_pair[1].parameters()):
|
||||
param_m.data.copy_(param.data) # initialize
|
||||
param_m.requires_grad = False # not update by gradient
|
||||
|
||||
|
||||
@torch.no_grad()
|
||||
def _momentum_update(self):
|
||||
for model_pair in self.model_pairs:
|
||||
for param, param_m in zip(model_pair[0].parameters(), model_pair[1].parameters()):
|
||||
param_m.data = param_m.data * self.momentum + param.data * (1. - self.momentum)
|
||||
|
||||
|
||||
@torch.no_grad()
|
||||
def _dequeue_and_enqueue(self, image_feat, text_feat):
|
||||
# gather keys before updating queue
|
||||
image_feats = concat_all_gather(image_feat)
|
||||
text_feats = concat_all_gather(text_feat)
|
||||
|
||||
batch_size = image_feats.shape[0]
|
||||
|
||||
ptr = int(self.queue_ptr)
|
||||
assert self.queue_size % batch_size == 0 # for simplicity
|
||||
|
||||
# replace the keys at ptr (dequeue and enqueue)
|
||||
self.image_queue[:, ptr:ptr + batch_size] = image_feats.T
|
||||
self.text_queue[:, ptr:ptr + batch_size] = text_feats.T
|
||||
ptr = (ptr + batch_size) % self.queue_size # move pointer
|
||||
|
||||
self.queue_ptr[0] = ptr
|
||||
|
||||
|
||||
def blip_pretrain(**kwargs):
|
||||
model = BLIP_Pretrain(**kwargs)
|
||||
return model
|
||||
|
||||
|
||||
@torch.no_grad()
|
||||
def concat_all_gather(tensor):
|
||||
"""
|
||||
Performs all_gather operation on the provided tensors.
|
||||
*** Warning ***: torch.distributed.all_gather has no gradient.
|
||||
"""
|
||||
tensors_gather = [torch.ones_like(tensor)
|
||||
for _ in range(torch.distributed.get_world_size())]
|
||||
torch.distributed.all_gather(tensors_gather, tensor, async_op=False)
|
||||
|
||||
output = torch.cat(tensors_gather, dim=0)
|
||||
return output
|
||||
|
||||
|
||||
from typing import List
|
||||
def tie_encoder_decoder_weights(encoder: nn.Module, decoder: nn.Module, base_model_prefix: str, skip_key:str):
|
||||
uninitialized_encoder_weights: List[str] = []
|
||||
if decoder.__class__ != encoder.__class__:
|
||||
print(
|
||||
f"{decoder.__class__} and {encoder.__class__} are not equal. In this case make sure that all encoder weights are correctly initialized."
|
||||
)
|
||||
|
||||
def tie_encoder_to_decoder_recursively(
|
||||
decoder_pointer: nn.Module,
|
||||
encoder_pointer: nn.Module,
|
||||
module_name: str,
|
||||
uninitialized_encoder_weights: List[str],
|
||||
skip_key: str,
|
||||
depth=0,
|
||||
):
|
||||
assert isinstance(decoder_pointer, nn.Module) and isinstance(
|
||||
encoder_pointer, nn.Module
|
||||
), f"{decoder_pointer} and {encoder_pointer} have to be of type torch.nn.Module"
|
||||
if hasattr(decoder_pointer, "weight") and skip_key not in module_name:
|
||||
assert hasattr(encoder_pointer, "weight")
|
||||
encoder_pointer.weight = decoder_pointer.weight
|
||||
if hasattr(decoder_pointer, "bias"):
|
||||
assert hasattr(encoder_pointer, "bias")
|
||||
encoder_pointer.bias = decoder_pointer.bias
|
||||
print(module_name+' is tied')
|
||||
return
|
||||
|
||||
encoder_modules = encoder_pointer._modules
|
||||
decoder_modules = decoder_pointer._modules
|
||||
if len(decoder_modules) > 0:
|
||||
assert (
|
||||
len(encoder_modules) > 0
|
||||
), f"Encoder module {encoder_pointer} does not match decoder module {decoder_pointer}"
|
||||
|
||||
all_encoder_weights = set([module_name + "/" + sub_name for sub_name in encoder_modules.keys()])
|
||||
encoder_layer_pos = 0
|
||||
for name, module in decoder_modules.items():
|
||||
if name.isdigit():
|
||||
encoder_name = str(int(name) + encoder_layer_pos)
|
||||
decoder_name = name
|
||||
if not isinstance(decoder_modules[decoder_name], type(encoder_modules[encoder_name])) and len(
|
||||
encoder_modules
|
||||
) != len(decoder_modules):
|
||||
# this can happen if the name corresponds to the position in a list module list of layers
|
||||
# in this case the decoder has added a cross-attention that the encoder does not have
|
||||
# thus skip this step and subtract one layer pos from encoder
|
||||
encoder_layer_pos -= 1
|
||||
continue
|
||||
elif name not in encoder_modules:
|
||||
continue
|
||||
elif depth > 500:
|
||||
raise ValueError(
|
||||
"Max depth of recursive function `tie_encoder_to_decoder` reached. It seems that there is a circular dependency between two or more `nn.Modules` of your model."
|
||||
)
|
||||
else:
|
||||
decoder_name = encoder_name = name
|
||||
tie_encoder_to_decoder_recursively(
|
||||
decoder_modules[decoder_name],
|
||||
encoder_modules[encoder_name],
|
||||
module_name + "/" + name,
|
||||
uninitialized_encoder_weights,
|
||||
skip_key,
|
||||
depth=depth + 1,
|
||||
)
|
||||
all_encoder_weights.remove(module_name + "/" + encoder_name)
|
||||
|
||||
uninitialized_encoder_weights += list(all_encoder_weights)
|
||||
|
||||
# tie weights recursively
|
||||
tie_encoder_to_decoder_recursively(decoder, encoder, base_model_prefix, uninitialized_encoder_weights, skip_key)
|
||||
@@ -0,0 +1,319 @@
|
||||
from extras.BLIP.models.med import BertConfig, BertModel
|
||||
from transformers import BertTokenizer
|
||||
|
||||
import torch
|
||||
from torch import nn
|
||||
import torch.nn.functional as F
|
||||
|
||||
from extras.BLIP.models.blip import create_vit, init_tokenizer, load_checkpoint
|
||||
|
||||
class BLIP_Retrieval(nn.Module):
|
||||
def __init__(self,
|
||||
med_config = 'configs/med_config.json',
|
||||
image_size = 384,
|
||||
vit = 'base',
|
||||
vit_grad_ckpt = False,
|
||||
vit_ckpt_layer = 0,
|
||||
embed_dim = 256,
|
||||
queue_size = 57600,
|
||||
momentum = 0.995,
|
||||
negative_all_rank = False,
|
||||
):
|
||||
"""
|
||||
Args:
|
||||
med_config (str): path for the mixture of encoder-decoder model's configuration file
|
||||
image_size (int): input image size
|
||||
vit (str): model size of vision transformer
|
||||
"""
|
||||
super().__init__()
|
||||
|
||||
self.visual_encoder, vision_width = create_vit(vit,image_size, vit_grad_ckpt, vit_ckpt_layer)
|
||||
self.tokenizer = init_tokenizer()
|
||||
med_config = BertConfig.from_json_file(med_config)
|
||||
med_config.encoder_width = vision_width
|
||||
self.text_encoder = BertModel(config=med_config, add_pooling_layer=False)
|
||||
|
||||
text_width = self.text_encoder.config.hidden_size
|
||||
|
||||
self.vision_proj = nn.Linear(vision_width, embed_dim)
|
||||
self.text_proj = nn.Linear(text_width, embed_dim)
|
||||
|
||||
self.itm_head = nn.Linear(text_width, 2)
|
||||
|
||||
# create momentum encoders
|
||||
self.visual_encoder_m, vision_width = create_vit(vit,image_size)
|
||||
self.vision_proj_m = nn.Linear(vision_width, embed_dim)
|
||||
self.text_encoder_m = BertModel(config=med_config, add_pooling_layer=False)
|
||||
self.text_proj_m = nn.Linear(text_width, embed_dim)
|
||||
|
||||
self.model_pairs = [[self.visual_encoder,self.visual_encoder_m],
|
||||
[self.vision_proj,self.vision_proj_m],
|
||||
[self.text_encoder,self.text_encoder_m],
|
||||
[self.text_proj,self.text_proj_m],
|
||||
]
|
||||
self.copy_params()
|
||||
|
||||
# create the queue
|
||||
self.register_buffer("image_queue", torch.randn(embed_dim, queue_size))
|
||||
self.register_buffer("text_queue", torch.randn(embed_dim, queue_size))
|
||||
self.register_buffer("idx_queue", torch.full((1,queue_size),-100))
|
||||
self.register_buffer("ptr_queue", torch.zeros(1, dtype=torch.long))
|
||||
|
||||
self.image_queue = nn.functional.normalize(self.image_queue, dim=0)
|
||||
self.text_queue = nn.functional.normalize(self.text_queue, dim=0)
|
||||
|
||||
self.queue_size = queue_size
|
||||
self.momentum = momentum
|
||||
self.temp = nn.Parameter(0.07*torch.ones([]))
|
||||
|
||||
self.negative_all_rank = negative_all_rank
|
||||
|
||||
|
||||
def forward(self, image, caption, alpha, idx):
|
||||
with torch.no_grad():
|
||||
self.temp.clamp_(0.001,0.5)
|
||||
|
||||
image_embeds = self.visual_encoder(image)
|
||||
image_atts = torch.ones(image_embeds.size()[:-1],dtype=torch.long).to(image.device)
|
||||
image_feat = F.normalize(self.vision_proj(image_embeds[:,0,:]),dim=-1)
|
||||
|
||||
text = self.tokenizer(caption, padding='max_length', truncation=True, max_length=35,
|
||||
return_tensors="pt").to(image.device)
|
||||
|
||||
text_output = self.text_encoder(text.input_ids, attention_mask = text.attention_mask,
|
||||
return_dict = True, mode = 'text')
|
||||
text_feat = F.normalize(self.text_proj(text_output.last_hidden_state[:,0,:]),dim=-1)
|
||||
|
||||
###============== Image-text Contrastive Learning ===================###
|
||||
idx = idx.view(-1,1)
|
||||
idx_all = torch.cat([idx.t(), self.idx_queue.clone().detach()],dim=1)
|
||||
pos_idx = torch.eq(idx, idx_all).float()
|
||||
sim_targets = pos_idx / pos_idx.sum(1,keepdim=True)
|
||||
|
||||
# get momentum features
|
||||
with torch.no_grad():
|
||||
self._momentum_update()
|
||||
image_embeds_m = self.visual_encoder_m(image)
|
||||
image_feat_m = F.normalize(self.vision_proj_m(image_embeds_m[:,0,:]),dim=-1)
|
||||
image_feat_m_all = torch.cat([image_feat_m.t(),self.image_queue.clone().detach()],dim=1)
|
||||
|
||||
text_output_m = self.text_encoder_m(text.input_ids, attention_mask = text.attention_mask,
|
||||
return_dict = True, mode = 'text')
|
||||
text_feat_m = F.normalize(self.text_proj_m(text_output_m.last_hidden_state[:,0,:]),dim=-1)
|
||||
text_feat_m_all = torch.cat([text_feat_m.t(),self.text_queue.clone().detach()],dim=1)
|
||||
|
||||
sim_i2t_m = image_feat_m @ text_feat_m_all / self.temp
|
||||
sim_t2i_m = text_feat_m @ image_feat_m_all / self.temp
|
||||
|
||||
sim_i2t_targets = alpha * F.softmax(sim_i2t_m, dim=1) + (1 - alpha) * sim_targets
|
||||
sim_t2i_targets = alpha * F.softmax(sim_t2i_m, dim=1) + (1 - alpha) * sim_targets
|
||||
|
||||
sim_i2t = image_feat @ text_feat_m_all / self.temp
|
||||
sim_t2i = text_feat @ image_feat_m_all / self.temp
|
||||
|
||||
loss_i2t = -torch.sum(F.log_softmax(sim_i2t, dim=1)*sim_i2t_targets,dim=1).mean()
|
||||
loss_t2i = -torch.sum(F.log_softmax(sim_t2i, dim=1)*sim_t2i_targets,dim=1).mean()
|
||||
|
||||
loss_ita = (loss_i2t+loss_t2i)/2
|
||||
|
||||
idxs = concat_all_gather(idx)
|
||||
self._dequeue_and_enqueue(image_feat_m, text_feat_m, idxs)
|
||||
|
||||
###============== Image-text Matching ===================###
|
||||
encoder_input_ids = text.input_ids.clone()
|
||||
encoder_input_ids[:,0] = self.tokenizer.enc_token_id
|
||||
|
||||
# forward the positve image-text pair
|
||||
bs = image.size(0)
|
||||
output_pos = self.text_encoder(encoder_input_ids,
|
||||
attention_mask = text.attention_mask,
|
||||
encoder_hidden_states = image_embeds,
|
||||
encoder_attention_mask = image_atts,
|
||||
return_dict = True,
|
||||
)
|
||||
|
||||
|
||||
if self.negative_all_rank:
|
||||
# compute sample similarity
|
||||
with torch.no_grad():
|
||||
mask = torch.eq(idx, idxs.t())
|
||||
|
||||
image_feat_world = concat_all_gather(image_feat)
|
||||
text_feat_world = concat_all_gather(text_feat)
|
||||
|
||||
sim_i2t = image_feat @ text_feat_world.t() / self.temp
|
||||
sim_t2i = text_feat @ image_feat_world.t() / self.temp
|
||||
|
||||
weights_i2t = F.softmax(sim_i2t,dim=1)
|
||||
weights_i2t.masked_fill_(mask, 0)
|
||||
|
||||
weights_t2i = F.softmax(sim_t2i,dim=1)
|
||||
weights_t2i.masked_fill_(mask, 0)
|
||||
|
||||
image_embeds_world = all_gather_with_grad(image_embeds)
|
||||
|
||||
# select a negative image (from all ranks) for each text
|
||||
image_embeds_neg = []
|
||||
for b in range(bs):
|
||||
neg_idx = torch.multinomial(weights_t2i[b], 1).item()
|
||||
image_embeds_neg.append(image_embeds_world[neg_idx])
|
||||
image_embeds_neg = torch.stack(image_embeds_neg,dim=0)
|
||||
|
||||
# select a negative text (from all ranks) for each image
|
||||
input_ids_world = concat_all_gather(encoder_input_ids)
|
||||
att_mask_world = concat_all_gather(text.attention_mask)
|
||||
|
||||
text_ids_neg = []
|
||||
text_atts_neg = []
|
||||
for b in range(bs):
|
||||
neg_idx = torch.multinomial(weights_i2t[b], 1).item()
|
||||
text_ids_neg.append(input_ids_world[neg_idx])
|
||||
text_atts_neg.append(att_mask_world[neg_idx])
|
||||
|
||||
else:
|
||||
with torch.no_grad():
|
||||
mask = torch.eq(idx, idx.t())
|
||||
|
||||
sim_i2t = image_feat @ text_feat.t() / self.temp
|
||||
sim_t2i = text_feat @ image_feat.t() / self.temp
|
||||
|
||||
weights_i2t = F.softmax(sim_i2t,dim=1)
|
||||
weights_i2t.masked_fill_(mask, 0)
|
||||
|
||||
weights_t2i = F.softmax(sim_t2i,dim=1)
|
||||
weights_t2i.masked_fill_(mask, 0)
|
||||
|
||||
# select a negative image (from same rank) for each text
|
||||
image_embeds_neg = []
|
||||
for b in range(bs):
|
||||
neg_idx = torch.multinomial(weights_t2i[b], 1).item()
|
||||
image_embeds_neg.append(image_embeds[neg_idx])
|
||||
image_embeds_neg = torch.stack(image_embeds_neg,dim=0)
|
||||
|
||||
# select a negative text (from same rank) for each image
|
||||
text_ids_neg = []
|
||||
text_atts_neg = []
|
||||
for b in range(bs):
|
||||
neg_idx = torch.multinomial(weights_i2t[b], 1).item()
|
||||
text_ids_neg.append(encoder_input_ids[neg_idx])
|
||||
text_atts_neg.append(text.attention_mask[neg_idx])
|
||||
|
||||
text_ids_neg = torch.stack(text_ids_neg,dim=0)
|
||||
text_atts_neg = torch.stack(text_atts_neg,dim=0)
|
||||
|
||||
text_ids_all = torch.cat([encoder_input_ids, text_ids_neg],dim=0)
|
||||
text_atts_all = torch.cat([text.attention_mask, text_atts_neg],dim=0)
|
||||
|
||||
image_embeds_all = torch.cat([image_embeds_neg,image_embeds],dim=0)
|
||||
image_atts_all = torch.cat([image_atts,image_atts],dim=0)
|
||||
|
||||
output_neg = self.text_encoder(text_ids_all,
|
||||
attention_mask = text_atts_all,
|
||||
encoder_hidden_states = image_embeds_all,
|
||||
encoder_attention_mask = image_atts_all,
|
||||
return_dict = True,
|
||||
)
|
||||
|
||||
|
||||
vl_embeddings = torch.cat([output_pos.last_hidden_state[:,0,:], output_neg.last_hidden_state[:,0,:]],dim=0)
|
||||
vl_output = self.itm_head(vl_embeddings)
|
||||
|
||||
itm_labels = torch.cat([torch.ones(bs,dtype=torch.long),torch.zeros(2*bs,dtype=torch.long)],
|
||||
dim=0).to(image.device)
|
||||
loss_itm = F.cross_entropy(vl_output, itm_labels)
|
||||
|
||||
return loss_ita, loss_itm
|
||||
|
||||
|
||||
@torch.no_grad()
|
||||
def copy_params(self):
|
||||
for model_pair in self.model_pairs:
|
||||
for param, param_m in zip(model_pair[0].parameters(), model_pair[1].parameters()):
|
||||
param_m.data.copy_(param.data) # initialize
|
||||
param_m.requires_grad = False # not update by gradient
|
||||
|
||||
|
||||
@torch.no_grad()
|
||||
def _momentum_update(self):
|
||||
for model_pair in self.model_pairs:
|
||||
for param, param_m in zip(model_pair[0].parameters(), model_pair[1].parameters()):
|
||||
param_m.data = param_m.data * self.momentum + param.data * (1. - self.momentum)
|
||||
|
||||
|
||||
@torch.no_grad()
|
||||
def _dequeue_and_enqueue(self, image_feat, text_feat, idxs):
|
||||
# gather keys before updating queue
|
||||
image_feats = concat_all_gather(image_feat)
|
||||
text_feats = concat_all_gather(text_feat)
|
||||
|
||||
|
||||
batch_size = image_feats.shape[0]
|
||||
|
||||
ptr = int(self.ptr_queue)
|
||||
assert self.queue_size % batch_size == 0 # for simplicity
|
||||
|
||||
# replace the keys at ptr (dequeue and enqueue)
|
||||
self.image_queue[:, ptr:ptr + batch_size] = image_feats.T
|
||||
self.text_queue[:, ptr:ptr + batch_size] = text_feats.T
|
||||
self.idx_queue[:, ptr:ptr + batch_size] = idxs.T
|
||||
ptr = (ptr + batch_size) % self.queue_size # move pointer
|
||||
|
||||
self.ptr_queue[0] = ptr
|
||||
|
||||
|
||||
def blip_retrieval(pretrained='',**kwargs):
|
||||
model = BLIP_Retrieval(**kwargs)
|
||||
if pretrained:
|
||||
model,msg = load_checkpoint(model,pretrained)
|
||||
print("missing keys:")
|
||||
print(msg.missing_keys)
|
||||
return model
|
||||
|
||||
|
||||
@torch.no_grad()
|
||||
def concat_all_gather(tensor):
|
||||
"""
|
||||
Performs all_gather operation on the provided tensors.
|
||||
*** Warning ***: torch.distributed.all_gather has no gradient.
|
||||
"""
|
||||
tensors_gather = [torch.ones_like(tensor)
|
||||
for _ in range(torch.distributed.get_world_size())]
|
||||
torch.distributed.all_gather(tensors_gather, tensor, async_op=False)
|
||||
|
||||
output = torch.cat(tensors_gather, dim=0)
|
||||
return output
|
||||
|
||||
|
||||
class GatherLayer(torch.autograd.Function):
|
||||
"""
|
||||
Gather tensors from all workers with support for backward propagation:
|
||||
This implementation does not cut the gradients as torch.distributed.all_gather does.
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def forward(ctx, x):
|
||||
output = [torch.zeros_like(x) for _ in range(torch.distributed.get_world_size())]
|
||||
torch.distributed.all_gather(output, x)
|
||||
return tuple(output)
|
||||
|
||||
@staticmethod
|
||||
def backward(ctx, *grads):
|
||||
all_gradients = torch.stack(grads)
|
||||
torch.distributed.all_reduce(all_gradients)
|
||||
return all_gradients[torch.distributed.get_rank()]
|
||||
|
||||
|
||||
def all_gather_with_grad(tensors):
|
||||
"""
|
||||
Performs all_gather operation on the provided tensors.
|
||||
Graph remains connected for backward grad computation.
|
||||
"""
|
||||
# Queue the gathered tensors
|
||||
world_size = torch.distributed.get_world_size()
|
||||
# There is no need for reduction in the single-proc case
|
||||
if world_size == 1:
|
||||
return tensors
|
||||
|
||||
tensor_all = GatherLayer.apply(tensors)
|
||||
|
||||
return torch.cat(tensor_all, dim=0)
|
||||
@@ -0,0 +1,186 @@
|
||||
from extras.BLIP.models.med import BertConfig, BertModel, BertLMHeadModel
|
||||
from extras.BLIP.models.blip import create_vit, init_tokenizer, load_checkpoint
|
||||
|
||||
import torch
|
||||
from torch import nn
|
||||
import torch.nn.functional as F
|
||||
from transformers import BertTokenizer
|
||||
import numpy as np
|
||||
|
||||
class BLIP_VQA(nn.Module):
|
||||
def __init__(self,
|
||||
med_config = 'configs/med_config.json',
|
||||
image_size = 480,
|
||||
vit = 'base',
|
||||
vit_grad_ckpt = False,
|
||||
vit_ckpt_layer = 0,
|
||||
):
|
||||
"""
|
||||
Args:
|
||||
med_config (str): path for the mixture of encoder-decoder model's configuration file
|
||||
image_size (int): input image size
|
||||
vit (str): model size of vision transformer
|
||||
"""
|
||||
super().__init__()
|
||||
|
||||
self.visual_encoder, vision_width = create_vit(vit, image_size, vit_grad_ckpt, vit_ckpt_layer, drop_path_rate=0.1)
|
||||
self.tokenizer = init_tokenizer()
|
||||
|
||||
encoder_config = BertConfig.from_json_file(med_config)
|
||||
encoder_config.encoder_width = vision_width
|
||||
self.text_encoder = BertModel(config=encoder_config, add_pooling_layer=False)
|
||||
|
||||
decoder_config = BertConfig.from_json_file(med_config)
|
||||
self.text_decoder = BertLMHeadModel(config=decoder_config)
|
||||
|
||||
|
||||
def forward(self, image, question, answer=None, n=None, weights=None, train=True, inference='rank', k_test=128):
|
||||
|
||||
image_embeds = self.visual_encoder(image)
|
||||
image_atts = torch.ones(image_embeds.size()[:-1],dtype=torch.long).to(image.device)
|
||||
|
||||
question = self.tokenizer(question, padding='longest', truncation=True, max_length=35,
|
||||
return_tensors="pt").to(image.device)
|
||||
question.input_ids[:,0] = self.tokenizer.enc_token_id
|
||||
|
||||
if train:
|
||||
'''
|
||||
n: number of answers for each question
|
||||
weights: weight for each answer
|
||||
'''
|
||||
answer = self.tokenizer(answer, padding='longest', return_tensors="pt").to(image.device)
|
||||
answer.input_ids[:,0] = self.tokenizer.bos_token_id
|
||||
answer_targets = answer.input_ids.masked_fill(answer.input_ids == self.tokenizer.pad_token_id, -100)
|
||||
|
||||
question_output = self.text_encoder(question.input_ids,
|
||||
attention_mask = question.attention_mask,
|
||||
encoder_hidden_states = image_embeds,
|
||||
encoder_attention_mask = image_atts,
|
||||
return_dict = True)
|
||||
|
||||
question_states = []
|
||||
question_atts = []
|
||||
for b, n in enumerate(n):
|
||||
question_states += [question_output.last_hidden_state[b]]*n
|
||||
question_atts += [question.attention_mask[b]]*n
|
||||
question_states = torch.stack(question_states,0)
|
||||
question_atts = torch.stack(question_atts,0)
|
||||
|
||||
answer_output = self.text_decoder(answer.input_ids,
|
||||
attention_mask = answer.attention_mask,
|
||||
encoder_hidden_states = question_states,
|
||||
encoder_attention_mask = question_atts,
|
||||
labels = answer_targets,
|
||||
return_dict = True,
|
||||
reduction = 'none',
|
||||
)
|
||||
|
||||
loss = weights * answer_output.loss
|
||||
loss = loss.sum()/image.size(0)
|
||||
|
||||
return loss
|
||||
|
||||
|
||||
else:
|
||||
question_output = self.text_encoder(question.input_ids,
|
||||
attention_mask = question.attention_mask,
|
||||
encoder_hidden_states = image_embeds,
|
||||
encoder_attention_mask = image_atts,
|
||||
return_dict = True)
|
||||
|
||||
if inference=='generate':
|
||||
num_beams = 3
|
||||
question_states = question_output.last_hidden_state.repeat_interleave(num_beams,dim=0)
|
||||
question_atts = torch.ones(question_states.size()[:-1],dtype=torch.long).to(question_states.device)
|
||||
model_kwargs = {"encoder_hidden_states": question_states, "encoder_attention_mask":question_atts}
|
||||
|
||||
bos_ids = torch.full((image.size(0),1),fill_value=self.tokenizer.bos_token_id,device=image.device)
|
||||
|
||||
outputs = self.text_decoder.generate(input_ids=bos_ids,
|
||||
max_length=10,
|
||||
min_length=1,
|
||||
num_beams=num_beams,
|
||||
eos_token_id=self.tokenizer.sep_token_id,
|
||||
pad_token_id=self.tokenizer.pad_token_id,
|
||||
**model_kwargs)
|
||||
|
||||
answers = []
|
||||
for output in outputs:
|
||||
answer = self.tokenizer.decode(output, skip_special_tokens=True)
|
||||
answers.append(answer)
|
||||
return answers
|
||||
|
||||
elif inference=='rank':
|
||||
max_ids = self.rank_answer(question_output.last_hidden_state, question.attention_mask,
|
||||
answer.input_ids, answer.attention_mask, k_test)
|
||||
return max_ids
|
||||
|
||||
|
||||
|
||||
def rank_answer(self, question_states, question_atts, answer_ids, answer_atts, k):
|
||||
|
||||
num_ques = question_states.size(0)
|
||||
start_ids = answer_ids[0,0].repeat(num_ques,1) # bos token
|
||||
|
||||
start_output = self.text_decoder(start_ids,
|
||||
encoder_hidden_states = question_states,
|
||||
encoder_attention_mask = question_atts,
|
||||
return_dict = True,
|
||||
reduction = 'none')
|
||||
logits = start_output.logits[:,0,:] # first token's logit
|
||||
|
||||
# topk_probs: top-k probability
|
||||
# topk_ids: [num_question, k]
|
||||
answer_first_token = answer_ids[:,1]
|
||||
prob_first_token = F.softmax(logits,dim=1).index_select(dim=1, index=answer_first_token)
|
||||
topk_probs, topk_ids = prob_first_token.topk(k,dim=1)
|
||||
|
||||
# answer input: [num_question*k, answer_len]
|
||||
input_ids = []
|
||||
input_atts = []
|
||||
for b, topk_id in enumerate(topk_ids):
|
||||
input_ids.append(answer_ids.index_select(dim=0, index=topk_id))
|
||||
input_atts.append(answer_atts.index_select(dim=0, index=topk_id))
|
||||
input_ids = torch.cat(input_ids,dim=0)
|
||||
input_atts = torch.cat(input_atts,dim=0)
|
||||
|
||||
targets_ids = input_ids.masked_fill(input_ids == self.tokenizer.pad_token_id, -100)
|
||||
|
||||
# repeat encoder's output for top-k answers
|
||||
question_states = tile(question_states, 0, k)
|
||||
question_atts = tile(question_atts, 0, k)
|
||||
|
||||
output = self.text_decoder(input_ids,
|
||||
attention_mask = input_atts,
|
||||
encoder_hidden_states = question_states,
|
||||
encoder_attention_mask = question_atts,
|
||||
labels = targets_ids,
|
||||
return_dict = True,
|
||||
reduction = 'none')
|
||||
|
||||
log_probs_sum = -output.loss
|
||||
log_probs_sum = log_probs_sum.view(num_ques,k)
|
||||
|
||||
max_topk_ids = log_probs_sum.argmax(dim=1)
|
||||
max_ids = topk_ids[max_topk_ids>=0,max_topk_ids]
|
||||
|
||||
return max_ids
|
||||
|
||||
|
||||
def blip_vqa(pretrained='',**kwargs):
|
||||
model = BLIP_VQA(**kwargs)
|
||||
if pretrained:
|
||||
model,msg = load_checkpoint(model,pretrained)
|
||||
# assert(len(msg.missing_keys)==0)
|
||||
return model
|
||||
|
||||
|
||||
def tile(x, dim, n_tile):
|
||||
init_dim = x.size(dim)
|
||||
repeat_idx = [1] * x.dim()
|
||||
repeat_idx[dim] = n_tile
|
||||
x = x.repeat(*(repeat_idx))
|
||||
order_index = torch.LongTensor(np.concatenate([init_dim * np.arange(n_tile) + i for i in range(init_dim)]))
|
||||
return torch.index_select(x, dim, order_index.to(x.device))
|
||||
|
||||
|
||||
@@ -0,0 +1,955 @@
|
||||
'''
|
||||
* Copyright (c) 2022, salesforce.com, inc.
|
||||
* All rights reserved.
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
|
||||
* By Junnan Li
|
||||
* Based on huggingface code base
|
||||
* https://github.com/huggingface/transformers/blob/v4.15.0/src/transformers/models/bert
|
||||
'''
|
||||
|
||||
import math
|
||||
import os
|
||||
import warnings
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional, Tuple
|
||||
|
||||
import torch
|
||||
from torch import Tensor, device, dtype, nn
|
||||
import torch.utils.checkpoint
|
||||
from torch import nn
|
||||
from torch.nn import CrossEntropyLoss
|
||||
import torch.nn.functional as F
|
||||
|
||||
from transformers.activations import ACT2FN
|
||||
from transformers.file_utils import (
|
||||
ModelOutput,
|
||||
)
|
||||
from transformers.modeling_outputs import (
|
||||
BaseModelOutputWithPastAndCrossAttentions,
|
||||
BaseModelOutputWithPoolingAndCrossAttentions,
|
||||
CausalLMOutputWithCrossAttentions,
|
||||
MaskedLMOutput,
|
||||
MultipleChoiceModelOutput,
|
||||
NextSentencePredictorOutput,
|
||||
QuestionAnsweringModelOutput,
|
||||
SequenceClassifierOutput,
|
||||
TokenClassifierOutput,
|
||||
)
|
||||
from transformers.modeling_utils import (
|
||||
PreTrainedModel,
|
||||
apply_chunking_to_forward,
|
||||
find_pruneable_heads_and_indices,
|
||||
prune_linear_layer,
|
||||
)
|
||||
from transformers.utils import logging
|
||||
from transformers.models.bert.configuration_bert import BertConfig
|
||||
|
||||
|
||||
logger = logging.get_logger(__name__)
|
||||
|
||||
|
||||
class BertEmbeddings(nn.Module):
|
||||
"""Construct the embeddings from word and position embeddings."""
|
||||
|
||||
def __init__(self, config):
|
||||
super().__init__()
|
||||
self.word_embeddings = nn.Embedding(config.vocab_size, config.hidden_size, padding_idx=config.pad_token_id)
|
||||
self.position_embeddings = nn.Embedding(config.max_position_embeddings, config.hidden_size)
|
||||
|
||||
# self.LayerNorm is not snake-cased to stick with TensorFlow model variable name and be able to load
|
||||
# any TensorFlow checkpoint file
|
||||
self.LayerNorm = nn.LayerNorm(config.hidden_size, eps=config.layer_norm_eps)
|
||||
self.dropout = nn.Dropout(config.hidden_dropout_prob)
|
||||
|
||||
# position_ids (1, len position emb) is contiguous in memory and exported when serialized
|
||||
self.register_buffer("position_ids", torch.arange(config.max_position_embeddings).expand((1, -1)))
|
||||
self.position_embedding_type = getattr(config, "position_embedding_type", "absolute")
|
||||
|
||||
self.config = config
|
||||
|
||||
def forward(
|
||||
self, input_ids=None, position_ids=None, inputs_embeds=None, past_key_values_length=0
|
||||
):
|
||||
if input_ids is not None:
|
||||
input_shape = input_ids.size()
|
||||
else:
|
||||
input_shape = inputs_embeds.size()[:-1]
|
||||
|
||||
seq_length = input_shape[1]
|
||||
|
||||
if position_ids is None:
|
||||
position_ids = self.position_ids[:, past_key_values_length : seq_length + past_key_values_length]
|
||||
|
||||
if inputs_embeds is None:
|
||||
inputs_embeds = self.word_embeddings(input_ids)
|
||||
|
||||
embeddings = inputs_embeds
|
||||
|
||||
if self.position_embedding_type == "absolute":
|
||||
position_embeddings = self.position_embeddings(position_ids)
|
||||
embeddings += position_embeddings
|
||||
embeddings = self.LayerNorm(embeddings)
|
||||
embeddings = self.dropout(embeddings)
|
||||
return embeddings
|
||||
|
||||
|
||||
class BertSelfAttention(nn.Module):
|
||||
def __init__(self, config, is_cross_attention):
|
||||
super().__init__()
|
||||
self.config = config
|
||||
if config.hidden_size % config.num_attention_heads != 0 and not hasattr(config, "embedding_size"):
|
||||
raise ValueError(
|
||||
"The hidden size (%d) is not a multiple of the number of attention "
|
||||
"heads (%d)" % (config.hidden_size, config.num_attention_heads)
|
||||
)
|
||||
|
||||
self.num_attention_heads = config.num_attention_heads
|
||||
self.attention_head_size = int(config.hidden_size / config.num_attention_heads)
|
||||
self.all_head_size = self.num_attention_heads * self.attention_head_size
|
||||
|
||||
self.query = nn.Linear(config.hidden_size, self.all_head_size)
|
||||
if is_cross_attention:
|
||||
self.key = nn.Linear(config.encoder_width, self.all_head_size)
|
||||
self.value = nn.Linear(config.encoder_width, self.all_head_size)
|
||||
else:
|
||||
self.key = nn.Linear(config.hidden_size, self.all_head_size)
|
||||
self.value = nn.Linear(config.hidden_size, self.all_head_size)
|
||||
|
||||
self.dropout = nn.Dropout(config.attention_probs_dropout_prob)
|
||||
self.position_embedding_type = getattr(config, "position_embedding_type", "absolute")
|
||||
if self.position_embedding_type == "relative_key" or self.position_embedding_type == "relative_key_query":
|
||||
self.max_position_embeddings = config.max_position_embeddings
|
||||
self.distance_embedding = nn.Embedding(2 * config.max_position_embeddings - 1, self.attention_head_size)
|
||||
self.save_attention = False
|
||||
|
||||
def save_attn_gradients(self, attn_gradients):
|
||||
self.attn_gradients = attn_gradients
|
||||
|
||||
def get_attn_gradients(self):
|
||||
return self.attn_gradients
|
||||
|
||||
def save_attention_map(self, attention_map):
|
||||
self.attention_map = attention_map
|
||||
|
||||
def get_attention_map(self):
|
||||
return self.attention_map
|
||||
|
||||
def transpose_for_scores(self, x):
|
||||
new_x_shape = x.size()[:-1] + (self.num_attention_heads, self.attention_head_size)
|
||||
x = x.view(*new_x_shape)
|
||||
return x.permute(0, 2, 1, 3)
|
||||
|
||||
def forward(
|
||||
self,
|
||||
hidden_states,
|
||||
attention_mask=None,
|
||||
head_mask=None,
|
||||
encoder_hidden_states=None,
|
||||
encoder_attention_mask=None,
|
||||
past_key_value=None,
|
||||
output_attentions=False,
|
||||
):
|
||||
mixed_query_layer = self.query(hidden_states)
|
||||
|
||||
# If this is instantiated as a cross-attention module, the keys
|
||||
# and values come from an encoder; the attention mask needs to be
|
||||
# such that the encoder's padding tokens are not attended to.
|
||||
is_cross_attention = encoder_hidden_states is not None
|
||||
|
||||
if is_cross_attention:
|
||||
key_layer = self.transpose_for_scores(self.key(encoder_hidden_states))
|
||||
value_layer = self.transpose_for_scores(self.value(encoder_hidden_states))
|
||||
attention_mask = encoder_attention_mask
|
||||
elif past_key_value is not None:
|
||||
key_layer = self.transpose_for_scores(self.key(hidden_states))
|
||||
value_layer = self.transpose_for_scores(self.value(hidden_states))
|
||||
key_layer = torch.cat([past_key_value[0], key_layer], dim=2)
|
||||
value_layer = torch.cat([past_key_value[1], value_layer], dim=2)
|
||||
else:
|
||||
key_layer = self.transpose_for_scores(self.key(hidden_states))
|
||||
value_layer = self.transpose_for_scores(self.value(hidden_states))
|
||||
|
||||
query_layer = self.transpose_for_scores(mixed_query_layer)
|
||||
|
||||
past_key_value = (key_layer, value_layer)
|
||||
|
||||
# Take the dot product between "query" and "key" to get the raw attention scores.
|
||||
attention_scores = torch.matmul(query_layer, key_layer.transpose(-1, -2))
|
||||
|
||||
if self.position_embedding_type == "relative_key" or self.position_embedding_type == "relative_key_query":
|
||||
seq_length = hidden_states.size()[1]
|
||||
position_ids_l = torch.arange(seq_length, dtype=torch.long, device=hidden_states.device).view(-1, 1)
|
||||
position_ids_r = torch.arange(seq_length, dtype=torch.long, device=hidden_states.device).view(1, -1)
|
||||
distance = position_ids_l - position_ids_r
|
||||
positional_embedding = self.distance_embedding(distance + self.max_position_embeddings - 1)
|
||||
positional_embedding = positional_embedding.to(dtype=query_layer.dtype) # fp16 compatibility
|
||||
|
||||
if self.position_embedding_type == "relative_key":
|
||||
relative_position_scores = torch.einsum("bhld,lrd->bhlr", query_layer, positional_embedding)
|
||||
attention_scores = attention_scores + relative_position_scores
|
||||
elif self.position_embedding_type == "relative_key_query":
|
||||
relative_position_scores_query = torch.einsum("bhld,lrd->bhlr", query_layer, positional_embedding)
|
||||
relative_position_scores_key = torch.einsum("bhrd,lrd->bhlr", key_layer, positional_embedding)
|
||||
attention_scores = attention_scores + relative_position_scores_query + relative_position_scores_key
|
||||
|
||||
attention_scores = attention_scores / math.sqrt(self.attention_head_size)
|
||||
if attention_mask is not None:
|
||||
# Apply the attention mask is (precomputed for all layers in BertModel forward() function)
|
||||
attention_scores = attention_scores + attention_mask
|
||||
|
||||
# Normalize the attention scores to probabilities.
|
||||
attention_probs = nn.Softmax(dim=-1)(attention_scores)
|
||||
|
||||
if is_cross_attention and self.save_attention:
|
||||
self.save_attention_map(attention_probs)
|
||||
attention_probs.register_hook(self.save_attn_gradients)
|
||||
|
||||
# This is actually dropping out entire tokens to attend to, which might
|
||||
# seem a bit unusual, but is taken from the original Transformer paper.
|
||||
attention_probs_dropped = self.dropout(attention_probs)
|
||||
|
||||
# Mask heads if we want to
|
||||
if head_mask is not None:
|
||||
attention_probs_dropped = attention_probs_dropped * head_mask
|
||||
|
||||
context_layer = torch.matmul(attention_probs_dropped, value_layer)
|
||||
|
||||
context_layer = context_layer.permute(0, 2, 1, 3).contiguous()
|
||||
new_context_layer_shape = context_layer.size()[:-2] + (self.all_head_size,)
|
||||
context_layer = context_layer.view(*new_context_layer_shape)
|
||||
|
||||
outputs = (context_layer, attention_probs) if output_attentions else (context_layer,)
|
||||
|
||||
outputs = outputs + (past_key_value,)
|
||||
return outputs
|
||||
|
||||
|
||||
class BertSelfOutput(nn.Module):
|
||||
def __init__(self, config):
|
||||
super().__init__()
|
||||
self.dense = nn.Linear(config.hidden_size, config.hidden_size)
|
||||
self.LayerNorm = nn.LayerNorm(config.hidden_size, eps=config.layer_norm_eps)
|
||||
self.dropout = nn.Dropout(config.hidden_dropout_prob)
|
||||
|
||||
def forward(self, hidden_states, input_tensor):
|
||||
hidden_states = self.dense(hidden_states)
|
||||
hidden_states = self.dropout(hidden_states)
|
||||
hidden_states = self.LayerNorm(hidden_states + input_tensor)
|
||||
return hidden_states
|
||||
|
||||
|
||||
class BertAttention(nn.Module):
|
||||
def __init__(self, config, is_cross_attention=False):
|
||||
super().__init__()
|
||||
self.self = BertSelfAttention(config, is_cross_attention)
|
||||
self.output = BertSelfOutput(config)
|
||||
self.pruned_heads = set()
|
||||
|
||||
def prune_heads(self, heads):
|
||||
if len(heads) == 0:
|
||||
return
|
||||
heads, index = find_pruneable_heads_and_indices(
|
||||
heads, self.self.num_attention_heads, self.self.attention_head_size, self.pruned_heads
|
||||
)
|
||||
|
||||
# Prune linear layers
|
||||
self.self.query = prune_linear_layer(self.self.query, index)
|
||||
self.self.key = prune_linear_layer(self.self.key, index)
|
||||
self.self.value = prune_linear_layer(self.self.value, index)
|
||||
self.output.dense = prune_linear_layer(self.output.dense, index, dim=1)
|
||||
|
||||
# Update hyper params and store pruned heads
|
||||
self.self.num_attention_heads = self.self.num_attention_heads - len(heads)
|
||||
self.self.all_head_size = self.self.attention_head_size * self.self.num_attention_heads
|
||||
self.pruned_heads = self.pruned_heads.union(heads)
|
||||
|
||||
def forward(
|
||||
self,
|
||||
hidden_states,
|
||||
attention_mask=None,
|
||||
head_mask=None,
|
||||
encoder_hidden_states=None,
|
||||
encoder_attention_mask=None,
|
||||
past_key_value=None,
|
||||
output_attentions=False,
|
||||
):
|
||||
self_outputs = self.self(
|
||||
hidden_states,
|
||||
attention_mask,
|
||||
head_mask,
|
||||
encoder_hidden_states,
|
||||
encoder_attention_mask,
|
||||
past_key_value,
|
||||
output_attentions,
|
||||
)
|
||||
attention_output = self.output(self_outputs[0], hidden_states)
|
||||
outputs = (attention_output,) + self_outputs[1:] # add attentions if we output them
|
||||
return outputs
|
||||
|
||||
|
||||
class BertIntermediate(nn.Module):
|
||||
def __init__(self, config):
|
||||
super().__init__()
|
||||
self.dense = nn.Linear(config.hidden_size, config.intermediate_size)
|
||||
if isinstance(config.hidden_act, str):
|
||||
self.intermediate_act_fn = ACT2FN[config.hidden_act]
|
||||
else:
|
||||
self.intermediate_act_fn = config.hidden_act
|
||||
|
||||
def forward(self, hidden_states):
|
||||
hidden_states = self.dense(hidden_states)
|
||||
hidden_states = self.intermediate_act_fn(hidden_states)
|
||||
return hidden_states
|
||||
|
||||
|
||||
class BertOutput(nn.Module):
|
||||
def __init__(self, config):
|
||||
super().__init__()
|
||||
self.dense = nn.Linear(config.intermediate_size, config.hidden_size)
|
||||
self.LayerNorm = nn.LayerNorm(config.hidden_size, eps=config.layer_norm_eps)
|
||||
self.dropout = nn.Dropout(config.hidden_dropout_prob)
|
||||
|
||||
def forward(self, hidden_states, input_tensor):
|
||||
hidden_states = self.dense(hidden_states)
|
||||
hidden_states = self.dropout(hidden_states)
|
||||
hidden_states = self.LayerNorm(hidden_states + input_tensor)
|
||||
return hidden_states
|
||||
|
||||
|
||||
class BertLayer(nn.Module):
|
||||
def __init__(self, config, layer_num):
|
||||
super().__init__()
|
||||
self.config = config
|
||||
self.chunk_size_feed_forward = config.chunk_size_feed_forward
|
||||
self.seq_len_dim = 1
|
||||
self.attention = BertAttention(config)
|
||||
self.layer_num = layer_num
|
||||
if self.config.add_cross_attention:
|
||||
self.crossattention = BertAttention(config, is_cross_attention=self.config.add_cross_attention)
|
||||
self.intermediate = BertIntermediate(config)
|
||||
self.output = BertOutput(config)
|
||||
|
||||
def forward(
|
||||
self,
|
||||
hidden_states,
|
||||
attention_mask=None,
|
||||
head_mask=None,
|
||||
encoder_hidden_states=None,
|
||||
encoder_attention_mask=None,
|
||||
past_key_value=None,
|
||||
output_attentions=False,
|
||||
mode=None,
|
||||
):
|
||||
# decoder uni-directional self-attention cached key/values tuple is at positions 1,2
|
||||
self_attn_past_key_value = past_key_value[:2] if past_key_value is not None else None
|
||||
self_attention_outputs = self.attention(
|
||||
hidden_states,
|
||||
attention_mask,
|
||||
head_mask,
|
||||
output_attentions=output_attentions,
|
||||
past_key_value=self_attn_past_key_value,
|
||||
)
|
||||
attention_output = self_attention_outputs[0]
|
||||
|
||||
outputs = self_attention_outputs[1:-1]
|
||||
present_key_value = self_attention_outputs[-1]
|
||||
|
||||
if mode=='multimodal':
|
||||
assert encoder_hidden_states is not None, "encoder_hidden_states must be given for cross-attention layers"
|
||||
|
||||
cross_attention_outputs = self.crossattention(
|
||||
attention_output,
|
||||
attention_mask,
|
||||
head_mask,
|
||||
encoder_hidden_states,
|
||||
encoder_attention_mask,
|
||||
output_attentions=output_attentions,
|
||||
)
|
||||
attention_output = cross_attention_outputs[0]
|
||||
outputs = outputs + cross_attention_outputs[1:-1] # add cross attentions if we output attention weights
|
||||
layer_output = apply_chunking_to_forward(
|
||||
self.feed_forward_chunk, self.chunk_size_feed_forward, self.seq_len_dim, attention_output
|
||||
)
|
||||
outputs = (layer_output,) + outputs
|
||||
|
||||
outputs = outputs + (present_key_value,)
|
||||
|
||||
return outputs
|
||||
|
||||
def feed_forward_chunk(self, attention_output):
|
||||
intermediate_output = self.intermediate(attention_output)
|
||||
layer_output = self.output(intermediate_output, attention_output)
|
||||
return layer_output
|
||||
|
||||
|
||||
class BertEncoder(nn.Module):
|
||||
def __init__(self, config):
|
||||
super().__init__()
|
||||
self.config = config
|
||||
self.layer = nn.ModuleList([BertLayer(config,i) for i in range(config.num_hidden_layers)])
|
||||
self.gradient_checkpointing = False
|
||||
|
||||
def forward(
|
||||
self,
|
||||
hidden_states,
|
||||
attention_mask=None,
|
||||
head_mask=None,
|
||||
encoder_hidden_states=None,
|
||||
encoder_attention_mask=None,
|
||||
past_key_values=None,
|
||||
use_cache=None,
|
||||
output_attentions=False,
|
||||
output_hidden_states=False,
|
||||
return_dict=True,
|
||||
mode='multimodal',
|
||||
):
|
||||
all_hidden_states = () if output_hidden_states else None
|
||||
all_self_attentions = () if output_attentions else None
|
||||
all_cross_attentions = () if output_attentions and self.config.add_cross_attention else None
|
||||
|
||||
next_decoder_cache = () if use_cache else None
|
||||
|
||||
for i in range(self.config.num_hidden_layers):
|
||||
layer_module = self.layer[i]
|
||||
if output_hidden_states:
|
||||
all_hidden_states = all_hidden_states + (hidden_states,)
|
||||
|
||||
layer_head_mask = head_mask[i] if head_mask is not None else None
|
||||
past_key_value = past_key_values[i] if past_key_values is not None else None
|
||||
|
||||
if self.gradient_checkpointing and self.training:
|
||||
|
||||
if use_cache:
|
||||
logger.warn(
|
||||
"`use_cache=True` is incompatible with gradient checkpointing. Setting `use_cache=False`..."
|
||||
)
|
||||
use_cache = False
|
||||
|
||||
def create_custom_forward(module):
|
||||
def custom_forward(*inputs):
|
||||
return module(*inputs, past_key_value, output_attentions)
|
||||
|
||||
return custom_forward
|
||||
|
||||
layer_outputs = torch.utils.checkpoint.checkpoint(
|
||||
create_custom_forward(layer_module),
|
||||
hidden_states,
|
||||
attention_mask,
|
||||
layer_head_mask,
|
||||
encoder_hidden_states,
|
||||
encoder_attention_mask,
|
||||
mode=mode,
|
||||
)
|
||||
else:
|
||||
layer_outputs = layer_module(
|
||||
hidden_states,
|
||||
attention_mask,
|
||||
layer_head_mask,
|
||||
encoder_hidden_states,
|
||||
encoder_attention_mask,
|
||||
past_key_value,
|
||||
output_attentions,
|
||||
mode=mode,
|
||||
)
|
||||
|
||||
hidden_states = layer_outputs[0]
|
||||
if use_cache:
|
||||
next_decoder_cache += (layer_outputs[-1],)
|
||||
if output_attentions:
|
||||
all_self_attentions = all_self_attentions + (layer_outputs[1],)
|
||||
|
||||
if output_hidden_states:
|
||||
all_hidden_states = all_hidden_states + (hidden_states,)
|
||||
|
||||
if not return_dict:
|
||||
return tuple(
|
||||
v
|
||||
for v in [
|
||||
hidden_states,
|
||||
next_decoder_cache,
|
||||
all_hidden_states,
|
||||
all_self_attentions,
|
||||
all_cross_attentions,
|
||||
]
|
||||
if v is not None
|
||||
)
|
||||
return BaseModelOutputWithPastAndCrossAttentions(
|
||||
last_hidden_state=hidden_states,
|
||||
past_key_values=next_decoder_cache,
|
||||
hidden_states=all_hidden_states,
|
||||
attentions=all_self_attentions,
|
||||
cross_attentions=all_cross_attentions,
|
||||
)
|
||||
|
||||
|
||||
class BertPooler(nn.Module):
|
||||
def __init__(self, config):
|
||||
super().__init__()
|
||||
self.dense = nn.Linear(config.hidden_size, config.hidden_size)
|
||||
self.activation = nn.Tanh()
|
||||
|
||||
def forward(self, hidden_states):
|
||||
# We "pool" the model by simply taking the hidden state corresponding
|
||||
# to the first token.
|
||||
first_token_tensor = hidden_states[:, 0]
|
||||
pooled_output = self.dense(first_token_tensor)
|
||||
pooled_output = self.activation(pooled_output)
|
||||
return pooled_output
|
||||
|
||||
|
||||
class BertPredictionHeadTransform(nn.Module):
|
||||
def __init__(self, config):
|
||||
super().__init__()
|
||||
self.dense = nn.Linear(config.hidden_size, config.hidden_size)
|
||||
if isinstance(config.hidden_act, str):
|
||||
self.transform_act_fn = ACT2FN[config.hidden_act]
|
||||
else:
|
||||
self.transform_act_fn = config.hidden_act
|
||||
self.LayerNorm = nn.LayerNorm(config.hidden_size, eps=config.layer_norm_eps)
|
||||
|
||||
def forward(self, hidden_states):
|
||||
hidden_states = self.dense(hidden_states)
|
||||
hidden_states = self.transform_act_fn(hidden_states)
|
||||
hidden_states = self.LayerNorm(hidden_states)
|
||||
return hidden_states
|
||||
|
||||
|
||||
class BertLMPredictionHead(nn.Module):
|
||||
def __init__(self, config):
|
||||
super().__init__()
|
||||
self.transform = BertPredictionHeadTransform(config)
|
||||
|
||||
# The output weights are the same as the input embeddings, but there is
|
||||
# an output-only bias for each token.
|
||||
self.decoder = nn.Linear(config.hidden_size, config.vocab_size, bias=False)
|
||||
|
||||
self.bias = nn.Parameter(torch.zeros(config.vocab_size))
|
||||
|
||||
# Need a link between the two variables so that the bias is correctly resized with `resize_token_embeddings`
|
||||
self.decoder.bias = self.bias
|
||||
|
||||
def forward(self, hidden_states):
|
||||
hidden_states = self.transform(hidden_states)
|
||||
hidden_states = self.decoder(hidden_states)
|
||||
return hidden_states
|
||||
|
||||
|
||||
class BertOnlyMLMHead(nn.Module):
|
||||
def __init__(self, config):
|
||||
super().__init__()
|
||||
self.predictions = BertLMPredictionHead(config)
|
||||
|
||||
def forward(self, sequence_output):
|
||||
prediction_scores = self.predictions(sequence_output)
|
||||
return prediction_scores
|
||||
|
||||
|
||||
class BertPreTrainedModel(PreTrainedModel):
|
||||
"""
|
||||
An abstract class to handle weights initialization and a simple interface for downloading and loading pretrained
|
||||
models.
|
||||
"""
|
||||
|
||||
config_class = BertConfig
|
||||
base_model_prefix = "bert"
|
||||
_keys_to_ignore_on_load_missing = [r"position_ids"]
|
||||
|
||||
def _init_weights(self, module):
|
||||
""" Initialize the weights """
|
||||
if isinstance(module, (nn.Linear, nn.Embedding)):
|
||||
# Slightly different from the TF version which uses truncated_normal for initialization
|
||||
# cf https://github.com/pytorch/pytorch/pull/5617
|
||||
module.weight.data.normal_(mean=0.0, std=self.config.initializer_range)
|
||||
elif isinstance(module, nn.LayerNorm):
|
||||
module.bias.data.zero_()
|
||||
module.weight.data.fill_(1.0)
|
||||
if isinstance(module, nn.Linear) and module.bias is not None:
|
||||
module.bias.data.zero_()
|
||||
|
||||
|
||||
class BertModel(BertPreTrainedModel):
|
||||
"""
|
||||
The model can behave as an encoder (with only self-attention) as well as a decoder, in which case a layer of
|
||||
cross-attention is added between the self-attention layers, following the architecture described in `Attention is
|
||||
all you need <https://arxiv.org/abs/1706.03762>`__ by Ashish Vaswani, Noam Shazeer, Niki Parmar, Jakob Uszkoreit,
|
||||
Llion Jones, Aidan N. Gomez, Lukasz Kaiser and Illia Polosukhin.
|
||||
argument and :obj:`add_cross_attention` set to :obj:`True`; an :obj:`encoder_hidden_states` is then expected as an
|
||||
input to the forward pass.
|
||||
"""
|
||||
|
||||
def __init__(self, config, add_pooling_layer=True):
|
||||
super().__init__(config)
|
||||
self.config = config
|
||||
|
||||
self.embeddings = BertEmbeddings(config)
|
||||
|
||||
self.encoder = BertEncoder(config)
|
||||
|
||||
self.pooler = BertPooler(config) if add_pooling_layer else None
|
||||
|
||||
self.init_weights()
|
||||
|
||||
|
||||
def get_input_embeddings(self):
|
||||
return self.embeddings.word_embeddings
|
||||
|
||||
def set_input_embeddings(self, value):
|
||||
self.embeddings.word_embeddings = value
|
||||
|
||||
def _prune_heads(self, heads_to_prune):
|
||||
"""
|
||||
Prunes heads of the model. heads_to_prune: dict of {layer_num: list of heads to prune in this layer} See base
|
||||
class PreTrainedModel
|
||||
"""
|
||||
for layer, heads in heads_to_prune.items():
|
||||
self.encoder.layer[layer].attention.prune_heads(heads)
|
||||
|
||||
|
||||
def get_extended_attention_mask(self, attention_mask: Tensor, input_shape: Tuple[int], device: device, is_decoder: bool) -> Tensor:
|
||||
"""
|
||||
Makes broadcastable attention and causal masks so that future and masked tokens are ignored.
|
||||
|
||||
Arguments:
|
||||
attention_mask (:obj:`torch.Tensor`):
|
||||
Mask with ones indicating tokens to attend to, zeros for tokens to ignore.
|
||||
input_shape (:obj:`Tuple[int]`):
|
||||
The shape of the input to the model.
|
||||
device: (:obj:`torch.device`):
|
||||
The device of the input to the model.
|
||||
|
||||
Returns:
|
||||
:obj:`torch.Tensor` The extended attention mask, with a the same dtype as :obj:`attention_mask.dtype`.
|
||||
"""
|
||||
# We can provide a self-attention mask of dimensions [batch_size, from_seq_length, to_seq_length]
|
||||
# ourselves in which case we just need to make it broadcastable to all heads.
|
||||
if attention_mask.dim() == 3:
|
||||
extended_attention_mask = attention_mask[:, None, :, :]
|
||||
elif attention_mask.dim() == 2:
|
||||
# Provided a padding mask of dimensions [batch_size, seq_length]
|
||||
# - if the model is a decoder, apply a causal mask in addition to the padding mask
|
||||
# - if the model is an encoder, make the mask broadcastable to [batch_size, num_heads, seq_length, seq_length]
|
||||
if is_decoder:
|
||||
batch_size, seq_length = input_shape
|
||||
|
||||
seq_ids = torch.arange(seq_length, device=device)
|
||||
causal_mask = seq_ids[None, None, :].repeat(batch_size, seq_length, 1) <= seq_ids[None, :, None]
|
||||
# in case past_key_values are used we need to add a prefix ones mask to the causal mask
|
||||
# causal and attention masks must have same type with pytorch version < 1.3
|
||||
causal_mask = causal_mask.to(attention_mask.dtype)
|
||||
|
||||
if causal_mask.shape[1] < attention_mask.shape[1]:
|
||||
prefix_seq_len = attention_mask.shape[1] - causal_mask.shape[1]
|
||||
causal_mask = torch.cat(
|
||||
[
|
||||
torch.ones((batch_size, seq_length, prefix_seq_len), device=device, dtype=causal_mask.dtype),
|
||||
causal_mask,
|
||||
],
|
||||
axis=-1,
|
||||
)
|
||||
|
||||
extended_attention_mask = causal_mask[:, None, :, :] * attention_mask[:, None, None, :]
|
||||
else:
|
||||
extended_attention_mask = attention_mask[:, None, None, :]
|
||||
else:
|
||||
raise ValueError(
|
||||
"Wrong shape for input_ids (shape {}) or attention_mask (shape {})".format(
|
||||
input_shape, attention_mask.shape
|
||||
)
|
||||
)
|
||||
|
||||
# Since attention_mask is 1.0 for positions we want to attend and 0.0 for
|
||||
# masked positions, this operation will create a tensor which is 0.0 for
|
||||
# positions we want to attend and -10000.0 for masked positions.
|
||||
# Since we are adding it to the raw scores before the softmax, this is
|
||||
# effectively the same as removing these entirely.
|
||||
extended_attention_mask = extended_attention_mask.to(dtype=self.dtype) # fp16 compatibility
|
||||
extended_attention_mask = (1.0 - extended_attention_mask) * -10000.0
|
||||
return extended_attention_mask
|
||||
|
||||
def forward(
|
||||
self,
|
||||
input_ids=None,
|
||||
attention_mask=None,
|
||||
position_ids=None,
|
||||
head_mask=None,
|
||||
inputs_embeds=None,
|
||||
encoder_embeds=None,
|
||||
encoder_hidden_states=None,
|
||||
encoder_attention_mask=None,
|
||||
past_key_values=None,
|
||||
use_cache=None,
|
||||
output_attentions=None,
|
||||
output_hidden_states=None,
|
||||
return_dict=None,
|
||||
is_decoder=False,
|
||||
mode='multimodal',
|
||||
):
|
||||
r"""
|
||||
encoder_hidden_states (:obj:`torch.FloatTensor` of shape :obj:`(batch_size, sequence_length, hidden_size)`, `optional`):
|
||||
Sequence of hidden-states at the output of the last layer of the encoder. Used in the cross-attention if
|
||||
the model is configured as a decoder.
|
||||
encoder_attention_mask (:obj:`torch.FloatTensor` of shape :obj:`(batch_size, sequence_length)`, `optional`):
|
||||
Mask to avoid performing attention on the padding token indices of the encoder input. This mask is used in
|
||||
the cross-attention if the model is configured as a decoder. Mask values selected in ``[0, 1]``:
|
||||
- 1 for tokens that are **not masked**,
|
||||
- 0 for tokens that are **masked**.
|
||||
past_key_values (:obj:`tuple(tuple(torch.FloatTensor))` of length :obj:`config.n_layers` with each tuple having 4 tensors of shape :obj:`(batch_size, num_heads, sequence_length - 1, embed_size_per_head)`):
|
||||
Contains precomputed key and value hidden states of the attention blocks. Can be used to speed up decoding.
|
||||
If :obj:`past_key_values` are used, the user can optionally input only the last :obj:`decoder_input_ids`
|
||||
(those that don't have their past key value states given to this model) of shape :obj:`(batch_size, 1)`
|
||||
instead of all :obj:`decoder_input_ids` of shape :obj:`(batch_size, sequence_length)`.
|
||||
use_cache (:obj:`bool`, `optional`):
|
||||
If set to :obj:`True`, :obj:`past_key_values` key value states are returned and can be used to speed up
|
||||
decoding (see :obj:`past_key_values`).
|
||||
"""
|
||||
output_attentions = output_attentions if output_attentions is not None else self.config.output_attentions
|
||||
output_hidden_states = (
|
||||
output_hidden_states if output_hidden_states is not None else self.config.output_hidden_states
|
||||
)
|
||||
return_dict = return_dict if return_dict is not None else self.config.use_return_dict
|
||||
|
||||
if is_decoder:
|
||||
use_cache = use_cache if use_cache is not None else self.config.use_cache
|
||||
else:
|
||||
use_cache = False
|
||||
|
||||
if input_ids is not None and inputs_embeds is not None:
|
||||
raise ValueError("You cannot specify both input_ids and inputs_embeds at the same time")
|
||||
elif input_ids is not None:
|
||||
input_shape = input_ids.size()
|
||||
batch_size, seq_length = input_shape
|
||||
device = input_ids.device
|
||||
elif inputs_embeds is not None:
|
||||
input_shape = inputs_embeds.size()[:-1]
|
||||
batch_size, seq_length = input_shape
|
||||
device = inputs_embeds.device
|
||||
elif encoder_embeds is not None:
|
||||
input_shape = encoder_embeds.size()[:-1]
|
||||
batch_size, seq_length = input_shape
|
||||
device = encoder_embeds.device
|
||||
else:
|
||||
raise ValueError("You have to specify either input_ids or inputs_embeds or encoder_embeds")
|
||||
|
||||
# past_key_values_length
|
||||
past_key_values_length = past_key_values[0][0].shape[2] if past_key_values is not None else 0
|
||||
|
||||
if attention_mask is None:
|
||||
attention_mask = torch.ones(((batch_size, seq_length + past_key_values_length)), device=device)
|
||||
|
||||
# We can provide a self-attention mask of dimensions [batch_size, from_seq_length, to_seq_length]
|
||||
# ourselves in which case we just need to make it broadcastable to all heads.
|
||||
extended_attention_mask: torch.Tensor = self.get_extended_attention_mask(attention_mask, input_shape,
|
||||
device, is_decoder)
|
||||
|
||||
# If a 2D or 3D attention mask is provided for the cross-attention
|
||||
# we need to make broadcastable to [batch_size, num_heads, seq_length, seq_length]
|
||||
if encoder_hidden_states is not None:
|
||||
if type(encoder_hidden_states) == list:
|
||||
encoder_batch_size, encoder_sequence_length, _ = encoder_hidden_states[0].size()
|
||||
else:
|
||||
encoder_batch_size, encoder_sequence_length, _ = encoder_hidden_states.size()
|
||||
encoder_hidden_shape = (encoder_batch_size, encoder_sequence_length)
|
||||
|
||||
if type(encoder_attention_mask) == list:
|
||||
encoder_extended_attention_mask = [self.invert_attention_mask(mask) for mask in encoder_attention_mask]
|
||||
elif encoder_attention_mask is None:
|
||||
encoder_attention_mask = torch.ones(encoder_hidden_shape, device=device)
|
||||
encoder_extended_attention_mask = self.invert_attention_mask(encoder_attention_mask)
|
||||
else:
|
||||
encoder_extended_attention_mask = self.invert_attention_mask(encoder_attention_mask)
|
||||
else:
|
||||
encoder_extended_attention_mask = None
|
||||
|
||||
# Prepare head mask if needed
|
||||
# 1.0 in head_mask indicate we keep the head
|
||||
# attention_probs has shape bsz x n_heads x N x N
|
||||
# input head_mask has shape [num_heads] or [num_hidden_layers x num_heads]
|
||||
# and head_mask is converted to shape [num_hidden_layers x batch x num_heads x seq_length x seq_length]
|
||||
head_mask = self.get_head_mask(head_mask, self.config.num_hidden_layers)
|
||||
|
||||
if encoder_embeds is None:
|
||||
embedding_output = self.embeddings(
|
||||
input_ids=input_ids,
|
||||
position_ids=position_ids,
|
||||
inputs_embeds=inputs_embeds,
|
||||
past_key_values_length=past_key_values_length,
|
||||
)
|
||||
else:
|
||||
embedding_output = encoder_embeds
|
||||
|
||||
encoder_outputs = self.encoder(
|
||||
embedding_output,
|
||||
attention_mask=extended_attention_mask,
|
||||
head_mask=head_mask,
|
||||
encoder_hidden_states=encoder_hidden_states,
|
||||
encoder_attention_mask=encoder_extended_attention_mask,
|
||||
past_key_values=past_key_values,
|
||||
use_cache=use_cache,
|
||||
output_attentions=output_attentions,
|
||||
output_hidden_states=output_hidden_states,
|
||||
return_dict=return_dict,
|
||||
mode=mode,
|
||||
)
|
||||
sequence_output = encoder_outputs[0]
|
||||
pooled_output = self.pooler(sequence_output) if self.pooler is not None else None
|
||||
|
||||
if not return_dict:
|
||||
return (sequence_output, pooled_output) + encoder_outputs[1:]
|
||||
|
||||
return BaseModelOutputWithPoolingAndCrossAttentions(
|
||||
last_hidden_state=sequence_output,
|
||||
pooler_output=pooled_output,
|
||||
past_key_values=encoder_outputs.past_key_values,
|
||||
hidden_states=encoder_outputs.hidden_states,
|
||||
attentions=encoder_outputs.attentions,
|
||||
cross_attentions=encoder_outputs.cross_attentions,
|
||||
)
|
||||
|
||||
|
||||
|
||||
class BertLMHeadModel(BertPreTrainedModel):
|
||||
|
||||
_keys_to_ignore_on_load_unexpected = [r"pooler"]
|
||||
_keys_to_ignore_on_load_missing = [r"position_ids", r"predictions.decoder.bias"]
|
||||
|
||||
def __init__(self, config):
|
||||
super().__init__(config)
|
||||
|
||||
self.bert = BertModel(config, add_pooling_layer=False)
|
||||
self.cls = BertOnlyMLMHead(config)
|
||||
|
||||
self.init_weights()
|
||||
|
||||
def get_output_embeddings(self):
|
||||
return self.cls.predictions.decoder
|
||||
|
||||
def set_output_embeddings(self, new_embeddings):
|
||||
self.cls.predictions.decoder = new_embeddings
|
||||
|
||||
def forward(
|
||||
self,
|
||||
input_ids=None,
|
||||
attention_mask=None,
|
||||
position_ids=None,
|
||||
head_mask=None,
|
||||
inputs_embeds=None,
|
||||
encoder_hidden_states=None,
|
||||
encoder_attention_mask=None,
|
||||
labels=None,
|
||||
past_key_values=None,
|
||||
use_cache=None,
|
||||
output_attentions=None,
|
||||
output_hidden_states=None,
|
||||
return_dict=None,
|
||||
return_logits=False,
|
||||
is_decoder=True,
|
||||
reduction='mean',
|
||||
mode='multimodal',
|
||||
):
|
||||
r"""
|
||||
encoder_hidden_states (:obj:`torch.FloatTensor` of shape :obj:`(batch_size, sequence_length, hidden_size)`, `optional`):
|
||||
Sequence of hidden-states at the output of the last layer of the encoder. Used in the cross-attention if
|
||||
the model is configured as a decoder.
|
||||
encoder_attention_mask (:obj:`torch.FloatTensor` of shape :obj:`(batch_size, sequence_length)`, `optional`):
|
||||
Mask to avoid performing attention on the padding token indices of the encoder input. This mask is used in
|
||||
the cross-attention if the model is configured as a decoder. Mask values selected in ``[0, 1]``:
|
||||
- 1 for tokens that are **not masked**,
|
||||
- 0 for tokens that are **masked**.
|
||||
labels (:obj:`torch.LongTensor` of shape :obj:`(batch_size, sequence_length)`, `optional`):
|
||||
Labels for computing the left-to-right language modeling loss (next word prediction). Indices should be in
|
||||
``[-100, 0, ..., config.vocab_size]`` (see ``input_ids`` docstring) Tokens with indices set to ``-100`` are
|
||||
ignored (masked), the loss is only computed for the tokens with labels n ``[0, ..., config.vocab_size]``
|
||||
past_key_values (:obj:`tuple(tuple(torch.FloatTensor))` of length :obj:`config.n_layers` with each tuple having 4 tensors of shape :obj:`(batch_size, num_heads, sequence_length - 1, embed_size_per_head)`):
|
||||
Contains precomputed key and value hidden states of the attention blocks. Can be used to speed up decoding.
|
||||
If :obj:`past_key_values` are used, the user can optionally input only the last :obj:`decoder_input_ids`
|
||||
(those that don't have their past key value states given to this model) of shape :obj:`(batch_size, 1)`
|
||||
instead of all :obj:`decoder_input_ids` of shape :obj:`(batch_size, sequence_length)`.
|
||||
use_cache (:obj:`bool`, `optional`):
|
||||
If set to :obj:`True`, :obj:`past_key_values` key value states are returned and can be used to speed up
|
||||
decoding (see :obj:`past_key_values`).
|
||||
Returns:
|
||||
Example::
|
||||
>>> from transformers import BertTokenizer, BertLMHeadModel, BertConfig
|
||||
>>> import torch
|
||||
>>> tokenizer = BertTokenizer.from_pretrained('bert-base-cased')
|
||||
>>> config = BertConfig.from_pretrained("bert-base-cased")
|
||||
>>> model = BertLMHeadModel.from_pretrained('bert-base-cased', config=config)
|
||||
>>> inputs = tokenizer("Hello, my dog is cute", return_tensors="pt")
|
||||
>>> outputs = model(**inputs)
|
||||
>>> prediction_logits = outputs.logits
|
||||
"""
|
||||
return_dict = return_dict if return_dict is not None else self.config.use_return_dict
|
||||
if labels is not None:
|
||||
use_cache = False
|
||||
|
||||
outputs = self.bert(
|
||||
input_ids,
|
||||
attention_mask=attention_mask,
|
||||
position_ids=position_ids,
|
||||
head_mask=head_mask,
|
||||
inputs_embeds=inputs_embeds,
|
||||
encoder_hidden_states=encoder_hidden_states,
|
||||
encoder_attention_mask=encoder_attention_mask,
|
||||
past_key_values=past_key_values,
|
||||
use_cache=use_cache,
|
||||
output_attentions=output_attentions,
|
||||
output_hidden_states=output_hidden_states,
|
||||
return_dict=return_dict,
|
||||
is_decoder=is_decoder,
|
||||
mode=mode,
|
||||
)
|
||||
|
||||
sequence_output = outputs[0]
|
||||
prediction_scores = self.cls(sequence_output)
|
||||
|
||||
if return_logits:
|
||||
return prediction_scores[:, :-1, :].contiguous()
|
||||
|
||||
lm_loss = None
|
||||
if labels is not None:
|
||||
# we are doing next-token prediction; shift prediction scores and input ids by one
|
||||
shifted_prediction_scores = prediction_scores[:, :-1, :].contiguous()
|
||||
labels = labels[:, 1:].contiguous()
|
||||
loss_fct = CrossEntropyLoss(reduction=reduction, label_smoothing=0.1)
|
||||
lm_loss = loss_fct(shifted_prediction_scores.view(-1, self.config.vocab_size), labels.view(-1))
|
||||
if reduction=='none':
|
||||
lm_loss = lm_loss.view(prediction_scores.size(0),-1).sum(1)
|
||||
|
||||
if not return_dict:
|
||||
output = (prediction_scores,) + outputs[2:]
|
||||
return ((lm_loss,) + output) if lm_loss is not None else output
|
||||
|
||||
return CausalLMOutputWithCrossAttentions(
|
||||
loss=lm_loss,
|
||||
logits=prediction_scores,
|
||||
past_key_values=outputs.past_key_values,
|
||||
hidden_states=outputs.hidden_states,
|
||||
attentions=outputs.attentions,
|
||||
cross_attentions=outputs.cross_attentions,
|
||||
)
|
||||
|
||||
def prepare_inputs_for_generation(self, input_ids, past=None, attention_mask=None, **model_kwargs):
|
||||
input_shape = input_ids.shape
|
||||
# if model is used as a decoder in encoder-decoder model, the decoder attention mask is created on the fly
|
||||
if attention_mask is None:
|
||||
attention_mask = input_ids.new_ones(input_shape)
|
||||
|
||||
# cut decoder_input_ids if past is used
|
||||
if past is not None:
|
||||
input_ids = input_ids[:, -1:]
|
||||
|
||||
return {
|
||||
"input_ids": input_ids,
|
||||
"attention_mask": attention_mask,
|
||||
"past_key_values": past,
|
||||
"encoder_hidden_states": model_kwargs.get("encoder_hidden_states", None),
|
||||
"encoder_attention_mask": model_kwargs.get("encoder_attention_mask", None),
|
||||
"is_decoder": True,
|
||||
}
|
||||
|
||||
def _reorder_cache(self, past, beam_idx):
|
||||
reordered_past = ()
|
||||
for layer_past in past:
|
||||
reordered_past += (tuple(past_state.index_select(0, beam_idx) for past_state in layer_past),)
|
||||
return reordered_past
|
||||
@@ -0,0 +1,843 @@
|
||||
import math
|
||||
import os
|
||||
import warnings
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional, Tuple
|
||||
|
||||
import torch
|
||||
from torch import Tensor, device, dtype, nn
|
||||
import torch.utils.checkpoint
|
||||
from torch import nn
|
||||
from torch.nn import CrossEntropyLoss
|
||||
import torch.nn.functional as F
|
||||
|
||||
from transformers.activations import ACT2FN
|
||||
from transformers.file_utils import (
|
||||
ModelOutput,
|
||||
)
|
||||
from transformers.modeling_outputs import (
|
||||
BaseModelOutputWithPastAndCrossAttentions,
|
||||
BaseModelOutputWithPoolingAndCrossAttentions,
|
||||
CausalLMOutputWithCrossAttentions,
|
||||
MaskedLMOutput,
|
||||
MultipleChoiceModelOutput,
|
||||
NextSentencePredictorOutput,
|
||||
QuestionAnsweringModelOutput,
|
||||
SequenceClassifierOutput,
|
||||
TokenClassifierOutput,
|
||||
)
|
||||
from transformers.modeling_utils import (
|
||||
PreTrainedModel,
|
||||
apply_chunking_to_forward,
|
||||
find_pruneable_heads_and_indices,
|
||||
prune_linear_layer,
|
||||
)
|
||||
from transformers.utils import logging
|
||||
from transformers.models.bert.configuration_bert import BertConfig
|
||||
|
||||
|
||||
logger = logging.get_logger(__name__)
|
||||
|
||||
|
||||
class BertEmbeddings(nn.Module):
|
||||
"""Construct the embeddings from word and position embeddings."""
|
||||
|
||||
def __init__(self, config):
|
||||
super().__init__()
|
||||
self.word_embeddings = nn.Embedding(config.vocab_size, config.hidden_size, padding_idx=config.pad_token_id)
|
||||
self.position_embeddings = nn.Embedding(config.max_position_embeddings, config.hidden_size)
|
||||
|
||||
# self.LayerNorm is not snake-cased to stick with TensorFlow model variable name and be able to load
|
||||
# any TensorFlow checkpoint file
|
||||
self.LayerNorm = nn.LayerNorm(config.hidden_size, eps=config.layer_norm_eps)
|
||||
self.dropout = nn.Dropout(config.hidden_dropout_prob)
|
||||
|
||||
# position_ids (1, len position emb) is contiguous in memory and exported when serialized
|
||||
self.register_buffer("position_ids", torch.arange(config.max_position_embeddings).expand((1, -1)))
|
||||
self.position_embedding_type = getattr(config, "position_embedding_type", "absolute")
|
||||
|
||||
self.config = config
|
||||
|
||||
def forward(
|
||||
self, input_ids=None, position_ids=None, inputs_embeds=None, past_key_values_length=0
|
||||
):
|
||||
if input_ids is not None:
|
||||
input_shape = input_ids.size()
|
||||
else:
|
||||
input_shape = inputs_embeds.size()[:-1]
|
||||
|
||||
seq_length = input_shape[1]
|
||||
|
||||
if position_ids is None:
|
||||
position_ids = self.position_ids[:, past_key_values_length : seq_length + past_key_values_length]
|
||||
|
||||
if inputs_embeds is None:
|
||||
inputs_embeds = self.word_embeddings(input_ids)
|
||||
|
||||
embeddings = inputs_embeds
|
||||
|
||||
if self.position_embedding_type == "absolute":
|
||||
position_embeddings = self.position_embeddings(position_ids)
|
||||
embeddings += position_embeddings
|
||||
embeddings = self.LayerNorm(embeddings)
|
||||
embeddings = self.dropout(embeddings)
|
||||
return embeddings
|
||||
|
||||
|
||||
class BertSelfAttention(nn.Module):
|
||||
def __init__(self, config, is_cross_attention):
|
||||
super().__init__()
|
||||
self.config = config
|
||||
if config.hidden_size % config.num_attention_heads != 0 and not hasattr(config, "embedding_size"):
|
||||
raise ValueError(
|
||||
"The hidden size (%d) is not a multiple of the number of attention "
|
||||
"heads (%d)" % (config.hidden_size, config.num_attention_heads)
|
||||
)
|
||||
|
||||
self.num_attention_heads = config.num_attention_heads
|
||||
self.attention_head_size = int(config.hidden_size / config.num_attention_heads)
|
||||
self.all_head_size = self.num_attention_heads * self.attention_head_size
|
||||
|
||||
self.query = nn.Linear(config.hidden_size, self.all_head_size)
|
||||
if is_cross_attention:
|
||||
self.key = nn.Linear(config.encoder_width, self.all_head_size)
|
||||
self.value = nn.Linear(config.encoder_width, self.all_head_size)
|
||||
else:
|
||||
self.key = nn.Linear(config.hidden_size, self.all_head_size)
|
||||
self.value = nn.Linear(config.hidden_size, self.all_head_size)
|
||||
|
||||
self.dropout = nn.Dropout(config.attention_probs_dropout_prob)
|
||||
self.position_embedding_type = getattr(config, "position_embedding_type", "absolute")
|
||||
if self.position_embedding_type == "relative_key" or self.position_embedding_type == "relative_key_query":
|
||||
self.max_position_embeddings = config.max_position_embeddings
|
||||
self.distance_embedding = nn.Embedding(2 * config.max_position_embeddings - 1, self.attention_head_size)
|
||||
self.save_attention = False
|
||||
|
||||
def save_attn_gradients(self, attn_gradients):
|
||||
self.attn_gradients = attn_gradients
|
||||
|
||||
def get_attn_gradients(self):
|
||||
return self.attn_gradients
|
||||
|
||||
def save_attention_map(self, attention_map):
|
||||
self.attention_map = attention_map
|
||||
|
||||
def get_attention_map(self):
|
||||
return self.attention_map
|
||||
|
||||
def transpose_for_scores(self, x):
|
||||
new_x_shape = x.size()[:-1] + (self.num_attention_heads, self.attention_head_size)
|
||||
x = x.view(*new_x_shape)
|
||||
return x.permute(0, 2, 1, 3)
|
||||
|
||||
def forward(
|
||||
self,
|
||||
hidden_states,
|
||||
attention_mask=None,
|
||||
head_mask=None,
|
||||
encoder_hidden_states=None,
|
||||
encoder_attention_mask=None,
|
||||
past_key_value=None,
|
||||
output_attentions=False,
|
||||
):
|
||||
mixed_query_layer = self.query(hidden_states)
|
||||
|
||||
# If this is instantiated as a cross-attention module, the keys
|
||||
# and values come from an encoder; the attention mask needs to be
|
||||
# such that the encoder's padding tokens are not attended to.
|
||||
is_cross_attention = encoder_hidden_states is not None
|
||||
|
||||
if is_cross_attention:
|
||||
key_layer = self.transpose_for_scores(self.key(encoder_hidden_states))
|
||||
value_layer = self.transpose_for_scores(self.value(encoder_hidden_states))
|
||||
attention_mask = encoder_attention_mask
|
||||
elif past_key_value is not None:
|
||||
key_layer = self.transpose_for_scores(self.key(hidden_states))
|
||||
value_layer = self.transpose_for_scores(self.value(hidden_states))
|
||||
key_layer = torch.cat([past_key_value[0], key_layer], dim=2)
|
||||
value_layer = torch.cat([past_key_value[1], value_layer], dim=2)
|
||||
else:
|
||||
key_layer = self.transpose_for_scores(self.key(hidden_states))
|
||||
value_layer = self.transpose_for_scores(self.value(hidden_states))
|
||||
|
||||
query_layer = self.transpose_for_scores(mixed_query_layer)
|
||||
|
||||
past_key_value = (key_layer, value_layer)
|
||||
|
||||
# Take the dot product between "query" and "key" to get the raw attention scores.
|
||||
attention_scores = torch.matmul(query_layer, key_layer.transpose(-1, -2))
|
||||
|
||||
if self.position_embedding_type == "relative_key" or self.position_embedding_type == "relative_key_query":
|
||||
seq_length = hidden_states.size()[1]
|
||||
position_ids_l = torch.arange(seq_length, dtype=torch.long, device=hidden_states.device).view(-1, 1)
|
||||
position_ids_r = torch.arange(seq_length, dtype=torch.long, device=hidden_states.device).view(1, -1)
|
||||
distance = position_ids_l - position_ids_r
|
||||
positional_embedding = self.distance_embedding(distance + self.max_position_embeddings - 1)
|
||||
positional_embedding = positional_embedding.to(dtype=query_layer.dtype) # fp16 compatibility
|
||||
|
||||
if self.position_embedding_type == "relative_key":
|
||||
relative_position_scores = torch.einsum("bhld,lrd->bhlr", query_layer, positional_embedding)
|
||||
attention_scores = attention_scores + relative_position_scores
|
||||
elif self.position_embedding_type == "relative_key_query":
|
||||
relative_position_scores_query = torch.einsum("bhld,lrd->bhlr", query_layer, positional_embedding)
|
||||
relative_position_scores_key = torch.einsum("bhrd,lrd->bhlr", key_layer, positional_embedding)
|
||||
attention_scores = attention_scores + relative_position_scores_query + relative_position_scores_key
|
||||
|
||||
attention_scores = attention_scores / math.sqrt(self.attention_head_size)
|
||||
if attention_mask is not None:
|
||||
# Apply the attention mask is (precomputed for all layers in BertModel forward() function)
|
||||
attention_scores = attention_scores + attention_mask
|
||||
|
||||
# Normalize the attention scores to probabilities.
|
||||
attention_probs = nn.Softmax(dim=-1)(attention_scores)
|
||||
|
||||
if is_cross_attention and self.save_attention:
|
||||
self.save_attention_map(attention_probs)
|
||||
attention_probs.register_hook(self.save_attn_gradients)
|
||||
|
||||
# This is actually dropping out entire tokens to attend to, which might
|
||||
# seem a bit unusual, but is taken from the original Transformer paper.
|
||||
attention_probs_dropped = self.dropout(attention_probs)
|
||||
|
||||
# Mask heads if we want to
|
||||
if head_mask is not None:
|
||||
attention_probs_dropped = attention_probs_dropped * head_mask
|
||||
|
||||
context_layer = torch.matmul(attention_probs_dropped, value_layer)
|
||||
|
||||
context_layer = context_layer.permute(0, 2, 1, 3).contiguous()
|
||||
new_context_layer_shape = context_layer.size()[:-2] + (self.all_head_size,)
|
||||
context_layer = context_layer.view(*new_context_layer_shape)
|
||||
|
||||
outputs = (context_layer, attention_probs) if output_attentions else (context_layer,)
|
||||
|
||||
outputs = outputs + (past_key_value,)
|
||||
return outputs
|
||||
|
||||
|
||||
class BertSelfOutput(nn.Module):
|
||||
def __init__(self, config, twin=False, merge=False):
|
||||
super().__init__()
|
||||
self.LayerNorm = nn.LayerNorm(config.hidden_size, eps=config.layer_norm_eps)
|
||||
self.dropout = nn.Dropout(config.hidden_dropout_prob)
|
||||
if twin:
|
||||
self.dense0 = nn.Linear(config.hidden_size, config.hidden_size)
|
||||
self.dense1 = nn.Linear(config.hidden_size, config.hidden_size)
|
||||
else:
|
||||
self.dense = nn.Linear(config.hidden_size, config.hidden_size)
|
||||
if merge:
|
||||
self.act = ACT2FN[config.hidden_act]
|
||||
self.merge_layer = nn.Linear(config.hidden_size * 2, config.hidden_size)
|
||||
self.merge = True
|
||||
else:
|
||||
self.merge = False
|
||||
|
||||
def forward(self, hidden_states, input_tensor):
|
||||
if type(hidden_states) == list:
|
||||
hidden_states0 = self.dense0(hidden_states[0])
|
||||
hidden_states1 = self.dense1(hidden_states[1])
|
||||
if self.merge:
|
||||
#hidden_states = self.merge_layer(self.act(torch.cat([hidden_states0,hidden_states1],dim=-1)))
|
||||
hidden_states = self.merge_layer(torch.cat([hidden_states0,hidden_states1],dim=-1))
|
||||
else:
|
||||
hidden_states = (hidden_states0+hidden_states1)/2
|
||||
else:
|
||||
hidden_states = self.dense(hidden_states)
|
||||
hidden_states = self.dropout(hidden_states)
|
||||
hidden_states = self.LayerNorm(hidden_states + input_tensor)
|
||||
return hidden_states
|
||||
|
||||
|
||||
class BertAttention(nn.Module):
|
||||
def __init__(self, config, is_cross_attention=False, layer_num=-1):
|
||||
super().__init__()
|
||||
if is_cross_attention:
|
||||
self.self0 = BertSelfAttention(config, is_cross_attention)
|
||||
self.self1 = BertSelfAttention(config, is_cross_attention)
|
||||
else:
|
||||
self.self = BertSelfAttention(config, is_cross_attention)
|
||||
self.output = BertSelfOutput(config, twin=is_cross_attention, merge=(is_cross_attention and layer_num>=6))
|
||||
self.pruned_heads = set()
|
||||
|
||||
def prune_heads(self, heads):
|
||||
if len(heads) == 0:
|
||||
return
|
||||
heads, index = find_pruneable_heads_and_indices(
|
||||
heads, self.self.num_attention_heads, self.self.attention_head_size, self.pruned_heads
|
||||
)
|
||||
|
||||
# Prune linear layers
|
||||
self.self.query = prune_linear_layer(self.self.query, index)
|
||||
self.self.key = prune_linear_layer(self.self.key, index)
|
||||
self.self.value = prune_linear_layer(self.self.value, index)
|
||||
self.output.dense = prune_linear_layer(self.output.dense, index, dim=1)
|
||||
|
||||
# Update hyper params and store pruned heads
|
||||
self.self.num_attention_heads = self.self.num_attention_heads - len(heads)
|
||||
self.self.all_head_size = self.self.attention_head_size * self.self.num_attention_heads
|
||||
self.pruned_heads = self.pruned_heads.union(heads)
|
||||
|
||||
def forward(
|
||||
self,
|
||||
hidden_states,
|
||||
attention_mask=None,
|
||||
head_mask=None,
|
||||
encoder_hidden_states=None,
|
||||
encoder_attention_mask=None,
|
||||
past_key_value=None,
|
||||
output_attentions=False,
|
||||
):
|
||||
if type(encoder_hidden_states)==list:
|
||||
self_outputs0 = self.self0(
|
||||
hidden_states,
|
||||
attention_mask,
|
||||
head_mask,
|
||||
encoder_hidden_states[0],
|
||||
encoder_attention_mask[0],
|
||||
past_key_value,
|
||||
output_attentions,
|
||||
)
|
||||
self_outputs1 = self.self1(
|
||||
hidden_states,
|
||||
attention_mask,
|
||||
head_mask,
|
||||
encoder_hidden_states[1],
|
||||
encoder_attention_mask[1],
|
||||
past_key_value,
|
||||
output_attentions,
|
||||
)
|
||||
attention_output = self.output([self_outputs0[0],self_outputs1[0]], hidden_states)
|
||||
|
||||
outputs = (attention_output,) + self_outputs0[1:] # add attentions if we output them
|
||||
else:
|
||||
self_outputs = self.self(
|
||||
hidden_states,
|
||||
attention_mask,
|
||||
head_mask,
|
||||
encoder_hidden_states,
|
||||
encoder_attention_mask,
|
||||
past_key_value,
|
||||
output_attentions,
|
||||
)
|
||||
attention_output = self.output(self_outputs[0], hidden_states)
|
||||
outputs = (attention_output,) + self_outputs[1:] # add attentions if we output them
|
||||
return outputs
|
||||
|
||||
|
||||
class BertIntermediate(nn.Module):
|
||||
def __init__(self, config):
|
||||
super().__init__()
|
||||
self.dense = nn.Linear(config.hidden_size, config.intermediate_size)
|
||||
if isinstance(config.hidden_act, str):
|
||||
self.intermediate_act_fn = ACT2FN[config.hidden_act]
|
||||
else:
|
||||
self.intermediate_act_fn = config.hidden_act
|
||||
|
||||
def forward(self, hidden_states):
|
||||
hidden_states = self.dense(hidden_states)
|
||||
hidden_states = self.intermediate_act_fn(hidden_states)
|
||||
return hidden_states
|
||||
|
||||
|
||||
class BertOutput(nn.Module):
|
||||
def __init__(self, config):
|
||||
super().__init__()
|
||||
self.dense = nn.Linear(config.intermediate_size, config.hidden_size)
|
||||
self.LayerNorm = nn.LayerNorm(config.hidden_size, eps=config.layer_norm_eps)
|
||||
self.dropout = nn.Dropout(config.hidden_dropout_prob)
|
||||
|
||||
def forward(self, hidden_states, input_tensor):
|
||||
hidden_states = self.dense(hidden_states)
|
||||
hidden_states = self.dropout(hidden_states)
|
||||
hidden_states = self.LayerNorm(hidden_states + input_tensor)
|
||||
return hidden_states
|
||||
|
||||
|
||||
class BertLayer(nn.Module):
|
||||
def __init__(self, config, layer_num):
|
||||
super().__init__()
|
||||
self.config = config
|
||||
self.chunk_size_feed_forward = config.chunk_size_feed_forward
|
||||
self.seq_len_dim = 1
|
||||
self.attention = BertAttention(config)
|
||||
self.layer_num = layer_num
|
||||
if self.config.add_cross_attention:
|
||||
self.crossattention = BertAttention(config, is_cross_attention=self.config.add_cross_attention, layer_num=layer_num)
|
||||
self.intermediate = BertIntermediate(config)
|
||||
self.output = BertOutput(config)
|
||||
|
||||
def forward(
|
||||
self,
|
||||
hidden_states,
|
||||
attention_mask=None,
|
||||
head_mask=None,
|
||||
encoder_hidden_states=None,
|
||||
encoder_attention_mask=None,
|
||||
past_key_value=None,
|
||||
output_attentions=False,
|
||||
mode=None,
|
||||
):
|
||||
# decoder uni-directional self-attention cached key/values tuple is at positions 1,2
|
||||
self_attn_past_key_value = past_key_value[:2] if past_key_value is not None else None
|
||||
self_attention_outputs = self.attention(
|
||||
hidden_states,
|
||||
attention_mask,
|
||||
head_mask,
|
||||
output_attentions=output_attentions,
|
||||
past_key_value=self_attn_past_key_value,
|
||||
)
|
||||
attention_output = self_attention_outputs[0]
|
||||
|
||||
outputs = self_attention_outputs[1:-1]
|
||||
present_key_value = self_attention_outputs[-1]
|
||||
|
||||
if mode=='multimodal':
|
||||
assert encoder_hidden_states is not None, "encoder_hidden_states must be given for cross-attention layers"
|
||||
cross_attention_outputs = self.crossattention(
|
||||
attention_output,
|
||||
attention_mask,
|
||||
head_mask,
|
||||
encoder_hidden_states,
|
||||
encoder_attention_mask,
|
||||
output_attentions=output_attentions,
|
||||
)
|
||||
attention_output = cross_attention_outputs[0]
|
||||
outputs = outputs + cross_attention_outputs[1:-1] # add cross attentions if we output attention weights
|
||||
layer_output = apply_chunking_to_forward(
|
||||
self.feed_forward_chunk, self.chunk_size_feed_forward, self.seq_len_dim, attention_output
|
||||
)
|
||||
outputs = (layer_output,) + outputs
|
||||
|
||||
outputs = outputs + (present_key_value,)
|
||||
|
||||
return outputs
|
||||
|
||||
def feed_forward_chunk(self, attention_output):
|
||||
intermediate_output = self.intermediate(attention_output)
|
||||
layer_output = self.output(intermediate_output, attention_output)
|
||||
return layer_output
|
||||
|
||||
|
||||
class BertEncoder(nn.Module):
|
||||
def __init__(self, config):
|
||||
super().__init__()
|
||||
self.config = config
|
||||
self.layer = nn.ModuleList([BertLayer(config,i) for i in range(config.num_hidden_layers)])
|
||||
self.gradient_checkpointing = False
|
||||
|
||||
def forward(
|
||||
self,
|
||||
hidden_states,
|
||||
attention_mask=None,
|
||||
head_mask=None,
|
||||
encoder_hidden_states=None,
|
||||
encoder_attention_mask=None,
|
||||
past_key_values=None,
|
||||
use_cache=None,
|
||||
output_attentions=False,
|
||||
output_hidden_states=False,
|
||||
return_dict=True,
|
||||
mode='multimodal',
|
||||
):
|
||||
all_hidden_states = () if output_hidden_states else None
|
||||
all_self_attentions = () if output_attentions else None
|
||||
all_cross_attentions = () if output_attentions and self.config.add_cross_attention else None
|
||||
|
||||
next_decoder_cache = () if use_cache else None
|
||||
|
||||
for i in range(self.config.num_hidden_layers):
|
||||
layer_module = self.layer[i]
|
||||
if output_hidden_states:
|
||||
all_hidden_states = all_hidden_states + (hidden_states,)
|
||||
|
||||
layer_head_mask = head_mask[i] if head_mask is not None else None
|
||||
past_key_value = past_key_values[i] if past_key_values is not None else None
|
||||
|
||||
if self.gradient_checkpointing and self.training:
|
||||
|
||||
if use_cache:
|
||||
logger.warn(
|
||||
"`use_cache=True` is incompatible with gradient checkpointing. Setting `use_cache=False`..."
|
||||
)
|
||||
use_cache = False
|
||||
|
||||
def create_custom_forward(module):
|
||||
def custom_forward(*inputs):
|
||||
return module(*inputs, past_key_value, output_attentions)
|
||||
|
||||
return custom_forward
|
||||
|
||||
layer_outputs = torch.utils.checkpoint.checkpoint(
|
||||
create_custom_forward(layer_module),
|
||||
hidden_states,
|
||||
attention_mask,
|
||||
layer_head_mask,
|
||||
encoder_hidden_states,
|
||||
encoder_attention_mask,
|
||||
mode=mode,
|
||||
)
|
||||
else:
|
||||
layer_outputs = layer_module(
|
||||
hidden_states,
|
||||
attention_mask,
|
||||
layer_head_mask,
|
||||
encoder_hidden_states,
|
||||
encoder_attention_mask,
|
||||
past_key_value,
|
||||
output_attentions,
|
||||
mode=mode,
|
||||
)
|
||||
|
||||
hidden_states = layer_outputs[0]
|
||||
if use_cache:
|
||||
next_decoder_cache += (layer_outputs[-1],)
|
||||
if output_attentions:
|
||||
all_self_attentions = all_self_attentions + (layer_outputs[1],)
|
||||
|
||||
if output_hidden_states:
|
||||
all_hidden_states = all_hidden_states + (hidden_states,)
|
||||
|
||||
if not return_dict:
|
||||
return tuple(
|
||||
v
|
||||
for v in [
|
||||
hidden_states,
|
||||
next_decoder_cache,
|
||||
all_hidden_states,
|
||||
all_self_attentions,
|
||||
all_cross_attentions,
|
||||
]
|
||||
if v is not None
|
||||
)
|
||||
return BaseModelOutputWithPastAndCrossAttentions(
|
||||
last_hidden_state=hidden_states,
|
||||
past_key_values=next_decoder_cache,
|
||||
hidden_states=all_hidden_states,
|
||||
attentions=all_self_attentions,
|
||||
cross_attentions=all_cross_attentions,
|
||||
)
|
||||
|
||||
|
||||
class BertPooler(nn.Module):
|
||||
def __init__(self, config):
|
||||
super().__init__()
|
||||
self.dense = nn.Linear(config.hidden_size, config.hidden_size)
|
||||
self.activation = nn.Tanh()
|
||||
|
||||
def forward(self, hidden_states):
|
||||
# We "pool" the model by simply taking the hidden state corresponding
|
||||
# to the first token.
|
||||
first_token_tensor = hidden_states[:, 0]
|
||||
pooled_output = self.dense(first_token_tensor)
|
||||
pooled_output = self.activation(pooled_output)
|
||||
return pooled_output
|
||||
|
||||
|
||||
class BertPredictionHeadTransform(nn.Module):
|
||||
def __init__(self, config):
|
||||
super().__init__()
|
||||
self.dense = nn.Linear(config.hidden_size, config.hidden_size)
|
||||
if isinstance(config.hidden_act, str):
|
||||
self.transform_act_fn = ACT2FN[config.hidden_act]
|
||||
else:
|
||||
self.transform_act_fn = config.hidden_act
|
||||
self.LayerNorm = nn.LayerNorm(config.hidden_size, eps=config.layer_norm_eps)
|
||||
|
||||
def forward(self, hidden_states):
|
||||
hidden_states = self.dense(hidden_states)
|
||||
hidden_states = self.transform_act_fn(hidden_states)
|
||||
hidden_states = self.LayerNorm(hidden_states)
|
||||
return hidden_states
|
||||
|
||||
|
||||
class BertLMPredictionHead(nn.Module):
|
||||
def __init__(self, config):
|
||||
super().__init__()
|
||||
self.transform = BertPredictionHeadTransform(config)
|
||||
|
||||
# The output weights are the same as the input embeddings, but there is
|
||||
# an output-only bias for each token.
|
||||
self.decoder = nn.Linear(config.hidden_size, config.vocab_size, bias=False)
|
||||
|
||||
self.bias = nn.Parameter(torch.zeros(config.vocab_size))
|
||||
|
||||
# Need a link between the two variables so that the bias is correctly resized with `resize_token_embeddings`
|
||||
self.decoder.bias = self.bias
|
||||
|
||||
def forward(self, hidden_states):
|
||||
hidden_states = self.transform(hidden_states)
|
||||
hidden_states = self.decoder(hidden_states)
|
||||
return hidden_states
|
||||
|
||||
|
||||
class BertOnlyMLMHead(nn.Module):
|
||||
def __init__(self, config):
|
||||
super().__init__()
|
||||
self.predictions = BertLMPredictionHead(config)
|
||||
|
||||
def forward(self, sequence_output):
|
||||
prediction_scores = self.predictions(sequence_output)
|
||||
return prediction_scores
|
||||
|
||||
|
||||
class BertPreTrainedModel(PreTrainedModel):
|
||||
"""
|
||||
An abstract class to handle weights initialization and a simple interface for downloading and loading pretrained
|
||||
models.
|
||||
"""
|
||||
|
||||
config_class = BertConfig
|
||||
base_model_prefix = "bert"
|
||||
_keys_to_ignore_on_load_missing = [r"position_ids"]
|
||||
|
||||
def _init_weights(self, module):
|
||||
""" Initialize the weights """
|
||||
if isinstance(module, (nn.Linear, nn.Embedding)):
|
||||
# Slightly different from the TF version which uses truncated_normal for initialization
|
||||
# cf https://github.com/pytorch/pytorch/pull/5617
|
||||
module.weight.data.normal_(mean=0.0, std=self.config.initializer_range)
|
||||
elif isinstance(module, nn.LayerNorm):
|
||||
module.bias.data.zero_()
|
||||
module.weight.data.fill_(1.0)
|
||||
if isinstance(module, nn.Linear) and module.bias is not None:
|
||||
module.bias.data.zero_()
|
||||
|
||||
|
||||
class BertModel(BertPreTrainedModel):
|
||||
"""
|
||||
The model can behave as an encoder (with only self-attention) as well as a decoder, in which case a layer of
|
||||
cross-attention is added between the self-attention layers, following the architecture described in `Attention is
|
||||
all you need <https://arxiv.org/abs/1706.03762>`__ by Ashish Vaswani, Noam Shazeer, Niki Parmar, Jakob Uszkoreit,
|
||||
Llion Jones, Aidan N. Gomez, Lukasz Kaiser and Illia Polosukhin.
|
||||
argument and :obj:`add_cross_attention` set to :obj:`True`; an :obj:`encoder_hidden_states` is then expected as an
|
||||
input to the forward pass.
|
||||
"""
|
||||
|
||||
def __init__(self, config, add_pooling_layer=True):
|
||||
super().__init__(config)
|
||||
self.config = config
|
||||
|
||||
self.embeddings = BertEmbeddings(config)
|
||||
|
||||
self.encoder = BertEncoder(config)
|
||||
|
||||
self.pooler = BertPooler(config) if add_pooling_layer else None
|
||||
|
||||
self.init_weights()
|
||||
|
||||
|
||||
def get_input_embeddings(self):
|
||||
return self.embeddings.word_embeddings
|
||||
|
||||
def set_input_embeddings(self, value):
|
||||
self.embeddings.word_embeddings = value
|
||||
|
||||
def _prune_heads(self, heads_to_prune):
|
||||
"""
|
||||
Prunes heads of the model. heads_to_prune: dict of {layer_num: list of heads to prune in this layer} See base
|
||||
class PreTrainedModel
|
||||
"""
|
||||
for layer, heads in heads_to_prune.items():
|
||||
self.encoder.layer[layer].attention.prune_heads(heads)
|
||||
|
||||
|
||||
def get_extended_attention_mask(self, attention_mask: Tensor, input_shape: Tuple[int], device: device, is_decoder: bool) -> Tensor:
|
||||
"""
|
||||
Makes broadcastable attention and causal masks so that future and masked tokens are ignored.
|
||||
|
||||
Arguments:
|
||||
attention_mask (:obj:`torch.Tensor`):
|
||||
Mask with ones indicating tokens to attend to, zeros for tokens to ignore.
|
||||
input_shape (:obj:`Tuple[int]`):
|
||||
The shape of the input to the model.
|
||||
device: (:obj:`torch.device`):
|
||||
The device of the input to the model.
|
||||
|
||||
Returns:
|
||||
:obj:`torch.Tensor` The extended attention mask, with a the same dtype as :obj:`attention_mask.dtype`.
|
||||
"""
|
||||
# We can provide a self-attention mask of dimensions [batch_size, from_seq_length, to_seq_length]
|
||||
# ourselves in which case we just need to make it broadcastable to all heads.
|
||||
if attention_mask.dim() == 3:
|
||||
extended_attention_mask = attention_mask[:, None, :, :]
|
||||
elif attention_mask.dim() == 2:
|
||||
# Provided a padding mask of dimensions [batch_size, seq_length]
|
||||
# - if the model is a decoder, apply a causal mask in addition to the padding mask
|
||||
# - if the model is an encoder, make the mask broadcastable to [batch_size, num_heads, seq_length, seq_length]
|
||||
if is_decoder:
|
||||
batch_size, seq_length = input_shape
|
||||
|
||||
seq_ids = torch.arange(seq_length, device=device)
|
||||
causal_mask = seq_ids[None, None, :].repeat(batch_size, seq_length, 1) <= seq_ids[None, :, None]
|
||||
# in case past_key_values are used we need to add a prefix ones mask to the causal mask
|
||||
# causal and attention masks must have same type with pytorch version < 1.3
|
||||
causal_mask = causal_mask.to(attention_mask.dtype)
|
||||
|
||||
if causal_mask.shape[1] < attention_mask.shape[1]:
|
||||
prefix_seq_len = attention_mask.shape[1] - causal_mask.shape[1]
|
||||
causal_mask = torch.cat(
|
||||
[
|
||||
torch.ones((batch_size, seq_length, prefix_seq_len), device=device, dtype=causal_mask.dtype),
|
||||
causal_mask,
|
||||
],
|
||||
axis=-1,
|
||||
)
|
||||
|
||||
extended_attention_mask = causal_mask[:, None, :, :] * attention_mask[:, None, None, :]
|
||||
else:
|
||||
extended_attention_mask = attention_mask[:, None, None, :]
|
||||
else:
|
||||
raise ValueError(
|
||||
"Wrong shape for input_ids (shape {}) or attention_mask (shape {})".format(
|
||||
input_shape, attention_mask.shape
|
||||
)
|
||||
)
|
||||
|
||||
# Since attention_mask is 1.0 for positions we want to attend and 0.0 for
|
||||
# masked positions, this operation will create a tensor which is 0.0 for
|
||||
# positions we want to attend and -10000.0 for masked positions.
|
||||
# Since we are adding it to the raw scores before the softmax, this is
|
||||
# effectively the same as removing these entirely.
|
||||
extended_attention_mask = extended_attention_mask.to(dtype=self.dtype) # fp16 compatibility
|
||||
extended_attention_mask = (1.0 - extended_attention_mask) * -10000.0
|
||||
return extended_attention_mask
|
||||
|
||||
def forward(
|
||||
self,
|
||||
input_ids=None,
|
||||
attention_mask=None,
|
||||
position_ids=None,
|
||||
head_mask=None,
|
||||
inputs_embeds=None,
|
||||
encoder_embeds=None,
|
||||
encoder_hidden_states=None,
|
||||
encoder_attention_mask=None,
|
||||
past_key_values=None,
|
||||
use_cache=None,
|
||||
output_attentions=None,
|
||||
output_hidden_states=None,
|
||||
return_dict=None,
|
||||
is_decoder=False,
|
||||
mode='multimodal',
|
||||
):
|
||||
r"""
|
||||
encoder_hidden_states (:obj:`torch.FloatTensor` of shape :obj:`(batch_size, sequence_length, hidden_size)`, `optional`):
|
||||
Sequence of hidden-states at the output of the last layer of the encoder. Used in the cross-attention if
|
||||
the model is configured as a decoder.
|
||||
encoder_attention_mask (:obj:`torch.FloatTensor` of shape :obj:`(batch_size, sequence_length)`, `optional`):
|
||||
Mask to avoid performing attention on the padding token indices of the encoder input. This mask is used in
|
||||
the cross-attention if the model is configured as a decoder. Mask values selected in ``[0, 1]``:
|
||||
- 1 for tokens that are **not masked**,
|
||||
- 0 for tokens that are **masked**.
|
||||
past_key_values (:obj:`tuple(tuple(torch.FloatTensor))` of length :obj:`config.n_layers` with each tuple having 4 tensors of shape :obj:`(batch_size, num_heads, sequence_length - 1, embed_size_per_head)`):
|
||||
Contains precomputed key and value hidden states of the attention blocks. Can be used to speed up decoding.
|
||||
If :obj:`past_key_values` are used, the user can optionally input only the last :obj:`decoder_input_ids`
|
||||
(those that don't have their past key value states given to this model) of shape :obj:`(batch_size, 1)`
|
||||
instead of all :obj:`decoder_input_ids` of shape :obj:`(batch_size, sequence_length)`.
|
||||
use_cache (:obj:`bool`, `optional`):
|
||||
If set to :obj:`True`, :obj:`past_key_values` key value states are returned and can be used to speed up
|
||||
decoding (see :obj:`past_key_values`).
|
||||
"""
|
||||
output_attentions = output_attentions if output_attentions is not None else self.config.output_attentions
|
||||
output_hidden_states = (
|
||||
output_hidden_states if output_hidden_states is not None else self.config.output_hidden_states
|
||||
)
|
||||
return_dict = return_dict if return_dict is not None else self.config.use_return_dict
|
||||
|
||||
if is_decoder:
|
||||
use_cache = use_cache if use_cache is not None else self.config.use_cache
|
||||
else:
|
||||
use_cache = False
|
||||
|
||||
if input_ids is not None and inputs_embeds is not None:
|
||||
raise ValueError("You cannot specify both input_ids and inputs_embeds at the same time")
|
||||
elif input_ids is not None:
|
||||
input_shape = input_ids.size()
|
||||
batch_size, seq_length = input_shape
|
||||
device = input_ids.device
|
||||
elif inputs_embeds is not None:
|
||||
input_shape = inputs_embeds.size()[:-1]
|
||||
batch_size, seq_length = input_shape
|
||||
device = inputs_embeds.device
|
||||
elif encoder_embeds is not None:
|
||||
input_shape = encoder_embeds.size()[:-1]
|
||||
batch_size, seq_length = input_shape
|
||||
device = encoder_embeds.device
|
||||
else:
|
||||
raise ValueError("You have to specify either input_ids or inputs_embeds or encoder_embeds")
|
||||
|
||||
# past_key_values_length
|
||||
past_key_values_length = past_key_values[0][0].shape[2] if past_key_values is not None else 0
|
||||
|
||||
if attention_mask is None:
|
||||
attention_mask = torch.ones(((batch_size, seq_length + past_key_values_length)), device=device)
|
||||
|
||||
# We can provide a self-attention mask of dimensions [batch_size, from_seq_length, to_seq_length]
|
||||
# ourselves in which case we just need to make it broadcastable to all heads.
|
||||
extended_attention_mask: torch.Tensor = self.get_extended_attention_mask(attention_mask, input_shape,
|
||||
device, is_decoder)
|
||||
|
||||
# If a 2D or 3D attention mask is provided for the cross-attention
|
||||
# we need to make broadcastable to [batch_size, num_heads, seq_length, seq_length]
|
||||
if encoder_hidden_states is not None:
|
||||
if type(encoder_hidden_states) == list:
|
||||
encoder_batch_size, encoder_sequence_length, _ = encoder_hidden_states[0].size()
|
||||
else:
|
||||
encoder_batch_size, encoder_sequence_length, _ = encoder_hidden_states.size()
|
||||
encoder_hidden_shape = (encoder_batch_size, encoder_sequence_length)
|
||||
|
||||
if type(encoder_attention_mask) == list:
|
||||
encoder_extended_attention_mask = [self.invert_attention_mask(mask) for mask in encoder_attention_mask]
|
||||
elif encoder_attention_mask is None:
|
||||
encoder_attention_mask = torch.ones(encoder_hidden_shape, device=device)
|
||||
encoder_extended_attention_mask = self.invert_attention_mask(encoder_attention_mask)
|
||||
else:
|
||||
encoder_extended_attention_mask = self.invert_attention_mask(encoder_attention_mask)
|
||||
else:
|
||||
encoder_extended_attention_mask = None
|
||||
|
||||
# Prepare head mask if needed
|
||||
# 1.0 in head_mask indicate we keep the head
|
||||
# attention_probs has shape bsz x n_heads x N x N
|
||||
# input head_mask has shape [num_heads] or [num_hidden_layers x num_heads]
|
||||
# and head_mask is converted to shape [num_hidden_layers x batch x num_heads x seq_length x seq_length]
|
||||
head_mask = self.get_head_mask(head_mask, self.config.num_hidden_layers)
|
||||
|
||||
if encoder_embeds is None:
|
||||
embedding_output = self.embeddings(
|
||||
input_ids=input_ids,
|
||||
position_ids=position_ids,
|
||||
inputs_embeds=inputs_embeds,
|
||||
past_key_values_length=past_key_values_length,
|
||||
)
|
||||
else:
|
||||
embedding_output = encoder_embeds
|
||||
|
||||
encoder_outputs = self.encoder(
|
||||
embedding_output,
|
||||
attention_mask=extended_attention_mask,
|
||||
head_mask=head_mask,
|
||||
encoder_hidden_states=encoder_hidden_states,
|
||||
encoder_attention_mask=encoder_extended_attention_mask,
|
||||
past_key_values=past_key_values,
|
||||
use_cache=use_cache,
|
||||
output_attentions=output_attentions,
|
||||
output_hidden_states=output_hidden_states,
|
||||
return_dict=return_dict,
|
||||
mode=mode,
|
||||
)
|
||||
sequence_output = encoder_outputs[0]
|
||||
pooled_output = self.pooler(sequence_output) if self.pooler is not None else None
|
||||
|
||||
if not return_dict:
|
||||
return (sequence_output, pooled_output) + encoder_outputs[1:]
|
||||
|
||||
return BaseModelOutputWithPoolingAndCrossAttentions(
|
||||
last_hidden_state=sequence_output,
|
||||
pooler_output=pooled_output,
|
||||
past_key_values=encoder_outputs.past_key_values,
|
||||
hidden_states=encoder_outputs.hidden_states,
|
||||
attentions=encoder_outputs.attentions,
|
||||
cross_attentions=encoder_outputs.cross_attentions,
|
||||
)
|
||||
|
||||
@@ -0,0 +1,308 @@
|
||||
'''
|
||||
* Copyright (c) 2022, salesforce.com, inc.
|
||||
* All rights reserved.
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
|
||||
* By Junnan Li
|
||||
* Based on timm code base
|
||||
* https://github.com/rwightman/pytorch-image-models/tree/master/timm
|
||||
'''
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
from functools import partial
|
||||
|
||||
from timm.models.vision_transformer import _cfg, PatchEmbed
|
||||
from timm.models.registry import register_model
|
||||
from timm.models.layers import trunc_normal_, DropPath
|
||||
from timm.models.helpers import named_apply, adapt_input_conv
|
||||
|
||||
|
||||
def checkpoint_wrapper(x):
|
||||
return x
|
||||
|
||||
|
||||
class Mlp(nn.Module):
|
||||
""" MLP as used in Vision Transformer, MLP-Mixer and related networks
|
||||
"""
|
||||
def __init__(self, in_features, hidden_features=None, out_features=None, act_layer=nn.GELU, drop=0.):
|
||||
super().__init__()
|
||||
out_features = out_features or in_features
|
||||
hidden_features = hidden_features or in_features
|
||||
self.fc1 = nn.Linear(in_features, hidden_features)
|
||||
self.act = act_layer()
|
||||
self.fc2 = nn.Linear(hidden_features, out_features)
|
||||
self.drop = nn.Dropout(drop)
|
||||
|
||||
def forward(self, x):
|
||||
x = self.fc1(x)
|
||||
x = self.act(x)
|
||||
x = self.drop(x)
|
||||
x = self.fc2(x)
|
||||
x = self.drop(x)
|
||||
return x
|
||||
|
||||
|
||||
class Attention(nn.Module):
|
||||
def __init__(self, dim, num_heads=8, qkv_bias=False, qk_scale=None, attn_drop=0., proj_drop=0.):
|
||||
super().__init__()
|
||||
self.num_heads = num_heads
|
||||
head_dim = dim // num_heads
|
||||
# NOTE scale factor was wrong in my original version, can set manually to be compat with prev weights
|
||||
self.scale = qk_scale or head_dim ** -0.5
|
||||
self.qkv = nn.Linear(dim, dim * 3, bias=qkv_bias)
|
||||
self.attn_drop = nn.Dropout(attn_drop)
|
||||
self.proj = nn.Linear(dim, dim)
|
||||
self.proj_drop = nn.Dropout(proj_drop)
|
||||
self.attn_gradients = None
|
||||
self.attention_map = None
|
||||
|
||||
def save_attn_gradients(self, attn_gradients):
|
||||
self.attn_gradients = attn_gradients
|
||||
|
||||
def get_attn_gradients(self):
|
||||
return self.attn_gradients
|
||||
|
||||
def save_attention_map(self, attention_map):
|
||||
self.attention_map = attention_map
|
||||
|
||||
def get_attention_map(self):
|
||||
return self.attention_map
|
||||
|
||||
def forward(self, x, register_hook=False):
|
||||
B, N, C = x.shape
|
||||
qkv = self.qkv(x).reshape(B, N, 3, self.num_heads, C // self.num_heads).permute(2, 0, 3, 1, 4)
|
||||
q, k, v = qkv[0], qkv[1], qkv[2] # make torchscript happy (cannot use tensor as tuple)
|
||||
|
||||
attn = (q @ k.transpose(-2, -1)) * self.scale
|
||||
attn = attn.softmax(dim=-1)
|
||||
attn = self.attn_drop(attn)
|
||||
|
||||
if register_hook:
|
||||
self.save_attention_map(attn)
|
||||
attn.register_hook(self.save_attn_gradients)
|
||||
|
||||
x = (attn @ v).transpose(1, 2).reshape(B, N, C)
|
||||
x = self.proj(x)
|
||||
x = self.proj_drop(x)
|
||||
return x
|
||||
|
||||
|
||||
class Block(nn.Module):
|
||||
|
||||
def __init__(self, dim, num_heads, mlp_ratio=4., qkv_bias=False, qk_scale=None, drop=0., attn_drop=0.,
|
||||
drop_path=0., act_layer=nn.GELU, norm_layer=nn.LayerNorm, use_grad_checkpointing=False):
|
||||
super().__init__()
|
||||
self.norm1 = norm_layer(dim)
|
||||
self.attn = Attention(
|
||||
dim, num_heads=num_heads, qkv_bias=qkv_bias, qk_scale=qk_scale, attn_drop=attn_drop, proj_drop=drop)
|
||||
# NOTE: drop path for stochastic depth, we shall see if this is better than dropout here
|
||||
self.drop_path = DropPath(drop_path) if drop_path > 0. else nn.Identity()
|
||||
self.norm2 = norm_layer(dim)
|
||||
mlp_hidden_dim = int(dim * mlp_ratio)
|
||||
self.mlp = Mlp(in_features=dim, hidden_features=mlp_hidden_dim, act_layer=act_layer, drop=drop)
|
||||
|
||||
if use_grad_checkpointing:
|
||||
self.attn = checkpoint_wrapper(self.attn)
|
||||
self.mlp = checkpoint_wrapper(self.mlp)
|
||||
|
||||
def forward(self, x, register_hook=False):
|
||||
x = x + self.drop_path(self.attn(self.norm1(x), register_hook=register_hook))
|
||||
x = x + self.drop_path(self.mlp(self.norm2(x)))
|
||||
return x
|
||||
|
||||
|
||||
class VisionTransformer(nn.Module):
|
||||
""" Vision Transformer
|
||||
A PyTorch impl of : `An Image is Worth 16x16 Words: Transformers for Image Recognition at Scale` -
|
||||
https://arxiv.org/abs/2010.11929
|
||||
"""
|
||||
def __init__(self, img_size=224, patch_size=16, in_chans=3, num_classes=1000, embed_dim=768, depth=12,
|
||||
num_heads=12, mlp_ratio=4., qkv_bias=True, qk_scale=None, representation_size=None,
|
||||
drop_rate=0., attn_drop_rate=0., drop_path_rate=0., norm_layer=None,
|
||||
use_grad_checkpointing=False, ckpt_layer=0):
|
||||
"""
|
||||
Args:
|
||||
img_size (int, tuple): input image size
|
||||
patch_size (int, tuple): patch size
|
||||
in_chans (int): number of input channels
|
||||
num_classes (int): number of classes for classification head
|
||||
embed_dim (int): embedding dimension
|
||||
depth (int): depth of transformer
|
||||
num_heads (int): number of attention heads
|
||||
mlp_ratio (int): ratio of mlp hidden dim to embedding dim
|
||||
qkv_bias (bool): enable bias for qkv if True
|
||||
qk_scale (float): override default qk scale of head_dim ** -0.5 if set
|
||||
representation_size (Optional[int]): enable and set representation layer (pre-logits) to this value if set
|
||||
drop_rate (float): dropout rate
|
||||
attn_drop_rate (float): attention dropout rate
|
||||
drop_path_rate (float): stochastic depth rate
|
||||
norm_layer: (nn.Module): normalization layer
|
||||
"""
|
||||
super().__init__()
|
||||
self.num_features = self.embed_dim = embed_dim # num_features for consistency with other models
|
||||
norm_layer = norm_layer or partial(nn.LayerNorm, eps=1e-6)
|
||||
|
||||
self.patch_embed = PatchEmbed(
|
||||
img_size=img_size, patch_size=patch_size, in_chans=in_chans, embed_dim=embed_dim)
|
||||
|
||||
num_patches = self.patch_embed.num_patches
|
||||
|
||||
self.cls_token = nn.Parameter(torch.zeros(1, 1, embed_dim))
|
||||
self.pos_embed = nn.Parameter(torch.zeros(1, num_patches + 1, embed_dim))
|
||||
self.pos_drop = nn.Dropout(p=drop_rate)
|
||||
|
||||
dpr = [x.item() for x in torch.linspace(0, drop_path_rate, depth)] # stochastic depth decay rule
|
||||
self.blocks = nn.ModuleList([
|
||||
Block(
|
||||
dim=embed_dim, num_heads=num_heads, mlp_ratio=mlp_ratio, qkv_bias=qkv_bias, qk_scale=qk_scale,
|
||||
drop=drop_rate, attn_drop=attn_drop_rate, drop_path=dpr[i], norm_layer=norm_layer,
|
||||
use_grad_checkpointing=(use_grad_checkpointing and i>=depth-ckpt_layer)
|
||||
)
|
||||
for i in range(depth)])
|
||||
self.norm = norm_layer(embed_dim)
|
||||
|
||||
trunc_normal_(self.pos_embed, std=.02)
|
||||
trunc_normal_(self.cls_token, std=.02)
|
||||
self.apply(self._init_weights)
|
||||
|
||||
def _init_weights(self, m):
|
||||
if isinstance(m, nn.Linear):
|
||||
trunc_normal_(m.weight, std=.02)
|
||||
if isinstance(m, nn.Linear) and m.bias is not None:
|
||||
nn.init.constant_(m.bias, 0)
|
||||
elif isinstance(m, nn.LayerNorm):
|
||||
nn.init.constant_(m.bias, 0)
|
||||
nn.init.constant_(m.weight, 1.0)
|
||||
|
||||
@torch.jit.ignore
|
||||
def no_weight_decay(self):
|
||||
return {'pos_embed', 'cls_token'}
|
||||
|
||||
def forward(self, x, register_blk=-1):
|
||||
B = x.shape[0]
|
||||
x = self.patch_embed(x)
|
||||
|
||||
cls_tokens = self.cls_token.expand(B, -1, -1) # stole cls_tokens impl from Phil Wang, thanks
|
||||
x = torch.cat((cls_tokens, x), dim=1)
|
||||
|
||||
x = x + self.pos_embed[:,:x.size(1),:]
|
||||
x = self.pos_drop(x)
|
||||
|
||||
for i,blk in enumerate(self.blocks):
|
||||
x = blk(x, register_blk==i)
|
||||
x = self.norm(x)
|
||||
|
||||
return x
|
||||
|
||||
@torch.jit.ignore()
|
||||
def load_pretrained(self, checkpoint_path, prefix=''):
|
||||
_load_weights(self, checkpoint_path, prefix)
|
||||
|
||||
|
||||
@torch.no_grad()
|
||||
def _load_weights(model: VisionTransformer, checkpoint_path: str, prefix: str = ''):
|
||||
""" Load weights from .npz checkpoints for official Google Brain Flax implementation
|
||||
"""
|
||||
import numpy as np
|
||||
|
||||
def _n2p(w, t=True):
|
||||
if w.ndim == 4 and w.shape[0] == w.shape[1] == w.shape[2] == 1:
|
||||
w = w.flatten()
|
||||
if t:
|
||||
if w.ndim == 4:
|
||||
w = w.transpose([3, 2, 0, 1])
|
||||
elif w.ndim == 3:
|
||||
w = w.transpose([2, 0, 1])
|
||||
elif w.ndim == 2:
|
||||
w = w.transpose([1, 0])
|
||||
return torch.from_numpy(w)
|
||||
|
||||
w = np.load(checkpoint_path)
|
||||
if not prefix and 'opt/target/embedding/kernel' in w:
|
||||
prefix = 'opt/target/'
|
||||
|
||||
if hasattr(model.patch_embed, 'backbone'):
|
||||
# hybrid
|
||||
backbone = model.patch_embed.backbone
|
||||
stem_only = not hasattr(backbone, 'stem')
|
||||
stem = backbone if stem_only else backbone.stem
|
||||
stem.conv.weight.copy_(adapt_input_conv(stem.conv.weight.shape[1], _n2p(w[f'{prefix}conv_root/kernel'])))
|
||||
stem.norm.weight.copy_(_n2p(w[f'{prefix}gn_root/scale']))
|
||||
stem.norm.bias.copy_(_n2p(w[f'{prefix}gn_root/bias']))
|
||||
if not stem_only:
|
||||
for i, stage in enumerate(backbone.stages):
|
||||
for j, block in enumerate(stage.blocks):
|
||||
bp = f'{prefix}block{i + 1}/unit{j + 1}/'
|
||||
for r in range(3):
|
||||
getattr(block, f'conv{r + 1}').weight.copy_(_n2p(w[f'{bp}conv{r + 1}/kernel']))
|
||||
getattr(block, f'norm{r + 1}').weight.copy_(_n2p(w[f'{bp}gn{r + 1}/scale']))
|
||||
getattr(block, f'norm{r + 1}').bias.copy_(_n2p(w[f'{bp}gn{r + 1}/bias']))
|
||||
if block.downsample is not None:
|
||||
block.downsample.conv.weight.copy_(_n2p(w[f'{bp}conv_proj/kernel']))
|
||||
block.downsample.norm.weight.copy_(_n2p(w[f'{bp}gn_proj/scale']))
|
||||
block.downsample.norm.bias.copy_(_n2p(w[f'{bp}gn_proj/bias']))
|
||||
embed_conv_w = _n2p(w[f'{prefix}embedding/kernel'])
|
||||
else:
|
||||
embed_conv_w = adapt_input_conv(
|
||||
model.patch_embed.proj.weight.shape[1], _n2p(w[f'{prefix}embedding/kernel']))
|
||||
model.patch_embed.proj.weight.copy_(embed_conv_w)
|
||||
model.patch_embed.proj.bias.copy_(_n2p(w[f'{prefix}embedding/bias']))
|
||||
model.cls_token.copy_(_n2p(w[f'{prefix}cls'], t=False))
|
||||
pos_embed_w = _n2p(w[f'{prefix}Transformer/posembed_input/pos_embedding'], t=False)
|
||||
if pos_embed_w.shape != model.pos_embed.shape:
|
||||
pos_embed_w = resize_pos_embed( # resize pos embedding when different size from pretrained weights
|
||||
pos_embed_w, model.pos_embed, getattr(model, 'num_tokens', 1), model.patch_embed.grid_size)
|
||||
model.pos_embed.copy_(pos_embed_w)
|
||||
model.norm.weight.copy_(_n2p(w[f'{prefix}Transformer/encoder_norm/scale']))
|
||||
model.norm.bias.copy_(_n2p(w[f'{prefix}Transformer/encoder_norm/bias']))
|
||||
# if isinstance(model.head, nn.Linear) and model.head.bias.shape[0] == w[f'{prefix}head/bias'].shape[-1]:
|
||||
# model.head.weight.copy_(_n2p(w[f'{prefix}head/kernel']))
|
||||
# model.head.bias.copy_(_n2p(w[f'{prefix}head/bias']))
|
||||
# if isinstance(getattr(model.pre_logits, 'fc', None), nn.Linear) and f'{prefix}pre_logits/bias' in w:
|
||||
# model.pre_logits.fc.weight.copy_(_n2p(w[f'{prefix}pre_logits/kernel']))
|
||||
# model.pre_logits.fc.bias.copy_(_n2p(w[f'{prefix}pre_logits/bias']))
|
||||
for i, block in enumerate(model.blocks.children()):
|
||||
block_prefix = f'{prefix}Transformer/encoderblock_{i}/'
|
||||
mha_prefix = block_prefix + 'MultiHeadDotProductAttention_1/'
|
||||
block.norm1.weight.copy_(_n2p(w[f'{block_prefix}LayerNorm_0/scale']))
|
||||
block.norm1.bias.copy_(_n2p(w[f'{block_prefix}LayerNorm_0/bias']))
|
||||
block.attn.qkv.weight.copy_(torch.cat([
|
||||
_n2p(w[f'{mha_prefix}{n}/kernel'], t=False).flatten(1).T for n in ('query', 'key', 'value')]))
|
||||
block.attn.qkv.bias.copy_(torch.cat([
|
||||
_n2p(w[f'{mha_prefix}{n}/bias'], t=False).reshape(-1) for n in ('query', 'key', 'value')]))
|
||||
block.attn.proj.weight.copy_(_n2p(w[f'{mha_prefix}out/kernel']).flatten(1))
|
||||
block.attn.proj.bias.copy_(_n2p(w[f'{mha_prefix}out/bias']))
|
||||
for r in range(2):
|
||||
getattr(block.mlp, f'fc{r + 1}').weight.copy_(_n2p(w[f'{block_prefix}MlpBlock_3/Dense_{r}/kernel']))
|
||||
getattr(block.mlp, f'fc{r + 1}').bias.copy_(_n2p(w[f'{block_prefix}MlpBlock_3/Dense_{r}/bias']))
|
||||
block.norm2.weight.copy_(_n2p(w[f'{block_prefix}LayerNorm_2/scale']))
|
||||
block.norm2.bias.copy_(_n2p(w[f'{block_prefix}LayerNorm_2/bias']))
|
||||
|
||||
|
||||
def interpolate_pos_embed(pos_embed_checkpoint, visual_encoder):
|
||||
# interpolate position embedding
|
||||
embedding_size = pos_embed_checkpoint.shape[-1]
|
||||
num_patches = visual_encoder.patch_embed.num_patches
|
||||
num_extra_tokens = visual_encoder.pos_embed.shape[-2] - num_patches
|
||||
# height (== width) for the checkpoint position embedding
|
||||
orig_size = int((pos_embed_checkpoint.shape[-2] - num_extra_tokens) ** 0.5)
|
||||
# height (== width) for the new position embedding
|
||||
new_size = int(num_patches ** 0.5)
|
||||
|
||||
if orig_size!=new_size:
|
||||
# class_token and dist_token are kept unchanged
|
||||
extra_tokens = pos_embed_checkpoint[:, :num_extra_tokens]
|
||||
# only the position tokens are interpolated
|
||||
pos_tokens = pos_embed_checkpoint[:, num_extra_tokens:]
|
||||
pos_tokens = pos_tokens.reshape(-1, orig_size, orig_size, embedding_size).permute(0, 3, 1, 2)
|
||||
pos_tokens = torch.nn.functional.interpolate(
|
||||
pos_tokens, size=(new_size, new_size), mode='bicubic', align_corners=False)
|
||||
pos_tokens = pos_tokens.permute(0, 2, 3, 1).flatten(1, 2)
|
||||
new_pos_embed = torch.cat((extra_tokens, pos_tokens), dim=1)
|
||||
print('reshape position embedding from %d to %d'%(orig_size ** 2,new_size ** 2))
|
||||
|
||||
return new_pos_embed
|
||||
else:
|
||||
return pos_embed_checkpoint
|
||||
@@ -0,0 +1,43 @@
|
||||
batch_size = 1
|
||||
modelname = "groundingdino"
|
||||
backbone = "swin_T_224_1k"
|
||||
position_embedding = "sine"
|
||||
pe_temperatureH = 20
|
||||
pe_temperatureW = 20
|
||||
return_interm_indices = [1, 2, 3]
|
||||
backbone_freeze_keywords = None
|
||||
enc_layers = 6
|
||||
dec_layers = 6
|
||||
pre_norm = False
|
||||
dim_feedforward = 2048
|
||||
hidden_dim = 256
|
||||
dropout = 0.0
|
||||
nheads = 8
|
||||
num_queries = 900
|
||||
query_dim = 4
|
||||
num_patterns = 0
|
||||
num_feature_levels = 4
|
||||
enc_n_points = 4
|
||||
dec_n_points = 4
|
||||
two_stage_type = "standard"
|
||||
two_stage_bbox_embed_share = False
|
||||
two_stage_class_embed_share = False
|
||||
transformer_activation = "relu"
|
||||
dec_pred_bbox_embed_share = True
|
||||
dn_box_noise_scale = 1.0
|
||||
dn_label_noise_ratio = 0.5
|
||||
dn_label_coef = 1.0
|
||||
dn_bbox_coef = 1.0
|
||||
embed_init_tgt = True
|
||||
dn_labelbook_size = 2000
|
||||
max_text_len = 256
|
||||
text_encoder_type = "bert-base-uncased"
|
||||
use_text_enhancer = True
|
||||
use_fusion_layer = True
|
||||
use_checkpoint = True
|
||||
use_transformer_ckpt = True
|
||||
use_text_cross_attention = True
|
||||
text_dropout = 0.0
|
||||
fusion_dropout = 0.0
|
||||
fusion_droppath = 0.1
|
||||
sub_sentence_present = True
|
||||
@@ -0,0 +1,100 @@
|
||||
from typing import Tuple, List
|
||||
|
||||
import ldm_patched.modules.model_management as model_management
|
||||
from ldm_patched.modules.model_patcher import ModelPatcher
|
||||
from modules.config import path_inpaint
|
||||
from modules.model_loader import load_file_from_url
|
||||
|
||||
import numpy as np
|
||||
import supervision as sv
|
||||
import torch
|
||||
from groundingdino.util.inference import Model
|
||||
from groundingdino.util.inference import load_model, preprocess_caption, get_phrases_from_posmap
|
||||
|
||||
|
||||
class GroundingDinoModel(Model):
|
||||
def __init__(self):
|
||||
self.config_file = 'extras/GroundingDINO/config/GroundingDINO_SwinT_OGC.py'
|
||||
self.model = None
|
||||
self.load_device = torch.device('cpu')
|
||||
self.offload_device = torch.device('cpu')
|
||||
|
||||
@torch.no_grad()
|
||||
@torch.inference_mode()
|
||||
def predict_with_caption(
|
||||
self,
|
||||
image: np.ndarray,
|
||||
caption: str,
|
||||
box_threshold: float = 0.35,
|
||||
text_threshold: float = 0.25
|
||||
) -> Tuple[sv.Detections, torch.Tensor, torch.Tensor, List[str]]:
|
||||
if self.model is None:
|
||||
filename = load_file_from_url(
|
||||
url="https://github.com/IDEA-Research/GroundingDINO/releases/download/v0.1.0-alpha/groundingdino_swint_ogc.pth",
|
||||
file_name='groundingdino_swint_ogc.pth',
|
||||
model_dir=path_inpaint)
|
||||
model = load_model(model_config_path=self.config_file, model_checkpoint_path=filename)
|
||||
|
||||
self.load_device = model_management.text_encoder_device()
|
||||
self.offload_device = model_management.text_encoder_offload_device()
|
||||
|
||||
model.to(self.offload_device)
|
||||
|
||||
self.model = ModelPatcher(model, load_device=self.load_device, offload_device=self.offload_device)
|
||||
|
||||
model_management.load_model_gpu(self.model)
|
||||
|
||||
processed_image = GroundingDinoModel.preprocess_image(image_bgr=image).to(self.load_device)
|
||||
boxes, logits, phrases = predict(
|
||||
model=self.model,
|
||||
image=processed_image,
|
||||
caption=caption,
|
||||
box_threshold=box_threshold,
|
||||
text_threshold=text_threshold,
|
||||
device=self.load_device)
|
||||
source_h, source_w, _ = image.shape
|
||||
detections = GroundingDinoModel.post_process_result(
|
||||
source_h=source_h,
|
||||
source_w=source_w,
|
||||
boxes=boxes,
|
||||
logits=logits)
|
||||
return detections, boxes, logits, phrases
|
||||
|
||||
|
||||
def predict(
|
||||
model,
|
||||
image: torch.Tensor,
|
||||
caption: str,
|
||||
box_threshold: float,
|
||||
text_threshold: float,
|
||||
device: str = "cuda"
|
||||
) -> Tuple[torch.Tensor, torch.Tensor, List[str]]:
|
||||
caption = preprocess_caption(caption=caption)
|
||||
|
||||
# override to use model wrapped by patcher
|
||||
model = model.model.to(device)
|
||||
image = image.to(device)
|
||||
|
||||
with torch.no_grad():
|
||||
outputs = model(image[None], captions=[caption])
|
||||
|
||||
prediction_logits = outputs["pred_logits"].cpu().sigmoid()[0] # prediction_logits.shape = (nq, 256)
|
||||
prediction_boxes = outputs["pred_boxes"].cpu()[0] # prediction_boxes.shape = (nq, 4)
|
||||
|
||||
mask = prediction_logits.max(dim=1)[0] > box_threshold
|
||||
logits = prediction_logits[mask] # logits.shape = (n, 256)
|
||||
boxes = prediction_boxes[mask] # boxes.shape = (n, 4)
|
||||
|
||||
tokenizer = model.tokenizer
|
||||
tokenized = tokenizer(caption)
|
||||
|
||||
phrases = [
|
||||
get_phrases_from_posmap(logit > text_threshold, tokenized, tokenizer).replace('.', '')
|
||||
for logit
|
||||
in logits
|
||||
]
|
||||
|
||||
return boxes, logits.max(dim=1)[0], phrases
|
||||
|
||||
|
||||
default_groundingdino = GroundingDinoModel().predict_with_caption
|
||||
@@ -0,0 +1,60 @@
|
||||
import os
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
from transformers import CLIPConfig, CLIPImageProcessor
|
||||
|
||||
import ldm_patched.modules.model_management as model_management
|
||||
import modules.config
|
||||
from extras.safety_checker.models.safety_checker import StableDiffusionSafetyChecker
|
||||
from ldm_patched.modules.model_patcher import ModelPatcher
|
||||
|
||||
safety_checker_repo_root = os.path.join(os.path.dirname(__file__), 'safety_checker')
|
||||
config_path = os.path.join(safety_checker_repo_root, "configs", "config.json")
|
||||
preprocessor_config_path = os.path.join(safety_checker_repo_root, "configs", "preprocessor_config.json")
|
||||
|
||||
|
||||
class Censor:
|
||||
def __init__(self):
|
||||
self.safety_checker_model: ModelPatcher | None = None
|
||||
self.clip_image_processor: CLIPImageProcessor | None = None
|
||||
self.load_device = torch.device('cpu')
|
||||
self.offload_device = torch.device('cpu')
|
||||
|
||||
def init(self):
|
||||
if self.safety_checker_model is None and self.clip_image_processor is None:
|
||||
safety_checker_model = modules.config.downloading_safety_checker_model()
|
||||
self.clip_image_processor = CLIPImageProcessor.from_json_file(preprocessor_config_path)
|
||||
clip_config = CLIPConfig.from_json_file(config_path)
|
||||
model = StableDiffusionSafetyChecker.from_pretrained(safety_checker_model, config=clip_config)
|
||||
model.eval()
|
||||
|
||||
self.load_device = model_management.text_encoder_device()
|
||||
self.offload_device = model_management.text_encoder_offload_device()
|
||||
|
||||
model.to(self.offload_device)
|
||||
|
||||
self.safety_checker_model = ModelPatcher(model, load_device=self.load_device, offload_device=self.offload_device)
|
||||
|
||||
def censor(self, images: list | np.ndarray) -> list | np.ndarray:
|
||||
self.init()
|
||||
model_management.load_model_gpu(self.safety_checker_model)
|
||||
|
||||
single = False
|
||||
if not isinstance(images, (list, np.ndarray)):
|
||||
images = [images]
|
||||
single = True
|
||||
|
||||
safety_checker_input = self.clip_image_processor(images, return_tensors="pt")
|
||||
safety_checker_input.to(device=self.load_device)
|
||||
checked_images, has_nsfw_concept = self.safety_checker_model.model(images=images,
|
||||
clip_input=safety_checker_input.pixel_values)
|
||||
checked_images = [image.astype(np.uint8) for image in checked_images]
|
||||
|
||||
if single:
|
||||
checked_images = checked_images[0]
|
||||
|
||||
return checked_images
|
||||
|
||||
|
||||
default_censor = Censor().censor
|
||||
@@ -0,0 +1,129 @@
|
||||
# Fooocus GPT2 Expansion
|
||||
# Algorithm created by Lvmin Zhang at 2023, Stanford
|
||||
# If used inside Fooocus, any use is permitted.
|
||||
# If used outside Fooocus, only non-commercial use is permitted (CC-By NC 4.0).
|
||||
# This applies to the word list, vocab, model, and algorithm.
|
||||
|
||||
|
||||
import os
|
||||
import torch
|
||||
import math
|
||||
import ldm_patched.modules.model_management as model_management
|
||||
|
||||
from transformers.generation.logits_process import LogitsProcessorList
|
||||
from transformers import AutoTokenizer, AutoModelForCausalLM, set_seed
|
||||
from modules.config import path_fooocus_expansion
|
||||
from ldm_patched.modules.model_patcher import ModelPatcher
|
||||
|
||||
|
||||
# limitation of np.random.seed(), called from transformers.set_seed()
|
||||
SEED_LIMIT_NUMPY = 2**32
|
||||
neg_inf = - 8192.0
|
||||
|
||||
|
||||
def safe_str(x):
|
||||
x = str(x)
|
||||
for _ in range(16):
|
||||
x = x.replace(' ', ' ')
|
||||
return x.strip(",. \r\n")
|
||||
|
||||
|
||||
def remove_pattern(x, pattern):
|
||||
for p in pattern:
|
||||
x = x.replace(p, '')
|
||||
return x
|
||||
|
||||
|
||||
class FooocusExpansion:
|
||||
def __init__(self):
|
||||
self.tokenizer = AutoTokenizer.from_pretrained(path_fooocus_expansion)
|
||||
|
||||
positive_words = open(os.path.join(path_fooocus_expansion, 'positive.txt'),
|
||||
encoding='utf-8').read().splitlines()
|
||||
positive_words = ['Ġ' + x.lower() for x in positive_words if x != '']
|
||||
|
||||
self.logits_bias = torch.zeros((1, len(self.tokenizer.vocab)), dtype=torch.float32) + neg_inf
|
||||
|
||||
debug_list = []
|
||||
for k, v in self.tokenizer.vocab.items():
|
||||
if k in positive_words:
|
||||
self.logits_bias[0, v] = 0
|
||||
debug_list.append(k[1:])
|
||||
|
||||
print(f'Fooocus V2 Expansion: Vocab with {len(debug_list)} words.')
|
||||
|
||||
# debug_list = '\n'.join(sorted(debug_list))
|
||||
# print(debug_list)
|
||||
|
||||
# t11 = self.tokenizer(',', return_tensors="np")
|
||||
# t198 = self.tokenizer('\n', return_tensors="np")
|
||||
# eos = self.tokenizer.eos_token_id
|
||||
|
||||
self.model = AutoModelForCausalLM.from_pretrained(path_fooocus_expansion)
|
||||
self.model.eval()
|
||||
|
||||
load_device = model_management.text_encoder_device()
|
||||
offload_device = model_management.text_encoder_offload_device()
|
||||
|
||||
# MPS hack
|
||||
if model_management.is_device_mps(load_device):
|
||||
load_device = torch.device('cpu')
|
||||
offload_device = torch.device('cpu')
|
||||
|
||||
use_fp16 = model_management.should_use_fp16(device=load_device)
|
||||
|
||||
if use_fp16:
|
||||
self.model.half()
|
||||
|
||||
self.patcher = ModelPatcher(self.model, load_device=load_device, offload_device=offload_device)
|
||||
print(f'Fooocus Expansion engine loaded for {load_device}, use_fp16 = {use_fp16}.')
|
||||
|
||||
@torch.no_grad()
|
||||
@torch.inference_mode()
|
||||
def logits_processor(self, input_ids, scores):
|
||||
assert scores.ndim == 2 and scores.shape[0] == 1
|
||||
self.logits_bias = self.logits_bias.to(scores)
|
||||
|
||||
bias = self.logits_bias.clone()
|
||||
bias[0, input_ids[0].to(bias.device).long()] = neg_inf
|
||||
bias[0, 11] = 0
|
||||
|
||||
return scores + bias
|
||||
|
||||
@torch.no_grad()
|
||||
@torch.inference_mode()
|
||||
def __call__(self, prompt, seed):
|
||||
if prompt == '':
|
||||
return ''
|
||||
|
||||
if self.patcher.current_device != self.patcher.load_device:
|
||||
print('Fooocus Expansion loaded by itself.')
|
||||
model_management.load_model_gpu(self.patcher)
|
||||
|
||||
seed = int(seed) % SEED_LIMIT_NUMPY
|
||||
set_seed(seed)
|
||||
prompt = safe_str(prompt) + ','
|
||||
|
||||
tokenized_kwargs = self.tokenizer(prompt, return_tensors="pt")
|
||||
tokenized_kwargs.data['input_ids'] = tokenized_kwargs.data['input_ids'].to(self.patcher.load_device)
|
||||
tokenized_kwargs.data['attention_mask'] = tokenized_kwargs.data['attention_mask'].to(self.patcher.load_device)
|
||||
|
||||
current_token_length = int(tokenized_kwargs.data['input_ids'].shape[1])
|
||||
max_token_length = 75 * int(math.ceil(float(current_token_length) / 75.0))
|
||||
max_new_tokens = max_token_length - current_token_length
|
||||
|
||||
if max_new_tokens == 0:
|
||||
return prompt[:-1]
|
||||
|
||||
# https://huggingface.co/blog/introducing-csearch
|
||||
# https://huggingface.co/docs/transformers/generation_strategies
|
||||
features = self.model.generate(**tokenized_kwargs,
|
||||
top_k=100,
|
||||
max_new_tokens=max_new_tokens,
|
||||
do_sample=True,
|
||||
logits_processor=LogitsProcessorList([self.logits_processor]))
|
||||
|
||||
response = self.tokenizer.batch_decode(features, skip_special_tokens=True)
|
||||
result = safe_str(response[0])
|
||||
|
||||
return result
|
||||
@@ -0,0 +1,50 @@
|
||||
import cv2
|
||||
import numpy as np
|
||||
import modules.config
|
||||
|
||||
|
||||
faceRestoreHelper = None
|
||||
|
||||
|
||||
def align_warp_face(self, landmark, border_mode='constant'):
|
||||
affine_matrix = cv2.estimateAffinePartial2D(landmark, self.face_template, method=cv2.LMEDS)[0]
|
||||
self.affine_matrices.append(affine_matrix)
|
||||
if border_mode == 'constant':
|
||||
border_mode = cv2.BORDER_CONSTANT
|
||||
elif border_mode == 'reflect101':
|
||||
border_mode = cv2.BORDER_REFLECT101
|
||||
elif border_mode == 'reflect':
|
||||
border_mode = cv2.BORDER_REFLECT
|
||||
input_img = self.input_img
|
||||
cropped_face = cv2.warpAffine(input_img, affine_matrix, self.face_size,
|
||||
borderMode=border_mode, borderValue=(135, 133, 132))
|
||||
return cropped_face
|
||||
|
||||
|
||||
def crop_image(img_rgb):
|
||||
global faceRestoreHelper
|
||||
|
||||
if faceRestoreHelper is None:
|
||||
from extras.facexlib.utils.face_restoration_helper import FaceRestoreHelper
|
||||
faceRestoreHelper = FaceRestoreHelper(
|
||||
upscale_factor=1,
|
||||
model_rootpath=modules.config.path_controlnet,
|
||||
device='cpu' # use cpu is safer since we are out of memory management
|
||||
)
|
||||
|
||||
faceRestoreHelper.clean_all()
|
||||
faceRestoreHelper.read_image(np.ascontiguousarray(img_rgb[:, :, ::-1].copy()))
|
||||
faceRestoreHelper.get_face_landmarks_5()
|
||||
|
||||
landmarks = faceRestoreHelper.all_landmarks_5
|
||||
# landmarks are already sorted with confidence.
|
||||
|
||||
if len(landmarks) == 0:
|
||||
print('No face detected')
|
||||
return img_rgb
|
||||
else:
|
||||
print(f'Detected {len(landmarks)} faces')
|
||||
|
||||
result = align_warp_face(faceRestoreHelper, landmarks[0])
|
||||
|
||||
return np.ascontiguousarray(result[:, :, ::-1].copy())
|
||||
@@ -0,0 +1,31 @@
|
||||
import torch
|
||||
from copy import deepcopy
|
||||
|
||||
from extras.facexlib.utils import load_file_from_url
|
||||
from .retinaface import RetinaFace
|
||||
|
||||
|
||||
def init_detection_model(model_name, half=False, device='cuda', model_rootpath=None):
|
||||
if model_name == 'retinaface_resnet50':
|
||||
model = RetinaFace(network_name='resnet50', half=half, device=device)
|
||||
model_url = 'https://github.com/xinntao/facexlib/releases/download/v0.1.0/detection_Resnet50_Final.pth'
|
||||
elif model_name == 'retinaface_mobile0.25':
|
||||
model = RetinaFace(network_name='mobile0.25', half=half, device=device)
|
||||
model_url = 'https://github.com/xinntao/facexlib/releases/download/v0.1.0/detection_mobilenet0.25_Final.pth'
|
||||
else:
|
||||
raise NotImplementedError(f'{model_name} is not implemented.')
|
||||
|
||||
model_path = load_file_from_url(
|
||||
url=model_url, model_dir='facexlib/weights', progress=True, file_name=None, save_dir=model_rootpath)
|
||||
|
||||
# TODO: clean pretrained model
|
||||
load_net = torch.load(model_path, map_location=lambda storage, loc: storage, weights_only=True)
|
||||
# remove unnecessary 'module.'
|
||||
for k, v in deepcopy(load_net).items():
|
||||
if k.startswith('module.'):
|
||||
load_net[k[7:]] = v
|
||||
load_net.pop(k)
|
||||
model.load_state_dict(load_net, strict=True)
|
||||
model.eval()
|
||||
model = model.to(device)
|
||||
return model
|
||||
@@ -0,0 +1,219 @@
|
||||
import cv2
|
||||
import numpy as np
|
||||
|
||||
from .matlab_cp2tform import get_similarity_transform_for_cv2
|
||||
|
||||
# reference facial points, a list of coordinates (x,y)
|
||||
REFERENCE_FACIAL_POINTS = [[30.29459953, 51.69630051], [65.53179932, 51.50139999], [48.02519989, 71.73660278],
|
||||
[33.54930115, 92.3655014], [62.72990036, 92.20410156]]
|
||||
|
||||
DEFAULT_CROP_SIZE = (96, 112)
|
||||
|
||||
|
||||
class FaceWarpException(Exception):
|
||||
|
||||
def __str__(self):
|
||||
return 'In File {}:{}'.format(__file__, super.__str__(self))
|
||||
|
||||
|
||||
def get_reference_facial_points(output_size=None, inner_padding_factor=0.0, outer_padding=(0, 0), default_square=False):
|
||||
"""
|
||||
Function:
|
||||
----------
|
||||
get reference 5 key points according to crop settings:
|
||||
0. Set default crop_size:
|
||||
if default_square:
|
||||
crop_size = (112, 112)
|
||||
else:
|
||||
crop_size = (96, 112)
|
||||
1. Pad the crop_size by inner_padding_factor in each side;
|
||||
2. Resize crop_size into (output_size - outer_padding*2),
|
||||
pad into output_size with outer_padding;
|
||||
3. Output reference_5point;
|
||||
Parameters:
|
||||
----------
|
||||
@output_size: (w, h) or None
|
||||
size of aligned face image
|
||||
@inner_padding_factor: (w_factor, h_factor)
|
||||
padding factor for inner (w, h)
|
||||
@outer_padding: (w_pad, h_pad)
|
||||
each row is a pair of coordinates (x, y)
|
||||
@default_square: True or False
|
||||
if True:
|
||||
default crop_size = (112, 112)
|
||||
else:
|
||||
default crop_size = (96, 112);
|
||||
!!! make sure, if output_size is not None:
|
||||
(output_size - outer_padding)
|
||||
= some_scale * (default crop_size * (1.0 +
|
||||
inner_padding_factor))
|
||||
Returns:
|
||||
----------
|
||||
@reference_5point: 5x2 np.array
|
||||
each row is a pair of transformed coordinates (x, y)
|
||||
"""
|
||||
|
||||
tmp_5pts = np.array(REFERENCE_FACIAL_POINTS)
|
||||
tmp_crop_size = np.array(DEFAULT_CROP_SIZE)
|
||||
|
||||
# 0) make the inner region a square
|
||||
if default_square:
|
||||
size_diff = max(tmp_crop_size) - tmp_crop_size
|
||||
tmp_5pts += size_diff / 2
|
||||
tmp_crop_size += size_diff
|
||||
|
||||
if (output_size and output_size[0] == tmp_crop_size[0] and output_size[1] == tmp_crop_size[1]):
|
||||
|
||||
return tmp_5pts
|
||||
|
||||
if (inner_padding_factor == 0 and outer_padding == (0, 0)):
|
||||
if output_size is None:
|
||||
return tmp_5pts
|
||||
else:
|
||||
raise FaceWarpException('No paddings to do, output_size must be None or {}'.format(tmp_crop_size))
|
||||
|
||||
# check output size
|
||||
if not (0 <= inner_padding_factor <= 1.0):
|
||||
raise FaceWarpException('Not (0 <= inner_padding_factor <= 1.0)')
|
||||
|
||||
if ((inner_padding_factor > 0 or outer_padding[0] > 0 or outer_padding[1] > 0) and output_size is None):
|
||||
output_size = tmp_crop_size * \
|
||||
(1 + inner_padding_factor * 2).astype(np.int32)
|
||||
output_size += np.array(outer_padding)
|
||||
if not (outer_padding[0] < output_size[0] and outer_padding[1] < output_size[1]):
|
||||
raise FaceWarpException('Not (outer_padding[0] < output_size[0] and outer_padding[1] < output_size[1])')
|
||||
|
||||
# 1) pad the inner region according inner_padding_factor
|
||||
if inner_padding_factor > 0:
|
||||
size_diff = tmp_crop_size * inner_padding_factor * 2
|
||||
tmp_5pts += size_diff / 2
|
||||
tmp_crop_size += np.round(size_diff).astype(np.int32)
|
||||
|
||||
# 2) resize the padded inner region
|
||||
size_bf_outer_pad = np.array(output_size) - np.array(outer_padding) * 2
|
||||
|
||||
if size_bf_outer_pad[0] * tmp_crop_size[1] != size_bf_outer_pad[1] * tmp_crop_size[0]:
|
||||
raise FaceWarpException('Must have (output_size - outer_padding)'
|
||||
'= some_scale * (crop_size * (1.0 + inner_padding_factor)')
|
||||
|
||||
scale_factor = size_bf_outer_pad[0].astype(np.float32) / tmp_crop_size[0]
|
||||
tmp_5pts = tmp_5pts * scale_factor
|
||||
# size_diff = tmp_crop_size * (scale_factor - min(scale_factor))
|
||||
# tmp_5pts = tmp_5pts + size_diff / 2
|
||||
tmp_crop_size = size_bf_outer_pad
|
||||
|
||||
# 3) add outer_padding to make output_size
|
||||
reference_5point = tmp_5pts + np.array(outer_padding)
|
||||
tmp_crop_size = output_size
|
||||
|
||||
return reference_5point
|
||||
|
||||
|
||||
def get_affine_transform_matrix(src_pts, dst_pts):
|
||||
"""
|
||||
Function:
|
||||
----------
|
||||
get affine transform matrix 'tfm' from src_pts to dst_pts
|
||||
Parameters:
|
||||
----------
|
||||
@src_pts: Kx2 np.array
|
||||
source points matrix, each row is a pair of coordinates (x, y)
|
||||
@dst_pts: Kx2 np.array
|
||||
destination points matrix, each row is a pair of coordinates (x, y)
|
||||
Returns:
|
||||
----------
|
||||
@tfm: 2x3 np.array
|
||||
transform matrix from src_pts to dst_pts
|
||||
"""
|
||||
|
||||
tfm = np.float32([[1, 0, 0], [0, 1, 0]])
|
||||
n_pts = src_pts.shape[0]
|
||||
ones = np.ones((n_pts, 1), src_pts.dtype)
|
||||
src_pts_ = np.hstack([src_pts, ones])
|
||||
dst_pts_ = np.hstack([dst_pts, ones])
|
||||
|
||||
A, res, rank, s = np.linalg.lstsq(src_pts_, dst_pts_)
|
||||
|
||||
if rank == 3:
|
||||
tfm = np.float32([[A[0, 0], A[1, 0], A[2, 0]], [A[0, 1], A[1, 1], A[2, 1]]])
|
||||
elif rank == 2:
|
||||
tfm = np.float32([[A[0, 0], A[1, 0], 0], [A[0, 1], A[1, 1], 0]])
|
||||
|
||||
return tfm
|
||||
|
||||
|
||||
def warp_and_crop_face(src_img, facial_pts, reference_pts=None, crop_size=(96, 112), align_type='smilarity'):
|
||||
"""
|
||||
Function:
|
||||
----------
|
||||
apply affine transform 'trans' to uv
|
||||
Parameters:
|
||||
----------
|
||||
@src_img: 3x3 np.array
|
||||
input image
|
||||
@facial_pts: could be
|
||||
1)a list of K coordinates (x,y)
|
||||
or
|
||||
2) Kx2 or 2xK np.array
|
||||
each row or col is a pair of coordinates (x, y)
|
||||
@reference_pts: could be
|
||||
1) a list of K coordinates (x,y)
|
||||
or
|
||||
2) Kx2 or 2xK np.array
|
||||
each row or col is a pair of coordinates (x, y)
|
||||
or
|
||||
3) None
|
||||
if None, use default reference facial points
|
||||
@crop_size: (w, h)
|
||||
output face image size
|
||||
@align_type: transform type, could be one of
|
||||
1) 'similarity': use similarity transform
|
||||
2) 'cv2_affine': use the first 3 points to do affine transform,
|
||||
by calling cv2.getAffineTransform()
|
||||
3) 'affine': use all points to do affine transform
|
||||
Returns:
|
||||
----------
|
||||
@face_img: output face image with size (w, h) = @crop_size
|
||||
"""
|
||||
|
||||
if reference_pts is None:
|
||||
if crop_size[0] == 96 and crop_size[1] == 112:
|
||||
reference_pts = REFERENCE_FACIAL_POINTS
|
||||
else:
|
||||
default_square = False
|
||||
inner_padding_factor = 0
|
||||
outer_padding = (0, 0)
|
||||
output_size = crop_size
|
||||
|
||||
reference_pts = get_reference_facial_points(output_size, inner_padding_factor, outer_padding,
|
||||
default_square)
|
||||
|
||||
ref_pts = np.float32(reference_pts)
|
||||
ref_pts_shp = ref_pts.shape
|
||||
if max(ref_pts_shp) < 3 or min(ref_pts_shp) != 2:
|
||||
raise FaceWarpException('reference_pts.shape must be (K,2) or (2,K) and K>2')
|
||||
|
||||
if ref_pts_shp[0] == 2:
|
||||
ref_pts = ref_pts.T
|
||||
|
||||
src_pts = np.float32(facial_pts)
|
||||
src_pts_shp = src_pts.shape
|
||||
if max(src_pts_shp) < 3 or min(src_pts_shp) != 2:
|
||||
raise FaceWarpException('facial_pts.shape must be (K,2) or (2,K) and K>2')
|
||||
|
||||
if src_pts_shp[0] == 2:
|
||||
src_pts = src_pts.T
|
||||
|
||||
if src_pts.shape != ref_pts.shape:
|
||||
raise FaceWarpException('facial_pts and reference_pts must have the same shape')
|
||||
|
||||
if align_type == 'cv2_affine':
|
||||
tfm = cv2.getAffineTransform(src_pts[0:3], ref_pts[0:3])
|
||||
elif align_type == 'affine':
|
||||
tfm = get_affine_transform_matrix(src_pts, ref_pts)
|
||||
else:
|
||||
tfm = get_similarity_transform_for_cv2(src_pts, ref_pts)
|
||||
|
||||
face_img = cv2.warpAffine(src_img, tfm, (crop_size[0], crop_size[1]))
|
||||
|
||||
return face_img
|
||||
@@ -0,0 +1,317 @@
|
||||
import numpy as np
|
||||
from numpy.linalg import inv, lstsq
|
||||
from numpy.linalg import matrix_rank as rank
|
||||
from numpy.linalg import norm
|
||||
|
||||
|
||||
class MatlabCp2tormException(Exception):
|
||||
|
||||
def __str__(self):
|
||||
return 'In File {}:{}'.format(__file__, super.__str__(self))
|
||||
|
||||
|
||||
def tformfwd(trans, uv):
|
||||
"""
|
||||
Function:
|
||||
----------
|
||||
apply affine transform 'trans' to uv
|
||||
|
||||
Parameters:
|
||||
----------
|
||||
@trans: 3x3 np.array
|
||||
transform matrix
|
||||
@uv: Kx2 np.array
|
||||
each row is a pair of coordinates (x, y)
|
||||
|
||||
Returns:
|
||||
----------
|
||||
@xy: Kx2 np.array
|
||||
each row is a pair of transformed coordinates (x, y)
|
||||
"""
|
||||
uv = np.hstack((uv, np.ones((uv.shape[0], 1))))
|
||||
xy = np.dot(uv, trans)
|
||||
xy = xy[:, 0:-1]
|
||||
return xy
|
||||
|
||||
|
||||
def tforminv(trans, uv):
|
||||
"""
|
||||
Function:
|
||||
----------
|
||||
apply the inverse of affine transform 'trans' to uv
|
||||
|
||||
Parameters:
|
||||
----------
|
||||
@trans: 3x3 np.array
|
||||
transform matrix
|
||||
@uv: Kx2 np.array
|
||||
each row is a pair of coordinates (x, y)
|
||||
|
||||
Returns:
|
||||
----------
|
||||
@xy: Kx2 np.array
|
||||
each row is a pair of inverse-transformed coordinates (x, y)
|
||||
"""
|
||||
Tinv = inv(trans)
|
||||
xy = tformfwd(Tinv, uv)
|
||||
return xy
|
||||
|
||||
|
||||
def findNonreflectiveSimilarity(uv, xy, options=None):
|
||||
options = {'K': 2}
|
||||
|
||||
K = options['K']
|
||||
M = xy.shape[0]
|
||||
x = xy[:, 0].reshape((-1, 1)) # use reshape to keep a column vector
|
||||
y = xy[:, 1].reshape((-1, 1)) # use reshape to keep a column vector
|
||||
|
||||
tmp1 = np.hstack((x, y, np.ones((M, 1)), np.zeros((M, 1))))
|
||||
tmp2 = np.hstack((y, -x, np.zeros((M, 1)), np.ones((M, 1))))
|
||||
X = np.vstack((tmp1, tmp2))
|
||||
|
||||
u = uv[:, 0].reshape((-1, 1)) # use reshape to keep a column vector
|
||||
v = uv[:, 1].reshape((-1, 1)) # use reshape to keep a column vector
|
||||
U = np.vstack((u, v))
|
||||
|
||||
# We know that X * r = U
|
||||
if rank(X) >= 2 * K:
|
||||
r, _, _, _ = lstsq(X, U, rcond=-1)
|
||||
r = np.squeeze(r)
|
||||
else:
|
||||
raise Exception('cp2tform:twoUniquePointsReq')
|
||||
sc = r[0]
|
||||
ss = r[1]
|
||||
tx = r[2]
|
||||
ty = r[3]
|
||||
|
||||
Tinv = np.array([[sc, -ss, 0], [ss, sc, 0], [tx, ty, 1]])
|
||||
T = inv(Tinv)
|
||||
T[:, 2] = np.array([0, 0, 1])
|
||||
|
||||
return T, Tinv
|
||||
|
||||
|
||||
def findSimilarity(uv, xy, options=None):
|
||||
options = {'K': 2}
|
||||
|
||||
# uv = np.array(uv)
|
||||
# xy = np.array(xy)
|
||||
|
||||
# Solve for trans1
|
||||
trans1, trans1_inv = findNonreflectiveSimilarity(uv, xy, options)
|
||||
|
||||
# Solve for trans2
|
||||
|
||||
# manually reflect the xy data across the Y-axis
|
||||
xyR = xy
|
||||
xyR[:, 0] = -1 * xyR[:, 0]
|
||||
|
||||
trans2r, trans2r_inv = findNonreflectiveSimilarity(uv, xyR, options)
|
||||
|
||||
# manually reflect the tform to undo the reflection done on xyR
|
||||
TreflectY = np.array([[-1, 0, 0], [0, 1, 0], [0, 0, 1]])
|
||||
|
||||
trans2 = np.dot(trans2r, TreflectY)
|
||||
|
||||
# Figure out if trans1 or trans2 is better
|
||||
xy1 = tformfwd(trans1, uv)
|
||||
norm1 = norm(xy1 - xy)
|
||||
|
||||
xy2 = tformfwd(trans2, uv)
|
||||
norm2 = norm(xy2 - xy)
|
||||
|
||||
if norm1 <= norm2:
|
||||
return trans1, trans1_inv
|
||||
else:
|
||||
trans2_inv = inv(trans2)
|
||||
return trans2, trans2_inv
|
||||
|
||||
|
||||
def get_similarity_transform(src_pts, dst_pts, reflective=True):
|
||||
"""
|
||||
Function:
|
||||
----------
|
||||
Find Similarity Transform Matrix 'trans':
|
||||
u = src_pts[:, 0]
|
||||
v = src_pts[:, 1]
|
||||
x = dst_pts[:, 0]
|
||||
y = dst_pts[:, 1]
|
||||
[x, y, 1] = [u, v, 1] * trans
|
||||
|
||||
Parameters:
|
||||
----------
|
||||
@src_pts: Kx2 np.array
|
||||
source points, each row is a pair of coordinates (x, y)
|
||||
@dst_pts: Kx2 np.array
|
||||
destination points, each row is a pair of transformed
|
||||
coordinates (x, y)
|
||||
@reflective: True or False
|
||||
if True:
|
||||
use reflective similarity transform
|
||||
else:
|
||||
use non-reflective similarity transform
|
||||
|
||||
Returns:
|
||||
----------
|
||||
@trans: 3x3 np.array
|
||||
transform matrix from uv to xy
|
||||
trans_inv: 3x3 np.array
|
||||
inverse of trans, transform matrix from xy to uv
|
||||
"""
|
||||
|
||||
if reflective:
|
||||
trans, trans_inv = findSimilarity(src_pts, dst_pts)
|
||||
else:
|
||||
trans, trans_inv = findNonreflectiveSimilarity(src_pts, dst_pts)
|
||||
|
||||
return trans, trans_inv
|
||||
|
||||
|
||||
def cvt_tform_mat_for_cv2(trans):
|
||||
"""
|
||||
Function:
|
||||
----------
|
||||
Convert Transform Matrix 'trans' into 'cv2_trans' which could be
|
||||
directly used by cv2.warpAffine():
|
||||
u = src_pts[:, 0]
|
||||
v = src_pts[:, 1]
|
||||
x = dst_pts[:, 0]
|
||||
y = dst_pts[:, 1]
|
||||
[x, y].T = cv_trans * [u, v, 1].T
|
||||
|
||||
Parameters:
|
||||
----------
|
||||
@trans: 3x3 np.array
|
||||
transform matrix from uv to xy
|
||||
|
||||
Returns:
|
||||
----------
|
||||
@cv2_trans: 2x3 np.array
|
||||
transform matrix from src_pts to dst_pts, could be directly used
|
||||
for cv2.warpAffine()
|
||||
"""
|
||||
cv2_trans = trans[:, 0:2].T
|
||||
|
||||
return cv2_trans
|
||||
|
||||
|
||||
def get_similarity_transform_for_cv2(src_pts, dst_pts, reflective=True):
|
||||
"""
|
||||
Function:
|
||||
----------
|
||||
Find Similarity Transform Matrix 'cv2_trans' which could be
|
||||
directly used by cv2.warpAffine():
|
||||
u = src_pts[:, 0]
|
||||
v = src_pts[:, 1]
|
||||
x = dst_pts[:, 0]
|
||||
y = dst_pts[:, 1]
|
||||
[x, y].T = cv_trans * [u, v, 1].T
|
||||
|
||||
Parameters:
|
||||
----------
|
||||
@src_pts: Kx2 np.array
|
||||
source points, each row is a pair of coordinates (x, y)
|
||||
@dst_pts: Kx2 np.array
|
||||
destination points, each row is a pair of transformed
|
||||
coordinates (x, y)
|
||||
reflective: True or False
|
||||
if True:
|
||||
use reflective similarity transform
|
||||
else:
|
||||
use non-reflective similarity transform
|
||||
|
||||
Returns:
|
||||
----------
|
||||
@cv2_trans: 2x3 np.array
|
||||
transform matrix from src_pts to dst_pts, could be directly used
|
||||
for cv2.warpAffine()
|
||||
"""
|
||||
trans, trans_inv = get_similarity_transform(src_pts, dst_pts, reflective)
|
||||
cv2_trans = cvt_tform_mat_for_cv2(trans)
|
||||
|
||||
return cv2_trans
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
"""
|
||||
u = [0, 6, -2]
|
||||
v = [0, 3, 5]
|
||||
x = [-1, 0, 4]
|
||||
y = [-1, -10, 4]
|
||||
|
||||
# In Matlab, run:
|
||||
#
|
||||
# uv = [u'; v'];
|
||||
# xy = [x'; y'];
|
||||
# tform_sim=cp2tform(uv,xy,'similarity');
|
||||
#
|
||||
# trans = tform_sim.tdata.T
|
||||
# ans =
|
||||
# -0.0764 -1.6190 0
|
||||
# 1.6190 -0.0764 0
|
||||
# -3.2156 0.0290 1.0000
|
||||
# trans_inv = tform_sim.tdata.Tinv
|
||||
# ans =
|
||||
#
|
||||
# -0.0291 0.6163 0
|
||||
# -0.6163 -0.0291 0
|
||||
# -0.0756 1.9826 1.0000
|
||||
# xy_m=tformfwd(tform_sim, u,v)
|
||||
#
|
||||
# xy_m =
|
||||
#
|
||||
# -3.2156 0.0290
|
||||
# 1.1833 -9.9143
|
||||
# 5.0323 2.8853
|
||||
# uv_m=tforminv(tform_sim, x,y)
|
||||
#
|
||||
# uv_m =
|
||||
#
|
||||
# 0.5698 1.3953
|
||||
# 6.0872 2.2733
|
||||
# -2.6570 4.3314
|
||||
"""
|
||||
u = [0, 6, -2]
|
||||
v = [0, 3, 5]
|
||||
x = [-1, 0, 4]
|
||||
y = [-1, -10, 4]
|
||||
|
||||
uv = np.array((u, v)).T
|
||||
xy = np.array((x, y)).T
|
||||
|
||||
print('\n--->uv:')
|
||||
print(uv)
|
||||
print('\n--->xy:')
|
||||
print(xy)
|
||||
|
||||
trans, trans_inv = get_similarity_transform(uv, xy)
|
||||
|
||||
print('\n--->trans matrix:')
|
||||
print(trans)
|
||||
|
||||
print('\n--->trans_inv matrix:')
|
||||
print(trans_inv)
|
||||
|
||||
print('\n---> apply transform to uv')
|
||||
print('\nxy_m = uv_augmented * trans')
|
||||
uv_aug = np.hstack((uv, np.ones((uv.shape[0], 1))))
|
||||
xy_m = np.dot(uv_aug, trans)
|
||||
print(xy_m)
|
||||
|
||||
print('\nxy_m = tformfwd(trans, uv)')
|
||||
xy_m = tformfwd(trans, uv)
|
||||
print(xy_m)
|
||||
|
||||
print('\n---> apply inverse transform to xy')
|
||||
print('\nuv_m = xy_augmented * trans_inv')
|
||||
xy_aug = np.hstack((xy, np.ones((xy.shape[0], 1))))
|
||||
uv_m = np.dot(xy_aug, trans_inv)
|
||||
print(uv_m)
|
||||
|
||||
print('\nuv_m = tformfwd(trans_inv, xy)')
|
||||
uv_m = tformfwd(trans_inv, xy)
|
||||
print(uv_m)
|
||||
|
||||
uv_m = tforminv(trans, xy)
|
||||
print('\nuv_m = tforminv(trans, xy)')
|
||||
print(uv_m)
|
||||
@@ -0,0 +1,366 @@
|
||||
import cv2
|
||||
import numpy as np
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
from PIL import Image
|
||||
from torchvision.models._utils import IntermediateLayerGetter as IntermediateLayerGetter
|
||||
|
||||
from extras.facexlib.detection.align_trans import get_reference_facial_points, warp_and_crop_face
|
||||
from extras.facexlib.detection.retinaface_net import FPN, SSH, MobileNetV1, make_bbox_head, make_class_head, make_landmark_head
|
||||
from extras.facexlib.detection.retinaface_utils import (PriorBox, batched_decode, batched_decode_landm, decode, decode_landm,
|
||||
py_cpu_nms)
|
||||
|
||||
|
||||
def generate_config(network_name):
|
||||
|
||||
cfg_mnet = {
|
||||
'name': 'mobilenet0.25',
|
||||
'min_sizes': [[16, 32], [64, 128], [256, 512]],
|
||||
'steps': [8, 16, 32],
|
||||
'variance': [0.1, 0.2],
|
||||
'clip': False,
|
||||
'loc_weight': 2.0,
|
||||
'gpu_train': True,
|
||||
'batch_size': 32,
|
||||
'ngpu': 1,
|
||||
'epoch': 250,
|
||||
'decay1': 190,
|
||||
'decay2': 220,
|
||||
'image_size': 640,
|
||||
'return_layers': {
|
||||
'stage1': 1,
|
||||
'stage2': 2,
|
||||
'stage3': 3
|
||||
},
|
||||
'in_channel': 32,
|
||||
'out_channel': 64
|
||||
}
|
||||
|
||||
cfg_re50 = {
|
||||
'name': 'Resnet50',
|
||||
'min_sizes': [[16, 32], [64, 128], [256, 512]],
|
||||
'steps': [8, 16, 32],
|
||||
'variance': [0.1, 0.2],
|
||||
'clip': False,
|
||||
'loc_weight': 2.0,
|
||||
'gpu_train': True,
|
||||
'batch_size': 24,
|
||||
'ngpu': 4,
|
||||
'epoch': 100,
|
||||
'decay1': 70,
|
||||
'decay2': 90,
|
||||
'image_size': 840,
|
||||
'return_layers': {
|
||||
'layer2': 1,
|
||||
'layer3': 2,
|
||||
'layer4': 3
|
||||
},
|
||||
'in_channel': 256,
|
||||
'out_channel': 256
|
||||
}
|
||||
|
||||
if network_name == 'mobile0.25':
|
||||
return cfg_mnet
|
||||
elif network_name == 'resnet50':
|
||||
return cfg_re50
|
||||
else:
|
||||
raise NotImplementedError(f'network_name={network_name}')
|
||||
|
||||
|
||||
class RetinaFace(nn.Module):
|
||||
|
||||
def __init__(self, network_name='resnet50', half=False, phase='test', device=None):
|
||||
self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') if device is None else device
|
||||
|
||||
super(RetinaFace, self).__init__()
|
||||
self.half_inference = half
|
||||
cfg = generate_config(network_name)
|
||||
self.backbone = cfg['name']
|
||||
|
||||
self.model_name = f'retinaface_{network_name}'
|
||||
self.cfg = cfg
|
||||
self.phase = phase
|
||||
self.target_size, self.max_size = 1600, 2150
|
||||
self.resize, self.scale, self.scale1 = 1., None, None
|
||||
self.mean_tensor = torch.tensor([[[[104.]], [[117.]], [[123.]]]], device=self.device)
|
||||
self.reference = get_reference_facial_points(default_square=True)
|
||||
# Build network.
|
||||
backbone = None
|
||||
if cfg['name'] == 'mobilenet0.25':
|
||||
backbone = MobileNetV1()
|
||||
self.body = IntermediateLayerGetter(backbone, cfg['return_layers'])
|
||||
elif cfg['name'] == 'Resnet50':
|
||||
import torchvision.models as models
|
||||
backbone = models.resnet50(weights=None)
|
||||
self.body = IntermediateLayerGetter(backbone, cfg['return_layers'])
|
||||
|
||||
in_channels_stage2 = cfg['in_channel']
|
||||
in_channels_list = [
|
||||
in_channels_stage2 * 2,
|
||||
in_channels_stage2 * 4,
|
||||
in_channels_stage2 * 8,
|
||||
]
|
||||
|
||||
out_channels = cfg['out_channel']
|
||||
self.fpn = FPN(in_channels_list, out_channels)
|
||||
self.ssh1 = SSH(out_channels, out_channels)
|
||||
self.ssh2 = SSH(out_channels, out_channels)
|
||||
self.ssh3 = SSH(out_channels, out_channels)
|
||||
|
||||
self.ClassHead = make_class_head(fpn_num=3, inchannels=cfg['out_channel'])
|
||||
self.BboxHead = make_bbox_head(fpn_num=3, inchannels=cfg['out_channel'])
|
||||
self.LandmarkHead = make_landmark_head(fpn_num=3, inchannels=cfg['out_channel'])
|
||||
|
||||
self.to(self.device)
|
||||
self.eval()
|
||||
if self.half_inference:
|
||||
self.half()
|
||||
|
||||
def forward(self, inputs):
|
||||
out = self.body(inputs)
|
||||
|
||||
if self.backbone == 'mobilenet0.25' or self.backbone == 'Resnet50':
|
||||
out = list(out.values())
|
||||
# FPN
|
||||
fpn = self.fpn(out)
|
||||
|
||||
# SSH
|
||||
feature1 = self.ssh1(fpn[0])
|
||||
feature2 = self.ssh2(fpn[1])
|
||||
feature3 = self.ssh3(fpn[2])
|
||||
features = [feature1, feature2, feature3]
|
||||
|
||||
bbox_regressions = torch.cat([self.BboxHead[i](feature) for i, feature in enumerate(features)], dim=1)
|
||||
classifications = torch.cat([self.ClassHead[i](feature) for i, feature in enumerate(features)], dim=1)
|
||||
tmp = [self.LandmarkHead[i](feature) for i, feature in enumerate(features)]
|
||||
ldm_regressions = (torch.cat(tmp, dim=1))
|
||||
|
||||
if self.phase == 'train':
|
||||
output = (bbox_regressions, classifications, ldm_regressions)
|
||||
else:
|
||||
output = (bbox_regressions, F.softmax(classifications, dim=-1), ldm_regressions)
|
||||
return output
|
||||
|
||||
def __detect_faces(self, inputs):
|
||||
# get scale
|
||||
height, width = inputs.shape[2:]
|
||||
self.scale = torch.tensor([width, height, width, height], dtype=torch.float32, device=self.device)
|
||||
tmp = [width, height, width, height, width, height, width, height, width, height]
|
||||
self.scale1 = torch.tensor(tmp, dtype=torch.float32, device=self.device)
|
||||
|
||||
# forawrd
|
||||
inputs = inputs.to(self.device)
|
||||
if self.half_inference:
|
||||
inputs = inputs.half()
|
||||
loc, conf, landmarks = self(inputs)
|
||||
|
||||
# get priorbox
|
||||
priorbox = PriorBox(self.cfg, image_size=inputs.shape[2:])
|
||||
priors = priorbox.forward().to(self.device)
|
||||
|
||||
return loc, conf, landmarks, priors
|
||||
|
||||
# single image detection
|
||||
def transform(self, image, use_origin_size):
|
||||
# convert to opencv format
|
||||
if isinstance(image, Image.Image):
|
||||
image = cv2.cvtColor(np.asarray(image), cv2.COLOR_RGB2BGR)
|
||||
image = image.astype(np.float32)
|
||||
|
||||
# testing scale
|
||||
im_size_min = np.min(image.shape[0:2])
|
||||
im_size_max = np.max(image.shape[0:2])
|
||||
resize = float(self.target_size) / float(im_size_min)
|
||||
|
||||
# prevent bigger axis from being more than max_size
|
||||
if np.round(resize * im_size_max) > self.max_size:
|
||||
resize = float(self.max_size) / float(im_size_max)
|
||||
resize = 1 if use_origin_size else resize
|
||||
|
||||
# resize
|
||||
if resize != 1:
|
||||
image = cv2.resize(image, None, None, fx=resize, fy=resize, interpolation=cv2.INTER_LINEAR)
|
||||
|
||||
# convert to torch.tensor format
|
||||
# image -= (104, 117, 123)
|
||||
image = image.transpose(2, 0, 1)
|
||||
image = torch.from_numpy(image).unsqueeze(0)
|
||||
|
||||
return image, resize
|
||||
|
||||
def detect_faces(
|
||||
self,
|
||||
image,
|
||||
conf_threshold=0.8,
|
||||
nms_threshold=0.4,
|
||||
use_origin_size=True,
|
||||
):
|
||||
image, self.resize = self.transform(image, use_origin_size)
|
||||
image = image.to(self.device)
|
||||
if self.half_inference:
|
||||
image = image.half()
|
||||
image = image - self.mean_tensor
|
||||
|
||||
loc, conf, landmarks, priors = self.__detect_faces(image)
|
||||
|
||||
boxes = decode(loc.data.squeeze(0), priors.data, self.cfg['variance'])
|
||||
boxes = boxes * self.scale / self.resize
|
||||
boxes = boxes.cpu().numpy()
|
||||
|
||||
scores = conf.squeeze(0).data.cpu().numpy()[:, 1]
|
||||
|
||||
landmarks = decode_landm(landmarks.squeeze(0), priors, self.cfg['variance'])
|
||||
landmarks = landmarks * self.scale1 / self.resize
|
||||
landmarks = landmarks.cpu().numpy()
|
||||
|
||||
# ignore low scores
|
||||
inds = np.where(scores > conf_threshold)[0]
|
||||
boxes, landmarks, scores = boxes[inds], landmarks[inds], scores[inds]
|
||||
|
||||
# sort
|
||||
order = scores.argsort()[::-1]
|
||||
boxes, landmarks, scores = boxes[order], landmarks[order], scores[order]
|
||||
|
||||
# do NMS
|
||||
bounding_boxes = np.hstack((boxes, scores[:, np.newaxis])).astype(np.float32, copy=False)
|
||||
keep = py_cpu_nms(bounding_boxes, nms_threshold)
|
||||
bounding_boxes, landmarks = bounding_boxes[keep, :], landmarks[keep]
|
||||
# self.t['forward_pass'].toc()
|
||||
# print(self.t['forward_pass'].average_time)
|
||||
# import sys
|
||||
# sys.stdout.flush()
|
||||
return np.concatenate((bounding_boxes, landmarks), axis=1)
|
||||
|
||||
def __align_multi(self, image, boxes, landmarks, limit=None):
|
||||
|
||||
if len(boxes) < 1:
|
||||
return [], []
|
||||
|
||||
if limit:
|
||||
boxes = boxes[:limit]
|
||||
landmarks = landmarks[:limit]
|
||||
|
||||
faces = []
|
||||
for landmark in landmarks:
|
||||
facial5points = [[landmark[2 * j], landmark[2 * j + 1]] for j in range(5)]
|
||||
|
||||
warped_face = warp_and_crop_face(np.array(image), facial5points, self.reference, crop_size=(112, 112))
|
||||
faces.append(warped_face)
|
||||
|
||||
return np.concatenate((boxes, landmarks), axis=1), faces
|
||||
|
||||
def align_multi(self, img, conf_threshold=0.8, limit=None):
|
||||
|
||||
rlt = self.detect_faces(img, conf_threshold=conf_threshold)
|
||||
boxes, landmarks = rlt[:, 0:5], rlt[:, 5:]
|
||||
|
||||
return self.__align_multi(img, boxes, landmarks, limit)
|
||||
|
||||
# batched detection
|
||||
def batched_transform(self, frames, use_origin_size):
|
||||
"""
|
||||
Arguments:
|
||||
frames: a list of PIL.Image, or torch.Tensor(shape=[n, h, w, c],
|
||||
type=np.float32, BGR format).
|
||||
use_origin_size: whether to use origin size.
|
||||
"""
|
||||
from_PIL = True if isinstance(frames[0], Image.Image) else False
|
||||
|
||||
# convert to opencv format
|
||||
if from_PIL:
|
||||
frames = [cv2.cvtColor(np.asarray(frame), cv2.COLOR_RGB2BGR) for frame in frames]
|
||||
frames = np.asarray(frames, dtype=np.float32)
|
||||
|
||||
# testing scale
|
||||
im_size_min = np.min(frames[0].shape[0:2])
|
||||
im_size_max = np.max(frames[0].shape[0:2])
|
||||
resize = float(self.target_size) / float(im_size_min)
|
||||
|
||||
# prevent bigger axis from being more than max_size
|
||||
if np.round(resize * im_size_max) > self.max_size:
|
||||
resize = float(self.max_size) / float(im_size_max)
|
||||
resize = 1 if use_origin_size else resize
|
||||
|
||||
# resize
|
||||
if resize != 1:
|
||||
if not from_PIL:
|
||||
frames = F.interpolate(frames, scale_factor=resize)
|
||||
else:
|
||||
frames = [
|
||||
cv2.resize(frame, None, None, fx=resize, fy=resize, interpolation=cv2.INTER_LINEAR)
|
||||
for frame in frames
|
||||
]
|
||||
|
||||
# convert to torch.tensor format
|
||||
if not from_PIL:
|
||||
frames = frames.transpose(1, 2).transpose(1, 3).contiguous()
|
||||
else:
|
||||
frames = frames.transpose((0, 3, 1, 2))
|
||||
frames = torch.from_numpy(frames)
|
||||
|
||||
return frames, resize
|
||||
|
||||
def batched_detect_faces(self, frames, conf_threshold=0.8, nms_threshold=0.4, use_origin_size=True):
|
||||
"""
|
||||
Arguments:
|
||||
frames: a list of PIL.Image, or np.array(shape=[n, h, w, c],
|
||||
type=np.uint8, BGR format).
|
||||
conf_threshold: confidence threshold.
|
||||
nms_threshold: nms threshold.
|
||||
use_origin_size: whether to use origin size.
|
||||
Returns:
|
||||
final_bounding_boxes: list of np.array ([n_boxes, 5],
|
||||
type=np.float32).
|
||||
final_landmarks: list of np.array ([n_boxes, 10], type=np.float32).
|
||||
"""
|
||||
# self.t['forward_pass'].tic()
|
||||
frames, self.resize = self.batched_transform(frames, use_origin_size)
|
||||
frames = frames.to(self.device)
|
||||
frames = frames - self.mean_tensor
|
||||
|
||||
b_loc, b_conf, b_landmarks, priors = self.__detect_faces(frames)
|
||||
|
||||
final_bounding_boxes, final_landmarks = [], []
|
||||
|
||||
# decode
|
||||
priors = priors.unsqueeze(0)
|
||||
b_loc = batched_decode(b_loc, priors, self.cfg['variance']) * self.scale / self.resize
|
||||
b_landmarks = batched_decode_landm(b_landmarks, priors, self.cfg['variance']) * self.scale1 / self.resize
|
||||
b_conf = b_conf[:, :, 1]
|
||||
|
||||
# index for selection
|
||||
b_indice = b_conf > conf_threshold
|
||||
|
||||
# concat
|
||||
b_loc_and_conf = torch.cat((b_loc, b_conf.unsqueeze(-1)), dim=2).float()
|
||||
|
||||
for pred, landm, inds in zip(b_loc_and_conf, b_landmarks, b_indice):
|
||||
|
||||
# ignore low scores
|
||||
pred, landm = pred[inds, :], landm[inds, :]
|
||||
if pred.shape[0] == 0:
|
||||
final_bounding_boxes.append(np.array([], dtype=np.float32))
|
||||
final_landmarks.append(np.array([], dtype=np.float32))
|
||||
continue
|
||||
|
||||
# sort
|
||||
# order = score.argsort(descending=True)
|
||||
# box, landm, score = box[order], landm[order], score[order]
|
||||
|
||||
# to CPU
|
||||
bounding_boxes, landm = pred.cpu().numpy(), landm.cpu().numpy()
|
||||
|
||||
# NMS
|
||||
keep = py_cpu_nms(bounding_boxes, nms_threshold)
|
||||
bounding_boxes, landmarks = bounding_boxes[keep, :], landm[keep]
|
||||
|
||||
# append
|
||||
final_bounding_boxes.append(bounding_boxes)
|
||||
final_landmarks.append(landmarks)
|
||||
# self.t['forward_pass'].toc(average=True)
|
||||
# self.batch_time += self.t['forward_pass'].diff
|
||||
# self.total_frame += len(frames)
|
||||
# print(self.batch_time / self.total_frame)
|
||||
|
||||
return final_bounding_boxes, final_landmarks
|
||||
@@ -0,0 +1,196 @@
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
|
||||
|
||||
def conv_bn(inp, oup, stride=1, leaky=0):
|
||||
return nn.Sequential(
|
||||
nn.Conv2d(inp, oup, 3, stride, 1, bias=False), nn.BatchNorm2d(oup),
|
||||
nn.LeakyReLU(negative_slope=leaky, inplace=True))
|
||||
|
||||
|
||||
def conv_bn_no_relu(inp, oup, stride):
|
||||
return nn.Sequential(
|
||||
nn.Conv2d(inp, oup, 3, stride, 1, bias=False),
|
||||
nn.BatchNorm2d(oup),
|
||||
)
|
||||
|
||||
|
||||
def conv_bn1X1(inp, oup, stride, leaky=0):
|
||||
return nn.Sequential(
|
||||
nn.Conv2d(inp, oup, 1, stride, padding=0, bias=False), nn.BatchNorm2d(oup),
|
||||
nn.LeakyReLU(negative_slope=leaky, inplace=True))
|
||||
|
||||
|
||||
def conv_dw(inp, oup, stride, leaky=0.1):
|
||||
return nn.Sequential(
|
||||
nn.Conv2d(inp, inp, 3, stride, 1, groups=inp, bias=False),
|
||||
nn.BatchNorm2d(inp),
|
||||
nn.LeakyReLU(negative_slope=leaky, inplace=True),
|
||||
nn.Conv2d(inp, oup, 1, 1, 0, bias=False),
|
||||
nn.BatchNorm2d(oup),
|
||||
nn.LeakyReLU(negative_slope=leaky, inplace=True),
|
||||
)
|
||||
|
||||
|
||||
class SSH(nn.Module):
|
||||
|
||||
def __init__(self, in_channel, out_channel):
|
||||
super(SSH, self).__init__()
|
||||
assert out_channel % 4 == 0
|
||||
leaky = 0
|
||||
if (out_channel <= 64):
|
||||
leaky = 0.1
|
||||
self.conv3X3 = conv_bn_no_relu(in_channel, out_channel // 2, stride=1)
|
||||
|
||||
self.conv5X5_1 = conv_bn(in_channel, out_channel // 4, stride=1, leaky=leaky)
|
||||
self.conv5X5_2 = conv_bn_no_relu(out_channel // 4, out_channel // 4, stride=1)
|
||||
|
||||
self.conv7X7_2 = conv_bn(out_channel // 4, out_channel // 4, stride=1, leaky=leaky)
|
||||
self.conv7x7_3 = conv_bn_no_relu(out_channel // 4, out_channel // 4, stride=1)
|
||||
|
||||
def forward(self, input):
|
||||
conv3X3 = self.conv3X3(input)
|
||||
|
||||
conv5X5_1 = self.conv5X5_1(input)
|
||||
conv5X5 = self.conv5X5_2(conv5X5_1)
|
||||
|
||||
conv7X7_2 = self.conv7X7_2(conv5X5_1)
|
||||
conv7X7 = self.conv7x7_3(conv7X7_2)
|
||||
|
||||
out = torch.cat([conv3X3, conv5X5, conv7X7], dim=1)
|
||||
out = F.relu(out)
|
||||
return out
|
||||
|
||||
|
||||
class FPN(nn.Module):
|
||||
|
||||
def __init__(self, in_channels_list, out_channels):
|
||||
super(FPN, self).__init__()
|
||||
leaky = 0
|
||||
if (out_channels <= 64):
|
||||
leaky = 0.1
|
||||
self.output1 = conv_bn1X1(in_channels_list[0], out_channels, stride=1, leaky=leaky)
|
||||
self.output2 = conv_bn1X1(in_channels_list[1], out_channels, stride=1, leaky=leaky)
|
||||
self.output3 = conv_bn1X1(in_channels_list[2], out_channels, stride=1, leaky=leaky)
|
||||
|
||||
self.merge1 = conv_bn(out_channels, out_channels, leaky=leaky)
|
||||
self.merge2 = conv_bn(out_channels, out_channels, leaky=leaky)
|
||||
|
||||
def forward(self, input):
|
||||
# names = list(input.keys())
|
||||
# input = list(input.values())
|
||||
|
||||
output1 = self.output1(input[0])
|
||||
output2 = self.output2(input[1])
|
||||
output3 = self.output3(input[2])
|
||||
|
||||
up3 = F.interpolate(output3, size=[output2.size(2), output2.size(3)], mode='nearest')
|
||||
output2 = output2 + up3
|
||||
output2 = self.merge2(output2)
|
||||
|
||||
up2 = F.interpolate(output2, size=[output1.size(2), output1.size(3)], mode='nearest')
|
||||
output1 = output1 + up2
|
||||
output1 = self.merge1(output1)
|
||||
|
||||
out = [output1, output2, output3]
|
||||
return out
|
||||
|
||||
|
||||
class MobileNetV1(nn.Module):
|
||||
|
||||
def __init__(self):
|
||||
super(MobileNetV1, self).__init__()
|
||||
self.stage1 = nn.Sequential(
|
||||
conv_bn(3, 8, 2, leaky=0.1), # 3
|
||||
conv_dw(8, 16, 1), # 7
|
||||
conv_dw(16, 32, 2), # 11
|
||||
conv_dw(32, 32, 1), # 19
|
||||
conv_dw(32, 64, 2), # 27
|
||||
conv_dw(64, 64, 1), # 43
|
||||
)
|
||||
self.stage2 = nn.Sequential(
|
||||
conv_dw(64, 128, 2), # 43 + 16 = 59
|
||||
conv_dw(128, 128, 1), # 59 + 32 = 91
|
||||
conv_dw(128, 128, 1), # 91 + 32 = 123
|
||||
conv_dw(128, 128, 1), # 123 + 32 = 155
|
||||
conv_dw(128, 128, 1), # 155 + 32 = 187
|
||||
conv_dw(128, 128, 1), # 187 + 32 = 219
|
||||
)
|
||||
self.stage3 = nn.Sequential(
|
||||
conv_dw(128, 256, 2), # 219 +3 2 = 241
|
||||
conv_dw(256, 256, 1), # 241 + 64 = 301
|
||||
)
|
||||
self.avg = nn.AdaptiveAvgPool2d((1, 1))
|
||||
self.fc = nn.Linear(256, 1000)
|
||||
|
||||
def forward(self, x):
|
||||
x = self.stage1(x)
|
||||
x = self.stage2(x)
|
||||
x = self.stage3(x)
|
||||
x = self.avg(x)
|
||||
# x = self.model(x)
|
||||
x = x.view(-1, 256)
|
||||
x = self.fc(x)
|
||||
return x
|
||||
|
||||
|
||||
class ClassHead(nn.Module):
|
||||
|
||||
def __init__(self, inchannels=512, num_anchors=3):
|
||||
super(ClassHead, self).__init__()
|
||||
self.num_anchors = num_anchors
|
||||
self.conv1x1 = nn.Conv2d(inchannels, self.num_anchors * 2, kernel_size=(1, 1), stride=1, padding=0)
|
||||
|
||||
def forward(self, x):
|
||||
out = self.conv1x1(x)
|
||||
out = out.permute(0, 2, 3, 1).contiguous()
|
||||
|
||||
return out.view(out.shape[0], -1, 2)
|
||||
|
||||
|
||||
class BboxHead(nn.Module):
|
||||
|
||||
def __init__(self, inchannels=512, num_anchors=3):
|
||||
super(BboxHead, self).__init__()
|
||||
self.conv1x1 = nn.Conv2d(inchannels, num_anchors * 4, kernel_size=(1, 1), stride=1, padding=0)
|
||||
|
||||
def forward(self, x):
|
||||
out = self.conv1x1(x)
|
||||
out = out.permute(0, 2, 3, 1).contiguous()
|
||||
|
||||
return out.view(out.shape[0], -1, 4)
|
||||
|
||||
|
||||
class LandmarkHead(nn.Module):
|
||||
|
||||
def __init__(self, inchannels=512, num_anchors=3):
|
||||
super(LandmarkHead, self).__init__()
|
||||
self.conv1x1 = nn.Conv2d(inchannels, num_anchors * 10, kernel_size=(1, 1), stride=1, padding=0)
|
||||
|
||||
def forward(self, x):
|
||||
out = self.conv1x1(x)
|
||||
out = out.permute(0, 2, 3, 1).contiguous()
|
||||
|
||||
return out.view(out.shape[0], -1, 10)
|
||||
|
||||
|
||||
def make_class_head(fpn_num=3, inchannels=64, anchor_num=2):
|
||||
classhead = nn.ModuleList()
|
||||
for i in range(fpn_num):
|
||||
classhead.append(ClassHead(inchannels, anchor_num))
|
||||
return classhead
|
||||
|
||||
|
||||
def make_bbox_head(fpn_num=3, inchannels=64, anchor_num=2):
|
||||
bboxhead = nn.ModuleList()
|
||||
for i in range(fpn_num):
|
||||
bboxhead.append(BboxHead(inchannels, anchor_num))
|
||||
return bboxhead
|
||||
|
||||
|
||||
def make_landmark_head(fpn_num=3, inchannels=64, anchor_num=2):
|
||||
landmarkhead = nn.ModuleList()
|
||||
for i in range(fpn_num):
|
||||
landmarkhead.append(LandmarkHead(inchannels, anchor_num))
|
||||
return landmarkhead
|
||||
@@ -0,0 +1,421 @@
|
||||
import numpy as np
|
||||
import torch
|
||||
import torchvision
|
||||
from itertools import product as product
|
||||
from math import ceil
|
||||
|
||||
|
||||
class PriorBox(object):
|
||||
|
||||
def __init__(self, cfg, image_size=None, phase='train'):
|
||||
super(PriorBox, self).__init__()
|
||||
self.min_sizes = cfg['min_sizes']
|
||||
self.steps = cfg['steps']
|
||||
self.clip = cfg['clip']
|
||||
self.image_size = image_size
|
||||
self.feature_maps = [[ceil(self.image_size[0] / step), ceil(self.image_size[1] / step)] for step in self.steps]
|
||||
self.name = 's'
|
||||
|
||||
def forward(self):
|
||||
anchors = []
|
||||
for k, f in enumerate(self.feature_maps):
|
||||
min_sizes = self.min_sizes[k]
|
||||
for i, j in product(range(f[0]), range(f[1])):
|
||||
for min_size in min_sizes:
|
||||
s_kx = min_size / self.image_size[1]
|
||||
s_ky = min_size / self.image_size[0]
|
||||
dense_cx = [x * self.steps[k] / self.image_size[1] for x in [j + 0.5]]
|
||||
dense_cy = [y * self.steps[k] / self.image_size[0] for y in [i + 0.5]]
|
||||
for cy, cx in product(dense_cy, dense_cx):
|
||||
anchors += [cx, cy, s_kx, s_ky]
|
||||
|
||||
# back to torch land
|
||||
output = torch.Tensor(anchors).view(-1, 4)
|
||||
if self.clip:
|
||||
output.clamp_(max=1, min=0)
|
||||
return output
|
||||
|
||||
|
||||
def py_cpu_nms(dets, thresh):
|
||||
"""Pure Python NMS baseline."""
|
||||
keep = torchvision.ops.nms(
|
||||
boxes=torch.Tensor(dets[:, :4]),
|
||||
scores=torch.Tensor(dets[:, 4]),
|
||||
iou_threshold=thresh,
|
||||
)
|
||||
|
||||
return list(keep)
|
||||
|
||||
|
||||
def point_form(boxes):
|
||||
""" Convert prior_boxes to (xmin, ymin, xmax, ymax)
|
||||
representation for comparison to point form ground truth data.
|
||||
Args:
|
||||
boxes: (tensor) center-size default boxes from priorbox layers.
|
||||
Return:
|
||||
boxes: (tensor) Converted xmin, ymin, xmax, ymax form of boxes.
|
||||
"""
|
||||
return torch.cat(
|
||||
(
|
||||
boxes[:, :2] - boxes[:, 2:] / 2, # xmin, ymin
|
||||
boxes[:, :2] + boxes[:, 2:] / 2),
|
||||
1) # xmax, ymax
|
||||
|
||||
|
||||
def center_size(boxes):
|
||||
""" Convert prior_boxes to (cx, cy, w, h)
|
||||
representation for comparison to center-size form ground truth data.
|
||||
Args:
|
||||
boxes: (tensor) point_form boxes
|
||||
Return:
|
||||
boxes: (tensor) Converted xmin, ymin, xmax, ymax form of boxes.
|
||||
"""
|
||||
return torch.cat(
|
||||
(boxes[:, 2:] + boxes[:, :2]) / 2, # cx, cy
|
||||
boxes[:, 2:] - boxes[:, :2],
|
||||
1) # w, h
|
||||
|
||||
|
||||
def intersect(box_a, box_b):
|
||||
""" We resize both tensors to [A,B,2] without new malloc:
|
||||
[A,2] -> [A,1,2] -> [A,B,2]
|
||||
[B,2] -> [1,B,2] -> [A,B,2]
|
||||
Then we compute the area of intersect between box_a and box_b.
|
||||
Args:
|
||||
box_a: (tensor) bounding boxes, Shape: [A,4].
|
||||
box_b: (tensor) bounding boxes, Shape: [B,4].
|
||||
Return:
|
||||
(tensor) intersection area, Shape: [A,B].
|
||||
"""
|
||||
A = box_a.size(0)
|
||||
B = box_b.size(0)
|
||||
max_xy = torch.min(box_a[:, 2:].unsqueeze(1).expand(A, B, 2), box_b[:, 2:].unsqueeze(0).expand(A, B, 2))
|
||||
min_xy = torch.max(box_a[:, :2].unsqueeze(1).expand(A, B, 2), box_b[:, :2].unsqueeze(0).expand(A, B, 2))
|
||||
inter = torch.clamp((max_xy - min_xy), min=0)
|
||||
return inter[:, :, 0] * inter[:, :, 1]
|
||||
|
||||
|
||||
def jaccard(box_a, box_b):
|
||||
"""Compute the jaccard overlap of two sets of boxes. The jaccard overlap
|
||||
is simply the intersection over union of two boxes. Here we operate on
|
||||
ground truth boxes and default boxes.
|
||||
E.g.:
|
||||
A ∩ B / A ∪ B = A ∩ B / (area(A) + area(B) - A ∩ B)
|
||||
Args:
|
||||
box_a: (tensor) Ground truth bounding boxes, Shape: [num_objects,4]
|
||||
box_b: (tensor) Prior boxes from priorbox layers, Shape: [num_priors,4]
|
||||
Return:
|
||||
jaccard overlap: (tensor) Shape: [box_a.size(0), box_b.size(0)]
|
||||
"""
|
||||
inter = intersect(box_a, box_b)
|
||||
area_a = ((box_a[:, 2] - box_a[:, 0]) * (box_a[:, 3] - box_a[:, 1])).unsqueeze(1).expand_as(inter) # [A,B]
|
||||
area_b = ((box_b[:, 2] - box_b[:, 0]) * (box_b[:, 3] - box_b[:, 1])).unsqueeze(0).expand_as(inter) # [A,B]
|
||||
union = area_a + area_b - inter
|
||||
return inter / union # [A,B]
|
||||
|
||||
|
||||
def matrix_iou(a, b):
|
||||
"""
|
||||
return iou of a and b, numpy version for data augenmentation
|
||||
"""
|
||||
lt = np.maximum(a[:, np.newaxis, :2], b[:, :2])
|
||||
rb = np.minimum(a[:, np.newaxis, 2:], b[:, 2:])
|
||||
|
||||
area_i = np.prod(rb - lt, axis=2) * (lt < rb).all(axis=2)
|
||||
area_a = np.prod(a[:, 2:] - a[:, :2], axis=1)
|
||||
area_b = np.prod(b[:, 2:] - b[:, :2], axis=1)
|
||||
return area_i / (area_a[:, np.newaxis] + area_b - area_i)
|
||||
|
||||
|
||||
def matrix_iof(a, b):
|
||||
"""
|
||||
return iof of a and b, numpy version for data augenmentation
|
||||
"""
|
||||
lt = np.maximum(a[:, np.newaxis, :2], b[:, :2])
|
||||
rb = np.minimum(a[:, np.newaxis, 2:], b[:, 2:])
|
||||
|
||||
area_i = np.prod(rb - lt, axis=2) * (lt < rb).all(axis=2)
|
||||
area_a = np.prod(a[:, 2:] - a[:, :2], axis=1)
|
||||
return area_i / np.maximum(area_a[:, np.newaxis], 1)
|
||||
|
||||
|
||||
def match(threshold, truths, priors, variances, labels, landms, loc_t, conf_t, landm_t, idx):
|
||||
"""Match each prior box with the ground truth box of the highest jaccard
|
||||
overlap, encode the bounding boxes, then return the matched indices
|
||||
corresponding to both confidence and location preds.
|
||||
Args:
|
||||
threshold: (float) The overlap threshold used when matching boxes.
|
||||
truths: (tensor) Ground truth boxes, Shape: [num_obj, 4].
|
||||
priors: (tensor) Prior boxes from priorbox layers, Shape: [n_priors,4].
|
||||
variances: (tensor) Variances corresponding to each prior coord,
|
||||
Shape: [num_priors, 4].
|
||||
labels: (tensor) All the class labels for the image, Shape: [num_obj].
|
||||
landms: (tensor) Ground truth landms, Shape [num_obj, 10].
|
||||
loc_t: (tensor) Tensor to be filled w/ encoded location targets.
|
||||
conf_t: (tensor) Tensor to be filled w/ matched indices for conf preds.
|
||||
landm_t: (tensor) Tensor to be filled w/ encoded landm targets.
|
||||
idx: (int) current batch index
|
||||
Return:
|
||||
The matched indices corresponding to 1)location 2)confidence
|
||||
3)landm preds.
|
||||
"""
|
||||
# jaccard index
|
||||
overlaps = jaccard(truths, point_form(priors))
|
||||
# (Bipartite Matching)
|
||||
# [1,num_objects] best prior for each ground truth
|
||||
best_prior_overlap, best_prior_idx = overlaps.max(1, keepdim=True)
|
||||
|
||||
# ignore hard gt
|
||||
valid_gt_idx = best_prior_overlap[:, 0] >= 0.2
|
||||
best_prior_idx_filter = best_prior_idx[valid_gt_idx, :]
|
||||
if best_prior_idx_filter.shape[0] <= 0:
|
||||
loc_t[idx] = 0
|
||||
conf_t[idx] = 0
|
||||
return
|
||||
|
||||
# [1,num_priors] best ground truth for each prior
|
||||
best_truth_overlap, best_truth_idx = overlaps.max(0, keepdim=True)
|
||||
best_truth_idx.squeeze_(0)
|
||||
best_truth_overlap.squeeze_(0)
|
||||
best_prior_idx.squeeze_(1)
|
||||
best_prior_idx_filter.squeeze_(1)
|
||||
best_prior_overlap.squeeze_(1)
|
||||
best_truth_overlap.index_fill_(0, best_prior_idx_filter, 2) # ensure best prior
|
||||
# TODO refactor: index best_prior_idx with long tensor
|
||||
# ensure every gt matches with its prior of max overlap
|
||||
for j in range(best_prior_idx.size(0)): # 判别此anchor是预测哪一个boxes
|
||||
best_truth_idx[best_prior_idx[j]] = j
|
||||
matches = truths[best_truth_idx] # Shape: [num_priors,4] 此处为每一个anchor对应的bbox取出来
|
||||
conf = labels[best_truth_idx] # Shape: [num_priors] 此处为每一个anchor对应的label取出来
|
||||
conf[best_truth_overlap < threshold] = 0 # label as background overlap<0.35的全部作为负样本
|
||||
loc = encode(matches, priors, variances)
|
||||
|
||||
matches_landm = landms[best_truth_idx]
|
||||
landm = encode_landm(matches_landm, priors, variances)
|
||||
loc_t[idx] = loc # [num_priors,4] encoded offsets to learn
|
||||
conf_t[idx] = conf # [num_priors] top class label for each prior
|
||||
landm_t[idx] = landm
|
||||
|
||||
|
||||
def encode(matched, priors, variances):
|
||||
"""Encode the variances from the priorbox layers into the ground truth boxes
|
||||
we have matched (based on jaccard overlap) with the prior boxes.
|
||||
Args:
|
||||
matched: (tensor) Coords of ground truth for each prior in point-form
|
||||
Shape: [num_priors, 4].
|
||||
priors: (tensor) Prior boxes in center-offset form
|
||||
Shape: [num_priors,4].
|
||||
variances: (list[float]) Variances of priorboxes
|
||||
Return:
|
||||
encoded boxes (tensor), Shape: [num_priors, 4]
|
||||
"""
|
||||
|
||||
# dist b/t match center and prior's center
|
||||
g_cxcy = (matched[:, :2] + matched[:, 2:]) / 2 - priors[:, :2]
|
||||
# encode variance
|
||||
g_cxcy /= (variances[0] * priors[:, 2:])
|
||||
# match wh / prior wh
|
||||
g_wh = (matched[:, 2:] - matched[:, :2]) / priors[:, 2:]
|
||||
g_wh = torch.log(g_wh) / variances[1]
|
||||
# return target for smooth_l1_loss
|
||||
return torch.cat([g_cxcy, g_wh], 1) # [num_priors,4]
|
||||
|
||||
|
||||
def encode_landm(matched, priors, variances):
|
||||
"""Encode the variances from the priorbox layers into the ground truth boxes
|
||||
we have matched (based on jaccard overlap) with the prior boxes.
|
||||
Args:
|
||||
matched: (tensor) Coords of ground truth for each prior in point-form
|
||||
Shape: [num_priors, 10].
|
||||
priors: (tensor) Prior boxes in center-offset form
|
||||
Shape: [num_priors,4].
|
||||
variances: (list[float]) Variances of priorboxes
|
||||
Return:
|
||||
encoded landm (tensor), Shape: [num_priors, 10]
|
||||
"""
|
||||
|
||||
# dist b/t match center and prior's center
|
||||
matched = torch.reshape(matched, (matched.size(0), 5, 2))
|
||||
priors_cx = priors[:, 0].unsqueeze(1).expand(matched.size(0), 5).unsqueeze(2)
|
||||
priors_cy = priors[:, 1].unsqueeze(1).expand(matched.size(0), 5).unsqueeze(2)
|
||||
priors_w = priors[:, 2].unsqueeze(1).expand(matched.size(0), 5).unsqueeze(2)
|
||||
priors_h = priors[:, 3].unsqueeze(1).expand(matched.size(0), 5).unsqueeze(2)
|
||||
priors = torch.cat([priors_cx, priors_cy, priors_w, priors_h], dim=2)
|
||||
g_cxcy = matched[:, :, :2] - priors[:, :, :2]
|
||||
# encode variance
|
||||
g_cxcy /= (variances[0] * priors[:, :, 2:])
|
||||
# g_cxcy /= priors[:, :, 2:]
|
||||
g_cxcy = g_cxcy.reshape(g_cxcy.size(0), -1)
|
||||
# return target for smooth_l1_loss
|
||||
return g_cxcy
|
||||
|
||||
|
||||
# Adapted from https://github.com/Hakuyume/chainer-ssd
|
||||
def decode(loc, priors, variances):
|
||||
"""Decode locations from predictions using priors to undo
|
||||
the encoding we did for offset regression at train time.
|
||||
Args:
|
||||
loc (tensor): location predictions for loc layers,
|
||||
Shape: [num_priors,4]
|
||||
priors (tensor): Prior boxes in center-offset form.
|
||||
Shape: [num_priors,4].
|
||||
variances: (list[float]) Variances of priorboxes
|
||||
Return:
|
||||
decoded bounding box predictions
|
||||
"""
|
||||
|
||||
boxes = torch.cat((priors[:, :2] + loc[:, :2] * variances[0] * priors[:, 2:],
|
||||
priors[:, 2:] * torch.exp(loc[:, 2:] * variances[1])), 1)
|
||||
boxes[:, :2] -= boxes[:, 2:] / 2
|
||||
boxes[:, 2:] += boxes[:, :2]
|
||||
return boxes
|
||||
|
||||
|
||||
def decode_landm(pre, priors, variances):
|
||||
"""Decode landm from predictions using priors to undo
|
||||
the encoding we did for offset regression at train time.
|
||||
Args:
|
||||
pre (tensor): landm predictions for loc layers,
|
||||
Shape: [num_priors,10]
|
||||
priors (tensor): Prior boxes in center-offset form.
|
||||
Shape: [num_priors,4].
|
||||
variances: (list[float]) Variances of priorboxes
|
||||
Return:
|
||||
decoded landm predictions
|
||||
"""
|
||||
tmp = (
|
||||
priors[:, :2] + pre[:, :2] * variances[0] * priors[:, 2:],
|
||||
priors[:, :2] + pre[:, 2:4] * variances[0] * priors[:, 2:],
|
||||
priors[:, :2] + pre[:, 4:6] * variances[0] * priors[:, 2:],
|
||||
priors[:, :2] + pre[:, 6:8] * variances[0] * priors[:, 2:],
|
||||
priors[:, :2] + pre[:, 8:10] * variances[0] * priors[:, 2:],
|
||||
)
|
||||
landms = torch.cat(tmp, dim=1)
|
||||
return landms
|
||||
|
||||
|
||||
def batched_decode(b_loc, priors, variances):
|
||||
"""Decode locations from predictions using priors to undo
|
||||
the encoding we did for offset regression at train time.
|
||||
Args:
|
||||
b_loc (tensor): location predictions for loc layers,
|
||||
Shape: [num_batches,num_priors,4]
|
||||
priors (tensor): Prior boxes in center-offset form.
|
||||
Shape: [1,num_priors,4].
|
||||
variances: (list[float]) Variances of priorboxes
|
||||
Return:
|
||||
decoded bounding box predictions
|
||||
"""
|
||||
boxes = (
|
||||
priors[:, :, :2] + b_loc[:, :, :2] * variances[0] * priors[:, :, 2:],
|
||||
priors[:, :, 2:] * torch.exp(b_loc[:, :, 2:] * variances[1]),
|
||||
)
|
||||
boxes = torch.cat(boxes, dim=2)
|
||||
|
||||
boxes[:, :, :2] -= boxes[:, :, 2:] / 2
|
||||
boxes[:, :, 2:] += boxes[:, :, :2]
|
||||
return boxes
|
||||
|
||||
|
||||
def batched_decode_landm(pre, priors, variances):
|
||||
"""Decode landm from predictions using priors to undo
|
||||
the encoding we did for offset regression at train time.
|
||||
Args:
|
||||
pre (tensor): landm predictions for loc layers,
|
||||
Shape: [num_batches,num_priors,10]
|
||||
priors (tensor): Prior boxes in center-offset form.
|
||||
Shape: [1,num_priors,4].
|
||||
variances: (list[float]) Variances of priorboxes
|
||||
Return:
|
||||
decoded landm predictions
|
||||
"""
|
||||
landms = (
|
||||
priors[:, :, :2] + pre[:, :, :2] * variances[0] * priors[:, :, 2:],
|
||||
priors[:, :, :2] + pre[:, :, 2:4] * variances[0] * priors[:, :, 2:],
|
||||
priors[:, :, :2] + pre[:, :, 4:6] * variances[0] * priors[:, :, 2:],
|
||||
priors[:, :, :2] + pre[:, :, 6:8] * variances[0] * priors[:, :, 2:],
|
||||
priors[:, :, :2] + pre[:, :, 8:10] * variances[0] * priors[:, :, 2:],
|
||||
)
|
||||
landms = torch.cat(landms, dim=2)
|
||||
return landms
|
||||
|
||||
|
||||
def log_sum_exp(x):
|
||||
"""Utility function for computing log_sum_exp while determining
|
||||
This will be used to determine unaveraged confidence loss across
|
||||
all examples in a batch.
|
||||
Args:
|
||||
x (Variable(tensor)): conf_preds from conf layers
|
||||
"""
|
||||
x_max = x.data.max()
|
||||
return torch.log(torch.sum(torch.exp(x - x_max), 1, keepdim=True)) + x_max
|
||||
|
||||
|
||||
# Original author: Francisco Massa:
|
||||
# https://github.com/fmassa/object-detection.torch
|
||||
# Ported to PyTorch by Max deGroot (02/01/2017)
|
||||
def nms(boxes, scores, overlap=0.5, top_k=200):
|
||||
"""Apply non-maximum suppression at test time to avoid detecting too many
|
||||
overlapping bounding boxes for a given object.
|
||||
Args:
|
||||
boxes: (tensor) The location preds for the img, Shape: [num_priors,4].
|
||||
scores: (tensor) The class predscores for the img, Shape:[num_priors].
|
||||
overlap: (float) The overlap thresh for suppressing unnecessary boxes.
|
||||
top_k: (int) The Maximum number of box preds to consider.
|
||||
Return:
|
||||
The indices of the kept boxes with respect to num_priors.
|
||||
"""
|
||||
|
||||
keep = torch.Tensor(scores.size(0)).fill_(0).long()
|
||||
if boxes.numel() == 0:
|
||||
return keep
|
||||
x1 = boxes[:, 0]
|
||||
y1 = boxes[:, 1]
|
||||
x2 = boxes[:, 2]
|
||||
y2 = boxes[:, 3]
|
||||
area = torch.mul(x2 - x1, y2 - y1)
|
||||
v, idx = scores.sort(0) # sort in ascending order
|
||||
# I = I[v >= 0.01]
|
||||
idx = idx[-top_k:] # indices of the top-k largest vals
|
||||
xx1 = boxes.new()
|
||||
yy1 = boxes.new()
|
||||
xx2 = boxes.new()
|
||||
yy2 = boxes.new()
|
||||
w = boxes.new()
|
||||
h = boxes.new()
|
||||
|
||||
# keep = torch.Tensor()
|
||||
count = 0
|
||||
while idx.numel() > 0:
|
||||
i = idx[-1] # index of current largest val
|
||||
# keep.append(i)
|
||||
keep[count] = i
|
||||
count += 1
|
||||
if idx.size(0) == 1:
|
||||
break
|
||||
idx = idx[:-1] # remove kept element from view
|
||||
# load bboxes of next highest vals
|
||||
torch.index_select(x1, 0, idx, out=xx1)
|
||||
torch.index_select(y1, 0, idx, out=yy1)
|
||||
torch.index_select(x2, 0, idx, out=xx2)
|
||||
torch.index_select(y2, 0, idx, out=yy2)
|
||||
# store element-wise max with next highest score
|
||||
xx1 = torch.clamp(xx1, min=x1[i])
|
||||
yy1 = torch.clamp(yy1, min=y1[i])
|
||||
xx2 = torch.clamp(xx2, max=x2[i])
|
||||
yy2 = torch.clamp(yy2, max=y2[i])
|
||||
w.resize_as_(xx2)
|
||||
h.resize_as_(yy2)
|
||||
w = xx2 - xx1
|
||||
h = yy2 - yy1
|
||||
# check sizes of xx1 and xx2.. after each iteration
|
||||
w = torch.clamp(w, min=0.0)
|
||||
h = torch.clamp(h, min=0.0)
|
||||
inter = w * h
|
||||
# IoU = i / (area(a) + area(b) - i)
|
||||
rem_areas = torch.index_select(area, 0, idx) # load remaining areas)
|
||||
union = (rem_areas - inter) + area[i]
|
||||
IoU = inter / union # store result in iou
|
||||
# keep only elements with an IoU <= overlap
|
||||
idx = idx[IoU.le(overlap)]
|
||||
return keep, count
|
||||
@@ -0,0 +1,24 @@
|
||||
import torch
|
||||
|
||||
from extras.facexlib.utils import load_file_from_url
|
||||
from .bisenet import BiSeNet
|
||||
from .parsenet import ParseNet
|
||||
|
||||
|
||||
def init_parsing_model(model_name='bisenet', half=False, device='cuda', model_rootpath=None):
|
||||
if model_name == 'bisenet':
|
||||
model = BiSeNet(num_class=19)
|
||||
model_url = 'https://github.com/xinntao/facexlib/releases/download/v0.2.0/parsing_bisenet.pth'
|
||||
elif model_name == 'parsenet':
|
||||
model = ParseNet(in_size=512, out_size=512, parsing_ch=19)
|
||||
model_url = 'https://github.com/xinntao/facexlib/releases/download/v0.2.2/parsing_parsenet.pth'
|
||||
else:
|
||||
raise NotImplementedError(f'{model_name} is not implemented.')
|
||||
|
||||
model_path = load_file_from_url(
|
||||
url=model_url, model_dir='facexlib/weights', progress=True, file_name=None, save_dir=model_rootpath)
|
||||
load_net = torch.load(model_path, map_location=lambda storage, loc: storage, weights_only=True)
|
||||
model.load_state_dict(load_net, strict=True)
|
||||
model.eval()
|
||||
model = model.to(device)
|
||||
return model
|
||||
@@ -0,0 +1,140 @@
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
|
||||
from .resnet import ResNet18
|
||||
|
||||
|
||||
class ConvBNReLU(nn.Module):
|
||||
|
||||
def __init__(self, in_chan, out_chan, ks=3, stride=1, padding=1):
|
||||
super(ConvBNReLU, self).__init__()
|
||||
self.conv = nn.Conv2d(in_chan, out_chan, kernel_size=ks, stride=stride, padding=padding, bias=False)
|
||||
self.bn = nn.BatchNorm2d(out_chan)
|
||||
|
||||
def forward(self, x):
|
||||
x = self.conv(x)
|
||||
x = F.relu(self.bn(x))
|
||||
return x
|
||||
|
||||
|
||||
class BiSeNetOutput(nn.Module):
|
||||
|
||||
def __init__(self, in_chan, mid_chan, num_class):
|
||||
super(BiSeNetOutput, self).__init__()
|
||||
self.conv = ConvBNReLU(in_chan, mid_chan, ks=3, stride=1, padding=1)
|
||||
self.conv_out = nn.Conv2d(mid_chan, num_class, kernel_size=1, bias=False)
|
||||
|
||||
def forward(self, x):
|
||||
feat = self.conv(x)
|
||||
out = self.conv_out(feat)
|
||||
return out, feat
|
||||
|
||||
|
||||
class AttentionRefinementModule(nn.Module):
|
||||
|
||||
def __init__(self, in_chan, out_chan):
|
||||
super(AttentionRefinementModule, self).__init__()
|
||||
self.conv = ConvBNReLU(in_chan, out_chan, ks=3, stride=1, padding=1)
|
||||
self.conv_atten = nn.Conv2d(out_chan, out_chan, kernel_size=1, bias=False)
|
||||
self.bn_atten = nn.BatchNorm2d(out_chan)
|
||||
self.sigmoid_atten = nn.Sigmoid()
|
||||
|
||||
def forward(self, x):
|
||||
feat = self.conv(x)
|
||||
atten = F.avg_pool2d(feat, feat.size()[2:])
|
||||
atten = self.conv_atten(atten)
|
||||
atten = self.bn_atten(atten)
|
||||
atten = self.sigmoid_atten(atten)
|
||||
out = torch.mul(feat, atten)
|
||||
return out
|
||||
|
||||
|
||||
class ContextPath(nn.Module):
|
||||
|
||||
def __init__(self):
|
||||
super(ContextPath, self).__init__()
|
||||
self.resnet = ResNet18()
|
||||
self.arm16 = AttentionRefinementModule(256, 128)
|
||||
self.arm32 = AttentionRefinementModule(512, 128)
|
||||
self.conv_head32 = ConvBNReLU(128, 128, ks=3, stride=1, padding=1)
|
||||
self.conv_head16 = ConvBNReLU(128, 128, ks=3, stride=1, padding=1)
|
||||
self.conv_avg = ConvBNReLU(512, 128, ks=1, stride=1, padding=0)
|
||||
|
||||
def forward(self, x):
|
||||
feat8, feat16, feat32 = self.resnet(x)
|
||||
h8, w8 = feat8.size()[2:]
|
||||
h16, w16 = feat16.size()[2:]
|
||||
h32, w32 = feat32.size()[2:]
|
||||
|
||||
avg = F.avg_pool2d(feat32, feat32.size()[2:])
|
||||
avg = self.conv_avg(avg)
|
||||
avg_up = F.interpolate(avg, (h32, w32), mode='nearest')
|
||||
|
||||
feat32_arm = self.arm32(feat32)
|
||||
feat32_sum = feat32_arm + avg_up
|
||||
feat32_up = F.interpolate(feat32_sum, (h16, w16), mode='nearest')
|
||||
feat32_up = self.conv_head32(feat32_up)
|
||||
|
||||
feat16_arm = self.arm16(feat16)
|
||||
feat16_sum = feat16_arm + feat32_up
|
||||
feat16_up = F.interpolate(feat16_sum, (h8, w8), mode='nearest')
|
||||
feat16_up = self.conv_head16(feat16_up)
|
||||
|
||||
return feat8, feat16_up, feat32_up # x8, x8, x16
|
||||
|
||||
|
||||
class FeatureFusionModule(nn.Module):
|
||||
|
||||
def __init__(self, in_chan, out_chan):
|
||||
super(FeatureFusionModule, self).__init__()
|
||||
self.convblk = ConvBNReLU(in_chan, out_chan, ks=1, stride=1, padding=0)
|
||||
self.conv1 = nn.Conv2d(out_chan, out_chan // 4, kernel_size=1, stride=1, padding=0, bias=False)
|
||||
self.conv2 = nn.Conv2d(out_chan // 4, out_chan, kernel_size=1, stride=1, padding=0, bias=False)
|
||||
self.relu = nn.ReLU(inplace=True)
|
||||
self.sigmoid = nn.Sigmoid()
|
||||
|
||||
def forward(self, fsp, fcp):
|
||||
fcat = torch.cat([fsp, fcp], dim=1)
|
||||
feat = self.convblk(fcat)
|
||||
atten = F.avg_pool2d(feat, feat.size()[2:])
|
||||
atten = self.conv1(atten)
|
||||
atten = self.relu(atten)
|
||||
atten = self.conv2(atten)
|
||||
atten = self.sigmoid(atten)
|
||||
feat_atten = torch.mul(feat, atten)
|
||||
feat_out = feat_atten + feat
|
||||
return feat_out
|
||||
|
||||
|
||||
class BiSeNet(nn.Module):
|
||||
|
||||
def __init__(self, num_class):
|
||||
super(BiSeNet, self).__init__()
|
||||
self.cp = ContextPath()
|
||||
self.ffm = FeatureFusionModule(256, 256)
|
||||
self.conv_out = BiSeNetOutput(256, 256, num_class)
|
||||
self.conv_out16 = BiSeNetOutput(128, 64, num_class)
|
||||
self.conv_out32 = BiSeNetOutput(128, 64, num_class)
|
||||
|
||||
def forward(self, x, return_feat=False):
|
||||
h, w = x.size()[2:]
|
||||
feat_res8, feat_cp8, feat_cp16 = self.cp(x) # return res3b1 feature
|
||||
feat_sp = feat_res8 # replace spatial path feature with res3b1 feature
|
||||
feat_fuse = self.ffm(feat_sp, feat_cp8)
|
||||
|
||||
out, feat = self.conv_out(feat_fuse)
|
||||
out16, feat16 = self.conv_out16(feat_cp8)
|
||||
out32, feat32 = self.conv_out32(feat_cp16)
|
||||
|
||||
out = F.interpolate(out, (h, w), mode='bilinear', align_corners=True)
|
||||
out16 = F.interpolate(out16, (h, w), mode='bilinear', align_corners=True)
|
||||
out32 = F.interpolate(out32, (h, w), mode='bilinear', align_corners=True)
|
||||
|
||||
if return_feat:
|
||||
feat = F.interpolate(feat, (h, w), mode='bilinear', align_corners=True)
|
||||
feat16 = F.interpolate(feat16, (h, w), mode='bilinear', align_corners=True)
|
||||
feat32 = F.interpolate(feat32, (h, w), mode='bilinear', align_corners=True)
|
||||
return out, out16, out32, feat, feat16, feat32
|
||||
else:
|
||||
return out, out16, out32
|
||||
@@ -0,0 +1,194 @@
|
||||
"""Modified from https://github.com/chaofengc/PSFRGAN
|
||||
"""
|
||||
import numpy as np
|
||||
import torch.nn as nn
|
||||
from torch.nn import functional as F
|
||||
|
||||
|
||||
class NormLayer(nn.Module):
|
||||
"""Normalization Layers.
|
||||
|
||||
Args:
|
||||
channels: input channels, for batch norm and instance norm.
|
||||
input_size: input shape without batch size, for layer norm.
|
||||
"""
|
||||
|
||||
def __init__(self, channels, normalize_shape=None, norm_type='bn'):
|
||||
super(NormLayer, self).__init__()
|
||||
norm_type = norm_type.lower()
|
||||
self.norm_type = norm_type
|
||||
if norm_type == 'bn':
|
||||
self.norm = nn.BatchNorm2d(channels, affine=True)
|
||||
elif norm_type == 'in':
|
||||
self.norm = nn.InstanceNorm2d(channels, affine=False)
|
||||
elif norm_type == 'gn':
|
||||
self.norm = nn.GroupNorm(32, channels, affine=True)
|
||||
elif norm_type == 'pixel':
|
||||
self.norm = lambda x: F.normalize(x, p=2, dim=1)
|
||||
elif norm_type == 'layer':
|
||||
self.norm = nn.LayerNorm(normalize_shape)
|
||||
elif norm_type == 'none':
|
||||
self.norm = lambda x: x * 1.0
|
||||
else:
|
||||
assert 1 == 0, f'Norm type {norm_type} not support.'
|
||||
|
||||
def forward(self, x, ref=None):
|
||||
if self.norm_type == 'spade':
|
||||
return self.norm(x, ref)
|
||||
else:
|
||||
return self.norm(x)
|
||||
|
||||
|
||||
class ReluLayer(nn.Module):
|
||||
"""Relu Layer.
|
||||
|
||||
Args:
|
||||
relu type: type of relu layer, candidates are
|
||||
- ReLU
|
||||
- LeakyReLU: default relu slope 0.2
|
||||
- PRelu
|
||||
- SELU
|
||||
- none: direct pass
|
||||
"""
|
||||
|
||||
def __init__(self, channels, relu_type='relu'):
|
||||
super(ReluLayer, self).__init__()
|
||||
relu_type = relu_type.lower()
|
||||
if relu_type == 'relu':
|
||||
self.func = nn.ReLU(True)
|
||||
elif relu_type == 'leakyrelu':
|
||||
self.func = nn.LeakyReLU(0.2, inplace=True)
|
||||
elif relu_type == 'prelu':
|
||||
self.func = nn.PReLU(channels)
|
||||
elif relu_type == 'selu':
|
||||
self.func = nn.SELU(True)
|
||||
elif relu_type == 'none':
|
||||
self.func = lambda x: x * 1.0
|
||||
else:
|
||||
assert 1 == 0, f'Relu type {relu_type} not support.'
|
||||
|
||||
def forward(self, x):
|
||||
return self.func(x)
|
||||
|
||||
|
||||
class ConvLayer(nn.Module):
|
||||
|
||||
def __init__(self,
|
||||
in_channels,
|
||||
out_channels,
|
||||
kernel_size=3,
|
||||
scale='none',
|
||||
norm_type='none',
|
||||
relu_type='none',
|
||||
use_pad=True,
|
||||
bias=True):
|
||||
super(ConvLayer, self).__init__()
|
||||
self.use_pad = use_pad
|
||||
self.norm_type = norm_type
|
||||
if norm_type in ['bn']:
|
||||
bias = False
|
||||
|
||||
stride = 2 if scale == 'down' else 1
|
||||
|
||||
self.scale_func = lambda x: x
|
||||
if scale == 'up':
|
||||
self.scale_func = lambda x: nn.functional.interpolate(x, scale_factor=2, mode='nearest')
|
||||
|
||||
self.reflection_pad = nn.ReflectionPad2d(int(np.ceil((kernel_size - 1.) / 2)))
|
||||
self.conv2d = nn.Conv2d(in_channels, out_channels, kernel_size, stride, bias=bias)
|
||||
|
||||
self.relu = ReluLayer(out_channels, relu_type)
|
||||
self.norm = NormLayer(out_channels, norm_type=norm_type)
|
||||
|
||||
def forward(self, x):
|
||||
out = self.scale_func(x)
|
||||
if self.use_pad:
|
||||
out = self.reflection_pad(out)
|
||||
out = self.conv2d(out)
|
||||
out = self.norm(out)
|
||||
out = self.relu(out)
|
||||
return out
|
||||
|
||||
|
||||
class ResidualBlock(nn.Module):
|
||||
"""
|
||||
Residual block recommended in: http://torch.ch/blog/2016/02/04/resnets.html
|
||||
"""
|
||||
|
||||
def __init__(self, c_in, c_out, relu_type='prelu', norm_type='bn', scale='none'):
|
||||
super(ResidualBlock, self).__init__()
|
||||
|
||||
if scale == 'none' and c_in == c_out:
|
||||
self.shortcut_func = lambda x: x
|
||||
else:
|
||||
self.shortcut_func = ConvLayer(c_in, c_out, 3, scale)
|
||||
|
||||
scale_config_dict = {'down': ['none', 'down'], 'up': ['up', 'none'], 'none': ['none', 'none']}
|
||||
scale_conf = scale_config_dict[scale]
|
||||
|
||||
self.conv1 = ConvLayer(c_in, c_out, 3, scale_conf[0], norm_type=norm_type, relu_type=relu_type)
|
||||
self.conv2 = ConvLayer(c_out, c_out, 3, scale_conf[1], norm_type=norm_type, relu_type='none')
|
||||
|
||||
def forward(self, x):
|
||||
identity = self.shortcut_func(x)
|
||||
|
||||
res = self.conv1(x)
|
||||
res = self.conv2(res)
|
||||
return identity + res
|
||||
|
||||
|
||||
class ParseNet(nn.Module):
|
||||
|
||||
def __init__(self,
|
||||
in_size=128,
|
||||
out_size=128,
|
||||
min_feat_size=32,
|
||||
base_ch=64,
|
||||
parsing_ch=19,
|
||||
res_depth=10,
|
||||
relu_type='LeakyReLU',
|
||||
norm_type='bn',
|
||||
ch_range=[32, 256]):
|
||||
super().__init__()
|
||||
self.res_depth = res_depth
|
||||
act_args = {'norm_type': norm_type, 'relu_type': relu_type}
|
||||
min_ch, max_ch = ch_range
|
||||
|
||||
ch_clip = lambda x: max(min_ch, min(x, max_ch)) # noqa: E731
|
||||
min_feat_size = min(in_size, min_feat_size)
|
||||
|
||||
down_steps = int(np.log2(in_size // min_feat_size))
|
||||
up_steps = int(np.log2(out_size // min_feat_size))
|
||||
|
||||
# =============== define encoder-body-decoder ====================
|
||||
self.encoder = []
|
||||
self.encoder.append(ConvLayer(3, base_ch, 3, 1))
|
||||
head_ch = base_ch
|
||||
for i in range(down_steps):
|
||||
cin, cout = ch_clip(head_ch), ch_clip(head_ch * 2)
|
||||
self.encoder.append(ResidualBlock(cin, cout, scale='down', **act_args))
|
||||
head_ch = head_ch * 2
|
||||
|
||||
self.body = []
|
||||
for i in range(res_depth):
|
||||
self.body.append(ResidualBlock(ch_clip(head_ch), ch_clip(head_ch), **act_args))
|
||||
|
||||
self.decoder = []
|
||||
for i in range(up_steps):
|
||||
cin, cout = ch_clip(head_ch), ch_clip(head_ch // 2)
|
||||
self.decoder.append(ResidualBlock(cin, cout, scale='up', **act_args))
|
||||
head_ch = head_ch // 2
|
||||
|
||||
self.encoder = nn.Sequential(*self.encoder)
|
||||
self.body = nn.Sequential(*self.body)
|
||||
self.decoder = nn.Sequential(*self.decoder)
|
||||
self.out_img_conv = ConvLayer(ch_clip(head_ch), 3)
|
||||
self.out_mask_conv = ConvLayer(ch_clip(head_ch), parsing_ch)
|
||||
|
||||
def forward(self, x):
|
||||
feat = self.encoder(x)
|
||||
x = feat + self.body(feat)
|
||||
x = self.decoder(x)
|
||||
out_img = self.out_img_conv(x)
|
||||
out_mask = self.out_mask_conv(x)
|
||||
return out_mask, out_img
|
||||
@@ -0,0 +1,69 @@
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
|
||||
|
||||
def conv3x3(in_planes, out_planes, stride=1):
|
||||
"""3x3 convolution with padding"""
|
||||
return nn.Conv2d(in_planes, out_planes, kernel_size=3, stride=stride, padding=1, bias=False)
|
||||
|
||||
|
||||
class BasicBlock(nn.Module):
|
||||
|
||||
def __init__(self, in_chan, out_chan, stride=1):
|
||||
super(BasicBlock, self).__init__()
|
||||
self.conv1 = conv3x3(in_chan, out_chan, stride)
|
||||
self.bn1 = nn.BatchNorm2d(out_chan)
|
||||
self.conv2 = conv3x3(out_chan, out_chan)
|
||||
self.bn2 = nn.BatchNorm2d(out_chan)
|
||||
self.relu = nn.ReLU(inplace=True)
|
||||
self.downsample = None
|
||||
if in_chan != out_chan or stride != 1:
|
||||
self.downsample = nn.Sequential(
|
||||
nn.Conv2d(in_chan, out_chan, kernel_size=1, stride=stride, bias=False),
|
||||
nn.BatchNorm2d(out_chan),
|
||||
)
|
||||
|
||||
def forward(self, x):
|
||||
residual = self.conv1(x)
|
||||
residual = F.relu(self.bn1(residual))
|
||||
residual = self.conv2(residual)
|
||||
residual = self.bn2(residual)
|
||||
|
||||
shortcut = x
|
||||
if self.downsample is not None:
|
||||
shortcut = self.downsample(x)
|
||||
|
||||
out = shortcut + residual
|
||||
out = self.relu(out)
|
||||
return out
|
||||
|
||||
|
||||
def create_layer_basic(in_chan, out_chan, bnum, stride=1):
|
||||
layers = [BasicBlock(in_chan, out_chan, stride=stride)]
|
||||
for i in range(bnum - 1):
|
||||
layers.append(BasicBlock(out_chan, out_chan, stride=1))
|
||||
return nn.Sequential(*layers)
|
||||
|
||||
|
||||
class ResNet18(nn.Module):
|
||||
|
||||
def __init__(self):
|
||||
super(ResNet18, self).__init__()
|
||||
self.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3, bias=False)
|
||||
self.bn1 = nn.BatchNorm2d(64)
|
||||
self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
|
||||
self.layer1 = create_layer_basic(64, 64, bnum=2, stride=1)
|
||||
self.layer2 = create_layer_basic(64, 128, bnum=2, stride=2)
|
||||
self.layer3 = create_layer_basic(128, 256, bnum=2, stride=2)
|
||||
self.layer4 = create_layer_basic(256, 512, bnum=2, stride=2)
|
||||
|
||||
def forward(self, x):
|
||||
x = self.conv1(x)
|
||||
x = F.relu(self.bn1(x))
|
||||
x = self.maxpool(x)
|
||||
|
||||
x = self.layer1(x)
|
||||
feat8 = self.layer2(x) # 1/8
|
||||
feat16 = self.layer3(feat8) # 1/16
|
||||
feat32 = self.layer4(feat16) # 1/32
|
||||
return feat8, feat16, feat32
|
||||
@@ -0,0 +1,7 @@
|
||||
from .face_utils import align_crop_face_landmarks, compute_increased_bbox, get_valid_bboxes, paste_face_back
|
||||
from .misc import img2tensor, load_file_from_url, scandir
|
||||
|
||||
__all__ = [
|
||||
'align_crop_face_landmarks', 'compute_increased_bbox', 'get_valid_bboxes', 'load_file_from_url', 'paste_face_back',
|
||||
'img2tensor', 'scandir'
|
||||
]
|
||||
@@ -0,0 +1,374 @@
|
||||
import cv2
|
||||
import numpy as np
|
||||
import os
|
||||
import torch
|
||||
from torchvision.transforms.functional import normalize
|
||||
|
||||
from extras.facexlib.detection import init_detection_model
|
||||
from extras.facexlib.parsing import init_parsing_model
|
||||
from extras.facexlib.utils.misc import img2tensor, imwrite
|
||||
|
||||
|
||||
def get_largest_face(det_faces, h, w):
|
||||
|
||||
def get_location(val, length):
|
||||
if val < 0:
|
||||
return 0
|
||||
elif val > length:
|
||||
return length
|
||||
else:
|
||||
return val
|
||||
|
||||
face_areas = []
|
||||
for det_face in det_faces:
|
||||
left = get_location(det_face[0], w)
|
||||
right = get_location(det_face[2], w)
|
||||
top = get_location(det_face[1], h)
|
||||
bottom = get_location(det_face[3], h)
|
||||
face_area = (right - left) * (bottom - top)
|
||||
face_areas.append(face_area)
|
||||
largest_idx = face_areas.index(max(face_areas))
|
||||
return det_faces[largest_idx], largest_idx
|
||||
|
||||
|
||||
def get_center_face(det_faces, h=0, w=0, center=None):
|
||||
if center is not None:
|
||||
center = np.array(center)
|
||||
else:
|
||||
center = np.array([w / 2, h / 2])
|
||||
center_dist = []
|
||||
for det_face in det_faces:
|
||||
face_center = np.array([(det_face[0] + det_face[2]) / 2, (det_face[1] + det_face[3]) / 2])
|
||||
dist = np.linalg.norm(face_center - center)
|
||||
center_dist.append(dist)
|
||||
center_idx = center_dist.index(min(center_dist))
|
||||
return det_faces[center_idx], center_idx
|
||||
|
||||
|
||||
class FaceRestoreHelper(object):
|
||||
"""Helper for the face restoration pipeline (base class)."""
|
||||
|
||||
def __init__(self,
|
||||
upscale_factor,
|
||||
face_size=512,
|
||||
crop_ratio=(1, 1),
|
||||
det_model='retinaface_resnet50',
|
||||
save_ext='png',
|
||||
template_3points=False,
|
||||
pad_blur=False,
|
||||
use_parse=False,
|
||||
device=None,
|
||||
model_rootpath=None):
|
||||
self.template_3points = template_3points # improve robustness
|
||||
self.upscale_factor = upscale_factor
|
||||
# the cropped face ratio based on the square face
|
||||
self.crop_ratio = crop_ratio # (h, w)
|
||||
assert (self.crop_ratio[0] >= 1 and self.crop_ratio[1] >= 1), 'crop ration only supports >=1'
|
||||
self.face_size = (int(face_size * self.crop_ratio[1]), int(face_size * self.crop_ratio[0]))
|
||||
|
||||
if self.template_3points:
|
||||
self.face_template = np.array([[192, 240], [319, 240], [257, 371]])
|
||||
else:
|
||||
# standard 5 landmarks for FFHQ faces with 512 x 512
|
||||
self.face_template = np.array([[192.98138, 239.94708], [318.90277, 240.1936], [256.63416, 314.01935],
|
||||
[201.26117, 371.41043], [313.08905, 371.15118]])
|
||||
self.face_template = self.face_template * (face_size / 512.0)
|
||||
if self.crop_ratio[0] > 1:
|
||||
self.face_template[:, 1] += face_size * (self.crop_ratio[0] - 1) / 2
|
||||
if self.crop_ratio[1] > 1:
|
||||
self.face_template[:, 0] += face_size * (self.crop_ratio[1] - 1) / 2
|
||||
self.save_ext = save_ext
|
||||
self.pad_blur = pad_blur
|
||||
if self.pad_blur is True:
|
||||
self.template_3points = False
|
||||
|
||||
self.all_landmarks_5 = []
|
||||
self.det_faces = []
|
||||
self.affine_matrices = []
|
||||
self.inverse_affine_matrices = []
|
||||
self.cropped_faces = []
|
||||
self.restored_faces = []
|
||||
self.pad_input_imgs = []
|
||||
|
||||
if device is None:
|
||||
self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
|
||||
else:
|
||||
self.device = device
|
||||
|
||||
# init face detection model
|
||||
self.face_det = init_detection_model(det_model, half=False, device=self.device, model_rootpath=model_rootpath)
|
||||
|
||||
# init face parsing model
|
||||
self.use_parse = use_parse
|
||||
self.face_parse = init_parsing_model(model_name='parsenet', device=self.device, model_rootpath=model_rootpath)
|
||||
|
||||
def set_upscale_factor(self, upscale_factor):
|
||||
self.upscale_factor = upscale_factor
|
||||
|
||||
def read_image(self, img):
|
||||
"""img can be image path or cv2 loaded image."""
|
||||
# self.input_img is Numpy array, (h, w, c), BGR, uint8, [0, 255]
|
||||
if isinstance(img, str):
|
||||
img = cv2.imread(img)
|
||||
|
||||
if np.max(img) > 256: # 16-bit image
|
||||
img = img / 65535 * 255
|
||||
if len(img.shape) == 2: # gray image
|
||||
img = cv2.cvtColor(img, cv2.COLOR_GRAY2BGR)
|
||||
elif img.shape[2] == 4: # RGBA image with alpha channel
|
||||
img = img[:, :, 0:3]
|
||||
|
||||
self.input_img = img
|
||||
|
||||
def get_face_landmarks_5(self,
|
||||
only_keep_largest=False,
|
||||
only_center_face=False,
|
||||
resize=None,
|
||||
blur_ratio=0.01,
|
||||
eye_dist_threshold=None):
|
||||
if resize is None:
|
||||
scale = 1
|
||||
input_img = self.input_img
|
||||
else:
|
||||
h, w = self.input_img.shape[0:2]
|
||||
scale = min(h, w) / resize
|
||||
h, w = int(h / scale), int(w / scale)
|
||||
input_img = cv2.resize(self.input_img, (w, h), interpolation=cv2.INTER_LANCZOS4)
|
||||
|
||||
with torch.no_grad():
|
||||
bboxes = self.face_det.detect_faces(input_img, 0.97) * scale
|
||||
for bbox in bboxes:
|
||||
# remove faces with too small eye distance: side faces or too small faces
|
||||
eye_dist = np.linalg.norm([bbox[5] - bbox[7], bbox[6] - bbox[8]])
|
||||
if eye_dist_threshold is not None and (eye_dist < eye_dist_threshold):
|
||||
continue
|
||||
|
||||
if self.template_3points:
|
||||
landmark = np.array([[bbox[i], bbox[i + 1]] for i in range(5, 11, 2)])
|
||||
else:
|
||||
landmark = np.array([[bbox[i], bbox[i + 1]] for i in range(5, 15, 2)])
|
||||
self.all_landmarks_5.append(landmark)
|
||||
self.det_faces.append(bbox[0:5])
|
||||
if len(self.det_faces) == 0:
|
||||
return 0
|
||||
if only_keep_largest:
|
||||
h, w, _ = self.input_img.shape
|
||||
self.det_faces, largest_idx = get_largest_face(self.det_faces, h, w)
|
||||
self.all_landmarks_5 = [self.all_landmarks_5[largest_idx]]
|
||||
elif only_center_face:
|
||||
h, w, _ = self.input_img.shape
|
||||
self.det_faces, center_idx = get_center_face(self.det_faces, h, w)
|
||||
self.all_landmarks_5 = [self.all_landmarks_5[center_idx]]
|
||||
|
||||
# pad blurry images
|
||||
if self.pad_blur:
|
||||
self.pad_input_imgs = []
|
||||
for landmarks in self.all_landmarks_5:
|
||||
# get landmarks
|
||||
eye_left = landmarks[0, :]
|
||||
eye_right = landmarks[1, :]
|
||||
eye_avg = (eye_left + eye_right) * 0.5
|
||||
mouth_avg = (landmarks[3, :] + landmarks[4, :]) * 0.5
|
||||
eye_to_eye = eye_right - eye_left
|
||||
eye_to_mouth = mouth_avg - eye_avg
|
||||
|
||||
# Get the oriented crop rectangle
|
||||
# x: half width of the oriented crop rectangle
|
||||
x = eye_to_eye - np.flipud(eye_to_mouth) * [-1, 1]
|
||||
# - np.flipud(eye_to_mouth) * [-1, 1]: rotate 90 clockwise
|
||||
# norm with the hypotenuse: get the direction
|
||||
x /= np.hypot(*x) # get the hypotenuse of a right triangle
|
||||
rect_scale = 1.5
|
||||
x *= max(np.hypot(*eye_to_eye) * 2.0 * rect_scale, np.hypot(*eye_to_mouth) * 1.8 * rect_scale)
|
||||
# y: half height of the oriented crop rectangle
|
||||
y = np.flipud(x) * [-1, 1]
|
||||
|
||||
# c: center
|
||||
c = eye_avg + eye_to_mouth * 0.1
|
||||
# quad: (left_top, left_bottom, right_bottom, right_top)
|
||||
quad = np.stack([c - x - y, c - x + y, c + x + y, c + x - y])
|
||||
# qsize: side length of the square
|
||||
qsize = np.hypot(*x) * 2
|
||||
border = max(int(np.rint(qsize * 0.1)), 3)
|
||||
|
||||
# get pad
|
||||
# pad: (width_left, height_top, width_right, height_bottom)
|
||||
pad = (int(np.floor(min(quad[:, 0]))), int(np.floor(min(quad[:, 1]))), int(np.ceil(max(quad[:, 0]))),
|
||||
int(np.ceil(max(quad[:, 1]))))
|
||||
pad = [
|
||||
max(-pad[0] + border, 1),
|
||||
max(-pad[1] + border, 1),
|
||||
max(pad[2] - self.input_img.shape[0] + border, 1),
|
||||
max(pad[3] - self.input_img.shape[1] + border, 1)
|
||||
]
|
||||
|
||||
if max(pad) > 1:
|
||||
# pad image
|
||||
pad_img = np.pad(self.input_img, ((pad[1], pad[3]), (pad[0], pad[2]), (0, 0)), 'reflect')
|
||||
# modify landmark coords
|
||||
landmarks[:, 0] += pad[0]
|
||||
landmarks[:, 1] += pad[1]
|
||||
# blur pad images
|
||||
h, w, _ = pad_img.shape
|
||||
y, x, _ = np.ogrid[:h, :w, :1]
|
||||
mask = np.maximum(1.0 - np.minimum(np.float32(x) / pad[0],
|
||||
np.float32(w - 1 - x) / pad[2]),
|
||||
1.0 - np.minimum(np.float32(y) / pad[1],
|
||||
np.float32(h - 1 - y) / pad[3]))
|
||||
blur = int(qsize * blur_ratio)
|
||||
if blur % 2 == 0:
|
||||
blur += 1
|
||||
blur_img = cv2.boxFilter(pad_img, 0, ksize=(blur, blur))
|
||||
# blur_img = cv2.GaussianBlur(pad_img, (blur, blur), 0)
|
||||
|
||||
pad_img = pad_img.astype('float32')
|
||||
pad_img += (blur_img - pad_img) * np.clip(mask * 3.0 + 1.0, 0.0, 1.0)
|
||||
pad_img += (np.median(pad_img, axis=(0, 1)) - pad_img) * np.clip(mask, 0.0, 1.0)
|
||||
pad_img = np.clip(pad_img, 0, 255) # float32, [0, 255]
|
||||
self.pad_input_imgs.append(pad_img)
|
||||
else:
|
||||
self.pad_input_imgs.append(np.copy(self.input_img))
|
||||
|
||||
return len(self.all_landmarks_5)
|
||||
|
||||
def align_warp_face(self, save_cropped_path=None, border_mode='constant'):
|
||||
"""Align and warp faces with face template.
|
||||
"""
|
||||
if self.pad_blur:
|
||||
assert len(self.pad_input_imgs) == len(
|
||||
self.all_landmarks_5), f'Mismatched samples: {len(self.pad_input_imgs)} and {len(self.all_landmarks_5)}'
|
||||
for idx, landmark in enumerate(self.all_landmarks_5):
|
||||
# use 5 landmarks to get affine matrix
|
||||
# use cv2.LMEDS method for the equivalence to skimage transform
|
||||
# ref: https://blog.csdn.net/yichxi/article/details/115827338
|
||||
affine_matrix = cv2.estimateAffinePartial2D(landmark, self.face_template, method=cv2.LMEDS)[0]
|
||||
self.affine_matrices.append(affine_matrix)
|
||||
# warp and crop faces
|
||||
if border_mode == 'constant':
|
||||
border_mode = cv2.BORDER_CONSTANT
|
||||
elif border_mode == 'reflect101':
|
||||
border_mode = cv2.BORDER_REFLECT101
|
||||
elif border_mode == 'reflect':
|
||||
border_mode = cv2.BORDER_REFLECT
|
||||
if self.pad_blur:
|
||||
input_img = self.pad_input_imgs[idx]
|
||||
else:
|
||||
input_img = self.input_img
|
||||
cropped_face = cv2.warpAffine(
|
||||
input_img, affine_matrix, self.face_size, borderMode=border_mode, borderValue=(135, 133, 132)) # gray
|
||||
self.cropped_faces.append(cropped_face)
|
||||
# save the cropped face
|
||||
if save_cropped_path is not None:
|
||||
path = os.path.splitext(save_cropped_path)[0]
|
||||
save_path = f'{path}_{idx:02d}.{self.save_ext}'
|
||||
imwrite(cropped_face, save_path)
|
||||
|
||||
def get_inverse_affine(self, save_inverse_affine_path=None):
|
||||
"""Get inverse affine matrix."""
|
||||
for idx, affine_matrix in enumerate(self.affine_matrices):
|
||||
inverse_affine = cv2.invertAffineTransform(affine_matrix)
|
||||
inverse_affine *= self.upscale_factor
|
||||
self.inverse_affine_matrices.append(inverse_affine)
|
||||
# save inverse affine matrices
|
||||
if save_inverse_affine_path is not None:
|
||||
path, _ = os.path.splitext(save_inverse_affine_path)
|
||||
save_path = f'{path}_{idx:02d}.pth'
|
||||
torch.save(inverse_affine, save_path)
|
||||
|
||||
def add_restored_face(self, face):
|
||||
self.restored_faces.append(face)
|
||||
|
||||
def paste_faces_to_input_image(self, save_path=None, upsample_img=None):
|
||||
h, w, _ = self.input_img.shape
|
||||
h_up, w_up = int(h * self.upscale_factor), int(w * self.upscale_factor)
|
||||
|
||||
if upsample_img is None:
|
||||
# simply resize the background
|
||||
upsample_img = cv2.resize(self.input_img, (w_up, h_up), interpolation=cv2.INTER_LANCZOS4)
|
||||
else:
|
||||
upsample_img = cv2.resize(upsample_img, (w_up, h_up), interpolation=cv2.INTER_LANCZOS4)
|
||||
|
||||
assert len(self.restored_faces) == len(
|
||||
self.inverse_affine_matrices), ('length of restored_faces and affine_matrices are different.')
|
||||
for restored_face, inverse_affine in zip(self.restored_faces, self.inverse_affine_matrices):
|
||||
# Add an offset to inverse affine matrix, for more precise back alignment
|
||||
if self.upscale_factor > 1:
|
||||
extra_offset = 0.5 * self.upscale_factor
|
||||
else:
|
||||
extra_offset = 0
|
||||
inverse_affine[:, 2] += extra_offset
|
||||
inv_restored = cv2.warpAffine(restored_face, inverse_affine, (w_up, h_up))
|
||||
|
||||
if self.use_parse:
|
||||
# inference
|
||||
face_input = cv2.resize(restored_face, (512, 512), interpolation=cv2.INTER_LINEAR)
|
||||
face_input = img2tensor(face_input.astype('float32') / 255., bgr2rgb=True, float32=True)
|
||||
normalize(face_input, (0.5, 0.5, 0.5), (0.5, 0.5, 0.5), inplace=True)
|
||||
face_input = torch.unsqueeze(face_input, 0).to(self.device)
|
||||
with torch.no_grad():
|
||||
out = self.face_parse(face_input)[0]
|
||||
out = out.argmax(dim=1).squeeze().cpu().numpy()
|
||||
|
||||
mask = np.zeros(out.shape)
|
||||
MASK_COLORMAP = [0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0, 255, 0, 0, 0]
|
||||
for idx, color in enumerate(MASK_COLORMAP):
|
||||
mask[out == idx] = color
|
||||
# blur the mask
|
||||
mask = cv2.GaussianBlur(mask, (101, 101), 11)
|
||||
mask = cv2.GaussianBlur(mask, (101, 101), 11)
|
||||
# remove the black borders
|
||||
thres = 10
|
||||
mask[:thres, :] = 0
|
||||
mask[-thres:, :] = 0
|
||||
mask[:, :thres] = 0
|
||||
mask[:, -thres:] = 0
|
||||
mask = mask / 255.
|
||||
|
||||
mask = cv2.resize(mask, restored_face.shape[:2])
|
||||
mask = cv2.warpAffine(mask, inverse_affine, (w_up, h_up), flags=3)
|
||||
inv_soft_mask = mask[:, :, None]
|
||||
pasted_face = inv_restored
|
||||
|
||||
else: # use square parse maps
|
||||
mask = np.ones(self.face_size, dtype=np.float32)
|
||||
inv_mask = cv2.warpAffine(mask, inverse_affine, (w_up, h_up))
|
||||
# remove the black borders
|
||||
inv_mask_erosion = cv2.erode(
|
||||
inv_mask, np.ones((int(2 * self.upscale_factor), int(2 * self.upscale_factor)), np.uint8))
|
||||
pasted_face = inv_mask_erosion[:, :, None] * inv_restored
|
||||
total_face_area = np.sum(inv_mask_erosion) # // 3
|
||||
# compute the fusion edge based on the area of face
|
||||
w_edge = int(total_face_area**0.5) // 20
|
||||
erosion_radius = w_edge * 2
|
||||
inv_mask_center = cv2.erode(inv_mask_erosion, np.ones((erosion_radius, erosion_radius), np.uint8))
|
||||
blur_size = w_edge * 2
|
||||
inv_soft_mask = cv2.GaussianBlur(inv_mask_center, (blur_size + 1, blur_size + 1), 0)
|
||||
if len(upsample_img.shape) == 2: # upsample_img is gray image
|
||||
upsample_img = upsample_img[:, :, None]
|
||||
inv_soft_mask = inv_soft_mask[:, :, None]
|
||||
|
||||
if len(upsample_img.shape) == 3 and upsample_img.shape[2] == 4: # alpha channel
|
||||
alpha = upsample_img[:, :, 3:]
|
||||
upsample_img = inv_soft_mask * pasted_face + (1 - inv_soft_mask) * upsample_img[:, :, 0:3]
|
||||
upsample_img = np.concatenate((upsample_img, alpha), axis=2)
|
||||
else:
|
||||
upsample_img = inv_soft_mask * pasted_face + (1 - inv_soft_mask) * upsample_img
|
||||
|
||||
if np.max(upsample_img) > 256: # 16-bit image
|
||||
upsample_img = upsample_img.astype(np.uint16)
|
||||
else:
|
||||
upsample_img = upsample_img.astype(np.uint8)
|
||||
if save_path is not None:
|
||||
path = os.path.splitext(save_path)[0]
|
||||
save_path = f'{path}.{self.save_ext}'
|
||||
imwrite(upsample_img, save_path)
|
||||
return upsample_img
|
||||
|
||||
def clean_all(self):
|
||||
self.all_landmarks_5 = []
|
||||
self.restored_faces = []
|
||||
self.affine_matrices = []
|
||||
self.cropped_faces = []
|
||||
self.inverse_affine_matrices = []
|
||||
self.det_faces = []
|
||||
self.pad_input_imgs = []
|
||||
@@ -0,0 +1,250 @@
|
||||
import cv2
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
|
||||
def compute_increased_bbox(bbox, increase_area, preserve_aspect=True):
|
||||
left, top, right, bot = bbox
|
||||
width = right - left
|
||||
height = bot - top
|
||||
|
||||
if preserve_aspect:
|
||||
width_increase = max(increase_area, ((1 + 2 * increase_area) * height - width) / (2 * width))
|
||||
height_increase = max(increase_area, ((1 + 2 * increase_area) * width - height) / (2 * height))
|
||||
else:
|
||||
width_increase = height_increase = increase_area
|
||||
left = int(left - width_increase * width)
|
||||
top = int(top - height_increase * height)
|
||||
right = int(right + width_increase * width)
|
||||
bot = int(bot + height_increase * height)
|
||||
return (left, top, right, bot)
|
||||
|
||||
|
||||
def get_valid_bboxes(bboxes, h, w):
|
||||
left = max(bboxes[0], 0)
|
||||
top = max(bboxes[1], 0)
|
||||
right = min(bboxes[2], w)
|
||||
bottom = min(bboxes[3], h)
|
||||
return (left, top, right, bottom)
|
||||
|
||||
|
||||
def align_crop_face_landmarks(img,
|
||||
landmarks,
|
||||
output_size,
|
||||
transform_size=None,
|
||||
enable_padding=True,
|
||||
return_inverse_affine=False,
|
||||
shrink_ratio=(1, 1)):
|
||||
"""Align and crop face with landmarks.
|
||||
|
||||
The output_size and transform_size are based on width. The height is
|
||||
adjusted based on shrink_ratio_h/shring_ration_w.
|
||||
|
||||
Modified from:
|
||||
https://github.com/NVlabs/ffhq-dataset/blob/master/download_ffhq.py
|
||||
|
||||
Args:
|
||||
img (Numpy array): Input image.
|
||||
landmarks (Numpy array): 5 or 68 or 98 landmarks.
|
||||
output_size (int): Output face size.
|
||||
transform_size (ing): Transform size. Usually the four time of
|
||||
output_size.
|
||||
enable_padding (float): Default: True.
|
||||
shrink_ratio (float | tuple[float] | list[float]): Shring the whole
|
||||
face for height and width (crop larger area). Default: (1, 1).
|
||||
|
||||
Returns:
|
||||
(Numpy array): Cropped face.
|
||||
"""
|
||||
lm_type = 'retinaface_5' # Options: dlib_5, retinaface_5
|
||||
|
||||
if isinstance(shrink_ratio, (float, int)):
|
||||
shrink_ratio = (shrink_ratio, shrink_ratio)
|
||||
if transform_size is None:
|
||||
transform_size = output_size * 4
|
||||
|
||||
# Parse landmarks
|
||||
lm = np.array(landmarks)
|
||||
if lm.shape[0] == 5 and lm_type == 'retinaface_5':
|
||||
eye_left = lm[0]
|
||||
eye_right = lm[1]
|
||||
mouth_avg = (lm[3] + lm[4]) * 0.5
|
||||
elif lm.shape[0] == 5 and lm_type == 'dlib_5':
|
||||
lm_eye_left = lm[2:4]
|
||||
lm_eye_right = lm[0:2]
|
||||
eye_left = np.mean(lm_eye_left, axis=0)
|
||||
eye_right = np.mean(lm_eye_right, axis=0)
|
||||
mouth_avg = lm[4]
|
||||
elif lm.shape[0] == 68:
|
||||
lm_eye_left = lm[36:42]
|
||||
lm_eye_right = lm[42:48]
|
||||
eye_left = np.mean(lm_eye_left, axis=0)
|
||||
eye_right = np.mean(lm_eye_right, axis=0)
|
||||
mouth_avg = (lm[48] + lm[54]) * 0.5
|
||||
elif lm.shape[0] == 98:
|
||||
lm_eye_left = lm[60:68]
|
||||
lm_eye_right = lm[68:76]
|
||||
eye_left = np.mean(lm_eye_left, axis=0)
|
||||
eye_right = np.mean(lm_eye_right, axis=0)
|
||||
mouth_avg = (lm[76] + lm[82]) * 0.5
|
||||
|
||||
eye_avg = (eye_left + eye_right) * 0.5
|
||||
eye_to_eye = eye_right - eye_left
|
||||
eye_to_mouth = mouth_avg - eye_avg
|
||||
|
||||
# Get the oriented crop rectangle
|
||||
# x: half width of the oriented crop rectangle
|
||||
x = eye_to_eye - np.flipud(eye_to_mouth) * [-1, 1]
|
||||
# - np.flipud(eye_to_mouth) * [-1, 1]: rotate 90 clockwise
|
||||
# norm with the hypotenuse: get the direction
|
||||
x /= np.hypot(*x) # get the hypotenuse of a right triangle
|
||||
rect_scale = 1 # TODO: you can edit it to get larger rect
|
||||
x *= max(np.hypot(*eye_to_eye) * 2.0 * rect_scale, np.hypot(*eye_to_mouth) * 1.8 * rect_scale)
|
||||
# y: half height of the oriented crop rectangle
|
||||
y = np.flipud(x) * [-1, 1]
|
||||
|
||||
x *= shrink_ratio[1] # width
|
||||
y *= shrink_ratio[0] # height
|
||||
|
||||
# c: center
|
||||
c = eye_avg + eye_to_mouth * 0.1
|
||||
# quad: (left_top, left_bottom, right_bottom, right_top)
|
||||
quad = np.stack([c - x - y, c - x + y, c + x + y, c + x - y])
|
||||
# qsize: side length of the square
|
||||
qsize = np.hypot(*x) * 2
|
||||
|
||||
quad_ori = np.copy(quad)
|
||||
# Shrink, for large face
|
||||
# TODO: do we really need shrink
|
||||
shrink = int(np.floor(qsize / output_size * 0.5))
|
||||
if shrink > 1:
|
||||
h, w = img.shape[0:2]
|
||||
rsize = (int(np.rint(float(w) / shrink)), int(np.rint(float(h) / shrink)))
|
||||
img = cv2.resize(img, rsize, interpolation=cv2.INTER_AREA)
|
||||
quad /= shrink
|
||||
qsize /= shrink
|
||||
|
||||
# Crop
|
||||
h, w = img.shape[0:2]
|
||||
border = max(int(np.rint(qsize * 0.1)), 3)
|
||||
crop = (int(np.floor(min(quad[:, 0]))), int(np.floor(min(quad[:, 1]))), int(np.ceil(max(quad[:, 0]))),
|
||||
int(np.ceil(max(quad[:, 1]))))
|
||||
crop = (max(crop[0] - border, 0), max(crop[1] - border, 0), min(crop[2] + border, w), min(crop[3] + border, h))
|
||||
if crop[2] - crop[0] < w or crop[3] - crop[1] < h:
|
||||
img = img[crop[1]:crop[3], crop[0]:crop[2], :]
|
||||
quad -= crop[0:2]
|
||||
|
||||
# Pad
|
||||
# pad: (width_left, height_top, width_right, height_bottom)
|
||||
h, w = img.shape[0:2]
|
||||
pad = (int(np.floor(min(quad[:, 0]))), int(np.floor(min(quad[:, 1]))), int(np.ceil(max(quad[:, 0]))),
|
||||
int(np.ceil(max(quad[:, 1]))))
|
||||
pad = (max(-pad[0] + border, 0), max(-pad[1] + border, 0), max(pad[2] - w + border, 0), max(pad[3] - h + border, 0))
|
||||
if enable_padding and max(pad) > border - 4:
|
||||
pad = np.maximum(pad, int(np.rint(qsize * 0.3)))
|
||||
img = np.pad(img, ((pad[1], pad[3]), (pad[0], pad[2]), (0, 0)), 'reflect')
|
||||
h, w = img.shape[0:2]
|
||||
y, x, _ = np.ogrid[:h, :w, :1]
|
||||
mask = np.maximum(1.0 - np.minimum(np.float32(x) / pad[0],
|
||||
np.float32(w - 1 - x) / pad[2]),
|
||||
1.0 - np.minimum(np.float32(y) / pad[1],
|
||||
np.float32(h - 1 - y) / pad[3]))
|
||||
blur = int(qsize * 0.02)
|
||||
if blur % 2 == 0:
|
||||
blur += 1
|
||||
blur_img = cv2.boxFilter(img, 0, ksize=(blur, blur))
|
||||
|
||||
img = img.astype('float32')
|
||||
img += (blur_img - img) * np.clip(mask * 3.0 + 1.0, 0.0, 1.0)
|
||||
img += (np.median(img, axis=(0, 1)) - img) * np.clip(mask, 0.0, 1.0)
|
||||
img = np.clip(img, 0, 255) # float32, [0, 255]
|
||||
quad += pad[:2]
|
||||
|
||||
# Transform use cv2
|
||||
h_ratio = shrink_ratio[0] / shrink_ratio[1]
|
||||
dst_h, dst_w = int(transform_size * h_ratio), transform_size
|
||||
template = np.array([[0, 0], [0, dst_h], [dst_w, dst_h], [dst_w, 0]])
|
||||
# use cv2.LMEDS method for the equivalence to skimage transform
|
||||
# ref: https://blog.csdn.net/yichxi/article/details/115827338
|
||||
affine_matrix = cv2.estimateAffinePartial2D(quad, template, method=cv2.LMEDS)[0]
|
||||
cropped_face = cv2.warpAffine(
|
||||
img, affine_matrix, (dst_w, dst_h), borderMode=cv2.BORDER_CONSTANT, borderValue=(135, 133, 132)) # gray
|
||||
|
||||
if output_size < transform_size:
|
||||
cropped_face = cv2.resize(
|
||||
cropped_face, (output_size, int(output_size * h_ratio)), interpolation=cv2.INTER_LINEAR)
|
||||
|
||||
if return_inverse_affine:
|
||||
dst_h, dst_w = int(output_size * h_ratio), output_size
|
||||
template = np.array([[0, 0], [0, dst_h], [dst_w, dst_h], [dst_w, 0]])
|
||||
# use cv2.LMEDS method for the equivalence to skimage transform
|
||||
# ref: https://blog.csdn.net/yichxi/article/details/115827338
|
||||
affine_matrix = cv2.estimateAffinePartial2D(
|
||||
quad_ori, np.array([[0, 0], [0, output_size], [dst_w, dst_h], [dst_w, 0]]), method=cv2.LMEDS)[0]
|
||||
inverse_affine = cv2.invertAffineTransform(affine_matrix)
|
||||
else:
|
||||
inverse_affine = None
|
||||
return cropped_face, inverse_affine
|
||||
|
||||
|
||||
def paste_face_back(img, face, inverse_affine):
|
||||
h, w = img.shape[0:2]
|
||||
face_h, face_w = face.shape[0:2]
|
||||
inv_restored = cv2.warpAffine(face, inverse_affine, (w, h))
|
||||
mask = np.ones((face_h, face_w, 3), dtype=np.float32)
|
||||
inv_mask = cv2.warpAffine(mask, inverse_affine, (w, h))
|
||||
# remove the black borders
|
||||
inv_mask_erosion = cv2.erode(inv_mask, np.ones((2, 2), np.uint8))
|
||||
inv_restored_remove_border = inv_mask_erosion * inv_restored
|
||||
total_face_area = np.sum(inv_mask_erosion) // 3
|
||||
# compute the fusion edge based on the area of face
|
||||
w_edge = int(total_face_area**0.5) // 20
|
||||
erosion_radius = w_edge * 2
|
||||
inv_mask_center = cv2.erode(inv_mask_erosion, np.ones((erosion_radius, erosion_radius), np.uint8))
|
||||
blur_size = w_edge * 2
|
||||
inv_soft_mask = cv2.GaussianBlur(inv_mask_center, (blur_size + 1, blur_size + 1), 0)
|
||||
img = inv_soft_mask * inv_restored_remove_border + (1 - inv_soft_mask) * img
|
||||
# float32, [0, 255]
|
||||
return img
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import os
|
||||
|
||||
from extras.facexlib.detection import init_detection_model
|
||||
from extras.facexlib.utils.face_restoration_helper import get_largest_face
|
||||
from extras.facexlib.visualization import visualize_detection
|
||||
|
||||
img_path = '/home/wxt/datasets/ffhq/ffhq_wild/00009.png'
|
||||
img_name = os.splitext(os.path.basename(img_path))[0]
|
||||
|
||||
# initialize model
|
||||
det_net = init_detection_model('retinaface_resnet50', half=False)
|
||||
img_ori = cv2.imread(img_path)
|
||||
h, w = img_ori.shape[0:2]
|
||||
# if larger than 800, scale it
|
||||
scale = max(h / 800, w / 800)
|
||||
if scale > 1:
|
||||
img = cv2.resize(img_ori, (int(w / scale), int(h / scale)), interpolation=cv2.INTER_LINEAR)
|
||||
|
||||
with torch.no_grad():
|
||||
bboxes = det_net.detect_faces(img, 0.97)
|
||||
if scale > 1:
|
||||
bboxes *= scale # the score is incorrect
|
||||
bboxes = get_largest_face(bboxes, h, w)[0]
|
||||
visualize_detection(img_ori, [bboxes], f'tmp/{img_name}_det.png')
|
||||
|
||||
landmarks = np.array([[bboxes[i], bboxes[i + 1]] for i in range(5, 15, 2)])
|
||||
|
||||
cropped_face, inverse_affine = align_crop_face_landmarks(
|
||||
img_ori,
|
||||
landmarks,
|
||||
output_size=512,
|
||||
transform_size=None,
|
||||
enable_padding=True,
|
||||
return_inverse_affine=True,
|
||||
shrink_ratio=(1, 1))
|
||||
|
||||
cv2.imwrite(f'tmp/{img_name}_cropeed_face.png', cropped_face)
|
||||
img = paste_face_back(img_ori, cropped_face, inverse_affine)
|
||||
cv2.imwrite(f'tmp/{img_name}_back.png', img)
|
||||
@@ -0,0 +1,118 @@
|
||||
import cv2
|
||||
import os
|
||||
import os.path as osp
|
||||
import torch
|
||||
from torch.hub import download_url_to_file, get_dir
|
||||
from urllib.parse import urlparse
|
||||
|
||||
ROOT_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
|
||||
def imwrite(img, file_path, params=None, auto_mkdir=True):
|
||||
"""Write image to file.
|
||||
|
||||
Args:
|
||||
img (ndarray): Image array to be written.
|
||||
file_path (str): Image file path.
|
||||
params (None or list): Same as opencv's :func:`imwrite` interface.
|
||||
auto_mkdir (bool): If the parent folder of `file_path` does not exist,
|
||||
whether to create it automatically.
|
||||
|
||||
Returns:
|
||||
bool: Successful or not.
|
||||
"""
|
||||
if auto_mkdir:
|
||||
dir_name = os.path.abspath(os.path.dirname(file_path))
|
||||
os.makedirs(dir_name, exist_ok=True)
|
||||
return cv2.imwrite(file_path, img, params)
|
||||
|
||||
|
||||
def img2tensor(imgs, bgr2rgb=True, float32=True):
|
||||
"""Numpy array to tensor.
|
||||
|
||||
Args:
|
||||
imgs (list[ndarray] | ndarray): Input images.
|
||||
bgr2rgb (bool): Whether to change bgr to rgb.
|
||||
float32 (bool): Whether to change to float32.
|
||||
|
||||
Returns:
|
||||
list[tensor] | tensor: Tensor images. If returned results only have
|
||||
one element, just return tensor.
|
||||
"""
|
||||
|
||||
def _totensor(img, bgr2rgb, float32):
|
||||
if img.shape[2] == 3 and bgr2rgb:
|
||||
if img.dtype == 'float64':
|
||||
img = img.astype('float32')
|
||||
img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
|
||||
img = torch.from_numpy(img.transpose(2, 0, 1))
|
||||
if float32:
|
||||
img = img.float()
|
||||
return img
|
||||
|
||||
if isinstance(imgs, list):
|
||||
return [_totensor(img, bgr2rgb, float32) for img in imgs]
|
||||
else:
|
||||
return _totensor(imgs, bgr2rgb, float32)
|
||||
|
||||
|
||||
def load_file_from_url(url, model_dir=None, progress=True, file_name=None, save_dir=None):
|
||||
"""Ref:https://github.com/1adrianb/face-alignment/blob/master/face_alignment/utils.py
|
||||
"""
|
||||
if model_dir is None:
|
||||
hub_dir = get_dir()
|
||||
model_dir = os.path.join(hub_dir, 'checkpoints')
|
||||
|
||||
if save_dir is None:
|
||||
save_dir = os.path.join(ROOT_DIR, model_dir)
|
||||
os.makedirs(save_dir, exist_ok=True)
|
||||
|
||||
parts = urlparse(url)
|
||||
filename = os.path.basename(parts.path)
|
||||
if file_name is not None:
|
||||
filename = file_name
|
||||
cached_file = os.path.abspath(os.path.join(save_dir, filename))
|
||||
if not os.path.exists(cached_file):
|
||||
print(f'Downloading: "{url}" to {cached_file}\n')
|
||||
download_url_to_file(url, cached_file, hash_prefix=None, progress=progress)
|
||||
return cached_file
|
||||
|
||||
|
||||
def scandir(dir_path, suffix=None, recursive=False, full_path=False):
|
||||
"""Scan a directory to find the interested files.
|
||||
Args:
|
||||
dir_path (str): Path of the directory.
|
||||
suffix (str | tuple(str), optional): File suffix that we are
|
||||
interested in. Default: None.
|
||||
recursive (bool, optional): If set to True, recursively scan the
|
||||
directory. Default: False.
|
||||
full_path (bool, optional): If set to True, include the dir_path.
|
||||
Default: False.
|
||||
Returns:
|
||||
A generator for all the interested files with relative paths.
|
||||
"""
|
||||
|
||||
if (suffix is not None) and not isinstance(suffix, (str, tuple)):
|
||||
raise TypeError('"suffix" must be a string or tuple of strings')
|
||||
|
||||
root = dir_path
|
||||
|
||||
def _scandir(dir_path, suffix, recursive):
|
||||
for entry in os.scandir(dir_path):
|
||||
if not entry.name.startswith('.') and entry.is_file():
|
||||
if full_path:
|
||||
return_path = entry.path
|
||||
else:
|
||||
return_path = osp.relpath(entry.path, root)
|
||||
|
||||
if suffix is None:
|
||||
yield return_path
|
||||
elif return_path.endswith(suffix):
|
||||
yield return_path
|
||||
else:
|
||||
if recursive:
|
||||
yield from _scandir(entry.path, suffix=suffix, recursive=recursive)
|
||||
else:
|
||||
continue
|
||||
|
||||
return _scandir(dir_path, suffix=suffix, recursive=recursive)
|
||||
@@ -0,0 +1,130 @@
|
||||
import sys
|
||||
|
||||
import modules.config
|
||||
import numpy as np
|
||||
import torch
|
||||
from extras.GroundingDINO.util.inference import default_groundingdino
|
||||
from extras.sam.predictor import SamPredictor
|
||||
from rembg import remove, new_session
|
||||
from segment_anything import sam_model_registry
|
||||
from segment_anything.utils.amg import remove_small_regions
|
||||
|
||||
|
||||
class SAMOptions:
|
||||
def __init__(self,
|
||||
# GroundingDINO
|
||||
dino_prompt: str = '',
|
||||
dino_box_threshold=0.3,
|
||||
dino_text_threshold=0.25,
|
||||
dino_erode_or_dilate=0,
|
||||
dino_debug=False,
|
||||
|
||||
# SAM
|
||||
max_detections=2,
|
||||
model_type='vit_b'
|
||||
):
|
||||
self.dino_prompt = dino_prompt
|
||||
self.dino_box_threshold = dino_box_threshold
|
||||
self.dino_text_threshold = dino_text_threshold
|
||||
self.dino_erode_or_dilate = dino_erode_or_dilate
|
||||
self.dino_debug = dino_debug
|
||||
self.max_detections = max_detections
|
||||
self.model_type = model_type
|
||||
|
||||
|
||||
def optimize_masks(masks: torch.Tensor) -> torch.Tensor:
|
||||
"""
|
||||
removes small disconnected regions and holes
|
||||
"""
|
||||
fine_masks = []
|
||||
for mask in masks.to('cpu').numpy(): # masks: [num_masks, 1, h, w]
|
||||
fine_masks.append(remove_small_regions(mask[0], 400, mode="holes")[0])
|
||||
masks = np.stack(fine_masks, axis=0)[:, np.newaxis]
|
||||
return torch.from_numpy(masks)
|
||||
|
||||
|
||||
def generate_mask_from_image(image: np.ndarray, mask_model: str = 'sam', extras=None,
|
||||
sam_options: SAMOptions | None = SAMOptions) -> tuple[np.ndarray | None, int | None, int | None, int | None]:
|
||||
dino_detection_count = 0
|
||||
sam_detection_count = 0
|
||||
sam_detection_on_mask_count = 0
|
||||
|
||||
if image is None:
|
||||
return None, dino_detection_count, sam_detection_count, sam_detection_on_mask_count
|
||||
|
||||
if extras is None:
|
||||
extras = {}
|
||||
|
||||
if 'image' in image:
|
||||
image = image['image']
|
||||
|
||||
if mask_model != 'sam' or sam_options is None:
|
||||
result = remove(
|
||||
image,
|
||||
session=new_session(mask_model, **extras),
|
||||
only_mask=True,
|
||||
**extras
|
||||
)
|
||||
|
||||
return result, dino_detection_count, sam_detection_count, sam_detection_on_mask_count
|
||||
|
||||
detections, boxes, logits, phrases = default_groundingdino(
|
||||
image=image,
|
||||
caption=sam_options.dino_prompt,
|
||||
box_threshold=sam_options.dino_box_threshold,
|
||||
text_threshold=sam_options.dino_text_threshold
|
||||
)
|
||||
|
||||
H, W = image.shape[0], image.shape[1]
|
||||
boxes = boxes * torch.Tensor([W, H, W, H])
|
||||
boxes[:, :2] = boxes[:, :2] - boxes[:, 2:] / 2
|
||||
boxes[:, 2:] = boxes[:, 2:] + boxes[:, :2]
|
||||
|
||||
sam_checkpoint = modules.config.download_sam_model(sam_options.model_type)
|
||||
sam = sam_model_registry[sam_options.model_type](checkpoint=sam_checkpoint)
|
||||
|
||||
sam_predictor = SamPredictor(sam)
|
||||
final_mask_tensor = torch.zeros((image.shape[0], image.shape[1]))
|
||||
dino_detection_count = boxes.size(0)
|
||||
|
||||
if dino_detection_count > 0:
|
||||
sam_predictor.set_image(image)
|
||||
|
||||
if sam_options.dino_erode_or_dilate != 0:
|
||||
for index in range(boxes.size(0)):
|
||||
assert boxes.size(1) == 4
|
||||
boxes[index][0] -= sam_options.dino_erode_or_dilate
|
||||
boxes[index][1] -= sam_options.dino_erode_or_dilate
|
||||
boxes[index][2] += sam_options.dino_erode_or_dilate
|
||||
boxes[index][3] += sam_options.dino_erode_or_dilate
|
||||
|
||||
if sam_options.dino_debug:
|
||||
from PIL import ImageDraw, Image
|
||||
debug_dino_image = Image.new("RGB", (image.shape[1], image.shape[0]), color="black")
|
||||
draw = ImageDraw.Draw(debug_dino_image)
|
||||
for box in boxes.numpy():
|
||||
draw.rectangle(box.tolist(), fill="white")
|
||||
return np.array(debug_dino_image), dino_detection_count, sam_detection_count, sam_detection_on_mask_count
|
||||
|
||||
transformed_boxes = sam_predictor.transform.apply_boxes_torch(boxes, image.shape[:2])
|
||||
masks, _, _ = sam_predictor.predict_torch(
|
||||
point_coords=None,
|
||||
point_labels=None,
|
||||
boxes=transformed_boxes,
|
||||
multimask_output=False,
|
||||
)
|
||||
|
||||
masks = optimize_masks(masks)
|
||||
sam_detection_count = len(masks)
|
||||
if sam_options.max_detections == 0:
|
||||
sam_options.max_detections = sys.maxsize
|
||||
sam_objects = min(len(logits), sam_options.max_detections)
|
||||
for obj_ind in range(sam_objects):
|
||||
mask_tensor = masks[obj_ind][0]
|
||||
final_mask_tensor += mask_tensor
|
||||
sam_detection_on_mask_count += 1
|
||||
|
||||
final_mask_tensor = (final_mask_tensor > 0).to('cpu').numpy()
|
||||
mask_image = np.dstack((final_mask_tensor, final_mask_tensor, final_mask_tensor)) * 255
|
||||
mask_image = np.array(mask_image, dtype=np.uint8)
|
||||
return mask_image, dino_detection_count, sam_detection_count, sam_detection_on_mask_count
|
||||
@@ -0,0 +1,63 @@
|
||||
import os
|
||||
import torch
|
||||
import ldm_patched.modules.model_management as model_management
|
||||
|
||||
from torchvision import transforms
|
||||
from torchvision.transforms.functional import InterpolationMode
|
||||
from modules.model_loader import load_file_from_url
|
||||
from modules.config import path_clip_vision
|
||||
from ldm_patched.modules.model_patcher import ModelPatcher
|
||||
from extras.BLIP.models.blip import blip_decoder
|
||||
|
||||
|
||||
blip_image_eval_size = 384
|
||||
blip_repo_root = os.path.join(os.path.dirname(__file__), 'BLIP')
|
||||
|
||||
|
||||
class Interrogator:
|
||||
def __init__(self):
|
||||
self.blip_model = None
|
||||
self.load_device = torch.device('cpu')
|
||||
self.offload_device = torch.device('cpu')
|
||||
self.dtype = torch.float32
|
||||
|
||||
@torch.no_grad()
|
||||
@torch.inference_mode()
|
||||
def interrogate(self, img_rgb):
|
||||
if self.blip_model is None:
|
||||
filename = load_file_from_url(
|
||||
url='https://huggingface.co/lllyasviel/misc/resolve/main/model_base_caption_capfilt_large.pth',
|
||||
model_dir=path_clip_vision,
|
||||
file_name='model_base_caption_capfilt_large.pth',
|
||||
)
|
||||
|
||||
model = blip_decoder(pretrained=filename, image_size=blip_image_eval_size, vit='base',
|
||||
med_config=os.path.join(blip_repo_root, "configs", "med_config.json"))
|
||||
model.eval()
|
||||
|
||||
self.load_device = model_management.text_encoder_device()
|
||||
self.offload_device = model_management.text_encoder_offload_device()
|
||||
self.dtype = torch.float32
|
||||
|
||||
model.to(self.offload_device)
|
||||
|
||||
if model_management.should_use_fp16(device=self.load_device):
|
||||
model.half()
|
||||
self.dtype = torch.float16
|
||||
|
||||
self.blip_model = ModelPatcher(model, load_device=self.load_device, offload_device=self.offload_device)
|
||||
|
||||
model_management.load_model_gpu(self.blip_model)
|
||||
|
||||
gpu_image = transforms.Compose([
|
||||
transforms.ToTensor(),
|
||||
transforms.Resize((blip_image_eval_size, blip_image_eval_size), interpolation=InterpolationMode.BICUBIC),
|
||||
transforms.Normalize((0.48145466, 0.4578275, 0.40821073), (0.26862954, 0.26130258, 0.27577711))
|
||||
])(img_rgb).unsqueeze(0).to(device=self.load_device, dtype=self.dtype)
|
||||
|
||||
caption = self.blip_model.model.generate(gpu_image, sample=True, num_beams=1, max_length=75)[0]
|
||||
|
||||
return caption
|
||||
|
||||
|
||||
default_interrogator = Interrogator().interrogate
|
||||
@@ -0,0 +1,284 @@
|
||||
import torch
|
||||
import ldm_patched.modules.clip_vision
|
||||
import safetensors.torch as sf
|
||||
import ldm_patched.modules.model_management as model_management
|
||||
import ldm_patched.ldm.modules.attention as attention
|
||||
|
||||
from extras.resampler import Resampler
|
||||
from ldm_patched.modules.model_patcher import ModelPatcher
|
||||
from modules.core import numpy_to_pytorch
|
||||
from modules.ops import use_patched_ops
|
||||
from ldm_patched.modules.ops import manual_cast
|
||||
|
||||
|
||||
SD_V12_CHANNELS = [320] * 4 + [640] * 4 + [1280] * 4 + [1280] * 6 + [640] * 6 + [320] * 6 + [1280] * 2
|
||||
SD_XL_CHANNELS = [640] * 8 + [1280] * 40 + [1280] * 60 + [640] * 12 + [1280] * 20
|
||||
|
||||
|
||||
def sdp(q, k, v, extra_options):
|
||||
return attention.optimized_attention(q, k, v, heads=extra_options["n_heads"], mask=None)
|
||||
|
||||
|
||||
class ImageProjModel(torch.nn.Module):
|
||||
def __init__(self, cross_attention_dim=1024, clip_embeddings_dim=1024, clip_extra_context_tokens=4):
|
||||
super().__init__()
|
||||
|
||||
self.cross_attention_dim = cross_attention_dim
|
||||
self.clip_extra_context_tokens = clip_extra_context_tokens
|
||||
self.proj = torch.nn.Linear(clip_embeddings_dim, self.clip_extra_context_tokens * cross_attention_dim)
|
||||
self.norm = torch.nn.LayerNorm(cross_attention_dim)
|
||||
|
||||
def forward(self, image_embeds):
|
||||
embeds = image_embeds
|
||||
clip_extra_context_tokens = self.proj(embeds).reshape(-1, self.clip_extra_context_tokens,
|
||||
self.cross_attention_dim)
|
||||
clip_extra_context_tokens = self.norm(clip_extra_context_tokens)
|
||||
return clip_extra_context_tokens
|
||||
|
||||
|
||||
class To_KV(torch.nn.Module):
|
||||
def __init__(self, cross_attention_dim):
|
||||
super().__init__()
|
||||
|
||||
channels = SD_XL_CHANNELS if cross_attention_dim == 2048 else SD_V12_CHANNELS
|
||||
self.to_kvs = torch.nn.ModuleList(
|
||||
[torch.nn.Linear(cross_attention_dim, channel, bias=False) for channel in channels])
|
||||
|
||||
def load_state_dict_ordered(self, sd):
|
||||
state_dict = []
|
||||
for i in range(4096):
|
||||
for k in ['k', 'v']:
|
||||
key = f'{i}.to_{k}_ip.weight'
|
||||
if key in sd:
|
||||
state_dict.append(sd[key])
|
||||
for i, v in enumerate(state_dict):
|
||||
self.to_kvs[i].weight = torch.nn.Parameter(v, requires_grad=False)
|
||||
|
||||
|
||||
class IPAdapterModel(torch.nn.Module):
|
||||
def __init__(self, state_dict, plus, cross_attention_dim=768, clip_embeddings_dim=1024, clip_extra_context_tokens=4,
|
||||
sdxl_plus=False):
|
||||
super().__init__()
|
||||
self.plus = plus
|
||||
if self.plus:
|
||||
self.image_proj_model = Resampler(
|
||||
dim=1280 if sdxl_plus else cross_attention_dim,
|
||||
depth=4,
|
||||
dim_head=64,
|
||||
heads=20 if sdxl_plus else 12,
|
||||
num_queries=clip_extra_context_tokens,
|
||||
embedding_dim=clip_embeddings_dim,
|
||||
output_dim=cross_attention_dim,
|
||||
ff_mult=4
|
||||
)
|
||||
else:
|
||||
self.image_proj_model = ImageProjModel(
|
||||
cross_attention_dim=cross_attention_dim,
|
||||
clip_embeddings_dim=clip_embeddings_dim,
|
||||
clip_extra_context_tokens=clip_extra_context_tokens
|
||||
)
|
||||
|
||||
self.image_proj_model.load_state_dict(state_dict["image_proj"])
|
||||
self.ip_layers = To_KV(cross_attention_dim)
|
||||
self.ip_layers.load_state_dict_ordered(state_dict["ip_adapter"])
|
||||
|
||||
|
||||
clip_vision: ldm_patched.modules.clip_vision.ClipVisionModel = None
|
||||
ip_negative: torch.Tensor = None
|
||||
ip_adapters: dict = {}
|
||||
|
||||
|
||||
def load_ip_adapter(clip_vision_path, ip_negative_path, ip_adapter_path):
|
||||
global clip_vision, ip_negative, ip_adapters
|
||||
|
||||
if clip_vision is None and isinstance(clip_vision_path, str):
|
||||
clip_vision = ldm_patched.modules.clip_vision.load(clip_vision_path)
|
||||
|
||||
if ip_negative is None and isinstance(ip_negative_path, str):
|
||||
ip_negative = sf.load_file(ip_negative_path)['data']
|
||||
|
||||
if not isinstance(ip_adapter_path, str) or ip_adapter_path in ip_adapters:
|
||||
return
|
||||
|
||||
load_device = model_management.get_torch_device()
|
||||
offload_device = torch.device('cpu')
|
||||
|
||||
use_fp16 = model_management.should_use_fp16(device=load_device)
|
||||
ip_state_dict = torch.load(ip_adapter_path, map_location="cpu", weights_only=True)
|
||||
plus = "latents" in ip_state_dict["image_proj"]
|
||||
cross_attention_dim = ip_state_dict["ip_adapter"]["1.to_k_ip.weight"].shape[1]
|
||||
sdxl = cross_attention_dim == 2048
|
||||
sdxl_plus = sdxl and plus
|
||||
|
||||
if plus:
|
||||
clip_extra_context_tokens = ip_state_dict["image_proj"]["latents"].shape[1]
|
||||
clip_embeddings_dim = ip_state_dict["image_proj"]["latents"].shape[2]
|
||||
else:
|
||||
clip_extra_context_tokens = ip_state_dict["image_proj"]["proj.weight"].shape[0] // cross_attention_dim
|
||||
clip_embeddings_dim = None
|
||||
|
||||
with use_patched_ops(manual_cast):
|
||||
ip_adapter = IPAdapterModel(
|
||||
ip_state_dict,
|
||||
plus=plus,
|
||||
cross_attention_dim=cross_attention_dim,
|
||||
clip_embeddings_dim=clip_embeddings_dim,
|
||||
clip_extra_context_tokens=clip_extra_context_tokens,
|
||||
sdxl_plus=sdxl_plus
|
||||
)
|
||||
|
||||
ip_adapter.sdxl = sdxl
|
||||
ip_adapter.load_device = load_device
|
||||
ip_adapter.offload_device = offload_device
|
||||
ip_adapter.dtype = torch.float16 if use_fp16 else torch.float32
|
||||
ip_adapter.to(offload_device, dtype=ip_adapter.dtype)
|
||||
|
||||
image_proj_model = ModelPatcher(model=ip_adapter.image_proj_model, load_device=load_device,
|
||||
offload_device=offload_device)
|
||||
ip_layers = ModelPatcher(model=ip_adapter.ip_layers, load_device=load_device,
|
||||
offload_device=offload_device)
|
||||
|
||||
ip_adapters[ip_adapter_path] = dict(
|
||||
ip_adapter=ip_adapter,
|
||||
image_proj_model=image_proj_model,
|
||||
ip_layers=ip_layers,
|
||||
ip_unconds=None
|
||||
)
|
||||
|
||||
return
|
||||
|
||||
|
||||
@torch.no_grad()
|
||||
@torch.inference_mode()
|
||||
def clip_preprocess(image):
|
||||
mean = torch.tensor([0.48145466, 0.4578275, 0.40821073], device=image.device, dtype=image.dtype).view([1, 3, 1, 1])
|
||||
std = torch.tensor([0.26862954, 0.26130258, 0.27577711], device=image.device, dtype=image.dtype).view([1, 3, 1, 1])
|
||||
image = image.movedim(-1, 1)
|
||||
|
||||
# https://github.com/tencent-ailab/IP-Adapter/blob/d580c50a291566bbf9fc7ac0f760506607297e6d/README.md?plain=1#L75
|
||||
B, C, H, W = image.shape
|
||||
assert H == 224 and W == 224
|
||||
|
||||
return (image - mean) / std
|
||||
|
||||
|
||||
@torch.no_grad()
|
||||
@torch.inference_mode()
|
||||
def preprocess(img, ip_adapter_path):
|
||||
global ip_adapters
|
||||
entry = ip_adapters[ip_adapter_path]
|
||||
|
||||
ldm_patched.modules.model_management.load_model_gpu(clip_vision.patcher)
|
||||
pixel_values = clip_preprocess(numpy_to_pytorch(img).to(clip_vision.load_device))
|
||||
outputs = clip_vision.model(pixel_values=pixel_values, output_hidden_states=True)
|
||||
|
||||
ip_adapter = entry['ip_adapter']
|
||||
ip_layers = entry['ip_layers']
|
||||
image_proj_model = entry['image_proj_model']
|
||||
ip_unconds = entry['ip_unconds']
|
||||
|
||||
if ip_adapter.plus:
|
||||
cond = outputs.hidden_states[-2]
|
||||
else:
|
||||
cond = outputs.image_embeds
|
||||
|
||||
cond = cond.to(device=ip_adapter.load_device, dtype=ip_adapter.dtype)
|
||||
|
||||
ldm_patched.modules.model_management.load_model_gpu(image_proj_model)
|
||||
cond = image_proj_model.model(cond).to(device=ip_adapter.load_device, dtype=ip_adapter.dtype)
|
||||
|
||||
ldm_patched.modules.model_management.load_model_gpu(ip_layers)
|
||||
|
||||
if ip_unconds is None:
|
||||
uncond = ip_negative.to(device=ip_adapter.load_device, dtype=ip_adapter.dtype)
|
||||
ip_unconds = [m(uncond).cpu() for m in ip_layers.model.to_kvs]
|
||||
entry['ip_unconds'] = ip_unconds
|
||||
|
||||
ip_conds = [m(cond).cpu() for m in ip_layers.model.to_kvs]
|
||||
|
||||
return ip_conds, ip_unconds
|
||||
|
||||
|
||||
@torch.no_grad()
|
||||
@torch.inference_mode()
|
||||
def patch_model(model, tasks):
|
||||
new_model = model.clone()
|
||||
|
||||
def make_attn_patcher(ip_index):
|
||||
def patcher(n, context_attn2, value_attn2, extra_options):
|
||||
org_dtype = n.dtype
|
||||
current_step = float(model.model.diffusion_model.current_step.detach().cpu().numpy()[0])
|
||||
cond_or_uncond = extra_options['cond_or_uncond']
|
||||
|
||||
q = n
|
||||
k = [context_attn2]
|
||||
v = [value_attn2]
|
||||
b, _, _ = q.shape
|
||||
|
||||
for (cs, ucs), cn_stop, cn_weight in tasks:
|
||||
if current_step < cn_stop:
|
||||
ip_k_c = cs[ip_index * 2].to(q)
|
||||
ip_v_c = cs[ip_index * 2 + 1].to(q)
|
||||
ip_k_uc = ucs[ip_index * 2].to(q)
|
||||
ip_v_uc = ucs[ip_index * 2 + 1].to(q)
|
||||
|
||||
ip_k = torch.cat([(ip_k_c, ip_k_uc)[i] for i in cond_or_uncond], dim=0)
|
||||
ip_v = torch.cat([(ip_v_c, ip_v_uc)[i] for i in cond_or_uncond], dim=0)
|
||||
|
||||
# Midjourney's attention formulation of image prompt (non-official reimplementation)
|
||||
# Written by Lvmin Zhang at Stanford University, 2023 Dec
|
||||
# For non-commercial use only - if you use this in commercial project then
|
||||
# probably it has some intellectual property issues.
|
||||
# Contact lvminzhang@acm.org if you are not sure.
|
||||
|
||||
# Below is the sensitive part with potential intellectual property issues.
|
||||
|
||||
ip_v_mean = torch.mean(ip_v, dim=1, keepdim=True)
|
||||
ip_v_offset = ip_v - ip_v_mean
|
||||
|
||||
B, F, C = ip_k.shape
|
||||
channel_penalty = float(C) / 1280.0
|
||||
weight = cn_weight * channel_penalty
|
||||
|
||||
ip_k = ip_k * weight
|
||||
ip_v = ip_v_offset + ip_v_mean * weight
|
||||
|
||||
k.append(ip_k)
|
||||
v.append(ip_v)
|
||||
|
||||
k = torch.cat(k, dim=1)
|
||||
v = torch.cat(v, dim=1)
|
||||
out = sdp(q, k, v, extra_options)
|
||||
|
||||
|
||||
return out.to(dtype=org_dtype)
|
||||
return patcher
|
||||
|
||||
def set_model_patch_replace(model, number, key):
|
||||
to = model.model_options["transformer_options"]
|
||||
if "patches_replace" not in to:
|
||||
to["patches_replace"] = {}
|
||||
if "attn2" not in to["patches_replace"]:
|
||||
to["patches_replace"]["attn2"] = {}
|
||||
if key not in to["patches_replace"]["attn2"]:
|
||||
to["patches_replace"]["attn2"][key] = make_attn_patcher(number)
|
||||
|
||||
number = 0
|
||||
|
||||
for id in [4, 5, 7, 8]:
|
||||
block_indices = range(2) if id in [4, 5] else range(10)
|
||||
for index in block_indices:
|
||||
set_model_patch_replace(new_model, number, ("input", id, index))
|
||||
number += 1
|
||||
|
||||
for id in range(6):
|
||||
block_indices = range(2) if id in [3, 4, 5] else range(10)
|
||||
for index in block_indices:
|
||||
set_model_patch_replace(new_model, number, ("output", id, index))
|
||||
number += 1
|
||||
|
||||
for index in range(10):
|
||||
set_model_patch_replace(new_model, number, ("middle", 0, index))
|
||||
number += 1
|
||||
|
||||
return new_model
|
||||
@@ -0,0 +1,81 @@
|
||||
import cv2
|
||||
import numpy as np
|
||||
|
||||
|
||||
def centered_canny(x: np.ndarray, canny_low_threshold, canny_high_threshold):
|
||||
assert isinstance(x, np.ndarray)
|
||||
assert x.ndim == 2 and x.dtype == np.uint8
|
||||
|
||||
y = cv2.Canny(x, int(canny_low_threshold), int(canny_high_threshold))
|
||||
y = y.astype(np.float32) / 255.0
|
||||
return y
|
||||
|
||||
|
||||
def centered_canny_color(x: np.ndarray, canny_low_threshold, canny_high_threshold):
|
||||
assert isinstance(x, np.ndarray)
|
||||
assert x.ndim == 3 and x.shape[2] == 3
|
||||
|
||||
result = [centered_canny(x[..., i], canny_low_threshold, canny_high_threshold) for i in range(3)]
|
||||
result = np.stack(result, axis=2)
|
||||
return result
|
||||
|
||||
|
||||
def pyramid_canny_color(x: np.ndarray, canny_low_threshold, canny_high_threshold):
|
||||
assert isinstance(x, np.ndarray)
|
||||
assert x.ndim == 3 and x.shape[2] == 3
|
||||
|
||||
H, W, C = x.shape
|
||||
acc_edge = None
|
||||
|
||||
for k in [0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]:
|
||||
Hs, Ws = int(H * k), int(W * k)
|
||||
small = cv2.resize(x, (Ws, Hs), interpolation=cv2.INTER_AREA)
|
||||
edge = centered_canny_color(small, canny_low_threshold, canny_high_threshold)
|
||||
if acc_edge is None:
|
||||
acc_edge = edge
|
||||
else:
|
||||
acc_edge = cv2.resize(acc_edge, (edge.shape[1], edge.shape[0]), interpolation=cv2.INTER_LINEAR)
|
||||
acc_edge = acc_edge * 0.75 + edge * 0.25
|
||||
|
||||
return acc_edge
|
||||
|
||||
|
||||
def norm255(x, low=4, high=96):
|
||||
assert isinstance(x, np.ndarray)
|
||||
assert x.ndim == 2 and x.dtype == np.float32
|
||||
|
||||
v_min = np.percentile(x, low)
|
||||
v_max = np.percentile(x, high)
|
||||
|
||||
x -= v_min
|
||||
x /= v_max - v_min
|
||||
|
||||
return x * 255.0
|
||||
|
||||
|
||||
def canny_pyramid(x, canny_low_threshold, canny_high_threshold):
|
||||
# For some reasons, SAI's Control-lora Canny seems to be trained on canny maps with non-standard resolutions.
|
||||
# Then we use pyramid to use all resolutions to avoid missing any structure in specific resolutions.
|
||||
|
||||
color_canny = pyramid_canny_color(x, canny_low_threshold, canny_high_threshold)
|
||||
result = np.sum(color_canny, axis=2)
|
||||
|
||||
return norm255(result, low=1, high=99).clip(0, 255).astype(np.uint8)
|
||||
|
||||
|
||||
def cpds(x):
|
||||
# cv2.decolor is not "decolor", it is Cewu Lu's method
|
||||
# See http://www.cse.cuhk.edu.hk/leojia/projects/color2gray/index.html
|
||||
# See https://docs.opencv.org/3.0-beta/modules/photo/doc/decolor.html
|
||||
|
||||
raw = cv2.GaussianBlur(x, (0, 0), 0.8)
|
||||
density, boost = cv2.decolor(raw)
|
||||
|
||||
raw = raw.astype(np.float32)
|
||||
density = density.astype(np.float32)
|
||||
boost = boost.astype(np.float32)
|
||||
|
||||
offset = np.sum((raw - boost) ** 2.0, axis=2) ** 0.5
|
||||
result = density + offset
|
||||
|
||||
return norm255(result, low=4, high=96).clip(0, 255).astype(np.uint8)
|
||||
@@ -0,0 +1,120 @@
|
||||
# modified from https://github.com/mlfoundations/open_flamingo/blob/main/open_flamingo/src/helpers.py
|
||||
import math
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
|
||||
|
||||
# FFN
|
||||
def FeedForward(dim, mult=4):
|
||||
inner_dim = int(dim * mult)
|
||||
return nn.Sequential(
|
||||
nn.LayerNorm(dim),
|
||||
nn.Linear(dim, inner_dim, bias=False),
|
||||
nn.GELU(),
|
||||
nn.Linear(inner_dim, dim, bias=False),
|
||||
)
|
||||
|
||||
|
||||
def reshape_tensor(x, heads):
|
||||
bs, length, width = x.shape
|
||||
#(bs, length, width) --> (bs, length, n_heads, dim_per_head)
|
||||
x = x.view(bs, length, heads, -1)
|
||||
# (bs, length, n_heads, dim_per_head) --> (bs, n_heads, length, dim_per_head)
|
||||
x = x.transpose(1, 2)
|
||||
# (bs, n_heads, length, dim_per_head) --> (bs*n_heads, length, dim_per_head)
|
||||
x = x.reshape(bs, heads, length, -1)
|
||||
return x
|
||||
|
||||
|
||||
class PerceiverAttention(nn.Module):
|
||||
def __init__(self, *, dim, dim_head=64, heads=8):
|
||||
super().__init__()
|
||||
self.scale = dim_head**-0.5
|
||||
self.dim_head = dim_head
|
||||
self.heads = heads
|
||||
inner_dim = dim_head * heads
|
||||
|
||||
self.norm1 = nn.LayerNorm(dim)
|
||||
self.norm2 = nn.LayerNorm(dim)
|
||||
|
||||
self.to_q = nn.Linear(dim, inner_dim, bias=False)
|
||||
self.to_kv = nn.Linear(dim, inner_dim * 2, bias=False)
|
||||
self.to_out = nn.Linear(inner_dim, dim, bias=False)
|
||||
|
||||
|
||||
def forward(self, x, latents):
|
||||
"""
|
||||
Args:
|
||||
x (torch.Tensor): image features
|
||||
shape (b, n1, D)
|
||||
latent (torch.Tensor): latent features
|
||||
shape (b, n2, D)
|
||||
"""
|
||||
x = self.norm1(x)
|
||||
latents = self.norm2(latents)
|
||||
|
||||
b, l, _ = latents.shape
|
||||
|
||||
q = self.to_q(latents)
|
||||
kv_input = torch.cat((x, latents), dim=-2)
|
||||
k, v = self.to_kv(kv_input).chunk(2, dim=-1)
|
||||
|
||||
q = reshape_tensor(q, self.heads)
|
||||
k = reshape_tensor(k, self.heads)
|
||||
v = reshape_tensor(v, self.heads)
|
||||
|
||||
# attention
|
||||
scale = 1 / math.sqrt(math.sqrt(self.dim_head))
|
||||
weight = (q * scale) @ (k * scale).transpose(-2, -1) # More stable with f16 than dividing afterwards
|
||||
weight = torch.softmax(weight.float(), dim=-1).type(weight.dtype)
|
||||
out = weight @ v
|
||||
|
||||
out = out.permute(0, 2, 1, 3).reshape(b, l, -1)
|
||||
|
||||
return self.to_out(out)
|
||||
|
||||
|
||||
class Resampler(nn.Module):
|
||||
def __init__(
|
||||
self,
|
||||
dim=1024,
|
||||
depth=8,
|
||||
dim_head=64,
|
||||
heads=16,
|
||||
num_queries=8,
|
||||
embedding_dim=768,
|
||||
output_dim=1024,
|
||||
ff_mult=4,
|
||||
):
|
||||
super().__init__()
|
||||
|
||||
self.latents = nn.Parameter(torch.randn(1, num_queries, dim) / dim**0.5)
|
||||
|
||||
self.proj_in = nn.Linear(embedding_dim, dim)
|
||||
|
||||
self.proj_out = nn.Linear(dim, output_dim)
|
||||
self.norm_out = nn.LayerNorm(output_dim)
|
||||
|
||||
self.layers = nn.ModuleList([])
|
||||
for _ in range(depth):
|
||||
self.layers.append(
|
||||
nn.ModuleList(
|
||||
[
|
||||
PerceiverAttention(dim=dim, dim_head=dim_head, heads=heads),
|
||||
FeedForward(dim=dim, mult=ff_mult),
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
def forward(self, x):
|
||||
latents = self.latents.repeat(x.size(0), 1, 1).to(x)
|
||||
|
||||
x = self.proj_in(x)
|
||||
|
||||
for attn, ff in self.layers:
|
||||
latents = attn(x, latents) + latents
|
||||
latents = ff(latents) + latents
|
||||
|
||||
latents = self.proj_out(latents)
|
||||
return self.norm_out(latents)
|
||||
@@ -0,0 +1,171 @@
|
||||
{
|
||||
"_name_or_path": "clip-vit-large-patch14/",
|
||||
"architectures": [
|
||||
"SafetyChecker"
|
||||
],
|
||||
"initializer_factor": 1.0,
|
||||
"logit_scale_init_value": 2.6592,
|
||||
"model_type": "clip",
|
||||
"projection_dim": 768,
|
||||
"text_config": {
|
||||
"_name_or_path": "",
|
||||
"add_cross_attention": false,
|
||||
"architectures": null,
|
||||
"attention_dropout": 0.0,
|
||||
"bad_words_ids": null,
|
||||
"bos_token_id": 0,
|
||||
"chunk_size_feed_forward": 0,
|
||||
"cross_attention_hidden_size": null,
|
||||
"decoder_start_token_id": null,
|
||||
"diversity_penalty": 0.0,
|
||||
"do_sample": false,
|
||||
"dropout": 0.0,
|
||||
"early_stopping": false,
|
||||
"encoder_no_repeat_ngram_size": 0,
|
||||
"eos_token_id": 2,
|
||||
"exponential_decay_length_penalty": null,
|
||||
"finetuning_task": null,
|
||||
"forced_bos_token_id": null,
|
||||
"forced_eos_token_id": null,
|
||||
"hidden_act": "quick_gelu",
|
||||
"hidden_size": 768,
|
||||
"id2label": {
|
||||
"0": "LABEL_0",
|
||||
"1": "LABEL_1"
|
||||
},
|
||||
"initializer_factor": 1.0,
|
||||
"initializer_range": 0.02,
|
||||
"intermediate_size": 3072,
|
||||
"is_decoder": false,
|
||||
"is_encoder_decoder": false,
|
||||
"label2id": {
|
||||
"LABEL_0": 0,
|
||||
"LABEL_1": 1
|
||||
},
|
||||
"layer_norm_eps": 1e-05,
|
||||
"length_penalty": 1.0,
|
||||
"max_length": 20,
|
||||
"max_position_embeddings": 77,
|
||||
"min_length": 0,
|
||||
"model_type": "clip_text_model",
|
||||
"no_repeat_ngram_size": 0,
|
||||
"num_attention_heads": 12,
|
||||
"num_beam_groups": 1,
|
||||
"num_beams": 1,
|
||||
"num_hidden_layers": 12,
|
||||
"num_return_sequences": 1,
|
||||
"output_attentions": false,
|
||||
"output_hidden_states": false,
|
||||
"output_scores": false,
|
||||
"pad_token_id": 1,
|
||||
"prefix": null,
|
||||
"problem_type": null,
|
||||
"pruned_heads": {},
|
||||
"remove_invalid_values": false,
|
||||
"repetition_penalty": 1.0,
|
||||
"return_dict": true,
|
||||
"return_dict_in_generate": false,
|
||||
"sep_token_id": null,
|
||||
"task_specific_params": null,
|
||||
"temperature": 1.0,
|
||||
"tie_encoder_decoder": false,
|
||||
"tie_word_embeddings": true,
|
||||
"tokenizer_class": null,
|
||||
"top_k": 50,
|
||||
"top_p": 1.0,
|
||||
"torch_dtype": null,
|
||||
"torchscript": false,
|
||||
"transformers_version": "4.21.0.dev0",
|
||||
"typical_p": 1.0,
|
||||
"use_bfloat16": false,
|
||||
"vocab_size": 49408
|
||||
},
|
||||
"text_config_dict": {
|
||||
"hidden_size": 768,
|
||||
"intermediate_size": 3072,
|
||||
"num_attention_heads": 12,
|
||||
"num_hidden_layers": 12
|
||||
},
|
||||
"torch_dtype": "float32",
|
||||
"transformers_version": null,
|
||||
"vision_config": {
|
||||
"_name_or_path": "",
|
||||
"add_cross_attention": false,
|
||||
"architectures": null,
|
||||
"attention_dropout": 0.0,
|
||||
"bad_words_ids": null,
|
||||
"bos_token_id": null,
|
||||
"chunk_size_feed_forward": 0,
|
||||
"cross_attention_hidden_size": null,
|
||||
"decoder_start_token_id": null,
|
||||
"diversity_penalty": 0.0,
|
||||
"do_sample": false,
|
||||
"dropout": 0.0,
|
||||
"early_stopping": false,
|
||||
"encoder_no_repeat_ngram_size": 0,
|
||||
"eos_token_id": null,
|
||||
"exponential_decay_length_penalty": null,
|
||||
"finetuning_task": null,
|
||||
"forced_bos_token_id": null,
|
||||
"forced_eos_token_id": null,
|
||||
"hidden_act": "quick_gelu",
|
||||
"hidden_size": 1024,
|
||||
"id2label": {
|
||||
"0": "LABEL_0",
|
||||
"1": "LABEL_1"
|
||||
},
|
||||
"image_size": 224,
|
||||
"initializer_factor": 1.0,
|
||||
"initializer_range": 0.02,
|
||||
"intermediate_size": 4096,
|
||||
"is_decoder": false,
|
||||
"is_encoder_decoder": false,
|
||||
"label2id": {
|
||||
"LABEL_0": 0,
|
||||
"LABEL_1": 1
|
||||
},
|
||||
"layer_norm_eps": 1e-05,
|
||||
"length_penalty": 1.0,
|
||||
"max_length": 20,
|
||||
"min_length": 0,
|
||||
"model_type": "clip_vision_model",
|
||||
"no_repeat_ngram_size": 0,
|
||||
"num_attention_heads": 16,
|
||||
"num_beam_groups": 1,
|
||||
"num_beams": 1,
|
||||
"num_hidden_layers": 24,
|
||||
"num_return_sequences": 1,
|
||||
"output_attentions": false,
|
||||
"output_hidden_states": false,
|
||||
"output_scores": false,
|
||||
"pad_token_id": null,
|
||||
"patch_size": 14,
|
||||
"prefix": null,
|
||||
"problem_type": null,
|
||||
"pruned_heads": {},
|
||||
"remove_invalid_values": false,
|
||||
"repetition_penalty": 1.0,
|
||||
"return_dict": true,
|
||||
"return_dict_in_generate": false,
|
||||
"sep_token_id": null,
|
||||
"task_specific_params": null,
|
||||
"temperature": 1.0,
|
||||
"tie_encoder_decoder": false,
|
||||
"tie_word_embeddings": true,
|
||||
"tokenizer_class": null,
|
||||
"top_k": 50,
|
||||
"top_p": 1.0,
|
||||
"torch_dtype": null,
|
||||
"torchscript": false,
|
||||
"transformers_version": "4.21.0.dev0",
|
||||
"typical_p": 1.0,
|
||||
"use_bfloat16": false
|
||||
},
|
||||
"vision_config_dict": {
|
||||
"hidden_size": 1024,
|
||||
"intermediate_size": 4096,
|
||||
"num_attention_heads": 16,
|
||||
"num_hidden_layers": 24,
|
||||
"patch_size": 14
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"crop_size": 224,
|
||||
"do_center_crop": true,
|
||||
"do_convert_rgb": true,
|
||||
"do_normalize": true,
|
||||
"do_resize": true,
|
||||
"feature_extractor_type": "CLIPFeatureExtractor",
|
||||
"image_mean": [
|
||||
0.48145466,
|
||||
0.4578275,
|
||||
0.40821073
|
||||
],
|
||||
"image_std": [
|
||||
0.26862954,
|
||||
0.26130258,
|
||||
0.27577711
|
||||
],
|
||||
"resample": 3,
|
||||
"size": 224
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
# from https://github.com/huggingface/diffusers/blob/main/src/diffusers/pipelines/stable_diffusion/safety_checker.py
|
||||
|
||||
# Copyright 2024 The HuggingFace Team. All rights reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
from transformers import CLIPConfig, CLIPVisionModel, PreTrainedModel
|
||||
from transformers.utils import logging
|
||||
|
||||
logger = logging.get_logger(__name__)
|
||||
|
||||
|
||||
def cosine_distance(image_embeds, text_embeds):
|
||||
normalized_image_embeds = nn.functional.normalize(image_embeds)
|
||||
normalized_text_embeds = nn.functional.normalize(text_embeds)
|
||||
return torch.mm(normalized_image_embeds, normalized_text_embeds.t())
|
||||
|
||||
|
||||
class StableDiffusionSafetyChecker(PreTrainedModel):
|
||||
config_class = CLIPConfig
|
||||
main_input_name = "clip_input"
|
||||
|
||||
_no_split_modules = ["CLIPEncoderLayer"]
|
||||
|
||||
def __init__(self, config: CLIPConfig):
|
||||
super().__init__(config)
|
||||
|
||||
self.vision_model = CLIPVisionModel(config.vision_config)
|
||||
self.visual_projection = nn.Linear(config.vision_config.hidden_size, config.projection_dim, bias=False)
|
||||
|
||||
self.concept_embeds = nn.Parameter(torch.ones(17, config.projection_dim), requires_grad=False)
|
||||
self.special_care_embeds = nn.Parameter(torch.ones(3, config.projection_dim), requires_grad=False)
|
||||
|
||||
self.concept_embeds_weights = nn.Parameter(torch.ones(17), requires_grad=False)
|
||||
self.special_care_embeds_weights = nn.Parameter(torch.ones(3), requires_grad=False)
|
||||
|
||||
@torch.no_grad()
|
||||
def forward(self, clip_input, images):
|
||||
pooled_output = self.vision_model(clip_input)[1] # pooled_output
|
||||
image_embeds = self.visual_projection(pooled_output)
|
||||
|
||||
# we always cast to float32 as this does not cause significant overhead and is compatible with bfloat16
|
||||
special_cos_dist = cosine_distance(image_embeds, self.special_care_embeds).cpu().float().numpy()
|
||||
cos_dist = cosine_distance(image_embeds, self.concept_embeds).cpu().float().numpy()
|
||||
|
||||
result = []
|
||||
batch_size = image_embeds.shape[0]
|
||||
for i in range(batch_size):
|
||||
result_img = {"special_scores": {}, "special_care": [], "concept_scores": {}, "bad_concepts": []}
|
||||
|
||||
# increase this value to create a stronger `nfsw` filter
|
||||
# at the cost of increasing the possibility of filtering benign images
|
||||
adjustment = 0.0
|
||||
|
||||
for concept_idx in range(len(special_cos_dist[0])):
|
||||
concept_cos = special_cos_dist[i][concept_idx]
|
||||
concept_threshold = self.special_care_embeds_weights[concept_idx].item()
|
||||
result_img["special_scores"][concept_idx] = round(concept_cos - concept_threshold + adjustment, 3)
|
||||
if result_img["special_scores"][concept_idx] > 0:
|
||||
result_img["special_care"].append({concept_idx, result_img["special_scores"][concept_idx]})
|
||||
adjustment = 0.01
|
||||
|
||||
for concept_idx in range(len(cos_dist[0])):
|
||||
concept_cos = cos_dist[i][concept_idx]
|
||||
concept_threshold = self.concept_embeds_weights[concept_idx].item()
|
||||
result_img["concept_scores"][concept_idx] = round(concept_cos - concept_threshold + adjustment, 3)
|
||||
if result_img["concept_scores"][concept_idx] > 0:
|
||||
result_img["bad_concepts"].append(concept_idx)
|
||||
|
||||
result.append(result_img)
|
||||
|
||||
has_nsfw_concepts = [len(res["bad_concepts"]) > 0 for res in result]
|
||||
|
||||
for idx, has_nsfw_concept in enumerate(has_nsfw_concepts):
|
||||
if has_nsfw_concept:
|
||||
if torch.is_tensor(images) or torch.is_tensor(images[0]):
|
||||
images[idx] = torch.zeros_like(images[idx]) # black image
|
||||
else:
|
||||
images[idx] = np.zeros(images[idx].shape) # black image
|
||||
|
||||
if any(has_nsfw_concepts):
|
||||
logger.warning(
|
||||
"Potential NSFW content was detected in one or more images. A black image will be returned instead."
|
||||
" Try again with a different prompt and/or seed."
|
||||
)
|
||||
|
||||
return images, has_nsfw_concepts
|
||||
|
||||
@torch.no_grad()
|
||||
def forward_onnx(self, clip_input: torch.Tensor, images: torch.Tensor):
|
||||
pooled_output = self.vision_model(clip_input)[1] # pooled_output
|
||||
image_embeds = self.visual_projection(pooled_output)
|
||||
|
||||
special_cos_dist = cosine_distance(image_embeds, self.special_care_embeds)
|
||||
cos_dist = cosine_distance(image_embeds, self.concept_embeds)
|
||||
|
||||
# increase this value to create a stronger `nsfw` filter
|
||||
# at the cost of increasing the possibility of filtering benign images
|
||||
adjustment = 0.0
|
||||
|
||||
special_scores = special_cos_dist - self.special_care_embeds_weights + adjustment
|
||||
# special_scores = special_scores.round(decimals=3)
|
||||
special_care = torch.any(special_scores > 0, dim=1)
|
||||
special_adjustment = special_care * 0.01
|
||||
special_adjustment = special_adjustment.unsqueeze(1).expand(-1, cos_dist.shape[1])
|
||||
|
||||
concept_scores = (cos_dist - self.concept_embeds_weights) + special_adjustment
|
||||
# concept_scores = concept_scores.round(decimals=3)
|
||||
has_nsfw_concepts = torch.any(concept_scores > 0, dim=1)
|
||||
|
||||
images[has_nsfw_concepts] = 0.0 # black image
|
||||
|
||||
return images, has_nsfw_concepts
|
||||
@@ -0,0 +1,288 @@
|
||||
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
# All rights reserved.
|
||||
|
||||
# This source code is licensed under the license found in the
|
||||
# LICENSE file in the root directory of this source tree.
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
from ldm_patched.modules import model_management
|
||||
from ldm_patched.modules.model_patcher import ModelPatcher
|
||||
|
||||
from segment_anything.modeling import Sam
|
||||
|
||||
from typing import Optional, Tuple
|
||||
|
||||
from segment_anything.utils.transforms import ResizeLongestSide
|
||||
|
||||
|
||||
class SamPredictor:
|
||||
def __init__(
|
||||
self,
|
||||
model: Sam,
|
||||
load_device=model_management.text_encoder_device(),
|
||||
offload_device=model_management.text_encoder_offload_device()
|
||||
) -> None:
|
||||
"""
|
||||
Uses SAM to calculate the image embedding for an image, and then
|
||||
allow repeated, efficient mask prediction given prompts.
|
||||
|
||||
Arguments:
|
||||
model (Sam): The model to use for mask prediction.
|
||||
"""
|
||||
super().__init__()
|
||||
|
||||
self.load_device = load_device
|
||||
self.offload_device = offload_device
|
||||
# can't use model.half() here as slow_conv2d_cpu is not implemented for half
|
||||
model.to(self.offload_device)
|
||||
|
||||
self.patcher = ModelPatcher(model, load_device=self.load_device, offload_device=self.offload_device)
|
||||
|
||||
self.transform = ResizeLongestSide(model.image_encoder.img_size)
|
||||
self.reset_image()
|
||||
|
||||
def set_image(
|
||||
self,
|
||||
image: np.ndarray,
|
||||
image_format: str = "RGB",
|
||||
) -> None:
|
||||
"""
|
||||
Calculates the image embeddings for the provided image, allowing
|
||||
masks to be predicted with the 'predict' method.
|
||||
|
||||
Arguments:
|
||||
image (np.ndarray): The image for calculating masks. Expects an
|
||||
image in HWC uint8 format, with pixel values in [0, 255].
|
||||
image_format (str): The color format of the image, in ['RGB', 'BGR'].
|
||||
"""
|
||||
assert image_format in [
|
||||
"RGB",
|
||||
"BGR",
|
||||
], f"image_format must be in ['RGB', 'BGR'], is {image_format}."
|
||||
if image_format != self.patcher.model.image_format:
|
||||
image = image[..., ::-1]
|
||||
|
||||
# Transform the image to the form expected by the model
|
||||
input_image = self.transform.apply_image(image)
|
||||
input_image_torch = torch.as_tensor(input_image, device=self.load_device)
|
||||
input_image_torch = input_image_torch.permute(2, 0, 1).contiguous()[None, :, :, :]
|
||||
|
||||
self.set_torch_image(input_image_torch, image.shape[:2])
|
||||
|
||||
@torch.no_grad()
|
||||
def set_torch_image(
|
||||
self,
|
||||
transformed_image: torch.Tensor,
|
||||
original_image_size: Tuple[int, ...],
|
||||
) -> None:
|
||||
"""
|
||||
Calculates the image embeddings for the provided image, allowing
|
||||
masks to be predicted with the 'predict' method. Expects the input
|
||||
image to be already transformed to the format expected by the model.
|
||||
|
||||
Arguments:
|
||||
transformed_image (torch.Tensor): The input image, with shape
|
||||
1x3xHxW, which has been transformed with ResizeLongestSide.
|
||||
original_image_size (tuple(int, int)): The size of the image
|
||||
before transformation, in (H, W) format.
|
||||
"""
|
||||
assert (
|
||||
len(transformed_image.shape) == 4
|
||||
and transformed_image.shape[1] == 3
|
||||
and max(*transformed_image.shape[2:]) == self.patcher.model.image_encoder.img_size
|
||||
), f"set_torch_image input must be BCHW with long side {self.patcher.model.image_encoder.img_size}."
|
||||
self.reset_image()
|
||||
|
||||
self.original_size = original_image_size
|
||||
self.input_size = tuple(transformed_image.shape[-2:])
|
||||
model_management.load_model_gpu(self.patcher)
|
||||
input_image = self.patcher.model.preprocess(transformed_image.to(self.load_device))
|
||||
self.features = self.patcher.model.image_encoder(input_image)
|
||||
self.is_image_set = True
|
||||
|
||||
def predict(
|
||||
self,
|
||||
point_coords: Optional[np.ndarray] = None,
|
||||
point_labels: Optional[np.ndarray] = None,
|
||||
box: Optional[np.ndarray] = None,
|
||||
mask_input: Optional[np.ndarray] = None,
|
||||
multimask_output: bool = True,
|
||||
return_logits: bool = False,
|
||||
) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
|
||||
"""
|
||||
Predict masks for the given input prompts, using the currently set image.
|
||||
|
||||
Arguments:
|
||||
point_coords (np.ndarray or None): A Nx2 array of point prompts to the
|
||||
model. Each point is in (X,Y) in pixels.
|
||||
point_labels (np.ndarray or None): A length N array of labels for the
|
||||
point prompts. 1 indicates a foreground point and 0 indicates a
|
||||
background point.
|
||||
box (np.ndarray or None): A length 4 array given a box prompt to the
|
||||
model, in XYXY format.
|
||||
mask_input (np.ndarray): A low resolution mask input to the model, typically
|
||||
coming from a previous prediction iteration. Has form 1xHxW, where
|
||||
for SAM, H=W=256.
|
||||
multimask_output (bool): If true, the model will return three masks.
|
||||
For ambiguous input prompts (such as a single click), this will often
|
||||
produce better masks than a single prediction. If only a single
|
||||
mask is needed, the model's predicted quality score can be used
|
||||
to select the best mask. For non-ambiguous prompts, such as multiple
|
||||
input prompts, multimask_output=False can give better results.
|
||||
return_logits (bool): If true, returns un-thresholded masks logits
|
||||
instead of a binary mask.
|
||||
|
||||
Returns:
|
||||
(np.ndarray): The output masks in CxHxW format, where C is the
|
||||
number of masks, and (H, W) is the original image size.
|
||||
(np.ndarray): An array of length C containing the model's
|
||||
predictions for the quality of each mask.
|
||||
(np.ndarray): An array of shape CxHxW, where C is the number
|
||||
of masks and H=W=256. These low resolution logits can be passed to
|
||||
a subsequent iteration as mask input.
|
||||
"""
|
||||
if not self.is_image_set:
|
||||
raise RuntimeError("An image must be set with .set_image(...) before mask prediction.")
|
||||
|
||||
# Transform input prompts
|
||||
coords_torch, labels_torch, box_torch, mask_input_torch = None, None, None, None
|
||||
if point_coords is not None:
|
||||
assert (
|
||||
point_labels is not None
|
||||
), "point_labels must be supplied if point_coords is supplied."
|
||||
point_coords = self.transform.apply_coords(point_coords, self.original_size)
|
||||
coords_torch = torch.as_tensor(point_coords, dtype=torch.float, device=self.load_device)
|
||||
labels_torch = torch.as_tensor(point_labels, dtype=torch.int, device=self.load_device)
|
||||
coords_torch, labels_torch = coords_torch[None, :, :], labels_torch[None, :]
|
||||
if box is not None:
|
||||
box = self.transform.apply_boxes(box, self.original_size)
|
||||
box_torch = torch.as_tensor(box, dtype=torch.float, device=self.load_device)
|
||||
box_torch = box_torch[None, :]
|
||||
if mask_input is not None:
|
||||
mask_input_torch = torch.as_tensor(mask_input, dtype=torch.float, device=self.load_device)
|
||||
mask_input_torch = mask_input_torch[None, :, :, :]
|
||||
|
||||
masks, iou_predictions, low_res_masks = self.predict_torch(
|
||||
coords_torch,
|
||||
labels_torch,
|
||||
box_torch,
|
||||
mask_input_torch,
|
||||
multimask_output,
|
||||
return_logits=return_logits,
|
||||
)
|
||||
|
||||
masks = masks[0].detach().cpu().numpy()
|
||||
iou_predictions = iou_predictions[0].detach().cpu().numpy()
|
||||
low_res_masks = low_res_masks[0].detach().cpu().numpy()
|
||||
return masks, iou_predictions, low_res_masks
|
||||
|
||||
@torch.no_grad()
|
||||
def predict_torch(
|
||||
self,
|
||||
point_coords: Optional[torch.Tensor],
|
||||
point_labels: Optional[torch.Tensor],
|
||||
boxes: Optional[torch.Tensor] = None,
|
||||
mask_input: Optional[torch.Tensor] = None,
|
||||
multimask_output: bool = True,
|
||||
return_logits: bool = False,
|
||||
) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
|
||||
"""
|
||||
Predict masks for the given input prompts, using the currently set image.
|
||||
Input prompts are batched torch tensors and are expected to already be
|
||||
transformed to the input frame using ResizeLongestSide.
|
||||
|
||||
Arguments:
|
||||
point_coords (torch.Tensor or None): A BxNx2 array of point prompts to the
|
||||
model. Each point is in (X,Y) in pixels.
|
||||
point_labels (torch.Tensor or None): A BxN array of labels for the
|
||||
point prompts. 1 indicates a foreground point and 0 indicates a
|
||||
background point.
|
||||
box (np.ndarray or None): A Bx4 array given a box prompt to the
|
||||
model, in XYXY format.
|
||||
mask_input (np.ndarray): A low resolution mask input to the model, typically
|
||||
coming from a previous prediction iteration. Has form Bx1xHxW, where
|
||||
for SAM, H=W=256. Masks returned by a previous iteration of the
|
||||
predict method do not need further transformation.
|
||||
multimask_output (bool): If true, the model will return three masks.
|
||||
For ambiguous input prompts (such as a single click), this will often
|
||||
produce better masks than a single prediction. If only a single
|
||||
mask is needed, the model's predicted quality score can be used
|
||||
to select the best mask. For non-ambiguous prompts, such as multiple
|
||||
input prompts, multimask_output=False can give better results.
|
||||
return_logits (bool): If true, returns un-thresholded masks logits
|
||||
instead of a binary mask.
|
||||
|
||||
Returns:
|
||||
(torch.Tensor): The output masks in BxCxHxW format, where C is the
|
||||
number of masks, and (H, W) is the original image size.
|
||||
(torch.Tensor): An array of shape BxC containing the model's
|
||||
predictions for the quality of each mask.
|
||||
(torch.Tensor): An array of shape BxCxHxW, where C is the number
|
||||
of masks and H=W=256. These low res logits can be passed to
|
||||
a subsequent iteration as mask input.
|
||||
"""
|
||||
if not self.is_image_set:
|
||||
raise RuntimeError("An image must be set with .set_image(...) before mask prediction.")
|
||||
|
||||
if point_coords is not None:
|
||||
points = (point_coords.to(self.load_device), point_labels.to(self.load_device))
|
||||
else:
|
||||
points = None
|
||||
|
||||
# load
|
||||
if boxes is not None:
|
||||
boxes = boxes.to(self.load_device)
|
||||
if mask_input is not None:
|
||||
mask_input = mask_input.to(self.load_device)
|
||||
model_management.load_model_gpu(self.patcher)
|
||||
|
||||
# Embed prompts
|
||||
sparse_embeddings, dense_embeddings = self.patcher.model.prompt_encoder(
|
||||
points=points,
|
||||
boxes=boxes,
|
||||
masks=mask_input,
|
||||
)
|
||||
|
||||
# Predict masks
|
||||
low_res_masks, iou_predictions = self.patcher.model.mask_decoder(
|
||||
image_embeddings=self.features,
|
||||
image_pe=self.patcher.model.prompt_encoder.get_dense_pe(),
|
||||
sparse_prompt_embeddings=sparse_embeddings,
|
||||
dense_prompt_embeddings=dense_embeddings,
|
||||
multimask_output=multimask_output,
|
||||
)
|
||||
|
||||
# Upscale the masks to the original image resolution
|
||||
masks = self.patcher.model.postprocess_masks(low_res_masks, self.input_size, self.original_size)
|
||||
|
||||
if not return_logits:
|
||||
masks = masks > self.patcher.model.mask_threshold
|
||||
|
||||
return masks, iou_predictions, low_res_masks
|
||||
|
||||
def get_image_embedding(self) -> torch.Tensor:
|
||||
"""
|
||||
Returns the image embeddings for the currently set image, with
|
||||
shape 1xCxHxW, where C is the embedding dimension and (H,W) are
|
||||
the embedding spatial dimension of SAM (typically C=256, H=W=64).
|
||||
"""
|
||||
if not self.is_image_set:
|
||||
raise RuntimeError(
|
||||
"An image must be set with .set_image(...) to generate an embedding."
|
||||
)
|
||||
assert self.features is not None, "Features must exist if an image has been set."
|
||||
return self.features
|
||||
|
||||
@property
|
||||
def device(self) -> torch.device:
|
||||
return self.patcher.model.device
|
||||
|
||||
def reset_image(self) -> None:
|
||||
"""Resets the currently set image."""
|
||||
self.is_image_set = False
|
||||
self.features = None
|
||||
self.orig_h = None
|
||||
self.orig_w = None
|
||||
self.input_h = None
|
||||
self.input_w = None
|
||||
@@ -0,0 +1,109 @@
|
||||
# https://github.com/city96/SD-Latent-Interposer/blob/main/interposer.py
|
||||
|
||||
import os
|
||||
|
||||
import safetensors.torch as sf
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
|
||||
import ldm_patched.modules.model_management
|
||||
from ldm_patched.modules.model_patcher import ModelPatcher
|
||||
from modules.config import path_vae_approx
|
||||
|
||||
|
||||
class ResBlock(nn.Module):
|
||||
"""Block with residuals"""
|
||||
|
||||
def __init__(self, ch):
|
||||
super().__init__()
|
||||
self.join = nn.ReLU()
|
||||
self.norm = nn.BatchNorm2d(ch)
|
||||
self.long = nn.Sequential(
|
||||
nn.Conv2d(ch, ch, kernel_size=3, stride=1, padding=1),
|
||||
nn.SiLU(),
|
||||
nn.Conv2d(ch, ch, kernel_size=3, stride=1, padding=1),
|
||||
nn.SiLU(),
|
||||
nn.Conv2d(ch, ch, kernel_size=3, stride=1, padding=1),
|
||||
nn.Dropout(0.1)
|
||||
)
|
||||
|
||||
def forward(self, x):
|
||||
x = self.norm(x)
|
||||
return self.join(self.long(x) + x)
|
||||
|
||||
|
||||
class ExtractBlock(nn.Module):
|
||||
"""Increase no. of channels by [out/in]"""
|
||||
|
||||
def __init__(self, ch_in, ch_out):
|
||||
super().__init__()
|
||||
self.join = nn.ReLU()
|
||||
self.short = nn.Conv2d(ch_in, ch_out, kernel_size=3, stride=1, padding=1)
|
||||
self.long = nn.Sequential(
|
||||
nn.Conv2d(ch_in, ch_out, kernel_size=3, stride=1, padding=1),
|
||||
nn.SiLU(),
|
||||
nn.Conv2d(ch_out, ch_out, kernel_size=3, stride=1, padding=1),
|
||||
nn.SiLU(),
|
||||
nn.Conv2d(ch_out, ch_out, kernel_size=3, stride=1, padding=1),
|
||||
nn.Dropout(0.1)
|
||||
)
|
||||
|
||||
def forward(self, x):
|
||||
return self.join(self.long(x) + self.short(x))
|
||||
|
||||
|
||||
class InterposerModel(nn.Module):
|
||||
"""Main neural network"""
|
||||
|
||||
def __init__(self, ch_in=4, ch_out=4, ch_mid=64, scale=1.0, blocks=12):
|
||||
super().__init__()
|
||||
self.ch_in = ch_in
|
||||
self.ch_out = ch_out
|
||||
self.ch_mid = ch_mid
|
||||
self.blocks = blocks
|
||||
self.scale = scale
|
||||
|
||||
self.head = ExtractBlock(self.ch_in, self.ch_mid)
|
||||
self.core = nn.Sequential(
|
||||
nn.Upsample(scale_factor=self.scale, mode="nearest"),
|
||||
*[ResBlock(self.ch_mid) for _ in range(blocks)],
|
||||
nn.BatchNorm2d(self.ch_mid),
|
||||
nn.SiLU(),
|
||||
)
|
||||
self.tail = nn.Conv2d(self.ch_mid, self.ch_out, kernel_size=3, stride=1, padding=1)
|
||||
|
||||
def forward(self, x):
|
||||
y = self.head(x)
|
||||
z = self.core(y)
|
||||
return self.tail(z)
|
||||
|
||||
|
||||
vae_approx_model = None
|
||||
vae_approx_filename = os.path.join(path_vae_approx, 'xl-to-v1_interposer-v4.0.safetensors')
|
||||
|
||||
|
||||
def parse(x):
|
||||
global vae_approx_model
|
||||
|
||||
x_origin = x.clone()
|
||||
|
||||
if vae_approx_model is None:
|
||||
model = InterposerModel()
|
||||
model.eval()
|
||||
sd = sf.load_file(vae_approx_filename)
|
||||
model.load_state_dict(sd)
|
||||
fp16 = ldm_patched.modules.model_management.should_use_fp16()
|
||||
if fp16:
|
||||
model = model.half()
|
||||
vae_approx_model = ModelPatcher(
|
||||
model=model,
|
||||
load_device=ldm_patched.modules.model_management.get_torch_device(),
|
||||
offload_device=torch.device('cpu')
|
||||
)
|
||||
vae_approx_model.dtype = torch.float16 if fp16 else torch.float32
|
||||
|
||||
ldm_patched.modules.model_management.load_model_gpu(vae_approx_model)
|
||||
|
||||
x = x_origin.to(device=vae_approx_model.load_device, dtype=vae_approx_model.dtype)
|
||||
x = vae_approx_model.model(x).to(x_origin)
|
||||
return x
|
||||
@@ -0,0 +1,98 @@
|
||||
# https://huggingface.co/spaces/SmilingWolf/wd-v1-4-tags
|
||||
# https://github.com/pythongosssss/ComfyUI-WD14-Tagger/blob/main/wd14tagger.py
|
||||
|
||||
# {
|
||||
# "wd-v1-4-moat-tagger-v2": "https://huggingface.co/SmilingWolf/wd-v1-4-moat-tagger-v2",
|
||||
# "wd-v1-4-convnextv2-tagger-v2": "https://huggingface.co/SmilingWolf/wd-v1-4-convnextv2-tagger-v2",
|
||||
# "wd-v1-4-convnext-tagger-v2": "https://huggingface.co/SmilingWolf/wd-v1-4-convnext-tagger-v2",
|
||||
# "wd-v1-4-convnext-tagger": "https://huggingface.co/SmilingWolf/wd-v1-4-convnext-tagger",
|
||||
# "wd-v1-4-vit-tagger-v2": "https://huggingface.co/SmilingWolf/wd-v1-4-vit-tagger-v2"
|
||||
# }
|
||||
|
||||
|
||||
import numpy as np
|
||||
import csv
|
||||
import onnxruntime as ort
|
||||
|
||||
from PIL import Image
|
||||
from onnxruntime import InferenceSession
|
||||
from modules.config import path_clip_vision
|
||||
from modules.model_loader import load_file_from_url
|
||||
|
||||
|
||||
global_model = None
|
||||
global_csv = None
|
||||
|
||||
|
||||
def default_interrogator(image_rgb, threshold=0.35, character_threshold=0.85, exclude_tags=""):
|
||||
global global_model, global_csv
|
||||
|
||||
model_name = "wd-v1-4-moat-tagger-v2"
|
||||
|
||||
model_onnx_filename = load_file_from_url(
|
||||
url=f'https://huggingface.co/lllyasviel/misc/resolve/main/{model_name}.onnx',
|
||||
model_dir=path_clip_vision,
|
||||
file_name=f'{model_name}.onnx',
|
||||
)
|
||||
|
||||
model_csv_filename = load_file_from_url(
|
||||
url=f'https://huggingface.co/lllyasviel/misc/resolve/main/{model_name}.csv',
|
||||
model_dir=path_clip_vision,
|
||||
file_name=f'{model_name}.csv',
|
||||
)
|
||||
|
||||
if global_model is not None:
|
||||
model = global_model
|
||||
else:
|
||||
model = InferenceSession(model_onnx_filename, providers=ort.get_available_providers())
|
||||
global_model = model
|
||||
|
||||
input = model.get_inputs()[0]
|
||||
height = input.shape[1]
|
||||
|
||||
image = Image.fromarray(image_rgb) # RGB
|
||||
ratio = float(height)/max(image.size)
|
||||
new_size = tuple([int(x*ratio) for x in image.size])
|
||||
image = image.resize(new_size, Image.LANCZOS)
|
||||
square = Image.new("RGB", (height, height), (255, 255, 255))
|
||||
square.paste(image, ((height-new_size[0])//2, (height-new_size[1])//2))
|
||||
|
||||
image = np.array(square).astype(np.float32)
|
||||
image = image[:, :, ::-1] # RGB -> BGR
|
||||
image = np.expand_dims(image, 0)
|
||||
|
||||
if global_csv is not None:
|
||||
csv_lines = global_csv
|
||||
else:
|
||||
csv_lines = []
|
||||
with open(model_csv_filename) as f:
|
||||
reader = csv.reader(f)
|
||||
next(reader)
|
||||
for row in reader:
|
||||
csv_lines.append(row)
|
||||
global_csv = csv_lines
|
||||
|
||||
tags = []
|
||||
general_index = None
|
||||
character_index = None
|
||||
for line_num, row in enumerate(csv_lines):
|
||||
if general_index is None and row[2] == "0":
|
||||
general_index = line_num
|
||||
elif character_index is None and row[2] == "4":
|
||||
character_index = line_num
|
||||
tags.append(row[1])
|
||||
|
||||
label_name = model.get_outputs()[0].name
|
||||
probs = model.run([label_name], {input.name: image})[0]
|
||||
|
||||
result = list(zip(tags, probs[0]))
|
||||
|
||||
general = [item for item in result[general_index:character_index] if item[1] > threshold]
|
||||
character = [item for item in result[character_index:] if item[1] > character_threshold]
|
||||
|
||||
all = character + general
|
||||
remove = [s.strip() for s in exclude_tags.lower().split(",")]
|
||||
all = [tag for tag in all if tag[0] not in remove]
|
||||
|
||||
res = ", ".join((item[0].replace("(", "\\(").replace(")", "\\)") for item in all)).replace('_', ' ')
|
||||
return res
|
||||
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {
|
||||
"id": "VjYy0F2gZIPR"
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"!pip install pygit2==1.15.1\n",
|
||||
"%cd /content\n",
|
||||
"!git clone https://github.com/lllyasviel/Fooocus.git\n",
|
||||
"%cd /content/Fooocus\n",
|
||||
"!python entry_with_update.py --share --always-high-vram\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"accelerator": "GPU",
|
||||
"colab": {
|
||||
"gpuType": "T4",
|
||||
"provenance": []
|
||||
},
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"name": "python"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 0
|
||||
}
|
||||
+1
-1
@@ -1 +1 @@
|
||||
version = '1.0.35'
|
||||
version = '2.5.5'
|
||||
@@ -0,0 +1,166 @@
|
||||
// based on https://github.com/AUTOMATIC1111/stable-diffusion-webui/blob/v1.6.0/javascript/contextMenus.js
|
||||
|
||||
var contextMenuInit = function() {
|
||||
let eventListenerApplied = false;
|
||||
let menuSpecs = new Map();
|
||||
|
||||
const uid = function() {
|
||||
return Date.now().toString(36) + Math.random().toString(36).substring(2);
|
||||
};
|
||||
|
||||
function showContextMenu(event, element, menuEntries) {
|
||||
let posx = event.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
|
||||
let posy = event.clientY + document.body.scrollTop + document.documentElement.scrollTop;
|
||||
|
||||
let oldMenu = gradioApp().querySelector('#context-menu');
|
||||
if (oldMenu) {
|
||||
oldMenu.remove();
|
||||
}
|
||||
|
||||
let baseStyle = window.getComputedStyle(gradioApp().querySelector('button.selected'));
|
||||
|
||||
const contextMenu = document.createElement('nav');
|
||||
contextMenu.id = "context-menu";
|
||||
contextMenu.style.background = baseStyle.background;
|
||||
contextMenu.style.color = baseStyle.color;
|
||||
contextMenu.style.fontFamily = baseStyle.fontFamily;
|
||||
contextMenu.style.top = posy + 'px';
|
||||
contextMenu.style.left = posx + 'px';
|
||||
|
||||
const contextMenuList = document.createElement('ul');
|
||||
contextMenuList.className = 'context-menu-items';
|
||||
contextMenu.append(contextMenuList);
|
||||
|
||||
menuEntries.forEach(function(entry) {
|
||||
let contextMenuEntry = document.createElement('a');
|
||||
contextMenuEntry.innerHTML = entry['name'];
|
||||
contextMenuEntry.addEventListener("click", function() {
|
||||
entry['func']();
|
||||
});
|
||||
contextMenuList.append(contextMenuEntry);
|
||||
|
||||
});
|
||||
|
||||
gradioApp().appendChild(contextMenu);
|
||||
|
||||
let menuWidth = contextMenu.offsetWidth + 4;
|
||||
let menuHeight = contextMenu.offsetHeight + 4;
|
||||
|
||||
let windowWidth = window.innerWidth;
|
||||
let windowHeight = window.innerHeight;
|
||||
|
||||
if ((windowWidth - posx) < menuWidth) {
|
||||
contextMenu.style.left = windowWidth - menuWidth + "px";
|
||||
}
|
||||
|
||||
if ((windowHeight - posy) < menuHeight) {
|
||||
contextMenu.style.top = windowHeight - menuHeight + "px";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function appendContextMenuOption(targetElementSelector, entryName, entryFunction) {
|
||||
|
||||
var currentItems = menuSpecs.get(targetElementSelector);
|
||||
|
||||
if (!currentItems) {
|
||||
currentItems = [];
|
||||
menuSpecs.set(targetElementSelector, currentItems);
|
||||
}
|
||||
let newItem = {
|
||||
id: targetElementSelector + '_' + uid(),
|
||||
name: entryName,
|
||||
func: entryFunction,
|
||||
isNew: true
|
||||
};
|
||||
|
||||
currentItems.push(newItem);
|
||||
return newItem['id'];
|
||||
}
|
||||
|
||||
function removeContextMenuOption(uid) {
|
||||
menuSpecs.forEach(function(v) {
|
||||
let index = -1;
|
||||
v.forEach(function(e, ei) {
|
||||
if (e['id'] == uid) {
|
||||
index = ei;
|
||||
}
|
||||
});
|
||||
if (index >= 0) {
|
||||
v.splice(index, 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function addContextMenuEventListener() {
|
||||
if (eventListenerApplied) {
|
||||
return;
|
||||
}
|
||||
gradioApp().addEventListener("click", function(e) {
|
||||
if (!e.isTrusted) {
|
||||
return;
|
||||
}
|
||||
|
||||
let oldMenu = gradioApp().querySelector('#context-menu');
|
||||
if (oldMenu) {
|
||||
oldMenu.remove();
|
||||
}
|
||||
});
|
||||
gradioApp().addEventListener("contextmenu", function(e) {
|
||||
let oldMenu = gradioApp().querySelector('#context-menu');
|
||||
if (oldMenu) {
|
||||
oldMenu.remove();
|
||||
}
|
||||
menuSpecs.forEach(function(v, k) {
|
||||
if (e.composedPath()[0].matches(k)) {
|
||||
showContextMenu(e, e.composedPath()[0], v);
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
});
|
||||
eventListenerApplied = true;
|
||||
|
||||
}
|
||||
|
||||
return [appendContextMenuOption, removeContextMenuOption, addContextMenuEventListener];
|
||||
};
|
||||
|
||||
var initResponse = contextMenuInit();
|
||||
var appendContextMenuOption = initResponse[0];
|
||||
var removeContextMenuOption = initResponse[1];
|
||||
var addContextMenuEventListener = initResponse[2];
|
||||
|
||||
let cancelGenerateForever = function() {
|
||||
clearInterval(window.generateOnRepeatInterval);
|
||||
};
|
||||
|
||||
(function() {
|
||||
//Start example Context Menu Items
|
||||
let generateOnRepeat = function(genbuttonid, interruptbuttonid) {
|
||||
let genbutton = gradioApp().querySelector(genbuttonid);
|
||||
let interruptbutton = gradioApp().querySelector(interruptbuttonid);
|
||||
if (!interruptbutton.offsetParent) {
|
||||
genbutton.click();
|
||||
}
|
||||
clearInterval(window.generateOnRepeatInterval);
|
||||
window.generateOnRepeatInterval = setInterval(function() {
|
||||
if (!interruptbutton.offsetParent) {
|
||||
genbutton.click();
|
||||
}
|
||||
},
|
||||
500);
|
||||
};
|
||||
|
||||
let generateOnRepeatForButtons = function() {
|
||||
generateOnRepeat('#generate_button', '#stop_button');
|
||||
};
|
||||
appendContextMenuOption('#generate_button', 'Generate forever', generateOnRepeatForButtons);
|
||||
|
||||
})();
|
||||
//End example Context Menu Items
|
||||
|
||||
document.onreadystatechange = function () {
|
||||
if (document.readyState == "complete") {
|
||||
addContextMenuEventListener();
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,128 @@
|
||||
function updateInput(target) {
|
||||
let e = new Event("input", {bubbles: true});
|
||||
Object.defineProperty(e, "target", {value: target});
|
||||
target.dispatchEvent(e);
|
||||
}
|
||||
|
||||
function keyupEditAttention(event) {
|
||||
let target = event.originalTarget || event.composedPath()[0];
|
||||
if (!target.matches("*:is([id*='_prompt'], .prompt) textarea")) return;
|
||||
if (!(event.metaKey || event.ctrlKey)) return;
|
||||
|
||||
let isPlus = event.key == "ArrowUp";
|
||||
let isMinus = event.key == "ArrowDown";
|
||||
if (!isPlus && !isMinus) return;
|
||||
|
||||
let selectionStart = target.selectionStart;
|
||||
let selectionEnd = target.selectionEnd;
|
||||
let text = target.value;
|
||||
|
||||
function selectCurrentParenthesisBlock(OPEN, CLOSE) {
|
||||
if (selectionStart !== selectionEnd) return false;
|
||||
|
||||
// Find opening parenthesis around current cursor
|
||||
const before = text.substring(0, selectionStart);
|
||||
let beforeParen = before.lastIndexOf(OPEN);
|
||||
if (beforeParen == -1) return false;
|
||||
let beforeParenClose = before.lastIndexOf(CLOSE);
|
||||
while (beforeParenClose !== -1 && beforeParenClose > beforeParen) {
|
||||
beforeParen = before.lastIndexOf(OPEN, beforeParen - 1);
|
||||
beforeParenClose = before.lastIndexOf(CLOSE, beforeParenClose - 1);
|
||||
}
|
||||
|
||||
// Find closing parenthesis around current cursor
|
||||
const after = text.substring(selectionStart);
|
||||
let afterParen = after.indexOf(CLOSE);
|
||||
if (afterParen == -1) return false;
|
||||
let afterParenOpen = after.indexOf(OPEN);
|
||||
while (afterParenOpen !== -1 && afterParen > afterParenOpen) {
|
||||
afterParen = after.indexOf(CLOSE, afterParen + 1);
|
||||
afterParenOpen = after.indexOf(OPEN, afterParenOpen + 1);
|
||||
}
|
||||
if (beforeParen === -1 || afterParen === -1) return false;
|
||||
|
||||
// Set the selection to the text between the parenthesis
|
||||
const parenContent = text.substring(beforeParen + 1, selectionStart + afterParen);
|
||||
const lastColon = parenContent.lastIndexOf(":");
|
||||
selectionStart = beforeParen + 1;
|
||||
selectionEnd = selectionStart + lastColon;
|
||||
target.setSelectionRange(selectionStart, selectionEnd);
|
||||
return true;
|
||||
}
|
||||
|
||||
function selectCurrentWord() {
|
||||
if (selectionStart !== selectionEnd) return false;
|
||||
const delimiters = ".,\\/!?%^*;:{}=`~() \r\n\t";
|
||||
|
||||
// seek backward until to find beggining
|
||||
while (!delimiters.includes(text[selectionStart - 1]) && selectionStart > 0) {
|
||||
selectionStart--;
|
||||
}
|
||||
|
||||
// seek forward to find end
|
||||
while (!delimiters.includes(text[selectionEnd]) && selectionEnd < text.length) {
|
||||
selectionEnd++;
|
||||
}
|
||||
|
||||
target.setSelectionRange(selectionStart, selectionEnd);
|
||||
return true;
|
||||
}
|
||||
|
||||
// If the user hasn't selected anything, let's select their current parenthesis block or word
|
||||
if (!selectCurrentParenthesisBlock('<', '>') && !selectCurrentParenthesisBlock('(', ')')) {
|
||||
selectCurrentWord();
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
var closeCharacter = ')';
|
||||
var delta = 0.1;
|
||||
|
||||
if (selectionStart > 0 && text[selectionStart - 1] == '<') {
|
||||
closeCharacter = '>';
|
||||
delta = 0.05;
|
||||
} else if (selectionStart == 0 || text[selectionStart - 1] != "(") {
|
||||
|
||||
// do not include spaces at the end
|
||||
while (selectionEnd > selectionStart && text[selectionEnd - 1] == ' ') {
|
||||
selectionEnd -= 1;
|
||||
}
|
||||
if (selectionStart == selectionEnd) {
|
||||
return;
|
||||
}
|
||||
|
||||
text = text.slice(0, selectionStart) + "(" + text.slice(selectionStart, selectionEnd) + ":1.0)" + text.slice(selectionEnd);
|
||||
|
||||
selectionStart += 1;
|
||||
selectionEnd += 1;
|
||||
}
|
||||
|
||||
var end = text.slice(selectionEnd + 1).indexOf(closeCharacter) + 1;
|
||||
var weight = parseFloat(text.slice(selectionEnd + 1, selectionEnd + 1 + end));
|
||||
if (isNaN(weight)) return;
|
||||
|
||||
weight += isPlus ? delta : -delta;
|
||||
weight = parseFloat(weight.toPrecision(12));
|
||||
if (String(weight).length == 1) weight += ".0";
|
||||
|
||||
if (closeCharacter == ')' && weight == 1) {
|
||||
var endParenPos = text.substring(selectionEnd).indexOf(')');
|
||||
text = text.slice(0, selectionStart - 1) + text.slice(selectionStart, selectionEnd) + text.slice(selectionEnd + endParenPos + 1);
|
||||
selectionStart--;
|
||||
selectionEnd--;
|
||||
} else {
|
||||
text = text.slice(0, selectionEnd + 1) + weight + text.slice(selectionEnd + end);
|
||||
}
|
||||
|
||||
target.focus();
|
||||
target.value = text;
|
||||
target.selectionStart = selectionStart;
|
||||
target.selectionEnd = selectionEnd;
|
||||
|
||||
updateInput(target);
|
||||
|
||||
}
|
||||
|
||||
addEventListener('keydown', (event) => {
|
||||
keyupEditAttention(event);
|
||||
});
|
||||
@@ -0,0 +1,260 @@
|
||||
// From A1111
|
||||
|
||||
function closeModal() {
|
||||
gradioApp().getElementById("lightboxModal").style.display = "none";
|
||||
}
|
||||
|
||||
function showModal(event) {
|
||||
const source = event.target || event.srcElement;
|
||||
const modalImage = gradioApp().getElementById("modalImage");
|
||||
const lb = gradioApp().getElementById("lightboxModal");
|
||||
modalImage.src = source.src;
|
||||
if (modalImage.style.display === 'none') {
|
||||
lb.style.setProperty('background-image', 'url(' + source.src + ')');
|
||||
}
|
||||
lb.style.display = "flex";
|
||||
lb.focus();
|
||||
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
function negmod(n, m) {
|
||||
return ((n % m) + m) % m;
|
||||
}
|
||||
|
||||
function updateOnBackgroundChange() {
|
||||
const modalImage = gradioApp().getElementById("modalImage");
|
||||
if (modalImage && modalImage.offsetParent) {
|
||||
let currentButton = selected_gallery_button();
|
||||
|
||||
if (currentButton?.children?.length > 0 && modalImage.src != currentButton.children[0].src) {
|
||||
modalImage.src = currentButton.children[0].src;
|
||||
if (modalImage.style.display === 'none') {
|
||||
const modal = gradioApp().getElementById("lightboxModal");
|
||||
modal.style.setProperty('background-image', `url(${modalImage.src})`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function all_gallery_buttons() {
|
||||
var allGalleryButtons = gradioApp().querySelectorAll('.image_gallery .thumbnails > .thumbnail-item.thumbnail-small');
|
||||
var visibleGalleryButtons = [];
|
||||
allGalleryButtons.forEach(function(elem) {
|
||||
if (elem.parentElement.offsetParent) {
|
||||
visibleGalleryButtons.push(elem);
|
||||
}
|
||||
});
|
||||
return visibleGalleryButtons;
|
||||
}
|
||||
|
||||
function selected_gallery_button() {
|
||||
return all_gallery_buttons().find(elem => elem.classList.contains('selected')) ?? null;
|
||||
}
|
||||
|
||||
function selected_gallery_index() {
|
||||
return all_gallery_buttons().findIndex(elem => elem.classList.contains('selected'));
|
||||
}
|
||||
|
||||
function modalImageSwitch(offset) {
|
||||
var galleryButtons = all_gallery_buttons();
|
||||
|
||||
if (galleryButtons.length > 1) {
|
||||
var currentButton = selected_gallery_button();
|
||||
|
||||
var result = -1;
|
||||
galleryButtons.forEach(function(v, i) {
|
||||
if (v == currentButton) {
|
||||
result = i;
|
||||
}
|
||||
});
|
||||
|
||||
if (result != -1) {
|
||||
var nextButton = galleryButtons[negmod((result + offset), galleryButtons.length)];
|
||||
nextButton.click();
|
||||
const modalImage = gradioApp().getElementById("modalImage");
|
||||
const modal = gradioApp().getElementById("lightboxModal");
|
||||
modalImage.src = nextButton.children[0].src;
|
||||
if (modalImage.style.display === 'none') {
|
||||
modal.style.setProperty('background-image', `url(${modalImage.src})`);
|
||||
}
|
||||
setTimeout(function() {
|
||||
modal.focus();
|
||||
}, 10);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function saveImage() {
|
||||
|
||||
}
|
||||
|
||||
function modalSaveImage(event) {
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
function modalNextImage(event) {
|
||||
modalImageSwitch(1);
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
function modalPrevImage(event) {
|
||||
modalImageSwitch(-1);
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
function modalKeyHandler(event) {
|
||||
switch (event.key) {
|
||||
case "s":
|
||||
saveImage();
|
||||
break;
|
||||
case "ArrowLeft":
|
||||
modalPrevImage(event);
|
||||
break;
|
||||
case "ArrowRight":
|
||||
modalNextImage(event);
|
||||
break;
|
||||
case "Escape":
|
||||
closeModal();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function setupImageForLightbox(e) {
|
||||
if (e.dataset.modded) {
|
||||
return;
|
||||
}
|
||||
|
||||
e.dataset.modded = true;
|
||||
e.style.cursor = 'pointer';
|
||||
e.style.userSelect = 'none';
|
||||
|
||||
var isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
|
||||
|
||||
// For Firefox, listening on click first switched to next image then shows the lightbox.
|
||||
// If you know how to fix this without switching to mousedown event, please.
|
||||
// For other browsers the event is click to make it possiblr to drag picture.
|
||||
var event = isFirefox ? 'mousedown' : 'click';
|
||||
|
||||
e.addEventListener(event, function(evt) {
|
||||
if (evt.button == 1) {
|
||||
open(evt.target.src);
|
||||
evt.preventDefault();
|
||||
return;
|
||||
}
|
||||
if (evt.button != 0) return;
|
||||
|
||||
modalZoomSet(gradioApp().getElementById('modalImage'), true);
|
||||
evt.preventDefault();
|
||||
showModal(evt);
|
||||
}, true);
|
||||
|
||||
}
|
||||
|
||||
function modalZoomSet(modalImage, enable) {
|
||||
if (modalImage) modalImage.classList.toggle('modalImageFullscreen', !!enable);
|
||||
}
|
||||
|
||||
function modalZoomToggle(event) {
|
||||
var modalImage = gradioApp().getElementById("modalImage");
|
||||
modalZoomSet(modalImage, !modalImage.classList.contains('modalImageFullscreen'));
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
function modalTileImageToggle(event) {
|
||||
const modalImage = gradioApp().getElementById("modalImage");
|
||||
const modal = gradioApp().getElementById("lightboxModal");
|
||||
const isTiling = modalImage.style.display === 'none';
|
||||
if (isTiling) {
|
||||
modalImage.style.display = 'block';
|
||||
modal.style.setProperty('background-image', 'none');
|
||||
} else {
|
||||
modalImage.style.display = 'none';
|
||||
modal.style.setProperty('background-image', `url(${modalImage.src})`);
|
||||
}
|
||||
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
onAfterUiUpdate(function() {
|
||||
var fullImg_preview = gradioApp().querySelectorAll('.image_gallery > div > img');
|
||||
if (fullImg_preview != null) {
|
||||
fullImg_preview.forEach(setupImageForLightbox);
|
||||
}
|
||||
updateOnBackgroundChange();
|
||||
});
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
//const modalFragment = document.createDocumentFragment();
|
||||
const modal = document.createElement('div');
|
||||
modal.onclick = closeModal;
|
||||
modal.id = "lightboxModal";
|
||||
modal.tabIndex = 0;
|
||||
modal.addEventListener('keydown', modalKeyHandler, true);
|
||||
|
||||
const modalControls = document.createElement('div');
|
||||
modalControls.className = 'modalControls gradio-container';
|
||||
modal.append(modalControls);
|
||||
|
||||
const modalZoom = document.createElement('span');
|
||||
modalZoom.className = 'modalZoom cursor';
|
||||
modalZoom.innerHTML = '⤡';
|
||||
modalZoom.addEventListener('click', modalZoomToggle, true);
|
||||
modalZoom.title = "Toggle zoomed view";
|
||||
modalControls.appendChild(modalZoom);
|
||||
|
||||
// const modalTileImage = document.createElement('span');
|
||||
// modalTileImage.className = 'modalTileImage cursor';
|
||||
// modalTileImage.innerHTML = '⊞';
|
||||
// modalTileImage.addEventListener('click', modalTileImageToggle, true);
|
||||
// modalTileImage.title = "Preview tiling";
|
||||
// modalControls.appendChild(modalTileImage);
|
||||
//
|
||||
// const modalSave = document.createElement("span");
|
||||
// modalSave.className = "modalSave cursor";
|
||||
// modalSave.id = "modal_save";
|
||||
// modalSave.innerHTML = "🖫";
|
||||
// modalSave.addEventListener("click", modalSaveImage, true);
|
||||
// modalSave.title = "Save Image(s)";
|
||||
// modalControls.appendChild(modalSave);
|
||||
|
||||
const modalClose = document.createElement('span');
|
||||
modalClose.className = 'modalClose cursor';
|
||||
modalClose.innerHTML = '×';
|
||||
modalClose.onclick = closeModal;
|
||||
modalClose.title = "Close image viewer";
|
||||
modalControls.appendChild(modalClose);
|
||||
|
||||
const modalImage = document.createElement('img');
|
||||
modalImage.id = 'modalImage';
|
||||
modalImage.onclick = closeModal;
|
||||
modalImage.tabIndex = 0;
|
||||
modalImage.addEventListener('keydown', modalKeyHandler, true);
|
||||
modal.appendChild(modalImage);
|
||||
|
||||
const modalPrev = document.createElement('a');
|
||||
modalPrev.className = 'modalPrev';
|
||||
modalPrev.innerHTML = '❮';
|
||||
modalPrev.tabIndex = 0;
|
||||
modalPrev.addEventListener('click', modalPrevImage, true);
|
||||
modalPrev.addEventListener('keydown', modalKeyHandler, true);
|
||||
modal.appendChild(modalPrev);
|
||||
|
||||
const modalNext = document.createElement('a');
|
||||
modalNext.className = 'modalNext';
|
||||
modalNext.innerHTML = '❯';
|
||||
modalNext.tabIndex = 0;
|
||||
modalNext.addEventListener('click', modalNextImage, true);
|
||||
modalNext.addEventListener('keydown', modalKeyHandler, true);
|
||||
|
||||
modal.appendChild(modalNext);
|
||||
|
||||
try {
|
||||
gradioApp().appendChild(modal);
|
||||
} catch (e) {
|
||||
gradioApp().body.appendChild(modal);
|
||||
}
|
||||
|
||||
document.body.appendChild(modal);
|
||||
|
||||
});
|
||||
@@ -0,0 +1,153 @@
|
||||
var re_num = /^[.\d]+$/;
|
||||
|
||||
var original_lines = {};
|
||||
var translated_lines = {};
|
||||
|
||||
function hasLocalization() {
|
||||
return window.localization && Object.keys(window.localization).length > 0;
|
||||
}
|
||||
|
||||
function textNodesUnder(el) {
|
||||
var n, a = [], walk = document.createTreeWalker(el, NodeFilter.SHOW_TEXT, null, false);
|
||||
while ((n = walk.nextNode())) a.push(n);
|
||||
return a;
|
||||
}
|
||||
|
||||
function canBeTranslated(node, text) {
|
||||
if (!text) return false;
|
||||
if (!node.parentElement) return false;
|
||||
var parentType = node.parentElement.nodeName;
|
||||
if (parentType == 'SCRIPT' || parentType == 'STYLE' || parentType == 'TEXTAREA') return false;
|
||||
if (re_num.test(text)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
function getTranslation(text) {
|
||||
if (!text) return undefined;
|
||||
|
||||
if (translated_lines[text] === undefined) {
|
||||
original_lines[text] = 1;
|
||||
}
|
||||
|
||||
var tl = localization[text];
|
||||
if (tl !== undefined) {
|
||||
translated_lines[tl] = 1;
|
||||
}
|
||||
|
||||
return tl;
|
||||
}
|
||||
|
||||
function processTextNode(node) {
|
||||
var text = node.textContent.trim();
|
||||
|
||||
if (!canBeTranslated(node, text)) return;
|
||||
|
||||
var tl = getTranslation(text);
|
||||
if (tl !== undefined) {
|
||||
node.textContent = tl;
|
||||
if (text && node.parentElement) {
|
||||
node.parentElement.setAttribute("data-original-text", text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function processNode(node) {
|
||||
if (node.nodeType == 3) {
|
||||
processTextNode(node);
|
||||
return;
|
||||
}
|
||||
|
||||
if (node.title) {
|
||||
let tl = getTranslation(node.title);
|
||||
if (tl !== undefined) {
|
||||
node.title = tl;
|
||||
}
|
||||
}
|
||||
|
||||
if (node.placeholder) {
|
||||
let tl = getTranslation(node.placeholder);
|
||||
if (tl !== undefined) {
|
||||
node.placeholder = tl;
|
||||
}
|
||||
}
|
||||
|
||||
textNodesUnder(node).forEach(function(node) {
|
||||
processTextNode(node);
|
||||
});
|
||||
}
|
||||
|
||||
function refresh_style_localization() {
|
||||
processNode(document.querySelector('.style_selections'));
|
||||
}
|
||||
|
||||
function refresh_aspect_ratios_label(value) {
|
||||
label = document.querySelector('#aspect_ratios_accordion div span');
|
||||
translation = getTranslation("Aspect Ratios");
|
||||
if (typeof translation == "undefined") {
|
||||
translation = "Aspect Ratios";
|
||||
}
|
||||
label.textContent = translation + " " + htmlDecode(value);
|
||||
}
|
||||
|
||||
function localizeWholePage() {
|
||||
processNode(gradioApp());
|
||||
|
||||
function elem(comp) {
|
||||
var elem_id = comp.props.elem_id ? comp.props.elem_id : "component-" + comp.id;
|
||||
return gradioApp().getElementById(elem_id);
|
||||
}
|
||||
|
||||
for (var comp of window.gradio_config.components) {
|
||||
if (comp.props.webui_tooltip) {
|
||||
let e = elem(comp);
|
||||
|
||||
let tl = e ? getTranslation(e.title) : undefined;
|
||||
if (tl !== undefined) {
|
||||
e.title = tl;
|
||||
}
|
||||
}
|
||||
if (comp.props.placeholder) {
|
||||
let e = elem(comp);
|
||||
let textbox = e ? e.querySelector('[placeholder]') : null;
|
||||
|
||||
let tl = textbox ? getTranslation(textbox.placeholder) : undefined;
|
||||
if (tl !== undefined) {
|
||||
textbox.placeholder = tl;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
if (!hasLocalization()) {
|
||||
return;
|
||||
}
|
||||
|
||||
onUiUpdate(function(m) {
|
||||
m.forEach(function(mutation) {
|
||||
mutation.addedNodes.forEach(function(node) {
|
||||
processNode(node);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
localizeWholePage();
|
||||
|
||||
if (localization.rtl) { // if the language is from right to left,
|
||||
(new MutationObserver((mutations, observer) => { // wait for the style to load
|
||||
mutations.forEach(mutation => {
|
||||
mutation.addedNodes.forEach(node => {
|
||||
if (node.tagName === 'STYLE') {
|
||||
observer.disconnect();
|
||||
|
||||
for (const x of node.sheet.rules) { // find all rtl media rules
|
||||
if (Array.from(x.media || []).includes('rtl')) {
|
||||
x.media.appendMedium('all'); // enable them
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
})).observe(gradioApp(), {childList: true});
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,263 @@
|
||||
// based on https://github.com/AUTOMATIC1111/stable-diffusion-webui/blob/v1.6.0/script.js
|
||||
function gradioApp() {
|
||||
const elems = document.getElementsByTagName('gradio-app');
|
||||
const elem = elems.length == 0 ? document : elems[0];
|
||||
|
||||
if (elem !== document) {
|
||||
elem.getElementById = function(id) {
|
||||
return document.getElementById(id);
|
||||
};
|
||||
}
|
||||
return elem.shadowRoot ? elem.shadowRoot : elem;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the currently selected top-level UI tab button (e.g. the button that says "Extras").
|
||||
*/
|
||||
function get_uiCurrentTab() {
|
||||
return gradioApp().querySelector('#tabs > .tab-nav > button.selected');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the first currently visible top-level UI tab content (e.g. the div hosting the "txt2img" UI).
|
||||
*/
|
||||
function get_uiCurrentTabContent() {
|
||||
return gradioApp().querySelector('#tabs > .tabitem[id^=tab_]:not([style*="display: none"])');
|
||||
}
|
||||
|
||||
var uiUpdateCallbacks = [];
|
||||
var uiAfterUpdateCallbacks = [];
|
||||
var uiLoadedCallbacks = [];
|
||||
var uiTabChangeCallbacks = [];
|
||||
var optionsChangedCallbacks = [];
|
||||
var uiAfterUpdateTimeout = null;
|
||||
var uiCurrentTab = null;
|
||||
|
||||
/**
|
||||
* Register callback to be called at each UI update.
|
||||
* The callback receives an array of MutationRecords as an argument.
|
||||
*/
|
||||
function onUiUpdate(callback) {
|
||||
uiUpdateCallbacks.push(callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register callback to be called soon after UI updates.
|
||||
* The callback receives no arguments.
|
||||
*
|
||||
* This is preferred over `onUiUpdate` if you don't need
|
||||
* access to the MutationRecords, as your function will
|
||||
* not be called quite as often.
|
||||
*/
|
||||
function onAfterUiUpdate(callback) {
|
||||
uiAfterUpdateCallbacks.push(callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register callback to be called when the UI is loaded.
|
||||
* The callback receives no arguments.
|
||||
*/
|
||||
function onUiLoaded(callback) {
|
||||
uiLoadedCallbacks.push(callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register callback to be called when the UI tab is changed.
|
||||
* The callback receives no arguments.
|
||||
*/
|
||||
function onUiTabChange(callback) {
|
||||
uiTabChangeCallbacks.push(callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register callback to be called when the options are changed.
|
||||
* The callback receives no arguments.
|
||||
* @param callback
|
||||
*/
|
||||
function onOptionsChanged(callback) {
|
||||
optionsChangedCallbacks.push(callback);
|
||||
}
|
||||
|
||||
function executeCallbacks(queue, arg) {
|
||||
for (const callback of queue) {
|
||||
try {
|
||||
callback(arg);
|
||||
} catch (e) {
|
||||
console.error("error running callback", callback, ":", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule the execution of the callbacks registered with onAfterUiUpdate.
|
||||
* The callbacks are executed after a short while, unless another call to this function
|
||||
* is made before that time. IOW, the callbacks are executed only once, even
|
||||
* when there are multiple mutations observed.
|
||||
*/
|
||||
function scheduleAfterUiUpdateCallbacks() {
|
||||
clearTimeout(uiAfterUpdateTimeout);
|
||||
uiAfterUpdateTimeout = setTimeout(function() {
|
||||
executeCallbacks(uiAfterUpdateCallbacks);
|
||||
}, 200);
|
||||
}
|
||||
|
||||
var executedOnLoaded = false;
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
var mutationObserver = new MutationObserver(function(m) {
|
||||
if (!executedOnLoaded && gradioApp().querySelector('#generate_button')) {
|
||||
executedOnLoaded = true;
|
||||
executeCallbacks(uiLoadedCallbacks);
|
||||
}
|
||||
|
||||
executeCallbacks(uiUpdateCallbacks, m);
|
||||
scheduleAfterUiUpdateCallbacks();
|
||||
const newTab = get_uiCurrentTab();
|
||||
if (newTab && (newTab !== uiCurrentTab)) {
|
||||
uiCurrentTab = newTab;
|
||||
executeCallbacks(uiTabChangeCallbacks);
|
||||
}
|
||||
});
|
||||
mutationObserver.observe(gradioApp(), {childList: true, subtree: true});
|
||||
initStylePreviewOverlay();
|
||||
});
|
||||
|
||||
var onAppend = function(elem, f) {
|
||||
var observer = new MutationObserver(function(mutations) {
|
||||
mutations.forEach(function(m) {
|
||||
if (m.addedNodes.length) {
|
||||
f(m.addedNodes);
|
||||
}
|
||||
});
|
||||
});
|
||||
observer.observe(elem, {childList: true});
|
||||
}
|
||||
|
||||
function addObserverIfDesiredNodeAvailable(querySelector, callback) {
|
||||
var elem = document.querySelector(querySelector);
|
||||
if (!elem) {
|
||||
window.setTimeout(() => addObserverIfDesiredNodeAvailable(querySelector, callback), 1000);
|
||||
return;
|
||||
}
|
||||
|
||||
onAppend(elem, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show reset button on toast "Connection errored out."
|
||||
*/
|
||||
addObserverIfDesiredNodeAvailable(".toast-wrap", function(added) {
|
||||
added.forEach(function(element) {
|
||||
if (element.innerText.includes("Connection errored out.")) {
|
||||
window.setTimeout(function() {
|
||||
document.getElementById("reset_button").classList.remove("hidden");
|
||||
document.getElementById("generate_button").classList.add("hidden");
|
||||
document.getElementById("skip_button").classList.add("hidden");
|
||||
document.getElementById("stop_button").classList.add("hidden");
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Add a ctrl+enter as a shortcut to start a generation
|
||||
*/
|
||||
document.addEventListener('keydown', function(e) {
|
||||
const isModifierKey = (e.metaKey || e.ctrlKey || e.altKey);
|
||||
const isEnterKey = (e.key == "Enter" || e.keyCode == 13);
|
||||
|
||||
if(isModifierKey && isEnterKey) {
|
||||
const generateButton = gradioApp().querySelector('button:not(.hidden)[id=generate_button]');
|
||||
if (generateButton) {
|
||||
generateButton.click();
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
const stopButton = gradioApp().querySelector('button:not(.hidden)[id=stop_button]')
|
||||
if(stopButton) {
|
||||
stopButton.click();
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function initStylePreviewOverlay() {
|
||||
let overlayVisible = false;
|
||||
const samplesPath = document.querySelector("meta[name='samples-path']").getAttribute("content")
|
||||
const overlay = document.createElement('div');
|
||||
const tooltip = document.createElement('div');
|
||||
tooltip.className = 'preview-tooltip';
|
||||
overlay.appendChild(tooltip);
|
||||
overlay.id = 'stylePreviewOverlay';
|
||||
document.body.appendChild(overlay);
|
||||
document.addEventListener('mouseover', function (e) {
|
||||
const label = e.target.closest('.style_selections label');
|
||||
if (!label) return;
|
||||
label.removeEventListener("mouseout", onMouseLeave);
|
||||
label.addEventListener("mouseout", onMouseLeave);
|
||||
overlayVisible = true;
|
||||
overlay.style.opacity = "1";
|
||||
const originalText = label.querySelector("span").getAttribute("data-original-text");
|
||||
const name = originalText || label.querySelector("span").textContent;
|
||||
overlay.style.backgroundImage = `url("${samplesPath.replace(
|
||||
"fooocus_v2",
|
||||
name.toLowerCase().replaceAll(" ", "_")
|
||||
).replaceAll("\\", "\\\\")}")`;
|
||||
|
||||
tooltip.textContent = name;
|
||||
|
||||
function onMouseLeave() {
|
||||
overlayVisible = false;
|
||||
overlay.style.opacity = "0";
|
||||
overlay.style.backgroundImage = "";
|
||||
label.removeEventListener("mouseout", onMouseLeave);
|
||||
}
|
||||
});
|
||||
document.addEventListener('mousemove', function (e) {
|
||||
if (!overlayVisible) return;
|
||||
overlay.style.left = `${e.clientX}px`;
|
||||
overlay.style.top = `${e.clientY}px`;
|
||||
overlay.className = e.clientY > window.innerHeight / 2 ? "lower-half" : "upper-half";
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* checks that a UI element is not in another hidden element or tab content
|
||||
*/
|
||||
function uiElementIsVisible(el) {
|
||||
if (el === document) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const computedStyle = getComputedStyle(el);
|
||||
const isVisible = computedStyle.display !== 'none';
|
||||
|
||||
if (!isVisible) return false;
|
||||
return uiElementIsVisible(el.parentNode);
|
||||
}
|
||||
|
||||
function uiElementInSight(el) {
|
||||
const clRect = el.getBoundingClientRect();
|
||||
const windowHeight = window.innerHeight;
|
||||
const isOnScreen = clRect.bottom > 0 && clRect.top < windowHeight;
|
||||
|
||||
return isOnScreen;
|
||||
}
|
||||
|
||||
function playNotification() {
|
||||
gradioApp().querySelector('#audio_notification audio')?.play();
|
||||
}
|
||||
|
||||
function set_theme(theme) {
|
||||
var gradioURL = window.location.href;
|
||||
if (!gradioURL.includes('?__theme=')) {
|
||||
window.location.replace(gradioURL + '?__theme=' + theme);
|
||||
}
|
||||
}
|
||||
|
||||
function htmlDecode(input) {
|
||||
var doc = new DOMParser().parseFromString(input, "text/html");
|
||||
return doc.documentElement.textContent;
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
window.main_viewer_height = 512;
|
||||
|
||||
function refresh_grid() {
|
||||
let gridContainer = document.querySelector('#final_gallery .grid-container');
|
||||
let final_gallery = document.getElementById('final_gallery');
|
||||
|
||||
if (gridContainer) if (final_gallery) {
|
||||
let rect = final_gallery.getBoundingClientRect();
|
||||
let cols = Math.ceil((rect.width - 16.0) / rect.height);
|
||||
if (cols < 2) cols = 2;
|
||||
gridContainer.style.setProperty('--grid-cols', cols);
|
||||
}
|
||||
}
|
||||
|
||||
function refresh_grid_delayed() {
|
||||
refresh_grid();
|
||||
setTimeout(refresh_grid, 100);
|
||||
setTimeout(refresh_grid, 500);
|
||||
setTimeout(refresh_grid, 1000);
|
||||
}
|
||||
|
||||
function resized() {
|
||||
let windowHeight = window.innerHeight - 260;
|
||||
let elements = document.getElementsByClassName('main_view');
|
||||
|
||||
if (windowHeight > 745) windowHeight = 745;
|
||||
|
||||
for (let i = 0; i < elements.length; i++) {
|
||||
elements[i].style.height = windowHeight + 'px';
|
||||
}
|
||||
|
||||
window.main_viewer_height = windowHeight;
|
||||
|
||||
refresh_grid();
|
||||
}
|
||||
|
||||
function viewer_to_top(delay = 100) {
|
||||
setTimeout(() => window.scrollTo({top: 0, behavior: 'smooth'}), delay);
|
||||
}
|
||||
|
||||
function viewer_to_bottom(delay = 100) {
|
||||
let element = document.getElementById('positive_prompt');
|
||||
let yPos = window.main_viewer_height;
|
||||
|
||||
if (element) {
|
||||
yPos = element.getBoundingClientRect().top + window.scrollY;
|
||||
}
|
||||
|
||||
setTimeout(() => window.scrollTo({top: yPos - 8, behavior: 'smooth'}), delay);
|
||||
}
|
||||
|
||||
window.addEventListener('resize', (e) => {
|
||||
resized();
|
||||
});
|
||||
|
||||
onUiLoaded(async () => {
|
||||
resized();
|
||||
});
|
||||
|
||||
function on_style_selection_blur() {
|
||||
let target = document.querySelector("#gradio_receiver_style_selections textarea");
|
||||
target.value = "on_style_selection_blur " + Math.random();
|
||||
let e = new Event("input", {bubbles: true})
|
||||
Object.defineProperty(e, "target", {value: target})
|
||||
target.dispatchEvent(e);
|
||||
}
|
||||
|
||||
onUiLoaded(async () => {
|
||||
let spans = document.querySelectorAll('.aspect_ratios span');
|
||||
|
||||
spans.forEach(function (span) {
|
||||
span.innerHTML = span.innerHTML.replace(/</g, '<').replace(/>/g, '>');
|
||||
});
|
||||
|
||||
document.querySelector('.style_selections').addEventListener('focusout', function (event) {
|
||||
setTimeout(() => {
|
||||
if (!this.contains(document.activeElement)) {
|
||||
on_style_selection_blur();
|
||||
}
|
||||
}, 200);
|
||||
});
|
||||
|
||||
let inputs = document.querySelectorAll('.lora_weight input[type="range"]');
|
||||
|
||||
inputs.forEach(function (input) {
|
||||
input.style.marginTop = '12px';
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,646 @@
|
||||
onUiLoaded(async() => {
|
||||
// Helper functions
|
||||
|
||||
// Detect whether the element has a horizontal scroll bar
|
||||
function hasHorizontalScrollbar(element) {
|
||||
return element.scrollWidth > element.clientWidth;
|
||||
}
|
||||
|
||||
// Function for defining the "Ctrl", "Shift" and "Alt" keys
|
||||
function isModifierKey(event, key) {
|
||||
switch (key) {
|
||||
case "Ctrl":
|
||||
return event.ctrlKey;
|
||||
case "Shift":
|
||||
return event.shiftKey;
|
||||
case "Alt":
|
||||
return event.altKey;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Create hotkey configuration with the provided options
|
||||
function createHotkeyConfig(defaultHotkeysConfig) {
|
||||
const result = {}; // Resulting hotkey configuration
|
||||
for (const key in defaultHotkeysConfig) {
|
||||
result[key] = defaultHotkeysConfig[key];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Default config
|
||||
const defaultHotkeysConfig = {
|
||||
canvas_hotkey_zoom: "Shift",
|
||||
canvas_hotkey_adjust: "Ctrl",
|
||||
canvas_zoom_undo_extra_key: "Ctrl",
|
||||
canvas_zoom_hotkey_undo: "KeyZ",
|
||||
canvas_hotkey_reset: "KeyR",
|
||||
canvas_hotkey_fullscreen: "KeyS",
|
||||
canvas_hotkey_move: "KeyF",
|
||||
canvas_show_tooltip: true,
|
||||
canvas_auto_expand: true,
|
||||
canvas_blur_prompt: true,
|
||||
};
|
||||
|
||||
// Loading the configuration from opts
|
||||
const hotkeysConfig = createHotkeyConfig(
|
||||
defaultHotkeysConfig
|
||||
);
|
||||
|
||||
let isMoving = false;
|
||||
let activeElement;
|
||||
|
||||
const elemData = {};
|
||||
|
||||
function applyZoomAndPan(elemId) {
|
||||
const targetElement = gradioApp().querySelector(elemId);
|
||||
|
||||
if (!targetElement) {
|
||||
console.log("Element not found");
|
||||
return;
|
||||
}
|
||||
|
||||
targetElement.style.transformOrigin = "0 0";
|
||||
|
||||
elemData[elemId] = {
|
||||
zoom: 1,
|
||||
panX: 0,
|
||||
panY: 0
|
||||
};
|
||||
|
||||
let fullScreenMode = false;
|
||||
|
||||
// Create tooltip
|
||||
function createTooltip() {
|
||||
const toolTipElemnt =
|
||||
targetElement.querySelector(".image-container");
|
||||
const tooltip = document.createElement("div");
|
||||
tooltip.className = "canvas-tooltip";
|
||||
|
||||
// Creating an item of information
|
||||
const info = document.createElement("i");
|
||||
info.className = "canvas-tooltip-info";
|
||||
info.textContent = "";
|
||||
|
||||
// Create a container for the contents of the tooltip
|
||||
const tooltipContent = document.createElement("div");
|
||||
tooltipContent.className = "canvas-tooltip-content";
|
||||
|
||||
// Define an array with hotkey information and their actions
|
||||
const hotkeysInfo = [
|
||||
{
|
||||
configKey: "canvas_hotkey_zoom",
|
||||
action: "Zoom canvas",
|
||||
keySuffix: " + wheel"
|
||||
},
|
||||
{
|
||||
configKey: "canvas_hotkey_adjust",
|
||||
action: "Adjust brush size",
|
||||
keySuffix: " + wheel"
|
||||
},
|
||||
{configKey: "canvas_zoom_hotkey_undo", action: "Undo last action", keyPrefix: `${hotkeysConfig.canvas_zoom_undo_extra_key} + ` },
|
||||
{configKey: "canvas_hotkey_reset", action: "Reset zoom"},
|
||||
{
|
||||
configKey: "canvas_hotkey_fullscreen",
|
||||
action: "Fullscreen mode"
|
||||
},
|
||||
{configKey: "canvas_hotkey_move", action: "Move canvas"}
|
||||
];
|
||||
|
||||
// Create hotkeys array based on the config values
|
||||
const hotkeys = hotkeysInfo.map((info) => {
|
||||
const configValue = hotkeysConfig[info.configKey];
|
||||
|
||||
let key = configValue.slice(-1);
|
||||
|
||||
if (info.keySuffix) {
|
||||
key = `${configValue}${info.keySuffix}`;
|
||||
}
|
||||
|
||||
if (info.keyPrefix && info.keyPrefix !== "None + ") {
|
||||
key = `${info.keyPrefix}${configValue[3]}`;
|
||||
}
|
||||
|
||||
return {
|
||||
key,
|
||||
action: info.action,
|
||||
};
|
||||
});
|
||||
|
||||
hotkeys
|
||||
.forEach(hotkey => {
|
||||
const p = document.createElement("p");
|
||||
p.innerHTML = `<b>${hotkey.key}</b> - ${hotkey.action}`;
|
||||
tooltipContent.appendChild(p);
|
||||
});
|
||||
|
||||
tooltip.append(info, tooltipContent);
|
||||
|
||||
// Add a hint element to the target element
|
||||
toolTipElemnt.appendChild(tooltip);
|
||||
}
|
||||
|
||||
//Show tool tip if setting enable
|
||||
if (hotkeysConfig.canvas_show_tooltip) {
|
||||
createTooltip();
|
||||
}
|
||||
|
||||
// Reset the zoom level and pan position of the target element to their initial values
|
||||
function resetZoom() {
|
||||
elemData[elemId] = {
|
||||
zoomLevel: 1,
|
||||
panX: 0,
|
||||
panY: 0
|
||||
};
|
||||
|
||||
targetElement.style.overflow = "hidden";
|
||||
|
||||
targetElement.isZoomed = false;
|
||||
|
||||
targetElement.style.transform = `scale(${elemData[elemId].zoomLevel}) translate(${elemData[elemId].panX}px, ${elemData[elemId].panY}px)`;
|
||||
|
||||
const canvas = gradioApp().querySelector(
|
||||
`${elemId} canvas[key="interface"]`
|
||||
);
|
||||
|
||||
toggleOverlap("off");
|
||||
fullScreenMode = false;
|
||||
|
||||
const closeBtn = targetElement.querySelector("button[aria-label='Remove Image']");
|
||||
if (closeBtn) {
|
||||
closeBtn.addEventListener("click", resetZoom);
|
||||
}
|
||||
|
||||
if (canvas) {
|
||||
const parentElement = targetElement.closest('[id^="component-"]');
|
||||
if (
|
||||
canvas &&
|
||||
parseFloat(canvas.style.width) > parentElement.offsetWidth &&
|
||||
parseFloat(targetElement.style.width) > parentElement.offsetWidth
|
||||
) {
|
||||
fitToElement();
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
targetElement.style.width = "";
|
||||
}
|
||||
|
||||
// Toggle the zIndex of the target element between two values, allowing it to overlap or be overlapped by other elements
|
||||
function toggleOverlap(forced = "") {
|
||||
const zIndex1 = "0";
|
||||
const zIndex2 = "998";
|
||||
|
||||
targetElement.style.zIndex =
|
||||
targetElement.style.zIndex !== zIndex2 ? zIndex2 : zIndex1;
|
||||
|
||||
if (forced === "off") {
|
||||
targetElement.style.zIndex = zIndex1;
|
||||
} else if (forced === "on") {
|
||||
targetElement.style.zIndex = zIndex2;
|
||||
}
|
||||
}
|
||||
|
||||
// Adjust the brush size based on the deltaY value from a mouse wheel event
|
||||
function adjustBrushSize(
|
||||
elemId,
|
||||
deltaY,
|
||||
withoutValue = false,
|
||||
percentage = 5
|
||||
) {
|
||||
const input =
|
||||
gradioApp().querySelector(
|
||||
`${elemId} input[aria-label='Brush radius']`
|
||||
) ||
|
||||
gradioApp().querySelector(
|
||||
`${elemId} button[aria-label="Use brush"]`
|
||||
);
|
||||
|
||||
if (input) {
|
||||
input.click();
|
||||
if (!withoutValue) {
|
||||
const maxValue =
|
||||
parseFloat(input.getAttribute("max")) || 100;
|
||||
const changeAmount = maxValue * (percentage / 100);
|
||||
const newValue =
|
||||
parseFloat(input.value) +
|
||||
(deltaY > 0 ? -changeAmount : changeAmount);
|
||||
input.value = Math.min(Math.max(newValue, 0), maxValue);
|
||||
input.dispatchEvent(new Event("change"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reset zoom when uploading a new image
|
||||
const fileInput = gradioApp().querySelector(
|
||||
`${elemId} input[type="file"][accept="image/*"].svelte-116rqfv`
|
||||
);
|
||||
fileInput.addEventListener("click", resetZoom);
|
||||
|
||||
// Update the zoom level and pan position of the target element based on the values of the zoomLevel, panX and panY variables
|
||||
function updateZoom(newZoomLevel, mouseX, mouseY) {
|
||||
newZoomLevel = Math.max(0.1, Math.min(newZoomLevel, 15));
|
||||
|
||||
elemData[elemId].panX +=
|
||||
mouseX - (mouseX * newZoomLevel) / elemData[elemId].zoomLevel;
|
||||
elemData[elemId].panY +=
|
||||
mouseY - (mouseY * newZoomLevel) / elemData[elemId].zoomLevel;
|
||||
|
||||
targetElement.style.transformOrigin = "0 0";
|
||||
targetElement.style.transform = `translate(${elemData[elemId].panX}px, ${elemData[elemId].panY}px) scale(${newZoomLevel})`;
|
||||
targetElement.style.overflow = "visible";
|
||||
|
||||
toggleOverlap("on");
|
||||
|
||||
return newZoomLevel;
|
||||
}
|
||||
|
||||
// Change the zoom level based on user interaction
|
||||
function changeZoomLevel(operation, e) {
|
||||
if (isModifierKey(e, hotkeysConfig.canvas_hotkey_zoom)) {
|
||||
e.preventDefault();
|
||||
|
||||
let zoomPosX, zoomPosY;
|
||||
let delta = 0.2;
|
||||
|
||||
if (elemData[elemId].zoomLevel > 7) {
|
||||
delta = 0.9;
|
||||
} else if (elemData[elemId].zoomLevel > 2) {
|
||||
delta = 0.6;
|
||||
}
|
||||
|
||||
zoomPosX = e.clientX;
|
||||
zoomPosY = e.clientY;
|
||||
|
||||
fullScreenMode = false;
|
||||
elemData[elemId].zoomLevel = updateZoom(
|
||||
elemData[elemId].zoomLevel +
|
||||
(operation === "+" ? delta : -delta),
|
||||
zoomPosX - targetElement.getBoundingClientRect().left,
|
||||
zoomPosY - targetElement.getBoundingClientRect().top
|
||||
);
|
||||
|
||||
targetElement.isZoomed = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This function fits the target element to the screen by calculating
|
||||
* the required scale and offsets. It also updates the global variables
|
||||
* zoomLevel, panX, and panY to reflect the new state.
|
||||
*/
|
||||
|
||||
function fitToElement() {
|
||||
//Reset Zoom
|
||||
targetElement.style.transform = `translate(${0}px, ${0}px) scale(${1})`;
|
||||
|
||||
let parentElement;
|
||||
|
||||
parentElement = targetElement.closest('[id^="component-"]');
|
||||
|
||||
// Get element and screen dimensions
|
||||
const elementWidth = targetElement.offsetWidth;
|
||||
const elementHeight = targetElement.offsetHeight;
|
||||
|
||||
const screenWidth = parentElement.clientWidth - 24;
|
||||
const screenHeight = parentElement.clientHeight;
|
||||
|
||||
// Calculate scale and offsets
|
||||
const scaleX = screenWidth / elementWidth;
|
||||
const scaleY = screenHeight / elementHeight;
|
||||
const scale = Math.min(scaleX, scaleY);
|
||||
|
||||
const offsetX =0;
|
||||
const offsetY =0;
|
||||
|
||||
// Apply scale and offsets to the element
|
||||
targetElement.style.transform = `translate(${offsetX}px, ${offsetY}px) scale(${scale})`;
|
||||
|
||||
// Update global variables
|
||||
elemData[elemId].zoomLevel = scale;
|
||||
elemData[elemId].panX = offsetX;
|
||||
elemData[elemId].panY = offsetY;
|
||||
|
||||
fullScreenMode = false;
|
||||
toggleOverlap("off");
|
||||
}
|
||||
|
||||
// Undo last action
|
||||
function undoLastAction(e) {
|
||||
let isCtrlPressed = isModifierKey(e, hotkeysConfig.canvas_zoom_undo_extra_key)
|
||||
const isAuxButton = e.button >= 3;
|
||||
|
||||
if (isAuxButton) {
|
||||
isCtrlPressed = true
|
||||
} else {
|
||||
if (!isModifierKey(e, hotkeysConfig.canvas_zoom_undo_extra_key)) return;
|
||||
}
|
||||
|
||||
// Move undoBtn query outside the if statement to avoid unnecessary queries
|
||||
const undoBtn = document.querySelector(`${activeElement} button[aria-label="Undo"]`);
|
||||
|
||||
if ((isCtrlPressed) && undoBtn ) {
|
||||
e.preventDefault();
|
||||
undoBtn.click();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This function fits the target element to the screen by calculating
|
||||
* the required scale and offsets. It also updates the global variables
|
||||
* zoomLevel, panX, and panY to reflect the new state.
|
||||
*/
|
||||
|
||||
// Fullscreen mode
|
||||
function fitToScreen() {
|
||||
const canvas = gradioApp().querySelector(
|
||||
`${elemId} canvas[key="interface"]`
|
||||
);
|
||||
|
||||
if (!canvas) return;
|
||||
|
||||
targetElement.style.width = (canvas.offsetWidth + 2) + "px";
|
||||
targetElement.style.overflow = "visible";
|
||||
|
||||
if (fullScreenMode) {
|
||||
resetZoom();
|
||||
fullScreenMode = false;
|
||||
return;
|
||||
}
|
||||
|
||||
//Reset Zoom
|
||||
targetElement.style.transform = `translate(${0}px, ${0}px) scale(${1})`;
|
||||
|
||||
// Get scrollbar width to right-align the image
|
||||
const scrollbarWidth =
|
||||
window.innerWidth - document.documentElement.clientWidth;
|
||||
|
||||
// Get element and screen dimensions
|
||||
const elementWidth = targetElement.offsetWidth;
|
||||
const elementHeight = targetElement.offsetHeight;
|
||||
const screenWidth = window.innerWidth - scrollbarWidth;
|
||||
const screenHeight = window.innerHeight;
|
||||
|
||||
// Get element's coordinates relative to the page
|
||||
const elementRect = targetElement.getBoundingClientRect();
|
||||
const elementY = elementRect.y;
|
||||
const elementX = elementRect.x;
|
||||
|
||||
// Calculate scale and offsets
|
||||
const scaleX = screenWidth / elementWidth;
|
||||
const scaleY = screenHeight / elementHeight;
|
||||
const scale = Math.min(scaleX, scaleY);
|
||||
|
||||
// Get the current transformOrigin
|
||||
const computedStyle = window.getComputedStyle(targetElement);
|
||||
const transformOrigin = computedStyle.transformOrigin;
|
||||
const [originX, originY] = transformOrigin.split(" ");
|
||||
const originXValue = parseFloat(originX);
|
||||
const originYValue = parseFloat(originY);
|
||||
|
||||
// Calculate offsets with respect to the transformOrigin
|
||||
const offsetX =
|
||||
(screenWidth - elementWidth * scale) / 2 -
|
||||
elementX -
|
||||
originXValue * (1 - scale);
|
||||
const offsetY =
|
||||
(screenHeight - elementHeight * scale) / 2 -
|
||||
elementY -
|
||||
originYValue * (1 - scale);
|
||||
|
||||
// Apply scale and offsets to the element
|
||||
targetElement.style.transform = `translate(${offsetX}px, ${offsetY}px) scale(${scale})`;
|
||||
|
||||
// Update global variables
|
||||
elemData[elemId].zoomLevel = scale;
|
||||
elemData[elemId].panX = offsetX;
|
||||
elemData[elemId].panY = offsetY;
|
||||
|
||||
fullScreenMode = true;
|
||||
toggleOverlap("on");
|
||||
}
|
||||
|
||||
// Handle keydown events
|
||||
function handleKeyDown(event) {
|
||||
// Disable key locks to make pasting from the buffer work correctly
|
||||
if ((event.ctrlKey && event.code === 'KeyV') || (event.ctrlKey && event.code === 'KeyC') || event.code === "F5") {
|
||||
return;
|
||||
}
|
||||
|
||||
// before activating shortcut, ensure user is not actively typing in an input field
|
||||
if (!hotkeysConfig.canvas_blur_prompt) {
|
||||
if (event.target.nodeName === 'TEXTAREA' || event.target.nodeName === 'INPUT') {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const hotkeyActions = {
|
||||
[hotkeysConfig.canvas_hotkey_reset]: resetZoom,
|
||||
[hotkeysConfig.canvas_hotkey_overlap]: toggleOverlap,
|
||||
[hotkeysConfig.canvas_hotkey_fullscreen]: fitToScreen,
|
||||
[hotkeysConfig.canvas_zoom_hotkey_undo]: undoLastAction,
|
||||
};
|
||||
|
||||
const action = hotkeyActions[event.code];
|
||||
if (action) {
|
||||
event.preventDefault();
|
||||
action(event);
|
||||
}
|
||||
|
||||
if (
|
||||
isModifierKey(event, hotkeysConfig.canvas_hotkey_zoom) ||
|
||||
isModifierKey(event, hotkeysConfig.canvas_hotkey_adjust)
|
||||
) {
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
// Get Mouse position
|
||||
function getMousePosition(e) {
|
||||
mouseX = e.offsetX;
|
||||
mouseY = e.offsetY;
|
||||
}
|
||||
|
||||
// Simulation of the function to put a long image into the screen.
|
||||
// We detect if an image has a scroll bar or not, make a fullscreen to reveal the image, then reduce it to fit into the element.
|
||||
// We hide the image and show it to the user when it is ready.
|
||||
|
||||
targetElement.isExpanded = false;
|
||||
function autoExpand() {
|
||||
const canvas = document.querySelector(`${elemId} canvas[key="interface"]`);
|
||||
if (canvas) {
|
||||
if (hasHorizontalScrollbar(targetElement) && targetElement.isExpanded === false) {
|
||||
targetElement.style.visibility = "hidden";
|
||||
setTimeout(() => {
|
||||
fitToScreen();
|
||||
resetZoom();
|
||||
targetElement.style.visibility = "visible";
|
||||
targetElement.isExpanded = true;
|
||||
}, 10);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
targetElement.addEventListener("mousemove", getMousePosition);
|
||||
targetElement.addEventListener("auxclick", undoLastAction);
|
||||
|
||||
//observers
|
||||
// Creating an observer with a callback function to handle DOM changes
|
||||
const observer = new MutationObserver((mutationsList, observer) => {
|
||||
for (let mutation of mutationsList) {
|
||||
// If the style attribute of the canvas has changed, by observation it happens only when the picture changes
|
||||
if (mutation.type === 'attributes' && mutation.attributeName === 'style' &&
|
||||
mutation.target.tagName.toLowerCase() === 'canvas') {
|
||||
targetElement.isExpanded = false;
|
||||
setTimeout(resetZoom, 10);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Apply auto expand if enabled
|
||||
if (hotkeysConfig.canvas_auto_expand) {
|
||||
targetElement.addEventListener("mousemove", autoExpand);
|
||||
// Set up an observer to track attribute changes
|
||||
observer.observe(targetElement, { attributes: true, childList: true, subtree: true });
|
||||
}
|
||||
|
||||
// Handle events only inside the targetElement
|
||||
let isKeyDownHandlerAttached = false;
|
||||
|
||||
function handleMouseMove() {
|
||||
if (!isKeyDownHandlerAttached) {
|
||||
document.addEventListener("keydown", handleKeyDown);
|
||||
isKeyDownHandlerAttached = true;
|
||||
|
||||
activeElement = elemId;
|
||||
}
|
||||
}
|
||||
|
||||
function handleMouseLeave() {
|
||||
if (isKeyDownHandlerAttached) {
|
||||
document.removeEventListener("keydown", handleKeyDown);
|
||||
isKeyDownHandlerAttached = false;
|
||||
|
||||
activeElement = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Add mouse event handlers
|
||||
targetElement.addEventListener("mousemove", handleMouseMove);
|
||||
targetElement.addEventListener("mouseleave", handleMouseLeave);
|
||||
|
||||
targetElement.addEventListener("wheel", e => {
|
||||
// change zoom level
|
||||
const operation = e.deltaY > 0 ? "-" : "+";
|
||||
changeZoomLevel(operation, e);
|
||||
|
||||
// Handle brush size adjustment with ctrl key pressed
|
||||
if (isModifierKey(e, hotkeysConfig.canvas_hotkey_adjust)) {
|
||||
e.preventDefault();
|
||||
|
||||
// Increase or decrease brush size based on scroll direction
|
||||
adjustBrushSize(elemId, e.deltaY);
|
||||
}
|
||||
});
|
||||
|
||||
// Handle the move event for pan functionality. Updates the panX and panY variables and applies the new transform to the target element.
|
||||
function handleMoveKeyDown(e) {
|
||||
|
||||
// Disable key locks to make pasting from the buffer work correctly
|
||||
if ((e.ctrlKey && e.code === 'KeyV') || (e.ctrlKey && e.code === 'KeyC') || e.code === "F5") {
|
||||
return;
|
||||
}
|
||||
|
||||
// before activating shortcut, ensure user is not actively typing in an input field
|
||||
if (!hotkeysConfig.canvas_blur_prompt) {
|
||||
if (e.target.nodeName === 'TEXTAREA' || e.target.nodeName === 'INPUT') {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (e.code === hotkeysConfig.canvas_hotkey_move) {
|
||||
if (!e.ctrlKey && !e.metaKey && isKeyDownHandlerAttached) {
|
||||
e.preventDefault();
|
||||
document.activeElement.blur();
|
||||
isMoving = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function handleMoveKeyUp(e) {
|
||||
if (e.code === hotkeysConfig.canvas_hotkey_move) {
|
||||
isMoving = false;
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener("keydown", handleMoveKeyDown);
|
||||
document.addEventListener("keyup", handleMoveKeyUp);
|
||||
|
||||
// Detect zoom level and update the pan speed.
|
||||
function updatePanPosition(movementX, movementY) {
|
||||
let panSpeed = 2;
|
||||
|
||||
if (elemData[elemId].zoomLevel > 8) {
|
||||
panSpeed = 3.5;
|
||||
}
|
||||
|
||||
elemData[elemId].panX += movementX * panSpeed;
|
||||
elemData[elemId].panY += movementY * panSpeed;
|
||||
|
||||
// Delayed redraw of an element
|
||||
requestAnimationFrame(() => {
|
||||
targetElement.style.transform = `translate(${elemData[elemId].panX}px, ${elemData[elemId].panY}px) scale(${elemData[elemId].zoomLevel})`;
|
||||
toggleOverlap("on");
|
||||
});
|
||||
}
|
||||
|
||||
function handleMoveByKey(e) {
|
||||
if (isMoving && elemId === activeElement) {
|
||||
updatePanPosition(e.movementX, e.movementY);
|
||||
targetElement.style.pointerEvents = "none";
|
||||
targetElement.style.overflow = "visible";
|
||||
} else {
|
||||
targetElement.style.pointerEvents = "auto";
|
||||
}
|
||||
}
|
||||
|
||||
// Prevents sticking to the mouse
|
||||
window.onblur = function() {
|
||||
isMoving = false;
|
||||
};
|
||||
|
||||
// Checks for extension
|
||||
function checkForOutBox() {
|
||||
const parentElement = targetElement.closest('[id^="component-"]');
|
||||
if (parentElement.offsetWidth < targetElement.offsetWidth && !targetElement.isExpanded) {
|
||||
resetZoom();
|
||||
targetElement.isExpanded = true;
|
||||
}
|
||||
|
||||
if (parentElement.offsetWidth < targetElement.offsetWidth && elemData[elemId].zoomLevel == 1) {
|
||||
resetZoom();
|
||||
}
|
||||
|
||||
if (parentElement.offsetWidth < targetElement.offsetWidth && targetElement.offsetWidth * elemData[elemId].zoomLevel > parentElement.offsetWidth && elemData[elemId].zoomLevel < 1 && !targetElement.isZoomed) {
|
||||
resetZoom();
|
||||
}
|
||||
}
|
||||
|
||||
targetElement.addEventListener("mousemove", checkForOutBox);
|
||||
|
||||
window.addEventListener('resize', (e) => {
|
||||
resetZoom();
|
||||
|
||||
targetElement.isExpanded = false;
|
||||
targetElement.isZoomed = false;
|
||||
});
|
||||
|
||||
gradioApp().addEventListener("mousemove", handleMoveByKey);
|
||||
}
|
||||
|
||||
applyZoomAndPan("#inpaint_canvas");
|
||||
applyZoomAndPan("#inpaint_mask_canvas");
|
||||
});
|
||||
@@ -0,0 +1,485 @@
|
||||
{
|
||||
"Preview": "Preview",
|
||||
"Gallery": "Gallery",
|
||||
"Generate": "Generate",
|
||||
"Skip": "Skip",
|
||||
"Stop": "Stop",
|
||||
"Reconnect": "Reconnect",
|
||||
"Input Image": "Input Image",
|
||||
"Advanced": "Advanced",
|
||||
"Upscale or Variation": "Upscale or Variation",
|
||||
"Image Prompt": "Image Prompt",
|
||||
"Inpaint or Outpaint": "Inpaint or Outpaint",
|
||||
"Outpaint Direction": "Outpaint Direction",
|
||||
"Enable Advanced Masking Features": "Enable Advanced Masking Features",
|
||||
"Method": "Method",
|
||||
"Describe": "Describe",
|
||||
"Content Type": "Content Type",
|
||||
"Photograph": "Photograph",
|
||||
"Art/Anime": "Art/Anime",
|
||||
"Apply Styles": "Apply Styles",
|
||||
"Describe this Image into Prompt": "Describe this Image into Prompt",
|
||||
"Image Size and Recommended Size": "Image Size and Recommended Size",
|
||||
"Upscale or Variation:": "Upscale or Variation:",
|
||||
"Disabled": "Disabled",
|
||||
"Vary (Subtle)": "Vary (Subtle)",
|
||||
"Vary (Strong)": "Vary (Strong)",
|
||||
"Upscale (1.5x)": "Upscale (1.5x)",
|
||||
"Upscale (2x)": "Upscale (2x)",
|
||||
"Upscale (Fast 2x)": "Upscale (Fast 2x)",
|
||||
"\ud83d\udcd4 Documentation": "\uD83D\uDCD4 Documentation",
|
||||
"Image": "Image",
|
||||
"Stop At": "Stop At",
|
||||
"Weight": "Weight",
|
||||
"Type": "Type",
|
||||
"PyraCanny": "PyraCanny",
|
||||
"CPDS": "CPDS",
|
||||
"* \"Image Prompt\" is powered by Fooocus Image Mixture Engine (v1.0.1).": "* \"Image Prompt\" is powered by Fooocus Image Mixture Engine (v1.0.1).",
|
||||
"The scaler multiplied to positive ADM (use 1.0 to disable).": "The scaler multiplied to positive ADM (use 1.0 to disable).",
|
||||
"The scaler multiplied to negative ADM (use 1.0 to disable).": "The scaler multiplied to negative ADM (use 1.0 to disable).",
|
||||
"When to end the guidance from positive/negative ADM.": "When to end the guidance from positive/negative ADM.",
|
||||
"Similar to the Control Mode in A1111 (use 0.0 to disable).": "Similar to the Control Mode in A1111 (use 0.0 to disable).",
|
||||
"Outpaint Expansion (": "Outpaint Expansion (",
|
||||
"Outpaint": "Outpaint",
|
||||
"Left": "Left",
|
||||
"Right": "Right",
|
||||
"Top": "Top",
|
||||
"Bottom": "Bottom",
|
||||
"* \"Inpaint or Outpaint\" is powered by the sampler \"DPMPP Fooocus Seamless 2M SDE Karras Inpaint Sampler\" (beta)": "* \"Inpaint or Outpaint\" is powered by the sampler \"DPMPP Fooocus Seamless 2M SDE Karras Inpaint Sampler\" (beta)",
|
||||
"Advanced options": "Advanced options",
|
||||
"Generate mask from image": "Generate mask from image",
|
||||
"Settings": "Settings",
|
||||
"Style": "Style",
|
||||
"Styles": "Styles",
|
||||
"Preset": "Preset",
|
||||
"Performance": "Performance",
|
||||
"Speed": "Speed",
|
||||
"Quality": "Quality",
|
||||
"Extreme Speed": "Extreme Speed",
|
||||
"Lightning": "Lightning",
|
||||
"Aspect Ratios": "Aspect Ratios",
|
||||
"width \u00d7 height": "width \u00d7 height",
|
||||
"Image Number": "Image Number",
|
||||
"Negative Prompt": "Negative Prompt",
|
||||
"Describing what you do not want to see.": "Describing what you do not want to see.",
|
||||
"Random": "Random",
|
||||
"Seed": "Seed",
|
||||
"Disable seed increment": "Disable seed increment",
|
||||
"Disable automatic seed increment when image number is > 1.": "Disable automatic seed increment when image number is > 1.",
|
||||
"Read wildcards in order": "Read wildcards in order",
|
||||
"Black Out NSFW": "Black Out NSFW",
|
||||
"Use black image if NSFW is detected.": "Use black image if NSFW is detected.",
|
||||
"Save only final enhanced image": "Save only final enhanced image",
|
||||
"Save Metadata to Images": "Save Metadata to Images",
|
||||
"Adds parameters to generated images allowing manual regeneration.": "Adds parameters to generated images allowing manual regeneration.",
|
||||
"\ud83d\udcda History Log": "\uD83D\uDCDA History Log",
|
||||
"Image Style": "Image Style",
|
||||
"Fooocus V2": "Fooocus V2",
|
||||
"Random Style": "Random Style",
|
||||
"Default (Slightly Cinematic)": "Default (Slightly Cinematic)",
|
||||
"Fooocus Masterpiece": "Fooocus Masterpiece",
|
||||
"Fooocus Photograph": "Fooocus Photograph",
|
||||
"Fooocus Negative": "Fooocus Negative",
|
||||
"SAI 3D Model": "SAI 3D Model",
|
||||
"SAI Analog Film": "SAI Analog Film",
|
||||
"SAI Anime": "SAI Anime",
|
||||
"SAI Cinematic": "SAI Cinematic",
|
||||
"SAI Comic Book": "SAI Comic Book",
|
||||
"SAI Craft Clay": "SAI Craft Clay",
|
||||
"SAI Digital Art": "SAI Digital Art",
|
||||
"SAI Enhance": "SAI Enhance",
|
||||
"SAI Fantasy Art": "SAI Fantasy Art",
|
||||
"SAI Isometric": "SAI Isometric",
|
||||
"SAI Line Art": "SAI Line Art",
|
||||
"SAI Lowpoly": "SAI Lowpoly",
|
||||
"SAI Neonpunk": "SAI Neonpunk",
|
||||
"SAI Origami": "SAI Origami",
|
||||
"SAI Photographic": "SAI Photographic",
|
||||
"SAI Pixel Art": "SAI Pixel Art",
|
||||
"SAI Texture": "SAI Texture",
|
||||
"MRE Cinematic Dynamic": "MRE Cinematic Dynamic",
|
||||
"MRE Spontaneous Picture": "MRE Spontaneous Picture",
|
||||
"MRE Artistic Vision": "MRE Artistic Vision",
|
||||
"MRE Dark Dream": "MRE Dark Dream",
|
||||
"MRE Gloomy Art": "MRE Gloomy Art",
|
||||
"MRE Bad Dream": "MRE Bad Dream",
|
||||
"MRE Underground": "MRE Underground",
|
||||
"MRE Surreal Painting": "MRE Surreal Painting",
|
||||
"MRE Dynamic Illustration": "MRE Dynamic Illustration",
|
||||
"MRE Undead Art": "MRE Undead Art",
|
||||
"MRE Elemental Art": "MRE Elemental Art",
|
||||
"MRE Space Art": "MRE Space Art",
|
||||
"MRE Ancient Illustration": "MRE Ancient Illustration",
|
||||
"MRE Brave Art": "MRE Brave Art",
|
||||
"MRE Heroic Fantasy": "MRE Heroic Fantasy",
|
||||
"MRE Dark Cyberpunk": "MRE Dark Cyberpunk",
|
||||
"MRE Lyrical Geometry": "MRE Lyrical Geometry",
|
||||
"MRE Sumi E Symbolic": "MRE Sumi E Symbolic",
|
||||
"MRE Sumi E Detailed": "MRE Sumi E Detailed",
|
||||
"MRE Manga": "MRE Manga",
|
||||
"MRE Anime": "MRE Anime",
|
||||
"MRE Comic": "MRE Comic",
|
||||
"Ads Advertising": "Ads Advertising",
|
||||
"Ads Automotive": "Ads Automotive",
|
||||
"Ads Corporate": "Ads Corporate",
|
||||
"Ads Fashion Editorial": "Ads Fashion Editorial",
|
||||
"Ads Food Photography": "Ads Food Photography",
|
||||
"Ads Gourmet Food Photography": "Ads Gourmet Food Photography",
|
||||
"Ads Luxury": "Ads Luxury",
|
||||
"Ads Real Estate": "Ads Real Estate",
|
||||
"Ads Retail": "Ads Retail",
|
||||
"Artstyle Abstract": "Artstyle Abstract",
|
||||
"Artstyle Abstract Expressionism": "Artstyle Abstract Expressionism",
|
||||
"Artstyle Art Deco": "Artstyle Art Deco",
|
||||
"Artstyle Art Nouveau": "Artstyle Art Nouveau",
|
||||
"Artstyle Constructivist": "Artstyle Constructivist",
|
||||
"Artstyle Cubist": "Artstyle Cubist",
|
||||
"Artstyle Expressionist": "Artstyle Expressionist",
|
||||
"Artstyle Graffiti": "Artstyle Graffiti",
|
||||
"Artstyle Hyperrealism": "Artstyle Hyperrealism",
|
||||
"Artstyle Impressionist": "Artstyle Impressionist",
|
||||
"Artstyle Pointillism": "Artstyle Pointillism",
|
||||
"Artstyle Pop Art": "Artstyle Pop Art",
|
||||
"Artstyle Psychedelic": "Artstyle Psychedelic",
|
||||
"Artstyle Renaissance": "Artstyle Renaissance",
|
||||
"Artstyle Steampunk": "Artstyle Steampunk",
|
||||
"Artstyle Surrealist": "Artstyle Surrealist",
|
||||
"Artstyle Typography": "Artstyle Typography",
|
||||
"Artstyle Watercolor": "Artstyle Watercolor",
|
||||
"Futuristic Biomechanical": "Futuristic Biomechanical",
|
||||
"Futuristic Biomechanical Cyberpunk": "Futuristic Biomechanical Cyberpunk",
|
||||
"Futuristic Cybernetic": "Futuristic Cybernetic",
|
||||
"Futuristic Cybernetic Robot": "Futuristic Cybernetic Robot",
|
||||
"Futuristic Cyberpunk Cityscape": "Futuristic Cyberpunk Cityscape",
|
||||
"Futuristic Futuristic": "Futuristic Futuristic",
|
||||
"Futuristic Retro Cyberpunk": "Futuristic Retro Cyberpunk",
|
||||
"Futuristic Retro Futurism": "Futuristic Retro Futurism",
|
||||
"Futuristic Sci Fi": "Futuristic Sci Fi",
|
||||
"Futuristic Vaporwave": "Futuristic Vaporwave",
|
||||
"Game Bubble Bobble": "Game Bubble Bobble",
|
||||
"Game Cyberpunk Game": "Game Cyberpunk Game",
|
||||
"Game Fighting Game": "Game Fighting Game",
|
||||
"Game Gta": "Game Gta",
|
||||
"Game Mario": "Game Mario",
|
||||
"Game Minecraft": "Game Minecraft",
|
||||
"Game Pokemon": "Game Pokemon",
|
||||
"Game Retro Arcade": "Game Retro Arcade",
|
||||
"Game Retro Game": "Game Retro Game",
|
||||
"Game Rpg Fantasy Game": "Game Rpg Fantasy Game",
|
||||
"Game Strategy Game": "Game Strategy Game",
|
||||
"Game Streetfighter": "Game Streetfighter",
|
||||
"Game Zelda": "Game Zelda",
|
||||
"Misc Architectural": "Misc Architectural",
|
||||
"Misc Disco": "Misc Disco",
|
||||
"Misc Dreamscape": "Misc Dreamscape",
|
||||
"Misc Dystopian": "Misc Dystopian",
|
||||
"Misc Fairy Tale": "Misc Fairy Tale",
|
||||
"Misc Gothic": "Misc Gothic",
|
||||
"Misc Grunge": "Misc Grunge",
|
||||
"Misc Horror": "Misc Horror",
|
||||
"Misc Kawaii": "Misc Kawaii",
|
||||
"Misc Lovecraftian": "Misc Lovecraftian",
|
||||
"Misc Macabre": "Misc Macabre",
|
||||
"Misc Manga": "Misc Manga",
|
||||
"Misc Metropolis": "Misc Metropolis",
|
||||
"Misc Minimalist": "Misc Minimalist",
|
||||
"Misc Monochrome": "Misc Monochrome",
|
||||
"Misc Nautical": "Misc Nautical",
|
||||
"Misc Space": "Misc Space",
|
||||
"Misc Stained Glass": "Misc Stained Glass",
|
||||
"Misc Techwear Fashion": "Misc Techwear Fashion",
|
||||
"Misc Tribal": "Misc Tribal",
|
||||
"Misc Zentangle": "Misc Zentangle",
|
||||
"Papercraft Collage": "Papercraft Collage",
|
||||
"Papercraft Flat Papercut": "Papercraft Flat Papercut",
|
||||
"Papercraft Kirigami": "Papercraft Kirigami",
|
||||
"Papercraft Paper Mache": "Papercraft Paper Mache",
|
||||
"Papercraft Paper Quilling": "Papercraft Paper Quilling",
|
||||
"Papercraft Papercut Collage": "Papercraft Papercut Collage",
|
||||
"Papercraft Papercut Shadow Box": "Papercraft Papercut Shadow Box",
|
||||
"Papercraft Stacked Papercut": "Papercraft Stacked Papercut",
|
||||
"Papercraft Thick Layered Papercut": "Papercraft Thick Layered Papercut",
|
||||
"Photo Alien": "Photo Alien",
|
||||
"Photo Film Noir": "Photo Film Noir",
|
||||
"Photo Glamour": "Photo Glamour",
|
||||
"Photo Hdr": "Photo Hdr",
|
||||
"Photo Iphone Photographic": "Photo Iphone Photographic",
|
||||
"Photo Long Exposure": "Photo Long Exposure",
|
||||
"Photo Neon Noir": "Photo Neon Noir",
|
||||
"Photo Silhouette": "Photo Silhouette",
|
||||
"Photo Tilt Shift": "Photo Tilt Shift",
|
||||
"Cinematic Diva": "Cinematic Diva",
|
||||
"Abstract Expressionism": "Abstract Expressionism",
|
||||
"Academia": "Academia",
|
||||
"Action Figure": "Action Figure",
|
||||
"Adorable 3D Character": "Adorable 3D Character",
|
||||
"Adorable Kawaii": "Adorable Kawaii",
|
||||
"Art Deco": "Art Deco",
|
||||
"Art Nouveau": "Art Nouveau",
|
||||
"Astral Aura": "Astral Aura",
|
||||
"Avant Garde": "Avant Garde",
|
||||
"Baroque": "Baroque",
|
||||
"Bauhaus Style Poster": "Bauhaus Style Poster",
|
||||
"Blueprint Schematic Drawing": "Blueprint Schematic Drawing",
|
||||
"Caricature": "Caricature",
|
||||
"Cel Shaded Art": "Cel Shaded Art",
|
||||
"Character Design Sheet": "Character Design Sheet",
|
||||
"Classicism Art": "Classicism Art",
|
||||
"Color Field Painting": "Color Field Painting",
|
||||
"Colored Pencil Art": "Colored Pencil Art",
|
||||
"Conceptual Art": "Conceptual Art",
|
||||
"Constructivism": "Constructivism",
|
||||
"Cubism": "Cubism",
|
||||
"Dadaism": "Dadaism",
|
||||
"Dark Fantasy": "Dark Fantasy",
|
||||
"Dark Moody Atmosphere": "Dark Moody Atmosphere",
|
||||
"Dmt Art Style": "Dmt Art Style",
|
||||
"Doodle Art": "Doodle Art",
|
||||
"Double Exposure": "Double Exposure",
|
||||
"Dripping Paint Splatter Art": "Dripping Paint Splatter Art",
|
||||
"Expressionism": "Expressionism",
|
||||
"Faded Polaroid Photo": "Faded Polaroid Photo",
|
||||
"Fauvism": "Fauvism",
|
||||
"Flat 2d Art": "Flat 2d Art",
|
||||
"Fortnite Art Style": "Fortnite Art Style",
|
||||
"Futurism": "Futurism",
|
||||
"Glitchcore": "Glitchcore",
|
||||
"Glo Fi": "Glo Fi",
|
||||
"Googie Art Style": "Googie Art Style",
|
||||
"Graffiti Art": "Graffiti Art",
|
||||
"Harlem Renaissance Art": "Harlem Renaissance Art",
|
||||
"High Fashion": "High Fashion",
|
||||
"Idyllic": "Idyllic",
|
||||
"Impressionism": "Impressionism",
|
||||
"Infographic Drawing": "Infographic Drawing",
|
||||
"Ink Dripping Drawing": "Ink Dripping Drawing",
|
||||
"Japanese Ink Drawing": "Japanese Ink Drawing",
|
||||
"Knolling Photography": "Knolling Photography",
|
||||
"Light Cheery Atmosphere": "Light Cheery Atmosphere",
|
||||
"Logo Design": "Logo Design",
|
||||
"Luxurious Elegance": "Luxurious Elegance",
|
||||
"Macro Photography": "Macro Photography",
|
||||
"Mandola Art": "Mandola Art",
|
||||
"Marker Drawing": "Marker Drawing",
|
||||
"Medievalism": "Medievalism",
|
||||
"Minimalism": "Minimalism",
|
||||
"Neo Baroque": "Neo Baroque",
|
||||
"Neo Byzantine": "Neo Byzantine",
|
||||
"Neo Futurism": "Neo Futurism",
|
||||
"Neo Impressionism": "Neo Impressionism",
|
||||
"Neo Rococo": "Neo Rococo",
|
||||
"Neoclassicism": "Neoclassicism",
|
||||
"Op Art": "Op Art",
|
||||
"Ornate And Intricate": "Ornate And Intricate",
|
||||
"Pencil Sketch Drawing": "Pencil Sketch Drawing",
|
||||
"Pop Art 2": "Pop Art 2",
|
||||
"Rococo": "Rococo",
|
||||
"Silhouette Art": "Silhouette Art",
|
||||
"Simple Vector Art": "Simple Vector Art",
|
||||
"Sketchup": "Sketchup",
|
||||
"Steampunk 2": "Steampunk 2",
|
||||
"Surrealism": "Surrealism",
|
||||
"Suprematism": "Suprematism",
|
||||
"Terragen": "Terragen",
|
||||
"Tranquil Relaxing Atmosphere": "Tranquil Relaxing Atmosphere",
|
||||
"Sticker Designs": "Sticker Designs",
|
||||
"Vibrant Rim Light": "Vibrant Rim Light",
|
||||
"Volumetric Lighting": "Volumetric Lighting",
|
||||
"Watercolor 2": "Watercolor 2",
|
||||
"Whimsical And Playful": "Whimsical And Playful",
|
||||
"Models": "Models",
|
||||
"Base Model (SDXL only)": "Base Model (SDXL only)",
|
||||
"sd_xl_base_1.0_0.9vae.safetensors": "sd_xl_base_1.0_0.9vae.safetensors",
|
||||
"bluePencilXL_v009.safetensors": "bluePencilXL_v009.safetensors",
|
||||
"bluePencilXL_v050.safetensors": "bluePencilXL_v050.safetensors",
|
||||
"DreamShaper_8_pruned.safetensors": "DreamShaper_8_pruned.safetensors",
|
||||
"realisticStockPhoto_v10.safetensors": "realisticStockPhoto_v10.safetensors",
|
||||
"realisticVisionV51_v51VAE.safetensors": "realisticVisionV51_v51VAE.safetensors",
|
||||
"sd_xl_refiner_1.0_0.9vae.safetensors": "sd_xl_refiner_1.0_0.9vae.safetensors",
|
||||
"Refiner (SDXL or SD 1.5)": "Refiner (SDXL or SD 1.5)",
|
||||
"None": "None",
|
||||
"LoRAs": "LoRAs",
|
||||
"SDXL LoRA 1": "SDXL LoRA 1",
|
||||
"sd_xl_offset_example-lora_1.0.safetensors": "sd_xl_offset_example-lora_1.0.safetensors",
|
||||
"3d_render_style_xl.safetensors": "3d_render_style_xl.safetensors",
|
||||
"Bloodstained-XL-V1.safetensors": "Bloodstained-XL-V1.safetensors",
|
||||
"SDXL_FILM_PHOTOGRAPHY_STYLE_BetaV0.4.safetensors": "SDXL_FILM_PHOTOGRAPHY_STYLE_BetaV0.4.safetensors",
|
||||
"SDXL LoRA 2": "SDXL LoRA 2",
|
||||
"SDXL LoRA 3": "SDXL LoRA 3",
|
||||
"SDXL LoRA 4": "SDXL LoRA 4",
|
||||
"SDXL LoRA 5": "SDXL LoRA 5",
|
||||
"Refresh": "Refresh",
|
||||
"\ud83d\udd04 Refresh All Files": "\ud83d\udd04 Refresh All Files",
|
||||
"Sampling Sharpness": "Sampling Sharpness",
|
||||
"Higher value means image and texture are sharper.": "Higher value means image and texture are sharper.",
|
||||
"Guidance Scale": "Guidance Scale",
|
||||
"Higher value means style is cleaner, vivider, and more artistic.": "Higher value means style is cleaner, vivider, and more artistic.",
|
||||
"Developer Debug Mode": "Developer Debug Mode",
|
||||
"Developer Debug Tools": "Developer Debug Tools",
|
||||
"Positive ADM Guidance Scaler": "Positive ADM Guidance Scaler",
|
||||
"The scaler multiplied to positive ADM (use 1.0 to disable). ": "The scaler multiplied to positive ADM (use 1.0 to disable). ",
|
||||
"Negative ADM Guidance Scaler": "Negative ADM Guidance Scaler",
|
||||
"The scaler multiplied to negative ADM (use 1.0 to disable). ": "The scaler multiplied to negative ADM (use 1.0 to disable). ",
|
||||
"ADM Guidance End At Step": "ADM Guidance End At Step",
|
||||
"When to end the guidance from positive/negative ADM. ": "When to end the guidance from positive/negative ADM. ",
|
||||
"Refiner swap method": "Refiner swap method",
|
||||
"joint": "joint",
|
||||
"separate": "separate",
|
||||
"vae": "vae",
|
||||
"CFG Mimicking from TSNR": "CFG Mimicking from TSNR",
|
||||
"Enabling Fooocus's implementation of CFG mimicking for TSNR (effective when real CFG > mimicked CFG).": "Enabling Fooocus's implementation of CFG mimicking for TSNR (effective when real CFG > mimicked CFG).",
|
||||
"CLIP Skip": "CLIP Skip",
|
||||
"Bypass CLIP layers to avoid overfitting (use 1 to not skip any layers, 2 is recommended).": "Bypass CLIP layers to avoid overfitting (use 1 to not skip any layers, 2 is recommended).",
|
||||
"Sampler": "Sampler",
|
||||
"dpmpp_2m_sde_gpu": "dpmpp_2m_sde_gpu",
|
||||
"Only effective in non-inpaint mode.": "Only effective in non-inpaint mode.",
|
||||
"euler": "euler",
|
||||
"euler_ancestral": "euler_ancestral",
|
||||
"heun": "heun",
|
||||
"dpm_2": "dpm_2",
|
||||
"dpm_2_ancestral": "dpm_2_ancestral",
|
||||
"lms": "lms",
|
||||
"dpm_fast": "dpm_fast",
|
||||
"dpm_adaptive": "dpm_adaptive",
|
||||
"dpmpp_2s_ancestral": "dpmpp_2s_ancestral",
|
||||
"dpmpp_sde": "dpmpp_sde",
|
||||
"dpmpp_sde_gpu": "dpmpp_sde_gpu",
|
||||
"dpmpp_2m": "dpmpp_2m",
|
||||
"dpmpp_2m_sde": "dpmpp_2m_sde",
|
||||
"dpmpp_3m_sde": "dpmpp_3m_sde",
|
||||
"dpmpp_3m_sde_gpu": "dpmpp_3m_sde_gpu",
|
||||
"ddpm": "ddpm",
|
||||
"ddim": "ddim",
|
||||
"uni_pc": "uni_pc",
|
||||
"uni_pc_bh2": "uni_pc_bh2",
|
||||
"Scheduler": "Scheduler",
|
||||
"karras": "karras",
|
||||
"Scheduler of Sampler.": "Scheduler of Sampler.",
|
||||
"normal": "normal",
|
||||
"exponential": "exponential",
|
||||
"sgm_uniform": "sgm_uniform",
|
||||
"simple": "simple",
|
||||
"ddim_uniform": "ddim_uniform",
|
||||
"VAE": "VAE",
|
||||
"Default (model)": "Default (model)",
|
||||
"Forced Overwrite of Sampling Step": "Forced Overwrite of Sampling Step",
|
||||
"Set as -1 to disable. For developer debugging.": "Set as -1 to disable. For developer debugging.",
|
||||
"Forced Overwrite of Refiner Switch Step": "Forced Overwrite of Refiner Switch Step",
|
||||
"Forced Overwrite of Generating Width": "Forced Overwrite of Generating Width",
|
||||
"Set as -1 to disable. For developer debugging. Results will be worse for non-standard numbers that SDXL is not trained on.": "Set as -1 to disable. For developer debugging. Results will be worse for non-standard numbers that SDXL is not trained on.",
|
||||
"Forced Overwrite of Generating Height": "Forced Overwrite of Generating Height",
|
||||
"Forced Overwrite of Denoising Strength of \"Vary\"": "Forced Overwrite of Denoising Strength of \"Vary\"",
|
||||
"Set as negative number to disable. For developer debugging.": "Set as negative number to disable. For developer debugging.",
|
||||
"Forced Overwrite of Denoising Strength of \"Upscale\"": "Forced Overwrite of Denoising Strength of \"Upscale\"",
|
||||
"Disable Preview": "Disable Preview",
|
||||
"Disable preview during generation.": "Disable preview during generation.",
|
||||
"Disable Intermediate Results": "Disable Intermediate Results",
|
||||
"Disable intermediate results during generation, only show final gallery.": "Disable intermediate results during generation, only show final gallery.",
|
||||
"Debug Inpaint Preprocessing": "Debug Inpaint Preprocessing",
|
||||
"Debug GroundingDINO": "Debug GroundingDINO",
|
||||
"Used for SAM object detection and box generation": "Used for SAM object detection and box generation",
|
||||
"GroundingDINO Box Erode or Dilate": "GroundingDINO Box Erode or Dilate",
|
||||
"Inpaint Engine": "Inpaint Engine",
|
||||
"v1": "v1",
|
||||
"v2.5": "v2.5",
|
||||
"v2.6": "v2.6",
|
||||
"Control Debug": "Control Debug",
|
||||
"Debug Preprocessors": "Debug Preprocessors",
|
||||
"Mixing Image Prompt and Vary/Upscale": "Mixing Image Prompt and Vary/Upscale",
|
||||
"Mixing Image Prompt and Inpaint": "Mixing Image Prompt and Inpaint",
|
||||
"Softness of ControlNet": "Softness of ControlNet",
|
||||
"Similar to the Control Mode in A1111 (use 0.0 to disable). ": "Similar to the Control Mode in A1111 (use 0.0 to disable). ",
|
||||
"Canny": "Canny",
|
||||
"Canny Low Threshold": "Canny Low Threshold",
|
||||
"Canny High Threshold": "Canny High Threshold",
|
||||
"FreeU": "FreeU",
|
||||
"Enabled": "Enabled",
|
||||
"B1": "B1",
|
||||
"B2": "B2",
|
||||
"S1": "S1",
|
||||
"S2": "S2",
|
||||
"\uD83D\uDD0E Type here to search styles ...": "\uD83D\uDD0E Type here to search styles ...",
|
||||
"Type prompt here.": "Type prompt here.",
|
||||
"Outpaint Expansion Direction:": "Outpaint Expansion Direction:",
|
||||
"* Powered by Fooocus Inpaint Engine (beta)": "* Powered by Fooocus Inpaint Engine (beta)",
|
||||
"Fooocus Enhance": "Fooocus Enhance",
|
||||
"Fooocus Cinematic": "Fooocus Cinematic",
|
||||
"Fooocus Sharp": "Fooocus Sharp",
|
||||
"For images created by Fooocus": "For images created by Fooocus",
|
||||
"Metadata": "Metadata",
|
||||
"Apply Metadata": "Apply Metadata",
|
||||
"Metadata Scheme": "Metadata Scheme",
|
||||
"Image Prompt parameters are not included. Use png and a1111 for compatibility with Civitai.": "Image Prompt parameters are not included. Use png and a1111 for compatibility with Civitai.",
|
||||
"fooocus (json)": "fooocus (json)",
|
||||
"a1111 (plain text)": "a1111 (plain text)",
|
||||
"Unsupported image type in input": "Unsupported image type in input",
|
||||
"Enhance": "Enhance",
|
||||
"Detection prompt": "Detection prompt",
|
||||
"Detection Prompt Quick List": "Detection Prompt Quick List",
|
||||
"Maximum number of detections": "Maximum number of detections",
|
||||
"Use with Enhance, skips image generation": "Use with Enhance, skips image generation",
|
||||
"Order of Processing": "Order of Processing",
|
||||
"Use before to enhance small details and after to enhance large areas.": "Use before to enhance small details and after to enhance large areas.",
|
||||
"Before First Enhancement": "Before First Enhancement",
|
||||
"After Last Enhancement": "After Last Enhancement",
|
||||
"Prompt Type": "Prompt Type",
|
||||
"Choose which prompt to use for Upscale or Variation.": "Choose which prompt to use for Upscale or Variation.",
|
||||
"Original Prompts": "Original Prompts",
|
||||
"Last Filled Enhancement Prompts": "Last Filled Enhancement Prompts",
|
||||
"Enable": "Enable",
|
||||
"Describe what you want to detect.": "Describe what you want to detect.",
|
||||
"Enhancement positive prompt": "Enhancement positive prompt",
|
||||
"Uses original prompt instead if empty.": "Uses original prompt instead if empty.",
|
||||
"Enhancement negative prompt": "Enhancement negative prompt",
|
||||
"Uses original negative prompt instead if empty.": "Uses original negative prompt instead if empty.",
|
||||
"Detection": "Detection",
|
||||
"u2net": "u2net",
|
||||
"u2netp": "u2netp",
|
||||
"u2net_human_seg": "u2net_human_seg",
|
||||
"u2net_cloth_seg": "u2net_cloth_seg",
|
||||
"silueta": "silueta",
|
||||
"isnet-general-use": "isnet-general-use",
|
||||
"isnet-anime": "isnet-anime",
|
||||
"sam": "sam",
|
||||
"Mask generation model": "Mask generation model",
|
||||
"Cloth category": "Cloth category",
|
||||
"Use singular whenever possible": "Use singular whenever possible",
|
||||
"full": "full",
|
||||
"upper": "upper",
|
||||
"lower": "lower",
|
||||
"SAM Options": "SAM Options",
|
||||
"SAM model": "SAM model",
|
||||
"vit_b": "vit_b",
|
||||
"vit_l": "vit_l",
|
||||
"vit_h": "vit_h",
|
||||
"Box Threshold": "Box Threshold",
|
||||
"Text Threshold": "Text Threshold",
|
||||
"Set to 0 to detect all": "Set to 0 to detect all",
|
||||
"Inpaint": "Inpaint",
|
||||
"Inpaint or Outpaint (default)": "Inpaint or Outpaint (default)",
|
||||
"Improve Detail (face, hand, eyes, etc.)": "Improve Detail (face, hand, eyes, etc.)",
|
||||
"Modify Content (add objects, change background, etc.)": "Modify Content (add objects, change background, etc.)",
|
||||
"Disable initial latent in inpaint": "Disable initial latent in inpaint",
|
||||
"Version of Fooocus inpaint model. If set, use performance Quality or Speed (no performance LoRAs) for best results.": "Version of Fooocus inpaint model. If set, use performance Quality or Speed (no performance LoRAs) for best results.",
|
||||
"Inpaint Denoising Strength": "Inpaint Denoising Strength",
|
||||
"Same as the denoising strength in A1111 inpaint. Only used in inpaint, not used in outpaint. (Outpaint always use 1.0)": "Same as the denoising strength in A1111 inpaint. Only used in inpaint, not used in outpaint. (Outpaint always use 1.0)",
|
||||
"Inpaint Respective Field": "Inpaint Respective Field",
|
||||
"The area to inpaint. Value 0 is same as \"Only Masked\" in A1111. Value 1 is same as \"Whole Image\" in A1111. Only used in inpaint, not used in outpaint. (Outpaint always use 1.0)": "The area to inpaint. Value 0 is same as \"Only Masked\" in A1111. Value 1 is same as \"Whole Image\" in A1111. Only used in inpaint, not used in outpaint. (Outpaint always use 1.0)",
|
||||
"Mask Erode or Dilate": "Mask Erode or Dilate",
|
||||
"Positive value will make white area in the mask larger, negative value will make white area smaller. (default is 0, always processed before any mask invert)": "Positive value will make white area in the mask larger, negative value will make white area smaller. (default is 0, always processed before any mask invert)",
|
||||
"Invert Mask When Generating": "Invert Mask When Generating",
|
||||
"Debug Enhance Masks": "Debug Enhance Masks",
|
||||
"Show enhance masks in preview and final results": "Show enhance masks in preview and final results",
|
||||
"Use GroundingDINO boxes instead of more detailed SAM masks": "Use GroundingDINO boxes instead of more detailed SAM masks",
|
||||
"highly detailed face": "highly detailed face",
|
||||
"detailed girl face": "detailed girl face",
|
||||
"detailed man face": "detailed man face",
|
||||
"detailed hand": "detailed hand",
|
||||
"beautiful eyes": "beautiful eyes",
|
||||
"face": "face",
|
||||
"eye": "eye",
|
||||
"mouth": "mouth",
|
||||
"hair": "hair",
|
||||
"hand": "hand",
|
||||
"body": "body"
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"Generate": "生成",
|
||||
"Input Image": "入力画像",
|
||||
"Advanced": "고급",
|
||||
"SAI 3D Model": "SAI 3D Modèle"
|
||||
}
|
||||
@@ -1,49 +1,57 @@
|
||||
import os
|
||||
import ssl
|
||||
import sys
|
||||
|
||||
print('[System ARGV] ' + str(sys.argv))
|
||||
|
||||
root = os.path.dirname(os.path.abspath(__file__))
|
||||
sys.path.append(root)
|
||||
os.chdir(root)
|
||||
|
||||
os.environ["PYTORCH_ENABLE_MPS_FALLBACK"] = "1"
|
||||
os.environ["PYTORCH_MPS_HIGH_WATERMARK_RATIO"] = "0.0"
|
||||
if "GRADIO_SERVER_PORT" not in os.environ:
|
||||
os.environ["GRADIO_SERVER_PORT"] = "7865"
|
||||
|
||||
ssl._create_default_https_context = ssl._create_unverified_context
|
||||
|
||||
import platform
|
||||
import fooocus_version
|
||||
|
||||
from modules.launch_util import is_installed, run, python, \
|
||||
run_pip, repo_dir, git_clone, requirements_met, script_path, dir_repos
|
||||
from build_launcher import build_launcher
|
||||
from modules.launch_util import is_installed, run, python, run_pip, requirements_met, delete_folder_content
|
||||
from modules.model_loader import load_file_from_url
|
||||
from modules.path import modelfile_path, lorafile_path
|
||||
|
||||
REINSTALL_ALL = False
|
||||
TRY_INSTALL_XFORMERS = False
|
||||
|
||||
|
||||
def prepare_environment():
|
||||
torch_index_url = os.environ.get('TORCH_INDEX_URL', "https://download.pytorch.org/whl/cu118")
|
||||
torch_index_url = os.environ.get('TORCH_INDEX_URL', "https://download.pytorch.org/whl/cu121")
|
||||
torch_command = os.environ.get('TORCH_COMMAND',
|
||||
f"pip install torch==2.0.1 torchvision==0.15.2 --extra-index-url {torch_index_url}")
|
||||
f"pip install torch==2.1.0 torchvision==0.16.0 --extra-index-url {torch_index_url}")
|
||||
requirements_file = os.environ.get('REQS_FILE', "requirements_versions.txt")
|
||||
|
||||
xformers_package = os.environ.get('XFORMERS_PACKAGE', 'xformers==0.0.20')
|
||||
|
||||
comfy_repo = os.environ.get('COMFY_REPO', "https://github.com/comfyanonymous/ComfyUI")
|
||||
comfy_commit_hash = os.environ.get('COMFY_COMMIT_HASH', "2bc12d3d22efb5c63ae3a7fc342bb2dd16b31735")
|
||||
|
||||
print(f"Python {sys.version}")
|
||||
print(f"Fooocus version: {fooocus_version.version}")
|
||||
|
||||
comfyui_name = 'ComfyUI-from-StabilityAI-Official'
|
||||
git_clone(comfy_repo, repo_dir(comfyui_name), "Inference Engine", comfy_commit_hash)
|
||||
sys.path.append(os.path.join(script_path, dir_repos, comfyui_name))
|
||||
|
||||
if REINSTALL_ALL or not is_installed("torch") or not is_installed("torchvision"):
|
||||
run(f'"{python}" -m {torch_command}', "Installing torch and torchvision", "Couldn't install torch", live=True)
|
||||
|
||||
if REINSTALL_ALL or not is_installed("xformers"):
|
||||
if platform.system() == "Windows":
|
||||
if platform.python_version().startswith("3.10"):
|
||||
run_pip(f"install -U -I --no-deps {xformers_package}", "xformers", live=True)
|
||||
else:
|
||||
print("Installation of xformers is not supported in this version of Python.")
|
||||
print(
|
||||
"You can also check this and build manually: https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Xformers#building-xformers-on-windows-by-duckness")
|
||||
if not is_installed("xformers"):
|
||||
exit(0)
|
||||
elif platform.system() == "Linux":
|
||||
run_pip(f"install -U -I --no-deps {xformers_package}", "xformers")
|
||||
if TRY_INSTALL_XFORMERS:
|
||||
if REINSTALL_ALL or not is_installed("xformers"):
|
||||
xformers_package = os.environ.get('XFORMERS_PACKAGE', 'xformers==0.0.23')
|
||||
if platform.system() == "Windows":
|
||||
if platform.python_version().startswith("3.10"):
|
||||
run_pip(f"install -U -I --no-deps {xformers_package}", "xformers", live=True)
|
||||
else:
|
||||
print("Installation of xformers is not supported in this version of Python.")
|
||||
print(
|
||||
"You can also check this and build manually: https://github.com/AUTOMATIC1111/stable-diffusion-webui/wiki/Xformers#building-xformers-on-windows-by-duckness")
|
||||
if not is_installed("xformers"):
|
||||
exit(0)
|
||||
elif platform.system() == "Linux":
|
||||
run_pip(f"install -U -I --no-deps {xformers_package}", "xformers")
|
||||
|
||||
if REINSTALL_ALL or not requirements_met(requirements_file):
|
||||
run_pip(f"install -r \"{requirements_file}\"", "requirements")
|
||||
@@ -51,38 +59,94 @@ def prepare_environment():
|
||||
return
|
||||
|
||||
|
||||
model_filenames = [
|
||||
('sd_xl_base_1.0_0.9vae.safetensors',
|
||||
'https://huggingface.co/stabilityai/stable-diffusion-xl-base-1.0/resolve/main/sd_xl_base_1.0_0.9vae.safetensors'),
|
||||
('sd_xl_refiner_1.0_0.9vae.safetensors',
|
||||
'https://huggingface.co/stabilityai/stable-diffusion-xl-refiner-1.0/resolve/main/sd_xl_refiner_1.0_0.9vae.safetensors')
|
||||
]
|
||||
|
||||
lora_filenames = [
|
||||
('sd_xl_offset_example-lora_1.0.safetensors',
|
||||
'https://huggingface.co/stabilityai/stable-diffusion-xl-base-1.0/resolve/main/sd_xl_offset_example-lora_1.0.safetensors')
|
||||
vae_approx_filenames = [
|
||||
('xlvaeapp.pth', 'https://huggingface.co/lllyasviel/misc/resolve/main/xlvaeapp.pth'),
|
||||
('vaeapp_sd15.pth', 'https://huggingface.co/lllyasviel/misc/resolve/main/vaeapp_sd15.pt'),
|
||||
('xl-to-v1_interposer-v4.0.safetensors',
|
||||
'https://huggingface.co/mashb1t/misc/resolve/main/xl-to-v1_interposer-v4.0.safetensors')
|
||||
]
|
||||
|
||||
|
||||
def download_models():
|
||||
for file_name, url in model_filenames:
|
||||
load_file_from_url(url=url, model_dir=modelfile_path, file_name=file_name)
|
||||
for file_name, url in lora_filenames:
|
||||
load_file_from_url(url=url, model_dir=lorafile_path, file_name=file_name)
|
||||
return
|
||||
|
||||
|
||||
def cuda_malloc():
|
||||
argv = sys.argv
|
||||
sys.argv = [sys.argv[0]]
|
||||
import cuda_malloc
|
||||
sys.argv = argv
|
||||
def ini_args():
|
||||
from args_manager import args
|
||||
return args
|
||||
|
||||
|
||||
prepare_environment()
|
||||
build_launcher()
|
||||
args = ini_args()
|
||||
|
||||
cuda_malloc()
|
||||
if args.gpu_device_id is not None:
|
||||
os.environ['CUDA_VISIBLE_DEVICES'] = str(args.gpu_device_id)
|
||||
print("Set device to:", args.gpu_device_id)
|
||||
|
||||
download_models()
|
||||
if args.hf_mirror is not None:
|
||||
os.environ['HF_MIRROR'] = str(args.hf_mirror)
|
||||
print("Set hf_mirror to:", args.hf_mirror)
|
||||
|
||||
from modules import config
|
||||
from modules.hash_cache import init_cache
|
||||
|
||||
os.environ["U2NET_HOME"] = config.path_inpaint
|
||||
|
||||
os.environ['GRADIO_TEMP_DIR'] = config.temp_path
|
||||
|
||||
if config.temp_path_cleanup_on_launch:
|
||||
print(f'[Cleanup] Attempting to delete content of temp dir {config.temp_path}')
|
||||
result = delete_folder_content(config.temp_path, '[Cleanup] ')
|
||||
if result:
|
||||
print("[Cleanup] Cleanup successful")
|
||||
else:
|
||||
print(f"[Cleanup] Failed to delete content of temp dir.")
|
||||
|
||||
|
||||
def download_models(default_model, previous_default_models, checkpoint_downloads, embeddings_downloads, lora_downloads, vae_downloads):
|
||||
from modules.util import get_file_from_folder_list
|
||||
|
||||
for file_name, url in vae_approx_filenames:
|
||||
load_file_from_url(url=url, model_dir=config.path_vae_approx, file_name=file_name)
|
||||
|
||||
load_file_from_url(
|
||||
url='https://huggingface.co/lllyasviel/misc/resolve/main/fooocus_expansion.bin',
|
||||
model_dir=config.path_fooocus_expansion,
|
||||
file_name='pytorch_model.bin'
|
||||
)
|
||||
|
||||
if args.disable_preset_download:
|
||||
print('Skipped model download.')
|
||||
return default_model, checkpoint_downloads
|
||||
|
||||
if not args.always_download_new_model:
|
||||
if not os.path.isfile(get_file_from_folder_list(default_model, config.paths_checkpoints)):
|
||||
for alternative_model_name in previous_default_models:
|
||||
if os.path.isfile(get_file_from_folder_list(alternative_model_name, config.paths_checkpoints)):
|
||||
print(f'You do not have [{default_model}] but you have [{alternative_model_name}].')
|
||||
print(f'Fooocus will use [{alternative_model_name}] to avoid downloading new models, '
|
||||
f'but you are not using the latest models.')
|
||||
print('Use --always-download-new-model to avoid fallback and always get new models.')
|
||||
checkpoint_downloads = {}
|
||||
default_model = alternative_model_name
|
||||
break
|
||||
|
||||
for file_name, url in checkpoint_downloads.items():
|
||||
model_dir = os.path.dirname(get_file_from_folder_list(file_name, config.paths_checkpoints))
|
||||
load_file_from_url(url=url, model_dir=model_dir, file_name=file_name)
|
||||
for file_name, url in embeddings_downloads.items():
|
||||
load_file_from_url(url=url, model_dir=config.path_embeddings, file_name=file_name)
|
||||
for file_name, url in lora_downloads.items():
|
||||
model_dir = os.path.dirname(get_file_from_folder_list(file_name, config.paths_loras))
|
||||
load_file_from_url(url=url, model_dir=model_dir, file_name=file_name)
|
||||
for file_name, url in vae_downloads.items():
|
||||
load_file_from_url(url=url, model_dir=config.path_vae, file_name=file_name)
|
||||
|
||||
return default_model, checkpoint_downloads
|
||||
|
||||
|
||||
config.default_base_model_name, config.checkpoint_downloads = download_models(
|
||||
config.default_base_model_name, config.previous_default_models, config.checkpoint_downloads,
|
||||
config.embeddings_downloads, config.lora_downloads, config.vae_downloads)
|
||||
|
||||
config.update_files()
|
||||
init_cache(config.model_filenames, config.paths_checkpoints, config.lora_filenames, config.paths_loras)
|
||||
|
||||
from webui import *
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,55 @@
|
||||
# https://github.com/comfyanonymous/ComfyUI/blob/master/nodes.py
|
||||
|
||||
#from: https://research.nvidia.com/labs/toronto-ai/AlignYourSteps/howto.html
|
||||
import numpy as np
|
||||
import torch
|
||||
|
||||
def loglinear_interp(t_steps, num_steps):
|
||||
"""
|
||||
Performs log-linear interpolation of a given array of decreasing numbers.
|
||||
"""
|
||||
xs = np.linspace(0, 1, len(t_steps))
|
||||
ys = np.log(t_steps[::-1])
|
||||
|
||||
new_xs = np.linspace(0, 1, num_steps)
|
||||
new_ys = np.interp(new_xs, xs, ys)
|
||||
|
||||
interped_ys = np.exp(new_ys)[::-1].copy()
|
||||
return interped_ys
|
||||
|
||||
NOISE_LEVELS = {"SD1": [14.6146412293, 6.4745760956, 3.8636745985, 2.6946151520, 1.8841921177, 1.3943805092, 0.9642583904, 0.6523686016, 0.3977456272, 0.1515232662, 0.0291671582],
|
||||
"SDXL":[14.6146412293, 6.3184485287, 3.7681790315, 2.1811480769, 1.3405244945, 0.8620721141, 0.5550693289, 0.3798540708, 0.2332364134, 0.1114188177, 0.0291671582],
|
||||
"SVD": [700.00, 54.5, 15.886, 7.977, 4.248, 1.789, 0.981, 0.403, 0.173, 0.034, 0.002]}
|
||||
|
||||
class AlignYourStepsScheduler:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required":
|
||||
{"model_type": (["SD1", "SDXL", "SVD"], ),
|
||||
"steps": ("INT", {"default": 10, "min": 10, "max": 10000}),
|
||||
"denoise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}),
|
||||
}
|
||||
}
|
||||
RETURN_TYPES = ("SIGMAS",)
|
||||
CATEGORY = "sampling/custom_sampling/schedulers"
|
||||
|
||||
FUNCTION = "get_sigmas"
|
||||
|
||||
def get_sigmas(self, model_type, steps, denoise):
|
||||
total_steps = steps
|
||||
if denoise < 1.0:
|
||||
if denoise <= 0.0:
|
||||
return (torch.FloatTensor([]),)
|
||||
total_steps = round(steps * denoise)
|
||||
|
||||
sigmas = NOISE_LEVELS[model_type][:]
|
||||
if (steps + 1) != len(sigmas):
|
||||
sigmas = loglinear_interp(sigmas, steps + 1)
|
||||
|
||||
sigmas = sigmas[-(total_steps + 1):]
|
||||
sigmas[-1] = 0
|
||||
return (torch.FloatTensor(sigmas), )
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"AlignYourStepsScheduler": AlignYourStepsScheduler,
|
||||
}
|
||||
@@ -0,0 +1,301 @@
|
||||
# https://github.com/comfyanonymous/ComfyUI/blob/master/nodes.py
|
||||
|
||||
#From https://github.com/kornia/kornia
|
||||
import math
|
||||
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
import ldm_patched.modules.model_management
|
||||
|
||||
def get_canny_nms_kernel(device=None, dtype=None):
|
||||
"""Utility function that returns 3x3 kernels for the Canny Non-maximal suppression."""
|
||||
return torch.tensor(
|
||||
[
|
||||
[[[0.0, 0.0, 0.0], [0.0, 1.0, -1.0], [0.0, 0.0, 0.0]]],
|
||||
[[[0.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, -1.0]]],
|
||||
[[[0.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, -1.0, 0.0]]],
|
||||
[[[0.0, 0.0, 0.0], [0.0, 1.0, 0.0], [-1.0, 0.0, 0.0]]],
|
||||
[[[0.0, 0.0, 0.0], [-1.0, 1.0, 0.0], [0.0, 0.0, 0.0]]],
|
||||
[[[-1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 0.0]]],
|
||||
[[[0.0, -1.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 0.0]]],
|
||||
[[[0.0, 0.0, -1.0], [0.0, 1.0, 0.0], [0.0, 0.0, 0.0]]],
|
||||
],
|
||||
device=device,
|
||||
dtype=dtype,
|
||||
)
|
||||
|
||||
|
||||
def get_hysteresis_kernel(device=None, dtype=None):
|
||||
"""Utility function that returns the 3x3 kernels for the Canny hysteresis."""
|
||||
return torch.tensor(
|
||||
[
|
||||
[[[0.0, 0.0, 0.0], [0.0, 0.0, 1.0], [0.0, 0.0, 0.0]]],
|
||||
[[[0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 1.0]]],
|
||||
[[[0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 1.0, 0.0]]],
|
||||
[[[0.0, 0.0, 0.0], [0.0, 0.0, 0.0], [1.0, 0.0, 0.0]]],
|
||||
[[[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 0.0, 0.0]]],
|
||||
[[[1.0, 0.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0]]],
|
||||
[[[0.0, 1.0, 0.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0]]],
|
||||
[[[0.0, 0.0, 1.0], [0.0, 0.0, 0.0], [0.0, 0.0, 0.0]]],
|
||||
],
|
||||
device=device,
|
||||
dtype=dtype,
|
||||
)
|
||||
|
||||
def gaussian_blur_2d(img, kernel_size, sigma):
|
||||
ksize_half = (kernel_size - 1) * 0.5
|
||||
|
||||
x = torch.linspace(-ksize_half, ksize_half, steps=kernel_size)
|
||||
|
||||
pdf = torch.exp(-0.5 * (x / sigma).pow(2))
|
||||
|
||||
x_kernel = pdf / pdf.sum()
|
||||
x_kernel = x_kernel.to(device=img.device, dtype=img.dtype)
|
||||
|
||||
kernel2d = torch.mm(x_kernel[:, None], x_kernel[None, :])
|
||||
kernel2d = kernel2d.expand(img.shape[-3], 1, kernel2d.shape[0], kernel2d.shape[1])
|
||||
|
||||
padding = [kernel_size // 2, kernel_size // 2, kernel_size // 2, kernel_size // 2]
|
||||
|
||||
img = torch.nn.functional.pad(img, padding, mode="reflect")
|
||||
img = torch.nn.functional.conv2d(img, kernel2d, groups=img.shape[-3])
|
||||
|
||||
return img
|
||||
|
||||
def get_sobel_kernel2d(device=None, dtype=None):
|
||||
kernel_x = torch.tensor([[-1.0, 0.0, 1.0], [-2.0, 0.0, 2.0], [-1.0, 0.0, 1.0]], device=device, dtype=dtype)
|
||||
kernel_y = kernel_x.transpose(0, 1)
|
||||
return torch.stack([kernel_x, kernel_y])
|
||||
|
||||
def spatial_gradient(input, normalized: bool = True):
|
||||
r"""Compute the first order image derivative in both x and y using a Sobel operator.
|
||||
.. image:: _static/img/spatial_gradient.png
|
||||
Args:
|
||||
input: input image tensor with shape :math:`(B, C, H, W)`.
|
||||
mode: derivatives modality, can be: `sobel` or `diff`.
|
||||
order: the order of the derivatives.
|
||||
normalized: whether the output is normalized.
|
||||
Return:
|
||||
the derivatives of the input feature map. with shape :math:`(B, C, 2, H, W)`.
|
||||
.. note::
|
||||
See a working example `here <https://kornia.readthedocs.io/en/latest/
|
||||
filtering_edges.html>`__.
|
||||
Examples:
|
||||
>>> input = torch.rand(1, 3, 4, 4)
|
||||
>>> output = spatial_gradient(input) # 1x3x2x4x4
|
||||
>>> output.shape
|
||||
torch.Size([1, 3, 2, 4, 4])
|
||||
"""
|
||||
# KORNIA_CHECK_IS_TENSOR(input)
|
||||
# KORNIA_CHECK_SHAPE(input, ['B', 'C', 'H', 'W'])
|
||||
|
||||
# allocate kernel
|
||||
kernel = get_sobel_kernel2d(device=input.device, dtype=input.dtype)
|
||||
if normalized:
|
||||
kernel = normalize_kernel2d(kernel)
|
||||
|
||||
# prepare kernel
|
||||
b, c, h, w = input.shape
|
||||
tmp_kernel = kernel[:, None, ...]
|
||||
|
||||
# Pad with "replicate for spatial dims, but with zeros for channel
|
||||
spatial_pad = [kernel.size(1) // 2, kernel.size(1) // 2, kernel.size(2) // 2, kernel.size(2) // 2]
|
||||
out_channels: int = 2
|
||||
padded_inp = torch.nn.functional.pad(input.reshape(b * c, 1, h, w), spatial_pad, 'replicate')
|
||||
out = F.conv2d(padded_inp, tmp_kernel, groups=1, padding=0, stride=1)
|
||||
return out.reshape(b, c, out_channels, h, w)
|
||||
|
||||
def rgb_to_grayscale(image, rgb_weights = None):
|
||||
r"""Convert a RGB image to grayscale version of image.
|
||||
|
||||
.. image:: _static/img/rgb_to_grayscale.png
|
||||
|
||||
The image data is assumed to be in the range of (0, 1).
|
||||
|
||||
Args:
|
||||
image: RGB image to be converted to grayscale with shape :math:`(*,3,H,W)`.
|
||||
rgb_weights: Weights that will be applied on each channel (RGB).
|
||||
The sum of the weights should add up to one.
|
||||
Returns:
|
||||
grayscale version of the image with shape :math:`(*,1,H,W)`.
|
||||
|
||||
.. note::
|
||||
See a working example `here <https://kornia.readthedocs.io/en/latest/
|
||||
color_conversions.html>`__.
|
||||
|
||||
Example:
|
||||
>>> input = torch.rand(2, 3, 4, 5)
|
||||
>>> gray = rgb_to_grayscale(input) # 2x1x4x5
|
||||
"""
|
||||
|
||||
if len(image.shape) < 3 or image.shape[-3] != 3:
|
||||
raise ValueError(f"Input size must have a shape of (*, 3, H, W). Got {image.shape}")
|
||||
|
||||
if rgb_weights is None:
|
||||
# 8 bit images
|
||||
if image.dtype == torch.uint8:
|
||||
rgb_weights = torch.tensor([76, 150, 29], device=image.device, dtype=torch.uint8)
|
||||
# floating point images
|
||||
elif image.dtype in (torch.float16, torch.float32, torch.float64):
|
||||
rgb_weights = torch.tensor([0.299, 0.587, 0.114], device=image.device, dtype=image.dtype)
|
||||
else:
|
||||
raise TypeError(f"Unknown data type: {image.dtype}")
|
||||
else:
|
||||
# is tensor that we make sure is in the same device/dtype
|
||||
rgb_weights = rgb_weights.to(image)
|
||||
|
||||
# unpack the color image channels with RGB order
|
||||
r: Tensor = image[..., 0:1, :, :]
|
||||
g: Tensor = image[..., 1:2, :, :]
|
||||
b: Tensor = image[..., 2:3, :, :]
|
||||
|
||||
w_r, w_g, w_b = rgb_weights.unbind()
|
||||
return w_r * r + w_g * g + w_b * b
|
||||
|
||||
def canny(
|
||||
input,
|
||||
low_threshold = 0.1,
|
||||
high_threshold = 0.2,
|
||||
kernel_size = 5,
|
||||
sigma = 1,
|
||||
hysteresis = True,
|
||||
eps = 1e-6,
|
||||
):
|
||||
r"""Find edges of the input image and filters them using the Canny algorithm.
|
||||
.. image:: _static/img/canny.png
|
||||
Args:
|
||||
input: input image tensor with shape :math:`(B,C,H,W)`.
|
||||
low_threshold: lower threshold for the hysteresis procedure.
|
||||
high_threshold: upper threshold for the hysteresis procedure.
|
||||
kernel_size: the size of the kernel for the gaussian blur.
|
||||
sigma: the standard deviation of the kernel for the gaussian blur.
|
||||
hysteresis: if True, applies the hysteresis edge tracking.
|
||||
Otherwise, the edges are divided between weak (0.5) and strong (1) edges.
|
||||
eps: regularization number to avoid NaN during backprop.
|
||||
Returns:
|
||||
- the canny edge magnitudes map, shape of :math:`(B,1,H,W)`.
|
||||
- the canny edge detection filtered by thresholds and hysteresis, shape of :math:`(B,1,H,W)`.
|
||||
.. note::
|
||||
See a working example `here <https://kornia.readthedocs.io/en/latest/
|
||||
canny.html>`__.
|
||||
Example:
|
||||
>>> input = torch.rand(5, 3, 4, 4)
|
||||
>>> magnitude, edges = canny(input) # 5x3x4x4
|
||||
>>> magnitude.shape
|
||||
torch.Size([5, 1, 4, 4])
|
||||
>>> edges.shape
|
||||
torch.Size([5, 1, 4, 4])
|
||||
"""
|
||||
# KORNIA_CHECK_IS_TENSOR(input)
|
||||
# KORNIA_CHECK_SHAPE(input, ['B', 'C', 'H', 'W'])
|
||||
# KORNIA_CHECK(
|
||||
# low_threshold <= high_threshold,
|
||||
# "Invalid input thresholds. low_threshold should be smaller than the high_threshold. Got: "
|
||||
# f"{low_threshold}>{high_threshold}",
|
||||
# )
|
||||
# KORNIA_CHECK(0 < low_threshold < 1, f'Invalid low threshold. Should be in range (0, 1). Got: {low_threshold}')
|
||||
# KORNIA_CHECK(0 < high_threshold < 1, f'Invalid high threshold. Should be in range (0, 1). Got: {high_threshold}')
|
||||
|
||||
device = input.device
|
||||
dtype = input.dtype
|
||||
|
||||
# To Grayscale
|
||||
if input.shape[1] == 3:
|
||||
input = rgb_to_grayscale(input)
|
||||
|
||||
# Gaussian filter
|
||||
blurred: Tensor = gaussian_blur_2d(input, kernel_size, sigma)
|
||||
|
||||
# Compute the gradients
|
||||
gradients: Tensor = spatial_gradient(blurred, normalized=False)
|
||||
|
||||
# Unpack the edges
|
||||
gx: Tensor = gradients[:, :, 0]
|
||||
gy: Tensor = gradients[:, :, 1]
|
||||
|
||||
# Compute gradient magnitude and angle
|
||||
magnitude: Tensor = torch.sqrt(gx * gx + gy * gy + eps)
|
||||
angle: Tensor = torch.atan2(gy, gx)
|
||||
|
||||
# Radians to Degrees
|
||||
angle = 180.0 * angle / math.pi
|
||||
|
||||
# Round angle to the nearest 45 degree
|
||||
angle = torch.round(angle / 45) * 45
|
||||
|
||||
# Non-maximal suppression
|
||||
nms_kernels: Tensor = get_canny_nms_kernel(device, dtype)
|
||||
nms_magnitude: Tensor = F.conv2d(magnitude, nms_kernels, padding=nms_kernels.shape[-1] // 2)
|
||||
|
||||
# Get the indices for both directions
|
||||
positive_idx: Tensor = (angle / 45) % 8
|
||||
positive_idx = positive_idx.long()
|
||||
|
||||
negative_idx: Tensor = ((angle / 45) + 4) % 8
|
||||
negative_idx = negative_idx.long()
|
||||
|
||||
# Apply the non-maximum suppression to the different directions
|
||||
channel_select_filtered_positive: Tensor = torch.gather(nms_magnitude, 1, positive_idx)
|
||||
channel_select_filtered_negative: Tensor = torch.gather(nms_magnitude, 1, negative_idx)
|
||||
|
||||
channel_select_filtered: Tensor = torch.stack(
|
||||
[channel_select_filtered_positive, channel_select_filtered_negative], 1
|
||||
)
|
||||
|
||||
is_max: Tensor = channel_select_filtered.min(dim=1)[0] > 0.0
|
||||
|
||||
magnitude = magnitude * is_max
|
||||
|
||||
# Threshold
|
||||
edges: Tensor = F.threshold(magnitude, low_threshold, 0.0)
|
||||
|
||||
low: Tensor = magnitude > low_threshold
|
||||
high: Tensor = magnitude > high_threshold
|
||||
|
||||
edges = low * 0.5 + high * 0.5
|
||||
edges = edges.to(dtype)
|
||||
|
||||
# Hysteresis
|
||||
if hysteresis:
|
||||
edges_old: Tensor = -torch.ones(edges.shape, device=edges.device, dtype=dtype)
|
||||
hysteresis_kernels: Tensor = get_hysteresis_kernel(device, dtype)
|
||||
|
||||
while ((edges_old - edges).abs() != 0).any():
|
||||
weak: Tensor = (edges == 0.5).float()
|
||||
strong: Tensor = (edges == 1).float()
|
||||
|
||||
hysteresis_magnitude: Tensor = F.conv2d(
|
||||
edges, hysteresis_kernels, padding=hysteresis_kernels.shape[-1] // 2
|
||||
)
|
||||
hysteresis_magnitude = (hysteresis_magnitude == 1).any(1, keepdim=True).to(dtype)
|
||||
hysteresis_magnitude = hysteresis_magnitude * weak + strong
|
||||
|
||||
edges_old = edges.clone()
|
||||
edges = hysteresis_magnitude + (hysteresis_magnitude == 0) * weak * 0.5
|
||||
|
||||
edges = hysteresis_magnitude
|
||||
|
||||
return magnitude, edges
|
||||
|
||||
|
||||
class Canny:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {"image": ("IMAGE",),
|
||||
"low_threshold": ("FLOAT", {"default": 0.4, "min": 0.01, "max": 0.99, "step": 0.01}),
|
||||
"high_threshold": ("FLOAT", {"default": 0.8, "min": 0.01, "max": 0.99, "step": 0.01})
|
||||
}}
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
FUNCTION = "detect_edge"
|
||||
|
||||
CATEGORY = "image/preprocessors"
|
||||
|
||||
def detect_edge(self, image, low_threshold, high_threshold):
|
||||
output = canny(image.to(ldm_patched.modules.model_management.get_torch_device()).movedim(-1, 1), low_threshold, high_threshold)
|
||||
img_out = output[1].to(ldm_patched.modules.model_management.intermediate_device()).repeat(1, 3, 1, 1).movedim(1, -1)
|
||||
return (img_out,)
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"Canny": Canny,
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
# https://github.com/comfyanonymous/ComfyUI/blob/master/nodes.py
|
||||
|
||||
import torch
|
||||
from ldm_patched.contrib.external import MAX_RESOLUTION
|
||||
|
||||
class CLIPTextEncodeSDXLRefiner:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"ascore": ("FLOAT", {"default": 6.0, "min": 0.0, "max": 1000.0, "step": 0.01}),
|
||||
"width": ("INT", {"default": 1024.0, "min": 0, "max": MAX_RESOLUTION}),
|
||||
"height": ("INT", {"default": 1024.0, "min": 0, "max": MAX_RESOLUTION}),
|
||||
"text": ("STRING", {"multiline": True}), "clip": ("CLIP", ),
|
||||
}}
|
||||
RETURN_TYPES = ("CONDITIONING",)
|
||||
FUNCTION = "encode"
|
||||
|
||||
CATEGORY = "advanced/conditioning"
|
||||
|
||||
def encode(self, clip, ascore, width, height, text):
|
||||
tokens = clip.tokenize(text)
|
||||
cond, pooled = clip.encode_from_tokens(tokens, return_pooled=True)
|
||||
return ([[cond, {"pooled_output": pooled, "aesthetic_score": ascore, "width": width,"height": height}]], )
|
||||
|
||||
class CLIPTextEncodeSDXL:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"width": ("INT", {"default": 1024.0, "min": 0, "max": MAX_RESOLUTION}),
|
||||
"height": ("INT", {"default": 1024.0, "min": 0, "max": MAX_RESOLUTION}),
|
||||
"crop_w": ("INT", {"default": 0, "min": 0, "max": MAX_RESOLUTION}),
|
||||
"crop_h": ("INT", {"default": 0, "min": 0, "max": MAX_RESOLUTION}),
|
||||
"target_width": ("INT", {"default": 1024.0, "min": 0, "max": MAX_RESOLUTION}),
|
||||
"target_height": ("INT", {"default": 1024.0, "min": 0, "max": MAX_RESOLUTION}),
|
||||
"text_g": ("STRING", {"multiline": True, "default": "CLIP_G"}), "clip": ("CLIP", ),
|
||||
"text_l": ("STRING", {"multiline": True, "default": "CLIP_L"}), "clip": ("CLIP", ),
|
||||
}}
|
||||
RETURN_TYPES = ("CONDITIONING",)
|
||||
FUNCTION = "encode"
|
||||
|
||||
CATEGORY = "advanced/conditioning"
|
||||
|
||||
def encode(self, clip, width, height, crop_w, crop_h, target_width, target_height, text_g, text_l):
|
||||
tokens = clip.tokenize(text_g)
|
||||
tokens["l"] = clip.tokenize(text_l)["l"]
|
||||
if len(tokens["l"]) != len(tokens["g"]):
|
||||
empty = clip.tokenize("")
|
||||
while len(tokens["l"]) < len(tokens["g"]):
|
||||
tokens["l"] += empty["l"]
|
||||
while len(tokens["l"]) > len(tokens["g"]):
|
||||
tokens["g"] += empty["g"]
|
||||
cond, pooled = clip.encode_from_tokens(tokens, return_pooled=True)
|
||||
return ([[cond, {"pooled_output": pooled, "width": width, "height": height, "crop_w": crop_w, "crop_h": crop_h, "target_width": target_width, "target_height": target_height}]], )
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"CLIPTextEncodeSDXLRefiner": CLIPTextEncodeSDXLRefiner,
|
||||
"CLIPTextEncodeSDXL": CLIPTextEncodeSDXL,
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
# https://github.com/comfyanonymous/ComfyUI/blob/master/nodes.py
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
import ldm_patched.modules.utils
|
||||
from enum import Enum
|
||||
|
||||
def resize_mask(mask, shape):
|
||||
return torch.nn.functional.interpolate(mask.reshape((-1, 1, mask.shape[-2], mask.shape[-1])), size=(shape[0], shape[1]), mode="bilinear").squeeze(1)
|
||||
|
||||
class PorterDuffMode(Enum):
|
||||
ADD = 0
|
||||
CLEAR = 1
|
||||
DARKEN = 2
|
||||
DST = 3
|
||||
DST_ATOP = 4
|
||||
DST_IN = 5
|
||||
DST_OUT = 6
|
||||
DST_OVER = 7
|
||||
LIGHTEN = 8
|
||||
MULTIPLY = 9
|
||||
OVERLAY = 10
|
||||
SCREEN = 11
|
||||
SRC = 12
|
||||
SRC_ATOP = 13
|
||||
SRC_IN = 14
|
||||
SRC_OUT = 15
|
||||
SRC_OVER = 16
|
||||
XOR = 17
|
||||
|
||||
|
||||
def porter_duff_composite(src_image: torch.Tensor, src_alpha: torch.Tensor, dst_image: torch.Tensor, dst_alpha: torch.Tensor, mode: PorterDuffMode):
|
||||
if mode == PorterDuffMode.ADD:
|
||||
out_alpha = torch.clamp(src_alpha + dst_alpha, 0, 1)
|
||||
out_image = torch.clamp(src_image + dst_image, 0, 1)
|
||||
elif mode == PorterDuffMode.CLEAR:
|
||||
out_alpha = torch.zeros_like(dst_alpha)
|
||||
out_image = torch.zeros_like(dst_image)
|
||||
elif mode == PorterDuffMode.DARKEN:
|
||||
out_alpha = src_alpha + dst_alpha - src_alpha * dst_alpha
|
||||
out_image = (1 - dst_alpha) * src_image + (1 - src_alpha) * dst_image + torch.min(src_image, dst_image)
|
||||
elif mode == PorterDuffMode.DST:
|
||||
out_alpha = dst_alpha
|
||||
out_image = dst_image
|
||||
elif mode == PorterDuffMode.DST_ATOP:
|
||||
out_alpha = src_alpha
|
||||
out_image = src_alpha * dst_image + (1 - dst_alpha) * src_image
|
||||
elif mode == PorterDuffMode.DST_IN:
|
||||
out_alpha = src_alpha * dst_alpha
|
||||
out_image = dst_image * src_alpha
|
||||
elif mode == PorterDuffMode.DST_OUT:
|
||||
out_alpha = (1 - src_alpha) * dst_alpha
|
||||
out_image = (1 - src_alpha) * dst_image
|
||||
elif mode == PorterDuffMode.DST_OVER:
|
||||
out_alpha = dst_alpha + (1 - dst_alpha) * src_alpha
|
||||
out_image = dst_image + (1 - dst_alpha) * src_image
|
||||
elif mode == PorterDuffMode.LIGHTEN:
|
||||
out_alpha = src_alpha + dst_alpha - src_alpha * dst_alpha
|
||||
out_image = (1 - dst_alpha) * src_image + (1 - src_alpha) * dst_image + torch.max(src_image, dst_image)
|
||||
elif mode == PorterDuffMode.MULTIPLY:
|
||||
out_alpha = src_alpha * dst_alpha
|
||||
out_image = src_image * dst_image
|
||||
elif mode == PorterDuffMode.OVERLAY:
|
||||
out_alpha = src_alpha + dst_alpha - src_alpha * dst_alpha
|
||||
out_image = torch.where(2 * dst_image < dst_alpha, 2 * src_image * dst_image,
|
||||
src_alpha * dst_alpha - 2 * (dst_alpha - src_image) * (src_alpha - dst_image))
|
||||
elif mode == PorterDuffMode.SCREEN:
|
||||
out_alpha = src_alpha + dst_alpha - src_alpha * dst_alpha
|
||||
out_image = src_image + dst_image - src_image * dst_image
|
||||
elif mode == PorterDuffMode.SRC:
|
||||
out_alpha = src_alpha
|
||||
out_image = src_image
|
||||
elif mode == PorterDuffMode.SRC_ATOP:
|
||||
out_alpha = dst_alpha
|
||||
out_image = dst_alpha * src_image + (1 - src_alpha) * dst_image
|
||||
elif mode == PorterDuffMode.SRC_IN:
|
||||
out_alpha = src_alpha * dst_alpha
|
||||
out_image = src_image * dst_alpha
|
||||
elif mode == PorterDuffMode.SRC_OUT:
|
||||
out_alpha = (1 - dst_alpha) * src_alpha
|
||||
out_image = (1 - dst_alpha) * src_image
|
||||
elif mode == PorterDuffMode.SRC_OVER:
|
||||
out_alpha = src_alpha + (1 - src_alpha) * dst_alpha
|
||||
out_image = src_image + (1 - src_alpha) * dst_image
|
||||
elif mode == PorterDuffMode.XOR:
|
||||
out_alpha = (1 - dst_alpha) * src_alpha + (1 - src_alpha) * dst_alpha
|
||||
out_image = (1 - dst_alpha) * src_image + (1 - src_alpha) * dst_image
|
||||
else:
|
||||
out_alpha = None
|
||||
out_image = None
|
||||
return out_image, out_alpha
|
||||
|
||||
|
||||
class PorterDuffImageComposite:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"source": ("IMAGE",),
|
||||
"source_alpha": ("MASK",),
|
||||
"destination": ("IMAGE",),
|
||||
"destination_alpha": ("MASK",),
|
||||
"mode": ([mode.name for mode in PorterDuffMode], {"default": PorterDuffMode.DST.name}),
|
||||
},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE", "MASK")
|
||||
FUNCTION = "composite"
|
||||
CATEGORY = "mask/compositing"
|
||||
|
||||
def composite(self, source: torch.Tensor, source_alpha: torch.Tensor, destination: torch.Tensor, destination_alpha: torch.Tensor, mode):
|
||||
batch_size = min(len(source), len(source_alpha), len(destination), len(destination_alpha))
|
||||
out_images = []
|
||||
out_alphas = []
|
||||
|
||||
for i in range(batch_size):
|
||||
src_image = source[i]
|
||||
dst_image = destination[i]
|
||||
|
||||
assert src_image.shape[2] == dst_image.shape[2] # inputs need to have same number of channels
|
||||
|
||||
src_alpha = source_alpha[i].unsqueeze(2)
|
||||
dst_alpha = destination_alpha[i].unsqueeze(2)
|
||||
|
||||
if dst_alpha.shape[:2] != dst_image.shape[:2]:
|
||||
upscale_input = dst_alpha.unsqueeze(0).permute(0, 3, 1, 2)
|
||||
upscale_output = ldm_patched.modules.utils.common_upscale(upscale_input, dst_image.shape[1], dst_image.shape[0], upscale_method='bicubic', crop='center')
|
||||
dst_alpha = upscale_output.permute(0, 2, 3, 1).squeeze(0)
|
||||
if src_image.shape != dst_image.shape:
|
||||
upscale_input = src_image.unsqueeze(0).permute(0, 3, 1, 2)
|
||||
upscale_output = ldm_patched.modules.utils.common_upscale(upscale_input, dst_image.shape[1], dst_image.shape[0], upscale_method='bicubic', crop='center')
|
||||
src_image = upscale_output.permute(0, 2, 3, 1).squeeze(0)
|
||||
if src_alpha.shape != dst_alpha.shape:
|
||||
upscale_input = src_alpha.unsqueeze(0).permute(0, 3, 1, 2)
|
||||
upscale_output = ldm_patched.modules.utils.common_upscale(upscale_input, dst_alpha.shape[1], dst_alpha.shape[0], upscale_method='bicubic', crop='center')
|
||||
src_alpha = upscale_output.permute(0, 2, 3, 1).squeeze(0)
|
||||
|
||||
out_image, out_alpha = porter_duff_composite(src_image, src_alpha, dst_image, dst_alpha, PorterDuffMode[mode])
|
||||
|
||||
out_images.append(out_image)
|
||||
out_alphas.append(out_alpha.squeeze(2))
|
||||
|
||||
result = (torch.stack(out_images), torch.stack(out_alphas))
|
||||
return result
|
||||
|
||||
|
||||
class SplitImageWithAlpha:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"image": ("IMAGE",),
|
||||
}
|
||||
}
|
||||
|
||||
CATEGORY = "mask/compositing"
|
||||
RETURN_TYPES = ("IMAGE", "MASK")
|
||||
FUNCTION = "split_image_with_alpha"
|
||||
|
||||
def split_image_with_alpha(self, image: torch.Tensor):
|
||||
out_images = [i[:,:,:3] for i in image]
|
||||
out_alphas = [i[:,:,3] if i.shape[2] > 3 else torch.ones_like(i[:,:,0]) for i in image]
|
||||
result = (torch.stack(out_images), 1.0 - torch.stack(out_alphas))
|
||||
return result
|
||||
|
||||
|
||||
class JoinImageWithAlpha:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"image": ("IMAGE",),
|
||||
"alpha": ("MASK",),
|
||||
}
|
||||
}
|
||||
|
||||
CATEGORY = "mask/compositing"
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
FUNCTION = "join_image_with_alpha"
|
||||
|
||||
def join_image_with_alpha(self, image: torch.Tensor, alpha: torch.Tensor):
|
||||
batch_size = min(len(image), len(alpha))
|
||||
out_images = []
|
||||
|
||||
alpha = 1.0 - resize_mask(alpha, image.shape[1:])
|
||||
for i in range(batch_size):
|
||||
out_images.append(torch.cat((image[i][:,:,:3], alpha[i].unsqueeze(2)), dim=2))
|
||||
|
||||
result = (torch.stack(out_images),)
|
||||
return result
|
||||
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"PorterDuffImageComposite": PorterDuffImageComposite,
|
||||
"SplitImageWithAlpha": SplitImageWithAlpha,
|
||||
"JoinImageWithAlpha": JoinImageWithAlpha,
|
||||
}
|
||||
|
||||
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {
|
||||
"PorterDuffImageComposite": "Porter-Duff Image Composite",
|
||||
"SplitImageWithAlpha": "Split Image with Alpha",
|
||||
"JoinImageWithAlpha": "Join Image with Alpha",
|
||||
}
|
||||
@@ -0,0 +1,316 @@
|
||||
# https://github.com/comfyanonymous/ComfyUI/blob/master/nodes.py
|
||||
|
||||
import ldm_patched.modules.samplers
|
||||
import ldm_patched.modules.sample
|
||||
from ldm_patched.k_diffusion import sampling as k_diffusion_sampling
|
||||
import ldm_patched.utils.latent_visualization
|
||||
import torch
|
||||
import ldm_patched.modules.utils
|
||||
|
||||
|
||||
class BasicScheduler:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required":
|
||||
{"model": ("MODEL",),
|
||||
"scheduler": (ldm_patched.modules.samplers.SCHEDULER_NAMES, ),
|
||||
"steps": ("INT", {"default": 20, "min": 1, "max": 10000}),
|
||||
"denoise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}),
|
||||
}
|
||||
}
|
||||
RETURN_TYPES = ("SIGMAS",)
|
||||
CATEGORY = "sampling/custom_sampling/schedulers"
|
||||
|
||||
FUNCTION = "get_sigmas"
|
||||
|
||||
def get_sigmas(self, model, scheduler, steps, denoise):
|
||||
total_steps = steps
|
||||
if denoise < 1.0:
|
||||
total_steps = int(steps/denoise)
|
||||
|
||||
ldm_patched.modules.model_management.load_models_gpu([model])
|
||||
sigmas = ldm_patched.modules.samplers.calculate_sigmas_scheduler(model.model, scheduler, total_steps).cpu()
|
||||
sigmas = sigmas[-(steps + 1):]
|
||||
return (sigmas, )
|
||||
|
||||
|
||||
class KarrasScheduler:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required":
|
||||
{"steps": ("INT", {"default": 20, "min": 1, "max": 10000}),
|
||||
"sigma_max": ("FLOAT", {"default": 14.614642, "min": 0.0, "max": 1000.0, "step":0.01, "round": False}),
|
||||
"sigma_min": ("FLOAT", {"default": 0.0291675, "min": 0.0, "max": 1000.0, "step":0.01, "round": False}),
|
||||
"rho": ("FLOAT", {"default": 7.0, "min": 0.0, "max": 100.0, "step":0.01, "round": False}),
|
||||
}
|
||||
}
|
||||
RETURN_TYPES = ("SIGMAS",)
|
||||
CATEGORY = "sampling/custom_sampling/schedulers"
|
||||
|
||||
FUNCTION = "get_sigmas"
|
||||
|
||||
def get_sigmas(self, steps, sigma_max, sigma_min, rho):
|
||||
sigmas = k_diffusion_sampling.get_sigmas_karras(n=steps, sigma_min=sigma_min, sigma_max=sigma_max, rho=rho)
|
||||
return (sigmas, )
|
||||
|
||||
class ExponentialScheduler:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required":
|
||||
{"steps": ("INT", {"default": 20, "min": 1, "max": 10000}),
|
||||
"sigma_max": ("FLOAT", {"default": 14.614642, "min": 0.0, "max": 1000.0, "step":0.01, "round": False}),
|
||||
"sigma_min": ("FLOAT", {"default": 0.0291675, "min": 0.0, "max": 1000.0, "step":0.01, "round": False}),
|
||||
}
|
||||
}
|
||||
RETURN_TYPES = ("SIGMAS",)
|
||||
CATEGORY = "sampling/custom_sampling/schedulers"
|
||||
|
||||
FUNCTION = "get_sigmas"
|
||||
|
||||
def get_sigmas(self, steps, sigma_max, sigma_min):
|
||||
sigmas = k_diffusion_sampling.get_sigmas_exponential(n=steps, sigma_min=sigma_min, sigma_max=sigma_max)
|
||||
return (sigmas, )
|
||||
|
||||
class PolyexponentialScheduler:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required":
|
||||
{"steps": ("INT", {"default": 20, "min": 1, "max": 10000}),
|
||||
"sigma_max": ("FLOAT", {"default": 14.614642, "min": 0.0, "max": 1000.0, "step":0.01, "round": False}),
|
||||
"sigma_min": ("FLOAT", {"default": 0.0291675, "min": 0.0, "max": 1000.0, "step":0.01, "round": False}),
|
||||
"rho": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 100.0, "step":0.01, "round": False}),
|
||||
}
|
||||
}
|
||||
RETURN_TYPES = ("SIGMAS",)
|
||||
CATEGORY = "sampling/custom_sampling/schedulers"
|
||||
|
||||
FUNCTION = "get_sigmas"
|
||||
|
||||
def get_sigmas(self, steps, sigma_max, sigma_min, rho):
|
||||
sigmas = k_diffusion_sampling.get_sigmas_polyexponential(n=steps, sigma_min=sigma_min, sigma_max=sigma_max, rho=rho)
|
||||
return (sigmas, )
|
||||
|
||||
class SDTurboScheduler:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required":
|
||||
{"model": ("MODEL",),
|
||||
"steps": ("INT", {"default": 1, "min": 1, "max": 10}),
|
||||
"denoise": ("FLOAT", {"default": 1.0, "min": 0, "max": 1.0, "step": 0.01}),
|
||||
}
|
||||
}
|
||||
RETURN_TYPES = ("SIGMAS",)
|
||||
CATEGORY = "sampling/custom_sampling/schedulers"
|
||||
|
||||
FUNCTION = "get_sigmas"
|
||||
|
||||
def get_sigmas(self, model, steps, denoise):
|
||||
start_step = 10 - int(10 * denoise)
|
||||
timesteps = torch.flip(torch.arange(1, 11) * 100 - 1, (0,))[start_step:start_step + steps]
|
||||
sigmas = model.model_sampling.sigma(timesteps)
|
||||
sigmas = torch.cat([sigmas, sigmas.new_zeros([1])])
|
||||
return (sigmas, )
|
||||
|
||||
class VPScheduler:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required":
|
||||
{"steps": ("INT", {"default": 20, "min": 1, "max": 10000}),
|
||||
"beta_d": ("FLOAT", {"default": 19.9, "min": 0.0, "max": 1000.0, "step":0.01, "round": False}), #TODO: fix default values
|
||||
"beta_min": ("FLOAT", {"default": 0.1, "min": 0.0, "max": 1000.0, "step":0.01, "round": False}),
|
||||
"eps_s": ("FLOAT", {"default": 0.001, "min": 0.0, "max": 1.0, "step":0.0001, "round": False}),
|
||||
}
|
||||
}
|
||||
RETURN_TYPES = ("SIGMAS",)
|
||||
CATEGORY = "sampling/custom_sampling/schedulers"
|
||||
|
||||
FUNCTION = "get_sigmas"
|
||||
|
||||
def get_sigmas(self, steps, beta_d, beta_min, eps_s):
|
||||
sigmas = k_diffusion_sampling.get_sigmas_vp(n=steps, beta_d=beta_d, beta_min=beta_min, eps_s=eps_s)
|
||||
return (sigmas, )
|
||||
|
||||
class SplitSigmas:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required":
|
||||
{"sigmas": ("SIGMAS", ),
|
||||
"step": ("INT", {"default": 0, "min": 0, "max": 10000}),
|
||||
}
|
||||
}
|
||||
RETURN_TYPES = ("SIGMAS","SIGMAS")
|
||||
CATEGORY = "sampling/custom_sampling/sigmas"
|
||||
|
||||
FUNCTION = "get_sigmas"
|
||||
|
||||
def get_sigmas(self, sigmas, step):
|
||||
sigmas1 = sigmas[:step + 1]
|
||||
sigmas2 = sigmas[step:]
|
||||
return (sigmas1, sigmas2)
|
||||
|
||||
class FlipSigmas:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required":
|
||||
{"sigmas": ("SIGMAS", ),
|
||||
}
|
||||
}
|
||||
RETURN_TYPES = ("SIGMAS",)
|
||||
CATEGORY = "sampling/custom_sampling/sigmas"
|
||||
|
||||
FUNCTION = "get_sigmas"
|
||||
|
||||
def get_sigmas(self, sigmas):
|
||||
sigmas = sigmas.flip(0)
|
||||
if sigmas[0] == 0:
|
||||
sigmas[0] = 0.0001
|
||||
return (sigmas,)
|
||||
|
||||
class KSamplerSelect:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required":
|
||||
{"sampler_name": (ldm_patched.modules.samplers.SAMPLER_NAMES, ),
|
||||
}
|
||||
}
|
||||
RETURN_TYPES = ("SAMPLER",)
|
||||
CATEGORY = "sampling/custom_sampling/samplers"
|
||||
|
||||
FUNCTION = "get_sampler"
|
||||
|
||||
def get_sampler(self, sampler_name):
|
||||
sampler = ldm_patched.modules.samplers.sampler_object(sampler_name)
|
||||
return (sampler, )
|
||||
|
||||
class SamplerDPMPP_2M_SDE:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required":
|
||||
{"solver_type": (['midpoint', 'heun'], ),
|
||||
"eta": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 100.0, "step":0.01, "round": False}),
|
||||
"s_noise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 100.0, "step":0.01, "round": False}),
|
||||
"noise_device": (['gpu', 'cpu'], ),
|
||||
}
|
||||
}
|
||||
RETURN_TYPES = ("SAMPLER",)
|
||||
CATEGORY = "sampling/custom_sampling/samplers"
|
||||
|
||||
FUNCTION = "get_sampler"
|
||||
|
||||
def get_sampler(self, solver_type, eta, s_noise, noise_device):
|
||||
if noise_device == 'cpu':
|
||||
sampler_name = "dpmpp_2m_sde"
|
||||
else:
|
||||
sampler_name = "dpmpp_2m_sde_gpu"
|
||||
sampler = ldm_patched.modules.samplers.ksampler(sampler_name, {"eta": eta, "s_noise": s_noise, "solver_type": solver_type})
|
||||
return (sampler, )
|
||||
|
||||
|
||||
class SamplerDPMPP_SDE:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required":
|
||||
{"eta": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 100.0, "step":0.01, "round": False}),
|
||||
"s_noise": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 100.0, "step":0.01, "round": False}),
|
||||
"r": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 100.0, "step":0.01, "round": False}),
|
||||
"noise_device": (['gpu', 'cpu'], ),
|
||||
}
|
||||
}
|
||||
RETURN_TYPES = ("SAMPLER",)
|
||||
CATEGORY = "sampling/custom_sampling/samplers"
|
||||
|
||||
FUNCTION = "get_sampler"
|
||||
|
||||
def get_sampler(self, eta, s_noise, r, noise_device):
|
||||
if noise_device == 'cpu':
|
||||
sampler_name = "dpmpp_sde"
|
||||
else:
|
||||
sampler_name = "dpmpp_sde_gpu"
|
||||
sampler = ldm_patched.modules.samplers.ksampler(sampler_name, {"eta": eta, "s_noise": s_noise, "r": r})
|
||||
return (sampler, )
|
||||
|
||||
|
||||
class SamplerTCD:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"eta": ("FLOAT", {"default": 0.3, "min": 0.0, "max": 1.0, "step": 0.01}),
|
||||
}
|
||||
}
|
||||
RETURN_TYPES = ("SAMPLER",)
|
||||
CATEGORY = "sampling/custom_sampling/samplers"
|
||||
|
||||
FUNCTION = "get_sampler"
|
||||
|
||||
def get_sampler(self, eta=0.3):
|
||||
sampler = ldm_patched.modules.samplers.ksampler("tcd", {"eta": eta})
|
||||
return (sampler, )
|
||||
|
||||
|
||||
class SamplerCustom:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required":
|
||||
{"model": ("MODEL",),
|
||||
"add_noise": ("BOOLEAN", {"default": True}),
|
||||
"noise_seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff}),
|
||||
"cfg": ("FLOAT", {"default": 8.0, "min": 0.0, "max": 100.0, "step":0.1, "round": 0.01}),
|
||||
"positive": ("CONDITIONING", ),
|
||||
"negative": ("CONDITIONING", ),
|
||||
"sampler": ("SAMPLER", ),
|
||||
"sigmas": ("SIGMAS", ),
|
||||
"latent_image": ("LATENT", ),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("LATENT","LATENT")
|
||||
RETURN_NAMES = ("output", "denoised_output")
|
||||
|
||||
FUNCTION = "sample"
|
||||
|
||||
CATEGORY = "sampling/custom_sampling"
|
||||
|
||||
def sample(self, model, add_noise, noise_seed, cfg, positive, negative, sampler, sigmas, latent_image):
|
||||
latent = latent_image
|
||||
latent_image = latent["samples"]
|
||||
if not add_noise:
|
||||
noise = torch.zeros(latent_image.size(), dtype=latent_image.dtype, layout=latent_image.layout, device="cpu")
|
||||
else:
|
||||
batch_inds = latent["batch_index"] if "batch_index" in latent else None
|
||||
noise = ldm_patched.modules.sample.prepare_noise(latent_image, noise_seed, batch_inds)
|
||||
|
||||
noise_mask = None
|
||||
if "noise_mask" in latent:
|
||||
noise_mask = latent["noise_mask"]
|
||||
|
||||
x0_output = {}
|
||||
callback = ldm_patched.utils.latent_visualization.prepare_callback(model, sigmas.shape[-1] - 1, x0_output)
|
||||
|
||||
disable_pbar = not ldm_patched.modules.utils.PROGRESS_BAR_ENABLED
|
||||
samples = ldm_patched.modules.sample.sample_custom(model, noise, cfg, sampler, sigmas, positive, negative, latent_image, noise_mask=noise_mask, callback=callback, disable_pbar=disable_pbar, seed=noise_seed)
|
||||
|
||||
out = latent.copy()
|
||||
out["samples"] = samples
|
||||
if "x0" in x0_output:
|
||||
out_denoised = latent.copy()
|
||||
out_denoised["samples"] = model.model.process_latent_out(x0_output["x0"].cpu())
|
||||
else:
|
||||
out_denoised = out
|
||||
return (out, out_denoised)
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"SamplerCustom": SamplerCustom,
|
||||
"BasicScheduler": BasicScheduler,
|
||||
"KarrasScheduler": KarrasScheduler,
|
||||
"ExponentialScheduler": ExponentialScheduler,
|
||||
"PolyexponentialScheduler": PolyexponentialScheduler,
|
||||
"VPScheduler": VPScheduler,
|
||||
"SDTurboScheduler": SDTurboScheduler,
|
||||
"KSamplerSelect": KSamplerSelect,
|
||||
"SamplerDPMPP_2M_SDE": SamplerDPMPP_2M_SDE,
|
||||
"SamplerDPMPP_SDE": SamplerDPMPP_SDE,
|
||||
"SamplerTCD": SamplerTCD,
|
||||
"SplitSigmas": SplitSigmas,
|
||||
"FlipSigmas": FlipSigmas,
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
# https://github.com/comfyanonymous/ComfyUI/blob/master/nodes.py
|
||||
|
||||
#code originally taken from: https://github.com/ChenyangSi/FreeU (under MIT License)
|
||||
|
||||
import torch
|
||||
|
||||
|
||||
def Fourier_filter(x, threshold, scale):
|
||||
# FFT
|
||||
x_freq = torch.fft.fftn(x.float(), dim=(-2, -1))
|
||||
x_freq = torch.fft.fftshift(x_freq, dim=(-2, -1))
|
||||
|
||||
B, C, H, W = x_freq.shape
|
||||
mask = torch.ones((B, C, H, W), device=x.device)
|
||||
|
||||
crow, ccol = H // 2, W //2
|
||||
mask[..., crow - threshold:crow + threshold, ccol - threshold:ccol + threshold] = scale
|
||||
x_freq = x_freq * mask
|
||||
|
||||
# IFFT
|
||||
x_freq = torch.fft.ifftshift(x_freq, dim=(-2, -1))
|
||||
x_filtered = torch.fft.ifftn(x_freq, dim=(-2, -1)).real
|
||||
|
||||
return x_filtered.to(x.dtype)
|
||||
|
||||
|
||||
class FreeU:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": { "model": ("MODEL",),
|
||||
"b1": ("FLOAT", {"default": 1.1, "min": 0.0, "max": 10.0, "step": 0.01}),
|
||||
"b2": ("FLOAT", {"default": 1.2, "min": 0.0, "max": 10.0, "step": 0.01}),
|
||||
"s1": ("FLOAT", {"default": 0.9, "min": 0.0, "max": 10.0, "step": 0.01}),
|
||||
"s2": ("FLOAT", {"default": 0.2, "min": 0.0, "max": 10.0, "step": 0.01}),
|
||||
}}
|
||||
RETURN_TYPES = ("MODEL",)
|
||||
FUNCTION = "patch"
|
||||
|
||||
CATEGORY = "model_patches"
|
||||
|
||||
def patch(self, model, b1, b2, s1, s2):
|
||||
model_channels = model.model.model_config.unet_config["model_channels"]
|
||||
scale_dict = {model_channels * 4: (b1, s1), model_channels * 2: (b2, s2)}
|
||||
on_cpu_devices = {}
|
||||
|
||||
def output_block_patch(h, hsp, transformer_options):
|
||||
scale = scale_dict.get(h.shape[1], None)
|
||||
if scale is not None:
|
||||
h[:,:h.shape[1] // 2] = h[:,:h.shape[1] // 2] * scale[0]
|
||||
if hsp.device not in on_cpu_devices:
|
||||
try:
|
||||
hsp = Fourier_filter(hsp, threshold=1, scale=scale[1])
|
||||
except:
|
||||
print("Device", hsp.device, "does not support the torch.fft functions used in the FreeU node, switching to CPU.")
|
||||
on_cpu_devices[hsp.device] = True
|
||||
hsp = Fourier_filter(hsp.cpu(), threshold=1, scale=scale[1]).to(hsp.device)
|
||||
else:
|
||||
hsp = Fourier_filter(hsp.cpu(), threshold=1, scale=scale[1]).to(hsp.device)
|
||||
|
||||
return h, hsp
|
||||
|
||||
m = model.clone()
|
||||
m.set_model_output_block_patch(output_block_patch)
|
||||
return (m, )
|
||||
|
||||
class FreeU_V2:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": { "model": ("MODEL",),
|
||||
"b1": ("FLOAT", {"default": 1.3, "min": 0.0, "max": 10.0, "step": 0.01}),
|
||||
"b2": ("FLOAT", {"default": 1.4, "min": 0.0, "max": 10.0, "step": 0.01}),
|
||||
"s1": ("FLOAT", {"default": 0.9, "min": 0.0, "max": 10.0, "step": 0.01}),
|
||||
"s2": ("FLOAT", {"default": 0.2, "min": 0.0, "max": 10.0, "step": 0.01}),
|
||||
}}
|
||||
RETURN_TYPES = ("MODEL",)
|
||||
FUNCTION = "patch"
|
||||
|
||||
CATEGORY = "model_patches"
|
||||
|
||||
def patch(self, model, b1, b2, s1, s2):
|
||||
model_channels = model.model.model_config.unet_config["model_channels"]
|
||||
scale_dict = {model_channels * 4: (b1, s1), model_channels * 2: (b2, s2)}
|
||||
on_cpu_devices = {}
|
||||
|
||||
def output_block_patch(h, hsp, transformer_options):
|
||||
scale = scale_dict.get(h.shape[1], None)
|
||||
if scale is not None:
|
||||
hidden_mean = h.mean(1).unsqueeze(1)
|
||||
B = hidden_mean.shape[0]
|
||||
hidden_max, _ = torch.max(hidden_mean.view(B, -1), dim=-1, keepdim=True)
|
||||
hidden_min, _ = torch.min(hidden_mean.view(B, -1), dim=-1, keepdim=True)
|
||||
hidden_mean = (hidden_mean - hidden_min.unsqueeze(2).unsqueeze(3)) / (hidden_max - hidden_min).unsqueeze(2).unsqueeze(3)
|
||||
|
||||
h[:,:h.shape[1] // 2] = h[:,:h.shape[1] // 2] * ((scale[0] - 1 ) * hidden_mean + 1)
|
||||
|
||||
if hsp.device not in on_cpu_devices:
|
||||
try:
|
||||
hsp = Fourier_filter(hsp, threshold=1, scale=scale[1])
|
||||
except:
|
||||
print("Device", hsp.device, "does not support the torch.fft functions used in the FreeU node, switching to CPU.")
|
||||
on_cpu_devices[hsp.device] = True
|
||||
hsp = Fourier_filter(hsp.cpu(), threshold=1, scale=scale[1]).to(hsp.device)
|
||||
else:
|
||||
hsp = Fourier_filter(hsp.cpu(), threshold=1, scale=scale[1]).to(hsp.device)
|
||||
|
||||
return h, hsp
|
||||
|
||||
m = model.clone()
|
||||
m.set_model_output_block_patch(output_block_patch)
|
||||
return (m, )
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"FreeU": FreeU,
|
||||
"FreeU_V2": FreeU_V2,
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
# https://github.com/comfyanonymous/ComfyUI/blob/master/nodes.py
|
||||
|
||||
import ldm_patched.modules.utils
|
||||
import ldm_patched.utils.path_utils
|
||||
import torch
|
||||
|
||||
def load_hypernetwork_patch(path, strength):
|
||||
sd = ldm_patched.modules.utils.load_torch_file(path, safe_load=True)
|
||||
activation_func = sd.get('activation_func', 'linear')
|
||||
is_layer_norm = sd.get('is_layer_norm', False)
|
||||
use_dropout = sd.get('use_dropout', False)
|
||||
activate_output = sd.get('activate_output', False)
|
||||
last_layer_dropout = sd.get('last_layer_dropout', False)
|
||||
|
||||
valid_activation = {
|
||||
"linear": torch.nn.Identity,
|
||||
"relu": torch.nn.ReLU,
|
||||
"leakyrelu": torch.nn.LeakyReLU,
|
||||
"elu": torch.nn.ELU,
|
||||
"swish": torch.nn.Hardswish,
|
||||
"tanh": torch.nn.Tanh,
|
||||
"sigmoid": torch.nn.Sigmoid,
|
||||
"softsign": torch.nn.Softsign,
|
||||
"mish": torch.nn.Mish,
|
||||
}
|
||||
|
||||
if activation_func not in valid_activation:
|
||||
print("Unsupported Hypernetwork format, if you report it I might implement it.", path, " ", activation_func, is_layer_norm, use_dropout, activate_output, last_layer_dropout)
|
||||
return None
|
||||
|
||||
out = {}
|
||||
|
||||
for d in sd:
|
||||
try:
|
||||
dim = int(d)
|
||||
except:
|
||||
continue
|
||||
|
||||
output = []
|
||||
for index in [0, 1]:
|
||||
attn_weights = sd[dim][index]
|
||||
keys = attn_weights.keys()
|
||||
|
||||
linears = filter(lambda a: a.endswith(".weight"), keys)
|
||||
linears = list(map(lambda a: a[:-len(".weight")], linears))
|
||||
layers = []
|
||||
|
||||
i = 0
|
||||
while i < len(linears):
|
||||
lin_name = linears[i]
|
||||
last_layer = (i == (len(linears) - 1))
|
||||
penultimate_layer = (i == (len(linears) - 2))
|
||||
|
||||
lin_weight = attn_weights['{}.weight'.format(lin_name)]
|
||||
lin_bias = attn_weights['{}.bias'.format(lin_name)]
|
||||
layer = torch.nn.Linear(lin_weight.shape[1], lin_weight.shape[0])
|
||||
layer.load_state_dict({"weight": lin_weight, "bias": lin_bias})
|
||||
layers.append(layer)
|
||||
if activation_func != "linear":
|
||||
if (not last_layer) or (activate_output):
|
||||
layers.append(valid_activation[activation_func]())
|
||||
if is_layer_norm:
|
||||
i += 1
|
||||
ln_name = linears[i]
|
||||
ln_weight = attn_weights['{}.weight'.format(ln_name)]
|
||||
ln_bias = attn_weights['{}.bias'.format(ln_name)]
|
||||
ln = torch.nn.LayerNorm(ln_weight.shape[0])
|
||||
ln.load_state_dict({"weight": ln_weight, "bias": ln_bias})
|
||||
layers.append(ln)
|
||||
if use_dropout:
|
||||
if (not last_layer) and (not penultimate_layer or last_layer_dropout):
|
||||
layers.append(torch.nn.Dropout(p=0.3))
|
||||
i += 1
|
||||
|
||||
output.append(torch.nn.Sequential(*layers))
|
||||
out[dim] = torch.nn.ModuleList(output)
|
||||
|
||||
class hypernetwork_patch:
|
||||
def __init__(self, hypernet, strength):
|
||||
self.hypernet = hypernet
|
||||
self.strength = strength
|
||||
def __call__(self, q, k, v, extra_options):
|
||||
dim = k.shape[-1]
|
||||
if dim in self.hypernet:
|
||||
hn = self.hypernet[dim]
|
||||
k = k + hn[0](k) * self.strength
|
||||
v = v + hn[1](v) * self.strength
|
||||
|
||||
return q, k, v
|
||||
|
||||
def to(self, device):
|
||||
for d in self.hypernet.keys():
|
||||
self.hypernet[d] = self.hypernet[d].to(device)
|
||||
return self
|
||||
|
||||
return hypernetwork_patch(out, strength)
|
||||
|
||||
class HypernetworkLoader:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": { "model": ("MODEL",),
|
||||
"hypernetwork_name": (ldm_patched.utils.path_utils.get_filename_list("hypernetworks"), ),
|
||||
"strength": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}),
|
||||
}}
|
||||
RETURN_TYPES = ("MODEL",)
|
||||
FUNCTION = "load_hypernetwork"
|
||||
|
||||
CATEGORY = "loaders"
|
||||
|
||||
def load_hypernetwork(self, model, hypernetwork_name, strength):
|
||||
hypernetwork_path = ldm_patched.utils.path_utils.get_full_path("hypernetworks", hypernetwork_name)
|
||||
model_hypernetwork = model.clone()
|
||||
patch = load_hypernetwork_patch(hypernetwork_path, strength)
|
||||
if patch is not None:
|
||||
model_hypernetwork.set_model_attn1_patch(patch)
|
||||
model_hypernetwork.set_model_attn2_patch(patch)
|
||||
return (model_hypernetwork,)
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"HypernetworkLoader": HypernetworkLoader
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
# https://github.com/comfyanonymous/ComfyUI/blob/master/nodes.py
|
||||
|
||||
#Taken from: https://github.com/tfernd/HyperTile/
|
||||
|
||||
import math
|
||||
from einops import rearrange
|
||||
# Use torch rng for consistency across generations
|
||||
from torch import randint
|
||||
|
||||
def random_divisor(value: int, min_value: int, /, max_options: int = 1) -> int:
|
||||
min_value = min(min_value, value)
|
||||
|
||||
# All big divisors of value (inclusive)
|
||||
divisors = [i for i in range(min_value, value + 1) if value % i == 0]
|
||||
|
||||
ns = [value // i for i in divisors[:max_options]] # has at least 1 element
|
||||
|
||||
if len(ns) - 1 > 0:
|
||||
idx = randint(low=0, high=len(ns) - 1, size=(1,)).item()
|
||||
else:
|
||||
idx = 0
|
||||
|
||||
return ns[idx]
|
||||
|
||||
class HyperTile:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": { "model": ("MODEL",),
|
||||
"tile_size": ("INT", {"default": 256, "min": 1, "max": 2048}),
|
||||
"swap_size": ("INT", {"default": 2, "min": 1, "max": 128}),
|
||||
"max_depth": ("INT", {"default": 0, "min": 0, "max": 10}),
|
||||
"scale_depth": ("BOOLEAN", {"default": False}),
|
||||
}}
|
||||
RETURN_TYPES = ("MODEL",)
|
||||
FUNCTION = "patch"
|
||||
|
||||
CATEGORY = "model_patches"
|
||||
|
||||
def patch(self, model, tile_size, swap_size, max_depth, scale_depth):
|
||||
model_channels = model.model.model_config.unet_config["model_channels"]
|
||||
|
||||
latent_tile_size = max(32, tile_size) // 8
|
||||
self.temp = None
|
||||
|
||||
def hypertile_in(q, k, v, extra_options):
|
||||
model_chans = q.shape[-2]
|
||||
orig_shape = extra_options['original_shape']
|
||||
apply_to = []
|
||||
for i in range(max_depth + 1):
|
||||
apply_to.append((orig_shape[-2] / (2 ** i)) * (orig_shape[-1] / (2 ** i)))
|
||||
|
||||
if model_chans in apply_to:
|
||||
shape = extra_options["original_shape"]
|
||||
aspect_ratio = shape[-1] / shape[-2]
|
||||
|
||||
hw = q.size(1)
|
||||
h, w = round(math.sqrt(hw * aspect_ratio)), round(math.sqrt(hw / aspect_ratio))
|
||||
|
||||
factor = (2 ** apply_to.index(model_chans)) if scale_depth else 1
|
||||
nh = random_divisor(h, latent_tile_size * factor, swap_size)
|
||||
nw = random_divisor(w, latent_tile_size * factor, swap_size)
|
||||
|
||||
if nh * nw > 1:
|
||||
q = rearrange(q, "b (nh h nw w) c -> (b nh nw) (h w) c", h=h // nh, w=w // nw, nh=nh, nw=nw)
|
||||
self.temp = (nh, nw, h, w)
|
||||
return q, k, v
|
||||
|
||||
return q, k, v
|
||||
def hypertile_out(out, extra_options):
|
||||
if self.temp is not None:
|
||||
nh, nw, h, w = self.temp
|
||||
self.temp = None
|
||||
out = rearrange(out, "(b nh nw) hw c -> b nh nw hw c", nh=nh, nw=nw)
|
||||
out = rearrange(out, "b nh nw (h w) c -> b (nh h nw w) c", h=h // nh, w=w // nw)
|
||||
return out
|
||||
|
||||
|
||||
m = model.clone()
|
||||
m.set_model_attn1_patch(hypertile_in)
|
||||
m.set_model_attn1_output_patch(hypertile_out)
|
||||
return (m, )
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"HyperTile": HyperTile,
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
# https://github.com/comfyanonymous/ComfyUI/blob/master/nodes.py
|
||||
|
||||
import ldm_patched.contrib.external
|
||||
import ldm_patched.utils.path_utils
|
||||
from ldm_patched.modules.args_parser import args
|
||||
|
||||
from PIL import Image
|
||||
from PIL.PngImagePlugin import PngInfo
|
||||
|
||||
import numpy as np
|
||||
import json
|
||||
import os
|
||||
|
||||
MAX_RESOLUTION = ldm_patched.contrib.external.MAX_RESOLUTION
|
||||
|
||||
class ImageCrop:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": { "image": ("IMAGE",),
|
||||
"width": ("INT", {"default": 512, "min": 1, "max": MAX_RESOLUTION, "step": 1}),
|
||||
"height": ("INT", {"default": 512, "min": 1, "max": MAX_RESOLUTION, "step": 1}),
|
||||
"x": ("INT", {"default": 0, "min": 0, "max": MAX_RESOLUTION, "step": 1}),
|
||||
"y": ("INT", {"default": 0, "min": 0, "max": MAX_RESOLUTION, "step": 1}),
|
||||
}}
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
FUNCTION = "crop"
|
||||
|
||||
CATEGORY = "image/transform"
|
||||
|
||||
def crop(self, image, width, height, x, y):
|
||||
x = min(x, image.shape[2] - 1)
|
||||
y = min(y, image.shape[1] - 1)
|
||||
to_x = width + x
|
||||
to_y = height + y
|
||||
img = image[:,y:to_y, x:to_x, :]
|
||||
return (img,)
|
||||
|
||||
class RepeatImageBatch:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": { "image": ("IMAGE",),
|
||||
"amount": ("INT", {"default": 1, "min": 1, "max": 64}),
|
||||
}}
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
FUNCTION = "repeat"
|
||||
|
||||
CATEGORY = "image/batch"
|
||||
|
||||
def repeat(self, image, amount):
|
||||
s = image.repeat((amount, 1,1,1))
|
||||
return (s,)
|
||||
|
||||
class SaveAnimatedWEBP:
|
||||
def __init__(self):
|
||||
self.output_dir = ldm_patched.utils.path_utils.get_output_directory()
|
||||
self.type = "output"
|
||||
self.prefix_append = ""
|
||||
|
||||
methods = {"default": 4, "fastest": 0, "slowest": 6}
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required":
|
||||
{"images": ("IMAGE", ),
|
||||
"filename_prefix": ("STRING", {"default": "ldm_patched"}),
|
||||
"fps": ("FLOAT", {"default": 6.0, "min": 0.01, "max": 1000.0, "step": 0.01}),
|
||||
"lossless": ("BOOLEAN", {"default": True}),
|
||||
"quality": ("INT", {"default": 80, "min": 0, "max": 100}),
|
||||
"method": (list(s.methods.keys()),),
|
||||
# "num_frames": ("INT", {"default": 0, "min": 0, "max": 8192}),
|
||||
},
|
||||
"hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ()
|
||||
FUNCTION = "save_images"
|
||||
|
||||
OUTPUT_NODE = True
|
||||
|
||||
CATEGORY = "image/animation"
|
||||
|
||||
def save_images(self, images, fps, filename_prefix, lossless, quality, method, num_frames=0, prompt=None, extra_pnginfo=None):
|
||||
method = self.methods.get(method)
|
||||
filename_prefix += self.prefix_append
|
||||
full_output_folder, filename, counter, subfolder, filename_prefix = ldm_patched.utils.path_utils.get_save_image_path(filename_prefix, self.output_dir, images[0].shape[1], images[0].shape[0])
|
||||
results = list()
|
||||
pil_images = []
|
||||
for image in images:
|
||||
i = 255. * image.cpu().numpy()
|
||||
img = Image.fromarray(np.clip(i, 0, 255).astype(np.uint8))
|
||||
pil_images.append(img)
|
||||
|
||||
metadata = pil_images[0].getexif()
|
||||
if not args.disable_server_info:
|
||||
if prompt is not None:
|
||||
metadata[0x0110] = "prompt:{}".format(json.dumps(prompt))
|
||||
if extra_pnginfo is not None:
|
||||
inital_exif = 0x010f
|
||||
for x in extra_pnginfo:
|
||||
metadata[inital_exif] = "{}:{}".format(x, json.dumps(extra_pnginfo[x]))
|
||||
inital_exif -= 1
|
||||
|
||||
if num_frames == 0:
|
||||
num_frames = len(pil_images)
|
||||
|
||||
c = len(pil_images)
|
||||
for i in range(0, c, num_frames):
|
||||
file = f"{filename}_{counter:05}_.webp"
|
||||
pil_images[i].save(os.path.join(full_output_folder, file), save_all=True, duration=int(1000.0/fps), append_images=pil_images[i + 1:i + num_frames], exif=metadata, lossless=lossless, quality=quality, method=method)
|
||||
results.append({
|
||||
"filename": file,
|
||||
"subfolder": subfolder,
|
||||
"type": self.type
|
||||
})
|
||||
counter += 1
|
||||
|
||||
animated = num_frames != 1
|
||||
return { "ui": { "images": results, "animated": (animated,) } }
|
||||
|
||||
class SaveAnimatedPNG:
|
||||
def __init__(self):
|
||||
self.output_dir = ldm_patched.utils.path_utils.get_output_directory()
|
||||
self.type = "output"
|
||||
self.prefix_append = ""
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required":
|
||||
{"images": ("IMAGE", ),
|
||||
"filename_prefix": ("STRING", {"default": "ldm_patched"}),
|
||||
"fps": ("FLOAT", {"default": 6.0, "min": 0.01, "max": 1000.0, "step": 0.01}),
|
||||
"compress_level": ("INT", {"default": 4, "min": 0, "max": 9})
|
||||
},
|
||||
"hidden": {"prompt": "PROMPT", "extra_pnginfo": "EXTRA_PNGINFO"},
|
||||
}
|
||||
|
||||
RETURN_TYPES = ()
|
||||
FUNCTION = "save_images"
|
||||
|
||||
OUTPUT_NODE = True
|
||||
|
||||
CATEGORY = "image/animation"
|
||||
|
||||
def save_images(self, images, fps, compress_level, filename_prefix="ldm_patched", prompt=None, extra_pnginfo=None):
|
||||
filename_prefix += self.prefix_append
|
||||
full_output_folder, filename, counter, subfolder, filename_prefix = ldm_patched.utils.path_utils.get_save_image_path(filename_prefix, self.output_dir, images[0].shape[1], images[0].shape[0])
|
||||
results = list()
|
||||
pil_images = []
|
||||
for image in images:
|
||||
i = 255. * image.cpu().numpy()
|
||||
img = Image.fromarray(np.clip(i, 0, 255).astype(np.uint8))
|
||||
pil_images.append(img)
|
||||
|
||||
metadata = None
|
||||
if not args.disable_server_info:
|
||||
metadata = PngInfo()
|
||||
if prompt is not None:
|
||||
metadata.add(b"ldm_patched", "prompt".encode("latin-1", "strict") + b"\0" + json.dumps(prompt).encode("latin-1", "strict"), after_idat=True)
|
||||
if extra_pnginfo is not None:
|
||||
for x in extra_pnginfo:
|
||||
metadata.add(b"ldm_patched", x.encode("latin-1", "strict") + b"\0" + json.dumps(extra_pnginfo[x]).encode("latin-1", "strict"), after_idat=True)
|
||||
|
||||
file = f"{filename}_{counter:05}_.png"
|
||||
pil_images[0].save(os.path.join(full_output_folder, file), pnginfo=metadata, compress_level=compress_level, save_all=True, duration=int(1000.0/fps), append_images=pil_images[1:])
|
||||
results.append({
|
||||
"filename": file,
|
||||
"subfolder": subfolder,
|
||||
"type": self.type
|
||||
})
|
||||
|
||||
return { "ui": { "images": results, "animated": (True,)} }
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"ImageCrop": ImageCrop,
|
||||
"RepeatImageBatch": RepeatImageBatch,
|
||||
"SaveAnimatedWEBP": SaveAnimatedWEBP,
|
||||
"SaveAnimatedPNG": SaveAnimatedPNG,
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
# https://github.com/comfyanonymous/ComfyUI/blob/master/nodes.py
|
||||
|
||||
import ldm_patched.modules.utils
|
||||
import torch
|
||||
|
||||
def reshape_latent_to(target_shape, latent):
|
||||
if latent.shape[1:] != target_shape[1:]:
|
||||
latent = ldm_patched.modules.utils.common_upscale(latent, target_shape[3], target_shape[2], "bilinear", "center")
|
||||
return ldm_patched.modules.utils.repeat_to_batch_size(latent, target_shape[0])
|
||||
|
||||
|
||||
class LatentAdd:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": { "samples1": ("LATENT",), "samples2": ("LATENT",)}}
|
||||
|
||||
RETURN_TYPES = ("LATENT",)
|
||||
FUNCTION = "op"
|
||||
|
||||
CATEGORY = "latent/advanced"
|
||||
|
||||
def op(self, samples1, samples2):
|
||||
samples_out = samples1.copy()
|
||||
|
||||
s1 = samples1["samples"]
|
||||
s2 = samples2["samples"]
|
||||
|
||||
s2 = reshape_latent_to(s1.shape, s2)
|
||||
samples_out["samples"] = s1 + s2
|
||||
return (samples_out,)
|
||||
|
||||
class LatentSubtract:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": { "samples1": ("LATENT",), "samples2": ("LATENT",)}}
|
||||
|
||||
RETURN_TYPES = ("LATENT",)
|
||||
FUNCTION = "op"
|
||||
|
||||
CATEGORY = "latent/advanced"
|
||||
|
||||
def op(self, samples1, samples2):
|
||||
samples_out = samples1.copy()
|
||||
|
||||
s1 = samples1["samples"]
|
||||
s2 = samples2["samples"]
|
||||
|
||||
s2 = reshape_latent_to(s1.shape, s2)
|
||||
samples_out["samples"] = s1 - s2
|
||||
return (samples_out,)
|
||||
|
||||
class LatentMultiply:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": { "samples": ("LATENT",),
|
||||
"multiplier": ("FLOAT", {"default": 1.0, "min": -10.0, "max": 10.0, "step": 0.01}),
|
||||
}}
|
||||
|
||||
RETURN_TYPES = ("LATENT",)
|
||||
FUNCTION = "op"
|
||||
|
||||
CATEGORY = "latent/advanced"
|
||||
|
||||
def op(self, samples, multiplier):
|
||||
samples_out = samples.copy()
|
||||
|
||||
s1 = samples["samples"]
|
||||
samples_out["samples"] = s1 * multiplier
|
||||
return (samples_out,)
|
||||
|
||||
class LatentInterpolate:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": { "samples1": ("LATENT",),
|
||||
"samples2": ("LATENT",),
|
||||
"ratio": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.01}),
|
||||
}}
|
||||
|
||||
RETURN_TYPES = ("LATENT",)
|
||||
FUNCTION = "op"
|
||||
|
||||
CATEGORY = "latent/advanced"
|
||||
|
||||
def op(self, samples1, samples2, ratio):
|
||||
samples_out = samples1.copy()
|
||||
|
||||
s1 = samples1["samples"]
|
||||
s2 = samples2["samples"]
|
||||
|
||||
s2 = reshape_latent_to(s1.shape, s2)
|
||||
|
||||
m1 = torch.linalg.vector_norm(s1, dim=(1))
|
||||
m2 = torch.linalg.vector_norm(s2, dim=(1))
|
||||
|
||||
s1 = torch.nan_to_num(s1 / m1)
|
||||
s2 = torch.nan_to_num(s2 / m2)
|
||||
|
||||
t = (s1 * ratio + s2 * (1.0 - ratio))
|
||||
mt = torch.linalg.vector_norm(t, dim=(1))
|
||||
st = torch.nan_to_num(t / mt)
|
||||
|
||||
samples_out["samples"] = st * (m1 * ratio + m2 * (1.0 - ratio))
|
||||
return (samples_out,)
|
||||
|
||||
class LatentBatch:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": { "samples1": ("LATENT",), "samples2": ("LATENT",)}}
|
||||
|
||||
RETURN_TYPES = ("LATENT",)
|
||||
FUNCTION = "batch"
|
||||
|
||||
CATEGORY = "latent/batch"
|
||||
|
||||
def batch(self, samples1, samples2):
|
||||
samples_out = samples1.copy()
|
||||
s1 = samples1["samples"]
|
||||
s2 = samples2["samples"]
|
||||
|
||||
if s1.shape[1:] != s2.shape[1:]:
|
||||
s2 = ldm_patched.modules.utils.common_upscale(s2, s1.shape[3], s1.shape[2], "bilinear", "center")
|
||||
s = torch.cat((s1, s2), dim=0)
|
||||
samples_out["samples"] = s
|
||||
samples_out["batch_index"] = samples1.get("batch_index", [x for x in range(0, s1.shape[0])]) + samples2.get("batch_index", [x for x in range(0, s2.shape[0])])
|
||||
return (samples_out,)
|
||||
|
||||
class LatentBatchSeedBehavior:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": { "samples": ("LATENT",),
|
||||
"seed_behavior": (["random", "fixed"],),}}
|
||||
|
||||
RETURN_TYPES = ("LATENT",)
|
||||
FUNCTION = "op"
|
||||
|
||||
CATEGORY = "latent/advanced"
|
||||
|
||||
def op(self, samples, seed_behavior):
|
||||
samples_out = samples.copy()
|
||||
latent = samples["samples"]
|
||||
if seed_behavior == "random":
|
||||
if 'batch_index' in samples_out:
|
||||
samples_out.pop('batch_index')
|
||||
elif seed_behavior == "fixed":
|
||||
batch_number = samples_out.get("batch_index", [0])[0]
|
||||
samples_out["batch_index"] = [batch_number] * latent.shape[0]
|
||||
|
||||
return (samples_out,)
|
||||
|
||||
NODE_CLASS_MAPPINGS = {
|
||||
"LatentAdd": LatentAdd,
|
||||
"LatentSubtract": LatentSubtract,
|
||||
"LatentMultiply": LatentMultiply,
|
||||
"LatentInterpolate": LatentInterpolate,
|
||||
"LatentBatch": LatentBatch,
|
||||
"LatentBatchSeedBehavior": LatentBatchSeedBehavior,
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user