source: orange-bioinformatics/widgets/OWGeneInfo.py @ 1336:c983e0920ffa

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