source: orange-bioinformatics/widgets/OWMoleculeVisualizer.py @ 1318:9451776b6c59

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