Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -1,72 +1,118 @@
|
|
1 |
-
import os, base64
|
2 |
import gradio as gr
|
3 |
import pandas as pd
|
4 |
from pathlib import Path
|
5 |
from typing import Dict, List, Tuple
|
|
|
|
|
6 |
|
7 |
-
# --- load & encode logo so it can't be clicked/downloaded ---
|
8 |
-
LOGO_PATH = Path("rowsquared-logo-large.png")
|
9 |
-
with open(LOGO_PATH, "rb") as f:
|
10 |
-
logo_b64 = base64.b64encode(f.read()).decode()
|
11 |
-
|
12 |
-
# --- load your ISCO hierarchy and build nested dict ---
|
13 |
PROCESSED_DATA_DIR = Path(".")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
14 |
df_isco = (
|
15 |
pd.read_excel(
|
16 |
PROCESSED_DATA_DIR / "isco_imperfect.xlsx",
|
17 |
converters={"major": str, "sub_major": str, "minor": str, "unit": str},
|
18 |
-
)[["major_label","sub_major_label","minor_label","unit_label"]]
|
19 |
-
.dropna()
|
|
|
|
|
20 |
)
|
21 |
-
|
|
|
|
|
22 |
for _, r in df_isco.iterrows():
|
23 |
-
hierarchy.setdefault(r.major_label, {})\
|
24 |
-
.setdefault(r.sub_major_label, {})\
|
25 |
-
.setdefault(r.minor_label, [])\
|
26 |
.append(r.unit_label)
|
|
|
|
|
27 |
for maj in hierarchy:
|
28 |
for sub in hierarchy[maj]:
|
29 |
for mn in hierarchy[maj][sub]:
|
30 |
-
hierarchy[maj][sub][mn] = sorted(dict.fromkeys(hierarchy[maj][sub][mn]))
|
31 |
-
|
32 |
-
|
33 |
-
def
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
-
|
58 |
-
records
|
59 |
-
|
60 |
-
|
61 |
-
|
62 |
-
|
63 |
-
|
64 |
-
|
65 |
-
|
66 |
-
|
67 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
68 |
return (
|
69 |
-
|
70 |
status_text(i),
|
71 |
gr.update(choices=maj_c, value=maj),
|
72 |
gr.update(choices=sub_c, value=sub),
|
@@ -74,153 +120,176 @@ def load_record(i):
|
|
74 |
gr.update(choices=un_c, value=un),
|
75 |
)
|
76 |
|
77 |
-
|
78 |
-
|
79 |
-
|
80 |
-
|
81 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
82 |
return (
|
83 |
-
gr.update(choices=majors(),
|
84 |
-
gr.update(choices=sub_c,
|
85 |
-
gr.update(choices=mn_c,
|
86 |
-
gr.update(choices=un_c,
|
87 |
status_text(i),
|
88 |
)
|
89 |
|
90 |
-
def on_sub_change(new_sub,i,
|
91 |
-
mn_c = minors(
|
92 |
-
|
93 |
-
|
94 |
-
|
|
|
|
|
95 |
return (
|
96 |
-
gr.update(choices=submajors(
|
97 |
-
gr.update(choices=mn_c,
|
98 |
-
gr.update(choices=un_c,
|
99 |
status_text(i),
|
100 |
)
|
101 |
|
102 |
-
def on_minor_change(
|
103 |
-
un_c = units(
|
104 |
-
|
105 |
-
records.loc[i,"
|
|
|
106 |
return (
|
107 |
-
gr.update(choices=minors(
|
108 |
-
gr.update(choices=un_c,
|
109 |
status_text(i),
|
110 |
)
|
111 |
|
112 |
-
def on_unit_change(
|
113 |
-
un_c=units(
|
114 |
-
|
115 |
-
|
116 |
-
records.loc[i,"
|
117 |
-
|
118 |
-
|
119 |
-
|
120 |
-
def
|
121 |
-
|
122 |
-
|
123 |
-
|
124 |
-
|
125 |
-
|
126 |
-
|
127 |
-
return (j,)+load_record(j)
|
128 |
-
|
129 |
-
def download_csv():
|
130 |
-
out = PROCESSED_DATA_DIR/"annotated_output.csv"
|
131 |
-
records.to_csv(out,index=False)
|
132 |
-
return str(out)
|
133 |
-
|
134 |
-
# --- build with login gate ---
|
135 |
-
def build_gradio_app():
|
136 |
-
USER = os.getenv("APP_USER","")
|
137 |
-
PWD = os.getenv("APP_PASS","")
|
138 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
139 |
with gr.Blocks() as demo:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
140 |
gr.HTML("""
|
141 |
<style>
|
142 |
-
|
143 |
-
|
144 |
-
|
145 |
-
|
146 |
-
|
147 |
-
|
148 |
-
|
149 |
-
|
150 |
-
|
151 |
-
|
152 |
-
|
153 |
-
|
154 |
-
|
155 |
-
with gr.Column(elem_id="app_panel", visible=False) as app_panel:
|
156 |
-
gr.HTML(f'<img src="data:image/png;base64,{logo_b64}" width="160" '
|
157 |
-
'style="pointer-events:none;user-select:none;display:block;margin:auto;"/>')
|
158 |
-
gr.Markdown("# ISCO Annotation", elem_id="title")
|
159 |
-
idx_state = gr.State(0)
|
160 |
record_md = gr.Markdown()
|
161 |
status_md = gr.Markdown()
|
162 |
-
prev_btn = gr.Button("⬅ Previous")
|
163 |
-
next_btn = gr.Button("✅ Next")
|
164 |
-
major_radio = gr.Radio("Level 1 Major", choices=[], interactive=True)
|
165 |
-
sub_radio = gr.Radio("Level 2 Sub-major",choices=[], interactive=True)
|
166 |
-
minor_radio = gr.Radio("Level 3 Minor", choices=[], interactive=True)
|
167 |
-
unit_radio = gr.Radio("Level 4 Unit", choices=[], interactive=True)
|
168 |
-
download_btn = gr.Button("📥 Download CSV")
|
169 |
-
download_file = gr.File(visible=False)
|
170 |
-
|
171 |
-
# --- login handler ---
|
172 |
-
def check_creds(u,p):
|
173 |
-
if u==USER and p==PWD:
|
174 |
-
return (
|
175 |
-
gr.update(visible=False), # hide login
|
176 |
-
gr.update(visible=True), # show app
|
177 |
-
gr.update(visible=False), # hide error
|
178 |
-
"" # clear user_in
|
179 |
-
)
|
180 |
-
else:
|
181 |
-
return (None, None,
|
182 |
-
gr.update(visible=True,value="❌ Bad credentials"),
|
183 |
-
"")
|
184 |
-
|
185 |
-
login_btn.click(
|
186 |
-
check_creds,
|
187 |
-
inputs=[user_in,pass_in],
|
188 |
-
outputs=[login_panel,app_panel,login_msg,user_in]
|
189 |
-
)
|
190 |
|
191 |
-
|
192 |
-
|
193 |
-
|
194 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
195 |
|
196 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
197 |
inputs=[idx_state],
|
198 |
-
outputs=[idx_state,record_md,status_md,
|
199 |
-
|
200 |
-
prev_btn.click(lambda i: save_and_jump(i,"prev"),
|
201 |
inputs=[idx_state],
|
202 |
-
outputs=[idx_state,record_md,status_md,
|
203 |
-
|
204 |
-
|
205 |
-
major_radio.change(
|
206 |
-
|
207 |
-
|
208 |
-
|
209 |
-
|
210 |
-
|
211 |
-
|
212 |
-
|
213 |
-
|
214 |
-
|
215 |
-
|
216 |
-
|
217 |
-
|
218 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
219 |
lambda: gr.update(visible=True), None, [download_file]
|
220 |
)
|
221 |
|
222 |
return demo
|
223 |
|
|
|
224 |
if __name__=="__main__":
|
225 |
demo = build_gradio_app()
|
226 |
demo.queue().launch(
|
@@ -229,4 +298,5 @@ if __name__=="__main__":
|
|
229 |
auth=(os.getenv("APP_USER",""), os.getenv("APP_PASS","")),
|
230 |
server_name="0.0.0.0", # optional, but explicit
|
231 |
server_port=int(os.getenv("PORT", 7860)),
|
232 |
-
)
|
|
|
|
|
|
1 |
import gradio as gr
|
2 |
import pandas as pd
|
3 |
from pathlib import Path
|
4 |
from typing import Dict, List, Tuple
|
5 |
+
import os
|
6 |
+
import base64
|
7 |
|
|
|
|
|
|
|
|
|
|
|
|
|
8 |
PROCESSED_DATA_DIR = Path(".")
|
9 |
+
# Embed logo as a base64 data URI to avoid Gradio toolbar interactions
|
10 |
+
logo_path = "rowsquared-logo-large.png"
|
11 |
+
with open(logo_path, "rb") as f:
|
12 |
+
logo_b64 = base64.b64encode(f.read()).decode("utf-8")
|
13 |
+
|
14 |
+
|
15 |
+
|
16 |
+
# ----------------------------
|
17 |
+
# Data loading & preprocessing
|
18 |
+
# ----------------------------
|
19 |
df_isco = (
|
20 |
pd.read_excel(
|
21 |
PROCESSED_DATA_DIR / "isco_imperfect.xlsx",
|
22 |
converters={"major": str, "sub_major": str, "minor": str, "unit": str},
|
23 |
+
)[["major_label", "sub_major_label", "minor_label", "unit_label"]]
|
24 |
+
.dropna()
|
25 |
+
.drop_duplicates()
|
26 |
+
.reset_index(drop=True)
|
27 |
)
|
28 |
+
|
29 |
+
# Build nested hierarchy dict: {major: {sub: {minor: [units]}}}
|
30 |
+
hierarchy: Dict[str, Dict[str, Dict[str, List[str]]]] = {}
|
31 |
for _, r in df_isco.iterrows():
|
32 |
+
hierarchy.setdefault(r.major_label, {}) \
|
33 |
+
.setdefault(r.sub_major_label, {}) \
|
34 |
+
.setdefault(r.minor_label, []) \
|
35 |
.append(r.unit_label)
|
36 |
+
|
37 |
+
# Ensure uniqueness & sorting at leaf lists
|
38 |
for maj in hierarchy:
|
39 |
for sub in hierarchy[maj]:
|
40 |
for mn in hierarchy[maj][sub]:
|
41 |
+
hierarchy[maj][sub][mn] = sorted(list(dict.fromkeys(hierarchy[maj][sub][mn])))
|
42 |
+
|
43 |
+
# Fast helpers for children
|
44 |
+
def majors() -> List[str]:
|
45 |
+
return sorted(hierarchy.keys())
|
46 |
+
|
47 |
+
def submajors(maj: str) -> List[str]:
|
48 |
+
return sorted(hierarchy.get(maj, {}).keys())
|
49 |
+
|
50 |
+
def minors(maj: str, sub: str) -> List[str]:
|
51 |
+
return sorted(hierarchy.get(maj, {}).get(sub, {}).keys())
|
52 |
+
|
53 |
+
def units(maj: str, sub: str, mn: str) -> List[str]:
|
54 |
+
return hierarchy.get(maj, {}).get(sub, {}).get(mn, [])
|
55 |
+
|
56 |
+
# ----------------------------
|
57 |
+
# Records to annotate
|
58 |
+
# ----------------------------
|
59 |
+
records = pd.read_excel(PROCESSED_DATA_DIR / "isco_predictions.xlsx").copy()
|
60 |
+
for col in ["major_label", "sub_major_label", "minor_label", "unit_label"]:
|
61 |
+
if col not in records:
|
62 |
+
records[col] = ""
|
63 |
+
|
64 |
+
if "annotated" not in records:
|
65 |
+
records["annotated"] = False
|
66 |
+
|
67 |
+
# ensure not views
|
68 |
+
for col in ["major_label", "sub_major_label", "minor_label", "unit_label", "annotated"]:
|
69 |
+
records[col] = records[col].copy()
|
70 |
+
|
71 |
+
records.reset_index(drop=True, inplace=True)
|
72 |
+
|
73 |
+
# -----------------------------------
|
74 |
+
# Core logic: clamp & state management
|
75 |
+
# -----------------------------------
|
76 |
+
def clamp_path(maj: str, sub: str, mn: str, un: str
|
77 |
+
) -> Tuple[str, str, str, str, List[str], List[str], List[str], List[str]]:
|
78 |
+
"""Return a valid (maj, sub, mn, un) tuple + their choices lists.
|
79 |
+
Only replace a level if it's invalid for the hierarchy."""
|
80 |
+
maj_choices = majors()
|
81 |
+
if maj not in maj_choices:
|
82 |
+
maj = maj_choices[0] if maj_choices else ""
|
83 |
+
|
84 |
+
sub_choices = submajors(maj) if maj else []
|
85 |
+
if sub not in sub_choices:
|
86 |
+
sub = sub_choices[0] if sub_choices else ""
|
87 |
+
|
88 |
+
mn_choices = minors(maj, sub) if sub else []
|
89 |
+
if mn not in mn_choices:
|
90 |
+
mn = mn_choices[0] if mn_choices else ""
|
91 |
+
|
92 |
+
un_choices = units(maj, sub, mn) if mn else []
|
93 |
+
if un not in un_choices:
|
94 |
+
un = un_choices[0] if un_choices else ""
|
95 |
+
|
96 |
+
return maj, sub, mn, un, maj_choices, sub_choices, mn_choices, un_choices
|
97 |
+
|
98 |
+
def save_record(i: int, maj: str, sub: str, mn: str, un: str) -> None:
|
99 |
+
records.loc[i, ["major_label", "sub_major_label", "minor_label", "unit_label"]] = [maj, sub, mn, un]
|
100 |
+
records.loc[i, "annotated"] = True
|
101 |
+
|
102 |
+
def status_text(i: int) -> str:
|
103 |
+
return f"**Status**: {'✅ Annotated' if records.loc[i, 'annotated'] else '❌ Not Annotated'}"
|
104 |
+
|
105 |
+
def load_record(i: int):
|
106 |
+
rec = records.loc[i]
|
107 |
+
maj, sub, mn, un, maj_c, sub_c, mn_c, un_c = clamp_path(
|
108 |
+
rec["major_label"], rec["sub_major_label"], rec["minor_label"], rec["unit_label"]
|
109 |
+
)
|
110 |
+
# Persist clamped values back (only if changed)
|
111 |
+
save_record(i, maj, sub, mn, un)
|
112 |
+
|
113 |
+
record_md = f"## Occupation: {rec['occupation_title_main']}\n## Industry: {rec['industry_title_main']}"
|
114 |
return (
|
115 |
+
record_md,
|
116 |
status_text(i),
|
117 |
gr.update(choices=maj_c, value=maj),
|
118 |
gr.update(choices=sub_c, value=sub),
|
|
|
120 |
gr.update(choices=un_c, value=un),
|
121 |
)
|
122 |
|
123 |
+
# ---------------------
|
124 |
+
# Event handler helpers
|
125 |
+
# ---------------------
|
126 |
+
def on_major_change(new_major: str, i: int):
|
127 |
+
sub_c = submajors(new_major)
|
128 |
+
sub = sub_c[0] if sub_c else ""
|
129 |
+
mn_c = minors(new_major, sub) if sub else []
|
130 |
+
mn = mn_c[0] if mn_c else ""
|
131 |
+
un_c = units(new_major, sub, mn) if mn else []
|
132 |
+
un = un_c[0] if un_c else ""
|
133 |
+
save_record(i, new_major, sub, mn, un)
|
134 |
return (
|
135 |
+
gr.update(choices=majors(), value=new_major),
|
136 |
+
gr.update(choices=sub_c, value=sub),
|
137 |
+
gr.update(choices=mn_c, value=mn),
|
138 |
+
gr.update(choices=un_c, value=un),
|
139 |
status_text(i),
|
140 |
)
|
141 |
|
142 |
+
def on_sub_change(new_sub: str, i: int, major: str):
|
143 |
+
mn_c = minors(major, new_sub)
|
144 |
+
mn = mn_c[0] if mn_c else ""
|
145 |
+
un_c = units(major, new_sub, mn) if mn else []
|
146 |
+
un = un_c[0] if un_c else ""
|
147 |
+
records.loc[i, ["sub_major_label", "minor_label", "unit_label"]] = [new_sub, mn, un]
|
148 |
+
records.loc[i, "annotated"] = True
|
149 |
return (
|
150 |
+
gr.update(choices=submajors(major), value=new_sub),
|
151 |
+
gr.update(choices=mn_c, value=mn),
|
152 |
+
gr.update(choices=un_c, value=un),
|
153 |
status_text(i),
|
154 |
)
|
155 |
|
156 |
+
def on_minor_change(new_minor: str, i: int, major: str, sub: str):
|
157 |
+
un_c = units(major, sub, new_minor)
|
158 |
+
un = un_c[0] if un_c else ""
|
159 |
+
records.loc[i, ["minor_label", "unit_label"]] = [new_minor, un]
|
160 |
+
records.loc[i, "annotated"] = True
|
161 |
return (
|
162 |
+
gr.update(choices=minors(major, sub), value=new_minor),
|
163 |
+
gr.update(choices=un_c, value=un),
|
164 |
status_text(i),
|
165 |
)
|
166 |
|
167 |
+
def on_unit_change(new_unit: str, i: int, major: str, sub: str, mn: str):
|
168 |
+
un_c = units(major, sub, mn)
|
169 |
+
if new_unit not in un_c:
|
170 |
+
new_unit = un_c[0] if un_c else ""
|
171 |
+
records.loc[i, "unit_label"] = new_unit
|
172 |
+
records.loc[i, "annotated"] = True
|
173 |
+
return gr.update(choices=un_c, value=new_unit), status_text(i)
|
174 |
+
|
175 |
+
def go_next(i: int) -> int:
|
176 |
+
return (i + 1) % len(records)
|
177 |
+
|
178 |
+
def go_prev(i: int) -> int:
|
179 |
+
return (i - 1) % len(records)
|
180 |
+
|
181 |
+
# ---- NAVIGATION: save + move + reload in ONE callback ----
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
182 |
|
183 |
+
def save_and_jump(i: int, direction: str):
|
184 |
+
# Final safety net: clamp and persist whatever is currently stored
|
185 |
+
rec = records.loc[i]
|
186 |
+
maj, sub, mn, un, *_ = clamp_path(
|
187 |
+
rec["major_label"], rec["sub_major_label"], rec["minor_label"], rec["unit_label"]
|
188 |
+
)
|
189 |
+
save_record(i, maj, sub, mn, un)
|
190 |
+
new_i = go_next(i) if direction == "next" else go_prev(i)
|
191 |
+
return (new_i,) + load_record(new_i)
|
192 |
+
|
193 |
+
def download_annotations() -> str:
|
194 |
+
path = PROCESSED_DATA_DIR / "annotated_output.csv"
|
195 |
+
records.to_csv(path, index=False)
|
196 |
+
return str(path)
|
197 |
+
|
198 |
+
# --------------
|
199 |
+
# Build the UI
|
200 |
+
# --------------
|
201 |
+
def build_gradio_app():
|
202 |
with gr.Blocks() as demo:
|
203 |
+
with gr.Row():
|
204 |
+
with gr.Column(scale=1):
|
205 |
+
# Static logo, non-interactive
|
206 |
+
gr.HTML(
|
207 |
+
f'<img src="data:image/png;base64,{logo_b64}" width="200" style="pointer-events:none; user-select:none; display:block;" />'
|
208 |
+
)
|
209 |
+
with gr.Row():
|
210 |
+
gr.Markdown("# ISCO Annotation", elem_id="isco-title")
|
211 |
gr.HTML("""
|
212 |
<style>
|
213 |
+
#isco-title {
|
214 |
+
text-align: center;
|
215 |
+
width: 100%;
|
216 |
+
margin: 0.5em 0;
|
217 |
+
}
|
218 |
+
footer { display: none !important; }
|
219 |
+
.gradio-container .api-link, .gradio-container .share-link { display: none !important; }
|
220 |
+
</style>
|
221 |
+
""")
|
222 |
+
|
223 |
+
idx_state = gr.State(0)
|
224 |
+
|
225 |
+
with gr.Group():
|
|
|
|
|
|
|
|
|
|
|
226 |
record_md = gr.Markdown()
|
227 |
status_md = gr.Markdown()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
228 |
|
229 |
+
with gr.Row():
|
230 |
+
prev_btn = gr.Button("⬅ Previous")
|
231 |
+
next_btn = gr.Button("✅ Next")
|
232 |
+
|
233 |
+
with gr.Row():
|
234 |
+
with gr.Column():
|
235 |
+
major_radio = gr.Radio(label="Level 1: Major", choices=[], interactive=True)
|
236 |
+
with gr.Column():
|
237 |
+
sub_radio = gr.Radio(label="Level 2: Sub-major", choices=[], interactive=True)
|
238 |
+
with gr.Column():
|
239 |
+
minor_radio = gr.Radio(label="Level 3: Minor", choices=[], interactive=True)
|
240 |
+
with gr.Column():
|
241 |
+
unit_radio = gr.Radio(label="Level 4: Unit", choices=[], interactive=True)
|
242 |
+
|
243 |
+
download_btn = gr.Button("📥 Download Annotations")
|
244 |
+
download_file = gr.File(label="Annotated CSV", visible=False)
|
245 |
|
246 |
+
# Initial load
|
247 |
+
demo.load(
|
248 |
+
lambda: (0,) + load_record(0),
|
249 |
+
outputs=[idx_state, record_md, status_md, major_radio, sub_radio, minor_radio, unit_radio],
|
250 |
+
)
|
251 |
+
|
252 |
+
next_btn.click(lambda i: save_and_jump(i, "next"),
|
253 |
inputs=[idx_state],
|
254 |
+
outputs=[idx_state, record_md, status_md, major_radio, sub_radio, minor_radio, unit_radio])
|
255 |
+
|
256 |
+
prev_btn.click(lambda i: save_and_jump(i, "prev"),
|
257 |
inputs=[idx_state],
|
258 |
+
outputs=[idx_state, record_md, status_md, major_radio, sub_radio, minor_radio, unit_radio])
|
259 |
+
|
260 |
+
# Change handlers (also update status)
|
261 |
+
major_radio.change(
|
262 |
+
on_major_change,
|
263 |
+
inputs=[major_radio, idx_state],
|
264 |
+
outputs=[major_radio, sub_radio, minor_radio, unit_radio, status_md],
|
265 |
+
)
|
266 |
+
|
267 |
+
sub_radio.change(
|
268 |
+
on_sub_change,
|
269 |
+
inputs=[sub_radio, idx_state, major_radio],
|
270 |
+
outputs=[sub_radio, minor_radio, unit_radio, status_md],
|
271 |
+
)
|
272 |
+
|
273 |
+
minor_radio.change(
|
274 |
+
on_minor_change,
|
275 |
+
inputs=[minor_radio, idx_state, major_radio, sub_radio],
|
276 |
+
outputs=[minor_radio, unit_radio, status_md],
|
277 |
+
)
|
278 |
+
|
279 |
+
unit_radio.change(
|
280 |
+
on_unit_change,
|
281 |
+
inputs=[unit_radio, idx_state, major_radio, sub_radio, minor_radio],
|
282 |
+
outputs=[unit_radio, status_md],
|
283 |
+
)
|
284 |
+
|
285 |
+
# Download
|
286 |
+
download_btn.click(download_annotations, outputs=[download_file]).then(
|
287 |
lambda: gr.update(visible=True), None, [download_file]
|
288 |
)
|
289 |
|
290 |
return demo
|
291 |
|
292 |
+
|
293 |
if __name__=="__main__":
|
294 |
demo = build_gradio_app()
|
295 |
demo.queue().launch(
|
|
|
298 |
auth=(os.getenv("APP_USER",""), os.getenv("APP_PASS","")),
|
299 |
server_name="0.0.0.0", # optional, but explicit
|
300 |
server_port=int(os.getenv("PORT", 7860)),
|
301 |
+
)
|
302 |
+
|