Deploy ESM-2, MoLFormer, and affinity ONNX application
Browse files
app.py
CHANGED
|
@@ -2,6 +2,8 @@ from __future__ import annotations
|
|
| 2 |
|
| 3 |
import html
|
| 4 |
import os
|
|
|
|
|
|
|
| 5 |
from functools import lru_cache
|
| 6 |
from pathlib import Path
|
| 7 |
|
|
@@ -42,6 +44,41 @@ ATOM_COLORS = {
|
|
| 42 |
"BR": "#92400e",
|
| 43 |
"I": "#7e22ce",
|
| 44 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 45 |
|
| 46 |
|
| 47 |
def validate_protein(sequence: str) -> str:
|
|
@@ -131,6 +168,11 @@ def predict(sequence: str, smiles: str):
|
|
| 131 |
)
|
| 132 |
|
| 133 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 134 |
def prediction_card(prediction: float) -> str:
|
| 135 |
return (
|
| 136 |
"<div class='score-card'>"
|
|
@@ -275,11 +317,9 @@ def molecule_3d(smiles: str) -> Image.Image:
|
|
| 275 |
return image
|
| 276 |
|
| 277 |
|
| 278 |
-
def
|
| 279 |
-
if pdb_file is None:
|
| 280 |
-
raise gr.Error("Upload a PDB file. A sequence alone has no 3D coordinates.")
|
| 281 |
coordinates = []
|
| 282 |
-
for line in
|
| 283 |
if line.startswith(("ATOM ", "HETATM")) and line[12:16].strip() == "CA":
|
| 284 |
try:
|
| 285 |
coordinates.append(
|
|
@@ -315,6 +355,30 @@ def protein_3d(pdb_file) -> Image.Image:
|
|
| 315 |
return image
|
| 316 |
|
| 317 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 318 |
EXAMPLE_PROTEIN = "MAVMKNYLLPILVLFLAYYYYSTNEEFRPEMLQGKKVIVTGASKGIGREMAYHLSKMGAHVVLTARSEEGLQK"
|
| 319 |
EXAMPLE_SMILES = "C1CC1(C2=CC=C(C=C2)F)C(=O)N3CC4CC4(C3)C5=CNC6=C5C=CC=N6"
|
| 320 |
|
|
@@ -367,6 +431,11 @@ with gr.Blocks(title="Protein-Compound Affinity Explorer", css=CSS) as demo:
|
|
| 367 |
value=EXAMPLE_SMILES,
|
| 368 |
)
|
| 369 |
run = gr.Button("Predict affinity and render inputs", variant="primary", size="lg")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 370 |
|
| 371 |
with gr.Column(visible=False) as results:
|
| 372 |
score = gr.HTML()
|
|
@@ -386,14 +455,35 @@ with gr.Blocks(title="Protein-Compound Affinity Explorer", css=CSS) as demo:
|
|
| 386 |
[score, image, view_3d, molecule_data, protein_view, protein_data],
|
| 387 |
).then(lambda: gr.update(visible=True), outputs=results)
|
| 388 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 389 |
with gr.Accordion("Optional protein 3D viewer", open=False):
|
| 390 |
gr.Markdown(
|
| 391 |
"A protein sequence does not contain 3D coordinates. Upload a PDB file "
|
| 392 |
-
"
|
| 393 |
)
|
| 394 |
pdb = gr.File(label="PDB file", file_types=[".pdb"], type="filepath")
|
| 395 |
pdb_view = gr.Image(label="Protein backbone projection", height=520)
|
| 396 |
gr.Button("Render PDB structure").click(protein_3d, pdb, pdb_view)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 397 |
gr.Markdown(
|
| 398 |
html.escape(
|
| 399 |
"Research demonstration only. Predictions are not medical or drug-development advice."
|
|
|
|
| 2 |
|
| 3 |
import html
|
| 4 |
import os
|
| 5 |
+
import urllib.error
|
| 6 |
+
import urllib.request
|
| 7 |
from functools import lru_cache
|
| 8 |
from pathlib import Path
|
| 9 |
|
|
|
|
| 44 |
"BR": "#92400e",
|
| 45 |
"I": "#7e22ce",
|
| 46 |
}
|
| 47 |
+
PAIR_EXAMPLES = [
|
| 48 |
+
{
|
| 49 |
+
"name": "Ubiquitin + caffeine",
|
| 50 |
+
"protein": (
|
| 51 |
+
"MQIFVKTLTGKTITLEVEPSDTIENVKAKIQDKEGIPPDQQRLIFAGKQLEDGRTL"
|
| 52 |
+
"SDYNIQKESTLHLVLRLRGG"
|
| 53 |
+
),
|
| 54 |
+
"smiles": "CN1C=NC2=C1C(=O)N(C(=O)N2C)C",
|
| 55 |
+
},
|
| 56 |
+
{
|
| 57 |
+
"name": "Lysozyme + ibuprofen",
|
| 58 |
+
"protein": (
|
| 59 |
+
"KVFGRCELAAAMKRHGLDNYRGYSLGNWVCAAKFESNFNTQATNRNTDGSTDYGIL"
|
| 60 |
+
"QINSRWWCNDGRTPGSRNLCNIPCSALLSSDITASVNCAKKIVSDGNGMNAWVAWR"
|
| 61 |
+
"NRCKGTDVQAWIRGCRL"
|
| 62 |
+
),
|
| 63 |
+
"smiles": "CC(C)CC1=CC=C(C=C1)C(C)C(=O)O",
|
| 64 |
+
},
|
| 65 |
+
{
|
| 66 |
+
"name": "Insulin B chain + aspirin",
|
| 67 |
+
"protein": "FVNQHLCGSHLVEALYLVCGERGFFYTPKT",
|
| 68 |
+
"smiles": "CC(=O)OC1=CC=CC=C1C(=O)O",
|
| 69 |
+
},
|
| 70 |
+
{
|
| 71 |
+
"name": "Villin headpiece + acetaminophen",
|
| 72 |
+
"protein": "LSDEDFKAVFGMTRSAFANLPLWKQQNLKKEKGLF",
|
| 73 |
+
"smiles": "CC(=O)NC1=CC=C(C=C1)O",
|
| 74 |
+
},
|
| 75 |
+
]
|
| 76 |
+
PDB_EXAMPLES = [
|
| 77 |
+
("1UBQ", "Ubiquitin"),
|
| 78 |
+
("1LYZ", "Lysozyme"),
|
| 79 |
+
("1CRN", "Crambin"),
|
| 80 |
+
("1L2Y", "Trp-cage"),
|
| 81 |
+
]
|
| 82 |
|
| 83 |
|
| 84 |
def validate_protein(sequence: str) -> str:
|
|
|
|
| 168 |
)
|
| 169 |
|
| 170 |
|
| 171 |
+
def pair_example(index: int) -> tuple[str, str]:
|
| 172 |
+
example = PAIR_EXAMPLES[index]
|
| 173 |
+
return example["protein"], example["smiles"]
|
| 174 |
+
|
| 175 |
+
|
| 176 |
def prediction_card(prediction: float) -> str:
|
| 177 |
return (
|
| 178 |
"<div class='score-card'>"
|
|
|
|
| 317 |
return image
|
| 318 |
|
| 319 |
|
| 320 |
+
def protein_backbone_image(pdb_text: str) -> Image.Image:
|
|
|
|
|
|
|
| 321 |
coordinates = []
|
| 322 |
+
for line in pdb_text.splitlines():
|
| 323 |
if line.startswith(("ATOM ", "HETATM")) and line[12:16].strip() == "CA":
|
| 324 |
try:
|
| 325 |
coordinates.append(
|
|
|
|
| 355 |
return image
|
| 356 |
|
| 357 |
|
| 358 |
+
def protein_3d(pdb_file) -> Image.Image:
|
| 359 |
+
if pdb_file is None:
|
| 360 |
+
raise gr.Error("Upload a PDB file. A sequence alone has no 3D coordinates.")
|
| 361 |
+
pdb_text = Path(pdb_file).read_text(encoding="utf-8", errors="replace")
|
| 362 |
+
return protein_backbone_image(pdb_text)
|
| 363 |
+
|
| 364 |
+
|
| 365 |
+
@lru_cache(maxsize=len(PDB_EXAMPLES))
|
| 366 |
+
def load_sample_pdb(pdb_id: str) -> Image.Image:
|
| 367 |
+
valid_ids = {identifier for identifier, _ in PDB_EXAMPLES}
|
| 368 |
+
if pdb_id not in valid_ids:
|
| 369 |
+
raise gr.Error("Unknown sample PDB identifier.")
|
| 370 |
+
request = urllib.request.Request(
|
| 371 |
+
f"https://files.rcsb.org/download/{pdb_id}.pdb",
|
| 372 |
+
headers={"User-Agent": "protein-compound-affinity-space/1.0"},
|
| 373 |
+
)
|
| 374 |
+
try:
|
| 375 |
+
with urllib.request.urlopen(request, timeout=30) as response:
|
| 376 |
+
pdb_text = response.read().decode("utf-8", errors="replace")
|
| 377 |
+
except (urllib.error.URLError, TimeoutError) as error:
|
| 378 |
+
raise gr.Error(f"Could not download {pdb_id} from RCSB PDB: {error}") from error
|
| 379 |
+
return protein_backbone_image(pdb_text)
|
| 380 |
+
|
| 381 |
+
|
| 382 |
EXAMPLE_PROTEIN = "MAVMKNYLLPILVLFLAYYYYSTNEEFRPEMLQGKKVIVTGASKGIGREMAYHLSKMGAHVVLTARSEEGLQK"
|
| 383 |
EXAMPLE_SMILES = "C1CC1(C2=CC=C(C=C2)F)C(=O)N3CC4CC4(C3)C5=CNC6=C5C=CC=N6"
|
| 384 |
|
|
|
|
| 431 |
value=EXAMPLE_SMILES,
|
| 432 |
)
|
| 433 |
run = gr.Button("Predict affinity and render inputs", variant="primary", size="lg")
|
| 434 |
+
gr.Markdown("#### One-click examples")
|
| 435 |
+
with gr.Row():
|
| 436 |
+
example_buttons = [
|
| 437 |
+
gr.Button(example["name"], size="sm") for example in PAIR_EXAMPLES
|
| 438 |
+
]
|
| 439 |
|
| 440 |
with gr.Column(visible=False) as results:
|
| 441 |
score = gr.HTML()
|
|
|
|
| 455 |
[score, image, view_3d, molecule_data, protein_view, protein_data],
|
| 456 |
).then(lambda: gr.update(visible=True), outputs=results)
|
| 457 |
|
| 458 |
+
for index, button in enumerate(example_buttons):
|
| 459 |
+
button.click(
|
| 460 |
+
lambda selected=index: pair_example(selected),
|
| 461 |
+
outputs=[protein, smiles],
|
| 462 |
+
).then(
|
| 463 |
+
predict,
|
| 464 |
+
[protein, smiles],
|
| 465 |
+
[score, image, view_3d, molecule_data, protein_view, protein_data],
|
| 466 |
+
).then(lambda: gr.update(visible=True), outputs=results)
|
| 467 |
+
|
| 468 |
with gr.Accordion("Optional protein 3D viewer", open=False):
|
| 469 |
gr.Markdown(
|
| 470 |
"A protein sequence does not contain 3D coordinates. Upload a PDB file "
|
| 471 |
+
"or select one of the four RCSB PDB examples."
|
| 472 |
)
|
| 473 |
pdb = gr.File(label="PDB file", file_types=[".pdb"], type="filepath")
|
| 474 |
pdb_view = gr.Image(label="Protein backbone projection", height=520)
|
| 475 |
gr.Button("Render PDB structure").click(protein_3d, pdb, pdb_view)
|
| 476 |
+
gr.Markdown("#### Sample PDB structures")
|
| 477 |
+
with gr.Row():
|
| 478 |
+
pdb_buttons = [
|
| 479 |
+
gr.Button(f"{pdb_id} · {name}", size="sm")
|
| 480 |
+
for pdb_id, name in PDB_EXAMPLES
|
| 481 |
+
]
|
| 482 |
+
for (pdb_id, _), button in zip(PDB_EXAMPLES, pdb_buttons):
|
| 483 |
+
button.click(
|
| 484 |
+
lambda selected=pdb_id: load_sample_pdb(selected),
|
| 485 |
+
outputs=pdb_view,
|
| 486 |
+
)
|
| 487 |
gr.Markdown(
|
| 488 |
html.escape(
|
| 489 |
"Research demonstration only. Predictions are not medical or drug-development advice."
|