source: orange-bioinformatics/orangecontrib/bio/widgets/OWGeneAtlasTissueExpression.py @ 1874:b3e32cc5cf6f

Revision 1874:b3e32cc5cf6f, 17.6 KB checked in by Ales Erjavec <ales.erjavec@…>, 6 months ago (diff)

Added new style widget meta descriptions.

RevLine 
[1633]1"""
2<name>Gene Atlas Tissue Expression</name>
[1428]3<description></description>
[1726]4<icon>icons/GeneExpressionAtlas.svg</icon>
[1428]5"""
[1633]6
7from __future__ import absolute_import
8
[1428]9import os, sys
[1633]10from collections import defaultdict
11import shelve
12
[1428]13import Orange
[1633]14from Orange.utils import lru_cache
15from Orange.OrangeWidgets import OWGUI
16from Orange.OrangeWidgets.OWWidget import *
[1428]17
[1633]18from Orange.orng import orngDataCaching, orngServerFiles
[1428]19
[1700]20from .. import obiArrayExpress, obiGene, obiGeneAtlas
[1440]21
[1874]22NAME = "Gene Atlas Tissue Expression"
23DESCRIPTION = ""
24ICON = "icons/GeneExpressionAtlas.svg"
25PRIORITY = 5000
26
27INPUTS = [("Example Table", Orange.data.Table, "set_data")]
28OUTPUTS = [("Selected Genes", Orange.data.Table)]
29
30REPLACES = ["_bioinformatics.widgets.OWGeneAtlasTissueExpression.OWGeneAtlasTissueExpression"]
31
32
[1472]33TAXID_TO_ORG = obiGeneAtlas.TAXID_TO_ORG
[1440]34
[1428]35class OWGeneAtlasTissueExpression(OWWidget):
36    contextHandlers = {"": DomainContextHandler("", ["selected_organism",
37                                                     "selected_gene_attr",
38                                                     "genes_in_columns",
39                                                     "selected_ef",
40                                                     "selected_ef_value"])}
[1698]41    settingsList = ["selected_organism", "selected_ef", "selected_ef_value"]
[1428]42   
[1430]43    ORGANISMS = obiArrayExpress.ATLAS_ORGANISMS
[1428]44    FACTORS = ["Organism part", "Disease state", "Cell type"]
45   
[1436]46    def __init__(self, parent=None, signalManager=None, title="Gene Atlas Tissue Expression"):
[1428]47        OWWidget.__init__(self, parent, signalManager, title)
48       
49        self.inputs = [("Example Table", Orange.data.Table, self.set_data)]
50        self.outputs = [("Selected Genes", Orange.data.Table)]
51       
52        self.selected_organism = "Homo sapiens"
53        self.selected_gene_attr = 0
54        self.genes_in_columns = False
55        self.selected_ef = 0
56        self.selected_ef_value = 0
57       
58        self.loadSettings()
59       
60        #####
61        # GUI
62        #####
63        box = OWGUI.widgetBox(self.controlArea, "Info", addSpace=True)
[1436]64        self.info_label = OWGUI.widgetLabel(box, "No data on input.\n")
[1428]65       
66        box = OWGUI.widgetBox(self.controlArea, "Organism", addSpace=True)
67        cb = OWGUI.comboBox(box, self, "selected_organism",
68                            items=self.ORGANISMS,
69                            tooltip="Organism name",
70                            callback=self.on_organism_change,
71                            sendSelectedValue=True,
72                            valueType=str
73                            )
74        cb.setMaximumWidth(250)
75       
76        box = OWGUI.widgetBox(self.controlArea, "Gene Attribute", addSpace=True)
77        self.gene_attr_cb = OWGUI.comboBox(box, self, "selected_gene_attr",
78                              tooltip="Attribute (column) containing the gene names.",
79                              callback=self.on_gene_attr_change,
80                              )
81        self.gene_attr_cb.setMaximumWidth(250)
82       
83        cb = OWGUI.checkBox(box, self, "genes_in_columns", "Use attribute names",
84                       tooltip="Gene names in columns.",
85                       callback=self.on_genes_change,)
86        cb.disables.append((-1, self.gene_attr_cb))
87        cb.makeConsistent()
88       
89        box = OWGUI.widgetBox(self.controlArea, "Tissues", addSpace=True)
90        self.categories_cb = OWGUI.comboBox(box, self, "selected_ef",
91                                box="Categories", 
92                                items=self.FACTORS,
93                                tooltip="Experimental factor.",
94                                callback=self.on_ef_change,
95                                )
[1434]96        self.categories_cb.box.setFlat(True)
[1428]97       
98        self.values_cb = OWGUI.comboBox(box, self, "selected_ef_value",
99                                box="Values",
100                                tooltip="Experimental factor value.",
101                                callback=self.on_ef_value_change
102                                )
103        self.values_cb.setMaximumWidth(250)
[1434]104        self.values_cb.box.setFlat(True)
[1428]105       
[1440]106        box = OWGUI.widgetBox(self.controlArea, "Cache", addSpace=True)
107        OWGUI.button(box, self, "Clear cache",
108                     callback=self.on_cache_clear,
109                     tooltip="Clear Gene Atlas cache.")
110       
[1428]111        OWGUI.rubber(self.controlArea)
112       
113        OWGUI.button(self.controlArea, self, label="Commit",
114                     callback=self.commit,
115                     tooltip="Send selected genes")
116       
[1430]117        self.report_view = QTreeView(self.mainArea)
118        self.report_view.setSelectionMode(QTreeView.ExtendedSelection)
[1428]119        self.report_view.setSortingEnabled(True)
120        self.report_view.setRootIsDecorated(False)
121        self.report_view.setAlternatingRowColors(True)
[1430]122        self.report_view.setEditTriggers(QTreeView.NoEditTriggers)
[1428]123        self.mainArea.layout().addWidget(self.report_view)
[1430]124        self.report_header = ["Gene symbol", "Up", "Down"]
[1428]125       
126        model = QStandardItemModel()
127        model.setHorizontalHeaderLabels(self.report_header)
128        self.report_view.setModel(model)
129       
130        self.data = None
131        self.candidate_vars = []
132        self.candidate_var_names = []
133        self.results = {}, {}, {}
134       
[1472]135        self.ensembl_info = None
[1438]136        self.gene_matcher = obiGene.GMDirect()
[1472]137        self.loaded_matcher_taxid = None
138        self.unknown_genes = []
[1438]139        self.query_genes = []
140       
[1472]141#        self.set_organism(self.selected_organism, update_results=False)
[1428]142       
[1472]143        self.get_atlas_summary = obiGeneAtlas.get_atlas_summary
[1428]144       
[1438]145        #Cached construct_matcher
[1472]146        @lru_cache(maxsize=3)
147        def my_cached_matcher(org):
148            return obiGeneAtlas.default_gene_matcher(org)
149        self.construct_matcher = my_cached_matcher
[1428]150       
151    def set_data(self, data=None):
152        """ Set the input example table with gene names.
153        """
154        self.closeContext("")
155        self.clear()
156        self.data = data
157        if data is not None:
158            self.init_gene_box(data)
[1436]159            taxid = orngDataCaching.data_hints.get_hint(data, key="taxid", default=None)
160            if taxid is not None:
161                if taxid in TAXID_TO_ORG:
162                    self.selected_organism = TAXID_TO_ORG[taxid]
163                   
164            genesinrows = orngDataCaching.data_hints.get_hint(data, key="genesinrows", default=None)
165            if genesinrows is not None:
166                self.genes_in_columns = genesinrows
167               
[1428]168            self.openContext("", data)
[1438]169            self.update_gene_matcher()
[1428]170            self.update_info_box()
171            self.run_query()
172            self.update_ef_values_box()
173            self.update_report_view()
174        else:
175            pass
176           
177    def clear(self):
178        """ Clear the widget state.
179        """
180        self.data = None
181        self.gene_attr_cb.clear()
182        self.values_cb.clear()
183        self.selected_gene_attr = 0
184        self.selected_ef_value = 0
185        self.candidate_vars = []
186        self.candidate_var_names = []
187        self.results = {}, {}, {}
188        model = QStandardItemModel()
189        model.setHorizontalHeaderLabels(self.report_header)
190        self.report_view.setModel(model)
191
192    def init_gene_box(self, data):
193        """ Populate the 'Gene Attribute' box
194        """
195        vars = data.domain.variables + data.domain.get_metas().values()
[1555]196        vars = [var for var in vars if isinstance(var, (Orange.feature.String))]
[1428]197        self.candidate_vars = vars
198        self.gene_attr_cb.addItems([var.name for var in vars])
[1432]199        # See if any var name contains 'gene'
200        gene_in_name = [v for v in vars if "gene" in v.name.lower()]
201        if gene_in_name:
202            self.selected_gene_attr = vars.index(gene_in_name[-1])
[1428]203        self.candidate_var_names = [a.name for a in data.domain.attributes]
204        if not self.candidate_vars:
[1472]205            self.genes_in_columns = True           
[1428]206           
207    def on_organism_change(self):
208        """ Called when the user changes the organism.
209        """
[1438]210        self.update_gene_matcher()
[1428]211        self.run_query()
212        self.update_ef_values_box()
213        self.update_report_view()
214   
215    def on_gene_attr_change(self):
216        """ Called  when the user changes source gene attribute.
217        """
218        self.update_info_box()
219        self.run_query()
220        self.update_ef_values_box()
221        self.update_report_view()
222   
223    def on_genes_change(self):
224        """ Called when the user toogles the "Genes in columns' check box.
225        """
226        self.update_info_box()
227        self.run_query()
228        self.update_ef_values_box()
229        self.update_report_view()
230   
231    def on_ef_change(self):
232        """ Called when the user changes the EF factor.
233        """
234        self.update_ef_values_box()
235        # TODO: restore previous selected value for factor (if value in ef_values)
236        self.update_report_view()
237       
238    def on_ef_value_change(self):
239        """ Called when the user changes the EF value.
240        """
241        results = self.results[self.selected_ef]
242        ef_value = self.ef_values[self.selected_ef_value]
243        self.update_report_view()
244       
[1440]245    def on_cache_clear(self):
[1472]246        from contextlib import closing
247        with closing(obiGeneAtlas._cache()) as cache:
248            cache.clear()
[1440]249       
[1472]250    def input_genes(self, full_res=False):
[1428]251        """ Extract input gene names from ``self.data``.
252        """
253        if self.genes_in_columns:
254            names = self.candidate_var_names
255        else:
256            names = []
257            attr = self.candidate_vars[self.selected_gene_attr]
258            for ex in self.data:
259                if not ex[attr].is_special():
260                    names.append(str(ex[attr]))
[1472]261           
[1428]262        return sorted(set(names))
263   
[1472]264    def match_genes(self, genes, full_res=True):
265        """ Match the genes to ensembl gene ids.
266        """
267        if self.gene_matcher and self.ensembl_info:
268            self.gene_matcher.set_targets(self.ensembl_info.keys())
269            match_genes = map(self.gene_matcher.match, genes)
270            genes = map(self.gene_matcher.umatch, genes)
271        else:
272            match_genes = None
273        genes = sorted(set(genes) - set([None]))
274       
275        if full_res:
276            return genes, match_genes
277        else:
278            return genes
279   
[1428]280    def run_query(self):
281        """ Query the Gene Atlas.
282        """
283        if self.data is not None:
284            genes = self.input_genes()
[1472]285            matched, match_res = self.match_genes(genes, True)
286            all_matched, unknown = [], []
287            for gene, match in zip(genes, match_res):
288                if len(match) == 1:
289                    all_matched.append(match[0])
290                elif len(match) > 1:
291                    all_matched.append(match[0])
292                else:
293                    unknown.append(gene)
294            self.unknown_genes = unknown
295            self.warning(2)
296            self.information(2)
297            if float(len(unknown)) / len(genes) > 0.5:
298                self.warning(2, "%i unknown genes" % len(unknown))
299            elif unknown:
300                self.information(2, "%i unknown genes" % len(unknown))
301               
302            gm = obiGene.GMDirect()
303            gm.set_targets(all_matched)
304           
[1428]305            # Non threaded
[1472]306#            self.results = self.get_atlas_summary(all_matched, self.selected_organism, gm)
[1428]307            # Threaded
[1432]308            self.error(0)
[1436]309            self.update_info_box(query_running=True)
[1500]310            self.progressBarInit()
[1430]311            self.controlArea.setEnabled(False)
[1472]312            self.results = None
[1430]313            try:
[1472]314                call = self.asyncCall(self.get_atlas_summary, (all_matched,
315                                                               self.selected_organism,
316                                                               gm),
[1434]317                                      name="Query Gene Expression Atlas",
318                                      onError=self.handle_assync_error)
[1500]319                QObject.connect(call, SIGNAL("progressChanged(float)"), self.progressBarSet)
320                call(progress_callback=call.emitProgressChanged)
[1430]321                self.results = call.get_result(processEvents=True)
[1432]322            except obiArrayExpress.GeneAtlasError, ex:
323                self.error(0, str(ex))
[1430]324            finally:
325                self.controlArea.setEnabled(True)
[1436]326                self.update_info_box(query_running=False)
[1500]327                self.progressBarFinished()
[1438]328           
329            self.query_genes = genes
330       
331    def update_gene_matcher(self):
[1472]332        taxid = dict((v,k) for k,v in TAXID_TO_ORG.items()).get(self.selected_organism)
333        self.warning(0)
334        if taxid != self.loaded_matcher_taxid:
335            if taxid:
336                self.ensembl_info = obiGene.EnsembleGeneInfo(taxid)
337                self.gene_matcher = self.construct_matcher(taxid)
338            else:
339                self.warning(0, "Cannot match gene names!")
340                self.ensembl_info = None
341                self.gene_matcher = obiGene.GMDirect()
342            self.loded_matcher_taxid = taxid
[1428]343       
344    def update_ef_values_box(self):
345        """ Update the "Values" box.
346        """
347        ef_values = set() 
348        results = self.results[self.selected_ef]
[1434]349        efv_count = defaultdict(int)
[1428]350        if self.results:
351            for gene, val in  results.iteritems():
352                for ef_val, (up, down) in val.iteritems():
353                   ef_values.add(ef_val)
[1434]354                   efv_count[ef_val] += up + down
355       
356        # Sort by up and down count sum of all genes
357        self.ef_values = sorted(ef_values, key=efv_count.get, reverse=True)
[1428]358        self.values_cb.clear()
359        self.values_cb.addItems(self.ef_values)
360        if self.ef_values:
361            self.selected_ef_value = min(len(self.ef_values) - 1, self.selected_ef_value)
362       
363    def update_report_view(self):
364        results = self.results[self.selected_ef]
365       
366        model = QStandardItemModel()
[1430]367       
368               
[1428]369        def standard_item(val):
[1430]370            item = StandardPyItem()
371            item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable)
[1428]372            item.setData(QVariant(val), Qt.DisplayRole)
373            return item
374       
375        if results and self.ef_values:
376            ef_value = self.ef_values[self.selected_ef_value]
377            for gene, efvs in results.iteritems():
378                if ef_value in efvs:
379                    up, down = efvs[ef_value]
380                    model.appendRow([standard_item(gene),
381                                     standard_item(up),
382                                     standard_item(down)])
383       
384        model.setHorizontalHeaderLabels(self.report_header)
385        self.report_view.setModel(model)
[1432]386        self.report_view.resizeColumnToContents(0)
387        self.report_view.resizeColumnToContents(1)
388        self.report_view.resizeColumnToContents(2)
[1428]389       
[1436]390    def update_info_box(self, query_running=False):
391        text = ""
[1428]392        if self.data is not None:
393            genes = self.input_genes()
[1472]394#            unique, matched = self.match_genes(genes, full_res=True)
395            # TODO: How many genes are matched (should update after the query is run).
396            if self.results and not query_running:
397#                matched = min(max([len(r) for r in self.results]), len(genes))
398                matched = len(genes) - len(self.unknown_genes)
399                with_results = len(reduce(set.union, [d.keys() for d in self.results], set()))
400                text = "{0} gene names on input.\n{1} genes matched.\n{2} genes annotated.".format(len(genes), matched, with_results)
401            else:
402                text = "{0} gene names on input.\n\n".format(len(genes))
[1428]403        else:
[1472]404            text = "No data on input.\n\n"
[1436]405        if query_running:
406            text += "Querying Gene Atlas. Please wait."
407        self.info_label.setText(text)
[1428]408           
409    def selected_report_genes(self):
410        """ Return the gene names selected in the report view.
411        """
412        rows = self.report_view.selectionModel().selectedRows(0)
413        genes = []
414        for index in rows:
415            name = str(index.data(Qt.DisplayRole).toString())
416            genes.append(name)
417        return genes
418           
419    def commit(self):
420        genes = set(self.selected_report_genes())
[1438]421        self.gene_matcher.set_targets(genes)
422       
[1428]423        genes = [gene.lower() for gene in genes]
424        if self.genes_in_columns:
425            attrs = [a for a in self.data.domain.attributes \
[1438]426                     if a.name.lower() in genes or self.gene_matcher.umatch(a.name)]
[1428]427            domain = Orange.data.Domain(attrs, self.data.domain.class_var)
428            domain.add_metas(self.data.domain.get_metas())
429            data = Orange.data.Table(domain, self.data)
430        else:
431            attr = self.candidate_vars[self.selected_gene_attr]
432            examples = []
433            for ex in self.data:
[1438]434                if str(ex[attr]).lower() in genes or self.gene_matcher.umatch(str(ex[attr])):
[1428]435                    examples.append(ex)
436            if examples:
437                data = Orange.data.Table(examples)
438            else:
439                data = None
440        self.send("Selected Genes", data)
[1434]441       
442    def handle_assync_error(self, *args):
443        pass
[1438]444       
445       
[1430]446class StandardPyItem(QStandardItem):
447    def __lt__(self, other):
448        my = self.data(Qt.DisplayRole).toPyObject()
[1434]449        other = other.data(Qt.DisplayRole).toPyObject()
[1430]450        return my < other
[1428]451       
452   
453if __name__ == "__main__":
454    app = QApplication(sys.argv)
455    w = OWGeneAtlasTissueExpression()
[1430]456    data = Orange.data.Table("RUNX1.tab")
[1428]457    w.show()
458    w.set_data(data)
459    app.exec_()
460    w.saveSettings()
[1555]461       
Note: See TracBrowser for help on using the repository browser.