libokj commited on
Commit
c94f054
·
1 Parent(s): d861e44

Update result view and fix bugs

Browse files
app/fn.py CHANGED
@@ -3,6 +3,7 @@ from email.mime.multipart import MIMEMultipart
3
  from email.mime.text import MIMEText
4
  from email.utils import formatdate, make_msgid
5
  from functools import cache
 
6
  import os
7
  from pathlib import Path
8
  import smtplib
@@ -11,13 +12,14 @@ import tempfile
11
 
12
  import pandas as pd
13
  from bokeh.models import NumberFormatter, BooleanFormatter, HTMLTemplateFormatter
 
14
  import gradio as gr
15
  import pytz
16
  import panel as pn
17
  import seaborn as sns
18
  from markdown import markdown
19
  from rdkit import Chem, RDConfig
20
- from rdkit.Chem import PandasTools, Crippen, Descriptors, rdMolDescriptors, Lipinski, rdmolops
21
  import requests
22
 
23
  from app import static
@@ -153,7 +155,7 @@ def bms(mol):
153
 
154
 
155
  SCORE_MAP = {
156
- 'SAscore': sascorer.calculateScore,
157
  'LogP': Crippen.MolLogP,
158
  'Molecular Weight': Descriptors.MolWt,
159
  'Number of Atoms': rdMolDescriptors.CalcNumAtoms,
@@ -180,16 +182,6 @@ FILTER_MAP = {
180
  }
181
 
182
 
183
- def validate_columns(df, mandatory_cols):
184
- missing_cols = [col for col in mandatory_cols if col not in df.columns]
185
- if missing_cols:
186
- error_message = (f"The following mandatory columns are missing "
187
- f"in the uploaded dataset: {str(mandatory_cols).strip('[]')}.")
188
- raise ValueError(error_message)
189
- else:
190
- return
191
-
192
-
193
  def get_timezone_by_ip(ip, session):
194
  try:
195
  data = session.get(f'https://worldtimeapi.org/api/ip/{ip}').json()
@@ -289,20 +281,6 @@ def read_molecule_file(in_file, allowed_extentions):
289
  return content, extension, None
290
 
291
 
292
- def show_target(in_protein):
293
- molecule, extension, html = read_molecule_file(in_protein, allowed_extentions=['pdb'])
294
- if molecule is not None:
295
- html = static.TARGET_RENDERING_TEMPLATE.format(molecule=molecule, fmt=extension)
296
-
297
- return static.IFRAME_TEMPLATE.format(html=html)
298
-
299
- def show_complex(complex_path):
300
- protein_complex, extension, html = read_molecule_file(complex_path, allowed_extentions=['pdb'])
301
- if protein_complex is not None:
302
- html = static.COMPLEX_RENDERING_TEMPLATE.format(complex=protein_complex, fmt=extension)
303
-
304
- return static.IFRAME_TEMPLATE.format(html=html)
305
-
306
  # def create_complex_view_html(
307
  # complex_path, pocket_path_dict=None,
308
  # interactive_ligands=True, interactive_pockets=True
@@ -413,23 +391,24 @@ def show_complex(complex_path):
413
  # html = static.COMPLEX_RENDERING_TEMPLATE.format(viewer_models=viewer_models)
414
  # return static.IFRAME_TEMPLATE.format(html=html)
415
 
416
- def create_result_table_html(summary_df, opts=(), progress=gr.Progress(track_tqdm=True)):
417
- html_df = summary_df.copy()
418
  column_aliases = {
 
 
419
  'ID1': 'Compound ID',
420
  'ID2': 'Target ID',
421
- 'X1': 'Compound SMILES',
422
- 'ligand_conf_path': 'Pose',
423
- 'output_path': 'Pose'
424
  }
425
- # drop any columns ending with '_path'
426
- hidden_cols = [col for col in html_df.columns if col.endswith('_path')]
427
-
428
  html_df.rename(columns=column_aliases, inplace=True)
429
- if 'Compound' in html_df.columns and 'Exclude Molecular Graph' not in opts:
430
- html_df['Compound'] = html_df['Compound'].apply(PandasTools.PrintAsImageString)
431
- else:
432
- html_df.drop(['Compound'], axis=1, inplace=True)
433
 
434
  # if 'Scaffold' in html_df.columns and 'Exclude Scaffold Graph' not in opts:
435
  # html_df['Scaffold'] = html_df['Scaffold'].parallel_apply(
@@ -437,13 +416,13 @@ def create_result_table_html(summary_df, opts=(), progress=gr.Progress(track_tqd
437
  # else:
438
  # html_df.drop(['Scaffold'], axis=1, inplace=True)
439
 
440
- # html_df.index.name = 'Index'
441
-
442
  num_cols = html_df.select_dtypes('number').columns
443
  num_col_colors = sns.color_palette('husl', len(num_cols))
444
  bool_cols = html_df.select_dtypes(bool).columns
445
 
446
- image_zoom_formatter = HTMLTemplateFormatter(template='<div class="image-zoom-viewer"><%= value %></div>')
 
 
447
  uniprot_id_formatter = HTMLTemplateFormatter(
448
  template='<% if (value == value) { ' # Check if value is not NaN
449
  'if (/^[OPQ][0-9][A-Z0-9]{3}[0-9]|[A-NR-Z][0-9]([A-Z][A-Z0-9]{2}[0-9]){1,2}$/.test(value)) '
@@ -473,12 +452,27 @@ def create_result_table_html(summary_df, opts=(), progress=gr.Progress(track_tqd
473
 
474
  # html = df.to_html(file)
475
  # return html
 
 
 
 
 
 
 
 
 
 
476
 
477
  report_table = pn.widgets.Tabulator(
478
  html_df, formatters=formatters,
479
- frozen_columns=['Compound ID', 'Compound'],
480
  hidden_columns=hidden_cols,
481
- disabled=True, sizing_mode='stretch_both', pagination='local', page_size=10
 
 
 
 
 
482
  )
483
 
484
  for i, col in enumerate(num_cols):
@@ -488,7 +482,7 @@ def create_result_table_html(summary_df, opts=(), progress=gr.Progress(track_tqd
488
  subset=html_df.columns == col, cmap=cmap)
489
 
490
  # TODO change this to use commonn substructures
491
- pie_charts = {}
492
  # for y in html_df.columns.intersection(['Interaction Probability', 'Binding Affinity (IC50 [nM])']):
493
  # for category in categories:
494
  # pie_charts[y][category] = []
@@ -505,68 +499,6 @@ def create_result_table_html(summary_df, opts=(), progress=gr.Progress(track_tqd
505
  # pie_charts[y] = {k: v for k, v in pie_charts[y].items() if any(v)}
506
  # pie_charts = {k: v for k, v in pie_charts.items() if any(v)}
507
 
508
- panel_css = """
509
- .tabulator {
510
- font-family: Courier New !important;
511
- font-weight: normal !important;
512
- font-size: 12px !important;
513
- }
514
-
515
- .tabulator-cell {
516
- overflow: visible !important;
517
- align-content: center !important;
518
- }
519
-
520
- .tabulator-cell:hover {
521
- z-index: 1000 !important;
522
- }
523
-
524
- .image-zoom-viewer {
525
- display: inline-block;
526
- overflow: visible;
527
- z-index: 1000;
528
- }
529
-
530
- .image-zoom-viewer::after {
531
- content: "";
532
- top: 0;
533
- left: 0;
534
- width: 100%;
535
- height: 100%;
536
- pointer-events: none;
537
- }
538
-
539
- .image-zoom-viewer:hover::after {
540
- pointer-events: all;
541
- }
542
-
543
- /* When hovering over the container, scale its child (the SVG) */
544
- .tabulator-cell:hover .image-zoom-viewer svg {
545
- padding: 3px;
546
- position: absolute;
547
- background-color: rgba(250, 250, 250, 0.854);
548
- box-shadow: 0 0 10px rgba(0, 0, 0, 0.618);
549
- border-radius: 3px;
550
- transform: scale(3); /* Scale up the SVG */
551
- transition: transform 0.3s ease;
552
- pointer-events: none; /* Prevents the SVG from blocking mouse interactions */
553
- z-index: 1000;
554
- }
555
- """
556
-
557
- pn.extension(
558
- raw_css=[panel_css],
559
- js_files={'panel_custom': 'app/panel.js'},
560
- # js_modules={'3Dmol': 'static/3Dmol-min.js'},
561
- inline=True,
562
- )
563
-
564
- template = pn.template.VanillaTemplate(
565
- sidebar=[],
566
- header=False,
567
- busy_indicator=None,
568
- )
569
-
570
  # stats_pane = pn.Column()
571
  # if pie_charts:
572
  # for score_name, figure_dict in pie_charts.items():
@@ -584,64 +516,34 @@ def create_result_table_html(summary_df, opts=(), progress=gr.Progress(track_tqd
584
  # template.main.append(
585
  # pn.Card(stats_pane, sizing_mode='stretch_width', title='Summary Statistics', margin=10)
586
  # )
587
-
588
- template.main.append(
589
- pn.Card(report_table, title=f'GenFBDD Results', # width=1200,
590
- margin=10)
591
- )
592
 
593
  with tempfile.TemporaryDirectory() as tmpdir:
594
  file = Path(tmpdir) / 'report.html'
595
- template.save(file)
596
- html_str = file.read_text()
597
- iframe_html = static.IFRAME_TEMPLATE.format(html=html_str)
 
598
  return iframe_html
599
 
600
 
601
- def pdb_query(query, method):
602
- """Downloads protein structure data or searches FASTA sequence."""
603
- gr.Info(f'Querying protein by {method}...')
604
- try:
605
- if method == 'PDB ID':
606
- url = f"https://files.rcsb.org/download/{query}.pdb"
607
- return download_file(url)
608
- elif method == 'UniProt ID':
609
- pdb_ids = uniprot_to_pdb(query)
610
- if pdb_ids:
611
- # Download the first associated PDB file
612
- return download_file(f"https://files.rcsb.org/download/{pdb_ids[0]}.pdb")
613
- else:
614
- raise ValueError(f"No PDB IDs found for UniProt ID: {query}")
615
- elif method == 'FASTA Sequence':
616
- pdb_ids = fasta_to_pdb(query)
617
- if pdb_ids:
618
- # Download the first associated PDB file
619
- return download_file(f"https://files.rcsb.org/download/{pdb_ids[0]}.pdb")
620
- else:
621
- raise ValueError("No PDB IDs found for the provided FASTA sequence.")
622
- else:
623
- raise ValueError(f"Unsupported method: {method}")
624
- except Exception as e:
625
- gr.Warning(f"Error downloading PDB file: {e}")
626
- return None
627
-
628
-
629
  def download_file(url):
630
  """Downloads a small file to a temporary location, preserving its filename."""
631
- try:
632
- response = requests.get(url)
633
- response.raise_for_status()
 
634
 
635
- filename = Path(url).name
636
- temp_dir = Path(tempfile.gettempdir())
637
- temp_path = temp_dir / filename
638
 
639
- temp_path.write_bytes(response.content)
640
 
641
- return str(temp_path)
642
- except Exception as e:
643
- gr.Error(f"Download error: {e}")
644
- return None
645
 
646
 
647
  def uniprot_to_pdb(uniprot_id):
@@ -681,7 +583,6 @@ def uniprot_to_pdb(uniprot_id):
681
  data = response.json()
682
  return [entry["identifier"] for entry in data.get("result_set", [])]
683
  except Exception as e:
684
- print(f"Error querying UniProt ID: {e}")
685
  return []
686
 
687
 
@@ -711,5 +612,4 @@ def fasta_to_pdb(fasta_sequence):
711
  data = response.json()
712
  return [entry["identifier"] for entry in data.get("result_set", [])]
713
  except Exception as e:
714
- print(f"Error querying FASTA sequence: {e}")
715
  return []
 
3
  from email.mime.text import MIMEText
4
  from email.utils import formatdate, make_msgid
5
  from functools import cache
6
+ import html
7
  import os
8
  from pathlib import Path
9
  import smtplib
 
12
 
13
  import pandas as pd
14
  from bokeh.models import NumberFormatter, BooleanFormatter, HTMLTemplateFormatter
15
+ from bokeh.resources import INLINE
16
  import gradio as gr
17
  import pytz
18
  import panel as pn
19
  import seaborn as sns
20
  from markdown import markdown
21
  from rdkit import Chem, RDConfig
22
+ from rdkit.Chem import Crippen, Descriptors, rdMolDescriptors, Lipinski, rdmolops, Draw
23
  import requests
24
 
25
  from app import static
 
155
 
156
 
157
  SCORE_MAP = {
158
+ 'Synthetic Accessibility': sascorer.calculateScore,
159
  'LogP': Crippen.MolLogP,
160
  'Molecular Weight': Descriptors.MolWt,
161
  'Number of Atoms': rdMolDescriptors.CalcNumAtoms,
 
182
  }
183
 
184
 
 
 
 
 
 
 
 
 
 
 
185
  def get_timezone_by_ip(ip, session):
186
  try:
187
  data = session.get(f'https://worldtimeapi.org/api/ip/{ip}').json()
 
281
  return content, extension, None
282
 
283
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
284
  # def create_complex_view_html(
285
  # complex_path, pocket_path_dict=None,
286
  # interactive_ligands=True, interactive_pockets=True
 
391
  # html = static.COMPLEX_RENDERING_TEMPLATE.format(viewer_models=viewer_models)
392
  # return static.IFRAME_TEMPLATE.format(html=html)
393
 
394
+ def create_result_table_html(summary_df, result_info, opts=(), progress=gr.Progress(track_tqdm=True)):
395
+ html_df = summary_df.copy().drop(columns=['mol'])
396
  column_aliases = {
397
+ 'out_path': 'Pose',
398
+ 'ligand_conf_path': 'Pose',
399
  'ID1': 'Compound ID',
400
  'ID2': 'Target ID',
401
+ 'X1': 'Fragment SMILES',
402
+ 'X1^': 'Compound SMILES',
403
+ 'name': 'Complex Name',
404
  }
405
+ output_dir = Path(result_info['output_dir'])
406
+ job_type = result_info['type']
 
407
  html_df.rename(columns=column_aliases, inplace=True)
408
+ html_df['Pose'] = html_df['Pose'].apply(lambda x: str(output_dir / job_type / x))
409
+ # drop remaining columns ending with '_path'
410
+ hidden_cols = [col for col in html_df.columns if col.endswith('_path')]
411
+ html_df.index.name = 'Index'
412
 
413
  # if 'Scaffold' in html_df.columns and 'Exclude Scaffold Graph' not in opts:
414
  # html_df['Scaffold'] = html_df['Scaffold'].parallel_apply(
 
416
  # else:
417
  # html_df.drop(['Scaffold'], axis=1, inplace=True)
418
 
 
 
419
  num_cols = html_df.select_dtypes('number').columns
420
  num_col_colors = sns.color_palette('husl', len(num_cols))
421
  bool_cols = html_df.select_dtypes(bool).columns
422
 
423
+ image_zoom_formatter = HTMLTemplateFormatter(
424
+ template='<img src="data:image/svg+xml,<%= value %>" alt="Molecule" class="zoom-img">'
425
+ )
426
  uniprot_id_formatter = HTMLTemplateFormatter(
427
  template='<% if (value == value) { ' # Check if value is not NaN
428
  'if (/^[OPQ][0-9][A-Z0-9]{3}[0-9]|[A-NR-Z][0-9]([A-Z][A-Z0-9]{2}[0-9]){1,2}$/.test(value)) '
 
452
 
453
  # html = df.to_html(file)
454
  # return html
455
+ static_url = "gradio_api/file=app/static/"
456
+ pn.extension(
457
+ design='material',
458
+ css_files=[
459
+ static_url + 'panel.css'
460
+ ],
461
+ js_files={
462
+ 'panel_custom': static_url + 'panel.js',
463
+ },
464
+ )
465
 
466
  report_table = pn.widgets.Tabulator(
467
  html_df, formatters=formatters,
468
+ frozen_columns=['Pose', 'Compound ID', 'Compound'],
469
  hidden_columns=hidden_cols,
470
+ sizing_mode='stretch_both',
471
+ disabled=True, selectable=False,
472
+ pagination='local',
473
+ configuration={
474
+ 'rowHeight': 60,
475
+ },
476
  )
477
 
478
  for i, col in enumerate(num_cols):
 
482
  subset=html_df.columns == col, cmap=cmap)
483
 
484
  # TODO change this to use commonn substructures
485
+ # pie_charts = {}
486
  # for y in html_df.columns.intersection(['Interaction Probability', 'Binding Affinity (IC50 [nM])']):
487
  # for category in categories:
488
  # pie_charts[y][category] = []
 
499
  # pie_charts[y] = {k: v for k, v in pie_charts[y].items() if any(v)}
500
  # pie_charts = {k: v for k, v in pie_charts.items() if any(v)}
501
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
502
  # stats_pane = pn.Column()
503
  # if pie_charts:
504
  # for score_name, figure_dict in pie_charts.items():
 
516
  # template.main.append(
517
  # pn.Card(stats_pane, sizing_mode='stretch_width', title='Summary Statistics', margin=10)
518
  # )
519
+ report = pn.Column(pn.Accordion(
520
+ (f'{job_type.title()} Results', report_table),
521
+ toggle=True, margin=5, active=[0]
522
+ ))
 
523
 
524
  with tempfile.TemporaryDirectory() as tmpdir:
525
  file = Path(tmpdir) / 'report.html'
526
+ report.save(file)
527
+ # iframe_html = static.IFRAME_LINK_TEMPLATE.format(src="gradio_api/file=" + str(file))
528
+ html_str = file.read_text() # .replace('\'', '\"')
529
+ iframe_html = static.IFRAME_TEMPLATE.format(srcdoc=html.escape(html_str), aspect_ratio='1.090 / 1')
530
  return iframe_html
531
 
532
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
533
  def download_file(url):
534
  """Downloads a small file to a temporary location, preserving its filename."""
535
+ response = requests.get(url)
536
+ if response.status_code == 404:
537
+ raise ValueError('No record found for the provided PDB ID.')
538
+ response.raise_for_status()
539
 
540
+ filename = Path(url).name
541
+ temp_dir = Path(tempfile.gettempdir()) / 'gradio'
542
+ temp_path = temp_dir / filename
543
 
544
+ temp_path.write_bytes(response.content)
545
 
546
+ return str(temp_path)
 
 
 
547
 
548
 
549
  def uniprot_to_pdb(uniprot_id):
 
583
  data = response.json()
584
  return [entry["identifier"] for entry in data.get("result_set", [])]
585
  except Exception as e:
 
586
  return []
587
 
588
 
 
612
  data = response.json()
613
  return [entry["identifier"] for entry in data.get("result_set", [])]
614
  except Exception as e:
 
615
  return []
app/main.py CHANGED
@@ -3,6 +3,7 @@ import zipfile
3
  from datetime import datetime
4
  from pathlib import Path
5
  from time import sleep, time
 
6
 
7
  import torch
8
  from email_validator import validate_email, EmailNotValidError
@@ -12,17 +13,17 @@ from gradio_rangeslider import RangeSlider
12
  from omegaconf import OmegaConf
13
  import pandas as pd
14
  from rdkit import Chem
15
- from rdkit.Chem import PandasTools
16
 
17
  from inference import (read_fragment_library, process_fragment_library, extract_pockets,
18
  dock_fragments, generate_linkers, select_fragment_pairs)
19
  from app import static, fn, db
20
 
21
 
22
- gr.set_static_paths(paths=["data/", "results/"])
23
  job_db = db.init_job_db()
24
 
25
- FRAG_LIBS = {
26
  lib_path.stem.replace('_', ' '): str(lib_path) for lib_path in Path('data/fragment_libraries').glob('*')
27
  }
28
 
@@ -48,20 +49,14 @@ POCKET_EXTRACT_OPTS = {
48
  }
49
 
50
 
51
- # TODO import from inference
52
- def process_drug_library_upload(library_upload):
53
- if library_upload.endswith('.csv'):
54
- df = pd.read_csv(library_upload)
55
- elif library_upload.endswith('.sdf'):
56
- df = PandasTools.LoadSDF(
57
- library_upload,
58
- smilesName='X1', molColName='mol',
59
- )
60
- else:
61
- raise gr.Error('Current supported fragment library formats only include CSV and SDF files.')
62
- fn.validate_columns(df, ['X1'])
63
- return df
64
 
 
65
 
66
  def query_job_status(job_id):
67
  gr.Info('Start querying the job database...')
@@ -69,10 +64,10 @@ def query_job_status(job_id):
69
  retry = 0
70
  while not stop:
71
  try:
72
- sleep(5)
73
  job = job_db.job_lookup(job_id)
74
  if job:
75
  if job['status'] == "RUNNING":
 
76
  yield {
77
  pred_lookup_status: f'''
78
  Your job (ID: **{job['id']}**) started at **{job['start_time']}** and is **RUNNING...**
@@ -86,7 +81,7 @@ using the job id. You will also receive an email notification once the job is do
86
  }
87
  if job['status'] == "COMPLETED":
88
  stop = True
89
- msg = f"Your GenFBDD job (ID: {job['id']}) has been **COMPLETED**"
90
  msg += f" at {job['end_time']}" if job.get('end_time') else ""
91
  msg += f" and the results will expire by {job['expiry_time']}." if job.get('expiry_time') else "."
92
  msg += f' Redirecting to the Results page...'
@@ -112,13 +107,14 @@ using the job id. You will also receive an email notification once the job is do
112
  tabs: gr.Tabs(selected='job'),
113
  }
114
  else:
115
- stop = (retry > 3)
116
  if not stop:
117
  msg = f'Job ID {job_id} not found. Retrying... ({retry})'
118
  else:
119
  msg = f'Job ID {job_id} not found after {retry} retries. Please double-check the job ID.'
120
  gr.Info(msg)
121
  retry += 1
 
122
  yield {
123
  pred_lookup_status: msg,
124
  pred_lookup_btn: gr.Button(visible=True),
@@ -177,7 +173,7 @@ def job_validate(
177
  job_id = str(uuid.uuid4())
178
  job_info = {
179
  'id': job_id,
180
- 'status': 'RUNNING',
181
  'fragment_library_file': frag_file,
182
  'protein_structure_file': prot_file,
183
  'pocket_extraction_method': pocket_extraction_method,
@@ -200,10 +196,10 @@ def dock_link(
200
  linker_frag_dist, linker_strategy, linker_n_mols, linker_size, linker_steps,
201
  job_info
202
  ):
 
203
  job_id = job_info['id']
204
  pocket_extract_method = job_info['pocket_extraction_method']
205
  pocket_path_dict = job_info['protein_pocket_files']
206
- update_info = {}
207
 
208
  config = OmegaConf.load('configs/gen_fbdd_v1.yaml')
209
  device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
@@ -213,6 +209,11 @@ def dock_link(
213
  frag_lib['X2'] = prot
214
  frag_lib['ID2'] = Path(prot).stem
215
 
 
 
 
 
 
216
  try:
217
  docking_df = dock_fragments(
218
  df=frag_lib, out_dir=out_dir,
@@ -230,7 +231,8 @@ def dock_link(
230
  temp_sigma_data_tr=config.temp_sigma_data_tr,
231
  temp_sigma_data_rot=config.temp_sigma_data_rot,
232
  temp_sigma_data_tor=config.temp_sigma_data_tor,
233
- save_docking=pocket_extract_method == 'clustering', device=device,
 
234
  )
235
 
236
  linking_df = select_fragment_pairs(
@@ -290,7 +292,6 @@ def dock_link(
290
  )
291
 
292
 
293
-
294
  def get_session_state(request: gr.Request):
295
  return request
296
 
@@ -299,7 +300,7 @@ THEME = gr.themes.Base(
299
  spacing_size="sm", text_size='md', font=gr.themes.GoogleFont("Roboto"),
300
  primary_hue='emerald', secondary_hue='emerald', neutral_hue='slate',
301
  ).set(
302
- body_background_fill='*primary_50'
303
  # background_fill_primary='#eef3f9',
304
  # background_fill_secondary='white',
305
  # checkbox_label_background_fill='#eef3f9',
@@ -327,7 +328,7 @@ with gr.Blocks(theme=THEME, title='GenFBDD', css=static.CSS, delete_cache=(3600,
327
 
328
  # script_init_frame = gr.HTML(static.PROTEIN_VIEW_IFRAME)
329
  with gr.Tabs() as tabs:
330
- with gr.Tab(label='Home', id='home'):
331
  gr.Markdown('''
332
  # GenFBDD - A Fragment-Based Drug Design Protocol Based on SOTA Molecular Generative Models
333
 
@@ -342,7 +343,7 @@ with gr.Blocks(theme=THEME, title='GenFBDD', css=static.CSS, delete_cache=(3600,
342
  frag_lib_dropdown = gr.Dropdown(
343
  label='Select a Preset Fragment Library',
344
  choices=list(FRAG_LIBS.keys()),
345
- value=None,
346
  )
347
  # with gr.Row():
348
  # gr.File(label='Example SDF fragment library',
@@ -350,18 +351,20 @@ with gr.Blocks(theme=THEME, title='GenFBDD', css=static.CSS, delete_cache=(3600,
350
  # gr.File(label='Example CSV fragment library',
351
  # value='data/examples/fragment_library.csv', interactive=False)
352
  frag_lib_upload_btn = gr.UploadButton(
353
- label='OR Upload Your Own Library', variant='primary'
354
  )
355
 
356
  frag_lib_file = gr.File(
357
- label='Fragment Library File (Original)', file_count='single', interactive=False, visible=False
 
 
358
  )
359
- frag_lib_orig_df = gr.State(value=pd.DataFrame())
360
- frag_lib_mod_df = gr.State(value=pd.DataFrame())
361
  # TODO: Tabulator with gr.HTML() for fragment library preview
362
  frag_lib_view = gr.DataFrame(
 
363
  visible=True, interactive=False,
364
- elem_id='frag_lib_view',
365
  )
366
 
367
  with gr.Group():
@@ -375,7 +378,9 @@ with gr.Blocks(theme=THEME, title='GenFBDD', css=static.CSS, delete_cache=(3600,
375
  value=['Dehalogenate Fragments', 'Discard Inorganic Fragments'],
376
  interactive=True,
377
  )
378
- frag_lib_process_btn = gr.Button(value='Process Fragments', variant='primary')
 
 
379
  # Fragment library preview
380
 
381
  with gr.Column(variant='panel'):
@@ -394,22 +399,25 @@ with gr.Blocks(theme=THEME, title='GenFBDD', css=static.CSS, delete_cache=(3600,
394
  )
395
  prot_query_input = gr.Textbox(
396
  show_label=False, placeholder='Enter the protein query here',
397
- scale=3,
398
  )
399
 
400
  with gr.Row():
401
- prot_query_btn = gr.Button(value='Query', variant='primary', scale=1)
 
 
 
402
  prot_upload_btn = gr.UploadButton(
403
  label='OR Upload Your PDB/FASTA File', variant='primary',
404
  file_types=['.pdb', '.fasta'],
405
- scale=2
406
  )
407
 
408
  input_prot_file = gr.File(
409
- label='Protein Structure File (Original)', file_count='single',
410
- interactive=False, visible=False
411
  )
412
- input_prot_view = gr.HTML('<div id="input_protein_view" class="mol-container"></div>')
413
 
414
  with gr.Group():
415
  pocket_extract_dropdown = gr.Dropdown(
@@ -422,12 +430,14 @@ with gr.Blocks(theme=THEME, title='GenFBDD', css=static.CSS, delete_cache=(3600,
422
  selected_pocket = gr.Textbox(visible=False)
423
  selected_ligand = gr.Textbox(visible=False)
424
  pocket_files = gr.Files(visible=False)
425
- pocket_extract_btn = gr.Button(value='Extract Pocket', variant='primary')
 
 
426
  # Target protein preview
427
  with gr.Row():
428
  with gr.Column(variant='panel'):
429
  gr.Markdown('## Dock Phase Settings')
430
- n_confs_per_frag = gr.Slider(
431
  value=5, minimum=1, maximum=20, step=1,
432
  label="Number of conformers to generate per fragment",
433
  interactive=True
@@ -450,7 +460,7 @@ with gr.Blocks(theme=THEME, title='GenFBDD', css=static.CSS, delete_cache=(3600,
450
  )
451
  with gr.Column(variant='panel'):
452
  gr.Markdown('## Link Phase Settings')
453
- frag_conf_combo_strategy = gr.Radio(
454
  label='Select a Fragment-Conformer Linking Strategy',
455
  choices=[
456
  'Link Pairs of Fragment-Conformers Contacting the Pocket',
@@ -458,12 +468,12 @@ with gr.Blocks(theme=THEME, title='GenFBDD', css=static.CSS, delete_cache=(3600,
458
  ],
459
  value='Link Pairs of Fragment-Conformers Contacting the Pocket',
460
  )
461
- frag_dist_range_slider = RangeSlider(
462
  value=[2, 8], minimum=1, maximum=10, step=1,
463
  label="Fragment-Conformer Distance Range (Å) Eligible for Linking",
464
  interactive=True
465
  )
466
- n_mols_per_combo_slider = gr.Slider(
467
  value=10, minimum=1, maximum=20, step=1,
468
  label="Number of molecules to generate per fragment conformer combination",
469
  interactive=True
@@ -474,13 +484,13 @@ with gr.Blocks(theme=THEME, title='GenFBDD', css=static.CSS, delete_cache=(3600,
474
  choices=['DiffLinker'],
475
  interactive=True,
476
  )
477
- linker_size_slider = gr.Slider(
478
  minimum=0, maximum=20, step=1,
479
  label="Linker Size",
480
  info="0: automatically predicted; >=1: fixed size",
481
  interactive=True
482
  )
483
- linker_steps_slider = gr.Slider(
484
  minimum=100, maximum=500, step=10,
485
  label="Number of Denoising Steps for Generating Linkers",
486
  interactive=True
@@ -489,14 +499,16 @@ with gr.Blocks(theme=THEME, title='GenFBDD', css=static.CSS, delete_cache=(3600,
489
  email_input =gr.Textbox(
490
  label='Email Address (Optional)',
491
  info="Your email address will be used to notify you of the status of your job. "
492
- "If you cannot receive the email, please check your spam/junk folder."
 
493
  )
494
  with gr.Column():
495
- clr_btn = gr.ClearButton(
496
- value='Reset Inputs',
497
- components=[]
 
 
498
  )
499
- run_btn = gr.Button(value='Run GenFBDD', variant='primary')
500
  with gr.Tab(label='Results', id='result'):
501
  # Results
502
  result_state = gr.State(value={})
@@ -509,14 +521,14 @@ with gr.Blocks(theme=THEME, title='GenFBDD', css=static.CSS, delete_cache=(3600,
509
  filters = gr.CheckboxGroup(list(fn.FILTER_MAP.keys()), label='Compound Filters')
510
  with gr.Row():
511
  prop_clr_btn = gr.ClearButton(value='Clear Properties', interactive=False)
512
- prop_calc_btn = gr.Button(value='Calculate Properties', interactive=False)
513
 
514
- with gr.Row():
515
- result_table_view = gr.HTML('<div id="result_view" class="fancy-table"></div>')
516
- with gr.Column():
517
- result_prot_view = gr.HTML('<div id="result_protein_view" class="mol-container"></div>')
518
- result_file_btn = gr.Button(value='Create Result File', visible=False)
519
- result_download_file = gr.File(label='Download Result File', visible=False)
520
 
521
  with gr.Tab(label='Job Status', id='job'):
522
  gr.Markdown('''
@@ -540,10 +552,10 @@ with gr.Blocks(theme=THEME, title='GenFBDD', css=static.CSS, delete_cache=(3600,
540
  pred_lookup_status = gr.Markdown()
541
 
542
  # Event handlers
543
- ## Home tab
544
  ### Fragment Library
545
- frag_lib_dropdown.change(
546
- fn=lambda lib: gr.File(FRAG_LIBS[lib], visible=True),
547
  inputs=[frag_lib_dropdown],
548
  outputs=[frag_lib_file],
549
  )
@@ -554,8 +566,8 @@ with gr.Blocks(theme=THEME, title='GenFBDD', css=static.CSS, delete_cache=(3600,
554
  )
555
 
556
  # Changing the file updates the original df, the modified df, and the view
557
- frag_lib_file.change(
558
- fn=read_fragment_library,
559
  inputs=[frag_lib_file],
560
  outputs=[frag_lib_orig_df],
561
  ).success(
@@ -586,7 +598,7 @@ with gr.Blocks(theme=THEME, title='GenFBDD', css=static.CSS, delete_cache=(3600,
586
  }
587
  elif filepath.suffix == '.fasta':
588
  seq = next(SeqIO.parse(file, 'fasta')).seq
589
- filepath = fn.pdb_query(seq, method='FASTA Sequence')
590
  return {
591
  input_prot_file: gr.File(str(filepath), visible=True),
592
  prot_query_input: seq,
@@ -611,17 +623,45 @@ with gr.Blocks(theme=THEME, title='GenFBDD', css=static.CSS, delete_cache=(3600,
611
  outputs=[input_prot_file, prot_query_dropdown, prot_query_input],
612
  )
613
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
614
  prot_query_btn.click(
615
- fn=fn.pdb_query,
616
  inputs=[prot_query_input, prot_query_dropdown],
617
  outputs=[input_prot_file],
618
  )
619
 
620
- input_prot_file.change(
621
- fn=lambda x, y: [gr.File(str(x), visible=True)],
 
 
622
  inputs=[input_prot_file, input_prot_view],
623
- outputs=[input_prot_file],
624
- js=static.CREATE_MOL_VIEW,
625
  )
626
 
627
  #### Pocket Extraction
@@ -652,7 +692,7 @@ with gr.Blocks(theme=THEME, title='GenFBDD', css=static.CSS, delete_cache=(3600,
652
  outputs=[pocket_files, selected_ligand, selected_pocket],
653
  ).success(
654
  fn=lambda x, y: gr.Info('Pocket extraction completed.'),
655
- js=static.UPDATE_PROT_VIEW,
656
  inputs=[pocket_files, input_prot_view],
657
  )
658
 
@@ -676,18 +716,40 @@ with gr.Blocks(theme=THEME, title='GenFBDD', css=static.CSS, delete_cache=(3600,
676
  fn=dock_link,
677
  inputs=[
678
  frag_lib_mod_df, input_prot_file,
679
- dock_steps, n_confs_per_frag, dock_confidence_cutoff,
680
- frag_dist_range_slider, frag_conf_combo_strategy,n_mols_per_combo_slider,
681
- linker_size_slider, linker_steps_slider,
682
  run_state
683
  ],
684
  outputs=[result_state, run_state],
685
  concurrency_limit=1, concurrency_id="gpu_queue"
686
  )
687
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
688
  ### Job Status
689
  user_job_lookup = pred_lookup_btn.click(
690
- lambda: '<div class="loader"></ div>',
691
  outputs=loader_html,
692
  ).success(
693
  fn=query_job_status,
@@ -734,29 +796,44 @@ with gr.Blocks(theme=THEME, title='GenFBDD', css=static.CSS, delete_cache=(3600,
734
  protein_structure_file = Path(result_info['protein_structure_file'])
735
  if result_type == 'docking':
736
  result_df = pd.read_csv(result_dir / 'docking_summary.csv')
737
- result_df['Compound'] = result_df['X1'].apply(Chem.MolFromSmiles)
738
  elif result_type == 'linking':
739
  result_df = pd.read_csv(result_dir / 'linking_summary.csv')
740
  result_df = result_df[~result_df['X1^'].str.contains('.', regex=False)]
741
- result_df['Compound'] = result_df['X1^'].apply(Chem.MolFromSmiles)
742
- result_df.dropna(subset=['Compound'], inplace=True)
743
  else:
744
  raise gr.Error('Invalid result type')
 
 
 
 
 
 
 
 
 
 
 
 
 
745
  return {
746
  result_table_orig_df: result_df,
747
  result_table_mod_df: result_df.copy(deep=True),
748
  result_protein_file: str(protein_structure_file),
 
749
  }
750
 
751
  def update_table(orig_df, score_list, filter_list, progress=gr.Progress(track_tqdm=True)):
 
752
  mod_df = orig_df.copy()
753
  try:
754
  for filter_name in filter_list:
755
- mod_df[filter_name] = mod_df['Compound'].parallel_apply(
756
  lambda x: fn.FILTER_MAP[filter_name](x) if not pd.isna(x) else x)
757
 
758
  for score_name in score_list:
759
- mod_df[score_name] = mod_df['Compound'].parallel_apply(
760
  lambda x: fn.SCORE_MAP[score_name](x) if not pd.isna(x) else x)
761
 
762
  return {result_table_mod_df: mod_df}
@@ -772,45 +849,47 @@ with gr.Blocks(theme=THEME, title='GenFBDD', css=static.CSS, delete_cache=(3600,
772
  )
773
 
774
  result_protein_file.change(
775
- fn=lambda x, y: str(x),
776
- js=static.CREATE_MOL_VIEW,
777
  inputs=[result_protein_file, result_prot_view],
778
- outputs=[result_protein_file],
779
  )
780
  result_table_mod_df.change(
781
  fn=fn.create_result_table_html,
782
- inputs=[result_table_mod_df],
783
  outputs=[result_table_view]
784
  ).success(
785
- fn=lambda x: gr.Button(visible=True),
786
  inputs=[result_file_btn],
787
- outputs=[result_file_btn],
788
  )
789
  prop_calc_btn.click(
790
  fn=update_table,
791
  inputs=[result_table_orig_df, scores, filters],
792
  outputs=[result_table_mod_df],
793
- show_progress='full',
794
  )
795
  prop_clr_btn.click(
796
- fn=lambda orig_df: [orig_df, [], [], gr.Button(visible=False), gr.File(visible=False)],
797
  inputs=[result_table_orig_df],
798
- outputs=[result_table_mod_df, scores, filters, result_file_btn, result_download_file],
799
- show_progress='full',
800
  )
801
 
802
-
803
  def generate_result_zip(result_info, compound_mod_df, protein_file):
804
- folder_path = Path(result_info['output_dir'])
805
- zip_path = folder_path.with_suffix('.zip')
806
- compound_mod_df.to_csv(folder_path / f'{result_info["type"]}_summary.csv', index=False)
 
 
 
807
  with zipfile.ZipFile(zip_path, 'w') as zip_file:
808
- for file in folder_path.iterdir():
809
- zip_file.write(file, arcname=file.name)
810
- # Copy protein structure file to zip
 
811
  zip_file.write(Path(protein_file), arcname=Path(protein_file).name)
 
812
  return gr.File(str(zip_path), visible=True)
813
 
 
814
  result_file_btn.click(
815
  fn=generate_result_zip,
816
  inputs=[result_state, result_table_mod_df, result_protein_file],
 
3
  from datetime import datetime
4
  from pathlib import Path
5
  from time import sleep, time
6
+ import urllib.parse
7
 
8
  import torch
9
  from email_validator import validate_email, EmailNotValidError
 
13
  from omegaconf import OmegaConf
14
  import pandas as pd
15
  from rdkit import Chem
16
+ from rdkit.Chem import PandasTools, Draw
17
 
18
  from inference import (read_fragment_library, process_fragment_library, extract_pockets,
19
  dock_fragments, generate_linkers, select_fragment_pairs)
20
  from app import static, fn, db
21
 
22
 
23
+ gr.set_static_paths(paths=["data/", "results/", "app/"])
24
  job_db = db.init_job_db()
25
 
26
+ FRAG_LIBS = {'': None} | {
27
  lib_path.stem.replace('_', ' '): str(lib_path) for lib_path in Path('data/fragment_libraries').glob('*')
28
  }
29
 
 
49
  }
50
 
51
 
52
+ def gr_error_wrapper(func):
53
+ def wrapper(*args, **kwargs):
54
+ try:
55
+ return func(*args, **kwargs)
56
+ except Exception as e:
57
+ raise gr.Error(str(e))
 
 
 
 
 
 
 
58
 
59
+ return wrapper
60
 
61
  def query_job_status(job_id):
62
  gr.Info('Start querying the job database...')
 
64
  retry = 0
65
  while not stop:
66
  try:
 
67
  job = job_db.job_lookup(job_id)
68
  if job:
69
  if job['status'] == "RUNNING":
70
+ sleep(5)
71
  yield {
72
  pred_lookup_status: f'''
73
  Your job (ID: **{job['id']}**) started at **{job['start_time']}** and is **RUNNING...**
 
81
  }
82
  if job['status'] == "COMPLETED":
83
  stop = True
84
+ msg = f"Your GenFBDD job (ID: {job['id']}) has been COMPLETED"
85
  msg += f" at {job['end_time']}" if job.get('end_time') else ""
86
  msg += f" and the results will expire by {job['expiry_time']}." if job.get('expiry_time') else "."
87
  msg += f' Redirecting to the Results page...'
 
107
  tabs: gr.Tabs(selected='job'),
108
  }
109
  else:
110
+ stop = (retry > 2)
111
  if not stop:
112
  msg = f'Job ID {job_id} not found. Retrying... ({retry})'
113
  else:
114
  msg = f'Job ID {job_id} not found after {retry} retries. Please double-check the job ID.'
115
  gr.Info(msg)
116
  retry += 1
117
+ sleep(5)
118
  yield {
119
  pred_lookup_status: msg,
120
  pred_lookup_btn: gr.Button(visible=True),
 
173
  job_id = str(uuid.uuid4())
174
  job_info = {
175
  'id': job_id,
176
+ 'status': 'QUEUED',
177
  'fragment_library_file': frag_file,
178
  'protein_structure_file': prot_file,
179
  'pocket_extraction_method': pocket_extraction_method,
 
196
  linker_frag_dist, linker_strategy, linker_n_mols, linker_size, linker_steps,
197
  job_info
198
  ):
199
+ update_info = {}
200
  job_id = job_info['id']
201
  pocket_extract_method = job_info['pocket_extraction_method']
202
  pocket_path_dict = job_info['protein_pocket_files']
 
203
 
204
  config = OmegaConf.load('configs/gen_fbdd_v1.yaml')
205
  device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
 
209
  frag_lib['X2'] = prot
210
  frag_lib['ID2'] = Path(prot).stem
211
 
212
+ job_db.job_update(
213
+ job_id=job_id,
214
+ update_info={'status': 'RUNNING'},
215
+ )
216
+
217
  try:
218
  docking_df = dock_fragments(
219
  df=frag_lib, out_dir=out_dir,
 
231
  temp_sigma_data_tr=config.temp_sigma_data_tr,
232
  temp_sigma_data_rot=config.temp_sigma_data_rot,
233
  temp_sigma_data_tor=config.temp_sigma_data_tor,
234
+ save_docking=(pocket_extract_method == 'clustering'),
235
+ device=device,
236
  )
237
 
238
  linking_df = select_fragment_pairs(
 
292
  )
293
 
294
 
 
295
  def get_session_state(request: gr.Request):
296
  return request
297
 
 
300
  spacing_size="sm", text_size='md', font=gr.themes.GoogleFont("Roboto"),
301
  primary_hue='emerald', secondary_hue='emerald', neutral_hue='slate',
302
  ).set(
303
+ # body_background_fill='*primary_50'
304
  # background_fill_primary='#eef3f9',
305
  # background_fill_secondary='white',
306
  # checkbox_label_background_fill='#eef3f9',
 
328
 
329
  # script_init_frame = gr.HTML(static.PROTEIN_VIEW_IFRAME)
330
  with gr.Tabs() as tabs:
331
+ with gr.Tab(label='Start', id='start'):
332
  gr.Markdown('''
333
  # GenFBDD - A Fragment-Based Drug Design Protocol Based on SOTA Molecular Generative Models
334
 
 
343
  frag_lib_dropdown = gr.Dropdown(
344
  label='Select a Preset Fragment Library',
345
  choices=list(FRAG_LIBS.keys()),
346
+ value='',
347
  )
348
  # with gr.Row():
349
  # gr.File(label='Example SDF fragment library',
 
351
  # gr.File(label='Example CSV fragment library',
352
  # value='data/examples/fragment_library.csv', interactive=False)
353
  frag_lib_upload_btn = gr.UploadButton(
354
+ label='OR Upload Your Own Library', variant='primary', interactive=True,
355
  )
356
 
357
  frag_lib_file = gr.File(
358
+ value=None, label='Fragment Library File (Original)',
359
+ file_count='single', file_types=['.sdf', '.csv'],
360
+ interactive=False, visible=False
361
  )
362
+ frag_lib_orig_df = gr.State(value=pd.DataFrame(columns=['X1', 'ID1', 'mol']))
363
+ frag_lib_mod_df = gr.State(value=pd.DataFrame(columns=['X1', 'ID1', 'mol']))
364
  # TODO: Tabulator with gr.HTML() for fragment library preview
365
  frag_lib_view = gr.DataFrame(
366
+ value=pd.DataFrame(columns=['X1', 'ID1']), elem_id='frag_lib_view',
367
  visible=True, interactive=False,
 
368
  )
369
 
370
  with gr.Group():
 
378
  value=['Dehalogenate Fragments', 'Discard Inorganic Fragments'],
379
  interactive=True,
380
  )
381
+ frag_lib_process_btn = gr.Button(
382
+ value='Process Fragments', variant='primary', interactive=True,
383
+ )
384
  # Fragment library preview
385
 
386
  with gr.Column(variant='panel'):
 
399
  )
400
  prot_query_input = gr.Textbox(
401
  show_label=False, placeholder='Enter the protein query here',
402
+ scale=3, interactive=True
403
  )
404
 
405
  with gr.Row():
406
+ prot_query_btn = gr.Button(
407
+ value='Query', variant='primary',
408
+ scale=1, interactive=True
409
+ )
410
  prot_upload_btn = gr.UploadButton(
411
  label='OR Upload Your PDB/FASTA File', variant='primary',
412
  file_types=['.pdb', '.fasta'],
413
+ scale=2, interactive=True,
414
  )
415
 
416
  input_prot_file = gr.File(
417
+ value=None, label='Protein Structure File (Original)',
418
+ interactive=False, visible=False, file_count='single',
419
  )
420
+ input_prot_view = gr.HTML(value='<div id="input_protein_view" class="mol-container"></div>')
421
 
422
  with gr.Group():
423
  pocket_extract_dropdown = gr.Dropdown(
 
430
  selected_pocket = gr.Textbox(visible=False)
431
  selected_ligand = gr.Textbox(visible=False)
432
  pocket_files = gr.Files(visible=False)
433
+ pocket_extract_btn = gr.Button(
434
+ value='Extract Pocket', variant='primary', interactive=True
435
+ )
436
  # Target protein preview
437
  with gr.Row():
438
  with gr.Column(variant='panel'):
439
  gr.Markdown('## Dock Phase Settings')
440
+ dock_n_poses = gr.Slider(
441
  value=5, minimum=1, maximum=20, step=1,
442
  label="Number of conformers to generate per fragment",
443
  interactive=True
 
460
  )
461
  with gr.Column(variant='panel'):
462
  gr.Markdown('## Link Phase Settings')
463
+ link_frag_pose_strategy = gr.Radio(
464
  label='Select a Fragment-Conformer Linking Strategy',
465
  choices=[
466
  'Link Pairs of Fragment-Conformers Contacting the Pocket',
 
468
  ],
469
  value='Link Pairs of Fragment-Conformers Contacting the Pocket',
470
  )
471
+ link_frag_dist_range = RangeSlider(
472
  value=[2, 8], minimum=1, maximum=10, step=1,
473
  label="Fragment-Conformer Distance Range (Å) Eligible for Linking",
474
  interactive=True
475
  )
476
+ link_n_mols = gr.Slider(
477
  value=10, minimum=1, maximum=20, step=1,
478
  label="Number of molecules to generate per fragment conformer combination",
479
  interactive=True
 
484
  choices=['DiffLinker'],
485
  interactive=True,
486
  )
487
+ link_linker_size = gr.Slider(
488
  minimum=0, maximum=20, step=1,
489
  label="Linker Size",
490
  info="0: automatically predicted; >=1: fixed size",
491
  interactive=True
492
  )
493
+ link_steps = gr.Slider(
494
  minimum=100, maximum=500, step=10,
495
  label="Number of Denoising Steps for Generating Linkers",
496
  interactive=True
 
499
  email_input =gr.Textbox(
500
  label='Email Address (Optional)',
501
  info="Your email address will be used to notify you of the status of your job. "
502
+ "If you cannot receive the email, please check your spam/junk folder.",
503
+ type='email'
504
  )
505
  with gr.Column():
506
+ start_clr_btn = gr.ClearButton(
507
+ value='Reset Inputs', interactive=True,
508
+ )
509
+ run_btn = gr.Button(
510
+ value='Run GenFBDD', variant='primary', interactive=True,
511
  )
 
512
  with gr.Tab(label='Results', id='result'):
513
  # Results
514
  result_state = gr.State(value={})
 
521
  filters = gr.CheckboxGroup(list(fn.FILTER_MAP.keys()), label='Compound Filters')
522
  with gr.Row():
523
  prop_clr_btn = gr.ClearButton(value='Clear Properties', interactive=False)
524
+ prop_calc_btn = gr.Button(value='Calculate Properties', interactive=False, variant='primary')
525
 
526
+ with gr.Row():
527
+ result_table_view = gr.HTML('<div id="result_view" class="fancy-table"></div>')
528
+ with gr.Column():
529
+ result_prot_view = gr.HTML('<div id="result_protein_view" class="mol-container"></div>')
530
+ result_file_btn = gr.Button(value='Create Result File', visible=False, variant='primary')
531
+ result_download_file = gr.File(label='Download Result File', visible=False)
532
 
533
  with gr.Tab(label='Job Status', id='job'):
534
  gr.Markdown('''
 
552
  pred_lookup_status = gr.Markdown()
553
 
554
  # Event handlers
555
+ ## Start tab
556
  ### Fragment Library
557
+ frag_lib_dropdown_change = frag_lib_dropdown.change(
558
+ fn=lambda lib: gr.File(FRAG_LIBS[lib], visible=bool(lib)),
559
  inputs=[frag_lib_dropdown],
560
  outputs=[frag_lib_file],
561
  )
 
566
  )
567
 
568
  # Changing the file updates the original df, the modified df, and the view
569
+ frag_lib_file_change = frag_lib_file.change(
570
+ fn=gr_error_wrapper(read_fragment_library),
571
  inputs=[frag_lib_file],
572
  outputs=[frag_lib_orig_df],
573
  ).success(
 
598
  }
599
  elif filepath.suffix == '.fasta':
600
  seq = next(SeqIO.parse(file, 'fasta')).seq
601
+ filepath = pdb_query(seq, method='FASTA Sequence')
602
  return {
603
  input_prot_file: gr.File(str(filepath), visible=True),
604
  prot_query_input: seq,
 
623
  outputs=[input_prot_file, prot_query_dropdown, prot_query_input],
624
  )
625
 
626
+ def pdb_query(query, method):
627
+ """Downloads protein structure data or searches FASTA sequence."""
628
+ gr.Info(f'Querying protein by {method}...')
629
+ try:
630
+ if method == 'PDB ID':
631
+ url = f"https://files.rcsb.org/download/{query}.pdb"
632
+ file = fn.download_file(url)
633
+ elif method == 'UniProt ID':
634
+ pdb_ids = fn.uniprot_to_pdb(query)
635
+ if pdb_ids:
636
+ # Download the first associated PDB file
637
+ file = fn.download_file(f"https://files.rcsb.org/download/{pdb_ids[0]}.pdb")
638
+ else:
639
+ raise ValueError(f"No PDB IDs found for UniProt ID: {query}")
640
+ elif method == 'FASTA Sequence':
641
+ pdb_ids = fn.fasta_to_pdb(query)
642
+ if pdb_ids:
643
+ # Download the first associated PDB file
644
+ file = fn.download_file(f"https://files.rcsb.org/download/{pdb_ids[0]}.pdb")
645
+ else:
646
+ raise ValueError("No PDB IDs found for the provided FASTA sequence.")
647
+ else:
648
+ raise ValueError(f"Unsupported method: {method}")
649
+ return {input_prot_file: gr.File(str(file), visible=True)}
650
+ except Exception as e:
651
+ gr.Error(f"Query error: {str(e)}")
652
+
653
  prot_query_btn.click(
654
+ fn=pdb_query,
655
  inputs=[prot_query_input, prot_query_dropdown],
656
  outputs=[input_prot_file],
657
  )
658
 
659
+ input_prot_file_change = input_prot_file.change(
660
+ fn=lambda: gr.Info('Rendering 3DMol view...'),
661
+ ).then(
662
+ fn=lambda x, y: gr.Info('3DMol view rendered.'),
663
  inputs=[input_prot_file, input_prot_view],
664
+ js=static.CREATE_INPUT_MOL_VIEW,
 
665
  )
666
 
667
  #### Pocket Extraction
 
692
  outputs=[pocket_files, selected_ligand, selected_pocket],
693
  ).success(
694
  fn=lambda x, y: gr.Info('Pocket extraction completed.'),
695
+ js=static.UPDATE_MOL_VIEW,
696
  inputs=[pocket_files, input_prot_view],
697
  )
698
 
 
716
  fn=dock_link,
717
  inputs=[
718
  frag_lib_mod_df, input_prot_file,
719
+ dock_steps, dock_n_poses, dock_confidence_cutoff,
720
+ link_frag_dist_range, link_frag_pose_strategy, link_n_mols,
721
+ link_linker_size, link_steps,
722
  run_state
723
  ],
724
  outputs=[result_state, run_state],
725
  concurrency_limit=1, concurrency_id="gpu_queue"
726
  )
727
 
728
+ start_reset_components=[
729
+ frag_lib_dropdown, frag_lib_process_opts,
730
+ prot_query_dropdown, prot_query_input, input_prot_file,
731
+ dock_n_poses, dock_confidence_cutoff, dock_model, dock_steps,
732
+ link_frag_pose_strategy, link_n_mols, link_frag_dist_range, link_model, link_linker_size, link_steps,
733
+ email_input
734
+ ]
735
+
736
+ def reset_components(components):
737
+ return [
738
+ type(component)(
739
+ value=component.value,
740
+ visible=component.visible,
741
+ ) for component in components
742
+ ]
743
+
744
+ start_clr_btn.click(
745
+ fn=lambda: reset_components(start_reset_components),
746
+ outputs=start_reset_components,
747
+ show_progress='hidden',
748
+ )
749
+
750
  ### Job Status
751
  user_job_lookup = pred_lookup_btn.click(
752
+ fn=lambda: '<div class="loader"></ div>',
753
  outputs=loader_html,
754
  ).success(
755
  fn=query_job_status,
 
796
  protein_structure_file = Path(result_info['protein_structure_file'])
797
  if result_type == 'docking':
798
  result_df = pd.read_csv(result_dir / 'docking_summary.csv')
799
+ result_df['mol'] = result_df['X1'].apply(Chem.MolFromSmiles)
800
  elif result_type == 'linking':
801
  result_df = pd.read_csv(result_dir / 'linking_summary.csv')
802
  result_df = result_df[~result_df['X1^'].str.contains('.', regex=False)]
803
+ result_df['mol'] = result_df['X1^'].apply(Chem.MolFromSmiles)
804
+ result_df.dropna(subset=['mol'], inplace=True)
805
  else:
806
  raise gr.Error('Invalid result type')
807
+
808
+ draw_opts = Draw.rdMolDraw2D.MolDrawOptions()
809
+ draw_opts.clearBackground = False
810
+ draw_opts.bondLineWidth = 0.5
811
+ draw_opts.explicitMethyl = True
812
+ draw_opts.singleColourWedgeBonds = True
813
+ draw_opts.useCDKAtomPalette()
814
+ PandasTools.drawOptions = draw_opts
815
+ PandasTools.molSize = (90, 56)
816
+ PandasTools.molRepresentation = 'svg'
817
+ # Convert to URI-formatted inline SVG
818
+ result_df['Compound'] = result_df['mol'].apply(PandasTools.PrintAsImageString).apply(urllib.parse.quote)
819
+
820
  return {
821
  result_table_orig_df: result_df,
822
  result_table_mod_df: result_df.copy(deep=True),
823
  result_protein_file: str(protein_structure_file),
824
+ result_download_file: gr.File('', visible=False),
825
  }
826
 
827
  def update_table(orig_df, score_list, filter_list, progress=gr.Progress(track_tqdm=True)):
828
+ gr.Info('Calculating properties...')
829
  mod_df = orig_df.copy()
830
  try:
831
  for filter_name in filter_list:
832
+ mod_df[filter_name] = mod_df['mol'].apply(
833
  lambda x: fn.FILTER_MAP[filter_name](x) if not pd.isna(x) else x)
834
 
835
  for score_name in score_list:
836
+ mod_df[score_name] = mod_df['mol'].apply(
837
  lambda x: fn.SCORE_MAP[score_name](x) if not pd.isna(x) else x)
838
 
839
  return {result_table_mod_df: mod_df}
 
849
  )
850
 
851
  result_protein_file.change(
852
+ fn=lambda x, y: gr.Info('Rendering result table and 3DMol view...'),
853
+ js=static.CREATE_OUTPUT_MOL_VIEW,
854
  inputs=[result_protein_file, result_prot_view],
 
855
  )
856
  result_table_mod_df.change(
857
  fn=fn.create_result_table_html,
858
+ inputs=[result_table_mod_df, result_state],
859
  outputs=[result_table_view]
860
  ).success(
861
+ fn=lambda x: [gr.Button(visible=True), gr.Button(interactive=True), gr.Button(interactive=True)],
862
  inputs=[result_file_btn],
863
+ outputs=[result_file_btn, prop_calc_btn, prop_clr_btn],
864
  )
865
  prop_calc_btn.click(
866
  fn=update_table,
867
  inputs=[result_table_orig_df, scores, filters],
868
  outputs=[result_table_mod_df],
 
869
  )
870
  prop_clr_btn.click(
871
+ fn=lambda orig_df: [orig_df, [], [], gr.File(visible=False)],
872
  inputs=[result_table_orig_df],
873
+ outputs=[result_table_mod_df, scores, filters, result_download_file],
 
874
  )
875
 
 
876
  def generate_result_zip(result_info, compound_mod_df, protein_file):
877
+ result_path = Path(result_info['output_dir'])
878
+ zip_path = result_path.with_suffix('.zip')
879
+ cols_to_drop = ['mol', 'Compound', 'protein_path']
880
+ compound_mod_df.drop(columns=[col for col in cols_to_drop if col in compound_mod_df.columns], inplace=True)
881
+ compound_mod_df.to_csv(result_path / f'{result_info["type"]}_summary.csv', index=False)
882
+
883
  with zipfile.ZipFile(zip_path, 'w') as zip_file:
884
+ for file in result_path.rglob('*'):
885
+ if file.is_file(): # Skip directories
886
+ archive_path = file.relative_to(result_path)
887
+ zip_file.write(file, arcname=archive_path)
888
  zip_file.write(Path(protein_file), arcname=Path(protein_file).name)
889
+
890
  return gr.File(str(zip_path), visible=True)
891
 
892
+
893
  result_file_btn.click(
894
  fn=generate_result_zip,
895
  inputs=[result_state, result_table_mod_df, result_protein_file],
app/static.py CHANGED
@@ -143,640 +143,50 @@ footer {
143
  .mol-container {
144
  width: 100%;
145
  aspect-ratio: 1.618 / 1;
146
- position: relative;
147
- box-sizing: border-box;
148
  }
149
- """
150
-
151
- IFRAME_TEMPLATE = """
152
- <iframe style="width: 100%; aspect-ratio: 1.618 / 1;" name="result" allow="display-capture; encrypted-media;"
153
- sandbox="allow-modals allow-forms allow-scripts allow-same-origin allow-popups
154
- allow-top-navigation-by-user-activation allow-downloads" allowfullscreen=""
155
- allowpaymentrequest="" frameborder="0" srcdoc='{html}'></iframe>
156
- """
157
-
158
- PROTEIN_VIEW_IFRAME = IFRAME_TEMPLATE.format(html="""
159
- <!DOCTYPE html>
160
- <html>
161
- <head>
162
- <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
163
- <script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
164
- <script src="https://3Dmol.org/build/3Dmol.js"></script>
165
- <style>
166
- html, body {
167
- height: 100%;
168
- margin: 0;
169
- overflow: hidden;
170
- }
171
- .mol-container {
172
- width: 100%;
173
- height: 100%;
174
- position: relative;
175
- box-sizing: border-box;
176
- }
177
- .mol-container select {
178
- background-image: none;
179
- }
180
- </style>
181
- </head>
182
- <body>
183
- <div id="container" class="mol-container"></div>
184
- <script>
185
- var viewer = null;
186
- $(document).ready(function() {
187
- let element = $("#container");
188
- let config = { backgroundColor: "white" };
189
- viewer = $3Dmol.createViewer(element, config);
190
- let ligandStyle = { stick: { colorscheme: "greenCarbon" } };
191
- let proteinStyle = { cartoon: { colorscheme: "Jmol" } };
192
- let pocketStyle = { sphere: { colorscheme: "blueCarbon", opacity: 0.618 } };
193
- });
194
- </script>
195
- </body>
196
- </html>
197
- """)
198
-
199
- COMPLEX_RENDERING_TEMPLATE = """
200
- <!DOCTYPE html>
201
- <html>
202
- <head>
203
- <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
204
- <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.3/jquery.min.js" integrity="sha512-STof4xm1wgkfm7heWqFJVn58Hm3EtS31XFaagaa8VMReCXAkQnJZ+jEy8PCC/iT18dFy95WcExNHFTqLyp72eQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
205
- <script src="https://3Dmol.org/build/3Dmol.js"></script>
206
- <style>
207
- html, body {{
208
- height: 100%;
209
- margin: 0;
210
- overflow: hidden;
211
- }}
212
- .mol-container {{
213
- width: 100%;
214
- height: 100%;
215
- position: relative;
216
- box-sizing: border-box;
217
- }}
218
- .mol-container select {{
219
- background-image: none;
220
- }}
221
- </style>
222
- </head>
223
- <body>
224
- <div id="container" class="mol-container"></div>
225
- <script>
226
- var viewer = null;
227
- $(document).ready(function() {{
228
- let element = $("#container");
229
- let config = {{ backgroundColor: "white" }};
230
- let viewer = $3Dmol.createViewer(element, config);
231
- let ligandStyle = {{ stick: {{ colorscheme: "greenCarbon" }} }};
232
- let proteinStyle = {{ cartoon: {{ colorscheme: "Jmol" }} }};
233
- let pocketStyle = {{ sphere: {{ colorscheme: "blueCarbon", opacity: 0.618 }} }};
234
-
235
- {viewer_models}
236
-
237
- viewer.zoomTo({{ "model": 0 }});
238
- viewer.render();
239
- }});
240
- </script>
241
- </body>
242
- </html>
243
- """
244
-
245
-
246
- FRAGMENTS_RENDERING_TEMPLATE = """<!DOCTYPE html>
247
- <html>
248
- <head>
249
- <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
250
- <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.3/jquery.min.js" integrity="sha512-STof4xm1wgkfm7heWqFJVn58Hm3EtS31XFaagaa8VMReCXAkQnJZ+jEy8PCC/iT18dFy95WcExNHFTqLyp72eQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
251
- <script src="https://3Dmol.org/build/3Dmol.js"></script>
252
- <style>
253
- html, body {{
254
- height: 100%;
255
- margin: 0;
256
- overflow: hidden;
257
- }}
258
- .mol-container {{
259
- width: 100%;
260
- height: 100%;
261
- position: relative;
262
- box-sizing: border-box;
263
- }}
264
- .mol-container select{{
265
- background-image:None;
266
- }}
267
- </style>
268
- </head>
269
-
270
- <body>
271
- <div id="container" class="mol-container"></div>
272
- <script>
273
- $(document).ready(function() {{
274
- let element = $("#container");
275
- let config = {{ backgroundColor: "white" }};
276
- let viewer = $3Dmol.createViewer(element, config);
277
- let defaultStyle = {{ stick: {{ colorscheme: "greenCarbon" }} }};
278
- viewer.addModel(`{molecule}`, "{fmt}");
279
- viewer.getModel(0).setStyle(defaultStyle);
280
-
281
- viewer.getModel(0).setClickable(
282
- {{}},
283
- true,
284
- function (_atom, _viewer, _event, _container) {{
285
- if (!_atom.isClicked) {{
286
- _atom.isClicked = true;
287
- _viewer.addStyle(
288
- {{"serial": _atom.serial, "model": 0}},
289
- {{"sphere": {{"color": "magenta", "radius": 0.4}} }}
290
- );
291
- window.parent.postMessage({{
292
- name: "atom_selection",
293
- data: {{"atom": _atom.serial, "add": true}}
294
- }}, "*");
295
- }} else {{
296
- delete _atom.isClicked;
297
- _viewer.setStyle({{"serial": _atom.serial, "model": 0}}, defaultStyle);
298
- window.parent.postMessage({{
299
- name: "atom_selection",
300
- data: {{"atom": _atom.serial, "add": false}}
301
- }}, "*");
302
- }}
303
- _viewer.render();
304
- }}
305
- );
306
-
307
- viewer.zoomTo({{ "model": 0 }});
308
- viewer.zoom(0.7);
309
- viewer.render();
310
- }});
311
- </script>
312
- </body>
313
- </html>
314
- """
315
-
316
- TARGET_RENDERING_TEMPLATE = """<!DOCTYPE html>
317
- <html>
318
- <head>
319
- <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
320
- <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.3/jquery.min.js" integrity="sha512-STof4xm1wgkfm7heWqFJVn58Hm3EtS31XFaagaa8VMReCXAkQnJZ+jEy8PCC/iT18dFy95WcExNHFTqLyp72eQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
321
- <script src="https://3Dmol.org/build/3Dmol.js"></script>
322
- <style>
323
- html, body {{
324
- height: 100%;
325
- margin: 0;
326
- overflow: hidden;
327
- }}
328
- .mol-container {{
329
- width: 100%;
330
- height: 100%;
331
- position: relative;
332
- box-sizing: border-box;
333
- }}
334
- .mol-container select {{
335
- background-image: none;
336
- }}
337
- </style>
338
- </head>
339
-
340
- <body>
341
- <div id="container" class="mol-container"></div>
342
- <script>
343
- $(document).ready(function() {{
344
- let element = $("#container");
345
- let config = {{ backgroundColor: "white" }};
346
- let viewer = $3Dmol.createViewer(element, config);
347
- let proteinStyle = {{ cartoon: {{ colorscheme: "ssPyMOL" }} }};
348
- viewer.addModel(`{molecule}`, "{fmt}");
349
- viewer.getModel(0).setStyle(proteinStyle);
350
-
351
- viewer.zoomTo({{ "model": 0 }});
352
- viewer.zoom(0.7);
353
- viewer.render();
354
- }});
355
- </script>
356
- </body>
357
- </html>
358
- """
359
-
360
- COMPLEX_RENDERING_TEMPLATE_OLD = """<!DOCTYPE html>
361
- <html>
362
- <head>
363
- <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
364
- <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.3/jquery.min.js" integrity="sha512-STof4xm1wgkfm7heWqFJVn58Hm3EtS31XFaagaa8VMReCXAkQnJZ+jEy8PCC/iT18dFy95WcExNHFTqLyp72eQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
365
- <script src="https://3Dmol.org/build/3Dmol.js"></script>
366
- <style>
367
- html, body {{
368
- height: 100%;
369
- margin: 0;
370
- overflow: hidden;
371
- }}
372
- .mol-container {{
373
- width: 100%;
374
- height: 100%;
375
- position: relative;
376
- box-sizing: border-box;
377
- }}
378
- .mol-container select {{
379
- background-image: none;
380
- }}
381
- </style>
382
- </head>
383
-
384
- <body>
385
- <div id="container" class="mol-container"></div>
386
- <script>
387
- $(document).ready(function() {{
388
- let element = $("#container");
389
- let config = {{ backgroundColor: "white" }};
390
- let viewer = $3Dmol.createViewer(element, config);
391
- let ligandStyle = {{ stick: {{ colorscheme: "greenCarbon" }} }};
392
- let proteinStyle = {{ cartoon: {{ colorscheme: "Jmol" }} }};
393
- let defaultStyle = {{ stick: {{ colorscheme: "greenCarbon" }} }}; // Store default style
394
- let selectedLigand = null; // Keep track of currently selected ligand
395
-
396
- viewer.setStyle({{ hetflag: false }}, proteinStyle);
397
- viewer.setStyle({{ hetflag: true }}, ligandStyle);
398
- viewer.addModel(`{complex}`, "{fmt}");
399
-
400
- viewer.getModel(0).setClickable(
401
- {{ hetflag: true, byres: true }},
402
- true,
403
- function (_atom, _viewer, _event, _container) {{
404
- let currentLigand = {{ resn: _atom.resn, chain: _atom.chain, resi: _atom.resi }};
405
-
406
- if (selectedLigand === currentLigand) {{
407
- // Deselect ligand
408
- selectedLigand = null;
409
- _viewer.setStyle({{ resn: _atom.resn, chain: _atom.chain, resi: _atom.resi }}, defaultStyle);
410
- console.log("Deselected Residue:", currentLigand);
411
- window.parent.postMessage({{
412
- name: "ligand_selection",
413
- data: {{ residue: currentLigand, add: false }}
414
- }}, "*");
415
- }} else {{
416
- // Select ligand and deselect previous
417
- if (selectedLigand) {{
418
- _viewer.setStyle({{ resn: selectedLigand.resn, chain: selectedLigand.chain, resi: selectedLigand.resi }}, defaultStyle);
419
- }}
420
- selectedLigand = currentLigand;
421
- _viewer.setStyle({{ resn: _atom.resn, chain: _atom.chain, resi: _atom.resi }}, {{ stick: {{ color: "red", radius: 0.4}} }});
422
- console.log("Selected Residue:", currentLigand);
423
- window.parent.postMessage({{
424
- name: "ligand_selection",
425
- data: {{ residue: currentLigand, add: true }}
426
- }}, "*");
427
- }}
428
- _viewer.render();
429
- }}
430
- );
431
-
432
- viewer.render();
433
- }});
434
- </script>
435
- </body>
436
- </html>
437
- """
438
-
439
- COMPLEX_WITH_POCKETS_RENDERING_TEMPLATE = """<!DOCTYPE html>
440
- <html>
441
- <head>
442
- <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
443
- <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.3/jquery.min.js" integrity="sha512-STof4xm1wgkfm7heWqFJVn58Hm3EtS31XFaagaa8VMReCXAkQnJZ+jEy8PCC/iT18dFy95WcExNHFTqLyp72eQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
444
- <script src="https://3Dmol.org/build/3Dmol.js"></script>
445
- <style>
446
- html, body {{
447
- height: 100%;
448
- margin: 0;
449
- overflow: hidden;
450
- }}
451
- .mol-container {{
452
- width: 100%;
453
- height: 100%;
454
- position: relative;
455
- box-sizing: border-box;
456
- }}
457
- .mol-container select {{
458
- background-image: none;
459
- }}
460
- </style>
461
- </head>
462
-
463
- <body>
464
- <div id="container" class="mol-container"></div>
465
- <script>
466
- $(document).ready(function() {{
467
- let element = $("#container");
468
- let config = {{ backgroundColor: "white" }};
469
- let viewer = $3Dmol.createViewer(element, config);
470
- let ligandStyle = {{ stick: {{ colorscheme: "greenCarbon" }} }};
471
- let proteinStyle = {{ cartoon: {{ colorscheme: "Jmol" }} }};
472
- let defaultStyle = {{ stick: {{ colorscheme: "greenCarbon" }} }}; // Store default style
473
- let selectedLigand = null; // Keep track of currently selected ligand
474
-
475
- viewer.addModel(`{complex}`, "{fmt}");
476
- viewer.setStyle({{ hetflag: false }}, proteinStyle);
477
- viewer.setStyle({{ hetflag: true }}, ligandStyle);
478
-
479
- viewer.getModel(0).setClickable(
480
- {{ hetflag: true, byres: true }},
481
- true,
482
- function (_atom, _viewer, _event, _container) {{
483
- let currentLigand = {{ resn: _atom.resn, chain: _atom.chain, resi: _atom.resi }};
484
-
485
- if (selectedLigand === currentLigand) {{
486
- // Deselect ligand
487
- selectedLigand = null;
488
- _viewer.setStyle({{ resn: _atom.resn, chain: _atom.chain, resi: _atom.resi }}, defaultStyle);
489
- console.log("Deselected Residue:", currentLigand);
490
- window.parent.postMessage({{
491
- name: "ligand_selection",
492
- data: {{ residue: currentLigand, add: false }}
493
- }}, "*");
494
- }} else {{
495
- // Select ligand and deselect previous
496
- if (selectedLigand) {{
497
- _viewer.setStyle({{ resn: selectedLigand.resn, chain: selectedLigand.chain, resi: selectedLigand.resi }}, defaultStyle);
498
- }}
499
- selectedLigand = currentLigand;
500
- _viewer.setStyle({{ resn: _atom.resn, chain: _atom.chain, resi: _atom.resi }}, {{ stick: {{ color: "red", radius: 0.4}} }});
501
- console.log("Selected Residue:", currentLigand);
502
- window.parent.postMessage({{
503
- name: "ligand_selection",
504
- data: {{ residue: currentLigand, add: true }}
505
- }}, "*");
506
- }}
507
- _viewer.render();
508
- }}
509
- );
510
- // Add pockets
511
- viewer.addModel
512
-
513
- viewer.zoomTo({{ "model": 0 }});
514
- viewer.zoom(0.7);
515
- viewer.render();
516
- }});
517
- </script>
518
- </body>
519
- </html>
520
- """
521
-
522
-
523
- FRAGMENTS_AND_TARGET_RENDERING_TEMPLATE = """
524
- <!DOCTYPE html>
525
- <html>
526
- <head>
527
- <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
528
- <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.3/jquery.min.js" integrity="sha512-STof4xm1wgkfm7heWqFJVn58Hm3EtS31XFaagaa8VMReCXAkQnJZ+jEy8PCC/iT18dFy95WcExNHFTqLyp72eQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
529
- <script src="https://3Dmol.org/build/3Dmol.js"></script>
530
- <style>
531
- html, body {{
532
- height: 100%;
533
- margin: 0;
534
- overflow: hidden;
535
- }}
536
- .mol-container {{
537
- width: 100%;
538
- height: 100%;
539
- position: relative;
540
- box-sizing: border-box;
541
- }}
542
- .mol-container select{{
543
- background-image:None;
544
- }}
545
- </style>
546
- </head>
547
-
548
- <body>
549
- <div id="container" class="mol-container"></div>
550
- <script>
551
- $(document).ready(function() {{
552
- let element = $("#container");
553
- let config = {{ backgroundColor: "white" }};
554
- let viewer = $3Dmol.createViewer(element, config);
555
- let defaultStyle = {{ stick: {{ colorscheme: "greenCarbon" }} }};
556
- let proteinStyle = {{ cartoon: {{ colorscheme: "ssPyMOL" }} }};
557
-
558
- viewer.addModel(`{molecule}`, "{fmt}");
559
- viewer.getModel(0).setStyle(defaultStyle);
560
- viewer.getModel(0).setClickable(
561
- {{}},
562
- true,
563
- function (_atom, _viewer, _event, _container) {{
564
- if (!_atom.isClicked) {{
565
- _atom.isClicked = true;
566
- _viewer.addStyle(
567
- {{"serial": _atom.serial, "model": 0}},
568
- {{"sphere": {{"color": "magenta", "radius": 0.4}} }}
569
- );
570
- window.parent.postMessage({{
571
- name: "atom_selection",
572
- data: {{"atom": _atom.serial, "add": true}}
573
- }}, "*");
574
- }} else {{
575
- delete _atom.isClicked;
576
- _viewer.setStyle({{"serial": _atom.serial, "model": 0}}, defaultStyle);
577
- window.parent.postMessage({{
578
- name: "atom_selection",
579
- data: {{"atom": _atom.serial, "add": false}}
580
- }}, "*");
581
- }}
582
- _viewer.render();
583
- }}
584
- );
585
-
586
- viewer.addModel(`{target}`, "{target_fmt}");
587
- viewer.getModel(1).setStyle(proteinStyle);
588
-
589
- viewer.zoomTo({{ "model": 0 }});
590
- viewer.zoom(0.7);
591
- viewer.render();
592
- }});
593
- </script>
594
- </body>
595
- </html>
596
- """
597
-
598
- SAMPLES_RENDERING_TEMPLATE = """<!DOCTYPE html>
599
- <html>
600
- <head>
601
- <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
602
- <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.3/jquery.min.js" integrity="sha512-STof4xm1wgkfm7heWqFJVn58Hm3EtS31XFaagaa8VMReCXAkQnJZ+jEy8PCC/iT18dFy95WcExNHFTqLyp72eQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
603
- <script src="https://3Dmol.org/build/3Dmol.js"></script>
604
- <style>
605
- html, body {{
606
- height: 100%;
607
- margin: 0;
608
- overflow: hidden;
609
- }}
610
- .mol-container {{
611
- width: 100%;
612
- height: 100%;
613
- position: relative;
614
- box-sizing: border-box;
615
- }}
616
- .mol-container select{{
617
- background-image:None;
618
- }}
619
- </style>
620
- </head>
621
-
622
- <body>
623
- <div id="container" class="mol-container"></div>
624
- <br>
625
- <button id="fragments">Input Fragments</button>
626
- <button id="molecule">Output Molecule</button>
627
- <script>
628
- let element = $("#container");
629
- let config = {{ backgroundColor: "white" }};
630
- let viewer = $3Dmol.createViewer( element, config );
631
-
632
- $(document).ready(function() {{
633
- viewer.addModel(`{fragments}`, "{fragments_fmt}")
634
- viewer.getModel().setStyle({{ stick: {{ colorscheme:"greenCarbon" }} }})
635
- viewer.getModel().hide();
636
- viewer.addModel(`{molecule}`, "{molecule_fmt}")
637
- viewer.getModel().setStyle({{ stick: {{ colorscheme:"greenCarbon" }} }})
638
- viewer.zoomTo({{ "model": 0 }});
639
- viewer.zoom(0.7);
640
- viewer.render();
641
- }});
642
- $("#fragments").click(function() {{
643
- viewer.getModel(0).show();
644
- viewer.getModel(1).hide();
645
- viewer.render();
646
- }});
647
- $("#molecule").click(function() {{
648
- viewer.getModel(1).show();
649
- viewer.getModel(0).hide();
650
- viewer.render();
651
- }});
652
- </script>
653
- </body>
654
- </html>
655
- """
656
-
657
- SAMPLES_WITH_TARGET_RENDERING_TEMPLATE = """
658
- <!DOCTYPE html>
659
- <html>
660
- <head>
661
- <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
662
- <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.3/jquery.min.js" integrity="sha512-STof4xm1wgkfm7heWqFJVn58Hm3EtS31XFaagaa8VMReCXAkQnJZ+jEy8PCC/iT18dFy95WcExNHFTqLyp72eQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
663
- <script src="https://3Dmol.org/build/3Dmol.js"></script>
664
- <style>
665
- html, body {{
666
- height: 100%;
667
- margin: 0;
668
- overflow: hidden;
669
- }}
670
- .mol-container {{
671
- width: 100%;
672
- height: 100%;
673
- position: relative;
674
- box-sizing: border-box;
675
- }}
676
- .mol-container select{{
677
- background-image:None;
678
- }}
679
- </style>
680
- </head>
681
-
682
- <body>
683
- <div id="container" class="mol-container"></div>
684
- <br>
685
- <button id="fragments">Input Fragments</button>
686
- <button id="molecule">Output Molecule</button>
687
- <button id="show-target">Show Target</button>
688
- <button id="hide-target">Hide Target</button>
689
- <script>
690
- let element = $("#container");
691
- let config = {{ backgroundColor: "white" }};
692
- let viewer = $3Dmol.createViewer( element, config );
693
-
694
- $(document).ready(function() {{
695
- viewer.addModel(`{fragments}`, "{fragments_fmt}")
696
- viewer.getModel(0).setStyle({{ stick: {{ colorscheme:"greenCarbon" }} }})
697
- viewer.getModel(0).hide();
698
-
699
- viewer.addModel(`{molecule}`, "{molecule_fmt}")
700
- viewer.getModel(1).setStyle({{ stick: {{ colorscheme:"greenCarbon" }} }})
701
-
702
- viewer.addModel(`{target}`, "{target_fmt}")
703
- viewer.getModel(2).setStyle({{ cartoon: {{ colorscheme: "ssPyMOL" }} }})
704
-
705
- viewer.zoomTo({{ "model": 0 }});
706
- viewer.zoom(0.7);
707
- viewer.render();
708
- }});
709
- $("#fragments").click(function() {{
710
- viewer.getModel(0).show();
711
- viewer.getModel(1).hide();
712
- viewer.render();
713
- }});
714
- $("#molecule").click(function() {{
715
- viewer.getModel(1).show();
716
- viewer.getModel(0).hide();
717
- viewer.render();
718
- }});
719
- $("#show-target").click(function() {{
720
- viewer.getModel(2).show();
721
- viewer.render();
722
- }});
723
- $("#hide-target").click(function() {{
724
- viewer.getModel(2).hide();
725
- viewer.render();
726
- }});
727
- </script>
728
- </body>
729
- </html>
730
- """
731
 
 
 
 
732
 
733
- INVALID_FORMAT_MSG = """
734
- <!DOCTYPE html>
735
- <html>
736
- <head>
737
- <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
738
- <style>
739
- body{{
740
- font-family:sans-serif
741
- }}
742
- </style>
743
- </head>
 
744
 
745
- <body>
746
- <h3>Invalid file format: {extension}</h3>
747
- Allowed formats for the fragments file:
748
- <ul>
749
- <li>.pdb</li>
750
- <li>.sdf</li>
751
- <li>.mol</li>
752
- <li>.mol2</li>
753
- </ul>
754
-
755
- Allowed formats for the optional protein file:
756
- <ul>
757
- <li>.pdb</li>
758
- </ul>
759
- </body>
760
- </html>
 
 
761
  """
762
 
763
- ERROR_FORMAT_MSG = """
764
- <!DOCTYPE html>
765
- <html>
766
- <head>
767
- <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
768
- <style>
769
- body{{
770
- font-family:sans-serif
771
- }}
772
- </style>
773
- </head>
774
-
775
- <body>
776
- <h3>Error:</h3>
777
- {message}
778
- </body>
779
- </html>
780
  """
781
 
782
  SETUP_JS = """
@@ -902,7 +312,7 @@ RETURN_ATOM_SELECTION_JS = """
902
  }
903
  """
904
 
905
- CREATE_MOL_VIEW = """
906
  (mol_file, view_html) => {
907
  try {
908
  let viewer;
@@ -915,20 +325,24 @@ CREATE_MOL_VIEW = """
915
  const element_id = idMatch[1];
916
  const element = document.getElementById(element_id);
917
 
918
- // Get the file format
919
- fmt = mol_file.path.split('.').pop();
920
- $.get(mol_file.url, function(molContent) {
921
- if (!element.querySelector('canvas')) {
922
- viewer = $3Dmol.createViewer(element, viewerConfig);
923
- } else {
924
- viewer = element.querySelector('canvas')._3dmol_viewer;
925
- viewer.clear();
926
- selectedElements = {
927
- "atoms": [],
928
- "ligand": {name: null, resn: null, chain: null, resi: null},
929
- "pocket": {name: null, id: null}
930
- }
931
  }
 
 
 
 
 
 
 
 
932
  model = viewer.addModel(molContent, fmt);
933
  model.setStyle({ hetflag: false }, proteinStyle);
934
  model.setStyle({ hetflag: true }, ligandStyle);
@@ -977,7 +391,7 @@ CREATE_MOL_VIEW = """
977
  }
978
  """
979
 
980
- UPDATE_PROT_VIEW = """
981
  (mol_files, view_html) => {
982
  try {
983
  let viewer;
@@ -1064,3 +478,80 @@ UPDATE_PROT_VIEW = """
1064
  }
1065
  """
1066
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
143
  .mol-container {
144
  width: 100%;
145
  aspect-ratio: 1.618 / 1;
 
 
146
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
147
 
148
+ .mol-container > canvas {
149
+ position: relative ! important;
150
+ }
151
 
152
+ .gr-btn-grp {
153
+ display: flex;
154
+ flex-wrap: wrap;
155
+ gap: var(--layout-gap);
156
+ width: var(--size-full);
157
+ position: relative
158
+ border-radius: 0
159
+ border-radius: var(--container-radius);
160
+ background: var(--background-fill-secondary);
161
+ padding: var(--size-2)
162
+ align-items: stretch
163
+ }
164
 
165
+ .gr-btn-grp > button {
166
+ flex: 1 1 0;
167
+ display: inline-flex;
168
+ justify-content: center;
169
+ align-items: center;
170
+ transition: var(--button-transition);
171
+ padding: var(--size-0-5) var(--size-2);
172
+ text-align: center;
173
+ border: var(--button-border-width) solid var(--button-secondary-border-color);
174
+ background: var(--button-secondary-background-fill);
175
+ color: var(--button-secondary-text-color);
176
+ box-shadow: var(--button-secondary-shadow)
177
+ border-radius: var(--button-large-radius);
178
+ padding: calc(var(--button-large-padding) - 1px);
179
+ font-family: monospace;
180
+ font-weight: var(--button-large-text-weight);
181
+ font-size: calc(var(--button-large-text-size) - 1px);
182
+ }
183
  """
184
 
185
+ IFRAME_TEMPLATE = """
186
+ <iframe style="width: 100%; aspect-ratio: {aspect_ratio}; overflow: visible; border: none;" scrolling="no"
187
+ frameborder="0" allow="display-capture; encrypted-media;"
188
+ sandbox="allow-modals allow-forms allow-scripts allow-same-origin allow-popups
189
+ allow-top-navigation-by-user-activation allow-downloads" srcdoc='{srcdoc}'></iframe>
 
 
 
 
 
 
 
 
 
 
 
 
190
  """
191
 
192
  SETUP_JS = """
 
312
  }
313
  """
314
 
315
+ CREATE_INPUT_MOL_VIEW = """
316
  (mol_file, view_html) => {
317
  try {
318
  let viewer;
 
325
  const element_id = idMatch[1];
326
  const element = document.getElementById(element_id);
327
 
328
+ if (!element.querySelector('canvas')) {
329
+ viewer = $3Dmol.createViewer(element, viewerConfig);
330
+ } else {
331
+ viewer = element.querySelector('canvas')._3dmol_viewer;
332
+ viewer.clear();
333
+ selectedElements = {
334
+ "atoms": [],
335
+ "ligand": {name: null, resn: null, chain: null, resi: null},
336
+ "pocket": {name: null, id: null}
 
 
 
 
337
  }
338
+ }
339
+
340
+ if (mol_file == null) {
341
+ return;
342
+ }
343
+
344
+ $.get(mol_file.url, function(molContent) {
345
+ fmt = mol_file.path.split('.').pop();
346
  model = viewer.addModel(molContent, fmt);
347
  model.setStyle({ hetflag: false }, proteinStyle);
348
  model.setStyle({ hetflag: true }, ligandStyle);
 
391
  }
392
  """
393
 
394
+ UPDATE_MOL_VIEW = """
395
  (mol_files, view_html) => {
396
  try {
397
  let viewer;
 
478
  }
479
  """
480
 
481
+ CREATE_OUTPUT_MOL_VIEW = """
482
+ (mol_file, view_html) => {
483
+ try {
484
+ let viewer;
485
+ const idMatch = view_html.match(/id="(\w+)"/);
486
+ if (!idMatch || !idMatch[1]) {
487
+ console.error("Invalid view_html: No ID found.");
488
+ return;
489
+ }
490
+ const element_id = idMatch[1];
491
+ const element = document.getElementById(element_id);
492
+
493
+ fmt = mol_file.path.split('.').pop();
494
+ $.get(mol_file.url, function(molContent) {
495
+ if (!element.querySelector('canvas')) {
496
+ viewer = $3Dmol.createViewer(element, viewerConfig);
497
+ } else {
498
+ viewer = element.querySelector('canvas')._3dmol_viewer;
499
+ viewer.clear();
500
+ }
501
+ model = viewer.addModel(molContent, fmt);
502
+ model.setStyle({ hetflag: false }, proteinStyle);
503
+ model.setStyle({ hetflag: true }, ligandStyle);
504
+ viewer.zoomTo();
505
+ viewer.render();
506
+
507
+ const container = document.createElement("div");
508
+ container.classList.add("gr-btn-grp");
509
+
510
+ // Molecule Button
511
+ const toggleMoleculeButton = document.createElement("button");
512
+ toggleMoleculeButton.textContent = "Hide Generated Molecule";
513
+ let moleculeIsHidden = false;
514
+ toggleMoleculeButton.onclick = function() {
515
+ if (viewer.models.length === 2) {
516
+ moleculeIsHidden = !moleculeIsHidden;
517
+ viewer.addStyle({model: 1}, {stick: {hidden: moleculeIsHidden} });
518
+ toggleMoleculeButton.textContent = moleculeIsHidden ? "Show Generated Molecule" : "Hide Generated Molecule";
519
+ viewer.render();
520
+ }
521
+ };
522
+ container.appendChild(toggleMoleculeButton);
523
+
524
+ // Ligand Button
525
+ if (viewer.getAtomsFromSel({model: 0, hetflag : true}).length > 0) {
526
+ const toggleLigandButton = document.createElement("button");
527
+ toggleLigandButton.textContent = "Hide Co-Crystallized Ligand";
528
+ let ligandIsHidden = false;
529
+ toggleLigandButton.onclick = function() {
530
+ ligandIsHidden = !ligandIsHidden;
531
+ viewer.addStyle({model: 0, hetflag: true}, {stick: {hidden: ligandIsHidden} });
532
+ toggleLigandButton.textContent = ligandIsHidden ? "Show Co-Crystallized Ligand" : "Hide Co-Crystallized Ligand";
533
+ viewer.render();
534
+ };
535
+ container.appendChild(toggleLigandButton);
536
+ }
537
+
538
+ // Protein Button
539
+ const toggleProteinButton = document.createElement("button");
540
+ toggleProteinButton.textContent = "Hide Target Protein";
541
+ let proteinIsHidden = false;
542
+ toggleProteinButton.onclick = function() {
543
+ proteinIsHidden = !proteinIsHidden;
544
+ viewer.addStyle({model: 0, hetflag: false}, {cartoon: {hidden: proteinIsHidden} });
545
+ toggleProteinButton.textContent = proteinIsHidden ? "Show Target Protein" : "Hide Target Protein";
546
+ viewer.render();
547
+ };
548
+ container.appendChild(toggleProteinButton);
549
+ element.parentElement.appendChild(container);
550
+ }).fail(function(error) {
551
+ console.error("Error loading molecule:", error);
552
+ });
553
+ } catch (error) {
554
+ console.error("An error occurred:", error);
555
+ }
556
+ }
557
+ """
app/static/medium-zoom.min.js ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ /*! medium-zoom 1.1.0 | MIT License | https://github.com/francoischalifour/medium-zoom */
2
+ !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e=e||self).mediumZoom=t()}(this,(function(){"use strict";var e=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var o=arguments[t];for(var n in o)Object.prototype.hasOwnProperty.call(o,n)&&(e[n]=o[n])}return e},t=function(e){return"IMG"===e.tagName},o=function(e){return e&&1===e.nodeType},n=function(e){return".svg"===(e.currentSrc||e.src).substr(-4).toLowerCase()},i=function(e){try{return Array.isArray(e)?e.filter(t):function(e){return NodeList.prototype.isPrototypeOf(e)}(e)?[].slice.call(e).filter(t):o(e)?[e].filter(t):"string"==typeof e?[].slice.call(document.querySelectorAll(e)).filter(t):[]}catch(e){throw new TypeError("The provided selector is invalid.\nExpects a CSS selector, a Node element, a NodeList or an array.\nSee: https://github.com/francoischalifour/medium-zoom")}},r=function(e){var t=document.createElement("div");return t.classList.add("medium-zoom-overlay"),t.style.background=e,t},d=function(e){var t=e.getBoundingClientRect(),o=t.top,n=t.left,i=t.width,r=t.height,d=e.cloneNode(),a=window.pageYOffset||document.documentElement.scrollTop||document.body.scrollTop||0,m=window.pageXOffset||document.documentElement.scrollLeft||document.body.scrollLeft||0;return d.removeAttribute("id"),d.style.position="absolute",d.style.top=o+a+"px",d.style.left=n+m+"px",d.style.width=i+"px",d.style.height=r+"px",d.style.transform="",d},a=function(t,o){var n=e({bubbles:!1,cancelable:!1,detail:void 0},o);if("function"==typeof window.CustomEvent)return new CustomEvent(t,n);var i=document.createEvent("CustomEvent");return i.initCustomEvent(t,n.bubbles,n.cancelable,n.detail),i};return function(e,t){void 0===t&&(t={});var o=t.insertAt;if(e&&"undefined"!=typeof document){var n=document.head||document.getElementsByTagName("head")[0],i=document.createElement("style");i.type="text/css","top"===o&&n.firstChild?n.insertBefore(i,n.firstChild):n.appendChild(i),i.styleSheet?i.styleSheet.cssText=e:i.appendChild(document.createTextNode(e))}}(".medium-zoom-overlay{position:fixed;top:0;right:0;bottom:0;left:0;opacity:0;transition:opacity .3s;will-change:opacity}.medium-zoom--opened .medium-zoom-overlay{cursor:pointer;cursor:zoom-out;opacity:1}.medium-zoom-image{cursor:pointer;cursor:zoom-in;transition:transform .3s cubic-bezier(.2,0,.2,1)!important}.medium-zoom-image--hidden{visibility:hidden}.medium-zoom-image--opened{position:relative;cursor:pointer;cursor:zoom-out;will-change:transform}"),function t(m){var l=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},c=window.Promise||function(e){function t(){}e(t,t)},u=function(e){var t=e.target;t!==N?-1!==x.indexOf(t)&&w({target:t}):E()},s=function(){if(!A&&k.original){var e=window.pageYOffset||document.documentElement.scrollTop||document.body.scrollTop||0;Math.abs(S-e)>T.scrollOffset&&setTimeout(E,150)}},f=function(e){var t=e.key||e.keyCode;"Escape"!==t&&"Esc"!==t&&27!==t||E()},p=function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},n=t;if(t.background&&(N.style.background=t.background),t.container&&t.container instanceof Object&&(n.container=e({},T.container,t.container)),t.template){var i=o(t.template)?t.template:document.querySelector(t.template);n.template=i}return T=e({},T,n),x.forEach((function(e){e.dispatchEvent(a("medium-zoom:update",{detail:{zoom:j}}))})),j},g=function(){var o=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return t(e({},T,o))},v=function(){for(var e=arguments.length,t=Array(e),o=0;o<e;o++)t[o]=arguments[o];var n=t.reduce((function(e,t){return[].concat(e,i(t))}),[]);return n.filter((function(e){return-1===x.indexOf(e)})).forEach((function(e){x.push(e),e.classList.add("medium-zoom-image")})),O.forEach((function(e){var t=e.type,o=e.listener,i=e.options;n.forEach((function(e){e.addEventListener(t,o,i)}))})),j},h=function(){for(var e=arguments.length,t=Array(e),o=0;o<e;o++)t[o]=arguments[o];k.zoomed&&E();var n=t.length>0?t.reduce((function(e,t){return[].concat(e,i(t))}),[]):x;return n.forEach((function(e){e.classList.remove("medium-zoom-image"),e.dispatchEvent(a("medium-zoom:detach",{detail:{zoom:j}}))})),x=x.filter((function(e){return-1===n.indexOf(e)})),j},z=function(e,t){var o=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};return x.forEach((function(n){n.addEventListener("medium-zoom:"+e,t,o)})),O.push({type:"medium-zoom:"+e,listener:t,options:o}),j},y=function(e,t){var o=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};return x.forEach((function(n){n.removeEventListener("medium-zoom:"+e,t,o)})),O=O.filter((function(o){return!(o.type==="medium-zoom:"+e&&o.listener.toString()===t.toString())})),j},b=function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},i=t.target,r=function(){var t={width:document.documentElement.clientWidth,height:document.documentElement.clientHeight,left:0,top:0,right:0,bottom:0},i=void 0,r=void 0;if(T.container)if(T.container instanceof Object)i=(t=e({},t,T.container)).width-t.left-t.right-2*T.margin,r=t.height-t.top-t.bottom-2*T.margin;else{var d=(o(T.container)?T.container:document.querySelector(T.container)).getBoundingClientRect(),a=d.width,m=d.height,l=d.left,c=d.top;t=e({},t,{width:a,height:m,left:l,top:c})}i=i||t.width-2*T.margin,r=r||t.height-2*T.margin;var u=k.zoomedHd||k.original,s=n(u)?i:u.naturalWidth||i,f=n(u)?r:u.naturalHeight||r,p=u.getBoundingClientRect(),g=p.top,v=p.left,h=p.width,z=p.height,y=Math.min(Math.max(h,s),i)/h,b=Math.min(Math.max(z,f),r)/z,E=Math.min(y,b),w="scale("+E+") translate3d("+((i-h)/2-v+T.margin+t.left)/E+"px, "+((r-z)/2-g+T.margin+t.top)/E+"px, 0)";k.zoomed.style.transform=w,k.zoomedHd&&(k.zoomedHd.style.transform=w)};return new c((function(e){if(i&&-1===x.indexOf(i))e(j);else{if(k.zoomed)e(j);else{if(i)k.original=i;else{if(!(x.length>0))return void e(j);var t=x;k.original=t[0]}if(k.original.dispatchEvent(a("medium-zoom:open",{detail:{zoom:j}})),S=window.pageYOffset||document.documentElement.scrollTop||document.body.scrollTop||0,A=!0,k.zoomed=d(k.original),document.body.appendChild(N),T.template){var n=o(T.template)?T.template:document.querySelector(T.template);k.template=document.createElement("div"),k.template.appendChild(n.content.cloneNode(!0)),document.body.appendChild(k.template)}if(k.original.parentElement&&"PICTURE"===k.original.parentElement.tagName&&k.original.currentSrc&&(k.zoomed.src=k.original.currentSrc),document.body.appendChild(k.zoomed),window.requestAnimationFrame((function(){document.body.classList.add("medium-zoom--opened")})),k.original.classList.add("medium-zoom-image--hidden"),k.zoomed.classList.add("medium-zoom-image--opened"),k.zoomed.addEventListener("click",E),k.zoomed.addEventListener("transitionend",(function t(){A=!1,k.zoomed.removeEventListener("transitionend",t),k.original.dispatchEvent(a("medium-zoom:opened",{detail:{zoom:j}})),e(j)})),k.original.getAttribute("data-zoom-src")){k.zoomedHd=k.zoomed.cloneNode(),k.zoomedHd.removeAttribute("srcset"),k.zoomedHd.removeAttribute("sizes"),k.zoomedHd.removeAttribute("loading"),k.zoomedHd.src=k.zoomed.getAttribute("data-zoom-src"),k.zoomedHd.onerror=function(){clearInterval(m),console.warn("Unable to reach the zoom image target "+k.zoomedHd.src),k.zoomedHd=null,r()};var m=setInterval((function(){k.zoomedHd.complete&&(clearInterval(m),k.zoomedHd.classList.add("medium-zoom-image--opened"),k.zoomedHd.addEventListener("click",E),document.body.appendChild(k.zoomedHd),r())}),10)}else if(k.original.hasAttribute("srcset")){k.zoomedHd=k.zoomed.cloneNode(),k.zoomedHd.removeAttribute("sizes"),k.zoomedHd.removeAttribute("loading");var l=k.zoomedHd.addEventListener("load",(function(){k.zoomedHd.removeEventListener("load",l),k.zoomedHd.classList.add("medium-zoom-image--opened"),k.zoomedHd.addEventListener("click",E),document.body.appendChild(k.zoomedHd),r()}))}else r()}}}))},E=function(){return new c((function(e){if(!A&&k.original){A=!0,document.body.classList.remove("medium-zoom--opened"),k.zoomed.style.transform="",k.zoomedHd&&(k.zoomedHd.style.transform=""),k.template&&(k.template.style.transition="opacity 150ms",k.template.style.opacity=0),k.original.dispatchEvent(a("medium-zoom:close",{detail:{zoom:j}})),k.zoomed.addEventListener("transitionend",(function t(){k.original.classList.remove("medium-zoom-image--hidden"),document.body.removeChild(k.zoomed),k.zoomedHd&&document.body.removeChild(k.zoomedHd),document.body.removeChild(N),k.zoomed.classList.remove("medium-zoom-image--opened"),k.template&&document.body.removeChild(k.template),A=!1,k.zoomed.removeEventListener("transitionend",t),k.original.dispatchEvent(a("medium-zoom:closed",{detail:{zoom:j}})),k.original=null,k.zoomed=null,k.zoomedHd=null,k.template=null,e(j)}))}else e(j)}))},w=function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=e.target;return k.original?E():b({target:t})},L=function(){return T},H=function(){return x},C=function(){return k.original},x=[],O=[],A=!1,S=0,T=l,k={original:null,zoomed:null,zoomedHd:null,template:null};"[object Object]"===Object.prototype.toString.call(m)?T=m:(m||"string"==typeof m)&&v(m),T=e({margin:0,background:"#fff",scrollOffset:40,container:null,template:null},T);var N=r(T.background);document.addEventListener("click",u),document.addEventListener("keyup",f),document.addEventListener("scroll",s),window.addEventListener("resize",E);var j={open:b,close:E,toggle:w,update:p,clone:g,attach:v,detach:h,on:z,off:y,getOptions:L,getImages:H,getZoomedImage:C};return j}}));
app/{panel.css → static/panel.css} RENAMED
@@ -4,39 +4,37 @@
4
  font-size: 12px !important;
5
  }
6
 
7
- .tabulator-cell {
8
  overflow: visible !important;
9
  }
10
 
 
 
 
 
11
 
12
- .image-zoom-viewer {
13
- display: inline-block;
14
- overflow: visible;
15
- z-index: 1000;
16
  }
17
 
18
- .image-zoom-viewer::after {
19
- content: "";
20
- top: 0;
21
- left: 0;
22
- width: 100%;
23
- height: 100%;
24
- pointer-events: none;
25
  }
26
 
27
- .image-zoom-viewer:hover::after {
28
- pointer-events: all;
 
29
  }
30
 
31
  /* When hovering over the container, scale its child (the SVG) */
32
- .tabulator-cell:hover .image-zoom-viewer svg {
33
  padding: 3px;
34
- position: absolute;
 
35
  background-color: rgba(250, 250, 250, 0.854);
36
  box-shadow: 0 0 10px rgba(0, 0, 0, 0.618);
37
  border-radius: 3px;
38
- transform: scale(3); /* Scale up the SVG */
39
  transition: transform 0.3s ease;
40
- pointer-events: none; /* Prevents the SVG from blocking mouse interactions */
41
- z-index: 1000;
42
- }
 
4
  font-size: 12px !important;
5
  }
6
 
7
+ .tabulator:hover {
8
  overflow: visible !important;
9
  }
10
 
11
+ .tabulator-cell:has(> img) {
12
+ padding: 2px !important;
13
+ height: 60px !important;
14
+ }
15
 
16
+ .tabulator-cell:hover {
17
+ overflow: visible !important;
 
 
18
  }
19
 
20
+ .tabulator-row:hover {
21
+ z-index: 10000;
 
 
 
 
 
22
  }
23
 
24
+ .zoom-img {
25
+ position: relative;
26
+ transition: transform 0.3s ease;
27
  }
28
 
29
  /* When hovering over the container, scale its child (the SVG) */
30
+ .tabulator-cell:hover .zoom-img {
31
  padding: 3px;
32
+ position: fixed;
33
+ transform: translate(100%, 0%) scale(3); /* Center and scale */
34
  background-color: rgba(250, 250, 250, 0.854);
35
  box-shadow: 0 0 10px rgba(0, 0, 0, 0.618);
36
  border-radius: 3px;
 
37
  transition: transform 0.3s ease;
38
+ pointer-events: none; /* Prevent the image from blocking interactions */
39
+ z-index: 10000; /* Ensure it appears above other elements */
40
+ }
app/{panel.js → static/panel.js} RENAMED
@@ -28,34 +28,31 @@ const molDisplayButtonFormatter = function (cell, formatterParams, onRendered) {
28
  try {
29
  let viewer;
30
  const elementId = 'result_protein_view'; // Ensure this element exists in your HTML
31
- const element = document.getElementById(elementId);
32
 
33
  // Check if the element exists and contains a canvas
34
  if (element && element.querySelector('canvas')) {
35
  viewer = element.querySelector('canvas')._3dmol_viewer;
36
-
37
- // Remove all models except the first one
38
- for (let i = 1; i < viewer.models.length; i++) {
39
- viewer.removeModel(i);
40
- }
41
  } else {
42
  console.error("Invalid element_id: No canvas found.");
43
  return;
44
  }
45
 
46
  // Get the file format from the cell content (assumed to be a property of molFile)
47
- const fmt = molFile.orig_name.split('.').pop();
48
- const filename = molFile.orig_name;
49
- console.log("File:", filename, "Format:", fmt); // Log file name and format
50
 
51
  // Fetch the molecule content using the URL
52
- $.get(molFile.url, function(molContent) {
53
  // Add the model to the viewer and set the style
 
 
 
54
  const model = viewer.addModel(molContent, fmt);
55
- model.setStyle({}, { color: 'lightblue', opacity: 0.5 }); // Change style as needed
56
 
57
  console.log("Rendering protein view.");
58
- viewer.zoomTo();
59
  viewer.render();
60
  });
61
  } catch (error) {
@@ -69,5 +66,5 @@ const molDisplayButtonFormatter = function (cell, formatterParams, onRendered) {
69
 
70
  Tabulator.extendModule("format", "formatters", {
71
  executeScriptFormatter: executeScriptFormatter,
72
- buttonFormatter: buttonFormatter
73
  });
 
28
  try {
29
  let viewer;
30
  const elementId = 'result_protein_view'; // Ensure this element exists in your HTML
31
+ const element = window.parent.document.getElementById(elementId);
32
 
33
  // Check if the element exists and contains a canvas
34
  if (element && element.querySelector('canvas')) {
35
  viewer = element.querySelector('canvas')._3dmol_viewer;
 
 
 
 
 
36
  } else {
37
  console.error("Invalid element_id: No canvas found.");
38
  return;
39
  }
40
 
41
  // Get the file format from the cell content (assumed to be a property of molFile)
42
+ const fmt = molFile.split('.').pop();
43
+ molUrl = 'gradio_api/file=' + molFile;
 
44
 
45
  // Fetch the molecule content using the URL
46
+ window.parent.$.get(molUrl, function(molContent) {
47
  // Add the model to the viewer and set the style
48
+ for (let i = viewer.models.length - 1; i > 0; i--) {
49
+ viewer.removeModel(i); // Or viewer.models.splice(i, 1);
50
+ }
51
  const model = viewer.addModel(molContent, fmt);
52
+ model.setStyle({}, { stick: {colorscheme: 'magentaCarbon' } }); // Change style as needed
53
 
54
  console.log("Rendering protein view.");
55
+ viewer.zoomTo({model: model});
56
  viewer.render();
57
  });
58
  } catch (error) {
 
66
 
67
  Tabulator.extendModule("format", "formatters", {
68
  executeScriptFormatter: executeScriptFormatter,
69
+ molDisplayButtonFormatter: molDisplayButtonFormatter
70
  });
app/static/zooming.min.js ADDED
@@ -0,0 +1 @@
 
 
1
+ !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t=t||self).Zooming=e()}(this,function(){"use strict";var t="auto",e="zoom-in",i="zoom-out",n="grab",s="move";function o(t,e,i){var n={passive:!1};!(arguments.length>3&&void 0!==arguments[3])||arguments[3]?t.addEventListener(e,i,n):t.removeEventListener(e,i,n)}function r(t,e){if(t){var i=new Image;i.onload=function(){e&&e(i)},i.src=t}}function a(t){return t.dataset.original?t.dataset.original:"A"===t.parentNode.tagName?t.parentNode.getAttribute("href"):null}function l(t,e,i){!function(t){var e=h.transitionProp,i=h.transformProp;if(t.transition){var n=t.transition;delete t.transition,t[e]=n}if(t.transform){var s=t.transform;delete t.transform,t[i]=s}}(e);var n=t.style,s={};for(var o in e)i&&(s[o]=n[o]||""),n[o]=e[o];return s}var h={transitionProp:"transition",transEndEvent:"transitionend",transformProp:"transform",transformCssProp:"transform"},c=h.transformCssProp,u=h.transEndEvent;var d=function(){},f={enableGrab:!0,preloadImage:!1,closeOnWindowResize:!0,transitionDuration:.4,transitionTimingFunction:"cubic-bezier(0.4, 0, 0, 1)",bgColor:"rgb(255, 255, 255)",bgOpacity:1,scaleBase:1,scaleExtra:.5,scrollThreshold:40,zIndex:998,customSize:null,onOpen:d,onClose:d,onGrab:d,onMove:d,onRelease:d,onBeforeOpen:d,onBeforeClose:d,onBeforeGrab:d,onBeforeRelease:d,onImageLoading:d,onImageLoaded:d},p={init:function(t){var e,i;e=this,i=t,Object.getOwnPropertyNames(Object.getPrototypeOf(e)).forEach(function(t){e[t]=e[t].bind(i)})},click:function(t){if(t.preventDefault(),m(t))return window.open(this.target.srcOriginal||t.currentTarget.src,"_blank");this.shown?this.released?this.close():this.release():this.open(t.currentTarget)},scroll:function(){var t=document.documentElement||document.body.parentNode||document.body,e=window.pageXOffset||t.scrollLeft,i=window.pageYOffset||t.scrollTop;null===this.lastScrollPosition&&(this.lastScrollPosition={x:e,y:i});var n=this.lastScrollPosition.x-e,s=this.lastScrollPosition.y-i,o=this.options.scrollThreshold;(Math.abs(s)>=o||Math.abs(n)>=o)&&(this.lastScrollPosition=null,this.close())},keydown:function(t){(function(t){return"Escape"===(t.key||t.code)||27===t.keyCode})(t)&&(this.released?this.close():this.release(this.close))},mousedown:function(t){if(y(t)&&!m(t)){t.preventDefault();var e=t.clientX,i=t.clientY;this.pressTimer=setTimeout(function(){this.grab(e,i)}.bind(this),200)}},mousemove:function(t){this.released||this.move(t.clientX,t.clientY)},mouseup:function(t){y(t)&&!m(t)&&(clearTimeout(this.pressTimer),this.released?this.close():this.release())},touchstart:function(t){t.preventDefault();var e=t.touches[0],i=e.clientX,n=e.clientY;this.pressTimer=setTimeout(function(){this.grab(i,n)}.bind(this),200)},touchmove:function(t){if(!this.released){var e=t.touches[0],i=e.clientX,n=e.clientY;this.move(i,n)}},touchend:function(t){(function(t){t.targetTouches.length})(t)||(clearTimeout(this.pressTimer),this.released?this.close():this.release())},clickOverlay:function(){this.close()},resizeWindow:function(){this.close()}};function y(t){return 0===t.button}function m(t){return t.metaKey||t.ctrlKey}var g={init:function(t){this.el=document.createElement("div"),this.instance=t,this.parent=document.body,l(this.el,{position:"fixed",top:0,left:0,right:0,bottom:0,opacity:0}),this.updateStyle(t.options),o(this.el,"click",t.handler.clickOverlay.bind(t))},updateStyle:function(t){l(this.el,{zIndex:t.zIndex,backgroundColor:t.bgColor,transition:"opacity\n "+t.transitionDuration+"s\n "+t.transitionTimingFunction})},insert:function(){this.parent.appendChild(this.el)},remove:function(){this.parent.removeChild(this.el)},fadeIn:function(){this.el.offsetWidth,this.el.style.opacity=this.instance.options.bgOpacity},fadeOut:function(){this.el.style.opacity=0}},v="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},b=function(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")},w=function(){function t(t,e){for(var i=0;i<e.length;i++){var n=e[i];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(t,n.key,n)}}return function(e,i,n){return i&&t(e.prototype,i),n&&t(e,n),e}}(),x=Object.assign||function(t){for(var e=1;e<arguments.length;e++){var i=arguments[e];for(var n in i)Object.prototype.hasOwnProperty.call(i,n)&&(t[n]=i[n])}return t},O={init:function(t,e){this.el=t,this.instance=e,this.srcThumbnail=this.el.getAttribute("src"),this.srcset=this.el.getAttribute("srcset"),this.srcOriginal=a(this.el),this.rect=this.el.getBoundingClientRect(),this.translate=null,this.scale=null,this.styleOpen=null,this.styleClose=null},zoomIn:function(){var t=this.instance.options,e=t.zIndex,s=t.enableGrab,o=t.transitionDuration,r=t.transitionTimingFunction;this.translate=this.calculateTranslate(),this.scale=this.calculateScale(),this.styleOpen={position:"relative",zIndex:e+1,cursor:s?n:i,transition:c+"\n "+o+"s\n "+r,transform:"translate3d("+this.translate.x+"px, "+this.translate.y+"px, 0px)\n scale("+this.scale.x+","+this.scale.y+")",height:this.rect.height+"px",width:this.rect.width+"px"},this.el.offsetWidth,this.styleClose=l(this.el,this.styleOpen,!0)},zoomOut:function(){this.el.offsetWidth,l(this.el,{transform:"none"})},grab:function(t,e,i){var n=k(),o=n.x-t,r=n.y-e;l(this.el,{cursor:s,transform:"translate3d(\n "+(this.translate.x+o)+"px, "+(this.translate.y+r)+"px, 0px)\n scale("+(this.scale.x+i)+","+(this.scale.y+i)+")"})},move:function(t,e,i){var n=k(),s=n.x-t,o=n.y-e;l(this.el,{transition:c,transform:"translate3d(\n "+(this.translate.x+s)+"px, "+(this.translate.y+o)+"px, 0px)\n scale("+(this.scale.x+i)+","+(this.scale.y+i)+")"})},restoreCloseStyle:function(){l(this.el,this.styleClose)},restoreOpenStyle:function(){l(this.el,this.styleOpen)},upgradeSource:function(){if(this.srcOriginal){var t=this.el.parentNode;this.srcset&&this.el.removeAttribute("srcset");var e=this.el.cloneNode(!1);e.setAttribute("src",this.srcOriginal),e.style.position="fixed",e.style.visibility="hidden",t.appendChild(e),setTimeout(function(){this.el.setAttribute("src",this.srcOriginal),t.removeChild(e)}.bind(this),50)}},downgradeSource:function(){this.srcOriginal&&(this.srcset&&this.el.setAttribute("srcset",this.srcset),this.el.setAttribute("src",this.srcThumbnail))},calculateTranslate:function(){var t=k(),e=this.rect.left+this.rect.width/2,i=this.rect.top+this.rect.height/2;return{x:t.x-e,y:t.y-i}},calculateScale:function(){var t=this.el.dataset,e=t.zoomingHeight,i=t.zoomingWidth,n=this.instance.options,s=n.customSize,o=n.scaleBase;if(!s&&e&&i)return{x:i/this.rect.width,y:e/this.rect.height};if(s&&"object"===(void 0===s?"undefined":v(s)))return{x:s.width/this.rect.width,y:s.height/this.rect.height};var r=this.rect.width/2,a=this.rect.height/2,l=k(),h={x:l.x-r,y:l.y-a},c=h.x/r,u=h.y/a,d=o+Math.min(c,u);if(s&&"string"==typeof s){var f=i||this.el.naturalWidth,p=e||this.el.naturalHeight,y=parseFloat(s)*f/(100*this.rect.width),m=parseFloat(s)*p/(100*this.rect.height);if(d>y||d>m)return{x:y,y:m}}return{x:d,y:d}}};function k(){var t=document.documentElement;return{x:Math.min(t.clientWidth,window.innerWidth)/2,y:Math.min(t.clientHeight,window.innerHeight)/2}}function S(t,e,i){["mousedown","mousemove","mouseup","touchstart","touchmove","touchend"].forEach(function(n){o(t,n,e[n],i)})}return function(){function i(t){b(this,i),this.target=Object.create(O),this.overlay=Object.create(g),this.handler=Object.create(p),this.body=document.body,this.shown=!1,this.lock=!1,this.released=!0,this.lastScrollPosition=null,this.pressTimer=null,this.options=x({},f,t),this.overlay.init(this),this.handler.init(this)}return w(i,[{key:"listen",value:function(t){if("string"==typeof t)for(var i=document.querySelectorAll(t),n=i.length;n--;)this.listen(i[n]);else"IMG"===t.tagName&&(t.style.cursor=e,o(t,"click",this.handler.click),this.options.preloadImage&&r(a(t)));return this}},{key:"config",value:function(t){return t?(x(this.options,t),this.overlay.updateStyle(this.options),this):this.options}},{key:"open",value:function(t){var e=this,i=arguments.length>1&&void 0!==arguments[1]?arguments[1]:this.options.onOpen;if(!this.shown&&!this.lock){var n="string"==typeof t?document.querySelector(t):t;if("IMG"===n.tagName){if(this.options.onBeforeOpen(n),this.target.init(n,this),!this.options.preloadImage){var s=this.target.srcOriginal;null!=s&&(this.options.onImageLoading(n),r(s,this.options.onImageLoaded))}this.shown=!0,this.lock=!0,this.target.zoomIn(),this.overlay.insert(),this.overlay.fadeIn(),o(document,"scroll",this.handler.scroll),o(document,"keydown",this.handler.keydown),this.options.closeOnWindowResize&&o(window,"resize",this.handler.resizeWindow);return o(n,u,function t(){o(n,u,t,!1),e.lock=!1,e.target.upgradeSource(),e.options.enableGrab&&S(document,e.handler,!0),i(n)}),this}}}},{key:"close",value:function(){var e=this,i=arguments.length>0&&void 0!==arguments[0]?arguments[0]:this.options.onClose;if(this.shown&&!this.lock){var n=this.target.el;this.options.onBeforeClose(n),this.lock=!0,this.body.style.cursor=t,this.overlay.fadeOut(),this.target.zoomOut(),o(document,"scroll",this.handler.scroll,!1),o(document,"keydown",this.handler.keydown,!1),this.options.closeOnWindowResize&&o(window,"resize",this.handler.resizeWindow,!1);return o(n,u,function t(){o(n,u,t,!1),e.shown=!1,e.lock=!1,e.target.downgradeSource(),e.options.enableGrab&&S(document,e.handler,!1),e.target.restoreCloseStyle(),e.overlay.remove(),i(n)}),this}}},{key:"grab",value:function(t,e){var i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:this.options.scaleExtra,n=arguments.length>3&&void 0!==arguments[3]?arguments[3]:this.options.onGrab;if(this.shown&&!this.lock){var s=this.target.el;this.options.onBeforeGrab(s),this.released=!1,this.target.grab(t,e,i);return o(s,u,function t(){o(s,u,t,!1),n(s)}),this}}},{key:"move",value:function(t,e){var i=arguments.length>2&&void 0!==arguments[2]?arguments[2]:this.options.scaleExtra,n=arguments.length>3&&void 0!==arguments[3]?arguments[3]:this.options.onMove;if(this.shown&&!this.lock){this.released=!1,this.body.style.cursor=s,this.target.move(t,e,i);var r=this.target.el;return o(r,u,function t(){o(r,u,t,!1),n(r)}),this}}},{key:"release",value:function(){var e=this,i=arguments.length>0&&void 0!==arguments[0]?arguments[0]:this.options.onRelease;if(this.shown&&!this.lock){var n=this.target.el;this.options.onBeforeRelease(n),this.lock=!0,this.body.style.cursor=t,this.target.restoreOpenStyle();return o(n,u,function t(){o(n,u,t,!1),e.lock=!1,e.released=!0,i(n)}),this}}}]),i}()});
configs/gen_fbdd_v1.yaml ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ score_ckpt: resources/checkpoints/DiffDock/score_model/best_ema_inference_epoch_model.pt
2
+ confidence_ckpt: resources/checkpoints/DiffDock/confidence_model/best_model_epoch75.pt
3
+ docking_batch_size: 20
4
+ #confidence_model_dir: ./workdir/v1.1/confidence_model
5
+ different_schedules: false
6
+ inf_sched_alpha: 1
7
+ inf_sched_beta: 1
8
+ inference_steps: 20
9
+ initial_noise_std_proportion: 1.4601642460337794
10
+ limit_failures: 5
11
+ #model_dir: ./workdir/v1.1/score_model
12
+ #comment
13
+ no_final_step_noise: true
14
+ no_model: false
15
+ no_random: false
16
+ no_random_pocket: false
17
+ ode: false
18
+ old_filtering_model: true
19
+ old_score_model: false
20
+ resample_rdkit: false
21
+ samples_per_complex: 10
22
+ sigma_schedule: expbeta
23
+ temp_psi_rot: 0.9022615585677628
24
+ temp_psi_tor: 0.5946212391366862
25
+ temp_psi_tr: 0.727287304570729
26
+ temp_sampling_rot: 2.06391612594481
27
+ temp_sampling_tor: 7.044261621607846
28
+ temp_sampling_tr: 1.170050527854316
29
+ temp_sigma_data_rot: 0.7464326999906034
30
+ temp_sigma_data_tor: 0.6943254174849822
31
+ temp_sigma_data_tr: 0.9299802531572672
32
+
33
+ rmsd_threshold: 1.5
34
+
35
+ linker_ckpt:
36
+ pocket_full: resources/checkpoints/DiffLinker/pockets_difflinker_full_no_anchors_fc_pdb_excluded.ckpt
37
+ pocket_bb: resources/checkpoints/DiffLinker/pockets_difflinker_backbone.ckpt
38
+ geom: resources/checkpoints/DiffLinker/geom_difflinker.ckpt
39
+ size_ckpt: resources/checkpoints/DiffLinker/geom_size_gnn.ckpt
40
+ linker_condition: 'none' # pocket
41
+ linker_batch_size: 64
42
+ linker_steps: 1000
inference.py CHANGED
@@ -69,9 +69,15 @@ pandarallel.initialize(nb_workers=nb_workers, progress_bar=progress_bar)
69
 
70
 
71
  def read_fragment_library(file_path):
 
 
72
  file_path = Path(file_path)
73
  if file_path.suffix == '.csv':
74
  df = pd.read_csv(file_path)
 
 
 
 
75
  PandasTools.AddMoleculeColumnToFrame(df, smilesCol='X1', molCol='mol')
76
  elif file_path.suffix == '.sdf':
77
  df = PandasTools.LoadSDF(file_path, smilesName='X1', molColName='mol')
@@ -606,7 +612,7 @@ def process_docking_results(
606
  linking_df.drop(columns=['fragment_mol']).to_csv(Path(args.out_dir, 'linking_summary.csv'), index=False)
607
  return linking_df
608
  else:
609
- raise ValueError('No eligible fragment pairs found for linking.')
610
 
611
 
612
  def extract_pockets(protein_path, ligand_residue=None, top_pockets=None):
@@ -1340,7 +1346,8 @@ if __name__ == "__main__":
1340
  out_dir=args.out_dir,
1341
  )
1342
  if linking_df is None or len(linking_df) == 0:
1343
- raise ValueError('No eligible fragment-conformer pairs found for fragment linking.')
 
1344
 
1345
  generate_linkers(
1346
  linking_df,
 
69
 
70
 
71
  def read_fragment_library(file_path):
72
+ if file_path is None:
73
+ return pd.DataFrame(columns=['X1', 'ID1', 'mol'])
74
  file_path = Path(file_path)
75
  if file_path.suffix == '.csv':
76
  df = pd.read_csv(file_path)
77
+ # Validate columns
78
+ for col in ['X1', 'ID1']:
79
+ if col not in df.columns:
80
+ raise ValueError(f"Column '{col}' not found in CSV file.")
81
  PandasTools.AddMoleculeColumnToFrame(df, smilesCol='X1', molCol='mol')
82
  elif file_path.suffix == '.sdf':
83
  df = PandasTools.LoadSDF(file_path, smilesName='X1', molColName='mol')
 
612
  linking_df.drop(columns=['fragment_mol']).to_csv(Path(args.out_dir, 'linking_summary.csv'), index=False)
613
  return linking_df
614
  else:
615
+ raise ValueError('No eligible fragment pose pairs found for linking.')
616
 
617
 
618
  def extract_pockets(protein_path, ligand_residue=None, top_pockets=None):
 
1346
  out_dir=args.out_dir,
1347
  )
1348
  if linking_df is None or len(linking_df) == 0:
1349
+ log.error('No eligible fragment pose pairs found for linking.')
1350
+ sys.exit()
1351
 
1352
  generate_linkers(
1353
  linking_df,