ChenlongDeng commited on
Commit
45c9afd
·
verified ·
1 Parent(s): 7f17b23

Upload 12 files

Browse files
.gitattributes CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ static/ruc-logo.png filter=lfs diff=lfs merge=lfs -text
Dockerfile ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.9
2
+
3
+ WORKDIR /code
4
+
5
+ COPY ./requirements.txt /code/requirements.txt
6
+
7
+ # Set cache directory permissions to prevent HF errors
8
+ RUN mkdir -p /code/cache && chmod 777 /code/cache
9
+ ENV HF_HOME=/code/cache
10
+
11
+ RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
12
+
13
+ COPY . .
14
+
15
+ # Ensure submissions directory exists and is writable
16
+ RUN mkdir -p /code/submissions && chmod 777 /code/submissions
17
+
18
+ # Expose port 7860
19
+ EXPOSE 7860
20
+
21
+ CMD ["gunicorn", "-b", "0.0.0.0:7860", "app:app"]
README.md CHANGED
@@ -1,10 +1,138 @@
1
  ---
2
  title: DISBench Leaderboard
3
- emoji: 📉
4
- colorFrom: red
5
- colorTo: yellow
6
  sdk: docker
7
- pinned: false
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
  title: DISBench Leaderboard
3
+ emoji: 🏆
4
+ colorFrom: yellow
5
+ colorTo: red
6
  sdk: docker
7
+ app_port: 7860
8
+ pinned: true
9
  ---
10
 
11
+ # 🏆 DISBench Leaderboard
12
+
13
+ Welcome to the official leaderboard for **DISBench (DeepImageSearch Benchmark)**!
14
+
15
+ DISBench is a comprehensive benchmark for evaluating DeepImageSearch methods on photo collections. This leaderboard tracks and compares the performance of various approaches on standardized evaluation metrics.
16
+
17
+ ## 📊 Evaluation Metrics
18
+
19
+ ### Core Metrics
20
+
21
+ - **Exact Match (EM)**: Percentage of queries where the predicted photo set exactly matches the ground truth
22
+ - **F1 Score**: Harmonic mean of precision and recall at the set level
23
+
24
+ ### Query Types
25
+
26
+ 1. **Intra-Event**: Search within a single event or time period (e.g., "sunset photos from our beach vacation")
27
+ 2. **Inter-Event**: Search across multiple events or time periods (e.g., "all birthday party photos from last year")
28
+
29
+ ### Tracks
30
+
31
+ - **Standard Track**: Uses predefined constraints and standard model configurations
32
+ - **Open Track**: Allows custom models, additional training data, and external resources
33
+
34
+ All metrics are reported as:
35
+ - Overall (all queries)
36
+ - Intra-event only
37
+ - Inter-event only
38
+
39
+ ## 🚀 How to Submit
40
+
41
+ ### Step-by-Step Guide
42
+
43
+ 1. **Prepare Your Results**
44
+ - Run your method on the DISBench test set
45
+ - Format predictions according to the submission schema (see below)
46
+
47
+ 2. **Submit via Web Interface**
48
+ - Navigate to the **Submit** tab on this Space
49
+ - Upload your JSON file containing metadata and predictions
50
+ - Click "Submit"
51
+
52
+ 3. **Automated Processing**
53
+ - The system validates your submission format
54
+ - A Pull Request is automatically created
55
+ - Maintainers review the submission
56
+
57
+ 4. **Leaderboard Update**
58
+ - Once approved and merged, the Space automatically rebuilds
59
+ - Your results are evaluated against ground truth
60
+ - The leaderboard updates with your scores
61
+
62
+ ### Submission Format
63
+
64
+ ```json
65
+ {
66
+ "meta": {
67
+ "method_name": "Your Method Name",
68
+ "organization": "Your Organization",
69
+ "track": "Standard",
70
+ "agent_framework": "Your Agent Framework (if applicable)",
71
+ "backbone_model": "Your Backbone Model",
72
+ "retriever_model": "Your Retriever Model (if applicable)",
73
+ "project_url": "https://github.com/your-repo"
74
+ },
75
+ "predictions": {
76
+ "1": ["photo_id_1", "photo_id_2", "photo_id_3"],
77
+ "2": ["photo_id_4"],
78
+ "3": ["photo_id_5", "photo_id_6"],
79
+ ...
80
+ }
81
+ }
82
+ ```
83
+
84
+ ### Field Descriptions
85
+
86
+ **Meta Fields:**
87
+ - `method_name` (required): Name of your method/system
88
+ - `organization` (optional): Your institution or organization
89
+ - `track` (required): Either "Standard" or "Open"
90
+ - `agent_framework` (optional): Agent framework used (e.g., "ReAct", "AutoGPT")
91
+ - `backbone_model` (required): Core model used (e.g., "GPT-4", "Claude-3")
92
+ - `retriever_model` (optional): Retrieval model used (e.g., "CLIP-ViT-L/14", "BM25")
93
+ - `project_url` (optional): Link to your project/paper
94
+
95
+ **Predictions:**
96
+ - Keys are query IDs (as strings)
97
+ - Values are arrays of photo IDs (as strings)
98
+ - Photo IDs should match those in the ground truth dataset
99
+
100
+ ## 📋 Leaderboard Rules
101
+
102
+ ### Uniqueness & Deduplication
103
+
104
+ Each entry is uniquely identified by the combination of:
105
+ - Method name
106
+ - Agent framework
107
+ - Backbone model
108
+ - Retriever model
109
+ - Track
110
+
111
+ If you submit multiple times with the same configuration, only the **latest submission** will appear on the leaderboard.
112
+
113
+ ### Ranking
114
+
115
+ Entries are ranked by **Overall EM Score** in descending order. The leaderboard displays:
116
+ - Overall EM & F1
117
+ - Intra-event EM & F1
118
+ - Inter-event EM & F1
119
+
120
+ ### Separate Tracks
121
+
122
+ Standard and Open track submissions are ranked separately to ensure fair comparison.
123
+
124
+ ## 📄 Citation
125
+
126
+ If you use DISBench in your research, please cite:
127
+
128
+ ```bibtex
129
+ @misc{deng2026deepimagesearchbenchmarkingmultimodalagents,
130
+ title={DeepImageSearch: Benchmarking Multimodal Agents for Context-Aware Image Retrieval in Visual Histories},
131
+ author={Chenlong Deng and Mengjie Deng and Junjie Wu and Dun Zeng and Teng Wang and Qingsong Xie and Jiadeng Huang and Shengjie Ma and Changwang Zhang and Zhaoxiang Wang and Jun Wang and Yutao Zhu and Zhicheng Dou},
132
+ year={2026},
133
+ eprint={2602.10809},
134
+ archivePrefix={arXiv},
135
+ primaryClass={cs.CV},
136
+ url={https://arxiv.org/abs/2602.10809}
137
+ }
138
+ ```
app.py ADDED
@@ -0,0 +1,239 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ app.py - DISBench Leaderboard Main Application
3
+
4
+ Startup Flow:
5
+ 1. Space rebuild (triggered by PR merge) → Docker container starts
6
+ 2. Call evaluate.run_evaluation() to scan new submissions in submissions/
7
+ 3. Calculate EM/F1 scores for new submissions, update leaderboard_data.json
8
+ 4. Commit updated data back to repository (persistence)
9
+ 5. Start Flask Web server
10
+ """
11
+
12
+ import os
13
+ import json
14
+ import logging
15
+ from datetime import datetime
16
+ from flask import Flask, render_template, request, redirect, url_for, jsonify
17
+ from huggingface_hub import HfApi, CommitOperationAdd
18
+
19
+ # Evaluation module
20
+ from evaluate import run_evaluation, commit_leaderboard_to_repo
21
+
22
+ logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
23
+ logger = logging.getLogger(__name__)
24
+
25
+ app = Flask(__name__)
26
+ app.secret_key = os.environ.get("SECRET_KEY", "disbench-leaderboard-secret-key")
27
+
28
+ # --- Configuration ---
29
+ LEADERBOARD_FILE = "leaderboard_data.json"
30
+ SUBMISSIONS_DIR = "submissions"
31
+ os.makedirs(SUBMISSIONS_DIR, exist_ok=True)
32
+
33
+ # HuggingFace Space configuration
34
+ HF_TOKEN = os.environ.get("HF_TOKEN")
35
+ SPACE_ID = os.environ.get("SPACE_ID", "RUC-NLPIR/DISBench-Leaderboard")
36
+
37
+
38
+ # ============================================================
39
+ # Automatic Evaluation on Startup
40
+ # ============================================================
41
+
42
+ def startup_evaluation():
43
+ """
44
+ Automatically run evaluation when the app starts.
45
+
46
+ When maintainers merge a PR containing new submission files,
47
+ HF Space will automatically rebuild and restart, and this function will be called:
48
+ - Scan all files in submissions/ directory
49
+ - Re-evaluate all submissions (deduplicate using configuration combinations)
50
+ - Compare with groundtruth.jsonl to calculate scores
51
+ - Update leaderboard_data.json
52
+ - Commit results back to repository for persistence
53
+
54
+ Note:
55
+ - Every startup re-evaluates all files, making the logic simpler
56
+ - submissions/ is the single source of truth
57
+ - Evaluation is fast and won't affect startup speed
58
+ """
59
+ logger.info("=" * 60)
60
+ logger.info("DISBench: Running startup evaluation...")
61
+ logger.info("=" * 60)
62
+
63
+ try:
64
+ total, _ = run_evaluation()
65
+
66
+ if total > 0:
67
+ logger.info(f"Evaluated all submissions. Committing to repo...")
68
+ commit_leaderboard_to_repo()
69
+ else:
70
+ logger.info("No submissions found.")
71
+
72
+ logger.info(f"Leaderboard has {total} unique configurations. Ready to serve.")
73
+
74
+ except Exception as e:
75
+ logger.error(f"Startup evaluation failed: {e}")
76
+ logger.info("Continuing with existing leaderboard data...")
77
+
78
+
79
+ # Execute startup evaluation
80
+ startup_evaluation()
81
+
82
+
83
+ # ============================================================
84
+ # Data Loading
85
+ # ============================================================
86
+
87
+ def load_leaderboard():
88
+ if os.path.exists(LEADERBOARD_FILE):
89
+ with open(LEADERBOARD_FILE, 'r', encoding='utf-8') as f:
90
+ return json.load(f)
91
+ return []
92
+
93
+
94
+ # ============================================================
95
+ # Submission Validation
96
+ # ============================================================
97
+
98
+ def validate_submission(submission):
99
+ errors = []
100
+ if not isinstance(submission, dict):
101
+ return ["Submission must be a JSON object with 'meta' and 'predictions' fields."]
102
+
103
+ meta = submission.get("meta")
104
+ preds = submission.get("predictions")
105
+
106
+ if not meta or not isinstance(meta, dict):
107
+ errors.append("Missing or invalid 'meta' field.")
108
+ else:
109
+ required_meta = ["method_name"]
110
+ for field in required_meta:
111
+ if field not in meta:
112
+ errors.append(f"Missing required field: meta.{field}")
113
+
114
+ valid_tracks = ["Standard", "Open"]
115
+ if meta.get("track") and meta["track"] not in valid_tracks:
116
+ errors.append(f"meta.track must be one of: {valid_tracks}")
117
+
118
+ if not preds or not isinstance(preds, dict):
119
+ errors.append("Missing or invalid 'predictions' field.")
120
+
121
+ return errors
122
+
123
+
124
+ # ============================================================
125
+ # PR Creation
126
+ # ============================================================
127
+
128
+ def create_pr_submission(submission_json, method_name):
129
+ """Create a PR via HF Hub API, upload submission file to submissions/ directory"""
130
+ if not HF_TOKEN:
131
+ raise RuntimeError(
132
+ "HF_TOKEN not configured. Please set the HF_TOKEN secret in your Space settings."
133
+ )
134
+
135
+ api = HfApi(token=HF_TOKEN)
136
+
137
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
138
+ safe_name = method_name.replace(" ", "-").replace("/", "_")
139
+ filename = f"{safe_name}_{timestamp}.json"
140
+ path_in_repo = f"submissions/{filename}"
141
+
142
+ content = json.dumps(submission_json, indent=2, ensure_ascii=False).encode("utf-8")
143
+
144
+ commit_info = api.create_commit(
145
+ repo_id=SPACE_ID,
146
+ repo_type="space",
147
+ operations=[
148
+ CommitOperationAdd(
149
+ path_in_repo=path_in_repo,
150
+ path_or_fileobj=content,
151
+ )
152
+ ],
153
+ commit_message=f"[Submission] Add results for {method_name}",
154
+ commit_description=(
155
+ f"**Method**: {method_name}\n"
156
+ f"**Organization**: {submission_json.get('meta', {}).get('organization', 'N/A')}\n"
157
+ f"**Track**: {submission_json.get('meta', {}).get('track', 'N/A')}\n"
158
+ f"**Agent**: {submission_json.get('meta', {}).get('agent_framework', 'N/A')}\n"
159
+ f"**Backbone**: {submission_json.get('meta', {}).get('backbone_model', 'N/A')}\n"
160
+ f"**Retriever**: {submission_json.get('meta', {}).get('retriever_model', 'N/A')}\n\n"
161
+ f"Submitted at {datetime.now().strftime('%Y-%m-%d %H:%M:%S UTC')}"
162
+ ),
163
+ create_pr=True,
164
+ )
165
+
166
+ return commit_info
167
+
168
+
169
+ # ============================================================
170
+ # Routes
171
+ # ============================================================
172
+
173
+ @app.route('/')
174
+ def index():
175
+ data = load_leaderboard()
176
+ return render_template('index.html', data=data)
177
+
178
+
179
+ @app.route('/upload', methods=['POST'])
180
+ def upload_file():
181
+ """Handle submission: validate → create PR → return result"""
182
+ if 'file' not in request.files:
183
+ return jsonify({"success": False, "error": "No file uploaded."}), 400
184
+
185
+ file = request.files['file']
186
+ if file.filename == '':
187
+ return jsonify({"success": False, "error": "No file selected."}), 400
188
+
189
+ try:
190
+ submission = json.load(file)
191
+ except json.JSONDecodeError as e:
192
+ return jsonify({"success": False, "error": f"Invalid JSON file: {e}"}), 400
193
+
194
+ errors = validate_submission(submission)
195
+ if errors:
196
+ return jsonify({"success": False, "error": "Validation failed.", "details": errors}), 400
197
+
198
+ method_name = submission["meta"]["method_name"]
199
+
200
+ # Local backup
201
+ safe_name = method_name.replace(" ", "-").replace("/", "_")
202
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
203
+ local_path = os.path.join(SUBMISSIONS_DIR, f"{safe_name}_{timestamp}.json")
204
+ with open(local_path, 'w', encoding='utf-8') as f:
205
+ json.dump(submission, f, indent=2, ensure_ascii=False)
206
+
207
+ # Create PR
208
+ try:
209
+ commit_info = create_pr_submission(submission, method_name)
210
+ pr_url = getattr(commit_info, 'pr_url', None)
211
+ return jsonify({
212
+ "success": True,
213
+ "message": f"Submission for '{method_name}' has been submitted as a Pull Request!",
214
+ "pr_url": pr_url or f"https://huggingface.co/spaces/{SPACE_ID}/discussions",
215
+ })
216
+ except RuntimeError as e:
217
+ return jsonify({
218
+ "success": True,
219
+ "message": (
220
+ f"Submission for '{method_name}' saved locally. "
221
+ f"PR creation skipped: {str(e)}. "
222
+ f"Maintainers will review it manually."
223
+ ),
224
+ "pr_url": None,
225
+ })
226
+ except Exception as e:
227
+ return jsonify({
228
+ "success": True,
229
+ "message": (
230
+ f"Submission for '{method_name}' saved locally, "
231
+ f"but PR creation failed: {str(e)}. "
232
+ f"Please contact the maintainers."
233
+ ),
234
+ "pr_url": None,
235
+ })
236
+
237
+
238
+ if __name__ == '__main__':
239
+ app.run(debug=False, host="0.0.0.0", port=7860)
evaluate.py ADDED
@@ -0,0 +1,371 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ evaluate.py - DISBench Evaluation Script
3
+
4
+ Responsibilities:
5
+ 1. Scan all JSON submission files in submissions/ directory
6
+ 2. For each unevaluated submission, compare with groundtruth.jsonl to calculate scores
7
+ 3. Append new results to leaderboard_data.json
8
+ 4. (Optional) Commit updated leaderboard_data.json back to HF repository
9
+
10
+ Execution:
11
+ - Automatic: Called when app.py starts (automatically triggered on Space rebuild)
12
+ - Manual: python evaluate.py
13
+ """
14
+
15
+ import os
16
+ import json
17
+ import logging
18
+ from datetime import datetime
19
+ from typing import Dict, List, Set, Tuple, Optional
20
+
21
+ logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
22
+ logger = logging.getLogger(__name__)
23
+
24
+ # --- Path Configuration ---
25
+ SUBMISSIONS_DIR = "submissions"
26
+ GROUND_TRUTH_FILE = "groundtruth.jsonl"
27
+ LEADERBOARD_FILE = "leaderboard_data.json"
28
+
29
+
30
+ # ============================================================
31
+ # Core Evaluation Logic
32
+ # ============================================================
33
+
34
+ def compute_em(predicted: Set[str], gold: Set[str]) -> float:
35
+ """Exact Match: returns 1 if predicted set exactly matches gold set, otherwise 0"""
36
+ return 1.0 if predicted == gold else 0.0
37
+
38
+
39
+ def compute_f1(predicted: Set[str], gold: Set[str]) -> float:
40
+ """F1 Score: harmonic mean of set-based precision and recall"""
41
+ if not predicted and not gold:
42
+ return 1.0
43
+ if not predicted or not gold:
44
+ return 0.0
45
+
46
+ tp = len(predicted & gold)
47
+ precision = tp / len(predicted)
48
+ recall = tp / len(gold)
49
+
50
+ if precision + recall == 0:
51
+ return 0.0
52
+ return 2 * precision * recall / (precision + recall)
53
+
54
+
55
+ def load_ground_truth() -> Dict:
56
+ """
57
+ Load ground truth file (JSONL format).
58
+
59
+ Input format (groundtruth.jsonl):
60
+ Each line is a JSON object:
61
+ {
62
+ "query_id": "1",
63
+ "user_id": "...",
64
+ "query": "...",
65
+ "answer": ["photo_id_1", "photo_id_2"],
66
+ "event_type": "intra-event" // "intra-event" or "inter-event"
67
+ }
68
+
69
+ Converted to internal format:
70
+ {
71
+ "queries": {
72
+ "1": {
73
+ "type": "intra", // "intra" or "inter"
74
+ "gold_photos": ["photo_id_1", "photo_id_2"]
75
+ },
76
+ ...
77
+ }
78
+ }
79
+ """
80
+ if not os.path.exists(GROUND_TRUTH_FILE):
81
+ logger.warning(f"Ground truth file not found: {GROUND_TRUTH_FILE}")
82
+ return {}
83
+
84
+ queries = {}
85
+ with open(GROUND_TRUTH_FILE, 'r', encoding='utf-8') as f:
86
+ for line_num, line in enumerate(f, 1):
87
+ line = line.strip()
88
+ if not line:
89
+ continue
90
+
91
+ try:
92
+ entry = json.loads(line)
93
+ query_id = entry.get("query_id")
94
+ answer = entry.get("answer", [])
95
+ event_type = entry.get("event_type", "intra-event")
96
+
97
+ # Convert event_type: "intra-event" -> "intra", "inter-event" -> "inter"
98
+ query_type = event_type.replace("-event", "")
99
+
100
+ queries[query_id] = {
101
+ "type": query_type,
102
+ "gold_photos": answer
103
+ }
104
+ except json.JSONDecodeError as e:
105
+ logger.warning(f"Invalid JSON at line {line_num}: {e}")
106
+ continue
107
+ except Exception as e:
108
+ logger.warning(f"Error processing line {line_num}: {e}")
109
+ continue
110
+
111
+ return {"queries": queries}
112
+
113
+
114
+ def evaluate_predictions(
115
+ predictions: Dict[str, List[str]],
116
+ ground_truth: Dict
117
+ ) -> Dict[str, float]:
118
+ """
119
+ Calculate all metrics for a submission's predictions.
120
+
121
+ Returns:
122
+ {
123
+ "overall_em": float,
124
+ "overall_f1": float,
125
+ "intra_em": float,
126
+ "intra_f1": float,
127
+ "inter_em": float,
128
+ "inter_f1": float
129
+ }
130
+ """
131
+ queries = ground_truth.get("queries", {})
132
+
133
+ if not queries:
134
+ logger.warning("Ground truth has no queries, returning zeros.")
135
+ return {
136
+ "overall_em": 0.0, "overall_f1": 0.0,
137
+ "intra_em": 0.0, "intra_f1": 0.0,
138
+ "inter_em": 0.0, "inter_f1": 0.0,
139
+ }
140
+
141
+ # Collect scores by type
142
+ scores_by_type = {"intra": {"em": [], "f1": []}, "inter": {"em": [], "f1": []}}
143
+ all_em, all_f1 = [], []
144
+
145
+ for query_id, query_info in queries.items():
146
+ gold_set = set(query_info.get("gold_photos", []))
147
+ pred_set = set(predictions.get(query_id, []))
148
+ query_type = query_info.get("type", "intra") # Default to intra
149
+
150
+ em = compute_em(pred_set, gold_set)
151
+ f1 = compute_f1(pred_set, gold_set)
152
+
153
+ all_em.append(em)
154
+ all_f1.append(f1)
155
+
156
+ if query_type in scores_by_type:
157
+ scores_by_type[query_type]["em"].append(em)
158
+ scores_by_type[query_type]["f1"].append(f1)
159
+
160
+ def safe_mean(lst):
161
+ return round(sum(lst) / len(lst) * 100, 1) if lst else 0.0
162
+
163
+ return {
164
+ "overall_em": safe_mean(all_em),
165
+ "overall_f1": safe_mean(all_f1),
166
+ "intra_em": safe_mean(scores_by_type["intra"]["em"]),
167
+ "intra_f1": safe_mean(scores_by_type["intra"]["f1"]),
168
+ "inter_em": safe_mean(scores_by_type["inter"]["em"]),
169
+ "inter_f1": safe_mean(scores_by_type["inter"]["f1"]),
170
+ }
171
+
172
+
173
+ # ============================================================
174
+ # Submission Management
175
+ # ============================================================
176
+
177
+ def get_entry_key(entry: Dict) -> Tuple:
178
+ """
179
+ Generate unique identifier key for an entry.
180
+
181
+ The same method may have multiple different configurations (different backbone, retriever, etc.),
182
+ Only when all key configuration fields are the same, they are considered the same submission.
183
+
184
+ Returns: (method, agent, backbone, retriever, track)
185
+ """
186
+ return (
187
+ entry.get("method", ""),
188
+ entry.get("agent", ""),
189
+ entry.get("backbone", ""),
190
+ entry.get("retriever", ""),
191
+ entry.get("track", "Standard"),
192
+ )
193
+
194
+
195
+ def load_leaderboard() -> list:
196
+ if os.path.exists(LEADERBOARD_FILE):
197
+ with open(LEADERBOARD_FILE, 'r', encoding='utf-8') as f:
198
+ return json.load(f)
199
+ return []
200
+
201
+
202
+ def save_leaderboard(data: list):
203
+ with open(LEADERBOARD_FILE, 'w', encoding='utf-8') as f:
204
+ json.dump(data, f, indent=2, ensure_ascii=False)
205
+
206
+
207
+ def process_submission(filepath: str, ground_truth: Dict) -> Optional[Dict]:
208
+ """
209
+ Process a single submission file, return leaderboard entry (or None if error).
210
+ """
211
+ try:
212
+ with open(filepath, 'r', encoding='utf-8') as f:
213
+ submission = json.load(f)
214
+
215
+ meta = submission.get("meta", {})
216
+ predictions = submission.get("predictions", {})
217
+
218
+ if not meta.get("method_name"):
219
+ logger.warning(f"Skipping {filepath}: missing method_name")
220
+ return None
221
+
222
+ if not predictions:
223
+ logger.warning(f"Skipping {filepath}: empty predictions")
224
+ return None
225
+
226
+ # Calculate scores
227
+ scores = evaluate_predictions(predictions, ground_truth)
228
+
229
+ entry = {
230
+ "method": meta.get("method_name", "Unknown"),
231
+ "url": meta.get("project_url", "#"),
232
+ "org": meta.get("organization", "Anonymous"),
233
+ "agent": meta.get("agent_framework", "Unknown"),
234
+ "backbone": meta.get("backbone_model", "Unknown"),
235
+ "retriever": meta.get("retriever_model", "Unknown"),
236
+ "track": meta.get("track", "Standard"),
237
+ "date": datetime.now().strftime("%Y-%m-%d"),
238
+ **scores,
239
+ }
240
+
241
+ logger.info(
242
+ f"Evaluated '{entry['method']}': "
243
+ f"Overall EM={scores['overall_em']}, F1={scores['overall_f1']}"
244
+ )
245
+ return entry
246
+
247
+ except Exception as e:
248
+ logger.error(f"Error processing {filepath}: {e}")
249
+ return None
250
+
251
+
252
+ def run_evaluation():
253
+ """
254
+ Main evaluation pipeline:
255
+ 1. Load ground truth
256
+ 2. Scan all files in submissions/ and re-evaluate
257
+ 3. Deduplicate using configuration combinations (method, agent, backbone, retriever, track)
258
+ 4. If multiple submissions exist for the same configuration, keep the latest (sorted by filename, last file is considered latest)
259
+ 5. Return (number of entries, total entries)
260
+
261
+ Notes:
262
+ - No evaluated.json is maintained, all files are re-evaluated on each startup
263
+ - submissions/ is the single source of truth
264
+ - Benefits: simple logic, no state inconsistency, automatic recalculation when evaluation logic changes
265
+ """
266
+ # 1. Load ground truth
267
+ ground_truth = load_ground_truth()
268
+ if not ground_truth:
269
+ logger.info("No ground truth file found. Skipping evaluation.")
270
+ return 0, 0
271
+
272
+ # 2. Scan all submissions and evaluate
273
+ if not os.path.exists(SUBMISSIONS_DIR):
274
+ logger.info("No submissions directory found.")
275
+ return 0, 0
276
+
277
+ # Use dictionary to store: key is configuration tuple, value is (entry, filename)
278
+ # If multiple submissions exist for the same configuration, later evaluated ones will overwrite earlier ones (keep latest)
279
+ entries_by_config = {}
280
+
281
+ for filename in sorted(os.listdir(SUBMISSIONS_DIR)):
282
+ if not filename.endswith(".json"):
283
+ continue
284
+
285
+ filepath = os.path.join(SUBMISSIONS_DIR, filename)
286
+ logger.info(f"Processing submission: {filename}")
287
+
288
+ entry = process_submission(filepath, ground_truth)
289
+ if entry is not None:
290
+ config_key = get_entry_key(entry)
291
+
292
+ # If this configuration already exists, it means there's a duplicate submission, replace old with new
293
+ if config_key in entries_by_config:
294
+ old_filename = entries_by_config[config_key][1]
295
+ logger.info(
296
+ f"Config {config_key} already exists (from {old_filename}), "
297
+ f"replacing with {filename}"
298
+ )
299
+
300
+ entries_by_config[config_key] = (entry, filename)
301
+
302
+ # 3. Extract all unique entries
303
+ leaderboard = [entry for entry, _ in entries_by_config.values()]
304
+
305
+ # 4. Save results
306
+ save_leaderboard(leaderboard)
307
+ logger.info(f"Leaderboard updated: {len(leaderboard)} unique configurations.")
308
+
309
+ return len(leaderboard), len(leaderboard)
310
+
311
+
312
+ def commit_leaderboard_to_repo():
313
+ """
314
+ (Optional) Commit the updated leaderboard_data.json back to HF repository,
315
+ to persist data (avoid re-evaluation on every restart).
316
+
317
+ Note: We no longer commit evaluated.json, as we re-evaluate from submissions/ on each startup.
318
+ """
319
+ hf_token = os.environ.get("HF_TOKEN")
320
+ space_id = os.environ.get("SPACE_ID")
321
+
322
+ if not hf_token or not space_id:
323
+ logger.info("HF_TOKEN or SPACE_ID not set, skipping repo commit.")
324
+ return
325
+
326
+ try:
327
+ from huggingface_hub import HfApi, CommitOperationAdd
328
+
329
+ api = HfApi(token=hf_token)
330
+
331
+ # Only commit leaderboard_data.json
332
+ if not os.path.exists(LEADERBOARD_FILE):
333
+ logger.warning(f"Leaderboard file {LEADERBOARD_FILE} not found, skipping commit.")
334
+ return
335
+
336
+ with open(LEADERBOARD_FILE, 'rb') as f:
337
+ api.create_commit(
338
+ repo_id=space_id,
339
+ repo_type="space",
340
+ operations=[
341
+ CommitOperationAdd(
342
+ path_in_repo=LEADERBOARD_FILE,
343
+ path_or_fileobj=f.read(),
344
+ )
345
+ ],
346
+ commit_message="[Auto] Update leaderboard scores",
347
+ )
348
+ logger.info("Leaderboard committed to repo successfully.")
349
+
350
+ except Exception as e:
351
+ logger.error(f"Failed to commit to repo: {e}")
352
+
353
+
354
+ # ============================================================
355
+ # Entry Point
356
+ # ============================================================
357
+
358
+ if __name__ == "__main__":
359
+ logger.info("=" * 60)
360
+ logger.info("DISBench Evaluation Pipeline - Manual Run")
361
+ logger.info("=" * 60)
362
+
363
+ total, _ = run_evaluation()
364
+
365
+ if total > 0:
366
+ logger.info(f"Evaluated all submissions. Committing to repo...")
367
+ commit_leaderboard_to_repo()
368
+ else:
369
+ logger.info("No submissions found.")
370
+
371
+ logger.info(f"Leaderboard has {total} unique configurations.")
groundtruth.jsonl ADDED
@@ -0,0 +1,122 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {"query_id": "1", "user_id": "10287726@N02", "query": "Find photos from the musical performance identified by the blue and white event logo on site, where only the lead singer appears on stage.", "answer": ["7759256930", "7759407170", "7759295108", "7759433016"], "event_type": "intra-event"}
2
+ {"query_id": "2", "user_id": "10287726@N02", "query": "Find photos taken while watching that match of body contact competition without equipment, which contain the scoreboard showing a contest between two purely Asian countries.", "answer": ["7859417230", "7859414230", "7859412098"], "event_type": "intra-event"}
3
+ {"query_id": "3", "user_id": "10297518@N00", "query": "Find the photo taken on the day when the wall covered with drawings of fresh ingredients was seen, that contains metal chairs.", "answer": ["12678291825"], "event_type": "intra-event"}
4
+ {"query_id": "4", "user_id": "10297518@N00", "query": "All photos taken during the calendar week when a foggy cityscape was photographed in a western North American city at dusk, excluding those containing wine bottles.", "answer": ["12345034875", "12345198653", "12345043105", "12345208993", "12345510174", "12345514604"], "event_type": "intra-event"}
5
+ {"query_id": "5", "user_id": "10299779@N03", "query": "Find all indoor photos taken during the city hall visit where you saw the nude statue commemorating a cultural figure of that country, which contain at least one complete European classical lamp.", "answer": ["9687658675", "9690897644", "9690945632", "9687710449"], "event_type": "intra-event"}
6
+ {"query_id": "6", "user_id": "10299779@N03", "query": "Find all photos of flowers that do not contain purple flowers, taken on the day when the main building of the locally famous ship repair and mechanical engineering company whose name starts with the letter \"K\" was seen.", "answer": ["10413130075", "10413006546", "10413168333", "10412794946", "10413195113", "10412848095", "10412814313"], "event_type": "intra-event"}
7
+ {"query_id": "7", "user_id": "12276997@N06", "query": "I took many photos of a certain animal at Mirador de La Antigua; find similar photos of this animal that I took at Torrejón el Rubio.", "answer": ["4849540315", "4849543217", "4850192364", "4850188322", "4850192226"], "event_type": "inter-event"}
8
+ {"query_id": "8", "user_id": "12276997@N06", "query": "Find photos of cows taken on the day I photographed a group of the animal that I took many photos of at Mirador de La Antigua.", "answer": ["4064083683", "4064082669", "4064820510", "4064824294"], "event_type": "inter-event"}
9
+ {"query_id": "9", "user_id": "12276997@N06", "query": "Find photos providing a closer view of the spire-topped building that appears in the distance in the first photo taken on April 18, 2014.", "answer": ["13956729065", "13933619246"], "event_type": "inter-event"}
10
+ {"query_id": "10", "user_id": "12734746@N00", "query": "Find all photos of C-3PO taken on the day when Micky was not photographed.", "answer": ["5740173261", "5740173785"], "event_type": "intra-event"}
11
+ {"query_id": "11", "user_id": "12734746@N00", "query": "Find all photos of Darth Maul taken on the day when both Ahsoka and Darth Maul were photographed.", "answer": ["5977807515", "5977840951", "5872342937"], "event_type": "intra-event"}
12
+ {"query_id": "12", "user_id": "12734746@N00", "query": "Find all photos of the Lego-built United States Capitol taken on the day during the Legoland event when I photographed many Lego-built U.S. presidents.", "answer": ["4531838972", "4531206035", "4531205283"], "event_type": "intra-event"}
13
+ {"query_id": "13", "user_id": "13101981@N03", "query": "Find photos of the Sydney Harbour Bridge at night taken on a day several months before I watched fireworks at the Potts Point apartment, and on that day I also went to a bar.", "answer": ["5770246050", "5769706101", "5770247096", "5769704353", "5769702669"], "event_type": "intra-event"}
14
+ {"query_id": "14", "user_id": "13101981@N03", "query": "Find all photos of the Sydney Opera House taken on the day I visited the exhibition featuring hanging T-shirts.", "answer": ["6529354413", "6529356351", "6529392021"], "event_type": "intra-event"}
15
+ {"query_id": "15", "user_id": "14580956@N08", "query": "Find side-view photos of the large statue of the animated character that appears as a small statue in the photos where I was behind the TV-shaped photo frame.", "answer": ["11330906063", "11332580254"], "event_type": "inter-event"}
16
+ {"query_id": "16", "user_id": "14580956@N08", "query": "Find all photos of animal sculptures wearing props around their necks that were taken on the day when the orangutan was playing with a red prop.", "answer": ["11654699444", "11654633534"], "event_type": "intra-event"}
17
+ {"query_id": "17", "user_id": "14580956@N08", "query": "Find all photos of tigers that I took in early April of the same year when I photographed a tiger yawning with its mouth wide open.", "answer": ["13748820094", "13748673695", "13748447223", "13749066994"], "event_type": "inter-event"}
18
+ {"query_id": "18", "user_id": "14737255@N00", "query": "Find photos of Richard Müller standing almost completely sideways during the Open Air Festival 2010.", "answer": ["4901394195"], "event_type": "intra-event"}
19
+ {"query_id": "19", "user_id": "14737255@N00", "query": "Find photos of the lead singer of Faithless holding a microphone and singing during the Open Air Festival 2010.", "answer": ["4902747962", "4902161109", "4902160123", "4902744724", "4902157461", "4902156713"], "event_type": "intra-event"}
20
+ {"query_id": "20", "user_id": "14737255@N00", "query": "Find all close-up or medium photos of human statues taken during the 2009 Paris trip before the first photo of a Van Gogh painting was taken.", "answer": ["3906532981", "3906533767"], "event_type": "intra-event"}
21
+ {"query_id": "21", "user_id": "14798958@N08", "query": "Find all photos in which a building with \"1926\" on the lintel appears, even if only partially visible.", "answer": ["12713450225", "12713612803", "12713424065", "12713611993"], "event_type": "intra-event"}
22
+ {"query_id": "22", "user_id": "14798958@N08", "query": "Identify the person who posed with a triangulation station in both December 2013 and February 2014, and retrieve the photo of them with the station from the December 2013 event.", "answer": ["11308410435", "11670823024", "11670470795", "11670806594"], "event_type": "inter-event"}
23
+ {"query_id": "23", "user_id": "14798958@N08", "query": "Find all solo photos of the same person with the triangulation station taken on both December 7 and December 30, 2013.", "answer": ["11670442965", "11308452886", "11308450456"], "event_type": "inter-event"}
24
+ {"query_id": "24", "user_id": "15352839@N00", "query": "Find the photo of that large cruise ship, which was photographed in Chile, later docking in another country.", "answer": ["5500698550"], "event_type": "inter-event"}
25
+ {"query_id": "25", "user_id": "15352839@N00", "query": "Find all photos of street performers taken in the city where the centennial monument was photographed.", "answer": ["5500929668", "5500334405"], "event_type": "intra-event"}
26
+ {"query_id": "26", "user_id": "15352839@N00", "query": "Find all photos of anchor sculptures taken in the city where the centennial monument was photographed.", "answer": ["5500340243", "5500927560", "5500927690"], "event_type": "intra-event"}
27
+ {"query_id": "27", "user_id": "15803691@N00", "query": "Find photos of road signs taken on the way to the Zen Debris concert.", "answer": ["199242541", "199242771", "199242963"], "event_type": "intra-event"}
28
+ {"query_id": "28", "user_id": "15803691@N00", "query": "Find photos where a scarf that appeared in the 2004 DCC RHPS performance appears again at a later event.", "answer": ["281594862", "281593571", "281526899"], "event_type": "inter-event"}
29
+ {"query_id": "29", "user_id": "18383978@N00", "query": "Find the photo of me rock climbing alone on the day after the group photo of three people taken in Scotland.", "answer": ["5385417640"], "event_type": "intra-event"}
30
+ {"query_id": "30", "user_id": "18383978@N00", "query": "Find photos of several men wearing the same shoes that I photographed during my trip to Southern Europe two calender years ago.", "answer": ["12017070886"], "event_type": "inter-event"}
31
+ {"query_id": "31", "user_id": "21435131@N06", "query": "Find photos of the person who took pictures in a church during the trip to France, holding a baby.", "answer": ["6400941291", "6400940763"], "event_type": "intra-event"}
32
+ {"query_id": "32", "user_id": "21895046@N08", "query": "Find solo photos of any child taken in a museum with dinosaur skeletons.", "answer": ["5471527175", "3571341554", "3570529059", "3571332812", "3571336630"], "event_type": "intra-event"}
33
+ {"query_id": "33", "user_id": "21895046@N08", "query": "Find photos of the woman who has worn a heart-shaped necklace holding a newborn baby.", "answer": ["4764933969", "4764970551", "4764930661"], "event_type": "inter-event"}
34
+ {"query_id": "34", "user_id": "22017657@N05", "query": "Find the photos of the man's watch face appeared at the event on Saturday who wears a hat on Sunday.", "answer": ["9106789483", "9106781343", "9106753625", "9108880170"], "event_type": "inter-event"}
35
+ {"query_id": "35", "user_id": "22017657@N05", "query": "The photo of the woman wearing a light blue patterned top that was taken immediately after the photo of the lady in a bright red top at the TV drama fan meeting event.", "answer": ["9422191489"], "event_type": "intra-event"}
36
+ {"query_id": "36", "user_id": "22017657@N05", "query": "Find the solo photos from the fan event that night in which the man who wore a T-shirt with a green logo at the TV series discussion appears.", "answer": ["9429645636", "9426974443"], "event_type": "intra-event"}
37
+ {"query_id": "37", "user_id": "22526649@N03", "query": "Find the group photo of four people taken in front of the yellow wall with a circular pattern that appeared at the Economic Club.", "answer": ["13568401013"], "event_type": "intra-event"}
38
+ {"query_id": "38", "user_id": "22736462@N07", "query": "Check if there is anyone who has both ridden a bike and performed as DJ; if so, find photos of them playing DJ.", "answer": ["4213476097", "4214221364", "4213455031", "4213454895", "4214221174", "4214242858", "4214242796", "4214242702"], "event_type": "inter-event"}
39
+ {"query_id": "39", "user_id": "22736462@N07", "query": "Find photos from a birthday party held in a room with calligraphy or paintings hanging on the wall, where the birthday person is holding a wine glass.", "answer": ["3456340318", "3456340218", "3455521861"], "event_type": "intra-event"}
40
+ {"query_id": "40", "user_id": "22736462@N07", "query": "Find all photos from the hiking trip in the western United States containing the woman who wore multiple necklaces at the birthday party with the large yellow floor lamp.", "answer": ["4000393384", "4000534281", "4001299644", "4001299798", "4000534371"], "event_type": "inter-event"}
41
+ {"query_id": "41", "user_id": "23090753@N06", "query": "Find the logos found elsewhere in the venue (not on the stage) during another recent performance by the female bassist who previously performed in an enclosed shed.", "answer": ["5545472770", "8537595742"], "event_type": "inter-event"}
42
+ {"query_id": "42", "user_id": "23518714@N00", "query": "There have been two occasions, less than 2 years apart, when photos were taken on the same day of the border signs of the same two adjacent states. Please find the border sign photos of the smaller state from those two occasions.", "answer": ["2164102074", "2163302409", "2329450116", "2329512252"], "event_type": "inter-event"}
43
+ {"query_id": "43", "user_id": "23736466@N00", "query": "Find photos where the jersey number of the FMVP on the team playing against the free-throwing shooting guard appears in other sports.", "answer": ["2861882952", "2982419979"], "event_type": "inter-event"}
44
+ {"query_id": "44", "user_id": "24232779@N00", "query": "There is a person wearing a red T-shirt. Find photos of this person wearing the same jacket on two occasions separated by more than 8 months.", "answer": ["13114381394", "6601840711", "6601826625"], "event_type": "inter-event"}
45
+ {"query_id": "45", "user_id": "24413182@N00", "query": "Please help me find the photo that is a landscape shot taken on the road, captured after we photographed the person providing transportation services, but before we arrived at Aleppo Citadel.", "answer": ["4650094397", "4650711100", "4650711710", "4650093789"], "event_type": "intra-event"}
46
+ {"query_id": "46", "user_id": "24413182@N00", "query": "Find photos taken 7 days before the photo of the elderly Maasai woman wearing a layered bead necklace. The target photos feature a vertebrate animal that relies on external heat sources to regulate its body temperature.", "answer": ["10901596755", "10901899493"], "event_type": "intra-event"}
47
+ {"query_id": "47", "user_id": "24468935@N03", "query": "Find photos of a girl taken on the coast where the legend of Odysseus and Princess Nausicaa took place. She has appeared in another photo wearing a blue headscarf and sunglasses.", "answer": ["7769170062", "7769178796", "7769192290", "7769175414", "7769210734", "7769235402", "7769197742", "7769228788", "7769259012", "7769222512", "7769255270", "7769248762"], "event_type": "inter-event"}
48
+ {"query_id": "48", "user_id": "24468935@N03", "query": "Find photos depicting a woman swimming next to the legendary fragment of land that, according to myth, was struck by a trident and separated from the continent. The woman also appears in another photo wearing a blue headscarf and sunglasses.", "answer": ["7767611716", "7767606504", "7767603054"], "event_type": "inter-event"}
49
+ {"query_id": "49", "user_id": "24468935@N03", "query": "Find photos of the person doing rappelling during a rainforest canyon adventure at the edge of a volcanic belt, who previously had selfies with elephants.", "answer": ["5555244846", "5555244956", "5555244744", "5555244914", "5555244802", "5555244688", "5554657497", "5554658813", "5554657587", "5554658759", "5554658551", "5555246166", "5555246112", "5554658467", "5555246296", "5554657545", "5555246004", "5554658427", "5554658587", "5554657643"], "event_type": "inter-event"}
50
+ {"query_id": "50", "user_id": "24736216@N07", "query": "Find the photos I took during the Caribbean cruise that contain animals which closely resemble the animal depicted on one of my souvenir T-shirts.", "answer": ["5726408412", "5726408366", "5725852439"], "event_type": "inter-event"}
51
+ {"query_id": "51", "user_id": "24736216@N07", "query": "Find an interface similar to the one displayed on the LG screen.", "answer": ["12933192564"], "event_type": "inter-event"}
52
+ {"query_id": "52", "user_id": "24819841@N06", "query": "Find the photo of a woman taken at the main civic square of the United Kingdom's second largest city, next to the bronze fountain popularly known as \"The Woman in the Hot Water Bath\", where this woman had previously taken a photo in the country that was the world's second largest by territory at that time.", "answer": ["2887804961"], "event_type": "inter-event"}
53
+ {"query_id": "53", "user_id": "25367139@N00", "query": "Find all photos taken during the January 11 match that include an advertising board of a brand which had at least five of its boards displayed consecutively along the edge of the field during a preseason friendly in the third quarter of 2013.", "answer": ["12009721833", "12010249266", "12010250456", "12009782424", "12009689213", "12009759074"], "event_type": "intra-event"}
54
+ {"query_id": "54", "user_id": "25652622@N00", "query": "Find all group photos of four people, at any location, that include the person who wore a dark blue jacket climbing the largest glacier in the United States in 2009.", "answer": ["4857960798", "4877577777", "4095181696", "6997586514"], "event_type": "inter-event"}
55
+ {"query_id": "55", "user_id": "25652622@N00", "query": "Identify the companion who was holding a red object during my climb of a Washington glacier named after a geologist. Then, locate all photos of this person sitting on large stones.", "answer": ["4613611446", "4613612288"], "event_type": "inter-event"}
56
+ {"query_id": "56", "user_id": "25899413@N04", "query": "Find the man who was closest to the plastic box while resting at the Swiss ski resort at an altitude of 1675 meters. Then get all photos of him smiling, regardless of location.", "answer": ["4514232684", "4513592917", "3504418456", "3503604671"], "event_type": "inter-event"}
57
+ {"query_id": "57", "user_id": "25899413@N04", "query": "Locate the orange tool that appeared during the trip in Switzerland. Then, find photos from the Pragelato trip where that type of tool was put on the ground.", "answer": ["4416229191"], "event_type": "inter-event"}
58
+ {"query_id": "58", "user_id": "27550543@N02", "query": "Is there a person who has both tried to lift a black tool over their head and has held a steering wheel? If so, try to find all photos of this person holding a wine glass.", "answer": ["3460813802", "3460788528", "3460672156"], "event_type": "inter-event"}
59
+ {"query_id": "59", "user_id": "27550543@N02", "query": "I have photographed a type of vehicle with logo of two red animals. Please find all photos from 2011 that contain exactly two vehicles of this type.", "answer": ["6070729736", "6070183061", "6070728050", "6070183739", "6070179861", "6070739508", "6070181495", "6070193407", "6070180415"], "event_type": "intra-event"}
60
+ {"query_id": "60", "user_id": "27634886@N00", "query": "Find all photos with the sea taken at the beach two days after watching the fireworks show.", "answer": ["6009152901", "6009707544", "6009157655"], "event_type": "intra-event"}
61
+ {"query_id": "61", "user_id": "27634886@N00", "query": "Find all photos of the white-haired grandma at the mini golf course on the day when there was a fireworks show at night.", "answer": ["6009148499", "6009696628"], "event_type": "intra-event"}
62
+ {"query_id": "62", "user_id": "27634886@N00", "query": "Find all photos taken of the person who wore a bathrobe on Christmas Day 2012 during his later trip to Pompei.", "answer": ["9486332346", "9476277061", "9483505769"], "event_type": "inter-event"}
63
+ {"query_id": "63", "user_id": "27637456@N06", "query": "Find photos of the hiker with a backpack that has an orange-red logo on top, taken later when he was setting out towards the stone bridge on the road from Hontanas to Boadilla del Camino.", "answer": ["7036534497", "7036532695"], "event_type": "intra-event"}
64
+ {"query_id": "64", "user_id": "27637456@N06", "query": "Find solo photos of the woman who posed with the fish-carved stone in Arles, taken before she saw the strange face statue during her trip to the ancient Roman seaside port city.", "answer": ["5778612789", "5778617109"], "event_type": "intra-event"}
65
+ {"query_id": "65", "user_id": "27637456@N06", "query": "Find the photos of the hiker interacting with a dog, whose backpack has an orange-red logo in the middle.", "answer": ["8558357036", "6856070036"], "event_type": "intra-event"}
66
+ {"query_id": "66", "user_id": "28157992@N03", "query": "Find photos of the person who posed with the ice hockey statue, specifically the ones where they are wearing a hat.", "answer": ["9896642915", "6123707828"], "event_type": "inter-event"}
67
+ {"query_id": "67", "user_id": "28157992@N03", "query": "Find fireworks photos from my trip, taken on the day I also photographed a city view containing the bridge I crossed at customs a year prior.", "answer": ["9647794441", "9647793803", "9647795907", "9647793275", "9651031044"], "event_type": "inter-event"}
68
+ {"query_id": "68", "user_id": "28495173@N00", "query": "Find the photos from Yeomen where the woman who helped with makeup in both Gondoliers and Utopia Limited is wearing small earrings.", "answer": ["4021141323", "4021900844"], "event_type": "intra-event"}
69
+ {"query_id": "69", "user_id": "28495173@N00", "query": "Find all photos taken at the beginning of the year seven years after 2005 in which I photographed the same cruise ship that I captured at dusk in Otago in 2005.", "answer": ["6657554835", "6666029973", "6657006619", "6666129439", "6657056897", "6658404791", "6657058951", "6657057827", "6657055985"], "event_type": "inter-event"}
70
+ {"query_id": "70", "user_id": "28495173@N00", "query": "Find all photos from 2008 showing ships docked at the warehouse where a grey warship was once docked.", "answer": ["6740675373", "3401390348", "3400575971"], "event_type": "inter-event"}
71
+ {"query_id": "71", "user_id": "30872191@N00", "query": "Find photos of the female athlete who wore different glasses in the first two Brunettes practices in 2013, where she is preparing to catch the ball during the first practice.", "answer": ["9213002082", "9210216039", "9213008110"], "event_type": "inter-event"}
72
+ {"query_id": "72", "user_id": "30872191@N00", "query": "Find the race photos of the woman wearing a headband who participated in the Walk to End Alzheimer's event in 2012 and also received a finisher medal in the Hospital Hill run in the same year.", "answer": ["7339977074", "7154879151"], "event_type": "inter-event"}
73
+ {"query_id": "73", "user_id": "31058815@N00", "query": "Find photos of the bridge crossing the canyon near the giant bicycle sculpture in New Zealand.", "answer": ["7483049266", "7483050402"], "event_type": "intra-event"}
74
+ {"query_id": "74", "user_id": "31058815@N00", "query": "Find group photos of people on the observation deck, where all the people in the photo have taken a helicopter sightseeing tour nearby.", "answer": ["4513080187", "4513722624"], "event_type": "intra-event"}
75
+ {"query_id": "75", "user_id": "31058815@N00", "query": "Find group photos taken on the way to the Koala Conservation Reserve featuring the same tourists who were later photographed at the best-preserved convict settlement in Australia.", "answer": ["5872148402", "5871590137"], "event_type": "inter-event"}
76
+ {"query_id": "76", "user_id": "35032604@N00", "query": "Find walking photos from the 2012 trip during which the beige wide-brimmed sun hat from the 2008 Puerto Rico vacation reappeared.", "answer": ["8367884194", "8367874070"], "event_type": "inter-event"}
77
+ {"query_id": "77", "user_id": "35032604@N00", "query": "Find photos of the purple-blue plush toy received on Christmas morning while it was still in the bag.", "answer": ["2135703854", "2135704246", "2134924297", "2135704784", "2135704966", "2134925041"], "event_type": "intra-event"}
78
+ {"query_id": "78", "user_id": "35032604@N00", "query": "Find Christmas photos of the woman who wore a pink patterned dress and glasses on Thanksgiving, in which she is showing a toothy smile.", "answer": ["2149872721", "2149886507", "2149889275", "2150682448", "2150694380"], "event_type": "inter-event"}
79
+ {"query_id": "79", "user_id": "39979407@N05", "query": "Find photos of the dog that appears in photos for four consecutive years, where it is lying on the ground wearing a leash.", "answer": ["9505410488", "9505377294"], "event_type": "inter-event"}
80
+ {"query_id": "80", "user_id": "39979407@N05", "query": "Find photos where the dog that often goes hiking together is lying on the ground wearing a leash.", "answer": ["9505410488", "9505377294"], "event_type": "inter-event"}
81
+ {"query_id": "81", "user_id": "39979407@N05", "query": "Find photos of pink flowers taken on the two days when many cairns were photographed.", "answer": ["9318157923", "9320981086"], "event_type": "intra-event"}
82
+ {"query_id": "82", "user_id": "40817698@N07", "query": "Find photos of the car, which was photographed on two different days, parked in front of the store.", "answer": ["10912713835"], "event_type": "inter-event"}
83
+ {"query_id": "83", "user_id": "40817698@N07", "query": "Find photos of the car, where I sat inside laughing later, parked in front of a store.", "answer": ["10912713835"], "event_type": "intra-event"}
84
+ {"query_id": "84", "user_id": "40817698@N07", "query": "Find all indoor photos of me taken on the day when I wore two different hats.", "answer": ["11781868496", "11781862686", "11781854676", "11781364664", "11781471144"], "event_type": "intra-event"}
85
+ {"query_id": "85", "user_id": "41610421@N05", "query": "Find all photos of cats on the windowsill taken after the cat tree was assembled.", "answer": ["6925126388", "7071197841", "6925115942", "7301319300", "9286900781"], "event_type": "inter-event"}
86
+ {"query_id": "86", "user_id": "41610421@N05", "query": "Find all photos of the cat that was present during the assembly of the cat tree standing on the table.", "answer": ["5436177059", "5436791744", "5436790026"], "event_type": "intra-event"}
87
+ {"query_id": "87", "user_id": "41610421@N05", "query": "I've drawn a political party's symbol on the beach; find photos of the promotional material of the party placed on the sofa.", "answer": ["4781159336", "4780522565"], "event_type": "inter-event"}
88
+ {"query_id": "88", "user_id": "41838028@N00", "query": "Find the photo where the toy that the dog was biting is later hanging on a backpack.", "answer": ["2274176358"], "event_type": "inter-event"}
89
+ {"query_id": "89", "user_id": "41838028@N00", "query": "Find photos where the toy that the dog was biting is later placed on the bench.", "answer": ["2274156016", "2273358409"], "event_type": "inter-event"}
90
+ {"query_id": "90", "user_id": "41838028@N00", "query": "Find photos where the young boy wore a jacket in 2008 that he also wore in 2007.", "answer": ["2777734502"], "event_type": "inter-event"}
91
+ {"query_id": "91", "user_id": "43145783@N00", "query": "Find photos from the Royal Regiment of Scotland band parade that include leopard print clothing.", "answer": ["416271783", "416272319", "416272242", "416271972", "416271490", "416271886", "416271306"], "event_type": "intra-event"}
92
+ {"query_id": "92", "user_id": "47554402@N00", "query": "Find all photos of that non-plaster statue that were taken twice within half a year.", "answer": ["4766071721", "6101461613", "4305319876"], "event_type": "inter-event"}
93
+ {"query_id": "93", "user_id": "47642109@N04", "query": "Find photos showing the object (which a puppy was playing with outdoors) lying on the ground after having a large portion torn off by another puppy.", "answer": ["6708877621", "6708882313", "6725766223", "6725760161", "6725685701", "6725679177", "6725674339", "6708692717", "6725559125"], "event_type": "intra-event"}
94
+ {"query_id": "94", "user_id": "49475364@N00", "query": "Find the building that has appeared in both real life and non-real form at different locations, and retrieve both its real life version and non-real version photos.", "answer": ["7382741514", "10628350484"], "event_type": "inter-event"}
95
+ {"query_id": "95", "user_id": "49475364@N00", "query": "Find the solo photos of the person who appears in a dining table group photo with two others (where all three cooked in the same kitchen), but who only has individual shots taken in that kitchen.", "answer": ["5279736855", "5522330465"], "event_type": "inter-event"}
96
+ {"query_id": "96", "user_id": "49645113@N07", "query": "Find the photos of dancing with exposed midriff that were taken approximately 30 minutes to 1 hour before a performer holding a torch performed.", "answer": ["9994719874", "9994777326"], "event_type": "intra-event"}
97
+ {"query_id": "97", "user_id": "54368512@N00", "query": "I once photographed an animal sleeping on a tool marked \"4x4\". Now I need to find all photos of animals of the same species, but of the opposite sex, with their eyes open.", "answer": ["8096054958", "8096049037", "8096048161", "8096073106", "8096049471"], "event_type": "inter-event"}
98
+ {"query_id": "98", "user_id": "54368512@N00", "query": "Find photos that contain strictly carnivorous animals, taken within three months after photos of animals about 25 centimeters in body length that like to eat nuts and insects.", "answer": ["8096077441", "8096082466", "8096076611", "8096050475", "8096057334", "8096047771", "8096062198", "8096075411", "8096064490", "8096059707", "8096049037", "8096072144", "8096070276", "8096062065", "8096049989", "8096053895", "8096062876", "8096054958", "8096073106", "8096063428", "8096054475", "8096061340", "8096046937", "8096069206", "8096071444", "8096059069", "8096067368", "8096049471", "8096048161", "8096058114", "8096053005", "8096052363", "8096074701", "8096047347", "8096078287"], "event_type": "intra-event"}
99
+ {"query_id": "99", "user_id": "54368512@N00", "query": "Identify the person who, during a match, wore a black helmet with the same number on top as Iniesta's Barcelona number. Then find all the photos in which this person is not wearing a helmet.", "answer": ["10262806964", "10262887436"], "event_type": "inter-event"}
100
+ {"query_id": "100", "user_id": "55772206@N03", "query": "Find all photos where the person who was wearing flip-flops at the beach appears in a rainforest that has existed on Earth for about 130 million years.", "answer": ["8120951530", "8120946816", "8120936155"], "event_type": "inter-event"}
101
+ {"query_id": "101", "user_id": "55772206@N03", "query": "There's a photo of one people smiling brightly, wearing clothing whose brand logo is a stone. Find all photos from that same trip that contain coconut trees.", "answer": ["7975722484", "7975713847", "7975715557", "7975712984", "8014500983", "7975716537", "7975717703"], "event_type": "intra-event"}
102
+ {"query_id": "102", "user_id": "57269089@N03", "query": "Identify the person who appears most frequently in horseback riding photos. I want to find all photos of this person sitting on the grass.", "answer": ["7660499744", "7660500656", "7660501552"], "event_type": "inter-event"}
103
+ {"query_id": "103", "user_id": "57269089@N03", "query": "Identify the date of the photo showing a person holding a glass item first manufactured in 1753. Then find all photos of seaside sunsets taken during the next calendar month after that date.", "answer": ["5978560626", "5978559064", "5981890977", "5982435276", "5981883283"], "event_type": "inter-event"}
104
+ {"query_id": "104", "user_id": "62606667@N04", "query": "Based on the photo taken during the second trip to Florence showing a building with colonnades reflected on the river, find the river view photos taken from the same side and viewpoint during the first trip.", "answer": ["6168137826", "6167602051"], "event_type": "inter-event"}
105
+ {"query_id": "105", "user_id": "62606667@N04", "query": "Find solo photos of the man who wore a black hat while camping on the Alaska ferry and hiking in Juneau, showing him with a backpack during the trip where he saw wild bears.", "answer": ["6529792617", "6529792013"], "event_type": "inter-event"}
106
+ {"query_id": "106", "user_id": "62606667@N04", "query": "Find solo photos of the man wearing sunglasses at Everest Base Camp who wore the same top on both the Puffing Billy steam train and the Manly Ferry.", "answer": ["8360209009", "8361271630", "8360207761", "8360208375", "8360215489", "8360215211", "8361277548"], "event_type": "inter-event"}
107
+ {"query_id": "107", "user_id": "65367662@N00", "query": "Find the moments right after the woman who performed wearing leopard-print pants at the CalTech Visit fired a handgun at a shooting range in California.", "answer": ["8158465001", "8158462583", "8158491180"], "event_type": "intra-event"}
108
+ {"query_id": "108", "user_id": "65367662@N00", "query": "Find photos taken in 2012 on the beach near where Sheep crab was seen diving, showing the person who wore a blue and white headscarf during the Grant Lakes hike.", "answer": ["6886948892", "6886950944", "6886936510"], "event_type": "inter-event"}
109
+ {"query_id": "109", "user_id": "65367662@N00", "query": "Find underwater solo photos of a diver at Sliver Prince Diving in 2012 whose snorkel color is different from any snorkel color that appeared in the September 2012 diving activity.", "answer": ["7249396480", "7249390648", "7249381698", "7249394202"], "event_type": "inter-event"}
110
+ {"query_id": "110", "user_id": "67298685@N00", "query": "Find the photos from the June 2008 Algonquin canoe trip where the man who interacted with a dog during the August 2008 Algonquin Highland hiking trip is holding a wine glass.", "answer": ["2636424739", "2637249688", "2637247844"], "event_type": "inter-event"}
111
+ {"query_id": "111", "user_id": "67298685@N00", "query": "Find photos from the July 2011 Killarney canoe trip showing the person who participated in cooking during both the June 2008 Algonquin canoe trip and the July 2011 Killarney canoe trip, where this person is sitting in a boat.", "answer": ["5957229745", "5957790476", "5957219347", "5957790338", "5957790178"], "event_type": "intra-event"}
112
+ {"query_id": "112", "user_id": "67298685@N00", "query": "Find photos from the 2012 French River canoe trip showing the same dog from the close-up sleeping shot inside the tent, now seen on a canoe.", "answer": ["8423526781", "8423515763", "8424617216", "8423516443"], "event_type": "intra-event"}
113
+ {"query_id": "113", "user_id": "68712269@N00", "query": "Find the lakeside photos taken during a 2013 hiking trip in which both this trip and the Little Bulger hike in the same year captured the same landmark.", "answer": ["10498268355", "10498271225", "10498273155", "10498265896"], "event_type": "inter-event"}
114
+ {"query_id": "114", "user_id": "69099808@N00", "query": "Find the outdoor group photo of the man who once hugged his knees while watching a game, taken during the calendar week of a milestone 10-year birthday celebration.", "answer": ["4730861417", "4730942747"], "event_type": "intra-event"}
115
+ {"query_id": "115", "user_id": "70408381@N00", "query": "Find all photos with a body of water that I took during the outing with a friend who was wearing a themed T-shirt encouraging females to try an alcoholic beverage.", "answer": ["7626314726", "7626306292", "7626411894", "7626371328", "7626214908", "7626401908", "7626294468", "7626363828", "7626425526"], "event_type": "intra-event"}
116
+ {"query_id": "116", "user_id": "70554294@N00", "query": "Find all photos containing a red warning flag that were taken during the trip when I visited a Mayan ruin.", "answer": ["471404130", "471404248", "471422465", "471424669", "471425395", "471408496", "471429531"], "event_type": "intra-event"}
117
+ {"query_id": "117", "user_id": "70554294@N00", "query": "Find all photos taken at that software-themed gathering held at a location not by the sea, which include two specific men who also appeared together in a photo at another place within the same month.", "answer": ["2258888189", "2258884481"], "event_type": "inter-event"}
118
+ {"query_id": "118", "user_id": "7276266@N05", "query": "Find photos of yellow flowers taken near the memorial site for the heavy coastal defense kinetic weapon that was transported in 1991.", "answer": ["2927208756", "2927211222", "2927203590"], "event_type": "intra-event"}
119
+ {"query_id": "119", "user_id": "7276266@N05", "query": "Find all photos from two trips that include the woman who wore exactly the same outfit (top and bottom) in both trips, where the two trips are more than three months apart.", "answer": ["3743782238", "3743779180", "2859132587"], "event_type": "inter-event"}
120
+ {"query_id": "120", "user_id": "7276266@N05", "query": "Find photos of rodents taken on the day of visiting the area near the artificial lake recreation zone that was developed during the Great Depression in the United States.", "answer": ["1590169209", "1590167229"], "event_type": "intra-event"}
121
+ {"query_id": "121", "user_id": "7664723@N05", "query": "A boy and a girl have worn identical tops (same design). Please find all photos containing them wearing these tops.", "answer": ["6964340856", "7362155924", "7450745238", "7450731212", "7450212688", "7450615604", "7450624464", "7450315166"], "event_type": "inter-event"}
122
+ {"query_id": "122", "user_id": "7664723@N05", "query": "Find the photos containing beer taken during the calendar week when visiting the church whose construction took more than 500 years to complete.", "answer": ["7362595606", "7177235085", "7362587718", "7177019057", "7362611520", "7362616970", "7362543680", "7177024063", "7362252240", "7177399111", "7177243049"], "event_type": "intra-event"}
leaderboard_data.json ADDED
@@ -0,0 +1 @@
 
 
1
+ []
requirements.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ flask
2
+ pandas
3
+ huggingface_hub
4
+ python-dotenv
5
+ gunicorn
static/.DS_Store ADDED
Binary file (6.15 kB). View file
 
static/ruc-logo.png ADDED

Git LFS Details

  • SHA256: ef9994109e95399545c23e9c7857ebb4a97066044f14599f557dbab4d28a29a2
  • Pointer size: 131 Bytes
  • Size of remote file: 229 kB
static/script.js ADDED
@@ -0,0 +1,253 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const rawData = window.SERVER_DATA || [];
2
+
3
+ // --- State Management ---
4
+ let state = {
5
+ championTrack: "Standard",
6
+ sortKey: "overall_em",
7
+ sortDir: "desc",
8
+ filters: {
9
+ track: "all",
10
+ agent: "",
11
+ backbone: "",
12
+ retriever: "" // New retriever filter
13
+ }
14
+ };
15
+
16
+ // List of score column keys (used to determine which columns need highlighting)
17
+ const SCORE_KEYS = ["overall_em", "overall_f1", "intra_em", "intra_f1", "inter_em", "inter_f1"];
18
+
19
+ // --- Main Tab Switching ---
20
+ function switchMainTab(tabName) {
21
+ document.querySelectorAll('.nav-btn').forEach(btn => btn.classList.remove('active'));
22
+ const map = {'leaderboard': 0, 'full-metrics': 1, 'submit': 2};
23
+ document.querySelectorAll('.nav-btn')[map[tabName]].classList.add('active');
24
+
25
+ document.querySelectorAll('.view-section').forEach(el => el.style.display = 'none');
26
+ document.getElementById(`view-${tabName}`).style.display = 'block';
27
+
28
+ if (tabName === 'leaderboard') renderChampionTable();
29
+ if (tabName === 'full-metrics') renderFullTable();
30
+ }
31
+
32
+ // --- Update active-sort state for all table headers ---
33
+ function updateSortHeaders() {
34
+ document.querySelectorAll('.sortable').forEach(th => {
35
+ const key = th.dataset.key;
36
+ // Remove arrows from text
37
+ let text = th.textContent.replace(/ ↓/g, '').replace(/ ↑/g, '').trim();
38
+
39
+ if (key === state.sortKey) {
40
+ th.classList.add('active-sort');
41
+ text += state.sortDir === 'desc' ? ' ↓' : ' ↑';
42
+ } else {
43
+ th.classList.remove('active-sort');
44
+ }
45
+ th.textContent = text;
46
+ });
47
+ }
48
+
49
+ // --- Logic: Champion Table ---
50
+ function renderChampionTable() {
51
+ const tbody = document.querySelector('#champion-table tbody');
52
+ let data = rawData.filter(d => d.track === state.championTrack);
53
+
54
+ // For each method name, keep only the one with the highest overall_em
55
+ const methodBestMap = new Map();
56
+ data.forEach(row => {
57
+ const method = row.method;
58
+ if (!methodBestMap.has(method) || row.overall_em > methodBestMap.get(method).overall_em) {
59
+ methodBestMap.set(method, row);
60
+ }
61
+ });
62
+
63
+ // Convert to array
64
+ data = Array.from(methodBestMap.values());
65
+ data = sortData(data);
66
+
67
+ tbody.innerHTML = data.map((row, idx) => {
68
+ return buildRowHTML(row, idx + 1, false);
69
+ }).join('');
70
+
71
+ updateSortHeaders();
72
+ }
73
+
74
+ // --- Logic: Full Table ---
75
+ function renderFullTable() {
76
+ const tbody = document.querySelector('#full-table tbody');
77
+
78
+ let data = rawData.filter(d => {
79
+ const f = state.filters;
80
+ if (f.track !== 'all' && d.track !== f.track) return false;
81
+ if (f.agent && !d.agent.toLowerCase().includes(f.agent.toLowerCase())) return false;
82
+ if (f.backbone && !d.backbone.toLowerCase().includes(f.backbone.toLowerCase())) return false;
83
+ if (f.retriever && !d.retriever.toLowerCase().includes(f.retriever.toLowerCase())) return false;
84
+ return true;
85
+ });
86
+
87
+ data = sortData(data);
88
+
89
+ if (data.length === 0) {
90
+ tbody.innerHTML = `<tr><td colspan="12" style="text-align:center; padding:20px; color:#888;">No matching results.</td></tr>`;
91
+ return;
92
+ }
93
+
94
+ tbody.innerHTML = data.map((row, idx) => {
95
+ return buildRowHTML(row, idx + 1, true);
96
+ }).join('');
97
+
98
+ updateSortHeaders();
99
+ }
100
+
101
+ // --- Helper: Sort ---
102
+ function sortData(data) {
103
+ return data.sort((a, b) => {
104
+ let valA = a[state.sortKey];
105
+ let valB = b[state.sortKey];
106
+ if (typeof valA === 'number') {
107
+ return state.sortDir === 'desc' ? valB - valA : valA - valB;
108
+ }
109
+ return 0;
110
+ });
111
+ }
112
+
113
+ // --- Helper: Row HTML Builder ---
114
+ function buildRowHTML(row, rank, showTrack) {
115
+ const medal = rank === 1 ? '🥇' : rank === 2 ? '🥈' : rank === 3 ? '🥉' : rank;
116
+
117
+ const trackTd = showTrack
118
+ ? `<td><span class="track-tag ${row.track}">${row.track}</span></td>`
119
+ : '';
120
+
121
+ // Generate score cell, add active-col class to current sort column
122
+ function scoreCell(key, value) {
123
+ const isActive = (key === state.sortKey) ? ' active-col' : '';
124
+ return `<td class="score-cell${isActive}">${value.toFixed(1)}</td>`;
125
+ }
126
+
127
+ return `
128
+ <tr>
129
+ <td class="rank-col">${medal}</td>
130
+ <td class="method-col align-left">
131
+ <a href="${row.url}" target="_blank" class="method-name">${row.method}</a>
132
+ <div class="org-name">${row.date}</div>
133
+ </td>
134
+ ${trackTd}
135
+ <td>${row.agent}</td>
136
+ <td>${row.backbone}</td>
137
+ <td>${row.retriever}</td>
138
+ ${scoreCell('overall_em', row.overall_em)}
139
+ ${scoreCell('overall_f1', row.overall_f1)}
140
+ ${scoreCell('intra_em', row.intra_em)}
141
+ ${scoreCell('intra_f1', row.intra_f1)}
142
+ ${scoreCell('inter_em', row.inter_em)}
143
+ ${scoreCell('inter_f1', row.inter_f1)}
144
+ </tr>
145
+ `;
146
+ }
147
+
148
+ // --- Event Listeners ---
149
+
150
+ // 1. Champion Sub-Tabs (Standard/Open)
151
+ document.querySelectorAll('.sub-tab-btn').forEach(btn => {
152
+ btn.addEventListener('click', (e) => {
153
+ document.querySelectorAll('.sub-tab-btn').forEach(b => b.classList.remove('active'));
154
+ e.target.classList.add('active');
155
+ state.championTrack = e.target.dataset.track;
156
+ renderChampionTable();
157
+ });
158
+ });
159
+
160
+ // 2. Sorting
161
+ document.querySelectorAll('.sortable').forEach(th => {
162
+ th.addEventListener('click', (e) => {
163
+ const key = e.currentTarget.dataset.key;
164
+ if (state.sortKey === key) {
165
+ state.sortDir = state.sortDir === 'desc' ? 'asc' : 'desc';
166
+ } else {
167
+ state.sortKey = key;
168
+ state.sortDir = 'desc';
169
+ }
170
+
171
+ // Re-render the currently visible table
172
+ if (document.getElementById('view-leaderboard').style.display !== 'none') {
173
+ renderChampionTable();
174
+ } else {
175
+ renderFullTable();
176
+ }
177
+ });
178
+ });
179
+
180
+ // 3. Full Metrics Filters
181
+ document.getElementById('filter-track').addEventListener('change', (e) => {
182
+ state.filters.track = e.target.value;
183
+ renderFullTable();
184
+ });
185
+ document.getElementById('filter-agent').addEventListener('input', (e) => {
186
+ state.filters.agent = e.target.value;
187
+ renderFullTable();
188
+ });
189
+ document.getElementById('filter-backbone').addEventListener('input', (e) => {
190
+ state.filters.backbone = e.target.value;
191
+ renderFullTable();
192
+ });
193
+ document.getElementById('filter-retriever').addEventListener('input', (e) => {
194
+ state.filters.retriever = e.target.value;
195
+ renderFullTable();
196
+ });
197
+
198
+ // 4. Submit Form (AJAX, for displaying PR link feedback)
199
+ document.getElementById('submit-form').addEventListener('submit', async (e) => {
200
+ e.preventDefault();
201
+
202
+ const fileInput = document.getElementById('submit-file');
203
+ const submitBtn = document.getElementById('submit-btn');
204
+ const resultDiv = document.getElementById('submit-result');
205
+
206
+ if (!fileInput.files.length) return;
207
+
208
+ // Disable button, show loading
209
+ submitBtn.disabled = true;
210
+ submitBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Submitting...';
211
+ resultDiv.style.display = 'none';
212
+
213
+ const formData = new FormData();
214
+ formData.append('file', fileInput.files[0]);
215
+
216
+ try {
217
+ const response = await fetch('/upload', {
218
+ method: 'POST',
219
+ body: formData,
220
+ });
221
+
222
+ const data = await response.json();
223
+
224
+ resultDiv.style.display = 'block';
225
+
226
+ if (data.success) {
227
+ let html = `<strong>✅ ${data.message}</strong>`;
228
+ if (data.pr_url) {
229
+ html += `<br><br>🔗 <a href="${data.pr_url}" target="_blank">View your Pull Request →</a>`;
230
+ }
231
+ html += `<br><br><small>After maintainers review and merge your PR, scores will be computed and published on the leaderboard.</small>`;
232
+ resultDiv.className = 'success';
233
+ resultDiv.innerHTML = html;
234
+ } else {
235
+ let html = `<strong>❌ ${data.error || 'Submission failed.'}</strong>`;
236
+ if (data.details) {
237
+ html += '<ul>' + data.details.map(d => `<li>${d}</li>`).join('') + '</ul>';
238
+ }
239
+ resultDiv.className = 'error';
240
+ resultDiv.innerHTML = html;
241
+ }
242
+ } catch (err) {
243
+ resultDiv.style.display = 'block';
244
+ resultDiv.className = 'error';
245
+ resultDiv.innerHTML = `<strong>❌ Network error:</strong> ${err.message}`;
246
+ } finally {
247
+ submitBtn.disabled = false;
248
+ submitBtn.innerHTML = '<i class="fas fa-paper-plane"></i> Submit & Create PR';
249
+ }
250
+ });
251
+
252
+ // --- Init ---
253
+ renderChampionTable();
static/styles.css ADDED
@@ -0,0 +1,442 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @import url('https://fonts.googleapis.com/css2?family=Lato:wght@300;400;700&display=swap');
2
+
3
+ :root {
4
+ --bg: #f6f1eb;
5
+ --bg-ink: #141210;
6
+ --muted: #3d3832; /* Darkened: original #5a534a was too light */
7
+ --accent: #cb5a2d;
8
+ --accent-light: #e8d0c3;
9
+ --line: #ddd3c9;
10
+ --card: #fffaf4;
11
+ --shadow: 0 4px 20px rgba(20, 18, 16, 0.08);
12
+ --header-bg: #fdfdfd;
13
+ }
14
+
15
+ body {
16
+ margin: 0;
17
+ font-family: "Lato", sans-serif;
18
+ color: var(--bg-ink);
19
+ background: radial-gradient(circle at top left, #fdeee0 0%, var(--bg) 50%, #f3efe8 100%);
20
+ min-height: 100vh;
21
+ }
22
+
23
+ .page { max-width: 1280px; margin: 0 auto; padding: 0 24px 60px; }
24
+
25
+ /* ===== Paper Header ===== */
26
+ .paper-header {
27
+ padding: 48px 0 24px;
28
+ text-align: center;
29
+ }
30
+
31
+ /* RUC Logo */
32
+ .institution-logo {
33
+ margin-bottom: 20px;
34
+ }
35
+ .ruc-logo {
36
+ height: 64px;
37
+ width: auto;
38
+ opacity: 0.9;
39
+ }
40
+
41
+ .paper-title {
42
+ font-family: "Lato", sans-serif;
43
+ font-size: 2.2rem;
44
+ font-weight: 700;
45
+ line-height: 1.35;
46
+ color: var(--bg-ink);
47
+ margin: 0 auto 16px;
48
+ max-width: 900px;
49
+ }
50
+
51
+ .paper-authors {
52
+ font-size: 0.95rem;
53
+ color: var(--muted);
54
+ line-height: 1.7;
55
+ margin: 0 auto 6px;
56
+ max-width: 860px;
57
+ }
58
+ .paper-authors sup { font-size: 0.7em; color: var(--accent); margin-right: 1px; }
59
+
60
+ .paper-affiliations {
61
+ font-size: 0.88rem;
62
+ color: #666; /* Darkened: original #888 was too light */
63
+ margin: 0 auto 20px;
64
+ }
65
+ .paper-affiliations sup { font-size: 0.7em; color: var(--accent); }
66
+ .affil-sep { margin: 0 8px; }
67
+
68
+ .paper-links {
69
+ display: flex;
70
+ gap: 12px;
71
+ justify-content: center;
72
+ }
73
+
74
+ .badge-link {
75
+ text-decoration: none; color: var(--bg-ink); font-weight: bold;
76
+ padding: 8px 18px; border: 1px solid var(--line); border-radius: 20px;
77
+ background: white; transition: all 0.2s; display: flex; align-items: center; gap: 8px;
78
+ font-size: 0.9rem;
79
+ }
80
+ .badge-link:hover { border-color: var(--accent); color: var(--accent); }
81
+
82
+ /* ===== Leaderboard Hero ===== */
83
+ .leaderboard-hero {
84
+ text-align: center;
85
+ padding: 10px 0 30px;
86
+ }
87
+
88
+ .hero-divider {
89
+ width: 60px;
90
+ height: 3px;
91
+ background: var(--line);
92
+ margin: 0 auto 28px;
93
+ border-radius: 2px;
94
+ }
95
+
96
+ .leaderboard-title {
97
+ font-family: "Lato", sans-serif;
98
+ font-size: 2.1rem;
99
+ font-weight: 700;
100
+ color: var(--bg-ink);
101
+ margin: 0 0 8px;
102
+ }
103
+ .leaderboard-title i { color: var(--accent); margin-right: 6px; }
104
+
105
+ .leaderboard-subtitle {
106
+ font-size: 1.1rem;
107
+ color: var(--muted);
108
+ margin: 0 0 24px;
109
+ }
110
+
111
+ /* Stat Bar */
112
+ .stat-bar {
113
+ display: flex;
114
+ align-items: center;
115
+ justify-content: center;
116
+ gap: 24px;
117
+ background: white;
118
+ border: 1px solid var(--line);
119
+ border-radius: 12px;
120
+ padding: 16px 40px;
121
+ width: fit-content;
122
+ margin: 0 auto;
123
+ box-shadow: var(--shadow);
124
+ }
125
+
126
+ .stat-item {
127
+ display: flex;
128
+ flex-direction: column;
129
+ align-items: center;
130
+ gap: 2px;
131
+ }
132
+
133
+ .stat-num {
134
+ font-family: "Lato", sans-serif;
135
+ font-size: 1.5rem;
136
+ font-weight: 700;
137
+ color: var(--accent);
138
+ }
139
+
140
+ .stat-label {
141
+ font-size: 0.78rem;
142
+ color: var(--muted);
143
+ text-transform: uppercase;
144
+ letter-spacing: 0.5px;
145
+ }
146
+
147
+ .stat-divider {
148
+ width: 1px;
149
+ height: 32px;
150
+ background: var(--line);
151
+ }
152
+
153
+ /* ===== Info Section ===== */
154
+ .info-section { margin-bottom: 36px; }
155
+ .info-grid {
156
+ display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px;
157
+ }
158
+ @media (max-width: 800px) {
159
+ .info-grid { grid-template-columns: 1fr; }
160
+ }
161
+
162
+ .info-card {
163
+ background: white; padding: 24px; border-radius: 12px;
164
+ border: 1px solid var(--line); box-shadow: var(--shadow);
165
+ }
166
+ .info-card h3 {
167
+ margin: 0 0 12px; color: var(--accent);
168
+ display: flex; align-items: center; gap: 10px;
169
+ font-size: 1.2rem;
170
+ }
171
+ .info-card p {
172
+ font-size: 1.00rem;
173
+ line-height: 1.65;
174
+ color: var(--bg-ink); /* Changed to black, no longer using gray */
175
+ margin: 0 0 10px;
176
+ }
177
+ .info-card p:last-child { margin-bottom: 0; }
178
+ .info-card code {
179
+ background: #f4f0ec; padding: 2px 6px; border-radius: 4px; font-size: 0.85em;
180
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace; color: var(--accent);
181
+ }
182
+
183
+ .info-list {
184
+ list-style: none; padding: 0; margin: 8px 0 12px;
185
+ }
186
+ .info-list li {
187
+ font-size: 0.93rem;
188
+ line-height: 1.6;
189
+ color: var(--bg-ink); /* Changed to black */
190
+ padding: 5px 0 5px 16px;
191
+ position: relative;
192
+ }
193
+ .info-list li::before {
194
+ content: "";
195
+ position: absolute;
196
+ left: 0; top: 13px;
197
+ width: 6px; height: 6px;
198
+ border-radius: 50%;
199
+ background: var(--accent);
200
+ }
201
+ .info-list.compact li { padding: 3px 0 3px 16px; }
202
+ .info-list.compact li::before { top: 11px; }
203
+
204
+ /* ===== Main Navigation (Tabs) ===== */
205
+ .main-nav {
206
+ display: flex; justify-content: center; gap: 10px; margin-bottom: 30px;
207
+ background: rgba(255,255,255,0.6); padding: 10px; border-radius: 50px;
208
+ width: fit-content; margin-left: auto; margin-right: auto;
209
+ border: 1px solid var(--line);
210
+ }
211
+ .nav-btn {
212
+ border: none; background: transparent; padding: 10px 24px;
213
+ border-radius: 30px; font-size: 1rem; font-weight: bold; color: var(--muted);
214
+ cursor: pointer; transition: all 0.2s; display: flex; align-items: center; gap: 8px;
215
+ font-family: "Lato", sans-serif;
216
+ }
217
+ .nav-btn.active { background: var(--accent); color: white; box-shadow: 0 2px 10px rgba(203, 90, 45, 0.3); }
218
+ .nav-btn:hover:not(.active) { background: var(--accent-light); color: var(--accent); }
219
+
220
+ /* ===== Sub Tabs ===== */
221
+ .sub-tabs-container { display: flex; justify-content: flex-start; gap: 20px; margin-bottom: 20px; border-bottom: 2px solid var(--line); padding-bottom: 10px; }
222
+ .sub-tab-btn {
223
+ background: none; border: none; font-size: 1.1rem; font-weight: bold;
224
+ color: var(--muted); cursor: pointer; padding: 5px 10px;
225
+ border-bottom: 3px solid transparent; transition: all 0.2s;
226
+ font-family: "Lato", sans-serif;
227
+ }
228
+ .sub-tab-btn.active { color: var(--accent); border-bottom-color: var(--accent); }
229
+
230
+ /* ===== Filters Toolbar ===== */
231
+ .filters-toolbar {
232
+ display: flex; gap: 20px; margin-bottom: 20px; background: white;
233
+ padding: 15px; border-radius: 10px; border: 1px solid var(--line); align-items: center; flex-wrap: wrap;
234
+ }
235
+ .filter-group { display: flex; align-items: center; gap: 10px; }
236
+ .filter-group label { font-weight: bold; font-size: 0.9rem; color: var(--muted); }
237
+ .filter-group input, .filter-group select {
238
+ padding: 8px 12px; border: 1px solid var(--line); border-radius: 6px; outline: none; font-family: "Lato", sans-serif;
239
+ }
240
+
241
+ /* ===== Tables ===== */
242
+ .table-container { overflow-x: auto; background: white; border-radius: 12px; border: 1px solid var(--line); box-shadow: var(--shadow); }
243
+ table { width: 100%; border-collapse: collapse; min-width: 1000px; }
244
+
245
+ th, td {
246
+ padding: 12px;
247
+ text-align: center;
248
+ border-bottom: 1px solid #f0f0f0;
249
+ font-size: 0.95rem;
250
+ white-space: nowrap;
251
+ }
252
+
253
+ th {
254
+ background: var(--header-bg); padding: 15px 12px;
255
+ border-bottom: 1px solid var(--line); color: var(--muted); font-size: 0.9rem;
256
+ cursor: pointer; user-select: none;
257
+ }
258
+ th.align-left { text-align: left; }
259
+
260
+ /* Highlight table header for current sort column */
261
+ th.sortable.active-sort {
262
+ background: #fff5eb;
263
+ color: var(--accent);
264
+ font-weight: 700;
265
+ }
266
+
267
+ tr:last-child td { border-bottom: none; }
268
+ tr:hover td { background: #fafafa; }
269
+
270
+ .rank-col { font-weight: bold; font-size: 1.1rem; width: 60px; }
271
+
272
+ .method-col {
273
+ text-align: left;
274
+ min-width: 220px;
275
+ max-width: 350px;
276
+ white-space: normal;
277
+ }
278
+
279
+ .method-name { font-weight: 700; color: var(--bg-ink); text-decoration: none; font-size: 1rem; line-height: 1.4; display: block; }
280
+ .org-name { font-size: 0.8rem; color: #666; margin-top: 4px; }
281
+
282
+ .track-tag { font-size: 0.75rem; padding: 3px 8px; border-radius: 4px; font-weight: bold; text-transform: uppercase; }
283
+ .track-tag.Standard { background: #e3f2fd; color: #1565c0; }
284
+ .track-tag.Open { background: #fce4ec; color: #c2185b; }
285
+
286
+ /* ---- Score columns: default all black ---- */
287
+ td.score-cell {
288
+ font-weight: 600;
289
+ color: var(--bg-ink);
290
+ font-size: 0.95rem;
291
+ }
292
+
293
+ /* Highlight score in current sort column as orange */
294
+ td.score-cell.active-col {
295
+ font-weight: 700;
296
+ color: var(--accent);
297
+ background: #fff8f5;
298
+ font-size: 1.05rem;
299
+ }
300
+
301
+ /* ===== Submit Section ===== */
302
+ .submit-container { display: flex; justify-content: center; }
303
+ .submit-card {
304
+ background: white; width: 100%; max-width: 700px; padding: 32px;
305
+ border-radius: 16px; border: 1px solid var(--line); box-shadow: var(--shadow);
306
+ }
307
+ .submit-card h2 {
308
+ margin-top: 0;
309
+ display: flex; align-items: center; gap: 10px;
310
+ }
311
+ .submit-card h2 i { color: var(--accent); }
312
+ .submit-card h4 { margin: 20px 0 10px; color: var(--bg-ink); }
313
+
314
+ /* Submit Flow Diagram */
315
+ .submit-flow {
316
+ display: flex;
317
+ align-items: center;
318
+ justify-content: center;
319
+ gap: 12px;
320
+ margin: 24px 0;
321
+ padding: 20px;
322
+ background: #faf8f5;
323
+ border-radius: 12px;
324
+ border: 1px solid var(--line);
325
+ }
326
+ .flow-step {
327
+ display: flex;
328
+ align-items: center;
329
+ gap: 12px;
330
+ flex: 1;
331
+ }
332
+ .flow-icon {
333
+ width: 44px; height: 44px;
334
+ border-radius: 50%;
335
+ background: var(--accent);
336
+ color: white;
337
+ display: flex; align-items: center; justify-content: center;
338
+ font-size: 1.1rem;
339
+ flex-shrink: 0;
340
+ }
341
+ .flow-text {
342
+ font-size: 0.85rem;
343
+ line-height: 1.4;
344
+ color: var(--bg-ink);
345
+ }
346
+ .flow-text strong { display: block; font-size: 0.9rem; }
347
+ .flow-arrow {
348
+ color: var(--line);
349
+ font-size: 1.2rem;
350
+ flex-shrink: 0;
351
+ }
352
+
353
+ .field-desc p {
354
+ font-size: 0.9rem; color: var(--bg-ink); margin: 6px 0; line-height: 1.5;
355
+ }
356
+ .field-desc code {
357
+ background: #f4f0ec; padding: 2px 6px; border-radius: 4px; font-size: 0.85em;
358
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace; color: var(--accent);
359
+ }
360
+
361
+ .code-block {
362
+ background: #faf8f5; padding: 16px; border-radius: 8px;
363
+ font-size: 0.85rem; overflow-x: auto; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; color: #333;
364
+ border: 1px solid #e8e2da;
365
+ white-space: pre;
366
+ line-height: 1.55;
367
+ }
368
+
369
+ .upload-form { margin-top: 16px; }
370
+ .upload-form input[type="file"] {
371
+ width: 100%; padding: 10px; border: 2px dashed var(--line); border-radius: 8px;
372
+ background: #faf8f5; cursor: pointer; font-family: "Lato", sans-serif;
373
+ box-sizing: border-box;
374
+ }
375
+
376
+ .btn.primary {
377
+ background: var(--accent); color: white; border: none; padding: 12px 24px;
378
+ border-radius: 8px; font-weight: bold; cursor: pointer; width: 100%; margin-top: 16px;
379
+ transition: background 0.2s;
380
+ font-family: "Lato", sans-serif; font-size: 1rem;
381
+ display: flex; align-items: center; justify-content: center; gap: 8px;
382
+ }
383
+ .btn.primary:hover { background: #b14a1f; }
384
+ .btn.primary:disabled { background: #ccc; cursor: not-allowed; }
385
+
386
+ /* Submit Result Messages */
387
+ #submit-result {
388
+ margin-top: 20px;
389
+ padding: 16px;
390
+ border-radius: 8px;
391
+ font-size: 0.93rem;
392
+ line-height: 1.6;
393
+ }
394
+ #submit-result.success {
395
+ background: #e8f5e9;
396
+ border: 1px solid #a5d6a7;
397
+ color: #2e7d32;
398
+ }
399
+ #submit-result.error {
400
+ background: #fce4ec;
401
+ border: 1px solid #ef9a9a;
402
+ color: #c62828;
403
+ }
404
+ #submit-result a {
405
+ color: var(--accent);
406
+ font-weight: bold;
407
+ }
408
+
409
+ /* ===== Citation ===== */
410
+ .citation-section {
411
+ margin-top: 48px;
412
+ text-align: center;
413
+ }
414
+ .citation-section h3 {
415
+ color: var(--muted);
416
+ font-size: 1rem;
417
+ margin-bottom: 12px;
418
+ display: flex; align-items: center; justify-content: center; gap: 8px;
419
+ }
420
+ .citation-section h3 i { color: var(--accent); }
421
+ .citation-block {
422
+ text-align: left;
423
+ max-width: 800px;
424
+ margin: 0 auto;
425
+ font-size: 0.82rem;
426
+ }
427
+
428
+ /* ===== Footer ===== */
429
+ .footer { text-align: center; padding: 40px; color: var(--muted); font-size: 0.9rem; }
430
+
431
+ /* ===== Responsive ===== */
432
+ @media (max-width: 640px) {
433
+ .paper-title { font-size: 1.4rem; }
434
+ .leaderboard-title { font-size: 1.8rem; }
435
+ .stat-bar { flex-wrap: wrap; padding: 16px 20px; gap: 16px; }
436
+ .stat-divider { display: none; }
437
+ .paper-authors { font-size: 0.85rem; }
438
+ .paper-links { flex-wrap: wrap; }
439
+ .submit-flow { flex-direction: column; gap: 16px; }
440
+ .flow-arrow { transform: rotate(90deg); }
441
+ .ruc-logo { height: 48px; }
442
+ }
templates/index.html ADDED
@@ -0,0 +1,310 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>DISBench Leaderboard</title>
7
+ <!-- Favicon: RUC Logo -->
8
+ <link rel="icon" type="image/svg+xml" href="{{ url_for('static', filename='ruc-logo.png') }}" />
9
+ <link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}" />
10
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" />
11
+ </head>
12
+ <body>
13
+ <div class="page">
14
+
15
+ <!-- Paper Header -->
16
+ <header class="paper-header">
17
+ <!-- RUC Logo -->
18
+ <!-- <div class="institution-logo">
19
+ <img src="{{ url_for('static', filename='ruc-logo.png') }}" alt="Renmin University of China" class="ruc-logo" />
20
+ </div> -->
21
+
22
+ <h1 class="paper-title">DeepImageSearch: Benchmarking Multimodal Agents for Context-Aware Image Retrieval in Visual Histories</h1>
23
+ <p class="paper-authors">
24
+ Chenlong Deng<sup>1</sup>, Mengjie Deng<sup>1</sup>, Junjie Wu<sup>2</sup>, Dun Zeng<sup>2</sup>, Teng Wang<sup>2</sup>, Qingsong Xie<sup>2</sup>, Jiadeng Huang<sup>2</sup>,
25
+ Shengjie Ma<sup>1</sup>, Changwang Zhang<sup>2</sup>, Zhaoxiang Wang<sup>2</sup>, Jun Wang<sup>2</sup>, Yutao Zhu<sup>1</sup>, Zhicheng Dou<sup>1</sup>
26
+ </p>
27
+ <p class="paper-affiliations">
28
+ <span class="affil"><sup>1</sup> Gaoling School of Artificial Intelligence, Renmin University of China</span>
29
+ <span class="affil-sep">&middot;</span>
30
+ <span class="affil"><sup>2</sup> OPPO Research Institute</span>
31
+ </p>
32
+ <div class="paper-links">
33
+ <a class="badge-link" href="https://arxiv.org/abs/2602.10809" target="_blank"><i class="fas fa-file-pdf"></i> Paper</a>
34
+ <a class="badge-link" href="https://github.com/RUC-NLPIR/DeepImageSearch" target="_blank"><i class="fab fa-github"></i> Code</a>
35
+ <a class="badge-link" href="https://huggingface.co/datasets/RUC-NLPIR/DISBench" target="_blank"><i class="fas fa-database"></i> Dataset</a>
36
+ </div>
37
+ </header>
38
+
39
+ <!-- Leaderboard Title -->
40
+ <section class="leaderboard-hero">
41
+ <div class="hero-divider"></div>
42
+ <h2 class="leaderboard-title"><i class="fas fa-ranking-star"></i> 🏆 DISBench Leaderboard</h2>
43
+ <p class="leaderboard-subtitle">Track and compare multimodal agents on the DeepImageSearch task</p>
44
+ </section>
45
+
46
+ <main>
47
+
48
+ <!-- Info Cards -->
49
+ <section class="info-section">
50
+ <div class="info-grid">
51
+
52
+ <div class="info-card">
53
+ <h3><i class="fas fa-lightbulb"></i> What is DeepImageSearch and DISBench?</h3>
54
+ <p>
55
+ <strong>DeepImageSearch</strong> represents a paradigm evolution in image retrieval, advancing from independent image matching to <strong>corpus-level contextual reasoning over visual histories</strong>.
56
+ People capture thousands of photos over the years, forming rich episodic memories where information is distributed across temporal sequences rather than confined to single snapshots.
57
+ Many real-world queries over such episodic memories cannot be resolved by evaluating each image independently.
58
+ The target images can only be identified by exploring and reasoning over the entire image corpus.
59
+ This <strong>corpus-level contextual reasoning</strong> makes <strong>agentic capabilities essential rather than auxiliary</strong>.
60
+ </p>
61
+ <p>
62
+ <strong>DISBench</strong> is the <strong>first benchmark</strong> designed for this task.
63
+ Given a user's photo collection and a natural language query,
64
+ agents must autonomously plan search trajectories, discover latent cross-image associations,
65
+ and chain scattered visual evidence through multi-step exploration to return the exact set of qualifying images.
66
+ The benchmark covers two reasoning patterns:
67
+ <strong>Intra-Event</strong> queries that require locating a target event via contextual clues and then filtering within it,
68
+ and <strong>Inter-Event</strong> queries that demand scanning across multiple events to find recurring elements under temporal or spatial constraints.
69
+ </p>
70
+ </div>
71
+
72
+ <div class="info-card">
73
+ <h3><i class="fas fa-book-open"></i> How to Read the Leaderboard</h3>
74
+ <p>
75
+ <strong>Champion List</strong> shows top results per track. Use the sub-tabs to switch between:
76
+ </p>
77
+ <ul class="info-list">
78
+ <li><span class="track-tag Standard">Standard</span> Pre-processing is limited to encoding images into embeddings for building a retrieval index. No additional pre-computation (e.g., captioning, graph construction) is allowed. Tests agentic reasoning over raw visual data.</li>
79
+ <li><span class="track-tag Open">Open</span> Arbitrary pre-processing is permitted (captioning, knowledge graph construction, structured indexing, etc.). Tests system-level upper bounds with full engineering freedom.</li>
80
+ </ul>
81
+ <p>
82
+ <strong>Full Analysis</strong> lets you compare across tracks and filter by agent framework, backbone model, or retriever. Click any score column header to sort and highlight.
83
+ </p>
84
+ </div>
85
+
86
+ <div class="info-card">
87
+ <h3><i class="fas fa-ruler-combined"></i> Evaluation Metrics</h3>
88
+ <p>
89
+ All metrics are computed at the <strong>set level</strong>: models must predict the exact set of target images for each query.
90
+ </p>
91
+ <ul class="info-list">
92
+ <li><strong>EM (Exact Match)</strong>: the predicted set must be identical to the ground truth (no extra, no missing).</li>
93
+ <li><strong>F1 Score</strong>: harmonic mean of precision and recall over the predicted vs. ground-truth image sets.</li>
94
+ </ul>
95
+ <p>
96
+ Scores are reported across three dimensions:
97
+ <strong>Overall</strong> (all queries),
98
+ <strong>Intra-Event</strong> (locate a specific event, then filter targets within it), and
99
+ <strong>Inter-Event</strong> (scan across multiple events to find recurring elements under temporal/spatial constraints).
100
+ </p>
101
+ </div>
102
+
103
+ <div class="info-card">
104
+ <h3><i class="fas fa-cloud-arrow-up"></i> How to Submit</h3>
105
+ <p>
106
+ Prepare a <code>.json</code> file with two fields: <code>meta</code> (your method info) and <code>predictions</code> (your model outputs).
107
+ Go to the <strong>Submit</strong> tab, upload the file, and the system will automatically create a
108
+ <strong>Pull Request</strong> on the Space repository for review.
109
+ </p>
110
+ <p>
111
+ After maintainers merge your PR, the evaluation script will compute scores and update the leaderboard.
112
+ </p>
113
+ <p>Required fields in <code>meta</code>:</p>
114
+ <ul class="info-list compact">
115
+ <li><code>method_name</code>: display name for your method</li>
116
+ <li><code>agent_framework</code>, <code>backbone_model</code>, <code>retriever_model</code></li>
117
+ <li><code>track</code>: <code>"Standard"</code> or <code>"Open"</code></li>
118
+ </ul>
119
+ <p>See the <strong>Submit</strong> tab for the full JSON template and format details.</p>
120
+ </div>
121
+
122
+ </div>
123
+ </section>
124
+
125
+ <!-- Main Navigation -->
126
+ <nav class="main-nav">
127
+ <button class="nav-btn active" onclick="switchMainTab('leaderboard')">
128
+ <i class="fas fa-trophy"></i> Champion List
129
+ </button>
130
+ <button class="nav-btn" onclick="switchMainTab('full-metrics')">
131
+ <i class="fas fa-chart-bar"></i> Full Analysis
132
+ </button>
133
+ <button class="nav-btn" onclick="switchMainTab('submit')">
134
+ <i class="fas fa-upload"></i> Submit
135
+ </button>
136
+ </nav>
137
+
138
+ <!-- Champion List View -->
139
+ <section id="view-leaderboard" class="view-section active">
140
+ <div class="sub-tabs-container">
141
+ <button class="sub-tab-btn active" data-track="Standard">Standard Track</button>
142
+ <button class="sub-tab-btn" data-track="Open">Open Track</button>
143
+ </div>
144
+
145
+ <div class="table-container">
146
+ <table id="champion-table">
147
+ <thead>
148
+ <tr>
149
+ <th class="rank-col">Rank</th>
150
+ <th class="method-col align-left">Method</th>
151
+ <th>Agent</th>
152
+ <th>Backbone</th>
153
+ <th>Retriever</th>
154
+ <th class="sortable active-sort" data-key="overall_em">Overall EM ↓</th>
155
+ <th class="sortable" data-key="overall_f1">Overall F1</th>
156
+ <th class="sortable" data-key="intra_em">Intra EM</th>
157
+ <th class="sortable" data-key="intra_f1">Intra F1</th>
158
+ <th class="sortable" data-key="inter_em">Inter EM</th>
159
+ <th class="sortable" data-key="inter_f1">Inter F1</th>
160
+ </tr>
161
+ </thead>
162
+ <tbody></tbody>
163
+ </table>
164
+ </div>
165
+ </section>
166
+
167
+ <!-- Full Analysis View -->
168
+ <section id="view-full-metrics" class="view-section" style="display: none;">
169
+
170
+ <div class="filters-toolbar">
171
+ <div class="filter-group">
172
+ <label>Track:</label>
173
+ <select id="filter-track"><option value="all">All Tracks</option><option value="Standard">Standard</option><option value="Open">Open</option></select>
174
+ </div>
175
+ <div class="filter-group">
176
+ <label>Agent:</label>
177
+ <input type="text" id="filter-agent" placeholder="e.g. ImageSeeker">
178
+ </div>
179
+ <div class="filter-group">
180
+ <label>Backbone:</label>
181
+ <input type="text" id="filter-backbone" placeholder="e.g. Gemini">
182
+ </div>
183
+ <div class="filter-group">
184
+ <label>Retriever:</label>
185
+ <input type="text" id="filter-retriever" placeholder="e.g. CLIP">
186
+ </div>
187
+ </div>
188
+
189
+ <div class="table-container">
190
+ <table id="full-table">
191
+ <thead>
192
+ <tr>
193
+ <th class="rank-col">Rank</th>
194
+ <th class="method-col align-left">Method</th>
195
+ <th class="track-col">Track</th>
196
+ <th>Agent</th>
197
+ <th>Backbone</th>
198
+ <th>Retriever</th>
199
+ <th class="sortable active-sort" data-key="overall_em">Overall EM ↓</th>
200
+ <th class="sortable" data-key="overall_f1">Overall F1</th>
201
+ <th class="sortable" data-key="intra_em">Intra EM</th>
202
+ <th class="sortable" data-key="intra_f1">Intra F1</th>
203
+ <th class="sortable" data-key="inter_em">Inter EM</th>
204
+ <th class="sortable" data-key="inter_f1">Inter F1</th>
205
+ </tr>
206
+ </thead>
207
+ <tbody></tbody>
208
+ </table>
209
+ </div>
210
+ </section>
211
+
212
+ <!-- Submit View -->
213
+ <section id="view-submit" class="view-section" style="display: none;">
214
+ <div class="submit-container">
215
+ <div class="submit-card">
216
+ <h2><i class="fas fa-code-pull-request"></i> Submit via Pull Request</h2>
217
+ <p>
218
+ Upload a <code>.json</code> file containing your method metadata and predictions.
219
+ The system will create a <strong>Pull Request</strong> on the
220
+ <a href="https://huggingface.co/spaces/" target="_blank">Space repository</a>.
221
+ Maintainers will review, run evaluation, and merge your results into the leaderboard.
222
+ </p>
223
+
224
+ <div class="submit-flow">
225
+ <div class="flow-step">
226
+ <div class="flow-icon"><i class="fas fa-upload"></i></div>
227
+ <div class="flow-text"><strong>1. Upload</strong><br>Submit your JSON file below</div>
228
+ </div>
229
+ <div class="flow-arrow"><i class="fas fa-arrow-right"></i></div>
230
+ <div class="flow-step">
231
+ <div class="flow-icon"><i class="fas fa-code-pull-request"></i></div>
232
+ <div class="flow-text"><strong>2. PR Created</strong><br>A Pull Request is opened automatically</div>
233
+ </div>
234
+ <div class="flow-arrow"><i class="fas fa-arrow-right"></i></div>
235
+ <div class="flow-step">
236
+ <div class="flow-icon"><i class="fas fa-check-circle"></i></div>
237
+ <div class="flow-text"><strong>3. Review &amp; Merge</strong><br>Maintainers evaluate and publish scores</div>
238
+ </div>
239
+ </div>
240
+
241
+ <h4>JSON Format</h4>
242
+ <pre class="code-block">{
243
+ "meta": {
244
+ "method_name": "My-Agent",
245
+ "organization": "My-Org",
246
+ "project_url": "https://github.com/...",
247
+ "agent_framework": "ImageSeeker",
248
+ "backbone_model": "Gemini-3-Pro",
249
+ "retriever_model": "Qwen3-VL-Embedding-8B",
250
+ "track": "Standard"
251
+ },
252
+ "predictions": {
253
+ "query_001": ["photo_id_1", "photo_id_2"],
254
+ "query_002": ["photo_id_5"],
255
+ ...
256
+ }
257
+ }</pre>
258
+
259
+ <h4>Field Descriptions</h4>
260
+ <div class="field-desc">
261
+ <p><code>meta.method_name</code>: Display name shown on the leaderboard (required)</p>
262
+ <p><code>meta.organization</code>: Your team or organization name (optional, not displayed)</p>
263
+ <p><code>meta.project_url</code>: Link to paper, code, or project page (optional)</p>
264
+ <p><code>meta.agent_framework</code>: Agent framework used (e.g. ReAct, ImageSeeker)</p>
265
+ <p><code>meta.backbone_model</code>: Main LLM/VLM backbone (e.g. GPT-4o, Gemini-3-Pro)</p>
266
+ <p><code>meta.retriever_model</code>: Retrieval model used (e.g. Qwen3-VL-Embedding-8B)</p>
267
+ <p><code>meta.track</code>: Must be <code>"Standard"</code> or <code>"Open"</code></p>
268
+ <p><code>predictions</code>: A dict mapping each query ID to a list of predicted photo IDs</p>
269
+ </div>
270
+
271
+ <form id="submit-form" class="upload-form">
272
+ <input type="file" name="file" id="submit-file" accept=".json" required>
273
+ <button type="submit" class="btn primary" id="submit-btn">
274
+ <i class="fas fa-paper-plane"></i> Submit &amp; Create PR
275
+ </button>
276
+ </form>
277
+
278
+ <!-- Submission Result -->
279
+ <div id="submit-result" style="display: none;"></div>
280
+ </div>
281
+ </div>
282
+ </section>
283
+
284
+ </main>
285
+
286
+ <!-- Citation -->
287
+ <section class="citation-section">
288
+ <h3><i class="fas fa-quote-left"></i> Citation</h3>
289
+ <pre class="code-block citation-block">@misc{deng2026deepimagesearchbenchmarkingmultimodalagents,
290
+ title={DeepImageSearch: Benchmarking Multimodal Agents for Context-Aware Image Retrieval in Visual Histories},
291
+ author={Chenlong Deng and Mengjie Deng and Junjie Wu and Dun Zeng and Teng Wang and Qingsong Xie and Jiadeng Huang and Shengjie Ma and Changwang Zhang and Zhaoxiang Wang and Jun Wang and Yutao Zhu and Zhicheng Dou},
292
+ year={2026},
293
+ eprint={2602.10809},
294
+ archivePrefix={arXiv},
295
+ primaryClass={cs.CV},
296
+ url={https://arxiv.org/abs/2602.10809}
297
+ }</pre>
298
+ </section>
299
+
300
+ <footer class="footer">
301
+ <p>DISBench Leaderboard &middot; Powered by Hugging Face Spaces</p>
302
+ </footer>
303
+ </div>
304
+
305
+ <script>
306
+ window.SERVER_DATA = {{ data | tojson | default([]) }};
307
+ </script>
308
+ <script src="{{ url_for('static', filename='script.js') }}"></script>
309
+ </body>
310
+ </html>