Compare commits
940 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ca29a69779 | ||
| dcc5fd71ee | |||
|
|
fee4901657 | ||
| 71873ddf35 | |||
|
|
f8552ca551 | ||
| 995a795060 | |||
|
|
7ab81c5797 | ||
| bc1e23944c | |||
| a3fe20500a | |||
| 61a4e32deb | |||
| 3d681f77e1 | |||
| 5a9ccfd1f6 | |||
| fc57b7a126 | |||
| 06205e0790 | |||
| 4be6fd6b83 | |||
| 714e1e139e | |||
|
|
01c0e0b1df | ||
| 4457ef2147 | |||
| 8ca60d54b3 | |||
| 5696c993dc | |||
| 1206e15309 | |||
|
|
4f2b51b211 | ||
| 1b9c55a46a | |||
| f4844d2e06 | |||
| 06fab0eab9 | |||
| a16b87ac28 | |||
| cce1367a72 | |||
| 28f26e92a4 | |||
|
|
2f5cc3030d | ||
| 1cf6e32303 | |||
| 7f49893d2c | |||
| ba0d1ea903 | |||
| 70fb276fdf | |||
| 43711680ba | |||
| 3d2ca4855c | |||
| fe7e542b19 | |||
| 501eb923f1 | |||
| c15035b6b7 | |||
| 6a9317facd | |||
|
|
95e515114a | ||
| fd6ae91993 | |||
| 3798714369 | |||
|
|
067496b18c | ||
| ad112d1f08 | |||
| a3dff7decc | |||
| 2bcaa4256d | |||
|
|
006f7c01fd | ||
| 2c8764a27d | |||
| 50135b5fe9 | |||
| 8d764e2d46 | |||
| 6eb313fa76 | |||
| 1f8ef52b60 | |||
| 2be009c647 | |||
| 8df6b003e5 | |||
| 7089cf356a | |||
| 1e551d6e96 | |||
| 8e588d79c8 | |||
| 8d93405399 | |||
| 6ff6111091 | |||
| a8b6ef20cc | |||
| a8ff1d4cd0 | |||
| 47fcb9ebfe | |||
| e8305652fd | |||
| 33495cfe03 | |||
| 8ac35d7280 | |||
| c926a75a79 | |||
| fa9b17191d | |||
| 755b394c1c | |||
|
|
717df63d62 | ||
| d75c55b2b1 | |||
| e52ee2604c | |||
| c7feb6952d | |||
| d64758f268 | |||
| 6202d224fe | |||
|
|
4ac90e1e56 | ||
| 8f104cf402 | |||
| 787f74949b | |||
| 196ef7afe1 | |||
|
|
18ac3ffac0 | ||
| ba69e7957c | |||
|
|
5acb47532c | ||
| b5b0aa4f82 | |||
|
|
9a91583ed0 | ||
| b98fd00ade | |||
|
|
d5c5e12589 | ||
| e495fd30c4 | |||
| 8516a1d639 | |||
| 006992e43c | |||
| 48911e9348 | |||
| e4e1a905d1 | |||
| fc935d9fc8 | |||
| 0c6a9f2310 | |||
| d23fd8bd07 | |||
| 9d6ae87d0f | |||
| fc5a8bdd8b | |||
| b8717f1327 | |||
| 0aa317aae5 | |||
|
|
edc19bdff8 | ||
| 11a7204c98 | |||
| eab7883979 | |||
| 2d4249e73a | |||
| 63db1352ee | |||
| 8308115f36 | |||
|
|
02fce838db | ||
| 360d171355 | |||
| eb26e2a11b | |||
| 2b29e34b52 | |||
| fd8766ed87 | |||
| 5de8804da1 | |||
| 2988fd387e | |||
| 1b017edfad | |||
| 903ce7d46b | |||
| 41bcb80167 | |||
|
|
d3f85060ca | ||
| 90178e2f61 | |||
|
|
b9f9a003a2 | ||
| 734f4c7750 | |||
| c78cd898f2 | |||
| 74a249bd06 | |||
| 2020953b93 | |||
| 3826bb3d9e | |||
| 7ffc06f3c7 | |||
|
|
eea1a75d4a | ||
| b9bff38b64 | |||
| f04862933f | |||
| f5b8375fd3 | |||
| db1cdf4280 | |||
| fa1e86ff07 | |||
|
|
9a95454723 | ||
| dd1875ea5c | |||
|
|
df4fabb32a | ||
| 99114f14f6 | |||
|
|
fc3a69bbb0 | ||
| 9594be2606 | |||
|
|
96fd239608 | ||
| 61de7e9e22 | |||
|
|
24c4cdc39f | ||
| fadbf77866 | |||
| 03819a3d90 | |||
| d6d0777113 | |||
| 1aa83e0ef1 | |||
|
|
b09c644e01 | ||
| e403870874 | |||
| 1586ce2d6c | |||
| 576353cfe8 | |||
|
|
cefc415c98 | ||
| bc0ef7893e | |||
|
|
0e802d8194 | ||
| d7718d4dcb | |||
|
|
4c2e02e912 | ||
| b8774e0b0b | |||
| 6e75642090 | |||
| aaa0d1003d | |||
| 5960918137 | |||
| 3dc0532df0 | |||
| 96863adf53 | |||
|
|
08425a623e | ||
| b787759f44 | |||
|
|
25ef7c05e6 | ||
| c36bb80d6a | |||
|
|
c069f3e1b3 | ||
| 215d59c8bf | |||
| 008a33a9b1 | |||
| 3e787234c7 | |||
| 1173510105 | |||
| a391f3018c | |||
| b6e1e20b7c | |||
| 572f2fb811 | |||
| 2e2d422910 | |||
| f0556e4411 | |||
| 4a97105e4b | |||
| 797f73c39a | |||
| b8f796fd3f | |||
| 78673ea11a | |||
| c6a14c0768 | |||
|
|
70a966d8dc | ||
| c42511dd44 | |||
|
|
db62f9e998 | ||
| 0610d2f9f0 | |||
| c1dd0ee190 | |||
| a45c407568 | |||
|
|
813f57861c | ||
| 3faee98ec8 | |||
| ca02132c8d | |||
|
|
cb4ef25b73 | ||
| c8b7367815 | |||
| a268caaa30 | |||
| 6b25abff70 | |||
| 21c807f358 | |||
| 56fdae4275 | |||
| e6a06c9f43 | |||
| f979a63d3d | |||
|
|
327bc54e22 | ||
| a51b15da3f | |||
| 7271b422f9 | |||
| 1866ba66c8 | |||
|
|
6175a04a90 | ||
| 7120f3e93b | |||
| acc13183e2 | |||
| f75fc19c5b | |||
|
|
2650c8b8cf | ||
| 1de3cbf65a | |||
|
|
4a9d0c9e44 | ||
| 88ecd05b95 | |||
| df812eaad5 | |||
|
|
d62da494c8 | ||
| e631fc15d8 | |||
|
|
ecbf1ce0c8 | ||
| e5c0087c9a | |||
| 4348ed1bb2 | |||
|
|
5c11fde0a9 | ||
| 4ca1efeeb8 | |||
| aa7ce2ea27 | |||
|
|
174f0cdcb6 | ||
| 860517a321 | |||
|
|
66daae6d9e | ||
| 83001a0d82 | |||
| 1b7921a7f2 | |||
| 8badb6adc1 | |||
| 37682e7b8a | |||
| 56e74a0e7d | |||
| ec4574ed5c | |||
| 21d20e0fc7 | |||
| 7ce3a83c58 | |||
| 6dff1879c4 | |||
| c09644b29d | |||
| d8cf44134c | |||
| ca856384f3 | |||
| 4e2c9df6a4 | |||
| 8b822e0fa8 | |||
| 67d398caf7 | |||
|
|
c2c27f8279 | ||
| 50b3422528 | |||
| 4639eee0b9 | |||
| b4b27aea3d | |||
| e483b282db | |||
| 36391db607 | |||
| 5362334ff3 | |||
| fdf11d8147 | |||
|
|
204f653b72 | ||
| 48ae950d57 | |||
| 925c893f3f | |||
|
|
b54423a151 | ||
| ce374163ca | |||
| 3644f344da | |||
| d1266a1ce1 | |||
| f7d0b0768a | |||
| 630616ec72 | |||
|
|
7f7bef7581 | ||
| d2f2b206bb | |||
| 6fa1c06053 | |||
| 5d4ca816cd | |||
| 443b6c1d7b | |||
| 505a5ec833 | |||
|
|
3a7289bf5e | ||
| 2718bc6247 | |||
|
|
515d2651bf | ||
| ef25f56380 | |||
|
|
5b280ccc1e | ||
| cbbd23aa33 | |||
|
|
860d0ad014 | ||
| fa344a5799 | |||
|
|
3919de5bd5 | ||
| 1a0a98a453 | |||
| d79f7e9ccd | |||
| 50e41ff261 | |||
| 430b282039 | |||
|
|
17133771bb | ||
| e5a7d47b21 | |||
|
|
71ec61e27b | ||
| b3575eb068 | |||
| 216511b951 | |||
| 6dabbf874f | |||
|
|
d5aad06c88 | ||
| 5d6672069e | |||
| 140ad83380 | |||
| ea805d1362 | |||
| 9e16f2faf9 | |||
| 2a36d9364f | |||
| 27426ce7a5 | |||
|
|
69adadd6d7 | ||
| 6f96498de6 | |||
| 836b6e64f6 | |||
|
|
fab7dd7eec | ||
| 9263f8ef5c | |||
|
|
658728efef | ||
| 6b8432f5b2 | |||
| bc709c4184 | |||
| b49462abeb | |||
| d9d4e3c9bf | |||
| fe04dd80e5 | |||
|
|
718950cf0d | ||
| 17a0068757 | |||
| abc6caa2d0 | |||
|
|
99fb82561b | ||
| 61ba08d0b8 | |||
| 40b5688158 | |||
|
|
0a4e253cbd | ||
| 6428e38ab9 | |||
| fc4f4f81ad | |||
| f6629852eb | |||
| 3adf6cfd58 | |||
| b15816ca9f | |||
| 6b1d5827d6 | |||
| f0391f59c9 | |||
| 006a0894b8 | |||
| 9c5a471234 | |||
| 1c7f4912ce | |||
| df1be10057 | |||
| 954c576131 | |||
| 867720a897 | |||
| 2b40602bdc | |||
| 11173b9c0a | |||
|
|
52d46e77db | ||
| e7838b0f2f | |||
|
|
2ae3810cf6 | ||
| 178fe4d2da | |||
| 2d79ef8fe5 | |||
| d56c5493cd | |||
| cf6e5a40fc | |||
| 64abd67b9b | |||
|
|
c19e856800 | ||
| 02a26086c4 | |||
|
|
35f880bc2f | ||
| c0ddeceeea | |||
| 67fd5e8581 | |||
| bf699ec1fb | |||
| 3094632134 | |||
| 6985ff0fce | |||
| 33f7be42c5 | |||
|
|
36fac70361 | ||
| ca5e8d2fbb | |||
| 828067f486 | |||
|
|
e5f4c0b952 | ||
| edb1775967 | |||
|
|
d0d6908a74 | ||
| c037b87675 | |||
| 52bc322b2b | |||
| 8479caf53a | |||
| 82e2c898d2 | |||
|
|
4852076e4a | ||
| 15cbc21e5b | |||
| ffae5ee54e | |||
|
|
10d77f20d1 | ||
| 4be0d14b74 | |||
| e883dbad81 | |||
| a2abad344f | |||
| d44b1cf8b1 | |||
| c5b6499e41 | |||
| a951ebf1be | |||
| 32da803df9 | |||
| 07d60cf735 | |||
|
|
fceb851c32 | ||
| e1af5ca60f | |||
| 7fb31fc4d7 | |||
| 5c6ba65469 | |||
| cd9fc46ff8 | |||
| 2a88e17b23 | |||
| 69f4371007 | |||
|
|
af2c816d35 | ||
| f51b25f0af | |||
| c3f4845b4f | |||
| 8ae323f5c3 | |||
|
|
a3805a765b | ||
| 8c03034acf | |||
| 4160f3d6d7 | |||
|
|
4742e1ff6a | ||
| 4af1abe4e1 | |||
| 131f49da8e | |||
|
|
f9bf496bd3 | ||
| 9648e3ea96 | |||
| 4be756a867 | |||
| 46face0ee5 | |||
| 6f3b1ea985 | |||
| 3c9181d93d | |||
|
|
a05f24785e | ||
| 9d615c915c | |||
| d2539918b2 | |||
|
|
ed264cb528 | ||
| ad208a5ef8 | |||
| ddc9510c2b | |||
| 855be3551a | |||
| db301b1be2 | |||
| 07b99d91a5 | |||
| 0fea8d6065 | |||
|
|
2a67f1667a | ||
| 76bd0d339a | |||
|
|
43759082dd | ||
| fc4d0f3bb2 | |||
| a47a8ec413 | |||
| 3455c60236 | |||
| edc25fbf9d | |||
|
|
dc38f2308b | ||
| 7d64cac661 | |||
|
|
ab4f1acd75 | ||
| 9f8fbdd5fc | |||
| d1e6cd388c | |||
| 5d09a13d88 | |||
| 0490e80c48 | |||
|
|
48553ba9b1 | ||
| 0f6a5e5fa9 | |||
| 8ff36105d1 | |||
|
|
ce78271af4 | ||
| 57ee735e5c | |||
| 32e1a9d847 | |||
| 5cc816d0af | |||
| 4117fd7b5b | |||
| c86ce302a9 | |||
|
|
c33ce05951 | ||
| 7f2f7cd07a | |||
|
|
1454f6192b | ||
| ceae979f37 | |||
| fcd6ef0975 | |||
| d8ff8afcd4 | |||
| 03fa1f26d0 | |||
| e65c7f3be8 | |||
|
|
20d6352351 | ||
| 5ece269adb | |||
| e0851250ee | |||
| 799ea554de | |||
| df323504fe | |||
| 0ab8aa3a2f | |||
|
|
dae8a3409a | ||
| 0dfcaa4b70 | |||
|
|
98cb2c08ea | ||
| 57cb136a09 | |||
|
|
1d84bca753 | ||
| 4f261be4c7 | |||
| 40eb75f85a | |||
| 13c018a797 | |||
| 8f20a0b3b1 | |||
| 6b6a6b2249 | |||
| 2ca32675ec | |||
| 381d713837 | |||
| a898e7e4f1 | |||
|
|
6d13a3283b | ||
| ab8537483d | |||
| a22229849c | |||
|
|
1ba266080c | ||
| 6500a00682 | |||
| 9602085f82 | |||
|
|
a1c369de9b | ||
| 6238693ffb | |||
|
|
f3a387e77f | ||
| 71cb80d544 | |||
| 77ff7962cc | |||
|
|
a516b1b247 | ||
| 67a99a1a19 | |||
|
|
e55daee756 | ||
| 1111610f32 | |||
| 81484e8160 | |||
|
|
2e349bd705 | ||
| a156803389 | |||
| 2955b5ec02 | |||
| ff52100e23 | |||
| 026c0792be | |||
| b632ed1095 | |||
|
|
98beea37e6 | ||
| 4bcae0f921 | |||
| 22fb5a5656 | |||
| 4da625e439 | |||
| 05e268d466 | |||
| 42a9a0ca15 | |||
| b6feb9adb3 | |||
| 1bc18a201c | |||
| c12f2cee80 | |||
| c2c583fce6 | |||
| 5600624c57 | |||
| 66c0649d7e | |||
| 2446c401d9 | |||
| 4d0df364d3 | |||
| ecdf0f122b | |||
| df5234aa52 | |||
| 62080e6b40 | |||
| 2e3f46ea36 | |||
| be9847e9d2 | |||
| 2f7317b328 | |||
| bd3b1ba043 | |||
|
|
59e82dfd00 | ||
| 0b86a0009d | |||
| 49327a8dbd | |||
| 301bb916da | |||
| 285bf0164b | |||
| 90907e0a9c | |||
| 9def3734af | |||
|
|
3a241e897b | ||
| ee617b73a2 | |||
| 92cea90971 | |||
|
|
452ba20216 | ||
| cf29035e28 | |||
|
|
3fc09dd2aa | ||
| fe101f9328 | |||
| 754d81edf3 | |||
| 3d399ba1f5 | |||
| 6dc1000de5 | |||
|
|
89b4deb5cd | ||
| 6e0e69b9f7 | |||
| b8519e8770 | |||
| 0f69c346cd | |||
| 88014d24c1 | |||
| ea4d743a25 | |||
|
|
5935d4865c | ||
| c5826f8887 | |||
| 62f0b15193 | |||
| d846266332 | |||
|
|
06d0331dee | ||
| e6b065767c | |||
| f3a96dedd7 | |||
|
|
016324e71c | ||
| a92aead769 | |||
| 882cf55fc5 | |||
|
|
ee02c13d5d | ||
|
|
9ccd4ea235 | ||
|
|
86416d50cb | ||
| 1d5442ac08 | |||
|
|
f3c7196921 | ||
|
|
14f901f1be | ||
|
|
9f93c01ff7 | ||
| 203ae09606 | |||
| 2d39c5e4d1 | |||
| 9049e0d27f | |||
|
|
d5d41fc759 | ||
|
|
d0f9bf1733 | ||
|
|
7d46d1160d | ||
|
|
b8d4e697ac | ||
|
|
4664661cfb | ||
|
|
d99fd76c0b | ||
|
|
fcf918c488 | ||
|
|
32747baa27 | ||
|
|
9e974eda27 | ||
|
|
598479bb55 | ||
|
|
4ef6ae90f2 | ||
|
|
4865b10ced | ||
|
|
3362fabed7 | ||
|
|
4076698530 | ||
|
|
a21bfec3d9 | ||
|
|
7ffedd9ceb | ||
|
|
9ad0055336 | ||
|
|
70c4e9bc5e | ||
|
|
43770e2967 | ||
|
|
f3b3c2f526 | ||
|
|
279ac03dc3 | ||
| 4c0a7bbec7 | |||
|
|
f5f9158779 | ||
| c319dacb24 | |||
| 814768525f | |||
|
|
38d056570f | ||
|
|
f386563aa1 | ||
|
|
110506c9a9 | ||
|
|
7e0058a611 | ||
|
|
d89f596a5d | ||
|
|
5de2dfefcb | ||
|
|
bb1f066c3c | ||
|
|
44b451e66b | ||
| a2ed2ebe00 | |||
| 8127fc2960 | |||
|
|
6171790f66 | ||
|
|
ebb36f62dd | ||
|
|
644f1031f6 | ||
|
|
fd711b475f | ||
|
|
57132a4721 | ||
| f71dc5c5ab | |||
| 4630d78fc2 | |||
| da640e888d | |||
|
|
35cd4fd6f1 | ||
|
|
f06e652b82 | ||
|
|
5fc8047c8f | ||
|
|
0363fd5194 | ||
|
|
826a5e9874 | ||
|
|
f668eb8b9b | ||
|
|
5964778a64 | ||
|
|
8135f68230 | ||
|
|
24c77376b2 | ||
|
|
f364afcb42 | ||
|
|
4051902f09 | ||
|
|
a28b9c8981 | ||
|
|
9a5c86ea35 | ||
|
|
08534a4739 | ||
|
|
1db77b969b | ||
|
|
99dce077c4 | ||
|
|
402adc44e8 | ||
|
|
c6bdf0b6a5 | ||
|
|
1c2fb8b972 | ||
| a61bf36df5 | |||
|
|
d678a85957 | ||
|
|
684592ae37 | ||
|
|
f0ed243c91 | ||
|
|
cba3863e5a | ||
|
|
1d26b23221 | ||
|
|
b827e9eaa7 | ||
|
|
60d150a411 | ||
|
|
c781b1b4e4 | ||
|
|
565e475ace | ||
|
|
7c15d75011 | ||
|
|
b676877242 | ||
|
|
7768e594b5 | ||
|
|
9ef331c272 | ||
|
|
4a1792c209 | ||
|
|
91447a2d62 | ||
|
|
ed5bdd99e6 | ||
|
|
feca7a3dcd | ||
|
|
2d9020358d | ||
|
|
51259097fa | ||
|
|
8a4aeb8dfe | ||
|
|
4b0542a513 | ||
|
|
bf04a4e04a | ||
|
|
fa4ca935bb | ||
|
|
b52e22d81f | ||
|
|
2f96e10b9d | ||
|
|
031cb094e7 | ||
|
|
8afc5f0c0c | ||
|
|
17f14581d7 | ||
|
|
8361736679 | ||
|
|
0b9927fcf5 | ||
|
|
8139e271de | ||
|
|
6fe08e6b82 | ||
|
|
968da6f558 | ||
|
|
11ae0b1054 | ||
| 5ebfd2a3c2 | |||
| b36131eed5 | |||
|
|
a7bfcc12b9 | ||
|
|
ab275b8e5f | ||
|
|
d211b47f4c | ||
|
|
812ffaf8ea | ||
|
|
f7a496723c | ||
|
|
48847a19c7 | ||
|
|
8d0083c4aa | ||
|
|
3c143274c5 | ||
|
|
747e97e0c9 | ||
|
|
c6fe9d2026 | ||
|
|
75090b8575 | ||
|
|
8f76c789cf | ||
| 4664568672 | |||
| 3fb6644543 | |||
| d909673071 | |||
|
|
d281d6576c | ||
|
|
8bebc4f692 | ||
|
|
1cd273c375 | ||
|
|
249170ea30 | ||
|
|
1a429b3024 | ||
|
|
e05cab812a | ||
|
|
7607d7a3b6 | ||
|
|
e51be04b95 | ||
|
|
de1f5c968a | ||
|
|
bf819bcf48 | ||
|
|
6f26e5cc3d | ||
|
|
f9c5c82381 | ||
|
|
79487dbec2 | ||
| 58721bea1a | |||
|
|
03e96669da | ||
|
|
eb529d24d2 | ||
|
|
ebd4fccda2 | ||
|
|
97dcc5ac76 | ||
|
|
9c7a189beb | ||
|
|
6061b3150e | ||
|
|
3982c5d498 | ||
|
|
404ca49821 | ||
|
|
6e4775a124 | ||
|
|
5ab82bc133 | ||
|
|
00ef3ae925 | ||
|
|
90d8069cc3 | ||
|
|
457567ef74 | ||
|
|
1128ca5252 | ||
|
|
86c5f25205 | ||
|
|
a706da2490 | ||
|
|
d67bdd2616 | ||
|
|
c3f2ad45c3 | ||
|
|
26c07c3205 | ||
|
|
c995e0d235 | ||
|
|
463a60a99c | ||
|
|
98a46a85b2 | ||
|
|
186c42d667 | ||
|
|
f3a47a5b08 | ||
|
|
af995a74f3 | ||
|
|
3abd955465 | ||
|
|
cba8131367 | ||
|
|
831eddc136 | ||
| 9e852d1afc | |||
| 3ec9caae09 | |||
| 11281fef53 | |||
|
|
9d497b70bf | ||
|
|
2a334156a8 | ||
|
|
086804780d | ||
|
|
731fba55ec | ||
|
|
a3b24f9242 | ||
|
|
af71e35e73 | ||
|
|
3e8996a024 | ||
| 03bdf980bc | |||
| 1084bc0a80 | |||
| 504944f696 | |||
|
|
b53f72f0ad | ||
|
|
aad754f472 | ||
|
|
f5d1127d21 | ||
|
|
080c258d15 | ||
|
|
5adde23a45 | ||
|
|
2359a08519 | ||
|
|
d1f9979ab1 | ||
|
|
bcc47f3740 | ||
|
|
4f700976dd | ||
| 039f963661 | |||
|
|
3ebdb4bed0 | ||
|
|
016b26f5cf | ||
|
|
e5010c7772 | ||
|
|
a4d9713785 | ||
|
|
b21c1db2a9 | ||
|
|
d967fafe3c | ||
|
|
7d15397ce3 | ||
|
|
d978740f98 | ||
|
|
c174326762 | ||
|
|
3d9dc5c008 | ||
|
|
3cc05cde14 | ||
|
|
f96caccfcb | ||
|
|
d7a2c6830f | ||
|
|
045b1baa60 | ||
|
|
fb555b278a | ||
|
|
d865e2f1af | ||
|
|
c70ddb3cb1 | ||
|
|
8ad3059592 | ||
|
|
ee3b616ec1 | ||
|
|
286e62df92 | ||
|
|
b07bb3dde2 | ||
|
|
10dfe9fb65 | ||
|
|
a0d172e3dc | ||
|
|
94878448c8 | ||
|
|
745aa6e812 | ||
|
|
b14d95ad2b | ||
| 65cbd6ef28 | |||
|
|
bb64088282 | ||
|
|
b6f6bc5b20 | ||
|
|
7957d2c566 | ||
|
|
84ef7e59c9 | ||
|
|
cae4f8b934 | ||
|
|
53494c7327 | ||
|
|
05c822617a | ||
|
|
6b114c2461 | ||
|
|
cd9cd9ef9d | ||
|
|
37278e363c | ||
|
|
92a5325aad | ||
|
|
7fec0c7e44 | ||
|
|
59bc40427c | ||
| 5ec2b08e34 | |||
|
|
b38e942acb | ||
|
|
832a438b24 | ||
|
|
43777f58f6 | ||
|
|
aa4c7c3385 | ||
|
|
b85cc898d5 | ||
|
|
da9025e032 | ||
|
|
5c67026637 | ||
|
|
ee2f36fb40 | ||
|
|
5ac3526384 | ||
|
|
3be9c974b5 | ||
|
|
18a702572f | ||
|
|
63f23cf78e | ||
|
|
2e42ba174f | ||
|
|
8bc88ca195 | ||
|
|
f5ff15fb9a | ||
|
|
4b7592c279 | ||
|
|
0fe06ade5b | ||
|
|
27f6a89a29 | ||
|
|
b311069722 | ||
|
|
26c6e1f4b8 | ||
|
|
088fa516a8 | ||
|
|
975aadbf07 | ||
|
|
1f0103480d | ||
|
|
679d3e1980 | ||
|
|
3c28cf0e01 | ||
|
|
9308f60b88 | ||
|
|
ebd0e588d4 | ||
|
|
42fe859fca | ||
|
|
850f02338e | ||
|
|
10539f0ba5 | ||
|
|
4a6e73f4f7 | ||
|
|
f396f98e73 | ||
|
|
de23c28e40 | ||
|
|
fd49f1b484 | ||
|
|
ff1d918d43 | ||
|
|
d52aa15aac | ||
|
|
3a4cbb1bb6 | ||
|
|
3866d7ce4d | ||
|
|
989cd51162 | ||
|
|
1fd018512f | ||
|
|
1cdd760e40 | ||
|
|
1333e6cbca | ||
|
|
4e710dda5e | ||
|
|
77e1d0925d | ||
|
|
60e864b259 | ||
|
|
a3a72b9b93 | ||
|
|
6a5e0adfb2 | ||
|
|
5ad19b4d7b | ||
|
|
cb6fb9d78b | ||
|
|
e4336cca30 | ||
|
|
a785bca880 | ||
|
|
afab283988 | ||
|
|
644a97aee8 | ||
|
|
12469c8c1e | ||
|
|
93db0c21ef | ||
|
|
7e99920fc5 | ||
|
|
cda8daeb35 | ||
|
|
2b29b6cfe2 | ||
|
|
7d5429a162 | ||
|
|
e41d81cbd9 | ||
|
|
55b5ca7381 | ||
|
|
ec88564e65 | ||
|
|
fbb7a918cc | ||
|
|
8ffb7d8961 | ||
|
|
7db9e0ef16 | ||
|
|
f1d7abeb25 | ||
|
|
d78940da3f | ||
|
|
a6616f5986 | ||
|
|
f94a29bf4b | ||
|
|
bf2a09e630 | ||
|
|
486ffa2505 | ||
|
|
c9e5dd542c | ||
| 9d36b9686e | |||
|
|
282f2db756 | ||
|
|
2925a5f20e | ||
| 7152c5b229 | |||
|
|
17ea7ab703 | ||
|
|
8f83115efc | ||
|
|
6d6b1e9155 | ||
|
|
144e56cdd9 | ||
|
|
28908dd07c | ||
|
|
ad2b798f11 | ||
|
|
f022153fa2 | ||
|
|
141b49ff39 | ||
|
|
59bba1429c | ||
|
|
f3f55a7ee0 | ||
|
|
75af0404b3 | ||
|
|
483e18259a | ||
| f7cbdbc5ca | |||
| 7335aa9597 | |||
| f01078fc21 | |||
| 616de26150 | |||
| 78b666ffdb | |||
|
|
68be2a0418 | ||
|
|
78a2b21466 | ||
|
|
5814113f73 | ||
|
|
eb1f1d481e | ||
|
|
6c3dfddd28 | ||
|
|
5162270d28 | ||
|
|
ac2a41d2d8 | ||
|
|
5637c938cf | ||
|
|
d2c12a9f1c | ||
|
|
51c3a9e9ee | ||
|
|
9750039097 | ||
|
|
824ce821cd | ||
|
|
90f22c2288 | ||
|
|
fbd299c7e7 | ||
|
|
36942b316a | ||
|
|
6c773c7c94 | ||
|
|
c525eba885 | ||
|
|
0338462a85 | ||
|
|
fc6098414e | ||
|
|
daf4ee190e | ||
|
|
0ec65a0b41 | ||
|
|
ae79faa7ed | ||
|
|
d356cf734b | ||
|
|
9e7224e0ae | ||
|
|
2faeb639be | ||
|
|
b76df1b583 | ||
|
|
835bda0a53 | ||
|
|
aed65b411a | ||
|
|
8050bdf82d | ||
|
|
d623cf9539 | ||
|
|
89a52a0948 | ||
|
|
5a7ac860a8 | ||
|
|
fc31960c61 | ||
|
|
ece1859a63 | ||
|
|
7b3a873800 | ||
|
|
a0a89fe704 | ||
|
|
dafb6fae7a | ||
|
|
69aaea24f9 | ||
|
|
82bebe6b41 | ||
|
|
f8d30c9b0e | ||
|
|
126451a7a9 | ||
|
|
cf15163bd9 | ||
|
|
6322c4720f | ||
|
|
08d956940e | ||
|
|
f74a6a0b8b | ||
|
|
779f34f500 | ||
|
|
c827a25dab | ||
|
|
0a0d51d278 | ||
|
|
70684d119f | ||
|
|
153c5f4f9d | ||
|
|
8b3a0baaa6 | ||
|
|
5e9deae765 | ||
|
|
4772c244c2 | ||
|
|
80190ccba7 | ||
|
|
abd3ebec1f | ||
|
|
7485aa999f | ||
|
|
ad1d69fa66 | ||
| 977ce3ae93 | |||
|
|
93f21eefd7 | ||
|
|
44cc881ac9 | ||
|
|
ee3cae6472 | ||
|
|
b78152b149 | ||
|
|
85841cdf1f | ||
|
|
ed3f656d5e | ||
|
|
a4fb6bd1d2 | ||
|
|
05f48de3f1 | ||
|
|
401fec8539 | ||
|
|
b13509e9eb | ||
|
|
a15860abac | ||
|
|
673ed325d1 | ||
|
|
63f52fc841 | ||
|
|
200e8b2351 | ||
|
|
e4f23f5101 | ||
|
|
b41d63ea4d | ||
|
|
418480f1fc | ||
|
|
6955b6e292 | ||
|
|
abe35bf967 | ||
|
|
174ab8fd8b | ||
|
|
7ff72b4086 | ||
|
|
cb144c7c2c | ||
|
|
5f3d55b760 | ||
|
|
a6940235be | ||
|
|
4287ac8885 | ||
|
|
08f508f4c3 | ||
|
|
6124eab971 | ||
|
|
65b045e1a2 | ||
|
|
bd28aa0361 | ||
|
|
a5c6ffaa02 | ||
|
|
34c785b92c | ||
|
|
7ad1cb47f3 | ||
|
|
7cb56e9e7f | ||
|
|
4fabee69d8 | ||
|
|
230ccba909 | ||
|
|
b867f25c78 | ||
|
|
9b715c69c0 | ||
|
|
cacc076959 | ||
|
|
7df7aadea8 | ||
|
|
56e619d239 | ||
|
|
0e634ee2ac | ||
|
|
19746c0b76 | ||
|
|
7b844c805d | ||
|
|
723503851b | ||
|
|
57e69907d5 | ||
|
|
f03dac0167 | ||
|
|
8ff983f16e | ||
|
|
10ccf0cc97 |
@@ -1,2 +1,3 @@
|
||||
black --line-length=100 $(git diff --cached --name-only --diff-filter=ACM -- '***.py')
|
||||
git add $(git diff --cached --name-only --diff-filter=ACM -- '***.py')
|
||||
black --line-length=100 $(git diff --cached --name-only --diff-filter=ACM -- '*.py')
|
||||
isort --line-length=100 --profile=black --multi-line=3 --trailing-comma $(git diff --cached --name-only --diff-filter=ACM -- '*.py')
|
||||
git add $(git diff --cached --name-only --diff-filter=ACM -- '*.py')
|
||||
234
.gitlab-ci.yml
@@ -1,33 +1,87 @@
|
||||
# This file is a template, and might need editing before it works on your project.
|
||||
# Official language image. Look for the different tagged releases at:
|
||||
# https://hub.docker.com/r/library/python/tags/
|
||||
image: $CI_DOCKER_REGISTRY/python:3.9
|
||||
image: $CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX/python:3.11
|
||||
#commands to run in the Docker container before starting each job.
|
||||
variables:
|
||||
DOCKER_TLS_CERTDIR: ""
|
||||
BEC_CORE_BRANCH: "main"
|
||||
OPHYD_DEVICES_BRANCH: "main"
|
||||
CHILD_PIPELINE_BRANCH: $CI_DEFAULT_BRANCH
|
||||
|
||||
workflow:
|
||||
rules:
|
||||
- if: $CI_PIPELINE_SOURCE == "schedule"
|
||||
- if: $CI_PIPELINE_SOURCE == "web"
|
||||
- if: $CI_PIPELINE_SOURCE == "pipeline"
|
||||
- if: $CI_PIPELINE_SOURCE == "parent_pipeline"
|
||||
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
|
||||
- if: $CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS
|
||||
when: never
|
||||
- if: $CI_COMMIT_BRANCH
|
||||
|
||||
include:
|
||||
- template: Security/Secret-Detection.gitlab-ci.yml
|
||||
|
||||
- project: "bec/awi_utils"
|
||||
file: "/templates/check-packages-job.yml"
|
||||
inputs:
|
||||
stage: test
|
||||
path: "."
|
||||
pytest_args: "-v --random-order tests/"
|
||||
exclude_packages: ""
|
||||
|
||||
# different stages in the pipeline
|
||||
stages:
|
||||
- Formatter
|
||||
- test
|
||||
- AdditionalTests
|
||||
- End2End
|
||||
- Deploy
|
||||
|
||||
.install-qt-webengine-deps: &install-qt-webengine-deps
|
||||
- apt-get -y install libnss3 libxdamage1 libasound2 libatomic1 libxcursor1
|
||||
- export QTWEBENGINE_DISABLE_SANDBOX=1
|
||||
|
||||
.clone-repos: &clone-repos
|
||||
- echo -e "\033[35;1m Using branch $BEC_CORE_BRANCH of BEC CORE \033[0;m";
|
||||
- git clone --branch $BEC_CORE_BRANCH https://gitlab.psi.ch/bec/bec.git
|
||||
- echo -e "\033[35;1m Using branch $OPHYD_DEVICES_BRANCH of OPHYD_DEVICES \033[0;m";
|
||||
- git clone --branch $OPHYD_DEVICES_BRANCH https://gitlab.psi.ch/bec/ophyd_devices.git
|
||||
- export OHPYD_DEVICES_PATH=$PWD/ophyd_devices
|
||||
|
||||
.install-repos: &install-repos
|
||||
- pip install -e ./ophyd_devices
|
||||
- pip install -e ./bec/bec_lib[dev]
|
||||
- pip install -e ./bec/bec_ipython_client
|
||||
|
||||
.install-os-packages: &install-os-packages
|
||||
- apt-get update
|
||||
- apt-get install -y libgl1-mesa-glx libegl1-mesa x11-utils libxkbcommon-x11-0 libdbus-1-3 xvfb
|
||||
- *install-qt-webengine-deps
|
||||
|
||||
before_script:
|
||||
- if [[ "$CI_PROJECT_PATH" != "bec/bec_widgets" ]]; then
|
||||
echo -e "\033[35;1m Using branch $CHILD_PIPELINE_BRANCH of BEC Widgets \033[0;m";
|
||||
test -d bec_widgets || git clone --branch $CHILD_PIPELINE_BRANCH https://gitlab.psi.ch/bec/bec_widgets.git; cd bec_widgets;
|
||||
fi
|
||||
|
||||
formatter:
|
||||
stage: Formatter
|
||||
needs: []
|
||||
script:
|
||||
- pip install black
|
||||
- black --check --diff --color --line-length=100 ./
|
||||
- pip install black isort
|
||||
- isort --check --diff ./
|
||||
- black --check --diff --color ./
|
||||
rules:
|
||||
- if: $CI_PROJECT_PATH == "bec/bec_widgets"
|
||||
|
||||
pylint:
|
||||
stage: Formatter
|
||||
needs: []
|
||||
script:
|
||||
before_script:
|
||||
- pip install pylint pylint-exit anybadge
|
||||
- pip install -e .[dev]
|
||||
- pip install -e .[dev,pyqt6]
|
||||
script:
|
||||
- mkdir ./pylint
|
||||
- pylint ./bec_widgets --output-format=text --output=./pylint/pylint.log | tee ./pylint/pylint.log || pylint-exit $?
|
||||
- PYLINT_SCORE=$(sed -n 's/^Your code has been rated at \([-0-9.]*\)\/.*/\1/p' ./pylint/pylint.log)
|
||||
@@ -37,25 +91,135 @@ pylint:
|
||||
paths:
|
||||
- ./pylint/
|
||||
expire_in: 1 week
|
||||
rules:
|
||||
- if: $CI_PROJECT_PATH == "bec/bec_widgets"
|
||||
|
||||
pylint-check:
|
||||
stage: Formatter
|
||||
needs: []
|
||||
allow_failure: true
|
||||
before_script:
|
||||
- pip install pylint pylint-exit anybadge
|
||||
- apt-get update
|
||||
- apt-get install -y bc
|
||||
script:
|
||||
- git fetch origin $CI_MERGE_REQUEST_TARGET_BRANCH_NAME
|
||||
# Identify changed Python files
|
||||
- if [ "$CI_PIPELINE_SOURCE" == "merge_request_event" ]; then
|
||||
TARGET_BRANCH_COMMIT_SHA=$(git rev-parse origin/$CI_MERGE_REQUEST_TARGET_BRANCH_NAME);
|
||||
CHANGED_FILES=$(git diff --name-only $TARGET_BRANCH_COMMIT_SHA HEAD | grep '\.py$' || true);
|
||||
else
|
||||
CHANGED_FILES=$(git diff --name-only $CI_COMMIT_BEFORE_SHA $CI_COMMIT_SHA | grep '\.py$' || true);
|
||||
fi
|
||||
- if [ -z "$CHANGED_FILES" ]; then echo "No Python files changed."; exit 0; fi
|
||||
|
||||
- echo "Changed Python files:"
|
||||
- $CHANGED_FILES
|
||||
# Run pylint only on changed files
|
||||
- mkdir ./pylint
|
||||
- pylint $CHANGED_FILES --output-format=text | tee ./pylint/pylint_changed_files.log || pylint-exit $?
|
||||
- PYLINT_SCORE=$(sed -n 's/^Your code has been rated at \([-0-9.]*\)\/.*/\1/p' ./pylint/pylint_changed_files.log)
|
||||
- echo "Pylint score is $PYLINT_SCORE"
|
||||
|
||||
# Fail the job if the pylint score is below 9
|
||||
- if [ "$(echo "$PYLINT_SCORE < 9" | bc)" -eq 1 ]; then echo "Your pylint score is below the acceptable threshold (9)."; exit 1; fi
|
||||
artifacts:
|
||||
paths:
|
||||
- ./pylint/
|
||||
expire_in: 1 week
|
||||
rules:
|
||||
- if: $CI_PROJECT_PATH == "bec/bec_widgets"
|
||||
|
||||
tests:
|
||||
stage: test
|
||||
needs: []
|
||||
variables:
|
||||
QT_QPA_PLATFORM: "offscreen"
|
||||
QT_QPA_PLATFORM: "offscreen"
|
||||
script:
|
||||
- apt-get update
|
||||
- apt-get install -y libgl1-mesa-glx x11-utils libxkbcommon-x11-0
|
||||
- pip install .[dev]
|
||||
- coverage run --source=./bec_widgets -m pytest -v --junitxml=report.xml --random-order --full-trace ./tests
|
||||
- *clone-repos
|
||||
- *install-os-packages
|
||||
- *install-repos
|
||||
- pip install -e .[dev,pyqt6]
|
||||
- coverage run --source=./bec_widgets -m pytest -v --junitxml=report.xml --random-order --full-trace ./tests/unit_tests
|
||||
- coverage report
|
||||
- coverage xml
|
||||
coverage: '/(?i)total.*? (100(?:\.0+)?\%|[1-9]?\d(?:\.\d+)?\%)$/'
|
||||
artifacts:
|
||||
reports:
|
||||
junit: report.xml
|
||||
cobertura: coverage.xml
|
||||
coverage_report:
|
||||
coverage_format: cobertura
|
||||
path: coverage.xml
|
||||
paths:
|
||||
- tests/reference_failures/
|
||||
when: always
|
||||
|
||||
test-matrix:
|
||||
parallel:
|
||||
matrix:
|
||||
- PYTHON_VERSION:
|
||||
- "3.10"
|
||||
- "3.11"
|
||||
- "3.12"
|
||||
QT_PCKG:
|
||||
- "pyside6"
|
||||
- "pyqt6"
|
||||
|
||||
stage: AdditionalTests
|
||||
needs: []
|
||||
variables:
|
||||
QT_QPA_PLATFORM: "offscreen"
|
||||
PYTHON_VERSION: ""
|
||||
QT_PCKG: ""
|
||||
image: $CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX/python:$PYTHON_VERSION
|
||||
script:
|
||||
- *clone-repos
|
||||
- *install-os-packages
|
||||
- *install-repos
|
||||
- pip install -e .[dev,$QT_PCKG]
|
||||
- pytest -v --junitxml=report.xml --random-order ./tests/unit_tests
|
||||
allow_failure: true
|
||||
|
||||
end-2-end-conda:
|
||||
stage: End2End
|
||||
needs: []
|
||||
image: continuumio/miniconda3
|
||||
allow_failure: false
|
||||
variables:
|
||||
QT_QPA_PLATFORM: "offscreen"
|
||||
script:
|
||||
- *clone-repos
|
||||
- *install-os-packages
|
||||
- conda config --prepend channels conda-forge
|
||||
- conda config --set channel_priority strict
|
||||
- conda config --set always_yes yes --set changeps1 no
|
||||
- conda create -q -n test-environment python=3.11
|
||||
- conda init bash
|
||||
- source ~/.bashrc
|
||||
- conda activate test-environment
|
||||
|
||||
- cd ./bec
|
||||
- source ./bin/install_bec_dev.sh -t
|
||||
- cd ../
|
||||
- pip install -e ./ophyd_devices
|
||||
|
||||
- pip install -e .[dev,pyqt6]
|
||||
- cd ./tests/end-2-end
|
||||
- pytest -v --start-servers --flush-redis --random-order
|
||||
|
||||
artifacts:
|
||||
when: on_failure
|
||||
paths:
|
||||
- ./logs/*.log
|
||||
expire_in: 1 week
|
||||
|
||||
rules:
|
||||
- if: '$CI_PIPELINE_SOURCE == "schedule"'
|
||||
- if: '$CI_PIPELINE_SOURCE == "web"'
|
||||
- if: '$CI_PIPELINE_SOURCE == "pipeline"'
|
||||
- if: '$CI_PIPELINE_SOURCE == "parent_pipeline"'
|
||||
- if: '$CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "main"'
|
||||
- if: '$CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "production"'
|
||||
|
||||
semver:
|
||||
stage: Deploy
|
||||
@@ -71,31 +235,29 @@ semver:
|
||||
- git fetch --tags
|
||||
- git tag
|
||||
|
||||
# build
|
||||
- pip install python-semantic-release==7.* wheel
|
||||
# build and publish package
|
||||
- pip install python-semantic-release==9.* wheel build twine
|
||||
- export GL_TOKEN=$CI_UPDATES
|
||||
- export REPOSITORY_USERNAME=__token__
|
||||
- export REPOSITORY_PASSWORD=$CI_PYPI_TOKEN
|
||||
- >
|
||||
semantic-release publish -v DEBUG
|
||||
-D version_variable=./setup.py:__version__
|
||||
-D hvcs=gitlab
|
||||
|
||||
- semantic-release -vv version
|
||||
|
||||
# check if any artifacts were created
|
||||
- if [ ! -d dist ]; then echo No release will be made; exit 0; fi
|
||||
- twine upload dist/* -u __token__ -p $CI_PYPI_TOKEN --skip-existing
|
||||
- semantic-release publish
|
||||
|
||||
allow_failure: false
|
||||
rules:
|
||||
- if: '$CI_COMMIT_REF_NAME == "master"'
|
||||
- if: '$CI_COMMIT_REF_NAME == "main" && $CI_PROJECT_PATH == "bec/bec_widgets"'
|
||||
|
||||
# pages:
|
||||
# stage: Deploy
|
||||
# needs: ["tests"]
|
||||
# script:
|
||||
# - git clone --branch $OPHYD_DEVICES_BRANCH https://oauth2:$CI_OPHYD_DEVICES_KEY@gitlab.psi.ch/bec/ophyd_devices.git
|
||||
# - export OPHYD_DEVICES_PATH=$PWD/ophyd_devices
|
||||
# - pip install -r ./docs/source/requirements.txt
|
||||
# - apt-get install -y gcc
|
||||
# - *install-bec-services
|
||||
# - cd ./docs/source; make html
|
||||
# - curl -X POST -d "branches=$CI_COMMIT_REF_NAME" -d "token=$RTD_TOKEN" https://readthedocs.org/api/v2/webhook/beamline-experiment-control/221870/
|
||||
# rules:
|
||||
# - if: '$CI_COMMIT_REF_NAME == "master"'
|
||||
# - if: '$CI_COMMIT_REF_NAME == "production"'
|
||||
pages:
|
||||
stage: Deploy
|
||||
needs: ["semver"]
|
||||
variables:
|
||||
TARGET_BRANCH: $CI_COMMIT_REF_NAME
|
||||
rules:
|
||||
- if: "$CI_COMMIT_TAG != null"
|
||||
variables:
|
||||
TARGET_BRANCH: $CI_COMMIT_TAG
|
||||
- if: '$CI_COMMIT_REF_NAME == "main" && $CI_PROJECT_PATH == "bec/bec_widgets"'
|
||||
script:
|
||||
- curl -X POST -d "branches=$CI_COMMIT_REF_NAME" -d "token=$RTD_TOKEN" https://readthedocs.org/api/v2/webhook/bec-widgets/253243/
|
||||
|
||||
17
.gitlab/issue_templates/bug_report_template.md
Normal file
@@ -0,0 +1,17 @@
|
||||
## Bug report
|
||||
|
||||
## Summary
|
||||
|
||||
[Provide a brief description of the bug.]
|
||||
|
||||
## Expected Behavior vs Actual Behavior
|
||||
|
||||
[Describe what you expected to happen and what actually happened.]
|
||||
|
||||
## Steps to Reproduce
|
||||
|
||||
[Outline the steps that lead to the bug's occurrence. Be specific and provide a clear sequence of actions.]
|
||||
|
||||
## Related Issues
|
||||
|
||||
[Paste links to any related issues or feature requests.]
|
||||
27
.gitlab/issue_templates/documentation_update_template.md
Normal file
@@ -0,0 +1,27 @@
|
||||
## Documentation Section
|
||||
|
||||
[Specify the section or page of the documentation that needs updating]
|
||||
|
||||
## Current Information
|
||||
|
||||
[Provide the current information in the documentation that needs to be updated]
|
||||
|
||||
## Proposed Update
|
||||
|
||||
[Describe the proposed update or correction. Be specific about the changes that need to be made]
|
||||
|
||||
## Reason for Update
|
||||
|
||||
[Explain the reason for the documentation update. Include any recent changes, new features, or corrections that necessitate the update]
|
||||
|
||||
## Additional Context
|
||||
|
||||
[Include any additional context or information that can help the documentation team understand the update better]
|
||||
|
||||
## Attachments
|
||||
|
||||
[Attach any files, screenshots, or references that can assist in making the documentation update]
|
||||
|
||||
## Priority
|
||||
|
||||
[Assign a priority level to the documentation update based on its urgency. Use a scale such as Low, Medium, High]
|
||||
40
.gitlab/issue_templates/feature_request_template.md
Normal file
@@ -0,0 +1,40 @@
|
||||
## Feature Summary
|
||||
|
||||
[Provide a brief and clear summary of the new feature you are requesting]
|
||||
|
||||
## Problem Description
|
||||
|
||||
[Explain the problem or need that this feature aims to address. Be specific about the issues or gaps in the current functionality]
|
||||
|
||||
## Use Case
|
||||
|
||||
[Describe a real-world scenario or use case where this feature would be beneficial. Explain how it would improve the user experience or workflow]
|
||||
|
||||
## Proposed Solution
|
||||
|
||||
[If you have a specific solution in mind, describe it here. Explain how it would work and how it would address the problem described above]
|
||||
|
||||
## Benefits
|
||||
|
||||
[Explain the benefits and advantages of implementing this feature. Highlight how it adds value to the product or improves user satisfaction]
|
||||
|
||||
## Alternatives Considered
|
||||
|
||||
[If you've considered alternative solutions or workarounds, mention them here. Explain why the proposed feature is the preferred option]
|
||||
|
||||
## Impact on Existing Functionality
|
||||
|
||||
[Discuss how the new feature might impact or interact with existing features. Address any potential conflicts or dependencies]
|
||||
|
||||
## Priority
|
||||
|
||||
[Assign a priority level to the feature request based on its importance. Use a scale such as Low, Medium, High]
|
||||
|
||||
## Attachments
|
||||
|
||||
[Include any relevant attachments, such as sketches, diagrams, or references that can help the development team understand your feature request better]
|
||||
|
||||
## Additional Information
|
||||
|
||||
[Provide any additional information that might be relevant to the feature request, such as user feedback, market trends, or similar features in other products]
|
||||
|
||||
28
.gitlab/merge_request_templates/default.md
Normal file
@@ -0,0 +1,28 @@
|
||||
## Description
|
||||
|
||||
[Provide a brief description of the changes introduced by this merge request.]
|
||||
|
||||
## Related Issues
|
||||
|
||||
[Cite any related issues or feature requests that are addressed or resolved by this merge request. Use the gitlab syntax for linking issues, for example, `fixes #123` or `closes #123`.]
|
||||
|
||||
## Type of Change
|
||||
|
||||
- Change 1
|
||||
- Change 2
|
||||
|
||||
## Potential side effects
|
||||
|
||||
[Describe any potential side effects or risks of merging this MR.]
|
||||
|
||||
## Screenshots / GIFs (if applicable)
|
||||
|
||||
[Include any relevant screenshots or GIFs to showcase the changes made.]
|
||||
|
||||
## Additional Comments
|
||||
|
||||
[Add any additional comments or information that may be helpful for reviewers.]
|
||||
|
||||
## Definition of Done
|
||||
- [ ] Documentation is up-to-date.
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
# A comma-separated list of package or module names from where C extensions may
|
||||
# be loaded. Extensions are loading into the active Python interpreter and may
|
||||
# run arbitrary code.
|
||||
extension-pkg-allow-list=PyQt5, pyqtgraph
|
||||
extension-pkg-allow-list=PyQt6, PySide6, pyqtgraph
|
||||
|
||||
# A comma-separated list of package or module names from where C extensions may
|
||||
# be loaded. Extensions are loading into the active Python interpreter and may
|
||||
@@ -52,7 +52,7 @@ persistent=yes
|
||||
|
||||
# Minimum Python version to use for version dependent checks. Will default to
|
||||
# the version used to run pylint.
|
||||
py-version=3.9
|
||||
py-version=3.10
|
||||
|
||||
# When enabled, pylint would attempt to guess common misconfiguration and emit
|
||||
# user-friendly hints instead of false-positive error messages.
|
||||
|
||||
25
.readthedocs.yaml
Normal file
@@ -0,0 +1,25 @@
|
||||
# .readthedocs.yaml
|
||||
# Read the Docs configuration file
|
||||
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
|
||||
|
||||
# Required
|
||||
version: 2
|
||||
|
||||
# Set the version of Python and other tools you might need
|
||||
build:
|
||||
os: ubuntu-20.04
|
||||
tools:
|
||||
python: "3.10"
|
||||
|
||||
# Build documentation in the docs/ directory with Sphinx
|
||||
sphinx:
|
||||
configuration: docs/conf.py
|
||||
|
||||
# If using Sphinx, optionally build your docs in additional formats such as PDF
|
||||
# formats:
|
||||
# - pdf
|
||||
|
||||
# Optionally declare the Python requirements required to build your docs
|
||||
python:
|
||||
install:
|
||||
- requirements: docs/requirements.txt
|
||||
512
CHANGELOG.md
@@ -1,441 +1,149 @@
|
||||
# Changelog
|
||||
# CHANGELOG
|
||||
|
||||
<!--next-version-placeholder-->
|
||||
## v0.92.4 (2024-07-31)
|
||||
|
||||
## v0.25.0 (2023-09-12)
|
||||
### Fix
|
||||
|
||||
* fix: fix missmatch of signal/slot in image and motormap ([`dcc5fd7`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/dcc5fd71ee9f51767a7b2b1ed6200e89d1ef754c))
|
||||
|
||||
## v0.92.3 (2024-07-28)
|
||||
|
||||
### Fix
|
||||
|
||||
* fix(docs): moved to pyside6 ([`71873dd`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/71873ddf359516ded8f74f4d2f73df4156aa1368))
|
||||
|
||||
## v0.92.2 (2024-07-28)
|
||||
|
||||
### Fix
|
||||
|
||||
* fix(widgets): fixed import for tictactoe example ([`995a795`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/995a795060bebe25c17108d80ae0fa30463f03b1))
|
||||
|
||||
## v0.92.1 (2024-07-28)
|
||||
|
||||
### Build
|
||||
|
||||
* build(ci): install ophyd_devices in editable mode for pipelines ([`06205e0`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/06205e07903d93accf40abab153f440059f236ed))
|
||||
|
||||
### Fix
|
||||
|
||||
* fix: use SafeSlot instead of Slot ([`bc1e239`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/bc1e23944cc0e5a861e3d0b4dc5b4ac6292d5269))
|
||||
|
||||
* fix: linting ([`a3fe205`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/a3fe20500ae2ac03dcde07432f7e21ce5262ce46))
|
||||
|
||||
* fix: always add a QApplication for tests ([`61a4e32`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/61a4e32deb337ed27f2f43358b88b7266413b58e))
|
||||
|
||||
* fix: add xvfb to draw offscreen ([`3d681f7`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/3d681f77e144e74138fc5fa65630004d7c166878))
|
||||
|
||||
* fix: reset ErrorPopup singleton between tests ([`5a9ccfd`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/5a9ccfd1f6d2aacd5d86c1a34f74163b272d1ae4))
|
||||
|
||||
* fix: metaclass + QObject segfaults PyQt(cpp bindings) ([`fc57b7a`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/fc57b7a1262031a2df9e6a99493db87e766b779a))
|
||||
|
||||
### Refactor
|
||||
|
||||
* refactor: renamed DeviceMonitor2DMessage ([`4be6fd6`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/4be6fd6b83ea1048f16310f7d2bbe777b13b245e))
|
||||
|
||||
* refactor: rename device_monitor to device_monitor_2d ([`714e1e1`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/714e1e139e0033d2725fefb636c419ca137a68c6))
|
||||
|
||||
## v0.92.0 (2024-07-24)
|
||||
|
||||
### Feature
|
||||
|
||||
* ComboBox to switch between entries mode ([`f2fde2c`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/f2fde2cf5c4b219520eb0257c1c8e02ce66cde87))
|
||||
* feat(dock): dock style sheets updated ([`8ca60d5`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/8ca60d54b3cfa621172ce097fc1ba514c47ebac7))
|
||||
|
||||
* feat(general_gui): general gui added ([`5696c99`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/5696c993dc1c0da40ff3e99f754c246cc017ea32))
|
||||
|
||||
### Fix
|
||||
|
||||
* Extra columns works again ([`2123361`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/2123361ada9767333792d34de56d6f1447f67cda))
|
||||
* Resize table is user controlled ([`63e3896`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/63e389672560e505159de2014846d1506b05633f))
|
||||
* fix(dock): custom label can be created closable ([`4457ef2`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/4457ef2147e21b856c9dcaf63c81ba98002dcaf1))
|
||||
|
||||
## v0.24.2 (2023-09-12)
|
||||
* fix(device_combobox): set minimum size to 125px ([`1206e15`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/1206e153094cd8505badf69a1461572a76b4c5ad))
|
||||
|
||||
### Fix
|
||||
|
||||
* Changes e20643 ([`2657440`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/265744076cc53bd054b45c12de3bb24b23e1845c))
|
||||
|
||||
## v0.24.1 (2023-09-08)
|
||||
|
||||
### Fix
|
||||
|
||||
* Typo fixed in mca_plot.py ([`3b12f1b`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/3b12f1bc1d65772fc3613f62013809445dcead7a))
|
||||
|
||||
## v0.24.0 (2023-09-08)
|
||||
## v0.91.0 (2024-07-23)
|
||||
|
||||
### Feature
|
||||
|
||||
* HistogramLUT for mca_plot ([`ae04072`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/ae040727fc60160de8b50ac1af51fba676106e52))
|
||||
* feat(dock_area): plugin added ([`a16b87a`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/a16b87ac28d164230dd2e8020f50ff3a63cd407e))
|
||||
|
||||
## v0.23.0 (2023-09-08)
|
||||
* feat(dock_area): Added toolbar to dock area to add widgets without CLI interactions ([`cce1367`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/cce1367a72fca7206d351894bd1831b7bbfa7ec6))
|
||||
|
||||
* feat(toolbar): expandable menu actions ([`28f26e9`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/28f26e92a46063db1a194be552156a5d3b2c43e7))
|
||||
|
||||
### Fix
|
||||
|
||||
* fix(status_item): icons changed to material design ([`1b9c55a`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/1b9c55a46a0dfd8678c8e95ff64dd6e8cfb9233e))
|
||||
|
||||
* fix(plugins): Qt Designer plugins icons adjusted ([`f4844d2`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/f4844d2e067ce75dc64b89b230d7932b308ddfc2))
|
||||
|
||||
### Test
|
||||
|
||||
* test(dock_area): tests extended ([`06fab0e`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/06fab0eab926cef5677d4988fd1fce09da342dd8))
|
||||
|
||||
## v0.90.0 (2024-07-23)
|
||||
|
||||
### Feature
|
||||
|
||||
* Added key bindings and help dialog ([`ade893d`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/ade893d33d07f1190994de19b84d4021586bcbcb))
|
||||
* feat(image_widget): plugin added ([`4371168`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/43711680ba253f81fb0ffe764bcaae701b02bb49))
|
||||
|
||||
## v0.22.0 (2023-09-08)
|
||||
* feat(image_widget): all toolbar actions added ([`501eb92`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/501eb923f12fa6aaa93f5428ca78e57694edfbc0))
|
||||
|
||||
* feat(image_widget): image_widget added ([`6a9317f`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/6a9317facda896ee784c7fc1db0cd3d68cdfcf73))
|
||||
|
||||
### Fix
|
||||
|
||||
* fix(axis_setting): fix compatibility for issue with horizontal line for PyQt6 ([`1cf6e32`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/1cf6e32303f82bc7c3f3391d0e96a88bc31f29fc))
|
||||
|
||||
* fix(image_widget): image_widget autorange fixed ([`7f49893`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/7f49893d2ce3b9d02efa764f7f10442ed6ab8f3c))
|
||||
|
||||
* fix(image_widget): image widget adjusted ([`3d2ca48`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/3d2ca4855c36fe0af59a4b540caa3c8023a81773))
|
||||
|
||||
* fix(image): only single monitor image is allowed ([`fe7e542`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/fe7e542b19dc5b401523501acb74ac03edf62ad4))
|
||||
|
||||
* fix(image): raw data are saved in image item to always have precise processing ([`c15035b`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/c15035b6b769a96780a16da9e7f75af3b823654c))
|
||||
|
||||
### Refactor
|
||||
|
||||
* refactor(jupyter_console_example): added examples of standalone widgets ([`ba0d1ea`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/ba0d1ea9031b4ae2e2e73bf269fbfad973b924a5))
|
||||
|
||||
### Test
|
||||
|
||||
* test(image_widget): tests added ([`70fb276`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/70fb276fdf31dffc105435d3dfe7c5caea0b10ce))
|
||||
|
||||
## v0.89.0 (2024-07-22)
|
||||
|
||||
### Feature
|
||||
|
||||
* Added FFT ([`b984f0f`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/b984f0f36e2178690eaaec091d4a7b9443f2378f))
|
||||
* feat(themes): moved themes to bec_qthemes ([`3798714`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/3798714369adf4023f833b7749d2f46a0ec74eee))
|
||||
|
||||
## v0.21.2 (2023-09-08)
|
||||
### Unknown
|
||||
|
||||
### Fix
|
||||
* Revert "feat(themes): moved themes to bec_qthemes"
|
||||
|
||||
* Moved mask as a last step of image processing ([`87d5467`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/87d546764318679cd80e56d17d590f0e31e51504))
|
||||
This reverts commit 3798714369adf4023f833b7749d2f46a0ec74eee ([`fd6ae91`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/fd6ae91993a23a7b8dbb2cf3c4b7c3eda6d2b0f6))
|
||||
|
||||
## v0.21.1 (2023-09-08)
|
||||
|
||||
### Fix
|
||||
|
||||
* Update_signal typo fixed ([`43f03b5`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/43f03b543083da9b743828139a92f87732187dd9))
|
||||
|
||||
## v0.21.0 (2023-09-08)
|
||||
|
||||
### Feature
|
||||
|
||||
* Added functionality to load mask ([`33d1193`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/33d1193c9623b157cc74883184677a727b8e33ce))
|
||||
|
||||
### Fix
|
||||
|
||||
* Path to mask fixed ([`ef42921`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/ef42921c9a585bce8a97fc8bb251e27a9455a771))
|
||||
|
||||
## v0.20.0 (2023-09-08)
|
||||
|
||||
### Feature
|
||||
|
||||
* Added rotate and transpose logic ([`acd7a3b`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/acd7a3bc92746c7e56dc8699c4378d2ab778267f))
|
||||
|
||||
### Fix
|
||||
|
||||
* Added missing .ui file to git ([`ae8fc94`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/ae8fc9497954ca49c16d76eaeea7ecc7659c1269))
|
||||
|
||||
## v0.19.2 (2023-09-08)
|
||||
|
||||
### Fix
|
||||
|
||||
* Rotation logic fixed ([`6733371`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/6733371c2ccb4e233d9aa9421e21d627978925d7))
|
||||
|
||||
## v0.19.1 (2023-09-08)
|
||||
|
||||
### Fix
|
||||
|
||||
* Rotation always counter-clockwise ([`00385ab`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/00385abbf98add7945af170b292774d377473a70))
|
||||
|
||||
## v0.19.0 (2023-09-08)
|
||||
|
||||
### Feature
|
||||
|
||||
* Rotation of the image to the left/right by 90, 180, 270 degree ([`327f6b3`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/327f6b3df300d1f88b475973a86175379688aa9b))
|
||||
* Simulation stream with Gaussian peak in 1st quadrant ([`4fa8d46`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/4fa8d46631ff822d5465564434d173dd766a6b1a))
|
||||
* Eiger_plot.py in example folder with new GUI ([`5cbedec`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/5cbedec5d9f6a6ae763e2cb336ecb40c4d3e1ed1))
|
||||
|
||||
## v0.18.1 (2023-09-08)
|
||||
|
||||
### Fix
|
||||
|
||||
* Online changes ([`29c983f`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/29c983fb268bb2dbcfe552453501ff42442f075f))
|
||||
|
||||
## v0.18.0 (2023-09-08)
|
||||
|
||||
### Feature
|
||||
|
||||
* Eigerplot added ([`70d74c7`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/70d74c774d2b318d99c049f0f03743e77812df98))
|
||||
|
||||
## v0.17.1 (2023-09-08)
|
||||
|
||||
### Fix
|
||||
|
||||
* Start_device_consumer changed from EP device_status to scan_status ([`46a3981`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/46a3981e7dfd5ded7b7f325301d2a25c47abd16f))
|
||||
|
||||
## v0.17.0 (2023-09-07)
|
||||
|
||||
### Feature
|
||||
|
||||
* Console arguments added for Redis port, device, and sub_device tag ([`fb52b2a`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/fb52b2a8e59fca556764e0dc32bd4edc167e31d3))
|
||||
* Plot flips every second row ([`c368871`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/c36887191914d23e85a1b480dac324be0eefb963))
|
||||
* Device_consumer is getting scanID and initialise stream_consumer ([`9271b91`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/9271b91113a3bbd46f0bffdaef7b50b629e4f44f))
|
||||
* Simulation and simple 2D plot for mca card stream ([`bfef713`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/bfef71382e6a1180d750d2c800650942c5da7a21))
|
||||
|
||||
## v0.16.4 (2023-09-06)
|
||||
|
||||
### Fix
|
||||
|
||||
* Self.limit_map_data fixed to be initialised only with integers from limits ([`b62509a`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/b62509a28e970358c3ffd4f7d55c2a6bbef35970))
|
||||
|
||||
## v0.16.3 (2023-09-06)
|
||||
|
||||
### Fix
|
||||
|
||||
* Limit spinBoxes morphed to doubleSpinBoxes ([`a1264fe`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/a1264fe4e2e0c864c68786d6db16550f489b00fa))
|
||||
## v0.88.1 (2024-07-22)
|
||||
|
||||
### Documentation
|
||||
|
||||
* PyqtGraph controls in help ([`2397af1`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/2397af140f2f9ee23ed5e62ef9bdf4d0aba249a1))
|
||||
|
||||
## v0.16.2 (2023-09-06)
|
||||
* docs: readthedocs icon path fixed ([`2bcaa42`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/2bcaa4256d6daaefacb3ead8c72458d7b1498e29))
|
||||
|
||||
### Fix
|
||||
|
||||
* X and y motor can be linked again ([`f45512e`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/f45512e0ae9c189a1d26456333c5b348cd681ce7))
|
||||
* fix(plot_base): set_xy autorange moved to plotbase from waveform ([`a3dff7d`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/a3dff7decc16115c12dc6b4ef1572552368da309))
|
||||
|
||||
## v0.16.1 (2023-09-06)
|
||||
### Refactor
|
||||
|
||||
* refactor(toolbar): generalizations of the ToolBarAction ([`ad112d1`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/ad112d1f08157f6987edd48a0bacf9f669ef1997))
|
||||
|
||||
## v0.88.0 (2024-07-19)
|
||||
|
||||
### Fix
|
||||
|
||||
* Default values fixed from .yaml ([`8a6e2da`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/8a6e2daaf95cb5417951cbe3cca0cb3e909b08b4))
|
||||
* fix(waveform_widget): plot API unified with BECFigure ([`2c8764a`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/2c8764a27de89b39b717032b58465e120ec57fbc))
|
||||
|
||||
## v0.16.0 (2023-09-06)
|
||||
* fix(colormap_selector): compatibility for PyQt6 when using designer fixed ([`50135b5`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/50135b5fe90a88618291e9357f180cb19251dace))
|
||||
|
||||
### Feature
|
||||
* fix(waveform_widget): adapted for BECWidget base class ([`6eb313f`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/6eb313fa76e559d62ecd8fa8849142b83817e47c))
|
||||
|
||||
* Added help button ([`2087d19`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/2087d19d3c2349e160327880210a5cf129852f09))
|
||||
* Table can be loaded from .csv ([`15d995f`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/15d995f66b892f55526bd8b0954b6886d8f861ea))
|
||||
* Table can be exported to csv ([`772f18f`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/772f18fa09bef54c849d2fdd58e02e8dada84a4e))
|
||||
* Additional extra rows takes values from previous row ([`1235294`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/1235294b034dae50ff9a2ea93bc1a318383cbbf5))
|
||||
* Additional columns can be added through .yaml ([`fa76acb`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/fa76acbd6dda1695add1c1159c4a96c33741a4c7))
|
||||
### Test
|
||||
|
||||
### Fix
|
||||
|
||||
* Help extended ([`9fba033`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/9fba0334a0389f66344b84dd434d4d9a39b1565e))
|
||||
* Table loads number of columns correctly ([`bf12963`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/bf129632471da2e6dc5d637a5b02c321d8d3dcac))
|
||||
* Content always aligned to centre ([`74884a3`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/74884a37076cd047e2dc75e07246f73e5f93167e))
|
||||
|
||||
## v0.15.0 (2023-09-06)
|
||||
|
||||
### Feature
|
||||
|
||||
* Step for x and y can be linked or separated ([`16ab746`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/16ab746f54007ee0647b6602b7d74a4a59401705))
|
||||
* User can choose if to save coordinates when moving to absolute coordinates ([`6324199`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/632419929921fbe4e970149ce8d4e617566f71fc))
|
||||
|
||||
### Fix
|
||||
|
||||
* Table checkbox fixed ([`7e6244c`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/7e6244c5d3698e6fea944b9501064470b6c884c7))
|
||||
* Partial fix to table checkBox ([`75f5c8f`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/75f5c8fcd6e80288e1f3bc1b9c0c0b3edd1335bc))
|
||||
* Coordinates markers are updated on the map, if X, Y in table manually is changed ([`0aa667b`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/0aa667b70d48356bdda59b879baa3862c5e2e756))
|
||||
* Added float validator to the table ([`be1bd81`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/be1bd81d60373a0d9e776dc3f0d879d1bf905f7a))
|
||||
* Table bug, when deleted multiple rows ([`9d83a45`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/9d83a455e899e3018364123707064882076c4eb0))
|
||||
* Table bug, when user deleted row and wanted to go to the previous position ([`63e6d61`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/63e6d61c2e6f9cbc069c9d55c7006d18b6b34b4d))
|
||||
|
||||
## v0.14.2 (2023-09-05)
|
||||
|
||||
### Fix
|
||||
|
||||
* Bec_config initialisation by command line argument ([`b7a1b8b`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/b7a1b8bca1b89df859c9ed0ed17862bb6d533de7))
|
||||
|
||||
## v0.14.1 (2023-09-05)
|
||||
|
||||
### Fix
|
||||
|
||||
* Gui default tab changed to coordinates table ([`3c74fa5`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/3c74fa59b7b83976b13afc821c1333868e62a686))
|
||||
|
||||
## v0.14.0 (2023-09-05)
|
||||
|
||||
### Feature
|
||||
|
||||
* Enable gui button, in the case that motor movement is not finished ([`84155d2`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/84155d22640e229820fa5104975d2675f63cef31))
|
||||
* Saved coordinates are shown on the map ([`0ca665a`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/0ca665a1e91d9c5dee9af0218c2e211de8304b26))
|
||||
|
||||
### Fix
|
||||
|
||||
* Motor position points can be switched on/off if points were deleted ([`5b30dfd`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/5b30dfd43fcbe4b9941e26cab76005ffeb21d95f))
|
||||
* Highlight disapear with new motor ([`3fb8651`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/3fb8651dd5777861488928b414d5bdacb517d0e9))
|
||||
* New points do not make invisible points visible again ([`fb10551`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/fb105513e52bcd9c62dfead16e91b45ecd817612))
|
||||
* Checkbox visibility toggle is working. ([`a178c43`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/a178c434b1d9efc1795b6f5115e2a8b9685ccdf2))
|
||||
* Saved coordinates can be removed from table and from the map again ([`c32e95a`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/c32e95a57d3faec46652b413581d830698855367))
|
||||
|
||||
## v0.13.0 (2023-09-05)
|
||||
|
||||
### Feature
|
||||
|
||||
* Crosshair highlight at motor position ([`9228e5a`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/9228e5aea3d5e4192733539643654fd635c63559))
|
||||
* Increase step size double with key bindings ([`e9ef1e3`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/e9ef1e315bc7222c38c1f2f3f410f5cdff994f08))
|
||||
* Go, set, save current coordinates and keyboard shortcuts ([`5d6a328`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/5d6a328728a017eb4f1d191c96d2659800d41941))
|
||||
|
||||
### Fix
|
||||
|
||||
* Spinbox limits in ui file ([`8de08cf`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/8de08cf9ccb092b3cfa5cf751f69fbf5edd2b217))
|
||||
* Precision updated correctly ([`172ccc6`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/172ccc69056380abcddf572f668a4ddbd5d34eec))
|
||||
|
||||
## v0.12.0 (2023-09-04)
|
||||
|
||||
### Feature
|
||||
|
||||
* Config from .yaml file ([`1a67758`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/1a677584708e1c91491fe84db169103bdda488e5))
|
||||
* Removal of motor configurations from user ([`34212d4`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/34212d4d45c88a7bba75f289a25e5488ff95fc73))
|
||||
|
||||
### Fix
|
||||
|
||||
* Error message if motor do not have limits attribute ([`bf93b02`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/bf93b02cdc82086b32e2bd16f4b506c1bb76c65d))
|
||||
|
||||
### Documentation
|
||||
|
||||
* Added documentation to all classes and methods ([`4afaa1b`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/4afaa1b0ce1f29e4193e6999ecc13b1f0f662213))
|
||||
|
||||
## v0.11.0 (2023-09-04)
|
||||
|
||||
### Feature
|
||||
|
||||
* Colorbutton next to each curve in the table to be able to set up colors ([`2c6719c`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/2c6719cf390e6638cadbc814eb0c085bb45c3c6c))
|
||||
|
||||
### Fix
|
||||
|
||||
* User selected colors are preserved with the new scan ([`8e7885f`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/8e7885f36dd2812e3285c4d2d101212055644c7b))
|
||||
* Colorbutton change now symbols as well ([`6d2e1c9`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/6d2e1c9d08595a45f502287c6490905e8df3db10))
|
||||
|
||||
## v0.10.0 (2023-09-01)
|
||||
|
||||
### Feature
|
||||
|
||||
* Load and export configuration into .yaml from GUI ([`e527353`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/e5273539741a1261e69b1bf76af78c7c1ab0d901))
|
||||
* Error messages if name or entry is wrong ([`415c4ee`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/415c4ee3f232c02ee5a00a82352c7fbb0d324449))
|
||||
* Number of columns can be dynamically changed ([`65bfccc`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/65bfccce8fce158150652fead769721de805d99e))
|
||||
* Multi window interface created for extreme BL ([`69c38d6`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/69c38d67e4e9b8a30767f6f67defce6c5c2e5b16))
|
||||
|
||||
### Fix
|
||||
|
||||
* Check if num_columns is not higher that actual number of plots ([`aac6e17`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/aac6e172f6e4583e751bee00db6f381aaff8ac69))
|
||||
* Add max number of columns according to the number of plots ([`fbd71c1`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/fbd71c131386508a9ec7bb5963afefc13f8b1618))
|
||||
* More specific error messages ([`583e643`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/583e643dacac3d7aaa744751baef2da69f6f892e))
|
||||
* Bec_dispatcher.py can take multiple workers as a list ([`7bcf88d`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/7bcf88d5eb139aa3cf491185b9fb3f45aa5e39a2))
|
||||
* Config.yaml can be passed as a console argument to extreme.py ([`b8aa373`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/b8aa37321d6ac0ebd9f2237c8d2ed6594b614b57))
|
||||
* Columns span generalised for any number of columns ([`2d851b6`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/2d851b6b4eb0002e32908c2effbfb79122f18c24))
|
||||
|
||||
### Documentation
|
||||
|
||||
* Updated documentation and TODOs ([`0ebe35a`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/0ebe35ac7a144db84c323f9ecb85dfdf6de66c21))
|
||||
* Fixed documentation ([`2f7c1b9`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/2f7c1b92a9624741f6dea44fc8f3c19a8a506fd9))
|
||||
|
||||
## v0.9.0 (2023-08-29)
|
||||
|
||||
### Feature
|
||||
|
||||
* Migrate to .yaml config file instead of argparse ([`a9f1688`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/a9f16884b0b274e36fdb531b56a26343692a78f5))
|
||||
* Better color coding of curves ([`0eff18f`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/0eff18f5a074ea806d43d52ae72bf87f0187a26d))
|
||||
|
||||
## v0.8.1 (2023-08-29)
|
||||
|
||||
### Fix
|
||||
|
||||
* Added missing local .ui file ([`f0589b7`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/f0589b79ec7f50ee9d040b911d1874b4232659d5))
|
||||
|
||||
## v0.8.0 (2023-08-29)
|
||||
|
||||
### Feature
|
||||
|
||||
* User can specify tuple of (x,y) devices which wants to plot ([`3344f1b`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/3344f1b92a7e4f4ecd2e63c66aa01d3a4c325070))
|
||||
* Fit table hardcode to "gaussian_fit_worker_3" ([`3af57ab`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/3af57abc4888dfcd0224bf50708488dc8192be84))
|
||||
* Crosshair snapped to x, y data automatically, clicked coordinates glows ([`49ba6fe`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/49ba6feb3a8494336c5772a06e9569d611fc240a))
|
||||
* Crosshair snaps to data, but it is activated with button due to debug ([`223f102`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/223f102aa9f0e625fecef37c827c55f9062330d7))
|
||||
* Dap fit plotted as curve, data as scatter ([`118f6af`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/118f6af2b97188398a3dd0e2121f73328c53465b))
|
||||
* Oneplot can receive one motor and one monitor signal ([`ff545bf`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/ff545bf5c9e707f2dd9b43f9d059aa8605f3916b))
|
||||
* Oneplot initialized as an example app for plotting motor vs monitor signals + dispatcher loop over msg ([`98c0c64`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/98c0c64e8577f7e40eb0324dfe97d0ae4670c3a2))
|
||||
|
||||
### Fix
|
||||
|
||||
* User can disable dap_worker and just choose signals to plot ([`cab5354`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/cab53543e644921df69c57c70ad2b3a03bbafcc1))
|
||||
* Crosshair snaps correctly to x dataset ([`2ed5d72`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/2ed5d7208c42f8a1175a49236d706ebf503875e4))
|
||||
|
||||
## v0.7.0 (2023-08-28)
|
||||
|
||||
### Feature
|
||||
|
||||
* Labels of current motors are shown in motors limits ([`413e435`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/413e4356cfde6e2432682332e470eb69427ad397))
|
||||
* Total number of points, scatter size and number of point to dim after last position can be changed from GUI ([`e0b52fc`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/e0b52fcedca46d913d1677b45f9815eccd92e8f7))
|
||||
* Speed and frequency can be updated from GUI ([`f391a2f`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/f391a2fd004f1dc8187cfe12d60f856427ae01ec))
|
||||
* Speed and frequency is retrieved from devices ([`ce98164`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/ce9816480b82373895b602d1a1bca7d1d9725f01))
|
||||
* Delete coordinate table row by DELETE or BACKSPACE key ([`5dd0af6`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/5dd0af6894a5d97457d60ef18b098e40856e4875))
|
||||
* Motor selection ([`cab32be`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/cab32be0092185870b5a12398103475342c8b1fd))
|
||||
* New GUI ([`0226188`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/0226188079f1dac4eece6b1a6fa330620f1504bc))
|
||||
* Keyboard shortcut to go to coordinates ([`3c0e595`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/3c0e5955d40a67935b8fb064d5c52fd3f29bd1a1))
|
||||
* Ability to choose how many points should be dimmed before reaching the threshold + total number of point which should be stored. ([`9eae697`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/9eae697df8a2f3961454db9ed397353f110c0e67))
|
||||
* Stop movement function, one callback function for 2 motors, move_finished is emitted in move_motor function not in callback ([`187c748`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/187c748e87264448d5026d9fa2f15b5fc9a55949))
|
||||
* Controls are disabled while motor is moving and enabled when motor movement is finished ([`ed84293`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/ed842931971fbf87ed2f3e366eb822531ef5aacc))
|
||||
* Motor coordinates are now scatter instead of image ([`3f6d5c6`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/3f6d5c66411459703c402f7449e8b1abae9a2b08))
|
||||
* Going to absolute coordinates saves coordinate in the table for later use with tag ([`8be98c9`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/8be98c9bb6af941a69c593c62d5c52339d2262bc))
|
||||
* Table with coordinates getting initial coordinates of motor ([`92388c3`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/92388c3cab7e024978aaa2906afbd698015dec66))
|
||||
* Motor move to absolute (X,Y) coordinates ([`cbe27e4`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/cbe27e46cfb6282c71844641e1ed6059e8fa96bf))
|
||||
* Motor limits can be changed by spinBoxes ([`2d1665c`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/2d1665c76b8174d9fffa3442afa98fe1ea6ac207))
|
||||
* Switch for keyboard shortcuts for motor movement ([`cac4562`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/cac45626fc9a315f9012b110760a92e27e5ed226))
|
||||
* Setting map according to motor limits ([`512e698`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/512e698e26d9eef05b4f430475ccc268b68ad632))
|
||||
* Map of motor position ([`e6952a6`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/e6952a6d13c84487fd6ab08056f1f5b46d594b8a))
|
||||
* Motor_example.py created, motor samx and samy can be moved by buttons ([`947ba9f`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/947ba9f8b730e96082cb51ff6894734a0e119ca1))
|
||||
|
||||
### Fix
|
||||
|
||||
* Line_plot.py default changed back to "gauss_bpm" ([`64708bc`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/64708bc1b2e6a4256da9123d0215fc87e0afa455))
|
||||
* Motor selection is disabled while motor is moving ([`c7e35d7`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/c7e35d7da69853343aa7eee53c8ad988eb490d93))
|
||||
* Init_motor_map receive motor position from motor_thread ([`95ead71`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/95ead7117e59e0979aec51b85b49537ab728cad4))
|
||||
* Motor movement absolute fixed - movement by thread ([`11aa15f`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/11aa15fefda7433e885cc8586f93c97af83b0c48))
|
||||
|
||||
## v0.6.3 (2023-08-17)
|
||||
|
||||
### Fix
|
||||
|
||||
* Crosshair handles dynamic changes of number of curves in 1D plot ([`242737b`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/242737b516af7c524a6c8a98db566815f0f4ab65))
|
||||
|
||||
### Documentation
|
||||
|
||||
* Crosshair class documentation ([`8a60cad`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/8a60cad9187df2b2bc93dc78dd01ceb42df9c9af))
|
||||
|
||||
## v0.6.2 (2023-08-17)
|
||||
|
||||
### Fix
|
||||
|
||||
* Correct coordinates for cursor table ([`ce54daf`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/ce54daf754cb2410790216585467c0ffcc8e3587))
|
||||
|
||||
## v0.6.1 (2023-08-14)
|
||||
|
||||
### Fix
|
||||
|
||||
* Crosshair snaps to correct coordinates also with logx and logy ([`167a891`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/167a891c474b09ef7738e473c4a2e89dbbcbe881))
|
||||
|
||||
## v0.6.0 (2023-08-11)
|
||||
|
||||
### Feature
|
||||
|
||||
* New GUI for line_plot.py ([`b57b3bb`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/b57b3bb1afc7c85acc7ed328ac8a219f392869f1))
|
||||
* Cursor universal signals ([`20e9516`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/20e951659558b7fc023e357bfe07d812c5fd020a))
|
||||
|
||||
## v0.5.0 (2023-08-11)
|
||||
|
||||
### Feature
|
||||
|
||||
* Add generic connect function for slots ([`6a3df34`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/6a3df34cdfbec2434153362ded630305e5dc5e28))
|
||||
* Add possibility to provide service config ([`8c9a9c9`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/8c9a9c93535ee77c0622b483a3157af367ebce1f))
|
||||
|
||||
### Fix
|
||||
|
||||
* Dispatcher argparse and scan_plot tests ([`67f619e`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/67f619ee897e0040c6310e67d69fbb2e0685293d))
|
||||
* Gui event removing bugs ([`a9dd191`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/a9dd191629295ca476e2f9a1b9944ff355216583))
|
||||
|
||||
## v0.4.0 (2023-08-11)
|
||||
|
||||
### Feature
|
||||
|
||||
* Cursor universal for 1D and 2D ([`f75554b`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/f75554bd7b072207847956a8720b9a62c20ba2c8))
|
||||
* Added qt_utils package with general Crosshair function ([`5353fed`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/5353fed7bfe1819819fa3348ec93d2d0ba540628))
|
||||
* 2D plot updating ([`d32088b`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/d32088b643a4d0613c32fb464a0a55a3b6b684d6))
|
||||
* Metadata available on_dap_update ([`18b5d46`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/18b5d46678619a972815532629ce96c121f5fcc9))
|
||||
* Plotting from streamer ([`bb806c1`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/bb806c149dee88023ecb647b523cbd5189ea9001))
|
||||
* Added Legend to plot ([`0feca4b`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/0feca4b1578820ec1f5f3ead3073e4d45c23798b))
|
||||
* Cursor coordinate as a QTable ([`a999f76`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/a999f7669a12910ad66e10a6d2e75197b2dce1c2))
|
||||
* Changed from PlotItem to GraphicsLayoutWidget, added LabelItem ([`075cc79`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/075cc79d6fa011803cf4a06fbff8faa951c1b59f))
|
||||
* Add display_ui_file.py ([`91d8ffa`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/91d8ffacffcbeebdf7623caf62e07244c4dcee16))
|
||||
* Add disconnect_dap_slot ([`1325704`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/1325704750ebab897e3dcae80c9d455bfbbf886f))
|
||||
* Inherit from GraphicsView for consistency with 2D plot ([`d8c101c`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/d8c101cdd7f960a152a1f318911cac6eecf6bad4))
|
||||
* Add BECScanPlot2D ([`67905e8`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/67905e896c81383f57c268db544b3314104bda38))
|
||||
* Emit the full bec message to slots ([`1bb3020`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/1bb30207038f3a54c0e96dbbbcd1ea7f6c70eca2))
|
||||
|
||||
### Fix
|
||||
|
||||
* Q selection for gui_event signal ([`0bf452a`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/0bf452ad1b7d9ad941e2ef4b8d61ec4ed5266415))
|
||||
* Fixed logic in data subscription ([`c2d469b`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/c2d469b4543fcf237b274399b83969cc2213b61b))
|
||||
* Scan_plot to accept metadata from dap signal ([`7bec0b5`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/7bec0b5e6c1663670f8fcc2fc6aa6c8b6df28b61))
|
||||
* Plotting latest 1d curves ([`378be81`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/378be81bf6dd5e9239f8f1fb908cafc97161c79d))
|
||||
* Testing the data structure of plotting ([`4fb0a3b`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/4fb0a3b058957f5b37227ff7c8e9bdf5259a1cde))
|
||||
* Fix examples when run directly as a script ([`cd11ee5`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/cd11ee51c1c725255e748a32b89a74487e84a631))
|
||||
* Module paths ([`e7f644c`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/e7f644c5079a8665d7d872eb0b27ed7da6cbd078))
|
||||
|
||||
## v0.3.0 (2023-07-19)
|
||||
|
||||
### Feature
|
||||
|
||||
* Add auto-computed color_list from colormaps ([`3e1708b`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/3e1708bf48bc15a25c0d01242fff28d6db868e02))
|
||||
* Add functionality for plotting multiple signals ([`10e2906`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/10e29064455f50bc3b66c55b4361575957db1489))
|
||||
* Added lineplot widget ([`989a3f0`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/989a3f080839b98f1e1c2118600cddf449120124))
|
||||
* Added ctrl_c from grum ([`8fee13a`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/8fee13a67bef3ed6ed6de9d47438f04687f548d8))
|
||||
|
||||
### Fix
|
||||
|
||||
* Add warning for non-existing signalz ([`48075e4`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/48075e4fe3187f6ac8d0b61f94f8df73b8fd6daf))
|
||||
* Documentation and bugfix for mouse_moved ([`a460f3c`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/a460f3c0bd7b9e106a758bc330f361868407b1e3))
|
||||
|
||||
### Documentation
|
||||
|
||||
* Add notes about qt designer install via conda-forge ([`d8038a8`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/d8038a8cd0efa3a16df403390164603e4e8afdd8))
|
||||
* Added license ([`db2d33e`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/db2d33e8912dc493cce9ee7f09d8336155110079))
|
||||
|
||||
## v0.2.1 (2023-07-13)
|
||||
|
||||
### Fix
|
||||
|
||||
* Fixed setup config (wrong name) ([`947db1e`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/947db1e0f32b067e67f94a7c8321da5194b1547b))
|
||||
* Fixed bec_lib dependency ([`86f4def`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/86f4deffd899111e8997010487ec54c6c62c43ab))
|
||||
|
||||
## v0.2.0 (2023-07-13)
|
||||
|
||||
### Feature
|
||||
|
||||
* Move ivan's qtwidgets to bec-widgets ([`34e5ed2`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/34e5ed2cf7e6128a51c110db8870d9560f2b2831))
|
||||
|
||||
## v0.1.0 (2023-07-11)
|
||||
|
||||
### Feature
|
||||
|
||||
* Added config plotter ([`db274c6`](https://gitlab.psi.ch/bec/bec-widgets/-/commit/db274c644f643f830c35b6a92edd328bf7e24f59))
|
||||
* test(waveform_widget): test added ([`8d764e2`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/8d764e2d46a1e017dadc3c4630648c1ca708afc2))
|
||||
|
||||
72
README.md
@@ -1,2 +1,74 @@
|
||||
# BEC Widgets
|
||||
|
||||
BEC Widgets is a GUI framework designed for interaction with [BEC (Beamline Experiment Control)](https://gitlab.psi.ch/bec/bec).
|
||||
## Installation
|
||||
|
||||
Use the package manager [pip](https://pip.pypa.io/en/stable/) to install BEC Widgets:
|
||||
|
||||
```bash
|
||||
pip install bec_widgets PyQt6
|
||||
```
|
||||
|
||||
For development purposes, you can clone the repository and install the package locally in editable mode:
|
||||
|
||||
```bash
|
||||
git clone https://gitlab.psi.ch/bec/bec-widgets
|
||||
cd bec_widgets
|
||||
pip install -e .[dev,pyqt6]
|
||||
```
|
||||
|
||||
BEC Widgets currently supports both Pyside6 and PyQt6, however, no default distribution is specified. As a result, users must install one of the supported
|
||||
Python Qt distributions manually.
|
||||
|
||||
To select a specific Python Qt distribution, install the package with an additional tag:
|
||||
|
||||
```bash
|
||||
pip install bec_widgets[pyqt6]
|
||||
```
|
||||
or
|
||||
|
||||
```bash
|
||||
pip install bec_widgets[pyside6]
|
||||
```
|
||||
## Documentation
|
||||
|
||||
Documentation of BEC Widgets can be found [here](https://bec-widgets.readthedocs.io/en/latest/). The documentation of the BEC can be found [here](https://bec.readthedocs.io/en/latest/).
|
||||
|
||||
## Contributing
|
||||
|
||||
All commits should use the Angular commit scheme:
|
||||
|
||||
> #### <a name="commit-header"></a>Angular Commit Message Header
|
||||
>
|
||||
> ```
|
||||
> <type>(<scope>): <short summary>
|
||||
> │ │ │
|
||||
> │ │ └─⫸ Summary in present tense. Not capitalized. No period at the end.
|
||||
> │ │
|
||||
> │ └─⫸ Commit Scope: animations|bazel|benchpress|common|compiler|compiler-cli|core|
|
||||
> │ elements|forms|http|language-service|localize|platform-browser|
|
||||
> │ platform-browser-dynamic|platform-server|router|service-worker|
|
||||
> │ upgrade|zone.js|packaging|changelog|docs-infra|migrations|ngcc|ve|
|
||||
> │ devtools
|
||||
> │
|
||||
> └─⫸ Commit Type: build|ci|docs|feat|fix|perf|refactor|test
|
||||
> ```
|
||||
>
|
||||
> The `<type>` and `<summary>` fields are mandatory, the `(<scope>)` field is optional.
|
||||
|
||||
> ##### Type
|
||||
>
|
||||
> Must be one of the following:
|
||||
>
|
||||
> * **build**: Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm)
|
||||
> * **ci**: Changes to our CI configuration files and scripts (examples: CircleCi, SauceLabs)
|
||||
> * **docs**: Documentation only changes
|
||||
> * **feat**: A new feature
|
||||
> * **fix**: A bug fix
|
||||
> * **perf**: A code change that improves performance
|
||||
> * **refactor**: A code change that neither fixes a bug nor adds a feature
|
||||
> * **test**: Adding missing tests or correcting existing tests
|
||||
|
||||
## License
|
||||
|
||||
[BSD-3-Clause](https://choosealicense.com/licenses/bsd-3-clause/)
|
||||
BIN
bec_widgets/assets/app_icons/BEC-Dark.png
Normal file
|
After Width: | Height: | Size: 1.6 MiB |
BIN
bec_widgets/assets/app_icons/bec_widgets_icon.png
Normal file
|
After Width: | Height: | Size: 1.7 MiB |
BIN
bec_widgets/assets/app_icons/terminal_icon.png
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
bec_widgets/assets/designer_icons/code.png
Normal file
|
After Width: | Height: | Size: 4.3 KiB |
BIN
bec_widgets/assets/designer_icons/color_button.png
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
BIN
bec_widgets/assets/designer_icons/colormap_selector.png
Normal file
|
After Width: | Height: | Size: 6.7 KiB |
BIN
bec_widgets/assets/designer_icons/device_box.png
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
bec_widgets/assets/designer_icons/device_combo_box.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
bec_widgets/assets/designer_icons/device_line_edit.png
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
bec_widgets/assets/designer_icons/dock_area.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
bec_widgets/assets/designer_icons/games.png
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
BIN
bec_widgets/assets/designer_icons/image.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
bec_widgets/assets/designer_icons/motor_map.png
Normal file
|
After Width: | Height: | Size: 7.1 KiB |
BIN
bec_widgets/assets/designer_icons/position_indicator.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
bec_widgets/assets/designer_icons/queue.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
bec_widgets/assets/designer_icons/ring_progress.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
bec_widgets/assets/designer_icons/scan_control.png
Normal file
|
After Width: | Height: | Size: 4.3 KiB |
BIN
bec_widgets/assets/designer_icons/spinner.png
Normal file
|
After Width: | Height: | Size: 6.0 KiB |
BIN
bec_widgets/assets/designer_icons/status.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
bec_widgets/assets/designer_icons/stop.png
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
BIN
bec_widgets/assets/designer_icons/text.png
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
bec_widgets/assets/designer_icons/toggle.png
Normal file
|
After Width: | Height: | Size: 4.6 KiB |
BIN
bec_widgets/assets/designer_icons/waveform.png
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
BIN
bec_widgets/assets/designer_icons/web.png
Normal file
|
After Width: | Height: | Size: 8.5 KiB |
3
bec_widgets/assets/status_icons/error.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="48px" viewBox="0 -960 960 960" width="48px" fill="#EA3323">
|
||||
<path d="M479.85-265.87q19.8 0 32.69-12.46 12.9-12.46 12.9-32.26 0-19.8-12.75-32.98-12.74-13.17-32.54-13.17-19.8 0-32.69 13.16-12.9 13.15-12.9 32.95 0 19.8 12.75 32.28 12.74 12.48 32.54 12.48Zm-36.46-166.56h79.22v-262.61h-79.22v262.61Zm36.95 366.56q-86.2 0-161.5-32.39-75.3-32.4-131.74-88.84-56.44-56.44-88.84-131.73-32.39-75.3-32.39-161.59t32.39-161.67q32.4-75.37 88.75-131.34t131.69-88.62q75.34-32.65 161.67-32.65 86.34 0 161.78 32.61 75.45 32.6 131.37 88.5 55.93 55.89 88.55 131.45 32.63 75.56 32.63 161.87 0 86.29-32.65 161.58t-88.62 131.48q-55.97 56.18-131.42 88.76-75.46 32.58-161.67 32.58Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 718 B |
3
bec_widgets/assets/status_icons/not_connected.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="48px" viewBox="0 -960 960 960" width="48px" fill="#EA3323">
|
||||
<path d="m759.04-283.09-63.13-62q49.31-9.43 84.44-46.02 35.13-36.59 35.13-86.14 0-55.49-39.42-95.08-39.43-39.58-94.93-39.58h-153.3v-79.79h152.74q89.28 0 151.7 62.71Q894.7-566.28 894.7-477q0 63.7-38.26 115.96-38.27 52.26-97.4 77.95ZM596.83-443.61l-65.66-66.78h110.05v66.78h-44.39ZM804.96-56 58.48-802.48 106-850l746.48 746.48L804.96-56ZM443.22-265.87H279.43q-89.28 0-151.7-62.42Q65.3-390.72 65.3-480q0-72.57 43.09-129.54 43.09-56.98 112.09-76.07l70.13 70.7h-11.18q-55.73 0-95.32 39.3-39.59 39.31-39.59 95.61t39.66 95.61q39.66 39.3 95.5 39.3h163.54v79.22ZM319.35-446.61v-66.78h77.3l66.78 66.78H319.35Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 721 B |
3
bec_widgets/assets/status_icons/refresh.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="48px" viewBox="0 -960 960 960" width="48px" fill="#FFFF55">
|
||||
<path d="M478.3-145.87q-138.65 0-236.39-97.74-97.74-97.74-97.74-236.25t97.74-236.68q97.74-98.16 236.39-98.16 88.4 0 155.45 35.76 67.04 35.76 115.86 98.9V-814.7h66.78v274.92H540.91V-606h165.74q-38.56-57.74-95.3-93.33-56.74-35.58-133.05-35.58-106.88 0-180.89 73.98-74.02 73.99-74.02 180.83 0 106.84 74.02 180.93 74.02 74.08 180.91 74.08 80.16 0 147.74-46.08 67.59-46.09 95.16-121.83H803q-29.56 110.65-119.67 178.89-90.1 68.24-205.03 68.24Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 559 B |
3
bec_widgets/assets/status_icons/running.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="48px" viewBox="0 -960 960 960" width="48px" fill="#75FB4C">
|
||||
<path d="m419.87-289.52 289.22-289.22-57.31-56.87L419.87-403.7 304.96-518.61l-56.31 56.87 171.22 172.22Zm60.21 223.65q-85.47 0-161.01-32.39-75.53-32.4-131.97-88.84-56.44-56.44-88.84-131.89-32.39-75.46-32.39-160.93 0-86.47 32.39-162.01 32.4-75.53 88.75-131.5t131.85-88.62q75.5-32.65 161.01-32.65 86.52 0 162.12 32.61 75.61 32.6 131.53 88.5 55.93 55.89 88.55 131.45Q894.7-566.58 894.7-480q0 85.55-32.65 161.07-32.65 75.53-88.62 131.9-55.97 56.37-131.42 88.77-75.46 32.39-161.93 32.39Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 604 B |
3
bec_widgets/assets/status_icons/warning.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="48px" viewBox="0 -960 960 960" width="48px" fill="#F19E39">
|
||||
<path d="M27.56-112.65 480-894.7l452.44 782.05H27.56Zm456.62-125.48q13.15 0 22.61-9.64 9.47-9.65 9.47-22.8t-9.64-22.33q-9.65-9.19-22.8-9.19t-22.61 9.36q-9.47 9.36-9.47 22.51 0 13.15 9.64 22.62 9.65 9.47 22.8 9.47ZM454-348h60v-219.48h-60V-348Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 364 B |
3
bec_widgets/assets/toolbar_icons/add.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="48px" viewBox="0 -960 960 960" width="48px" fill="#FFFFFF">
|
||||
<path d="M440.39-440.39H185.87v-79.22h254.52V-774.7h79.22v255.09H774.7v79.22H519.61v254.52h-79.22v-254.52Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 228 B |
3
bec_widgets/assets/toolbar_icons/attach_all.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="48px" viewBox="0 -960 960 960" width="48px" fill="#FFFFFF">
|
||||
<path d="m145.26-88.13-57.13-57.13 137.39-137.39H105.87v-79.22h256v256h-79.22v-119.65L145.26-88.13Zm669.48 0L677.91-225.52v119.65h-79.78v-256H854.7v79.22H734.48l137.39 137.39-57.13 57.13Zm-708.87-510v-79.78h119.65L88.13-814.74l57.13-57.13 137.39 137.39V-854.7h79.22v256.57h-256Zm492.26 0V-854.7h79.78v120.22l137.83-138.39 57.13 57.13-138.39 137.83H854.7v79.78H598.13Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 489 B |
3
bec_widgets/assets/toolbar_icons/auto_range.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="48px" viewBox="0 -960 960 960" width="48px" fill="#FFFFFF">
|
||||
<path d="M114.02-114.02v-308.13h68.13v192.02l547.72-547.72H537.85v-68.37h308.37v308.37h-68.37v-192.02L230.13-182.15h192.02v68.13H114.02Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 258 B |
3
bec_widgets/assets/toolbar_icons/compare.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="48px" viewBox="0 -960 960 960" width="48px" fill="#FFFFFF">
|
||||
<path d="m311.5-154.02-47.74-47.74 116.94-116.7H74.02v-68.37H380.7L263.76-503.76l47.74-47.74 198.98 198.74L311.5-154.02Zm337-254.72L449.76-607.48 648.5-806.22l47.74 47.74-116.7 116.94h306.68v68.37H579.54l116.7 116.69-47.74 47.74Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 351 B |
4
bec_widgets/assets/toolbar_icons/connection.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="48px" viewBox="0 0 24 24" width="48px" fill="#FFFFFF">
|
||||
<path d="M0 0h24v24H0V0z" fill="none"/>
|
||||
<path d="M17 7h-4v2h4c1.65 0 3 1.35 3 3s-1.35 3-3 3h-4v2h4c2.76 0 5-2.24 5-5s-2.24-5-5-5zm-6 8H7c-1.65 0-3-1.35-3-3s1.35-3 3-3h4V7H7c-2.76 0-5 2.24-5 5s2.24 5 5 5h4v-2zm-3-4h8v2H8z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 341 B |
3
bec_widgets/assets/toolbar_icons/device_box.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="48px" viewBox="0 -960 960 960" width="48px" fill="#FFFFFF">
|
||||
<path d="M403.39-158.74 82.13-480l321.26-321.26v642.52Zm153.22 0v-642.52L878.44-480 556.61-158.74Zm81.57-195.74L763.13-480 638.18-605.52v251.04Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 266 B |
3
bec_widgets/assets/toolbar_icons/drag_pan_mode.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="48px" viewBox="0 -960 960 960" width="48px" fill="#FFFFFF">
|
||||
<path d="M480-54 303.43-230.56 361-288.13l79.39 78.83v-231.09H209.3L283.13-366l-57.57 57.57L54-480l172.56-172.57L284.13-595l-74.83 75.39h231.09v-231.65L366-676.87l-57.57-57.57L480-906l171.57 171.56L594-676.87l-74.39-74.39v231.65h231.65L676.87-594l57.57-57.57L906-480 734.44-308.43 676.87-366l74.39-74.39H519.61v231.09L599-288.13l57.57 57.57L480-54Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 470 B |
9
bec_widgets/assets/toolbar_icons/export.svg
Normal file
@@ -0,0 +1,9 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="48px" viewBox="0 0 24 24" width="48px"
|
||||
fill="#FFFFFF">
|
||||
<g>
|
||||
<rect fill="none" height="24" width="24"/>
|
||||
</g>
|
||||
<g>
|
||||
<path d="M18,15v3H6v-3H4v3c0,1.1,0.9,2,2,2h12c1.1,0,2-0.9,2-2v-3H18z M7,9l1.41,1.41L11,7.83V16h2V7.83l2.59,2.58L17,9l-5-5L7,9z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 371 B |
11
bec_widgets/assets/toolbar_icons/fft.svg
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 100 96" version="1.1" xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/"
|
||||
style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<rect id="Artboard1" x="0" y="0" width="100" height="96.486" style="fill:none;"/>
|
||||
<g id="Artboard11" serif:id="Artboard1">
|
||||
<path d="M11.379,24.832C11.379,24.261 11.843,23.798 12.414,23.798C18.11,23.798 20.117,19.072 22.06,14.503C23.704,10.634 25.403,6.634 29.483,6.634C33.902,6.634 35.376,12.91 36.934,19.555C38.473,26.113 40.065,32.893 44.138,32.893C48.25,32.893 50.78,28.962 53.457,24.8C56.279,20.412 59.198,15.876 64.31,15.876C69.322,15.876 72.165,20.305 74.915,24.588C77.707,28.935 80.343,33.04 84.999,33.04C85.571,33.04 86.034,33.503 86.034,34.075C86.034,34.647 85.571,35.11 84.999,35.11C79.212,35.11 76.004,30.115 73.175,25.708C70.612,21.716 68.192,17.946 64.31,17.946C60.328,17.946 57.835,21.819 55.197,25.92C52.338,30.366 49.381,34.963 44.138,34.963C38.425,34.963 36.643,27.371 34.92,20.028C33.613,14.46 32.262,8.703 29.482,8.703C26.953,8.703 25.71,11.2 23.963,15.311C21.964,20.014 19.479,25.867 12.413,25.867C11.843,25.867 11.379,25.403 11.379,24.832M44.361,44.584C43.504,44.584 42.557,44.882 42.557,45.739L42.586,50.703L39.522,50.703L43.922,61.878L48.807,50.703L45.691,50.703L45.604,46.255C45.602,45.398 45.218,44.584 44.361,44.584ZM6.034,37.487L6.034,6.674L5,6.674L5,38.522L95,38.522L95,37.487L6.034,37.487M77.414,91.881L77.414,63.849C77.414,63.277 76.951,62.814 76.379,62.814C75.808,62.814 75.345,63.277 75.345,63.849L75.345,91.881C75.345,92.045 75.391,92.194 75.458,92.332L61.955,92.332C62.022,92.194 62.068,92.045 62.068,91.881L62.068,82.718C62.068,82.146 61.605,81.683 61.034,81.683C60.462,81.683 59.999,82.146 59.999,82.718L59.999,91.881C59.999,92.045 60.045,92.194 60.112,92.332L45.059,92.332C45.126,92.194 45.172,92.045 45.172,91.881L45.172,75.943C45.172,75.372 44.709,74.909 44.138,74.909C43.567,74.909 43.104,75.372 43.104,75.943L43.104,91.881C43.104,92.045 43.15,92.194 43.217,92.332L23.852,92.332C23.92,92.194 23.966,92.045 23.966,91.881L23.966,63.849C23.966,63.277 23.502,62.814 22.931,62.814C22.36,62.814 21.897,63.277 21.897,63.849L21.897,91.881C21.897,92.045 21.943,92.194 22.011,92.332L6.034,92.332L6.034,62.881L5,62.881L5,93.366L95,93.366L95,92.332L77.301,92.332C77.368,92.194 77.414,92.045 77.414,91.881"
|
||||
style="fill:white;fill-rule:nonzero;stroke:white;stroke-width:5px;"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
3
bec_widgets/assets/toolbar_icons/fitting_parameters.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="48px" viewBox="0 -960 960 960" width="48px" fill="#FFFFFF">
|
||||
<path d="m78.89-112.59 263.02-367.17h202l303.5-354.22v721.39H78.89Zm62.24-262.74-54.7-39.78 166.59-233.02h201L640.46-864.8l51.45 44.26-205.82 240.78H287.33l-146.2 204.43Zm70.54 194.37h567.37v-468.78L574.98-411.63H376.22L211.67-180.96Zm567.37 0Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 366 B |
4
bec_widgets/assets/toolbar_icons/history.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="48px" viewBox="0 0 24 24" width="48px" fill="#FFFFFF">
|
||||
<path d="M0 0h24v24H0V0z" fill="none"/>
|
||||
<path d="M13 3c-4.97 0-9 4.03-9 9H1l3.89 3.89.07.14L9 12H6c0-3.87 3.13-7 7-7s7 3.13 7 7-3.13 7-7 7c-1.93 0-3.68-.79-4.94-2.06l-1.42 1.42C8.27 19.99 10.51 21 13 21c4.97 0 9-4.03 9-9s-4.03-9-9-9zm-1 5v5l4.25 2.52.77-1.28-3.52-2.09V8z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 392 B |
3
bec_widgets/assets/toolbar_icons/image.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="48px" viewBox="0 -960 960 960" width="48px" fill="#FFFFFF">
|
||||
<path d="M185.09-105.87q-32.51 0-55.87-23.35-23.35-23.36-23.35-55.87v-589.82q0-32.74 23.35-56.26 23.36-23.53 55.87-23.53h589.82q32.74 0 56.26 23.53 23.53 23.52 23.53 56.26v589.82q0 32.51-23.53 55.87-23.52 23.35-56.26 23.35H185.09Zm43.56-166.04h503.7L578-481.48l-132 171-93-127-124.35 165.57Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 413 B |
3
bec_widgets/assets/toolbar_icons/image_autorange.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="48px" viewBox="0 -960 960 960" width="48px" fill="#FFFFFF">
|
||||
<path d="M283.85-289.91h67.41l42.32-112.66h172.59l43.33 112.66h68.85l-164-428h-67.5l-163 428Zm127.74-165.72 67.34-182.87H481l68.41 182.87H411.59Zm68.53 381.61q-86.32 0-160.51-31t-128.89-85.7q-54.7-54.7-85.7-128.89-31-74.19-31-160.51 0-85.31 30.94-159.4t85.7-128.9q54.76-54.8 128.95-86.3t160.51-31.5q85.31 0 159.42 31.47 74.1 31.47 128.91 86.27 54.82 54.8 86.29 128.88 31.48 74.08 31.48 159.6 0 86.2-31.5 160.39-31.5 74.19-86.3 128.95-54.81 54.76-128.9 85.7-74.09 30.94-159.4 30.94ZM480-480Zm-.04 337.85q144.08 0 240.99-96.74 96.9-96.74 96.9-241.07 0-144.32-96.86-241.11-96.86-96.78-240.95-96.78-144.08 0-240.99 96.74-96.9 96.74-96.9 241.07 0 144.32 96.86 241.11 96.86 96.78 240.95 96.78Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 809 B |
9
bec_widgets/assets/toolbar_icons/import.svg
Normal file
@@ -0,0 +1,9 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="48px" viewBox="0 0 24 24" width="48px"
|
||||
fill="#FFFFFF">
|
||||
<g>
|
||||
<rect fill="none" height="24" width="24"/>
|
||||
</g>
|
||||
<g>
|
||||
<path d="M18,15v3H6v-3H4v3c0,1.1,0.9,2,2,2h12c1.1,0,2-0.9,2-2v-3H18z M17,11l-1.41-1.41L13,12.17V4h-2v8.17L8.41,9.59L7,11l5,5 L17,11z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 377 B |
3
bec_widgets/assets/toolbar_icons/line_axis.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="48px" viewBox="0 -960 960 960" width="48px" fill="#FFFFFF">
|
||||
<path d="M110.39-110.39v-89.57l77.52-77.52v167.09h-77.52Zm165.57 0v-250.13l77.52-77.52v327.65h-77.52Zm165.56 0v-327.65l77.52 77.95v249.7h-77.52Zm165.57 0v-250.83l77.52-76.96v327.79h-77.52Zm165.56 0v-411.83l76.96-76.96v488.79h-76.96ZM110.39-335.65v-112.7L400-735.96l160 160 289.61-290.61v112.14L560-463.26l-160-160-289.61 287.61Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 450 B |
3
bec_widgets/assets/toolbar_icons/line_curve.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="48px" viewBox="0 -960 960 960" width="48px" fill="#FFFFFF">
|
||||
<path d="M725.93-155.93q0-118.18-45-222.09t-122-180.91q-77-77-180.91-122t-222.09-45v-68.14q132.68 0 248.61 50.23 115.92 50.23 202.5 136.75 86.57 86.53 136.8 202.53 50.23 116.01 50.23 248.63h-68.14Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 319 B |
3
bec_widgets/assets/toolbar_icons/lock_aspect_ratio.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="48px" viewBox="0 -960 960 960" width="48px" fill="#FFFFFF">
|
||||
<path d="M571.91-279.09h191v-194h-60v134h-131v60ZM198.09-486.91h60v-134h131v-60h-191v194Zm-53 341.04q-32.51 0-55.87-23.35-23.35-23.36-23.35-55.87v-509.82q0-32.74 23.35-56.26 23.36-23.53 55.87-23.53h669.82q32.74 0 56.26 23.53 23.53 23.52 23.53 56.26v509.82q0 32.51-23.53 55.87-23.52 23.35-56.26 23.35H145.09Zm0-79.22h669.82v-509.82H145.09v509.82Zm0 0v-509.82 509.82Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 487 B |
BIN
bec_widgets/assets/toolbar_icons/log_scale.png
Normal file
|
After Width: | Height: | Size: 6.3 KiB |
3
bec_widgets/assets/toolbar_icons/motor_map.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="48px" viewBox="0 -960 960 960" width="48px" fill="#FFFFFF">
|
||||
<path d="M443.78-28.43v-75Q305.65-118 211.54-212.11q-94.11-94.11-108.11-231.67h-75v-72.44h75Q118-654.35 212.11-748.46q94.11-94.11 231.67-108.11v-75h72.44v75q137.56 14 231.67 108.11Q842-654.35 856.57-516.22h75v72.44h-75q-14 137.56-108.11 231.67Q654.35-118 516.22-103.43v75h-72.44Zm36.12-152.66q123.4 0 211.21-87.7 87.8-87.71 87.8-211.11 0-123.4-87.7-211.21-87.71-87.8-211.11-87.8-123.4 0-211.21 87.7-87.8 87.71-87.8 211.11 0 123.4 87.7 211.21 87.71 87.8 211.11 87.8ZM480-330q-63 0-106.5-43.5T330-480q0-63 43.5-106.5T480-630q63 0 106.5 43.5T630-480q0 63-43.5 106.5T480-330Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 693 B |
3
bec_widgets/assets/toolbar_icons/photo_library.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="48px" viewBox="0 -960 960 960" width="48px" fill="#FFFFFF">
|
||||
<path d="M354.61-386.61h391l-127-171-103 135-68-87-93 123ZM274.7-195.48q-32.51 0-55.87-23.35-23.35-23.36-23.35-55.87v-549.82q0-32.74 23.35-56.26 23.36-23.53 55.87-23.53h549.82q32.74 0 56.26 23.53 23.53 23.52 23.53 56.26v549.82q0 32.51-23.53 55.87-23.52 23.35-56.26 23.35H274.7Zm0-79.22h549.82v-549.82H274.7v549.82ZM135.48-55.69q-32.74 0-56.26-23.53-23.53-23.52-23.53-56.26v-629.04h79.79v629.04h629.04v79.79H135.48ZM274.7-824.52v549.82-549.82Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 564 B |
3
bec_widgets/assets/toolbar_icons/progress.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="48px" viewBox="0 -960 960 960" width="48px" fill="#FFFFFF">
|
||||
<path d="M480-65.87q-87.51 0-162.97-31.89-75.47-31.89-131.43-87.84-55.95-55.96-87.84-131.43Q65.87-392.49 65.87-480q0-87.47 31.88-162.87 31.89-75.39 87.75-131.51 55.87-56.11 131.41-88.21Q392.44-894.7 480-894.7q15.96 0 27.78 12.16 11.83 12.16 11.83 28.07 0 15.9-11.83 27.73-11.82 11.83-27.78 11.83-139.31 0-237.11 97.8-97.8 97.8-97.8 237.1 0 139.31 97.8 237.12 97.8 97.8 237.1 97.8 139.31 0 237.12-97.8 97.8-97.8 97.8-237.11 0-15.96 11.83-27.78 11.83-11.83 27.73-11.83 15.91 0 28.07 11.83Q894.7-495.96 894.7-480q0 87.56-32.14 163.1-32.14 75.54-88.11 131.44-55.97 55.9-131.44 87.74Q567.55-65.87 480-65.87Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 724 B |
3
bec_widgets/assets/toolbar_icons/queue.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="48px" viewBox="0 -960 960 960" width="48px" fill="#FFFFFF">
|
||||
<path d="M854.7-105.87H105.87v-209.61H854.7v209.61Zm0-269.61H105.87v-209.04H854.7v209.04Zm0-269.04H105.87V-854.7H854.7v210.18Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 248 B |
3
bec_widgets/assets/toolbar_icons/rectangle_mode.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="48px" viewBox="0 -960 960 960" width="48px" fill="#FFFFFF">
|
||||
<path d="M444.19-421.3q39.72 0 67.4-27.78 27.67-27.78 27.67-66.91 0-39.14-27.71-66.57Q483.83-610 444.4-610q-39.44 0-66.92 27.28Q350-555.44 350-516.3q0 39.13 27.36 67.06 27.35 27.94 66.83 27.94ZM634.52-274 532.78-375.74q-22.56 12.31-44.41 19.02-21.84 6.72-43.28 6.72-69.57 0-117.7-48.62-48.13-48.62-48.13-117.88 0-67.98 48.29-116.39t117.08-48.41q68.79 0 117.08 48.41Q610-584.48 610-515.75q0 21.72-6.93 44.13-6.94 22.4-20.37 44.97l102.73 102.74L634.52-274ZM185.09-105.87q-32.51 0-55.87-23.35-23.35-23.36-23.35-55.87V-352h79.22v166.91H352v79.22H185.09Zm422.91 0v-79.22h166.91V-352h79.79v166.91q0 32.51-23.53 55.87-23.52 23.35-56.26 23.35H608ZM105.87-608v-166.91q0-32.74 23.35-56.26 23.36-23.53 55.87-23.53H352v79.79H185.09V-608h-79.22Zm669.04 0v-166.91H608v-79.79h166.91q32.74 0 56.26 23.53 23.53 23.52 23.53 56.26V-608h-79.79Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 946 B |
5
bec_widgets/assets/toolbar_icons/remove.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="48px" viewBox="0 0 24 24" width="48px"
|
||||
fill="#8B1A10">
|
||||
<rect fill="none" height="24" width="24"/>
|
||||
<path d="M19,19H5V5h14V19z M3,3v18h18V3H3z M17,15.59L15.59,17L12,13.41L8.41,17L7,15.59L10.59,12L7,8.41L8.41,7L12,10.59L15.59,7 L17,8.41L13.41,12L17,15.59z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 357 B |
3
bec_widgets/assets/toolbar_icons/reset_settings.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="48px" viewBox="0 -960 960 960" width="48px" fill="#FFFFFF">
|
||||
<path d="M473.54-344.48v-63.59h187.18v63.59H473.54Zm81.92 230.46v-56.15h-81.92v-63.59h81.92v-56.39h63.58v176.13h-63.58Zm103.58-56.15v-63.59h187.18v63.59H659.04Zm41.68-116.92v-177.13h63.58v56.15h81.92v63.59H764.3v57.39h-63.58ZM841.22-540h-68.46q-22.98-101.89-103.94-170.83-80.95-68.93-188.89-68.93-125.29 0-212.37 87.18T180.48-480q0 78.61 36.59 143.32 36.58 64.7 96.95 104.46v-108.26h66.46v226.46H154.02v-66.46h117.41q-71.56-48.72-114.48-127.36-42.93-78.64-42.93-172.16 0-76.22 28.86-142.78 28.86-66.57 78.29-116.04 49.42-49.47 116.01-78.43 66.58-28.97 142.82-28.97 136.52 0 237.87 87.8Q819.22-670.63 841.22-540Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 733 B |
3
bec_widgets/assets/toolbar_icons/restore_state.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="48px" viewBox="0 -960 960 960" width="48px" fill="#FFFFFF">
|
||||
<path d="M336.04-487.39 145.09-678.35v156.65H65.87v-293H358.3v79.79H201.22l191.39 190.95-56.57 56.57ZM145.09-145.87q-32.51 0-55.87-23.35-23.35-23.36-23.35-55.87V-451.7h79.22v226.61H493v79.22H145.09Zm669.82-278.3v-310.74H428.3v-79.79h386.61q32.74 0 56.26 23.53 23.53 23.52 23.53 56.26v310.74h-79.79Zm79.79 60v218.87H553v-218.87h341.7Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 455 B |
3
bec_widgets/assets/toolbar_icons/ring_progress.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="48px" viewBox="0 -960 960 960" width="48px" fill="#FFFFFF">
|
||||
<path d="M479.87-54q-87.96 0-165.74-33.42-77.79-33.43-135.53-91.18-57.75-57.74-91.18-135.66Q54-392.17 54-480.13q0-87.96 33.42-165.74 33.43-77.79 91.18-135.53 57.74-57.75 135.62-91.18Q392.09-906 480-906h36.22v340.7q24.82 10.69 40.8 33.52Q573-508.96 573-480.17q0 38.43-27.38 65.8Q518.24-387 479.79-387q-38.44 0-65.62-27.37Q387-441.74 387-480.17q0-28.79 15.98-51.61 15.98-22.83 40.8-33.52v-97.22Q378.22-650.39 336-599.76q-42.22 50.63-42.22 119.5 0 78.19 54.12 132.33 54.12 54.15 132.02 54.15 77.91 0 132.1-54.09 54.2-54.1 54.2-131.79 0-42.47-16.28-77.88-16.29-35.42-45.42-60.98l52.05-52.05q38.18 35.64 60.7 84.75 22.51 49.11 22.51 105.82 0 108.57-75.58 183.9-75.59 75.32-184.13 75.32-108.55 0-183.92-75.32-75.37-75.33-75.37-183.89 0-99.62 63.68-172.01 63.67-72.39 159.32-85.65v-93.22Q309.39-817.61 218.2-717.8 127-617.98 127-480.14q0 147.23 103.1 250.18Q333.19-127 480.14-127t249.9-103.02Q833-333.04 833-479.81q0-76.45-29.58-141.91-29.57-65.46-80.81-114.89l51.48-51.48q61.05 58.34 96.48 137.6Q906-571.23 906-479.73q0 87.82-33.42 165.6-33.43 77.79-91.18 135.53-57.74 57.75-135.66 91.18Q567.83-54 479.87-54Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
3
bec_widgets/assets/toolbar_icons/rotate_left.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="48px" viewBox="0 -960 960 960" width="48px" fill="#FFFFFF">
|
||||
<path d="M435-72.11q-49.67-7-95.99-25.36-46.31-18.36-86.55-49.55l49.21-49.74q31.76 24 65.29 37.26 33.52 13.26 68.04 19.02v68.37Zm90 0v-68.37q109.28-21.24 181.07-102.9 71.78-81.66 71.78-197.71 0-124.37-84.11-210.87t-208.72-86.5h-18.8L540.67-664l-49.74 49.74L332.2-773l158.73-158.74 49.74 49.26-75.41 75.65h19.28q75.48 0 141.34 28.72t114.98 78.56q49.12 49.83 77.24 116.29 28.12 66.46 28.12 142.17 0 142.39-90.8 245.07Q664.63-93.35 525-72.11ZM189.46-209.78q-28.96-38.72-47.82-86.3-18.86-47.57-25.62-100.01h68.89q5 37.76 18.38 72.17 13.38 34.4 36.14 64.16l-49.97 49.98Zm-73.44-276.31q6.52-50.71 25-97.29 18.48-46.58 48.44-87.77l50.21 48.26q-22.76 33-36.14 67.52-13.38 34.52-18.62 69.28h-68.89Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 811 B |
3
bec_widgets/assets/toolbar_icons/rotate_right.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="48px" viewBox="0 -960 960 960" width="48px" fill="#FFFFFF">
|
||||
<path d="M527-72.11v-68.37q34.52-5.76 68.04-19.02 33.53-13.26 65.29-37.26l49.21 49.74q-40.24 31.19-86.55 49.55Q576.67-79.11 527-72.11Zm-90 0Q297.37-93.35 206.7-196.02q-90.68-102.68-90.68-245.07 0-75.71 28-142.17t77.12-116.29q49.12-49.84 114.98-78.56 65.86-28.72 141.34-28.72h19.28l-75.41-75.65 49.74-49.26L629.8-773 471.07-614.26 421.33-664l74.69-74.46h-19.04q-124.61 0-208.72 86.5t-84.11 210.87q0 116.05 71.78 197.71 71.79 81.66 181.07 102.9v68.37Zm335.54-137.67-49.97-49.98q22.76-29.76 36.14-64.16 13.38-34.41 18.38-72.17h69.13q-7 52.44-25.86 100.01-18.86 47.58-47.82 86.3Zm73.68-276.31h-69.13q-5.24-34.76-18.62-69.28t-36.14-67.52l50.21-48.26q29.96 41.19 48.44 87.77 18.48 46.58 25.24 97.29Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 815 B |
3
bec_widgets/assets/toolbar_icons/save.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="48px" viewBox="0 -960 960 960" width="48px" fill="#FFFFFF">
|
||||
<path d="M854.7-689.22v504.13q0 32.51-23.53 55.87-23.52 23.35-56.26 23.35H185.09q-32.51 0-55.87-23.35-23.35-23.36-23.35-55.87v-589.82q0-32.74 23.35-56.26 23.36-23.53 55.87-23.53h504.13L854.7-689.22Zm-79.79 35.48L653.74-774.91H185.09v589.82h589.82v-468.65ZM479.76-250.09q43.24 0 73.74-30.26 30.5-30.27 30.5-73.5 0-43.24-30.26-73.74-30.27-30.5-73.5-30.5-43.24 0-73.74 30.27-30.5 30.26-30.5 73.5 0 43.23 30.26 73.73 30.27 30.5 73.5 30.5ZM238.09-578.91h358v-143h-358v143Zm-53-74.83v468.65-589.82 121.17Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 621 B |
3
bec_widgets/assets/toolbar_icons/save_state.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="48px" viewBox="0 -960 960 960" width="48px" fill="#FFFFFF">
|
||||
<path d="M185.87-98.52v-681.39q0-32.74 23.35-56.26 23.36-23.53 55.87-23.53h429.82q32.74 0 56.26 23.53 23.53 23.52 23.53 56.26v681.39L480-224.17 185.87-98.52Zm79.22-120.39L480-309.18l214.91 90.27v-561H265.09v561Zm0-561h429.82-429.82Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 354 B |
3
bec_widgets/assets/toolbar_icons/scan_control.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="48px" viewBox="0 -960 960 960" width="48px" fill="#FFFFFF">
|
||||
<path d="M185.09-105.87q-32.93 0-56.08-23.14-23.14-23.15-23.14-56.08v-589.82q0-33.16 23.14-56.47 23.15-23.32 56.08-23.32h589.82q33.16 0 56.47 23.32 23.32 23.31 23.32 56.47v589.82q0 32.93-23.32 56.08-23.31 23.14-56.47 23.14H185.09Zm439.21-79.22h71l79.61-79.61v-40.39H744.3l-120 120ZM281-389.48l141-140 90 90L725.52-654 679-700.52l-167 167-90-90L234.48-436 281-389.48Zm-95.91 204.39h34.56l120-120h-71l-83.56 83.57v36.43Zm350.91 0 120-120h-71l-120 120h71Zm-159.87 0 120-120h-71l-120 120h71Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 609 B |
4
bec_widgets/assets/toolbar_icons/settings.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="48px" viewBox="0 0 24 24" width="48px" fill="#FFFFFF">
|
||||
<path d="M0 0h24v24H0V0z" fill="none"/>
|
||||
<path d="M19.43 12.98c.04-.32.07-.64.07-.98 0-.34-.03-.66-.07-.98l2.11-1.65c.19-.15.24-.42.12-.64l-2-3.46c-.09-.16-.26-.25-.44-.25-.06 0-.12.01-.17.03l-2.49 1c-.52-.4-1.08-.73-1.69-.98l-.38-2.65C14.46 2.18 14.25 2 14 2h-4c-.25 0-.46.18-.49.42l-.38 2.65c-.61.25-1.17.59-1.69.98l-2.49-1c-.06-.02-.12-.03-.18-.03-.17 0-.34.09-.43.25l-2 3.46c-.13.22-.07.49.12.64l2.11 1.65c-.04.32-.07.65-.07.98 0 .33.03.66.07.98l-2.11 1.65c-.19.15-.24.42-.12.64l2 3.46c.09.16.26.25.44.25.06 0 .12-.01.17-.03l2.49-1c.52.4 1.08.73 1.69.98l.38 2.65c.03.24.24.42.49.42h4c.25 0 .46-.18.49-.42l.38-2.65c.61-.25 1.17-.59 1.69-.98l2.49 1c.06.02.12.03.18.03.17 0 .34-.09.43-.25l2-3.46c.12-.22.07-.49-.12-.64l-2.11-1.65zm-1.98-1.71c.04.31.05.52.05.73 0 .21-.02.43-.05.73l-.14 1.13.89.7 1.08.84-.7 1.21-1.27-.51-1.04-.42-.9.68c-.43.32-.84.56-1.25.73l-1.06.43-.16 1.13-.2 1.35h-1.4l-.19-1.35-.16-1.13-1.06-.43c-.43-.18-.83-.41-1.23-.71l-.91-.7-1.06.43-1.27.51-.7-1.21 1.08-.84.89-.7-.14-1.13c-.03-.31-.05-.54-.05-.74s.02-.43.05-.73l.14-1.13-.89-.7-1.08-.84.7-1.21 1.27.51 1.04.42.9-.68c.43-.32.84-.56 1.25-.73l1.06-.43.16-1.13.2-1.35h1.39l.19 1.35.16 1.13 1.06.43c.43.18.83.41 1.23.71l.91.7 1.06-.43 1.27-.51.7 1.21-1.07.85-.89.7.14 1.13zM12 8c-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4-1.79-4-4-4zm0 6c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
3
bec_widgets/assets/toolbar_icons/status.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="48px" viewBox="0 -960 960 960" width="48px" fill="#FFFFFF">
|
||||
<path d="M286.83-277h60v-205h-60v205Zm326.91 0h60v-420h-60v420ZM450-277h60v-118h-60v118Zm0-205h60v-60h-60v60ZM185.09-105.87q-32.51 0-55.87-23.35-23.35-23.36-23.35-55.87v-589.82q0-32.74 23.35-56.26 23.36-23.53 55.87-23.53h589.82q32.74 0 56.26 23.53 23.53 23.52 23.53 56.26v589.82q0 32.51-23.53 55.87-23.52 23.35-56.26 23.35H185.09Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 452 B |
3
bec_widgets/assets/toolbar_icons/terminal.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="48px" viewBox="0 -960 960 960" width="48px" fill="#FFFFFF">
|
||||
<path d="M145.09-145.87q-32.51 0-55.87-23.35-23.35-23.36-23.35-55.87v-509.82q0-32.74 23.35-56.26 23.36-23.53 55.87-23.53h669.82q32.74 0 56.26 23.53 23.53 23.52 23.53 56.26v509.82q0 32.51-23.53 55.87-23.52 23.35-56.26 23.35H145.09Zm0-79.22h669.82v-425.82H145.09v425.82ZM300-292l-42-42 103-104-104-104 43-42 146 146-146 146Zm190 4v-60h220v60H490Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 466 B |
3
bec_widgets/assets/toolbar_icons/transform.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="48px" viewBox="0 -960 960 960" width="48px" fill="#FFFFFF">
|
||||
<path d="M636.17-32.59 480.35-188.41l45.82-46.07 77.13 77.37v-137.32H356.93q-28.1 0-46.8-18.55-18.7-18.54-18.7-46.95V-600.3H74.02v-65.27h217.41v-137.32l-77.36 77.37-45.59-45.83 155.59-155.82 156.06 155.82-46.06 45.83-77.14-77.37v442.96h529.29v65.5H669.04v137.32l77.13-77.37L792-188.41 636.17-32.59ZM603.3-419.93V-600.3H416.93v-65.27H603.3q28.37 0 47.06 18.56 18.68 18.55 18.68 46.71v180.37H603.3Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 518 B |
3
bec_widgets/assets/toolbar_icons/waveform.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="48px" viewBox="0 -960 960 960" width="48px" fill="#FFFFFF">
|
||||
<path d="M126-206.43 66.43-266 380-579.57 539.43-419.7l298-335 56.14 54.57-352.44 399.26L380-460.43l-254 254Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 231 B |
@@ -1,457 +0,0 @@
|
||||
import os
|
||||
import threading
|
||||
import time
|
||||
import warnings
|
||||
from typing import Any
|
||||
|
||||
import numpy as np
|
||||
import pyqtgraph
|
||||
import pyqtgraph as pg
|
||||
from bec_lib.core import BECMessage
|
||||
from PyQt5.QtCore import pyqtSlot
|
||||
from PyQt5.QtWidgets import QCheckBox, QTableWidgetItem
|
||||
from pyqtgraph import mkBrush, mkColor, mkPen
|
||||
from pyqtgraph.Qt import QtCore, QtWidgets, uic
|
||||
from pyqtgraph.Qt.QtCore import pyqtSignal
|
||||
|
||||
from bec_widgets.bec_dispatcher import bec_dispatcher
|
||||
from bec_lib.core.redis_connector import MessageObject, RedisConnector
|
||||
|
||||
client = bec_dispatcher.client
|
||||
|
||||
|
||||
class BasicPlot(QtWidgets.QWidget):
|
||||
update_signal = pyqtSignal()
|
||||
roi_signal = pyqtSignal(tuple)
|
||||
|
||||
def __init__(self, name="", y_value_list=["gauss_bpm"]) -> None:
|
||||
"""
|
||||
Basic plot widget for displaying scan data.
|
||||
|
||||
Args:
|
||||
name (str, optional): Name of the plot. Defaults to "".
|
||||
y_value_list (list, optional): List of signals to be plotted. Defaults to ["gauss_bpm"].
|
||||
"""
|
||||
|
||||
super(BasicPlot, self).__init__()
|
||||
# Set style for pyqtgraph plots
|
||||
pg.setConfigOption("background", "w")
|
||||
pg.setConfigOption("foreground", "k")
|
||||
current_path = os.path.dirname(__file__)
|
||||
uic.loadUi(os.path.join(current_path, "basic_plot.ui"), self)
|
||||
|
||||
# Set splitter distribution of widgets
|
||||
self.splitter.setSizes([3, 1])
|
||||
|
||||
self._idle_time = 100
|
||||
self.title = ""
|
||||
self.label_bottom = ""
|
||||
self.label_left = ""
|
||||
self.producer = RedisConnector(["localhost:6379"]).producer()
|
||||
|
||||
self.scan_motors = []
|
||||
self.y_value_list = y_value_list
|
||||
self.previous_y_value_list = None
|
||||
self.plotter_data_x = []
|
||||
self.plotter_data_y = []
|
||||
self.curves = []
|
||||
self.pens = []
|
||||
self.brushs = []
|
||||
|
||||
self.plotter_scan_id = None
|
||||
|
||||
# TODO to be moved to utils function
|
||||
plotstyles = {
|
||||
"symbol": "o",
|
||||
"symbolSize": 10,
|
||||
}
|
||||
|
||||
color_list = BasicPlot.golden_angle_color(colormap="CET-R2", num=len(self.y_value_list))
|
||||
|
||||
# setup plots - GraphicsLayoutWidget
|
||||
# LabelItem
|
||||
self.label = pg.LabelItem(justify="center")
|
||||
self.glw.addItem(self.label)
|
||||
self.label.setText("ROI region")
|
||||
|
||||
# PlotItem - main window
|
||||
self.glw.nextRow()
|
||||
self.plot = pg.PlotItem()
|
||||
self.plot.setLogMode(True, True)
|
||||
self.glw.addItem(self.plot)
|
||||
self.plot.addLegend()
|
||||
|
||||
# ImageItem - 2D view #TODO add 2D plot for ROI and 1D plot for mouse click
|
||||
self.glw.nextRow()
|
||||
self.plot_roi = pg.PlotItem()
|
||||
self.img = pg.ImageItem()
|
||||
self.glw.addItem(self.plot_roi)
|
||||
self.plot_roi.addItem(self.img)
|
||||
|
||||
# ROI selector - so far from [-1,1] #TODO update to scale with xrange
|
||||
self.roi_selector = pg.LinearRegionItem([-1, 1])
|
||||
|
||||
for ii, y_value in enumerate(self.y_value_list):
|
||||
pen = mkPen(color=color_list[ii], width=2, style=QtCore.Qt.DashLine)
|
||||
brush = mkBrush(color=color_list[ii])
|
||||
curve = pg.PlotDataItem(
|
||||
**plotstyles, symbolBrush=brush, pen=pen, skipFiniteCheck=True, name=y_value
|
||||
)
|
||||
self.plot.addItem(curve)
|
||||
self.curves.append(curve)
|
||||
self.pens.append(pen)
|
||||
self.brushs.append(brush)
|
||||
|
||||
self.add_crosshair(self.plot)
|
||||
self.add_crosshair(self.plot_roi)
|
||||
|
||||
self.crosshair_v = pg.InfiniteLine(angle=90, movable=False)
|
||||
self.crosshair_h = pg.InfiniteLine(angle=0, movable=False)
|
||||
#
|
||||
# for plot in (self.plot_roi, self.plot):
|
||||
# plot.addItem(self.crosshair_v, ignoreBounds=True)
|
||||
# plot.addItem(self.crosshair_h, ignoreBounds=True)
|
||||
|
||||
# self.plot.addItem(self.crosshair_v, ignoreBounds=True)
|
||||
# self.plot.addItem(self.crosshair_h, ignoreBounds=True)
|
||||
|
||||
# self.plot_roi.addItem(self.crosshair_v, ignoreBounds=True)
|
||||
# self.plot_roi.addItem(self.crosshair_h, ignoreBounds=True)
|
||||
|
||||
# Add textItems
|
||||
self.add_text_items()
|
||||
|
||||
# Manage signals
|
||||
self.proxy = pg.SignalProxy(
|
||||
self.plot.scene().sigMouseMoved, rateLimit=60, slot=self.mouse_moved
|
||||
)
|
||||
self.proxy_update = pg.SignalProxy(self.update_signal, rateLimit=25, slot=self.update)
|
||||
self.roi_selector.sigRegionChangeFinished.connect(self.get_roi_region)
|
||||
|
||||
# Debug functions
|
||||
self.pushButton_debug.clicked.connect(self.generate_2D_data_update)
|
||||
# self.generate_2D_data()
|
||||
|
||||
self._current_proj = None
|
||||
self._current_metadata_ep = "px_stream/projection_{}/metadata"
|
||||
|
||||
self.data_retriever = threading.Thread(target=self.on_projection, daemon=True)
|
||||
self.data_retriever.start()
|
||||
|
||||
def debug(self):
|
||||
"""
|
||||
Debug button just for quick testing
|
||||
"""
|
||||
|
||||
def generate_2D_data(self):
|
||||
data = np.random.normal(size=(1, 100))
|
||||
self.img.setImage(data)
|
||||
|
||||
def generate_2D_data_update(self):
|
||||
data = np.random.normal(size=(200, 300))
|
||||
self.img.setImage(data, levels=(0.2, 0.5))
|
||||
|
||||
def add_crosshair(self, plot):
|
||||
crosshair_v = pg.InfiniteLine(angle=90, movable=False)
|
||||
crosshair_h = pg.InfiniteLine(angle=0, movable=False)
|
||||
|
||||
plot.addItem(crosshair_v)
|
||||
plot.addItem(crosshair_h)
|
||||
|
||||
def get_roi_region(self):
|
||||
"""For testing purpose now, get roi region and print it to self.label as tuple"""
|
||||
region = self.roi_selector.getRegion()
|
||||
self.label.setText(f"x = {(10**region[0]):.4f}, y ={(10**region[1]):.4f}")
|
||||
return_dict = {
|
||||
"horiz_roi": [
|
||||
np.where(self.plotter_data_x[0] > 10 ** region[0])[0][0],
|
||||
np.where(self.plotter_data_x[0] < 10 ** region[1])[0][-1],
|
||||
]
|
||||
}
|
||||
msg = BECMessage.DeviceMessage(signals=return_dict).dumps()
|
||||
self.producer.set_and_publish("px_stream/gui_event", msg=msg)
|
||||
self.roi_signal.emit(region)
|
||||
|
||||
def add_text_items(self): # TODO probably can be removed
|
||||
"""Add text items to the plot"""
|
||||
|
||||
# self.mouse_box_data.setText("Mouse cursor")
|
||||
# TODO Via StyleSheet, one may set the color of the full QLabel
|
||||
# self.mouse_box_data.setStyleSheet(f"QLabel {{color : rgba{self.pens[0].color().getRgb()}}}")
|
||||
|
||||
def mouse_moved(self, event: tuple) -> None:
|
||||
"""
|
||||
Update the mouse table with the current mouse position and the corresponding data.
|
||||
|
||||
Args:
|
||||
event (tuple): Mouse event containing the position of the mouse cursor.
|
||||
The position is stored in first entry as horizontal, vertical pixel.
|
||||
"""
|
||||
pos = event[0]
|
||||
if not self.plot.sceneBoundingRect().contains(pos):
|
||||
return
|
||||
mousePoint = self.plot.vb.mapSceneToView(pos)
|
||||
self.crosshair_v.setPos(mousePoint.x())
|
||||
self.crosshair_h.setPos(mousePoint.y())
|
||||
if not self.plotter_data_x:
|
||||
return
|
||||
|
||||
closest_point = self.closest_x_y_value(
|
||||
mousePoint.x(), self.plotter_data_x[0], self.plotter_data_y[0]
|
||||
)
|
||||
# self.precision = 3
|
||||
# ii = 0
|
||||
# y_value = self.y_value_list[ii]
|
||||
# x_data = f"{10**closest_point[0]:.{self.precision}f}"
|
||||
# y_data = f"{10**closest_point[1]:.{self.precision}f}"
|
||||
#
|
||||
# # Write coordinate to QTable
|
||||
# self.mouse_table.setItem(ii, 1, QTableWidgetItem(str(y_value)))
|
||||
# self.mouse_table.setItem(ii, 2, QTableWidgetItem(str(x_data)))
|
||||
# self.mouse_table.setItem(ii, 3, QTableWidgetItem(str(y_data)))
|
||||
#
|
||||
# self.mouse_table.resizeColumnsToContents()
|
||||
|
||||
def closest_x_y_value(self, input_value, list_x, list_y) -> tuple:
|
||||
"""
|
||||
Find the closest x and y value to the input value.
|
||||
|
||||
Args:
|
||||
input_value (float): Input value
|
||||
list_x (list): List of x values
|
||||
list_y (list): List of y values
|
||||
|
||||
Returns:
|
||||
tuple: Closest x and y value
|
||||
"""
|
||||
arr = np.asarray(list_x)
|
||||
i = (np.abs(arr - input_value)).argmin()
|
||||
return list_x[i], list_y[i]
|
||||
|
||||
def update(self):
|
||||
"""Update the plot with the new data."""
|
||||
# check if roi selector is in the plot
|
||||
if self.roi_selector not in self.plot.items:
|
||||
self.plot.addItem(self.roi_selector)
|
||||
|
||||
# check if QTable was initialised and if list of devices was changed
|
||||
if self.y_value_list != self.previous_y_value_list:
|
||||
self.setup_cursor_table()
|
||||
self.previous_y_value_list = self.y_value_list.copy() if self.y_value_list else None
|
||||
|
||||
self.curves[0].setData(self.plotter_data_x[0], self.plotter_data_y[0])
|
||||
# if len(self.plotter_data_x[0]) <= 1:
|
||||
# return
|
||||
# self.plot.setLabel("bottom", self.label_bottom)
|
||||
# self.plot.setLabel("left", self.label_left)
|
||||
# for ii in range(len(self.y_value_list)):
|
||||
# self.curves[0].setData(self.plotter_data_x[0], self.plotter_data_y[0])
|
||||
|
||||
@pyqtSlot(dict, dict)
|
||||
def on_scan_segment(self, data: dict, metadata: dict) -> None:
|
||||
"""Update function that is called during the scan callback. To avoid
|
||||
too many renderings, the GUI is only processing events every <_idle_time> ms.
|
||||
|
||||
Args:
|
||||
data (dict): Dictionary containing a new scan segment
|
||||
metadata (dict): Scan metadata
|
||||
|
||||
"""
|
||||
if metadata["scanID"] != self.plotter_scan_id:
|
||||
self.plotter_scan_id = metadata["scanID"]
|
||||
self._reset_plot_data()
|
||||
|
||||
self.title = f"Scan {metadata['scan_number']}"
|
||||
|
||||
self.scan_motors = scan_motors = metadata.get("scan_report_devices")
|
||||
# client = BECClient()
|
||||
remove_y_value_index = [
|
||||
index
|
||||
for index, y_value in enumerate(self.y_value_list)
|
||||
if y_value not in client.device_manager.devices
|
||||
]
|
||||
if remove_y_value_index:
|
||||
for ii in sorted(remove_y_value_index, reverse=True):
|
||||
# TODO Use bec warning message??? to be discussed with Klaus
|
||||
warnings.warn(
|
||||
f"Warning: no matching signal for {self.y_value_list[ii]} found in list of devices. Removing from plot."
|
||||
)
|
||||
self.remove_curve_by_name(self.plot, self.y_value_list[ii])
|
||||
self.y_value_list.pop(ii)
|
||||
|
||||
self.precision = client.device_manager.devices[scan_motors[0]]._info["describe"][
|
||||
scan_motors[0]
|
||||
]["precision"]
|
||||
# TODO after update of bec_lib, this will be new way to access data
|
||||
# self.precision = client.device_manager.devices[scan_motors[0]].precision
|
||||
|
||||
x = data["data"][scan_motors[0]][scan_motors[0]]["value"]
|
||||
self.plotter_data_x.append(x)
|
||||
for ii, y_value in enumerate(self.y_value_list):
|
||||
y = data["data"][y_value][y_value]["value"]
|
||||
self.plotter_data_y[ii].append(y)
|
||||
self.label_bottom = scan_motors[0]
|
||||
self.label_left = f"{', '.join(self.y_value_list)}"
|
||||
|
||||
# print(f'metadata scan N{metadata["scan_number"]}') #TODO put as label on top of plot
|
||||
# print(f'Data point = {data["point_id"]}') #TODO can be used for progress bar
|
||||
|
||||
if len(self.plotter_data_x) <= 1:
|
||||
return
|
||||
self.update_signal.emit()
|
||||
|
||||
def _reset_plot_data(self):
|
||||
"""Reset the plot data."""
|
||||
self.plotter_data_x = []
|
||||
self.plotter_data_y = []
|
||||
for ii in range(len(self.y_value_list)):
|
||||
self.curves[ii].setData([], [])
|
||||
self.plotter_data_y.append([])
|
||||
|
||||
def setup_cursor_table(self):
|
||||
"""QTable formatting according to N of devices displayed in plot."""
|
||||
|
||||
# Init number of rows in table according to n of devices
|
||||
self.mouse_table.setRowCount(len(self.y_value_list))
|
||||
|
||||
for ii, y_value in enumerate(self.y_value_list):
|
||||
checkbox = QCheckBox()
|
||||
checkbox.setChecked(True)
|
||||
# TODO just for testing, will be replaced by removing/adding curve
|
||||
checkbox.stateChanged.connect(lambda: print("status Changed"))
|
||||
# checkbox.stateChanged.connect(lambda: self.remove_curve_by_name(plot=self.plot, checkbox=checkbox, name=y_value))
|
||||
self.mouse_table.setCellWidget(ii, 0, checkbox)
|
||||
self.mouse_table.setItem(ii, 1, QTableWidgetItem(str(y_value)))
|
||||
|
||||
self.mouse_table.resizeColumnsToContents()
|
||||
|
||||
@staticmethod
|
||||
def remove_curve_by_name(plot: pyqtgraph.PlotItem, name: str) -> None:
|
||||
# def remove_curve_by_name(plot: pyqtgraph.PlotItem, checkbox: QtWidgets.QCheckBox, name: str) -> None:
|
||||
"""Removes a curve from the given plot by the specified name.
|
||||
|
||||
Args:
|
||||
plot (pyqtgraph.PlotItem): The plot from which to remove the curve.
|
||||
name (str): The name of the curve to remove.
|
||||
"""
|
||||
# if checkbox.isChecked():
|
||||
for item in plot.items:
|
||||
if isinstance(item, pg.PlotDataItem) and getattr(item, "opts", {}).get("name") == name:
|
||||
plot.removeItem(item)
|
||||
return
|
||||
|
||||
# else:
|
||||
# return
|
||||
|
||||
@staticmethod
|
||||
def golden_ratio(num: int) -> list:
|
||||
"""Calculate the golden ratio for a given number of angles.
|
||||
|
||||
Args:
|
||||
num (int): Number of angles
|
||||
"""
|
||||
phi = 2 * np.pi * ((1 + np.sqrt(5)) / 2)
|
||||
angles = []
|
||||
for ii in range(num):
|
||||
x = np.cos(ii * phi)
|
||||
y = np.sin(ii * phi)
|
||||
angle = np.arctan2(y, x)
|
||||
angles.append(angle)
|
||||
return angles
|
||||
|
||||
@staticmethod
|
||||
def golden_angle_color(colormap: str, num: int) -> list:
|
||||
"""
|
||||
Extract num colors for from the specified colormap following golden angle distribution.
|
||||
|
||||
Args:
|
||||
colormap (str): Name of the colormap
|
||||
num (int): Number of requested colors
|
||||
|
||||
Returns:
|
||||
list: List of colors with length <num>
|
||||
|
||||
Raises:
|
||||
ValueError: If the number of requested colors is greater than the number of colors in the colormap.
|
||||
"""
|
||||
|
||||
cmap = pg.colormap.get(colormap)
|
||||
cmap_colors = cmap.color
|
||||
if num > len(cmap_colors):
|
||||
raise ValueError(
|
||||
f"Number of colors requested ({num}) is greater than the number of colors in the colormap ({len(cmap_colors)})"
|
||||
)
|
||||
angles = BasicPlot.golden_ratio(len(cmap_colors))
|
||||
color_selection = np.round(np.interp(angles, (-np.pi, np.pi), (0, len(cmap_colors))))
|
||||
colors = [
|
||||
mkColor(tuple((cmap_colors[int(ii)] * 255).astype(int))) for ii in color_selection[:num]
|
||||
]
|
||||
return colors
|
||||
|
||||
def on_projection(self):
|
||||
while True:
|
||||
if self._current_proj is None:
|
||||
time.sleep(0.1)
|
||||
continue
|
||||
endpoint = f"px_stream/projection_{self._current_proj}/data"
|
||||
msgs = client.producer.lrange(topic=endpoint, start=-1, end=-1)
|
||||
data = [BECMessage.DeviceMessage.loads(msg) for msg in msgs]
|
||||
if not data:
|
||||
continue
|
||||
with np.errstate(divide="ignore", invalid="ignore"):
|
||||
self.plotter_data_y = [
|
||||
np.sum(
|
||||
np.sum(data[-1].content["signals"]["data"] * self._current_norm, axis=1)
|
||||
/ np.sum(self._current_norm, axis=0),
|
||||
axis=0,
|
||||
).squeeze()
|
||||
]
|
||||
|
||||
self.update_signal.emit()
|
||||
|
||||
@pyqtSlot(dict, dict)
|
||||
def on_dap_update(self, data: dict, metadata: dict):
|
||||
self.img.setImage(data["z"].T)
|
||||
# time.sleep(0,1)
|
||||
|
||||
@pyqtSlot(dict)
|
||||
def new_proj(self, data):
|
||||
proj_nr = data["proj_nr"]
|
||||
endpoint = f"px_stream/projection_{proj_nr}/metadata"
|
||||
msg_raw = client.producer.get(topic=endpoint)
|
||||
msg = BECMessage.DeviceMessage.loads(msg_raw)
|
||||
self._current_q = msg.content["signals"]["q"]
|
||||
self._current_norm = msg.content["signals"]["norm_sum"]
|
||||
self._current_metadata = msg.content["signals"]["metadata"]
|
||||
|
||||
self.plotter_data_x = [self._current_q]
|
||||
self._current_proj = proj_nr
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
|
||||
from bec_widgets import ctrl_c
|
||||
from bec_widgets.bec_dispatcher import bec_dispatcher
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
"--signals",
|
||||
help="specify recorded signals",
|
||||
nargs="+",
|
||||
default=["gauss_bpm"],
|
||||
)
|
||||
# default = ["gauss_bpm", "bpm4i", "bpm5i", "bpm6i", "xert"],
|
||||
value = parser.parse_args()
|
||||
print(f"Plotting signals for: {', '.join(value.signals)}")
|
||||
client = bec_dispatcher.client
|
||||
# client.start()
|
||||
app = QtWidgets.QApplication([])
|
||||
ctrl_c.setup(app)
|
||||
plot = BasicPlot(y_value_list=value.signals)
|
||||
# bec_dispatcher.connect(plot)
|
||||
bec_dispatcher.connect_proj_id(plot.new_proj)
|
||||
bec_dispatcher.connect_dap_slot(plot.on_dap_update, "px_dap_worker")
|
||||
plot.show()
|
||||
# client.callbacks.register("scan_segment", plot, sync=False)
|
||||
app.exec_()
|
||||
@@ -1,77 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>Form</class>
|
||||
<widget class="QWidget" name="Form">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>845</width>
|
||||
<height>635</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Line Plot</string>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QSplitter" name="splitter">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="opaqueResize">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<widget class="QWidget" name="verticalLayoutWidget">
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QPushButton" name="pushButton_debug">
|
||||
<property name="text">
|
||||
<string>Debug</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="GraphicsLayoutWidget" name="glw"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QTableWidget" name="mouse_table">
|
||||
<property name="textElideMode">
|
||||
<enum>Qt::ElideMiddle</enum>
|
||||
</property>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Display</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Device</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>X</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Y</string>
|
||||
</property>
|
||||
</column>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>GraphicsLayoutWidget</class>
|
||||
<extends>QGraphicsView</extends>
|
||||
<header>pyqtgraph.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
@@ -1,237 +0,0 @@
|
||||
import argparse
|
||||
import itertools
|
||||
import os
|
||||
from dataclasses import dataclass
|
||||
from threading import RLock
|
||||
|
||||
from bec_lib import BECClient
|
||||
from bec_lib.core import BECMessage, MessageEndpoints, ServiceConfig
|
||||
from bec_lib.core.redis_connector import RedisConsumerThreaded
|
||||
from PyQt5.QtCore import QObject, pyqtSignal
|
||||
|
||||
|
||||
@dataclass
|
||||
class _BECDap:
|
||||
"""Utility class to keep track of slots associated with a particular dap redis consumer"""
|
||||
|
||||
consumer: RedisConsumerThreaded
|
||||
slots = set()
|
||||
|
||||
|
||||
# Adding a new pyqt signal requres a class factory, as they must be part of the class definition
|
||||
# and cannot be dynamically added as class attributes after the class has been defined.
|
||||
_signal_class_factory = (
|
||||
type(f"Signal{i}", (QObject,), dict(signal=pyqtSignal(dict, dict))) for i in itertools.count()
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class _Connection:
|
||||
"""Utility class to keep track of slots connected to a particular redis consumer"""
|
||||
|
||||
consumer: RedisConsumerThreaded
|
||||
slots = set()
|
||||
# keep a reference to a new signal class, so it is not gc'ed
|
||||
_signal_container = next(_signal_class_factory)()
|
||||
|
||||
def __post_init__(self):
|
||||
self.signal = self._signal_container.signal
|
||||
|
||||
|
||||
class _BECDispatcher(QObject):
|
||||
new_scan = pyqtSignal(dict, dict)
|
||||
scan_segment = pyqtSignal(dict, dict)
|
||||
new_dap_data = pyqtSignal(dict, dict)
|
||||
|
||||
new_projection_id = pyqtSignal(dict)
|
||||
new_projection_data = pyqtSignal(dict)
|
||||
|
||||
def __init__(self, bec_config=None):
|
||||
super().__init__()
|
||||
self.client = BECClient()
|
||||
|
||||
# TODO: this is a workaround for now to provide service config within qtdesigner, but is
|
||||
# it possible to provide config via a cli arg?
|
||||
if bec_config is None and os.path.isfile("bec_config.yaml"):
|
||||
bec_config = "bec_config.yaml"
|
||||
|
||||
self.client.initialize(config=ServiceConfig(config_path=bec_config))
|
||||
|
||||
self._slot_signal_map = {
|
||||
"on_scan_segment": self.scan_segment,
|
||||
"on_new_scan": self.new_scan,
|
||||
}
|
||||
self._daps = {}
|
||||
self._connections = {}
|
||||
|
||||
self._scan_id = None
|
||||
scan_lock = RLock()
|
||||
|
||||
# self.new_projection_id.connect(self.new_projection_data)
|
||||
|
||||
def _scan_segment_cb(msg):
|
||||
msg = BECMessage.ScanMessage.loads(msg.value)[0]
|
||||
with scan_lock:
|
||||
# TODO: use ScanStatusMessage instead?
|
||||
scan_id = msg.content["scanID"]
|
||||
if self._scan_id != scan_id:
|
||||
self._scan_id = scan_id
|
||||
self.new_scan.emit(msg.content, msg.metadata)
|
||||
self.scan_segment.emit(msg.content, msg.metadata)
|
||||
|
||||
scan_segment_topic = MessageEndpoints.scan_segment()
|
||||
self._scan_segment_thread = self.client.connector.consumer(
|
||||
topics=scan_segment_topic,
|
||||
cb=_scan_segment_cb,
|
||||
)
|
||||
self._scan_segment_thread.start()
|
||||
|
||||
def connect(self, widget):
|
||||
for slot_name, signal in self._slot_signal_map.items():
|
||||
slot = getattr(widget, slot_name, None)
|
||||
if callable(slot):
|
||||
signal.connect(slot)
|
||||
|
||||
def connect_slot(self, slot, topic):
|
||||
# create new connection for topic if it doesn't exist
|
||||
if topic not in self._connections:
|
||||
|
||||
def cb(msg):
|
||||
msg = BECMessage.MessageReader.loads(msg.value)
|
||||
if not isinstance(msg, list):
|
||||
msg = [msg]
|
||||
for msg_i in msg:
|
||||
self._connections[topic].signal.emit(msg_i.content, msg_i.metadata)
|
||||
|
||||
consumer = self.client.connector.consumer(topics=topic, cb=cb)
|
||||
consumer.start()
|
||||
|
||||
self._connections[topic] = _Connection(consumer)
|
||||
|
||||
# connect slot if it's not connected
|
||||
if slot not in self._connections[topic].slots:
|
||||
self._connections[topic].signal.connect(slot)
|
||||
self._connections[topic].slots.add(slot)
|
||||
|
||||
def disconnect_slot(self, slot, topic):
|
||||
if topic not in self._connections:
|
||||
return
|
||||
|
||||
if slot not in self._connections[topic].slots:
|
||||
return
|
||||
|
||||
self._connections[topic].signal.disconnect(slot)
|
||||
self._connections[topic].slots.remove(slot)
|
||||
|
||||
if not self._connections[topic].slots:
|
||||
# shutdown consumer if there are no more connected slots
|
||||
self._connections[topic].consumer.shutdown()
|
||||
del self._connections[topic]
|
||||
|
||||
def connect_dap_slot(self, slot, dap_names):
|
||||
if not isinstance(dap_names, list):
|
||||
dap_names = [dap_names]
|
||||
|
||||
for dap_name in dap_names:
|
||||
if dap_name not in self._daps: # create a new consumer and connect slot
|
||||
self.add_new_dap_connection(slot, dap_name)
|
||||
|
||||
else:
|
||||
# connect slot if it's not yet connected
|
||||
if slot not in self._daps[dap_name].slots:
|
||||
self.new_dap_data.connect(slot)
|
||||
self._daps[dap_name].slots.add(slot)
|
||||
|
||||
def add_new_dap_connection(self, slot, dap_name):
|
||||
def _dap_cb(msg):
|
||||
msg = BECMessage.ProcessedDataMessage.loads(msg.value)
|
||||
if not isinstance(msg, list):
|
||||
msg = [msg]
|
||||
for i in msg:
|
||||
self.new_dap_data.emit(i.content["data"], i.metadata)
|
||||
|
||||
dap_ep = MessageEndpoints.processed_data(dap_name)
|
||||
consumer = self.client.connector.consumer(topics=dap_ep, cb=_dap_cb)
|
||||
consumer.start()
|
||||
|
||||
self.new_dap_data.connect(slot)
|
||||
|
||||
self._daps[dap_name] = _BECDap(consumer)
|
||||
self._daps[dap_name].slots.add(slot)
|
||||
|
||||
def disconnect_dap_slot(self, slot, dap_name):
|
||||
if dap_name not in self._daps:
|
||||
return
|
||||
|
||||
if slot not in self._daps[dap_name].slots:
|
||||
return
|
||||
|
||||
self.new_dap_data.disconnect(slot)
|
||||
self._daps[dap_name].slots.remove(slot)
|
||||
|
||||
if not self._daps[dap_name].slots:
|
||||
# shutdown consumer if there are no more connected slots
|
||||
self._daps[dap_name].consumer.shutdown()
|
||||
del self._daps[dap_name]
|
||||
|
||||
# def connect_proj_data(self, slot):
|
||||
# keys = self.client.producer.keys("px_stream/projection_*")
|
||||
# keys = keys or []
|
||||
#
|
||||
# def _dap_cb(msg):
|
||||
# msg = BECMessage.DeviceMessage.loads(msg.value)
|
||||
# self.new_projection_data.emit(msg.content["data"])
|
||||
#
|
||||
# proj_numbers = set(key.decode().split("px_stream/projection_")[1].split("/")[0] for key in keys)
|
||||
# last_proj_id = sorted(proj_numbers)[-1]
|
||||
# dap_ep = MessageEndpoints.processed_data(f"px_stream/projection_{last_proj_id}/")
|
||||
#
|
||||
# consumer = self.client.connector.consumer(topics=dap_ep, cb=_dap_cb)
|
||||
# consumer.start()
|
||||
#
|
||||
# self.new_projection_data.connect(slot)
|
||||
|
||||
def connect_proj_id(self, slot):
|
||||
def _dap_cb(msg):
|
||||
msg = BECMessage.DeviceMessage.loads(msg.value)
|
||||
self.new_projection_id.emit(msg.content["signals"])
|
||||
|
||||
dap_ep = "px_stream/proj_nr"
|
||||
consumer = self.client.connector.consumer(topics=dap_ep, cb=_dap_cb)
|
||||
consumer.start()
|
||||
|
||||
self.new_projection_id.connect(slot)
|
||||
|
||||
def connect_proj_data(self, slot: object, data_ep: str) -> object:
|
||||
def _dap_cb(msg):
|
||||
msg = BECMessage.DeviceMessage.loads(msg.value)
|
||||
self.new_projection_data.emit(msg.content["signals"])
|
||||
|
||||
consumer = self.client.connector.consumer(topics=data_ep, cb=_dap_cb)
|
||||
consumer.start()
|
||||
self._daps[data_ep] = _BECDap(consumer)
|
||||
self._daps[data_ep].slots.add(slot)
|
||||
|
||||
self.new_projection_data.connect(slot)
|
||||
|
||||
def disconnect_proj_data(self, slot, data_ep):
|
||||
if data_ep not in self._daps:
|
||||
return
|
||||
|
||||
if slot not in self._daps[data_ep].slots:
|
||||
return
|
||||
|
||||
self.new_projection_data.disconnect(slot)
|
||||
self._daps[data_ep].slots.remove(slot)
|
||||
|
||||
if not self._daps[data_ep].slots:
|
||||
# shutdown consumer if there are no more connected slots
|
||||
self._daps[data_ep].consumer.shutdown()
|
||||
del self._daps[data_ep]
|
||||
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--bec-config", default=None)
|
||||
args, _ = parser.parse_known_args()
|
||||
|
||||
bec_dispatcher = _BECDispatcher(args.bec_config)
|
||||
1
bec_widgets/cli/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from .client import *
|
||||
153
bec_widgets/cli/auto_updates.py
Normal file
@@ -0,0 +1,153 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .client import BECDockArea, BECFigure
|
||||
|
||||
|
||||
class ScanInfo(BaseModel):
|
||||
scan_id: str
|
||||
scan_number: int
|
||||
scan_name: str
|
||||
scan_report_devices: list
|
||||
monitored_devices: list
|
||||
status: str
|
||||
model_config: dict = {"validate_assignment": True}
|
||||
|
||||
|
||||
class AutoUpdates:
|
||||
create_default_dock: bool = False
|
||||
enabled: bool = False
|
||||
dock_name: str = None
|
||||
|
||||
def __init__(self, gui: BECDockArea):
|
||||
self.gui = gui
|
||||
|
||||
def start_default_dock(self):
|
||||
"""
|
||||
Create a default dock for the auto updates.
|
||||
"""
|
||||
dock = self.gui.add_dock("default_figure")
|
||||
dock.add_widget("BECFigure")
|
||||
self.dock_name = "default_figure"
|
||||
|
||||
@staticmethod
|
||||
def get_scan_info(msg) -> ScanInfo:
|
||||
"""
|
||||
Update the script with the given data.
|
||||
"""
|
||||
info = msg.info
|
||||
status = msg.status
|
||||
scan_id = msg.scan_id
|
||||
scan_number = info.get("scan_number", 0)
|
||||
scan_name = info.get("scan_name", "Unknown")
|
||||
scan_report_devices = info.get("scan_report_devices", [])
|
||||
monitored_devices = info.get("readout_priority", {}).get("monitored", [])
|
||||
monitored_devices = [dev for dev in monitored_devices if dev not in scan_report_devices]
|
||||
return ScanInfo(
|
||||
scan_id=scan_id,
|
||||
scan_number=scan_number,
|
||||
scan_name=scan_name,
|
||||
scan_report_devices=scan_report_devices,
|
||||
monitored_devices=monitored_devices,
|
||||
status=status,
|
||||
)
|
||||
|
||||
def get_default_figure(self) -> BECFigure | None:
|
||||
"""
|
||||
Get the default figure from the GUI.
|
||||
"""
|
||||
dock = self.gui.panels.get(self.dock_name, [])
|
||||
if not dock:
|
||||
return None
|
||||
widgets = dock.widget_list
|
||||
if not widgets:
|
||||
return None
|
||||
return widgets[0]
|
||||
|
||||
def run(self, msg):
|
||||
"""
|
||||
Run the update function if enabled.
|
||||
"""
|
||||
if not self.enabled:
|
||||
return
|
||||
if msg.status != "open":
|
||||
return
|
||||
info = self.get_scan_info(msg)
|
||||
self.handler(info)
|
||||
|
||||
@staticmethod
|
||||
def get_selected_device(monitored_devices, selected_device):
|
||||
"""
|
||||
Get the selected device for the plot. If no device is selected, the first
|
||||
device in the monitored devices list is selected.
|
||||
"""
|
||||
if selected_device:
|
||||
return selected_device
|
||||
if len(monitored_devices) > 0:
|
||||
sel_device = monitored_devices[0]
|
||||
return sel_device
|
||||
return None
|
||||
|
||||
def handler(self, info: ScanInfo) -> None:
|
||||
"""
|
||||
Default update function.
|
||||
"""
|
||||
if info.scan_name == "line_scan" and info.scan_report_devices:
|
||||
self.simple_line_scan(info)
|
||||
return
|
||||
if info.scan_name == "grid_scan" and info.scan_report_devices:
|
||||
self.simple_grid_scan(info)
|
||||
return
|
||||
if info.scan_report_devices:
|
||||
self.best_effort(info)
|
||||
return
|
||||
|
||||
def simple_line_scan(self, info: ScanInfo) -> None:
|
||||
"""
|
||||
Simple line scan.
|
||||
"""
|
||||
fig = self.get_default_figure()
|
||||
if not fig:
|
||||
return
|
||||
dev_x = info.scan_report_devices[0]
|
||||
dev_y = self.get_selected_device(info.monitored_devices, self.gui.selected_device)
|
||||
if not dev_y:
|
||||
return
|
||||
fig.clear_all()
|
||||
plt = fig.plot(x_name=dev_x, y_name=dev_y, label=f"Scan {info.scan_number} - {dev_y}")
|
||||
plt.set(title=f"Scan {info.scan_number}", x_label=dev_x, y_label=dev_y)
|
||||
|
||||
def simple_grid_scan(self, info: ScanInfo) -> None:
|
||||
"""
|
||||
Simple grid scan.
|
||||
"""
|
||||
fig = self.get_default_figure()
|
||||
if not fig:
|
||||
return
|
||||
dev_x = info.scan_report_devices[0]
|
||||
dev_y = info.scan_report_devices[1]
|
||||
dev_z = self.get_selected_device(info.monitored_devices, self.gui.selected_device)
|
||||
fig.clear_all()
|
||||
plt = fig.plot(
|
||||
x_name=dev_x, y_name=dev_y, z_name=dev_z, label=f"Scan {info.scan_number} - {dev_z}"
|
||||
)
|
||||
plt.set(title=f"Scan {info.scan_number}", x_label=dev_x, y_label=dev_y)
|
||||
|
||||
def best_effort(self, info: ScanInfo) -> None:
|
||||
"""
|
||||
Best effort scan.
|
||||
"""
|
||||
fig = self.get_default_figure()
|
||||
if not fig:
|
||||
return
|
||||
dev_x = info.scan_report_devices[0]
|
||||
dev_y = self.get_selected_device(info.monitored_devices, self.gui.selected_device)
|
||||
if not dev_y:
|
||||
return
|
||||
fig.clear_all()
|
||||
plt = fig.plot(x_name=dev_x, y_name=dev_y, label=f"Scan {info.scan_number} - {dev_y}")
|
||||
plt.set(title=f"Scan {info.scan_number}", x_label=dev_x, y_label=dev_y)
|
||||
2745
bec_widgets/cli/client.py
Normal file
362
bec_widgets/cli/client_utils.py
Normal file
@@ -0,0 +1,362 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib
|
||||
import importlib.metadata as imd
|
||||
import json
|
||||
import os
|
||||
import select
|
||||
import subprocess
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
import uuid
|
||||
from functools import wraps
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from bec_lib.endpoints import MessageEndpoints
|
||||
from bec_lib.logger import bec_logger
|
||||
from bec_lib.utils.import_utils import isinstance_based_on_class_name, lazy_import, lazy_import_from
|
||||
from qtpy.QtCore import QEventLoop, QSocketNotifier, QTimer
|
||||
|
||||
import bec_widgets.cli.client as client
|
||||
from bec_widgets.cli.auto_updates import AutoUpdates
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from bec_lib.device import DeviceBase
|
||||
|
||||
from bec_widgets.cli.client import BECDockArea, BECFigure
|
||||
|
||||
from bec_lib.serialization import MsgpackSerialization
|
||||
|
||||
messages = lazy_import("bec_lib.messages")
|
||||
# from bec_lib.connector import MessageObject
|
||||
MessageObject = lazy_import_from("bec_lib.connector", ("MessageObject",))
|
||||
BECDispatcher = lazy_import_from("bec_widgets.utils.bec_dispatcher", ("BECDispatcher",))
|
||||
|
||||
logger = bec_logger.logger
|
||||
|
||||
|
||||
def rpc_call(func):
|
||||
"""
|
||||
A decorator for calling a function on the server.
|
||||
|
||||
Args:
|
||||
func: The function to call.
|
||||
|
||||
Returns:
|
||||
The result of the function call.
|
||||
"""
|
||||
|
||||
@wraps(func)
|
||||
def wrapper(self, *args, **kwargs):
|
||||
# we could rely on a strict type check here, but this is more flexible
|
||||
# moreover, it would anyway crash for objects...
|
||||
out = []
|
||||
for arg in args:
|
||||
if hasattr(arg, "name"):
|
||||
arg = arg.name
|
||||
out.append(arg)
|
||||
args = tuple(out)
|
||||
for key, val in kwargs.items():
|
||||
if hasattr(val, "name"):
|
||||
kwargs[key] = val.name
|
||||
if not self.gui_is_alive():
|
||||
raise RuntimeError("GUI is not alive")
|
||||
return self._run_rpc(func.__name__, *args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
def _get_output(process, logger) -> None:
|
||||
log_func = {process.stdout: logger.debug, process.stderr: logger.error}
|
||||
stream_buffer = {process.stdout: [], process.stderr: []}
|
||||
try:
|
||||
os.set_blocking(process.stdout.fileno(), False)
|
||||
os.set_blocking(process.stderr.fileno(), False)
|
||||
while process.poll() is None:
|
||||
readylist, _, _ = select.select([process.stdout, process.stderr], [], [], 1)
|
||||
for stream in (process.stdout, process.stderr):
|
||||
buf = stream_buffer[stream]
|
||||
if stream in readylist:
|
||||
buf.append(stream.read(4096))
|
||||
output, _, remaining = "".join(buf).rpartition("\n")
|
||||
if output:
|
||||
log_func[stream](output)
|
||||
buf.clear()
|
||||
buf.append(remaining)
|
||||
except Exception as e:
|
||||
print(f"Error reading process output: {str(e)}")
|
||||
|
||||
|
||||
def _start_plot_process(gui_id: str, gui_class: type, config: dict | str, logger=None) -> None:
|
||||
"""
|
||||
Start the plot in a new process.
|
||||
|
||||
Logger must be a logger object with "debug" and "error" functions,
|
||||
or it can be left to "None" as default. None means output from the
|
||||
process will not be captured.
|
||||
"""
|
||||
# pylint: disable=subprocess-run-check
|
||||
command = ["bec-gui-server", "--id", gui_id, "--gui_class", gui_class.__name__]
|
||||
if config:
|
||||
if isinstance(config, dict):
|
||||
config = json.dumps(config)
|
||||
command.extend(["--config", config])
|
||||
|
||||
env_dict = os.environ.copy()
|
||||
env_dict["PYTHONUNBUFFERED"] = "1"
|
||||
if logger is None:
|
||||
stdout_redirect = subprocess.DEVNULL
|
||||
stderr_redirect = subprocess.DEVNULL
|
||||
else:
|
||||
stdout_redirect = subprocess.PIPE
|
||||
stderr_redirect = subprocess.PIPE
|
||||
|
||||
process = subprocess.Popen(
|
||||
command,
|
||||
text=True,
|
||||
start_new_session=True,
|
||||
stdout=stdout_redirect,
|
||||
stderr=stderr_redirect,
|
||||
env=env_dict,
|
||||
)
|
||||
if logger is None:
|
||||
process_output_processing_thread = None
|
||||
else:
|
||||
process_output_processing_thread = threading.Thread(
|
||||
target=_get_output, args=(process, logger)
|
||||
)
|
||||
process_output_processing_thread.start()
|
||||
return process, process_output_processing_thread
|
||||
|
||||
|
||||
class BECGuiClientMixin:
|
||||
def __init__(self, **kwargs) -> None:
|
||||
super().__init__(**kwargs)
|
||||
self._process = None
|
||||
self._process_output_processing_thread = None
|
||||
self.auto_updates = self._get_update_script()
|
||||
self._target_endpoint = MessageEndpoints.scan_status()
|
||||
self._selected_device = None
|
||||
|
||||
def _get_update_script(self) -> AutoUpdates | None:
|
||||
eps = imd.entry_points(group="bec.widgets.auto_updates")
|
||||
for ep in eps:
|
||||
if ep.name == "plugin_widgets_update":
|
||||
try:
|
||||
spec = importlib.util.find_spec(ep.module)
|
||||
# if the module is not found, we skip it
|
||||
if spec is None:
|
||||
continue
|
||||
return ep.load()(gui=self)
|
||||
except Exception as e:
|
||||
print(f"Error loading auto update script from plugin: {str(e)}")
|
||||
return None
|
||||
|
||||
@property
|
||||
def selected_device(self):
|
||||
"""
|
||||
Selected device for the plot.
|
||||
"""
|
||||
return self._selected_device
|
||||
|
||||
@selected_device.setter
|
||||
def selected_device(self, device: str | DeviceBase):
|
||||
if isinstance_based_on_class_name(device, "bec_lib.device.DeviceBase"):
|
||||
self._selected_device = device.name
|
||||
elif isinstance(device, str):
|
||||
self._selected_device = device
|
||||
else:
|
||||
raise ValueError("Device must be a string or a device object")
|
||||
|
||||
def _start_update_script(self) -> None:
|
||||
self._client.connector.register(
|
||||
self._target_endpoint, cb=self._handle_msg_update, parent=self
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _handle_msg_update(msg: MessageObject, parent: BECGuiClientMixin) -> None:
|
||||
if parent.auto_updates is not None:
|
||||
# pylint: disable=protected-access
|
||||
parent._update_script_msg_parser(msg.value)
|
||||
|
||||
def _update_script_msg_parser(self, msg: messages.BECMessage) -> None:
|
||||
if isinstance(msg, messages.ScanStatusMessage):
|
||||
if not self.gui_is_alive():
|
||||
return
|
||||
self.auto_updates.run(msg)
|
||||
|
||||
def show(self) -> None:
|
||||
"""
|
||||
Show the figure.
|
||||
"""
|
||||
if self._process is None or self._process.poll() is not None:
|
||||
self._start_update_script()
|
||||
self._process, self._process_output_processing_thread = _start_plot_process(
|
||||
self._gui_id, self.__class__, self._client._service_config.config
|
||||
)
|
||||
while not self.gui_is_alive():
|
||||
print("Waiting for GUI to start...")
|
||||
time.sleep(1)
|
||||
|
||||
def close(self) -> None:
|
||||
"""
|
||||
Close the gui window.
|
||||
"""
|
||||
if self._process is None:
|
||||
return
|
||||
|
||||
self._client.shutdown()
|
||||
if self._process:
|
||||
self._process.terminate()
|
||||
if self._process_output_processing_thread:
|
||||
self._process_output_processing_thread.join()
|
||||
self._process.wait()
|
||||
self._process = None
|
||||
|
||||
|
||||
class RPCResponseTimeoutError(Exception):
|
||||
"""Exception raised when an RPC response is not received within the expected time."""
|
||||
|
||||
def __init__(self, request_id, timeout):
|
||||
super().__init__(
|
||||
f"RPC response not received within {timeout} seconds for request ID {request_id}"
|
||||
)
|
||||
|
||||
|
||||
class QtRedisMessageWaiter:
|
||||
def __init__(self, redis_connector, message_to_wait):
|
||||
self.ev_loop = QEventLoop()
|
||||
self.response = None
|
||||
self.connector = redis_connector
|
||||
self.message_to_wait = message_to_wait
|
||||
self.pubsub = redis_connector._redis_conn.pubsub()
|
||||
self.pubsub.subscribe(self.message_to_wait.endpoint)
|
||||
fd = self.pubsub.connection._sock.fileno()
|
||||
self.notifier = QSocketNotifier(fd, QSocketNotifier.Read)
|
||||
self.notifier.activated.connect(self._pubsub_readable)
|
||||
|
||||
def _msg_received(self, msg_obj):
|
||||
self.response = msg_obj.value
|
||||
self.ev_loop.quit()
|
||||
|
||||
def wait(self, timeout=1):
|
||||
timer = QTimer()
|
||||
timer.singleShot(timeout * 1000, self.ev_loop.quit)
|
||||
self.ev_loop.exec_()
|
||||
timer.stop()
|
||||
self.notifier.setEnabled(False)
|
||||
self.pubsub.close()
|
||||
return self.response
|
||||
|
||||
def _pubsub_readable(self, fd):
|
||||
while True:
|
||||
msg = self.pubsub.get_message()
|
||||
if msg:
|
||||
if msg["type"] == "subscribe":
|
||||
# get_message buffers, so we may already have the answer
|
||||
# let's check...
|
||||
continue
|
||||
else:
|
||||
break
|
||||
else:
|
||||
return
|
||||
channel = msg["channel"].decode()
|
||||
msg = MessageObject(topic=channel, value=MsgpackSerialization.loads(msg["data"]))
|
||||
self.connector._execute_callback(self._msg_received, msg, {})
|
||||
|
||||
|
||||
class RPCBase:
|
||||
def __init__(self, gui_id: str = None, config: dict = None, parent=None) -> None:
|
||||
self._client = BECDispatcher().client
|
||||
self._config = config if config is not None else {}
|
||||
self._gui_id = gui_id if gui_id is not None else str(uuid.uuid4())
|
||||
self._parent = parent
|
||||
super().__init__()
|
||||
# print(f"RPCBase: {self._gui_id}")
|
||||
|
||||
def __repr__(self):
|
||||
type_ = type(self)
|
||||
qualname = type_.__qualname__
|
||||
return f"<{qualname} object at {hex(id(self))}>"
|
||||
|
||||
@property
|
||||
def _root(self):
|
||||
"""
|
||||
Get the root widget. This is the BECFigure widget that holds
|
||||
the anchor gui_id.
|
||||
"""
|
||||
parent = self
|
||||
# pylint: disable=protected-access
|
||||
while parent._parent is not None:
|
||||
parent = parent._parent
|
||||
return parent
|
||||
|
||||
def _run_rpc(self, method, *args, wait_for_rpc_response=True, timeout=3, **kwargs):
|
||||
"""
|
||||
Run the RPC call.
|
||||
|
||||
Args:
|
||||
method: The method to call.
|
||||
args: The arguments to pass to the method.
|
||||
wait_for_rpc_response: Whether to wait for the RPC response.
|
||||
kwargs: The keyword arguments to pass to the method.
|
||||
|
||||
Returns:
|
||||
The result of the RPC call.
|
||||
"""
|
||||
request_id = str(uuid.uuid4())
|
||||
rpc_msg = messages.GUIInstructionMessage(
|
||||
action=method,
|
||||
parameter={"args": args, "kwargs": kwargs, "gui_id": self._gui_id},
|
||||
metadata={"request_id": request_id},
|
||||
)
|
||||
|
||||
# pylint: disable=protected-access
|
||||
receiver = self._root._gui_id
|
||||
if wait_for_rpc_response:
|
||||
redis_msg = QtRedisMessageWaiter(
|
||||
self._client.connector, MessageEndpoints.gui_instruction_response(request_id)
|
||||
)
|
||||
|
||||
self._client.connector.set_and_publish(MessageEndpoints.gui_instructions(receiver), rpc_msg)
|
||||
|
||||
if wait_for_rpc_response:
|
||||
response = redis_msg.wait(timeout)
|
||||
|
||||
if response is None:
|
||||
raise RPCResponseTimeoutError(request_id, timeout)
|
||||
|
||||
# get class name
|
||||
if not response.accepted:
|
||||
raise ValueError(response.message["error"])
|
||||
msg_result = response.message.get("result")
|
||||
return self._create_widget_from_msg_result(msg_result)
|
||||
|
||||
def _create_widget_from_msg_result(self, msg_result):
|
||||
if msg_result is None:
|
||||
return None
|
||||
if isinstance(msg_result, list):
|
||||
return [self._create_widget_from_msg_result(res) for res in msg_result]
|
||||
if isinstance(msg_result, dict):
|
||||
if "__rpc__" not in msg_result:
|
||||
return {
|
||||
key: self._create_widget_from_msg_result(val) for key, val in msg_result.items()
|
||||
}
|
||||
cls = msg_result.pop("widget_class", None)
|
||||
msg_result.pop("__rpc__", None)
|
||||
|
||||
if not cls:
|
||||
return msg_result
|
||||
|
||||
cls = getattr(client, cls)
|
||||
# print(msg_result)
|
||||
return cls(parent=self, **msg_result)
|
||||
return msg_result
|
||||
|
||||
def gui_is_alive(self):
|
||||
"""
|
||||
Check if the GUI is alive.
|
||||
"""
|
||||
heart = self._client.connector.get(MessageEndpoints.gui_heartbeat(self._root._gui_id))
|
||||
return heart is not None
|
||||
189
bec_widgets/cli/generate_cli.py
Normal file
@@ -0,0 +1,189 @@
|
||||
# pylint: disable=missing-module-docstring
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import inspect
|
||||
import os
|
||||
import sys
|
||||
|
||||
import black
|
||||
import isort
|
||||
|
||||
from bec_widgets.utils.generate_designer_plugin import DesignerPluginGenerator
|
||||
from bec_widgets.utils.plugin_utils import BECClassContainer, get_rpc_classes
|
||||
|
||||
if sys.version_info >= (3, 11):
|
||||
from typing import get_overloads
|
||||
else:
|
||||
print(
|
||||
"Python version is less than 3.11, using dummy function for get_overloads. "
|
||||
"If you want to use the real function 'typing.get_overloads()', please use Python 3.11 or later."
|
||||
)
|
||||
|
||||
def get_overloads(_obj):
|
||||
"""
|
||||
Dummy function for Python versions before 3.11.
|
||||
"""
|
||||
return []
|
||||
|
||||
|
||||
class ClientGenerator:
|
||||
def __init__(self):
|
||||
self.header = """# This file was automatically generated by generate_cli.py\n
|
||||
import enum
|
||||
from typing import Literal, Optional, overload
|
||||
|
||||
from bec_widgets.cli.client_utils import RPCBase, rpc_call, BECGuiClientMixin
|
||||
|
||||
# pylint: skip-file"""
|
||||
|
||||
self.content = ""
|
||||
|
||||
def generate_client(self, class_container: BECClassContainer):
|
||||
"""
|
||||
Generate the client for the published classes.
|
||||
|
||||
Args:
|
||||
class_container: The class container with the classes to generate the client for.
|
||||
"""
|
||||
rpc_top_level_classes = class_container.rpc_top_level_classes
|
||||
rpc_top_level_classes.sort(key=lambda x: x.__name__)
|
||||
connector_classes = class_container.connector_classes
|
||||
connector_classes.sort(key=lambda x: x.__name__)
|
||||
|
||||
self.write_client_enum(rpc_top_level_classes)
|
||||
for cls in connector_classes:
|
||||
self.content += "\n\n"
|
||||
self.generate_content_for_class(cls)
|
||||
|
||||
def write_client_enum(self, published_classes: list[type]):
|
||||
"""
|
||||
Write the client enum to the content.
|
||||
"""
|
||||
self.content += """
|
||||
class Widgets(str, enum.Enum):
|
||||
\"\"\"
|
||||
Enum for the available widgets.
|
||||
\"\"\"
|
||||
"""
|
||||
for cls in published_classes:
|
||||
self.content += f'{cls.__name__} = "{cls.__name__}"\n '
|
||||
|
||||
def generate_content_for_class(self, cls):
|
||||
"""
|
||||
Generate the content for the class.
|
||||
|
||||
Args:
|
||||
cls: The class for which to generate the content.
|
||||
"""
|
||||
|
||||
class_name = cls.__name__
|
||||
|
||||
# Generate the content
|
||||
if cls.__name__ == "BECDockArea":
|
||||
self.content += f"""
|
||||
class {class_name}(RPCBase, BECGuiClientMixin):"""
|
||||
else:
|
||||
self.content += f"""
|
||||
class {class_name}(RPCBase):"""
|
||||
if not cls.USER_ACCESS:
|
||||
self.content += """...
|
||||
"""
|
||||
for method in cls.USER_ACCESS:
|
||||
obj = getattr(cls, method)
|
||||
if isinstance(obj, property):
|
||||
self.content += """
|
||||
@property
|
||||
@rpc_call"""
|
||||
sig = str(inspect.signature(obj.fget))
|
||||
doc = inspect.getdoc(obj.fget)
|
||||
else:
|
||||
sig = str(inspect.signature(obj))
|
||||
doc = inspect.getdoc(obj)
|
||||
overloads = get_overloads(obj)
|
||||
for overload in overloads:
|
||||
sig_overload = str(inspect.signature(overload))
|
||||
self.content += f"""
|
||||
@overload
|
||||
def {method}{str(sig_overload)}: ...
|
||||
"""
|
||||
|
||||
self.content += """
|
||||
@rpc_call"""
|
||||
self.content += f"""
|
||||
def {method}{str(sig)}:
|
||||
\"\"\"
|
||||
{doc}
|
||||
\"\"\""""
|
||||
|
||||
def write(self, file_name: str):
|
||||
"""
|
||||
Write the content to a file, automatically formatted with black.
|
||||
|
||||
Args:
|
||||
file_name(str): The name of the file to write to.
|
||||
"""
|
||||
# Combine header and content, then format with black
|
||||
full_content = self.header + "\n" + self.content
|
||||
try:
|
||||
formatted_content = black.format_str(full_content, mode=black.FileMode(line_length=100))
|
||||
except black.NothingChanged:
|
||||
formatted_content = full_content
|
||||
|
||||
isort.Config(
|
||||
profile="black",
|
||||
line_length=100,
|
||||
multi_line_output=3,
|
||||
include_trailing_comma=True,
|
||||
known_first_party=["bec_widgets"],
|
||||
)
|
||||
formatted_content = isort.code(formatted_content)
|
||||
|
||||
with open(file_name, "w", encoding="utf-8") as file:
|
||||
file.write(formatted_content)
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
Main entry point for the script, controlled by command line arguments.
|
||||
"""
|
||||
|
||||
parser = argparse.ArgumentParser(description="Auto-generate the client for RPC widgets")
|
||||
parser.add_argument("--core", action="store_true", help="Whether to generate the core client")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.core:
|
||||
current_path = os.path.dirname(__file__)
|
||||
client_path = os.path.join(current_path, "client.py")
|
||||
|
||||
rpc_classes = get_rpc_classes("bec_widgets")
|
||||
|
||||
generator = ClientGenerator()
|
||||
generator.generate_client(rpc_classes)
|
||||
generator.write(client_path)
|
||||
|
||||
for cls in rpc_classes.plugins:
|
||||
plugin = DesignerPluginGenerator(cls)
|
||||
if not hasattr(plugin, "info"):
|
||||
continue
|
||||
|
||||
# if the class directory already has a register, plugin and pyproject file, skip
|
||||
if os.path.exists(
|
||||
os.path.join(plugin.info.base_path, f"register_{plugin.info.plugin_name_snake}.py")
|
||||
):
|
||||
continue
|
||||
if os.path.exists(
|
||||
os.path.join(plugin.info.base_path, f"{plugin.info.plugin_name_snake}_plugin.py")
|
||||
):
|
||||
continue
|
||||
if os.path.exists(
|
||||
os.path.join(plugin.info.base_path, f"{plugin.info.plugin_name_snake}.pyproject")
|
||||
):
|
||||
continue
|
||||
plugin.run()
|
||||
|
||||
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
sys.argv = ["generate_cli.py", "--core"]
|
||||
main()
|
||||
80
bec_widgets/cli/rpc_register.py
Normal file
@@ -0,0 +1,80 @@
|
||||
from threading import Lock
|
||||
from weakref import WeakValueDictionary
|
||||
|
||||
from qtpy.QtCore import QObject
|
||||
|
||||
|
||||
class RPCRegister:
|
||||
"""
|
||||
A singleton class that keeps track of all the RPC objects registered in the system for CLI usage.
|
||||
"""
|
||||
|
||||
_instance = None
|
||||
_initialized = False
|
||||
_lock = Lock()
|
||||
|
||||
def __new__(cls, *args, **kwargs):
|
||||
if cls._instance is None:
|
||||
cls._instance = super(RPCRegister, cls).__new__(cls)
|
||||
cls._initialized = False
|
||||
return cls._instance
|
||||
|
||||
def __init__(self):
|
||||
if self._initialized:
|
||||
return
|
||||
self._rpc_register = WeakValueDictionary()
|
||||
self._initialized = True
|
||||
|
||||
def add_rpc(self, rpc: QObject):
|
||||
"""
|
||||
Add an RPC object to the register.
|
||||
|
||||
Args:
|
||||
rpc(QObject): The RPC object to be added to the register.
|
||||
"""
|
||||
if not hasattr(rpc, "gui_id"):
|
||||
raise ValueError("RPC object must have a 'gui_id' attribute.")
|
||||
self._rpc_register[rpc.gui_id] = rpc
|
||||
|
||||
def remove_rpc(self, rpc: str):
|
||||
"""
|
||||
Remove an RPC object from the register.
|
||||
|
||||
Args:
|
||||
rpc(str): The RPC object to be removed from the register.
|
||||
"""
|
||||
if not hasattr(rpc, "gui_id"):
|
||||
raise ValueError(f"RPC object {rpc} must have a 'gui_id' attribute.")
|
||||
self._rpc_register.pop(rpc.gui_id, None)
|
||||
|
||||
def get_rpc_by_id(self, gui_id: str) -> QObject:
|
||||
"""
|
||||
Get an RPC object by its ID.
|
||||
|
||||
Args:
|
||||
gui_id(str): The ID of the RPC object to be retrieved.
|
||||
|
||||
Returns:
|
||||
QObject: The RPC object with the given ID.
|
||||
"""
|
||||
rpc_object = self._rpc_register.get(gui_id, None)
|
||||
return rpc_object
|
||||
|
||||
def list_all_connections(self) -> dict:
|
||||
"""
|
||||
List all the registered RPC objects.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing all the registered RPC objects.
|
||||
"""
|
||||
with self._lock:
|
||||
connections = dict(self._rpc_register)
|
||||
return connections
|
||||
|
||||
@classmethod
|
||||
def reset_singleton(cls):
|
||||
"""
|
||||
Reset the singleton instance.
|
||||
"""
|
||||
cls._instance = None
|
||||
cls._initialized = False
|
||||
53
bec_widgets/cli/rpc_wigdet_handler.py
Normal file
@@ -0,0 +1,53 @@
|
||||
from bec_widgets.utils import BECConnector
|
||||
|
||||
|
||||
class RPCWidgetHandler:
|
||||
"""Handler class for creating widgets from RPC messages."""
|
||||
|
||||
def __init__(self):
|
||||
self._widget_classes = None
|
||||
|
||||
@property
|
||||
def widget_classes(self):
|
||||
"""
|
||||
Get the available widget classes.
|
||||
|
||||
Returns:
|
||||
dict: The available widget classes.
|
||||
"""
|
||||
if self._widget_classes is None:
|
||||
self.update_available_widgets()
|
||||
return self._widget_classes
|
||||
|
||||
def update_available_widgets(self):
|
||||
"""
|
||||
Update the available widgets.
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
from bec_widgets.utils.plugin_utils import get_rpc_classes
|
||||
|
||||
clss = get_rpc_classes("bec_widgets")
|
||||
self._widget_classes = {cls.__name__: cls for cls in clss.top_level_classes}
|
||||
|
||||
def create_widget(self, widget_type, **kwargs) -> BECConnector:
|
||||
"""
|
||||
Create a widget from an RPC message.
|
||||
|
||||
Args:
|
||||
widget_type(str): The type of the widget.
|
||||
**kwargs: The keyword arguments for the widget.
|
||||
|
||||
Returns:
|
||||
widget(BECConnector): The created widget.
|
||||
"""
|
||||
if self._widget_classes is None:
|
||||
self.update_available_widgets()
|
||||
widget_class = self._widget_classes.get(widget_type)
|
||||
if widget_class:
|
||||
return widget_class(**kwargs)
|
||||
raise ValueError(f"Unknown widget type: {widget_type}")
|
||||
|
||||
|
||||
widget_handler = RPCWidgetHandler()
|
||||
234
bec_widgets/cli/server.py
Normal file
@@ -0,0 +1,234 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import inspect
|
||||
import json
|
||||
import signal
|
||||
import sys
|
||||
from contextlib import redirect_stderr, redirect_stdout
|
||||
from typing import Union
|
||||
|
||||
from bec_lib.endpoints import MessageEndpoints
|
||||
from bec_lib.logger import bec_logger
|
||||
from bec_lib.service_config import ServiceConfig
|
||||
from bec_lib.utils.import_utils import lazy_import
|
||||
from qtpy.QtCore import QTimer
|
||||
|
||||
from bec_widgets.cli.rpc_register import RPCRegister
|
||||
from bec_widgets.utils import BECDispatcher
|
||||
from bec_widgets.utils.bec_connector import BECConnector
|
||||
from bec_widgets.utils.bec_dispatcher import QtRedisConnector
|
||||
from bec_widgets.widgets.dock.dock_area import BECDockArea
|
||||
from bec_widgets.widgets.figure import BECFigure
|
||||
|
||||
messages = lazy_import("bec_lib.messages")
|
||||
logger = bec_logger.logger
|
||||
|
||||
|
||||
class BECWidgetsCLIServer:
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
gui_id: str = None,
|
||||
dispatcher: BECDispatcher = None,
|
||||
client=None,
|
||||
config=None,
|
||||
gui_class: Union[BECFigure, BECDockArea] = BECFigure,
|
||||
) -> None:
|
||||
self.dispatcher = BECDispatcher(config=config) if dispatcher is None else dispatcher
|
||||
self.client = self.dispatcher.client if client is None else client
|
||||
self.client.start()
|
||||
self.gui_id = gui_id
|
||||
self.gui = gui_class(gui_id=self.gui_id)
|
||||
self.rpc_register = RPCRegister()
|
||||
self.rpc_register.add_rpc(self.gui)
|
||||
|
||||
self.dispatcher.connect_slot(
|
||||
self.on_rpc_update, MessageEndpoints.gui_instructions(self.gui_id)
|
||||
)
|
||||
|
||||
# Setup QTimer for heartbeat
|
||||
self._shutdown_event = False
|
||||
self._heartbeat_timer = QTimer()
|
||||
self._heartbeat_timer.timeout.connect(self.emit_heartbeat)
|
||||
self._heartbeat_timer.start(200)
|
||||
|
||||
def on_rpc_update(self, msg: dict, metadata: dict):
|
||||
request_id = metadata.get("request_id")
|
||||
try:
|
||||
obj = self.get_object_from_config(msg["parameter"])
|
||||
method = msg["action"]
|
||||
args = msg["parameter"].get("args", [])
|
||||
kwargs = msg["parameter"].get("kwargs", {})
|
||||
res = self.run_rpc(obj, method, args, kwargs)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
self.send_response(request_id, False, {"error": str(e)})
|
||||
else:
|
||||
self.send_response(request_id, True, {"result": res})
|
||||
|
||||
def send_response(self, request_id: str, accepted: bool, msg: dict):
|
||||
self.client.connector.set_and_publish(
|
||||
MessageEndpoints.gui_instruction_response(request_id),
|
||||
messages.RequestResponseMessage(accepted=accepted, message=msg),
|
||||
expire=60,
|
||||
)
|
||||
|
||||
def get_object_from_config(self, config: dict):
|
||||
gui_id = config.get("gui_id")
|
||||
obj = self.rpc_register.get_rpc_by_id(gui_id)
|
||||
if obj is None:
|
||||
raise ValueError(f"Object with gui_id {gui_id} not found")
|
||||
return obj
|
||||
|
||||
def run_rpc(self, obj, method, args, kwargs):
|
||||
method_obj = getattr(obj, method)
|
||||
# check if the method accepts args and kwargs
|
||||
if not callable(method_obj):
|
||||
res = method_obj
|
||||
else:
|
||||
sig = inspect.signature(method_obj)
|
||||
if sig.parameters:
|
||||
res = method_obj(*args, **kwargs)
|
||||
else:
|
||||
res = method_obj()
|
||||
|
||||
if isinstance(res, list):
|
||||
res = [self.serialize_object(obj) for obj in res]
|
||||
elif isinstance(res, dict):
|
||||
res = {key: self.serialize_object(val) for key, val in res.items()}
|
||||
else:
|
||||
res = self.serialize_object(res)
|
||||
return res
|
||||
|
||||
def serialize_object(self, obj):
|
||||
if isinstance(obj, BECConnector):
|
||||
return {
|
||||
"gui_id": obj.gui_id,
|
||||
"widget_class": obj.__class__.__name__,
|
||||
"config": obj.config.model_dump(),
|
||||
"__rpc__": True,
|
||||
}
|
||||
return obj
|
||||
|
||||
def emit_heartbeat(self):
|
||||
if self._shutdown_event is False:
|
||||
self.client.connector.set(
|
||||
MessageEndpoints.gui_heartbeat(self.gui_id),
|
||||
messages.StatusMessage(name=self.gui_id, status=1, info={}),
|
||||
expire=1,
|
||||
)
|
||||
|
||||
def shutdown(self): # TODO not sure if needed when cleanup is done at level of BECConnector
|
||||
self._shutdown_event = True
|
||||
self._heartbeat_timer.stop()
|
||||
self.gui.close()
|
||||
self.client.shutdown()
|
||||
|
||||
|
||||
class SimpleFileLikeFromLogOutputFunc:
|
||||
def __init__(self, log_func):
|
||||
self._log_func = log_func
|
||||
|
||||
def write(self, buffer):
|
||||
for line in buffer.rstrip().splitlines():
|
||||
line = line.rstrip()
|
||||
if line:
|
||||
self._log_func(line)
|
||||
|
||||
def flush(self):
|
||||
return
|
||||
|
||||
def close(self):
|
||||
return
|
||||
|
||||
|
||||
def _start_server(gui_id: str, gui_class: Union[BECFigure, BECDockArea], config: str | None = None):
|
||||
if config:
|
||||
try:
|
||||
config = json.loads(config)
|
||||
service_config = ServiceConfig(config=config)
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
service_config = ServiceConfig(config_path=config)
|
||||
else:
|
||||
# if no config is provided, use the default config
|
||||
service_config = ServiceConfig()
|
||||
|
||||
bec_logger.configure(
|
||||
service_config.redis,
|
||||
QtRedisConnector,
|
||||
service_name="BECWidgetsCLIServer",
|
||||
service_config=service_config.service_config,
|
||||
)
|
||||
server = BECWidgetsCLIServer(gui_id=gui_id, config=service_config, gui_class=gui_class)
|
||||
return server
|
||||
|
||||
|
||||
def main():
|
||||
import argparse
|
||||
import os
|
||||
|
||||
from qtpy.QtCore import QSize
|
||||
from qtpy.QtGui import QIcon
|
||||
from qtpy.QtWidgets import QApplication, QMainWindow
|
||||
|
||||
import bec_widgets
|
||||
|
||||
parser = argparse.ArgumentParser(description="BEC Widgets CLI Server")
|
||||
parser.add_argument("--id", type=str, help="The id of the server")
|
||||
parser.add_argument(
|
||||
"--gui_class",
|
||||
type=str,
|
||||
help="Name of the gui class to be rendered. Possible values: \n- BECFigure\n- BECDockArea",
|
||||
)
|
||||
parser.add_argument("--config", type=str, help="Config file or config string.")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.gui_class == "BECFigure":
|
||||
gui_class = BECFigure
|
||||
elif args.gui_class == "BECDockArea":
|
||||
gui_class = BECDockArea
|
||||
else:
|
||||
print(
|
||||
"Please specify a valid gui_class to run. Use -h for help."
|
||||
"\n Starting with default gui_class BECFigure."
|
||||
)
|
||||
gui_class = BECFigure
|
||||
|
||||
with redirect_stdout(SimpleFileLikeFromLogOutputFunc(logger.debug)):
|
||||
with redirect_stderr(SimpleFileLikeFromLogOutputFunc(logger.error)):
|
||||
app = QApplication(sys.argv)
|
||||
app.setApplicationName("BEC Figure")
|
||||
module_path = os.path.dirname(bec_widgets.__file__)
|
||||
icon = QIcon()
|
||||
icon.addFile(
|
||||
os.path.join(module_path, "assets", "app_icons", "bec_widgets_icon.png"),
|
||||
size=QSize(48, 48),
|
||||
)
|
||||
app.setWindowIcon(icon)
|
||||
|
||||
win = QMainWindow()
|
||||
win.setWindowTitle("BEC Widgets")
|
||||
|
||||
server = _start_server(args.id, gui_class, args.config)
|
||||
|
||||
gui = server.gui
|
||||
win.setCentralWidget(gui)
|
||||
win.resize(800, 600)
|
||||
win.show()
|
||||
|
||||
app.aboutToQuit.connect(server.shutdown)
|
||||
|
||||
def sigint_handler(*args):
|
||||
# display message, for people to let it terminate gracefully
|
||||
print("Caught SIGINT, exiting")
|
||||
app.quit()
|
||||
|
||||
signal.signal(signal.SIGINT, sigint_handler)
|
||||
signal.signal(signal.SIGTERM, sigint_handler)
|
||||
|
||||
sys.exit(app.exec())
|
||||
|
||||
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
main()
|
||||
@@ -1,129 +0,0 @@
|
||||
from typing import List
|
||||
|
||||
import numpy as np
|
||||
import pyqtgraph as pg
|
||||
from pyqtgraph import mkPen
|
||||
from pyqtgraph.Qt import QtCore, QtWidgets
|
||||
|
||||
|
||||
class ConfigPlotter(pg.GraphicsWidget):
|
||||
"""
|
||||
ConfigPlotter is a widget that can be used to plot data from multiple channels
|
||||
in a grid layout. The layout is specified by a list of dicts, where each dict
|
||||
specifies the position of the plot in the grid, the channels to plot, and the
|
||||
type of plot to use. The plot type is specified by the name of the pyqtgraph
|
||||
item to use. For example, to plot a single channel in a PlotItem, the config
|
||||
would look like this:
|
||||
|
||||
config = [
|
||||
{
|
||||
"cols": 1,
|
||||
"rows": 1,
|
||||
"y": 0,
|
||||
"x": 0,
|
||||
"config": {"channels": ["a"], "label_xy": ["", "a"], "item": "PlotItem"},
|
||||
}
|
||||
]
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, configs: List[dict], parent=None):
|
||||
super().__init__(parent)
|
||||
self.configs = configs
|
||||
self.plots = {}
|
||||
self._init_ui()
|
||||
self._init_plots()
|
||||
|
||||
def _init_ui(self):
|
||||
pg.setConfigOption("background", "w")
|
||||
pg.setConfigOption("foreground", "k")
|
||||
|
||||
# pylint: disable=no-member
|
||||
self.pen = mkPen(color=(56, 76, 107), width=4, style=QtCore.Qt.SolidLine)
|
||||
|
||||
self.view = pg.GraphicsView()
|
||||
self.view.setAntialiasing(True)
|
||||
self.view.show()
|
||||
|
||||
self.layout = pg.GraphicsLayout()
|
||||
self.view.setCentralWidget(self.layout)
|
||||
|
||||
def _init_plots(self):
|
||||
for config in self.configs:
|
||||
channels = config["config"]["channels"]
|
||||
for channel in channels:
|
||||
item = pg.PlotItem()
|
||||
self.layout.addItem(
|
||||
item,
|
||||
row=config["y"],
|
||||
col=config["x"],
|
||||
rowspan=config["rows"],
|
||||
colspan=config["cols"],
|
||||
)
|
||||
|
||||
# call the corresponding init function, e.g. init_plotitem
|
||||
init_func = getattr(self, f"init_{config['config']['item']}")
|
||||
init_func(channel, config["config"], item)
|
||||
|
||||
# self.init_ImageItem(channel, config["config"], item)
|
||||
|
||||
def init_PlotItem(self, channel: str, config: dict, item: pg.GraphicsItem):
|
||||
"""
|
||||
Initialize a PlotItem
|
||||
|
||||
Args:
|
||||
channel(str): channel to plot
|
||||
config(dict): config dict for the channel
|
||||
item(pg.GraphicsItem): PlotItem to plot the data
|
||||
"""
|
||||
# pylint: disable=invalid-name
|
||||
plot_data = item.plot(np.random.rand(100), pen=self.pen)
|
||||
item.setLabel("left", channel)
|
||||
self.plots[channel] = {"item": item, "plot_data": plot_data}
|
||||
|
||||
def init_ImageItem(self, channel: str, config: dict, item: pg.GraphicsItem):
|
||||
"""
|
||||
Initialize an ImageItem
|
||||
|
||||
Args:
|
||||
channel(str): channel to plot
|
||||
config(dict): config dict for the channel
|
||||
item(pg.GraphicsItem): ImageItem to plot the data
|
||||
"""
|
||||
# pylint: disable=invalid-name
|
||||
img = pg.ImageItem()
|
||||
item.addItem(img)
|
||||
img.setImage(np.random.rand(100, 100))
|
||||
self.plots[channel] = {"item": item, "plot_data": img}
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
|
||||
CONFIG = [
|
||||
{
|
||||
"cols": 1,
|
||||
"rows": 1,
|
||||
"y": 0,
|
||||
"x": 0,
|
||||
"config": {"channels": ["a"], "label_xy": ["", "a"], "item": "PlotItem"},
|
||||
},
|
||||
{
|
||||
"cols": 1,
|
||||
"rows": 1,
|
||||
"y": 1,
|
||||
"x": 0,
|
||||
"config": {"channels": ["b"], "label_xy": ["", "b"], "item": "PlotItem"},
|
||||
},
|
||||
{
|
||||
"cols": 1,
|
||||
"rows": 2,
|
||||
"y": 0,
|
||||
"x": 1,
|
||||
"config": {"channels": ["c"], "label_xy": ["", "c"], "item": "ImageItem"},
|
||||
},
|
||||
]
|
||||
|
||||
app = QtWidgets.QApplication(sys.argv)
|
||||
win = ConfigPlotter(CONFIG)
|
||||
pg.exec()
|
||||
@@ -1,38 +0,0 @@
|
||||
import signal
|
||||
import socket
|
||||
from PyQt5.QtNetwork import QAbstractSocket
|
||||
|
||||
|
||||
def setup(app):
|
||||
app.signalwatchdog = SignalWatchdog() # need to store to keep socket pair alive
|
||||
signal.signal(signal.SIGINT, make_quit_handler(app))
|
||||
|
||||
|
||||
def make_quit_handler(app):
|
||||
def handler(*args):
|
||||
print() # make ^C appear on its own line
|
||||
app.quit()
|
||||
|
||||
return handler
|
||||
|
||||
|
||||
class SignalWatchdog(QAbstractSocket):
|
||||
def __init__(self):
|
||||
"""
|
||||
Propagates system signals from Python to QEventLoop
|
||||
adapted from https://stackoverflow.com/a/65802260/655404
|
||||
"""
|
||||
super().__init__(QAbstractSocket.SctpSocket, None)
|
||||
|
||||
self.writer, self.reader = writer, reader = socket.socketpair()
|
||||
writer.setblocking(False)
|
||||
|
||||
fd_writer = writer.fileno()
|
||||
fd_reader = reader.fileno()
|
||||
|
||||
signal.set_wakeup_fd(fd_writer) # Python hook
|
||||
self.setSocketDescriptor(fd_reader) # Qt hook
|
||||
|
||||
self.readyRead.connect(
|
||||
lambda: None
|
||||
) # dummy function call that lets the Python interpreter run
|
||||
@@ -1,33 +0,0 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
from PyQt5 import QtWidgets, uic
|
||||
|
||||
|
||||
class UI(QtWidgets.QWidget):
|
||||
def __init__(self, uipath):
|
||||
super().__init__()
|
||||
|
||||
self.ui = uic.loadUi(uipath, self)
|
||||
|
||||
_, fname = os.path.split(uipath)
|
||||
self.setWindowTitle(fname)
|
||||
|
||||
self.show()
|
||||
|
||||
|
||||
def main():
|
||||
"""A basic script to display UI file
|
||||
|
||||
Run the script, passing UI file path as an argument, e.g.
|
||||
$ python bec_widgets/display_ui_file.py bec_widgets/line_plot.ui
|
||||
"""
|
||||
app = QtWidgets.QApplication(sys.argv)
|
||||
|
||||
UI(sys.argv[1])
|
||||
|
||||
sys.exit(app.exec_())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,128 +0,0 @@
|
||||
import threading
|
||||
import numpy as np
|
||||
import pyqtgraph as pg
|
||||
from PyQt5.QtCore import pyqtProperty, pyqtSlot, pyqtSignal
|
||||
from PyQt5.QtWidgets import QHBoxLayout, QWidget, QCheckBox
|
||||
|
||||
import zmq
|
||||
import json
|
||||
import h5py
|
||||
import os
|
||||
|
||||
|
||||
|
||||
|
||||
class EigerPlot(QWidget):
|
||||
update_signale = pyqtSignal()
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.mask_file = os.path.expanduser('~/Data10/software/radial_integration_scipts/bad_pix_map_Eiger9M.h5')
|
||||
|
||||
pg.setConfigOptions(background="w", foreground="k", antialias=True)
|
||||
|
||||
self.layout = QHBoxLayout()
|
||||
|
||||
self.setLayout(self.layout)
|
||||
|
||||
self.hist_lim = [0,20]
|
||||
|
||||
|
||||
self.glw = pg.GraphicsLayoutWidget()
|
||||
self.use_fft = False
|
||||
|
||||
# self.glw.show()
|
||||
# self.setCentralItem(self.glw)
|
||||
|
||||
self.checkBox_FFT = QCheckBox("FFT")
|
||||
self.checkBox_FFT.stateChanged.connect(self.on_fft_changed)
|
||||
|
||||
self.layout.addWidget(self.checkBox_FFT)
|
||||
|
||||
self.layout.addWidget(self.glw)
|
||||
|
||||
|
||||
self.plot_item = pg.PlotItem()
|
||||
self.plot_item.setAspectLocked(True)
|
||||
self.imageItem = pg.ImageItem()
|
||||
self.plot_item.addItem(self.imageItem)
|
||||
|
||||
self.glw.addItem(self.plot_item)
|
||||
|
||||
self.hist = pg.HistogramLUTItem()
|
||||
self.hist.setImageItem(self.imageItem)
|
||||
self.hist.setLevels(min=self.hist_lim[0],max=self.hist_lim[1])
|
||||
self.hist.setHistogramRange(self.hist_lim[0] - 0.1 * self.hist_lim[0],self.hist_lim[1] + 0.1 * self.hist_lim[1])
|
||||
self.hist.disableAutoHistogramRange()
|
||||
|
||||
self.hist.gradient.loadPreset('magma')
|
||||
|
||||
self.glw.addItem(self.hist)
|
||||
|
||||
# self.plot_item.addItem(self.hist)
|
||||
|
||||
# add plot and histogram to glw
|
||||
# self.glw.addItem(self.plot_item)
|
||||
# self.glw.addItem(self.hist)
|
||||
|
||||
# self.imageItem.setImage([[0,1,2],[4,5,6]])
|
||||
self.update_signale.connect(self.on_image_update)
|
||||
self.mask = None
|
||||
self._load_mask()
|
||||
|
||||
self.start_zmq_consumer()
|
||||
|
||||
def start_zmq_consumer(self):
|
||||
consumer_thread = threading.Thread(target=self.zmq_consumer, daemon=True).start()
|
||||
|
||||
def _load_mask(self):
|
||||
with h5py.File(self.mask_file, "r") as f:
|
||||
self.mask = f["data"][...]
|
||||
|
||||
def zmq_consumer(self):
|
||||
try:
|
||||
print("starting consumer")
|
||||
live_stream_url = "tcp://129.129.95.38:20000"
|
||||
receiver = zmq.Context().socket(zmq.SUB)
|
||||
receiver.connect(live_stream_url)
|
||||
receiver.setsockopt_string(zmq.SUBSCRIBE, "")
|
||||
|
||||
while True:
|
||||
|
||||
raw_meta, raw_data = receiver.recv_multipart()
|
||||
meta = json.loads(raw_meta.decode('utf-8'))
|
||||
self.image = np.frombuffer(raw_data, dtype=meta['type']).reshape(meta['shape'])
|
||||
self.update_signale.emit()
|
||||
|
||||
finally:
|
||||
receiver.disconnect(live_stream_url)
|
||||
receiver.context.term()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_fft_changed(self):
|
||||
self.update_signale.emit()
|
||||
|
||||
@pyqtSlot()
|
||||
def on_image_update(self):
|
||||
# if self.checkBox_FFT.isChecked():
|
||||
# img = np.log10(np.abs(np.fft.fftshift(np.fft.fft2(self.image*(1-self.mask.T)))))
|
||||
# else:
|
||||
|
||||
img = np.log10(self.image*(1-self.mask)+1)
|
||||
self.imageItem.setImage(img,autoLevels=False)
|
||||
|
||||
# hardcoded hist level
|
||||
# self.hist.setLevels(min=self.hist_lim[0],max=self.hist_lim[1])
|
||||
# self.hist.setHistogramRange(self.hist_lim[0] - 0.1 * self.hist_lim[0],self.hist_lim[1] + 0.1 * self.hist_lim[1])
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
|
||||
from PyQt5.QtWidgets import QApplication
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
|
||||
plot = EigerPlot()
|
||||
|
||||
plot.show()
|
||||
|
||||
sys.exit(app.exec_())
|
||||
@@ -1,298 +0,0 @@
|
||||
import json
|
||||
import json
|
||||
import os
|
||||
import threading
|
||||
import time
|
||||
|
||||
import h5py
|
||||
import numpy as np
|
||||
import pyqtgraph as pg
|
||||
import zmq
|
||||
from PyQt5.QtCore import pyqtSignal
|
||||
from PyQt5.QtCore import pyqtSlot
|
||||
from PyQt5.QtGui import QKeySequence
|
||||
from PyQt5.QtWidgets import (
|
||||
QWidget,
|
||||
QFileDialog,
|
||||
QShortcut,
|
||||
QDialog,
|
||||
QVBoxLayout,
|
||||
QLabel,
|
||||
QFrame,
|
||||
)
|
||||
from pyqtgraph.Qt import uic
|
||||
from scipy.stats import multivariate_normal
|
||||
|
||||
|
||||
class EigerPlot(QWidget):
|
||||
update_signal = pyqtSignal()
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
# pg.setConfigOptions(background="w", foreground="k", antialias=True)
|
||||
|
||||
current_path = os.path.dirname(__file__)
|
||||
uic.loadUi(os.path.join(current_path, "eiger_plot.ui"), self)
|
||||
|
||||
# Set widow name
|
||||
self.setWindowTitle("Eiger Plot")
|
||||
|
||||
self.hist_lims = None
|
||||
self.mask = None
|
||||
self.image = None
|
||||
|
||||
# UI
|
||||
self.init_ui()
|
||||
self.hook_signals()
|
||||
self.key_bindings()
|
||||
|
||||
# ZMQ Consumer
|
||||
self.start_zmq_consumer()
|
||||
|
||||
def init_ui(self):
|
||||
# Create Plot and add ImageItem
|
||||
self.plot_item = pg.PlotItem()
|
||||
self.plot_item.setAspectLocked(True)
|
||||
self.imageItem = pg.ImageItem()
|
||||
self.plot_item.addItem(self.imageItem)
|
||||
|
||||
# Setting up histogram
|
||||
self.hist = pg.HistogramLUTItem()
|
||||
self.hist.setImageItem(self.imageItem)
|
||||
self.hist.gradient.loadPreset("magma")
|
||||
self.update_hist()
|
||||
|
||||
# Adding Items to Graphical Layout
|
||||
self.glw.addItem(self.plot_item)
|
||||
self.glw.addItem(self.hist)
|
||||
|
||||
def hook_signals(self):
|
||||
# Buttons
|
||||
self.pushButton_test.clicked.connect(self.start_sim_stream)
|
||||
self.pushButton_mask.clicked.connect(self.load_mask_dialog)
|
||||
self.pushButton_delete_mask.clicked.connect(self.delete_mask)
|
||||
self.pushButton_help.clicked.connect(self.show_help_dialog)
|
||||
|
||||
# SpinBoxes
|
||||
self.doubleSpinBox_hist_min.valueChanged.connect(self.update_hist)
|
||||
self.doubleSpinBox_hist_max.valueChanged.connect(self.update_hist)
|
||||
|
||||
# Signal/Slots
|
||||
self.update_signal.connect(self.on_image_update)
|
||||
|
||||
def key_bindings(self):
|
||||
# Key bindings for rotation
|
||||
rotate_plus = QShortcut(QKeySequence("Ctrl+A"), self)
|
||||
rotate_minus = QShortcut(QKeySequence("Ctrl+Z"), self)
|
||||
self.comboBox_rotation.setToolTip("Increase rotation: Ctrl+A\nDecrease rotation: Ctrl+Z")
|
||||
self.checkBox_transpose.setToolTip("Toggle transpose: Ctrl+T")
|
||||
|
||||
max_index = self.comboBox_rotation.count() - 1 # Maximum valid index
|
||||
|
||||
rotate_plus.activated.connect(
|
||||
lambda: self.comboBox_rotation.setCurrentIndex(
|
||||
min(self.comboBox_rotation.currentIndex() + 1, max_index)
|
||||
)
|
||||
)
|
||||
|
||||
rotate_minus.activated.connect(
|
||||
lambda: self.comboBox_rotation.setCurrentIndex(
|
||||
max(self.comboBox_rotation.currentIndex() - 1, 0)
|
||||
)
|
||||
)
|
||||
|
||||
# Key bindings for transpose
|
||||
transpose = QShortcut(QKeySequence("Ctrl+T"), self)
|
||||
transpose.activated.connect(self.checkBox_transpose.toggle)
|
||||
|
||||
FFT = QShortcut(QKeySequence("Ctrl+F"), self)
|
||||
FFT.activated.connect(self.checkBox_FFT.toggle)
|
||||
self.checkBox_FFT.setToolTip("Toggle FFT: Ctrl+F")
|
||||
|
||||
log = QShortcut(QKeySequence("Ctrl+L"), self)
|
||||
log.activated.connect(self.checkBox_log.toggle)
|
||||
self.checkBox_log.setToolTip("Toggle log: Ctrl+L")
|
||||
|
||||
mask = QShortcut(QKeySequence("Ctrl+M"), self)
|
||||
mask.activated.connect(self.pushButton_mask.click)
|
||||
self.pushButton_mask.setToolTip("Load mask: Ctrl+M")
|
||||
|
||||
delete_mask = QShortcut(QKeySequence("Ctrl+D"), self)
|
||||
delete_mask.activated.connect(self.pushButton_delete_mask.click)
|
||||
self.pushButton_delete_mask.setToolTip("Delete mask: Ctrl+D")
|
||||
|
||||
def update_hist(self):
|
||||
self.hist_levels = [
|
||||
self.doubleSpinBox_hist_min.value(),
|
||||
self.doubleSpinBox_hist_max.value(),
|
||||
]
|
||||
self.hist.setLevels(min=self.hist_levels[0], max=self.hist_levels[1])
|
||||
self.hist.setHistogramRange(
|
||||
self.hist_levels[0] - 0.1 * self.hist_levels[0],
|
||||
self.hist_levels[1] + 0.1 * self.hist_levels[1],
|
||||
)
|
||||
|
||||
def load_mask_dialog(self):
|
||||
options = QFileDialog.Options()
|
||||
options |= QFileDialog.ReadOnly
|
||||
file_name, _ = QFileDialog.getOpenFileName(
|
||||
self, "Select Mask File", "", "H5 Files (*.h5);;All Files (*)", options=options
|
||||
)
|
||||
if file_name:
|
||||
self.load_mask(file_name)
|
||||
|
||||
def load_mask(self, path):
|
||||
try:
|
||||
with h5py.File(path, "r") as f:
|
||||
self.mask = f["data"][...]
|
||||
if self.mask is not None:
|
||||
# Set label to mask name without path
|
||||
self.label_mask.setText(os.path.basename(path))
|
||||
except KeyError as e:
|
||||
# Update GUI with the error message
|
||||
print(f"Error: {str(e)}")
|
||||
|
||||
def delete_mask(self):
|
||||
self.mask = None
|
||||
self.label_mask.setText("No Mask")
|
||||
|
||||
@pyqtSlot()
|
||||
def on_image_update(self):
|
||||
# TODO first rotate then transpose
|
||||
if self.mask is not None:
|
||||
# self.image = np.ma.masked_array(self.image, mask=self.mask) #TODO test if np works
|
||||
self.image = self.image * (1 - self.mask) + 1
|
||||
|
||||
if self.checkBox_FFT.isChecked():
|
||||
self.image = np.abs(np.fft.fftshift(np.fft.fft2(self.image)))
|
||||
|
||||
if self.comboBox_rotation.currentIndex() > 0: # rotate
|
||||
self.image = np.rot90(self.image, k=self.comboBox_rotation.currentIndex(), axes=(0, 1))
|
||||
|
||||
if self.checkBox_transpose.isChecked(): # transpose
|
||||
self.image = np.transpose(self.image)
|
||||
|
||||
if self.checkBox_log.isChecked():
|
||||
self.image = np.log(self.image)
|
||||
|
||||
self.imageItem.setImage(self.image, autoLevels=False)
|
||||
|
||||
###############################
|
||||
# ZMQ Consumer
|
||||
###############################
|
||||
|
||||
def start_zmq_consumer(self):
|
||||
consumer_thread = threading.Thread(target=self.zmq_consumer, daemon=True).start()
|
||||
|
||||
def zmq_consumer(self):
|
||||
try:
|
||||
print("starting consumer")
|
||||
live_stream_url = "tcp://129.129.95.38:20000"
|
||||
receiver = zmq.Context().socket(zmq.SUB)
|
||||
receiver.connect(live_stream_url)
|
||||
receiver.setsockopt_string(zmq.SUBSCRIBE, "")
|
||||
|
||||
while True:
|
||||
raw_meta, raw_data = receiver.recv_multipart()
|
||||
meta = json.loads(raw_meta.decode("utf-8"))
|
||||
self.image = np.frombuffer(raw_data, dtype=meta["type"]).reshape(meta["shape"])
|
||||
self.update_signal.emit()
|
||||
|
||||
finally:
|
||||
receiver.disconnect(live_stream_url)
|
||||
receiver.context.term()
|
||||
|
||||
###############################
|
||||
# just simulations from here
|
||||
###############################
|
||||
|
||||
def show_help_dialog(self):
|
||||
dialog = QDialog(self)
|
||||
dialog.setWindowTitle("Help")
|
||||
|
||||
layout = QVBoxLayout()
|
||||
|
||||
# Key bindings section
|
||||
layout.addWidget(QLabel("Keyboard Shortcuts:"))
|
||||
|
||||
key_bindings = [
|
||||
("Ctrl+A", "Increase rotation"),
|
||||
("Ctrl+Z", "Decrease rotation"),
|
||||
("Ctrl+T", "Toggle transpose"),
|
||||
("Ctrl+F", "Toggle FFT"),
|
||||
("Ctrl+L", "Toggle log scale"),
|
||||
("Ctrl+M", "Load mask"),
|
||||
("Ctrl+D", "Delete mask"),
|
||||
]
|
||||
|
||||
for keys, action in key_bindings:
|
||||
layout.addWidget(QLabel(f"{keys} - {action}"))
|
||||
|
||||
# Separator
|
||||
separator = QFrame()
|
||||
separator.setFrameShape(QFrame.HLine)
|
||||
separator.setFrameShadow(QFrame.Sunken)
|
||||
layout.addWidget(separator)
|
||||
|
||||
# Histogram section
|
||||
layout.addWidget(QLabel("Histogram:"))
|
||||
layout.addWidget(
|
||||
QLabel(
|
||||
"Use the Double Spin Boxes to adjust the minimum and maximum values of the histogram."
|
||||
)
|
||||
)
|
||||
|
||||
# Another Separator
|
||||
another_separator = QFrame()
|
||||
another_separator.setFrameShape(QFrame.HLine)
|
||||
another_separator.setFrameShadow(QFrame.Sunken)
|
||||
layout.addWidget(another_separator)
|
||||
|
||||
# Mask section
|
||||
layout.addWidget(QLabel("Mask:"))
|
||||
layout.addWidget(
|
||||
QLabel(
|
||||
"Use 'Load Mask' to load a mask from an H5 file. 'Delete Mask' removes the current mask."
|
||||
)
|
||||
)
|
||||
|
||||
dialog.setLayout(layout)
|
||||
dialog.exec_()
|
||||
|
||||
###############################
|
||||
# just simulations from here
|
||||
###############################
|
||||
def start_sim_stream(self):
|
||||
sim_stream_thread = threading.Thread(target=self.sim_stream, daemon=True)
|
||||
sim_stream_thread.start()
|
||||
|
||||
def sim_stream(self):
|
||||
for i in range(100):
|
||||
# Generate 100x100 image of random noise
|
||||
self.image = np.random.rand(100, 100) * 0.2
|
||||
|
||||
# Define Gaussian parameters
|
||||
x, y = np.mgrid[0:50, 0:50]
|
||||
pos = np.dstack((x, y))
|
||||
|
||||
# Center at (25, 25) longer along y-axis
|
||||
rv = multivariate_normal(mean=[25, 25], cov=[[25, 0], [0, 80]])
|
||||
|
||||
# Generate Gaussian in the first quadrant
|
||||
gaussian_quadrant = rv.pdf(pos) * 40
|
||||
|
||||
# Place Gaussian in the first quadrant
|
||||
self.image[0:50, 0:50] += gaussian_quadrant * 10
|
||||
|
||||
self.update_signal.emit()
|
||||
time.sleep(0.1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
from PyQt5.QtWidgets import QApplication
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
plot = EigerPlot()
|
||||
plot.show()
|
||||
sys.exit(app.exec_())
|
||||
@@ -1,214 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>Form</class>
|
||||
<widget class="QWidget" name="Form">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>808</width>
|
||||
<height>675</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="title">
|
||||
<string>Plot Control</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Histogram MIN</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QDoubleSpinBox" name="doubleSpinBox_hist_min">
|
||||
<property name="minimum">
|
||||
<double>-100000.000000000000000</double>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>100000.000000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Histogram MAX</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QDoubleSpinBox" name="doubleSpinBox_hist_max">
|
||||
<property name="minimum">
|
||||
<double>-100000.000000000000000</double>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>100000.000000000000000</double>
|
||||
</property>
|
||||
<property name="value">
|
||||
<double>2.000000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_2">
|
||||
<property name="title">
|
||||
<string>Data Control</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBox_FFT">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>FFT</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBox_log">
|
||||
<property name="text">
|
||||
<string>log</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="pushButton_mask">
|
||||
<property name="text">
|
||||
<string>Load Mask</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="pushButton_delete_mask">
|
||||
<property name="text">
|
||||
<string>Delete Mask</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_3">
|
||||
<property name="title">
|
||||
<string>Orientation</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="2" column="1">
|
||||
<widget class="QComboBox" name="comboBox_rotation">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>0</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>90</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>180</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>270</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Rotation</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="checkBox_transpose">
|
||||
<property name="text">
|
||||
<string>Transpose</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_4">
|
||||
<property name="title">
|
||||
<string>Help</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_mask">
|
||||
<property name="text">
|
||||
<string>No Mask</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="pushButton_help">
|
||||
<property name="text">
|
||||
<string>Help</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="pushButton_test">
|
||||
<property name="text">
|
||||
<string>Simulated Stream</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="GraphicsLayoutWidget" name="glw"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>GraphicsLayoutWidget</class>
|
||||
<extends>QGraphicsView</extends>
|
||||
<header>pyqtgraph.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
@@ -1,77 +0,0 @@
|
||||
plot_settings:
|
||||
background_color: "black"
|
||||
num_columns: 3
|
||||
colormap: "plasma"
|
||||
#TODO add more settings
|
||||
# - plot size
|
||||
|
||||
plot_data:
|
||||
- plot_name: "BPM plot"
|
||||
x:
|
||||
label: 'Motor X'
|
||||
signals:
|
||||
- name: "samx"
|
||||
# entry: "samx"
|
||||
y:
|
||||
label: 'BPM'
|
||||
signals:
|
||||
- name: "gauss_bpm"
|
||||
entry: "gauss_bpm"
|
||||
- name: "gauss_adc1"
|
||||
entry: "gauss_adc1"
|
||||
- name: "gauss_adc2"
|
||||
entry: "gauss_adc2"
|
||||
|
||||
- plot_name: "BPM plot 2"
|
||||
x:
|
||||
label: 'Motor X'
|
||||
signals:
|
||||
- name: "samx"
|
||||
entry: "samx"
|
||||
y:
|
||||
label: 'BPM'
|
||||
signals:
|
||||
- name: "gauss_bpm"
|
||||
entry: "gauss_bpm"
|
||||
- name: "gauss_adc1"
|
||||
entry: "gauss_adc1"
|
||||
- plot_name: "BPM plot 3"
|
||||
x:
|
||||
label: 'Motor X'
|
||||
signals:
|
||||
- name: "samx"
|
||||
entry: "samx"
|
||||
y:
|
||||
label: 'BPM'
|
||||
signals:
|
||||
- name: "gauss_bpm"
|
||||
entry: "gauss_bpm"
|
||||
- name: "gauss_adc1"
|
||||
entry: "gauss_adc1"
|
||||
- plot_name: "ADC plot"
|
||||
x:
|
||||
label: 'Motor Y'
|
||||
signals:
|
||||
- name: "samy"
|
||||
# entry: "samy" # here I also forgot to specify entry
|
||||
y:
|
||||
label: 'ADC'
|
||||
signals:
|
||||
- name: "gauss_bpm"
|
||||
entry: "gauss_bpm"
|
||||
- name: "samx"
|
||||
# I will not specify entry, because I want to take hint from gauss_adc2
|
||||
- plot_name: "Multi"
|
||||
x:
|
||||
label: 'Motor X'
|
||||
signals:
|
||||
- name: "samx"
|
||||
entry: "samx"
|
||||
y:
|
||||
label: 'Multi'
|
||||
signals:
|
||||
- name: "gauss_bpm"
|
||||
entry: "gauss_bpm"
|
||||
- name: "samx"
|
||||
entry: ["samx", "samx_setpoint"]
|
||||
# entry: ["samx","incorect"] #multiple entries for one device
|
||||
@@ -1,516 +0,0 @@
|
||||
import os
|
||||
|
||||
import numpy as np
|
||||
import pyqtgraph as pg
|
||||
from PyQt5.QtCore import pyqtSignal, pyqtSlot
|
||||
from PyQt5.QtWidgets import QApplication, QWidget, QTableWidgetItem, QTableWidget, QFileDialog
|
||||
from pyqtgraph import mkBrush, mkColor, mkPen
|
||||
from pyqtgraph.Qt import QtCore, uic
|
||||
|
||||
from bec_lib.core import MessageEndpoints
|
||||
from bec_widgets.qt_utils import Crosshair, Colors
|
||||
from pyqtgraph.Qt import QtWidgets
|
||||
from pyqtgraph import ColorButton
|
||||
|
||||
|
||||
# TODO implement:
|
||||
# - implement scanID database for visualizing previous scans
|
||||
|
||||
|
||||
class PlotApp(QWidget):
|
||||
"""
|
||||
Main class for PlotApp, designed to plot multiple signals in a grid layout
|
||||
based on a flexible YAML configuration.
|
||||
|
||||
Attributes:
|
||||
update_signal (pyqtSignal): Signal to trigger plot updates.
|
||||
plot_data (list of dict): List of dictionaries containing plot configurations.
|
||||
Each dictionary specifies x and y signals, including their
|
||||
name and entry, for a particular plot.
|
||||
|
||||
Args:
|
||||
plot_settings (dict): Dictionary containing global plot settings such as background color.
|
||||
plot_data (list of dict): List of dictionaries specifying the signals to plot.
|
||||
Each dictionary should contain:
|
||||
- 'x': Dictionary specifying the x-axis settings including
|
||||
a 'signals' list with 'name' and 'entry' fields.
|
||||
If there are multiple entries for one device name, they can be passed as a list.
|
||||
- 'y': Similar to 'x', but for the y-axis.
|
||||
Example:
|
||||
[
|
||||
{
|
||||
'plot_name': 'Plot 1',
|
||||
'x': {'label': 'X Label', 'signals': [{'name': 'x1', 'entry': 'x1_entry'}]},
|
||||
'y': {'label': 'Y Label', 'signals': [{'name': 'y1', 'entry': 'y1_entry'}]}
|
||||
},
|
||||
...
|
||||
]
|
||||
parent (QWidget, optional): Parent widget.
|
||||
"""
|
||||
|
||||
update_signal = pyqtSignal()
|
||||
update_dap_signal = pyqtSignal()
|
||||
|
||||
def __init__(self, plot_settings: dict, plot_data: list, parent=None):
|
||||
super(PlotApp, self).__init__(parent)
|
||||
|
||||
# YAML config
|
||||
self.plot_settings = plot_settings
|
||||
self.plot_data = plot_data
|
||||
|
||||
# Setting global plot settings
|
||||
self.init_plot_background(self.plot_settings["background_color"])
|
||||
|
||||
# Loading UI
|
||||
current_path = os.path.dirname(__file__)
|
||||
uic.loadUi(os.path.join(current_path, "extreme.ui"), self)
|
||||
|
||||
# Nested dictionary to hold x and y data for multiple plots
|
||||
self.data = {}
|
||||
|
||||
self.crosshairs = None
|
||||
self.plots = None
|
||||
self.curves_data = None
|
||||
self.grid_coordinates = None
|
||||
self.scanID = None
|
||||
|
||||
self.user_colors = {} # key: (plot_name, y_name, y_entry), value: color
|
||||
|
||||
# Initialize the UI
|
||||
self.init_ui(self.plot_settings["num_columns"])
|
||||
self.spinBox_N_columns.setValue(
|
||||
self.plot_settings["num_columns"]
|
||||
) # TODO has to be checked if it will not setup more columns than plots
|
||||
self.spinBox_N_columns.setMaximum(len(self.plot_data))
|
||||
self.splitter.setSizes([400, 100])
|
||||
|
||||
# Buttons
|
||||
self.pushButton_save.clicked.connect(self.save_settings_to_yaml)
|
||||
self.pushButton_load.clicked.connect(self.load_settings_from_yaml)
|
||||
|
||||
# Connect the update signal to the update plot method
|
||||
self.proxy_update_plot = pg.SignalProxy(
|
||||
self.update_signal, rateLimit=25, slot=self.update_plot
|
||||
)
|
||||
|
||||
# Change layout of plots when the number of columns is changed in GUI
|
||||
self.spinBox_N_columns.valueChanged.connect(lambda x: self.init_ui(x))
|
||||
|
||||
def init_plot_background(self, background_color: str) -> None:
|
||||
"""
|
||||
Initialize plot settings based on the background color.
|
||||
|
||||
Args:
|
||||
background_color (str): The background color ('white' or 'black').
|
||||
|
||||
This method sets the background and foreground colors for pyqtgraph.
|
||||
If the background is dark ('black'), the foreground will be set to 'white',
|
||||
and vice versa.
|
||||
"""
|
||||
if background_color.lower() == "black":
|
||||
pg.setConfigOption("background", "k")
|
||||
pg.setConfigOption("foreground", "w")
|
||||
elif background_color.lower() == "white":
|
||||
pg.setConfigOption("background", "w")
|
||||
pg.setConfigOption("foreground", "k")
|
||||
else:
|
||||
print(f"Warning: Unknown background color {background_color}. Using default settings.")
|
||||
|
||||
def init_ui(self, num_columns: int = 3) -> None:
|
||||
"""
|
||||
Initialize the UI components, create plots and store their grid positions.
|
||||
|
||||
Args:
|
||||
num_columns (int): Number of columns to wrap the layout.
|
||||
|
||||
This method initializes a dictionary `self.plots` to store the plot objects
|
||||
along with their corresponding x and y signal names. It dynamically arranges
|
||||
the plots in a grid layout based on the given number of columns and dynamically
|
||||
stretches the last plots to fit the remaining space.
|
||||
"""
|
||||
self.glw.clear()
|
||||
self.plots = {}
|
||||
self.grid_coordinates = []
|
||||
|
||||
num_plots = len(self.plot_data)
|
||||
|
||||
# Check if num_columns exceeds the number of plots
|
||||
if num_columns > num_plots:
|
||||
num_columns = num_plots
|
||||
self.plot_settings["num_columns"] = num_columns # Update the settings
|
||||
print(
|
||||
f"Warning: num_columns in the YAML file was greater than the number of plots. Resetting num_columns to {num_columns}."
|
||||
)
|
||||
|
||||
num_rows = num_plots // num_columns
|
||||
last_row_cols = num_plots % num_columns
|
||||
remaining_space = num_columns - last_row_cols
|
||||
|
||||
for i, plot_config in enumerate(self.plot_data):
|
||||
row, col = i // num_columns, i % num_columns
|
||||
colspan = 1
|
||||
|
||||
if row == num_rows and remaining_space > 0:
|
||||
if last_row_cols == 1:
|
||||
colspan = num_columns
|
||||
else:
|
||||
colspan = remaining_space // last_row_cols + 1
|
||||
remaining_space -= colspan - 1
|
||||
last_row_cols -= 1
|
||||
|
||||
plot_name = plot_config.get("plot_name", "")
|
||||
x_label = plot_config["x"].get("label", "")
|
||||
y_label = plot_config["y"].get("label", "")
|
||||
|
||||
plot = self.glw.addPlot(row=row, col=col, colspan=colspan, title=plot_name)
|
||||
plot.setLabel("bottom", x_label)
|
||||
plot.setLabel("left", y_label)
|
||||
plot.addLegend()
|
||||
|
||||
self.plots[plot_name] = plot
|
||||
self.grid_coordinates.append((row, col))
|
||||
|
||||
self.init_curves()
|
||||
|
||||
def init_curves(self) -> None:
|
||||
"""
|
||||
Initialize curve data and properties, and update table row labels.
|
||||
|
||||
This method initializes a nested dictionary `self.curves_data` to store
|
||||
the curve objects for each x and y signal pair. It also updates the row labels
|
||||
in `self.tableWidget_crosshair` to include the grid position for each y-value.
|
||||
"""
|
||||
self.curves_data = {}
|
||||
row_labels = []
|
||||
|
||||
for idx, plot_config in enumerate(self.plot_data):
|
||||
plot_name = plot_config.get("plot_name", "")
|
||||
plot = self.plots[plot_name]
|
||||
plot.clear()
|
||||
|
||||
y_configs = plot_config["y"]["signals"]
|
||||
colors_ys = Colors.golden_angle_color(
|
||||
colormap=self.plot_settings["colormap"], num=len(y_configs)
|
||||
)
|
||||
|
||||
curve_list = []
|
||||
for i, (y_config, color) in enumerate(zip(y_configs, colors_ys)):
|
||||
y_name = y_config["name"]
|
||||
y_entries = y_config.get("entry", [y_name])
|
||||
|
||||
if not isinstance(y_entries, list):
|
||||
y_entries = [y_entries]
|
||||
|
||||
for y_entry in y_entries:
|
||||
user_color = self.user_colors.get((plot_name, y_name, y_entry), None)
|
||||
color_to_use = user_color if user_color else color
|
||||
|
||||
pen_curve = mkPen(color=color_to_use, width=2, style=QtCore.Qt.DashLine)
|
||||
brush_curve = mkBrush(color=color_to_use)
|
||||
|
||||
curve_data = pg.PlotDataItem(
|
||||
symbolSize=5,
|
||||
symbolBrush=brush_curve,
|
||||
pen=pen_curve,
|
||||
skipFiniteCheck=True,
|
||||
name=f"{y_name} ({y_entry})",
|
||||
)
|
||||
|
||||
curve_list.append((y_name, y_entry, curve_data))
|
||||
plot.addItem(curve_data)
|
||||
row_labels.append(f"{y_name} ({y_entry}) - {plot_name}")
|
||||
|
||||
# Create a ColorButton and set its color
|
||||
color_btn = ColorButton()
|
||||
color_btn.setColor(color_to_use)
|
||||
color_btn.sigColorChanged.connect(
|
||||
lambda btn=color_btn, plot=plot_name, yname=y_name, yentry=y_entry, curve=curve_data: self.change_curve_color(
|
||||
btn, plot, yname, yentry, curve
|
||||
)
|
||||
)
|
||||
|
||||
# Add the ColorButton as a QWidget to the table
|
||||
color_widget = QtWidgets.QWidget()
|
||||
layout = QtWidgets.QHBoxLayout()
|
||||
layout.addWidget(color_btn)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
color_widget.setLayout(layout)
|
||||
|
||||
row = len(row_labels) - 1 # The row index in the table
|
||||
self.tableWidget_crosshair.setCellWidget(row, 2, color_widget)
|
||||
|
||||
self.curves_data[plot_name] = curve_list
|
||||
|
||||
self.tableWidget_crosshair.setRowCount(len(row_labels))
|
||||
self.tableWidget_crosshair.setVerticalHeaderLabels(row_labels)
|
||||
self.hook_crosshair()
|
||||
|
||||
# def change_curve_color(self, btn, curve):
|
||||
# """Change the color of a curve."""
|
||||
# color = btn.color()
|
||||
# pen_curve = mkPen(color=color, width=2, style=QtCore.Qt.DashLine)
|
||||
# brush_curve = mkBrush(color=color)
|
||||
# curve.setPen(pen_curve)
|
||||
# curve.setSymbolBrush(brush_curve)
|
||||
|
||||
def change_curve_color(self, btn, plot_name, y_name, y_entry, curve):
|
||||
"""
|
||||
Change the color of a curve and update the corresponding ColorButton.
|
||||
|
||||
Args:
|
||||
btn (ColorButton): The ColorButton that was clicked.
|
||||
plot_name (str): The name of the plot where the curve belongs.
|
||||
y_name (str): The name of the y signal.
|
||||
y_entry (str): The entry of the y signal.
|
||||
curve (PlotDataItem): The curve to be changed.
|
||||
"""
|
||||
color = btn.color()
|
||||
pen_curve = mkPen(color=color, width=2, style=QtCore.Qt.DashLine)
|
||||
brush_curve = mkBrush(color=color)
|
||||
curve.setPen(pen_curve)
|
||||
curve.setSymbolBrush(brush_curve)
|
||||
self.user_colors[(plot_name, y_name, y_entry)] = color
|
||||
|
||||
def hook_crosshair(self):
|
||||
"""Attach crosshairs to each plot and connect them to the update_table method."""
|
||||
self.crosshairs = {}
|
||||
for plot_name, plot in self.plots.items():
|
||||
crosshair = Crosshair(plot, precision=3)
|
||||
crosshair.coordinatesChanged1D.connect(
|
||||
lambda x, y, plot=plot: self.update_table(
|
||||
self.tableWidget_crosshair, x, y, column=0, plot=plot
|
||||
)
|
||||
)
|
||||
crosshair.coordinatesClicked1D.connect(
|
||||
lambda x, y, plot=plot: self.update_table(
|
||||
self.tableWidget_crosshair, x, y, column=1, plot=plot
|
||||
)
|
||||
)
|
||||
self.crosshairs[plot_name] = crosshair
|
||||
|
||||
def update_table(
|
||||
self, table_widget: QTableWidget, x: float, y_values: list, column: int, plot: pg.PlotItem
|
||||
) -> None:
|
||||
"""
|
||||
Update the table with coordinates based on cursor movements and clicks.
|
||||
|
||||
Args:
|
||||
table_widget (QTableWidget): The table to be updated.
|
||||
x (float): The x-coordinate from the plot.
|
||||
y_values (list): The y-coordinates from the plot.
|
||||
column (int): The column in the table to be updated.
|
||||
plot (PlotItem): The plot from which the coordinates are taken.
|
||||
|
||||
This method calculates the correct row in the table for each y-value
|
||||
and updates the cell at (row, column) with the new x and y coordinates.
|
||||
"""
|
||||
plot_name = [name for name, value in self.plots.items() if value == plot][0]
|
||||
|
||||
starting_row = 0
|
||||
for plot_config in self.plot_data:
|
||||
if plot_config.get("plot_name", "") == plot_name:
|
||||
break
|
||||
for y_config in plot_config.get("y", {}).get("signals", []):
|
||||
y_entries = y_config.get("entry", [y_config.get("name", "")])
|
||||
if not isinstance(y_entries, list):
|
||||
y_entries = [y_entries]
|
||||
starting_row += len(y_entries)
|
||||
|
||||
for i, y in enumerate(y_values):
|
||||
row = starting_row + i
|
||||
table_widget.setItem(row, column, QTableWidgetItem(f"({x}, {y})"))
|
||||
table_widget.resizeColumnsToContents()
|
||||
|
||||
def update_plot(self) -> None:
|
||||
"""Update the plot data based on the stored data dictionary."""
|
||||
for plot_name, curve_list in self.curves_data.items():
|
||||
for y_name, y_entry, curve in curve_list:
|
||||
x_config = next(
|
||||
(pc["x"] for pc in self.plot_data if pc.get("plot_name") == plot_name), {}
|
||||
)
|
||||
x_signal_config = x_config["signals"][0]
|
||||
x_name = x_signal_config.get("name", "")
|
||||
x_entry = x_signal_config.get("entry", x_name)
|
||||
|
||||
key = (x_name, x_entry, y_name, y_entry)
|
||||
data_x = self.data.get(key, {}).get("x", [])
|
||||
data_y = self.data.get(key, {}).get("y", [])
|
||||
|
||||
curve.setData(data_x, data_y)
|
||||
|
||||
@pyqtSlot(dict, dict)
|
||||
def on_scan_segment(self, msg, metadata) -> None:
|
||||
"""
|
||||
Handle new scan segments and saves data to a dictionary.
|
||||
|
||||
Args:
|
||||
msg (dict): Message received with scan data.
|
||||
metadata (dict): Metadata of the scan.
|
||||
"""
|
||||
|
||||
current_scanID = msg.get("scanID", None)
|
||||
if current_scanID is None:
|
||||
return
|
||||
|
||||
if current_scanID != self.scanID:
|
||||
self.scanID = current_scanID
|
||||
self.data = {}
|
||||
self.init_curves()
|
||||
|
||||
for plot_config in self.plot_data:
|
||||
plot_name = plot_config.get("plot_name", "Unnamed Plot")
|
||||
x_config = plot_config["x"]
|
||||
x_signal_config = x_config["signals"][0] # Assuming there's at least one signal for x
|
||||
|
||||
x_name = x_signal_config.get("name", "")
|
||||
if not x_name:
|
||||
raise ValueError(f"Name for x signal must be specified in plot: {plot_name}.")
|
||||
|
||||
x_entry_list = x_signal_config.get("entry", [])
|
||||
if not x_entry_list:
|
||||
x_entry_list = dev[x_name]._hints if hasattr(dev[x_name], "_hints") else [x_name]
|
||||
|
||||
if not isinstance(x_entry_list, list):
|
||||
x_entry_list = [x_entry_list]
|
||||
|
||||
y_configs = plot_config["y"]["signals"]
|
||||
|
||||
for x_entry in x_entry_list:
|
||||
for y_config in y_configs:
|
||||
y_name = y_config.get("name", "")
|
||||
if not y_name:
|
||||
raise ValueError(
|
||||
f"Name for y signal must be specified in plot: {plot_name}."
|
||||
)
|
||||
|
||||
y_entry_list = y_config.get("entry", [])
|
||||
if not y_entry_list:
|
||||
y_entry_list = (
|
||||
dev[y_name]._hints if hasattr(dev[y_name], "_hints") else [y_name]
|
||||
)
|
||||
|
||||
if not isinstance(y_entry_list, list):
|
||||
y_entry_list = [y_entry_list]
|
||||
|
||||
for y_entry in y_entry_list:
|
||||
key = (x_name, x_entry, y_name, y_entry)
|
||||
|
||||
data_x = msg["data"].get(x_name, {}).get(x_entry, {}).get("value", None)
|
||||
data_y = msg["data"].get(y_name, {}).get(y_entry, {}).get("value", None)
|
||||
|
||||
if data_x is None:
|
||||
raise ValueError(
|
||||
f"Incorrect entry '{x_entry}' specified for x in plot: {plot_name}, x name: {x_name}"
|
||||
)
|
||||
|
||||
if data_y is None:
|
||||
if hasattr(dev[y_name], "_hints"):
|
||||
raise ValueError(
|
||||
f"Incorrect entry '{y_entry}' specified for y in plot: {plot_name}, y name: {y_name}"
|
||||
)
|
||||
else:
|
||||
raise ValueError(
|
||||
f"No hints available for y in plot: {plot_name}, and name '{y_name}' did not work as entry"
|
||||
)
|
||||
|
||||
if data_x is not None:
|
||||
self.data.setdefault(key, {}).setdefault("x", []).append(data_x)
|
||||
|
||||
if data_y is not None:
|
||||
self.data.setdefault(key, {}).setdefault("y", []).append(data_y)
|
||||
|
||||
self.update_signal.emit()
|
||||
|
||||
def save_settings_to_yaml(self):
|
||||
"""Save the current settings to a .yaml file using a file dialog."""
|
||||
options = QFileDialog.Options()
|
||||
options |= QFileDialog.DontUseNativeDialog
|
||||
file_path, _ = QFileDialog.getSaveFileName(
|
||||
self, "Save Settings", "", "YAML Files (*.yaml);;All Files (*)", options=options
|
||||
)
|
||||
|
||||
if file_path:
|
||||
try:
|
||||
if not file_path.endswith(".yaml"):
|
||||
file_path += ".yaml"
|
||||
|
||||
with open(file_path, "w") as file:
|
||||
yaml.dump(
|
||||
{"plot_settings": self.plot_settings, "plot_data": self.plot_data}, file
|
||||
)
|
||||
print(f"Settings saved to {file_path}")
|
||||
except Exception as e:
|
||||
print(f"An error occurred while saving the settings to {file_path}: {e}")
|
||||
|
||||
def load_settings_from_yaml(self):
|
||||
"""Load settings from a .yaml file using a file dialog and update the current settings."""
|
||||
options = QFileDialog.Options()
|
||||
options |= QFileDialog.DontUseNativeDialog
|
||||
file_path, _ = QFileDialog.getOpenFileName(
|
||||
self, "Load Settings", "", "YAML Files (*.yaml);;All Files (*)", options=options
|
||||
)
|
||||
|
||||
if file_path:
|
||||
try:
|
||||
with open(file_path, "r") as file:
|
||||
config = yaml.safe_load(file)
|
||||
|
||||
self.plot_settings = config.get("plot_settings", {})
|
||||
self.plot_data = config.get("plot_data", {})
|
||||
# Reinitialize the UI and plots
|
||||
# TODO implement, change background works only before loading .ui file
|
||||
# self.init_plot_background(self.plot_settings["background_color"])
|
||||
self.init_ui(self.plot_settings["num_columns"])
|
||||
self.init_curves()
|
||||
print(f"Settings loaded from {file_path}")
|
||||
except FileNotFoundError:
|
||||
print(f"The file {file_path} was not found.")
|
||||
except Exception as e:
|
||||
print(f"An error occurred while loading the settings from {file_path}: {e}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import yaml
|
||||
import argparse
|
||||
|
||||
from bec_widgets import ctrl_c
|
||||
from bec_widgets.bec_dispatcher import bec_dispatcher
|
||||
|
||||
parser = argparse.ArgumentParser(description="Plotting App")
|
||||
parser.add_argument(
|
||||
"--config", "-c", help="Path to the .yaml configuration file", default="config_example.yaml"
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
try:
|
||||
with open(args.config, "r") as file:
|
||||
config = yaml.safe_load(file)
|
||||
|
||||
plot_settings = config.get("plot_settings", {})
|
||||
plot_data = config.get("plot_data", {})
|
||||
|
||||
except FileNotFoundError:
|
||||
print(f"The file {args.config} was not found.")
|
||||
exit(1)
|
||||
except Exception as e:
|
||||
print(f"An error occurred while loading the config file: {e}")
|
||||
exit(1)
|
||||
|
||||
# BECclient global variables
|
||||
client = bec_dispatcher.client
|
||||
client.start()
|
||||
|
||||
dev = client.device_manager.devices
|
||||
scans = client.scans
|
||||
queue = client.queue
|
||||
|
||||
app = QApplication([])
|
||||
plotApp = PlotApp(plot_settings=plot_settings, plot_data=plot_data)
|
||||
|
||||
# Connecting signals from bec_dispatcher
|
||||
bec_dispatcher.connect_slot(plotApp.on_scan_segment, MessageEndpoints.scan_segment())
|
||||
ctrl_c.setup(app)
|
||||
|
||||
window = plotApp
|
||||
window.show()
|
||||
app.exec_()
|
||||
@@ -1,115 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>MultiWindow</class>
|
||||
<widget class="QWidget" name="MultiWindow">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>1248</width>
|
||||
<height>564</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>MultiWindow</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QSplitter" name="splitter">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<widget class="GraphicsLayoutWidget" name="glw"/>
|
||||
<widget class="QWidget" name="">
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="1" column="0">
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="3">
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="title">
|
||||
<string>Cursor</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QTableWidget" name="tableWidget_crosshair">
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Moved</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Clicked</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Color</string>
|
||||
</property>
|
||||
</column>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Number of Columns:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<widget class="QSpinBox" name="spinBox_N_columns">
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>3</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QPushButton" name="pushButton_load">
|
||||
<property name="text">
|
||||
<string>Load Config</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2">
|
||||
<widget class="QPushButton" name="pushButton_save">
|
||||
<property name="text">
|
||||
<string>Save Config</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>GraphicsLayoutWidget</class>
|
||||
<extends>QGraphicsView</extends>
|
||||
<header>pyqtgraph.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
0
bec_widgets/examples/general_app/__init__.py
Normal file
92
bec_widgets/examples/general_app/general_app.py
Normal file
@@ -0,0 +1,92 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
from qtpy.QtCore import QSize
|
||||
from qtpy.QtGui import QActionGroup, QIcon
|
||||
from qtpy.QtWidgets import QApplication, QMainWindow, QStyle
|
||||
|
||||
import bec_widgets
|
||||
from bec_widgets.examples.general_app.web_links import BECWebLinksMixin
|
||||
from bec_widgets.utils.colors import apply_theme
|
||||
from bec_widgets.utils.ui_loader import UILoader
|
||||
|
||||
MODULE_PATH = os.path.dirname(bec_widgets.__file__)
|
||||
|
||||
|
||||
class BECGeneralApp(QMainWindow):
|
||||
def __init__(self, parent=None):
|
||||
super(BECGeneralApp, self).__init__(parent)
|
||||
ui_file_path = os.path.join(os.path.dirname(__file__), "general_app.ui")
|
||||
self.load_ui(ui_file_path)
|
||||
|
||||
self.resize(1280, 720)
|
||||
|
||||
self.ini_ui()
|
||||
|
||||
def ini_ui(self):
|
||||
self._setup_icons()
|
||||
self._hook_menubar_docs()
|
||||
self._hook_theme_bar()
|
||||
|
||||
def load_ui(self, ui_file):
|
||||
loader = UILoader(self)
|
||||
self.ui = loader.loader(ui_file)
|
||||
self.setCentralWidget(self.ui)
|
||||
|
||||
def _hook_menubar_docs(self):
|
||||
# BEC Docs
|
||||
self.ui.action_BEC_docs.triggered.connect(BECWebLinksMixin.open_bec_docs)
|
||||
# BEC Widgets Docs
|
||||
self.ui.action_BEC_widgets_docs.triggered.connect(BECWebLinksMixin.open_bec_widgets_docs)
|
||||
# Bug report
|
||||
self.ui.action_bug_report.triggered.connect(BECWebLinksMixin.open_bec_bug_report)
|
||||
|
||||
def change_theme(self, theme):
|
||||
apply_theme(theme)
|
||||
|
||||
def _setup_icons(self):
|
||||
help_icon = QApplication.style().standardIcon(QStyle.SP_MessageBoxQuestion)
|
||||
bug_icon = QApplication.style().standardIcon(QStyle.SP_MessageBoxInformation)
|
||||
computer_icon = QIcon.fromTheme("computer")
|
||||
widget_icon = QIcon(os.path.join(MODULE_PATH, "assets", "designer_icons", "dock_area.png"))
|
||||
|
||||
self.ui.action_BEC_docs.setIcon(help_icon)
|
||||
self.ui.action_BEC_widgets_docs.setIcon(help_icon)
|
||||
self.ui.action_bug_report.setIcon(bug_icon)
|
||||
|
||||
self.ui.central_tab.setTabIcon(0, widget_icon)
|
||||
self.ui.central_tab.setTabIcon(1, computer_icon)
|
||||
|
||||
def _hook_theme_bar(self):
|
||||
self.ui.action_light.setCheckable(True)
|
||||
self.ui.action_dark.setCheckable(True)
|
||||
|
||||
# Create an action group to make sure only one can be checked at a time
|
||||
theme_group = QActionGroup(self)
|
||||
theme_group.addAction(self.ui.action_light)
|
||||
theme_group.addAction(self.ui.action_dark)
|
||||
theme_group.setExclusive(True)
|
||||
|
||||
# Connect the actions to the theme change method
|
||||
|
||||
self.ui.action_light.triggered.connect(lambda: self.change_theme("light"))
|
||||
self.ui.action_dark.triggered.connect(lambda: self.change_theme("dark"))
|
||||
|
||||
self.ui.action_dark.trigger()
|
||||
|
||||
|
||||
def main(): # pragma: no cover
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
icon = QIcon()
|
||||
icon.addFile(
|
||||
os.path.join(MODULE_PATH, "assets", "app_icons", "BEC-Dark.png"), size=QSize(48, 48)
|
||||
)
|
||||
app.setWindowIcon(icon)
|
||||
main_window = BECGeneralApp()
|
||||
main_window.show()
|
||||
sys.exit(app.exec_())
|
||||
|
||||
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
main()
|
||||
262
bec_widgets/examples/general_app/general_app.ui
Normal file
@@ -0,0 +1,262 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>MainWindow</class>
|
||||
<widget class="QMainWindow" name="MainWindow">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>1718</width>
|
||||
<height>1139</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>MainWindow</string>
|
||||
</property>
|
||||
<property name="tabShape">
|
||||
<enum>QTabWidget::TabShape::Rounded</enum>
|
||||
</property>
|
||||
<widget class="QWidget" name="centralwidget">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<item>
|
||||
<widget class="QTabWidget" name="central_tab">
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="dock_area_tab">
|
||||
<attribute name="title">
|
||||
<string>Dock Area</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<property name="leftMargin">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="BECDockArea" name="dock_area"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="vscode_tab">
|
||||
<attribute name="icon">
|
||||
<iconset theme="QIcon::ThemeIcon::Computer"/>
|
||||
</attribute>
|
||||
<attribute name="title">
|
||||
<string>Visual Studio Code</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<property name="leftMargin">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="VSCodeEditor" name="vscode"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QMenuBar" name="menubar">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>1718</width>
|
||||
<height>31</height>
|
||||
</rect>
|
||||
</property>
|
||||
<widget class="QMenu" name="menuHelp">
|
||||
<property name="title">
|
||||
<string>Help</string>
|
||||
</property>
|
||||
<addaction name="action_BEC_docs"/>
|
||||
<addaction name="action_BEC_widgets_docs"/>
|
||||
<addaction name="action_bug_report"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menuTheme">
|
||||
<property name="title">
|
||||
<string>Theme</string>
|
||||
</property>
|
||||
<addaction name="action_light"/>
|
||||
<addaction name="action_dark"/>
|
||||
</widget>
|
||||
<addaction name="menuTheme"/>
|
||||
<addaction name="menuHelp"/>
|
||||
</widget>
|
||||
<widget class="QStatusBar" name="statusbar"/>
|
||||
<widget class="QDockWidget" name="dock_scan_control">
|
||||
<property name="windowTitle">
|
||||
<string>Scan Control</string>
|
||||
</property>
|
||||
<attribute name="dockWidgetArea">
|
||||
<number>2</number>
|
||||
</attribute>
|
||||
<widget class="QWidget" name="dockWidgetContents_2">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_4">
|
||||
<item>
|
||||
<widget class="ScanControl" name="scan_control"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
<widget class="QDockWidget" name="dock_status_2">
|
||||
<property name="windowTitle">
|
||||
<string>BEC Service Status</string>
|
||||
</property>
|
||||
<attribute name="dockWidgetArea">
|
||||
<number>2</number>
|
||||
</attribute>
|
||||
<widget class="QWidget" name="dockWidgetContents_3">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_5">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="BECStatusBox" name="bec_status_box_2"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
<widget class="QDockWidget" name="dock_queue">
|
||||
<property name="windowTitle">
|
||||
<string>Scan Queue</string>
|
||||
</property>
|
||||
<attribute name="dockWidgetArea">
|
||||
<number>2</number>
|
||||
</attribute>
|
||||
<widget class="QWidget" name="dockWidgetContents_4">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_6">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="BECQueue" name="bec_queue">
|
||||
<row/>
|
||||
<column/>
|
||||
<column/>
|
||||
<column/>
|
||||
<item row="0" column="0"/>
|
||||
<item row="0" column="1"/>
|
||||
<item row="0" column="2"/>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
<action name="action_BEC_docs">
|
||||
<property name="icon">
|
||||
<iconset theme="QIcon::ThemeIcon::DialogQuestion"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>BEC Docs</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_BEC_widgets_docs">
|
||||
<property name="icon">
|
||||
<iconset theme="QIcon::ThemeIcon::DialogQuestion"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>BEC Widgets Docs</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_bug_report">
|
||||
<property name="icon">
|
||||
<iconset theme="QIcon::ThemeIcon::DialogError"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Bug Report</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_light">
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Light</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_dark">
|
||||
<property name="checkable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Dark</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>WebsiteWidget</class>
|
||||
<extends>QWebEngineView</extends>
|
||||
<header>website_widget</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>BECQueue</class>
|
||||
<extends>QTableWidget</extends>
|
||||
<header>bec_queue</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>ScanControl</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>scan_control</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>VSCodeEditor</class>
|
||||
<extends>WebsiteWidget</extends>
|
||||
<header>vs_code_editor</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>BECStatusBox</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>bec_status_box</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>BECDockArea</class>
|
||||
<extends>QWidget</extends>
|
||||
<header>dock_area</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>QWebEngineView</class>
|
||||
<extends></extends>
|
||||
<header location="global">QtWebEngineWidgets/QWebEngineView</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||