Spaces:
Running
Running
phyloforfun
commited on
Commit
•
87c3140
1
Parent(s):
34de6b9
Add application file
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- .gitignore +199 -0
- LICENSE +674 -0
- VoucherVision_Reference.yaml +103 -0
- __init__.py +0 -0
- api_cost/api_cost.yaml +9 -0
- app.py +1344 -0
- bin/version.yml +2 -0
- create_desktop_shortcut.py +69 -0
- custom_prompts/required_structure.yaml +62 -0
- custom_prompts/version_2.yaml +229 -0
- custom_prompts/version_2_OSU.yaml +230 -0
- demo/ba/ba.jpg +3 -0
- demo/ba/ba.png +3 -0
- demo/ba/ba2.png +3 -0
- demo/demo_gallery/NY_1928185102_Heliotropiaceae_Heliotropium_indicum.jpg +3 -0
- demo/demo_gallery/SMF_3046042583_Ebenaceae_Diospyros_mespiliformis.jpg +3 -0
- demo/demo_gallery/UM_1807475718_Monimiaceae_Hedycarya_parvifolia.jpg +3 -0
- demo/demo_gallery/UM_1915455196_Cardiopteridaceae_Citronella_sarmentosa.jpg +3 -0
- demo/demo_images/UM_1807464860_Phellinaceae_Phelline_dumbeensis.jpg +3 -0
- demo/img/expense_report.PNG +3 -0
- demo/img/prompt_1.PNG +3 -0
- demo/img/prompt_2.PNG +3 -0
- demo/img/prompt_3.PNG +3 -0
- demo/img/prompt_4.PNG +3 -0
- demo/img/prompt_5.PNG +3 -0
- demo/img/validation_1.PNG +3 -0
- demo/img/validation_gpt.PNG +3 -0
- demo/img/validation_gpu.PNG +3 -0
- demo/img/validation_palm.PNG +3 -0
- domain_knowledge/SLTP_UM_AllAsiaMinimalInRegion.xlsx +0 -0
- img/icon.ico +0 -0
- img/icon.jpg +3 -0
- img/icon2.ico +0 -0
- img/logo.png +3 -0
- requirements.txt +34 -0
- run_VoucherVision.py +31 -0
- vouchervision/LLM_Falcon.py +112 -0
- vouchervision/LLM_PaLM.py +209 -0
- vouchervision/LLM_chatGPT_3_5.py +420 -0
- vouchervision/LM2_logger.py +117 -0
- vouchervision/LeafMachine2_Config_Builder.py +246 -0
- vouchervision/OCR_google_cloud_vision.py +107 -0
- vouchervision/PaLM_example_script.py +70 -0
- vouchervision/VoucherVision_Config_Builder.py +576 -0
- vouchervision/component_detector/LICENSE +674 -0
- vouchervision/component_detector/__init__.py +0 -0
- vouchervision/component_detector/armature_processing.py +1047 -0
- vouchervision/component_detector/color_profiles/ColorProfile__LANDMARK.csv +9 -0
- vouchervision/component_detector/color_profiles/ColorProfile__LANDMARK_ARM.csv +4 -0
- vouchervision/component_detector/color_profiles/ColorProfile__PLANT.csv +11 -0
.gitignore
ADDED
@@ -0,0 +1,199 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Files
|
2 |
+
PRIVATE_DATA.yaml
|
3 |
+
LeafMachine2_TRAINING_ONLY.yaml
|
4 |
+
LeafMachine2_TEMPLATE.yaml
|
5 |
+
LeafMachine2_WW.yaml
|
6 |
+
yolov8x-pose.pt
|
7 |
+
yolov8n.pt
|
8 |
+
*PRIVATE_DATA*
|
9 |
+
|
10 |
+
|
11 |
+
# Dirs
|
12 |
+
demo/demo_output/*
|
13 |
+
demo/demo_configs/*
|
14 |
+
wandb/
|
15 |
+
venv_LM2_linux/
|
16 |
+
venv_LM2_l/
|
17 |
+
venv_LM2_310/
|
18 |
+
venv_LM2_38/
|
19 |
+
venv_LM2/
|
20 |
+
venv_VV/
|
21 |
+
tests/
|
22 |
+
.vscode/
|
23 |
+
runs/
|
24 |
+
KP_Test/
|
25 |
+
|
26 |
+
# VV Specific
|
27 |
+
.streamlit*/
|
28 |
+
demo/demo_output/*
|
29 |
+
demo/validation_configs/*
|
30 |
+
/bin/*
|
31 |
+
!/bin/version.yml
|
32 |
+
release*
|
33 |
+
expense_report/*
|
34 |
+
/custom_prompts/*
|
35 |
+
!/custom_prompts/required_structure.yaml
|
36 |
+
!/custom_prompts/version_2.yaml
|
37 |
+
!/custom_prompts/version_2_OSU.yaml
|
38 |
+
leafmachine2/*/.gitignore
|
39 |
+
|
40 |
+
/bin/*
|
41 |
+
!/bin/version.yml
|
42 |
+
|
43 |
+
vouchervision/release_manager/
|
44 |
+
|
45 |
+
vouchervision/component_detector/datasets/
|
46 |
+
vouchervision/component_detector/wandb/
|
47 |
+
vouchervision/component_detector/runs/
|
48 |
+
vouchervision/component_detector/architecture/
|
49 |
+
vouchervision/component_detector/yolov5x6.pt
|
50 |
+
|
51 |
+
vouchervision/instructor-xl/
|
52 |
+
vouchervision/instructor-embedding/
|
53 |
+
|
54 |
+
vouchervision/SLTP_*
|
55 |
+
|
56 |
+
vouchervision/gradio_ocr.py
|
57 |
+
vouchervision/build_dataset.py
|
58 |
+
vouchervision/evaluate_LLM_predictions.py
|
59 |
+
vouchervision/QLoRa__x__GPT-NeoX-20B.py
|
60 |
+
vouchervision/QLoRa_GPT_NeoX_20B.py
|
61 |
+
vouchervision/run_VoucherVision_gradio.py
|
62 |
+
vouchervision/stratify_groundtruth_transcriptions.py
|
63 |
+
|
64 |
+
leafmachine2/component_detector/runs/
|
65 |
+
leafmachine2/component_detector/architecture/
|
66 |
+
|
67 |
+
|
68 |
+
# Byte-compiled / optimized / DLL files
|
69 |
+
__pycache__/
|
70 |
+
*.py[cod]
|
71 |
+
*$py.class
|
72 |
+
|
73 |
+
# C extensions
|
74 |
+
*.so
|
75 |
+
|
76 |
+
# Distribution / packaging
|
77 |
+
.Python
|
78 |
+
build/
|
79 |
+
develop-eggs/
|
80 |
+
dist/
|
81 |
+
downloads/
|
82 |
+
eggs/
|
83 |
+
.eggs/
|
84 |
+
lib/
|
85 |
+
lib64/
|
86 |
+
parts/
|
87 |
+
sdist/
|
88 |
+
var/
|
89 |
+
wheels/
|
90 |
+
pip-wheel-metadata/
|
91 |
+
share/python-wheels/
|
92 |
+
*.egg-info/
|
93 |
+
.installed.cfg
|
94 |
+
*.egg
|
95 |
+
MANIFEST
|
96 |
+
|
97 |
+
# PyInstaller
|
98 |
+
# Usually these files are written by a python script from a template
|
99 |
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
100 |
+
*.manifest
|
101 |
+
*.spec
|
102 |
+
|
103 |
+
# Installer logs
|
104 |
+
pip-log.txt
|
105 |
+
pip-delete-this-directory.txt
|
106 |
+
|
107 |
+
# Unit test / coverage reports
|
108 |
+
htmlcov/
|
109 |
+
.tox/
|
110 |
+
.nox/
|
111 |
+
.coverage
|
112 |
+
.coverage.*
|
113 |
+
.cache
|
114 |
+
nosetests.xml
|
115 |
+
coverage.xml
|
116 |
+
*.cover
|
117 |
+
*.py,cover
|
118 |
+
.hypothesis/
|
119 |
+
.pytest_cache/
|
120 |
+
|
121 |
+
# Translations
|
122 |
+
*.mo
|
123 |
+
*.pot
|
124 |
+
|
125 |
+
# Django stuff:
|
126 |
+
*.log
|
127 |
+
local_settings.py
|
128 |
+
db.sqlite3
|
129 |
+
db.sqlite3-journal
|
130 |
+
|
131 |
+
# Flask stuff:
|
132 |
+
instance/
|
133 |
+
.webassets-cache
|
134 |
+
|
135 |
+
# Scrapy stuff:
|
136 |
+
.scrapy
|
137 |
+
|
138 |
+
# Sphinx documentation
|
139 |
+
docs/_build/
|
140 |
+
|
141 |
+
# PyBuilder
|
142 |
+
target/
|
143 |
+
|
144 |
+
# Jupyter Notebook
|
145 |
+
.ipynb_checkpoints
|
146 |
+
|
147 |
+
# IPython
|
148 |
+
profile_default/
|
149 |
+
ipython_config.py
|
150 |
+
|
151 |
+
# pyenv
|
152 |
+
.python-version
|
153 |
+
|
154 |
+
# pipenv
|
155 |
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
156 |
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
157 |
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
158 |
+
# install all needed dependencies.
|
159 |
+
#Pipfile.lock
|
160 |
+
|
161 |
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
|
162 |
+
__pypackages__/
|
163 |
+
|
164 |
+
# Celery stuff
|
165 |
+
celerybeat-schedule
|
166 |
+
celerybeat.pid
|
167 |
+
|
168 |
+
# SageMath parsed files
|
169 |
+
*.sage.py
|
170 |
+
|
171 |
+
# Environments
|
172 |
+
.env
|
173 |
+
.venv
|
174 |
+
env/
|
175 |
+
venv/
|
176 |
+
venv_LM2/
|
177 |
+
venv_LM2_linux/
|
178 |
+
ENV/
|
179 |
+
env.bak/
|
180 |
+
venv.bak/
|
181 |
+
|
182 |
+
# Spyder project settings
|
183 |
+
.spyderproject
|
184 |
+
.spyproject
|
185 |
+
|
186 |
+
# Rope project settings
|
187 |
+
.ropeproject
|
188 |
+
|
189 |
+
# mkdocs documentation
|
190 |
+
/site
|
191 |
+
|
192 |
+
# mypy
|
193 |
+
.mypy_cache/
|
194 |
+
.dmypy.json
|
195 |
+
dmypy.json
|
196 |
+
|
197 |
+
# Pyre type checker
|
198 |
+
.pyre/
|
199 |
+
VoucherVision.yaml
|
LICENSE
ADDED
@@ -0,0 +1,674 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
GNU GENERAL PUBLIC LICENSE
|
2 |
+
Version 3, 29 June 2007
|
3 |
+
|
4 |
+
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
5 |
+
Everyone is permitted to copy and distribute verbatim copies
|
6 |
+
of this license document, but changing it is not allowed.
|
7 |
+
|
8 |
+
Preamble
|
9 |
+
|
10 |
+
The GNU General Public License is a free, copyleft license for
|
11 |
+
software and other kinds of works.
|
12 |
+
|
13 |
+
The licenses for most software and other practical works are designed
|
14 |
+
to take away your freedom to share and change the works. By contrast,
|
15 |
+
the GNU General Public License is intended to guarantee your freedom to
|
16 |
+
share and change all versions of a program--to make sure it remains free
|
17 |
+
software for all its users. We, the Free Software Foundation, use the
|
18 |
+
GNU General Public License for most of our software; it applies also to
|
19 |
+
any other work released this way by its authors. You can apply it to
|
20 |
+
your programs, too.
|
21 |
+
|
22 |
+
When we speak of free software, we are referring to freedom, not
|
23 |
+
price. Our General Public Licenses are designed to make sure that you
|
24 |
+
have the freedom to distribute copies of free software (and charge for
|
25 |
+
them if you wish), that you receive source code or can get it if you
|
26 |
+
want it, that you can change the software or use pieces of it in new
|
27 |
+
free programs, and that you know you can do these things.
|
28 |
+
|
29 |
+
To protect your rights, we need to prevent others from denying you
|
30 |
+
these rights or asking you to surrender the rights. Therefore, you have
|
31 |
+
certain responsibilities if you distribute copies of the software, or if
|
32 |
+
you modify it: responsibilities to respect the freedom of others.
|
33 |
+
|
34 |
+
For example, if you distribute copies of such a program, whether
|
35 |
+
gratis or for a fee, you must pass on to the recipients the same
|
36 |
+
freedoms that you received. You must make sure that they, too, receive
|
37 |
+
or can get the source code. And you must show them these terms so they
|
38 |
+
know their rights.
|
39 |
+
|
40 |
+
Developers that use the GNU GPL protect your rights with two steps:
|
41 |
+
(1) assert copyright on the software, and (2) offer you this License
|
42 |
+
giving you legal permission to copy, distribute and/or modify it.
|
43 |
+
|
44 |
+
For the developers' and authors' protection, the GPL clearly explains
|
45 |
+
that there is no warranty for this free software. For both users' and
|
46 |
+
authors' sake, the GPL requires that modified versions be marked as
|
47 |
+
changed, so that their problems will not be attributed erroneously to
|
48 |
+
authors of previous versions.
|
49 |
+
|
50 |
+
Some devices are designed to deny users access to install or run
|
51 |
+
modified versions of the software inside them, although the manufacturer
|
52 |
+
can do so. This is fundamentally incompatible with the aim of
|
53 |
+
protecting users' freedom to change the software. The systematic
|
54 |
+
pattern of such abuse occurs in the area of products for individuals to
|
55 |
+
use, which is precisely where it is most unacceptable. Therefore, we
|
56 |
+
have designed this version of the GPL to prohibit the practice for those
|
57 |
+
products. If such problems arise substantially in other domains, we
|
58 |
+
stand ready to extend this provision to those domains in future versions
|
59 |
+
of the GPL, as needed to protect the freedom of users.
|
60 |
+
|
61 |
+
Finally, every program is threatened constantly by software patents.
|
62 |
+
States should not allow patents to restrict development and use of
|
63 |
+
software on general-purpose computers, but in those that do, we wish to
|
64 |
+
avoid the special danger that patents applied to a free program could
|
65 |
+
make it effectively proprietary. To prevent this, the GPL assures that
|
66 |
+
patents cannot be used to render the program non-free.
|
67 |
+
|
68 |
+
The precise terms and conditions for copying, distribution and
|
69 |
+
modification follow.
|
70 |
+
|
71 |
+
TERMS AND CONDITIONS
|
72 |
+
|
73 |
+
0. Definitions.
|
74 |
+
|
75 |
+
"This License" refers to version 3 of the GNU General Public License.
|
76 |
+
|
77 |
+
"Copyright" also means copyright-like laws that apply to other kinds of
|
78 |
+
works, such as semiconductor masks.
|
79 |
+
|
80 |
+
"The Program" refers to any copyrightable work licensed under this
|
81 |
+
License. Each licensee is addressed as "you". "Licensees" and
|
82 |
+
"recipients" may be individuals or organizations.
|
83 |
+
|
84 |
+
To "modify" a work means to copy from or adapt all or part of the work
|
85 |
+
in a fashion requiring copyright permission, other than the making of an
|
86 |
+
exact copy. The resulting work is called a "modified version" of the
|
87 |
+
earlier work or a work "based on" the earlier work.
|
88 |
+
|
89 |
+
A "covered work" means either the unmodified Program or a work based
|
90 |
+
on the Program.
|
91 |
+
|
92 |
+
To "propagate" a work means to do anything with it that, without
|
93 |
+
permission, would make you directly or secondarily liable for
|
94 |
+
infringement under applicable copyright law, except executing it on a
|
95 |
+
computer or modifying a private copy. Propagation includes copying,
|
96 |
+
distribution (with or without modification), making available to the
|
97 |
+
public, and in some countries other activities as well.
|
98 |
+
|
99 |
+
To "convey" a work means any kind of propagation that enables other
|
100 |
+
parties to make or receive copies. Mere interaction with a user through
|
101 |
+
a computer network, with no transfer of a copy, is not conveying.
|
102 |
+
|
103 |
+
An interactive user interface displays "Appropriate Legal Notices"
|
104 |
+
to the extent that it includes a convenient and prominently visible
|
105 |
+
feature that (1) displays an appropriate copyright notice, and (2)
|
106 |
+
tells the user that there is no warranty for the work (except to the
|
107 |
+
extent that warranties are provided), that licensees may convey the
|
108 |
+
work under this License, and how to view a copy of this License. If
|
109 |
+
the interface presents a list of user commands or options, such as a
|
110 |
+
menu, a prominent item in the list meets this criterion.
|
111 |
+
|
112 |
+
1. Source Code.
|
113 |
+
|
114 |
+
The "source code" for a work means the preferred form of the work
|
115 |
+
for making modifications to it. "Object code" means any non-source
|
116 |
+
form of a work.
|
117 |
+
|
118 |
+
A "Standard Interface" means an interface that either is an official
|
119 |
+
standard defined by a recognized standards body, or, in the case of
|
120 |
+
interfaces specified for a particular programming language, one that
|
121 |
+
is widely used among developers working in that language.
|
122 |
+
|
123 |
+
The "System Libraries" of an executable work include anything, other
|
124 |
+
than the work as a whole, that (a) is included in the normal form of
|
125 |
+
packaging a Major Component, but which is not part of that Major
|
126 |
+
Component, and (b) serves only to enable use of the work with that
|
127 |
+
Major Component, or to implement a Standard Interface for which an
|
128 |
+
implementation is available to the public in source code form. A
|
129 |
+
"Major Component", in this context, means a major essential component
|
130 |
+
(kernel, window system, and so on) of the specific operating system
|
131 |
+
(if any) on which the executable work runs, or a compiler used to
|
132 |
+
produce the work, or an object code interpreter used to run it.
|
133 |
+
|
134 |
+
The "Corresponding Source" for a work in object code form means all
|
135 |
+
the source code needed to generate, install, and (for an executable
|
136 |
+
work) run the object code and to modify the work, including scripts to
|
137 |
+
control those activities. However, it does not include the work's
|
138 |
+
System Libraries, or general-purpose tools or generally available free
|
139 |
+
programs which are used unmodified in performing those activities but
|
140 |
+
which are not part of the work. For example, Corresponding Source
|
141 |
+
includes interface definition files associated with source files for
|
142 |
+
the work, and the source code for shared libraries and dynamically
|
143 |
+
linked subprograms that the work is specifically designed to require,
|
144 |
+
such as by intimate data communication or control flow between those
|
145 |
+
subprograms and other parts of the work.
|
146 |
+
|
147 |
+
The Corresponding Source need not include anything that users
|
148 |
+
can regenerate automatically from other parts of the Corresponding
|
149 |
+
Source.
|
150 |
+
|
151 |
+
The Corresponding Source for a work in source code form is that
|
152 |
+
same work.
|
153 |
+
|
154 |
+
2. Basic Permissions.
|
155 |
+
|
156 |
+
All rights granted under this License are granted for the term of
|
157 |
+
copyright on the Program, and are irrevocable provided the stated
|
158 |
+
conditions are met. This License explicitly affirms your unlimited
|
159 |
+
permission to run the unmodified Program. The output from running a
|
160 |
+
covered work is covered by this License only if the output, given its
|
161 |
+
content, constitutes a covered work. This License acknowledges your
|
162 |
+
rights of fair use or other equivalent, as provided by copyright law.
|
163 |
+
|
164 |
+
You may make, run and propagate covered works that you do not
|
165 |
+
convey, without conditions so long as your license otherwise remains
|
166 |
+
in force. You may convey covered works to others for the sole purpose
|
167 |
+
of having them make modifications exclusively for you, or provide you
|
168 |
+
with facilities for running those works, provided that you comply with
|
169 |
+
the terms of this License in conveying all material for which you do
|
170 |
+
not control copyright. Those thus making or running the covered works
|
171 |
+
for you must do so exclusively on your behalf, under your direction
|
172 |
+
and control, on terms that prohibit them from making any copies of
|
173 |
+
your copyrighted material outside their relationship with you.
|
174 |
+
|
175 |
+
Conveying under any other circumstances is permitted solely under
|
176 |
+
the conditions stated below. Sublicensing is not allowed; section 10
|
177 |
+
makes it unnecessary.
|
178 |
+
|
179 |
+
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
180 |
+
|
181 |
+
No covered work shall be deemed part of an effective technological
|
182 |
+
measure under any applicable law fulfilling obligations under article
|
183 |
+
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
184 |
+
similar laws prohibiting or restricting circumvention of such
|
185 |
+
measures.
|
186 |
+
|
187 |
+
When you convey a covered work, you waive any legal power to forbid
|
188 |
+
circumvention of technological measures to the extent such circumvention
|
189 |
+
is effected by exercising rights under this License with respect to
|
190 |
+
the covered work, and you disclaim any intention to limit operation or
|
191 |
+
modification of the work as a means of enforcing, against the work's
|
192 |
+
users, your or third parties' legal rights to forbid circumvention of
|
193 |
+
technological measures.
|
194 |
+
|
195 |
+
4. Conveying Verbatim Copies.
|
196 |
+
|
197 |
+
You may convey verbatim copies of the Program's source code as you
|
198 |
+
receive it, in any medium, provided that you conspicuously and
|
199 |
+
appropriately publish on each copy an appropriate copyright notice;
|
200 |
+
keep intact all notices stating that this License and any
|
201 |
+
non-permissive terms added in accord with section 7 apply to the code;
|
202 |
+
keep intact all notices of the absence of any warranty; and give all
|
203 |
+
recipients a copy of this License along with the Program.
|
204 |
+
|
205 |
+
You may charge any price or no price for each copy that you convey,
|
206 |
+
and you may offer support or warranty protection for a fee.
|
207 |
+
|
208 |
+
5. Conveying Modified Source Versions.
|
209 |
+
|
210 |
+
You may convey a work based on the Program, or the modifications to
|
211 |
+
produce it from the Program, in the form of source code under the
|
212 |
+
terms of section 4, provided that you also meet all of these conditions:
|
213 |
+
|
214 |
+
a) The work must carry prominent notices stating that you modified
|
215 |
+
it, and giving a relevant date.
|
216 |
+
|
217 |
+
b) The work must carry prominent notices stating that it is
|
218 |
+
released under this License and any conditions added under section
|
219 |
+
7. This requirement modifies the requirement in section 4 to
|
220 |
+
"keep intact all notices".
|
221 |
+
|
222 |
+
c) You must license the entire work, as a whole, under this
|
223 |
+
License to anyone who comes into possession of a copy. This
|
224 |
+
License will therefore apply, along with any applicable section 7
|
225 |
+
additional terms, to the whole of the work, and all its parts,
|
226 |
+
regardless of how they are packaged. This License gives no
|
227 |
+
permission to license the work in any other way, but it does not
|
228 |
+
invalidate such permission if you have separately received it.
|
229 |
+
|
230 |
+
d) If the work has interactive user interfaces, each must display
|
231 |
+
Appropriate Legal Notices; however, if the Program has interactive
|
232 |
+
interfaces that do not display Appropriate Legal Notices, your
|
233 |
+
work need not make them do so.
|
234 |
+
|
235 |
+
A compilation of a covered work with other separate and independent
|
236 |
+
works, which are not by their nature extensions of the covered work,
|
237 |
+
and which are not combined with it such as to form a larger program,
|
238 |
+
in or on a volume of a storage or distribution medium, is called an
|
239 |
+
"aggregate" if the compilation and its resulting copyright are not
|
240 |
+
used to limit the access or legal rights of the compilation's users
|
241 |
+
beyond what the individual works permit. Inclusion of a covered work
|
242 |
+
in an aggregate does not cause this License to apply to the other
|
243 |
+
parts of the aggregate.
|
244 |
+
|
245 |
+
6. Conveying Non-Source Forms.
|
246 |
+
|
247 |
+
You may convey a covered work in object code form under the terms
|
248 |
+
of sections 4 and 5, provided that you also convey the
|
249 |
+
machine-readable Corresponding Source under the terms of this License,
|
250 |
+
in one of these ways:
|
251 |
+
|
252 |
+
a) Convey the object code in, or embodied in, a physical product
|
253 |
+
(including a physical distribution medium), accompanied by the
|
254 |
+
Corresponding Source fixed on a durable physical medium
|
255 |
+
customarily used for software interchange.
|
256 |
+
|
257 |
+
b) Convey the object code in, or embodied in, a physical product
|
258 |
+
(including a physical distribution medium), accompanied by a
|
259 |
+
written offer, valid for at least three years and valid for as
|
260 |
+
long as you offer spare parts or customer support for that product
|
261 |
+
model, to give anyone who possesses the object code either (1) a
|
262 |
+
copy of the Corresponding Source for all the software in the
|
263 |
+
product that is covered by this License, on a durable physical
|
264 |
+
medium customarily used for software interchange, for a price no
|
265 |
+
more than your reasonable cost of physically performing this
|
266 |
+
conveying of source, or (2) access to copy the
|
267 |
+
Corresponding Source from a network server at no charge.
|
268 |
+
|
269 |
+
c) Convey individual copies of the object code with a copy of the
|
270 |
+
written offer to provide the Corresponding Source. This
|
271 |
+
alternative is allowed only occasionally and noncommercially, and
|
272 |
+
only if you received the object code with such an offer, in accord
|
273 |
+
with subsection 6b.
|
274 |
+
|
275 |
+
d) Convey the object code by offering access from a designated
|
276 |
+
place (gratis or for a charge), and offer equivalent access to the
|
277 |
+
Corresponding Source in the same way through the same place at no
|
278 |
+
further charge. You need not require recipients to copy the
|
279 |
+
Corresponding Source along with the object code. If the place to
|
280 |
+
copy the object code is a network server, the Corresponding Source
|
281 |
+
may be on a different server (operated by you or a third party)
|
282 |
+
that supports equivalent copying facilities, provided you maintain
|
283 |
+
clear directions next to the object code saying where to find the
|
284 |
+
Corresponding Source. Regardless of what server hosts the
|
285 |
+
Corresponding Source, you remain obligated to ensure that it is
|
286 |
+
available for as long as needed to satisfy these requirements.
|
287 |
+
|
288 |
+
e) Convey the object code using peer-to-peer transmission, provided
|
289 |
+
you inform other peers where the object code and Corresponding
|
290 |
+
Source of the work are being offered to the general public at no
|
291 |
+
charge under subsection 6d.
|
292 |
+
|
293 |
+
A separable portion of the object code, whose source code is excluded
|
294 |
+
from the Corresponding Source as a System Library, need not be
|
295 |
+
included in conveying the object code work.
|
296 |
+
|
297 |
+
A "User Product" is either (1) a "consumer product", which means any
|
298 |
+
tangible personal property which is normally used for personal, family,
|
299 |
+
or household purposes, or (2) anything designed or sold for incorporation
|
300 |
+
into a dwelling. In determining whether a product is a consumer product,
|
301 |
+
doubtful cases shall be resolved in favor of coverage. For a particular
|
302 |
+
product received by a particular user, "normally used" refers to a
|
303 |
+
typical or common use of that class of product, regardless of the status
|
304 |
+
of the particular user or of the way in which the particular user
|
305 |
+
actually uses, or expects or is expected to use, the product. A product
|
306 |
+
is a consumer product regardless of whether the product has substantial
|
307 |
+
commercial, industrial or non-consumer uses, unless such uses represent
|
308 |
+
the only significant mode of use of the product.
|
309 |
+
|
310 |
+
"Installation Information" for a User Product means any methods,
|
311 |
+
procedures, authorization keys, or other information required to install
|
312 |
+
and execute modified versions of a covered work in that User Product from
|
313 |
+
a modified version of its Corresponding Source. The information must
|
314 |
+
suffice to ensure that the continued functioning of the modified object
|
315 |
+
code is in no case prevented or interfered with solely because
|
316 |
+
modification has been made.
|
317 |
+
|
318 |
+
If you convey an object code work under this section in, or with, or
|
319 |
+
specifically for use in, a User Product, and the conveying occurs as
|
320 |
+
part of a transaction in which the right of possession and use of the
|
321 |
+
User Product is transferred to the recipient in perpetuity or for a
|
322 |
+
fixed term (regardless of how the transaction is characterized), the
|
323 |
+
Corresponding Source conveyed under this section must be accompanied
|
324 |
+
by the Installation Information. But this requirement does not apply
|
325 |
+
if neither you nor any third party retains the ability to install
|
326 |
+
modified object code on the User Product (for example, the work has
|
327 |
+
been installed in ROM).
|
328 |
+
|
329 |
+
The requirement to provide Installation Information does not include a
|
330 |
+
requirement to continue to provide support service, warranty, or updates
|
331 |
+
for a work that has been modified or installed by the recipient, or for
|
332 |
+
the User Product in which it has been modified or installed. Access to a
|
333 |
+
network may be denied when the modification itself materially and
|
334 |
+
adversely affects the operation of the network or violates the rules and
|
335 |
+
protocols for communication across the network.
|
336 |
+
|
337 |
+
Corresponding Source conveyed, and Installation Information provided,
|
338 |
+
in accord with this section must be in a format that is publicly
|
339 |
+
documented (and with an implementation available to the public in
|
340 |
+
source code form), and must require no special password or key for
|
341 |
+
unpacking, reading or copying.
|
342 |
+
|
343 |
+
7. Additional Terms.
|
344 |
+
|
345 |
+
"Additional permissions" are terms that supplement the terms of this
|
346 |
+
License by making exceptions from one or more of its conditions.
|
347 |
+
Additional permissions that are applicable to the entire Program shall
|
348 |
+
be treated as though they were included in this License, to the extent
|
349 |
+
that they are valid under applicable law. If additional permissions
|
350 |
+
apply only to part of the Program, that part may be used separately
|
351 |
+
under those permissions, but the entire Program remains governed by
|
352 |
+
this License without regard to the additional permissions.
|
353 |
+
|
354 |
+
When you convey a copy of a covered work, you may at your option
|
355 |
+
remove any additional permissions from that copy, or from any part of
|
356 |
+
it. (Additional permissions may be written to require their own
|
357 |
+
removal in certain cases when you modify the work.) You may place
|
358 |
+
additional permissions on material, added by you to a covered work,
|
359 |
+
for which you have or can give appropriate copyright permission.
|
360 |
+
|
361 |
+
Notwithstanding any other provision of this License, for material you
|
362 |
+
add to a covered work, you may (if authorized by the copyright holders of
|
363 |
+
that material) supplement the terms of this License with terms:
|
364 |
+
|
365 |
+
a) Disclaiming warranty or limiting liability differently from the
|
366 |
+
terms of sections 15 and 16 of this License; or
|
367 |
+
|
368 |
+
b) Requiring preservation of specified reasonable legal notices or
|
369 |
+
author attributions in that material or in the Appropriate Legal
|
370 |
+
Notices displayed by works containing it; or
|
371 |
+
|
372 |
+
c) Prohibiting misrepresentation of the origin of that material, or
|
373 |
+
requiring that modified versions of such material be marked in
|
374 |
+
reasonable ways as different from the original version; or
|
375 |
+
|
376 |
+
d) Limiting the use for publicity purposes of names of licensors or
|
377 |
+
authors of the material; or
|
378 |
+
|
379 |
+
e) Declining to grant rights under trademark law for use of some
|
380 |
+
trade names, trademarks, or service marks; or
|
381 |
+
|
382 |
+
f) Requiring indemnification of licensors and authors of that
|
383 |
+
material by anyone who conveys the material (or modified versions of
|
384 |
+
it) with contractual assumptions of liability to the recipient, for
|
385 |
+
any liability that these contractual assumptions directly impose on
|
386 |
+
those licensors and authors.
|
387 |
+
|
388 |
+
All other non-permissive additional terms are considered "further
|
389 |
+
restrictions" within the meaning of section 10. If the Program as you
|
390 |
+
received it, or any part of it, contains a notice stating that it is
|
391 |
+
governed by this License along with a term that is a further
|
392 |
+
restriction, you may remove that term. If a license document contains
|
393 |
+
a further restriction but permits relicensing or conveying under this
|
394 |
+
License, you may add to a covered work material governed by the terms
|
395 |
+
of that license document, provided that the further restriction does
|
396 |
+
not survive such relicensing or conveying.
|
397 |
+
|
398 |
+
If you add terms to a covered work in accord with this section, you
|
399 |
+
must place, in the relevant source files, a statement of the
|
400 |
+
additional terms that apply to those files, or a notice indicating
|
401 |
+
where to find the applicable terms.
|
402 |
+
|
403 |
+
Additional terms, permissive or non-permissive, may be stated in the
|
404 |
+
form of a separately written license, or stated as exceptions;
|
405 |
+
the above requirements apply either way.
|
406 |
+
|
407 |
+
8. Termination.
|
408 |
+
|
409 |
+
You may not propagate or modify a covered work except as expressly
|
410 |
+
provided under this License. Any attempt otherwise to propagate or
|
411 |
+
modify it is void, and will automatically terminate your rights under
|
412 |
+
this License (including any patent licenses granted under the third
|
413 |
+
paragraph of section 11).
|
414 |
+
|
415 |
+
However, if you cease all violation of this License, then your
|
416 |
+
license from a particular copyright holder is reinstated (a)
|
417 |
+
provisionally, unless and until the copyright holder explicitly and
|
418 |
+
finally terminates your license, and (b) permanently, if the copyright
|
419 |
+
holder fails to notify you of the violation by some reasonable means
|
420 |
+
prior to 60 days after the cessation.
|
421 |
+
|
422 |
+
Moreover, your license from a particular copyright holder is
|
423 |
+
reinstated permanently if the copyright holder notifies you of the
|
424 |
+
violation by some reasonable means, this is the first time you have
|
425 |
+
received notice of violation of this License (for any work) from that
|
426 |
+
copyright holder, and you cure the violation prior to 30 days after
|
427 |
+
your receipt of the notice.
|
428 |
+
|
429 |
+
Termination of your rights under this section does not terminate the
|
430 |
+
licenses of parties who have received copies or rights from you under
|
431 |
+
this License. If your rights have been terminated and not permanently
|
432 |
+
reinstated, you do not qualify to receive new licenses for the same
|
433 |
+
material under section 10.
|
434 |
+
|
435 |
+
9. Acceptance Not Required for Having Copies.
|
436 |
+
|
437 |
+
You are not required to accept this License in order to receive or
|
438 |
+
run a copy of the Program. Ancillary propagation of a covered work
|
439 |
+
occurring solely as a consequence of using peer-to-peer transmission
|
440 |
+
to receive a copy likewise does not require acceptance. However,
|
441 |
+
nothing other than this License grants you permission to propagate or
|
442 |
+
modify any covered work. These actions infringe copyright if you do
|
443 |
+
not accept this License. Therefore, by modifying or propagating a
|
444 |
+
covered work, you indicate your acceptance of this License to do so.
|
445 |
+
|
446 |
+
10. Automatic Licensing of Downstream Recipients.
|
447 |
+
|
448 |
+
Each time you convey a covered work, the recipient automatically
|
449 |
+
receives a license from the original licensors, to run, modify and
|
450 |
+
propagate that work, subject to this License. You are not responsible
|
451 |
+
for enforcing compliance by third parties with this License.
|
452 |
+
|
453 |
+
An "entity transaction" is a transaction transferring control of an
|
454 |
+
organization, or substantially all assets of one, or subdividing an
|
455 |
+
organization, or merging organizations. If propagation of a covered
|
456 |
+
work results from an entity transaction, each party to that
|
457 |
+
transaction who receives a copy of the work also receives whatever
|
458 |
+
licenses to the work the party's predecessor in interest had or could
|
459 |
+
give under the previous paragraph, plus a right to possession of the
|
460 |
+
Corresponding Source of the work from the predecessor in interest, if
|
461 |
+
the predecessor has it or can get it with reasonable efforts.
|
462 |
+
|
463 |
+
You may not impose any further restrictions on the exercise of the
|
464 |
+
rights granted or affirmed under this License. For example, you may
|
465 |
+
not impose a license fee, royalty, or other charge for exercise of
|
466 |
+
rights granted under this License, and you may not initiate litigation
|
467 |
+
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
468 |
+
any patent claim is infringed by making, using, selling, offering for
|
469 |
+
sale, or importing the Program or any portion of it.
|
470 |
+
|
471 |
+
11. Patents.
|
472 |
+
|
473 |
+
A "contributor" is a copyright holder who authorizes use under this
|
474 |
+
License of the Program or a work on which the Program is based. The
|
475 |
+
work thus licensed is called the contributor's "contributor version".
|
476 |
+
|
477 |
+
A contributor's "essential patent claims" are all patent claims
|
478 |
+
owned or controlled by the contributor, whether already acquired or
|
479 |
+
hereafter acquired, that would be infringed by some manner, permitted
|
480 |
+
by this License, of making, using, or selling its contributor version,
|
481 |
+
but do not include claims that would be infringed only as a
|
482 |
+
consequence of further modification of the contributor version. For
|
483 |
+
purposes of this definition, "control" includes the right to grant
|
484 |
+
patent sublicenses in a manner consistent with the requirements of
|
485 |
+
this License.
|
486 |
+
|
487 |
+
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
488 |
+
patent license under the contributor's essential patent claims, to
|
489 |
+
make, use, sell, offer for sale, import and otherwise run, modify and
|
490 |
+
propagate the contents of its contributor version.
|
491 |
+
|
492 |
+
In the following three paragraphs, a "patent license" is any express
|
493 |
+
agreement or commitment, however denominated, not to enforce a patent
|
494 |
+
(such as an express permission to practice a patent or covenant not to
|
495 |
+
sue for patent infringement). To "grant" such a patent license to a
|
496 |
+
party means to make such an agreement or commitment not to enforce a
|
497 |
+
patent against the party.
|
498 |
+
|
499 |
+
If you convey a covered work, knowingly relying on a patent license,
|
500 |
+
and the Corresponding Source of the work is not available for anyone
|
501 |
+
to copy, free of charge and under the terms of this License, through a
|
502 |
+
publicly available network server or other readily accessible means,
|
503 |
+
then you must either (1) cause the Corresponding Source to be so
|
504 |
+
available, or (2) arrange to deprive yourself of the benefit of the
|
505 |
+
patent license for this particular work, or (3) arrange, in a manner
|
506 |
+
consistent with the requirements of this License, to extend the patent
|
507 |
+
license to downstream recipients. "Knowingly relying" means you have
|
508 |
+
actual knowledge that, but for the patent license, your conveying the
|
509 |
+
covered work in a country, or your recipient's use of the covered work
|
510 |
+
in a country, would infringe one or more identifiable patents in that
|
511 |
+
country that you have reason to believe are valid.
|
512 |
+
|
513 |
+
If, pursuant to or in connection with a single transaction or
|
514 |
+
arrangement, you convey, or propagate by procuring conveyance of, a
|
515 |
+
covered work, and grant a patent license to some of the parties
|
516 |
+
receiving the covered work authorizing them to use, propagate, modify
|
517 |
+
or convey a specific copy of the covered work, then the patent license
|
518 |
+
you grant is automatically extended to all recipients of the covered
|
519 |
+
work and works based on it.
|
520 |
+
|
521 |
+
A patent license is "discriminatory" if it does not include within
|
522 |
+
the scope of its coverage, prohibits the exercise of, or is
|
523 |
+
conditioned on the non-exercise of one or more of the rights that are
|
524 |
+
specifically granted under this License. You may not convey a covered
|
525 |
+
work if you are a party to an arrangement with a third party that is
|
526 |
+
in the business of distributing software, under which you make payment
|
527 |
+
to the third party based on the extent of your activity of conveying
|
528 |
+
the work, and under which the third party grants, to any of the
|
529 |
+
parties who would receive the covered work from you, a discriminatory
|
530 |
+
patent license (a) in connection with copies of the covered work
|
531 |
+
conveyed by you (or copies made from those copies), or (b) primarily
|
532 |
+
for and in connection with specific products or compilations that
|
533 |
+
contain the covered work, unless you entered into that arrangement,
|
534 |
+
or that patent license was granted, prior to 28 March 2007.
|
535 |
+
|
536 |
+
Nothing in this License shall be construed as excluding or limiting
|
537 |
+
any implied license or other defenses to infringement that may
|
538 |
+
otherwise be available to you under applicable patent law.
|
539 |
+
|
540 |
+
12. No Surrender of Others' Freedom.
|
541 |
+
|
542 |
+
If conditions are imposed on you (whether by court order, agreement or
|
543 |
+
otherwise) that contradict the conditions of this License, they do not
|
544 |
+
excuse you from the conditions of this License. If you cannot convey a
|
545 |
+
covered work so as to satisfy simultaneously your obligations under this
|
546 |
+
License and any other pertinent obligations, then as a consequence you may
|
547 |
+
not convey it at all. For example, if you agree to terms that obligate you
|
548 |
+
to collect a royalty for further conveying from those to whom you convey
|
549 |
+
the Program, the only way you could satisfy both those terms and this
|
550 |
+
License would be to refrain entirely from conveying the Program.
|
551 |
+
|
552 |
+
13. Use with the GNU Affero General Public License.
|
553 |
+
|
554 |
+
Notwithstanding any other provision of this License, you have
|
555 |
+
permission to link or combine any covered work with a work licensed
|
556 |
+
under version 3 of the GNU Affero General Public License into a single
|
557 |
+
combined work, and to convey the resulting work. The terms of this
|
558 |
+
License will continue to apply to the part which is the covered work,
|
559 |
+
but the special requirements of the GNU Affero General Public License,
|
560 |
+
section 13, concerning interaction through a network will apply to the
|
561 |
+
combination as such.
|
562 |
+
|
563 |
+
14. Revised Versions of this License.
|
564 |
+
|
565 |
+
The Free Software Foundation may publish revised and/or new versions of
|
566 |
+
the GNU General Public License from time to time. Such new versions will
|
567 |
+
be similar in spirit to the present version, but may differ in detail to
|
568 |
+
address new problems or concerns.
|
569 |
+
|
570 |
+
Each version is given a distinguishing version number. If the
|
571 |
+
Program specifies that a certain numbered version of the GNU General
|
572 |
+
Public License "or any later version" applies to it, you have the
|
573 |
+
option of following the terms and conditions either of that numbered
|
574 |
+
version or of any later version published by the Free Software
|
575 |
+
Foundation. If the Program does not specify a version number of the
|
576 |
+
GNU General Public License, you may choose any version ever published
|
577 |
+
by the Free Software Foundation.
|
578 |
+
|
579 |
+
If the Program specifies that a proxy can decide which future
|
580 |
+
versions of the GNU General Public License can be used, that proxy's
|
581 |
+
public statement of acceptance of a version permanently authorizes you
|
582 |
+
to choose that version for the Program.
|
583 |
+
|
584 |
+
Later license versions may give you additional or different
|
585 |
+
permissions. However, no additional obligations are imposed on any
|
586 |
+
author or copyright holder as a result of your choosing to follow a
|
587 |
+
later version.
|
588 |
+
|
589 |
+
15. Disclaimer of Warranty.
|
590 |
+
|
591 |
+
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
592 |
+
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
593 |
+
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
594 |
+
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
595 |
+
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
596 |
+
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
597 |
+
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
598 |
+
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
599 |
+
|
600 |
+
16. Limitation of Liability.
|
601 |
+
|
602 |
+
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
603 |
+
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
604 |
+
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
605 |
+
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
606 |
+
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
607 |
+
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
608 |
+
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
609 |
+
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
610 |
+
SUCH DAMAGES.
|
611 |
+
|
612 |
+
17. Interpretation of Sections 15 and 16.
|
613 |
+
|
614 |
+
If the disclaimer of warranty and limitation of liability provided
|
615 |
+
above cannot be given local legal effect according to their terms,
|
616 |
+
reviewing courts shall apply local law that most closely approximates
|
617 |
+
an absolute waiver of all civil liability in connection with the
|
618 |
+
Program, unless a warranty or assumption of liability accompanies a
|
619 |
+
copy of the Program in return for a fee.
|
620 |
+
|
621 |
+
END OF TERMS AND CONDITIONS
|
622 |
+
|
623 |
+
How to Apply These Terms to Your New Programs
|
624 |
+
|
625 |
+
If you develop a new program, and you want it to be of the greatest
|
626 |
+
possible use to the public, the best way to achieve this is to make it
|
627 |
+
free software which everyone can redistribute and change under these terms.
|
628 |
+
|
629 |
+
To do so, attach the following notices to the program. It is safest
|
630 |
+
to attach them to the start of each source file to most effectively
|
631 |
+
state the exclusion of warranty; and each file should have at least
|
632 |
+
the "copyright" line and a pointer to where the full notice is found.
|
633 |
+
|
634 |
+
<one line to give the program's name and a brief idea of what it does.>
|
635 |
+
Copyright (C) <year> <name of author>
|
636 |
+
|
637 |
+
This program is free software: you can redistribute it and/or modify
|
638 |
+
it under the terms of the GNU General Public License as published by
|
639 |
+
the Free Software Foundation, either version 3 of the License, or
|
640 |
+
(at your option) any later version.
|
641 |
+
|
642 |
+
This program is distributed in the hope that it will be useful,
|
643 |
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
644 |
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
645 |
+
GNU General Public License for more details.
|
646 |
+
|
647 |
+
You should have received a copy of the GNU General Public License
|
648 |
+
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
649 |
+
|
650 |
+
Also add information on how to contact you by electronic and paper mail.
|
651 |
+
|
652 |
+
If the program does terminal interaction, make it output a short
|
653 |
+
notice like this when it starts in an interactive mode:
|
654 |
+
|
655 |
+
<program> Copyright (C) <year> <name of author>
|
656 |
+
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
657 |
+
This is free software, and you are welcome to redistribute it
|
658 |
+
under certain conditions; type `show c' for details.
|
659 |
+
|
660 |
+
The hypothetical commands `show w' and `show c' should show the appropriate
|
661 |
+
parts of the General Public License. Of course, your program's commands
|
662 |
+
might be different; for a GUI interface, you would use an "about box".
|
663 |
+
|
664 |
+
You should also get your employer (if you work as a programmer) or school,
|
665 |
+
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
666 |
+
For more information on this, and how to apply and follow the GNU GPL, see
|
667 |
+
<https://www.gnu.org/licenses/>.
|
668 |
+
|
669 |
+
The GNU General Public License does not permit incorporating your program
|
670 |
+
into proprietary programs. If your program is a subroutine library, you
|
671 |
+
may consider it more useful to permit linking proprietary applications with
|
672 |
+
the library. If this is what you want to do, use the GNU Lesser General
|
673 |
+
Public License instead of this License. But first, please read
|
674 |
+
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
VoucherVision_Reference.yaml
ADDED
@@ -0,0 +1,103 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# To use default value, set to null
|
2 |
+
leafmachine:
|
3 |
+
|
4 |
+
use_RGB_label_images: True
|
5 |
+
|
6 |
+
do:
|
7 |
+
check_for_illegal_filenames: False
|
8 |
+
check_for_corrupt_images_make_vertical: False
|
9 |
+
print:
|
10 |
+
verbose: True
|
11 |
+
optional_warnings: True
|
12 |
+
|
13 |
+
logging:
|
14 |
+
log_level: null
|
15 |
+
|
16 |
+
|
17 |
+
# Overall Project Input Settings
|
18 |
+
project:
|
19 |
+
# Image to Process
|
20 |
+
dir_images_local: 'D:\Dropbox\LM2_Env\VoucherVision_Datasets\2022_09_07_thru12_S3_jacortez_AllAsia' # 'D:/Dropbox/LM2_Env/VoucherVision_Datasets/Compare_Set_Easy_10imgs/imgs' #'D:\D_Desktop\Richie\Imgs' #'D:/Dropbox/LM2_Env/Image_Datasets/Acacia/Acacia_prickles_4-26-23_LANCZOS/images/short' #'D:\D_Desktop\Richie\Imgs' #'home/brlab/Dropbox/LM2_Env/Image_Datasets/Manuscript_Images' # 'D:\Dropbox\LM2_Env\Image_Datasets\SET_FieldPrism_Test\TESTING_OUTPUT\Images_Processed\REU_Field_QR-Code-Images\Cannon_Corrected\Images_Corrected' # 'F:\temp_3sppFamily' # 'D:/Dropbox/LM2_Env/Image_Datasets/GBIF_BroadSample_3SppPerFamily' # SET_Diospyros/images_short' # 'D:/Dropbox/LM2_Env/Image_Datasets/SET_Diospyros/images_short' #'D:\Dropbox\LM2_Env\Image_Datasets\GBIF_BroadSample_Herbarium' #'D:/Dropbox/LM2_Env/Image_Datasets/SET_Diospyros/images_short' # str | only for image_location:local | full path for directory containing images
|
21 |
+
# dir_images_local: 'D:/Dropbox/LM2_Env/VoucherVision_Datasets/Compare_Set_Easy_10imgs/imgs' #'D:\D_Desktop\Richie\Imgs' #'D:/Dropbox/LM2_Env/Image_Datasets/Acacia/Acacia_prickles_4-26-23_LANCZOS/images/short' #'D:\D_Desktop\Richie\Imgs' #'home/brlab/Dropbox/LM2_Env/Image_Datasets/Manuscript_Images' # 'D:\Dropbox\LM2_Env\Image_Datasets\SET_FieldPrism_Test\TESTING_OUTPUT\Images_Processed\REU_Field_QR-Code-Images\Cannon_Corrected\Images_Corrected' # 'F:\temp_3sppFamily' # 'D:/Dropbox/LM2_Env/Image_Datasets/GBIF_BroadSample_3SppPerFamily' # SET_Diospyros/images_short' # 'D:/Dropbox/LM2_Env/Image_Datasets/SET_Diospyros/images_short' #'D:\Dropbox\LM2_Env\Image_Datasets\GBIF_BroadSample_Herbarium' #'D:/Dropbox/LM2_Env/Image_Datasets/SET_Diospyros/images_short' # str | only for image_location:local | full path for directory containing images
|
22 |
+
image_location: 'local'
|
23 |
+
|
24 |
+
continue_run_from_partial_xlsx: 'D:\Dropbox\LM2_Env\VoucherVision_Datasets\POC_chatGPT__2022_09_07_thru12_S3_jacortez_AllAsia\2022_09_07_thru12_S3_jacortez_AllAsia\Transcription\transcribed.xlsx'
|
25 |
+
# continue_run_from_partial_xlsx: null
|
26 |
+
|
27 |
+
# Project Output Dir
|
28 |
+
dir_output: 'D:/Dropbox/LM2_Env/VoucherVision_Datasets/POC_chatGPT__2022_09_07_thru12_S3_jacortez_AllAsia' # 'D:/Dropbox/LM2_Env/Image_Datasets/TEST_LM2' # 'D:\D_Desktop\Richie\Richie_Out'
|
29 |
+
run_name: 'POC_chatGPT' #'images_short_TEST' #'images_short_landmark'
|
30 |
+
|
31 |
+
prefix_removal: 'MICH-V-'
|
32 |
+
suffix_removal: ''
|
33 |
+
catalog_numerical_only: True
|
34 |
+
|
35 |
+
# Embeddings and LLM
|
36 |
+
use_domain_knowledge: True
|
37 |
+
embeddings_database_name: 'EmbeddingsDB_all_asia_minimal_InRegion'
|
38 |
+
build_new_embeddings_database: False
|
39 |
+
path_to_domain_knowledge_xlsx: 'D:\Dropbox\LeafMachine2\leafmachine2\transcription\domain_knowledge/AllAsiaMinimalasof25May2023_2__InRegion.xlsx' #'D:/Dropbox/LeafMachine2/leafmachine2/transcription/domain_knowledge/AllAsiaMinimalasof25May2023_2__TRIMMEDtiny.xlsx'
|
40 |
+
|
41 |
+
batch_size: 500 #null # null = all
|
42 |
+
num_workers: 1 # int |DEFAULT| 4 # More is not always better. Most hardware loses performance after 4
|
43 |
+
|
44 |
+
modules:
|
45 |
+
specimen_crop: True
|
46 |
+
|
47 |
+
LLM_version: 'chatGPT' # from 'chatGPT' OR 'PaLM'
|
48 |
+
|
49 |
+
cropped_components:
|
50 |
+
# empty list for all, add to list to IGNORE, lowercase, comma seperated
|
51 |
+
# archival |FROM|
|
52 |
+
# ruler, barcode, colorcard, label, map, envelope, photo, attached_item, weights
|
53 |
+
# plant |FROM|
|
54 |
+
# leaf_whole, leaf_partial, leaflet, seed_fruit_one, seed_fruit_many, flower_one, flower_many, bud, specimen, roots, wood
|
55 |
+
do_save_cropped_annotations: True
|
56 |
+
save_cropped_annotations: ['label','barcode'] # 'save_all' to save all classes
|
57 |
+
save_per_image: False # creates a folder for each image, saves crops into class-names folders # TODO
|
58 |
+
save_per_annotation_class: True # saves crops into class-names folders
|
59 |
+
binarize_labels: False
|
60 |
+
binarize_labels_skeletonize: False
|
61 |
+
|
62 |
+
data:
|
63 |
+
save_json_rulers: False
|
64 |
+
save_json_measurements: False
|
65 |
+
save_individual_csv_files_rulers: False
|
66 |
+
save_individual_csv_files_measurements: False
|
67 |
+
include_darwin_core_data_from_combined_file: False
|
68 |
+
do_apply_conversion_factor: False ###########################
|
69 |
+
|
70 |
+
overlay:
|
71 |
+
save_overlay_to_pdf: True
|
72 |
+
save_overlay_to_jpgs: True
|
73 |
+
overlay_dpi: 300 # int |FROM| 100 to 300
|
74 |
+
overlay_background_color: 'black' # str |FROM| 'white' or 'black'
|
75 |
+
|
76 |
+
show_archival_detections: True
|
77 |
+
ignore_archival_detections_classes: []
|
78 |
+
show_plant_detections: True
|
79 |
+
ignore_plant_detections_classes: ['leaf_whole', 'specimen'] #['leaf_whole', 'leaf_partial', 'specimen']
|
80 |
+
show_segmentations: True
|
81 |
+
show_landmarks: True
|
82 |
+
ignore_landmark_classes: []
|
83 |
+
|
84 |
+
line_width_archival: 2 # int
|
85 |
+
line_width_plant: 6 # int
|
86 |
+
line_width_seg: 12 # int # thick = 12
|
87 |
+
line_width_efd: 6 # int # thick = 3
|
88 |
+
alpha_transparency_archival: 0.3 # float between 0 and 1
|
89 |
+
alpha_transparency_plant: 0
|
90 |
+
alpha_transparency_seg_whole_leaf: 0.4
|
91 |
+
alpha_transparency_seg_partial_leaf: 0.3
|
92 |
+
|
93 |
+
# Configure Archival Component Detector
|
94 |
+
archival_component_detector:
|
95 |
+
# ./leafmachine2/component_detector/runs/train/detector_type/detector_version/detector_iteration/weights/detector_weights
|
96 |
+
detector_type: 'Archival_Detector'
|
97 |
+
detector_version: 'PREP_final'
|
98 |
+
detector_iteration: 'PREP_final'
|
99 |
+
detector_weights: 'best.pt'
|
100 |
+
minimum_confidence_threshold: 0.5
|
101 |
+
do_save_prediction_overlay_images: True
|
102 |
+
ignore_objects_for_overlay: [] # list[str] # list of objects that can be excluded from the overlay # all = null
|
103 |
+
|
__init__.py
ADDED
File without changes
|
api_cost/api_cost.yaml
ADDED
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
GPT_3_5:
|
2 |
+
in: 0.0015
|
3 |
+
out: 0.002
|
4 |
+
GPT_4:
|
5 |
+
in: 0.03
|
6 |
+
out: 0.06
|
7 |
+
PALM2:
|
8 |
+
in: 0.0
|
9 |
+
out: 0.0
|
app.py
ADDED
@@ -0,0 +1,1344 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import streamlit as st
|
2 |
+
import yaml, os, json, random, time, re
|
3 |
+
import matplotlib.pyplot as plt
|
4 |
+
import plotly.graph_objs as go
|
5 |
+
import numpy as np
|
6 |
+
from itertools import chain
|
7 |
+
from PIL import Image
|
8 |
+
import pandas as pd
|
9 |
+
from typing import Union
|
10 |
+
from streamlit_extras.let_it_rain import rain
|
11 |
+
from vouchervision.LeafMachine2_Config_Builder import write_config_file
|
12 |
+
from vouchervision.VoucherVision_Config_Builder import build_VV_config, run_demo_tests_GPT, run_demo_tests_Palm , TestOptionsGPT, TestOptionsPalm, check_if_usable, run_api_tests
|
13 |
+
from vouchervision.vouchervision_main import voucher_vision, voucher_vision_OCR_test
|
14 |
+
from vouchervision.general_utils import test_GPU, get_cfg_from_full_path, summarize_expense_report, create_google_ocr_yaml_config, validate_dir
|
15 |
+
|
16 |
+
PROMPTS_THAT_NEED_DOMAIN_KNOWLEDGE = ["Version 1","Version 1 PaLM 2"]
|
17 |
+
COLORS_EXPENSE_REPORT = {
|
18 |
+
'GPT_4': '#8fff66', # Bright Green
|
19 |
+
'GPT_3_5': '#006400', # Dark Green
|
20 |
+
'PALM2': '#66a8ff' # blue
|
21 |
+
}
|
22 |
+
|
23 |
+
class ProgressReport:
|
24 |
+
def __init__(self, overall_bar, batch_bar, text_overall, text_batch):
|
25 |
+
self.overall_bar = overall_bar
|
26 |
+
self.batch_bar = batch_bar
|
27 |
+
self.text_overall = text_overall
|
28 |
+
self.text_batch = text_batch
|
29 |
+
self.current_overall_step = 0
|
30 |
+
self.total_overall_steps = 20 # number of major steps in machine function
|
31 |
+
self.current_batch = 0
|
32 |
+
self.total_batches = 20
|
33 |
+
|
34 |
+
def update_overall(self, step_name=""):
|
35 |
+
self.current_overall_step += 1
|
36 |
+
self.overall_bar.progress(self.current_overall_step / self.total_overall_steps)
|
37 |
+
self.text_overall.text(step_name)
|
38 |
+
|
39 |
+
def update_batch(self, step_name=""):
|
40 |
+
self.current_batch += 1
|
41 |
+
self.batch_bar.progress(self.current_batch / self.total_batches)
|
42 |
+
self.text_batch.text(step_name)
|
43 |
+
|
44 |
+
def set_n_batches(self, n_batches):
|
45 |
+
self.total_batches = n_batches
|
46 |
+
|
47 |
+
def set_n_overall(self, total_overall_steps):
|
48 |
+
self.total_overall_steps = total_overall_steps
|
49 |
+
|
50 |
+
def reset_batch(self, step_name):
|
51 |
+
self.current_batch = 0
|
52 |
+
self.batch_bar.progress(0)
|
53 |
+
self.text_batch.text(step_name)
|
54 |
+
def reset_overall(self, step_name):
|
55 |
+
self.current_overall_step = 0
|
56 |
+
self.overall_bar.progress(0)
|
57 |
+
self.text_overall.text(step_name)
|
58 |
+
|
59 |
+
def get_n_images(self):
|
60 |
+
return self.n_images
|
61 |
+
def get_n_overall(self):
|
62 |
+
return self.total_overall_steps
|
63 |
+
|
64 |
+
def does_private_file_exist():
|
65 |
+
dir_home = os.path.dirname(os.path.dirname(__file__))
|
66 |
+
path_cfg_private = os.path.join(dir_home, 'PRIVATE_DATA.yaml')
|
67 |
+
return os.path.exists(path_cfg_private)
|
68 |
+
|
69 |
+
def setup_streamlit_config(dir_home):
|
70 |
+
# Define the directory path and filename
|
71 |
+
dir_path = os.path.join(dir_home, ".streamlit")
|
72 |
+
file_path = os.path.join(dir_path, "config.toml")
|
73 |
+
|
74 |
+
# Check if directory exists, if not create it
|
75 |
+
if not os.path.exists(dir_path):
|
76 |
+
os.makedirs(dir_path)
|
77 |
+
|
78 |
+
# Create or modify the file with the provided content
|
79 |
+
config_content = f"""
|
80 |
+
[theme]
|
81 |
+
base = "dark"
|
82 |
+
primaryColor = "#00ff00"
|
83 |
+
|
84 |
+
[server]
|
85 |
+
enableStaticServing = false
|
86 |
+
runOnSave = true
|
87 |
+
port = 8524
|
88 |
+
"""
|
89 |
+
|
90 |
+
with open(file_path, "w") as f:
|
91 |
+
f.write(config_content.strip())
|
92 |
+
|
93 |
+
def display_scrollable_results(JSON_results, test_results, OPT2, OPT3):
|
94 |
+
"""
|
95 |
+
Display the results from JSON_results in a scrollable container.
|
96 |
+
"""
|
97 |
+
# Initialize the container
|
98 |
+
con_results = st.empty()
|
99 |
+
with con_results.container():
|
100 |
+
|
101 |
+
# Start the custom container for all the results
|
102 |
+
results_html = """<div class='scrollable-results-container'>"""
|
103 |
+
|
104 |
+
for idx, (test_name, _) in enumerate(sorted(test_results.items())):
|
105 |
+
_, ind_opt1, ind_opt2, ind_opt3 = test_name.split('__')
|
106 |
+
opt2_readable = "Use LeafMachine2" if OPT2[int(ind_opt2.split('-')[1])] else "Don't use LeafMachine2"
|
107 |
+
opt3_readable = f"{OPT3[int(ind_opt3.split('-')[1])]}"
|
108 |
+
|
109 |
+
if JSON_results[idx] is None:
|
110 |
+
results_html += f"<p>None</p>"
|
111 |
+
else:
|
112 |
+
formatted_json = json.dumps(JSON_results[idx], indent=4)
|
113 |
+
results_html += f"<pre>[{opt2_readable}] + [{opt3_readable}]<br/>{formatted_json}</pre>"
|
114 |
+
|
115 |
+
# End the custom container
|
116 |
+
results_html += """</div>"""
|
117 |
+
|
118 |
+
# The CSS to make this container scrollable
|
119 |
+
css = """
|
120 |
+
<style>
|
121 |
+
.scrollable-results-container {
|
122 |
+
overflow-y: auto;
|
123 |
+
height: 600px;
|
124 |
+
width: 100%;
|
125 |
+
white-space: pre-wrap; # To wrap the content
|
126 |
+
font-family: monospace; # To give the JSON a code-like appearance
|
127 |
+
}
|
128 |
+
</style>
|
129 |
+
"""
|
130 |
+
|
131 |
+
# Apply the CSS and then the results
|
132 |
+
st.markdown(css, unsafe_allow_html=True)
|
133 |
+
st.markdown(results_html, unsafe_allow_html=True)
|
134 |
+
|
135 |
+
def display_test_results(test_results, JSON_results, llm_version):
|
136 |
+
if llm_version == 'gpt':
|
137 |
+
OPT1, OPT2, OPT3 = TestOptionsGPT.get_options()
|
138 |
+
elif llm_version == 'palm':
|
139 |
+
OPT1, OPT2, OPT3 = TestOptionsPalm.get_options()
|
140 |
+
else:
|
141 |
+
raise
|
142 |
+
|
143 |
+
widths = [1] * (len(OPT1) + 2) + [2]
|
144 |
+
columns = st.columns(widths)
|
145 |
+
|
146 |
+
with columns[0]:
|
147 |
+
st.write("LeafMachine2")
|
148 |
+
with columns[1]:
|
149 |
+
st.write("Prompt")
|
150 |
+
with columns[len(OPT1) + 2]:
|
151 |
+
st.write("Scroll to See Last Transcription in Each Test")
|
152 |
+
|
153 |
+
already_written = set()
|
154 |
+
|
155 |
+
for test_name, result in sorted(test_results.items()):
|
156 |
+
_, ind_opt1, _, _ = test_name.split('__')
|
157 |
+
option_value = OPT1[int(ind_opt1.split('-')[1])]
|
158 |
+
|
159 |
+
if option_value not in already_written:
|
160 |
+
with columns[int(ind_opt1.split('-')[1]) + 2]:
|
161 |
+
st.write(option_value)
|
162 |
+
already_written.add(option_value)
|
163 |
+
|
164 |
+
printed_options = set()
|
165 |
+
|
166 |
+
with columns[-1]:
|
167 |
+
display_scrollable_results(JSON_results, test_results, OPT2, OPT3)
|
168 |
+
|
169 |
+
# Close the custom container
|
170 |
+
st.write('</div>', unsafe_allow_html=True)
|
171 |
+
|
172 |
+
|
173 |
+
for idx, (test_name, result) in enumerate(sorted(test_results.items())):
|
174 |
+
_, ind_opt1, ind_opt2, ind_opt3 = test_name.split('__')
|
175 |
+
opt2_readable = "Use LeafMachine2" if OPT2[int(ind_opt2.split('-')[1])] else "Don't use LeafMachine2"
|
176 |
+
opt3_readable = f"{OPT3[int(ind_opt3.split('-')[1])]}"
|
177 |
+
|
178 |
+
if (opt2_readable, opt3_readable) not in printed_options:
|
179 |
+
with columns[0]:
|
180 |
+
st.info(f"{opt2_readable}")
|
181 |
+
st.write('---')
|
182 |
+
with columns[1]:
|
183 |
+
st.info(f"{opt3_readable}")
|
184 |
+
st.write('---')
|
185 |
+
printed_options.add((opt2_readable, opt3_readable))
|
186 |
+
|
187 |
+
with columns[int(ind_opt1.split('-')[1]) + 2]:
|
188 |
+
if result:
|
189 |
+
st.success(f"Test Passed")
|
190 |
+
else:
|
191 |
+
st.error(f"Test Failed")
|
192 |
+
st.write('---')
|
193 |
+
|
194 |
+
# success_count = sum(1 for result in test_results.values() if result)
|
195 |
+
# failure_count = len(test_results) - success_count
|
196 |
+
# proportional_rain("🥇", success_count, "💔", failure_count, font_size=72, falling_speed=5, animation_length="infinite")
|
197 |
+
rain_emojis(test_results)
|
198 |
+
|
199 |
+
def add_emoji_delay():
|
200 |
+
time.sleep(0.3)
|
201 |
+
|
202 |
+
def rain_emojis(test_results):
|
203 |
+
# test_results = {
|
204 |
+
# 'test1': True, # Test passed
|
205 |
+
# 'test2': True, # Test passed
|
206 |
+
# 'test3': True, # Test passed
|
207 |
+
# 'test4': False, # Test failed
|
208 |
+
# 'test5': False, # Test failed
|
209 |
+
# 'test6': False, # Test failed
|
210 |
+
# 'test7': False, # Test failed
|
211 |
+
# 'test8': False, # Test failed
|
212 |
+
# 'test9': False, # Test failed
|
213 |
+
# 'test10': False, # Test failed
|
214 |
+
# }
|
215 |
+
success_emojis = ["🥇", "🏆", "🍾", "🙌"]
|
216 |
+
failure_emojis = ["💔", "😭"]
|
217 |
+
|
218 |
+
success_count = sum(1 for result in test_results.values() if result)
|
219 |
+
failure_count = len(test_results) - success_count
|
220 |
+
|
221 |
+
chosen_emoji = random.choice(success_emojis)
|
222 |
+
for _ in range(success_count):
|
223 |
+
rain(
|
224 |
+
emoji=chosen_emoji,
|
225 |
+
font_size=72,
|
226 |
+
falling_speed=4,
|
227 |
+
animation_length=2,
|
228 |
+
)
|
229 |
+
add_emoji_delay()
|
230 |
+
|
231 |
+
chosen_emoji = random.choice(failure_emojis)
|
232 |
+
for _ in range(failure_count):
|
233 |
+
rain(
|
234 |
+
emoji=chosen_emoji,
|
235 |
+
font_size=72,
|
236 |
+
falling_speed=5,
|
237 |
+
animation_length=1,
|
238 |
+
)
|
239 |
+
add_emoji_delay()
|
240 |
+
|
241 |
+
def get_prompt_versions(LLM_version):
|
242 |
+
yaml_files = [f for f in os.listdir(os.path.join(st.session_state.dir_home, 'custom_prompts')) if f.endswith('.yaml')]
|
243 |
+
|
244 |
+
if LLM_version in ["GPT 4", "GPT 3.5", "Azure GPT 4", "Azure GPT 3.5"]:
|
245 |
+
versions = ["Version 1", "Version 1 No Domain Knowledge", "Version 2"]
|
246 |
+
return (versions + yaml_files, "Version 2")
|
247 |
+
elif LLM_version in ["PaLM 2",]:
|
248 |
+
versions = ["Version 1 PaLM 2", "Version 1 PaLM 2 No Domain Knowledge", "Version 2 PaLM 2"]
|
249 |
+
return (versions + yaml_files, "Version 2 PaLM 2")
|
250 |
+
else:
|
251 |
+
# Handle other cases or raise an error
|
252 |
+
return (yaml_files, None)
|
253 |
+
|
254 |
+
def get_private_file():
|
255 |
+
dir_home = os.path.dirname(os.path.dirname(__file__))
|
256 |
+
path_cfg_private = os.path.join(dir_home, 'PRIVATE_DATA.yaml')
|
257 |
+
return get_cfg_from_full_path(path_cfg_private)
|
258 |
+
|
259 |
+
def create_space_saver():
|
260 |
+
st.subheader("Space Saving Options")
|
261 |
+
col_ss_1, col_ss_2 = st.columns([2,2])
|
262 |
+
with col_ss_1:
|
263 |
+
st.write("Several folders are created and populated with data during the VoucherVision transcription process.")
|
264 |
+
st.write("Below are several options that will allow you to automatically delete temporary files that you may not need for everyday operations.")
|
265 |
+
st.write("VoucherVision creates the following folders. Folders marked with a :star: are required if you want to use VoucherVisionEditor for quality control.")
|
266 |
+
st.write("`../[Run Name]/Archival_Components`")
|
267 |
+
st.write("`../[Run Name]/Config_File`")
|
268 |
+
st.write("`../[Run Name]/Cropped_Images` :star:")
|
269 |
+
st.write("`../[Run Name]/Logs`")
|
270 |
+
st.write("`../[Run Name]/Original_Images` :star:")
|
271 |
+
st.write("`../[Run Name]/Transcription` :star:")
|
272 |
+
with col_ss_2:
|
273 |
+
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))
|
274 |
+
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.")
|
275 |
+
|
276 |
+
|
277 |
+
# def create_private_file():
|
278 |
+
# st.session_state.proceed_to_main = False
|
279 |
+
|
280 |
+
# if st.session_state.private_file:
|
281 |
+
# cfg_private = get_private_file()
|
282 |
+
# create_private_file_0(cfg_private)
|
283 |
+
# else:
|
284 |
+
# st.title("VoucherVision")
|
285 |
+
# create_private_file_0()
|
286 |
+
|
287 |
+
def create_private_file():
|
288 |
+
st.session_state.proceed_to_main = False
|
289 |
+
st.title("VoucherVision")
|
290 |
+
col_private,_= st.columns([12,2])
|
291 |
+
|
292 |
+
if st.session_state.private_file:
|
293 |
+
cfg_private = get_private_file()
|
294 |
+
else:
|
295 |
+
cfg_private = {}
|
296 |
+
cfg_private['openai'] = {}
|
297 |
+
cfg_private['openai']['OPENAI_API_KEY'] =''
|
298 |
+
|
299 |
+
cfg_private['openai_azure'] = {}
|
300 |
+
cfg_private['openai_azure']['openai_api_key'] = ''
|
301 |
+
cfg_private['openai_azure']['api_version'] = ''
|
302 |
+
cfg_private['openai_azure']['openai_api_base'] =''
|
303 |
+
cfg_private['openai_azure']['openai_organization'] =''
|
304 |
+
cfg_private['openai_azure']['openai_api_type'] =''
|
305 |
+
|
306 |
+
cfg_private['google_cloud'] = {}
|
307 |
+
cfg_private['google_cloud']['path_json_file'] =''
|
308 |
+
|
309 |
+
cfg_private['google_palm'] = {}
|
310 |
+
cfg_private['google_palm']['google_palm_api'] =''
|
311 |
+
|
312 |
+
|
313 |
+
with col_private:
|
314 |
+
st.header("Set API keys")
|
315 |
+
st.info("***Note:*** There is a known bug with tabs in Streamlit. If you update an input field it may take you back to the 'Project Settings' tab. Changes that you made are saved, it's just an annoying glitch. We are aware of this issue and will fix it as soon as we can.")
|
316 |
+
st.warning("To commit changes to API keys you must press the 'Set API Keys' button at the bottom of the page.")
|
317 |
+
st.write("Before using VoucherVision you must set your API keys. All keys are stored locally on your computer and are never made public.")
|
318 |
+
st.write("API keys are stored in `../VoucherVision/PRIVATE_DATA.yaml`.")
|
319 |
+
st.write("Deleting this file will allow you to reset API keys. Alternatively, you can edit the keys in the user interface.")
|
320 |
+
st.write("Leave keys blank if you do not intend to use that service.")
|
321 |
+
|
322 |
+
st.write("---")
|
323 |
+
st.subheader("Google Vision (*Required*)")
|
324 |
+
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) ")
|
325 |
+
st.markdown("""
|
326 |
+
Once your account is created, [visit this page](https://console.cloud.google.com) and create a project. Then follow these instructions:
|
327 |
+
|
328 |
+
- **Select your Project**: If you have multiple projects, ensure you select the one where you've enabled the Vision API.
|
329 |
+
- **Open the Navigation Menu**: Click on the hamburger menu (three horizontal lines) in the top left corner.
|
330 |
+
- **Go to IAM & Admin**: In the navigation pane, hover over "IAM & Admin" and then click on "Service accounts."
|
331 |
+
- **Locate Your Service Account**: Find the service account for which you wish to download the JSON key. If you haven't created a service account yet, you'll need to do so by clicking the "CREATE SERVICE ACCOUNT" button at the top.
|
332 |
+
- **Download the JSON Key**:
|
333 |
+
- Click on the three dots (actions menu) on the right side of your service account name.
|
334 |
+
- Select "Manage keys."
|
335 |
+
- In the pop-up window, click on the "ADD KEY" button and select "JSON."
|
336 |
+
- The JSON key file will automatically be downloaded to your computer.
|
337 |
+
- **Store Safely**: 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.
|
338 |
+
""")
|
339 |
+
with st.container():
|
340 |
+
c_in_ocr, c_button_ocr = st.columns([10,2])
|
341 |
+
with c_in_ocr:
|
342 |
+
google_vision = st.text_input(label = 'Full path to Google Cloud JSON API key file', value = cfg_private['google_cloud'].get('path_json_file', ''),
|
343 |
+
placeholder = 'e.g. C:/Documents/Secret_Files/google_API/application_default_credentials.json',
|
344 |
+
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.",
|
345 |
+
type='password',key='924857298734590283750932809238')
|
346 |
+
with c_button_ocr:
|
347 |
+
st.empty()
|
348 |
+
|
349 |
+
|
350 |
+
st.write("---")
|
351 |
+
st.subheader("OpenAI")
|
352 |
+
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).")
|
353 |
+
with st.container():
|
354 |
+
c_in_openai, c_button_openai = st.columns([10,2])
|
355 |
+
with c_in_openai:
|
356 |
+
openai_api_key = st.text_input("openai_api_key", cfg_private['openai'].get('OPENAI_API_KEY', ''),
|
357 |
+
help='The actual API key. Likely to be a string of 2 character, a dash, and then a 48-character string: sk-XXXXXXXX...',
|
358 |
+
placeholder = 'e.g. sk-XXXXXXXX...',
|
359 |
+
type='password')
|
360 |
+
with c_button_openai:
|
361 |
+
st.empty()
|
362 |
+
|
363 |
+
st.write("---")
|
364 |
+
st.subheader("OpenAI - Azure")
|
365 |
+
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.")
|
366 |
+
azure_openai_api_version = st.text_input("azure_openai_api_version", cfg_private['openai_azure'].get('api_version', ''),
|
367 |
+
help='API Version e.g. "2023-05-15"',
|
368 |
+
placeholder = 'e.g. 2023-05-15',
|
369 |
+
type='password')
|
370 |
+
azure_openai_api_key = st.text_input("azure_openai_api_key", cfg_private['openai_azure'].get('openai_api_key', ''),
|
371 |
+
help='The actual API key. Likely to be a 32-character string',
|
372 |
+
placeholder = 'e.g. 12333333333333333333333333333332',
|
373 |
+
type='password')
|
374 |
+
azure_openai_api_base = st.text_input("azure_openai_api_base", cfg_private['openai_azure'].get('openai_api_base', ''),
|
375 |
+
help='The base url for the API e.g. "https://api.umgpt.umich.edu/azure-openai-api"',
|
376 |
+
placeholder = 'e.g. https://api.umgpt.umich.edu/azure-openai-api',
|
377 |
+
type='password')
|
378 |
+
azure_openai_organization = st.text_input("azure_openai_organization", cfg_private['openai_azure'].get('openai_organization', ''),
|
379 |
+
help='Your organization code. Likely a short string',
|
380 |
+
placeholder = 'e.g. 123456',
|
381 |
+
type='password')
|
382 |
+
azure_openai_api_type = st.text_input("azure_openai_api_type", cfg_private['openai_azure'].get('openai_api_type', ''),
|
383 |
+
help='The API type. Typically "azure"',
|
384 |
+
placeholder = 'e.g. azure',
|
385 |
+
type='password')
|
386 |
+
with st.container():
|
387 |
+
c_in_azure, c_button_azure = st.columns([10,2])
|
388 |
+
with c_button_azure:
|
389 |
+
st.empty()
|
390 |
+
|
391 |
+
st.write("---")
|
392 |
+
st.subheader("Google PaLM 2")
|
393 |
+
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."')
|
394 |
+
with st.container():
|
395 |
+
c_in_palm, c_button_palm = st.columns([10,2])
|
396 |
+
with c_in_palm:
|
397 |
+
google_palm = st.text_input("Google PaLM 2 API Key", cfg_private['google_palm'].get('google_palm_api', ''),
|
398 |
+
help='The MakerSuite API key e.g. a 32-character string',
|
399 |
+
placeholder='e.g. SATgthsykuE64FgrrrrEervr3S4455t_geyDeGq',
|
400 |
+
type='password')
|
401 |
+
|
402 |
+
with st.container():
|
403 |
+
with c_button_ocr:
|
404 |
+
st.write("##")
|
405 |
+
st.button("Test OCR", on_click=test_API, args=['google_vision',c_in_ocr, cfg_private,openai_api_key,azure_openai_api_version,azure_openai_api_key,
|
406 |
+
azure_openai_api_base,azure_openai_organization,azure_openai_api_type,google_vision,google_palm])
|
407 |
+
|
408 |
+
with st.container():
|
409 |
+
with c_button_openai:
|
410 |
+
st.write("##")
|
411 |
+
st.button("Test OpenAI", on_click=test_API, args=['openai',c_in_openai, cfg_private,openai_api_key,azure_openai_api_version,azure_openai_api_key,
|
412 |
+
azure_openai_api_base,azure_openai_organization,azure_openai_api_type,google_vision,google_palm])
|
413 |
+
|
414 |
+
with st.container():
|
415 |
+
with c_button_azure:
|
416 |
+
st.write("##")
|
417 |
+
st.button("Test Azure OpenAI", on_click=test_API, args=['azure_openai',c_in_azure, cfg_private,openai_api_key,azure_openai_api_version,azure_openai_api_key,
|
418 |
+
azure_openai_api_base,azure_openai_organization,azure_openai_api_type,google_vision,google_palm])
|
419 |
+
|
420 |
+
with st.container():
|
421 |
+
with c_button_palm:
|
422 |
+
st.write("##")
|
423 |
+
st.button("Test PaLM 2", on_click=test_API, args=['palm',c_in_palm, cfg_private,openai_api_key,azure_openai_api_version,azure_openai_api_key,
|
424 |
+
azure_openai_api_base,azure_openai_organization,azure_openai_api_type,google_vision,google_palm])
|
425 |
+
|
426 |
+
|
427 |
+
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,
|
428 |
+
azure_openai_api_base,azure_openai_organization,azure_openai_api_type,google_vision,google_palm])
|
429 |
+
if st.button('Proceed to VoucherVision'):
|
430 |
+
st.session_state.proceed_to_private = False
|
431 |
+
st.session_state.proceed_to_main = True
|
432 |
+
|
433 |
+
def test_API(api, message_loc, 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_vision,google_palm):
|
434 |
+
# Save the API keys
|
435 |
+
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_vision,google_palm)
|
436 |
+
|
437 |
+
with st.spinner('Performing validation checks...'):
|
438 |
+
if api == 'google_vision':
|
439 |
+
print("*** Google Vision OCR API Key ***")
|
440 |
+
try:
|
441 |
+
demo_config_path = os.path.join(st.session_state.dir_home,'demo','validation_configs','google_vision_ocr_test.yaml')
|
442 |
+
demo_images_path = os.path.join(st.session_state.dir_home, 'demo', 'demo_images')
|
443 |
+
demo_out_path = os.path.join(st.session_state.dir_home, 'demo', 'demo_output','run_name')
|
444 |
+
create_google_ocr_yaml_config(demo_config_path, demo_images_path, demo_out_path)
|
445 |
+
voucher_vision_OCR_test(demo_config_path, st.session_state.dir_home, None, demo_images_path)
|
446 |
+
with message_loc:
|
447 |
+
st.success("Google Vision OCR API Key Valid :white_check_mark:")
|
448 |
+
return True
|
449 |
+
except Exception as e:
|
450 |
+
with message_loc:
|
451 |
+
st.error(f"Google Vision OCR API Key Failed! {e}")
|
452 |
+
return False
|
453 |
+
|
454 |
+
elif api == 'openai':
|
455 |
+
print("*** OpenAI API Key ***")
|
456 |
+
try:
|
457 |
+
if run_api_tests('openai'):
|
458 |
+
with message_loc:
|
459 |
+
st.success("OpenAI API Key Valid :white_check_mark:")
|
460 |
+
else:
|
461 |
+
with message_loc:
|
462 |
+
st.error("OpenAI API Key Failed:exclamation:")
|
463 |
+
return False
|
464 |
+
except Exception as e:
|
465 |
+
with message_loc:
|
466 |
+
st.error(f"OpenAI API Key Failed:exclamation: {e}")
|
467 |
+
|
468 |
+
elif api == 'azure_openai':
|
469 |
+
print("*** Azure OpenAI API Key ***")
|
470 |
+
try:
|
471 |
+
if run_api_tests('azure_openai'):
|
472 |
+
with message_loc:
|
473 |
+
st.success("Azure OpenAI API Key Valid :white_check_mark:")
|
474 |
+
else:
|
475 |
+
with message_loc:
|
476 |
+
st.error(f"Azure OpenAI API Key Failed:exclamation:")
|
477 |
+
return False
|
478 |
+
except Exception as e:
|
479 |
+
with message_loc:
|
480 |
+
st.error(f"Azure OpenAI API Key Failed:exclamation: {e}")
|
481 |
+
elif api == 'palm':
|
482 |
+
print("*** Google PaLM 2 API Key ***")
|
483 |
+
try:
|
484 |
+
if run_api_tests('palm'):
|
485 |
+
with message_loc:
|
486 |
+
st.success("Google PaLM 2 API Key Valid :white_check_mark:")
|
487 |
+
else:
|
488 |
+
with message_loc:
|
489 |
+
st.error("Google PaLM 2 API Key Failed:exclamation:")
|
490 |
+
return False
|
491 |
+
except Exception as e:
|
492 |
+
with message_loc:
|
493 |
+
st.error(f"Google PaLM 2 API Key Failed:exclamation: {e}")
|
494 |
+
|
495 |
+
|
496 |
+
def save_changes_to_API_keys(cfg_private,openai_api_key,azure_openai_api_version,azure_openai_api_key,
|
497 |
+
azure_openai_api_base,azure_openai_organization,azure_openai_api_type,google_vision,google_palm):
|
498 |
+
# Update the configuration dictionary with the new values
|
499 |
+
cfg_private['openai']['OPENAI_API_KEY'] = openai_api_key
|
500 |
+
|
501 |
+
cfg_private['openai_azure']['api_version'] = azure_openai_api_version
|
502 |
+
cfg_private['openai_azure']['openai_api_key'] = azure_openai_api_key
|
503 |
+
cfg_private['openai_azure']['openai_api_base'] = azure_openai_api_base
|
504 |
+
cfg_private['openai_azure']['openai_organization'] = azure_openai_organization
|
505 |
+
cfg_private['openai_azure']['openai_api_type'] = azure_openai_api_type
|
506 |
+
|
507 |
+
cfg_private['google_cloud']['path_json_file'] = google_vision
|
508 |
+
|
509 |
+
cfg_private['google_palm']['google_palm_api'] = google_palm
|
510 |
+
# Call the function to write the updated configuration to the YAML file
|
511 |
+
write_config_file(cfg_private, st.session_state.dir_home, filename="PRIVATE_DATA.yaml")
|
512 |
+
st.session_state.private_file = does_private_file_exist()
|
513 |
+
|
514 |
+
# Function to load a YAML file and update session_state
|
515 |
+
def load_prompt_yaml(filename):
|
516 |
+
with open(filename, 'r') as file:
|
517 |
+
st.session_state['prompt_info'] = yaml.safe_load(file)
|
518 |
+
st.session_state['instructions'] = st.session_state['prompt_info'].get('instructions', st.session_state['default_instructions'])
|
519 |
+
st.session_state['json_formatting_instructions'] = st.session_state['prompt_info'].get('json_formatting_instructions', st.session_state['default_json_formatting_instructions'] )
|
520 |
+
st.session_state['rules'] = st.session_state['prompt_info'].get('rules', {})
|
521 |
+
st.session_state['mapping'] = st.session_state['prompt_info'].get('mapping', {})
|
522 |
+
st.session_state['LLM'] = st.session_state['prompt_info'].get('LLM', 'gpt')
|
523 |
+
|
524 |
+
# Placeholder:
|
525 |
+
st.session_state['assigned_columns'] = list(chain.from_iterable(st.session_state['mapping'].values()))
|
526 |
+
|
527 |
+
def save_prompt_yaml(filename):
|
528 |
+
yaml_content = {
|
529 |
+
'instructions': st.session_state['instructions'],
|
530 |
+
'json_formatting_instructions': st.session_state['json_formatting_instructions'],
|
531 |
+
'rules': st.session_state['rules'],
|
532 |
+
'mapping': st.session_state['mapping'],
|
533 |
+
'LLM': st.session_state['LLM']
|
534 |
+
}
|
535 |
+
|
536 |
+
dir_prompt = os.path.join(st.session_state.dir_home, 'custom_prompts')
|
537 |
+
filepath = os.path.join(dir_prompt, f"{filename}.yaml")
|
538 |
+
|
539 |
+
with open(filepath, 'w') as file:
|
540 |
+
yaml.safe_dump(yaml_content, file)
|
541 |
+
|
542 |
+
st.success(f"Prompt saved as '{filename}.yaml'.")
|
543 |
+
|
544 |
+
def check_unique_mapping_assignments():
|
545 |
+
if len(st.session_state['assigned_columns']) != len(set(st.session_state['assigned_columns'])):
|
546 |
+
st.error("Each column name must be assigned to only one category.")
|
547 |
+
return False
|
548 |
+
else:
|
549 |
+
st.success("Mapping confirmed.")
|
550 |
+
return True
|
551 |
+
|
552 |
+
def check_prompt_yaml_filename(fname):
|
553 |
+
# Check if the filename only contains letters, numbers, underscores, and dashes
|
554 |
+
pattern = r'^[\w-]+$'
|
555 |
+
|
556 |
+
# The \w matches any alphanumeric character and is equivalent to the character class [a-zA-Z0-9_].
|
557 |
+
# The hyphen - is literally matched.
|
558 |
+
|
559 |
+
if re.match(pattern, fname):
|
560 |
+
return True
|
561 |
+
else:
|
562 |
+
return False
|
563 |
+
|
564 |
+
|
565 |
+
def btn_load_prompt(selected_yaml_file, dir_prompt):
|
566 |
+
if selected_yaml_file:
|
567 |
+
yaml_file_path = os.path.join(dir_prompt, selected_yaml_file)
|
568 |
+
load_prompt_yaml(yaml_file_path)
|
569 |
+
elif not selected_yaml_file:
|
570 |
+
# Directly assigning default values since no file is selected
|
571 |
+
st.session_state['prompt_info'] = {}
|
572 |
+
st.session_state['instructions'] = st.session_state['default_instructions']
|
573 |
+
st.session_state['json_formatting_instructions'] = st.session_state['default_json_formatting_instructions']
|
574 |
+
st.session_state['rules'] = {}
|
575 |
+
st.session_state['LLM'] = 'gpt'
|
576 |
+
|
577 |
+
st.session_state['assigned_columns'] = []
|
578 |
+
|
579 |
+
st.session_state['prompt_info'] = {
|
580 |
+
'instructions': st.session_state['instructions'],
|
581 |
+
'json_formatting_instructions': st.session_state['json_formatting_instructions'],
|
582 |
+
'rules': st.session_state['rules'],
|
583 |
+
'mapping': st.session_state['mapping'],
|
584 |
+
'LLM': st.session_state['LLM']
|
585 |
+
}
|
586 |
+
|
587 |
+
def build_LLM_prompt_config():
|
588 |
+
st.session_state['assigned_columns'] = []
|
589 |
+
st.session_state['default_instructions'] = """1. Refactor the unstructured OCR text into a dictionary based on the JSON structure outlined below.
|
590 |
+
2. You should map the unstructured OCR text to the appropriate JSON key and then populate the field based on its rules.
|
591 |
+
3. Some JSON key fields are permitted to remain empty if the corresponding information is not found in the unstructured OCR text.
|
592 |
+
4. Ignore any information in the OCR text that doesn't fit into the defined JSON structure.
|
593 |
+
5. Duplicate dictionary fields are not allowed.
|
594 |
+
6. Ensure that all JSON keys are in lowercase.
|
595 |
+
7. Ensure that new JSON field values follow sentence case capitalization.
|
596 |
+
8. Ensure all key-value pairs in the JSON dictionary strictly adhere to the format and data types specified in the template.
|
597 |
+
9. Ensure the output JSON string is valid JSON format. It should not have trailing commas or unquoted keys.
|
598 |
+
10. Only return a JSON dictionary represented as a string. You should not explain your answer."""
|
599 |
+
st.session_state['default_json_formatting_instructions'] = """The next section of instructions outlines how to format the JSON dictionary. The keys are the same as those of the final formatted JSON object.
|
600 |
+
For each key there is a format requirement that specifies how to transcribe the information for that key.
|
601 |
+
The possible formatting options are:
|
602 |
+
1. "verbatim transcription" - field is populated with verbatim text from the unformatted OCR.
|
603 |
+
2. "spell check transcription" - field is populated with spelling corrected text from the unformatted OCR.
|
604 |
+
3. "boolean yes no" - field is populated with only yes or no.
|
605 |
+
4. "boolean 1 0" - field is populated with only 1 or 0.
|
606 |
+
5. "integer" - field is populated with only an integer.
|
607 |
+
6. "[list]" - field is populated from one of the values in the list.
|
608 |
+
7. "yyyy-mm-dd" - field is populated with a date in the format year-month-day.
|
609 |
+
The desired null value is also given. Populate the field with the null value of the information for that key is not present in the unformatted OCR text."""
|
610 |
+
|
611 |
+
# Start building the Streamlit app
|
612 |
+
col_prompt_main_left, ___, col_prompt_main_right = st.columns([6,1,3])
|
613 |
+
|
614 |
+
|
615 |
+
with col_prompt_main_left:
|
616 |
+
|
617 |
+
st.title("Custom LLM Prompt Builder")
|
618 |
+
st.subheader('About')
|
619 |
+
st.write("This form allows you to craft a prompt for your specific task.")
|
620 |
+
st.subheader('How it works')
|
621 |
+
st.write("1. Edit this page until you are happy with your instructions. We recommend looking at the basic structure, writing down your prompt inforamtion in a Word document so that it does not randomly disappear, and then copying and pasting that info into this form once your whole prompt structure is defined.")
|
622 |
+
st.write("2. After you enter all of your prompt instructions, click 'Save' and give your file a name.")
|
623 |
+
st.write("3. This file will be saved as a yaml configuration file in the `..VoucherVision/custom_prompts` folder.")
|
624 |
+
st.write("4. When you go back the main VoucherVision page you will now see your custom prompt available in the 'Prompt Version' dropdown menu.")
|
625 |
+
st.write("5. Select your custom prompt. Note, your prompt will only be available for the LLM that you set when filling out the form below.")
|
626 |
+
|
627 |
+
|
628 |
+
dir_prompt = os.path.join(st.session_state.dir_home, 'custom_prompts')
|
629 |
+
yaml_files = [f for f in os.listdir(dir_prompt) if f.endswith('.yaml')]
|
630 |
+
col_load_text, col_load_btn = st.columns([8,2])
|
631 |
+
with col_load_text:
|
632 |
+
# Dropdown for selecting a YAML file
|
633 |
+
selected_yaml_file = st.selectbox('Select a prompt YAML file to load:', [''] + yaml_files)
|
634 |
+
with col_load_btn:
|
635 |
+
st.write('##')
|
636 |
+
# Button to load the selected prompt
|
637 |
+
st.button('Load Prompt', on_click=btn_load_prompt, args=[selected_yaml_file, dir_prompt])
|
638 |
+
|
639 |
+
|
640 |
+
|
641 |
+
# Define the options for the dropdown
|
642 |
+
llm_options = ['gpt', 'palm']
|
643 |
+
# Create the dropdown and set the value to session_state['LLM']
|
644 |
+
st.session_state['LLM'] = st.selectbox('Set LLM:', llm_options, index=llm_options.index(st.session_state.get('LLM', 'gpt')))
|
645 |
+
|
646 |
+
|
647 |
+
|
648 |
+
# Instructions Section
|
649 |
+
st.header("Instructions")
|
650 |
+
st.write("These are the general instructions that guide the LLM through the transcription task. We recommend using the default instructions unless you have a specific reason to change them.")
|
651 |
+
|
652 |
+
st.session_state['instructions'] = st.text_area("Enter instructions:", value=st.session_state['default_instructions'].strip(), height=350, disabled=True)
|
653 |
+
|
654 |
+
st.write('---')
|
655 |
+
|
656 |
+
# Column Instructions Section
|
657 |
+
st.header("JSON Formatting Instructions")
|
658 |
+
st.write("The following section tells the LLM how we want to structure the JSON dictionary. We do not recommend changing this section because it would likely result in unstable and inconsistent behavior.")
|
659 |
+
st.session_state['json_formatting_instructions'] = st.text_area("Enter column instructions:", value=st.session_state['default_json_formatting_instructions'], height=350, disabled=True)
|
660 |
+
|
661 |
+
|
662 |
+
|
663 |
+
|
664 |
+
|
665 |
+
st.write('---')
|
666 |
+
col_left, col_right = st.columns([6,4])
|
667 |
+
with col_left:
|
668 |
+
st.subheader('Add/Edit Columns')
|
669 |
+
|
670 |
+
# Initialize rules in session state if not already present
|
671 |
+
if 'rules' not in st.session_state or not st.session_state['rules']:
|
672 |
+
st.session_state['rules']['Dictionary'] = {
|
673 |
+
"catalog_number": {
|
674 |
+
"format": "verbatim transcription",
|
675 |
+
"null_value": "",
|
676 |
+
"description": "The barcode identifier, typically a number with at least 6 digits, but fewer than 30 digits."
|
677 |
+
}
|
678 |
+
}
|
679 |
+
st.session_state['rules']['SpeciesName'] = {
|
680 |
+
"taxonomy": ["Genus_species"]
|
681 |
+
}
|
682 |
+
|
683 |
+
# Layout for adding a new column name
|
684 |
+
# col_text, col_textbtn = st.columns([8, 2])
|
685 |
+
# with col_text:
|
686 |
+
new_column_name = st.text_input("Enter a new column name:")
|
687 |
+
# with col_textbtn:
|
688 |
+
# st.write('##')
|
689 |
+
if st.button("Add New Column") and new_column_name:
|
690 |
+
if new_column_name not in st.session_state['rules']['Dictionary']:
|
691 |
+
st.session_state['rules']['Dictionary'][new_column_name] = {"format": "", "null_value": "", "description": ""}
|
692 |
+
st.success(f"New column '{new_column_name}' added. Now you can edit its properties.")
|
693 |
+
else:
|
694 |
+
st.error("Column name already exists. Please enter a unique column name.")
|
695 |
+
|
696 |
+
# Get columns excluding the protected "catalog_number"
|
697 |
+
st.write('#')
|
698 |
+
editable_columns = [col for col in st.session_state['rules']['Dictionary'] if col != "catalog_number"]
|
699 |
+
column_name = st.selectbox("Select a column to edit:", [""] + editable_columns)
|
700 |
+
|
701 |
+
# Handle rules editing
|
702 |
+
current_rule = st.session_state['rules']['Dictionary'].get(column_name, {
|
703 |
+
"format": "",
|
704 |
+
"null_value": "",
|
705 |
+
"description": ""
|
706 |
+
})
|
707 |
+
|
708 |
+
if 'selected_column' not in st.session_state:
|
709 |
+
st.session_state['selected_column'] = column_name
|
710 |
+
|
711 |
+
|
712 |
+
|
713 |
+
|
714 |
+
# Form for input fields
|
715 |
+
with st.form(key='rule_form'):
|
716 |
+
format_options = ["verbatim transcription", "spell check transcription", "boolean yes no", "boolean 1 0", "integer", "[list]", "yyyy-mm-dd"]
|
717 |
+
current_rule["format"] = st.selectbox("Format:", format_options, index=format_options.index(current_rule["format"]) if current_rule["format"] else 0)
|
718 |
+
current_rule["null_value"] = st.text_input("Null value:", value=current_rule["null_value"])
|
719 |
+
current_rule["description"] = st.text_area("Description:", value=current_rule["description"])
|
720 |
+
commit_button = st.form_submit_button("Commit Column")
|
721 |
+
|
722 |
+
default_rule = {
|
723 |
+
"format": format_options[0], # default format
|
724 |
+
"null_value": "", # default null value
|
725 |
+
"description": "", # default description
|
726 |
+
}
|
727 |
+
if st.session_state['selected_column'] != column_name:
|
728 |
+
# Column has changed. Update the session_state selected column.
|
729 |
+
st.session_state['selected_column'] = column_name
|
730 |
+
# Reset the current rule to the default for this new column, or a blank rule if not set.
|
731 |
+
current_rule = st.session_state['rules']['Dictionary'].get(column_name, default_rule.copy())
|
732 |
+
|
733 |
+
# Handle commit action
|
734 |
+
if commit_button and column_name:
|
735 |
+
# Commit the rules to the session state.
|
736 |
+
st.session_state['rules']['Dictionary'][column_name] = current_rule.copy()
|
737 |
+
st.success(f"Column '{column_name}' added/updated in rules.")
|
738 |
+
|
739 |
+
# Force the form to reset by clearing the fields from the session state
|
740 |
+
st.session_state.pop('selected_column', None) # Clear the selected column to force reset
|
741 |
+
|
742 |
+
# st.session_state['rules'][column_name] = current_rule
|
743 |
+
# st.success(f"Column '{column_name}' added/updated in rules.")
|
744 |
+
|
745 |
+
# # Reset current_rule to default values for the next input
|
746 |
+
# current_rule["format"] = default_rule["format"]
|
747 |
+
# current_rule["null_value"] = default_rule["null_value"]
|
748 |
+
# current_rule["description"] = default_rule["description"]
|
749 |
+
|
750 |
+
# # To ensure that the form fields are reset, we can clear them from the session state
|
751 |
+
# for key in current_rule.keys():
|
752 |
+
# st.session_state[key] = default_rule[key]
|
753 |
+
|
754 |
+
# Layout for removing an existing column
|
755 |
+
# del_col, del_colbtn = st.columns([8, 2])
|
756 |
+
# with del_col:
|
757 |
+
delete_column_name = st.selectbox("Select a column to delete:", [""] + editable_columns, key='delete_column')
|
758 |
+
# with del_colbtn:
|
759 |
+
# st.write('##')
|
760 |
+
if st.button("Delete Column") and delete_column_name:
|
761 |
+
del st.session_state['rules'][delete_column_name]
|
762 |
+
st.success(f"Column '{delete_column_name}' removed from rules.")
|
763 |
+
|
764 |
+
|
765 |
+
|
766 |
+
|
767 |
+
with col_right:
|
768 |
+
# Display the current state of the JSON rules
|
769 |
+
st.subheader('Formatted Columns')
|
770 |
+
st.json(st.session_state['rules']['Dictionary'])
|
771 |
+
|
772 |
+
# st.subheader('All Prompt Info')
|
773 |
+
# st.json(st.session_state['prompt_info'])
|
774 |
+
|
775 |
+
|
776 |
+
st.write('---')
|
777 |
+
|
778 |
+
|
779 |
+
col_left_mapping, col_right_mapping = st.columns([6,4])
|
780 |
+
with col_left_mapping:
|
781 |
+
st.header("Mapping")
|
782 |
+
st.write("Assign each column name to a single category.")
|
783 |
+
st.session_state['refresh_mapping'] = False
|
784 |
+
|
785 |
+
# Dynamically create a list of all column names that can be assigned
|
786 |
+
# This assumes that the column names are the keys in the dictionary under 'rules'
|
787 |
+
all_column_names = list(st.session_state['rules']['Dictionary'].keys())
|
788 |
+
|
789 |
+
categories = ['TAXONOMY', 'GEOGRAPHY', 'LOCALITY', 'COLLECTING', 'MISCELLANEOUS']
|
790 |
+
if ('mapping' not in st.session_state) or (st.session_state['mapping'] == {}):
|
791 |
+
st.session_state['mapping'] = {category: [] for category in categories}
|
792 |
+
for category in categories:
|
793 |
+
# Filter out the already assigned columns
|
794 |
+
available_columns = [col for col in all_column_names if col not in st.session_state['assigned_columns'] or col in st.session_state['mapping'].get(category, [])]
|
795 |
+
|
796 |
+
# Ensure the current mapping is a subset of the available options
|
797 |
+
current_mapping = [col for col in st.session_state['mapping'].get(category, []) if col in available_columns]
|
798 |
+
|
799 |
+
# Provide a safe default if the current mapping is empty or contains invalid options
|
800 |
+
safe_default = current_mapping if all(col in available_columns for col in current_mapping) else []
|
801 |
+
|
802 |
+
# Create a multi-select widget for the category with a safe default
|
803 |
+
selected_columns = st.multiselect(
|
804 |
+
f"Select columns for {category}:",
|
805 |
+
available_columns,
|
806 |
+
default=safe_default,
|
807 |
+
key=f"mapping_{category}"
|
808 |
+
)
|
809 |
+
# Update the assigned_columns based on the selections
|
810 |
+
for col in current_mapping:
|
811 |
+
if col not in selected_columns and col in st.session_state['assigned_columns']:
|
812 |
+
st.session_state['assigned_columns'].remove(col)
|
813 |
+
st.session_state['refresh_mapping'] = True
|
814 |
+
|
815 |
+
for col in selected_columns:
|
816 |
+
if col not in st.session_state['assigned_columns']:
|
817 |
+
st.session_state['assigned_columns'].append(col)
|
818 |
+
st.session_state['refresh_mapping'] = True
|
819 |
+
|
820 |
+
# Update the mapping in session state when there's a change
|
821 |
+
st.session_state['mapping'][category] = selected_columns
|
822 |
+
if st.session_state['refresh_mapping']:
|
823 |
+
st.session_state['refresh_mapping'] = False
|
824 |
+
|
825 |
+
# Button to confirm and save the mapping configuration
|
826 |
+
if st.button('Confirm Mapping'):
|
827 |
+
if check_unique_mapping_assignments():
|
828 |
+
# Proceed with further actions since the mapping is confirmed and unique
|
829 |
+
pass
|
830 |
+
|
831 |
+
with col_right_mapping:
|
832 |
+
# Display the current state of the JSON rules
|
833 |
+
st.subheader('Formatted Column Maps')
|
834 |
+
st.json(st.session_state['mapping'])
|
835 |
+
|
836 |
+
|
837 |
+
col_left_save, col_right_save = st.columns([6,4])
|
838 |
+
with col_left_save:
|
839 |
+
# Input for new file name
|
840 |
+
new_filename = st.text_input("Enter filename to save your prompt as a configuration YAML:",placeholder='my_prompt_name')
|
841 |
+
# Button to save the new YAML file
|
842 |
+
if st.button('Save YAML', type='primary'):
|
843 |
+
if new_filename:
|
844 |
+
if check_unique_mapping_assignments():
|
845 |
+
if check_prompt_yaml_filename(new_filename):
|
846 |
+
save_prompt_yaml(new_filename)
|
847 |
+
else:
|
848 |
+
st.error("File name can only contain letters, numbers, underscores, and dashes. Cannot contain spaces.")
|
849 |
+
else:
|
850 |
+
st.error("Mapping contains an error. Make sure that each column is assigned to only ***one*** category.")
|
851 |
+
else:
|
852 |
+
st.error("Please enter a filename.")
|
853 |
+
|
854 |
+
if st.button('Exit'):
|
855 |
+
st.session_state.proceed_to_build_llm_prompt = False
|
856 |
+
st.session_state.proceed_to_main = True
|
857 |
+
st.rerun()
|
858 |
+
with col_prompt_main_right:
|
859 |
+
st.subheader('All Prompt Components')
|
860 |
+
st.session_state['prompt_info'] = {
|
861 |
+
'instructions': st.session_state['instructions'],
|
862 |
+
'json_formatting_instructions': st.session_state['json_formatting_instructions'],
|
863 |
+
'rules': st.session_state['rules'],
|
864 |
+
'mapping': st.session_state['mapping'],
|
865 |
+
'LLM': st.session_state['LLM']
|
866 |
+
}
|
867 |
+
st.json(st.session_state['prompt_info'])
|
868 |
+
|
869 |
+
def save_yaml(content, filename="rules_config.yaml"):
|
870 |
+
with open(filename, 'w') as file:
|
871 |
+
yaml.dump(content, file)
|
872 |
+
|
873 |
+
def show_header_welcome():
|
874 |
+
st.session_state.logo_path = os.path.join(st.session_state.dir_home, 'img','logo.png')
|
875 |
+
st.session_state.logo = Image.open(st.session_state.logo_path)
|
876 |
+
st.image(st.session_state.logo, width=250)
|
877 |
+
|
878 |
+
def content_header():
|
879 |
+
col_run_1, col_run_2, col_run_3 = st.columns([4,2,2])
|
880 |
+
col_test = st.container()
|
881 |
+
|
882 |
+
st.write("")
|
883 |
+
st.write("")
|
884 |
+
st.write("")
|
885 |
+
st.write("")
|
886 |
+
st.subheader("Overall Progress")
|
887 |
+
col_run_info_1 = st.columns([1])[0]
|
888 |
+
st.write("")
|
889 |
+
st.write("")
|
890 |
+
st.write("")
|
891 |
+
st.write("")
|
892 |
+
st.header("Configuration Settings")
|
893 |
+
|
894 |
+
with col_run_info_1:
|
895 |
+
# Progress
|
896 |
+
# Progress
|
897 |
+
# st.subheader('Project')
|
898 |
+
# bar = st.progress(0)
|
899 |
+
# new_text = st.empty() # Placeholder for current step name
|
900 |
+
# progress_report = ProgressReportVV(bar, new_text, n_images=10)
|
901 |
+
|
902 |
+
# Progress
|
903 |
+
overall_progress_bar = st.progress(0)
|
904 |
+
text_overall = st.empty() # Placeholder for current step name
|
905 |
+
st.subheader('Transcription Progress')
|
906 |
+
batch_progress_bar = st.progress(0)
|
907 |
+
text_batch = st.empty() # Placeholder for current step name
|
908 |
+
progress_report = ProgressReport(overall_progress_bar, batch_progress_bar, text_overall, text_batch)
|
909 |
+
st.info("***Note:*** There is a known bug with tabs in Streamlit. If you update an input field it may take you back to the 'Project Settings' tab. Changes that you made are saved, it's just an annoying glitch. We are aware of this issue and will fix it as soon as we can.")
|
910 |
+
st.write("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`")
|
911 |
+
|
912 |
+
|
913 |
+
with col_run_1:
|
914 |
+
show_header_welcome()
|
915 |
+
st.subheader('Run VoucherVision')
|
916 |
+
if check_if_usable():
|
917 |
+
if st.button("Start Processing", type='primary'):
|
918 |
+
|
919 |
+
# First, write the config file.
|
920 |
+
write_config_file(st.session_state.config, st.session_state.dir_home, filename="VoucherVision.yaml")
|
921 |
+
|
922 |
+
path_custom_prompts = os.path.join(st.session_state.dir_home,'custom_prompts',st.session_state.config['leafmachine']['project']['prompt_version'])
|
923 |
+
# Call the machine function.
|
924 |
+
last_JSON_response, total_cost = voucher_vision(None, st.session_state.dir_home, path_custom_prompts, None, progress_report,path_api_cost=os.path.join(st.session_state.dir_home,'api_cost','api_cost.yaml'))
|
925 |
+
|
926 |
+
if total_cost:
|
927 |
+
st.success(f":money_with_wings: This run cost :heavy_dollar_sign:{total_cost:.4f}")
|
928 |
+
|
929 |
+
# Format the JSON string for display.
|
930 |
+
if last_JSON_response is None:
|
931 |
+
st.markdown(f"Last JSON object in the batch: NONE")
|
932 |
+
else:
|
933 |
+
try:
|
934 |
+
formatted_json = json.dumps(json.loads(last_JSON_response), indent=4)
|
935 |
+
except:
|
936 |
+
formatted_json = json.dumps(last_JSON_response, indent=4)
|
937 |
+
st.markdown(f"Last JSON object in the batch:\n```\n{formatted_json}\n```")
|
938 |
+
st.balloons()
|
939 |
+
|
940 |
+
else:
|
941 |
+
st.button("Start Processing", type='primary', disabled=True)
|
942 |
+
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.")
|
943 |
+
|
944 |
+
with col_run_2:
|
945 |
+
st.subheader('Run Tests', help="")
|
946 |
+
st.write('We include a single image for testing. If you want to test all of the available prompts and LLMs on a different set of images, copy your images into `../VoucherVision/demo/demo_images`.')
|
947 |
+
if st.button("Test GPT"):
|
948 |
+
progress_report.set_n_overall(TestOptionsGPT.get_length())
|
949 |
+
test_results, JSON_results = run_demo_tests_GPT(progress_report)
|
950 |
+
with col_test:
|
951 |
+
display_test_results(test_results, JSON_results, 'gpt')
|
952 |
+
st.balloons()
|
953 |
+
|
954 |
+
if st.button("Test PaLM2"):
|
955 |
+
progress_report.set_n_overall(TestOptionsPalm.get_length())
|
956 |
+
test_results, JSON_results = run_demo_tests_Palm(progress_report)
|
957 |
+
with col_test:
|
958 |
+
display_test_results(test_results, JSON_results, 'palm')
|
959 |
+
st.balloons()
|
960 |
+
|
961 |
+
with col_run_3:
|
962 |
+
st.subheader('Check GPU')
|
963 |
+
if st.button("GPU"):
|
964 |
+
success, info = test_GPU()
|
965 |
+
|
966 |
+
if success:
|
967 |
+
st.balloons()
|
968 |
+
for message in info:
|
969 |
+
st.success(message)
|
970 |
+
else:
|
971 |
+
for message in info:
|
972 |
+
st.error(message)
|
973 |
+
|
974 |
+
def content_tab_settings():
|
975 |
+
st.header('Project')
|
976 |
+
col_project_1, col_project_2 = st.columns([4,2])
|
977 |
+
|
978 |
+
st.write("---")
|
979 |
+
st.header('Input Images')
|
980 |
+
col_local_1, col_local_2 = st.columns([4,2])
|
981 |
+
|
982 |
+
# st.write("---")
|
983 |
+
# st.header('Modules')
|
984 |
+
# col_m1, col_m2 = st.columns(2)
|
985 |
+
|
986 |
+
st.write("---")
|
987 |
+
st.header('Cropped Components')
|
988 |
+
col_cropped_1, col_cropped_2 = st.columns([4,4])
|
989 |
+
|
990 |
+
os.path.join(st.session_state.dir_home, )
|
991 |
+
### Project
|
992 |
+
with col_project_1:
|
993 |
+
st.session_state.config['leafmachine']['project']['run_name'] = st.text_input("Run name", st.session_state.config['leafmachine']['project'].get('run_name', ''))
|
994 |
+
st.session_state.config['leafmachine']['project']['dir_output'] = st.text_input("Output directory", st.session_state.config['leafmachine']['project'].get('dir_output', ''))
|
995 |
+
|
996 |
+
### Input Images Local
|
997 |
+
with col_local_1:
|
998 |
+
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', ''))
|
999 |
+
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)
|
1000 |
+
st.write("---")
|
1001 |
+
st.subheader('LLM Version')
|
1002 |
+
st.markdown(
|
1003 |
+
"""
|
1004 |
+
***Note:*** GPT-4 is 20x more expensive than GPT-3.5
|
1005 |
+
"""
|
1006 |
+
)
|
1007 |
+
st.session_state.config['leafmachine']['LLM_version'] = st.selectbox("LLM version", ["GPT 4", "GPT 3.5", "Azure GPT 4", "Azure GPT 3.5", "PaLM 2"], index=["GPT 4", "GPT 3.5", "Azure GPT 4", "Azure GPT 3.5", "PaLM 2"].index(st.session_state.config['leafmachine'].get('LLM_version', 'Azure GPT 4')))
|
1008 |
+
|
1009 |
+
st.write("---")
|
1010 |
+
st.subheader('Prompt Version')
|
1011 |
+
versions, default_version = get_prompt_versions(st.session_state.config['leafmachine']['LLM_version'])
|
1012 |
+
|
1013 |
+
if versions:
|
1014 |
+
selected_version = st.session_state.config['leafmachine']['project'].get('prompt_version', default_version)
|
1015 |
+
if selected_version not in versions:
|
1016 |
+
selected_version = default_version
|
1017 |
+
st.session_state.config['leafmachine']['project']['prompt_version'] = st.selectbox("Prompt Version", versions, index=versions.index(selected_version))
|
1018 |
+
|
1019 |
+
# if st.session_state.config['leafmachine']['LLM_version'] in ["GPT 4", "GPT 3.5", "Azure GPT 4", "Azure GPT 3.5",]:
|
1020 |
+
# st.session_state.config['leafmachine']['project']['prompt_version'] = st.selectbox("Prompt Version", ["Version 1", "Version 1 No Domain Knowledge", "Version 2"], index=["Version 1", "Version 1 No Domain Knowledge", "Version 2"].index(st.session_state.config['leafmachine']['project'].get('prompt_version', "Version 2")))
|
1021 |
+
# elif st.session_state.config['leafmachine']['LLM_version'] in ["PaLM 2",]:
|
1022 |
+
# st.session_state.config['leafmachine']['project']['prompt_version'] = st.selectbox("Prompt Version", ["Version 1 PaLM 2", "Version 1 PaLM 2 No Domain Knowledge", "Version 2 PaLM 2"], index=["Version 1 PaLM 2", "Version 1 PaLM 2 No Domain Knowledge", "Version 2 PaLM 2"].index(st.session_state.config['leafmachine']['project'].get('prompt_version', "Version 2 PaLM 2")))
|
1023 |
+
|
1024 |
+
### Modules
|
1025 |
+
# with col_m1:
|
1026 |
+
# st.session_state.config['leafmachine']['modules']['specimen_crop'] = st.checkbox("Specimen Close-up", st.session_state.config['leafmachine']['modules'].get('specimen_crop', True),disabled=True)
|
1027 |
+
|
1028 |
+
### cropped_components
|
1029 |
+
# with col_cropped_1:
|
1030 |
+
# st.session_state.config['leafmachine']['cropped_components']['do_save_cropped_annotations'] = st.checkbox("Save cropped components as images", st.session_state.config['leafmachine']['cropped_components'].get('do_save_cropped_annotations', True), disabled=True)
|
1031 |
+
# st.session_state.config['leafmachine']['cropped_components']['save_per_image'] = st.checkbox("Save cropped components grouped by specimen", st.session_state.config['leafmachine']['cropped_components'].get('save_per_image', False), disabled=True)
|
1032 |
+
# st.session_state.config['leafmachine']['cropped_components']['save_per_annotation_class'] = st.checkbox("Save cropped components grouped by type", st.session_state.config['leafmachine']['cropped_components'].get('save_per_annotation_class', True), disabled=True)
|
1033 |
+
# st.session_state.config['leafmachine']['cropped_components']['binarize_labels'] = st.checkbox("Binarize labels", st.session_state.config['leafmachine']['cropped_components'].get('binarize_labels', False), disabled=True)
|
1034 |
+
# st.session_state.config['leafmachine']['cropped_components']['binarize_labels_skeletonize'] = st.checkbox("Binarize and skeletonize labels", st.session_state.config['leafmachine']['cropped_components'].get('binarize_labels_skeletonize', False), disabled=True)
|
1035 |
+
|
1036 |
+
with col_cropped_1:
|
1037 |
+
default_crops = st.session_state.config['leafmachine']['cropped_components'].get('save_cropped_annotations', ['leaf_whole'])
|
1038 |
+
st.write("Prior to transcription, use LeafMachine2 to crop all labels from input images to create label collages for each specimen image. (Requires GPU)")
|
1039 |
+
st.session_state.config['leafmachine']['use_RGB_label_images'] = st.checkbox("Use LeafMachine2 label collage for transcriptions", st.session_state.config['leafmachine'].get('use_RGB_label_images', False))
|
1040 |
+
|
1041 |
+
st.session_state.config['leafmachine']['cropped_components']['save_cropped_annotations'] = st.multiselect("Components to crop",
|
1042 |
+
['ruler', 'barcode','label', 'colorcard','map','envelope','photo','attached_item','weights',
|
1043 |
+
'leaf_whole', 'leaf_partial', 'leaflet', 'seed_fruit_one', 'seed_fruit_many', 'flower_one', 'flower_many', 'bud','specimen','roots','wood'],default=default_crops)
|
1044 |
+
with col_cropped_2:
|
1045 |
+
ba = os.path.join(st.session_state.dir_home,'demo', 'ba','ba2.png')
|
1046 |
+
image = Image.open(ba)
|
1047 |
+
st.image(image, caption='LeafMachine2 Collage', output_format = "PNG")
|
1048 |
+
|
1049 |
+
def content_tab_component():
|
1050 |
+
st.header('Archival Components')
|
1051 |
+
ACD_version = st.selectbox("Archival Component Detector (ACD) Version", ["Version 2.1", "Version 2.2"])
|
1052 |
+
|
1053 |
+
ACD_confidence_default = int(st.session_state.config['leafmachine']['archival_component_detector']['minimum_confidence_threshold'] * 100)
|
1054 |
+
ACD_confidence = st.number_input("ACD Confidence Threshold (%)", min_value=0, max_value=100,value=ACD_confidence_default)
|
1055 |
+
st.session_state.config['leafmachine']['archival_component_detector']['minimum_confidence_threshold'] = float(ACD_confidence/100)
|
1056 |
+
|
1057 |
+
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))
|
1058 |
+
|
1059 |
+
st.session_state.config['leafmachine']['archival_component_detector']['ignore_objects_for_overlay'] = st.multiselect("Hide Archival Components in Prediction Overlay Images",
|
1060 |
+
['ruler', 'barcode','label', 'colorcard','map','envelope','photo','attached_item','weights',],
|
1061 |
+
default=[])
|
1062 |
+
|
1063 |
+
# Depending on the selected version, set the configuration
|
1064 |
+
if ACD_version == "Version 2.1":
|
1065 |
+
st.session_state.config['leafmachine']['archival_component_detector']['detector_type'] = 'Archival_Detector'
|
1066 |
+
st.session_state.config['leafmachine']['archival_component_detector']['detector_version'] = 'PREP_final'
|
1067 |
+
st.session_state.config['leafmachine']['archival_component_detector']['detector_iteration'] = 'PREP_final'
|
1068 |
+
st.session_state.config['leafmachine']['archival_component_detector']['detector_weights'] = 'best.pt'
|
1069 |
+
elif ACD_version == "Version 2.2": #TODO update this to version 2.2
|
1070 |
+
st.session_state.config['leafmachine']['archival_component_detector']['detector_type'] = 'Archival_Detector'
|
1071 |
+
st.session_state.config['leafmachine']['archival_component_detector']['detector_version'] = 'PREP_final'
|
1072 |
+
st.session_state.config['leafmachine']['archival_component_detector']['detector_iteration'] = 'PREP_final'
|
1073 |
+
st.session_state.config['leafmachine']['archival_component_detector']['detector_weights'] = 'best.pt'
|
1074 |
+
|
1075 |
+
|
1076 |
+
def content_tab_processing():
|
1077 |
+
st.header('Processing Options')
|
1078 |
+
col_processing_1, col_processing_2 = st.columns([2,2,])
|
1079 |
+
with col_processing_1:
|
1080 |
+
st.subheader('Compute Options')
|
1081 |
+
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=True)
|
1082 |
+
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.')
|
1083 |
+
with col_processing_2:
|
1084 |
+
st.subheader('Misc')
|
1085 |
+
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', ''))
|
1086 |
+
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', ''))
|
1087 |
+
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))
|
1088 |
+
|
1089 |
+
### Logging and Image Validation - col_v1
|
1090 |
+
st.header('Logging and Image Validation')
|
1091 |
+
col_v1, col_v2 = st.columns(2)
|
1092 |
+
with col_v1:
|
1093 |
+
st.session_state.config['leafmachine']['do']['check_for_illegal_filenames'] = st.checkbox("Check for illegal filenames", st.session_state.config['leafmachine']['do'].get('check_for_illegal_filenames', True))
|
1094 |
+
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))
|
1095 |
+
|
1096 |
+
st.session_state.config['leafmachine']['print']['verbose'] = st.checkbox("Print verbose", st.session_state.config['leafmachine']['print'].get('verbose', True))
|
1097 |
+
st.session_state.config['leafmachine']['print']['optional_warnings'] = st.checkbox("Show optional warnings", st.session_state.config['leafmachine']['print'].get('optional_warnings', True))
|
1098 |
+
|
1099 |
+
with col_v2:
|
1100 |
+
log_level = st.session_state.config['leafmachine']['logging'].get('log_level', None)
|
1101 |
+
log_level_display = log_level if log_level is not None else 'default'
|
1102 |
+
selected_log_level = st.selectbox("Logging Level", ['default', 'DEBUG', 'INFO', 'WARNING', 'ERROR'], index=['default', 'DEBUG', 'INFO', 'WARNING', 'ERROR'].index(log_level_display))
|
1103 |
+
|
1104 |
+
if selected_log_level == 'default':
|
1105 |
+
st.session_state.config['leafmachine']['logging']['log_level'] = None
|
1106 |
+
else:
|
1107 |
+
st.session_state.config['leafmachine']['logging']['log_level'] = selected_log_level
|
1108 |
+
|
1109 |
+
def content_tab_domain():
|
1110 |
+
st.header('Embeddings Database')
|
1111 |
+
col_emb_1, col_emb_2 = st.columns([4,2])
|
1112 |
+
with col_emb_1:
|
1113 |
+
st.markdown(
|
1114 |
+
"""
|
1115 |
+
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.
|
1116 |
+
- Note: Using domain knowledge may increase the chance that foreign text is included in the final transcription
|
1117 |
+
- Disabling this feature will show the LLM multiple examples of an empty JSON skeleton structure instead
|
1118 |
+
- Enabling this option requires a GPU with at least 8GB of VRAM
|
1119 |
+
- 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.
|
1120 |
+
"""
|
1121 |
+
)
|
1122 |
+
|
1123 |
+
st.write(f"Domain Knowledge is only available for the following prompts:")
|
1124 |
+
for available_prompts in PROMPTS_THAT_NEED_DOMAIN_KNOWLEDGE:
|
1125 |
+
st.markdown(f"- {available_prompts}")
|
1126 |
+
|
1127 |
+
if st.session_state.config['leafmachine']['project']['prompt_version'] in PROMPTS_THAT_NEED_DOMAIN_KNOWLEDGE:
|
1128 |
+
st.session_state.config['leafmachine']['project']['use_domain_knowledge'] = st.checkbox("Use domain knowledge", True, disabled=True)
|
1129 |
+
else:
|
1130 |
+
st.session_state.config['leafmachine']['project']['use_domain_knowledge'] = st.checkbox("Use domain knowledge", False, disabled=True)
|
1131 |
+
|
1132 |
+
st.write("")
|
1133 |
+
if st.session_state.config['leafmachine']['project']['use_domain_knowledge']:
|
1134 |
+
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', ''))
|
1135 |
+
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))
|
1136 |
+
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', ''))
|
1137 |
+
else:
|
1138 |
+
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)
|
1139 |
+
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)
|
1140 |
+
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)
|
1141 |
+
|
1142 |
+
def render_expense_report_summary():
|
1143 |
+
expense_summary = st.session_state.expense_summary
|
1144 |
+
expense_report = st.session_state.expense_report
|
1145 |
+
st.header('Expense Report Summary')
|
1146 |
+
|
1147 |
+
if expense_summary:
|
1148 |
+
st.metric(label="Total Cost", value=f"${round(expense_summary['total_cost_sum'], 4):,}")
|
1149 |
+
col1, col2 = st.columns(2)
|
1150 |
+
|
1151 |
+
# Run count and total costs
|
1152 |
+
with col1:
|
1153 |
+
st.metric(label="Run Count", value=expense_summary['run_count'])
|
1154 |
+
st.metric(label="Tokens In", value=f"{expense_summary['tokens_in_sum']:,}")
|
1155 |
+
|
1156 |
+
# Token information
|
1157 |
+
with col2:
|
1158 |
+
st.metric(label="Total Images", value=expense_summary['n_images_sum'])
|
1159 |
+
st.metric(label="Tokens Out", value=f"{expense_summary['tokens_out_sum']:,}")
|
1160 |
+
|
1161 |
+
|
1162 |
+
# Calculate cost proportion per image for each API version
|
1163 |
+
st.subheader('Average Cost per Image by API Version')
|
1164 |
+
cost_labels = []
|
1165 |
+
cost_values = []
|
1166 |
+
total_images = 0
|
1167 |
+
cost_per_image_dict = {}
|
1168 |
+
# Iterate through the expense report to accumulate costs and image counts
|
1169 |
+
for index, row in expense_report.iterrows():
|
1170 |
+
api_version = row['api_version']
|
1171 |
+
total_cost = row['total_cost']
|
1172 |
+
n_images = row['n_images']
|
1173 |
+
total_images += n_images # Keep track of total images processed
|
1174 |
+
if api_version not in cost_per_image_dict:
|
1175 |
+
cost_per_image_dict[api_version] = {'total_cost': 0, 'n_images': 0}
|
1176 |
+
cost_per_image_dict[api_version]['total_cost'] += total_cost
|
1177 |
+
cost_per_image_dict[api_version]['n_images'] += n_images
|
1178 |
+
|
1179 |
+
api_versions = list(cost_per_image_dict.keys())
|
1180 |
+
colors = [COLORS_EXPENSE_REPORT[version] if version in COLORS_EXPENSE_REPORT else '#DDDDDD' for version in api_versions]
|
1181 |
+
|
1182 |
+
# Calculate the cost per image for each API version
|
1183 |
+
for version, cost_data in cost_per_image_dict.items():
|
1184 |
+
total_cost = cost_data['total_cost']
|
1185 |
+
n_images = cost_data['n_images']
|
1186 |
+
# Calculate the cost per image for this version
|
1187 |
+
cost_per_image = total_cost / n_images if n_images > 0 else 0
|
1188 |
+
cost_labels.append(version)
|
1189 |
+
cost_values.append(cost_per_image)
|
1190 |
+
# Generate the pie chart
|
1191 |
+
cost_pie_chart = go.Figure(data=[go.Pie(labels=cost_labels, values=cost_values, hole=.3)])
|
1192 |
+
# Update traces for custom text in hoverinfo, displaying cost with a dollar sign and two decimal places
|
1193 |
+
cost_pie_chart.update_traces(
|
1194 |
+
marker=dict(colors=colors),
|
1195 |
+
text=[f"${value:.2f}" for value in cost_values], # Formats the cost as a string with a dollar sign and two decimals
|
1196 |
+
textinfo='percent+label',
|
1197 |
+
hoverinfo='label+percent+text' # Adds custom text (formatted cost) to the hover information
|
1198 |
+
)
|
1199 |
+
st.plotly_chart(cost_pie_chart, use_container_width=True)
|
1200 |
+
|
1201 |
+
|
1202 |
+
|
1203 |
+
st.subheader('Proportion of Total Cost by API Version')
|
1204 |
+
cost_labels = []
|
1205 |
+
cost_proportions = []
|
1206 |
+
total_cost_by_version = {}
|
1207 |
+
# Sum the total cost for each API version
|
1208 |
+
for index, row in expense_report.iterrows():
|
1209 |
+
api_version = row['api_version']
|
1210 |
+
total_cost = row['total_cost']
|
1211 |
+
if api_version not in total_cost_by_version:
|
1212 |
+
total_cost_by_version[api_version] = 0
|
1213 |
+
total_cost_by_version[api_version] += total_cost
|
1214 |
+
# Calculate the combined total cost for all versions
|
1215 |
+
combined_total_cost = sum(total_cost_by_version.values())
|
1216 |
+
# Calculate the proportion of total cost for each API version
|
1217 |
+
for version, total_cost in total_cost_by_version.items():
|
1218 |
+
proportion = (total_cost / combined_total_cost) * 100 if combined_total_cost > 0 else 0
|
1219 |
+
cost_labels.append(version)
|
1220 |
+
cost_proportions.append(proportion)
|
1221 |
+
# Generate the pie chart
|
1222 |
+
cost_pie_chart = go.Figure(data=[go.Pie(labels=cost_labels, values=cost_proportions, hole=.3)])
|
1223 |
+
# Update traces for custom text in hoverinfo
|
1224 |
+
cost_pie_chart.update_traces(
|
1225 |
+
marker=dict(colors=colors),
|
1226 |
+
text=[f"${cost:.2f}" for cost in total_cost_by_version.values()], # This will format the cost to 2 decimal places
|
1227 |
+
textinfo='percent+label',
|
1228 |
+
hoverinfo='label+percent+text' # This tells Plotly to show the label, percent, and custom text (cost) on hover
|
1229 |
+
)
|
1230 |
+
st.plotly_chart(cost_pie_chart, use_container_width=True)
|
1231 |
+
|
1232 |
+
# API version usage percentages pie chart
|
1233 |
+
st.subheader('Runs by API Version')
|
1234 |
+
api_versions = list(expense_summary['api_version_percentages'].keys())
|
1235 |
+
percentages = [expense_summary['api_version_percentages'][version] for version in api_versions]
|
1236 |
+
pie_chart = go.Figure(data=[go.Pie(labels=api_versions, values=percentages, hole=.3)])
|
1237 |
+
pie_chart.update_layout(margin=dict(t=0, b=0, l=0, r=0))
|
1238 |
+
pie_chart.update_traces(marker=dict(colors=colors),)
|
1239 |
+
st.plotly_chart(pie_chart, use_container_width=True)
|
1240 |
+
|
1241 |
+
else:
|
1242 |
+
st.error('No expense report data available.')
|
1243 |
+
|
1244 |
+
def sidebar_content():
|
1245 |
+
try:
|
1246 |
+
validate_dir(os.path.join(st.session_state.dir_home,'expense_report'))
|
1247 |
+
st.session_state.expense_summary, st.session_state.expense_report = summarize_expense_report(os.path.join(st.session_state.dir_home,'expense_report','expense_report.csv'))
|
1248 |
+
render_expense_report_summary()
|
1249 |
+
except:
|
1250 |
+
st.header('Expense Report Summary')
|
1251 |
+
st.write('Available after first run...')
|
1252 |
+
|
1253 |
+
# # Check if the expense summary is available in the session state
|
1254 |
+
# if 'expense' not in st.session_state or st.session_state.expense is None:
|
1255 |
+
# st.sidebar.write('No expense report data available.')
|
1256 |
+
# return
|
1257 |
+
|
1258 |
+
# # Retrieve the expense report summary
|
1259 |
+
# expense_summary = st.session_state.expense
|
1260 |
+
|
1261 |
+
# # Display the expense report summary
|
1262 |
+
# st.sidebar.markdown('**Run Count**: ' + str(expense_summary['run_count']))
|
1263 |
+
|
1264 |
+
# # API version usage percentages
|
1265 |
+
# st.sidebar.markdown('**API Version Usage**:')
|
1266 |
+
# for version, percentage in expense_summary['api_version_percentages'].items():
|
1267 |
+
# st.sidebar.markdown(f'- {version}: {percentage:.2f}%')
|
1268 |
+
|
1269 |
+
# # Summary of costs and tokens
|
1270 |
+
# st.sidebar.markdown('**Total Cost**: $' + str(round(expense_summary['total_cost_sum'], 4)))
|
1271 |
+
# st.sidebar.markdown('**Tokens In**: ' + str(expense_summary['tokens_in_sum']))
|
1272 |
+
# st.sidebar.markdown('**Tokens Out**: ' + str(expense_summary['tokens_out_sum']))
|
1273 |
+
# # st.sidebar.markdown('**Rate In**: $' + str(round(expense_summary['rate_in_sum'], 2)) + ' per 1000 tokens')
|
1274 |
+
# # st.sidebar.markdown('**Rate Out**: $' + str(round(expense_summary['rate_out_sum'], 2)) + ' per 1000 tokens')
|
1275 |
+
# st.sidebar.markdown('**Cost In**: $' + str(round(expense_summary['cost_in_sum'], 4)))
|
1276 |
+
# st.sidebar.markdown('**Cost Out**: $' + str(round(expense_summary['cost_out_sum'], 4)))
|
1277 |
+
|
1278 |
+
def main():
|
1279 |
+
with st.sidebar:
|
1280 |
+
sidebar_content()
|
1281 |
+
# Main App
|
1282 |
+
content_header()
|
1283 |
+
|
1284 |
+
tab_settings, tab_prompt, tab_domain, tab_component, tab_processing, tab_private, tab_delete = st.tabs(["Project Settings", "Prompt Builder", "Domain Knowledge","Component Detector", "Processing Options", "API Keys", "Space-Saver"])
|
1285 |
+
|
1286 |
+
with tab_settings:
|
1287 |
+
content_tab_settings()
|
1288 |
+
|
1289 |
+
with tab_prompt:
|
1290 |
+
if st.button("Build Custom LLM Prompt"):
|
1291 |
+
st.session_state.proceed_to_build_llm_prompt = True
|
1292 |
+
st.rerun()
|
1293 |
+
|
1294 |
+
with tab_component:
|
1295 |
+
content_tab_component()
|
1296 |
+
|
1297 |
+
with tab_domain:
|
1298 |
+
content_tab_domain()
|
1299 |
+
|
1300 |
+
with tab_processing:
|
1301 |
+
content_tab_processing()
|
1302 |
+
|
1303 |
+
with tab_private:
|
1304 |
+
if st.button("Edit API Keys"):
|
1305 |
+
st.session_state.proceed_to_private = True
|
1306 |
+
st.rerun()
|
1307 |
+
|
1308 |
+
with tab_delete:
|
1309 |
+
create_space_saver()
|
1310 |
+
|
1311 |
+
st.set_page_config(layout="wide", page_icon='img/icon.ico', page_title='VoucherVision')
|
1312 |
+
|
1313 |
+
# Default YAML file path
|
1314 |
+
if 'config' not in st.session_state:
|
1315 |
+
st.session_state.config, st.session_state.dir_home = build_VV_config()
|
1316 |
+
setup_streamlit_config(st.session_state.dir_home)
|
1317 |
+
|
1318 |
+
if 'proceed_to_main' not in st.session_state:
|
1319 |
+
st.session_state.proceed_to_main = False # New state variable to control the flow
|
1320 |
+
|
1321 |
+
if 'proceed_to_build_llm_prompt' not in st.session_state:
|
1322 |
+
st.session_state.proceed_to_build_llm_prompt = False # New state variable to control the flow
|
1323 |
+
if 'proceed_to_private' not in st.session_state:
|
1324 |
+
st.session_state.proceed_to_private = False # New state variable to control the flow
|
1325 |
+
|
1326 |
+
if 'private_file' not in st.session_state:
|
1327 |
+
st.session_state.private_file = does_private_file_exist()
|
1328 |
+
if st.session_state.private_file:
|
1329 |
+
st.session_state.proceed_to_main = True
|
1330 |
+
|
1331 |
+
# Initialize session_state variables if they don't exist
|
1332 |
+
if 'prompt_info' not in st.session_state:
|
1333 |
+
st.session_state['prompt_info'] = {}
|
1334 |
+
if 'rules' not in st.session_state:
|
1335 |
+
st.session_state['rules'] = {}
|
1336 |
+
|
1337 |
+
if not st.session_state.private_file:
|
1338 |
+
create_private_file()
|
1339 |
+
elif st.session_state.proceed_to_build_llm_prompt:
|
1340 |
+
build_LLM_prompt_config()
|
1341 |
+
elif st.session_state.proceed_to_private:
|
1342 |
+
create_private_file()
|
1343 |
+
elif st.session_state.proceed_to_main:
|
1344 |
+
main()
|
bin/version.yml
ADDED
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
1 |
+
last_update: '2023-10-24'
|
2 |
+
version: v-2-1
|
create_desktop_shortcut.py
ADDED
@@ -0,0 +1,69 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os, sys
|
2 |
+
import win32com.client
|
3 |
+
import tkinter as tk
|
4 |
+
from tkinter import filedialog
|
5 |
+
from PIL import Image, ImageEnhance
|
6 |
+
|
7 |
+
def create_shortcut():
|
8 |
+
# Request user's confirmation
|
9 |
+
confirmation = input("Do you want to create a shortcut for the VoucherVision? (y/n): ")
|
10 |
+
|
11 |
+
if confirmation.lower() != "y":
|
12 |
+
print("Okay, no shortcut will be created.")
|
13 |
+
return
|
14 |
+
|
15 |
+
# Get the script path
|
16 |
+
script_path = os.path.abspath(__file__)
|
17 |
+
# Get the directory of the script
|
18 |
+
script_dir = os.path.dirname(script_path)
|
19 |
+
|
20 |
+
# Path to the icon file
|
21 |
+
icon_path = os.path.join(script_dir, 'img', 'icon.jpg')
|
22 |
+
img = Image.open(icon_path)
|
23 |
+
enhancer = ImageEnhance.Color(img)
|
24 |
+
img_enhanced = enhancer.enhance(1.5)
|
25 |
+
img_enhanced.save(os.path.join(script_dir, 'img', 'icon.ico'), format='ICO', sizes=[(256,256)])
|
26 |
+
icon_path_ico = os.path.join(script_dir, 'img', 'icon.ico')
|
27 |
+
|
28 |
+
# Construct the path to the static folder
|
29 |
+
static_dir = os.path.join(script_dir, "static")
|
30 |
+
|
31 |
+
# Ask for the name of the shortcut
|
32 |
+
shortcut_name = "Voucher Vision"
|
33 |
+
|
34 |
+
root = tk.Tk()
|
35 |
+
root.withdraw() # Hide the main window
|
36 |
+
|
37 |
+
root.update() # Ensures that the dialog appears on top
|
38 |
+
folder_path = filedialog.askdirectory(title="Choose location to save the shortcut")
|
39 |
+
print(f"Shortcut will be saved to {folder_path}")
|
40 |
+
|
41 |
+
venv_path = filedialog.askdirectory(title="Choose the location of your Python virtual environment")
|
42 |
+
print(f"Using virtual environment located at {venv_path}")
|
43 |
+
|
44 |
+
# Path to the activate script in the venv
|
45 |
+
activate_path = os.path.join(venv_path, "Scripts")
|
46 |
+
|
47 |
+
shortcut_path = os.path.join(folder_path, f'{shortcut_name}.lnk')
|
48 |
+
|
49 |
+
shell = win32com.client.Dispatch("WScript.Shell")
|
50 |
+
shortcut = shell.CreateShortCut(shortcut_path)
|
51 |
+
shortcut.Targetpath = "%windir%\System32\cmd.exe"
|
52 |
+
# The command activates the venv, navigates to the script's directory, then runs the script
|
53 |
+
# shortcut.Arguments = f'/K "{activate_path} & cd /D {os.path.dirname(script_path)} & streamlit run VoucherVisionEditor.py"'
|
54 |
+
# shortcut.Arguments = f'/K "{activate_path} & cd /D {static_dir} & start cmd /c python -m http.server & cd /D {script_dir} & streamlit run VoucherVisionEditor.py"'
|
55 |
+
streamlit_exe = os.path.join(venv_path, "Scripts","streamlit")
|
56 |
+
print(script_dir)
|
57 |
+
print(streamlit_exe)
|
58 |
+
activate_path = os.path.join(script_dir,"venv_VV","Scripts")
|
59 |
+
print(activate_path)
|
60 |
+
shortcut.Arguments = f'/K cd /D ""{activate_path}"" && activate && cd /D ""{script_dir}"" && python run_VoucherVision.py'
|
61 |
+
# Set the icon of the shortcut
|
62 |
+
shortcut.IconLocation = icon_path_ico
|
63 |
+
|
64 |
+
shortcut.save()
|
65 |
+
|
66 |
+
print(f"Shortcut created with the name '{shortcut_name}' in the chosen directory.")
|
67 |
+
|
68 |
+
if __name__ == "__main__":
|
69 |
+
create_shortcut()
|
custom_prompts/required_structure.yaml
ADDED
@@ -0,0 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
LLM: gpt
|
2 |
+
instructions: '1. Refactor the unstructured OCR text into a dictionary based on the
|
3 |
+
JSON structure outlined below.
|
4 |
+
|
5 |
+
2. You should map the unstructured OCR text to the appropriate JSON key and then
|
6 |
+
populate the field based on its rules.
|
7 |
+
|
8 |
+
3. Some JSON key fields are permitted to remain empty if the corresponding information
|
9 |
+
is not found in the unstructured OCR text.
|
10 |
+
|
11 |
+
4. Ignore any information in the OCR text that doesn''t fit into the defined JSON
|
12 |
+
structure.
|
13 |
+
|
14 |
+
5. Duplicate dictionary fields are not allowed.
|
15 |
+
|
16 |
+
6. Ensure that all JSON keys are in lowercase.
|
17 |
+
|
18 |
+
7. Ensure that new JSON field values follow sentence case capitalization.
|
19 |
+
|
20 |
+
8. Ensure all key-value pairs in the JSON dictionary strictly adhere to the format
|
21 |
+
and data types specified in the template.
|
22 |
+
|
23 |
+
9. Ensure the output JSON string is valid JSON format. It should not have trailing
|
24 |
+
commas or unquoted keys.
|
25 |
+
|
26 |
+
10. Only return a JSON dictionary represented as a string. You should not explain
|
27 |
+
your answer.'
|
28 |
+
json_formatting_instructions: "The next section of instructions outlines how to format\
|
29 |
+
\ the JSON dictionary. The keys are the same as those of the final formatted JSON\
|
30 |
+
\ object.\nFor each key there is a format requirement that specifies how to transcribe\
|
31 |
+
\ the information for that key. \nThe possible formatting options are:\n1. \"verbatim\
|
32 |
+
\ transcription\" - field is populated with verbatim text from the unformatted OCR.\n\
|
33 |
+
2. \"spell check transcription\" - field is populated with spelling corrected text\
|
34 |
+
\ from the unformatted OCR.\n3. \"boolean yes no\" - field is populated with only\
|
35 |
+
\ yes or no.\n4. \"boolean 1 0\" - field is populated with only 1 or 0.\n5. \"integer\"\
|
36 |
+
\ - field is populated with only an integer.\n6. \"[list]\" - field is populated\
|
37 |
+
\ from one of the values in the list.\n7. \"yyyy-mm-dd\" - field is populated with\
|
38 |
+
\ a date in the format year-month-day.\nThe desired null value is also given. Populate\
|
39 |
+
\ the field with the null value of the information for that key is not present in\
|
40 |
+
\ the unformatted OCR text."
|
41 |
+
mapping:
|
42 |
+
# Add column names to the desired category. This is used to map the VV Editor.
|
43 |
+
COLLECTING: []
|
44 |
+
GEOGRAPHY: []
|
45 |
+
LOCALITY: []
|
46 |
+
MISCELLANEOUS: []
|
47 |
+
TAXONOMY:
|
48 |
+
- catalog_number
|
49 |
+
rules:
|
50 |
+
Dictionary:
|
51 |
+
# Manually add rows here. You MUST keep 'catalog_number' unchanged. Use 'catalog_number' as a guide for adding more columns.
|
52 |
+
# The only values allowed in the 'format' key are those outlines above in the 'json_formatting_instructions' section.
|
53 |
+
# If you want an empty cell by default, use '' for the 'null_value'.
|
54 |
+
catalog_number:
|
55 |
+
description: The barcode identifier, typically a number with at least 6 digits,
|
56 |
+
but fewer than 30 digits.
|
57 |
+
format: verbatim transcription
|
58 |
+
null_value: ''
|
59 |
+
# Do not change or remove below. This is required for some LLMs
|
60 |
+
SpeciesName:
|
61 |
+
taxonomy:
|
62 |
+
- Genus_species
|
custom_prompts/version_2.yaml
ADDED
@@ -0,0 +1,229 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
LLM: gpt
|
2 |
+
instructions: '1. Refactor the unstructured OCR text into a dictionary based on the
|
3 |
+
JSON structure outlined below.
|
4 |
+
|
5 |
+
2. You should map the unstructured OCR text to the appropriate JSON key and then
|
6 |
+
populate the field based on its rules.
|
7 |
+
|
8 |
+
3. Some JSON key fields are permitted to remain empty if the corresponding information
|
9 |
+
is not found in the unstructured OCR text.
|
10 |
+
|
11 |
+
4. Ignore any information in the OCR text that doesn''t fit into the defined JSON
|
12 |
+
structure.
|
13 |
+
|
14 |
+
5. Duplicate dictionary fields are not allowed.
|
15 |
+
|
16 |
+
6. Ensure that all JSON keys are in lowercase.
|
17 |
+
|
18 |
+
7. Ensure that new JSON field values follow sentence case capitalization.
|
19 |
+
|
20 |
+
8. Ensure all key-value pairs in the JSON dictionary strictly adhere to the format
|
21 |
+
and data types specified in the template.
|
22 |
+
|
23 |
+
9. Ensure the output JSON string is valid JSON format. It should not have trailing
|
24 |
+
commas or unquoted keys.
|
25 |
+
|
26 |
+
10. Only return a JSON dictionary represented as a string. You should not explain
|
27 |
+
your answer.'
|
28 |
+
json_formatting_instructions: "The next section of instructions outlines how to format\
|
29 |
+
\ the JSON dictionary. The keys are the same as those of the final formatted JSON\
|
30 |
+
\ object.\nFor each key there is a format requirement that specifies how to transcribe\
|
31 |
+
\ the information for that key. \nThe possible formatting options are:\n1. \"verbatim\
|
32 |
+
\ transcription\" - field is populated with verbatim text from the unformatted OCR.\n\
|
33 |
+
2. \"spell check transcription\" - field is populated with spelling corrected text\
|
34 |
+
\ from the unformatted OCR.\n3. \"boolean yes no\" - field is populated with only\
|
35 |
+
\ yes or no.\n4. \"boolean 1 0\" - field is populated with only 1 or 0.\n5. \"integer\"\
|
36 |
+
\ - field is populated with only an integer.\n6. \"[list]\" - field is populated\
|
37 |
+
\ from one of the values in the list.\n7. \"yyyy-mm-dd\" - field is populated with\
|
38 |
+
\ a date in the format year-month-day.\nThe desired null value is also given. Populate\
|
39 |
+
\ the field with the null value of the information for that key is not present in\
|
40 |
+
\ the unformatted OCR text."
|
41 |
+
mapping:
|
42 |
+
COLLECTING:
|
43 |
+
- collectors
|
44 |
+
- collector_number
|
45 |
+
- determined_by
|
46 |
+
- multiple_names
|
47 |
+
- verbatim_date
|
48 |
+
- date
|
49 |
+
- end_date
|
50 |
+
GEOGRAPHY:
|
51 |
+
- country
|
52 |
+
- state
|
53 |
+
- county
|
54 |
+
- min_elevation
|
55 |
+
- max_elevation
|
56 |
+
- elevation_units
|
57 |
+
LOCALITY:
|
58 |
+
- locality_name
|
59 |
+
- verbatim_coordinates
|
60 |
+
- decimal_coordinates
|
61 |
+
- datum
|
62 |
+
- plant_description
|
63 |
+
- cultivated
|
64 |
+
- habitat
|
65 |
+
MISCELLANEOUS: []
|
66 |
+
TAXONOMY:
|
67 |
+
- catalog_number
|
68 |
+
- genus
|
69 |
+
- species
|
70 |
+
- subspecies
|
71 |
+
- variety
|
72 |
+
- forma
|
73 |
+
rules:
|
74 |
+
Dictionary:
|
75 |
+
catalog_number:
|
76 |
+
description: The barcode identifier, typically a number with at least 6 digits,
|
77 |
+
but fewer than 30 digits.
|
78 |
+
format: verbatim transcription
|
79 |
+
null_value: ''
|
80 |
+
collector_number:
|
81 |
+
description: Unique identifier or number that denotes the specific collecting
|
82 |
+
event and associated with the collector.
|
83 |
+
format: verbatim transcription
|
84 |
+
null_value: s.n.
|
85 |
+
collectors:
|
86 |
+
description: Full name(s) of the individual(s) responsible for collecting the
|
87 |
+
specimen. When multiple collectors are involved, their names should be separated
|
88 |
+
by commas.
|
89 |
+
format: verbatim transcription
|
90 |
+
null_value: not present
|
91 |
+
country:
|
92 |
+
description: Country that corresponds to the current geographic location of
|
93 |
+
collection. Capitalize first letter of each word. If abbreviation is given
|
94 |
+
populate field with the full spelling of the country's name.
|
95 |
+
format: spell check transcription
|
96 |
+
null_value: ''
|
97 |
+
county:
|
98 |
+
description: Administrative division 2 that corresponds to the current geographic
|
99 |
+
location of collection; capitalize first letter of each word. Administrative
|
100 |
+
division 2 is equivalent to a U.S. county, parish, borough.
|
101 |
+
format: spell check transcription
|
102 |
+
null_value: ''
|
103 |
+
cultivated:
|
104 |
+
description: Cultivated plants are intentionally grown by humans. In text descriptions,
|
105 |
+
look for planting dates, garden locations, ornamental, cultivar names, garden,
|
106 |
+
or farm to indicate cultivated plant.
|
107 |
+
format: boolean yes no
|
108 |
+
null_value: ''
|
109 |
+
date:
|
110 |
+
description: 'Date the specimen was collected formatted as year-month-day. If
|
111 |
+
specific components of the date are unknown, they should be replaced with
|
112 |
+
zeros. Examples: ''0000-00-00'' if the entire date is unknown, ''YYYY-00-00''
|
113 |
+
if only the year is known, and ''YYYY-MM-00'' if year and month are known
|
114 |
+
but day is not.'
|
115 |
+
format: yyyy-mm-dd
|
116 |
+
null_value: ''
|
117 |
+
datum:
|
118 |
+
description: Datum of location coordinates. Possible values are include in the
|
119 |
+
format list. Leave field blank if unclear. [WGS84, WGS72, WGS66, WGS60, NAD83,
|
120 |
+
NAD27, OSGB36, ETRS89, ED50, GDA94, JGD2011, Tokyo97, KGD2002, TWD67, TWD97,
|
121 |
+
BJS54, XAS80, GCJ-02, BD-09, PZ-90.11, GTRF, CGCS2000, ITRF88, ITRF89, ITRF90,
|
122 |
+
ITRF91, ITRF92, ITRF93, ITRF94, ITRF96, ITRF97, ITRF2000, ITRF2005, ITRF2008,
|
123 |
+
ITRF2014, Hong Kong Principal Datum, SAD69]
|
124 |
+
format: '[list]'
|
125 |
+
null_value: ''
|
126 |
+
decimal_coordinates:
|
127 |
+
description: Correct and convert the verbatim location coordinates to conform
|
128 |
+
with the decimal degrees GPS coordinate format.
|
129 |
+
format: spell check transcription
|
130 |
+
null_value: ''
|
131 |
+
determined_by:
|
132 |
+
description: Full name of the individual responsible for determining the taxanomic
|
133 |
+
name of the specimen. Sometimes the name will be near to the characters 'det'
|
134 |
+
to denote determination. This name may be isolated from other names in the
|
135 |
+
unformatted OCR text.
|
136 |
+
format: verbatim transcription
|
137 |
+
null_value: ''
|
138 |
+
elevation_units:
|
139 |
+
description: 'Elevation units must be meters. If min_elevation field is populated,
|
140 |
+
then elevation_units: ''m''. Otherwise elevation_units: ''''.'
|
141 |
+
format: spell check transcription
|
142 |
+
null_value: ''
|
143 |
+
end_date:
|
144 |
+
description: 'If a date range is provided, this represents the later or ending
|
145 |
+
date of the collection period, formatted as year-month-day. If specific components
|
146 |
+
of the date are unknown, they should be replaced with zeros. Examples: ''0000-00-00''
|
147 |
+
if the entire end date is unknown, ''YYYY-00-00'' if only the year of the
|
148 |
+
end date is known, and ''YYYY-MM-00'' if year and month of the end date are
|
149 |
+
known but the day is not.'
|
150 |
+
format: yyyy-mm-dd
|
151 |
+
null_value: ''
|
152 |
+
forma:
|
153 |
+
description: Taxonomic determination to form (f.).
|
154 |
+
format: verbatim transcription
|
155 |
+
null_value: ''
|
156 |
+
genus:
|
157 |
+
description: Taxonomic determination to genus. Genus must be capitalized. If
|
158 |
+
genus is not present use the taxonomic family name followed by the word 'indet'.
|
159 |
+
format: verbatim transcription
|
160 |
+
null_value: ''
|
161 |
+
habitat:
|
162 |
+
description: Description of a plant's habitat or the location where the specimen
|
163 |
+
was collected. Ignore descriptions of the plant itself.
|
164 |
+
format: verbatim transcription
|
165 |
+
null_value: ''
|
166 |
+
locality_name:
|
167 |
+
description: Description of geographic location, landscape, landmarks, regional
|
168 |
+
features, nearby places, or any contextual information aiding in pinpointing
|
169 |
+
the exact origin or site of the specimen.
|
170 |
+
format: verbatim transcription
|
171 |
+
null_value: ''
|
172 |
+
max_elevation:
|
173 |
+
description: Maximum elevation or altitude in meters. If only one elevation
|
174 |
+
is present, then max_elevation should be set to the null_value. Only if units
|
175 |
+
are explicit then convert from feet ('ft' or 'ft.' or 'feet') to meters ('m'
|
176 |
+
or 'm.' or 'meters'). Round to integer.
|
177 |
+
format: integer
|
178 |
+
null_value: ''
|
179 |
+
min_elevation:
|
180 |
+
description: Minimum elevation or altitude in meters. Only if units are explicit
|
181 |
+
then convert from feet ('ft' or 'ft.' or 'feet') to meters ('m' or 'm.' or
|
182 |
+
'meters'). Round to integer.
|
183 |
+
format: integer
|
184 |
+
null_value: ''
|
185 |
+
multiple_names:
|
186 |
+
description: Indicate whether multiple people or collector names are present
|
187 |
+
in the unformatted OCR text. If you see more than one person's name the value
|
188 |
+
is 'yes'; otherwise the value is 'no'.
|
189 |
+
format: boolean yes no
|
190 |
+
null_value: ''
|
191 |
+
plant_description:
|
192 |
+
description: Description of plant features such as leaf shape, size, color,
|
193 |
+
stem texture, height, flower structure, scent, fruit or seed characteristics,
|
194 |
+
root system type, overall growth habit and form, any notable aroma or secretions,
|
195 |
+
presence of hairs or bristles, and any other distinguishing morphological
|
196 |
+
or physiological characteristics.
|
197 |
+
format: verbatim transcription
|
198 |
+
null_value: ''
|
199 |
+
species:
|
200 |
+
description: Taxonomic determination to species, do not capitalize species.
|
201 |
+
format: verbatim transcription
|
202 |
+
null_value: ''
|
203 |
+
state:
|
204 |
+
description: Administrative division 1 that corresponds to the current geographic
|
205 |
+
location of collection. Capitalize first letter of each word. Administrative
|
206 |
+
division 1 is equivalent to a U.S. State.
|
207 |
+
format: spell check transcription
|
208 |
+
null_value: ''
|
209 |
+
subspecies:
|
210 |
+
description: Taxonomic determination to subspecies (subsp.).
|
211 |
+
format: verbatim transcription
|
212 |
+
null_value: ''
|
213 |
+
variety:
|
214 |
+
description: Taxonomic determination to variety (var).
|
215 |
+
format: verbatim transcription
|
216 |
+
null_value: ''
|
217 |
+
verbatim_coordinates:
|
218 |
+
description: Verbatim location coordinates as they appear on the label. Do not
|
219 |
+
convert formats. Possible coordinate types are one of [Lat, Long, UTM, TRS].
|
220 |
+
format: verbatim transcription
|
221 |
+
null_value: ''
|
222 |
+
verbatim_date:
|
223 |
+
description: Date of collection exactly as it appears on the label. Do not change
|
224 |
+
the format or correct typos.
|
225 |
+
format: verbatim transcription
|
226 |
+
null_value: s.d.
|
227 |
+
SpeciesName:
|
228 |
+
taxonomy:
|
229 |
+
- Genus_species
|
custom_prompts/version_2_OSU.yaml
ADDED
@@ -0,0 +1,230 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
LLM: gpt
|
2 |
+
instructions: '1. Refactor the unstructured OCR text into a dictionary based on the
|
3 |
+
JSON structure outlined below.
|
4 |
+
|
5 |
+
2. You should map the unstructured OCR text to the appropriate JSON key and then
|
6 |
+
populate the field based on its rules.
|
7 |
+
|
8 |
+
3. Some JSON key fields are permitted to remain empty if the corresponding information
|
9 |
+
is not found in the unstructured OCR text.
|
10 |
+
|
11 |
+
4. Ignore any information in the OCR text that doesn''t fit into the defined JSON
|
12 |
+
structure.
|
13 |
+
|
14 |
+
5. Duplicate dictionary fields are not allowed.
|
15 |
+
|
16 |
+
6. Ensure that all JSON keys are in lowercase.
|
17 |
+
|
18 |
+
7. Ensure that new JSON field values follow sentence case capitalization.
|
19 |
+
|
20 |
+
8. Ensure all key-value pairs in the JSON dictionary strictly adhere to the format
|
21 |
+
and data types specified in the template.
|
22 |
+
|
23 |
+
9. Ensure the output JSON string is valid JSON format. It should not have trailing
|
24 |
+
commas or unquoted keys.
|
25 |
+
|
26 |
+
10. Only return a JSON dictionary represented as a string. You should not explain
|
27 |
+
your answer.'
|
28 |
+
json_formatting_instructions: "The next section of instructions outlines how to format\
|
29 |
+
\ the JSON dictionary. The keys are the same as those of the final formatted JSON\
|
30 |
+
\ object.\nFor each key there is a format requirement that specifies how to transcribe\
|
31 |
+
\ the information for that key. \nThe possible formatting options are:\n1. \"verbatim\
|
32 |
+
\ transcription\" - field is populated with verbatim text from the unformatted OCR.\n\
|
33 |
+
2. \"spell check transcription\" - field is populated with spelling corrected text\
|
34 |
+
\ from the unformatted OCR.\n3. \"boolean yes no\" - field is populated with only\
|
35 |
+
\ yes or no.\n4. \"boolean 1 0\" - field is populated with only 1 or 0.\n5. \"integer\"\
|
36 |
+
\ - field is populated with only an integer.\n6. \"[list]\" - field is populated\
|
37 |
+
\ from one of the values in the list.\n7. \"yyyy-mm-dd\" - field is populated with\
|
38 |
+
\ a date in the format year-month-day.\nThe desired null value is also given. Populate\
|
39 |
+
\ the field with the null value of the information for that key is not present in\
|
40 |
+
\ the unformatted OCR text."
|
41 |
+
mapping:
|
42 |
+
COLLECTING:
|
43 |
+
- collectors
|
44 |
+
- collector_number
|
45 |
+
- determined_by
|
46 |
+
- multiple_names
|
47 |
+
- verbatim_date
|
48 |
+
- date
|
49 |
+
- end_date
|
50 |
+
GEOGRAPHY:
|
51 |
+
- country
|
52 |
+
- state
|
53 |
+
- county
|
54 |
+
- min_elevation
|
55 |
+
- max_elevation
|
56 |
+
- elevation_units
|
57 |
+
LOCALITY:
|
58 |
+
- locality_name
|
59 |
+
- verbatim_coordinates
|
60 |
+
- decimal_coordinates
|
61 |
+
- datum
|
62 |
+
- plant_description
|
63 |
+
- cultivated
|
64 |
+
- habitat
|
65 |
+
MISCELLANEOUS: []
|
66 |
+
TAXONOMY:
|
67 |
+
- catalog_number
|
68 |
+
- genus
|
69 |
+
- species
|
70 |
+
- subspecies
|
71 |
+
- variety
|
72 |
+
- forma
|
73 |
+
rules:
|
74 |
+
Dictionary:
|
75 |
+
catalog_number:
|
76 |
+
description: The barcode identifier, typically a number with at least 6 digits,
|
77 |
+
but fewer than 30 digits.
|
78 |
+
format: verbatim transcription
|
79 |
+
null_value: ''
|
80 |
+
collector_number:
|
81 |
+
description: Unique identifier or number that denotes the specific collecting
|
82 |
+
event and associated with the collector.
|
83 |
+
format: verbatim transcription
|
84 |
+
null_value: s.n.
|
85 |
+
collectors:
|
86 |
+
description: Full name(s) of the individual(s) responsible for collecting the
|
87 |
+
specimen. When multiple collectors are involved, their names should be separated
|
88 |
+
by commas.
|
89 |
+
format: verbatim transcription
|
90 |
+
null_value: not present
|
91 |
+
country:
|
92 |
+
description: Country that corresponds to the current geographic location of
|
93 |
+
collection. Capitalize first letter of each word. If abbreviation is given
|
94 |
+
populate field with the full spelling of the country's name.
|
95 |
+
format: spell check transcription
|
96 |
+
null_value: ''
|
97 |
+
county:
|
98 |
+
description: Administrative division 2 that corresponds to the current geographic
|
99 |
+
location of collection; capitalize first letter of each word. Administrative
|
100 |
+
division 2 is equivalent to a U.S. county, parish, borough.
|
101 |
+
format: spell check transcription
|
102 |
+
null_value: ''
|
103 |
+
cultivated:
|
104 |
+
description: Cultivated plants are intentionally grown by humans. In text descriptions,
|
105 |
+
look for planting dates, garden locations, ornamental, cultivar names, garden,
|
106 |
+
or farm to indicate cultivated plant. The value 1 indicates that the specimen
|
107 |
+
was cultivated, the value zero otherwise.
|
108 |
+
format: boolean 1 0
|
109 |
+
null_value: '0'
|
110 |
+
date:
|
111 |
+
description: 'Date the specimen was collected formatted as year-month-day. If
|
112 |
+
specific components of the date are unknown, they should be replaced with
|
113 |
+
zeros. Examples: ''0000-00-00'' if the entire date is unknown, ''YYYY-00-00''
|
114 |
+
if only the year is known, and ''YYYY-MM-00'' if year and month are known
|
115 |
+
but day is not.'
|
116 |
+
format: yyyy-mm-dd
|
117 |
+
null_value: ''
|
118 |
+
datum:
|
119 |
+
description: Datum of location coordinates. Possible values are include in the
|
120 |
+
format list. Leave field blank if unclear. [WGS84, WGS72, WGS66, WGS60, NAD83,
|
121 |
+
NAD27, OSGB36, ETRS89, ED50, GDA94, JGD2011, Tokyo97, KGD2002, TWD67, TWD97,
|
122 |
+
BJS54, XAS80, GCJ-02, BD-09, PZ-90.11, GTRF, CGCS2000, ITRF88, ITRF89, ITRF90,
|
123 |
+
ITRF91, ITRF92, ITRF93, ITRF94, ITRF96, ITRF97, ITRF2000, ITRF2005, ITRF2008,
|
124 |
+
ITRF2014, Hong Kong Principal Datum, SAD69]
|
125 |
+
format: '[list]'
|
126 |
+
null_value: ''
|
127 |
+
decimal_coordinates:
|
128 |
+
description: Correct and convert the verbatim location coordinates to conform
|
129 |
+
with the decimal degrees GPS coordinate format.
|
130 |
+
format: spell check transcription
|
131 |
+
null_value: ''
|
132 |
+
determined_by:
|
133 |
+
description: Full name of the individual responsible for determining the taxanomic
|
134 |
+
name of the specimen. Sometimes the name will be near to the characters 'det'
|
135 |
+
to denote determination. This name may be isolated from other names in the
|
136 |
+
unformatted OCR text.
|
137 |
+
format: verbatim transcription
|
138 |
+
null_value: ''
|
139 |
+
elevation_units:
|
140 |
+
description: 'Elevation units must be meters. If min_elevation field is populated,
|
141 |
+
then elevation_units: ''m''. Otherwise elevation_units: ''''.'
|
142 |
+
format: spell check transcription
|
143 |
+
null_value: ''
|
144 |
+
end_date:
|
145 |
+
description: 'If a date range is provided, this represents the later or ending
|
146 |
+
date of the collection period, formatted as year-month-day. If specific components
|
147 |
+
of the date are unknown, they should be replaced with zeros. Examples: ''0000-00-00''
|
148 |
+
if the entire end date is unknown, ''YYYY-00-00'' if only the year of the
|
149 |
+
end date is known, and ''YYYY-MM-00'' if year and month of the end date are
|
150 |
+
known but the day is not.'
|
151 |
+
format: yyyy-mm-dd
|
152 |
+
null_value: ''
|
153 |
+
forma:
|
154 |
+
description: Taxonomic determination to form (f.).
|
155 |
+
format: verbatim transcription
|
156 |
+
null_value: ''
|
157 |
+
genus:
|
158 |
+
description: Taxonomic determination to genus. Genus must be capitalized. If
|
159 |
+
genus is not present use the taxonomic family name followed by the word 'indet'.
|
160 |
+
format: verbatim transcription
|
161 |
+
null_value: ''
|
162 |
+
habitat:
|
163 |
+
description: Description of a plant's habitat or the location where the specimen
|
164 |
+
was collected. Ignore descriptions of the plant itself.
|
165 |
+
format: verbatim transcription
|
166 |
+
null_value: ''
|
167 |
+
locality_name:
|
168 |
+
description: Description of geographic location, landscape, landmarks, regional
|
169 |
+
features, nearby places, or any contextual information aiding in pinpointing
|
170 |
+
the exact origin or site of the specimen.
|
171 |
+
format: verbatim transcription
|
172 |
+
null_value: ''
|
173 |
+
max_elevation:
|
174 |
+
description: Maximum elevation or altitude in meters. If only one elevation
|
175 |
+
is present, then max_elevation should be set to the null_value. Only if units
|
176 |
+
are explicit then convert from feet ('ft' or 'ft.' or 'feet') to meters ('m'
|
177 |
+
or 'm.' or 'meters'). Round to integer.
|
178 |
+
format: integer
|
179 |
+
null_value: ''
|
180 |
+
min_elevation:
|
181 |
+
description: Minimum elevation or altitude in meters. Only if units are explicit
|
182 |
+
then convert from feet ('ft' or 'ft.' or 'feet') to meters ('m' or 'm.' or
|
183 |
+
'meters'). Round to integer.
|
184 |
+
format: integer
|
185 |
+
null_value: ''
|
186 |
+
multiple_names:
|
187 |
+
description: Indicate whether multiple people or collector names are present
|
188 |
+
in the unformatted OCR text. If you see more than one person's name the value
|
189 |
+
is 'yes'; otherwise the value is 'no'.
|
190 |
+
format: boolean yes no
|
191 |
+
null_value: ''
|
192 |
+
plant_description:
|
193 |
+
description: Description of plant features such as leaf shape, size, color,
|
194 |
+
stem texture, height, flower structure, scent, fruit or seed characteristics,
|
195 |
+
root system type, overall growth habit and form, any notable aroma or secretions,
|
196 |
+
presence of hairs or bristles, and any other distinguishing morphological
|
197 |
+
or physiological characteristics.
|
198 |
+
format: verbatim transcription
|
199 |
+
null_value: ''
|
200 |
+
species:
|
201 |
+
description: Taxonomic determination to species, do not capitalize species.
|
202 |
+
format: verbatim transcription
|
203 |
+
null_value: ''
|
204 |
+
state:
|
205 |
+
description: Administrative division 1 that corresponds to the current geographic
|
206 |
+
location of collection. Capitalize first letter of each word. Administrative
|
207 |
+
division 1 is equivalent to a U.S. State.
|
208 |
+
format: spell check transcription
|
209 |
+
null_value: ''
|
210 |
+
subspecies:
|
211 |
+
description: Taxonomic determination to subspecies (subsp.).
|
212 |
+
format: verbatim transcription
|
213 |
+
null_value: ''
|
214 |
+
variety:
|
215 |
+
description: Taxonomic determination to variety (var).
|
216 |
+
format: verbatim transcription
|
217 |
+
null_value: ''
|
218 |
+
verbatim_coordinates:
|
219 |
+
description: Verbatim location coordinates as they appear on the label. Do not
|
220 |
+
convert formats. Possible coordinate types are one of [Lat, Long, UTM, TRS].
|
221 |
+
format: verbatim transcription
|
222 |
+
null_value: ''
|
223 |
+
verbatim_date:
|
224 |
+
description: Date of collection exactly as it appears on the label. Do not change
|
225 |
+
the format or correct typos.
|
226 |
+
format: verbatim transcription
|
227 |
+
null_value: s.d.
|
228 |
+
SpeciesName:
|
229 |
+
taxonomy:
|
230 |
+
- Genus_species
|
demo/ba/ba.jpg
ADDED
Git LFS Details
|
demo/ba/ba.png
ADDED
Git LFS Details
|
demo/ba/ba2.png
ADDED
Git LFS Details
|
demo/demo_gallery/NY_1928185102_Heliotropiaceae_Heliotropium_indicum.jpg
ADDED
Git LFS Details
|
demo/demo_gallery/SMF_3046042583_Ebenaceae_Diospyros_mespiliformis.jpg
ADDED
Git LFS Details
|
demo/demo_gallery/UM_1807475718_Monimiaceae_Hedycarya_parvifolia.jpg
ADDED
Git LFS Details
|
demo/demo_gallery/UM_1915455196_Cardiopteridaceae_Citronella_sarmentosa.jpg
ADDED
Git LFS Details
|
demo/demo_images/UM_1807464860_Phellinaceae_Phelline_dumbeensis.jpg
ADDED
Git LFS Details
|
demo/img/expense_report.PNG
ADDED
Git LFS Details
|
demo/img/prompt_1.PNG
ADDED
Git LFS Details
|
demo/img/prompt_2.PNG
ADDED
Git LFS Details
|
demo/img/prompt_3.PNG
ADDED
Git LFS Details
|
demo/img/prompt_4.PNG
ADDED
Git LFS Details
|
demo/img/prompt_5.PNG
ADDED
Git LFS Details
|
demo/img/validation_1.PNG
ADDED
Git LFS Details
|
demo/img/validation_gpt.PNG
ADDED
Git LFS Details
|
demo/img/validation_gpu.PNG
ADDED
Git LFS Details
|
demo/img/validation_palm.PNG
ADDED
Git LFS Details
|
domain_knowledge/SLTP_UM_AllAsiaMinimalInRegion.xlsx
ADDED
Binary file (600 kB). View file
|
|
img/icon.ico
ADDED
img/icon.jpg
ADDED
Git LFS Details
|
img/icon2.ico
ADDED
img/logo.png
ADDED
Git LFS Details
|
requirements.txt
ADDED
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
--extra-index-url https://download.pytorch.org/whl/cpu
|
2 |
+
|
3 |
+
torch==2.0.1
|
4 |
+
torchvision==0.15.2
|
5 |
+
torchaudio==2.0.2
|
6 |
+
wheel
|
7 |
+
streamlit
|
8 |
+
streamlit-extras
|
9 |
+
plotly
|
10 |
+
pyyaml
|
11 |
+
Pillow
|
12 |
+
pandas
|
13 |
+
matplotlib
|
14 |
+
matplotlib-inline
|
15 |
+
tqdm
|
16 |
+
openai
|
17 |
+
langchain
|
18 |
+
tiktoken
|
19 |
+
openpyxl
|
20 |
+
google-generativeai
|
21 |
+
google-cloud-storage
|
22 |
+
google-cloud-vision
|
23 |
+
opencv-python
|
24 |
+
chromadb
|
25 |
+
chroma-migrate
|
26 |
+
InstructorEmbedding
|
27 |
+
transformers
|
28 |
+
sentence-transformers
|
29 |
+
seaborn
|
30 |
+
dask
|
31 |
+
psutil
|
32 |
+
py-cpuinfo
|
33 |
+
azureml-sdk
|
34 |
+
azure-identity
|
run_VoucherVision.py
ADDED
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import streamlit.web.cli as stcli
|
2 |
+
import os, sys
|
3 |
+
|
4 |
+
# Insert a file uploader that accepts multiple files at a time:
|
5 |
+
# import streamlit as st
|
6 |
+
# uploaded_files = st.file_uploader("Choose a CSV file", accept_multiple_files=True)
|
7 |
+
# for uploaded_file in uploaded_files:
|
8 |
+
# bytes_data = uploaded_file.read()
|
9 |
+
# st.write("filename:", uploaded_file.name)
|
10 |
+
# st.write(bytes_data)
|
11 |
+
|
12 |
+
|
13 |
+
def resolve_path(path):
|
14 |
+
resolved_path = os.path.abspath(os.path.join(os.getcwd(), path))
|
15 |
+
return resolved_path
|
16 |
+
|
17 |
+
|
18 |
+
if __name__ == "__main__":
|
19 |
+
dir_home = os.path.dirname(__file__)
|
20 |
+
|
21 |
+
# pip install protobuf==3.20.0
|
22 |
+
|
23 |
+
sys.argv = [
|
24 |
+
"streamlit",
|
25 |
+
"run",
|
26 |
+
resolve_path(os.path.join(dir_home,"vouchervision", "VoucherVision_GUI.py")),
|
27 |
+
"--global.developmentMode=false",
|
28 |
+
"--server.port=8525",
|
29 |
+
|
30 |
+
]
|
31 |
+
sys.exit(stcli.main())
|
vouchervision/LLM_Falcon.py
ADDED
@@ -0,0 +1,112 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os, sys, inspect, json, time
|
2 |
+
|
3 |
+
# currentdir = os.path.dirname(os.path.abspath(
|
4 |
+
# inspect.getfile(inspect.currentframe())))
|
5 |
+
# parentdir = os.path.dirname(currentdir)
|
6 |
+
# sys.path.append(parentdir)
|
7 |
+
|
8 |
+
# from prompts import PROMPT_PaLM_UMICH_skeleton_all_asia, PROMPT_PaLM_OCR_Organized, PROMPT_PaLM_Redo
|
9 |
+
# from LLM_PaLM import create_OCR_analog_for_input, num_tokens_from_string
|
10 |
+
|
11 |
+
'''
|
12 |
+
https://docs.ai21.com/docs/python-sdk-with-amazon-bedrock
|
13 |
+
|
14 |
+
|
15 |
+
https://techcommunity.microsoft.com/t5/ai-machine-learning-blog/falcon-llms-in-azure-machine-learning/ba-p/3876847
|
16 |
+
https://github.com/Azure/azureml-examples/blob/main/sdk/python/foundation-models/huggingface/inference/text-generation-streaming/text-generation-streaming-online-endpoint.ipynb
|
17 |
+
https://ml.azure.com/registries/HuggingFace/models/tiiuae-falcon-40b-instruct/version/12?tid=e66e77b4-5724-44d7-8721-06df160450ce#overview
|
18 |
+
https://azure.microsoft.com/en-us/products/machine-learning/
|
19 |
+
'''
|
20 |
+
|
21 |
+
|
22 |
+
|
23 |
+
# from azure.ai.ml import MLClient
|
24 |
+
# from azure.identity import (
|
25 |
+
# DefaultAzureCredential,
|
26 |
+
# InteractiveBrowserCredential,
|
27 |
+
# ClientSecretCredential,
|
28 |
+
# )
|
29 |
+
# from azure.ai.ml.entities import AmlCompute
|
30 |
+
|
31 |
+
# try:
|
32 |
+
# credential = DefaultAzureCredential()
|
33 |
+
# credential.get_token("https://management.azure.com/.default")
|
34 |
+
# except Exception as ex:
|
35 |
+
# credential = InteractiveBrowserCredential()
|
36 |
+
|
37 |
+
# # connect to a workspace
|
38 |
+
# workspace_ml_client = None
|
39 |
+
# try:
|
40 |
+
# workspace_ml_client = MLClient.from_config(credential)
|
41 |
+
# subscription_id = workspace_ml_client.subscription_id
|
42 |
+
# workspace = workspace_ml_client.workspace_name
|
43 |
+
# resource_group = workspace_ml_client.resource_group_name
|
44 |
+
# except Exception as ex:
|
45 |
+
# print(ex)
|
46 |
+
# # Enter details of your workspace
|
47 |
+
# subscription_id = "<SUBSCRIPTION_ID>"
|
48 |
+
# resource_group = "<RESOURCE_GROUP>"
|
49 |
+
# workspace = "<AML_WORKSPACE_NAME>"
|
50 |
+
# workspace_ml_client = MLClient(
|
51 |
+
# credential, subscription_id, resource_group, workspace
|
52 |
+
# )
|
53 |
+
# # Connect to the HuggingFaceHub registry
|
54 |
+
# registry_ml_client = MLClient(credential, registry_name="HuggingFace")
|
55 |
+
# print(registry_ml_client)
|
56 |
+
|
57 |
+
'''
|
58 |
+
def OCR_to_dict_Falcon(logger, OCR, VVE):
|
59 |
+
# Find a similar example from the domain knowledge
|
60 |
+
domain_knowledge_example = VVE.query_db(OCR, 4)
|
61 |
+
similarity = VVE.get_similarity()
|
62 |
+
domain_knowledge_example_string = json.dumps(domain_knowledge_example)
|
63 |
+
|
64 |
+
try:
|
65 |
+
logger.info(f'Length of OCR raw -- {len(OCR)}')
|
66 |
+
except:
|
67 |
+
print(f'Length of OCR raw -- {len(OCR)}')
|
68 |
+
|
69 |
+
# Create input: output: for Falcon
|
70 |
+
# Assuming Falcon requires a similar structure as PaLM
|
71 |
+
in_list, out_list = create_OCR_analog_for_input(domain_knowledge_example)
|
72 |
+
|
73 |
+
# Construct the prompt for Falcon
|
74 |
+
# Adjust this based on Falcon's requirements
|
75 |
+
# prompt = PROMPT_Falcon_skeleton(OCR, in_list, out_list)
|
76 |
+
prompt = PROMPT_PaLM_UMICH_skeleton_all_asia(OCR, in_list, out_list) # must provide examples to PaLM differently than for chatGPT, at least 2 examples
|
77 |
+
|
78 |
+
|
79 |
+
nt = num_tokens_from_string(prompt, "falcon_model_name") # Replace "falcon_model_name" with the appropriate model name for Falcon
|
80 |
+
try:
|
81 |
+
logger.info(f'Prompt token length --- {nt}')
|
82 |
+
except:
|
83 |
+
print(f'Prompt token length --- {nt}')
|
84 |
+
|
85 |
+
# Assuming Falcon has a similar API structure as PaLM
|
86 |
+
# Adjust the settings based on Falcon's requirements
|
87 |
+
Falcon_settings = {
|
88 |
+
'model': 'models/falcon_model_name', # Replace with the appropriate model name for Falcon
|
89 |
+
'temperature': 0,
|
90 |
+
'candidate_count': 1,
|
91 |
+
'top_k': 40,
|
92 |
+
'top_p': 0.95,
|
93 |
+
'max_output_tokens': 8000,
|
94 |
+
'stop_sequences': [],
|
95 |
+
# Add any other required settings for Falcon
|
96 |
+
}
|
97 |
+
|
98 |
+
# Send the prompt to Falcon for inference
|
99 |
+
# Adjust the API call based on Falcon's requirements
|
100 |
+
response = falcon.generate_text(**Falcon_settings, prompt=prompt)
|
101 |
+
|
102 |
+
# Process the response from Falcon
|
103 |
+
if response and response.result:
|
104 |
+
if isinstance(response.result, (str, bytes)):
|
105 |
+
response_valid = check_and_redo_JSON(response, Falcon_settings, logger)
|
106 |
+
else:
|
107 |
+
response_valid = {}
|
108 |
+
else:
|
109 |
+
response_valid = {}
|
110 |
+
|
111 |
+
return response_valid
|
112 |
+
'''
|
vouchervision/LLM_PaLM.py
ADDED
@@ -0,0 +1,209 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import sys
|
3 |
+
import inspect
|
4 |
+
import json
|
5 |
+
from json import JSONDecodeError
|
6 |
+
import tiktoken
|
7 |
+
import random
|
8 |
+
import google.generativeai as palm
|
9 |
+
|
10 |
+
currentdir = os.path.dirname(os.path.abspath(
|
11 |
+
inspect.getfile(inspect.currentframe())))
|
12 |
+
parentdir = os.path.dirname(currentdir)
|
13 |
+
sys.path.append(parentdir)
|
14 |
+
|
15 |
+
from prompt_catalog import PromptCatalog
|
16 |
+
from general_utils import num_tokens_from_string
|
17 |
+
|
18 |
+
"""
|
19 |
+
DEPRECATED:
|
20 |
+
Safety setting regularly block a response, so set to 4 to disable
|
21 |
+
|
22 |
+
class HarmBlockThreshold(Enum):
|
23 |
+
HARM_BLOCK_THRESHOLD_UNSPECIFIED = 0
|
24 |
+
BLOCK_LOW_AND_ABOVE = 1
|
25 |
+
BLOCK_MEDIUM_AND_ABOVE = 2
|
26 |
+
BLOCK_ONLY_HIGH = 3
|
27 |
+
BLOCK_NONE = 4
|
28 |
+
"""
|
29 |
+
|
30 |
+
SAFETY_SETTINGS = [
|
31 |
+
{
|
32 |
+
"category": "HARM_CATEGORY_DEROGATORY",
|
33 |
+
"threshold": "BLOCK_NONE",
|
34 |
+
},
|
35 |
+
{
|
36 |
+
"category": "HARM_CATEGORY_TOXICITY",
|
37 |
+
"threshold": "BLOCK_NONE",
|
38 |
+
},
|
39 |
+
{
|
40 |
+
"category": "HARM_CATEGORY_VIOLENCE",
|
41 |
+
"threshold": "BLOCK_NONE",
|
42 |
+
},
|
43 |
+
{
|
44 |
+
"category": "HARM_CATEGORY_SEXUAL",
|
45 |
+
"threshold": "BLOCK_NONE",
|
46 |
+
},
|
47 |
+
{
|
48 |
+
"category": "HARM_CATEGORY_MEDICAL",
|
49 |
+
"threshold": "BLOCK_NONE",
|
50 |
+
},
|
51 |
+
{
|
52 |
+
"category": "HARM_CATEGORY_DANGEROUS",
|
53 |
+
"threshold": "BLOCK_NONE",
|
54 |
+
},
|
55 |
+
]
|
56 |
+
|
57 |
+
PALM_SETTINGS = {
|
58 |
+
'model': 'models/text-bison-001',
|
59 |
+
'temperature': 0,
|
60 |
+
'candidate_count': 1,
|
61 |
+
'top_k': 40,
|
62 |
+
'top_p': 0.95,
|
63 |
+
'max_output_tokens': 8000,
|
64 |
+
'stop_sequences': [],
|
65 |
+
'safety_settings': SAFETY_SETTINGS,
|
66 |
+
}
|
67 |
+
|
68 |
+
PALM_SETTINGS_REDO = {
|
69 |
+
'model': 'models/text-bison-001',
|
70 |
+
'temperature': 0.05,
|
71 |
+
'candidate_count': 1,
|
72 |
+
'top_k': 40,
|
73 |
+
'top_p': 0.95,
|
74 |
+
'max_output_tokens': 8000,
|
75 |
+
'stop_sequences': [],
|
76 |
+
'safety_settings': SAFETY_SETTINGS,
|
77 |
+
}
|
78 |
+
|
79 |
+
def OCR_to_dict_PaLM(logger, OCR, prompt_version, VVE):
|
80 |
+
try:
|
81 |
+
logger.info(f'Length of OCR raw -- {len(OCR)}')
|
82 |
+
except:
|
83 |
+
print(f'Length of OCR raw -- {len(OCR)}')
|
84 |
+
|
85 |
+
# prompt = PROMPT_PaLM_UMICH_skeleton_all_asia(OCR, in_list, out_list) # must provide examples to PaLM differently than for chatGPT, at least 2 examples
|
86 |
+
Prompt = PromptCatalog(OCR)
|
87 |
+
if prompt_version in ['prompt_v2_palm2']:
|
88 |
+
version = 'v2'
|
89 |
+
prompt = Prompt.prompt_v2_palm2(OCR)
|
90 |
+
|
91 |
+
elif prompt_version in ['prompt_v1_palm2',]:
|
92 |
+
version = 'v1'
|
93 |
+
# create input: output: for PaLM
|
94 |
+
# Find a similar example from the domain knowledge
|
95 |
+
domain_knowledge_example = VVE.query_db(OCR, 4)
|
96 |
+
similarity= VVE.get_similarity()
|
97 |
+
domain_knowledge_example_string = json.dumps(domain_knowledge_example)
|
98 |
+
in_list, out_list = create_OCR_analog_for_input(domain_knowledge_example)
|
99 |
+
prompt = Prompt.prompt_v1_palm2(in_list, out_list, OCR)
|
100 |
+
|
101 |
+
elif prompt_version in ['prompt_v1_palm2_noDomainKnowledge',]:
|
102 |
+
version = 'v1'
|
103 |
+
prompt = Prompt.prompt_v1_palm2_noDomainKnowledge(OCR)
|
104 |
+
else:
|
105 |
+
version = 'custom'
|
106 |
+
prompt, n_fields, xlsx_headers = Prompt.prompt_v2_custom(prompt_version, OCR=OCR, is_palm=True)
|
107 |
+
# raise
|
108 |
+
|
109 |
+
nt = num_tokens_from_string(prompt, "cl100k_base")
|
110 |
+
# try:
|
111 |
+
logger.info(f'Prompt token length --- {nt}')
|
112 |
+
# except:
|
113 |
+
# print(f'Prompt token length --- {nt}')
|
114 |
+
|
115 |
+
do_use_SOP = False ########
|
116 |
+
|
117 |
+
if do_use_SOP:
|
118 |
+
'''TODO: Check back later to see if LangChain will support PaLM'''
|
119 |
+
# logger.info(f'Waiting for PaLM API call --- Using StructuredOutputParser')
|
120 |
+
# response = structured_output_parser(OCR, prompt, logger)
|
121 |
+
# return response['Dictionary']
|
122 |
+
pass
|
123 |
+
|
124 |
+
else:
|
125 |
+
# try:
|
126 |
+
logger.info(f'Waiting for PaLM 2 API call')
|
127 |
+
# except:
|
128 |
+
# print(f'Waiting for PaLM 2 API call --- Content')
|
129 |
+
|
130 |
+
# safety_thresh = 4
|
131 |
+
# PaLM_settings = {'model': 'models/text-bison-001','temperature': 0,'candidate_count': 1,'top_k': 40,'top_p': 0.95,'max_output_tokens': 8000,'stop_sequences': [],
|
132 |
+
# 'safety_settings': [{"category":"HARM_CATEGORY_DEROGATORY","threshold":safety_thresh},{"category":"HARM_CATEGORY_TOXICITY","threshold":safety_thresh},{"category":"HARM_CATEGORY_VIOLENCE","threshold":safety_thresh},{"category":"HARM_CATEGORY_SEXUAL","threshold":safety_thresh},{"category":"HARM_CATEGORY_MEDICAL","threshold":safety_thresh},{"category":"HARM_CATEGORY_DANGEROUS","threshold":safety_thresh}],}
|
133 |
+
response = palm.generate_text(prompt=prompt, **PALM_SETTINGS)
|
134 |
+
|
135 |
+
|
136 |
+
if response and response.result:
|
137 |
+
if isinstance(response.result, (str, bytes)):
|
138 |
+
response_valid = check_and_redo_JSON(response, logger, version)
|
139 |
+
else:
|
140 |
+
response_valid = {}
|
141 |
+
else:
|
142 |
+
response_valid = {}
|
143 |
+
|
144 |
+
logger.info(f'Candidate JSON\n{response.result}')
|
145 |
+
return response_valid, nt
|
146 |
+
|
147 |
+
def check_and_redo_JSON(response, logger, version):
|
148 |
+
try:
|
149 |
+
response_valid = json.loads(response.result)
|
150 |
+
logger.info(f'Response --- First call passed')
|
151 |
+
return response_valid
|
152 |
+
except JSONDecodeError:
|
153 |
+
|
154 |
+
try:
|
155 |
+
response_valid = json.loads(response.result.strip('```').replace('json\n', '', 1).replace('json', '', 1))
|
156 |
+
logger.info(f'Response --- Manual removal of ```json succeeded')
|
157 |
+
return response_valid
|
158 |
+
except:
|
159 |
+
logger.info(f'Response --- First call failed. Redo...')
|
160 |
+
Prompt = PromptCatalog()
|
161 |
+
if version == 'v1':
|
162 |
+
prompt_redo = Prompt.prompt_palm_redo_v1(response.result)
|
163 |
+
elif version == 'v2':
|
164 |
+
prompt_redo = Prompt.prompt_palm_redo_v2(response.result)
|
165 |
+
elif version == 'custom':
|
166 |
+
prompt_redo = Prompt.prompt_v2_custom_redo(response.result, is_palm=True)
|
167 |
+
|
168 |
+
|
169 |
+
# prompt_redo = PROMPT_PaLM_Redo(response.result)
|
170 |
+
try:
|
171 |
+
response = palm.generate_text(prompt=prompt_redo, **PALM_SETTINGS)
|
172 |
+
response_valid = json.loads(response.result)
|
173 |
+
logger.info(f'Response --- Second call passed')
|
174 |
+
return response_valid
|
175 |
+
except JSONDecodeError:
|
176 |
+
logger.info(f'Response --- Second call failed. Final redo. Temperature changed to 0.05')
|
177 |
+
try:
|
178 |
+
response = palm.generate_text(prompt=prompt_redo, **PALM_SETTINGS_REDO)
|
179 |
+
response_valid = json.loads(response.result)
|
180 |
+
logger.info(f'Response --- Third call passed')
|
181 |
+
return response_valid
|
182 |
+
except JSONDecodeError:
|
183 |
+
return None
|
184 |
+
|
185 |
+
|
186 |
+
def create_OCR_analog_for_input(domain_knowledge_example):
|
187 |
+
in_list = []
|
188 |
+
out_list = []
|
189 |
+
# Iterate over the domain_knowledge_example (list of dictionaries)
|
190 |
+
for row_dict in domain_knowledge_example:
|
191 |
+
# Convert the dictionary to a JSON string and add it to the out_list
|
192 |
+
domain_knowledge_example_string = json.dumps(row_dict)
|
193 |
+
out_list.append(domain_knowledge_example_string)
|
194 |
+
|
195 |
+
# Create a single string from all values in the row_dict
|
196 |
+
row_text = '||'.join(str(v) for v in row_dict.values())
|
197 |
+
|
198 |
+
# Split the row text by '||', shuffle the parts, and then re-join with a single space
|
199 |
+
parts = row_text.split('||')
|
200 |
+
random.shuffle(parts)
|
201 |
+
shuffled_text = ' '.join(parts)
|
202 |
+
|
203 |
+
# Add the shuffled_text to the in_list
|
204 |
+
in_list.append(shuffled_text)
|
205 |
+
return in_list, out_list
|
206 |
+
|
207 |
+
|
208 |
+
def strip_problematic_chars(s):
|
209 |
+
return ''.join(c for c in s if c.isprintable())
|
vouchervision/LLM_chatGPT_3_5.py
ADDED
@@ -0,0 +1,420 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import openai
|
2 |
+
import os, json, sys, inspect, time, requests
|
3 |
+
from langchain.output_parsers import StructuredOutputParser, ResponseSchema
|
4 |
+
from langchain.prompts import PromptTemplate, ChatPromptTemplate, HumanMessagePromptTemplate
|
5 |
+
from langchain.llms import OpenAI
|
6 |
+
from langchain.chat_models import ChatOpenAI, AzureChatOpenAI
|
7 |
+
from langchain.schema import HumanMessage
|
8 |
+
from general_utils import num_tokens_from_string
|
9 |
+
|
10 |
+
currentdir = os.path.dirname(os.path.abspath(
|
11 |
+
inspect.getfile(inspect.currentframe())))
|
12 |
+
parentdir = os.path.dirname(currentdir)
|
13 |
+
sys.path.append(parentdir)
|
14 |
+
|
15 |
+
from prompts import PROMPT_UMICH_skeleton_all_asia, PROMPT_OCR_Organized, PROMPT_UMICH_skeleton_all_asia_GPT4, PROMPT_OCR_Organized_GPT4, PROMPT_JSON
|
16 |
+
from prompt_catalog import PromptCatalog
|
17 |
+
|
18 |
+
RETRY_DELAY = 61 # Wait 60 seconds before retrying
|
19 |
+
MAX_RETRIES = 5 # Maximum number of retries
|
20 |
+
|
21 |
+
|
22 |
+
def azure_call(model, messages):
|
23 |
+
response = model(messages=messages)
|
24 |
+
return response
|
25 |
+
|
26 |
+
def OCR_to_dict(is_azure, logger, MODEL, prompt, llm, prompt_version):
|
27 |
+
for i in range(MAX_RETRIES):
|
28 |
+
try:
|
29 |
+
do_use_SOP = True
|
30 |
+
|
31 |
+
if do_use_SOP:
|
32 |
+
logger.info(f'Waiting for {MODEL} API call --- Using StructuredOutputParser')
|
33 |
+
response = structured_output_parser(is_azure, MODEL, llm, prompt, logger, prompt_version)
|
34 |
+
if response is None:
|
35 |
+
return None
|
36 |
+
else:
|
37 |
+
return response['Dictionary']
|
38 |
+
|
39 |
+
else:
|
40 |
+
### Direct GPT ###
|
41 |
+
logger.info(f'Waiting for {MODEL} API call')
|
42 |
+
if not is_azure:
|
43 |
+
response = openai.ChatCompletion.create(
|
44 |
+
model=MODEL,
|
45 |
+
temperature = 0,
|
46 |
+
messages=[
|
47 |
+
{"role": "system", "content": "You are a helpful assistant acting as a transcription expert and your job is to transcribe herbarium specimen labels based on OCR data and reformat it to meet Darwin Core Archive Standards into a Python dictionary based on certain rules."},
|
48 |
+
{"role": "user", "content": prompt},
|
49 |
+
],
|
50 |
+
max_tokens=4096,
|
51 |
+
)
|
52 |
+
# print the model's response
|
53 |
+
return response.choices[0].message['content']
|
54 |
+
else:
|
55 |
+
msg = HumanMessage(
|
56 |
+
content=prompt
|
57 |
+
)
|
58 |
+
response = azure_call(llm, [msg])
|
59 |
+
return response.content
|
60 |
+
except Exception as e:
|
61 |
+
logger.error(f'{e}')
|
62 |
+
if i < MAX_RETRIES - 1: # No delay needed after the last try
|
63 |
+
time.sleep(RETRY_DELAY)
|
64 |
+
else:
|
65 |
+
raise
|
66 |
+
|
67 |
+
# def OCR_to_dict(logger, MODEL, prompt, OCR, BASE_URL, HEADERS):
|
68 |
+
# for i in range(MAX_RETRIES):
|
69 |
+
# try:
|
70 |
+
# do_use_SOP = False
|
71 |
+
|
72 |
+
# if do_use_SOP:
|
73 |
+
# logger.info(f'Waiting for {MODEL} API call --- Using StructuredOutputParser -- Content')
|
74 |
+
# response = structured_output_parser(MODEL, OCR, prompt, logger)
|
75 |
+
# if response is None:
|
76 |
+
# return None
|
77 |
+
# else:
|
78 |
+
# return response['Dictionary']
|
79 |
+
|
80 |
+
# else:
|
81 |
+
# ### Direct GPT through Azure ###
|
82 |
+
# logger.info(f'Waiting for {MODEL} API call')
|
83 |
+
# response = azure_gpt_request(prompt, BASE_URL, HEADERS, model_name=MODEL)
|
84 |
+
|
85 |
+
# # Handle the response data. Note: You might need to adjust the following line based on the exact response format of the Azure API.
|
86 |
+
# content = response.get("choices", [{}])[0].get("message", {}).get("content", "")
|
87 |
+
# return content
|
88 |
+
# except requests.exceptions.RequestException as e: # Replace openai.error.APIError with requests exception.
|
89 |
+
# # Handle HTTP exceptions. You can adjust this based on the Azure API's error responses.
|
90 |
+
# if e.response.status_code == 502:
|
91 |
+
# logger.info(f' *** 502 error was encountered, wait and try again ***')
|
92 |
+
# if i < MAX_RETRIES - 1:
|
93 |
+
# time.sleep(RETRY_DELAY)
|
94 |
+
# else:
|
95 |
+
# raise
|
96 |
+
|
97 |
+
|
98 |
+
def OCR_to_dict_16k(is_azure, logger, MODEL, prompt, llm, prompt_version):
|
99 |
+
for i in range(MAX_RETRIES):
|
100 |
+
try:
|
101 |
+
fs = FunctionSchema()
|
102 |
+
response = openai.ChatCompletion.create(
|
103 |
+
model=MODEL,
|
104 |
+
temperature = 0,
|
105 |
+
messages=[
|
106 |
+
{"role": "system", "content": "You are a helpful assistant acting as a transcription expert and your job is to transcribe herbarium specimen labels based on OCR data and reformat it to meet Darwin Core Archive Standards into a Python dictionary based on certain rules."},
|
107 |
+
{"role": "user", "content": prompt},
|
108 |
+
],
|
109 |
+
max_tokens=8000,
|
110 |
+
function_call= "none",
|
111 |
+
functions= fs.format_C21_AA_V1()
|
112 |
+
|
113 |
+
)
|
114 |
+
# Try to parse the response into JSON
|
115 |
+
call_failed = False
|
116 |
+
try:
|
117 |
+
response_string = response.choices[0].message['content']
|
118 |
+
except:
|
119 |
+
call_failed = True
|
120 |
+
response_string = prompt
|
121 |
+
|
122 |
+
if not call_failed:
|
123 |
+
try:
|
124 |
+
# Try to parse the response into JSON
|
125 |
+
response_dict = json.loads(response_string)
|
126 |
+
return response_dict['Dictionary']
|
127 |
+
except json.JSONDecodeError:
|
128 |
+
# If the response is not a valid JSON, call the structured_output_parser_for_function_calls_fail function
|
129 |
+
logger.info(f'Invalid JSON response, calling structured_output_parser_for_function_calls_fail function')
|
130 |
+
logger.info(f'Waiting for {MODEL} API call --- Using StructuredOutputParser --- JSON Fixer')
|
131 |
+
response_sop = structured_output_parser_for_function_calls_fail(is_azure, MODEL, response_string, logger, llm, prompt_version, is_helper=False)
|
132 |
+
if response_sop is None:
|
133 |
+
return None
|
134 |
+
else:
|
135 |
+
return response_sop['Dictionary']
|
136 |
+
else:
|
137 |
+
try:
|
138 |
+
logger.info(f'Call Failed. Attempting fallback JSON parse without guidance')
|
139 |
+
logger.info(f'Waiting for {MODEL} API call --- Using StructuredOutputParser --- JSON Fixer')
|
140 |
+
response_sop = structured_output_parser_for_function_calls_fail(is_azure, MODEL, response_string, logger, llm, prompt_version, is_helper=False)
|
141 |
+
if response_sop is None:
|
142 |
+
return None
|
143 |
+
else:
|
144 |
+
return response_sop['Dictionary']
|
145 |
+
except:
|
146 |
+
return None
|
147 |
+
except Exception as e:
|
148 |
+
# if e.status_code == 401: # or you can check the error message
|
149 |
+
logger.info(f' *** 401 error was encountered, wait and try again ***')
|
150 |
+
# If a 401 error was encountered, wait and try again
|
151 |
+
if i < MAX_RETRIES - 1: # No delay needed after the last try
|
152 |
+
time.sleep(RETRY_DELAY)
|
153 |
+
else:
|
154 |
+
# If it was a different error, re-raise it
|
155 |
+
raise
|
156 |
+
|
157 |
+
def structured_output_parser(is_azure, MODEL, llm, prompt_template, logger, prompt_version, is_helper=False):
|
158 |
+
if not is_helper:
|
159 |
+
response_schemas = [
|
160 |
+
ResponseSchema(name="SpeciesName", description="Taxonomic determination, genus_species"),
|
161 |
+
ResponseSchema(name="Dictionary", description='Formatted JSON object'),]#prompt_template),]
|
162 |
+
elif is_helper:
|
163 |
+
response_schemas = [
|
164 |
+
ResponseSchema(name="Dictionary", description='Formatted JSON object'),#prompt_template),
|
165 |
+
ResponseSchema(name="Summary", description="A one sentence summary of the content"),]
|
166 |
+
|
167 |
+
output_parser = StructuredOutputParser.from_response_schemas(response_schemas)
|
168 |
+
|
169 |
+
format_instructions = output_parser.get_format_instructions()
|
170 |
+
|
171 |
+
prompt = ChatPromptTemplate(
|
172 |
+
messages=[
|
173 |
+
HumanMessagePromptTemplate.from_template("Parse the OCR text into the correct structured format.\n{format_instructions}\n{question}")
|
174 |
+
],
|
175 |
+
input_variables=["question"],
|
176 |
+
partial_variables={"format_instructions": format_instructions}
|
177 |
+
)
|
178 |
+
|
179 |
+
# Handle Azure vs OpenAI implementation
|
180 |
+
if is_azure:
|
181 |
+
_input = prompt.format_prompt(question=prompt_template)
|
182 |
+
msg = HumanMessage(content=_input.to_string())
|
183 |
+
output = azure_call(llm, [msg])
|
184 |
+
else:
|
185 |
+
chat_model = ChatOpenAI(temperature=0, model=MODEL)
|
186 |
+
_input = prompt.format_prompt(question=prompt_template)
|
187 |
+
output = chat_model(_input.to_messages())
|
188 |
+
|
189 |
+
# Log token length if running with Gradio
|
190 |
+
try:
|
191 |
+
nt = num_tokens_from_string(_input.to_string(), "cl100k_base")
|
192 |
+
logger.info(f'Prompt token length --- {nt}')
|
193 |
+
except:
|
194 |
+
pass
|
195 |
+
|
196 |
+
# Parse the output
|
197 |
+
try:
|
198 |
+
# Check if output is of type 'ai' and parse accordingly
|
199 |
+
if output.type == 'ai':
|
200 |
+
parsed_content = output.content
|
201 |
+
logger.info(f'Formatted JSON\n{parsed_content}')
|
202 |
+
else:
|
203 |
+
# If not 'ai', log and set parsed_content to None or a default value
|
204 |
+
logger.error('Output type is not "ai". Unable to parse.')
|
205 |
+
return None
|
206 |
+
|
207 |
+
# Clean up the parsed content
|
208 |
+
parsed_content = parsed_content.replace('\n', "").replace('\t', "").replace('|', "")
|
209 |
+
|
210 |
+
# Attempt to parse the cleaned content
|
211 |
+
try:
|
212 |
+
refined_response = output_parser.parse(parsed_content)
|
213 |
+
return refined_response
|
214 |
+
except Exception as parse_error:
|
215 |
+
# Handle parsing errors specifically
|
216 |
+
logger.error(f'Parsing Error: {parse_error}')
|
217 |
+
return structured_output_parser_for_function_calls_fail(is_azure, MODEL, parsed_content, logger, llm, prompt_version, is_helper)
|
218 |
+
|
219 |
+
except Exception as e:
|
220 |
+
# Handle any other exceptions that might occur
|
221 |
+
logger.error(f'Unexpected Error: {e}')
|
222 |
+
return None
|
223 |
+
|
224 |
+
def structured_output_parser_for_function_calls_fail(is_azure, MODEL, failed_response, logger, llm, prompt_version, is_helper=False, try_ind=0):
|
225 |
+
if try_ind > 5:
|
226 |
+
return None
|
227 |
+
|
228 |
+
# prompt_redo = PROMPT_JSON('helper' if is_helper else 'dict', failed_response)
|
229 |
+
Prompt = PromptCatalog()
|
230 |
+
if prompt_version in ['prompt_v1_verbose', 'prompt_v1_verbose_noDomainKnowledge']:
|
231 |
+
prompt_redo = Prompt.prompt_gpt_redo_v1(failed_response)
|
232 |
+
elif prompt_version in ['prompt_v2_json_rules']:
|
233 |
+
prompt_redo = Prompt.prompt_gpt_redo_v2(failed_response)
|
234 |
+
else:
|
235 |
+
prompt_redo = Prompt.prompt_v2_custom_redo(failed_response, is_palm=False)
|
236 |
+
|
237 |
+
response_schemas = [
|
238 |
+
ResponseSchema(name="Summary", description="A one sentence summary of the content"),
|
239 |
+
ResponseSchema(name="Dictionary", description='Formatted JSON object')
|
240 |
+
]
|
241 |
+
|
242 |
+
output_parser = StructuredOutputParser.from_response_schemas(response_schemas)
|
243 |
+
format_instructions = output_parser.get_format_instructions()
|
244 |
+
|
245 |
+
prompt = ChatPromptTemplate(
|
246 |
+
messages=[
|
247 |
+
HumanMessagePromptTemplate.from_template("The following text contains JSON formatted text, but there is an error that you need to correct.\n{format_instructions}\n{question}")
|
248 |
+
],
|
249 |
+
input_variables=["question"],
|
250 |
+
partial_variables={"format_instructions": format_instructions}
|
251 |
+
)
|
252 |
+
|
253 |
+
_input = prompt.format_prompt(question=prompt_redo)
|
254 |
+
|
255 |
+
# Log token length if running with Gradio
|
256 |
+
try:
|
257 |
+
nt = num_tokens_from_string(_input.to_string(), "cl100k_base")
|
258 |
+
logger.info(f'Prompt Redo token length --- {nt}')
|
259 |
+
except:
|
260 |
+
pass
|
261 |
+
|
262 |
+
if is_azure:
|
263 |
+
msg = HumanMessage(content=_input.to_string())
|
264 |
+
output = azure_call(llm, [msg])
|
265 |
+
else:
|
266 |
+
chat_model = ChatOpenAI(temperature=0, model=MODEL)
|
267 |
+
output = chat_model(_input.to_messages())
|
268 |
+
|
269 |
+
try:
|
270 |
+
refined_response = output_parser.parse(output.content)
|
271 |
+
except json.decoder.JSONDecodeError as e:
|
272 |
+
try_ind += 1
|
273 |
+
error_message = str(e)
|
274 |
+
redo_content = f'The error messsage is: {error_message}\nThe broken JSON object is: {output.content}'
|
275 |
+
logger.info(f'[Failed JSON Object]\n{output.content}')
|
276 |
+
refined_response = structured_output_parser_for_function_calls_fail(is_azure, MODEL, redo_content, logger, llm, prompt_version, is_helper, try_ind)
|
277 |
+
except:
|
278 |
+
try_ind += 1
|
279 |
+
logger.info(f'[Failed JSON Object]\n{output.content}')
|
280 |
+
refined_response = structured_output_parser_for_function_calls_fail(is_azure, MODEL, output.content, logger, llm, prompt_version, is_helper, try_ind)
|
281 |
+
|
282 |
+
return refined_response
|
283 |
+
|
284 |
+
|
285 |
+
|
286 |
+
|
287 |
+
class FunctionSchema:
|
288 |
+
def __init__(self):
|
289 |
+
pass
|
290 |
+
|
291 |
+
def format_C21_AA_V1(self):
|
292 |
+
return [
|
293 |
+
{
|
294 |
+
"name": "format_C21_AA_V1",
|
295 |
+
"description": "Format the given data into a specific dictionary",
|
296 |
+
"parameters": {
|
297 |
+
"type": "object",
|
298 |
+
"properties": {}, # specify parameters here if your function requires any
|
299 |
+
"required": [] # list of required parameters
|
300 |
+
},
|
301 |
+
"output_type": "json",
|
302 |
+
"output_schema": {
|
303 |
+
"type": "object",
|
304 |
+
"properties": {
|
305 |
+
"Dictionary": {
|
306 |
+
"type": "object",
|
307 |
+
"properties": {
|
308 |
+
"Catalog Number": {"type": "array", "items": {"type": "string"}},
|
309 |
+
"Genus": {"type": "array", "items": {"type": "string"}},
|
310 |
+
"Species": {"type": "array", "items": {"type": "string"}},
|
311 |
+
"subspecies": {"type": "array", "items": {"type": "string"}},
|
312 |
+
"variety": {"type": "array", "items": {"type": "string"}},
|
313 |
+
"forma": {"type": "array", "items": {"type": "string"}},
|
314 |
+
"Country": {"type": "array", "items": {"type": "string"}},
|
315 |
+
"State": {"type": "array", "items": {"type": "string"}},
|
316 |
+
"County": {"type": "array", "items": {"type": "string"}},
|
317 |
+
"Locality Name": {"type": "array", "items": {"type": "string"}},
|
318 |
+
"Min Elevation": {"type": "array", "items": {"type": "string"}},
|
319 |
+
"Max Elevation": {"type": "array", "items": {"type": "string"}},
|
320 |
+
"Elevation Units": {"type": "array", "items": {"type": "string"}},
|
321 |
+
"Verbatim Coordinates": {"type": "array", "items": {"type": "string"}},
|
322 |
+
"Datum": {"type": "array", "items": {"type": "string"}},
|
323 |
+
"Cultivated": {"type": "array", "items": {"type": "string"}},
|
324 |
+
"Habitat": {"type": "array", "items": {"type": "string"}},
|
325 |
+
"Collectors": {"type": "array", "items": {"type": "string"}},
|
326 |
+
"Collector Number": {"type": "array", "items": {"type": "string"}},
|
327 |
+
"Verbatim Date": {"type": "array", "items": {"type": "string"}},
|
328 |
+
"Date": {"type": "array", "items": {"type": "string"}},
|
329 |
+
"End Date": {"type": "array", "items": {"type": "string"}}
|
330 |
+
}
|
331 |
+
},
|
332 |
+
"SpeciesName": {
|
333 |
+
"type": "object",
|
334 |
+
"properties": {
|
335 |
+
"taxonomy": {"type": "array", "items": {"type": "string"}}
|
336 |
+
}
|
337 |
+
}
|
338 |
+
}
|
339 |
+
}
|
340 |
+
}
|
341 |
+
]
|
342 |
+
|
343 |
+
def format_C21_AA_V1_helper(self):
|
344 |
+
return [
|
345 |
+
{
|
346 |
+
"name": "format_C21_AA_V1_helper",
|
347 |
+
"description": "Helper function for format_C21_AA_V1 to further format the given data",
|
348 |
+
"parameters": {
|
349 |
+
"type": "object",
|
350 |
+
"properties": {}, # specify parameters here if your function requires any
|
351 |
+
"required": [] # list of required parameters
|
352 |
+
},
|
353 |
+
"output_type": "json",
|
354 |
+
"output_schema": {
|
355 |
+
"type": "object",
|
356 |
+
"properties": {
|
357 |
+
"Dictionary": {
|
358 |
+
"type": "object",
|
359 |
+
"properties": {
|
360 |
+
"TAXONOMY": {
|
361 |
+
"type": "object",
|
362 |
+
"properties": {
|
363 |
+
"Order": {"type": "array", "items": {"type": "string"}},
|
364 |
+
"Family": {"type": "array", "items": {"type": "string"}},
|
365 |
+
"Genus":{"type": "array", "items": {"type": "string"}},
|
366 |
+
"Species": {"type": "array", "items": {"type": "string"}},
|
367 |
+
"Subspecies": {"type": "array", "items": {"type": "string"}},
|
368 |
+
"Variety": {"type": "array", "items": {"type": "string"}},
|
369 |
+
"Forma": {"type": "array", "items": {"type": "string"}},
|
370 |
+
}
|
371 |
+
},
|
372 |
+
"GEOGRAPHY": {
|
373 |
+
"type": "object",
|
374 |
+
"properties": {
|
375 |
+
"Country": {"type": "array", "items": {"type": "string"}},
|
376 |
+
"State": {"type": "array", "items": {"type": "string"}},
|
377 |
+
"Prefecture": {"type": "array", "items": {"type": "string"}},
|
378 |
+
"Province": {"type": "array", "items": {"type": "string"}},
|
379 |
+
"District": {"type": "array", "items": {"type": "string"}},
|
380 |
+
"County": {"type": "array", "items": {"type": "string"}},
|
381 |
+
"City": {"type": "array", "items": {"type": "string"}},
|
382 |
+
"Administrative Division": {"type": "array", "items": {"type": "string"}},
|
383 |
+
}
|
384 |
+
},
|
385 |
+
"LOCALITY": {
|
386 |
+
"type": "object",
|
387 |
+
"properties": {
|
388 |
+
"Landscape": {"type": "array", "items": {"type": "string"}},
|
389 |
+
"Nearby Places": {"type": "array", "items": {"type": "string"}},
|
390 |
+
}
|
391 |
+
},
|
392 |
+
"COLLECTING": {
|
393 |
+
"type": "object",
|
394 |
+
"properties": {
|
395 |
+
"Collector": {"type": "array", "items": {"type": "string"}},
|
396 |
+
"Collector's Number": {"type": "array", "items": {"type": "string"}},
|
397 |
+
"Verbatim Date": {"type": "array", "items": {"type": "string"}},
|
398 |
+
"Formatted Date": {"type": "array", "items": {"type": "string"}},
|
399 |
+
"Cultivation Status": {"type": "array", "items": {"type": "string"}},
|
400 |
+
"Habitat Description": {"type": "array", "items": {"type": "string"}},
|
401 |
+
}
|
402 |
+
},
|
403 |
+
"MISCELLANEOUS": {
|
404 |
+
"type": "object",
|
405 |
+
"properties": {
|
406 |
+
"Additional Information": {"type": "array", "items": {"type": "string"}},
|
407 |
+
}
|
408 |
+
}
|
409 |
+
}
|
410 |
+
},
|
411 |
+
"Summary": {
|
412 |
+
"type": "object",
|
413 |
+
"properties": {
|
414 |
+
"Content Summary": {"type": "array", "items": {"type": "string"}}
|
415 |
+
}
|
416 |
+
}
|
417 |
+
}
|
418 |
+
}
|
419 |
+
}
|
420 |
+
]
|
vouchervision/LM2_logger.py
ADDED
@@ -0,0 +1,117 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import logging, os, psutil, torch, platform, cpuinfo, yaml #py-cpuinfo
|
2 |
+
from vouchervision.general_utils import get_datetime, print_main_warn, print_main_info
|
3 |
+
|
4 |
+
def start_logging(Dirs, cfg):
|
5 |
+
run_name = cfg['leafmachine']['project']['run_name']
|
6 |
+
path_log = os.path.join(Dirs.path_log, '__'.join(['LM2-log',str(get_datetime()), run_name])+'.log')
|
7 |
+
|
8 |
+
# Disable default StreamHandler
|
9 |
+
logging.getLogger().handlers = []
|
10 |
+
|
11 |
+
# create logger
|
12 |
+
logger = logging.getLogger('Hardware Components')
|
13 |
+
logger.setLevel(logging.DEBUG)
|
14 |
+
|
15 |
+
# create file handler and set level to debug
|
16 |
+
fh = logging.FileHandler(path_log)
|
17 |
+
fh.setLevel(logging.DEBUG)
|
18 |
+
|
19 |
+
# create console handler and set level to debug
|
20 |
+
ch = logging.StreamHandler()
|
21 |
+
ch.setLevel(logging.DEBUG)
|
22 |
+
|
23 |
+
# create formatter
|
24 |
+
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(name)s - %(message)s')
|
25 |
+
|
26 |
+
# add formatter to handlers
|
27 |
+
fh.setFormatter(formatter)
|
28 |
+
ch.setFormatter(formatter)
|
29 |
+
|
30 |
+
# add handlers to logger
|
31 |
+
logger.addHandler(fh)
|
32 |
+
logger.addHandler(ch)
|
33 |
+
|
34 |
+
# Create a logger for the file handler
|
35 |
+
file_logger = logging.getLogger('file_logger')
|
36 |
+
file_logger.setLevel(logging.DEBUG)
|
37 |
+
file_formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
|
38 |
+
file_handler = logging.FileHandler(path_log)
|
39 |
+
file_handler.setLevel(logging.DEBUG)
|
40 |
+
file_handler.setFormatter(file_formatter)
|
41 |
+
file_logger.addHandler(file_handler)
|
42 |
+
# Disable propagation of log messages to the root logger
|
43 |
+
file_logger.propagate = False
|
44 |
+
|
45 |
+
# 'application' code
|
46 |
+
# logger.debug('debug message')
|
47 |
+
# logger.info('info message')
|
48 |
+
# logger.warning('warn message')
|
49 |
+
# logger.error('error message')
|
50 |
+
# logger.critical('critical message')
|
51 |
+
|
52 |
+
# Get CPU information
|
53 |
+
logger.info(f"CPU: {find_cpu_info()}")
|
54 |
+
|
55 |
+
# Get GPU information (using PyTorch)
|
56 |
+
if torch.cuda.is_available():
|
57 |
+
num_gpus = torch.cuda.device_count()
|
58 |
+
if num_gpus == 1:
|
59 |
+
gpu = torch.cuda.get_device_properties(0)
|
60 |
+
logger.info(f"GPU: {gpu.name} ({gpu.total_memory // (1024 * 1024)} MB)")
|
61 |
+
else:
|
62 |
+
for i in range(num_gpus):
|
63 |
+
gpu = torch.cuda.get_device_properties(i)
|
64 |
+
logger.info(f"GPU {i}: {gpu.name} ({gpu.total_memory // (1024 * 1024)} MB)")
|
65 |
+
else:
|
66 |
+
logger.info("No GPU found")
|
67 |
+
logger.info("LeafMachine2 image cropping and embedding search will be extremely slow or not possible.")
|
68 |
+
print_main_info("No GPU found!")
|
69 |
+
print_main_info("LeafMachine2 image cropping and embedding search will be extremely slow or not possible.")
|
70 |
+
|
71 |
+
# Get memory information
|
72 |
+
mem_info = psutil.virtual_memory()
|
73 |
+
logger.info(f"Memory: {mem_info.total // (1024 * 1024)} MB")
|
74 |
+
logger.info(LM2_banner())
|
75 |
+
logger.info(f"Config added to log file")
|
76 |
+
file_logger.info('Config:\n{}'.format(yaml.dump(cfg)))
|
77 |
+
|
78 |
+
|
79 |
+
return logger
|
80 |
+
|
81 |
+
def find_cpu_info():
|
82 |
+
cpu_info = []
|
83 |
+
cpu_info.append(platform.processor())
|
84 |
+
try:
|
85 |
+
|
86 |
+
with open('/proc/cpuinfo') as f:
|
87 |
+
for line in f:
|
88 |
+
if line.startswith('model name'):
|
89 |
+
cpu_info.append(line.split(':')[1].strip())
|
90 |
+
break
|
91 |
+
return ' / '.join(cpu_info)
|
92 |
+
except:
|
93 |
+
try:
|
94 |
+
info = cpuinfo.get_cpu_info()
|
95 |
+
cpu_info = []
|
96 |
+
cpu_info.append(info['brand_raw'])
|
97 |
+
cpu_info.append(f"{info['hz_actual_friendly']}")
|
98 |
+
return ' / '.join(cpu_info)
|
99 |
+
except:
|
100 |
+
return "CPU: UNKNOWN"
|
101 |
+
|
102 |
+
|
103 |
+
def LM2_banner():
|
104 |
+
logo = """
|
105 |
+
_ __ __ __ _ _ ___
|
106 |
+
| | / _| \/ | | | (_) |__ \
|
107 |
+
| | ___ __ _| |_| \ / | __ _ ___| |__ _ _ __ ___ ) |
|
108 |
+
| | / _ \/ _` | _| |\/| |/ _` |/ __| '_ \| | '_ \ / _ \ / /
|
109 |
+
| |___| __/ (_| | | | | | | (_| | (__| | | | | | | | __// /_
|
110 |
+
|______\___|\__,_|_| |_| |_|\__,_|\___|_| |_|_|_| |_|\___|____|
|
111 |
+
__ __ _ _| |_ __ ___ _
|
112 |
+
\ \ / / | | |_ _| \ \ / (_) (_)
|
113 |
+
\ \ / /__ _ _ ___| |__ |_|_ _ _\ \ / / _ ___ _ ___ _ __
|
114 |
+
\ \/ / _ \| | | |/ __| '_ \ / _ \ '__\ \/ / | / __| |/ _ \| '_ \
|
115 |
+
\ / (_) | |_| | (__| | | | __/ | \ / | \__ \ | (_) | | | |
|
116 |
+
\/ \___/ \__,_|\___|_| |_|\___|_| \/ |_|___/_|\___/|_| |_|"""
|
117 |
+
return logo
|
vouchervision/LeafMachine2_Config_Builder.py
ADDED
@@ -0,0 +1,246 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os, yaml, platform
|
2 |
+
|
3 |
+
def get_default_download_folder():
|
4 |
+
system_platform = platform.system() # Gets the system platform, e.g., 'Linux', 'Windows', 'Darwin'
|
5 |
+
|
6 |
+
if system_platform == "Windows":
|
7 |
+
# Typically, the Downloads folder for Windows is in the user's profile folder
|
8 |
+
default_output_folder = os.path.join(os.getenv('USERPROFILE'), 'Downloads')
|
9 |
+
elif system_platform == "Darwin":
|
10 |
+
# Typically, the Downloads folder for macOS is in the user's home directory
|
11 |
+
default_output_folder = os.path.join(os.path.expanduser("~"), 'Downloads')
|
12 |
+
elif system_platform == "Linux":
|
13 |
+
# Typically, the Downloads folder for Linux is in the user's home directory
|
14 |
+
default_output_folder = os.path.join(os.path.expanduser("~"), 'Downloads')
|
15 |
+
else:
|
16 |
+
default_output_folder = "set/path/to/downloads/folder"
|
17 |
+
print("Please manually set the output folder")
|
18 |
+
return default_output_folder
|
19 |
+
|
20 |
+
def build_LM2_config():
|
21 |
+
dir_home = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
|
22 |
+
|
23 |
+
|
24 |
+
# Initialize the base structure
|
25 |
+
config_data = {
|
26 |
+
'leafmachine': {}
|
27 |
+
}
|
28 |
+
|
29 |
+
# Modular sections to be added to 'leafmachine'
|
30 |
+
do_section = {
|
31 |
+
'check_for_illegal_filenames': True,
|
32 |
+
'check_for_corrupt_images_make_vertical': True,
|
33 |
+
'run_leaf_processing': True
|
34 |
+
}
|
35 |
+
|
36 |
+
print_section = {
|
37 |
+
'verbose': True,
|
38 |
+
'optional_warnings': True
|
39 |
+
}
|
40 |
+
|
41 |
+
logging_section = {
|
42 |
+
'log_level': None
|
43 |
+
}
|
44 |
+
|
45 |
+
default_output_folder = get_default_download_folder()
|
46 |
+
project_section = {
|
47 |
+
'dir_output': default_output_folder,
|
48 |
+
# 'dir_output': 'D:/D_Desktop/LM2',
|
49 |
+
'run_name': 'test',
|
50 |
+
'image_location': 'local',
|
51 |
+
'GBIF_mode': 'all',
|
52 |
+
'batch_size': 40,
|
53 |
+
'num_workers': 2,
|
54 |
+
'dir_images_local': '',
|
55 |
+
# 'dir_images_local': 'D:\Dropbox\LM2_Env\Image_Datasets\Manuscript_Images',
|
56 |
+
'path_combined_csv_local': None,
|
57 |
+
'path_occurrence_csv_local': None,
|
58 |
+
'path_images_csv_local': None,
|
59 |
+
'use_existing_plant_component_detections': None,
|
60 |
+
'use_existing_archival_component_detections': None,
|
61 |
+
'process_subset_of_images': False,
|
62 |
+
'dir_images_subset': '',
|
63 |
+
'n_images_per_species': 10,
|
64 |
+
'species_list': ''
|
65 |
+
}
|
66 |
+
|
67 |
+
cropped_components_section = {
|
68 |
+
'do_save_cropped_annotations': False,
|
69 |
+
'save_cropped_annotations': ['label'],
|
70 |
+
'save_per_image': False,
|
71 |
+
'save_per_annotation_class': True,
|
72 |
+
'binarize_labels': False,
|
73 |
+
'binarize_labels_skeletonize': False
|
74 |
+
}
|
75 |
+
|
76 |
+
modules_section = {
|
77 |
+
'armature': False,
|
78 |
+
'specimen_crop': False
|
79 |
+
}
|
80 |
+
|
81 |
+
data_section = {
|
82 |
+
'save_json_rulers': False,
|
83 |
+
'save_json_measurements': False,
|
84 |
+
'save_individual_csv_files_rulers': False,
|
85 |
+
'save_individual_csv_files_measurements': False,
|
86 |
+
'save_individual_csv_files_landmarks': False,
|
87 |
+
'save_individual_efd_files': False,
|
88 |
+
'include_darwin_core_data_from_combined_file': False,
|
89 |
+
'do_apply_conversion_factor': True
|
90 |
+
}
|
91 |
+
|
92 |
+
overlay_section = {
|
93 |
+
'save_overlay_to_pdf': False,
|
94 |
+
'save_overlay_to_jpgs': True,
|
95 |
+
'overlay_dpi': 300, # Between 100 to 300
|
96 |
+
'overlay_background_color': 'black', # Either 'white' or 'black'
|
97 |
+
|
98 |
+
'show_archival_detections': True,
|
99 |
+
'show_plant_detections': True,
|
100 |
+
'show_segmentations': True,
|
101 |
+
'show_landmarks': True,
|
102 |
+
'ignore_archival_detections_classes': [],
|
103 |
+
'ignore_plant_detections_classes': ['leaf_whole', 'specimen'], # Could also include 'leaf_partial' and others if needed
|
104 |
+
'ignore_landmark_classes': [],
|
105 |
+
|
106 |
+
'line_width_archival': 12, # Previous value given was 2
|
107 |
+
'line_width_plant': 12, # Previous value given was 6
|
108 |
+
'line_width_seg': 12, # 12 is specified as "thick"
|
109 |
+
'line_width_efd': 12, # 3 is specified as "thick" but 12 is given here
|
110 |
+
'alpha_transparency_archival': 0.3,
|
111 |
+
'alpha_transparency_plant': 0,
|
112 |
+
'alpha_transparency_seg_whole_leaf': 0.4,
|
113 |
+
'alpha_transparency_seg_partial_leaf': 0.3
|
114 |
+
}
|
115 |
+
|
116 |
+
plant_component_detector_section = {
|
117 |
+
'detector_type': 'Plant_Detector',
|
118 |
+
'detector_version': 'PLANT_GroupAB_200',
|
119 |
+
'detector_iteration': 'PLANT_GroupAB_200',
|
120 |
+
'detector_weights': 'best.pt',
|
121 |
+
'minimum_confidence_threshold': 0.3, # Default is 0.5
|
122 |
+
'do_save_prediction_overlay_images': True,
|
123 |
+
'ignore_objects_for_overlay': [] # 'leaf_partial' can be included if needed
|
124 |
+
}
|
125 |
+
|
126 |
+
archival_component_detector_section = {
|
127 |
+
'detector_type': 'Archival_Detector',
|
128 |
+
'detector_version': 'PREP_final',
|
129 |
+
'detector_iteration': 'PREP_final',
|
130 |
+
'detector_weights': 'best.pt',
|
131 |
+
'minimum_confidence_threshold': 0.5, # Default is 0.5
|
132 |
+
'do_save_prediction_overlay_images': True,
|
133 |
+
'ignore_objects_for_overlay': []
|
134 |
+
}
|
135 |
+
|
136 |
+
armature_component_detector_section = {
|
137 |
+
'detector_type': 'Armature_Detector',
|
138 |
+
'detector_version': 'ARM_A_1000',
|
139 |
+
'detector_iteration': 'ARM_A_1000',
|
140 |
+
'detector_weights': 'best.pt',
|
141 |
+
'minimum_confidence_threshold': 0.5, # Optionally: 0.2
|
142 |
+
'do_save_prediction_overlay_images': True,
|
143 |
+
'ignore_objects_for_overlay': []
|
144 |
+
}
|
145 |
+
|
146 |
+
landmark_detector_section = {
|
147 |
+
'landmark_whole_leaves': True,
|
148 |
+
'landmark_partial_leaves': False,
|
149 |
+
'detector_type': 'Landmark_Detector_YOLO',
|
150 |
+
'detector_version': 'Landmarks',
|
151 |
+
'detector_iteration': 'Landmarks_V2',
|
152 |
+
'detector_weights': 'best.pt',
|
153 |
+
'minimum_confidence_threshold': 0.02,
|
154 |
+
'do_save_prediction_overlay_images': True,
|
155 |
+
'ignore_objects_for_overlay': [],
|
156 |
+
'use_existing_landmark_detections': None, # Example path provided
|
157 |
+
'do_show_QC_images': False,
|
158 |
+
'do_save_QC_images': True,
|
159 |
+
'do_show_final_images': False,
|
160 |
+
'do_save_final_images': True
|
161 |
+
}
|
162 |
+
|
163 |
+
landmark_detector_armature_section = {
|
164 |
+
'upscale_factor': 10,
|
165 |
+
'detector_type': 'Landmark_Detector_YOLO',
|
166 |
+
'detector_version': 'Landmarks_Arm_A_200',
|
167 |
+
'detector_iteration': 'Landmarks_Arm_A_200',
|
168 |
+
'detector_weights': 'last.pt',
|
169 |
+
'minimum_confidence_threshold': 0.06,
|
170 |
+
'do_save_prediction_overlay_images': True,
|
171 |
+
'ignore_objects_for_overlay': [],
|
172 |
+
'use_existing_landmark_detections': None, # Example path provided
|
173 |
+
'do_show_QC_images': True,
|
174 |
+
'do_save_QC_images': True,
|
175 |
+
'do_show_final_images': True,
|
176 |
+
'do_save_final_images': True
|
177 |
+
}
|
178 |
+
|
179 |
+
ruler_detection_section = {
|
180 |
+
'detect_ruler_type': True,
|
181 |
+
'ruler_detector': 'ruler_classifier_38classes_v-1.pt',
|
182 |
+
'ruler_binary_detector': 'model_scripted_resnet_720_withCompression.pt',
|
183 |
+
'minimum_confidence_threshold': 0.4,
|
184 |
+
'save_ruler_validation': False,
|
185 |
+
'save_ruler_validation_summary': True,
|
186 |
+
'save_ruler_processed': False
|
187 |
+
}
|
188 |
+
|
189 |
+
leaf_segmentation_section = {
|
190 |
+
'segment_whole_leaves': True,
|
191 |
+
'segment_partial_leaves': False,
|
192 |
+
|
193 |
+
'keep_only_best_one_leaf_one_petiole': True,
|
194 |
+
|
195 |
+
'save_segmentation_overlay_images_to_pdf': True,
|
196 |
+
'save_each_segmentation_overlay_image': True,
|
197 |
+
'save_individual_overlay_images': True, # Not recommended due to potential file count
|
198 |
+
'overlay_line_width': 1, # Default is 1
|
199 |
+
|
200 |
+
'use_efds_for_png_masks': False, # Requires calculate_elliptic_fourier_descriptors to be True
|
201 |
+
'save_masks_color': True,
|
202 |
+
'save_full_image_masks_color': True,
|
203 |
+
'save_rgb_cropped_images': True,
|
204 |
+
|
205 |
+
'find_minimum_bounding_box': True,
|
206 |
+
|
207 |
+
'calculate_elliptic_fourier_descriptors': True, # Default is True
|
208 |
+
'elliptic_fourier_descriptor_order': 40, # Default is 40
|
209 |
+
|
210 |
+
'segmentation_model': 'GroupB_Dataset_100000_Iter_1176PTS_512Batch_smooth_l1_LR00025_BGR',
|
211 |
+
'minimum_confidence_threshold': 0.7, # Alternatively: 0.9
|
212 |
+
'generate_overlay': True,
|
213 |
+
'overlay_dpi': 300, # Range: 100 to 300
|
214 |
+
'overlay_background_color': 'black' # Options: 'white' or 'black'
|
215 |
+
}
|
216 |
+
|
217 |
+
# Add the sections to the 'leafmachine' key
|
218 |
+
config_data['leafmachine']['do'] = do_section
|
219 |
+
config_data['leafmachine']['print'] = print_section
|
220 |
+
config_data['leafmachine']['logging'] = logging_section
|
221 |
+
config_data['leafmachine']['project'] = project_section
|
222 |
+
config_data['leafmachine']['cropped_components'] = cropped_components_section
|
223 |
+
config_data['leafmachine']['modules'] = modules_section
|
224 |
+
config_data['leafmachine']['data'] = data_section
|
225 |
+
config_data['leafmachine']['overlay'] = overlay_section
|
226 |
+
config_data['leafmachine']['plant_component_detector'] = plant_component_detector_section
|
227 |
+
config_data['leafmachine']['archival_component_detector'] = archival_component_detector_section
|
228 |
+
config_data['leafmachine']['armature_component_detector'] = armature_component_detector_section
|
229 |
+
config_data['leafmachine']['landmark_detector'] = landmark_detector_section
|
230 |
+
config_data['leafmachine']['landmark_detector_armature'] = landmark_detector_armature_section
|
231 |
+
config_data['leafmachine']['ruler_detection'] = ruler_detection_section
|
232 |
+
config_data['leafmachine']['leaf_segmentation'] = leaf_segmentation_section
|
233 |
+
|
234 |
+
return config_data, dir_home
|
235 |
+
|
236 |
+
def write_config_file(config_data, dir_home, filename="LeafMachine2.yaml"):
|
237 |
+
file_path = os.path.join(dir_home, filename)
|
238 |
+
|
239 |
+
# Write the data to a YAML file
|
240 |
+
with open(file_path, "w") as outfile:
|
241 |
+
yaml.dump(config_data, outfile, default_flow_style=False)
|
242 |
+
|
243 |
+
if __name__ == '__main__':
|
244 |
+
config_data, dir_home = build_LM2_config()
|
245 |
+
write_config_file(config_data, dir_home)
|
246 |
+
|
vouchervision/OCR_google_cloud_vision.py
ADDED
@@ -0,0 +1,107 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os, io, sys, inspect
|
2 |
+
from google.cloud import vision, storage
|
3 |
+
from PIL import Image, ImageDraw
|
4 |
+
|
5 |
+
currentdir = os.path.dirname(os.path.abspath(
|
6 |
+
inspect.getfile(inspect.currentframe())))
|
7 |
+
parentdir = os.path.dirname(currentdir)
|
8 |
+
sys.path.append(parentdir)
|
9 |
+
|
10 |
+
def draw_boxes(image, bounds, color):
|
11 |
+
if bounds:
|
12 |
+
draw = ImageDraw.Draw(image)
|
13 |
+
width, height = image.size
|
14 |
+
line_width = int((width + height) / 2 * 0.001) # This sets the line width as 0.5% of the average dimension
|
15 |
+
|
16 |
+
for bound in bounds:
|
17 |
+
draw.polygon(
|
18 |
+
[
|
19 |
+
bound["vertices"][0]["x"], bound["vertices"][0]["y"],
|
20 |
+
bound["vertices"][1]["x"], bound["vertices"][1]["y"],
|
21 |
+
bound["vertices"][2]["x"], bound["vertices"][2]["y"],
|
22 |
+
bound["vertices"][3]["x"], bound["vertices"][3]["y"],
|
23 |
+
],
|
24 |
+
outline=color,
|
25 |
+
width=line_width
|
26 |
+
)
|
27 |
+
return image
|
28 |
+
|
29 |
+
def detect_text(path):
|
30 |
+
client = vision.ImageAnnotatorClient()
|
31 |
+
with io.open(path, 'rb') as image_file:
|
32 |
+
content = image_file.read()
|
33 |
+
image = vision.Image(content=content)
|
34 |
+
response = client.document_text_detection(image=image)
|
35 |
+
texts = response.text_annotations
|
36 |
+
|
37 |
+
if response.error.message:
|
38 |
+
raise Exception(
|
39 |
+
'{}\nFor more info on error messages, check: '
|
40 |
+
'https://cloud.google.com/apis/design/errors'.format(
|
41 |
+
response.error.message))
|
42 |
+
|
43 |
+
# Extract bounding boxes
|
44 |
+
bounds = []
|
45 |
+
text_to_box_mapping = {}
|
46 |
+
for text in texts[1:]: # Skip the first entry, as it represents the entire detected text
|
47 |
+
# Convert BoundingPoly to dictionary
|
48 |
+
bound_dict = {
|
49 |
+
"vertices": [
|
50 |
+
{"x": vertex.x, "y": vertex.y} for vertex in text.bounding_poly.vertices
|
51 |
+
]
|
52 |
+
}
|
53 |
+
bounds.append(bound_dict)
|
54 |
+
text_to_box_mapping[str(bound_dict)] = text.description
|
55 |
+
|
56 |
+
if texts:
|
57 |
+
# cleaned_text = texts[0].description.replace("\n", " ").replace("\t", " ").replace("|", " ")
|
58 |
+
cleaned_text = texts[0].description
|
59 |
+
return cleaned_text, bounds, text_to_box_mapping
|
60 |
+
else:
|
61 |
+
return '', None, None
|
62 |
+
|
63 |
+
def overlay_boxes_on_image(path, bounds):
|
64 |
+
image = Image.open(path)
|
65 |
+
draw_boxes(image, bounds, "green")
|
66 |
+
return image
|
67 |
+
|
68 |
+
|
69 |
+
|
70 |
+
|
71 |
+
|
72 |
+
|
73 |
+
|
74 |
+
|
75 |
+
|
76 |
+
|
77 |
+
|
78 |
+
|
79 |
+
|
80 |
+
|
81 |
+
|
82 |
+
|
83 |
+
|
84 |
+
|
85 |
+
|
86 |
+
|
87 |
+
|
88 |
+
# ''' Google Vision'''
|
89 |
+
# def detect_text(path):
|
90 |
+
# """Detects text in the file located in the local filesystem."""
|
91 |
+
# client = vision.ImageAnnotatorClient()
|
92 |
+
|
93 |
+
# with io.open(path, 'rb') as image_file:
|
94 |
+
# content = image_file.read()
|
95 |
+
|
96 |
+
# image = vision.Image(content=content)
|
97 |
+
|
98 |
+
# response = client.document_text_detection(image=image)
|
99 |
+
# texts = response.text_annotations
|
100 |
+
|
101 |
+
# if response.error.message:
|
102 |
+
# raise Exception(
|
103 |
+
# '{}\nFor more info on error messages, check: '
|
104 |
+
# 'https://cloud.google.com/apis/design/errors'.format(
|
105 |
+
# response.error.message))
|
106 |
+
|
107 |
+
# return texts[0].description if texts else ''
|
vouchervision/PaLM_example_script.py
ADDED
@@ -0,0 +1,70 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
At the command line, only need to run once to install the package via pip:
|
3 |
+
$ pip install google-generativeai
|
4 |
+
"""
|
5 |
+
|
6 |
+
import google.generativeai as palm
|
7 |
+
|
8 |
+
palm.configure(api_key="YOUR API KEY")
|
9 |
+
|
10 |
+
defaults = {
|
11 |
+
'model': 'models/text-bison-001',
|
12 |
+
'temperature': 0,
|
13 |
+
'candidate_count': 1,
|
14 |
+
'top_k': 40,
|
15 |
+
'top_p': 0.95,
|
16 |
+
'max_output_tokens': 1024,
|
17 |
+
'stop_sequences': [],
|
18 |
+
'safety_settings': [{"category":"HARM_CATEGORY_DEROGATORY","threshold":1},{"category":"HARM_CATEGORY_TOXICITY","threshold":1},{"category":"HARM_CATEGORY_VIOLENCE","threshold":2},{"category":"HARM_CATEGORY_SEXUAL","threshold":2},{"category":"HARM_CATEGORY_MEDICAL","threshold":2},{"category":"HARM_CATEGORY_DANGEROUS","threshold":2}],
|
19 |
+
}
|
20 |
+
prompt = """1. Your job is to return a new dict based on the structure of the reference dict ref_dict and these are your rules.
|
21 |
+
2. You must look at ref_dict and refactor the new text called OCR to match the same formatting.
|
22 |
+
3. OCR contains unstructured text inside of [], use your knowledge to put the OCR text into the correct ref_dict column.
|
23 |
+
4. If OCR is mostly empty and contains substantially less text than the ref_dict examples, then only return "None" and skip all other steps.
|
24 |
+
5. If there is a field that does not have a direct proxy in the OCR text, you can fill it in based on your knowledge, but you cannot generate new information.
|
25 |
+
6. Never put text from the ref_dict values into the new dict, but you must use the headers from ref_dict.
|
26 |
+
7. There cannot be duplicate dictionary fields.
|
27 |
+
8. Only return the new dict, do not explain your answer.
|
28 |
+
|
29 |
+
"Genus" - {"format": "[Genus]" or "[Family] indet" if no genus", "null_value": "", "description": taxonomic determination to genus, do captalize genus}
|
30 |
+
"Species"- {"format": "[species]" or "indet" if no species, "null_value": "", "description": taxonomic determination to species, do not captalize species}
|
31 |
+
"subspecies" - {"format": "[subspecies]", "null_value": "", "description": taxonomic determination to subspecies (subsp.)}
|
32 |
+
"variety" - {"format": "[variety]", "null_value": "", "description": taxonomic determination to variety (var)}
|
33 |
+
"forma" - {"format": "[form]", "null_value": "", "description": taxonomic determination to form (f.)}
|
34 |
+
|
35 |
+
"Country" - {"format": "[Country]", "null_value": "no data", "description": Country that corresponds to the current geographic location of collection; capitalize first letter of each word; use the entire location name even if an abreviation is given}
|
36 |
+
"State" - {"format": "[Adm. Division 1]", "null_value": "no data", "description": Administrative division 1 that corresponds to the current geographic location of collection; capitalize first letter of each word}
|
37 |
+
"County" - {"format": "[Adm. Division 2]", "null_value": "no data", "description": Administrative division 2 that corresponds to the current geographic location of collection; capitalize first letter of each word}
|
38 |
+
"Locality Name" - {"format": "verbatim", if no geographic info: "no data provided on label of catalog no: [######]", or if illegible: "locality present but illegible/not translated for catalog no: #######", or if no named locality: "no named locality for catalog no: #######", "description": "Description of geographic location or landscape"}
|
39 |
+
|
40 |
+
"Min Elevation" - {format: "elevation integer", "null_value": "","description": Elevation or altitude in meters, convert from feet to meters if 'm' or 'meters' is not in the text and round to integer, default field for elevation if a range is not given}
|
41 |
+
"Max Elevation" - {format: "elevation integer", "null_value": "","description": Elevation or altitude in meters, convert from feet to meters if 'm' or 'meters' is not in the text and round to integer, maximum elevation if there are two elevations listed but '' otherwise}
|
42 |
+
"Elevation Units" - {format: "m", "null_value": "","description": "m" only if an elevation is present}
|
43 |
+
|
44 |
+
"Verbatim Coordinates" - {"format": "[Lat, Long | UTM | TRS]", "null_value": "", "description": Verbatim coordinates as they appear on the label, fix typos to match standardized GPS coordinate format}
|
45 |
+
|
46 |
+
"Datum" - {"format": "[WGS84, NAD23 etc.]", "null_value": "not present", "description": Datum of coordinates on label; "" is GPS coordinates are not in OCR}
|
47 |
+
"Cultivated" - {"format": "yes", "null_value": "", "description": Indicates if specimen was grown in cultivation}
|
48 |
+
"Habitat" - {"format": "verbatim", "null_value": "", "description": Description of habitat or location where specimen was collected, ignore descriptions of the plant itself}
|
49 |
+
"Collectors" - {"format": "[Collector]", "null_value": "not present", "description": Full name of person (i.e., agent) who collected the specimen; if more than one person then separate the names with commas}
|
50 |
+
"Collector Number" - {"format": "[Collector No.]", "null_value": "s.n.", "description": Sequential number assigned to collection, associated with the collector}
|
51 |
+
"Verbatim Date" - {"format": "verbatim", "null_value": "s.d.", "description": Date of collection exactly as it appears on the label}
|
52 |
+
"Date" - {"format": "[yyyy-mm-dd]", "null_value": "", "description": Date of collection formatted as year, month, and day; zeros may be used for unknown values i.e. 0000-00-00 if no date, YYYY-00-00 if only year, YYYY-MM-00 if no day}
|
53 |
+
"End Date" - {"format": "[yyyy-mm-dd]", "null_value": "", "description": If date range is listed, later date of collection range}
|
54 |
+
input: El Kala Algeria Aegilops El Tarf 1919-05-20 locality not transcribed for catalog no: 1702723 Charles d'Alleizette ovata May 20, 1919 s.n.
|
55 |
+
|
56 |
+
output: {"Genus": "Aegilops", "Species": "ovata", "subspecies": "", "variety": "", "forma": "", "Country": "Algeria", "State": "El Tarf", "County": "El Kala", "Locality Name": "locality not transcribed for catalog no: 1702723", "Min Elevation": "", "Max Elevation": "", "Elevation Units": "", "Verbatim Coordinates": "", "Datum": "", "Cultivated": "", "Habitat": "", "Collectors": "Charles d'Alleizette", "Collector Number": "s.n.", "Verbatim Date": "May 20, 1919", "Date": "1919-05-20", "End Date": ""}
|
57 |
+
|
58 |
+
input: El Kala Algeria Agrostis El Tarf 1918-06-08 locality not transcribed for catalog no: 1702919 Charles d'Alleizette pallida 8 Juin 1918 7748
|
59 |
+
|
60 |
+
output: {"Genus": "Agrostis", "Species": "pallida", "subspecies": "", "variety": "", "forma": "", "Country": "Algeria", "State": "El Tarf", "County": "El Kala", "Locality Name": "locality not transcribed for catalog no: 1702919", "Min Elevation": "", "Max Elevation": "", "Elevation Units": "", "Verbatim Coordinates": "", "Datum": "", "Cultivated": "", "Habitat": "", "Collectors": "Charles d'Alleizette", "Collector Number": "7748", "Verbatim Date": "8 Juin 1918", "Date": "1918-06-08", "End Date": ""}
|
61 |
+
|
62 |
+
input: Gympie river nr. sawmill Australia Hydrilla Queensland 1943-12-26 locality not transcribed for catalog no: 1702580 M. S. Clemens verticillata Dec. 26/43 43329
|
63 |
+
|
64 |
+
output:"""
|
65 |
+
|
66 |
+
response = palm.generate_text(
|
67 |
+
**defaults,
|
68 |
+
prompt=prompt
|
69 |
+
)
|
70 |
+
print(response.result)
|
vouchervision/VoucherVision_Config_Builder.py
ADDED
@@ -0,0 +1,576 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os, yaml, platform, traceback
|
2 |
+
from vouchervision.LeafMachine2_Config_Builder import get_default_download_folder, write_config_file
|
3 |
+
from vouchervision.general_utils import validate_dir, print_main_fail
|
4 |
+
from vouchervision.vouchervision_main import voucher_vision
|
5 |
+
from general_utils import get_cfg_from_full_path
|
6 |
+
|
7 |
+
def build_VV_config():
|
8 |
+
#############################################
|
9 |
+
############ Set common defaults ############
|
10 |
+
#############################################
|
11 |
+
# Changing the values below will set new
|
12 |
+
# default values each time you open the
|
13 |
+
# VoucherVision user interface
|
14 |
+
#############################################
|
15 |
+
#############################################
|
16 |
+
#############################################
|
17 |
+
|
18 |
+
dir_home = os.path.dirname(os.path.dirname(__file__))
|
19 |
+
run_name = 'test'
|
20 |
+
# dir_images_local = 'D:/Dropbox/LM2_Env/Image_Datasets/GBIF_BroadSample_3SppPerFamily1'
|
21 |
+
dir_images_local = os.path.join(dir_home,'demo','demo_images')
|
22 |
+
|
23 |
+
# The default output location is the computer's "Downloads" folder
|
24 |
+
# You can set dir_output directly by typing the folder path,
|
25 |
+
# OR you can uncomment the line "dir_output = default_output_folder"
|
26 |
+
# to have VoucherVision save to the Downloads folder by default
|
27 |
+
default_output_folder = get_default_download_folder()
|
28 |
+
dir_output = default_output_folder
|
29 |
+
# dir_output = 'D:/D_Desktop/LM2'
|
30 |
+
|
31 |
+
prefix_removal = '' #'MICH-V-'
|
32 |
+
suffix_removal = ''
|
33 |
+
catalog_numerical_only = False
|
34 |
+
|
35 |
+
LLM_version_user = 'Azure GPT 4'
|
36 |
+
prompt_version = 'Version 2' # from ["Version 1", "Version 1 No Domain Knowledge", "Version 2"]
|
37 |
+
use_LeafMachine2_collage_images = False # Use LeafMachine2 collage images
|
38 |
+
|
39 |
+
batch_size = 500
|
40 |
+
|
41 |
+
path_domain_knowledge = os.path.join(dir_home,'domain_knowledge','SLTP_UM_AllAsiaMinimalInRegion.xlsx')
|
42 |
+
embeddings_database_name = os.path.splitext(os.path.basename(path_domain_knowledge))[0]
|
43 |
+
|
44 |
+
#############################################
|
45 |
+
#############################################
|
46 |
+
########## DO NOT EDIT BELOW HERE ###########
|
47 |
+
#############################################
|
48 |
+
#############################################
|
49 |
+
return assemble_config(dir_home, run_name, dir_images_local,dir_output,
|
50 |
+
prefix_removal,suffix_removal,catalog_numerical_only,LLM_version_user,batch_size,
|
51 |
+
path_domain_knowledge,embeddings_database_name,use_LeafMachine2_collage_images,
|
52 |
+
prompt_version, use_domain_knowledge=False)
|
53 |
+
|
54 |
+
def assemble_config(dir_home, run_name, dir_images_local,dir_output,
|
55 |
+
prefix_removal,suffix_removal,catalog_numerical_only,LLM_version_user,batch_size,
|
56 |
+
path_domain_knowledge,embeddings_database_name,use_LeafMachine2_collage_images,
|
57 |
+
prompt_version, use_domain_knowledge=False):
|
58 |
+
|
59 |
+
|
60 |
+
# Initialize the base structure
|
61 |
+
config_data = {
|
62 |
+
'leafmachine': {}
|
63 |
+
}
|
64 |
+
|
65 |
+
# Modular sections to be added to 'leafmachine'
|
66 |
+
do_section = {
|
67 |
+
'check_for_illegal_filenames': False,
|
68 |
+
'check_for_corrupt_images_make_vertical': True,
|
69 |
+
}
|
70 |
+
|
71 |
+
print_section = {
|
72 |
+
'verbose': True,
|
73 |
+
'optional_warnings': True
|
74 |
+
}
|
75 |
+
|
76 |
+
logging_section = {
|
77 |
+
'log_level': None
|
78 |
+
}
|
79 |
+
|
80 |
+
|
81 |
+
project_section = {
|
82 |
+
'dir_output': dir_output,
|
83 |
+
'run_name': run_name,
|
84 |
+
'image_location': 'local',
|
85 |
+
'batch_size': batch_size,
|
86 |
+
'num_workers': 1,
|
87 |
+
'dir_images_local': dir_images_local,
|
88 |
+
'continue_run_from_partial_xlsx': '',
|
89 |
+
'prefix_removal': prefix_removal,
|
90 |
+
'suffix_removal': suffix_removal,
|
91 |
+
'catalog_numerical_only': catalog_numerical_only,
|
92 |
+
'use_domain_knowledge': use_domain_knowledge,
|
93 |
+
'embeddings_database_name': embeddings_database_name,
|
94 |
+
'build_new_embeddings_database': False,
|
95 |
+
'path_to_domain_knowledge_xlsx': path_domain_knowledge,
|
96 |
+
'prompt_version': prompt_version,
|
97 |
+
'delete_all_temps': False,
|
98 |
+
'delete_temps_keep_VVE': False,
|
99 |
+
}
|
100 |
+
|
101 |
+
modules_section = {
|
102 |
+
'specimen_crop': True
|
103 |
+
}
|
104 |
+
|
105 |
+
LLM_version = LLM_version_user
|
106 |
+
use_RGB_label_images = use_LeafMachine2_collage_images # Use LeafMachine2 collage images
|
107 |
+
|
108 |
+
cropped_components_section = {
|
109 |
+
'do_save_cropped_annotations': True,
|
110 |
+
'save_cropped_annotations': ['label','barcode'],
|
111 |
+
'save_per_image': False,
|
112 |
+
'save_per_annotation_class': True,
|
113 |
+
'binarize_labels': False,
|
114 |
+
'binarize_labels_skeletonize': False
|
115 |
+
}
|
116 |
+
|
117 |
+
data_section = {
|
118 |
+
'save_json_rulers': False,
|
119 |
+
'save_json_measurements': False,
|
120 |
+
'save_individual_csv_files_rulers': False,
|
121 |
+
'save_individual_csv_files_measurements': False,
|
122 |
+
'save_individual_csv_files_landmarks': False,
|
123 |
+
'save_individual_efd_files': False,
|
124 |
+
'include_darwin_core_data_from_combined_file': False,
|
125 |
+
'do_apply_conversion_factor': False
|
126 |
+
}
|
127 |
+
|
128 |
+
overlay_section = {
|
129 |
+
'save_overlay_to_pdf': False,
|
130 |
+
'save_overlay_to_jpgs': True,
|
131 |
+
'overlay_dpi': 300, # Between 100 to 300
|
132 |
+
'overlay_background_color': 'black', # Either 'white' or 'black'
|
133 |
+
|
134 |
+
'show_archival_detections': True,
|
135 |
+
'show_plant_detections': True,
|
136 |
+
'show_segmentations': True,
|
137 |
+
'show_landmarks': True,
|
138 |
+
'ignore_archival_detections_classes': [],
|
139 |
+
'ignore_plant_detections_classes': ['leaf_whole', 'specimen'], # Could also include 'leaf_partial' and others if needed
|
140 |
+
'ignore_landmark_classes': [],
|
141 |
+
|
142 |
+
'line_width_archival': 12, # Previous value given was 2
|
143 |
+
'line_width_plant': 12, # Previous value given was 6
|
144 |
+
'line_width_seg': 12, # 12 is specified as "thick"
|
145 |
+
'line_width_efd': 12, # 3 is specified as "thick" but 12 is given here
|
146 |
+
'alpha_transparency_archival': 0.3,
|
147 |
+
'alpha_transparency_plant': 0,
|
148 |
+
'alpha_transparency_seg_whole_leaf': 0.4,
|
149 |
+
'alpha_transparency_seg_partial_leaf': 0.3
|
150 |
+
}
|
151 |
+
|
152 |
+
archival_component_detector_section = {
|
153 |
+
'detector_type': 'Archival_Detector',
|
154 |
+
'detector_version': 'PREP_final',
|
155 |
+
'detector_iteration': 'PREP_final',
|
156 |
+
'detector_weights': 'best.pt',
|
157 |
+
'minimum_confidence_threshold': 0.5, # Default is 0.5
|
158 |
+
'do_save_prediction_overlay_images': True,
|
159 |
+
'ignore_objects_for_overlay': []
|
160 |
+
}
|
161 |
+
|
162 |
+
# Add the sections to the 'leafmachine' key
|
163 |
+
config_data['leafmachine']['do'] = do_section
|
164 |
+
config_data['leafmachine']['print'] = print_section
|
165 |
+
config_data['leafmachine']['logging'] = logging_section
|
166 |
+
config_data['leafmachine']['project'] = project_section
|
167 |
+
config_data['leafmachine']['LLM_version'] = LLM_version
|
168 |
+
config_data['leafmachine']['use_RGB_label_images'] = use_RGB_label_images
|
169 |
+
config_data['leafmachine']['cropped_components'] = cropped_components_section
|
170 |
+
config_data['leafmachine']['modules'] = modules_section
|
171 |
+
config_data['leafmachine']['data'] = data_section
|
172 |
+
config_data['leafmachine']['overlay'] = overlay_section
|
173 |
+
config_data['leafmachine']['archival_component_detector'] = archival_component_detector_section
|
174 |
+
|
175 |
+
return config_data, dir_home
|
176 |
+
|
177 |
+
def build_api_tests(api):
|
178 |
+
dir_home = os.path.dirname(os.path.dirname(__file__))
|
179 |
+
path_to_configs = os.path.join(dir_home,'demo','demo_configs')
|
180 |
+
|
181 |
+
dir_home = os.path.dirname(os.path.dirname(__file__))
|
182 |
+
dir_images_local = os.path.join(dir_home,'demo','demo_images')
|
183 |
+
validate_dir(os.path.join(dir_home,'demo','demo_configs'))
|
184 |
+
path_domain_knowledge = os.path.join(dir_home,'domain_knowledge','SLTP_UM_AllAsiaMinimalInRegion.xlsx')
|
185 |
+
embeddings_database_name = os.path.splitext(os.path.basename(path_domain_knowledge))[0]
|
186 |
+
prefix_removal = ''
|
187 |
+
suffix_removal = ''
|
188 |
+
catalog_numerical_only = False
|
189 |
+
batch_size = 500
|
190 |
+
|
191 |
+
|
192 |
+
# ### Option 1: "GPT 4" of ["GPT 4", "GPT 3.5", "Azure GPT 4", "Azure GPT 3.5", "PaLM 2"]
|
193 |
+
# LLM_version_user = 'Azure GPT 4'
|
194 |
+
|
195 |
+
# ### Option 2: False of [False, True]
|
196 |
+
# use_LeafMachine2_collage_images = False
|
197 |
+
|
198 |
+
# ### Option 3: False of [False, True]
|
199 |
+
# use_domain_knowledge = True
|
200 |
+
|
201 |
+
test_results = {}
|
202 |
+
if api == 'openai':
|
203 |
+
OPT1, OPT2, OPT3 = TestOptionsAPI_openai.get_options()
|
204 |
+
elif api == 'palm':
|
205 |
+
OPT1, OPT2, OPT3 = TestOptionsAPI_palm.get_options()
|
206 |
+
elif api == 'azure_openai':
|
207 |
+
OPT1, OPT2, OPT3 = TestOptionsAPI_azure_openai.get_options()
|
208 |
+
else:
|
209 |
+
raise
|
210 |
+
|
211 |
+
ind = -1
|
212 |
+
ind_opt1 = -1
|
213 |
+
ind_opt2 = -1
|
214 |
+
ind_opt3 = -1
|
215 |
+
|
216 |
+
for opt1 in OPT1:
|
217 |
+
ind_opt1+= 1
|
218 |
+
for opt2 in OPT2:
|
219 |
+
ind_opt2 += 1
|
220 |
+
for opt3 in OPT3:
|
221 |
+
ind += 1
|
222 |
+
ind_opt3 += 1
|
223 |
+
|
224 |
+
LLM_version_user = opt1
|
225 |
+
use_LeafMachine2_collage_images = opt2
|
226 |
+
prompt_version = opt3
|
227 |
+
|
228 |
+
filename = f"{ind}__OPT1-{ind_opt1}__OPT2-{ind_opt2}__OPT3-{ind_opt3}.yaml"
|
229 |
+
run_name = f"{ind}__OPT1-{ind_opt1}__OPT2-{ind_opt2}__OPT3-{ind_opt3}"
|
230 |
+
|
231 |
+
dir_output = os.path.join(dir_home,'demo','demo_output','run_name')
|
232 |
+
validate_dir(dir_output)
|
233 |
+
|
234 |
+
config_data, dir_home = assemble_config(dir_home, run_name, dir_images_local,dir_output,
|
235 |
+
prefix_removal,suffix_removal,catalog_numerical_only,LLM_version_user,batch_size,
|
236 |
+
path_domain_knowledge,embeddings_database_name,use_LeafMachine2_collage_images,
|
237 |
+
prompt_version)
|
238 |
+
|
239 |
+
write_config_file(config_data, os.path.join(dir_home,'demo','demo_configs'),filename=filename)
|
240 |
+
|
241 |
+
test_results[run_name] = False
|
242 |
+
ind_opt3 = -1
|
243 |
+
ind_opt2 = -1
|
244 |
+
ind_opt1 = -1
|
245 |
+
|
246 |
+
return dir_home, path_to_configs, test_results
|
247 |
+
|
248 |
+
def build_demo_tests(llm_version):
|
249 |
+
dir_home = os.path.dirname(os.path.dirname(__file__))
|
250 |
+
path_to_configs = os.path.join(dir_home,'demo','demo_configs')
|
251 |
+
|
252 |
+
dir_home = os.path.dirname(os.path.dirname(__file__))
|
253 |
+
dir_images_local = os.path.join(dir_home,'demo','demo_images')
|
254 |
+
validate_dir(os.path.join(dir_home,'demo','demo_configs'))
|
255 |
+
path_domain_knowledge = os.path.join(dir_home,'domain_knowledge','SLTP_UM_AllAsiaMinimalInRegion.xlsx')
|
256 |
+
embeddings_database_name = os.path.splitext(os.path.basename(path_domain_knowledge))[0]
|
257 |
+
prefix_removal = ''
|
258 |
+
suffix_removal = ''
|
259 |
+
catalog_numerical_only = False
|
260 |
+
batch_size = 500
|
261 |
+
|
262 |
+
|
263 |
+
# ### Option 1: "GPT 4" of ["GPT 4", "GPT 3.5", "Azure GPT 4", "Azure GPT 3.5", "PaLM 2"]
|
264 |
+
# LLM_version_user = 'Azure GPT 4'
|
265 |
+
|
266 |
+
# ### Option 2: False of [False, True]
|
267 |
+
# use_LeafMachine2_collage_images = False
|
268 |
+
|
269 |
+
# ### Option 3: False of [False, True]
|
270 |
+
# use_domain_knowledge = True
|
271 |
+
|
272 |
+
test_results = {}
|
273 |
+
if llm_version == 'gpt':
|
274 |
+
OPT1, OPT2, OPT3 = TestOptionsGPT.get_options()
|
275 |
+
elif llm_version == 'palm':
|
276 |
+
OPT1, OPT2, OPT3 = TestOptionsPalm.get_options()
|
277 |
+
else:
|
278 |
+
raise
|
279 |
+
|
280 |
+
ind = -1
|
281 |
+
ind_opt1 = -1
|
282 |
+
ind_opt2 = -1
|
283 |
+
ind_opt3 = -1
|
284 |
+
|
285 |
+
for opt1 in OPT1:
|
286 |
+
ind_opt1+= 1
|
287 |
+
for opt2 in OPT2:
|
288 |
+
ind_opt2 += 1
|
289 |
+
for opt3 in OPT3:
|
290 |
+
ind += 1
|
291 |
+
ind_opt3 += 1
|
292 |
+
|
293 |
+
LLM_version_user = opt1
|
294 |
+
use_LeafMachine2_collage_images = opt2
|
295 |
+
prompt_version = opt3
|
296 |
+
|
297 |
+
filename = f"{ind}__OPT1-{ind_opt1}__OPT2-{ind_opt2}__OPT3-{ind_opt3}.yaml"
|
298 |
+
run_name = f"{ind}__OPT1-{ind_opt1}__OPT2-{ind_opt2}__OPT3-{ind_opt3}"
|
299 |
+
|
300 |
+
dir_output = os.path.join(dir_home,'demo','demo_output','run_name')
|
301 |
+
validate_dir(dir_output)
|
302 |
+
|
303 |
+
|
304 |
+
if llm_version == 'gpt':
|
305 |
+
if prompt_version in ['Version 1']:
|
306 |
+
config_data, dir_home = assemble_config(dir_home, run_name, dir_images_local,dir_output,
|
307 |
+
prefix_removal,suffix_removal,catalog_numerical_only,LLM_version_user,batch_size,
|
308 |
+
path_domain_knowledge,embeddings_database_name,use_LeafMachine2_collage_images,
|
309 |
+
prompt_version, use_domain_knowledge=True)
|
310 |
+
else:
|
311 |
+
config_data, dir_home = assemble_config(dir_home, run_name, dir_images_local,dir_output,
|
312 |
+
prefix_removal,suffix_removal,catalog_numerical_only,LLM_version_user,batch_size,
|
313 |
+
path_domain_knowledge,embeddings_database_name,use_LeafMachine2_collage_images,
|
314 |
+
prompt_version)
|
315 |
+
elif llm_version == 'palm':
|
316 |
+
if prompt_version in ['Version 1 PaLM 2']:
|
317 |
+
config_data, dir_home = assemble_config(dir_home, run_name, dir_images_local,dir_output,
|
318 |
+
prefix_removal,suffix_removal,catalog_numerical_only,LLM_version_user,batch_size,
|
319 |
+
path_domain_knowledge,embeddings_database_name,use_LeafMachine2_collage_images,
|
320 |
+
prompt_version, use_domain_knowledge=True)
|
321 |
+
else:
|
322 |
+
config_data, dir_home = assemble_config(dir_home, run_name, dir_images_local,dir_output,
|
323 |
+
prefix_removal,suffix_removal,catalog_numerical_only,LLM_version_user,batch_size,
|
324 |
+
path_domain_knowledge,embeddings_database_name,use_LeafMachine2_collage_images,
|
325 |
+
prompt_version)
|
326 |
+
|
327 |
+
|
328 |
+
write_config_file(config_data, os.path.join(dir_home,'demo','demo_configs'),filename=filename)
|
329 |
+
|
330 |
+
test_results[run_name] = False
|
331 |
+
ind_opt3 = -1
|
332 |
+
ind_opt2 = -1
|
333 |
+
ind_opt1 = -1
|
334 |
+
|
335 |
+
return dir_home, path_to_configs, test_results
|
336 |
+
|
337 |
+
class TestOptionsGPT:
|
338 |
+
OPT1 = ["GPT 4", "GPT 3.5", "Azure GPT 4", "Azure GPT 3.5"]
|
339 |
+
OPT2 = [False, True]
|
340 |
+
OPT3 = ["Version 1", "Version 1 No Domain Knowledge", "Version 2"]
|
341 |
+
|
342 |
+
@classmethod
|
343 |
+
def get_options(cls):
|
344 |
+
return cls.OPT1, cls.OPT2, cls.OPT3
|
345 |
+
@classmethod
|
346 |
+
def get_length(cls):
|
347 |
+
return 24
|
348 |
+
|
349 |
+
class TestOptionsPalm:
|
350 |
+
OPT1 = ["PaLM 2"]
|
351 |
+
OPT2 = [False, True]
|
352 |
+
OPT3 = ["Version 1 PaLM 2", "Version 1 PaLM 2 No Domain Knowledge", "Version 2 PaLM 2"]
|
353 |
+
|
354 |
+
@classmethod
|
355 |
+
def get_options(cls):
|
356 |
+
return cls.OPT1, cls.OPT2, cls.OPT3
|
357 |
+
@classmethod
|
358 |
+
def get_length(cls):
|
359 |
+
return 6
|
360 |
+
|
361 |
+
class TestOptionsAPI_openai:
|
362 |
+
OPT1 = ["GPT 3.5"]
|
363 |
+
OPT2 = [False]
|
364 |
+
OPT3 = ["Version 2"]
|
365 |
+
|
366 |
+
@classmethod
|
367 |
+
def get_options(cls):
|
368 |
+
return cls.OPT1, cls.OPT2, cls.OPT3
|
369 |
+
@classmethod
|
370 |
+
def get_length(cls):
|
371 |
+
return 24
|
372 |
+
|
373 |
+
class TestOptionsAPI_azure_openai:
|
374 |
+
OPT1 = ["Azure GPT 3.5"]
|
375 |
+
OPT2 = [False]
|
376 |
+
OPT3 = ["Version 2"]
|
377 |
+
|
378 |
+
@classmethod
|
379 |
+
def get_options(cls):
|
380 |
+
return cls.OPT1, cls.OPT2, cls.OPT3
|
381 |
+
@classmethod
|
382 |
+
def get_length(cls):
|
383 |
+
return 24
|
384 |
+
|
385 |
+
class TestOptionsAPI_palm:
|
386 |
+
OPT1 = ["PaLM 2"]
|
387 |
+
OPT2 = [False]
|
388 |
+
OPT3 = ["Version 2 PaLM 2"]
|
389 |
+
|
390 |
+
@classmethod
|
391 |
+
def get_options(cls):
|
392 |
+
return cls.OPT1, cls.OPT2, cls.OPT3
|
393 |
+
@classmethod
|
394 |
+
def get_length(cls):
|
395 |
+
return 6
|
396 |
+
|
397 |
+
def run_demo_tests_GPT(progress_report):
|
398 |
+
dir_home, path_to_configs, test_results = build_demo_tests('gpt')
|
399 |
+
progress_report.set_n_overall(len(test_results.items()))
|
400 |
+
|
401 |
+
JSON_results = {}
|
402 |
+
|
403 |
+
for ind, (cfg, result) in enumerate(test_results.items()):
|
404 |
+
OPT1, OPT2, OPT3 = TestOptionsGPT.get_options()
|
405 |
+
|
406 |
+
test_ind, ind_opt1, ind_opt2, ind_opt3 = cfg.split('__')
|
407 |
+
opt1_readable = OPT1[int(ind_opt1.split('-')[1])]
|
408 |
+
|
409 |
+
if opt1_readable in ["Azure GPT 4", "Azure GPT 3.5"]:
|
410 |
+
api_version = 'gpt-azure'
|
411 |
+
elif opt1_readable in ["GPT 4", "GPT 3.5"]:
|
412 |
+
api_version = 'gpt'
|
413 |
+
else:
|
414 |
+
raise
|
415 |
+
|
416 |
+
opt2_readable = "Use LeafMachine2 for Collage Images" if OPT2[int(ind_opt2.split('-')[1])] else "Don't use LeafMachine2 for Collage Images"
|
417 |
+
opt3_readable = f"Prompt {OPT3[int(ind_opt3.split('-')[1])]}"
|
418 |
+
# Construct the human-readable test name
|
419 |
+
human_readable_name = f"{opt1_readable}, {opt2_readable}, {opt3_readable}"
|
420 |
+
get_n_overall = progress_report.get_n_overall()
|
421 |
+
progress_report.update_overall(f"Test {int(test_ind)+1} of {get_n_overall} --- Validating {human_readable_name}")
|
422 |
+
print_main_fail(f"Starting validation test: {human_readable_name}")
|
423 |
+
cfg_file_path = os.path.join(path_to_configs,'.'.join([cfg,'yaml']))
|
424 |
+
|
425 |
+
if check_API_key(dir_home, api_version) and check_API_key(dir_home, 'google-vision-ocr'):
|
426 |
+
try:
|
427 |
+
last_JSON_response, total_cost = voucher_vision(cfg_file_path, dir_home, cfg_test=None, progress_report=progress_report, test_ind=int(test_ind))
|
428 |
+
test_results[cfg] = True
|
429 |
+
JSON_results[ind] = last_JSON_response
|
430 |
+
except Exception as e:
|
431 |
+
JSON_results[ind] = None
|
432 |
+
test_results[cfg] = False
|
433 |
+
print(f"An exception occurred: {e}")
|
434 |
+
traceback.print_exc() # This will print the full traceback
|
435 |
+
else:
|
436 |
+
fail_response = ''
|
437 |
+
if not check_API_key(dir_home, 'google-vision-ocr'):
|
438 |
+
fail_response += "No API key found for Google Vision OCR"
|
439 |
+
if not check_API_key(dir_home, api_version):
|
440 |
+
fail_response += f" + No API key found for {api_version}"
|
441 |
+
test_results[cfg] = False
|
442 |
+
JSON_results[ind] = fail_response
|
443 |
+
print(f"No API key found for {fail_response}")
|
444 |
+
|
445 |
+
return test_results, JSON_results
|
446 |
+
|
447 |
+
def run_demo_tests_Palm(progress_report):
|
448 |
+
api_version = 'palm'
|
449 |
+
|
450 |
+
dir_home, path_to_configs, test_results = build_demo_tests('palm')
|
451 |
+
progress_report.set_n_overall(len(test_results.items()))
|
452 |
+
|
453 |
+
JSON_results = {}
|
454 |
+
|
455 |
+
for ind, (cfg, result) in enumerate(test_results.items()):
|
456 |
+
OPT1, OPT2, OPT3 = TestOptionsPalm.get_options()
|
457 |
+
test_ind, ind_opt1, ind_opt2, ind_opt3 = cfg.split('__')
|
458 |
+
opt1_readable = OPT1[int(ind_opt1.split('-')[1])]
|
459 |
+
opt2_readable = "Use LeafMachine2 for Collage Images" if OPT2[int(ind_opt2.split('-')[1])] else "Don't use LeafMachine2 for Collage Images"
|
460 |
+
opt3_readable = f"Prompt {OPT3[int(ind_opt3.split('-')[1])]}"
|
461 |
+
# opt3_readable = "Use Domain Knowledge" if OPT3[int(ind_opt3.split('-')[1])] else "Don't use Domain Knowledge"
|
462 |
+
# Construct the human-readable test name
|
463 |
+
human_readable_name = f"{opt1_readable}, {opt2_readable}, {opt3_readable}"
|
464 |
+
get_n_overall = progress_report.get_n_overall()
|
465 |
+
progress_report.update_overall(f"Test {int(test_ind)+1} of {get_n_overall} --- Validating {human_readable_name}")
|
466 |
+
print_main_fail(f"Starting validation test: {human_readable_name}")
|
467 |
+
cfg_file_path = os.path.join(path_to_configs,'.'.join([cfg,'yaml']))
|
468 |
+
|
469 |
+
if check_API_key(dir_home, api_version) and check_API_key(dir_home, 'google-vision-ocr') :
|
470 |
+
try:
|
471 |
+
last_JSON_response, total_cost = voucher_vision(cfg_file_path, dir_home, cfg_test=None, progress_report=progress_report, test_ind=int(test_ind))
|
472 |
+
test_results[cfg] = True
|
473 |
+
JSON_results[ind] = last_JSON_response
|
474 |
+
except Exception as e:
|
475 |
+
test_results[cfg] = False
|
476 |
+
JSON_results[ind] = None
|
477 |
+
print(f"An exception occurred: {e}")
|
478 |
+
traceback.print_exc() # This will print the full traceback
|
479 |
+
else:
|
480 |
+
fail_response = ''
|
481 |
+
if not check_API_key(dir_home, 'google-vision-ocr'):
|
482 |
+
fail_response += "No API key found for Google Vision OCR"
|
483 |
+
if not check_API_key(dir_home, api_version):
|
484 |
+
fail_response += f" + No API key found for {api_version}"
|
485 |
+
test_results[cfg] = False
|
486 |
+
JSON_results[ind] = fail_response
|
487 |
+
print(f"No API key found for {fail_response}")
|
488 |
+
|
489 |
+
return test_results, JSON_results
|
490 |
+
|
491 |
+
def run_api_tests(api):
|
492 |
+
try:
|
493 |
+
dir_home, path_to_configs, test_results = build_api_tests(api)
|
494 |
+
|
495 |
+
JSON_results = {}
|
496 |
+
|
497 |
+
for ind, (cfg, result) in enumerate(test_results.items()):
|
498 |
+
if api == 'openai':
|
499 |
+
OPT1, OPT2, OPT3 = TestOptionsAPI_openai.get_options()
|
500 |
+
elif 'azure_openai':
|
501 |
+
OPT1, OPT2, OPT3 = TestOptionsAPI_azure_openai.get_options()
|
502 |
+
elif 'palm':
|
503 |
+
OPT1, OPT2, OPT3 = TestOptionsAPI_palm.get_options()
|
504 |
+
test_ind, ind_opt1, ind_opt2, ind_opt3 = cfg.split('__')
|
505 |
+
opt1_readable = OPT1[int(ind_opt1.split('-')[1])]
|
506 |
+
opt2_readable = "Use LeafMachine2 for Collage Images" if OPT2[int(ind_opt2.split('-')[1])] else "Don't use LeafMachine2 for Collage Images"
|
507 |
+
opt3_readable = f"Prompt {OPT3[int(ind_opt3.split('-')[1])]}"
|
508 |
+
# opt3_readable = "Use Domain Knowledge" if OPT3[int(ind_opt3.split('-')[1])] else "Don't use Domain Knowledge"
|
509 |
+
# Construct the human-readable test name
|
510 |
+
human_readable_name = f"{opt1_readable}, {opt2_readable}, {opt3_readable}"
|
511 |
+
print_main_fail(f"Starting validation test: {human_readable_name}")
|
512 |
+
cfg_file_path = os.path.join(path_to_configs,'.'.join([cfg,'yaml']))
|
513 |
+
|
514 |
+
if check_API_key(dir_home, api) and check_API_key(dir_home, 'google-vision-ocr') :
|
515 |
+
try:
|
516 |
+
last_JSON_response, total_cost = voucher_vision(cfg_file_path, dir_home, None, cfg_test=None, progress_report=None, test_ind=int(test_ind))
|
517 |
+
test_results[cfg] = True
|
518 |
+
JSON_results[ind] = last_JSON_response
|
519 |
+
return True
|
520 |
+
|
521 |
+
except Exception as e:
|
522 |
+
print(e)
|
523 |
+
return False
|
524 |
+
else:
|
525 |
+
return False
|
526 |
+
except Exception as e:
|
527 |
+
print(e)
|
528 |
+
return False
|
529 |
+
|
530 |
+
def has_API_key(val):
|
531 |
+
if val != '':
|
532 |
+
return True
|
533 |
+
else:
|
534 |
+
return False
|
535 |
+
|
536 |
+
def check_if_usable():
|
537 |
+
dir_home = os.path.dirname(os.path.dirname(__file__))
|
538 |
+
path_cfg_private = os.path.join(dir_home, 'PRIVATE_DATA.yaml')
|
539 |
+
cfg_private = get_cfg_from_full_path(path_cfg_private)
|
540 |
+
|
541 |
+
has_key_openai = has_API_key(cfg_private['openai']['OPENAI_API_KEY'])
|
542 |
+
|
543 |
+
has_key_azure_openai = has_API_key(cfg_private['openai_azure']['api_version'])
|
544 |
+
|
545 |
+
has_key_palm2 = has_API_key(cfg_private['google_palm']['google_palm_api'])
|
546 |
+
|
547 |
+
has_key_google_OCR = has_API_key(cfg_private['google_cloud']['path_json_file'])
|
548 |
+
|
549 |
+
if has_key_google_OCR and (has_key_azure_openai or has_key_openai or has_key_palm2):
|
550 |
+
return True
|
551 |
+
else:
|
552 |
+
return False
|
553 |
+
|
554 |
+
def check_API_key(dir_home, api_version):
|
555 |
+
dir_home = os.path.dirname(os.path.dirname(__file__))
|
556 |
+
path_cfg_private = os.path.join(dir_home, 'PRIVATE_DATA.yaml')
|
557 |
+
cfg_private = get_cfg_from_full_path(path_cfg_private)
|
558 |
+
|
559 |
+
has_key_openai = has_API_key(cfg_private['openai']['OPENAI_API_KEY'])
|
560 |
+
|
561 |
+
has_key_azure_openai = has_API_key(cfg_private['openai_azure']['api_version'])
|
562 |
+
|
563 |
+
has_key_palm2 = has_API_key(cfg_private['google_palm']['google_palm_api'])
|
564 |
+
|
565 |
+
has_key_google_OCR = has_API_key(cfg_private['google_cloud']['path_json_file'])
|
566 |
+
|
567 |
+
if api_version == 'palm' and has_key_palm2:
|
568 |
+
return True
|
569 |
+
elif api_version in ['gpt','openai'] and has_key_openai:
|
570 |
+
return True
|
571 |
+
elif api_version in ['gpt-azure', 'azure_openai'] and has_key_azure_openai:
|
572 |
+
return True
|
573 |
+
elif api_version == 'google-vision-ocr' and has_key_google_OCR:
|
574 |
+
return True
|
575 |
+
else:
|
576 |
+
return False
|
vouchervision/component_detector/LICENSE
ADDED
@@ -0,0 +1,674 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
GNU GENERAL PUBLIC LICENSE
|
2 |
+
Version 3, 29 June 2007
|
3 |
+
|
4 |
+
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
5 |
+
Everyone is permitted to copy and distribute verbatim copies
|
6 |
+
of this license document, but changing it is not allowed.
|
7 |
+
|
8 |
+
Preamble
|
9 |
+
|
10 |
+
The GNU General Public License is a free, copyleft license for
|
11 |
+
software and other kinds of works.
|
12 |
+
|
13 |
+
The licenses for most software and other practical works are designed
|
14 |
+
to take away your freedom to share and change the works. By contrast,
|
15 |
+
the GNU General Public License is intended to guarantee your freedom to
|
16 |
+
share and change all versions of a program--to make sure it remains free
|
17 |
+
software for all its users. We, the Free Software Foundation, use the
|
18 |
+
GNU General Public License for most of our software; it applies also to
|
19 |
+
any other work released this way by its authors. You can apply it to
|
20 |
+
your programs, too.
|
21 |
+
|
22 |
+
When we speak of free software, we are referring to freedom, not
|
23 |
+
price. Our General Public Licenses are designed to make sure that you
|
24 |
+
have the freedom to distribute copies of free software (and charge for
|
25 |
+
them if you wish), that you receive source code or can get it if you
|
26 |
+
want it, that you can change the software or use pieces of it in new
|
27 |
+
free programs, and that you know you can do these things.
|
28 |
+
|
29 |
+
To protect your rights, we need to prevent others from denying you
|
30 |
+
these rights or asking you to surrender the rights. Therefore, you have
|
31 |
+
certain responsibilities if you distribute copies of the software, or if
|
32 |
+
you modify it: responsibilities to respect the freedom of others.
|
33 |
+
|
34 |
+
For example, if you distribute copies of such a program, whether
|
35 |
+
gratis or for a fee, you must pass on to the recipients the same
|
36 |
+
freedoms that you received. You must make sure that they, too, receive
|
37 |
+
or can get the source code. And you must show them these terms so they
|
38 |
+
know their rights.
|
39 |
+
|
40 |
+
Developers that use the GNU GPL protect your rights with two steps:
|
41 |
+
(1) assert copyright on the software, and (2) offer you this License
|
42 |
+
giving you legal permission to copy, distribute and/or modify it.
|
43 |
+
|
44 |
+
For the developers' and authors' protection, the GPL clearly explains
|
45 |
+
that there is no warranty for this free software. For both users' and
|
46 |
+
authors' sake, the GPL requires that modified versions be marked as
|
47 |
+
changed, so that their problems will not be attributed erroneously to
|
48 |
+
authors of previous versions.
|
49 |
+
|
50 |
+
Some devices are designed to deny users access to install or run
|
51 |
+
modified versions of the software inside them, although the manufacturer
|
52 |
+
can do so. This is fundamentally incompatible with the aim of
|
53 |
+
protecting users' freedom to change the software. The systematic
|
54 |
+
pattern of such abuse occurs in the area of products for individuals to
|
55 |
+
use, which is precisely where it is most unacceptable. Therefore, we
|
56 |
+
have designed this version of the GPL to prohibit the practice for those
|
57 |
+
products. If such problems arise substantially in other domains, we
|
58 |
+
stand ready to extend this provision to those domains in future versions
|
59 |
+
of the GPL, as needed to protect the freedom of users.
|
60 |
+
|
61 |
+
Finally, every program is threatened constantly by software patents.
|
62 |
+
States should not allow patents to restrict development and use of
|
63 |
+
software on general-purpose computers, but in those that do, we wish to
|
64 |
+
avoid the special danger that patents applied to a free program could
|
65 |
+
make it effectively proprietary. To prevent this, the GPL assures that
|
66 |
+
patents cannot be used to render the program non-free.
|
67 |
+
|
68 |
+
The precise terms and conditions for copying, distribution and
|
69 |
+
modification follow.
|
70 |
+
|
71 |
+
TERMS AND CONDITIONS
|
72 |
+
|
73 |
+
0. Definitions.
|
74 |
+
|
75 |
+
"This License" refers to version 3 of the GNU General Public License.
|
76 |
+
|
77 |
+
"Copyright" also means copyright-like laws that apply to other kinds of
|
78 |
+
works, such as semiconductor masks.
|
79 |
+
|
80 |
+
"The Program" refers to any copyrightable work licensed under this
|
81 |
+
License. Each licensee is addressed as "you". "Licensees" and
|
82 |
+
"recipients" may be individuals or organizations.
|
83 |
+
|
84 |
+
To "modify" a work means to copy from or adapt all or part of the work
|
85 |
+
in a fashion requiring copyright permission, other than the making of an
|
86 |
+
exact copy. The resulting work is called a "modified version" of the
|
87 |
+
earlier work or a work "based on" the earlier work.
|
88 |
+
|
89 |
+
A "covered work" means either the unmodified Program or a work based
|
90 |
+
on the Program.
|
91 |
+
|
92 |
+
To "propagate" a work means to do anything with it that, without
|
93 |
+
permission, would make you directly or secondarily liable for
|
94 |
+
infringement under applicable copyright law, except executing it on a
|
95 |
+
computer or modifying a private copy. Propagation includes copying,
|
96 |
+
distribution (with or without modification), making available to the
|
97 |
+
public, and in some countries other activities as well.
|
98 |
+
|
99 |
+
To "convey" a work means any kind of propagation that enables other
|
100 |
+
parties to make or receive copies. Mere interaction with a user through
|
101 |
+
a computer network, with no transfer of a copy, is not conveying.
|
102 |
+
|
103 |
+
An interactive user interface displays "Appropriate Legal Notices"
|
104 |
+
to the extent that it includes a convenient and prominently visible
|
105 |
+
feature that (1) displays an appropriate copyright notice, and (2)
|
106 |
+
tells the user that there is no warranty for the work (except to the
|
107 |
+
extent that warranties are provided), that licensees may convey the
|
108 |
+
work under this License, and how to view a copy of this License. If
|
109 |
+
the interface presents a list of user commands or options, such as a
|
110 |
+
menu, a prominent item in the list meets this criterion.
|
111 |
+
|
112 |
+
1. Source Code.
|
113 |
+
|
114 |
+
The "source code" for a work means the preferred form of the work
|
115 |
+
for making modifications to it. "Object code" means any non-source
|
116 |
+
form of a work.
|
117 |
+
|
118 |
+
A "Standard Interface" means an interface that either is an official
|
119 |
+
standard defined by a recognized standards body, or, in the case of
|
120 |
+
interfaces specified for a particular programming language, one that
|
121 |
+
is widely used among developers working in that language.
|
122 |
+
|
123 |
+
The "System Libraries" of an executable work include anything, other
|
124 |
+
than the work as a whole, that (a) is included in the normal form of
|
125 |
+
packaging a Major Component, but which is not part of that Major
|
126 |
+
Component, and (b) serves only to enable use of the work with that
|
127 |
+
Major Component, or to implement a Standard Interface for which an
|
128 |
+
implementation is available to the public in source code form. A
|
129 |
+
"Major Component", in this context, means a major essential component
|
130 |
+
(kernel, window system, and so on) of the specific operating system
|
131 |
+
(if any) on which the executable work runs, or a compiler used to
|
132 |
+
produce the work, or an object code interpreter used to run it.
|
133 |
+
|
134 |
+
The "Corresponding Source" for a work in object code form means all
|
135 |
+
the source code needed to generate, install, and (for an executable
|
136 |
+
work) run the object code and to modify the work, including scripts to
|
137 |
+
control those activities. However, it does not include the work's
|
138 |
+
System Libraries, or general-purpose tools or generally available free
|
139 |
+
programs which are used unmodified in performing those activities but
|
140 |
+
which are not part of the work. For example, Corresponding Source
|
141 |
+
includes interface definition files associated with source files for
|
142 |
+
the work, and the source code for shared libraries and dynamically
|
143 |
+
linked subprograms that the work is specifically designed to require,
|
144 |
+
such as by intimate data communication or control flow between those
|
145 |
+
subprograms and other parts of the work.
|
146 |
+
|
147 |
+
The Corresponding Source need not include anything that users
|
148 |
+
can regenerate automatically from other parts of the Corresponding
|
149 |
+
Source.
|
150 |
+
|
151 |
+
The Corresponding Source for a work in source code form is that
|
152 |
+
same work.
|
153 |
+
|
154 |
+
2. Basic Permissions.
|
155 |
+
|
156 |
+
All rights granted under this License are granted for the term of
|
157 |
+
copyright on the Program, and are irrevocable provided the stated
|
158 |
+
conditions are met. This License explicitly affirms your unlimited
|
159 |
+
permission to run the unmodified Program. The output from running a
|
160 |
+
covered work is covered by this License only if the output, given its
|
161 |
+
content, constitutes a covered work. This License acknowledges your
|
162 |
+
rights of fair use or other equivalent, as provided by copyright law.
|
163 |
+
|
164 |
+
You may make, run and propagate covered works that you do not
|
165 |
+
convey, without conditions so long as your license otherwise remains
|
166 |
+
in force. You may convey covered works to others for the sole purpose
|
167 |
+
of having them make modifications exclusively for you, or provide you
|
168 |
+
with facilities for running those works, provided that you comply with
|
169 |
+
the terms of this License in conveying all material for which you do
|
170 |
+
not control copyright. Those thus making or running the covered works
|
171 |
+
for you must do so exclusively on your behalf, under your direction
|
172 |
+
and control, on terms that prohibit them from making any copies of
|
173 |
+
your copyrighted material outside their relationship with you.
|
174 |
+
|
175 |
+
Conveying under any other circumstances is permitted solely under
|
176 |
+
the conditions stated below. Sublicensing is not allowed; section 10
|
177 |
+
makes it unnecessary.
|
178 |
+
|
179 |
+
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
180 |
+
|
181 |
+
No covered work shall be deemed part of an effective technological
|
182 |
+
measure under any applicable law fulfilling obligations under article
|
183 |
+
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
184 |
+
similar laws prohibiting or restricting circumvention of such
|
185 |
+
measures.
|
186 |
+
|
187 |
+
When you convey a covered work, you waive any legal power to forbid
|
188 |
+
circumvention of technological measures to the extent such circumvention
|
189 |
+
is effected by exercising rights under this License with respect to
|
190 |
+
the covered work, and you disclaim any intention to limit operation or
|
191 |
+
modification of the work as a means of enforcing, against the work's
|
192 |
+
users, your or third parties' legal rights to forbid circumvention of
|
193 |
+
technological measures.
|
194 |
+
|
195 |
+
4. Conveying Verbatim Copies.
|
196 |
+
|
197 |
+
You may convey verbatim copies of the Program's source code as you
|
198 |
+
receive it, in any medium, provided that you conspicuously and
|
199 |
+
appropriately publish on each copy an appropriate copyright notice;
|
200 |
+
keep intact all notices stating that this License and any
|
201 |
+
non-permissive terms added in accord with section 7 apply to the code;
|
202 |
+
keep intact all notices of the absence of any warranty; and give all
|
203 |
+
recipients a copy of this License along with the Program.
|
204 |
+
|
205 |
+
You may charge any price or no price for each copy that you convey,
|
206 |
+
and you may offer support or warranty protection for a fee.
|
207 |
+
|
208 |
+
5. Conveying Modified Source Versions.
|
209 |
+
|
210 |
+
You may convey a work based on the Program, or the modifications to
|
211 |
+
produce it from the Program, in the form of source code under the
|
212 |
+
terms of section 4, provided that you also meet all of these conditions:
|
213 |
+
|
214 |
+
a) The work must carry prominent notices stating that you modified
|
215 |
+
it, and giving a relevant date.
|
216 |
+
|
217 |
+
b) The work must carry prominent notices stating that it is
|
218 |
+
released under this License and any conditions added under section
|
219 |
+
7. This requirement modifies the requirement in section 4 to
|
220 |
+
"keep intact all notices".
|
221 |
+
|
222 |
+
c) You must license the entire work, as a whole, under this
|
223 |
+
License to anyone who comes into possession of a copy. This
|
224 |
+
License will therefore apply, along with any applicable section 7
|
225 |
+
additional terms, to the whole of the work, and all its parts,
|
226 |
+
regardless of how they are packaged. This License gives no
|
227 |
+
permission to license the work in any other way, but it does not
|
228 |
+
invalidate such permission if you have separately received it.
|
229 |
+
|
230 |
+
d) If the work has interactive user interfaces, each must display
|
231 |
+
Appropriate Legal Notices; however, if the Program has interactive
|
232 |
+
interfaces that do not display Appropriate Legal Notices, your
|
233 |
+
work need not make them do so.
|
234 |
+
|
235 |
+
A compilation of a covered work with other separate and independent
|
236 |
+
works, which are not by their nature extensions of the covered work,
|
237 |
+
and which are not combined with it such as to form a larger program,
|
238 |
+
in or on a volume of a storage or distribution medium, is called an
|
239 |
+
"aggregate" if the compilation and its resulting copyright are not
|
240 |
+
used to limit the access or legal rights of the compilation's users
|
241 |
+
beyond what the individual works permit. Inclusion of a covered work
|
242 |
+
in an aggregate does not cause this License to apply to the other
|
243 |
+
parts of the aggregate.
|
244 |
+
|
245 |
+
6. Conveying Non-Source Forms.
|
246 |
+
|
247 |
+
You may convey a covered work in object code form under the terms
|
248 |
+
of sections 4 and 5, provided that you also convey the
|
249 |
+
machine-readable Corresponding Source under the terms of this License,
|
250 |
+
in one of these ways:
|
251 |
+
|
252 |
+
a) Convey the object code in, or embodied in, a physical product
|
253 |
+
(including a physical distribution medium), accompanied by the
|
254 |
+
Corresponding Source fixed on a durable physical medium
|
255 |
+
customarily used for software interchange.
|
256 |
+
|
257 |
+
b) Convey the object code in, or embodied in, a physical product
|
258 |
+
(including a physical distribution medium), accompanied by a
|
259 |
+
written offer, valid for at least three years and valid for as
|
260 |
+
long as you offer spare parts or customer support for that product
|
261 |
+
model, to give anyone who possesses the object code either (1) a
|
262 |
+
copy of the Corresponding Source for all the software in the
|
263 |
+
product that is covered by this License, on a durable physical
|
264 |
+
medium customarily used for software interchange, for a price no
|
265 |
+
more than your reasonable cost of physically performing this
|
266 |
+
conveying of source, or (2) access to copy the
|
267 |
+
Corresponding Source from a network server at no charge.
|
268 |
+
|
269 |
+
c) Convey individual copies of the object code with a copy of the
|
270 |
+
written offer to provide the Corresponding Source. This
|
271 |
+
alternative is allowed only occasionally and noncommercially, and
|
272 |
+
only if you received the object code with such an offer, in accord
|
273 |
+
with subsection 6b.
|
274 |
+
|
275 |
+
d) Convey the object code by offering access from a designated
|
276 |
+
place (gratis or for a charge), and offer equivalent access to the
|
277 |
+
Corresponding Source in the same way through the same place at no
|
278 |
+
further charge. You need not require recipients to copy the
|
279 |
+
Corresponding Source along with the object code. If the place to
|
280 |
+
copy the object code is a network server, the Corresponding Source
|
281 |
+
may be on a different server (operated by you or a third party)
|
282 |
+
that supports equivalent copying facilities, provided you maintain
|
283 |
+
clear directions next to the object code saying where to find the
|
284 |
+
Corresponding Source. Regardless of what server hosts the
|
285 |
+
Corresponding Source, you remain obligated to ensure that it is
|
286 |
+
available for as long as needed to satisfy these requirements.
|
287 |
+
|
288 |
+
e) Convey the object code using peer-to-peer transmission, provided
|
289 |
+
you inform other peers where the object code and Corresponding
|
290 |
+
Source of the work are being offered to the general public at no
|
291 |
+
charge under subsection 6d.
|
292 |
+
|
293 |
+
A separable portion of the object code, whose source code is excluded
|
294 |
+
from the Corresponding Source as a System Library, need not be
|
295 |
+
included in conveying the object code work.
|
296 |
+
|
297 |
+
A "User Product" is either (1) a "consumer product", which means any
|
298 |
+
tangible personal property which is normally used for personal, family,
|
299 |
+
or household purposes, or (2) anything designed or sold for incorporation
|
300 |
+
into a dwelling. In determining whether a product is a consumer product,
|
301 |
+
doubtful cases shall be resolved in favor of coverage. For a particular
|
302 |
+
product received by a particular user, "normally used" refers to a
|
303 |
+
typical or common use of that class of product, regardless of the status
|
304 |
+
of the particular user or of the way in which the particular user
|
305 |
+
actually uses, or expects or is expected to use, the product. A product
|
306 |
+
is a consumer product regardless of whether the product has substantial
|
307 |
+
commercial, industrial or non-consumer uses, unless such uses represent
|
308 |
+
the only significant mode of use of the product.
|
309 |
+
|
310 |
+
"Installation Information" for a User Product means any methods,
|
311 |
+
procedures, authorization keys, or other information required to install
|
312 |
+
and execute modified versions of a covered work in that User Product from
|
313 |
+
a modified version of its Corresponding Source. The information must
|
314 |
+
suffice to ensure that the continued functioning of the modified object
|
315 |
+
code is in no case prevented or interfered with solely because
|
316 |
+
modification has been made.
|
317 |
+
|
318 |
+
If you convey an object code work under this section in, or with, or
|
319 |
+
specifically for use in, a User Product, and the conveying occurs as
|
320 |
+
part of a transaction in which the right of possession and use of the
|
321 |
+
User Product is transferred to the recipient in perpetuity or for a
|
322 |
+
fixed term (regardless of how the transaction is characterized), the
|
323 |
+
Corresponding Source conveyed under this section must be accompanied
|
324 |
+
by the Installation Information. But this requirement does not apply
|
325 |
+
if neither you nor any third party retains the ability to install
|
326 |
+
modified object code on the User Product (for example, the work has
|
327 |
+
been installed in ROM).
|
328 |
+
|
329 |
+
The requirement to provide Installation Information does not include a
|
330 |
+
requirement to continue to provide support service, warranty, or updates
|
331 |
+
for a work that has been modified or installed by the recipient, or for
|
332 |
+
the User Product in which it has been modified or installed. Access to a
|
333 |
+
network may be denied when the modification itself materially and
|
334 |
+
adversely affects the operation of the network or violates the rules and
|
335 |
+
protocols for communication across the network.
|
336 |
+
|
337 |
+
Corresponding Source conveyed, and Installation Information provided,
|
338 |
+
in accord with this section must be in a format that is publicly
|
339 |
+
documented (and with an implementation available to the public in
|
340 |
+
source code form), and must require no special password or key for
|
341 |
+
unpacking, reading or copying.
|
342 |
+
|
343 |
+
7. Additional Terms.
|
344 |
+
|
345 |
+
"Additional permissions" are terms that supplement the terms of this
|
346 |
+
License by making exceptions from one or more of its conditions.
|
347 |
+
Additional permissions that are applicable to the entire Program shall
|
348 |
+
be treated as though they were included in this License, to the extent
|
349 |
+
that they are valid under applicable law. If additional permissions
|
350 |
+
apply only to part of the Program, that part may be used separately
|
351 |
+
under those permissions, but the entire Program remains governed by
|
352 |
+
this License without regard to the additional permissions.
|
353 |
+
|
354 |
+
When you convey a copy of a covered work, you may at your option
|
355 |
+
remove any additional permissions from that copy, or from any part of
|
356 |
+
it. (Additional permissions may be written to require their own
|
357 |
+
removal in certain cases when you modify the work.) You may place
|
358 |
+
additional permissions on material, added by you to a covered work,
|
359 |
+
for which you have or can give appropriate copyright permission.
|
360 |
+
|
361 |
+
Notwithstanding any other provision of this License, for material you
|
362 |
+
add to a covered work, you may (if authorized by the copyright holders of
|
363 |
+
that material) supplement the terms of this License with terms:
|
364 |
+
|
365 |
+
a) Disclaiming warranty or limiting liability differently from the
|
366 |
+
terms of sections 15 and 16 of this License; or
|
367 |
+
|
368 |
+
b) Requiring preservation of specified reasonable legal notices or
|
369 |
+
author attributions in that material or in the Appropriate Legal
|
370 |
+
Notices displayed by works containing it; or
|
371 |
+
|
372 |
+
c) Prohibiting misrepresentation of the origin of that material, or
|
373 |
+
requiring that modified versions of such material be marked in
|
374 |
+
reasonable ways as different from the original version; or
|
375 |
+
|
376 |
+
d) Limiting the use for publicity purposes of names of licensors or
|
377 |
+
authors of the material; or
|
378 |
+
|
379 |
+
e) Declining to grant rights under trademark law for use of some
|
380 |
+
trade names, trademarks, or service marks; or
|
381 |
+
|
382 |
+
f) Requiring indemnification of licensors and authors of that
|
383 |
+
material by anyone who conveys the material (or modified versions of
|
384 |
+
it) with contractual assumptions of liability to the recipient, for
|
385 |
+
any liability that these contractual assumptions directly impose on
|
386 |
+
those licensors and authors.
|
387 |
+
|
388 |
+
All other non-permissive additional terms are considered "further
|
389 |
+
restrictions" within the meaning of section 10. If the Program as you
|
390 |
+
received it, or any part of it, contains a notice stating that it is
|
391 |
+
governed by this License along with a term that is a further
|
392 |
+
restriction, you may remove that term. If a license document contains
|
393 |
+
a further restriction but permits relicensing or conveying under this
|
394 |
+
License, you may add to a covered work material governed by the terms
|
395 |
+
of that license document, provided that the further restriction does
|
396 |
+
not survive such relicensing or conveying.
|
397 |
+
|
398 |
+
If you add terms to a covered work in accord with this section, you
|
399 |
+
must place, in the relevant source files, a statement of the
|
400 |
+
additional terms that apply to those files, or a notice indicating
|
401 |
+
where to find the applicable terms.
|
402 |
+
|
403 |
+
Additional terms, permissive or non-permissive, may be stated in the
|
404 |
+
form of a separately written license, or stated as exceptions;
|
405 |
+
the above requirements apply either way.
|
406 |
+
|
407 |
+
8. Termination.
|
408 |
+
|
409 |
+
You may not propagate or modify a covered work except as expressly
|
410 |
+
provided under this License. Any attempt otherwise to propagate or
|
411 |
+
modify it is void, and will automatically terminate your rights under
|
412 |
+
this License (including any patent licenses granted under the third
|
413 |
+
paragraph of section 11).
|
414 |
+
|
415 |
+
However, if you cease all violation of this License, then your
|
416 |
+
license from a particular copyright holder is reinstated (a)
|
417 |
+
provisionally, unless and until the copyright holder explicitly and
|
418 |
+
finally terminates your license, and (b) permanently, if the copyright
|
419 |
+
holder fails to notify you of the violation by some reasonable means
|
420 |
+
prior to 60 days after the cessation.
|
421 |
+
|
422 |
+
Moreover, your license from a particular copyright holder is
|
423 |
+
reinstated permanently if the copyright holder notifies you of the
|
424 |
+
violation by some reasonable means, this is the first time you have
|
425 |
+
received notice of violation of this License (for any work) from that
|
426 |
+
copyright holder, and you cure the violation prior to 30 days after
|
427 |
+
your receipt of the notice.
|
428 |
+
|
429 |
+
Termination of your rights under this section does not terminate the
|
430 |
+
licenses of parties who have received copies or rights from you under
|
431 |
+
this License. If your rights have been terminated and not permanently
|
432 |
+
reinstated, you do not qualify to receive new licenses for the same
|
433 |
+
material under section 10.
|
434 |
+
|
435 |
+
9. Acceptance Not Required for Having Copies.
|
436 |
+
|
437 |
+
You are not required to accept this License in order to receive or
|
438 |
+
run a copy of the Program. Ancillary propagation of a covered work
|
439 |
+
occurring solely as a consequence of using peer-to-peer transmission
|
440 |
+
to receive a copy likewise does not require acceptance. However,
|
441 |
+
nothing other than this License grants you permission to propagate or
|
442 |
+
modify any covered work. These actions infringe copyright if you do
|
443 |
+
not accept this License. Therefore, by modifying or propagating a
|
444 |
+
covered work, you indicate your acceptance of this License to do so.
|
445 |
+
|
446 |
+
10. Automatic Licensing of Downstream Recipients.
|
447 |
+
|
448 |
+
Each time you convey a covered work, the recipient automatically
|
449 |
+
receives a license from the original licensors, to run, modify and
|
450 |
+
propagate that work, subject to this License. You are not responsible
|
451 |
+
for enforcing compliance by third parties with this License.
|
452 |
+
|
453 |
+
An "entity transaction" is a transaction transferring control of an
|
454 |
+
organization, or substantially all assets of one, or subdividing an
|
455 |
+
organization, or merging organizations. If propagation of a covered
|
456 |
+
work results from an entity transaction, each party to that
|
457 |
+
transaction who receives a copy of the work also receives whatever
|
458 |
+
licenses to the work the party's predecessor in interest had or could
|
459 |
+
give under the previous paragraph, plus a right to possession of the
|
460 |
+
Corresponding Source of the work from the predecessor in interest, if
|
461 |
+
the predecessor has it or can get it with reasonable efforts.
|
462 |
+
|
463 |
+
You may not impose any further restrictions on the exercise of the
|
464 |
+
rights granted or affirmed under this License. For example, you may
|
465 |
+
not impose a license fee, royalty, or other charge for exercise of
|
466 |
+
rights granted under this License, and you may not initiate litigation
|
467 |
+
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
468 |
+
any patent claim is infringed by making, using, selling, offering for
|
469 |
+
sale, or importing the Program or any portion of it.
|
470 |
+
|
471 |
+
11. Patents.
|
472 |
+
|
473 |
+
A "contributor" is a copyright holder who authorizes use under this
|
474 |
+
License of the Program or a work on which the Program is based. The
|
475 |
+
work thus licensed is called the contributor's "contributor version".
|
476 |
+
|
477 |
+
A contributor's "essential patent claims" are all patent claims
|
478 |
+
owned or controlled by the contributor, whether already acquired or
|
479 |
+
hereafter acquired, that would be infringed by some manner, permitted
|
480 |
+
by this License, of making, using, or selling its contributor version,
|
481 |
+
but do not include claims that would be infringed only as a
|
482 |
+
consequence of further modification of the contributor version. For
|
483 |
+
purposes of this definition, "control" includes the right to grant
|
484 |
+
patent sublicenses in a manner consistent with the requirements of
|
485 |
+
this License.
|
486 |
+
|
487 |
+
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
488 |
+
patent license under the contributor's essential patent claims, to
|
489 |
+
make, use, sell, offer for sale, import and otherwise run, modify and
|
490 |
+
propagate the contents of its contributor version.
|
491 |
+
|
492 |
+
In the following three paragraphs, a "patent license" is any express
|
493 |
+
agreement or commitment, however denominated, not to enforce a patent
|
494 |
+
(such as an express permission to practice a patent or covenant not to
|
495 |
+
sue for patent infringement). To "grant" such a patent license to a
|
496 |
+
party means to make such an agreement or commitment not to enforce a
|
497 |
+
patent against the party.
|
498 |
+
|
499 |
+
If you convey a covered work, knowingly relying on a patent license,
|
500 |
+
and the Corresponding Source of the work is not available for anyone
|
501 |
+
to copy, free of charge and under the terms of this License, through a
|
502 |
+
publicly available network server or other readily accessible means,
|
503 |
+
then you must either (1) cause the Corresponding Source to be so
|
504 |
+
available, or (2) arrange to deprive yourself of the benefit of the
|
505 |
+
patent license for this particular work, or (3) arrange, in a manner
|
506 |
+
consistent with the requirements of this License, to extend the patent
|
507 |
+
license to downstream recipients. "Knowingly relying" means you have
|
508 |
+
actual knowledge that, but for the patent license, your conveying the
|
509 |
+
covered work in a country, or your recipient's use of the covered work
|
510 |
+
in a country, would infringe one or more identifiable patents in that
|
511 |
+
country that you have reason to believe are valid.
|
512 |
+
|
513 |
+
If, pursuant to or in connection with a single transaction or
|
514 |
+
arrangement, you convey, or propagate by procuring conveyance of, a
|
515 |
+
covered work, and grant a patent license to some of the parties
|
516 |
+
receiving the covered work authorizing them to use, propagate, modify
|
517 |
+
or convey a specific copy of the covered work, then the patent license
|
518 |
+
you grant is automatically extended to all recipients of the covered
|
519 |
+
work and works based on it.
|
520 |
+
|
521 |
+
A patent license is "discriminatory" if it does not include within
|
522 |
+
the scope of its coverage, prohibits the exercise of, or is
|
523 |
+
conditioned on the non-exercise of one or more of the rights that are
|
524 |
+
specifically granted under this License. You may not convey a covered
|
525 |
+
work if you are a party to an arrangement with a third party that is
|
526 |
+
in the business of distributing software, under which you make payment
|
527 |
+
to the third party based on the extent of your activity of conveying
|
528 |
+
the work, and under which the third party grants, to any of the
|
529 |
+
parties who would receive the covered work from you, a discriminatory
|
530 |
+
patent license (a) in connection with copies of the covered work
|
531 |
+
conveyed by you (or copies made from those copies), or (b) primarily
|
532 |
+
for and in connection with specific products or compilations that
|
533 |
+
contain the covered work, unless you entered into that arrangement,
|
534 |
+
or that patent license was granted, prior to 28 March 2007.
|
535 |
+
|
536 |
+
Nothing in this License shall be construed as excluding or limiting
|
537 |
+
any implied license or other defenses to infringement that may
|
538 |
+
otherwise be available to you under applicable patent law.
|
539 |
+
|
540 |
+
12. No Surrender of Others' Freedom.
|
541 |
+
|
542 |
+
If conditions are imposed on you (whether by court order, agreement or
|
543 |
+
otherwise) that contradict the conditions of this License, they do not
|
544 |
+
excuse you from the conditions of this License. If you cannot convey a
|
545 |
+
covered work so as to satisfy simultaneously your obligations under this
|
546 |
+
License and any other pertinent obligations, then as a consequence you may
|
547 |
+
not convey it at all. For example, if you agree to terms that obligate you
|
548 |
+
to collect a royalty for further conveying from those to whom you convey
|
549 |
+
the Program, the only way you could satisfy both those terms and this
|
550 |
+
License would be to refrain entirely from conveying the Program.
|
551 |
+
|
552 |
+
13. Use with the GNU Affero General Public License.
|
553 |
+
|
554 |
+
Notwithstanding any other provision of this License, you have
|
555 |
+
permission to link or combine any covered work with a work licensed
|
556 |
+
under version 3 of the GNU Affero General Public License into a single
|
557 |
+
combined work, and to convey the resulting work. The terms of this
|
558 |
+
License will continue to apply to the part which is the covered work,
|
559 |
+
but the special requirements of the GNU Affero General Public License,
|
560 |
+
section 13, concerning interaction through a network will apply to the
|
561 |
+
combination as such.
|
562 |
+
|
563 |
+
14. Revised Versions of this License.
|
564 |
+
|
565 |
+
The Free Software Foundation may publish revised and/or new versions of
|
566 |
+
the GNU General Public License from time to time. Such new versions will
|
567 |
+
be similar in spirit to the present version, but may differ in detail to
|
568 |
+
address new problems or concerns.
|
569 |
+
|
570 |
+
Each version is given a distinguishing version number. If the
|
571 |
+
Program specifies that a certain numbered version of the GNU General
|
572 |
+
Public License "or any later version" applies to it, you have the
|
573 |
+
option of following the terms and conditions either of that numbered
|
574 |
+
version or of any later version published by the Free Software
|
575 |
+
Foundation. If the Program does not specify a version number of the
|
576 |
+
GNU General Public License, you may choose any version ever published
|
577 |
+
by the Free Software Foundation.
|
578 |
+
|
579 |
+
If the Program specifies that a proxy can decide which future
|
580 |
+
versions of the GNU General Public License can be used, that proxy's
|
581 |
+
public statement of acceptance of a version permanently authorizes you
|
582 |
+
to choose that version for the Program.
|
583 |
+
|
584 |
+
Later license versions may give you additional or different
|
585 |
+
permissions. However, no additional obligations are imposed on any
|
586 |
+
author or copyright holder as a result of your choosing to follow a
|
587 |
+
later version.
|
588 |
+
|
589 |
+
15. Disclaimer of Warranty.
|
590 |
+
|
591 |
+
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
592 |
+
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
593 |
+
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
594 |
+
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
595 |
+
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
596 |
+
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
597 |
+
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
598 |
+
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
599 |
+
|
600 |
+
16. Limitation of Liability.
|
601 |
+
|
602 |
+
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
603 |
+
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
604 |
+
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
605 |
+
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
606 |
+
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
607 |
+
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
608 |
+
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
609 |
+
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
610 |
+
SUCH DAMAGES.
|
611 |
+
|
612 |
+
17. Interpretation of Sections 15 and 16.
|
613 |
+
|
614 |
+
If the disclaimer of warranty and limitation of liability provided
|
615 |
+
above cannot be given local legal effect according to their terms,
|
616 |
+
reviewing courts shall apply local law that most closely approximates
|
617 |
+
an absolute waiver of all civil liability in connection with the
|
618 |
+
Program, unless a warranty or assumption of liability accompanies a
|
619 |
+
copy of the Program in return for a fee.
|
620 |
+
|
621 |
+
END OF TERMS AND CONDITIONS
|
622 |
+
|
623 |
+
How to Apply These Terms to Your New Programs
|
624 |
+
|
625 |
+
If you develop a new program, and you want it to be of the greatest
|
626 |
+
possible use to the public, the best way to achieve this is to make it
|
627 |
+
free software which everyone can redistribute and change under these terms.
|
628 |
+
|
629 |
+
To do so, attach the following notices to the program. It is safest
|
630 |
+
to attach them to the start of each source file to most effectively
|
631 |
+
state the exclusion of warranty; and each file should have at least
|
632 |
+
the "copyright" line and a pointer to where the full notice is found.
|
633 |
+
|
634 |
+
<one line to give the program's name and a brief idea of what it does.>
|
635 |
+
Copyright (C) <year> <name of author>
|
636 |
+
|
637 |
+
This program is free software: you can redistribute it and/or modify
|
638 |
+
it under the terms of the GNU General Public License as published by
|
639 |
+
the Free Software Foundation, either version 3 of the License, or
|
640 |
+
(at your option) any later version.
|
641 |
+
|
642 |
+
This program is distributed in the hope that it will be useful,
|
643 |
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
644 |
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
645 |
+
GNU General Public License for more details.
|
646 |
+
|
647 |
+
You should have received a copy of the GNU General Public License
|
648 |
+
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
649 |
+
|
650 |
+
Also add information on how to contact you by electronic and paper mail.
|
651 |
+
|
652 |
+
If the program does terminal interaction, make it output a short
|
653 |
+
notice like this when it starts in an interactive mode:
|
654 |
+
|
655 |
+
<program> Copyright (C) <year> <name of author>
|
656 |
+
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
657 |
+
This is free software, and you are welcome to redistribute it
|
658 |
+
under certain conditions; type `show c' for details.
|
659 |
+
|
660 |
+
The hypothetical commands `show w' and `show c' should show the appropriate
|
661 |
+
parts of the General Public License. Of course, your program's commands
|
662 |
+
might be different; for a GUI interface, you would use an "about box".
|
663 |
+
|
664 |
+
You should also get your employer (if you work as a programmer) or school,
|
665 |
+
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
666 |
+
For more information on this, and how to apply and follow the GNU GPL, see
|
667 |
+
<http://www.gnu.org/licenses/>.
|
668 |
+
|
669 |
+
The GNU General Public License does not permit incorporating your program
|
670 |
+
into proprietary programs. If your program is a subroutine library, you
|
671 |
+
may consider it more useful to permit linking proprietary applications with
|
672 |
+
the library. If this is what you want to do, use the GNU Lesser General
|
673 |
+
Public License instead of this License. But first, please read
|
674 |
+
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
|
vouchervision/component_detector/__init__.py
ADDED
File without changes
|
vouchervision/component_detector/armature_processing.py
ADDED
@@ -0,0 +1,1047 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os, math, cv2, random
|
2 |
+
import numpy as np
|
3 |
+
from itertools import combinations
|
4 |
+
from PIL import Image
|
5 |
+
from dataclasses import dataclass, field
|
6 |
+
from typing import List, Dict
|
7 |
+
from sklearn.linear_model import LinearRegression
|
8 |
+
from scipy.optimize import fsolve, minimize
|
9 |
+
|
10 |
+
|
11 |
+
@dataclass()
|
12 |
+
class ArmatureSkeleton:
|
13 |
+
cfg: str
|
14 |
+
Dirs: str
|
15 |
+
leaf_type: str
|
16 |
+
all_points: list
|
17 |
+
dir_temp: str
|
18 |
+
file_name: str
|
19 |
+
width: int
|
20 |
+
height: int
|
21 |
+
logger: object
|
22 |
+
|
23 |
+
is_complete: bool = False
|
24 |
+
keep_going: bool = False
|
25 |
+
|
26 |
+
do_show_QC_images: bool = False
|
27 |
+
do_save_QC_images: bool = False
|
28 |
+
|
29 |
+
classes: int = 0
|
30 |
+
points_list: int = 0
|
31 |
+
|
32 |
+
image: int = 0
|
33 |
+
|
34 |
+
ordered_middle: int = 0
|
35 |
+
midvein_fit: int = 0
|
36 |
+
midvein_fit_points: int = 0
|
37 |
+
ordered_midvein_length: float = 0.0
|
38 |
+
has_middle = False
|
39 |
+
|
40 |
+
has_outer = False
|
41 |
+
has_tip = False
|
42 |
+
|
43 |
+
is_split = False
|
44 |
+
|
45 |
+
ordered_petiole: int = 0
|
46 |
+
ordered_petiole_length: float = 0.0
|
47 |
+
has_ordered_petiole = False
|
48 |
+
|
49 |
+
has_apex: bool = False
|
50 |
+
apex_left: int = 0
|
51 |
+
apex_right: int = 0
|
52 |
+
apex_center: int = 0
|
53 |
+
apex_angle_type: str = 'NA'
|
54 |
+
apex_angle_degrees: float = 0.0
|
55 |
+
|
56 |
+
has_base: bool = False
|
57 |
+
base_left: int = 0
|
58 |
+
base_right: int = 0
|
59 |
+
base_center: int = 0
|
60 |
+
base_angle_type: str = 'NA'
|
61 |
+
base_angle_degrees: float = 0.0
|
62 |
+
|
63 |
+
has_lamina_base: bool = False
|
64 |
+
lamina_base: int = 0
|
65 |
+
|
66 |
+
has_lamina_length: bool = False
|
67 |
+
lamina_fit: int = 0
|
68 |
+
lamina_length: float = 0.0
|
69 |
+
|
70 |
+
has_width: bool = False
|
71 |
+
lamina_width: float = 0.0
|
72 |
+
width_left: float = 0.0
|
73 |
+
width_right: float = 0.0
|
74 |
+
|
75 |
+
|
76 |
+
|
77 |
+
def __init__(self, cfg, logger, Dirs, leaf_type, all_points, height, width, dir_temp, file_name) -> None:
|
78 |
+
# Store the necessary arguments as instance attributes
|
79 |
+
self.cfg = cfg
|
80 |
+
self.Dirs = Dirs
|
81 |
+
self.leaf_type = leaf_type
|
82 |
+
self.all_points = all_points
|
83 |
+
self.height = height
|
84 |
+
self.width = width
|
85 |
+
self.dir_temp = dir_temp
|
86 |
+
self.file_name = file_name
|
87 |
+
|
88 |
+
logger.name = f'[{leaf_type} - {file_name}]'
|
89 |
+
self.logger = logger
|
90 |
+
|
91 |
+
self.init_lists_dicts()
|
92 |
+
|
93 |
+
""" Setup """
|
94 |
+
self.set_cfg_values()
|
95 |
+
self.define_landmark_classes()
|
96 |
+
|
97 |
+
self.setup_QC_image()
|
98 |
+
self.setup_angle_image()
|
99 |
+
self.setup_final_image()
|
100 |
+
|
101 |
+
self.parse_all_points()
|
102 |
+
self.convert_YOLO_bbox_to_point()
|
103 |
+
|
104 |
+
if (len(self.points_list['outer']) > 6) and (len(self.points_list['middle']) > 3):
|
105 |
+
self.keep_going = True
|
106 |
+
|
107 |
+
""" Landmarks """
|
108 |
+
if self.keep_going:
|
109 |
+
# Start with ordering the midvein and petiole
|
110 |
+
self.order_middle()
|
111 |
+
# print(self.ordered_midvein)
|
112 |
+
if self.keep_going:
|
113 |
+
# Split the image using the midvein IF has_midvein == True
|
114 |
+
self.split_image_by_middle()
|
115 |
+
if self.keep_going:
|
116 |
+
self.group_outer_points()
|
117 |
+
if self.keep_going:
|
118 |
+
# Measure
|
119 |
+
self.measure_armature()
|
120 |
+
if self.keep_going:
|
121 |
+
# calc tangent angle of outer and inner polys
|
122 |
+
self.calc_angle_tangent()
|
123 |
+
if self.keep_going:
|
124 |
+
self.calc_angle_curl()
|
125 |
+
if self.keep_going:
|
126 |
+
# self.calc_angle_bend()
|
127 |
+
self.calc_curvature_radius()
|
128 |
+
if self.keep_going:
|
129 |
+
self.calc_direct_length()
|
130 |
+
|
131 |
+
# self.show_QC_image()
|
132 |
+
# self.show_angle_image()
|
133 |
+
|
134 |
+
self.is_complete = True # TODO add ways to set True
|
135 |
+
|
136 |
+
|
137 |
+
def measure_armature(self):
|
138 |
+
# wb = width_base = line between the last outer and inner points
|
139 |
+
# Define the line function
|
140 |
+
def line_func(x):
|
141 |
+
return self.wb_slope * x + self.wb_intercept
|
142 |
+
def middle_func(x):
|
143 |
+
return self.middle_poly[0]*x**2 + self.middle_poly[1]*x + self.middle_poly[2]
|
144 |
+
# Define the difference function
|
145 |
+
def line_middle_diff(x):
|
146 |
+
return line_func(x) - middle_func(x)
|
147 |
+
|
148 |
+
# Convert the points to numpy arrays
|
149 |
+
last_point_right = np.array(self.last_point_right)
|
150 |
+
last_point_left = np.array(self.last_point_left)
|
151 |
+
|
152 |
+
# Calculate the Euclidean distance between the points
|
153 |
+
self.width_base = np.linalg.norm(last_point_right - last_point_left)
|
154 |
+
print("The distance between the last points of the right and left segments is:", self.width_base)
|
155 |
+
|
156 |
+
# Intersection of the width and the middlepoly# Draw a line between the last points of the outer_left and outer_right segments
|
157 |
+
cv2.line(self.image, (int(self.last_point_left[0]), int(self.last_point_left[1])), (int(self.last_point_right[0]), int(self.last_point_right[1])), gc('white'), thickness=2)
|
158 |
+
cv2.line(self.image_angles, (int(self.last_point_left[0]), int(self.last_point_left[1])), (int(self.last_point_right[0]), int(self.last_point_right[1])), color=gc('white'), thickness=2)
|
159 |
+
|
160 |
+
# Calculate the slope and y-intercept of the line
|
161 |
+
self.wb_slope = (self.last_point_right[1] - self.last_point_left[1]) / (self.last_point_right[0] - self.last_point_left[0])
|
162 |
+
self.wb_intercept = self.last_point_left[1] - self.wb_slope * self.last_point_left[0]
|
163 |
+
|
164 |
+
# Find the intersection point
|
165 |
+
intersection_x = fsolve(line_middle_diff, 0)[0]
|
166 |
+
intersection_y = line_func(intersection_x)
|
167 |
+
|
168 |
+
self.width_base_inter = [(int(intersection_x), int(intersection_y))]
|
169 |
+
# Calculate the midpoint between the last points
|
170 |
+
self.width_base_mid = (last_point_right + last_point_left) / 2
|
171 |
+
|
172 |
+
cv2.circle(self.image, (int(intersection_x), int(intersection_y)), radius=2, color=gc('green'), thickness=-1)
|
173 |
+
cv2.circle(self.image, (int(intersection_x), int(intersection_y)), radius=4, color=gc('black'), thickness=2)
|
174 |
+
cv2.circle(self.image, (int(self.width_base_mid[0]), int(self.width_base_mid[1])), radius=2, color=gc('red'), thickness=-1)
|
175 |
+
cv2.circle(self.image, (int(self.width_base_mid[0]), int(self.width_base_mid[1])), radius=4, color=gc('black'), thickness=2)
|
176 |
+
|
177 |
+
print("The intersection point of the line and the middle polynomial is:", (intersection_x, intersection_y))
|
178 |
+
|
179 |
+
|
180 |
+
|
181 |
+
def calc_direct_length(self):
|
182 |
+
# Calculate the x-coordinate of the intersection point
|
183 |
+
x_intersection = (self.wb_intercept_perpendicular - self.wb_intercept) / (self.wb_slope - self.wb_slope_perpendicular)
|
184 |
+
|
185 |
+
# Calculate the y-coordinate of the intersection point
|
186 |
+
y_intersection = self.wb_slope * x_intersection + self.wb_intercept
|
187 |
+
|
188 |
+
# Store the intersection point as self.wb_origin
|
189 |
+
self.wb_origin = np.array([x_intersection, y_intersection])
|
190 |
+
|
191 |
+
# Calculate the distance between the intersection point and self.inter_point
|
192 |
+
self.length_direct = np.linalg.norm(self.wb_origin - self.inter_point)
|
193 |
+
# Plot a 2-pixel thick red line from self.wb_origin to self.inter_point
|
194 |
+
cv2.line(self.image_angles, tuple(map(int, self.wb_origin)), tuple(map(int, self.inter_point)), gc('red'), thickness=2)
|
195 |
+
|
196 |
+
|
197 |
+
|
198 |
+
def calc_curvature_radius(self):
|
199 |
+
def fit_circle_least_squares(points):
|
200 |
+
if len(points) <= 1:
|
201 |
+
return 0.0, (0, 0)
|
202 |
+
|
203 |
+
def calc_residuals(params, points):
|
204 |
+
x0, y0, r = params
|
205 |
+
residuals = np.sqrt((points[:, 0] - x0) ** 2 + (points[:, 1] - y0) ** 2) - r
|
206 |
+
return residuals
|
207 |
+
|
208 |
+
def objective(params, points):
|
209 |
+
return np.sum(calc_residuals(params, points) ** 2)
|
210 |
+
|
211 |
+
x_mean = np.mean(points[:, 0])
|
212 |
+
y_mean = np.mean(points[:, 1])
|
213 |
+
r_mean = np.mean(np.sqrt((points[:, 0] - x_mean) ** 2 + (points[:, 1] - y_mean) ** 2))
|
214 |
+
init_params = [x_mean, y_mean, r_mean]
|
215 |
+
|
216 |
+
result = minimize(objective, init_params, args=(points,), method='L-BFGS-B')
|
217 |
+
x0, y0, r = result.x
|
218 |
+
|
219 |
+
return r, (x0, y0)
|
220 |
+
|
221 |
+
self.radius_middle, center_middle = fit_circle_least_squares(self.ordered_middle_np)
|
222 |
+
self.radius_outer_left, center_outer_left = fit_circle_least_squares(self.ordered_outer_left_np)
|
223 |
+
self.radius_outer_right, center_outer_right = fit_circle_least_squares(self.ordered_outer_right_np)
|
224 |
+
|
225 |
+
|
226 |
+
# Plot the circles on self.image_angles
|
227 |
+
cv2.circle(self.image_angles, (int(center_middle[0]), int(center_middle[1])), int(self.radius_middle), gc('yellow'), thickness=1)
|
228 |
+
cv2.circle(self.image_angles, (int(center_outer_left[0]), int(center_outer_left[1])), int(self.radius_outer_left), gc('pink'), thickness=1)
|
229 |
+
cv2.circle(self.image_angles, (int(center_outer_right[0]), int(center_outer_right[1])), int(self.radius_outer_right), gc('cyan'), thickness=1)
|
230 |
+
|
231 |
+
print('hi')
|
232 |
+
|
233 |
+
|
234 |
+
def calc_angle_bend(self):
|
235 |
+
print('hi')
|
236 |
+
|
237 |
+
|
238 |
+
|
239 |
+
def calc_angle_curl(self):
|
240 |
+
# Define the perpendicular line function
|
241 |
+
def wb_line_perpendicular(x):
|
242 |
+
return self.wb_slope_perpendicular * x + self.wb_intercept_perpendicular
|
243 |
+
|
244 |
+
|
245 |
+
# Calculate the slope of the line perpendicular to the given line
|
246 |
+
self.wb_slope_perpendicular = -1 / self.wb_slope
|
247 |
+
# Calculate the y-intercept of the line perpendicular to the given line
|
248 |
+
self.wb_intercept_perpendicular = self.inter_point[1] - self.wb_slope_perpendicular * self.inter_point[0]
|
249 |
+
|
250 |
+
# Line fit to first 3 points in self.ordered_middle
|
251 |
+
self.middle_tip_poly = np.polyfit(self.ordered_middle_np[0:3, 0], self.ordered_middle_np[0:3, 1], 1)
|
252 |
+
middle_tip_slope = self.middle_tip_poly[0]
|
253 |
+
|
254 |
+
# angle between middle_tip fit the curl perpendicular
|
255 |
+
theta = math.atan(abs((middle_tip_slope - self.wb_slope_perpendicular) / (1 + self.wb_slope_perpendicular*middle_tip_slope)))
|
256 |
+
|
257 |
+
# Convert the angle to degrees
|
258 |
+
self.angle_curl = math.degrees(theta)
|
259 |
+
|
260 |
+
print("The angle between the lines is:", self.angle_curl, "degrees")
|
261 |
+
|
262 |
+
# Draw the tangents at the intersection point
|
263 |
+
intersection_point = np.array(self.inter_point_outer_inner, dtype=int)
|
264 |
+
length = 50 # Length of the tangent lines
|
265 |
+
|
266 |
+
# Calculate the points for the tangent lines
|
267 |
+
curl_tangent_point1 = (intersection_point[0] - length, intersection_point[1] - length * self.wb_slope_perpendicular)
|
268 |
+
curl_tangent_point2 = (intersection_point[0] + length, intersection_point[1] + length * self.wb_slope_perpendicular)
|
269 |
+
middle_tip_tangent_point1 = (intersection_point[0] - length, intersection_point[1] - length * middle_tip_slope)
|
270 |
+
middle_tip_tangent_point2 = (intersection_point[0] + length, intersection_point[1] + length * middle_tip_slope)
|
271 |
+
|
272 |
+
# Convert the points to integers
|
273 |
+
curl_tangent_point1 = tuple(map(int, curl_tangent_point1))
|
274 |
+
curl_tangent_point2 = tuple(map(int, curl_tangent_point2))
|
275 |
+
middle_tip_tangent_point1 = tuple(map(int, middle_tip_tangent_point1))
|
276 |
+
middle_tip_tangent_point2 = tuple(map(int, middle_tip_tangent_point2))
|
277 |
+
|
278 |
+
# Draw the tangent lines
|
279 |
+
cv2.line(self.image_angles, intersection_point, curl_tangent_point1, gc('teal'), 1)
|
280 |
+
cv2.line(self.image_angles, intersection_point, curl_tangent_point2, gc('teal'), 1)
|
281 |
+
cv2.line(self.image_angles, intersection_point, middle_tip_tangent_point1, gc('teal'), 1)
|
282 |
+
cv2.line(self.image_angles, intersection_point, middle_tip_tangent_point2, gc('teal'), 1)
|
283 |
+
|
284 |
+
# Draw the arc representing the angle
|
285 |
+
cv2.ellipse(self.image_angles, tuple(intersection_point), (length, length), 0, 0, self.angle_curl, gc('teal'), 2)
|
286 |
+
cv2.ellipse(self.image_angles, tuple(intersection_point), (length, length), 180, 0, self.angle_curl, gc('teal'), 2)
|
287 |
+
|
288 |
+
### plot the wb_line_perpendicular
|
289 |
+
# Calculate the y values for the start and end points of the line
|
290 |
+
y_start = max(0, int(wb_line_perpendicular(0)))
|
291 |
+
y_end = min(self.height, int(wb_line_perpendicular(self.width)))
|
292 |
+
|
293 |
+
# Define the range of y values for the line
|
294 |
+
y_range = np.linspace(y_start, y_end, num=100, dtype=int) # You can adjust 'num' to control the number of points
|
295 |
+
|
296 |
+
# Draw the dotted gray line
|
297 |
+
for i in range(len(y_range) - 1):
|
298 |
+
y1, x1 = y_range[i], int((y_range[i] - self.wb_intercept_perpendicular) / self.wb_slope_perpendicular)
|
299 |
+
x1 = max(0, min(x1, self.width)) # Keep x1 within the bounds of the image width
|
300 |
+
y2, x2 = y_range[i+1], int((y_range[i+1] - self.wb_intercept_perpendicular) / self.wb_slope_perpendicular)
|
301 |
+
x2 = max(0, min(x2, self.width)) # Keep x2 within the bounds of the image width
|
302 |
+
|
303 |
+
if i % 2 == 0: # Change the value of 2 to adjust the spacing between the dots
|
304 |
+
cv2.line(self.image_angles, (x1, y1), (x2, y2), gc('white'), 1)
|
305 |
+
|
306 |
+
|
307 |
+
|
308 |
+
|
309 |
+
def calc_angle_tangent(self):
|
310 |
+
# Define the polynomial functions
|
311 |
+
def left_func(x):
|
312 |
+
return self.left_poly[0]*x**2 + self.left_poly[1]*x + self.left_poly[2]
|
313 |
+
|
314 |
+
def right_func(x):
|
315 |
+
return self.right_poly[0]*x**2 + self.right_poly[1]*x + self.right_poly[2]
|
316 |
+
|
317 |
+
# Define the difference function
|
318 |
+
def left_right_diff(x):
|
319 |
+
return left_func(x) - right_func(x)
|
320 |
+
|
321 |
+
# Find the x-coordinate of the intersection point
|
322 |
+
intersection_x = fsolve(left_right_diff, 0)[0]
|
323 |
+
|
324 |
+
# Calculate the y-coordinate of the intersection point on the left and right curves
|
325 |
+
intersection_y_left = left_func(intersection_x)
|
326 |
+
intersection_y_right = right_func(intersection_x)
|
327 |
+
|
328 |
+
# Calculate the derivatives of the polynomials at the intersection point
|
329 |
+
left_derivative = 2*self.left_poly[0]*intersection_x + self.left_poly[1]
|
330 |
+
right_derivative = 2*self.right_poly[0]*intersection_x + self.right_poly[1]
|
331 |
+
|
332 |
+
# Calculate the angle between the tangents to the polynomials at the intersection point
|
333 |
+
theta = math.atan(abs((right_derivative - left_derivative) / (1 + left_derivative*right_derivative)))
|
334 |
+
|
335 |
+
# Convert the angle to degrees
|
336 |
+
self.angle_tangent = math.degrees(theta)
|
337 |
+
|
338 |
+
print("The angle between the left and right polynomials at their point of intersection is:", theta, "degrees")
|
339 |
+
|
340 |
+
# Draw the tangents at the intersection point
|
341 |
+
intersection_point = np.array([int(intersection_x), int(intersection_y_left + (intersection_y_right - intersection_y_left)/2)])
|
342 |
+
length = 30 # Length of the tangent lines
|
343 |
+
|
344 |
+
# Calculate the points for the tangent lines
|
345 |
+
left_tangent_point1 = (intersection_point[0] - length, intersection_point[1] - length * left_derivative)
|
346 |
+
left_tangent_point2 = (intersection_point[0] + length, intersection_point[1] + length * left_derivative)
|
347 |
+
right_tangent_point1 = (intersection_point[0] - length, intersection_point[1] - length * right_derivative)
|
348 |
+
right_tangent_point2 = (intersection_point[0] + length, intersection_point[1] + length * right_derivative)
|
349 |
+
|
350 |
+
# Convert the points to integers
|
351 |
+
left_tangent_point1 = tuple(map(int, left_tangent_point1))
|
352 |
+
left_tangent_point2 = tuple(map(int, left_tangent_point2))
|
353 |
+
right_tangent_point1 = tuple(map(int, right_tangent_point1))
|
354 |
+
right_tangent_point2 = tuple(map(int, right_tangent_point2))
|
355 |
+
|
356 |
+
# # Draw the tangent lines
|
357 |
+
# cv2.line(self.image_angles, intersection_point, left_tangent_point1, gc('yellow'), 1)
|
358 |
+
# cv2.line(self.image_angles, intersection_point, left_tangent_point2, gc('yellow'), 1)
|
359 |
+
# cv2.line(self.image_angles, intersection_point, right_tangent_point1, gc('yellow'), 1)
|
360 |
+
# cv2.line(self.image_angles, intersection_point, right_tangent_point2, gc('yellow'), 1)
|
361 |
+
|
362 |
+
# Draw the arc representing the angle
|
363 |
+
cv2.ellipse(self.image_angles, tuple(intersection_point), (length, length), 0, 0, self.angle_tangent, gc('yellow'), 2)
|
364 |
+
cv2.ellipse(self.image_angles, tuple(intersection_point), (length, length), 180, 0, self.angle_tangent, gc('yellow'), 2)
|
365 |
+
|
366 |
+
# self.show_angle_image()
|
367 |
+
# return theta
|
368 |
+
|
369 |
+
|
370 |
+
def group_outer_points(self):
|
371 |
+
# Split the points into two groups based on their position relative to the line
|
372 |
+
self.outer_left = []
|
373 |
+
self.outer_right = []
|
374 |
+
|
375 |
+
# if 'tip' in self.points_list:
|
376 |
+
|
377 |
+
for point in self.points_list['outer']:
|
378 |
+
x, y = point
|
379 |
+
predicted_y = self.predict_y(x)
|
380 |
+
|
381 |
+
if y > predicted_y:
|
382 |
+
self.outer_right.append(point)
|
383 |
+
else:
|
384 |
+
self.outer_left.append(point)
|
385 |
+
|
386 |
+
self.outer_right = np.array(self.outer_right)
|
387 |
+
self.outer_left = np.array(self.outer_left)
|
388 |
+
|
389 |
+
if (len(self.outer_right) < 3) or (len(self.outer_left) < 3):
|
390 |
+
self.keep_going = False
|
391 |
+
else:
|
392 |
+
# Plot `outer_left` points in pink
|
393 |
+
for point in self.outer_left:
|
394 |
+
x, y = point
|
395 |
+
cv2.circle(self.image, (x, y), radius=5, color=gc('pink'), thickness=-1)
|
396 |
+
|
397 |
+
# Plot `outer_right` points in cyan
|
398 |
+
for point in self.outer_right:
|
399 |
+
x, y = point
|
400 |
+
cv2.circle(self.image, (x, y), radius=5, color=gc('cyan'), thickness=-1)
|
401 |
+
|
402 |
+
### outer_left
|
403 |
+
self.outer_left = self.order_points(self.outer_left)
|
404 |
+
self.outer_left = self.remove_duplicate_points(self.outer_left)
|
405 |
+
# self.outer_left = self.check_momentum(self.outer_left, False)
|
406 |
+
self.order_points_plot(self.outer_left, 'outer_left', 'final')
|
407 |
+
self.order_points_plot(self.outer_left, 'outer_left', 'QC')
|
408 |
+
self.outer_left_length, self.outer_left = self.get_length_of_ordered_points(self.outer_left, 'outer_left')
|
409 |
+
self.has_outer_left = True
|
410 |
+
|
411 |
+
|
412 |
+
### outer_right
|
413 |
+
self.outer_right = self.order_points(self.outer_right)
|
414 |
+
self.outer_right = self.remove_duplicate_points(self.outer_right)
|
415 |
+
# self.outer_right = self.check_momentum(self.outer_right, False)
|
416 |
+
self.order_points_plot(self.outer_right, 'outer_right', 'final')
|
417 |
+
self.order_points_plot(self.outer_right, 'outer_right', 'QC')
|
418 |
+
self.outer_right_length, self.outer_right = self.get_length_of_ordered_points(self.outer_right, 'outer_right')
|
419 |
+
self.has_middle = True
|
420 |
+
|
421 |
+
print(f"Length outer_left - {self.outer_left_length}")
|
422 |
+
print(f"Length outer_right - {self.outer_right_length}")
|
423 |
+
|
424 |
+
self.outer_right_np = np.array(self.outer_right)
|
425 |
+
self.outer_left_np = np.array(self.outer_left)
|
426 |
+
self.ordered_middle_np = np.array(self.ordered_middle)
|
427 |
+
|
428 |
+
# Fit 2nd order polynomials to the line segments
|
429 |
+
self.left_poly = np.polyfit(self.outer_left_np[:, 0], self.outer_left_np[:, 1], 2)
|
430 |
+
self.right_poly = np.polyfit(self.outer_right_np[:, 0], self.outer_right_np[:, 1], 2)
|
431 |
+
self.middle_poly = np.polyfit(self.ordered_middle_np[:, 0], self.ordered_middle_np[:, 1], 2)
|
432 |
+
|
433 |
+
|
434 |
+
# Evaluate polynomial coefficients for a range of x values
|
435 |
+
x_range = np.linspace(0, self.width, num=100)
|
436 |
+
left_line = np.polyval(self.left_poly, x_range)
|
437 |
+
right_line = np.polyval(self.right_poly, x_range)
|
438 |
+
self.middle_line = np.polyval(self.middle_poly, x_range)
|
439 |
+
|
440 |
+
# Plot lines of fit as white lines
|
441 |
+
for i in range(len(x_range)-1):
|
442 |
+
cv2.line(self.image, (int(x_range[i]), int(left_line[i])), (int(x_range[i+1]), int(left_line[i+1])), color=gc('gray'), thickness=1)
|
443 |
+
cv2.line(self.image, (int(x_range[i]), int(right_line[i])), (int(x_range[i+1]), int(right_line[i+1])), color=gc('white'), thickness=1)
|
444 |
+
cv2.line(self.image, (int(x_range[i]), int(self.middle_line[i])), (int(x_range[i+1]), int(self.middle_line[i+1])), color=gc('white'), thickness=2)
|
445 |
+
|
446 |
+
# Define the polynomial functions
|
447 |
+
def left_func(x):
|
448 |
+
return self.left_poly[0]*x**2 + self.left_poly[1]*x + self.left_poly[2]
|
449 |
+
|
450 |
+
def right_func(x):
|
451 |
+
return self.right_poly[0]*x**2 + self.right_poly[1]*x + self.right_poly[2]
|
452 |
+
|
453 |
+
def middle_func(x):
|
454 |
+
return self.middle_poly[0]*x**2 + self.middle_poly[1]*x + self.middle_poly[2]
|
455 |
+
|
456 |
+
# Define the difference functions
|
457 |
+
def left_middle_diff(x):
|
458 |
+
return left_func(x) - middle_func(x)
|
459 |
+
|
460 |
+
def right_middle_diff(x):
|
461 |
+
return right_func(x) - middle_func(x)
|
462 |
+
|
463 |
+
def left_right_diff(x):
|
464 |
+
return left_func(x) - right_func(x)
|
465 |
+
|
466 |
+
# Find the intersection points
|
467 |
+
left_middle_intersection_x = fsolve(left_middle_diff, 0)
|
468 |
+
right_middle_intersection_x = fsolve(right_middle_diff, 0)
|
469 |
+
left_right_intersection_x = fsolve(left_right_diff, 0)
|
470 |
+
|
471 |
+
left_middle_intersection_y = left_func(left_middle_intersection_x)[0]
|
472 |
+
right_middle_intersection_y = right_func(right_middle_intersection_x)[0]
|
473 |
+
left_right_intersection_y = left_func(left_right_intersection_x)[0]
|
474 |
+
|
475 |
+
# Keep only points within the image boundaries
|
476 |
+
intersection_points = np.array([[left_middle_intersection_x, left_middle_intersection_y], [right_middle_intersection_x, right_middle_intersection_y], [left_right_intersection_x, left_right_intersection_y]])
|
477 |
+
intersection_points = intersection_points[(intersection_points[:, 0] >= 0) & (intersection_points[:, 0] <= self.width) & (intersection_points[:, 1] >= 0) & (intersection_points[:, 1] <= self.height)]
|
478 |
+
|
479 |
+
if intersection_points.size == 0:
|
480 |
+
self.keep_going = False
|
481 |
+
else:
|
482 |
+
# Compute the average of the intersection points
|
483 |
+
intersection_x = np.mean(intersection_points[:, 0])
|
484 |
+
intersection_y = np.mean(intersection_points[:, 1])
|
485 |
+
|
486 |
+
self.inter_point = [int(intersection_x), int(intersection_y)]
|
487 |
+
self.inter_point_outer_inner = [int(left_right_intersection_x), int(left_right_intersection_y)]
|
488 |
+
|
489 |
+
# Draw intersection point on the image
|
490 |
+
cv2.circle(self.image, (int(intersection_x), int(intersection_y)), radius=5, color=gc('green'), thickness=-1)
|
491 |
+
print(f"Length outer_left - {self.outer_left_length}")
|
492 |
+
print(f"Length outer_right - {self.outer_right_length}")
|
493 |
+
print(f"Intersection point - ({int(intersection_x)}, {int(intersection_y)})")
|
494 |
+
|
495 |
+
# Make the first points be at the tip, last points far away at base
|
496 |
+
def reorder_segment(segment, inter):
|
497 |
+
# Convert to numpy arrays for easier manipulation
|
498 |
+
segment = np.array(segment)
|
499 |
+
inter = np.array(inter)
|
500 |
+
|
501 |
+
# Calculate the Euclidean distance from the INTER point to the first and last points in the segment
|
502 |
+
dist_first = np.linalg.norm(segment[0] - inter)
|
503 |
+
dist_last = np.linalg.norm(segment[-1] - inter)
|
504 |
+
|
505 |
+
# If the last point is closer to the INTER point than the first point, reverse the order of the segment
|
506 |
+
if dist_last < dist_first:
|
507 |
+
segment = segment[::-1]
|
508 |
+
|
509 |
+
return segment.tolist()
|
510 |
+
|
511 |
+
self.ordered_middle = reorder_segment(self.ordered_middle, self.inter_point)
|
512 |
+
self.outer_left = reorder_segment(self.outer_left, self.inter_point)
|
513 |
+
self.outer_right = reorder_segment(self.outer_right, self.inter_point)
|
514 |
+
|
515 |
+
self.ordered_outer_right_np = np.array(self.outer_right)
|
516 |
+
self.ordered_outer_left_np = np.array(self.outer_left)
|
517 |
+
self.ordered_middle_np = np.array(self.ordered_middle)
|
518 |
+
|
519 |
+
# Draw a black ring around the last point of the outer_left segment
|
520 |
+
self.last_point_left = self.outer_left[-1]
|
521 |
+
cv2.circle(self.image, (int(self.last_point_left[0]), int(self.last_point_left[1])), radius=4, color=gc('black'), thickness=2)
|
522 |
+
cv2.circle(self.image, (int(self.last_point_left[0]), int(self.last_point_left[1])), radius=6, color=gc('white'), thickness=2)
|
523 |
+
|
524 |
+
# Draw a black ring around the last point of the outer_right segment
|
525 |
+
self.last_point_right = self.outer_right[-1]
|
526 |
+
cv2.circle(self.image, (int(self.last_point_right[0]), int(self.last_point_right[1])), radius=4, color=gc('black'), thickness=2)
|
527 |
+
cv2.circle(self.image, (int(self.last_point_right[0]), int(self.last_point_right[1])), radius=6, color=gc('white'), thickness=2)
|
528 |
+
|
529 |
+
# self.show_QC_image()
|
530 |
+
# print('hi')
|
531 |
+
|
532 |
+
|
533 |
+
|
534 |
+
|
535 |
+
def split_image_by_middle(self):
|
536 |
+
|
537 |
+
if not self.has_middle:
|
538 |
+
self.keep_going = False
|
539 |
+
else:
|
540 |
+
n_fit = 2
|
541 |
+
|
542 |
+
# Convert the points to a numpy array
|
543 |
+
points_arr = np.array(self.ordered_middle)
|
544 |
+
|
545 |
+
# Fit a line to the points
|
546 |
+
self.midvein_fit = np.polyfit(points_arr[:, 0], points_arr[:, 1], n_fit)
|
547 |
+
|
548 |
+
# Plot a sample of points from along the line
|
549 |
+
max_dim = max(self.height, self.width)
|
550 |
+
if max_dim < 400:
|
551 |
+
num_points = 40
|
552 |
+
elif max_dim < 1000:
|
553 |
+
num_points = 80
|
554 |
+
else:
|
555 |
+
num_points = 120
|
556 |
+
|
557 |
+
# Get the endpoints of the line segment that lies within the bounds of the image
|
558 |
+
x1 = 0
|
559 |
+
y1 = int(self.midvein_fit[0] * x1**2 + self.midvein_fit[1] * x1 + self.midvein_fit[2])
|
560 |
+
x2 = self.width - 1
|
561 |
+
y2 = int(self.midvein_fit[0] * x2**2 + self.midvein_fit[1] * x2 + self.midvein_fit[2])
|
562 |
+
|
563 |
+
denom = self.midvein_fit[0]
|
564 |
+
if denom == 0:
|
565 |
+
denom = 0.0000000001
|
566 |
+
if y1 < 0:
|
567 |
+
y1 = 0
|
568 |
+
x1 = int((y1 - self.midvein_fit[1]) / denom)
|
569 |
+
if y2 >= self.height:
|
570 |
+
y2 = self.height - 1
|
571 |
+
x2 = int((y2 - self.midvein_fit[1]) / denom)
|
572 |
+
|
573 |
+
# Sample num_points points along the line segment within the bounds of the image
|
574 |
+
x_vals = np.linspace(x1, x2, num_points)
|
575 |
+
y_vals = self.midvein_fit[0] * x_vals**2 + self.midvein_fit[1] * x_vals + self.midvein_fit[2]
|
576 |
+
|
577 |
+
# Remove any points that are outside the bounds of the image
|
578 |
+
indices = np.where((y_vals >= 0) & (y_vals < self.height))[0]
|
579 |
+
x_vals = x_vals[indices]
|
580 |
+
y_vals = y_vals[indices]
|
581 |
+
|
582 |
+
# Recompute y-values using the line equation and updated x-values
|
583 |
+
y_vals = self.midvein_fit[0] * x_vals + self.midvein_fit[1]
|
584 |
+
|
585 |
+
self.midvein_fit_points = np.column_stack((x_vals, y_vals))
|
586 |
+
self.is_split = True
|
587 |
+
|
588 |
+
# Draw line of fit
|
589 |
+
# for point in self.midvein_fit_points:
|
590 |
+
# cv2.circle(self.image, tuple(point.astype(int)), radius=1, color=(255, 255, 255), thickness=-1)
|
591 |
+
|
592 |
+
def predict_y(self, x):
|
593 |
+
return self.midvein_fit[0] * x**2 + self.midvein_fit[1] * x + self.midvein_fit[2]
|
594 |
+
|
595 |
+
def order_middle(self):
|
596 |
+
|
597 |
+
|
598 |
+
if 'middle' not in self.points_list:
|
599 |
+
self.keep_going = False
|
600 |
+
else:
|
601 |
+
if len(self.points_list['middle']) >= 5:
|
602 |
+
self.logger.debug(f"Ordered Middle - Raw list contains {len(self.points_list['middle'])} points - using momentum")
|
603 |
+
self.ordered_middle = self.order_points(self.points_list['middle'])
|
604 |
+
self.ordered_middle = self.remove_duplicate_points(self.ordered_middle)
|
605 |
+
|
606 |
+
self.ordered_middle = self.check_momentum(self.ordered_middle, False)
|
607 |
+
|
608 |
+
self.v_tip = self.find_v_tip(self.points_list['outer'])
|
609 |
+
# self.ordered_middle.append(self.v_tip)
|
610 |
+
|
611 |
+
|
612 |
+
self.order_points_plot(self.ordered_middle, 'middle', 'QC')
|
613 |
+
self.ordered_middle_length, self.ordered_middle = self.get_length_of_ordered_points(self.ordered_middle, 'middle')
|
614 |
+
|
615 |
+
|
616 |
+
self.has_middle = True
|
617 |
+
else:
|
618 |
+
self.keep_going = False
|
619 |
+
self.logger.debug(f"Ordered Middle - Raw list contains {len(self.points_list['middle'])} points - SKIPPING MIDDLE")
|
620 |
+
|
621 |
+
def v_shape_template(self, tip, scale):
|
622 |
+
return np.array([
|
623 |
+
[tip[0] - scale, tip[1] + scale],
|
624 |
+
tip,
|
625 |
+
[tip[0] + scale, tip[1] + scale]
|
626 |
+
])
|
627 |
+
|
628 |
+
def error_function(self, params, points):
|
629 |
+
tip = params[:2]
|
630 |
+
scale = params[2]
|
631 |
+
template_points = self.v_shape_template(tip, scale)
|
632 |
+
|
633 |
+
error = 0
|
634 |
+
for p in points:
|
635 |
+
dist = np.min(np.linalg.norm(template_points - p, axis=1))
|
636 |
+
error += dist
|
637 |
+
|
638 |
+
return error
|
639 |
+
|
640 |
+
def find_v_tip(self, points):
|
641 |
+
points = np.array(points)
|
642 |
+
initial_guess = np.mean(points, axis=0)
|
643 |
+
initial_scale = np.linalg.norm(np.max(points, axis=0) - np.min(points, axis=0)) / 2
|
644 |
+
|
645 |
+
result = minimize(
|
646 |
+
self.error_function,
|
647 |
+
np.hstack([initial_guess, initial_scale]),
|
648 |
+
args=(points,),
|
649 |
+
method='Nelder-Mead'
|
650 |
+
)
|
651 |
+
|
652 |
+
tip = result.x[:2]
|
653 |
+
return tuple(map(int, tip))
|
654 |
+
|
655 |
+
def show_QC_image(self):
|
656 |
+
if self.do_show_QC_images:
|
657 |
+
cv2.imshow('QC image', self.image)
|
658 |
+
cv2.waitKey(0)
|
659 |
+
|
660 |
+
def show_angle_image(self):
|
661 |
+
if self.do_show_QC_images:
|
662 |
+
cv2.imshow('Angles image', self.image_angles)
|
663 |
+
cv2.waitKey(0)
|
664 |
+
|
665 |
+
def show_final_image(self):
|
666 |
+
if self.do_show_final_images:
|
667 |
+
cv2.imshow('Final image', self.image_final)
|
668 |
+
cv2.waitKey(0)
|
669 |
+
|
670 |
+
def get_length_of_ordered_points(self, points, name):
|
671 |
+
# if self.file_name == 'B_774373631_Ebenaceae_Diospyros_buxifolia__L__438-687-578-774':
|
672 |
+
# print('hi')
|
673 |
+
total_length = 0
|
674 |
+
total_length_first_pass = 0
|
675 |
+
for i in range(len(points) - 1):
|
676 |
+
x1, y1 = points[i]
|
677 |
+
x2, y2 = points[i+1]
|
678 |
+
segment_length = math.sqrt((x2-x1)**2 + (y2-y1)**2)
|
679 |
+
total_length_first_pass += segment_length
|
680 |
+
cutoff = total_length_first_pass / 2
|
681 |
+
# print(f'Total length of {name}: {total_length_first_pass}')
|
682 |
+
# print(f'points length {len(points)}')
|
683 |
+
self.logger.debug(f"Total length of {name}: {total_length_first_pass}")
|
684 |
+
self.logger.debug(f"Points length {len(points)}")
|
685 |
+
|
686 |
+
|
687 |
+
# If there are more than 2 points, this will exclude extreme outliers, or
|
688 |
+
# misordered points that don't belong
|
689 |
+
if len(points) > 2:
|
690 |
+
pop_ind = []
|
691 |
+
for i in range(len(points) - 1):
|
692 |
+
x1, y1 = points[i]
|
693 |
+
x2, y2 = points[i+1]
|
694 |
+
segment_length = math.sqrt((x2-x1)**2 + (y2-y1)**2)
|
695 |
+
if segment_length < cutoff:
|
696 |
+
total_length += segment_length
|
697 |
+
else:
|
698 |
+
pop_ind.append(i)
|
699 |
+
|
700 |
+
for exclude in pop_ind:
|
701 |
+
points.pop(exclude)
|
702 |
+
# print(f'Total length of {name}: {total_length}')
|
703 |
+
# print(f'Excluded {len(pop_ind)} points')
|
704 |
+
# print(f'points length {len(points)}')
|
705 |
+
self.logger.debug(f"Total length of {name}: {total_length}")
|
706 |
+
self.logger.debug(f"Excluded {len(pop_ind)} points")
|
707 |
+
self.logger.debug(f"Points length {len(points)}")
|
708 |
+
|
709 |
+
else:
|
710 |
+
total_length = total_length_first_pass
|
711 |
+
|
712 |
+
return total_length, points
|
713 |
+
|
714 |
+
def order_points_plot(self, points, version, QC_or_final):
|
715 |
+
# thk_base = 0
|
716 |
+
thk_base = 16
|
717 |
+
|
718 |
+
if version == 'middle':
|
719 |
+
# color = (0, 255, 0)
|
720 |
+
color = gc('green') # blue
|
721 |
+
thick = 1 #2 + thk_base
|
722 |
+
elif version == 'tip':
|
723 |
+
color = gc('green')
|
724 |
+
thick = 1 #2 + thk_base
|
725 |
+
elif version == 'outer':
|
726 |
+
color = gc('red')
|
727 |
+
thick = 1 #2 + thk_base
|
728 |
+
elif version == 'outer_left':
|
729 |
+
color = gc('pink')
|
730 |
+
thick = 1 #2 + thk_base
|
731 |
+
elif version == 'outer_right':
|
732 |
+
color = gc('cyan')
|
733 |
+
thick = 1 #2 + thk_base
|
734 |
+
|
735 |
+
|
736 |
+
# elif version == 'lamina_width_alt':
|
737 |
+
# color = (100, 100, 255)
|
738 |
+
# thick = 2 + thk_base
|
739 |
+
# elif version == 'not_reflex':
|
740 |
+
# color = (200, 0, 123)
|
741 |
+
# thick = 3 + thk_base
|
742 |
+
# elif version == 'reflex':
|
743 |
+
# color = (0, 120, 200)
|
744 |
+
# thick = 3 + thk_base
|
745 |
+
# elif version == 'petiole_tip_alt':
|
746 |
+
# color = (255, 55, 100)
|
747 |
+
# thick = 1 + thk_base
|
748 |
+
# elif version == 'petiole_tip':
|
749 |
+
# color = (100, 255, 55)
|
750 |
+
# thick = 1 + thk_base
|
751 |
+
# elif version == 'failed_angle':
|
752 |
+
# color = (0, 0, 0)
|
753 |
+
# thick = 3 + thk_base
|
754 |
+
# Convert the points to a numpy array and round to integer values
|
755 |
+
points_arr = np.round(np.array(points)).astype(int)
|
756 |
+
|
757 |
+
# Draw a green line connecting all of the points
|
758 |
+
if QC_or_final == 'QC':
|
759 |
+
for i in range(len(points_arr) - 1):
|
760 |
+
cv2.line(self.image, tuple(points_arr[i]), tuple(points_arr[i+1]), color, thick)
|
761 |
+
else:
|
762 |
+
for i in range(len(points_arr) - 1):
|
763 |
+
cv2.line(self.image_final, tuple(points_arr[i]), tuple(points_arr[i+1]), color, thick)
|
764 |
+
|
765 |
+
def check_momentum(self, coords, info):
|
766 |
+
original_coords = coords
|
767 |
+
# find middle index of coordinates
|
768 |
+
mid_idx = len(coords) // 2
|
769 |
+
|
770 |
+
# set up variables for running average
|
771 |
+
running_avg = np.array(coords[mid_idx-1])
|
772 |
+
avg_count = 1
|
773 |
+
|
774 |
+
# iterate over coordinates to check momentum change
|
775 |
+
prev_vec = np.array(coords[mid_idx-1]) - np.array(coords[mid_idx-2])
|
776 |
+
cur_idx = mid_idx - 1
|
777 |
+
while cur_idx >= 0:
|
778 |
+
cur_vec = np.array(coords[cur_idx]) - np.array(coords[cur_idx-1])
|
779 |
+
|
780 |
+
# add current point to running average
|
781 |
+
running_avg = (running_avg * avg_count + np.array(coords[cur_idx])) / (avg_count + 1)
|
782 |
+
avg_count += 1
|
783 |
+
|
784 |
+
# check for momentum change
|
785 |
+
if self.check_momentum_change(prev_vec, cur_vec):
|
786 |
+
break
|
787 |
+
|
788 |
+
prev_vec = cur_vec
|
789 |
+
cur_idx -= 1
|
790 |
+
|
791 |
+
# use running average to check for momentum change
|
792 |
+
cur_vec = np.array(coords[cur_idx]) - running_avg
|
793 |
+
if self.check_momentum_change(prev_vec, cur_vec):
|
794 |
+
cur_idx += 1
|
795 |
+
|
796 |
+
prev_vec = np.array(coords[mid_idx+1]) - np.array(coords[mid_idx])
|
797 |
+
cur_idx2 = mid_idx + 1
|
798 |
+
while cur_idx2 < len(coords):
|
799 |
+
|
800 |
+
# check if current index is out of range
|
801 |
+
if cur_idx2 >= len(coords):
|
802 |
+
break
|
803 |
+
|
804 |
+
cur_vec = np.array(coords[cur_idx2]) - np.array(coords[cur_idx2-1])
|
805 |
+
|
806 |
+
# add current point to running average
|
807 |
+
running_avg = (running_avg * avg_count + np.array(coords[cur_idx2])) / (avg_count + 1)
|
808 |
+
avg_count += 1
|
809 |
+
|
810 |
+
# check for momentum change
|
811 |
+
if self.check_momentum_change(prev_vec, cur_vec):
|
812 |
+
break
|
813 |
+
|
814 |
+
prev_vec = cur_vec
|
815 |
+
cur_idx2 += 1
|
816 |
+
|
817 |
+
# use running average to check for momentum change
|
818 |
+
if cur_idx2 < len(coords):
|
819 |
+
cur_vec = np.array(coords[cur_idx2]) - running_avg
|
820 |
+
if self.check_momentum_change(prev_vec, cur_vec):
|
821 |
+
cur_idx2 -= 1
|
822 |
+
|
823 |
+
# remove problematic points and subsequent points from list of coordinates
|
824 |
+
new_coords = coords[:cur_idx2] + coords[mid_idx:cur_idx2:-1]
|
825 |
+
if info:
|
826 |
+
return new_coords, len(original_coords) != len(new_coords)
|
827 |
+
else:
|
828 |
+
return new_coords
|
829 |
+
|
830 |
+
# define function to check for momentum change
|
831 |
+
def check_momentum_change(self, prev_vec, cur_vec):
|
832 |
+
dot_product = np.dot(prev_vec, cur_vec)
|
833 |
+
prev_norm = np.linalg.norm(prev_vec)
|
834 |
+
cur_norm = np.linalg.norm(cur_vec)
|
835 |
+
denom = (prev_norm * cur_norm)
|
836 |
+
if denom == 0:
|
837 |
+
denom = 0.0000000001
|
838 |
+
cos_theta = dot_product / denom
|
839 |
+
theta = np.arccos(cos_theta)
|
840 |
+
return abs(theta) > np.pi / 2
|
841 |
+
|
842 |
+
def remove_duplicate_points(self, points):
|
843 |
+
unique_set = set()
|
844 |
+
new_list = []
|
845 |
+
|
846 |
+
for item in points:
|
847 |
+
if item not in unique_set:
|
848 |
+
unique_set.add(item)
|
849 |
+
new_list.append(item)
|
850 |
+
return new_list
|
851 |
+
|
852 |
+
def distance(self, point1, point2):
|
853 |
+
x1, y1 = point1
|
854 |
+
x2, y2 = point2
|
855 |
+
return math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)
|
856 |
+
|
857 |
+
### Shortest distance
|
858 |
+
def order_points(self, points):
|
859 |
+
points = [tuple(point) for point in points] # Convert numpy.ndarray points to tuples
|
860 |
+
|
861 |
+
best_tour = None
|
862 |
+
shortest_tour_length = float('inf')
|
863 |
+
|
864 |
+
for start_point in points:
|
865 |
+
tour = [start_point]
|
866 |
+
unvisited = set(points) - {start_point}
|
867 |
+
|
868 |
+
while unvisited:
|
869 |
+
nearest = min(unvisited, key=lambda point: self.distance(tour[-1], point))
|
870 |
+
tour.append(nearest)
|
871 |
+
unvisited.remove(nearest)
|
872 |
+
|
873 |
+
# Calculate the length of the current tour
|
874 |
+
tour_length = sum(self.distance(tour[i - 1], tour[i]) for i in range(1, len(tour)))
|
875 |
+
|
876 |
+
# Update the best_tour if the current tour is shorter
|
877 |
+
if tour_length < shortest_tour_length:
|
878 |
+
shortest_tour_length = tour_length
|
879 |
+
best_tour = tour
|
880 |
+
|
881 |
+
return best_tour
|
882 |
+
|
883 |
+
|
884 |
+
### Smoothest
|
885 |
+
'''
|
886 |
+
def angle_between_points(self, p1, p2, p3):
|
887 |
+
v1 = np.array([p1[0] - p2[0], p1[1] - p2[1]])
|
888 |
+
v2 = np.array([p3[0] - p2[0], p3[1] - p2[1]])
|
889 |
+
angle = np.arccos(np.dot(v1, v2) / (np.linalg.norm(v1) * np.linalg.norm(v2)))
|
890 |
+
return angle
|
891 |
+
|
892 |
+
def order_points(self, points):
|
893 |
+
points = [tuple(point) for point in points] # Convert numpy.ndarray points to tuples
|
894 |
+
|
895 |
+
best_tour = None
|
896 |
+
largest_sum_angles = 0
|
897 |
+
|
898 |
+
for start_point in points:
|
899 |
+
tour = [start_point]
|
900 |
+
unvisited = set(points) - {start_point}
|
901 |
+
|
902 |
+
while unvisited:
|
903 |
+
nearest = min(unvisited, key=lambda point: self.distance(tour[-1], point))
|
904 |
+
tour.append(nearest)
|
905 |
+
unvisited.remove(nearest)
|
906 |
+
|
907 |
+
# Calculate the sum of angles for the current tour
|
908 |
+
sum_angles = sum(self.angle_between_points(tour[i - 1], tour[i], tour[i + 1]) for i in range(1, len(tour) - 1))
|
909 |
+
|
910 |
+
# Update the best_tour if the current tour has a larger sum of angles
|
911 |
+
if sum_angles > largest_sum_angles:
|
912 |
+
largest_sum_angles = sum_angles
|
913 |
+
best_tour = tour
|
914 |
+
|
915 |
+
return best_tour
|
916 |
+
'''
|
917 |
+
### ^^^ Smoothest
|
918 |
+
|
919 |
+
|
920 |
+
|
921 |
+
|
922 |
+
def convert_YOLO_bbox_to_point(self):
|
923 |
+
for point_type, bbox in self.points_list.items():
|
924 |
+
xy_points = []
|
925 |
+
for point in bbox:
|
926 |
+
x = point[0]
|
927 |
+
y = point[1]
|
928 |
+
w = point[2]
|
929 |
+
h = point[3]
|
930 |
+
x1 = int((x - w/2) * self.width)
|
931 |
+
y1 = int((y - h/2) * self.height)
|
932 |
+
x2 = int((x + w/2) * self.width)
|
933 |
+
y2 = int((y + h/2) * self.height)
|
934 |
+
xy_points.append((int((x1+x2)/2), int((y1+y2)/2)))
|
935 |
+
self.points_list[point_type] = xy_points
|
936 |
+
|
937 |
+
def parse_all_points(self):
|
938 |
+
points_list = {}
|
939 |
+
|
940 |
+
for sublist in self.all_points:
|
941 |
+
key = sublist[0]
|
942 |
+
value = sublist[1:]
|
943 |
+
|
944 |
+
key = self.swap_number_for_string(key)
|
945 |
+
|
946 |
+
if key not in points_list:
|
947 |
+
points_list[key] = []
|
948 |
+
points_list[key].append(value)
|
949 |
+
|
950 |
+
# print(points_list)
|
951 |
+
self.points_list = points_list
|
952 |
+
|
953 |
+
def swap_number_for_string(self, key):
|
954 |
+
for k, v in self.classes.items():
|
955 |
+
if v == key:
|
956 |
+
return k
|
957 |
+
return key
|
958 |
+
|
959 |
+
def setup_final_image(self):
|
960 |
+
self.image_final = cv2.imread(os.path.join(self.dir_temp, '.'.join([self.file_name, 'jpg'])))
|
961 |
+
|
962 |
+
if self.leaf_type == 'Landmarks_Armature':
|
963 |
+
self.path_image_final = os.path.join(self.Dirs.landmarks_armature_overlay_final, '.'.join([self.file_name, 'jpg']))
|
964 |
+
|
965 |
+
def setup_QC_image(self):
|
966 |
+
self.image = cv2.imread(os.path.join(self.dir_temp, '.'.join([self.file_name, 'jpg'])))
|
967 |
+
|
968 |
+
if self.leaf_type == 'Landmarks_Armature':
|
969 |
+
self.path_QC_image = os.path.join(self.Dirs.landmarks_armature_overlay_QC, '.'.join([self.file_name, 'jpg']))
|
970 |
+
|
971 |
+
def setup_angle_image(self):
|
972 |
+
self.image_angles = cv2.imread(os.path.join(self.dir_temp, '.'.join([self.file_name, 'jpg'])))
|
973 |
+
|
974 |
+
if self.leaf_type == 'Landmarks_Armature':
|
975 |
+
self.path_angles_image = os.path.join(self.Dirs.landmarks_armature_overlay_angles, '.'.join([self.file_name, 'jpg']))
|
976 |
+
|
977 |
+
def define_landmark_classes(self):
|
978 |
+
self.classes = {
|
979 |
+
'tip': 0,
|
980 |
+
'middle': 1,
|
981 |
+
'outer': 2,
|
982 |
+
}
|
983 |
+
|
984 |
+
def set_cfg_values(self):
|
985 |
+
self.do_show_QC_images = self.cfg['leafmachine']['landmark_detector_armature']['do_show_QC_images']
|
986 |
+
self.do_save_QC_images = self.cfg['leafmachine']['landmark_detector_armature']['do_save_QC_images']
|
987 |
+
self.do_show_final_images = self.cfg['leafmachine']['landmark_detector_armature']['do_show_final_images']
|
988 |
+
self.do_save_final_images = self.cfg['leafmachine']['landmark_detector_armature']['do_save_final_images']
|
989 |
+
|
990 |
+
def init_lists_dicts(self):
|
991 |
+
# Initialize all lists and dictionaries
|
992 |
+
self.classes = {}
|
993 |
+
self.points_list = []
|
994 |
+
self.image = []
|
995 |
+
|
996 |
+
|
997 |
+
self.ordered_middle = []
|
998 |
+
|
999 |
+
self.midvein_fit = []
|
1000 |
+
self.midvein_fit_points = []
|
1001 |
+
|
1002 |
+
self.outer_right = []
|
1003 |
+
self.outer_left = []
|
1004 |
+
|
1005 |
+
# self.ordered_outer_left = []
|
1006 |
+
# self.ordered_outer_right = []
|
1007 |
+
|
1008 |
+
self.tip = []
|
1009 |
+
|
1010 |
+
self.apex_left = []
|
1011 |
+
self.apex_right = []
|
1012 |
+
self.apex_center = []
|
1013 |
+
|
1014 |
+
|
1015 |
+
self.base_left = []
|
1016 |
+
self.base_right = []
|
1017 |
+
self.base_center = []
|
1018 |
+
self.lamina_base = []
|
1019 |
+
self.width_left = []
|
1020 |
+
self.width_right = []
|
1021 |
+
|
1022 |
+
def get_final(self):
|
1023 |
+
self.image_final = np.hstack((self.image, self.image_angles))
|
1024 |
+
return self.image_final
|
1025 |
+
|
1026 |
+
def euclidean_distance(p1, p2):
|
1027 |
+
return math.sqrt((p1[0] - p2[0])**2 + (p1[1] - p2[1])**2)
|
1028 |
+
|
1029 |
+
def gc(color):
|
1030 |
+
colors = {
|
1031 |
+
'red': (0, 0, 255),
|
1032 |
+
'green': (0, 255, 0),
|
1033 |
+
'blue': (255, 0, 0),
|
1034 |
+
'yellow': (0, 255, 255),
|
1035 |
+
'pink': (255, 0, 255),
|
1036 |
+
'cyan': (255, 255, 0),
|
1037 |
+
'black': (0, 0, 0),
|
1038 |
+
'white': (255, 255, 255),
|
1039 |
+
'gray': (128, 128, 128),
|
1040 |
+
'orange': (0, 165, 255),
|
1041 |
+
'purple': (128, 0, 128),
|
1042 |
+
'lightpink': (203, 192, 255),
|
1043 |
+
'brown': (42, 42, 165),
|
1044 |
+
'navy': (128, 0, 0),
|
1045 |
+
'teal': (128, 128, 0),
|
1046 |
+
}
|
1047 |
+
return colors.get(color.lower(), (0, 0, 0))
|
vouchervision/component_detector/color_profiles/ColorProfile__LANDMARK.csv
ADDED
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
apex_angle,255,178,54
|
2 |
+
base_angle,228,255,54
|
3 |
+
lamina_base,52,240,233
|
4 |
+
lamina_tip,246,33,255
|
5 |
+
lamina_width,35,44,255
|
6 |
+
lobe_tip,229,237,46
|
7 |
+
midvein_trace,246,33,255
|
8 |
+
petiole_tip,255,33,42
|
9 |
+
petiole_trace,255,33,42
|
vouchervision/component_detector/color_profiles/ColorProfile__LANDMARK_ARM.csv
ADDED
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
1 |
+
tip,35,44,255
|
2 |
+
middle,228,255,54
|
3 |
+
outer,52,240,233
|
4 |
+
,,,
|
vouchervision/component_detector/color_profiles/ColorProfile__PLANT.csv
ADDED
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
Leaf_WHOLE,0,255,55,00ff37
|
2 |
+
Leaf_PARTIAL,0,255,250,69fffc
|
3 |
+
Leaflet,255,203,0,ffcb00
|
4 |
+
Seed_Fruit_ONE,252,255,0,fcff00
|
5 |
+
Seed_Fruit_MANY,0,0,0,0
|
6 |
+
Flower_ONE,255,52,255,ff34ff
|
7 |
+
Flower_MANY,154,0,255,9a00ff
|
8 |
+
Bud,255,0,9,ff0009
|
9 |
+
Specimen,0,0,0,ceffc4
|
10 |
+
Roots,255,134,0,ff8600
|
11 |
+
Wood,144,22,22,901616
|