Changeset 1920:16fbbd5ec146 in orange-bioinformatics


Ignore:
Timestamp:
11/29/13 18:42:38 (5 months ago)
Author:
Ales Erjavec <ales.erjavec@…>
Branch:
default
Message:

Implemented completion on gene symbol names.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • orangecontrib/bio/widgets/OWSelectGenes.py

    r1913 r1920  
     1import sys 
    12import re 
    23import unicodedata 
     
    1415    QStyleOptionViewItemV4, QPalette, QColor, QApplication, QAction, 
    1516    QToolButton, QItemSelectionModel, QPlainTextDocumentLayout, QTextDocument, 
    16     QRadioButton, QButtonGroup, QStyleOptionButton, QMenu, QDialog 
     17    QRadioButton, QButtonGroup, QStyleOptionButton, QMenu, QDialog, 
     18    QStackedWidget, QComboBox 
    1719) 
    1820 
    19 from PyQt4.QtCore import Qt, QEvent, QVariant, QThread, pyqtSignal as Signal 
     21from PyQt4.QtCore import Qt, QEvent, QVariant, QThread 
     22from PyQt4.QtCore import pyqtSignal as Signal 
    2023 
    2124import Orange 
     
    3336from Orange.orng.orngDataCaching import data_hints 
    3437from Orange.bio import obiGene as geneinfo 
     38from Orange.bio import obiTaxonomy as taxonomy 
    3539 
    3640 
     
    133137 
    134138    settingsList = ["autoCommit", "preserveOrder", "savedSelections", 
    135                     "selectedSelectionIndex", "selectedSource"] 
     139                    "selectedSelectionIndex", "selectedSource", 
     140                    "completeOnSymbols"] 
    136141 
    137142    SelectInput, SelectCustom = 0, 1 
     
    152157        self.selectedSelectionIndex = -1 
    153158        self.selectedSource = OWSelectGenes.SelectCustom 
     159        self.completeOnSymbols = True 
    154160 
    155161        self.loadSettings() 
     
    157163        # Input variables that could contain names 
    158164        self.variables = VariableListModel() 
    159         # All gene names from the input (in self.geneIndex column) 
     165        # All gene names and their symbols 
    160166        self.geneNames = [] 
     167        # A list of (name, info) where name is from the input 
     168        # (geneVar column) and info is the NCBIGeneInfo object if available 
     169        # or None 
     170        self.genes = [] 
    161171        # Output changed flag 
    162172        self._changedFlag = False 
     
    170180        # Selected subset variable index 
    171181        self.subsetGeneIndex = -1 
     182        self.organisms = [] 
     183        self.taxidindex = {} 
    172184        self.geneinfo = (None, None) 
    173185        self._executor = ThreadExecutor() 
     186 
    174187        self._infotask = None 
    175188 
     
    232245        box = OWGUI.widgetBox(self.entrybox, "Select Genes", flat=True) 
    233246        box.setToolTip("Enter gene names to select") 
     247        box.layout().setSpacing(1) 
    234248 
    235249        self.entryField = ListTextEdit(box) 
     
    242256        completer = ListCompleter() 
    243257        completer.setCompletionMode(QCompleter.PopupCompletion) 
    244 #         completer.setCompletionRole(OWSelectGenes.CompletionRole) 
     258        completer.setCompletionRole( 
     259            self.CompletionRole if self.completeOnSymbols else Qt.DisplayRole 
     260        ) 
    245261        completer.setCaseSensitivity(Qt.CaseInsensitive) 
    246262        completer.setMaxVisibleItems(10) 
     
    261277        self.entryField.setCompleter(completer) 
    262278 
     279        toolbar = QFrame() 
     280        toolbar.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Fixed) 
     281        layout = QHBoxLayout() 
     282        layout.setContentsMargins(0, 0, 0, 0) 
     283        layout.setSpacing(1) 
     284 
     285        addaction = QAction("+", self) 
     286        addmenu = QMenu() 
     287 
     288        action = addmenu.addAction("Import names from gene sets...") 
     289        action.triggered.connect(self.importGeneSet) 
     290 
     291#         addmenu.addAction("Import names from text file...") 
     292        addaction.setMenu(addmenu) 
     293 
     294        def button(action, popupMode=None): 
     295            b = QToolButton() 
     296            b.setDefaultAction(action) 
     297            if popupMode is not None: 
     298                b.setPopupMode(QToolButton.InstantPopup) 
     299            return b 
     300 
     301        b = button(addaction, popupMode=QToolButton.InstantPopup) 
     302        layout.addWidget(b) 
     303 
     304        moreaction = QAction("More", self) 
     305 
     306        moremenu = QMenu() 
     307        self.completeOnSymbolsAction = QAction( 
     308            "Complete on gene symbol names", self, 
     309            toolTip="Use symbol names for auto completion.", 
     310            checkable=True, 
     311            checked=self.completeOnSymbols 
     312        ) 
     313 
     314        self.completeOnSymbolsAction.toggled[bool].connect( 
     315            self._onToggleSymbolCompletion 
     316        ) 
     317 
     318        moremenu.addAction(self.completeOnSymbolsAction) 
     319 
     320        self.translateAction = QAction( 
     321            "Translate all names to official symbol names", self, 
     322            enabled=False 
     323        ) 
     324        self.translateAction.triggered.connect(self._onTranslate) 
     325 
     326        moremenu.addAction(self.translateAction) 
     327        moreaction.setMenu(moremenu) 
     328 
     329        b = button(moreaction, popupMode=QToolButton.InstantPopup) 
     330        layout.addWidget(b) 
     331 
     332        self.organismsCombo = QComboBox() 
     333        self.organismsCombo.addItem("...") 
     334        self.organismsCombo.model().item(0).setEnabled(False) 
     335        self.organismsCombo.setMinimumWidth(200) 
     336        self.organismsCombo.activated[int].connect(self._onOrganismActivated) 
     337 
     338        layout.addSpacing(10) 
     339        layout.addWidget(self.organismsCombo) 
     340        self.pb = QProgressBar() 
     341        self.pb.hide() 
     342        self.pbstack = QStackedWidget() 
     343        self.pbstack.addWidget(self.pb) 
     344 
     345        layout.addSpacing(10) 
     346        layout.addWidget(self.pbstack, 0, Qt.AlignRight | Qt.AlignVCenter) 
     347        toolbar.setLayout(layout) 
     348 
     349        box.layout().addWidget(toolbar) 
     350 
    263351        box = OWGUI.widgetBox(self.entrybox, "Saved Selections", flat=True) 
    264352        box.setToolTip("Save/Select/Update saved gene selections") 
     
    288376            toolTip="Delete the current saved selection") 
    289377 
    290         self.actionMore = QAction("More", self) 
    291  
    292         optionsmenu = QMenu() 
    293         action = optionsmenu.addAction("Import names from gene sets...") 
    294         action.triggered.connect(self.importGeneSet) 
    295  
    296         self.actionMore.setMenu(optionsmenu) 
    297  
    298378        toolbar = QFrame() 
    299379        layout = QHBoxLayout() 
     
    301381        layout.setSpacing(1) 
    302382 
    303         def button(action): 
    304             b = QToolButton() 
    305             b.setDefaultAction(action) 
    306             return b 
    307  
    308383        b = button(self.actionAdd) 
    309384        layout.addWidget(b) 
     
    313388 
    314389        b = button(self.actionSave) 
    315         layout.addWidget(b) 
    316  
    317         b = button(self.actionMore) 
    318         b.setPopupMode(QToolButton.InstantPopup) 
    319390        layout.addWidget(b) 
    320391 
     
    351422                QItemSelectionModel.Select 
    352423            ) 
     424 
    353425        self._updateActions() 
     426 
     427        self._inittask = Task( 
     428            function=lambda: 
     429                [(taxid, taxonomy.name(taxid)) 
     430                  for taxid in taxonomy.common_taxids()]) 
     431 
     432        self._inittask.finished.connect(self._onInit) 
     433        self._executor.submit(self._inittask) 
     434        self.pb.show() 
     435        self.pb.setRange(0, 0) 
     436 
     437    def _onInit(self): 
     438        self.organisms = self._inittask.result() 
     439        self.organismsCombo.clear() 
     440        self.organismsCombo.addItems([name for _, name in self.organisms]) 
     441 
     442        self.taxidindex = \ 
     443            {taxid: i for i, (taxid, _) in enumerate(self.organisms)} 
     444 
     445        self.organismsCombo.setCurrentIndex( 
     446            self.taxidindex.get(self.taxid, -1)) 
     447 
     448        self.pb.hide() 
     449 
     450    def _updateGeneInfo(self): 
     451        self.geneinfo = (self.taxid, None) 
     452        self.translateAction.setEnabled(False) 
     453 
     454        if self._infotask: 
     455            # Try to cancel existing pending task 
     456            self._infotask.future().cancel() 
     457            self._infotask.resultReady.disconnect(self._onGeneInfoReady) 
     458            self._infotask.exceptionReady.disconnect(self._onGeneInfoError) 
     459 
     460        def ncbi_gene_info(taxid=self.taxid): 
     461            try: 
     462                return (taxid, geneinfo.NCBIGeneInfo(taxid)) 
     463            except BaseException: 
     464                sys.excepthook(*sys.exc_info()) 
     465                raise 
     466 
     467        task = Task(function=ncbi_gene_info) 
     468        task.resultReady.connect(self._onGeneInfoReady) 
     469        task.exceptionReady.connect(self._onGeneInfoError) 
     470        self._infotask = task 
     471        self._executor.submit(task) 
     472        self.pb.show() 
     473 
     474    def _onOrganismActivated(self, index): 
     475        try: 
     476            taxid, _ = self.organisms[index] 
     477        except IndexError: 
     478            pass 
     479        else: 
     480            if taxid != self.taxid: 
     481                self.taxid = taxid 
     482                self._updateGeneInfo() 
    354483 
    355484    def setData(self, data): 
     
    375504        self._changedFlag = True 
    376505 
     506        oldtaxid = self.taxid 
    377507        if data is not None: 
    378508            self.taxid = data_hints.get_hint(data, "taxid", None) 
     
    385515            self.geneinfo = (None, None) 
    386516 
    387         if self.taxid != self.geneinfo[0]:  # and self.taxid in available_organisms 
    388             self.geneinfo = (self.taxid, None) 
    389             # TODO: cache the least recently used info objects. 
    390             # Or better yet, optimize the implementation of NCBIGeneInfo 
    391             task = Task(function=lambda taxid=self.taxid: 
    392                                     (taxid, geneinfo.NCBIGeneInfo(taxid))) 
    393             task.resultReady.connect(self._on_geneInfoReady) 
    394             self._infotask = task 
    395             self._executor.submit(task) 
     517        if oldtaxid != self.taxid: 
     518            self.organismsCombo.setCurrentIndex( 
     519                self.taxidindex.get(self.taxid, -1)) 
     520 
     521            if self.taxid in self.taxidindex: 
     522                self._updateGeneInfo() 
    396523 
    397524        self._updateCompletionModel() 
     
    473600        selection = self.selectedGenes() 
    474601 
     602        if self.geneinfo[1] is not None: 
     603            backmap = dict((info.symbol, name) for name, info in self.genes 
     604                           if info is not None) 
     605            names = set([name for name, _ in self.genes]) 
     606            selection = [backmap.get(name, name) for name in selection 
     607                         and name not in names] 
     608 
    475609        if self.geneVar is not None: 
    476610            data = select_by_genes(self.data, self.geneVar, 
     
    515649 
    516650        names = sorted(set(names)) 
     651        genes = zip(names, map(infodict.get, names)) 
     652 
     653        symbols = [info.symbol for _, info in genes if info is not None] 
    517654 
    518655        model = QStandardItemModel() 
    519656 
    520         def make_row(name): 
    521             if name in infodict: 
    522                 info = infodict[name] 
     657        def make_row(name, info): 
     658            if info is not None: 
    523659                col1 = QStandardItem(name) 
    524660                col1.setData(info.symbol, OWSelectGenes.CompletionRole) 
     
    527663                        QStandardItem(info.description)] 
    528664            else: 
    529                 return [QStandardItem(name)] 
    530  
    531         for name in names: 
    532             model.appendRow(make_row(name)) 
    533  
    534         self.geneNames = names 
     665                col1 = QStandardItem(name) 
     666                col1.setData(name, OWSelectGenes.CompletionRole) 
     667                return [col1] 
     668 
     669        for name, info in genes: 
     670            model.appendRow(make_row(name, info)) 
     671 
     672        self.geneNames = sorted(set(names) | set(symbols)) 
     673        self.genes = genes 
    535674        self.entryField.completer().model().setSourceModel(model) 
    536         self.entryField.document().highlighter.setNames(names) 
     675        self.entryField.document().highlighter.setNames(names + symbols) 
     676 
     677        self._updatePopupSections() 
    537678 
    538679    def _onGeneIndexChanged(self): 
     
    559700            item.modified = item.savedata != names 
    560701 
    561     def _on_geneInfoReady(self, geneinfo): 
     702    def _onToggleSymbolCompletion(self, state): 
     703        completer = self.entryField.completer() 
     704        completer.setCompletionRole( 
     705            self.CompletionRole if state else Qt.DisplayRole 
     706        ) 
     707        self.completeOnSymbols = state 
     708        self._updatePopupSections() 
     709 
     710    def _onTranslate(self): 
     711        if self.geneinfo[1] is not None: 
     712            items = self.entryField.items() 
     713            entries = map(self.geneinfo[1].get_info, items) 
     714            items = [info.symbol if info is not None else item 
     715                     for item, info in zip(items, entries)] 
     716            self.entryField.selectAll() 
     717            self.entryField.insertPlainText(" ".join(items)) 
     718 
     719    def _onGeneInfoReady(self, geneinfo): 
    562720        assert QThread.currentThread() is self.thread() 
    563721        # Check if the gene info is for the correct (current requested) 
     
    566724        if self.geneinfo[0] == geneinfo[0]: 
    567725            self.geneinfo = geneinfo 
     726            self.translateAction.setEnabled(True) 
     727            self.pb.hide() 
    568728            self._updateCompletionModel() 
     729 
     730    def _onGeneInfoError(self, exc): 
     731        self.error(0, str(exc)) 
     732 
     733    def _updatePopupSections(self): 
     734        completer = self.entryField.completer() 
     735        popup = completer.popup() 
     736        assert isinstance(popup, QTreeView) 
     737        header = popup.header() 
     738        # The column in which the symbols should be 
     739        symbol_col = 0 if self.completeOnSymbols else 1 
     740        if symbol_col != header.sectionPosition(1): 
     741            header.moveSection(0, 1) 
    569742 
    570743    def _selectedSaveSlot(self): 
     
    655828            self.entryField.setFocus() 
    656829            self.entryField.moveCursor(QTextCursor.End) 
    657             self.taxid = dialog.currentOrganism() 
    658830 
    659831    def _onSelectedSaveSlotChanged(self): 
     
    738910 
    739911    def onDeleteWidget(self): 
     912        self._inittask.future().cancel() 
     913 
    740914        if self._infotask: 
    741915            self._infotask.future().cancel() 
    742916 
    743         self._executor.shutdown() 
     917        self._executor.shutdown(wait=True) 
    744918        OWWidget.onDeleteWidget(self) 
    745919 
     
    11581332 
    11591333from PyQt4.QtGui import ( 
    1160     QVBoxLayout, QComboBox, QLineEdit, QDialogButtonBox, 
    1161     QProgressBar, QStackedWidget, QSizePolicy 
     1334    QVBoxLayout, QLineEdit, QDialogButtonBox, 
     1335    QProgressBar, QSizePolicy 
    11621336) 
    11631337 
    11641338from PyQt4.QtCore import QSize 
    11651339from Orange.bio import obiGeneSets as genesets 
    1166 from Orange.bio import obiTaxonomy as taxonomy 
    11671340from Orange.utils import serverfiles 
    11681341 
Note: See TracChangeset for help on using the changeset viewer.