NidhiS09 commited on
Commit
005f1ba
ยท
verified ยท
1 Parent(s): 5ce0f6d

Update src/main.py

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