source: orange-bioinformatics/Orange/bioinformatics/widgets/OWMoleculeVisualizer.py @ 1625:cefeb35cbfc9

Revision 1625:cefeb35cbfc9, 34.5 KB checked in by mitar, 2 years ago (diff)

Moving files around.

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