Nightfury16 commited on
Commit
009f7e5
·
1 Parent(s): da36cb1

update app.py

Browse files
Files changed (1) hide show
  1. app.py +134 -259
app.py CHANGED
@@ -25,350 +25,225 @@ VERIFY_FILE = os.path.join(CACHE_DIR, "verifications.csv")
25
  SKIP_FILE = os.path.join(CACHE_DIR, "skipped.csv")
26
  LOCK_FILE = os.path.join(CACHE_DIR, "data.lock")
27
 
 
 
 
 
28
  def sync_pull():
29
  print(f"🔄 Syncing from {DATASET_REPO_ID}...")
30
  token = HF_TOKEN if HF_TOKEN and len(HF_TOKEN) > 5 else None
31
-
32
  for filename in ["annotations.csv", "verifications.csv", "skipped.csv"]:
33
  try:
34
- hf_hub_download(
35
- repo_id=DATASET_REPO_ID,
36
- filename=filename,
37
- repo_type="dataset",
38
- local_dir=CACHE_DIR,
39
- token=token
40
- )
41
  print(f"✅ Loaded {filename}")
42
  except Exception as e:
43
  print(f"ℹ️ {filename} sync info: {e}")
44
 
45
- MAX_IMAGES = 6
46
- THUMB_SIZE = (350, 350)
47
- ROOM_CLASSES = ["living_room", "bedroom", "kitchen", "bathroom", "dining_room", "outdoor", "other"]
48
-
49
-
50
  def sync_push_background(local_path, remote_filename):
51
- if not HF_TOKEN: return
 
52
  def _push():
53
  try:
54
- api = HfApi(token=HF_TOKEN)
55
- api.upload_file(
56
- path_or_fileobj=local_path,
57
- path_in_repo=remote_filename,
58
- repo_id=DATASET_REPO_ID,
59
- repo_type="dataset",
60
- commit_message=f"Update {remote_filename}"
61
- )
62
- print(f"☁️ Synced {remote_filename}")
63
- except Exception as e:
64
- print(f"❌ Sync failed: {e}")
65
  threading.Thread(target=_push).start()
66
 
67
  def init_files():
68
  sync_pull()
69
  for f in [LABEL_FILE, VERIFY_FILE, SKIP_FILE]:
70
  if not os.path.exists(f):
71
- if f == LABEL_FILE:
72
- cols = ["timestamp", "user", "group_id", "url", "score", "label"]
73
- elif f == VERIFY_FILE:
74
- cols = ["timestamp", "user", "group_id", "url", "is_correct", "corrected_label", "corrected_score"]
75
- else:
76
- cols = ["timestamp", "user", "group_id"]
77
  pd.DataFrame(columns=cols).to_csv(f, index=False)
78
-
79
- if not os.path.exists(URL_FILE):
80
- print(f"⚠️ {URL_FILE} not found in root! Please upload it.")
81
 
82
  init_files()
83
 
84
- def get_image_optimized(url):
85
- if not url: return None
86
- try:
87
- headers = {'User-Agent': 'Mozilla/5.0'}
88
- response = requests.get(url, headers=headers, timeout=3)
89
- if response.status_code == 200:
90
- img = Image.open(BytesIO(response.content))
91
- img.thumbnail(THUMB_SIZE, Image.Resampling.LANCZOS)
92
- return img
93
- except: pass
94
- return url
95
-
96
  def load_all_urls():
97
  urls = []
98
- if not os.path.exists(URL_FILE):
99
- return urls
100
-
101
  try:
102
  with open(URL_FILE, 'r') as f:
103
  data = json.load(f)
104
  if "groups" in data:
105
- for group in data["groups"]:
106
- urls.extend(group.get("images", []))
107
- elif isinstance(data, dict):
108
- for rows in data.values():
109
- if isinstance(rows, list):
110
- for row in rows:
111
- if "unstaged_images" in row:
112
- urls.append(row["unstaged_images"])
113
- except Exception as e:
114
- print(f"Error loading URLs: {e}")
115
  return urls
116
 
117
  def get_ordered_groups():
118
  groups = []
119
  seen = set()
120
- all_urls = load_all_urls()
121
- for u in all_urls:
122
- if u:
123
- try: gid = u.split("-m")[0].split("/")[-1]
124
- except: gid = "unknown"
125
- if gid not in seen:
126
- groups.append(gid)
127
- seen.add(gid)
128
  return groups
129
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
130
  def get_group_urls(target_gid):
131
- urls = []
132
- all_urls = load_all_urls()
133
- for u in all_urls:
134
- if u and target_gid in u:
135
- urls.append(u)
136
- return urls[:MAX_IMAGES]
137
 
138
  def get_saved_values(gid, mode):
139
  saved_data = {}
140
  try:
141
- filename = LABEL_FILE if mode == "label" else VERIFY_FILE
142
- if os.path.exists(filename):
143
- df = pd.read_csv(filename)
144
- rows = df[df['group_id'] == gid]
145
- for _, row in rows.iterrows():
146
- if mode == "label":
147
- saved_data[row['url']] = {"score": row['score'], "label": row['label']}
148
- else:
149
- sc = row['corrected_score'] if 'corrected_score' in row else 5
150
- saved_data[row['url']] = {"is_correct": row['is_correct'], "label": row['corrected_label'], "score": sc}
151
  except: pass
152
  return saved_data
153
 
154
- def get_stats_text():
155
- all_gids = get_ordered_groups()
156
- try: l = len(pd.read_csv(LABEL_FILE)['group_id'].unique())
157
- except: l = 0
158
- try: v = len(pd.read_csv(VERIFY_FILE)['group_id'].unique())
159
- except: v = 0
160
- return f"**Total Properties:** {len(all_gids)} | **Labeled:** {l} | **Verified:** {v}"
161
-
162
  def render_workspace(mode, history, specific_index=None, move_back=False):
163
- all_groups = get_ordered_groups()
 
 
 
 
 
 
164
  total_groups = len(all_groups)
165
 
166
- try: l_done = set(pd.read_csv(LABEL_FILE)['group_id'].unique())
167
- except: l_done = set()
168
- try: v_done = set(pd.read_csv(VERIFY_FILE)['group_id'].unique())
169
- except: v_done = set()
170
- try: s_done = set(pd.read_csv(SKIP_FILE)['group_id'].unique())
171
- except: s_done = set()
172
-
173
  target_gid = None
174
- target_idx = -1
175
-
176
  if specific_index is not None:
177
- if 0 <= specific_index < total_groups:
178
- target_gid = all_groups[specific_index]
179
- target_idx = specific_index
180
- else:
181
- return {log_box: "End of list."}
182
-
183
  elif move_back and len(history) > 1:
184
- history.pop()
185
- target_gid = history[-1]
186
- try: target_idx = all_groups.index(target_gid)
187
- except: target_idx = 0
188
-
189
  else:
190
- candidates = []
191
- for i, gid in enumerate(all_groups):
192
- if gid in s_done: continue
193
- is_ready = False
194
- if mode == "label" and gid not in l_done: is_ready = True
195
- elif mode == "verify" and gid in l_done and gid not in v_done: is_ready = True
196
-
197
- if is_ready:
198
- candidates.append((i, gid))
199
 
 
200
  if not candidates:
201
- return {screen_menu: gr.update(visible=True), screen_work: gr.update(visible=False), log_box: "No more tasks found."}
202
-
203
- target_idx, target_gid = random.choice(candidates)
204
 
205
  urls = get_group_urls(target_gid)
206
- if not history or history[-1] != target_gid:
207
- history.append(target_gid)
208
-
209
- saved_vals = get_saved_values(target_gid, mode)
210
- r1_vals = get_saved_values(target_gid, "label") if mode == "verify" else {}
211
-
212
- processed_images = [None] * MAX_IMAGES
213
- with ThreadPoolExecutor(max_workers=MAX_IMAGES) as executor:
214
- futures = {executor.submit(get_image_optimized, u): i for i, u in enumerate(urls)}
215
- for future in futures:
216
- processed_images[futures[future]] = future.result()
217
-
218
- header = f"# Property #{target_idx + 1} (ID: {target_gid})"
219
 
 
 
 
 
 
 
 
 
 
 
 
 
 
220
  updates = {
221
- screen_menu: gr.update(visible=False),
222
- screen_work: gr.update(visible=True),
223
- header_md: header,
224
- state_urls: urls,
225
- state_hist: history,
226
- state_idx: target_idx,
227
- top_stats: get_stats_text(),
228
- log_box: f"Loaded group {target_gid}"
229
  }
230
 
231
  for i in range(MAX_IMAGES):
232
- img_c = img_objs[i]
233
  base = i * 4
234
- c_sld, c_drp, c_chk, c_lbl = input_objs[base], input_objs[base+1], input_objs[base+2], input_objs[base+3]
235
-
236
  if i < len(urls):
237
  u = urls[i]
238
- img_data = processed_images[i]
239
- updates[img_c] = gr.update(value=img_data, visible=True)
240
  v_sc = saved_vals.get(u, {}).get('score', 5)
241
- v_lbl = saved_vals.get(u, {}).get('label', ROOM_CLASSES[0])
242
- v_chk = saved_vals.get(u, {}).get('is_correct', True)
243
 
244
- if mode == "label":
 
 
 
245
  updates[c_sld] = gr.update(visible=True, value=v_sc, interactive=True)
246
  updates[c_drp] = gr.update(visible=True, value=v_lbl, interactive=True)
247
  updates[c_chk] = gr.update(visible=False)
248
- updates[c_lbl] = gr.update(visible=False)
249
- else:
250
- prev_lbl = r1_vals.get(u, {}).get('label', "Unknown")
251
- prev_sc = r1_vals.get(u, {}).get('score', 0)
252
-
253
- display_score = v_sc if u in saved_vals else prev_sc
254
-
255
- updates[c_sld] = gr.update(visible=True, value=display_score, interactive=True)
256
- updates[c_lbl] = gr.update(visible=True, value=f"**Labeled:** {prev_lbl} | **Score:** {prev_sc}")
257
- updates[c_drp] = gr.update(visible=True, value=v_lbl, interactive=True)
258
- updates[c_chk] = gr.update(visible=True, value=v_chk, interactive=True)
259
  else:
260
- updates[img_c] = gr.update(value=None, visible=False)
261
- updates[c_sld] = gr.update(visible=False)
262
- updates[c_drp] = gr.update(visible=False)
263
- updates[c_chk] = gr.update(visible=False)
264
- updates[c_lbl] = gr.update(visible=False)
265
  return updates
266
 
267
  def save_data(mode, history, urls, *args):
268
  if not history: return
269
- gid = history[-1]
270
- ts = datetime.now().isoformat()
271
- rows = []
272
  for i, u in enumerate(urls):
273
- base = i * 4
274
- sc, lbl, chk = args[base], args[base+1], args[base+2]
275
- if mode == "label":
276
- rows.append([ts, "user", gid, u, sc, lbl])
277
- else:
278
- rows.append([ts, "user", gid, u, chk, lbl, sc])
279
 
280
- fname = LABEL_FILE if mode == "label" else VERIFY_FILE
281
  with FileLock(LOCK_FILE):
282
- with open(fname, "a", newline="") as f:
283
- csv.writer(f).writerows(rows)
284
- remote_filename = os.path.basename(fname)
285
- sync_push_background(fname, remote_filename)
286
- return render_workspace(mode, history)
287
-
288
- def skip_group(idx, history, mode):
289
- if history:
290
- gid = history[-1]
291
- with FileLock(LOCK_FILE):
292
- with open(SKIP_FILE, "a", newline="") as f:
293
- csv.writer(f).writerow([datetime.now().isoformat(), "user", gid])
294
- sync_push_background(SKIP_FILE, "skipped.csv")
295
  return render_workspace(mode, history)
296
 
297
- def refresh_cat():
298
- all_gids = get_ordered_groups()
299
- try: l_set = set(pd.read_csv(LABEL_FILE)['group_id'].unique())
300
- except: l_set = set()
301
- try: v_set = set(pd.read_csv(VERIFY_FILE)['group_id'].unique())
302
- except: v_set = set()
303
- data = []
304
- for i, gid in enumerate(all_gids):
305
- s = "⚪ Pending"
306
- if gid in v_set: s = "✅ Verified"
307
- elif gid in l_set: s = "🔵 Labeled"
308
- data.append([i+1, s, gid])
309
- return pd.DataFrame(data, columns=["#", "Status", "ID"])
310
-
311
- with gr.Blocks(theme=gr.themes.Soft(), title="Labeling Tool") as demo:
312
- state_mode = gr.State("label")
313
- state_hist = gr.State([])
314
- state_urls = gr.State([])
315
- state_idx = gr.State(0)
316
 
317
- with gr.Row(variant="panel"):
318
- top_stats = gr.Markdown("Loading stats...")
319
- btn_home = gr.Button("🏠 Home", size="sm", scale=0)
320
-
321
- with gr.Tabs():
322
- with gr.Tab("Workspace", id=0):
323
- with gr.Group() as screen_menu:
324
- gr.Markdown("# Welcome! 👋")
325
- gr.Markdown("Persistent & Compatible Version")
326
- with gr.Row():
327
- b_start_l = gr.Button("Start Labeling", variant="primary")
328
- b_start_v = gr.Button("Start Verification")
329
-
330
- with gr.Group(visible=False) as screen_work:
331
- header_md = gr.Markdown()
332
- img_objs, input_objs = [], []
333
- with gr.Row():
334
- for i in range(MAX_IMAGES):
335
- with gr.Column(min_width=250):
336
- img = gr.Image(interactive=False, height=280, show_label=False)
337
- with gr.Group():
338
- sld = gr.Slider(1, 10, step=1, label="Score", visible=False)
339
- lbl = gr.Markdown(visible=False)
340
- drp = gr.Dropdown(ROOM_CLASSES, label="Class", visible=False, interactive=True)
341
- chk = gr.Checkbox(label="Correct?", value=True, visible=False)
342
-
343
- drp.select(fn=lambda: False, outputs=chk)
344
-
345
- img_objs.append(img)
346
- input_objs.extend([sld, drp, chk, lbl])
347
- with gr.Row():
348
- b_back = gr.Button("⬅ Back")
349
- b_skip = gr.Button("Skip ➡")
350
- b_save = gr.Button("💾 Save & Next", variant="primary")
351
- log_box = gr.Textbox(label="Log", interactive=False, max_lines=1)
352
-
353
- with gr.Tab("Catalog", id=1):
354
- with gr.Row():
355
- num_in = gr.Number(value=1, label="Property #", precision=0)
356
- b_go_l = gr.Button("Go (Label)")
357
- b_go_v = gr.Button("Go (Verify)")
358
- df_cat = gr.Dataframe(interactive=False)
359
- b_ref_cat = gr.Button("Refresh")
360
 
361
  ALL_IO = [screen_menu, screen_work, header_md, state_urls, state_hist, state_idx, top_stats, log_box] + img_objs + input_objs
362
 
363
  b_start_l.click(lambda: "label", None, state_mode).then(render_workspace, [state_mode, state_hist], ALL_IO)
364
  b_start_v.click(lambda: "verify", None, state_mode).then(render_workspace, [state_mode, state_hist], ALL_IO)
 
 
365
  b_save.click(save_data, [state_mode, state_hist, state_urls] + input_objs, ALL_IO)
366
- b_skip.click(skip_group, [state_idx, state_hist, state_mode], ALL_IO)
367
  b_back.click(lambda m, h: render_workspace(m, h, move_back=True), [state_mode, state_hist], ALL_IO)
368
  btn_home.click(lambda: {screen_menu: gr.update(visible=True), screen_work: gr.update(visible=False), state_hist: []}, None, [screen_menu, screen_work, state_hist])
369
- b_go_l.click(lambda: "label", None, state_mode).then(lambda n, m, h: render_workspace(m, h, specific_index=int(n)-1), [num_in, state_mode, state_hist], ALL_IO)
370
- b_go_v.click(lambda: "verify", None, state_mode).then(lambda n, m, h: render_workspace(m, h, specific_index=int(n)-1), [num_in, state_mode, state_hist], ALL_IO)
371
- b_ref_cat.click(refresh_cat, None, df_cat)
372
- demo.load(refresh_cat, None, df_cat).then(get_stats_text, None, top_stats)
373
 
374
  demo.queue().launch(server_name="0.0.0.0", server_port=7860)
 
25
  SKIP_FILE = os.path.join(CACHE_DIR, "skipped.csv")
26
  LOCK_FILE = os.path.join(CACHE_DIR, "data.lock")
27
 
28
+ MAX_IMAGES = 6
29
+ THUMB_SIZE = (350, 350)
30
+ ROOM_CLASSES = ["living_room", "bedroom", "kitchen", "bathroom", "dining_room", "outdoor", "other"]
31
+
32
  def sync_pull():
33
  print(f"🔄 Syncing from {DATASET_REPO_ID}...")
34
  token = HF_TOKEN if HF_TOKEN and len(HF_TOKEN) > 5 else None
 
35
  for filename in ["annotations.csv", "verifications.csv", "skipped.csv"]:
36
  try:
37
+ hf_hub_download(repo_id=DATASET_REPO_ID, filename=filename, repo_type="dataset", local_dir=CACHE_DIR, token=token)
 
 
 
 
 
 
38
  print(f"✅ Loaded {filename}")
39
  except Exception as e:
40
  print(f"ℹ️ {filename} sync info: {e}")
41
 
 
 
 
 
 
42
  def sync_push_background(local_path, remote_filename):
43
+ token = HF_TOKEN if HF_TOKEN and len(HF_TOKEN) > 5 else None
44
+ if not token: return
45
  def _push():
46
  try:
47
+ api = HfApi(token=token)
48
+ api.upload_file(path_or_fileobj=local_path, path_in_repo=remote_filename, repo_id=DATASET_REPO_ID, repo_type="dataset")
49
+ except Exception as e: print(f"❌ Sync failed: {e}")
 
 
 
 
 
 
 
 
50
  threading.Thread(target=_push).start()
51
 
52
  def init_files():
53
  sync_pull()
54
  for f in [LABEL_FILE, VERIFY_FILE, SKIP_FILE]:
55
  if not os.path.exists(f):
56
+ cols = ["timestamp", "user", "group_id", "url", "score", "label"] if f == LABEL_FILE else \
57
+ ["timestamp", "user", "group_id", "url", "is_correct", "corrected_label", "corrected_score"] if f == VERIFY_FILE else \
58
+ ["timestamp", "user", "group_id"]
 
 
 
59
  pd.DataFrame(columns=cols).to_csv(f, index=False)
 
 
 
60
 
61
  init_files()
62
 
 
 
 
 
 
 
 
 
 
 
 
 
63
  def load_all_urls():
64
  urls = []
65
+ if not os.path.exists(URL_FILE): return urls
 
 
66
  try:
67
  with open(URL_FILE, 'r') as f:
68
  data = json.load(f)
69
  if "groups" in data:
70
+ for group in data["groups"]: urls.extend(group.get("images", []))
71
+ except: pass
 
 
 
 
 
 
 
 
72
  return urls
73
 
74
  def get_ordered_groups():
75
  groups = []
76
  seen = set()
77
+ for u in load_all_urls():
78
+ try: gid = u.split("-m")[0].split("/")[-1]
79
+ except: gid = "unknown"
80
+ if gid not in seen:
81
+ groups.append(gid); seen.add(gid)
 
 
 
82
  return groups
83
 
84
+ def get_flagged_groups():
85
+ if not os.path.exists(LABEL_FILE): return []
86
+ try:
87
+ df = pd.read_csv(LABEL_FILE)
88
+ errors = df[(df['score'] == 10) & (df['label'] != 'living_room')]
89
+ return errors['group_id'].unique().tolist()
90
+ except: return []
91
+
92
+ def get_stats_text():
93
+ all_gids = get_ordered_groups()
94
+ flagged = get_flagged_groups()
95
+ try: l = len(pd.read_csv(LABEL_FILE)['group_id'].unique())
96
+ except: l = 0
97
+ err_msg = f" | ⚠️ **Errors to Fix:** {len(flagged)}" if flagged else ""
98
+ return f"**Total Properties:** {len(all_gids)} | **Labeled:** {l}{err_msg}"
99
+
100
  def get_group_urls(target_gid):
101
+ return [u for u in load_all_urls() if target_gid in u][:MAX_IMAGES]
 
 
 
 
 
102
 
103
  def get_saved_values(gid, mode):
104
  saved_data = {}
105
  try:
106
+ fname = LABEL_FILE if mode in ["label", "fix"] else VERIFY_FILE
107
+ df = pd.read_csv(fname)
108
+ rows = df[df['group_id'] == gid]
109
+ for _, row in rows.iterrows():
110
+ if mode in ["label", "fix"]:
111
+ saved_data[row['url']] = {"score": row['score'], "label": row['label']}
112
+ else:
113
+ saved_data[row['url']] = {"is_correct": row['is_correct'], "label": row['corrected_label'], "score": row['corrected_score']}
 
 
114
  except: pass
115
  return saved_data
116
 
 
 
 
 
 
 
 
 
117
  def render_workspace(mode, history, specific_index=None, move_back=False):
118
+ if mode == "fix":
119
+ all_groups = get_flagged_groups()
120
+ if not all_groups:
121
+ return {screen_menu: gr.update(visible=True), screen_work: gr.update(visible=False), log_box: "🎉 No more errors found!"}
122
+ else:
123
+ all_groups = get_ordered_groups()
124
+
125
  total_groups = len(all_groups)
126
 
 
 
 
 
 
 
 
127
  target_gid = None
 
 
128
  if specific_index is not None:
129
+ if 0 <= specific_index < total_groups: target_gid = all_groups[specific_index]
130
+ else: return {log_box: "Index out of range."}
 
 
 
 
131
  elif move_back and len(history) > 1:
132
+ history.pop(); target_gid = history[-1]
 
 
 
 
133
  else:
134
+ try: done = set(pd.read_csv(VERIFY_FILE if mode == "verify" else LABEL_FILE)['group_id'].unique())
135
+ except: done = set()
 
 
 
 
 
 
 
136
 
137
+ candidates = [g for g in all_groups if g not in done] if mode != "fix" else all_groups
138
  if not candidates:
139
+ return {screen_menu: gr.update(visible=True), screen_work: gr.update(visible=False), log_box: "All items completed in this mode."}
140
+ target_gid = candidates[0]
 
141
 
142
  urls = get_group_urls(target_gid)
143
+ if not history or history[-1] != target_gid: history.append(target_gid)
 
 
 
 
 
 
 
 
 
 
 
 
144
 
145
+ saved_vals = get_saved_values(target_gid, mode)
146
+ target_idx = all_groups.index(target_gid)
147
+
148
+ with ThreadPoolExecutor(max_workers=MAX_IMAGES) as ex:
149
+ def get_img(u):
150
+ try:
151
+ r = requests.get(u, timeout=3, headers={'User-Agent': 'Mozilla/5.0'})
152
+ img = Image.open(BytesIO(r.content))
153
+ img.thumbnail(THUMB_SIZE); return img
154
+ except: return None
155
+ processed_images = list(ex.map(get_img, urls))
156
+
157
+ header = f"# {'🛠 FIX MODE' if mode == 'fix' else 'Property'} #{target_idx + 1} (ID: {target_gid})"
158
  updates = {
159
+ screen_menu: gr.update(visible=False), screen_work: gr.update(visible=True),
160
+ header_md: header, state_urls: urls, state_hist: history, state_idx: target_idx,
161
+ top_stats: get_stats_text(), log_box: f"Mode: {mode} | ID: {target_gid}"
 
 
 
 
 
162
  }
163
 
164
  for i in range(MAX_IMAGES):
 
165
  base = i * 4
166
+ c_sld, c_drp, c_chk, c_lbl = input_objs[base:base+4]
 
167
  if i < len(urls):
168
  u = urls[i]
169
+ updates[img_objs[i]] = gr.update(value=processed_images[i], visible=True)
 
170
  v_sc = saved_vals.get(u, {}).get('score', 5)
171
+ v_lbl = saved_vals.get(u, {}).get('label', "living_room")
 
172
 
173
+ is_error = (v_sc == 10 and v_lbl != "living_room")
174
+ error_border = "⚠️ INCORRECT (Score 10 only for Living Room)" if is_error else ""
175
+
176
+ if mode in ["label", "fix"]:
177
  updates[c_sld] = gr.update(visible=True, value=v_sc, interactive=True)
178
  updates[c_drp] = gr.update(visible=True, value=v_lbl, interactive=True)
179
  updates[c_chk] = gr.update(visible=False)
180
+ updates[c_lbl] = gr.update(visible=True if is_error else False, value=f"<span style='color:red'>{error_border}</span>")
181
+ else:
182
+ updates[c_sld] = gr.update(visible=True, value=v_sc)
183
+ updates[c_chk] = gr.update(visible=True, value=True)
184
+ updates[c_lbl] = gr.update(visible=True, value=f"Prev: {v_lbl} ({v_sc})")
185
+ updates[c_drp] = gr.update(visible=True, value=v_lbl)
 
 
 
 
 
186
  else:
187
+ updates[img_objs[i]] = gr.update(visible=False)
188
+ for obj in [c_sld, c_drp, c_chk, c_lbl]: updates[obj] = gr.update(visible=False)
189
+
 
 
190
  return updates
191
 
192
  def save_data(mode, history, urls, *args):
193
  if not history: return
194
+ gid = history[-1]; ts = datetime.now().isoformat(); rows = []
 
 
195
  for i, u in enumerate(urls):
196
+ sc, lbl, chk = args[i*4], args[i*4+1], args[i*4+2]
197
+ if mode in ["label", "fix"]: rows.append([ts, "user", gid, u, sc, lbl])
198
+ else: rows.append([ts, "user", gid, u, chk, lbl, sc])
 
 
 
199
 
200
+ fname = LABEL_FILE if mode in ["label", "fix"] else VERIFY_FILE
201
  with FileLock(LOCK_FILE):
202
+ with open(fname, "a", newline="") as f: csv.writer(f).writerows(rows)
203
+ sync_push_background(fname, os.path.basename(fname))
 
 
 
 
 
 
 
 
 
 
 
204
  return render_workspace(mode, history)
205
 
206
+ with gr.Blocks(theme=gr.themes.Soft()) as demo:
207
+ state_mode, state_hist, state_urls, state_idx = gr.State("label"), gr.State([]), gr.State([]), gr.State(0)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
208
 
209
+ with gr.Row():
210
+ top_stats = gr.Markdown("Loading...")
211
+ btn_home = gr.Button("🏠 Home", size="sm")
212
+
213
+ with gr.Group() as screen_menu:
214
+ gr.Markdown("# Property Labeler Pro")
215
+ with gr.Row():
216
+ b_start_l = gr.Button("Start Labeling", variant="primary")
217
+ b_start_v = gr.Button("Start Verification")
218
+ b_start_f = gr.Button("🛠 Fix Errors (Score 10 Check)", variant="secondary")
219
+
220
+ with gr.Group(visible=False) as screen_work:
221
+ header_md = gr.Markdown()
222
+ img_objs, input_objs = [], []
223
+ with gr.Row():
224
+ for i in range(MAX_IMAGES):
225
+ with gr.Column():
226
+ img = gr.Image(interactive=False, height=250)
227
+ sld = gr.Slider(1, 10, step=1, label="Score")
228
+ lbl = gr.Markdown(visible=False)
229
+ drp = gr.Dropdown(ROOM_CLASSES, label="Class")
230
+ chk = gr.Checkbox(label="Correct?", value=True, visible=False)
231
+ img_objs.append(img); input_objs.extend([sld, drp, chk, lbl])
232
+ with gr.Row():
233
+ b_back = gr.Button("⬅ Back")
234
+ b_save = gr.Button("💾 Save & Next", variant="primary")
235
+ log_box = gr.Textbox(label="Status", interactive=False)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
236
 
237
  ALL_IO = [screen_menu, screen_work, header_md, state_urls, state_hist, state_idx, top_stats, log_box] + img_objs + input_objs
238
 
239
  b_start_l.click(lambda: "label", None, state_mode).then(render_workspace, [state_mode, state_hist], ALL_IO)
240
  b_start_v.click(lambda: "verify", None, state_mode).then(render_workspace, [state_mode, state_hist], ALL_IO)
241
+ b_start_f.click(lambda: "fix", None, state_mode).then(render_workspace, [state_mode, state_hist], ALL_IO)
242
+
243
  b_save.click(save_data, [state_mode, state_hist, state_urls] + input_objs, ALL_IO)
 
244
  b_back.click(lambda m, h: render_workspace(m, h, move_back=True), [state_mode, state_hist], ALL_IO)
245
  btn_home.click(lambda: {screen_menu: gr.update(visible=True), screen_work: gr.update(visible=False), state_hist: []}, None, [screen_menu, screen_work, state_hist])
246
+
247
+ demo.load(get_stats_text, None, top_stats)
 
 
248
 
249
  demo.queue().launch(server_name="0.0.0.0", server_port=7860)