source: orange-bioinformatics/_bioinformatics/widgets/OWPIPAx.py @ 1654:61fbdcb67aab

Revision 1654:61fbdcb67aab, 19.4 KB checked in by mitar, 2 years ago (diff)

Merge.

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