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

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

Added new style widget meta descriptions.

Line 
1"""
2<name>PIPAx</name>
3<description>Access data from PIPA RNA-Seq database.</description>
4<icon>icons/PIPA.svg</icon>
5<priority>35</priority>
6"""
7
8from __future__ import absolute_import
9
10import sys, os
11from collections import defaultdict
12import math
13from datetime import date
14
15from Orange.orng import orngEnviron
16from Orange.OrangeWidgets import OWGUI
17from Orange.OrangeWidgets.OWWidget import *
18
19from .. import obiDicty
20
21from .OWPIPA import (MyTreeWidgetItem, ListItemDelegate,
22                    SelectionSetsWidget, SortedListWidget)
23
24NAME = "PIPAx"
25DESCRIPTION = "Access data from PIPA RNA-Seq database."
26ICON = "icons/PIPA.svg"
27PRIORITY = 35
28
29INPUTS = []
30OUTPUTS = [("Example table", Orange.data.Table)]
31
32REPLACES = ["_bioinformatics.widgets.OWPIPAx.OWPIPAx"]
33
34try:
35    from ast import literal_eval
36except ImportError:
37    # Compatibility with Python 2.5
38    literal_eval = eval
39
40
41def tfloat(s):
42    try:
43        return float(s)
44    except:
45        return None
46
47
48class MyTreeWidgetItem(QTreeWidgetItem):
49
50    def __init__(self, parent, *args):
51        QTreeWidgetItem.__init__(self, parent, *args)
52        self.par = parent
53
54    def __contains__(self, text):
55        return any(text.upper() in str(self.text(i)).upper() \
56                   for i in range(self.columnCount()))
57
58    def __lt__(self, o1):
59        col = self.par.sortColumn()
60        if col in [8, 9]:  # WARNING: hardcoded column numbers
61            return tfloat(self.text(col)) < tfloat(o1.text(col))
62        else:
63            return QTreeWidgetItem.__lt__(self, o1)
64
65
66# set buffer file
67bufferpath = os.path.join(orngEnviron.directoryNames["bufferDir"], "pipax")
68
69try:
70    os.makedirs(bufferpath)
71except:
72    pass
73
74bufferfile = os.path.join(bufferpath, "database.sq3")
75
76
77class SelectionByKey(object):
78    """An object stores item selection by unique key values
79    (works only for row selections in list and table models)
80    Example::
81
82        ## Save selection by unique tuple pairs (DisplayRole of column 1 and 2)
83        selection = SelectionsByKey(itemView.selectionModel().selection(),
84                                    key = (1,2))
85        ## restore selection (Possibly omitting rows not present in the model)
86        selection.select(itemView.selectionModel())
87
88    """
89
90    def __init__(self, itemSelection, name="", key=(0,)):
91        self._key = key
92        self.name = name
93        self._selected_keys = []
94        if itemSelection:
95            self.setSelection(itemSelection)
96
97    def _row_key(self, model, row):
98        def key(row, col):
99            return str(model.data(model.index(row, col),
100                                  Qt.DisplayRole).toString())
101
102        return tuple(key(row, col) for col in self._key)
103
104    def setSelection(self, itemSelection):
105        self._selected_keys = [self._row_key(ind.model(), ind.row()) \
106                               for ind in itemSelection.indexes() \
107                               if ind.column() == 0]
108
109    def select(self, selectionModel):
110        model = selectionModel.model()
111        selectionModel.clear()
112        for i in range(model.rowCount()):
113            if self._row_key(model, i) in self._selected_keys:
114                selectionModel.select(model.index(i, 0),
115                    QItemSelectionModel.Select | QItemSelectionModel.Rows)
116
117    def __len__(self):
118        return len(self._selected_keys)
119
120
121# Mapping from PIPAx.results_list annotation keys to Header names.
122HEADER = [("_cached", ""),
123          ("data_name", "Name"),
124          ("species_name", "Species"),
125          ("strain", "Strain"),
126          ("Experiment", "Experiment"),
127          ("genotype", "Genotype"),
128          ("treatment", "Treatment"),
129          ("growth", "Growth"),
130          ("tp", "Timepoint"),
131          ("replicate", "Replicate"),
132          ("unique_id", "ID"),
133          ("date_rnaseq", "Date RNAseq"),
134          ("adapter_type", "Adapter"),
135          ("experimenter", "Experimenter"),
136          ("band", "Band"),
137          ("polya", "Polya"),
138          ("primer", "Primer"),
139          ("shearing", "Shearing")
140          ]
141
142# Index of unique_id
143ID_INDEX = 10
144
145# Index of 'date_rnaseq'
146DATE_INDEX = 11
147
148SORTING_MODEL_LIST = \
149    ["Strain", "Experiment", "Genotype",
150     "Timepoint", "Growth", "Species",
151     "ID", "Name", "Replicate"]
152
153
154class OWPIPAx(OWWidget):
155    settingsList = ["server", "excludeconstant", "username", "password",
156                    "joinreplicates", "selectionSetsWidget.selections",
157                    "columnsSortingWidget.sortingOrder", "currentSelection",
158                    "log2", "experimentsHeaderState", "rtypei" ]
159
160    def __init__(self, parent=None, signalManager=None, name="PIPAx"):
161        OWWidget.__init__(self, parent, signalManager, name)
162        self.outputs = [("Example table", ExampleTable)]
163
164        self.username = ""
165        self.password = ""
166        self.log2 = False
167        self.rtypei = 5 #hardcoded rpkm mapability polya
168
169        self.selectedExperiments = []
170        self.buffer = obiDicty.BufferSQLite(bufferfile)
171
172        self.searchString = ""
173        self.excludeconstant = False
174        self.joinreplicates = False
175        self.currentSelection = None
176
177        self.experimentsHeaderState = \
178                dict(((name, False) for _, name in HEADER[:ID_INDEX + 1]))
179
180        self.result_types = []
181        self.mappings = {}
182
183        self.controlArea.setMaximumWidth(250)
184        self.controlArea.setMinimumWidth(250)
185
186        OWGUI.button(self.controlArea, self, "Reload",
187                     callback=self.Reload)
188        OWGUI.button(self.controlArea, self, "Clear cache",
189                     callback=self.clear_cache)
190
191        b = OWGUI.widgetBox(self.controlArea, "Experiment Sets")
192        self.selectionSetsWidget = SelectionSetsWidget(self)
193        self.selectionSetsWidget.setSizePolicy(QSizePolicy.Preferred,
194                                               QSizePolicy.Maximum)
195        b.layout().addWidget(self.selectionSetsWidget)
196
197        OWGUI.separator(self.controlArea)
198
199        b = OWGUI.widgetBox(self.controlArea, "Sort output columns")
200        self.columnsSortingWidget = SortedListWidget(self)
201        self.columnsSortingWidget.setSizePolicy(QSizePolicy.Preferred,
202                                                QSizePolicy.Maximum)
203        b.layout().addWidget(self.columnsSortingWidget)
204        sorting_model = QStringListModel(SORTING_MODEL_LIST)
205        self.columnsSortingWidget.setModel(sorting_model)
206
207        self.columnsSortingWidget.sortingOrder = \
208                ["Strain", "Experiment", "Genotype", "Timepoint"]
209
210        OWGUI.separator(self.controlArea)
211
212        box = OWGUI.widgetBox(self.controlArea, 'Expression Type')
213        self.expressionTypesCB = OWGUI.comboBox(box, self, "rtypei",
214                items=[],
215                callback=self.UpdateResultsList)
216
217        OWGUI.checkBox(self.controlArea, self, "excludeconstant",
218                       "Exclude labels with constant values"
219                       )
220
221        OWGUI.checkBox(self.controlArea, self, "joinreplicates",
222                       "Average replicates (use median)"
223                       )
224
225        OWGUI.checkBox(self.controlArea, self, "log2",
226                       "Logarithmic (base 2) transformation"
227                       )
228
229        self.commit_button = OWGUI.button(self.controlArea, self, "&Commit",
230                                          callback=self.Commit)
231        self.commit_button.setDisabled(True)
232
233        OWGUI.rubber(self.controlArea)
234
235        box = OWGUI.widgetBox(self.controlArea, "Authentication")
236
237        OWGUI.lineEdit(box, self, "username", "Username:",
238                       labelWidth=100,
239                       orientation='horizontal',
240                       callback=self.AuthChanged)
241
242        self.passf = OWGUI.lineEdit(box, self, "password", "Password:",
243                                    labelWidth=100,
244                                    orientation='horizontal',
245                                    callback=self.AuthChanged)
246
247        self.passf.setEchoMode(QLineEdit.Password)
248
249        OWGUI.lineEdit(self.mainArea, self, "searchString", "Search",
250                       callbackOnType=True,
251                       callback=self.SearchUpdate)
252
253        self.headerLabels = [t[1] for t in HEADER]
254
255        self.experimentsWidget = QTreeWidget()
256        self.experimentsWidget.setHeaderLabels(self.headerLabels)
257        self.experimentsWidget.setSelectionMode(QTreeWidget.ExtendedSelection)
258        self.experimentsWidget.setRootIsDecorated(False)
259        self.experimentsWidget.setSortingEnabled(True)
260
261        contextEventFilter = OWGUI.VisibleHeaderSectionContextEventFilter(
262                            self.experimentsWidget, self.experimentsWidget
263                            )
264
265        self.experimentsWidget.header().installEventFilter(contextEventFilter)
266        self.experimentsWidget.setItemDelegateForColumn(0,
267                    OWGUI.IndicatorItemDelegate(self, role=Qt.DisplayRole)
268                    )
269
270        self.experimentsWidget.setAlternatingRowColors(True)
271
272        self.connect(self.experimentsWidget.selectionModel(),
273                 SIGNAL("selectionChanged(QItemSelection, QItemSelection)"),
274                 self.onSelectionChanged)
275
276        self.selectionSetsWidget.setSelectionModel(
277                            self.experimentsWidget.selectionModel()
278                            )
279
280        self.mainArea.layout().addWidget(self.experimentsWidget)
281
282        self.loadSettings()
283
284        self.restoreHeaderState()
285
286        self.connect(self.experimentsWidget.header(),
287                     SIGNAL("geometriesChanged()"),
288                     self.saveHeaderState)
289
290        self.dbc = None
291
292        self.AuthSet()
293
294        QTimer.singleShot(100, self.UpdateExperiments)
295
296        self.resize(800, 600)
297
298    def AuthSet(self):
299        if len(self.username):
300            self.passf.setDisabled(False)
301        else:
302            self.passf.setDisabled(True)
303
304    def AuthChanged(self):
305        self.AuthSet()
306        self.ConnectAndUpdate()
307
308    def ConnectAndUpdate(self):
309        self.Connect()
310        self.UpdateExperiments(reload=True)
311
312    def Connect(self):
313        self.error(1)
314        self.warning(1)
315
316        def en(x):
317            return x if len(x) else None
318
319        self.dbc = obiDicty.PIPAx(buffer=self.buffer,
320                                  username=en(self.username),
321                                  password=self.password)
322
323        #check password
324        if en(self.username) != None:
325            try:
326                self.dbc.mappings(reload=True)
327            except obiDicty.AuthenticationError:
328                self.error(1, "Wrong username or password")
329                self.dbc = None
330            except Exception, ex:
331                print "Error when contacting the PIPA database", ex
332                import traceback
333                print traceback.format_exc()
334                try:  # maybe cached?
335                    self.dbc.mappings()
336                    self.warning(1, "Can not access database - using cached data.")
337                except Exception, ex:
338                    self.dbc = None
339                    self.error(1, "Can not access database.")
340
341    def Reload(self):
342        self.UpdateExperiments(reload=True)
343
344    def clear_cache(self):
345        self.buffer.clear()
346        self.Reload()
347
348    def rtype(self):
349        """Return selected result template type """
350        if self.result_types:
351            return self.result_types[self.rtypei][0]
352        else:
353            return "-1"
354
355    def UpdateExperimentTypes(self):
356        self.expressionTypesCB.clear()
357        items = [desc for _, desc  in self.result_types]
358        self.expressionTypesCB.addItems(items)
359        self.rtypei = max(0, min(self.rtypei, len(self.result_types) - 1))
360
361    def UpdateExperiments(self, reload=False):
362        self.experimentsWidget.clear()
363        self.items = []
364
365        self.progressBarInit()
366
367        if not self.dbc:
368            self.Connect()
369
370        mappings = {}
371        result_types = []
372        sucind = False  # success indicator for database index
373
374        try:
375            mappings = self.dbc.mappings(reload=reload)
376            result_types = self.dbc.result_types(reload=reload)
377            sucind = True
378        except Exception, ex:
379            try:
380                mappings = self.dbc.mappings()
381                result_types = self.dbc.result_types()
382                self.warning(0, "Can not access database - using cached data.")
383                sucind = True
384            except Exception, ex:
385                self.error(0, "Can not access database.")
386
387        if sucind:
388            self.warning(0)
389            self.error(0)
390
391        self.mappings = mappings
392        self.result_types = result_types
393
394        self.UpdateExperimentTypes()
395
396        results_list = {}
397        try:
398            results_list = self.dbc.results_list(self.rtype(), reload=reload)
399        except Exception, ex:
400            try:
401                results_list = self.dbc.results_list(self.rtype())
402            except Exception, ex:
403                self.error(0, "Can not access database.")
404
405        self.results_list = results_list
406        mappings_key_dict = dict(((m["data_id"], m["id"]), key) \
407                                 for key, m in mappings.items())
408
409        def mapping_unique_id(annot):
410            """Map annotations dict from results_list to unique
411            `mappings` ids.
412            """
413            data_id, mappings_id = annot["data_id"], annot["mappings_id"]
414            return mappings_key_dict[data_id, mappings_id]
415
416        elements = []
417        pos = 0
418
419        for r_id, r_annot in self.results_list.items():
420            pos += 1
421            d = defaultdict(lambda: "?", r_annot)
422            row_items = [""] + [d.get(key, "?") for key, _ in HEADER[1:]]
423            date_string = row_items[DATE_INDEX]
424            try:
425                time_dict = literal_eval(date_string)
426            except Exception:
427                time_dict = {}
428
429            if time_dict and "dateUTC" in time_dict and \
430                    "monthUTC" in time_dict and "fullYearUTC" in time_dict:
431                date_rna = date(time_dict["fullYearUTC"],
432                                time_dict["monthUTC"] + 1,  # Why is month 0 based?
433                                time_dict["dateUTC"])
434
435                row_items[DATE_INDEX] = date_rna.strftime("%x")
436
437            row_items[ID_INDEX] = mapping_unique_id(r_annot)
438            elements.append(row_items)
439
440            ci = MyTreeWidgetItem(self.experimentsWidget, row_items)
441
442            self.items.append(ci)
443
444        for i in range(len(self.headerLabels)):
445            self.experimentsWidget.resizeColumnToContents(i)
446
447        # which is the ok buffer version
448        # FIXME: what attribute to use for version?
449        self.wantbufver = \
450            lambda x, ad=self.results_list: \
451                defaultdict(lambda: "?", ad[x])["date"]
452
453        self.wantbufver = lambda x: "0"
454
455        self.UpdateCached()
456
457        self.progressBarFinished()
458
459        if self.currentSelection:
460            self.currentSelection.select(self.experimentsWidget.selectionModel())
461
462        self.handle_commit_button()
463
464    def UpdateResultsList(self, reload=False):
465        results_list = {}
466        try:
467            results_list = self.dbc.results_list(self.rtype(), reload=reload)
468        except Exception:
469            results_list = self.dbc.results_list(self.rtype())
470        self.results_list = results_list
471        self.UpdateCached()
472
473    def UpdateCached(self):
474        if self.wantbufver and self.dbc:
475            fn = self.dbc.download_key_function()
476            result_id_key = dict(((m["data_id"], m["mappings_id"]), key) \
477                                 for key, m in self.results_list.items())
478
479            for item in self.items:
480                c = str(item.text(10))
481                mapping = self.mappings[c]
482                data_id, mappings_id = mapping["data_id"], mapping["id"]
483                r_id = result_id_key[data_id, mappings_id]
484                # Get the buffered version
485                buffered = self.dbc.inBuffer(fn(r_id))
486                value = " " if buffered == self.wantbufver(r_id) else ""
487                item.setData(0, Qt.DisplayRole, QVariant(value))
488
489    def SearchUpdate(self, string=""):
490        for item in self.items:
491            item.setHidden(not all(s in item \
492                                   for s in self.searchString.split())
493                           )
494
495    def Commit(self):
496        if not self.dbc:
497            self.Connect()
498
499        pb = OWGUI.ProgressBar(self, iterations=100)
500
501        table = None
502
503        ids = []
504        for item in self.experimentsWidget.selectedItems():
505            unique_id = str(item.text(10))
506            annots = self.mappings[unique_id]
507            ids.append((annots["data_id"], annots["id"]))
508
509        transfn = None
510        if self.log2:
511            transfn = lambda x: math.log(x + 1.0, 2)
512
513        reverse_header_dict = dict((name, key) for key, name in HEADER)
514
515        hview = self.experimentsWidget.header()
516        shownHeaders = [label for i, label in \
517                        list(enumerate(self.headerLabels))[1:] \
518                        if not hview.isSectionHidden(i)
519                        ]
520
521        allowed_labels = [reverse_header_dict.get(label, label) \
522                          for label in shownHeaders]
523
524        if self.joinreplicates and "id" not in allowed_labels:
525            # need 'id' labels in join_replicates for attribute names
526            allowed_labels.append("id")
527
528        if len(ids):
529            table = self.dbc.get_data(ids=ids, result_type=self.rtype(),
530                          callback=pb.advance,
531                          exclude_constant_labels=self.excludeconstant,
532#                          bufver=self.wantbufver,
533                          transform=transfn,
534                          allowed_labels=allowed_labels)
535
536            if self.joinreplicates:
537                table = obiDicty.join_replicates(table,
538                    ignorenames=["replicate", "data_id", "mappings_id",
539                                 "data_name", "id", "unique_id"],
540                    namefn=None,
541                    avg=obiDicty.median
542                    )
543
544            # Sort attributes
545            sortOrder = self.columnsSortingWidget.sortingOrder
546
547            all_values = defaultdict(set)
548            for at in table.domain.attributes:
549                atts = at.attributes
550                for name in sortOrder:
551                    all_values[name].add(atts.get(reverse_header_dict[name], ""))
552
553            isnum = {}
554            for at, vals in all_values.items():
555                vals = filter(None, vals)
556                try:
557                    for a in vals:
558                        float(a)
559                    isnum[at] = True
560                except:
561                    isnum[at] = False
562
563            def optfloat(x, at):
564                if x == "":
565                    return ""
566                else:
567                    return float(x) if isnum[at] else x
568
569            def sorting_key(attr):
570                atts = attr.attributes
571                return tuple([optfloat(atts.get(reverse_header_dict[name], ""), name) \
572                              for name in sortOrder])
573
574            attributes = sorted(table.domain.attributes,
575                                key=sorting_key)
576
577            domain = orange.Domain(attributes, table.domain.classVar)
578            domain.addmetas(table.domain.getmetas())
579            table = orange.ExampleTable(domain, table)
580
581            from Orange.orng.orngDataCaching import data_hints
582            data_hints.set_hint(table, "taxid", "352472")
583            data_hints.set_hint(table, "genesinrows", False)
584
585            self.send("Example table", table)
586
587            self.UpdateCached()
588
589        pb.finish()
590
591    def onSelectionChanged(self, selected, deselected):
592        self.handle_commit_button()
593
594    def handle_commit_button(self):
595        self.currentSelection = \
596            SelectionByKey(self.experimentsWidget.selectionModel().selection(),
597                           key=(1, 2, 3, 10))
598        self.commit_button.setDisabled(not len(self.currentSelection))
599
600    def saveHeaderState(self):
601        hview = self.experimentsWidget.header()
602        for i, label in enumerate(self.headerLabels):
603            self.experimentsHeaderState[label] = hview.isSectionHidden(i)
604
605    def restoreHeaderState(self):
606        hview = self.experimentsWidget.header()
607        state = self.experimentsHeaderState
608        for i, label in enumerate(self.headerLabels):
609            hview.setSectionHidden(i, state.get(label, True))
610            self.experimentsWidget.resizeColumnToContents(i)
611
612if __name__ == "__main__":
613    app = QApplication(sys.argv)
614    obiDicty.verbose = True
615    w = OWPIPAx()
616    w.show()
617    app.exec_()
618    w.saveSettings()
Note: See TracBrowser for help on using the repository browser.