Spaces:
Sleeping
Sleeping
first pass
Browse files- app.py +245 -0
- requirements.txt +4 -0
app.py
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import datetime as dt
|
| 3 |
+
from typing import List
|
| 4 |
+
|
| 5 |
+
import gradio as gr
|
| 6 |
+
import pandas as pd
|
| 7 |
+
from datasets import load_dataset, Dataset
|
| 8 |
+
|
| 9 |
+
repo = 'hugging-science/m-boltz-submissions'
|
| 10 |
+
CONFIGS = {'antibody':'Antibody–Antigen', 'ligand':'Allosteric–Orthosteric', 'final':'final'}
|
| 11 |
+
|
| 12 |
+
# Column schemas per tab (used to create empty frames and to order columns)
|
| 13 |
+
COLUMNS = {
|
| 14 |
+
"antibody": [
|
| 15 |
+
"timestamp",
|
| 16 |
+
"user",
|
| 17 |
+
"model_name",
|
| 18 |
+
"antibody_id",
|
| 19 |
+
"antigen_id",
|
| 20 |
+
"predicted_affinity",
|
| 21 |
+
"notes",
|
| 22 |
+
],
|
| 23 |
+
"ligand": [
|
| 24 |
+
"timestamp",
|
| 25 |
+
"user",
|
| 26 |
+
"model_name",
|
| 27 |
+
"protein_id",
|
| 28 |
+
"ligand_type",
|
| 29 |
+
"predicted_kd",
|
| 30 |
+
"notes",
|
| 31 |
+
],
|
| 32 |
+
"final": [
|
| 33 |
+
"timestamp",
|
| 34 |
+
"team_name",
|
| 35 |
+
"archive_url",
|
| 36 |
+
"results_summary",
|
| 37 |
+
"contact_email",
|
| 38 |
+
],
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
# You need a write‑enabled token available to the Space (Settings → Repository secrets)
|
| 42 |
+
# with name HF_TOKEN. This function raises a helpful error if it is missing.
|
| 43 |
+
def _hf_token() -> str:
|
| 44 |
+
token = os.getenv("HF_TOKEN")
|
| 45 |
+
if not token:
|
| 46 |
+
raise RuntimeError(
|
| 47 |
+
"Missing HF_TOKEN. Add a write-enabled token in your Space secrets."
|
| 48 |
+
)
|
| 49 |
+
return token
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
def _empty_df(columns: List[str]) -> pd.DataFrame:
|
| 53 |
+
return pd.DataFrame(columns=columns)
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
def load_df(config: str, columns: List[str]) -> pd.DataFrame:
|
| 57 |
+
"""Load the 'train' split from a Hub dataset into a pandas DataFrame.
|
| 58 |
+
Returns an empty DataFrame with the expected columns if the dataset doesn't exist yet.
|
| 59 |
+
"""
|
| 60 |
+
try:
|
| 61 |
+
ds = load_dataset(repo, config, split="train", token=_hf_token())
|
| 62 |
+
df = ds.to_pandas()
|
| 63 |
+
# Ensure all expected columns exist and in correct order
|
| 64 |
+
for c in columns:
|
| 65 |
+
if c not in df.columns:
|
| 66 |
+
df[c] = pd.NA
|
| 67 |
+
return df[columns]
|
| 68 |
+
except Exception:
|
| 69 |
+
# Fresh repo or first run: return empty with correct columns
|
| 70 |
+
return _empty_df(columns)
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
def push_df(config: str, df: pd.DataFrame) -> None:
|
| 74 |
+
"""Overwrite the dataset's 'train' split on the Hub with the provided DataFrame.
|
| 75 |
+
If the repo doesn't exist, this will create it under your account/org.
|
| 76 |
+
"""
|
| 77 |
+
# Convert to datasets.Dataset (drops pandas index)
|
| 78 |
+
ds = Dataset.from_pandas(df.reset_index(drop=True), preserve_index=False)
|
| 79 |
+
# Overwrite the dataset on the Hub. If it doesn't exist, it's created.
|
| 80 |
+
ds.push_to_hub(repo, config_name=config, token=_hf_token())
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
# --- Tab logic --------------------------------------------------------------
|
| 84 |
+
# Antibody–Antigen
|
| 85 |
+
|
| 86 |
+
def submit_antibody(user, model_name, antibody_id, antigen_id, predicted_affinity, notes):
|
| 87 |
+
config = CONFIGS["antibody"]
|
| 88 |
+
cols = COLUMNS["antibody"]
|
| 89 |
+
df = load_df(config, cols)
|
| 90 |
+
row = {
|
| 91 |
+
"timestamp": dt.datetime.now(dt.timezone.utc).isoformat(),
|
| 92 |
+
"user": user or "",
|
| 93 |
+
"model_name": model_name or "",
|
| 94 |
+
"antibody_id": antibody_id or "",
|
| 95 |
+
"antigen_id": antigen_id or "",
|
| 96 |
+
"predicted_affinity": float(predicted_affinity) if predicted_affinity is not None else None,
|
| 97 |
+
"notes": notes or "",
|
| 98 |
+
}
|
| 99 |
+
df = pd.concat([df, pd.DataFrame([row])], ignore_index=True)
|
| 100 |
+
push_df(config, df)
|
| 101 |
+
# Re-load to ensure what we show is exactly what's on the Hub
|
| 102 |
+
return load_df(config, cols)
|
| 103 |
+
|
| 104 |
+
|
| 105 |
+
def refresh_antibody():
|
| 106 |
+
return load_df(CONFIGS["antibody"], COLUMNS["antibody"])
|
| 107 |
+
|
| 108 |
+
|
| 109 |
+
# Allosteric–Orthosteric
|
| 110 |
+
|
| 111 |
+
def submit_ligand(user, model_name, protein_id, ligand_type, predicted_kd, notes):
|
| 112 |
+
config = CONFIGS["ligand"]
|
| 113 |
+
cols = COLUMNS["ligand"]
|
| 114 |
+
df = load_df(config, cols)
|
| 115 |
+
row = {
|
| 116 |
+
"timestamp": dt.datetime.now(dt.timezone.utc).isoformat(),
|
| 117 |
+
"user": user or "",
|
| 118 |
+
"model_name": model_name or "",
|
| 119 |
+
"protein_id": protein_id or "",
|
| 120 |
+
"ligand_type": ligand_type or "",
|
| 121 |
+
"predicted_kd": float(predicted_kd) if predicted_kd is not None else None,
|
| 122 |
+
"notes": notes or "",
|
| 123 |
+
}
|
| 124 |
+
df = pd.concat([df, pd.DataFrame([row])], ignore_index=True)
|
| 125 |
+
push_df(config, df)
|
| 126 |
+
return load_df(config, cols)
|
| 127 |
+
|
| 128 |
+
|
| 129 |
+
def refresh_ligand():
|
| 130 |
+
return load_df(CONFIGS["ligand"], COLUMNS["ligand"])
|
| 131 |
+
|
| 132 |
+
|
| 133 |
+
# Final Submission
|
| 134 |
+
|
| 135 |
+
def submit_final(team_name, archive_url, results_summary, contact_email):
|
| 136 |
+
config = CONFIGS["final"]
|
| 137 |
+
cols = COLUMNS["final"]
|
| 138 |
+
df = load_df(config, cols)
|
| 139 |
+
row = {
|
| 140 |
+
"timestamp": dt.datetime.now(dt.timezone.utc).isoformat(),
|
| 141 |
+
"team_name": team_name or "",
|
| 142 |
+
"archive_url": archive_url or "",
|
| 143 |
+
"results_summary": results_summary or "",
|
| 144 |
+
"contact_email": contact_email or "",
|
| 145 |
+
}
|
| 146 |
+
df = pd.concat([df, pd.DataFrame([row])], ignore_index=True)
|
| 147 |
+
push_df(config, df)
|
| 148 |
+
return load_df(config, cols)
|
| 149 |
+
|
| 150 |
+
|
| 151 |
+
def refresh_final():
|
| 152 |
+
return load_df(CONFIGS["final"], COLUMNS["final"])
|
| 153 |
+
|
| 154 |
+
|
| 155 |
+
# --- UI ---------------------------------------------------------------------
|
| 156 |
+
with gr.Blocks(title="Binding Challenges") as app:
|
| 157 |
+
gr.Markdown("# Binding Challenges\nMinimal demo: submit → write to HF dataset → re-hydrate DataFrame from the same dataset.")
|
| 158 |
+
|
| 159 |
+
with gr.Tab("Antibody–Antigen Binding Challenge"):
|
| 160 |
+
gr.Markdown("""
|
| 161 |
+
# Antibody-Antigen Binding Challenge
|
| 162 |
+
The goal of this challenge is to improve Boltz-2 accuracy for predicting the correct poses of a VHH binding to an antigen.\n
|
| 163 |
+
Accuracy will be measured through the Capri-Q docking assessment classification scores and the final winner will be determined based on the number of successful top-1 predictions on our *internal* test set. However, you are encouraged to submit results on the training set during the hack to see where you stack up.\n
|
| 164 |
+
A prediction is deemed successful if the Capri-Q classification is either “high”, “medium”, or “acceptable”.
|
| 165 |
+
If multiple entries reach the same number of successful predictions, ties are broken by looking at the number of predictions with “High” classification, then with “Medium” classification and finally with “Acceptable” classification.
|
| 166 |
+
If there is still a tie then, we will look at the mean RMSD across all successful predictions.
|
| 167 |
+
""")
|
| 168 |
+
with gr.Row():
|
| 169 |
+
aa_user = gr.Textbox(label="User / Team", placeholder="Your name or team")
|
| 170 |
+
aa_model = gr.Textbox(label="Model Name")
|
| 171 |
+
with gr.Row():
|
| 172 |
+
aa_antibody = gr.Textbox(label="Antibody ID")
|
| 173 |
+
aa_antigen = gr.Textbox(label="Antigen ID")
|
| 174 |
+
aa_aff = gr.Number(label="Predicted affinity (nM)")
|
| 175 |
+
aa_notes = gr.Textbox(label="Notes", lines=3)
|
| 176 |
+
with gr.Row():
|
| 177 |
+
aa_submit = gr.Button("Submit")
|
| 178 |
+
aa_refresh = gr.Button("Refresh table")
|
| 179 |
+
aa_df = gr.Dataframe(
|
| 180 |
+
value=load_df(CONFIGS["antibody"], COLUMNS["antibody"]),
|
| 181 |
+
label="Submissions (Antibody–Antigen)",
|
| 182 |
+
interactive=False,
|
| 183 |
+
wrap=True,
|
| 184 |
+
)
|
| 185 |
+
aa_submit.click(
|
| 186 |
+
submit_antibody,
|
| 187 |
+
inputs=[aa_user, aa_model, aa_antibody, aa_antigen, aa_aff, aa_notes],
|
| 188 |
+
outputs=aa_df,
|
| 189 |
+
)
|
| 190 |
+
aa_refresh.click(refresh_antibody, outputs=aa_df)
|
| 191 |
+
|
| 192 |
+
with gr.Tab("Allosteric–Orthosteric Ligand Binding Challenge"):
|
| 193 |
+
gr.Markdown("""
|
| 194 |
+
# Allosteric-Orthosteric Ligand Binding Challenge
|
| 195 |
+
The goal of this challenge is to improve Boltz-2 accuracy for predicting the binding poses of either allosteric or orthosteric ligands.\n
|
| 196 |
+
The winner will be determined by accuracy measured on our *internal* test set by calculating the RMSD between the top-1 prediction and the experimental pose. However, submit your intermediate results here to see where you stack up!
|
| 197 |
+
""")
|
| 198 |
+
with gr.Row():
|
| 199 |
+
li_user = gr.Textbox(label="User / Team")
|
| 200 |
+
li_model = gr.Textbox(label="Model Name")
|
| 201 |
+
with gr.Row():
|
| 202 |
+
li_protein = gr.Textbox(label="Protein ID")
|
| 203 |
+
li_type = gr.Radio(["allosteric", "orthosteric"], label="Ligand type")
|
| 204 |
+
li_kd = gr.Number(label="Predicted Kd (nM)")
|
| 205 |
+
li_notes = gr.Textbox(label="Notes", lines=3)
|
| 206 |
+
with gr.Row():
|
| 207 |
+
li_submit = gr.Button("Submit")
|
| 208 |
+
li_refresh = gr.Button("Refresh table")
|
| 209 |
+
li_df = gr.Dataframe(
|
| 210 |
+
value=load_df(CONFIGS["ligand"], COLUMNS["ligand"]),
|
| 211 |
+
label="Submissions (Ligand Binding)",
|
| 212 |
+
interactive=False,
|
| 213 |
+
wrap=True,
|
| 214 |
+
)
|
| 215 |
+
li_submit.click(
|
| 216 |
+
submit_ligand,
|
| 217 |
+
inputs=[li_user, li_model, li_protein, li_type, li_kd, li_notes],
|
| 218 |
+
outputs=li_df,
|
| 219 |
+
)
|
| 220 |
+
li_refresh.click(refresh_ligand, outputs=li_df)
|
| 221 |
+
|
| 222 |
+
with gr.Tab("3) Final Submission"):
|
| 223 |
+
fs_team = gr.Textbox(label="Team name")
|
| 224 |
+
fs_archive = gr.Textbox(label="Archive URL (e.g., model artifacts)")
|
| 225 |
+
fs_summary = gr.Textbox(label="Results summary", lines=4)
|
| 226 |
+
fs_email = gr.Textbox(label="Contact email")
|
| 227 |
+
with gr.Row():
|
| 228 |
+
fs_submit = gr.Button("Submit")
|
| 229 |
+
fs_refresh = gr.Button("Refresh table")
|
| 230 |
+
fs_df = gr.Dataframe(
|
| 231 |
+
value=load_df(CONFIGS["final"], COLUMNS["final"]),
|
| 232 |
+
label="Final submissions",
|
| 233 |
+
interactive=False,
|
| 234 |
+
wrap=True,
|
| 235 |
+
)
|
| 236 |
+
fs_submit.click(
|
| 237 |
+
submit_final,
|
| 238 |
+
inputs=[fs_team, fs_archive, fs_summary, fs_email],
|
| 239 |
+
outputs=fs_df,
|
| 240 |
+
)
|
| 241 |
+
fs_refresh.click(refresh_final, outputs=fs_df)
|
| 242 |
+
|
| 243 |
+
# For local dev: `python app.py`
|
| 244 |
+
if __name__ == "__main__":
|
| 245 |
+
app.launch()
|
requirements.txt
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
gradio>=4.44.0
|
| 2 |
+
pandas>=2.2.2
|
| 3 |
+
datasets>=3.0.0
|
| 4 |
+
huggingface_hub>=0.24.5
|