File size: 11,095 Bytes
6ae8aba
 
 
10db86a
5b42e90
78efe8b
6ae8aba
5b42e90
bf9faf8
6ae8aba
5b42e90
 
 
 
 
 
 
 
 
bf9faf8
 
 
 
 
 
6ae8aba
01ad525
baae497
74aafd0
 
10db86a
74aafd0
01ad525
baae497
74aafd0
 
10db86a
74aafd0
bf9faf8
 
 
 
 
 
01ad525
 
74aafd0
 
10db86a
74aafd0
bf9faf8
 
 
 
 
 
64dcfc0
5b42e90
64dcfc0
78ffed6
 
 
 
64dcfc0
 
bf9faf8
 
10db86a
 
78ffed6
bf9faf8
 
6ae8aba
 
 
 
 
bf9faf8
78efe8b
 
 
 
 
 
 
 
bf9faf8
 
 
 
 
 
 
 
 
6ae8aba
 
 
 
 
 
 
 
 
74aafd0
6ae8aba
 
 
 
 
bf9faf8
74aafd0
bf9faf8
 
 
 
74aafd0
 
 
 
 
0d46775
74aafd0
 
10db86a
592b931
74aafd0
5fe97ad
74aafd0
 
6ae8aba
 
 
 
 
 
 
 
74aafd0
6ae8aba
74aafd0
 
64dcfc0
bf9faf8
 
5b42e90
 
74aafd0
 
 
64dcfc0
74aafd0
 
 
 
64dcfc0
74aafd0
 
 
 
 
 
 
 
6ae8aba
 
 
 
 
5b42e90
64dcfc0
5b42e90
10db86a
6ae8aba
 
 
 
 
10db86a
 
 
bf9faf8
10db86a
 
 
bf9faf8
 
 
 
 
baae497
64dcfc0
 
bf9faf8
64dcfc0
 
bf9faf8
6ae8aba
 
 
 
5b42e90
10db86a
6ae8aba
bf9faf8
6ae8aba
 
 
 
 
 
 
 
 
 
 
 
5b42e90
6ae8aba
 
 
 
 
d7a1226
 
10db86a
d7a1226
10db86a
 
 
d7a1226
 
 
10db86a
d7a1226
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
import json
import os
import pandas as pd
from datetime import datetime
from utils import create_hyperlinked_names, process_model_size, MODEL_SIZE_COL_NAME
from datasets import *

BASE_COLS = ['Rank', 'Models', MODEL_SIZE_COL_NAME, 'Date']
BASE_DATA_TITLE_TYPE = ['str', 'markdown', 'str', 'str']

OVERALL_COLS_V2 = ["Overall-V2", 'Image-Overall', 'Video-Overall', 'Visdoc-Overall']
COLUMN_NAMES_V2 = BASE_COLS + OVERALL_COLS_V2
DATA_TITLE_TYPE_V2 = BASE_DATA_TITLE_TYPE + \
                    ['number'] * len(OVERALL_COLS_V2)

OVERALL_COLS_V3 = ["Overall", "Overall-V3๐Ÿ†•", "Text-Overall", "Audio-Overall", "Agent-Overall"]
COLUMN_NAMES_V3 = BASE_COLS + OVERALL_COLS_V3
DATA_TITLE_TYPE_V3 = BASE_DATA_TITLE_TYPE + \
                    ['number'] * len(OVERALL_COLS_V3)

SUB_TASKS_T = ["FollowIR", "R2MED", "InfoSearch", "BRIGHT", "LongEmbed", "MultiConIR", "NanoBEIR"]
TASKS_T = ['Text-Overall'] + SUB_TASKS_T + ALL_DATASETS_SPLITS['text']
COLUMN_NAMES_T = BASE_COLS + TASKS_T
DATA_TITLE_TYPE_T = BASE_DATA_TITLE_TYPE + \
                    ['number'] * len(TASKS_T)

SUB_TASKS_I = ["I-CLS", "I-QA", "I-RET", "I-VG"]
TASKS_I = ['Image-Overall'] + SUB_TASKS_I + ALL_DATASETS_SPLITS['image']
COLUMN_NAMES_I = BASE_COLS + TASKS_I
DATA_TITLE_TYPE_I = BASE_DATA_TITLE_TYPE + \
                    ['number'] * len(TASKS_I)

SUB_TASKS_V = ["V-CLS", "V-QA", "V-RET", "V-MRET"]
TASKS_V = ['Video-Overall'] + SUB_TASKS_V + ALL_DATASETS_SPLITS['video']
COLUMN_NAMES_V = BASE_COLS + TASKS_V
DATA_TITLE_TYPE_V = BASE_DATA_TITLE_TYPE + \
                    ['number'] * len(TASKS_V)

SUB_TASKS_A = ["A-CLS", "A-RET"]
TASKS_A = ['Audio-Overall'] + SUB_TASKS_A + ALL_DATASETS_SPLITS['audio']
COLUMN_NAMES_A = BASE_COLS + TASKS_A
DATA_TITLE_TYPE_A = BASE_DATA_TITLE_TYPE + \
                    ['number'] * len(TASKS_A)

SUB_TASKS_D = ['ViDoRe-V1', 'ViDoRe-V2', 'VisRAG', 'VisDoc-OOD']
TASKS_D = ['Visdoc-Overall'] + SUB_TASKS_D + ALL_DATASETS_SPLITS['visdoc']
COLUMN_NAMES_D = BASE_COLS + TASKS_D
DATA_TITLE_TYPE_D = BASE_DATA_TITLE_TYPE + \
                    ['number'] * len(TASKS_D)

SUB_TASKS_AG = ['Tool', 'GUI', 'Memory']
TASKS_AG = ['Agent-Overall'] + SUB_TASKS_AG + ALL_DATASETS_SPLITS['agent']
COLUMN_NAMES_AG = BASE_COLS + TASKS_AG
DATA_TITLE_TYPE_AG = BASE_DATA_TITLE_TYPE + \
                    ['number'] * len(TASKS_AG)

TABLE_INTRODUCTION = """**MMEB**: Massive MultiModal Embedding Benchmark \n
                        Models are ranked based on **Overall**(V3-ALL). **Overall-V3๐Ÿ†•**: Newly added datasets in V3."""
TABLE_INTRODUCTION_I = """**I-CLS**: Image Classification, **I-QA**: (Image) Visual Question Answering, **I-RET**: Image Retrieval, **I-VG**: (Image) Visual Grounding \n
                        Models are ranked based on **Image-Overall**\n
                        **Models from the old V1 leaderboard are missing detailed scores of each dataset. 
                        We hope the authors of the models on V1 leaderboard could rerun your models using our updated V2 pipeline, 
                        and provide us the scores sheet with the new format, so that we can make them consistent with the other models' formats.**"""
TABLE_INTRODUCTION_V = """**V-CLS**: Video Classification, **V-QA**: (Video) Visual Question Answering, **V-RET**: Video Retrieval, **V-MRET**: Video Moment Retrieval \n
                        Models are ranked based on **Video-Overall**"""
TABLE_INTRODUCTION_A = """**A-CLS**: Audio Classification, **A-RET**: Audio Retrieval \n
                        Models are ranked based on **Audio-Overall**"""
TABLE_INTRODUCTION_D = """โš ๏ธ Please re-evaluate your models if you see a 0 on ViDoSeek-page-fixed or MMLongBench-page-fixed datasets. \n
**VisDoc**: Visual Document Understanding \n
                        Models are ranked based on **Visdoc-Overall**"""
TABLE_INTRODUCTION_AG = """**Tool**: Tool Retrieval, **GUI**: GUI Control, **Memory**: Agent Memory Retrieval \n
                        Models are ranked based on **Agent-Overall**"""

LEADERBOARD_INFO = """
## Dataset Summary
"""

CITATION_BUTTON_TEXT_V2 = r"""@misc{meng2025vlm2vecv2advancingmultimodalembedding,
      title={VLM2Vec-V2: Advancing Multimodal Embedding for Videos, Images, and Visual Documents}, 
      author={Rui Meng and Ziyan Jiang and Ye Liu and Mingyi Su and Xinyi Yang and Yuepeng Fu and Can Qin and Zeyuan Chen and Ran Xu and Caiming Xiong and Yingbo Zhou and Wenhu Chen and Semih Yavuz},
      year={2025},
      eprint={2507.04590},
      archivePrefix={arXiv},
      primaryClass={cs.CV},
      url={https://arxiv.org/abs/2507.04590}, 
}"""
CITATION_BUTTON_TEXT_V3 = r"""@misc{huang2026mmebv3measuringperformancegaps,
      title={MMEB-V3: Measuring the Performance Gaps of Omni-Modality Embedding Models}, 
      author={Haohang Huang and Xuan Lu and Mingyi Su and Xuan Zhang and Ziyan Jiang and Ping Nie and Kai Zou and Tomas Pfister and Wenhu Chen and Wei Zhang and Xiaoyu Shen and Rui Meng},
      year={2026},
      eprint={2604.23321},
      archivePrefix={arXiv},
      primaryClass={cs.IR},
      url={https://arxiv.org/abs/2604.23321}, 
}"""

def load_single_json(file_path):
    with open(file_path, 'r') as file:
        data = json.load(file)
    return data

def load_data(base_dir=SCORE_BASE_DIR):
    all_data = []
    for file_name in os.listdir(base_dir):
        if file_name.endswith('.json'):
            file_path = os.path.join(base_dir, file_name)
            data = load_single_json(file_path)
            all_data.append(data)
    return all_data

def load_scores(raw_scores={}):
    """This function loads the raw scores from the user provided scores summary and flattens them into a single dictionary."""
    # temp fix, will figure out later ===========
    if any(_ in raw_scores for _ in ['tool', 'gui', 'memory']):
        raw_scores['agent'] = raw_scores.pop('tool', {}) | raw_scores.pop('gui', {}) | raw_scores.pop('memory', {})
    # ===========================================
    all_scores = {}
    for modality, datasets_list in DATASETS.items(): # Ex.: ('image', {'I-CLS': [...], 'I-QA': [...]})
        for sub_task, datasets in datasets_list.items(): # Ex.: ('I-CLS', ['VOC2007', 'N24News', ...])
            for dataset in datasets: # Ex.: 'VOC2007'
                score = raw_scores.get(modality, {}).get(dataset, 0.0)
                score = 0.0 if isinstance(score, str) and "N/A" in score else score
                metric = SPECIAL_METRICS.get(dataset, 'hit@1')
                if isinstance(score, dict):
                    if 'visdoc' in modality:
                        metric = "ndcg_linear@5" if "ndcg_linear@5" in score else "ndcg@5"
                    score = score.get(metric, 0.0)
                all_scores[dataset] = round(score * 100.0, 2)
    return all_scores

def calculate_score(raw_scores=None):
    """This function calculates the overall average scores for all datasets as well as avg scores for each modality and sub-task based on the raw scores.
    """
    def get_avg(sum_score, leng):
        avg = sum_score / leng if leng > 0 else 0.0
        avg = round(avg, 2)  # Round to 2 decimal places
        return avg
    
    all_scores = load_scores(raw_scores)
    avg_scores = {}

    # Calculate overall score for all datasets
    avg_scores['Overall'] = get_avg(sum(all_scores.values()), len(ALL_DATASETS))
    v2_scores = {k:v for k,v in all_scores.items() if k in ALL_DATASETS_SPLITS['image'] or k in ALL_DATASETS_SPLITS['video'] or k in ALL_DATASETS_SPLITS['visdoc']}
    avg_scores['Overall-V2'] = get_avg(sum(v2_scores.values()), len(v2_scores))
    v3_newonly_scores = {k:v for k,v in all_scores.items() if k in ALL_DATASETS_SPLITS['text'] or k in ALL_DATASETS_SPLITS['audio'] or k in ALL_DATASETS_SPLITS['agent']}
    avg_scores['Overall-V3๐Ÿ†•'] = get_avg(sum(v3_newonly_scores.values()), len(v3_newonly_scores))

    # Calculate scores for each modality
    for modality in MODALITIES:
        datasets_for_each_modality = ALL_DATASETS_SPLITS[modality]
        avg_scores[f"{modality.capitalize()}-Overall"] = get_avg(
            sum(all_scores.get(dataset, 0.0) for dataset in datasets_for_each_modality),
            len(datasets_for_each_modality)
        )
    
    # Calculate scores for each sub-task
    for modality, datasets_list in DATASETS.items():
        for sub_task, datasets in datasets_list.items():
            sub_task_score = sum(all_scores.get(dataset, 0.0) for dataset in datasets)
            avg_scores[sub_task] = get_avg(sub_task_score, len(datasets))

    all_scores.update(avg_scores)
    return all_scores

def generate_model_row(data):
    metadata = data['metadata']
    row = {
        'Models': metadata.get('model_name', None), 
        MODEL_SIZE_COL_NAME: metadata.get('model_size', None),
        'URL': metadata.get('url', None), 
        'Submitted by': metadata.get('data_source', 'Self-Reported'),
        'Date': metadata.get('report_generated_date', None)
    }
    scores = calculate_score(data['metrics'])
    row.update(scores)
    return row

def print_time(time: str|None):
    try:
        dt = datetime.strptime(time, "%Y-%m-%dT%H:%M:%S.%f")
        return dt.strftime("%y-%m-%d")
    except (ValueError, TypeError):
        return 'unknown'

medal_map = {
    "1": "๐Ÿ†",
    "2": "๐Ÿฅˆ",
    "3": "๐Ÿฅ‰"
}
def rank_models(df, column='Overall', rank_name='Rank'):
    """Ranks the models based on the specific score."""
    df = df.sort_values(by=column, ascending=False).reset_index(drop=True)
    df[rank_name] = df[column].rank(method='min', ascending=False).astype(int).astype(str).map(lambda x: medal_map.get(x, x))
    return df

def get_df(rank_column='Overall'):
    """Generates a DataFrame from the loaded data."""
    all_data = load_data()
    rows = [generate_model_row(data) for data in all_data]
    df = pd.DataFrame(rows)
    df[MODEL_SIZE_COL_NAME] = df[MODEL_SIZE_COL_NAME].apply(process_model_size)
    df['Date'] = df['Date'].apply(print_time)
    df = create_hyperlinked_names(df)
    df = rank_models(df, column=rank_column)
    return df

def refresh_data():
    df = get_df()
    return df[COLUMN_NAMES]

def search_and_filter_models(df, query, min_size, max_size):
    filtered_df = df.copy()
    
    if query:
        filtered_df = filtered_df[filtered_df['Models'].str.contains(query, case=False, na=False)]

    size_mask = filtered_df[MODEL_SIZE_COL_NAME].apply(lambda x: 
        (min_size <= 1000.0 <= max_size) if x == 'unknown' 
        else (min_size <= x <= max_size))
    
    filtered_df = filtered_df[size_mask]
    
    return filtered_df[COLUMN_NAMES]

def save_ranking_summary(df, name, save_now=True, dir='rankings'):
    csv_path, json_path = os.path.join(dir, f'{name}.csv'), os.path.join(dir, f'{name}.jsonl')
    if save_now:
        df.to_csv(csv_path, index=False)
        df.to_json(json_path, orient='records', lines=True)
    return csv_path, json_path

def download_ranking(df, name, format='csv', dir='rankings'):
    csv_path, json_path = save_ranking_summary(df, name, save_now=False, dir=dir)
    return csv_path if format == 'csv' else json_path