source: orange-bioinformatics/widgets/OWKEGGPathwayBrowser.py @ 368:22fa60831b3c

Revision 368:22fa60831b3c, 23.0 KB checked in by ales_erjavec <ales.erjavec@…>, 5 years ago (diff)

-changed name from KEGG Pathway browser to KEGG Pathway Browser

Line 
1"""
2<name>KEGG Pathway Browser</name>
3<description>Browser that - given a set of genes - searches and displays relevant KEGG pathways</description>
4<priority>220</priority>
5<icon>icons/KEGG.png</icon>
6"""
7
8import sys
9import orange
10import obiKEGG
11
12from OWWidget import *
13from qt import *
14
15import OWGUI
16
17from collections import defaultdict
18
19def split_and_strip(string, sep=None):
20    return [s.strip() for s in string.split(sep)]
21
22class PathwayToolTip(QToolTip):
23    def __init__(self, parent):
24        QToolTip.__init__(self, parent)
25        self.parent = parent
26
27    def maybeTip(self, p):
28        objs = [(id, bb) for id, bb in  self.parent.GetObjects(p.x() ,p.y()) if id in self.parent.objects]
29        if objs:
30            genes = map(self.parent.master.uniqueGenesDict.get, dict(objs).keys())
31            text = "<br>".join(genes)
32            self.tip(QRect(p.x()-2, p.y()-2, 4, 4), text)
33
34class PathwayView(QScrollView):
35    def __init__(self, master, *args):
36        QScrollView.__init__(self, *args)
37        self.master = master
38        self.toolTip = PathwayToolTip(self)
39        self.setHScrollBarMode(QScrollView.Auto)
40        self.setVScrollBarMode(QScrollView.Auto)
41        self.setMouseTracking(True)
42        self.viewport().setMouseTracking(True)
43        self.bbDict = {}
44        self.pixmap = None
45        self.image = None
46        self.popup = QPopupMenu()
47        self.popup.insertItem("View genes on KEGG website", 0, 0)
48        self.popup.insertItem("View pathway on KEGG website", 1, 1)
49        self.popup.insertItem("View linked pathway", 2, 2)
50        self.connect(self.popup, SIGNAL("activated ( int ) "), self.PopupAction)
51       
52    def SetPathway(self, pathway=None, objects=[]):
53        self.pathway = pathway
54        self.objects = objects
55        if pathway:
56            pathway.api.download_progress_callback = self.master.progressBarSet
57            self.master.progressBarInit()
58            self.image = image = self.pathway.get_image()
59            self.bbDict = self.pathway.get_bounding_box_dict()
60            self.master.progressBarFinished()
61            self.ShowImage()
62##            image.save(self.pathway.local_database_path+"TmpPathwayImage.png")
63##            self.pixmap = QPixmap(obiKEGG.default_database_path+"TmpPathwayImage.png")
64##            w, h = image.size
65##            self.resizeContents(w, h)
66##            self.updateContents(self.contentsX(), self.contentsY() ,self.viewport().width(), self.viewport().height())
67        else:
68            self.bbDict = {}
69            self.pixmap = None
70            self.resizeContents(0,0)
71
72    def ShowImage(self):
73        if self.master.autoResize:
74            import Image
75            w, h = self.image.size
76            self.resizeFactor = factor = min(self.viewport().width()/float(w), self.viewport().height()/float(h))
77            image = self.image.resize((int(w*factor), int(h*factor)), Image.ANTIALIAS)
78        else:
79            image = self.image
80            self.resizeFactor = 1
81        image.save(self.pathway.local_database_path+"TmpPathwayImage.png")
82        self.pixmap = QPixmap(self.pathway.local_database_path+"TmpPathwayImage.png")
83        w, h = image.size
84        self.resizeContents(w, h)
85        self.updateContents(self.contentsX(), self.contentsY() ,self.viewport().width(), self.viewport().height())
86
87    def drawContents(self, painter, cx=0, cy=0, cw=-1, ch=-1):
88        QScrollView.drawContents(self, painter, cx, cy, cw, ch)
89        if self.pixmap:
90            cw = cw!=-1 and cw or self.viewport().width()
91            ch = ch!=-1 and ch or self.viewport().height()
92            painter.drawPixmap(cx, cy, self.pixmap, cx, cy, min(cw, self.pixmap.width()-cx), min(ch, self.pixmap.height()-cy))
93            painter.save()
94
95            painter.setPen(QPen(Qt.blue, 2, Qt.SolidLine))
96            painter.setBrush(QBrush(Qt.NoBrush))
97            for rect in reduce(lambda a,b:a.union(b), [bbList for id, bbList in self.bbDict.items() if id in self.objects], set()):
98                x1, y1, x2, y2 = map(lambda x:int(self.resizeFactor*x), rect)
99                painter.drawRect(x1+1, y1+1, x2-x1, y2-y1)
100               
101            painter.setPen(QPen(Qt.red, 2, Qt.SolidLine))
102            for rect in self.master.selectedObjects.keys():
103                x1, y1, x2, y2 = map(lambda x:int(self.resizeFactor*x), rect)
104                painter.drawRect(x1+1, y1+1, x2-x1, y2-y1)
105            painter.restore()
106
107    def GetObjects(self, x, y):
108        def _in(x, y, bb):
109##            if bb[0]=="rect":
110            x1, y1, x2, y2 = map(lambda x:int(self.resizeFactor*x), bb)
111            return x>=x1 and y>=y1 and x<x2 and y<y2
112##            else:
113##                x1, y1, r = map(lambda x:int(self.resizeFactor*x), bb[1:])
114##                return abs(x1-x)<=r and abs(y1-y)<=r
115        x, y = self.viewportToContents(x, y)
116        objs = []
117        for id, bbList in self.bbDict.items():
118##            if id in self.objects:
119            for bb in bbList:
120                if _in(x, y, bb):
121                    objs.append((id, bb))
122        return objs
123   
124##    def viewportMouseMoveEvent(self, event):
125##        x, y = event.x(), event.y()
126##        objs = self.GetObjects(x, y)
127
128    def viewportMousePressEvent(self, event):
129        x, y = event.x(), event.y()
130        old = set(self.master.selectedObjects.keys())
131        objs = self.GetObjects(x, y)
132        if event.button()==Qt.LeftButton:
133            self.master.SelectObjects([(id, bb) for (id, bb) in objs if id in self.objects])
134            for rect in set(self.master.selectedObjects.keys()).union(old):
135                x1, y1, x2, y2 = map(lambda x:int(self.resizeFactor*x), rect)
136                self.updateContents(x1-1, y1-1, x2-x1+2, y2-y1+2)
137        elif event.button()==Qt.RightButton:
138            self.popup.objs = objs
139            self.popup.setItemEnabled(0, any(id for id, bb in objs if id in self.objects))
140            self.popup.setItemEnabled(2, len(objs)==1 and objs[-1][0].startswith("path:"))
141            self.popup.popup(self.mapToGlobal(event.pos()))
142        else:
143            QScrollView.viewportMousePressEvent(self, event)
144
145    def resizeEvent(self, event):
146        QScrollView.resizeEvent(self, event)
147        if self.master.autoResize and self.image:
148            self.ShowImage()
149
150    def PopupAction(self, id):
151        import webbrowser
152        if id==0:
153            genes = [s.split(":")[-1].strip() for s, t in self.popup.objs if s in self.objects]
154            address = "http://www.genome.jp/dbget-bin/www_bget?"+self.pathway.org+"+"+"+".join(genes)
155        elif id==1:
156##            genes = [s for s, t in self.popup.objs]
157##            s = reduce(lambda s,g:s.union(self.master.org.get_enzymes_by_gene(g)), genes, set())
158##            address = "http://www.genome.jp/dbget-bin/www_bget?enzyme+"+"+".join([e.split(":")[-1] for e in s])
159            genes = [s.split(":")[-1].strip() for s, t in self.popup.objs if s in self.objects]
160            address = "http://www.genome.jp/dbget-bin/show_pathway?"+self.pathway.pathway_id.split(":")[-1]+(genes and "+"+"+".join(genes) or "")
161        elif id==2:
162            self.master.selectedObjects = defaultdict(list)
163            self.master.Commit()
164            self.SetPathway(obiKEGG.KEGGPathway(self.popup.objs[-1][0]))
165            return
166        try:
167            webbrowser.open(address)
168        except:
169            pass
170   
171class OWKEGGPathwayBrowser(OWWidget):
172    settingsList = ["organismIndex", "geneAttrIndex", "autoCommit", "autoResize", "useReference", "useAttrNames", "caseSensitive"]
173    contextHandlers = {"":DomainContextHandler("",[ContextField("organismIndex", DomainContextHandler.Required + DomainContextHandler.IncludeMetaAttributes),
174                                                   ContextField("geneAttrIndex", DomainContextHandler.Required + DomainContextHandler.IncludeMetaAttributes),
175                                                   ContextField("useAttrNames", DomainContextHandler.Required + DomainContextHandler.IncludeMetaAttributes)])}
176    def __init__(self, parent=None, signalManager=None, name="KEGG Pathway Browser"):
177        OWWidget.__init__(self, parent, signalManager, name)
178        self.inputs = [("Examples", ExampleTable, self.SetData), ("Reference", ExampleTable, self.SetRefData)]
179        self.outputs = [("Selected Examples", ExampleTable), ("Unselected Examples", ExampleTable)]
180        self.organismIndex = 0
181        self.geneAttrIndex = 0
182        self.autoCommit = False
183        self.autoResize = True
184        self.useReference = False
185        self.useAttrNames = False
186        self.caseSensitive = True
187        self.showOntology = True
188        self.autoFindBestOrg = False
189        self.loadSettings()
190
191        self.controlArea.setMaximumWidth(250)
192        self.organismCodes = obiKEGG.KEGGInterfaceLocal().list_organisms().items()
193        self.organismCodes.sort()
194        items = [code+": "+desc for code, desc in self.organismCodes]
195        self.organismCodes = [code for code, desc in self.organismCodes]
196        cb = OWGUI.comboBox(self.controlArea, self, "organismIndex", box="Organism", items=items, callback=self.Update, addSpace=True)
197        cb.setMaximumWidth(200)
198       
199        box = OWGUI.widgetBox(self.controlArea, "Gene attribure")
200        self.geneAttrCombo = OWGUI.comboBox(box, self, "geneAttrIndex", callback=self.Update)
201        OWGUI.checkBox(box, self, "useAttrNames", "Use variable names", callback=self.UseAttrNamesCallback)
202        OWGUI.checkBox(box, self, "caseSensitive", "Case sensitive gene matching", callback=self.Update)
203        OWGUI.separator(self.controlArea)
204       
205        self.geneAttrCombo.setDisabled(bool(self.useAttrNames))
206       
207        OWGUI.checkBox(self.controlArea, self, "useReference", "From signal", box="Reference", callback=self.Update)
208        OWGUI.separator(self.controlArea)
209
210        OWGUI.checkBox(self.controlArea, self, "showOntology", "Show pathways in full ontology", box="Ontology", callback=self.UpdateListView)
211       
212        OWGUI.checkBox(self.controlArea, self, "autoResize", "Resize to fit", box="Image", callback=lambda :self.pathwayView.image and self.pathwayView.ShowImage())
213        OWGUI.separator(self.controlArea)
214
215        box = OWGUI.widgetBox(self.controlArea, "Selection")
216        OWGUI.checkBox(box, self, "autoCommit", "Commit on update")
217        OWGUI.button(box, self, "Commit", callback=self.Commit)
218        OWGUI.rubber(self.controlArea)
219
220        self.mainAreaLayout = QVBoxLayout(self.mainArea, QVBoxLayout.TopToBottom)
221        spliter = QSplitter(Qt.Vertical, self.mainArea)
222        self.pathwayView = PathwayView(self, spliter)
223        self.mainAreaLayout.addWidget(spliter)
224
225        self.listView = QListView(spliter)
226        for header in ["Pathway", "P value", "Genes", "Reference"]:
227            self.listView.addColumn(header)
228        self.listView.setSelectionMode(QListView.Single)
229        self.listView.setSorting(1)
230        #self.listView.setAllColumnsShowFocus(1)
231        self.listView.setMaximumHeight(200)
232       
233        self.connect(self.listView, SIGNAL("selectionChanged ( QListViewItem * )"), self.UpdatePathwayView)
234       
235        self.ctrlPressed=False
236        self.selectedObjects = defaultdict(list)
237        self.data = None
238        self.refData = None
239        self.loadedOrganism = None
240       
241    def SetData(self, data=None):
242        self.closeContext()
243        self.data = data
244        if data:
245            self.SetBestGeneAttrAndOrganism()
246            self.openContext("", data)
247            self.Update()
248        else:
249            self.listView.clear()
250            self.selectedObjects = defaultdict(list)
251            self.pathwayView.SetPathway(None)
252            self.send("Selected Examples", None)
253            self.send("Unselected Examples", None)
254
255    def SetRefData(self, data=None):
256        self.refData = data
257        if self.useReference and self.data:
258            self.Update()
259
260    def UseAttrNamesCallback(self):
261        self.geneAttrCombo.setDisabled(bool(self.useAttrNames))
262        self.Update()
263
264    def SetBestGeneAttrAndOrganism(self):
265        self.geneAttrCandidates = self.data.domain.attributes + self.data.domain.getmetas().values()
266        self.geneAttrCandidates = filter(lambda v:v.varType in [orange.VarTypes.Discrete ,orange.VarTypes.String], self.geneAttrCandidates)
267        self.geneAttrCombo.clear()
268        self.geneAttrCombo.insertStrList([var.name for var in self.geneAttrCandidates])
269        data = self.data
270        if len(data)>20:
271            data = data.select(orange.MakeRandomIndices2(data, 20))
272        from cPickle import load
273        score = {}
274        self.progressBarInit()
275        attrNames = [str(v.name).strip() for v in self.data.domain.attributes]
276        testOrgs = self.autoFindBestOrg and self.organismCodes or [self.organismCodes[self.organismIndex]]
277        for i, org in enumerate(testOrgs):
278            try:
279                geneNames = load(open(os.path.join(obiKEGG.default_database_path, org+"_genenames.pickle")))
280            except:
281                continue
282            for attr in self.geneAttrCandidates:
283                vals = [str(e[attr]).strip() for e in data if not e[attr].isSpecial()]
284                vals = reduce(list.__add__, (split_and_strip(val, ",") for val in vals), [])
285                match = filter(lambda v:v in geneNames, vals)
286                score[(attr, org)] = len(match)
287            match = [v for v in attrNames if v in geneNames]
288            score[("_var_names_", org)] = len(match)
289            self.progressBarSet(i*100.0/len(self.organismCodes))
290        self.progressBarFinished()
291        score = [(s, attr, org) for (attr, org), s in score.items()]
292        score.sort()
293        if not score:
294            self.useAttrNames = False
295            self.geneAttrIndex = len(self.geneAttrCandidates)-1
296            self.organismIndex = 0
297        elif score[-1][1]=="_var_names_":
298            self.useAttrNames = True
299            self.geneAttrIndex = 0 #self.geneAttrCandidates.index(score[-2][1])
300            self.organismIndex = self.organismCodes.index(score[-1][2])
301        else:
302            self.useAttrNames = False
303            self.geneAttrIndex = self.geneAttrCandidates.index(score[-1][1])
304            self.organismIndex = self.organismCodes.index(score[-1][2])
305        self.geneAttrCombo.setDisabled(bool(self.useAttrNames))
306               
307    def UpdateListView(self):
308        self.listView.clear()
309        if not self.data:
310            return
311        allPathways = self.org.list_pathways()
312        allRefPathways = obiKEGG.KEGGInterfaceLocal().list_pathways(org="map")
313        items = []
314        if self.showOntology:
315            self.koOrthology = obiKEGG.KEGGInterfaceLocal().get_ko_orthology()
316            self.listView.setRootIsDecorated(True)
317            path_ids = set([s[-5:] for s in self.pathways.keys()])
318            def _walkCollect(koClass):
319                if koClass.ko_class_id in path_ids:
320                    return [koClass]
321                else:
322                    c = reduce(lambda li,c:li+_walkCollect(c), [child for child in koClass.children], [])
323                    return c + (c and [koClass] or [])
324            allClasses = reduce(lambda li1, li2: li1+li2, [_walkCollect(c) for c in self.koOrthology], [])
325            def _walkCreate(koClass, lvItem):
326                item = QListViewItem(lvItem)
327                id = "path:"+self.organismCodes[self.organismIndex]+koClass.ko_class_id
328                if koClass.ko_class_id in path_ids:
329                    genes, p_value, ref = self.pathways[id]
330                    item.setText(0, allPathways.get(id, id))
331                    item.setText(1, "%.5f" % p_value)
332                    item.setText(2, "%i of %i" %(len(genes), len(self.genes)))
333                    item.setText(3, "%i of %i" %(ref, len(self.referenceGenes)))
334                    item.pathway_id = id
335                else:
336                    item.setText(0, allPathways.get(id, koClass.class_name))
337                    if id in allPathways:
338                        item.pathway_id = id
339                    elif "path:map"+koClass.ko_class_id in allRefPathways:
340                        item.pathway_id = "path:map"+koClass.ko_class_id
341                    else:
342                        item.pathway_id = None
343               
344                for child in koClass.children:
345                    if child in allClasses:
346                        _walkCreate(child, item)
347                item.setOpen(True)                       
348           
349            for koClass in self.koOrthology:
350                if koClass in allClasses:
351                    _walkCreate(koClass, self.listView)
352            self.listView.triggerUpdate()
353        else:
354            self.listView.setRootIsDecorated(False)
355            pathways = self.pathways.items()
356            pathways.sort(lambda a,b:cmp(a[1][1], b[1][1]))
357            for id, (genes, p_value, ref) in pathways:
358                item = QListViewItem(self.listView)
359                item.setText(0, allPathways.get(id, id))
360                item.setText(1, "%.5f" % p_value)
361                item.setText(2, "%i of %i" %(len(genes), len(self.genes)))
362                item.setText(3, "%i of %i" %(ref, len(self.referenceGenes)))
363                item.pathway_id = id
364                items.append(item)
365        self.bestPValueItem = items and items[0] or None
366
367    def UpdatePathwayView(self, item=None):
368        self.selectedObjects = defaultdict(list)
369        self.Commit()
370        item = item or self.bestPValueItem
371        if not item or not item.pathway_id:
372            self.pathwayView.SetPathway(None)
373            return
374        self.pathway = obiKEGG.KEGGPathway(item.pathway_id)
375        self.pathway.api.download_progress_callback = self.progressBarSet
376        self.pathwayView.SetPathway(self.pathway, self.pathways.get(item.pathway_id, [[]])[0])
377       
378    def Update(self):
379        if not self.data:
380            return
381        self.error(0)
382        self.information(0)
383        if self.useAttrNames:
384            genes = [str(v.name).strip() for v in self.data.domain.attributes]
385        elif self.geneAttrCandidates:
386            geneAttr = self.geneAttrCandidates[min(self.geneAttrIndex, len(self.geneAttrCandidates)-1)]
387            genes = [str(e[geneAttr]) for e in self.data if not e[geneAttr].isSpecial()]
388            if any("," in gene for gene in genes):
389                genes = reduce(list.__add__, (split_and_strip(gene, ",") for gene in genes), [])
390                self.information(0, "Separators detected in input gene names. Assuming multiple genes per example.")
391        else:
392            self.error(0, "Cannot extact gene names from input")
393            genes = []
394        if self.loadedOrganism!=self.organismCodes[self.organismIndex]:
395            self.org = obiKEGG.KEGGOrganism(self.organismCodes[self.organismIndex])
396            self.org.api.download_progress_callback=self.progressBarSet
397            self.loadedOrganism = self.organismCodes[self.organismIndex]
398        self.progressBarInit()
399        uniqueGenes, conflicting, unknown = self.org.get_unique_gene_ids(set(genes), self.caseSensitive)
400        self.progressBarFinished()
401        if conflicting:
402            print "Conflicting genes:", conflicting
403        if unknown:
404            print "Unknown genes:", unknown
405        self.information(1)
406        if self.useReference and self.refData:
407            if self.useAttrNames:
408                reference = [str(v.name).strip() for v in self.refData]
409            else:
410                geneAttr = self.geneAttrCandidates[min(self.geneAttrIndex, len(self.geneAttrCandidates)-1)]
411                reference = [str(e[geneAttr]) for e in self.refData if not e[geneAttr].isSpecial()]
412                if any("," in gene for gene in reference):
413                    reference = reduce(list.__add__, (split_and_strip(gene, ",") for gene in reference), [])
414                    self.information(1, "Separators detected in reference gene names. Assuming multiple genes per example.")
415            self.progressBarInit()
416            uniqueRefGenes, conflicting, unknown = self.org.get_unique_gene_ids(set(reference), self.caseSensitive)
417            self.progressBarFinished()
418            self.referenceGenes = reference = uniqueRefGenes.keys()
419        else:
420            self.referenceGenes = reference = self.org.get_genes()
421        self.uniqueGenesDict = uniqueGenes
422        self.genes = uniqueGenes.keys()
423        self.revUniqueGenesDict = dict([(val, key) for key, val in self.uniqueGenesDict.items()])
424        self.progressBarInit()
425        self.pathways = self.org.get_enriched_pathways_by_genes(self.genes, reference, callback=self.progressBarSet)
426        self.progressBarFinished()
427        self.UpdateListView()
428        self.listView.setSelected(self.bestPValueItem, True)
429        #self.UpdatePathwayView()
430
431    def SelectObjects(self, objs):
432        if (not self.selectedObjects or self.ctrlPressed) and not objs:
433            return
434        if self.ctrlPressed:
435            for id, rect in objs:
436                if id in self.selectedObjects[rect]:
437                    self.selectedObjects[rect].pop(self.selectedObjects[rect].index(id))
438                    if not self.selectedObjects[rect]:
439                        del self.selectedObjects[rect]
440                else:
441                    self.selectedObjects[rect].append(id)
442        else:
443            self.selectedObjects.clear()
444            for id, rect in objs:
445                self.selectedObjects[rect].append(id)
446        if self.autoCommit:
447            self.Commit()
448           
449
450    def Commit(self):
451        if self.data:
452            if self.useAttrNames:
453                selectedGenes = reduce(set.union, self.selectedObjects.values(), set())
454                selectedVars = [self.data.domain[self.uniqueGenesDict[gene]] for gene in selectedGenes]
455                newDomain = orange.Domain(selectedVars ,0)
456                self.send("Selected Examples", orange.ExampleTable(newDomain, self.data))
457            else:
458                geneAttr = self.geneAttrCandidates[min(self.geneAttrIndex, len(self.geneAttrCandidates)-1)]
459                selectedExamples = []
460                otherExamples = []
461                selectedGenes = reduce(set.union, self.selectedObjects.values(), set())
462                for ex in self.data:
463                    names = [self.revUniqueGenesDict.get(name, None) for name in split_and_strip(str(ex[geneAttr]), ",")]
464                    if any(name and name in selectedGenes for name in names):
465                        selectedExamples.append(ex)
466                    else:
467                        otherExamples.append(ex)
468                self.send("Selected Examples", selectedExamples and orange.ExampleTable(selectedExamples) or None)
469                self.send("Unselected Examples", otherExamples and orange.ExampleTable(otherExamples) or None)
470        else:
471            self.send("Selected Examples", None)
472            self.send("Unselected Examples", None)
473       
474    def keyPressEvent(self, key):
475        if key.key()==Qt.Key_Control:
476            self.ctrlPressed=True
477        else:
478            OWWidget.keyPressEvent(self, key)
479
480    def keyReleaseEvent(self, key):
481        if key.key()==Qt.Key_Control:
482            self.ctrlPressed=False
483        else:
484            OWWidget.keyReleaseEvent(self, key)
485
486if __name__=="__main__":
487    app = QApplication(sys.argv)
488    data = orange.ExampleTable("../../orange/doc/datasets/brown-selected.tab")
489    w = OWKEGGPathwayBrowser()
490    app.setMainWidget(w)
491    w.show()
492    w.SetData(data)
493    app.exec_loop()
494    w.saveSettings()
Note: See TracBrowser for help on using the repository browser.