source: orange-bioinformatics/widgets/OWGEODatasets.py @ 1343:67c0a2030d8c

Revision 1343:67c0a2030d8c, 20.2 KB checked in by ales_erjavec <ales.erjavec@…>, 3 years ago (diff)

Improved ftplib.error_temp handling (waiting 3 seconds before retrying).
Display an error in the GEO Datasets widget after 3 retrys.

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
81class OWGEODatasets(OWWidget):
82    settingsList = ["outputRows", "mergeSpots", "gdsSelectionStates", "splitterSettings", "currentGds", "autoCommit"]
83
84    def __init__(self, parent=None ,signalManager=None, name=" GEO Data sets"):
85        OWWidget.__init__(self, parent ,signalManager, name)
86
87        self.outputs = [("Expression Data", ExampleTable)]
88
89        ## Settings
90#        self.selectedSubsets = []
91#        self.sampleSubsets = []
92        self.selectedAnnotation = 0
93        self.includeIf = False
94        self.minSamples = 3
95        self.autoCommit = False
96        self.outputRows = 0
97        self.mergeSpots = True
98        self.filterString = ""
99        self.currentGds = None
100        self.selectionChanged = False
101        self.autoCommit = False
102        self.gdsSelectionStates = {}
103        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',
104                                 '\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']
105
106        self.loadSettings()
107
108        ## GUI
109        self.infoBox = OWGUI.widgetLabel(OWGUI.widgetBox(self.controlArea, "Info", addSpace=True), "\n\n")
110#        box = OWGUI.widgetBox(self.controlArea, "Sample Subset")
111#        OWGUI.listBox(box, self, "selectedSubsets", "sampleSubsets", selectionMode=QListWidget.ExtendedSelection)
112#        box = OWGUI.widgetBox(self.controlArea, "Sample Annotations (Types)")
113#        self.annotationCombo = OWGUI.comboBox(box, self, "selectedAnnotation", items=["Include all"])
114       
115##        OWGUI.button(box, self, "Clear selection", callback=self.clearSubsetSelection)
116##        c = OWGUI.checkBox(box, self, "includeIf", "Include if at least", callback=self.commitIf)
117##        OWGUI.spin(OWGUI.indentedBox(box), self, "minSamples", 2, 100, posttext="samples", callback=self.commitIf)
118
119        box = OWGUI.widgetBox(self.controlArea, "Output", addSpace=True)
120        OWGUI.radioButtonsInBox(box, self, "outputRows", ["Genes or spots", "Samples"], "Rows", callback=self.commitIf)
121        OWGUI.checkBox(box, self, "mergeSpots", "Merge spots of same gene", callback=self.commitIf)
122
123        box = OWGUI.widgetBox(self.controlArea, "Output", addSpace=True)
124        self.commitButton = OWGUI.button(box, self, "Commit", callback=self.commit)
125#        self.commitButton.setDisabled(True)
126        cb = OWGUI.checkBox(box, self, "autoCommit", "Commit on any change")
127        OWGUI.setStopper(self, self.commitButton, cb, "selectionChanged", self.commit)
128##        OWGUI.checkBox(box, self, "autoCommit", "Commit automatically")
129        OWGUI.rubber(self.controlArea)
130
131        self.filterLineEdit = OWGUIEx.lineEditHint(self.mainArea, self, "filterString", "Filter", caseSensitive=False, matchAnywhere=True, listUpdateCallback=self.filter, callbackOnType=True, callback=self.filter, delimiters=" ")
132        splitter = QSplitter(Qt.Vertical, self.mainArea)
133        self.mainArea.layout().addWidget(splitter)
134        self.treeWidget = QTreeView(splitter)
135#        splitter.addWidget(self.treeWidget)
136       
137        self.treeWidget.setSelectionMode(QAbstractItemView.SingleSelection)
138        self.treeWidget.setRootIsDecorated(False)
139        self.treeWidget.setSortingEnabled(True)
140        self.treeWidget.setAlternatingRowColors(True)
141        self.treeWidget.setItemDelegate(LinkStyledItemDelegate(self.treeWidget))
142        self.treeWidget.setItemDelegateForColumn(0, OWGUI.IndicatorItemDelegate(self.treeWidget, role=Qt.DisplayRole))
143       
144#        self.mainArea.layout().addWidget(self.treeWidget)
145        self.connect(self.treeWidget, SIGNAL("itemSelectionChanged ()"), self.updateSelection)
146        self.treeWidget.viewport().setMouseTracking(True)
147##        self.connect(self.treeWidget, SIGNAL("currentItemChanged(QTreeWidgetItem*, QTreeWidgetItem*))"), self.updateSelection_)
148#        self.connect(self.treeWidget.model(), SIGNAL("layoutChanged()"), self.filter)
149        splitterH = QSplitter(Qt.Horizontal, splitter) 
150#        splitter.addWidget(splitterH)
151        box = OWGUI.widgetBox(splitterH, "Description")
152        self.infoGDS = OWGUI.widgetLabel(box, "")
153        self.infoGDS.setWordWrap(True)
154        OWGUI.rubber(box)
155       
156        box = OWGUI.widgetBox(splitterH, "Sample Annotations")
157        self.annotationsTree = QTreeWidget(box)
158        self.annotationsTree.setHeaderLabels(["Type (Sample annotations)", "Sample count"])
159        self.annotationsTree.setRootIsDecorated(True)
160        box.layout().addWidget(self.annotationsTree)
161        self.connect(self.annotationsTree, SIGNAL("itemChanged(QTreeWidgetItem * , int)"), self.annotationSelectionChanged)
162        self._annotationsUpdating = False
163        self.splitters = splitter, splitterH
164        self.connect(splitter, SIGNAL("splitterMoved(int, int)"), self.splitterMoved)
165        self.connect(splitterH, SIGNAL("splitterMoved(int, int)"), self.splitterMoved)
166       
167        for sp, setting in zip(self.splitters, self.splitterSettings):
168            sp.restoreState(setting)
169           
170        self.searchKeys = ["dataset_id", "title", "platform_organism", "description"]
171        self.cells = []
172#        self.currentGds = None
173        QTimer.singleShot(50, self.updateTable)
174        self.resize(1000, 600)
175
176    def updateInfo(self):
177        gds_info = obiGEO.GDSInfo()
178        text = "%i datasets\n%i datasets cached\n" % (len(gds_info), len(glob.glob(orngServerFiles.localpath("GEO") + "/GDS*")))
179        filtered = len([row for row in range(len(self.cells)) if self.rowFiltered(row)])
180        if len(self.cells) != filtered:
181            text += ("%i after filtering") % filtered
182        self.infoBox.setText(text)
183       
184    def updateTable(self):
185        self.treeItems = []
186        self.progressBarInit()
187        with orngServerFiles.DownloadProgress.setredirect(self.progressBarSet):
188            info = obiGEO.GDSInfo()
189        milestones = set(range(0, len(info), max(len(info)/100, 1)))
190        self.cells = cells = []
191        gdsLinks = []
192        pmLinks = []
193        localGDS = []
194        self.gds = []
195        for i, (name, gds) in enumerate(info.items()):
196            local = os.path.exists(orngServerFiles.localpath(obiGEO.DOMAIN, gds["dataset_id"] + ".soft.gz"))
197            cells.append([" " if local else "", gds["dataset_id"], gds["title"], gds["platform_organism"], len(gds["samples"]), gds["feature_count"],
198                          gds["gene_count"], len(gds["subsets"]), gds.get("pubmed_id", "")])
199
200            gdsLinks.append("http://www.ncbi.nlm.nih.gov/sites/GDSbrowser?acc=%s" % gds["dataset_id"])
201            pmLinks.append("http://www.ncbi.nlm.nih.gov/pubmed/%s" % gds.get("pubmed_id") if gds.get("pubmed_id") else QVariant())
202
203            if local:
204                localGDS.append(i)
205            self.gds.append(gds)
206           
207            if i in milestones:
208                self.progressBarSet(100.0*i/len(info))
209
210        model = TreeModel(cells, ["", "ID", "Title", "Organism", "Samples", "Features", "Genes", "Subsets", "PubMedID"], self.treeWidget)
211        model.setColumnLinks(1, gdsLinks)
212        model.setColumnLinks(8, pmLinks)
213#        for i in localGDS:
214#            model._roleData[Qt.ForegroundRole][i].update(zip(range(2, 8), [QVariant(QColor(LOCAL_GDS_COLOR))] * 6))
215#            mode.._roleData[OWGUI.IndicatorItemDelegate.]
216        proxyModel = QSortFilterProxyModel(self.treeWidget)
217        proxyModel.setSourceModel(model)
218        self.treeWidget.setModel(proxyModel)
219        self.connect(self.treeWidget.selectionModel(), SIGNAL("selectionChanged(QItemSelection , QItemSelection )"), self.updateSelection)
220        filterItems = " ".join([self.gds[i][key] for i in range(len(self.gds)) for key in self.searchKeys])
221        filterItems = reduce(lambda s, d: s.replace(d, " "), [",", ".", ":", "!", "?", "(", ")"], filterItems.lower())
222        filterItems = sorted(set(filterItems.split(" ")))
223        self.filterLineEdit.setItems(filterItems)
224       
225        for i in range(8):
226            self.treeWidget.resizeColumnToContents(i)
227        self.treeWidget.setColumnWidth(1, min(self.treeWidget.columnWidth(1), 300))
228        self.treeWidget.setColumnWidth(2, min(self.treeWidget.columnWidth(2), 200))
229        self.progressBarFinished()
230
231        if self.currentGds:
232            gdss = [(i, model.data(model.index(i,1), Qt.DisplayRole)) for i in range(model.rowCount())]
233            current = [i for i, variant in gdss if variant.isValid() and str(variant.toString()) == self.currentGds["dataset_id"]]
234            if current:
235                mapFromSource = self.treeWidget.model().mapFromSource
236                self.treeWidget.selectionModel().select(mapFromSource(model.index(current[0], 0)), QItemSelectionModel.Select | QItemSelectionModel.Rows)
237           
238        self.updateInfo()
239
240    def updateSelection(self, *args):
241        current = self.treeWidget.selectedIndexes()
242        mapToSource = self.treeWidget.model().mapToSource
243        current = [mapToSource(index).row() for index in current]
244        if current:
245            self.currentGds = self.gds[current[0]]
246            self.setAnnotations(self.currentGds)
247            self.infoGDS.setText(self.currentGds.get("description", ""))
248        else:
249            self.currentGds = None
250#        self.commitButton.setDisabled(not bool(self.currentGds))
251        self.commitIf()
252       
253   
254    def setAnnotations(self, gds):
255#        self.sampleSubsets = ["%s (%d)" % (s["description"], len(s["sample_id"])) for s in gds["subsets"]]
256#        self.annotationCombo.clear()
257#        self.annotationCombo.addItems(["Include all"] + list(set([sampleinfo["type"] for sampleinfo in gds["subsets"]])))
258       
259        self._annotationsUpdating = True
260        self.annotationsTree.clear()
261        annotations = reduce(lambda d, info: d[info["type"]].add(info["description"]) or d, gds["subsets"], defaultdict(set))
262        subsetscount = dict([(s["description"], str(len(s["sample_id"]))) for s in gds["subsets"]])
263        for type, subsets in annotations.items():
264            key = (gds["dataset_id"], type)
265            subsetItem = QTreeWidgetItem(self.annotationsTree, [type])
266            subsetItem.setFlags(subsetItem.flags() | Qt.ItemIsUserCheckable | Qt.ItemIsTristate)
267            subsetItem.setCheckState(0, self.gdsSelectionStates.get(key, Qt.Checked))
268            subsetItem.key = key
269            for subset in subsets:
270                key = (gds["dataset_id"], type, subset)
271                item = QTreeWidgetItem(subsetItem, [subset, subsetscount.get(subset, "")])
272                item.setFlags(item.flags() | Qt.ItemIsUserCheckable)
273                item.setCheckState(0, self.gdsSelectionStates.get(key, Qt.Checked))
274                item.key = key
275        self._annotationsUpdating = False
276        self.annotationsTree.expandAll()
277        for i in range(self.annotationsTree.columnCount()):
278            self.annotationsTree.resizeColumnToContents(i)
279               
280    def annotationSelectionChanged(self, item, column):
281        if self._annotationsUpdating:
282            return 
283        for i in range(self.annotationsTree.topLevelItemCount()):
284            item = self.annotationsTree.topLevelItem(i)
285            self.gdsSelectionStates[item.key] = item.checkState(0)
286            for j in range(item.childCount()):
287                child = item.child(j)
288                self.gdsSelectionStates[child.key] = child.checkState(0)
289       
290    def rowFiltered(self, row):
291        filterStrings = self.filterString.lower().split()
292        try:
293            string = " ".join([self.gds[row].get(key, "").lower() for key in self.searchKeys])
294            return not all([s in string for s in filterStrings])
295        except UnicodeDecodeError:
296            string = " ".join([unicode(self.gds[row].get(key, "").lower(), errors="ignore") for key in self.searchKeys])
297            return not all([s in string for s in filterStrings])
298   
299    def filter(self):
300        filterStrings = self.filterString.lower().split()
301        mapFromSource = self.treeWidget.model().mapFromSource
302        index = self.treeWidget.model().sourceModel().index
303       
304        for i, row in enumerate(self.cells):
305            self.treeWidget.setRowHidden(mapFromSource(index(i, 0)).row(), QModelIndex(), self.rowFiltered(i))
306        self.updateInfo()
307
308    def commitIf(self):
309        if self.autoCommit:
310            self.commit()
311        else:
312            self.selectionChanged = True
313           
314    def commit(self):
315        if self.currentGds:
316#            classes = [s["description"] for s in self.currentGds["subsets"]]
317#            classes = [classes[i] for i in self.selectedSubsets] or None
318#            sample_type = self.annotationCombo.currentText()
319            sample_type = None
320            self.progressBarInit()
321            self.progressBarSet(10)
322           
323            def getdata(gds_id, **kwargs):
324                gds = obiGEO.GDS(gds_id)
325                data = gds.getdata(**kwargs)
326                return data
327            from OWConcurrent import createTask
328            self.setEnabled(False)
329            qApp.processEvents()
330            self.get_data_async = createTask(getdata, (self.currentGds["dataset_id"],), dict(report_genes=self.mergeSpots,
331                                                                   transpose=self.outputRows,
332                                                                   sample_type=sample_type if sample_type!="Include all" else None),
333                                             onResult=self.onData, onFinished=lambda: self.setEnabled(True),
334                                             threadPool=QThreadPool.globalInstance()
335                                             )
336           
337    def commit(self):
338        if self.currentGds:
339            self.error(0) 
340            sample_type = None
341            self.progressBarInit()
342            self.progressBarSet(10)
343           
344            def getdata(gds_id, **kwargs):
345                gds = obiGEO.GDS(gds_id)
346                data = gds.getdata(**kwargs)
347                return data
348           
349            self.setEnabled(False)
350#            qApp.processEvents()
351            call = self.asyncCall(getdata, (self.currentGds["dataset_id"],), dict(report_genes=self.mergeSpots,
352                                           transpose=self.outputRows,
353                                           sample_type=sample_type if sample_type!="Include all" else None),
354                                  onResult=self.onData,
355                                  onFinished=lambda: self.setEnabled(True),
356                                  onError=self.onAsyncError,
357                                  threadPool=QThreadPool.globalInstance()
358                                 )
359            call.__call__() #invoke
360#            data = gds.getdata(report_genes=self.mergeSpots, transpose=self.outputRows, sample_type=sample_type if sample_type!="Include all" else None)
361
362    def onAsyncError(self, (exctype, value, tb)):
363        import ftplib
364        if issubclass(exctype, ftplib.error_temp):
365            self.error(0, "Can not download dataset from NCBI ftp server! Try again later.")
366        elif issubclass(exctype, ftplib.all_errors):
367            self.error(0, "Error while connecting to the NCBI ftp server! %s" % str(value))
368        else:
369            sys.excepthook(exctype, value, tb)
370           
371        self.progressBarFinished()
372
373    def onData(self, data):
374        self.progressBarSet(50)
375#            samples = set([self.annotationsTree.topLevelItem(i).child(j).key[2] for i in range(self.annotationsTree.topLevelItemCount()) for j in self.annotationsTree.topLevelItem(i).])
376        class itemiter(QTreeWidgetItemIterator):
377            def next(self):
378                self.__iadd__(1)
379                if self.value():
380                    return self.value()
381                else:
382                    raise StopIteration
383            def __iter__(self):
384                return self
385           
386        samples = set([str(item.text(0)) for item in itemiter(self.annotationsTree) if self.gdsSelectionStates.get(item.key, True)])
387       
388        if self.outputRows:
389            select = [1 if samples.intersection(str(ex.getclass()).split("|")) else 0 for ex in data]
390            data = data.select(select)
391            data.domain.classVar.values = ["|".join([cl for cl in val.split("|") if cl in samples]) for val in data.domain.classVar.values]
392        else:
393            domain = orange.Domain([attr for attr in data.domain.attributes if samples.intersection(attr.attributes.values())], data.domain.classVar)
394            domain.addmetas(data.domain.getmetas())
395            for attr in domain.attributes:
396                attr.attributes = dict([(key, value) for key, value in attr.attributes.items() if value in samples])
397            data = orange.ExampleTable(domain, data)
398
399        data_hints.set_hint(data, "taxid", self.currentGds.get("taxid", ""), 10.0)
400        data_hints.set_hint(data, "genesinrows", self.outputRows, 10.0)
401       
402        self.progressBarFinished()
403        self.send("Expression Data", data)
404
405        model = self.treeWidget.model().sourceModel()
406        row = self.gds.index(self.currentGds)
407#            model._roleData[Qt.ForegroundRole][row].update(zip(range(1, 7), [QVariant(QColor(LOCAL_GDS_COLOR))] * 6))
408        model.setData(model.index(row, 0),  QVariant(" "), Qt.DisplayRole) 
409#            model.emit(SIGNAL("dataChanged(const QModelIndex &, const QModelIndex &)"), model.index(row, 0), model.index(row, 0))
410        self.updateInfo()
411        self.selectionChanged = False
412       
413    def splitterMoved(self, *args):
414        self.splitterSettings = [str(sp.saveState()) for sp in self.splitters]
415
416if __name__ == "__main__":
417    app = QApplication(sys.argv)
418    w = OWGEODatasets()
419    w.show()
420    app.exec_()
421    w.saveSettings()
Note: See TracBrowser for help on using the repository browser.