Skip to content

Commit 35df132

Browse files
committed
Added 6-view pdf of PyMOL protein structure
1 parent 64a54dc commit 35df132

File tree

7 files changed

+165
-9
lines changed

7 files changed

+165
-9
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ csubst/*-darwin.so
66
csubst/*.c
77
csubst/.DS_Store
88
.DS_Store
9+
.vscode/

csubst/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = '1.4.9'
1+
__version__ = '1.4.10'

csubst/csubst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ def command_site(args):
3434
main_site(g)
3535
print('csubst site: Time elapsed: {:,} sec'.format(int(time.time() - start)))
3636
print('csubst site end:', datetime.datetime.now(datetime.timezone.utc), flush=True)
37+
if (g['pdb'] is not None):
38+
# This should be executed at the very end, otherwise CSUBST's main process is killed.
39+
from parser_pymol import quit_pymol
40+
quit_pymol()
3741

3842
def command_analyze(args):
3943
print('csubst analyze start:', datetime.datetime.now(datetime.timezone.utc), flush=True)

csubst/main_site.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -694,6 +694,8 @@ def main_site(g):
694694
df = parser_pymol.add_pdb_residue_numbering(df=df)
695695
g['session_file_path'] = g['pdb_outfile_base']+'.pymol.pse'
696696
parser_pymol.write_pymol_session(df=df, g=g)
697+
parser_pymol.save_six_views()
698+
parser_pymol.save_6view_pdf(pdf_filename=os.path.join(g['site_outdir'], f'csubst_site.{id_base}.pymol.pdf'))
697699
plot_barchart(df, g)
698700
plot_state(N_tensor, S_tensor, g['branch_ids'], g)
699701
if g['pdb'] is None:
@@ -707,6 +709,4 @@ def main_site(g):
707709
print('')
708710
tmp_files = [f for f in os.listdir() if f.startswith('tmp.csubst.')]
709711
_ = [os.remove(ts) for ts in tmp_files]
710-
if (g['pdb'] is not None):
711-
# This should be executed at the very end, otherwise csubst's main process is killed.
712-
parser_pymol.quit_pymol()
712+
return None

csubst/parser_biodb.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@ def get_top_hit_ids(my_hits):
1717
top_hit_ids = []
1818
for i in range(len(my_hits.descriptions)):
1919
top_hit_title = my_hits.descriptions[i].title
20-
top_hit_id = re.findall('\|.*\|', top_hit_title)[0]
21-
top_hit_id = re.sub('\|', '', top_hit_id)
22-
top_hit_id = re.sub('\..*', '', top_hit_id)
20+
top_hit_id = re.findall(r'\|.*\|', top_hit_title)[0]
21+
top_hit_id = re.sub(r'\|', '', top_hit_id)
22+
top_hit_id = re.sub(r'\..*', '', top_hit_id)
2323
top_hit_ids.append(top_hit_id)
2424
return top_hit_ids
2525

csubst/parser_misc.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,8 @@ def read_input(g):
4444
return g
4545
if (g['omegaC_method']!='submodel'):
4646
return g
47-
base_model = re.sub('\+G.*', '', g['substitution_model'])
48-
base_model = re.sub('\+R.*', '', base_model)
47+
base_model = re.sub(r'\+G.*', '', g['substitution_model'])
48+
base_model = re.sub(r'\+R.*', '', base_model)
4949
txt = 'Instantaneous substitution rate matrix will be generated using the base model: {}'
5050
print(txt.format(base_model))
5151
txt = 'Transition matrix will be generated using the model in the ancestral state reconstruction: {}'

csubst/parser_pymol.py

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import numpy
2+
import matplotlib.pyplot
23
import pandas
34
import pymol
45

@@ -319,3 +320,153 @@ def write_pymol_session(df, g):
319320

320321
def quit_pymol():
321322
pymol.cmd.quit(code=0)
323+
324+
def save_six_views(selection='all',
325+
prefix='tmp.csubst.pymol',
326+
width=900,
327+
height=900,
328+
dpi=300,
329+
ray=True):
330+
"""
331+
Saves six images of the selected object from +X, -X, +Y, -Y, +Z, -Z directions.
332+
"""
333+
334+
# Each direction is 18 floats:
335+
# 1) 3x3 rotation matrix = 9 floats
336+
# 2) camera position (3 floats)
337+
# 3) front, back (2 floats)
338+
# 4) perspective (1 float)
339+
# 5) origin shift (3 floats)
340+
directions = {
341+
'pos_x': [
342+
# Rotation: point the camera along +X => +X points "out of screen"
343+
0.0, 0.0, -1.0, # row1
344+
0.0, 1.0, 0.0, # row2
345+
1.0, 0.0, 0.0, # row3
346+
347+
# Camera position (move camera +X):
348+
100.0, 0.0, 0.0,
349+
350+
# front plane, back plane:
351+
0.0, 200.0,
352+
353+
# perspective (1.0 => perspective on, -1 or 0 => orthoscopic):
354+
1.0,
355+
356+
# origin translation:
357+
0.0, 0.0, 0.0
358+
],
359+
'neg_x': [
360+
0.0, 0.0, 1.0,
361+
0.0, 1.0, 0.0,
362+
-1.0, 0.0, 0.0,
363+
364+
-100.0, 0.0, 0.0,
365+
0.0, 200.0,
366+
1.0,
367+
0.0, 0.0, 0.0
368+
],
369+
'pos_y': [
370+
# Camera along +Y => +Y out of screen
371+
1.0, 0.0, 0.0,
372+
0.0, 0.0, -1.0,
373+
0.0, 1.0, 0.0,
374+
375+
0.0, 100.0, 0.0,
376+
0.0, 200.0,
377+
1.0,
378+
0.0, 0.0, 0.0
379+
],
380+
'neg_y': [
381+
1.0, 0.0, 0.0,
382+
0.0, 0.0, 1.0,
383+
0.0, -1.0, 0.0,
384+
385+
0.0, -100.0, 0.0,
386+
0.0, 200.0,
387+
1.0,
388+
0.0, 0.0, 0.0
389+
],
390+
'pos_z': [
391+
# Camera along +Z => +Z out of screen
392+
1.0, 0.0, 0.0,
393+
0.0, 1.0, 0.0,
394+
0.0, 0.0, 1.0,
395+
396+
0.0, 0.0, 100.0,
397+
0.0, 200.0,
398+
1.0,
399+
0.0, 0.0, 0.0
400+
],
401+
'neg_z': [
402+
1.0, 0.0, 0.0,
403+
0.0, 1.0, 0.0,
404+
0.0, 0.0, -1.0,
405+
406+
0.0, 0.0, -100.0,
407+
0.0, 200.0,
408+
1.0,
409+
0.0, 0.0, 0.0
410+
],
411+
}
412+
413+
for direction, view in directions.items():
414+
pymol.cmd.set_view(view)
415+
pymol.cmd.zoom(selection, buffer=0.5)
416+
417+
filename = f"{prefix}_{direction}.png"
418+
pymol.cmd.png(filename, width=width, height=height, dpi=dpi, ray=ray)
419+
print(f"Saved {filename}")
420+
return None
421+
422+
def save_6view_pdf(image_prefix='tmp.csubst.pymol',
423+
directions=None,
424+
pdf_filename='6view.pdf'):
425+
"""
426+
Combines the 6 saved view images into a single PDF with 2 columns and 3 rows.
427+
428+
Parameters
429+
----------
430+
image_prefix : str
431+
Common prefix used when saving the 6 PNG images with save_six_views.
432+
directions : list of str
433+
List of the view directions in the order you want them arranged.
434+
Default is ['pos_x','neg_x','pos_y','neg_y','pos_z','neg_z'].
435+
pdf_filename : str
436+
Name of the output PDF file.
437+
"""
438+
if directions is None:
439+
directions = ['pos_x','neg_x','pos_y','neg_y','pos_z','neg_z']
440+
441+
# Create a figure with 3 rows & 2 columns
442+
fig, axes = matplotlib.pyplot.subplots(nrows=3, ncols=2, figsize=(8.3, 11.7)) # A4 paper size
443+
444+
for idx, direction in enumerate(directions):
445+
row = idx // 2
446+
col = idx % 2
447+
ax = axes[row, col]
448+
449+
# Construct filename for each view image
450+
img_file = f"{image_prefix}_{direction}.png"
451+
452+
if not os.path.isfile(img_file):
453+
print(f"Warning: {img_file} not found. Skipping.")
454+
ax.axis('off')
455+
ax.set_title(f"{direction} (missing)")
456+
continue
457+
458+
# Read and show image
459+
img = matplotlib.image.imread(img_file)
460+
ax.imshow(img)
461+
462+
# Remove axes ticks
463+
ax.axis('off')
464+
465+
# Optionally label each subplot
466+
ax.set_title(direction)
467+
468+
matplotlib.pyplot.tight_layout()
469+
matplotlib.pyplot.savefig(pdf_filename)
470+
matplotlib.pyplot.close(fig)
471+
print(f"Saved 6-view PDF as {pdf_filename}")
472+
return None

0 commit comments

Comments
 (0)