Source code for schrodinger.ui.qt.rdmol_views

from typing import Optional
from typing import Tuple

from rdkit.Chem.rdchem import Mol

from schrodinger.Qt.QtCore import QRectF
from schrodinger.Qt.QtCore import Qt
from schrodinger.Qt.QtGui import QPainter
from schrodinger.Qt.QtGui import QPaintEvent
from schrodinger.Qt.QtGui import QResizeEvent
from schrodinger.Qt.QtWidgets import QFrame
from schrodinger.Qt.QtWidgets import QGraphicsItem
from schrodinger.Qt.QtWidgets import QLabel
from schrodinger.Qt.QtWidgets import QSizePolicy
from schrodinger.Qt.QtWidgets import QStyleOptionGraphicsItem
from schrodinger.Qt.QtWidgets import QVBoxLayout
from schrodinger.Qt.QtWidgets import QWidget
from schrodinger.ui.qt.delegates import RdMolDelegate
from schrodinger.ui.qt.tooltips import TooltipMixin


[docs]class RDMolStructureItem(QGraphicsItem): """ Graphics item that displays a 2D structure image using a `RdMolDelegate`. """
[docs] def __init__(self, parent: Optional[QGraphicsItem] = None): super().__init__(parent=parent) self._rect = QRectF(0, 0, 500, 500) self._rdmol = None self._color = None self._atom_idcs = None self._image_delegate = None self.setImageDelegate(RdMolDelegate())
[docs] def getRDMol(self) -> Optional[Mol]: """ :return: the ligand structure currently stored on this object """ return self._rdmol
[docs] def setRDMol(self, rdmol: Optional[Mol]): """ Assign a new ligand structure. :param rdmol: optionally, a `Mol` instance to render into a 2D image; if `None`, this widget will not render an image :param update: whether to update the displayed image """ self._rdmol = rdmol self.update(self._rect)
[docs] def getAtomHighlights( self) -> Tuple[Optional[Tuple[int]], Optional[Tuple[int]]]: """ :return: a tuple containing the highlighted atom indices and highlight color (in RGB form) currently stored on this object """ return self._atom_idcs, self._color
[docs] def setAtomHighlights(self, atom_idcs: Optional[Tuple[int]], color: Optional[Tuple[int]]): """ Assign the highlighted atoms and highlighting color for the image. :param atom_idcs: indices of the atoms to be highlighted :param color: color (in RGB form) to use for drawing highlighted atoms :param update: whether to update the displayed image """ self._atom_idcs = atom_idcs self._color = color self.update(self._rect)
[docs] def setImageDelegate(self, delegate: RdMolDelegate): """ Assign a delegate that can be used to provide 2D structure images. :param delegate: optionally, a delegate instance. If `None`, this widget will not render an image :param update: whether to update the displayed image """ self._image_delegate = delegate
[docs] def boundingRect(self) -> QRectF: """ :return: the area into which the structure image is rendered """ return self._rect
[docs] def setBoundingRect(self, rect: QRectF): """ :param rect: set the area into which the structure image is rendered """ self._rect = rect
[docs] def paint(self, painter: QPainter, option: QStyleOptionGraphicsItem, widget: Optional[QWidget]): # Override this method to properly draw the structure image if not self._image_delegate or not self._rdmol: return pic = self._image_delegate.getPicture(self._rdmol, atom_idcs=self._atom_idcs, color=self._color) rect = self.boundingRect() pic_width = max(pic.boundingRect().width(), 1) pic_height = max(pic.boundingRect().height(), 1) scale = min(rect.size().width() / pic_width, rect.size().height() / pic_height) pic_center = pic.boundingRect().center() * scale painter.translate(rect.center() - pic_center) painter.scale(scale, scale) painter.drawPicture(0, 0, pic)
[docs]class AbstractRDMolImageLabel(QLabel): """ Abstract widget that draws 2D molecule images using `RdMolDelegate`. In order to show any image, an instance must be assigned a `RdMolDelegate` instance (using `setImageDelegate()`) and a `Mol` instance (using `setRDMol()`). Modifications such as parameters for atom coloring and labeling are supported but not required. """
[docs] def __init__(self, parent: Optional[QWidget] = None): super().__init__(parent=parent) self.setFrameShape(QFrame.NoFrame) self.setAlignment(Qt.AlignCenter) # Put a box around this label self.setLineWidth(1) size_policy = self._getSizePolicy() self.setSizePolicy(size_policy) self._rdmol = None self._image_delegate = None self._image_width = None self._image_height = None self._atom_idcs = None self._highlight_color = None self._atom_labels = None size = 200 self.setImageSize(width=size, height=size)
def _getSizePolicy(self) -> QSizePolicy: """ Return a size policy for this widget. This method is only expected to be called during initialization. Can be overridden to modify the applied size policy. :return: a size policy to use for this widget """ size_policy = QSizePolicy() size_policy.setHorizontalStretch(0) size_policy.setVerticalStretch(0) return size_policy
[docs] def paintEvent(self, event: QPaintEvent): # Override `paintEvent()` to control `QPicture` size. br = self.picture().boundingRect() cr = self.contentsRect() img_w, img_h = br.width(), br.height() painter = QPainter() painter.begin(self) self.drawFrame(painter) width_ratio = self._image_width / img_w height_ratio = self._image_height / img_h scale = min(width_ratio, height_ratio) painter.scale(scale, scale) x_offset = (cr.width() - (img_w * scale)) // 2 y_offset = (cr.height() - (img_h * scale)) // 2 painter.drawPicture(cr.x() + x_offset - br.x(), cr.y() + y_offset - br.y(), self.picture()) painter.end()
[docs] def setImageDelegate(self, delegate: Optional[RdMolDelegate], update: bool = True): """ Assign a delegate that can be used to provide 2D structure images. :param delegate: optionally, a delegate instance. If `None`, this widget will not render an image :param update: whether to update the displayed image """ self._image_delegate = delegate if update: self._updateImage()
[docs] def setRDMol(self, rdmol: Optional[Mol], update: bool = True): """ Assign a new ligand structure. :param rdmol: optionally, a `Mol` instance to render into a 2D image; if `None`, this widget will not render an image :param update: whether to update the displayed image """ self._rdmol = rdmol if update: self._updateImage()
[docs] def getImageSize(self) -> Tuple[int, int]: """ :return: a tuple containing the assigned (width, height) of the image """ return (self._image_width, self._image_height)
[docs] def setImageSize(self, width: int, height: int): """ Assign the size of the image. """ self._image_width = width self._image_height = height
def _updateImage(self): """ Generate a new image to display in this widget if possible. Otherwise, clear the displayed contents of this widget. """ if self._rdmol is None or self._image_delegate is None: self.clear() return pic = self._image_delegate.getPicture(rdmol=self._rdmol, atom_idcs=self._atom_idcs, color=self._highlight_color, atom_labels=self._atom_labels) self.setPicture(pic)
[docs] def setAtomHighlights(self, atom_idcs: Optional[Tuple[int]], color: Optional[Tuple[int]], update: bool = True): """ Assign the highlighted atoms and highlighting color for the image. :param atom_idcs: indices of the atoms to be highlighted :param color: color (in RGB form) to use for drawing highlighted atoms :param update: whether to update the displayed image """ self._atom_idcs = atom_idcs self._highlight_color = color if update: self._updateImage()
[docs] def setAtomLabels(self, atom_labels: Optional[Tuple[str]], update: bool = True): """ Assign the atom labels for the image. :param atom_labels: a tuple of labels ordered so that the index of each atom matches the index of the corresponding label :param update: whether to update the displayed image """ self._atom_labels = atom_labels if update: self._updateImage()
[docs] def clearModifications(self, update: bool = True): """ Clear any stored image modification parameters. :param update: whether to update the displayed image """ self.setAtomHighlights(atom_idcs=None, color=None, update=False) self.setAtomLabels(atom_labels=None, update=False) if update: self._updateImage()
[docs]class ResizableRDMolImageLabel(AbstractRDMolImageLabel): """ Widget showing a 2D structure image that can be resized at will by the user. """ def _getSizePolicy(self) -> QSizePolicy: size_policy = super()._getSizePolicy() policy = QSizePolicy.MinimumExpanding size_policy.setHorizontalPolicy(policy) size_policy.setVerticalPolicy(policy) return size_policy
[docs] def resizeEvent(self, event: QResizeEvent): super().resizeEvent(event) size = event.size() self.setImageSize(width=size.width(), height=size.height())
[docs] def setImageSize(self, width: int, height: int): super().setImageSize(width=width, height=height) self.update()
[docs]class FixedSizeRDMolImageLabel(AbstractRDMolImageLabel): """ Widget showing a 2D structure image that can be assigned a fixed size. """ def _getSizePolicy(self) -> QSizePolicy: size_policy = super()._getSizePolicy() policy = QSizePolicy.Fixed size_policy.setHorizontalPolicy(policy) size_policy.setVerticalPolicy(policy) return size_policy
[docs] def setImageSize(self, width: int, height: int): super().setImageSize(width=width, height=height) self.setFixedSize(width, height)
[docs]class RDMolTooltipWithLabel(TooltipMixin, QFrame): """ A tooltip widget that renders a 2D image of a specified ligand structure. It can also show any number of additional text labels at the bottom. """
[docs] def __init__(self, parent: Optional[QWidget] = None): flags = Qt.FramelessWindowHint | Qt.ToolTip super().__init__(parent=parent, flags=flags) self.setLayout(QVBoxLayout(self)) self.image_lbl = FixedSizeRDMolImageLabel(parent=self) self.layout().addWidget(self.image_lbl)
[docs] def setImageSize(self, width: int, height: int): """ Assign the size of the image. """ self.image_lbl.setImageSize(width=width, height=height)
[docs] def setRDMol(self, rdmol: Optional[Mol], update: bool = True): """ Assign a new ligand structure. :param rdmol: optionally, a `Mol` instance to render into a 2D image; if `None`, this widget will not render an image :param update: whether to update the displayed image """ self.image_lbl.setRDMol(rdmol=rdmol, update=update)
[docs] def setImageDelegate(self, delegate: RdMolDelegate, update: bool = True): """ Assign a delegate that can be used to provide 2D structure images. :param delegate: optionally, a delegate instance. If `None`, this widget will not render an image :param update: whether to update the displayed image """ self.image_lbl.setImageDelegate(delegate=delegate, update=update)
[docs] def addLabel(self, text: str): """ Add a label containing the specified text underneath the structure picture. :param text: text to include below the image """ label = QLabel(text=text, parent=self) self.layout().addWidget(label)