Spaces:
No application file
No application file
Upload 6 files
Browse files- Dockerfile +22 -0
- README.MD +98 -0
- config.py +5 -0
- favicon.ico +0 -0
- main.py +187 -0
- requirements.txt +8 -0
Dockerfile
ADDED
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
FROM python:3.11-slim
|
2 |
+
|
3 |
+
WORKDIR /code
|
4 |
+
|
5 |
+
COPY requirements.txt ./
|
6 |
+
|
7 |
+
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
|
8 |
+
|
9 |
+
RUN useradd -m -u 1000 user
|
10 |
+
|
11 |
+
USER user
|
12 |
+
|
13 |
+
ENV HOME=/home/user \
|
14 |
+
PATH=/home/user/.local/bin:$PATH
|
15 |
+
|
16 |
+
WORKDIR $HOME/app
|
17 |
+
|
18 |
+
COPY --chown=user . $HOME/app
|
19 |
+
|
20 |
+
COPY --chown=user config/config.toml $HOME/app/.streamlit/config.toml
|
21 |
+
|
22 |
+
CMD ["streamlit", "run", "main.py", "--server.port=7860", "--server.address=0.0.0.0"]
|
README.MD
ADDED
@@ -0,0 +1,98 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Sparrow UI
|
2 |
+
|
3 |
+
## Description
|
4 |
+
|
5 |
+
[Sparrow UI](https://katanaml-org-sparrow-ui.hf.space) module implements UI logic with Streamlit for document data annotation, model training/tuning and document data extraction.
|
6 |
+
|
7 |
+
#### Dashboard UI:
|
8 |
+
|
9 |
+
![Sparrow Dashboard](https://github.com/katanaml/sparrow/blob/main/sparrow-ui/donut/assets/dashboard.png)
|
10 |
+
|
11 |
+
#### Annotation UI:
|
12 |
+
|
13 |
+
![Sparrow Annotation](https://github.com/katanaml/sparrow/blob/main/sparrow-ui/donut/assets/annotation.png)
|
14 |
+
|
15 |
+
#### Inference UI:
|
16 |
+
|
17 |
+
![Sparrow Inference](https://github.com/katanaml/sparrow/blob/main/sparrow-ui/donut/assets/inference.png)
|
18 |
+
|
19 |
+
#### Review UI:
|
20 |
+
|
21 |
+
![Sparrow Review](https://github.com/katanaml/sparrow/blob/main/sparrow-ui/donut/assets/review.png)
|
22 |
+
|
23 |
+
#### Labels/Groups CRUD UI:
|
24 |
+
|
25 |
+
![Sparrow CRUD](https://github.com/katanaml/sparrow/blob/main/sparrow-ui/donut/assets/crud.png)
|
26 |
+
|
27 |
+
## Instructions
|
28 |
+
|
29 |
+
1. Install
|
30 |
+
|
31 |
+
Streamlit docs:
|
32 |
+
https://docs.streamlit.io/library/get-started/installation
|
33 |
+
|
34 |
+
```
|
35 |
+
pip install -r requirements.txt
|
36 |
+
```
|
37 |
+
|
38 |
+
2. Run
|
39 |
+
|
40 |
+
```
|
41 |
+
streamlit run main.py
|
42 |
+
```
|
43 |
+
|
44 |
+
After annotating files, click Export Labels button to produce JSON mapping with key/value pairs
|
45 |
+
|
46 |
+
## Run in Docker container
|
47 |
+
|
48 |
+
1. Build Docker image
|
49 |
+
|
50 |
+
```
|
51 |
+
docker build --tag katanaml/sparrow-ui .
|
52 |
+
```
|
53 |
+
|
54 |
+
2. Run Docker container
|
55 |
+
|
56 |
+
```
|
57 |
+
docker run -it --name sparrow-ui -p 7860:7860 katanaml/sparrow-ui:latest
|
58 |
+
```
|
59 |
+
|
60 |
+
## Deploy to Hugging Face Spaces
|
61 |
+
|
62 |
+
1. Create new Space - https://huggingface.co/spaces
|
63 |
+
|
64 |
+
2. Clone Space repo and init Git LFS. Copy Sparrow UI files. We are using config.toml from config folder, when deploying Docker container on Hugging Face Spaces, it can't read from standard .streamlit folder
|
65 |
+
|
66 |
+
```
|
67 |
+
git lfs install
|
68 |
+
```
|
69 |
+
|
70 |
+
3. Add these files to LFS config
|
71 |
+
|
72 |
+
```
|
73 |
+
git lfs track "assets/ab.png"
|
74 |
+
git lfs track "docs/image/receipt_00001.png"
|
75 |
+
git lfs track "docs/image/receipt_00002.png"
|
76 |
+
git lfs track "docs/image/receipt_00003.png"
|
77 |
+
git lfs track "docs/image/w_invoice1.png"
|
78 |
+
```
|
79 |
+
|
80 |
+
4. Commit and push code to Hugging Face Space, follow Space instructions. Docker container will be deployed automatically. Space example:
|
81 |
+
|
82 |
+
```
|
83 |
+
https://huggingface.co/spaces/katanaml-org/sparrow-ui
|
84 |
+
```
|
85 |
+
|
86 |
+
5. Sparrow UI will be accessible by URL, you can get it from Hugging Face Space info. For example:
|
87 |
+
|
88 |
+
```
|
89 |
+
https://katanaml-org-sparrow-ui.hf.space
|
90 |
+
```
|
91 |
+
|
92 |
+
## Author
|
93 |
+
|
94 |
+
[Katana ML](https://katanaml.io), [Andrej Baranovskij](https://github.com/abaranovskis-redsamurai)
|
95 |
+
|
96 |
+
## License
|
97 |
+
|
98 |
+
Licensed under the Apache License, Version 2.0. Copyright 2020-2023 Katana ML, Andrej Baranovskij. [Copy of the license](https://github.com/katanaml/sparrow/blob/main/LICENSE).
|
config.py
ADDED
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
class Settings():
|
2 |
+
sparrow_key = ""
|
3 |
+
|
4 |
+
|
5 |
+
settings = Settings()
|
favicon.ico
ADDED
main.py
ADDED
@@ -0,0 +1,187 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import streamlit as st
|
2 |
+
from streamlit_option_menu import option_menu
|
3 |
+
from tools.utilities import load_css
|
4 |
+
import json
|
5 |
+
|
6 |
+
from views.dashboard import Dashboard
|
7 |
+
from views.data_annotation import DataAnnotation
|
8 |
+
from views.model_training import ModelTraining
|
9 |
+
from views.model_tuning import ModelTuning
|
10 |
+
from views.data_inference import DataInference
|
11 |
+
from views.setup import Setup
|
12 |
+
from views.data_review import DataReview
|
13 |
+
from views.about import About
|
14 |
+
|
15 |
+
import streamlit_javascript as st_js
|
16 |
+
|
17 |
+
st.set_page_config(
|
18 |
+
page_title="Sparrow",
|
19 |
+
page_icon="favicon.ico",
|
20 |
+
layout="wide"
|
21 |
+
)
|
22 |
+
|
23 |
+
load_css()
|
24 |
+
|
25 |
+
|
26 |
+
class Model:
|
27 |
+
menuTitle = "Sparrow"
|
28 |
+
option1 = "Dashboard"
|
29 |
+
option2 = "Data Annotation"
|
30 |
+
option3 = "Model Training"
|
31 |
+
option4 = "Model Tuning"
|
32 |
+
option5 = "Inference"
|
33 |
+
option6 = "Data Review"
|
34 |
+
option7 = "Setup"
|
35 |
+
option8 = "About"
|
36 |
+
|
37 |
+
menuIcon = "menu-up"
|
38 |
+
icon1 = "speedometer"
|
39 |
+
icon2 = "activity"
|
40 |
+
icon3 = "motherboard"
|
41 |
+
icon4 = "graph-up-arrow"
|
42 |
+
icon5 = "journal-arrow-down"
|
43 |
+
icon6 = "droplet"
|
44 |
+
icon7 = "clipboard-data"
|
45 |
+
icon8 = "chat"
|
46 |
+
|
47 |
+
|
48 |
+
def view(model):
|
49 |
+
with st.sidebar:
|
50 |
+
menuItem = option_menu(model.menuTitle,
|
51 |
+
[model.option1, model.option2, model.option5, model.option6, model.option7, model.option8],
|
52 |
+
icons=[model.icon1, model.icon2, model.icon5, model.icon6, model.icon7, model.icon8],
|
53 |
+
menu_icon=model.menuIcon,
|
54 |
+
default_index=0,
|
55 |
+
styles={
|
56 |
+
"container": {"padding": "5!important", "background-color": "#fafafa"},
|
57 |
+
"icon": {"color": "black", "font-size": "25px"},
|
58 |
+
"nav-link": {"font-size": "16px", "text-align": "left", "margin": "0px",
|
59 |
+
"--hover-color": "#eee"},
|
60 |
+
"nav-link-selected": {"background-color": "#037ffc"},
|
61 |
+
})
|
62 |
+
|
63 |
+
if menuItem == model.option1:
|
64 |
+
Dashboard().view(Dashboard.Model())
|
65 |
+
logout_widget()
|
66 |
+
|
67 |
+
if menuItem == model.option2:
|
68 |
+
if 'ui_width' not in st.session_state or 'device_type' not in st.session_state or 'device_width' not in st.session_state:
|
69 |
+
# Get UI width
|
70 |
+
ui_width = st_js.st_javascript("window.innerWidth", key="ui_width_comp")
|
71 |
+
device_width = st_js.st_javascript("window.screen.width", key="device_width_comp")
|
72 |
+
|
73 |
+
if ui_width > 0 and device_width > 0:
|
74 |
+
# Add 20% of current screen width to compensate for the sidebar
|
75 |
+
ui_width = round(ui_width + (20 * ui_width / 100))
|
76 |
+
|
77 |
+
if device_width > 768:
|
78 |
+
device_type = 'desktop'
|
79 |
+
else:
|
80 |
+
device_type = 'mobile'
|
81 |
+
|
82 |
+
st.session_state['ui_width'] = ui_width
|
83 |
+
st.session_state['device_type'] = device_type
|
84 |
+
st.session_state['device_width'] = device_width
|
85 |
+
|
86 |
+
st.experimental_rerun()
|
87 |
+
else:
|
88 |
+
DataAnnotation().view(DataAnnotation.Model(), st.session_state['ui_width'], st.session_state['device_type'],
|
89 |
+
st.session_state['device_width'])
|
90 |
+
logout_widget()
|
91 |
+
|
92 |
+
if menuItem == model.option3:
|
93 |
+
ModelTraining().view(ModelTraining.Model())
|
94 |
+
logout_widget()
|
95 |
+
|
96 |
+
if menuItem == model.option4:
|
97 |
+
ModelTuning().view(ModelTuning.Model())
|
98 |
+
logout_widget()
|
99 |
+
|
100 |
+
if menuItem == model.option5:
|
101 |
+
if 'ui_width' not in st.session_state or 'device_type' not in st.session_state or 'device_width' not in st.session_state:
|
102 |
+
# Get UI width
|
103 |
+
ui_width = st_js.st_javascript("window.innerWidth", key="ui_width_comp")
|
104 |
+
device_width = st_js.st_javascript("window.screen.width", key="device_width_comp")
|
105 |
+
|
106 |
+
if ui_width > 0 and device_width > 0:
|
107 |
+
# Add 20% of current screen width to compensate for the sidebar
|
108 |
+
ui_width = round(ui_width + (20 * ui_width / 100))
|
109 |
+
|
110 |
+
if device_width > 768:
|
111 |
+
device_type = 'desktop'
|
112 |
+
else:
|
113 |
+
device_type = 'mobile'
|
114 |
+
|
115 |
+
st.session_state['ui_width'] = ui_width
|
116 |
+
st.session_state['device_type'] = device_type
|
117 |
+
st.session_state['device_width'] = device_width
|
118 |
+
|
119 |
+
st.experimental_rerun()
|
120 |
+
else:
|
121 |
+
DataInference().view(DataInference.Model(), st.session_state['ui_width'], st.session_state['device_type'],
|
122 |
+
st.session_state['device_width'])
|
123 |
+
|
124 |
+
logout_widget()
|
125 |
+
|
126 |
+
if menuItem == model.option6:
|
127 |
+
if 'ui_width' not in st.session_state or 'device_type' not in st.session_state or 'device_width' not in st.session_state:
|
128 |
+
# Get UI width
|
129 |
+
ui_width = st_js.st_javascript("window.innerWidth", key="ui_width_comp")
|
130 |
+
device_width = st_js.st_javascript("window.screen.width", key="device_width_comp")
|
131 |
+
|
132 |
+
if ui_width > 0 and device_width > 0:
|
133 |
+
# Add 20% of current screen width to compensate for the sidebar
|
134 |
+
ui_width = round(ui_width + (20 * ui_width / 100))
|
135 |
+
|
136 |
+
if device_width > 768:
|
137 |
+
device_type = 'desktop'
|
138 |
+
else:
|
139 |
+
device_type = 'mobile'
|
140 |
+
|
141 |
+
st.session_state['ui_width'] = ui_width
|
142 |
+
st.session_state['device_type'] = device_type
|
143 |
+
st.session_state['device_width'] = device_width
|
144 |
+
|
145 |
+
st.experimental_rerun()
|
146 |
+
else:
|
147 |
+
DataReview().view(DataReview.Model(), st.session_state['ui_width'], st.session_state['device_type'],
|
148 |
+
st.session_state['device_width'])
|
149 |
+
|
150 |
+
logout_widget()
|
151 |
+
|
152 |
+
if menuItem == model.option7:
|
153 |
+
Setup().view(Setup.Model())
|
154 |
+
logout_widget()
|
155 |
+
|
156 |
+
if menuItem == model.option8:
|
157 |
+
About().view(About.Model())
|
158 |
+
logout_widget()
|
159 |
+
|
160 |
+
|
161 |
+
def logout_widget():
|
162 |
+
with st.sidebar:
|
163 |
+
st.markdown("---")
|
164 |
+
# st.write("User:", "John Doe")
|
165 |
+
st.write("Version:", "2.0.0")
|
166 |
+
# st.button("Logout")
|
167 |
+
# st.markdown("---")
|
168 |
+
|
169 |
+
if 'visitors' not in st.session_state:
|
170 |
+
with open("docs/visitors.json", "r") as f:
|
171 |
+
visitors_json = json.load(f)
|
172 |
+
visitors = visitors_json["meta"]["visitors"]
|
173 |
+
|
174 |
+
visitors += 1
|
175 |
+
visitors_json["meta"]["visitors"] = visitors
|
176 |
+
|
177 |
+
with open("docs/visitors.json", "w") as f:
|
178 |
+
json.dump(visitors_json, f)
|
179 |
+
|
180 |
+
st.session_state['visitors'] = visitors
|
181 |
+
else:
|
182 |
+
visitors = st.session_state['visitors']
|
183 |
+
|
184 |
+
st.write("Counter:", visitors)
|
185 |
+
|
186 |
+
|
187 |
+
view(Model())
|
requirements.txt
ADDED
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
streamlit==1.22.0
|
2 |
+
streamlit_option_menu
|
3 |
+
streamlit-sparrow-labeling==0.1.1
|
4 |
+
streamlit_nested_layout
|
5 |
+
streamlit-javascript
|
6 |
+
natsort
|
7 |
+
streamlit-aggrid==0.3.3
|
8 |
+
pandas<2.0
|