m0ksh commited on
Commit
ea9947a
·
verified ·
1 Parent(s): 4da8903

Sync from GitHub (preserve manual model files)

Browse files
StreamlitApp/StreamlitApp.py CHANGED
@@ -156,30 +156,6 @@ if st.sidebar.button("Clear All Fields"):
156
  st.stop()
157
 
158
 
159
- def _prefill_peptide_viz_sequence() -> str:
160
- """Best-effort sequence for Visualize Peptide: optimized result → optimize input → analyze → Predict top."""
161
- out = st.session_state.get("optimize_output")
162
- if out and len(out) >= 3:
163
- improved = str(out[2] or "").strip()
164
- if improved:
165
- return improved
166
- oi = (st.session_state.get("optimize_input") or "").strip()
167
- if oi:
168
- return oi
169
- ai = (st.session_state.get("analyze_input") or "").strip()
170
- if ai:
171
- return ai
172
- preds = st.session_state.get("predictions") or []
173
- top = choose_top_candidate(preds)
174
- if top and top.get("Sequence"):
175
- return str(top["Sequence"]).strip()
176
- pi = st.session_state.get("predict_input_widget", "") or ""
177
- lines = [ln.strip() for ln in pi.splitlines() if ln.strip()]
178
- if lines:
179
- return lines[0]
180
- return ""
181
-
182
-
183
  # Load model once
184
  model = load_model()
185
 
@@ -460,6 +436,29 @@ elif page == "Analyze":
460
  ax.legend(loc='lower center', bbox_to_anchor=(0.85, 1.15), ncol=2, fontsize=7)
461
  st.pyplot(fig, use_container_width=False)
462
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
463
  st.divider()
464
  # Analysis Summary
465
  st.subheader("Analysis Summary")
@@ -611,25 +610,19 @@ elif page == "Visualize Peptide":
611
  "and functional residue map."
612
  )
613
 
614
- if not (st.session_state.get("visualize_peptide_input") or "").strip():
615
- sug = _prefill_peptide_viz_sequence()
616
- if sug:
617
- st.session_state.visualize_peptide_input = sug
618
-
619
  st.checkbox("Auto-run when sequence changes", value=False, key="viz_peptide_auto_run")
620
 
621
  st.text_input(
622
  "Peptide sequence",
623
  key="visualize_peptide_input",
624
- placeholder="One-letter amino-acid sequence",
625
- help="When empty, your last optimized / analyzed / predicted sequence is filled in automatically.",
626
  )
627
 
628
  seq_viz = (st.session_state.get("visualize_peptide_input") or "").strip()
629
  clean_viz = "".join(c for c in seq_viz.upper() if not c.isspace())
630
  if not clean_viz:
631
  st.session_state.viz_peptide_last_computed = ""
632
- st.info("Enter a sequence above (or navigate from Predict / Analyze / Optimize to auto-fill when empty).")
633
  else:
634
  run_viz = st.button("Run visualization", type="primary", key="viz_peptide_run_btn")
635
  auto_on = bool(st.session_state.get("viz_peptide_auto_run"))
@@ -641,7 +634,7 @@ elif page == "Visualize Peptide":
641
  need_compute = True
642
 
643
  if need_compute:
644
- with st.spinner("Building 3D view, helical wheel, and similarity search..."):
645
  st.session_state.viz_peptide_last_computed = clean_viz
646
 
647
  if clean_viz == st.session_state.get("viz_peptide_last_computed"):
@@ -669,7 +662,9 @@ elif page == "Visualize Peptide":
669
 
670
  with col_r:
671
  st.subheader("Helical wheel")
672
- st.caption("Circular α-helix projection: hydrophobic vs charged faces (approximate).")
 
 
673
  fig_wheel = plot_helical_wheel(clean_viz)
674
  st.pyplot(fig_wheel, use_container_width=True)
675
  plt.close(fig_wheel)
@@ -683,23 +678,6 @@ elif page == "Visualize Peptide":
683
  with st.expander("Map · legend", expanded=False):
684
  st.markdown(COMPACT_MAP_LEGEND)
685
 
686
- st.subheader("Most similar known AMP")
687
- st.caption(
688
- f"Compared to **{len(KNOWN_AMPS)}** unique AMP sequences (label = 1 in `Data/ampData.csv`)."
689
- )
690
- match_seq, sim_score = find_most_similar(clean_viz)
691
- if match_seq is not None:
692
- st.write(f"**Best match:** `{match_seq}`")
693
- st.write(f"**Similarity score:** **{sim_score:.3f}** (position match / max length)")
694
- if sim_score > 0.6:
695
- st.success("High similarity to a known AMP in the reference set.")
696
- elif sim_score > 0.3:
697
- st.warning("Moderate similarity — interpret with care.")
698
- else:
699
- st.error("Low similarity — sequence is distant from reference AMPs.")
700
- else:
701
- st.warning("Could not compute similarity (empty sequence after cleaning).")
702
-
703
  elif clean_viz != (st.session_state.get("viz_peptide_last_computed") or ""):
704
  st.caption("Click **Run visualization** or enable **Auto-run** to update the figures.")
705
 
@@ -806,9 +784,9 @@ PeptideAI is a lightweight Streamlit app for exploring antimicrobial peptide (AM
806
 
807
  It uses a trained neural network to estimate whether a peptide is likely to be antimicrobial, then helps you interpret and improve candidates:
808
  - **Predict**: batch predictions from multi-line or FASTA input, length warnings, persisted results, top-candidate highlight, and CSV export.
809
- - **Analyze**: single-sequence numerical & textual analysis — AMP prediction, composition, physicochemical table + radar, and exportable report (no 3D on this page).
810
  - **Optimize**: guided sequence optimization with mutation heatmap, step table, and confidence vs. step plot.
811
- - **Visualize Peptide**: one sequence with consistent coloring across functional map, helical wheel, and optional 3D approximation, plus similarity to known AMPs.
812
  - **Visualize t-SNE**: upload many sequences, embed with the model, run t-SNE, and explore clusters with filters and hover metadata.
813
  - **About**: this overview and disclaimer.
814
 
 
156
  st.stop()
157
 
158
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
159
  # Load model once
160
  model = load_model()
161
 
 
436
  ax.legend(loc='lower center', bbox_to_anchor=(0.85, 1.15), ncol=2, fontsize=7)
437
  st.pyplot(fig, use_container_width=False)
438
 
439
+ st.divider()
440
+ st.subheader("Most similar known AMP")
441
+ st.caption(
442
+ f"Compared to **{len(KNOWN_AMPS)}** unique AMP sequences (label = 1 in `Data/ampData.csv`)."
443
+ )
444
+ seq_sim = str(st.session_state.analyze_input or "").strip()
445
+ seq_clean_sim = "".join(c for c in seq_sim.upper() if not c.isspace())
446
+ if seq_clean_sim:
447
+ match_seq, sim_score = find_most_similar(seq_clean_sim)
448
+ if match_seq is not None:
449
+ st.write(f"**Best match:** `{match_seq}`")
450
+ st.write(f"**Similarity score:** **{sim_score:.3f}** (position match / max length)")
451
+ if sim_score > 0.6:
452
+ st.success("High similarity to a known AMP in the reference set.")
453
+ elif sim_score > 0.3:
454
+ st.warning("Moderate similarity — interpret with care.")
455
+ else:
456
+ st.error("Low similarity — sequence is distant from reference AMPs.")
457
+ else:
458
+ st.warning("Could not compute similarity.")
459
+ else:
460
+ st.caption("Run analysis with a sequence to compare against known AMPs.")
461
+
462
  st.divider()
463
  # Analysis Summary
464
  st.subheader("Analysis Summary")
 
610
  "and functional residue map."
611
  )
612
 
 
 
 
 
 
613
  st.checkbox("Auto-run when sequence changes", value=False, key="viz_peptide_auto_run")
614
 
615
  st.text_input(
616
  "Peptide sequence",
617
  key="visualize_peptide_input",
618
+ placeholder="Paste or type a one-letter amino-acid sequence",
 
619
  )
620
 
621
  seq_viz = (st.session_state.get("visualize_peptide_input") or "").strip()
622
  clean_viz = "".join(c for c in seq_viz.upper() if not c.isspace())
623
  if not clean_viz:
624
  st.session_state.viz_peptide_last_computed = ""
625
+ st.info("Enter a sequence above, then click **Run visualization** (or enable **Auto-run**).")
626
  else:
627
  run_viz = st.button("Run visualization", type="primary", key="viz_peptide_run_btn")
628
  auto_on = bool(st.session_state.get("viz_peptide_auto_run"))
 
634
  need_compute = True
635
 
636
  if need_compute:
637
+ with st.spinner("Building 3D view and helical wheel..."):
638
  st.session_state.viz_peptide_last_computed = clean_viz
639
 
640
  if clean_viz == st.session_state.get("viz_peptide_last_computed"):
 
662
 
663
  with col_r:
664
  st.subheader("Helical wheel")
665
+ st.caption(
666
+ "Radial spokes per residue, black connectors along the sequence, colored disks (same scheme as 3D)."
667
+ )
668
  fig_wheel = plot_helical_wheel(clean_viz)
669
  st.pyplot(fig_wheel, use_container_width=True)
670
  plt.close(fig_wheel)
 
678
  with st.expander("Map · legend", expanded=False):
679
  st.markdown(COMPACT_MAP_LEGEND)
680
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
681
  elif clean_viz != (st.session_state.get("viz_peptide_last_computed") or ""):
682
  st.caption("Click **Run visualization** or enable **Auto-run** to update the figures.")
683
 
 
784
 
785
  It uses a trained neural network to estimate whether a peptide is likely to be antimicrobial, then helps you interpret and improve candidates:
786
  - **Predict**: batch predictions from multi-line or FASTA input, length warnings, persisted results, top-candidate highlight, and CSV export.
787
+ - **Analyze**: single-sequence numerical & textual analysis — AMP prediction, composition, physicochemical table + radar, similarity to known AMPs, and exportable report (no 3D on this page).
788
  - **Optimize**: guided sequence optimization with mutation heatmap, step table, and confidence vs. step plot.
789
+ - **Visualize Peptide**: one sequence with consistent coloring across functional map, detailed helical wheel, and optional 3D approximation.
790
  - **Visualize t-SNE**: upload many sequences, embed with the model, run t-SNE, and explore clusters with filters and hover metadata.
791
  - **About**: this overview and disclaimer.
792
 
StreamlitApp/utils/peptide_extras.py CHANGED
@@ -152,14 +152,14 @@ def get_residue_color(aa: str) -> str:
152
 
153
 
154
  def residue_color_mpl(aa: str) -> str:
155
- """Matplotlib-compatible hex colors matching `get_residue_color` categories."""
156
  cat = get_residue_color(aa)
157
  return {
158
- "blue": "#4169E1",
159
- "red": "#E74C3C",
160
- "green": "#27AE60",
161
- "gray": "#95A5A6",
162
- }.get(cat, "#95A5A6")
163
 
164
 
165
  HELIX_WHEEL_LEGEND_MARKDOWN: str = """
@@ -178,7 +178,8 @@ COMPACT_3D_LEGEND: str = (
178
  "Helix trace is an approximation, not an experimental structure."
179
  )
180
  COMPACT_WHEEL_LEGEND: str = (
181
- "**100°** per residue. Cationic (blue) and hydrophobic (green) faces often matter for membrane-active helices."
 
182
  )
183
  COMPACT_MAP_LEGEND: str = (
184
  "Same scheme as 3D and wheel: charged vs hydrophobic distribution along the sequence."
@@ -187,8 +188,8 @@ COMPACT_MAP_LEGEND: str = (
187
 
188
  def plot_helical_wheel(sequence: str, figsize: Tuple[float, float] = (6.2, 6.2)) -> Any:
189
  """
190
- Amphipathic-style helical wheel (matplotlib polar). Colors match 3D / HTML maps.
191
- Uses dark text for contrast on all wedge colors (avoids white-on-light issues in Streamlit).
192
  """
193
  import matplotlib.pyplot as plt
194
  from matplotlib import patheffects as pe
@@ -198,54 +199,94 @@ def plot_helical_wheel(sequence: str, figsize: Tuple[float, float] = (6.2, 6.2))
198
  fig, ax = plt.subplots(figsize=figsize, subplot_kw={"projection": "polar"})
199
  fig.patch.set_facecolor("white")
200
  if n == 0:
201
- ax.set_facecolor("#f5f5f5")
202
  ax.set_title("Helical wheel (empty sequence)", pad=12)
203
  return fig
204
 
205
- ax.set_facecolor("#f5f5f5")
206
 
207
  angles_deg = np.array([i * 100.0 for i in range(n)], dtype=float) % 360.0
208
  angles_rad = np.deg2rad(angles_deg)
209
- width = np.deg2rad(min(360.0 / max(n, 1) * 0.92, 0.45))
210
- r0, r1 = 0.35, 1.0
211
- fs = max(7, min(12, int(240 / max(n, 1))))
212
- for i, aa in enumerate(clean):
 
 
 
 
 
213
  th = angles_rad[i]
214
- color = residue_color_mpl(aa)
215
- ax.bar(th, r1 - r0, width=width, bottom=r0, color=color, edgecolor="#222222", linewidth=0.5, align="center")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
216
  t = ax.text(
217
- th,
218
- (r0 + r1) / 2.0,
219
  aa,
220
  ha="center",
221
  va="center",
222
  fontsize=fs,
223
- color="#111111",
224
  fontweight="bold",
 
225
  )
226
- t.set_path_effects([pe.withStroke(linewidth=2.5, foreground="white")])
227
 
228
- ax.set_theta_zero_location("N")
229
- ax.set_theta_direction(-1)
230
- ax.set_ylim(0, 1.05)
231
  ax.set_yticklabels([])
232
  ax.set_xticklabels([])
233
  ax.grid(False)
234
- ax.set_title("Helical wheel (α-helix projection, approximate)", pad=14, fontsize=11, color="#111111")
 
 
 
 
 
235
  return fig
236
 
237
 
238
  def get_residue_style(aa: str) -> str:
 
239
  positive = ["K", "R", "H"]
240
  negative = ["D", "E"]
241
  hydrophobic = ["A", "V", "I", "L", "M", "F", "W", "Y"]
242
  if aa in positive:
243
- return "background-color: blue; color: white; padding: 2px;"
244
  if aa in negative:
245
- return "background-color: red; color: white; padding: 2px;"
246
  if aa in hydrophobic:
247
- return "background-color: green; color: white; padding: 2px;"
248
- return "background-color: lightgray; padding: 2px;"
249
 
250
 
251
  def build_importance_map_html(sequence: str) -> str:
 
152
 
153
 
154
  def residue_color_mpl(aa: str) -> str:
155
+ """Matplotlib-compatible hex colors matching `get_residue_color` categories (high-contrast for plots)."""
156
  cat = get_residue_color(aa)
157
  return {
158
+ "blue": "#1D4ED8",
159
+ "red": "#DC2626",
160
+ "green": "#16A34A",
161
+ "gray": "#57534E",
162
+ }.get(cat, "#57534E")
163
 
164
 
165
  HELIX_WHEEL_LEGEND_MARKDOWN: str = """
 
178
  "Helix trace is an approximation, not an experimental structure."
179
  )
180
  COMPACT_WHEEL_LEGEND: str = (
181
+ "**Radial lines:** each residue around the wheel. **Black segments:** connect residue i i+1 in sequence order "
182
+ "(classic helical-wheel “star” pattern). **Colors** match 3D: **blue** K,R,H · **red** D,E · **green** hydrophobic · **gray** other."
183
  )
184
  COMPACT_MAP_LEGEND: str = (
185
  "Same scheme as 3D and wheel: charged vs hydrophobic distribution along the sequence."
 
188
 
189
  def plot_helical_wheel(sequence: str, figsize: Tuple[float, float] = (6.2, 6.2)) -> Any:
190
  """
191
+ Detailed helical wheel (matplotlib polar): radial spokes, sequence-order connectors (i→i+1),
192
+ and colored residue disks same chemistry classes as 3D / HTML maps (high-contrast colors).
193
  """
194
  import matplotlib.pyplot as plt
195
  from matplotlib import patheffects as pe
 
199
  fig, ax = plt.subplots(figsize=figsize, subplot_kw={"projection": "polar"})
200
  fig.patch.set_facecolor("white")
201
  if n == 0:
202
+ ax.set_facecolor("#ffffff")
203
  ax.set_title("Helical wheel (empty sequence)", pad=12)
204
  return fig
205
 
206
+ ax.set_facecolor("#ffffff")
207
 
208
  angles_deg = np.array([i * 100.0 for i in range(n)], dtype=float) % 360.0
209
  angles_rad = np.deg2rad(angles_deg)
210
+ r_inner, r_ring = 0.06, 0.88
211
+ fs = max(7, min(11, int(220 / max(n, 1))))
212
+ pt_size = float(np.clip(8000.0 / max(n, 1), 130.0, 420.0))
213
+
214
+ ax.set_theta_zero_location("N")
215
+ ax.set_theta_direction(-1)
216
+
217
+ # Radial spokes (residue positions)
218
+ for i in range(n):
219
  th = angles_rad[i]
220
+ ax.plot(
221
+ [th, th],
222
+ [r_inner, r_ring],
223
+ color="#1a1a1a",
224
+ linewidth=0.65,
225
+ alpha=0.45,
226
+ zorder=1,
227
+ )
228
+
229
+ # Sequence-order connections (straight chords in the plane — classic wheel “star”)
230
+ for i in range(n - 1):
231
+ ax.plot(
232
+ [angles_rad[i], angles_rad[i + 1]],
233
+ [r_ring, r_ring],
234
+ color="#0a0a0a",
235
+ linewidth=1.05,
236
+ solid_capstyle="round",
237
+ zorder=2,
238
+ )
239
+
240
+ colors = [residue_color_mpl(aa) for aa in clean]
241
+ ax.scatter(
242
+ angles_rad,
243
+ np.full(n, r_ring),
244
+ s=pt_size,
245
+ c=colors,
246
+ edgecolors="#111111",
247
+ linewidths=1.2,
248
+ zorder=4,
249
+ )
250
+
251
+ for i, aa in enumerate(clean):
252
  t = ax.text(
253
+ angles_rad[i],
254
+ r_ring,
255
  aa,
256
  ha="center",
257
  va="center",
258
  fontsize=fs,
259
+ color="#0a0a0a",
260
  fontweight="bold",
261
+ zorder=5,
262
  )
263
+ t.set_path_effects([pe.withStroke(linewidth=2.2, foreground="white")])
264
 
265
+ ax.set_ylim(0, 1.0)
 
 
266
  ax.set_yticklabels([])
267
  ax.set_xticklabels([])
268
  ax.grid(False)
269
+ ax.set_title(
270
+ "Helical wheel (α-helix, 100°/residue) — spokes + sequence connectors",
271
+ pad=14,
272
+ fontsize=11,
273
+ color="#111111",
274
+ )
275
  return fig
276
 
277
 
278
  def get_residue_style(aa: str) -> str:
279
+ """Inline styles for sequence map — colors aligned with wheel / 3D categories (high contrast)."""
280
  positive = ["K", "R", "H"]
281
  negative = ["D", "E"]
282
  hydrophobic = ["A", "V", "I", "L", "M", "F", "W", "Y"]
283
  if aa in positive:
284
+ return "background-color: #1D4ED8; color: #ffffff; padding: 2px 3px; border-radius: 2px;"
285
  if aa in negative:
286
+ return "background-color: #DC2626; color: #ffffff; padding: 2px 3px; border-radius: 2px;"
287
  if aa in hydrophobic:
288
+ return "background-color: #16A34A; color: #ffffff; padding: 2px 3px; border-radius: 2px;"
289
+ return "background-color: #57534E; color: #ffffff; padding: 2px 3px; border-radius: 2px;"
290
 
291
 
292
  def build_importance_map_html(sequence: str) -> str: