"""
Phase pharmacophore workspace markers.
Copyright Schrodinger, LLC. All rights reserved.
"""
import math
import types
from collections import OrderedDict
from functools import partial
import numpy
import schrodinger
from schrodinger.application.phase import constants
from schrodinger.application.phase import pt_hypothesis
from schrodinger.graphics3d import arrow
from schrodinger.graphics3d import common
from schrodinger.graphics3d import sphere
from schrodinger.graphics3d import torus
from schrodinger.infra import mm
from schrodinger.infra import phase
from schrodinger.infra.mmbitset import Bitset
from schrodinger.Qt import QtCore
from schrodinger.Qt import QtWidgets
from schrodinger.structutils import analyze
from schrodinger.structutils import measure
from schrodinger.ui.qt.utils import maestro_required
maestro = schrodinger.get_maestro()
markers = None
LABEL_FONT_NAME = "Sans Serif"
if maestro:
LABEL_FONT_NAME = maestro.get_command_option("labelatom", "font")
def _calculate_arrow_tail(xyz, pxyz, rad, rcyl):
"""
Calculate coordinates of the projected point arrow tail position.
Tail position is calculated in such a way that arrow cylinder 'grows'
from the surface of the pharmacophore sphere.
:param xyz: coordinates of the pharmacophore site
:type xyz: (float, float, float)
:param pxyz: coordinates of the projected point (arrow end point)
:type pxyz: (float, float, float)
:param rad: pharmacophore feature sphere radius
:type rad: float
:param rcyl: radius of the arrow cylinder
:type rcyl: float
:return: tuple that contains xyz coordinates of the arrow tail point
:rtype: tuple
"""
difference = numpy.asarray(pxyz) - numpy.asarray(xyz)
delta = math.sqrt(rad * rad - rcyl * rcyl)
distance = measure.measure_distance(xyz, pxyz)
return numpy.asarray(xyz) + difference * delta / distance
[docs]def set_freestyle_site_type(site, feature_type):
"""
Updates a site with the given pharmacophore feature type, and if applicable
assigns projected coordinates of the corresponding free-style site type.
:return: pharmacophore site
:rtype: `phase.PhpSite`
:param feature_type: site's feature type
:type feature_type: str
"""
# Update the site type
site.setSiteType(feature_type)
# Assign free-style projected vector coordinates
proj_coords = []
x, y, z = site.getCoordinates()
if feature_type in [constants.FEATURE_A, constants.FEATURE_D]:
proj_coords.append([x, y, z + phase.PHASE_DEFAULT_EXTENDED_DISTANCE])
elif feature_type == constants.FEATURE_R:
proj_coords.append([x, y, z + phase.PHASE_DEFAULT_EXTENDED_DISTANCE])
proj_coords.append([x, y, z - phase.PHASE_DEFAULT_EXTENDED_DISTANCE])
site.setProjCoords(proj_coords)
[docs]def is_site_grouped(site):
"""
Get whether site is grouped
:param site: pharmacophore site
:type site: phase.PhpSite
:return: whether or not site is grouped
:rtype: bool
"""
return site.getMaskValue() not in constants.MASK_NAMES.values()
[docs]def get_mask_name(site):
"""
Get the name of the site's mask
:param site: pharmacophore site
:type site: phase.PhpSite
:return: site mask min match name
:rtype: str
"""
site_mask = site.getMaskValue()
for name, mask in constants.MASK_NAMES.items():
if mask == site_mask:
return name
# Otherwise the mask is part of a group
return constants.GROUPED_MASK_NAME
[docs]class PhaseSphere(sphere.MaestroSphere):
"""
Phase sphere graphics object.
"""
[docs] def __init__(self, *args, **kwargs):
# See base class for documentation.
super(PhaseSphere, self).__init__(*args, **kwargs)
# determines whether this sphere was 'selected'
self.is_selected = False
[docs] def setSelected(self, selected, color=None):
"""
Shows sphere as selected (with a glow effect). Glow effect color only
needs to be specified when turning it 'on'.
:param selected: whether sphere should be selected
:type selected: bool
:param color: color used for selected glow effect.
:type color: tuple
"""
if selected:
self.setGlowColor(*color)
self.is_selected = selected
self.setIsGlowing(selected)
[docs]class PhaseXvolMarkers(PhaseSphereMarkers):
"""
Class that defines group of spheres for the excluded volume.
"""
# exclude volume sphere color
SPHERE_COLOR = (0.2, 0.9, 0.95)
# excluded volume sphere opacity
SPHERE_OPACITY = 0.2
[docs] def __init__(self, entry_id, x_vol, interactive=False):
"""
Marker initializer.
:param entry_id: hypothesis entry ID
:type entry_id: int
:param x_vol: excluded volume object
:type x_vol: `phase.PhpExclVol`
"""
super(PhaseXvolMarkers, self).__init__(entry_id)
self.interactive = interactive
self.addExcludedVolumes(x_vol)
[docs] def addExcludedVolumes(self, x_vol):
"""
Adds excluded volume spheres to the current group.
:param x_vol: excluded volume object
:type x_vol: `phase.PhpExclVol`
"""
for sphere_idx in range(0, x_vol.numSpheres()):
radius = x_vol.getSphereRadius(sphere_idx)
xyz = x_vol.getSphereXYZ(sphere_idx)
self.addSphere(xyz, radius)
[docs]class PhaseTolMarkers(PhaseSphereMarkers):
"""
Class that defines group of tolerance spheres.
"""
# tolerance sphere color
SPHERE_COLOR = (0.5, 0.5, 0.5)
# tolerance sphere opacity
SPHERE_OPACITY = 0.5
[docs] def __init__(self, entry_id, sites):
"""
Marker initializer.
:param entry_id: hypothesis entry ID
:type entry_id: int
:param sites: Phase hypothesis sites
:type sites: list of `phase.PhpSite`
"""
super(PhaseTolMarkers, self).__init__(entry_id)
# keep track of the tolerance sphere markers
self._tol_markers = {}
for site in sites:
sphere = self.addSphere(site.getCoordinates(), site.getTol())
tol_marker_key = self._genTolMarkerKey(site)
self._tol_markers[tol_marker_key] = self.spheres.index(sphere)
[docs] def showTolMarker(self, site):
"""
Show tolerance marker for the given site.
:param site: Phase hypothesis site
:type site: `phase.PhpSite`
"""
tol_marker_key = self._genTolMarkerKey(site)
sph_idx = self._tol_markers.get(tol_marker_key)
if sph_idx is not None:
self.showSphere(sph_idx)
[docs] def hideTolMarker(self, site):
"""
Hide tolerance marker for the given site.
:param site: Phase hypothesis site
:type site: `phase.PhpSite`
"""
tol_marker_key = self._genTolMarkerKey(site)
sph_idx = self._tol_markers.get(tol_marker_key)
if sph_idx is not None:
self.hideSphere(sph_idx)
def _genTolMarkerKey(self, site):
"""
Creates an internal key for the given marker PhpSite.
"""
return self.entry_id, site.getDisplayName()
[docs]class PhaseMarkers(QtCore.QObject):
"""
Class for adding pharmacophore feature markers and controlling their
visibility.
:cvar editingFinished: signal that gets emitted when feature editing is
finished. This signal is emitted regardless of whether feature was changed
or not.
:vartype editingFinished: `QtCore.pyqtSignal`
:cvar closeFeatureEditDialog: signal that gets emitted when any open edit
feature dialog should be closed. This signal is emitted when the Phase GUI
workflow changes, or the panel is closed.
:vartype closeFeatureEditDialog: `QtCore.pyqtSignal`
:cvar workspaceMarkersChanged: signal that gets emitted when features are
shown or hidden in the Workspace.
:vartype workspaceMarkersChanged: `QtCore.pyqtSignal`
:ivar _phase_features: dictionary mapping feature key tuples to markers
associated with this panel
:vartype _phase_features: dict[(int, str), PhaseFeatureMarker]
"""
editingFinished = QtCore.pyqtSignal()
closeFeatureEditDialog = QtCore.pyqtSignal()
workspaceMarkersChanged = QtCore.pyqtSignal()
[docs] def __init__(self, *args, **kwargs):
self._phase_features = OrderedDict()
self._phase_xvol = {}
self._phase_tol = {}
super(PhaseMarkers, self).__init__(*args, **kwargs)
[docs] def featureExists(self, entry_id):
"""
This function checks whether pharmacophore features exist for a
structure with a given entry_id and returns True or False.
:param entry_id: structure entry_id
:type entry_id: int
:return: True if features were already added and False otherwise
:rtype: bool
"""
if entry_id in self._getEntryIDs():
return True
return False
[docs] def addStructureFeatures(self, st, mapper, entry_id, allow_picking=True):
"""
Add all pharmacophore feature markers for a given structure.
:param st: molecule structure
:type st: `structure.Structure`
:param mapper: Site mapper
:type mapper: phase.PhpSiteMapper
:param entry_id: structure entry id
:type entry_id: int
:param allow_picking: whether this marker can be 'picked', which allows
showing a context menu when right-clicked
:type allow_picking: bool
:return: list of sites that were added for this structure
:rtype: list
"""
try:
sites = mapper.mapSites(st.handle)
except phase.PhpException as exc:
msg = "Error encountered applying feature definitions to entry " \
"\'%s\'. Unable to generate features.\n\n%s" % (st.title, exc)
QtWidgets.QMessageBox.critical(None, "Error", msg)
return None
self.addSites(sites, entry_id, allow_picking=allow_picking)
return list(sites)
[docs] def addSites(self,
sites,
entry_id,
use_highlight=False,
allow_picking=True):
"""
Add pharmacophore features for given sites.
:param sites: list of pharmacophore sites
:type sites: list of `phase.PhpSite`
:param entry_id: structure entry id
:type entry_id: int
:param use_highlight: indicates whether sites should be 'highlighted'
:type use_highlight: list
:param allow_picking: whether this marker can be 'picked', which allows
showing a context menu when right-clicked
:type allow_picking: bool
"""
self.addCustomSites(sites,
entry_id,
use_highlight,
allow_picking,
marker_class=PhaseFeatureMarker)
[docs] def addCustomSites(self,
sites,
entry_id,
use_highlight=False,
allow_picking=True,
site_color_map=constants.FEATURE_COLORS,
*_,
marker_class):
"""
Add pharmacophore features for given sites using a custom marker class.
See `addSites` for additional argument documentation.
:param site_color_map: Mapping of phase interaction type -> color
:type site_color_map: Dict {phase.InteractionType: (r, g, b)}
:param marker_class: Marker class (Mandatory keyword-only argument)
:type marker_class: PhaseFeatureMarker
"""
for site in sites:
feature_key = self._genFeatureKey(entry_id, site.getDisplayName())
if feature_key in self._phase_features:
# Assume that the marker for this feature was already generated
continue
color = site_color_map[site.getSiteType()]
marker = marker_class(site, entry_id, color, use_highlight,
allow_picking)
self._phase_features[feature_key] = marker
[docs] def getFeature(self, feature_name, entry_id):
"""
Retrieve markers for the specified feature.
:param feature_name: feature name
:type feature_name: str
:param entry_id: entry id of the structure that this feature is
associated with
:type entry_id: int
:return: tuple that contains pharmacophore and projected points marker
:rtype: tuple
"""
feature_key = self._genFeatureKey(entry_id, feature_name)
try:
return self._phase_features[feature_key]
except KeyError:
err = "No feature exists for the specified feature name."
raise ValueError(err)
[docs] def getAllFeatures(self):
"""
Retrieve markers for all features.
:return: list of pharmacophore markers
:rtype: list
"""
return list(self._phase_features.values())
[docs] def getSites(self, entry_id):
"""
Get list of sites for all features with a given entry id.
:param entry_id: entry_id
:type entry_id: int
:return: list of sites
:rtype: list
"""
sites = []
for feature_name in self.getFeatureNames(entry_id):
feature = self.getFeature(feature_name, entry_id)
sites.append(feature.site)
return sites
[docs] def getFeatureNames(self, entry_id):
"""
Get list of feature names that were found for the structure with a
given entry id. Feature names are sorted in alphabetical order.
:param entry_id: entry_id
:type entry_id: int
:return; list of feature names
:rtype: list
"""
# Feature names contain a single character followed by the feature
# number as in 'A1', 'R11' etc. We want to sort feature names
# alphabetically first and by the feature number second as in:
# 'A1', 'A2', 'A11', 'D3', 'D4', 'P5' etc
def feature_key_func(feature_name):
return feature_name[0], int(feature_name[1:])
feature_names = [k[1] for k in self._getKeysForEntry(entry_id)]
return sorted(feature_names, key=feature_key_func)
[docs] @maestro_required
def addFeaturesForWorkspaceLigands(self, fds, allow_picking=True):
"""
Add feature markers for each included ligand PT entry, according to the
given feature definitions.
:param fds: list of feature definitions
:type st: list
:param allow_picking: indicates whether feature can be 'picked'. When
enabled this would make possible to show context menu when feature
is right-clicked. Default is True.
:type: allow_picking
:rtype: list of ints
:return: List of entry IDs for the included ligands.
"""
sts = maestro.get_included_entries()
# Make a list of all ligands in the Workspace (included entries).
ligand_sts = [
ligand.st for st in sts for ligand in analyze.find_ligands(st)
]
entry_ids = []
mapper = phase.PhpSiteMapper(fds)
for ligand_st in ligand_sts:
# get entry id as int from the ligand string property
try:
entry_id = int(ligand_st.atom[1].entry_id)
except ValueError:
continue
# create features only if needed
if not self.featureExists(entry_id):
self.addStructureFeatures(ligand_st,
mapper,
entry_id,
allow_picking=allow_picking)
entry_ids.append(entry_id)
# delay call to ensure Maestro workspace changes were applied
self._updateFeatures()
return entry_ids
def _updateFeatures(self):
"""
Show or hide features for entries that are included or excluded in the
workspace. Delete features for entries, which were deleted from the
project.
"""
entry_ids = self._getEntryIDs()
pt = maestro.project_table_get()
for eid in entry_ids:
row = pt.getRow(eid)
if row and row.in_workspace:
self.showAllFeaturesForEntry(eid)
else:
self.removeAllFeaturesForEntry(eid)
self.workspaceMarkersChanged.emit()
# --------- Methods to remove pharmacophore features ----------------
[docs] def removeFeature(self, feature_name, entry_id):
"""
Removes markers for the specified pharmacophore feature.
:param feature_name: feature name
:param feature_name: str
:param entry_id: entry id of the structure that this feature is
associated with
:type entry_id: int
"""
try:
feature_key = self._genFeatureKey(entry_id, feature_name)
self._hideFeatureForKey(feature_key)
self._phase_features[feature_key].clear()
del self._phase_features[feature_key]
except KeyError:
err = "Specified feature does not exist"
raise ValueError(err)
[docs] def removeAllFeatures(self):
"""
Removes markers for all pharmacophore features.
"""
self.hideAllFeatures()
self._phase_features.clear()
[docs] def removeAllFeaturesForEntry(self, entry_id):
"""
Removes markers for all features associated with a given entry.
:param entry_id: entry id of the structure that this feature is
associated with
:type entry_id: int
"""
self.hideAllFeaturesForEntry(entry_id)
for feature_key in self._getKeysForEntry(entry_id):
self._phase_features[feature_key].marker_group.clear()
del self._phase_features[feature_key]
# --------- Methods to show pharmacophore features ----------------
[docs] def showFeature(self, feature_name, entry_id):
"""
Show markers for the specified pharmacophore feature.
:param feature_name: feature name
:param feature_name: str
:param entry_id: entry id of the structure that this feature is
associated with
:type entry_id: int
"""
feature_key = self._genFeatureKey(entry_id, feature_name)
self._showFeatureForKey(feature_key)
[docs] def showAllFeatures(self):
"""
Shows markers for all pharmacophore features.
"""
for feature_key in self._phase_features:
self._showFeatureForKey(feature_key)
[docs] def showAllFeaturesForEntry(self, entry_id):
"""
Shows markers for all features associated with a given entry.
:param entry_id: entry id of the structure that this feature is
associated with
:type entry_id: int
"""
for feature_key in self._getKeysForEntry(entry_id):
self._showFeatureForKey(feature_key)
[docs] def showAllFeaturesForEntryByType(self, entry_id, feature_types):
"""
Show markers for all given feature types associated with a given entry.
:param entry_id: entry id of the structure that these features are
associated with.
:type entry_id: int
:param feature_types: list of feature types that markers should be
shown for.
:type feature_types: List of str
"""
for feature_key in self._getKeysForEntry(entry_id):
marker = self._phase_features[feature_key]
if marker.feature_type in feature_types:
marker.show()
self.showTolMarker(entry_id, marker.site)
else:
marker.hide()
self.hideTolMarker(entry_id, marker.site)
def _showFeatureForKey(self, feature_key):
"""
Show markers for a given feature key.
:param feature_key: unique feature key.
:type feature_key: str
"""
marker = self._phase_features[feature_key]
marker.show()
# --------- Methods to hide pharmacophore features ----------------
[docs] def hideFeature(self, feature_name, entry_id):
"""
Hide markers for the specified pharmacophore feature.
:param feature_name: feature name
:param feature_name: str
:param entry_id: entry id of the structure that this feature is
associated with
:type entry_id: int
"""
feature_key = self._genFeatureKey(entry_id, feature_name)
self._hideFeatureForKey(feature_key)
[docs] def hideAllFeatures(self):
"""
Hide markers for all pharmacophore features.
"""
for feature_key in self._phase_features:
self._hideFeatureForKey(feature_key)
[docs] def hideAllFeaturesForEntry(self, entry_id):
"""
Hide markers for all features associated with a given entry.
:param entry_id: entry id of the structure that this feature is
associated with
:type entry_id: int
"""
for feature_key in self._getKeysForEntry(entry_id):
self._hideFeatureForKey(feature_key)
def _hideFeatureForKey(self, feature_key):
"""
Hide markers for a given feature key.
:param feature_key: unique feature key.
:type feature_key: str
"""
marker = self._phase_features[feature_key]
marker.hide()
# --------- Excluded Volume methods -------------------------------
[docs] def hasXvolMarkers(self, entry_id):
"""
Checks whether excluded volume markers for a given entry ID exist.
:param entry_id: entry ID
:type entry_id: int
"""
return entry_id in self._phase_xvol
[docs] def removeXvolMarkers(self, entry_id):
"""
Removes all excluded volume markers for a given entry.
:param entry_id: entry ID
:type entry_id: int
"""
# If there are no excluded volume markers, do nothing
if not self.hasXvolMarkers(entry_id):
return
xvol_group = self._phase_xvol[entry_id]
xvol_group.hide()
xvol_group.clear()
del self._phase_xvol[entry_id]
[docs] def removeXvolMarkersWithIDs(self, entry_id, sphere_indices):
"""
Removes excluded volume markers with specified sphere ids for a
given entry.
:param entry_id: entry ID
:type entry_id: int
:param sphere_indices: list of sphere indices
:type sphere_indices: list
"""
xvol_group = self._phase_xvol[entry_id]
xvol_group.removeSpheres(sphere_indices)
[docs] def removeAllXvolMarkers(self):
"""
Removes all excluded volume markers.
"""
for xvol_key in list(self._phase_xvol):
self.removeXvolMarkers(xvol_key)
[docs] def addXvolMarkers(self, entry_id, x_vol, interactive=False):
"""
Adds excluded volume markers for a given entry ID.
:param entry_id: entry ID
:type entry_id: int
:param x_vol: data object that contains excluded volumes
:type x_vol: `phase.PhpExclVol`
:param interactive: indicates whether markers should be 'interactive'
:type interactive: bool
"""
if entry_id in self._phase_xvol:
# add new excluded volume markers to existing group
xvol_group = self._phase_xvol[entry_id]
xvol_group.addExcludedVolumes(x_vol)
else:
# create new markers group
xvol_group = PhaseXvolMarkers(entry_id,
x_vol,
interactive=interactive)
self._phase_xvol[entry_id] = xvol_group
xvol_group.show()
[docs] def findXvolSphereIndex(self, entry_id, pick_id):
"""
Finds excluded volume sphere index for a given sphere pick id. Pick
id is used to identify graphics3d objects picked in the Workspace.
:param entry_id: entry ID
:type entry_id: int
:param pick_id: pick id of graphics3d object
:type pick_id: int
:return: index of sphere with a given pick id in the list that
PhaseXvolMarkers object maintains.
:rtype: int
"""
xvol_group = self._phase_xvol[entry_id]
for row_id, sphere in enumerate(xvol_group.spheres):
if sphere.pick_id == pick_id:
return row_id
raise ValueError("Specified sphere does not exist.")
[docs] def setXvolSelected(self, entry_id, sphere_indices):
"""
Selects spheres with given sphere ids for a given entry id.
:param entry_id: entry ID
:type entry_id: int
:param sphere_indices: list of sphere indices
:type sphere_indices: list
"""
xvol_group = self._phase_xvol[entry_id]
xvol_group.setSelected(sphere_indices)
[docs] def updateXvolMarker(self, entry_id, sphere_index, r, x, y, z):
"""
This function is called to change radius and coordinates of
excluded volume with a given sphere id and entry id.
:param entry_id: entry ID
:type entry_id: int
:param sphere_index: sphere index
:type sphere_index: int
:param r: new radius
:type r: float
:param x: new x-coordinate
:type x: float
:param y: new y-coordinate
:type y: float
:param z: new z-coordinate
:type z: float
"""
xvol_group = self._phase_xvol[entry_id]
xvol_group.setSphereRadiusAndCoords(sphere_index, r, x, y, z)
# --------- Tolerance Sphere methods -------------------------------
[docs] def hasTolMarkers(self, entry_id):
"""
Checks whether tolerance markers for a given entry ID exist.
:param entry_id: entry ID
:type entry_id: int
"""
return entry_id in self._phase_tol
[docs] def removeTolMarkers(self, entry_id):
"""
Removes all tolerance markers for a given entry.
:param entry_id: entry ID
:type entry_id: int
"""
# If there are no tolerance markers, do nothing
if not self.hasTolMarkers(entry_id):
return
tol_group = self._phase_tol[entry_id]
tol_group.hide()
tol_group.clear()
del self._phase_tol[entry_id]
[docs] def removeAllTolMarkers(self):
"""
Removes all tolerance markers.
"""
for tol_key in list(self._phase_tol):
self.removeTolMarkers(tol_key)
[docs] def addTolMarkers(self, entry_id, sites):
"""
Adds tolerance markers for a given entry ID.
:param entry_id: entry ID
:type entry_id: int
:param sites: Phase hypothesis sites
:type sites: list of `phase.PhpSite`
"""
tol_group = PhaseTolMarkers(entry_id, sites)
self._phase_tol[entry_id] = tol_group
tol_group.show()
[docs] def showTolMarker(self, entry_id, site):
"""
Show tolerance marker for the given entry ID and site.
:param entry_id: hypothesis entry ID
:type entry_id: int
:param site: Phase hypothesis site
:type site: `phase.PhpSite`
"""
tol_group = self._phase_tol.get(entry_id)
if tol_group:
tol_group.showTolMarker(site)
[docs] def hideTolMarker(self, entry_id, site):
"""
Hide tolerance marker for the given entry ID and site.
:param entry_id: hypothesis entry ID
:type entry_id: int
:param site: Phase hypothesis site
:type site: `phase.PhpSite`
"""
tol_group = self._phase_tol.get(entry_id)
if tol_group:
tol_group.hideTolMarker(site)
[docs] def projectModel(self):
"""
Returns a Maestro project object.
:rtype: `schrodinger.MM_Project`
:return: Maestro project object.
"""
pt = maestro.project_table_get()
pt_model = pt.project_model
return pt_model
# --------- Reference Ligand methods -------------------------------
[docs] def hasRefCtMarker(self, entry_id):
"""
Checks whether refernce ligand marker for a given entry ID exists.
:param entry_id: entry ID
:type entry_id: int
"""
pt_model = self.projectModel()
# Int cast is needed here to make the sip wrappings compatible.
return pt_model.hypothesisHasReferenceLigandEntryId(int(entry_id))
[docs] def removeRefCtMarker(self, entry_id):
"""
Removes all reference ligand markers for a given entry.
:param entry_id: entry ID
:type entry_id: int
"""
# If there are no reference ligand markers, do nothing
if not self.hasRefCtMarker(entry_id):
return
pt_model = self.projectModel()
# Int cast is needed here to make the sip wrappings compatible.
ref_ligand_entry_id = pt_model.getHypothesisReferenceLigandEntryId(
int(entry_id))
if ref_ligand_entry_id:
maestro.command("entrydelete entry %s" % ref_ligand_entry_id)
# Int cast is needed here to make the sip wrappings compatible.
pt_model.clearReferenceLigandEntryIdFromHypothesis(int(entry_id))
[docs] def removeAllRefCtMarkers(self):
"""
Removes all reference ligand markers.
"""
pt_model = self.projectModel()
pt_model.removeHypothesisReferenceLigandEntries()
[docs] def addRefCtMarker(self, entry_id, ref_ct):
"""
Adds reference ligand markers for a given entry ID.
:param entry_id: entry ID
:type entry_id: str
:param ref_ct: data object that contains reference ligands
:type ref_ct: int
:note ownership is transferred to maestro project.
"""
pt_model = self.projectModel()
# If user is doing undo, then we dont have to add reference ct
# it should already be present in the project's undo object.
if self.hasRefCtMarker(entry_id):
return
pt = maestro.project_table_get()
title = "Reference Ligand (" + pt[entry_id].title + ")"
# Int cast is needed here to make the sip wrappings compatible.
ref_ligand_entry_id = pt_model.addReferenceLigandEntry(
ref_ct, title, int(entry_id))
if ref_ligand_entry_id:
maestro.command("entrywsincludelock entry %s" % ref_ligand_entry_id)
# --------- Methods to toggle feature 'selection' -----------------
[docs] def setSelection(self, features, entry_id, selected):
"""
Sets selection mode for multiple features.
:param features: list of feature names
:type features: list
:param entry_id: entry id of the structure that this feature is
associated with
:type entry_id: int
:param selected: indicates whether feature should be selected or not
:type selected: bool
"""
for feature_name in features:
self.setFeatureSelection(feature_name, entry_id, selected)
[docs] def setFeatureSelection(self, feature_name, entry_id, selected):
"""
Set specified pharmacophore feature selection mode. Shows glow
effect around markers.
:param feature_name: feature name
:param feature_name: str
:param entry_id: entry id of the structure that this feature is
associated with
:type entry_id: int
:param selected: indicates whether feature should be selected or not
:type selected: bool
"""
feature_key = self._genFeatureKey(entry_id, feature_name)
marker = self._phase_features[feature_key]
marker.setSelected(selected)
[docs] def clearSelection(self):
"""
Clear 'selection' for all pharmacophore features.
"""
for feature_key in self._phase_features:
marker = self._phase_features[feature_key]
marker.setSelected(False)
# --------- Methods to show feature labels ------------------------
[docs] def setFeatureLabelVisible(self, entry_id, visible):
"""
Shows or hides feature label for all markers.
:param entry_id: entry id of the structure that these features are
associated with
:type entry_id: int
:param visible: whether to make feature labels visible or not
:type visible: bool
"""
for feature_name in self.getFeatureNames(entry_id):
feature_marker = self.getFeature(feature_name, entry_id)
feature_marker.show_feature_labels = visible
if maestro:
feature_marker.createLabels()
# --------- Methods to show feature property labels ---------------
[docs] def setPropertyLabelVisible(self, entry_id, visible):
"""
Shows or hides detailed (property) label for all feature markers.
:param entry_id: entry id of the structure that these features are
associated with
:type entry_id: int
:param visible: whether to make property labels visible or not
:type visible: bool
"""
for feature_name in self.getFeatureNames(entry_id):
feature_marker = self.getFeature(feature_name, entry_id)
feature_marker.show_prop_labels = visible
if maestro:
feature_marker.createLabels()
# --------- Utility methods ---------------------------------------
def _genFeatureKey(self, entry_id, feature_name):
"""
Create a unique feature key.
:param entry_id: entry id of the structure that this feature is
associated with
:type entry_id: int
:param feature_name: feature name
:param feature_name: str
:return: tuple of (maestro entry ID, pharamcophore site name)
:rtype: (int, str)
"""
return entry_id, feature_name
def _getKeysForEntry(self, entry_id):
"""
Get a list of all feature keys for a given entry id.
:param entry_id: entry id of the structure that this feature is
associated with
:type entry_id: int
:return: list of feature keys
:rtype: list
"""
feature_keys = [h for h in self._phase_features if h[0] == entry_id]
return feature_keys
def _getEntryIDs(self):
"""
Get a list of unique entry ids that feature markers stored in this
mixin are associated with.
:return: set of unique entry ids
:rtype: set
"""
return {f[0] for f in self._phase_features}
[docs] def getFeatureFromId(self, marker_id):
"""
Return the marker with the given ID.
:param marker_id: ID of the marker (.id attribute)
:type marker_id: int
:return Marker object
:rtype `PhaseFeatureMarker`
"""
for marker in self._phase_features.values():
if marker.id == marker_id:
return marker
raise ValueError("Marker with ID %s was not found" % marker_id)
[docs]def get_phase_markers():
"""
This function returns global PhaseMarkers object that should be used
to generate Phase marker throughout Maestro session.
:return: PhaseMarkers object
:rtype: `PhaseMarkers`
"""
global markers
if markers is None:
markers = PhaseMarkers()
return markers
[docs]def update_hypothesis_entry(hypo, entry_id):
"""
Updates the structure in the project table for the given entry ID with
the current PhaseHypothesis, updating the reference ligand if needed.
:param hypo: hypothesis to assign to the given PT entry
:type hypo: `hypothesis.PhaseHypothesis`
:param entry_id: entry id to set hypothesis to
:type entry_id: int or str
"""
# If neccessary, remove reference ligand entry from the project table;
# it will be recreated and shown again after the update
markers = get_phase_markers()
if markers.hasRefCtMarker(entry_id):
markers.removeRefCtMarker(entry_id)
pt_hypothesis.update_hypothesis_entry(hypo, entry_id)
[docs]def show_workspace_phase_markers(entry_ids, include_Q=False):
"""
Delays the call to show workspace phase markers with a single shot to
allow for the project table project_model to update appropriately.
:param entry_ids: list of Phase hypothesis entries
:type entry_ids: list of int
:param include_Q: whether to include Q site phase markers
:type include_Q: bool
"""
# Reference ligand must be added in the project as part of current command
# which triggered this call. If we execute it through single slot,
# then current command finishes before single shot timer slot
# gets called, therefore we are not able to track undo state of changes.
eid_hypo_map = {eid: _get_hypothesis(eid) for eid in entry_ids}
_show_reference_ligand(eid_hypo_map)
show_markers_func = partial(_show_workspace_phase_markers, eid_hypo_map,
include_Q)
QtCore.QTimer.singleShot(0, show_markers_func)
def _undisplay_entry_atoms(entry_id):
"""
Undisplay all atoms associated with the given entry id in the
workspace ct.
We manipulate the workspace directly here instead of issuing a
maestro command so that we don't break the user's ability to
undo inclusion (MAE-38226). This function should only be
called when we need to bypass maestro command framework.
:param entry_id : Phase hypothesis entry id
:type entry_id : int
"""
st = maestro.workspace_get(copy=False)
atoms_bs = Bitset(size=st.atom_total)
# Cast entry ID to str for mmct call
mm.mmct_ct_get_entry_atoms(st, str(entry_id), atoms_bs)
for atom in atoms_bs:
mm.mmctg_atom_set_visible(st, atom, False)
maestro.workspace_set(st, regenerate_markers=True, copy=False)
def _show_reference_ligand(eid_hypo_map):
"""
Show reference ligand for the entries in `eid_hypo_map` that have a
hypothesis associated with them.
@NOTE - MAE-38321, MAE-38324
This function has to be kept separate from _show_workspace_phase_markers()
because it has to be executed immediately rather than running through
single slot initiated to execute _show_workspace_phase_markers().
Any project changes have to run always under command execution code
in order to allow UNDO. If this function is run through
_show_workspace_phase_markers(), then command gets completed before
executing this function and we lose the ability to undo.
:param eid_hypo_map: dictionary mapping of entry ids to hypothesis objects
:type eid_hypo_map: Dict[int, hypothesis.PhaseHypothesis]
"""
for eid, hypo in eid_hypo_map.items():
if hypo is not None:
set_reference_ligand_visible(eid, hypo.visibleRefCt(), hypo)
def _get_hypothesis(eid):
"""
Get hypothesis associated with an entry id.
:param entry_id: entry id of the structure that this hypothesis
associated with
:type eid: int
:return: Hypothesis.
:rtype: `hypothesis.PhaseHypothesis`
"""
try:
hypo = pt_hypothesis.get_hypothesis_from_project(eid)
return hypo
except RuntimeError as e:
QtWidgets.QMessageBox.warning(None, "Warning", str(e))
return None
def _show_workspace_phase_markers(eid_hypo_map, include_Q):
"""
Adds Phase pharmacophore site markers for the entries in `eid_hypo_map` that
have a hypothesis associated with them.
:param eid_hypo_map: dictionary mapping of entry ids to hypothesis objects
:type eid_hypo_map: Dict[int, hypothesis.PhaseHypothesis]
:param include_Q: whether to include Q site phase markers
:type include_Q: bool
"""
markers = get_phase_markers()
for eid, hypo in eid_hypo_map.items():
# PANEL-8876: Hide dummy atom particle at feature center
_undisplay_entry_atoms(eid)
markers.removeAllFeaturesForEntry(eid)
if hypo is not None:
sites = hypo.getHypoSites(include_Q)
markers.addSites(sites, eid, use_highlight=True)
set_excluded_volume_visible(eid, hypo.visibleXvol(), hypo)
set_feature_tolerance_visible(eid, hypo.visibleTol(), hypo)
set_property_labels_visible(eid, hypo.visiblePropLabels(), hypo)
# Update project table to make in-sync with newly added _phasehypo_ props
maestro.command('projectupdateviews')
[docs]def hide_workspace_phase_markers(entry_ids):
"""
Removes Phase pharmacophore site markers for given entries.
:param entry_ids: list of Phase hypothesis entries
:type entry_ids: list
"""
markers = get_phase_markers()
for eid in entry_ids:
markers.removeAllFeaturesForEntry(eid)
markers.removeXvolMarkers(eid)
markers.removeTolMarkers(eid)
# When removing programmatically, we should not change visibility
# state, so don't call set_reference_ligand_visible().
markers.removeRefCtMarker(eid)
[docs]def project_close():
"""
Delete all Phase feature markers.
"""
markers = get_phase_markers()
markers.removeAllFeatures()
markers.removeAllXvolMarkers()
markers.removeAllTolMarkers()
markers.removeAllRefCtMarkers()
[docs]def setup_project_close():
"""
Sets up function that should be called when project is closed.
"""
maestro.project_close_callback_add(project_close)
[docs]def set_entry_property(entry_id, prop_name, value):
"""
Sets entry ct property to a given value. This function is used
to set special hypothesis properties that define whether excluded
volumes, tolerances and reference structure should be visible.
:param entry_id: hypothesis entry id
:type entry_id: int
:param prop_name: property name
:type prop_name: str
:param value: property value
:type value: float or int or str
"""
pt = maestro.project_table_get()
if entry_id not in pt:
return
pt[entry_id][prop_name] = value
[docs]def set_excluded_volume_visible(entry_id, visible, hypo=None):
"""
Toggles hypothesis properties which can be shown in the workspace for
hypothesis with a given entry id.
:param entry_id: Phase hypothesis entry id
:type entry_id: int
:param visible: whether to make visible in the workspace or not
:type visible: bool
:param hypo: the hypothesis or None. If None, the hypothesis will be
obtained from the project entry id.
:type hypo: hypothesis.PhaseHypothesis
"""
markers = get_phase_markers()
hypo = hypo or pt_hypothesis.get_hypothesis_from_project(entry_id)
if not hypo or not hypo.hasXvol():
return
markers.removeXvolMarkers(entry_id)
if visible:
markers.addXvolMarkers(entry_id, hypo.getXvol())
# Set the visible hypothesis property to the current state and update
if hypo.property.get(constants.HYPO_XVOL_VISIBLE, None) != visible:
set_entry_property(entry_id, constants.HYPO_XVOL_VISIBLE, visible)
[docs]def set_reference_ligand_visible(entry_id, visible, hypo=None):
"""
Toggles display of reference ligand in the Workspace for
hypothesis with a given entry id.
:param entry_id: Phase hypothesis entry id
:type entry_id: int
:param visible: whether to make visible in the workspace or not
:type visible: bool
:param hypo: the hypothesis or None. If None, the hypothesis will be
obtained from the project entry id.
:type hypo: hypothesis.PhaseHypothesis
"""
markers = get_phase_markers()
hypo = hypo or pt_hypothesis.get_hypothesis_from_project(entry_id)
if not hypo or not hypo.hasRefCt():
return
if visible:
markers.addRefCtMarker(entry_id, hypo.getRefCt())
else:
markers.removeRefCtMarker(entry_id)
# Set the visible hypothesis property to the current state and update
if hypo.property.get(constants.HYPO_REFCT_VISIBLE, None) != visible:
set_entry_property(entry_id, constants.HYPO_REFCT_VISIBLE, visible)
[docs]def set_feature_tolerance_visible(entry_id, visible, hypo=None):
"""
Toggles display of feature tolerance spheres in the Workspace for
hypothesis with a given entry id.
:param entry_id: Phase hypothesis entry id
:type entry_id: int
:param visible: whether to make visible in the workspace or not
:type visible: bool
:param hypo: the hypothesis or None. If None, the hypothesis will be
obtained from the project entry id.
:type hypo: hypothesis.PhaseHypothesis
"""
markers = get_phase_markers()
hypo = hypo or pt_hypothesis.get_hypothesis_from_project(entry_id)
if not hypo:
return
markers.removeTolMarkers(entry_id)
if visible:
markers.addTolMarkers(entry_id, hypo.getHypoSites())
# Set the visible hypothesis property to the current state and update
if hypo.property.get(constants.HYPO_TOL_VISIBLE, None) != visible:
set_entry_property(entry_id, constants.HYPO_TOL_VISIBLE, visible)
[docs]def set_feature_labels_visible(entry_id, visible, hypo=None):
"""
Toggles display of feature labels for hypothesis with a given
entry_id.
:param entry_id: Phase hypothesis entry id
:type entry_id: int
:param visible: whether to make feature labels visible or not
:type visible: bool
:param hypo: the hypothesis or None. If None, the hypothesis will be
obtained from the project entry id.
:type hypo: hypothesis.PhaseHypothesis
"""
markers = get_phase_markers()
hypo = hypo or pt_hypothesis.get_hypothesis_from_project(entry_id)
if not hypo:
return
markers.setFeatureLabelVisible(entry_id, visible)
maestro.redraw()
# Set property to show feature labels to the current state and update
if hypo.property.get(constants.FEATURE_LABELS_VISIBLE, None) != visible:
set_entry_property(entry_id, constants.FEATURE_LABELS_VISIBLE, visible)
[docs]def set_property_labels_visible(entry_id, visible, hypo=None):
"""
Toggles display of feature property labels for hypothesis with a given
entry_id.
:param entry_id: Phase hypothesis entry id
:type entry_id: int
:param visible: whether to make property labels visible or not
:type visible: bool
:param hypo: the hypothesis or None. If None, the hypothesis will be
obtained from the project entry id.
:type hypo: hypothesis.PhaseHypothesis
"""
markers = get_phase_markers()
hypo = hypo or pt_hypothesis.get_hypothesis_from_project(entry_id)
if not hypo:
return
markers.setPropertyLabelVisible(entry_id, visible)
maestro.redraw()
# Set property to show detailed labels to the current state and update
if hypo.property.get(constants.PROP_LABELS_VISIBLE, None) != visible:
set_entry_property(entry_id, constants.PROP_LABELS_VISIBLE, visible)
[docs]def show_phase_hover_markers(pick_id):
"""
Show hover effect when mouse is hovered on a phase marker.
:param pick_id: Phase marker pick id
:type pick_id: int
"""
markers = get_phase_markers()
try:
marker = markers.getFeatureFromId(pick_id)
except ValueError:
# The marker may have been deleted, but it is still emitting
# phaseMarkerHoverEnter signals. This except block can hopefully be
# removed when MAE-44134 is addressed. (This may also apply to
# hide_phase_hover_markers().)
return
if marker.allow_picking:
marker.setHovered(True)
[docs]def hide_phase_hover_markers(pick_id):
"""
Remove hover effect when mouse is moved away from a phase marker.
:param pick_id: Phase marker pick id
:type pick_id: int
"""
markers = get_phase_markers()
try:
marker = markers.getFeatureFromId(pick_id)
if marker.allow_picking:
marker.setHovered(False)
except ValueError:
# it is possible that feature was removed while mouse was still
# hovering over it. This would result in a ValueError exception,
# which we can ignore
pass