source: orange-bioinformatics/widgets/OWPIPAx.py @ 1618:56ed7843a919

Revision 1618:56ed7843a919, 19.3 KB checked in by Ales Erjavec <ales.erjavec@…>, 2 years ago (diff)

Fixed date formating (using locale's date representation).

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