import io
import os
import sys
from datetime import datetime
from past.utils import old_div
import numpy
import reportlab
import reportlab.lib.colors as rlcolors
import reportlab.pdfgen.canvas as rlcanvas
import reportlab.platypus as platypus
import PIL
from reportlab.lib.units import inch
import schrodinger.ui.qt.structure2d as structure2d
from schrodinger.Qt import QtCore
from schrodinger.Qt import QtGui
from schrodinger.Qt import QtWidgets
from schrodinger.Qt.QtCore import QBuffer
from schrodinger.Qt.QtCore import QIODevice
from schrodinger.Qt.QtCore import QRect
from schrodinger.Qt.QtCore import QSize
from schrodinger.Qt.QtCore import Qt
from schrodinger.Qt.QtGui import QColor
from schrodinger.Qt.QtGui import QImage
from schrodinger.Qt.QtGui import QPainter
from schrodinger.Qt.QtGui import QPicture
styles = reportlab.lib.styles.getSampleStyleSheet()
HeaderStyle = styles["Heading1"]
ParaStyle = styles["Normal"]
gray = rlcolors.Color(
old_div(136., 256), old_div(136., 256), old_div(136., 256))
colors = [
"#75D44A", "#BF58CB", "#D44B33", "#88C7D4", "#3B3628", "#CE497C", "#72D49B",
"#59345F", "#5C7B37", "#707AC9", "#C78A3B", "#C7BD94", "#CE98B5", "#CECF4F",
"#874236", "#597576"
]
blackColorMap = {
5: 0xFF000000,
7: 0xFF000000,
8: 0xFF000000,
9: 0xFF000000,
13: 0xFF000000,
14: 0xFF000000,
15: 0xFF000000,
16: 0xFF000000,
17: 0xFF000000,
35: 0xFF000000,
53: 0xFF000000
}
[docs]def replica_name(n):
if n < 26:
return chr(ord('A') + n)
else:
return replica_name(old_div(n, 26) - 1) + replica_name(n % 26)
[docs]def change_plot_colors(axis,
spines=True,
ticks=True,
labels=True,
polar=False,
label_size=8):
gr = '#888888'
if ticks:
axis.tick_params(axis='x', colors=gr, labelsize=label_size)
axis.tick_params(axis='y', colors=gr, labelsize=label_size)
if spines:
types = ['top', 'bottom', 'right', 'left']
if polar:
types = ['polar']
for s in types:
axis.spines[s].set_color(gr)
if labels:
axis.yaxis.label.set_color(gr)
axis.xaxis.label.set_color(gr)
[docs]def load_gui():
app = QtWidgets.QApplication.instance()
if not app:
app = QtWidgets.QApplication(sys.argv)
return app
[docs]def new_page(Elements):
'''
insert page break
'''
Elements.append(platypus.PageBreak())
[docs]def add_vtable(Elements, table, style, width_list):
'''
add table, where header is in the first column
'''
width = [w * inch for w in width_list]
Elements.append(platypus.Table(table, width, style=style))
[docs]def add_table(Elements, table, style, col_width):
'''
horizontal table, where the header is on the first row
'''
nfields = len(table[0])
if isinstance(col_width, int) or isinstance(col_width, float):
width = nfields * [col_width * inch]
else:
width = [w * inch for w in col_width]
Elements.append(platypus.Table(table, width, style=style))
[docs]def add_and_parse_SMILES(smiles_str):
""" parse SMILES string such that it fits on the page """
smiles_style = ParaStyle
smiles_style.fontSize = 10
l = len(smiles_str)
new_str = ''
prev_idx = 0
max_length = 66
if l <= max_length:
return smiles_str
# output smiles only upto 3 lines; otherwise it gets messy.
elif l > max_length * 3:
return smiles_str[:max_length] + '...'
for i in list(range(0, l, max_length))[1:]:
new_str += smiles_str[prev_idx:i + 1] + ' '
prev_idx = i + 1
if prev_idx < l:
new_str += smiles_str[prev_idx:l]
return platypus.Paragraph(new_str, smiles_style)
[docs]def add_spacer(Elements):
Elements.append(platypus.Spacer(1, 11))
[docs]def get_image(fn, height, width):
with PIL.Image.open(fn) as img_temp:
size = img_temp.size
(sx, sy) = aspectScale(size[0], size[1], height, width)
img = platypus.Image(fn, sx * inch, sy * inch)
return img
[docs]def report_add_ms_logo(Elements):
logo_path = os.path.join(
os.environ.get("MMSHARE_EXEC"), "..", "..", "python", "scripts",
"event_analysis_dir", "schrodinger_ms_logo.png")
logo_img = platypus.Image(logo_path, 1.50 * inch, 0.43 * inch)
logo_img.hAlign = 'RIGHT'
Elements.append(logo_img)
[docs]def report_add_logo(Elements):
logo_path = os.path.join(
os.environ.get("MMSHARE_EXEC"), "..", "..", "python", "scripts",
"event_analysis_dir", "schrodinger_logo.png")
logo_img = platypus.Image(logo_path, 1.605 * inch, 0.225 * inch)
logo_img.hAlign = 'RIGHT'
Elements.append(logo_img)
[docs]def get_pargph(txt, fixed=False, fontsize=11, color='black', hAlign='left'):
style = ParaStyle
style.fontName = 'Courier' if fixed else 'Helvetica'
style.fontSize = fontsize
style.textColor = color
style.hAlign = hAlign
return platypus.Paragraph(txt, style)
[docs]def pargph(Elements, txt, fixed=False, fontsize=11, color='black',
leading=None):
"""
Add a paragraph to the report
:param list Elements: Report elements to add the paragraph to
:param str txt: The paragraph text
:param int fontsize: The size of the paragraph font
:param str color: Font color
:param leading: Spacing between paragraph lines, the default will be used
if nothing is passed
:type leading: int or None
"""
style = ParaStyle
style.fontName = 'Courier' if fixed else 'Helvetica'
style.fontSize = fontsize
style.textColor = color
if leading: # Set leading if a custom one has been provided
style.leading = leading
para = platypus.Paragraph(txt, style)
Elements.append(para)
[docs]def generate_ligand_2d_placeholder(filename, natoms):
qpic = QPicture()
painter = QPainter(qpic)
txt = "Molecule too big\n to render in 2D."
brect = painter.drawText(0, 0, 200, 200, QtCore.Qt.AlignCenter, txt)
painter.end()
qpic.setBoundingRect(brect)
# Convert the QPicture into QImage:
img = QImage(QtCore.QSize(200, 200), QImage.Format_ARGB32)
img.fill(0) # Initialize with zeros to overwrite garbage values
painter2 = QtGui.QPainter()
painter2.begin(img)
painter2.drawPicture(0, 0, qpic)
painter2.end()
img.save(filename)
[docs]def generate_aligned_2d_ligand_pair(fn_list, ct1, ct2, aligned_core):
"""
Given two ligands, try to generate a 2d-plot where they're aligned.
"""
from .depictor import Depictor, get_aligned_chmmol_from_ct
chmmol0, chmmol1 = get_aligned_chmmol_from_ct(ct1, ct2, aligned_core[0],
aligned_core[1], [], [])
chmmol_list = [chmmol0, chmmol1]
depictor = Depictor()
depictor.setCoorGenMode(False)
for fn, lig in zip(fn_list, chmmol_list):
depictor.mol2image(lig, fn)
[docs]def generate_ligand_2d_image(filename,
ligand_st=None,
scene=None,
crop=True,
ret_size=False):
''' given a scene, or a ligand st, output the 2d image'''
# if ligand is greater than 100 heavy atoms just return a
# placeholder
if ligand_st:
atom_heavy = len(
[atom for atom in ligand_st.atom if atom.atomic_number != 1])
else:
return
if atom_heavy > 100:
generate_ligand_2d_placeholder(filename, ligand_st.atom_total)
if ret_size:
return (200, 200)
return
if scene is None:
ligand_pic = structure2d.structure_item()
ligand_pic.set_structure(ligand_st)
ligand_pic.generate_picture()
scene = structure2d.structure_scene()
scene.addItem(ligand_pic)
scene.setBackgroundBrush(Qt.white)
img1 = QImage(1600, 1600, QImage.Format_ARGB32_Premultiplied)
p = QPainter(img1)
scene.render(p)
p.end()
if crop:
img1 = crop_image(img1)
img1.save(filename)
if ret_size:
return img1.size
[docs]def crop_image(image_in):
''' get rid of the white background rgb=[255,255,255]'''
#convert image to an NP array
if isinstance(image_in, QImage):
image_in = convertQimageToImage(image_in)
image_data = numpy.asarray(image_in)
# get the smallest value from RGB
image_data_bw = image_data.min(axis=2)
# get non-white rows and columns
non_empty_columns = numpy.where(image_data_bw.min(axis=0) != 255)[0]
# see if clear backckground works better
non_clear_columns = numpy.where(image_data_bw.min(axis=0) != 0)[0]
if len(non_clear_columns) < len(non_empty_columns):
non_empty_columns = non_clear_columns
non_empty_rows = numpy.where(image_data_bw.min(axis=1) != 255)[0]
if len(non_empty_columns) == 0 and len(non_empty_rows):
return image_in
cropBox = (min(non_empty_rows), max(non_empty_rows), min(non_empty_columns),
max(non_empty_columns))
image_data_new = image_data[cropBox[0]:cropBox[1] + 1, cropBox[2]:
cropBox[3] + 1, :]
image_out = PIL.Image.fromarray(image_data_new)
return image_out
[docs]def convertQimageToImage(qimg):
''' converts QImage object to PIL Image object, in order to crop'''
buffer = QBuffer()
buffer.open(QIODevice.ReadWrite)
qimg.save(buffer, "PNG")
strio = io.BytesIO()
strio.write(buffer.data())
buffer.close()
strio.seek(0)
img = PIL.Image.open(strio)
return img
[docs]def aspectScale(ix, iy, bx, by):
'''Scale image to fit into box (bx,by) keeping aspect ratio'''
if ix > iy:
# fit to width
scale_factor = old_div(bx, float(ix))
sy = scale_factor * iy
if sy > by:
scale_factor = old_div(by, float(iy))
sx = scale_factor * ix
sy = by
else:
sx = bx
else:
# fit to height
scale_factor = old_div(by, float(iy))
sx = scale_factor * ix
if sx > bx:
scale_factor = old_div(bx, float(ix))
sx = bx
sy = scale_factor * iy
else:
sy = by
return (sx, sy)
[docs]def get_qcolor(hex_color):
qc = QColor()
qc.setNamedColor(hex_color)
return qc
[docs]def save_2d_annotated_img(structure_item, filename, crop=True, ret_size=False):
#img1 = QtGui.QImage(1600, 1600, QtGui.QImage.Format_ARGB32_Premultiplied)
img1 = QtGui.QImage(1200, 1200, QtGui.QImage.Format_ARGB32)
img1.fill(QtCore.Qt.white)
scene = structure2d.structure_scene()
scene.addItem(structure_item)
p = QtGui.QPainter(img1)
scene.render(p)
p.end()
if crop:
img1 = crop_image(img1)
img1.save(filename)
if ret_size:
return convertQimageToImage(img1).size
[docs]class NumberedCanvas(rlcanvas.Canvas):
[docs] def __init__(self, *args, **kwargs):
rlcanvas.Canvas.__init__(self, *args, **kwargs)
self._saved_page_states = []
[docs] def showPage(self):
self._saved_page_states.append(dict(self.__dict__))
self._startPage()
[docs] def save(self):
"""add page info to each page (page x of y)"""
num_pages = len(self._saved_page_states)
for state in self._saved_page_states:
self.__dict__.update(state)
self.drawFixedContents(num_pages)
rlcanvas.Canvas.showPage(self)
rlcanvas.Canvas.save(self)
[docs] def drawFixedContents(self, page_count):
"""
Draw nonflowables such as footers, headers and fixed graphics
:param int page_count: Total number of pages
"""
self.setFont("Helvetica", 8)
self.drawRightString(8 * inch, 0.3 * inch,
"Page %d of %d" % (self._pageNumber, page_count))
s = "Schrödinger Inc. Report generated " + datetime.now().strftime(
"%m-%d-%Y %H:%M")
self.drawString(0.8 * inch, 0.3 * inch, s)