evaluationServer / main_prev.py
NidhiS09's picture
Restructure for challenges
7ee2ab0
Raw
History Blame Contribute Delete
33.7 kB
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()