source: orange-bioinformatics/orangecontrib/bio/widgets/OWMoleculeVisualizer.py @ 1994:633673fab61f

Revision 1994:633673fab61f, 35.5 KB checked in by Ales Erjavec <ales.erjavec@…>, 7 weeks ago (diff)

Normalized whitespace (and other pep8 fixes).

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