import math
from past.utils import old_div
import schrodinger
import schrodinger.protein._reliability as structure_reliability
from schrodinger.Qt import QtCore
from schrodinger.Qt import QtGui
from schrodinger.Qt import QtWidgets
from schrodinger.Qt.QtCore import Qt
from . import protein_health_viewer_ui
maestro = schrodinger.get_maestro()
SOURCE_INDEX_ROLE = Qt.UserRole + 1
COLORS = {
'g': QtGui.QColor(51, 255, 51),
'r': QtGui.QColor("red"),
'w': QtGui.QColor("white"),
'y': QtGui.QColor('yellow')
}
[docs]class BubbleTextItem(QtWidgets.QGraphicsTextItem):
"""
single-click reloads description and the table
"""
[docs] def __init__(self, master, index, text):
self.master = master
self.index = index
if text is None:
text = ""
super(BubbleTextItem, self).__init__(text)
[docs] def mousePressEvent(self, event):
"""
Called when the user clicks in the graphics area on the left side
of the panel - selects the set clicked by the user.
"""
self.master.selectSet(self.index)
[docs]class ProteinHealthViewer(QtWidgets.QWidget):
"""
Class representing the Protein Reliability Report, with the sets graphics
view on the left, and set points table on the right.
"""
[docs] def __init__(self, parent=None, flags=0):
super(ProteinHealthViewer, self).__init__(parent)
self.ui = protein_health_viewer_ui.Ui_Form()
self.ui.setupUi(self)
self.setWindowTitle('Protein Reliability Report')
# Index of the currently selected set:
self.clickedIndex = 0
self.textFont = QtGui.QFont()
self.textFont.setPointSize(10)
self.textFont.setUnderline(True)
# Will be shown only when "Steric Clashes" set is selected:
self.ui.show_crystal_mate_clashes_cb.hide()
self.ui.show_crystal_mate_clashes_cb.toggled.connect(
self.showCrystalMateClashesToggled)
self.ui.table.itemSelectionChanged.connect(self.tableSelected)
[docs] def showCrystalMateClashesToggled(self):
"""
Called when the "Show crystal mate clashes" checkbox is toggled.
Re-populates the set points table (right side of the panel).
"""
self.populateTableAndSummary()
[docs] def selectSet(self, set_index):
"""
Select the set with the given index.
"""
font = self.font()
font.setBold(True)
self.setFont(font)
if self.clickedIndex != set_index:
# Make the current set label bold, and remove bold from previous
# selection:
font2 = self.textItems[self.clickedIndex].font()
font2.setBold(False)
self.textItems[self.clickedIndex].setFont(font2)
self.clickedIndex = set_index
self.populateTableAndSummary()
self.refit()
[docs] def getCurrentSet(self):
"""
Return the currently selected set object.
"""
return self.ordered_sets[self.clickedIndex]
[docs] def loadResults(self, struct, firstTime, chain='All'):
# Reloads the model every time chain ID is changed, otherwise
# filter_data_by_chain changes the underlying data
model = structure_reliability.ModelCheck(struct, do_calc=False)
if chain != "All":
model.filter_data_by_chain(chain)
self.loadModel(model)
# PANEL-10965 - If summary table results are being shown for some set,
# update the table to show only the results for the current chain(s).
if self.ui.table.isVisible():
self.selectSet(self.clickedIndex)
[docs] def loadModel(self, model):
self.model = model
self.textItems = []
self.bubbleItems = []
dist = 20 # text to center of each bubble
textWidth = 140
textOption = QtGui.QTextOption()
textOption.setAlignment(Qt.AlignCenter)
textOption.setWrapMode(QtGui.QTextOption.WordWrap)
# Set up the bubbles and texts based on title order
bubbleHoriDist = 160
nrow = math.ceil(old_div(len(self.model.calc_sets), 4))
bubbleVerDist = int(old_div(550, nrow))
scene = QtWidgets.QGraphicsScene()
# Order the sets in an order in which we want them to be displayed.
# For now, it's the same order as the one they are created in - to
# match other reports. See PANEL-9419.
self.ordered_sets = self.model.calc_sets
z_orders = self._bubble_z_orders()
for b, s in enumerate(self.ordered_sets):
title = s.title
color = COLORS.get(s.color)
if not color:
raise ValueError('Invalid color: "%s"' % s.color)
xpos = (b % 4) * bubbleHoriDist
ypos = (old_div(b, 4)) * bubbleVerDist
dia = math.sqrt(s.area)
bubble = QtWidgets.QGraphicsEllipseItem(xpos - old_div(dia, 2),
ypos - old_div(dia, 2), dia,
dia)
bubble.setBrush(color)
bubble.setZValue(z_orders[s])
if color.red() == 255 and color.green() == 255:
bubble.setToolTip("No data")
text = BubbleTextItem(self, b, title)
text.setFont(self.textFont)
text.setTextWidth(textWidth)
text.setPos(
QtCore.QPointF(xpos - old_div(textWidth, 2), ypos + dist))
text.document().setDefaultTextOption(textOption)
scene.addItem(bubble)
scene.addItem(text)
self.bubbleItems.append(bubble)
self.textItems.append(text)
self.ui.view.setScene(scene)
scene.setSceneRect(
QtCore.QRectF(old_div(-bubbleHoriDist, 2),
old_div(-bubbleVerDist, 2), 670, 670))
def _bubble_z_orders(self):
"""
:return: a dictionary mapping the items in `ordered_sets` to a z-order
so that the largest bubble has the lowest z-value and all being < -9
:rtype: Dict[schrodinger.protein.analysis.Report.data_set, z-order]
"""
ret = dict()
sorted_data_sets = sorted(self.ordered_sets, key=lambda x: x.area)
for idx, s in enumerate(sorted_data_sets, start=10):
ret[s] = -idx
return ret
[docs] def populateTableAndSummary(self):
"""
Populate the set table (on the right side of the panel) with the points
in that set.
"""
a_set = self.getCurrentSet()
# Show/hide the set-specific widgets:
SC_set = (a_set.label == structure_reliability.STERIC_CLASHES)
self.ui.show_crystal_mate_clashes_cb.setVisible(SC_set)
# Make a list of point rows that should be shown in the table
if SC_set and not self.ui.show_crystal_mate_clashes_cb.isChecked():
show_points = []
for point in a_set.bad_points:
if '*' in point.descriptor: # skip clashes with crystal mates
continue
show_points.append(point)
else:
# In all other cases, show all points in the set:
show_points = a_set.bad_points
font = QtGui.QFont()
font.setPointSize(10)
self.ui.summary_label.setFont(font)
self.ui.summary_label.setText(a_set.summary)
tableWidget = self.ui.table # Readability
tableWidget.clear()
tableWidget.setColumnCount(0)
tableWidget.setRowCount(0)
tableWidget.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
tableWidget.setSelectionMode(
QtWidgets.QAbstractItemView.SingleSelection)
if a_set.bad_points:
# Show column headers only if the set contains data (even if all
# of it is hidden when "Show" checkbox is not checked)
tableWidget.setColumnCount(len(a_set.fields))
tableWidget.setHorizontalHeaderLabels(a_set.fields)
tableWidget.setRowCount(len(show_points))
for row, point in enumerate(show_points):
# Add the descriptor column:
item = QtWidgets.QTableWidgetItem(point.descriptor)
item.setFont(font)
item.setTextAlignment(Qt.AlignCenter)
item.setData(SOURCE_INDEX_ROLE, row)
tableWidget.setItem(row, 0, item)
# Add other columns, based on the values in the set:
col = 1
for value in point.values:
valueStr = self._valToString(value)
item = QtWidgets.QTableWidgetItem(valueStr)
item.setFont(font)
item.setTextAlignment(Qt.AlignCenter)
tableWidget.setItem(row, col, item)
col = col + 1
tableWidget.resizeColumnsToContents()
tableWidget.setSortingEnabled(True)
[docs] def tableSelected(self):
if not maestro:
return
selectedSet = self.getCurrentSet()
for item in self.ui.table.selectedItems():
if item.column() == 0:
row = item.data(SOURCE_INDEX_ROLE)
point = selectedSet.bad_points[row]
atom_list = ",".join([str(iatom) for iatom in point.atoms])
ws_st = maestro.workspace_get(copy=False)
# PANEL-10245 - fit won't zoom to invisible atoms so we make
# all fit atoms temporarily visible for the Maestro command.
fit_atoms = [ws_st.atom[anum] for anum in point.atoms]
inviz_atoms = [atom for atom in fit_atoms if not atom.visible]
for atom in inviz_atoms:
atom.visible = True
maestro.command("fit atom.num %s" % atom_list)
maestro.command("workspaceselectionreplace atom.num %s" %
atom_list)
for atom in inviz_atoms:
atom.visible = False
return
def _valToString(self, value):
valueStr = str(value)
try:
fval = float(value)
except:
pass
else:
if fval != 0 and abs(math.log10(abs(fval))) > 4:
valueStr = "%.3e" % fval
else:
valueStr = "%.3f" % fval
return valueStr
[docs] def refit(self):
if maestro:
maestro.command("fit all")
maestro.command("workspaceselectionclear")
[docs] def saveImage(self, filename):
format = QtGui.QImage.Format_ARGB32_Premultiplied
scene = self.ui.view.scene()
width = scene.width()
height = scene.height()
painter = QtGui.QPainter()
img = QtGui.QImage(QtCore.QSize(width, height), format)
painter.begin(img)
painter.fillRect(0, 0, width, height, QtCore.Qt.white)
scene.render(painter)
painter.end()
img.save(filename)
scene = self.ui.view.scene()
[docs] def reset(self):
scene = QtWidgets.QGraphicsScene()
self.ui.view.setScene(scene)
self.ui.summary_label.setText("Click on a text field to view details.")
self.ui.table.clear()
self.ui.table.setRowCount(0)
self.ui.table.setColumnCount(0)
self.ui.show_crystal_mate_clashes_cb.hide()