m0ksh commited on
Commit
e118453
·
verified ·
1 Parent(s): 6c0e330

Sync from GitHub (preserve manual model files)

Browse files
StreamlitApp/StreamlitApp.py CHANGED
@@ -24,6 +24,11 @@ from utils.ui_helpers import (
24
  sequence_health_label,
25
  build_analysis_summary_text,
26
  )
 
 
 
 
 
27
 
28
  try:
29
  import pyperclip # Optional; may not exist in all environments.
@@ -401,6 +406,36 @@ elif page == "Analyze":
401
  ax.legend(loc='lower center', bbox_to_anchor=(0.85, 1.15), ncol=2, fontsize=7)
402
  st.pyplot(fig, use_container_width=False)
403
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
404
  st.divider()
405
  # Analysis Summary
406
  st.subheader("Analysis Summary")
@@ -481,6 +516,17 @@ elif page == "Optimize":
481
  f"**Optimized Sequence:** {improved_seq} — Confidence: {round(improved_conf*100,1)}%"
482
  )
483
 
 
 
 
 
 
 
 
 
 
 
 
484
  # Optimization Summary box
485
  summary = optimization_summary(orig_seq, orig_conf, improved_seq, improved_conf)
486
  delta_str = f"{summary['delta_conf_pct']:+.2f}%"
 
24
  sequence_health_label,
25
  build_analysis_summary_text,
26
  )
27
+ from utils.peptide_extras import (
28
+ find_most_similar,
29
+ build_importance_map_html,
30
+ render_3d_structure,
31
+ )
32
 
33
  try:
34
  import pyperclip # Optional; may not exist in all environments.
 
406
  ax.legend(loc='lower center', bbox_to_anchor=(0.85, 1.15), ncol=2, fontsize=7)
407
  st.pyplot(fig, use_container_width=False)
408
 
409
+ st.divider()
410
+ sequence = st.session_state.analyze_input or ""
411
+ if sequence.strip():
412
+ st.subheader("Functional Region Highlighting")
413
+ st.caption(
414
+ "Highlighted regions indicate residues likely contributing to antimicrobial activity"
415
+ )
416
+ st.markdown(build_importance_map_html(sequence), unsafe_allow_html=True)
417
+
418
+ st.subheader("Most Similar Known AMP")
419
+ match_seq, sim_score = find_most_similar(sequence)
420
+ if match_seq is not None:
421
+ st.write(f"Sequence: **{match_seq}**")
422
+ st.write(f"Similarity Score: **{sim_score:.2f}**")
423
+ if sim_score > 0.6:
424
+ st.success("High similarity to known AMP")
425
+ elif sim_score > 0.3:
426
+ st.warning("Moderate similarity")
427
+ else:
428
+ st.error("Low similarity")
429
+
430
+ st.subheader("3D Structural Approximation")
431
+ st.caption(
432
+ "This is a structural approximation to visualize residue distribution (not an experimental structure)."
433
+ )
434
+ if not render_3d_structure(sequence):
435
+ st.info(
436
+ "3D view unavailable (install **py3dmol** in your environment, or try again after redeploy)."
437
+ )
438
+
439
  st.divider()
440
  # Analysis Summary
441
  st.subheader("Analysis Summary")
 
516
  f"**Optimized Sequence:** {improved_seq} — Confidence: {round(improved_conf*100,1)}%"
517
  )
518
 
519
+ if improved_seq and str(improved_seq).strip():
520
+ st.divider()
521
+ st.subheader("3D Structural Approximation (optimized sequence)")
522
+ st.caption(
523
+ "Helix-like CA trace approximation for the optimized sequence (not an experimental structure)."
524
+ )
525
+ if not render_3d_structure(improved_seq):
526
+ st.info(
527
+ "3D view unavailable (install **py3dmol** in your environment, or try again after redeploy)."
528
+ )
529
+
530
  # Optimization Summary box
531
  summary = optimization_summary(orig_seq, orig_conf, improved_seq, improved_conf)
532
  delta_str = f"{summary['delta_conf_pct']:+.2f}%"
StreamlitApp/utils/peptide_extras.py ADDED
@@ -0,0 +1,156 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Optional peptide UI helpers: 3D approximation (py3Dmol), known-AMP similarity, residue highlighting.
3
+
4
+ Does not modify model loading or prediction logic.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ import math
9
+ from typing import List, Optional, Tuple
10
+
11
+ # Small reference set of known AMP sequences (for similarity display only).
12
+ KNOWN_AMPS: List[str] = [
13
+ "KWKLFKKIGAVLKVL",
14
+ "GIGKFLHSAKKFGKAFVGEIMNS",
15
+ "LLGDFFRKSKEKIGKEFKRIVQRIKDFLRNLV",
16
+ "KLFKKILKYL",
17
+ "FLPLLAGLAANFLPKIFCKITRKC",
18
+ ]
19
+
20
+ # One-letter -> three-letter (for minimal PDB lines for py3Dmol).
21
+ _ONE_TO_THREE = {
22
+ "A": "ALA",
23
+ "R": "ARG",
24
+ "N": "ASN",
25
+ "D": "ASP",
26
+ "C": "CYS",
27
+ "Q": "GLN",
28
+ "E": "GLU",
29
+ "G": "GLY",
30
+ "H": "HIS",
31
+ "I": "ILE",
32
+ "L": "LEU",
33
+ "K": "LYS",
34
+ "M": "MET",
35
+ "F": "PHE",
36
+ "P": "PRO",
37
+ "S": "SER",
38
+ "T": "THR",
39
+ "W": "TRP",
40
+ "Y": "TYR",
41
+ "V": "VAL",
42
+ }
43
+
44
+
45
+ def sequence_similarity(seq1: str, seq2: str) -> float:
46
+ """Position-wise match rate normalized by max length (as specified)."""
47
+ if not seq1 or not seq2:
48
+ return 0.0
49
+ matches = sum(1 for a, b in zip(seq1, seq2) if a == b)
50
+ return matches / max(len(seq1), len(seq2))
51
+
52
+
53
+ def find_most_similar(sequence: str) -> Tuple[Optional[str], float]:
54
+ if not sequence or not KNOWN_AMPS:
55
+ return None, 0.0
56
+ best_seq = KNOWN_AMPS[0]
57
+ best_score = sequence_similarity(sequence, KNOWN_AMPS[0])
58
+ for amp in KNOWN_AMPS[1:]:
59
+ score = sequence_similarity(sequence, amp)
60
+ if score > best_score:
61
+ best_score = score
62
+ best_seq = amp
63
+ return best_seq, best_score
64
+
65
+
66
+ def get_residue_color(aa: str) -> str:
67
+ positive = ["K", "R", "H"]
68
+ negative = ["D", "E"]
69
+ hydrophobic = ["A", "V", "I", "L", "M", "F", "W", "Y"]
70
+ if aa in positive:
71
+ return "blue"
72
+ if aa in negative:
73
+ return "red"
74
+ if aa in hydrophobic:
75
+ return "green"
76
+ return "gray"
77
+
78
+
79
+ def get_residue_style(aa: str) -> str:
80
+ positive = ["K", "R", "H"]
81
+ negative = ["D", "E"]
82
+ hydrophobic = ["A", "V", "I", "L", "M", "F", "W", "Y"]
83
+ if aa in positive:
84
+ return "background-color: blue; color: white; padding: 2px;"
85
+ if aa in negative:
86
+ return "background-color: red; color: white; padding: 2px;"
87
+ if aa in hydrophobic:
88
+ return "background-color: green; color: white; padding: 2px;"
89
+ return "background-color: lightgray; padding: 2px;"
90
+
91
+
92
+ def build_importance_map_html(sequence: str) -> str:
93
+ """Build HTML for residue importance highlighting (escape non-AA safely)."""
94
+ import html as html_mod
95
+
96
+ parts: List[str] = []
97
+ for ch in sequence:
98
+ if ch.isspace():
99
+ continue
100
+ aa = ch.upper()
101
+ style = get_residue_style(aa)
102
+ parts.append(f'<span style="{style}">{html_mod.escape(aa)}</span>')
103
+ return "".join(parts)
104
+
105
+
106
+ def generate_helix_pdb(sequence: str) -> str:
107
+ """Generate a minimal PDB string (helix-like CA trace) for visualization."""
108
+ pdb_lines: List[str] = []
109
+ atom_index = 1
110
+ clean = "".join(c for c in sequence.upper() if not c.isspace())
111
+ for i, aa in enumerate(clean):
112
+ res_name = _ONE_TO_THREE.get(aa, "UNK")
113
+ angle = i * 100 * (math.pi / 180.0)
114
+ x = math.cos(angle) * 5.0
115
+ y = math.sin(angle) * 5.0
116
+ z = i * 1.5
117
+ res_num = i + 1
118
+ pdb_lines.append(
119
+ f"ATOM {atom_index:5d} CA {res_name:3s} A{res_num:4d} "
120
+ f"{x:8.3f}{y:8.3f}{z:8.3f} 1.00 0.00 C"
121
+ )
122
+ atom_index += 1
123
+ return "\n".join(pdb_lines)
124
+
125
+
126
+ def render_3d_structure(sequence: str, height: int = 400, width: int = 400) -> bool:
127
+ """
128
+ Render py3Dmol viewer via Streamlit components. Returns True if rendered.
129
+ """
130
+ import streamlit.components.v1 as components
131
+
132
+ clean = "".join(c for c in (sequence or "").upper() if not c.isspace())
133
+ if not clean:
134
+ return False
135
+ try:
136
+ import py3Dmol # type: ignore
137
+ except Exception:
138
+ return False
139
+
140
+ try:
141
+ pdb_data = generate_helix_pdb(clean)
142
+ view = py3Dmol.view(width=width, height=height)
143
+ view.addModel(pdb_data, "pdb")
144
+ for i, aa in enumerate(clean):
145
+ color = get_residue_color(aa)
146
+ # py3Dmol residue index is 1-based in this PDB
147
+ view.setStyle({"resi": i + 1}, {"sphere": {"color": color}})
148
+ view.zoomTo()
149
+ if hasattr(view, "_make_html"):
150
+ html = view._make_html()
151
+ else:
152
+ html = view.write()
153
+ components.html(html, height=height)
154
+ return True
155
+ except Exception:
156
+ return False
requirements.txt CHANGED
@@ -5,4 +5,5 @@ torch
5
  scikit-learn
6
  matplotlib
7
  plotly
8
- requests
 
 
5
  scikit-learn
6
  matplotlib
7
  plotly
8
+ requests
9
+ py3dmol