Upload folder using huggingface_hub
Browse files
app.py
CHANGED
|
@@ -126,16 +126,16 @@ CSS = """
|
|
| 126 |
|
| 127 |
.metric-strip {
|
| 128 |
display: grid;
|
| 129 |
-
grid-template-columns:
|
| 130 |
gap: 0.8rem;
|
| 131 |
}
|
| 132 |
|
| 133 |
.metric-card {
|
| 134 |
border: 1px solid var(--line);
|
| 135 |
background: linear-gradient(180deg, rgba(255,255,255,0.92), rgba(248,244,236,0.9));
|
| 136 |
-
padding: 0.
|
| 137 |
border-radius: 18px;
|
| 138 |
-
min-height:
|
| 139 |
box-shadow: 0 8px 24px rgba(23,37,45,0.04);
|
| 140 |
}
|
| 141 |
|
|
@@ -146,7 +146,7 @@ CSS = """
|
|
| 146 |
|
| 147 |
.metric-card strong {
|
| 148 |
display: block;
|
| 149 |
-
font-size:
|
| 150 |
margin-top: 0.15rem;
|
| 151 |
color: var(--ink);
|
| 152 |
}
|
|
@@ -161,23 +161,15 @@ CSS = """
|
|
| 161 |
text-decoration: underline;
|
| 162 |
}
|
| 163 |
|
| 164 |
-
.
|
| 165 |
-
|
| 166 |
-
grid-template-columns: repeat(3, minmax(0, 1fr));
|
| 167 |
-
gap: 0.95rem;
|
| 168 |
-
}
|
| 169 |
-
|
| 170 |
-
.guide-card {
|
| 171 |
border: 1px solid var(--line);
|
| 172 |
-
background: var(--card-strong);
|
| 173 |
-
padding: 1rem 1.05rem;
|
| 174 |
border-radius: 18px;
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
margin-bottom: 0.28rem;
|
| 181 |
}
|
| 182 |
|
| 183 |
.workspace {
|
|
@@ -246,8 +238,8 @@ CSS = """
|
|
| 246 |
|
| 247 |
.section-note {
|
| 248 |
color: var(--ink-soft);
|
| 249 |
-
font-size: 0.
|
| 250 |
-
margin: 0.
|
| 251 |
}
|
| 252 |
|
| 253 |
.action-row {
|
|
@@ -261,7 +253,7 @@ CSS = """
|
|
| 261 |
background: linear-gradient(180deg, rgba(255,255,255,0.94), rgba(248,244,236,0.92));
|
| 262 |
border: 1px solid var(--line);
|
| 263 |
border-radius: 24px;
|
| 264 |
-
padding:
|
| 265 |
box-shadow: var(--shadow);
|
| 266 |
}
|
| 267 |
|
|
@@ -294,7 +286,7 @@ CSS = """
|
|
| 294 |
.results-callout h3 {
|
| 295 |
margin: 0 0 0.55rem 0;
|
| 296 |
font-family: "Fraunces", serif;
|
| 297 |
-
font-size: 1.
|
| 298 |
}
|
| 299 |
|
| 300 |
.results-callout ul {
|
|
@@ -312,22 +304,23 @@ CSS = """
|
|
| 312 |
line-height: 1.5;
|
| 313 |
}
|
| 314 |
|
| 315 |
-
.
|
| 316 |
display: grid;
|
| 317 |
grid-template-columns: repeat(3, minmax(0, 1fr));
|
| 318 |
-
gap: 0.
|
|
|
|
| 319 |
}
|
| 320 |
|
| 321 |
-
.
|
| 322 |
border: 1px solid var(--line);
|
| 323 |
-
border-radius:
|
| 324 |
-
background: rgba(255,255,255,0.
|
| 325 |
-
padding: 0.
|
| 326 |
}
|
| 327 |
|
| 328 |
-
.
|
| 329 |
display: block;
|
| 330 |
-
margin-bottom: 0.
|
| 331 |
}
|
| 332 |
|
| 333 |
.examples-note {
|
|
@@ -449,10 +442,17 @@ CSS = """
|
|
| 449 |
.gradio-container .gradio-dataframe .wrap,
|
| 450 |
.gradio-container .gradio-dataframe .table-wrap,
|
| 451 |
.gradio-container .gradio-dataframe .scrollable,
|
|
|
|
| 452 |
.gradio-container [data-testid="dataframe"],
|
| 453 |
.gradio-container [data-testid="dataframe"] .wrap,
|
| 454 |
.gradio-container [data-testid="dataframe"] .table-wrap,
|
| 455 |
-
.gradio-container [data-testid="dataframe"] .scrollable
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 456 |
background: #fffdf7 !important;
|
| 457 |
border-color: var(--line) !important;
|
| 458 |
color: var(--ink) !important;
|
|
@@ -472,7 +472,39 @@ CSS = """
|
|
| 472 |
.gradio-container [data-testid="accordion"],
|
| 473 |
.gradio-container [data-testid="accordion"] * {
|
| 474 |
color: var(--ink) !important;
|
| 475 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 476 |
}
|
| 477 |
|
| 478 |
.gradio-container .gr-box,
|
|
@@ -484,8 +516,7 @@ CSS = """
|
|
| 484 |
@media (max-width: 980px) {
|
| 485 |
.hero-grid,
|
| 486 |
.helper-row,
|
| 487 |
-
.
|
| 488 |
-
.guide-grid,
|
| 489 |
.metric-strip {
|
| 490 |
grid-template-columns: 1fr;
|
| 491 |
}
|
|
@@ -652,9 +683,9 @@ def _decorate_valid_rows(valid_rows: list[dict[str, Any]]) -> list[dict[str, Any
|
|
| 652 |
|
| 653 |
def _build_summary(query_text: str, valid_rows: list[dict[str, Any]], invalid_rows: list[dict[str, Any]], warning: str | None) -> str:
|
| 654 |
best = valid_rows[0] if valid_rows else None
|
| 655 |
-
bullets = [f"<li><strong>Ranked
|
| 656 |
if invalid_rows:
|
| 657 |
-
bullets.append(f"<li><strong>Rejected
|
| 658 |
if best is not None:
|
| 659 |
bullets.append(
|
| 660 |
f"<li><strong>Top candidate:</strong> <code>{best['canonical_smiles']}</code> · {best['priority_band']} · relative score {best['relative_score']:.1f}/100</li>"
|
|
@@ -663,10 +694,13 @@ def _build_summary(query_text: str, valid_rows: list[dict[str, Any]], invalid_ro
|
|
| 663 |
bullets.append(f"<li><strong>Warning:</strong> {warning}</li>")
|
| 664 |
return (
|
| 665 |
"<div class='results-callout'>"
|
| 666 |
-
"<h3>
|
| 667 |
f"<ul>{''.join(bullets)}</ul>"
|
| 668 |
-
"<
|
| 669 |
-
"<
|
|
|
|
|
|
|
|
|
|
| 670 |
"</div>"
|
| 671 |
)
|
| 672 |
|
|
@@ -812,16 +846,15 @@ with gr.Blocks(title="BioAssayAlign Compatibility Explorer", analytics_enabled=F
|
|
| 812 |
<div class="eyebrow">BioAssayAlign · assay-conditioned compound ranking</div>
|
| 813 |
<div class="hero-title">Rank a compound list against one assay definition</div>
|
| 814 |
<div class="hero-copy">
|
| 815 |
-
Define
|
| 816 |
-
The output is intended for shortlist prioritization, retrospective analysis, and assay-conditioned compound triage.
|
| 817 |
</div>
|
| 818 |
</div>
|
| 819 |
<div class="hero-side">
|
| 820 |
<div class="hero-side-title">Operational scope</div>
|
| 821 |
<ul class="hero-list">
|
| 822 |
-
<li>
|
| 823 |
-
<li>Relative
|
| 824 |
-
<li>Immediate rejection of malformed
|
| 825 |
</ul>
|
| 826 |
</div>
|
| 827 |
</div>
|
|
@@ -834,22 +867,12 @@ with gr.Blocks(title="BioAssayAlign Compatibility Explorer", analytics_enabled=F
|
|
| 834 |
gr.HTML(
|
| 835 |
f"""
|
| 836 |
<div class="metric-strip">
|
| 837 |
-
<div class="metric-card"><span>Model</span><strong><a href="https://huggingface.co/{MODEL_REPO_ID}" target="_blank" rel="noopener">
|
| 838 |
-
<div class="metric-card"><span>
|
| 839 |
-
<div class="metric-card"><span>Score semantics</span><strong>Use rank and relative score within the submitted list</strong></div>
|
| 840 |
</div>
|
| 841 |
"""
|
| 842 |
)
|
| 843 |
-
|
| 844 |
-
gr.HTML(
|
| 845 |
-
"""
|
| 846 |
-
<div class="guide-grid">
|
| 847 |
-
<div class="guide-card"><strong>1. Specify the assay</strong>Include target, readout, organism, and format when known. Missing fields are allowed but reduce context.</div>
|
| 848 |
-
<div class="guide-card"><strong>2. Provide a candidate set</strong>Paste one SMILES per line or upload a CSV with a <code>smiles</code> column.</div>
|
| 849 |
-
<div class="guide-card"><strong>3. Read the output scientifically</strong>Use rank and relative score for shortlist decisions. Raw model score is an internal compatibility logit.</div>
|
| 850 |
-
</div>
|
| 851 |
-
"""
|
| 852 |
-
)
|
| 853 |
|
| 854 |
with gr.Tab("Rank Compounds"):
|
| 855 |
with gr.Row(elem_classes="workspace"):
|
|
@@ -862,7 +885,7 @@ with gr.Blocks(title="BioAssayAlign Compatibility Explorer", analytics_enabled=F
|
|
| 862 |
<div class="pane-title">Define the assay context</div>
|
| 863 |
</div>
|
| 864 |
</div>
|
| 865 |
-
<div class="pane-copy">
|
| 866 |
<div class="helper-row">
|
| 867 |
<div class="helper-chip"><strong>Describe the protocol signal</strong>State the readout, assay system, target biology, and measurement context.</div>
|
| 868 |
<div class="helper-chip"><strong>Use target identifiers</strong>UniProt IDs often improve separation between plausible and implausible candidates.</div>
|
|
@@ -871,8 +894,8 @@ with gr.Blocks(title="BioAssayAlign Compatibility Explorer", analytics_enabled=F
|
|
| 871 |
"""
|
| 872 |
)
|
| 873 |
example_name = gr.Dropdown(choices=list(EXAMPLES.keys()), value=DEFAULT_EXAMPLE_NAME, label="Example assay")
|
| 874 |
-
gr.HTML("<div class='examples-note'>The form starts with a live JAK2 example.
|
| 875 |
-
load_example_btn = gr.Button("
|
| 876 |
assay_title = gr.Textbox(label="Assay title", value=DEFAULT_EXAMPLE["title"])
|
| 877 |
description = gr.Textbox(label="Description", value=DEFAULT_EXAMPLE["description"], lines=6, placeholder="Describe the assay in practical lab language.")
|
| 878 |
with gr.Row():
|
|
@@ -892,7 +915,7 @@ with gr.Blocks(title="BioAssayAlign Compatibility Explorer", analytics_enabled=F
|
|
| 892 |
<div class="pane-title">Submit the candidate molecules</div>
|
| 893 |
</div>
|
| 894 |
</div>
|
| 895 |
-
<div class="pane-copy">The
|
| 896 |
"""
|
| 897 |
)
|
| 898 |
smiles_text = gr.Textbox(
|
|
@@ -908,31 +931,14 @@ with gr.Blocks(title="BioAssayAlign Compatibility Explorer", analytics_enabled=F
|
|
| 908 |
run_btn = gr.Button("Run assay-conditioned ranking", variant="primary")
|
| 909 |
clear_btn = gr.ClearButton(value="Clear inputs", components=[assay_title, description, organism, readout, assay_format, assay_type, target_uniprot, smiles_text, upload_file])
|
| 910 |
|
| 911 |
-
gr.HTML(
|
| 912 |
-
"""
|
| 913 |
-
<div class="summary-shell">
|
| 914 |
-
<div class="pane-header">
|
| 915 |
-
<div>
|
| 916 |
-
<div class="pane-kicker">Output</div>
|
| 917 |
-
<div class="pane-title">Interpret the ranked shortlist</div>
|
| 918 |
-
</div>
|
| 919 |
-
</div>
|
| 920 |
-
<div class="explain-grid">
|
| 921 |
-
<div class="explain-card"><strong>Priority band</strong>Use this as the first-pass triage signal when scanning the ranked list.</div>
|
| 922 |
-
<div class="explain-card"><strong>Relative score</strong>0–100 scale computed within the submitted list only. It is not calibrated across separate runs.</div>
|
| 923 |
-
<div class="explain-card"><strong>Model score</strong>Internal compatibility logit from the scorer head. Useful for export or debugging, not for direct biological interpretation.</div>
|
| 924 |
-
</div>
|
| 925 |
-
</div>
|
| 926 |
-
"""
|
| 927 |
-
)
|
| 928 |
summary = gr.HTML(elem_classes="results-shell")
|
| 929 |
-
with gr.Accordion("Serialized assay text used by the model", open=False):
|
| 930 |
assay_preview = gr.Textbox(lines=12, label="Model-facing assay text")
|
| 931 |
gr.HTML("<div class='section-note'><strong>Ranked candidates</strong></div>")
|
| 932 |
-
ranked_df = gr.Dataframe(label=None, show_label=False, interactive=False, wrap=True)
|
| 933 |
gr.HTML("<div class='section-note'><strong>Rejected inputs</strong></div>")
|
| 934 |
-
invalid_df = gr.Dataframe(label=None, show_label=False, interactive=False, wrap=True)
|
| 935 |
-
download_file = gr.File(label="
|
| 936 |
|
| 937 |
load_example_btn.click(
|
| 938 |
load_example,
|
|
@@ -945,30 +951,6 @@ with gr.Blocks(title="BioAssayAlign Compatibility Explorer", analytics_enabled=F
|
|
| 945 |
outputs=[summary, assay_preview, ranked_df, invalid_df, download_file],
|
| 946 |
)
|
| 947 |
|
| 948 |
-
with gr.Tab("How To Use This"):
|
| 949 |
-
gr.HTML(
|
| 950 |
-
"""
|
| 951 |
-
<div class="summary-shell">
|
| 952 |
-
<div class="pane-header">
|
| 953 |
-
<div>
|
| 954 |
-
<div class="pane-kicker">Practical guidance</div>
|
| 955 |
-
<div class="pane-title">Operating guidance</div>
|
| 956 |
-
</div>
|
| 957 |
-
</div>
|
| 958 |
-
<div class="explain-grid">
|
| 959 |
-
<div class="explain-card"><strong>Encode the assay explicitly</strong>Use full scientific language rather than keywords alone. Include target and readout when available.</div>
|
| 960 |
-
<div class="explain-card"><strong>Provide chemically reasonable candidates</strong>Prefer parent or neutralized structures. Upload a CSV with a <code>smiles</code> column for larger lists.</div>
|
| 961 |
-
<div class="explain-card"><strong>Use the output as a ranking signal</strong>Priority and relative score are the intended decision aids. Model score is not a calibrated success probability.</div>
|
| 962 |
-
</div>
|
| 963 |
-
<div class="footer-note" style="margin-top: 1rem;">
|
| 964 |
-
This Space is intended for assay-conditioned shortlist ranking. It is not a generative chemistry system and it does not replace confirmatory screening.
|
| 965 |
-
Export the ranked CSV if you want to continue downstream analysis in your own workflow.
|
| 966 |
-
</div>
|
| 967 |
-
</div>
|
| 968 |
-
"""
|
| 969 |
-
)
|
| 970 |
-
|
| 971 |
-
|
| 972 |
if __name__ == "__main__":
|
| 973 |
threading.Thread(target=_warm_model_background, daemon=True).start()
|
| 974 |
demo.queue(default_concurrency_limit=4).launch(
|
|
|
|
| 126 |
|
| 127 |
.metric-strip {
|
| 128 |
display: grid;
|
| 129 |
+
grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr);
|
| 130 |
gap: 0.8rem;
|
| 131 |
}
|
| 132 |
|
| 133 |
.metric-card {
|
| 134 |
border: 1px solid var(--line);
|
| 135 |
background: linear-gradient(180deg, rgba(255,255,255,0.92), rgba(248,244,236,0.9));
|
| 136 |
+
padding: 0.72rem 0.85rem;
|
| 137 |
border-radius: 18px;
|
| 138 |
+
min-height: 4.9rem;
|
| 139 |
box-shadow: 0 8px 24px rgba(23,37,45,0.04);
|
| 140 |
}
|
| 141 |
|
|
|
|
| 146 |
|
| 147 |
.metric-card strong {
|
| 148 |
display: block;
|
| 149 |
+
font-size: 1rem;
|
| 150 |
margin-top: 0.15rem;
|
| 151 |
color: var(--ink);
|
| 152 |
}
|
|
|
|
| 161 |
text-decoration: underline;
|
| 162 |
}
|
| 163 |
|
| 164 |
+
.compact-spec {
|
| 165 |
+
margin: 0.7rem 0 1rem 0;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 166 |
border: 1px solid var(--line);
|
|
|
|
|
|
|
| 167 |
border-radius: 18px;
|
| 168 |
+
background: rgba(255,255,255,0.82);
|
| 169 |
+
padding: 0.78rem 0.9rem;
|
| 170 |
+
color: var(--ink-soft);
|
| 171 |
+
font-size: 0.92rem;
|
| 172 |
+
line-height: 1.45;
|
|
|
|
| 173 |
}
|
| 174 |
|
| 175 |
.workspace {
|
|
|
|
| 238 |
|
| 239 |
.section-note {
|
| 240 |
color: var(--ink-soft);
|
| 241 |
+
font-size: 0.88rem;
|
| 242 |
+
margin: 0.1rem 0 0.32rem 0;
|
| 243 |
}
|
| 244 |
|
| 245 |
.action-row {
|
|
|
|
| 253 |
background: linear-gradient(180deg, rgba(255,255,255,0.94), rgba(248,244,236,0.92));
|
| 254 |
border: 1px solid var(--line);
|
| 255 |
border-radius: 24px;
|
| 256 |
+
padding: 0.9rem 1rem;
|
| 257 |
box-shadow: var(--shadow);
|
| 258 |
}
|
| 259 |
|
|
|
|
| 286 |
.results-callout h3 {
|
| 287 |
margin: 0 0 0.55rem 0;
|
| 288 |
font-family: "Fraunces", serif;
|
| 289 |
+
font-size: 1.1rem;
|
| 290 |
}
|
| 291 |
|
| 292 |
.results-callout ul {
|
|
|
|
| 304 |
line-height: 1.5;
|
| 305 |
}
|
| 306 |
|
| 307 |
+
.results-metrics {
|
| 308 |
display: grid;
|
| 309 |
grid-template-columns: repeat(3, minmax(0, 1fr));
|
| 310 |
+
gap: 0.65rem;
|
| 311 |
+
margin-top: 0.55rem;
|
| 312 |
}
|
| 313 |
|
| 314 |
+
.results-metric {
|
| 315 |
border: 1px solid var(--line);
|
| 316 |
+
border-radius: 14px;
|
| 317 |
+
background: rgba(255,255,255,0.9);
|
| 318 |
+
padding: 0.65rem 0.75rem;
|
| 319 |
}
|
| 320 |
|
| 321 |
+
.results-metric strong {
|
| 322 |
display: block;
|
| 323 |
+
margin-bottom: 0.16rem;
|
| 324 |
}
|
| 325 |
|
| 326 |
.examples-note {
|
|
|
|
| 442 |
.gradio-container .gradio-dataframe .wrap,
|
| 443 |
.gradio-container .gradio-dataframe .table-wrap,
|
| 444 |
.gradio-container .gradio-dataframe .scrollable,
|
| 445 |
+
.gradio-container .gradio-dataframe .table-container,
|
| 446 |
.gradio-container [data-testid="dataframe"],
|
| 447 |
.gradio-container [data-testid="dataframe"] .wrap,
|
| 448 |
.gradio-container [data-testid="dataframe"] .table-wrap,
|
| 449 |
+
.gradio-container [data-testid="dataframe"] .scrollable,
|
| 450 |
+
.result-frame,
|
| 451 |
+
.result-frame *,
|
| 452 |
+
.result-frame .wrap,
|
| 453 |
+
.result-frame .table-wrap,
|
| 454 |
+
.result-frame .table-container,
|
| 455 |
+
.result-frame .scrollable {
|
| 456 |
background: #fffdf7 !important;
|
| 457 |
border-color: var(--line) !important;
|
| 458 |
color: var(--ink) !important;
|
|
|
|
| 472 |
.gradio-container [data-testid="accordion"],
|
| 473 |
.gradio-container [data-testid="accordion"] * {
|
| 474 |
color: var(--ink) !important;
|
| 475 |
+
}
|
| 476 |
+
|
| 477 |
+
.result-accordion,
|
| 478 |
+
.result-accordion *,
|
| 479 |
+
.result-accordion [data-testid="accordion"],
|
| 480 |
+
.result-accordion [data-testid="accordion"] * {
|
| 481 |
+
color: var(--ink) !important;
|
| 482 |
+
background: #fffdf7 !important;
|
| 483 |
+
}
|
| 484 |
+
|
| 485 |
+
.result-file,
|
| 486 |
+
.result-file *,
|
| 487 |
+
.result-file .wrap,
|
| 488 |
+
.result-file .file-preview,
|
| 489 |
+
.result-file .file-preview-holder,
|
| 490 |
+
.result-file [data-testid="file"] {
|
| 491 |
+
background: #fffdf7 !important;
|
| 492 |
+
color: var(--ink) !important;
|
| 493 |
+
border-color: var(--line) !important;
|
| 494 |
+
}
|
| 495 |
+
|
| 496 |
+
.result-file button {
|
| 497 |
+
background: linear-gradient(180deg, #f6e7da, #f0ddcd) !important;
|
| 498 |
+
color: var(--ink) !important;
|
| 499 |
+
border: 1px solid #dbc4b4 !important;
|
| 500 |
+
}
|
| 501 |
+
|
| 502 |
+
.gradio-container .choices__list--dropdown,
|
| 503 |
+
.gradio-container .choices__list[aria-expanded],
|
| 504 |
+
.gradio-container .choices__list--single,
|
| 505 |
+
.gradio-container .choices__list--multiple {
|
| 506 |
+
background: #fffdf8 !important;
|
| 507 |
+
color: var(--ink) !important;
|
| 508 |
}
|
| 509 |
|
| 510 |
.gradio-container .gr-box,
|
|
|
|
| 516 |
@media (max-width: 980px) {
|
| 517 |
.hero-grid,
|
| 518 |
.helper-row,
|
| 519 |
+
.results-metrics,
|
|
|
|
| 520 |
.metric-strip {
|
| 521 |
grid-template-columns: 1fr;
|
| 522 |
}
|
|
|
|
| 683 |
|
| 684 |
def _build_summary(query_text: str, valid_rows: list[dict[str, Any]], invalid_rows: list[dict[str, Any]], warning: str | None) -> str:
|
| 685 |
best = valid_rows[0] if valid_rows else None
|
| 686 |
+
bullets = [f"<li><strong>Ranked:</strong> {len(valid_rows)}</li>"]
|
| 687 |
if invalid_rows:
|
| 688 |
+
bullets.append(f"<li><strong>Rejected:</strong> {len(invalid_rows)}</li>")
|
| 689 |
if best is not None:
|
| 690 |
bullets.append(
|
| 691 |
f"<li><strong>Top candidate:</strong> <code>{best['canonical_smiles']}</code> · {best['priority_band']} · relative score {best['relative_score']:.1f}/100</li>"
|
|
|
|
| 694 |
bullets.append(f"<li><strong>Warning:</strong> {warning}</li>")
|
| 695 |
return (
|
| 696 |
"<div class='results-callout'>"
|
| 697 |
+
"<h3>Ranking summary</h3>"
|
| 698 |
f"<ul>{''.join(bullets)}</ul>"
|
| 699 |
+
"<div class='results-metrics'>"
|
| 700 |
+
"<div class='results-metric'><strong>Priority</strong>Shortlist cue derived from the within-list ranking.</div>"
|
| 701 |
+
"<div class='results-metric'><strong>Relative score</strong>0–100 rescaling inside this submitted list only.</div>"
|
| 702 |
+
"<div class='results-metric'><strong>Model score</strong>Internal compatibility logit. Do not compare across unrelated runs.</div>"
|
| 703 |
+
"</div>"
|
| 704 |
"</div>"
|
| 705 |
)
|
| 706 |
|
|
|
|
| 846 |
<div class="eyebrow">BioAssayAlign · assay-conditioned compound ranking</div>
|
| 847 |
<div class="hero-title">Rank a compound list against one assay definition</div>
|
| 848 |
<div class="hero-copy">
|
| 849 |
+
Define one assay, submit a candidate molecule list, and obtain an assay-conditioned ranking for that specific list.
|
|
|
|
| 850 |
</div>
|
| 851 |
</div>
|
| 852 |
<div class="hero-side">
|
| 853 |
<div class="hero-side-title">Operational scope</div>
|
| 854 |
<ul class="hero-list">
|
| 855 |
+
<li>One assay at a time</li>
|
| 856 |
+
<li>Relative ranking within the submitted candidate set</li>
|
| 857 |
+
<li>Immediate rejection of malformed SMILES</li>
|
| 858 |
</ul>
|
| 859 |
</div>
|
| 860 |
</div>
|
|
|
|
| 867 |
gr.HTML(
|
| 868 |
f"""
|
| 869 |
<div class="metric-strip">
|
| 870 |
+
<div class="metric-card"><span>Model</span><strong><a href="https://huggingface.co/{MODEL_REPO_ID}" target="_blank" rel="noopener">{MODEL_REPO_ID}</a></strong></div>
|
| 871 |
+
<div class="metric-card"><span>Use</span><strong>One assay definition and up to {MAX_INPUT_SMILES} candidate SMILES. Interpret rank and relative score within the submitted list.</strong></div>
|
|
|
|
| 872 |
</div>
|
| 873 |
"""
|
| 874 |
)
|
| 875 |
+
gr.HTML("<div class='compact-spec'>You can write your own assay directly in the form below or load one of the live examples. Include target, readout, organism, and format when known. Paste one SMILES per line or upload a CSV with a <code>smiles</code> column.</div>")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 876 |
|
| 877 |
with gr.Tab("Rank Compounds"):
|
| 878 |
with gr.Row(elem_classes="workspace"):
|
|
|
|
| 885 |
<div class="pane-title">Define the assay context</div>
|
| 886 |
</div>
|
| 887 |
</div>
|
| 888 |
+
<div class="pane-copy">Edit the assay fields directly, or load one of the live examples and modify it.</div>
|
| 889 |
<div class="helper-row">
|
| 890 |
<div class="helper-chip"><strong>Describe the protocol signal</strong>State the readout, assay system, target biology, and measurement context.</div>
|
| 891 |
<div class="helper-chip"><strong>Use target identifiers</strong>UniProt IDs often improve separation between plausible and implausible candidates.</div>
|
|
|
|
| 894 |
"""
|
| 895 |
)
|
| 896 |
example_name = gr.Dropdown(choices=list(EXAMPLES.keys()), value=DEFAULT_EXAMPLE_NAME, label="Example assay")
|
| 897 |
+
gr.HTML("<div class='examples-note'>The form starts with a live JAK2 example. You can overwrite every field with your own assay or load another example.</div>")
|
| 898 |
+
load_example_btn = gr.Button("Load selected example or overwrite below with your own assay", variant="secondary")
|
| 899 |
assay_title = gr.Textbox(label="Assay title", value=DEFAULT_EXAMPLE["title"])
|
| 900 |
description = gr.Textbox(label="Description", value=DEFAULT_EXAMPLE["description"], lines=6, placeholder="Describe the assay in practical lab language.")
|
| 901 |
with gr.Row():
|
|
|
|
| 915 |
<div class="pane-title">Submit the candidate molecules</div>
|
| 916 |
</div>
|
| 917 |
</div>
|
| 918 |
+
<div class="pane-copy">The ranking is computed only within this submitted candidate set.</div>
|
| 919 |
"""
|
| 920 |
)
|
| 921 |
smiles_text = gr.Textbox(
|
|
|
|
| 931 |
run_btn = gr.Button("Run assay-conditioned ranking", variant="primary")
|
| 932 |
clear_btn = gr.ClearButton(value="Clear inputs", components=[assay_title, description, organism, readout, assay_format, assay_type, target_uniprot, smiles_text, upload_file])
|
| 933 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 934 |
summary = gr.HTML(elem_classes="results-shell")
|
| 935 |
+
with gr.Accordion("Serialized assay text used by the model", open=False, elem_classes="result-accordion"):
|
| 936 |
assay_preview = gr.Textbox(lines=12, label="Model-facing assay text")
|
| 937 |
gr.HTML("<div class='section-note'><strong>Ranked candidates</strong></div>")
|
| 938 |
+
ranked_df = gr.Dataframe(label=None, show_label=False, interactive=False, wrap=True, elem_classes="result-frame")
|
| 939 |
gr.HTML("<div class='section-note'><strong>Rejected inputs</strong></div>")
|
| 940 |
+
invalid_df = gr.Dataframe(label=None, show_label=False, interactive=False, wrap=True, elem_classes="result-frame")
|
| 941 |
+
download_file = gr.File(label="Export CSV", elem_classes="result-file")
|
| 942 |
|
| 943 |
load_example_btn.click(
|
| 944 |
load_example,
|
|
|
|
| 951 |
outputs=[summary, assay_preview, ranked_df, invalid_df, download_file],
|
| 952 |
)
|
| 953 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 954 |
if __name__ == "__main__":
|
| 955 |
threading.Thread(target=_warm_model_background, daemon=True).start()
|
| 956 |
demo.queue(default_concurrency_limit=4).launch(
|