tai-dang11 commited on
Commit
9ad51d4
·
1 Parent(s): aaefa57

Add application file

Browse files
Files changed (1) hide show
  1. app.py +644 -0
app.py ADDED
@@ -0,0 +1,644 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import numpy as np
3
+ import random
4
+ import json
5
+ import os
6
+ from gradio_molecule3d import Molecule3D # Import Molecule3D component
7
+ from rdkit import Chem
8
+ from rdkit.Chem import Descriptors, Draw, QED
9
+
10
+
11
+ class VirtualScreeningBOApp:
12
+ def __init__(
13
+ self,
14
+ ligands,
15
+ initial_pairs,
16
+ protein_pdb_path, # Hardcoded PDB file path
17
+ max_iterations=3,
18
+ comparisons_per_iteration=2,
19
+ show_smiles=True # <--- Added argument
20
+ ):
21
+ self.ligands = ligands
22
+ self.current_pairs = initial_pairs
23
+ self.completed_pairs = {}
24
+ self.comparison_results = []
25
+
26
+ self.bo_iteration = 0
27
+ self.is_completed = False
28
+ self.max_iterations = max_iterations
29
+ self.comparisons_per_iteration = comparisons_per_iteration
30
+
31
+ self.protein_pdb_path = protein_pdb_path # Store PDB path
32
+ self.protein_pdb_data = self._read_pdb_file() # Read PDB data
33
+ self.show_smiles = show_smiles # <--- Store argument
34
+
35
+ self.app = None
36
+
37
+ def _read_pdb_file(self):
38
+ """Read the PDB file from the hardcoded path."""
39
+ try:
40
+ with open(self.protein_pdb_path, 'r') as f:
41
+ pdb_data = f.read()
42
+ return pdb_data
43
+ except FileNotFoundError:
44
+ print(f"Error: Protein PDB file not found at {self.protein_pdb_path}")
45
+ return None
46
+
47
+ def _iteration_status(self):
48
+ """Return a string like 'Iteration 1/3' (1-based for user display)."""
49
+ return f"**Iteration**: {self.bo_iteration + 1}/{self.max_iterations}"
50
+
51
+ def compute_properties(self, smiles):
52
+ """Compute basic properties from RDKit."""
53
+ if Chem is None or smiles is None:
54
+ return {
55
+ "SMILES": smiles if smiles else "N/A",
56
+ "MW": None,
57
+ "LogP": None,
58
+ "TPSA": None,
59
+ "QED": None,
60
+ }
61
+ mol = Chem.MolFromSmiles(smiles)
62
+ if mol is None:
63
+ return {
64
+ "SMILES": "Invalid SMILES",
65
+ "MW": None,
66
+ "LogP": None,
67
+ "TPSA": None,
68
+ "QED": None,
69
+ }
70
+ return {
71
+ "SMILES": smiles,
72
+ "MW": round(Descriptors.MolWt(mol), 2),
73
+ "LogP": round(Descriptors.MolLogP(mol), 2),
74
+ "TPSA": round(Descriptors.TPSA(mol), 2),
75
+ "QED": round(QED.qed(mol), 2),
76
+ }
77
+
78
+ def _mol_to_image(self, ligand_name):
79
+ """Create a 300x300 image from the SMILES. If RDKit not present, returns None."""
80
+ if Chem is None:
81
+ return None
82
+ smiles = self.ligands.get(ligand_name, "")
83
+ mol = Chem.MolFromSmiles(smiles)
84
+ if mol is None:
85
+ return None
86
+ return np.array(Draw.MolToImage(mol, size=(300, 300)))
87
+
88
+ def _generate_new_pairs(self, n):
89
+ """Randomly pick n pairs from the ligand dictionary."""
90
+ keys = list(self.ligands.keys())
91
+ pairs = []
92
+ for _ in range(n):
93
+ a = random.choice(keys)
94
+ b = random.choice(keys)
95
+ while b == a:
96
+ b = random.choice(keys)
97
+ pairs.append((a, b))
98
+ return pairs
99
+
100
+ def _save_results(self):
101
+ """Saves iteration results to JSON."""
102
+ filename = f"comparison_results_iter_{self.bo_iteration}.json"
103
+ with open(filename, "w") as f:
104
+ json.dump(self.comparison_results, f, indent=4)
105
+ print(f"Results of iteration {self.bo_iteration} saved to {filename}")
106
+
107
+ def get_pair_index(self, pair_label):
108
+ """Parse 'Pair X (Pending)' or 'Pair X ✔' => integer X-1 (zero-based)."""
109
+ try:
110
+ parts = pair_label.split()
111
+ idx = int(parts[1]) - 1
112
+ return idx
113
+ except (IndexError, ValueError):
114
+ return 0
115
+
116
+ # --------------------------------------------------------------------------
117
+ # Gradio event methods
118
+ # --------------------------------------------------------------------------
119
+
120
+ def show_initial(self):
121
+ iteration_str = self._iteration_status()
122
+
123
+ if self.bo_iteration >= self.max_iterations:
124
+ return self.finish_bo_process()
125
+
126
+ if not self.current_pairs:
127
+ return (
128
+ iteration_str,
129
+ "No pairs available.",
130
+ None,
131
+ None,
132
+ gr.update(value=[], headers=["Ligand", "MW", "LogP", "TPSA", "QED"]),
133
+ gr.update(choices=[], value=""), # Set dropdown to empty
134
+ gr.update(),
135
+ gr.update(),
136
+ )
137
+
138
+ # Build updated labels
139
+ updated_labels = []
140
+ for i, pair in enumerate(self.current_pairs):
141
+ if pair in self.completed_pairs:
142
+ updated_labels.append(f"Pair {i+1} ✔")
143
+ else:
144
+ updated_labels.append(f"Pair {i+1} (Pending)")
145
+
146
+ default_label = updated_labels[0]
147
+ ligandA_id, ligandB_id = self.current_pairs[0]
148
+
149
+ imgA = self._mol_to_image(ligandA_id)
150
+ imgB = self._mol_to_image(ligandB_id)
151
+
152
+ propsA = self.compute_properties(self.ligands[ligandA_id])
153
+ propsB = self.compute_properties(self.ligands[ligandB_id])
154
+ if self.show_smiles:
155
+ table_headers = ["Ligand", "SMILES", "MW", "LogP", "TPSA", "QED"]
156
+ table_data = [
157
+ ["Ligand A", propsA["SMILES"], propsA["MW"], propsA["LogP"], propsA["TPSA"], propsA["QED"]],
158
+ ["Ligand B", propsB["SMILES"], propsB["MW"], propsB["LogP"], propsB["TPSA"], propsB["QED"]],
159
+ ]
160
+ else:
161
+ table_headers = ["Ligand", "MW", "LogP", "TPSA", "QED"]
162
+ table_data = [
163
+ ["Ligand A", propsA["MW"], propsA["LogP"], propsA["TPSA"], propsA["QED"]],
164
+ ["Ligand B", propsB["MW"], propsB["LogP"], propsB["TPSA"], propsB["QED"]],
165
+ ]
166
+
167
+ if (ligandA_id, ligandB_id) in self.completed_pairs:
168
+ arrow = ">" if self.completed_pairs[(ligandA_id, ligandB_id)] == 1 else "<"
169
+ else:
170
+ arrow = "vs"
171
+ pair_label_str = default_label
172
+
173
+ current_selection_msg = (
174
+ f"**Currently selected**: {pair_label_str} => **Ligand A** {arrow} **Ligand B**"
175
+ )
176
+
177
+ return (
178
+ iteration_str,
179
+ current_selection_msg,
180
+ imgA,
181
+ imgB,
182
+ gr.update(value=table_data, headers=table_headers),
183
+ gr.update(choices=updated_labels, value=default_label),
184
+ gr.update(),
185
+ gr.update(),
186
+ )
187
+
188
+ def update_view_on_dropdown(self, pair_label):
189
+ iteration_str = self._iteration_status()
190
+
191
+ if self.bo_iteration >= self.max_iterations:
192
+ return self.finish_bo_process()
193
+
194
+ if not pair_label or pair_label.strip() == "":
195
+ return (
196
+ self._iteration_status(),
197
+ "Please select a valid pair",
198
+ self._mol_to_image(list(self.ligands.keys())[0]), # Show first ligand
199
+ self._mol_to_image(list(self.ligands.keys())[1]), # Show second ligand
200
+ gr.update(value=[], headers=["Ligand", "MW", "LogP", "TPSA", "QED"]),
201
+ )
202
+
203
+ # If pair_label is "" or None, handle gracefully
204
+ # if not pair_label:
205
+ # return (
206
+ # iteration_str,
207
+ # "No pair selected or invalid selection!",
208
+ # None,
209
+ # None,
210
+ # gr.update(value=[], headers=["Ligand", "MW", "LogP", "TPSA", "QED"]),
211
+ # )
212
+
213
+ idx = self.get_pair_index(pair_label)
214
+ if idx < 0 or idx >= len(self.current_pairs):
215
+ return (
216
+ iteration_str,
217
+ f"Invalid pair: {pair_label}",
218
+ None,
219
+ None,
220
+ gr.update(value=[], headers=["Ligand", "MW", "LogP", "TPSA", "QED"]),
221
+ )
222
+
223
+ ligandA_id, ligandB_id = self.current_pairs[idx]
224
+ pair_done = (ligandA_id, ligandB_id) in self.completed_pairs
225
+ print(f"completed_pairs: {self.completed_pairs}")
226
+ print(f"current_pairs: {self.current_pairs}")
227
+ if (ligandA_id, ligandB_id) in self.completed_pairs:
228
+ arrow = ">" if self.completed_pairs[(ligandA_id, ligandB_id)] == 1 else "<"
229
+ else:
230
+ arrow = "vs"
231
+
232
+ imgA = self._mol_to_image(ligandA_id)
233
+ imgB = self._mol_to_image(ligandB_id)
234
+
235
+ propsA = self.compute_properties(self.ligands[ligandA_id])
236
+ propsB = self.compute_properties(self.ligands[ligandB_id])
237
+ if self.show_smiles:
238
+ table_headers = ["Ligand", "SMILES", "MW", "LogP", "TPSA", "QED"]
239
+ table_data = [
240
+ ["Ligand A", propsA["SMILES"], propsA["MW"], propsA["LogP"], propsA["TPSA"], propsA["QED"]],
241
+ ["Ligand B", propsB["SMILES"], propsB["MW"], propsB["LogP"], propsB["TPSA"], propsB["QED"]],
242
+ ]
243
+ else:
244
+ table_headers = ["Ligand", "MW", "LogP", "TPSA", "QED"]
245
+ table_data = [
246
+ ["Ligand A", propsA["MW"], propsA["LogP"], propsA["TPSA"], propsA["QED"]],
247
+ ["Ligand B", propsB["MW"], propsB["LogP"], propsB["TPSA"], propsB["QED"]],
248
+ ]
249
+
250
+ label_symbol = "✔" if pair_done else "(Pending)"
251
+ pair_label_str = f"Pair {idx+1} {label_symbol}"
252
+ current_selection_msg = (
253
+ f"**Currently selected**: {pair_label_str} => **Ligand A** {arrow} **Ligand B**"
254
+ )
255
+
256
+ return (
257
+ iteration_str,
258
+ current_selection_msg,
259
+ imgA,
260
+ imgB,
261
+ gr.update(value=table_data, headers=table_headers),
262
+ )
263
+
264
+ def get_pair_index(self, pair_label):
265
+ """Parse 'Pair X (Pending)' or 'Pair X ✔' => integer X-1 (zero-based)."""
266
+ if not pair_label:
267
+ return -1 # Return an invalid index for `None`
268
+ try:
269
+ parts = pair_label.split()
270
+ idx = int(parts[1]) - 1
271
+ return idx
272
+ except (IndexError, ValueError):
273
+ return -1
274
+
275
+ def show_pair(self, preference, pair_label):
276
+ iteration_str = self._iteration_status()
277
+ idx = self.get_pair_index(pair_label)
278
+ if idx < 0 or idx >= len(self.current_pairs):
279
+ idx = 0
280
+
281
+ ligandA_id, ligandB_id = self.current_pairs[idx]
282
+ self.comparison_results.append({
283
+ "Iteration": self.bo_iteration,
284
+ "Pair": (ligandA_id, ligandB_id),
285
+ "Preference": preference,
286
+ })
287
+ print(f"Logged preference: Iter={self.bo_iteration}, Pair=({ligandA_id}, {ligandB_id}), Choice={preference}")
288
+
289
+ if preference == "Ligand A":
290
+ self.completed_pairs[(ligandA_id, ligandB_id)] = 1
291
+ old_pair_str = "**Ligand A** > **Ligand B**"
292
+ else:
293
+ self.completed_pairs[(ligandA_id, ligandB_id)] = 0
294
+ old_pair_str = "**Ligand B** > **Ligand A**"
295
+
296
+ updated_labels = []
297
+ for i, p in enumerate(self.current_pairs):
298
+ if p in self.completed_pairs:
299
+ updated_labels.append(f"Pair {i+1} ✔")
300
+ else:
301
+ updated_labels.append(f"Pair {i+1} (Pending)")
302
+
303
+ next_idx = None
304
+ for i, p in enumerate(self.current_pairs):
305
+ if p not in self.completed_pairs:
306
+ next_idx = i
307
+ break
308
+
309
+ if next_idx is not None:
310
+ nextA_id, nextB_id = self.current_pairs[next_idx]
311
+ imgA = self._mol_to_image(nextA_id)
312
+ imgB = self._mol_to_image(nextB_id)
313
+ propsA = self.compute_properties(self.ligands[nextA_id])
314
+ propsB = self.compute_properties(self.ligands[nextB_id])
315
+ if self.show_smiles:
316
+ table_headers = ["Ligand", "SMILES", "MW", "LogP", "TPSA", "QED"]
317
+ table_data = [
318
+ ["Ligand A", propsA["SMILES"], propsA["MW"], propsA["LogP"], propsA["TPSA"], propsA["QED"]],
319
+ ["Ligand B", propsB["SMILES"], propsB["MW"], propsB["LogP"], propsB["TPSA"], propsB["QED"]],
320
+ ]
321
+ else:
322
+ table_headers = ["Ligand", "MW", "LogP", "TPSA", "QED"]
323
+ table_data = [
324
+ ["Ligand A", propsA["MW"], propsA["LogP"], propsA["TPSA"], propsA["QED"]],
325
+ ["Ligand B", propsB["MW"], propsB["LogP"], propsB["TPSA"], propsB["QED"]],
326
+ ]
327
+ next_label = updated_labels[next_idx]
328
+ current_selection_msg = (
329
+ f"**Currently selected**: {next_label} => **Ligand A** vs **Ligand B**"
330
+ )
331
+ bo_btn_state = False
332
+ dropdown_val = next_label
333
+ else:
334
+ current_selection_msg = (
335
+ f"**Currently selected**: {pair_label} => **Ligand A** vs **Ligand B**"
336
+ )
337
+ imgA = self._mol_to_image(ligandA_id)
338
+ imgB = self._mol_to_image(ligandB_id)
339
+ propsA = self.compute_properties(self.ligands[ligandA_id])
340
+ propsB = self.compute_properties(self.ligands[ligandB_id])
341
+ if self.show_smiles:
342
+ table_headers = ["Ligand", "SMILES", "MW", "LogP", "TPSA", "QED"]
343
+ table_data = [
344
+ ["Ligand A", propsA["SMILES"], propsA["MW"], propsA["LogP"], propsA["TPSA"], propsA["QED"]],
345
+ ["Ligand B", propsB["SMILES"], propsB["MW"], propsB["LogP"], propsB["TPSA"], propsB["QED"]],
346
+ ]
347
+ else:
348
+ table_headers = ["Ligand", "MW", "LogP", "TPSA", "QED"]
349
+ table_data = [
350
+ ["Ligand A", propsA["MW"], propsA["LogP"], propsA["TPSA"], propsA["QED"]],
351
+ ["Ligand B", propsB["MW"], propsB["LogP"], propsB["TPSA"], propsB["QED"]],
352
+ ]
353
+ bo_btn_state = True
354
+ dropdown_val = updated_labels[-1] if updated_labels else ""
355
+
356
+ selection_msg = (
357
+ f"You chose {old_pair_str} for {pair_label}.<br>"
358
+ f"{current_selection_msg}"
359
+ )
360
+ print(selection_msg)
361
+
362
+ return (
363
+ iteration_str,
364
+ selection_msg,
365
+ imgA,
366
+ imgB,
367
+ gr.update(value=table_data, headers=table_headers),
368
+ gr.update(choices=updated_labels, value=dropdown_val),
369
+ dropdown_val,
370
+ gr.update(interactive=bo_btn_state),
371
+ )
372
+
373
+ def start_bo_iteration(self):
374
+ iteration_str = self._iteration_status()
375
+
376
+ if self.bo_iteration >= self.max_iterations - 1:
377
+ self.is_completed = True
378
+ return self.finish_bo_process()
379
+
380
+ # Ensure all pairs are completed before proceeding
381
+ if len(self.completed_pairs) < len(self.current_pairs):
382
+ return (
383
+ iteration_str,
384
+ "Please complete all pairs first!", # Notify user
385
+ None,
386
+ None,
387
+ gr.update(value=[], headers=["Ligand", "MW", "LogP", "TPSA", "QED"]),
388
+ gr.update(),
389
+ gr.update(),
390
+ gr.update(interactive=False, value="Next BO Iteration"), # Keep button disabled
391
+ )
392
+
393
+ # Save results and increment the iteration
394
+ self._save_results()
395
+ self.bo_iteration += 1
396
+ iteration_str = self._iteration_status()
397
+
398
+ # Check if the BO process is complete
399
+ if self.bo_iteration == self.max_iterations:
400
+ final_message = """
401
+ <div style="text-align: center; font-size: 24px; margin-top: 50px;">
402
+ <strong>The BO process has been completed.</strong><br>
403
+ Thank you for your input!
404
+ </div>
405
+ """
406
+ return (
407
+ None, # Clear iteration status
408
+ gr.update(value=final_message), # Display final message
409
+ None, # Hide Ligand A image
410
+ None, # Hide Ligand B image
411
+ None, # Clear the properties table
412
+ gr.update(choices=[], value=""), # Clear dropdown
413
+ None, # Clear dropdown value
414
+ gr.update(interactive=False),
415
+ )
416
+
417
+ # Generate new pairs for the next iteration
418
+ new_pairs = self._generate_new_pairs(self.comparisons_per_iteration)
419
+ self.current_pairs = new_pairs
420
+ self.completed_pairs = {}
421
+
422
+ updated_labels = [f"Pair {i+1} (Pending)" for i in range(len(new_pairs))]
423
+ default_val = updated_labels[0] if updated_labels else ""
424
+
425
+ if new_pairs:
426
+ ligandA_id, ligandB_id = new_pairs[0]
427
+ imgA = self._mol_to_image(ligandA_id)
428
+ imgB = self._mol_to_image(ligandB_id)
429
+ propsA = self.compute_properties(self.ligands[ligandA_id])
430
+ propsB = self.compute_properties(self.ligands[ligandB_id])
431
+ table_headers = ["Ligand", "MW", "LogP", "TPSA", "QED"]
432
+ table_data = [
433
+ ["Ligand A", propsA["MW"], propsA["LogP"], propsA["TPSA"], propsA["QED"]],
434
+ ["Ligand B", propsB["MW"], propsB["LogP"], propsB["TPSA"], propsB["QED"]],
435
+ ]
436
+ msg = (
437
+ f"Starting iteration {self.bo_iteration}/{self.max_iterations} with {len(new_pairs)} new pairs.<br>"
438
+ f"**Currently selected**: {default_val} => **Ligand A** vs **Ligand B**"
439
+ )
440
+ else:
441
+ msg = f"Starting iteration {self.bo_iteration}/{self.max_iterations}, but no new pairs?"
442
+ imgA = None
443
+ imgB = None
444
+ table_data = []
445
+
446
+ return (
447
+ iteration_str,
448
+ msg,
449
+ imgA,
450
+ imgB,
451
+ gr.update(value=table_data, headers=table_headers),
452
+ gr.update(choices=updated_labels, value=default_val),
453
+ default_val,
454
+ gr.update(interactive=False),
455
+ gr.update(visible=True), # Submit button
456
+ gr.update(visible=True), # Preference radio
457
+ )
458
+
459
+
460
+ def finish_bo_process(self):
461
+ self.is_completed = True
462
+ final_message = """
463
+ <div style="text-align: center; font-size: 24px; margin-top: 50px;">
464
+ <strong>The BO process has been completed.</strong><br>
465
+ Thank you for your input!
466
+ </div>
467
+ """
468
+ self.protein_view.delete()
469
+ self.protein_view.visible = False
470
+ self.protein_view.showviewer = False
471
+ del self.protein_view
472
+ return (
473
+ "", # Clear iteration status
474
+ final_message, # Show completion message
475
+ gr.update(visible=False), # Clear Ligand A image
476
+ gr.update(visible=False), # Clear Ligand B image
477
+ gr.update(visible=False), # Hide properties table
478
+ gr.update(visible=False), # Hide dropdown
479
+ None, # Clear dropdown value
480
+ gr.update(visible=False), # Hide BO button
481
+ gr.update(visible=False), # Hide submit button
482
+ gr.update(visible=False), # Hide preference radio
483
+ )
484
+
485
+ def build_app(self):
486
+ with gr.Blocks() as app:
487
+ gr.Markdown("## Virtual Screening BO App")
488
+
489
+ iteration_status_text = gr.Markdown(value="Loading...", label="Iteration Status")
490
+ current_selection_text = gr.HTML(value="Initializing...", label="Current Selection")
491
+
492
+ with gr.Row():
493
+ pair_dropdown = gr.Dropdown(
494
+ label="Select a Pair",
495
+ allow_custom_value=True,
496
+ interactive=True,
497
+ )
498
+ preference_radio = gr.Radio(
499
+ ["Ligand A", "Ligand B"],
500
+ label="Your preference",
501
+ value="Ligand A"
502
+ )
503
+ bo_btn = gr.Button(value="Next BO Iteration", interactive=False)
504
+
505
+ with gr.Row():
506
+ with gr.Column():
507
+ out_imgA = gr.Image(label="Ligand A", width=650, height=325)
508
+ out_imgB = gr.Image(label="Ligand B", width=650, height=325)
509
+
510
+ with gr.Column():
511
+ if self.protein_pdb_data:
512
+ self.protein_view = Molecule3D(
513
+ label="Protein Structure",
514
+ reps=[
515
+ {
516
+ "model": 0,
517
+ "chain": "",
518
+ "resname": "",
519
+ "style": "cartoon",
520
+ "color": "spectrum",
521
+ "residue_range": "",
522
+ "around": 0,
523
+ "byres": False,
524
+ "visible": True
525
+ }
526
+ ],
527
+ # Pass the PDB file path, not the data
528
+ value=self.protein_pdb_path,
529
+ )
530
+ else:
531
+ # If PDB data not found, display a message
532
+ self.protein_view = gr.Markdown("**Protein PDB file not found.**")
533
+
534
+ out_table = gr.Dataframe(
535
+ headers=["Ligand", "SMILES", "MW", "LogP", "TPSA", "QED"],
536
+ label="Properties"
537
+ )
538
+
539
+ submit_btn = gr.Button("Submit Preference")
540
+
541
+ # Event: When the app loads, show the initial view
542
+ app.load(
543
+ fn=self.show_initial,
544
+ inputs=None,
545
+ outputs=[
546
+ iteration_status_text,
547
+ current_selection_text,
548
+ out_imgA,
549
+ out_imgB,
550
+ out_table,
551
+ pair_dropdown,
552
+ preference_radio,
553
+ bo_btn,
554
+ # Protein visualization is static; no need to output
555
+ ],
556
+ )
557
+
558
+ # Event: When the dropdown changes, update the view
559
+ pair_dropdown.change(
560
+ fn=self.update_view_on_dropdown,
561
+ inputs=pair_dropdown,
562
+ outputs=[
563
+ iteration_status_text,
564
+ current_selection_text,
565
+ out_imgA,
566
+ out_imgB,
567
+ out_table,
568
+ # Protein visualization is static; no need to output
569
+ ]
570
+ )
571
+
572
+ bo_btn.click(
573
+ fn=self.start_bo_iteration,
574
+ inputs=[],
575
+ outputs=[
576
+ iteration_status_text,
577
+ current_selection_text,
578
+ out_imgA,
579
+ out_imgB,
580
+ out_table,
581
+ pair_dropdown,
582
+ pair_dropdown,
583
+ bo_btn,
584
+ submit_btn, # Add submit button control
585
+ preference_radio # Add preference radio control
586
+ ]
587
+ )
588
+
589
+ # Event: When the submit button is clicked
590
+ submit_btn.click(
591
+ fn=self.show_pair,
592
+ inputs=[preference_radio, pair_dropdown],
593
+ outputs=[
594
+ iteration_status_text,
595
+ current_selection_text,
596
+ out_imgA,
597
+ out_imgB,
598
+ out_table,
599
+ pair_dropdown,
600
+ pair_dropdown,
601
+ bo_btn,
602
+ # Protein visualization is static; no need to output
603
+ ],
604
+ )
605
+
606
+
607
+ self.app = app
608
+
609
+ def launch(self, **kwargs):
610
+ if self.app is None:
611
+ self.build_app()
612
+ self.app.launch(**kwargs)
613
+
614
+
615
+ # ---------------------------
616
+ # Example Usage
617
+ # ---------------------------
618
+ if __name__ == "__main__":
619
+ ligands = {
620
+ "L1": "CCN(CC)CC(=O)c1ccccc1N(C)C",
621
+ "L2": "CC(C)Cc1ccc(cc1)C(C)C(=O)O",
622
+ "L3": "Cn1c(=O)c2c(ncnc2N(C)C)n(C)c1=O",
623
+ "L4": "CC(=O)Oc1ccccc1C(=O)O",
624
+ "L5": "CCCC",
625
+ }
626
+ initial_pairs = [("L1", "L2"), ("L3", "L4")]
627
+
628
+ # Hardcoded PDB file path
629
+ protein_pdb = "/home/taitdang/Dock/MOAVS/TYSY/protein/1syn.pdb" # Update this path as needed
630
+
631
+ # Check if the PDB file exists
632
+ if not os.path.isfile(protein_pdb):
633
+ print(f"Error: Protein PDB file not found at {protein_pdb}")
634
+ exit(1)
635
+
636
+ app = VirtualScreeningBOApp(
637
+ ligands=ligands,
638
+ initial_pairs=initial_pairs,
639
+ protein_pdb_path=protein_pdb, # Provide the PDB file path
640
+ max_iterations=2,
641
+ comparisons_per_iteration=2,
642
+ show_smiles=False
643
+ )
644
+ app.launch(share=True, debug=True)