source: orange-bioinformatics/orangecontrib/bio/widgets/OWMoleculeVisualizer.py @ 1874:b3e32cc5cf6f

Revision 1874:b3e32cc5cf6f, 35.1 KB checked in by Ales Erjavec <ales.erjavec@…>, 6 months ago (diff)

Added new style widget meta descriptions.

RevLine 
[391]1"""<name>Molecule Visualizer</name>
2<description>Rendering of 2D structure of molecules based on their SMILES description.</description>
[1726]3<icon>icons/MoleculeVisualizer.svg</icon>
[391]4<contact>Ales Erjavec (ales.erjavec(@at@)fri.uni-lj.si)</contact> 
[1052]5<priority>2050</priority>
[391]6"""
7
[1632]8from __future__ import absolute_import, with_statement
9
10import sys, os, urllib2, urllib
11import warnings
12import shelve
13from cStringIO import StringIO
14import pickle
15
16from PyQt4.QtSvg import *
[426]17
[391]18import orange
[1632]19from Orange.orng import orngEnviron
20from Orange.OrangeWidgets import OWGUI
21from Orange.OrangeWidgets.OWWidget import *
[398]22
[1632]23from .. import obiChem
[391]24
[1874]25NAME = "Molecule Visualizer"
26DESCRIPTION = "Rendering of 2D structure of molecules based on their SMILES description."
27ICON = "icons/MoleculeVisualizer.svg"
28PRIORITY = 2050
29
30INPUTS = [("Molecules", Orange.data.Table, "setMoleculeTable", Default),
31          ("Molecule subset", Orange.data.Table, "setMoleculeSubset"),
32          ("Fragments", Orange.data.Table, "setFragmentTable")]
33OUTPUTS = [("Selected Molecules", Orange.data.Table)]
34
35REPLACES = ["_bioinformatics.widgets.OWMoleculeVisualizer.OWMoleculeVisualizer"]
36
37
[949]38class dummy_module(object):
39    def __init__(self, name):
40        self.name = name
41    def __getattr__(self, name):
42        raise ImportError(self.name)
43    def __nonzero__(self):
44        return False
45
46try:
47    import pybel
48except ImportError:
49    pybel = dummy_module("pybel")
[1145]50except Exception, ex:
[1318]51    pybel = dummy_module("pybel")
[607]52
53svg_error_string = """<?xml version="1.0" ?>
54<svg height="185" version="1.0" width="250" xmlns="http://www.w3.org/2000/svg">
55    <g stroke="#000" stroke-width="1.0">
56        <text font-family="Arial" font-size="16" stroke="rgb(0, 0, 0)" x="0.0" y="16.0">
57            %s
58        </text>
59    </g>
60</svg>
61"""
62
63oasaLocal = True
[1337]64
65#  try import from bkchem
[391]66try:
[1337]67    from bkchem import oasa
[391]68except ImportError:
[1337]69    #  try stanalone import
70    try:
71        import oasa
72    except ImportError:
73        oasaLocal = False
[608]74
[1318]75if oasaLocal and pybel:
[608]76    def mol_to_svg(molSmiles, fragSmiles):
77        s = StringIO()
78        obiChem.mol_to_svg(molSmiles, fragSmiles, s)
79        s.seek(0)
80        return s.read()
81else:
82    def mol_to_svg(molSmiles, fragSmiles):
[659]83        params = urllib.urlencode({'molSmiles': molSmiles, 'fragSmiles': fragSmiles})
84        f = urllib2.urlopen("http://asterix.fri.uni-lj.si/misc/bkchem/drawMol_oasa.py", params)
[608]85        return f.read()
[391]86 
87class DrawContext(object):
88    def __init__(self, molecule="", fragment="", size=200, imageprefix="", imagename="", title="", grayedBackground=False, useCached=False):
89        self.molecule=molecule
90        self.fragment=fragment
91        self.size=size
92        self.imageprefix=imageprefix
93        self.imagename=imagename
94        self.title=title
95        self.grayedBackground=grayedBackground
96        self.useCached=useCached
97
98    def __hash__(self):
99        return (self.molecule+"%%"+self.fragment+"%%"+str(self.size)+"%%"+self.title+str(self.grayedBackground)).__hash__()
100
101    def __eq__(self, other):
102        return self.molecule==other.molecule and self.fragment==other.fragment and \
103               self.size==other.size and self.title==other.title and self.grayedBackground==other.grayedBackground
104
105    def __ne__(self, other):
106        return not self==other
107           
[607]108from threading import RLock
[391]109
110def synchronized(lock):
111    def syncfunc(func):
112        def f(*args, **kw):
[426]113            with lock:
114                ret = func(*args, **kw)
[391]115            return ret
116        return f
117    return syncfunc
118
[607]119class ImageCache(object):
120    __shared_state = {"shelve": None}
121    lock = RLock()
[608]122    @synchronized(lock)
[607]123    def __init__(self):
124        self.__dict__ = self.__shared_state
[1318]125        if self.shelve is None:
[949]126            try:
[1318]127                os.mkdir(os.path.join(orngEnviron.bufferDir, "molimages"))
[949]128            except OSError:
[1318]129                pass
130            try:
131                self.shelve = shelve.open(os.path.join(orngEnviron.bufferDir, "molimages", "cache.shelve"))
132            except Exception, ex:
133                warnings.warn("Cannot open molecule images cache! " + str(ex))
134                self.shelve = {}
135   
136   
[607]137    @synchronized(lock)
138    def __getitem__(self, key):
139        val_str = str(key)
140        if val_str in self.shelve:
141            return self.shelve[val_str]
142        else:
143            mol_smiles = key[0]
144            frag_smiles = key[1] if len(key) > 1 else None
[608]145            self.shelve[val_str] = mol_to_svg(mol_smiles, frag_smiles)
[607]146            return self.shelve[val_str]
[391]147
[607]148    @synchronized(lock)
149    def __setitem__(self, key, value):
[608]150        if len(self.shelve) > 1000:
151            self.sync()
[607]152        self.shelve[str(key)] = value
[391]153
[607]154           
155    @synchronized(lock)
156    def __contains__(self, key):
157        return str(key) in self.shelve
158   
159    @synchronized(lock)
160    def sync(self):
161        if len(self.shelve.keys()) > 1000:
162            for key in self.shelve.keys()[:-900]:
163                del self.shelve[key]
[1318]164        if hasattr(self.shelve, "sync"):
165            self.shelve.sync()
[391]166
[607]167    def __del__(self):
[1318]168        if hasattr(self.shelve, " sync"):
169            self.sync()
[391]170
[398]171class MolWidget(QFrame):
[607]172    def setSelected(self, val):
173        self.image.selected = val
174    def getSelected(self):
175        return self.image.selected
176    selected = property(getSelected, setSelected)
[608]177
[391]178    def __init__(self, master, parent, context):
[398]179        QFrame.__init__(self, parent)
[391]180        self.master=master
181        self.context=context
[398]182        self.label=QLabel()
[426]183        self.label.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)
[607]184        try:
[949]185            self.from_cache = (context.molecule, context.fragment) in ImageCache()
186            s = ImageCache()[context.molecule, context.fragment]
187            self.state = 1
[607]188        except Exception, ex:
189            from traceback import print_exc
190            print_exc()
191            s = svg_error_string % "Error loading: "+str(ex)
[1318]192            self.from_cache = False
[607]193            self.state = 0
194        self.image=SVGImageThumbnail(s, self)
[426]195        self.image.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
[391]196        self.label.setText(context.title)
197        self.label.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
198        self.label.setMaximumWidth(context.size)
[398]199        layout = QVBoxLayout()
200        layout.addWidget(self.label)
201        layout.addWidget(self.image)
202        self.setLayout(layout)
[426]203        self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
[398]204        self.show()
[391]205
206    def repaint(self):
[398]207        QFrame.repaint(self)
[391]208        self.label.repaint()
209        self.image.repaint()
210       
[607]211class SVGImageThumbnail(QFrame):
212    def setSelected(self, val):
213        self._selected = val
214        self.update()
215    def getSelected(self):
216        return self._selected
217    selected = property(getSelected, setSelected)
218   
[608]219    @property
220    def highlight(self):
221        return self.parent().context.grayedBackground
222   
[607]223    def __init__(self, file, parent):
224        QWidget.__init__(self, parent)
225        self.doc = file if type(file) == str else file.read()
226        self.renderer = QSvgRenderer(QByteArray(self.doc))
227        self.buffer = None
228        self.resize(200, 200)
229        self.selected = False
[391]230       
[607]231    def paintEvent(self, event):
232        if not self.buffer or self.buffer.size() != self.size():
233            defSize = self.renderer.defaultSize()
[608]234            scaleFactor = float(self.width()) / max(defSize.width(), defSize.height())
[607]235            self.buffer = QImage(self.size(), QImage.Format_ARGB32_Premultiplied)
[608]236            self.buffer.fill(0)
[607]237            painter = QPainter(self.buffer)
238            painter.setViewport(self.width()/2 - scaleFactor*defSize.width()/2, self.height()/2 - scaleFactor*defSize.height()/2,
239                                scaleFactor*defSize.width(), scaleFactor*defSize.height())
240            self.renderer.render(painter)
[608]241        painter = QPainter(self)           
[607]242        painter.setBrush(QBrush(Qt.white))
243        painter.drawRect(0, 0, self.width(), self.height())
244        if self.selected:
245            painter.setPen(QPen(QBrush(Qt.red), 2))
[608]246        if self.highlight:
247            painter.setBrush(QBrush(Qt.gray,  Qt.FDiagPattern))
248        else:
[607]249            painter.setBrush(Qt.NoBrush)
[608]250        painter.drawRect(0, 0, self.width(), self.height())
251        painter.drawImage(0, 0, self.buffer)
252
253    def sizeHint(self):
254        return QSize(self.parent().master.imageSize, self.parent().master.imageSize)
[391]255
[607]256    def mouseDoubleClickEvent(self, event):
257        self._bigimage = BigSvgWidget()
258        self._bigimage.load(QByteArray(self.doc))
259        self._bigimage.show()
[391]260
261    def mousePressEvent(self, event):
[608]262        self.parent().master.mouseAction(self.parent(), event)
[391]263
[607]264class BigSvgWidget(QSvgWidget):
265    def paintEvent(self, event):
266        painter = QPainter(self)
267        painter.setBrush(QBrush(Qt.white))
268        painter.drawRect(0, 0, self.width(), self.height())
269        painter.end()
270        QSvgWidget.paintEvent(self, event)
[391]271       
[398]272class ScrollArea(QScrollArea):
[391]273    def __init__(self, master, *args):
[398]274        QScrollArea.__init__(self, *args)
[391]275        self.master=master
276        self.viewport().setMouseTracking(True)
277        self.setMouseTracking(True)
278       
279    def resizeEvent(self, event):
[398]280        QScrollArea.resizeEvent(self, event)
[608]281        size = event.size()
282        w, h=self.width(), self.height()
283        oldNumColumns = self.master.numColumns
284        numColumns = w / (self.master.imageSize + self.master.gridLayout.horizontalSpacing()*2 + 20) or 1
285        if numColumns != oldNumColumns:
286            self.master.numColumns = numColumns
[426]287            self.master.rearrangeLayout()
[391]288
289class OWMoleculeVisualizer(OWWidget):
[608]290    settingsList=["colorFragmets","showFragments"]
[391]291    contextHandlers={"":DomainContextHandler("", [ContextField("moleculeTitleAttributeList",
292                                                    DomainContextHandler.List + DomainContextHandler.SelectedRequired + DomainContextHandler.IncludeMetaAttributes,
293                                                    selected="selectedMoleculeTitleAttrs"),
294                                                  ContextField("moleculeSmilesAttr", DomainContextHandler.Required + DomainContextHandler.IncludeMetaAttributes)], maxAttributesToPickle=10000)} ##maxAttributesToPickle=10000 some bug in Context handler
295    def __init__(self, parent=None, signalManager=None, name="Molecule visualizer"):
[398]296        OWWidget.__init__(self, parent, signalManager, name)
[391]297        self.inputs=[("Molecules", ExampleTable, self.setMoleculeTable), ("Molecule subset", ExampleTable, self.setMoleculeSubset), ("Fragments", ExampleTable, self.setFragmentTable)]
298        self.outputs=[("Selected Molecules", ExampleTable)]
299        self.colorFragments=1
300        self.showFragments=0
301        self.selectedFragment=""
302        self.moleculeSmiles=[]
303        self.fragmentSmiles=[]
304        self.defFragmentSmiles=[]
305        self.moleculeSmilesAttr=0
306        self.moleculeTitleAttr=0
307        self.moleculeTitleAttributeList=[]
308        self.selectedMoleculeTitleAttrs=[]
309        self.fragmentSmilesAttr=0
310        self.imageSize=200
311        self.numColumns=4
312        self.commitOnChange=0
313        self.overRideCache=True
314        ##GUI
315        box=OWGUI.widgetBox(self.controlArea, "Info", addSpace = True)
316        self.infoLabel=OWGUI.label(box, self, "Chemicals: ")
[607]317##        if not oasaLocal:
[391]318##            OWGUI.label(box, self, "OpenEye not installed, access to server required.")
319##            self.serverInfo = OWGUI.label(box, self, "")
320        box=OWGUI.radioButtonsInBox(self.controlArea, self, "showFragments", ["Show molecules", "Show fragments"], "Show", callback=self.showImages)
321        self.showFragmentsRadioButton=box.buttons[-1]
322        self.markFragmentsCheckBox=OWGUI.checkBox(box, self, "colorFragments", "Mark fragments", callback=self.redrawImages)
323        box.setSizePolicy(QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Maximum))
324        OWGUI.separator(self.controlArea)
325        self.moleculeSmilesCombo=OWGUI.comboBox(self.controlArea, self, "moleculeSmilesAttr", "Molecule SMILES Attribute",callback=self.showImages)
326        self.moleculeSmilesCombo.box.setSizePolicy(QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Maximum))
327        OWGUI.separator(self.controlArea)
328##        self.moleculeTitleCombo=OWGUI.comboBox(self.controlArea, self, "moleculeTitleAttr", "Molecule title attribute", callback=self.redrawImages)
329        box=OWGUI.widgetBox(self.controlArea, "Molecule Title Attributes", addSpace = True)
330##        self.moleculeTitleListBox=QListBox(box)
331##        self.moleculeTitleListBox.setSelectionMode(QListBox.Extended)
332##        self.moleculeTitleListBox.setMinimumHeight(100)
333##        self.connect(self.moleculeTitleListBox, SIGNAL("selectionChanged()"), self.updateTitles)
[398]334        self.moleculeTitleListBox=OWGUI.listBox(box, self, "selectedMoleculeTitleAttrs", "moleculeTitleAttributeList", selectionMode = QListWidget.ExtendedSelection, callback=self.updateTitles)
[391]335        self.moleculeTitleListBox.setMinimumHeight(100)
336##        OWGUI.separator(self.controlArea)
337##        self.moleculeTitleCombo.box.setSizePolicy(QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Maximum))
338        OWGUI.separator(self.controlArea)
339        self.fragmentSmilesCombo=OWGUI.comboBox(self.controlArea, self, "fragmentSmilesAttr", "Fragment SMILES Attribute", callback=self.updateFragmentsListBox)
340        self.fragmentSmilesCombo.box.setSizePolicy(QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Maximum))
341        OWGUI.separator(self.controlArea)
342        box=OWGUI.spin(self.controlArea, self, "imageSize", 50, 500, 50, box="Image Size", callback=self.redrawImages, callbackOnReturn = True)
343        box.setSizePolicy(QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Maximum))
344        OWGUI.separator(self.controlArea)
345        box=OWGUI.widgetBox(self.controlArea,"Selection", addSpace = True)
346        OWGUI.checkBox(box, self, "commitOnChange", "Commit on change")
347        self.selectMarkedMoleculesButton=OWGUI.button(box, self, "Select &matched molecules", self.selectMarked)
348        OWGUI.button(box, self, "&Commit", callback=self.commit)
349        OWGUI.separator(self.controlArea)
[903]350        OWGUI.button(self.controlArea, self, "&Save to HTML", self.saveToHTML, debuggingEnabled = 0)
[391]351        OWGUI.rubber(self.controlArea)
352       
[398]353        spliter=QSplitter(Qt.Vertical)
354        self.scrollArea=ScrollArea(self, spliter)
[608]355       
[398]356        self.molWidget=QWidget()
357        self.scrollArea.setWidget(self.molWidget)
358        self.mainArea.layout().addWidget(spliter)
359        self.gridLayout=QGridLayout(self.molWidget)
360        self.molWidget.setLayout(self.gridLayout)
[608]361
[949]362        if pybel:
363            self.listBox=QListWidget(spliter)
364        else:
365            self.listBox=QListWidget(None)
366            self.listBox.setHidden(True)
[608]367
368        self.connect(self.listBox, SIGNAL("itemClicked(QListWidgetItem *)"), self.fragmentSelection)
[949]369       
370        self.fragmentSmilesCombo.box.setDisabled(not pybel)
[391]371
372        self.imageWidgets=[]
373        self.candidateMolSmilesAttr=[]
374        self.candidateMolTitleAttr=[None]
375        self.candidateFragSmilesAttr=[None]
376        self.molData=None
377        self.molSubset=[]
378        self.fragData=None
379        self.ctrlPressed=FALSE
[903]380        self.resize(800,600)
[391]381        self.listBox.setMaximumHeight(150)
382        self.fragmentSmilesCombo.setDisabled(True)
383        self.selectMarkedMoleculesButton.setDisabled(True)
384        self.markFragmentsCheckBox.setDisabled(True)
385        self.showFragmentsRadioButton.setDisabled(True)
386        self.loadSettings()
387        self.failedCount=0
388        self.fromCacheCount=0
389       
[949]390        if not pybel:
391            self.showFragments = 0
[1318]392            self.warning(10, "Pybel module not installed. To view molecule fragments\nplease install openbabel python extension.")
393        if not oasaLocal:
394            self.warning(11, "OASA module not installed. For faster local molecule\nrendering install OASA module (part of BKChem)")
[949]395       
[391]396    def setMoleculeTable(self, data):
397        self.closeContext()
398        self.molData=data
399        if data:
400            self.setMoleculeSmilesCombo()
401            self.setMoleculeTitleListBox()
402            self.setFragmentSmilesCombo()
403            self.updateFragmentsListBox()
404            if self.molSubset:
405                try:
406                    self.molSubset=self.molSubset.select(self.molData.domain)
407                except:
408                    self.molSubset=[]
409            tmp=self.moleculeTitleAttributeList
[608]410            self.openContext("", data)
[391]411            if tmp and not self.moleculeTitleAttributeList: ##openContext somtimes crashes internaly and silently clears title list
412                self.moleculeTitleAttributeList=tmp
413            self.showImages()
414        else:
415            self.moleculeSmilesCombo.clear()
416            self.moleculeTitleListBox.clear()
417            self.moleculeSmilesAttr=0
418            self.moleculeTitleAttributeList=[]
419            self.selectedMoleculeTitleAttrs=[]
420            self.defFragmentSmiles=[]
421            if not self.fragmentSmilesAttr:
422                self.listBox.clear()
423            self.destroyImageWidgets()
424            self.openContext("",data)
425            self.send("Selected Molecules", None)
[607]426   
[391]427    def setMoleculeSubset(self, data):
428        self.molSubset=data
429        try:
430            self.molSubset=self.molSubset.select(self.molData.domain)
431        except:
432            self.molSubset=[]
433        self.showImages()
434           
435    def setFragmentTable(self, data):
436        self.fragData=data
437        if data:
438            self.setFragmentSmilesCombo()
439            self.updateFragmentsListBox()
440            self.selectedFragment=""
441            self.showImages()
442        else:
443            self.setFragmentSmilesCombo()
444            #self.fragmentSmilesAttr=0
445            self.updateFragmentsListBox()
446            if self.showFragments:
447                self.destroyImageWidgets()
448        self.fragmentSmilesCombo.setDisabled(bool(data))
449
450    def filterSmilesVariables(self, data):
[949]451#       import pybel
452        candidates = data.domain.variables+data.domain.getmetas().values()
453        candidates = filter(lambda v:v.varType==orange.VarTypes.Discrete or v.varType==orange.VarTypes.String, candidates)
[391]454        if len(data)>20:
455            data=data.select(orange.MakeRandomIndices2(data, 20))
456        vars=[]
[607]457        def isValidSmiles(s):
458            try:
459                pybel.readstring("smi", s)
460            except IOError:
461                return False
[949]462            except ImportError:
463                return True
[607]464            return True
[391]465           
466        import os
467        tmpFd1=os.dup(1)
468        tmpFd2=os.dup(2)
469        fd=os.open(os.devnull, os.O_APPEND)
470##        os.close(1)
471        os.dup2(fd, 1)
472        os.dup2(fd, 2)
473##        os.close(fd)
474        for var in candidates:
475            count=0
476            for e in data:
[949]477                if pybel and isValidSmiles(str(e[var])):
[391]478                    count+=1
479            vars.append((count,var))
480        names=[v.name for v in data.domain.variables+data.domain.getmetas().values()]
481        names=filter(isValidSmiles, names)
482##        os.close(1)
483        os.dup2(tmpFd1, 1)
484        os.dup2(tmpFd2, 2)
485##        os.close(tmpFd)
486        return vars, names
487       
488    def setMoleculeSmilesCombo(self):
489        candidates, self.defFragmentSmiles=self.filterSmilesVariables(self.molData)
490        self.candidateMolSmilesAttr=[c[1] for c in candidates]
491        best = reduce(lambda best,current:best[0]<current[0] and current or best, candidates)
492        self.moleculeSmilesCombo.clear()
[398]493        self.moleculeSmilesCombo.addItems([v.name for v in self.candidateMolSmilesAttr])
[391]494        self.moleculeSmilesAttr=candidates.index(best)
495
496    def setMoleculeTitleListBox(self):
497        self.icons=self.createAttributeIconDict()
498        vars=self.molData.domain.variables+self.molData.domain.getmetas().values()
499        self.moleculeTitleAttributeList=[attr.name for attr in vars]
500        self.selectedMoleculeTitleAttrs=[]
501
502    def updateTitles(self):
503        if not self.molData:
504            return
505        smilesAttr=self.candidateMolSmilesAttr[min(self.moleculeSmilesAttr, len(self.candidateMolSmilesAttr)-1)]
506        attrs = self.molData.domain.variables+self.molData.domain.getmetas().values()
507        selected=[attrs[i] for i in self.selectedMoleculeTitleAttrs]
508        for widget, example in zip(self.imageWidgets, filter(lambda e:not e[smilesAttr].isSpecial(),self.molData)):
509            text=" / ".join(map(str, [example[attr] for attr in selected]))
510            widget.label.setText(text)
511
512    def setFragmentSmilesCombo(self):
513        if self.fragData:
514            candidates, names=self.filterSmilesVariables(self.fragData)
515        else:
516            candidates=[]
517        self.candidateFragSmilesAttr=[None]+candidates
518        self.fragmentSmilesCombo.clear()
[398]519        self.fragmentSmilesCombo.addItems(["Default"]+[v.name for v in candidates])
[391]520        if self.fragmentSmilesAttr>len(candidates):
521            self.fragmentSmilesAttr=0
522
523    def updateFragmentsListBox(self):
[949]524        if not pybel:
525            return
[391]526        fAttr=self.candidateFragSmilesAttr[self.fragmentSmilesAttr]
527        if fAttr:
528            self.fragmentSmiles=[""]+[str(e[fAttr]) for e in self.fragData if not e[fAttr].isSpecial()]
529        else:
530            self.fragmentSmiles=[""]+self.defFragmentSmiles
531        self.listBox.clear()
[398]532        self.listBox.addItems(self.fragmentSmiles)
[391]533        self.showFragmentsRadioButton.setDisabled(len(self.fragmentSmiles)==1)
534        self.markFragmentsCheckBox.setDisabled(len(self.fragmentSmiles)==1)
535        self.selectMarkedMoleculesButton.setDisabled(True)
536       
[608]537    def fragmentSelection(self, item):
[949]538        if not pybel:
539            return
[608]540        index = self.listBox.indexFromItem(item).row()
[426]541        if index == -1:
542            index = 0
[391]543        self.selectedFragment=self.fragmentSmiles[index]
544        self.selectMarkedMoleculesButton.setEnabled(bool(self.selectedFragment))
545        self.markFragmentsCheckBox.setEnabled(bool(self.selectedFragment))
546        if not self.showFragments and self.colorFragments:
547            self.redrawImages()
548       
549    def renderImages(self,useCached=False):
[426]550##        def fixNumColumns(numItems, numColumns):
551##            if (self.imageSize+4)*(numItems/numColumns+1)>30000:
552##                return numItems/(30000/(self.imageSize+4))
553##            else:
554##                return numColumns
[391]555       
[398]556        self.numColumns=self.scrollArea.width()/(self.imageSize+4) or 1
[426]557        self.gridLayout = QGridLayout()
558        self.scrollArea.takeWidget()
559        self.molWidget = QWidget()
560        self.scrollArea.setWidget(self.molWidget)
561        self.molWidget.setLayout(self.gridLayout)
[391]562        self.imageWidgets=[]
[607]563##        self.imageCache.newEpoch()
[391]564        self.failedCount=0
565        self.fromCacheCount=0
566        if self.showFragments and self.fragmentSmiles:
[426]567            correctedNumColumns=self.numColumns #fixNumColumns(len(self.fragmentSmiles[1:]), self.numColumns)
[391]568            self.progressBarInit()
569            for i,fragment in enumerate(self.fragmentSmiles[1:]):
570                #imagename=self.imageprefix+str(i)+".bmp"
571                #vis.molecule2BMP(fragment, imagename, self.imageSize)
572##                image=MolImage(self,  self.molWidget, DrawContext(molecule=fragment, imagename=imagename, size=self.imageSize))
[607]573                image=MolWidget(self, self.molWidget, DrawContext(molecule=fragment, size=self.imageSize))
[391]574                self.gridLayout.addWidget(image, i/correctedNumColumns, i%correctedNumColumns)
575                self.imageWidgets.append(image)
576                self.progressBarSet(i*100/len(self.fragmentSmiles))
577            self.progressBarFinished()
578        elif self.molData and self.candidateMolSmilesAttr:
579            sAttr=self.candidateMolSmilesAttr[min(self.moleculeSmilesAttr, len(self.candidateMolSmilesAttr)-1)]
580            tAttr=self.candidateMolTitleAttr[min(self.moleculeTitleAttr, len(self.candidateMolTitleAttr)-1)]
581            if self.moleculeTitleAttr:
582                titleList=[str(e[tAttr]) for e in self.molData if not e[sAttr].isSpecial()]
583            else:
584                titleList=[]
585                if not sAttr:
586                    return
587            molSmiles=[(str(e[sAttr]), e) for e in self.molData if not e[sAttr].isSpecial()]
[426]588            correctedNumColumns=self.numColumns #fixNumColumns(len(molSmiles), self.numColumns)
[391]589            self.progressBarInit()
590            if self.colorFragments and self.selectedFragment:
591                fMap=map_fragments([self.selectedFragment], [t[0] for t  in molSmiles])
592            for i,((molecule, example), title) in enumerate(zip(molSmiles, titleList or [""]*len(molSmiles))):
593                #imagename=self.imageprefix+str(i)+".bmp"
594                if self.colorFragments and self.selectedFragment and fMap[molecule][self.selectedFragment]:
595                    context=DrawContext(molecule=molecule, fragment=self.selectedFragment, size=self.imageSize, title=title, grayedBackground=example in self.molSubset, useCached=useCached)
596                    #vis.moleculeFragment2BMP(molecule, self.selectedFragment, imagename, self.imageSize)
597                else:
598                    context=DrawContext(molecule=molecule, size=self.imageSize, title=title, grayedBackground=example in self.molSubset, useCached=useCached)
599                    #vis.molecule2BMP(molecule, imagename, self.imageSize)
600##                image=MolImage(self, self.molWidget, context)
601                image=MolWidget(self, self.molWidget, context)
602                self.gridLayout.addWidget(image, i/correctedNumColumns, i%correctedNumColumns)
603                self.imageWidgets.append(image)
604                self.progressBarSet(i*100/len(molSmiles))
605            self.progressBarFinished()
606            self.updateTitles()
607        #print "done drawing"
608        self.overRideCache=False
[949]609        self.fromCacheCount = sum(int(w.from_cache) for w in self.imageWidgets)
610        self.failedCount = len(self.imageWidgets) - sum(int(w.state) for w in self.imageWidgets)
[608]611       
[398]612        self.molWidget.setMinimumSize(self.gridLayout.sizeHint())
613        self.molWidget.show()
[391]614
615    def destroyImageWidgets(self):
616        for w in self.imageWidgets:
[426]617            w.hide()
[398]618            self.gridLayout.removeWidget(w)
[391]619        self.imageWidgets=[]
[426]620
621    def rearrangeLayout(self):
[608]622        self.numColumns=self.scrollArea.width() / (self.imageSize + self.gridLayout.horizontalSpacing()*2 + 20) or 1
[426]623        self.molWidget = QWidget()
624        self.gridLayout = QGridLayout()
625        self.molWidget.setLayout(self.gridLayout)
626        for i, w in enumerate(self.imageWidgets):
627            self.gridLayout.addWidget(w, i/self.numColumns, i%self.numColumns)
628        self.scrollArea.takeWidget()
629        self.scrollArea.setWidget(self.molWidget)
630        self.molWidget.setMinimumSize(self.gridLayout.sizeHint())
631        self.molWidget.show()
[391]632           
633    def showImages(self, useCached=False):
634        self.destroyImageWidgets()
[1318]635        self.warning(0)
[391]636       
637        self.renderImages(useCached)
[607]638        if not oasaLocal:
[391]639            if self.failedCount>0:
[949]640                self.infoLabel.setText("%i chemicals\nFailed to retrieve %i images from server\n%i images from server\n%i images from local cache" \
641                                       % (len(self.imageWidgets), self.failedCount, len(self.imageWidgets)-self.failedCount-self.fromCacheCount, self.fromCacheCount))
[1318]642                self.warning(0, "Failed to retrieve some images from server")
[391]643            elif self.fromCacheCount == len(self.imageWidgets):
644                self.infoLabel.setText("%i chemicals\nAll images from local cache" % len(self.imageWidgets))
645            elif self.fromCacheCount == 0:
646                self.infoLabel.setText("%i chemicals\nAll images from server" % len(self.imageWidgets))
647            else:
[446]648                self.infoLabel.setText("%i chemicals\n%i images from server\n%i from local cache" % (len(self.imageWidgets), len(self.imageWidgets)-self.fromCacheCount, self.fromCacheCount))
[391]649        else:
650            self.infoLabel.setText("Chemicals %i" % len(self.imageWidgets))
651
652    def redrawImages(self, useCached=False):
653        selected=map(lambda i:self.imageWidgets.index(i), filter(lambda i:i.selected, self.imageWidgets))
654        self.showImages(useCached=useCached)
655        for i in selected:
656            self.imageWidgets[i].selected=True
657            self.imageWidgets[i].repaint()
658           
659    def mouseAction(self, image, event):
660        if self.ctrlPressed:
661            image.selected=not image.selected
662        else:
663            for i in self.imageWidgets:
664                i.selected=False
665                i.repaint()
666            image.selected=True
667        image.repaint()
668        if self.commitOnChange:
669            self.commit()
670
671    def selectMarked(self):
[949]672        if not pybel:
673            return
[391]674        if not self.showFragments:
675            molecules=[i.context.molecule for i in self.imageWidgets]
676            fMap=map_fragments([self.selectedFragment], molecules)
677            for i in self.imageWidgets:
678                if fMap[i.context.molecule][self.selectedFragment]:
679                    i.selected=True
680                else:
681                    i.selected=False
682                i.repaint()
683        if self.commitOnChange:
684            self.commit()
685   
686    def commit(self):
687        if self.showFragments:
688            sAttr=self.candidateMolSmilesAttr[self.moleculeSmilesAttr]
[949]689            molecules=[str(e[sAttr]) for e in self.molData]
[391]690            fragments=[i.context.molecule for i in self.imageWidgets if i.selected]
691            fragmap=map_fragments(fragments, molecules)
692            match=filter(lambda m:max(fragmap[m].values()), molecules)
693            examples=[e for e in self.molData if str(e[sAttr]) in match]
694            if examples:
695                table=orange.ExampleTable(examples)
696                self.send("Selected Molecules", table)
697            else:
698                self.send("Selected Molecules", None)               
699        else:
700            mols=[i.context.molecule for i in self.imageWidgets if i.selected]
701            sAttr=self.candidateMolSmilesAttr[self.moleculeSmilesAttr]
702            examples=[e for e in self.molData if str(e[sAttr]) in mols]
703            if examples:
704                table=orange.ExampleTable(examples)
705                self.send("Selected Molecules", table)
706            else:
707                self.send("Selected Molecules", None)
708
709    def keyPressEvent(self, key):
710        if key.key()==Qt.Key_Control:
711            self.ctrlPressed=TRUE
712        else:
713            OWWidget.keyPressEvent(self, key)
714
715    def keyReleaseEvent(self, key):
716        if key.key()==Qt.Key_Control:
717            self.ctrlPressed=FALSE
718        else:
719            OWWidget.keyReleaseEvent(self, key)
720
721    def saveToHTML(self):
722        fileName=str(QFileDialog.getSaveFileName("index.html","HTML (.html)", None, "Save to.."))
723        if not fileName:
724            return
725        else:
726            file=open(fileName, "w")
727        import os
728        path, _ =os.path.split(fileName)
729        if "molimages" not in os.listdir(path):
730            os.mkdir(path+"/molimages")
731        title="Molekule"
732        file.write("<html><title>"+title+"</title>\n")
733        file.write("<body> <table border=\"1\">\n")
734        i=0
735        try:
736            import Image
737        except:
738            pass
739        for row in range(len(self.imageWidgets)/self.numColumns+1):
740            file.write("<tr>\n")
741            for col in range(self.numColumns):
742                if i>=len(self.imageWidgets):
743                    break
744                try:
745                    im=Image.open(self.imageWidgets[i].context.imagename)
746                    if im.mode!="RGB":
747                        im=im.convert("RGB")
748                    im.save(path+"/molimages/image"+str(i)+".gif", "GIF")
749                    file.write("<td><p>"+str(self.imageWidgets[i].label.text())+"</p><img src=\"./molimages/image"+str(i)+".gif\"></td>\n")
750                except:
751                    from shutil import copy
752                    copy(self.imageWidgets[i].context.imagename, path+"/molimages/")
753                    file.write("<td><p>"+str(self.imageWidgets[i].label.text())+"</p><img src=\"./molimages/image"+str(i)+".bmp\"></td>\n")
754                i+=1
755            file.write("</tr>\n")
756        file.write("</table></body></html>")
757        file.close()
758
759    def saveSettings(self, *args, **kw):
760        OWWidget.saveSettings(self, *args, **kw)
[607]761        ImageCache().sync()
[391]762
763def molecule2BMP(molSmiles, filename, size=200, title="", grayedBackground=False):
[607]764    moleculeFragment2BMP(molSmiles, None, filename)
[391]765
[607]766def moleculeFragment2BMP(molSmiles, fragSmiles, filename, size=200, title="", grayedBackground=False):
767    obiChem.mol_to_svg(molSmiles, fragSmiles, filename)
[391]768
769def remoteMoleculeFragment2BMP(molSmiles, fragSmiles, filename, size=200, title="", grayedBackground=False):
770    import cStringIO
771    import urllib
772    import Image, ImageDraw
773
774    from openbabel import OBConversion, OBMol
775    loader = OBConversion()
776    loader.SetInAndOutFormats("smi", "smi")
777    if not loader.ReadString(OBMol(), molSmiles):
778        img = Image.new("RGB", (size, size), (255,255,255))
779        draw = ImageDraw.Draw(img)
780        draw.text((10, size/2-10), "wrong SMILES notation", fill=(0, 0, 0))
781        img.save(filename)
782        return
783   
784    params = urllib.urlencode({'password': OWMoleculeVisualizer.serverPwd, 'molSmiles': molSmiles, 'fragSmiles': fragSmiles, 'size': size, 'title': title, 'grayBack': grayedBackground})
785    try:
786        f = urllib.urlopen("http://212.235.189.53/openEye/drawMol.py", params)
787        imgstr = f.read()
788    except IOError, er:
789        #print er
790        raise er
791    im = cStringIO.StringIO(imgstr)
792    img = Image.open(im)
793    #print img.format, img.size, img.mode
794    img.save(filename)
795    #del img
796
797def remoteMolecule2BMP(molSmiles, filename, size=200, title="", grayedBackground=False):
798    import cStringIO
799    import urllib
800    import Image, ImageDraw
801
802    from openbabel import OBConversion, OBMol
803    loader = OBConversion()
804    loader.SetInAndOutFormats("smi", "smi")
805    if not loader.ReadString(OBMol(), molSmiles):
806        img = Image.new("RGB", (size, size), (255,255,255))
807        draw = ImageDraw.Draw(img)
808        draw.text((10, size/2-10), "wrong SMILES notation", fill=(0, 0, 0))
809        img.save(filename)
810        return   
811   
812    params = urllib.urlencode({'password': OWMoleculeVisualizer.serverPwd, 'molSmiles': molSmiles, 'size': size, 'title': title, 'grayBack': grayedBackground})
813    try:
814        f = urllib.urlopen("http://212.235.189.53/openEye/drawMol.py", params)
815        imgstr = f.read()
816    except IOError, er:
817        #print er, "rm2BMP"
818        raise er
819    im = cStringIO.StringIO(imgstr)
820    img = Image.open(im)
821    #print img.format, img.size, img.mode
822    img.save(filename)
823    #del img       
824
[607]825
[391]826def map_fragments(fragments, smiles, binary=True):
[607]827    ret = {}
[391]828    for s in smiles:
[607]829        mol = pybel.readstring("smi", s)
[391]830        d={}
831        for f in fragments:
[607]832            pat = pybel.Smarts(f)
[391]833            count=0
[607]834            if pat.findall(mol):
[391]835                count+=1
836            if binary:
837                d[f]=count!=0 and 1 or 0
838            else:
839                d[f]=count
840        ret[s]=d
841    return ret
842
[607]843if not oasaLocal:
[391]844    moleculeFragment2BMP = remoteMoleculeFragment2BMP
845    molecule2BMP = remoteMolecule2BMP
846
847if __name__=="__main__":
[608]848    pass
849##    app=QApplication(sys.argv)
850##    from pywin.debugger import set_trace
851####    set_trace()
852##    w=OWMoleculeVisualizer()
853####    app.setMainWidget(w)
854##    w.show()
855##    data=orange.ExampleTable("E://chem/chemdata/BCMData_growth_frag.tab")
856##   
857##    w.setMoleculeTable(data)
858####    data=orange.ExampleTable("E://chem//new//sf.tab")
859####    w.setFragmentTable(data)
860##    app.exec_()
861##    w.saveSettings()
Note: See TracBrowser for help on using the repository browser.