Source code for schrodinger.ui.qt.dendrogram

import itertools
import math
import re
from collections import deque
from past.utils import old_div

from schrodinger.Qt import QtCore
from schrodinger.Qt.QtWidgets import QCheckBox
from schrodinger.Qt.QtWidgets import QComboBox
from schrodinger.Qt.QtWidgets import QDoubleSpinBox
from schrodinger.Qt.QtWidgets import QHBoxLayout
from schrodinger.Qt.QtWidgets import QLabel
from schrodinger.Qt.QtWidgets import QVBoxLayout
from schrodinger.Qt.QtWidgets import QWidget
from schrodinger.ui import dendrogram as dendro


[docs]def makeListOf(input): if isinstance(input, str): input = [input] else: try: iter(input) except TypeError: input = [input] else: input = list(input) return input
[docs]class DendrogramSyncer:
[docs] def __init__(self): self._dendrograms = [] self._views = []
[docs] def addTree(self, tree): self._dendrograms.append(tree) tree.m_graphicTree.aboutToUpdatePositions.connect( self.respondToUpdatePositions) tree.m_graphicTree.aboutToUpdatePositions.connect( self.respondToChangeSelection) tree.m_graphicTree.selectionChanged.connect( lambda: self.respondToChangeSelection(tree.m_graphicTree))
[docs] def removeTree(self, tree): self._dendrograms.remove(tree)
[docs] def respondToChangeSelection(self, graphicTree): if (len(self._dendrograms) < 2): return modifiedGraphicTree = graphicTree otherTrees = [ x.m_graphicTree for x in self._dendrograms if x.m_graphicTree != modifiedGraphicTree ] for tree in otherTrees: tree.copySelectionFrom(graphicTree)
[docs] def respondToUpdatePositions(self, graphicTree): if (len(self._dendrograms) < 2): return modifiedGraphicTree = graphicTree otherTrees = [ x.m_graphicTree for x in self._dendrograms if x.m_graphicTree != modifiedGraphicTree ] iterators = [] iterators.append((modifiedGraphicTree, dendro.DendrogramNodeBFIterator( modifiedGraphicTree.getFirstNode()))) for tree in otherTrees: iterators.append( (tree, dendro.DendrogramNodeBFIterator(tree.getFirstNode()))) while (iterators[0][1].node() is not None): graphNode = modifiedGraphicTree.getNode(iterators[0][1].node()) for otherTree in iterators[1:]: otherGraphNode = otherTree[0].getNode(otherTree[1].node()) otherGraphNode.setPos(graphNode.pos()) otherGraphNode.setCoordinates(graphNode.getCoordinates()) otherGraphNode.setVisible(graphNode.isVisible()) otherGraphNode.getLabel().setVisible( graphNode.getLabel().isVisible()) otherGraphNode.m_hideAfterAnimation = graphNode.m_hideAfterAnimation for it in iterators: it[1].next() # nopy3migrate for tree in otherTrees: tree.updatePositions(False)
[docs]class NewickNode: """ Temporary node structure. """
[docs] def __init__(self): self.children = [] self.parent = None self.leaf = [] self.label = '' self.distance = 1
[docs] def addChild(self, node): self.children.append(node) node.parent = self
[docs]def assignLeaves(node): """ Walk the tree from leaves to root, storing on each node all the leaves of its leaves """ nodes = [] queue = deque([]) queue.append(node) while (queue): thisNode = queue.popleft() for child in thisNode.children: queue.append(child) nodes.append(thisNode) for node in reversed(nodes): if (node.parent): node.parent.leaf += node.leaf
[docs]def buildTree(node): """ This recursive function builds hierarchy of `DendrogramNode` nodes by traversing passed tree, and returns dendrogram root node. """ child_nodes = [] for child in node.children: child_nodes.append(buildTree(child)) dendro_node = dendro.DendrogramNode() dendro_node.setDistance(node.distance) dendro_node.setLabel(node.label) for child in child_nodes: dendro_node.addChild(child) for leaf in node.leaf: dendro_node.setLeaf(leaf) return dendro_node
[docs]def parseNewickString(dndstring): """ Parses a Newick-formatted string and generates dendrogram tree. :rtype: `DendroGraphicalTree` :return: Graphic dendrogram tree. """ tree = root = None leaf_count = 0 # parse DND string while dndstring: c = dndstring[0] if c.isspace() or c == ',': # skip whitespace pass elif c == ';': # end of string break elif c == '(': # open a branch node = NewickNode() if root is None: # store root node root = node if tree: tree.addChild(node) node.parent = tree tree = node elif c == ')': # close a branch if len(dndstring) > 2 and dndstring[1] == ':': # extract branch distance (this should be split out # into a function) end = re.search(r',|\)|;', dndstring[1:]).start() dist = dndstring[2:end + 1] tree.distance = float(dist) dndstring = dndstring[end:] if tree: tree = tree.parent # get a parent node else: # parse branch # get text between this position and ')' or ',' end = re.search(r',|\)|;', dndstring).start() label_dist = dndstring[:end].split( ':') # extract label and distance label = label_dist[0] dist = 0.0 if len(label_dist) > 1: dist = float(label_dist[1]) # Create a new leaf node. node = NewickNode() node.label = label node.leaf.append(leaf_count) # node.distance = dist leaf_count += 1 tree.addChild(node) dndstring = dndstring[end - 1:] # process next character dndstring = dndstring[1:] assignLeaves(root) # traverse the tree bottom-up and build dendrogram dendrogram = buildTree(root) return dendrogram
[docs]class GuiPanel(QWidget):
[docs] def __init__(self, dendr): self._dendro = dendr self._preferences = dendro.DendrogramGraphicTreeCoordinatesPreferences() super(GuiPanel, self).__init__() super(GuiPanel, self).setLayout(QVBoxLayout()) self.combobox = QComboBox() self.combobox.addItem("linear", "linear") self.combobox.addItem("logarithmic", "log") self.rectCB = QCheckBox("Rectangular") self.sr = QCheckBox("Self-reorganize") self.layout().addWidget(self.combobox) self.combobox.currentIndexChanged.connect(self.updateCoordinates) self.sf = self.addFloatSpinBox("scale factor", 0, 100000) self.ml = self.addFloatSpinBox("minimum length") self.cl = self.addFloatSpinBox("cutoff length") self.ta = self.addFloatSpinBox("total angle", 0, 360) self.ct = self.addFloatSpinBox("color threshold", 0, 100000) self.ta.setValue(360) self.sf.setValue(100) self.cl.setValue(200) self.ml.setValue(30) self.bt = self.addFloatSpinBox("branch thickness", 0, 8.0) self.bt.valueChanged.connect(self.changeThickness) self.bt.setValue(0) self.ct.valueChanged.connect(self.colorByThreshold) self.layout().addWidget(self.rectCB) self.rectCB.stateChanged.connect(self.updateCoordinates) self.layout().addWidget(self.sr) self.sr.stateChanged.connect(self.updateCoordinates) self.changeThickness() self.updateCoordinates()
[docs] def addFloatSpinBox(self, name, min=0, max=1000): widget = QWidget() widget.setLayout(QHBoxLayout()) self.layout().addWidget(widget) widget.layout().addWidget(QLabel(name)) spinbox = QDoubleSpinBox() spinbox.setRange(min, max) spinbox.valueChanged.connect(self.updateCoordinates) widget.layout().addWidget(spinbox) return spinbox
[docs] def changeThickness(self): dendros = makeListOf(self._dendro) for tree in dendros: nodeIter = dendro.DendrogramNodeBFIterator( tree.m_graphicTree.getFirstNode()) while (nodeIter.node() is not None): node = tree.m_graphicTree.getNode(nodeIter.node()) if (self.bt.value() < 1): m = 0.5 M = 6 width = max(old_div(len(node.getLeafIndices()), 5.), m) width = min(M, width) node.setBranchWidth(width) else: node.setBranchWidth(self.bt.value()) nodeIter.next() # nopy3migrate
[docs] def changeScaling(self): self._preferences.logScale = (self.combobox.currentIndex() == 1) self._preferences.scaleFactor = self.sf.value() self._preferences.minimumLength = self.ml.value() self._preferences.cutoffLength = self.cl.value() self._preferences.totalAngle = self.ta.value() * math.pi / 180 self._preferences.rectangular = self.rectCB.isChecked() self._preferences.selfReorganize = self.sr.isChecked()
[docs] def updateCoordinates(self): self.changeScaling() dendros = makeListOf(self._dendro) for tree in dendros: tree.m_graphicTree.initializeCoordinates(self._preferences) tree.m_graphicTree.updatePositions()
[docs] def colorByThreshold(self, threshold): dendros = makeListOf(self._dendro) for tree in dendros: tree.m_graphicTree.assignColorsWithThreshold(threshold)
[docs]class Dendrogram(QtCore.QObject): selectionChanged = QtCore.pyqtSignal(set)
[docs] def __init__(self, ViewCls=None): super().__init__() if ViewCls is None: ViewCls = dendro.DendrogramView self._ViewCls = ViewCls self.leaves = [] self.m_graphicTree = None
[docs] def nodes(self): return self.m_graphicTree.listAllNodes()
[docs] def setLeaves(self, key, lis): self.initializeLeaves(lis) self.addLeafProperty(key, lis)
[docs] def isVisible(self): return self.m_graphicTree is not None and self.m_graphicTree.isVisible()
[docs] def initializeLeaves(self, lis): self.leaves = [] for entry in lis: self.leaves.append({})
[docs] def addLeafProperty(self, key, lis): if (len(lis) == len(self.leaves)): for index, entry in enumerate(lis): self.leaves[index][key] = entry
[docs] def getProperty(self, key, index): return self.leaves[index][key]
[docs] def loadTreeFromNewick(self, dndstring): self.m_tree = parseNewickString(dndstring)
[docs] def buildTree(self, similarityFunction): builder = dendro.DendrogramTreeBuilder() size = len(self.leaves) builder.initializeWithSize(size) for combination in itertools.combinations(list(range(size)), 2): element1 = self.leaves[combination[0]] element2 = self.leaves[combination[1]] distance = similarityFunction(element1, element2) builder.setDistance(*(combination + (distance,))) self.m_tree = builder.buildTree()
[docs] def runOnEachGraphicNode(self, function): for node in self.nodes(): function(self, node)
[docs] def showTree(self, initializeFunction, pref=None): """ Show the tree as per the options in the pref. :param initializeFunction: Method to call on all node. :type initializeFunction: Method :param pref: options to display the dendrogram :type pref: schrodinger.ui.dendrogram.DendrogramGraphicTreeCoordinatesPreferences :return: Dendrogram view. :rtype: schrodinger.application.msv.gui.dendrogram_viewer.MSVDendrogramView """ self.m_graphicTree = dendro.DendrogramGraphicTree(self.m_tree) self.runOnEachGraphicNode(initializeFunction) if not pref: pref = dendro.DendrogramGraphicTreeCoordinatesPreferences() self.m_graphicTree.initializeCoordinates(pref) self.m_graphicTree.updatePositions(True, False) self.m_view = self._ViewCls() self.m_view.getScene().drawTree(self.m_graphicTree) return self.m_view
[docs] def showLeaves(self, number): nodes = self.m_graphicTree.listAllNodes() nodes[0].show() shownLeaves = 1 for node in nodes: children = node.getChildren() if not len(children): continue isVisible = (shownLeaves + len(children) - 1 <= number) if (isVisible): shownLeaves += len(children) - 1 for child in children: child.setVisible(isVisible) self.m_graphicTree.displayLabels()
[docs] def getoptionsGUI(self): widget = GuiPanel(self) widget.show() return widget