42Cummer commited on
Commit
7ce6f00
Β·
verified Β·
1 Parent(s): 139688b

Upload 2 files

Browse files
Files changed (2) hide show
  1. app.py +175 -14
  2. requirements.txt +2 -2
app.py CHANGED
@@ -1,13 +1,38 @@
1
  import gradio as gr # type: ignore
2
  import os
 
3
 
4
  # Import your custom modules from the /scripts folder
5
  from scripts.download import download_and_clean_pdb
6
  from scripts.generator import run_broteinshake_generator
7
  from scripts.refine import polish_design
 
8
 
9
  # --- HELPER FUNCTIONS ---
10
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
  def extract_best_sequence(fasta_file: str) -> str:
12
  """Extract the best sequence (lowest score) from FASTA file."""
13
  best_score = float('inf')
@@ -54,17 +79,32 @@ def run_part1(pdb_id, fixed_chains, variable_chains):
54
  # This creates the .fa files you need for the ESM Atlas
55
  run_broteinshake_generator(pdb_path, fixed_chains, variable_chains)
56
 
57
- # Extract and display only the best sequence
58
  fa_file = os.path.join("generated", pdb_id.lower(), "seqs", f"{pdb_id.lower()}_clones.fa")
 
59
  best_sequence = extract_best_sequence(fa_file)
60
 
 
 
 
61
  # Parse score from header for status message
62
  score_part = [p for p in best_sequence.split('\n')[0].split(',') if 'score' in p][0]
63
  best_score = float(score_part.split('=')[1])
 
 
 
 
 
 
 
 
 
 
 
64
 
65
- return best_sequence, f"Part 1 Complete. Best design (score={best_score:.4f}) shown above. Copy the sequence and fold it at esmatlas.com."
66
  except Exception as e:
67
- return "", f"Error in Part 1: {str(e)}"
68
 
69
  def run_part2(pdb_id, uploaded_esm_file):
70
  """Aligns the ESM-folded PDB to the target structure."""
@@ -72,8 +112,27 @@ def run_part2(pdb_id, uploaded_esm_file):
72
  if uploaded_esm_file is None:
73
  return None, "⚠️ Please upload the PDB from ESM Atlas first."
74
 
 
 
 
 
 
 
 
 
 
75
  # Call the new lightweight Biopython-only script
76
- final_pdb_path, rmsd_val = polish_design(pdb_id, uploaded_esm_file.name)
 
 
 
 
 
 
 
 
 
 
77
 
78
  # Update the report to focus on Backbone Alignment
79
  report = (
@@ -82,12 +141,61 @@ def run_part2(pdb_id, uploaded_esm_file):
82
  f"🧬 Status: High-Precision Backbone Match"
83
  )
84
  return final_pdb_path, report
 
 
 
 
 
 
 
 
85
  except Exception as e:
86
- return None, f"❌ Error: {str(e)}"
 
 
 
 
87
 
88
  # --- GRADIO INTERFACE ---
89
 
90
- with gr.Blocks(theme=gr.themes.Soft()) as demo:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
91
  gr.Markdown("# broteinshake")
92
  gr.Markdown("### AI-Driven Structural Mimicry & BBB-Shuttle Design")
93
 
@@ -100,24 +208,77 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
100
  f_chains = gr.Textbox(label="Fixed Chains (Lock)", value="A")
101
  v_chains = gr.Textbox(label="Variable Chains (Key)", value="B")
102
 
103
- gen_btn = gr.Button("Generate Optimized Sequences", variant="primary")
104
- fa_output = gr.Code(label="Best Design Sequence (.fasta format)", language="markdown")
105
- status1 = gr.Textbox(label="System Status", interactive=False)
 
 
 
 
 
 
 
 
 
 
 
 
106
 
107
- gen_btn.click(run_part1, inputs=[pdb_input, f_chains, v_chains], outputs=[fa_output, status1])
 
108
 
109
  # TAB 2: STRUCTURAL VALIDATION
110
  with gr.Tab("2. Structural Validation"):
111
- gr.Markdown("### Align Designed Shuttle to Human Receptor")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
112
  with gr.Row():
113
  esm_upload = gr.File(label="Upload ESM-Folded PDB", file_types=[".pdb"])
114
  refined_download = gr.File(label="Download Aligned Lead (.pdb)")
115
 
116
- # Change the button text to match our new approach
117
  validate_btn = gr.Button("✨ Run Structural Alignment", variant="primary")
118
  status2 = gr.Textbox(label="Validation Report", interactive=False, lines=5)
119
-
120
- validate_btn.click(run_part2, inputs=[pdb_input, esm_upload], outputs=[refined_download, status2])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
121
 
122
  # Launch the app
123
  if __name__ == "__main__":
 
1
  import gradio as gr # type: ignore
2
  import os
3
+ from gradio_molecule3d import Molecule3D
4
 
5
  # Import your custom modules from the /scripts folder
6
  from scripts.download import download_and_clean_pdb
7
  from scripts.generator import run_broteinshake_generator
8
  from scripts.refine import polish_design
9
+ from scripts.visualize import create_design_plot
10
 
11
  # --- HELPER FUNCTIONS ---
12
 
13
+ def get_all_sequences(fasta_file: str) -> str:
14
+ """Get all designed sequences from FASTA file."""
15
+ sequences = []
16
+ with open(fasta_file, 'r') as f:
17
+ lines = [line.strip() for line in f.readlines() if line.strip()]
18
+
19
+ for i in range(0, len(lines), 2):
20
+ if i + 1 >= len(lines):
21
+ break
22
+ header = lines[i]
23
+ sequence = lines[i+1]
24
+
25
+ # Skip the original native sequence (first entry)
26
+ if "sample" not in header:
27
+ continue
28
+
29
+ sequences.append(f"{header}\n{sequence}")
30
+
31
+ if sequences:
32
+ return "\n\n".join(sequences)
33
+ else:
34
+ raise ValueError(f"No valid designs found in {fasta_file}")
35
+
36
  def extract_best_sequence(fasta_file: str) -> str:
37
  """Extract the best sequence (lowest score) from FASTA file."""
38
  best_score = float('inf')
 
79
  # This creates the .fa files you need for the ESM Atlas
80
  run_broteinshake_generator(pdb_path, fixed_chains, variable_chains)
81
 
82
+ # Get all sequences and the best one
83
  fa_file = os.path.join("generated", pdb_id.lower(), "seqs", f"{pdb_id.lower()}_clones.fa")
84
+ all_sequences = get_all_sequences(fa_file)
85
  best_sequence = extract_best_sequence(fa_file)
86
 
87
+ # Generate the dashboard plot
88
+ evolution_plot = create_design_plot(fa_file)
89
+
90
  # Parse score from header for status message
91
  score_part = [p for p in best_sequence.split('\n')[0].split(',') if 'score' in p][0]
92
  best_score = float(score_part.split('=')[1])
93
+
94
+ # Count number of designs
95
+ num_designs = len([s for s in all_sequences.split('\n\n') if s.strip()])
96
+
97
+ # Format status with best sequence
98
+ status_message = (
99
+ f"βœ… Design Complete! {num_designs} designs generated.\n\n"
100
+ f"⭐ Lead Candidate (Best Score: {best_score:.4f}):\n"
101
+ f"{best_sequence}\n\n"
102
+ f"πŸ“‹ Copy the lead sequence above and fold it at [esmatlas.com](https://esmatlas.com/resources?action=fold)."
103
+ )
104
 
105
+ return all_sequences, evolution_plot, status_message
106
  except Exception as e:
107
+ return "", None, f"❌ Error in Part 1: {str(e)}"
108
 
109
  def run_part2(pdb_id, uploaded_esm_file):
110
  """Aligns the ESM-folded PDB to the target structure."""
 
112
  if uploaded_esm_file is None:
113
  return None, "⚠️ Please upload the PDB from ESM Atlas first."
114
 
115
+ # Validate inputs
116
+ if not pdb_id or not pdb_id.strip():
117
+ return None, "❌ Error: PDB ID is required."
118
+
119
+ # Get file path
120
+ file_path = uploaded_esm_file.name if hasattr(uploaded_esm_file, 'name') else uploaded_esm_file
121
+ if not file_path or not os.path.exists(file_path):
122
+ return None, f"❌ Error: Uploaded file not found: {file_path}"
123
+
124
  # Call the new lightweight Biopython-only script
125
+ print(f"πŸ” Starting alignment for PDB ID: {pdb_id}, File: {file_path}")
126
+ final_pdb_path, rmsd_val = polish_design(pdb_id, file_path)
127
+
128
+ # Validate output
129
+ if not final_pdb_path or not os.path.exists(final_pdb_path):
130
+ return None, f"❌ Error: Alignment failed - output file not created: {final_pdb_path}"
131
+
132
+ if rmsd_val is None:
133
+ return None, "❌ Error: Alignment failed - RMSD calculation returned None"
134
+
135
+ print(f"βœ… Alignment successful: RMSD = {rmsd_val:.3f} Γ…, Output: {final_pdb_path}")
136
 
137
  # Update the report to focus on Backbone Alignment
138
  report = (
 
141
  f"🧬 Status: High-Precision Backbone Match"
142
  )
143
  return final_pdb_path, report
144
+ except FileNotFoundError as e:
145
+ error_msg = f"❌ File Error: {str(e)}"
146
+ print(error_msg)
147
+ return None, error_msg
148
+ except ValueError as e:
149
+ error_msg = f"❌ Validation Error: {str(e)}"
150
+ print(error_msg)
151
+ return None, error_msg
152
  except Exception as e:
153
+ error_msg = f"❌ Unexpected Error: {str(e)}\n\nTraceback:\n{type(e).__name__}"
154
+ print(error_msg)
155
+ import traceback
156
+ traceback.print_exc()
157
+ return None, error_msg
158
 
159
  # --- GRADIO INTERFACE ---
160
 
161
+ # 1. Define the Biohub Theme
162
+ biohub_theme = gr.themes.Soft(
163
+ primary_hue="emerald",
164
+ secondary_hue="slate",
165
+ neutral_hue="slate",
166
+ font=[gr.themes.GoogleFont("Inter"), "ui-sans-serif", "system-ui"],
167
+ ).set(
168
+ # Custom CSS variable overrides for that "Glass" look
169
+ block_title_text_weight="600",
170
+ block_label_text_size="*text_sm",
171
+ input_background_fill="*neutral_50",
172
+ button_primary_background_fill="linear-gradient(90deg, #10b981, #34d399)",
173
+ button_primary_text_color="white",
174
+ border_color_primary="*neutral_200",
175
+ )
176
+
177
+ # 2. Inject Custom CSS for the "Spice"
178
+ biohub_css = """
179
+ .gradio-container {
180
+ background: radial-gradient(circle at top right, #f8fafc, #f1f5f9); /* Subtle depth */
181
+ }
182
+ #biohub-header {
183
+ text-align: center;
184
+ padding: 20px;
185
+ background: #064e3b;
186
+ color: white;
187
+ border-radius: 15px;
188
+ margin-bottom: 20px;
189
+ box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
190
+ }
191
+ .gr-box {
192
+ border-radius: 12px !important;
193
+ border: 1px solid #e2e8f0 !important;
194
+ }
195
+ footer {display: none !important;} /* Cleans up the portfolio look */
196
+ """
197
+
198
+ with gr.Blocks(theme=biohub_theme, css=biohub_css) as demo:
199
  gr.Markdown("# broteinshake")
200
  gr.Markdown("### AI-Driven Structural Mimicry & BBB-Shuttle Design")
201
 
 
208
  f_chains = gr.Textbox(label="Fixed Chains (Lock)", value="A")
209
  v_chains = gr.Textbox(label="Variable Chains (Key)", value="B")
210
 
211
+ gen_btn = gr.Button("πŸš€ Generate Optimized Sequences", variant="primary")
212
+
213
+ # Stack components vertically
214
+ fa_output = gr.Code(
215
+ label="Designed Sequences",
216
+ language="markdown",
217
+ lines=10,
218
+ max_lines=20 # Prevents infinite growth when showing all 20 candidates
219
+ )
220
+ plot_output = gr.Plot(
221
+ label="Design Evolution Dashboard"
222
+ )
223
+
224
+ gr.Markdown("### System Status")
225
+ status1 = gr.Markdown()
226
 
227
+ gen_btn.click(run_part1, inputs=[pdb_input, f_chains, v_chains],
228
+ outputs=[fa_output, plot_output, status1])
229
 
230
  # TAB 2: STRUCTURAL VALIDATION
231
  with gr.Tab("2. Structural Validation"):
232
+ gr.Markdown("### 🧬 Final Structure Preview")
233
+
234
+ # Updated REPS for local dev visibility
235
+ REPS = [
236
+ {
237
+ "model": 0,
238
+ "style": "cartoon",
239
+ "color": "spectrum",
240
+ "opacity": 1.0
241
+ # Removing "chain": "A" is the key fix here!
242
+ }
243
+ ]
244
+
245
+ # This component ACTUALLY supports .pdb natively
246
+ protein_view = Molecule3D(label="3D Structure Viewer (Refined Shuttle)", reps=REPS)
247
+
248
  with gr.Row():
249
  esm_upload = gr.File(label="Upload ESM-Folded PDB", file_types=[".pdb"])
250
  refined_download = gr.File(label="Download Aligned Lead (.pdb)")
251
 
 
252
  validate_btn = gr.Button("✨ Run Structural Alignment", variant="primary")
253
  status2 = gr.Textbox(label="Validation Report", interactive=False, lines=5)
254
+
255
+ # Update viewer - REPS are set on component creation, just return the file path
256
+ def run_validation_with_view(pdb_id, file):
257
+ try:
258
+ # This is your existing alignment/refine logic
259
+ final_pdb, report = run_part2(pdb_id, file)
260
+
261
+ if final_pdb is not None and os.path.exists(final_pdb):
262
+ # Verify we're using the refined shuttle (Refined_Shuttle.pdb)
263
+ if "Refined_Shuttle.pdb" in final_pdb or os.path.basename(final_pdb) == "Refined_Shuttle.pdb":
264
+ print(f"🎯 Visualizing refined shuttle: {final_pdb}")
265
+ else:
266
+ print(f"⚠️ Warning: Expected Refined_Shuttle.pdb but got: {final_pdb}")
267
+
268
+ # Molecule3D expects just the file path string (REPS are already set on component)
269
+ return final_pdb, report, final_pdb
270
+ except Exception as e:
271
+ error_msg = f"❌ Error generating 3D view: {str(e)}"
272
+ print(error_msg)
273
+ import traceback
274
+ traceback.print_exc()
275
+ return None, error_msg, None
276
+
277
+ validate_btn.click(
278
+ run_validation_with_view,
279
+ inputs=[pdb_input, esm_upload],
280
+ outputs=[refined_download, status2, protein_view]
281
+ )
282
 
283
  # Launch the app
284
  if __name__ == "__main__":
requirements.txt CHANGED
@@ -1,4 +1,5 @@
1
- gradio
 
2
  accelerate==1.12.0
3
  backports.zstd
4
  biopython==1.86
@@ -19,7 +20,6 @@ MarkupSafe
19
  mpmath
20
  networkx
21
  numpy==2.2.6
22
- openmm
23
  packaging==25.0
24
  pillow
25
  psutil==7.2.1
 
1
+ gradio==5.50.0
2
+ gradio_molecule3d
3
  accelerate==1.12.0
4
  backports.zstd
5
  biopython==1.86
 
20
  mpmath
21
  networkx
22
  numpy==2.2.6
 
23
  packaging==25.0
24
  pillow
25
  psutil==7.2.1