source: orange-bioinformatics/widgets/OWPIPAx.py @ 1616:6c0bc463ca9b

Revision 1616:6c0bc463ca9b, 17.8 KB checked in by Ales Erjavec <ales.erjavec@…>, 2 years ago (diff)

Added new PIPAx widget.

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