libokj commited on
Commit
642d891
·
1 Parent(s): 8f135eb
app/fn.py CHANGED
@@ -1,3 +1,4 @@
 
1
  from datetime import datetime
2
  from email.mime.multipart import MIMEMultipart
3
  from email.mime.text import MIMEText
@@ -18,7 +19,7 @@ import panel as pn
18
  import seaborn as sns
19
  from markdown import markdown
20
  from rdkit import Chem, RDConfig
21
- from rdkit.Chem import Crippen, Descriptors, rdMolDescriptors, Lipinski, rdmolops
22
  import requests
23
 
24
  from app import static
@@ -37,6 +38,16 @@ COL_ALIASES = {
37
  'name': 'Complex Name',
38
  }
39
 
 
 
 
 
 
 
 
 
 
 
40
 
41
  def lipinski(mol):
42
  """
@@ -401,14 +412,46 @@ def read_molecule_file(in_file, allowed_extentions):
401
  # html = static.COMPLEX_RENDERING_TEMPLATE.format(viewer_models=viewer_models)
402
  # return static.IFRAME_TEMPLATE.format(html=html)
403
 
404
- def create_result_table_html(summary_df, result_info, opts=(), progress=gr.Progress(track_tqdm=True)):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
405
  html_df = summary_df.copy().drop(columns=['mol'])
406
- output_dir = Path(result_info['output_dir'])
407
- job_type = result_info['type']
408
  html_df.rename(columns=COL_ALIASES, inplace=True)
409
- html_df['Pose'] = html_df['Pose'].apply(lambda x: str(output_dir / job_type / x))
410
- # drop remaining columns ending with '_path'
 
 
411
  hidden_cols = [col for col in html_df.columns if col.endswith('_path')]
 
 
 
 
412
  html_df.index.name = 'Index'
413
 
414
  # if 'Scaffold' in html_df.columns and 'Exclude Scaffold Graph' not in opts:
@@ -448,7 +491,7 @@ def create_result_table_html(summary_df, result_info, opts=(), progress=gr.Progr
448
 
449
  report_table = pn.widgets.Tabulator(
450
  html_df, formatters=formatters,
451
- frozen_columns=['Pose', 'Compound ID', 'Compound'],
452
  hidden_columns=hidden_cols,
453
  sizing_mode='stretch_both',
454
  disabled=True, selectable=False,
@@ -499,17 +542,24 @@ def create_result_table_html(summary_df, result_info, opts=(), progress=gr.Progr
499
  # template.main.append(
500
  # pn.Card(stats_pane, sizing_mode='stretch_width', title='Summary Statistics', margin=10)
501
  # )
502
- report = pn.Column(pn.Accordion(
503
- (f'{job_type.title()} Results', report_table),
504
- toggle=True, margin=5, active=[0]
505
- ))
 
 
 
 
 
 
 
506
 
507
  with tempfile.TemporaryDirectory() as tmpdir:
508
  file = Path(tmpdir) / 'report.html'
509
  report.save(file)
510
  # iframe_html = static.IFRAME_LINK_TEMPLATE.format(src="gradio_api/file=" + str(file))
511
  html_str = file.read_text() # .replace('\'', '\"')
512
- iframe_html = static.IFRAME_TEMPLATE.format(srcdoc=html.escape(html_str), aspect_ratio='1.090 / 1')
513
  return iframe_html
514
 
515
 
 
1
+ import urllib.parse
2
  from datetime import datetime
3
  from email.mime.multipart import MIMEMultipart
4
  from email.mime.text import MIMEText
 
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, rdDepictor
23
  import requests
24
 
25
  from app import static
 
38
  'name': 'Complex Name',
39
  }
40
 
41
+ COL_DTYPE = {
42
+ 'out_path': 'str',
43
+ 'ligand_conf_path': 'str',
44
+ 'ID1': 'str',
45
+ 'ID2': 'str',
46
+ 'X1': 'str',
47
+ 'X1^': 'str',
48
+ 'name': 'str',
49
+ }
50
+
51
 
52
  def lipinski(mol):
53
  """
 
412
  # html = static.COMPLEX_RENDERING_TEMPLATE.format(viewer_models=viewer_models)
413
  # return static.IFRAME_TEMPLATE.format(html=html)
414
 
415
+ def prepare_df_for_table(result_df):
416
+ result_df.dropna(subset=['mol'], inplace=True)
417
+
418
+ rdDepictor.SetPreferCoordGen(True)
419
+ draw_opts = Draw.rdMolDraw2D.MolDrawOptions()
420
+ draw_opts.clearBackground = False
421
+ draw_opts.bondLineWidth = 0.5
422
+ draw_opts.explicitMethyl = True
423
+ draw_opts.singleColourWedgeBonds = True
424
+ draw_opts.addStereoAnnotation = False
425
+ draw_opts.useCDKAtomPalette()
426
+
427
+ def draw_mol(mol):
428
+ # Create a new drawer instance for each molecule (for efficiency)
429
+ drawer = Draw.MolDraw2DSVG(90, 56)
430
+ drawer.SetDrawOptions(draw_opts)
431
+
432
+ # Draw the molecule and return the SVG as a URI
433
+ drawer.DrawMolecule(mol)
434
+ drawer.FinishDrawing()
435
+ return urllib.parse.quote(drawer.GetDrawingText())
436
+
437
+ # Convert to URI-formatted inline SVG
438
+ result_df['Compound'] = result_df['mol'].apply(draw_mol)
439
+
440
+ return result_df
441
+
442
+
443
+ def create_result_table_html(summary_df, result_info=None, opts=(), progress=gr.Progress(track_tqdm=True)):
444
  html_df = summary_df.copy().drop(columns=['mol'])
 
 
445
  html_df.rename(columns=COL_ALIASES, inplace=True)
446
+ if result_info:
447
+ output_dir = Path(result_info['output_dir'])
448
+ job_type = result_info['type']
449
+ html_df['Pose'] = html_df['Pose'].apply(lambda x: str(output_dir / job_type / x))
450
  hidden_cols = [col for col in html_df.columns if col.endswith('_path')]
451
+ rightmost_cols = ['Complex Name', 'Fragment SMILES', 'Compound SMILES']
452
+ col_order = ([col for col in html_df.columns if col not in rightmost_cols] +
453
+ [col for col in html_df.columns if col in rightmost_cols])
454
+ html_df = html_df[col_order]
455
  html_df.index.name = 'Index'
456
 
457
  # if 'Scaffold' in html_df.columns and 'Exclude Scaffold Graph' not in opts:
 
491
 
492
  report_table = pn.widgets.Tabulator(
493
  html_df, formatters=formatters,
494
+ frozen_columns=['Index', 'Pose', 'Compound ID', 'Compound'],
495
  hidden_columns=hidden_cols,
496
  sizing_mode='stretch_both',
497
  disabled=True, selectable=False,
 
542
  # template.main.append(
543
  # pn.Card(stats_pane, sizing_mode='stretch_width', title='Summary Statistics', margin=10)
544
  # )
545
+ if result_info:
546
+ table_title = (f"{job_type.title()} Results "
547
+ f"({'No Linkable Pairs' if job_type != 'linking' else 'Generated Molecules'})")
548
+ report = pn.Column(pn.Accordion(
549
+ (table_title, report_table),
550
+ toggle=True, margin=5, active=[0]
551
+ ))
552
+ aspect_ratio = '1.090 / 1'
553
+ else:
554
+ report = report_table
555
+ aspect_ratio = '1.618 / 1'
556
 
557
  with tempfile.TemporaryDirectory() as tmpdir:
558
  file = Path(tmpdir) / 'report.html'
559
  report.save(file)
560
  # iframe_html = static.IFRAME_LINK_TEMPLATE.format(src="gradio_api/file=" + str(file))
561
  html_str = file.read_text() # .replace('\'', '\"')
562
+ iframe_html = static.IFRAME_TEMPLATE.format(srcdoc=html.escape(html_str), aspect_ratio=aspect_ratio)
563
  return iframe_html
564
 
565
 
app/main.py CHANGED
@@ -1,3 +1,7 @@
 
 
 
 
1
  import spaces
2
 
3
  import io
@@ -7,8 +11,7 @@ import uuid
7
  import zipfile
8
  from datetime import datetime
9
  from pathlib import Path
10
- from time import sleep, time
11
- import urllib.parse
12
 
13
  from dotenv import load_dotenv
14
  import torch
@@ -19,7 +22,6 @@ from gradio_rangeslider import RangeSlider
19
  from omegaconf import OmegaConf
20
  import pandas as pd
21
  from rdkit import Chem
22
- from rdkit.Chem import PandasTools, Draw, rdDepictor
23
 
24
  from inference import (read_fragment_library, process_fragment_library, extract_pockets,
25
  dock_fragments, generate_linkers, select_fragment_pairs)
@@ -27,12 +29,35 @@ from app import static, fn, db
27
 
28
 
29
  load_dotenv()
30
- Path(tempfile.gettempdir(), 'gradio').mkdir(exist_ok=True)
31
- SERVER_DATA_DIR = os.getenv('DATA', 'results')
32
- gr.set_static_paths(paths=["data/", SERVER_DATA_DIR, "app/"])
 
 
 
33
  job_db = db.init_job_db()
 
 
 
34
  os.chmod('./fpocket', 0o755)
35
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
  FRAG_LIBS = {'': None} | {
37
  lib_path.stem.replace('_', ' '): str(lib_path) for lib_path in Path('data/fragment_libraries').glob('*')
38
  }
@@ -58,6 +83,7 @@ POCKET_EXTRACT_OPTS = {
58
  }
59
  }
60
 
 
61
  def gr_error_wrapper(func):
62
  def wrapper(*args, **kwargs):
63
  try:
@@ -67,69 +93,71 @@ def gr_error_wrapper(func):
67
 
68
  return wrapper
69
 
70
- def query_job_status(job_id):
71
- gr.Info('Start querying the job database...')
72
  stop = False
 
73
  retry = 0
 
74
  while not stop:
75
- try:
76
- job = job_db.job_lookup(job_id)
77
- if job:
78
- if job['status'] == "RUNNING":
79
- yield {
80
- pred_lookup_status: f'''
81
- Your job (ID: **{job['id']}**) started at **{job['start_time']}** and is **RUNNING...**
82
-
83
- It might take a few minutes up to a few hours depending on the input size and the queue status.
84
- You may keep the page open and wait for job completion, or close the page and revisit later to look up the job status
85
- using the job id. You will also receive an email notification once the job is done.
86
- ''',
87
- pred_lookup_btn: gr.Button(visible=False),
88
- pred_lookup_stop_btn: gr.Button(visible=True),
89
- }
90
- if job['status'] == "COMPLETED":
91
- stop = True
92
- msg = f"Your GenFBDD job (ID: {job['id']}) has been **COMPLETED**"
93
- msg += f" at **{job['end_time']}**" if job.get('end_time') else ""
94
- msg += f" and the results will **EXPIRE** by **{job['expiry_time']}**." if job.get('expiry_time') else "."
95
- msg += f' Redirecting to the Results page...'
96
-
97
- gr.Info(msg)
98
- yield {
99
- pred_lookup_status: msg,
100
- tabs: gr.Tabs(selected='result'),
101
- result_state: job,
102
- pred_lookup_btn: gr.Button(visible=True),
103
- pred_lookup_stop_btn: gr.Button(visible=False),
104
- }
105
- if job['status'] == "FAILED":
106
- stop = True
107
- msg = f'Your GenFBDD job (ID: {job_id}) has **FAILED**'
108
- msg += f" at {job['end_time']}" if job.get('end_time') else ''
109
- msg += f" due to error: {job['error']}." if job.get('expiry_time') else '.'
110
- gr.Info(msg)
111
- yield {
112
- pred_lookup_status: msg,
113
- pred_lookup_btn: gr.Button(visible=True),
114
- pred_lookup_stop_btn: gr.Button(visible=False),
115
- }
116
- else:
117
- stop = (retry > 2)
118
- if not stop:
119
- msg = f'Job ID {job_id} not found. Retrying... ({retry})'
120
- else:
121
- msg = f'Job ID {job_id} not found after {retry} retries. Please double-check the job ID.'
122
- gr.Info(msg)
123
- retry += 1
124
  yield {
125
  pred_lookup_status: msg,
126
- pred_lookup_btn: gr.Button(visible=stop),
127
- pred_lookup_stop_btn: gr.Button(visible= not stop),
 
 
128
  }
129
- sleep(5)
130
 
131
- except Exception as e:
132
- raise gr.Error(f'Failed to retrieve job status due to error: {str(e)}')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
133
 
134
 
135
  def checkbox_group_selections_to_kwargs(selected_options, option_mapping):
@@ -140,10 +168,12 @@ def checkbox_group_selections_to_kwargs(selected_options, option_mapping):
140
  return kwargs
141
 
142
 
143
- def job_validate(
144
- frag_file, frag_df, prot_file,
 
 
145
  pocket_name, pocket_method, pocket_fs,
146
- email, run_info, session_info: gr.Request
147
  ):
148
  if len(frag_df) == 0 or not frag_file:
149
  raise gr.Error("Please provide a valid fragment library.")
@@ -168,14 +198,10 @@ def job_validate(
168
  except EmailNotValidError as e:
169
  raise gr.Error(f"Invalid email address: {str(e)}.")
170
 
171
- if run_info:
172
- raise gr.Error(f"You already have a running prediction job (ID: {run_info['id']}) under this session. "
173
- "Please wait for it to complete before submitting another job.")
174
  if check := job_db.check_user_running_job(email, session_info):
175
  raise gr.Error(check)
176
 
177
- gr.Info('Finished processing inputs. Initiating the GenFBDD job... '
178
- 'You will be redirected to Job Status page.')
179
  job_id = str(uuid.uuid4())
180
  job_info = {
181
  'id': job_id,
@@ -194,7 +220,17 @@ def job_validate(
194
  }
195
  job_db.insert(job_info)
196
 
197
- return job_info
 
 
 
 
 
 
 
 
 
 
198
 
199
  @spaces.GPU(duration=600)
200
  def dock_link(
@@ -216,7 +252,7 @@ def dock_link(
216
  device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
217
  print(f'Using device: {device}')
218
  date_time = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
219
- out_dir = Path(SERVER_DATA_DIR, f'{date_time}_{job_id}')
220
  frag_lib['X2'] = prot
221
  frag_lib['ID2'] = str(Path(prot).stem)
222
 
@@ -280,7 +316,6 @@ def dock_link(
280
  'output_dir': str(out_dir),
281
  'type': job_type,
282
  }
283
- # return {result_state: job_info | update_info, run_state: {}}
284
 
285
  except Exception as e:
286
  gr.Warning(f"Job failed due to error: {str(e)}")
@@ -289,7 +324,6 @@ def dock_link(
289
  'error': str(e),
290
  'output_dir': None
291
  }
292
- # return {result_state: {}, run_state: {}}
293
 
294
  finally:
295
  job_db.job_update(
@@ -298,10 +332,6 @@ def dock_link(
298
  )
299
 
300
 
301
- def get_session_state(request: gr.Request):
302
- return request
303
-
304
-
305
  THEME = gr.themes.Base(
306
  spacing_size="sm", text_size='md', font=gr.themes.GoogleFont("Roboto"),
307
  primary_hue='emerald', secondary_hue='emerald', neutral_hue='slate',
@@ -329,8 +359,6 @@ THEME = gr.themes.Base(
329
  )
330
 
331
  with gr.Blocks(theme=THEME, title='GenFBDD', css=static.CSS, delete_cache=(3600, 48 * 3600)) as demo:
332
- run_state = gr.State(value={})
333
- session_state = gr.State(value={})
334
  with gr.Column(variant='panel'):
335
  with gr.Tabs() as tabs:
336
  with gr.Tab(label='Start', id='start'):
@@ -361,11 +389,11 @@ with gr.Blocks(theme=THEME, title='GenFBDD', css=static.CSS, delete_cache=(3600,
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():
371
  frag_lib_process_opts = gr.CheckboxGroup(
@@ -510,6 +538,27 @@ with gr.Blocks(theme=THEME, title='GenFBDD', css=static.CSS, delete_cache=(3600,
510
  value='Run GenFBDD', variant='primary', interactive=True,
511
  )
512
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
513
  with gr.Tab(label='Results', id='result'):
514
  # Results
515
  result_state = gr.State(value={})
@@ -520,6 +569,7 @@ with gr.Blocks(theme=THEME, title='GenFBDD', css=static.CSS, delete_cache=(3600,
520
  with gr.Row():
521
  scores = gr.CheckboxGroup(list(fn.SCORE_MAP.keys()), label='Compound Scores')
522
  filters = gr.CheckboxGroup(list(fn.FILTER_MAP.keys()), label='Compound Filters')
 
523
  with gr.Row():
524
  prop_clr_btn = gr.ClearButton(value='Clear Properties', interactive=False)
525
  prop_calc_btn = gr.Button(value='Calculate Properties', interactive=False, variant='primary')
@@ -531,28 +581,6 @@ with gr.Blocks(theme=THEME, title='GenFBDD', css=static.CSS, delete_cache=(3600,
531
  result_file_btn = gr.Button(value='Create Result File', visible=False, variant='primary')
532
  result_download_file = gr.File(label='Download Result File', visible=False)
533
 
534
- with gr.Tab(label='Job Status', id='job'):
535
- gr.Markdown('''
536
- To check the status of an in-progress or historical job using the job ID and retrieve the predictions
537
- if the job has completed. Note that predictions are only kept for 48 hours upon job completion.
538
-
539
- You will be redirected to `Results` for carrying out further analysis and
540
- generating the full report when the job is done. If the the query fails to respond, please wait for a
541
- few minutes and refresh the page to try again.
542
- ''')
543
- with gr.Row():
544
- with gr.Column(scale=1):
545
- loader_html = gr.HTML('<div class="loader first-frame"></div>', visible=False)
546
- with gr.Column(scale=4):
547
- pred_lookup_id = gr.Textbox(
548
- label='Input Your Job ID', placeholder='e.g., e9dfd149-3f5c-48a6-b797-c27d027611ac',
549
- info="Your job ID is a UUID4 string that you receive after submitting a job on the "
550
- "page or in the email notification.")
551
- pred_lookup_example = gr.Button('Example', elem_classes=['example'], scale=1)
552
- pred_lookup_btn = gr.Button(value='Query Status', variant='primary', visible=True)
553
- pred_lookup_stop_btn = gr.Button(value='Stop Tracking', variant='stop', visible=False)
554
- pred_lookup_status = gr.Markdown("**Job Status**", container=True)
555
-
556
  # Event handlers
557
  ## Start tab
558
  ### Fragment Library
@@ -573,7 +601,7 @@ with gr.Blocks(theme=THEME, title='GenFBDD', css=static.CSS, delete_cache=(3600,
573
  inputs=[frag_lib_file],
574
  outputs=[frag_lib_orig_df],
575
  ).success(
576
- fn=lambda df: [df, gr.DataFrame(df.drop(columns='mol'), visible=True)],
577
  inputs=[frag_lib_orig_df],
578
  outputs=[frag_lib_mod_df, frag_lib_view],
579
  )
@@ -586,7 +614,7 @@ with gr.Blocks(theme=THEME, title='GenFBDD', css=static.CSS, delete_cache=(3600,
586
  new_df:=process_fragment_library(
587
  df, **checkbox_group_selections_to_kwargs(opts, FRAG_LIB_PROCESS_OPTS)
588
  ),
589
- gr.DataFrame(new_df.drop(columns='mol'), visible=True)
590
  ],
591
  inputs=[frag_lib_orig_df, frag_lib_process_opts],
592
  outputs=[frag_lib_mod_df, frag_lib_view],
@@ -705,26 +733,16 @@ with gr.Blocks(theme=THEME, title='GenFBDD', css=static.CSS, delete_cache=(3600,
705
  inputs=[selected_ligand, selected_pocket],
706
  outputs=[selected_ligand, selected_pocket],
707
  ).success(
708
- fn=job_validate,
709
- inputs=[
710
- frag_lib_file, frag_lib_mod_df, input_prot_file,
711
- selected_pocket, pocket_extract_dropdown, pocket_files,
712
- email_input, run_state
713
- ],
714
- outputs=[run_state],
715
- )
716
-
717
- job_valid.success(
718
- fn=dock_link,
719
  inputs=[
720
- frag_lib_mod_df, input_prot_file,
721
  dock_steps, dock_n_poses, dock_confidence_cutoff,
722
  link_frag_dist_range, link_frag_pose_strategy, link_n_mols,
723
  link_linker_size, link_steps,
724
- run_state
 
725
  ],
726
- # outputs=[result_state, run_state],
727
- concurrency_limit=1, concurrency_id="gpu_queue"
728
  )
729
 
730
  start_reset_components=[
@@ -750,57 +768,49 @@ with gr.Blocks(theme=THEME, title='GenFBDD', css=static.CSS, delete_cache=(3600,
750
  )
751
 
752
  ### Job Status
753
- pred_lookup_btn.click(
754
- fn=lambda: '<div class="loader"></ div>',
755
- outputs=loader_html,
756
- )
757
  user_job_lookup = pred_lookup_btn.click(
 
 
 
 
 
 
 
758
  fn=query_job_status,
759
  inputs=[pred_lookup_id],
760
  outputs=[pred_lookup_status, tabs, result_state, pred_lookup_btn, pred_lookup_stop_btn],
761
  show_progress='minimal',
762
- ).success(
763
- lambda: '<div class="loader first-frame"></ div>',
764
- outputs=loader_html,
765
  )
766
 
767
- job_valid_success = job_valid.success(
768
- fn=lambda job: [job['id'], gr.Tabs(selected='job')],
769
- inputs=[run_state],
770
- outputs=[pred_lookup_id, tabs],
 
 
 
771
  ).success(
772
- lambda: '<div class="loader"></ div>',
773
- outputs=loader_html,
774
- )
775
-
776
- auto_job_lookup = job_valid_success.success(
777
  fn=query_job_status,
778
  inputs=pred_lookup_id,
779
  outputs=[pred_lookup_status, tabs, result_state, pred_lookup_btn, pred_lookup_stop_btn],
780
  show_progress='minimal',
781
  cancels=[user_job_lookup],
782
- ).success(
783
- lambda: '<div class="loader first-frame"></ div>',
784
- outputs=loader_html,
785
  )
786
 
787
  pred_lookup_stop_btn.click(
788
  fn=lambda: [gr.Button(visible=True), gr.Button(visible=False)],
789
  outputs=[pred_lookup_btn, pred_lookup_stop_btn],
790
  cancels=[user_job_lookup, auto_job_lookup],
791
- ).success(
792
- lambda: '<div class="loader first-frame"></ div>',
793
- outputs=loader_html,
794
  )
795
 
796
- pred_lookup_example.click(
797
  fn=lambda: '80cf2658-7a1c-48d6-8372-61b978177fe6',
798
  outputs=[pred_lookup_id],
799
  show_progress='hidden'
800
  ).success(
801
  fn=query_job_status,
802
  inputs=pred_lookup_id,
803
- outputs=[pred_lookup_status, tabs, result_state],
804
  show_progress='minimal',
805
  cancels=[user_job_lookup, auto_job_lookup],
806
  )
@@ -811,29 +821,16 @@ with gr.Blocks(theme=THEME, title='GenFBDD', css=static.CSS, delete_cache=(3600,
811
  result_type = result_info['type']
812
  protein_structure_file = Path(result_info['protein_structure_file'])
813
  if result_type == 'docking':
814
- result_df = pd.read_csv(result_dir / 'docking_summary.csv')
815
  result_df['mol'] = result_df['X1'].apply(Chem.MolFromSmiles)
816
  elif result_type == 'linking':
817
- result_df = pd.read_csv(result_dir / 'linking_summary.csv')
818
- result_df = result_df[~result_df['X1^'].str.contains('.', regex=False)]
819
  result_df['mol'] = result_df['X1^'].apply(Chem.MolFromSmiles)
820
- result_df.dropna(subset=['mol'], inplace=True)
821
  else:
822
  raise gr.Error('Invalid result type')
823
 
824
- rdDepictor.SetPreferCoordGen(True)
825
- draw_opts = Draw.rdMolDraw2D.MolDrawOptions()
826
- draw_opts.clearBackground = False
827
- draw_opts.bondLineWidth = 0.5
828
- draw_opts.explicitMethyl = True
829
- draw_opts.singleColourWedgeBonds = True
830
- draw_opts.addStereoAnnotation = False
831
- draw_opts.useCDKAtomPalette()
832
- PandasTools.drawOptions = draw_opts
833
- PandasTools.molSize = (90, 56)
834
- PandasTools.molRepresentation = 'svg'
835
- # Convert to URI-formatted inline SVG
836
- result_df['Compound'] = result_df['mol'].apply(PandasTools.PrintAsImageString).apply(urllib.parse.quote)
837
 
838
  return {
839
  result_table_orig_df: result_df,
@@ -921,13 +918,14 @@ with gr.Blocks(theme=THEME, title='GenFBDD', css=static.CSS, delete_cache=(3600,
921
  outputs=[result_download_file],
922
  )
923
 
924
- demo.load(fn=get_session_state, inputs=None, outputs=session_state, js=static.SETUP_JS)
925
 
 
 
926
  demo.launch(
927
  server_name='0.0.0.0',
928
  max_file_size="5mb",
929
  ssr_mode=False,
930
- share=True,
931
- share_server_address="ciddr-lab.ac.cn:7000",
932
- share_server_protocol="https"
933
  )
 
1
+ import asyncio
2
+ import queue
3
+ import threading
4
+
5
  import spaces
6
 
7
  import io
 
11
  import zipfile
12
  from datetime import datetime
13
  from pathlib import Path
14
+ from time import time
 
15
 
16
  from dotenv import load_dotenv
17
  import torch
 
22
  from omegaconf import OmegaConf
23
  import pandas as pd
24
  from rdkit import Chem
 
25
 
26
  from inference import (read_fragment_library, process_fragment_library, extract_pockets,
27
  dock_fragments, generate_linkers, select_fragment_pairs)
 
29
 
30
 
31
  load_dotenv()
32
+ RESULTS_DIR = os.getenv('RESULTS', 'results')
33
+ MAX_CONCURRENT_JOBS = 1
34
+
35
+ task_queue = queue.Queue()
36
+ semaphore = threading.Semaphore(MAX_CONCURRENT_JOBS)
37
+ lock = threading.Lock()
38
  job_db = db.init_job_db()
39
+
40
+ Path(tempfile.gettempdir(), 'gradio').mkdir(exist_ok=True)
41
+ gr.set_static_paths(paths=["data/", RESULTS_DIR, "app/"])
42
  os.chmod('./fpocket', 0o755)
43
 
44
+ def task_worker():
45
+ """Worker function to process tasks from the queue with concurrency limit."""
46
+ while True:
47
+ data = task_queue.get() # Get the next task from the queue
48
+ with semaphore: # Ensure only 'MAX_CONCURRENT_JOBS' tasks run at once
49
+ with lock: # Ensure only one task is processed at a time (for shared state)
50
+ dock_link(*data)
51
+ task_queue.task_done()
52
+
53
+
54
+ worker_threads = []
55
+ for _ in range(MAX_CONCURRENT_JOBS):
56
+ worker_thread = threading.Thread(target=task_worker, daemon=True)
57
+ worker_threads.append(worker_thread)
58
+ worker_thread.start()
59
+
60
+
61
  FRAG_LIBS = {'': None} | {
62
  lib_path.stem.replace('_', ' '): str(lib_path) for lib_path in Path('data/fragment_libraries').glob('*')
63
  }
 
83
  }
84
  }
85
 
86
+
87
  def gr_error_wrapper(func):
88
  def wrapper(*args, **kwargs):
89
  try:
 
93
 
94
  return wrapper
95
 
96
+
97
+ async def query_job_status(job_id):
98
  stop = False
99
+ interval = 3 # Check every 3 seconds for better responsiveness
100
  retry = 0
101
+
102
  while not stop:
103
+ # Wait for a short interval before checking again
104
+ await asyncio.sleep(interval) # Non-blocking sleep
105
+ job = job_db.job_lookup(job_id)
106
+
107
+ if job: # If the job exists
108
+ if job['status'] == "RUNNING": # If the job is still running
109
+ yield {
110
+ pred_lookup_status: f'''
111
+ Your job (ID: **{job['id']}**) started at **{job['start_time']}** and is **RUNNING...**
112
+
113
+ It might take a few minutes to a few hours depending on the input size and the queue status.
114
+ You may keep the page open or close it and revisit later using the job ID.
115
+ You will receive an email notification once the job is done.
116
+ ''',
117
+ pred_lookup_btn: gr.update(visible=False),
118
+ pred_lookup_stop_btn: gr.update(visible=True),
119
+ }
120
+
121
+ elif job['status'] == "COMPLETED": # If the job is complete
122
+ stop = True
123
+ msg = f"Your job (ID: {job['id']}) has been **COMPLETED**"
124
+ msg += f" at **{job['end_time']}**" if job.get('end_time') else ""
125
+ msg += f" and the results will **EXPIRE** by **{job['expiry_time']}**." if job.get('expiry_time') else "."
126
+ msg += " Redirecting to the results page..."
127
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
128
  yield {
129
  pred_lookup_status: msg,
130
+ tabs: gr.Tabs(selected='result'),
131
+ result_state: job,
132
+ pred_lookup_btn: gr.update(visible=True),
133
+ pred_lookup_stop_btn: gr.update(visible=False),
134
  }
 
135
 
136
+ elif job['status'] == "FAILED": # If the job failed
137
+ stop = True
138
+ msg = f'Your job (ID: {job_id}) has **FAILED**'
139
+ msg += f" at {job['end_time']}" if job.get('end_time') else ''
140
+ msg += f" due to error: {job['error']}." if job.get('error') else '.'
141
+
142
+ yield {
143
+ pred_lookup_status: msg,
144
+ pred_lookup_btn: gr.update(visible=True),
145
+ pred_lookup_stop_btn: gr.update(visible=False),
146
+ }
147
+
148
+ else: # If the job is not found
149
+ stop = retry > 2 # Stop after 3 retries
150
+ if not stop:
151
+ msg = f'Job ID {job_id} not found. Retrying... ({retry})'
152
+ else:
153
+ msg = f'Job ID {job_id} not found after {retry} retries. Please double-check the job ID.'
154
+
155
+ retry += 1
156
+ yield {
157
+ pred_lookup_status: msg,
158
+ pred_lookup_btn: gr.update(visible=stop),
159
+ pred_lookup_stop_btn: gr.update(visible=not stop),
160
+ }
161
 
162
 
163
  def checkbox_group_selections_to_kwargs(selected_options, option_mapping):
 
168
  return kwargs
169
 
170
 
171
+ def job_submit(
172
+ frag_df, frag_file, prot_file,
173
+ dock_n_steps, dock_n_poses, dock_confidence_threshold,
174
+ linker_frag_dist, linker_strategy, linker_n_mols, linker_size, linker_steps,
175
  pocket_name, pocket_method, pocket_fs,
176
+ email, session_info: gr.Request
177
  ):
178
  if len(frag_df) == 0 or not frag_file:
179
  raise gr.Error("Please provide a valid fragment library.")
 
198
  except EmailNotValidError as e:
199
  raise gr.Error(f"Invalid email address: {str(e)}.")
200
 
 
 
 
201
  if check := job_db.check_user_running_job(email, session_info):
202
  raise gr.Error(check)
203
 
204
+ gr.Info('Finished processing inputs. Initiating the GenFBDD job... You will be redirected to Job Status page.')
 
205
  job_id = str(uuid.uuid4())
206
  job_info = {
207
  'id': job_id,
 
220
  }
221
  job_db.insert(job_info)
222
 
223
+ task_queue.put((
224
+ frag_df, prot_file,
225
+ dock_n_steps, dock_n_poses, dock_confidence_threshold,
226
+ linker_frag_dist, linker_strategy, linker_n_mols, linker_size, linker_steps,
227
+ job_info
228
+ ))
229
+
230
+ return {
231
+ pred_lookup_id: job_id,
232
+ tabs: gr.Tabs(selected='job'),
233
+ }
234
 
235
  @spaces.GPU(duration=600)
236
  def dock_link(
 
252
  device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
253
  print(f'Using device: {device}')
254
  date_time = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
255
+ out_dir = Path(RESULTS_DIR, f'{date_time}_{job_id}')
256
  frag_lib['X2'] = prot
257
  frag_lib['ID2'] = str(Path(prot).stem)
258
 
 
316
  'output_dir': str(out_dir),
317
  'type': job_type,
318
  }
 
319
 
320
  except Exception as e:
321
  gr.Warning(f"Job failed due to error: {str(e)}")
 
324
  'error': str(e),
325
  'output_dir': None
326
  }
 
327
 
328
  finally:
329
  job_db.job_update(
 
332
  )
333
 
334
 
 
 
 
 
335
  THEME = gr.themes.Base(
336
  spacing_size="sm", text_size='md', font=gr.themes.GoogleFont("Roboto"),
337
  primary_hue='emerald', secondary_hue='emerald', neutral_hue='slate',
 
359
  )
360
 
361
  with gr.Blocks(theme=THEME, title='GenFBDD', css=static.CSS, delete_cache=(3600, 48 * 3600)) as demo:
 
 
362
  with gr.Column(variant='panel'):
363
  with gr.Tabs() as tabs:
364
  with gr.Tab(label='Start', id='start'):
 
389
  )
390
  frag_lib_orig_df = gr.State(value=pd.DataFrame(columns=['X1', 'ID1', 'mol']))
391
  frag_lib_mod_df = gr.State(value=pd.DataFrame(columns=['X1', 'ID1', 'mol']))
392
+ # frag_lib_view = gr.DataFrame(
393
+ # value=pd.DataFrame(columns=['X1', 'ID1']), elem_id='frag_lib_view',
394
+ # visible=True, interactive=False,
395
+ # )
396
+ frag_lib_view = gr.HTML(static.IFRAME_TEMPLATE.format(aspect_ratio='1.618 /1', srcdoc=''))
397
 
398
  with gr.Group():
399
  frag_lib_process_opts = gr.CheckboxGroup(
 
538
  value='Run GenFBDD', variant='primary', interactive=True,
539
  )
540
 
541
+ with gr.Tab(label='Jobs', id='job'):
542
+ gr.Markdown('''
543
+ To check the status of an in-progress or historical job using the job ID and retrieve the predictions
544
+ if the job has completed. Note that predictions are only kept for 48 hours upon job completion.
545
+
546
+ You will be redirected to `Results` for carrying out further analysis and
547
+ generating the full report when the job is done. If the the query fails to respond, please wait for a
548
+ few minutes and refresh the page to try again.
549
+ ''')
550
+ with gr.Row():
551
+ with gr.Column(scale=1):
552
+ loader_html = gr.HTML('<div class="loader first-frame"></div>', visible=False)
553
+ with gr.Column(scale=4):
554
+ pred_lookup_id = gr.Textbox(
555
+ label='Input Your Job ID', placeholder='e.g., e9dfd149-3f5c-48a6-b797-c27d027611ac',
556
+ info="Your job ID is a UUID4 string that you receive after submitting a job on the "
557
+ "page or in the email notification.")
558
+ pred_lookup_btn = gr.Button(value='Query Status', variant='primary', visible=True)
559
+ pred_lookup_stop_btn = gr.Button(value='Stop Tracking', variant='stop', visible=False)
560
+ pred_lookup_status = gr.Markdown("**Job Status**", container=True)
561
+
562
  with gr.Tab(label='Results', id='result'):
563
  # Results
564
  result_state = gr.State(value={})
 
569
  with gr.Row():
570
  scores = gr.CheckboxGroup(list(fn.SCORE_MAP.keys()), label='Compound Scores')
571
  filters = gr.CheckboxGroup(list(fn.FILTER_MAP.keys()), label='Compound Filters')
572
+ result_example = gr.Button('Example', elem_classes=['example'])
573
  with gr.Row():
574
  prop_clr_btn = gr.ClearButton(value='Clear Properties', interactive=False)
575
  prop_calc_btn = gr.Button(value='Calculate Properties', interactive=False, variant='primary')
 
581
  result_file_btn = gr.Button(value='Create Result File', visible=False, variant='primary')
582
  result_download_file = gr.File(label='Download Result File', visible=False)
583
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
584
  # Event handlers
585
  ## Start tab
586
  ### Fragment Library
 
601
  inputs=[frag_lib_file],
602
  outputs=[frag_lib_orig_df],
603
  ).success(
604
+ fn=lambda df: [df.copy(), fn.create_result_table_html(fn.prepare_df_for_table(df))],
605
  inputs=[frag_lib_orig_df],
606
  outputs=[frag_lib_mod_df, frag_lib_view],
607
  )
 
614
  new_df:=process_fragment_library(
615
  df, **checkbox_group_selections_to_kwargs(opts, FRAG_LIB_PROCESS_OPTS)
616
  ),
617
+ fn.create_result_table_html(fn.prepare_df_for_table(new_df))
618
  ],
619
  inputs=[frag_lib_orig_df, frag_lib_process_opts],
620
  outputs=[frag_lib_mod_df, frag_lib_view],
 
733
  inputs=[selected_ligand, selected_pocket],
734
  outputs=[selected_ligand, selected_pocket],
735
  ).success(
736
+ fn=job_submit,
 
 
 
 
 
 
 
 
 
 
737
  inputs=[
738
+ frag_lib_mod_df, frag_lib_file, input_prot_file,
739
  dock_steps, dock_n_poses, dock_confidence_cutoff,
740
  link_frag_dist_range, link_frag_pose_strategy, link_n_mols,
741
  link_linker_size, link_steps,
742
+ selected_pocket, pocket_extract_dropdown, pocket_files,
743
+ email_input,
744
  ],
745
+ outputs=[pred_lookup_id, tabs],
 
746
  )
747
 
748
  start_reset_components=[
 
768
  )
769
 
770
  ### Job Status
 
 
 
 
771
  user_job_lookup = pred_lookup_btn.click(
772
+ fn=lambda: [
773
+ gr.update(value="Start querying the job database..."),
774
+ gr.update(visible=False),
775
+ gr.update(visible=True),
776
+ ],
777
+ outputs=[pred_lookup_status, pred_lookup_btn, pred_lookup_stop_btn],
778
+ ).success(
779
  fn=query_job_status,
780
  inputs=[pred_lookup_id],
781
  outputs=[pred_lookup_status, tabs, result_state, pred_lookup_btn, pred_lookup_stop_btn],
782
  show_progress='minimal',
 
 
 
783
  )
784
 
785
+ auto_job_lookup = job_valid.success(
786
+ fn=lambda: [
787
+ gr.update(value="Start querying the job database..."),
788
+ gr.update(visible=False),
789
+ gr.update(visible=True),
790
+ ],
791
+ outputs=[pred_lookup_status, pred_lookup_btn, pred_lookup_stop_btn],
792
  ).success(
 
 
 
 
 
793
  fn=query_job_status,
794
  inputs=pred_lookup_id,
795
  outputs=[pred_lookup_status, tabs, result_state, pred_lookup_btn, pred_lookup_stop_btn],
796
  show_progress='minimal',
797
  cancels=[user_job_lookup],
 
 
 
798
  )
799
 
800
  pred_lookup_stop_btn.click(
801
  fn=lambda: [gr.Button(visible=True), gr.Button(visible=False)],
802
  outputs=[pred_lookup_btn, pred_lookup_stop_btn],
803
  cancels=[user_job_lookup, auto_job_lookup],
 
 
 
804
  )
805
 
806
+ result_example.click(
807
  fn=lambda: '80cf2658-7a1c-48d6-8372-61b978177fe6',
808
  outputs=[pred_lookup_id],
809
  show_progress='hidden'
810
  ).success(
811
  fn=query_job_status,
812
  inputs=pred_lookup_id,
813
+ outputs=[pred_lookup_status, tabs, result_state, pred_lookup_btn, pred_lookup_stop_btn],
814
  show_progress='minimal',
815
  cancels=[user_job_lookup, auto_job_lookup],
816
  )
 
821
  result_type = result_info['type']
822
  protein_structure_file = Path(result_info['protein_structure_file'])
823
  if result_type == 'docking':
824
+ result_df = pd.read_csv(result_dir / 'docking_summary.csv', dtype=fn.COL_DTYPE)
825
  result_df['mol'] = result_df['X1'].apply(Chem.MolFromSmiles)
826
  elif result_type == 'linking':
827
+ result_df = pd.read_csv(result_dir / 'linking_summary.csv', dtype=fn.COL_DTYPE)
 
828
  result_df['mol'] = result_df['X1^'].apply(Chem.MolFromSmiles)
829
+ result_df = result_df[~result_df['X1^'].str.contains('.', regex=False)]
830
  else:
831
  raise gr.Error('Invalid result type')
832
 
833
+ result_df = fn.prepare_df_for_table(result_df)
 
 
 
 
 
 
 
 
 
 
 
 
834
 
835
  return {
836
  result_table_orig_df: result_df,
 
918
  outputs=[result_download_file],
919
  )
920
 
921
+ demo.load(None, None, None, js=static.SETUP_JS)
922
 
923
+ demo.share_token = 'genfbdd'
924
+ demo.queue(default_concurrency_limit=None)
925
  demo.launch(
926
  server_name='0.0.0.0',
927
  max_file_size="5mb",
928
  ssr_mode=False,
929
+ show_api=False,
930
+ enable_monitoring=True,
 
931
  )
app/static.py CHANGED
@@ -517,26 +517,41 @@ CREATE_OUTPUT_MOL_VIEW = """
517
  viewer.zoomTo();
518
  viewer.render();
519
 
520
- const container = document.createElement("div");
521
- container.classList.add("gr-btn-grp");
 
 
 
 
522
 
523
  // Molecule Button
524
- const toggleMoleculeButton = document.createElement("button");
525
- toggleMoleculeButton.textContent = "Hide Generated Molecule";
526
- let moleculeIsHidden = false;
527
- toggleMoleculeButton.onclick = function() {
528
- if (viewer.models.length === 2) {
529
- moleculeIsHidden = !moleculeIsHidden;
530
- viewer.addStyle({model: 1}, {stick: {hidden: moleculeIsHidden} });
531
- toggleMoleculeButton.textContent = moleculeIsHidden ? "Show Generated Molecule" : "Hide Generated Molecule";
532
- viewer.render();
533
- }
534
- };
535
- container.appendChild(toggleMoleculeButton);
 
 
 
 
536
 
537
  // Ligand Button
538
- if (viewer.getAtomsFromSel({model: 0, hetflag : true}).length > 0) {
539
- const toggleLigandButton = document.createElement("button");
 
 
 
 
 
 
 
540
  toggleLigandButton.textContent = "Hide Co-Crystallized Ligand";
541
  let ligandIsHidden = false;
542
  toggleLigandButton.onclick = function() {
@@ -546,20 +561,27 @@ CREATE_OUTPUT_MOL_VIEW = """
546
  viewer.render();
547
  };
548
  container.appendChild(toggleLigandButton);
 
 
 
549
  }
550
 
551
  // Protein Button
552
- const toggleProteinButton = document.createElement("button");
553
- toggleProteinButton.textContent = "Hide Target Protein";
554
- let proteinIsHidden = false;
555
- toggleProteinButton.onclick = function() {
556
- proteinIsHidden = !proteinIsHidden;
557
- viewer.addStyle({model: 0, hetflag: false}, {cartoon: {hidden: proteinIsHidden} });
558
- toggleProteinButton.textContent = proteinIsHidden ? "Show Target Protein" : "Hide Target Protein";
559
- viewer.render();
560
- };
561
- container.appendChild(toggleProteinButton);
562
- element.parentElement.appendChild(container);
 
 
 
 
563
  }).fail(function(error) {
564
  console.error("Error loading molecule:", error);
565
  });
 
517
  viewer.zoomTo();
518
  viewer.render();
519
 
520
+ let container = element.parentElement.querySelector(".gr-btn-grp");
521
+ if (!container) {
522
+ container = document.createElement("div");
523
+ container.classList.add("gr-btn-grp");
524
+ element.parentElement.appendChild(container);
525
+ }
526
 
527
  // Molecule Button
528
+ let toggleMoleculeButton = container.querySelector("#toggleMoleculeButton");
529
+ if (!toggleMoleculeButton) {
530
+ toggleMoleculeButton = document.createElement("button");
531
+ toggleMoleculeButton.id = "toggleMoleculeButton"; // Add an ID
532
+ toggleMoleculeButton.textContent = "Hide Generated Molecule";
533
+ let moleculeIsHidden = false;
534
+ toggleMoleculeButton.onclick = function() {
535
+ if (viewer.models.length === 2) {
536
+ moleculeIsHidden = !moleculeIsHidden;
537
+ viewer.addStyle({model: 1}, {stick: {hidden: moleculeIsHidden} });
538
+ toggleMoleculeButton.textContent = moleculeIsHidden ? "Show Generated Molecule" : "Hide Generated Molecule";
539
+ viewer.render();
540
+ }
541
+ };
542
+ container.appendChild(toggleMoleculeButton);
543
+ }
544
 
545
  // Ligand Button
546
+ const ligandButtonId = "toggleLigandButton";
547
+ let toggleLigandButton = container.querySelector("#" + ligandButtonId);
548
+
549
+ // Check for ligands and existing button
550
+ const hasLigands = viewer.getAtomsFromSel({model: 0, hetflag : true}).length > 0;
551
+ if (hasLigands && !toggleLigandButton) {
552
+ // Create button if ligands exist and it doesn't exist yet
553
+ toggleLigandButton = document.createElement("button");
554
+ toggleLigandButton.id = ligandButtonId;
555
  toggleLigandButton.textContent = "Hide Co-Crystallized Ligand";
556
  let ligandIsHidden = false;
557
  toggleLigandButton.onclick = function() {
 
561
  viewer.render();
562
  };
563
  container.appendChild(toggleLigandButton);
564
+ } else if (!hasLigands && toggleLigandButton) {
565
+ // Remove button if no ligands exist and it already exists
566
+ container.removeChild(toggleLigandButton);
567
  }
568
 
569
  // Protein Button
570
+ let toggleProteinButton = container.querySelector("#toggleProteinButton");
571
+ if (!toggleProteinButton) {
572
+ toggleProteinButton = document.createElement("button");
573
+ toggleProteinButton.id = "toggleProteinButton";
574
+ toggleProteinButton.textContent = "Hide Target Protein";
575
+ let proteinIsHidden = false;
576
+ toggleProteinButton.onclick = function() {
577
+ proteinIsHidden = !proteinIsHidden;
578
+ viewer.addStyle({model: 0, hetflag: false}, {cartoon: {hidden: proteinIsHidden} });
579
+ toggleProteinButton.textContent = proteinIsHidden ? "Show Target Protein" : "Hide Target Protein";
580
+ viewer.render();
581
+ };
582
+ container.appendChild(toggleProteinButton);
583
+ }
584
+
585
  }).fail(function(error) {
586
  console.error("Error loading molecule:", error);
587
  });
app/static/medium-zoom.min.js DELETED
@@ -1,2 +0,0 @@
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/static/zooming.min.js DELETED
@@ -1 +0,0 @@
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}()});
 
 
inference.py CHANGED
@@ -467,16 +467,15 @@ def dock_fragments(
467
  log.warning("Failed on", orig_complex_graph["name"], e)
468
  failures += 1
469
 
470
- # Tear down DiffDock models and datasets
471
- model.cpu()
472
  del model
473
  if confidence_model is not None:
474
- confidence_model.cpu()
475
  del confidence_model
476
  del test_dataset
477
  if confidence_test_dataset is not None:
478
  del confidence_test_dataset
479
  del test_loader
 
480
 
481
  docking_df = pd.concat(docking_dfs, ignore_index=True)
482
  # Save intermediate docking results
@@ -691,6 +690,8 @@ def select_fragment_pairs(
691
  ligand_residue=None,
692
  ):
693
  df = df[df['confidence'] > confidence_threshold].copy()
 
 
694
  if 'ligand_mol' not in df.columns:
695
  df['ligand_mol'] = df['ligand_conf_path'].apply(read_molecule)
696
 
@@ -1139,9 +1140,9 @@ def generate_linkers(
1139
  # batch_df['out_path'] = str(out_sdf)
1140
  # linking_dfs.append(batch_df)
1141
 
1142
- # Tear down
1143
- ddpm.cpu()
1144
  del ddpm
 
1145
 
1146
  if linking_dfs:
1147
  linking_summary_df = pd.concat(linking_dfs, ignore_index=True)
 
467
  log.warning("Failed on", orig_complex_graph["name"], e)
468
  failures += 1
469
 
470
+ # Teardown
 
471
  del model
472
  if confidence_model is not None:
 
473
  del confidence_model
474
  del test_dataset
475
  if confidence_test_dataset is not None:
476
  del confidence_test_dataset
477
  del test_loader
478
+ torch.cuda.empty_cache()
479
 
480
  docking_df = pd.concat(docking_dfs, ignore_index=True)
481
  # Save intermediate docking results
 
690
  ligand_residue=None,
691
  ):
692
  df = df[df['confidence'] > confidence_threshold].copy()
693
+ if 'ligand_conf_path' in df.columns:
694
+ df['ligand_conf_path'] = df['ligand_conf_path'].apply(Path)
695
  if 'ligand_mol' not in df.columns:
696
  df['ligand_mol'] = df['ligand_conf_path'].apply(read_molecule)
697
 
 
1140
  # batch_df['out_path'] = str(out_sdf)
1141
  # linking_dfs.append(batch_df)
1142
 
1143
+ # Teardown
 
1144
  del ddpm
1145
+ torch.cuda.empty_cache()
1146
 
1147
  if linking_dfs:
1148
  linking_summary_df = pd.concat(linking_dfs, ignore_index=True)
utils/inference_utils.py CHANGED
@@ -62,7 +62,17 @@ def compute_esm_embeddings(model, alphabet, labels, sequences):
62
  repr_layers = [33]
63
  truncation_seq_length = 1022
64
 
65
- dataset = FastaBatchedDataset(labels, sequences)
 
 
 
 
 
 
 
 
 
 
66
  batches = dataset.get_batch_indices(toks_per_batch, extra_toks_per_seq=1)
67
  data_loader = torch.utils.data.DataLoader(
68
  dataset, collate_fn=alphabet.get_batch_converter(truncation_seq_length), batch_sampler=batches
 
62
  repr_layers = [33]
63
  truncation_seq_length = 1022
64
 
65
+ # Deduplicate by label to avoid redundant ESM computation
66
+ unique_labels = set()
67
+ unique_sequences = []
68
+ for label, sequence in zip(labels, sequences):
69
+ if label not in unique_labels:
70
+ unique_labels.add(label)
71
+ unique_sequences.append(sequence)
72
+ unique_labels = list(unique_labels)
73
+
74
+ # Create the dataset with unique labels and sequences
75
+ dataset = FastaBatchedDataset(unique_labels, unique_sequences)
76
  batches = dataset.get_batch_indices(toks_per_batch, extra_toks_per_seq=1)
77
  data_loader = torch.utils.data.DataLoader(
78
  dataset, collate_fn=alphabet.get_batch_converter(truncation_seq_length), batch_sampler=batches