source: orange-bioinformatics/orangecontrib/bio/widgets/OWGeneInfo.py @ 1894:0c980fd08a69

Revision 1894:0c980fd08a69, 24.3 KB checked in by Ales Erjavec <ales.erjavec@…>, 6 months ago (diff)

Changed Gene Info widget initialization.

Line 
1"""
2<name>Gene Info</name>
3<description>Displays gene information from NCBI and other sources.</description>
4<priority>2010</priority>
5<contact>Ales Erjavec (ales.erjavec(@at@)fri.uni-lj.si)</contact>
6<icon>icons/GeneInfo.svg</icon>
7"""
8
9from __future__ import absolute_import, with_statement
10
11from collections import defaultdict
12from functools import partial
13
14from Orange.orng import orngServerFiles
15from Orange.orng.orngDataCaching import data_hints
16from Orange.OrangeWidgets import OWGUI
17from Orange.OrangeWidgets.OWWidget import *
18from Orange.utils import progress_bar_milestones
19
20import orange
21
22from .. import obiGene, obiTaxonomy
23
24
25NAME = "Gene Info"
26DESCRIPTION = "Displays gene information from NCBI and other sources."
27ICON = "icons/GeneInfo.svg"
28PRIORITY = 2010
29
30INPUTS = [("Examples", Orange.data.Table, "setData")]
31OUTPUTS = [("Selected Examples", Orange.data.Table)]
32
33REPLACES = ["_bioinformatics.widgets.OWGeneInfo.OWGeneInfo"]
34
35
36class TreeModel(QAbstractItemModel):
37    def __init__(self, data, header, parent):
38        QAbstractItemModel.__init__(self, parent)
39        self._data = [[QVariant(s) for s in row] for row in data]
40        self._dataDict = {}
41        self._header = header
42        self._roleData = {Qt.DisplayRole:self._data}
43        self._roleData = partial(defaultdict, partial(defaultdict, partial(defaultdict, QVariant)))(self._roleData)
44   
45    def setColumnLinks(self, column, links):
46        font =QFont()
47        font.setUnderline(True)
48        font = QVariant(font)
49        for i, link in enumerate(links):
50            self._roleData[LinkRole][i][column] = QVariant(link)
51            self._roleData[Qt.FontRole][i][column] = font
52            self._roleData[Qt.ForegroundRole][i][column] = QVariant(QColor(Qt.blue))
53   
54    def setRoleData(self, role, row, col, data):
55        self._roleData[role][row][col] = data
56       
57    def data(self, index, role=Qt.DisplayRole):
58        row, col = index.row(), index.column()
59        return self._roleData[role][row][col]
60       
61    def index(self, row, col, parent=QModelIndex()):
62        return self.createIndex(row, col, 0)
63   
64    def parent(self, index):
65        return QModelIndex()
66   
67    def rowCount(self, index=QModelIndex()):
68        if index.isValid():
69            return 0
70        else:
71            return len(self._data)
72       
73    def columnCount(self, index=QModelIndex()):
74        return len(self._header)
75
76    def headerData(self, section, orientation, role=Qt.DisplayRole):
77        if role==Qt.DisplayRole:
78            return QVariant(self._header[section])
79        return QVariant()
80
81from Orange.OrangeWidgets.OWGUI import LinkStyledItemDelegate, LinkRole
82
83def lru_cache(maxsize=100):
84    """ A least recently used cache function decorator.
85    """
86   
87    def decorating_function(func):
88        import functools
89        cache = {}
90       
91        functools.wraps(func)
92        def wrapped(*args, **kwargs):
93            key = args + tuple(sorted(kwargs.items()))
94            if key not in cache:
95                res = func(*args, **kwargs)
96                cache[key] = (time.time(), res)
97                if len(cache) > maxsize:
98                    key, (_, _) = min(cache.iteritems(), key=lambda item: item[1][0])
99                    del cache[key]
100            else:
101                _, res = cache[key]
102                cache[key] = (time.time(), res) # update the time
103               
104            return res
105       
106        def clear():
107            cache.clear()
108       
109        wrapped.clear = clear
110       
111        return wrapped
112    return decorating_function
113               
114class LinkFmt(object):
115    def __init__(self, link_fmt, name):
116        self.link_fmt = link_fmt
117        self.name = name
118       
119    def format(self, *args, **kwargs):
120        return Link(self.link_fmt.format(*args, **kwargs), **kwargs)
121   
122    def __repr__(self):
123        return "<LinkFmt " + repr(self.name) + " >"
124   
125    def __str__(self):
126        return self.name
127   
128class Link(object):
129    def __init__(self, link, text=None, **kwargs):
130        self.link = link
131        self.text = text if text is not None else "link"
132        self.__dict__.update(kwargs)
133       
134    def str(self):
135        return link
136   
137   
138@lru_cache(maxsize=2)
139def get_ncbi_info(taxid):
140    return obiGene.NCBIGeneInfo(taxid)
141
142def ncbi_info(taxid, genes):
143    info = get_ncbi_info(taxid)
144    schema_link = LinkFmt("http://www.ncbi.nlm.nih.gov/sites/entrez?Db=gene&Cmd=ShowDetailView&TermToSearch={gene_id}", name="NCBI ID")
145    schema = [schema_link, "Symbol", "Locus Tag", "Chromosome",
146              "Description", "Synonyms", "Nomenclature"]
147    ret = []
148    for gene in genes:
149        gi = info.get_info(gene)
150        if gi:
151            ret.append([schema_link.format(gene_id=gi.gene_id, text=gi.gene_id),
152                        gi.symbol + " (%s)" % gene if gene != gi.symbol else gi.symbol,
153                        gi.locus_tag or "",
154                        gi.chromosome or "",
155                        gi.description or "",
156                        ", ".join(gi.synonyms),
157                        gi.symbol_from_nomenclature_authority or ""
158                        ])
159        else:
160            ret.append(None)
161    return schema, ret
162   
163def dicty_info(taxid, genes):
164    from .. import obiDicty
165    info = obiDicty.DictyBase()
166    name_matcher = obiGene.GMDicty()
167    name_matcher.set_targets(info.info.keys())
168    schema_link = LinkFmt("http://dictybase.org/db/cgi-bin/gene_page.pl?dictybaseid={gene_id}", name="Dicty Base Id")
169    schema = [schema_link, "Name", "Synonyms", "Gene Products"]
170   
171    ret = []
172    for gene in genes:
173        gene = name_matcher.umatch(gene)
174        gi = info.info.get(gene, None)
175        if gi:
176            ret.append([schema_link.format(gene_id=gene, text=gene),
177                        gi[0] + " (%s)" % gene if gene != gi[0] else gi[0], # Gene Name
178                        ", ".join(gi[1]), # Synonyms
179                        gi[2] or "", # Gene Products
180                        ])
181           
182        else:
183            ret.append(None)
184   
185    return schema, ret
186   
187   
188INFO_SOURCES = {"default": [("NCBI Info", ncbi_info)],
189                "352472": [("NCBI Info", ncbi_info),
190                           ("Dicty Base", dicty_info)
191                           ]
192                }
193
194class OWGeneInfo(OWWidget):
195    settingsList = ["organismIndex", "geneAttr", "useAttr", "autoCommit"]
196    contextHandlers = {
197        "": DomainContextHandler(
198            "", ["organismIndex", "geneAttr", "useAttr", "useAltSource"]
199        )
200    }
201
202    def __init__(self, parent=None, signalManager=None, name="Gene Info"):
203        OWWidget.__init__(self, parent, signalManager, name)
204
205        self.inputs = [("Examples", ExampleTable, self.setData)]
206        self.outputs = [("Selected Examples", ExampleTable)]
207
208        self.organismIndex = 0
209        self.geneAttr = 0
210        self.useAttr = False
211        self.autoCommit = False
212        self.searchString = ""
213        self.selectionChangedFlag = False
214        self.useAltSource = 0
215        self.loadSettings()
216
217        self.__initialized = False
218
219        self.infoLabel = OWGUI.widgetLabel(
220            OWGUI.widgetBox(self.controlArea, "Info", addSpace=True),
221            "Initializing\n"
222        )
223
224        self.organisms = None
225        self.organismBox = OWGUI.widgetBox(
226            self.controlArea, "Organism", addSpace=True)
227
228        self.organismComboBox = OWGUI.comboBox(
229            self.organismBox, self, "organismIndex",
230            callback=self.setItems,
231            debuggingEnabled=0)
232
233        # For now only support one alt source, with a checkbox
234        # In the future this can be extended to multiple selections
235        self.altSourceCheck = OWGUI.checkBox(self.organismBox, self,
236                            "useAltSource", "Show information from dictyBase",
237                            callback=self.onAltSourceChange,
238#                            debuggingEnabled=0,
239                            )
240        self.altSourceCheck.hide()
241       
242        box = OWGUI.widgetBox(self.controlArea, "Gene names", addSpace=True)
243        self.geneAttrComboBox = OWGUI.comboBox(box, self, "geneAttr",
244                                "Gene atttibute", callback=self.setItems)
245       
246        c = OWGUI.checkBox(box, self, "useAttr", "Use attribute names",
247                           callback=self.setItems,
248                           disables=[(-1, self.geneAttrComboBox)])
249       
250        self.geneAttrComboBox.setDisabled(bool(self.useAttr))
251
252        box = OWGUI.widgetBox(self.controlArea, "Commit", addSpace=True)
253        b = OWGUI.button(box, self, "Commit", callback=self.commit)
254        c = OWGUI.checkBox(box, self, "autoCommit", "Commit on change")
255        OWGUI.setStopper(self, b, c, "selectionChangedFlag",
256                         callback=self.commit)
257       
258        ## A label for dictyExpress link
259        self.dictyExpressBox = OWGUI.widgetBox(self.controlArea, "Dicty Express")
260        self.linkLabel = OWGUI.widgetLabel(self.dictyExpressBox, "")
261        self.linkLabel.setOpenExternalLinks(False)
262        self.connect(self.linkLabel, SIGNAL("linkActivated(QString)"),
263                     self.onDictyExpressLink)
264        self.dictyExpressBox.hide()
265       
266        OWGUI.rubber(self.controlArea)
267
268        OWGUI.lineEdit(self.mainArea, self, "searchString", "Filter",
269                       callbackOnType=True, callback=self.searchUpdate)
270       
271        self.treeWidget = QTreeView(self.mainArea)
272        self.treeWidget.setRootIsDecorated(False)
273        self.treeWidget.setSelectionMode(QAbstractItemView.ExtendedSelection)
274        self.treeWidget.setItemDelegate(LinkStyledItemDelegate(self.treeWidget))
275        #self.connect(self.treeWidget, SIGNAL("itemSelectionChanged()"), self.commitIf)
276        self.treeWidget.viewport().setMouseTracking(True)
277        self.treeWidget.setSortingEnabled(True)
278        self.mainArea.layout().addWidget(self.treeWidget)
279       
280        box = OWGUI.widgetBox(self.mainArea, "",
281                              orientation="horizontal")
282        OWGUI.button(box, self, "Select Filtered",
283                     callback=self.selectFiltered)
284        OWGUI.button(box, self, "Clear Selection",
285                     callback=self.treeWidget.clearSelection)
286       
287        self.resize(1000, 700)
288
289        self.geneinfo = []
290        self.cells = []
291        self.row2geneinfo = {}
292        self.data = None
293        self.currentLoaded = None, None
294        self.selectionUpdateInProgress = False
295
296        self.setBlocking(True)
297        QTimer.singleShot(0, self.initialize)
298
299    def initialize(self):
300        if self.__initialized:
301            # Already initialized
302            return
303
304        self.organisms = sorted(
305            set([name.split(".")[-2] for name in
306                 orngServerFiles.listfiles("NCBI_geneinfo")] +
307                obiGene.NCBIGeneInfo.essential_taxids())
308        )
309
310        pb = OWGUI.ProgressBar(self, 100)
311        obiTaxonomy.ensure_downloaded(pb.advance)
312
313        self.organismComboBox.addItems(
314            [obiTaxonomy.name(tax_id) for tax_id in self.organisms]
315        )
316        pb.finish()
317
318        self.infoLabel.setText("No data on input\n")
319        self.__initialized = True
320        self.setBlocking(False)
321
322    def setData(self, data=None):
323        if not self.__initialized:
324            raise Exception("Not initialized")
325
326        self.closeContext()
327        self.data = data
328        if data:
329            self.geneAttrComboBox.clear()
330            self.attributes = [attr for attr in self.data.domain.variables + \
331                               self.data.domain.getmetas().values() \
332                               if attr.varType in [orange.VarTypes.String,
333                                                   orange.VarTypes.Discrete]]
334            self.geneAttrComboBox.addItems([attr.name for attr in self.attributes])
335            self.openContext("", data)
336            self.geneAttr = min(self.geneAttr, len(self.attributes) - 1)
337           
338            taxid = data_hints.get_hint(self.data, "taxid", "")
339            if taxid in self.organisms:
340                self.organismIndex = self.organisms.index(taxid)
341               
342            self.useAttr = data_hints.get_hint(self.data, "genesinrows",  self.useAttr)
343           
344            self.setItems()
345        else:
346            self.clear()
347
348    def infoSource(self):
349        """ Return the current selected info source getter function from
350        INFO_SOURCES
351        """
352        org = self.organisms[min(self.organismIndex, len(self.organisms) - 1)]
353        if org not in INFO_SOURCES:
354            org = "default"
355        sources = INFO_SOURCES[org]
356        name, func =  sources[min(self.useAltSource, len(sources) - 1)]
357        return name, func
358       
359    def setItems(self):
360        self.warning(0)
361        if not self.data:
362            return
363        if self.useAttr:
364            genes = [attr.name for attr in self.data.domain.attributes]
365        elif self.attributes:
366            attr = self.attributes[self.geneAttr]
367            genes = [str(ex[attr]) for ex in self.data if not ex[attr].isSpecial()]
368        else:
369            genes = []
370        if not genes:
371            self.warning(0, "Could not extract genes from input dataset.")
372        self.warning(1)
373        org = self.organisms[min(self.organismIndex, len(self.organisms) - 1)]
374        source_name, info_getter = self.infoSource()
375        info , currorg = self.currentLoaded
376        self.error(0)
377       
378        self.updateDictyExpressLink(genes, show=org == "352472")
379        self.altSourceCheck.setVisible(org == "352472")
380       
381        # get the info for the genes in a separate thread
382        self.progressBarInit()
383#        call = self.asyncCall(info_getter, (org, genes),
384#                              name="Load NCBI Gene Info",
385#                              blocking=False)
386#        call.connect(call, SIGNAL("progressChanged(float)"), self.progressBarSet, Qt.QueuedConnection)
387#        with orngServerFiles.DownloadProgress.setredirect(call.emitProgressChanged):
388#            call.__call__()
389#            schema, geneinfo = call.get_result()
390#        call.__call__()
391#        schema, geneinfo = call.get_result()
392        with orngServerFiles.DownloadProgress.setredirect(self.progressBarSet):
393            schema, geneinfo = info_getter(org, genes)
394        self.progressBarFinished()
395        # schema, geneinfo = info_getter(org, genes)
396
397        self.geneinfo = geneinfo = list(zip(genes, geneinfo))
398
399        self.progressBarInit()
400
401        milestones = progress_bar_milestones(len(geneinfo))
402        self.cells = cells = []
403        self.row2geneinfo = {}
404        links = []
405        for i, (gene, gi) in enumerate(geneinfo):
406            if gi:
407                row = []
408                for sch, item in zip(schema, gi):
409                    if isinstance(item, Link): # TODO: This should be handled by delegates
410                        row.append(item.text)
411                        links.append(item.link)
412                    else:
413                        row.append(item)
414                cells.append(row)
415                self.row2geneinfo[len(cells) - 1] = i
416#                cells.append([gi.gene_id, gi.symbol + " (%s)" % gene if gene != gi.symbol else gi.symbol,
417#                            gi.locus_tag or "", gi.chromosome or "", gi.description or "",
418#                            ", ".join(gi.synonyms), gi.symbol_from_nomenclature_authority or ""])
419#                links.append("http://www.ncbi.nlm.nih.gov/sites/entrez?Db=gene&Cmd=ShowDetailView&TermToSearch=%s" % gi.gene_id)
420
421            if i in milestones:
422                self.progressBarSet(100.0*i/len(geneinfo))
423
424        model = TreeModel(cells, [str(col) for col in schema], self.treeWidget)
425       
426        model.setColumnLinks(0, links)
427        proxyModel = QSortFilterProxyModel(self)
428        proxyModel.setSourceModel(model)
429        self.treeWidget.setModel(proxyModel)
430        self.connect(self.treeWidget.selectionModel(), SIGNAL("selectionChanged(QItemSelection , QItemSelection )"), self.commitIf)
431        for i in range(7):
432            self.treeWidget.resizeColumnToContents(i)
433            self.treeWidget.setColumnWidth(i, min(self.treeWidget.columnWidth(i), 200))
434        self.treeWidget.update()
435        self.progressBarFinished()
436
437        self.infoLabel.setText("%i genes\n%i matched NCBI's IDs" % (len(genes), len(cells)))
438        self.matchedInfo = len(genes), len(cells)
439
440    def clear(self):
441        self.infoLabel.setText("No data on input\n")
442        self.treeWidget.setModel(TreeModel([], ["NCBI ID", "Symbol", "Locus Tag",
443                                            "Chromosome", "Description", "Synonyms",
444                                            "Nomenclature"], self.treeWidget))
445        self.geneAttrComboBox.clear()
446        self.send("Selected Examples", None)
447
448    def commitIf(self, *args):
449        if self.autoCommit and not self.selectionUpdateInProgress:
450            self.commit()
451        else:
452            self.selectionChangedFlag = True
453
454    def commit(self):
455        if not self.data:
456            return
457        model = self.treeWidget.model()
458        mapToSource = model.mapToSource
459        selectedIds = [self.cells[mapToSource(index).row()][0] for index in self.treeWidget.selectedIndexes()]
460        selectedRows = self.treeWidget.selectedIndexes()
461        selectedRows = [mapToSource(index).row() for index in selectedRows]
462        model = model.sourceModel()
463       
464        selectedGeneids = [self.row2geneinfo[row] for row in selectedRows]
465        selectedIds = [self.geneinfo[i][0] for i in selectedGeneids]
466        selectedIds = set(selectedIds)
467        gene2row = dict((self.geneinfo[self.row2geneinfo[row]][0], row) \
468                        for row in selectedRows)
469       
470        if self.useAttr:
471            def is_selected(attr):
472                return attr.name in selectedIds
473            attrs = [attr for attr in self.data.domain.attributes if is_selected(attr)]
474            domain = orange.Domain(attrs, self.data.domain.classVar)
475            domain.addmetas(self.data.domain.getmetas())
476            newdata = orange.ExampleTable(domain, self.data)
477            self.send("Selected Examples", newdata)
478        elif self.attributes:
479            attr = self.attributes[self.geneAttr]
480            geneinfo = dict(self.geneinfo)
481            examples = [ex for ex in self.data if str(ex[attr]) in selectedIds]
482            if True:  # Add gene info
483                domain = orange.Domain(self.data.domain, self.data.domain.classVar)
484                domain.addmetas(self.data.domain.getmetas())
485                n_columns = model.columnCount()
486
487                headers = [str(model.headerData(i, Qt.Horizontal, Qt.DisplayRole).toString()) \
488                           for i in range(n_columns)]
489                new_meta_attrs = [(orange.newmetaid(), orange.StringVariable(name)) \
490                                  for name in headers]
491                domain.addmetas(dict(new_meta_attrs))
492                examples = [orange.Example(domain, ex) for ex in examples]
493                for ex in examples:
494                    for i, (_, meta) in enumerate(new_meta_attrs):
495                        row = gene2row[str(ex[attr])]
496                        ex[meta] = str(model.data(model.index(row, i), Qt.DisplayRole).toString())
497
498            if examples:
499                newdata = orange.ExampleTable(examples)
500            else:
501                newdata = None
502            self.send("Selected Examples", newdata)
503        else:
504            self.send("Selected Examples", None)
505           
506    def rowFiltered(self, row):
507        searchStrings = self.searchString.lower().split()
508        row = unicode(" ".join(self.cells[row]).lower(), errors="ignore")
509        return not all([s in row for s in searchStrings])
510   
511    def searchUpdate(self):
512        if not self.data:
513            return
514        searchStrings = self.searchString.lower().split()
515        index = self.treeWidget.model().sourceModel().index
516        mapFromSource = self.treeWidget.model().mapFromSource
517        for i, row in enumerate(self.cells):
518            row = unicode(" ".join(row).lower(), errors="ignore")
519            self.treeWidget.setRowHidden(mapFromSource(index(i, 0)).row(), QModelIndex(), not all([s in row for s in searchStrings]))
520        #self.treeWidget.model().setFilterRegExp(QRegExp(self.searchString, Qt.CaseInsensitive, QRegExp.FixedString))
521           
522    def selectFiltered(self):
523        if not self.data:
524            return
525        itemSelection = QItemSelection()
526       
527        index = self.treeWidget.model().sourceModel().index
528        mapFromSource = self.treeWidget.model().mapFromSource
529        for i, row in enumerate(self.cells):
530            if not self.rowFiltered(i):
531                itemSelection.select(mapFromSource(index(i, 0)), mapFromSource(index(i, 0)))
532        self.treeWidget.selectionModel().select(itemSelection, QItemSelectionModel.Select | QItemSelectionModel.Rows)
533       
534    def sendReport(self):
535        from Orange.OrangeWidgets import OWReport
536        genes, matched = self.matchedInfo
537        info, org = self.currentLoaded
538        self.reportRaw("<p>Input: %i genes of which %i (%.1f%%) matched NCBI synonyms<br>Organism: %s<br>Filter: %s</p>" % (genes, matched, 100.0 * matched / genes, obiTaxonomy.name(org), self.searchString))
539        self.reportSubsection("Gene list")
540        self.reportRaw(reportItemView(self.treeWidget))
541       
542    def updateDictyExpressLink(self, genes, show=False):
543        def fix(ddb):
544            if ddb.startswith("DDB"): 
545                if not ddb.startswith("DDB_G"):
546                    ddb = ddb.replace("DDB", "DDB_G")
547                return ddb
548            return None 
549        if show:
550            genes = [fix(gene) for gene in genes if fix(gene)]
551            link1 = '<a href="http://dictyexpress.biolab.si/run/index.php?gene=%s">Microarray profile</a>'
552            link2 = '<a href="http://dictyexpress.biolab.si/run/index.php?gene=%s&db=rnaseq">RNA-Seq profile</a>'
553            self.linkLabel.setText(link1 + "<br/>" + link2)
554           
555            show = any(genes)
556               
557        if show:
558            self.dictyExpressBox.show()
559        else:
560            self.dictyExpressBox.hide()
561
562    def onDictyExpressLink(self, link):
563        if not self.data:
564            return
565
566        selectedIndexes = self.treeWidget.selectedIndexes()
567        if not len(selectedIndexes):
568            QMessageBox.information(self, "No gene ids selected", "Please select some genes and try again.")
569            return
570        model = self.treeWidget.model()
571        mapToSource = model.mapToSource
572        selectedIds = [self.cells[mapToSource(index).row()][0] for index in selectedIndexes]
573        selectedRows = self.treeWidget.selectedIndexes()
574        selectedRows = [mapToSource(index).row() for index in selectedRows]
575        model = model.sourceModel()
576
577        selectedGeneids = [self.row2geneinfo[row] for row in selectedRows]
578        selectedIds = [self.geneinfo[i][0] for i in selectedGeneids]
579        selectedIds = set(selectedIds)
580
581        def fix(ddb):
582            if ddb.startswith("DDB"):
583                if not ddb.startswith("DDB_G"):
584                    ddb = ddb.replace("DDB", "DDB_G")
585                return ddb
586            return None
587
588        genes = [fix(gene) for gene in selectedIds if fix(gene)]
589        url = str(link) % " ".join(genes)
590        QDesktopServices.openUrl(QUrl(url))
591           
592    def onAltSourceChange(self):
593        self.setItems()
594       
595def reportItemView(view):
596    model = view.model()
597    return reportItemModel(view, model)
598   
599def reportItemModel(view, model, index=QModelIndex()):
600    if not index.isValid() or model.hasChildren(index):
601        columnCount, rowCount = model.columnCount(index), model.rowCount(index)
602        if not index.isValid():
603            text = '<table>\n<tr>' + ''.join('<th>%s</th>' % model.headerData(i, Qt.Horizontal, Qt.DisplayRole).toString() for i in range(columnCount)) +'</tr>\n'
604        else:
605#            variant = model.data(index, Qt.DisplayRole)
606#            text = '<table' + (' caption="%s"' % variant.toString() if variant.isValid() else '') + '>\n'
607            pass
608        text += ''.join('<tr>' + ''.join('<td>' + reportItemModel(view, model, model.index(row, column, index)) + '</td>' for column in range(columnCount)) + '</tr>\n' for row in range(rowCount) if not view.isRowHidden(row, index))
609        text += '</table>'
610        return text
611    else:
612        variant = model.data(index, Qt.DisplayRole)
613        return str(variant.toString()) if variant.isValid() else ""
614       
615if __name__ == "__main__":
616    app = QApplication(sys.argv)
617    data = orange.ExampleTable("brown-selected.tab")
618    w = OWGeneInfo()
619    w.show()
620    w.initialize()
621
622    w.setData(data)
623    app.exec_()
624    w.saveSettings()
Note: See TracBrowser for help on using the repository browser.