source: orange-bioinformatics/orangecontrib/bio/widgets/OWGOEnrichmentAnalysis.py @ 1883:8d992429535b

Revision 1883:8d992429535b, 49.4 KB checked in by markotoplak, 7 months ago (diff)

GO widget shows p-value and FDR separately.

Line 
1"""
2<name>GO Browser</name>
3<description>Enrichment analysis for Gene Ontology terms.</description>
4<contact>Ales Erjavec</contact>
5<icon>icons/GOBrowser.svg</icon>
6<priority>2020</priority>
7"""
8
9from __future__ import absolute_import, with_statement
10
11from collections import defaultdict
12from functools import partial
13import gc
14import sys, os, tarfile, math
15from os.path import join as p_join
16
17from Orange.orng import orngServerFiles
18from Orange.orng.orngDataCaching import data_hints
19from Orange.OrangeWidgets import OWGUI
20from Orange.OrangeWidgets.OWWidget import *
21
22from .. import obiGene, obiGO, obiProb, obiTaxonomy
23
24NAME = "GO Browser"
25DESCRIPTION = "Enrichment analysis for Gene Ontology terms."
26ICON = "icons/GOBrowser.svg"
27PRIORITY = 2020
28
29INPUTS = [("Cluster Examples", Orange.data.Table,
30           "SetClusterDataset", Single + Default),
31          ("Reference Examples", Orange.data.Table,
32           "SetReferenceDataset")]
33
34OUTPUTS = [("Selected Examples", Orange.data.Table),
35           ("Unselected Examples", Orange.data.Table),
36           ("Example With Unknown Genes", Orange.data.Table),
37           ("Enrichment Report", Orange.data.Table)]
38
39REPLACES = ["_bioinformatics.widgets.OWGOEnrichmentAnalysis.OWGOEnrichmentAnalysis"]
40
41
42dataDir = orngServerFiles.localpath("GO")
43
44def listAvailable():
45    files = orngServerFiles.listfiles("GO")
46    ret = {}
47    for file in files:
48        tags = orngServerFiles.info("GO", file)["tags"]
49        td = dict([tuple(tag.split(":")) for tag in tags if tag.startswith("#") and ":" in tag])
50        if "association" in file.lower():
51            ret[td.get("#organism", file)] = file
52    orgMap = {"352472":"44689"}
53    essential = ["gene_association.%s.tar.gz" % obiGO.from_taxid(id) for id in obiTaxonomy.essential_taxids() if obiGO.from_taxid(id)]
54    essentialNames = [obiTaxonomy.name(id) for id in obiTaxonomy.essential_taxids() if obiGO.from_taxid(id)]
55    ret.update(zip(essentialNames, essential))
56    return ret
57
58class _disablegc(object):
59    def __enter__(self):
60        gc.disable()
61    def __exit__(self, *args):
62        gc.enable()
63
64def getOrgFileName(org):
65    from Orange.orng import orngServerFiles
66    files = orngServerFiles.listfiles("go")
67    return [f for f in files if org in f].pop()
68
69class TreeNode(object):
70    def __init__(self, tuple, children):
71        self.tuple = tuple
72        self.children = children
73
74class GOTreeWidget(QTreeWidget):
75    def contextMenuEvent(self, event):
76        QTreeWidget.contextMenuEvent(self, event)
77##        print event.x(), event.y()
78        term = self.itemAt(event.pos()).term
79        self._currMenu = QMenu()
80        self._currAction = self._currMenu.addAction("View term on AmiGO website")
81##        self.connect(self, SIGNAL("triggered(QAction*)"), partial(self.BrowserAction, term))
82        self.connect(self._currAction, SIGNAL("triggered()"), lambda :self.BrowserAction(term))
83        self._currMenu.popup(event.globalPos())
84
85    def BrowserAction(self, term):
86        import webbrowser
87        if isinstance(term, obiGO.Term):
88            term = term.id
89        webbrowser.open("http://amigo.geneontology.org/cgi-bin/amigo/term-details.cgi?term="+term)
90       
91    def paintEvent(self, event):
92        QTreeWidget.paintEvent(self, event)
93        if getattr(self, "_userMessage", None):
94            painter = QPainter(self.viewport())
95            font = QFont(self.font())
96            font.setPointSize(15)
97            painter.setFont(font)
98            painter.drawText(self.viewport().geometry(), Qt.AlignCenter, self._userMessage)
99            painter.end()
100
101
102class OWGOEnrichmentAnalysis(OWWidget):
103    settingsList = ["annotationIndex", "useReferenceDataset", "aspectIndex",
104                    "geneAttrIndex", "geneMatcherSettings",
105                    "filterByNumOfInstances", "minNumOfInstances",
106                    "filterByPValue", "maxPValue", "selectionDirectAnnotation",
107                    "filterByPValue_nofdr", "maxPValue_nofdr",
108                    "selectionDisjoint", "selectionType",
109                    "selectionAddTermAsClass", "useAttrNames", "probFunc"
110                    ]
111
112    contextHandlers = {"": DomainContextHandler(
113                                "",
114                                ["geneAttrIndex", "useAttrNames",
115                                 "annotationIndex", "geneMatcherSettings"],
116                                matchValues=1)
117                       }
118
119    def __init__(self, parent=None, signalManager=None, name="GO Browser"):
120        OWWidget.__init__(self, parent, signalManager, name)
121        self.inputs = [("Cluster Examples", ExampleTable,
122                        self.SetClusterDataset, Default),
123                       ("Reference Examples", ExampleTable,
124                        self.SetReferenceDataset, Single + NonDefault)]
125
126        self.outputs = [("Selected Examples", ExampleTable, Default),
127                        ("Unselected Examples", ExampleTable, Default),
128                        ("Example With Unknown Genes", ExampleTable, Default),
129                        ("Enrichment Report", ExampleTable)]
130
131        self.annotationIndex = 0
132        self.autoFindBestOrg = False
133        self.useReferenceDataset = 0
134        self.aspectIndex = 0
135        self.geneAttrIndex = 0
136        self.useAttrNames = False
137        self.geneMatcherSettings = [True, False, False, False]
138        self.filterByNumOfInstances = False
139        self.minNumOfInstances = 1
140        self.filterByPValue = True
141        self.maxPValue = 0.1
142        self.filterByPValue_nofdr = False
143        self.maxPValue_nofdr = 0.1
144        self.probFunc = 0
145        self.selectionDirectAnnotation = 0
146        self.selectionDisjoint = 0
147        self.selectionAddTermAsClass = 0
148        self.selectionChanging = 0
149       
150        # check usage of all evidences
151        for etype in obiGO.evidenceTypesOrdered:
152            varName = "useEvidence" + etype
153            if varName not in self.settingsList: 
154                self.settingsList.append(varName)
155            code = compile("self.%s = True" % (varName), ".", "single")
156            exec(code)
157        self.annotationCodes = []
158       
159        self.loadSettings()
160       
161        #############
162        ##GUI
163        #############
164        self.tabs = OWGUI.tabWidget(self.controlArea)
165        ##Input tab
166        self.inputTab = OWGUI.createTabPage(self.tabs, "Input")
167        box = OWGUI.widgetBox(self.inputTab, "Info")
168        self.infoLabel = OWGUI.widgetLabel(box, "No data on input\n")
169       
170        OWGUI.button(box, self, "Ontology/Annotation Info", callback=self.ShowInfo,
171                     tooltip="Show information on loaded ontology and annotations",
172                     debuggingEnabled=0)
173        box = OWGUI.widgetBox(self.inputTab, "Organism", addSpace=True)
174        self.annotationComboBox = OWGUI.comboBox(box, self, "annotationIndex",
175                            items = self.annotationCodes, callback=self.Update,
176                            tooltip="Select organism", debuggingEnabled=0)
177       
178        # freeze until annotation combo box is updateded with available annotations.
179        if self.signalManager:
180            self.signalManager.freeze(self).push()
181        QTimer.singleShot(0, self.UpdateOrganismComboBox)
182       
183        self.geneAttrIndexCombo = OWGUI.comboBox(self.inputTab, self, "geneAttrIndex",
184                            box="Gene names", callback=self.Update,
185                            tooltip="Use this attribute to extract gene names from input data")
186        OWGUI.checkBox(self.geneAttrIndexCombo.box, self, "useAttrNames", "Use data attributes names",
187                       disables=[(-1, self.geneAttrIndexCombo)], callback=self.Update, 
188                       tooltip="Use attribute names for gene names")
189        OWGUI.button(self.geneAttrIndexCombo.box, self, "Gene matcher settings", 
190                     callback=self.UpdateGeneMatcher, 
191                     tooltip="Open gene matching settings dialog", 
192                     debuggingEnabled=0)
193       
194        self.referenceRadioBox = OWGUI.radioButtonsInBox(self.inputTab, self, "useReferenceDataset", 
195                                                         ["Entire genome", "Reference set (input)"],
196                                                         tooltips=["Use entire genome for reference",
197                                                                   "Use genes from Referece Examples input signal as reference"],
198                                                         box="Reference", callback=self.Update)
199        self.referenceRadioBox.buttons[1].setDisabled(True)
200        OWGUI.radioButtonsInBox(self.inputTab, self, "aspectIndex", ["Biological process",
201                                                                     "Cellular component",
202                                                                     "Molecular function"], 
203                                box="Aspect", callback=self.Update)
204       
205        self.geneAttrIndexCombo.setDisabled(bool(self.useAttrNames))
206       
207        ##Filter tab
208        self.filterTab = OWGUI.createTabPage(self.tabs, "Filter")
209        box = OWGUI.widgetBox(self.filterTab, "Filter GO Term Nodes", addSpace=True)
210        OWGUI.checkBox(box, self, "filterByNumOfInstances", "Genes",
211                       callback=self.FilterAndDisplayGraph, 
212                       tooltip="Filter by number of input genes mapped to a term")
213        OWGUI.spin(OWGUI.indentedBox(box), self, 'minNumOfInstances', 1, 100, 
214                   step=1, label='#:', labelWidth=15, 
215                   callback=self.FilterAndDisplayGraph, 
216                   callbackOnReturn=True, 
217                   tooltip="Min. number of input genes mapped to a term")
218       
219        OWGUI.checkBox(box, self, "filterByPValue_nofdr", "p-value",
220                       callback=self.FilterAndDisplayGraph, 
221                       tooltip="Filter by term p-value")
222        OWGUI.doubleSpin(OWGUI.indentedBox(box), self, 'maxPValue_nofdr', 1e-8, 1, 
223                         step=1e-8,  label='p:', labelWidth=15, 
224                         callback=self.FilterAndDisplayGraph, 
225                         callbackOnReturn=True, 
226                         tooltip="Max term p-value")
227
228        #use filterByPValue for FDR, as it was the default in prior versions
229        OWGUI.checkBox(box, self, "filterByPValue", "FDR",
230                       callback=self.FilterAndDisplayGraph, 
231                       tooltip="Filter by term FDR")
232        OWGUI.doubleSpin(OWGUI.indentedBox(box), self, 'maxPValue', 1e-8, 1, 
233                         step=1e-8,  label='p:', labelWidth=15, 
234                         callback=self.FilterAndDisplayGraph, 
235                         callbackOnReturn=True, 
236                         tooltip="Max term p-value")
237
238        box = OWGUI.widgetBox(box, "Significance test")
239
240        OWGUI.radioButtonsInBox(box, self, "probFunc", ["Binomial", "Hypergeometric"], 
241                                tooltips=["Use binomial distribution test", 
242                                          "Use hypergeometric distribution test"], 
243                                callback=self.Update)
244        box = OWGUI.widgetBox(self.filterTab, "Evidence codes in annotation", 
245                              addSpace=True)
246        self.evidenceCheckBoxDict = {}
247        for etype in obiGO.evidenceTypesOrdered:
248            self.evidenceCheckBoxDict[etype] = OWGUI.checkBox(box, self, "useEvidence"+etype, etype,
249                                            callback=self.Update, tooltip=obiGO.evidenceTypes[etype])
250       
251        ##Select tab
252        self.selectTab = OWGUI.createTabPage(self.tabs, "Select")
253        box = OWGUI.radioButtonsInBox(self.selectTab, self, "selectionDirectAnnotation", 
254                                      ["Directly or Indirectly", "Directly"], 
255                                      box="Annotated genes", 
256                                      callback=self.ExampleSelection)
257       
258        box = OWGUI.widgetBox(self.selectTab, "Output", addSpace=True)
259        OWGUI.radioButtonsInBox(box, self, "selectionDisjoint", 
260                                btnLabels=["All selected genes", 
261                                           "Term-specific genes", 
262                                           "Common term genes"], 
263                                tooltips=["Outputs genes annotated to all selected GO terms", 
264                                          "Outputs genes that appear in only one of selected GO terms", 
265                                          "Outputs genes common to all selected GO terms"], 
266                                callback=[self.ExampleSelection,
267                                          self.UpdateAddClassButton])
268        self.addClassCB = OWGUI.checkBox(box, self, "selectionAddTermAsClass",
269                                         "Add GO Term as class", 
270                                         callback=self.ExampleSelection)
271
272        # ListView for DAG, and table for significant GOIDs
273        self.DAGcolumns = ['GO term', 'Cluster', 'Reference', 'p-value', 'FDR', 'Genes', 'Enrichment']
274       
275        self.splitter = QSplitter(Qt.Vertical, self.mainArea)
276        self.mainArea.layout().addWidget(self.splitter)
277
278        # list view
279        self.listView = GOTreeWidget(self.splitter)
280        self.listView.setSelectionMode(QAbstractItemView.ExtendedSelection)
281        self.listView.setAllColumnsShowFocus(1)
282        self.listView.setColumnCount(len(self.DAGcolumns))
283        self.listView.setHeaderLabels(self.DAGcolumns)
284       
285        self.listView.header().setClickable(True)
286        self.listView.header().setSortIndicatorShown(True)
287        self.listView.setSortingEnabled(True)
288        self.listView.setItemDelegateForColumn(6, EnrichmentColumnItemDelegate(self))
289        self.listView.setRootIsDecorated(True)
290
291       
292        self.connect(self.listView, SIGNAL("itemSelectionChanged()"), self.ViewSelectionChanged)
293       
294        # table of significant GO terms
295        self.sigTerms = QTreeWidget(self.splitter)
296        self.sigTerms.setColumnCount(len(self.DAGcolumns))
297        self.sigTerms.setHeaderLabels(self.DAGcolumns)
298        self.sigTerms.setSortingEnabled(True)
299        self.sigTerms.setSelectionMode(QAbstractItemView.ExtendedSelection)
300        self.sigTerms.setItemDelegateForColumn(6, EnrichmentColumnItemDelegate(self))
301       
302        self.connect(self.sigTerms, SIGNAL("itemSelectionChanged()"), self.TableSelectionChanged)
303        self.splitter.show()
304
305        self.sigTableTermsSorted = []
306        self.graph = {}
307       
308        self.loadedAnnotationCode = "---"
309       
310        self.inputTab.layout().addStretch(1)
311        self.filterTab.layout().addStretch(1)
312        self.selectTab.layout().addStretch(1)
313       
314        self.resize(1000, 800)
315
316        self.clusterDataset = None
317        self.referenceDataset = None
318        self.ontology = None
319        self.annotations = None
320        self.treeStructRootKey = None
321        self.probFunctions = [obiProb.Binomial(), obiProb.Hypergeometric()]
322        self.selectedTerms = []
323       
324        self.connect(self, SIGNAL("widgetStateChanged(QString, int, QString)"), self.onStateChanged)
325       
326    def UpdateOrganismComboBox(self):
327        try:
328            if self.annotationCodes and len(self.annotationCodes) > self.annotationIndex:
329                currAnnotationCode = self.annotationCodes[self.annotationIndex]
330            else:
331                currAnnotationCode = None
332            self.progressBarInit()
333            with orngServerFiles.DownloadProgress.setredirect(self.progressBarSet):
334                self.annotationFiles = listAvailable()
335            self.progressBarFinished()
336            self.annotationCodes = sorted(self.annotationFiles.keys())
337            self.annotationComboBox.clear()
338            self.annotationComboBox.addItems(self.annotationCodes)
339            self.annotationComboBox.setCurrentIndex(self.annotationIndex)
340        finally:
341            if self.signalManager:
342                self.signalManager.freeze(self).pop()
343
344    def UpdateGeneMatcher(self):
345        dialog = GeneMatcherDialog(self, defaults=self.geneMatcherSettings, modal=True)
346        if dialog.exec_():
347            self.geneMatcherSettings = [getattr(dialog, item[0]) for item in dialog.items]
348            if self.annotations:
349                self.SetGeneMatcher()
350                if self.clusterDataset:
351                    self.Update()
352               
353    def Update(self):
354        if self.clusterDataset:
355            pb = OWGUI.ProgressBar(self, 100)
356            self.Load(pb=pb)
357            self.FilterUnknownGenes()
358            graph = self.Enrichment(pb=pb)
359            self.SetGraph(graph)
360
361    def UpdateGOAndAnnotation(self, tags=[]):
362        from .OWUpdateGenomicsDatabases import OWUpdateGenomicsDatabases
363        w = OWUpdateGenomicsDatabases(parent = self, searchString=" ".join(tags))
364        w.setModal(True)
365        w.show()
366        self.UpdateAnnotationComboBox()
367
368    def UpdateAnnotationComboBox(self):
369        if self.annotationCodes:
370            curr = self.annotationCodes[min(self.annotationIndex, len(self.annotationCodes)-1)]
371        else:
372            curr = None
373        self.annotationFiles = listAvailable()
374        self.annotationCodes = self.annotationFiles.keys()
375        index = curr and self.annotationCodes.index(curr) or 0
376        self.annotationComboBox.clear()
377        self.annotationComboBox.addItems(self.annotationCodes)
378        self.annotationComboBox.setCurrentIndex(index)
379        if not self.annotationCodes:
380            self.error(0, "No downloaded annotations!!\nClick the update button and update annotationa for at least one organism!")
381        else:
382            self.error(0)
383
384    def SetGenesComboBox(self):
385        self.candidateGeneAttrs = self.clusterDataset.domain.variables + self.clusterDataset.domain.getmetas().values()
386        self.candidateGeneAttrs = filter(lambda v: v.varType in [orange.VarTypes.String,
387                                                                 orange.VarTypes.Other,
388                                                                 orange.VarTypes.Discrete], 
389                                         self.candidateGeneAttrs)
390        self.geneAttrIndexCombo.clear()
391        self.geneAttrIndexCombo.addItems([a.name for a in  self.candidateGeneAttrs])
392
393    def FindBestGeneAttrAndOrganism(self):
394        if self.autoFindBestOrg: 
395            organismGenes = dict([(o,set(go.getCachedGeneNames(o))) for o in self.annotationCodes])
396        else:
397            currCode = self.annotationCodes[min(self.annotationIndex, len(self.annotationCodes)-1)]
398            filename = p_join(dataDir, self.annotationFiles[currCode])
399            try:
400                f = tarfile.open(filename)
401                info = [info for info in f.getmembers() if info.name.startswith("gene_names")].pop()
402                geneNames = cPickle.loads(f.extractfile(info).read().replace("\r\n", "\n"))
403            except Exception, ex:
404                geneNames = cPickle.loads(open(p_join(filename, "gene_names.pickle")).read().replace("\r\n", "\n"))
405            organismGenes = {currCode: set(geneNames)}
406        candidateGeneAttrs = self.clusterDataset.domain.attributes + self.clusterDataset.domain.getmetas().values()
407        candidateGeneAttrs = filter(lambda v: v.varType in [orange.VarTypes.String, 
408                                                            orange.VarTypes.Other, 
409                                                            orange.VarTypes.Discrete], 
410                                    candidateGeneAttrs)
411        attrNames = [v.name for v in self.clusterDataset.domain.variables]
412        cn = {}
413        for attr in candidateGeneAttrs:
414            vals = [str(e[attr]) for e in self.clusterDataset]
415            if any("," in val for val in vals):
416                vals = reduce(list.__add__, (val.split(",") for val in vals))
417            for organism, s in organismGenes.items():
418                l = filter(lambda a: a in s, vals)
419                cn[(attr,organism)] = len(set(l))
420        for organism, s in organismGenes.items():
421            l = filter(lambda a: a in s, attrNames)
422            cn[("_var_names_", organism)] = len(set(l))
423           
424        cn = cn.items()
425        cn.sort(lambda a,b:-cmp(a[1],b[1]))
426        ((bestAttr, organism), count) = cn[0]
427        if bestAttr=="_var_names_" and count<=len(attrNames)/10.0 or \
428           bestAttr!="_var_names_" and count<=len(self.clusterDataset)/10.0:
429            return
430       
431        self.annotationIndex = self.annotationCodes.index(organism)
432        if bestAttr=="_var_names_":
433            self.useAttrNames = True
434            self.geneAttrIndex = 0
435        else:
436            self.useAttrNames = False
437            self.geneAttrIndex = candidateGeneAttrs.index(bestAttr)
438
439    def SetClusterDataset(self, data=None):
440        if not self.annotationCodes:
441            QTimer.singleShot(200, lambda: self.SetClusterDataset(data))
442            return
443        self.closeContext()
444        self.clusterDataset = data
445        self.infoLabel.setText("\n")
446        if data:
447            self.SetGenesComboBox()
448            try:
449                taxid = data_hints.get_hint(data, "taxid", "")
450                code = obiGO.from_taxid(taxid)
451                filename = "gene_association.%s.tar.gz" % code
452                if filename in self.annotationFiles.values():
453                    self.annotationIndex = \
454                            [i for i, name in enumerate(self.annotationCodes) \
455                             if self.annotationFiles[name] == filename].pop()
456            except Exception:
457                pass
458            self.useAttrNames = data_hints.get_hint(data, "genesinrows",
459                                                    self.useAttrNames)
460            self.openContext("", data)
461            self.Update()
462        else:
463            self.infoLabel.setText("No data on input\n")
464            self.warning(0)
465            self.warning(1)
466            self.openContext("", None)
467            self.ClearGraph()
468            self.send("Selected Examples", None)
469            self.send("Unselected Examples", None)
470            self.send("Example With Unknown Genes", None)
471            self.send("Enrichment Report", None)
472
473    def SetReferenceDataset(self, data=None):
474        self.referenceDataset = data
475        self.referenceRadioBox.buttons[1].setDisabled(not bool(data))
476        self.referenceRadioBox.buttons[1].setText("Reference set")
477        if self.clusterDataset and self.useReferenceDataset:
478            self.useReferenceDataset = 0 if not data else 1
479            graph = self.Enrichment()
480            self.SetGraph(graph)
481        elif self.clusterDataset:
482            self.UpdateReferenceSetButton()
483
484    def UpdateReferenceSetButton(self):
485        allgenes, refgenes = None, None
486        if self.referenceDataset:
487            try:
488                allgenes = self.GenesFromExampleTable(self.referenceDataset)
489            except Exception:
490                allgenes = []
491            refgenes, unknown = self.FilterAnnotatedGenes(allgenes)
492        self.referenceRadioBox.buttons[1].setDisabled(not bool(allgenes))
493        self.referenceRadioBox.buttons[1].setText("Reference set " + ("(%i genes, %i matched)" % (len(allgenes), len(refgenes)) if allgenes and refgenes else ""))
494
495    def GenesFromExampleTable(self, data):
496        if self.useAttrNames:
497            genes = [v.name for v in data.domain.variables]
498        else:
499            attr = self.candidateGeneAttrs[min(self.geneAttrIndex, len(self.candidateGeneAttrs) - 1)]
500            genes = [str(ex[attr]) for ex in data if not ex[attr].isSpecial()]
501            if any("," in gene for gene in genes):
502                self.information(0, "Separators detected in gene names. Assuming multiple genes per example.")
503                genes = reduce(list.__add__, (genes.split(",") for genes in genes))
504        return genes
505       
506    def FilterAnnotatedGenes(self, genes):
507        matchedgenes = self.annotations.GetGeneNamesTranslator(genes).values()
508        return matchedgenes, [gene for gene in genes if gene not in matchedgenes]
509       
510    def FilterUnknownGenes(self):
511        if not self.useAttrNames and self.candidateGeneAttrs:
512            geneAttr = self.candidateGeneAttrs[min(self.geneAttrIndex, len(self.candidateGeneAttrs)-1)]
513            examples = []
514            for ex in self.clusterDataset:
515                if not any(self.annotations.genematcher.match(n.strip()) for n in str(ex[geneAttr]).split(",")):
516                    examples.append(ex)
517
518            self.send("Example With Unknown Genes", examples and orange.ExampleTable(examples) or None)
519        else:
520            self.send("Example With Unknown Genes", None)
521
522    def Load(self, pb=None):
523        go_files, tax_files = orngServerFiles.listfiles("GO"), orngServerFiles.listfiles("Taxonomy")
524        calls = []
525        pb, finish = (OWGUI.ProgressBar(self, 0), True) if pb is None else (pb, False)
526        count = 0
527        if not tax_files:
528            calls.append(("Taxonomy", "ncbi_taxnomy.tar.gz"))
529            count += 1
530        org = self.annotationCodes[min(self.annotationIndex, len(self.annotationCodes)-1)]
531        if org != self.loadedAnnotationCode:
532            count += 1
533            if self.annotationFiles[org] not in go_files:
534                calls.append(("GO", self.annotationFiles[org]))
535                count += 1
536               
537        if "gene_ontology_edit.obo.tar.gz" not in go_files:
538            calls.append(("GO", "gene_ontology_edit.obo.tar.gz"))
539            count += 1
540        if not self.ontology:
541            count += 1
542        pb.iter += count*100
543       
544        for i, args in enumerate(calls):
545            orngServerFiles.localpath_download(*args, **dict(callback=pb.advance))
546           
547        i = len(calls)
548        if not self.ontology:
549            self.ontology = obiGO.Ontology(progressCallback=lambda value: pb.advance())
550            i+=1
551        if org != self.loadedAnnotationCode:
552            self.annotations = None
553            gc.collect() # Force run garbage collection.
554            code = self.annotationFiles[org].split(".")[-3]
555            self.annotations = obiGO.Annotations(code, genematcher=obiGene.GMDirect(), progressCallback=lambda value: pb.advance())
556            i+=1
557            self.loadedAnnotationCode = org
558            count = defaultdict(int)
559            geneSets = defaultdict(set)
560
561            for anno in self.annotations.annotations:
562                count[anno.evidence]+=1
563                geneSets[anno.evidence].add(anno.geneName)
564            for etype in obiGO.evidenceTypesOrdered:
565                self.evidenceCheckBoxDict[etype].setEnabled(bool(count[etype]))
566                self.evidenceCheckBoxDict[etype].setText(etype+": %i annots(%i genes)" % (count[etype], len(geneSets[etype])))
567        if finish:
568            pb.finish()
569           
570    def SetGeneMatcher(self):
571        if self.annotations:
572            taxid = self.annotations.taxid
573            matchers = []
574            for matcher, use in zip([obiGene.GMGO, obiGene.GMKEGG, obiGene.GMNCBI, obiGene.GMAffy], self.geneMatcherSettings):
575                if use:
576                    try:
577                        if taxid == "352472":
578                            matchers.extend([matcher(taxid), obiGene.GMDicty(),
579                                            [matcher(taxid), obiGene.GMDicty()]])
580                            # The reason machers are duplicated is that we want `matcher` or `GMDicty` to
581                            # match genes by them self if possible. Only use the joint matcher if they fail.   
582                        else:
583                            matchers.append(matcher(taxid))
584                    except Exception, ex:
585                        print ex
586            self.annotations.genematcher = obiGene.matcher(matchers)
587            self.annotations.genematcher.set_targets(self.annotations.geneNames)
588           
589    def Enrichment(self, pb=None):
590        pb = OWGUI.ProgressBar(self, 100) if pb is None else pb
591        if not self.annotations.ontology:
592            self.annotations.ontology = self.ontology
593           
594        if isinstance(self.annotations.genematcher, obiGene.GMDirect):
595            self.SetGeneMatcher()
596        self.error(1)
597        self.warning([0, 1])
598        try:   
599            if self.useAttrNames:
600                clusterGenes = [v.name for v in self.clusterDataset.domain.variables]
601                self.information(0)
602            else:
603                geneAttr = self.candidateGeneAttrs[min(self.geneAttrIndex, len(self.candidateGeneAttrs)-1)]
604                clusterGenes = [str(ex[geneAttr]) for ex in self.clusterDataset if not ex[geneAttr].isSpecial()]
605                if any("," in gene for gene in clusterGenes):
606                    self.information(0, "Separators detected in cluster gene names. Assuming multiple genes per example.")
607                    clusterGenes = reduce(list.__add__, (genes.split(",") for genes in clusterGenes))
608                else:
609                    self.information(0)
610        except Exception, ex:
611            self.error(1, "Failed to extract gene names from input dataset! %s" % str(ex))
612            return {}
613        genesCount = len(clusterGenes)
614        genesSetCount = len(set(clusterGenes))
615       
616        self.clusterGenes = clusterGenes = self.annotations.GetGeneNamesTranslator(clusterGenes).values()
617       
618#        self.clusterGenes = clusterGenes = filter(lambda g: g in self.annotations.aliasMapper or g in self.annotations.additionalAliases, clusterGenes)
619        self.infoLabel.setText("%i unique genes on input\n%i (%.1f%%) genes with known annotations" % (genesSetCount, len(clusterGenes), 100.0*len(clusterGenes)/genesSetCount if genesSetCount else 0.0))
620       
621        referenceGenes = None
622        if self.referenceDataset:
623            try:
624                if self.useAttrNames:
625                    referenceGenes = [v.name for v in self.referenceDataset.domain.variables]
626                    self.information(1)
627                else:
628                    referenceGenes = [str(ex[geneAttr]) for ex in self.referenceDataset if not ex[geneAttr].isSpecial()]
629                    if any("," in gene for gene in clusterGenes):
630                        self.information(1, "Separators detected in reference gene names. Assuming multiple genes per example.")
631                        referenceGenes = reduce(list.__add__, (genes.split(",") for genes in referenceGenes))
632                    else:
633                        self.information(1)
634
635                refc = len(referenceGenes)
636#                referenceGenes = filter(lambda g: g in self.annotations.aliasMapper or g in self.annotations.additionalAliases, referenceGenes)
637                referenceGenes = self.annotations.GetGeneNamesTranslator(referenceGenes).values()
638                self.referenceRadioBox.buttons[1].setText("Reference set (%i genes, %i matched)" % (refc, len(referenceGenes)))
639                self.referenceRadioBox.buttons[1].setDisabled(False)
640                self.information(2)
641            except Exception, er:
642                if not self.referenceDataset:
643                    self.information(2, "Unable to extract gene names from reference dataset. Using entire genome for reference")
644                else:
645                    self.referenceRadioBox.buttons[1].setText("Reference set")
646                    self.referenceRadioBox.buttons[1].setDisabled(True)
647                referenceGenes = self.annotations.geneNames
648                self.useReferenceDataset = 0
649        else:
650            self.useReferenceDataset = 0
651        if not self.useReferenceDataset:
652            self.information(2)
653            self.information(1)
654            referenceGenes = self.annotations.geneNames
655        self.referenceGenes = referenceGenes
656        evidences = []
657        for etype in obiGO.evidenceTypesOrdered:
658            if getattr(self, "useEvidence"+etype):
659                evidences.append(etype)
660        aspect = ["P", "C", "F"][self.aspectIndex]
661       
662        if clusterGenes:
663            self.terms = terms = self.annotations.GetEnrichedTerms(clusterGenes, referenceGenes, evidences, aspect=aspect,
664                                                                   prob=self.probFunctions[self.probFunc], useFDR=False,
665                                                                   progressCallback=lambda value:pb.advance() )
666            ids = []
667            pvals = []
668            for i,d in self.terms.items():
669                ids.append(i)
670                pvals.append(d[1])
671            for i,fdr in zip(ids, obiProb.FDR(pvals)): #save FDR as the last part of the tuple
672                terms[i] = tuple(list(terms[i]) + [ fdr ])
673
674        else:
675            self.terms = terms = {}
676        if not self.terms:
677            self.warning(0, "No enriched terms found.")
678        else:
679            self.warning(0)
680           
681        pb.finish()
682        self.treeStructDict = {}
683        ids = self.terms.keys()
684       
685        self.treeStructRootKey = None
686       
687        parents = {}
688        for id in ids:
689            parents[id] = set([term for typeId, term in self.ontology[id].related])
690           
691        children = {}
692        for term in self.terms:
693            children[term] = set([id for id in ids if term in parents[id]])
694           
695        for term in self.terms:
696            self.treeStructDict[term] = TreeNode(self.terms[term], children[term])
697            if not self.ontology[term].related and not getattr(self.ontology[term], "is_obsolete", False):
698                self.treeStructRootKey = term
699        return terms
700       
701    def FilterGraph(self, graph):
702        if self.filterByPValue_nofdr:
703            graph = obiGO.filterByPValue(graph, self.maxPValue_nofdr)
704        if self.filterByPValue: #FDR
705            graph = dict(filter(lambda (k, e): e[3] <= self.maxPValue, graph.items()))
706        if self.filterByNumOfInstances:
707            graph = dict(filter(lambda (id,(genes, p, rc, fdr)):len(genes)>=self.minNumOfInstances, graph.items()))
708        return graph
709
710    def FilterAndDisplayGraph(self):
711        if self.clusterDataset:
712            self.graph = self.FilterGraph(self.originalGraph)
713            if self.originalGraph and not self.graph:
714                self.warning(1, "All found terms were filtered out.")
715            else:
716                self.warning(1)
717            self.ClearGraph()
718            self.DisplayGraph()
719
720    def SetGraph(self, graph=None):
721        self.originalGraph = graph
722        if graph:
723            self.FilterAndDisplayGraph()
724        else:
725            self.graph = {}
726            self.ClearGraph()
727
728    def ClearGraph(self):
729        self.listView.clear()
730        self.listViewItems=[]
731        self.sigTerms.clear()
732
733    def DisplayGraph(self):
734        fromParentDict = {}
735        self.termListViewItemDict = {}
736        self.listViewItems = []
737        enrichment = lambda t:float(len(t[0])) / t[2] * (float(len(self.referenceGenes))/len(self.clusterGenes))
738        maxFoldEnrichment = max([enrichment(term) for term in self.graph.values()] or [1])
739
740
741        def addNode(term, parent, parentDisplayNode):
742            if (parent, term) in fromParentDict:
743                return
744            if term in self.graph:
745                displayNode = GOTreeWidgetItem(self.ontology[term], self.graph[term], len(self.clusterGenes), len(self.referenceGenes), maxFoldEnrichment, parentDisplayNode)
746                displayNode.goId = term
747                self.listViewItems.append(displayNode)
748                if term in self.termListViewItemDict:
749                    self.termListViewItemDict[term].append(displayNode)
750                else:
751                    self.termListViewItemDict[term] = [displayNode]
752                fromParentDict[(parent, term)] = True
753                parent = term
754            else:
755                displayNode = parentDisplayNode
756
757            for c in self.treeStructDict[term].children:
758                addNode(c, parent, displayNode)
759
760        if self.treeStructDict:
761            addNode(self.treeStructRootKey, None, self.listView)
762
763        terms = self.graph.items()
764        terms = sorted(terms, key=lambda item: item[1][1])
765        self.sigTableTermsSorted = [t[0] for t in terms]
766
767        self.sigTerms.clear()
768        for i, (t_id, (genes, p_value, refCount, fdr)) in enumerate(terms):
769            item = GOTreeWidgetItem(self.ontology[t_id],
770                                    (genes, p_value, refCount, fdr),
771                                    len(self.clusterGenes),
772                                    len(self.referenceGenes),
773                                    maxFoldEnrichment,
774                                    self.sigTerms)
775            item.goId = t_id
776
777        self.listView.expandAll()
778        for i in range(5):
779            self.listView.resizeColumnToContents(i)
780            self.sigTerms.resizeColumnToContents(i)
781        self.sigTerms.resizeColumnToContents(6)
782        width = min(self.listView.columnWidth(0), 350)
783        self.listView.setColumnWidth(0, width)
784        self.sigTerms.setColumnWidth(0, width)
785
786        # Create and send the enrichemnt report table.
787        termsDomain = orange.Domain(
788            [orange.StringVariable("GO Term Id"),
789             orange.StringVariable("GO Term Name"),
790             orange.FloatVariable("Cluster Frequency"),
791             orange.FloatVariable("Reference Frequency"),
792             orange.FloatVariable("p-value"),
793             orange.FloatVariable("FDR"),
794             orange.FloatVariable("Enrichment"),
795             orange.StringVariable("Genes")
796             ], None)
797
798        terms = [[t_id,
799                  self.ontology[t_id].name,
800                  float(len(genes)) / len(self.clusterGenes),
801                  float(r_count) / len(self.referenceGenes),
802                  p_value,
803                  fdr,
804                  float(len(genes)) / len(self.clusterGenes) * \
805                  float(len(self.referenceGenes)) / r_count,
806                  ",".join(genes)
807                  ]
808                 for t_id, (genes, p_value, r_count, fdr) in terms]
809
810        if terms:
811            termsTable = orange.ExampleTable(termsDomain, terms)
812        else:
813            termsTable = orange.ExampleTable(termsDomain)
814        self.send("Enrichment Report", termsTable)
815
816    def ViewSelectionChanged(self):
817        if self.selectionChanging:
818            return
819
820        self.selectionChanging = 1
821        self.selectedTerms = []
822        selected = self.listView.selectedItems()
823        self.selectedTerms = list(set([lvi.term.id for lvi in selected]))
824        self.ExampleSelection()
825        self.selectionChanging = 0
826
827    def TableSelectionChanged(self):
828        if self.selectionChanging:
829            return
830       
831        self.selectionChanging = 1
832        self.selectedTerms = []
833        selectedIds = set([self.sigTerms.itemFromIndex(index).goId for index in self.sigTerms.selectedIndexes()])
834       
835        for i in range(self.sigTerms.topLevelItemCount()):
836            item = self.sigTerms.topLevelItem(i)
837            selected = item.goId in selectedIds
838            term = item.goId
839           
840            if selected:
841                self.selectedTerms.append(term)
842               
843            for lvi in self.termListViewItemDict[term]:
844                try:
845                    lvi.setSelected(selected)
846                    if selected: lvi.setExpanded(True)
847                except RuntimeError:    ##Underlying C/C++ object deleted (why??)
848                    pass
849               
850        self.ExampleSelection()
851        self.selectionChanging = 0
852           
853   
854    def UpdateAddClassButton(self):
855        self.addClassCB.setEnabled(self.selectionDisjoint == 1)
856       
857    def ExampleSelection(self):
858        selectedExamples = []
859        unselectedExamples = []
860        selectedGenes = []
861       
862        #change by Marko. don't do anything if there is no3 dataset
863        if not self.clusterDataset:
864            return
865       
866        selectedGenes = reduce(set.union, [v[0] for id, v in self.graph.items() if id in self.selectedTerms], set())
867        evidences = []
868        for etype in obiGO.evidenceTypesOrdered:
869            if getattr(self, "useEvidence"+etype):
870                evidences.append(etype)
871        allTerms = self.annotations.GetAnnotatedTerms(selectedGenes, 
872                          directAnnotationOnly=self.selectionDirectAnnotation, 
873                          evidenceCodes=evidences)
874           
875        if self.selectionDisjoint:
876            count = defaultdict(int)
877            for term in self.selectedTerms:
878                for g in allTerms.get(term, []):
879                    count[g]+=1
880            ccount = 1 if self.selectionDisjoint==1 else len(self.selectedTerms)
881            selectedGenes = [gene for gene, c in count.items() if c==ccount and gene in selectedGenes]
882        else:
883            selectedGenes = reduce(set.union, [allTerms.get(term, []) for term in self.selectedTerms], set())
884
885        if self.useAttrNames:
886            vars = [self.clusterDataset.domain[gene] for gene in set(selectedGenes)]
887            newDomain = orange.Domain(vars, self.clusterDataset.domain.classVar)
888            newdata = orange.ExampleTable(newDomain, self.clusterDataset)
889            self.send("Selected Examples", newdata)
890            self.send("Unselected Examples", None)
891        elif self.candidateGeneAttrs:
892            geneAttr = self.candidateGeneAttrs[min(self.geneAttrIndex, len(self.candidateGeneAttrs)-1)]
893            if self.selectionDisjoint == 1:
894                goVar = orange.EnumVariable("GO Term", values=list(self.selectedTerms))
895                newDomain = orange.Domain(self.clusterDataset.domain.variables, goVar)
896                newDomain.addmetas(self.clusterDataset.domain.getmetas())
897#            else:
898#                goVar = orange.StringVariable("GO Terms")
899#                newDomain = orange.Domain(self.clusterDataset.domain)
900#                newDomain.addmeta(orange.newmetaid(), goVar)
901           
902           
903            for ex in self.clusterDataset:
904                if not ex[geneAttr].isSpecial() and any(gene in selectedGenes for gene in str(ex[geneAttr]).split(",")):
905                    if self.selectionDisjoint == 1 and self.selectionAddTermAsClass:
906                        terms = filter(lambda term: any(gene in self.graph[term][0] for gene in str(ex[geneAttr]).split(",")) , self.selectedTerms)
907                        term = sorted(terms)[0]
908                        ex =  orange.Example(newDomain, ex)
909                        ex[goVar] = goVar(term)
910#                        ex.setclass(newClass(term))
911                    selectedExamples.append(ex)
912                else:
913                    unselectedExamples.append(ex)
914                   
915            if selectedExamples:
916                selectedExamples = orange.ExampleTable(selectedExamples)
917            else:
918                selectedExamples = None
919               
920            if unselectedExamples:
921                unselectedExamples = orange.ExampleTable(unselectedExamples)
922            else:
923                unselectedExamples = None
924
925            self.send("Selected Examples", selectedExamples)
926            self.send("Unselected Examples", unselectedExamples)
927
928    def ShowInfo(self):
929        dialog = QDialog(self)
930        dialog.setModal(False)
931        dialog.setLayout(QVBoxLayout())
932        label = QLabel(dialog)
933        label.setText("Ontology:\n"+self.ontology.header if self.ontology else "Ontology not loaded!")
934        dialog.layout().addWidget(label)
935
936        label = QLabel(dialog)
937        label.setText("Annotations:\n"+self.annotations.header.replace("!", "") if self.annotations else "Annotations not loaded!")
938        dialog.layout().addWidget(label)
939        dialog.show()
940       
941    def sendReport(self):
942        self.reportSettings("Settings", [("Organism", self.annotationCodes[min(self.annotationIndex, len(self.annotationCodes) - 1)]),
943                                         ("Significance test", ("Binomial" if self.probFunc == 0 else "Hypergeometric") )])
944        self.reportSettings("Filter", ([("Min cluster size", self.minNumOfInstances)] if self.filterByNumOfInstances else []) + \
945                                      ([("Max p-value", self.maxPValue_nofdr)] if self.filterByPValue_nofdr else []) + \
946                                      ([("Max FDR", self.maxPValue)] if self.filterByPValue else []))
947
948        def treeDepth(item):
949            return 1 + max([treeDepth(item.child(i)) for i in range(item.childCount())] +[0])
950       
951        def printTree(item, level, treeDepth):
952            text = '<tr>' + '<td width=16px></td>' * level
953            text += '<td colspan="%i">%s: %s</td>' % (treeDepth - level, item.term.id, item.term.name)
954            text += ''.join('<td>%s</td>' % item.text(i) for i in range(1, 5) + [6]) + '</tr>\n'
955            for i in range(item.childCount()):
956                text += printTree(item.child(i), level + 1, treeDepth)
957            return text
958       
959        treeDepth = max([treeDepth(self.listView.topLevelItem(i)) for i in range(self.listView.topLevelItemCount())] + [0])
960       
961        tableText = '<table>\n<tr>' + ''.join('<th>%s</th>' % s for s in ["Term:", "List:", "Reference:", "p-value:", "FDR", "Enrichment:"]) + '</tr>'
962       
963        treeText = '<table>\n' +  '<th colspan="%i">%s</th>' % (treeDepth, "Term:") 
964        treeText += ''.join('<th>%s</th>' % s for s in ["List:", "Reference:", "p-value:", "FDR", "Enrichment:"]) + '</tr>'
965       
966        for index in range(self.sigTerms.topLevelItemCount()):
967            item = self.sigTerms.topLevelItem(index)
968            tableText += printTree(item, 0, 1) 
969        tableText += '</table>' 
970       
971        for index in range(self.listView.topLevelItemCount()):
972            item = self.listView.topLevelItem(index)
973            treeText += printTree(item, 0, treeDepth)
974       
975        self.reportSection("Enriched Terms")
976        self.reportRaw(tableText)
977       
978        self.reportSection("Enriched Terms in the Ontology Tree")
979        self.reportRaw(treeText)
980       
981    def onStateChanged(self, stateType, id, text):
982        if stateType == "Warning":
983            self.listView._userMessage = text
984            self.listView.viewport().update()
985           
986    def onDeleteWidget(self):
987        """ Called before the widget is removed from the canvas.
988        """
989        self.annotations = None
990        self.ontology = None
991        gc.collect() # Force collection
992       
993
994fmtp = lambda score: "%0.5f" % score if score > 10e-4 else "%0.1e" % score
995fmtpdet = lambda score: "%0.9f" % score if score > 10e-4 else "%0.5e" % score
996
997class GOTreeWidgetItem(QTreeWidgetItem):
998    def __init__(self, term, enrichmentResult, nClusterGenes, nRefGenes, maxFoldEnrichment, parent):
999        QTreeWidgetItem.__init__(self, parent)
1000        self.term = term
1001        self.enrichmentResult = enrichmentResult
1002        self.nClusterGenes = nClusterGenes
1003        self.nRefGenes = nRefGenes
1004        self.maxFoldEnrichment = maxFoldEnrichment
1005        self.enrichment = enrichment = lambda t:float(len(t[0])) / t[2] * (float(nRefGenes)/nClusterGenes)
1006        self.setText(0, term.name)
1007        fmt = "%" + str(-int(math.log(nClusterGenes))) + "i (%.2f%%)"
1008        self.setText(1, fmt % (len(enrichmentResult[0]), 100.0*len(self.enrichmentResult[0])/nClusterGenes))
1009        fmt = "%" + str(-int(math.log(nRefGenes))) + "i (%.2f%%)"
1010        self.setText(2, fmt % (enrichmentResult[2], 100.0*enrichmentResult[2]/nRefGenes))
1011        self.setText(3, fmtp(enrichmentResult[1]))
1012        self.setToolTip(3, fmtpdet(enrichmentResult[1]))
1013        self.setText(4, fmtp(enrichmentResult[3])) #FDR
1014        self.setToolTip(4, fmtpdet(enrichmentResult[3]))
1015        self.setText(5, ", ".join(enrichmentResult[0]))
1016        self.setText(6, "%.2f" % (enrichment(enrichmentResult)))
1017        self.setToolTip(6, "%.2f" % (enrichment(enrichmentResult)))
1018        self.setToolTip(0, "<p>" + term.__repr__()[6:].strip().replace("\n", "<br>"))
1019        self.sortByData = [term.name, len(self.enrichmentResult[0]), enrichmentResult[2], enrichmentResult[1], enrichmentResult[3], ", ".join(enrichmentResult[0]), enrichment(enrichmentResult)]
1020
1021    def data(self, col, role):
1022        if role == Qt.UserRole:
1023            return QVariant(self.enrichment(self.enrichmentResult) / self.maxFoldEnrichment)
1024        else:
1025            return QTreeWidgetItem.data(self, col, role)
1026
1027    def __lt__(self, other):
1028        col = self.treeWidget().sortColumn()
1029        return self.sortByData[col] < other.sortByData[col]
1030   
1031class EnrichmentColumnItemDelegate(QItemDelegate):
1032    def paint(self, painter, option, index):
1033        self.drawBackground(painter, option, index)
1034        value, ok = index.data(Qt.UserRole).toDouble()
1035        if ok:
1036            painter.save()
1037            painter.setBrush(QBrush(Qt.white, Qt.SolidPattern))
1038            painter.drawRect(option.rect)
1039            painter.setBrush(QBrush(Qt.blue, Qt.SolidPattern))
1040            painter.drawRect(option.rect.x(), option.rect.y(), value*(option.rect.width()-1), option.rect.height()-1)
1041            painter.restore()
1042        else:
1043            QItemDelegate.paint(self, painter, option, index)
1044       
1045       
1046class GeneMatcherDialog(OWWidget):
1047    items = [("useGO", "Use gene names from Gene Ontology annotations"),
1048             ("useKEGG", "Use gene names from KEGG Genes database"),
1049             ("useNCBI", "Use gene names from NCBI Gene info database"),
1050             ("useAffy", "Use Affymetrix platform reference ids")]
1051    settingsList = [item[0] for item in items]
1052    def __init__(self, parent=None, defaults=[True, False, False, False], enabled=[False, True, True, True], **kwargs):
1053        OWWidget.__init__(self, parent, **kwargs)
1054        for item, default in zip(self.items, defaults):
1055            setattr(self, item[0], default)
1056           
1057        self.loadSettings()
1058        for item, enable in zip(self.items, enabled):
1059            cb = OWGUI.checkBox(self, self, *item)
1060            cb.setEnabled(enable)
1061           
1062        box = OWGUI.widgetBox(self, orientation="horizontal")
1063        OWGUI.button(box, self, "OK", callback=self.accept)
1064        OWGUI.button(box, self, "Cancel", callback=self.reject)
1065       
1066       
1067if __name__=="__main__":
1068    import sys
1069    app = QApplication(sys.argv)
1070    w=OWGOEnrichmentAnalysis()
1071    data = orange.ExampleTable("brown-selected.tab")
1072    w.show()
1073    w.SetClusterDataset(data)
1074    app.exec_()
1075    w.saveSettings()
1076       
Note: See TracBrowser for help on using the repository browser.