# --- UCSF Chimera Copyright --- # Copyright (c) 2000-2006 Regents of the University of California. # All rights reserved. This software provided pursuant to a # license agreement containing restrictions on its disclosure, # duplication and use. This notice must be embedded in or # attached to all copies, including partial copies, of the # software or any revisions or derivations thereof. # --- UCSF Chimera Copyright --- # # $Id: gui.py 39883 2014-06-03 22:00:38Z pett $ import chimera from chimera.baseDialog import ModelessDialog from chimera import UserError, replyobj from prefs import prefs from SimpleSession import SAVE_SESSION, registerAttribute def processModBaseID(IDcode, ignore_cache=False): """Locate a database ID code via ModBase, read it, and add it to the list of open models. _openModBaseIDModel(IDcode) => [model(s)] 'explodeNMR' controls whether multi-MODEL files are split into multiple Molecules (if False, use coord sets instead) """ identifyAs = IDcode from chimera import replyobj statusName = identifyAs or IDcode path = fetchModBase(IDcode, ignore_cache=ignore_cache) # Open PDB file as models from chimera import PDBio import os pdbio = PDBio() pdbio.explodeNMR = False molList = pdbio.readPDBfile(path) if not pdbio.ok(): replyobj.status("") raise UserError("Error reading PDB file: %s" % pdbio.error()) for m in molList: m.name = identifyAs # Post-process models to convert remark records # into molecule dictionary attribute #from baseDialog import buttonFuncName as makeIdentifier for m in molList: attr = {} remarks = m.pdbHeaders.get("REMARK", []) for remark in remarks: parts = remark.split(None, 2) try: info = parts[2] except IndexError: continue try: key, value = [ v.strip() for v in info.split(':', 1) ] except ValueError: continue try: value = int(value) except ValueError: try: value = float(value) except ValueError: pass attr[key] = value #setattr(m, "modbase_%s" % makeIdentifier(key), value) assignModbaseInfo(m, attr) # Register open message chimera._openedInfo = "Opened %s" % statusName ModBaseDialog(IDcode, molList) return molList def fetchModBase(IDcode, ignore_cache=False): """Fetch the output from ModBase and fix it up since the generated file (illegally) contains multiple XML tags at the document level""" from chimera import fetch if not ignore_cache: path = fetch.fetch_local_file('ModBase', IDcode + '.pdb') if path: return path from urllib import FancyURLopener class Wget(FancyURLopener): version = "Wget/1.10.2" f = Wget().open("http://salilab.org/modbase/retrieve/modbase" "?databaseID=%s" % IDcode) from OpenSave import osTemporaryFile filename = osTemporaryFile() tf = open(filename, "w") tf.write(f.readline()) tf.write("\n") tf.write(f.read()) tf.write("\n") tf.close() f.close() # Parse the XML file and write out # a temporary PDB file import xml.sax, xml.sax.handler from xml.sax import SAXException from xml.sax.handler import ContentHandler class Handler(ContentHandler): def __init__(self, *args, **kw): ContentHandler.__init__(self, *args, **kw) self.pdbContent = [] self.pdbActive = False def pdbFile(self): return ''.join(self.pdbContent).strip() def startElement(self, name, attrs): self.pdbActive = (name == "content") ContentHandler.startElement(self, name, attrs) def endElement(self, name): if name == "content": self.pdbActive = False ContentHandler.endElement(self, name) def characters(self, content): if self.pdbActive: self.pdbContent.append(content) ContentHandler.characters(self, content) handler = Handler() try: xml.sax.parse(filename, handler) except SAXException: raise UserError("No matching ModBase entry found for %s" % IDcode) content = handler.pdbFile() del handler if not content: raise UserError("No ModBase structure found for %s" % IDcode) f = open(filename, "w") print >> f, content f.close() del content spath = fetch.save_fetched_file(filename, 'ModBase', IDcode + '.pdb') if spath: return spath return filename class ModBaseDialog(ModelessDialog): buttons = ( "Hide", "Quit" ) help = "UsersGuide/modbase.html" provideStatus = True statusPosition = "left" def __init__(self, name, molList, tableData=None, alignment=None): if name: if name == "ModBase: Modeller Results": name = "Modeller Results" # for previous bug if name.startswith("ModBase: ") or name.startswith("Modeller Results"): self.title = name else: self.title = "ModBase: %s" % name else: self.title = "Modeller Results" self.molList = molList self.tableData = tableData self.alignment = alignment ModelessDialog.__init__(self) self.closeHandler = chimera.openModels.addRemoveHandler( self._modelClosedCB, None) self.selHandler = None self.sesHandler = chimera.triggers.addHandler( SAVE_SESSION, self._sessionCB, None) chimera.extension.manager.registerInstance(self) def fillInUI(self, parent): import Tkinter top = parent.winfo_toplevel() menubar = Tkinter.Menu(top, type="menubar", tearoff=False) top.config(menu=menubar) self.columnMenu = Tkinter.Menu(menubar) menubar.add_cascade(label="Columns", menu=self.columnMenu) fetchMenu = Tkinter.Menu(menubar) menubar.add_cascade(label="Fetch Scores", menu=fetchMenu) fetchMenu.add_command(label="zDOPE and Estimated RMSD/Overlap", command=self.fetchModbaseScores) from chimera.tkgui import aquaMenuBar aquaMenuBar(menubar, parent, pack = 'top') self._makeActionGroup(parent) from CGLtk.Table import SortableTable from prefs import colAttr, colOrder, prefs, defaults, colOrderModellerResults self.modBaseTable = SortableTable(parent, menuInfo=( self.columnMenu, prefs, defaults, False )) if not self.tableData: self._addColumn("Model", "lambda m: m.oslIdent()", format="%s", shown=True) if self.title == "Modeller Results": order = colOrderModellerResults else: order = colOrder for fieldName in order: keyName, format = colAttr[fieldName] self._addColumn(fieldName, "lambda m: m.modbaseInfo.get('%s', None)" % keyName, format=format) self.modBaseTable.setData(self.molList) chimera.triggers.addHandler("post-frame", self._launchTable, None) self.modBaseTable.pack(expand=True, fill="both") def fetchModbaseScores(self, modkey=None): from MultAlignViewer.prefs import prefs, MODELLER_KEY if not modkey: modkey = prefs[MODELLER_KEY] if not modkey: modkey = ModKeyDialog(prefs[MODELLER_KEY]).run(self.uiMaster()) if modkey: prefs[MODELLER_KEY] = modkey if not modkey: self.status("No Modeller license key provided", color="red") return FetchScores(self.molList, self.alignment, modkey, self.modBaseTable, self.status) def _makeActionGroup(self, parent): from prefs import prefs from chimera import chimage import Tkinter, Pmw d = prefs.get("treatment", {}) self.treatmentShow = d.get("show", 0) selAtoms = d.get("selectAtoms", 0) selModels = d.get("selectModels", 0) hideOthers = d.get("hideOthers", 1) self.rightArrow = chimage.get("rightarrow.png", parent) self.downArrow = chimage.get("downarrow.png", parent) if self.treatmentShow: relief = "groove" image = self.downArrow else: relief = "flat" image = self.rightArrow self.treatmentGroup = Pmw.Group(parent, collapsedsize=0, tagindent=0, ring_relief=relief, tag_pyclass=Tkinter.Button, tag_text=" Treatment of Chosen Models", tag_relief="flat", tag_compound="left", tag_image=image, tag_command=self._treatmentCB) if not self.treatmentShow: self.treatmentGroup.collapse() self.treatmentGroup.pack(side="top", fill="x", padx=3) interior = self.treatmentGroup.interior() self.treatmentSelAtom = Tkinter.IntVar(parent) self.treatmentSelAtom.set(selAtoms) b = Tkinter.Checkbutton(interior, text="Select atoms", onvalue=1, offvalue=0, variable=self.treatmentSelAtom, command=self._treatmentChangedCB) b.pack(side="left") self.treatmentSelModel = Tkinter.IntVar(parent) self.treatmentSelModel.set(selModels) b = Tkinter.Checkbutton(interior, text="Choose in Model Panel", onvalue=1, offvalue=0, variable=self.treatmentSelModel, command=self._treatmentChangedCB) b.pack(side="left") self.treatmentHideOthers = Tkinter.IntVar(parent) self.treatmentHideOthers.set(hideOthers) b = Tkinter.Checkbutton(interior, text="Hide others", onvalue=1, offvalue=0, variable=self.treatmentHideOthers, command=self._treatmentChangedCB) b.pack(side="left") def _treatmentCB(self): self.treatmentShow = not self.treatmentShow if self.treatmentShow: self.treatmentGroup.configure(ring_relief="groove", tag_image=self.downArrow) self.treatmentGroup.expand() else: self.treatmentGroup.configure(ring_relief="flat", tag_image=self.rightArrow) self.treatmentGroup.collapse() self._savePrefs() def _addColumn(self, title, attrFetch, format="%s", shown=None): if title in [c.title for c in self.modBaseTable.columns]: return if format[-1] == "f": # try to align decimal points kw = {'font': 'TkFixedFont'} else: kw = {} c = self.modBaseTable.addColumn(title, attrFetch, format=format, display=shown, **kw) self.modBaseTable.columnUpdate(c) def _launchTable(self, trigger, closure, mols): # There may be a small window where the dialog # can be destroyed before _launchTable gets # called in the post-frame trigger. if self.modBaseTable: self.modBaseTable.launch(browseCmd=self._selectModelCB, restoreInfo=self.tableData) self.selHandler = chimera.triggers.addHandler( "selection changed", self._selectionChangedCB, None) return chimera.triggerSet.ONESHOT def _modelClosedCB(self, trigger, closure, mols): remainder = [ m for m in self.molList if m not in mols ] if len(remainder) == 0: self.molList = [] self.exit() elif len(remainder) != len(self.molList): self.molList = remainder self.modBaseTable.setData(self.molList) self.modBaseTable.refresh(rebuild=True) def _selectionChangedCB(self, trigger, closure, ignore): from chimera import selection mols = selection.currentMolecules() selected = [ m for m in mols if m in self.molList ] self.modBaseTable.highlight(selected) def _selectModelCB(self, tableSel): if self.treatmentSelAtom.get(): from chimera import selection selection.clearCurrent() selection.addCurrent(tableSel) selection.addImpliedCurrent() if self.treatmentSelModel.get(): from ModelPanel import ModelPanel from chimera import dialogs d = dialogs.display(ModelPanel.name) d.selectionChange(tableSel) shown = {} if self.treatmentHideOthers.get(): for m in self.molList: key = (m.id, m.subid) shown[key] = m in tableSel or not tableSel else: for m in tableSel: key = (m.id, m.subid) shown[key] = True for m in chimera.openModels.list(): key = (m.id, m.subid) try: m.display = shown[key] except KeyError: pass def _treatmentChangedCB(self): self._selectModelCB(self.modBaseTable.selected()) self._savePrefs() def _savePrefs(self): from prefs import prefs prefs["treatment"] = { "show": self.treatmentShow, "selectAtoms": self.treatmentSelAtom.get(), "selectModels": self.treatmentSelModel.get(), "hideOthers": self.treatmentHideOthers.get(), } prefs.save() def _sessionCB(self, trigger, myData, sesFile): from SimpleSession import sessionID data = (1, # version self.title, # title [ sessionID(m) for m in self.molList ], # molecules [ m.modbaseInfo for m in self.molList ],# stats self.modBaseTable.getRestoreInfo()) # GUI print >> sesFile, """ try: from ModBase.gui import sessionRestore sessionRestore(%s) except: reportRestoreError("Error restoring ModBase") """ % repr(data) def exit(self): if self.molList: molList = [] for m in self.molList: molList.extend(chimera.openModels.list( m.id, m.subid)) chimera.openModels.close(molList) if self.closeHandler: chimera.openModels.deleteRemoveHandler( self.closeHandler) self.closeHandler = None if self.selHandler: chimera.triggers.deleteHandler("selection changed", self.selHandler) self.selHandler = None if self.sesHandler: chimera.triggers.deleteHandler(SAVE_SESSION, self.sesHandler) self.sesHandler = None chimera.extension.manager.deregisterInstance(self) self.destroy() self.modBaseTable = None def emName(self): return self.title def emRaise(self): self.enter() def emHide(self): self.Close() Hide = emHide def emQuit(self): self.exit() Quit = emQuit def addScoreColumn(self, scoreName): attrFetch = "lambda m: m.modbaseInfo.get('%s', None)" % scoreName self._addColumn(scoreName, attrFetch, format="%.2f", shown=True) def hideEmptyColumns(self): for col in self.modBaseTable.columns: for mol in self.molList: if col.displayValue(mol): break else: self.modBaseTable.columnUpdate(col, display=False) def assignModbaseInfo(m, info): from prefs import attrMap from SimpleSession.save import registerAttribute m.modbaseInfo = info for k, v in info.iteritems(): try: attrName = attrMap[k] except KeyError: pass else: setattr(m, attrName, v) registerAttribute(m.__class__, attrName) def sessionRestore(sessionData): from SimpleSession import idLookup version = sessionData[0] if version == 1: ignore, name, molIdList, infoList, tableData = sessionData molList = [ idLookup(mid) for mid in molIdList ] for m, info in zip(molList, infoList): assignModbaseInfo(m, info) else: raise ValueError("unknown ModBase version: %s" % str(version)) ModBaseDialog(name, molList, tableData=tableData) from chimera.baseDialog import ModalDialog class ModKeyDialog(ModalDialog): buttons = ('OK', 'Cancel') default = 'OK' help = "UsersGuide/modbase.html#fetchscores" def __init__(self, initialKey): self.initialKey = initialKey ModalDialog.__init__(self) def fillInUI(self, parent): from chimera.HtmlText import HtmlText ht = HtmlText(parent, relief='flat', width=30, height=3, wrap="word") ht.grid(row=0, column=0, columnspan=2) ht.insert('0.0', """The SaliLab Model Evaluation Server requires a Modeller license key to access. Please enter a valid key.""") from chimera.tkoptions import StringOption self.keyOption = StringOption(parent, 1, "Modeller license key", self.initialKey, None) def OK(self): self.Cancel(value=self.keyOption.get()) class FetchScores: def __init__(self, allMols, alignment, modkey, table, status): from prefs import attrMap, ZDOPE_PDB, TSV_RMSD_PDB, TSV_OVERLAP_PDB, \ ZDOPE_COL, TSV_RMSD_COL, TSV_OVERLAP_COL neededAttrs = [attrMap[attr] for attr in (ZDOPE_PDB, TSV_RMSD_PDB, TSV_OVERLAP_PDB)] for col in table.columns: if not col.display and col.title in ( ZDOPE_COL, TSV_RMSD_COL, TSV_OVERLAP_COL): table.columnUpdate(col, display=True) status("Initiating %s score requests to Modeller evaluation server" % len(allMols)) self.numRemaining = len(allMols) self.table = table self.status = status self.fetches = [ FetchScore(m, alignment, modkey, status, self._fsDoneCB) for m in allMols] def _fsDoneCB(self): self.table.refresh() self.numRemaining -= 1 if not self.numRemaining: self.status("Scores updated") self.fetches = None class FetchScore: Hostname = "modbase.compbio.ucsf.edu" def __init__(self, model, alignment, modKey, status, doneCB): from WebServices import httpq hq = httpq.get() self.slot = hq.newSlot(self.Hostname) self.model = model self.alignment = alignment self.modKey = modKey self.status = status self.doneCB = doneCB self.task = None from StringIO import StringIO io = StringIO() chimera.pdbWrite([self.model], chimera.Xform(), io) fileContents = io.getvalue() io.close() self.slot.request(self._submitJob, fileContents) def _submitJob(self, q, fileContents): from CGLutil.multipart import post_multipart fields = [("name", None, "chimera-ModBase"), ("modkey", None, self.modKey), # since only GS341 score needs seq_ident and we don't need that score... ("seq_ident", None, "100")] if self.alignment: fields.append(("alignment_file", None, self.alignment)) from xml.dom.minidom import parseString fields.append(("model_file", self.model.name+".pdb", fileContents)) out = post_multipart(self.Hostname, "/modeval/job", fields) dom = parseString(out) top = dom.getElementsByTagName('saliweb')[0] for results in top.getElementsByTagName('job'): self.url = results.getAttribute('xlink:href') dom.unlink() break else: dom.unlink() q.put(self._submitFailed) return q.put(self._submitSucceeded) def _submitFailed(self): self.status("Cannot submit evaluation job for " + self.model.name) self._done() def _submitSucceeded(self): from chimera.tasks import Task self.task = Task("Modeller score for %s" % self.model, self._cancelCB) self.task.updateStatus("score computation job submitted") self.slot.request(self._updateStatus) def _cancelCB(self): self._done() def _updateStatus(self, q): if self.model.__destroyed__: q.put(self._modelClosed) return import urllib2 try: u = urllib2.urlopen(self.url) except urllib2.HTTPError, detail: if detail.code == 503: import time time.sleep(5) q.put(self._notFinished) else: q.put(self._requestFailed) return from xml.dom.minidom import parseString dom = parseString(u.read()) top = dom.getElementsByTagName('saliweb')[0] from prefs import ZDOPE_PDB, attrMap, TSV_RMSD_PDB, TSV_OVERLAP_PDB for results in top.getElementsByTagName('results_file'): url = results.getAttribute('xlink:href') if "evaluation.xml" in url: u = urllib2.urlopen(url) dom2 = parseString(u.read()) zdope = dom2.getElementsByTagName("zdope")[0] tsvRmsd = dom2.getElementsByTagName("predicted_rmsd")[0] tsvOverlap = dom2.getElementsByTagName("predicted_no35")[0] for node, pdb in zip((zdope, tsvRmsd, tsvOverlap), (ZDOPE_PDB, TSV_RMSD_PDB, TSV_OVERLAP_PDB)): val = float(node.firstChild.nodeValue.strip()) self.model.modbaseInfo[pdb] = val setattr(self.model, attrMap[pdb], val) dom2.unlink() dom.unlink() q.put(self._finished) def _modelClosed(self): self.task.updateStatus("model closed") self._done() def _notFinished(self): if self.task: self.task.updateStatus("computing scores") if self.slot: self.slot.request(self._updateStatus) def _requestFailed(self): self.task.updateStatus("computation failed") self._done() def _finished(self): self.task.updateStatus("score updated") self._done() def _done(self): if self.task: self.task.finished() self.task = None if self.slot: self.slot.finished() self.slot = None self.doneCB()