Spaces:
Running
Running
File size: 116,595 Bytes
87c3140 e91ac58 87c3140 e91ac58 93fd830 87c3140 e91ac58 e729f97 87c3140 806953a e91ac58 524a99c b8abf64 524a99c b8abf64 0bf3a45 524a99c 0bf3a45 524a99c e729f97 87c3140 b8abf64 524a99c e91ac58 b8abf64 e91ac58 87c3140 e729f97 524a99c b8abf64 e91ac58 b8abf64 524a99c b8abf64 e91ac58 b8abf64 524a99c b8abf64 9d06861 b8abf64 e91ac58 b8abf64 e91ac58 b8abf64 e91ac58 b8abf64 e91ac58 0560c52 aedd7d9 b8abf64 aedd7d9 b8abf64 6d5de38 aedd7d9 6d5de38 0560c52 aedd7d9 e91ac58 b8abf64 e91ac58 b8abf64 e91ac58 524a99c e91ac58 e729f97 87c3140 8883473 87c3140 8883473 87c3140 8883473 87c3140 f2055e5 8883473 1f55e2a 87c3140 8883473 87c3140 8883473 87c3140 8883473 e91ac58 8883473 e91ac58 87c3140 e91ac58 aedd7d9 e91ac58 87c3140 e91ac58 7a93196 524a99c e91ac58 7a93196 e91ac58 87c3140 7a93196 87c3140 9f288f7 87c3140 e729f97 7a93196 87c3140 e91ac58 87c3140 7a93196 87c3140 7a93196 87c3140 7a93196 e91ac58 7a93196 87c3140 e91ac58 7a93196 e91ac58 b8abf64 e91ac58 b8abf64 e91ac58 b8abf64 e91ac58 87c3140 e91ac58 b8abf64 e91ac58 87c3140 e91ac58 b8abf64 e91ac58 b8abf64 e91ac58 b8abf64 87c3140 e91ac58 b8abf64 e91ac58 b8abf64 e91ac58 b8abf64 e91ac58 b8abf64 e91ac58 b8abf64 e91ac58 b8abf64 e91ac58 b8abf64 e91ac58 b8abf64 e91ac58 b8abf64 e91ac58 b8abf64 87c3140 e91ac58 b8abf64 e91ac58 b8abf64 e91ac58 b8abf64 e91ac58 b8abf64 e91ac58 b8abf64 e91ac58 b8abf64 e91ac58 b8abf64 e91ac58 b8abf64 87c3140 e729f97 524a99c 904c317 e91ac58 6d5de38 e91ac58 6d5de38 e91ac58 6d5de38 e91ac58 a1e2ec1 e91ac58 a1e2ec1 e91ac58 a1e2ec1 e91ac58 a1e2ec1 e91ac58 a1e2ec1 e91ac58 af032b9 e91ac58 6965e7c e91ac58 a1e2ec1 e91ac58 a1e2ec1 524a99c e91ac58 089a1e9 e91ac58 04463cb b8abf64 089a1e9 e91ac58 5661288 524a99c 5661288 e91ac58 5661288 e91ac58 5661288 e91ac58 5661288 e91ac58 5661288 aedd7d9 b9673e0 e91ac58 5661288 e91ac58 089a1e9 aedd7d9 b9673e0 a1e2ec1 e91ac58 524a99c e91ac58 3de0c83 e91ac58 b8abf64 e91ac58 b9673e0 e91ac58 524a99c e91ac58 a1e2ec1 e91ac58 a1e2ec1 e91ac58 b8abf64 e91ac58 524a99c e91ac58 524a99c e91ac58 524a99c 7a93196 524a99c e91ac58 9d06861 524a99c e91ac58 39c6a41 e91ac58 7a93196 e91ac58 7a93196 e91ac58 7a93196 e91ac58 7a93196 e91ac58 7a93196 e91ac58 7a93196 e91ac58 87c3140 dc252b5 e91ac58 e729f97 e91ac58 524a99c e91ac58 524a99c e91ac58 524a99c e91ac58 524a99c e91ac58 e729f97 e91ac58 524a99c e91ac58 eb18fda e91ac58 e729f97 e91ac58 87c3140 524a99c 26c9c07 e91ac58 93fd830 e91ac58 87c3140 e91ac58 87c3140 446471a e91ac58 e729f97 87c3140 e91ac58 0ee709f 524a99c e91ac58 87c3140 e91ac58 0ee709f e91ac58 e86548c 87c3140 e91ac58 87c3140 e91ac58 e729f97 446471a 6a78dda e91ac58 5efd4b8 e91ac58 c47fa5d c3e4ae3 e91ac58 e729f97 524a99c e91ac58 524a99c e91ac58 87c3140 e91ac58 87c3140 e91ac58 b8abf64 87c3140 e91ac58 524a99c e91ac58 4d14f52 e91ac58 4d14f52 e91ac58 b8abf64 524a99c b8abf64 524a99c b8abf64 524a99c b8abf64 f56fafe |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 1920 1921 1922 1923 1924 1925 1926 1927 1928 1929 1930 1931 1932 1933 1934 1935 1936 1937 1938 1939 1940 1941 1942 1943 1944 1945 1946 1947 1948 1949 1950 1951 1952 1953 1954 1955 1956 1957 1958 1959 1960 1961 1962 1963 1964 1965 1966 1967 1968 1969 1970 1971 1972 1973 1974 1975 1976 1977 1978 1979 1980 1981 1982 1983 1984 1985 1986 1987 1988 1989 1990 1991 1992 1993 1994 1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024 2025 2026 2027 2028 2029 2030 2031 2032 2033 2034 2035 2036 2037 2038 2039 2040 2041 2042 2043 2044 2045 2046 2047 2048 2049 2050 2051 2052 2053 2054 2055 2056 2057 2058 2059 2060 2061 2062 2063 2064 2065 2066 2067 2068 2069 2070 2071 2072 2073 2074 2075 2076 2077 2078 2079 2080 2081 2082 2083 2084 2085 2086 2087 2088 2089 2090 2091 2092 2093 2094 2095 2096 2097 2098 2099 2100 2101 2102 2103 2104 2105 2106 2107 2108 2109 2110 2111 2112 2113 2114 2115 2116 2117 2118 2119 2120 2121 2122 2123 2124 2125 2126 2127 2128 2129 2130 2131 2132 2133 2134 2135 2136 2137 2138 2139 2140 2141 2142 2143 2144 2145 2146 2147 2148 2149 2150 2151 2152 2153 2154 2155 2156 2157 2158 2159 2160 2161 2162 2163 2164 2165 2166 2167 2168 2169 2170 2171 2172 2173 2174 2175 2176 2177 2178 2179 2180 |
import streamlit as st
import yaml, os, json, random, time, re, torch, random, warnings, shutil, sys
import seaborn as sns
import plotly.graph_objs as go
from PIL import Image
import pandas as pd
from io import BytesIO
from streamlit_extras.let_it_rain import rain
from annotated_text import annotated_text
from vouchervision.LeafMachine2_Config_Builder import write_config_file
from vouchervision.VoucherVision_Config_Builder import build_VV_config, TestOptionsGPT, TestOptionsPalm, check_if_usable
from vouchervision.vouchervision_main import voucher_vision
from vouchervision.general_utils import test_GPU, get_cfg_from_full_path, summarize_expense_report, validate_dir
from vouchervision.model_maps import ModelMaps
from vouchervision.API_validation import APIvalidation
from vouchervision.utils_hf import setup_streamlit_config, save_uploaded_file, save_uploaded_local
from vouchervision.data_project import convert_pdf_to_jpg
from vouchervision.utils_LLM import check_system_gpus
#################################################################################################################################################
# Initializations ###############################################################################################################################
#################################################################################################################################################
st.set_page_config(layout="wide", page_icon='img/icon.ico', page_title='VoucherVision',initial_sidebar_state="collapsed")
# Parse the 'is_hf' argument and set it in session state
if 'is_hf' not in st.session_state:
is_hf_os = os.getenv('IS_HF', '').lower() # Get the environment variable and convert to lowercase for uniformity
if is_hf_os in ['1', 'true']: # Check against string representations of truthy values
st.session_state['is_hf'] = True
else:
st.session_state['is_hf'] = False
print(f"is_hf {st.session_state['is_hf']}")
# Default YAML file path
if 'config' not in st.session_state:
st.session_state.config, st.session_state.dir_home = build_VV_config(loaded_cfg=None)
setup_streamlit_config(st.session_state.dir_home)
########################################################################################################
### Global constants ####
########################################################################################################
MAX_GALLERY_IMAGES = 20
GALLERY_IMAGE_SIZE = 96
########################################################################################################
### Init funcs ####
########################################################################################################
def does_private_file_exist():
dir_home = os.path.dirname(__file__)
path_cfg_private = os.path.join(dir_home, 'PRIVATE_DATA.yaml')
return os.path.exists(path_cfg_private)
########################################################################################################
### Streamlit inits [FOR SAVE FILE] ####
########################################################################################################
########################################################################################################
### Streamlit inits [routing] ####
########################################################################################################
if st.session_state['is_hf']:
if 'proceed_to_main' not in st.session_state:
st.session_state.proceed_to_main = True
if 'proceed_to_private' not in st.session_state:
st.session_state.proceed_to_private = False
if 'private_file' not in st.session_state:
st.session_state.private_file = True
else:
if 'proceed_to_main' not in st.session_state:
st.session_state.proceed_to_main = False # New state variable to control the flow
if 'private_file' not in st.session_state:
st.session_state.private_file = does_private_file_exist()
if st.session_state.private_file:
st.session_state.proceed_to_main = True
if 'proceed_to_private' not in st.session_state:
st.session_state.proceed_to_private = False # New state variable to control the flow
if 'proceed_to_build_llm_prompt' not in st.session_state:
st.session_state.proceed_to_build_llm_prompt = False # New state variable to control the flow
if 'proceed_to_build_llm_prompt' not in st.session_state:
st.session_state.proceed_to_build_llm_prompt = False
if 'proceed_to_component_detector' not in st.session_state:
st.session_state.proceed_to_component_detector = False
if 'proceed_to_parsing_options' not in st.session_state:
st.session_state.proceed_to_parsing_options = False
if 'proceed_to_api_keys' not in st.session_state:
st.session_state.proceed_to_api_keys = False
if 'proceed_to_space_saver' not in st.session_state:
st.session_state.proceed_to_space_saver = False
if 'proceed_to_faqs' not in st.session_state:
st.session_state.proceed_to_faqs = False
########################################################################################################
### Streamlit inits [basics] ####
########################################################################################################
if 'processing_add_on' not in st.session_state:
st.session_state['processing_add_on'] = 0
if 'capability_score' not in st.session_state:
st.session_state['num_gpus'], st.session_state['gpu_dict'], st.session_state['total_vram_gb'], st.session_state['capability_score'] = check_system_gpus()
if 'formatted_json' not in st.session_state:
st.session_state['formatted_json'] = None
if 'formatted_json_WFO' not in st.session_state:
st.session_state['formatted_json_WFO'] = None
if 'formatted_json_GEO' not in st.session_state:
st.session_state['formatted_json_GEO'] = None
if 'lacks_GPU' not in st.session_state:
st.session_state['lacks_GPU'] = not torch.cuda.is_available()
if 'API_key_validation' not in st.session_state:
st.session_state['API_key_validation'] = False
if 'API_checked' not in st.session_state:
st.session_state['API_checked'] = False
if 'API_rechecked' not in st.session_state:
st.session_state['API_rechecked'] = False
if 'present_annotations' not in st.session_state:
st.session_state['present_annotations'] = None
if 'missing_annotations' not in st.session_state:
st.session_state['missing_annotations'] = None
if 'date_of_check' not in st.session_state:
st.session_state['date_of_check'] = None
if 'json_report' not in st.session_state:
st.session_state['json_report'] = False
if 'hold_output' not in st.session_state:
st.session_state['hold_output'] = False
if 'cost_openai' not in st.session_state:
st.session_state['cost_openai'] = None
if 'cost_azure' not in st.session_state:
st.session_state['cost_azure'] = None
if 'cost_google' not in st.session_state:
st.session_state['cost_google'] = None
if 'cost_mistral' not in st.session_state:
st.session_state['cost_mistral'] = None
if 'cost_local' not in st.session_state:
st.session_state['cost_local'] = None
if 'settings_filename' not in st.session_state:
st.session_state['settings_filename'] = None
if 'loaded_settings_filename' not in st.session_state:
st.session_state['loaded_settings_filename'] = None
if 'zip_filepath' not in st.session_state:
st.session_state['zip_filepath'] = None
########################################################################################################
### Streamlit inits [prompt builder] ####
########################################################################################################
# These are the fields that are in SLTPvA that are not required by another parsing valication function:
# "identifiedBy": "M.W. Lyon, Jr.",
# "recordedBy": "University of Michigan Herbarium",
# "recordNumber": "",
# "habitat": "wet subdunal woods",
# "occurrenceRemarks": "Indiana : Porter Co.",
# "degreeOfEstablishment": "",
# "minimumElevationInMeters": "",
# "maximumElevationInMeters": ""
if 'required_fields' not in st.session_state:
st.session_state['required_fields'] = ['catalogNumber','order','family','scientificName',
'scientificNameAuthorship','genus','subgenus','specificEpithet','infraspecificEpithet',
'verbatimEventDate','eventDate',
'country','stateProvince','county','municipality','locality','decimalLatitude','decimalLongitude','verbatimCoordinates',]
if 'prompt_info' not in st.session_state:
st.session_state['prompt_info'] = {}
if 'rules' not in st.session_state:
st.session_state['rules'] = {}
########################################################################################################
### Streamlit inits [gallery] ####
########################################################################################################
if 'uploader_idk' not in st.session_state:
st.session_state['uploader_idk'] = 1
if 'input_list_small' not in st.session_state:
st.session_state['input_list_small'] = []
if 'input_list' not in st.session_state:
st.session_state['input_list'] = []
if 'user_clicked_load_prompt_yaml' not in st.session_state:
st.session_state['user_clicked_load_prompt_yaml'] = None
if 'new_prompt_yaml_filename' not in st.session_state:
st.session_state['new_prompt_yaml_filename'] = None
if 'view_local_gallery' not in st.session_state:
st.session_state['view_local_gallery'] = False
if 'dir_images_local_TEMP' not in st.session_state:
st.session_state['dir_images_local_TEMP'] = False
if 'dir_uploaded_images' not in st.session_state:
st.session_state['dir_uploaded_images'] = os.path.join(st.session_state.dir_home,'uploads')
validate_dir(os.path.join(st.session_state.dir_home,'uploads'))
if 'dir_uploaded_images_small' not in st.session_state:
st.session_state['dir_uploaded_images_small'] = os.path.join(st.session_state.dir_home,'uploads_small')
validate_dir(os.path.join(st.session_state.dir_home,'uploads_small'))
########################################################################################################
### CONTENT [] ####
########################################################################################################
def content_input_images(col_left, col_right):
st.write('---')
# col1, col2 = st.columns([2,8])
with col_left:
st.header('Input Images')
if not st.session_state.is_hf:
### Input Images Local
st.session_state.config['leafmachine']['project']['dir_images_local'] = st.text_input("Input images directory", st.session_state.config['leafmachine']['project'].get('dir_images_local', ''))
st.session_state.config['leafmachine']['project']['continue_run_from_partial_xlsx'] = st.text_input("Continue run from partially completed project XLSX", st.session_state.config['leafmachine']['project'].get('continue_run_from_partial_xlsx', ''), disabled=True)
else:
pass
with col_left:
if st.session_state.is_hf:
st.session_state['dir_uploaded_images'] = os.path.join(st.session_state.dir_home,'uploads')
st.session_state['dir_uploaded_images_small'] = os.path.join(st.session_state.dir_home,'uploads_small')
uploaded_files = st.file_uploader("Upload Images", type=['jpg', 'jpeg','pdf'], accept_multiple_files=True, key=st.session_state['uploader_idk'])
st.button("Use Test Image",help="This will clear any uploaded images and load the 1 provided test image.",on_click=use_test_image)
with col_right:
if st.session_state.is_hf:
if uploaded_files:
# Clear input image gallery and input list
clear_image_gallery()
for uploaded_file in uploaded_files:
# Determine the file type
if uploaded_file.name.lower().endswith('.pdf'):
# Handle PDF files
file_path = save_uploaded_file(st.session_state['dir_uploaded_images'], uploaded_file)
# Convert each page of the PDF to an image
n_pages = convert_pdf_to_jpg(file_path, st.session_state['dir_uploaded_images'], dpi=st.session_state.config['leafmachine']['project']['dir_images_local'])
# Update the input list for each page image
converted_files = os.listdir(st.session_state['dir_uploaded_images'])
for file_name in converted_files:
if file_name.lower().endswith('.jpg'):
jpg_file_path = os.path.join(st.session_state['dir_uploaded_images'], file_name)
st.session_state['input_list'].append(jpg_file_path)
# Optionally, create a thumbnail for the gallery
img = Image.open(jpg_file_path)
img.thumbnail((GALLERY_IMAGE_SIZE, GALLERY_IMAGE_SIZE), Image.Resampling.LANCZOS)
file_path_small = save_uploaded_file(st.session_state['dir_uploaded_images_small'], uploaded_file, img)
st.session_state['input_list_small'].append(file_path_small)
else:
# Handle JPG/JPEG files (existing process)
file_path = save_uploaded_file(st.session_state['dir_uploaded_images'], uploaded_file)
st.session_state['input_list'].append(file_path)
img = Image.open(file_path)
img.thumbnail((GALLERY_IMAGE_SIZE, GALLERY_IMAGE_SIZE), Image.Resampling.LANCZOS)
file_path_small = save_uploaded_file(st.session_state['dir_uploaded_images_small'], uploaded_file, img)
st.session_state['input_list_small'].append(file_path_small)
# After processing all files
st.info(f"Processing images from {st.session_state.config['leafmachine']['project']['dir_images_local']}")
if st.session_state['input_list_small']:
if len(st.session_state['input_list_small']) > MAX_GALLERY_IMAGES:
# Only take the first 100 images from the list
images_to_display = st.session_state['input_list_small'][:MAX_GALLERY_IMAGES]
else:
# If there are less than 100 images, take them all
images_to_display = st.session_state['input_list_small']
st.image(images_to_display)
else:
st.session_state['view_local_gallery'] = st.toggle("View Image Gallery",)
if st.session_state['view_local_gallery'] and st.session_state['input_list_small'] and (st.session_state['dir_images_local_TEMP'] == st.session_state.config['leafmachine']['project']['dir_images_local']):
if MAX_GALLERY_IMAGES <= st.session_state['processing_add_on']:
info_txt = f"Showing {MAX_GALLERY_IMAGES} out of {st.session_state['processing_add_on']} images"
else:
info_txt = f"Showing {st.session_state['processing_add_on']} out of {st.session_state['processing_add_on']} images"
st.info(info_txt)
try:
st.image(st.session_state['input_list_small'], width=GALLERY_IMAGE_SIZE)
except:
pass
elif not st.session_state['view_local_gallery'] and st.session_state['input_list_small'] and (st.session_state['dir_images_local_TEMP'] == st.session_state.config['leafmachine']['project']['dir_images_local']):
pass
elif not st.session_state['view_local_gallery'] and not st.session_state['input_list_small'] and (st.session_state['dir_images_local_TEMP'] == st.session_state.config['leafmachine']['project']['dir_images_local']):
pass
# elif st.session_state['input_list_small'] and (st.session_state['dir_images_local_TEMP'] != st.session_state.config['leafmachine']['project']['dir_images_local']):
elif (st.session_state['dir_images_local_TEMP'] != st.session_state.config['leafmachine']['project']['dir_images_local']):
dir_images_local = st.session_state.config['leafmachine']['project']['dir_images_local']
count_n_imgs = list_jpg_files(dir_images_local)
st.session_state['processing_add_on'] = count_n_imgs
# print(st.session_state['processing_add_on'])
st.session_state['dir_images_local_TEMP'] = st.session_state.config['leafmachine']['project']['dir_images_local']
print("rerun")
st.rerun()
def list_jpg_files(directory_path):
jpg_count = 0
clear_image_gallery()
st.session_state['input_list_small'] = []
if not os.path.isdir(directory_path):
return None
jpg_count = count_jpg_images(directory_path)
jpg_files = []
for root, dirs, files in os.walk(directory_path):
for file in files:
if file.lower().endswith('.jpg'):
jpg_files.append(os.path.join(root, file))
if len(jpg_files) == MAX_GALLERY_IMAGES:
break
if len(jpg_files) == MAX_GALLERY_IMAGES:
break
for simg in jpg_files:
simg2 = Image.open(simg)
simg2.thumbnail((GALLERY_IMAGE_SIZE, GALLERY_IMAGE_SIZE), Image.Resampling.LANCZOS)
file_path_small = save_uploaded_local(st.session_state['dir_uploaded_images_small'], simg, simg2)
st.session_state['input_list_small'].append(file_path_small)
return jpg_count
def count_jpg_images(directory_path):
if not os.path.isdir(directory_path):
return None
jpg_count = 0
for root, dirs, files in os.walk(directory_path):
for file in files:
if file.lower().endswith('.jpg'):
jpg_count += 1
return jpg_count
def create_download_button(zip_filepath, col, key):
with col:
labal_n_images = f"Download Results for {st.session_state['processing_add_on']} Images"
with open(zip_filepath, 'rb') as f:
bytes_io = BytesIO(f.read())
st.download_button(
label=labal_n_images,
type='primary',
data=bytes_io,
file_name=os.path.basename(zip_filepath),
mime='application/zip',
use_container_width=True,key=key,
)
def delete_directory(dir_path):
try:
shutil.rmtree(dir_path)
st.session_state['input_list'] = []
st.session_state['input_list_small'] = []
# st.success(f"Deleted previously uploaded images, making room for new images: {dir_path}")
except OSError as e:
st.error(f"Error: {dir_path} : {e.strerror}")
def clear_image_gallery():
delete_directory(st.session_state['dir_uploaded_images'])
delete_directory(st.session_state['dir_uploaded_images_small'])
validate_dir(st.session_state['dir_uploaded_images'])
validate_dir(st.session_state['dir_uploaded_images_small'])
def use_test_image():
st.info(f"Processing images from {os.path.join(st.session_state.dir_home,'demo','demo_images')}")
st.session_state.config['leafmachine']['project']['dir_images_local'] = os.path.join(st.session_state.dir_home,'demo','demo_images')
n_images = len([f for f in os.listdir(st.session_state.config['leafmachine']['project']['dir_images_local']) if os.path.isfile(os.path.join(st.session_state.config['leafmachine']['project']['dir_images_local'], f))])
st.session_state['processing_add_on'] = n_images
clear_image_gallery()
st.session_state['uploader_idk'] += 1
for file in os.listdir(st.session_state.config['leafmachine']['project']['dir_images_local']):
file_path = save_uploaded_file(os.path.join(st.session_state.dir_home,'demo','demo_images'), file)
st.session_state['input_list'].append(file_path)
img = Image.open(file_path)
img.thumbnail((GALLERY_IMAGE_SIZE, GALLERY_IMAGE_SIZE), Image.Resampling.LANCZOS)
file_path_small = save_uploaded_file(st.session_state['dir_uploaded_images_small'], file, img)
st.session_state['input_list_small'].append(file_path_small)
def refresh():
st.session_state['uploader_idk'] += 1
st.write('')
# def display_image_gallery():
# # Initialize the container
# con_image = st.empty()
# # Start the div for the image grid
# img_grid_html = """
# <div style='display: flex; flex-wrap: wrap; align-items: flex-start; overflow-y: auto; max-height: 400px; gap: 10px;'>
# """
# # Loop through each image in the input list
# # with con_image.container():
# for image_path in st.session_state['input_list']:
# # Open the image and create a thumbnail
# img = Image.open(image_path)
# img.thumbnail((120, 120), Image.Resampling.LANCZOS)
# # Convert the image to base64
# base64_image = image_to_base64(img)
# # Append the image to the grid HTML
# # img_html = f"""
# # <div style='display: flex; flex-wrap: wrap; overflow-y: auto; max-height: 400px;'>
# # <img src='data:image/jpeg;base64,{base64_image}' alt='Image' style='max-width: 100%; height: auto;'>
# # </div>
# # """
# img_html = f"""
# <img src='data:image/jpeg;base64,{base64_image}' alt='Image' style='max-width: 100%; height: auto;'>
# """
# img_grid_html += img_html
# # st.markdown(img_html, unsafe_allow_html=True)
# # Close the div for the image grid
# img_grid_html += "</div>"
# # Display the image grid in the container
# with con_image.container():
# st.markdown(img_grid_html, unsafe_allow_html=True)
# # The CSS to make the images display inline and be responsive
# css = """
# <style>
# .scrollable-image-container img {
# max-width: 100%;
# height: auto;
# }
# </style>
# """
# # Apply the CSS
# st.markdown(css, unsafe_allow_html=True)
########################################################################################################
########################################################################################################
########################################################################################################
class ProgressReport:
def __init__(self, overall_bar, batch_bar, text_overall, text_batch):
self.overall_bar = overall_bar
self.batch_bar = batch_bar
self.text_overall = text_overall
self.text_batch = text_batch
self.current_overall_step = 0
self.total_overall_steps = 20 # number of major steps in machine function
self.current_batch = 0
self.total_batches = 20
def update_overall(self, step_name=""):
self.current_overall_step += 1
self.overall_bar.progress(self.current_overall_step / self.total_overall_steps)
self.text_overall.text(step_name)
def update_batch(self, step_name=""):
self.current_batch += 1
self.batch_bar.progress(self.current_batch / self.total_batches)
self.text_batch.text(step_name)
def set_n_batches(self, n_batches):
self.total_batches = n_batches
def set_n_overall(self, total_overall_steps):
self.current_overall_step = 0
self.overall_bar.progress(0)
self.total_overall_steps = total_overall_steps
def reset_batch(self, step_name):
self.current_batch = 0
self.batch_bar.progress(0)
self.text_batch.text(step_name)
def reset_overall(self, step_name):
self.current_overall_step = 0
self.overall_bar.progress(0)
self.text_overall.text(step_name)
def get_n_images(self):
return self.n_images
def get_n_overall(self):
return self.total_overall_steps
class JSONReport:
def __init__(self, col_updates, col_json, col_json_WFO, col_json_GEO, col_json_map):
self.plant_list = [':evergreen_tree:', ':deciduous_tree:',':palm_tree:',
':maple_leaf:',':fallen_leaf:',':mushroom:',':leaves:',
':cactus:',':seedling:',':tulip:',':sunflower:',':hibiscus:',
':cherry_blossom:',':rose:',]
self.location_list = [':earth_africa:',':earth_americas:',':earth_asia:',]
self.book_list = [':bookmark_tabs:',':ledger:',':notebook:',':clipboard:',':scroll:',
':notebook_with_decorative_cover:',':green_book:',':blue_book:',
':open_book:',':closed_book:',':book:',
':orange_book:',':books:',':memo:',':pencil:',
]
# Create placeholders for each JSON component
self.col_updates = col_updates
self.col_json = col_json
self.col_json_WFO = col_json_WFO
self.col_json_GEO = col_json_GEO
self.col_json_map = col_json_map
self.update_main = col_updates.empty()
self.update_left = col_json.empty()
self.header_json = col_json.empty()
self.json_placeholder = col_json.empty()
self.update_middle = col_json_WFO.empty()
self.header_json_WFO = col_json_WFO.empty()
self.json_WFO_placeholder = col_json_WFO.empty()
self.update_right = col_json_GEO.empty()
self.header_json_GEO = col_json_GEO.empty()
self.json_GEO_placeholder = col_json_GEO.empty()
self.update_map = col_json_map.empty()
self.header_json_map = col_json_map.empty()
self.json_map = col_json_map.empty()
self.json = None
self.json_WFO = None
self.json_GEO = None
self.text_main = ''
self.text_middle = ''
self.text_right = ''
self.header_text_main = None
self.header_text_middle = None
self.header_text_right = None
def set_JSON(self, json_main, json_WFO, json_GEO):
i_plant = random.randint(0,len(self.plant_list)-1)
i_location = random.randint(0,len(self.location_list)-1)
i_book = random.randint(0,len(self.book_list)-1)
self.json = json_main
self.json_WFO = json_WFO
self.json_GEO = json_GEO
# Update placeholders with new JSON data
self.header_text_main = None
self.header_text_middle = None
self.header_text_right = None
self.update_main.subheader(f':loudspeaker: {self.text_main}')
self.update_left.subheader(f'{self.book_list[i_book]}', divider='rainbow')
self.update_middle.subheader(f'{self.plant_list[i_plant]}', divider='rainbow')
self.update_right.subheader(f'{self.location_list[i_location]}', divider='rainbow')
self.update_map.subheader(f':world_map:', divider='rainbow')
self.header_json.markdown('**LLM-derived information from the OCR text**')
self.header_json_WFO.markdown('World Flora Online')
self.header_json_GEO.markdown('Geolocate')
self.header_json_map.markdown(f':large_purple_circle: :violet[Geolocated] :large_green_circle: :green[From OCR Text]')
self.json_placeholder.json(self.json)
self.json_WFO_placeholder.json(self.json_WFO)
self.json_GEO_placeholder.json(self.json_GEO)
# If GEO data is available, plot on the map
# Clear the existing content in the map placeholder
# Clear the existing content in the map placeholder
self.json_map.empty()
map_points = []
map_data = []
# Function to safely convert to float
def safe_float_convert(value):
try:
return float(value)
except (ValueError, TypeError):
return None
# Check and process first point's data
lat = safe_float_convert(self.json_GEO.get("GEO_decimal_lat")) if self.json_GEO else None
lon = safe_float_convert(self.json_GEO.get("GEO_decimal_long")) if self.json_GEO else None
if lat is not None and lon is not None:
map_points.append({'lat': lat, 'lon': lon, 'color': '#8800ff' , 'size': [50000]})
# Check and process second point's data
lat_verbatim = safe_float_convert(self.json.get("decimalLatitude")) if self.json else None
lon_verbatim = safe_float_convert(self.json.get("decimalLongitude")) if self.json else None
if lat_verbatim is not None and lon_verbatim is not None:
map_points.append({'lat': lat_verbatim, 'lon': lon_verbatim, 'color': '#00c227' , 'size': [25000]})
# Convert the list of points to a DataFrame
map_data = pd.DataFrame(map_points)
# Display the map if map_data is not empty
if not map_data.empty:
with self.json_map:
st.map(map_data, zoom=4, size='size', color='color', use_container_width=True)
def set_text(self, text_main=None, text_middle=None, text_right=None):
if text_main:
self.text_main = text_main
self.update_main.subheader(f':loudspeaker: {self.text_main}')
if text_middle:
self.text_middle = text_middle
self.update_middle.subheader('', divider='rainbow')
if text_right:
self.text_right = text_right
self.update_right.subheader(self.text_right, divider='rainbow')
def clear_JSON(self):
self.json = None
self.json_WFO = None
self.json_GEO = None
# Clear the content in the placeholders
self.json_placeholder.empty()
self.json_WFO_placeholder.empty()
self.json_GEO_placeholder.empty()
def format_json(self, json_obj):
try:
return json.dumps(json.loads(json_obj), indent=4, sort_keys=False)
except:
return json.dumps(json_obj, indent=4, sort_keys=False)
def setup_streamlit_config(dir_home):
# Define the directory path and filename
dir_path = os.path.join(dir_home, ".streamlit")
file_path = os.path.join(dir_path, "config.toml")
# Check if directory exists, if not create it
if not os.path.exists(dir_path):
os.makedirs(dir_path)
# Create or modify the file with the provided content
config_content = f"""
[theme]
base = "dark"
primaryColor = "#00ff00"
[server]
enableStaticServing = false
runOnSave = true
port = 8524
"""
with open(file_path, "w") as f:
f.write(config_content.strip())
def display_scrollable_results(JSON_results, test_results, OPT2, OPT3):
"""
Display the results from JSON_results in a scrollable container.
"""
# Initialize the container
con_results = st.empty()
with con_results.container():
# Start the custom container for all the results
results_html = """<div class='scrollable-results-container'>"""
for idx, (test_name, _) in enumerate(sorted(test_results.items())):
_, ind_opt1, ind_opt2, ind_opt3 = test_name.split('__')
opt2_readable = "Use LeafMachine2" if OPT2[int(ind_opt2.split('-')[1])] else "Don't use LeafMachine2"
opt3_readable = f"{OPT3[int(ind_opt3.split('-')[1])]}"
if JSON_results[idx] is None:
results_html += f"<p>None</p>"
else:
formatted_json = json.dumps(JSON_results[idx], indent=4, sort_keys=False)
results_html += f"<pre>[{opt2_readable}] + [{opt3_readable}]<br/>{formatted_json}</pre>"
# End the custom container
results_html += """</div>"""
# The CSS to make this container scrollable
css = """
<style>
.scrollable-results-container {
overflow-y: auto;
height: 600px;
width: 100%;
white-space: pre-wrap; # To wrap the content
font-family: monospace; # To give the JSON a code-like appearance
}
</style>
"""
# Apply the CSS and then the results
st.markdown(css, unsafe_allow_html=True)
st.markdown(results_html, unsafe_allow_html=True)
def display_test_results(test_results, JSON_results, llm_version):
if llm_version == 'gpt':
OPT1, OPT2, OPT3 = TestOptionsGPT.get_options()
elif llm_version == 'palm':
OPT1, OPT2, OPT3 = TestOptionsPalm.get_options()
else:
raise
widths = [1] * (len(OPT1) + 2) + [2]
columns = st.columns(widths)
with columns[0]:
st.write("LeafMachine2")
with columns[1]:
st.write("Prompt")
with columns[len(OPT1) + 2]:
st.write("Scroll to See Last Transcription in Each Test")
already_written = set()
for test_name, result in sorted(test_results.items()):
_, ind_opt1, _, _ = test_name.split('__')
option_value = OPT1[int(ind_opt1.split('-')[1])]
if option_value not in already_written:
with columns[int(ind_opt1.split('-')[1]) + 2]:
st.write(option_value)
already_written.add(option_value)
printed_options = set()
with columns[-1]:
display_scrollable_results(JSON_results, test_results, OPT2, OPT3)
# Close the custom container
st.write('</div>', unsafe_allow_html=True)
for idx, (test_name, result) in enumerate(sorted(test_results.items())):
_, ind_opt1, ind_opt2, ind_opt3 = test_name.split('__')
opt2_readable = "Use LeafMachine2" if OPT2[int(ind_opt2.split('-')[1])] else "Don't use LeafMachine2"
opt3_readable = f"{OPT3[int(ind_opt3.split('-')[1])]}"
if (opt2_readable, opt3_readable) not in printed_options:
with columns[0]:
st.info(f"{opt2_readable}")
st.write('---')
with columns[1]:
st.info(f"{opt3_readable}")
st.write('---')
printed_options.add((opt2_readable, opt3_readable))
with columns[int(ind_opt1.split('-')[1]) + 2]:
if result:
st.success(f"Test Passed")
else:
st.error(f"Test Failed")
st.write('---')
# success_count = sum(1 for result in test_results.values() if result)
# failure_count = len(test_results) - success_count
# proportional_rain("π₯", success_count, "π", failure_count, font_size=72, falling_speed=5, animation_length="infinite")
rain_emojis(test_results)
def add_emoji_delay():
time.sleep(0.3)
def rain_emojis(test_results):
# test_results = {
# 'test1': True, # Test passed
# 'test2': True, # Test passed
# 'test3': True, # Test passed
# 'test4': False, # Test failed
# 'test5': False, # Test failed
# 'test6': False, # Test failed
# 'test7': False, # Test failed
# 'test8': False, # Test failed
# 'test9': False, # Test failed
# 'test10': False, # Test failed
# }
success_emojis = ["π₯", "π", "πΎ", "π"]
failure_emojis = ["π", "π"]
success_count = sum(1 for result in test_results.values() if result)
failure_count = len(test_results) - success_count
chosen_emoji = random.choice(success_emojis)
for _ in range(success_count):
rain(
emoji=chosen_emoji,
font_size=72,
falling_speed=4,
animation_length=2,
)
add_emoji_delay()
chosen_emoji = random.choice(failure_emojis)
for _ in range(failure_count):
rain(
emoji=chosen_emoji,
font_size=72,
falling_speed=5,
animation_length=1,
)
add_emoji_delay()
def format_json(json_obj):
try:
return json.dumps(json.loads(json_obj), indent=4, sort_keys=False)
except:
return json.dumps(json_obj, indent=4, sort_keys=False)
def get_prompt_versions(LLM_version):
yaml_files = [f for f in os.listdir(os.path.join(st.session_state.dir_home, 'custom_prompts')) if f.endswith('.yaml')]
return yaml_files
def get_private_file():
dir_home = os.path.dirname(__file__)
path_cfg_private = os.path.join(dir_home, 'PRIVATE_DATA.yaml')
return get_cfg_from_full_path(path_cfg_private)
def blog_text_and_image(text=None, fullpath=None, width=700):
if text:
st.markdown(f"{text}")
if fullpath:
st.session_state.logo = Image.open(fullpath)
st.image(st.session_state.logo, width=width)
def blog_text(text_bold, text):
st.markdown(f"- **{text_bold}**{text}")
def blog_text_plain(text_bold, text):
st.markdown(f"**{text_bold}** {text}")
def create_private_file():
section_left = 2
section_mid = 6
section_right = 2
st.session_state.proceed_to_main = False
st.title("VoucherVision")
_, col_private,__= st.columns([section_left,section_mid, section_right])
if st.session_state.private_file:
cfg_private = get_private_file()
else:
cfg_private = {}
cfg_private['openai'] = {}
cfg_private['openai']['OPENAI_API_KEY'] =''
cfg_private['openai_azure'] = {}
cfg_private['openai_azure']['OPENAI_API_KEY_AZURE'] = ''
cfg_private['openai_azure']['OPENAI_API_VERSION'] = ''
cfg_private['openai_azure']['OPENAI_API_BASE'] =''
cfg_private['openai_azure']['OPENAI_ORGANIZATION'] =''
cfg_private['openai_azure']['OPENAI_API_TYPE'] =''
cfg_private['google'] = {}
cfg_private['google']['GOOGLE_APPLICATION_CREDENTIALS'] =''
cfg_private['google']['GOOGLE_PALM_API'] =''
cfg_private['google']['GOOGLE_PROJECT_ID'] =''
cfg_private['google']['GOOGLE_LOCATION'] =''
cfg_private['mistral'] = {}
cfg_private['mistral']['MISTRAL_API_KEY'] =''
cfg_private['here'] = {}
cfg_private['here']['APP_ID'] =''
cfg_private['here']['API_KEY'] =''
cfg_private['open_cage_geocode'] = {}
cfg_private['open_cage_geocode']['API_KEY'] =''
with col_private:
st.header("Set API keys")
st.warning("To commit changes to API keys you must press the 'Set API Keys' button at the bottom of the page.")
st.write("Before using VoucherVision you must set your API keys. All keys are stored locally on your computer and are never made public.")
st.write("API keys are stored in `../VoucherVision/PRIVATE_DATA.yaml`.")
st.write("Deleting this file will allow you to reset API keys. Alternatively, you can edit the keys in the user interface or by manually editing the `.yaml` file in a text editor.")
st.write("Leave keys blank if you do not intend to use that service.")
st.write("---")
st.subheader("Google Vision (*Required*) / Google PaLM 2 / Google Gemini")
st.markdown("VoucherVision currently uses [Google Vision API](https://cloud.google.com/vision/docs/ocr) for OCR. Generating an API key for this is more involved than the others. [Please carefully follow the instructions outlined here to create and setup your account.](https://cloud.google.com/vision/docs/setup) ")
st.markdown("""Once your account is created, [visit this page](https://console.cloud.google.com) and create a project. Then follow these instructions:""")
with st.expander("**View Google API Instructions**"):
blog_text_and_image(text="Select your project, then in the search bar, search for `vertex ai` and select the option in the photo below.",
fullpath=os.path.join(st.session_state.dir_home, 'demo','google','google_api_00.png'))
blog_text_and_image(text="On the main overview page, click `Enable All Recommended APIs`. Sometimes this button may be hidden. In that case, enable all of the suggested APIs listed on this page.",
fullpath=os.path.join(st.session_state.dir_home, 'demo','google','google_api_0.png'))
blog_text_and_image(text="Sometimes this button may be hidden. In that case, enable all of the suggested APIs listed on this page.",
fullpath=os.path.join(st.session_state.dir_home, 'demo','google','google_api_2.png'))
blog_text_and_image(text="Make sure that all APIs are enabled.",
fullpath=os.path.join(st.session_state.dir_home, 'demo','google','google_api_1.png'))
blog_text_and_image(text="Find the `Vision AI API` service and go to its page.",
fullpath=os.path.join(st.session_state.dir_home, 'demo','google','google_api_3.png'))
blog_text_and_image(text="Find the `Vision AI API` service and go to its page. This is the API service required to use OCR in VoucherVision and must be enabled.",
fullpath=os.path.join(st.session_state.dir_home, 'demo','google','google_api_6.png'))
blog_text_and_image(text="You can also search for the Vertex AI Vision service.",
fullpath=os.path.join(st.session_state.dir_home, 'demo','google','google_api_4.png'))
blog_text_and_image(text=None,
fullpath=os.path.join(st.session_state.dir_home, 'demo','google','google_api_5.png'))
st.subheader("Getting a Google JSON authentication key")
st.write("Google uses a JSON file to store additional authentication information. Save this file in a safe, private location and assign the `GOOGLE_APPLICATION_CREDENTIALS` value to the file path. For Hugging Face, copy the contents of the JSON file including the `\{\}` and paste it as the secret value.")
st.write("To download your JSON key...")
blog_text_and_image(text="Open the navigation menu. Click on the hamburger menu (three horizontal lines) in the top left corner. Go to IAM & Admin. ",
fullpath=os.path.join(st.session_state.dir_home, 'demo','google','google_api_7.png'),width=300)
blog_text_and_image(text="In the navigation pane, hover over `IAM & Admin` and then click on `Service accounts`.",
fullpath=os.path.join(st.session_state.dir_home, 'demo','google','google_api_8.png'))
blog_text_and_image(text="Find the default Compute Engine service account, select it.",
fullpath=os.path.join(st.session_state.dir_home, 'demo','google','google_api_9.png'))
blog_text_and_image(text="Click `Add Key`.",
fullpath=os.path.join(st.session_state.dir_home, 'demo','google','google_api_10.png'))
blog_text_and_image(text="Select `JSON` and click create. This will download your key. Store this in a safe location. The file path to this safe location is the value that you enter into the `GOOGLE_APPLICATION_CREDENTIALS` value.",
fullpath=os.path.join(st.session_state.dir_home, 'demo','google','google_api_11.png'))
blog_text(text_bold="Store Safely", text=": This file contains sensitive data that can be used to authenticate and bill your Google Cloud account. Never commit it to public repositories or expose it in any way. Always keep it safe and secure.")
st.write("Below is an example of the JSON key.")
st.json({
"type": "service_account",
"project_id": "NAME OF YOUR PROJECT",
"private_key_id": "XXXXXXXXXXXXXXXXXXXXXXXX",
"private_key": "-----BEGIN PRIVATE KEY-----\naaaaaaaaaaa\n-----END PRIVATE KEY-----\n",
"client_email": "EMAIL-ADDRESS@developer.gserviceaccount.com",
"client_id": "ID NUMBER",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "A LONG URL",
"universe_domain": "googleapis.com"
})
google_application_credentials = st.text_input(label = 'Full path to Google Cloud JSON API key file', value = cfg_private['google'].get('GOOGLE_APPLICATION_CREDENTIALS', ''),
placeholder = 'e.g. C:/Documents/Secret_Files/google_API/application_default_credentials.json',
help ="This API Key is in the form of a JSON file. Please save the JSON file in a safe directory. DO NOT store the JSON key inside of the VoucherVision directory.",
type='password')
google_project_location = st.text_input(label = 'Google project location', value = cfg_private['google'].get('GOOGLE_LOCATION', ''),
placeholder = 'e.g. us-central1',
help ="This is the location of where your Google services are operating.",
type='password')
google_project_id = st.text_input(label = 'Google project ID', value = cfg_private['google'].get('GOOGLE_PROJECT_ID', ''),
placeholder = 'e.g. my-project-name',
help ="This is the value in the `project_id` field in your JSON key.",
type='password')
st.write("---")
st.subheader("OpenAI")
st.markdown("API key for first-party OpenAI API. Create an account with OpenAI [here](https://platform.openai.com/signup), then create an API key [here](https://platform.openai.com/account/api-keys).")
openai_api_key = st.text_input("openai_api_key", cfg_private['openai'].get('OPENAI_API_KEY', ''),
help='The actual API key. Likely to be a string of 2 character, a dash, and then a 48-character string: sk-XXXXXXXX...',
placeholder = 'e.g. sk-XXXXXXXX...',
type='password')
st.write("---")
st.subheader("OpenAI - Azure")
st.markdown("This version OpenAI relies on Azure servers directly as is intended for private enterprise instances of OpenAI's services, such as [UM-GPT](https://its.umich.edu/computing/ai). Administrators will provide you with the following information.")
azure_openai_api_version = st.text_input("OPENAI_API_VERSION", cfg_private['openai_azure'].get('OPENAI_API_VERSION', ''),
help='API Version e.g. "2023-05-15"',
placeholder = 'e.g. 2023-05-15',
type='password')
azure_openai_api_key = st.text_input("OPENAI_API_KEY_AZURE", cfg_private['openai_azure'].get('OPENAI_API_KEY_AZURE', ''),
help='The actual API key. Likely to be a 32-character string. This might also be called "endpoint."',
placeholder = 'e.g. 12333333333333333333333333333332',
type='password')
azure_openai_api_base = st.text_input("OPENAI_API_BASE", cfg_private['openai_azure'].get('OPENAI_API_BASE', ''),
help='The base url for the API e.g. "https://api.umgpt.umich.edu/azure-openai-api"',
placeholder = 'e.g. https://api.umgpt.umich.edu/azure-openai-api',
type='password')
azure_openai_organization = st.text_input("OPENAI_ORGANIZATION", cfg_private['openai_azure'].get('OPENAI_ORGANIZATION', ''),
help='Your organization code. Likely a short string.',
placeholder = 'e.g. 123456',
type='password')
azure_openai_api_type = st.text_input("OPENAI_API_TYPE", cfg_private['openai_azure'].get('OPENAI_API_TYPE', ''),
help='The API type. Typically "azure"',
placeholder = 'e.g. azure',
type='password')
# st.write("---")
# st.subheader("Google PaLM 2 (Deprecated)")
# st.write("Plea")
# st.markdown('Follow these [instructions](https://developers.generativeai.google/tutorials/setup) to generate an API key for PaLM 2. You may need to also activate an account with [MakerSuite](https://makersuite.google.com/app/apikey) and enable "early access." If this is deprecated, then use the full Google API instructions above.')
# google_palm = st.text_input("Google PaLM 2 API Key", cfg_private['google'].get('GOOGLE_PALM_API', ''),
# help='The MakerSuite API key e.g. a 32-character string',
# placeholder='e.g. SATgthsykuE64FgrrrrEervr3S4455t_geyDeGq',
# type='password')
st.write("---")
st.subheader("MistralAI")
st.markdown('Follow these [instructions](https://platform.here.com/sign-up?step=verify-identity) to generate an API key for HERE.')
mistral_API_KEY = st.text_input("MistralAI API Key", cfg_private['mistral'].get('MISTRAL_API_KEY', ''),
help='e.g. a 32-character string',
placeholder='e.g. SATgthsykuE64FgrrrrEervr3S4455t_geyDeGq',
type='password')
st.write("---")
st.subheader("HERE Geocoding")
st.markdown('Follow these [instructions](https://platform.here.com/sign-up?step=verify-identity) to generate an API key for HERE.')
hre_APP_ID = st.text_input("HERE Geocoding App ID", cfg_private['here'].get('APP_ID', ''),
help='e.g. a 32-character string',
placeholder='e.g. SATgthsykuE64FgrrrrEervr3S4455t_geyDeGq',
type='password')
hre_API_KEY = st.text_input("HERE Geocoding API Key", cfg_private['here'].get('API_KEY', ''),
help='e.g. a 32-character string',
placeholder='e.g. SATgthsykuE64FgrrrrEervr3S4455t_geyDeGq',
type='password')
st.button("Set API Keys",type='primary', on_click=save_changes_to_API_keys, args=[cfg_private,openai_api_key,azure_openai_api_version,azure_openai_api_key,
azure_openai_api_base,azure_openai_organization,azure_openai_api_type,
google_application_credentials, google_project_location, google_project_id,
mistral_API_KEY, hre_APP_ID, hre_API_KEY])
if st.button('Proceed to VoucherVision'):
st.session_state.private_file = does_private_file_exist()
st.session_state.proceed_to_private = False
st.session_state.proceed_to_main = True
st.rerun()
def save_changes_to_API_keys(cfg_private,openai_api_key,azure_openai_api_version,azure_openai_api_key,
azure_openai_api_base,azure_openai_organization,azure_openai_api_type,
google_application_credentials, google_project_location, google_project_id,
mistral_API_KEY, hre_APP_ID, hre_API_KEY):
# Update the configuration dictionary with the new values
cfg_private['openai']['OPENAI_API_KEY'] = openai_api_key
cfg_private['openai_azure']['OPENAI_API_VERSION'] = azure_openai_api_version
cfg_private['openai_azure']['OPENAI_API_KEY_AZURE'] = azure_openai_api_key
cfg_private['openai_azure']['OPENAI_API_BASE'] = azure_openai_api_base
cfg_private['openai_azure']['OPENAI_ORGANIZATION'] = azure_openai_organization
cfg_private['openai_azure']['OPENAI_API_TYPE'] = azure_openai_api_type
cfg_private['google']['GOOGLE_APPLICATION_CREDENTIALS'] = google_application_credentials
cfg_private['google']['GOOGLE_PROJECT_ID'] = google_project_location
cfg_private['google']['GOOGLE_LOCATION'] = google_project_id
cfg_private['mistral']['MISTRAL_API_KEY'] = mistral_API_KEY
cfg_private['here']['APP_ID'] = hre_APP_ID
cfg_private['here']['API_KEY'] = hre_API_KEY
# Call the function to write the updated configuration to the YAML file
write_config_file(cfg_private, st.session_state.dir_home, filename="PRIVATE_DATA.yaml")
# st.session_state.private_file = does_private_file_exist()
# Function to load a YAML file and update session_state
### Updated to match HF version
# def save_prompt_yaml(filename):
def show_header_welcome():
st.session_state.logo_path = os.path.join(st.session_state.dir_home, 'img','logo.png')
st.session_state.logo = Image.open(st.session_state.logo_path)
st.image(st.session_state.logo, width=250)
def determine_n_images():
try:
# Check if 'dir_uploaded_images' key exists in session state and it is not empty
if 'dir_uploaded_images' in st.session_state and st.session_state['dir_uploaded_images']:
dir_path = st.session_state['dir_uploaded_images'] # This would be the path to the directory
# Count only files (not directories) in the specified directory
count = len([f for f in os.listdir(dir_path) if os.path.isfile(os.path.join(dir_path, f))])
return count
else:
return None # Return 0 if the directory path doesn't exist or is empty
except Exception as e:
print(e)
return None
# def determine_n_images():
# try:
# # Check if 'dir_uploaded_images' key exists and it is not empty
# if 'dir_uploaded_images' in st and st['dir_uploaded_images']:
# dir_path = st['dir_uploaded_images'] # This would be the path to the directory
# return len([f for f in os.listdir(dir_path) if os.path.isfile(os.path.join(dir_path, f))])
# else:
# return None
# except:
# return None
def save_api_status(present_keys, missing_keys, date_of_check):
with open(os.path.join(st.session_state.dir_home,'api_status.yaml'), 'w') as file:
yaml.dump({'present_keys': present_keys, 'missing_keys': missing_keys, "date": date_of_check}, file)
def load_api_status():
try:
with open(os.path.join(st.session_state.dir_home,'api_status.yaml'), 'r') as file:
status = yaml.safe_load(file)
return status.get('present_keys', []), status.get('missing_keys', []), status.get('date', [])
except FileNotFoundError:
return None, None, None
def display_api_key_status(ccol):
if not st.session_state['API_checked']:
present_keys, missing_keys, date_of_check = load_api_status()
if present_keys is None and missing_keys is None:
st.session_state['API_checked'] = False
else:
# Convert keys to annotations (similar to what you do in check_api_key_status)
present_annotations = []
missing_annotations = []
for key in present_keys:
if "Valid" in key:
show_text = key.split('(')[0]
present_annotations.append((show_text, "ready!", "#059c1b")) # Green for valid
elif "Invalid" in key:
show_text = key.split('(')[0]
present_annotations.append((show_text, "error", "#870307")) # Red for invalid
st.session_state['present_annotations'] = present_annotations
st.session_state['missing_annotations'] = missing_annotations
st.session_state['date_of_check'] = date_of_check
st.session_state['API_checked'] = True
# print('for')
# print(st.session_state['present_annotations'])
# print(st.session_state['missing_annotations'])
else:
# print('else')
# print(st.session_state['present_annotations'])
# print(st.session_state['missing_annotations'])
pass
# Check if the API status has already been retrieved
if 'API_checked' not in st.session_state or not st.session_state['API_checked'] or st.session_state['API_rechecked']:
with ccol:
with st.spinner('Verifying APIs by sending short requests...'):
check_api_key_status()
st.session_state['API_checked'] = True
st.session_state['API_rechecked'] = False
st.markdown(f"Last checked on {st.session_state['date_of_check']}")
# Display present keys horizontally
if 'present_annotations' in st.session_state and st.session_state['present_annotations']:
annotated_text(*st.session_state['present_annotations'])
# Display missing keys horizontally
if 'missing_annotations' in st.session_state and st.session_state['missing_annotations']:
annotated_text(*st.session_state['missing_annotations'])
def check_api_key_status():
try:
path_cfg_private = os.path.join(st.session_state.dir_home, 'PRIVATE_DATA.yaml')
cfg_private = get_cfg_from_full_path(path_cfg_private)
except:
cfg_private = None
API_Validator = APIvalidation(cfg_private, st.session_state.dir_home, st.session_state['is_hf'])
present_keys, missing_keys, date_of_check = API_Validator.report_api_key_status() # Assuming this function returns two lists
# Prepare annotations for present keys
present_annotations = []
missing_annotations = []
for key in present_keys:
if "Valid" in key:
show_text = key.split('(')[0]
present_annotations.append((show_text, "ready!", "#059c1b")) # Green for valid
elif "Invalid" in key:
show_text = key.split('(')[0]
present_annotations.append((show_text, "error", "#870307")) # Red for invalid
# Prepare annotations for missing keys
for key in missing_keys:
show_text = key.split('(')[0]
missing_annotations.append((show_text, "n/a", " ", "#c4c4c4")) # Red for invalid
# Save API key status
save_api_status(present_keys, missing_keys, date_of_check)
st.session_state['present_annotations'] = present_annotations
st.session_state['missing_annotations'] = missing_annotations
st.session_state['date_of_check'] = date_of_check
def convert_cost_dict_to_table(cost, name):
# Convert the dictionary to a pandas DataFrame for nicer display
df = pd.DataFrame.from_dict(cost, orient='index')
df.reset_index(inplace=True)
df.columns = [str(name), 'Input', 'Output']
# Apply color gradient
cm = sns.light_palette("green", as_cmap=True)
styled_df = df.style.background_gradient(cmap=cm, subset=['Input', 'Output'])
return styled_df
def get_all_cost_tables():
warnings.filterwarnings('ignore', message=".*is_sparse is deprecated.*")
CostMap = ModelMaps
cost_names = CostMap.get_all_mapping_cost()
path_api_cost = os.path.join(st.session_state.dir_home,'api_cost','api_cost.yaml')
with open(path_api_cost, 'r') as file:
cost_data = yaml.safe_load(file)
cost_openai = {}
cost_azure = {}
cost_google = {}
cost_mistral = {}
cost_local = {}
for key, value in cost_names.items():
parts = value.split("_")
if 'LOCAL' in parts:
cost_local[key] = cost_data.get(value,'')
elif 'AZURE' in parts:
cost_azure[key] = cost_data.get(value,'')
elif 'GPT' in parts:
cost_openai[key] = cost_data.get(value,'')
elif 'PALM2' in parts or 'GEMINI' in parts:
cost_google[key] = cost_data.get(value,'')
elif 'MISTRAL' in parts:
cost_mistral[key] = cost_data.get(value,'')
styled_cost_openai = convert_cost_dict_to_table(cost_openai, "OpenAI")
styled_cost_azure = convert_cost_dict_to_table(cost_azure, "OpenAI (Azure Endpoints)")
styled_cost_google = convert_cost_dict_to_table(cost_google, "Google (VertexAI)")
styled_cost_mistral = convert_cost_dict_to_table(cost_mistral, "MistralAI")
styled_cost_local = convert_cost_dict_to_table(cost_local, "Local Models")
return cost_openai, styled_cost_openai, cost_azure, styled_cost_azure, cost_google, styled_cost_google, cost_mistral, styled_cost_mistral, cost_local, styled_cost_local
def content_header():
col_logo, col_run_1, col_run_2, col_run_3, col_run_4 = st.columns([2,2,2,2,4])
with col_run_4:
with st.expander("View Messages and Updates"):
st.info("***Note:*** If you use VoucherVision frequently, you can change the default values that are auto-populated in the form below. In a text editor or IDE, edit the first few rows in the file `../VoucherVision/vouchervision/VoucherVision_Config_Builder.py`")
st.info("Please enable LeafMachine2 collage for full-sized images of herbarium vouchers, you will get better results!")
col_test = st.container()
st.subheader("Overall Progress")
col_run_info_1 = st.columns([1])[0]
col_updates_1, col_updates_2 = st.columns([5,1])
col_json, col_json_WFO, col_json_GEO, col_json_map = st.columns([2, 2, 2, 2])
with col_run_info_1:
# Progress
overall_progress_bar = st.progress(0)
text_overall = st.empty() # Placeholder for current step name
st.subheader('Transcription Progress')
batch_progress_bar = st.progress(0)
text_batch = st.empty() # Placeholder for current step name
progress_report = ProgressReport(overall_progress_bar, batch_progress_bar, text_overall, text_batch)
st.session_state['hold_output'] = st.toggle('View Final Transcription')
with col_logo:
show_header_welcome()
with col_run_1:
N_STEPS = 6
if check_if_usable(is_hf=st.session_state['is_hf']):
b_text = f"Start Processing {st.session_state['processing_add_on']} Images" if st.session_state['processing_add_on'] > 1 else f"Start Processing {st.session_state['processing_add_on']} Image"
if st.session_state['processing_add_on'] == 0:
b_text = f"Start Processing"
if st.button(b_text, type='primary',use_container_width=True):
st.session_state['formatted_json'] = {}
st.session_state['formatted_json_WFO'] = {}
st.session_state['formatted_json_GEO'] = {}
st.session_state['json_report'] = JSONReport(col_updates_1, col_json, col_json_WFO, col_json_GEO, col_json_map)
st.session_state['json_report'].set_JSON(st.session_state['formatted_json'], st.session_state['formatted_json_WFO'], st.session_state['formatted_json_GEO'])
# Define number of overall steps
progress_report.set_n_overall(N_STEPS)
progress_report.update_overall(f"Starting VoucherVision...")
# First, write the config file.
write_config_file(st.session_state.config, st.session_state.dir_home, filename="VoucherVision.yaml")
path_custom_prompts = os.path.join(st.session_state.dir_home,'custom_prompts',st.session_state.config['leafmachine']['project']['prompt_version'])
# Call the machine function.
total_cost = 0.00
n_failed_OCR = 0
n_failed_LLM_calls = 0
# try:
voucher_vision_output = voucher_vision(None,
st.session_state.dir_home,
path_custom_prompts,
None,
progress_report,
st.session_state['json_report'],
path_api_cost=os.path.join(st.session_state.dir_home,'api_cost','api_cost.yaml'),
is_hf = st.session_state['is_hf'],
is_real_run=True)
st.session_state['formatted_json'] = voucher_vision_output['last_JSON_response']
st.session_state['formatted_json_WFO'] = voucher_vision_output['final_WFO_record']
st.session_state['formatted_json_GEO'] = voucher_vision_output['final_GEO_record']
total_cost = voucher_vision_output['total_cost']
n_failed_OCR = voucher_vision_output['n_failed_OCR']
n_failed_LLM_calls = voucher_vision_output['n_failed_LLM_calls']
st.session_state['zip_filepath'] = voucher_vision_output['zip_filepath']
# st.balloons()
# except Exception as e:
# with col_run_4:
# st.error(f"Transcription failed. Error: {e}")
if n_failed_OCR > 0:
with col_run_4:
st.error(f"Caution:heavy_exclamation_mark: :loudspeaker: {n_failed_LLM_calls} images had a no extractable OCR text :eyes:")
if n_failed_LLM_calls > 0:
with col_run_4:
st.error(f"Caution:heavy_exclamation_mark: :loudspeaker: {n_failed_LLM_calls} images had a failed LLM API call :eyes:")
st.error(f"Make sure that you have access to the chosen LLM API model. Sometimes certain OpenAI accounts do not have access to all models, for example")
if total_cost:
with col_run_4:
st.success(f":money_with_wings: This run cost :heavy_dollar_sign:{total_cost:.4f}")
else:
with col_run_4:
st.info(f":money_with_wings: This run cost :heavy_dollar_sign:{total_cost:.4f}")
if st.session_state['zip_filepath']:
create_download_button(st.session_state['zip_filepath'], col_run_1,key=97863332)
else:
st.button("Start Processing", type='primary', disabled=True)
with col_run_4:
st.error(":heavy_exclamation_mark: Required API keys not set. Please visit the 'API Keys' tab and set the Google Vision OCR API key and at least one LLM key.")
if st.session_state['formatted_json']:
if st.session_state['hold_output']:
st.session_state['json_report'].set_JSON(st.session_state['formatted_json'], st.session_state['formatted_json_WFO'], st.session_state['formatted_json_GEO'])
if st.session_state['zip_filepath']:
create_download_button(st.session_state['zip_filepath'], col_run_1,key=978633452)
with col_run_1:
ct_left, ct_right = st.columns([1,1])
with ct_left:
st.button("Refresh", on_click=refresh, use_container_width=True)
with ct_right:
# st.page_link(os.path.join(os.path.dirname(__file__),"pages","faqs.py"), label="FAQs", icon="β")
st.page_link(os.path.join("pages","faqs.py"), label="FAQs", icon="β")
# if st.button('FAQs', use_container_width=True):
# st.session_state.proceed_to_faqs = True
# st.session_state.proceed_to_main = False
# st.rerun()
# with col_run_2:
# if st.button("Test GPT"):
# progress_report.set_n_overall(TestOptionsGPT.get_length())
# test_results, JSON_results = run_demo_tests_GPT(progress_report)
# with col_test:
# display_test_results(test_results, JSON_results, 'gpt')
# st.balloons()
# if st.button("Test PaLM2"):
# progress_report.set_n_overall(TestOptionsPalm.get_length())
# test_results, JSON_results = run_demo_tests_Palm(progress_report)
# with col_test:
# display_test_results(test_results, JSON_results, 'palm')
# st.balloons()
with col_run_2:
if st.button('Save Current Settings',use_container_width=True):
if st.session_state.settings_filename:
config_file_path = os.path.join(st.session_state.dir_home, 'settings', st.session_state['settings_filename'] + '.yaml')
with open(config_file_path, 'w') as file:
yaml.dump(st.session_state.config, file, default_flow_style=False)
with col_run_4:
st.success(f'Current settings saved to {config_file_path}')
else:
with col_run_4:
st.error('Missing settings file name. Settings not saved.')
# st.session_state.config
with col_run_3:
st.session_state['settings_filename'] = st.text_input('Setting File Name',placeholder="Settings fileame",label_visibility='collapsed',value=None)
with col_run_2:
if st.button('Load Settings',use_container_width=True):
if st.session_state['loaded_settings_filename']:
path_load_settings = os.path.join(st.session_state['dir_settings'],st.session_state['loaded_settings_filename'])
if os.path.exists(path_load_settings) and not None:
with open(path_load_settings, 'r') as file:
loaded_config = yaml.safe_load(file)
st.session_state.config, st.session_state.dir_home = build_VV_config(loaded_cfg=loaded_config)
with col_run_4:
st.success(f'Loaded settings from {path_load_settings}')
else:
st.error(f'Path to settings file does not exist: {path_load_settings}')
else:
with col_run_4:
st.warning(f'Filename not selected')
with col_run_3:
st.session_state['settings_choice_null'] = 'Select previous settings...'
st.session_state['dir_settings'] = os.path.join(st.session_state.dir_home, 'settings')
all_settings_files = [st.session_state['settings_choice_null']] + [f for f in os.listdir(st.session_state['dir_settings']) if f.endswith('.yaml')]
settings_choice = st.selectbox('Load Previous Settings', all_settings_files,label_visibility='collapsed')
if settings_choice != st.session_state['settings_choice_null']:
st.session_state['loaded_settings_filename'] = settings_choice
with col_run_2:
if st.button("Check GPU Status",use_container_width=True):
success, info = test_GPU()
if success:
st.balloons()
with col_run_4:
for message in info:
st.success(message)
else:
with col_run_4:
for message in info:
st.warning(message)
def content_project_settings(col):
### Project
with col:
st.header('Project Settings')
st.session_state.config['leafmachine']['project']['run_name'] = st.text_input("Run name", st.session_state.config['leafmachine']['project'].get('run_name', ''),key=63456)
if not st.session_state.is_hf:
st.session_state.config['leafmachine']['project']['dir_output'] = st.text_input("Output directory", st.session_state.config['leafmachine']['project'].get('dir_output', ''))
def content_llm_cost():
st.write("---")
st.header('LLM Cost Calculator')
# ( n_in/1000 * Input + n_out/1000 * Output ) * n_img = COST
calculator_1,calculator_2,calculator_3,calculator_4,calculator_5 = st.columns([1,1,1,1,1])
st.subheader('Cost Matrix')
st.markdown('The table shows the cost of each LLM API per 1,000 tokens. An average VoucherVision call uses 2,000 input tokens and receives 500 output tokens.')
col_cost_1, col_cost_2, col_cost_3, col_cost_4, col_cost_5 = st.columns([1,1,1,1,1])
# Load all cost tables if not already done
if 'all_llm_cost' not in st.session_state:
st.session_state['all_llm_cost'] = True
st.session_state['cost_openai'], st.session_state['styled_cost_openai'], st.session_state['cost_azure'], st.session_state['styled_cost_azure'], st.session_state['cost_google'], st.session_state['styled_cost_google'], st.session_state['cost_mistral'], st.session_state['styled_cost_mistral'], st.session_state['cost_local'], st.session_state['styled_cost_local'] = get_all_cost_tables()
with calculator_1:
# Combine all model names into a single list
model_names = []
for df in [st.session_state['cost_openai'], st.session_state['cost_azure'], st.session_state['cost_google'], st.session_state['cost_mistral'], st.session_state['cost_local']]:
for key in df.keys():
model_names.append(key)
# Create a dropdown for model selection
selected_model = st.selectbox("Select a model", options=model_names)
with calculator_2:
# Create input fields for n_in, n_out, n_img
n_in = st.number_input("Tokens In", min_value=0, value=2000, step=50)
with calculator_3:
n_out = st.number_input("Tokens Out", min_value=0, value=500, step=50)
with calculator_4:
n_img = st.number_input("Number of Images", min_value=0, value=1000, step=100)
# Function to find the model's Input and Output values
def find_model_values(model, all_dfs):
for df in all_dfs:
if model in df.keys():
return df[model]['in'], df[model]['out']
return None, None
# Calculate and display cost when button is pressed
input_value, output_value = find_model_values(selected_model,
[st.session_state['cost_openai'], st.session_state['cost_azure'], st.session_state['cost_google'], st.session_state['cost_mistral'], st.session_state['cost_local']])
if input_value is not None and output_value is not None:
cost = (n_in/1000 * input_value + n_out/1000 * output_value) * n_img
with calculator_5:
st.text_input("Total Cost", f"${round(cost,2)}") # selected_model
with col_cost_1:
rounding = 4
st.dataframe(st.session_state.styled_cost_openai.format(precision=rounding), hide_index=True,)
with col_cost_2:
st.dataframe(st.session_state.styled_cost_azure.format(precision=rounding), hide_index=True,)
with col_cost_3:
st.dataframe(st.session_state.styled_cost_google.format(precision=rounding), hide_index=True,)
with col_cost_4:
st.dataframe(st.session_state.styled_cost_mistral.format(precision=rounding), hide_index=True,)
with col_cost_5:
st.dataframe(st.session_state.styled_cost_local.format(precision=rounding), hide_index=True,)
def content_prompt_and_llm_version():
st.header('Prompt Version')
col_prompt_1, col_prompt_2 = st.columns([4,2])
with col_prompt_1:
available_prompts = get_prompt_versions(st.session_state.config['leafmachine']['LLM_version'])
if available_prompts:
default_version = available_prompts[0] ######### Can be configured by user #################################################################
selected_version = st.session_state.config['leafmachine']['project'].get('prompt_version', default_version)
if selected_version not in available_prompts:
selected_version = default_version
st.session_state.config['leafmachine']['project']['prompt_version'] = st.selectbox("Prompt Version", available_prompts, index=available_prompts.index(selected_version),label_visibility='collapsed')
with col_prompt_2:
# if st.button("Build Custom LLM Prompt"):
# st.page_link(os.path.join(os.path.dirname(__file__),"pages","prompt_builder.py"), label="Prompt Builder", icon="π§")
st.page_link(os.path.join("pages","prompt_builder.py"), label="Prompt Builder", icon="π§")
st.header('LLM Version')
col_llm_1, col_llm_2 = st.columns([4,2])
with col_llm_1:
GUI_MODEL_LIST = ModelMaps.get_models_gui_list()
st.session_state.config['leafmachine']['LLM_version'] = st.selectbox("LLM version", GUI_MODEL_LIST, index=GUI_MODEL_LIST.index(st.session_state.config['leafmachine'].get('LLM_version', ModelMaps.MODELS_GUI_DEFAULT)))
def content_api_check():
# In your Streamlit layout
# Create two columns for the header and the button
col_llm_2a, col_llm_2b = st.columns([6, 2]) # Adjust the ratio as needed
# Place the header in the first column
with col_llm_2a:
st.header('Available APIs')
# Display API key status
display_api_key_status(col_llm_2a)
# Place the button in the second column, right-justified
# with col_llm_2b:
if st.button("Re-Check API Keys"):
st.session_state['API_checked'] = False
st.session_state['API_rechecked'] = True
st.rerun()
# with col_llm_2c:
if not st.session_state.is_hf:
if st.button("Edit API Keys"):
st.session_state.proceed_to_private = True
st.rerun()
def adjust_ocr_options_based_on_capability(capability_score):
llava_models_requirements = {
"liuhaotian/llava-v1.6-mistral-7b": {"full": 18, "4bit": 9},
"liuhaotian/llava-v1.6-34b": {"full": 70, "4bit": 25},
"liuhaotian/llava-v1.6-vicuna-13b": {"full": 33, "4bit": 15},
"liuhaotian/llava-v1.6-vicuna-7b": {"full": 20, "4bit": 10},
}
if capability_score == 'no_gpu':
return False
else:
capability_score_n = int(capability_score.split("_")[1].split("GB")[0])
supported_models = [model for model, reqs in llava_models_requirements.items()
if reqs["full"] <= capability_score_n or reqs["4bit"] <= capability_score_n]
# If no models are supported, disable the LLaVA option
if not supported_models:
# Assuming the LLaVA option is the last in your list
return False # Indicate LLaVA is not supported
return True # Indicate LLaVA is supported
def content_ocr_method():
st.write("---")
st.header('OCR Methods')
with st.expander("Read about available OCR methods"):
st.subheader("Overview")
st.markdown("""VoucherVision can use the `Google Vision API`, `CRAFT` text detection + `trOCR`, and all `LLaVA v1.6` models.
VoucherVision sends the OCR inside of the LLM prompt. We have found that sending multiple copies, or multiple version of
the OCR text to the LLM helps the LLM maintain focus on the OCR text -- our prompts are quite long and the OCR text is reletively short.
Below you can choose the OCR method/s. You can 'stack' all of the methods if you want, which may improve results because
different OCR methods have different strengths, giving the LLM more information to work with. Alternative.y, you can select a single method and
send 2 copies to the LLM by enabling that option below.""")
st.subheader("Google Vision API")
st.markdown("""`Google Vision API` provides several OCR methods. We use the `document_text_detection()` service, designed to handle dense text blocks.
The `Handwritten` option CAN also be used for printed and mixed labels, but it is also optimized for handwriting. `Handwritten` uses the Google Vision Beta service.
This is the recommended default OCR method. `Printed` uses the regular Google Vision service and works well for general use.
You can also supplement Google Vision OCR by enabling trOCR, which is optimized for handwriting. trOCR requires segmented word images, which is provided as part
of the Google Vision metadata. trOCR does not require a GPU, but it runs *much* faster with a GPU.""")
st.subheader("LLaVA")
st.markdown("""`LLaVA` can replace Google Vision APIs. It requires the use of LeafMachine2 collage, or images that are majority text. It may struggle with very
long texts. LLaVA models are multimodal, meaning that we can upload the image and the model will transcribe (and even parse) the text all at once. With VoucherVision, we
support 4 different LLaVA models of varying sizes, some are much more capable than others. These models tend to outperform all other OCR methods for handwriting.
LLaVA models are run locally and require powerful GPUs to implement. While LLaVA models are capable of handling both the OCR and text parsing tasks all in one step,
this option only uses LLaVA to transcribe all of the text in the image and still uses a separate LLM to parse text in to categories. """)
st.subheader("CRAFT + trOCR")
st.markdown("""This pairing can replace Google Vision APIs and is computationally lighter than LLaVA. `CRAFT` locates text, segments lines of text, and feeds the segmentations
to the `trOCR` transformer model. This pairing requires at least an 8 GB GPU. trOCR is a Microsoft model optimized for handwriting. The base model is not as accurate as
LLaVA or Google Vision, but if you have a trOCR-based model, let us know and we will add support.""")
c1, c2 = st.columns([4,4])
# Check if LLaVA models are supported based on capability score
llava_supported = adjust_ocr_options_based_on_capability(st.session_state.capability_score)
if llava_supported:
st.success("LLaVA models are supported on this computer")
else:
st.warning("LLaVA models are NOT supported on this computer. Requires a GPU with at least 12 GB of VRAM.")
demo_text_h = f"Google_OCR_Handwriting:\nHERBARIUM OF MARCUS W. LYON , JR . Tracaulon sagittatum Indiana : Porter Co. incal Springs edge wet subdunal woods 1927 TX 11 Ilowers pink UNIVERSITE HERBARIUM MICH University of Michigan Herbarium 1439649 copyright reserved PERSICARIA FEB 2 6 1965 cm "
demo_text_tr = f"trOCR:\nherbarium of marcus w. lyon jr. : : : tracaulon sagittatum indiana porter co. incal springs TX 11 Ilowers pink 1439649 copyright reserved D H U Q "
demo_text_p = f"Google_OCR_Printed:\nTracaulon sagittatum Indiana : Porter Co. incal Springs edge wet subdunal woods 1927 Ilowers pink 1439649 copyright reserved PERSICARIA FEB 2 6 1965 cm "
demo_text_b = demo_text_h + '\n' + demo_text_p
demo_text_trb = demo_text_h + '\n' + demo_text_p + '\n' + demo_text_tr
demo_text_trh = demo_text_h + '\n' + demo_text_tr
demo_text_trp = demo_text_p + '\n' + demo_text_tr
options = ["Google Vision Handwritten", "Google Vision Printed", "CRAFT + trOCR","LLaVA"]
options_llava = ["llava-v1.6-mistral-7b", "llava-v1.6-34b", "llava-v1.6-vicuna-13b", "llava-v1.6-vicuna-7b",]
options_llava_bit = ["full", "4bit",]
captions_llava = [
"Full Model: 18 GB VRAM, 4-bit: 9 GB VRAM",
"Full Model: 70 GB VRAM, 4-bit: 25 GB VRAM",
"Full Model: 33 GB VRAM, 4-bit: 15 GB VRAM",
"Full Model: 20 GB VRAM, 4-bit: 10 GB VRAM",
]
captions_llava_bit = ["Full Model","4-bit Quantization",]
# Get the current OCR option from session state
OCR_option = st.session_state.config['leafmachine']['project']['OCR_option']
OCR_option_llava = st.session_state.config['leafmachine']['project']['OCR_option_llava']
OCR_option_llava_bit = st.session_state.config['leafmachine']['project']['OCR_option_llava_bit']
double_OCR = st.session_state.config['leafmachine']['project']['double_OCR']
# Map the OCR option to the index in options list
# You need to define the mapping based on your application's logic
default_index = 0 # Default to 0 if option not found
default_index_llava = 0 # Default to 0 if option not found
default_index_llava_bit = 0
with c1:
st.subheader("API Methods (Google Vision)")
st.write("Using APIs for OCR allows VoucherVision to run on most computers.")
st.session_state.config['leafmachine']['project']['double_OCR'] = st.checkbox(label="Send 2 copies of the OCR to the LLM",
help="This can help the LLMs focus attention on the OCR and not get lost in the longer instruction text",
value=double_OCR)
# Create the radio button
# OCR_option_select = st.radio(
# "Select the OCR Method",
# options,
# index=default_index,
# help="",captions=captions,
# )
default_values = [options[default_index]]
OCR_option_select = st.multiselect(
"Select the OCR Method(s)",
options=options,
default=default_values,
help="Select one or more OCR methods."
)
# st.session_state.config['leafmachine']['project']['OCR_option'] = OCR_option_select
# Handling multiple selections (Example logic)
OCR_options = {
"Google Vision Handwritten": 'hand',
"Google Vision Printed": 'normal',
"CRAFT + trOCR": 'CRAFT',
"LLaVA": 'LLaVA',
}
# Map selected options to their corresponding internal representations
selected_OCR_options = [OCR_options[option] for option in OCR_option_select]
# Assuming you need to use these mapped values elsewhere in your application
st.session_state.config['leafmachine']['project']['OCR_option'] = selected_OCR_options
with c2:
st.subheader("Local Methods")
st.write("Local methods are free, but require a capable GPU. ")
st.write("Supplement Google Vision OCR with trOCR (handwriting OCR) using `microsoft/trocr-base-handwritten`. This option requires Google Vision API and a GPU.")
if 'CRAFT' in selected_OCR_options:
do_use_trOCR = st.checkbox("Enable trOCR", value=True, key="Enable trOCR1",disabled=True)#,disabled=st.session_state['lacks_GPU'])
else:
do_use_trOCR = st.checkbox("Enable trOCR", value=st.session_state.config['leafmachine']['project']['do_use_trOCR'],key="Enable trOCR2")#,disabled=st.session_state['lacks_GPU'])
st.session_state.config['leafmachine']['project']['do_use_trOCR'] = do_use_trOCR
if 'LLaVA' in selected_OCR_options:
OCR_option_llava = st.radio(
"Select the LLaVA version",
options_llava,
index=default_index_llava,
help="",captions=captions_llava,
)
st.session_state.config['leafmachine']['project']['OCR_option_llava'] = OCR_option_llava
OCR_option_llava_bit = st.radio(
"Select the LLaVA quantization level",
options_llava_bit,
index=default_index_llava_bit,
help="",captions=captions_llava_bit,
)
st.session_state.config['leafmachine']['project']['OCR_option_llava_bit'] = OCR_option_llava_bit
# st.markdown("Below is an example of what the LLM would see given the choice of OCR ensemble. One, two, or three version of OCR can be fed into the LLM prompt. Typically, 'printed + handwritten' works well. If you have a GPU then you can enable trOCR.")
# if (OCR_option == 'hand') and not do_use_trOCR:
# st.text_area(label='Handwritten/Printed',placeholder=demo_text_h,disabled=True, label_visibility='visible', height=150)
# elif (OCR_option == 'normal') and not do_use_trOCR:
# st.text_area(label='Printed',placeholder=demo_text_p,disabled=True, label_visibility='visible', height=150)
# elif (OCR_option == 'both') and not do_use_trOCR:
# st.text_area(label='Handwritten/Printed + Printed',placeholder=demo_text_b,disabled=True, label_visibility='visible', height=150)
# elif (OCR_option == 'both') and do_use_trOCR:
# st.text_area(label='Handwritten/Printed + Printed + trOCR',placeholder=demo_text_trb,disabled=True, label_visibility='visible', height=150)
# elif (OCR_option == 'normal') and do_use_trOCR:
# st.text_area(label='Printed + trOCR',placeholder=demo_text_trp,disabled=True, label_visibility='visible', height=150)
# elif (OCR_option == 'hand') and do_use_trOCR:
# st.text_area(label='Handwritten/Printed + trOCR',placeholder=demo_text_trh,disabled=True, label_visibility='visible', height=150)
def content_collage_overlay():
st.write("---")
col_collage, col_overlay = st.columns([4,4])
with col_collage:
st.header('LeafMachine2 Label Collage')
st.info("NOTE: We strongly recommend enabling LeafMachine2 cropping if your images are full sized herbarium sheet. Often, the OCR algorithm struggles with full sheets, but works well with the collage images. We have disabled the collage by default for this Hugging Face Space because the Space lacks a GPU and the collage creation takes a bit longer.")
default_crops = st.session_state.config['leafmachine']['cropped_components']['save_cropped_annotations']
st.write("Prior to transcription, use LeafMachine2 to crop all labels from input images to create label collages for each specimen image. Showing just the text labels to the OCR algorithms significantly improves performance. This runs slowly on the free Hugging Face Space, but runs quickly with a fast CPU or any GPU.")
st.session_state.config['leafmachine']['use_RGB_label_images'] = st.checkbox(":rainbow[Use LeafMachine2 label collage for transcriptions]", st.session_state.config['leafmachine'].get('use_RGB_label_images', False))
option_selected_crops = st.multiselect(label="Components to crop",
options=['ruler', 'barcode','label', 'colorcard','map','envelope','photo','attached_item','weights',
'leaf_whole', 'leaf_partial', 'leaflet', 'seed_fruit_one', 'seed_fruit_many', 'flower_one', 'flower_many', 'bud','specimen','roots','wood'],default=default_crops)
st.session_state.config['leafmachine']['cropped_components']['save_cropped_annotations'] = option_selected_crops
# Load the image only if it's not already in the session state
if "demo_collage" not in st.session_state:
# ba = os.path.join(st.session_state.dir_home, 'demo', 'ba', 'ba2.png')
ba = os.path.join(st.session_state.dir_home, 'demo', 'ba', 'ba2.png')
st.session_state["demo_collage"] = Image.open(ba)
# Display the image
with st.expander(":frame_with_picture: View an example of the LeafMachine2 collage image"):
st.image(st.session_state["demo_collage"], caption='LeafMachine2 Collage', output_format="PNG")
# st.image(st.session_state["demo_collage"], caption='LeafMachine2 Collage', output_format="JPEG")
with col_overlay:
st.header('OCR Overlay Image')
st.write('This will plot bounding boxes around all text that Google Vision was able to detect. If there are no boxes around text, then the OCR failed, so that missing text will not be seen by the LLM when it is creating the JSON object. The created image will be viewable in the VoucherVisionEditor.')
do_create_OCR_helper_image = st.checkbox("Create image showing an overlay of the OCR detections",value=st.session_state.config['leafmachine']['do_create_OCR_helper_image'],disabled=True)
st.session_state.config['leafmachine']['do_create_OCR_helper_image'] = do_create_OCR_helper_image
if "demo_overlay" not in st.session_state:
# ocr = os.path.join(st.session_state.dir_home,'demo', 'ba','ocr.png')
ocr = os.path.join(st.session_state.dir_home,'demo', 'ba','ocr2.png')
st.session_state["demo_overlay"] = Image.open(ocr)
with st.expander(":frame_with_picture: View an example of the OCR overlay image"):
st.image(st.session_state["demo_overlay"], caption='OCR Overlay Images', output_format = "PNG")
# st.image(st.session_state["demo_overlay"], caption='OCR Overlay Images', output_format = "JPEG")
def content_archival_components():
st.write("---")
st.header('Archival Components')
ACD_version = st.selectbox("Archival Component Detector (ACD) Version", ["Version 2.1", "Version 2.2"])
ACD_confidence_default = int(st.session_state.config['leafmachine']['archival_component_detector']['minimum_confidence_threshold'] * 100)
ACD_confidence = st.number_input("ACD Confidence Threshold (%)", min_value=0, max_value=100,value=ACD_confidence_default)
st.session_state.config['leafmachine']['archival_component_detector']['minimum_confidence_threshold'] = float(ACD_confidence/100)
st.session_state.config['leafmachine']['archival_component_detector']['do_save_prediction_overlay_images'] = st.checkbox("Save Archival Prediction Overlay Images", st.session_state.config['leafmachine']['archival_component_detector'].get('do_save_prediction_overlay_images', True))
st.session_state.config['leafmachine']['archival_component_detector']['ignore_objects_for_overlay'] = st.multiselect("Hide Archival Components in Prediction Overlay Images",
['ruler', 'barcode','label', 'colorcard','map','envelope','photo','attached_item','weights',],
default=[])
# Depending on the selected version, set the configuration
if ACD_version == "Version 2.1":
st.session_state.config['leafmachine']['archival_component_detector']['detector_type'] = 'Archival_Detector'
st.session_state.config['leafmachine']['archival_component_detector']['detector_version'] = 'PREP_final'
st.session_state.config['leafmachine']['archival_component_detector']['detector_iteration'] = 'PREP_final'
st.session_state.config['leafmachine']['archival_component_detector']['detector_weights'] = 'best.pt'
elif ACD_version == "Version 2.2": #TODO update this to version 2.2
st.session_state.config['leafmachine']['archival_component_detector']['detector_type'] = 'Archival_Detector'
st.session_state.config['leafmachine']['archival_component_detector']['detector_version'] = 'PREP_final'
st.session_state.config['leafmachine']['archival_component_detector']['detector_iteration'] = 'PREP_final'
st.session_state.config['leafmachine']['archival_component_detector']['detector_weights'] = 'best.pt'
def content_processing_options():
st.write("---")
st.header('Processing Options')
col_processing_1, col_processing_2 = st.columns([2,2,])
with col_processing_1:
st.subheader('Compute Options')
st.session_state.config['leafmachine']['project']['num_workers'] = st.number_input("Number of CPU workers", value=st.session_state.config['leafmachine']['project'].get('num_workers', 1), disabled=False)
st.session_state.config['leafmachine']['project']['batch_size'] = st.number_input("Batch size", value=st.session_state.config['leafmachine']['project'].get('batch_size', 500), help='Sets the batch size for the LeafMachine2 cropping. If computer RAM is filled, lower this value to ~100.')
st.session_state.config['leafmachine']['project']['pdf_conversion_dpi'] = st.number_input("PDF conversion DPI", value=st.session_state.config['leafmachine']['project'].get('pdf_conversion_dpi', 100), help='DPI of the JPG created from the page of a PDF. 100 should be fine for most cases, but 200 or 300 might be better for large images.')
with col_processing_2:
st.subheader('Filename Prefix Handling')
st.session_state.config['leafmachine']['project']['prefix_removal'] = st.text_input("Remove prefix from catalog number", st.session_state.config['leafmachine']['project'].get('prefix_removal', ''),placeholder="e.g. MICH-V-")
st.session_state.config['leafmachine']['project']['suffix_removal'] = st.text_input("Remove suffix from catalog number", st.session_state.config['leafmachine']['project'].get('suffix_removal', ''),placeholder="e.g. _B")
st.session_state.config['leafmachine']['project']['catalog_numerical_only'] = st.checkbox("Require 'Catalog Number' to be numerical only", st.session_state.config['leafmachine']['project'].get('catalog_numerical_only', True))
### Logging and Image Validation - col_v1
st.write("---")
col_v1, col_v2 = st.columns(2)
with col_v1:
st.header('Logging and Image Validation')
option_check_illegal = st.checkbox("Check for illegal filenames", value=st.session_state.config['leafmachine']['do']['check_for_illegal_filenames'])
st.session_state.config['leafmachine']['do']['check_for_illegal_filenames'] = option_check_illegal
option_skip_vertical = st.checkbox("Skip vertical image requirement (e.g. horizontal PDFs)", value=st.session_state.config['leafmachine']['do']['skip_vertical'],help='LeafMachine2 label collage requires images to have vertical aspect ratios for stability. If your input images have a horizonatal aspect ratio, try skipping the vertical requirement first, look for strange behavior, and then reassess. If your image/PDFs are already closeups and you do not need the collage, then skipping the vertical requirement is the right choice.')
st.session_state.config['leafmachine']['do']['skip_vertical'] = option_skip_vertical
st.session_state.config['leafmachine']['do']['check_for_corrupt_images_make_vertical'] = st.checkbox("Check for corrupt images", st.session_state.config['leafmachine']['do'].get('check_for_corrupt_images_make_vertical', True),disabled=True)
st.session_state.config['leafmachine']['print']['verbose'] = st.checkbox("Print verbose", st.session_state.config['leafmachine']['print'].get('verbose', True))
st.session_state.config['leafmachine']['print']['optional_warnings'] = st.checkbox("Show optional warnings", st.session_state.config['leafmachine']['print'].get('optional_warnings', True))
log_level = st.session_state.config['leafmachine']['logging'].get('log_level', None)
log_level_display = log_level if log_level is not None else 'default'
selected_log_level = st.selectbox("Logging Level", ['default', 'DEBUG', 'INFO', 'WARNING', 'ERROR'], index=['default', 'DEBUG', 'INFO', 'WARNING', 'ERROR'].index(log_level_display))
if selected_log_level == 'default':
st.session_state.config['leafmachine']['logging']['log_level'] = None
else:
st.session_state.config['leafmachine']['logging']['log_level'] = selected_log_level
with col_v2:
print(f"Number of GPUs: {st.session_state.num_gpus}")
print(f"GPU Details: {st.session_state.gpu_dict}")
print(f"Total VRAM: {st.session_state.total_vram_gb} GB")
print(f"Capability Score: {st.session_state.capability_score}")
st.header('System GPU Information')
st.markdown(f"**Torch CUDA:** {torch.cuda.is_available()}")
st.markdown(f"**Number of GPUs:** {st.session_state.num_gpus}")
if st.session_state.num_gpus > 0:
st.markdown("**GPU Details:**")
for gpu_id, vram in st.session_state.gpu_dict.items():
st.text(f"{gpu_id}: {vram}")
st.markdown(f"**Total VRAM:** {st.session_state.total_vram_gb} GB")
st.markdown(f"**Capability Score:** {st.session_state.capability_score}")
else:
st.warning("No GPUs detected in the system.")
def content_tab_domain():
st.write("---")
st.header('Embeddings Database')
col_emb_1, col_emb_2 = st.columns([4,2])
with col_emb_1:
st.markdown(
"""
VoucherVision includes the option of using domain knowledge inside of the dynamically generated prompts. The OCR text is queried against a database of existing label transcriptions. The most similar existing transcriptions act as an example of what the LLM should emulate and are shown to the LLM as JSON objects. VoucherVision uses cosine similarity search to return the most similar existing transcription.
- Note: Using domain knowledge may increase the chance that foreign text is included in the final transcription
- Disabling this feature will show the LLM multiple examples of an empty JSON skeleton structure instead
- Enabling this option requires a GPU with at least 8GB of VRAM
- The domain knowledge files can be located in the directory "../VoucherVision/domain_knowledge". On first run the embeddings database must be created, which takes time. If the database creation runs each time you use VoucherVision, then something is wrong.
"""
)
st.write(f"Domain Knowledge is only available for the following prompts:")
for available_prompts in ModelMaps.PROMPTS_THAT_NEED_DOMAIN_KNOWLEDGE:
st.markdown(f"- {available_prompts}")
if st.session_state.config['leafmachine']['project']['prompt_version'] in ModelMaps.PROMPTS_THAT_NEED_DOMAIN_KNOWLEDGE:
st.session_state.config['leafmachine']['project']['use_domain_knowledge'] = st.checkbox("Use domain knowledge", True, disabled=True)
else:
st.session_state.config['leafmachine']['project']['use_domain_knowledge'] = st.checkbox("Use domain knowledge", False, disabled=True)
st.write("")
if st.session_state.config['leafmachine']['project']['use_domain_knowledge']:
st.session_state.config['leafmachine']['project']['embeddings_database_name'] = st.text_input("Embeddings database name (only use underscores)", st.session_state.config['leafmachine']['project'].get('embeddings_database_name', ''))
st.session_state.config['leafmachine']['project']['build_new_embeddings_database'] = st.checkbox("Build *new* embeddings database", st.session_state.config['leafmachine']['project'].get('build_new_embeddings_database', False))
st.session_state.config['leafmachine']['project']['path_to_domain_knowledge_xlsx'] = st.text_input("Path to domain knowledge CSV file (will be used to create new embeddings database)", st.session_state.config['leafmachine']['project'].get('path_to_domain_knowledge_xlsx', ''))
else:
st.session_state.config['leafmachine']['project']['embeddings_database_name'] = st.text_input("Embeddings database name (only use underscores)", st.session_state.config['leafmachine']['project'].get('embeddings_database_name', ''), disabled=True)
st.session_state.config['leafmachine']['project']['build_new_embeddings_database'] = st.checkbox("Build *new* embeddings database", st.session_state.config['leafmachine']['project'].get('build_new_embeddings_database', False), disabled=True)
st.session_state.config['leafmachine']['project']['path_to_domain_knowledge_xlsx'] = st.text_input("Path to domain knowledge CSV file (will be used to create new embeddings database)", st.session_state.config['leafmachine']['project'].get('path_to_domain_knowledge_xlsx', ''), disabled=True)
def content_space_saver():
st.write("---")
st.subheader("Space Saving Options")
col_ss_1, col_ss_2 = st.columns([2,2])
with col_ss_1:
st.write("Several folders are created and populated with data during the VoucherVision transcription process.")
st.write("Below are several options that will allow you to automatically delete temporary files that you may not need for everyday operations.")
st.write("VoucherVision creates the following folders. Folders marked with a :star: are required if you want to use VoucherVisionEditor for quality control.")
st.write("`../[Run Name]/Archival_Components`")
st.write("`../[Run Name]/Config_File`")
st.write("`../[Run Name]/Cropped_Images` :star:")
st.write("`../[Run Name]/Logs`")
st.write("`../[Run Name]/Original_Images` :star:")
st.write("`../[Run Name]/Transcription` :star:")
with col_ss_2:
st.session_state.config['leafmachine']['project']['delete_temps_keep_VVE'] = st.checkbox("Delete Temporary Files (KEEP files required for VoucherVisionEditor)", st.session_state.config['leafmachine']['project'].get('delete_temps_keep_VVE', False))
st.session_state.config['leafmachine']['project']['delete_all_temps'] = st.checkbox("Keep only the final transcription file", st.session_state.config['leafmachine']['project'].get('delete_all_temps', False),help="*WARNING:* This limits your ability to do quality assurance. This will delete all folders created by VoucherVision, leaving only the `transcription.xlsx` file.")
#################################################################################################################################################
# render_expense_report_summary #################################################################################################################
#################################################################################################################################################
def render_expense_report_summary():
expense_summary = st.session_state.expense_summary
expense_report = st.session_state.expense_report
st.header('Expense Report Summary')
if not expense_summary:
st.warning('No expense report data available.')
else:
st.metric(label="Total Cost", value=f"${round(expense_summary['total_cost_sum'], 4):,}")
col1, col2 = st.columns(2)
# Run count and total costs
with col1:
st.metric(label="Run Count", value=expense_summary['run_count'])
st.metric(label="Tokens In", value=f"{expense_summary['tokens_in_sum']:,}")
# Token information
with col2:
st.metric(label="Total Images", value=expense_summary['n_images_sum'])
st.metric(label="Tokens Out", value=f"{expense_summary['tokens_out_sum']:,}")
# Calculate cost proportion per image for each API version
st.subheader('Average Cost per Image by API Version')
cost_labels = []
cost_values = []
total_images = 0
cost_per_image_dict = {}
# Iterate through the expense report to accumulate costs and image counts
for index, row in expense_report.iterrows():
api_version = row['api_version']
total_cost = row['total_cost']
n_images = row['n_images']
total_images += n_images # Keep track of total images processed
if api_version not in cost_per_image_dict:
cost_per_image_dict[api_version] = {'total_cost': 0, 'n_images': 0}
cost_per_image_dict[api_version]['total_cost'] += total_cost
cost_per_image_dict[api_version]['n_images'] += n_images
api_versions = list(cost_per_image_dict.keys())
colors = [ModelMaps.COLORS_EXPENSE_REPORT[version] if version in ModelMaps.COLORS_EXPENSE_REPORT else '#DDDDDD' for version in api_versions]
# Calculate the cost per image for each API version
for version, cost_data in cost_per_image_dict.items():
total_cost = cost_data['total_cost']
n_images = cost_data['n_images']
# Calculate the cost per image for this version
cost_per_image = total_cost / n_images if n_images > 0 else 0
cost_labels.append(version)
cost_values.append(cost_per_image)
# Generate the pie chart
cost_pie_chart = go.Figure(data=[go.Pie(labels=cost_labels, values=cost_values, hole=.3)])
# Update traces for custom text in hoverinfo, displaying cost with a dollar sign and two decimal places
cost_pie_chart.update_traces(
marker=dict(colors=colors),
text=[f"${value:.4f}" for value in cost_values], # Formats the cost as a string with a dollar sign and two decimals
textinfo='percent+label',
hoverinfo='label+percent+text' # Adds custom text (formatted cost) to the hover information
)
st.plotly_chart(cost_pie_chart, use_container_width=True)
st.subheader('Proportion of Total Cost by API Version')
cost_labels = []
cost_proportions = []
total_cost_by_version = {}
# Sum the total cost for each API version
for index, row in expense_report.iterrows():
api_version = row['api_version']
total_cost = row['total_cost']
if api_version not in total_cost_by_version:
total_cost_by_version[api_version] = 0
total_cost_by_version[api_version] += total_cost
# Calculate the combined total cost for all versions
combined_total_cost = sum(total_cost_by_version.values())
# Calculate the proportion of total cost for each API version
for version, total_cost in total_cost_by_version.items():
proportion = (total_cost / combined_total_cost) * 100 if combined_total_cost > 0 else 0
cost_labels.append(version)
cost_proportions.append(proportion)
# Generate the pie chart
cost_pie_chart = go.Figure(data=[go.Pie(labels=cost_labels, values=cost_proportions, hole=.3)])
# Update traces for custom text in hoverinfo
cost_pie_chart.update_traces(
marker=dict(colors=colors),
text=[f"${cost:.4f}" for cost in total_cost_by_version.values()], # This will format the cost to 2 decimal places
textinfo='percent+label',
hoverinfo='label+percent+text' # This tells Plotly to show the label, percent, and custom text (cost) on hover
)
st.plotly_chart(cost_pie_chart, use_container_width=True)
# API version usage percentages pie chart
st.subheader('Runs by API Version')
api_versions = list(expense_summary['api_version_percentages'].keys())
percentages = [expense_summary['api_version_percentages'][version] for version in api_versions]
pie_chart = go.Figure(data=[go.Pie(labels=api_versions, values=percentages, hole=.3)])
pie_chart.update_layout(margin=dict(t=0, b=0, l=0, r=0))
pie_chart.update_traces(marker=dict(colors=colors),)
st.plotly_chart(pie_chart, use_container_width=True)
def content_less_used():
st.write('---')
st.write(':octagonal_sign: ***NOTE:*** Settings below are not relevant for most projects. Some settings below may not be reflected in saved settings files and would need to be set each time.')
#################################################################################################################################################
# Sidebar #######################################################################################################################################
#################################################################################################################################################
def sidebar_content():
# st.page_link(os.path.join(os.path.dirname(__file__),'app.py'), label="Home", icon="π ")
# st.page_link(os.path.join(os.path.dirname(__file__),"pages","prompt_builder.py"), label="Prompt Builder", icon="π§")
# st.page_link("pages/page_2.py", label="Page 2", icon="2οΈβ£", disabled=True)
# st.page_link("http://www.google.com", label="Google", icon="π")
if not os.path.exists(os.path.join(st.session_state.dir_home,'expense_report')):
validate_dir(os.path.join(st.session_state.dir_home,'expense_report'))
expense_report_path = os.path.join(st.session_state.dir_home, 'expense_report', 'expense_report.csv')
if os.path.exists(expense_report_path):
# File exists, proceed with summarization
st.session_state.expense_summary, st.session_state.expense_report = summarize_expense_report(expense_report_path)
render_expense_report_summary()
else:
# File does not exist, handle this case appropriately
# For example, you could set the session state variables to None or an empty value
st.session_state.expense_summary, st.session_state.expense_report = None, None
st.header('Expense Report Summary')
st.write('Available after first run...')
#################################################################################################################################################
# Routing Function ##############################################################################################################################
#################################################################################################################################################
def main():
with st.sidebar:
sidebar_content()
# Main App
content_header()
col_input, col_gallery = st.columns([4,8])
content_project_settings(col_input)
content_input_images(col_input, col_gallery)
col3, col4 = st.columns([1,1])
with col3:
content_prompt_and_llm_version()
with col4:
content_api_check()
content_ocr_method()
content_collage_overlay()
content_llm_cost()
content_processing_options()
content_less_used()
with st.expander("View additional settings"):
content_archival_components()
content_space_saver()
#################################################################################################################################################
# Main ##########################################################################################################################################
#################################################################################################################################################
if st.session_state['is_hf']:
# if st.session_state.proceed_to_build_llm_prompt:
# build_LLM_prompt_config()
if st.session_state.proceed_to_main:
main()
else:
if not st.session_state.private_file:
create_private_file()
# elif st.session_state.proceed_to_build_llm_prompt:
# build_LLM_prompt_config()
elif st.session_state.proceed_to_private and not st.session_state['is_hf']:
create_private_file()
elif st.session_state.proceed_to_main:
main()
|