source: orange-bioinformatics/widgets/OWGEODatasets.py @ 1346:99978b5d0d5f

Revision 1346:99978b5d0d5f, 21.2 KB checked in by ales_erjavec <ales.erjavec@…>, 3 years ago (diff)

Fixed sample selection (only select samples that have a selected sample annotation value from all sample types, unless no value is selected (in this case ignore the sample type))

Line 
1"""<name>GEO DataSets</name>
2<description>Access to Gene Expression Omnibus data sets.</description>
3<priority>20</priority>
4<contact>Ales Erjavec (ales.erjavec(@at@)fri.uni-lj.si)</contact>
5<icon>icons/GEODataSets.png</icon>
6"""
7
8from __future__ import with_statement
9
10import sys, os, glob
11from OWWidget import *
12import OWGUI, OWGUIEx
13import obiGEO
14import orngServerFiles
15
16from orngDataCaching import data_hints
17
18from collections import defaultdict
19from functools import partial
20
21LOCAL_GDS_COLOR = Qt.darkGreen
22
23class TreeModel(QAbstractItemModel):
24    def __init__(self, data, header, parent):
25        QAbstractItemModel.__init__(self, parent)
26        self._data = [[QVariant(s) for s in row] for row in data]
27        self._dataDict = {}
28        self._header = {Qt.Horizontal: dict([(i, {Qt.DisplayRole: h}) for i, h in enumerate(header)])}
29        self._roleData = {Qt.DisplayRole:self._data}
30        dataStore = partial(defaultdict, partial(defaultdict, partial(defaultdict, QVariant)))
31        self._roleData = dataStore(self._roleData)
32        self._header = dataStore(self._header)
33   
34    def setColumnLinks(self, column, links):
35        font =QFont()
36        font.setUnderline(True)
37        font = QVariant(font)
38        for i, link in enumerate(links):
39            self._roleData[LinkRole][i][column] = QVariant(link)
40            self._roleData[Qt.FontRole][i][column] = font
41            self._roleData[Qt.ForegroundRole][i][column] = QVariant(QColor(Qt.blue))
42   
43    def setRoleData(self, role, row, col, data):
44        self._roleData[role][row][col] = data
45       
46    def setData(self, index, value, role=Qt.EditRole):
47        self._roleData[role][index.row()][index.column()] = value
48        self.emit(SIGNAL("dataChanged(QModelIndex, QModelIndex)"), index, index)
49       
50    def data(self, index, role):
51        row, col = index.row(), index.column()
52        return self._roleData[role][row][col]
53       
54    def index(self, row, col, parent=QModelIndex()):
55        return self.createIndex(row, col, 0)
56   
57    def parent(self, index):
58        return QModelIndex()
59   
60    def rowCount(self, index=QModelIndex()):
61        if index.isValid():
62            return 0
63        else:
64            return len(self._data)
65       
66    def columnCount(self, index):
67        return len(self._header[Qt.Horizontal])
68
69    def headerData(self, section, orientation, role):
70        try:
71            return QVariant(self._header[orientation][section][role])
72        except KeyError, er:
73#            print >> sys.stderr, er
74            return QVariant()
75       
76    def setHeaderData(self, section, orientation, value, role=Qt.EditRole):
77        self._header[orientation][section][role] = value
78   
79from OWGUI import LinkStyledItemDelegate, LinkRole
80
81def childiter(item):
82    """ Iterate over the children of an QTreeWidgetItem instance.
83    """
84    for i in range(item.childCount()):
85        yield item.child(i)
86               
87class OWGEODatasets(OWWidget):
88    settingsList = ["outputRows", "mergeSpots", "gdsSelectionStates", "splitterSettings", "currentGds", "autoCommit"]
89
90    def __init__(self, parent=None ,signalManager=None, name=" GEO Data sets"):
91        OWWidget.__init__(self, parent ,signalManager, name)
92
93        self.outputs = [("Expression Data", ExampleTable)]
94
95        ## Settings
96#        self.selectedSubsets = []
97#        self.sampleSubsets = []
98        self.selectedAnnotation = 0
99        self.includeIf = False
100        self.minSamples = 3
101        self.autoCommit = False
102        self.outputRows = 0
103        self.mergeSpots = True
104        self.filterString = ""
105        self.currentGds = None
106        self.selectionChanged = False
107        self.autoCommit = False
108        self.gdsSelectionStates = {}
109        self.splitterSettings = ['\x00\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x01\xea\x00\x00\x00\xd7\x01\x00\x00\x00\x07\x01\x00\x00\x00\x02',
110                                 '\x00\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x01\xb5\x00\x00\x02\x10\x01\x00\x00\x00\x07\x01\x00\x00\x00\x01']
111
112        self.loadSettings()
113
114        ## GUI
115        self.infoBox = OWGUI.widgetLabel(OWGUI.widgetBox(self.controlArea, "Info", addSpace=True), "\n\n")
116#        box = OWGUI.widgetBox(self.controlArea, "Sample Subset")
117#        OWGUI.listBox(box, self, "selectedSubsets", "sampleSubsets", selectionMode=QListWidget.ExtendedSelection)
118#        box = OWGUI.widgetBox(self.controlArea, "Sample Annotations (Types)")
119#        self.annotationCombo = OWGUI.comboBox(box, self, "selectedAnnotation", items=["Include all"])
120       
121##        OWGUI.button(box, self, "Clear selection", callback=self.clearSubsetSelection)
122##        c = OWGUI.checkBox(box, self, "includeIf", "Include if at least", callback=self.commitIf)
123##        OWGUI.spin(OWGUI.indentedBox(box), self, "minSamples", 2, 100, posttext="samples", callback=self.commitIf)
124
125        box = OWGUI.widgetBox(self.controlArea, "Output", addSpace=True)
126        OWGUI.radioButtonsInBox(box, self, "outputRows", ["Genes or spots", "Samples"], "Rows", callback=self.commitIf)
127        OWGUI.checkBox(box, self, "mergeSpots", "Merge spots of same gene", callback=self.commitIf)
128
129        box = OWGUI.widgetBox(self.controlArea, "Output", addSpace=True)
130        self.commitButton = OWGUI.button(box, self, "Commit", callback=self.commit)
131#        self.commitButton.setDisabled(True)
132        cb = OWGUI.checkBox(box, self, "autoCommit", "Commit on any change")
133        OWGUI.setStopper(self, self.commitButton, cb, "selectionChanged", self.commit)
134##        OWGUI.checkBox(box, self, "autoCommit", "Commit automatically")
135        OWGUI.rubber(self.controlArea)
136
137        self.filterLineEdit = OWGUIEx.lineEditHint(self.mainArea, self, "filterString", "Filter", caseSensitive=False, matchAnywhere=True, listUpdateCallback=self.filter, callbackOnType=True, callback=self.filter, delimiters=" ")
138        splitter = QSplitter(Qt.Vertical, self.mainArea)
139        self.mainArea.layout().addWidget(splitter)
140        self.treeWidget = QTreeView(splitter)
141#        splitter.addWidget(self.treeWidget)
142       
143        self.treeWidget.setSelectionMode(QAbstractItemView.SingleSelection)
144        self.treeWidget.setRootIsDecorated(False)
145        self.treeWidget.setSortingEnabled(True)
146        self.treeWidget.setAlternatingRowColors(True)
147        self.treeWidget.setItemDelegate(LinkStyledItemDelegate(self.treeWidget))
148        self.treeWidget.setItemDelegateForColumn(0, OWGUI.IndicatorItemDelegate(self.treeWidget, role=Qt.DisplayRole))
149       
150#        self.mainArea.layout().addWidget(self.treeWidget)
151        self.connect(self.treeWidget, SIGNAL("itemSelectionChanged ()"), self.updateSelection)
152        self.treeWidget.viewport().setMouseTracking(True)
153##        self.connect(self.treeWidget, SIGNAL("currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*))"), self.updateSelection_)
154#        self.connect(self.treeWidget.model(), SIGNAL("layoutChanged()"), self.filter)
155        splitterH = QSplitter(Qt.Horizontal, splitter) 
156#        splitter.addWidget(splitterH)
157        box = OWGUI.widgetBox(splitterH, "Description")
158        self.infoGDS = OWGUI.widgetLabel(box, "")
159        self.infoGDS.setWordWrap(True)
160        OWGUI.rubber(box)
161       
162        box = OWGUI.widgetBox(splitterH, "Sample Annotations")
163        self.annotationsTree = QTreeWidget(box)
164        self.annotationsTree.setHeaderLabels(["Type (Sample annotations)", "Sample count"])
165        self.annotationsTree.setRootIsDecorated(True)
166        box.layout().addWidget(self.annotationsTree)
167        self.connect(self.annotationsTree, SIGNAL("itemChanged(QTreeWidgetItem * , int)"), self.annotationSelectionChanged)
168        self._annotationsUpdating = False
169        self.splitters = splitter, splitterH
170        self.connect(splitter, SIGNAL("splitterMoved(int, int)"), self.splitterMoved)
171        self.connect(splitterH, SIGNAL("splitterMoved(int, int)"), self.splitterMoved)
172       
173        for sp, setting in zip(self.splitters, self.splitterSettings):
174            sp.restoreState(setting)
175           
176        self.searchKeys = ["dataset_id", "title", "platform_organism", "description"]
177        self.cells = []
178#        self.currentGds = None
179        QTimer.singleShot(50, self.updateTable)
180        self.resize(1000, 600)
181
182    def updateInfo(self):
183        gds_info = obiGEO.GDSInfo()
184        text = "%i datasets\n%i datasets cached\n" % (len(gds_info), len(glob.glob(orngServerFiles.localpath("GEO") + "/GDS*")))
185        filtered = len([row for row in range(len(self.cells)) if self.rowFiltered(row)])
186        if len(self.cells) != filtered:
187            text += ("%i after filtering") % filtered
188        self.infoBox.setText(text)
189       
190    def updateTable(self):
191        self.treeItems = []
192        self.progressBarInit()
193        with orngServerFiles.DownloadProgress.setredirect(self.progressBarSet):
194            info = obiGEO.GDSInfo()
195        milestones = set(range(0, len(info), max(len(info)/100, 1)))
196        self.cells = cells = []
197        gdsLinks = []
198        pmLinks = []
199        localGDS = []
200        self.gds = []
201        for i, (name, gds) in enumerate(info.items()):
202            local = os.path.exists(orngServerFiles.localpath(obiGEO.DOMAIN, gds["dataset_id"] + ".soft.gz"))
203            cells.append([" " if local else "", gds["dataset_id"], gds["title"], gds["platform_organism"], len(gds["samples"]), gds["feature_count"],
204                          gds["gene_count"], len(gds["subsets"]), gds.get("pubmed_id", "")])
205
206            gdsLinks.append("http://www.ncbi.nlm.nih.gov/sites/GDSbrowser?acc=%s" % gds["dataset_id"])
207            pmLinks.append("http://www.ncbi.nlm.nih.gov/pubmed/%s" % gds.get("pubmed_id") if gds.get("pubmed_id") else QVariant())
208
209            if local:
210                localGDS.append(i)
211            self.gds.append(gds)
212           
213            if i in milestones:
214                self.progressBarSet(100.0*i/len(info))
215
216        model = TreeModel(cells, ["", "ID", "Title", "Organism", "Samples", "Features", "Genes", "Subsets", "PubMedID"], self.treeWidget)
217        model.setColumnLinks(1, gdsLinks)
218        model.setColumnLinks(8, pmLinks)
219#        for i in localGDS:
220#            model._roleData[Qt.ForegroundRole][i].update(zip(range(2, 8), [QVariant(QColor(LOCAL_GDS_COLOR))] * 6))
221#            mode.._roleData[OWGUI.IndicatorItemDelegate.]
222        proxyModel = QSortFilterProxyModel(self.treeWidget)
223        proxyModel.setSourceModel(model)
224        self.treeWidget.setModel(proxyModel)
225        self.connect(self.treeWidget.selectionModel(), SIGNAL("selectionChanged(QItemSelection , QItemSelection )"), self.updateSelection)
226        filterItems = " ".join([self.gds[i][key] for i in range(len(self.gds)) for key in self.searchKeys])
227        filterItems = reduce(lambda s, d: s.replace(d, " "), [",", ".", ":", "!", "?", "(", ")"], filterItems.lower())
228        filterItems = sorted(set(filterItems.split(" ")))
229        self.filterLineEdit.setItems(filterItems)
230       
231        for i in range(8):
232            self.treeWidget.resizeColumnToContents(i)
233        self.treeWidget.setColumnWidth(1, min(self.treeWidget.columnWidth(1), 300))
234        self.treeWidget.setColumnWidth(2, min(self.treeWidget.columnWidth(2), 200))
235        self.progressBarFinished()
236
237        if self.currentGds:
238            gdss = [(i, model.data(model.index(i,1), Qt.DisplayRole)) for i in range(model.rowCount())]
239            current = [i for i, variant in gdss if variant.isValid() and str(variant.toString()) == self.currentGds["dataset_id"]]
240            if current:
241                mapFromSource = self.treeWidget.model().mapFromSource
242                self.treeWidget.selectionModel().select(mapFromSource(model.index(current[0], 0)), QItemSelectionModel.Select | QItemSelectionModel.Rows)
243           
244        self.updateInfo()
245
246    def updateSelection(self, *args):
247        current = self.treeWidget.selectedIndexes()
248        mapToSource = self.treeWidget.model().mapToSource
249        current = [mapToSource(index).row() for index in current]
250        if current:
251            self.currentGds = self.gds[current[0]]
252            self.setAnnotations(self.currentGds)
253            self.infoGDS.setText(self.currentGds.get("description", ""))
254        else:
255            self.currentGds = None
256#        self.commitButton.setDisabled(not bool(self.currentGds))
257        self.commitIf()
258       
259   
260    def setAnnotations(self, gds):
261#        self.sampleSubsets = ["%s (%d)" % (s["description"], len(s["sample_id"])) for s in gds["subsets"]]
262#        self.annotationCombo.clear()
263#        self.annotationCombo.addItems(["Include all"] + list(set([sampleinfo["type"] for sampleinfo in gds["subsets"]])))
264       
265        self._annotationsUpdating = True
266        self.annotationsTree.clear()
267        annotations = reduce(lambda d, info: d[info["type"]].add(info["description"]) or d, gds["subsets"], defaultdict(set))
268        subsetscount = dict([(s["description"], str(len(s["sample_id"]))) for s in gds["subsets"]])
269        for type, subsets in annotations.items():
270            key = (gds["dataset_id"], type)
271            subsetItem = QTreeWidgetItem(self.annotationsTree, [type])
272            subsetItem.setFlags(subsetItem.flags() | Qt.ItemIsUserCheckable | Qt.ItemIsTristate)
273            subsetItem.setCheckState(0, self.gdsSelectionStates.get(key, Qt.Checked))
274            subsetItem.key = key
275            for subset in subsets:
276                key = (gds["dataset_id"], type, subset)
277                item = QTreeWidgetItem(subsetItem, [subset, subsetscount.get(subset, "")])
278                item.setFlags(item.flags() | Qt.ItemIsUserCheckable)
279                item.setCheckState(0, self.gdsSelectionStates.get(key, Qt.Checked))
280                item.key = key
281        self._annotationsUpdating = False
282        self.annotationsTree.expandAll()
283        for i in range(self.annotationsTree.columnCount()):
284            self.annotationsTree.resizeColumnToContents(i)
285               
286    def annotationSelectionChanged(self, item, column):
287        if self._annotationsUpdating:
288            return 
289        for i in range(self.annotationsTree.topLevelItemCount()):
290            item = self.annotationsTree.topLevelItem(i)
291            self.gdsSelectionStates[item.key] = item.checkState(0)
292            for j in range(item.childCount()):
293                child = item.child(j)
294                self.gdsSelectionStates[child.key] = child.checkState(0)
295       
296    def rowFiltered(self, row):
297        filterStrings = self.filterString.lower().split()
298        try:
299            string = " ".join([self.gds[row].get(key, "").lower() for key in self.searchKeys])
300            return not all([s in string for s in filterStrings])
301        except UnicodeDecodeError:
302            string = " ".join([unicode(self.gds[row].get(key, "").lower(), errors="ignore") for key in self.searchKeys])
303            return not all([s in string for s in filterStrings])
304   
305    def filter(self):
306        filterStrings = self.filterString.lower().split()
307        mapFromSource = self.treeWidget.model().mapFromSource
308        index = self.treeWidget.model().sourceModel().index
309       
310        for i, row in enumerate(self.cells):
311            self.treeWidget.setRowHidden(mapFromSource(index(i, 0)).row(), QModelIndex(), self.rowFiltered(i))
312        self.updateInfo()
313
314    def selectedSamples(self):
315        """ Return the currently selected sample annotations (list of
316        sample type, sample value tuples).
317       
318        .. note:: if some Sample annotation type has no selected values.
319                  this method will return all values for it.
320       
321        """
322        samples = []
323        unused_types = []
324        for stype in childiter(self.annotationsTree.invisibleRootItem()):
325            selected_values = []
326            all_values = []
327            for sval in childiter(stype):
328                value = (str(stype.text(0)), str(sval.text(0)))
329                if self.gdsSelectionStates.get(sval.key, True):
330                    selected_values.append(value)
331                all_values.append(value)
332            if selected_values:
333                samples.extend(selected_values)
334            else:
335                # If no sample of sample type is selected we don't filter on it.
336                samples.extend(all_values)
337                unused_types.append(str(stype.text(0)))
338               
339        return samples
340   
341    def commitIf(self):
342        if self.autoCommit:
343            self.commit()
344        else:
345            self.selectionChanged = True
346           
347#    def commit(self):
348#        if self.currentGds:
349##            classes = [s["description"] for s in self.currentGds["subsets"]]
350##            classes = [classes[i] for i in self.selectedSubsets] or None
351##            sample_type = self.annotationCombo.currentText()
352#            sample_type = None
353#            self.progressBarInit()
354#            self.progressBarSet(10)
355#           
356#            def getdata(gds_id, **kwargs):
357#                gds = obiGEO.GDS(gds_id)
358#                data = gds.getdata(**kwargs)
359#                return data
360#            from OWConcurrent import createTask
361#            self.setEnabled(False)
362#            qApp.processEvents()
363#            self.get_data_async = createTask(getdata, (self.currentGds["dataset_id"],), dict(report_genes=self.mergeSpots,
364#                                                                   transpose=self.outputRows,
365#                                                                   sample_type=sample_type if sample_type!="Include all" else None),
366#                                             onResult=self.onData, onFinished=lambda: self.setEnabled(True),
367#                                             threadPool=QThreadPool.globalInstance()
368#                                             )
369   
370    def commit(self):
371        if self.currentGds:
372            self.error(0) 
373            sample_type = None
374            self.progressBarInit()
375            self.progressBarSet(10)
376           
377            def getdata(gds_id, **kwargs):
378                gds = obiGEO.GDS(gds_id)
379                data = gds.getdata(**kwargs)
380                return data
381           
382            self.setEnabled(False)
383            call = self.asyncCall(getdata, (self.currentGds["dataset_id"],), dict(report_genes=self.mergeSpots,
384                                           transpose=self.outputRows,
385                                           sample_type=sample_type if sample_type!="Include all" else None),
386                                  onResult=self.onData,
387                                  onFinished=lambda: self.setEnabled(True),
388                                  onError=self.onAsyncError,
389                                  threadPool=QThreadPool.globalInstance()
390                                 )
391            call.__call__() #invoke
392#            data = gds.getdata(report_genes=self.mergeSpots, transpose=self.outputRows, sample_type=sample_type if sample_type!="Include all" else None)
393
394    def onAsyncError(self, (exctype, value, tb)):
395        import ftplib
396        if issubclass(exctype, ftplib.error_temp):
397            self.error(0, "Can not download dataset from NCBI ftp server! Try again later.")
398        elif issubclass(exctype, ftplib.all_errors):
399            self.error(0, "Error while connecting to the NCBI ftp server! %s" % str(value))
400        else:
401            sys.excepthook(exctype, value, tb)
402           
403        self.progressBarFinished()
404
405    def onData(self, data):
406        self.progressBarSet(50)
407       
408        samples = self.selectedSamples()
409       
410        message = None
411        if self.outputRows:
412            samples = set(s[1] for s in samples) # dont have info on sample types in the data class variable
413            select = [1 if samples.issuperset(str(ex.getclass()).split("|")) else 0 for ex in data]
414            data = data.select(select)
415            # TODO: add sample types as separate features 
416            data.domain.classVar.values = ["|".join([cl for cl in val.split("|") if cl in samples]) for val in data.domain.classVar.values]
417            if len(data) == 0:
418                message = "No selected samples with selected sample annotations."
419        else:
420            samples = set(samples)
421            domain = orange.Domain([attr for attr in data.domain.attributes if samples.issuperset(attr.attributes.items())], data.domain.classVar)
422            domain.addmetas(data.domain.getmetas())
423            stypes = set(s[0] for s in samples)
424            for attr in domain.attributes:
425                attr.attributes = dict([(key, value) for key, value in attr.attributes.items() if key in stypes])
426            data = orange.ExampleTable(domain, data)
427
428        data_hints.set_hint(data, "taxid", self.currentGds.get("taxid", ""), 10.0)
429        data_hints.set_hint(data, "genesinrows", self.outputRows, 10.0)
430       
431        self.progressBarFinished()
432        self.send("Expression Data", data)
433
434        model = self.treeWidget.model().sourceModel()
435        row = self.gds.index(self.currentGds)
436#            model._roleData[Qt.ForegroundRole][row].update(zip(range(1, 7), [QVariant(QColor(LOCAL_GDS_COLOR))] * 6))
437        model.setData(model.index(row, 0),  QVariant(" "), Qt.DisplayRole) 
438#            model.emit(SIGNAL("dataChanged(const QModelIndex &, const QModelIndex &)"), model.index(row, 0), model.index(row, 0))
439        self.updateInfo()
440        self.selectionChanged = False
441       
442    def splitterMoved(self, *args):
443        self.splitterSettings = [str(sp.saveState()) for sp in self.splitters]
444
445if __name__ == "__main__":
446    app = QApplication(sys.argv)
447    w = OWGEODatasets()
448    w.show()
449    app.exec_()
450    w.saveSettings()
Note: See TracBrowser for help on using the repository browser.