loodvanniekerkginkgo commited on
Commit
10e69e7
·
1 Parent(s): 7498e81

Leaderboard updates, no more anonymous (tracking via huggingface

Browse files
Files changed (5) hide show
  1. about.py +44 -2
  2. app.py +17 -31
  3. constants.py +3 -1
  4. submit.py +47 -31
  5. utils.py +9 -5
about.py CHANGED
@@ -22,9 +22,51 @@ TODO
22
  We'd like to add some more existing models to the leaderboard. Some examples of models we'd like to add:
23
  - TODO
24
 
25
- **FAQs**
26
 
27
  """
 
28
  FAQS = {
29
- "Example FAQ with dropdown": """Full answer to this question""",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
  }
 
22
  We'd like to add some more existing models to the leaderboard. Some examples of models we'd like to add:
23
  - TODO
24
 
25
+ ### FAQs
26
 
27
  """
28
+ # Note(Lood): Let's track these FAQs in the main Google Doc and have that remain the source of truth.
29
  FAQS = {
30
+ "Is there a fee to enter?": "No. Participation is free of charge.",
31
+ "Who can participate?": "Anyone. We encourage academic labs, individuals, and especially industry teams who use developability models in production.",
32
+ "Where can I find more information about the methods used to generate the data?": (
33
+ "Our [PROPHET-Ab preprint](https://www.biorxiv.org/content/10.1101/2025.05.01.651684v1) described in detail the methods used to generate the training dataset. "
34
+ "Note: These assays may differ from previously published methods, and these correlations between literature data and experimental data are also described in the preprint. "
35
+ "These same methods are used to generate the heldout test data."
36
+ ),
37
+ "How were the heldout sequences designed?": (
38
+ "We sampled 80 paired antibody sequences from [OAS](https://opig.stats.ox.ac.uk/webapps/oas/). We tried to represent the range of germline variants, sequence identities to germline, and CDR3 lengths. "
39
+ "The sequences in the dataset are quite diverse as measured by pairwise sequence identity."
40
+ ),
41
+ "Do I need to design new proteins?": (
42
+ "No. This is just a predictive competition, which will be judged according to the correlation between predictions and experimental values. "
43
+ "There may be a generative round in the future."
44
+ ),
45
+ "Can I participate anonymously?": (
46
+ "Yes! Please create an anonymous Hugging Face account so that we can uniquely associate submissions. "
47
+ "Note that top participants will be contacted to identify themselves at the end of the tournament."
48
+ ),
49
+ "How is intellectual property handled?": (
50
+ "Participants retain IP rights to the methods they use and develop during the tournament. Read more details in our terms here [link]."
51
+ ),
52
+ "Do I need to submit my code / methods in order to participate?": (
53
+ "No, there are no requirements to submit code / methods and submitted predictions remain private."
54
+ "We also have an optional field for including a short model description. "
55
+ "Top performing participants will be requested to identify themselves at the end of the tournament. "
56
+ "There will be one prize for the best open-source model, which will require code / methods to be available."
57
+ ),
58
+ "How are winners determined?": (
59
+ "There will be 6 prizes (one for each of the assay properties plus an “open-source” prize). "
60
+ "For the property-specific prizes, winners will be determined by the submission with the highest Spearman rank correlation coefficient on the private holdout set. "
61
+ "For the “open-source prize”, this will be determined by the highest average Spearman across all properties. "
62
+ "We reserve the right to award the open-source prize to a predictor with competitive results for a subset of properties (e.g. a top polyreactivity model)."
63
+ ),
64
+ "How does the open-source prize work?": (
65
+ "Participants who open-source their code and methods will be eligible for the open-source prize (as well as the other prizes)."
66
+ ),
67
+ "What do I need to submit?": (
68
+ "There is a '✉️ Submit' tab on the Hugging Face competition page to upload predictions for datasets - for each dataset participants need to submit a CSV containing a column for each property they would like to predict (e.g. called “HIC”), "
69
+ "and a row with the sequence matching the sequence in the input file. These predictions are then evaluated in the backend using the Spearman rank correlation between predictions and experimental values, "
70
+ "and these metrics are then added to the leaderboard. Predictions remain private and are not seen by other contestants."
71
+ ),
72
  }
app.py CHANGED
@@ -10,6 +10,7 @@ from constants import (
10
  ASSAY_EMOJIS,
11
  ASSAY_DESCRIPTION,
12
  EXAMPLE_FILE_DICT,
 
13
  )
14
  from about import ABOUT_TEXT, FAQS
15
  from submit import make_submission
@@ -20,36 +21,25 @@ def format_leaderboard_table(df_results: pd.DataFrame, assay: str | None = None)
20
  # Having a submission time column, and a user column where the username is clickable (this is a pro for usability but con for anonymity)
21
  # full_df.rename(columns={'submission_time': 'submission time', 'problem_type': 'problem type'}, inplace=True)
22
  # to_show['user'] = to_show['user'].apply(lambda x: make_user_clickable(x)).astype(str)
23
- column_order = ["model", "property", "spearman", "spearman_cross_val"]
24
  df = df_results.query("assay.isin(@ASSAY_RENAME.keys())").copy()
25
  if assay is not None:
26
  df = df[df["assay"] == assay]
27
- df = df[column_order]
28
  return df.sort_values(by="spearman", ascending=False)
29
 
30
 
31
- # Cache the results to avoid multiple downloads
32
- _cached_results = None
33
-
34
-
35
- def get_cached_results():
36
- global _cached_results
37
- if _cached_results is None:
38
- _cached_results = fetch_hf_results()
39
- return _cached_results
40
-
41
-
42
  def get_leaderboard_object(assay: str | None = None):
43
- filter_columns = ["model"]
44
  if assay is None:
45
  filter_columns.append("property")
46
  # TODO how to sort filter columns alphabetically?
 
47
  Leaderboard(
48
  # TODO(Lood) check that this actually refreshes using the function
49
- value=format_leaderboard_table(df_results=get_cached_results(), assay=assay),
50
  datatype=["str", "str", "str", "number"],
51
- select_columns=["model", "property", "spearman", "spearman_cross_val"],
52
- search_columns=["model"],
53
  filter_columns=filter_columns,
54
  every=60,
55
  render=True,
@@ -57,6 +47,7 @@ def get_leaderboard_object(assay: str | None = None):
57
 
58
 
59
  with gr.Blocks() as demo:
 
60
  gr.Markdown("""
61
  ## Welcome to the Ginkgo Antibody Developability Benchmark!
62
 
@@ -75,9 +66,11 @@ with gr.Blocks() as demo:
75
  width="50vw", # 50% of the "viewport width"
76
  )
77
  gr.Markdown(ABOUT_TEXT)
78
- for question, answer in FAQS.items():
79
- with gr.Accordion(question):
80
- gr.Markdown(answer)
 
 
81
 
82
  # Procedurally make these 5 tabs
83
  for assay in ASSAY_LIST:
@@ -100,7 +93,7 @@ with gr.Blocks() as demo:
100
  # Antibody Developability Submission
101
  Upload a CSV to get a score!
102
 
103
- Please use your Hugging Face account name to submit your model - we use this to track separate submissions, but only Hugging Face/Ginkgo will see these usernames (unless you choose to make them public).
104
  Your submission will be evaluated and added to the leaderboard.
105
  """
106
  )
@@ -114,7 +107,7 @@ with gr.Blocks() as demo:
114
  with gr.Column():
115
  username_input = gr.Textbox(
116
  label="Username",
117
- placeholder="Enter your Hugging Face username or leave blank for anonymous submissions",
118
  info="This will be used to track your submissions, and to update your results if you submit again.",
119
  )
120
  model_name_input = gr.Textbox(
@@ -123,7 +116,7 @@ with gr.Blocks() as demo:
123
  info="This will be displayed on the leaderboard.",
124
  )
125
  model_description_input = gr.Textbox(
126
- label="Model Description",
127
  placeholder="Brief description of your model and approach",
128
  info="Describe your model, training data, or methodology.",
129
  lines=3,
@@ -141,13 +134,6 @@ with gr.Blocks() as demo:
141
  )
142
  submission_file = gr.File(label="Submission CSV")
143
 
144
- # If username is empty, set to anonymous submission
145
- username_input.change(
146
- fn=lambda x: x if x and x.strip() else None,
147
- inputs=username_input,
148
- outputs=user_state,
149
- )
150
-
151
  def update_submission_type_and_file(submission_type):
152
  """
153
  Based on the submission type selected in the dropdown,
@@ -203,7 +189,7 @@ with gr.Blocks() as demo:
203
  gr.Markdown(
204
  """
205
  <div style="text-align: center; font-size: 14px; color: gray; margin-top: 2em;">
206
- 📬 For questions or feedback, contact <a href="mailto:datapoints@ginkgobioworks.com">datapoints@ginkgobioworks.com</a> or visit the Community tab at the top of this page.
207
  </div>
208
  """,
209
  elem_id="contact-footer",
 
10
  ASSAY_EMOJIS,
11
  ASSAY_DESCRIPTION,
12
  EXAMPLE_FILE_DICT,
13
+ LEADERBOARD_DISPLAY_COLUMNS,
14
  )
15
  from about import ABOUT_TEXT, FAQS
16
  from submit import make_submission
 
21
  # Having a submission time column, and a user column where the username is clickable (this is a pro for usability but con for anonymity)
22
  # full_df.rename(columns={'submission_time': 'submission time', 'problem_type': 'problem type'}, inplace=True)
23
  # to_show['user'] = to_show['user'].apply(lambda x: make_user_clickable(x)).astype(str)
 
24
  df = df_results.query("assay.isin(@ASSAY_RENAME.keys())").copy()
25
  if assay is not None:
26
  df = df[df["assay"] == assay]
27
+ df = df[LEADERBOARD_DISPLAY_COLUMNS]
28
  return df.sort_values(by="spearman", ascending=False)
29
 
30
 
 
 
 
 
 
 
 
 
 
 
 
31
  def get_leaderboard_object(assay: str | None = None):
32
+ filter_columns = ["dataset"]
33
  if assay is None:
34
  filter_columns.append("property")
35
  # TODO how to sort filter columns alphabetically?
36
+ # Bug: Can't leave search_columns empty because then it says "Column None not found in headers"
37
  Leaderboard(
38
  # TODO(Lood) check that this actually refreshes using the function
39
+ value=format_leaderboard_table(df_results=fetch_hf_results(), assay=assay),
40
  datatype=["str", "str", "str", "number"],
41
+ select_columns=["model", "property", "spearman", "dataset"],
42
+ search_columns=["model"], # Note(Lood): Would be nice to make this clear it's searching on model name
43
  filter_columns=filter_columns,
44
  every=60,
45
  render=True,
 
47
 
48
 
49
  with gr.Blocks() as demo:
50
+ # TODO: Add Ginkgo logo here on the top right
51
  gr.Markdown("""
52
  ## Welcome to the Ginkgo Antibody Developability Benchmark!
53
 
 
66
  width="50vw", # 50% of the "viewport width"
67
  )
68
  gr.Markdown(ABOUT_TEXT)
69
+ for i, (question, answer) in enumerate(FAQS.items()):
70
+ # Would love to make questions bold but accordion doesn't support it
71
+ question = f"{i+1}. {question}"
72
+ with gr.Accordion(question, open=False):
73
+ gr.Markdown(f"*{answer}*") # Italics for answers
74
 
75
  # Procedurally make these 5 tabs
76
  for assay in ASSAY_LIST:
 
93
  # Antibody Developability Submission
94
  Upload a CSV to get a score!
95
 
96
+ Please use your Hugging Face account name to submit your model - we use this to track separate submissions, and if you would like to remain anonymous please set up an anonymous huggingface account.
97
  Your submission will be evaluated and added to the leaderboard.
98
  """
99
  )
 
107
  with gr.Column():
108
  username_input = gr.Textbox(
109
  label="Username",
110
+ placeholder="Enter your Hugging Face username",
111
  info="This will be used to track your submissions, and to update your results if you submit again.",
112
  )
113
  model_name_input = gr.Textbox(
 
116
  info="This will be displayed on the leaderboard.",
117
  )
118
  model_description_input = gr.Textbox(
119
+ label="Model Description (optional)",
120
  placeholder="Brief description of your model and approach",
121
  info="Describe your model, training data, or methodology.",
122
  lines=3,
 
134
  )
135
  submission_file = gr.File(label="Submission CSV")
136
 
 
 
 
 
 
 
 
137
  def update_submission_type_and_file(submission_type):
138
  """
139
  Based on the submission type selected in the dropdown,
 
189
  gr.Markdown(
190
  """
191
  <div style="text-align: center; font-size: 14px; color: gray; margin-top: 2em;">
192
+ 📬 For questions or feedback, contact <a href="mailto:antibodycompetition@ginkgobioworks.com">antibodycompetition@ginkgobioworks.com</a> or visit the Community tab at the top of this page.
193
  </div>
194
  """,
195
  elem_id="contact-footer",
constants.py CHANGED
@@ -59,4 +59,6 @@ ORGANIZATION = "ginkgo-datapoints"
59
  SUBMISSIONS_REPO = f"{ORGANIZATION}/abdev-bench-submissions"
60
  RESULTS_REPO = f"{ORGANIZATION}/abdev-bench-results"
61
 
62
- ANONYMOUS_SUBMISSION_USERNAME = "anonymoussubmissions"
 
 
 
59
  SUBMISSIONS_REPO = f"{ORGANIZATION}/abdev-bench-submissions"
60
  RESULTS_REPO = f"{ORGANIZATION}/abdev-bench-results"
61
 
62
+ # Leaderboard dataframes
63
+ LEADERBOARD_RESULTS_COLUMNS = ["model", "assay", "spearman", "dataset", "user"] # The columns expected from the results dataset
64
+ LEADERBOARD_DISPLAY_COLUMNS = ["model", "property", "spearman", "dataset", "user"] # After changing assay to property (pretty formatting)
submit.py CHANGED
@@ -3,48 +3,26 @@ import tempfile
3
  from typing import BinaryIO
4
  import json
5
 
 
6
  import gradio as gr
7
  from datetime import datetime
8
  import uuid
9
 
10
- from constants import API, SUBMISSIONS_REPO, ANONYMOUS_SUBMISSION_USERNAME
11
  from validation import validate_csv_file, validate_username
12
 
13
 
14
- def make_submission(
15
- submitted_file: BinaryIO,
16
  user_state,
17
- submission_type: str = "GDPa1",
18
- model_name: str = "",
19
- model_description: str = "",
20
  ):
21
- user_state = user_state or ANONYMOUS_SUBMISSION_USERNAME
22
- validate_username(user_state)
23
-
24
- model_name = model_name.strip()
25
- model_description = model_description.strip()
26
-
27
- if not model_name:
28
- raise gr.Error("Please provide a model name.")
29
- if not model_description:
30
- raise gr.Error("Please provide a model description.")
31
- if submitted_file is None:
32
- raise gr.Error("Please upload a CSV file before submitting.")
33
-
34
- file_path = submitted_file.name
35
-
36
- if not file_path:
37
- raise gr.Error("Uploaded file object does not have a valid file path.")
38
-
39
- path_obj = Path(file_path)
40
-
41
- if path_obj.suffix.lower() != ".csv":
42
- raise gr.Error("File must be a CSV file. Please upload a .csv file.")
43
-
44
  timestamp = datetime.utcnow().isoformat()
45
  submission_id = str(uuid.uuid4())
46
 
47
- with path_obj.open("rb") as f_in:
48
  file_content = f_in.read().decode("utf-8")
49
 
50
  validate_csv_file(file_content, submission_type)
@@ -58,7 +36,7 @@ def make_submission(
58
  "submission_time": timestamp,
59
  "evaluated": False,
60
  "user": user_state,
61
- "model": model_name,
62
  "model_description": model_description,
63
  "csv_content": file_content,
64
  "dataset": submission_type,
@@ -77,4 +55,42 @@ def make_submission(
77
  )
78
  Path(tmp_name).unlink()
79
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80
  return "✅ Your submission has been received! Sit tight and your scores will appear on the leaderboard shortly."
 
3
  from typing import BinaryIO
4
  import json
5
 
6
+ from click import pass_obj
7
  import gradio as gr
8
  from datetime import datetime
9
  import uuid
10
 
11
+ from constants import API, SUBMISSIONS_REPO
12
  from validation import validate_csv_file, validate_username
13
 
14
 
15
+ def upload_submission(
16
+ file_path: Path,
17
  user_state,
18
+ submission_type: str,
19
+ model_name: str,
20
+ model_description: str,
21
  ):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
  timestamp = datetime.utcnow().isoformat()
23
  submission_id = str(uuid.uuid4())
24
 
25
+ with file_path.open("rb") as f_in:
26
  file_content = f_in.read().decode("utf-8")
27
 
28
  validate_csv_file(file_content, submission_type)
 
36
  "submission_time": timestamp,
37
  "evaluated": False,
38
  "user": user_state,
39
+ "model_name": model_name,
40
  "model_description": model_description,
41
  "csv_content": file_content,
42
  "dataset": submission_type,
 
55
  )
56
  Path(tmp_name).unlink()
57
 
58
+ def make_submission(
59
+ submitted_file: BinaryIO,
60
+ user_state,
61
+ submission_type: str = "GDPa1",
62
+ model_name: str = "",
63
+ model_description: str = "",
64
+ ):
65
+ user_state = user_state
66
+ validate_username(user_state)
67
+
68
+ model_name = model_name.strip()
69
+ model_description = model_description.strip()
70
+
71
+ if not model_name:
72
+ raise gr.Error("Please provide a model name.")
73
+ if not model_description:
74
+ raise gr.Error("Please provide a model description.")
75
+ if submitted_file is None:
76
+ raise gr.Error("Please upload a CSV file before submitting.")
77
+
78
+ file_path = submitted_file.name
79
+
80
+ if not file_path:
81
+ raise gr.Error("Uploaded file object does not have a valid file path.")
82
+
83
+ path_obj = Path(file_path)
84
+
85
+ if path_obj.suffix.lower() != ".csv":
86
+ raise gr.Error("File must be a CSV file. Please upload a .csv file.")
87
+
88
+ upload_submission(
89
+ file_path=path_obj,
90
+ user_state=user_state,
91
+ submission_type=submission_type,
92
+ model_name=model_name,
93
+ model_description=model_description,
94
+ )
95
+
96
  return "✅ Your submission has been received! Sit tight and your scores will appear on the leaderboard shortly."
utils.py CHANGED
@@ -7,7 +7,9 @@ import pandas as pd
7
  from datasets import load_dataset
8
  from huggingface_hub import hf_hub_download
9
 
10
- from constants import API, SUBMISSIONS_REPO, RESULTS_REPO, ASSAY_RENAME
 
 
11
 
12
  # def make_user_clickable(name):
13
  # link =f'https://huggingface.co/{name}'
@@ -23,10 +25,12 @@ def show_output_box(message):
23
 
24
 
25
  def fetch_hf_results():
26
- ds = load_dataset(
27
- RESULTS_REPO, split="no_low_spearman", download_mode="force_redownload"
28
- )
29
- df = pd.DataFrame(ds).drop_duplicates(subset=["model", "assay"])
 
 
30
  df["property"] = df["assay"].map(ASSAY_RENAME)
31
  print(df.head())
32
  return df
 
7
  from datasets import load_dataset
8
  from huggingface_hub import hf_hub_download
9
 
10
+ from constants import API, SUBMISSIONS_REPO, RESULTS_REPO, ASSAY_RENAME, LEADERBOARD_RESULTS_COLUMNS
11
+
12
+ pd.set_option('display.max_columns', None)
13
 
14
  # def make_user_clickable(name):
15
  # link =f'https://huggingface.co/{name}'
 
25
 
26
 
27
  def fetch_hf_results():
28
+ # Should cache by default if not using force_redownload
29
+ df = load_dataset(
30
+ RESULTS_REPO, data_files="auto_submissions/metrics_all.csv",
31
+ )["train"].to_pandas()
32
+ assert all(col in df.columns for col in LEADERBOARD_RESULTS_COLUMNS), f"Expected columns {LEADERBOARD_RESULTS_COLUMNS} not found in {df.columns}. Missing columns: {set(LEADERBOARD_COLUMNS) - set(df.columns)}"
33
+ df = df.drop_duplicates(subset=["model", "assay"])
34
  df["property"] = df["assay"].map(ASSAY_RENAME)
35
  print(df.head())
36
  return df