Spaces:
Running
Running
ZhaohanM commited on
Commit Β·
ba4f1b4
1
Parent(s): 01d9e59
Polish ExplainBind demo UI
Browse files- .gitignore +1 -0
- app.py +274 -50
.gitignore
CHANGED
|
@@ -2,5 +2,6 @@
|
|
| 2 |
.ipynb_checkpoints/
|
| 3 |
__pycache__/
|
| 4 |
*.pyc
|
|
|
|
| 5 |
bin/
|
| 6 |
*.log
|
|
|
|
| 2 |
.ipynb_checkpoints/
|
| 3 |
__pycache__/
|
| 4 |
*.pyc
|
| 5 |
+
.venv/
|
| 6 |
bin/
|
| 7 |
*.log
|
app.py
CHANGED
|
@@ -60,7 +60,7 @@ LOSCAZLO_B64 = _load_logo_b64(LOSCAZLO_LOGO)
|
|
| 60 |
# UI-visible names (Halogen bonding removed)
|
| 61 |
INTERACTION_NAMES = [
|
| 62 |
"Hydrogen bonding",
|
| 63 |
-
"Salt
|
| 64 |
"ΟβΟ Stacking",
|
| 65 |
"CationβΟ",
|
| 66 |
"Hydrophobic",
|
|
@@ -76,9 +76,26 @@ N_VISIBLE_SPEC = len(VISIBLE2UNDERLYING) # 6
|
|
| 76 |
# βββββ Helper utilities βββββββββββββββββββββββββββββββββββββββββββ
|
| 77 |
three2one = {k.upper(): v for k, v in IUPACData.protein_letters_3to1.items()}
|
| 78 |
three2one.update({"MSE": "M", "SEC": "C", "PYL": "K"})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 79 |
STANDARD_AA_SET = set("ACDEFGHIKLMNPQRSTVWY") # Uppercase FASTA amino acids
|
| 80 |
|
| 81 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 82 |
def simple_seq_from_structure(path: str) -> str:
|
| 83 |
"""Extract the longest chain and return standard 1-letter amino acid sequence."""
|
| 84 |
parser = MMCIFParser(QUIET=True) if path.endswith(".cif") else PDBParser(QUIET=True)
|
|
@@ -549,7 +566,7 @@ def visualize_attention_and_ranges(
|
|
| 549 |
rows.append(
|
| 550 |
f"<tr>"
|
| 551 |
f"<td style='border:1px solid #ddd;padding:6px'><strong>Top {rank}</strong></td>"
|
| 552 |
-
f"<td style='border:1px solid #ddd;padding:6px'>Protein: <strong>{
|
| 553 |
f"<td style='border:1px solid #ddd;padding:6px'>Ligand: <strong>{i+1}:{d_tokens[i]}</strong></td>"
|
| 554 |
f"<td style='border:1px solid #ddd;padding:6px'>Score: <strong>{val.item():.6f}</strong></td>"
|
| 555 |
f"</tr>"
|
|
@@ -586,16 +603,15 @@ def visualize_attention_and_ranges(
|
|
| 586 |
f"<tr>"
|
| 587 |
f"<td style='border:1px solid #ddd;padding:6px'><strong>Top {rank}</strong></td>"
|
| 588 |
f"<td style='border:1px solid #ddd;padding:6px'>"
|
| 589 |
-
f"
|
| 590 |
f"</td>"
|
| 591 |
f"<td style='border:1px solid #ddd;padding:6px'>"
|
| 592 |
-
f"
|
| 593 |
f"</td>"
|
| 594 |
f"</tr>"
|
| 595 |
)
|
| 596 |
|
| 597 |
ranges_html = (
|
| 598 |
-
"<h4 style='margin:12px 0 6px'>Top-K Residues (ranked by aggregated attention)</h4>"
|
| 599 |
"<table style='border-collapse:collapse;margin:6px 0 16px;width:100%'>"
|
| 600 |
"<thead><tr style='background:#f5f5f5'>"
|
| 601 |
"<th style='border:1px solid #ddd;padding:6px'>Rank</th>"
|
|
@@ -737,6 +753,10 @@ def visualize_attention_and_ranges(
|
|
| 737 |
return prob_html, ranges_html, heat_html
|
| 738 |
|
| 739 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 740 |
|
| 741 |
|
| 742 |
# βββββ Gradio callbacks βββββββββββββββββββββββββββββββββββββββββ
|
|
@@ -834,10 +854,10 @@ def inference_cb(prot_seq, drug_seq, head_choice, topk_choice):
|
|
| 834 |
# Input validation
|
| 835 |
# ------------------------------
|
| 836 |
if not prot_seq or not prot_seq.strip():
|
| 837 |
-
return "
|
| 838 |
|
| 839 |
if not drug_seq or not drug_seq.strip():
|
| 840 |
-
return "
|
| 841 |
|
| 842 |
prot_seq = prot_seq.strip()
|
| 843 |
drug_seq_in = drug_seq.strip()
|
|
@@ -851,10 +871,8 @@ def inference_cb(prot_seq, drug_seq, head_choice, topk_choice):
|
|
| 851 |
if ltype == "smiles":
|
| 852 |
conv = smiles_to_selfies(drug_seq_in)
|
| 853 |
if conv is None:
|
| 854 |
-
return (
|
| 855 |
-
"
|
| 856 |
-
"The SMILES appears invalid.</p>",
|
| 857 |
-
"",
|
| 858 |
)
|
| 859 |
drug_seq_for_tokenizer = conv
|
| 860 |
else:
|
|
@@ -962,54 +980,159 @@ def inference_cb(prot_seq, drug_seq, head_choice, topk_choice):
|
|
| 962 |
raw_selfies=raw_selfies,
|
| 963 |
)
|
| 964 |
|
| 965 |
-
|
| 966 |
-
return full_html
|
| 967 |
|
| 968 |
def clear_cb():
|
| 969 |
-
return "", "", "", None, ""
|
| 970 |
-
# protein_seq, drug_seq,
|
| 971 |
|
| 972 |
|
| 973 |
# βββββ Gradio interface definition βββββββββββββββββββββββββββββββ
|
| 974 |
css = """
|
| 975 |
:root{
|
| 976 |
-
--bg:#f8fafc; --card:#
|
| 977 |
-
--muted:#
|
| 978 |
-
--radius:14px; --icon-size:20px;
|
| 979 |
}
|
| 980 |
|
| 981 |
*{box-sizing:border-box}
|
| 982 |
-
html,body{background:
|
| 983 |
-
.gradio-container{max-width:1120px;margin:0 auto}
|
| 984 |
|
| 985 |
/* Title and subtitle */
|
| 986 |
h1{
|
|
|
|
|
|
|
|
|
|
| 987 |
font-family:Inter,ui-sans-serif;letter-spacing:.2px;font-weight:700;
|
| 988 |
-
font-size:32px;margin:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 989 |
}
|
| 990 |
.subtle{color:var(--muted);font-size:14px;text-align:center;margin:-6px 0 18px}
|
| 991 |
|
| 992 |
/* Card style */
|
| 993 |
.card{
|
| 994 |
-
background:
|
| 995 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 996 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 997 |
|
| 998 |
/* Top links */
|
| 999 |
.link-row{display:flex;justify-content:center;gap:14px;margin:0 auto 18px;flex-wrap:wrap}
|
| 1000 |
|
| 1001 |
-
/*
|
| 1002 |
-
.grid-2{display:
|
| 1003 |
.grid-2 .col{display:flex;flex-direction:column;gap:12px}
|
| 1004 |
|
| 1005 |
/* Buttons */
|
| 1006 |
.gr-button{border-radius:12px !important;font-weight:700 !important;letter-spacing:.2px}
|
| 1007 |
#extract-btn{background:linear-gradient(90deg,#EFAFB2,#EFAFB2); color:#0f172a}
|
| 1008 |
-
#inference-btn{background:linear-gradient(90deg,#B2CBDF,#B2CBDF); color:#0f172a}
|
| 1009 |
-
#clear-btn{background:#FFE2B5; color:#0A0A0A; border:1px solid var(--border)}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1010 |
|
| 1011 |
/* Result spacing */
|
| 1012 |
#result-table{margin-bottom:16px}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1013 |
|
| 1014 |
/* Figure container */
|
| 1015 |
.figure-wrap{border:1px solid var(--border);border-radius:12px;overflow:hidden;box-shadow:var(--shadow)}
|
|
@@ -1095,9 +1218,97 @@ h1{
|
|
| 1095 |
.project-links{
|
| 1096 |
display:flex !important;
|
| 1097 |
justify-content:center !important;
|
| 1098 |
-
gap:
|
| 1099 |
flex-wrap:wrap !important;
|
| 1100 |
-
margin-bottom:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1101 |
}
|
| 1102 |
|
| 1103 |
#example-btn {
|
|
@@ -1111,13 +1322,13 @@ h1{
|
|
| 1111 |
}
|
| 1112 |
|
| 1113 |
#extract-sa-btn{
|
| 1114 |
-
background:#
|
| 1115 |
color:#0f172a !important;
|
| 1116 |
}
|
| 1117 |
|
| 1118 |
|
| 1119 |
"""
|
| 1120 |
-
with gr.Blocks() as demo:
|
| 1121 |
|
| 1122 |
gr.Markdown("<h1>ExplainBind: Token-level ProteinβLigand Interaction Visualiser</h1>")
|
| 1123 |
|
|
@@ -1163,10 +1374,13 @@ with gr.Blocks() as demo:
|
|
| 1163 |
# βββββββββββββββββββββββββββββββ
|
| 1164 |
# Guidelines
|
| 1165 |
# βββββββββββββββββββββββββββββββ
|
| 1166 |
-
|
| 1167 |
-
|
| 1168 |
-
|
| 1169 |
-
|
|
|
|
|
|
|
|
|
|
| 1170 |
<strong>Input formats:</strong>
|
| 1171 |
The system supports either <em>structure-aware (SA)</em> sequences derived from
|
| 1172 |
protein structures or conventional <em>FASTA</em> sequences.
|
|
@@ -1174,23 +1388,27 @@ with gr.Blocks() as demo:
|
|
| 1174 |
<code>.cif</code> files to extract the corresponding sequence representation.
|
| 1175 |
Ligands can be provided in <em>SMILES</em> or <em>SELFIES</em> format.
|
| 1176 |
</li>
|
| 1177 |
-
|
| 1178 |
-
<li>
|
|
|
|
| 1179 |
<strong>Interaction channel selection:</strong>
|
| 1180 |
Users may select a specific non-covalent interaction type
|
| 1181 |
(e.g., hydrogen bonding, hydrophobic interactions) or the
|
| 1182 |
overall interaction channel to visualise the corresponding
|
| 1183 |
token-level binding patterns.
|
| 1184 |
</li>
|
| 1185 |
-
|
| 1186 |
-
<li>
|
|
|
|
| 1187 |
<strong>Model outputs:</strong>
|
| 1188 |
The system reports (i) a predicted binding probability for the
|
| 1189 |
-
protein
|
| 1190 |
heat map illustrating spatial interaction patterns.
|
| 1191 |
</li>
|
| 1192 |
</ol>
|
| 1193 |
-
|
|
|
|
|
|
|
| 1194 |
|
| 1195 |
|
| 1196 |
# βββββββββββββββββββββββββββββββ
|
|
@@ -1226,7 +1444,7 @@ with gr.Blocks() as demo:
|
|
| 1226 |
render=False,
|
| 1227 |
)
|
| 1228 |
|
| 1229 |
-
with gr.Group():
|
| 1230 |
|
| 1231 |
gr.Markdown("### Example")
|
| 1232 |
|
|
@@ -1273,8 +1491,9 @@ with gr.Blocks() as demo:
|
|
| 1273 |
top_k_dd = gr.Dropdown(
|
| 1274 |
label="Top-K residue",
|
| 1275 |
choices=[str(i) for i in range(1, 21)],
|
| 1276 |
-
value="
|
| 1277 |
-
interactive=
|
|
|
|
| 1278 |
)
|
| 1279 |
with gr.Row():
|
| 1280 |
btn_infer = gr.Button(
|
|
@@ -1292,7 +1511,12 @@ with gr.Blocks() as demo:
|
|
| 1292 |
# βββββββββββββββββββββββββββββββ
|
| 1293 |
with gr.Column(elem_classes=["card"]):
|
| 1294 |
status_box = gr.HTML(elem_id="status-box")
|
| 1295 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1296 |
|
| 1297 |
|
| 1298 |
# βββββββββββββββββββββββββββββββ
|
|
@@ -1328,7 +1552,7 @@ with gr.Blocks() as demo:
|
|
| 1328 |
btn_infer.click(
|
| 1329 |
fn=inference_cb,
|
| 1330 |
inputs=[protein_seq, drug_seq, head_dd, top_k_dd],
|
| 1331 |
-
outputs=[
|
| 1332 |
)
|
| 1333 |
|
| 1334 |
clear_btn.click(
|
|
@@ -1337,7 +1561,9 @@ with gr.Blocks() as demo:
|
|
| 1337 |
outputs=[
|
| 1338 |
protein_seq,
|
| 1339 |
drug_seq,
|
| 1340 |
-
|
|
|
|
|
|
|
| 1341 |
structure_file,
|
| 1342 |
status_box,
|
| 1343 |
],
|
|
@@ -1346,7 +1572,5 @@ with gr.Blocks() as demo:
|
|
| 1346 |
|
| 1347 |
|
| 1348 |
demo.launch(
|
| 1349 |
-
theme=gr.themes.Default(),
|
| 1350 |
-
css=css,
|
| 1351 |
show_error=True
|
| 1352 |
-
)
|
|
|
|
| 60 |
# UI-visible names (Halogen bonding removed)
|
| 61 |
INTERACTION_NAMES = [
|
| 62 |
"Hydrogen bonding",
|
| 63 |
+
"Salt Bridges",
|
| 64 |
"ΟβΟ Stacking",
|
| 65 |
"CationβΟ",
|
| 66 |
"Hydrophobic",
|
|
|
|
| 76 |
# βββββ Helper utilities βββββββββββββββββββββββββββββββββββββββββββ
|
| 77 |
three2one = {k.upper(): v for k, v in IUPACData.protein_letters_3to1.items()}
|
| 78 |
three2one.update({"MSE": "M", "SEC": "C", "PYL": "K"})
|
| 79 |
+
one2three = {
|
| 80 |
+
"A": "ALA", "R": "ARG", "N": "ASN", "D": "ASP", "C": "CYS",
|
| 81 |
+
"Q": "GLN", "E": "GLU", "G": "GLY", "H": "HIS", "I": "ILE",
|
| 82 |
+
"L": "LEU", "K": "LYS", "M": "MET", "F": "PHE", "P": "PRO",
|
| 83 |
+
"S": "SER", "T": "THR", "W": "TRP", "Y": "TYR", "V": "VAL",
|
| 84 |
+
"U": "SEC", "O": "PYL", "B": "ASX", "Z": "GLX", "X": "UNK",
|
| 85 |
+
}
|
| 86 |
STANDARD_AA_SET = set("ACDEFGHIKLMNPQRSTVWY") # Uppercase FASTA amino acids
|
| 87 |
|
| 88 |
|
| 89 |
+
def format_residue_label(token: str, position: int) -> str:
|
| 90 |
+
"""Format tokenizer residue tokens as three-letter amino-acid labels."""
|
| 91 |
+
clean_token = (token or "").replace("β", "").strip()
|
| 92 |
+
residue = clean_token[:1].upper()
|
| 93 |
+
residue_3 = one2three.get(residue)
|
| 94 |
+
if residue_3:
|
| 95 |
+
return f"{residue_3} ({position})"
|
| 96 |
+
return f"{clean_token or '?'} ({position})"
|
| 97 |
+
|
| 98 |
+
|
| 99 |
def simple_seq_from_structure(path: str) -> str:
|
| 100 |
"""Extract the longest chain and return standard 1-letter amino acid sequence."""
|
| 101 |
parser = MMCIFParser(QUIET=True) if path.endswith(".cif") else PDBParser(QUIET=True)
|
|
|
|
| 566 |
rows.append(
|
| 567 |
f"<tr>"
|
| 568 |
f"<td style='border:1px solid #ddd;padding:6px'><strong>Top {rank}</strong></td>"
|
| 569 |
+
f"<td style='border:1px solid #ddd;padding:6px'>Protein: <strong>{format_residue_label(p_tokens[j], j+1)}</strong></td>"
|
| 570 |
f"<td style='border:1px solid #ddd;padding:6px'>Ligand: <strong>{i+1}:{d_tokens[i]}</strong></td>"
|
| 571 |
f"<td style='border:1px solid #ddd;padding:6px'>Score: <strong>{val.item():.6f}</strong></td>"
|
| 572 |
f"</tr>"
|
|
|
|
| 603 |
f"<tr>"
|
| 604 |
f"<td style='border:1px solid #ddd;padding:6px'><strong>Top {rank}</strong></td>"
|
| 605 |
f"<td style='border:1px solid #ddd;padding:6px'>"
|
| 606 |
+
f"<strong>{format_residue_label(p_tokens[j], j+1)}</strong>"
|
| 607 |
f"</td>"
|
| 608 |
f"<td style='border:1px solid #ddd;padding:6px'>"
|
| 609 |
+
f"<strong>{val.item():.6f}</strong>"
|
| 610 |
f"</td>"
|
| 611 |
f"</tr>"
|
| 612 |
)
|
| 613 |
|
| 614 |
ranges_html = (
|
|
|
|
| 615 |
"<table style='border-collapse:collapse;margin:6px 0 16px;width:100%'>"
|
| 616 |
"<thead><tr style='background:#f5f5f5'>"
|
| 617 |
"<th style='border:1px solid #ddd;padding:6px'>Rank</th>"
|
|
|
|
| 753 |
return prob_html, ranges_html, heat_html
|
| 754 |
|
| 755 |
|
| 756 |
+
def error_outputs(message: str):
|
| 757 |
+
return f"<p style='color:red'>{message}</p>", "", ""
|
| 758 |
+
|
| 759 |
+
|
| 760 |
|
| 761 |
|
| 762 |
# βββββ Gradio callbacks βββββββββββββββββββββββββββββββββββββββββ
|
|
|
|
| 854 |
# Input validation
|
| 855 |
# ------------------------------
|
| 856 |
if not prot_seq or not prot_seq.strip():
|
| 857 |
+
return error_outputs("Please extract or enter a protein sequence first.")
|
| 858 |
|
| 859 |
if not drug_seq or not drug_seq.strip():
|
| 860 |
+
return error_outputs("Please enter a ligand sequence (SELFIES or SMILES).")
|
| 861 |
|
| 862 |
prot_seq = prot_seq.strip()
|
| 863 |
drug_seq_in = drug_seq.strip()
|
|
|
|
| 871 |
if ltype == "smiles":
|
| 872 |
conv = smiles_to_selfies(drug_seq_in)
|
| 873 |
if conv is None:
|
| 874 |
+
return error_outputs(
|
| 875 |
+
"SMILESβSELFIES conversion failed. The SMILES appears invalid."
|
|
|
|
|
|
|
| 876 |
)
|
| 877 |
drug_seq_for_tokenizer = conv
|
| 878 |
else:
|
|
|
|
| 980 |
raw_selfies=raw_selfies,
|
| 981 |
)
|
| 982 |
|
| 983 |
+
return prob_html, table_html, heat_html
|
|
|
|
| 984 |
|
| 985 |
def clear_cb():
|
| 986 |
+
return "", "", "", "", "", None, ""
|
| 987 |
+
# protein_seq, drug_seq, binding_probability, binding_locations, interaction_map, structure_file, status_box
|
| 988 |
|
| 989 |
|
| 990 |
# βββββ Gradio interface definition βββββββββββββββββββββββββββββββ
|
| 991 |
css = """
|
| 992 |
:root{
|
| 993 |
+
--page:#0b1020; --bg:#f8fafc; --card:#ffffff; --text:#0f172a;
|
| 994 |
+
--muted:#475569; --border:#dbe3ee; --shadow:0 16px 44px rgba(2,6,23,.18);
|
| 995 |
+
--radius:14px; --icon-size:20px; --accent:#2563eb; --accent-soft:#eff6ff;
|
| 996 |
}
|
| 997 |
|
| 998 |
*{box-sizing:border-box}
|
| 999 |
+
html,body{background:var(--page)!important;color:var(--text)!important}
|
| 1000 |
+
.gradio-container{max-width:1120px;margin:0 auto;padding:0 20px 32px}
|
| 1001 |
|
| 1002 |
/* Title and subtitle */
|
| 1003 |
h1{
|
| 1004 |
+
display:block;
|
| 1005 |
+
width:fit-content;
|
| 1006 |
+
max-width:100%;
|
| 1007 |
font-family:Inter,ui-sans-serif;letter-spacing:.2px;font-weight:700;
|
| 1008 |
+
font-size:32px;line-height:1.22;margin:24px auto 18px;text-align:center;
|
| 1009 |
+
padding:14px 22px;
|
| 1010 |
+
border:1px solid #dbe3ee;
|
| 1011 |
+
border-radius:16px;
|
| 1012 |
+
background:#ffffff;
|
| 1013 |
+
color:#0f172a!important;
|
| 1014 |
+
box-shadow:0 14px 36px rgba(2,6,23,.16);
|
| 1015 |
+
text-shadow:none;
|
| 1016 |
}
|
| 1017 |
.subtle{color:var(--muted);font-size:14px;text-align:center;margin:-6px 0 18px}
|
| 1018 |
|
| 1019 |
/* Card style */
|
| 1020 |
.card{
|
| 1021 |
+
background:linear-gradient(180deg,#ffffff 0%,#f8fafc 100%)!important;
|
| 1022 |
+
border:1px solid var(--border);
|
| 1023 |
+
border-radius:var(--radius);
|
| 1024 |
+
box-shadow:var(--shadow);
|
| 1025 |
+
padding:22px;
|
| 1026 |
+
color:var(--text)!important;
|
| 1027 |
}
|
| 1028 |
+
.card label,
|
| 1029 |
+
.card p,
|
| 1030 |
+
.card li,
|
| 1031 |
+
.card strong{color:var(--text)!important}
|
| 1032 |
|
| 1033 |
/* Top links */
|
| 1034 |
.link-row{display:flex;justify-content:center;gap:14px;margin:0 auto 18px;flex-wrap:wrap}
|
| 1035 |
|
| 1036 |
+
/* Input and controls are stacked for better scanning on narrow screens. */
|
| 1037 |
+
.grid-2{display:flex;flex-direction:column;gap:16px}
|
| 1038 |
.grid-2 .col{display:flex;flex-direction:column;gap:12px}
|
| 1039 |
|
| 1040 |
/* Buttons */
|
| 1041 |
.gr-button{border-radius:12px !important;font-weight:700 !important;letter-spacing:.2px}
|
| 1042 |
#extract-btn{background:linear-gradient(90deg,#EFAFB2,#EFAFB2); color:#0f172a}
|
| 1043 |
+
#inference-btn{background:linear-gradient(90deg,#B2CBDF,#B2CBDF)!important; color:#0f172a!important}
|
| 1044 |
+
#clear-btn{background:#FFE2B5!important; color:#0A0A0A!important; border:1px solid var(--border)!important}
|
| 1045 |
+
|
| 1046 |
+
#left,
|
| 1047 |
+
#right{
|
| 1048 |
+
background:#ffffff!important;
|
| 1049 |
+
border:1px solid var(--border);
|
| 1050 |
+
border-radius:12px;
|
| 1051 |
+
padding:14px;
|
| 1052 |
+
}
|
| 1053 |
+
|
| 1054 |
+
#protein-seq textarea,
|
| 1055 |
+
#drug-seq textarea{
|
| 1056 |
+
background:#ffffff!important;
|
| 1057 |
+
color:#0f172a!important;
|
| 1058 |
+
border:1px solid #cbd5e1!important;
|
| 1059 |
+
border-radius:10px!important;
|
| 1060 |
+
}
|
| 1061 |
+
|
| 1062 |
+
#right input,
|
| 1063 |
+
#right button{
|
| 1064 |
+
color:#0f172a!important;
|
| 1065 |
+
}
|
| 1066 |
+
|
| 1067 |
+
#right [role="listbox"],
|
| 1068 |
+
#right [role="option"]{
|
| 1069 |
+
background:#ffffff!important;
|
| 1070 |
+
color:#0f172a!important;
|
| 1071 |
+
border-color:#cbd5e1!important;
|
| 1072 |
+
}
|
| 1073 |
+
|
| 1074 |
+
#right [role="option"]:hover,
|
| 1075 |
+
#right [role="option"][aria-selected="true"]{
|
| 1076 |
+
background:#eff6ff!important;
|
| 1077 |
+
color:#0f172a!important;
|
| 1078 |
+
}
|
| 1079 |
+
|
| 1080 |
+
#structure-file button{
|
| 1081 |
+
background:#ffffff!important;
|
| 1082 |
+
color:#0f172a!important;
|
| 1083 |
+
border:1px dashed #94a3b8!important;
|
| 1084 |
+
}
|
| 1085 |
+
|
| 1086 |
+
#structure-file svg{
|
| 1087 |
+
color:#2563eb!important;
|
| 1088 |
+
}
|
| 1089 |
+
|
| 1090 |
+
#example-panel{
|
| 1091 |
+
background:#f8fafc!important;
|
| 1092 |
+
border:1px solid #dbe3ee!important;
|
| 1093 |
+
border-radius:12px!important;
|
| 1094 |
+
padding:12px!important;
|
| 1095 |
+
}
|
| 1096 |
+
|
| 1097 |
+
#example-panel table,
|
| 1098 |
+
#example-panel iframe,
|
| 1099 |
+
#example-panel .table-wrap{
|
| 1100 |
+
background:#ffffff!important;
|
| 1101 |
+
color:#0f172a!important;
|
| 1102 |
+
}
|
| 1103 |
|
| 1104 |
/* Result spacing */
|
| 1105 |
#result-table{margin-bottom:16px}
|
| 1106 |
+
.result-heading{
|
| 1107 |
+
display:flex;
|
| 1108 |
+
align-items:center;
|
| 1109 |
+
gap:12px;
|
| 1110 |
+
margin:22px 0 12px;
|
| 1111 |
+
padding:10px 12px;
|
| 1112 |
+
border:1px solid #dbe3ee;
|
| 1113 |
+
border-left:4px solid #2563eb;
|
| 1114 |
+
border-radius:10px;
|
| 1115 |
+
background:#f8fafc;
|
| 1116 |
+
color:#0f172a !important;
|
| 1117 |
+
}
|
| 1118 |
+
.result-index{
|
| 1119 |
+
display:inline-flex;
|
| 1120 |
+
align-items:center;
|
| 1121 |
+
justify-content:center;
|
| 1122 |
+
min-width:34px;
|
| 1123 |
+
height:24px;
|
| 1124 |
+
border-radius:9999px;
|
| 1125 |
+
background:#0f172a;
|
| 1126 |
+
color:#fff !important;
|
| 1127 |
+
font-size:12px;
|
| 1128 |
+
font-weight:700;
|
| 1129 |
+
}
|
| 1130 |
+
.result-title{
|
| 1131 |
+
font-size:18px;
|
| 1132 |
+
font-weight:700;
|
| 1133 |
+
line-height:1.25;
|
| 1134 |
+
color:#0f172a !important;
|
| 1135 |
+
}
|
| 1136 |
|
| 1137 |
/* Figure container */
|
| 1138 |
.figure-wrap{border:1px solid var(--border);border-radius:12px;overflow:hidden;box-shadow:var(--shadow)}
|
|
|
|
| 1218 |
.project-links{
|
| 1219 |
display:flex !important;
|
| 1220 |
justify-content:center !important;
|
| 1221 |
+
gap:14px !important;
|
| 1222 |
flex-wrap:wrap !important;
|
| 1223 |
+
margin-bottom:24px !important;
|
| 1224 |
+
}
|
| 1225 |
+
|
| 1226 |
+
.guidelines-panel{
|
| 1227 |
+
margin:0 auto 18px;
|
| 1228 |
+
border:1px solid #dbe3ee;
|
| 1229 |
+
border-left:4px solid var(--accent);
|
| 1230 |
+
border-radius:14px;
|
| 1231 |
+
background:linear-gradient(180deg,#ffffff 0%,#f7fbff 100%);
|
| 1232 |
+
box-shadow:var(--shadow);
|
| 1233 |
+
color:#0f172a!important;
|
| 1234 |
+
overflow:hidden;
|
| 1235 |
+
}
|
| 1236 |
+
|
| 1237 |
+
.guidelines-panel summary{
|
| 1238 |
+
display:flex;
|
| 1239 |
+
align-items:center;
|
| 1240 |
+
justify-content:space-between;
|
| 1241 |
+
cursor:pointer;
|
| 1242 |
+
padding:16px 18px;
|
| 1243 |
+
color:#0f172a!important;
|
| 1244 |
+
font-size:18px;
|
| 1245 |
+
font-weight:800;
|
| 1246 |
+
border-bottom:1px solid #e2e8f0;
|
| 1247 |
+
}
|
| 1248 |
+
|
| 1249 |
+
.guidelines-panel summary::-webkit-details-marker{display:none}
|
| 1250 |
+
.guidelines-panel summary::after{
|
| 1251 |
+
content:"v";
|
| 1252 |
+
display:inline-flex;
|
| 1253 |
+
align-items:center;
|
| 1254 |
+
justify-content:center;
|
| 1255 |
+
width:26px;
|
| 1256 |
+
height:26px;
|
| 1257 |
+
border-radius:9999px;
|
| 1258 |
+
background:#eaf2ff;
|
| 1259 |
+
color:#1d4ed8;
|
| 1260 |
+
font-size:12px;
|
| 1261 |
+
font-weight:800;
|
| 1262 |
+
}
|
| 1263 |
+
|
| 1264 |
+
.guidelines-copy{
|
| 1265 |
+
padding:16px 18px 18px;
|
| 1266 |
+
color:#0f172a!important;
|
| 1267 |
+
}
|
| 1268 |
+
|
| 1269 |
+
.guidelines-list{
|
| 1270 |
+
list-style:none;
|
| 1271 |
+
display:grid;
|
| 1272 |
+
gap:12px;
|
| 1273 |
+
margin:0!important;
|
| 1274 |
+
padding:0!important;
|
| 1275 |
+
}
|
| 1276 |
+
|
| 1277 |
+
.guideline-item{
|
| 1278 |
+
position:relative;
|
| 1279 |
+
min-height:72px;
|
| 1280 |
+
padding:12px 14px 12px 48px;
|
| 1281 |
+
border:1px solid #e2e8f0;
|
| 1282 |
+
border-radius:12px;
|
| 1283 |
+
background:#ffffff;
|
| 1284 |
+
color:#0f172a!important;
|
| 1285 |
+
line-height:1.55;
|
| 1286 |
+
}
|
| 1287 |
+
|
| 1288 |
+
.guideline-number{
|
| 1289 |
+
position:absolute;
|
| 1290 |
+
left:14px;
|
| 1291 |
+
top:14px;
|
| 1292 |
+
display:inline-flex;
|
| 1293 |
+
align-items:center;
|
| 1294 |
+
justify-content:center;
|
| 1295 |
+
width:24px;
|
| 1296 |
+
height:24px;
|
| 1297 |
+
border-radius:9999px;
|
| 1298 |
+
background:#0f172a;
|
| 1299 |
+
color:#fff!important;
|
| 1300 |
+
font-size:12px;
|
| 1301 |
+
font-weight:800;
|
| 1302 |
+
}
|
| 1303 |
+
|
| 1304 |
+
.guideline-item strong{color:#0f172a!important}
|
| 1305 |
+
.guideline-item em{color:#1d4ed8!important;font-style:normal;font-weight:650}
|
| 1306 |
+
.guideline-item code{
|
| 1307 |
+
background:#eef2ff;
|
| 1308 |
+
color:#1e3a8a!important;
|
| 1309 |
+
border:1px solid #dbe3ff;
|
| 1310 |
+
border-radius:6px;
|
| 1311 |
+
padding:1px 5px;
|
| 1312 |
}
|
| 1313 |
|
| 1314 |
#example-btn {
|
|
|
|
| 1322 |
}
|
| 1323 |
|
| 1324 |
#extract-sa-btn{
|
| 1325 |
+
background:#eadcf8 !important;
|
| 1326 |
color:#0f172a !important;
|
| 1327 |
}
|
| 1328 |
|
| 1329 |
|
| 1330 |
"""
|
| 1331 |
+
with gr.Blocks(theme=gr.themes.Default(), css=css) as demo:
|
| 1332 |
|
| 1333 |
gr.Markdown("<h1>ExplainBind: Token-level ProteinβLigand Interaction Visualiser</h1>")
|
| 1334 |
|
|
|
|
| 1374 |
# βββββββββββββββββββββββββββββββ
|
| 1375 |
# Guidelines
|
| 1376 |
# βββββββββββββββββββββββββββββββ
|
| 1377 |
+
gr.HTML("""
|
| 1378 |
+
<details class="guidelines-panel" open>
|
| 1379 |
+
<summary>Guidelines for Users</summary>
|
| 1380 |
+
<div class="guidelines-copy">
|
| 1381 |
+
<ol class="guidelines-list">
|
| 1382 |
+
<li class="guideline-item">
|
| 1383 |
+
<span class="guideline-number">1</span>
|
| 1384 |
<strong>Input formats:</strong>
|
| 1385 |
The system supports either <em>structure-aware (SA)</em> sequences derived from
|
| 1386 |
protein structures or conventional <em>FASTA</em> sequences.
|
|
|
|
| 1388 |
<code>.cif</code> files to extract the corresponding sequence representation.
|
| 1389 |
Ligands can be provided in <em>SMILES</em> or <em>SELFIES</em> format.
|
| 1390 |
</li>
|
| 1391 |
+
|
| 1392 |
+
<li class="guideline-item">
|
| 1393 |
+
<span class="guideline-number">2</span>
|
| 1394 |
<strong>Interaction channel selection:</strong>
|
| 1395 |
Users may select a specific non-covalent interaction type
|
| 1396 |
(e.g., hydrogen bonding, hydrophobic interactions) or the
|
| 1397 |
overall interaction channel to visualise the corresponding
|
| 1398 |
token-level binding patterns.
|
| 1399 |
</li>
|
| 1400 |
+
|
| 1401 |
+
<li class="guideline-item">
|
| 1402 |
+
<span class="guideline-number">3</span>
|
| 1403 |
<strong>Model outputs:</strong>
|
| 1404 |
The system reports (i) a predicted binding probability for the
|
| 1405 |
+
protein-ligand pair, (ii) a ranked Top-K residue table, and (iii) a token-level interaction
|
| 1406 |
heat map illustrating spatial interaction patterns.
|
| 1407 |
</li>
|
| 1408 |
</ol>
|
| 1409 |
+
</div>
|
| 1410 |
+
</details>
|
| 1411 |
+
""")
|
| 1412 |
|
| 1413 |
|
| 1414 |
# βββββββββββββββββββββββββββββββ
|
|
|
|
| 1444 |
render=False,
|
| 1445 |
)
|
| 1446 |
|
| 1447 |
+
with gr.Group(elem_id="example-panel"):
|
| 1448 |
|
| 1449 |
gr.Markdown("### Example")
|
| 1450 |
|
|
|
|
| 1491 |
top_k_dd = gr.Dropdown(
|
| 1492 |
label="Top-K residue",
|
| 1493 |
choices=[str(i) for i in range(1, 21)],
|
| 1494 |
+
value="5",
|
| 1495 |
+
interactive=False,
|
| 1496 |
+
visible=False,
|
| 1497 |
)
|
| 1498 |
with gr.Row():
|
| 1499 |
btn_infer = gr.Button(
|
|
|
|
| 1511 |
# βββββββββββββββββββββββββββββββ
|
| 1512 |
with gr.Column(elem_classes=["card"]):
|
| 1513 |
status_box = gr.HTML(elem_id="status-box")
|
| 1514 |
+
gr.HTML("<div class='result-heading'><span class='result-index'>01</span><span class='result-title'>Binding Probability</span></div>")
|
| 1515 |
+
binding_probability = gr.HTML(elem_id="binding-probability")
|
| 1516 |
+
gr.HTML("<div class='result-heading'><span class='result-index'>02</span><span class='result-title'>Binding-site Locations</span></div>")
|
| 1517 |
+
binding_locations = gr.HTML(elem_id="binding-site-locations")
|
| 1518 |
+
gr.HTML("<div class='result-heading'><span class='result-index'>03</span><span class='result-title'>Interaction Map</span></div>")
|
| 1519 |
+
interaction_map = gr.HTML(elem_id="interaction-map")
|
| 1520 |
|
| 1521 |
|
| 1522 |
# βββββββββββββββββββββββββββββββ
|
|
|
|
| 1552 |
btn_infer.click(
|
| 1553 |
fn=inference_cb,
|
| 1554 |
inputs=[protein_seq, drug_seq, head_dd, top_k_dd],
|
| 1555 |
+
outputs=[binding_probability, binding_locations, interaction_map],
|
| 1556 |
)
|
| 1557 |
|
| 1558 |
clear_btn.click(
|
|
|
|
| 1561 |
outputs=[
|
| 1562 |
protein_seq,
|
| 1563 |
drug_seq,
|
| 1564 |
+
binding_probability,
|
| 1565 |
+
binding_locations,
|
| 1566 |
+
interaction_map,
|
| 1567 |
structure_file,
|
| 1568 |
status_box,
|
| 1569 |
],
|
|
|
|
| 1572 |
|
| 1573 |
|
| 1574 |
demo.launch(
|
|
|
|
|
|
|
| 1575 |
show_error=True
|
| 1576 |
+
)
|