Spaces:
Sleeping
Sleeping
| import os | |
| import json | |
| import uuid | |
| import time | |
| import tempfile | |
| from datetime import datetime, timezone | |
| from typing import Any, Dict, List, Tuple, Optional | |
| import gradio as gr | |
| import pandas as pd | |
| from huggingface_hub import HfApi, hf_hub_download | |
| from huggingface_hub.utils import HfHubHTTPError | |
| # ========================= | |
| # CONFIG | |
| # ========================= | |
| DB_REPO_ID = os.getenv("DB_REPO_ID", "VizWiz-Challenges/submissions-db") | |
| DB_REPO_TYPE = "dataset" | |
| SUBMISSIONS_TOKEN = os.getenv("SUBMISSIONS_TOKEN", "") | |
| DAILY_SUBMISSION_CAP = 5 | |
| PHASES = [ | |
| {"label": "Dev (test-dev2024)", "codename": "test-dev2024"}, | |
| {"label": "Standard (test-standard2024)", "codename": "test-standard2024"}, | |
| {"label": "Challenge (test-challenge2024)", "codename": "test-challenge2024"}, | |
| ] | |
| CHALLENGE_TYPES = ["Object Detection", "Instance Segmentation"] | |
| LEADERBOARD_METRICS = ["bbox_mAP", "bbox_AP50", "segm_mAP", "segm_AP50"] | |
| DEFAULT_SORT_METRIC = "segm_AP50" | |
| LEADERBOARD_METIRCS_VQA = ["overall"] | |
| DEFAULT_SORT_METRIC = "overall" | |
| # ========================= | |
| # HF API | |
| # ========================= | |
| _api = None | |
| def api_client() -> HfApi: | |
| global _api | |
| if _api is None: | |
| _api = HfApi() | |
| return _api | |
| # ========================= | |
| # SUBMISSION HELPERS | |
| # ========================= | |
| def _today_utc_str() -> str: | |
| return datetime.now(timezone.utc).strftime("%Y-%m-%d") | |
| def _count_submissions_today(username: str, phase_codename: str | None = None) -> int: | |
| """Count today's submissions for a user, optionally filtered by phase.""" | |
| try: | |
| files = api_client().list_repo_files( | |
| repo_id=DB_REPO_ID, repo_type=DB_REPO_TYPE, token=SUBMISSIONS_TOKEN | |
| ) | |
| today = _today_utc_str() | |
| count = 0 | |
| for f in files: | |
| if not (f.startswith("submissions/") and f.endswith("/meta.json")): | |
| continue | |
| try: | |
| p = hf_hub_download( | |
| repo_id=DB_REPO_ID, repo_type=DB_REPO_TYPE, | |
| filename=f, token=SUBMISSIONS_TOKEN | |
| ) | |
| meta = json.load(open(p)) | |
| if meta.get("username", "").lower() != username.lower(): | |
| continue | |
| if phase_codename and meta.get("phase_codename") != phase_codename: | |
| continue | |
| ts = meta.get("timestamp", 0) | |
| sub_date = datetime.fromtimestamp(ts, tz=timezone.utc).strftime("%Y-%m-%d") | |
| if sub_date == today: | |
| count += 1 | |
| except Exception: | |
| continue | |
| return count | |
| except Exception: | |
| return 0 | |
| def _get_cap_for_phase(phase_codename: str) -> int: | |
| """Return the daily submission cap for a given phase.""" | |
| return 1 if phase_codename == "test-challenge2024" else DAILY_SUBMISSION_CAP | |
| def _validate_submission_json(obj: Any) -> Tuple[bool, str]: | |
| if not isinstance(obj, list): | |
| return False, "Submission must be a JSON list of annotations." | |
| required_keys = {"image_id", "score", "category_id", "area", "bbox", "segmentation"} | |
| for i, ann in enumerate(obj): | |
| if not isinstance(ann, dict): | |
| return False, f"Annotation at index {i} must be an object/dict." | |
| missing = required_keys - set(ann.keys()) | |
| if missing: | |
| return False, f"Annotation at index {i} missing keys: {sorted(list(missing))}" | |
| if not isinstance(ann["image_id"], int): | |
| return False, f"image_id at index {i} must be an integer." | |
| if not isinstance(ann["category_id"], int): | |
| return False, f"category_id at index {i} must be an integer." | |
| if not isinstance(ann["score"], (int, float)): | |
| return False, f"score at index {i} must be a number." | |
| if not isinstance(ann["area"], (int, float)): | |
| return False, f"area at index {i} must be a number." | |
| bbox = ann["bbox"] | |
| if not (isinstance(bbox, list) and len(bbox) == 4 and all(isinstance(x, (int, float)) for x in bbox)): | |
| return False, f"bbox at index {i} must be a list of 4 numbers." | |
| if not isinstance(ann["segmentation"], list): | |
| return False, f"segmentation at index {i} must be a list." | |
| return True, "OK" | |
| def _upload_json(data: Any, path_in_repo: str, commit_message: str = "") -> None: | |
| with tempfile.NamedTemporaryFile(mode="w", suffix=".json", delete=False) as tmp: | |
| json.dump(data, tmp, ensure_ascii=False) | |
| tmp_path = tmp.name | |
| try: | |
| api_client().upload_file( | |
| path_or_fileobj=tmp_path, | |
| path_in_repo=path_in_repo, | |
| repo_id=DB_REPO_ID, | |
| repo_type=DB_REPO_TYPE, | |
| token=SUBMISSIONS_TOKEN, | |
| commit_message=commit_message or f"Add {path_in_repo}", | |
| ) | |
| finally: | |
| try: | |
| os.remove(tmp_path) | |
| except OSError: | |
| pass | |
| def _create_submission_record(*, pred, team, model_name, phase_codename, | |
| challenge_type, original_filename, username, email) -> str: | |
| if not SUBMISSIONS_TOKEN: | |
| raise ValueError("Missing SUBMISSIONS_TOKEN.") | |
| submission_id = str(uuid.uuid4()) | |
| ts = int(time.time()) | |
| meta = { | |
| "submission_id": submission_id, | |
| "team": team.strip(), | |
| "model": model_name.strip(), | |
| "phase_codename": phase_codename, | |
| "challenge_type": challenge_type, | |
| "timestamp": ts, | |
| "original_filename": original_filename, | |
| "username": username, | |
| "email": email, | |
| } | |
| status = {"state": "queued", "timestamp": ts} | |
| base = f"submissions/{submission_id}" | |
| _upload_json(pred, f"{base}/pred.json", f"pred {submission_id}") | |
| _upload_json(meta, f"{base}/meta.json", f"meta {submission_id}") | |
| _upload_json(status, f"{base}/status.json", f"status {submission_id}") | |
| return submission_id | |
| def _load_leaderboard_df() -> pd.DataFrame: | |
| empty = pd.DataFrame(columns=["team", "model", "phase_codename", *LEADERBOARD_METRICS, "timestamp"]) | |
| if not SUBMISSIONS_TOKEN: | |
| return empty | |
| try: | |
| path = hf_hub_download( | |
| repo_id=DB_REPO_ID, repo_type=DB_REPO_TYPE, | |
| filename="leaderboard.jsonl", token=SUBMISSIONS_TOKEN | |
| ) | |
| except HfHubHTTPError as e: | |
| if "404" in str(e): | |
| return empty | |
| raise | |
| rows = [] | |
| with open(path, "r", encoding="utf-8") as f: | |
| for line in f: | |
| line = line.strip() | |
| if not line: | |
| continue | |
| try: | |
| rows.append(json.loads(line)) | |
| except json.JSONDecodeError: | |
| continue | |
| if not rows: | |
| return empty | |
| df = pd.DataFrame(rows) | |
| for col in ["team", "model", "phase_codename", "timestamp", *LEADERBOARD_METRICS]: | |
| if col not in df.columns: | |
| df[col] = None | |
| if DEFAULT_SORT_METRIC in df.columns: | |
| df = df.sort_values(by=DEFAULT_SORT_METRIC, ascending=False, kind="mergesort") | |
| return df | |
| def _load_user_submissions(username: str) -> List[Dict]: | |
| if not SUBMISSIONS_TOKEN: | |
| return [] | |
| try: | |
| files = api_client().list_repo_files( | |
| repo_id=DB_REPO_ID, repo_type=DB_REPO_TYPE, token=SUBMISSIONS_TOKEN | |
| ) | |
| except Exception: | |
| return [] | |
| results = [] | |
| for f in files: | |
| if not (f.startswith("submissions/") and f.endswith("/meta.json")): | |
| continue | |
| try: | |
| sid = f.split("/")[1] | |
| meta_path = hf_hub_download( | |
| repo_id=DB_REPO_ID, repo_type=DB_REPO_TYPE, | |
| filename=f, token=SUBMISSIONS_TOKEN | |
| ) | |
| meta = json.load(open(meta_path)) | |
| if meta.get("username", "").lower() != username.lower(): | |
| continue | |
| try: | |
| status_path = hf_hub_download( | |
| repo_id=DB_REPO_ID, repo_type=DB_REPO_TYPE, | |
| filename=f"submissions/{sid}/status.json", token=SUBMISSIONS_TOKEN | |
| ) | |
| status = json.load(open(status_path)) | |
| except Exception: | |
| status = {"state": "unknown"} | |
| metrics = status.get("metrics", {}) if status.get("state") == "done" else {} | |
| error = status.get("error", "") if status.get("state") == "failed" else "" | |
| results.append({ | |
| "submission_id": sid, | |
| "team": meta.get("team", ""), | |
| "model": meta.get("model", ""), | |
| "phase": meta.get("phase_codename", ""), | |
| "challenge_type": meta.get("challenge_type", ""), | |
| "timestamp": meta.get("timestamp", 0), | |
| "state": status.get("state", "unknown"), | |
| "error": error[:120] if error else "", | |
| **metrics, | |
| }) | |
| except Exception: | |
| continue | |
| results.sort(key=lambda x: x["timestamp"], reverse=True) | |
| return results | |
| # ========================= | |
| # GRADIO HANDLER FUNCTIONS | |
| # ========================= | |
| def load_leaderboard(): | |
| try: | |
| df = _load_leaderboard_df() | |
| except Exception as e: | |
| return pd.DataFrame(), f"β Could not load leaderboard: {e}" | |
| if not df.empty and "phase_codename" in df.columns: | |
| df = df[df["phase_codename"] == "test-challenge2024"] | |
| if df.empty: | |
| return pd.DataFrame(), "βΉοΈ No scored Standard phase submissions yet. Be the first!" | |
| df_display = df.copy() | |
| df_display.insert(0, "Rank", range(1, len(df_display) + 1)) | |
| if "timestamp" in df_display.columns: | |
| df_display["Scored At"] = pd.to_datetime( | |
| df_display["timestamp"], unit="s", errors="coerce" | |
| ).dt.strftime("%d %b %Y, %I:%M %p") | |
| df_display.drop(columns=["timestamp"], inplace=True) | |
| for col in ["username", "email", "phase_codename", "submission_id"]: | |
| if col in df_display.columns: | |
| df_display.drop(columns=[col], inplace=True) | |
| return df_display, "" | |
| def handle_submit(file, team, model_name, phase_label, challenge_type, profile: gr.OAuthProfile | None): | |
| if profile is None: | |
| return "β You must be logged in with your HuggingFace account to submit.", "" | |
| username = profile.username | |
| email = getattr(profile, "email", "") or "" | |
| if not SUBMISSIONS_TOKEN: | |
| return "β Missing SUBMISSIONS_TOKEN. Add it in Space Settings β Secrets.", "" | |
| if file is None: | |
| return "β Please upload a JSON file.", "" | |
| if not team.strip(): | |
| return "β Please enter a Team / Display Name.", "" | |
| if not model_name.strip(): | |
| return "β Please enter a Model Name.", "" | |
| # Resolve phase codename first (needed for cap check) | |
| phase_codename = next((p["codename"] for p in PHASES if p["label"] == phase_label), phase_label) | |
| cap = _get_cap_for_phase(phase_codename) | |
| # Daily cap check (phase-specific) | |
| subs_today = _count_submissions_today(username, phase_codename) | |
| if subs_today >= cap: | |
| phase_label_str = "challenge" if phase_codename == "test-challenge2024" else "this" | |
| return f"β You've reached your daily limit of {cap} submission(s) for the {phase_label_str} phase. Come back tomorrow!", "" | |
| # Parse JSON | |
| try: | |
| with open(file, "r", encoding="utf-8") as f: | |
| pred_obj = json.load(f) | |
| except Exception: | |
| return "β Could not parse JSON file.", "" | |
| # Validate | |
| ok, msg = _validate_submission_json(pred_obj) | |
| if not ok: | |
| return f"β Invalid submission format: {msg}", "" | |
| original_filename = os.path.basename(file) | |
| # Upload | |
| try: | |
| submission_id = _create_submission_record( | |
| pred=pred_obj, | |
| team=team, | |
| model_name=model_name, | |
| phase_codename=phase_codename, | |
| challenge_type=challenge_type, | |
| original_filename=original_filename, | |
| username=username, | |
| email=email, | |
| ) | |
| except Exception as e: | |
| return f"β Upload failed: {e}", "" | |
| remaining = cap - subs_today - 1 | |
| return ( | |
| f"β Submission queued successfully! Visit **My Submissions** to see the results. You have {remaining}/{cap} submissions remaining today for this phase.", | |
| submission_id, | |
| ) | |
| def load_my_submissions(phase_filter: str, profile: gr.OAuthProfile | None): | |
| if profile is None: | |
| return pd.DataFrame(), "β Please log in to view your submissions.", "" | |
| username = profile.username | |
| submissions = _load_user_submissions(username) | |
| if not submissions: | |
| return pd.DataFrame(), "βΉοΈ No submissions yet. Head to Submit Predictions to get started!", "" | |
| # Keep unfiltered list for accurate stats | |
| all_submissions = submissions[:] | |
| if phase_filter and phase_filter != "All": | |
| submissions = [s for s in submissions if s["phase"] == phase_filter] | |
| if not submissions: | |
| return pd.DataFrame(), f"βΉοΈ No submissions found for phase **{phase_filter}**.", "" | |
| state_icons = {"queued": "π‘", "running": "π΅", "done": "π’", "failed": "π΄", "unknown": "βͺ"} | |
| df = pd.DataFrame(submissions) | |
| if "timestamp" in df.columns: | |
| df["Submitted At"] = pd.to_datetime( | |
| df["timestamp"], unit="s", errors="coerce" | |
| ).dt.strftime("%d %b %Y, %I:%M %p") | |
| if "state" in df.columns: | |
| df["Status"] = df["state"].apply(lambda s: f"{state_icons.get(s, 'βͺ')} {s.capitalize()}") | |
| display_cols = ["Submitted At", "Status", "team", "model", "phase", "challenge_type", "error"] | |
| metric_cols = [m for m in LEADERBOARD_METRICS if m in df.columns] | |
| display_cols += metric_cols | |
| df_display = df[[c for c in display_cols if c in df.columns]].copy() | |
| df_display.rename(columns={ | |
| "team": "Team", "model": "Model", "phase": "Phase", | |
| "challenge_type": "Challenge Type", "error": "Error", | |
| }, inplace=True) | |
| for m in metric_cols: | |
| if m in df_display.columns: | |
| df_display[m] = df_display[m].apply(lambda x: f"{x:.4f}" if pd.notna(x) else "") | |
| # Summary stats (always from unfiltered list) | |
| total = len(all_submissions) | |
| done = sum(1 for s in all_submissions if s["state"] == "done") | |
| today_count = sum( | |
| 1 for s in all_submissions | |
| if datetime.fromtimestamp(s["timestamp"], tz=timezone.utc).strftime("%Y-%m-%d") == _today_utc_str() | |
| ) | |
| stats = ( | |
| f"**Total:** {total} | " | |
| f"**Scored:** {done} | " | |
| f"**Today:** {today_count}/{DAILY_SUBMISSION_CAP}" | |
| ) | |
| return df_display, "", stats | |
| def get_daily_cap_info(profile: gr.OAuthProfile | None): | |
| if profile is None: | |
| return "" | |
| lines = [] | |
| for p in PHASES: | |
| cap = _get_cap_for_phase(p["codename"]) | |
| used = _count_submissions_today(profile.username, p["codename"]) | |
| remaining = cap - used | |
| lines.append(f"**{p['label'].split('(')[0].strip()}:** {remaining}/{cap} remaining") | |
| return " \n".join(lines) | |
| def get_user_greeting(profile: gr.OAuthProfile | None): | |
| if profile is None: | |
| return "π Log in with your HuggingFace account to submit predictions." | |
| return f"π€ Logged in as **{profile.username}**" | |
| # ========================= | |
| # STATIC CONTENT | |
| # ========================= | |
| EVAL_DETAILS_MD = """ | |
| ### How is the Score Calculated? | |
| Your submission is evaluated automatically against hidden ground-truth annotations using **pycocotools**. | |
| | Metric | Description | | |
| |--------|-------------| | |
| | `bbox_mAP` | Bounding box mean average precision | | |
| | `bbox_AP50` | Bounding box AP at IoU = 0.50 | | |
| | `segm_mAP` | Segmentation mean average precision | | |
| | `segm_AP50` | Segmentation AP at IoU = 0.50 *(default ranking metric)* | | |
| """ | |
| FORMAT_MD = """ | |
| ### Submission Format | |
| Your JSON file must be a **list of annotation objects**, each containing: | |
| ```json | |
| [ | |
| { | |
| "image_id": 123, | |
| "category_id": 101, | |
| "score": 0.95, | |
| "area": 1024.0, | |
| "bbox": [x, y, width, height], | |
| "segmentation": [[x1, y1, x2, y2, ...]] | |
| }, | |
| ... | |
| ] | |
| ``` | |
| """ | |
| CHALLENGES = [ | |
| { | |
| "id": "object-localization", | |
| "title": "Object Localization", | |
| "emoji": "π―", | |
| "description": "Detect and segment objects in images taken by blind photographers. Submit bounding box and instance segmentation predictions evaluated with pycocotools.", | |
| "metrics": "bbox_mAP Β· bbox_AP50 Β· segm_mAP Β· segm_AP50", | |
| "route": "/object-localization", | |
| "active": True, | |
| }, | |
| { | |
| "id": "vqa", | |
| "title": "Visual Question Answering", | |
| "emoji": "π€", | |
| "description": "Answer open-ended questions about images taken by blind users. Models are evaluated on answer accuracy and relevance.", | |
| "metrics": "Coming soon", | |
| "route": "/vqa", | |
| "active": True, | |
| }, | |
| { | |
| "id": "answer-grounding", | |
| "title": "Answer Grounding", | |
| "emoji": "π", | |
| "description": "Ground free-form answers to visual regions in images taken by blind photographers.", | |
| "metrics": "Coming soon", | |
| "route": "/answer-grounding", | |
| "active": False, | |
| }, | |
| ] | |
| def _challenge_card_html(c: dict) -> str: | |
| """Single self-contained card with button inside the HTML.""" | |
| if c["active"]: | |
| return f""" | |
| <div style="border:2px solid #2563eb;border-radius:12px;padding:24px; | |
| background:#f0f7ff;display:flex;flex-direction:column;height:260px;box-sizing:border-box;"> | |
| <div style="font-size:2rem;margin-bottom:8px;">{c['emoji']}</div> | |
| <h3 style="margin:0 0 6px 0;color:#1e40af;font-size:1rem;font-weight:700;">{c['title']}</h3> | |
| <p style="margin:0 0 10px 0;color:#374151;font-size:0.82rem;line-height:1.45;flex:1;">{c['description']}</p> | |
| <div style="background:#dbeafe;border-radius:5px;padding:4px 8px; | |
| font-size:0.72rem;color:#1d4ed8;font-family:monospace;margin-bottom:14px;"> | |
| π {c['metrics']} | |
| </div> | |
| <button onclick="window.location.href='{c['route']}'" | |
| style="background:#2563eb;color:white;border:none;padding:8px 0;width:100%; | |
| border-radius:7px;font-size:0.85rem;font-weight:600;cursor:pointer;"> | |
| Enter Challenge β | |
| </button> | |
| </div>""" | |
| else: | |
| return f""" | |
| <div style="border:2px solid #e5e7eb;border-radius:12px;padding:24px; | |
| background:#f9fafb;display:flex;flex-direction:column;height:260px;box-sizing:border-box;opacity:0.55;"> | |
| <div style="font-size:2rem;margin-bottom:8px;">{c['emoji']}</div> | |
| <h3 style="margin:0 0 6px 0;color:#6b7280;font-size:1rem;font-weight:700;">{c['title']}</h3> | |
| <p style="margin:0 0 10px 0;color:#9ca3af;font-size:0.82rem;line-height:1.45;flex:1;">{c['description']}</p> | |
| <div style="background:#f3f4f6;border-radius:5px;padding:4px 8px; | |
| font-size:0.72rem;color:#9ca3af;font-family:monospace;margin-bottom:14px;"> | |
| π {c['metrics']} | |
| </div> | |
| <button disabled | |
| style="background:#e5e7eb;color:#9ca3af;border:none;padding:8px 0;width:100%; | |
| border-radius:7px;font-size:0.85rem;font-weight:600;cursor:not-allowed;"> | |
| π Coming Soon | |
| </button> | |
| </div>""" | |
| # ========================= | |
| # BUILD UI | |
| # ========================= | |
| with gr.Blocks() as demo: | |
| # ββ HOME PAGE βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| with gr.Row(): | |
| with gr.Column(scale=5): | |
| gr.Markdown("# π VizWiz Benchmark Arena") | |
| gr.Markdown( | |
| "Automated evaluation platform for VizWiz challenges β " | |
| "datasets collected from blind photographers using a smartphone app." | |
| ) | |
| with gr.Column(scale=1, min_width=160): | |
| gr.LoginButton(size="lg") | |
| home_greeting = gr.Markdown("π Log in to submit.") | |
| gr.Markdown("---") | |
| gr.Markdown("## Challenges") | |
| gr.Markdown( | |
| "Choose a challenge to view its leaderboard, submit predictions, and track your results." | |
| ) | |
| with gr.Row(equal_height=True): | |
| for c in CHALLENGES: | |
| with gr.Column(scale=1): | |
| gr.HTML(_challenge_card_html(c)) | |
| gr.Markdown( | |
| "---\n*More challenges coming soon. " | |
| "All challenges use HuggingFace OAuth β log in once to access everything.*" | |
| ) | |
| demo.load(get_user_greeting, outputs=[home_greeting]) | |
| # ββ OBJECT LOCALIZATION PAGE ββββββββββββββββββββββββββββββββββββββββββ | |
| with demo.route("Object Localization", "/object-localization") as obj_loc: | |
| with gr.Row(): | |
| with gr.Column(scale=5): | |
| gr.Markdown("# π― Object Localization Challenge") | |
| gr.Markdown( | |
| "Submit bounding box and instance segmentation predictions " | |
| "evaluated automatically against hidden ground-truth annotations." | |
| ) | |
| with gr.Column(scale=1, min_width=160): | |
| gr.LoginButton(size="lg") | |
| ol_greeting = gr.Markdown("π Log in to submit.") | |
| gr.Markdown("---") | |
| with gr.Tabs(): | |
| # Leaderboard | |
| with gr.TabItem("π Leaderboard"): | |
| gr.Markdown("### Challenge Phase Rankings") | |
| gr.Markdown(f"Ranked by **{DEFAULT_SORT_METRIC}** (descending). Challenge phase only.") | |
| with gr.Accordion("π How is the Score Calculated?", open=False): | |
| gr.Markdown(EVAL_DETAILS_MD) | |
| lb_msg = gr.Markdown("") | |
| lb_table = gr.Dataframe(interactive=False, wrap=True) | |
| refresh_lb_btn = gr.Button("π Refresh Leaderboard", variant="secondary", size="sm") | |
| def refresh_leaderboard(profile: gr.OAuthProfile | None): | |
| df, msg = load_leaderboard() | |
| return df, msg, get_user_greeting(profile) | |
| refresh_lb_btn.click(refresh_leaderboard, outputs=[lb_table, lb_msg, ol_greeting]) | |
| obj_loc.load(refresh_leaderboard, outputs=[lb_table, lb_msg, ol_greeting]) | |
| # Submit | |
| with gr.TabItem("π Submit Predictions"): | |
| with gr.Row(): | |
| submit_greeting = gr.Markdown("π Log in with HuggingFace to submit.") | |
| cap_info = gr.Markdown("") | |
| gr.Markdown("---") | |
| with gr.Row(): | |
| with gr.Column(scale=3): | |
| gr.Markdown("#### Upload Submission File") | |
| file_input = gr.File(label="Choose a JSON file", file_types=[".json"]) | |
| with gr.Accordion("π Submission Format", open=False): | |
| gr.Markdown(FORMAT_MD) | |
| with gr.Column(scale=2): | |
| gr.Markdown("#### Submission Info") | |
| team_input = gr.Textbox(label="Team / Display Name", placeholder="e.g. My Awesome Team") | |
| model_input = gr.Textbox(label="Model Name", placeholder="e.g. ResNet50-FPN") | |
| phase_input = gr.Dropdown( | |
| label="Phase", | |
| choices=[p["label"] for p in PHASES], | |
| value=PHASES[0]["label"], | |
| ) | |
| challenge_input = gr.Radio( | |
| label="Challenge Type", | |
| choices=CHALLENGE_TYPES, | |
| value=CHALLENGE_TYPES[0], | |
| ) | |
| submit_btn = gr.Button("Submit (Queue for Evaluation)", variant="primary", size="lg") | |
| submit_status = gr.Markdown("") | |
| submission_id_box = gr.Code(label="Submission ID", language=None, visible=False) | |
| def do_submit(file, team, model_name, phase_label, challenge_type, profile: gr.OAuthProfile | None): | |
| msg, sid = handle_submit(file, team, model_name, phase_label, challenge_type, profile) | |
| return msg, gr.update(value=sid, visible=bool(sid)) | |
| submit_btn.click( | |
| do_submit, | |
| inputs=[file_input, team_input, model_input, phase_input, challenge_input], | |
| outputs=[submit_status, submission_id_box], | |
| ) | |
| def update_submit_ui(profile: gr.OAuthProfile | None): | |
| return get_user_greeting(profile), get_daily_cap_info(profile) | |
| obj_loc.load(update_submit_ui, outputs=[submit_greeting, cap_info]) | |
| # My Submissions | |
| with gr.TabItem("π My Submissions"): | |
| my_sub_greeting = gr.Markdown("π Log in with HuggingFace to view your submissions.") | |
| my_sub_stats = gr.Markdown("") | |
| with gr.Row(): | |
| phase_filter = gr.Dropdown( | |
| label="Filter by Phase", | |
| choices=["All"] + [p["codename"] for p in PHASES], | |
| value="All", | |
| scale=2, | |
| ) | |
| refresh_my_btn = gr.Button("π Refresh", variant="secondary", scale=1) | |
| my_sub_msg = gr.Markdown("") | |
| my_sub_table = gr.Dataframe(interactive=False, wrap=True) | |
| def refresh_my_subs(phase_filter, profile: gr.OAuthProfile | None): | |
| df, msg, stats = load_my_submissions(phase_filter, profile) | |
| return df, msg, stats, get_user_greeting(profile) | |
| refresh_my_btn.click( | |
| refresh_my_subs, | |
| inputs=[phase_filter], | |
| outputs=[my_sub_table, my_sub_msg, my_sub_stats, my_sub_greeting], | |
| ) | |
| phase_filter.change( | |
| refresh_my_subs, | |
| inputs=[phase_filter], | |
| outputs=[my_sub_table, my_sub_msg, my_sub_stats, my_sub_greeting], | |
| ) | |
| obj_loc.load( | |
| refresh_my_subs, | |
| inputs=[phase_filter], | |
| outputs=[my_sub_table, my_sub_msg, my_sub_stats, my_sub_greeting], | |
| ) | |
| # ββ VQA PAGE ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # with demo.route("Visual Question Answering", "/vqa"): | |
| # gr.Markdown("# π€ Visual Question Answering") | |
| # gr.Markdown("### π Coming Soon") | |
| # gr.Markdown( | |
| # "This challenge is currently under development. " | |
| # ) | |
| # ββ VQA PAGE ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| with demo.route("Visual Question Answering", "/vqa") as vqa_page: | |
| with gr.Row(): | |
| with gr.Column(scale=5): | |
| gr.Markdown("# π€ Visual Question Answering Challenge") | |
| gr.Markdown( | |
| "Answer open-ended questions about images taken by blind users. " | |
| "Models are evaluated on answer accuracy and relevance." | |
| ) | |
| with gr.Column(scale=1, min_width=160): | |
| gr.LoginButton(size="lg") | |
| vqa_greeting = gr.Markdown("π Log in to submit.") | |
| gr.Markdown("---") | |
| with gr.Tabs(): | |
| # 1. Leaderboard Tab | |
| with gr.TabItem("π Leaderboard"): | |
| gr.Markdown("### VQA Challenge Rankings") | |
| gr.Markdown("Ranked by **Accuracy** (descending).") | |
| vqa_lb_msg = gr.Markdown("") | |
| vqa_lb_table = gr.Dataframe(interactive=False, wrap=True) | |
| refresh_vqa_lb_btn = gr.Button("π Refresh Leaderboard", variant="secondary", size="sm") | |
| def refresh_vqa_leaderboard(profile: gr.OAuthProfile | None): | |
| # Task 2: Default empty DF structure | |
| cols = ["Rank", "Team", "Model", "Accuracy", "Scored At"] | |
| empty_df = pd.DataFrame(columns=cols) | |
| try: | |
| df = _load_leaderboard_df() | |
| # Filter for VQA challenge specifically | |
| if not df.empty and "challenge_type" in df.columns: | |
| df = df[df["challenge_type"].str.lower() == "visual question answering"] | |
| except Exception as e: | |
| return empty_df, f"β Could not load leaderboard: {e}", get_user_greeting(profile) | |
| if df.empty: | |
| return empty_df, "βΉοΈ No VQA submissions yet. Be the first!", get_user_greeting(profile) | |
| df_display = df.copy() | |
| df_display.insert(0, "Rank", range(1, len(df_display) + 1)) | |
| if "timestamp" in df_display.columns: | |
| df_display["Scored At"] = pd.to_datetime( | |
| df_display["timestamp"], unit="s", errors="coerce" | |
| ).dt.strftime("%d %b %Y, %I:%M %p") | |
| # Filter to VQA specific metrics | |
| display_cols = ["Rank", "team", "model", "accuracy", "Scored At"] | |
| df_display = df_display[[c for c in display_cols if c in df_display.columns]] | |
| df_display.rename(columns={"team": "Team", "model": "Model", "accuracy": "Accuracy"}, inplace=True) | |
| return df_display, "", get_user_greeting(profile) | |
| refresh_vqa_lb_btn.click(refresh_vqa_leaderboard, outputs=[vqa_lb_table, vqa_lb_msg, vqa_greeting]) | |
| vqa_page.load(refresh_vqa_leaderboard, outputs=[vqa_lb_table, vqa_lb_msg, vqa_greeting]) | |
| # 2. Submit Tab | |
| with gr.TabItem("π Submit Predictions"): | |
| with gr.Row(): | |
| vqa_submit_greeting = gr.Markdown("π Log in with HuggingFace to submit.") | |
| vqa_cap_info = gr.Markdown("") | |
| gr.Markdown("---") | |
| with gr.Row(): | |
| with gr.Column(scale=3): | |
| gr.Markdown("#### Upload VQA Results") | |
| vqa_file_input = gr.File(label="Choose a JSON file", file_types=[".json"]) | |
| with gr.Column(scale=2): | |
| gr.Markdown("#### Submission Info") | |
| vqa_team_input = gr.Textbox(label="Team / Display Name") | |
| vqa_model_input = gr.Textbox(label="Model Name") | |
| vqa_phase_input = gr.Dropdown( | |
| label="Phase", | |
| choices=[p["label"] for p in PHASES], | |
| value=PHASES[0]["label"], | |
| ) | |
| vqa_submit_btn = gr.Button("Submit (Queue for Evaluation)", variant="primary", size="lg") | |
| vqa_submit_status = gr.Markdown("") | |
| vqa_sid_box = gr.Code(label="Submission ID", visible=False) | |
| def do_vqa_submit(file, team, model, phase, profile: gr.OAuthProfile | None): | |
| # We pass "Visual Question Answering" as the challenge type | |
| msg, sid = handle_submit(file, team, model, phase, "Visual Question Answering", profile) | |
| return msg, gr.update(value=sid, visible=bool(sid)) | |
| vqa_submit_btn.click( | |
| do_vqa_submit, | |
| inputs=[vqa_file_input, vqa_team_input, vqa_model_input, vqa_phase_input], | |
| outputs=[vqa_submit_status, vqa_sid_box], | |
| ) | |
| def update_vqa_submit_ui(profile: gr.OAuthProfile | None): | |
| return get_user_greeting(profile), get_daily_cap_info(profile) | |
| vqa_page.load(update_vqa_submit_ui, outputs=[vqa_submit_greeting, vqa_cap_info]) | |
| # 3. My Submissions Tab | |
| with gr.TabItem("π My Submissions"): | |
| vqa_my_sub_greeting = gr.Markdown("π Log in to view your submissions.") | |
| vqa_my_sub_stats = gr.Markdown("") | |
| with gr.Row(): | |
| vqa_phase_filter = gr.Dropdown( | |
| label="Filter by Phase", | |
| choices=["All"] + [p["codename"] for p in PHASES], | |
| value="All", | |
| scale=2 | |
| ) | |
| vqa_refresh_my_btn = gr.Button("π Refresh", variant="secondary", scale=1) | |
| vqa_my_sub_table = gr.Dataframe(interactive=False, wrap=True) | |
| def refresh_vqa_my_subs(phase_filter, profile: gr.OAuthProfile | None): | |
| df, msg, stats = load_my_submissions(phase_filter, profile) | |
| # Further filter results to only show VQA | |
| if not df.empty and "Challenge Type" in df.columns: | |
| df = df[df["Challenge Type"] == "Visual Question Answering"] | |
| return df, msg, stats, get_user_greeting(profile) | |
| vqa_refresh_my_btn.click( | |
| refresh_vqa_my_subs, | |
| inputs=[vqa_phase_filter], | |
| outputs=[vqa_my_sub_table, gr.Markdown(), vqa_my_sub_stats, vqa_my_sub_greeting], | |
| ) | |
| vqa_page.load( | |
| refresh_vqa_my_subs, | |
| inputs=[vqa_phase_filter], | |
| outputs=[vqa_my_sub_table, gr.Markdown(), vqa_my_sub_stats, vqa_my_sub_greeting], | |
| ) | |
| # ββ ANSWER GROUNDING PAGE βββββββββββββββββββββββββββββββββββββββββββββ | |
| with demo.route("Answer Grounding", "/answer-grounding"): | |
| gr.Markdown("# π Answer Grounding") | |
| gr.Markdown("### π Coming Soon") | |
| gr.Markdown( | |
| "This challenge is currently under development. " | |
| ) | |
| if __name__ == "__main__": | |
| demo.launch() |