HFswapnil commited on
Commit
742bccd
·
verified ·
1 Parent(s): da791fc

Update src/main.py

Browse files
Files changed (1) hide show
  1. src/main.py +106 -386
src/main.py CHANGED
@@ -1,394 +1,114 @@
1
- import os
2
- import json
3
- import uuid
4
- import time
5
- import tempfile
6
- from typing import Any, Dict, List, Tuple
7
-
8
  import streamlit as st
9
  import pandas as pd
 
 
 
 
 
 
10
  from PIL import Image
11
- from huggingface_hub import HfApi, hf_hub_download
12
- from huggingface_hub.utils import HfHubHTTPError
13
 
14
-
15
- # =========================
16
- # CONFIG
17
- # =========================
18
-
19
- st.set_page_config(
20
- page_title="AI Benchmark Arena",
21
- page_icon="🏆",
22
- layout="wide",
23
- initial_sidebar_state="expanded",
 
 
 
 
 
 
 
 
 
 
24
  )
25
 
26
- #Set this to the private dataset repo that acts as the "database"
27
- DB_REPO_ID = os.getenv("DB_REPO_ID", "NidhiS09/VizWiz-submissions-db")
28
- DB_REPO_TYPE = "dataset"
29
-
30
- # This must exist as a Space Secret in the PUBLIC UI Space
31
- SUBMISSIONS_TOKEN = os.getenv("SUBMISSIONS_TOKEN", "")
32
-
33
- # Phase config (copied from EvalAI config intent)
34
- # PHASES = [
35
- # {"label": "Dev (qeury-dev2024)", "codename": "test-dev2024"},
36
- # {"label": "Standard (query-standard2024)", "codename": "test-standard2024"},
37
- # {"label": "Challenge (query-challenge2024)", "codename": "test-challenge2024"},
38
- # ]
39
-
40
- CHALLENGE_TYPES = ["Object Detection", "Instance Segmentation"]
41
-
42
- # Leaderboard columns from your EvalAI yaml
43
- LEADERBOARD_METRICS = ["bbox_mAP", "bbox_AP50", "segm_mAP", "segm_AP50"]
44
- DEFAULT_SORT_METRIC = "segm_AP50"
45
-
46
-
47
- # =========================
48
- # HELPERS
49
- # =========================
50
-
51
- def _require_token() -> None:
52
- if not SUBMISSIONS_TOKEN:
53
- st.error(
54
- "Missing SUBMISSIONS_TOKEN. Add it in Space Settings → Secrets "
55
- "(token must have read/write access ONLY to the private DB dataset repo)."
56
- )
57
- st.stop()
58
-
59
-
60
- def _validate_submission_json(obj: Any) -> Tuple[bool, str]:
61
- """
62
- Validates the submission format from your instructions:
63
- - Top-level must be a list
64
- - Each item must be a dict containing:
65
- image_id (int), score (number), category_id (int), area (number),
66
- bbox ([x,y,w,h]), segmentation (list)
67
- """
68
- if not isinstance(obj, list):
69
- return False, "Submission must be a JSON list of annotations."
70
-
71
- required_keys = {"image_id", "score", "category_id", "area", "bbox", "segmentation"}
72
-
73
- for i, ann in enumerate(obj):
74
- if not isinstance(ann, dict):
75
- return False, f"Annotation at index {i} must be an object/dict."
76
-
77
- missing = required_keys - set(ann.keys())
78
- if missing:
79
- return False, f"Annotation at index {i} missing keys: {sorted(list(missing))}"
80
-
81
- # Basic type checks
82
- if not isinstance(ann["image_id"], int):
83
- return False, f"image_id at index {i} must be an integer."
84
- if not isinstance(ann["category_id"], int):
85
- return False, f"category_id at index {i} must be an integer."
86
-
87
- if not isinstance(ann["score"], (int, float)):
88
- return False, f"score at index {i} must be a number."
89
-
90
- if not isinstance(ann["area"], (int, float)):
91
- return False, f"area at index {i} must be a number."
92
-
93
- bbox = ann["bbox"]
94
- if not (isinstance(bbox, list) and len(bbox) == 4 and all(isinstance(x, (int, float)) for x in bbox)):
95
- return False, f"bbox at index {i} must be a list of 4 numbers: [x, y, w, h]."
96
-
97
- segm = ann["segmentation"]
98
- if not isinstance(segm, list):
99
- return False, f"segmentation at index {i} must be a list."
100
-
101
- return True, "OK"
102
-
103
-
104
- def _upload_json(api: HfApi, data: Dict[str, Any] | List[Any], path_in_repo: str) -> None:
105
- with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as tmp:
106
- json.dump(data, tmp, ensure_ascii=False)
107
- tmp_path = tmp.name
108
-
109
- try:
110
- api.upload_file(
111
- path_or_fileobj=tmp_path,
112
- path_in_repo=path_in_repo,
113
- repo_id=DB_REPO_ID,
114
- repo_type=DB_REPO_TYPE,
115
- token=SUBMISSIONS_TOKEN,
116
- commit_message=f"Add {path_in_repo}",
117
- )
118
- finally:
119
- try:
120
- os.remove(tmp_path)
121
- except OSError:
122
- pass
123
-
124
-
125
- def _create_submission_record(
126
- *,
127
- pred: List[Dict[str, Any]],
128
- team: str,
129
- model_name: str,
130
- phase_codename: str,
131
- challenge_type: str,
132
- original_filename: str,
133
- ) -> str:
134
- """
135
- Writes pred/meta/status to the private DB dataset repo.
136
- Returns submission_id.
137
- """
138
- _require_token()
139
- api = HfApi()
140
-
141
- submission_id = str(uuid.uuid4())
142
- ts = int(time.time())
143
-
144
- meta = {
145
- "submission_id": submission_id,
146
- "team": team.strip(),
147
- "model": model_name.strip(),
148
- "phase_codename": phase_codename,
149
- "challenge_type": challenge_type,
150
- "timestamp": ts,
151
- "original_filename": original_filename,
152
- }
153
-
154
- status = {"state": "queued", "timestamp": ts}
155
-
156
- base = f"submissions/{submission_id}"
157
- _upload_json(api, pred, f"{base}/pred.json")
158
- _upload_json(api, meta, f"{base}/meta.json")
159
- _upload_json(api, status, f"{base}/status.json")
160
-
161
- return submission_id
162
-
163
-
164
- def _download_leaderboard_jsonl() -> str | None:
165
- """
166
- Downloads leaderboard.jsonl from the DB repo.
167
- Returns local path or None if missing.
168
- """
169
- _require_token()
170
- try:
171
- return hf_hub_download(
172
- repo_id=DB_REPO_ID,
173
- repo_type=DB_REPO_TYPE,
174
- filename="leaderboard.jsonl",
175
- token=SUBMISSIONS_TOKEN,
176
- )
177
- except HfHubHTTPError as e:
178
- # Most common: 404 when file doesn't exist yet
179
- if "404" in str(e):
180
- return None
181
- raise
182
-
183
-
184
- def _load_leaderboard_df() -> pd.DataFrame:
185
- path = _download_leaderboard_jsonl()
186
- if path is None:
187
- return pd.DataFrame(columns=["team", "model", "phase_codename", *LEADERBOARD_METRICS, "timestamp"])
188
-
189
- rows = []
190
- with open(path, "r", encoding="utf-8") as f:
191
- for line in f:
192
- line = line.strip()
193
- if not line:
194
- continue
195
- try:
196
- rows.append(json.loads(line))
197
- except json.JSONDecodeError:
198
- # Skip malformed lines rather than crashing the UI
199
- continue
200
-
201
- if not rows:
202
- return pd.DataFrame(columns=["team", "model", "phase_codename", *LEADERBOARD_METRICS, "timestamp"])
203
-
204
- df = pd.DataFrame(rows)
205
-
206
- # Ensure columns exist
207
- for col in ["team", "model", "phase_codename", "timestamp", *LEADERBOARD_METRICS]:
208
- if col not in df.columns:
209
- df[col] = None
210
-
211
- # Sort descending by default metric
212
- if DEFAULT_SORT_METRIC in df.columns:
213
- df = df.sort_values(by=DEFAULT_SORT_METRIC, ascending=False, kind="mergesort")
214
-
215
- return df
216
-
217
-
218
- # =========================
219
- # UI
220
- # =========================
221
-
222
- def render_overview():
223
- with st.expander("ℹ️ Overview of the AI Benchmark Arena"):
224
- st.markdown(
225
- """
226
-
227
- **Note:** This Hugging Face Space queues submissions for evaluation and persists results in a private database repo.
228
- """
229
- )
230
- # Keep this optional so missing image doesn't crash the Space
231
- try:
232
- overview_image = Image.open("src/overview_image.png").resize((600, 600))
233
- st.image(overview_image, caption="Example of an object localization task")
234
- except Exception:
235
- st.info("Overview image not found at src/overview_image.png (optional).")
236
-
237
-
238
- def render_eval_details():
239
- with st.expander("📐 How is the Score Calculated?"):
240
- st.markdown(
241
- """
242
- Your submission is evaluated offline by a private evaluator against hidden ground-truth annotations.
243
- The leaderboard reports:
244
-
245
- - bbox_mAP
246
- - bbox_AP50
247
- - segm_mAP
248
- - segm_AP50 (default ranking)
249
-
250
- Raw submissions are kept private; only scores and metadata are shown.
251
- """
252
- )
253
-
254
-
255
- def page_submit():
256
- st.header("🚀 Submit your Predictions")
257
-
258
- col1, col2 = st.columns([2, 1])
259
-
260
- with col2:
261
- st.subheader("Submission Info")
262
- team = st.text_input("Team / Display Name", value=st.session_state.get("team", ""))
263
- model_name = st.text_input("Model Name", value=st.session_state.get("model_name", ""))
264
-
265
- phase_label = st.selectbox("Phase", [p["label"] for p in PHASES])
266
- phase_codename = next(p["codename"] for p in PHASES if p["label"] == phase_label)
267
-
268
- challenge_type = st.radio("Challenge type", CHALLENGE_TYPES, horizontal=False)
269
-
270
- st.session_state["team"] = team
271
- st.session_state["model_name"] = model_name
272
-
273
- st.caption("Your submission will be queued for evaluation. Scores appear on the leaderboard after processing.")
274
-
275
- with col1:
276
- st.subheader("Upload Submission File")
277
- uploaded_file = st.file_uploader("Choose a JSON file", type=["json"])
278
-
279
- if uploaded_file is None:
280
- st.info("Upload a JSON file that contains a list of annotations.")
281
- return
282
-
283
- # Parse JSON
284
- try:
285
- raw = uploaded_file.getvalue().decode("utf-8")
286
- pred_obj = json.loads(raw)
287
- except Exception:
288
- st.error("Could not parse JSON. Please upload a valid JSON file.")
289
- return
290
-
291
- ok, msg = _validate_submission_json(pred_obj)
292
- if not ok:
293
- st.error(f"Invalid submission format: {msg}")
294
- return
295
-
296
- st.success("Submission file looks valid ✅")
297
-
298
- submit_clicked = st.button("Submit (Queue for Evaluation)", type="primary")
299
-
300
- if submit_clicked:
301
- if not team.strip():
302
- st.error("Please enter Team / Display Name.")
303
- return
304
- if not model_name.strip():
305
- st.error("Please enter Model Name.")
306
- return
307
-
308
- with st.spinner("Uploading submission to the private database repo..."):
309
- try:
310
- submission_id = _create_submission_record(
311
- pred=pred_obj,
312
- team=team,
313
- model_name=model_name,
314
- phase_codename=phase_codename,
315
- challenge_type=challenge_type,
316
- original_filename=uploaded_file.name,
317
- )
318
- except Exception as e:
319
- st.error(f"Upload failed: {e}")
320
- return
321
-
322
- st.balloons()
323
- st.success("Submission queued successfully!")
324
- st.code(f"Submission ID: {submission_id}")
325
-
326
- st.info("Next: the private evaluator will score your submission and update the leaderboard.")
327
-
328
-
329
- def page_leaderboard():
330
- st.header("🏆 Leaderboard")
331
- st.write(f"Ranked by **{DEFAULT_SORT_METRIC}** (descending).")
332
-
333
- with st.spinner("Loading leaderboard from private database repo..."):
334
- try:
335
- df = _load_leaderboard_df()
336
- except Exception as e:
337
- st.error(f"Could not load leaderboard: {e}")
338
- return
339
-
340
  if df.empty:
341
- st.info("No scored submissions yet. Submit a model to get started!")
342
- return
343
-
344
- # Add Rank column
345
- df_display = df.copy()
346
- df_display.insert(0, "Rank", range(1, len(df_display) + 1))
347
-
348
- # Optional: pretty timestamp
349
- if "timestamp" in df_display.columns:
350
- df_display["timestamp"] = pd.to_datetime(df_display["timestamp"], unit="s", errors="coerce")
351
-
352
- st.dataframe(
353
- df_display,
354
- column_config={
355
- "Rank": st.column_config.Column("Rank", width="small"),
356
- "team": "Team",
357
- "model": "Model",
358
- "phase_codename": "Phase",
359
- "bbox_mAP": st.column_config.NumberColumn("bbox_mAP", format="%.4f"),
360
- "bbox_AP50": st.column_config.NumberColumn("bbox_AP50", format="%.4f"),
361
- "segm_mAP": st.column_config.NumberColumn("segm_mAP", format="%.4f"),
362
- "segm_AP50": st.column_config.NumberColumn("segm_AP50", format="%.4f"),
363
- "timestamp": st.column_config.DatetimeColumn("Scored at", format="D MMM YYYY, h:mm a"),
364
- },
365
- use_container_width=True,
366
- hide_index=True,
367
- )
368
-
369
-
370
- def main():
371
- st.sidebar.title("AI Benchmark Arena 🏆")
372
-
373
- # Warn early if DB repo isn't configured
374
- if DB_REPO_ID.startswith("NidhiS09/"):
375
- st.sidebar.warning("Set DB_REPO_ID env var or hardcode your private DB dataset repo id in main.py.")
376
-
377
- menu = ["Submit Model", "Leaderboard"]
378
- choice = st.sidebar.radio("Navigation", menu)
379
-
380
- st.sidebar.markdown("---")
381
- st.sidebar.caption("This Space queues submissions to a private DB repo and reads leaderboard results from it.")
382
-
383
- render_overview()
384
- render_eval_details()
385
- st.markdown("---")
386
-
387
- if choice == "Submit Model":
388
- page_submit()
389
  else:
390
- page_leaderboard()
391
-
392
-
393
- if __name__ == "__main__":
394
- main()
 
 
 
 
 
 
 
 
1
  import streamlit as st
2
  import pandas as pd
3
+ import os
4
+ import hashlib
5
+ from datetime import datetime
6
+ from pathlib import Path
7
+ from huggingface_hub import CommitScheduler
8
+ from localization_eval import evaluate_submission
9
  from PIL import Image
 
 
10
 
11
+ # --- PERSISTENCE CONFIGURATION ---
12
+ DATA_FILENAME = "submissions.csv"
13
+ USER_FILENAME = "users.csv"
14
+ DATA_DIR = Path("data")
15
+ DATA_DIR.mkdir(exist_ok=True)
16
+
17
+ DATA_PATH = DATA_DIR / DATA_FILENAME
18
+ USER_PATH = DATA_DIR / USER_FILENAME
19
+
20
+ # Initialize CommitScheduler
21
+ # This will automatically sync everything in the /data folder to your HF Dataset
22
+ repo_id = "VizWiz-Challenges/submissions-db" # TODO: Change this
23
+
24
+ scheduler = CommitScheduler(
25
+ repo_id=repo_id,
26
+ repo_type="dataset",
27
+ folder_path=DATA_DIR,
28
+ path_in_repo="data",
29
+ every=5,
30
+ token=os.getenv("SubmissionsToken")
31
  )
32
 
33
+ # --- DB HELPERS (Replaced SQLite with Pandas/CSV) ---
34
+
35
+ def init_db():
36
+ # Create files if they don't exist
37
+ if not USER_PATH.exists():
38
+ pd.DataFrame(columns=["username", "password"]).to_csv(USER_PATH, index=False)
39
+ if not DATA_PATH.exists():
40
+ pd.DataFrame(columns=["username", "bbox_mAP", "bbox_AP50", "segm_mAP", "segm_AP50", "timestamp"]).to_csv(DATA_PATH, index=False)
41
+
42
+ def make_hashes(password):
43
+ return hashlib.sha256(str.encode(password)).hexdigest()
44
+
45
+ def add_user(username, password):
46
+ with scheduler.lock:
47
+ df = pd.read_csv(USER_PATH)
48
+ if username in df['username'].values:
49
+ return False
50
+ new_user = pd.DataFrame([{"username": username, "password": make_hashes(password)}])
51
+ df = pd.concat([df, new_user], ignore_index=True)
52
+ df.to_csv(USER_PATH, index=False)
53
+ return True
54
+
55
+ def login_user(username, password):
56
+ df = pd.read_csv(USER_PATH)
57
+ user_row = df[(df['username'] == username) & (df['password'] == make_hashes(password))]
58
+ return not user_row.empty
59
+
60
+ def save_submission(username, bbox_mAP, bbox_AP50, segm_mAP, segm_AP50):
61
+ with scheduler.lock:
62
+ df = pd.read_csv(DATA_PATH)
63
+ new_sub = pd.DataFrame([{
64
+ "username": username,
65
+ "bbox_mAP": bbox_mAP,
66
+ "bbox_AP50": bbox_AP50,
67
+ "segm_mAP": segm_mAP,
68
+ "segm_AP50": segm_AP50,
69
+ "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
70
+ }])
71
+ df = pd.concat([df, new_sub], ignore_index=True)
72
+ df.to_csv(DATA_PATH, index=False)
73
+
74
+ def get_leaderboard_data():
75
+ if not DATA_PATH.exists():
76
+ return pd.DataFrame()
77
+
78
+ df = pd.read_csv(DATA_PATH)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
79
  if df.empty:
80
+ return df
81
+
82
+ # Logic to get the BEST score per user
83
+ # Sorting by segm_mAP (desc) and timestamp (asc) to get best/earliest
84
+ df = df.sort_values(by=['segm_mAP', 'timestamp'], ascending=[False, True])
85
+ df_best = df.drop_duplicates(subset='username', keep='first')
86
+
87
+ df_best = df_best.rename(columns={'segm_mAP': 'Best_segm_mAP'})
88
+ return df_best
89
+
90
+ # --- CONFIGURATION & SETUP ---
91
+ st.set_page_config(page_title="AI Benchmark Arena", page_icon="🏆", layout="wide")
92
+
93
+
94
+ def main_app():
95
+
96
+ if st.button("Evaluate"):
97
+ with st.spinner('Calculating...'):
98
+ bbox_mAP, bbox_AP50, segm_mAP, segm_AP50 = evaluate_submission("src/biv_query.json", save_path)
99
+ if bbox_mAP is not None:
100
+ save_submission(st.session_state['username'], bbox_mAP, bbox_AP50, segm_mAP, segm_AP50)
101
+ st.balloons()
102
+ st.success("Submission Successful!")
103
+
104
+ if __name__ == '__main__':
105
+ init_db()
106
+
107
+ if 'logged_in' not in st.session_state:
108
+ st.session_state['logged_in'] = False
109
+ st.session_state['username'] = None
110
+
111
+ if not st.session_state['logged_in']:
112
+ ui_login_signup()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
113
  else:
114
+ main_app()