Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1a02da1aca | ||
|
|
b3b1804783 | ||
|
|
d3d76e6eeb | ||
|
|
a3298018b9 | ||
|
|
fc3f9351ca | ||
|
|
5081f25cf9 | ||
|
|
fe77b220fa | ||
|
|
82cdd4edf7 | ||
|
|
cdbba8bf6a | ||
|
|
8cd9baa94d | ||
|
|
fc3a628b95 | ||
|
|
d64b23d34b | ||
|
|
0437c456a3 | ||
|
|
0a7df88c9a | ||
|
|
70d18dfc43 | ||
|
|
6be16be3fd | ||
|
|
13cce62633 | ||
|
|
af2f84468b | ||
|
|
18f4e7d4f1 | ||
|
|
3f717ce2c4 | ||
|
|
0e4e2c06af | ||
|
|
59e40f8c6f | ||
|
|
8711f2efcc | ||
|
|
f249c445e7 | ||
|
|
20397b69c2 | ||
|
|
305ab2db29 | ||
|
|
078553f45f | ||
|
|
9f2c766050 | ||
|
|
b7db90fc84 | ||
|
|
0b5dc28154 | ||
|
|
2693d11275 | ||
|
|
75b9857672 | ||
|
|
0982fac3aa | ||
|
|
d3e24d1896 | ||
|
|
1aa08b814b | ||
|
|
c15bfae5b0 | ||
|
|
8818475fbb | ||
|
|
a05f4d7b14 | ||
|
|
7d210b53f4 | ||
|
|
4413d22e5e | ||
|
|
736275ef28 | ||
|
|
b783d6f770 | ||
|
|
ce2455672f | ||
|
|
a0f7ad3a7b | ||
|
|
3ec307880c | ||
|
|
11f0c1f3c1 | ||
|
|
d36cb04f91 | ||
|
|
e91b29defb | ||
|
|
c207f97e7c | ||
|
|
c5d81146df | ||
|
|
70d6b75853 | ||
|
|
885896aab6 | ||
|
|
cb136e6818 | ||
|
|
3884a8cb1b | ||
|
|
be0ba5284f | ||
|
|
7956f23d96 | ||
|
|
d9af505f39 | ||
|
|
3462257c23 | ||
|
|
243e449ae0 | ||
|
|
06f5859f60 | ||
|
|
928577a9bc | ||
|
|
97ff2fac7f | ||
|
|
006e8cb5d0 | ||
|
|
ccff96aad4 | ||
|
|
2ebe252045 | ||
|
|
5ac9fbb62c | ||
|
|
756943f0f2 | ||
|
|
34db35ad07 | ||
|
|
25d0a3ca8a | ||
|
|
228f2f414b | ||
|
|
96e0a52c25 | ||
|
|
31dde39e63 | ||
|
|
76e14d6f2a | ||
|
|
6987878ef0 | ||
|
|
a961ee2443 | ||
|
|
dd1fd21cce | ||
|
|
8308a22068 | ||
|
|
b3f64d1664 | ||
|
|
898238d833 | ||
|
|
79adf0f82c | ||
|
|
c9cfd25f96 | ||
|
|
d89fa084af | ||
|
|
d1a05e0d7a | ||
|
|
5854a35c95 | ||
|
|
2fa527bca3 | ||
|
|
9e11087466 | ||
|
|
dfeb6a73e4 | ||
|
|
f26fd2e6de | ||
|
|
d3af128043 | ||
|
|
7fda04154f | ||
|
|
9b773390a2 | ||
|
|
c2af417e61 | ||
|
|
42a27443b2 | ||
|
|
f6265de9c5 | ||
|
|
9b95e5b2fa | ||
|
|
2621d78607 | ||
|
|
e2d558b889 | ||
|
|
4bfde14aa8 | ||
|
|
e0e9ac790a | ||
|
|
ad1132acee | ||
|
|
5493722f39 | ||
|
|
aca74337be | ||
|
|
7f2301f813 | ||
|
|
c8315a6b20 | ||
|
|
96a2eca686 | ||
|
|
c7d18550ca | ||
|
|
0c8c98e655 | ||
|
|
b2f84ab8b5 | ||
|
|
5fa5506db4 | ||
|
|
ef5240e3d8 | ||
|
|
11be4840ce | ||
|
|
bc97746c3c | ||
|
|
d4cbefc986 | ||
|
|
0ed4e75ca9 | ||
|
|
016bf23a17 | ||
|
|
81d5b3f6a7 | ||
|
|
dfaae09506 | ||
|
|
f6dd7f2985 | ||
|
|
1f3a85dbdc | ||
|
|
0334e24c0c | ||
|
|
4fc7e245f6 | ||
|
|
1571b10043 | ||
|
|
61d64cc994 | ||
|
|
3723bf0ded | ||
|
|
a619d66179 | ||
|
|
82e71a2845 | ||
|
|
c7390e6750 | ||
|
|
fd557bdd1e | ||
|
|
4b7dbc141d | ||
|
|
99a2fae2ae | ||
|
|
44f9a2c26b | ||
|
|
6fe92bc7c6 | ||
|
|
75f2f81585 | ||
|
|
fec747cf94 | ||
|
|
c36ec00dd3 | ||
|
|
573ac457a3 | ||
|
|
0b116dfed9 | ||
|
|
1643fb3fb2 | ||
|
|
dad80da1dd | ||
|
|
0e42e60aa8 | ||
|
|
4b140e6512 | ||
|
|
b68e247675 | ||
|
|
7c5746c0bb | ||
|
|
0a1e1bc1f6 | ||
|
|
cd1e380901 | ||
|
|
da497db110 | ||
|
|
e8a222489d | ||
|
|
609b851359 | ||
|
|
544bc3f01a | ||
|
|
6ae1fb1c50 | ||
|
|
947a97d3b7 | ||
|
|
36fcbd48c8 | ||
|
|
b8bf4a8ea7 | ||
|
|
e2a9eed38f | ||
|
|
85fd06594c | ||
|
|
17bb87ebf4 | ||
|
|
0e5ebde862 | ||
|
|
ad9f747c5b | ||
|
|
58287392fb | ||
|
|
f0721e09c9 | ||
|
|
da5862c2f9 | ||
|
|
028b487d8a | ||
|
|
7c2788d777 | ||
|
|
ad833649b9 | ||
|
|
0accd11dc5 | ||
|
|
fc2d09304c | ||
|
|
1a47e37e5a | ||
|
|
cd28d6a9fe | ||
|
|
c613ed01cb | ||
|
|
8c3a57abf0 | ||
|
|
12fdc5e25c | ||
|
|
81514ee96f | ||
|
|
eed01cb41d | ||
|
|
9499a537d8 | ||
|
|
80a240efee | ||
|
|
5726132d70 | ||
|
|
2da943a58e | ||
|
|
87a9a9b54c | ||
|
|
cff12d66e0 | ||
|
|
e6fc81d1a3 | ||
|
|
adc6c36aec | ||
|
|
f81cff61df | ||
|
|
d98b65bcc2 | ||
|
|
a05ffef169 | ||
|
|
7df370d3a4 | ||
|
|
4cfaf36851 | ||
|
|
2d5d4188a3 | ||
|
|
10be52d481 | ||
|
|
49ce366a98 | ||
|
|
60c026ecc2 | ||
|
|
9595e09f1a | ||
|
|
0881ed9dc8 | ||
|
|
f970a12053 | ||
|
|
c5f58e4ece | ||
|
|
1ca1c2af54 | ||
|
|
5aa5d3c12f | ||
|
|
5c63ad12e1 | ||
|
|
ca9de242af | ||
|
|
c4d7e60600 | ||
|
|
354ac53664 | ||
|
|
db94472a27 | ||
|
|
085cba73ec | ||
|
|
e43dc78e33 | ||
|
|
943cb53ccb | ||
|
|
10deecb7b0 | ||
|
|
b91513dfe5 | ||
|
|
1393a83f0f | ||
|
|
d407e5af40 | ||
|
|
a4dd0a2e00 | ||
|
|
d29a90b146 | ||
|
|
d58bf02893 | ||
|
|
161861f06f | ||
|
|
d5b8e3ccdb | ||
|
|
75a14df5d6 | ||
|
|
9d6a2d3c1c | ||
|
|
cfdf30d0ee | ||
|
|
c17479029f | ||
|
|
8197f914da | ||
|
|
7fa46ca3b0 | ||
|
|
d3ff9e834c | ||
|
|
bd090bb40f | ||
|
|
67c6571b38 | ||
|
|
570d297ac9 | ||
|
|
d5b840450f | ||
|
|
96df6b6a53 | ||
|
|
7ce673175a | ||
|
|
f788a636a3 | ||
|
|
1e1d80f30c | ||
|
|
328a90ff04 | ||
|
|
4f3aeba493 | ||
|
|
b7f3f9e533 | ||
|
|
6a1832394c | ||
|
|
809ce6f0b4 | ||
|
|
f0663e3386 | ||
|
|
052d14b1a4 | ||
|
|
076a3aa5d7 | ||
|
|
d74304d0f2 | ||
|
|
0faaec395a | ||
|
|
c40f3e32b2 | ||
|
|
61681ddf5a | ||
|
|
0c5fed6780 | ||
|
|
bb9ec72418 | ||
|
|
29999e97dc | ||
|
|
f013d3d16c | ||
|
|
1c6b0b6c63 | ||
|
|
471b90c533 | ||
|
|
64a2f6358b | ||
|
|
5398b651ed | ||
|
|
3f97c5c75d | ||
|
|
9f49c6d054 | ||
|
|
9129653f40 | ||
|
|
bfe0fd2137 | ||
|
|
7f4e0e5384 | ||
|
|
1fb8bf7f05 | ||
|
|
a9ed8cae2f | ||
|
|
6d0d804a9e | ||
|
|
525e1643a1 | ||
|
|
ddc4bfd460 | ||
|
|
e9b9486bf1 | ||
|
|
d80c8c1156 | ||
|
|
ed030f1542 | ||
|
|
3cbaa6b0f3 | ||
|
|
800bb10835 | ||
|
|
1c32d60a56 | ||
|
|
512635f2c0 | ||
|
|
ca98326f3e | ||
|
|
e4e5b21b56 | ||
|
|
3d60b109bd | ||
|
|
83bca12a75 | ||
|
|
18e9221aa2 | ||
|
|
dc67e74789 | ||
|
|
138a951b2d | ||
|
|
6e3daba1ed | ||
|
|
4e1cd2c872 | ||
|
|
d3bc62cbb9 | ||
|
|
bc52d51900 | ||
|
|
4bc2b67a85 | ||
|
|
3e7560a565 | ||
|
|
a75ed2ff1a | ||
|
|
9f783b2f26 | ||
|
|
9010056bee | ||
|
|
ccf142bd78 | ||
|
|
8d1ff510b9 | ||
|
|
9db29fbb28 | ||
|
|
b2598fef93 | ||
|
|
48d5380ec5 | ||
|
|
0d22efc8b1 | ||
|
|
62475fdd65 | ||
|
|
ebbc837629 | ||
|
|
ada7688deb | ||
|
|
79c403bd15 | ||
|
|
53fa16dd70 | ||
|
|
fa018e6e4f | ||
|
|
edb7908328 | ||
|
|
1037a88b40 | ||
|
|
e30e253957 | ||
|
|
4f20ab8d1d | ||
|
|
84df61f696 | ||
|
|
4021d3e617 | ||
|
|
0f76196dda | ||
|
|
891634a912 | ||
|
|
412ea5e89b | ||
|
|
0a8b87d574 | ||
|
|
f0e13f430c | ||
|
|
e3676d681f | ||
|
|
9715cd273f | ||
|
|
e1f37d25e3 | ||
|
|
0604551199 | ||
|
|
9b6c10b664 | ||
|
|
7cd8e0362e | ||
|
|
f57f224fd6 | ||
|
|
a2aa2601d5 | ||
|
|
5dabd12c9a | ||
|
|
0817e50109 | ||
|
|
04906ee2cb | ||
|
|
4a265c4768 | ||
|
|
f05771b9b6 | ||
|
|
588a74cfeb | ||
|
|
93209bf261 | ||
|
|
d766172383 | ||
|
|
0d65516cbb | ||
|
|
2a0fa503c9 | ||
|
|
9a9274f443 | ||
|
|
8c2eab8626 | ||
|
|
2ae4674336 | ||
|
|
3d9e37b602 | ||
|
|
0ba8c8cf26 | ||
|
|
5f6f648636 | ||
|
|
4bdbdd7bde | ||
|
|
7c20d7d23e | ||
|
|
5072b191b7 | ||
|
|
f73a472324 | ||
|
|
4aeb009129 | ||
|
|
d4cd834fb8 | ||
|
|
de65293966 | ||
|
|
0010d31995 | ||
|
|
b5f7ca83d0 | ||
|
|
7c5f644349 | ||
|
|
4935791ebb | ||
|
|
6122ca4b0c | ||
|
|
ebdaefbe14 | ||
|
|
fb3e41e54f | ||
|
|
b8b98cb758 | ||
|
|
74e5859366 | ||
|
|
29ecec9a9e | ||
|
|
ed2781d125 | ||
|
|
0c1d3f52d6 | ||
|
|
8ffc9a394c | ||
|
|
f5e8c3ba8c | ||
|
|
38e795eb35 | ||
|
|
b06e26b82e | ||
|
|
7e637da47f | ||
|
|
e01a74d6ad | ||
|
|
9d0f87e43f | ||
|
|
d53d725fcc | ||
|
|
a1d1ce8c03 | ||
|
|
881a18d392 | ||
|
|
eae6b03f25 | ||
|
|
ac0c2f43d3 | ||
|
|
34eb4837cd | ||
|
|
e501753407 | ||
|
|
93c4559bfd | ||
|
|
c4b270bc96 | ||
|
|
3467cf0663 | ||
|
|
4c5ef98571 | ||
|
|
2ee4dfd507 | ||
|
|
270a539d91 | ||
|
|
ce0c620a36 | ||
|
|
44e5bcc0e2 | ||
|
|
c537f72d63 | ||
|
|
ef4c0c6f78 | ||
|
|
a84a3b16e0 | ||
|
|
e13e3ac013 | ||
|
|
9e2e4d4db6 | ||
|
|
db8000d51e | ||
|
|
a84c4f3048 | ||
|
|
03dcb7c424 | ||
|
|
e2bf6f1a20 | ||
|
|
e7d913ccd0 | ||
|
|
13e5b4e800 | ||
|
|
769c6a47a7 | ||
|
|
102902ec68 | ||
|
|
b7d0c793c0 | ||
|
|
0d8cc9c2fe | ||
|
|
4ac582c045 | ||
|
|
5a801ecbd2 | ||
|
|
380021fdf1 | ||
|
|
c3b7822ad2 | ||
|
|
5a8466b4df | ||
|
|
5574fe6627 | ||
|
|
a395bbf3b4 | ||
|
|
f6413f4202 | ||
|
|
bf3d12fc73 | ||
|
|
1a80777aac | ||
|
|
2794623194 | ||
|
|
c7237b36fd | ||
|
|
c5b53df261 | ||
|
|
9e3e21da4c | ||
|
|
4a0011514e | ||
|
|
5d634ecaf4 | ||
|
|
3b53a30591 | ||
|
|
3bd8700201 | ||
|
|
0fcb7bf7bd | ||
|
|
c61aec2faf | ||
|
|
a870ca0689 | ||
|
|
a13eb5a965 | ||
|
|
25ee8cc1c5 | ||
|
|
cc10c1eb8e | ||
|
|
d3921204ae | ||
|
|
947393c6cf | ||
|
|
63a0c22da3 | ||
|
|
1294bfe15e | ||
|
|
866666060c | ||
|
|
e05dfe289f | ||
|
|
cdd3a4992d | ||
|
|
2e87cd861a | ||
|
|
8783642393 | ||
|
|
c1024002f4 | ||
|
|
6631495bbe | ||
|
|
8bf3329e92 | ||
|
|
1dbd3f076d | ||
|
|
86ed9640cb | ||
|
|
b5c8541a48 | ||
|
|
a601dae138 | ||
|
|
ce8af0532c | ||
|
|
e1e0eb5e0f | ||
|
|
83d149f950 | ||
|
|
01c9a9a3c2 | ||
|
|
15af4a4230 | ||
|
|
27079a0394 | ||
|
|
b83e06e83f | ||
|
|
b36460e477 | ||
|
|
b59937d351 | ||
|
|
8e0b1c65f5 | ||
|
|
a4b9abfc61 | ||
|
|
85d10e0b38 | ||
|
|
14f6fff9ea | ||
|
|
ceafc51aa3 | ||
|
|
84744429e0 | ||
|
|
8d5709ba6f | ||
|
|
c733e4ab93 | ||
|
|
1c8831f757 | ||
|
|
101e783e1e | ||
|
|
26f4689210 | ||
|
|
77f006ad0a | ||
|
|
6335071950 | ||
|
|
22cc4ea1b4 | ||
|
|
d04d386a16 | ||
|
|
4d3cc9d6ee | ||
|
|
bf5d0c73de | ||
|
|
5bfa6997c9 | ||
|
|
c9cd557666 | ||
|
|
2cd69ce5fd | ||
|
|
cf02031ba2 | ||
|
|
81a17c9352 | ||
|
|
1b6b35f5d6 | ||
|
|
43c3d060b0 | ||
|
|
2db7fcc004 | ||
|
|
4e744a8e82 | ||
|
|
08cb017e57 | ||
|
|
fcc24fca04 | ||
|
|
7797c40c4d | ||
|
|
9e55ed1209 | ||
|
|
ec927d7e66 | ||
|
|
bf164d0c6a | ||
|
|
d3ed28041a | ||
|
|
e3cc2873a4 | ||
|
|
52a0874b0a | ||
|
|
77f6979b7b | ||
|
|
c519cb1dc9 | ||
|
|
f4edef96cc | ||
|
|
1935b80212 | ||
|
|
e6b5af1dae | ||
|
|
48cf40ce16 | ||
|
|
fc7636fb3f | ||
|
|
df7109cf67 | ||
|
|
a8e5651067 | ||
|
|
b970d0db47 | ||
|
|
6df71eea84 | ||
|
|
d9cae971d7 | ||
|
|
6e275722f2 | ||
|
|
f76b77c584 | ||
|
|
f273d74ae6 | ||
|
|
01cbdf2e26 | ||
|
|
f4c5af00b4 | ||
|
|
bc331154bb | ||
|
|
5d2afd3de6 | ||
|
|
9a59708688 | ||
|
|
251f9e74cd | ||
|
|
7347b605e2 | ||
|
|
69efc6f0c7 | ||
|
|
3c948c9ddd | ||
|
|
c978412177 | ||
|
|
bbdc7c5ec7 | ||
|
|
9a34eb0ca2 | ||
|
|
16fc9a0258 | ||
|
|
b19f582ea5 | ||
|
|
acc2fbfc79 | ||
|
|
a9322ab055 | ||
|
|
47f4d1309d | ||
|
|
744931c6e3 | ||
|
|
1ca839e0d9 | ||
|
|
9ecd60554e | ||
|
|
c930fbe6eb | ||
|
|
458901bb2a | ||
|
|
e44fd922d4 | ||
|
|
ba752c4b55 | ||
|
|
be29ad7f67 | ||
|
|
7fad03daf1 | ||
|
|
6756e43749 | ||
|
|
d8c2e90b43 | ||
|
|
8573bcf25a | ||
|
|
10074824d2 | ||
|
|
1843ee3cc3 | ||
|
|
3a55fbe33a | ||
|
|
7ec9b9c92e | ||
|
|
7f0a05e035 | ||
|
|
8e2c3328ff | ||
|
|
1331396a65 | ||
|
|
f2eff3d494 | ||
|
|
6cd7f4375b | ||
|
|
1526bed627 | ||
|
|
429b093d5d | ||
|
|
0876875c59 | ||
|
|
967188b681 | ||
|
|
ab518d30cc | ||
|
|
77d283cb6a | ||
|
|
ae7d6592c1 | ||
|
|
5b04fac197 | ||
|
|
3d0e9adf61 | ||
|
|
21b87535b1 | ||
|
|
d46d85b237 | ||
|
|
ebcb022cb4 | ||
|
|
f04c2320ed | ||
|
|
ef2650c48c | ||
|
|
10d3e5a02d | ||
|
|
589445b32d | ||
|
|
ac027e2cac | ||
|
|
fe771feda3 | ||
|
|
561a567c15 | ||
|
|
d82e74c3bb | ||
|
|
d418e8eec3 | ||
|
|
ea8d26e534 | ||
|
|
ccb533536a | ||
|
|
11b5ffcc3a | ||
|
|
4abd03eb94 | ||
|
|
fce5f2748d | ||
|
|
00dc764a7c | ||
|
|
bffdac5c47 | ||
|
|
38d8d4c5b4 | ||
|
|
6595a3e53b | ||
|
|
46294b9571 | ||
|
|
c30fdc2286 | ||
|
|
729aaf488a | ||
|
|
45e2619033 | ||
|
|
f10b53c9b6 | ||
|
|
dfbf0f6f2c | ||
|
|
d419585fcd | ||
|
|
e673ba4349 | ||
|
|
fa8c415eb9 | ||
|
|
b1c96340da | ||
|
|
3f47fc5916 | ||
|
|
e4faaded4b | ||
|
|
bf77b39f4b | ||
|
|
670df99a5f | ||
|
|
ec2714aa78 | ||
|
|
a297c002c3 | ||
|
|
f6205ba530 | ||
|
|
1415f1a18e | ||
|
|
8ed56e8f44 | ||
|
|
cf0af6f241 | ||
|
|
4434f9432e | ||
|
|
439a1947cd | ||
|
|
ec7a74ca72 | ||
|
|
8c9e054602 | ||
|
|
6fb30e197d | ||
|
|
c272899e64 | ||
|
|
747c08e457 | ||
|
|
cad71dd6e6 | ||
|
|
7d5893b935 | ||
|
|
7d3ceb5009 | ||
|
|
750797d06d | ||
|
|
853bc12ef1 | ||
|
|
f3d34b3146 | ||
|
|
ad9fff5298 | ||
|
|
9581e9ac0c | ||
|
|
249fa66d94 | ||
|
|
45d513541b | ||
|
|
7016f19ae9 | ||
|
|
cf62329b8c | ||
|
|
5f11e40a4b | ||
|
|
93cca1f399 | ||
|
|
40a2a19564 | ||
|
|
367d960309 | ||
|
|
c24feebac1 | ||
|
|
74ef4d9743 | ||
|
|
e609fb6ca7 | ||
|
|
2799c6257e | ||
|
|
f04f76f1ea | ||
|
|
95287f3c89 | ||
|
|
bb579b21ad | ||
|
|
441987b5f5 | ||
|
|
85fc329364 |
@@ -2,7 +2,7 @@
|
||||
* Python version:
|
||||
* Operating System:
|
||||
|
||||
### Description
|
||||
### tooltip
|
||||
|
||||
Describe what you were trying to get done.
|
||||
Tell us what happened, what went wrong, and what you expected to happen.
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
name: "{{ cookiecutter.project_slug }} CI build"
|
||||
# GitHub CI build pipeline
|
||||
name: CI build
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- main
|
||||
jobs:
|
||||
build:
|
||||
runs-on: "{% raw %} ${{ matrix.os }} {% endraw %}"
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
PYTHONIOENCODING: "utf8"
|
||||
strategy:
|
||||
@@ -17,17 +19,66 @@ jobs:
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Backup pyproject.toml
|
||||
run: cp pyproject.toml pyproject.toml.ci.bak
|
||||
|
||||
- name: Strip tooltip entries from pyproject.toml for CI
|
||||
run: |
|
||||
# Remove `tooltip = ...` from [project] and drop [project.tooltip] section if present.
|
||||
python - <<'PY'
|
||||
from pathlib import Path
|
||||
p = Path('pyproject.toml')
|
||||
text = p.read_text()
|
||||
out_lines = []
|
||||
cur_section = None
|
||||
skip_section = False
|
||||
for line in text.splitlines():
|
||||
s = line.strip()
|
||||
if s.startswith('[') and s.endswith(']'):
|
||||
cur_section = s.strip('[]')
|
||||
# skip any subsection that starts with project.tooltip (or exact match)
|
||||
if cur_section == 'project.tooltip' or cur_section.startswith('project.tooltip'):
|
||||
skip_section = True
|
||||
continue
|
||||
else:
|
||||
skip_section = False
|
||||
if skip_section:
|
||||
continue
|
||||
# drop top-level tooltip = ... when inside [project]
|
||||
if cur_section == 'project' and s and s.split('=')[0].strip() == 'tooltip':
|
||||
continue
|
||||
out_lines.append(line)
|
||||
p.write_text('\n'.join(out_lines) + '\n')
|
||||
PY
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "{% raw %} ${{ matrix.python-version }} {% endraw %}"
|
||||
python-version: 3.12
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install .[dev]
|
||||
pip install -r requirements.txt
|
||||
|
||||
- name: Clone ComfyUI dependency
|
||||
run: |
|
||||
git clone --depth 1 https://github.com/comfyanonymous/ComfyUI.git ../ComfyUI-repo
|
||||
cp -r ../ComfyUI-repo/* ../
|
||||
python -m pip install -r ../ComfyUI-repo/requirements.txt
|
||||
|
||||
- name: Run Linting
|
||||
run: |
|
||||
ruff check .
|
||||
|
||||
- name: Run Tests
|
||||
env:
|
||||
PYTHONPATH: .
|
||||
run: |
|
||||
pytest tests/
|
||||
|
||||
- name: Restore original pyproject.toml
|
||||
if: always()
|
||||
run: mv pyproject.toml.ci.bak pyproject.toml
|
||||
|
||||
@@ -116,3 +116,7 @@ node.zip
|
||||
/.vs
|
||||
*.editorconfig
|
||||
*.yaml
|
||||
!.pre-commit-config.yaml
|
||||
/more_math.pyproj
|
||||
/more_math.slnx
|
||||
/more_math/Parser/.antlr
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
repos:
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.8.4
|
||||
hooks:
|
||||
# Run the linter
|
||||
- id: ruff
|
||||
args: [--fix]
|
||||
# Run the formatter
|
||||
- id: ruff-format
|
||||
@@ -0,0 +1,212 @@
|
||||
# Function Consistency Check: README.md vs MathExpr.g4
|
||||
|
||||
## Executive Summary
|
||||
✅ **All documented functions have grammar rules defined**
|
||||
✅ **All grammar functions have documentation**
|
||||
⚠️ **Minor discrepancies in aliases and function naming**
|
||||
|
||||
## Functions in README but Need Verification in Grammar
|
||||
|
||||
### ✅ Verified - All Present
|
||||
|
||||
#### Basic Math (All Present)
|
||||
- ✅ abs (AbsFunc)
|
||||
- ✅ sqrt (SqrtFunc)
|
||||
- ✅ ln (LnFunc)
|
||||
- ✅ log (LogFunc)
|
||||
- ✅ exp (ExpFunc)
|
||||
- ✅ pow (PowFunc as POWE)
|
||||
- ✅ floor (FloorFunc)
|
||||
- ✅ ceil (CeilFunc)
|
||||
- ✅ round (RoundFunc)
|
||||
- ✅ fract (FractFunc)
|
||||
- ✅ sign (SignFunc)
|
||||
- ✅ gamma (GammaFunc)
|
||||
- ✅ dist/distance (DistFunc)
|
||||
- ✅ clamp (ClampFunc)
|
||||
- ✅ step (StepFunc)
|
||||
|
||||
#### Trigonometric (All Present)
|
||||
- ✅ sin, cos, tan, asin, acos, atan, atan2 (all present)
|
||||
|
||||
#### Hyperbolic (All Present)
|
||||
- ✅ sinh, cosh, tanh, asinh, acosh, atanh (all present)
|
||||
|
||||
#### Machine Learning / Activation (All Present)
|
||||
- ✅ relu (ReluFunc)
|
||||
- ✅ gelu (GeluFunc)
|
||||
- ✅ softplus (SoftplusFunc)
|
||||
- ✅ sigm (SigmoidFunc as SIGM)
|
||||
- ✅ softmax (SoftmaxFunc)
|
||||
- ✅ softmin (SoftminFunc)
|
||||
|
||||
#### Interpolation (All Present)
|
||||
- ✅ smoothstep (SmoothstepFunc)
|
||||
- ✅ smootherstep (SmootherstepFunc)
|
||||
- ✅ cubic_ease/cubic (CubicEaseFunc)
|
||||
- ✅ sine_ease/sine (SineEaseFunc)
|
||||
- ✅ elastic_ease/elastic (ElasticEaseFunc)
|
||||
- ✅ lerp (LerpFunc)
|
||||
|
||||
#### Aggregates & Tensor Operations (All Present)
|
||||
- ✅ tmin (TMinFunc)
|
||||
- ✅ tmax (TMaxFunc)
|
||||
- ✅ smin (SMinFunc)
|
||||
- ✅ smax (SMaxFunc)
|
||||
- ✅ sum (SumFunc)
|
||||
- ✅ mean (MeanFunc)
|
||||
- ✅ std (StdFunc)
|
||||
- ✅ var (VarFunc)
|
||||
- ✅ median (MedianFunc)
|
||||
- ✅ mode (ModeFunc)
|
||||
- ✅ quartile (QuartileFunc)
|
||||
- ✅ percentile (PercentileFunc)
|
||||
- ✅ quantile (QuantileFunc)
|
||||
- ✅ dot (DotFunc)
|
||||
- ✅ moment (MomentFunc)
|
||||
- ✅ topk (TopkFunc)
|
||||
- ✅ botk (BotkFunc)
|
||||
- ✅ topk_ind/topk_indices (TopkIndFunc)
|
||||
- ✅ botk_ind/botk_indices (BotkIndFunc)
|
||||
- ✅ sort (SortFunc)
|
||||
- ✅ argsort (ArgsortFunc)
|
||||
- ✅ argmin (ArgminFunc)
|
||||
- ✅ argmax (ArgmaxFunc)
|
||||
- ✅ unique (UniqueFunc)
|
||||
- ✅ tnorm (TNormFunc)
|
||||
- ✅ snorm (SNormFunc)
|
||||
- ✅ swap (SwapFunc)
|
||||
- ✅ cossim (CossimFunc)
|
||||
- ✅ flip (FlipFunc)
|
||||
- ✅ cov (CovFunc)
|
||||
- ✅ append (AppendFunc)
|
||||
- ✅ any (AnyFunc)
|
||||
- ✅ all (AllFunc)
|
||||
- ✅ cumsum (CumsumFunc)
|
||||
- ✅ cumprod (CumprodFunc)
|
||||
- ✅ tensor (EmptyTensorFunc)
|
||||
- ✅ flatten (FlattenFunc)
|
||||
|
||||
#### Advanced Tensor Operations (All Present)
|
||||
- ✅ map (MapFunc)
|
||||
- ✅ ezconvolution/ezconv (EzConvFunc)
|
||||
- ✅ convolution/conv (ConvFunc)
|
||||
- ✅ get_value (GetValueFunc)
|
||||
- ✅ crop (CropFunc)
|
||||
- ✅ permute/perm (PermuteFunc)
|
||||
- ✅ reshape/rshp (ReshapeFunc)
|
||||
- ✅ blur/gaussian (GaussianFunc)
|
||||
- ✅ edge (EdgeFunc)
|
||||
- ✅ batch_shuffle/shuffle/select (BatchShuffleFunc)
|
||||
- ✅ matmul (MatmulFunc)
|
||||
- ✅ cross (CrossFunc)
|
||||
|
||||
#### Optical Flow (All Present)
|
||||
- ✅ rife (RifeFunc)
|
||||
- ✅ motion_mask (MotionMaskFunc)
|
||||
- ✅ flow_to_image (FlowToImageFunc)
|
||||
- ✅ flow_apply (FlowApplyFunc)
|
||||
|
||||
#### FFT (All Present)
|
||||
- ✅ fft (SFFT)
|
||||
- ✅ ifft (SIFFT)
|
||||
- ✅ angle (ANGL)
|
||||
|
||||
#### Utility (All Present)
|
||||
- ✅ print (PrintFunc as PRNT)
|
||||
- ✅ print_shape/pshp (PrintShapeFunc)
|
||||
- ✅ pinv (PinvFunc)
|
||||
- ✅ range (RangeFunc)
|
||||
- ✅ nan_to_num/nvl (NvlFunc)
|
||||
- ✅ remap (RemapFunc)
|
||||
- ✅ timestamp (TimestampFunc as TIMESTAMP)
|
||||
- ✅ count/length/cnt (CountFunc)
|
||||
|
||||
#### Random Distributions (All Present)
|
||||
- ✅ random_normal/randn/noise (NoiseFunc)
|
||||
- ✅ random_uniform/rand (RandFunc)
|
||||
- ✅ random_exponential/rande (ExponentialFunc)
|
||||
- ✅ random_cauchy/randc (CauchyFunc)
|
||||
- ✅ random_log_normal/randln (LogNormalFunc)
|
||||
- ✅ random_bernoulli/randb (BernoulliFunc)
|
||||
- ✅ random_poisson/randp (PoissonFunc)
|
||||
- ✅ random_gamma/randg (GammaDistFunc)
|
||||
- ✅ random_beta/randbeta (BetaDistFunc)
|
||||
- ✅ random_laplace/randl (LaplaceDistFunc)
|
||||
- ✅ random_gumbel/randgumbel (GumbelDistFunc)
|
||||
- ✅ random_weibull/randw (WeibullDistFunc)
|
||||
- ✅ random_chi2/randchi2 (Chi2DistFunc)
|
||||
- ✅ random_studentt/randt (StudentTDistFunc)
|
||||
|
||||
#### Bitwise Operations (All Present)
|
||||
- ✅ band/bitwise_and (BitAndFunc)
|
||||
- ✅ bor/bitwise_or (BitOrFunc)
|
||||
- ✅ xor/bitwise_xor (BitXorFunc)
|
||||
- ✅ bnot/bitwise_not (BitNotFunc)
|
||||
- ✅ bitcount/popcount/popcnt (BitCountFunc)
|
||||
- ✅ << (LSHIFT)
|
||||
- ✅ >> (RSHIFT)
|
||||
|
||||
#### Stack (All Present)
|
||||
- ✅ stack_push (PushFunc)
|
||||
- ✅ stack_pop (PopFunc)
|
||||
- ✅ stack_get (GetFunc)
|
||||
- ✅ stack_clear (ClearFunc)
|
||||
- ✅ stack_has (HasFunc)
|
||||
|
||||
## Minor Naming Discrepancies
|
||||
|
||||
### README Mentions These Aliases Not Explicitly in Grammar Lexer
|
||||
1. `now` - README mentions as alias for `timestamp`, but only TIMESTAMP: 'timestamp' is in grammar
|
||||
- **Fix Needed**: Add `TIMESTAMP: 'timestamp' | 'now';`
|
||||
|
||||
2. `flow_view` - README mentions as alias for `flow_to_image`
|
||||
- **Fix Needed**: Add to `FLOW_TO_IMAGE: 'flow_to_image' | 'flow_view';`
|
||||
|
||||
3. `apply_flow` - README mentions as alias for `flow_apply`
|
||||
- **Fix Needed**: Add to `FLOW_APPLY: 'flow_apply' | 'apply_flow';`
|
||||
|
||||
4. Bitwise aliases - Should update for consistency:
|
||||
- `BAND: 'band' | 'bitwise_and' | 'bitand';` (currently has 'bitand' as alias)
|
||||
- `BOR: 'bor' | 'bitwise_or' | 'bitor';` (currently has 'bitor' as alias)
|
||||
- `XOR: 'xor' | 'bitwise_xor' | 'xor';` (should add 'bitwise_xor')
|
||||
- `BNOT: 'bnot' | 'bitnot' | 'bitwise_not';` (should add 'bitwise_not')
|
||||
|
||||
## Summary of Issues
|
||||
|
||||
### Critical (Must Fix)
|
||||
None - all documented functions work
|
||||
|
||||
### High Priority (Should Fix)
|
||||
1. Add missing aliases for consistency:
|
||||
- `now` for `timestamp`
|
||||
- `flow_view` for `flow_to_image`
|
||||
- `apply_flow` for `flow_apply`
|
||||
- `bitwise_*` names for bitwise operators
|
||||
|
||||
### Low Priority (Nice to Have)
|
||||
- Documentation in README could list all aliases explicitly
|
||||
|
||||
## Recommended Grammar Updates
|
||||
|
||||
```antlr
|
||||
// Update these lexer rules:
|
||||
TIMESTAMP: 'timestamp' | 'now';
|
||||
FLOW_APPLY: 'flow_apply' | 'apply_flow';
|
||||
FLOW_TO_IMAGE: 'flow_to_image' | 'flow_view';
|
||||
BAND: 'band' | 'bitand' | 'bitwise_and';
|
||||
BOR: 'bor' | 'bitor' | 'bitwise_or';
|
||||
XOR: 'xor' | 'bitwise_xor';
|
||||
BNOT: 'bnot' | 'bitnot' | 'bitwise_not';
|
||||
BITCOUNT: 'bitcount' | 'popcount' | 'popcnt' | 'bitcount';
|
||||
```
|
||||
|
||||
## Conclusion
|
||||
|
||||
✅ **Status: EXCELLENT CONSISTENCY**
|
||||
- 100% of documented functions have grammar rules
|
||||
- 100% of grammar functions have documentation
|
||||
- Only minor alias discrepancies that don't affect functionality
|
||||
- All core functionality is properly defined and documented
|
||||
|
||||
**Recommendation**: Regenerate parser after applying the minor grammar updates above to ensure all documented aliases work correctly.
|
||||
@@ -1,43 +1,556 @@
|
||||
# More Math
|
||||
|
||||
Adds math nodes for numbers and types which do not need it. I got inspired by was_extras node.
|
||||
Adds math nodes for numbers and types which do not need it. Inspired by the `was_extras` node.
|
||||
|
||||
#WARNING This node is not compatible to ComfyUI-Impact-Pack which forces older antlr version.
|
||||
There is workflow folder containing examples. It will expand.
|
||||
|
||||
The most important thing is that last part of input must be an expression. Node uses it to output result. It can be constant, function call, variable or mixture of these. No loops, branching using if. Branches, loops, ... can be before the final expression.
|
||||
|
||||
## Quickstart
|
||||
|
||||
1. Install [ComfyUI](https://docs.comfy.org/get_started).
|
||||
1. Clone this repository into `ComfyUI/custom_nodes`.
|
||||
1. open command prompt/terminal/bash in your comfy folder
|
||||
1. activate environment `./venv/Scripts/activate`
|
||||
1. go to more_math folder `cd ./custom_nodes/more_math/`
|
||||
1. install requirements `pip install -r requirements.txt`
|
||||
1. Restart ComfyUI.
|
||||
|
||||
You can also get the node from comfy manager under the name of More math.
|
||||
|
||||
# Features
|
||||
## Features
|
||||
|
||||
- expression parsing with support for functions and variables:
|
||||
- functions:
|
||||
- run expressions on both components of CONDITIONING (not that I know what they do), LATENT, IMAGE, NOISE and FLOAT
|
||||
- math operators (binary and unary +,- and ^,*,/,%)
|
||||
- boolean operators (false = 0.0; true = 1.0) (<,<=,>,>=,==,!=)
|
||||
- sin, cos, tan
|
||||
- asin, acos, atan (can produce NaN outside range -1...1)
|
||||
- atan2 (x,y)
|
||||
- sinh, cosh, tanh
|
||||
- asinh, acosh, atanh (can produce NaN outside range -1...1)
|
||||
- abs, sqrt (produces NaN on negative numbers), ln (log with base e), log (base 10), exp, pow
|
||||
- smin, tmin, smax, tmax, tnorm, snorm (norm and t prefixed are not applicable on float math node)
|
||||
- s prefixed return one number
|
||||
- t prefixed work element-wise
|
||||
- floor, ceil, round, gamma, clamp, sigm (sigmoid)
|
||||
- angle (audio node)
|
||||
- variables
|
||||
- a, b, c, d : inputs of type based on node
|
||||
- w, x, y, z : each node allows use of 4 additional floats
|
||||
- W : Width of tensor - does not work on FLOAT and CONDITIONING
|
||||
- H : Height of tensor - does not work on FLOAT and CONDITIONING
|
||||
- C : Channel of tensor - used as color chanel in IMAGE or as information channel in others. does not work on FLOAT and CONDITIONING
|
||||
- B : Batch - does not work on FLOAT and CONDITIONING
|
||||
- X : X position in tensor - does not work on FLOAT and CONDITIONING
|
||||
- Y : Y position in tensor - does not work on FLOAT and CONDITIONING
|
||||
- I : Tensor used to initalise sampler before noise injection - usually zeroed out and generated by Empty Latent Image node - Exclusive for NOISE
|
||||
- use constant e and pi
|
||||
- Functions and variables in math expressions.
|
||||
- Conversion between `INT` and `FLOAT`, `INT` and `BOOLEAN`, and `AUDIO` and `IMAGE`
|
||||
- image/audio conversion encodes frequency content into RGB channels.
|
||||
- Node support for `FLOAT`, `STRING`, `CONDITIONING`, `LATENT`, `IMAGE`, `MASK`, `NOISE`, `AUDIO`, `VIDEO`, `MODEL`, `CLIP`, `VAE`, `SIGMAS`, and `GUIDER`.
|
||||
- Vector math with list literals `[v1, v2, ...]` and mixed operations between lists, scalars, and tensors.
|
||||
- Custom functions `funcname(variable, variable, ...)->expression;`
|
||||
- can be used later in expressions and in other custom functions,
|
||||
- shadowing built-in functions is not supported,
|
||||
- recursion is allowed, so be careful.
|
||||
- Custom variables `varname=expression;`
|
||||
- can be used later in the same expression chain,
|
||||
- compound assignments are supported: `+=`, `-=`, `*=`, `/=`, `%=`.
|
||||
- Indexed assignment `a[i, j, ...] = expression;`
|
||||
- supports multidimensional tensors and nested lists,
|
||||
- scalar values and 1-element tensors/lists fill the selected slice,
|
||||
- leading singleton dimensions are squeezed when needed to match target rank.
|
||||
- python-like slicing
|
||||
- `a[start:end]` or `a[start:end:step]` for 1D slices,
|
||||
- `a[start1:end1, start2:end2, ...]` for N-D slices (step can also be specified),
|
||||
- negative indices count from the end of the dimension.
|
||||
- json-like dictionary literals `{key1: value1, key2: value2, ...}` for structured data. Keys can be strings or numbers or variables (variable will be evaluated to its value), values can be any expression. Indexing using [].
|
||||
- Control flow:
|
||||
- `if/else`
|
||||
- `while`
|
||||
- `for`
|
||||
- blocks `{ ... }`
|
||||
- `return`
|
||||
- `break`
|
||||
- `continue`
|
||||
- Stack support shared across evaluations through stack connections.
|
||||
- useful for `GuiderMath` state between steps.
|
||||
- Comments:
|
||||
- line comments `# ...`
|
||||
- block comments `/* ... */`
|
||||
- When a tensor is expected, lists can often be used instead and will be promoted automatically.
|
||||
|
||||
### Control Flow Statements
|
||||
|
||||
- **If/Else**: `if (condition) statement [else statement]`
|
||||
- **While Loops**: `while (condition) statement`
|
||||
- **Blocks**: `{ statement1; statement2; ... }`
|
||||
- New variables defined in blocks are isolated and don't leak to outer scope
|
||||
- Modifications to existing variables persist to outer scope
|
||||
- **Return Statements**: `return [expression];`
|
||||
- Early return from functions or top-level expressions
|
||||
- **For Loops**: `for (variable in expression) statement`
|
||||
- Iterates over list elements
|
||||
- Iterates over tensor elements along dimension `0`
|
||||
- Scalars are treated as one-item iterables
|
||||
- **Break/Continue**: `break;`, `continue;`
|
||||
- Control loop execution (works in `while` and `for` loops)
|
||||
|
||||
## Operators
|
||||
|
||||
- Math: `+`, `-`, `*`, `/`, `%`, `^`, `|x|` (abs / norm-style magnitude)
|
||||
- Assignment: `=`, `+=`, `-=`, `*=`, `/=`, `%=`
|
||||
- also works on indexed targets, for example `a[i] += 1;` or `a[i, j] = expression;`.
|
||||
- Boolean: `<`, `<=`, `>`, `>=`, `==`, `!=`
|
||||
(`false = 0.0`, `true = 1.0`)
|
||||
- Ternary: `condition ? a : b`
|
||||
- Constants: `pi`/`PI`, `e`/`E` (case-insensitive).
|
||||
- `none` / `None` / `null` / `NULL`: empty/no-value literal.
|
||||
- Bitwise shifts: `<<`, `>>`
|
||||
- Indexing: `x[i]` or `x[i, j, ...]`
|
||||
- works on tensors, nested lists, and strings,
|
||||
- supports scalar, tensor, and list indices.
|
||||
- Lists: `[v1, v2, ...]`
|
||||
- vector math is supported,
|
||||
- lists can broadcast across tensor operations in many cases,
|
||||
- lists can be used for color triples, coordinates, kernel sizes, and permutations.
|
||||
- **Length Mismatch Handling**: All math nodes (except Model, Clip, Vae which default to broadcast) include a `length_mismatch` option to handle inputs with different batch sizes, sample counts, or list lengths. The target length is determined by the **maximum length** among all provided inputs (`a`, `b`, `c`, `d`).
|
||||
- `do nothing`: dones **no validation** on input
|
||||
- `tile`: Repeats shorter inputs to match the maximum length.
|
||||
- `error` (Default): Raises a `ValueError` if any input lengths differ.
|
||||
- `pad`: Shorter inputs are padded with zeros to match the maximum length.
|
||||
|
||||
## Functions
|
||||
|
||||
### 1) Core Math
|
||||
|
||||
#### 1.1 Elementary & Numeric
|
||||
- `abs(x)` or `|x|`: absolute value; tensor/list inputs are handled element-wise.
|
||||
- `sqrt(x)`: square root.
|
||||
- `ln(x)`: natural logarithm.
|
||||
- `log(x)`: base-10 logarithm.
|
||||
- `exp(x)`: exponential `e^x`.
|
||||
- `pow(x, y)`: power `x^y`.
|
||||
- `floor(x)`: round down.
|
||||
- `ceil(x)`: round up.
|
||||
- `round(x)`: round to nearest integer.
|
||||
- `fract(x)`: fractional part (`x - floor(x)`).
|
||||
- `sign(x)`: sign (`-1`, `0`, `1`).
|
||||
- `gamma(x)`: gamma function.
|
||||
- `clamp(x, min, max)`: clamp to interval.
|
||||
- `step(x, edge)`: returns `1` when `x >= edge`, otherwise `0`.
|
||||
- `dist(x1, y1, x2, y2)` / `distance`: Euclidean distance between 2D points.
|
||||
|
||||
#### 1.2 Trigonometric
|
||||
- `sin(x)`: sine in radians.
|
||||
- `cos(x)`: cosine in radians.
|
||||
- `tan(x)`: tangent in radians.
|
||||
- `asin(x)`: inverse sine.
|
||||
- `acos(x)`: inverse cosine.
|
||||
- `atan(x)`: inverse tangent.
|
||||
- `atan2(y, x)`: quadrant-aware inverse tangent.
|
||||
|
||||
#### 1.3 Hyperbolic
|
||||
- `sinh(x)`: hyperbolic sine.
|
||||
- `cosh(x)`: hyperbolic cosine.
|
||||
- `tanh(x)`: hyperbolic tangent.
|
||||
- `asinh(x)`: inverse hyperbolic sine.
|
||||
- `acosh(x)`: inverse hyperbolic cosine.
|
||||
- `atanh(x)`: inverse hyperbolic tangent.
|
||||
|
||||
#### 1.4 Activation / ML
|
||||
- `relu(x)`: `max(0, x)`.
|
||||
- `gelu(x)`: Gaussian Error Linear Unit.
|
||||
- `softplus(x)`: smooth ReLU-like function.
|
||||
- `sigm(x)`: sigmoid.
|
||||
- `softmax(x, [dim])`: softmax normalization.
|
||||
- `softmin(x, [dim])`: softmin normalization.
|
||||
- `erf(x)`: error function.
|
||||
- `erfinv(x)`: inverse error function.
|
||||
|
||||
#### 1.5 Interpolation & Remap
|
||||
- `lerp(a, b, t)`: linear interpolation.
|
||||
- `smoothstep(x, e0, e1)`: 3rd-order smooth transition.
|
||||
- `smootherstep(x, e0, e1)`: 5th-order smoother transition.
|
||||
- `cubic_ease(a, b, t)` / `cubic`: cubic easing interpolation.
|
||||
- `sine_ease(a, b, t)` / `sine`: sine easing interpolation.
|
||||
- `elastic_ease(a, b, t)` / `elastic`: elastic easing interpolation.
|
||||
- `remap(v, i_min, i_max, o_min, o_max)`: map value from one interval to another.
|
||||
|
||||
---
|
||||
|
||||
### 2) Tensor, Stats & Data Ops
|
||||
|
||||
#### 2.1 Element-wise / Tensor Math
|
||||
- `tmin(x, y)`: element-wise minimum.
|
||||
- `tmax(x, y)`: element-wise maximum.
|
||||
- `tnorm(x,[dim])`: L2 normalization along the last dimension. If dimension is provided, it uses this dimension. Dimension list can be provided.
|
||||
- `snorm(x, [dim])`: scalar/tensor norm magnitude. Frobenius norm along `dim` if specified (`dim` can be a single dimension or a list of dimensions); otherwise over the whole tensor.
|
||||
- `cossim(a, b)` / `cosine_similarity`: cosine similarity.
|
||||
- `cov(x, y)`: covariance.
|
||||
- `corr(x, y)` / `correlation`: Pearson correlation.
|
||||
- `entropy(x)`: Shannon entropy.
|
||||
- `flip(x, dims)`: flip selected dimensions; `dims` can be a single dimension, list, or tensor of dimensions, so multiple axes can be flipped at once.
|
||||
- `swap(tensor, dim, i1, i2)`: swap two indices along dimension `dim`.
|
||||
|
||||
#### 2.2 Reductions & Statistical Aggregates
|
||||
- `sum(x, [dims])`: sum; optional `dims` can be a single dimension or a list of dimensions.
|
||||
- `mean(x)`: mean.
|
||||
- `std(x)`: standard deviation.
|
||||
- `var(x)`: variance.
|
||||
- `median(x)`: median.
|
||||
- `mode(x)`: mode.
|
||||
- `quartile(x, k)` / `quartil`: quartile (`k` in `0..4`).
|
||||
- `percentile(x, p)` / `prcnt`: percentile (`p` in `0..100`).
|
||||
- `quantile(x, q)`: quantile (`q` in `0..1`).
|
||||
- `moment(x, a, k)`: k-th moment around center `a`.
|
||||
- `any(x)`: `1.0` if any element is non-zero.
|
||||
- `all(x)`: `1.0` if all elements are non-zero.
|
||||
- `count(x)` / `length(x)` / `cnt(x)`: number of elements or length.
|
||||
- `cumsum(x, [dim])`: cumulative sum; `dim` defaults to `0`.
|
||||
- `cumprod(x)`: cumulative product along dimension `0`.
|
||||
- `smin(x, ...[, dims])`: scalar minimum across inputs; optionally reduce the first tensor/list input across `dims`.
|
||||
- `smax(x, ...[, dims])`: scalar maximum across inputs; optionally reduce the first tensor/list input across `dims`.
|
||||
|
||||
#### 2.3 Sorting / Selection / Indices
|
||||
- `sort(x, [descending], [dim])`: tensors are sorted along the specified dimension (`dim`); if `dim` is not specified, it defaults to the last dimension (`dim=-1`); lists use Python sorting.
|
||||
- `argsort(x, [descending], [dim])`: returns indices along `dim` (defaults to last dimension); `descending` defaults to `false`.
|
||||
- `argmin(x, [as_position])`: global minimum index for tensors; when `as_position` is truthy, returns coordinates as a list.
|
||||
- `argmax(x, [as_position])`: global maximum index for tensors; when `as_position` is truthy, returns coordinates as a list.
|
||||
- `topk(x, k)`: tensors keep the same shape and all non-top-k values are zeroed; lists return the sorted top-k slice.
|
||||
- `botk(x, k)`: same as `topk`, but for smallest values.
|
||||
- `topk_ind(x, k)` / `topk_indices`: global top-k indices from `flatten()`.
|
||||
- `botk_ind(x, k)` / `botk_indices`: global bottom-k indices from `flatten()`.
|
||||
- `unique(x)`: sorted unique values for tensors and lists.
|
||||
- `where(cond, a, b)`: tensor broadcasting for tensor inputs; recursive truthy selection for lists/scalars.
|
||||
- `histogram(x, bins, min, max)` / `hist`: histogram counts from `flatten()` in the given interval.
|
||||
|
||||
#### 2.4 Shape / Structure / Layout
|
||||
- `shape(x)`: returns shape as a Python list.
|
||||
- `flatten(x)`: flattens tensor or nested list.
|
||||
- `reshape(tensor, shape)` / `rshp`: reshape with element-count validation.
|
||||
- `squeeze(x, [dim])`: removes singleton dimensions; optional `dim` follows PyTorch semantics.
|
||||
- `unsqueeze(x, dim)`: inserts a singleton dimension at `dim`.
|
||||
- `permute(tensor, dims)` / `perm`: reorder dimensions.
|
||||
- `crop(tensor, position, size)`: extract a region from tensor or string.
|
||||
- `pad(tensor, padding)`: pad tensor.
|
||||
- `overlay(base, overlay, offset, [opacity])`: overlay one value/tensor onto another.
|
||||
- `append(a, b)`: append or concatenate items, lists, and tensors.
|
||||
- `repeat(x, count, [dims])`: repeat values; tensor `count` can be a scalar or a per-dimension list when `dims` is provided.
|
||||
- `batch_shuffle(tensor, indices)` / `shuffle` / `select`: reorder along batch dimension.
|
||||
- `concatenate(..., dim)` / `concat` / `cat`: concatenate tensors or lists.
|
||||
- `roll(tensor, shifts, [dim])`: circular shift.
|
||||
- `tensor(shape, [value, [type]])`: create a filled tensor; `type` a tensor to copy its dtype to self when being created.
|
||||
- `interpolate_linear(tensor, scale)`: linear interpolation-based resizing. Scale can be a single float or a list. When it is a list, it is interpreted as the target output size.
|
||||
- `interpolate_area(tensor, scale)`: area interpolation-based resizing.
|
||||
- `interpolate_nearest(tensor, scale)` / `interpolate_nearest_exact`: nearest neighbor interpolation-based resizing.
|
||||
|
||||
#### 2.5 Linear Algebra
|
||||
- `dot(a, b)`: dot product after flattening.
|
||||
- `matmul(a, b)`: matrix multiplication.
|
||||
- `cross(a, b)`: cross product; last dimension must be `3`.
|
||||
- `pinv(x)`: permutation inverse for permutation-like values.
|
||||
- `singular_value_decomposition(x,[full_matrices])`/`svd`: singular value decomposition.
|
||||
- `diagonal_matrix(x,shape,[offset],[dim1],[dim2])` / `diag`: create a diagonal matrix from a vector. The diagonal can be offset from the main diagonal using the `offset` parameter. The `dim1` and `dim2` parameters specify which dimensions to use for the diagonal.
|
||||
#### 2.6 Mapping / Sampling
|
||||
- `map(tensor, c1, ...)`: coordinate remapping via `grid_sample`.
|
||||
- supports up to `3` coordinate inputs,
|
||||
- uses sampling coordinates derived from the provided coordinate tensors or functions,
|
||||
- intended for spatial remapping / resampling.
|
||||
- `get_value(tensor, position)`: read value at an N-D position using flat offset math. Superseeded by tensor[position] indexing.
|
||||
|
||||
---
|
||||
|
||||
### 3) Imaging & Spatial Processing
|
||||
|
||||
#### 3.1 Filters & Convolution
|
||||
- `blur(x, sigma, [reshape])` / `gaussian`: Gaussian blur using separable convolution. If `reshape` is truthy, tries orienting the input such that channel is in the direction of the filter; otherwise it expects the color dimension to be last.
|
||||
- `edge(x, [kernel_size])`: Sobel-style edge detection.
|
||||
- `ezconvolution(tensor, ...)` / `ezconv`: convolution with auto layout handling.
|
||||
- `convolution(tensor, ...)` / `conv`: direct convolution. Expects [batch, channel, spatial...] layout.
|
||||
|
||||
#### 3.2 Mask Morphology
|
||||
- `dilate(x, [kernel_size])`: dilation.
|
||||
- `erode(x, [kernel_size])`: erosion.
|
||||
- `morph_open(x, [kernel_size])`: opening = erosion followed by dilation.
|
||||
- `morph_close(x, [kernel_size])`: closing = dilation followed by erosion.
|
||||
|
||||
---
|
||||
|
||||
### 4) Optical Flow
|
||||
- `rife(img1, img2, [tiling_size, iterations, multi_scale])`: compute optical flow using a RAFT-based model.
|
||||
- `tiling_size` chunks the image into overlapping `tiling_size`x`tiling_size` tiles to conserve memory; defaults to `0` (auto-enables `1024`x`1024` tiling above 2MPx); a value between `0` and `1` is taken as a fraction of the resolution instead.
|
||||
- `iterations` defaults to `12`.
|
||||
- `multi_scale` (default `false`) also runs a low-resolution pass of the base image to help catch large movements.
|
||||
- `motion_mask(flow)`: motion/occlusion mask from flow.
|
||||
- `flow_to_image(flow)`: visualize flow as RGB.
|
||||
- `flow_apply(image, flow)`: warp image by flow.
|
||||
- `flow_mag(flow)` / `flow_magnitude`: flow vector magnitude (or anything else, uses first 2 positions of last dimension).
|
||||
- `flow_ang(flow)` / `flow_angle`: flow vector angle in radians (`atan2(dy, dx)`). (or anything else, uses first 2 positions of last dimension)
|
||||
|
||||
---
|
||||
|
||||
### 5) Frequency Domain (FFT)
|
||||
- `fft(x)`: fast Fourier transform across all dimensions.
|
||||
- `ifft(x, [shape])`: inverse FFT.
|
||||
- `angle(x)`: phase angle of complex values.
|
||||
|
||||
---
|
||||
|
||||
### 6) Random & Noise
|
||||
|
||||
#### 6.1 Random Distributions
|
||||
- All random generators are seeded and deterministic for a given seed.
|
||||
- All use the current node shape by default (based on shape of input to the node), but an optional trailing `shape` argument can be provided to specify a different output shape.
|
||||
- All use a `rand<dist>(...)` / `random_<distribution>(...)` naming convention, but the argument list before `[shape]` differs per distribution - see each entry below.
|
||||
- `noise(seed, [shape])` / `randn` / `random_normal`: normal distribution. Same generator as in `Random Noise` node.
|
||||
- `rand(seed, [shape])` / `randu` / `random_uniform`: uniform distribution.
|
||||
- `rande(seed, lambda, [shape])` / `random_exponential`: exponential distribution.
|
||||
- `randc(seed, median, sigma, [shape])` / `random_cauchy`: Cauchy distribution.
|
||||
- `randln(seed, mean, std, [shape])` / `random_log_normal`: log-normal distribution.
|
||||
- `randb(seed, probability, [shape])` / `random_bernoulli`: Bernoulli distribution.
|
||||
- `randp(seed, lambda, [shape])` / `random_poisson`: Poisson distribution.
|
||||
- `randg(seed, shape_param, scale, [shape])` / `random_gamma`: gamma distribution.
|
||||
- `randbeta(seed, alpha, beta, [shape])` / `random_beta`: beta distribution.
|
||||
- `randl(seed, loc, scale, [shape])` / `random_laplace`: Laplace distribution.
|
||||
- `randgumbel(seed, loc, scale, [shape])` / `random_gumbel`: Gumbel distribution.
|
||||
- `randw(seed, scale, concentration, [shape])` / `random_weibull`: Weibull distribution.
|
||||
- `randchi2(seed, df, [shape])` / `random_chi2`: chi-squared distribution.
|
||||
- `randt(seed, df, [shape])` / `random_studentt`: Student’s t distribution.
|
||||
|
||||
#### 6.2 Procedural Noise
|
||||
- `perlin(seed, scale, [octaves, [offset, [shape]]])` / `perlin_noise`:
|
||||
- smooth gradient noise,
|
||||
- supports arbitrary dimensional grids.
|
||||
- `voronoi(seed, scale, [jitter], [offset], [shape])` / `cellular` / `worley` / `voronoi_noise` / `cellular_noise`:
|
||||
- cellular / Voronoi noise,
|
||||
- `jitter` is clamped to `0..1`.
|
||||
- `plasma(seed, scale, [octaves, [offset, [shape]]])` / `turbulence` / `plasma_noise`:
|
||||
- high-frequency turbulence-style noise.
|
||||
- `ridged(seed, scale, [octaves, [offset, [shape]]])` / `ridged_noise`:
|
||||
- ridged multifractal noise with sharp peaks and mountain-like structures.
|
||||
- `domain_warp(seed, scale, warp_scale, warp_strength, [octaves, [warp_octaves, [offset, [shape]]]])` / `domain_warp_noise`:
|
||||
- domain-warped noise for breaking up regular patterns and adding complex organic detail.
|
||||
|
||||
---
|
||||
|
||||
### 7) Bitwise
|
||||
|
||||
#### 7.1 Shift Operators
|
||||
- `a << b`: Bitwise left shift.
|
||||
- `a >> b`: Bitwise right shift.
|
||||
|
||||
#### 7.2 Bitwise Functions
|
||||
- `band(a, b)` / `bitwise_and`: bitwise AND.
|
||||
- `bor(a, b)` / `bitwise_or`: bitwise OR.
|
||||
- `bxor(a, b)` / `bitwise_xor`: bitwise XOR.
|
||||
- `bnot(a)` / `bitwise_not`: bitwise NOT.
|
||||
- `bitcount(a)` / `popcount` / `popcnt`: number of set bits.
|
||||
|
||||
---
|
||||
|
||||
### 8) Strings
|
||||
- `upper(str)`: convert to uppercase.
|
||||
- `lower(str)`: convert to lowercase.
|
||||
- `trim(str)`: trim surrounding whitespace.
|
||||
- `split(str, [delimiter])`: split string.
|
||||
- `join(list, [separator])`: join list into string.
|
||||
- `substring(str, start, [length])` / `substr`: substring extraction.
|
||||
- `find(str, search)`: first match position.
|
||||
- `replace(str, search, replacement)`: replace occurrences.
|
||||
- `startswith(str, prefix)`: returns `1.0` if the string starts with `prefix`, otherwise `0.0`.
|
||||
- `endswith(str, suffix)`: returns `1.0` if the string ends with `suffix`, otherwise `0.0`.
|
||||
|
||||
---
|
||||
|
||||
### 9) Color
|
||||
- `rgb_to_hsv(...)`: convert RGB to HSV.
|
||||
- accepts packed tensor/list input or separate `r, g, b`,
|
||||
- optional trailing boolean argument enables hue in degrees - the 2nd argument in packed mode (`rgb, [degrees]`), the 4th in separate mode (`r, g, b, [degrees]`).
|
||||
- `hsv_to_rgb(...)`: convert HSV to RGB.
|
||||
- accepts packed tensor/list input or separate `h, s, v`,
|
||||
- optional trailing boolean argument treats hue as degrees - the 2nd argument in packed mode (`hsv, [degrees]`), the 4th in separate mode (`h, s, v, [degrees]`).
|
||||
- `rgb_to_oklab(...)`: convert RGB to OKLab.
|
||||
- accepts packed tensor/list input or separate `r, g, b`.
|
||||
- `oklab_to_rgb(...)`: convert OKLab to RGB.
|
||||
- accepts packed tensor/list input or separate `L, a, b`.
|
||||
- `rgb_to_cielab(...)`: convert RGB to CIELAB.
|
||||
- accepts packed tensor/list input or separate `r, g, b`.
|
||||
- `cielab_to_rgb(...)`: convert CIELAB to RGB.
|
||||
- accepts packed tensor/list input or separate `L, a, b`.
|
||||
- `int_to_rgb(value)`: convert packed integer color to RGB triplet.
|
||||
- `rgb_to_int(...)`: convert RGB triplet to packed integer color.
|
||||
|
||||
---
|
||||
|
||||
### 10) Utility & Conversion
|
||||
- `print(x)`: print value and return it.
|
||||
- `print_shape(x)` / `pshp`: print shape and return value.
|
||||
- `range(start, end, step)`: numeric sequence as a list.
|
||||
- `linspace(start, end, count)`: evenly spaced sequence.
|
||||
- `logspace(start, end, count, base)`: logarithmically spaced sequence.
|
||||
- `nan_to_num(x, nan, posinf, neginf)` / `nvl`: replace `NaN` and infinities.
|
||||
- `timestamp()` / `now`: current Unix timestamp.
|
||||
- `int(x)`: convert to int32 or nested int values.
|
||||
- `float(x)`: convert to float or nested float values.
|
||||
- `as_nested(x)` / `as_nested_tensor`: convert list to a nested tensor. Usefull for video+audio models. Values do not need to be in the same or compatible shape.
|
||||
|
||||
___
|
||||
|
||||
### Generators
|
||||
- `text_image(text, font, size, [max_width], [weight], [rotation_angle], [line_spacing], [italic], [underline])` - renders text to an 2D tensor
|
||||
- `coordinates(shape, dim, [dtype])` / `coords` - generates a tensor whose values are the coordinates of each element along the specified dimension. dtype is copied from tensor at that position. Default is float32.
|
||||
|
||||
|
||||
---
|
||||
|
||||
### 11) State / Stack
|
||||
- `stack_push(id, value)`: push into stack slot.
|
||||
- `stack_pop(id)`: pop from stack slot.
|
||||
- `stack_get(id)`: read top value without removing it.
|
||||
- `stack_clear(id)`: clear stack slot.
|
||||
- `stack_has(id)`: check whether slot exists and is non-empty.
|
||||
|
||||
### 12) Dictionary
|
||||
- `add_key(dict, key, value)` - add a key-value pair to a dictionary.
|
||||
- `remove_key(dict, key)` / `rm_key` - remove a key-value pair from a dictionary.
|
||||
- `keys(dict)` - returns a list of dictionary keys in insertion order.
|
||||
|
||||
## Variables
|
||||
|
||||
- **Common variables (except FLOAT)**:
|
||||
- - `D{N}` - position in n-th dimension of tensor, for example `D0`, `D1`, `D2`
|
||||
- - `S{N}` - size of n-th dimension of tensor, for example `S0`, `S1`, `S2`
|
||||
- - `V{N}` - value input, for example `V0`, `V1`, `V2`
|
||||
- - `V` - list of value inputs
|
||||
- - `F{N}` - float input, for example `F0`, `F1`, `F2`
|
||||
- - `F` - list of float inputs
|
||||
- - `Fcnt` or `F_count`: number of float inputs
|
||||
- - `Vcnt` or `V_count`: number of value inputs
|
||||
- `depth`: Current recursion depth (0 at top level)
|
||||
- **common inputs** (legacy):
|
||||
- `a`, `b`, `c`, `d`
|
||||
- **Extra floats** (legacy):
|
||||
- `w`, `x`, `y`, `z`
|
||||
- **INSIDE IFFT**
|
||||
- `F` or `frequency_count` – frequency count (freq domain, iFFT only)
|
||||
- `K` or `frequency` - isotropic frequency (Euclidean norm of indices, iFFT only)
|
||||
- `Kx`, `Ky`, `K_dimN` - frequency index for specific dimension
|
||||
- `Fx`, `Fy`, `F_dimN` - frequency count for specific dimension
|
||||
- **IMAGE and LATENT**:
|
||||
- `C` or `channel` - channel of image
|
||||
- `X` - X position in image, origin in top-left
|
||||
- `Y` - Y position in image, origin in top-left
|
||||
- `W` or `width` - image width
|
||||
- `H` or `height` - image height
|
||||
- `B` or `batch` - batch index
|
||||
- `T` or `batch_count` - number of batches
|
||||
- `N` or `channel_count` - channel count
|
||||
- **LATENT**
|
||||
- `V{N}_{M}` - In case of nested tensors, takes the M-th tensor from N-th input. For example, `V0_1` is the second tensor from the first input.
|
||||
- **MODEL, VAE, CLIP**:
|
||||
- `V{N}_d` - difference against the original input (for example applied lora)
|
||||
- `K` or `key` - name of current layer. For example, `proj_out.bias`, `diffusion_model.blocks.5.attn.out_proj.weight` or `txtfusion.layerwise_blocks.0.prenorm.scale`.
|
||||
- `L` or `layer` - index of current layer
|
||||
- `LC` or `layer_count` - number of layers
|
||||
- **IMAGE KERNEL**: in `conv`/`convolution` or `ezconv`/`ezconvolution` function kernel input
|
||||
- `kX`, `kY`, `kZ` (also lowercase `kx`, `ky`, `kz`) - position in kernel, centered at `0.0`; `kZ`/`kz` is only set for 3D kernels
|
||||
- `kW`, `kernel_width` - kernel width
|
||||
- `kH`, `kernel_height` - kernel height
|
||||
- `kD`, `kernel_depth` - kernel depth
|
||||
|
||||
- **AUDIO**:
|
||||
- `B` or `batch` - batch index
|
||||
- `N` or `channel_count` - channel count
|
||||
- `C` or `channel` - audio channel
|
||||
- `S` or `sample` - current sample
|
||||
- `T` or `sample_count` - audio length in samples
|
||||
- `R` or `sample_rate` - sample rate
|
||||
|
||||
- **VIDEO**
|
||||
- refer to `IMAGE and LATENT` for the visual part
|
||||
- `batch` means `frame`
|
||||
- `batch_count` means `frame_count`
|
||||
- refer to `AUDIO` for sound part
|
||||
- **NOISE**
|
||||
- refer to `IMAGE and LATENT` for most variables
|
||||
- `I` or `input_latent` - latent used as input to generate noise
|
||||
- **GUIDER**
|
||||
- refer to `IMAGE and LATENT`
|
||||
- `sigma` - current sigma value
|
||||
- `seed` - seed used for noise generation
|
||||
- `steps` - total number of sampling steps
|
||||
- `current_step` - current step index, `0..steps`
|
||||
- `sample` - tensor input to guider or output from sampling
|
||||
|
||||
## SelectiveGuiderMathNode (hooking)
|
||||
|
||||
Adds support for hooking into specific layers/blocks of the model during guided diffusion.
|
||||
|
||||
Current implementation details:
|
||||
|
||||
- `hook_target` supports runtime filtering in node UI.
|
||||
- `layer_x` is used as direct index match (`idx == layer_x`) for current hook context.
|
||||
- For guiders with `original_conds`, base conditions are restored before reattaching hooks to avoid hook accumulation across reruns/interrupted runs.
|
||||
- Active hook paths currently include:
|
||||
- Attention override (`attn1` / `attn2` / `double_block_attn` / `single_block_attn` / `attn_unknown`)
|
||||
- DiT block replace (`dit.double_block`, `dit.single_block`)
|
||||
- UNet block patches (`input_block_patch`, `middle_patch`, `output_block_patch`)
|
||||
- Timestep embedding start via `emb_patch` (`block_name="time_emb"`, `layer_x=0`)
|
||||
- Whole-model edges via diffusion-model wrapper (`model_begin`, `model_end`)
|
||||
|
||||
### Side behavior (positive / negative)
|
||||
|
||||
When guider has `original_conds`, hooks are attached separately for:
|
||||
|
||||
- `positive`
|
||||
- `negative`
|
||||
|
||||
Runtime variables expose side information:
|
||||
|
||||
- `cond_side`: `"positive" | "negative" | "mixed" | "unknown"`
|
||||
- `cond_index`: `0 | 1 | -1`
|
||||
- `is_positive`: `1.0` or `0.0`
|
||||
- `is_negative`: `1.0` or `0.0`
|
||||
|
||||
### Variable reference (`SelectiveGuiderMathNode`)
|
||||
|
||||
The following variables are available in `Expression`.
|
||||
|
||||
| Variable | Type | Description |
|
||||
|---|---|---|
|
||||
| `inp` | tensor | Current tensor received by the active hook. |
|
||||
| `sample` | tensor | Alias of `inp`. |
|
||||
| `F0..Fn` | float/tensor | Individual float inputs from the node. |
|
||||
| `F` | list/tensor | Collection of all float inputs. |
|
||||
| `D0..Dn` | tensor | Per-dimension index tensors from `generate_dim_variables`. |
|
||||
| `S0..Sn` | float | Per-dimension sizes from `generate_dim_variables`. |
|
||||
| `hook_kind` | string | Active hook identifier: `attn1`, `attn2`, `double_block_attn`, `single_block_attn`, `attn_unknown`, `dit_block`, `unet_block`, `model_begin`, `model_end`, or `unknown`. |
|
||||
| `hook_domain` | string | High-level domain: `attention`, `diffusion`, or `unknown`. |
|
||||
| `attn_kind` | string | Attention kind: `attn1`, `attn2`, `double_block_attn`, `single_block_attn`, `attn_unknown`, or `none` outside attention hooks. |
|
||||
| `transformer_index` | float | Attention sub-block index inside a UNet block (`-1` if unavailable). |
|
||||
| `is_attn1` | float (0/1) | `1` when current hook is `attn1`, else `0`. |
|
||||
| `is_attn2` | float (0/1) | `1` when current hook is `attn2`, else `0`. |
|
||||
| `is_attn1_hook` | float (0/1) | `1` when `attn_kind=="attn1"`, else `0`. |
|
||||
| `is_attn2_hook` | float (0/1) | `1` when `attn_kind=="attn2"`, else `0`. |
|
||||
| `is_dit` | float (0/1) | `1` when current hook is DiT block hook, else `0`. |
|
||||
| `is_unet_block` | float (0/1) | `1` when current hook is UNet block hook, else `0`. |
|
||||
| `is_time_emb` | float (0/1) | `1` when current hook is timestep embedding entry (`block_name=="time_emb"`), else `0`. |
|
||||
| `block_name` | string | Block/stage name (`input`, `middle`, `output`, `time_emb`, `model`, DiT block type, etc.). |
|
||||
| `layer_id` | float | Numeric block/layer id used by current hook context. |
|
||||
| `layer` | float | Alias of `layer_id`. |
|
||||
| `i` | float | Alias of `layer_id`. |
|
||||
| `layer_key` | string | Composite identifier for debug/filtering (for example `output.6.attn2.0`, `unet.time_emb.0`, `model.begin`). |
|
||||
| `total_blocks` | float | Total blocks in stream if available, otherwise `-1`. |
|
||||
| `has_qkv` | float (0/1) | `1` in attention hooks where `q/k/v` are valid; `0` in diffusion/block hooks. |
|
||||
| `q` | tensor | Query tensor in attention hooks; fallback placeholder otherwise. |
|
||||
| `k` | tensor | Key tensor in attention hooks; fallback placeholder otherwise. |
|
||||
| `v` | tensor | Value tensor in attention hooks; fallback placeholder otherwise. |
|
||||
| `heads` | float | Number of attention heads (attention hooks only, else `0`). |
|
||||
| `dim_head` | float | Per-head channel size (`q.shape[-1] / heads`) when available. |
|
||||
| `activations_shape` | list | Raw shape from transformer context. Empty list if unavailable. |
|
||||
| `activation_b` | float | Batch dimension from `activations_shape[0]` (or `-1`). |
|
||||
| `activation_c` | float | Channel dimension from `activations_shape[1]` (or `-1`). |
|
||||
| `activation_h` | float | Height dimension from `activations_shape[2]` (or `-1`). |
|
||||
| `activation_w` | float | Width dimension from `activations_shape[3]` (or `-1`). |
|
||||
| `attn_mode` | string | Legacy compatibility field (default `unknown`). |
|
||||
| `attention_relation` | string | Inferred semantic relation: `self`, `cross`, or `unknown`. |
|
||||
| `is_self_attention` | float (0/1) | `1` when the active attention is self-attention. |
|
||||
| `is_cross_attention` | float (0/1) | `1` when the active attention is cross-attention. |
|
||||
| `has_context` | float (0/1) | `1` when attention context appears to be present. |
|
||||
| `query_tokens` | float | Query sequence length. |
|
||||
| `context_tokens` | float | Context sequence length. |
|
||||
| `value_tokens` | float | Value sequence length. |
|
||||
| `activation_rank` | float | Rank of `activations_shape`. |
|
||||
| `activation_t` | float | Temporal dimension for video-like activations (`-1` if unavailable). |
|
||||
|
||||
#### Practical notes
|
||||
|
||||
- On SD1.x, repeated hits on the same `layer_id` are normal in attention because one UNet block can contain multiple transformer sub-blocks.
|
||||
- Use `transformer_index` to target exactly one sub-block.
|
||||
- For timestep-begin hooking use `layer_x=0` and filter by `block_name=="time_emb"` (or `layer_key=="unet.time_emb.0"`).
|
||||
- For model-edge hooks filter by `hook_kind=="model_begin"` or `hook_kind=="model_end"`.
|
||||
- For model-agnostic expressions, prefer guard variables: `has_qkv`, `is_dit`, `is_unet_block`, `is_attn1`, `is_attn2`.
|
||||
- `attn2` is only a hook label, not guaranteed to mean real cross-attention.
|
||||
- Use `is_cross_attention` only as an inferred relation from runtime metadata.
|
||||
- Use `attention_relation` for semantic relation (`self`/`cross`) and `attn_kind` for hook-path classification.
|
||||
|
||||
- Selective guider math:
|
||||
- `hook_target`: `all`, `dit_block`, `unet_block`, `attn1`, `attn2`,
|
||||
`double_block_attn`, `single_block_attn`, `model_begin`, `model_end`
|
||||
|
||||
+8
-14
@@ -1,16 +1,10 @@
|
||||
"""Top-level package for more_math."""
|
||||
|
||||
__all__ = [
|
||||
"NODE_CLASS_MAPPINGS",
|
||||
"NODE_DISPLAY_NAME_MAPPINGS",
|
||||
|
||||
]
|
||||
|
||||
__author__ = """Daniel Martinek"""
|
||||
__email__ = "danda.martinek@gmail.com"
|
||||
__version__ = "0.0.1"
|
||||
|
||||
from .src.more_math.nodes import NODE_CLASS_MAPPINGS
|
||||
from .src.more_math.nodes import NODE_DISPLAY_NAME_MAPPINGS
|
||||
import traceback
|
||||
|
||||
try:
|
||||
from .more_math.nodes import comfy_entrypoint as comfy_entrypoint
|
||||
except ImportError:
|
||||
print("Something is seriously wrong")
|
||||
traceback.print_exc()
|
||||
|
||||
WEB_DIRECTORY = "./web"
|
||||
__all__ = ["comfy_entrypoint", "WEB_DIRECTORY"]
|
||||
@@ -0,0 +1,172 @@
|
||||
from .helper_functions import (
|
||||
generate_dim_variables,
|
||||
parse_expr,
|
||||
getIndexTensorAlongDim,
|
||||
as_tensor,
|
||||
normalize_to_common_shape,
|
||||
make_zero_like,
|
||||
get_v_variable,
|
||||
get_f_variable,
|
||||
checkLazyNew,
|
||||
get_tensor_device,
|
||||
move_to_device,
|
||||
)
|
||||
from .Parser.UnifiedMathVisitor import UnifiedMathVisitor
|
||||
from comfy_api.latest import io
|
||||
import comfy
|
||||
import torch
|
||||
from .Stack import MrmthStack
|
||||
import copy
|
||||
from .ParseTree import MrmthParseTree
|
||||
|
||||
class AudioMathNode(io.ComfyNode):
|
||||
"""
|
||||
Enables math expressions on Audio.
|
||||
|
||||
Inputs:
|
||||
V: Autogrow audio inputs (V0, V1, ...)
|
||||
F: Autogrow float inputs (F0, F1, ...)
|
||||
Expression: Expression
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def define_schema(cls) -> io.Schema:
|
||||
return io.Schema(
|
||||
node_id="mrmth_ag_AudioMathNode",
|
||||
category="More math/math",
|
||||
display_name="Audio math",
|
||||
inputs=[
|
||||
io.Autogrow.Input(id="V",template=io.Autogrow.TemplatePrefix(io.Audio.Input("values", optional=True), prefix="V", min=1, max=50)),
|
||||
io.Autogrow.Input(id="F", template=io.Autogrow.TemplatePrefix(io.Float.Input("float", default=0.0, optional=True, lazy=True, force_input=True), prefix="F", min=1, max=50)),
|
||||
io.MultiType.Input(
|
||||
io.String.Input("Expression", default="V0", multiline=False),
|
||||
types=[io.String,MrmthParseTree],
|
||||
tooltip="Expression to apply on weights",
|
||||
),
|
||||
io.Combo.Input(
|
||||
id="length_mismatch",
|
||||
options=["do nothing","error","tile", "pad"],
|
||||
display_name="on size mismatch",
|
||||
default="error",
|
||||
tooltip="How to handle mismatched audio shapes. tile: repeat shorter inputs; error: raise error on mismatch; pad: treat missing samples as zero."
|
||||
),
|
||||
io.Int.Input(id="batching", default=0),
|
||||
io.Boolean.Input(
|
||||
id="remember_stack",
|
||||
default=False,
|
||||
display_name="Remember stack across batch",
|
||||
tooltip=(
|
||||
"If enabled, stack is copied at output leading to changes being remembered during batch operations (node runs multiple times in sucession). If disabled each batch gets it's own copy of the stack."
|
||||
),
|
||||
),
|
||||
io.Boolean.Input(
|
||||
id="use_compute_device",
|
||||
default=True,
|
||||
display_name="Move tensors to GPU",
|
||||
tooltip="Temporarily copies audio tensors to the compute device for math and moves the result back afterwards.",
|
||||
),
|
||||
MrmthStack.Input(id="stack", tooltip="Access stack between nodes",optional=True)
|
||||
],
|
||||
outputs=[
|
||||
io.Audio.Output(is_output_list=True),
|
||||
MrmthStack.Output(),
|
||||
],
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def check_lazy_status(cls, Expression, V, F, length_mismatch="tile",batching=0, remember_stack=False,use_compute_device=True,stack={}):
|
||||
return checkLazyNew(Expression,V,F)
|
||||
|
||||
|
||||
@classmethod
|
||||
def execute(cls, V, F, Expression, length_mismatch="tile",batching=0, remember_stack=False,use_compute_device=True, stack={}):
|
||||
# Identify all present audio inputs and their keys
|
||||
tensor_keys = [k for k, v in V.items() if v is not None and isinstance(v, dict) and "waveform" in v]
|
||||
if not tensor_keys:
|
||||
raise ValueError("At least one audio input is required.")
|
||||
stack = stack if remember_stack else (copy.deepcopy(stack) if stack is not None else {})
|
||||
original_waveform = V[tensor_keys[0]]["waveform"]
|
||||
original_device = get_tensor_device(original_waveform)
|
||||
compute_device = comfy.model_management.get_torch_device() if use_compute_device else original_device
|
||||
working_V = move_to_device(V, compute_device) if compute_device is not None and compute_device != original_device else V
|
||||
waveforms = {k: working_V[k]["waveform"] for k in tensor_keys}
|
||||
sample_rates = {k + "sr": working_V[k].get("sample_rate", 44100) for k in tensor_keys}
|
||||
|
||||
# Normalize all waveforms together
|
||||
normalized_waveforms = normalize_to_common_shape(*waveforms.values(), mode=length_mismatch)
|
||||
V_norm_waveforms = dict(zip(tensor_keys, normalized_waveforms))
|
||||
|
||||
ref_waveform = normalized_waveforms[0]
|
||||
common_shape = ref_waveform.shape
|
||||
sample_rate = V[tensor_keys[0]].get("sample_rate", 44100)
|
||||
|
||||
if(length_mismatch == "error"):
|
||||
for name in tensor_keys:
|
||||
if waveforms[name].shape != common_shape:
|
||||
raise ValueError(f"Input '{name}' has shape ({waveforms[name].shape[0]}, {waveforms[name].shape[2]}), expected ({common_shape[0]}, {common_shape[2]}) to match input.")
|
||||
|
||||
# Setup legacy variables a, b, c, d
|
||||
a_w = V_norm_waveforms.get("V0", make_zero_like(ref_waveform))
|
||||
b_w = V_norm_waveforms.get("V1", make_zero_like(a_w))
|
||||
c_w = V_norm_waveforms.get("V2", make_zero_like(a_w))
|
||||
d_w = V_norm_waveforms.get("V3", make_zero_like(a_w))
|
||||
|
||||
# Ensure legacy are normalized
|
||||
a_w, b_w, c_w, d_w = normalize_to_common_shape(a_w, b_w, c_w, d_w, mode=length_mismatch)
|
||||
|
||||
variables = {
|
||||
"a": a_w, "b": b_w, "c": c_w, "d": d_w,
|
||||
"w": F.get("F0", 0.0) if F.get("F0") is not None else 0.0,
|
||||
"x": F.get("F1", 0.0) if F.get("F1") is not None else 0.0,
|
||||
"y": F.get("F2", 0.0) if F.get("F2") is not None else 0.0,
|
||||
"z": F.get("F3", 0.0) if F.get("F3") is not None else 0.0,
|
||||
"B": getIndexTensorAlongDim(a_w, 0),
|
||||
"batch": getIndexTensorAlongDim(a_w, 0),
|
||||
"C": getIndexTensorAlongDim(a_w, 1),
|
||||
"channel": getIndexTensorAlongDim(a_w, 1),
|
||||
"N": float(a_w.shape[1]),
|
||||
"channel_count": float(a_w.shape[1]),
|
||||
"S": getIndexTensorAlongDim(a_w, 2),
|
||||
"sample": getIndexTensorAlongDim(a_w, 2),
|
||||
"T": float(a_w.shape[2]),
|
||||
"sample_count": float(a_w.shape[2]),
|
||||
"R": sample_rate,
|
||||
"sample_rate": sample_rate,
|
||||
"batch_count": float(a_w.shape[0]),
|
||||
} | generate_dim_variables(a_w) | V_norm_waveforms | sample_rates
|
||||
|
||||
v_stacked, v_cnt = get_v_variable(V_norm_waveforms, length_mismatch=length_mismatch)
|
||||
if v_stacked is not None:
|
||||
variables["V"] = v_stacked
|
||||
variables["Vcnt"] = float(v_cnt)
|
||||
variables["V_count"] = float(v_cnt)
|
||||
|
||||
f_stacked, f_cnt = get_f_variable(F)
|
||||
if f_stacked is not None:
|
||||
variables["F"] = f_stacked
|
||||
variables["Fcnt"] = float(f_cnt)
|
||||
variables["F_count"] = float(f_cnt)
|
||||
|
||||
for k, val in F.items():
|
||||
variables[k] = val if val is not None else 0.0
|
||||
|
||||
tree = None
|
||||
if isinstance(Expression,str):
|
||||
tree = parse_expr(Expression)
|
||||
else:
|
||||
tree = Expression
|
||||
visitor = UnifiedMathVisitor(variables, a_w.shape,a_w.device,state_storage=stack)
|
||||
result = visitor.visit(tree)
|
||||
result = as_tensor(result, a_w.shape)
|
||||
result = move_to_device(result, original_device)
|
||||
|
||||
if batching and batching > 0:
|
||||
res = torch.split(result, batching, dim=0)
|
||||
res_list = []
|
||||
for result_chunk in res:
|
||||
res_list.append({"waveform": result_chunk, "sample_rate": sample_rate})
|
||||
return (res_list, stack)
|
||||
else:
|
||||
stack = stack if remember_stack else copy.deepcopy(stack)
|
||||
|
||||
return ([{"waveform": result, "sample_rate": sample_rate}], stack)
|
||||
@@ -0,0 +1,62 @@
|
||||
from comfy_api.latest import io
|
||||
import torch
|
||||
|
||||
windows = {"bartlett": torch.bartlett_window, "blackman": torch.blackman_window, "hamming": torch.hamming_window, "hann": torch.hann_window, "kaiser": torch.kaiser_window}
|
||||
|
||||
|
||||
class AudioToSpectrogram(io.ComfyNode):
|
||||
"""
|
||||
Converts Audio to an Image spectrogram.
|
||||
Red = Real, Green = logarithm of value (just so it looks good), Blue = Imaginary.
|
||||
Each audio channel is stacked vertically.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def define_schema(cls) -> io.Schema:
|
||||
return io.Schema(
|
||||
node_id="mrmth_AudioToImageSpectrogram",
|
||||
category="More math/converters",
|
||||
display_name="Audio -> Spectrogram",
|
||||
inputs=[
|
||||
io.Audio.Input(id="audio", tooltip="Input audio"),
|
||||
io.Int.Input(id="window_length", default=1024, min=16, max=4096, tooltip="Window length in samples"),
|
||||
io.Int.Input(id="hop_length", default=256, min=1, max=4096, tooltip="Stride of the window (hop length) in samples"),
|
||||
io.Int.Input(
|
||||
id="bucket_count", default=513, min=2, max=4096, tooltip="Number of frequency buckets (determines resolution)"
|
||||
),
|
||||
io.Combo.Input(id="window_type", default="hann", options=list(windows.keys()), tooltip="Type of window function to apply"),
|
||||
],
|
||||
outputs=[
|
||||
io.Image.Output(id="image"),
|
||||
io.Int.Output(id="channel_count", display_name="Channel count", tooltip="Number of channels in the output image"),
|
||||
io.Int.Output(id="sample_rate", display_name="Sample rate", tooltip="Sample rate of the input audio"),
|
||||
],
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def execute(cls, audio, window_length, hop_length, bucket_count, window_type):
|
||||
waveform = audio["waveform"]
|
||||
sample_rate = audio["sample_rate"]
|
||||
B, C, S = waveform.shape
|
||||
n_fft = (bucket_count - 1) * 2
|
||||
flat_waveform = waveform.reshape(B * C, S)
|
||||
window = windows[window_type](window_length, device=waveform.device)
|
||||
|
||||
stft_out = torch.stft(
|
||||
flat_waveform,
|
||||
n_fft=n_fft,
|
||||
hop_length=hop_length,
|
||||
win_length=window_length,
|
||||
window=window,
|
||||
center=True,
|
||||
pad_mode="reflect",
|
||||
normalized=False,
|
||||
onesided=True,
|
||||
return_complex=True,
|
||||
)
|
||||
|
||||
F, T = stft_out.shape[1], stft_out.shape[2]
|
||||
stft_out = stft_out.reshape(B, C, F, T).reshape(B, C * F, T)
|
||||
image = torch.stack([stft_out.real, torch.log1p(torch.abs(stft_out)), stft_out.imag], dim=-1)
|
||||
print(image.type())
|
||||
return (image, C, sample_rate)
|
||||
@@ -0,0 +1,133 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import torch
|
||||
|
||||
from comfy.patcher_extension import PatcherInjection
|
||||
from comfy.weight_adapter.bypass import BypassForwardHook
|
||||
from comfy_api.latest import io
|
||||
|
||||
from .helper_functions import parse_expr
|
||||
from .Parser.UnifiedMathVisitor import UnifiedMathVisitor
|
||||
from .Stack import MrmthStack
|
||||
from .ParseTree import MrmthParseTree
|
||||
|
||||
@staticmethod
|
||||
def _parse_strengths(Expression, stack):
|
||||
tree = None
|
||||
variables = {}
|
||||
if isinstance(Expression,str):
|
||||
tree = parse_expr(Expression)
|
||||
else:
|
||||
tree = Expression
|
||||
visitor = UnifiedMathVisitor(variables, [1],torch.device("cpu"),state_storage=stack)
|
||||
result = visitor.visit(tree)
|
||||
return (list(result), stack)
|
||||
|
||||
class BatchLoraHooksNode(io.ComfyNode):
|
||||
"""Scale existing bypass LoRA injections on a model/clip pair. Works with models/loaders/Load lora (Bypass) (For Debugging) or more_math batch lora node."""
|
||||
|
||||
@staticmethod
|
||||
def _get_bypass_hooks(injections):
|
||||
if injections is None:
|
||||
return []
|
||||
|
||||
hooks = []
|
||||
for injection in injections:
|
||||
closure = getattr(getattr(injection, "inject", None), "__closure__", None)
|
||||
if not closure:
|
||||
continue
|
||||
for cell in closure:
|
||||
cell_value = cell.cell_contents
|
||||
if hasattr(cell_value, "hooks") and hasattr(cell_value, "create_injections"):
|
||||
manager_hooks = getattr(cell_value, "hooks", None)
|
||||
if manager_hooks:
|
||||
hooks.extend(manager_hooks)
|
||||
break
|
||||
return hooks
|
||||
|
||||
@staticmethod
|
||||
def _build_bypass_injection(hooks, scale: float):
|
||||
scaled_hooks = []
|
||||
for hook in hooks:
|
||||
adapter = copy.deepcopy(hook.adapter)
|
||||
scaled_hooks.append(BypassForwardHook(hook.module, adapter, multiplier=hook.multiplier * scale))
|
||||
|
||||
def inject_all(model_patcher):
|
||||
for hook in scaled_hooks:
|
||||
hook.inject()
|
||||
|
||||
def eject_all(model_patcher):
|
||||
for hook in scaled_hooks:
|
||||
hook.eject()
|
||||
|
||||
return [PatcherInjection(inject=inject_all, eject=eject_all)]
|
||||
|
||||
@classmethod
|
||||
def _scale_bypass_injections(cls, patcher, scale: float):
|
||||
if patcher is None:
|
||||
return
|
||||
|
||||
injections = getattr(patcher, "get_injections", lambda _key: None)("bypass_lora")
|
||||
if injections is None:
|
||||
return
|
||||
|
||||
hooks = cls._get_bypass_hooks(injections)
|
||||
if not hooks:
|
||||
return
|
||||
|
||||
was_injected = getattr(patcher, "is_injected", False)
|
||||
if was_injected:
|
||||
patcher.eject_model()
|
||||
|
||||
patcher.set_injections("bypass_lora", cls._build_bypass_injection(hooks, scale))
|
||||
|
||||
if was_injected:
|
||||
patcher.inject_model()
|
||||
|
||||
@classmethod
|
||||
def define_schema(cls) -> io.Schema:
|
||||
return io.Schema(
|
||||
node_id="mrmth_LoraBatchRescale",
|
||||
category="More math",
|
||||
display_name="Scale LoRA (Batch)",
|
||||
inputs=[
|
||||
io.Model.Input(id="model", tooltip="The model with bypass LoRA injections"),
|
||||
io.Clip.Input(id="clip", tooltip="The clip with bypass LoRA injections"),
|
||||
io.MultiType.Input(
|
||||
io.String.Input("model_strength", default="[1.0]", multiline=False),
|
||||
types=[io.String, MrmthParseTree],
|
||||
tooltip="List of model-side LoRA strengths",
|
||||
), io.MultiType.Input(
|
||||
io.String.Input("clip_strength", default="[1.0]", multiline=False),
|
||||
types=[io.String, MrmthParseTree],
|
||||
tooltip="List of clip-side LoRA strengths",
|
||||
),
|
||||
MrmthStack.Input(id="stack", optional=True)
|
||||
],
|
||||
outputs=[
|
||||
io.Model.Output(id="model", tooltip="The model with scaled bypass LoRA injections", is_output_list=True),
|
||||
io.Clip.Output(id="clip", tooltip="The clip with scaled bypass LoRA injections", is_output_list=True),
|
||||
MrmthStack.Output(id="stack", tooltip="The stack"),
|
||||
],
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def execute(cls, model, clip, model_strength, clip_strength, stack=None):
|
||||
stack = copy.deepcopy(stack) if stack is not None else {}
|
||||
weights0, stack = _parse_strengths(model_strength, {})
|
||||
weights1, stack = _parse_strengths(clip_strength, {})
|
||||
scaled_models = []
|
||||
scaled_clips = []
|
||||
if len(weights0) != len(weights1):
|
||||
raise ValueError("model_strength and clip_strength must have the same number of elements. Model: {}, Clip: {}".format(len(weights0), len(weights1)))
|
||||
for w0,w1 in zip(weights0, weights1):
|
||||
scaled_model = model.clone()
|
||||
scaled_clip = clip.clone()
|
||||
cls._scale_bypass_injections(scaled_model, w0)
|
||||
clip_patcher = getattr(scaled_clip, "patcher", None)
|
||||
cls._scale_bypass_injections(clip_patcher, w1)
|
||||
scaled_models.append(scaled_model)
|
||||
scaled_clips.append(scaled_clip)
|
||||
|
||||
return (scaled_models, scaled_clips, stack)
|
||||
@@ -0,0 +1,101 @@
|
||||
from email.mime import base
|
||||
|
||||
from comfy_api.latest import io
|
||||
import folder_paths
|
||||
from .Stack import MrmthStack
|
||||
import comfy
|
||||
import os
|
||||
from .helper_functions import parse_expr
|
||||
from .Parser.UnifiedMathVisitor import UnifiedMathVisitor
|
||||
import torch
|
||||
import copy
|
||||
from .ParseTree import MrmthParseTree
|
||||
|
||||
class BatchLoraNode(io.ComfyNode):
|
||||
"""
|
||||
A node that applies a batch of LoRA weights to a model.
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def _parse_strengths(Expression, stack,lora_list,lora_count,base_model):
|
||||
tree = None
|
||||
variables = {"lora_names": lora_list, "lora_count": lora_count,"base_model": base_model}
|
||||
if isinstance(Expression,str):
|
||||
tree = parse_expr(Expression)
|
||||
else:
|
||||
tree = Expression
|
||||
visitor = UnifiedMathVisitor(variables, [1],torch.device("cpu"),state_storage=stack)
|
||||
print(stack)
|
||||
result = visitor.visit(tree)
|
||||
return (list(result), stack)
|
||||
@classmethod
|
||||
def define_schema(cls) -> io.Schema:
|
||||
return io.Schema(
|
||||
node_id="mrmth_BatchLoraNode",
|
||||
category="More math",
|
||||
display_name="Batch LoRa apply",
|
||||
inputs=[
|
||||
io.Model.Input(id="model", tooltip="The model to apply the LoRA weights to"),
|
||||
io.Clip.Input(id="clip", tooltip="The clip to apply the LoRA weights to"),
|
||||
io.String.Input(id="folder", tooltip="The sub-folder containing the LoRA weights"),
|
||||
io.Boolean.Input(id="add_no_lora", tooltip="Whether to add a model execution without a lora"),
|
||||
io.MultiType.Input(
|
||||
io.String.Input("model_strength", default="[1.0]", multiline=False),
|
||||
types=[io.String,MrmthParseTree],
|
||||
tooltip="strength of lora applied to model",
|
||||
), io.MultiType.Input(
|
||||
io.String.Input("clip_strength", default="[1.0]", multiline=False),
|
||||
types=[io.String,MrmthParseTree],
|
||||
tooltip="strength of lora applied to clip",
|
||||
),
|
||||
MrmthStack.Input(id="stack", tooltip="The stack", optional=True),
|
||||
],
|
||||
outputs=[
|
||||
io.Model.Output(id="model", tooltip="The model with the LoRA weights applied", is_output_list=True),
|
||||
io.Clip.Output(id="clip", tooltip="The clip with the LoRA weights applied", is_output_list=True),
|
||||
MrmthStack.Output(id="stack", tooltip="The stack"),
|
||||
],
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def execute(cls, model, clip, folder, add_no_lora, model_strength, clip_strength, stack=None):
|
||||
paths = folder_paths.get_folder_paths("loras")
|
||||
print(paths)
|
||||
lora_path = folder_paths.get_filename_list("loras")
|
||||
stack = copy.deepcopy(stack) if stack is not None else {}
|
||||
selected_folder = os.path.normpath(folder)
|
||||
if selected_folder:
|
||||
lora_path = [
|
||||
lora
|
||||
for lora in lora_path
|
||||
if os.path.normpath(lora) == selected_folder
|
||||
or os.path.normpath(lora).startswith(selected_folder + os.sep)
|
||||
]
|
||||
|
||||
model_strengths, stack = cls._parse_strengths(model_strength, stack, lora_path, len(lora_path), add_no_lora)
|
||||
clip_strengths, stack = cls._parse_strengths(clip_strength, stack, lora_path, len(lora_path), add_no_lora)
|
||||
print(stack)
|
||||
model_lora = [model] if add_no_lora else []
|
||||
clip_lora = [clip] if add_no_lora else []
|
||||
for index, lora in enumerate(lora_path):
|
||||
model = model.clone()
|
||||
clip = clip.clone()
|
||||
full_lora_path = folder_paths.get_full_path("loras", lora)
|
||||
if full_lora_path is None:
|
||||
print(f"BatchLoraNode: Skipping missing LoRA: {lora}")
|
||||
continue
|
||||
|
||||
model_strength_value = model_strengths[index] if index < len(model_strengths) else model_strengths[-1]
|
||||
clip_strength_value = clip_strengths[index] if index < len(clip_strengths) else clip_strengths[-1]
|
||||
|
||||
loraw = comfy.utils.load_torch_file(full_lora_path, safe_load=True)
|
||||
modellr, cliplr = comfy.sd.load_bypass_lora_for_models(
|
||||
model,
|
||||
clip,
|
||||
loraw,
|
||||
model_strength_value,
|
||||
clip_strength_value,
|
||||
)
|
||||
model_lora.append(modellr)
|
||||
clip_lora.append(cliplr)
|
||||
return (model_lora, clip_lora, stack)
|
||||
@@ -0,0 +1,98 @@
|
||||
from comfy_api.latest import io
|
||||
from inspect import cleandoc
|
||||
from .helper_functions import checkLazyNew
|
||||
from .Stack import MrmthStack
|
||||
from .ParseTree import MrmthParseTree
|
||||
import copy
|
||||
import comfy
|
||||
import comfy.utils
|
||||
|
||||
|
||||
|
||||
class CLIPMathNode(io.ComfyNode):
|
||||
"""
|
||||
This node enables the use of math expressions on CLIP weights using Autogrow inputs.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def define_schema(cls) -> io.Schema:
|
||||
return io.Schema(
|
||||
node_id="mrmth_ag_CLIPMathNode",
|
||||
display_name="CLIP Math",
|
||||
category="More math/math",
|
||||
inputs=[
|
||||
io.Autogrow.Input(id="V",template=io.Autogrow.TemplatePrefix(io.Clip.Input("values"), prefix="V", min=1, max=50)),
|
||||
io.Autogrow.Input(id="F", template=io.Autogrow.TemplatePrefix(io.Float.Input("float", default=0.0, optional=True, lazy=True, force_input=True), prefix="F", min=1, max=50)),
|
||||
io.MultiType.Input(
|
||||
io.String.Input("Expression", default="V0", multiline=False),
|
||||
types=[io.String,MrmthParseTree],
|
||||
tooltip="Expression to apply on weights",
|
||||
),
|
||||
io.Combo.Input(
|
||||
id="length_mismatch",
|
||||
options=["do nothing","error","tile", "pad"],
|
||||
display_name="on size mismatch",
|
||||
default="error",
|
||||
tooltip="How to handle mismatched layer counts."
|
||||
),
|
||||
io.Boolean.Input(
|
||||
id="dict_mode",
|
||||
default=False,
|
||||
display_name="Dictionary mode",
|
||||
tooltip="Evaluates CLIP weights as dictionaries. Per-layer and dimension variables are unavailable.",
|
||||
),
|
||||
io.Boolean.Input(
|
||||
id="use_compute_device",
|
||||
default=True,
|
||||
display_name="Move tensors to GPU",
|
||||
tooltip="Temporarily copies CLIP patch tensors to the compute device for math and moves the patches back afterwards.",
|
||||
),
|
||||
MrmthStack.Input(id="stack", tooltip="Access stack between nodes",optional=True)
|
||||
],
|
||||
outputs=[
|
||||
io.Clip.Output(),
|
||||
MrmthStack.Output(),
|
||||
],
|
||||
)
|
||||
|
||||
tooltip = cleandoc(__doc__)
|
||||
|
||||
@classmethod
|
||||
def check_lazy_status(cls, Expression, V, F, length_mismatch="tile",use_compute_device=True,stack={},dict_mode=False):
|
||||
return checkLazyNew(Expression,V,F)
|
||||
|
||||
|
||||
@classmethod
|
||||
def execute(cls, V, F, Expression, length_mismatch="tile",use_compute_device=True,stack={},dict_mode=False) -> io.NodeOutput:
|
||||
# Determine reference CLIP
|
||||
a = V.get("V0")
|
||||
stack = copy.deepcopy(stack) if stack is not None else {}
|
||||
if a is None:
|
||||
for m in V.values():
|
||||
if m is not None:
|
||||
a = m
|
||||
break
|
||||
if a is None:
|
||||
raise ValueError("At least one input CLIP is required.")
|
||||
|
||||
# Prepare CLIP patchers
|
||||
patchers_V = {}
|
||||
for k, v in V.items():
|
||||
if v is not None:
|
||||
patchers_V[k] = v.patcher
|
||||
|
||||
# Call autogrow patch calculation
|
||||
from .modelLikeCommon import calculate_patches_autogrow, calculate_patches_dict_mode
|
||||
|
||||
ref_patcher = getattr(V.get("V0"), "patcher", None)
|
||||
layer_count = ref_patcher.model.state_dict().__len__() if ref_patcher is not None and hasattr(ref_patcher, "model") and hasattr(ref_patcher.model, "state_dict") else 0
|
||||
pbar = comfy.utils.ProgressBar(layer_count)
|
||||
aliases = {"a": "V0", "b": "V1", "c": "V2", "d": "V3", "w": "F0", "x": "F1", "y": "F2", "z": "F3"}
|
||||
|
||||
calculate_patches = calculate_patches_dict_mode if dict_mode else calculate_patches_autogrow
|
||||
patches = calculate_patches(Expression, V=patchers_V, F=F,pbar=pbar, mapping=aliases,stack=stack,use_compute_device=use_compute_device)
|
||||
|
||||
out_clip = a.clone()
|
||||
if patches:
|
||||
out_clip.add_patches(patches, 1.0, 1.0)
|
||||
return (out_clip,stack)
|
||||
@@ -0,0 +1,255 @@
|
||||
import torch
|
||||
from .helper_functions import checkLazyNew, generate_dim_variables, parse_expr, getIndexTensorAlongDim, as_tensor, normalize_to_common_shape, make_zero_like, get_v_variable, get_f_variable, get_tensor_device, move_to_device
|
||||
from .Parser.UnifiedMathVisitor import UnifiedMathVisitor
|
||||
from comfy_api.latest import io
|
||||
import comfy
|
||||
import copy
|
||||
from .Stack import MrmthStack
|
||||
from .ParseTree import MrmthParseTree
|
||||
|
||||
class ConditioningMathNode(io.ComfyNode):
|
||||
"""
|
||||
Enables math expressions on Audio.
|
||||
|
||||
Inputs:
|
||||
I: Autogrow image inputs (I0, I1, ...)
|
||||
F: Autogrow float inputs (F0, F1, ...)
|
||||
Image: Expression
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def define_schema(cls) -> io.Schema:
|
||||
return io.Schema(
|
||||
node_id="mrmth_ag_ConditioningMathNode",
|
||||
category="More math/math",
|
||||
display_name="Conditioning math",
|
||||
inputs=[
|
||||
io.Autogrow.Input(id="V",template=io.Autogrow.TemplatePrefix(io.Conditioning.Input("values"), prefix="V", min=1, max=50)),
|
||||
io.Autogrow.Input(id="F", template=io.Autogrow.TemplatePrefix(io.Float.Input("float", default=0.0, optional=True, lazy=True, force_input=True), prefix="F", min=1, max=50)),
|
||||
io.MultiType.Input(
|
||||
io.String.Input("Expression", display_name="Tensor expr.", default="V0", multiline=False),
|
||||
types=[io.String,MrmthParseTree],
|
||||
tooltip="Expression to apply on tensor part of conditioning",
|
||||
),
|
||||
io.MultiType.Input(
|
||||
io.String.Input("Expression_pi", display_name="pooled output expr.", default="V0", multiline=False),
|
||||
types=[io.String,MrmthParseTree],
|
||||
tooltip="Expression to apply on pooled_input part of conditioning",
|
||||
),
|
||||
io.Combo.Input(
|
||||
id="length_mismatch",
|
||||
options=["do nothing","error","tile", "pad"],
|
||||
display_name="on size mismatch",
|
||||
default="error",
|
||||
tooltip="How to handle mismatched image batch sizes. tile: repeat shorter inputs; error: raise error on mismatch; pad: treat missing frames as zero."
|
||||
),
|
||||
io.Int.Input(id="batching", default=0),
|
||||
io.Boolean.Input(
|
||||
id="remember_stack",
|
||||
default=False,
|
||||
display_name="Remember stack across batch",
|
||||
tooltip=(
|
||||
"If enabled, stack is copied at output leading to changes being remembered during batch operations (node runs multiple times in sucession). If disabled each batch gets it's own copy of the stack."
|
||||
),
|
||||
),
|
||||
io.Boolean.Input(
|
||||
id="use_compute_device",
|
||||
default=True,
|
||||
display_name="Move tensors to GPU",
|
||||
tooltip="Temporarily copies conditioning tensors to the compute device for math and moves the result back afterwards.",
|
||||
),
|
||||
MrmthStack.Input(id="stack",optional=True)
|
||||
],
|
||||
outputs=[
|
||||
io.Conditioning.Output(is_output_list=True),
|
||||
MrmthStack.Output()
|
||||
],
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def check_lazy_status(cls, Expression,Expression_pi, V, F, length_mismatch="tile", batching=0,remember_stack=False,use_compute_device=True, stack={}):
|
||||
d = checkLazyNew(Expression,V,F)
|
||||
b = checkLazyNew(Expression_pi,V,F)
|
||||
return d|b
|
||||
|
||||
|
||||
@classmethod
|
||||
def execute(cls, V, F, Expression, Expression_pi, length_mismatch="tile", batching=0,remember_stack=False,use_compute_device=True, stack={}):
|
||||
# Identify all present conditioning inputs
|
||||
tensor_keys = [k for k, v in V.items() if v is not None and isinstance(v, list) and len(v) > 0]
|
||||
if not tensor_keys:
|
||||
raise ValueError("At least one input is required.")
|
||||
stack = stack if remember_stack else (copy.deepcopy(stack) if stack is not None else {})
|
||||
|
||||
needed_vars = checkLazyNew(Expression, V, F) | checkLazyNew(Expression_pi, V, F)
|
||||
needed_tensor_keys = [k for k in tensor_keys if k in needed_vars]
|
||||
if not needed_tensor_keys:
|
||||
needed_tensor_keys = tensor_keys
|
||||
|
||||
# Extract tensors and pooled outputs
|
||||
original_tensor = None
|
||||
tensors = {}
|
||||
pooled_outputs = {}
|
||||
for key in needed_tensor_keys:
|
||||
conditioning = V[key]
|
||||
tensors[key] = conditioning[0][0]
|
||||
if original_tensor is None:
|
||||
original_tensor = conditioning[0][0]
|
||||
# pooled_output is optional in the dict
|
||||
|
||||
pooled_outputs[key] = conditioning[0][1].get("pooled_output")
|
||||
|
||||
original_device = get_tensor_device(original_tensor)
|
||||
compute_device = comfy.model_management.get_torch_device() if use_compute_device else original_device
|
||||
working_V = move_to_device(V, compute_device) if compute_device is not None and compute_device != original_device else V
|
||||
|
||||
# Extract tensors and pooled outputs
|
||||
tensors = {}
|
||||
pooled_outputs = {}
|
||||
for key in needed_tensor_keys:
|
||||
conditioning = working_V[key]
|
||||
tensors[key] = conditioning[0][0]
|
||||
pooled_outputs[key] = conditioning[0][1].get("pooled_output")
|
||||
|
||||
# Normalize main tensors
|
||||
norm_tensors_batch = normalize_to_common_shape(*tensors.values(), mode=length_mismatch)
|
||||
V_norm_tensors = dict(zip(needed_tensor_keys, norm_tensors_batch))
|
||||
|
||||
ref_tensor = norm_tensors_batch[0]
|
||||
|
||||
# Normalize pooled outputs (if they exist)
|
||||
valid_pooled_keys = [k for k, v in pooled_outputs.items() if v is not None]
|
||||
if valid_pooled_keys:
|
||||
norm_pooled_batch = normalize_to_common_shape(*[pooled_outputs[k] for k in valid_pooled_keys], mode=length_mismatch)
|
||||
V_norm_pooled = dict(zip(valid_pooled_keys, norm_pooled_batch))
|
||||
ref_pooled = norm_pooled_batch[0]
|
||||
else:
|
||||
V_norm_pooled = {}
|
||||
ref_pooled = torch.tensor([])
|
||||
|
||||
# Setup legacy variables a, b, c, d (Main Tensor)
|
||||
a = V_norm_tensors.get("V0", make_zero_like(ref_tensor))
|
||||
b = V_norm_tensors.get("V1", make_zero_like(a))
|
||||
c = V_norm_tensors.get("V2", make_zero_like(a))
|
||||
d = V_norm_tensors.get("V3", make_zero_like(a))
|
||||
a, b, c, d = normalize_to_common_shape(a, b, c, d, mode=length_mismatch)
|
||||
|
||||
# variables for Main Tensor (Expression)
|
||||
variables = {
|
||||
"a": a, "b": b, "c": c, "d": d,
|
||||
"w": F.get("F0", 0.0) if F.get("F0") is not None else 0.0,
|
||||
"x": F.get("F1", 0.0) if F.get("F1") is not None else 0.0,
|
||||
"y": F.get("F2", 0.0) if F.get("F2") is not None else 0.0,
|
||||
"z": F.get("F3", 0.0) if F.get("F3") is not None else 0.0,
|
||||
"B": getIndexTensorAlongDim(a, 0),
|
||||
"batch": getIndexTensorAlongDim(a, 0),
|
||||
"T": a.shape[0],
|
||||
"batch_count": a.shape[0],
|
||||
} | generate_dim_variables(a) | V_norm_tensors
|
||||
|
||||
v_stacked, v_cnt = get_v_variable(V_norm_tensors, length_mismatch=length_mismatch)
|
||||
if v_stacked is not None:
|
||||
variables["V"] = v_stacked
|
||||
variables["Vcnt"] = float(v_cnt)
|
||||
variables["V_count"] = float(v_cnt)
|
||||
|
||||
f_stacked, f_cnt = get_f_variable(F)
|
||||
if f_stacked is not None:
|
||||
variables["F"] = f_stacked
|
||||
variables["Fcnt"] = float(f_cnt)
|
||||
variables["F_count"] = float(f_cnt)
|
||||
|
||||
for k, val in F.items():
|
||||
variables[k] = val if val is not None else 0.0
|
||||
|
||||
# Execute Expression (Main Tensor)
|
||||
rtensor = None
|
||||
tree = None
|
||||
if isinstance(Expression,str):
|
||||
tree = parse_expr(Expression)
|
||||
else:
|
||||
tree = Expression
|
||||
visitor = UnifiedMathVisitor(variables, a.shape,a.device, state_storage=stack)
|
||||
rtensor = visitor.visit(tree)
|
||||
rtensor = as_tensor(rtensor, a.shape)
|
||||
|
||||
|
||||
# variables for Pooled Output (Expression_pi)
|
||||
a_p = V_norm_pooled.get("V0", make_zero_like(ref_pooled))
|
||||
b_p = V_norm_pooled.get("V1", make_zero_like(a_p))
|
||||
c_p = V_norm_pooled.get("V2", make_zero_like(a_p))
|
||||
d_p = V_norm_pooled.get("V3", make_zero_like(a_p))
|
||||
a_p, b_p, c_p, d_p = normalize_to_common_shape(a_p, b_p, c_p, d_p, mode=length_mismatch)
|
||||
|
||||
variables_pi = {
|
||||
"a": a_p, "b": b_p, "c": c_p, "d": d_p,
|
||||
"w": F.get("F0", 0.0) if F.get("F0") is not None else 0.0,
|
||||
"x": F.get("F1", 0.0) if F.get("F1") is not None else 0.0,
|
||||
"y": F.get("F2", 0.0) if F.get("F2") is not None else 0.0,
|
||||
"z": F.get("F3", 0.0) if F.get("F3") is not None else 0.0,
|
||||
"B": getIndexTensorAlongDim(a_p, 0) if a_p.numel() > 0 else torch.tensor([]),
|
||||
"batch": getIndexTensorAlongDim(a_p, 0) if a_p.numel() > 0 else torch.tensor([]),
|
||||
"T": a_p.shape[0] if a_p.numel() > 0 else 0,
|
||||
"batch_count": a_p.shape[0] if a_p.numel() > 0 else 0,
|
||||
} | generate_dim_variables(a_p) | V_norm_pooled
|
||||
|
||||
v_stacked, v_cnt = get_v_variable(V_norm_pooled, length_mismatch=length_mismatch)
|
||||
if v_stacked is not None:
|
||||
variables_pi["V"] = v_stacked
|
||||
variables_pi["Vcnt"] = float(v_cnt)
|
||||
variables_pi["V_count"] = float(v_cnt)
|
||||
|
||||
f_stacked, f_cnt = get_f_variable(F)
|
||||
if f_stacked is not None:
|
||||
variables_pi["F"] = f_stacked
|
||||
variables_pi["Fcnt"] = float(f_cnt)
|
||||
variables_pi["F_count"] = float(f_cnt)
|
||||
|
||||
for k, val in F.items():
|
||||
variables_pi[k] = val if val is not None else 0.0
|
||||
|
||||
# Execute Expression_pi
|
||||
rpooled = None
|
||||
if valid_pooled_keys:
|
||||
tree_pi = None
|
||||
if isinstance(Expression_pi,str):
|
||||
tree_pi = parse_expr(Expression_pi)
|
||||
else:
|
||||
tree_pi = Expression_pi
|
||||
visitor_pi = UnifiedMathVisitor(variables_pi, a_p.shape,a_p.device, state_storage=stack)
|
||||
rpooled_raw = visitor_pi.visit(tree_pi)
|
||||
rpooled = as_tensor(rpooled_raw, a_p.shape)
|
||||
|
||||
|
||||
if rtensor is None:
|
||||
rtensor = torch.zeros([1])
|
||||
if rpooled is None:
|
||||
rpooled = torch.zeros([1])
|
||||
|
||||
# batching = size of each chunk -> use torch.split(tensor, batching, dim=0)
|
||||
if batching and batching > 0:
|
||||
rt_chunks = torch.split(rtensor, batching, dim=0)
|
||||
rp_chunks = torch.split(rpooled, batching, dim=0)
|
||||
res_list = []
|
||||
for i in range(max(len(rt_chunks), len(rp_chunks))):
|
||||
result_tensor = rt_chunks[i] if i < len(rt_chunks) else torch.zeros([1])
|
||||
result_pooled = rp_chunks[i] if i < len(rp_chunks) else torch.zeros([1])
|
||||
base = copy.deepcopy(V["V0"])
|
||||
# base[0] is a tuple (tensor, dict), need to reconstruct
|
||||
old_dict = base[0][1] if len(base[0]) > 1 else {}
|
||||
new_dict = old_dict.copy()
|
||||
new_dict["pooled_output"] = result_pooled
|
||||
base[0] = (result_tensor, new_dict)
|
||||
res_list.append(base)
|
||||
else:
|
||||
# Single output (no batching)
|
||||
base = copy.deepcopy(V["V0"])
|
||||
# base[0] is a tuple (tensor, dict), need to reconstruct
|
||||
old_dict = base[0][1] if len(base[0]) > 1 else {}
|
||||
new_dict = old_dict.copy()
|
||||
new_dict["pooled_output"] = rpooled
|
||||
base[0] = (rtensor, new_dict)
|
||||
res_list = [base]
|
||||
|
||||
stack = stack if remember_stack else copy.deepcopy(stack)
|
||||
return (move_to_device(res_list, original_device),stack)
|
||||
@@ -0,0 +1,103 @@
|
||||
from inspect import cleandoc
|
||||
import torch
|
||||
|
||||
from .helper_functions import parse_expr, get_v_variable, checkLazyNew
|
||||
from .Parser.UnifiedMathVisitor import UnifiedMathVisitor
|
||||
|
||||
from comfy_api.latest import io
|
||||
from .Stack import MrmthStack
|
||||
from .ParseTree import MrmthParseTree
|
||||
import copy
|
||||
|
||||
|
||||
class FloatMathNode(io.ComfyNode):
|
||||
"""
|
||||
This node enables the use of math expressions on Floats.
|
||||
|
||||
Inputs:
|
||||
V: Autogrow float inputs (V0, V1, ...)
|
||||
FloatFunc: String, describing math expression.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def define_schema(cls) -> io.Schema:
|
||||
return io.Schema(
|
||||
node_id="mrmth_ag_FloatMathNode",
|
||||
category="More math/math",
|
||||
display_name="Float math",
|
||||
inputs=[
|
||||
io.Autogrow.Input(
|
||||
id="V",
|
||||
template=io.Autogrow.TemplatePrefix(
|
||||
io.Float.Input("values"), prefix="V", min=1, max=50
|
||||
),
|
||||
),
|
||||
io.MultiType.Input(
|
||||
io.String.Input("FloatFunc", default="V0", multiline=False),
|
||||
types=[io.String, MrmthParseTree],
|
||||
tooltip="Expression to use on inputs",
|
||||
),
|
||||
io.Boolean.Input(
|
||||
id="remember_stack",
|
||||
default=False,
|
||||
display_name="Remember stack across batch",
|
||||
tooltip=(
|
||||
"If enabled, stack is copied at output leading to changes being remembered during batch operations (node runs multiple times in sucession). If disabled each batch gets it's own copy of the stack."
|
||||
),
|
||||
),
|
||||
MrmthStack.Input(
|
||||
id="stack", tooltip="Access stack between nodes", optional=True
|
||||
),
|
||||
],
|
||||
outputs=[
|
||||
io.Float.Output(),
|
||||
MrmthStack.Output(),
|
||||
],
|
||||
)
|
||||
|
||||
tooltip = cleandoc(__doc__)
|
||||
|
||||
@classmethod
|
||||
def check_lazy_status(cls, FloatFunc, V, remember_stack=False, stack={}):
|
||||
# remember_stack ani stack nemění lazy logiku
|
||||
return checkLazyNew(FloatFunc, V, V)
|
||||
|
||||
@classmethod
|
||||
def execute(cls, FloatFunc, V, remember_stack=False, stack={}):
|
||||
work_stack = stack if remember_stack else (copy.deepcopy(stack) if stack is not None else {})
|
||||
|
||||
variables = {}
|
||||
# Populate aliases
|
||||
variables["a"] = V.get("V0", 0.0)
|
||||
variables["b"] = V.get("V1", 0.0)
|
||||
variables["c"] = V.get("V2", 0.0)
|
||||
variables["d"] = V.get("V3", 0.0)
|
||||
variables["w"] = V.get("V4", 0.0)
|
||||
variables["x"] = V.get("V5", 0.0)
|
||||
variables["y"] = V.get("V6", 0.0)
|
||||
variables["z"] = V.get("V7", 0.0)
|
||||
|
||||
# Populate all V inputs
|
||||
for k, val in V.items():
|
||||
variables[k] = val if val is not None else 0.0
|
||||
|
||||
v_stacked, v_cnt = get_v_variable(variables)
|
||||
if v_stacked is not None:
|
||||
variables["V"] = v_stacked
|
||||
variables["Vcnt"] = float(v_cnt)
|
||||
variables["V_count"] = float(v_cnt)
|
||||
|
||||
tree = None
|
||||
if isinstance(FloatFunc, str):
|
||||
tree = parse_expr(FloatFunc)
|
||||
else:
|
||||
tree = FloatFunc
|
||||
# scalar execution
|
||||
visitor = UnifiedMathVisitor(variables, [1], state_storage=work_stack)
|
||||
result = visitor.visit(tree)
|
||||
|
||||
# Result might be float or tensor(scalar)
|
||||
if torch.is_tensor(result):
|
||||
result = result.flatten()[0].item()
|
||||
returned_stack = work_stack if remember_stack else copy.deepcopy(work_stack)
|
||||
return (float(result), returned_stack)
|
||||
@@ -0,0 +1,322 @@
|
||||
import torch
|
||||
from .Parser.UnifiedMathVisitor import UnifiedMathVisitor
|
||||
from .helper_functions import (
|
||||
checkLazyNew,
|
||||
generate_dim_variables,
|
||||
getIndexTensorAlongDim,
|
||||
parse_expr,
|
||||
make_zero_like,
|
||||
as_tensor,
|
||||
get_v_variable,
|
||||
get_f_variable,
|
||||
LazyVariableDict,
|
||||
)
|
||||
from comfy_api.latest import io
|
||||
import comfy.sampler_helpers
|
||||
import comfy.model_patcher
|
||||
import comfy.utils
|
||||
import comfy.hooks
|
||||
import comfy.samplers
|
||||
from .Stack import MrmthStack
|
||||
from .ParseTree import MrmthParseTree
|
||||
import copy
|
||||
|
||||
|
||||
class GuiderMathNode(io.ComfyNode):
|
||||
"""
|
||||
Enables math expressions on Guiders (sampler inputs) with autogrow support.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def define_schema(cls) -> io.Schema:
|
||||
return io.Schema(
|
||||
node_id="mrmth_ag_GuiderMathNode",
|
||||
category="More math/math",
|
||||
display_name="Guider math",
|
||||
inputs=[
|
||||
io.Autogrow.Input(id="V", template=io.Autogrow.TemplatePrefix(io.Guider.Input("values"), prefix="V", min=1, max=50)),
|
||||
io.Autogrow.Input(id="F", template=io.Autogrow.TemplatePrefix(io.Float.Input("float", default=0.0, optional=True, lazy=True, force_input=True), prefix="F", min=1, max=50)),
|
||||
io.MultiType.Input(
|
||||
io.String.Input("Expression", default="V0", multiline=False),
|
||||
types=[io.String,MrmthParseTree],
|
||||
tooltip="Expression to apply on input guiders. Aliases: a=G0, b=G1, c=G2, d=G3, w=F0, x=F1, y=F2, z=F3. Context: steps, current_step",
|
||||
),
|
||||
io.MultiType.Input(
|
||||
io.String.Input("Expression1", default="sample", multiline=False),
|
||||
types=[io.String,MrmthParseTree],
|
||||
tooltip="Expression to apply after generation finishes.",
|
||||
),
|
||||
io.Boolean.Input(
|
||||
id="remember_stack",
|
||||
default=False,
|
||||
display_name="Remember stack across batch",
|
||||
tooltip=(
|
||||
"If enabled, stack is copied at output leading to changes being remembered during batch operations (node runs multiple times in sucession). If disabled each batch gets it's own copy of the stack."
|
||||
),
|
||||
),
|
||||
MrmthStack.Input(id="stack", tooltip="Access stack between nodes",optional=True)
|
||||
],
|
||||
outputs=[
|
||||
io.Guider.Output(),
|
||||
MrmthStack.Output()
|
||||
],
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def check_lazy_status(cls, Expression,Expression1, V, F,remember_stack=True,stack={}):
|
||||
d = checkLazyNew(Expression,V,F)
|
||||
b = checkLazyNew(Expression1,V,F)
|
||||
return d|b
|
||||
|
||||
|
||||
@classmethod
|
||||
def execute(cls, V, F, Expression, Expression1,remember_stack = True, stack=None):
|
||||
stack = stack if remember_stack else (copy.deepcopy(stack) if stack is not None else {})
|
||||
guider = MathGuider(V, F, Expression, Expression1, stack);
|
||||
stack = stack if remember_stack else copy.deepcopy(stack)
|
||||
return (guider, stack)
|
||||
|
||||
|
||||
class MathGuider:
|
||||
def __init__(self, V, F, expression,expression1,stack):
|
||||
self.V = V
|
||||
self.F = F
|
||||
self.expression = expression
|
||||
if isinstance(expression,str):
|
||||
self.tree = parse_expr(expression)
|
||||
else:
|
||||
self.tree = expression
|
||||
if isinstance(expression1,str):
|
||||
self.tree1 = parse_expr(expression1)
|
||||
else:
|
||||
self.tree1 = expression1
|
||||
self.inner_model = None # Will be set during sample
|
||||
self.sigmas = None
|
||||
self.current_step = 0
|
||||
self.steps = 0
|
||||
self.stck = stack
|
||||
|
||||
@property
|
||||
def model_patcher(self):
|
||||
for v in self.V.values():
|
||||
if v is not None and hasattr(v, "model_patcher"):
|
||||
return v.model_patcher
|
||||
|
||||
return None
|
||||
|
||||
@property
|
||||
def cfg(self):
|
||||
"""Delegate cfg to the primary guider."""
|
||||
for v in self.V.values():
|
||||
if v is not None and hasattr(v, "cfg"):
|
||||
return v.cfg
|
||||
return 1.0 # Default value if no guider has cfg
|
||||
|
||||
@cfg.setter
|
||||
def cfg(self, value):
|
||||
"""Set cfg on all guiders that support it."""
|
||||
for v in self.V.values():
|
||||
if v is not None and hasattr(v, "cfg"):
|
||||
v.cfg = value
|
||||
|
||||
def __call__(self, x, sigma, model_options={}, seed=None):
|
||||
lazy_g_results = {}
|
||||
|
||||
zero_cache = [None]
|
||||
def get_zero_cache():
|
||||
if zero_cache[0] is None:
|
||||
zero_cache[0] = torch.zeros_like(x)
|
||||
return zero_cache[0]
|
||||
|
||||
def make_lazy(guider_obj):
|
||||
def lazy_eval():
|
||||
if guider_obj is not None:
|
||||
return guider_obj(x, sigma, model_options=model_options, seed=seed)
|
||||
return get_zero_cache()
|
||||
lazy_eval.is_lazy_var = True
|
||||
return lazy_eval
|
||||
|
||||
for k, guider in self.V.items():
|
||||
lazy_g_results[k] = make_lazy(guider)
|
||||
|
||||
eval_samples, variables = self.setVars(x, sigma, seed, lazy_g_results)
|
||||
visitor = UnifiedMathVisitor(variables, eval_samples.shape,eval_samples.device,state_storage=self.stck)
|
||||
result_tensor = visitor.visit(self.tree)
|
||||
self.current_step = self.current_step + 1;
|
||||
return as_tensor(result_tensor, eval_samples.shape).to(x.device)
|
||||
|
||||
|
||||
def setVars(self, x, sigma, seed, g_results):
|
||||
eval_samples = x
|
||||
ndim = eval_samples.ndim
|
||||
|
||||
batch_dim = 0
|
||||
channel_dim = 1
|
||||
height_dim = 2
|
||||
width_dim = 3
|
||||
time_dim = None
|
||||
|
||||
if ndim >= 5:
|
||||
time_dim = 2
|
||||
channel_dim = 1
|
||||
height_dim = 3
|
||||
width_dim = 4
|
||||
|
||||
frame_count = eval_samples.shape[time_dim] if time_dim is not None else 1
|
||||
|
||||
def make_lazy_var(value_fn):
|
||||
value_fn.is_lazy_var = True
|
||||
return value_fn
|
||||
|
||||
variables = LazyVariableDict()
|
||||
variables.update({
|
||||
"w": self.F.get("F0", 0.0),
|
||||
"x": self.F.get("F1", 0.0),
|
||||
"y": self.F.get("F2", 0.0),
|
||||
"z": self.F.get("F3", 0.0),
|
||||
"B": getIndexTensorAlongDim(eval_samples, batch_dim),
|
||||
"batch": getIndexTensorAlongDim(eval_samples, batch_dim),
|
||||
"W": float(eval_samples.shape[width_dim]) if width_dim < ndim else 0.0,
|
||||
"width": float(eval_samples.shape[width_dim]) if width_dim < ndim else 0.0,
|
||||
"H": float(eval_samples.shape[height_dim]) if height_dim < ndim else 0.0,
|
||||
"height": float(eval_samples.shape[height_dim]) if height_dim < ndim else 0.0,
|
||||
"T": float(frame_count),
|
||||
"batch_count": float(eval_samples.shape[0]),
|
||||
"N": float(eval_samples.shape[channel_dim]) if channel_dim < ndim else 0.0,
|
||||
"channel_count": float(eval_samples.shape[channel_dim]) if channel_dim < ndim else 0.0,
|
||||
"sigma": sigma.item() if isinstance(sigma,torch.Tensor) else sigma,
|
||||
"seed": seed if seed is not None else 0,
|
||||
"steps": self.steps,
|
||||
"current_step": self.current_step,
|
||||
"sample": x
|
||||
})
|
||||
if g_results is not None:
|
||||
for k, func in g_results.items():
|
||||
variables[k] = func
|
||||
|
||||
variables["a"] = make_lazy_var(lambda: variables["V0"] if "V0" in g_results else make_zero_like(eval_samples))
|
||||
variables["b"] = make_lazy_var(lambda: variables["V1"] if "V1" in g_results else make_zero_like(eval_samples))
|
||||
variables["c"] = make_lazy_var(lambda: variables["V2"] if "V2" in g_results else make_zero_like(eval_samples))
|
||||
variables["d"] = make_lazy_var(lambda: variables["V3"] if "V3" in g_results else make_zero_like(eval_samples))
|
||||
|
||||
def lazy_stacked_v():
|
||||
evaluated_g_results = {k: variables[k] for k in g_results.keys()}
|
||||
v_stacked, v_cnt = get_v_variable(evaluated_g_results)
|
||||
return v_stacked
|
||||
variables["V"] = make_lazy_var(lazy_stacked_v)
|
||||
|
||||
def lazy_v_cnt():
|
||||
evaluated_g_results = {k: variables[k] for k in g_results.keys()}
|
||||
v_stacked, v_cnt = get_v_variable(evaluated_g_results)
|
||||
return float(v_cnt)
|
||||
variables["Vcnt"] = make_lazy_var(lazy_v_cnt)
|
||||
variables["V_count"] = variables["Vcnt"]
|
||||
|
||||
for k, v in self.F.items():
|
||||
variables[k] = v if v is not None else 0.0
|
||||
|
||||
f_stacked, f_cnt = get_f_variable(self.F)
|
||||
if f_stacked is not None:
|
||||
variables["F"] = f_stacked
|
||||
variables["Fcnt"] = float(f_cnt)
|
||||
variables["F_count"] = float(f_cnt)
|
||||
|
||||
variables.update(generate_dim_variables(eval_samples))
|
||||
return eval_samples,variables
|
||||
|
||||
def sample(self, noise, latent_image, sampler, sigmas, denoise_mask=None, callback=None, disable_pbar=False, seed=None):
|
||||
self.sigmas = sigmas
|
||||
self.steps = len(sigmas)
|
||||
if sigmas.shape[-1] == 0:
|
||||
return latent_image
|
||||
active_guiders = [g for g in self.V.values() if g is not None]
|
||||
if not active_guiders:
|
||||
return latent_image
|
||||
|
||||
primary_guider = active_guiders[0]
|
||||
cleanup_items = []
|
||||
|
||||
try:
|
||||
for guider in active_guiders:
|
||||
if hasattr(guider, "original_conds"):
|
||||
guider.conds = {}
|
||||
for k in guider.original_conds:
|
||||
guider.conds[k] = list(map(lambda a: a.copy(), guider.original_conds[k]))
|
||||
|
||||
if hasattr(comfy.samplers, "preprocess_conds_hooks") and hasattr(guider, "conds"):
|
||||
comfy.samplers.preprocess_conds_hooks(guider.conds)
|
||||
|
||||
if hasattr(guider, "model_patcher"):
|
||||
guider._orig_model_options = guider.model_options
|
||||
guider.model_options = comfy.model_patcher.create_model_options_clone(guider.model_options)
|
||||
comfy.sampler_helpers.prepare_model_patcher(guider.model_patcher, guider.conds, guider.model_options)
|
||||
if hasattr(comfy.samplers, "filter_registered_hooks_on_conds"):
|
||||
comfy.samplers.filter_registered_hooks_on_conds(guider.conds, guider.model_options)
|
||||
|
||||
guider.inner_model, guider.conds, guider.loaded_models = comfy.sampler_helpers.prepare_sampling(
|
||||
guider.model_patcher, noise.shape, guider.conds, guider.model_options
|
||||
)
|
||||
|
||||
cleanup_items.append(guider)
|
||||
|
||||
device = primary_guider.model_patcher.load_device
|
||||
noise = noise.to(device)
|
||||
latent_image = latent_image.to(device)
|
||||
sigmas = sigmas.to(device)
|
||||
|
||||
for guider in active_guiders:
|
||||
if hasattr(guider, "model_options"):
|
||||
comfy.samplers.cast_to_load_options(guider.model_options, device=device, dtype=guider.model_patcher.model_dtype())
|
||||
|
||||
patchers = set(g.model_patcher for g in active_guiders if hasattr(g, "model_patcher"))
|
||||
for p in patchers:
|
||||
p.pre_run()
|
||||
|
||||
try:
|
||||
if latent_image is not None and torch.count_nonzero(latent_image) > 0:
|
||||
latent_image = primary_guider.inner_model.process_latent_in(latent_image)
|
||||
|
||||
for guider in active_guiders:
|
||||
if hasattr(guider, "inner_model") and hasattr(guider, "conds"):
|
||||
guider.conds = comfy.samplers.process_conds(
|
||||
guider.inner_model, noise, guider.conds, device, latent_image, denoise_mask, seed
|
||||
)
|
||||
|
||||
self.inner_model = primary_guider.inner_model
|
||||
|
||||
extra_model_options = comfy.model_patcher.create_model_options_clone(primary_guider.model_options)
|
||||
extra_model_options.setdefault("transformer_options", {})["sample_sigmas"] = sigmas
|
||||
extra_args = {"model_options": extra_model_options, "seed": seed}
|
||||
|
||||
output = sampler.sample(self, sigmas, extra_args, callback, noise, latent_image, denoise_mask, disable_pbar)
|
||||
|
||||
eval_samples, variables = self.setVars(output, 0.0, seed, None)
|
||||
variables["noise"] = noise
|
||||
variables["previous_sample"] = latent_image
|
||||
visitor = UnifiedMathVisitor(variables, eval_samples.shape,state_storage=self.stck)
|
||||
output = visitor.visit(self.tree1)
|
||||
|
||||
if hasattr(primary_guider.inner_model, "process_latent_out"):
|
||||
output = primary_guider.inner_model.process_latent_out(output.to(torch.float32))
|
||||
|
||||
return output
|
||||
|
||||
finally:
|
||||
for p in patchers:
|
||||
p.cleanup()
|
||||
|
||||
finally:
|
||||
for guider in cleanup_items:
|
||||
if hasattr(guider, "model_patcher") and hasattr(guider, "loaded_models"):
|
||||
comfy.sampler_helpers.cleanup_models(guider.conds, guider.loaded_models)
|
||||
if hasattr(guider, "_orig_model_options"):
|
||||
comfy.samplers.cast_to_load_options(guider.model_options, device=guider.model_patcher.offload_device)
|
||||
guider.model_options = guider._orig_model_options
|
||||
guider.model_patcher.restore_hook_patches()
|
||||
|
||||
del guider.inner_model
|
||||
del guider.loaded_models
|
||||
del guider.conds
|
||||
if hasattr(guider, "_orig_model_options"): del guider._orig_model_options
|
||||
|
||||
self.inner_model = None
|
||||
@@ -0,0 +1,166 @@
|
||||
from .helper_functions import generate_dim_variables, parse_expr, getIndexTensorAlongDim, as_tensor, normalize_to_common_shape, make_zero_like, get_v_variable, get_f_variable, checkLazyNew, get_tensor_device, move_to_device
|
||||
from .Parser.UnifiedMathVisitor import UnifiedMathVisitor
|
||||
from comfy_api.latest import io
|
||||
import comfy
|
||||
import torch
|
||||
from .Stack import MrmthStack
|
||||
from .ParseTree import MrmthParseTree
|
||||
import copy
|
||||
|
||||
|
||||
class ImageMathNode(io.ComfyNode):
|
||||
"""
|
||||
Enables math expressions on Images using Autogrow inputs.
|
||||
|
||||
Inputs:
|
||||
V: Autogrow image inputs (V0, V1, ...)
|
||||
F: Autogrow float inputs (F0, F1, ...)
|
||||
Image: Expression to apply on input images
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def define_schema(cls) -> io.Schema:
|
||||
return io.Schema(
|
||||
node_id="mrmth_ag_ImageMathNode",
|
||||
category="More math/math",
|
||||
display_name="Image math",
|
||||
inputs=[
|
||||
io.Autogrow.Input(id="V",template=io.Autogrow.TemplatePrefix(io.Image.Input("values"), prefix="V", min=1, max=50)),
|
||||
io.Autogrow.Input(id="F", template=io.Autogrow.TemplatePrefix(io.Float.Input("float", default=0.0, optional=True, lazy=True, force_input=True), prefix="F", min=1, max=50)),
|
||||
io.MultiType.Input(
|
||||
io.String.Input("Expression", default="V0", multiline=False),
|
||||
types=[io.String,MrmthParseTree],
|
||||
tooltip="Expression to apply on input images",
|
||||
),
|
||||
io.Combo.Input(
|
||||
id="length_mismatch",
|
||||
options=["do nothing","error","tile", "pad"],
|
||||
display_name="on size mismatch",
|
||||
default="error",
|
||||
tooltip="How to handle mismatched image batch sizes. tile: repeat shorter inputs; error: raise error on mismatch; pad: treat missing frames as zero."
|
||||
),
|
||||
io.Int.Input(id="batching", default=0),
|
||||
io.Boolean.Input(
|
||||
id="remember_stack",
|
||||
default=False,
|
||||
display_name="Remember stack across batch",
|
||||
tooltip=(
|
||||
"If enabled, stack is copied at output leading to changes being remembered during batch operations (node runs multiple times in sucession). If disabled each batch gets it's own copy of the stack."
|
||||
),
|
||||
),
|
||||
io.Boolean.Input(
|
||||
id="use_compute_device",
|
||||
default=True,
|
||||
display_name="Move tensors to GPU",
|
||||
tooltip="Temporarily copies image tensors to the compute device for math and moves the result back afterwards.",
|
||||
),
|
||||
MrmthStack.Input(id="stack", tooltip="Access stack between nodes",optional=True)
|
||||
],
|
||||
outputs=[
|
||||
io.Image.Output(is_output_list=True),
|
||||
MrmthStack.Output(),
|
||||
],
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def check_lazy_status(cls, Expression, V, F, length_mismatch="tile",batching=0,remember_stack=False,use_compute_device=True,stack={}):
|
||||
return checkLazyNew(Expression,V,F)
|
||||
|
||||
@classmethod
|
||||
def execute(cls, V, F, Expression, length_mismatch="error",batching=0,remember_stack=False,use_compute_device=True,stack={}):
|
||||
# I and F are Autogrow.Type which is dict[str, Any]
|
||||
|
||||
# Identify all present tensors and their keys
|
||||
tensor_keys = [k for k, v in V.items() if v is not None]
|
||||
if not tensor_keys:
|
||||
raise ValueError("At least one input is required.")
|
||||
|
||||
tensors = [V[k] for k in tensor_keys]
|
||||
original_device = get_tensor_device(tensors[0])
|
||||
compute_device = comfy.model_management.get_torch_device() if use_compute_device else original_device
|
||||
working_V = move_to_device(V, compute_device) if compute_device is not None and compute_device != original_device else V
|
||||
tensors = [working_V[k] for k in tensor_keys]
|
||||
stack = stack if remember_stack else (copy.deepcopy(stack) if stack is not None else {})
|
||||
|
||||
# Normalize all tensors together to find the common target shape
|
||||
normalized_tensors = normalize_to_common_shape(*tensors, mode=length_mismatch)
|
||||
V_norm = dict(zip(tensor_keys, normalized_tensors))
|
||||
|
||||
# Use first normalized tensor to establish the reference shape
|
||||
ref_tensor = normalized_tensors[0]
|
||||
common_shape = ref_tensor.shape
|
||||
|
||||
# Setup legacy variables a, b, c, d
|
||||
ae = V_norm.get("V0", make_zero_like(ref_tensor))
|
||||
be = V_norm.get("V1", make_zero_like(ae))
|
||||
ce = V_norm.get("V2", make_zero_like(ae))
|
||||
de = V_norm.get("V3", make_zero_like(ae))
|
||||
|
||||
# Ensure legacy variables are normalized in case they were zero-initialized
|
||||
ae, be, ce, de = normalize_to_common_shape(ae, be, ce, de, mode=length_mismatch)
|
||||
|
||||
if(length_mismatch == "error"):
|
||||
for name, tensor in V.items():
|
||||
if tensor is not None and tensor.shape[0] != common_shape[0]:
|
||||
raise ValueError(f"Input '{name}' has shape {tensor.shape[0]}, expected {common_shape[0]} to match largest input.")
|
||||
|
||||
variables = {
|
||||
"a": ae, "b": be, "c": ce, "d": de,
|
||||
"w": F.get("F0", 0.0) if F.get("F0") is not None else 0.0,
|
||||
"x": F.get("F1", 0.0) if F.get("F1") is not None else 0.0,
|
||||
"y": F.get("F2", 0.0) if F.get("F2") is not None else 0.0,
|
||||
"z": F.get("F3", 0.0) if F.get("F3") is not None else 0.0,
|
||||
"X": getIndexTensorAlongDim(ae, 2),
|
||||
"Y": getIndexTensorAlongDim(ae, 1),
|
||||
"B": getIndexTensorAlongDim(ae, 0),
|
||||
"batch": getIndexTensorAlongDim(ae, 0),
|
||||
"C": getIndexTensorAlongDim(ae, 3),
|
||||
"channel": getIndexTensorAlongDim(ae, 3),
|
||||
"W": float(ae.shape[2]),
|
||||
"width": float(ae.shape[2]),
|
||||
"H": float(ae.shape[1]),
|
||||
"height": float(ae.shape[1]),
|
||||
"T": float(ae.shape[0]),
|
||||
"batch_count": float(ae.shape[0]),
|
||||
"N": float(ae.shape[3]),
|
||||
"channel_count": float(ae.shape[3]),
|
||||
} | generate_dim_variables(ae)
|
||||
|
||||
# Add all dynamic inputs
|
||||
variables.update(V_norm)
|
||||
|
||||
v_stacked, v_cnt = get_v_variable(V_norm, length_mismatch=length_mismatch)
|
||||
if v_stacked is not None:
|
||||
variables["V"] = v_stacked
|
||||
variables["Vcnt"] = float(v_cnt)
|
||||
variables["V_count"] = float(v_cnt)
|
||||
|
||||
f_stacked, f_cnt = get_f_variable(F)
|
||||
if f_stacked is not None:
|
||||
variables["F"] = f_stacked
|
||||
variables["Fcnt"] = float(f_cnt)
|
||||
variables["F_count"] = float(f_cnt)
|
||||
|
||||
for k, val in F.items():
|
||||
variables[k] = val if val is not None else 0.0
|
||||
|
||||
tree = None
|
||||
if isinstance(Expression,str):
|
||||
tree = parse_expr(Expression)
|
||||
else:
|
||||
tree = Expression
|
||||
visitor = UnifiedMathVisitor(variables, ae.shape,ae.device,state_storage=stack)
|
||||
result = visitor.visit(tree)
|
||||
result = as_tensor(result, ae.shape)
|
||||
result = move_to_device(result, original_device)
|
||||
|
||||
if batching and batching > 0:
|
||||
res = torch.split(result, batching, dim=0)
|
||||
res_list = []
|
||||
for result_chunk in res:
|
||||
res_list.append(result_chunk)
|
||||
stack = stack if remember_stack else copy.deepcopy(stack)
|
||||
return (res_list, stack)
|
||||
else:
|
||||
stack = stack if remember_stack else copy.deepcopy(stack)
|
||||
return ([result], stack)
|
||||
@@ -0,0 +1,270 @@
|
||||
from inspect import cleandoc
|
||||
from comfy_api.latest import io
|
||||
from .helper_functions import (
|
||||
generate_dim_variables,
|
||||
getIndexTensorAlongDim,
|
||||
parse_expr,
|
||||
as_tensor,
|
||||
normalize_to_common_shape,
|
||||
make_zero_like,
|
||||
get_v_variable,
|
||||
get_f_variable,
|
||||
checkLazyNew,
|
||||
get_tensor_device,
|
||||
move_to_device,
|
||||
)
|
||||
from .Parser.UnifiedMathVisitor import UnifiedMathVisitor
|
||||
import comfy
|
||||
import torch
|
||||
from comfy.nested_tensor import NestedTensor
|
||||
from .Stack import MrmthStack
|
||||
from .ParseTree import MrmthParseTree
|
||||
import copy
|
||||
|
||||
class LatentMathNode(io.ComfyNode):
|
||||
"""
|
||||
This node enables the use of math expressions on Latents using Autogrow inputs.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def define_schema(cls) -> io.Schema:
|
||||
""" """
|
||||
return io.Schema(
|
||||
node_id="mrmth_ag_LatentMathNode",
|
||||
display_name="Latent math",
|
||||
category="More math/math",
|
||||
inputs=[
|
||||
io.Autogrow.Input(id="V",template=io.Autogrow.TemplatePrefix(io.Latent.Input("values"), prefix="V", min=1, max=50)),
|
||||
io.Autogrow.Input(id="F", template=io.Autogrow.TemplatePrefix(io.Float.Input("float", default=0.0, optional=True, lazy=True, force_input=True), prefix="F", min=1, max=50)),
|
||||
io.MultiType.Input(
|
||||
io.String.Input("Expression", default="V0", multiline=False),
|
||||
types=[io.String,MrmthParseTree],
|
||||
tooltip="Expression to apply on input latents",
|
||||
),
|
||||
io.Combo.Input(
|
||||
id="length_mismatch",
|
||||
options=["do nothing","error","tile", "pad"],
|
||||
display_name="on size mismatch",
|
||||
default="error",
|
||||
tooltip="How to handle mismatched latent batch sizes. tile: repeat shorter inputs; error: raise error on mismatch; pad: treat missing frames as zero."
|
||||
),
|
||||
io.Int.Input(id="batching"),
|
||||
io.Boolean.Input(
|
||||
id="remember_stack",
|
||||
default=False,
|
||||
display_name="Remember stack across batch",
|
||||
tooltip=(
|
||||
"If enabled, stack is copied at output leading to changes being remembered during batch operations (node runs multiple times in sucession). If disabled each batch gets it's own copy of the stack."
|
||||
),
|
||||
),
|
||||
io.Boolean.Input(
|
||||
id="use_compute_device",
|
||||
default=True,
|
||||
display_name="Move tensors to GPU",
|
||||
tooltip="Temporarily copies latent tensors to the compute device for math and moves the result back afterwards.",
|
||||
),
|
||||
MrmthStack.Input(id="stack", tooltip="Access stack between nodes",optional=True)
|
||||
],
|
||||
outputs=[
|
||||
io.Latent.Output(is_output_list=True),
|
||||
MrmthStack.Output(),
|
||||
|
||||
],
|
||||
)
|
||||
|
||||
tooltip = cleandoc(__doc__)
|
||||
|
||||
@classmethod
|
||||
def check_lazy_status(cls, Expression, V, F,batching, length_mismatch="tile",remember_stack=False,use_compute_device=True,stack={}):
|
||||
return checkLazyNew(Expression,V,F)
|
||||
|
||||
@classmethod
|
||||
def execute(cls, V, F, Expression,batching, length_mismatch="tile",remember_stack=False,use_compute_device=True,stack={}) -> io.NodeOutput:
|
||||
# Determine reference latent
|
||||
ref_latent = None
|
||||
for lat in V.values():
|
||||
if lat is not None:
|
||||
ref_latent = lat
|
||||
break
|
||||
|
||||
if ref_latent is None:
|
||||
raise ValueError("At least one input is required.")
|
||||
stack = stack if remember_stack else (copy.deepcopy(stack) if stack is not None else {})
|
||||
original_device = get_tensor_device(ref_latent.get("samples"))
|
||||
compute_device = comfy.model_management.get_torch_device() if use_compute_device else original_device
|
||||
working_V = move_to_device(V, compute_device) if compute_device is not None and compute_device != original_device else V
|
||||
|
||||
# Identify all present tensors and their keys.
|
||||
# NestedTensor inputs keep their original latent dict under the base key (V0, V1, ...)
|
||||
# so downstream nodes receive the original NestedTensor, while individual components
|
||||
# are also exposed as V0_0, V0_1, ... for math expressions.
|
||||
tensor_keys = []
|
||||
V_norm_samples = {}
|
||||
nested_component_keys = {} # maps original key -> list of expanded component variable names
|
||||
for k, v in working_V.items():
|
||||
if v is None:
|
||||
continue
|
||||
samples = v.get("samples")
|
||||
if getattr(samples, "is_nested", False):
|
||||
component_names = []
|
||||
for idx, t in enumerate(samples.tensors):
|
||||
comp_key = f"{k}_{idx}"
|
||||
V_norm_samples[comp_key] = t
|
||||
component_names.append(comp_key)
|
||||
nested_component_keys[k] = component_names
|
||||
# Keep the original NestedTensor under the base key so V0 returns it unchanged
|
||||
tensor_keys.append(k)
|
||||
V_norm_samples[k] = samples
|
||||
else:
|
||||
tensor_keys.append(k)
|
||||
V_norm_samples[k] = samples
|
||||
|
||||
# Normalize all together; NestedTensor base entries are skipped but other non-tensor values are still supported
|
||||
at_list = [V_norm_samples[k] for k in tensor_keys if torch.is_tensor(V_norm_samples[k])]
|
||||
if at_list:
|
||||
normalized_samples = normalize_to_common_shape(*at_list, mode=length_mismatch)
|
||||
norm_iter = iter(normalized_samples)
|
||||
for key in tensor_keys:
|
||||
if not torch.is_tensor(V_norm_samples[key]):
|
||||
continue
|
||||
V_norm_samples[key] = next(norm_iter)
|
||||
|
||||
# Add nested component variables to the list of available inputs for variable binding
|
||||
tensor_keys.extend([c for components in nested_component_keys.values() for c in components])
|
||||
|
||||
def _resolve_alias(base):
|
||||
# Aliases a/b/c/d should point to the first tensor component of a NestedTensor input
|
||||
components = nested_component_keys.get(base)
|
||||
if components:
|
||||
return V_norm_samples[components[0]]
|
||||
return V_norm_samples.get(base)
|
||||
|
||||
first_sample = next(iter(V_norm_samples.values()))
|
||||
if not torch.is_tensor(first_sample):
|
||||
# If the first value is a NestedTensor, grab its first component for metadata
|
||||
first_sample = first_sample.tensors[0]
|
||||
ae_res = _resolve_alias("V0")
|
||||
ae = ae_res if ae_res is not None else make_zero_like(first_sample)
|
||||
be_res = _resolve_alias("V1")
|
||||
be = be_res if be_res is not None else make_zero_like(ae)
|
||||
ce_res = _resolve_alias("V2")
|
||||
ce = ce_res if ce_res is not None else make_zero_like(ae)
|
||||
de_res = _resolve_alias("V3")
|
||||
de = de_res if de_res is not None else make_zero_like(ae)
|
||||
|
||||
# Ensure legacy are normalized
|
||||
ae, be, ce, de = normalize_to_common_shape(ae, be, ce, de, mode=length_mismatch)
|
||||
|
||||
if length_mismatch == "error":
|
||||
for name in tensor_keys:
|
||||
sample = V_norm_samples.get(name)
|
||||
if sample is None:
|
||||
continue
|
||||
if not torch.is_tensor(sample):
|
||||
continue
|
||||
if sample.shape[0] != ae.shape[0]:
|
||||
raise ValueError(f"Input '{name}' has shape {sample.shape[0]}, expected {ae.shape[0]} to match input.")
|
||||
|
||||
# parse expression once
|
||||
tree = None
|
||||
if isinstance(Expression,str):
|
||||
tree = parse_expr(Expression)
|
||||
else:
|
||||
tree = Expression
|
||||
|
||||
ndim = ae.ndim
|
||||
batch_dim = 0
|
||||
channel_dim = -3
|
||||
height_dim = -2
|
||||
width_dim = -1
|
||||
time_dim = None
|
||||
if ndim >= 5:
|
||||
time_dim = -4
|
||||
|
||||
frame_count = ae.shape[time_dim] if time_dim is not None else ae.shape[batch_dim]
|
||||
|
||||
variables = {
|
||||
"a": ae, "b": be, "c": ce, "d": de,
|
||||
"w": F.get("F0", 0.0) if F.get("F0") is not None else 0.0,
|
||||
"x": F.get("F1", 0.0) if F.get("F1") is not None else 0.0,
|
||||
"y": F.get("F2", 0.0) if F.get("F2") is not None else 0.0,
|
||||
"z": F.get("F3", 0.0) if F.get("F3") is not None else 0.0,
|
||||
"X": getIndexTensorAlongDim(ae, width_dim),
|
||||
"Y": getIndexTensorAlongDim(ae, height_dim),
|
||||
"B": getIndexTensorAlongDim(ae, batch_dim),
|
||||
"batch": getIndexTensorAlongDim(ae, batch_dim),
|
||||
"C": getIndexTensorAlongDim(ae, channel_dim),
|
||||
"channel": getIndexTensorAlongDim(ae, channel_dim),
|
||||
"W": float(ae.shape[width_dim]),
|
||||
"width": float(ae.shape[width_dim]),
|
||||
"H": float(ae.shape[height_dim]),
|
||||
"height": float(ae.shape[height_dim]),
|
||||
"T": float(frame_count),
|
||||
"batch_count": float(ae.shape[batch_dim]),
|
||||
"N": float(ae.shape[channel_dim]),
|
||||
"channel_count": float(ae.shape[channel_dim]),
|
||||
} | generate_dim_variables(ae)
|
||||
|
||||
if time_dim is not None:
|
||||
F_idx = getIndexTensorAlongDim(ae, time_dim)
|
||||
variables.update({"frame_idx": F_idx, "frame": F_idx, "frame_count": frame_count})
|
||||
|
||||
# Add all dynamic inputs
|
||||
variables.update(V_norm_samples)
|
||||
|
||||
v_stacked, v_cnt = get_v_variable(V_norm_samples, length_mismatch=length_mismatch)
|
||||
if v_stacked is not None:
|
||||
variables["V"] = v_stacked
|
||||
variables["Vcnt"] = float(v_cnt)
|
||||
variables["V_count"] = float(v_cnt)
|
||||
|
||||
f_stacked, f_cnt = get_f_variable(F)
|
||||
if f_stacked is not None:
|
||||
variables["F"] = f_stacked
|
||||
variables["Fcnt"] = float(f_cnt)
|
||||
variables["F_count"] = float(f_cnt)
|
||||
|
||||
for k, v in F.items():
|
||||
variables[k] = v if v is not None else 0.0
|
||||
|
||||
visitor = UnifiedMathVisitor(variables, ae.shape,ae.device,state_storage=stack)
|
||||
raw_result = visitor.visit(tree)
|
||||
result_t = as_tensor(raw_result, ae.shape)
|
||||
|
||||
result_t = move_to_device(result_t, original_device)
|
||||
result_latent = ref_latent.copy()
|
||||
|
||||
# If the visitor produced a NestedTensor (whole-tensor arithmetic on a NestedTensor
|
||||
# input), propagate that through as a single downstream-compatible latent.
|
||||
if getattr(result_t, "is_nested", False):
|
||||
rl = result_latent.copy()
|
||||
rl["samples"] = result_t
|
||||
stack = stack if remember_stack else copy.deepcopy(stack)
|
||||
return (move_to_device([rl], original_device), stack)
|
||||
|
||||
# If the visitor produced a list/tuple, emit each element as a separate latent.
|
||||
if isinstance(result_t, (list, tuple)) and result_t and isinstance(result_t[0], torch.Tensor):
|
||||
results = []
|
||||
for comp in result_t:
|
||||
rl = result_latent.copy()
|
||||
rl["samples"] = comp
|
||||
results.append(rl)
|
||||
stack = stack if remember_stack else copy.deepcopy(stack)
|
||||
return (move_to_device(results, original_device), stack)
|
||||
|
||||
if batching > 0:
|
||||
res = torch.split(result_t, batching)
|
||||
results = []
|
||||
for result_tensor in res:
|
||||
rl = result_latent.copy()
|
||||
rl["samples"] = result_tensor
|
||||
results.append(rl)
|
||||
stack = stack if remember_stack else copy.deepcopy(stack)
|
||||
return (move_to_device(results, original_device), stack)
|
||||
rl = result_latent.copy()
|
||||
rl["samples"] = result_t
|
||||
stack = stack if remember_stack else copy.deepcopy(stack)
|
||||
return (move_to_device([rl], original_device), stack)
|
||||
@@ -0,0 +1,80 @@
|
||||
from inspect import cleandoc
|
||||
from comfy_api.latest import io
|
||||
import copy
|
||||
import comfy.utils
|
||||
from .helper_functions import checkLazyNew
|
||||
from .Stack import MrmthStack
|
||||
from .ParseTree import MrmthParseTree
|
||||
from .loraDictCommon import calculate_lora_dict_autogrow, calculate_lora_dict_dict_mode
|
||||
|
||||
|
||||
class LoraMathNode(io.ComfyNode):
|
||||
"""
|
||||
This node enables the use of math expressions directly on raw LoRA
|
||||
tensors (LORA_MODEL: dict of lora_up.weight / lora_down.weight / alpha /
|
||||
diff tensors) using Autogrow inputs.
|
||||
|
||||
Unlike Model/VAE/CLIP Math, these tensors are not attached to a
|
||||
ModelPatcher, so there is no base-vs-patched distinction - the tensors
|
||||
already *are* the LoRA's deltas - and therefore no V{n}_d.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def define_schema(cls) -> io.Schema:
|
||||
return io.Schema(
|
||||
node_id="mrmth_ag_LoraMathNode",
|
||||
display_name="Lora Math",
|
||||
category="More math/math",
|
||||
inputs=[
|
||||
io.Autogrow.Input(id="V", template=io.Autogrow.TemplatePrefix(io.LoraModel.Input("values"), prefix="V", min=1, max=50)),
|
||||
io.Autogrow.Input(id="F", template=io.Autogrow.TemplatePrefix(io.Float.Input("float", default=0.0, optional=True, lazy=True, force_input=True), prefix="F", min=1, max=50)),
|
||||
io.MultiType.Input(
|
||||
io.String.Input("Expression", default="V0", multiline=False),
|
||||
types=[io.String, MrmthParseTree],
|
||||
tooltip="Expression to apply on LoRA tensors",
|
||||
),
|
||||
io.Boolean.Input(
|
||||
id="dict_mode",
|
||||
default=False,
|
||||
display_name="Dictionary mode",
|
||||
tooltip="Evaluates LoRA tensors as dictionaries. Per-layer and dimension variables are unavailable.",
|
||||
),
|
||||
io.Boolean.Input(
|
||||
id="use_compute_device",
|
||||
default=True,
|
||||
display_name="Move tensors to GPU",
|
||||
tooltip="Temporarily copies LoRA tensors to the compute device for math and moves the result back afterwards.",
|
||||
),
|
||||
MrmthStack.Input(id="stack", tooltip="Access stack between nodes", optional=True)
|
||||
],
|
||||
outputs=[
|
||||
io.LoraModel.Output(),
|
||||
MrmthStack.Output(),
|
||||
],
|
||||
)
|
||||
|
||||
tooltip = cleandoc(__doc__)
|
||||
|
||||
@classmethod
|
||||
def check_lazy_status(cls, Expression, V, F, use_compute_device=True, stack={}, dict_mode=False):
|
||||
return checkLazyNew(Expression, V, F)
|
||||
|
||||
@classmethod
|
||||
def execute(cls, V, F, Expression, use_compute_device=True, stack={}, dict_mode=False) -> io.NodeOutput:
|
||||
if not any(v is not None for v in V.values()):
|
||||
raise ValueError("At least one input LoRA is required.")
|
||||
stack = copy.deepcopy(stack) if stack is not None else {}
|
||||
|
||||
ref_lora = V.get("V0")
|
||||
if ref_lora is None:
|
||||
for m in V.values():
|
||||
if m is not None:
|
||||
ref_lora = m
|
||||
break
|
||||
layer_count = len(ref_lora) if ref_lora is not None else 0
|
||||
pbar = comfy.utils.ProgressBar(layer_count)
|
||||
|
||||
aliases = {"a": "V0", "b": "V1", "c": "V2", "d": "V3", "w": "F0", "x": "F1", "y": "F2", "z": "F3"}
|
||||
calculate_lora_dict = calculate_lora_dict_dict_mode if dict_mode else calculate_lora_dict_autogrow
|
||||
result = calculate_lora_dict(Expression, V=V, F=F, pbar=pbar, mapping=aliases, stack=stack, use_compute_device=use_compute_device)
|
||||
return (result, stack)
|
||||
@@ -0,0 +1,147 @@
|
||||
from .helper_functions import generate_dim_variables,parse_expr, getIndexTensorAlongDim, as_tensor, normalize_to_common_shape,make_zero_like, get_v_variable, get_f_variable, checkLazyNew
|
||||
from .Parser.UnifiedMathVisitor import UnifiedMathVisitor
|
||||
from comfy_api.latest import io
|
||||
import torch
|
||||
from .Stack import MrmthStack
|
||||
from .ParseTree import MrmthParseTree
|
||||
import copy
|
||||
|
||||
|
||||
class MaskMathNode(io.ComfyNode):
|
||||
"""
|
||||
Enables math expressions on Masks using Autogrow inputs.
|
||||
|
||||
Inputs:
|
||||
V: Autogrow mask inputs (V0, V1, ...)
|
||||
F: Autogrow float inputs (F0, F1, ...)
|
||||
Mask: Expression to apply on input masks
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def define_schema(cls) -> io.Schema:
|
||||
return io.Schema(
|
||||
node_id="mrmth_ag_MaskMathNode",
|
||||
category="More math/math",
|
||||
display_name="Mask math",
|
||||
inputs=[
|
||||
io.Autogrow.Input(id="V",template=io.Autogrow.TemplatePrefix(io.Mask.Input("values"), prefix="V", min=1, max=50)),
|
||||
io.Autogrow.Input(id="F", template=io.Autogrow.TemplatePrefix(io.Float.Input("float", default=0.0, optional=True, lazy=True, force_input=True), prefix="F", min=1, max=50)),
|
||||
io.MultiType.Input(
|
||||
io.String.Input("Expression", default="V0", multiline=False),
|
||||
types=[io.String,MrmthParseTree],
|
||||
tooltip="Expression to apply on input masks",
|
||||
),
|
||||
io.Combo.Input(
|
||||
id="length_mismatch",
|
||||
options=["do nothing","error","tile", "pad"],
|
||||
display_name="on size mismatch",
|
||||
default="error",
|
||||
tooltip="How to handle mismatched mask batch sizes. tile: repeat shorter inputs; error: raise error on mismatch; pad: treat missing frames as zero."
|
||||
),
|
||||
io.Int.Input(id="batching", default=0),
|
||||
io.Boolean.Input(
|
||||
id="remember_stack",
|
||||
default=False,
|
||||
display_name="Remember stack across batch",
|
||||
tooltip=(
|
||||
"If enabled, stack is copied at output leading to changes being remembered during batch operations (node runs multiple times in sucession). If disabled each batch gets it's own copy of the stack."
|
||||
),
|
||||
),
|
||||
MrmthStack.Input(id="stack", tooltip="Access stack between nodes",optional=True)
|
||||
],
|
||||
outputs=[
|
||||
io.Mask.Output(is_output_list=True),
|
||||
MrmthStack.Output(),
|
||||
],
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def check_lazy_status(cls, Expression, V, F, length_mismatch="tile",batching=0,remember_stack=False,stack={}):
|
||||
return checkLazyNew(Expression,V,F)
|
||||
|
||||
@classmethod
|
||||
def execute(cls, V, F, Expression, length_mismatch="tile",batching=0,remember_stack=False,stack={}):
|
||||
# Identify all present tensors and their keys
|
||||
tensor_keys = [k for k, v in V.items() if v is not None]
|
||||
if not tensor_keys:
|
||||
raise ValueError("At least one input is required.")
|
||||
|
||||
tensors = [V[k] for k in tensor_keys]
|
||||
stack = stack if remember_stack else (copy.deepcopy(stack) if stack is not None else {})
|
||||
# Normalize all tensors together
|
||||
normalized_tensors = normalize_to_common_shape(*tensors, mode=length_mismatch)
|
||||
V_norm = dict(zip(tensor_keys, normalized_tensors))
|
||||
|
||||
# Establish reference shape
|
||||
ref_tensor = normalized_tensors[0]
|
||||
common_shape = ref_tensor.shape
|
||||
|
||||
if(length_mismatch == "error"):
|
||||
for name, tensor in V.items():
|
||||
if tensor is not None and tensor.shape[0] != common_shape[0]:
|
||||
raise ValueError(f"Input '{name}' has shape {tensor.shape[0]}, expected {common_shape[0]} to match largest input.")
|
||||
|
||||
# Setup legacy variables a, b, c, d
|
||||
ae = V_norm.get("V0", make_zero_like(ref_tensor))
|
||||
be = V_norm.get("V1", make_zero_like(ae))
|
||||
ce = V_norm.get("V2", make_zero_like(ae))
|
||||
de = V_norm.get("V3", make_zero_like(ae))
|
||||
|
||||
# Ensure legacy are normalized
|
||||
ae, be, ce, de = normalize_to_common_shape(ae, be, ce, de, mode=length_mismatch)
|
||||
|
||||
variables = {
|
||||
"a": ae, "b": be, "c": ce, "d": de,
|
||||
"w": F.get("F0", 0.0) if F.get("F0") is not None else 0.0,
|
||||
"x": F.get("F1", 0.0) if F.get("F1") is not None else 0.0,
|
||||
"y": F.get("F2", 0.0) if F.get("F2") is not None else 0.0,
|
||||
"z": F.get("F3", 0.0) if F.get("F3") is not None else 0.0,
|
||||
"X": getIndexTensorAlongDim(ae, 2),
|
||||
"Y": getIndexTensorAlongDim(ae, 1),
|
||||
"B": getIndexTensorAlongDim(ae, 0),
|
||||
"batch": getIndexTensorAlongDim(ae, 0),
|
||||
"W": float(ae.shape[2]),
|
||||
"width": float(ae.shape[2]),
|
||||
"H": float(ae.shape[1]),
|
||||
"height": float(ae.shape[1]),
|
||||
"T": float(ae.shape[0]),
|
||||
"batch_count": float(ae.shape[0]),
|
||||
} | generate_dim_variables(ae)
|
||||
|
||||
v_stacked, v_cnt = get_v_variable(V_norm, length_mismatch=length_mismatch)
|
||||
if v_stacked is not None:
|
||||
variables["V"] = v_stacked
|
||||
variables["Vcnt"] = float(v_cnt)
|
||||
variables["V_count"] = float(v_cnt)
|
||||
|
||||
f_stacked, f_cnt = get_f_variable(F)
|
||||
if f_stacked is not None:
|
||||
variables["F"] = f_stacked
|
||||
variables["Fcnt"] = float(f_cnt)
|
||||
variables["F_count"] = float(f_cnt)
|
||||
|
||||
# Add all dynamic inputs
|
||||
variables.update(V_norm)
|
||||
|
||||
for k, val in F.items():
|
||||
variables[k] = val if val is not None else 0.0
|
||||
|
||||
tree = None
|
||||
if isinstance(Expression,str):
|
||||
tree = parse_expr(Expression)
|
||||
else:
|
||||
tree = Expression
|
||||
visitor = UnifiedMathVisitor(variables, ae.shape,ae.device,state_storage=stack)
|
||||
result = visitor.visit(tree)
|
||||
result = as_tensor(result, ae.shape)
|
||||
|
||||
if batching and batching > 0:
|
||||
res = torch.split(result, batching, dim=0)
|
||||
res_list = []
|
||||
for result_chunk in res:
|
||||
res_list.append(result_chunk)
|
||||
stack = stack if remember_stack else copy.deepcopy(stack)
|
||||
return (res_list, stack)
|
||||
else:
|
||||
stack = stack if remember_stack else copy.deepcopy(stack)
|
||||
return ([result], stack)
|
||||
@@ -0,0 +1,92 @@
|
||||
from inspect import cleandoc
|
||||
from comfy_api.latest import io
|
||||
from .helper_functions import checkLazyNew
|
||||
from .Stack import MrmthStack
|
||||
from .ParseTree import MrmthParseTree
|
||||
import copy
|
||||
import comfy.utils
|
||||
from .modelLikeCommon import calculate_patches_autogrow, calculate_patches_dict_mode
|
||||
|
||||
|
||||
class ModelMathNode(io.ComfyNode):
|
||||
"""
|
||||
This node enables the use of math expressions on Model weights (state_dict) using Autogrow inputs.
|
||||
Functionally acts as a custom model merge.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def define_schema(cls) -> io.Schema:
|
||||
return io.Schema(
|
||||
node_id="mrmth_ag_ModelMathNode",
|
||||
display_name="Model Math",
|
||||
category="More math/math",
|
||||
inputs=[
|
||||
io.Autogrow.Input(id="V",template=io.Autogrow.TemplatePrefix(io.Model.Input("values"), prefix="V", min=1, max=50)),
|
||||
io.Autogrow.Input(id="F", template=io.Autogrow.TemplatePrefix(io.Float.Input("float", default=0.0, optional=True, lazy=True, force_input=True), prefix="F", min=1, max=50)),
|
||||
io.MultiType.Input(
|
||||
io.String.Input("Expression", default="V0", multiline=False),
|
||||
types=[io.String,MrmthParseTree],
|
||||
tooltip="Expression to apply on weights",
|
||||
),
|
||||
io.Combo.Input(
|
||||
id="length_mismatch",
|
||||
options=["do nothing","error","tile", "pad"],
|
||||
display_name="on size mismatch",
|
||||
default="error",
|
||||
tooltip="How to handle mismatched layer counts. For models, this usually defaults to broadcast (zero for missing layers)."
|
||||
),
|
||||
io.Boolean.Input(
|
||||
id="dict_mode",
|
||||
default=False,
|
||||
display_name="Dictionary mode",
|
||||
tooltip="Evaluates model weights as dictionaries. Per-layer and dimension variables are unavailable.",
|
||||
),
|
||||
io.Boolean.Input(
|
||||
id="use_compute_device",
|
||||
default=True,
|
||||
display_name="Move tensors to GPU",
|
||||
tooltip="Temporarily copies model tensors to the compute device for math and moves the patches back afterwards.",
|
||||
),
|
||||
MrmthStack.Input(id="stack", tooltip="Access stack between nodes",optional=True)
|
||||
],
|
||||
outputs=[
|
||||
io.Model.Output(),
|
||||
MrmthStack.Output(),
|
||||
|
||||
],
|
||||
)
|
||||
|
||||
tooltip = cleandoc(__doc__)
|
||||
|
||||
@classmethod
|
||||
def check_lazy_status(cls, Expression, V, F, length_mismatch="tile",use_compute_device=True,stack={},dict_mode=False):
|
||||
return checkLazyNew(Expression,V,F)
|
||||
|
||||
@classmethod
|
||||
def execute(cls, V, F, Expression, length_mismatch="tile",use_compute_device=True,stack={},dict_mode=False) -> io.NodeOutput:
|
||||
# Determine reference model for cloning
|
||||
a = V.get("V0")
|
||||
stack = copy.deepcopy(stack) if stack is not None else {}
|
||||
|
||||
if a is None:
|
||||
# Try finding first valid model
|
||||
for m in V.values():
|
||||
if m is not None:
|
||||
a = m
|
||||
break
|
||||
if a is None:
|
||||
raise ValueError("At least one input model is required.")
|
||||
|
||||
layer_count = V.get("V0").model.state_dict().__len__() if hasattr(V.get("V0"), "model") and hasattr(V.get("V0").model, "state_dict") else 0
|
||||
pbar = comfy.utils.ProgressBar(layer_count)
|
||||
|
||||
|
||||
|
||||
aliases = {"a": "V0", "b": "V1", "c": "V2", "d": "V3", "w": "F0", "x": "F1", "y": "F2", "z": "F3"}
|
||||
calculate_patches = calculate_patches_dict_mode if dict_mode else calculate_patches_autogrow
|
||||
patches = calculate_patches(Expression, V=V, F=F,pbar=pbar, mapping=aliases,stack=stack,use_compute_device=use_compute_device)
|
||||
|
||||
out_model = a.clone()
|
||||
if patches:
|
||||
out_model.add_patches(patches, 1.0, 1.0)
|
||||
return (out_model,stack)
|
||||
@@ -0,0 +1,138 @@
|
||||
from .helper_functions import generate_dim_variables, as_tensor, parse_expr, getIndexTensorAlongDim, make_zero_like, get_v_variable, get_f_variable, checkLazyNew
|
||||
from comfy_api.latest import io
|
||||
import torch
|
||||
from .Parser.UnifiedMathVisitor import UnifiedMathVisitor
|
||||
from .Stack import MrmthStack
|
||||
from .ParseTree import MrmthParseTree
|
||||
import copy
|
||||
|
||||
class NoiseMathNode(io.ComfyNode):
|
||||
"""
|
||||
This node enables the use of math expressions on noise generators.
|
||||
inputs:
|
||||
a, b, c, d:
|
||||
Noise generators.
|
||||
w, x, y, z:
|
||||
Floats.
|
||||
Noise expression:
|
||||
The expression to apply on those noise generators.
|
||||
Note that variables X, Y, W, H, C, batch, batch_count, input_latent refer to input_latent.
|
||||
outputs:
|
||||
NOISE:
|
||||
The resulting noise generator.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def define_schema(cls) -> io.Schema:
|
||||
return io.Schema(
|
||||
node_id="mrmth_ag_NoiseMathNode",
|
||||
display_name="Noise math",
|
||||
category="More math/math",
|
||||
inputs=[
|
||||
io.Autogrow.Input(id="V",template=io.Autogrow.TemplatePrefix(io.Noise.Input("values"), prefix="V", min=1, max=50)),
|
||||
io.Autogrow.Input(id="F", template=io.Autogrow.TemplatePrefix(io.Float.Input("float", default=0.0, optional=True, lazy=True, force_input=True), prefix="F", min=1, max=50)),
|
||||
io.MultiType.Input(
|
||||
io.String.Input("Noise", default="V0", multiline=False),
|
||||
types=[io.String,MrmthParseTree],
|
||||
tooltip="Expression for noise",
|
||||
),
|
||||
io.Boolean.Input(
|
||||
id="remember_stack",
|
||||
default=False,
|
||||
display_name="Remember stack across batch",
|
||||
tooltip=(
|
||||
"If enabled, stack is copied at output leading to changes being remembered during batch operations (node runs multiple times in sucession). If disabled each batch gets it's own copy of the stack."
|
||||
),
|
||||
),
|
||||
MrmthStack.Input(id="stack", tooltip="Access stack between nodes",optional=True)
|
||||
],
|
||||
outputs=[
|
||||
io.Noise.Output(),
|
||||
MrmthStack.Output(),
|
||||
],
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def check_lazy_status(cls, Noise, V, F,remember_stack=False,stack={}):
|
||||
return checkLazyNew(Noise,V,F)
|
||||
|
||||
@classmethod
|
||||
def execute(cls, Noise, V,F,remember_stack=False,stack={}):
|
||||
stack = stack if remember_stack else (copy.deepcopy(stack) if stack is not None else {})
|
||||
executer = NoiseExecutor(V,F, Noise,stack)
|
||||
stack = stack if remember_stack else copy.deepcopy(stack)
|
||||
return (executer,stack)
|
||||
|
||||
|
||||
class NoiseExecutor:
|
||||
def __init__(self, V,F, expr,stack):
|
||||
self.V = V
|
||||
self.F = F
|
||||
if isinstance(expr,str):
|
||||
self.tree = parse_expr(expr)
|
||||
else:
|
||||
self.tree = expr
|
||||
self.stack = stack
|
||||
|
||||
seed = -1
|
||||
|
||||
def generate_noise(self, input_latent: torch.Tensor) -> torch.Tensor:
|
||||
samples = input_latent["samples"]
|
||||
|
||||
vals = {v: (self.V[v].generate_noise(input_latent) if self.V[v] is not None else make_zero_like(samples)) for v in self.V}
|
||||
|
||||
ndim = samples.ndim
|
||||
batch_dim = 0
|
||||
channel_dim = -3
|
||||
height_dim = -2
|
||||
width_dim = -1
|
||||
time_dim = None
|
||||
if ndim >= 5:
|
||||
time_dim = -4
|
||||
|
||||
frame_count = samples.shape[time_dim] if time_dim is not None else samples.shape[batch_dim]
|
||||
|
||||
B = getIndexTensorAlongDim(samples, batch_dim)
|
||||
W = getIndexTensorAlongDim(samples, width_dim)
|
||||
H = getIndexTensorAlongDim(samples, height_dim)
|
||||
C = getIndexTensorAlongDim(samples, channel_dim)
|
||||
|
||||
variables = {
|
||||
"a": vals.get("V0") if "V0" in vals else make_zero_like(samples),
|
||||
"b": vals.get("V1") if "V1" in vals else make_zero_like(samples),
|
||||
"c": vals.get("V2") if "V2" in vals else make_zero_like(samples),
|
||||
"d": vals.get("V3") if "V3" in vals else make_zero_like(samples),
|
||||
"w": self.F.get("F0", 0.0),
|
||||
"x": self.F.get("F1", 0.0),
|
||||
"y": self.F.get("F2", 0.0),
|
||||
"z": self.F.get("F3", 0.0),
|
||||
"B": B, "batch": B,
|
||||
"X": W, "width": float(samples.shape[width_dim]),
|
||||
"Y": H, "height": float(samples.shape[height_dim]),
|
||||
"C": C, "channel": C,
|
||||
"W": float(samples.shape[width_dim]), "H": float(samples.shape[height_dim]), "I": samples,
|
||||
"T": float(frame_count), "N": float(samples.shape[channel_dim]),
|
||||
"batch_count": float(samples.shape[batch_dim]), "channel_count": float(samples.shape[channel_dim]),
|
||||
"input_latent": samples,
|
||||
} | generate_dim_variables(samples) | vals | self.F
|
||||
|
||||
v_stacked, v_cnt = get_v_variable(vals)
|
||||
if v_stacked is not None:
|
||||
variables["V"] = v_stacked
|
||||
variables["Vcnt"] = float(v_cnt)
|
||||
variables["V_count"] = float(v_cnt)
|
||||
|
||||
f_stacked, f_cnt = get_f_variable(self.F)
|
||||
if f_stacked is not None:
|
||||
variables["F"] = f_stacked
|
||||
variables["Fcnt"] = float(f_cnt)
|
||||
variables["F_count"] = float(f_cnt)
|
||||
|
||||
if time_dim is not None:
|
||||
F = getIndexTensorAlongDim(samples, time_dim)
|
||||
variables.update({"frame": F, "frame_count": frame_count})
|
||||
|
||||
visitor = UnifiedMathVisitor(variables, samples.shape,samples.device,state_storage=self.stack)
|
||||
result = visitor.visit(self.tree)
|
||||
result = as_tensor(result, samples.shape)
|
||||
return result
|
||||
@@ -0,0 +1,14 @@
|
||||
import antlr4
|
||||
from comfy_api.latest import io
|
||||
|
||||
@io.comfytype(io_type="SYNTAX_TREE")
|
||||
class MrmthParseTree(io.ComfyTypeIO):
|
||||
Type = antlr4.ParseTreeVisitor # Python type hint
|
||||
|
||||
class Input(io.Input):
|
||||
def __init__(self, id: str, **kwargs):
|
||||
super().__init__(id, **kwargs)
|
||||
|
||||
class Output(io.Output):
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
@@ -0,0 +1,6 @@
|
||||
class LambdaFunction:
|
||||
__slots__ = ("params", "body", "env")
|
||||
def __init__(self, params, body, env):
|
||||
self.params = params
|
||||
self.body = body
|
||||
self.env = env.copy() if env is not None else {}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@@ -0,0 +1,412 @@
|
||||
SIN=1
|
||||
COS=2
|
||||
TAN=3
|
||||
ASIN=4
|
||||
ACOS=5
|
||||
ATAN=6
|
||||
ATAN2=7
|
||||
SINH=8
|
||||
COSH=9
|
||||
TANH=10
|
||||
ASINH=11
|
||||
ACOSH=12
|
||||
ATANH=13
|
||||
ABS=14
|
||||
SQRT=15
|
||||
LN=16
|
||||
LOG=17
|
||||
EXP=18
|
||||
SMIN=19
|
||||
SMAX=20
|
||||
TMIN=21
|
||||
TMAX=22
|
||||
TNORM=23
|
||||
SNORM=24
|
||||
FLOOR=25
|
||||
CEIL=26
|
||||
ROUND=27
|
||||
GAMMA=28
|
||||
POWE=29
|
||||
SIGM=30
|
||||
CLAMP=31
|
||||
SFFT=32
|
||||
SIFFT=33
|
||||
ANGL=34
|
||||
PRNT=35
|
||||
PRINT_SHAPE=36
|
||||
NVL=37
|
||||
LERP=38
|
||||
STEP=39
|
||||
SMOOTHSTEP=40
|
||||
FRACT=41
|
||||
RELU=42
|
||||
SOFTPLUS=43
|
||||
GELU=44
|
||||
SIGN=45
|
||||
MAP=46
|
||||
EZCONV=47
|
||||
CONV=48
|
||||
SWAP=49
|
||||
PERM=50
|
||||
RESHAPE=51
|
||||
RANGE=52
|
||||
LINSPACE=53
|
||||
LOGSPACE=54
|
||||
TOPK=55
|
||||
BOTK=56
|
||||
PINV=57
|
||||
SUM=58
|
||||
MEAN=59
|
||||
STD=60
|
||||
VAR=61
|
||||
QUARTILE=62
|
||||
PERCENTILE=63
|
||||
QUANTILE=64
|
||||
DOT=65
|
||||
MOMENT=66
|
||||
ERF=67
|
||||
ERFINV=68
|
||||
ANY=69
|
||||
ALL=70
|
||||
EDGE=71
|
||||
GAUSSIAN=72
|
||||
MEDIAN=73
|
||||
MODE=74
|
||||
CUMSUM=75
|
||||
CUMPROD=76
|
||||
TOPK_IND=77
|
||||
BOTK_IND=78
|
||||
CUBIC_EASE=79
|
||||
ELASTIC_EASE=80
|
||||
SINE_EASE=81
|
||||
SMOOTHERSTEP=82
|
||||
DIST=83
|
||||
REMAP=84
|
||||
COSSIM=85
|
||||
COUNT=86
|
||||
KEYS=87
|
||||
ENABLE_TRACKING=88
|
||||
DISABLE_TRACKING=89
|
||||
RESET_TRACKING=90
|
||||
GET_CHANGED_KEYS=91
|
||||
GET_ADDED_KEYS=92
|
||||
GET_REMOVED_KEYS=93
|
||||
GET_UNCHANGED_KEYS=94
|
||||
REPEAT=95
|
||||
FLATTEN=96
|
||||
APPEND=97
|
||||
GET_VALUE=98
|
||||
FLOW_APPLY=99
|
||||
BATCH_SHUFFLE=100
|
||||
MOTION_MASK=101
|
||||
FLOW_TO_IMAGE=102
|
||||
FLOW_MAG=103
|
||||
FLOW_ANG=104
|
||||
WHERE=105
|
||||
HISTOGRAM=106
|
||||
OVERLAY=107
|
||||
PAD=108
|
||||
CROSS=109
|
||||
MATMUL=110
|
||||
RIFE=111
|
||||
BNOT=112
|
||||
BITCOUNT=113
|
||||
SHAPE=114
|
||||
BAND=115
|
||||
XOR=116
|
||||
BOR=117
|
||||
TENSOR=118
|
||||
ADD_KEY=119
|
||||
REMOVE_KEY=120
|
||||
PUSH=121
|
||||
POP=122
|
||||
CLEAR=123
|
||||
HAS=124
|
||||
GET=125
|
||||
CONCAT=126
|
||||
INT=127
|
||||
FLOAT=128
|
||||
UPPER=129
|
||||
LOWER=130
|
||||
TRIM=131
|
||||
SPLIT=132
|
||||
JOIN=133
|
||||
SUBSTRING=134
|
||||
FIND=135
|
||||
REPLACE=136
|
||||
DILATE=137
|
||||
ERODE=138
|
||||
MORPH_OPEN=139
|
||||
MORPH_CLOSE=140
|
||||
RGB_TO_OKLAB=141
|
||||
RGB_TO_CIELAB=142
|
||||
OKLAB_TO_RGB=143
|
||||
CIELAB_TO_RGB=144
|
||||
RGB_TO_HSV=145
|
||||
HSV_TO_RGB=146
|
||||
INT_TO_RGB=147
|
||||
RGB_TO_INT=148
|
||||
INTERPOLATE_LINEAR=149
|
||||
INTERPOLATE_AREA=150
|
||||
INTERPOLATE_NEAREST=151
|
||||
TEXT_IMAGE=152
|
||||
AS_NESTED=153
|
||||
SVD=154
|
||||
DIAG=155
|
||||
IF=156
|
||||
ELSE=157
|
||||
WHILE=158
|
||||
FOR=159
|
||||
IN=160
|
||||
BREAK=161
|
||||
CONTINUE=162
|
||||
RETURN=163
|
||||
TIMESTAMP=164
|
||||
SORT=165
|
||||
ARGSORT=166
|
||||
ARGMIN=167
|
||||
ARGMAX=168
|
||||
SOFTMAX=169
|
||||
SOFTMIN=170
|
||||
UNIQUE=171
|
||||
FLIP=172
|
||||
STARTSWITH=173
|
||||
ENDSWITH=174
|
||||
SQUEEZE=175
|
||||
UNSQUEEZE=176
|
||||
ROLL=177
|
||||
COV=178
|
||||
CORR=179
|
||||
ENTROPY=180
|
||||
CROP=181
|
||||
NONE=182
|
||||
COORDS=183
|
||||
NOISE=184
|
||||
RAND=185
|
||||
CAUCHY=186
|
||||
EXPONENTIAL=187
|
||||
LOGNORMAL=188
|
||||
BERNOULLI=189
|
||||
POISSON=190
|
||||
GAMMADIST=191
|
||||
BETADIST=192
|
||||
LAPLACEDIST=193
|
||||
GUMBELDIST=194
|
||||
WEIBULLDIST=195
|
||||
CHI2DIST=196
|
||||
STUDENTTDIST=197
|
||||
PERLIN=198
|
||||
CELLULAR=199
|
||||
PLASMA=200
|
||||
RIDGED=201
|
||||
DOMAIN_WARP=202
|
||||
PLUS=203
|
||||
MINUS=204
|
||||
MULT=205
|
||||
DIV=206
|
||||
MOD=207
|
||||
POW=208
|
||||
LSHIFT=209
|
||||
RSHIFT=210
|
||||
GE=211
|
||||
GT=212
|
||||
LE=213
|
||||
LT=214
|
||||
EQ=215
|
||||
EQUEALS=216
|
||||
PLUS_EQ=217
|
||||
MINUS_EQ=218
|
||||
MULT_EQ=219
|
||||
DIV_EQ=220
|
||||
MOD_EQ=221
|
||||
NE=222
|
||||
PIPE=223
|
||||
LPAREN=224
|
||||
RPAREN=225
|
||||
COMMA=226
|
||||
SEMICOLON=227
|
||||
ARROW=228
|
||||
LBRACKET=229
|
||||
RBRACKET=230
|
||||
QUESTION=231
|
||||
COLON=232
|
||||
LBRACE=233
|
||||
RBRACE=234
|
||||
NUMBER=235
|
||||
CONSTANT=236
|
||||
STRING=237
|
||||
VARIABLE=238
|
||||
SL_COMMENT=239
|
||||
ML_COMMENT=240
|
||||
WS=241
|
||||
'sin'=1
|
||||
'cos'=2
|
||||
'tan'=3
|
||||
'asin'=4
|
||||
'acos'=5
|
||||
'atan'=6
|
||||
'atan2'=7
|
||||
'sinh'=8
|
||||
'cosh'=9
|
||||
'tanh'=10
|
||||
'asinh'=11
|
||||
'acosh'=12
|
||||
'atanh'=13
|
||||
'abs'=14
|
||||
'sqrt'=15
|
||||
'ln'=16
|
||||
'log'=17
|
||||
'exp'=18
|
||||
'smin'=19
|
||||
'smax'=20
|
||||
'tmin'=21
|
||||
'tmax'=22
|
||||
'tnorm'=23
|
||||
'snorm'=24
|
||||
'floor'=25
|
||||
'ceil'=26
|
||||
'round'=27
|
||||
'gamma'=28
|
||||
'pow'=29
|
||||
'sigm'=30
|
||||
'clamp'=31
|
||||
'fft'=32
|
||||
'ifft'=33
|
||||
'angle'=34
|
||||
'print'=35
|
||||
'lerp'=38
|
||||
'step'=39
|
||||
'smoothstep'=40
|
||||
'fract'=41
|
||||
'relu'=42
|
||||
'softplus'=43
|
||||
'gelu'=44
|
||||
'sign'=45
|
||||
'map'=46
|
||||
'swap'=49
|
||||
'range'=52
|
||||
'linspace'=53
|
||||
'logspace'=54
|
||||
'topk'=55
|
||||
'botk'=56
|
||||
'pinv'=57
|
||||
'sum'=58
|
||||
'mean'=59
|
||||
'std'=60
|
||||
'var'=61
|
||||
'quantile'=64
|
||||
'dot'=65
|
||||
'moment'=66
|
||||
'erf'=67
|
||||
'erfinv'=68
|
||||
'any'=69
|
||||
'all'=70
|
||||
'edge'=71
|
||||
'median'=73
|
||||
'mode'=74
|
||||
'cumsum'=75
|
||||
'cumprod'=76
|
||||
'smootherstep'=82
|
||||
'remap'=84
|
||||
'keys'=87
|
||||
'repeat'=95
|
||||
'flatten'=96
|
||||
'append'=97
|
||||
'get_value'=98
|
||||
'flow_apply'=99
|
||||
'motion_mask'=101
|
||||
'flow_to_image'=102
|
||||
'where'=105
|
||||
'overlay'=107
|
||||
'pad'=108
|
||||
'cross'=109
|
||||
'matmul'=110
|
||||
'rife'=111
|
||||
'shape'=114
|
||||
'tensor'=118
|
||||
'add_key'=119
|
||||
'stack_push'=121
|
||||
'stack_pop'=122
|
||||
'stack_clear'=123
|
||||
'stack_has'=124
|
||||
'stack_get'=125
|
||||
'int'=127
|
||||
'float'=128
|
||||
'upper'=129
|
||||
'lower'=130
|
||||
'trim'=131
|
||||
'split'=132
|
||||
'join'=133
|
||||
'find'=135
|
||||
'replace'=136
|
||||
'dilate'=137
|
||||
'erode'=138
|
||||
'morph_open'=139
|
||||
'morph_close'=140
|
||||
'rgb_to_oklab'=141
|
||||
'rgb_to_cielab'=142
|
||||
'oklab_to_rgb'=143
|
||||
'cielab_to_rgb'=144
|
||||
'rgb_to_hsv'=145
|
||||
'hsv_to_rgb'=146
|
||||
'int_to_rgb'=147
|
||||
'rgb_to_int'=148
|
||||
'interpolate_linear'=149
|
||||
'interpolate_area'=150
|
||||
'text_image'=152
|
||||
'if'=156
|
||||
'else'=157
|
||||
'while'=158
|
||||
'for'=159
|
||||
'in'=160
|
||||
'break'=161
|
||||
'continue'=162
|
||||
'return'=163
|
||||
'sort'=165
|
||||
'argsort'=166
|
||||
'argmin'=167
|
||||
'argmax'=168
|
||||
'softmax'=169
|
||||
'softmin'=170
|
||||
'unique'=171
|
||||
'flip'=172
|
||||
'startswith'=173
|
||||
'endswith'=174
|
||||
'squeeze'=175
|
||||
'unsqueeze'=176
|
||||
'roll'=177
|
||||
'cov'=178
|
||||
'entropy'=180
|
||||
'crop'=181
|
||||
'+'=203
|
||||
'-'=204
|
||||
'*'=205
|
||||
'/'=206
|
||||
'%'=207
|
||||
'^'=208
|
||||
'<<'=209
|
||||
'>>'=210
|
||||
'>='=211
|
||||
'>'=212
|
||||
'<='=213
|
||||
'<'=214
|
||||
'=='=215
|
||||
'='=216
|
||||
'+='=217
|
||||
'-='=218
|
||||
'*='=219
|
||||
'/='=220
|
||||
'%='=221
|
||||
'!='=222
|
||||
'|'=223
|
||||
'('=224
|
||||
')'=225
|
||||
','=226
|
||||
';'=227
|
||||
'->'=228
|
||||
'['=229
|
||||
']'=230
|
||||
'?'=231
|
||||
':'=232
|
||||
'{'=233
|
||||
'}'=234
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,412 @@
|
||||
SIN=1
|
||||
COS=2
|
||||
TAN=3
|
||||
ASIN=4
|
||||
ACOS=5
|
||||
ATAN=6
|
||||
ATAN2=7
|
||||
SINH=8
|
||||
COSH=9
|
||||
TANH=10
|
||||
ASINH=11
|
||||
ACOSH=12
|
||||
ATANH=13
|
||||
ABS=14
|
||||
SQRT=15
|
||||
LN=16
|
||||
LOG=17
|
||||
EXP=18
|
||||
SMIN=19
|
||||
SMAX=20
|
||||
TMIN=21
|
||||
TMAX=22
|
||||
TNORM=23
|
||||
SNORM=24
|
||||
FLOOR=25
|
||||
CEIL=26
|
||||
ROUND=27
|
||||
GAMMA=28
|
||||
POWE=29
|
||||
SIGM=30
|
||||
CLAMP=31
|
||||
SFFT=32
|
||||
SIFFT=33
|
||||
ANGL=34
|
||||
PRNT=35
|
||||
PRINT_SHAPE=36
|
||||
NVL=37
|
||||
LERP=38
|
||||
STEP=39
|
||||
SMOOTHSTEP=40
|
||||
FRACT=41
|
||||
RELU=42
|
||||
SOFTPLUS=43
|
||||
GELU=44
|
||||
SIGN=45
|
||||
MAP=46
|
||||
EZCONV=47
|
||||
CONV=48
|
||||
SWAP=49
|
||||
PERM=50
|
||||
RESHAPE=51
|
||||
RANGE=52
|
||||
LINSPACE=53
|
||||
LOGSPACE=54
|
||||
TOPK=55
|
||||
BOTK=56
|
||||
PINV=57
|
||||
SUM=58
|
||||
MEAN=59
|
||||
STD=60
|
||||
VAR=61
|
||||
QUARTILE=62
|
||||
PERCENTILE=63
|
||||
QUANTILE=64
|
||||
DOT=65
|
||||
MOMENT=66
|
||||
ERF=67
|
||||
ERFINV=68
|
||||
ANY=69
|
||||
ALL=70
|
||||
EDGE=71
|
||||
GAUSSIAN=72
|
||||
MEDIAN=73
|
||||
MODE=74
|
||||
CUMSUM=75
|
||||
CUMPROD=76
|
||||
TOPK_IND=77
|
||||
BOTK_IND=78
|
||||
CUBIC_EASE=79
|
||||
ELASTIC_EASE=80
|
||||
SINE_EASE=81
|
||||
SMOOTHERSTEP=82
|
||||
DIST=83
|
||||
REMAP=84
|
||||
COSSIM=85
|
||||
COUNT=86
|
||||
KEYS=87
|
||||
ENABLE_TRACKING=88
|
||||
DISABLE_TRACKING=89
|
||||
RESET_TRACKING=90
|
||||
GET_CHANGED_KEYS=91
|
||||
GET_ADDED_KEYS=92
|
||||
GET_REMOVED_KEYS=93
|
||||
GET_UNCHANGED_KEYS=94
|
||||
REPEAT=95
|
||||
FLATTEN=96
|
||||
APPEND=97
|
||||
GET_VALUE=98
|
||||
FLOW_APPLY=99
|
||||
BATCH_SHUFFLE=100
|
||||
MOTION_MASK=101
|
||||
FLOW_TO_IMAGE=102
|
||||
FLOW_MAG=103
|
||||
FLOW_ANG=104
|
||||
WHERE=105
|
||||
HISTOGRAM=106
|
||||
OVERLAY=107
|
||||
PAD=108
|
||||
CROSS=109
|
||||
MATMUL=110
|
||||
RIFE=111
|
||||
BNOT=112
|
||||
BITCOUNT=113
|
||||
SHAPE=114
|
||||
BAND=115
|
||||
XOR=116
|
||||
BOR=117
|
||||
TENSOR=118
|
||||
ADD_KEY=119
|
||||
REMOVE_KEY=120
|
||||
PUSH=121
|
||||
POP=122
|
||||
CLEAR=123
|
||||
HAS=124
|
||||
GET=125
|
||||
CONCAT=126
|
||||
INT=127
|
||||
FLOAT=128
|
||||
UPPER=129
|
||||
LOWER=130
|
||||
TRIM=131
|
||||
SPLIT=132
|
||||
JOIN=133
|
||||
SUBSTRING=134
|
||||
FIND=135
|
||||
REPLACE=136
|
||||
DILATE=137
|
||||
ERODE=138
|
||||
MORPH_OPEN=139
|
||||
MORPH_CLOSE=140
|
||||
RGB_TO_OKLAB=141
|
||||
RGB_TO_CIELAB=142
|
||||
OKLAB_TO_RGB=143
|
||||
CIELAB_TO_RGB=144
|
||||
RGB_TO_HSV=145
|
||||
HSV_TO_RGB=146
|
||||
INT_TO_RGB=147
|
||||
RGB_TO_INT=148
|
||||
INTERPOLATE_LINEAR=149
|
||||
INTERPOLATE_AREA=150
|
||||
INTERPOLATE_NEAREST=151
|
||||
TEXT_IMAGE=152
|
||||
AS_NESTED=153
|
||||
SVD=154
|
||||
DIAG=155
|
||||
IF=156
|
||||
ELSE=157
|
||||
WHILE=158
|
||||
FOR=159
|
||||
IN=160
|
||||
BREAK=161
|
||||
CONTINUE=162
|
||||
RETURN=163
|
||||
TIMESTAMP=164
|
||||
SORT=165
|
||||
ARGSORT=166
|
||||
ARGMIN=167
|
||||
ARGMAX=168
|
||||
SOFTMAX=169
|
||||
SOFTMIN=170
|
||||
UNIQUE=171
|
||||
FLIP=172
|
||||
STARTSWITH=173
|
||||
ENDSWITH=174
|
||||
SQUEEZE=175
|
||||
UNSQUEEZE=176
|
||||
ROLL=177
|
||||
COV=178
|
||||
CORR=179
|
||||
ENTROPY=180
|
||||
CROP=181
|
||||
NONE=182
|
||||
COORDS=183
|
||||
NOISE=184
|
||||
RAND=185
|
||||
CAUCHY=186
|
||||
EXPONENTIAL=187
|
||||
LOGNORMAL=188
|
||||
BERNOULLI=189
|
||||
POISSON=190
|
||||
GAMMADIST=191
|
||||
BETADIST=192
|
||||
LAPLACEDIST=193
|
||||
GUMBELDIST=194
|
||||
WEIBULLDIST=195
|
||||
CHI2DIST=196
|
||||
STUDENTTDIST=197
|
||||
PERLIN=198
|
||||
CELLULAR=199
|
||||
PLASMA=200
|
||||
RIDGED=201
|
||||
DOMAIN_WARP=202
|
||||
PLUS=203
|
||||
MINUS=204
|
||||
MULT=205
|
||||
DIV=206
|
||||
MOD=207
|
||||
POW=208
|
||||
LSHIFT=209
|
||||
RSHIFT=210
|
||||
GE=211
|
||||
GT=212
|
||||
LE=213
|
||||
LT=214
|
||||
EQ=215
|
||||
EQUEALS=216
|
||||
PLUS_EQ=217
|
||||
MINUS_EQ=218
|
||||
MULT_EQ=219
|
||||
DIV_EQ=220
|
||||
MOD_EQ=221
|
||||
NE=222
|
||||
PIPE=223
|
||||
LPAREN=224
|
||||
RPAREN=225
|
||||
COMMA=226
|
||||
SEMICOLON=227
|
||||
ARROW=228
|
||||
LBRACKET=229
|
||||
RBRACKET=230
|
||||
QUESTION=231
|
||||
COLON=232
|
||||
LBRACE=233
|
||||
RBRACE=234
|
||||
NUMBER=235
|
||||
CONSTANT=236
|
||||
STRING=237
|
||||
VARIABLE=238
|
||||
SL_COMMENT=239
|
||||
ML_COMMENT=240
|
||||
WS=241
|
||||
'sin'=1
|
||||
'cos'=2
|
||||
'tan'=3
|
||||
'asin'=4
|
||||
'acos'=5
|
||||
'atan'=6
|
||||
'atan2'=7
|
||||
'sinh'=8
|
||||
'cosh'=9
|
||||
'tanh'=10
|
||||
'asinh'=11
|
||||
'acosh'=12
|
||||
'atanh'=13
|
||||
'abs'=14
|
||||
'sqrt'=15
|
||||
'ln'=16
|
||||
'log'=17
|
||||
'exp'=18
|
||||
'smin'=19
|
||||
'smax'=20
|
||||
'tmin'=21
|
||||
'tmax'=22
|
||||
'tnorm'=23
|
||||
'snorm'=24
|
||||
'floor'=25
|
||||
'ceil'=26
|
||||
'round'=27
|
||||
'gamma'=28
|
||||
'pow'=29
|
||||
'sigm'=30
|
||||
'clamp'=31
|
||||
'fft'=32
|
||||
'ifft'=33
|
||||
'angle'=34
|
||||
'print'=35
|
||||
'lerp'=38
|
||||
'step'=39
|
||||
'smoothstep'=40
|
||||
'fract'=41
|
||||
'relu'=42
|
||||
'softplus'=43
|
||||
'gelu'=44
|
||||
'sign'=45
|
||||
'map'=46
|
||||
'swap'=49
|
||||
'range'=52
|
||||
'linspace'=53
|
||||
'logspace'=54
|
||||
'topk'=55
|
||||
'botk'=56
|
||||
'pinv'=57
|
||||
'sum'=58
|
||||
'mean'=59
|
||||
'std'=60
|
||||
'var'=61
|
||||
'quantile'=64
|
||||
'dot'=65
|
||||
'moment'=66
|
||||
'erf'=67
|
||||
'erfinv'=68
|
||||
'any'=69
|
||||
'all'=70
|
||||
'edge'=71
|
||||
'median'=73
|
||||
'mode'=74
|
||||
'cumsum'=75
|
||||
'cumprod'=76
|
||||
'smootherstep'=82
|
||||
'remap'=84
|
||||
'keys'=87
|
||||
'repeat'=95
|
||||
'flatten'=96
|
||||
'append'=97
|
||||
'get_value'=98
|
||||
'flow_apply'=99
|
||||
'motion_mask'=101
|
||||
'flow_to_image'=102
|
||||
'where'=105
|
||||
'overlay'=107
|
||||
'pad'=108
|
||||
'cross'=109
|
||||
'matmul'=110
|
||||
'rife'=111
|
||||
'shape'=114
|
||||
'tensor'=118
|
||||
'add_key'=119
|
||||
'stack_push'=121
|
||||
'stack_pop'=122
|
||||
'stack_clear'=123
|
||||
'stack_has'=124
|
||||
'stack_get'=125
|
||||
'int'=127
|
||||
'float'=128
|
||||
'upper'=129
|
||||
'lower'=130
|
||||
'trim'=131
|
||||
'split'=132
|
||||
'join'=133
|
||||
'find'=135
|
||||
'replace'=136
|
||||
'dilate'=137
|
||||
'erode'=138
|
||||
'morph_open'=139
|
||||
'morph_close'=140
|
||||
'rgb_to_oklab'=141
|
||||
'rgb_to_cielab'=142
|
||||
'oklab_to_rgb'=143
|
||||
'cielab_to_rgb'=144
|
||||
'rgb_to_hsv'=145
|
||||
'hsv_to_rgb'=146
|
||||
'int_to_rgb'=147
|
||||
'rgb_to_int'=148
|
||||
'interpolate_linear'=149
|
||||
'interpolate_area'=150
|
||||
'text_image'=152
|
||||
'if'=156
|
||||
'else'=157
|
||||
'while'=158
|
||||
'for'=159
|
||||
'in'=160
|
||||
'break'=161
|
||||
'continue'=162
|
||||
'return'=163
|
||||
'sort'=165
|
||||
'argsort'=166
|
||||
'argmin'=167
|
||||
'argmax'=168
|
||||
'softmax'=169
|
||||
'softmin'=170
|
||||
'unique'=171
|
||||
'flip'=172
|
||||
'startswith'=173
|
||||
'endswith'=174
|
||||
'squeeze'=175
|
||||
'unsqueeze'=176
|
||||
'roll'=177
|
||||
'cov'=178
|
||||
'entropy'=180
|
||||
'crop'=181
|
||||
'+'=203
|
||||
'-'=204
|
||||
'*'=205
|
||||
'/'=206
|
||||
'%'=207
|
||||
'^'=208
|
||||
'<<'=209
|
||||
'>>'=210
|
||||
'>='=211
|
||||
'>'=212
|
||||
'<='=213
|
||||
'<'=214
|
||||
'=='=215
|
||||
'='=216
|
||||
'+='=217
|
||||
'-='=218
|
||||
'*='=219
|
||||
'/='=220
|
||||
'%='=221
|
||||
'!='=222
|
||||
'|'=223
|
||||
'('=224
|
||||
')'=225
|
||||
','=226
|
||||
';'=227
|
||||
'->'=228
|
||||
'['=229
|
||||
']'=230
|
||||
'?'=231
|
||||
':'=232
|
||||
'{'=233
|
||||
'}'=234
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,54 @@
|
||||
from .antlr_router import get_antlr_modules
|
||||
_, _, MathExprVisitor = get_antlr_modules()
|
||||
|
||||
class MoreMathValidationVisitor(MathExprVisitor):
|
||||
def __init__(self, initial_variables, initial_functions):
|
||||
self.variables = set(initial_variables.keys())
|
||||
self.functions = set(initial_functions.keys())
|
||||
self._scope_stack = []
|
||||
|
||||
def visitIfStmt(self, ctx):
|
||||
self.visit(ctx.condition())
|
||||
|
||||
self.visit(ctx.thenBody())
|
||||
if ctx.elseBody():
|
||||
self.visit(ctx.elseBody())
|
||||
return None
|
||||
|
||||
def visitWhileStmt(self, ctx):
|
||||
self.visit(ctx.condition())
|
||||
self.visit(ctx.body())
|
||||
return None
|
||||
|
||||
def visitFunctionDef(self, ctx):
|
||||
func_name = ctx.VARIABLE().getText()
|
||||
self.functions.add(func_name)
|
||||
return None
|
||||
|
||||
def visitCallExp(self, ctx):
|
||||
func_name = ctx.VARIABLE().getText()
|
||||
if func_name not in self.functions and func_name not in self.variables:
|
||||
raise ValueError(f"{ctx.start.line}:{ctx.start.column}: unknown function or variable: {func_name}")
|
||||
|
||||
if ctx.exprList():
|
||||
for e in ctx.exprList().expr():
|
||||
self.visit(e)
|
||||
|
||||
return "NOT_NULL"
|
||||
def visitForStmt(self, ctx):
|
||||
iterator_name = ctx.VARIABLE().getText()
|
||||
source_expr = ctx.expr()
|
||||
self.visit(source_expr)
|
||||
self._scope_stack.append(self.variables.copy())
|
||||
|
||||
self.variables.add(iterator_name)
|
||||
|
||||
try:
|
||||
if ctx.block():
|
||||
self.visit(ctx.block())
|
||||
elif ctx.expr():
|
||||
self.visit(ctx.expr())
|
||||
finally:
|
||||
self.variables = self._scope_stack.pop()
|
||||
|
||||
return None
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,26 @@
|
||||
import sys
|
||||
import antlr4
|
||||
|
||||
def get_antlr_modules():
|
||||
runtime_version = "0.0.0"
|
||||
|
||||
try:
|
||||
if sys.version_info >= (3, 8):
|
||||
import importlib.metadata
|
||||
runtime_version = importlib.metadata.version("antlr4-python3-runtime")
|
||||
else:
|
||||
import pkg_resources
|
||||
runtime_version = pkg_resources.get_distribution("antlr4-python3-runtime").version
|
||||
except Exception:
|
||||
runtime_version = getattr(antlr4, "__version__", "4.13.2")
|
||||
|
||||
if runtime_version.startswith("4.9"):
|
||||
from .legacy.MathExprLexer import MathExprLexer
|
||||
from .legacy.MathExprParser import MathExprParser
|
||||
from .legacy.MathExprVisitor import MathExprVisitor
|
||||
else:
|
||||
from .grammer.MathExprLexer import MathExprLexer
|
||||
from .grammer.MathExprParser import MathExprParser
|
||||
from .grammer.MathExprVisitor import MathExprVisitor
|
||||
|
||||
return MathExprLexer, MathExprParser, MathExprVisitor
|
||||
@@ -0,0 +1,481 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Build script for the more_math ANTLR grammar and its generated symbol tables.
|
||||
|
||||
.DESCRIPTION
|
||||
1. Compiles more_math/Parser/MathExpr.g4 into more_math/Parser/grammer
|
||||
(the parser antlr_router.py loads for current antlr4-python3-runtime
|
||||
versions), using whatever ANTLR toolchain is available on this
|
||||
machine (an 'antlr4' launcher on PATH, or 'java -jar <jar>').
|
||||
2. Parses MathExpr.g4 itself (keywords, constants, function tokens and
|
||||
their doc-comment signatures/descriptions) and regenerates:
|
||||
- more_math/Parser/inbuilt_symbols.py
|
||||
- web/inbuilt_symbols.js
|
||||
so that the Python interpreter and the web autocomplete UI both read
|
||||
an up-to-date, identical view of the language's built-in symbols.
|
||||
|
||||
.PARAMETER SkipAntlr
|
||||
Skip step 1 (grammar compilation) and only regenerate the symbol tables.
|
||||
|
||||
.PARAMETER AntlrJar
|
||||
Path to an antlr-*-complete.jar to use for step 1. Defaults to
|
||||
$env:ANTLR_JAR, then tools\antlr.jar next to this script.
|
||||
|
||||
.PARAMETER AntlrLegacyJar
|
||||
Optional path to an ANTLR 4.9.x complete jar to use for regenerating
|
||||
more_math/Parser/legacy (the antlr4-python3-runtime==4.9.x compatibility
|
||||
path used by antlr_router.py). Defaults to $env:ANTLR_LEGACY_JAR. When
|
||||
not given, the 'antlr4' launcher (antlr4-tools) is tried instead, pinned
|
||||
to -AntlrLegacyVersion, same as the main grammar target.
|
||||
|
||||
.PARAMETER AntlrVersion
|
||||
ANTLR release to use when the 'antlr4' launcher comes from the
|
||||
antlr4-tools pip package (it otherwise queries Maven Central for the
|
||||
latest release on every run). Defaults to $env:ANTLR4_TOOLS_ANTLR_VERSION,
|
||||
then '4.13.2' to match the version more_math/Parser/grammer was last
|
||||
generated with. Ignored when using a plain jar/java invocation.
|
||||
|
||||
.PARAMETER AntlrLegacyVersion
|
||||
Same as -AntlrVersion, but for the more_math/Parser/legacy (ANTLR 4.9.x)
|
||||
target. Defaults to $env:ANTLR4_TOOLS_ANTLR_LEGACY_VERSION, then '4.9.3'
|
||||
to match the version more_math/Parser/legacy was last generated with.
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[switch]$SkipAntlr,
|
||||
[string]$AntlrJar = $env:ANTLR_JAR,
|
||||
[string]$AntlrLegacyJar = $env:ANTLR_LEGACY_JAR,
|
||||
[string]$AntlrVersion = $(if ($env:ANTLR4_TOOLS_ANTLR_VERSION) { $env:ANTLR4_TOOLS_ANTLR_VERSION } else { '4.13.2' }),
|
||||
[string]$AntlrLegacyVersion = $(if ($env:ANTLR4_TOOLS_ANTLR_LEGACY_VERSION) { $env:ANTLR4_TOOLS_ANTLR_LEGACY_VERSION } else { '4.9.3' })
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
# This script lives at more_math/Parser/compile.ps1, two levels under the
|
||||
# node root (more_math/Parser -> more_math -> node root).
|
||||
$ParserDir = $PSScriptRoot
|
||||
$NodeRoot = Split-Path (Split-Path $ParserDir -Parent) -Parent
|
||||
$GrammarFile = Join-Path $ParserDir 'MathExpr.g4'
|
||||
$GrammarOutDir = Join-Path $ParserDir 'grammer'
|
||||
$LegacyOutDir = Join-Path $ParserDir 'legacy'
|
||||
$WebDir = Join-Path $NodeRoot 'web'
|
||||
$PySymbolsPath = Join-Path $ParserDir 'inbuilt_symbols.py'
|
||||
$JsSymbolsPath = Join-Path $WebDir 'inbuilt_symbols.js'
|
||||
|
||||
if (-not (Test-Path $GrammarFile)) {
|
||||
throw "Grammar file not found: $GrammarFile"
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Step 1: ANTLR compilation
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
function Invoke-AntlrCompile {
|
||||
# Runs with cwd = $ParserDir and passes ANTLR just the grammar's leaf
|
||||
# filename, so the "# Generated from MathExpr.g4 by ANTLR ..." header it
|
||||
# writes into the output files stays a plain relative name instead of
|
||||
# embedding this machine's local absolute path.
|
||||
param(
|
||||
[Parameter(Mandatory)][string]$Grammar,
|
||||
[Parameter(Mandatory)][string]$OutDir,
|
||||
[string]$Jar,
|
||||
[string]$Version = $AntlrVersion
|
||||
)
|
||||
|
||||
$grammarLeaf = Split-Path -Leaf $Grammar
|
||||
|
||||
$antlrCmd = Get-Command antlr4 -ErrorAction SilentlyContinue
|
||||
if ($antlrCmd) {
|
||||
# antlr4-tools (the pip-installed launcher) otherwise hits Maven
|
||||
# Central on every invocation to resolve "latest"; pin it so the
|
||||
# build is reproducible and doesn't need that network call.
|
||||
$previousVersionEnv = $env:ANTLR4_TOOLS_ANTLR_VERSION
|
||||
$env:ANTLR4_TOOLS_ANTLR_VERSION = $Version
|
||||
Push-Location $ParserDir
|
||||
try {
|
||||
Write-Host "[antlr] antlr4 (v$Version) -Dlanguage=Python3 -visitor -o `"$OutDir`" `"$grammarLeaf`""
|
||||
& $antlrCmd.Source -Dlanguage=Python3 -visitor -o $OutDir $grammarLeaf
|
||||
if ($LASTEXITCODE -ne 0) { throw "antlr4 failed with exit code $LASTEXITCODE" }
|
||||
} finally {
|
||||
Pop-Location
|
||||
$env:ANTLR4_TOOLS_ANTLR_VERSION = $previousVersionEnv
|
||||
}
|
||||
return $true
|
||||
}
|
||||
|
||||
$javaCmd = Get-Command java -ErrorAction SilentlyContinue
|
||||
if (-not $javaCmd) {
|
||||
return $false
|
||||
}
|
||||
|
||||
if (-not $Jar) {
|
||||
$default = Join-Path $NodeRoot 'tools\antlr.jar'
|
||||
if (Test-Path $default) { $Jar = $default }
|
||||
}
|
||||
|
||||
if (-not $Jar -or -not (Test-Path $Jar)) {
|
||||
return $false
|
||||
}
|
||||
|
||||
Push-Location $ParserDir
|
||||
try {
|
||||
Write-Host "[antlr] java -jar `"$Jar`" -Dlanguage=Python3 -visitor -o `"$OutDir`" `"$grammarLeaf`""
|
||||
& $javaCmd.Source -jar $Jar -Dlanguage=Python3 -visitor -o $OutDir $grammarLeaf
|
||||
if ($LASTEXITCODE -ne 0) { throw "ANTLR jar invocation failed with exit code $LASTEXITCODE" }
|
||||
} finally {
|
||||
Pop-Location
|
||||
}
|
||||
return $true
|
||||
}
|
||||
|
||||
if ($SkipAntlr) {
|
||||
Write-Host "[antlr] Skipping grammar compilation (-SkipAntlr)."
|
||||
} else {
|
||||
$ok = Invoke-AntlrCompile -Grammar $GrammarFile -OutDir $GrammarOutDir -Jar $AntlrJar
|
||||
if (-not $ok) {
|
||||
Write-Warning ("ANTLR toolchain not found (no 'antlr4' on PATH and no usable jar). " +
|
||||
"Skipping grammar compilation; '$GrammarOutDir' was left untouched. " +
|
||||
"Install Java + antlr4-tools ('pip install antlr4-tools'), or set `$env:ANTLR_JAR " +
|
||||
"to an antlr-*-complete.jar, then re-run.")
|
||||
}
|
||||
|
||||
if ($AntlrLegacyJar -and -not (Test-Path $AntlrLegacyJar)) {
|
||||
Write-Warning "AntlrLegacyJar '$AntlrLegacyJar' does not exist; falling back to the antlr4 launcher for the legacy target."
|
||||
$AntlrLegacyJar = $null
|
||||
}
|
||||
|
||||
$legacyOk = Invoke-AntlrCompile -Grammar $GrammarFile -OutDir $LegacyOutDir -Jar $AntlrLegacyJar -Version $AntlrLegacyVersion
|
||||
if (-not $legacyOk) {
|
||||
Write-Warning ("ANTLR toolchain not found for the legacy (ANTLR 4.9.x) target. " +
|
||||
"Skipping legacy grammar compilation; '$LegacyOutDir' was left untouched. " +
|
||||
"Install antlr4-tools, or set `$env:ANTLR_LEGACY_JAR to an antlr-4.9.x-complete.jar, then re-run.")
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Step 2: parse MathExpr.g4 for its built-in symbol table
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
function Get-GrammarSections {
|
||||
param([Parameter(Mandatory)][string]$Text)
|
||||
|
||||
$funcStart = $Text.IndexOf('func0:')
|
||||
$lexerMarker = '// LEXER RULES'
|
||||
$lexerStart = $Text.IndexOf($lexerMarker)
|
||||
if ($funcStart -lt 0 -or $lexerStart -lt 0 -or $lexerStart -le $funcStart) {
|
||||
throw "Could not locate expected grammar sections ('func0:' / '$lexerMarker') - has MathExpr.g4's layout changed?"
|
||||
}
|
||||
|
||||
[pscustomobject]@{
|
||||
FuncSection = $Text.Substring($funcStart, $lexerStart - $funcStart)
|
||||
LexerSection = $Text.Substring($lexerStart)
|
||||
}
|
||||
}
|
||||
|
||||
function Get-LexerTokens {
|
||||
# Returns an ordered map of TOKEN_NAME -> raw string-literal alternatives,
|
||||
# for every lexer rule that defines at least one word-like literal
|
||||
# (i.e. skips punctuation/operator tokens and regex-only tokens like
|
||||
# NUMBER/STRING/VARIABLE/WS/comments, which have no literal alternatives).
|
||||
param([Parameter(Mandatory)][string]$LexerSection)
|
||||
|
||||
$tokenRegex = [regex]'(?ms)^[ \t]*([A-Z][A-Z0-9_]*)[ \t]*:[ \t]*(.*?);'
|
||||
$literalRegex = [regex]"'([a-zA-Z_][a-zA-Z0-9_]*)'"
|
||||
|
||||
$tokens = [ordered]@{}
|
||||
foreach ($m in $tokenRegex.Matches($LexerSection)) {
|
||||
$name = $m.Groups[1].Value
|
||||
$body = $m.Groups[2].Value
|
||||
$literals = @($literalRegex.Matches($body) | ForEach-Object { $_.Groups[1].Value })
|
||||
if ($literals.Count -gt 0) {
|
||||
$tokens[$name] = $literals
|
||||
}
|
||||
}
|
||||
return $tokens
|
||||
}
|
||||
|
||||
function Get-FunctionTokenNames {
|
||||
# A token counts as a "function" token when it is used as `TOKEN LPAREN`
|
||||
# inside the func0..funcNoise rules (the atom productions for calls).
|
||||
# This deliberately excludes structural keywords like IF/WHILE/FOR, which
|
||||
# are also followed by LPAREN but in ifStmt/whileStmt/forStmt, outside
|
||||
# this section.
|
||||
param([Parameter(Mandatory)][string]$FuncSection)
|
||||
|
||||
$names = New-Object System.Collections.Generic.HashSet[string]
|
||||
foreach ($m in [regex]::Matches($FuncSection, '\b([A-Z][A-Z0-9_]*)\s+LPAREN\b')) {
|
||||
[void]$names.Add($m.Groups[1].Value)
|
||||
}
|
||||
return $names
|
||||
}
|
||||
|
||||
function Get-UniqueLowerWords {
|
||||
# De-duplicates literal alternatives that only differ by case (e.g.
|
||||
# 'pi'/'PI', 'none'/'None'/'null'/'NULL') down to one lowercase spelling
|
||||
# per distinct word, preserving first-seen order.
|
||||
param([string[]]$Words)
|
||||
|
||||
$seen = New-Object System.Collections.Generic.HashSet[string]
|
||||
$result = New-Object System.Collections.Generic.List[string]
|
||||
foreach ($w in $Words) {
|
||||
$lw = $w.ToLowerInvariant()
|
||||
if ($seen.Add($lw)) { [void]$result.Add($lw) }
|
||||
}
|
||||
return $result
|
||||
}
|
||||
|
||||
function Get-FunctionDocEntries {
|
||||
# Pairs each `/** ... */` doc comment in the func section with the
|
||||
# lexer token of the call it documents (the `TOKEN LPAREN` that follows
|
||||
# the comment, skipping over an optional leading `|` alternation).
|
||||
param([Parameter(Mandatory)][string]$FuncSection)
|
||||
|
||||
$pattern = [regex]'(?s)/\*\*(?<doc>.*?)\*/\s*\|?\s*(?<token>[A-Z][A-Z0-9_]*)\s+LPAREN'
|
||||
$entries = New-Object System.Collections.Generic.List[object]
|
||||
foreach ($m in $pattern.Matches($FuncSection)) {
|
||||
$lines = @($m.Groups['doc'].Value -split "`r?`n" | ForEach-Object { $_.Trim() } | Where-Object { $_ -ne '' })
|
||||
if ($lines.Count -eq 0) { continue }
|
||||
$entries.Add([pscustomobject]@{
|
||||
Token = $m.Groups['token'].Value
|
||||
FirstLine = $lines[0]
|
||||
})
|
||||
}
|
||||
return $entries
|
||||
}
|
||||
|
||||
function Split-ArgSpec {
|
||||
# Parses a comma-separated arg list like "x, [dims]" or "x, ..." into
|
||||
# min/max argument counts and display names (brackets/ellipsis stripped).
|
||||
param([string]$ArgsText)
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($ArgsText)) {
|
||||
return [pscustomobject]@{ Min = 0; Max = 0; Variadic = $false; Display = @() }
|
||||
}
|
||||
|
||||
$min = 0
|
||||
$max = 0
|
||||
$variadic = $false
|
||||
$display = New-Object System.Collections.Generic.List[string]
|
||||
|
||||
foreach ($raw in ($ArgsText -split ',')) {
|
||||
$p = $raw.Trim()
|
||||
if ($p -eq '') { continue }
|
||||
if ($p -eq '...') { $variadic = $true; [void]$display.Add('...'); continue }
|
||||
$isOptional = $p.StartsWith('[') -and $p.EndsWith(']')
|
||||
$clean = $p.Trim('[', ']').Trim()
|
||||
[void]$display.Add($clean)
|
||||
$max++
|
||||
if (-not $isOptional) { $min++ }
|
||||
}
|
||||
|
||||
[pscustomobject]@{ Min = $min; Max = $max; Variadic = $variadic; Display = $display.ToArray() }
|
||||
}
|
||||
|
||||
function ConvertTo-FunctionSignature {
|
||||
# Parses a doc-comment first line such as:
|
||||
# "sum(x, [dims]) - computes the sum of elements of x, ..."
|
||||
# "rgb_to_hsv(rgb, [degrees]) / rgb_to_hsv(r, g, b, [degrees]) - converts ..."
|
||||
# into arg-count bounds, display arg names (from the first signature) and
|
||||
# the trailing description text.
|
||||
param([Parameter(Mandatory)][string]$Line)
|
||||
|
||||
$sigMatch = [regex]::Match($Line, '^[a-zA-Z_][a-zA-Z0-9_]*\((?<args>.*?)\)')
|
||||
if (-not $sigMatch.Success) { return $null }
|
||||
|
||||
$rest = $Line.Substring($sigMatch.Index + $sigMatch.Length)
|
||||
$spec1 = Split-ArgSpec -ArgsText $sigMatch.Groups['args'].Value
|
||||
|
||||
# Some functions document two call forms separated by " / "; fold the
|
||||
# second form's bounds in so min/max cover both (e.g. rgb_to_hsv).
|
||||
$altMatch = [regex]::Match($rest, '^\s*/\s*[a-zA-Z_][a-zA-Z0-9_]*\((?<args>.*?)\)')
|
||||
$spec2 = $null
|
||||
if ($altMatch.Success) {
|
||||
$spec2 = Split-ArgSpec -ArgsText $altMatch.Groups['args'].Value
|
||||
$rest = $rest.Substring($altMatch.Index + $altMatch.Length)
|
||||
}
|
||||
|
||||
$descMatch = [regex]::Match($rest, '^\s*-\s*(?<desc>.*)$')
|
||||
$desc = if ($descMatch.Success) { $descMatch.Groups['desc'].Value.Trim() } else { $rest.Trim() }
|
||||
|
||||
$min = $spec1.Min
|
||||
$variadic = $spec1.Variadic
|
||||
$max = $spec1.Max
|
||||
if ($spec2) {
|
||||
if ($spec2.Min -lt $min) { $min = $spec2.Min }
|
||||
if ($spec2.Variadic) { $variadic = $true }
|
||||
if ($spec2.Max -gt $max) { $max = $spec2.Max }
|
||||
}
|
||||
|
||||
[pscustomobject]@{
|
||||
MinArgs = $min
|
||||
MaxArgs = if ($variadic) { $null } else { $max }
|
||||
ArgNames = $spec1.Display
|
||||
Description = $desc
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host "[symbols] Parsing $GrammarFile"
|
||||
$grammarText = Get-Content -Raw -LiteralPath $GrammarFile
|
||||
$sections = Get-GrammarSections -Text $grammarText
|
||||
$allTokens = Get-LexerTokens -LexerSection $sections.LexerSection
|
||||
$functionTokenNames = Get-FunctionTokenNames -FuncSection $sections.FuncSection
|
||||
|
||||
$constantWords = @()
|
||||
$keywordWords = New-Object System.Collections.Generic.List[string]
|
||||
$functionWords = New-Object System.Collections.Generic.List[string]
|
||||
$functionTokenToWords = [ordered]@{}
|
||||
|
||||
$tokenNames = @($allTokens.psbase.Keys)
|
||||
foreach ($name in $tokenNames) {
|
||||
$words = Get-UniqueLowerWords -Words $allTokens[$name]
|
||||
if ($name -eq 'CONSTANT') {
|
||||
$constantWords = $words
|
||||
continue
|
||||
}
|
||||
if ($functionTokenNames.Contains($name)) {
|
||||
$functionTokenToWords[$name] = $words
|
||||
foreach ($w in $words) { [void]$functionWords.Add($w) }
|
||||
} else {
|
||||
foreach ($w in $words) { [void]$keywordWords.Add($w) }
|
||||
}
|
||||
}
|
||||
|
||||
$KEYWORDS = @($keywordWords | Sort-Object -Unique)
|
||||
$CONSTANTS = @($constantWords | Sort-Object -Unique)
|
||||
$FUNCTIONS = @($functionWords | Sort-Object -Unique)
|
||||
|
||||
$docEntries = Get-FunctionDocEntries -FuncSection $sections.FuncSection
|
||||
$sigByToken = @{}
|
||||
foreach ($e in $docEntries) {
|
||||
$sig = ConvertTo-FunctionSignature -Line $e.FirstLine
|
||||
if ($sig) { $sigByToken[$e.Token] = $sig }
|
||||
}
|
||||
|
||||
$functionTokens = @($functionTokenToWords.psbase.Keys)
|
||||
$undocumented = @($functionTokens | Where-Object { -not $sigByToken.ContainsKey($_) })
|
||||
if ($undocumented.Count -gt 0) {
|
||||
Write-Warning "No parsable doc comment found for function token(s): $($undocumented -join ', '). They will get placeholder metadata."
|
||||
}
|
||||
|
||||
$FUNCTION_META = [ordered]@{}
|
||||
foreach ($token in $functionTokens) {
|
||||
$sig = $sigByToken[$token]
|
||||
foreach ($alias in $functionTokenToWords[$token]) {
|
||||
if ($sig) {
|
||||
$snippet = if ($sig.ArgNames.Count -gt 0) { "$alias($($sig.ArgNames -join ', '))" } else { "$alias()" }
|
||||
$FUNCTION_META[$alias] = [pscustomobject]@{
|
||||
MinArgs = $sig.MinArgs
|
||||
MaxArgs = $sig.MaxArgs
|
||||
Snippet = $snippet
|
||||
Description = $sig.Description
|
||||
}
|
||||
} else {
|
||||
$FUNCTION_META[$alias] = [pscustomobject]@{
|
||||
MinArgs = 0
|
||||
MaxArgs = $null
|
||||
Snippet = "$alias()"
|
||||
Description = ''
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Step 3: emit the generated files
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
function Set-Utf8NoBom {
|
||||
# Windows PowerShell 5.1's Set-Content has no utf8NoBOM encoding, so write
|
||||
# the file directly via .NET to avoid a BOM in the generated .py/.js files.
|
||||
param([string]$Path, [string]$Content)
|
||||
$utf8NoBom = New-Object System.Text.UTF8Encoding($false)
|
||||
[System.IO.File]::WriteAllText($Path, $Content, $utf8NoBom)
|
||||
}
|
||||
|
||||
function ConvertTo-JsString {
|
||||
param([string]$Value)
|
||||
$escaped = $Value.Replace('\', '\\').Replace('"', '\"')
|
||||
return '"' + $escaped + '"'
|
||||
}
|
||||
|
||||
function ConvertTo-PyString {
|
||||
param([string]$Value)
|
||||
$escaped = $Value.Replace('\', '\\').Replace("'", "\'")
|
||||
return "'" + $escaped + "'"
|
||||
}
|
||||
|
||||
function Write-JsSymbols {
|
||||
param(
|
||||
[string]$Path,
|
||||
[string[]]$Keywords,
|
||||
[string[]]$Constants,
|
||||
[string[]]$Functions,
|
||||
[System.Collections.Specialized.OrderedDictionary]$FunctionMeta
|
||||
)
|
||||
|
||||
$sb = New-Object System.Text.StringBuilder
|
||||
[void]$sb.AppendLine('// Generated automatically by compile.ps1. Do not edit.')
|
||||
[void]$sb.AppendLine()
|
||||
[void]$sb.AppendLine('export const KEYWORDS = new Set([')
|
||||
[void]$sb.AppendLine(($Keywords | ForEach-Object { ConvertTo-JsString $_ }) -join ', ')
|
||||
[void]$sb.AppendLine(']);')
|
||||
[void]$sb.AppendLine()
|
||||
[void]$sb.AppendLine('export const CONSTANTS = new Set([')
|
||||
[void]$sb.AppendLine(($Constants | ForEach-Object { ConvertTo-JsString $_ }) -join ', ')
|
||||
[void]$sb.AppendLine(']);')
|
||||
[void]$sb.AppendLine()
|
||||
[void]$sb.AppendLine('export const FUNCTIONS = new Set([')
|
||||
[void]$sb.AppendLine(($Functions | ForEach-Object { ConvertTo-JsString $_ }) -join ', ')
|
||||
[void]$sb.AppendLine(']);')
|
||||
[void]$sb.AppendLine()
|
||||
[void]$sb.AppendLine('export const FUNCTION_META = {')
|
||||
foreach ($name in @($FunctionMeta.psbase.Keys)) {
|
||||
$meta = $FunctionMeta[$name]
|
||||
$maxArgsJs = if ($null -eq $meta.MaxArgs) { 'null' } else { $meta.MaxArgs }
|
||||
[void]$sb.AppendLine(" $(ConvertTo-JsString $name): { minArgs: $($meta.MinArgs), maxArgs: $maxArgsJs, snippet: $(ConvertTo-JsString $meta.Snippet), description: $(ConvertTo-JsString $meta.Description) },")
|
||||
}
|
||||
[void]$sb.AppendLine('};')
|
||||
|
||||
Set-Utf8NoBom -Path $Path -Content $sb.ToString()
|
||||
}
|
||||
|
||||
function Write-PySymbols {
|
||||
param(
|
||||
[string]$Path,
|
||||
[string[]]$Keywords,
|
||||
[string[]]$Constants,
|
||||
[string[]]$Functions,
|
||||
[System.Collections.Specialized.OrderedDictionary]$FunctionMeta
|
||||
)
|
||||
|
||||
$sb = New-Object System.Text.StringBuilder
|
||||
[void]$sb.AppendLine('# Generated automatically by compile.ps1. Do not edit.')
|
||||
[void]$sb.AppendLine('INBUILT_KEYWORDS = {')
|
||||
[void]$sb.AppendLine(($Keywords | ForEach-Object { ConvertTo-PyString $_ }) -join ', ')
|
||||
[void]$sb.AppendLine('}')
|
||||
[void]$sb.AppendLine('INBUILT_CONSTANTS = {')
|
||||
[void]$sb.AppendLine(($Constants | ForEach-Object { ConvertTo-PyString $_ }) -join ', ')
|
||||
[void]$sb.AppendLine('}')
|
||||
[void]$sb.AppendLine('INBUILT_FUNCTIONS = {')
|
||||
[void]$sb.AppendLine(($Functions | ForEach-Object { ConvertTo-PyString $_ }) -join ', ')
|
||||
[void]$sb.AppendLine('}')
|
||||
[void]$sb.AppendLine()
|
||||
[void]$sb.AppendLine('INBUILT_FUNCTION_META = {')
|
||||
foreach ($name in @($FunctionMeta.psbase.Keys)) {
|
||||
$meta = $FunctionMeta[$name]
|
||||
$maxArgsPy = if ($null -eq $meta.MaxArgs) { 'None' } else { $meta.MaxArgs }
|
||||
[void]$sb.AppendLine(" $(ConvertTo-PyString $name): {'min_args': $($meta.MinArgs), 'max_args': $maxArgsPy, 'snippet': $(ConvertTo-PyString $meta.Snippet), 'description': $(ConvertTo-PyString $meta.Description)},")
|
||||
}
|
||||
[void]$sb.AppendLine('}')
|
||||
|
||||
Set-Utf8NoBom -Path $Path -Content $sb.ToString()
|
||||
}
|
||||
|
||||
Write-Host "[symbols] Writing $PySymbolsPath"
|
||||
Write-PySymbols -Path $PySymbolsPath -Keywords $KEYWORDS -Constants $CONSTANTS -Functions $FUNCTIONS -FunctionMeta $FUNCTION_META
|
||||
|
||||
Write-Host "[symbols] Writing $JsSymbolsPath"
|
||||
Write-JsSymbols -Path $JsSymbolsPath -Keywords $KEYWORDS -Constants $CONSTANTS -Functions $FUNCTIONS -FunctionMeta $FUNCTION_META
|
||||
|
||||
Write-Host "[symbols] $($FUNCTIONS.Count) functions, $($KEYWORDS.Count) keywords, $($CONSTANTS.Count) constants."
|
||||
Write-Host 'Done.'
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,412 @@
|
||||
SIN=1
|
||||
COS=2
|
||||
TAN=3
|
||||
ASIN=4
|
||||
ACOS=5
|
||||
ATAN=6
|
||||
ATAN2=7
|
||||
SINH=8
|
||||
COSH=9
|
||||
TANH=10
|
||||
ASINH=11
|
||||
ACOSH=12
|
||||
ATANH=13
|
||||
ABS=14
|
||||
SQRT=15
|
||||
LN=16
|
||||
LOG=17
|
||||
EXP=18
|
||||
SMIN=19
|
||||
SMAX=20
|
||||
TMIN=21
|
||||
TMAX=22
|
||||
TNORM=23
|
||||
SNORM=24
|
||||
FLOOR=25
|
||||
CEIL=26
|
||||
ROUND=27
|
||||
GAMMA=28
|
||||
POWE=29
|
||||
SIGM=30
|
||||
CLAMP=31
|
||||
SFFT=32
|
||||
SIFFT=33
|
||||
ANGL=34
|
||||
PRNT=35
|
||||
PRINT_SHAPE=36
|
||||
NVL=37
|
||||
LERP=38
|
||||
STEP=39
|
||||
SMOOTHSTEP=40
|
||||
FRACT=41
|
||||
RELU=42
|
||||
SOFTPLUS=43
|
||||
GELU=44
|
||||
SIGN=45
|
||||
MAP=46
|
||||
EZCONV=47
|
||||
CONV=48
|
||||
SWAP=49
|
||||
PERM=50
|
||||
RESHAPE=51
|
||||
RANGE=52
|
||||
LINSPACE=53
|
||||
LOGSPACE=54
|
||||
TOPK=55
|
||||
BOTK=56
|
||||
PINV=57
|
||||
SUM=58
|
||||
MEAN=59
|
||||
STD=60
|
||||
VAR=61
|
||||
QUARTILE=62
|
||||
PERCENTILE=63
|
||||
QUANTILE=64
|
||||
DOT=65
|
||||
MOMENT=66
|
||||
ERF=67
|
||||
ERFINV=68
|
||||
ANY=69
|
||||
ALL=70
|
||||
EDGE=71
|
||||
GAUSSIAN=72
|
||||
MEDIAN=73
|
||||
MODE=74
|
||||
CUMSUM=75
|
||||
CUMPROD=76
|
||||
TOPK_IND=77
|
||||
BOTK_IND=78
|
||||
CUBIC_EASE=79
|
||||
ELASTIC_EASE=80
|
||||
SINE_EASE=81
|
||||
SMOOTHERSTEP=82
|
||||
DIST=83
|
||||
REMAP=84
|
||||
COSSIM=85
|
||||
COUNT=86
|
||||
KEYS=87
|
||||
ENABLE_TRACKING=88
|
||||
DISABLE_TRACKING=89
|
||||
RESET_TRACKING=90
|
||||
GET_CHANGED_KEYS=91
|
||||
GET_ADDED_KEYS=92
|
||||
GET_REMOVED_KEYS=93
|
||||
GET_UNCHANGED_KEYS=94
|
||||
REPEAT=95
|
||||
FLATTEN=96
|
||||
APPEND=97
|
||||
GET_VALUE=98
|
||||
FLOW_APPLY=99
|
||||
BATCH_SHUFFLE=100
|
||||
MOTION_MASK=101
|
||||
FLOW_TO_IMAGE=102
|
||||
FLOW_MAG=103
|
||||
FLOW_ANG=104
|
||||
WHERE=105
|
||||
HISTOGRAM=106
|
||||
OVERLAY=107
|
||||
PAD=108
|
||||
CROSS=109
|
||||
MATMUL=110
|
||||
RIFE=111
|
||||
BNOT=112
|
||||
BITCOUNT=113
|
||||
SHAPE=114
|
||||
BAND=115
|
||||
XOR=116
|
||||
BOR=117
|
||||
TENSOR=118
|
||||
ADD_KEY=119
|
||||
REMOVE_KEY=120
|
||||
PUSH=121
|
||||
POP=122
|
||||
CLEAR=123
|
||||
HAS=124
|
||||
GET=125
|
||||
CONCAT=126
|
||||
INT=127
|
||||
FLOAT=128
|
||||
UPPER=129
|
||||
LOWER=130
|
||||
TRIM=131
|
||||
SPLIT=132
|
||||
JOIN=133
|
||||
SUBSTRING=134
|
||||
FIND=135
|
||||
REPLACE=136
|
||||
DILATE=137
|
||||
ERODE=138
|
||||
MORPH_OPEN=139
|
||||
MORPH_CLOSE=140
|
||||
RGB_TO_OKLAB=141
|
||||
RGB_TO_CIELAB=142
|
||||
OKLAB_TO_RGB=143
|
||||
CIELAB_TO_RGB=144
|
||||
RGB_TO_HSV=145
|
||||
HSV_TO_RGB=146
|
||||
INT_TO_RGB=147
|
||||
RGB_TO_INT=148
|
||||
INTERPOLATE_LINEAR=149
|
||||
INTERPOLATE_AREA=150
|
||||
INTERPOLATE_NEAREST=151
|
||||
TEXT_IMAGE=152
|
||||
AS_NESTED=153
|
||||
SVD=154
|
||||
DIAG=155
|
||||
IF=156
|
||||
ELSE=157
|
||||
WHILE=158
|
||||
FOR=159
|
||||
IN=160
|
||||
BREAK=161
|
||||
CONTINUE=162
|
||||
RETURN=163
|
||||
TIMESTAMP=164
|
||||
SORT=165
|
||||
ARGSORT=166
|
||||
ARGMIN=167
|
||||
ARGMAX=168
|
||||
SOFTMAX=169
|
||||
SOFTMIN=170
|
||||
UNIQUE=171
|
||||
FLIP=172
|
||||
STARTSWITH=173
|
||||
ENDSWITH=174
|
||||
SQUEEZE=175
|
||||
UNSQUEEZE=176
|
||||
ROLL=177
|
||||
COV=178
|
||||
CORR=179
|
||||
ENTROPY=180
|
||||
CROP=181
|
||||
NONE=182
|
||||
COORDS=183
|
||||
NOISE=184
|
||||
RAND=185
|
||||
CAUCHY=186
|
||||
EXPONENTIAL=187
|
||||
LOGNORMAL=188
|
||||
BERNOULLI=189
|
||||
POISSON=190
|
||||
GAMMADIST=191
|
||||
BETADIST=192
|
||||
LAPLACEDIST=193
|
||||
GUMBELDIST=194
|
||||
WEIBULLDIST=195
|
||||
CHI2DIST=196
|
||||
STUDENTTDIST=197
|
||||
PERLIN=198
|
||||
CELLULAR=199
|
||||
PLASMA=200
|
||||
RIDGED=201
|
||||
DOMAIN_WARP=202
|
||||
PLUS=203
|
||||
MINUS=204
|
||||
MULT=205
|
||||
DIV=206
|
||||
MOD=207
|
||||
POW=208
|
||||
LSHIFT=209
|
||||
RSHIFT=210
|
||||
GE=211
|
||||
GT=212
|
||||
LE=213
|
||||
LT=214
|
||||
EQ=215
|
||||
EQUEALS=216
|
||||
PLUS_EQ=217
|
||||
MINUS_EQ=218
|
||||
MULT_EQ=219
|
||||
DIV_EQ=220
|
||||
MOD_EQ=221
|
||||
NE=222
|
||||
PIPE=223
|
||||
LPAREN=224
|
||||
RPAREN=225
|
||||
COMMA=226
|
||||
SEMICOLON=227
|
||||
ARROW=228
|
||||
LBRACKET=229
|
||||
RBRACKET=230
|
||||
QUESTION=231
|
||||
COLON=232
|
||||
LBRACE=233
|
||||
RBRACE=234
|
||||
NUMBER=235
|
||||
CONSTANT=236
|
||||
STRING=237
|
||||
VARIABLE=238
|
||||
SL_COMMENT=239
|
||||
ML_COMMENT=240
|
||||
WS=241
|
||||
'sin'=1
|
||||
'cos'=2
|
||||
'tan'=3
|
||||
'asin'=4
|
||||
'acos'=5
|
||||
'atan'=6
|
||||
'atan2'=7
|
||||
'sinh'=8
|
||||
'cosh'=9
|
||||
'tanh'=10
|
||||
'asinh'=11
|
||||
'acosh'=12
|
||||
'atanh'=13
|
||||
'abs'=14
|
||||
'sqrt'=15
|
||||
'ln'=16
|
||||
'log'=17
|
||||
'exp'=18
|
||||
'smin'=19
|
||||
'smax'=20
|
||||
'tmin'=21
|
||||
'tmax'=22
|
||||
'tnorm'=23
|
||||
'snorm'=24
|
||||
'floor'=25
|
||||
'ceil'=26
|
||||
'round'=27
|
||||
'gamma'=28
|
||||
'pow'=29
|
||||
'sigm'=30
|
||||
'clamp'=31
|
||||
'fft'=32
|
||||
'ifft'=33
|
||||
'angle'=34
|
||||
'print'=35
|
||||
'lerp'=38
|
||||
'step'=39
|
||||
'smoothstep'=40
|
||||
'fract'=41
|
||||
'relu'=42
|
||||
'softplus'=43
|
||||
'gelu'=44
|
||||
'sign'=45
|
||||
'map'=46
|
||||
'swap'=49
|
||||
'range'=52
|
||||
'linspace'=53
|
||||
'logspace'=54
|
||||
'topk'=55
|
||||
'botk'=56
|
||||
'pinv'=57
|
||||
'sum'=58
|
||||
'mean'=59
|
||||
'std'=60
|
||||
'var'=61
|
||||
'quantile'=64
|
||||
'dot'=65
|
||||
'moment'=66
|
||||
'erf'=67
|
||||
'erfinv'=68
|
||||
'any'=69
|
||||
'all'=70
|
||||
'edge'=71
|
||||
'median'=73
|
||||
'mode'=74
|
||||
'cumsum'=75
|
||||
'cumprod'=76
|
||||
'smootherstep'=82
|
||||
'remap'=84
|
||||
'keys'=87
|
||||
'repeat'=95
|
||||
'flatten'=96
|
||||
'append'=97
|
||||
'get_value'=98
|
||||
'flow_apply'=99
|
||||
'motion_mask'=101
|
||||
'flow_to_image'=102
|
||||
'where'=105
|
||||
'overlay'=107
|
||||
'pad'=108
|
||||
'cross'=109
|
||||
'matmul'=110
|
||||
'rife'=111
|
||||
'shape'=114
|
||||
'tensor'=118
|
||||
'add_key'=119
|
||||
'stack_push'=121
|
||||
'stack_pop'=122
|
||||
'stack_clear'=123
|
||||
'stack_has'=124
|
||||
'stack_get'=125
|
||||
'int'=127
|
||||
'float'=128
|
||||
'upper'=129
|
||||
'lower'=130
|
||||
'trim'=131
|
||||
'split'=132
|
||||
'join'=133
|
||||
'find'=135
|
||||
'replace'=136
|
||||
'dilate'=137
|
||||
'erode'=138
|
||||
'morph_open'=139
|
||||
'morph_close'=140
|
||||
'rgb_to_oklab'=141
|
||||
'rgb_to_cielab'=142
|
||||
'oklab_to_rgb'=143
|
||||
'cielab_to_rgb'=144
|
||||
'rgb_to_hsv'=145
|
||||
'hsv_to_rgb'=146
|
||||
'int_to_rgb'=147
|
||||
'rgb_to_int'=148
|
||||
'interpolate_linear'=149
|
||||
'interpolate_area'=150
|
||||
'text_image'=152
|
||||
'if'=156
|
||||
'else'=157
|
||||
'while'=158
|
||||
'for'=159
|
||||
'in'=160
|
||||
'break'=161
|
||||
'continue'=162
|
||||
'return'=163
|
||||
'sort'=165
|
||||
'argsort'=166
|
||||
'argmin'=167
|
||||
'argmax'=168
|
||||
'softmax'=169
|
||||
'softmin'=170
|
||||
'unique'=171
|
||||
'flip'=172
|
||||
'startswith'=173
|
||||
'endswith'=174
|
||||
'squeeze'=175
|
||||
'unsqueeze'=176
|
||||
'roll'=177
|
||||
'cov'=178
|
||||
'entropy'=180
|
||||
'crop'=181
|
||||
'+'=203
|
||||
'-'=204
|
||||
'*'=205
|
||||
'/'=206
|
||||
'%'=207
|
||||
'^'=208
|
||||
'<<'=209
|
||||
'>>'=210
|
||||
'>='=211
|
||||
'>'=212
|
||||
'<='=213
|
||||
'<'=214
|
||||
'=='=215
|
||||
'='=216
|
||||
'+='=217
|
||||
'-='=218
|
||||
'*='=219
|
||||
'/='=220
|
||||
'%='=221
|
||||
'!='=222
|
||||
'|'=223
|
||||
'('=224
|
||||
')'=225
|
||||
','=226
|
||||
';'=227
|
||||
'->'=228
|
||||
'['=229
|
||||
']'=230
|
||||
'?'=231
|
||||
':'=232
|
||||
'{'=233
|
||||
'}'=234
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,412 @@
|
||||
SIN=1
|
||||
COS=2
|
||||
TAN=3
|
||||
ASIN=4
|
||||
ACOS=5
|
||||
ATAN=6
|
||||
ATAN2=7
|
||||
SINH=8
|
||||
COSH=9
|
||||
TANH=10
|
||||
ASINH=11
|
||||
ACOSH=12
|
||||
ATANH=13
|
||||
ABS=14
|
||||
SQRT=15
|
||||
LN=16
|
||||
LOG=17
|
||||
EXP=18
|
||||
SMIN=19
|
||||
SMAX=20
|
||||
TMIN=21
|
||||
TMAX=22
|
||||
TNORM=23
|
||||
SNORM=24
|
||||
FLOOR=25
|
||||
CEIL=26
|
||||
ROUND=27
|
||||
GAMMA=28
|
||||
POWE=29
|
||||
SIGM=30
|
||||
CLAMP=31
|
||||
SFFT=32
|
||||
SIFFT=33
|
||||
ANGL=34
|
||||
PRNT=35
|
||||
PRINT_SHAPE=36
|
||||
NVL=37
|
||||
LERP=38
|
||||
STEP=39
|
||||
SMOOTHSTEP=40
|
||||
FRACT=41
|
||||
RELU=42
|
||||
SOFTPLUS=43
|
||||
GELU=44
|
||||
SIGN=45
|
||||
MAP=46
|
||||
EZCONV=47
|
||||
CONV=48
|
||||
SWAP=49
|
||||
PERM=50
|
||||
RESHAPE=51
|
||||
RANGE=52
|
||||
LINSPACE=53
|
||||
LOGSPACE=54
|
||||
TOPK=55
|
||||
BOTK=56
|
||||
PINV=57
|
||||
SUM=58
|
||||
MEAN=59
|
||||
STD=60
|
||||
VAR=61
|
||||
QUARTILE=62
|
||||
PERCENTILE=63
|
||||
QUANTILE=64
|
||||
DOT=65
|
||||
MOMENT=66
|
||||
ERF=67
|
||||
ERFINV=68
|
||||
ANY=69
|
||||
ALL=70
|
||||
EDGE=71
|
||||
GAUSSIAN=72
|
||||
MEDIAN=73
|
||||
MODE=74
|
||||
CUMSUM=75
|
||||
CUMPROD=76
|
||||
TOPK_IND=77
|
||||
BOTK_IND=78
|
||||
CUBIC_EASE=79
|
||||
ELASTIC_EASE=80
|
||||
SINE_EASE=81
|
||||
SMOOTHERSTEP=82
|
||||
DIST=83
|
||||
REMAP=84
|
||||
COSSIM=85
|
||||
COUNT=86
|
||||
KEYS=87
|
||||
ENABLE_TRACKING=88
|
||||
DISABLE_TRACKING=89
|
||||
RESET_TRACKING=90
|
||||
GET_CHANGED_KEYS=91
|
||||
GET_ADDED_KEYS=92
|
||||
GET_REMOVED_KEYS=93
|
||||
GET_UNCHANGED_KEYS=94
|
||||
REPEAT=95
|
||||
FLATTEN=96
|
||||
APPEND=97
|
||||
GET_VALUE=98
|
||||
FLOW_APPLY=99
|
||||
BATCH_SHUFFLE=100
|
||||
MOTION_MASK=101
|
||||
FLOW_TO_IMAGE=102
|
||||
FLOW_MAG=103
|
||||
FLOW_ANG=104
|
||||
WHERE=105
|
||||
HISTOGRAM=106
|
||||
OVERLAY=107
|
||||
PAD=108
|
||||
CROSS=109
|
||||
MATMUL=110
|
||||
RIFE=111
|
||||
BNOT=112
|
||||
BITCOUNT=113
|
||||
SHAPE=114
|
||||
BAND=115
|
||||
XOR=116
|
||||
BOR=117
|
||||
TENSOR=118
|
||||
ADD_KEY=119
|
||||
REMOVE_KEY=120
|
||||
PUSH=121
|
||||
POP=122
|
||||
CLEAR=123
|
||||
HAS=124
|
||||
GET=125
|
||||
CONCAT=126
|
||||
INT=127
|
||||
FLOAT=128
|
||||
UPPER=129
|
||||
LOWER=130
|
||||
TRIM=131
|
||||
SPLIT=132
|
||||
JOIN=133
|
||||
SUBSTRING=134
|
||||
FIND=135
|
||||
REPLACE=136
|
||||
DILATE=137
|
||||
ERODE=138
|
||||
MORPH_OPEN=139
|
||||
MORPH_CLOSE=140
|
||||
RGB_TO_OKLAB=141
|
||||
RGB_TO_CIELAB=142
|
||||
OKLAB_TO_RGB=143
|
||||
CIELAB_TO_RGB=144
|
||||
RGB_TO_HSV=145
|
||||
HSV_TO_RGB=146
|
||||
INT_TO_RGB=147
|
||||
RGB_TO_INT=148
|
||||
INTERPOLATE_LINEAR=149
|
||||
INTERPOLATE_AREA=150
|
||||
INTERPOLATE_NEAREST=151
|
||||
TEXT_IMAGE=152
|
||||
AS_NESTED=153
|
||||
SVD=154
|
||||
DIAG=155
|
||||
IF=156
|
||||
ELSE=157
|
||||
WHILE=158
|
||||
FOR=159
|
||||
IN=160
|
||||
BREAK=161
|
||||
CONTINUE=162
|
||||
RETURN=163
|
||||
TIMESTAMP=164
|
||||
SORT=165
|
||||
ARGSORT=166
|
||||
ARGMIN=167
|
||||
ARGMAX=168
|
||||
SOFTMAX=169
|
||||
SOFTMIN=170
|
||||
UNIQUE=171
|
||||
FLIP=172
|
||||
STARTSWITH=173
|
||||
ENDSWITH=174
|
||||
SQUEEZE=175
|
||||
UNSQUEEZE=176
|
||||
ROLL=177
|
||||
COV=178
|
||||
CORR=179
|
||||
ENTROPY=180
|
||||
CROP=181
|
||||
NONE=182
|
||||
COORDS=183
|
||||
NOISE=184
|
||||
RAND=185
|
||||
CAUCHY=186
|
||||
EXPONENTIAL=187
|
||||
LOGNORMAL=188
|
||||
BERNOULLI=189
|
||||
POISSON=190
|
||||
GAMMADIST=191
|
||||
BETADIST=192
|
||||
LAPLACEDIST=193
|
||||
GUMBELDIST=194
|
||||
WEIBULLDIST=195
|
||||
CHI2DIST=196
|
||||
STUDENTTDIST=197
|
||||
PERLIN=198
|
||||
CELLULAR=199
|
||||
PLASMA=200
|
||||
RIDGED=201
|
||||
DOMAIN_WARP=202
|
||||
PLUS=203
|
||||
MINUS=204
|
||||
MULT=205
|
||||
DIV=206
|
||||
MOD=207
|
||||
POW=208
|
||||
LSHIFT=209
|
||||
RSHIFT=210
|
||||
GE=211
|
||||
GT=212
|
||||
LE=213
|
||||
LT=214
|
||||
EQ=215
|
||||
EQUEALS=216
|
||||
PLUS_EQ=217
|
||||
MINUS_EQ=218
|
||||
MULT_EQ=219
|
||||
DIV_EQ=220
|
||||
MOD_EQ=221
|
||||
NE=222
|
||||
PIPE=223
|
||||
LPAREN=224
|
||||
RPAREN=225
|
||||
COMMA=226
|
||||
SEMICOLON=227
|
||||
ARROW=228
|
||||
LBRACKET=229
|
||||
RBRACKET=230
|
||||
QUESTION=231
|
||||
COLON=232
|
||||
LBRACE=233
|
||||
RBRACE=234
|
||||
NUMBER=235
|
||||
CONSTANT=236
|
||||
STRING=237
|
||||
VARIABLE=238
|
||||
SL_COMMENT=239
|
||||
ML_COMMENT=240
|
||||
WS=241
|
||||
'sin'=1
|
||||
'cos'=2
|
||||
'tan'=3
|
||||
'asin'=4
|
||||
'acos'=5
|
||||
'atan'=6
|
||||
'atan2'=7
|
||||
'sinh'=8
|
||||
'cosh'=9
|
||||
'tanh'=10
|
||||
'asinh'=11
|
||||
'acosh'=12
|
||||
'atanh'=13
|
||||
'abs'=14
|
||||
'sqrt'=15
|
||||
'ln'=16
|
||||
'log'=17
|
||||
'exp'=18
|
||||
'smin'=19
|
||||
'smax'=20
|
||||
'tmin'=21
|
||||
'tmax'=22
|
||||
'tnorm'=23
|
||||
'snorm'=24
|
||||
'floor'=25
|
||||
'ceil'=26
|
||||
'round'=27
|
||||
'gamma'=28
|
||||
'pow'=29
|
||||
'sigm'=30
|
||||
'clamp'=31
|
||||
'fft'=32
|
||||
'ifft'=33
|
||||
'angle'=34
|
||||
'print'=35
|
||||
'lerp'=38
|
||||
'step'=39
|
||||
'smoothstep'=40
|
||||
'fract'=41
|
||||
'relu'=42
|
||||
'softplus'=43
|
||||
'gelu'=44
|
||||
'sign'=45
|
||||
'map'=46
|
||||
'swap'=49
|
||||
'range'=52
|
||||
'linspace'=53
|
||||
'logspace'=54
|
||||
'topk'=55
|
||||
'botk'=56
|
||||
'pinv'=57
|
||||
'sum'=58
|
||||
'mean'=59
|
||||
'std'=60
|
||||
'var'=61
|
||||
'quantile'=64
|
||||
'dot'=65
|
||||
'moment'=66
|
||||
'erf'=67
|
||||
'erfinv'=68
|
||||
'any'=69
|
||||
'all'=70
|
||||
'edge'=71
|
||||
'median'=73
|
||||
'mode'=74
|
||||
'cumsum'=75
|
||||
'cumprod'=76
|
||||
'smootherstep'=82
|
||||
'remap'=84
|
||||
'keys'=87
|
||||
'repeat'=95
|
||||
'flatten'=96
|
||||
'append'=97
|
||||
'get_value'=98
|
||||
'flow_apply'=99
|
||||
'motion_mask'=101
|
||||
'flow_to_image'=102
|
||||
'where'=105
|
||||
'overlay'=107
|
||||
'pad'=108
|
||||
'cross'=109
|
||||
'matmul'=110
|
||||
'rife'=111
|
||||
'shape'=114
|
||||
'tensor'=118
|
||||
'add_key'=119
|
||||
'stack_push'=121
|
||||
'stack_pop'=122
|
||||
'stack_clear'=123
|
||||
'stack_has'=124
|
||||
'stack_get'=125
|
||||
'int'=127
|
||||
'float'=128
|
||||
'upper'=129
|
||||
'lower'=130
|
||||
'trim'=131
|
||||
'split'=132
|
||||
'join'=133
|
||||
'find'=135
|
||||
'replace'=136
|
||||
'dilate'=137
|
||||
'erode'=138
|
||||
'morph_open'=139
|
||||
'morph_close'=140
|
||||
'rgb_to_oklab'=141
|
||||
'rgb_to_cielab'=142
|
||||
'oklab_to_rgb'=143
|
||||
'cielab_to_rgb'=144
|
||||
'rgb_to_hsv'=145
|
||||
'hsv_to_rgb'=146
|
||||
'int_to_rgb'=147
|
||||
'rgb_to_int'=148
|
||||
'interpolate_linear'=149
|
||||
'interpolate_area'=150
|
||||
'text_image'=152
|
||||
'if'=156
|
||||
'else'=157
|
||||
'while'=158
|
||||
'for'=159
|
||||
'in'=160
|
||||
'break'=161
|
||||
'continue'=162
|
||||
'return'=163
|
||||
'sort'=165
|
||||
'argsort'=166
|
||||
'argmin'=167
|
||||
'argmax'=168
|
||||
'softmax'=169
|
||||
'softmin'=170
|
||||
'unique'=171
|
||||
'flip'=172
|
||||
'startswith'=173
|
||||
'endswith'=174
|
||||
'squeeze'=175
|
||||
'unsqueeze'=176
|
||||
'roll'=177
|
||||
'cov'=178
|
||||
'entropy'=180
|
||||
'crop'=181
|
||||
'+'=203
|
||||
'-'=204
|
||||
'*'=205
|
||||
'/'=206
|
||||
'%'=207
|
||||
'^'=208
|
||||
'<<'=209
|
||||
'>>'=210
|
||||
'>='=211
|
||||
'>'=212
|
||||
'<='=213
|
||||
'<'=214
|
||||
'=='=215
|
||||
'='=216
|
||||
'+='=217
|
||||
'-='=218
|
||||
'*='=219
|
||||
'/='=220
|
||||
'%='=221
|
||||
'!='=222
|
||||
'|'=223
|
||||
'('=224
|
||||
')'=225
|
||||
','=226
|
||||
';'=227
|
||||
'->'=228
|
||||
'['=229
|
||||
']'=230
|
||||
'?'=231
|
||||
':'=232
|
||||
'{'=233
|
||||
'}'=234
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,278 @@
|
||||
# Generated automatically by compile.ps1. Do not edit.
|
||||
INBUILT_KEYWORDS = {
|
||||
'break', 'continue', 'else', 'for', 'if', 'in', 'none', 'null', 'return', 'while'
|
||||
}
|
||||
INBUILT_CONSTANTS = {
|
||||
'e', 'pi'
|
||||
}
|
||||
INBUILT_FUNCTIONS = {
|
||||
'abs', 'acos', 'acosh', 'add_key', 'added_keys', 'all', 'angle', 'any', 'append', 'argmax', 'argmin', 'argsort', 'as_nested', 'as_nested_tensor', 'asin', 'asinh', 'atan', 'atan2', 'atanh', 'band', 'batch_shuffle', 'bitcount', 'bitwise_and', 'bitwise_not', 'bitwise_or', 'bitwise_xor', 'blur', 'bnot', 'bor', 'botk', 'botk_ind', 'botk_indices', 'bxor', 'cat', 'ceil', 'cellular', 'cellular_noise', 'cielab_to_rgb', 'clamp', 'cnt', 'concat', 'concatenate', 'conv', 'convolution', 'coordinates', 'coords', 'corr', 'correlation', 'cos', 'cosh', 'cosine_similarity', 'cossim', 'count', 'cov', 'crop', 'cross', 'cubic', 'cubic_ease', 'cumprod', 'cumsum', 'diag', 'diagonal_matrix', 'dilate', 'disable_tracking', 'dist', 'distance', 'domain_warp', 'domain_warp_noise', 'dot', 'edge', 'elastic', 'elastic_ease', 'enable_tracking', 'endswith', 'entropy', 'erf', 'erfinv', 'erode', 'exp', 'ezconv', 'ezconvolution', 'fft', 'find', 'flatten', 'flip', 'float', 'floor', 'flow_ang', 'flow_angle', 'flow_apply', 'flow_mag', 'flow_magnitude', 'flow_to_image', 'fract', 'gamma', 'gaussian', 'gelu', 'get_added_keys', 'get_changed_keys', 'get_removed_keys', 'get_unchanged_keys', 'get_value', 'hist', 'histogram', 'hsv_to_rgb', 'changed_keys', 'ifft', 'int', 'int_to_rgb', 'interpolate_area', 'interpolate_linear', 'interpolate_nearest', 'interpolate_nearest_exact', 'join', 'keys', 'length', 'lerp', 'linspace', 'ln', 'log', 'logspace', 'lower', 'map', 'matmul', 'mean', 'median', 'mode', 'moment', 'morph_close', 'morph_open', 'motion_mask', 'nan_to_num', 'noise', 'now', 'nvl', 'oklab_to_rgb', 'overlay', 'pad', 'percentile', 'perlin', 'perlin_noise', 'perm', 'permute', 'pinv', 'plasma', 'plasma_noise', 'popcnt', 'popcount', 'pow', 'prcnt', 'print', 'print_shape', 'pshp', 'quantile', 'quartil', 'quartile', 'rand', 'randb', 'randbeta', 'randc', 'rande', 'randg', 'randgumbel', 'randchi2', 'randl', 'randln', 'randn', 'random_bernoulli', 'random_beta', 'random_cauchy', 'random_exponential', 'random_gamma', 'random_gumbel', 'random_chi2', 'random_laplace', 'random_log_normal', 'random_normal', 'random_poisson', 'random_studentt', 'random_uniform', 'random_weibull', 'randp', 'randt', 'randu', 'randw', 'range', 'relu', 'remap', 'remove_key', 'removed_keys', 'repeat', 'replace', 'reset_tracking', 'reshape', 'rgb_to_cielab', 'rgb_to_hsv', 'rgb_to_int', 'rgb_to_oklab', 'ridged', 'ridged_noise', 'rife', 'rm_key', 'roll', 'round', 'rshp', 'select', 'shape', 'shuffle', 'sigm', 'sign', 'sin', 'sine', 'sine_ease', 'singular_value_decomposition', 'sinh', 'smax', 'smin', 'smootherstep', 'smoothstep', 'snorm', 'softmax', 'softmin', 'softplus', 'sort', 'split', 'sqrt', 'squeeze', 'stack_clear', 'stack_get', 'stack_has', 'stack_pop', 'stack_push', 'startswith', 'std', 'step', 'substr', 'substring', 'sum', 'svd', 'swap', 'tan', 'tanh', 'tensor', 'text_image', 'timestamp', 'tmax', 'tmin', 'tnorm', 'topk', 'topk_ind', 'topk_indices', 'track_off', 'track_on', 'track_reset', 'trim', 'turbulence', 'unchanged_keys', 'unique', 'unsqueeze', 'upper', 'var', 'voronoi', 'voronoi_noise', 'where', 'worley'
|
||||
}
|
||||
|
||||
INBUILT_FUNCTION_META = {
|
||||
'sin': {'min_args': 1, 'max_args': 1, 'snippet': 'sin(x)', 'description': 'applies sinus function to value or each element of value'},
|
||||
'cos': {'min_args': 1, 'max_args': 1, 'snippet': 'cos(x)', 'description': 'applies cosinus function to value or each element of value'},
|
||||
'tan': {'min_args': 1, 'max_args': 1, 'snippet': 'tan(x)', 'description': 'applies tangents function to value or each element of value'},
|
||||
'asin': {'min_args': 1, 'max_args': 1, 'snippet': 'asin(x)', 'description': 'applies arcus sinus function to value or each element of value'},
|
||||
'acos': {'min_args': 1, 'max_args': 1, 'snippet': 'acos(x)', 'description': 'applies arcus cosinus function to value or each element of value'},
|
||||
'atan': {'min_args': 1, 'max_args': 1, 'snippet': 'atan(x)', 'description': 'applies arcus tangents function to value or each element of value'},
|
||||
'atan2': {'min_args': 2, 'max_args': 2, 'snippet': 'atan2(y, x)', 'description': 'computes the arc tangent of y/x, using the signs of both arguments to determine the quadrant'},
|
||||
'sinh': {'min_args': 1, 'max_args': 1, 'snippet': 'sinh(x)', 'description': 'applies hyperbolic sinus function to value or each element of value'},
|
||||
'cosh': {'min_args': 1, 'max_args': 1, 'snippet': 'cosh(x)', 'description': 'applies hyperbolic cosinus function to value or each element of value'},
|
||||
'tanh': {'min_args': 1, 'max_args': 1, 'snippet': 'tanh(x)', 'description': 'applies hyperbolic tangents function to value or each element of value'},
|
||||
'asinh': {'min_args': 1, 'max_args': 1, 'snippet': 'asinh(x)', 'description': 'applies hyperbolic arcus sinus function to value or each element of value'},
|
||||
'acosh': {'min_args': 1, 'max_args': 1, 'snippet': 'acosh(x)', 'description': 'applies hyperbolic arcus cosinus function to value or each element of value'},
|
||||
'atanh': {'min_args': 1, 'max_args': 1, 'snippet': 'atanh(x)', 'description': 'applies hyperbolic arcus tangents function to value or each element of value'},
|
||||
'abs': {'min_args': 1, 'max_args': 1, 'snippet': 'abs(x)', 'description': 'applies per element absolute value function. Same as |x| for numbers.'},
|
||||
'sqrt': {'min_args': 1, 'max_args': 1, 'snippet': 'sqrt(x)', 'description': 'applies sqere root per element. Negative numbers return NaN (not a number)'},
|
||||
'ln': {'min_args': 1, 'max_args': 1, 'snippet': 'ln(x)', 'description': 'applies natural logarithm to value (per element). Negative numbers return NaN (not a number)'},
|
||||
'log': {'min_args': 1, 'max_args': 1, 'snippet': 'log(x)', 'description': 'applies base 10 logarithm to value (per element). Negative numbers return NaN (not a number)'},
|
||||
'exp': {'min_args': 1, 'max_args': 1, 'snippet': 'exp(x)', 'description': 'applies e^x to value (per element)'},
|
||||
'smin': {'min_args': 1, 'max_args': None, 'snippet': 'smin(x, ...)', 'description': 'computes the minimum of inputs or elements. Tensors must have the same size or be broadcastable to same size. When called as smin(x, dims) with x a tensor/list, reduces x along dims (a single dim or list of dims) instead.'},
|
||||
'smax': {'min_args': 1, 'max_args': None, 'snippet': 'smax(x, ...)', 'description': 'computes the maximum of inputs or elements. Tensors must have the same size or be broadcastable to same size. When called as smax(x, dims) with x a tensor/list, reduces x along dims (a single dim or list of dims) instead.'},
|
||||
'tmin': {'min_args': 2, 'max_args': 2, 'snippet': 'tmin(x, y)', 'description': 'elementwise minimum of tensor or list. min(x,y) when inputs are floats'},
|
||||
'tmax': {'min_args': 2, 'max_args': 2, 'snippet': 'tmax(x, y)', 'description': 'elementwise maximum of tensor or list. max(x,y) when inputs are floats'},
|
||||
'tnorm': {'min_args': 1, 'max_args': 2, 'snippet': 'tnorm(x, dim)', 'description': 'normalises tensor or list by multiplication such that sum(x^2)==1.0 for each slice. Defaults to the last dimension; dim selects a different slicing dimension.'},
|
||||
'snorm': {'min_args': 1, 'max_args': 2, 'snippet': 'snorm(x, dim)', 'description': 'frobenius norm of tensor. Along dimension if dimension specified. Dimension can be a list. Defaults to no dimension.'},
|
||||
'floor': {'min_args': 1, 'max_args': 1, 'snippet': 'floor(x)', 'description': 'returns the largest integer less than or equal to x'},
|
||||
'ceil': {'min_args': 1, 'max_args': 1, 'snippet': 'ceil(x)', 'description': 'returns the smallest integer greater than or equal to x'},
|
||||
'round': {'min_args': 1, 'max_args': 1, 'snippet': 'round(x)', 'description': 'rounds x to the nearest integer'},
|
||||
'gamma': {'min_args': 1, 'max_args': 1, 'snippet': 'gamma(x)', 'description': 'computes the gamma function (per element)'},
|
||||
'pow': {'min_args': 2, 'max_args': 2, 'snippet': 'pow(x, y)', 'description': 'computes x raised to the power of y'},
|
||||
'sigm': {'min_args': 1, 'max_args': 1, 'snippet': 'sigm(x)', 'description': 'applies the sigmoid function: 1 / (1 + exp(-x))'},
|
||||
'clamp': {'min_args': 3, 'max_args': 3, 'snippet': 'clamp(x, min, max)', 'description': 'clamps x between min and max per element'},
|
||||
'fft': {'min_args': 1, 'max_args': 1, 'snippet': 'fft(x)', 'description': 'computes fast Fourier transform of x'},
|
||||
'ifft': {'min_args': 1, 'max_args': 2, 'snippet': 'ifft(x, shape)', 'description': 'inverse fast Fourier transform of x. shape (defaults to the node\'s tensor shape) sizes the K/Kx/Ky/.../frequency variables made available while evaluating x, it does not resize x itself.'},
|
||||
'angle': {'min_args': 1, 'max_args': 1, 'snippet': 'angle(x)', 'description': 'returns the angle of a complex number or vector'},
|
||||
'print': {'min_args': 1, 'max_args': 1, 'snippet': 'print(x)', 'description': 'prints the value of x to the console while passing output unchanged'},
|
||||
'print_shape': {'min_args': 1, 'max_args': 1, 'snippet': 'print_shape(x)', 'description': 'prints the shape of the tensor x'},
|
||||
'pshp': {'min_args': 1, 'max_args': 1, 'snippet': 'pshp(x)', 'description': 'prints the shape of the tensor x'},
|
||||
'nvl': {'min_args': 4, 'max_args': 4, 'snippet': 'nvl(x, nan, pos_inf, neg_inf)', 'description': 'null value replacement - inspired by NVL'},
|
||||
'nan_to_num': {'min_args': 4, 'max_args': 4, 'snippet': 'nan_to_num(x, nan, pos_inf, neg_inf)', 'description': 'null value replacement - inspired by NVL'},
|
||||
'lerp': {'min_args': 3, 'max_args': 3, 'snippet': 'lerp(a, b, t)', 'description': 'linear interpolation between a and b by t per element. t can be float or tensor.'},
|
||||
'step': {'min_args': 2, 'max_args': 2, 'snippet': 'step(x, edge)', 'description': 'returns 0 if x < edge, else 1'},
|
||||
'smoothstep': {'min_args': 3, 'max_args': 3, 'snippet': 'smoothstep(x, edge0, edge1)', 'description': 'smooth interpolation between edge0 and edge1'},
|
||||
'fract': {'min_args': 1, 'max_args': 1, 'snippet': 'fract(x)', 'description': 'returns the fractional part of x: x - floor(x)'},
|
||||
'relu': {'min_args': 1, 'max_args': 1, 'snippet': 'relu(x)', 'description': 'applies rectified linear unit function: max(0, x)'},
|
||||
'softplus': {'min_args': 1, 'max_args': 1, 'snippet': 'softplus(x)', 'description': 'applies the softplus function: ln(1 + exp(x))'},
|
||||
'gelu': {'min_args': 1, 'max_args': 1, 'snippet': 'gelu(x)', 'description': 'applies the Gaussian Error Linear Unit (GELU) activation function'},
|
||||
'sign': {'min_args': 1, 'max_args': 1, 'snippet': 'sign(x)', 'description': 'returns the sign of x: -1, 0, or 1'},
|
||||
'map': {'min_args': 2, 'max_args': 4, 'snippet': 'map(tensor, coord1, coord2, coord3)', 'description': 'samples/remaps input tensor using coordinates'},
|
||||
'ezconvolution': {'min_args': 2, 'max_args': 3, 'snippet': 'ezconvolution(tensor, kernel_sizes..., kernel)', 'description': 'applies convolution with kernel. Attempts to correctly convolve over size dimensions.'},
|
||||
'ezconv': {'min_args': 2, 'max_args': 3, 'snippet': 'ezconv(tensor, kernel_sizes..., kernel)', 'description': 'applies convolution with kernel. Attempts to correctly convolve over size dimensions.'},
|
||||
'convolution': {'min_args': 2, 'max_args': 3, 'snippet': 'convolution(tensor, kernel_sizes..., kernel)', 'description': 'applies convolution with kernel. Expects [batch,channel, ...]'},
|
||||
'conv': {'min_args': 2, 'max_args': 3, 'snippet': 'conv(tensor, kernel_sizes..., kernel)', 'description': 'applies convolution with kernel. Expects [batch,channel, ...]'},
|
||||
'swap': {'min_args': 4, 'max_args': 4, 'snippet': 'swap(x, dim, idx1, idx2)', 'description': 'swaps elements along dim between idx1 and idx2'},
|
||||
'permute': {'min_args': 2, 'max_args': 2, 'snippet': 'permute(x, dims)', 'description': 'permutes the dimensions of tensor x according to dims'},
|
||||
'perm': {'min_args': 2, 'max_args': 2, 'snippet': 'perm(x, dims)', 'description': 'permutes the dimensions of tensor x according to dims'},
|
||||
'reshape': {'min_args': 2, 'max_args': 2, 'snippet': 'reshape(x, shape)', 'description': 'reshapes tensor x to the specified shape'},
|
||||
'rshp': {'min_args': 2, 'max_args': 2, 'snippet': 'rshp(x, shape)', 'description': 'reshapes tensor x to the specified shape'},
|
||||
'range': {'min_args': 3, 'max_args': 3, 'snippet': 'range(start, stop, step)', 'description': 'creates a range of values between start (inclusive) and stop (exclusive) using step. Use linspace if you want value count.'},
|
||||
'linspace': {'min_args': 3, 'max_args': 3, 'snippet': 'linspace(start, stop, num)', 'description': 'creates num linearly spaced values from start to stop (inclusive). Use range if you want to control the step between values.'},
|
||||
'logspace': {'min_args': 4, 'max_args': 4, 'snippet': 'logspace(start, stop, num, base)', 'description': 'creates logarithmically spaced values'},
|
||||
'topk': {'min_args': 2, 'max_args': 2, 'snippet': 'topk(x, k)', 'description': 'returns the k largest elements of x. For tensors it keeps them in place.'},
|
||||
'botk': {'min_args': 2, 'max_args': 2, 'snippet': 'botk(x, k)', 'description': 'returns the k smallest elements of x. For tensors it keeps them in place.'},
|
||||
'pinv': {'min_args': 1, 'max_args': 1, 'snippet': 'pinv(x)', 'description': 'computes the permutative inverse of a list x (list must have uniqe elements) -- TODO: CHECK --'},
|
||||
'sum': {'min_args': 1, 'max_args': 2, 'snippet': 'sum(x, dims)', 'description': 'computes the sum of elements of x, optionally along one or more dimensions'},
|
||||
'mean': {'min_args': 1, 'max_args': 1, 'snippet': 'mean(x)', 'description': 'computes the arithmetic mean of elements of x'},
|
||||
'std': {'min_args': 1, 'max_args': 1, 'snippet': 'std(x)', 'description': 'computes the standard deviation of elements of x'},
|
||||
'var': {'min_args': 1, 'max_args': 1, 'snippet': 'var(x)', 'description': 'computes the variance of elements of x'},
|
||||
'quartile': {'min_args': 2, 'max_args': 2, 'snippet': 'quartile(x, q)', 'description': 'computes the q-th quartile of x'},
|
||||
'quartil': {'min_args': 2, 'max_args': 2, 'snippet': 'quartil(x, q)', 'description': 'computes the q-th quartile of x'},
|
||||
'percentile': {'min_args': 2, 'max_args': 2, 'snippet': 'percentile(x, p)', 'description': 'computes the p-th percentile of x'},
|
||||
'prcnt': {'min_args': 2, 'max_args': 2, 'snippet': 'prcnt(x, p)', 'description': 'computes the p-th percentile of x'},
|
||||
'quantile': {'min_args': 2, 'max_args': 2, 'snippet': 'quantile(x, q)', 'description': 'computes the q-th quantile of x'},
|
||||
'dot': {'min_args': 2, 'max_args': 2, 'snippet': 'dot(x, y)', 'description': 'computes the dot product of x and y after flattening both to 1D. Use cross(x, y) if you need a per-vector product with the last dimension kept.'},
|
||||
'moment': {'min_args': 3, 'max_args': 3, 'snippet': 'moment(x, a, k)', 'description': 'computes the k-th moment of x around a'},
|
||||
'erf': {'min_args': 1, 'max_args': 1, 'snippet': 'erf(x)', 'description': 'computes the error function (torch.erf)'},
|
||||
'erfinv': {'min_args': 1, 'max_args': 1, 'snippet': 'erfinv(x)', 'description': 'computes the inverse error function (torch.erfinv)'},
|
||||
'any': {'min_args': 1, 'max_args': 1, 'snippet': 'any(x)', 'description': 'returns 1 if any element of x is non-zero otherwise 0'},
|
||||
'all': {'min_args': 1, 'max_args': 1, 'snippet': 'all(x)', 'description': 'returns 1 if all elements of x are non-zero otherwise 0'},
|
||||
'edge': {'min_args': 1, 'max_args': 2, 'snippet': 'edge(x, matrix_size)', 'description': 'detects edges in x. Matrix size denotes the size of edge detection matrix.'},
|
||||
'blur': {'min_args': 2, 'max_args': 3, 'snippet': 'blur(x, sigma, reshape)', 'description': 'applies a Gaussian blur to x with specified sigma. if reshape has value of 1.0, then it tries orienting the input such that channel is in the direction of filter. Otherwise it expect color dimension to be the last.'},
|
||||
'gaussian': {'min_args': 2, 'max_args': 3, 'snippet': 'gaussian(x, sigma, reshape)', 'description': 'applies a Gaussian blur to x with specified sigma. if reshape has value of 1.0, then it tries orienting the input such that channel is in the direction of filter. Otherwise it expect color dimension to be the last.'},
|
||||
'median': {'min_args': 1, 'max_args': 1, 'snippet': 'median(x)', 'description': 'computes the median of elements of x'},
|
||||
'mode': {'min_args': 1, 'max_args': 1, 'snippet': 'mode(x)', 'description': 'computes the mode of elements of x'},
|
||||
'cumsum': {'min_args': 1, 'max_args': 2, 'snippet': 'cumsum(x, dim)', 'description': 'computes the cumulative sum, defaulting to the first dimension.'},
|
||||
'cumprod': {'min_args': 1, 'max_args': 1, 'snippet': 'cumprod(x)', 'description': 'computes the cumulative product over first dimension.'},
|
||||
'topk_ind': {'min_args': 2, 'max_args': 2, 'snippet': 'topk_ind(x, k)', 'description': 'returns the indices of the k largest elements of x'},
|
||||
'topk_indices': {'min_args': 2, 'max_args': 2, 'snippet': 'topk_indices(x, k)', 'description': 'returns the indices of the k largest elements of x'},
|
||||
'botk_ind': {'min_args': 2, 'max_args': 2, 'snippet': 'botk_ind(x, k)', 'description': 'returns the indices of the k smallest elements of x'},
|
||||
'botk_indices': {'min_args': 2, 'max_args': 2, 'snippet': 'botk_indices(x, k)', 'description': 'returns the indices of the k smallest elements of x'},
|
||||
'cubic_ease': {'min_args': 3, 'max_args': 3, 'snippet': 'cubic_ease(a, b, t)', 'description': 'cubic ease between a and b'},
|
||||
'cubic': {'min_args': 3, 'max_args': 3, 'snippet': 'cubic(a, b, t)', 'description': 'cubic ease between a and b'},
|
||||
'elastic_ease': {'min_args': 3, 'max_args': 3, 'snippet': 'elastic_ease(a, b, t)', 'description': 'elastic ease between a and b'},
|
||||
'elastic': {'min_args': 3, 'max_args': 3, 'snippet': 'elastic(a, b, t)', 'description': 'elastic ease between a and b'},
|
||||
'sine_ease': {'min_args': 3, 'max_args': 3, 'snippet': 'sine_ease(a, b, t)', 'description': 'sine easing between a and b'},
|
||||
'sine': {'min_args': 3, 'max_args': 3, 'snippet': 'sine(a, b, t)', 'description': 'sine easing between a and b'},
|
||||
'smootherstep': {'min_args': 3, 'max_args': 3, 'snippet': 'smootherstep(x, edge0, edge1)', 'description': 'smoother interpolation between edge0 and edge1'},
|
||||
'dist': {'min_args': 4, 'max_args': 4, 'snippet': 'dist(x1, y1, x2, y2)', 'description': 'computes Euclidean distance between (x1, y1) and (x2, y2)'},
|
||||
'distance': {'min_args': 4, 'max_args': 4, 'snippet': 'distance(x1, y1, x2, y2)', 'description': 'computes Euclidean distance between (x1, y1) and (x2, y2)'},
|
||||
'remap': {'min_args': 5, 'max_args': 5, 'snippet': 'remap(v, i_min, i_max, o_min, o_max)', 'description': 'remaps values from input range to output range'},
|
||||
'cossim': {'min_args': 2, 'max_args': 2, 'snippet': 'cossim(x, y)', 'description': 'computes the cosine similarity between x and y.'},
|
||||
'cosine_similarity': {'min_args': 2, 'max_args': 2, 'snippet': 'cosine_similarity(x, y)', 'description': 'computes the cosine similarity between x and y.'},
|
||||
'count': {'min_args': 1, 'max_args': 1, 'snippet': 'count(x)', 'description': 'returns the number of elements in x'},
|
||||
'cnt': {'min_args': 1, 'max_args': 1, 'snippet': 'cnt(x)', 'description': 'returns the number of elements in x'},
|
||||
'length': {'min_args': 1, 'max_args': 1, 'snippet': 'length(x)', 'description': 'returns the number of elements in x'},
|
||||
'keys': {'min_args': 1, 'max_args': 1, 'snippet': 'keys(x)', 'description': 'returns a list of dictionary keys in insertion order'},
|
||||
'enable_tracking': {'min_args': 1, 'max_args': 1, 'snippet': 'enable_tracking(d)', 'description': 'enables change tracking for ChangeTrackingDict d (returns d)'},
|
||||
'track_on': {'min_args': 1, 'max_args': 1, 'snippet': 'track_on(d)', 'description': 'enables change tracking for ChangeTrackingDict d (returns d)'},
|
||||
'disable_tracking': {'min_args': 1, 'max_args': 1, 'snippet': 'disable_tracking(d)', 'description': 'disables change tracking for ChangeTrackingDict d (returns d)'},
|
||||
'track_off': {'min_args': 1, 'max_args': 1, 'snippet': 'track_off(d)', 'description': 'disables change tracking for ChangeTrackingDict d (returns d)'},
|
||||
'reset_tracking': {'min_args': 1, 'max_args': 1, 'snippet': 'reset_tracking(d)', 'description': 'resets change tracking state for ChangeTrackingDict d (returns d)'},
|
||||
'track_reset': {'min_args': 1, 'max_args': 1, 'snippet': 'track_reset(d)', 'description': 'resets change tracking state for ChangeTrackingDict d (returns d)'},
|
||||
'get_changed_keys': {'min_args': 1, 'max_args': 1, 'snippet': 'get_changed_keys(d)', 'description': 'returns list of keys that were modified or added in ChangeTrackingDict d'},
|
||||
'changed_keys': {'min_args': 1, 'max_args': 1, 'snippet': 'changed_keys(d)', 'description': 'returns list of keys that were modified or added in ChangeTrackingDict d'},
|
||||
'get_added_keys': {'min_args': 1, 'max_args': 1, 'snippet': 'get_added_keys(d)', 'description': 'returns list of keys that were added (newly created) in ChangeTrackingDict d'},
|
||||
'added_keys': {'min_args': 1, 'max_args': 1, 'snippet': 'added_keys(d)', 'description': 'returns list of keys that were added (newly created) in ChangeTrackingDict d'},
|
||||
'get_removed_keys': {'min_args': 1, 'max_args': 1, 'snippet': 'get_removed_keys(d)', 'description': 'returns list of keys that were removed from ChangeTrackingDict d'},
|
||||
'removed_keys': {'min_args': 1, 'max_args': 1, 'snippet': 'removed_keys(d)', 'description': 'returns list of keys that were removed from ChangeTrackingDict d'},
|
||||
'get_unchanged_keys': {'min_args': 1, 'max_args': 1, 'snippet': 'get_unchanged_keys(d)', 'description': 'returns list of keys that remain unchanged in ChangeTrackingDict d'},
|
||||
'unchanged_keys': {'min_args': 1, 'max_args': 1, 'snippet': 'unchanged_keys(d)', 'description': 'returns list of keys that remain unchanged in ChangeTrackingDict d'},
|
||||
'repeat': {'min_args': 2, 'max_args': 3, 'snippet': 'repeat(x, count, dims)', 'description': 'repeats tensor elements; count may be scalar or per-dim list'},
|
||||
'flatten': {'min_args': 1, 'max_args': 1, 'snippet': 'flatten(x)', 'description': 'flattens tensor into a 1D tensor'},
|
||||
'append': {'min_args': 2, 'max_args': 2, 'snippet': 'append(x, y)', 'description': 'appends y to the end of x. If inputs are tensors use concatenate(x,...,dim)'},
|
||||
'get_value': {'min_args': 1, 'max_args': 1, 'snippet': 'get_value(slot)', 'description': 'returns the value at slot from stack'},
|
||||
'flow_apply': {'min_args': 2, 'max_args': 2, 'snippet': 'flow_apply(image, flow)', 'description': 'applies optical flow to an image'},
|
||||
'batch_shuffle': {'min_args': 2, 'max_args': 2, 'snippet': 'batch_shuffle(x, indices)', 'description': 'shuffles and duplicates or skips the batch dimension of x according to indices'},
|
||||
'shuffle': {'min_args': 2, 'max_args': 2, 'snippet': 'shuffle(x, indices)', 'description': 'shuffles and duplicates or skips the batch dimension of x according to indices'},
|
||||
'select': {'min_args': 2, 'max_args': 2, 'snippet': 'select(x, indices)', 'description': 'shuffles and duplicates or skips the batch dimension of x according to indices'},
|
||||
'motion_mask': {'min_args': 1, 'max_args': 1, 'snippet': 'motion_mask(x)', 'description': 'generates a motion mask from optical flow'},
|
||||
'flow_to_image': {'min_args': 1, 'max_args': 1, 'snippet': 'flow_to_image(x)', 'description': 'converts optical flow x to an RGB image. Up-Down is blue-yellow Left-right green-magenta.'},
|
||||
'flow_mag': {'min_args': 1, 'max_args': 1, 'snippet': 'flow_mag(x)', 'description': 'computes the magnitude of optical flow (or any other structure with last dimension of 2)'},
|
||||
'flow_magnitude': {'min_args': 1, 'max_args': 1, 'snippet': 'flow_magnitude(x)', 'description': 'computes the magnitude of optical flow (or any other structure with last dimension of 2)'},
|
||||
'flow_ang': {'min_args': 1, 'max_args': 1, 'snippet': 'flow_ang(x)', 'description': 'computes the angle of optical flow (basically angle() but real and imaginery is separate in 2 dimensions)'},
|
||||
'flow_angle': {'min_args': 1, 'max_args': 1, 'snippet': 'flow_angle(x)', 'description': 'computes the angle of optical flow (basically angle() but real and imaginery is separate in 2 dimensions)'},
|
||||
'where': {'min_args': 3, 'max_args': 3, 'snippet': 'where(condition, x, y)', 'description': 'returns x if condition is true, else y. Runs per element.'},
|
||||
'histogram': {'min_args': 4, 'max_args': 4, 'snippet': 'histogram(x, bins, min, max)', 'description': 'computes histogram'},
|
||||
'hist': {'min_args': 4, 'max_args': 4, 'snippet': 'hist(x, bins, min, max)', 'description': 'computes histogram'},
|
||||
'overlay': {'min_args': 3, 'max_args': 4, 'snippet': 'overlay(base, overlay, offset, opacity)', 'description': 'overlays overlay on base at offset. Opacity controls blending. It can be tensor or float. If it is tensor it should be the same shape as overlay.'},
|
||||
'pad': {'min_args': 2, 'max_args': 2, 'snippet': 'pad(x, padding)', 'description': 'pads tensor x with specified padding (dim 0 neg, dim 0 pos, dim 1 ...)'},
|
||||
'cross': {'min_args': 2, 'max_args': 2, 'snippet': 'cross(x, y)', 'description': 'computes the cross product of x and y. Last dimension must be 3'},
|
||||
'matmul': {'min_args': 2, 'max_args': 2, 'snippet': 'matmul(x, y)', 'description': 'computes the matrix multiplication of x and y'},
|
||||
'rife': {'min_args': 2, 'max_args': 5, 'snippet': 'rife(image1, image2, tiling_size, iterations, multi_scale)', 'description': 'computes the optical flow field ([H, W, 2]) between image1 and image2 using a RAFT-based model'},
|
||||
'bnot': {'min_args': 1, 'max_args': 1, 'snippet': 'bnot(x)', 'description': 'computes the bitwise NOT'},
|
||||
'bitwise_not': {'min_args': 1, 'max_args': 1, 'snippet': 'bitwise_not(x)', 'description': 'computes the bitwise NOT'},
|
||||
'bitcount': {'min_args': 1, 'max_args': 1, 'snippet': 'bitcount(x)', 'description': 'returns the number of set bits'},
|
||||
'popcount': {'min_args': 1, 'max_args': 1, 'snippet': 'popcount(x)', 'description': 'returns the number of set bits'},
|
||||
'popcnt': {'min_args': 1, 'max_args': 1, 'snippet': 'popcnt(x)', 'description': 'returns the number of set bits'},
|
||||
'shape': {'min_args': 1, 'max_args': 1, 'snippet': 'shape(x)', 'description': 'returns the shape of tensor as a list'},
|
||||
'band': {'min_args': 2, 'max_args': 2, 'snippet': 'band(x, y)', 'description': 'computes the element-wise bitwise AND of x and y'},
|
||||
'bitwise_and': {'min_args': 2, 'max_args': 2, 'snippet': 'bitwise_and(x, y)', 'description': 'computes the element-wise bitwise AND of x and y'},
|
||||
'bxor': {'min_args': 2, 'max_args': 2, 'snippet': 'bxor(x, y)', 'description': 'computes the element-wise bitwise XOR of x and y'},
|
||||
'bitwise_xor': {'min_args': 2, 'max_args': 2, 'snippet': 'bitwise_xor(x, y)', 'description': 'computes the element-wise bitwise XOR of x and y'},
|
||||
'bor': {'min_args': 2, 'max_args': 2, 'snippet': 'bor(x, y)', 'description': 'computes the element-wise bitwise OR of x and y'},
|
||||
'bitwise_or': {'min_args': 2, 'max_args': 2, 'snippet': 'bitwise_or(x, y)', 'description': 'computes the element-wise bitwise OR of x and y'},
|
||||
'tensor': {'min_args': 1, 'max_args': 3, 'snippet': 'tensor(shape, value, dtype_template)', 'description': 'creates a tensor with specified shape and optional value/type. Default value is 0 and default format FP32'},
|
||||
'add_key': {'min_args': 3, 'max_args': 3, 'snippet': 'add_key(dict, key, value)', 'description': 'adds or replaces a dictionary entry and returns the updated dictionary'},
|
||||
'remove_key': {'min_args': 2, 'max_args': 2, 'snippet': 'remove_key(dict, key)', 'description': 'removes a dictionary entry and returns the updated dictionary'},
|
||||
'rm_key': {'min_args': 2, 'max_args': 2, 'snippet': 'rm_key(dict, key)', 'description': 'removes a dictionary entry and returns the updated dictionary'},
|
||||
'stack_push': {'min_args': 2, 'max_args': 2, 'snippet': 'stack_push(slot, val)', 'description': 'pushes val onto the end of slot stack'},
|
||||
'stack_pop': {'min_args': 1, 'max_args': 1, 'snippet': 'stack_pop(slot)', 'description': 'removes and returns the last element of slot stack'},
|
||||
'stack_clear': {'min_args': 1, 'max_args': 1, 'snippet': 'stack_clear(slot)', 'description': 'clears the contents of slot stack'},
|
||||
'stack_has': {'min_args': 1, 'max_args': 1, 'snippet': 'stack_has(slot)', 'description': 'returns 1.0 if slot exists in stack and is not empty else returns 0.0'},
|
||||
'stack_get': {'min_args': 1, 'max_args': 1, 'snippet': 'stack_get(slot)', 'description': 'returns the last pushed value in slot without popping it'},
|
||||
'cat': {'min_args': 3, 'max_args': None, 'snippet': 'cat(x1, x2, ..., dim)', 'description': 'concatenates tensors along the specified dimension'},
|
||||
'concat': {'min_args': 3, 'max_args': None, 'snippet': 'concat(x1, x2, ..., dim)', 'description': 'concatenates tensors along the specified dimension'},
|
||||
'concatenate': {'min_args': 3, 'max_args': None, 'snippet': 'concatenate(x1, x2, ..., dim)', 'description': 'concatenates tensors along the specified dimension'},
|
||||
'int': {'min_args': 1, 'max_args': 1, 'snippet': 'int(x)', 'description': 'casts x to integer'},
|
||||
'float': {'min_args': 1, 'max_args': 1, 'snippet': 'float(x)', 'description': 'casts x to float'},
|
||||
'upper': {'min_args': 1, 'max_args': 1, 'snippet': 'upper(x)', 'description': 'converts string to uppercase'},
|
||||
'lower': {'min_args': 1, 'max_args': 1, 'snippet': 'lower(x)', 'description': 'converts string to lowercase'},
|
||||
'trim': {'min_args': 1, 'max_args': 1, 'snippet': 'trim(x)', 'description': 'removes leading and trailing whitespace from string'},
|
||||
'split': {'min_args': 1, 'max_args': 2, 'snippet': 'split(x, delimiter)', 'description': 'splits string to list of strings based on delimiter. Default is space.'},
|
||||
'join': {'min_args': 1, 'max_args': 2, 'snippet': 'join(x, separator)', 'description': 'joins strings in list using separator. Defalut separator is "". If you want to join tensors use concatinate(...)'},
|
||||
'substring': {'min_args': 2, 'max_args': 3, 'snippet': 'substring(s, start, length)', 'description': 'returns length characters of s starting at start. If length is not supplied, it returns the rest of the string'},
|
||||
'substr': {'min_args': 2, 'max_args': 3, 'snippet': 'substr(s, start, length)', 'description': 'returns length characters of s starting at start. If length is not supplied, it returns the rest of the string'},
|
||||
'find': {'min_args': 2, 'max_args': 2, 'snippet': 'find(s, sub)', 'description': 'returns the index of the first occurrence of sub in s. -1 if not found. Works only for strings.'},
|
||||
'replace': {'min_args': 3, 'max_args': 3, 'snippet': 'replace(s, old, new)', 'description': 'replaces occurrences of old with new in s. Mostly for strings but can work with lists and tensors.'},
|
||||
'dilate': {'min_args': 1, 'max_args': 2, 'snippet': 'dilate(x, kernel)', 'description': 'applies dilation to x. kernel is optional.'},
|
||||
'erode': {'min_args': 1, 'max_args': 2, 'snippet': 'erode(x, kernel)', 'description': 'applies erosion to x. kernel is size of used matrix.'},
|
||||
'morph_open': {'min_args': 1, 'max_args': 2, 'snippet': 'morph_open(x, kernel)', 'description': 'applies morphological opening to x. kernel is size of used matrix.'},
|
||||
'morph_close': {'min_args': 1, 'max_args': 2, 'snippet': 'morph_close(x, kernel)', 'description': 'applies morphological closing to x. kernel is size of used matrix.'},
|
||||
'rgb_to_oklab': {'min_args': 1, 'max_args': 3, 'snippet': 'rgb_to_oklab(r, g, b)', 'description': 'converts RGB to OKLab. It has 2 modes. Aither 1 or 3 parameters. With 1 parameter it expects last dimension of 3 and with 3 it concatinates them after converting.'},
|
||||
'rgb_to_cielab': {'min_args': 1, 'max_args': 3, 'snippet': 'rgb_to_cielab(r, g, b)', 'description': 'converts RGB to CIELAB. It has 2 modes. Aither 1 or 3 parameters. With 1 parameter it expects last dimension of 3 and with 3 it concatinates them after converting.'},
|
||||
'oklab_to_rgb': {'min_args': 1, 'max_args': 3, 'snippet': 'oklab_to_rgb(oklab)', 'description': 'converts OKLab to RGB. It has 2 modes. Aither 1 or 3 parameters. With 1 parameter it expects last dimension of 3 and with 3 it concatinates them after converting.'},
|
||||
'cielab_to_rgb': {'min_args': 1, 'max_args': 3, 'snippet': 'cielab_to_rgb(cielab)', 'description': 'converts CIELAB to RGB. It has 2 modes. Aither 1 or 3 parameters. With 1 parameter it expects last dimension of 3 and with 3 it concatinates them after converting.'},
|
||||
'rgb_to_hsv': {'min_args': 1, 'max_args': 4, 'snippet': 'rgb_to_hsv(rgb, degrees)', 'description': 'converts RGB to HSV. It has 2 modes. Aither 1 or 3 parameters. With 1 parameter it expects last dimension of 3 and with 3 it concatinates them after converting. If degrees is specified and nonzero it returns hue as 0-360 instead of 0-1'},
|
||||
'hsv_to_rgb': {'min_args': 1, 'max_args': 4, 'snippet': 'hsv_to_rgb(hsv, degrees)', 'description': 'converts HSV to RGB. It has 2 modes. Aither 1 or 3 parameters. With 1 parameter it expects last dimension of 3 and with 3 it concatinates them after converting.'},
|
||||
'int_to_rgb': {'min_args': 1, 'max_args': 1, 'snippet': 'int_to_rgb(val)', 'description': 'converts an integer to an RGB color. If input is tensor it stacks the resulting R, G and B tensors using last dimension. If It is list, List of lists is returned and if value, 3 long list is returned.'},
|
||||
'rgb_to_int': {'min_args': 3, 'max_args': 3, 'snippet': 'rgb_to_int(r, g, b)', 'description': 'converts RGB components to an integer It uses last dimension of tensor (or list) as R,G,B if one value. Othervise uses whole thing as channel.'},
|
||||
'interpolate_linear': {'min_args': 2, 'max_args': 2, 'snippet': 'interpolate_linear(x, y)', 'description': 'performs linear interpolation on X. Assumes that dim 0 is batch and dim 1 = channel'},
|
||||
'interpolate_area': {'min_args': 2, 'max_args': 2, 'snippet': 'interpolate_area(x, y)', 'description': 'performs area interpolation X. Assumes that dim 0 is batch and dim 1 = channel'},
|
||||
'interpolate_nearest': {'min_args': 2, 'max_args': 2, 'snippet': 'interpolate_nearest(x, y)', 'description': 'performs nearest neighbor interpolation X. Assumes that dim 0 is batch and dim 1 = channel'},
|
||||
'interpolate_nearest_exact': {'min_args': 2, 'max_args': 2, 'snippet': 'interpolate_nearest_exact(x, y)', 'description': 'performs nearest neighbor interpolation X. Assumes that dim 0 is batch and dim 1 = channel'},
|
||||
'text_image': {'min_args': 3, 'max_args': 9, 'snippet': 'text_image(text, font, size, max_width, weight, rotation_angle, line_spacing, italic, underline)', 'description': 'renders text to an 2D tensor'},
|
||||
'as_nested_tensor': {'min_args': 1, 'max_args': 1, 'snippet': 'as_nested_tensor(x)', 'description': 'converts a list to a nested tensor (special object containing tensors of different shapes behaving like a tensor)'},
|
||||
'as_nested': {'min_args': 1, 'max_args': 1, 'snippet': 'as_nested(x)', 'description': 'converts a list to a nested tensor (special object containing tensors of different shapes behaving like a tensor)'},
|
||||
'svd': {'min_args': 1, 'max_args': 2, 'snippet': 'svd(x, full_matrices)', 'description': 'computes the singular value decomposition of x. Returns a list of 3 tensors: U, S, V such that x = matmul(matmul(U, diagonal_matrix(S,shape(U))), V)'},
|
||||
'singular_value_decomposition': {'min_args': 1, 'max_args': 2, 'snippet': 'singular_value_decomposition(x, full_matrices)', 'description': 'computes the singular value decomposition of x. Returns a list of 3 tensors: U, S, V such that x = matmul(matmul(U, diagonal_matrix(S,shape(U))), V)'},
|
||||
'diagonal_matrix': {'min_args': 2, 'max_args': 5, 'snippet': 'diagonal_matrix(x, shape, offset, dim1, dim2)', 'description': 'creates a diagonal matrix from x with specified shape. offset is the diagonal offset. dim1 and dim2 are the dimensions to place the diagonal on.'},
|
||||
'diag': {'min_args': 2, 'max_args': 5, 'snippet': 'diag(x, shape, offset, dim1, dim2)', 'description': 'creates a diagonal matrix from x with specified shape. offset is the diagonal offset. dim1 and dim2 are the dimensions to place the diagonal on.'},
|
||||
'timestamp': {'min_args': 0, 'max_args': 0, 'snippet': 'timestamp()', 'description': 'returns the current system timestamp'},
|
||||
'now': {'min_args': 0, 'max_args': 0, 'snippet': 'now()', 'description': 'returns the current system timestamp'},
|
||||
'sort': {'min_args': 1, 'max_args': 3, 'snippet': 'sort(x, desc, dim)', 'description': 'returns a sorted version of x (If input is tensor, it sorts the last dimension)'},
|
||||
'argsort': {'min_args': 1, 'max_args': 3, 'snippet': 'argsort(x, desc, dim)', 'description': 'returns the indices that would sort x. desc defaults to ascending and dim defaults to the last dimension.'},
|
||||
'argmin': {'min_args': 1, 'max_args': 2, 'snippet': 'argmin(x, as_position)', 'description': 'returns the minimum position in flattened x or coordinates as a list when requested'},
|
||||
'argmax': {'min_args': 1, 'max_args': 2, 'snippet': 'argmax(x, as_position)', 'description': 'returns the maximum position in flattened x or coordinates as a list when requested'},
|
||||
'softmax': {'min_args': 1, 'max_args': 2, 'snippet': 'softmax(x, axis)', 'description': 'applies the softmax function to x (sum of slice == 1.0 and max value <= 1.0). axis defaults to last.'},
|
||||
'softmin': {'min_args': 1, 'max_args': 2, 'snippet': 'softmin(x, axis)', 'description': 'applies the softmin function to x (same as softmax(-x,[axis])). axis defaults to last.'},
|
||||
'unique': {'min_args': 1, 'max_args': 1, 'snippet': 'unique(x)', 'description': 'returns the unique elements (sorted, flatened)'},
|
||||
'flip': {'min_args': 2, 'max_args': 2, 'snippet': 'flip(x, dims)', 'description': 'reverses the order of elements along the specified dimensions. Works also for text and list with dims=0'},
|
||||
'startswith': {'min_args': 2, 'max_args': 2, 'snippet': 'startswith(text, prefix)', 'description': 'returns 1 if text starts with prefix, else 0'},
|
||||
'endswith': {'min_args': 2, 'max_args': 2, 'snippet': 'endswith(text, suffix)', 'description': 'returns 1 if text ends with suffix, else 0'},
|
||||
'squeeze': {'min_args': 1, 'max_args': 2, 'snippet': 'squeeze(x, dim)', 'description': 'removes size-1 dimensions from tensor x, optionally at dim'},
|
||||
'unsqueeze': {'min_args': 2, 'max_args': 2, 'snippet': 'unsqueeze(x, dim)', 'description': 'inserts a size-1 dimension into tensor x at dim'},
|
||||
'roll': {'min_args': 2, 'max_args': 3, 'snippet': 'roll(x, shifts, axis)', 'description': 'rolls tensor x along axis'},
|
||||
'cov': {'min_args': 2, 'max_args': 2, 'snippet': 'cov(x, y)', 'description': 'computes the covariance matrix of x and y'},
|
||||
'corr': {'min_args': 2, 'max_args': 2, 'snippet': 'corr(x, y)', 'description': 'computes the correlation between x and y'},
|
||||
'correlation': {'min_args': 2, 'max_args': 2, 'snippet': 'correlation(x, y)', 'description': 'computes the correlation between x and y'},
|
||||
'entropy': {'min_args': 1, 'max_args': 1, 'snippet': 'entropy(x)', 'description': 'computes shanon entropy'},
|
||||
'crop': {'min_args': 3, 'max_args': 3, 'snippet': 'crop(x, start, size)', 'description': 'crops tensor x. Take size and begin at start. start should be the low corner. The result is at most size (if size is bigger then rest of tensor).'},
|
||||
'coords': {'min_args': 2, 'max_args': 3, 'snippet': 'coords(shape, dim, dtype)', 'description': 'generates a tensor with the specified shape whose values are the coordinates of each element along the specified dimension. The dtype can be specified to control the data type of the output tensor.'},
|
||||
'coordinates': {'min_args': 2, 'max_args': 3, 'snippet': 'coordinates(shape, dim, dtype)', 'description': 'generates a tensor with the specified shape whose values are the coordinates of each element along the specified dimension. The dtype can be specified to control the data type of the output tensor.'},
|
||||
'noise': {'min_args': 1, 'max_args': 2, 'snippet': 'noise(seed, shape)', 'description': 'generates normally distributed random noise'},
|
||||
'randn': {'min_args': 1, 'max_args': 2, 'snippet': 'randn(seed, shape)', 'description': 'generates normally distributed random noise'},
|
||||
'random_normal': {'min_args': 1, 'max_args': 2, 'snippet': 'random_normal(seed, shape)', 'description': 'generates normally distributed random noise'},
|
||||
'rand': {'min_args': 1, 'max_args': 2, 'snippet': 'rand(seed, shape)', 'description': 'generates uniformly distributed random noise'},
|
||||
'randu': {'min_args': 1, 'max_args': 2, 'snippet': 'randu(seed, shape)', 'description': 'generates uniformly distributed random noise'},
|
||||
'random_uniform': {'min_args': 1, 'max_args': 2, 'snippet': 'random_uniform(seed, shape)', 'description': 'generates uniformly distributed random noise'},
|
||||
'randc': {'min_args': 3, 'max_args': 4, 'snippet': 'randc(seed, median, sigma, shape)', 'description': 'generates Cauchy distributed random values.'},
|
||||
'random_cauchy': {'min_args': 3, 'max_args': 4, 'snippet': 'random_cauchy(seed, median, sigma, shape)', 'description': 'generates Cauchy distributed random values.'},
|
||||
'rande': {'min_args': 2, 'max_args': 3, 'snippet': 'rande(seed, lambda, shape)', 'description': 'generates exponentially distributed random values'},
|
||||
'random_exponential': {'min_args': 2, 'max_args': 3, 'snippet': 'random_exponential(seed, lambda, shape)', 'description': 'generates exponentially distributed random values'},
|
||||
'randln': {'min_args': 3, 'max_args': 4, 'snippet': 'randln(seed, mean, std, shape)', 'description': 'generates log-normally distributed random values.'},
|
||||
'random_log_normal': {'min_args': 3, 'max_args': 4, 'snippet': 'random_log_normal(seed, mean, std, shape)', 'description': 'generates log-normally distributed random values.'},
|
||||
'randb': {'min_args': 2, 'max_args': 3, 'snippet': 'randb(seed, probability, shape)', 'description': 'generates Bernoulli distributed random values (0 or 1). Probability can be a tensor'},
|
||||
'random_bernoulli': {'min_args': 2, 'max_args': 3, 'snippet': 'random_bernoulli(seed, probability, shape)', 'description': 'generates Bernoulli distributed random values (0 or 1). Probability can be a tensor'},
|
||||
'randp': {'min_args': 2, 'max_args': 3, 'snippet': 'randp(seed, lambda, shape)', 'description': 'generates Poisson distributed random values.'},
|
||||
'random_poisson': {'min_args': 2, 'max_args': 3, 'snippet': 'random_poisson(seed, lambda, shape)', 'description': 'generates Poisson distributed random values.'},
|
||||
'randg': {'min_args': 3, 'max_args': 4, 'snippet': 'randg(seed, shape_param, scale, shape)', 'description': 'generates Gamma distributed random values.'},
|
||||
'random_gamma': {'min_args': 3, 'max_args': 4, 'snippet': 'random_gamma(seed, shape_param, scale, shape)', 'description': 'generates Gamma distributed random values.'},
|
||||
'randbeta': {'min_args': 3, 'max_args': 4, 'snippet': 'randbeta(seed, alpha, beta, shape)', 'description': 'generates Beta distributed random values'},
|
||||
'random_beta': {'min_args': 3, 'max_args': 4, 'snippet': 'random_beta(seed, alpha, beta, shape)', 'description': 'generates Beta distributed random values'},
|
||||
'randl': {'min_args': 3, 'max_args': 4, 'snippet': 'randl(seed, loc, scale, shape)', 'description': 'generates Laplace distributed random values'},
|
||||
'random_laplace': {'min_args': 3, 'max_args': 4, 'snippet': 'random_laplace(seed, loc, scale, shape)', 'description': 'generates Laplace distributed random values'},
|
||||
'randgumbel': {'min_args': 3, 'max_args': 4, 'snippet': 'randgumbel(seed, loc, scale, shape)', 'description': 'generates Gumbel distributed random values'},
|
||||
'random_gumbel': {'min_args': 3, 'max_args': 4, 'snippet': 'random_gumbel(seed, loc, scale, shape)', 'description': 'generates Gumbel distributed random values'},
|
||||
'randw': {'min_args': 3, 'max_args': 4, 'snippet': 'randw(seed, scale, concentration, shape)', 'description': 'generates Weibull distributed random values'},
|
||||
'random_weibull': {'min_args': 3, 'max_args': 4, 'snippet': 'random_weibull(seed, scale, concentration, shape)', 'description': 'generates Weibull distributed random values'},
|
||||
'randchi2': {'min_args': 2, 'max_args': 3, 'snippet': 'randchi2(seed, df, shape)', 'description': 'generates Chi-squared distributed random values'},
|
||||
'random_chi2': {'min_args': 2, 'max_args': 3, 'snippet': 'random_chi2(seed, df, shape)', 'description': 'generates Chi-squared distributed random values'},
|
||||
'randt': {'min_args': 2, 'max_args': 3, 'snippet': 'randt(seed, df, shape)', 'description': 'generates Student-t distributed random values'},
|
||||
'random_studentt': {'min_args': 2, 'max_args': 3, 'snippet': 'random_studentt(seed, df, shape)', 'description': 'generates Student-t distributed random values'},
|
||||
'perlin': {'min_args': 2, 'max_args': 5, 'snippet': 'perlin(seed, scale, octaves, offset, shape)', 'description': 'generates Perlin noise'},
|
||||
'perlin_noise': {'min_args': 2, 'max_args': 5, 'snippet': 'perlin_noise(seed, scale, octaves, offset, shape)', 'description': 'generates Perlin noise'},
|
||||
'cellular': {'min_args': 2, 'max_args': 5, 'snippet': 'cellular(seed, scale, jitter, offset, shape)', 'description': 'generates Cellular/Voronoi noise'},
|
||||
'voronoi': {'min_args': 2, 'max_args': 5, 'snippet': 'voronoi(seed, scale, jitter, offset, shape)', 'description': 'generates Cellular/Voronoi noise'},
|
||||
'worley': {'min_args': 2, 'max_args': 5, 'snippet': 'worley(seed, scale, jitter, offset, shape)', 'description': 'generates Cellular/Voronoi noise'},
|
||||
'cellular_noise': {'min_args': 2, 'max_args': 5, 'snippet': 'cellular_noise(seed, scale, jitter, offset, shape)', 'description': 'generates Cellular/Voronoi noise'},
|
||||
'voronoi_noise': {'min_args': 2, 'max_args': 5, 'snippet': 'voronoi_noise(seed, scale, jitter, offset, shape)', 'description': 'generates Cellular/Voronoi noise'},
|
||||
'plasma': {'min_args': 2, 'max_args': 5, 'snippet': 'plasma(seed, scale, octaves, offset, shape)', 'description': 'generates Plasma/Turbulence noise - same as perlin but default octaves 4'},
|
||||
'turbulence': {'min_args': 2, 'max_args': 5, 'snippet': 'turbulence(seed, scale, octaves, offset, shape)', 'description': 'generates Plasma/Turbulence noise - same as perlin but default octaves 4'},
|
||||
'plasma_noise': {'min_args': 2, 'max_args': 5, 'snippet': 'plasma_noise(seed, scale, octaves, offset, shape)', 'description': 'generates Plasma/Turbulence noise - same as perlin but default octaves 4'},
|
||||
'ridged': {'min_args': 2, 'max_args': 5, 'snippet': 'ridged(seed, scale, octaves, offset, shape)', 'description': 'generates ridged multi-fractal noise'},
|
||||
'ridged_noise': {'min_args': 2, 'max_args': 5, 'snippet': 'ridged_noise(seed, scale, octaves, offset, shape)', 'description': 'generates ridged multi-fractal noise'},
|
||||
'domain_warp': {'min_args': 4, 'max_args': 8, 'snippet': 'domain_warp(seed, scale, warp_scale, warp_strength, octaves, warp_octaves, offset, shape)', 'description': 'generates domain warped noise'},
|
||||
'domain_warp_noise': {'min_args': 4, 'max_args': 8, 'snippet': 'domain_warp_noise(seed, scale, warp_scale, warp_strength, octaves, warp_octaves, offset, shape)', 'description': 'generates domain warped noise'},
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,412 @@
|
||||
SIN=1
|
||||
COS=2
|
||||
TAN=3
|
||||
ASIN=4
|
||||
ACOS=5
|
||||
ATAN=6
|
||||
ATAN2=7
|
||||
SINH=8
|
||||
COSH=9
|
||||
TANH=10
|
||||
ASINH=11
|
||||
ACOSH=12
|
||||
ATANH=13
|
||||
ABS=14
|
||||
SQRT=15
|
||||
LN=16
|
||||
LOG=17
|
||||
EXP=18
|
||||
SMIN=19
|
||||
SMAX=20
|
||||
TMIN=21
|
||||
TMAX=22
|
||||
TNORM=23
|
||||
SNORM=24
|
||||
FLOOR=25
|
||||
CEIL=26
|
||||
ROUND=27
|
||||
GAMMA=28
|
||||
POWE=29
|
||||
SIGM=30
|
||||
CLAMP=31
|
||||
SFFT=32
|
||||
SIFFT=33
|
||||
ANGL=34
|
||||
PRNT=35
|
||||
PRINT_SHAPE=36
|
||||
NVL=37
|
||||
LERP=38
|
||||
STEP=39
|
||||
SMOOTHSTEP=40
|
||||
FRACT=41
|
||||
RELU=42
|
||||
SOFTPLUS=43
|
||||
GELU=44
|
||||
SIGN=45
|
||||
MAP=46
|
||||
EZCONV=47
|
||||
CONV=48
|
||||
SWAP=49
|
||||
PERM=50
|
||||
RESHAPE=51
|
||||
RANGE=52
|
||||
LINSPACE=53
|
||||
LOGSPACE=54
|
||||
TOPK=55
|
||||
BOTK=56
|
||||
PINV=57
|
||||
SUM=58
|
||||
MEAN=59
|
||||
STD=60
|
||||
VAR=61
|
||||
QUARTILE=62
|
||||
PERCENTILE=63
|
||||
QUANTILE=64
|
||||
DOT=65
|
||||
MOMENT=66
|
||||
ERF=67
|
||||
ERFINV=68
|
||||
ANY=69
|
||||
ALL=70
|
||||
EDGE=71
|
||||
GAUSSIAN=72
|
||||
MEDIAN=73
|
||||
MODE=74
|
||||
CUMSUM=75
|
||||
CUMPROD=76
|
||||
TOPK_IND=77
|
||||
BOTK_IND=78
|
||||
CUBIC_EASE=79
|
||||
ELASTIC_EASE=80
|
||||
SINE_EASE=81
|
||||
SMOOTHERSTEP=82
|
||||
DIST=83
|
||||
REMAP=84
|
||||
COSSIM=85
|
||||
COUNT=86
|
||||
KEYS=87
|
||||
ENABLE_TRACKING=88
|
||||
DISABLE_TRACKING=89
|
||||
RESET_TRACKING=90
|
||||
GET_CHANGED_KEYS=91
|
||||
GET_ADDED_KEYS=92
|
||||
GET_REMOVED_KEYS=93
|
||||
GET_UNCHANGED_KEYS=94
|
||||
REPEAT=95
|
||||
FLATTEN=96
|
||||
APPEND=97
|
||||
GET_VALUE=98
|
||||
FLOW_APPLY=99
|
||||
BATCH_SHUFFLE=100
|
||||
MOTION_MASK=101
|
||||
FLOW_TO_IMAGE=102
|
||||
FLOW_MAG=103
|
||||
FLOW_ANG=104
|
||||
WHERE=105
|
||||
HISTOGRAM=106
|
||||
OVERLAY=107
|
||||
PAD=108
|
||||
CROSS=109
|
||||
MATMUL=110
|
||||
RIFE=111
|
||||
BNOT=112
|
||||
BITCOUNT=113
|
||||
SHAPE=114
|
||||
BAND=115
|
||||
XOR=116
|
||||
BOR=117
|
||||
TENSOR=118
|
||||
ADD_KEY=119
|
||||
REMOVE_KEY=120
|
||||
PUSH=121
|
||||
POP=122
|
||||
CLEAR=123
|
||||
HAS=124
|
||||
GET=125
|
||||
CONCAT=126
|
||||
INT=127
|
||||
FLOAT=128
|
||||
UPPER=129
|
||||
LOWER=130
|
||||
TRIM=131
|
||||
SPLIT=132
|
||||
JOIN=133
|
||||
SUBSTRING=134
|
||||
FIND=135
|
||||
REPLACE=136
|
||||
DILATE=137
|
||||
ERODE=138
|
||||
MORPH_OPEN=139
|
||||
MORPH_CLOSE=140
|
||||
RGB_TO_OKLAB=141
|
||||
RGB_TO_CIELAB=142
|
||||
OKLAB_TO_RGB=143
|
||||
CIELAB_TO_RGB=144
|
||||
RGB_TO_HSV=145
|
||||
HSV_TO_RGB=146
|
||||
INT_TO_RGB=147
|
||||
RGB_TO_INT=148
|
||||
INTERPOLATE_LINEAR=149
|
||||
INTERPOLATE_AREA=150
|
||||
INTERPOLATE_NEAREST=151
|
||||
TEXT_IMAGE=152
|
||||
AS_NESTED=153
|
||||
SVD=154
|
||||
DIAG=155
|
||||
IF=156
|
||||
ELSE=157
|
||||
WHILE=158
|
||||
FOR=159
|
||||
IN=160
|
||||
BREAK=161
|
||||
CONTINUE=162
|
||||
RETURN=163
|
||||
TIMESTAMP=164
|
||||
SORT=165
|
||||
ARGSORT=166
|
||||
ARGMIN=167
|
||||
ARGMAX=168
|
||||
SOFTMAX=169
|
||||
SOFTMIN=170
|
||||
UNIQUE=171
|
||||
FLIP=172
|
||||
STARTSWITH=173
|
||||
ENDSWITH=174
|
||||
SQUEEZE=175
|
||||
UNSQUEEZE=176
|
||||
ROLL=177
|
||||
COV=178
|
||||
CORR=179
|
||||
ENTROPY=180
|
||||
CROP=181
|
||||
NONE=182
|
||||
COORDS=183
|
||||
NOISE=184
|
||||
RAND=185
|
||||
CAUCHY=186
|
||||
EXPONENTIAL=187
|
||||
LOGNORMAL=188
|
||||
BERNOULLI=189
|
||||
POISSON=190
|
||||
GAMMADIST=191
|
||||
BETADIST=192
|
||||
LAPLACEDIST=193
|
||||
GUMBELDIST=194
|
||||
WEIBULLDIST=195
|
||||
CHI2DIST=196
|
||||
STUDENTTDIST=197
|
||||
PERLIN=198
|
||||
CELLULAR=199
|
||||
PLASMA=200
|
||||
RIDGED=201
|
||||
DOMAIN_WARP=202
|
||||
PLUS=203
|
||||
MINUS=204
|
||||
MULT=205
|
||||
DIV=206
|
||||
MOD=207
|
||||
POW=208
|
||||
LSHIFT=209
|
||||
RSHIFT=210
|
||||
GE=211
|
||||
GT=212
|
||||
LE=213
|
||||
LT=214
|
||||
EQ=215
|
||||
EQUEALS=216
|
||||
PLUS_EQ=217
|
||||
MINUS_EQ=218
|
||||
MULT_EQ=219
|
||||
DIV_EQ=220
|
||||
MOD_EQ=221
|
||||
NE=222
|
||||
PIPE=223
|
||||
LPAREN=224
|
||||
RPAREN=225
|
||||
COMMA=226
|
||||
SEMICOLON=227
|
||||
ARROW=228
|
||||
LBRACKET=229
|
||||
RBRACKET=230
|
||||
QUESTION=231
|
||||
COLON=232
|
||||
LBRACE=233
|
||||
RBRACE=234
|
||||
NUMBER=235
|
||||
CONSTANT=236
|
||||
STRING=237
|
||||
VARIABLE=238
|
||||
SL_COMMENT=239
|
||||
ML_COMMENT=240
|
||||
WS=241
|
||||
'sin'=1
|
||||
'cos'=2
|
||||
'tan'=3
|
||||
'asin'=4
|
||||
'acos'=5
|
||||
'atan'=6
|
||||
'atan2'=7
|
||||
'sinh'=8
|
||||
'cosh'=9
|
||||
'tanh'=10
|
||||
'asinh'=11
|
||||
'acosh'=12
|
||||
'atanh'=13
|
||||
'abs'=14
|
||||
'sqrt'=15
|
||||
'ln'=16
|
||||
'log'=17
|
||||
'exp'=18
|
||||
'smin'=19
|
||||
'smax'=20
|
||||
'tmin'=21
|
||||
'tmax'=22
|
||||
'tnorm'=23
|
||||
'snorm'=24
|
||||
'floor'=25
|
||||
'ceil'=26
|
||||
'round'=27
|
||||
'gamma'=28
|
||||
'pow'=29
|
||||
'sigm'=30
|
||||
'clamp'=31
|
||||
'fft'=32
|
||||
'ifft'=33
|
||||
'angle'=34
|
||||
'print'=35
|
||||
'lerp'=38
|
||||
'step'=39
|
||||
'smoothstep'=40
|
||||
'fract'=41
|
||||
'relu'=42
|
||||
'softplus'=43
|
||||
'gelu'=44
|
||||
'sign'=45
|
||||
'map'=46
|
||||
'swap'=49
|
||||
'range'=52
|
||||
'linspace'=53
|
||||
'logspace'=54
|
||||
'topk'=55
|
||||
'botk'=56
|
||||
'pinv'=57
|
||||
'sum'=58
|
||||
'mean'=59
|
||||
'std'=60
|
||||
'var'=61
|
||||
'quantile'=64
|
||||
'dot'=65
|
||||
'moment'=66
|
||||
'erf'=67
|
||||
'erfinv'=68
|
||||
'any'=69
|
||||
'all'=70
|
||||
'edge'=71
|
||||
'median'=73
|
||||
'mode'=74
|
||||
'cumsum'=75
|
||||
'cumprod'=76
|
||||
'smootherstep'=82
|
||||
'remap'=84
|
||||
'keys'=87
|
||||
'repeat'=95
|
||||
'flatten'=96
|
||||
'append'=97
|
||||
'get_value'=98
|
||||
'flow_apply'=99
|
||||
'motion_mask'=101
|
||||
'flow_to_image'=102
|
||||
'where'=105
|
||||
'overlay'=107
|
||||
'pad'=108
|
||||
'cross'=109
|
||||
'matmul'=110
|
||||
'rife'=111
|
||||
'shape'=114
|
||||
'tensor'=118
|
||||
'add_key'=119
|
||||
'stack_push'=121
|
||||
'stack_pop'=122
|
||||
'stack_clear'=123
|
||||
'stack_has'=124
|
||||
'stack_get'=125
|
||||
'int'=127
|
||||
'float'=128
|
||||
'upper'=129
|
||||
'lower'=130
|
||||
'trim'=131
|
||||
'split'=132
|
||||
'join'=133
|
||||
'find'=135
|
||||
'replace'=136
|
||||
'dilate'=137
|
||||
'erode'=138
|
||||
'morph_open'=139
|
||||
'morph_close'=140
|
||||
'rgb_to_oklab'=141
|
||||
'rgb_to_cielab'=142
|
||||
'oklab_to_rgb'=143
|
||||
'cielab_to_rgb'=144
|
||||
'rgb_to_hsv'=145
|
||||
'hsv_to_rgb'=146
|
||||
'int_to_rgb'=147
|
||||
'rgb_to_int'=148
|
||||
'interpolate_linear'=149
|
||||
'interpolate_area'=150
|
||||
'text_image'=152
|
||||
'if'=156
|
||||
'else'=157
|
||||
'while'=158
|
||||
'for'=159
|
||||
'in'=160
|
||||
'break'=161
|
||||
'continue'=162
|
||||
'return'=163
|
||||
'sort'=165
|
||||
'argsort'=166
|
||||
'argmin'=167
|
||||
'argmax'=168
|
||||
'softmax'=169
|
||||
'softmin'=170
|
||||
'unique'=171
|
||||
'flip'=172
|
||||
'startswith'=173
|
||||
'endswith'=174
|
||||
'squeeze'=175
|
||||
'unsqueeze'=176
|
||||
'roll'=177
|
||||
'cov'=178
|
||||
'entropy'=180
|
||||
'crop'=181
|
||||
'+'=203
|
||||
'-'=204
|
||||
'*'=205
|
||||
'/'=206
|
||||
'%'=207
|
||||
'^'=208
|
||||
'<<'=209
|
||||
'>>'=210
|
||||
'>='=211
|
||||
'>'=212
|
||||
'<='=213
|
||||
'<'=214
|
||||
'=='=215
|
||||
'='=216
|
||||
'+='=217
|
||||
'-='=218
|
||||
'*='=219
|
||||
'/='=220
|
||||
'%='=221
|
||||
'!='=222
|
||||
'|'=223
|
||||
'('=224
|
||||
')'=225
|
||||
','=226
|
||||
';'=227
|
||||
'->'=228
|
||||
'['=229
|
||||
']'=230
|
||||
'?'=231
|
||||
':'=232
|
||||
'{'=233
|
||||
'}'=234
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,412 @@
|
||||
SIN=1
|
||||
COS=2
|
||||
TAN=3
|
||||
ASIN=4
|
||||
ACOS=5
|
||||
ATAN=6
|
||||
ATAN2=7
|
||||
SINH=8
|
||||
COSH=9
|
||||
TANH=10
|
||||
ASINH=11
|
||||
ACOSH=12
|
||||
ATANH=13
|
||||
ABS=14
|
||||
SQRT=15
|
||||
LN=16
|
||||
LOG=17
|
||||
EXP=18
|
||||
SMIN=19
|
||||
SMAX=20
|
||||
TMIN=21
|
||||
TMAX=22
|
||||
TNORM=23
|
||||
SNORM=24
|
||||
FLOOR=25
|
||||
CEIL=26
|
||||
ROUND=27
|
||||
GAMMA=28
|
||||
POWE=29
|
||||
SIGM=30
|
||||
CLAMP=31
|
||||
SFFT=32
|
||||
SIFFT=33
|
||||
ANGL=34
|
||||
PRNT=35
|
||||
PRINT_SHAPE=36
|
||||
NVL=37
|
||||
LERP=38
|
||||
STEP=39
|
||||
SMOOTHSTEP=40
|
||||
FRACT=41
|
||||
RELU=42
|
||||
SOFTPLUS=43
|
||||
GELU=44
|
||||
SIGN=45
|
||||
MAP=46
|
||||
EZCONV=47
|
||||
CONV=48
|
||||
SWAP=49
|
||||
PERM=50
|
||||
RESHAPE=51
|
||||
RANGE=52
|
||||
LINSPACE=53
|
||||
LOGSPACE=54
|
||||
TOPK=55
|
||||
BOTK=56
|
||||
PINV=57
|
||||
SUM=58
|
||||
MEAN=59
|
||||
STD=60
|
||||
VAR=61
|
||||
QUARTILE=62
|
||||
PERCENTILE=63
|
||||
QUANTILE=64
|
||||
DOT=65
|
||||
MOMENT=66
|
||||
ERF=67
|
||||
ERFINV=68
|
||||
ANY=69
|
||||
ALL=70
|
||||
EDGE=71
|
||||
GAUSSIAN=72
|
||||
MEDIAN=73
|
||||
MODE=74
|
||||
CUMSUM=75
|
||||
CUMPROD=76
|
||||
TOPK_IND=77
|
||||
BOTK_IND=78
|
||||
CUBIC_EASE=79
|
||||
ELASTIC_EASE=80
|
||||
SINE_EASE=81
|
||||
SMOOTHERSTEP=82
|
||||
DIST=83
|
||||
REMAP=84
|
||||
COSSIM=85
|
||||
COUNT=86
|
||||
KEYS=87
|
||||
ENABLE_TRACKING=88
|
||||
DISABLE_TRACKING=89
|
||||
RESET_TRACKING=90
|
||||
GET_CHANGED_KEYS=91
|
||||
GET_ADDED_KEYS=92
|
||||
GET_REMOVED_KEYS=93
|
||||
GET_UNCHANGED_KEYS=94
|
||||
REPEAT=95
|
||||
FLATTEN=96
|
||||
APPEND=97
|
||||
GET_VALUE=98
|
||||
FLOW_APPLY=99
|
||||
BATCH_SHUFFLE=100
|
||||
MOTION_MASK=101
|
||||
FLOW_TO_IMAGE=102
|
||||
FLOW_MAG=103
|
||||
FLOW_ANG=104
|
||||
WHERE=105
|
||||
HISTOGRAM=106
|
||||
OVERLAY=107
|
||||
PAD=108
|
||||
CROSS=109
|
||||
MATMUL=110
|
||||
RIFE=111
|
||||
BNOT=112
|
||||
BITCOUNT=113
|
||||
SHAPE=114
|
||||
BAND=115
|
||||
XOR=116
|
||||
BOR=117
|
||||
TENSOR=118
|
||||
ADD_KEY=119
|
||||
REMOVE_KEY=120
|
||||
PUSH=121
|
||||
POP=122
|
||||
CLEAR=123
|
||||
HAS=124
|
||||
GET=125
|
||||
CONCAT=126
|
||||
INT=127
|
||||
FLOAT=128
|
||||
UPPER=129
|
||||
LOWER=130
|
||||
TRIM=131
|
||||
SPLIT=132
|
||||
JOIN=133
|
||||
SUBSTRING=134
|
||||
FIND=135
|
||||
REPLACE=136
|
||||
DILATE=137
|
||||
ERODE=138
|
||||
MORPH_OPEN=139
|
||||
MORPH_CLOSE=140
|
||||
RGB_TO_OKLAB=141
|
||||
RGB_TO_CIELAB=142
|
||||
OKLAB_TO_RGB=143
|
||||
CIELAB_TO_RGB=144
|
||||
RGB_TO_HSV=145
|
||||
HSV_TO_RGB=146
|
||||
INT_TO_RGB=147
|
||||
RGB_TO_INT=148
|
||||
INTERPOLATE_LINEAR=149
|
||||
INTERPOLATE_AREA=150
|
||||
INTERPOLATE_NEAREST=151
|
||||
TEXT_IMAGE=152
|
||||
AS_NESTED=153
|
||||
SVD=154
|
||||
DIAG=155
|
||||
IF=156
|
||||
ELSE=157
|
||||
WHILE=158
|
||||
FOR=159
|
||||
IN=160
|
||||
BREAK=161
|
||||
CONTINUE=162
|
||||
RETURN=163
|
||||
TIMESTAMP=164
|
||||
SORT=165
|
||||
ARGSORT=166
|
||||
ARGMIN=167
|
||||
ARGMAX=168
|
||||
SOFTMAX=169
|
||||
SOFTMIN=170
|
||||
UNIQUE=171
|
||||
FLIP=172
|
||||
STARTSWITH=173
|
||||
ENDSWITH=174
|
||||
SQUEEZE=175
|
||||
UNSQUEEZE=176
|
||||
ROLL=177
|
||||
COV=178
|
||||
CORR=179
|
||||
ENTROPY=180
|
||||
CROP=181
|
||||
NONE=182
|
||||
COORDS=183
|
||||
NOISE=184
|
||||
RAND=185
|
||||
CAUCHY=186
|
||||
EXPONENTIAL=187
|
||||
LOGNORMAL=188
|
||||
BERNOULLI=189
|
||||
POISSON=190
|
||||
GAMMADIST=191
|
||||
BETADIST=192
|
||||
LAPLACEDIST=193
|
||||
GUMBELDIST=194
|
||||
WEIBULLDIST=195
|
||||
CHI2DIST=196
|
||||
STUDENTTDIST=197
|
||||
PERLIN=198
|
||||
CELLULAR=199
|
||||
PLASMA=200
|
||||
RIDGED=201
|
||||
DOMAIN_WARP=202
|
||||
PLUS=203
|
||||
MINUS=204
|
||||
MULT=205
|
||||
DIV=206
|
||||
MOD=207
|
||||
POW=208
|
||||
LSHIFT=209
|
||||
RSHIFT=210
|
||||
GE=211
|
||||
GT=212
|
||||
LE=213
|
||||
LT=214
|
||||
EQ=215
|
||||
EQUEALS=216
|
||||
PLUS_EQ=217
|
||||
MINUS_EQ=218
|
||||
MULT_EQ=219
|
||||
DIV_EQ=220
|
||||
MOD_EQ=221
|
||||
NE=222
|
||||
PIPE=223
|
||||
LPAREN=224
|
||||
RPAREN=225
|
||||
COMMA=226
|
||||
SEMICOLON=227
|
||||
ARROW=228
|
||||
LBRACKET=229
|
||||
RBRACKET=230
|
||||
QUESTION=231
|
||||
COLON=232
|
||||
LBRACE=233
|
||||
RBRACE=234
|
||||
NUMBER=235
|
||||
CONSTANT=236
|
||||
STRING=237
|
||||
VARIABLE=238
|
||||
SL_COMMENT=239
|
||||
ML_COMMENT=240
|
||||
WS=241
|
||||
'sin'=1
|
||||
'cos'=2
|
||||
'tan'=3
|
||||
'asin'=4
|
||||
'acos'=5
|
||||
'atan'=6
|
||||
'atan2'=7
|
||||
'sinh'=8
|
||||
'cosh'=9
|
||||
'tanh'=10
|
||||
'asinh'=11
|
||||
'acosh'=12
|
||||
'atanh'=13
|
||||
'abs'=14
|
||||
'sqrt'=15
|
||||
'ln'=16
|
||||
'log'=17
|
||||
'exp'=18
|
||||
'smin'=19
|
||||
'smax'=20
|
||||
'tmin'=21
|
||||
'tmax'=22
|
||||
'tnorm'=23
|
||||
'snorm'=24
|
||||
'floor'=25
|
||||
'ceil'=26
|
||||
'round'=27
|
||||
'gamma'=28
|
||||
'pow'=29
|
||||
'sigm'=30
|
||||
'clamp'=31
|
||||
'fft'=32
|
||||
'ifft'=33
|
||||
'angle'=34
|
||||
'print'=35
|
||||
'lerp'=38
|
||||
'step'=39
|
||||
'smoothstep'=40
|
||||
'fract'=41
|
||||
'relu'=42
|
||||
'softplus'=43
|
||||
'gelu'=44
|
||||
'sign'=45
|
||||
'map'=46
|
||||
'swap'=49
|
||||
'range'=52
|
||||
'linspace'=53
|
||||
'logspace'=54
|
||||
'topk'=55
|
||||
'botk'=56
|
||||
'pinv'=57
|
||||
'sum'=58
|
||||
'mean'=59
|
||||
'std'=60
|
||||
'var'=61
|
||||
'quantile'=64
|
||||
'dot'=65
|
||||
'moment'=66
|
||||
'erf'=67
|
||||
'erfinv'=68
|
||||
'any'=69
|
||||
'all'=70
|
||||
'edge'=71
|
||||
'median'=73
|
||||
'mode'=74
|
||||
'cumsum'=75
|
||||
'cumprod'=76
|
||||
'smootherstep'=82
|
||||
'remap'=84
|
||||
'keys'=87
|
||||
'repeat'=95
|
||||
'flatten'=96
|
||||
'append'=97
|
||||
'get_value'=98
|
||||
'flow_apply'=99
|
||||
'motion_mask'=101
|
||||
'flow_to_image'=102
|
||||
'where'=105
|
||||
'overlay'=107
|
||||
'pad'=108
|
||||
'cross'=109
|
||||
'matmul'=110
|
||||
'rife'=111
|
||||
'shape'=114
|
||||
'tensor'=118
|
||||
'add_key'=119
|
||||
'stack_push'=121
|
||||
'stack_pop'=122
|
||||
'stack_clear'=123
|
||||
'stack_has'=124
|
||||
'stack_get'=125
|
||||
'int'=127
|
||||
'float'=128
|
||||
'upper'=129
|
||||
'lower'=130
|
||||
'trim'=131
|
||||
'split'=132
|
||||
'join'=133
|
||||
'find'=135
|
||||
'replace'=136
|
||||
'dilate'=137
|
||||
'erode'=138
|
||||
'morph_open'=139
|
||||
'morph_close'=140
|
||||
'rgb_to_oklab'=141
|
||||
'rgb_to_cielab'=142
|
||||
'oklab_to_rgb'=143
|
||||
'cielab_to_rgb'=144
|
||||
'rgb_to_hsv'=145
|
||||
'hsv_to_rgb'=146
|
||||
'int_to_rgb'=147
|
||||
'rgb_to_int'=148
|
||||
'interpolate_linear'=149
|
||||
'interpolate_area'=150
|
||||
'text_image'=152
|
||||
'if'=156
|
||||
'else'=157
|
||||
'while'=158
|
||||
'for'=159
|
||||
'in'=160
|
||||
'break'=161
|
||||
'continue'=162
|
||||
'return'=163
|
||||
'sort'=165
|
||||
'argsort'=166
|
||||
'argmin'=167
|
||||
'argmax'=168
|
||||
'softmax'=169
|
||||
'softmin'=170
|
||||
'unique'=171
|
||||
'flip'=172
|
||||
'startswith'=173
|
||||
'endswith'=174
|
||||
'squeeze'=175
|
||||
'unsqueeze'=176
|
||||
'roll'=177
|
||||
'cov'=178
|
||||
'entropy'=180
|
||||
'crop'=181
|
||||
'+'=203
|
||||
'-'=204
|
||||
'*'=205
|
||||
'/'=206
|
||||
'%'=207
|
||||
'^'=208
|
||||
'<<'=209
|
||||
'>>'=210
|
||||
'>='=211
|
||||
'>'=212
|
||||
'<='=213
|
||||
'<'=214
|
||||
'=='=215
|
||||
'='=216
|
||||
'+='=217
|
||||
'-='=218
|
||||
'*='=219
|
||||
'/='=220
|
||||
'%='=221
|
||||
'!='=222
|
||||
'|'=223
|
||||
'('=224
|
||||
')'=225
|
||||
','=226
|
||||
';'=227
|
||||
'->'=228
|
||||
'['=229
|
||||
']'=230
|
||||
'?'=231
|
||||
':'=232
|
||||
'{'=233
|
||||
'}'=234
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,270 @@
|
||||
"""
|
||||
GPU-accelerated noise generation utilities for arbitrary ND tensors.
|
||||
Pure PyTorch implementation with deterministic seeding.
|
||||
True Perlin noise with gradient interpolation - arbitrary dimensions.
|
||||
"""
|
||||
|
||||
import torch
|
||||
|
||||
class NoiseUtils:
|
||||
@staticmethod
|
||||
def _fade(t):
|
||||
"""
|
||||
Optimalizovaná Perlinova fade křivka.
|
||||
Místo t**3 používáme t * t * t, což je v PyTorchi řádově rychlejší.
|
||||
"""
|
||||
return t * t * t * (t * (t * 6.0 - 15.0) + 10.0)
|
||||
|
||||
@staticmethod
|
||||
def _hash_nd(coords, seed=0):
|
||||
"""
|
||||
PLNĚ VEKTORIZOVANÁ matematická hashovací funkce.
|
||||
Žádné bitové operace. Využívá pseudo-náhodnou sinusovou vlnu.
|
||||
Čas výpočtu pro 25 mil. bodů klesne na milisekundy.
|
||||
"""
|
||||
# Použijeme stabilní deterministické koeficienty pro míchání dimenzí
|
||||
constants = [12.9898, 78.233, 45.164, 92.411, 27.531, 53.892]
|
||||
|
||||
# Inicializace jako float pro implicitní broadcasting místo full_like
|
||||
dt = float(seed)
|
||||
|
||||
# Lineární kombinace souřadnic s prvočíselnými vahami
|
||||
for i, coord in enumerate(coords):
|
||||
# Pokud už je to float32, to() je skoro no-op, ale můžeme rovnou přičítat
|
||||
dt = dt + coord.to(torch.float32) * constants[i % len(constants)]
|
||||
|
||||
# Velmi rychlý a osvědčený hash z GPU shaderů: fract(sin(x) * 43758.5453)
|
||||
st = torch.sin(dt) * 43758.5453
|
||||
h = st - torch.floor(st)
|
||||
|
||||
# Vrátí hodnotu bezpečně v rozsahu [0, 0.9999] jako původní funkce
|
||||
return h.clamp(0.0, 1.0)
|
||||
|
||||
@staticmethod
|
||||
def perlin_noise_nd(coords_input, scale=1.0, seed=0, device=None):
|
||||
"""
|
||||
N-dimensional Perlin noise with proper gradient interpolation.
|
||||
coords_input: tuple of coordinate tensors with same shape
|
||||
scale: frequency scale
|
||||
seed: random seed
|
||||
Returns: noise tensor in same shape as input coordinates
|
||||
"""
|
||||
ndim = len(coords_input)
|
||||
|
||||
# Scale coordinates
|
||||
coords = tuple(c / scale for c in coords_input)
|
||||
|
||||
# Split into integer and fractional parts
|
||||
# Keep coords_floor as float32 to avoid expensive conversions to and from long
|
||||
coords_floor = tuple(torch.floor(c).to(torch.float32) for c in coords)
|
||||
coords_frac = tuple(c - coords_floor[d] for d, c in enumerate(coords))
|
||||
|
||||
# Fade curves for each dimension
|
||||
fades = tuple(NoiseUtils._fade(f) for f in coords_frac)
|
||||
|
||||
# Accumulate weighted corner contributions
|
||||
result = torch.zeros_like(coords_input[0])
|
||||
prime_seeds = [1013, 1619, 3137, 5021, 7919, 10427]
|
||||
for corner_idx in range(1 << ndim):
|
||||
corner_offsets = tuple((corner_idx >> d) & 1 for d in range(ndim))
|
||||
corner_coords = tuple(coords_floor[d] + corner_offsets[d] for d in range(ndim))
|
||||
|
||||
grad_components = []
|
||||
for d in range(ndim):
|
||||
h = NoiseUtils._hash_nd(corner_coords, seed + prime_seeds[d % len(prime_seeds)] * (d + 1))
|
||||
grad_components.append(h * 2.0 - 1.0)
|
||||
|
||||
# Optimalizace: výpočet normy a dot produktu iterativně bez torch.stack
|
||||
grad_norm_sq = grad_components[0] * grad_components[0]
|
||||
for d in range(1, ndim):
|
||||
grad_norm_sq += grad_components[d] * grad_components[d]
|
||||
|
||||
inv_norm = torch.where(grad_norm_sq == 0.0, torch.ones_like(grad_norm_sq), torch.rsqrt(grad_norm_sq))
|
||||
|
||||
dot = grad_components[0] * (coords_frac[0] - corner_offsets[0]) * inv_norm
|
||||
for d in range(1, ndim):
|
||||
dot += grad_components[d] * (coords_frac[d] - corner_offsets[d]) * inv_norm
|
||||
|
||||
weight = fades[0] if corner_offsets[0] else (1.0 - fades[0])
|
||||
for d in range(1, ndim):
|
||||
weight = weight * (fades[d] if corner_offsets[d] else (1.0 - fades[d]))
|
||||
|
||||
result += dot * weight
|
||||
|
||||
return torch.clamp(result, -1, 1)
|
||||
|
||||
@staticmethod
|
||||
def cellular_noise_nd(coords_input, scale=1.0, jitter=0.5, seed=0, device=None):
|
||||
"""
|
||||
N-dimensional Cellular (Voronoi) noise - OPRAVENÁ VERZE.
|
||||
Správně odečítá jednotlivé dimenze tensorů.
|
||||
"""
|
||||
ndim = len(coords_input)
|
||||
device = coords_input[0].device
|
||||
|
||||
# 1. Škálování a rozdělení souřadnic
|
||||
coords = tuple(c / scale for c in coords_input)
|
||||
# Optimalizace: uložit jako float32 pro rychlejší indexování a hash
|
||||
coords_cell = tuple(torch.floor(c).to(torch.float32) for c in coords)
|
||||
|
||||
# OPRAVA: Musíme indexovat konkrétní prvek z tuple pomocí `coords_cell[i]`
|
||||
coords_frac = tuple(coords[i] - coords_cell[i] for i in range(ndim))
|
||||
|
||||
# Pro inicializaci použijeme první tensor z mřížky, abychom dodrželi správný shape
|
||||
min_dist = torch.full_like(coords_input[0], float('inf'), dtype=torch.float32, device=device)
|
||||
|
||||
# 2. Generování všech 3^ndim offsetů přímo v PyTorchi
|
||||
base_range = torch.tensor([-1.0, 0.0, 1.0], dtype=torch.float32, device=device)
|
||||
offset_grids = torch.meshgrid(*[base_range] * ndim, indexing='ij')
|
||||
gpu_offsets = torch.stack([g.flatten() for g in offset_grids], dim=-1)
|
||||
num_neighbors = gpu_offsets.shape[0]
|
||||
|
||||
prime_seeds = [1013, 1619, 3137, 5021, 7919, 10427]
|
||||
|
||||
# 3. Plně akcelerovaný výpočet sousedství
|
||||
for n in range(num_neighbors):
|
||||
current_offset = gpu_offsets[n]
|
||||
|
||||
# Souřadnice sousední buňky složené ze všech dimenzí naráz pro správný hash
|
||||
neighbor_coords = tuple(coords_cell[i] + current_offset[i] for i in range(ndim))
|
||||
|
||||
dist_sq = 0.0
|
||||
for i in range(ndim):
|
||||
# Generování unikátního seedu pro každou dimenzi aktuálního souseda
|
||||
s = seed + prime_seeds[i % len(prime_seeds)] * (i + 2)
|
||||
h = NoiseUtils._hash_nd(neighbor_coords, s)
|
||||
|
||||
# Pozice feature pointu: offset + hash * jitter
|
||||
pos_i = current_offset[i] + h * jitter
|
||||
|
||||
# Vzdálenost pixelu od tohoto bodu
|
||||
diff = coords_frac[i] - pos_i
|
||||
dist_sq += diff * diff
|
||||
|
||||
dist = torch.sqrt(dist_sq)
|
||||
min_dist = torch.minimum(min_dist, dist)
|
||||
|
||||
return min_dist
|
||||
|
||||
|
||||
|
||||
@staticmethod
|
||||
def plasma_noise_nd(
|
||||
coords_input,
|
||||
scale=1.0,
|
||||
seed=0,
|
||||
device=None,
|
||||
octaves=4,
|
||||
persistence=0.5,
|
||||
lacunarity=2.0,
|
||||
):
|
||||
"""
|
||||
N-dimensional Plasma/Turbulence noise in [0, 1].
|
||||
Uses Perlin octaves with automatic octave limiting to reduce aliasing.
|
||||
"""
|
||||
result = torch.zeros_like(coords_input[0])
|
||||
amplitude = 1.0
|
||||
frequency = 1.0
|
||||
max_amplitude = 0.0
|
||||
|
||||
for octave in range(octaves):
|
||||
noise_octave = NoiseUtils.perlin_noise_nd(
|
||||
tuple((c / scale) * frequency for c in coords_input),
|
||||
1.0,
|
||||
seed + octave * 1000,
|
||||
device
|
||||
)
|
||||
|
||||
result = result + noise_octave * amplitude
|
||||
max_amplitude += amplitude
|
||||
|
||||
amplitude *= persistence
|
||||
frequency *= lacunarity
|
||||
|
||||
# Normalizace zpět do [-1, 1] a potom do [0, 1]
|
||||
result = result / max_amplitude
|
||||
return (result*2)+0.5
|
||||
|
||||
@staticmethod
|
||||
def _accumulate_octaves_nd(sample_fn, octaves=1, persistence=0.5, lacunarity=2.0):
|
||||
octaves = max(1, int(octaves))
|
||||
result = None
|
||||
amplitude = 1.0
|
||||
frequency = 1.0
|
||||
max_amplitude = 0.0
|
||||
|
||||
for octave in range(octaves):
|
||||
value = sample_fn(frequency, octave)
|
||||
if result is None:
|
||||
result = torch.zeros_like(value, dtype=torch.float32)
|
||||
result = result + value * amplitude
|
||||
max_amplitude += amplitude
|
||||
amplitude *= persistence
|
||||
frequency *= lacunarity
|
||||
|
||||
if max_amplitude == 0.0:
|
||||
return result
|
||||
return result / max_amplitude
|
||||
|
||||
@staticmethod
|
||||
def ridged_noise_nd(coords_input, scale=1.0, seed=0, device=None, octaves=4, persistence=0.5, lacunarity=2.0, ridge_power=2.0):
|
||||
"""
|
||||
Ridged multifractal noise in [0, 1].
|
||||
"""
|
||||
def sample(frequency, octave):
|
||||
noise = NoiseUtils.perlin_noise_nd(
|
||||
tuple((c / scale) * frequency for c in coords_input),
|
||||
1.0,
|
||||
seed + octave * 1000,
|
||||
device,
|
||||
)
|
||||
ridged = (1.0 - noise.abs()).clamp(0.0, 1.0)
|
||||
return ridged.pow(ridge_power)
|
||||
|
||||
return NoiseUtils._accumulate_octaves_nd(sample, octaves, persistence, lacunarity).clamp(0.0, 1.0)
|
||||
|
||||
@staticmethod
|
||||
def domain_warp_noise_nd(
|
||||
coords_input,
|
||||
scale=1.0,
|
||||
seed=0,
|
||||
device=None,
|
||||
warp_scale=1.0,
|
||||
warp_strength=0.35,
|
||||
octaves=4,
|
||||
warp_octaves=2,
|
||||
persistence=0.5,
|
||||
lacunarity=2.0,
|
||||
):
|
||||
"""
|
||||
Domain-warped Perlin noise in [0, 1].
|
||||
"""
|
||||
ndim = len(coords_input)
|
||||
|
||||
warp_fields = []
|
||||
for dim in range(ndim):
|
||||
def sample_warp(frequency, octave, dim=dim):
|
||||
return NoiseUtils.perlin_noise_nd(
|
||||
tuple((c / warp_scale) * frequency for c in coords_input),
|
||||
1.0,
|
||||
seed + 10000 + dim * 1000 + octave * 97,
|
||||
device,
|
||||
)
|
||||
|
||||
warp_fields.append(NoiseUtils._accumulate_octaves_nd(sample_warp, warp_octaves, persistence, lacunarity))
|
||||
|
||||
warped_coords = tuple(
|
||||
(coords_input[dim] / scale) + warp_fields[dim] * warp_strength
|
||||
for dim in range(ndim)
|
||||
)
|
||||
|
||||
def sample_base(frequency, octave):
|
||||
return NoiseUtils.perlin_noise_nd(
|
||||
tuple(c * frequency for c in warped_coords),
|
||||
1.0,
|
||||
seed + 20000 + octave * 1000,
|
||||
device,
|
||||
)
|
||||
|
||||
base = NoiseUtils._accumulate_octaves_nd(sample_base, octaves, persistence, lacunarity)
|
||||
return ((base + 1.0) * 0.5).clamp(0.0, 1.0)
|
||||
@@ -0,0 +1,426 @@
|
||||
import os
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
from torchvision.models.optical_flow import raft_large, Raft_Large_Weights
|
||||
from torchvision.utils import flow_to_image as torchvision_flow_to_image
|
||||
import numpy as np
|
||||
|
||||
_raft_model = None
|
||||
|
||||
def get_raft_model(device):
|
||||
global _raft_model
|
||||
if _raft_model is None:
|
||||
# First, try to load a user-provided checkpoint from ComfyUI's configurable
|
||||
# "optical_flow" folder (preferred). If that fails, fall back to the
|
||||
# torchvision default weights.
|
||||
try:
|
||||
# Imported here to avoid adding a hard dependency at module import time
|
||||
# when this utility may be used outside of a full ComfyUI environment.
|
||||
import folder_paths
|
||||
import comfy.utils
|
||||
|
||||
names = folder_paths.get_filename_list("optical_flow")
|
||||
model_name = None
|
||||
if names:
|
||||
# Choose the first available file by default (matching other UI lists).
|
||||
model_name = names[0]
|
||||
else:
|
||||
# Try to use torchvision weights but save them to the optical_flow folder
|
||||
try:
|
||||
weights = Raft_Large_Weights.DEFAULT
|
||||
|
||||
# Instantiate the torchvision model (this will download weights into the torchvision cache)
|
||||
try:
|
||||
model_dl = raft_large(weights=weights, progress=True)
|
||||
except Exception:
|
||||
model_dl = raft_large(weights=weights, progress=False)
|
||||
|
||||
# Move state dict to CPU and detach
|
||||
state_dict = {k: v.cpu() for k, v in model_dl.state_dict().items()}
|
||||
|
||||
# Try to infer original filename/extension from the weights metadata or URL
|
||||
candidate_name = None
|
||||
try:
|
||||
url = getattr(weights, 'url', None) or getattr(weights, '_url', None)
|
||||
if url:
|
||||
candidate_name = os.path.basename(str(url).split('?')[0])
|
||||
except Exception:
|
||||
candidate_name = None
|
||||
if not candidate_name:
|
||||
try:
|
||||
meta = getattr(weights, 'meta', None) or {}
|
||||
if isinstance(meta, dict):
|
||||
candidate_name = meta.get('filename') or meta.get('name')
|
||||
except Exception:
|
||||
candidate_name = None
|
||||
|
||||
# Ensure folder exists: use folder_paths mapping when available
|
||||
save_dir = None
|
||||
if hasattr(folder_paths, 'folder_names_and_paths') and 'optical_flow' in folder_paths.folder_names_and_paths:
|
||||
dirs = folder_paths.folder_names_and_paths['optical_flow'][0]
|
||||
if isinstance(dirs, (list, tuple)) and len(dirs) > 0:
|
||||
save_dir = dirs[0]
|
||||
if not save_dir:
|
||||
save_dir = os.path.join(os.path.dirname(__file__), "..", "..", "models", "optical_flow")
|
||||
os.makedirs(save_dir, exist_ok=True)
|
||||
|
||||
# Decide filename and format
|
||||
if candidate_name:
|
||||
model_name = candidate_name
|
||||
else:
|
||||
model_name = "raft_large_default.pth"
|
||||
|
||||
model_path = folder_paths.get_full_path("optical_flow", model_name) or os.path.join(save_dir, model_name)
|
||||
|
||||
# Save in original format if known (.safetensors or .pth/.pt), otherwise default to .pth
|
||||
_, ext = os.path.splitext(model_name)
|
||||
ext = ext.lower()
|
||||
saved_ok = False
|
||||
try:
|
||||
if ext in ('.safetensors', '.sft'):
|
||||
# Save as safetensors
|
||||
try:
|
||||
comfy.utils.save_torch_file(state_dict, model_path)
|
||||
saved_ok = True
|
||||
except Exception:
|
||||
import safetensors.torch
|
||||
safetensors.torch.save_file(state_dict, model_path)
|
||||
saved_ok = True
|
||||
else:
|
||||
# Default: save as a PyTorch checkpoint (.pth)
|
||||
try:
|
||||
import torch as _torch
|
||||
_torch.save(state_dict, model_path)
|
||||
saved_ok = True
|
||||
except Exception:
|
||||
# Fallback to comfy.utils (may write safetensors)
|
||||
try:
|
||||
comfy.utils.save_torch_file(state_dict, model_path)
|
||||
saved_ok = True
|
||||
except Exception:
|
||||
saved_ok = False
|
||||
except Exception:
|
||||
saved_ok = False
|
||||
|
||||
if not saved_ok:
|
||||
model_name = None
|
||||
except Exception:
|
||||
model_name = None
|
||||
|
||||
if model_name:
|
||||
try:
|
||||
model_path = folder_paths.get_full_path_or_raise("optical_flow", model_name)
|
||||
sd = comfy.utils.load_torch_file(model_path, safe_load=True)
|
||||
|
||||
# Basic validation for a RAFT-large state dict
|
||||
has_raft_keys = (
|
||||
any(k.startswith("feature_encoder.") for k in sd)
|
||||
and any(k.startswith("context_encoder.") for k in sd)
|
||||
and any(k.startswith("update_block.") for k in sd)
|
||||
)
|
||||
if has_raft_keys:
|
||||
model = raft_large(weights=None, progress=False)
|
||||
model.load_state_dict(sd)
|
||||
_raft_model = model.to(device).eval()
|
||||
return _raft_model
|
||||
except Exception:
|
||||
pass
|
||||
except Exception:
|
||||
# If anything goes wrong with folder resolution / loading, fall back below.
|
||||
pass
|
||||
|
||||
# Fallback to torchvision-provided default weights
|
||||
weights = Raft_Large_Weights.DEFAULT
|
||||
_raft_model = raft_large(weights=weights, progress=False).to(device).eval()
|
||||
return _raft_model
|
||||
|
||||
def warp(x, flow, padding_mode='reflection'):
|
||||
"""
|
||||
Warps an image or tensor using the given flow.
|
||||
x: [B, C, H, W]
|
||||
flow: [B, 2, H, W] (flow in pixels)
|
||||
padding_mode: passed to F.grid_sample (e.g. 'zeros' for occlusion detection)
|
||||
"""
|
||||
b, c, h, w = x.shape
|
||||
device = flow.device # Use flow device as the master device
|
||||
x = x.to(device)
|
||||
|
||||
# Create grid
|
||||
grid_y, grid_x = torch.meshgrid(
|
||||
torch.linspace(0, h - 1, h, device=device),
|
||||
torch.linspace(0, w - 1, w, device=device),
|
||||
indexing='ij'
|
||||
)
|
||||
# [H, W]
|
||||
|
||||
grid = torch.stack((grid_x, grid_y), dim=0).unsqueeze(0).repeat(b, 1, 1, 1) # [B, 2, H, W]
|
||||
|
||||
# Add flow to grid
|
||||
v_grid = grid + flow # [B, 2, H, W]
|
||||
|
||||
# Normalize to [-1, 1] for grid_sample
|
||||
v_grid_x = 2.0 * v_grid[:, 0, :, :] / max(w - 1, 1) - 1.0
|
||||
v_grid_y = 2.0 * v_grid[:, 1, :, :] / max(h - 1, 1) - 1.0
|
||||
|
||||
v_grid = torch.stack((v_grid_x, v_grid_y), dim=3) # [B, H, W, 2] with (x, y)
|
||||
|
||||
return F.grid_sample(x, v_grid, mode='bilinear', padding_mode=padding_mode, align_corners=True)
|
||||
|
||||
def preprocess_image(img, device):
|
||||
"""
|
||||
img: [B, H, W, C] in range [0, 1]
|
||||
Returns: [B, 3, H, W] normalized for RAFT
|
||||
"""
|
||||
if img.ndim == 3:
|
||||
img = img.unsqueeze(0)
|
||||
|
||||
# ComfyUI [B, H, W, C] -> Torch [B, C, H, W]
|
||||
img = img.movedim(-1, 1)
|
||||
|
||||
# Ensure RGB
|
||||
if img.shape[1] == 1:
|
||||
img = img.expand(-1, 3, -1, -1)
|
||||
elif img.shape[1] > 3:
|
||||
img = img[:, :3, :, :]
|
||||
|
||||
# Scale to [0, 255] as RAFT transforms usually expect this
|
||||
img = img * 255.0
|
||||
|
||||
# The transform expects [0, 255] and returns normalized [-1, 1]
|
||||
# It takes (img1, img2) but we can use it for one or just follow its logic
|
||||
# Actually, let's just follow the logic: 2 * (img / 255.0) - 1.0
|
||||
# Wait, if I do that, it's just 2 * img_01 - 1.0.
|
||||
|
||||
return (2.0 * (img / 255.0) - 1.0).to(device)
|
||||
|
||||
def get_optical_flow(img1, img2, tiling_size=0, iterations=12, multi_scale=False):
|
||||
"""
|
||||
img1, img2: [H, W, C] or [B, H, W, C]
|
||||
Returns: [B, H, W, 2] flow vectors
|
||||
"""
|
||||
device = img1.device
|
||||
model = get_raft_model(device)
|
||||
|
||||
img1_proc = preprocess_image(img1, device)
|
||||
img2_proc = preprocess_image(img2, device)
|
||||
|
||||
b, _, h, w = img1_proc.shape
|
||||
|
||||
# Auto-tiling for large images to prevent OOM
|
||||
# 2MPx is a reasonable limit for 8GB VRAM without tiling
|
||||
if tiling_size <= 0 and (h * w) > 2000000:
|
||||
tiling_size = 1024
|
||||
|
||||
# Handle fraction or float conversion
|
||||
if 0 < tiling_size < 1:
|
||||
tiling_size = int(min(h, w) * tiling_size)
|
||||
else:
|
||||
tiling_size = int(tiling_size)
|
||||
|
||||
# Ensure divisible by 8 for RAFT
|
||||
h_new, w_new = (h // 8) * 8, (w // 8) * 8
|
||||
if h != h_new or w != w_new:
|
||||
img1_proc = F.interpolate(img1_proc, size=(h_new, w_new), mode='bilinear', align_corners=False)
|
||||
img2_proc = F.interpolate(img2_proc, size=(h_new, w_new), mode='bilinear', align_corners=False)
|
||||
|
||||
# Process sequentially to save memory
|
||||
flows = []
|
||||
with torch.no_grad():
|
||||
for i in range(b):
|
||||
p1 = img1_proc[i:i+1]
|
||||
p2 = img2_proc[i:i+1]
|
||||
|
||||
global_flow = None
|
||||
if multi_scale:
|
||||
# 1. Global Pass
|
||||
# Downsample more aggressively to capture massive jumps (max 256)
|
||||
g_size = 256
|
||||
g_h = (min(h_new, g_size) // 8) * 8
|
||||
g_w = (min(w_new, g_size) // 8) * 8
|
||||
|
||||
gp1 = F.interpolate(p1, size=(g_h, g_w), mode='bilinear', align_corners=False)
|
||||
gp2 = F.interpolate(p2, size=(g_h, g_w), mode='bilinear', align_corners=False)
|
||||
|
||||
# Global flow estimation
|
||||
global_flow = model(gp1, gp2, num_flow_updates=iterations)[-1]
|
||||
|
||||
# Upsample global flow to high-res
|
||||
global_flow = F.interpolate(global_flow, size=(h_new, w_new), mode='bilinear', align_corners=False)
|
||||
global_flow[:, 0] *= float(w_new) / g_w
|
||||
global_flow[:, 1] *= float(h_new) / g_h
|
||||
|
||||
# print(f"DEBUG: Global flow at center: {global_flow[0, :, h_new//2, w_new//2]}")
|
||||
|
||||
# 2. Warp img2 using global flow
|
||||
p2_warped = warp(p2, global_flow)
|
||||
target_p2 = p2_warped
|
||||
else:
|
||||
target_p2 = p2
|
||||
|
||||
# 3. Local Refinement (Tiled or Full)
|
||||
if tiling_size > 0:
|
||||
f_residual = _tiled_flow(model, p1, target_p2, tiling_size, iterations)
|
||||
else:
|
||||
f_residual = model(p1, target_p2, num_flow_updates=iterations)[-1]
|
||||
|
||||
# 4. Combine
|
||||
if global_flow is not None:
|
||||
total_flow = global_flow + f_residual
|
||||
else:
|
||||
total_flow = f_residual
|
||||
|
||||
flows.append(total_flow)
|
||||
|
||||
if i % 4 == 0 and b > 4:
|
||||
torch.cuda.empty_cache()
|
||||
|
||||
flow = torch.cat(flows, dim=0)
|
||||
|
||||
# Resize flow back if needed
|
||||
if h != h_new or w != w_new:
|
||||
flow = F.interpolate(flow, size=(h, w), mode='bilinear', align_corners=False)
|
||||
# Rescale flow values
|
||||
flow[:, 0] *= float(w) / w_new
|
||||
flow[:, 1] *= float(h) / h_new
|
||||
|
||||
# [B, 2, H, W] -> [B, H, W, 2]
|
||||
return flow.movedim(1, -1)
|
||||
|
||||
def calculate_occlusion_mask(flow):
|
||||
"""
|
||||
Calculates a disocclusion mask: pixels revealed by motion that have no
|
||||
valid source data (e.g. background uncovered when an object moves away).
|
||||
|
||||
flow: [B, H, W, 2] or [H, W, 2]
|
||||
Returns: [B, H, W] mask (1.0 = disoccluded/revealed, 0.0 = valid)
|
||||
"""
|
||||
was_single = (flow.ndim == 3)
|
||||
if was_single:
|
||||
flow = flow.unsqueeze(0)
|
||||
|
||||
# sanitize flow
|
||||
flow = torch.nan_to_num(flow, nan=0.0, posinf=0.0, neginf=0.0)
|
||||
|
||||
b, h, w, _ = flow.shape
|
||||
device = flow.device
|
||||
|
||||
# [B, H, W, 2] -> [B, 2, H, W] for warp()
|
||||
flow_nchw = flow.movedim(-1, 1)
|
||||
|
||||
# Warp a solid-ones image with the flow.
|
||||
# Use zero padding so samples from outside are 0, making disocclusions visible
|
||||
ones = torch.ones((b, 1, h, w), device=device)
|
||||
warped_ones = warp(ones, flow_nchw, padding_mode='zeros') # [B, 1, H, W]
|
||||
|
||||
# Pixels that are < threshold are disoccluded
|
||||
mask = 1.0 - warped_ones.squeeze(1).clamp(0.0, 1.0) # [B, H, W]
|
||||
|
||||
return mask.squeeze(0) if was_single else mask
|
||||
|
||||
def apply_flow(image, flow):
|
||||
"""
|
||||
Warps an image using optical flow vectors.
|
||||
image: [B, H, W, C] (ComfyUI format)
|
||||
flow: [B, H, W, 2] (from rife())
|
||||
Returns: [B, H, W, C] warped image
|
||||
"""
|
||||
if image.ndim == 3:
|
||||
image = image.unsqueeze(0)
|
||||
if flow.ndim == 3:
|
||||
flow = flow.unsqueeze(0)
|
||||
|
||||
# [B, H, W, 2] -> [B, 2, H, W]
|
||||
flow_nchw = flow.movedim(-1, 1)
|
||||
|
||||
# Ensure image is on the same device as flow
|
||||
img_nchw = image.movedim(-1, 1).to(flow.device)
|
||||
|
||||
warped = warp(img_nchw, flow_nchw)
|
||||
|
||||
# [B, C, H, W] -> [B, H, W, C]
|
||||
return warped.movedim(1, -1)
|
||||
|
||||
def flow_to_image(flow):
|
||||
"""
|
||||
Converts flow vectors [B, H, W, 2] to RGB images [B, H, W, 3] for visualization.
|
||||
"""
|
||||
if flow.ndim == 3:
|
||||
flow = flow.unsqueeze(0)
|
||||
|
||||
# [B, H, W, 2] -> [B, 2, H, W]
|
||||
flow_torch = flow.movedim(-1, 1)
|
||||
|
||||
# torchvision_flow_to_image expects (N, 2, H, W)
|
||||
img_uint8 = torchvision_flow_to_image(flow_torch)
|
||||
|
||||
# [B, 3, H, W] -> [B, H, W, 3]
|
||||
img = img_uint8.movedim(1, -1).float() / 255.0
|
||||
|
||||
return img
|
||||
|
||||
def _tiled_flow(model, img1, img2, tiling_size, iterations):
|
||||
"""
|
||||
Robust tiling implementation with weighted blending and edge coverage.
|
||||
"""
|
||||
b, c, h, w = img1.shape
|
||||
device = img1.device
|
||||
|
||||
# Enforce minimum tile size for RAFT
|
||||
if tiling_size < 256:
|
||||
# If user provided a very small value, maybe they meant fraction?
|
||||
# But for ComfyUI, we'll just enforce a safe minimum.
|
||||
tiling_size = max(256, tiling_size)
|
||||
|
||||
# Ensure tiling_size is divisible by 8
|
||||
tiling_size = (tiling_size // 8) * 8
|
||||
|
||||
# Clip tiling_size to image dimensions
|
||||
t_h = int(min(tiling_size, h))
|
||||
t_w = int(min(tiling_size, w))
|
||||
|
||||
# If image is smaller than tiling_size after divisibility adjustments
|
||||
if t_h < 8 or t_w < 8:
|
||||
return model(img1, img2, num_flow_updates=iterations)[-1]
|
||||
|
||||
output_flow = torch.zeros((b, 2, h, w), device=device)
|
||||
weights = torch.zeros((b, 1, h, w), device=device)
|
||||
|
||||
# Stride of 50% overlap is usually good
|
||||
stride_h = int(t_h // 2)
|
||||
stride_w = int(t_w // 2)
|
||||
|
||||
# Generate coordinates for tiles
|
||||
y_coords = list(range(0, h - t_h, stride_h)) + [max(0, h - t_h)]
|
||||
x_coords = list(range(0, w - t_w, stride_w)) + [max(0, w - t_w)]
|
||||
|
||||
# Remove duplicates if any (when image size matches stride)
|
||||
y_coords = sorted(list(set(y_coords)))
|
||||
x_coords = sorted(list(set(x_coords)))
|
||||
|
||||
for y in y_coords:
|
||||
for x in x_coords:
|
||||
patch1 = img1[:, :, y:y+t_h, x:x+t_w]
|
||||
patch2 = img2[:, :, y:y+t_h, x:x+t_w]
|
||||
|
||||
# RAFT expects inputs divisible by 8, but we already ensured t_h, t_w are.
|
||||
patch_flow = model(patch1, patch2, num_flow_updates=iterations)[-1]
|
||||
|
||||
# Create local mask for this tile
|
||||
# Linear tapering only on overlapping edges
|
||||
layer_mask = torch.ones((1, 1, t_h, t_w), device=device)
|
||||
overlap_h = stride_h
|
||||
overlap_w = stride_w
|
||||
|
||||
if y > 0: # Fade in top
|
||||
layer_mask[:, :, :overlap_h, :] *= torch.linspace(0, 1, overlap_h, device=device).view(1, 1, overlap_h, 1)
|
||||
if y + t_h < h: # Fade out bottom
|
||||
layer_mask[:, :, -overlap_h:, :] *= torch.linspace(1, 0, overlap_h, device=device).view(1, 1, overlap_h, 1)
|
||||
if x > 0: # Fade in left
|
||||
layer_mask[:, :, :, :overlap_w] *= torch.linspace(0, 1, overlap_w, device=device).view(1, 1, 1, overlap_w)
|
||||
if x + t_w < w: # Fade out right
|
||||
layer_mask[:, :, :, -overlap_w:] *= torch.linspace(1, 0, overlap_w, device=device).view(1, 1, 1, overlap_w)
|
||||
|
||||
output_flow[:, :, y:y+t_h, x:x+t_w] += patch_flow * layer_mask
|
||||
weights[:, :, y:y+t_h, x:x+t_w] += layer_mask
|
||||
|
||||
return output_flow / torch.clamp(weights, min=1e-6)
|
||||
@@ -0,0 +1,25 @@
|
||||
from comfy_api.latest import io
|
||||
from .helper_functions import parse_expr
|
||||
from .ParseTree import MrmthParseTree
|
||||
|
||||
class ScriptTextInput(io.ComfyNode):
|
||||
|
||||
@classmethod
|
||||
def define_schema(cls) -> io.Schema:
|
||||
return io.Schema(
|
||||
node_id="mrmth_ScriptInput",
|
||||
category="More math",
|
||||
display_name="Script input",
|
||||
inputs=[
|
||||
io.String.Input(id="script", multiline=True),
|
||||
],
|
||||
outputs=[
|
||||
MrmthParseTree.Output()
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@classmethod
|
||||
def execute(cls, script):
|
||||
expr = parse_expr(script)
|
||||
return (expr,)
|
||||
@@ -0,0 +1,685 @@
|
||||
import copy
|
||||
import torch
|
||||
import comfy.model_patcher
|
||||
from comfy_api.latest import io
|
||||
import comfy.hooks
|
||||
import logging
|
||||
import comfy.patcher_extension
|
||||
|
||||
from .Parser.UnifiedMathVisitor import UnifiedMathVisitor
|
||||
from .helper_functions import checkLazyNew, generate_dim_variables, parse_expr, as_tensor
|
||||
from .Stack import MrmthStack
|
||||
from .ParseTree import MrmthParseTree
|
||||
|
||||
|
||||
class SelectiveGuiderMathNode(io.ComfyNode):
|
||||
@classmethod
|
||||
def define_schema(cls) -> io.Schema:
|
||||
return io.Schema(
|
||||
node_id="mrmth_ag_SelectiveGuiderMathNode",
|
||||
category="More math/math",
|
||||
display_name="Selective guider math",
|
||||
inputs=[
|
||||
io.Guider.Input("V"),
|
||||
io.Autogrow.Input(
|
||||
id="F",
|
||||
template=io.Autogrow.TemplatePrefix(
|
||||
io.Float.Input("float", default=0.0, optional=True, lazy=True, force_input=True),
|
||||
prefix="F",
|
||||
min=1,
|
||||
max=50,
|
||||
),
|
||||
),
|
||||
io.MultiType.Input(
|
||||
io.String.Input("Expression", default="print([layer, layer_key, hook_kind, cond_side, activations_shape]); inp", multiline=False),
|
||||
types=[io.String, MrmthParseTree],
|
||||
),
|
||||
io.Int.Input("layer_x", default=0, min=-999, max=999),
|
||||
io.Combo.Input(
|
||||
"hook_target",
|
||||
options=["all", "dit_block", "unet_block", "attn1", "attn2", "double_block_attn", "single_block_attn", "model_begin", "model_end"],
|
||||
default="all"
|
||||
),
|
||||
MrmthStack.Input(id="stack", tooltip="Access stack between nodes", optional=True),
|
||||
io.Boolean.Input(id="remember_stack", default=False, display_name="Remember stack across batch"),
|
||||
io.Boolean.Input(id="debug_hooks", default=False, display_name="Debug hook logs"),
|
||||
],
|
||||
outputs=[io.Guider.Output(), MrmthStack.Output()],
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def check_lazy_status(
|
||||
cls,
|
||||
V,
|
||||
F,
|
||||
Expression,
|
||||
layer_x=0,
|
||||
hook_target="all",
|
||||
remember_stack=False,
|
||||
debug_hooks=False,
|
||||
stack={},
|
||||
):
|
||||
return checkLazyNew(Expression, {}, F)
|
||||
|
||||
@classmethod
|
||||
def execute(
|
||||
cls,
|
||||
V,
|
||||
F,
|
||||
Expression,
|
||||
layer_x=0,
|
||||
hook_target="all",
|
||||
remember_stack=False,
|
||||
debug_hooks=False,
|
||||
stack=None,
|
||||
):
|
||||
if V is None or not hasattr(V, "model_options"):
|
||||
raise ValueError("Vstupní guider musí mít model_options.")
|
||||
|
||||
stack = stack if remember_stack else (copy.deepcopy(stack) if stack is not None else {})
|
||||
tree = parse_expr(Expression) if isinstance(Expression, str) else Expression
|
||||
|
||||
hit_flags = {
|
||||
"attn1": False,
|
||||
"attn2": False,
|
||||
"double_block_attn": False,
|
||||
"single_block_attn": False,
|
||||
"dit": False,
|
||||
"attn_unknown": False,
|
||||
"unet": False,
|
||||
}
|
||||
|
||||
debug_hook_events = []
|
||||
|
||||
def dbg(msg: str):
|
||||
if debug_hooks:
|
||||
logging.warning(f"[SelectiveGuiderMathNode] {msg}")
|
||||
|
||||
def record_hook(kind: str, side: str, layer_id=None, stage=None, key=None):
|
||||
if not debug_hooks:
|
||||
return
|
||||
parts = [f"hook={kind}", f"side={side}"]
|
||||
if stage is not None:
|
||||
parts.append(f"stage={stage}")
|
||||
if layer_id is not None:
|
||||
parts.append(f"layer_id={layer_id}")
|
||||
if key is not None:
|
||||
parts.append(f"key={key}")
|
||||
line = " ".join(parts)
|
||||
debug_hook_events.append(line)
|
||||
dbg(f"HOOK {line}")
|
||||
|
||||
def resolve_attn_kind(transformer_options: dict, q: torch.Tensor | None = None, k: torch.Tensor | None = None, v: torch.Tensor | None = None) -> str:
|
||||
# Flux / MM-DiT
|
||||
btype = transformer_options.get("block_type", None)
|
||||
if btype == "double":
|
||||
return "double_block_attn"
|
||||
if btype == "single":
|
||||
return "single_block_attn"
|
||||
|
||||
raw = transformer_options.get(
|
||||
"attn_name",
|
||||
transformer_options.get(
|
||||
"attn_type",
|
||||
transformer_options.get(
|
||||
"block_attn",
|
||||
transformer_options.get("transformer_index", None),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
blk = transformer_options.get("block", None)
|
||||
if isinstance(blk, (tuple, list)) and len(blk) > 0:
|
||||
blk_name = str(blk[0]).lower()
|
||||
if "self" in blk_name or "attn1" in blk_name:
|
||||
return "attn1"
|
||||
if "cross" in blk_name or "attn2" in blk_name:
|
||||
return "attn2"
|
||||
|
||||
if isinstance(raw, str):
|
||||
r = raw.lower()
|
||||
if r in ("attn1", "self", "self_attn", "self_attention", "self_attn_1"):
|
||||
return "attn1"
|
||||
if r in ("attn2", "cross", "cross_attn", "cross_attention", "self_attn_2"):
|
||||
return "attn2"
|
||||
return "attn_unknown"
|
||||
|
||||
if isinstance(raw, (int, float)):
|
||||
if int(raw) == 0:
|
||||
return "attn1"
|
||||
if int(raw) == 1:
|
||||
return "attn2"
|
||||
|
||||
# Bezpečný fallback podle délek tokenů (funguje i pro FLOW self-attn)
|
||||
if isinstance(q, torch.Tensor) and isinstance(k, torch.Tensor):
|
||||
if q.ndim >= 2 and k.ndim >= 2:
|
||||
q_tokens = int(q.shape[-2])
|
||||
k_tokens = int(k.shape[-2])
|
||||
if q_tokens == k_tokens:
|
||||
return "attn1"
|
||||
return "attn2"
|
||||
|
||||
return "attn_unknown"
|
||||
|
||||
def resolve_x(total_blocks: int | None) -> int:
|
||||
x = int(layer_x)
|
||||
if x < 0 and total_blocks is not None:
|
||||
x = total_blocks + x
|
||||
if total_blocks is not None:
|
||||
x = max(0, min(x, total_blocks - 1))
|
||||
return x
|
||||
|
||||
def match_index(idx: int, total_blocks: int | None, stage: str, topts: dict, hook_kind: str) -> bool:
|
||||
x = resolve_x(total_blocks)
|
||||
return idx == x
|
||||
|
||||
def target_indices():
|
||||
return [int(layer_x)]
|
||||
|
||||
def side_from_cond_or_uncond(cond_or_uncond):
|
||||
if isinstance(cond_or_uncond, (list, tuple)) and len(cond_or_uncond) > 0:
|
||||
vals = list(cond_or_uncond)
|
||||
uniq = set(vals)
|
||||
if len(uniq) == 1:
|
||||
idx = int(vals[0])
|
||||
if idx == 0:
|
||||
return ("positive", 0)
|
||||
if idx == 1:
|
||||
return ("negative", 1)
|
||||
return ("mixed", -1)
|
||||
return ("unknown", -1)
|
||||
|
||||
def run_expr(inp: torch.Tensor, extra_vars: dict | None = None) -> torch.Tensor:
|
||||
# v run_expr(...) rozšiř výchozí proměnné:
|
||||
variables = {
|
||||
"inp": inp,
|
||||
"sample": inp,
|
||||
"hook_kind": "unknown",
|
||||
"hook_domain": "unknown",
|
||||
"attn_kind": "none",
|
||||
"attn_mode": "unknown",
|
||||
"transformer_index": -1.0,
|
||||
"is_dit": 0.0,
|
||||
"is_unet_block": 0.0,
|
||||
"is_time_emb": 0.0,
|
||||
"is_attn1": 0.0,
|
||||
"is_attn2": 0.0,
|
||||
"is_self_attention": 0.0,
|
||||
"is_cross_attention": 0.0,
|
||||
"has_context": 0.0,
|
||||
"block_name": "",
|
||||
"layer_key": "",
|
||||
"layer_id": -1.0,
|
||||
"layer": -1.0,
|
||||
"i": -1.0,
|
||||
"total_blocks": -1.0,
|
||||
"has_qkv": 0.0,
|
||||
"q": inp,
|
||||
"k": inp,
|
||||
"v": inp,
|
||||
"heads": 0.0,
|
||||
"dim_head": 0.0,
|
||||
"query_tokens": -1.0,
|
||||
"context_tokens": -1.0,
|
||||
"value_tokens": -1.0,
|
||||
"activations_shape": [],
|
||||
"activation_rank": -1.0,
|
||||
"activation_b": -1.0,
|
||||
"activation_c": -1.0,
|
||||
"activation_t": -1.0,
|
||||
"activation_h": -1.0,
|
||||
"activation_w": -1.0,
|
||||
"cond_side": "unknown",
|
||||
"cond_index": -1.0,
|
||||
"is_positive": 0.0,
|
||||
"is_negative": 0.0,
|
||||
"is_attn1_hook": 0.0,
|
||||
"is_attn2_hook": 0.0,
|
||||
"is_double_block_attn_hook": 0.0,
|
||||
"is_single_block_attn_hook": 0.0,
|
||||
"attention_relation": "unknown",
|
||||
} | generate_dim_variables(inp)
|
||||
|
||||
if extra_vars:
|
||||
variables.update(extra_vars)
|
||||
|
||||
for kf, vf in F.items():
|
||||
variables[kf] = vf if vf is not None else 0.0
|
||||
|
||||
act_shape = variables.get("activations_shape", [])
|
||||
if isinstance(act_shape, (list, tuple)):
|
||||
variables["activation_rank"] = float(len(act_shape))
|
||||
if len(act_shape) >= 1:
|
||||
variables["activation_b"] = float(act_shape[0])
|
||||
if len(act_shape) >= 2:
|
||||
variables["activation_c"] = float(act_shape[1])
|
||||
if len(act_shape) == 4:
|
||||
variables["activation_h"] = float(act_shape[2])
|
||||
variables["activation_w"] = float(act_shape[3])
|
||||
elif len(act_shape) >= 5:
|
||||
variables["activation_t"] = float(act_shape[2])
|
||||
variables["activation_h"] = float(act_shape[3])
|
||||
variables["activation_w"] = float(act_shape[4])
|
||||
|
||||
qv = variables.get("q", inp)
|
||||
kv = variables.get("k", inp)
|
||||
vv = variables.get("v", inp)
|
||||
|
||||
if variables.get("attn_kind") == "attn1":
|
||||
variables["is_attn1_hook"] = 1.0
|
||||
elif variables.get("attn_kind") == "attn2":
|
||||
variables["is_attn2_hook"] = 1.0
|
||||
elif variables.get("attn_kind") == "double_block_attn":
|
||||
variables["is_double_block_attn_hook"] = 1.0
|
||||
elif variables.get("attn_kind") == "single_block_attn":
|
||||
variables["is_single_block_attn_hook"] = 1.0
|
||||
|
||||
if hasattr(qv, "shape") and len(qv.shape) >= 2:
|
||||
variables["query_tokens"] = float(qv.shape[-2])
|
||||
if hasattr(kv, "shape") and len(kv.shape) >= 2:
|
||||
variables["context_tokens"] = float(kv.shape[-2])
|
||||
variables["has_context"] = 1.0
|
||||
if hasattr(vv, "shape") and len(vv.shape) >= 2:
|
||||
variables["value_tokens"] = float(vv.shape[-2])
|
||||
|
||||
qt = variables["query_tokens"]
|
||||
kt = variables["context_tokens"]
|
||||
|
||||
if variables["has_context"] == 1.0 and qt > 0 and kt > 0:
|
||||
if qt != kt:
|
||||
variables["is_cross_attention"] = 1.0
|
||||
variables["is_self_attention"] = 0.0
|
||||
variables["attention_relation"] = "cross"
|
||||
else:
|
||||
variables["is_cross_attention"] = 0.0
|
||||
variables["is_self_attention"] = 1.0
|
||||
variables["attention_relation"] = "self"
|
||||
else:
|
||||
variables["is_cross_attention"] = 0.0
|
||||
variables["is_self_attention"] = 0.0
|
||||
variables["attention_relation"] = "unknown"
|
||||
|
||||
visitor = UnifiedMathVisitor(variables, inp.shape, inp.device, state_storage=stack)
|
||||
out = visitor.visit(tree)
|
||||
# preserve dtype/device (float8-safe vs float16 promotion side effects)
|
||||
return as_tensor(out, inp.shape).to(device=inp.device, dtype=inp.dtype)
|
||||
|
||||
def register_dit(patched_dict, forced_side=None):
|
||||
topts = patched_dict["transformer_options"]
|
||||
|
||||
def dit_wrap(args, extra_args):
|
||||
idx = int(args.get("transformer_options", {}).get("block_index", -1))
|
||||
total_blocks = args.get("transformer_options", {}).get("total_blocks", None)
|
||||
|
||||
if not match_index(idx, total_blocks, "dit", args.get("transformer_options", {}), "dit_block"):
|
||||
return extra_args["original_block"](args)
|
||||
if not hit_flags["dit"]:
|
||||
dbg(f"DIT HIT idx={idx} total={total_blocks}")
|
||||
hit_flags["dit"] = True
|
||||
btype = args.get("transformer_options", {}).get("block_type", "dit")
|
||||
co = args.get("transformer_options", {}).get("cond_or_uncond", None)
|
||||
auto_side, auto_idx = side_from_cond_or_uncond(co)
|
||||
side = forced_side if forced_side is not None else auto_side
|
||||
idx_side = 0 if side == "positive" else (1 if side == "negative" else auto_idx)
|
||||
|
||||
meta = {
|
||||
"hook_kind": "dit_block",
|
||||
"hook_domain": "diffusion",
|
||||
"attn_kind": "none",
|
||||
"is_dit": 1.0,
|
||||
"is_attn1": 0.0,
|
||||
"is_attn2": 0.0,
|
||||
"layer_id": float(idx),
|
||||
"layer": float(idx),
|
||||
"i": float(idx),
|
||||
"total_blocks": float(total_blocks) if total_blocks is not None else -1.0,
|
||||
"block_name": btype,
|
||||
"layer_key": f"dit.{btype}.{idx}",
|
||||
"has_qkv": 0.0,
|
||||
"cond_side": side,
|
||||
"cond_index": float(idx_side),
|
||||
"is_positive": 1.0 if side == "positive" else 0.0,
|
||||
"is_negative": 1.0 if side == "negative" else 0.0,
|
||||
"activations_shape": tensor_shape(out["img"]) if isinstance(out, dict) and "img" in out else [],
|
||||
}
|
||||
|
||||
out = extra_args["original_block"](args)
|
||||
if isinstance(out, dict) and "img" in out:
|
||||
out2 = dict(out)
|
||||
out2["img"] = run_expr(out["img"], meta)
|
||||
return out2
|
||||
return out
|
||||
|
||||
patches_replace = topts.get("patches_replace", {}).copy()
|
||||
dit_map = patches_replace.get("dit", {}).copy()
|
||||
for li in target_indices():
|
||||
dit_map[("double_block", li)] = dit_wrap
|
||||
dit_map[("single_block", li)] = dit_wrap
|
||||
patches_replace["dit"] = dit_map
|
||||
topts["patches_replace"] = patches_replace
|
||||
patched_dict["transformer_options"] = topts
|
||||
|
||||
def register_attn(patched_dict, forced_side=None):
|
||||
topts = patched_dict["transformer_options"].copy()
|
||||
|
||||
def attn_override(original_attn, q, k, v, heads, **kwargs):
|
||||
transformer_options = kwargs.get("transformer_options", {})
|
||||
attn_kind = resolve_attn_kind(transformer_options, q=q, k=k, v=v)
|
||||
|
||||
blk = transformer_options.get("block", None)
|
||||
btype = str(transformer_options.get("block_type", "unknown"))
|
||||
bindex = int(transformer_options.get("block_index", -1))
|
||||
|
||||
if isinstance(blk, (tuple, list)) and len(blk) >= 2:
|
||||
stage = str(blk[0])
|
||||
layer_id = int(blk[1])
|
||||
else:
|
||||
# lepší fallback pro Flux/FLOW
|
||||
stage = btype if btype in ("double", "single") else str(transformer_options.get("model_type", "unknown")).lower()
|
||||
layer_id = bindex
|
||||
|
||||
t_index_raw = transformer_options.get("transformer_index", None)
|
||||
t_index = int(t_index_raw) if isinstance(t_index_raw, (int, float)) else layer_id
|
||||
|
||||
if hook_target != "all" and hook_target in ("attn1", "attn2", "double_block_attn", "single_block_attn"):
|
||||
if hook_target != attn_kind:
|
||||
return original_attn(q, k, v, heads, **kwargs)
|
||||
|
||||
idx = layer_id
|
||||
total_blocks = None
|
||||
|
||||
if not match_index(idx, total_blocks, stage, transformer_options, attn_kind):
|
||||
return original_attn(q, k, v, heads, **kwargs)
|
||||
|
||||
co = transformer_options.get("cond_or_uncond", None)
|
||||
auto_side, auto_idx = side_from_cond_or_uncond(co)
|
||||
side = forced_side if forced_side is not None else auto_side
|
||||
|
||||
if not hit_flags.get(attn_kind, False):
|
||||
dbg(f"{attn_kind.upper()} HIT stage={stage} layer_id={layer_id} side={side}")
|
||||
hit_flags[attn_kind] = True
|
||||
|
||||
act_shape = transformer_options.get("activations_shape", [])
|
||||
if not isinstance(act_shape, (list, tuple)):
|
||||
act_shape = []
|
||||
|
||||
meta = {
|
||||
"hook_kind": attn_kind,
|
||||
"hook_domain": "attention",
|
||||
"attn_kind": attn_kind,
|
||||
"transformer_index": float(t_index),
|
||||
"is_dit": 0.0,
|
||||
"is_attn1": 1.0 if attn_kind == "attn1" else 0.0,
|
||||
"is_attn2": 1.0 if attn_kind == "attn2" else 0.0,
|
||||
"block_name": stage,
|
||||
"layer_id": float(layer_id),
|
||||
"layer": float(layer_id),
|
||||
"i": float(layer_id),
|
||||
"total_blocks": -1.0,
|
||||
"has_qkv": 1.0,
|
||||
"q": q,
|
||||
"k": k,
|
||||
"v": v,
|
||||
"heads": float(heads),
|
||||
"dim_head": float(q.shape[-1] // heads) if heads > 0 else 0.0, # <- oprava
|
||||
"layer_key": f"{stage}.{layer_id}.{attn_kind}.{t_index}",
|
||||
"cond_side": side,
|
||||
"cond_index": float(0 if side == "positive" else (1 if side == "negative" else auto_idx)),
|
||||
"is_positive": 1.0 if side == "positive" else 0.0,
|
||||
"is_negative": 1.0 if side == "negative" else 0.0,
|
||||
} | {
|
||||
"activations_shape": list(act_shape),
|
||||
"activation_b": float(act_shape[0]) if len(act_shape) > 0 else -1.0,
|
||||
"activation_c": float(act_shape[1]) if len(act_shape) > 1 else -1.0,
|
||||
"activation_h": float(act_shape[2]) if len(act_shape) > 2 else -1.0,
|
||||
"activation_w": float(act_shape[3]) if len(act_shape) > 3 else -1.0,
|
||||
}
|
||||
|
||||
out = original_attn(q, k, v, heads, **kwargs)
|
||||
return run_expr(out, meta)
|
||||
|
||||
topts["optimized_attention_override"] = attn_override
|
||||
patched_dict["transformer_options"] = topts
|
||||
return patched_dict
|
||||
|
||||
def register_unet_blocks(patched_dict, forced_side=None):
|
||||
topts = patched_dict["transformer_options"].copy()
|
||||
patches = topts.get("patches", {}).copy()
|
||||
|
||||
def _parse_block(transformer_options: dict, fallback_stage: str):
|
||||
blk = transformer_options.get("block", None)
|
||||
if isinstance(blk, (tuple, list)) and len(blk) >= 2:
|
||||
return str(blk[0]), int(blk[1])
|
||||
return fallback_stage, int(transformer_options.get("block_index", -1))
|
||||
|
||||
def _apply_block_expr(t: torch.Tensor, transformer_options: dict, stage: str, idx: int):
|
||||
if not match_index(idx, None, stage, transformer_options, "unet_block"):
|
||||
return t
|
||||
|
||||
co = transformer_options.get("cond_or_uncond", None)
|
||||
auto_side, auto_idx = side_from_cond_or_uncond(co)
|
||||
side = forced_side if forced_side is not None else auto_side
|
||||
idx_side = 0 if side == "positive" else (1 if side == "negative" else auto_idx)
|
||||
|
||||
if not hit_flags["unet"]:
|
||||
dbg(f"UNET BLOCK HIT stage={stage} layer_id={idx} side={side}")
|
||||
hit_flags["unet"] = True
|
||||
|
||||
meta = {
|
||||
"hook_kind": "unet_block",
|
||||
"hook_domain": "diffusion",
|
||||
"attn_kind": "none",
|
||||
"is_dit": 0.0,
|
||||
"is_unet_block": 1.0,
|
||||
"is_time_emb": 1.0 if stage == "time_emb" else 0.0,
|
||||
"is_attn1": 0.0,
|
||||
"is_attn2": 0.0,
|
||||
"block_name": stage,
|
||||
"layer_id": float(idx),
|
||||
"layer": float(idx),
|
||||
"i": float(idx),
|
||||
"total_blocks": -1.0,
|
||||
"has_qkv": 0.0,
|
||||
"layer_key": f"unet.{stage}.{idx}",
|
||||
"cond_side": side,
|
||||
"cond_index": float(idx_side),
|
||||
"is_positive": 1.0 if side == "positive" else 0.0,
|
||||
"is_negative": 1.0 if side == "negative" else 0.0,
|
||||
"activations_shape": tensor_shape(t),
|
||||
}
|
||||
|
||||
return run_expr(t, meta)
|
||||
|
||||
def input_block_patch(h, transformer_options):
|
||||
stage, idx = _parse_block(transformer_options, "input")
|
||||
return _apply_block_expr(h, transformer_options, stage, idx)
|
||||
|
||||
def output_block_patch(h, hsp, transformer_options):
|
||||
stage, idx = _parse_block(transformer_options, "output")
|
||||
h2 = _apply_block_expr(h, transformer_options, stage, idx)
|
||||
return h2, hsp
|
||||
|
||||
def middle_patch(h, transformer_options):
|
||||
stage, idx = _parse_block(transformer_options, "middle")
|
||||
return _apply_block_expr(h, transformer_options, stage, idx)
|
||||
|
||||
def emb_patch(emb, model_channels, transformer_options):
|
||||
# začátek timestep pipeline
|
||||
return _apply_block_expr(emb, transformer_options, "time_emb", 0)
|
||||
|
||||
patches["input_block_patch"] = list(patches.get("input_block_patch", [])) + [input_block_patch]
|
||||
patches["output_block_patch"] = list(patches.get("output_block_patch", [])) + [output_block_patch]
|
||||
patches["middle_patch"] = list(patches.get("middle_patch", [])) + [middle_patch]
|
||||
patches["emb_patch"] = list(patches.get("emb_patch", [])) + [emb_patch]
|
||||
patches["middle_block_after_patch"] = list(patches.get("middle_block_after_patch", [])) + [middle_patch]
|
||||
|
||||
topts["patches"] = patches
|
||||
patched_dict["transformer_options"] = topts
|
||||
return patched_dict
|
||||
|
||||
def register_model_edges(patched_dict, forced_side=None):
|
||||
topts = patched_dict["transformer_options"].copy()
|
||||
wrappers = topts.get("wrappers", {}).copy()
|
||||
wtype = comfy.patcher_extension.WrappersMP.DIFFUSION_MODEL
|
||||
bucket = wrappers.get(wtype, {}).copy()
|
||||
key = "mrmth_model_edges"
|
||||
|
||||
def model_edges_wrapper(executor, x, *args, **kwargs):
|
||||
if hook_target in ("all", "model_begin", "unet_block"):
|
||||
pre_meta = {
|
||||
"hook_kind": "model_begin",
|
||||
"hook_domain": "diffusion",
|
||||
"attn_kind": "none",
|
||||
"block_name": "model",
|
||||
"layer_id": -1.0,
|
||||
"layer": -1.0,
|
||||
"i": -1.0,
|
||||
"total_blocks": -1.0,
|
||||
"has_qkv": 0.0,
|
||||
"layer_key": "model.begin",
|
||||
"activations_shape": tensor_shape(x),
|
||||
}
|
||||
xin = run_expr(x, pre_meta) if isinstance(x, torch.Tensor) else x
|
||||
else:
|
||||
xin = x
|
||||
|
||||
out = executor(xin, *args, **kwargs)
|
||||
|
||||
if isinstance(out, torch.Tensor) and hook_target in ("all", "model_end", "unet_block"):
|
||||
post_meta = {
|
||||
"hook_kind": "model_end",
|
||||
"hook_domain": "diffusion",
|
||||
"attn_kind": "none",
|
||||
"block_name": "model",
|
||||
"layer_id": -1.0,
|
||||
"layer": -1.0,
|
||||
"i": -1.0,
|
||||
"total_blocks": -1.0,
|
||||
"has_qkv": 0.0,
|
||||
"layer_key": "model.end",
|
||||
"activations_shape": tensor_shape(out),
|
||||
}
|
||||
return run_expr(out, post_meta)
|
||||
return out
|
||||
|
||||
bucket[key] = list(bucket.get(key, [])) + [model_edges_wrapper]
|
||||
wrappers[wtype] = bucket
|
||||
topts["wrappers"] = wrappers
|
||||
patched_dict["transformer_options"] = topts
|
||||
return patched_dict
|
||||
|
||||
def build_transformers_for_side(side_label=None):
|
||||
p = comfy.model_patcher.create_model_options_clone(V.model_options)
|
||||
p.setdefault("transformer_options", {})
|
||||
if hook_target in ("all", "dit_block"):
|
||||
register_dit(p, forced_side=side_label)
|
||||
if hook_target in ("all", "unet_block"):
|
||||
p = register_unet_blocks(p, forced_side=side_label)
|
||||
if hook_target in ("all", "attn1", "attn2", "double_block_attn", "single_block_attn"):
|
||||
p = register_attn(p, forced_side=side_label)
|
||||
if hook_target in ("all", "unet_block", "model_begin", "model_end"):
|
||||
p = register_model_edges(p, forced_side=side_label)
|
||||
return p["transformer_options"]
|
||||
|
||||
def attach_side_hook(side_name: str):
|
||||
if not hasattr(V, "original_conds") or side_name not in V.original_conds:
|
||||
return
|
||||
tdict = build_transformers_for_side(side_name)
|
||||
hook = comfy.hooks.TransformerOptionsHook(
|
||||
transformers_dict=tdict,
|
||||
hook_scope=comfy.hooks.EnumHookScope.HookedOnly,
|
||||
)
|
||||
hg = comfy.hooks.HookGroup()
|
||||
hg.add(hook)
|
||||
|
||||
new_conds = []
|
||||
for c in V.original_conds[side_name]:
|
||||
cc = c.copy()
|
||||
old = cc.get("hooks", None)
|
||||
cc["hooks"] = comfy.hooks.HookGroup.combine_all_hooks([old, hg])
|
||||
new_conds.append(cc)
|
||||
V.original_conds[side_name] = new_conds
|
||||
|
||||
# po parse_expr(...)
|
||||
|
||||
if hasattr(V, "original_conds"):
|
||||
def make_shallow_cond_copy(conds_dict):
|
||||
new_dict = {}
|
||||
for k, v_list in conds_dict.items():
|
||||
# Kondice obvykle obsahují fragmentová pole (slovníky nebo listy)
|
||||
new_dict[k] = [item.copy() if hasattr(item, "copy") else copy.copy(item) for item in v_list]
|
||||
return new_dict
|
||||
|
||||
if not hasattr(V, "_mrmth_base_original_conds"):
|
||||
V._mrmth_base_original_conds = make_shallow_cond_copy(V.original_conds)
|
||||
else:
|
||||
V.original_conds = make_shallow_cond_copy(V._mrmth_base_original_conds)
|
||||
|
||||
if hasattr(V, "original_conds"):
|
||||
attach_side_hook("positive")
|
||||
attach_side_hook("negative")
|
||||
dbg(f"has original_conds: {list(V.original_conds.keys())}")
|
||||
for side in ("positive", "negative"):
|
||||
if side in V.original_conds:
|
||||
cnt = sum(1 for c in V.original_conds[side] if c.get("hooks", None) is not None)
|
||||
dbg(f"side={side} conds_with_hooks={cnt}/{len(V.original_conds[side])}")
|
||||
else:
|
||||
patched = comfy.model_patcher.create_model_options_clone(V.model_options)
|
||||
patched.setdefault("transformer_options", {})
|
||||
if hook_target in ("all", "dit_block"):
|
||||
register_dit(patched, forced_side=None)
|
||||
if hook_target in ("all", "unet_block"):
|
||||
patched = register_unet_blocks(patched, forced_side=None)
|
||||
if hook_target in ("all", "attn1", "attn2", "double_block_attn", "single_block_attn"):
|
||||
patched = register_attn(patched, forced_side=None)
|
||||
if hook_target in ("all", "unet_block", "model_begin", "model_end"):
|
||||
patched = register_model_edges(patched, forced_side=None)
|
||||
V.model_options = patched
|
||||
|
||||
if hasattr(V, "model_patcher") and V.model_patcher is not None and not getattr(V, "_mrmth_debug_callbacks_added", False):
|
||||
def _on_register_all_hook_patches(model_patcher, hooks, target_dict, model_options, registered):
|
||||
try:
|
||||
reg_len = len(registered) if registered is not None else 0
|
||||
in_len = len(hooks) if hooks is not None else 0
|
||||
tr_count = len(registered.get_type(comfy.hooks.EnumHookType.TransformerOptions)) if registered is not None else 0
|
||||
dbg(f"REGISTER hooks_in={in_len} registered={reg_len} transformer_registered={tr_count}")
|
||||
if model_options is not None:
|
||||
rh = model_options.get("registered_hooks", None)
|
||||
dbg(f"model_options.registered_hooks={'set' if rh is not None else 'None'}")
|
||||
except Exception as e:
|
||||
logging.warning(f"[SelectiveGuiderMathNode] REGISTER debug error: {e}")
|
||||
|
||||
def _on_apply_hooks(model_patcher, hooks):
|
||||
try:
|
||||
hl = len(hooks) if hooks is not None else 0
|
||||
dbg(f"APPLY hooks_len={hl}")
|
||||
except Exception as e:
|
||||
logging.warning(f"[SelectiveGuiderMathNode] APPLY debug error: {e}")
|
||||
|
||||
V.model_patcher.add_callback_with_key(
|
||||
comfy.patcher_extension.CallbacksMP.ON_REGISTER_ALL_HOOK_PATCHES,
|
||||
"mrmth_selective_debug",
|
||||
_on_register_all_hook_patches,
|
||||
)
|
||||
V.model_patcher.add_callback_with_key(
|
||||
comfy.patcher_extension.CallbacksMP.ON_APPLY_HOOKS,
|
||||
"mrmth_selective_debug",
|
||||
_on_apply_hooks,
|
||||
)
|
||||
V._mrmth_debug_callbacks_added = True
|
||||
|
||||
stack = stack if remember_stack else copy.deepcopy(stack)
|
||||
result = (V, stack)
|
||||
|
||||
# 6) Before return add compact summary:
|
||||
|
||||
if debug_hooks:
|
||||
uniq = sorted(set(debug_hook_events))
|
||||
dbg(f"HOOK SUMMARY total={len(debug_hook_events)} unique={len(uniq)}")
|
||||
for item in uniq:
|
||||
dbg(f"HOOK SUMMARY ITEM {item}")
|
||||
|
||||
return result
|
||||
|
||||
def tensor_shape(x):
|
||||
return list(x.shape) if isinstance(x, torch.Tensor) else []
|
||||
@@ -0,0 +1,113 @@
|
||||
from .helper_functions import generate_dim_variables, parse_expr, getIndexTensorAlongDim, as_tensor, prepare_inputs, make_zero_like, normalize_to_common_shape, checkLazyNew
|
||||
from .Parser.UnifiedMathVisitor import UnifiedMathVisitor
|
||||
from comfy_api.latest import io
|
||||
from .Stack import MrmthStack
|
||||
from .ParseTree import MrmthParseTree
|
||||
import copy
|
||||
|
||||
class SigmasMathNode(io.ComfyNode):
|
||||
"""
|
||||
Enables math expressions on Images with autogrow support.
|
||||
|
||||
Inputs:
|
||||
I: Autogrow image inputs (I0, I1, ...)
|
||||
F: Autogrow float inputs (F0, F1, ...)
|
||||
Image: Expression
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def define_schema(cls) -> io.Schema:
|
||||
return io.Schema(
|
||||
node_id="mrmth_ag_SigmasMathNode",
|
||||
category="More math/math",
|
||||
display_name="Sigmas math",
|
||||
inputs=[
|
||||
io.Autogrow.Input(id="V",template=io.Autogrow.TemplatePrefix(io.Sigmas.Input("values"), prefix="V", min=1, max=50)),
|
||||
io.Autogrow.Input(id="F", template=io.Autogrow.TemplatePrefix(io.Float.Input("float", default=0.0, optional=True, lazy=True, force_input=True), prefix="F", min=1, max=50)),
|
||||
io.MultiType.Input(
|
||||
io.String.Input("Expression", default="V0", multiline=False),
|
||||
types=[io.String,MrmthParseTree],
|
||||
tooltip="Expression to apply on input images",
|
||||
),
|
||||
io.Combo.Input(
|
||||
id="length_mismatch",
|
||||
options=["do nothing","error","tile", "pad"],
|
||||
display_name="on size mismatch",
|
||||
default="error",
|
||||
tooltip="How to handle mismatched image batch sizes. tile: repeat shorter inputs; error: raise error on mismatch; pad: treat missing frames as zero."
|
||||
),
|
||||
MrmthStack.Input(id="stack", tooltip="Access stack between nodes",optional=True)
|
||||
],
|
||||
outputs=[
|
||||
io.Sigmas.Output(),
|
||||
MrmthStack.Output(),
|
||||
],
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def check_lazy_status(cls, Expression, V, F, length_mismatch="tile",stack={}):
|
||||
return checkLazyNew(Expression,V,F)
|
||||
|
||||
@classmethod
|
||||
def execute(cls, V, F, Expression, length_mismatch="tile",stack={}):
|
||||
# I and F are Autogrow.Type which is dict[str, Any]
|
||||
|
||||
# Determine reference image for zero-initialization (fallback for a,b,c,d)
|
||||
ref_image = None
|
||||
for img in V.values():
|
||||
if img is not None:
|
||||
ref_image = img
|
||||
break
|
||||
|
||||
if ref_image is None:
|
||||
raise ValueError("At least one input is required.")
|
||||
stack = copy.deepcopy(stack) if stack is not None else {}
|
||||
a = V.get("V0")
|
||||
b = V.get("V1")
|
||||
c = V.get("V2")
|
||||
d = V.get("V3")
|
||||
|
||||
if a is None:
|
||||
a = make_zero_like(ref_image)
|
||||
|
||||
ae, be, ce, de = prepare_inputs(a, b, c, d)
|
||||
|
||||
ae, be, ce, de = normalize_to_common_shape(ae, be, ce, de, mode=length_mismatch)
|
||||
|
||||
if(length_mismatch == "error"):
|
||||
max_length = ae.shape[0]
|
||||
for name, tensor in V.items():
|
||||
if tensor is not None and tensor.shape[0] != max_length:
|
||||
raise ValueError(f"Input '{name}' has shape {tensor.shape[0]}, expected {max_length} to match largest input.")
|
||||
|
||||
variables = {
|
||||
"a": ae, "b": be, "c": ce, "d": de,
|
||||
"w": F.get("F0", 0.0) if F.get("F0") is not None else 0.0,
|
||||
"x": F.get("F1", 0.0) if F.get("F1") is not None else 0.0,
|
||||
"y": F.get("F2", 0.0) if F.get("F2") is not None else 0.0,
|
||||
"z": F.get("F3", 0.0) if F.get("F3") is not None else 0.0,
|
||||
"B": getIndexTensorAlongDim(ae, 0),
|
||||
"batch": getIndexTensorAlongDim(ae, 0),
|
||||
"T": ae.shape[0],
|
||||
"batch_count": ae.shape[0],
|
||||
} | generate_dim_variables(ae)
|
||||
|
||||
# Add all dynamic inputs
|
||||
for k, v in V.items():
|
||||
if v is not None:
|
||||
# Normalize all images in I to match ae.shape
|
||||
norm_v = normalize_to_common_shape(ae, v, mode=length_mismatch)[1]
|
||||
variables[k] = norm_v
|
||||
|
||||
for k, v in F.items():
|
||||
variables[k] = v if v is not None else 0.0
|
||||
|
||||
tree = None
|
||||
if isinstance(Expression,str):
|
||||
tree = parse_expr(Expression)
|
||||
else:
|
||||
tree = Expression
|
||||
visitor = UnifiedMathVisitor(variables, ae.shape,ae.device,state_storage=stack)
|
||||
result = visitor.visit(tree)
|
||||
result = as_tensor(result, ae.shape)
|
||||
return (result,stack)
|
||||
@@ -0,0 +1,53 @@
|
||||
import torch
|
||||
from comfy_api.latest import io
|
||||
|
||||
windows = {"bartlett": torch.bartlett_window, "blackman": torch.blackman_window, "hamming": torch.hamming_window, "hann": torch.hann_window, "kaiser": torch.kaiser_window}
|
||||
|
||||
|
||||
class SpectrogramToAudio(io.ComfyNode):
|
||||
"""
|
||||
Converts an Image spectrogram back to Audio.
|
||||
Red is real part and blue is imaginary. Green is ignored.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def define_schema(cls) -> io.Schema:
|
||||
return io.Schema(
|
||||
node_id="mrmth_ImageSpectrogramToAudio",
|
||||
category="More math/converters",
|
||||
display_name="Spectrogram -> Audio",
|
||||
inputs=[
|
||||
io.Image.Input(id="image", tooltip="Input spectrogram image (R=Real, G=Magnitude, B=Imaginary)"),
|
||||
io.Int.Input(id="channel_count", default=1, min=1, tooltip="Number of audio channels"),
|
||||
io.Int.Input(id="sample_rate", default=44100, min=1, tooltip="Sample rate of the output audio"),
|
||||
io.Int.Input(id="window_length", default=1024, min=16, tooltip="Window length in samples"),
|
||||
io.Int.Input(id="hop_length", default=256, min=1, tooltip="Stride of the window (hop length) in samples"),
|
||||
io.Combo.Input(id="window_type", default="hann", options=list(windows.keys()), tooltip="Type of window function to apply"),
|
||||
],
|
||||
outputs=[
|
||||
io.Audio.Output(id="audio", tooltip="Output audio"),
|
||||
],
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def execute(cls, image, channel_count, sample_rate, window_length, hop_length, window_type):
|
||||
B, H, W, _ = image.shape
|
||||
bucket_count = H // channel_count
|
||||
n_fft = (bucket_count - 1) * 2
|
||||
real = image[..., 0].reshape(B * channel_count, bucket_count, W)
|
||||
imag = image[..., 2].reshape(B * channel_count, bucket_count, W)
|
||||
stft_complex = torch.complex(real, imag)
|
||||
window = windows[window_type](window_length, device=image.device)
|
||||
waveform = torch.istft(
|
||||
stft_complex,
|
||||
n_fft=n_fft,
|
||||
hop_length=hop_length,
|
||||
win_length=window_length,
|
||||
window=window,
|
||||
center=True,
|
||||
normalized=False,
|
||||
onesided=True,
|
||||
)
|
||||
|
||||
waveform = waveform.reshape(B, channel_count, -1)
|
||||
return ({"waveform": waveform, "sample_rate": sample_rate},)
|
||||
@@ -0,0 +1,13 @@
|
||||
from comfy_api.latest import io
|
||||
|
||||
@io.comfytype(io_type="STACK")
|
||||
class MrmthStack(io.ComfyTypeIO):
|
||||
Type = dict # Python type hint
|
||||
|
||||
class Input(io.Input):
|
||||
def __init__(self, id: str, **kwargs):
|
||||
super().__init__(id, **kwargs)
|
||||
|
||||
class Output(io.Output):
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
@@ -0,0 +1,24 @@
|
||||
from comfy_api.latest import io
|
||||
from .Stack import MrmthStack
|
||||
|
||||
class StackUnBatcher(io.ComfyNode):
|
||||
"""
|
||||
Takes a list of stack created during operation running on batched values and outputs the last stack, making it possible to use the stack in non-batched operations after. When used on non-batched stack, it will do nothing.
|
||||
"""
|
||||
@classmethod
|
||||
def define_schema(cls) -> io.Schema:
|
||||
return io.Schema(
|
||||
node_id="mrmth_StackUnBatcher",
|
||||
category="More math",
|
||||
display_name="Stack UnBatcher",
|
||||
is_input_list=True,
|
||||
inputs=[
|
||||
MrmthStack.Input(id="stack", tooltip="Input stack to unbatch"),
|
||||
],
|
||||
outputs=[
|
||||
MrmthStack.Output(id="unbatched", tooltip="Unbatched stack output"),
|
||||
])
|
||||
|
||||
@classmethod
|
||||
def execute(cls, stack):
|
||||
return (stack[-1],)
|
||||
@@ -0,0 +1,112 @@
|
||||
from .helper_functions import (
|
||||
parse_expr,
|
||||
get_v_variable,
|
||||
get_f_variable,
|
||||
checkLazyNew
|
||||
)
|
||||
from .Parser.UnifiedMathVisitor import UnifiedMathVisitor
|
||||
from comfy_api.latest import io
|
||||
import torch
|
||||
from .Stack import MrmthStack
|
||||
import copy
|
||||
from .ParseTree import MrmthParseTree
|
||||
|
||||
class StringMathNode(io.ComfyNode):
|
||||
"""
|
||||
Enables math expressions on String values.
|
||||
|
||||
Inputs:
|
||||
V: Autogrow string inputs (V0, V1, ...)
|
||||
F: Autogrow float inputs (F0, F1, ...)
|
||||
Expression: Math expression to evaluate
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def define_schema(cls) -> io.Schema:
|
||||
return io.Schema(
|
||||
node_id="mrmth_ag_StringMathNode",
|
||||
category="More math/math",
|
||||
display_name="String math",
|
||||
inputs=[
|
||||
io.Autogrow.Input(id="V",template=io.Autogrow.TemplatePrefix(io.String.Input("values"), prefix="V", min=1, max=50)),
|
||||
io.Autogrow.Input(id="F", template=io.Autogrow.TemplatePrefix(io.Float.Input("float", default=0.0, optional=True, lazy=True, force_input=True), prefix="F", min=1, max=50)),
|
||||
io.MultiType.Input(
|
||||
io.String.Input("Expression", default="V0", multiline=False),
|
||||
types=[io.String,MrmthParseTree],
|
||||
tooltip="Expression to apply on weights",
|
||||
),
|
||||
io.Int.Input(id="batching", default=0,min=-1),
|
||||
io.Boolean.Input(
|
||||
id="remember_stack",
|
||||
default=False,
|
||||
display_name="Remember stack across batch",
|
||||
tooltip=(
|
||||
"If enabled, stack is copied at output leading to changes being remembered during batch operations (node runs multiple times in sucession). If disabled each batch gets it's own copy of the stack."
|
||||
),
|
||||
),
|
||||
MrmthStack.Input(id="stack", tooltip="Access stack between nodes",optional=True)
|
||||
],
|
||||
outputs=[
|
||||
io.String.Output(is_output_list=True),
|
||||
MrmthStack.Output(),
|
||||
],
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def check_lazy_status(cls, Expression, V, F,batching=0, remember_stack=False,stack={}):
|
||||
return checkLazyNew(Expression,V,F)
|
||||
|
||||
|
||||
@classmethod
|
||||
def execute(cls, V, F, Expression, batching=0, remember_stack=False, stack={}):
|
||||
|
||||
variables = {
|
||||
"a": V.get("V0",""), "b": V.get("V1",""), "c": V.get("V2",""), "d": V.get("V3",""),
|
||||
"w": F.get("F0", 0.0) if F.get("F0") is not None else 0.0,
|
||||
"x": F.get("F1", 0.0) if F.get("F1") is not None else 0.0,
|
||||
"y": F.get("F2", 0.0) if F.get("F2") is not None else 0.0,
|
||||
"z": F.get("F3", 0.0) if F.get("F3") is not None else 0.0
|
||||
}
|
||||
|
||||
v_stacked, v_cnt = get_v_variable(V, length_mismatch="do nothing")
|
||||
if v_stacked is not None:
|
||||
variables["V"] = v_stacked
|
||||
variables["Vcnt"] = float(v_cnt)
|
||||
variables["V_count"] = float(v_cnt)
|
||||
|
||||
f_stacked, f_cnt = get_f_variable(F)
|
||||
if f_stacked is not None:
|
||||
variables["F"] = f_stacked
|
||||
variables["Fcnt"] = float(f_cnt)
|
||||
variables["F_count"] = float(f_cnt)
|
||||
|
||||
for k, val in F.items():
|
||||
variables[k] = val if val is not None else 0.0
|
||||
for k, val in V.items():
|
||||
variables[k] = val if val is not None else ""
|
||||
|
||||
tree = None
|
||||
if isinstance(Expression,str):
|
||||
tree = parse_expr(Expression)
|
||||
else:
|
||||
tree = Expression
|
||||
|
||||
visitor = UnifiedMathVisitor(variables, len(V["V0"]),torch.device("cpu"),state_storage=stack)
|
||||
result = visitor.visit(tree)
|
||||
|
||||
if batching == -1:
|
||||
return (result, stack)
|
||||
|
||||
result = str(result)
|
||||
if batching and batching > 0:
|
||||
def chunks(s, n):
|
||||
"""Produce `n`-character chunks from `s`."""
|
||||
for start in range(0, len(s), n):
|
||||
yield s[start:start+n]
|
||||
|
||||
stack = stack if remember_stack else copy.deepcopy(stack)
|
||||
return (chunks(result,batching), stack)
|
||||
else:
|
||||
stack = stack if remember_stack else copy.deepcopy(stack)
|
||||
|
||||
return ([result], stack)
|
||||
@@ -0,0 +1,98 @@
|
||||
from inspect import cleandoc
|
||||
from comfy_api.latest import io
|
||||
import copy
|
||||
from .helper_functions import checkLazyNew
|
||||
from .Stack import MrmthStack
|
||||
from .ParseTree import MrmthParseTree
|
||||
import comfy.utils
|
||||
|
||||
class VAEMathNode(io.ComfyNode):
|
||||
"""
|
||||
This node enables the use of math expressions on VAE weights using Autogrow inputs.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def define_schema(cls) -> io.Schema:
|
||||
return io.Schema(
|
||||
node_id="mrmth_ag_VAEMathNode",
|
||||
display_name="VAE Math",
|
||||
category="More math/math",
|
||||
inputs=[
|
||||
io.Autogrow.Input(id="V",template=io.Autogrow.TemplatePrefix(io.Vae.Input("values"), prefix="V", min=1, max=50)),
|
||||
io.Autogrow.Input(id="F", template=io.Autogrow.TemplatePrefix(io.Float.Input("float", default=0.0, optional=True, lazy=True, force_input=True), prefix="F", min=1, max=50)),
|
||||
io.MultiType.Input(
|
||||
io.String.Input("Expression", default="V0", multiline=False),
|
||||
types=[io.String,MrmthParseTree],
|
||||
tooltip="Expression to apply on weights",
|
||||
),
|
||||
io.Combo.Input(
|
||||
id="length_mismatch",
|
||||
options=["do nothing","error","tile", "pad"],
|
||||
display_name="on size mismatch",
|
||||
default="error",
|
||||
tooltip="How to handle mismatched layer counts. For models, this usually defaults to broadcast (zero for missing layers)."
|
||||
),
|
||||
io.Boolean.Input(
|
||||
id="dict_mode",
|
||||
default=False,
|
||||
display_name="Dictionary mode",
|
||||
tooltip="Evaluates VAE weights as dictionaries. Per-layer and dimension variables are unavailable.",
|
||||
),
|
||||
io.Boolean.Input(
|
||||
id="use_compute_device",
|
||||
default=True,
|
||||
display_name="Move tensors to GPU",
|
||||
tooltip="Temporarily copies VAE tensors to the compute device for math and moves the patches back afterwards.",
|
||||
),
|
||||
MrmthStack.Input(id="stack", tooltip="Access stack between nodes",optional=True)
|
||||
],
|
||||
outputs=[
|
||||
io.Vae.Output(),
|
||||
MrmthStack.Output(),
|
||||
],
|
||||
)
|
||||
|
||||
tooltip = cleandoc(__doc__)
|
||||
|
||||
@classmethod
|
||||
def check_lazy_status(cls, Expression, V, F, length_mismatch="tile",use_compute_device=True,stack={},dict_mode=False):
|
||||
return checkLazyNew(Expression,V,F)
|
||||
|
||||
@classmethod
|
||||
def execute(cls, V, F, Expression, length_mismatch="tile",use_compute_device=True,stack={},dict_mode=False) -> io.NodeOutput:
|
||||
# Determine reference VAE
|
||||
a = V.get("V0")
|
||||
if a is None:
|
||||
for m in V.values():
|
||||
if m is not None:
|
||||
a = m
|
||||
break
|
||||
if a is None:
|
||||
raise ValueError("At least one input VAE is required.")
|
||||
stack = copy.deepcopy(stack) if stack is not None else {}
|
||||
|
||||
# Prepare VAE patchers for calculation
|
||||
# We need to map VAE wrappers to their patchers for `calculate_patches`
|
||||
|
||||
patchers_V = {}
|
||||
for k, v in V.items():
|
||||
if v is not None:
|
||||
patchers_V[k] = v.patcher
|
||||
|
||||
# Calculate patches using the patchers (weights are in patcher.model.state_dict)
|
||||
from .modelLikeCommon import calculate_patches_autogrow, calculate_patches_dict_mode
|
||||
aliases = {"a": "V0", "b": "V1", "c": "V2", "d": "V3", "w": "F0", "x": "F1", "y": "F2", "z": "F3"}
|
||||
ref_patcher = getattr(V.get("V0"), "patcher", None)
|
||||
layer_count = ref_patcher.model.state_dict().__len__() if ref_patcher is not None and hasattr(ref_patcher, "model") and hasattr(ref_patcher.model, "state_dict") else 0
|
||||
pbar = comfy.utils.ProgressBar(layer_count)
|
||||
calculate_patches = calculate_patches_dict_mode if dict_mode else calculate_patches_autogrow
|
||||
patches = calculate_patches(Expression, V=patchers_V, F=F, pbar=pbar, mapping=aliases, stack=stack, use_compute_device=use_compute_device)
|
||||
|
||||
# VAE does not have a clone method, so we shallow copy and clone the patcher
|
||||
out_vae = copy.copy(a)
|
||||
out_vae.patcher = a.patcher.clone()
|
||||
|
||||
if patches:
|
||||
out_vae.patcher.add_patches(patches, 1.0, 1.0)
|
||||
|
||||
return (out_vae,stack)
|
||||
@@ -0,0 +1,283 @@
|
||||
from .helper_functions import generate_dim_variables, parse_expr, getIndexTensorAlongDim, as_tensor, normalize_to_common_shape, make_zero_like, get_v_variable, get_f_variable, checkLazyNew, LazyVariableDict, get_tensor_device, move_to_device
|
||||
from .Parser.UnifiedMathVisitor import UnifiedMathVisitor
|
||||
from comfy_api.latest import io, InputImpl, Types
|
||||
from .Stack import MrmthStack
|
||||
from .ParseTree import MrmthParseTree
|
||||
import copy
|
||||
import comfy
|
||||
|
||||
|
||||
def _get_video_components(video):
|
||||
if hasattr(video, "get_components"):
|
||||
return video.get_components()
|
||||
if isinstance(video, dict):
|
||||
return video
|
||||
raise TypeError(f"Unsupported video input type: {type(video).__name__}")
|
||||
|
||||
|
||||
def _get_video_tensor(video):
|
||||
components = _get_video_components(video)
|
||||
return components["images"] if isinstance(components, dict) else components.images
|
||||
|
||||
|
||||
def _get_video_audio(video):
|
||||
components = _get_video_components(video)
|
||||
audio = components.get("audio") if isinstance(components, dict) else components.audio
|
||||
if audio is None:
|
||||
return None
|
||||
return audio["waveform"] if isinstance(audio, dict) else audio.waveform
|
||||
|
||||
|
||||
def _get_video_sample_rate(video):
|
||||
components = _get_video_components(video)
|
||||
audio = components.get("audio") if isinstance(components, dict) else components.audio
|
||||
if isinstance(audio, dict):
|
||||
return audio.get("sample_rate", 44100)
|
||||
if audio is not None:
|
||||
return getattr(audio, "sample_rate", 44100)
|
||||
return 44100
|
||||
|
||||
|
||||
def _build_video_output(images, waveform, sample_rate, source_video):
|
||||
components = _get_video_components(source_video)
|
||||
metadata = components.get("metadata") if isinstance(components, dict) else components.metadata
|
||||
frame_rate = components.get("frame_rate") if isinstance(components, dict) else components.frame_rate
|
||||
audio = {"waveform": waveform, "sample_rate": sample_rate} if waveform is not None else None
|
||||
return InputImpl.VideoFromComponents(Types.VideoComponents(images=images, audio=audio, frame_rate=frame_rate, metadata=metadata))
|
||||
|
||||
class VideoMathNode(io.ComfyNode):
|
||||
"""
|
||||
Enables math expressions on Audio.
|
||||
|
||||
Inputs:
|
||||
I: Autogrow image inputs (I0, I1, ...)
|
||||
F: Autogrow float inputs (F0, F1, ...)
|
||||
Image: Expression
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def define_schema(cls) -> io.Schema:
|
||||
return io.Schema(
|
||||
node_id="mrmth_ag_VideoMathNode",
|
||||
category="More math/math",
|
||||
display_name="Video math",
|
||||
inputs=[
|
||||
io.Autogrow.Input(id="V",template=io.Autogrow.TemplatePrefix(io.Video.Input("values"), prefix="V", min=1, max=50)),
|
||||
io.Autogrow.Input(id="F", template=io.Autogrow.TemplatePrefix(io.Float.Input("float", default=0.0, optional=True, lazy=True, force_input=True), prefix="F", min=1, max=50)),
|
||||
io.MultiType.Input(
|
||||
io.String.Input("Expression", default="V0", multiline=False),
|
||||
types=[io.String,MrmthParseTree],
|
||||
tooltip="Expression to apply on tensor part of video",
|
||||
),
|
||||
io.MultiType.Input(
|
||||
io.String.Input("Expression_pi", default="V0", multiline=False),
|
||||
types=[io.String,MrmthParseTree],
|
||||
tooltip="Expression to apply on audio part of video",
|
||||
)
|
||||
, io.Combo.Input(
|
||||
id="length_mismatch",
|
||||
options=["do nothing","error","tile", "pad"],
|
||||
display_name="on size mismatch",
|
||||
default="error",
|
||||
tooltip="How to handle mismatched video lengths. tile: repeat shorter inputs; error: raise error on mismatch; pad: treat missing frames as zero."
|
||||
),
|
||||
io.Boolean.Input(
|
||||
id="use_compute_device",
|
||||
default=True,
|
||||
display_name="Move tensors to GPU",
|
||||
tooltip="Temporarily copies video tensors to the compute device for math and moves the result back afterwards.",
|
||||
),
|
||||
MrmthStack.Input(id="stack", tooltip="Access stack between nodes",optional=True)
|
||||
],
|
||||
outputs=[
|
||||
io.Video.Output(),
|
||||
MrmthStack.Output(),
|
||||
],
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def check_lazy_status(cls, Expression,Expression_pi, V, F, length_mismatch="tile",use_compute_device=True,stack={}):
|
||||
d = checkLazyNew(Expression,V,F)
|
||||
b = checkLazyNew(Expression_pi,V,F)
|
||||
return d|b
|
||||
|
||||
@classmethod
|
||||
def execute(cls, V, F, Expression, Expression_pi, length_mismatch="tile",use_compute_device=True,stack={}):
|
||||
tensor_keys = [k for k, v in V.items() if v is not None]
|
||||
if not tensor_keys:
|
||||
raise ValueError("At least one input is required.")
|
||||
|
||||
needed_vars = checkLazyNew(Expression, V, F) | checkLazyNew(Expression_pi, V, F)
|
||||
needed_tensor_keys = [k for k in tensor_keys if k in needed_vars]
|
||||
if not needed_tensor_keys:
|
||||
needed_tensor_keys = tensor_keys
|
||||
|
||||
tensors = [_get_video_tensor(V[k]) for k in needed_tensor_keys]
|
||||
original_device = get_tensor_device(tensors[0])
|
||||
compute_device = comfy.model_management.get_torch_device() if use_compute_device else original_device
|
||||
working_V = move_to_device(V, compute_device) if compute_device is not None and compute_device != original_device else V
|
||||
|
||||
# Normalize all tensors together to find the common target shape
|
||||
tensors = [_get_video_tensor(working_V[k]) for k in needed_tensor_keys]
|
||||
normalized_tensors = normalize_to_common_shape(*tensors, mode=length_mismatch)
|
||||
V_norm = dict(zip(needed_tensor_keys, normalized_tensors))
|
||||
|
||||
# Use first normalized tensor to establish the reference shape
|
||||
ref_tensor = normalized_tensors[0]
|
||||
common_shape = ref_tensor.shape
|
||||
stack = copy.deepcopy(stack) if stack is not None else {}
|
||||
sample_rates = {k + "sr": _get_video_sample_rate(V[k]) for k in needed_tensor_keys}
|
||||
|
||||
# Setup legacy variables a, b, c, d
|
||||
ae = V_norm.get("V0", make_zero_like(ref_tensor))
|
||||
be = V_norm.get("V1", make_zero_like(ae))
|
||||
ce = V_norm.get("V2", make_zero_like(ae))
|
||||
de = V_norm.get("V3", make_zero_like(ae))
|
||||
|
||||
# Ensure legacy variables are normalized in case they were zero-initialized
|
||||
ae, be, ce, de = normalize_to_common_shape(ae, be, ce, de, mode=length_mismatch)
|
||||
|
||||
if(length_mismatch == "error"):
|
||||
for name, video in V.items():
|
||||
if video is not None and _get_video_tensor(video).shape[0] != common_shape[0]:
|
||||
raise ValueError(f"Input '{name}' has shape {_get_video_tensor(video).shape[0]}, expected {common_shape[0]} to match input.")
|
||||
|
||||
variables = LazyVariableDict({
|
||||
"a": ae, "b": be, "c": ce, "d": de,
|
||||
"w": F.get("F0", 0.0) if F.get("F0") is not None else 0.0,
|
||||
"x": F.get("F1", 0.0) if F.get("F1") is not None else 0.0,
|
||||
"y": F.get("F2", 0.0) if F.get("F2") is not None else 0.0,
|
||||
"z": F.get("F3", 0.0) if F.get("F3") is not None else 0.0,
|
||||
"X": getIndexTensorAlongDim(ae, 2),
|
||||
"Y": getIndexTensorAlongDim(ae, 1),
|
||||
"B": getIndexTensorAlongDim(ae, 0),
|
||||
"batch": getIndexTensorAlongDim(ae, 0),
|
||||
"C": getIndexTensorAlongDim(ae, 3),
|
||||
"channel": getIndexTensorAlongDim(ae, 3),
|
||||
"W": float(ae.shape[2]),
|
||||
"width": float(ae.shape[2]),
|
||||
"H": float(ae.shape[1]),
|
||||
"height": float(ae.shape[1]),
|
||||
"T": float(ae.shape[0]),
|
||||
"batch_count": float(ae.shape[0]),
|
||||
"N": float(ae.shape[3]),
|
||||
"channel_count": float(ae.shape[3]),
|
||||
} | generate_dim_variables(ae) | V_norm | sample_rates)
|
||||
|
||||
for v_name, v_tensor in V_norm.items():
|
||||
variables[v_name] = (lambda t=v_tensor: t)
|
||||
variables[v_name].is_lazy_var = True
|
||||
|
||||
def lazy_v_stack():
|
||||
return get_v_variable(V_norm, length_mismatch=length_mismatch)[0]
|
||||
lazy_v_stack.is_lazy_var = True
|
||||
def lazy_v_count():
|
||||
return float(get_v_variable(V_norm, length_mismatch=length_mismatch)[1])
|
||||
lazy_v_count.is_lazy_var = True
|
||||
variables["V"] = lazy_v_stack
|
||||
variables["Vcnt"] = lazy_v_count
|
||||
variables["V_count"] = lazy_v_count
|
||||
|
||||
f_stacked, f_cnt = get_f_variable(F)
|
||||
if f_stacked is not None:
|
||||
variables["F"] = f_stacked
|
||||
variables["Fcnt"] = float(f_cnt)
|
||||
variables["F_count"] = float(f_cnt)
|
||||
|
||||
for k, val in F.items():
|
||||
variables[k] = val if val is not None else 0.0
|
||||
|
||||
tree = None
|
||||
if isinstance(Expression,str):
|
||||
tree = parse_expr(Expression)
|
||||
else:
|
||||
tree = Expression
|
||||
visitor = UnifiedMathVisitor(variables, ae.shape,ae.device,state_storage=stack)
|
||||
result_video = visitor.visit(tree)
|
||||
result_video = as_tensor(result_video, ae.shape)
|
||||
|
||||
|
||||
|
||||
|
||||
waveforms = {}
|
||||
for k in needed_tensor_keys:
|
||||
waveform = _get_video_audio(working_V[k])
|
||||
if waveform is None:
|
||||
raise ValueError(f"Input '{k}' does not contain audio data.")
|
||||
waveforms[k] = waveform
|
||||
|
||||
# Normalize all waveforms together
|
||||
normalized_waveforms = normalize_to_common_shape(*waveforms.values(), mode=length_mismatch)
|
||||
V_norm_waveforms = dict(zip(needed_tensor_keys, normalized_waveforms))
|
||||
|
||||
ref_waveform = normalized_waveforms[0]
|
||||
common_shape = ref_waveform.shape
|
||||
sample_rate = _get_video_sample_rate(V[needed_tensor_keys[0]])
|
||||
|
||||
if(length_mismatch == "error"):
|
||||
for name in needed_tensor_keys:
|
||||
if waveforms[name].shape != common_shape:
|
||||
raise ValueError(f"Input '{name}' has shape ({waveforms[name].shape[0]}, {waveforms[name].shape[2]}), expected ({common_shape[0]}, {common_shape[2]}) to match input.")
|
||||
|
||||
# Setup legacy variables a, b, c, d
|
||||
a_w = V_norm_waveforms.get("V0", make_zero_like(ref_waveform))
|
||||
b_w = V_norm_waveforms.get("V1", make_zero_like(a_w))
|
||||
c_w = V_norm_waveforms.get("V2", make_zero_like(a_w))
|
||||
d_w = V_norm_waveforms.get("V3", make_zero_like(a_w))
|
||||
|
||||
# Ensure legacy are normalized
|
||||
a_w, b_w, c_w, d_w = normalize_to_common_shape(a_w, b_w, c_w, d_w, mode=length_mismatch)
|
||||
|
||||
variables = LazyVariableDict({
|
||||
"a": a_w, "b": b_w, "c": c_w, "d": d_w,
|
||||
"w": F.get("F0", 0.0) if F.get("F0") is not None else 0.0,
|
||||
"x": F.get("F1", 0.0) if F.get("F1") is not None else 0.0,
|
||||
"y": F.get("F2", 0.0) if F.get("F2") is not None else 0.0,
|
||||
"z": F.get("F3", 0.0) if F.get("F3") is not None else 0.0,
|
||||
"B": getIndexTensorAlongDim(a_w, 0),
|
||||
"C": getIndexTensorAlongDim(a_w, 1),
|
||||
"channel": getIndexTensorAlongDim(a_w, 1),
|
||||
"S": getIndexTensorAlongDim(a_w, 2),
|
||||
"sample": getIndexTensorAlongDim(a_w, 2),
|
||||
"R": sample_rate,
|
||||
"sample_rate": sample_rate,
|
||||
"batch": getIndexTensorAlongDim(a_w, 0),
|
||||
"T": float(a_w.shape[0]),
|
||||
"batch_count": float(a_w.shape[0]),
|
||||
} | generate_dim_variables(a_w) | sample_rates)
|
||||
|
||||
for v_name, v_tensor in V_norm_waveforms.items():
|
||||
variables[v_name] = (lambda t=v_tensor: t)
|
||||
variables[v_name].is_lazy_var = True
|
||||
|
||||
def lazy_v_stack():
|
||||
return get_v_variable(V_norm_waveforms, length_mismatch=length_mismatch)[0]
|
||||
lazy_v_stack.is_lazy_var = True
|
||||
def lazy_v_count():
|
||||
return float(get_v_variable(V_norm_waveforms, length_mismatch=length_mismatch)[1])
|
||||
lazy_v_count.is_lazy_var = True
|
||||
variables["V"] = lazy_v_stack
|
||||
variables["Vcnt"] = lazy_v_count
|
||||
variables["V_count"] = lazy_v_count
|
||||
|
||||
f_stacked, f_cnt = get_f_variable(F)
|
||||
if f_stacked is not None:
|
||||
variables["F"] = f_stacked
|
||||
variables["Fcnt"] = float(f_cnt)
|
||||
variables["F_count"] = float(f_cnt)
|
||||
|
||||
for k, val in F.items():
|
||||
variables[k] = val if val is not None else 0.0
|
||||
|
||||
tree_pi = None
|
||||
if isinstance(Expression_pi,str):
|
||||
tree_pi = parse_expr(Expression_pi)
|
||||
else:
|
||||
tree_pi = Expression_pi
|
||||
visitor = UnifiedMathVisitor(variables, a_w.shape,a_w.device,state_storage=stack)
|
||||
result_waveform = visitor.visit(tree_pi)
|
||||
result_waveform = as_tensor(result_waveform, a_w.shape)
|
||||
result_waveform = move_to_device(result_waveform, original_device)
|
||||
result_video = move_to_device(result_video, original_device)
|
||||
|
||||
return (_build_video_output(result_video, result_waveform, sample_rate, V[needed_tensor_keys[0]]), stack)
|
||||
@@ -0,0 +1,91 @@
|
||||
import torch
|
||||
from ..helper_functions import generate_dim_variables,parse_expr, getIndexTensorAlongDim, as_tensor, commonLazy, normalize_to_common_shape,prepare_inputs
|
||||
from ..Parser.UnifiedMathVisitor import UnifiedMathVisitor
|
||||
from comfy_api.latest import io
|
||||
|
||||
|
||||
class AudioMathNodeOLD(io.ComfyNode):
|
||||
"""
|
||||
Enables math expressions on Audio.
|
||||
|
||||
Inputs:
|
||||
a, b, c, d: Audio inputs (b, c, d default to zero if not provided)
|
||||
w, x, y, z: Float variables for expressions
|
||||
Audio: Expression to apply on input audio
|
||||
|
||||
Outputs:
|
||||
AUDIO: Result of applying expression to input audio
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def define_schema(cls) -> io.Schema:
|
||||
return io.Schema(
|
||||
is_deprecated=True,
|
||||
node_id="mrmth_AudioMathNode",
|
||||
inputs=[
|
||||
io.Audio.Input(id="a"),
|
||||
io.Audio.Input(id="b", optional=True, lazy=True),
|
||||
io.Audio.Input(id="c", optional=True, lazy=True),
|
||||
io.Audio.Input(id="d", optional=True, lazy=True),
|
||||
io.Float.Input(id="w", default=0.0, optional=True, lazy=True, force_input=True),
|
||||
io.Float.Input(id="x", default=0.0, optional=True, lazy=True, force_input=True),
|
||||
io.Float.Input(id="y", default=0.0, optional=True, lazy=True, force_input=True),
|
||||
io.Float.Input(id="z", default=0.0, optional=True, lazy=True, force_input=True),
|
||||
io.String.Input(id="Audio", default="a*(1-w)+b*w", tooltip="Expression to apply on input audio waveforms"),
|
||||
io.Combo.Input(
|
||||
id="length_mismatch",
|
||||
options=["tile", "error", "pad"],
|
||||
default="error",
|
||||
tooltip="How to handle mismatched audio sample counts. tile: repeat shorter inputs; error: raise error on mismatch; pad: treat missing samples as zero."
|
||||
)
|
||||
],
|
||||
outputs=[
|
||||
io.Audio.Output(),
|
||||
],
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def check_lazy_status(cls, Audio, a, b=[], c=[], d=[], w=0, x=0, y=0, z=0, length_mismatch="tile"):
|
||||
return commonLazy(Audio, a, b, c, d, w, x, y, z)
|
||||
|
||||
@classmethod
|
||||
def execute(cls, Audio, a, b=None, c=None, d=None, w=0.0, x=0.0, y=0.0, z=0.0, length_mismatch="tile") -> io.NodeOutput:
|
||||
a, b, c, d = prepare_inputs(a, b, c, d)
|
||||
|
||||
av = a["waveform"]
|
||||
sample_rate = a["sample_rate"]
|
||||
|
||||
bv = None if b is None else b["waveform"]
|
||||
cv = None if c is None else c["waveform"]
|
||||
dv = None if d is None else d["waveform"]
|
||||
|
||||
if(length_mismatch == "error"):
|
||||
# For audio, we check the length dimension (last one) as well as batch
|
||||
tensors_to_check = [t for t in [av, bv, cv, dv] if t is not None]
|
||||
max_batch = max(t.shape[0] for t in tensors_to_check)
|
||||
max_len = max(t.shape[-1] for t in tensors_to_check)
|
||||
for tensor, name in zip([av, bv, cv, dv], ["a", "b", "c", "d"]):
|
||||
if tensor is not None:
|
||||
if tensor.shape[0] != max_batch or tensor.shape[-1] != max_len:
|
||||
raise ValueError(f"Input '{name}' has shape ({tensor.shape[0]}, {tensor.shape[-1]}), expected ({max_batch}, {max_len}) to match largest input.")
|
||||
ae, be, ce, de = normalize_to_common_shape(av, bv, cv, dv, mode=length_mismatch)
|
||||
|
||||
variables = {
|
||||
"a": ae, "b": be, "c": ce, "d": de,
|
||||
"w": w, "x": x, "y": y, "z": z,
|
||||
"C": getIndexTensorAlongDim(ae, 1),
|
||||
"channel": getIndexTensorAlongDim(ae, 1),
|
||||
"S": getIndexTensorAlongDim(ae, 2),
|
||||
"sample": getIndexTensorAlongDim(ae, 2),
|
||||
"R": torch.full_like(ae, sample_rate, dtype=torch.float32),
|
||||
"sample_rate": torch.full_like(ae, sample_rate, dtype=torch.float32),
|
||||
"T": torch.full_like(ae, ae.shape[2], dtype=torch.float32),
|
||||
"sample_count": torch.full_like(ae, ae.shape[2], dtype=torch.float32),
|
||||
"N": ae.shape[1],
|
||||
"channel_count": ae.shape[1],
|
||||
} | generate_dim_variables(ae)
|
||||
tree = parse_expr(Audio);
|
||||
visitor = UnifiedMathVisitor(variables, ae.shape)
|
||||
result = visitor.visit(tree)
|
||||
final_res = as_tensor(result, ae.shape)
|
||||
return ({"waveform": final_res, "sample_rate": sample_rate},)
|
||||
@@ -0,0 +1,58 @@
|
||||
from comfy_api.latest import io
|
||||
from ..modelLikeCommon import calculate_patches
|
||||
from inspect import cleandoc
|
||||
from ..helper_functions import commonLazy
|
||||
|
||||
|
||||
class CLIPMathNodeOLD(io.ComfyNode):
|
||||
"""
|
||||
This node enables the use of math expressions on CLIP weights.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def define_schema(cls) -> io.Schema:
|
||||
return io.Schema(
|
||||
node_id="mrmth_CLIPMathNode",
|
||||
display_name="CLIP Math",
|
||||
is_deprecated=True,
|
||||
inputs=[
|
||||
io.Clip.Input(id="a", tooltip="Main CLIP (base)"),
|
||||
io.Clip.Input(id="b", optional=True, lazy=True, tooltip="Optional 2nd CLIP"),
|
||||
io.Clip.Input(id="c", optional=True, lazy=True, tooltip="Optional 3rd CLIP"),
|
||||
io.Clip.Input(id="d", optional=True, lazy=True, tooltip="Optional 4th CLIP"),
|
||||
io.Float.Input(id="w", default=0.0, optional=True, lazy=True, force_input=True),
|
||||
io.Float.Input(id="x", default=0.0, optional=True, lazy=True, force_input=True),
|
||||
io.Float.Input(id="y", default=0.0, optional=True, lazy=True, force_input=True),
|
||||
io.Float.Input(id="z", default=0.0, optional=True, lazy=True, force_input=True),
|
||||
io.String.Input(id="Model", default="a*(1-w)+b*w", tooltip="Expression to apply on weights"),
|
||||
io.Combo.Input(
|
||||
id="length_mismatch",
|
||||
options=["tile", "error", "pad"],
|
||||
default="error",
|
||||
tooltip="How to handle mismatched layer counts. For models, this usually defaults to broadcast (zero for missing layers)."
|
||||
)
|
||||
],
|
||||
outputs=[
|
||||
io.Clip.Output(),
|
||||
],
|
||||
)
|
||||
|
||||
tooltip = cleandoc(__doc__)
|
||||
|
||||
@classmethod
|
||||
def check_lazy_status(cls, Model, a, b=[], c=[], d=[], w=0, x=0, y=0, z=0, length_mismatch="tile"):
|
||||
return commonLazy(Model, a, b, c, d, w, x, y, z)
|
||||
|
||||
@classmethod
|
||||
def execute(cls, Model, a, b=None, c=None, d=None, w=0.0, x=0.0, y=0.0, z=0.0, length_mismatch="tile") -> io.NodeOutput:
|
||||
patcher_a = a.patcher
|
||||
patcher_b = b.patcher if b else None
|
||||
patcher_c = c.patcher if c else None
|
||||
patcher_d = d.patcher if d else None
|
||||
|
||||
patches = calculate_patches(Model, patcher_a, patcher_b, patcher_c, patcher_d, w, x, y, z)
|
||||
|
||||
out_clip = a.clone()
|
||||
if patches:
|
||||
out_clip.add_patches(patches, 1.0, 1.0)
|
||||
return (out_clip,)
|
||||
@@ -0,0 +1,107 @@
|
||||
from ..helper_functions import commonLazy,parse_expr, generate_dim_variables, as_tensor, prepare_inputs, getIndexTensorAlongDim, normalize_to_common_shape
|
||||
from comfy_api.latest import io
|
||||
import torch
|
||||
|
||||
from ..Parser.UnifiedMathVisitor import UnifiedMathVisitor
|
||||
|
||||
class ConditioningMathNodeOLD(io.ComfyNode):
|
||||
"""
|
||||
Enables math operations on conditionings.
|
||||
|
||||
Inputs:
|
||||
a, b, c, d: Conditioning inputs (b, c, d default to zero if not provided)
|
||||
w, x, y, z: Float variables for expressions
|
||||
Tensor: Expression for the tensor part (describes image composition)
|
||||
pooled_output: Expression for the pooled output (condensed representation)
|
||||
|
||||
Outputs:
|
||||
CONDITIONING: Result of applying expressions to input conditionings
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def define_schema(cls) -> io.Schema:
|
||||
return io.Schema(
|
||||
node_id="mrmth_ConditioningMathNode",
|
||||
display_name="Conditioning math",
|
||||
is_deprecated=True,
|
||||
inputs=[
|
||||
io.Conditioning.Input(id="a"),
|
||||
io.Conditioning.Input(id="b", optional=True, lazy=True),
|
||||
io.Conditioning.Input(id="c", optional=True, lazy=True),
|
||||
io.Conditioning.Input(id="d", optional=True, lazy=True),
|
||||
io.Float.Input(id="w", default=0.0, optional=True, lazy=True, force_input=True),
|
||||
io.Float.Input(id="x", default=0.0, optional=True, lazy=True, force_input=True),
|
||||
io.Float.Input(id="y", default=0.0, optional=True, lazy=True, force_input=True),
|
||||
io.Float.Input(id="z", default=0.0, optional=True, lazy=True, force_input=True),
|
||||
io.String.Input(id="Tensor", default="a*(1-w)+b*w", tooltip="Expression for tensor part (image composition)"),
|
||||
io.String.Input(
|
||||
id="pooled_output", default="a*(1-w)+b*w", tooltip="Expression for pooled output (condensed representation)"
|
||||
),
|
||||
io.Combo.Input(
|
||||
id="length_mismatch",
|
||||
options=["tile", "error", "pad"],
|
||||
default="error",
|
||||
tooltip="How to handle mismatched conditioning segment counts. tile: repeat shorter inputs; error: raise error on mismatch; pad: treat missing as zero."
|
||||
)
|
||||
],
|
||||
outputs=[
|
||||
io.Conditioning.Output(),
|
||||
],
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def check_lazy_status(cls, Tensor, pooled_output, a, b=[], c=[], d=[], w=0, x=0, y=0, z=0, length_mismatch="tile"):
|
||||
tensor_needs = set(commonLazy(Tensor, a, b, c, d, w, x, y, z))
|
||||
pooled_needs = set(commonLazy(pooled_output, a, b, c, d, w, x, y, z))
|
||||
return list(tensor_needs.union(pooled_needs))
|
||||
|
||||
@classmethod
|
||||
def execute(cls, Tensor, pooled_output, a, b=None, c=None, d=None, w=0.0, x=0.0, y=0.0, z=0.0, length_mismatch="tile"):
|
||||
# Default missing conditionings to zero
|
||||
a_c, b_c, c_c, d_c = prepare_inputs(a, b, c, d)
|
||||
|
||||
# We process the first segment of each conditioning
|
||||
ta_full, da = a_c[0]
|
||||
tb_full, db = b_c[0]
|
||||
tc_full, dc = c_c[0]
|
||||
td_full, dd = d_c[0]
|
||||
|
||||
ta, tb, tc, td = normalize_to_common_shape(ta_full, tb_full, tc_full, td_full, mode=length_mismatch)
|
||||
|
||||
B_val = getIndexTensorAlongDim(ta, 0)
|
||||
variables = {
|
||||
"a": ta, "b": tb, "c": tc, "d": td, "w": w, "x": x, "y": y, "z": z,
|
||||
"B": B_val, "batch": B_val,
|
||||
"T": ta.shape[0], "batch_count": ta.shape[0],
|
||||
"N": ta.shape[1], "channel_count": ta.shape[1],
|
||||
} | generate_dim_variables(ta)
|
||||
|
||||
tree = parse_expr(Tensor)
|
||||
visitor = UnifiedMathVisitor(variables, ta.shape)
|
||||
result_tensor = as_tensor(visitor.visit(tree), ta.shape)
|
||||
|
||||
new_dict = da.copy()
|
||||
pa = da.get("pooled_output")
|
||||
if pa is not None:
|
||||
pb = db.get("pooled_output")
|
||||
pc = dc.get("pooled_output")
|
||||
pd = dd.get("pooled_output")
|
||||
|
||||
pb = pb if pb is not None else torch.zeros_like(pa)
|
||||
pc = pc if pc is not None else torch.zeros_like(pa)
|
||||
pd = pd if pd is not None else torch.zeros_like(pa)
|
||||
|
||||
pa, pb, pc, pd = normalize_to_common_shape(pa, pb, pc, pd, mode=length_mismatch)
|
||||
|
||||
variables_pooled = {"a": pa, "b": pb, "c": pc, "d": pd, "w": w, "x": x, "y": y, "z": z} | generate_dim_variables(pa)
|
||||
tree_p = parse_expr(pooled_output)
|
||||
visitor_p = UnifiedMathVisitor(variables_pooled, pa.shape)
|
||||
result_pooled = as_tensor(visitor_p.visit(tree_p), pa.shape)
|
||||
new_dict["pooled_output"] = result_pooled
|
||||
|
||||
# Create new conditioning list
|
||||
output_cond = [(result_tensor, new_dict)]
|
||||
if len(a) > 1:
|
||||
output_cond.extend(a[1:])
|
||||
|
||||
return (output_cond,)
|
||||
@@ -0,0 +1,67 @@
|
||||
from inspect import cleandoc
|
||||
|
||||
from ..helper_functions import parse_expr
|
||||
from ..Parser.UnifiedMathVisitor import UnifiedMathVisitor
|
||||
|
||||
from ..helper_functions import commonLazy
|
||||
from comfy_api.latest import io
|
||||
|
||||
|
||||
class FloatMathNodeOLD(io.ComfyNode):
|
||||
"""
|
||||
This node enables the use of math expressions on Floats.
|
||||
|
||||
Inputs:
|
||||
a, b, c, d: Floats, bound to variables with the same name.
|
||||
w, x, y, z: Floats, bound to variables of the expression.
|
||||
FloatFunc: String, describing math expression.
|
||||
Valid functions: sin, cos, tan, abs, sqrt, min, max, norm, etc.
|
||||
Operators: +, -, *, /, ^, %.
|
||||
Constants: e, pi.
|
||||
|
||||
Outputs:
|
||||
FLOAT: The result of evaluating the math expression.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def define_schema(cls) -> io.Schema:
|
||||
return io.Schema(
|
||||
node_id="mrmth_FloatMathNode",
|
||||
is_deprecated=True,
|
||||
display_name="Float math",
|
||||
inputs=[
|
||||
io.Float.Input(id="a", force_input=True),
|
||||
io.Float.Input(id="b", default=0.0, optional=True, lazy=True, force_input=True),
|
||||
io.Float.Input(id="c", default=0.0, optional=True, lazy=True, force_input=True),
|
||||
io.Float.Input(id="d", default=0.0, optional=True, lazy=True, force_input=True),
|
||||
io.Float.Input(id="w", default=0.0, optional=True, lazy=True, force_input=True),
|
||||
io.Float.Input(id="x", default=0.0, optional=True, lazy=True, force_input=True),
|
||||
io.Float.Input(id="y", default=0.0, optional=True, lazy=True, force_input=True),
|
||||
io.Float.Input(id="z", default=0.0, optional=True, lazy=True, force_input=True),
|
||||
io.String.Input(id="FloatFunc", default="a*(1-w)+b*w", tooltip="Expression to use on inputs"),
|
||||
],
|
||||
outputs=[
|
||||
io.Float.Output(),
|
||||
],
|
||||
)
|
||||
|
||||
tooltip = cleandoc(__doc__)
|
||||
|
||||
@classmethod
|
||||
def check_lazy_status(cls, FloatFunc, a, b=[], c=[], d=[], w=0, x=0, y=0, z=0,):
|
||||
return commonLazy(FloatFunc, a, b, c, d, w, x, y, z)
|
||||
|
||||
@classmethod
|
||||
def execute(cls, FloatFunc, a, b=0.0, c=0.0, d=0.0, w=0.0, x=0.0, y=0.0, z=0.0):
|
||||
|
||||
variables = {
|
||||
"a": a,
|
||||
"b": b,
|
||||
"c": c,
|
||||
"d": d,
|
||||
"w": w, "x": x, "y": y, "z": z
|
||||
}
|
||||
tree = parse_expr(FloatFunc);
|
||||
visitor = UnifiedMathVisitor(variables, [1])
|
||||
result = visitor.visit(tree)
|
||||
return (result,)
|
||||
@@ -0,0 +1,85 @@
|
||||
from ..helper_functions import generate_dim_variables, parse_expr, getIndexTensorAlongDim, as_tensor, prepare_inputs, commonLazy, normalize_to_common_shape
|
||||
from ..Parser.UnifiedMathVisitor import UnifiedMathVisitor
|
||||
from comfy_api.latest import io
|
||||
|
||||
|
||||
class ImageMathNodeOLD(io.ComfyNode):
|
||||
"""
|
||||
Enables math expressions on Images.
|
||||
|
||||
Inputs:
|
||||
a, b, c, d: Image inputs (b, c, d default to zero if not provided)
|
||||
w, x, y, z: Float variables for expressions
|
||||
Image: Expression to apply on input images
|
||||
|
||||
Outputs:
|
||||
IMAGE: Result of applying expression to input images
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def define_schema(cls) -> io.Schema:
|
||||
return io.Schema(
|
||||
node_id="mrmth_ImageMathNode",
|
||||
display_name="Image math",
|
||||
is_deprecated=True,
|
||||
inputs=[
|
||||
io.Image.Input(id="a"),
|
||||
io.Image.Input(id="b", optional=True, lazy=True),
|
||||
io.Image.Input(id="c", optional=True, lazy=True),
|
||||
io.Image.Input(id="d", optional=True, lazy=True),
|
||||
io.Float.Input(id="w", default=0.0, optional=True, lazy=True, force_input=True),
|
||||
io.Float.Input(id="x", default=0.0, optional=True, lazy=True, force_input=True),
|
||||
io.Float.Input(id="y", default=0.0, optional=True, lazy=True, force_input=True),
|
||||
io.Float.Input(id="z", default=0.0, optional=True, lazy=True, force_input=True),
|
||||
io.String.Input(id="Image", default="a*(1-w)+b*w", tooltip="Expression to apply on input images"),
|
||||
io.Combo.Input(
|
||||
id="length_mismatch",
|
||||
options=["tile", "error", "pad"],
|
||||
default="error",
|
||||
tooltip="How to handle mismatched image batch sizes. tile: repeat shorter inputs; error: raise error on mismatch; pad: treat missing frames as zero."
|
||||
)
|
||||
],
|
||||
outputs=[
|
||||
io.Image.Output(),
|
||||
],
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def check_lazy_status(cls, Image, a, b=[], c=[], d=[], w=0, x=0, y=0, z=0, length_mismatch="tile"):
|
||||
return commonLazy(Image, a, b, c, d, w, x, y, z)
|
||||
|
||||
@classmethod
|
||||
def execute(cls, Image, a, b=None, c=None, d=None, w=0.0, x=0.0, y=0.0, z=0.0, length_mismatch="tile"):
|
||||
ae, be, ce, de = prepare_inputs(a, b, c, d)
|
||||
print(f"DEBUG: shapes {ae.shape[0]}, {be.shape[0]}, {ce.shape[0]}, {de.shape[0]}")
|
||||
|
||||
if(length_mismatch == "error"):
|
||||
max_length = max(ae.shape[0], be.shape[0], ce.shape[0], de.shape[0])
|
||||
for tensor, name in zip([ae, be, ce, de], ["a", "b", "c", "d"]):
|
||||
if tensor.shape[0] != max_length:
|
||||
raise ValueError(f"Input '{name}' has shape {tensor.shape[0]}, expected {max_length} to match largest input.")
|
||||
ae, be, ce, de = normalize_to_common_shape(ae, be, ce, de, mode=length_mismatch)
|
||||
|
||||
variables = {
|
||||
"a": ae, "b": be, "c": ce, "d": de,
|
||||
"w": w, "x": x, "y": y, "z": z,
|
||||
"X": getIndexTensorAlongDim(ae, 3),
|
||||
"Y": getIndexTensorAlongDim(ae, 2),
|
||||
"B": getIndexTensorAlongDim(ae, 0),
|
||||
"batch": getIndexTensorAlongDim(ae, 0),
|
||||
"C": getIndexTensorAlongDim(ae, 1),
|
||||
"channel": getIndexTensorAlongDim(ae, 1),
|
||||
"W": ae.shape[2],
|
||||
"width": ae.shape[2],
|
||||
"H": ae.shape[1],
|
||||
"height": ae.shape[1],
|
||||
"T": ae.shape[0],
|
||||
"batch_count": ae.shape[0],
|
||||
"N": ae.shape[3],
|
||||
"channel_count": ae.shape[3],
|
||||
} | generate_dim_variables(ae)
|
||||
tree = parse_expr(Image);
|
||||
visitor = UnifiedMathVisitor(variables, ae.shape)
|
||||
result = visitor.visit(tree)
|
||||
result = as_tensor(result, ae.shape)
|
||||
return (result,)
|
||||
@@ -0,0 +1,159 @@
|
||||
from inspect import cleandoc
|
||||
from comfy_api.latest import io
|
||||
from ..helper_functions import (
|
||||
generate_dim_variables,
|
||||
getIndexTensorAlongDim,
|
||||
parse_expr,
|
||||
as_tensor,
|
||||
normalize_to_common_shape,
|
||||
prepare_inputs
|
||||
)
|
||||
from ..helper_functions import commonLazy
|
||||
from ..Parser.UnifiedMathVisitor import UnifiedMathVisitor
|
||||
import torch
|
||||
|
||||
|
||||
class LatentMathNodeOLD(io.ComfyNode):
|
||||
"""
|
||||
This node enables the use of math expressions on Latents.
|
||||
inputs:
|
||||
a, b, c, d:
|
||||
Latent, bound to variables with the same name. Defaults to zero latent if not provided.
|
||||
w, x, y, z:
|
||||
Floats, bound to variables of the expression. Defaults to 0.0 if not provided.
|
||||
Latent expression:
|
||||
String, describing expression to aply to latents.
|
||||
|
||||
outputs:
|
||||
LATENT:
|
||||
Returns a LATENT object that contains the result of the math expression applied to the input conditionings.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def define_schema(cls) -> io.Schema:
|
||||
""" """
|
||||
return io.Schema(
|
||||
node_id="mrmth_LatentMathNode",
|
||||
display_name="Latent math",
|
||||
is_deprecated=True,
|
||||
inputs=[
|
||||
io.Latent.Input(id="a"),
|
||||
io.Latent.Input(id="b", optional=True, lazy=True),
|
||||
io.Latent.Input(id="c", optional=True, lazy=True),
|
||||
io.Latent.Input(id="d", optional=True, lazy=True),
|
||||
io.Float.Input(id="w", default=0.0, optional=True, lazy=True, force_input=True),
|
||||
io.Float.Input(id="x", default=0.0, optional=True, lazy=True, force_input=True),
|
||||
io.Float.Input(id="y", default=0.0, optional=True, lazy=True, force_input=True),
|
||||
io.Float.Input(id="z", default=0.0, optional=True, lazy=True, force_input=True),
|
||||
io.String.Input(id="Latent", default="a*(1-w)+b*w", tooltip="Expression to apply on input latents"),
|
||||
io.Combo.Input(
|
||||
id="length_mismatch",
|
||||
options=["tile", "error", "pad"],
|
||||
default="error",
|
||||
tooltip="How to handle mismatched latent batch sizes. tile: repeat shorter inputs; error: raise error on mismatch; pad: treat missing frames as zero."
|
||||
)
|
||||
],
|
||||
outputs=[
|
||||
io.Latent.Output(),
|
||||
],
|
||||
)
|
||||
|
||||
tooltip = cleandoc(__doc__)
|
||||
|
||||
@classmethod
|
||||
def check_lazy_status(cls, Latent, a, b=[], c=[], d=[], w=0, x=0, y=0, z=0, length_mismatch="tile"):
|
||||
return commonLazy(Latent, a, b, c, d, w, x, y, z)
|
||||
|
||||
@classmethod
|
||||
def execute(cls, Latent, a, b=None, c=None, d=None, w=0.0, x=0.0, y=0.0, z=0.0, length_mismatch="tile") -> io.NodeOutput:
|
||||
# Identify if any input is a NestedTensor and track original sizes for restoration
|
||||
stacked = False
|
||||
orig_split_sizes = None
|
||||
|
||||
for item in [a, b, c, d]:
|
||||
if item is not None:
|
||||
samples = item.get("samples")
|
||||
if getattr(samples, "is_nested", False):
|
||||
stacked = True
|
||||
# Store original split sizes (batch dimension)
|
||||
orig_split_sizes = [t.shape[0] for t in samples.tensors]
|
||||
break
|
||||
|
||||
if stacked:
|
||||
if a is not None and getattr(a.get("samples"), "is_nested", False):
|
||||
a = a.copy()
|
||||
a["samples"] = torch.cat(a["samples"].tensors, dim=0)
|
||||
if b is not None and getattr(b.get("samples"), "is_nested", False):
|
||||
b = b.copy()
|
||||
b["samples"] = torch.cat(b["samples"].tensors, dim=0)
|
||||
if c is not None and getattr(c.get("samples"), "is_nested", False):
|
||||
c = c.copy()
|
||||
c["samples"] = torch.cat(c["samples"].tensors, dim=0)
|
||||
if d is not None and getattr(d.get("samples"), "is_nested", False):
|
||||
d = d.copy()
|
||||
d["samples"] = torch.cat(d["samples"].tensors, dim=0)
|
||||
|
||||
a_c, b_c, c_c, d_c = prepare_inputs(a, b, c, d)
|
||||
at,bt,ct,dt = a_c["samples"],b_c["samples"],c_c["samples"],d_c["samples"]
|
||||
if(length_mismatch == "error"):
|
||||
# Check only available tensors
|
||||
tensors_to_check = [t for t in [at, bt, ct, dt] if t is not None]
|
||||
max_length = max(t.shape[0] for t in tensors_to_check)
|
||||
for tensor, name in zip([at, bt, ct, dt], ["a", "b", "c", "d"]):
|
||||
if tensor is not None:
|
||||
if tensor.shape[0] != max_length:
|
||||
raise ValueError(f"Input '{name}' has shape {tensor.shape[0]}, expected {max_length} to match largest input.")
|
||||
ae, be, ce, de = normalize_to_common_shape(at, bt, ct, dt, mode=length_mismatch)
|
||||
|
||||
# parse expression once
|
||||
tree = parse_expr(Latent)
|
||||
|
||||
ndim = ae.ndim
|
||||
batch_dim = 0
|
||||
channel_dim = -3
|
||||
height_dim = -2
|
||||
width_dim = -1
|
||||
time_dim = None
|
||||
if ndim >= 5:
|
||||
time_dim = -4
|
||||
|
||||
frame_count = ae.shape[time_dim] if time_dim is not None else ae.shape[batch_dim]
|
||||
|
||||
variables = {
|
||||
"a": ae, "b": be, "c": ce, "d": de,
|
||||
"w": w, "x": x, "y": y, "z": z,
|
||||
"X": getIndexTensorAlongDim(ae, width_dim),
|
||||
"Y": getIndexTensorAlongDim(ae, height_dim),
|
||||
"B": getIndexTensorAlongDim(ae, batch_dim),
|
||||
"batch": getIndexTensorAlongDim(ae, batch_dim),
|
||||
"C": getIndexTensorAlongDim(ae, channel_dim),
|
||||
"channel": getIndexTensorAlongDim(ae, channel_dim),
|
||||
"W": ae.shape[width_dim],
|
||||
"width": ae.shape[width_dim],
|
||||
"H": ae.shape[height_dim],
|
||||
"height": ae.shape[height_dim],
|
||||
"T": frame_count,
|
||||
"batch_count": ae.shape[batch_dim],
|
||||
"N": ae.shape[channel_dim],
|
||||
"channel_count": ae.shape[channel_dim],
|
||||
} | generate_dim_variables(ae)
|
||||
|
||||
if time_dim is not None:
|
||||
F = getIndexTensorAlongDim(ae, time_dim)
|
||||
variables.update({"frame_idx": F, "frame": F, "frame_count": frame_count})
|
||||
|
||||
visitor = UnifiedMathVisitor(variables, ae.shape)
|
||||
result_t = as_tensor(visitor.visit(tree), ae.shape)
|
||||
|
||||
result_latent = a_c.copy()
|
||||
if stacked and orig_split_sizes is not None:
|
||||
from comfy.nested_tensor import NestedTensor
|
||||
# Restore original split sizes
|
||||
result_latent["samples"] = NestedTensor(torch.split(result_t, orig_split_sizes, dim=0))
|
||||
else:
|
||||
result_latent["samples"] = result_t
|
||||
|
||||
return (result_latent,)
|
||||
@@ -0,0 +1,82 @@
|
||||
from ..helper_functions import generate_dim_variables,parse_expr, getIndexTensorAlongDim, as_tensor, commonLazy, normalize_to_common_shape,prepare_inputs
|
||||
from ..Parser.UnifiedMathVisitor import UnifiedMathVisitor
|
||||
from comfy_api.latest import io
|
||||
|
||||
|
||||
|
||||
|
||||
class MaskMathNodeOLD(io.ComfyNode):
|
||||
"""
|
||||
Enables math expressions on Masks.
|
||||
|
||||
Inputs:
|
||||
a, b, c, d: Mask inputs (b, c, d default to zero if not provided)
|
||||
w, x, y, z: Float variables for expressions
|
||||
Mask: Expression to apply on input masks
|
||||
|
||||
Outputs:
|
||||
MASK: Result of applying expression to input masks
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def define_schema(cls) -> io.Schema:
|
||||
return io.Schema(
|
||||
node_id="mrmth_MaskMathNode",
|
||||
display_name="Mask math",
|
||||
is_deprecated=True,
|
||||
inputs=[
|
||||
io.Mask.Input(id="a"),
|
||||
io.Mask.Input(id="b", optional=True, lazy=True),
|
||||
io.Mask.Input(id="c", optional=True, lazy=True),
|
||||
io.Mask.Input(id="d", optional=True, lazy=True),
|
||||
io.Float.Input(id="w", default=0.0, optional=True, lazy=True, force_input=True),
|
||||
io.Float.Input(id="x", default=0.0, optional=True, lazy=True, force_input=True),
|
||||
io.Float.Input(id="y", default=0.0, optional=True, lazy=True, force_input=True),
|
||||
io.Float.Input(id="z", default=0.0, optional=True, lazy=True, force_input=True),
|
||||
io.String.Input(id="Mask", default="a*(1-w)+b*w", tooltip="Expression to apply on input masks"),
|
||||
io.Combo.Input(
|
||||
id="length_mismatch",
|
||||
options=["tile", "error", "pad"],
|
||||
default="error",
|
||||
tooltip="How to handle mismatched mask batch sizes. tile: repeat shorter inputs; error: raise error on mismatch; pad: treat missing frames as zero."
|
||||
)
|
||||
],
|
||||
outputs=[
|
||||
io.Mask.Output(),
|
||||
],
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def check_lazy_status(cls, Mask, a, b=[], c=[], d=[], w=0, x=0, y=0, z=0, length_mismatch="tile"):
|
||||
return commonLazy(Mask, a, b, c, d, w, x, y, z)
|
||||
|
||||
@classmethod
|
||||
def execute(cls, Mask, a, b=None, c=None, d=None, w=0.0, x=0.0, y=0.0, z=0.0, length_mismatch="tile"):
|
||||
a, b, c, d = prepare_inputs(a, b, c, d)
|
||||
|
||||
if(length_mismatch == "error"):
|
||||
max_length = max(a.shape[0], b.shape[0], c.shape[0], d.shape[0])
|
||||
for tensor, name in zip([a, b, c, d], ["a", "b", "c", "d"]):
|
||||
if tensor.shape[0] != max_length:
|
||||
raise ValueError(f"Input '{name}' has shape {tensor.shape[0]}, expected {max_length} to match largest input.")
|
||||
ae, be, ce, de = normalize_to_common_shape(a, b, c, d, mode=length_mismatch)
|
||||
|
||||
variables = {
|
||||
"a": ae, "b": be, "c": ce, "d": de,
|
||||
"w": w, "x": x, "y": y, "z": z,
|
||||
"X": getIndexTensorAlongDim(ae, 2),
|
||||
"Y": getIndexTensorAlongDim(ae, 1),
|
||||
"B": getIndexTensorAlongDim(ae, 0),
|
||||
"batch": getIndexTensorAlongDim(ae, 0),
|
||||
"W": ae.shape[2],
|
||||
"width": ae.shape[2],
|
||||
"H": ae.shape[1],
|
||||
"height": ae.shape[1],
|
||||
"T": ae.shape[0],
|
||||
"batch_count": ae.shape[0],
|
||||
} | generate_dim_variables(ae)
|
||||
tree = parse_expr(Mask);
|
||||
visitor = UnifiedMathVisitor(variables, ae.shape)
|
||||
result = visitor.visit(tree)
|
||||
result = as_tensor(result, ae.shape)
|
||||
return (result,)
|
||||
@@ -0,0 +1,53 @@
|
||||
from inspect import cleandoc
|
||||
from comfy_api.latest import io
|
||||
from ..helper_functions import commonLazy
|
||||
from ..modelLikeCommon import calculate_patches
|
||||
|
||||
|
||||
class ModelMathNodeOLD(io.ComfyNode):
|
||||
"""
|
||||
This node enables the use of math expressions on Model weights (state_dict).
|
||||
Functionally acts as a custom model merge.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def define_schema(cls) -> io.Schema:
|
||||
return io.Schema(
|
||||
node_id="mrmth_ModelMathNode",
|
||||
display_name="Model Math",
|
||||
is_deprecated=True,
|
||||
inputs=[
|
||||
io.Model.Input(id="a", tooltip="Main model (base)"),
|
||||
io.Model.Input(id="b", optional=True, lazy=True, tooltip="Optional 2nd model"),
|
||||
io.Model.Input(id="c", optional=True, lazy=True, tooltip="Optional 3rd model"),
|
||||
io.Model.Input(id="d", optional=True, lazy=True, tooltip="Optional 4th model"),
|
||||
io.Float.Input(id="w", default=0.0, optional=True, lazy=True, force_input=True),
|
||||
io.Float.Input(id="x", default=0.0, optional=True, lazy=True, force_input=True),
|
||||
io.Float.Input(id="y", default=0.0, optional=True, lazy=True, force_input=True),
|
||||
io.Float.Input(id="z", default=0.0, optional=True, lazy=True, force_input=True),
|
||||
io.String.Input(id="Model", default="a*(1-w)+b*w", tooltip="Expression to apply on weights"),
|
||||
io.Combo.Input(
|
||||
id="length_mismatch",
|
||||
options=["tile", "error", "pad"],
|
||||
default="error",
|
||||
tooltip="How to handle mismatched layer counts. For models, this usually defaults to broadcast (zero for missing layers)."
|
||||
)
|
||||
],
|
||||
outputs=[
|
||||
io.Model.Output(),
|
||||
],
|
||||
)
|
||||
|
||||
tooltip = cleandoc(__doc__)
|
||||
|
||||
@classmethod
|
||||
def check_lazy_status(cls, Model, a, b=[], c=[], d=[], w=0, x=0, y=0, z=0, length_mismatch="tile"):
|
||||
return commonLazy(Model, a, b, c, d, w, x, y, z)
|
||||
|
||||
@classmethod
|
||||
def execute(cls, Model, a, b=None, c=None, d=None, w=0.0, x=0.0, y=0.0, z=0.0, length_mismatch="tile") -> io.NodeOutput:
|
||||
patches = calculate_patches(Model, a, b, c, d, w, x, y, z)
|
||||
out_model = a.clone()
|
||||
if patches:
|
||||
out_model.add_patches(patches, 1.0, 1.0)
|
||||
return (out_model,)
|
||||
@@ -0,0 +1,210 @@
|
||||
from inspect import cleandoc
|
||||
|
||||
import torch
|
||||
|
||||
from ..helper_functions import (
|
||||
generate_dim_variables,
|
||||
getIndexTensorAlongDim,
|
||||
commonLazy,
|
||||
parse_expr,
|
||||
make_zero_like,
|
||||
as_tensor,
|
||||
)
|
||||
|
||||
from ..Parser.UnifiedMathVisitor import UnifiedMathVisitor
|
||||
from comfy_api.latest import io
|
||||
|
||||
import comfy.nested_tensor as _nested_tensor_module
|
||||
|
||||
|
||||
class NoiseMathNodeOLD(io.ComfyNode):
|
||||
"""
|
||||
This node enables the use of math expressions on Latents.
|
||||
inputs:
|
||||
a, b, c, d:
|
||||
Noise generators, bound to variables with the same name. Defaults to zero latent if not provided.
|
||||
w, x, y, z:
|
||||
Floats, bound to variables of the expression. Defaults to 0.0 if not provided.
|
||||
Latent expression:
|
||||
String, describing expression to mix noise.
|
||||
|
||||
outputs:
|
||||
LATENT:
|
||||
Returns a LATENT object that contains the result of the math expression applied to the input conditionings.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def define_schema(cls) -> io.Schema:
|
||||
""" """
|
||||
return io.Schema(
|
||||
is_deprecated=True,
|
||||
node_id="mrmth_NoiseMathNode",
|
||||
#category="More math",
|
||||
display_name="Noise math",
|
||||
inputs=[
|
||||
io.Noise.Input(id="a"),
|
||||
io.Noise.Input(id="b", optional=True, lazy=True),
|
||||
io.Noise.Input(id="c", optional=True, lazy=True),
|
||||
io.Noise.Input(id="d", optional=True, lazy=True),
|
||||
io.Float.Input(id="w", default=0.0, optional=True, lazy=True, force_input=True),
|
||||
io.Float.Input(id="x", default=0.0, optional=True, lazy=True, force_input=True),
|
||||
io.Float.Input(id="y", default=0.0, optional=True, lazy=True, force_input=True),
|
||||
io.Float.Input(id="z", default=0.0, optional=True, lazy=True, force_input=True),
|
||||
io.String.Input(id="Noise", default="a*(1-w)+b*w", tooltip="Expression to apply on input noise generators"),
|
||||
],
|
||||
outputs=[
|
||||
io.Noise.Output(),
|
||||
],
|
||||
)
|
||||
|
||||
# RETURN_NAMES = ("image_output_name",)
|
||||
tooltip = cleandoc(__doc__)
|
||||
# OUTPUT_NODE = False
|
||||
# OUTPUT_TOOLTIPS = ("",) # Tooltips for the output node
|
||||
|
||||
CATEGORY = "More math"
|
||||
|
||||
@classmethod
|
||||
def check_lazy_status(cls, Noise, a, b=[], c=[], d=[], w=0, x=0, y=0, z=0):
|
||||
return commonLazy(Noise, a, b, c, d, w, x, y, z)
|
||||
|
||||
@classmethod
|
||||
def execute(cls, Noise, a, b=None, c=None, d=None, w=0.0, x=0.0, y=0.0, z=0.0):
|
||||
return (NoiseExecutor(a, b, c, d, w, x, y, z, Noise),)
|
||||
|
||||
"""
|
||||
The node will always be re executed if any of the inputs change but
|
||||
this method can be used to force the node to execute again even when the inputs don't change.
|
||||
You can make this node return a number or a string. This value will be compared to the one returned the last time the node was
|
||||
executed, if it is different the node will be executed again.
|
||||
This method is used in the core repo for the LoadImage node where they return the image hash as a string, if the image hash
|
||||
changes between executions the LoadImage node is executed again.
|
||||
"""
|
||||
# @classmethod
|
||||
# def IS_CHANGED(s, image, string_field, int_field, float_field, print_to_screen):
|
||||
# return ""
|
||||
|
||||
|
||||
class NoiseExecutor:
|
||||
def __init__(self, a, b, c, d, w, x, y, z, Noise):
|
||||
self.a = a
|
||||
self.b = b
|
||||
self.c = c
|
||||
self.d = d
|
||||
self.w = w
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.z = z
|
||||
self.expr = Noise
|
||||
self.tree = parse_expr(Noise)
|
||||
|
||||
seed = -1
|
||||
|
||||
def generate_noise(self, input_latent: torch.Tensor) -> torch.Tensor:
|
||||
samples = input_latent["samples"]
|
||||
merged_result=None
|
||||
a_val = self.a.generate_noise(input_latent) if self.a is not None else make_zero_like(samples)
|
||||
b_val = self.b.generate_noise(input_latent) if self.b is not None else make_zero_like(samples)
|
||||
c_val = self.c.generate_noise(input_latent) if self.c is not None else make_zero_like(samples)
|
||||
d_val = self.d.generate_noise(input_latent) if self.d is not None else make_zero_like(samples)
|
||||
|
||||
# nested case: merge subtensors, evaluate once, split back
|
||||
if hasattr(samples, "is_nested") and getattr(samples, "is_nested"):
|
||||
sample_list = samples.unbind()
|
||||
sizes = [t.shape[0] for t in sample_list]
|
||||
merged_samples = torch.cat(sample_list, dim=0)
|
||||
|
||||
def merge_to_tensor(val, ref):
|
||||
if val is None:
|
||||
return make_zero_like(ref)
|
||||
if hasattr(val, "is_nested") and getattr(val, "is_nested"):
|
||||
lst = val.unbind()
|
||||
return torch.cat(lst, dim=0)
|
||||
if isinstance(val, (list, tuple)):
|
||||
return torch.cat(list(val), dim=0)
|
||||
if torch.is_tensor(val):
|
||||
if val.shape == ref.shape:
|
||||
return val
|
||||
if val.shape[0] == sum(sizes):
|
||||
return val
|
||||
# if val has per-subtensor batches, try to split and cat
|
||||
try:
|
||||
if val.shape[0] == len(sample_list):
|
||||
return torch.cat(
|
||||
[val[i].unsqueeze(0).expand(sample_list[i].shape[0], *val.shape[1:]) for i in range(len(sample_list))],
|
||||
dim=0,
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
return make_zero_like(ref)
|
||||
|
||||
merged_a = merge_to_tensor(a_val, merged_samples)
|
||||
merged_b = merge_to_tensor(b_val, merged_samples)
|
||||
merged_c = merge_to_tensor(c_val, merged_samples)
|
||||
merged_d = merge_to_tensor(d_val, merged_samples)
|
||||
|
||||
else:
|
||||
merged_samples = samples
|
||||
merged_a = a_val
|
||||
merged_b = b_val
|
||||
merged_c = c_val
|
||||
merged_d = d_val
|
||||
|
||||
# evaluate once
|
||||
ndim = merged_samples.ndim
|
||||
batch_dim = 0
|
||||
channel_dim = -3
|
||||
height_dim = -2
|
||||
width_dim = -1
|
||||
time_dim = None
|
||||
if ndim >= 5:
|
||||
time_dim = -4
|
||||
|
||||
frame_count = merged_samples.shape[time_dim] if time_dim is not None else merged_samples.shape[batch_dim]
|
||||
|
||||
B = getIndexTensorAlongDim(merged_samples, batch_dim)
|
||||
W = getIndexTensorAlongDim(merged_samples, width_dim)
|
||||
H = getIndexTensorAlongDim(merged_samples, height_dim)
|
||||
C = getIndexTensorAlongDim(merged_samples, channel_dim)
|
||||
variables = {
|
||||
"a": merged_a,
|
||||
"b": merged_b,
|
||||
"c": merged_c,
|
||||
"d": merged_d,
|
||||
"w": self.w,
|
||||
"x": self.x,
|
||||
"y": self.y,
|
||||
"z": self.z,
|
||||
"B": B,
|
||||
"X": W,
|
||||
"Y": H,
|
||||
"C": C,
|
||||
"W": merged_samples.shape[width_dim],
|
||||
"H": merged_samples.shape[height_dim],
|
||||
"I": merged_samples,
|
||||
"T": frame_count,
|
||||
"N": merged_samples.shape[channel_dim],
|
||||
"batch": B,
|
||||
"width": merged_samples.shape[width_dim],
|
||||
"height": merged_samples.shape[height_dim],
|
||||
"channel": C,
|
||||
"batch_count": merged_samples.shape[0],
|
||||
"channel_count": merged_samples.shape[1],
|
||||
"input_latent": merged_samples,
|
||||
} | generate_dim_variables(merged_samples)
|
||||
if time_dim is not None:
|
||||
F = getIndexTensorAlongDim(merged_samples, time_dim)
|
||||
variables.update({"frame": F, "frame_count": frame_count})
|
||||
tree = parse_expr(self.expr);
|
||||
visitor = UnifiedMathVisitor(variables, merged_samples.shape)
|
||||
result_tensor = visitor.visit(tree)
|
||||
merged_result = as_tensor(result_tensor, variables["a"].shape)
|
||||
|
||||
if hasattr(samples, "is_nested") and getattr(samples, "is_nested"):
|
||||
split_results = list(merged_result.split(sizes, dim=0))
|
||||
return _nested_tensor_module.NestedTensor(split_results)
|
||||
|
||||
return merged_result
|
||||
@@ -0,0 +1,63 @@
|
||||
from inspect import cleandoc
|
||||
from comfy_api.latest import io
|
||||
import copy
|
||||
from ..modelLikeCommon import calculate_patches
|
||||
from ..helper_functions import commonLazy
|
||||
|
||||
|
||||
class VAEMathNodeOLD(io.ComfyNode):
|
||||
"""
|
||||
This node enables the use of math expressions on VAE weights.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def define_schema(cls) -> io.Schema:
|
||||
return io.Schema(
|
||||
node_id="mrmth_VAEMathNode",
|
||||
display_name="VAE Math",
|
||||
is_deprecated=True,
|
||||
inputs=[
|
||||
io.Vae.Input(id="a", tooltip="Main VAE (base)"),
|
||||
io.Vae.Input(id="b", optional=True, lazy=True, tooltip="Optional 2nd VAE"),
|
||||
io.Vae.Input(id="c", optional=True, lazy=True, tooltip="Optional 3rd VAE"),
|
||||
io.Vae.Input(id="d", optional=True, lazy=True, tooltip="Optional 4th VAE"),
|
||||
io.Float.Input(id="w", default=0.0, lazy=True, optional=True, force_input=True),
|
||||
io.Float.Input(id="x", default=0.0, lazy=True, optional=True, force_input=True),
|
||||
io.Float.Input(id="y", default=0.0, lazy=True, optional=True, force_input=True),
|
||||
io.Float.Input(id="z", default=0.0, lazy=True, optional=True, force_input=True),
|
||||
io.String.Input(id="Model", default="a*(1-w)+b*w", tooltip="Expression to apply on weights"),
|
||||
io.Combo.Input(
|
||||
id="length_mismatch",
|
||||
options=["tile", "error", "pad"],
|
||||
default="error",
|
||||
tooltip="How to handle mismatched layer counts. For models, this usually defaults to broadcast (zero for missing layers)."
|
||||
)
|
||||
],
|
||||
outputs=[
|
||||
io.Vae.Output(),
|
||||
],
|
||||
)
|
||||
|
||||
tooltip = cleandoc(__doc__)
|
||||
|
||||
@classmethod
|
||||
def check_lazy_status(cls, Model, a, b=[], c=[], d=[], w=0, x=0, y=0, z=0, length_mismatch="tile"):
|
||||
return commonLazy(Model, a, b, c, d, w, x, y, z)
|
||||
|
||||
@classmethod
|
||||
def execute(cls, Model, a, b=None, c=None, d=None, w=0.0, x=0.0, y=0.0, z=0.0, length_mismatch="tile") -> io.NodeOutput:
|
||||
patcher_a = a.patcher
|
||||
patcher_b = b.patcher if b else None
|
||||
patcher_c = c.patcher if c else None
|
||||
patcher_d = d.patcher if d else None
|
||||
|
||||
patches = calculate_patches(Model, patcher_a, patcher_b, patcher_c, patcher_d, w, x, y, z)
|
||||
|
||||
# VAE does not have a clone method, so we shallow copy and clone the patcher
|
||||
out_vae = copy.copy(a)
|
||||
out_vae.patcher = a.patcher.clone()
|
||||
|
||||
if patches:
|
||||
out_vae.patcher.add_patches(patches, 1.0, 1.0)
|
||||
|
||||
return (out_vae,)
|
||||
@@ -0,0 +1,158 @@
|
||||
import torch
|
||||
import math
|
||||
from ..helper_functions import (
|
||||
generate_dim_variables,
|
||||
getIndexTensorAlongDim,
|
||||
parse_expr,
|
||||
commonLazy,
|
||||
normalize_to_common_shape,
|
||||
as_tensor,
|
||||
prepare_inputs
|
||||
)
|
||||
|
||||
from ..Parser.UnifiedMathVisitor import UnifiedMathVisitor
|
||||
from comfy_api.latest import io
|
||||
from comfy_api.latest import VideoComponents, VideoFromComponents
|
||||
|
||||
|
||||
class VideoMathNodeOLD(io.ComfyNode):
|
||||
"""
|
||||
Enables math expressions on Videos (Images + Audio).
|
||||
Video object is expected to have 'images' and 'audio' components.
|
||||
Expressions for 'Images' and 'Audio' are handled separately.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def define_schema(cls) -> io.Schema:
|
||||
return io.Schema(
|
||||
node_id="mrmth_VideoMathNode",
|
||||
is_deprecated=True,
|
||||
display_name="Video math",
|
||||
inputs=[
|
||||
io.Video.Input(id="a"),
|
||||
io.Video.Input(id="b", optional=True, lazy=True),
|
||||
io.Video.Input(id="c", optional=True, lazy=True),
|
||||
io.Video.Input(id="d", optional=True, lazy=True),
|
||||
io.Float.Input(id="w", default=0.0, optional=True, lazy=True, force_input=True),
|
||||
io.Float.Input(id="x", default=0.0, optional=True, lazy=True, force_input=True),
|
||||
io.Float.Input(id="y", default=0.0, optional=True, lazy=True, force_input=True),
|
||||
io.Float.Input(id="z", default=0.0, optional=True, lazy=True, force_input=True),
|
||||
io.String.Input(id="Images", default="a*(1-w)+b*w", tooltip="Expression for the image frames"),
|
||||
io.String.Input(id="Audio", default="a*(1-w)+b*w", tooltip="Expression for the audio component"),
|
||||
io.Combo.Input(
|
||||
id="length_mismatch",
|
||||
options=["tile", "error", "pad"],
|
||||
default="error",
|
||||
tooltip="How to handle mismatched frame/sample counts. tile: repeat shorter inputs; error: raise error on mismatch; pad: treat missing as zero."
|
||||
)
|
||||
],
|
||||
outputs=[
|
||||
io.Video.Output(),
|
||||
],
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def check_lazy_status(cls, Images, Audio, a, b=[], c=[], d=[], w=0, x=0, y=0, z=0, length_mismatch="tile"):
|
||||
return commonLazy(Images, a, b, c, d, w, x, y, z) + commonLazy(Audio, a, b, c, d, w, x, y, z)
|
||||
|
||||
@classmethod
|
||||
def execute(cls, Images, Audio, a, b=None, c=None, d=None, w=0.0, x=0.0, y=0.0, z=0.0, length_mismatch="tile") -> io.NodeOutput:
|
||||
|
||||
ac, bc, cc, dc = prepare_inputs(a,b,c,d)
|
||||
imgs_ae = ac.images
|
||||
imgs_be = bc.images
|
||||
imgs_ce = cc.images
|
||||
imgs_de = dc.images
|
||||
imgs_ae, imgs_be, imgs_ce, imgs_de = normalize_to_common_shape(imgs_ae, imgs_be, imgs_ce, imgs_de, mode=length_mismatch)
|
||||
|
||||
img_vars = {
|
||||
"a": imgs_ae, "b": imgs_be, "c": imgs_ce, "d": imgs_de,
|
||||
"w": w, "x": x, "y": y, "z": z,
|
||||
"B": getIndexTensorAlongDim(imgs_ae, 0),
|
||||
"frame": getIndexTensorAlongDim(imgs_ae, 0),
|
||||
"C": getIndexTensorAlongDim(imgs_ae, 1),
|
||||
"channel": getIndexTensorAlongDim(imgs_ae, 1),
|
||||
"H": imgs_ae.shape[2],
|
||||
"height": imgs_ae.shape[2],
|
||||
"W": imgs_ae.shape[3],
|
||||
"width": imgs_ae.shape[3],
|
||||
"T": imgs_ae.shape[0],
|
||||
"frame_count": imgs_ae.shape[0],
|
||||
"R": float(ac.frame_rate),
|
||||
"frame_rate": float(ac.frame_rate),
|
||||
"N": imgs_ae.shape[1],
|
||||
"channel_count": imgs_ae.shape[1],
|
||||
} | generate_dim_variables(imgs_ae)
|
||||
|
||||
tree = parse_expr(Images);
|
||||
visitor = UnifiedMathVisitor(img_vars, imgs_ae.shape)
|
||||
result_tensor = visitor.visit(tree)
|
||||
result_imgs = as_tensor(result_tensor, imgs_ae.shape)
|
||||
|
||||
# --- Process Audio ---
|
||||
wav_a = ac.audio["waveform"]
|
||||
sample_rate = ac.audio["sample_rate"]
|
||||
wav_b = bc.audio["waveform"] if bc is not None else None
|
||||
wav_c = cc.audio["waveform"] if cc is not None else None
|
||||
wav_d = dc.audio["waveform"] if dc is not None else None
|
||||
|
||||
# Determine target_wav_len as max of all provided audio
|
||||
wav_lengths = [wav_a.shape[2]]
|
||||
if bc is not None: wav_lengths.append(wav_b.shape[2])
|
||||
if cc is not None: wav_lengths.append(wav_c.shape[2])
|
||||
if dc is not None: wav_lengths.append(wav_d.shape[2])
|
||||
target_wav_len = max(wav_lengths)
|
||||
|
||||
def resolve_wav(tensor, comp, name):
|
||||
if comp is None:
|
||||
return 0.0
|
||||
|
||||
curr_len = tensor.shape[2]
|
||||
if curr_len == target_wav_len:
|
||||
return tensor
|
||||
|
||||
if length_mismatch == "tile":
|
||||
return tensor.repeat(1, 1, math.ceil(target_wav_len / curr_len))[:, :, :target_wav_len]
|
||||
elif length_mismatch == "pad":
|
||||
out = torch.zeros((tensor.shape[0], tensor.shape[1], target_wav_len), device=tensor.device, dtype=tensor.dtype)
|
||||
out[:, :, :curr_len] = tensor
|
||||
return out
|
||||
else: # error
|
||||
raise ValueError(f"Audio samples mismatch in {name}: expected {target_wav_len} to match longest input, got {curr_len}. Set length_mismatch to 'broadcast' or 'pad' to handle this.")
|
||||
|
||||
wav_ae = resolve_wav(wav_a, True, "input a")
|
||||
wav_be = resolve_wav(wav_b, bc, "input b")
|
||||
wav_ce = resolve_wav(wav_c, cc, "input c")
|
||||
wav_de = resolve_wav(wav_d, dc, "input d")
|
||||
|
||||
wav_ae, wav_be, wav_ce, wav_de = normalize_to_common_shape(wav_ae, wav_be, wav_ce, wav_de, mode=length_mismatch)
|
||||
|
||||
wav_vars = {
|
||||
"a": wav_ae, "b": wav_be, "c": wav_ce, "d": wav_de,
|
||||
"w": w, "x": x, "y": y, "z": z,
|
||||
"B": getIndexTensorAlongDim(wav_ae, 0),
|
||||
"batch": getIndexTensorAlongDim(wav_ae, 0),
|
||||
"C": getIndexTensorAlongDim(wav_ae, 1),
|
||||
"channel": getIndexTensorAlongDim(wav_ae, 1),
|
||||
"S": getIndexTensorAlongDim(wav_ae, 2),
|
||||
"sample": getIndexTensorAlongDim(wav_ae, 2),
|
||||
"R": sample_rate,
|
||||
"sample_rate": sample_rate,
|
||||
"T": wav_ae.shape[2],
|
||||
"sample_count": wav_ae.shape[2],
|
||||
"N": wav_ae.shape[1],
|
||||
"channel_count": wav_ae.shape[1],
|
||||
} | generate_dim_variables(wav_ae)
|
||||
|
||||
tree = parse_expr(Audio);
|
||||
visitor = UnifiedMathVisitor(wav_vars, wav_ae.shape)
|
||||
result_tensor = visitor.visit(tree)
|
||||
result_audio_wav = as_tensor(result_tensor, wav_ae.shape)
|
||||
|
||||
output = VideoFromComponents(
|
||||
VideoComponents(
|
||||
images=result_imgs, audio={"waveform": result_audio_wav, "sample_rate": sample_rate}, frame_rate=ac.frame_rate
|
||||
)
|
||||
)
|
||||
|
||||
return (output,)
|
||||
@@ -0,0 +1,455 @@
|
||||
from antlr4.error.ErrorListener import ErrorListener
|
||||
from antlr4 import InputStream, CommonTokenStream
|
||||
|
||||
import torch
|
||||
|
||||
from .Parser.antlr_router import get_antlr_modules
|
||||
MathExprLexer, MathExprParser, _ = get_antlr_modules()
|
||||
|
||||
import re
|
||||
|
||||
|
||||
class LazyVariableDict(dict):
|
||||
def __getitem__(self, key):
|
||||
val = super().__getitem__(key)
|
||||
if callable(val) and getattr(val, "is_lazy_var", False):
|
||||
val = val()
|
||||
self[key] = val
|
||||
return val
|
||||
|
||||
def get(self, key, default=None):
|
||||
try:
|
||||
return self[key]
|
||||
except KeyError:
|
||||
return default
|
||||
|
||||
def copy(self):
|
||||
return LazyVariableDict(self)
|
||||
|
||||
def __or__(self, other):
|
||||
res = self.copy()
|
||||
res.update(other)
|
||||
return res
|
||||
|
||||
def __ror__(self, other):
|
||||
res = LazyVariableDict(other)
|
||||
res.update(self)
|
||||
return res
|
||||
|
||||
def __ior__(self, other):
|
||||
self.update(other)
|
||||
return self
|
||||
|
||||
|
||||
class ThrowingErrorListener(ErrorListener):
|
||||
"""Error listener that raises ValueError on syntax errors."""
|
||||
|
||||
def syntaxError(self, recognizer, offendingSymbol, line, column, msg, e):
|
||||
raise ValueError(f"Syntax error in expression at line {line}, col {column}: {msg}")
|
||||
|
||||
|
||||
def as_tensor(value, shape):
|
||||
if getattr(value, "is_nested", False):
|
||||
return value
|
||||
if isinstance(value, torch.Tensor):
|
||||
# Pass tensors through unchanged; the expression defines the output shape.
|
||||
return value.contiguous()
|
||||
if isinstance(value, (float, int)):
|
||||
value = (value,)
|
||||
return torch.broadcast_to(torch.Tensor(value).to(dtype=torch.float32), shape).contiguous()
|
||||
if isinstance(value, (list, tuple)) and value and isinstance(value[0], torch.Tensor):
|
||||
# Keep lists of tensors as-is (e.g. intentionally returning a list of latents).
|
||||
return value
|
||||
return torch.cat(value)
|
||||
|
||||
|
||||
def parse_expr(expr: str):
|
||||
"""Parse a math expression and return the parse tree."""
|
||||
input_stream = InputStream(expr)
|
||||
lexer = MathExprLexer(input_stream)
|
||||
stream = CommonTokenStream(lexer)
|
||||
parser = MathExprParser(stream)
|
||||
parser.addErrorListener(ThrowingErrorListener())
|
||||
return parser.start()
|
||||
|
||||
|
||||
def getIndexTensorAlongDim(tensor, dim):
|
||||
"""Create a tensor of indices along a dimension, broadcasted to full shape."""
|
||||
shape = tensor.shape
|
||||
values = torch.arange(shape[dim], dtype=torch.float32, device=tensor.device)
|
||||
view_shape = [1] * len(shape)
|
||||
view_shape[dim] = shape[dim]
|
||||
values = values.view(*view_shape)
|
||||
return values.expand(*shape)
|
||||
|
||||
|
||||
def commonLazy(expr, a, b=None, c=None, d=None, w=0.0, x=0.0, y=0.0, z=0.0):
|
||||
"""Determine which lazy inputs are needed based on expression variables."""
|
||||
variables = {"a": a, "b": b, "c": c, "d": d, "w": w, "x": x, "y": y, "z": z}
|
||||
need_eval = []
|
||||
input_stream = InputStream(expr)
|
||||
lexer = MathExprLexer(input_stream)
|
||||
stream = CommonTokenStream(lexer)
|
||||
stream.fill()
|
||||
for token in filter(lambda t: t.type == MathExprParser.VARIABLE, stream.tokens):
|
||||
if token.text in variables and variables[token.text] is None:
|
||||
need_eval.append(token.text)
|
||||
return need_eval
|
||||
|
||||
|
||||
def generate_dim_variables(tensor: torch.Tensor):
|
||||
"""Generate index and size tensors for each dimension of the input tensor."""
|
||||
variables = {}
|
||||
for dim, size in enumerate(tensor.shape):
|
||||
variables[f"D{dim}"] = getIndexTensorAlongDim(tensor, dim)
|
||||
variables[f"S{dim}"] = float(size) # Scalar size, not tensor
|
||||
return variables
|
||||
|
||||
|
||||
def prepare_inputs(a, b, c, d):
|
||||
"""
|
||||
Ensures optional inputs b, c, d are zero-initialized like a if None.
|
||||
Returns the prepared (a, b, c, d).
|
||||
"""
|
||||
b = b if b is not None else make_zero_like(a)
|
||||
c = c if c is not None else make_zero_like(a)
|
||||
d = d if d is not None else make_zero_like(a)
|
||||
return a, b, c, d
|
||||
|
||||
|
||||
def make_zero_like(ref):
|
||||
"""
|
||||
Create a zero-initialized version of the reference object, maintaining its structure.
|
||||
Handles Conditioning, Audio, Latent, VideoComponents, and Tensors.
|
||||
"""
|
||||
if ref is None:
|
||||
return None
|
||||
|
||||
# raw torch tensor
|
||||
if torch.is_tensor(ref):
|
||||
return torch.zeros_like(ref)
|
||||
|
||||
# Conditioning: list of tuples [(tensor, dict)]
|
||||
if isinstance(ref, list) and len(ref) > 0 and isinstance(ref[0], (list, tuple)) and len(ref[0]) >= 2:
|
||||
zeroed_cond = []
|
||||
for entry in ref:
|
||||
ref_tensor = entry[0]
|
||||
ref_dict = entry[1]
|
||||
if torch.is_tensor(ref_tensor):
|
||||
new_dict = ref_dict.copy()
|
||||
if "pooled_output" in new_dict and torch.is_tensor(new_dict["pooled_output"]):
|
||||
new_dict["pooled_output"] = torch.zeros_like(new_dict["pooled_output"])
|
||||
zeroed_cond.append((torch.zeros_like(ref_tensor), new_dict))
|
||||
return zeroed_cond
|
||||
|
||||
# Audio or Latent: dict
|
||||
if isinstance(ref, dict):
|
||||
if "waveform" in ref: # Audio
|
||||
return {"waveform": make_zero_like(ref["waveform"]), "sample_rate": ref["sample_rate"]}
|
||||
if "samples" in ref: # Latent
|
||||
return {"samples": make_zero_like(ref["samples"])}
|
||||
|
||||
# NestedTensor
|
||||
if getattr(ref, "is_nested", False):
|
||||
from comfy.nested_tensor import NestedTensor
|
||||
return NestedTensor([torch.zeros_like(t) for t in ref.tensors])
|
||||
|
||||
# VideoComponents or other objects with images/audio attributes
|
||||
if hasattr(ref, "images") and hasattr(ref, "audio"):
|
||||
# Dynamically create same type (e.g. VideoComponents)
|
||||
return type(ref)(
|
||||
images=torch.zeros_like(ref.images),
|
||||
audio={"waveform": torch.zeros_like(ref.audio["waveform"]), "sample_rate": ref.audio["sample_rate"]},
|
||||
frame_rate=ref.frame_rate,
|
||||
metadata=None,
|
||||
)
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def get_tensor_device(value):
|
||||
if torch.is_tensor(value):
|
||||
return value.device
|
||||
if getattr(value, "is_nested", False):
|
||||
for tensor in value.tensors:
|
||||
if torch.is_tensor(tensor):
|
||||
return tensor.device
|
||||
if isinstance(value, dict):
|
||||
for item in value.values():
|
||||
device = get_tensor_device(item)
|
||||
if device is not None:
|
||||
return device
|
||||
if isinstance(value, (list, tuple)):
|
||||
for item in value:
|
||||
device = get_tensor_device(item)
|
||||
if device is not None:
|
||||
return device
|
||||
return None
|
||||
|
||||
|
||||
def move_to_device(value, device):
|
||||
if value is None or device is None:
|
||||
return value
|
||||
|
||||
if torch.is_tensor(value):
|
||||
return value.to(device=device)
|
||||
|
||||
if getattr(value, "is_nested", False):
|
||||
from comfy.nested_tensor import NestedTensor
|
||||
|
||||
return NestedTensor([move_to_device(t, device) for t in value.tensors])
|
||||
|
||||
if isinstance(value, dict):
|
||||
return {k: move_to_device(v, device) for k, v in value.items()}
|
||||
|
||||
if isinstance(value, list):
|
||||
return [move_to_device(v, device) for v in value]
|
||||
|
||||
if isinstance(value, tuple):
|
||||
return tuple(move_to_device(v, device) for v in value)
|
||||
|
||||
if hasattr(value, "images") and hasattr(value, "audio"):
|
||||
images = move_to_device(getattr(value, "images", None), device)
|
||||
audio = move_to_device(getattr(value, "audio", None), device)
|
||||
try:
|
||||
return type(value)(
|
||||
images=images,
|
||||
audio=audio,
|
||||
frame_rate=getattr(value, "frame_rate", None),
|
||||
metadata=getattr(value, "metadata", None),
|
||||
)
|
||||
except Exception:
|
||||
return value
|
||||
|
||||
return value
|
||||
|
||||
|
||||
def normalize_to_common_shape(*tensors, mode="pad"):
|
||||
"""
|
||||
Normalize multiple tensors to a common shape.
|
||||
- mode="pad": Pad with zeros to the max shape.
|
||||
- mode="tile": Tile to match (or exceed) dimensions, then truncate excess.
|
||||
"""
|
||||
if mode not in ("pad", "tile"):
|
||||
return tensors
|
||||
valid_tensors = [t for t in tensors if torch.is_tensor(t)]
|
||||
if len(valid_tensors) <= 1:
|
||||
return tensors
|
||||
|
||||
# Check if all shapes are already the same
|
||||
if all(t.shape == valid_tensors[0].shape for t in valid_tensors[1:]):
|
||||
return tensors
|
||||
|
||||
# Determine common shape
|
||||
target_ndim = max(t.ndim for t in valid_tensors)
|
||||
max_shape = [0] * target_ndim
|
||||
|
||||
for t in valid_tensors:
|
||||
for i in range(t.ndim):
|
||||
# Align from trailing dimensions
|
||||
max_shape[-(i + 1)] = max(max_shape[-(i + 1)], t.shape[-(i + 1)])
|
||||
|
||||
target_shape = tuple(max_shape)
|
||||
|
||||
def normalize_one(t, shape):
|
||||
if t.shape == shape:
|
||||
return t.contiguous()
|
||||
|
||||
# Match ndim first
|
||||
curr_t = t
|
||||
while curr_t.ndim < len(shape):
|
||||
curr_t = curr_t.unsqueeze(0)
|
||||
|
||||
if mode == "tile":
|
||||
# Calculate required repeats for each dimension
|
||||
repeats = []
|
||||
for s1, s2 in zip(curr_t.shape, shape):
|
||||
if s1 == 0: # Handle zero-sized tensors
|
||||
return curr_t
|
||||
# Ceiling division to ensure coverage
|
||||
repeat_times = (s2 + s1 - 1) // s1
|
||||
repeats.append(repeat_times)
|
||||
|
||||
# Repeat and truncate
|
||||
curr_t = curr_t.repeat(repeats)
|
||||
slices = tuple(slice(0, s2) for s2 in shape)
|
||||
return curr_t[slices].contiguous()
|
||||
|
||||
# Fallback to padding if not tile mode
|
||||
out = torch.zeros(shape, dtype=curr_t.dtype, device=curr_t.device)
|
||||
slices = tuple(slice(0, d) for d in curr_t.shape)
|
||||
out[slices] = curr_t
|
||||
return out.contiguous()
|
||||
|
||||
result = []
|
||||
for t in tensors:
|
||||
if torch.is_tensor(t):
|
||||
result.append(normalize_one(t, target_shape))
|
||||
else:
|
||||
result.append(t.contiguous())
|
||||
return tuple(result)
|
||||
|
||||
def get_v_variable(v_norm_dict, length_mismatch="error"):
|
||||
"""
|
||||
Collects V0, V1, ... from the dict (ignoring NestedTensor component keys
|
||||
like V0_0, V1_1), and returns them as a list and count.
|
||||
NestedTensor entries are represented as a list of their components so that
|
||||
V[idx][comp] indexing works as expected.
|
||||
"""
|
||||
base_keys = sorted(
|
||||
[k for k in v_norm_dict.keys() if re.fullmatch(r"V\d+", k)],
|
||||
key=lambda x: int(x[1:])
|
||||
)
|
||||
listt = []
|
||||
for k in base_keys:
|
||||
val = v_norm_dict[k]
|
||||
if getattr(val, "is_nested", False):
|
||||
listt.append(list(val.tensors))
|
||||
else:
|
||||
listt.append(val)
|
||||
return listt, len(listt)
|
||||
|
||||
def get_f_variable(f_dict):
|
||||
"""
|
||||
Collects F0, F1, ... from the dict, stacks them into an F 1D tensor,
|
||||
and returns (F_stacked, F_count).
|
||||
"""
|
||||
sorted_keys = sorted([k for k in f_dict.keys() if k.startswith("F")], key=lambda x: int(x[1:]))
|
||||
ordered_values = []
|
||||
|
||||
for k in sorted_keys:
|
||||
val = f_dict[k]
|
||||
if torch.is_tensor(val):
|
||||
# If it's a tensor, just get the first element if it's a scalar or keep as is if it's already 1D?
|
||||
# Usually F inputs are floats. If they are tensors, we'll try to treat them as values.
|
||||
ordered_values.append(val.flatten()[0] if val.numel() > 0 else torch.tensor(0.0))
|
||||
elif isinstance(val, (int, float)):
|
||||
ordered_values.append(torch.tensor(float(val)))
|
||||
else:
|
||||
ordered_values.append(torch.tensor(0.0))
|
||||
|
||||
if not ordered_values:
|
||||
return None, 0
|
||||
|
||||
try:
|
||||
stacked = torch.stack(ordered_values)
|
||||
return stacked, len(ordered_values)
|
||||
except Exception:
|
||||
return None, len(ordered_values)
|
||||
|
||||
def _collect_reads_only( node, needed_vars, assigned_vars, shadowed_vars):
|
||||
if node is None:
|
||||
return
|
||||
|
||||
node_type = type(node).__name__
|
||||
|
||||
if node_type == 'VariableExpContext':
|
||||
var_name = node.VARIABLE().getText()
|
||||
if var_name in shadowed_vars:
|
||||
return
|
||||
if var_name not in assigned_vars:
|
||||
needed_vars.add(var_name)
|
||||
return
|
||||
|
||||
if node_type == 'FunctionDefContext':
|
||||
return
|
||||
|
||||
if node_type == 'VarDefContext':
|
||||
for expr in node.expr():
|
||||
_collect_reads_only(expr, needed_vars, assigned_vars, shadowed_vars)
|
||||
return
|
||||
|
||||
for i in range(node.getChildCount()):
|
||||
_collect_reads_only(node.getChild(i), needed_vars, assigned_vars, shadowed_vars)
|
||||
|
||||
def _collect_vars_from_node( node, needed_vars, assigned_vars, shadowed_vars):
|
||||
"""Recursively collect variable reads from an AST node"""
|
||||
if node is None:
|
||||
return
|
||||
|
||||
node_type = type(node).__name__
|
||||
|
||||
# Found a variable read
|
||||
if node_type == 'VariableExpContext':
|
||||
var_name = node.VARIABLE().getText()
|
||||
# Skip if shadowed
|
||||
if var_name in shadowed_vars:
|
||||
return
|
||||
if var_name not in assigned_vars:
|
||||
needed_vars.add(var_name)
|
||||
return
|
||||
|
||||
# Skip
|
||||
if node_type == 'FunctionDefContext':
|
||||
return
|
||||
|
||||
# Recursively visit children
|
||||
for i in range(node.getChildCount()):
|
||||
_collect_vars_from_node(node.getChild(i), needed_vars, assigned_vars, shadowed_vars)
|
||||
|
||||
def checkLazyNew(Expression, V, F):
|
||||
tree = None
|
||||
parser = None
|
||||
if isinstance(Expression,str):
|
||||
tree = parse_expr(Expression)
|
||||
parser = tree.parser
|
||||
else:
|
||||
tree = Expression
|
||||
parser = tree.parser
|
||||
|
||||
# Support aliases
|
||||
aliases = {"a": "V0", "b": "V1", "c": "V2", "d": "V3",
|
||||
"w": "F0", "x": "F1", "y": "F2", "z": "F3"}
|
||||
|
||||
assigned_vars = set()
|
||||
needed_vars = set()
|
||||
|
||||
# Process all top-level statements
|
||||
for child in tree.children:
|
||||
if not hasattr(child, 'getRuleIndex'):
|
||||
continue
|
||||
|
||||
rule_name = parser.ruleNames[child.getRuleIndex()] if child.getRuleIndex() < len(parser.ruleNames) else None
|
||||
|
||||
# Process function definitions: scan for reads but ignore writes
|
||||
if rule_name == 'funcDef':
|
||||
func_params = set()
|
||||
if child.paramList():
|
||||
for param in child.paramList().VARIABLE():
|
||||
func_params.add(param.getText())
|
||||
|
||||
_collect_reads_only(child, needed_vars, assigned_vars, func_params)
|
||||
|
||||
# Top-level assignments
|
||||
elif rule_name == 'varDef':
|
||||
var_name = child.VARIABLE().getText()
|
||||
_collect_vars_from_node(child, needed_vars, assigned_vars, set())
|
||||
assigned_vars.add(var_name)
|
||||
|
||||
# Track other top-level statements
|
||||
else:
|
||||
_collect_vars_from_node(child, needed_vars, assigned_vars, set())
|
||||
|
||||
# Normalize variable names through aliases
|
||||
needed = set()
|
||||
for var in needed_vars:
|
||||
norm = aliases.get(var, var)
|
||||
if var == "V":
|
||||
needed.update(V.keys())
|
||||
if var == "F":
|
||||
needed.update(F.keys())
|
||||
# Handle NestedTensor component variables like V1_0 or F2_3 by mapping back to V1/F2
|
||||
comp_match = re.fullmatch(r"([VF]\d+)_(\d+)", norm)
|
||||
if comp_match:
|
||||
needed.add(comp_match.group(1))
|
||||
elif re.fullmatch(r"[VF][0-9]+", norm):
|
||||
needed.add(norm)
|
||||
elif norm.endswith("_d"):
|
||||
# Delta references like V0_d or the aliased a_d still require
|
||||
# their underlying model, even when the bare name is never
|
||||
# referenced elsewhere in the expression.
|
||||
delta_base = aliases.get(norm[:-2], norm[:-2])
|
||||
if re.fullmatch(r"[VF][0-9]+", delta_base):
|
||||
needed.add(delta_base)
|
||||
|
||||
return needed
|
||||
@@ -0,0 +1,228 @@
|
||||
from .helper_functions import generate_dim_variables, parse_expr, as_tensor, get_v_variable, get_f_variable, get_tensor_device, move_to_device, LazyVariableDict, checkLazyNew
|
||||
from .Parser.UnifiedMathVisitor import MathDict, UnifiedMathVisitor
|
||||
import comfy.model_management
|
||||
import torch
|
||||
from collections.abc import Mapping
|
||||
|
||||
|
||||
def calculate_lora_dict_autogrow(Expr, V, F, pbar=None, mapping=None, stack=[], use_compute_device=False):
|
||||
|
||||
"""
|
||||
Calculate a new LORA_MODEL dict[str, Tensor] from math expressions applied
|
||||
over the UNION of keys from all input LoRA dicts, using Autogrow inputs.
|
||||
|
||||
Unlike calculate_patches_autogrow (Model/VAE/CLIP), there is no
|
||||
ModelPatcher/backup involved here - a LORA_MODEL is just raw tensors
|
||||
(lora_up.weight, lora_down.weight, alpha, diff, ...). So V{n} is simply
|
||||
that tensor as-is (zero-filled when a given key is missing from that
|
||||
input), and there is no V{n}_d - these tensors already *are* deltas.
|
||||
"""
|
||||
if V is None: V = {}
|
||||
if F is None: F = {}
|
||||
if mapping is None: mapping = {}
|
||||
|
||||
needed_vars = checkLazyNew(Expr, V, F)
|
||||
needed_v_names = [v_name for v_name in V.keys() if v_name in needed_vars]
|
||||
if not needed_v_names:
|
||||
needed_v_names = [v_name for v_name in V.keys() if V.get(v_name) is not None]
|
||||
|
||||
if not any(V.get(v_name) is not None for v_name in needed_v_names):
|
||||
return {}
|
||||
|
||||
# Collect all unique keys from all inputs, preserving order from first input
|
||||
all_keys_list = []
|
||||
seen_keys = set()
|
||||
for d in V.values():
|
||||
if d is None:
|
||||
continue
|
||||
for key in d.keys():
|
||||
if key not in seen_keys:
|
||||
seen_keys.add(key)
|
||||
all_keys_list.append(key)
|
||||
|
||||
tree = parse_expr(Expr) if isinstance(Expr, str) else Expr
|
||||
|
||||
original_device = None
|
||||
compute_device = comfy.model_management.get_torch_device() if use_compute_device else None
|
||||
|
||||
const_f_vars = {k: val if val is not None else 0.0 for k, val in F.items()}
|
||||
const_f_alias_vars = {
|
||||
alias: (F[target] if F[target] is not None else 0.0)
|
||||
for alias, target in mapping.items()
|
||||
if target in F
|
||||
}
|
||||
mapping_items = tuple(mapping.items())
|
||||
|
||||
input_dicts = {}
|
||||
for v_name in needed_v_names:
|
||||
d = V.get(v_name)
|
||||
if d is None:
|
||||
continue
|
||||
if original_device is None:
|
||||
original_device = get_tensor_device(d)
|
||||
if compute_device is not None and original_device is not None and compute_device != original_device:
|
||||
d = move_to_device(d, compute_device)
|
||||
input_dicts[v_name] = d
|
||||
|
||||
layer_count = len(all_keys_list)
|
||||
|
||||
def make_lazy(value_fn):
|
||||
def lazy_eval():
|
||||
return value_fn()
|
||||
lazy_eval.is_lazy_var = True
|
||||
return lazy_eval
|
||||
|
||||
result = {}
|
||||
|
||||
for layer_idx, key in enumerate(all_keys_list):
|
||||
variables = LazyVariableDict(const_f_vars)
|
||||
variables.update(const_f_alias_vars)
|
||||
|
||||
variables["L"] = float(layer_idx)
|
||||
variables["layer"] = float(layer_idx)
|
||||
variables["LC"] = float(layer_count)
|
||||
variables["layer_count"] = float(layer_count)
|
||||
variables["K"] = key
|
||||
variables["key"] = key
|
||||
|
||||
ref_tensor = None
|
||||
present = {}
|
||||
for v_name in V.keys():
|
||||
d = input_dicts.get(v_name)
|
||||
if d is None:
|
||||
continue
|
||||
t = d.get(key, None)
|
||||
if t is not None:
|
||||
present[v_name] = t
|
||||
if ref_tensor is None:
|
||||
ref_tensor = t
|
||||
|
||||
if ref_tensor is None:
|
||||
continue
|
||||
|
||||
for v_name in V.keys():
|
||||
if v_name in present:
|
||||
t = present[v_name]
|
||||
variables[v_name] = make_lazy(lambda t=t: t)
|
||||
else:
|
||||
variables[v_name] = make_lazy(lambda rt=ref_tensor: torch.zeros_like(rt))
|
||||
|
||||
# Populate aliases for V (a, b, c, d)
|
||||
for alias, target in mapping_items:
|
||||
if target in variables:
|
||||
variables[alias] = variables[target]
|
||||
elif target.startswith("V") and target[1:].isdigit():
|
||||
variables[alias] = make_lazy(lambda rt=ref_tensor: torch.zeros_like(rt))
|
||||
|
||||
v_cache = [None]
|
||||
|
||||
def lazy_v_stack():
|
||||
if v_cache[0] is None:
|
||||
v_cache[0] = get_v_variable(variables)
|
||||
return v_cache[0][0]
|
||||
|
||||
lazy_v_stack.is_lazy_var = True
|
||||
|
||||
def lazy_v_count():
|
||||
if v_cache[0] is None:
|
||||
v_cache[0] = get_v_variable(variables)
|
||||
return float(v_cache[0][1])
|
||||
|
||||
lazy_v_count.is_lazy_var = True
|
||||
|
||||
variables["V"] = lazy_v_stack
|
||||
variables["Vcnt"] = lazy_v_count
|
||||
variables["V_count"] = lazy_v_count
|
||||
|
||||
f_stacked, f_cnt = get_f_variable(F)
|
||||
if f_stacked is not None:
|
||||
variables["F"] = f_stacked
|
||||
variables["Fcnt"] = float(f_cnt)
|
||||
variables["F_count"] = float(f_cnt)
|
||||
|
||||
variables = variables | generate_dim_variables(ref_tensor)
|
||||
|
||||
visitor = UnifiedMathVisitor(variables, ref_tensor.shape, state_storage=stack)
|
||||
res = visitor.visit(tree)
|
||||
res = as_tensor(res, ref_tensor.shape)
|
||||
|
||||
if compute_device is not None and original_device is not None and original_device != compute_device:
|
||||
res = res.to(device=original_device)
|
||||
|
||||
result[key] = res
|
||||
|
||||
if pbar is not None:
|
||||
pbar.update(1)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def calculate_lora_dict_dict_mode(Expr, V, F, pbar=None, mapping=None, stack=[], use_compute_device=False):
|
||||
if V is None:
|
||||
V = {}
|
||||
if F is None:
|
||||
F = {}
|
||||
if mapping is None:
|
||||
mapping = {}
|
||||
|
||||
needed_vars = checkLazyNew(Expr, V, F)
|
||||
needed_v_names = [name for name in V if name in needed_vars]
|
||||
if "V0" in V and "V0" not in needed_v_names:
|
||||
needed_v_names.append("V0")
|
||||
if not needed_v_names:
|
||||
needed_v_names = [name for name, value in V.items() if value is not None]
|
||||
|
||||
input_dicts = {name: V[name] for name in needed_v_names if V.get(name) is not None}
|
||||
if not input_dicts:
|
||||
return {}
|
||||
|
||||
all_keys = []
|
||||
seen_keys = set()
|
||||
for value in input_dicts.values():
|
||||
for key in value:
|
||||
if key not in seen_keys:
|
||||
seen_keys.add(key)
|
||||
all_keys.append(key)
|
||||
|
||||
compute_device = comfy.model_management.get_torch_device() if use_compute_device else None
|
||||
original_device = get_tensor_device(next(iter(input_dicts.values())))
|
||||
if compute_device is not None and original_device is not None and compute_device != original_device:
|
||||
input_dicts = {name: move_to_device(value, compute_device) for name, value in input_dicts.items()}
|
||||
|
||||
references = {}
|
||||
for key in all_keys:
|
||||
for value in input_dicts.values():
|
||||
tensor = value.get(key)
|
||||
if tensor is not None:
|
||||
references[key] = tensor
|
||||
break
|
||||
|
||||
def weight_dict(name):
|
||||
value = input_dicts.get(name, {})
|
||||
return MathDict({key: value.get(key, torch.zeros_like(references[key])) for key in all_keys})
|
||||
|
||||
variables = LazyVariableDict({name: value if value is not None else 0.0 for name, value in F.items()})
|
||||
for alias, target in mapping.items():
|
||||
if target in F:
|
||||
variables[alias] = F[target] if F[target] is not None else 0.0
|
||||
for name in needed_v_names:
|
||||
variables[name] = weight_dict(name)
|
||||
for alias, target in mapping.items():
|
||||
if target in variables:
|
||||
variables[alias] = variables[target]
|
||||
|
||||
visitor_device = compute_device if compute_device is not None else next(iter(references.values())).device
|
||||
result = UnifiedMathVisitor(variables, device=visitor_device, state_storage=stack).visit(parse_expr(Expr) if isinstance(Expr, str) else Expr)
|
||||
if not isinstance(result, Mapping):
|
||||
if isinstance(result, (float, int)):
|
||||
result = MathDict({key: torch.full_like(reference, result) for key, reference in references.items()})
|
||||
else:
|
||||
raise ValueError("Dictionary mode expressions must return a dictionary")
|
||||
if tuple(result) != tuple(all_keys):
|
||||
raise ValueError("Dictionary mode expressions must preserve all LoRA keys")
|
||||
|
||||
if compute_device is not None and original_device is not None and original_device != compute_device:
|
||||
result = {key: value.to(device=original_device) for key, value in result.items()}
|
||||
if pbar is not None:
|
||||
pbar.update(len(result))
|
||||
return dict(result)
|
||||
@@ -0,0 +1,541 @@
|
||||
from .helper_functions import generate_dim_variables, parse_expr, as_tensor, get_v_variable, get_f_variable, get_tensor_device, move_to_device, LazyVariableDict, checkLazyNew
|
||||
from .Parser.UnifiedMathVisitor import MathDict, ChangeTrackingDict, UnifiedMathVisitor
|
||||
import comfy
|
||||
import comfy.model_management
|
||||
import comfy.model_patcher
|
||||
import comfy.lora
|
||||
import torch
|
||||
|
||||
|
||||
def _get_model_patcher(obj):
|
||||
# A MODEL input *is* a ModelPatcher already - not something wrapping one.
|
||||
if isinstance(obj, comfy.model_patcher.ModelPatcher):
|
||||
return obj
|
||||
patcher = getattr(obj, "patcher", None)
|
||||
if patcher is None and hasattr(obj, "model"):
|
||||
patcher = getattr(obj.model, "patcher", None)
|
||||
return patcher
|
||||
|
||||
|
||||
def get_effective_state_dict(obj):
|
||||
if hasattr(obj, "patcher") and hasattr(obj.patcher, "model_state_dict"):
|
||||
return obj.patcher.model_state_dict()
|
||||
if hasattr(obj, "model_state_dict"):
|
||||
return obj.model_state_dict()
|
||||
if hasattr(obj, "model") and hasattr(obj.model, "patcher") and hasattr(obj.model.patcher, "model_state_dict"):
|
||||
return obj.model.patcher.model_state_dict()
|
||||
if hasattr(obj, "model") and hasattr(obj.model, "model_state_dict"):
|
||||
return obj.model.model_state_dict()
|
||||
if hasattr(obj, "model") and hasattr(obj.model, "state_dict"):
|
||||
return obj.model.state_dict()
|
||||
if hasattr(obj, "state_dict"):
|
||||
return obj.state_dict()
|
||||
return None
|
||||
|
||||
|
||||
def get_effective_weight(obj, key):
|
||||
patcher = _get_model_patcher(obj)
|
||||
if patcher is not None and hasattr(patcher, "patch_weight_to_device"):
|
||||
try:
|
||||
return patcher.patch_weight_to_device(key, return_weight=True)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if hasattr(obj, "patch_weight_to_device"):
|
||||
try:
|
||||
return obj.patch_weight_to_device(key, return_weight=True)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
sd = get_effective_state_dict(obj)
|
||||
if sd is not None:
|
||||
return sd.get(key, None)
|
||||
return None
|
||||
|
||||
|
||||
def get_base_weight(obj, key):
|
||||
"""True pre-patch weight for `key`, regardless of whether the patcher
|
||||
currently has this key's patches baked into the live model (`backup`
|
||||
holds the pristine value whenever that happened; `use_ejected` does not
|
||||
undo weight patches, only injections, so it can't be relied on here)."""
|
||||
patcher = _get_model_patcher(obj)
|
||||
if patcher is not None:
|
||||
backup = getattr(patcher, "backup", None)
|
||||
if backup is not None:
|
||||
bk = backup.get(key, None)
|
||||
if bk is not None:
|
||||
return bk.weight
|
||||
model = getattr(patcher, "model", None)
|
||||
if model is not None:
|
||||
try:
|
||||
weight, _, _ = comfy.model_patcher.get_key_weight(model, key)
|
||||
return weight
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
sd = get_effective_state_dict(obj)
|
||||
if sd is not None:
|
||||
return sd.get(key, None)
|
||||
return None
|
||||
|
||||
|
||||
def get_patched_weight(obj, key, base_weight):
|
||||
"""Fully patched weight for `key`, computed fresh from `base_weight` so
|
||||
the result never depends on whether the model happens to be baked
|
||||
in-place already (reusing an already-baked weight as input would
|
||||
double-apply patches whose strength_model != 1.0)."""
|
||||
if base_weight is None:
|
||||
return None
|
||||
patcher = _get_model_patcher(obj)
|
||||
if patcher is not None:
|
||||
patches = getattr(patcher, "patches", None)
|
||||
if patches is not None and key in patches:
|
||||
try:
|
||||
return comfy.lora.calculate_weight(patches[key], base_weight.clone(), key)
|
||||
except Exception:
|
||||
pass
|
||||
return base_weight
|
||||
|
||||
|
||||
def coerce_like(weight, reference, device=None):
|
||||
if isinstance(weight, torch.Tensor) and isinstance(reference, torch.Tensor):
|
||||
target_device = device if device is not None else reference.device
|
||||
if weight.device != target_device or weight.dtype != reference.dtype:
|
||||
return weight.to(device=target_device, dtype=reference.dtype)
|
||||
return weight
|
||||
|
||||
|
||||
def calculate_patches(Model, a, b=None, c=None, d=None, w=0.0, x=0.0, y=0.0, z=0.0, use_compute_device=False):
|
||||
"""Legacy calculate_patches for backward compatibility."""
|
||||
return calculate_patches_autogrow(Model, V={"V0": a, "V1": b, "V2": c, "V3": d}, F={"F0": w, "F1": x, "F2": y, "F3": z}, pbar=None, mapping={"a": "V0", "b": "V1", "c": "V2", "d": "V3", "w": "F0", "x": "F1", "y": "F2", "z": "F3"}, use_compute_device=use_compute_device)
|
||||
|
||||
def calculate_patches_autogrow(Expr, V, F, pbar=None, mapping=None, stack=[], use_compute_device=False):
|
||||
"""
|
||||
Calculate patches for model-like objects (Model, VAE, CLIP) using Autogrow inputs.
|
||||
Iterates over the UNION of keys from all input models to support merging disjoint architectures/patches.
|
||||
|
||||
In autogrow mode:
|
||||
- The expression is evaluated once PER KEY (per-layer iteration)
|
||||
- V0, V1, V2, V3 contain only the weight tensor for the current key (not all keys)
|
||||
- Expression must return a TENSOR (the patch for this key)
|
||||
- Returning a dict will raise an error; use dict_mode=True instead
|
||||
|
||||
Performance note:
|
||||
- If expression applies the same operation to all keys (e.g., V0 *= 2), it will iterate
|
||||
through each key in Python, making it O(num_keys) in Python overhead
|
||||
- For efficiency with bulk operations on multiple keys, use dict_mode=True instead
|
||||
- dict_mode evaluates the expression once with V0 as a complete dict, allowing batch operations
|
||||
|
||||
Args:
|
||||
Expr: The math expression string or parse tree.
|
||||
V: Dictionary of input models (Autogrow Input).
|
||||
F: Dictionary of input floats (Autogrow Float).
|
||||
mapping: Optional dict mapping legacy alias names to V keys (e.g. {"a": "V0"}).
|
||||
"""
|
||||
if V is None: V = {}
|
||||
if F is None: F = {}
|
||||
if mapping is None: mapping = {}
|
||||
|
||||
needed_vars = checkLazyNew(Expr, V, F)
|
||||
needed_v_names = [v_name for v_name in V.keys() if v_name in needed_vars]
|
||||
if not needed_v_names:
|
||||
needed_v_names = [v_name for v_name in V.keys() if V.get(v_name) is not None]
|
||||
|
||||
# Collect all unique keys from all models, preserving order from first model
|
||||
all_keys_list = [] # Preserves order
|
||||
seen_keys = set() # Fast O(1) duplicate checking
|
||||
|
||||
models = [v for v in V.values() if v is not None]
|
||||
if not models:
|
||||
return {}
|
||||
|
||||
for m in models:
|
||||
sd = get_effective_state_dict(m)
|
||||
if sd is None:
|
||||
sd_keys = []
|
||||
else:
|
||||
sd_keys = sd.keys()
|
||||
|
||||
for key in sd_keys:
|
||||
if key not in seen_keys:
|
||||
seen_keys.add(key)
|
||||
all_keys_list.append(key)
|
||||
|
||||
# Function to get weight from a valid object
|
||||
def get_weight(obj, key):
|
||||
return get_effective_weight(obj, key)
|
||||
|
||||
tree = None
|
||||
if isinstance(Expr,str):
|
||||
tree = parse_expr(Expr)
|
||||
else:
|
||||
tree = Expr
|
||||
patches = {}
|
||||
original_device = None
|
||||
compute_device = comfy.model_management.get_torch_device() if use_compute_device else None
|
||||
needs_deltas = not (isinstance(Expr, str) and "_d" not in Expr)
|
||||
const_f_vars = {k: val if val is not None else 0.0 for k, val in F.items()}
|
||||
const_f_alias_vars = {
|
||||
alias: (F[target] if F[target] is not None else 0.0)
|
||||
for alias, target in mapping.items()
|
||||
if target in F
|
||||
}
|
||||
mapping_items = tuple(mapping.items())
|
||||
|
||||
current_state_dicts = {}
|
||||
for v_name in needed_v_names:
|
||||
v_val = V.get(v_name)
|
||||
if v_val is None:
|
||||
continue
|
||||
current_sd = get_effective_state_dict(v_val)
|
||||
if current_sd is None:
|
||||
continue
|
||||
if original_device is None:
|
||||
original_device = get_tensor_device(current_sd)
|
||||
if compute_device is not None and original_device is not None and compute_device != original_device:
|
||||
current_sd = move_to_device(current_sd, compute_device)
|
||||
current_state_dicts[v_name] = current_sd
|
||||
|
||||
# Progress bar if possible (comfy.utils.ProgressBar might assume unthreaded?)
|
||||
# Just skip for utility or use if substantial.
|
||||
layer_count = len(all_keys_list)
|
||||
|
||||
def make_lazy(value_fn):
|
||||
def lazy_eval():
|
||||
return value_fn()
|
||||
lazy_eval.is_lazy_var = True
|
||||
return lazy_eval
|
||||
|
||||
for layer_idx, key in enumerate(all_keys_list):
|
||||
|
||||
variables = LazyVariableDict(const_f_vars)
|
||||
# Also populate mapped aliases for F (w, x, y, z)
|
||||
variables.update(const_f_alias_vars)
|
||||
|
||||
variables["L"] = float(layer_idx)
|
||||
variables["layer"] = float(layer_idx)
|
||||
variables["LC"] = float(layer_count)
|
||||
variables["layer_count"] = float(layer_count)
|
||||
variables["K"] = key
|
||||
variables["key"] = key
|
||||
|
||||
# Inject weights for this key from V models
|
||||
valid_key = False
|
||||
ref_tensor = None
|
||||
present_weights = {}
|
||||
present_bases = {}
|
||||
|
||||
for v_name in V.keys():
|
||||
current_sd = current_state_dicts.get(v_name)
|
||||
if current_sd is None:
|
||||
continue
|
||||
raw_tensor = current_sd.get(key, None)
|
||||
if raw_tensor is not None:
|
||||
v_val = V.get(v_name)
|
||||
base_tensor = get_base_weight(v_val, key) if v_val is not None else None
|
||||
if base_tensor is None:
|
||||
base_tensor = raw_tensor
|
||||
base_tensor = coerce_like(base_tensor, raw_tensor, device=compute_device)
|
||||
patched_tensor = get_patched_weight(v_val, key, base_tensor) if v_val is not None else None
|
||||
if patched_tensor is None:
|
||||
patched_tensor = raw_tensor
|
||||
patched_tensor = coerce_like(patched_tensor, raw_tensor, device=compute_device)
|
||||
present_weights[v_name] = patched_tensor
|
||||
if needs_deltas:
|
||||
present_bases[v_name] = base_tensor
|
||||
if ref_tensor is None:
|
||||
ref_tensor = patched_tensor
|
||||
valid_key = True
|
||||
|
||||
if not valid_key:
|
||||
continue
|
||||
|
||||
# Find reference shape
|
||||
if ref_tensor is None:
|
||||
continue # Should not happen if valid_key is true
|
||||
|
||||
# Fill missing models with lazy values so V[n] keeps working
|
||||
v_cache = [None]
|
||||
|
||||
def lazy_v_stack():
|
||||
if v_cache[0] is None:
|
||||
v_cache[0] = get_v_variable(variables)
|
||||
return v_cache[0][0]
|
||||
|
||||
lazy_v_stack.is_lazy_var = True
|
||||
|
||||
def lazy_v_count():
|
||||
if v_cache[0] is None:
|
||||
v_cache[0] = get_v_variable(variables)
|
||||
return float(v_cache[0][1])
|
||||
|
||||
lazy_v_count.is_lazy_var = True
|
||||
|
||||
for v_name in V.keys():
|
||||
if v_name in present_weights:
|
||||
w_tensor = present_weights[v_name]
|
||||
variables[v_name] = make_lazy(lambda wt=w_tensor: wt)
|
||||
if needs_deltas:
|
||||
base_tensor = present_bases.get(v_name, w_tensor)
|
||||
variables[f"{v_name}_d"] = make_lazy(lambda wt=w_tensor, bt=base_tensor: wt - bt)
|
||||
else:
|
||||
variables[v_name] = make_lazy(lambda rt=ref_tensor: torch.zeros_like(rt))
|
||||
if needs_deltas:
|
||||
variables[f"{v_name}_d"] = make_lazy(lambda rt=ref_tensor: torch.zeros_like(rt))
|
||||
|
||||
# Populate aliases for V (a, b, c, d)
|
||||
for alias, target in mapping_items:
|
||||
if target in variables:
|
||||
variables[alias] = variables[target]
|
||||
elif target in V:
|
||||
# V key exists in input dict but this specific layer key is missing
|
||||
variables[alias] = make_lazy(lambda rt=ref_tensor: torch.zeros_like(rt))
|
||||
else:
|
||||
# Target doesn't exist at all (e.g., V1 not provided)
|
||||
# Check if it's a V-key pattern and zero-fill
|
||||
if target.startswith("V") and target[1:].isdigit():
|
||||
variables[alias] = make_lazy(lambda rt=ref_tensor: torch.zeros_like(rt))
|
||||
|
||||
variables["V"] = lazy_v_stack
|
||||
variables["Vcnt"] = lazy_v_count
|
||||
variables["V_count"] = lazy_v_count
|
||||
|
||||
f_stacked, f_cnt = get_f_variable(F)
|
||||
if f_stacked is not None:
|
||||
variables["F"] = f_stacked
|
||||
variables["Fcnt"] = float(f_cnt)
|
||||
variables["F_count"] = float(f_cnt)
|
||||
|
||||
variables = variables | generate_dim_variables(ref_tensor)
|
||||
|
||||
# Execute math
|
||||
# Store original V0 before visiting, since visitor may modify it
|
||||
original_v0 = variables.get("V0")
|
||||
if original_v0 is not None:
|
||||
# If it's a tensor, clone it; if it's a dict, copy its items
|
||||
if isinstance(original_v0, torch.Tensor):
|
||||
original_v0 = original_v0.clone()
|
||||
elif isinstance(original_v0, dict):
|
||||
original_v0 = MathDict({k: (v.clone() if isinstance(v, torch.Tensor) else v) for k, v in original_v0.items()})
|
||||
|
||||
visitor = UnifiedMathVisitor(variables, ref_tensor.shape,state_storage=stack)
|
||||
res = visitor.visit(tree)
|
||||
|
||||
# In autogrow mode, expression must return a single tensor (patch for this key)
|
||||
# If a dict is returned, that's an error - use dict_mode instead
|
||||
if isinstance(res, dict) and not isinstance(res, torch.Tensor):
|
||||
raise ValueError(f"Autogrow mode expects tensor output, got dict. Use dict_mode=True for dict output.")
|
||||
|
||||
res = as_tensor(res, ref_tensor.shape)
|
||||
|
||||
# Extract original for this specific key
|
||||
original = None
|
||||
if original_v0 is not None:
|
||||
if isinstance(original_v0, torch.Tensor):
|
||||
original = original_v0
|
||||
elif isinstance(original_v0, dict):
|
||||
# Should not happen in autogrow mode - V0 is always a tensor
|
||||
original = original_v0.get(key)
|
||||
|
||||
if original is None:
|
||||
original = torch.zeros_like(res)
|
||||
|
||||
diff = res - original
|
||||
|
||||
# Clean up: don't store zero patches
|
||||
if not torch.all(diff == 0):
|
||||
patches[key] = (diff,)
|
||||
|
||||
if pbar is not None:
|
||||
pbar.update(1)
|
||||
|
||||
if compute_device is not None and original_device is not None and original_device != compute_device:
|
||||
patches = {
|
||||
key: ((value[0].to(device=original_device),) if isinstance(value, tuple) and value and torch.is_tensor(value[0]) else value)
|
||||
for key, value in patches.items()
|
||||
}
|
||||
|
||||
return patches
|
||||
|
||||
|
||||
def calculate_patches_dict_mode(Expr, V, F, pbar=None, mapping=None, stack=[], use_compute_device=False):
|
||||
"""
|
||||
Calculate patches for model-like objects (Model, VAE, CLIP) using Dictionary mode.
|
||||
Evaluates the expression once for all keys simultaneously.
|
||||
|
||||
In dict_mode:
|
||||
- The expression is evaluated ONCE (not per-key)
|
||||
- V0, V1, V2, V3 are MathDict objects containing all weights across all keys
|
||||
- Expression can modify individual keys: V0[key] *= 2
|
||||
- Expression can apply bulk operations: V0 *= 2
|
||||
- Expression must return a DICT/MATHDICT with exactly the same keys as the input models
|
||||
- The returned dict must preserve the original key structure (cannot add/remove keys)
|
||||
|
||||
Performance:
|
||||
- More efficient than autogrow_mode for bulk operations since evaluation is done once
|
||||
- Suitable for complex transformations that need access to all keys simultaneously
|
||||
|
||||
Args:
|
||||
Expr: The math expression string or parse tree.
|
||||
V: Dictionary of input models (Dict Mode Input).
|
||||
F: Dictionary of input floats (Dict Mode Float).
|
||||
mapping: Optional dict mapping legacy alias names to V keys.
|
||||
"""
|
||||
if V is None:
|
||||
V = {}
|
||||
if F is None:
|
||||
F = {}
|
||||
if mapping is None:
|
||||
mapping = {}
|
||||
|
||||
needed_vars = checkLazyNew(Expr, V, F)
|
||||
needed_v_names = [name for name in V if name in needed_vars]
|
||||
if "V0" in V and "V0" not in needed_v_names:
|
||||
needed_v_names.append("V0")
|
||||
if not needed_v_names:
|
||||
needed_v_names = [name for name, value in V.items() if value is not None]
|
||||
|
||||
models = [value for value in V.values() if value is not None]
|
||||
if not models:
|
||||
return {}
|
||||
|
||||
all_keys = []
|
||||
seen_keys = set()
|
||||
for model in models:
|
||||
state_dict = get_effective_state_dict(model)
|
||||
if state_dict is None:
|
||||
continue
|
||||
for key in state_dict:
|
||||
if key not in seen_keys:
|
||||
seen_keys.add(key)
|
||||
all_keys.append(key)
|
||||
|
||||
tree = parse_expr(Expr) if isinstance(Expr, str) else Expr
|
||||
compute_device = comfy.model_management.get_torch_device() if use_compute_device else None
|
||||
original_device = None
|
||||
state_dicts = {}
|
||||
for name in needed_v_names:
|
||||
value = V.get(name)
|
||||
if value is None:
|
||||
continue
|
||||
state_dict = get_effective_state_dict(value)
|
||||
if state_dict is None:
|
||||
continue
|
||||
if original_device is None:
|
||||
original_device = get_tensor_device(state_dict)
|
||||
if compute_device is not None and original_device is not None and compute_device != original_device:
|
||||
state_dict = move_to_device(state_dict, compute_device)
|
||||
state_dicts[name] = state_dict
|
||||
|
||||
if not state_dicts:
|
||||
return {}
|
||||
|
||||
all_keys = [key for key in all_keys if any(key in state_dict for state_dict in state_dicts.values())]
|
||||
|
||||
# Mirrors calculate_patches_autogrow's needs_deltas: a plain substring
|
||||
# check on the expression text, since checkLazyNew's returned variable
|
||||
# names never carry "_d" suffixes (only bare V{n}/alias names), so a
|
||||
# membership test against needed_vars can never match here.
|
||||
needs_deltas = not (isinstance(Expr, str) and "_d" not in Expr)
|
||||
delta_names = set(needed_v_names) if needs_deltas else set()
|
||||
weights = {name: {} for name in needed_v_names}
|
||||
bases = {name: {} for name in delta_names}
|
||||
references = {}
|
||||
|
||||
for key in all_keys:
|
||||
for name in needed_v_names:
|
||||
state_dict = state_dicts.get(name)
|
||||
raw_weight = state_dict.get(key) if state_dict is not None else None
|
||||
if raw_weight is None:
|
||||
continue
|
||||
model = V[name]
|
||||
base_weight = get_base_weight(model, key)
|
||||
if base_weight is None:
|
||||
base_weight = raw_weight
|
||||
base_weight = coerce_like(base_weight, raw_weight, device=compute_device)
|
||||
patched_weight = get_patched_weight(model, key, base_weight)
|
||||
if patched_weight is None:
|
||||
patched_weight = raw_weight
|
||||
patched_weight = coerce_like(patched_weight, raw_weight, device=compute_device)
|
||||
weights[name][key] = patched_weight
|
||||
if name in delta_names:
|
||||
bases[name][key] = base_weight
|
||||
if key not in references:
|
||||
references[key] = patched_weight
|
||||
|
||||
if not references:
|
||||
return {}
|
||||
|
||||
def weight_dict(name):
|
||||
"""Create a ChangeTrackingDict that monitors changes to weights"""
|
||||
tracking_dict = ChangeTrackingDict({key: weights[name].get(key, torch.zeros_like(references[key])) for key in all_keys})
|
||||
return tracking_dict
|
||||
|
||||
variables = LazyVariableDict({name: value if value is not None else 0.0 for name, value in F.items()})
|
||||
for alias, target in mapping.items():
|
||||
if target in F:
|
||||
variables[alias] = F[target] if F[target] is not None else 0.0
|
||||
|
||||
for name in needed_v_names:
|
||||
variables[name] = weight_dict(name)
|
||||
|
||||
for alias, target in mapping.items():
|
||||
if target in variables:
|
||||
variables[alias] = variables[target]
|
||||
|
||||
for name in delta_names:
|
||||
def make_delta_dict(name=name):
|
||||
return MathDict({
|
||||
key: weights[name][key] - bases[name][key] if key in weights[name] else torch.zeros_like(references[key])
|
||||
for key in all_keys
|
||||
})
|
||||
|
||||
make_delta_dict.is_lazy_var = True
|
||||
variables[f"{name}_d"] = make_delta_dict
|
||||
|
||||
visitor_device = compute_device if compute_device is not None else next(iter(references.values())).device
|
||||
# Store a copy of V0 BEFORE visiting, since the visitor may modify it
|
||||
v0_dict = variables.get("V0", MathDict({key: torch.zeros_like(reference) for key, reference in references.items()}))
|
||||
originals = MathDict({k: (v.clone() if isinstance(v, torch.Tensor) else v) for k, v in v0_dict.items()})
|
||||
result = UnifiedMathVisitor(variables, device=visitor_device, state_storage=stack).visit(tree)
|
||||
if not isinstance(result, (MathDict, dict)):
|
||||
if isinstance(result, (float, int)):
|
||||
result = MathDict({key: torch.full_like(reference, result) for key, reference in references.items()})
|
||||
else:
|
||||
raise ValueError("Dictionary mode expressions must return a dictionary")
|
||||
|
||||
if tuple(result) != tuple(all_keys):
|
||||
raise ValueError("Dictionary mode expressions must preserve all model keys")
|
||||
|
||||
patches = {}
|
||||
|
||||
# Optimization: if result is a ChangeTrackingDict, only process changed keys
|
||||
changed_keys_set = None
|
||||
if isinstance(result, ChangeTrackingDict):
|
||||
changed_keys_set = result.get_changed_keys()
|
||||
keys_to_process = changed_keys_set
|
||||
else:
|
||||
# Fallback: process all keys
|
||||
keys_to_process = all_keys
|
||||
|
||||
for key in keys_to_process:
|
||||
value = result.get(key)
|
||||
if value is None:
|
||||
# Key was removed or doesn't exist
|
||||
if pbar is not None:
|
||||
pbar.update(1)
|
||||
continue
|
||||
|
||||
if not torch.is_tensor(value):
|
||||
value = torch.full_like(references[key], value)
|
||||
|
||||
diff = value - originals[key]
|
||||
if not torch.all(diff == 0):
|
||||
patches[key] = (diff,)
|
||||
|
||||
if pbar is not None:
|
||||
pbar.update(1)
|
||||
|
||||
if compute_device is not None and original_device is not None and original_device != compute_device:
|
||||
patches = {key: (value[0].to(device=original_device),) for key, value in patches.items()}
|
||||
return patches
|
||||
@@ -0,0 +1,188 @@
|
||||
from .SigmasMathNode import SigmasMathNode
|
||||
from .GuiderMathNode import GuiderMathNode
|
||||
from .deprecated.NoiseMathNode import NoiseMathNodeOLD
|
||||
from .deprecated.AudioMathNode import AudioMathNodeOLD
|
||||
from .deprecated.ConditioningMathNode import ConditioningMathNodeOLD
|
||||
from .deprecated.ImageMathNode import ImageMathNodeOLD
|
||||
from .deprecated.LatentMathNode import LatentMathNodeOLD
|
||||
from .deprecated.MaskMathNode import MaskMathNodeOLD
|
||||
from .deprecated.ModelMathNode import ModelMathNodeOLD
|
||||
from .deprecated.VaeMathNode import VAEMathNodeOLD
|
||||
from .deprecated.ClipMathNode import CLIPMathNodeOLD
|
||||
from .deprecated.VideoMathNode import VideoMathNodeOLD
|
||||
from .deprecated.FloatMathNode import FloatMathNodeOLD
|
||||
|
||||
from .FloatMathNode import FloatMathNode
|
||||
from .ConditioningMathNode import ConditioningMathNode
|
||||
from .LatentMathNode import LatentMathNode
|
||||
from .ImageMathNode import ImageMathNode
|
||||
from .MaskMathNode import MaskMathNode
|
||||
from .VideoMathNode import VideoMathNode
|
||||
from .ModelMathNode import ModelMathNode
|
||||
from .VaeMathNode import VAEMathNode
|
||||
from .ClipMathNode import CLIPMathNode
|
||||
from .LoraMathNode import LoraMathNode
|
||||
from .SpectrogramToAudioNode import SpectrogramToAudio
|
||||
from .AudioToSpectrogramNode import AudioToSpectrogram
|
||||
|
||||
from .NoiseMathNode import NoiseMathNode
|
||||
from .AudioMathNode import AudioMathNode
|
||||
from .StringMathNode import StringMathNode
|
||||
from .SelectiveGuiderMathNode import SelectiveGuiderMathNode
|
||||
from .StackUnBatcher import StackUnBatcher
|
||||
|
||||
from .ScriptTextWindow import ScriptTextInput
|
||||
from .BatchLoraNode import BatchLoraNode
|
||||
from .BatchLoraHooksNode import BatchLoraHooksNode
|
||||
|
||||
from comfy_api.latest import ComfyExtension, io
|
||||
|
||||
|
||||
class IntToFloatNode(io.ComfyNode):
|
||||
"""
|
||||
Converts int to float.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def define_schema(cls) -> io.Schema:
|
||||
""" """
|
||||
return io.Schema(
|
||||
node_id="mrmth_IntToFloat",
|
||||
display_name="Int -> Float",
|
||||
category="More math/converters",
|
||||
inputs=[
|
||||
io.Int.Input(id="value", default=0),
|
||||
],
|
||||
outputs=[
|
||||
io.Float.Output(),
|
||||
],
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def execute(cls, value):
|
||||
return (float(value),)
|
||||
|
||||
|
||||
class FloatToIntNode(io.ComfyNode):
|
||||
"""
|
||||
Converts float to int.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def define_schema(cls) -> io.Schema:
|
||||
""" """
|
||||
return io.Schema(
|
||||
node_id="mrmth_FloatToInt",
|
||||
category="More math/converters",
|
||||
display_name="Float -> Int",
|
||||
inputs=[
|
||||
io.Float.Input(id="value", default=0.0),
|
||||
],
|
||||
outputs=[
|
||||
io.Int.Output(),
|
||||
],
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def execute(cls, value):
|
||||
return (int(value),)
|
||||
|
||||
class BoolToIntNode(io.ComfyNode):
|
||||
"""
|
||||
Converts bool to int.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def define_schema(cls) -> io.Schema:
|
||||
""" """
|
||||
return io.Schema(
|
||||
node_id="mrmth_BoolToInt",
|
||||
display_name="Bool -> Int",
|
||||
category="More math/converters",
|
||||
inputs=[
|
||||
io.Boolean.Input(id="value", default=0),
|
||||
],
|
||||
outputs=[
|
||||
io.Int.Output(),
|
||||
],
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def execute(cls, value):
|
||||
return (int(value),)
|
||||
|
||||
|
||||
class IntToBoolNode(io.ComfyNode):
|
||||
"""
|
||||
Converts int to bool.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def define_schema(cls) -> io.Schema:
|
||||
""" """
|
||||
return io.Schema(
|
||||
node_id="mrmth_IntToBool",
|
||||
category="More math/converters",
|
||||
display_name="Int -> bool",
|
||||
inputs=[
|
||||
io.Int.Input(id="value", default=0.0),
|
||||
],
|
||||
outputs=[
|
||||
io.Boolean.Output(),
|
||||
],
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def execute(cls, value):
|
||||
return (bool(value),)
|
||||
|
||||
|
||||
class MoreMathExtension(ComfyExtension):
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
async def get_node_list(self) -> list[type[io.ComfyNode]]:
|
||||
return [
|
||||
ConditioningMathNode,
|
||||
ConditioningMathNodeOLD,
|
||||
ModelMathNode,
|
||||
ModelMathNodeOLD,
|
||||
CLIPMathNode,
|
||||
CLIPMathNodeOLD,
|
||||
VAEMathNode,
|
||||
VAEMathNodeOLD,
|
||||
LoraMathNode,
|
||||
LatentMathNode,
|
||||
LatentMathNodeOLD,
|
||||
ImageMathNode,
|
||||
ImageMathNodeOLD,
|
||||
MaskMathNode,
|
||||
MaskMathNodeOLD,
|
||||
FloatMathNode,
|
||||
FloatMathNodeOLD,
|
||||
NoiseMathNodeOLD,
|
||||
IntToFloatNode,
|
||||
FloatToIntNode,
|
||||
BoolToIntNode,
|
||||
IntToBoolNode,
|
||||
AudioMathNodeOLD,
|
||||
VideoMathNode,
|
||||
VideoMathNodeOLD,
|
||||
AudioToSpectrogram,
|
||||
SpectrogramToAudio,
|
||||
SigmasMathNode,
|
||||
GuiderMathNode,
|
||||
NoiseMathNode,
|
||||
AudioMathNode,
|
||||
StringMathNode,
|
||||
ScriptTextInput,
|
||||
SelectiveGuiderMathNode,
|
||||
StackUnBatcher,
|
||||
BatchLoraNode,
|
||||
BatchLoraHooksNode
|
||||
]
|
||||
|
||||
|
||||
async def comfy_entrypoint() -> MoreMathExtension:
|
||||
return MoreMathExtension()
|
||||
+7
-3
@@ -4,14 +4,15 @@ build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "more_math"
|
||||
version = "0.0.2"
|
||||
description = "Adds math nodes for numbers and types which do not need it."
|
||||
version = "1.1.0"
|
||||
tooltip = "Adds math nodes for numbers and types which do not need it."
|
||||
authors = [
|
||||
{name = "Daniel Martinek", email = "danda.martinek@gmail.com"}
|
||||
]
|
||||
readme = "README.md"
|
||||
license = {text = "GNU General Public License v3"}
|
||||
classifiers = []
|
||||
description = "Adds math nodes for FLOAT, STRING, CONDITIONING, LATENT, IMAGE, MASK, NOISE, AUDIO, VIDEO, MODEL, CLIP, VAE, SIGMAS, and GUIDER types and allows usage of math expressions with large number of functions and variables, not limited to inputs. List of those is on github of this extension."
|
||||
dependencies = [
|
||||
|
||||
]
|
||||
@@ -37,7 +38,7 @@ Repository = "https://github.com/mcDandy/more_math.git"
|
||||
[tool.comfy]
|
||||
PublisherId = "mkdaniel"
|
||||
DisplayName = "More Math"
|
||||
Icon = ""
|
||||
Icon = "https://raw.githubusercontent.com/mcDandy/more_math/60c026ecc2cd5f7600e82d87afbe795747e52890/icon.png"
|
||||
|
||||
|
||||
[tool.setuptools.package-data]
|
||||
@@ -81,3 +82,6 @@ select = [
|
||||
|
||||
[tool.ruff.lint.flake8-quotes]
|
||||
inline-quotes = "double"
|
||||
|
||||
[tool.ruff.lint.per-file-ignores]
|
||||
"more_math/Parser/*.py" = ["F401", "F403", "F405"]
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
[pytest]
|
||||
pythonpath = . ../..
|
||||
+3
-1
@@ -1 +1,3 @@
|
||||
antlr4-python3-runtime>=4.11.0
|
||||
antlr4-python3-runtime
|
||||
torch
|
||||
pillow
|
||||
@@ -1,117 +0,0 @@
|
||||
from inspect import cleandoc
|
||||
import torch
|
||||
from antlr4 import CommonTokenStream, InputStream
|
||||
from .Parser.MathExprParser import MathExprParser
|
||||
from .Parser.MathExprLexer import MathExprLexer
|
||||
from .Parser.TensorEvalVisitor import TensorEvalVisitor
|
||||
from .helper_functions import getIndexTensorAlongDim
|
||||
|
||||
class AudioMathNode:
|
||||
"""
|
||||
This node enables the use of math expressions on AUDIO tensors.
|
||||
INPUTS:
|
||||
a, b, c, d:
|
||||
AUDIO, bound to variables with the same name. Defaults to zero AUDIO if not provided.
|
||||
w, x, y, z:
|
||||
Floats, bound to variables of the expression. Defaults to 0.0 if not provided.
|
||||
Audio expression:
|
||||
String, describing expression to apply to audio tensors.
|
||||
|
||||
OUTPUTS:
|
||||
AUDIO:
|
||||
Returns an AUDIO object that contains the result of the math expression applied to the input audio tensors.
|
||||
"""
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
"""
|
||||
"""
|
||||
return {
|
||||
"required": {
|
||||
"a": ("AUDIO", {
|
||||
"description": "Input audio tensor"
|
||||
}),
|
||||
"AudioExpr": ("STRING", {
|
||||
"multiline": False,
|
||||
"default": "a*(1-w)+b*w",
|
||||
"description": "Expression to apply on input audio tensors"
|
||||
}),
|
||||
},
|
||||
"optional": {
|
||||
"b": ("AUDIO", {
|
||||
"default": 0,
|
||||
"description": "Second input audio tensor"
|
||||
}),
|
||||
"c": ("AUDIO", {
|
||||
"default": 0,
|
||||
"description": "Third input audio tensor"
|
||||
}),
|
||||
"d": ("AUDIO", {
|
||||
"default": 0,
|
||||
"description": "Fourth input audio tensor"
|
||||
}),
|
||||
"w": ("FLOAT", {
|
||||
"default": 0,
|
||||
"forceInput": True
|
||||
}),
|
||||
"x": ("FLOAT", {
|
||||
"default": 0,
|
||||
"forceInput": True
|
||||
}),
|
||||
"y": ("FLOAT", {
|
||||
"default": 0,
|
||||
"forceInput": True
|
||||
}),
|
||||
"z": ("FLOAT", {
|
||||
"default": 0,
|
||||
"forceInput": True
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("AUDIO",)
|
||||
DESCRIPTION = cleandoc(__doc__)
|
||||
FUNCTION = "audioMathNode"
|
||||
CATEGORY = "More math"
|
||||
|
||||
def audioMathNode(self, a, AudioExpr, b=None, c=None, d=None, w=0.0, x=0.0, y=0.0, z=0.0):
|
||||
|
||||
|
||||
bv = b if b else {'waveform':torch.zeros_like(a['waveform']),'sample_rate':a['sample_rate']}
|
||||
cv = c if c else {'waveform':torch.zeros_like(a['waveform']),'sample_rate':a['sample_rate']}
|
||||
dv = d if d else {'waveform':torch.zeros_like(a['waveform']),'sample_rate':a['sample_rate']}
|
||||
|
||||
B = getIndexTensorAlongDim(a['waveform'], 0)
|
||||
C = getIndexTensorAlongDim(a['waveform'], 1)
|
||||
S = getIndexTensorAlongDim(a['waveform'], 2)
|
||||
R = torch.full_like(S, a['sample_rate'], dtype=torch.float32)
|
||||
T = torch.full_like(S, a['waveform'].shape[2], dtype=torch.float32)
|
||||
|
||||
variables = {
|
||||
'a': a['waveform'], 'b': bv['waveform'], 'c': cv['waveform'], 'd': dv['waveform'],
|
||||
'w': w, 'x': x, 'y': y, 'z': z,
|
||||
'B': B, 'C': C, 'S': S,'R': R, 'T' : T
|
||||
}
|
||||
|
||||
input_stream = InputStream(AudioExpr)
|
||||
lexer = MathExprLexer(input_stream)
|
||||
stream = CommonTokenStream(lexer)
|
||||
parser = MathExprParser(stream)
|
||||
tree = parser.expr()
|
||||
|
||||
visitor = TensorEvalVisitor(variables, a['waveform'].shape)
|
||||
result_tensor = visitor.visit(tree)
|
||||
|
||||
print("Audio Tree\n" + tree.toStringTree(recog=parser))
|
||||
print("Result Tensor: ", result_tensor)
|
||||
print("Result Tensor Shape: ", result_tensor.shape)
|
||||
|
||||
# Create output dictionary with the same sample rate
|
||||
output = {
|
||||
'waveform': result_tensor,
|
||||
'sample_rate': a['sample_rate']
|
||||
}
|
||||
|
||||
return (output,)
|
||||
@@ -1,149 +0,0 @@
|
||||
from inspect import cleandoc
|
||||
from antlr4 import CommonTokenStream, InputStream
|
||||
import torch
|
||||
|
||||
from .Parser.MathExprParser import MathExprParser
|
||||
from .Parser.MathExprLexer import MathExprLexer
|
||||
from .Parser.TensorEvalVisitor import TensorEvalVisitor
|
||||
|
||||
|
||||
|
||||
class ConditioningMathNode:
|
||||
"""
|
||||
This node enables the use of math on conditionings. It is recommended to keep the Tensor expression the same as the pooled_output expression.
|
||||
INPUTS:
|
||||
a, b, c, d:
|
||||
Conditioning, bound to variables with the same name. Defaults to zero conditioning if not provided.
|
||||
w, x, y, z:
|
||||
Floats, bound to variables of the expression. Defaults to 0.0 if not provided.
|
||||
Tensor expression:
|
||||
String, describing expression to mix tensor part of conditioning. Tensor probably describes the composition of the image as it has the say what is on the image. Valid functions are sin, cos, tan, abs, sqrt, min, max, norm. Valid operators are +, -, *, /, ^, %. Usable constants are e and pi.
|
||||
Pooled output expression:
|
||||
String, describing expression to mix pooled_output part of conditioning. pooled_output does not have that much say in the image but can change some details of the image or completly change the image in some cases, probably also used for positional and temporal conditioning. Expression uses same syntax as Tensor expression.
|
||||
|
||||
OUTPUTS:
|
||||
CONDITIONING:
|
||||
Returns a CONDITIONING object that contains the result of the math expression applied to the input conditionings.
|
||||
"""
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
"""
|
||||
"""
|
||||
return {
|
||||
"required": {
|
||||
"a": ("CONDITIONING", {
|
||||
|
||||
}),
|
||||
|
||||
"Tensor": ("STRING", {
|
||||
"multiline": False, #True if you want the field to look like the one on the ClipTextEncode node
|
||||
"default": "a*(1-w)+b*w",
|
||||
"description": "Describes composition of the image. Valid functions are sin, cos, tan, asin, acos, atan, atan2, sinh, cosh, tanh, asinh, acosh, atanh, abs, sqrt, ln, log, exp, pow, min, max, norm, floor, ceil, round, gamma. Valid operators are +, -, *, /, %, ^,!˛&,|. Usable constants are e and pi."
|
||||
|
||||
}),
|
||||
"pooled_output": ("STRING", {
|
||||
"multiline": False, #True if you want the field to look like the one on the ClipTextEncode node
|
||||
"default": "a*(1-w)+b*w",
|
||||
"description": "Can change some details of the image, probably also used for positional and temporal conditioning."
|
||||
}),
|
||||
},
|
||||
"optional": {
|
||||
"b": ("CONDITIONING", {
|
||||
"default": 0,
|
||||
}),
|
||||
"c": ("CONDITIONING", {
|
||||
"default": 0,
|
||||
}),
|
||||
"d": ("CONDITIONING", {
|
||||
"default": 0,
|
||||
}),
|
||||
"w": ("FLOAT", {
|
||||
"default": 0,
|
||||
"forceInput":True
|
||||
}),
|
||||
"x": ("FLOAT", {
|
||||
"default": 0,
|
||||
"forceInput":True
|
||||
}),
|
||||
"y": ("FLOAT", {
|
||||
"default": 0,
|
||||
"forceInput":True
|
||||
}),
|
||||
"z": ("FLOAT", {
|
||||
"default": 0,
|
||||
"forceInput":True
|
||||
}),
|
||||
|
||||
|
||||
# "int_field": ("INT", {"default": 0, "min": 0, "max": 100, "step": 1}),
|
||||
# "float_field": ("FLOAT", {"default": 0.5, "min": -10.0, "max": 10.0, "step": 0.001}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("CONDITIONING",)
|
||||
#RETURN_NAMES = ("image_output_name",)
|
||||
DESCRIPTION = cleandoc(__doc__)
|
||||
FUNCTION = "condMathNode"
|
||||
|
||||
#OUTPUT_NODE = False
|
||||
#OUTPUT_TOOLTIPS = ("",) # Tooltips for the output node
|
||||
|
||||
CATEGORY = "More math"
|
||||
|
||||
def condMathNode(self, Tensor,pooled_output, a, b=None, c=None, d=None,w=0.0,x=0.0,y=0.0,z=0.0):
|
||||
if b is None:
|
||||
b = [[torch.zeros_like(a[0][0]), {"pooled_output": torch.zeros_like(a[0][1]["pooled_output"])}]]
|
||||
if c is None:
|
||||
c = [[torch.zeros_like(a[0][0]), {"pooled_output": torch.zeros_like(a[0][1]["pooled_output"])}]]
|
||||
if d is None:
|
||||
d = [[torch.zeros_like(a[0][0]), {"pooled_output": torch.zeros_like(a[0][1]["pooled_output"])}]]
|
||||
|
||||
|
||||
ta = a[0][0].clone()
|
||||
tb = b[0][0].clone()
|
||||
pa = a[0][1]["pooled_output"].clone()
|
||||
pb = b[0][1]["pooled_output"].clone()
|
||||
tc = c[0][0].clone()
|
||||
td = d[0][0].clone()
|
||||
pc = c[0][1]["pooled_output"].clone()
|
||||
pd = d[0][1]["pooled_output"].clone()
|
||||
|
||||
variables = {'a': ta, 'b': tb, 'c': tc, 'd': td, 'w': w, 'x': x, 'y': y, 'z': z}
|
||||
input_stream = InputStream(Tensor)
|
||||
lexer = MathExprLexer(input_stream)
|
||||
stream = CommonTokenStream(lexer)
|
||||
parser = MathExprParser(stream)
|
||||
tree = parser.expr()
|
||||
print("Tensor\n"+tree.toStringTree(recog=parser))
|
||||
visitor = TensorEvalVisitor(variables,ta.shape)
|
||||
result1 = visitor.visit(tree)
|
||||
|
||||
|
||||
variables = {'a': pa, 'b': pb, 'c': pc, 'd': pd, 'w': w, 'x': x, 'y': y, 'z': z}
|
||||
input_stream = InputStream(Tensor)
|
||||
lexer = MathExprLexer(input_stream)
|
||||
stream = CommonTokenStream(lexer)
|
||||
parser = MathExprParser(stream)
|
||||
tree = parser.expr()
|
||||
print("pooled_output\n"+tree.toStringTree(recog=parser))
|
||||
visitor = TensorEvalVisitor(variables,pa.shape)
|
||||
result2 = visitor.visit(tree)
|
||||
|
||||
|
||||
result = [[result1, {"pooled_output": result2}]]
|
||||
return (result,)
|
||||
|
||||
"""
|
||||
The node will always be re executed if any of the inputs change but
|
||||
this method can be used to force the node to execute again even when the inputs don't change.
|
||||
You can make this node return a number or a string. This value will be compared to the one returned the last time the node was
|
||||
executed, if it is different the node will be executed again.
|
||||
This method is used in the core repo for the LoadImage node where they return the image hash as a string, if the image hash
|
||||
changes between executions the LoadImage node is executed again.
|
||||
"""
|
||||
#@classmethod
|
||||
#def IS_CHANGED(s, image, string_field, int_field, float_field, print_to_screen):
|
||||
# return ""
|
||||
@@ -1,120 +0,0 @@
|
||||
from inspect import cleandoc
|
||||
from math import e
|
||||
|
||||
from antlr4 import CommonTokenStream, InputStream
|
||||
|
||||
from .helper_functions import ThrowingErrorListener
|
||||
|
||||
from .Parser.MathExprParser import MathExprParser
|
||||
from .Parser.MathExprLexer import MathExprLexer
|
||||
from .Parser.FloatEvalVisitor import FloatEvalVisitor
|
||||
|
||||
class FloatMathNode:
|
||||
"""
|
||||
This node enables the use of math expressions on Latents.
|
||||
INPUTS:
|
||||
a, b, c, d:
|
||||
Floats, bound to variables with the same name. Defaults to 0.0 if not provided.
|
||||
w, x, y, z:
|
||||
Floats, bound to variables of the expression. Defaults to 0.0 if not provided.
|
||||
Latent expression:
|
||||
String, describing expression to mix latents. Valid functions are sin, cos, tan, abs, sqrt, min, max, norm. Valid operators are +, -, *, /, ^, %. Usable constants are e and pi.
|
||||
|
||||
OUTPUTS:
|
||||
LATENT:
|
||||
Returns a LATENT object that contains the result of the math expression applied to the input conditionings.
|
||||
"""
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
"""
|
||||
"""
|
||||
return {
|
||||
"required": {
|
||||
"a": ("FLOAT", {
|
||||
"default": 0,
|
||||
"forceInput":True
|
||||
}),
|
||||
|
||||
"FloatFunc": ("STRING", {
|
||||
"multiline": False, #True if you want the field to look like the one on the ClipTextEncode node
|
||||
"default": "a*(1-w)+b*w",
|
||||
"description": "Expression to use on inputs"
|
||||
|
||||
}),
|
||||
},
|
||||
"optional": {
|
||||
"b": ("FLOAT", {
|
||||
"default": 0,
|
||||
"forceInput":True
|
||||
}),
|
||||
"c": ("FLOAT", {
|
||||
"default": 0,
|
||||
"forceInput":True
|
||||
}),
|
||||
"d": ("FLOAT", {
|
||||
"default": 0,
|
||||
"forceInput":True
|
||||
}),
|
||||
"w": ("FLOAT", {
|
||||
"default": 0,
|
||||
"forceInput":True
|
||||
}),
|
||||
"x": ("FLOAT", {
|
||||
"default": 0,
|
||||
"forceInput":True
|
||||
}),
|
||||
"y": ("FLOAT", {
|
||||
"default": 0,
|
||||
"forceInput":True
|
||||
}),
|
||||
"z": ("FLOAT", {
|
||||
"default": 0,
|
||||
"forceInput":True
|
||||
}),
|
||||
|
||||
|
||||
# "int_field": ("INT", {"default": 0, "min": 0, "max": 100, "step": 1}),
|
||||
# "float_field": ("FLOAT", {"default": 0.5, "min": -10.0, "max": 10.0, "step": 0.001}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("FLOAT",)
|
||||
#RETURN_NAMES = ("image_output_name",)
|
||||
DESCRIPTION = cleandoc(__doc__)
|
||||
FUNCTION = "fltMathNode"
|
||||
|
||||
#OUTPUT_NODE = False
|
||||
#OUTPUT_TOOLTIPS = ("",) # Tooltips for the output node
|
||||
|
||||
CATEGORY = "More math"
|
||||
|
||||
def fltMathNode(self, FloatFunc, a, b=0.0, c=0.0, d=0.0, w=0.0, x=0.0, y=0.0, z=0.0):
|
||||
|
||||
|
||||
|
||||
variables = {'a': a, 'b': b, 'c': c, 'd': d, 'w': w, 'x': x, 'y': y, 'z': z}
|
||||
input_stream = InputStream(FloatFunc)
|
||||
lexer = MathExprLexer(input_stream)
|
||||
stream = CommonTokenStream(lexer)
|
||||
parser = MathExprParser(stream)
|
||||
parser.addErrorListener(ThrowingErrorListener())
|
||||
tree = parser.expr()
|
||||
print("Tree\n"+tree.toStringTree(recog=parser))
|
||||
visitor = FloatEvalVisitor(variables)
|
||||
result = visitor.visit(tree)
|
||||
return (result,)
|
||||
|
||||
"""
|
||||
The node will always be re executed if any of the inputs change but
|
||||
this method can be used to force the node to execute again even when the inputs don't change.
|
||||
You can make this node return a number or a string. This value will be compared to the one returned the last time the node was
|
||||
executed, if it is different the node will be executed again.
|
||||
This method is used in the core repo for the LoadImage node where they return the image hash as a string, if the image hash
|
||||
changes between executions the LoadImage node is executed again.
|
||||
"""
|
||||
#@classmethod
|
||||
#def IS_CHANGED(s, image, string_field, int_field, float_field, print_to_screen):
|
||||
# return ""
|
||||
@@ -1,132 +0,0 @@
|
||||
from inspect import cleandoc
|
||||
|
||||
from antlr4 import CommonTokenStream
|
||||
from antlr4.atn.LexerActionExecutor import InputStream
|
||||
import torch
|
||||
from .helper_functions import ThrowingErrorListener, getIndexTensorAlongDim
|
||||
|
||||
from .Parser.MathExprParser import MathExprParser
|
||||
from .Parser.MathExprLexer import MathExprLexer
|
||||
from .Parser.TensorEvalVisitor import TensorEvalVisitor
|
||||
|
||||
class ImageMathNode:
|
||||
"""
|
||||
This node enables the use of math expressions on Latents.
|
||||
INPUTS:
|
||||
a, b, c, d:
|
||||
Latent, bound to variables with the same name. Defaults to zero latent if not provided.
|
||||
w, x, y, z:
|
||||
Floats, bound to variables of the expression. Defaults to 0.0 if not provided.
|
||||
Image expression:
|
||||
String, describing expression to mix images. Valid functions are sin, cos, tan, asin, acos, atan, atan2, sinh, cosh, tanh, asinh, acosh, atanh, abs, sqrt, ln, log, exp, pow, min, max, norm, floor, ceil, round, gamma. Valid operators are +, -, *, /, %, ^,!˛&,|. Usable constants are e and pi.
|
||||
|
||||
OUTPUTS:
|
||||
LATENT:
|
||||
Returns a LATENT object that contains the result of the math expression applied to the input conditionings.
|
||||
"""
|
||||
def __init__(self):
|
||||
shape = []
|
||||
B = None
|
||||
C = None
|
||||
X = None
|
||||
Y = None
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
"""
|
||||
"""
|
||||
return {
|
||||
"required": {
|
||||
"a": ("IMAGE", {
|
||||
|
||||
}),
|
||||
|
||||
"Image": ("STRING", {
|
||||
"multiline": False, #True if you want the field to look like the one on the ClipTextEncode node
|
||||
"default": "a*(1-w)+b*w",
|
||||
"description": "Describes expression to apply to the image."
|
||||
|
||||
}),
|
||||
},
|
||||
"optional": {
|
||||
"b": ("IMAGE", {
|
||||
"default": 0,
|
||||
}),
|
||||
"c": ("IMAGE", {
|
||||
"default": 0,
|
||||
}),
|
||||
"d": ("IMAGE", {
|
||||
"default": 0,
|
||||
}),
|
||||
"w": ("FLOAT", {
|
||||
"default": 0,
|
||||
"forceInput":True
|
||||
}),
|
||||
"x": ("FLOAT", {
|
||||
"default": 0,
|
||||
"forceInput":True
|
||||
}),
|
||||
"y": ("FLOAT", {
|
||||
"default": 0,
|
||||
"forceInput":True
|
||||
}),
|
||||
"z": ("FLOAT", {
|
||||
"default": 0,
|
||||
"forceInput":True
|
||||
}),
|
||||
|
||||
|
||||
# "int_field": ("INT", {"default": 0, "min": 0, "max": 100, "step": 1}),
|
||||
# "float_field": ("FLOAT", {"default": 0.5, "min": -10.0, "max": 10.0, "step": 0.001}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
#RETURN_NAMES = ("image_output_name",)
|
||||
DESCRIPTION = cleandoc(__doc__)
|
||||
FUNCTION = "imgMathNode"
|
||||
|
||||
#OUTPUT_NODE = False
|
||||
#OUTPUT_TOOLTIPS = ("",) # Tooltips for the output node
|
||||
|
||||
CATEGORY = "More math"
|
||||
|
||||
def imgMathNode(self, Image, a, b=None, c=None, d=None, w=0.0, x=0.0, y=0.0, z=0.0):
|
||||
b = torch.zeros_like(a) if b is None else b
|
||||
c = torch.zeros_like(a) if c is None else c
|
||||
d = torch.zeros_like(a) if d is None else d
|
||||
|
||||
batch = range(0,a.shape[0],1)
|
||||
width = range(0,a.shape[2],1)
|
||||
height = range(0,a.shape[1],1)
|
||||
color = range(0,a.shape[3],1)
|
||||
|
||||
B = getIndexTensorAlongDim(a, 0)
|
||||
W = getIndexTensorAlongDim(a, 2)
|
||||
H = getIndexTensorAlongDim(a, 1)
|
||||
C = getIndexTensorAlongDim(a, 3)
|
||||
|
||||
variables = {'a': a, 'b': b, 'c': c, 'd': d, 'w': w, 'x': x, 'y': y, 'z': z,'B':B,'X':W,'Y':H,'C':C,'W':a.shape[1],'H':a.shape[2] }
|
||||
input_stream = InputStream(Image)
|
||||
lexer = MathExprLexer(input_stream)
|
||||
stream = CommonTokenStream(lexer)
|
||||
parser = MathExprParser(stream)
|
||||
parser.addErrorListener(ThrowingErrorListener())
|
||||
tree = parser.expr()
|
||||
print("Tree\n"+tree.toStringTree(recog=parser))
|
||||
visitor = TensorEvalVisitor(variables,a.shape)
|
||||
result = visitor.visit(tree)
|
||||
return (result,)
|
||||
|
||||
|
||||
"""
|
||||
The node will always be re executed if any of the inputs change but
|
||||
this method can be used to force the node to execute again even when the inputs don't change.
|
||||
You can make this node return a number or a string. This value will be compared to the one returned the last time the node was
|
||||
executed, if it is different the node will be executed again.
|
||||
This method is used in the core repo for the LoadImage node where they return the image hash as a string, if the image hash
|
||||
changes between executions the LoadImage node is executed again.
|
||||
"""
|
||||
#@classmethod
|
||||
#def IS_CHANGED(s, image, string_field, int_field, float_field, print_to_screen):
|
||||
# return ""
|
||||
@@ -1,125 +0,0 @@
|
||||
from inspect import cleandoc
|
||||
from math import e
|
||||
|
||||
from antlr4 import CommonTokenStream, InputStream
|
||||
import torch
|
||||
|
||||
from .helper_functions import ThrowingErrorListener, getIndexTensorAlongDim
|
||||
|
||||
from .Parser.MathExprParser import MathExprParser
|
||||
from .Parser.MathExprLexer import MathExprLexer
|
||||
from .Parser.TensorEvalVisitor import TensorEvalVisitor
|
||||
|
||||
class LatentMathNode:
|
||||
"""
|
||||
This node enables the use of math expressions on Latents.
|
||||
INPUTS:
|
||||
a, b, c, d:
|
||||
Latent, bound to variables with the same name. Defaults to zero latent if not provided.
|
||||
w, x, y, z:
|
||||
Floats, bound to variables of the expression. Defaults to 0.0 if not provided.
|
||||
Latent expression:
|
||||
String, describing expression to aply to latents.
|
||||
|
||||
OUTPUTS:
|
||||
LATENT:
|
||||
Returns a LATENT object that contains the result of the math expression applied to the input conditionings.
|
||||
"""
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
"""
|
||||
"""
|
||||
return {
|
||||
"required": {
|
||||
"a": ("LATENT", {
|
||||
|
||||
}),
|
||||
|
||||
"Latent": ("STRING", {
|
||||
"multiline": False, #True if you want the field to look like the one on the ClipTextEncode node
|
||||
"default": "a*(1-w)+b*w",
|
||||
"description": "Expression to apply on input latents"
|
||||
|
||||
}),
|
||||
},
|
||||
"optional": {
|
||||
"b": ("LATENT", {
|
||||
"default": 0,
|
||||
}),
|
||||
"c": ("LATENT", {
|
||||
"default": 0,
|
||||
}),
|
||||
"d": ("LATENT", {
|
||||
"default": 0,
|
||||
}),
|
||||
"w": ("FLOAT", {
|
||||
"default": 0,
|
||||
"forceInput":True
|
||||
}),
|
||||
"x": ("FLOAT", {
|
||||
"default": 0,
|
||||
"forceInput":True
|
||||
}),
|
||||
"y": ("FLOAT", {
|
||||
"default": 0,
|
||||
"forceInput":True
|
||||
}),
|
||||
"z": ("FLOAT", {
|
||||
"default": 0,
|
||||
"forceInput":True
|
||||
}),
|
||||
|
||||
|
||||
# "int_field": ("INT", {"default": 0, "min": 0, "max": 100, "step": 1}),
|
||||
# "float_field": ("FLOAT", {"default": 0.5, "min": -10.0, "max": 10.0, "step": 0.001}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("LATENT",)
|
||||
#RETURN_NAMES = ("image_output_name",)
|
||||
DESCRIPTION = cleandoc(__doc__)
|
||||
FUNCTION = "latMathNode"
|
||||
|
||||
#OUTPUT_NODE = False
|
||||
#OUTPUT_TOOLTIPS = ("",) # Tooltips for the output node
|
||||
|
||||
CATEGORY = "More math"
|
||||
|
||||
def latMathNode(self, Latent, a, b=None, c=None, d=None, w=0.0, x=0.0, y=0.0, z=0.0):
|
||||
a = a["samples"]
|
||||
b = torch.zeros_like(a) if b is None else b["samples"]
|
||||
c = torch.zeros_like(a) if c is None else c["samples"]
|
||||
d = torch.zeros_like(a) if d is None else d["samples"]
|
||||
|
||||
B = getIndexTensorAlongDim(a, 0)
|
||||
W = getIndexTensorAlongDim(a, 3)
|
||||
H = getIndexTensorAlongDim(a, 2)
|
||||
C = getIndexTensorAlongDim(a, 1)
|
||||
|
||||
variables = {'a': a, 'b': b, 'c': c, 'd': d, 'w': w, 'x': x, 'y': y, 'z': z,'B':B,'X':W,'Y':H,'C':C,'W':a.shape[3],'H':a.shape[2]}
|
||||
input_stream = InputStream(Latent)
|
||||
lexer = MathExprLexer(input_stream)
|
||||
stream = CommonTokenStream(lexer)
|
||||
parser = MathExprParser(stream)
|
||||
parser.addErrorListener(ThrowingErrorListener())
|
||||
tree = parser.expr()
|
||||
print("Tree\n"+tree.toStringTree(recog=parser))
|
||||
visitor = TensorEvalVisitor(variables,a.shape)
|
||||
result1 = visitor.visit(tree)
|
||||
result = {"samples": result1}
|
||||
return (result,)
|
||||
|
||||
"""
|
||||
The node will always be re executed if any of the inputs change but
|
||||
this method can be used to force the node to execute again even when the inputs don't change.
|
||||
You can make this node return a number or a string. This value will be compared to the one returned the last time the node was
|
||||
executed, if it is different the node will be executed again.
|
||||
This method is used in the core repo for the LoadImage node where they return the image hash as a string, if the image hash
|
||||
changes between executions the LoadImage node is executed again.
|
||||
"""
|
||||
#@classmethod
|
||||
#def IS_CHANGED(s, image, string_field, int_field, float_field, print_to_screen):
|
||||
# return ""
|
||||
@@ -1,153 +0,0 @@
|
||||
from inspect import cleandoc
|
||||
from math import e
|
||||
|
||||
from antlr4 import CommonTokenStream, InputStream
|
||||
import torch
|
||||
|
||||
from .helper_functions import ThrowingErrorListener, getIndexTensorAlongDim
|
||||
|
||||
from .Parser.MathExprParser import MathExprParser
|
||||
from .Parser.MathExprLexer import MathExprLexer
|
||||
from .Parser.TensorEvalVisitor import TensorEvalVisitor
|
||||
|
||||
class NoiseMathNode:
|
||||
"""
|
||||
This node enables the use of math expressions on Latents.
|
||||
INPUTS:
|
||||
a, b, c, d:
|
||||
Noise generators, bound to variables with the same name. Defaults to zero latent if not provided.
|
||||
w, x, y, z:
|
||||
Floats, bound to variables of the expression. Defaults to 0.0 if not provided.
|
||||
Latent expression:
|
||||
String, describing expression to mix noise.
|
||||
|
||||
OUTPUTS:
|
||||
LATENT:
|
||||
Returns a LATENT object that contains the result of the math expression applied to the input conditionings.
|
||||
"""
|
||||
def __init__(self):
|
||||
va= None
|
||||
vb= None
|
||||
vc= None
|
||||
vd= None
|
||||
vw= 0.0
|
||||
vx= 0.0
|
||||
vy= 0.0
|
||||
vz= 0.0
|
||||
expr="";
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
"""
|
||||
"""
|
||||
return {
|
||||
"required": {
|
||||
"a": ("NOISE", {
|
||||
|
||||
}),
|
||||
|
||||
"Noise": ("STRING", {
|
||||
"multiline": False, #True if you want the field to look like the one on the ClipTextEncode node
|
||||
"default": "a*(1-w)+b*w",
|
||||
"description": "Expression describing manipulation of noise."
|
||||
|
||||
}),
|
||||
},
|
||||
"optional": {
|
||||
"b": ("NOISE", {
|
||||
"default": 0,
|
||||
}),
|
||||
"c": ("NOISE", {
|
||||
"default": 0,
|
||||
}),
|
||||
"d": ("NOISE", {
|
||||
"default": 0,
|
||||
}),
|
||||
"w": ("FLOAT", {
|
||||
"default": 0,
|
||||
"forceInput":True
|
||||
}),
|
||||
"x": ("FLOAT", {
|
||||
"default": 0,
|
||||
"forceInput":True
|
||||
}),
|
||||
"y": ("FLOAT", {
|
||||
"default": 0,
|
||||
"forceInput":True
|
||||
}),
|
||||
"z": ("FLOAT", {
|
||||
"default": 0,
|
||||
"forceInput":True
|
||||
}),
|
||||
|
||||
|
||||
# "int_field": ("INT", {"default": 0, "min": 0, "max": 100, "step": 1}),
|
||||
# "float_field": ("FLOAT", {"default": 0.5, "min": -10.0, "max": 10.0, "step": 0.001}),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("NOISE",)
|
||||
#RETURN_NAMES = ("image_output_name",)
|
||||
DESCRIPTION = cleandoc(__doc__)
|
||||
FUNCTION = "noiMathNode"
|
||||
seed = 0
|
||||
#OUTPUT_NODE = False
|
||||
#OUTPUT_TOOLTIPS = ("",) # Tooltips for the output node
|
||||
|
||||
CATEGORY = "More math"
|
||||
|
||||
def noiMathNode(self, Noise, a, b=None, c=None, d=None, w=0.0, x=0.0, y=0.0, z=0.0):
|
||||
self.va = a
|
||||
self.vb = b
|
||||
self.vc = c
|
||||
self.vd = d
|
||||
self.vw = w
|
||||
self.vx = x
|
||||
self.vy = y
|
||||
self.vz = z
|
||||
|
||||
self.expr = Noise
|
||||
return (self,)
|
||||
|
||||
"""
|
||||
The node will always be re executed if any of the inputs change but
|
||||
this method can be used to force the node to execute again even when the inputs don't change.
|
||||
You can make this node return a number or a string. This value will be compared to the one returned the last time the node was
|
||||
executed, if it is different the node will be executed again.
|
||||
This method is used in the core repo for the LoadImage node where they return the image hash as a string, if the image hash
|
||||
changes between executions the LoadImage node is executed again.
|
||||
"""
|
||||
#@classmethod
|
||||
#def IS_CHANGED(s, image, string_field, int_field, float_field, print_to_screen):
|
||||
# return ""
|
||||
|
||||
|
||||
def generate_noise(self, input_latent:torch.Tensor) -> torch.Tensor:
|
||||
if self.vb is None:
|
||||
self.vb = torch.zeros_like(input_latent["samples"])
|
||||
else:
|
||||
self.vb = self.vb.generate_noise(input_latent)
|
||||
if self.vc is None:
|
||||
self.vc = torch.zeros_like(input_latent["samples"])
|
||||
else:
|
||||
self.vc = self.vc.generate_noise(input_latent)
|
||||
if self.vd is None:
|
||||
self.vd = torch.zeros_like(input_latent["samples"])
|
||||
else:
|
||||
self.vd = self.vd.generate_noise(input_latent)
|
||||
|
||||
B = getIndexTensorAlongDim(input_latent["samples"], 0)
|
||||
W = getIndexTensorAlongDim(input_latent["samples"], 3)
|
||||
H = getIndexTensorAlongDim(input_latent["samples"], 2)
|
||||
C = getIndexTensorAlongDim(input_latent["samples"], 1)
|
||||
|
||||
variables = {'a': self.va.generate_noise(input_latent), 'b': self.vb, 'c': self.vc, 'd': self.vd, 'w': self.vw, 'x': self.vx, 'y': self.vy, 'z': self.vz,'B':B,'X':W,'Y':H,'C':C,'W':input_latent["samples"].shape[3],'H':input_latent["samples"].shape[2],'I':input_latent["samples"]}
|
||||
input_stream = InputStream(self.expr)
|
||||
lexer = MathExprLexer(input_stream)
|
||||
stream = CommonTokenStream(lexer)
|
||||
parser = MathExprParser(stream)
|
||||
parser.addErrorListener(ThrowingErrorListener())
|
||||
tree = parser.expr()
|
||||
print("Tree\n"+tree.toStringTree(recog=parser))
|
||||
visitor = TensorEvalVisitor(variables,variables['a'].shape)
|
||||
result = visitor.visit(tree)
|
||||
return result
|
||||
@@ -1,150 +0,0 @@
|
||||
import math
|
||||
from tkinter import SE
|
||||
from sympy import true
|
||||
import torch
|
||||
from .MathExprVisitor import MathExprVisitor
|
||||
|
||||
class FloatEvalVisitor(MathExprVisitor):
|
||||
def __init__(self, variables):
|
||||
self.variables = variables
|
||||
|
||||
def visitNumberExp(self, ctx):
|
||||
print("Visiting number expression:", ctx.getText())
|
||||
return float(ctx.getText())
|
||||
|
||||
def visitConstantExp(self, ctx):
|
||||
name = ctx.getText().lower()
|
||||
if name == "pi":
|
||||
return 3.141592653589793
|
||||
if name == "e":
|
||||
return 2.718281828459045
|
||||
raise ValueError(f"Unknown constant: {name}")
|
||||
|
||||
def visitVariableExp(self, ctx):
|
||||
name = ctx.getText()
|
||||
if name not in self.variables:
|
||||
raise ValueError(f"Variable '{name}' not found")
|
||||
return self.variables[name]
|
||||
|
||||
def visitParenExp(self, ctx):
|
||||
return self.visit(ctx.expr())
|
||||
|
||||
def visitUnaryPlus(self, ctx):
|
||||
return +self.visit(ctx.unaryExpr())
|
||||
|
||||
def visitUnaryMinus(self, ctx):
|
||||
return -self.visit(ctx.unaryExpr())
|
||||
|
||||
def visitAddExp(self, ctx):
|
||||
return self.visit(ctx.addExpr()) + self.visit(ctx.mulExpr())
|
||||
|
||||
def visitSubExp(self, ctx):
|
||||
return self.visit(ctx.addExpr()) - self.visit(ctx.mulExpr())
|
||||
|
||||
def visitMulExp(self, ctx):
|
||||
print("Visiting multiplication expression:", ctx.getText())
|
||||
if ctx.mulExpr() is None or ctx.powExpr() is None:
|
||||
raise ValueError("Invalid multiplication expression")
|
||||
return self.visit(ctx.mulExpr()) * self.visit(ctx.powExpr())
|
||||
|
||||
def visitDivExp(self, ctx):
|
||||
return self.visit(ctx.mulExpr()) / self.visit(ctx.powExpr())
|
||||
|
||||
def visitModExp(self, ctx):
|
||||
return self.visit(ctx.mulExpr()) % self.visit(ctx.powExpr())
|
||||
|
||||
def visitPowExp(self, ctx):
|
||||
return math.pow(self.visit(ctx.unaryExpr()), self.visit(ctx.powExpr()))
|
||||
|
||||
def visitNeExp(self, ctx):
|
||||
return float(self.visit(ctx.compExpr()) != self.visit(ctx.addExpr()))
|
||||
def visitEqExp(self, ctx):
|
||||
return float(self.visit(ctx.compExpr()) == self.visit(ctx.addExpr()))
|
||||
def visitGtExp(self, ctx):
|
||||
return float(self.visit(ctx.compExpr()) > self.visit(ctx.addExpr()))
|
||||
def visitLtExp(self, ctx):
|
||||
return float(self.visit(ctx.compExpr()) < self.visit(ctx.addExpr()))
|
||||
def visitGeExp(self, ctx):
|
||||
return float(self.visit(ctx.compExpr()) >= self.visit(ctx.addExpr()))
|
||||
def visitLeExp(self, ctx):
|
||||
return float(self.visit(ctx.compExpr()) <= self.visit(ctx.addExpr()))
|
||||
|
||||
def visitToUnary(self, ctx):
|
||||
return self.visit(ctx.unaryExpr())
|
||||
|
||||
def visitToPow(self, ctx):
|
||||
return self.visit(ctx.powExpr())
|
||||
|
||||
def visitToMul(self, ctx):
|
||||
return self.visit(ctx.mulExpr())
|
||||
|
||||
def visitToAdd(self, ctx):
|
||||
return self.visit(ctx.addExpr())
|
||||
|
||||
def visitToGt(self, ctx):
|
||||
return self.visit(ctx.gtExpr())
|
||||
def visitToLt(self, ctx):
|
||||
return self.visit(ctx.ltExpr())
|
||||
def visitToEq(self, ctx):
|
||||
return self.visit(ctx.eqExpr())
|
||||
def visitToNeq(self, ctx):
|
||||
return self.visit(ctx.neqExpr())
|
||||
def visitToGe(self, ctx):
|
||||
return self.visit(ctx.gteExpr())
|
||||
def visitToLe(self, ctx):
|
||||
return self.visit(ctx.lteExpr())
|
||||
|
||||
# Single-argument functions
|
||||
def visitSinFunc(self, ctx): return math.sin(self.visit(ctx.expr()))
|
||||
def visitCosFunc(self, ctx): return math.cos(self.visit(ctx.expr()))
|
||||
def visitTanFunc(self, ctx): return math.tan(self.visit(ctx.expr()))
|
||||
def visitAsinFunc(self, ctx): return math.asin(self.visit(ctx.expr()))
|
||||
def visitAcosFunc(self, ctx): return math.acos(self.visit(ctx.expr()))
|
||||
def visitAtanFunc(self, ctx): return math.atan(self.visit(ctx.expr()))
|
||||
def visitSinhFunc(self, ctx): return math.sinh(self.visit(ctx.expr()))
|
||||
def visitCoshFunc(self, ctx): return math.cosh(self.visit(ctx.expr()))
|
||||
def visitTanhFunc(self, ctx): return math.tanh(self.visit(ctx.expr()))
|
||||
def visitAsinhFunc(self, ctx): return math.asinh(self.visit(ctx.expr()))
|
||||
def visitAcoshFunc(self, ctx): return math.acosh(self.visit(ctx.expr()))
|
||||
def visitAtanhFunc(self, ctx): return math.atanh(self.visit(ctx.expr()))
|
||||
def visitAbsFunc(self, ctx): return math.abs(self.visit(ctx.expr()))
|
||||
def visitSqrtFunc(self, ctx): return math.sqrt(self.visit(ctx.expr()))
|
||||
def visitLnFunc(self, ctx): return math.log(self.visit(ctx.expr()))
|
||||
def visitLogFunc(self, ctx): return math.log10(self.visit(ctx.expr()))
|
||||
def visitExpFunc(self, ctx): return math.exp(self.visit(ctx.expr()))
|
||||
def visitNormFunc(self, ctx): return math.sqrt(math.avg(x**2 for x in self.visit(ctx.expr())))
|
||||
def visitFloorFunc(self, ctx): return math.floor(self.visit(ctx.expr()))
|
||||
def visitSigmoidFunc(self, ctx): return 1/(1+math.exp(-self.visit(ctx.expr())))
|
||||
def visitCeilFunc(self, ctx): return math.ceil(self.visit(ctx.expr()))
|
||||
def visitRoundFunc(self, ctx): return math.round(self.visit(ctx.expr()))
|
||||
def visitGammaFunc(self, ctx): return math.gamma(self.visit(ctx.expr())).exp()
|
||||
|
||||
# Two-argument functions
|
||||
def visitPowFunc(self, ctx):
|
||||
return math.pow(self.visit(ctx.expr(0)), self.visit(ctx.expr(1)))
|
||||
def visitAtan2Func(self, ctx):
|
||||
return math.atan2(self.visit(ctx.expr(0)), self.visit(ctx.expr(1)))
|
||||
|
||||
# N-argument functions
|
||||
def visitSMinFunc(self, ctx):
|
||||
args = [self.visit(e) for e in ctx.expr()]
|
||||
return math.min(args)
|
||||
def visitSMaxFunc(self, ctx):
|
||||
args = [self.visit(e) for e in ctx.expr()]
|
||||
return math.max(args)
|
||||
|
||||
def visitFunc1Exp(self, ctx):
|
||||
return self.visitChildren(ctx)
|
||||
def visitFunc2Exp(self, ctx):
|
||||
return self.visitChildren(ctx)
|
||||
def visitFuncNExp(self, ctx):
|
||||
return self.visitChildren(ctx)
|
||||
def visitAtomExp(self, ctx):
|
||||
return self.visitChildren(ctx)
|
||||
|
||||
def visitFunc2Expr(self, ctx):
|
||||
return self.visit(ctx.getChild(0)) # forward to Atan2Func, PowFunc, etc.
|
||||
|
||||
def visitExpr(self, ctx):
|
||||
print("Visiting expression:", ctx.getText())
|
||||
return self.visitChildren(ctx)
|
||||
@@ -1,157 +0,0 @@
|
||||
grammar MathExpr;
|
||||
|
||||
// Top-level expression, with operator precedence (lowest to highest)
|
||||
expr
|
||||
: atom | compExpr
|
||||
;
|
||||
|
||||
compExpr
|
||||
: compExpr GT addExpr # GtExp
|
||||
| compExpr GE addExpr # GeExp
|
||||
| compExpr LT addExpr # LtExp
|
||||
| compExpr LE addExpr # LeExp
|
||||
| compExpr EQ addExpr # EqExp
|
||||
| compExpr NE addExpr # NeExp
|
||||
| addExpr # ToAdd
|
||||
;
|
||||
|
||||
addExpr
|
||||
: addExpr PLUS mulExpr # AddExp
|
||||
| addExpr MINUS mulExpr # SubExp
|
||||
| mulExpr # ToMul
|
||||
;
|
||||
|
||||
mulExpr
|
||||
: mulExpr MULT powExpr # MulExp
|
||||
| mulExpr DIV powExpr # DivExp
|
||||
| mulExpr MOD powExpr # ModExp
|
||||
| powExpr # ToPow
|
||||
;
|
||||
|
||||
powExpr
|
||||
: unaryExpr POW powExpr # PowExp
|
||||
| unaryExpr # ToUnary
|
||||
;
|
||||
|
||||
unaryExpr
|
||||
: PLUS unaryExpr # UnaryPlus
|
||||
| MINUS unaryExpr # UnaryMinus
|
||||
| atom # ToAtom
|
||||
;
|
||||
|
||||
// Atoms: function calls, variable, number, constant, or parenthesized expression
|
||||
atom
|
||||
: func1 # Func1Exp
|
||||
| func2 # Func2Exp
|
||||
| func3 # Func3Exp
|
||||
| funcN # FuncNExp
|
||||
| VARIABLE # VariableExp
|
||||
| NUMBER # NumberExp
|
||||
| CONSTANT # ConstantExp
|
||||
| '(' expr ')' # ParenExp
|
||||
;
|
||||
|
||||
// Single-argument functions
|
||||
func1
|
||||
: SIN '(' expr ')' # SinFunc
|
||||
| COS '(' expr ')' # CosFunc
|
||||
| TAN '(' expr ')' # TanFunc
|
||||
| ASIN '(' expr ')' # AsinFunc
|
||||
| ACOS '(' expr ')' # AcosFunc
|
||||
| ATAN '(' expr ')' # AtanFunc
|
||||
| SINH '(' expr ')' # SinhFunc
|
||||
| COSH '(' expr ')' # CoshFunc
|
||||
| TANH '(' expr ')' # TanhFunc
|
||||
| ASINH '(' expr ')' # AsinhFunc
|
||||
| ACOSH '(' expr ')' # AcoshFunc
|
||||
| ATANH '(' expr ')' # AtanhFunc
|
||||
| ABS '(' expr ')' # AbsFunc
|
||||
| SQRT '(' expr ')' # SqrtFunc
|
||||
| LN '(' expr ')' # LnFunc
|
||||
| LOG '(' expr ')' # LogFunc
|
||||
| EXP '(' expr ')' # ExpFunc
|
||||
| TNORM '(' expr ')' # TNormFunc
|
||||
| SNORM '(' expr ')' # SNormFunc
|
||||
| FLOOR '(' expr ')' # FloorFunc
|
||||
| CEIL '(' expr ')' # CeilFunc
|
||||
| ROUND '(' expr ')' # RoundFunc
|
||||
| GAMMA '(' expr ')' # GammaFunc
|
||||
| SIGM '(' expr ')' # sigmoidFunc
|
||||
| SFFT '(' expr ')' # sfftFunc
|
||||
| SIFFT '(' expr ')' # sifftFunc
|
||||
| ANGL '(' expr ')' # anglFunc
|
||||
|
||||
;
|
||||
|
||||
// Two-argument functions
|
||||
func2
|
||||
: POWE '(' expr ',' expr ')' # PowFunc
|
||||
| ATAN2 '(' expr ',' expr ')' # Atan2Func
|
||||
| TMIN '(' expr ',' expr ')' # TMinFunc
|
||||
| TMAX '(' expr ',' expr ')' # TMaxFunc
|
||||
;
|
||||
func3
|
||||
: CLAMP '(' expr ',' expr ',' expr ')' # ClampFunc
|
||||
;
|
||||
// N-argument functions (at least 2 arguments)
|
||||
funcN
|
||||
: SMIN '(' expr (',' expr)+ ')' # SMinFunc
|
||||
| SMAX '(' expr (',' expr)+ ')' # SMaxFunc
|
||||
|
||||
;
|
||||
|
||||
// LEXER RULES
|
||||
|
||||
SIN : 'sin';
|
||||
COS : 'cos';
|
||||
TAN : 'tan';
|
||||
ASIN : 'asin';
|
||||
ACOS : 'acos';
|
||||
ATAN : 'atan';
|
||||
ATAN2 : 'atan2';
|
||||
SINH : 'sinh';
|
||||
COSH : 'cosh';
|
||||
TANH : 'tanh';
|
||||
ASINH : 'asinh';
|
||||
ACOSH : 'acosh';
|
||||
ATANH : 'atanh';
|
||||
ABS : 'abs';
|
||||
SQRT : 'sqrt';
|
||||
LN : 'ln';
|
||||
LOG : 'log';
|
||||
EXP : 'exp';
|
||||
SMIN : 'smin';
|
||||
SMAX : 'smax';
|
||||
TMIN : 'tmin';
|
||||
TMAX : 'tmax';
|
||||
TNORM : 'tnorm';
|
||||
SNORM : 'snorm';
|
||||
FLOOR : 'floor';
|
||||
CEIL : 'ceil';
|
||||
ROUND : 'round';
|
||||
GAMMA : 'gamma';
|
||||
POWE : 'pow';
|
||||
SIGM : 'sigm';
|
||||
CLAMP : 'clamp';
|
||||
SFFT : 'fft';
|
||||
SIFFT : 'ifft';
|
||||
ANGL : 'angle';
|
||||
|
||||
PLUS : '+';
|
||||
MINUS : '-';
|
||||
MULT : '*';
|
||||
DIV : '/';
|
||||
MOD : '%';
|
||||
POW : '^';
|
||||
|
||||
GE : '>=';
|
||||
GT : '>';
|
||||
LE : '<=';
|
||||
LT : '<';
|
||||
EQ : '==';
|
||||
NE : '!=';
|
||||
|
||||
CONSTANT : ('pi'|'PI'|'e'|'E');
|
||||
NUMBER : [0-9]+ ('.' [0-9]+)?;
|
||||
VARIABLE : [a-zA-Z_] [a-zA-Z_0-9]*;
|
||||
WS : [ \t\r\n]+ -> skip;
|
||||
File diff suppressed because one or more lines are too long
@@ -1,102 +0,0 @@
|
||||
T__0=1
|
||||
T__1=2
|
||||
T__2=3
|
||||
SIN=4
|
||||
COS=5
|
||||
TAN=6
|
||||
ASIN=7
|
||||
ACOS=8
|
||||
ATAN=9
|
||||
ATAN2=10
|
||||
SINH=11
|
||||
COSH=12
|
||||
TANH=13
|
||||
ASINH=14
|
||||
ACOSH=15
|
||||
ATANH=16
|
||||
ABS=17
|
||||
SQRT=18
|
||||
LN=19
|
||||
LOG=20
|
||||
EXP=21
|
||||
SMIN=22
|
||||
SMAX=23
|
||||
TMIN=24
|
||||
TMAX=25
|
||||
TNORM=26
|
||||
SNORM=27
|
||||
FLOOR=28
|
||||
CEIL=29
|
||||
ROUND=30
|
||||
GAMMA=31
|
||||
POWE=32
|
||||
SIGM=33
|
||||
CLAMP=34
|
||||
SFFT=35
|
||||
SIFFT=36
|
||||
ANGL=37
|
||||
PLUS=38
|
||||
MINUS=39
|
||||
MULT=40
|
||||
DIV=41
|
||||
MOD=42
|
||||
POW=43
|
||||
GE=44
|
||||
GT=45
|
||||
LE=46
|
||||
LT=47
|
||||
EQ=48
|
||||
NE=49
|
||||
CONSTANT=50
|
||||
NUMBER=51
|
||||
VARIABLE=52
|
||||
WS=53
|
||||
'('=1
|
||||
')'=2
|
||||
','=3
|
||||
'sin'=4
|
||||
'cos'=5
|
||||
'tan'=6
|
||||
'asin'=7
|
||||
'acos'=8
|
||||
'atan'=9
|
||||
'atan2'=10
|
||||
'sinh'=11
|
||||
'cosh'=12
|
||||
'tanh'=13
|
||||
'asinh'=14
|
||||
'acosh'=15
|
||||
'atanh'=16
|
||||
'abs'=17
|
||||
'sqrt'=18
|
||||
'ln'=19
|
||||
'log'=20
|
||||
'exp'=21
|
||||
'smin'=22
|
||||
'smax'=23
|
||||
'tmin'=24
|
||||
'tmax'=25
|
||||
'tnorm'=26
|
||||
'snorm'=27
|
||||
'floor'=28
|
||||
'ceil'=29
|
||||
'round'=30
|
||||
'gamma'=31
|
||||
'pow'=32
|
||||
'sigm'=33
|
||||
'clamp'=34
|
||||
'fft'=35
|
||||
'ifft'=36
|
||||
'angle'=37
|
||||
'+'=38
|
||||
'-'=39
|
||||
'*'=40
|
||||
'/'=41
|
||||
'%'=42
|
||||
'^'=43
|
||||
'>='=44
|
||||
'>'=45
|
||||
'<='=46
|
||||
'<'=47
|
||||
'=='=48
|
||||
'!='=49
|
||||
File diff suppressed because one or more lines are too long
@@ -1,236 +0,0 @@
|
||||
# Generated from MathExpr.g4 by ANTLR 4.13.2
|
||||
from antlr4 import *
|
||||
from io import StringIO
|
||||
import sys
|
||||
if sys.version_info[1] > 5:
|
||||
from typing import TextIO
|
||||
else:
|
||||
from typing.io import TextIO
|
||||
|
||||
|
||||
def serializedATN():
|
||||
return [
|
||||
4,0,53,346,6,-1,2,0,7,0,2,1,7,1,2,2,7,2,2,3,7,3,2,4,7,4,2,5,7,5,
|
||||
2,6,7,6,2,7,7,7,2,8,7,8,2,9,7,9,2,10,7,10,2,11,7,11,2,12,7,12,2,
|
||||
13,7,13,2,14,7,14,2,15,7,15,2,16,7,16,2,17,7,17,2,18,7,18,2,19,7,
|
||||
19,2,20,7,20,2,21,7,21,2,22,7,22,2,23,7,23,2,24,7,24,2,25,7,25,2,
|
||||
26,7,26,2,27,7,27,2,28,7,28,2,29,7,29,2,30,7,30,2,31,7,31,2,32,7,
|
||||
32,2,33,7,33,2,34,7,34,2,35,7,35,2,36,7,36,2,37,7,37,2,38,7,38,2,
|
||||
39,7,39,2,40,7,40,2,41,7,41,2,42,7,42,2,43,7,43,2,44,7,44,2,45,7,
|
||||
45,2,46,7,46,2,47,7,47,2,48,7,48,2,49,7,49,2,50,7,50,2,51,7,51,2,
|
||||
52,7,52,1,0,1,0,1,1,1,1,1,2,1,2,1,3,1,3,1,3,1,3,1,4,1,4,1,4,1,4,
|
||||
1,5,1,5,1,5,1,5,1,6,1,6,1,6,1,6,1,6,1,7,1,7,1,7,1,7,1,7,1,8,1,8,
|
||||
1,8,1,8,1,8,1,9,1,9,1,9,1,9,1,9,1,9,1,10,1,10,1,10,1,10,1,10,1,11,
|
||||
1,11,1,11,1,11,1,11,1,12,1,12,1,12,1,12,1,12,1,13,1,13,1,13,1,13,
|
||||
1,13,1,13,1,14,1,14,1,14,1,14,1,14,1,14,1,15,1,15,1,15,1,15,1,15,
|
||||
1,15,1,16,1,16,1,16,1,16,1,17,1,17,1,17,1,17,1,17,1,18,1,18,1,18,
|
||||
1,19,1,19,1,19,1,19,1,20,1,20,1,20,1,20,1,21,1,21,1,21,1,21,1,21,
|
||||
1,22,1,22,1,22,1,22,1,22,1,23,1,23,1,23,1,23,1,23,1,24,1,24,1,24,
|
||||
1,24,1,24,1,25,1,25,1,25,1,25,1,25,1,25,1,26,1,26,1,26,1,26,1,26,
|
||||
1,26,1,27,1,27,1,27,1,27,1,27,1,27,1,28,1,28,1,28,1,28,1,28,1,29,
|
||||
1,29,1,29,1,29,1,29,1,29,1,30,1,30,1,30,1,30,1,30,1,30,1,31,1,31,
|
||||
1,31,1,31,1,32,1,32,1,32,1,32,1,32,1,33,1,33,1,33,1,33,1,33,1,33,
|
||||
1,34,1,34,1,34,1,34,1,35,1,35,1,35,1,35,1,35,1,36,1,36,1,36,1,36,
|
||||
1,36,1,36,1,37,1,37,1,38,1,38,1,39,1,39,1,40,1,40,1,41,1,41,1,42,
|
||||
1,42,1,43,1,43,1,43,1,44,1,44,1,45,1,45,1,45,1,46,1,46,1,47,1,47,
|
||||
1,47,1,48,1,48,1,48,1,49,1,49,1,49,1,49,1,49,3,49,318,8,49,1,50,
|
||||
4,50,321,8,50,11,50,12,50,322,1,50,1,50,4,50,327,8,50,11,50,12,50,
|
||||
328,3,50,331,8,50,1,51,1,51,5,51,335,8,51,10,51,12,51,338,9,51,1,
|
||||
52,4,52,341,8,52,11,52,12,52,342,1,52,1,52,0,0,53,1,1,3,2,5,3,7,
|
||||
4,9,5,11,6,13,7,15,8,17,9,19,10,21,11,23,12,25,13,27,14,29,15,31,
|
||||
16,33,17,35,18,37,19,39,20,41,21,43,22,45,23,47,24,49,25,51,26,53,
|
||||
27,55,28,57,29,59,30,61,31,63,32,65,33,67,34,69,35,71,36,73,37,75,
|
||||
38,77,39,79,40,81,41,83,42,85,43,87,44,89,45,91,46,93,47,95,48,97,
|
||||
49,99,50,101,51,103,52,105,53,1,0,5,2,0,69,69,101,101,1,0,48,57,
|
||||
3,0,65,90,95,95,97,122,4,0,48,57,65,90,95,95,97,122,3,0,9,10,13,
|
||||
13,32,32,352,0,1,1,0,0,0,0,3,1,0,0,0,0,5,1,0,0,0,0,7,1,0,0,0,0,9,
|
||||
1,0,0,0,0,11,1,0,0,0,0,13,1,0,0,0,0,15,1,0,0,0,0,17,1,0,0,0,0,19,
|
||||
1,0,0,0,0,21,1,0,0,0,0,23,1,0,0,0,0,25,1,0,0,0,0,27,1,0,0,0,0,29,
|
||||
1,0,0,0,0,31,1,0,0,0,0,33,1,0,0,0,0,35,1,0,0,0,0,37,1,0,0,0,0,39,
|
||||
1,0,0,0,0,41,1,0,0,0,0,43,1,0,0,0,0,45,1,0,0,0,0,47,1,0,0,0,0,49,
|
||||
1,0,0,0,0,51,1,0,0,0,0,53,1,0,0,0,0,55,1,0,0,0,0,57,1,0,0,0,0,59,
|
||||
1,0,0,0,0,61,1,0,0,0,0,63,1,0,0,0,0,65,1,0,0,0,0,67,1,0,0,0,0,69,
|
||||
1,0,0,0,0,71,1,0,0,0,0,73,1,0,0,0,0,75,1,0,0,0,0,77,1,0,0,0,0,79,
|
||||
1,0,0,0,0,81,1,0,0,0,0,83,1,0,0,0,0,85,1,0,0,0,0,87,1,0,0,0,0,89,
|
||||
1,0,0,0,0,91,1,0,0,0,0,93,1,0,0,0,0,95,1,0,0,0,0,97,1,0,0,0,0,99,
|
||||
1,0,0,0,0,101,1,0,0,0,0,103,1,0,0,0,0,105,1,0,0,0,1,107,1,0,0,0,
|
||||
3,109,1,0,0,0,5,111,1,0,0,0,7,113,1,0,0,0,9,117,1,0,0,0,11,121,1,
|
||||
0,0,0,13,125,1,0,0,0,15,130,1,0,0,0,17,135,1,0,0,0,19,140,1,0,0,
|
||||
0,21,146,1,0,0,0,23,151,1,0,0,0,25,156,1,0,0,0,27,161,1,0,0,0,29,
|
||||
167,1,0,0,0,31,173,1,0,0,0,33,179,1,0,0,0,35,183,1,0,0,0,37,188,
|
||||
1,0,0,0,39,191,1,0,0,0,41,195,1,0,0,0,43,199,1,0,0,0,45,204,1,0,
|
||||
0,0,47,209,1,0,0,0,49,214,1,0,0,0,51,219,1,0,0,0,53,225,1,0,0,0,
|
||||
55,231,1,0,0,0,57,237,1,0,0,0,59,242,1,0,0,0,61,248,1,0,0,0,63,254,
|
||||
1,0,0,0,65,258,1,0,0,0,67,263,1,0,0,0,69,269,1,0,0,0,71,273,1,0,
|
||||
0,0,73,278,1,0,0,0,75,284,1,0,0,0,77,286,1,0,0,0,79,288,1,0,0,0,
|
||||
81,290,1,0,0,0,83,292,1,0,0,0,85,294,1,0,0,0,87,296,1,0,0,0,89,299,
|
||||
1,0,0,0,91,301,1,0,0,0,93,304,1,0,0,0,95,306,1,0,0,0,97,309,1,0,
|
||||
0,0,99,317,1,0,0,0,101,320,1,0,0,0,103,332,1,0,0,0,105,340,1,0,0,
|
||||
0,107,108,5,40,0,0,108,2,1,0,0,0,109,110,5,41,0,0,110,4,1,0,0,0,
|
||||
111,112,5,44,0,0,112,6,1,0,0,0,113,114,5,115,0,0,114,115,5,105,0,
|
||||
0,115,116,5,110,0,0,116,8,1,0,0,0,117,118,5,99,0,0,118,119,5,111,
|
||||
0,0,119,120,5,115,0,0,120,10,1,0,0,0,121,122,5,116,0,0,122,123,5,
|
||||
97,0,0,123,124,5,110,0,0,124,12,1,0,0,0,125,126,5,97,0,0,126,127,
|
||||
5,115,0,0,127,128,5,105,0,0,128,129,5,110,0,0,129,14,1,0,0,0,130,
|
||||
131,5,97,0,0,131,132,5,99,0,0,132,133,5,111,0,0,133,134,5,115,0,
|
||||
0,134,16,1,0,0,0,135,136,5,97,0,0,136,137,5,116,0,0,137,138,5,97,
|
||||
0,0,138,139,5,110,0,0,139,18,1,0,0,0,140,141,5,97,0,0,141,142,5,
|
||||
116,0,0,142,143,5,97,0,0,143,144,5,110,0,0,144,145,5,50,0,0,145,
|
||||
20,1,0,0,0,146,147,5,115,0,0,147,148,5,105,0,0,148,149,5,110,0,0,
|
||||
149,150,5,104,0,0,150,22,1,0,0,0,151,152,5,99,0,0,152,153,5,111,
|
||||
0,0,153,154,5,115,0,0,154,155,5,104,0,0,155,24,1,0,0,0,156,157,5,
|
||||
116,0,0,157,158,5,97,0,0,158,159,5,110,0,0,159,160,5,104,0,0,160,
|
||||
26,1,0,0,0,161,162,5,97,0,0,162,163,5,115,0,0,163,164,5,105,0,0,
|
||||
164,165,5,110,0,0,165,166,5,104,0,0,166,28,1,0,0,0,167,168,5,97,
|
||||
0,0,168,169,5,99,0,0,169,170,5,111,0,0,170,171,5,115,0,0,171,172,
|
||||
5,104,0,0,172,30,1,0,0,0,173,174,5,97,0,0,174,175,5,116,0,0,175,
|
||||
176,5,97,0,0,176,177,5,110,0,0,177,178,5,104,0,0,178,32,1,0,0,0,
|
||||
179,180,5,97,0,0,180,181,5,98,0,0,181,182,5,115,0,0,182,34,1,0,0,
|
||||
0,183,184,5,115,0,0,184,185,5,113,0,0,185,186,5,114,0,0,186,187,
|
||||
5,116,0,0,187,36,1,0,0,0,188,189,5,108,0,0,189,190,5,110,0,0,190,
|
||||
38,1,0,0,0,191,192,5,108,0,0,192,193,5,111,0,0,193,194,5,103,0,0,
|
||||
194,40,1,0,0,0,195,196,5,101,0,0,196,197,5,120,0,0,197,198,5,112,
|
||||
0,0,198,42,1,0,0,0,199,200,5,115,0,0,200,201,5,109,0,0,201,202,5,
|
||||
105,0,0,202,203,5,110,0,0,203,44,1,0,0,0,204,205,5,115,0,0,205,206,
|
||||
5,109,0,0,206,207,5,97,0,0,207,208,5,120,0,0,208,46,1,0,0,0,209,
|
||||
210,5,116,0,0,210,211,5,109,0,0,211,212,5,105,0,0,212,213,5,110,
|
||||
0,0,213,48,1,0,0,0,214,215,5,116,0,0,215,216,5,109,0,0,216,217,5,
|
||||
97,0,0,217,218,5,120,0,0,218,50,1,0,0,0,219,220,5,116,0,0,220,221,
|
||||
5,110,0,0,221,222,5,111,0,0,222,223,5,114,0,0,223,224,5,109,0,0,
|
||||
224,52,1,0,0,0,225,226,5,115,0,0,226,227,5,110,0,0,227,228,5,111,
|
||||
0,0,228,229,5,114,0,0,229,230,5,109,0,0,230,54,1,0,0,0,231,232,5,
|
||||
102,0,0,232,233,5,108,0,0,233,234,5,111,0,0,234,235,5,111,0,0,235,
|
||||
236,5,114,0,0,236,56,1,0,0,0,237,238,5,99,0,0,238,239,5,101,0,0,
|
||||
239,240,5,105,0,0,240,241,5,108,0,0,241,58,1,0,0,0,242,243,5,114,
|
||||
0,0,243,244,5,111,0,0,244,245,5,117,0,0,245,246,5,110,0,0,246,247,
|
||||
5,100,0,0,247,60,1,0,0,0,248,249,5,103,0,0,249,250,5,97,0,0,250,
|
||||
251,5,109,0,0,251,252,5,109,0,0,252,253,5,97,0,0,253,62,1,0,0,0,
|
||||
254,255,5,112,0,0,255,256,5,111,0,0,256,257,5,119,0,0,257,64,1,0,
|
||||
0,0,258,259,5,115,0,0,259,260,5,105,0,0,260,261,5,103,0,0,261,262,
|
||||
5,109,0,0,262,66,1,0,0,0,263,264,5,99,0,0,264,265,5,108,0,0,265,
|
||||
266,5,97,0,0,266,267,5,109,0,0,267,268,5,112,0,0,268,68,1,0,0,0,
|
||||
269,270,5,102,0,0,270,271,5,102,0,0,271,272,5,116,0,0,272,70,1,0,
|
||||
0,0,273,274,5,105,0,0,274,275,5,102,0,0,275,276,5,102,0,0,276,277,
|
||||
5,116,0,0,277,72,1,0,0,0,278,279,5,97,0,0,279,280,5,110,0,0,280,
|
||||
281,5,103,0,0,281,282,5,108,0,0,282,283,5,101,0,0,283,74,1,0,0,0,
|
||||
284,285,5,43,0,0,285,76,1,0,0,0,286,287,5,45,0,0,287,78,1,0,0,0,
|
||||
288,289,5,42,0,0,289,80,1,0,0,0,290,291,5,47,0,0,291,82,1,0,0,0,
|
||||
292,293,5,37,0,0,293,84,1,0,0,0,294,295,5,94,0,0,295,86,1,0,0,0,
|
||||
296,297,5,62,0,0,297,298,5,61,0,0,298,88,1,0,0,0,299,300,5,62,0,
|
||||
0,300,90,1,0,0,0,301,302,5,60,0,0,302,303,5,61,0,0,303,92,1,0,0,
|
||||
0,304,305,5,60,0,0,305,94,1,0,0,0,306,307,5,61,0,0,307,308,5,61,
|
||||
0,0,308,96,1,0,0,0,309,310,5,33,0,0,310,311,5,61,0,0,311,98,1,0,
|
||||
0,0,312,313,5,112,0,0,313,318,5,105,0,0,314,315,5,80,0,0,315,318,
|
||||
5,73,0,0,316,318,7,0,0,0,317,312,1,0,0,0,317,314,1,0,0,0,317,316,
|
||||
1,0,0,0,318,100,1,0,0,0,319,321,7,1,0,0,320,319,1,0,0,0,321,322,
|
||||
1,0,0,0,322,320,1,0,0,0,322,323,1,0,0,0,323,330,1,0,0,0,324,326,
|
||||
5,46,0,0,325,327,7,1,0,0,326,325,1,0,0,0,327,328,1,0,0,0,328,326,
|
||||
1,0,0,0,328,329,1,0,0,0,329,331,1,0,0,0,330,324,1,0,0,0,330,331,
|
||||
1,0,0,0,331,102,1,0,0,0,332,336,7,2,0,0,333,335,7,3,0,0,334,333,
|
||||
1,0,0,0,335,338,1,0,0,0,336,334,1,0,0,0,336,337,1,0,0,0,337,104,
|
||||
1,0,0,0,338,336,1,0,0,0,339,341,7,4,0,0,340,339,1,0,0,0,341,342,
|
||||
1,0,0,0,342,340,1,0,0,0,342,343,1,0,0,0,343,344,1,0,0,0,344,345,
|
||||
6,52,0,0,345,106,1,0,0,0,7,0,317,322,328,330,336,342,1,6,0,0
|
||||
]
|
||||
|
||||
class MathExprLexer(Lexer):
|
||||
|
||||
atn = ATNDeserializer().deserialize(serializedATN())
|
||||
|
||||
decisionsToDFA = [ DFA(ds, i) for i, ds in enumerate(atn.decisionToState) ]
|
||||
|
||||
T__0 = 1
|
||||
T__1 = 2
|
||||
T__2 = 3
|
||||
SIN = 4
|
||||
COS = 5
|
||||
TAN = 6
|
||||
ASIN = 7
|
||||
ACOS = 8
|
||||
ATAN = 9
|
||||
ATAN2 = 10
|
||||
SINH = 11
|
||||
COSH = 12
|
||||
TANH = 13
|
||||
ASINH = 14
|
||||
ACOSH = 15
|
||||
ATANH = 16
|
||||
ABS = 17
|
||||
SQRT = 18
|
||||
LN = 19
|
||||
LOG = 20
|
||||
EXP = 21
|
||||
SMIN = 22
|
||||
SMAX = 23
|
||||
TMIN = 24
|
||||
TMAX = 25
|
||||
TNORM = 26
|
||||
SNORM = 27
|
||||
FLOOR = 28
|
||||
CEIL = 29
|
||||
ROUND = 30
|
||||
GAMMA = 31
|
||||
POWE = 32
|
||||
SIGM = 33
|
||||
CLAMP = 34
|
||||
SFFT = 35
|
||||
SIFFT = 36
|
||||
ANGL = 37
|
||||
PLUS = 38
|
||||
MINUS = 39
|
||||
MULT = 40
|
||||
DIV = 41
|
||||
MOD = 42
|
||||
POW = 43
|
||||
GE = 44
|
||||
GT = 45
|
||||
LE = 46
|
||||
LT = 47
|
||||
EQ = 48
|
||||
NE = 49
|
||||
CONSTANT = 50
|
||||
NUMBER = 51
|
||||
VARIABLE = 52
|
||||
WS = 53
|
||||
|
||||
channelNames = [ u"DEFAULT_TOKEN_CHANNEL", u"HIDDEN" ]
|
||||
|
||||
modeNames = [ "DEFAULT_MODE" ]
|
||||
|
||||
literalNames = [ "<INVALID>",
|
||||
"'('", "')'", "','", "'sin'", "'cos'", "'tan'", "'asin'", "'acos'",
|
||||
"'atan'", "'atan2'", "'sinh'", "'cosh'", "'tanh'", "'asinh'",
|
||||
"'acosh'", "'atanh'", "'abs'", "'sqrt'", "'ln'", "'log'", "'exp'",
|
||||
"'smin'", "'smax'", "'tmin'", "'tmax'", "'tnorm'", "'snorm'",
|
||||
"'floor'", "'ceil'", "'round'", "'gamma'", "'pow'", "'sigm'",
|
||||
"'clamp'", "'fft'", "'ifft'", "'angle'", "'+'", "'-'", "'*'",
|
||||
"'/'", "'%'", "'^'", "'>='", "'>'", "'<='", "'<'", "'=='", "'!='" ]
|
||||
|
||||
symbolicNames = [ "<INVALID>",
|
||||
"SIN", "COS", "TAN", "ASIN", "ACOS", "ATAN", "ATAN2", "SINH",
|
||||
"COSH", "TANH", "ASINH", "ACOSH", "ATANH", "ABS", "SQRT", "LN",
|
||||
"LOG", "EXP", "SMIN", "SMAX", "TMIN", "TMAX", "TNORM", "SNORM",
|
||||
"FLOOR", "CEIL", "ROUND", "GAMMA", "POWE", "SIGM", "CLAMP",
|
||||
"SFFT", "SIFFT", "ANGL", "PLUS", "MINUS", "MULT", "DIV", "MOD",
|
||||
"POW", "GE", "GT", "LE", "LT", "EQ", "NE", "CONSTANT", "NUMBER",
|
||||
"VARIABLE", "WS" ]
|
||||
|
||||
ruleNames = [ "T__0", "T__1", "T__2", "SIN", "COS", "TAN", "ASIN", "ACOS",
|
||||
"ATAN", "ATAN2", "SINH", "COSH", "TANH", "ASINH", "ACOSH",
|
||||
"ATANH", "ABS", "SQRT", "LN", "LOG", "EXP", "SMIN", "SMAX",
|
||||
"TMIN", "TMAX", "TNORM", "SNORM", "FLOOR", "CEIL", "ROUND",
|
||||
"GAMMA", "POWE", "SIGM", "CLAMP", "SFFT", "SIFFT", "ANGL",
|
||||
"PLUS", "MINUS", "MULT", "DIV", "MOD", "POW", "GE", "GT",
|
||||
"LE", "LT", "EQ", "NE", "CONSTANT", "NUMBER", "VARIABLE",
|
||||
"WS" ]
|
||||
|
||||
grammarFileName = "MathExpr.g4"
|
||||
|
||||
def __init__(self, input=None, output:TextIO = sys.stdout):
|
||||
super().__init__(input, output)
|
||||
self.checkVersion("4.13.2")
|
||||
self._interp = LexerATNSimulator(self, self.atn, self.decisionsToDFA, PredictionContextCache())
|
||||
self._actions = None
|
||||
self._predicates = None
|
||||
|
||||
|
||||
@@ -1,102 +0,0 @@
|
||||
T__0=1
|
||||
T__1=2
|
||||
T__2=3
|
||||
SIN=4
|
||||
COS=5
|
||||
TAN=6
|
||||
ASIN=7
|
||||
ACOS=8
|
||||
ATAN=9
|
||||
ATAN2=10
|
||||
SINH=11
|
||||
COSH=12
|
||||
TANH=13
|
||||
ASINH=14
|
||||
ACOSH=15
|
||||
ATANH=16
|
||||
ABS=17
|
||||
SQRT=18
|
||||
LN=19
|
||||
LOG=20
|
||||
EXP=21
|
||||
SMIN=22
|
||||
SMAX=23
|
||||
TMIN=24
|
||||
TMAX=25
|
||||
TNORM=26
|
||||
SNORM=27
|
||||
FLOOR=28
|
||||
CEIL=29
|
||||
ROUND=30
|
||||
GAMMA=31
|
||||
POWE=32
|
||||
SIGM=33
|
||||
CLAMP=34
|
||||
SFFT=35
|
||||
SIFFT=36
|
||||
ANGL=37
|
||||
PLUS=38
|
||||
MINUS=39
|
||||
MULT=40
|
||||
DIV=41
|
||||
MOD=42
|
||||
POW=43
|
||||
GE=44
|
||||
GT=45
|
||||
LE=46
|
||||
LT=47
|
||||
EQ=48
|
||||
NE=49
|
||||
CONSTANT=50
|
||||
NUMBER=51
|
||||
VARIABLE=52
|
||||
WS=53
|
||||
'('=1
|
||||
')'=2
|
||||
','=3
|
||||
'sin'=4
|
||||
'cos'=5
|
||||
'tan'=6
|
||||
'asin'=7
|
||||
'acos'=8
|
||||
'atan'=9
|
||||
'atan2'=10
|
||||
'sinh'=11
|
||||
'cosh'=12
|
||||
'tanh'=13
|
||||
'asinh'=14
|
||||
'acosh'=15
|
||||
'atanh'=16
|
||||
'abs'=17
|
||||
'sqrt'=18
|
||||
'ln'=19
|
||||
'log'=20
|
||||
'exp'=21
|
||||
'smin'=22
|
||||
'smax'=23
|
||||
'tmin'=24
|
||||
'tmax'=25
|
||||
'tnorm'=26
|
||||
'snorm'=27
|
||||
'floor'=28
|
||||
'ceil'=29
|
||||
'round'=30
|
||||
'gamma'=31
|
||||
'pow'=32
|
||||
'sigm'=33
|
||||
'clamp'=34
|
||||
'fft'=35
|
||||
'ifft'=36
|
||||
'angle'=37
|
||||
'+'=38
|
||||
'-'=39
|
||||
'*'=40
|
||||
'/'=41
|
||||
'%'=42
|
||||
'^'=43
|
||||
'>='=44
|
||||
'>'=45
|
||||
'<='=46
|
||||
'<'=47
|
||||
'=='=48
|
||||
'!='=49
|
||||
@@ -1,570 +0,0 @@
|
||||
# Generated from MathExpr.g4 by ANTLR 4.13.2
|
||||
from antlr4 import *
|
||||
if "." in __name__:
|
||||
from .MathExprParser import MathExprParser
|
||||
else:
|
||||
from MathExprParser import MathExprParser
|
||||
|
||||
# This class defines a complete listener for a parse tree produced by MathExprParser.
|
||||
class MathExprListener(ParseTreeListener):
|
||||
|
||||
# Enter a parse tree produced by MathExprParser#expr.
|
||||
def enterExpr(self, ctx:MathExprParser.ExprContext):
|
||||
pass
|
||||
|
||||
# Exit a parse tree produced by MathExprParser#expr.
|
||||
def exitExpr(self, ctx:MathExprParser.ExprContext):
|
||||
pass
|
||||
|
||||
|
||||
# Enter a parse tree produced by MathExprParser#LtExp.
|
||||
def enterLtExp(self, ctx:MathExprParser.LtExpContext):
|
||||
pass
|
||||
|
||||
# Exit a parse tree produced by MathExprParser#LtExp.
|
||||
def exitLtExp(self, ctx:MathExprParser.LtExpContext):
|
||||
pass
|
||||
|
||||
|
||||
# Enter a parse tree produced by MathExprParser#EqExp.
|
||||
def enterEqExp(self, ctx:MathExprParser.EqExpContext):
|
||||
pass
|
||||
|
||||
# Exit a parse tree produced by MathExprParser#EqExp.
|
||||
def exitEqExp(self, ctx:MathExprParser.EqExpContext):
|
||||
pass
|
||||
|
||||
|
||||
# Enter a parse tree produced by MathExprParser#ToAdd.
|
||||
def enterToAdd(self, ctx:MathExprParser.ToAddContext):
|
||||
pass
|
||||
|
||||
# Exit a parse tree produced by MathExprParser#ToAdd.
|
||||
def exitToAdd(self, ctx:MathExprParser.ToAddContext):
|
||||
pass
|
||||
|
||||
|
||||
# Enter a parse tree produced by MathExprParser#GeExp.
|
||||
def enterGeExp(self, ctx:MathExprParser.GeExpContext):
|
||||
pass
|
||||
|
||||
# Exit a parse tree produced by MathExprParser#GeExp.
|
||||
def exitGeExp(self, ctx:MathExprParser.GeExpContext):
|
||||
pass
|
||||
|
||||
|
||||
# Enter a parse tree produced by MathExprParser#LeExp.
|
||||
def enterLeExp(self, ctx:MathExprParser.LeExpContext):
|
||||
pass
|
||||
|
||||
# Exit a parse tree produced by MathExprParser#LeExp.
|
||||
def exitLeExp(self, ctx:MathExprParser.LeExpContext):
|
||||
pass
|
||||
|
||||
|
||||
# Enter a parse tree produced by MathExprParser#NeExp.
|
||||
def enterNeExp(self, ctx:MathExprParser.NeExpContext):
|
||||
pass
|
||||
|
||||
# Exit a parse tree produced by MathExprParser#NeExp.
|
||||
def exitNeExp(self, ctx:MathExprParser.NeExpContext):
|
||||
pass
|
||||
|
||||
|
||||
# Enter a parse tree produced by MathExprParser#GtExp.
|
||||
def enterGtExp(self, ctx:MathExprParser.GtExpContext):
|
||||
pass
|
||||
|
||||
# Exit a parse tree produced by MathExprParser#GtExp.
|
||||
def exitGtExp(self, ctx:MathExprParser.GtExpContext):
|
||||
pass
|
||||
|
||||
|
||||
# Enter a parse tree produced by MathExprParser#AddExp.
|
||||
def enterAddExp(self, ctx:MathExprParser.AddExpContext):
|
||||
pass
|
||||
|
||||
# Exit a parse tree produced by MathExprParser#AddExp.
|
||||
def exitAddExp(self, ctx:MathExprParser.AddExpContext):
|
||||
pass
|
||||
|
||||
|
||||
# Enter a parse tree produced by MathExprParser#ToMul.
|
||||
def enterToMul(self, ctx:MathExprParser.ToMulContext):
|
||||
pass
|
||||
|
||||
# Exit a parse tree produced by MathExprParser#ToMul.
|
||||
def exitToMul(self, ctx:MathExprParser.ToMulContext):
|
||||
pass
|
||||
|
||||
|
||||
# Enter a parse tree produced by MathExprParser#SubExp.
|
||||
def enterSubExp(self, ctx:MathExprParser.SubExpContext):
|
||||
pass
|
||||
|
||||
# Exit a parse tree produced by MathExprParser#SubExp.
|
||||
def exitSubExp(self, ctx:MathExprParser.SubExpContext):
|
||||
pass
|
||||
|
||||
|
||||
# Enter a parse tree produced by MathExprParser#MulExp.
|
||||
def enterMulExp(self, ctx:MathExprParser.MulExpContext):
|
||||
pass
|
||||
|
||||
# Exit a parse tree produced by MathExprParser#MulExp.
|
||||
def exitMulExp(self, ctx:MathExprParser.MulExpContext):
|
||||
pass
|
||||
|
||||
|
||||
# Enter a parse tree produced by MathExprParser#ModExp.
|
||||
def enterModExp(self, ctx:MathExprParser.ModExpContext):
|
||||
pass
|
||||
|
||||
# Exit a parse tree produced by MathExprParser#ModExp.
|
||||
def exitModExp(self, ctx:MathExprParser.ModExpContext):
|
||||
pass
|
||||
|
||||
|
||||
# Enter a parse tree produced by MathExprParser#DivExp.
|
||||
def enterDivExp(self, ctx:MathExprParser.DivExpContext):
|
||||
pass
|
||||
|
||||
# Exit a parse tree produced by MathExprParser#DivExp.
|
||||
def exitDivExp(self, ctx:MathExprParser.DivExpContext):
|
||||
pass
|
||||
|
||||
|
||||
# Enter a parse tree produced by MathExprParser#ToPow.
|
||||
def enterToPow(self, ctx:MathExprParser.ToPowContext):
|
||||
pass
|
||||
|
||||
# Exit a parse tree produced by MathExprParser#ToPow.
|
||||
def exitToPow(self, ctx:MathExprParser.ToPowContext):
|
||||
pass
|
||||
|
||||
|
||||
# Enter a parse tree produced by MathExprParser#PowExp.
|
||||
def enterPowExp(self, ctx:MathExprParser.PowExpContext):
|
||||
pass
|
||||
|
||||
# Exit a parse tree produced by MathExprParser#PowExp.
|
||||
def exitPowExp(self, ctx:MathExprParser.PowExpContext):
|
||||
pass
|
||||
|
||||
|
||||
# Enter a parse tree produced by MathExprParser#ToUnary.
|
||||
def enterToUnary(self, ctx:MathExprParser.ToUnaryContext):
|
||||
pass
|
||||
|
||||
# Exit a parse tree produced by MathExprParser#ToUnary.
|
||||
def exitToUnary(self, ctx:MathExprParser.ToUnaryContext):
|
||||
pass
|
||||
|
||||
|
||||
# Enter a parse tree produced by MathExprParser#UnaryPlus.
|
||||
def enterUnaryPlus(self, ctx:MathExprParser.UnaryPlusContext):
|
||||
pass
|
||||
|
||||
# Exit a parse tree produced by MathExprParser#UnaryPlus.
|
||||
def exitUnaryPlus(self, ctx:MathExprParser.UnaryPlusContext):
|
||||
pass
|
||||
|
||||
|
||||
# Enter a parse tree produced by MathExprParser#UnaryMinus.
|
||||
def enterUnaryMinus(self, ctx:MathExprParser.UnaryMinusContext):
|
||||
pass
|
||||
|
||||
# Exit a parse tree produced by MathExprParser#UnaryMinus.
|
||||
def exitUnaryMinus(self, ctx:MathExprParser.UnaryMinusContext):
|
||||
pass
|
||||
|
||||
|
||||
# Enter a parse tree produced by MathExprParser#ToAtom.
|
||||
def enterToAtom(self, ctx:MathExprParser.ToAtomContext):
|
||||
pass
|
||||
|
||||
# Exit a parse tree produced by MathExprParser#ToAtom.
|
||||
def exitToAtom(self, ctx:MathExprParser.ToAtomContext):
|
||||
pass
|
||||
|
||||
|
||||
# Enter a parse tree produced by MathExprParser#Func1Exp.
|
||||
def enterFunc1Exp(self, ctx:MathExprParser.Func1ExpContext):
|
||||
pass
|
||||
|
||||
# Exit a parse tree produced by MathExprParser#Func1Exp.
|
||||
def exitFunc1Exp(self, ctx:MathExprParser.Func1ExpContext):
|
||||
pass
|
||||
|
||||
|
||||
# Enter a parse tree produced by MathExprParser#Func2Exp.
|
||||
def enterFunc2Exp(self, ctx:MathExprParser.Func2ExpContext):
|
||||
pass
|
||||
|
||||
# Exit a parse tree produced by MathExprParser#Func2Exp.
|
||||
def exitFunc2Exp(self, ctx:MathExprParser.Func2ExpContext):
|
||||
pass
|
||||
|
||||
|
||||
# Enter a parse tree produced by MathExprParser#Func3Exp.
|
||||
def enterFunc3Exp(self, ctx:MathExprParser.Func3ExpContext):
|
||||
pass
|
||||
|
||||
# Exit a parse tree produced by MathExprParser#Func3Exp.
|
||||
def exitFunc3Exp(self, ctx:MathExprParser.Func3ExpContext):
|
||||
pass
|
||||
|
||||
|
||||
# Enter a parse tree produced by MathExprParser#FuncNExp.
|
||||
def enterFuncNExp(self, ctx:MathExprParser.FuncNExpContext):
|
||||
pass
|
||||
|
||||
# Exit a parse tree produced by MathExprParser#FuncNExp.
|
||||
def exitFuncNExp(self, ctx:MathExprParser.FuncNExpContext):
|
||||
pass
|
||||
|
||||
|
||||
# Enter a parse tree produced by MathExprParser#VariableExp.
|
||||
def enterVariableExp(self, ctx:MathExprParser.VariableExpContext):
|
||||
pass
|
||||
|
||||
# Exit a parse tree produced by MathExprParser#VariableExp.
|
||||
def exitVariableExp(self, ctx:MathExprParser.VariableExpContext):
|
||||
pass
|
||||
|
||||
|
||||
# Enter a parse tree produced by MathExprParser#NumberExp.
|
||||
def enterNumberExp(self, ctx:MathExprParser.NumberExpContext):
|
||||
pass
|
||||
|
||||
# Exit a parse tree produced by MathExprParser#NumberExp.
|
||||
def exitNumberExp(self, ctx:MathExprParser.NumberExpContext):
|
||||
pass
|
||||
|
||||
|
||||
# Enter a parse tree produced by MathExprParser#ConstantExp.
|
||||
def enterConstantExp(self, ctx:MathExprParser.ConstantExpContext):
|
||||
pass
|
||||
|
||||
# Exit a parse tree produced by MathExprParser#ConstantExp.
|
||||
def exitConstantExp(self, ctx:MathExprParser.ConstantExpContext):
|
||||
pass
|
||||
|
||||
|
||||
# Enter a parse tree produced by MathExprParser#ParenExp.
|
||||
def enterParenExp(self, ctx:MathExprParser.ParenExpContext):
|
||||
pass
|
||||
|
||||
# Exit a parse tree produced by MathExprParser#ParenExp.
|
||||
def exitParenExp(self, ctx:MathExprParser.ParenExpContext):
|
||||
pass
|
||||
|
||||
|
||||
# Enter a parse tree produced by MathExprParser#SinFunc.
|
||||
def enterSinFunc(self, ctx:MathExprParser.SinFuncContext):
|
||||
pass
|
||||
|
||||
# Exit a parse tree produced by MathExprParser#SinFunc.
|
||||
def exitSinFunc(self, ctx:MathExprParser.SinFuncContext):
|
||||
pass
|
||||
|
||||
|
||||
# Enter a parse tree produced by MathExprParser#CosFunc.
|
||||
def enterCosFunc(self, ctx:MathExprParser.CosFuncContext):
|
||||
pass
|
||||
|
||||
# Exit a parse tree produced by MathExprParser#CosFunc.
|
||||
def exitCosFunc(self, ctx:MathExprParser.CosFuncContext):
|
||||
pass
|
||||
|
||||
|
||||
# Enter a parse tree produced by MathExprParser#TanFunc.
|
||||
def enterTanFunc(self, ctx:MathExprParser.TanFuncContext):
|
||||
pass
|
||||
|
||||
# Exit a parse tree produced by MathExprParser#TanFunc.
|
||||
def exitTanFunc(self, ctx:MathExprParser.TanFuncContext):
|
||||
pass
|
||||
|
||||
|
||||
# Enter a parse tree produced by MathExprParser#AsinFunc.
|
||||
def enterAsinFunc(self, ctx:MathExprParser.AsinFuncContext):
|
||||
pass
|
||||
|
||||
# Exit a parse tree produced by MathExprParser#AsinFunc.
|
||||
def exitAsinFunc(self, ctx:MathExprParser.AsinFuncContext):
|
||||
pass
|
||||
|
||||
|
||||
# Enter a parse tree produced by MathExprParser#AcosFunc.
|
||||
def enterAcosFunc(self, ctx:MathExprParser.AcosFuncContext):
|
||||
pass
|
||||
|
||||
# Exit a parse tree produced by MathExprParser#AcosFunc.
|
||||
def exitAcosFunc(self, ctx:MathExprParser.AcosFuncContext):
|
||||
pass
|
||||
|
||||
|
||||
# Enter a parse tree produced by MathExprParser#AtanFunc.
|
||||
def enterAtanFunc(self, ctx:MathExprParser.AtanFuncContext):
|
||||
pass
|
||||
|
||||
# Exit a parse tree produced by MathExprParser#AtanFunc.
|
||||
def exitAtanFunc(self, ctx:MathExprParser.AtanFuncContext):
|
||||
pass
|
||||
|
||||
|
||||
# Enter a parse tree produced by MathExprParser#SinhFunc.
|
||||
def enterSinhFunc(self, ctx:MathExprParser.SinhFuncContext):
|
||||
pass
|
||||
|
||||
# Exit a parse tree produced by MathExprParser#SinhFunc.
|
||||
def exitSinhFunc(self, ctx:MathExprParser.SinhFuncContext):
|
||||
pass
|
||||
|
||||
|
||||
# Enter a parse tree produced by MathExprParser#CoshFunc.
|
||||
def enterCoshFunc(self, ctx:MathExprParser.CoshFuncContext):
|
||||
pass
|
||||
|
||||
# Exit a parse tree produced by MathExprParser#CoshFunc.
|
||||
def exitCoshFunc(self, ctx:MathExprParser.CoshFuncContext):
|
||||
pass
|
||||
|
||||
|
||||
# Enter a parse tree produced by MathExprParser#TanhFunc.
|
||||
def enterTanhFunc(self, ctx:MathExprParser.TanhFuncContext):
|
||||
pass
|
||||
|
||||
# Exit a parse tree produced by MathExprParser#TanhFunc.
|
||||
def exitTanhFunc(self, ctx:MathExprParser.TanhFuncContext):
|
||||
pass
|
||||
|
||||
|
||||
# Enter a parse tree produced by MathExprParser#AsinhFunc.
|
||||
def enterAsinhFunc(self, ctx:MathExprParser.AsinhFuncContext):
|
||||
pass
|
||||
|
||||
# Exit a parse tree produced by MathExprParser#AsinhFunc.
|
||||
def exitAsinhFunc(self, ctx:MathExprParser.AsinhFuncContext):
|
||||
pass
|
||||
|
||||
|
||||
# Enter a parse tree produced by MathExprParser#AcoshFunc.
|
||||
def enterAcoshFunc(self, ctx:MathExprParser.AcoshFuncContext):
|
||||
pass
|
||||
|
||||
# Exit a parse tree produced by MathExprParser#AcoshFunc.
|
||||
def exitAcoshFunc(self, ctx:MathExprParser.AcoshFuncContext):
|
||||
pass
|
||||
|
||||
|
||||
# Enter a parse tree produced by MathExprParser#AtanhFunc.
|
||||
def enterAtanhFunc(self, ctx:MathExprParser.AtanhFuncContext):
|
||||
pass
|
||||
|
||||
# Exit a parse tree produced by MathExprParser#AtanhFunc.
|
||||
def exitAtanhFunc(self, ctx:MathExprParser.AtanhFuncContext):
|
||||
pass
|
||||
|
||||
|
||||
# Enter a parse tree produced by MathExprParser#AbsFunc.
|
||||
def enterAbsFunc(self, ctx:MathExprParser.AbsFuncContext):
|
||||
pass
|
||||
|
||||
# Exit a parse tree produced by MathExprParser#AbsFunc.
|
||||
def exitAbsFunc(self, ctx:MathExprParser.AbsFuncContext):
|
||||
pass
|
||||
|
||||
|
||||
# Enter a parse tree produced by MathExprParser#SqrtFunc.
|
||||
def enterSqrtFunc(self, ctx:MathExprParser.SqrtFuncContext):
|
||||
pass
|
||||
|
||||
# Exit a parse tree produced by MathExprParser#SqrtFunc.
|
||||
def exitSqrtFunc(self, ctx:MathExprParser.SqrtFuncContext):
|
||||
pass
|
||||
|
||||
|
||||
# Enter a parse tree produced by MathExprParser#LnFunc.
|
||||
def enterLnFunc(self, ctx:MathExprParser.LnFuncContext):
|
||||
pass
|
||||
|
||||
# Exit a parse tree produced by MathExprParser#LnFunc.
|
||||
def exitLnFunc(self, ctx:MathExprParser.LnFuncContext):
|
||||
pass
|
||||
|
||||
|
||||
# Enter a parse tree produced by MathExprParser#LogFunc.
|
||||
def enterLogFunc(self, ctx:MathExprParser.LogFuncContext):
|
||||
pass
|
||||
|
||||
# Exit a parse tree produced by MathExprParser#LogFunc.
|
||||
def exitLogFunc(self, ctx:MathExprParser.LogFuncContext):
|
||||
pass
|
||||
|
||||
|
||||
# Enter a parse tree produced by MathExprParser#ExpFunc.
|
||||
def enterExpFunc(self, ctx:MathExprParser.ExpFuncContext):
|
||||
pass
|
||||
|
||||
# Exit a parse tree produced by MathExprParser#ExpFunc.
|
||||
def exitExpFunc(self, ctx:MathExprParser.ExpFuncContext):
|
||||
pass
|
||||
|
||||
|
||||
# Enter a parse tree produced by MathExprParser#TNormFunc.
|
||||
def enterTNormFunc(self, ctx:MathExprParser.TNormFuncContext):
|
||||
pass
|
||||
|
||||
# Exit a parse tree produced by MathExprParser#TNormFunc.
|
||||
def exitTNormFunc(self, ctx:MathExprParser.TNormFuncContext):
|
||||
pass
|
||||
|
||||
|
||||
# Enter a parse tree produced by MathExprParser#SNormFunc.
|
||||
def enterSNormFunc(self, ctx:MathExprParser.SNormFuncContext):
|
||||
pass
|
||||
|
||||
# Exit a parse tree produced by MathExprParser#SNormFunc.
|
||||
def exitSNormFunc(self, ctx:MathExprParser.SNormFuncContext):
|
||||
pass
|
||||
|
||||
|
||||
# Enter a parse tree produced by MathExprParser#FloorFunc.
|
||||
def enterFloorFunc(self, ctx:MathExprParser.FloorFuncContext):
|
||||
pass
|
||||
|
||||
# Exit a parse tree produced by MathExprParser#FloorFunc.
|
||||
def exitFloorFunc(self, ctx:MathExprParser.FloorFuncContext):
|
||||
pass
|
||||
|
||||
|
||||
# Enter a parse tree produced by MathExprParser#CeilFunc.
|
||||
def enterCeilFunc(self, ctx:MathExprParser.CeilFuncContext):
|
||||
pass
|
||||
|
||||
# Exit a parse tree produced by MathExprParser#CeilFunc.
|
||||
def exitCeilFunc(self, ctx:MathExprParser.CeilFuncContext):
|
||||
pass
|
||||
|
||||
|
||||
# Enter a parse tree produced by MathExprParser#RoundFunc.
|
||||
def enterRoundFunc(self, ctx:MathExprParser.RoundFuncContext):
|
||||
pass
|
||||
|
||||
# Exit a parse tree produced by MathExprParser#RoundFunc.
|
||||
def exitRoundFunc(self, ctx:MathExprParser.RoundFuncContext):
|
||||
pass
|
||||
|
||||
|
||||
# Enter a parse tree produced by MathExprParser#GammaFunc.
|
||||
def enterGammaFunc(self, ctx:MathExprParser.GammaFuncContext):
|
||||
pass
|
||||
|
||||
# Exit a parse tree produced by MathExprParser#GammaFunc.
|
||||
def exitGammaFunc(self, ctx:MathExprParser.GammaFuncContext):
|
||||
pass
|
||||
|
||||
|
||||
# Enter a parse tree produced by MathExprParser#sigmoidFunc.
|
||||
def enterSigmoidFunc(self, ctx:MathExprParser.SigmoidFuncContext):
|
||||
pass
|
||||
|
||||
# Exit a parse tree produced by MathExprParser#sigmoidFunc.
|
||||
def exitSigmoidFunc(self, ctx:MathExprParser.SigmoidFuncContext):
|
||||
pass
|
||||
|
||||
|
||||
# Enter a parse tree produced by MathExprParser#sfftFunc.
|
||||
def enterSfftFunc(self, ctx:MathExprParser.SfftFuncContext):
|
||||
pass
|
||||
|
||||
# Exit a parse tree produced by MathExprParser#sfftFunc.
|
||||
def exitSfftFunc(self, ctx:MathExprParser.SfftFuncContext):
|
||||
pass
|
||||
|
||||
|
||||
# Enter a parse tree produced by MathExprParser#sifftFunc.
|
||||
def enterSifftFunc(self, ctx:MathExprParser.SifftFuncContext):
|
||||
pass
|
||||
|
||||
# Exit a parse tree produced by MathExprParser#sifftFunc.
|
||||
def exitSifftFunc(self, ctx:MathExprParser.SifftFuncContext):
|
||||
pass
|
||||
|
||||
|
||||
# Enter a parse tree produced by MathExprParser#anglFunc.
|
||||
def enterAnglFunc(self, ctx:MathExprParser.AnglFuncContext):
|
||||
pass
|
||||
|
||||
# Exit a parse tree produced by MathExprParser#anglFunc.
|
||||
def exitAnglFunc(self, ctx:MathExprParser.AnglFuncContext):
|
||||
pass
|
||||
|
||||
|
||||
# Enter a parse tree produced by MathExprParser#PowFunc.
|
||||
def enterPowFunc(self, ctx:MathExprParser.PowFuncContext):
|
||||
pass
|
||||
|
||||
# Exit a parse tree produced by MathExprParser#PowFunc.
|
||||
def exitPowFunc(self, ctx:MathExprParser.PowFuncContext):
|
||||
pass
|
||||
|
||||
|
||||
# Enter a parse tree produced by MathExprParser#Atan2Func.
|
||||
def enterAtan2Func(self, ctx:MathExprParser.Atan2FuncContext):
|
||||
pass
|
||||
|
||||
# Exit a parse tree produced by MathExprParser#Atan2Func.
|
||||
def exitAtan2Func(self, ctx:MathExprParser.Atan2FuncContext):
|
||||
pass
|
||||
|
||||
|
||||
# Enter a parse tree produced by MathExprParser#TMinFunc.
|
||||
def enterTMinFunc(self, ctx:MathExprParser.TMinFuncContext):
|
||||
pass
|
||||
|
||||
# Exit a parse tree produced by MathExprParser#TMinFunc.
|
||||
def exitTMinFunc(self, ctx:MathExprParser.TMinFuncContext):
|
||||
pass
|
||||
|
||||
|
||||
# Enter a parse tree produced by MathExprParser#TMaxFunc.
|
||||
def enterTMaxFunc(self, ctx:MathExprParser.TMaxFuncContext):
|
||||
pass
|
||||
|
||||
# Exit a parse tree produced by MathExprParser#TMaxFunc.
|
||||
def exitTMaxFunc(self, ctx:MathExprParser.TMaxFuncContext):
|
||||
pass
|
||||
|
||||
|
||||
# Enter a parse tree produced by MathExprParser#ClampFunc.
|
||||
def enterClampFunc(self, ctx:MathExprParser.ClampFuncContext):
|
||||
pass
|
||||
|
||||
# Exit a parse tree produced by MathExprParser#ClampFunc.
|
||||
def exitClampFunc(self, ctx:MathExprParser.ClampFuncContext):
|
||||
pass
|
||||
|
||||
|
||||
# Enter a parse tree produced by MathExprParser#SMinFunc.
|
||||
def enterSMinFunc(self, ctx:MathExprParser.SMinFuncContext):
|
||||
pass
|
||||
|
||||
# Exit a parse tree produced by MathExprParser#SMinFunc.
|
||||
def exitSMinFunc(self, ctx:MathExprParser.SMinFuncContext):
|
||||
pass
|
||||
|
||||
|
||||
# Enter a parse tree produced by MathExprParser#SMaxFunc.
|
||||
def enterSMaxFunc(self, ctx:MathExprParser.SMaxFuncContext):
|
||||
pass
|
||||
|
||||
# Exit a parse tree produced by MathExprParser#SMaxFunc.
|
||||
def exitSMaxFunc(self, ctx:MathExprParser.SMaxFuncContext):
|
||||
pass
|
||||
|
||||
|
||||
|
||||
del MathExprParser
|
||||
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user