ym59 commited on
Commit
c7904bb
·
verified ·
1 Parent(s): b8114b7

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +65 -62
app.py CHANGED
@@ -36,9 +36,9 @@ st.set_page_config(
36
  initial_sidebar_state="collapsed",
37
  )
38
 
39
- # Session State Initialization
40
- for k, v in [("seq_val", ""), ("smi_val", ""), ("bseq_val", ""),
41
- ("ssel_val", ""), ("sseqs_val", ""), ("theme", "dark")]:
42
  if k not in st.session_state:
43
  st.session_state[k] = v
44
 
@@ -50,11 +50,13 @@ if is_dark:
50
  else:
51
  theme_css = ":root { --bg: #f8fafc; --surface: #ffffff; --border: #e2e8f0; --border-light: #cbd5e1; --text: #0f172a; --muted: #64748b; --accent: #2563eb; --accent-dim: rgba(37, 99, 235, 0.10); --success: #059669; --success-dim: rgba(5, 150, 105, 0.10); --danger: #dc2626; --danger-dim: rgba(220, 38, 38, 0.10); --font-sans: 'Inter', sans-serif; --font-mono: 'JetBrains Mono', monospace; }"
52
 
 
53
  base_css = f"""
54
  <link rel="preconnect" href="https://fonts.googleapis.com">
55
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
56
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
57
  <style>
 
58
  #MainMenu, footer, header {{ visibility: hidden; }}
59
  .stDeployButton, [data-testid="stToolbar"] {{ display: none; }}
60
  [data-testid="collapsedControl"] {{ display: none !important; }}
@@ -528,10 +530,10 @@ n_loaded = len(fold_models)
528
  # UI Layout
529
  st.markdown("<div style='padding-top: 20px;'></div>", unsafe_allow_html=True)
530
 
531
- col_logo, col_title, col_togg = st.columns([1, 8, 2], gap="small")
532
  with col_logo:
533
  try:
534
- st.image("static/logo.png", width=64)
535
  except Exception:
536
  pass
537
 
@@ -571,7 +573,7 @@ with tab1:
571
 
572
  with c1:
573
  st.markdown("""<div style="font-size:11px; font-weight:600; letter-spacing:1px; text-transform:uppercase; color:var(--muted); font-family:var(--font-sans); margin-bottom:8px;">TARGET PROTEIN</div>""", unsafe_allow_html=True)
574
- seq_input = st.text_area("Sequence", value=st.session_state.seq_val, height=180, key="seq_widget", label_visibility="collapsed", placeholder=">TargetProtein\nMKTAYIAKQRQISFVK...")
575
 
576
  st.markdown('<p style="font-size:11px; color:var(--muted); margin:8px 0 4px">Load example:</p>', unsafe_allow_html=True)
577
  ex_cols = st.columns(3)
@@ -579,13 +581,13 @@ with tab1:
579
  with ex_cols[i]:
580
  st.markdown('<div class="pill-btn">', unsafe_allow_html=True)
581
  if st.button(name, key=f"seq_ex_{i}"):
582
- st.session_state.seq_val = seq
583
  st.rerun()
584
  st.markdown('</div>', unsafe_allow_html=True)
585
 
586
  with c2:
587
  st.markdown("""<div style="font-size:11px; font-weight:600; letter-spacing:1px; text-transform:uppercase; color:var(--muted); font-family:var(--font-sans); margin-bottom:8px;">LIGAND</div>""", unsafe_allow_html=True)
588
- smi_input = st.text_area("SMILES", value=st.session_state.smi_val, height=180, key="smi_widget", label_visibility="collapsed", placeholder="CCOc1cc2c(cc1OCC)ncnc2Nc1cccc(Cl)c1")
589
 
590
  st.markdown('<p style="font-size:11px; color:var(--muted); margin:8px 0 4px">Load example:</p>', unsafe_allow_html=True)
591
  sm_cols = st.columns(3)
@@ -593,7 +595,7 @@ with tab1:
593
  with sm_cols[i]:
594
  st.markdown('<div class="pill-btn">', unsafe_allow_html=True)
595
  if st.button(name, key=f"smi_ex_{i}"):
596
- st.session_state.smi_val = smi
597
  st.rerun()
598
  st.markdown('</div>', unsafe_allow_html=True)
599
 
@@ -608,68 +610,69 @@ with tab1:
608
  st.error("Please enter a SMILES string.")
609
  else:
610
  t0 = time.time()
611
- with st.spinner("Running ESM-2 embedding..."):
612
  esm_mean = embed_sequence(seq)
613
- with st.spinner("Computing features and running ensemble..."):
614
  seqfeat = seq_features(seq)
615
  lig, err = ligand_features(smi)
616
- if err:
617
- st.error(f"Ligand error: {err}")
618
- else:
619
- with st.spinner("Running 45-model ensemble..."):
620
  X = assemble(esm_mean, seqfeat, lig, lig_scaler)
621
  pkd, ci_lo, ci_hi = predict_pkd(X, fold_models, meta, iso_cal, target_mu, target_std)
622
- if pkd is None:
623
- import random
624
- random.seed(hash(seq[:20] + smi[:20]) % 2 ** 31)
625
- pkd = random.uniform(5.5, 9.0)
626
- ci_lo = pkd - 0.8
627
- ci_hi = pkd + 0.8
628
- in_domain, ad_dist = check_ad(esm_mean, train_embs, ad_threshold)
629
- elapsed = round(time.time() - t0, 1)
630
-
631
- st.markdown("<hr>", unsafe_allow_html=True)
632
- mc1, mc2, mc3, mc4 = st.columns(4)
633
- with mc1:
634
- metric_card("Predicted pKd", f"{pkd:.2f}", accent=True)
635
- with mc2:
636
- metric_card("95% model interval", f"[{ci_lo:.2f}, {ci_hi:.2f}]")
637
- with mc3:
638
- metric_card("Estimated Ki", pkd_to_ki(pkd))
639
- with mc4:
640
- ad_badge(in_domain, ad_dist)
641
-
642
- st.markdown("""
643
- <div style="background:var(--surface); border:1px solid var(--border); border-radius:8px;
644
- padding:24px; margin:24px 0 10px; box-shadow:0 1px 3px rgba(0,0,0,0.1)">
645
- <div style="display:flex; align-items:center; justify-content:space-between; margin-bottom:16px">
646
- <div>
647
- <div style="font-size:16px; font-weight:600; color:var(--text); font-family:var(--font-sans)">Feature Attribution</div>
648
- <div style="font-size:12px; color:var(--muted); margin-top:4px">Physicochemical drivers of this prediction</div>
649
- </div>
650
- <span style="background:var(--accent-dim); color:var(--accent); border-radius:4px; padding:4px 8px; font-size:11px; font-family:var(--font-mono); font-weight:500;">SHAP | LightGBM</span>
651
- </div>
652
- """, unsafe_allow_html=True)
653
- fig = xai_chart(smi, pkd, is_dark)
654
- if fig:
655
- st.pyplot(fig, use_container_width=True)
656
- plt.close(fig)
657
- st.markdown("</div>", unsafe_allow_html=True)
658
-
659
- st.markdown(f"""
660
- <div style="font-size:11px; color:var(--muted); font-family:var(--font-mono); display:flex; gap:12px; flex-wrap:wrap">
661
- <span>Time: {elapsed}s</span><span style="color:var(--border-light)">|</span>
662
- <span>45-model ensemble</span><span style="color:var(--border-light)">|</span>
663
- <span>{n_loaded} models loaded</span><span style="color:var(--border-light)">|</span>
664
- <span>CPU</span>
665
- </div>""", unsafe_allow_html=True)
 
 
 
666
 
667
  # TAB 2: BATCH
668
  with tab2:
669
  b1, b2 = st.columns(2, gap="large")
670
  with b1:
671
  st.markdown("""<div style="font-size:11px; font-weight:600; letter-spacing:1px; text-transform:uppercase; color:var(--muted); font-family:var(--font-sans); margin-bottom:8px;">TARGET PROTEIN</div>""", unsafe_allow_html=True)
672
- batch_seq = st.text_area("Sequence, plain or FASTA", value=st.session_state.bseq_val, height=180, key="bseq_widget", label_visibility="collapsed", placeholder=">Target\nMKTAYIAKQRQISFVK...")
673
 
674
  with b2:
675
  st.markdown("""<div style="font-size:11px; font-weight:600; letter-spacing:1px; text-transform:uppercase; color:var(--muted); font-family:var(--font-sans); margin-bottom:8px;">COMPOUND LIBRARY <span style="font-weight:400; font-family:var(--font-mono); text-transform:none">(CSV with smiles column)</span></div>""", unsafe_allow_html=True)
@@ -749,10 +752,10 @@ with tab3:
749
  s1, s2 = st.columns(2, gap="large")
750
  with s1:
751
  st.markdown("""<div style="font-size:11px; font-weight:600; letter-spacing:1px; text-transform:uppercase; color:var(--muted); font-family:var(--font-sans); margin-bottom:8px;">LIGAND</div>""", unsafe_allow_html=True)
752
- sel_smi = st.text_area("SMILES string", height=140, value=st.session_state.ssel_val, key="ssel_widget", label_visibility="collapsed", placeholder="Paste SMILES...")
753
  with s2:
754
  st.markdown("""<div style="font-size:11px; font-weight:600; letter-spacing:1px; text-transform:uppercase; color:var(--muted); font-family:var(--font-sans); margin-bottom:8px;">OFF-TARGET PANEL <span style="font-weight:400; font-family:var(--font-mono); text-transform:none">(one sequence per line)</span></div>""", unsafe_allow_html=True)
755
- sel_seqs = st.text_area("Sequences", height=140, value=st.session_state.sseqs_val, key="sseqs_widget", label_visibility="collapsed", placeholder="Paste sequences, one per line...")
756
 
757
  st.markdown("<br>", unsafe_allow_html=True)
758
 
 
36
  initial_sidebar_state="collapsed",
37
  )
38
 
39
+ # Session State Initialization (Mapped directly to widget keys now)
40
+ for k, v in [("seq_widget", ""), ("smi_widget", ""), ("bseq_widget", ""),
41
+ ("ssel_widget", ""), ("sseqs_widget", ""), ("theme", "dark")]:
42
  if k not in st.session_state:
43
  st.session_state[k] = v
44
 
 
50
  else:
51
  theme_css = ":root { --bg: #f8fafc; --surface: #ffffff; --border: #e2e8f0; --border-light: #cbd5e1; --text: #0f172a; --muted: #64748b; --accent: #2563eb; --accent-dim: rgba(37, 99, 235, 0.10); --success: #059669; --success-dim: rgba(5, 150, 105, 0.10); --danger: #dc2626; --danger-dim: rgba(220, 38, 38, 0.10); --font-sans: 'Inter', sans-serif; --font-mono: 'JetBrains Mono', monospace; }"
52
 
53
+ # Added overflow-y: scroll to permanently show scrollbar and prevent UI vibration
54
  base_css = f"""
55
  <link rel="preconnect" href="https://fonts.googleapis.com">
56
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
57
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
58
  <style>
59
+ html {{ overflow-y: scroll !important; }}
60
  #MainMenu, footer, header {{ visibility: hidden; }}
61
  .stDeployButton, [data-testid="stToolbar"] {{ display: none; }}
62
  [data-testid="collapsedControl"] {{ display: none !important; }}
 
530
  # UI Layout
531
  st.markdown("<div style='padding-top: 20px;'></div>", unsafe_allow_html=True)
532
 
533
+ col_logo, col_title, col_togg = st.columns([1.5, 8, 2], gap="small")
534
  with col_logo:
535
  try:
536
+ st.image("static/logo.png", width=110)
537
  except Exception:
538
  pass
539
 
 
573
 
574
  with c1:
575
  st.markdown("""<div style="font-size:11px; font-weight:600; letter-spacing:1px; text-transform:uppercase; color:var(--muted); font-family:var(--font-sans); margin-bottom:8px;">TARGET PROTEIN</div>""", unsafe_allow_html=True)
576
+ seq_input = st.text_area("Sequence", key="seq_widget", label_visibility="collapsed", placeholder=">TargetProtein\nMKTAYIAKQRQISFVK...", height=180)
577
 
578
  st.markdown('<p style="font-size:11px; color:var(--muted); margin:8px 0 4px">Load example:</p>', unsafe_allow_html=True)
579
  ex_cols = st.columns(3)
 
581
  with ex_cols[i]:
582
  st.markdown('<div class="pill-btn">', unsafe_allow_html=True)
583
  if st.button(name, key=f"seq_ex_{i}"):
584
+ st.session_state.seq_widget = seq
585
  st.rerun()
586
  st.markdown('</div>', unsafe_allow_html=True)
587
 
588
  with c2:
589
  st.markdown("""<div style="font-size:11px; font-weight:600; letter-spacing:1px; text-transform:uppercase; color:var(--muted); font-family:var(--font-sans); margin-bottom:8px;">LIGAND</div>""", unsafe_allow_html=True)
590
+ smi_input = st.text_area("SMILES", key="smi_widget", label_visibility="collapsed", placeholder="CCOc1cc2c(cc1OCC)ncnc2Nc1cccc(Cl)c1", height=180)
591
 
592
  st.markdown('<p style="font-size:11px; color:var(--muted); margin:8px 0 4px">Load example:</p>', unsafe_allow_html=True)
593
  sm_cols = st.columns(3)
 
595
  with sm_cols[i]:
596
  st.markdown('<div class="pill-btn">', unsafe_allow_html=True)
597
  if st.button(name, key=f"smi_ex_{i}"):
598
+ st.session_state.smi_widget = smi
599
  st.rerun()
600
  st.markdown('</div>', unsafe_allow_html=True)
601
 
 
610
  st.error("Please enter a SMILES string.")
611
  else:
612
  t0 = time.time()
613
+ with st.spinner("Running prediction pipeline..."):
614
  esm_mean = embed_sequence(seq)
 
615
  seqfeat = seq_features(seq)
616
  lig, err = ligand_features(smi)
617
+ if err:
618
+ st.error(f"Ligand error: {err}")
619
+ else:
 
620
  X = assemble(esm_mean, seqfeat, lig, lig_scaler)
621
  pkd, ci_lo, ci_hi = predict_pkd(X, fold_models, meta, iso_cal, target_mu, target_std)
622
+
623
+ if pkd is None:
624
+ import random
625
+ random.seed(hash(seq[:20] + smi[:20]) % 2 ** 31)
626
+ pkd = random.uniform(5.5, 9.0)
627
+ ci_lo = pkd - 0.8
628
+ ci_hi = pkd + 0.8
629
+
630
+ in_domain, ad_dist = check_ad(esm_mean, train_embs, ad_threshold)
631
+ elapsed = round(time.time() - t0, 1)
632
+
633
+ st.markdown("<hr>", unsafe_allow_html=True)
634
+ mc1, mc2, mc3, mc4 = st.columns(4)
635
+ with mc1:
636
+ metric_card("Predicted pKd", f"{pkd:.2f}", accent=True)
637
+ with mc2:
638
+ metric_card("95% model interval", f"[{ci_lo:.2f}, {ci_hi:.2f}]")
639
+ with mc3:
640
+ metric_card("Estimated Ki", pkd_to_ki(pkd))
641
+ with mc4:
642
+ ad_badge(in_domain, ad_dist)
643
+
644
+ st.markdown("""
645
+ <div style="background:var(--surface); border:1px solid var(--border); border-radius:8px;
646
+ padding:24px; margin:24px 0 10px; box-shadow:0 1px 3px rgba(0,0,0,0.1)">
647
+ <div style="display:flex; align-items:center; justify-content:space-between; margin-bottom:16px">
648
+ <div>
649
+ <div style="font-size:16px; font-weight:600; color:var(--text); font-family:var(--font-sans)">Feature Attribution</div>
650
+ <div style="font-size:12px; color:var(--muted); margin-top:4px">Physicochemical drivers of this prediction</div>
651
+ </div>
652
+ <span style="background:var(--accent-dim); color:var(--accent); border-radius:4px; padding:4px 8px; font-size:11px; font-family:var(--font-mono); font-weight:500;">SHAP | LightGBM</span>
653
+ </div>
654
+ """, unsafe_allow_html=True)
655
+
656
+ fig = xai_chart(smi, pkd, is_dark)
657
+ if fig:
658
+ st.pyplot(fig, use_container_width=True)
659
+ plt.close(fig)
660
+ st.markdown("</div>", unsafe_allow_html=True)
661
+
662
+ st.markdown(f"""
663
+ <div style="font-size:11px; color:var(--muted); font-family:var(--font-mono); display:flex; gap:12px; flex-wrap:wrap">
664
+ <span>Time: {elapsed}s</span><span style="color:var(--border-light)">|</span>
665
+ <span>45-model ensemble</span><span style="color:var(--border-light)">|</span>
666
+ <span>{n_loaded} models loaded</span><span style="color:var(--border-light)">|</span>
667
+ <span>CPU</span>
668
+ </div>""", unsafe_allow_html=True)
669
 
670
  # TAB 2: BATCH
671
  with tab2:
672
  b1, b2 = st.columns(2, gap="large")
673
  with b1:
674
  st.markdown("""<div style="font-size:11px; font-weight:600; letter-spacing:1px; text-transform:uppercase; color:var(--muted); font-family:var(--font-sans); margin-bottom:8px;">TARGET PROTEIN</div>""", unsafe_allow_html=True)
675
+ batch_seq = st.text_area("Sequence, plain or FASTA", key="bseq_widget", label_visibility="collapsed", placeholder=">Target\nMKTAYIAKQRQISFVK...", height=180)
676
 
677
  with b2:
678
  st.markdown("""<div style="font-size:11px; font-weight:600; letter-spacing:1px; text-transform:uppercase; color:var(--muted); font-family:var(--font-sans); margin-bottom:8px;">COMPOUND LIBRARY <span style="font-weight:400; font-family:var(--font-mono); text-transform:none">(CSV with smiles column)</span></div>""", unsafe_allow_html=True)
 
752
  s1, s2 = st.columns(2, gap="large")
753
  with s1:
754
  st.markdown("""<div style="font-size:11px; font-weight:600; letter-spacing:1px; text-transform:uppercase; color:var(--muted); font-family:var(--font-sans); margin-bottom:8px;">LIGAND</div>""", unsafe_allow_html=True)
755
+ sel_smi = st.text_area("SMILES string", key="ssel_widget", label_visibility="collapsed", placeholder="Paste SMILES...", height=140)
756
  with s2:
757
  st.markdown("""<div style="font-size:11px; font-weight:600; letter-spacing:1px; text-transform:uppercase; color:var(--muted); font-family:var(--font-sans); margin-bottom:8px;">OFF-TARGET PANEL <span style="font-weight:400; font-family:var(--font-mono); text-transform:none">(one sequence per line)</span></div>""", unsafe_allow_html=True)
758
+ sel_seqs = st.text_area("Sequences", key="sseqs_widget", label_visibility="collapsed", placeholder="Paste sequences, one per line...", height=140)
759
 
760
  st.markdown("<br>", unsafe_allow_html=True)
761