class OWdictyExpress(OWWidget): name = "dictyExpress" description = "Time-course gene expression data" icon = "../widgets/icons/OWdictyExpress.png" want_main_area = True priority = 3 class Inputs: pass class Outputs: etc_data = Output("Data", Table) class Error(OWWidget.Error): unreachable_host = Msg('Host not reachable') invalid_credentials = Msg('Invalid credentials') username = settings.Setting('') # password = settings.Setting('') gene_as_attr_name = settings.Setting(0) selected_item = settings.Setting(None, schema_only=True) auto_commit = settings.Setting(False, schema_only=True) def __init__(self): super().__init__() self.res = None self.organism = '44689' self.server = 'https://dictyexpress.research.bcm.edu' self.headerLabels = [x[1] for x in Labels] self.searchString = "" self.items = [] self.progress_bar = None # threads self.threadpool = QThreadPool() # Login Section box = gui.widgetBox(self.controlArea, 'Login') self.namefield = gui.lineEdit(box, self, "username", "Username:"******"password", "Password:"******"Output", addSpace=True) gui.radioButtonsInBox(box, self, "gene_as_attr_name", ["Genes in rows", "Genes in columns"], callback=self.invalidate) self.controlArea.layout().addWidget(h_line()) self.refresh_button = gui.button(self.controlArea, self, "Refresh", callback=self.refresh) self.handle_cache_button(True) gui.rubber(self.controlArea) self.commit_button = gui.auto_commit(self.controlArea, self, "auto_commit", "&Commit", box=False) # Experiment Section label = QLabel("Available projects:") my_font = QFont() my_font.setBold(True) label.setFont(my_font) self.mainArea.layout().addWidget(label) self.mainArea.layout().addWidget(h_line()) self.filter = gui.lineEdit(self.mainArea, self, "searchString", "Filter:", callbackOnType=True, callback=self.search_update) self.experimentsWidget = QTreeWidget(alternatingRowColors=True, rootIsDecorated=False, uniformRowHeights=True, sortingEnabled=True) self.experimentsWidget.setItemDelegateForColumn( 0, gui.IndicatorItemDelegate(self, role=Qt.DisplayRole)) self.experimentsWidget.selectionModel().selectionChanged.connect( self.on_selection_changed) self.experimentsWidget.setHeaderLabels(self.headerLabels) self.mainArea.layout().addWidget(self.experimentsWidget) self.auth_set() self.connect() self.sizeHint() def sizeHint(self): return QSize(1400, 680) def auth_set(self): self.passfield.setDisabled(not self.username) def auth_changed(self): self.auth_set() self.connect() def refresh(self): self.reset() self.load_experiments() def reset(self): self.experimentsWidget.clear() # clear QTreeWidget self.items = [] # self.lastSelected = None self.searchString = "" def search_update(self): parts = self.searchString.split() for item in self.items: item.setHidden(not all(s in item for s in parts)) def progress_advance(self): # GUI should be updated in main thread. That's why we are calling advance method here assert threading.current_thread() == threading.main_thread() if self.progress_bar: self.progress_bar.advance() def handle_error(self, ex): self.progress_bar.finish() self.setStatusMessage('') if isinstance(ex, ConnectionError) or isinstance(ex, ValueError): self.Error.unreachable_host() print(ex) def load_experiments_result(self, experiments): self.load_tree_items(experiments) self.progress_bar.finish() self.setStatusMessage('') def connect(self): self.res = None self.Error.clear() self.reset() self.handle_cache_button(False) user, password = resolwe.DEFAULT_EMAIL, resolwe.DEFAULT_PASSWD if self.username or self.password: user, password = self.username, self.password try: self.res = resolwe.connect(user, password, self.server, 'genesis') except resolwe.ResolweAuthException: self.Error.invalid_credentials() else: self.load_experiments() self.handle_cache_button(True) def load_experiments(self): if self.res: # init progress bar self.progress_bar = gui.ProgressBar(self, iterations=2) # status message self.setStatusMessage('downloading experiments') worker = Worker(self.res.fetch_etc_objects, progress_callback=True) worker.signals.progress.connect(self.progress_advance) worker.signals.result.connect(self.load_experiments_result) worker.signals.error.connect(self.handle_error) # move download process to worker thread self.threadpool.start(worker) def load_tree_items(self, list_of_exp): self.items = [ CustomTreeItem(self.experimentsWidget, item) for item in list_of_exp ] for i in range(len(self.headerLabels)): self.experimentsWidget.resizeColumnToContents(i) self.set_cached_indicator() self.set_selected() def set_selected(self): for item in self.items: if self.selected_item and item.gen_data_id == self.selected_item: self.experimentsWidget.setCurrentItem(item) def on_selection_changed(self): self.invalidate() def invalidate(self): self.commit() def handle_cache_button(self, handle): self.refresh_button.setEnabled(handle) def send_to_output(self, result): self.progress_bar.finish() self.setStatusMessage('') etc_json, table_name = result # convert to table data = etc_to_table(etc_json, bool(self.gene_as_attr_name)) # set table name data.name = table_name # match genes gene_matcher = GeneMatcher(str(self.organism)) if not bool(self.gene_as_attr_name): if 'Gene' in data.domain: gene_column = data.domain['Gene'] gene_names = data.get_column_view(gene_column)[0] gene_matcher.genes = gene_names domain_ids = Domain([], metas=[StringVariable(ENTREZ_ID)]) data_ids = [[str(gene.gene_id) if gene.gene_id else '?'] for gene in gene_matcher.genes] table_ids = Table(domain_ids, data_ids) data = Table.concatenate([data, table_ids]) data.attributes[GENE_ID_COLUMN] = ENTREZ_ID else: gene_matcher.match_table_attributes(data) data.attributes[GENE_ID_ATTRIBUTE] = ENTREZ_ID # add table attributes data.attributes[TAX_ID] = str(self.organism) data.attributes[GENE_AS_ATTRIBUTE_NAME] = bool(self.gene_as_attr_name) # reset cache indicators self.set_cached_indicator() # send data to the output signal self.Outputs.etc_data.send(data) def commit(self): self.Error.clear() selected_item = self.experimentsWidget.currentItem( ) # get selected TreeItem self.selected_item = selected_item.gen_data_id if selected_item: # init progress bar self.progress_bar = gui.ProgressBar(self, iterations=1) # status message self.setStatusMessage('downloading experiment data') worker = Worker( self.res.download_etc_data, selected_item.gen_data_id, table_name=selected_item.data_name, progress_callback=True, ) worker.signals.progress.connect(self.progress_advance) worker.signals.result.connect(self.send_to_output) worker.signals.error.connect(self.handle_error) # move download process to worker thread self.threadpool.start(worker) def set_cached_indicator(self): cached = self.res.get_cached_ids() for item in self.items: if item.gen_data_id in cached: item.setData(0, Qt.DisplayRole, " ") else: item.setData(0, Qt.DisplayRole, "")
class OWDatabasesUpdate(OWWidget): name = "Databases Update" description = "Update local systems biology databases." icon = "../widgets/icons/OWDatabasesUpdate.svg" priority = 1 inputs = [] outputs = [] want_main_area = False def __init__(self, parent=None, signalManager=None, name="Databases update"): OWWidget.__init__(self, parent, signalManager, name, wantMainArea=False) self.searchString = "" fbox = gui.widgetBox(self.controlArea, "Filter") self.completer = TokenListCompleter(self, caseSensitivity=Qt.CaseInsensitive) self.lineEditFilter = QLineEdit(textChanged=self.search_update) self.lineEditFilter.setCompleter(self.completer) fbox.layout().addWidget(self.lineEditFilter) box = gui.widgetBox(self.controlArea, "Files") self.filesView = QTreeWidget(self) self.filesView.setHeaderLabels(header_labels) self.filesView.setRootIsDecorated(False) self.filesView.setUniformRowHeights(True) self.filesView.setSelectionMode(QAbstractItemView.NoSelection) self.filesView.setSortingEnabled(True) self.filesView.sortItems(header.Title, Qt.AscendingOrder) self.filesView.setItemDelegateForColumn( 0, UpdateOptionsItemDelegate(self.filesView)) self.filesView.model().layoutChanged.connect(self.search_update) box.layout().addWidget(self.filesView) layout = QHBoxLayout() gui.widgetBox(self.controlArea, margin=0, orientation=layout) self.updateButton = gui.button( box, self, "Update all", callback=self.update_all, tooltip="Update all updatable files", ) self.downloadButton = gui.button( box, self, "Download all", callback=self.download_filtered, tooltip="Download all filtered files shown") self.cancelButton = gui.button( box, self, "Cancel", callback=self.cancel_active_threads, tooltip="Cancel scheduled downloads/updates.") self.addButton = gui.button(box, self, "Add ...", callback=self.__handle_dialog, tooltip="Add files for personal use.") layout.addWidget(self.updateButton) layout.addWidget(self.downloadButton) layout.addWidget(self.cancelButton) layout.addStretch() layout.addWidget(self.addButton) # Enable retryButton once connection is established # self.retryButton = gui.button( # box, self, "Reconnect", callback=self.initialize_files_view # ) # self.retryButton.hide() self.resize(800, 600) self.update_items = [] self._dialog = None self.progress_bar = None # threads self.threadpool = QThreadPool(self) #self.threadpool.setMaxThreadCount(1) self.workers = list() self.initialize_files_view() def __handle_dialog(self): if not self._dialog: self._dialog = FileUploadHelper(self) self._dialog.show() def __progress_advance(self): # GUI should be updated in main thread. That's why we are calling advance method here if self.progress_bar: self.progress_bar.advance() def handle_worker_exception(self, ex): self.progress_bar.finish() self.setStatusMessage('') if isinstance(ex, ConnectionError): # TODO: set warning messages pass print(ex) def initialize_files_view(self): # self.retryButton.hide() # clear view self.filesView.clear() # init progress bar self.progress_bar = gui.ProgressBar(self, iterations=3) # status message self.setStatusMessage('initializing') worker = Worker(evaluate_files_state, progress_callback=True) worker.signals.progress.connect(self.__progress_advance) worker.signals.result.connect(self.set_files_list) worker.signals.error.connect(self.handle_worker_exception) # move download process to worker thread self.threadpool.start(worker) self.setEnabled(False) def __create_action_button(self, fs, retry=None): if not fs.state not in [OUTDATED, USER_FILE] or not retry: self.filesView.setItemWidget(fs.tree_item, header.Update, None) button = QToolButton(None) if not retry: if fs.state == OUTDATED: button.setText('Update') button.clicked.connect( partial(self.submit_download_task, fs.domain, fs.filename, True)) elif fs.state == USER_FILE: if not fs.info_server: button.setText('Remove') button.clicked.connect( partial(self.submit_remove_task, fs.domain, fs.filename)) else: button.setText('Use server version') button.clicked.connect( partial(self.submit_download_task, fs.domain, fs.filename, True)) else: button.setText('Retry') button.clicked.connect( partial(self.submit_download_task, fs.domain, fs.filename, True)) button.setMaximumWidth(120) button.setMaximumHeight(20) button.setMinimumHeight(20) if sys.platform == "darwin": button.setAttribute(Qt.WA_MacSmallSize) self.filesView.setItemWidget(fs.tree_item, header.Update, button) def set_files_list(self, result): """ Set the files to show. """ assert threading.current_thread() == threading.main_thread() self.progress_bar.finish() self.setStatusMessage('') self.setEnabled(True) self.update_items = result all_tags = set() for fs in self.update_items: fs.tree_item = FileStateItem(fs) fs.download_option = DownloadOption(state=fs.state) fs.download_option.download_clicked.connect( partial(self.submit_download_task, fs.domain, fs.filename)) fs.download_option.remove_clicked.connect( partial(self.submit_remove_task, fs.domain, fs.filename)) # add widget items to the QTreeWidget self.filesView.addTopLevelItems( [fs.tree_item for fs in self.update_items]) # add action widgets to tree items for fs in self.update_items: self.filesView.setItemWidget(fs.tree_item, header.Download, fs.download_option) if fs.state in [USER_FILE, OUTDATED]: self.__create_action_button(fs) all_tags.update(fs.tags) self.filesView.setColumnWidth( header.Download, self.filesView.sizeHintForColumn(header.Download)) for column in range(1, len(header_labels)): self.filesView.resizeColumnToContents(column) hints = [hint for hint in sorted(all_tags) if not hint.startswith("#")] self.completer.setTokenList(hints) self.search_update() self.toggle_action_buttons() self.cancelButton.setEnabled(False) def toggle_action_buttons(self): selected_items = [ fs for fs in self.update_items if not fs.tree_item.isHidden() ] def button_check(sel_items, state, button): for item in sel_items: if item.state != state: button.setEnabled(False) else: button.setEnabled(True) break button_check(selected_items, OUTDATED, self.updateButton) button_check(selected_items, AVAILABLE, self.downloadButton) def search_update(self, searchString=None): strings = str(self.lineEditFilter.text()).split() for fs in self.update_items: hide = not all(UpdateItem_match(fs, string) for string in strings) fs.tree_item.setHidden(hide) self.toggle_action_buttons() def update_all(self): for fs in self.update_items: if fs.state == OUTDATED and not fs.tree_item.isHidden(): self.submit_download_task(fs.domain, fs.filename) def download_filtered(self): for fs in self.update_items: if not fs.tree_item.isHidden() and fs.state in [ AVAILABLE, OUTDATED ]: self.submit_download_task(fs.domain, fs.filename, start=False) self.run_download_tasks() def submit_download_task(self, domain, filename, start=True): """ Submit the (domain, filename) to be downloaded/updated. """ # get selected tree item index = self.tree_item_index(domain, filename) fs = self.update_items[index] worker = Worker(download_server_file, fs, index, progress_callback=True) worker.signals.progress.connect(self.__progress_advance) worker.signals.result.connect(self.on_download_finished) worker.signals.error.connect(self.on_download_exception) self.workers.append(worker) if start: self.run_download_tasks() def run_download_tasks(self): self.cancelButton.setEnabled(True) # init progress bar self.progress_bar = gui.ProgressBar(self, iterations=len(self.workers) * 100) # status message self.setStatusMessage('downloading') # move workers to threadpool [self.threadpool.start(worker) for worker in self.workers] self.filesView.setDisabled(True) # reset list of workers self.workers = list() def on_download_exception(self, ex): assert threading.current_thread() == threading.main_thread() self.progress_bar.finish() self.setStatusMessage('') print(ex) if isinstance(ex, ValueError): fs, index = ex.args # restore state and retry fs.refresh_state() fs.tree_item.update_data(fs) fs.download_option.state = fs.state self.__create_action_button(fs, retry=True) def on_download_finished(self, result): assert threading.current_thread() == threading.main_thread() # We check if all workers have completed. If not, continue if self.progress_bar.count == 100 or self.threadpool.activeThreadCount( ) == 0: self.filesView.setDisabled(False) self.progress_bar.finish() self.setStatusMessage('') fs, index = result # re-evaluate File State info = serverfiles.info(fs.domain, fs.filename) fs.refresh_state(info_local=info, info_server=info) # reinitialize treeWidgetItem fs.tree_item.update_data(fs) # reinitialize OptionWidget fs.download_option.state = fs.state self.filesView.setItemWidget(fs.tree_item, header.Update, None) self.toggle_action_buttons() for column in range(1, len(header_labels)): self.filesView.resizeColumnToContents(column) def submit_remove_task(self, domain, filename): serverfiles.LOCALFILES.remove(domain, filename) index = self.tree_item_index(domain, filename) fs = self.update_items[index] if fs.state == USER_FILE: self.filesView.takeTopLevelItem( self.filesView.indexOfTopLevelItem(fs.tree_item)) self.update_items.remove(fs) # self.filesView.removeItemWidget(index) else: # refresh item state fs.info_local = None fs.refresh_state() # reinitialize treeWidgetItem fs.tree_item.update_data(fs) # reinitialize OptionWidget fs.download_option.state = fs.state self.toggle_action_buttons() def cancel_active_threads(self): """ Cancel all pending update/download tasks (that have not yet started). """ if self.threadpool: self.threadpool.clear() def tree_item_index(self, domain, filename): for i, fs in enumerate(self.update_items): if fs.domain == domain and fs.filename == filename: return i raise ValueError("%r, %r not in update list" % (domain, filename)) def onDeleteWidget(self): self.cancel_active_threads() OWWidget.onDeleteWidget(self)
class OWGenes(OWWidget): name = "Genes" description = "Tool for working with genes" icon = "../widgets/icons/OWGeneInfo.svg" priority = 5 want_main_area = True selected_organism: int = Setting(11) search_pattern: str = Setting('') exclude_unmatched = Setting(True) replace_id_with_symbol = Setting(True) auto_commit = Setting(True) settingsHandler = DomainContextHandler() selected_gene_col = ContextSetting(None) use_attr_names = ContextSetting(True) replaces = [ 'orangecontrib.bioinformatics.widgets.OWGeneNameMatcher.OWGeneNameMatcher' ] class Inputs: data_table = Input("Data", Table) class Outputs: data_table = Output("Data", Table) gene_matcher_results = Output("Genes", Table) class Information(OWWidget.Information): pass def sizeHint(self): return QSize(1280, 960) def __init__(self): super().__init__() # ATTRIBUTES # self.target_database = NCBI_ID # input data self.input_data = None self.input_genes = None self.tax_id = None self.column_candidates = [] # input options self.organisms = [] # gene matcher self.gene_matcher = None # threads self.threadpool = QThreadPool(self) self.workers = None # progress bar self.progress_bar = None self._timer = QTimer() self._timer.timeout.connect(self._apply_filter) self._timer.setSingleShot(True) # GUI SECTION # # Control area self.info_box = widgetLabel( widgetBox(self.controlArea, "Info", addSpace=True), 'No data on input.\n') organism_box = vBox(self.controlArea, 'Organism') self.organism_select_combobox = comboBox( organism_box, self, 'selected_organism', callback=self.on_input_option_change) self.get_available_organisms() self.organism_select_combobox.setCurrentIndex(self.selected_organism) box = widgetBox(self.controlArea, 'Gene IDs in the input data') self.gene_columns_model = itemmodels.DomainModel( valid_types=(StringVariable, DiscreteVariable)) self.gene_column_combobox = comboBox( box, self, 'selected_gene_col', label='Stored in data column', model=self.gene_columns_model, sendSelectedValue=True, callback=self.on_input_option_change) self.attr_names_checkbox = checkBox( box, self, 'use_attr_names', 'Stored as feature (column) names', disables=[(-1, self.gene_column_combobox)], callback=self.on_input_option_change) self.gene_column_combobox.setDisabled(bool(self.use_attr_names)) output_box = vBox(self.controlArea, 'Output') # separator(output_box) # output_box.layout().addWidget(horizontal_line()) # separator(output_box) self.exclude_radio = checkBox(output_box, self, 'exclude_unmatched', 'Exclude unmatched genes', callback=self.commit) self.replace_radio = checkBox(output_box, self, 'replace_id_with_symbol', 'Replace feature IDs with gene names', callback=self.commit) auto_commit(self.controlArea, self, "auto_commit", "&Commit", box=False) rubber(self.controlArea) # Main area self.filter = lineEdit(self.mainArea, self, 'search_pattern', 'Filter:', callbackOnType=True, callback=self.handle_filter_callback) # rubber(self.radio_group) self.mainArea.layout().addWidget(self.filter) # set splitter self.splitter = QSplitter() self.splitter.setOrientation(Qt.Vertical) self.table_model = GeneInfoModel() self.table_view = QTableView() self.table_view.setAlternatingRowColors(True) self.table_view.viewport().setMouseTracking(True) self.table_view.setSortingEnabled(True) self.table_view.setShowGrid(False) self.table_view.verticalHeader().hide() # self.table_view.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) self.unknown_model = UnknownGeneInfoModel() self.unknown_view = QTableView() self.unknown_view.setModel(self.unknown_model) self.unknown_view.verticalHeader().hide() self.unknown_view.setShowGrid(False) self.unknown_view.setSelectionMode(QAbstractItemView.NoSelection) self.unknown_view.horizontalHeader().setSectionResizeMode( QHeaderView.Stretch) self.splitter.addWidget(self.table_view) self.splitter.addWidget(self.unknown_view) self.splitter.setStretchFactor(0, 90) self.splitter.setStretchFactor(1, 10) self.mainArea.layout().addWidget(self.splitter) def handle_filter_callback(self): self._timer.stop() self._timer.start(500) def _apply_filter(self): # filter only if input data is present and model is populated if self.table_model.table is not None: self.table_model.update_model( filter_pattern=str(self.search_pattern)) self.commit() def __reset_widget_state(self): self.table_view.clearSpans() self.table_view.setModel(None) self.table_model.clear() self.unknown_model.clear() self._update_info_box() def _update_info_box(self): if self.input_genes and self.gene_matcher: num_genes = len(self.gene_matcher.genes) known_genes = len(self.gene_matcher.get_known_genes()) info_text = '{} genes in input data\n' \ '{} genes match Entrez database\n' \ '{} genes with match conflicts\n'.format(num_genes, known_genes, num_genes - known_genes) else: info_text = 'No data on input.' self.info_box.setText(info_text) def _progress_advance(self): # GUI should be updated in main thread. That's why we are calling advance method here if self.progress_bar: self.progress_bar.advance() def _handle_matcher_results(self): assert threading.current_thread() == threading.main_thread() if self.progress_bar: self.progress_bar.finish() self.setStatusMessage('') # update info box self._update_info_box() # set output options self.toggle_radio_options() # set known genes self.table_model.initialize(self.gene_matcher.genes) self.table_view.setModel(self.table_model) self.table_view.selectionModel().selectionChanged.connect(self.commit) self.table_view.setSelectionBehavior(QAbstractItemView.SelectRows) self.table_view.setItemDelegateForColumn( self.table_model.entrez_column_index, LinkStyledItemDelegate(self.table_view)) v_header = self.table_view.verticalHeader() option = self.table_view.viewOptions() size = self.table_view.style().sizeFromContents( QStyle.CT_ItemViewItem, option, QSize(20, 20), self.table_view) v_header.setDefaultSectionSize(size.height() + 2) v_header.setMinimumSectionSize(5) self.table_view.horizontalHeader().setStretchLastSection(True) # set unknown genes self.unknown_model.initialize(self.gene_matcher.genes) self.unknown_view.verticalHeader().setStretchLastSection(True) self._apply_filter() def get_available_organisms(self): available_organism = sorted([(tax_id, taxonomy.name(tax_id)) for tax_id in taxonomy.common_taxids()], key=lambda x: x[1]) self.organisms = [tax_id[0] for tax_id in available_organism] self.organism_select_combobox.addItems( [tax_id[1] for tax_id in available_organism]) def gene_names_from_table(self): """ Extract and return gene names from `Orange.data.Table`. """ self.input_genes = [] if self.input_data: if self.use_attr_names: self.input_genes = [ str(attr.name).strip() for attr in self.input_data.domain.attributes ] else: if self.selected_gene_col is None: self.selected_gene_col = self.gene_column_identifier() self.input_genes = [ str(e[self.selected_gene_col]) for e in self.input_data if not np.isnan(e[self.selected_gene_col]) ] def _update_gene_matcher(self): self.gene_names_from_table() self.gene_matcher = GeneMatcher(self.get_selected_organism(), case_insensitive=True) self.gene_matcher.genes = self.input_genes self.gene_matcher.organism = self.get_selected_organism() def get_selected_organism(self): return self.organisms[self.selected_organism] def match_genes(self): if self.gene_matcher: # init progress bar self.progress_bar = ProgressBar(self, iterations=len( self.gene_matcher.genes)) # status message self.setStatusMessage('Gene matcher running') worker = Worker(self.gene_matcher.run_matcher, progress_callback=True) worker.signals.progress.connect(self._progress_advance) worker.signals.finished.connect(self._handle_matcher_results) # move download process to worker thread self.threadpool.start(worker) def on_input_option_change(self): self.__reset_widget_state() self._update_gene_matcher() self.match_genes() def gene_column_identifier(self): """ Get most suitable column that stores genes. If there are several suitable columns, select the one with most unique values. Take the best one. """ # candidates -> (variable, num of unique values) candidates = ((col, np.unique(self.input_data.get_column_view(col)[0]).size) for col in self.gene_columns_model if isinstance(col, DiscreteVariable) or isinstance(col, StringVariable)) best_candidate, _ = sorted(candidates, key=lambda x: x[1])[-1] return best_candidate def find_genes_location(self): """ Try locate the genes in the input data when we first load the data. Proposed rules: - when no suitable feature names are present, check the columns. - find the most suitable column, that is, the one with most unique values. """ domain = self.input_data.domain if not domain.attributes: if self.selected_gene_col is None: self.selected_gene_col = self.gene_column_identifier() self.use_attr_names = False @Inputs.data_table def handle_input(self, data): self.closeContext() self.input_data = None self.input_genes = None self.__reset_widget_state() self.gene_columns_model.set_domain(None) self.selected_gene_col = None if data: self.input_data = data self.gene_columns_model.set_domain(self.input_data.domain) # check if input table has tax_id, human is used if tax_id is not found self.tax_id = str(self.input_data.attributes.get(TAX_ID, '9606')) # check for gene location. Default is that genes are attributes in the input table. self.use_attr_names = self.input_data.attributes.get( GENE_AS_ATTRIBUTE_NAME, self.use_attr_names) if self.tax_id in self.organisms and not self.selected_organism: self.selected_organism = self.organisms.index(self.tax_id) self.openContext(self.input_data.domain) self.find_genes_location() self.on_input_option_change() def commit(self): selection = self.table_view.selectionModel().selectedRows( self.table_model.entrez_column_index) selected_genes = [row.data() for row in selection] if not len(selected_genes): selected_genes = self.table_model.get_filtered_genes() gene_ids = self.get_target_ids() known_genes = [gid for gid in gene_ids if gid != '?'] table = None gm_table = None if known_genes: # Genes are in rows (we have a column with genes). if not self.use_attr_names: if self.target_database in self.input_data.domain: gene_var = self.input_data.domain[self.target_database] metas = self.input_data.domain.metas else: gene_var = StringVariable(self.target_database) metas = self.input_data.domain.metas + (gene_var, ) domain = Domain(self.input_data.domain.attributes, self.input_data.domain.class_vars, metas) table = self.input_data.transform(domain) col, _ = table.get_column_view(gene_var) col[:] = gene_ids # filter selected rows selected_rows = [ row_index for row_index, row in enumerate(table) if str(row[gene_var]) in selected_genes ] # handle table attributes table.attributes[TAX_ID] = self.get_selected_organism() table.attributes[GENE_AS_ATTRIBUTE_NAME] = False table.attributes[GENE_ID_COLUMN] = self.target_database table = table[selected_rows] if selected_rows else table if self.exclude_unmatched: # create filter from selected column for genes only_known = table_filter.FilterStringList( gene_var, known_genes) # apply filter to the data table = table_filter.Values([only_known])(table) self.Outputs.data_table.send(table) # genes are are in columns (genes are features). else: domain = self.input_data.domain.copy() table = self.input_data.transform(domain) for gene in self.gene_matcher.genes: if gene.input_name in table.domain: table.domain[gene.input_name].attributes[self.target_database] = \ str(gene.ncbi_id) if gene.ncbi_id else '?' if self.replace_id_with_symbol: try: table.domain[gene.input_name].name = str( gene.symbol) except AttributeError: # TODO: missing gene symbol, need to handle this? pass # filter selected columns selected = [ column for column in table.domain.attributes if self.target_database in column.attributes and str(column.attributes[ self.target_database]) in selected_genes ] output_attrs = table.domain.attributes if selected: output_attrs = selected if self.exclude_unmatched: output_attrs = [ col for col in output_attrs if col.attributes[self.target_database] in known_genes ] domain = Domain(output_attrs, table.domain.class_vars, table.domain.metas) table = table.from_table(domain, table) # handle table attributes table.attributes[TAX_ID] = self.get_selected_organism() table.attributes[GENE_AS_ATTRIBUTE_NAME] = True table.attributes[GENE_ID_ATTRIBUTE] = self.target_database gm_table = self.gene_matcher.to_data_table( selected_genes=selected_genes if selected_genes else None) self.Outputs.data_table.send(table) self.Outputs.gene_matcher_results.send(gm_table) def toggle_radio_options(self): self.replace_radio.setEnabled(bool(self.use_attr_names)) if self.gene_matcher.genes: # enable checkbox if unknown genes are detected self.exclude_radio.setEnabled( len(self.gene_matcher.genes) != len( self.gene_matcher.get_known_genes())) self.exclude_unmatched = len(self.gene_matcher.genes) != len( self.gene_matcher.get_known_genes()) def get_target_ids(self): return [ str(gene.ncbi_id) if gene.ncbi_id else '?' for gene in self.gene_matcher.genes ]
class OWGeneSets(OWWidget): name = "Gene Set Enrichment" description = "" icon = "icons/OWGeneSets.svg" priority = 9 want_main_area = True settingsHandler = OrganismContextHandler() # settings auto_commit = Setting(True) stored_selections = ContextSetting([]) organism = ContextSetting(None) min_count = Setting(5) use_min_count = Setting(True) max_p_value = Setting(0.0001) use_p_value = Setting(False) max_fdr = Setting(0.01) use_max_fdr = Setting(True) use_reference_data = Setting(True) COUNT, REFERENCE, P_VAL, FDR, ENRICHMENT, GENES, CATEGORY, TERM = range(8) DATA_HEADER_LABELS = [ "Count", 'Reference', 'p-Value', 'FDR', 'Enrichment', 'Genes In Set', 'Category', 'Term' ] class Inputs: genes = Input("Genes", Table) custom_sets = Input('Custom Gene Sets', Table) reference = Input("Reference Genes", Table) class Outputs: matched_genes = Output("Matched Genes", Table) class Information(OWWidget.Information): pass class Warning(OWWidget.Warning): all_sets_filtered = Msg('All sets were filtered out.') class Error(OWWidget.Error): organism_mismatch = Msg( 'Organism in input data and custom gene sets does not match') missing_annotation = Msg(ERROR_ON_MISSING_ANNOTATION) missing_gene_id = Msg(ERROR_ON_MISSING_GENE_ID) missing_tax_id = Msg(ERROR_ON_MISSING_TAX_ID) cant_reach_host = Msg("Host orange.biolab.si is unreachable.") cant_load_organisms = Msg( "No available organisms, please check your connection.") def __init__(self): super().__init__() # commit self.commit_button = None # gene sets object self.gene_sets_obj = geneset.GeneSets() # progress bar self.progress_bar = None self.progress_bar_iterations = None # data self.input_data = None self.input_genes = [] self.tax_id = None self.use_attr_names = None self.gene_id_attribute = None self.gene_id_column = None # custom gene sets self.custom_data = None self.feature_model = DomainModel(valid_types=(DiscreteVariable, StringVariable)) self.gene_set_label = None self.gs_label_combobox = None self.custom_tax_id = None self.custom_use_attr_names = None self.custom_gene_id_attribute = None self.custom_gene_id_column = None # reference genes self.reference_radio_box = None self.reference_data = None self.reference_genes = None self.reference_tax_id = None self.reference_attr_names = None self.reference_gene_id_attribute = None self.reference_gene_id_column = None # info box self.input_info = None self.num_of_sel_genes = 0 # filter self.line_edit_filter = None self.search_pattern = '' self.organism_select_combobox = None # data model view self.data_view = None self.data_model = None # gene matcher NCBI self.gene_matcher = None # filter proxy model self.filter_proxy_model = None # hierarchy widget self.hierarchy_widget = None self.hierarchy_state = None # spinbox self.spin_widget = None # threads self.threadpool = QThreadPool(self) self.workers = None self._task = None # type: Optional[Task] self._executor = ThreadExecutor() # gui self.setup_gui() def __reset_widget_state(self): # reset hierarchy widget state self.hierarchy_widget.clear() # clear data view self.init_item_model() # reset filters self.setup_filter_model() def cancel(self): """ Cancel the current task (if any). """ if self._task is not None: self._task.cancel() assert self._task.future.done() # disconnect the `_task_finished` slot self._task.watcher.done.disconnect(self._init_gene_sets_finished) self._task = None @Slot() def progress_advance(self): # GUI should be updated in main thread. That's why we are calling advance method here if self.progress_bar: self.progress_bar.advance() def __get_input_genes(self): self.input_genes = [] if self.use_attr_names: for variable in self.input_data.domain.attributes: self.input_genes.append( str(variable.attributes.get(self.gene_id_attribute, '?'))) else: genes, _ = self.input_data.get_column_view(self.gene_id_column) self.input_genes = [str(g) for g in genes] def __construct_custom_gene_sets(self): custom_set_hier = ('Custom sets', ) # delete any custom sets if they exists self.gene_sets_obj.delete_sets_by_hierarchy(custom_set_hier) if self.custom_data and self.custom_gene_id_column: gene_sets_names, _ = self.custom_data.get_column_view( self.gene_set_label) gene_names, _ = self.custom_data.get_column_view( self.custom_gene_id_column) temp_dict = defaultdict(list) for set_name, gene_name in zip(gene_sets_names, gene_names): temp_dict[set_name].append(gene_name) g_sets = [] for key, value in temp_dict.items(): g_sets.append( geneset.GeneSet(gs_id=key, hierarchy=custom_set_hier, organism=self.custom_tax_id, name=key, genes=set(value))) self.gene_sets_obj.update(g_sets) def __update_hierarchy(self): self.set_hierarchy_model( self.hierarchy_widget, hierarchy_tree(self.gene_sets_obj.hierarchies())) self.set_selected_hierarchies() def update_tree_view(self): if self.use_reference_data and self.reference_data: self.init_gene_sets(reference_genes=self.reference_genes) else: self.init_gene_sets() def invalidate(self): # clear self.__reset_widget_state() self.update_info_box() if self.input_data is not None: # setup self.__construct_custom_gene_sets() self.__get_input_genes() self.__update_hierarchy() self.update_tree_view() def __check_organism_mismatch(self): """ Check if organisms from different inputs match. :return: True if there is a mismatch """ if self.tax_id is not None and self.custom_tax_id is not None: return self.tax_id != self.custom_tax_id return False def __get_reference_genes(self): self.reference_genes = [] if self.reference_attr_names: for variable in self.reference_data.domain.attributes: self.reference_genes.append( str( variable.attributes.get( self.reference_gene_id_attribute, '?'))) else: genes, _ = self.reference_data.get_column_view( self.reference_gene_id_column) self.reference_genes = [str(g) for g in genes] @Inputs.reference def handle_reference_genes(self, data): """ Set the (optional) input dataset with reference gene names. """ if data: self.reference_data = data self.reference_tax_id = str( self.reference_data.attributes.get(TAX_ID, None)) self.reference_attr_names = self.reference_data.attributes.get( GENE_AS_ATTRIBUTE_NAME, None) self.reference_gene_id_attribute = self.reference_data.attributes.get( GENE_ID_ATTRIBUTE, None) self.reference_gene_id_column = self.reference_data.attributes.get( GENE_ID_COLUMN, None) if not (self.reference_attr_names is not None and ((self.reference_gene_id_attribute is None) ^ (self.reference_gene_id_column is None))): if self.reference_tax_id is None: self.Error.missing_annotation() return self.Error.missing_gene_id() return elif self.reference_tax_id is None: self.Error.missing_tax_id() return self.__get_reference_genes() self.reference_radio_box.setEnabled(bool(self.reference_data)) self.invalidate() @Inputs.custom_sets def handle_custom_input(self, data): self.Error.clear() self.__reset_widget_state() self.custom_data = None self.custom_tax_id = None self.custom_use_attr_names = None self.custom_gene_id_attribute = None self.custom_gene_id_column = None self.gs_label_combobox.setDisabled(True) self.feature_model.set_domain(None) if data: self.custom_data = data self.custom_tax_id = str( self.custom_data.attributes.get(TAX_ID, None)) self.custom_use_attr_names = self.custom_data.attributes.get( GENE_AS_ATTRIBUTE_NAME, None) self.custom_gene_id_attribute = self.custom_data.attributes.get( GENE_ID_ATTRIBUTE, None) self.custom_gene_id_column = self.custom_data.attributes.get( GENE_ID_COLUMN, None) if not (self.custom_use_attr_names is not None and ((self.custom_gene_id_attribute is None) ^ (self.custom_gene_id_column is None))): if self.custom_tax_id is None: self.Error.missing_annotation() return self.Error.missing_gene_id() return elif self.custom_tax_id is None: self.Error.missing_tax_id() return if self.__check_organism_mismatch(): self.Error.organism_mismatch() return self.gs_label_combobox.setDisabled(False) self.feature_model.set_domain(self.custom_data.domain) if self.feature_model: self.gene_set_label = self.feature_model[0] self.invalidate() @Inputs.genes def handle_genes_input(self, data): self.closeContext() self.Error.clear() self.__reset_widget_state() # clear output self.Outputs.matched_genes.send(None) # clear input genes self.input_genes = [] self.gs_label_combobox.setDisabled(True) self.update_info_box() if data: self.input_data = data self.tax_id = str(self.input_data.attributes.get(TAX_ID, None)) self.use_attr_names = self.input_data.attributes.get( GENE_AS_ATTRIBUTE_NAME, None) self.gene_id_attribute = self.input_data.attributes.get( GENE_ID_ATTRIBUTE, None) self.gene_id_column = self.input_data.attributes.get( GENE_ID_COLUMN, None) if not (self.use_attr_names is not None and ((self.gene_id_attribute is None) ^ (self.gene_id_column is None))): if self.tax_id is None: self.Error.missing_annotation() return self.Error.missing_gene_id() return elif self.tax_id is None: self.Error.missing_tax_id() return if self.__check_organism_mismatch(): self.Error.organism_mismatch() return self.openContext(self.tax_id) # if input data change, we need to set feature model again if self.custom_data: self.gs_label_combobox.setDisabled(False) self.feature_model.set_domain(self.custom_data.domain) if self.feature_model: self.gene_set_label = self.feature_model[0] self.download_gene_sets() def update_info_box(self): info_string = '' if self.input_genes: info_string += '{} unique gene names on input.\n'.format( len(self.input_genes)) info_string += '{} genes on output.\n'.format( self.num_of_sel_genes) else: info_string += 'No genes on input.\n' self.input_info.setText(info_string) def on_gene_sets_download(self, result): # make sure this happens in the main thread. # Qt insists that widgets be created within the GUI(main) thread. assert threading.current_thread() == threading.main_thread() self.setStatusMessage('') if result: for res in result: g_sets = geneset.load_gene_sets(res, self.tax_id) self.gene_sets_obj.update([g_set for g_set in g_sets]) # add custom sets if there are any self.invalidate() self.update_info_box() def download_gene_sets(self): self.Error.clear() # reset hierarchy widget state self.hierarchy_widget.clear() # clear data view self.init_item_model() # get all gene sets for selected organism gene_sets = geneset.list_all(organism=self.tax_id) # status message self.setStatusMessage('downloading sets') worker = Worker(download_gene_sets, self.tax_id, gene_sets) worker.signals.result.connect(self.on_gene_sets_download) # move download process to worker thread self.threadpool.start(worker) def set_hierarchy_model(self, tree_widget, sets): def beautify_displayed_text(text): if '_' in text: return text.replace('_', ' ').title() else: return text # TODO: maybe optimize this code? for key, value in sets.items(): item = QTreeWidgetItem(tree_widget, [beautify_displayed_text(key)]) item.setFlags(item.flags() & (Qt.ItemIsUserCheckable | ~Qt.ItemIsSelectable | Qt.ItemIsEnabled)) item.setExpanded(True) item.hierarchy = key if value: item.setFlags(item.flags() | Qt.ItemIsTristate) self.set_hierarchy_model(item, value) else: if item.parent(): item.hierarchy = (item.parent().hierarchy, key) if not item.childCount() and not item.parent(): item.hierarchy = (key, ) def init_gene_sets(self, reference_genes=None): if self._task is not None: self.cancel() assert self._task is None self._task = Task() progress_advance = methodinvoke(self, "progress_advance") def callback(): if self._task.cancelled: raise KeyboardInterrupt() if self.progress_bar: progress_advance() if reference_genes is None: reference_genes = self.gene_sets_obj.genes() self.init_item_model() sets_to_display = self.get_hierarchies(only_selected=True) # save setting on selected hierarchies self.stored_selections = sets_to_display # save context self.closeContext() f = partial(self.set_items, self.gene_sets_obj, sets_to_display, set(self.input_genes), reference_genes, self.min_count if self.use_min_count else 1, callback=callback) progress_iterations = sum([ len(g_set) for hier, g_set in self.gene_sets_obj.map_hierarchy_to_sets().items() if hier in sets_to_display ]) self.progress_bar = ProgressBar(self, iterations=progress_iterations) self._task.future = self._executor.submit(f) self._task.watcher = FutureWatcher(self._task.future) self._task.watcher.done.connect(self._init_gene_sets_finished) self.openContext(self.tax_id) @Slot(concurrent.futures.Future) def _init_gene_sets_finished(self, f): assert self.thread() is QThread.currentThread() assert threading.current_thread() == threading.main_thread() assert self._task is not None assert self._task.future is f assert f.done() self._task = None self.progress_bar.finish() self.setStatusMessage('') try: results = f.result() # type: list [self.data_model.appendRow(model_item) for model_item in results] self.filter_proxy_model.setSourceModel(self.data_model) self._update_fdr() self.filter_data_view() except Exception as ex: print(ex) def set_selected_hierarchies(self): iterator = QTreeWidgetItemIterator(self.hierarchy_widget, QTreeWidgetItemIterator.All) while iterator.value(): # note: if hierarchy value is not a tuple, then this is just top level qTreeWidgetItem that # holds subcategories. We don't want to display all sets from category if type(iterator.value().hierarchy) is not str: if iterator.value().hierarchy in self.stored_selections: iterator.value().setCheckState(0, Qt.Checked) else: iterator.value().setCheckState(0, Qt.Unchecked) iterator += 1 # if no items are checked, we check first one at random if len(self.get_hierarchies(only_selected=True)) == 0: iterator = QTreeWidgetItemIterator( self.hierarchy_widget, QTreeWidgetItemIterator.NotChecked) while iterator.value(): if type(iterator.value().hierarchy) is not str: iterator.value().setCheckState(0, Qt.Checked) return iterator += 1 def get_hierarchies(self, **kwargs): """ return selected hierarchy """ only_selected = kwargs.get('only_selected', None) sets_to_display = list() if only_selected: iterator = QTreeWidgetItemIterator(self.hierarchy_widget, QTreeWidgetItemIterator.Checked) else: iterator = QTreeWidgetItemIterator(self.hierarchy_widget) while iterator.value(): # note: if hierarchy value is not a tuple, then this is just top level qTreeWidgetItem that # holds subcategories. We don't want to display all sets from category if type(iterator.value().hierarchy) is not str: if not only_selected: sets_to_display.append(iterator.value().hierarchy) else: if not iterator.value().isDisabled(): sets_to_display.append(iterator.value().hierarchy) iterator += 1 return sets_to_display def filter_data_view(self): filter_proxy = self.filter_proxy_model # type: FilterProxyModel model = filter_proxy.sourceModel() # type: QStandardItemModel assert isinstance(model, QStandardItemModel) search_term = self.search_pattern.lower().strip().split() # apply filtering rules filters = [ FilterProxyModel.Filter( self.TERM, Qt.DisplayRole, lambda value: all(fs in value.lower() for fs in search_term)) ] # if self.use_min_count: # filters.append( # FilterProxyModel.Filter( # self.COUNT, Qt.DisplayRole, # lambda value: value >= self.min_count, # ) # ) if self.use_p_value: filters.append( FilterProxyModel.Filter( self.P_VAL, Qt.DisplayRole, lambda value: value < self.max_p_value)) if self.use_max_fdr: filters.append( FilterProxyModel.Filter(self.FDR, Qt.DisplayRole, lambda value: value < self.max_fdr)) filter_proxy.set_filters(filters) if model.rowCount() and not filter_proxy.rowCount(): self.Warning.all_sets_filtered() else: self.Warning.clear() def __get_source_data(self, proxy_row_index, column): proxy_index = self.filter_proxy_model.index(proxy_row_index, column) source_index = self.filter_proxy_model.mapToSource(proxy_index) return source_index.data(role=Qt.DisplayRole) def _update_fdr(self): # Update the FDR in place due to a changed selected categories set and # results for all of these categories are already available. proxy = self.filter_proxy_model model = self.filter_proxy_model.sourceModel() if model is not None: assert isinstance(model, QStandardItemModel) p_values = [(i, self.__get_source_data(i, self.P_VAL)) for i in range(proxy.rowCount())] fdr_values = FDR([p_val for _, p_val in p_values]) for i, fdr_val in zip([i for i, _ in p_values], fdr_values): proxy_index = proxy.index(i, self.FDR) source_index = self.filter_proxy_model.mapToSource(proxy_index) source_item = model.item(source_index.row(), self.FDR) source_item.setData(fdr_val, role=Qt.DisplayRole) source_item.setData(fdr_val, role=Qt.ToolTipRole) def commit(self): selection_model = self.data_view.selectionModel() if selection_model: # genes_from_set = selection_model.selectedRows(GENES) matched_genes = selection_model.selectedRows(self.COUNT) if matched_genes and self.input_genes: genes = [ model_index.data(Qt.UserRole) for model_index in matched_genes ] output_genes = [ gene_name for gene_name in list(set.union(*genes)) ] self.num_of_sel_genes = len(output_genes) self.update_info_box() if self.use_attr_names: selected = [ column for column in self.input_data.domain.attributes if self.gene_id_attribute in column.attributes and str(column.attributes[ self.gene_id_attribute]) in output_genes ] domain = Domain(selected, self.input_data.domain.class_vars, self.input_data.domain.metas) new_data = self.input_data.from_table( domain, self.input_data) self.Outputs.matched_genes.send(new_data) else: selected_rows = [] for row_index, row in enumerate(self.input_data): gene_in_row = str(row[self.gene_id_column]) if gene_in_row in self.input_genes and gene_in_row in output_genes: selected_rows.append(row_index) if selected_rows: selected = self.input_data[selected_rows] else: selected = None self.Outputs.matched_genes.send(selected) def assign_delegates(self): self.data_view.setItemDelegateForColumn(self.GENES, NumericalColumnDelegate(self)) self.data_view.setItemDelegateForColumn(self.COUNT, NumericalColumnDelegate(self)) self.data_view.setItemDelegateForColumn(self.REFERENCE, NumericalColumnDelegate(self)) self.data_view.setItemDelegateForColumn( self.P_VAL, NumericalColumnDelegate(self, precision=2, notation='e')) self.data_view.setItemDelegateForColumn( self.FDR, NumericalColumnDelegate(self, precision=2, notation='e')) self.data_view.setItemDelegateForColumn( self.ENRICHMENT, NumericalColumnDelegate(self, precision=1)) def setup_filter_model(self): self.filter_proxy_model = FilterProxyModel() self.filter_proxy_model.setFilterKeyColumn(self.TERM) self.data_view.setModel(self.filter_proxy_model) def setup_filter_area(self): h_layout = QHBoxLayout() h_layout.setSpacing(100) h_widget = widgetBox(self.mainArea, orientation=h_layout) spin(h_widget, self, 'min_count', 0, 100, label='Count', tooltip='Minimum genes count', checked='use_min_count', callback=self.invalidate, callbackOnReturn=True, checkCallback=self.invalidate) doubleSpin(h_widget, self, 'max_p_value', 0.0, 1.0, 0.0001, label='p-value', tooltip='Maximum p-value of the enrichment score', checked='use_p_value', callback=self.filter_data_view, callbackOnReturn=True, checkCallback=self.filter_data_view) doubleSpin(h_widget, self, 'max_fdr', 0.0, 1.0, 0.0001, label='FDR', tooltip='Maximum false discovery rate', checked='use_max_fdr', callback=self.filter_data_view, callbackOnReturn=True, checkCallback=self.filter_data_view) self.line_edit_filter = lineEdit(h_widget, self, 'search_pattern') self.line_edit_filter.setPlaceholderText('Filter gene sets ...') self.line_edit_filter.textChanged.connect(self.filter_data_view) def setup_control_area(self): info_box = vBox(self.controlArea, 'Info') self.input_info = widgetLabel(info_box) box = vBox(self.controlArea, "Custom Gene Sets") self.gs_label_combobox = comboBox(box, self, "gene_set_label", sendSelectedValue=True, model=self.feature_model, callback=self.invalidate) self.gs_label_combobox.setDisabled(True) self.reference_radio_box = radioButtonsInBox( self.controlArea, self, "use_reference_data", ["Entire genome", "Reference gene set (input)"], tooltips=[ "Use entire genome (for gene set enrichment)", "Use reference set of genes" ], box="Reference", callback=self.invalidate) self.reference_radio_box.setEnabled(False) hierarchy_box = widgetBox(self.controlArea, "Gene Set Categories") self.hierarchy_widget = QTreeWidget(self) self.hierarchy_widget.setEditTriggers(QTreeView.NoEditTriggers) self.hierarchy_widget.setHeaderLabels([' ']) self.hierarchy_widget.itemClicked.connect(self.update_tree_view) hierarchy_box.layout().addWidget(self.hierarchy_widget) self.commit_button = auto_commit(self.controlArea, self, "auto_commit", "&Commit", box=False) def setup_gui(self): # control area self.setup_control_area() # main area self.data_view = QTreeView() self.setup_filter_model() self.setup_filter_area() self.data_view.setAlternatingRowColors(True) self.data_view.sortByColumn(self.COUNT, Qt.DescendingOrder) self.data_view.setSortingEnabled(True) self.data_view.setSelectionMode(QTreeView.ExtendedSelection) self.data_view.setEditTriggers(QTreeView.NoEditTriggers) self.data_view.viewport().setMouseTracking(False) self.data_view.setItemDelegateForColumn( self.TERM, LinkStyledItemDelegate(self.data_view)) self.data_view.selectionModel().selectionChanged.connect(self.commit) self.mainArea.layout().addWidget(self.data_view) self.data_view.header().setSectionResizeMode( QHeaderView.ResizeToContents) self.assign_delegates() @staticmethod def set_items(gene_sets, sets_to_display, genes, ref, count_treshold, callback): model_items = [] if not genes: return for gene_set in gene_sets: if gene_set.hierarchy not in sets_to_display: continue enrichemnt_result = gene_set.set_enrichment( ref, genes.intersection(ref)) callback() if len(enrichemnt_result.query) >= count_treshold: category_column = QStandardItem() name_column = QStandardItem() count_column = QStandardItem() genes_column = QStandardItem() ref_column = QStandardItem() pval_column = QStandardItem() fdr_column = QStandardItem() enrichemnt_column = QStandardItem() category_column.setData(", ".join(gene_set.hierarchy), Qt.DisplayRole) name_column.setData(gene_set.name, Qt.DisplayRole) name_column.setData(gene_set.name, Qt.ToolTipRole) name_column.setData(gene_set.link, LinkRole) name_column.setForeground(QColor(Qt.blue)) count_column.setData(len(enrichemnt_result.query), Qt.DisplayRole) count_column.setData(set(enrichemnt_result.query), Qt.UserRole) genes_column.setData(len(gene_set.genes), Qt.DisplayRole) genes_column.setData( set(gene_set.genes), Qt.UserRole ) # store genes to get then on output on selection ref_column.setData(len(enrichemnt_result.reference), Qt.DisplayRole) pval_column.setData(enrichemnt_result.p_value, Qt.DisplayRole) pval_column.setData(enrichemnt_result.p_value, Qt.ToolTipRole) enrichemnt_column.setData(enrichemnt_result.enrichment_score, Qt.DisplayRole) enrichemnt_column.setData(enrichemnt_result.enrichment_score, Qt.ToolTipRole) model_items.append([ count_column, ref_column, pval_column, fdr_column, enrichemnt_column, genes_column, category_column, name_column ]) return model_items def init_item_model(self): if self.data_model: self.data_model.clear() self.setup_filter_model() else: self.data_model = QStandardItemModel() self.data_model.setSortRole(Qt.UserRole) self.data_model.setHorizontalHeaderLabels(self.DATA_HEADER_LABELS) def sizeHint(self): return QSize(1280, 960)
class OWGeneNameMatcher(OWWidget): name = "Gene Name Matcher" description = "Tool for working with genes" icon = "../widgets/icons/OWGeneInfo.svg" priority = 5 want_main_area = True use_attr_names = Setting(True) selected_organism = Setting(11) selected_filter = Setting(0) gene_as_attr_name = Setting(0) filter_unknown = Setting(True) include_entrez_id = Setting(True) # include_ensembl_id = Setting(True) auto_commit = Setting(True) class Inputs: data_table = Input("Data", Table) class Outputs: custom_data_table = Output("Data", Table) class Information(OWWidget.Information): pass def sizeHint(self): return QSize(1280, 960) def __init__(self): super().__init__() # ATTRIBUTES # # input data self.input_data = None self.input_genes = None self.tax_id = None self.column_candidates = [] self.selected_gene_col = None # input options self.organisms = [] # gene matcher self.gene_matcher = None # threads self.threadpool = QThreadPool(self) self.workers = None # progress bar self.progress_bar = None # filter self.filter_labels = ['Unique', 'Partial', 'Unknown'] # GUI SECTION # # Control area self.info_box = widgetLabel( widgetBox(self.controlArea, "Info", addSpace=True), "Initializing\n") organism_box = vBox(self.controlArea, 'Organism') self.organism_select_combobox = comboBox( organism_box, self, 'selected_organism', callback=self.on_input_option_change) self.get_available_organisms() self.organism_select_combobox.setCurrentIndex(self.selected_organism) box = widgetBox(self.controlArea, 'Gene names') self.gene_columns_model = itemmodels.DomainModel( valid_types=(StringVariable, )) self.gene_column_combobox = comboBox( box, self, 'selected_gene_col', model=self.gene_columns_model, sendSelectedValue=True, callback=self.on_input_option_change) self.attr_names_checkbox = checkBox( box, self, 'use_attr_names', 'Use attribute names', disables=[(-1, self.gene_column_combobox)], callback=self.on_input_option_change) self.gene_column_combobox.setDisabled(bool(self.use_attr_names)) output_box = vBox(self.controlArea, 'Output settings') # TODO: will widget support transposing tables? # radioButtonsInBox(output_box, self, "gene_as_attr_name", ["Genes in rows", "Genes in columns"], # callback=self.on_output_option_change) # separator(output_box) checkBox(output_box, self, 'filter_unknown', 'Filter unknown genes', callback=self.on_output_option_change) separator(output_box) output_box.layout().addWidget(horizontal_line()) checkBox(output_box, self, 'include_entrez_id', 'Include Entrez ID', callback=self.on_output_option_change) # TODO: provide support for ensembl ids as output option # checkBox(output_box, self, 'include_ensembl_id', 'Include Ensembl ID', callback=self.on_output_option_change) auto_commit(self.controlArea, self, "auto_commit", label="Commit") rubber(self.controlArea) # Main area filter_box = hBox(self.mainArea, 'Filter results') self.radio_group = radioButtons(filter_box, self, value='selected_filter', btnLabels=self.filter_labels, orientation=Qt.Horizontal, callback=self.on_filter_changed) rubber(self.radio_group) self.mainArea.layout().addWidget(filter_box) self.proxy_model = FilterProxyModel(self) self.extended_view = ExtendedTableView(parent=self) self.extended_view.genes_view.setModel(self.proxy_model) self.extended_view.genes_selection_model().selectionChanged.connect( self.__selection_changed) self.mainArea.layout().addWidget(self.extended_view, 1) def __reset_widget_state(self): self.Outputs.custom_data_table.send(None) self.proxy_model.setSourceModel(None) self.extended_view.reset_genes_model() self.extended_view.reset_info_model() def __selection_changed(self): genes = [ model_index.data() for model_index in self.extended_view.get_selected_gens() ] self.extended_view.set_info_model(genes) def _update_info_box(self): if self.input_genes and self.gene_matcher: num_genes = len(self.gene_matcher.genes) known_genes = len(self.gene_matcher.get_known_genes()) info_text = 'Genes on input: {}\n' \ 'Known genes : {} ({:.2f} %)\n'.format(num_genes, known_genes, known_genes * 100 / num_genes) else: info_text = 'No genes on input' self.info_box.setText(info_text) def _progress_advance(self): # GUI should be updated in main thread. That's why we are calling advance method here if self.progress_bar: self.progress_bar.advance() def _handle_matcher_results(self): assert threading.current_thread() == threading.main_thread() if self.progress_bar: self.progress_bar.finish() self.setStatusMessage('') # if no known genes, clean up and return if not len(self.gene_matcher.get_known_genes()): self._update_info_box() self.__reset_widget_state() return self._update_info_box() self.extended_view.set_genes_model(self.gene_matcher.genes) self.proxy_model.setSourceModel(self.extended_view.genes_model) self.extended_view.genes_view.resizeRowsToContents() self.commit() def get_available_organisms(self): available_organism = sorted([(tax_id, taxonomy.name(tax_id)) for tax_id in taxonomy.common_taxids()], key=lambda x: x[1]) self.organisms = [tax_id[0] for tax_id in available_organism] self.organism_select_combobox.addItems( [tax_id[1] for tax_id in available_organism]) def gene_names_from_table(self): """ Extract and return gene names from `Orange.data.Table`. """ self.input_genes = [] if self.input_data: if self.use_attr_names: self.input_genes = [ str(attr.name).strip() for attr in self.input_data.domain.attributes ] elif self.selected_gene_col: if self.selected_gene_col in self.input_data.domain: self.input_genes = [ str(e[self.selected_gene_col]) for e in self.input_data if not np.isnan(e[self.selected_gene_col]) ] def _update_gene_matcher(self): self.gene_names_from_table() if not self.input_genes: self._update_info_box() if not self.gene_matcher: self.gene_matcher = GeneMatcher(self.get_selected_organism(), case_insensitive=True) self.gene_matcher.genes = self.input_genes self.gene_matcher.organism = self.get_selected_organism() def get_selected_organism(self): return self.organisms[self.selected_organism] def match_genes(self): if self.gene_matcher: # init progress bar self.progress_bar = ProgressBar(self, iterations=len( self.gene_matcher.genes)) # status message self.setStatusMessage('Gene matcher running') worker = Worker(self.gene_matcher.run_matcher, progress_callback=True) worker.signals.progress.connect(self._progress_advance) worker.signals.finished.connect(self._handle_matcher_results) # move download process to worker thread self.threadpool.start(worker) def on_input_option_change(self): self.__reset_widget_state() self._update_gene_matcher() self.match_genes() @Inputs.data_table def handle_input(self, data): self.__reset_widget_state() self.gene_columns_model.set_domain(None) if data: self.input_data = data self.gene_columns_model.set_domain(self.input_data.domain) if self.gene_columns_model: self.selected_gene_col = self.gene_columns_model[0] self.tax_id = str(self.input_data.attributes.get(TAX_ID, '')) self.use_attr_names = self.input_data.attributes.get( GENE_AS_ATTRIBUTE_NAME, self.use_attr_names) if self.tax_id in self.organisms: self.selected_organism = self.organisms.index(self.tax_id) self.on_input_option_change() @staticmethod def get_gene_id_identifier(gene_id_strings): # type: (Set[str]) -> str if not len(gene_id_strings): return NCBI_ID regex = re.compile(r'Entrez ID \(.*?\)') filtered = filter(regex.search, gene_id_strings) return NCBI_ID + ' ({})'.format(len(set(filtered)) + 1) def __handle_ids(self, data_table): """ If 'use_attr_names' is True, genes from the input data are in columns. """ if self.use_attr_names: # set_of_attributes = set([key for attr in data_table.domain[:] for key in attr.attributes.keys() # if key.startswith(NCBI_ID)]) # gene_id = self.get_gene_id_identifier(set_of_attributes) gene_id = NCBI_ID for gene in self.gene_matcher.genes: if gene.ncbi_id: data_table.domain[ gene.input_name].attributes[gene_id] = str( gene.ncbi_id) else: set_of_variables = set([ var.name for var in data_table.domain.variables + data_table.domain.metas if var.name.startswith(NCBI_ID) ]) gene_id = self.get_gene_id_identifier(set_of_variables) temp_domain = Domain([], metas=[StringVariable(gene_id)]) temp_data = [[str(gene.ncbi_id) if gene.ncbi_id else '?'] for gene in self.gene_matcher.genes] temp_table = Table(temp_domain, temp_data) # if columns differ, then concatenate. if NCBI_ID in data_table.domain: if gene_id != NCBI_ID and not np.array_equal( np.array(temp_data).ravel(), data_table.get_column_view(NCBI_ID)[0]): data_table = Table.concatenate([data_table, temp_table]) else: gene_id = NCBI_ID else: data_table = Table.concatenate([data_table, temp_table]) return data_table, gene_id def __apply_filters(self, data_table): set_of_attributes = set([ key for attr in data_table.domain[:] for key in attr.attributes.keys() if key == NCBI_ID ]) gene_id = NCBI_ID if NCBI_ID in data_table.domain or set_of_attributes else None if self.include_entrez_id: data_table, gene_id = self.__handle_ids(data_table) if self.filter_unknown: known_input_genes = [ gene.input_name for gene in self.gene_matcher.get_known_genes() ] if self.use_attr_names: temp_domain = Domain([ attr for attr in data_table.domain.attributes if attr.name in known_input_genes ], metas=data_table.domain.metas, class_vars=data_table.domain.class_vars) data_table = data_table.transform(temp_domain) else: # create filter from selected column for genes only_known = table_filter.FilterStringList( self.selected_gene_col, known_input_genes) # apply filter to the data data_table = table_filter.Values([only_known])(data_table) return data_table, gene_id def commit(self): self.Outputs.custom_data_table.send(None) if not self.input_data: return if not self.use_attr_names and not self.gene_columns_model: return output_data_table = self.input_data.transform( self.input_data.domain.copy()) output_data_table, gene_id = self.__apply_filters( output_data_table.copy()) # handle table attributes output_data_table.attributes[TAX_ID] = self.get_selected_organism() output_data_table.attributes[GENE_AS_ATTRIBUTE_NAME] = bool( self.use_attr_names) if not bool(self.use_attr_names): output_data_table.attributes[GENE_ID_COLUMN] = gene_id else: output_data_table.attributes[GENE_ID_ATTRIBUTE] = gene_id self.Outputs.custom_data_table.send(output_data_table) # gene_objs = [self.proxy_model.index(row, 0).data() for row in range(self.proxy_model.rowCount())] def on_output_option_change(self): self.commit() def on_filter_changed(self): self.proxy_model.invalidateFilter() self.extended_view.genes_view.resizeRowsToContents()
class OWdictyExpress(OWWidget): name = "dictyExpress" description = "Time-course gene expression data" icon = "../widgets/icons/OWdictyExpress.png" want_main_area = True priority = 3 class Inputs: pass class Outputs: etc_data = Output("Data", Table) class Error(OWWidget.Error): unreachable_host = Msg('Host not reachable') invalid_credentials = Msg('Invalid credentials') username = settings.Setting('') password = settings.Setting('') setTimeVariable = settings.Setting(False) def __init__(self): super().__init__() self.res = None self.orgnism = 352472 self.server = 'https://dictyexpress.research.bcm.edu' self.headerLabels = [x[1] for x in Labels] self.searchString = "" self.items = [] self.lastSelected = None # store last selected customTreeItem self.progress_bar = None # threads self.threadpool = QThreadPool() # Login Section box = gui.widgetBox(self.controlArea, 'Login') self.namefield = gui.lineEdit(box, self, "username", "Username:"******"password", "Password:"******"setTimeVariable", "Set Time variable") self.time_var_checkBox.setToolTip( 'Create new column where each row represents one time point') self.controlArea.layout().addWidget(h_line()) self.commit_button = gui.button(self.controlArea, self, "Commit", callback=self.commit) self.handle_commit_button(False) self.refresh_button = gui.button(self.controlArea, self, "Refresh", callback=self.refresh) self.handle_cache_button(True) gui.rubber(self.controlArea) # Experiment Section label = QLabel("Available projects:") my_font = QFont() my_font.setBold(True) label.setFont(my_font) self.mainArea.layout().addWidget(label) self.mainArea.layout().addWidget(h_line()) self.filter = gui.lineEdit(self.mainArea, self, "searchString", "Filter:", callbackOnType=True, callback=self.search_update) self.experimentsWidget = QTreeWidget(alternatingRowColors=True, rootIsDecorated=False, uniformRowHeights=True, sortingEnabled=True) self.experimentsWidget.setItemDelegateForColumn( 0, gui.IndicatorItemDelegate(self, role=Qt.DisplayRole)) self.experimentsWidget.selectionModel().selectionChanged.connect( self.onSelectionChanged) self.experimentsWidget.setHeaderLabels(self.headerLabels) self.mainArea.layout().addWidget(self.experimentsWidget) self.auth_set() self.connect() def auth_set(self): self.passfield.setDisabled(not self.username) def auth_changed(self): self.auth_set() self.connect() def refresh(self): self.reset() self.load_experiments() def reset(self): self.experimentsWidget.clear() # clear QTreeWidget self.items = [] self.lastSelected = None self.searchString = "" self.handle_commit_button(False) def search_update(self): parts = self.searchString.split() for item in self.items: item.setHidden(not all(s in item for s in parts)) def progress_advance(self): # GUI should be updated in main thread. That's why we are calling advance method here assert threading.current_thread() == threading.main_thread() if self.progress_bar: self.progress_bar.advance() def handle_error(self, ex): self.progress_bar.finish() self.setStatusMessage('') if isinstance(ex, ConnectionError) or isinstance(ex, ValueError): self.Error.unreachable_host() print(ex) def load_experiments_result(self, experiments): self.load_tree_items(experiments) self.progress_bar.finish() self.setStatusMessage('') def connect(self): self.res = None self.Error.clear() self.reset() self.handle_commit_button(False) self.handle_cache_button(False) user, password = resolwe.DEFAULT_EMAIL, resolwe.DEFAULT_PASSWD if self.username or self.password: user, password = self.username, self.password try: self.res = resolwe.connect(user, password, self.server, 'genesis') except resolwe.ResolweAuthException as e: self.Error.invalid_credentials() else: self.load_experiments() self.handle_cache_button(True) def load_experiments(self): if self.res: # init progress bar self.progress_bar = gui.ProgressBar(self, iterations=3) # status message self.setStatusMessage('downloading experiments') worker = Worker(self.res.fetch_etc_objects, progress_callback=True) worker.signals.progress.connect(self.progress_advance) worker.signals.result.connect(self.load_experiments_result) worker.signals.error.connect(self.handle_error) # move download process to worker thread self.threadpool.start(worker) def load_tree_items(self, list_of_exp): self.items = [ CustomTreeItem(self.experimentsWidget, item) for item in list_of_exp ] for i in range(len(self.headerLabels)): self.experimentsWidget.resizeColumnToContents(i) def onSelectionChanged(self): self.handle_commit_button(True) def handle_commit_button(self, handle): self.commit_button.setEnabled(handle) def handle_cache_button(self, handle): self.refresh_button.setEnabled(handle) def send_to_output(self, etc_json): self.progress_bar.finish() self.setStatusMessage('') data = etc_to_table(etc_json, self.setTimeVariable) data.attributes[TAX_ID] = self.orgnism data.attributes[GENE_AS_ATTRIBUTE_NAME] = self.setTimeVariable self.Outputs.etc_data.send(data) def commit(self): self.Error.clear() # init progress bar self.progress_bar = gui.ProgressBar(self, iterations=3) # status message self.setStatusMessage('downloading experiment data') selected_item = self.experimentsWidget.currentItem( ) # get selected TreeItem if self.lastSelected: self.lastSelected.setData(0, Qt.DisplayRole, "") self.lastSelected = selected_item selected_item.setData(0, Qt.DisplayRole, " ") worker = Worker(self.res.download_etc_data, selected_item.gen_data_id, progress_callback=True) worker.signals.progress.connect(self.progress_advance) worker.signals.result.connect(self.send_to_output) worker.signals.error.connect(self.handle_error) # move download process to worker thread self.threadpool.start(worker)
class OWGeneSets(OWWidget): name = "Gene Sets" description = "" icon = "icons/OWGeneSets.svg" priority = 9 want_main_area = True settingsHandler = OrganismContextHandler() # settings auto_commit = Setting(True) stored_selections = ContextSetting([]) organism = ContextSetting(None) class Inputs: genes = Input("Genes", Table) class Outputs: matched_genes = Output("Matched Genes", Table) class Information(OWWidget.Information): pass class Error(OWWidget.Error): missing_annotation = Msg(ERROR_ON_MISSING_ANNOTATION) missing_gene_id = Msg(ERROR_ON_MISSING_GENE_ID) missing_tax_id = Msg(ERROR_ON_MISSING_TAX_ID) cant_reach_host = Msg("Host orange.biolab.si is unreachable.") cant_load_organisms = Msg( "No available organisms, please check your connection.") def __init__(self): super().__init__() # commit self.commit_button = None # progress bar self.progress_bar = None self.progress_bar_iterations = None # data self.input_data = None self.input_genes = None self.tax_id = None self.use_attr_names = None self.gene_id_attribute = None self.gene_id_column = None self.input_info = None self.num_of_sel_genes = 0 # filter self.lineEdit_filter = None self.search_pattern = '' self.organism_select_combobox = None # data model view self.data_view = None self.data_model = None # gene matcher NCBI self.gene_matcher = None # filter proxy model self.filter_proxy_model = None # hierarchy widget self.hierarchy_widget = None self.hierarchy_state = None # threads self.threadpool = QThreadPool(self) self.workers = None # gui self.setup_gui() def _progress_advance(self): # GUI should be updated in main thread. That's why we are calling advance method here if self.progress_bar: self.progress_bar.advance() def __get_genes(self): self.input_genes = [] if self.use_attr_names: for variable in self.input_data.domain.attributes: self.input_genes.append( str(variable.attributes.get(self.gene_id_attribute, '?'))) else: genes, _ = self.input_data.get_column_view(self.gene_id_column) self.input_genes = [str(g) for g in genes] @Inputs.genes def handle_input(self, data): self.closeContext() self.Error.clear() if data: self.input_data = data self.tax_id = str(self.input_data.attributes.get(TAX_ID, None)) self.use_attr_names = self.input_data.attributes.get( GENE_AS_ATTRIBUTE_NAME, None) self.gene_id_attribute = self.input_data.attributes.get( GENE_ID_ATTRIBUTE, None) self.gene_id_column = self.input_data.attributes.get( GENE_ID_COLUMN, None) if not (self.use_attr_names is not None and ((self.gene_id_attribute is None) ^ (self.gene_id_column is None))): if self.tax_id is None: self.Error.missing_annotation() return self.Error.missing_gene_id() return elif self.tax_id is None: self.Error.missing_tax_id() return self.openContext(self.tax_id) self.__get_genes() self.download_gene_sets() def update_info_box(self): info_string = '' if self.input_genes: info_string += '{} unique gene names on input.\n'.format( len(self.input_genes)) info_string += '{} genes on output.\n'.format( self.num_of_sel_genes) else: info_string += 'No genes on input.\n' self.input_info.setText(info_string) def on_gene_sets_download(self, result): # make sure this happens in the main thread. # Qt insists that widgets be created within the GUI(main) thread. assert threading.current_thread() == threading.main_thread() self.progress_bar.finish() self.setStatusMessage('') tax_id, sets = result self.set_hierarchy_model(self.hierarchy_widget, *hierarchy_tree(tax_id, sets)) self.set_selected_hierarchies() self.update_info_box() self.workers = defaultdict(list) self.progress_bar_iterations = dict() for selected_hierarchy in self.get_hierarchies(): gene_sets = geneset.load_gene_sets(selected_hierarchy) worker = Worker(get_collections, gene_sets, set(self.input_genes), progress_callback=True, partial_result=True) worker.signals.error.connect(self.handle_error) worker.signals.finished.connect(self.handle_worker_finished) worker.signals.progress.connect(self._progress_advance) worker.signals.partial_result.connect(self.populate_data_model) worker.setAutoDelete(False) self.workers[selected_hierarchy] = worker self.progress_bar_iterations[selected_hierarchy] = len(gene_sets) self.display_gene_sets() def handle_worker_finished(self): # We check if all workers have completed. If not, continue # dirty hax, is this ok? if self.progress_bar and self.progress_bar.widget.progressBarValue == 100: self.progress_bar.finish() self.setStatusMessage('') self.hierarchy_widget.setDisabled(False) # adjust column width for i in range(len(DATA_HEADER_LABELS) - 1): self.data_view.resizeColumnToContents(i) self.filter_proxy_model.setSourceModel(self.data_model) def populate_data_model(self, partial_result): assert threading.current_thread() == threading.main_thread() if partial_result: self.data_model.appendRow(partial_result) def set_hierarchy_model(self, model, tax_id, sets): def beautify_displayed_text(text): if '_' in text: return text.replace('_', ' ').title() else: return text # TODO: maybe optimize this code? for key, value in sets.items(): item = QTreeWidgetItem(model, [beautify_displayed_text(key)]) item.setFlags(item.flags() & (Qt.ItemIsUserCheckable | ~Qt.ItemIsSelectable | Qt.ItemIsEnabled)) item.setExpanded(True) item.tax_id = tax_id item.hierarchy = key if value: item.setFlags(item.flags() | Qt.ItemIsTristate) self.set_hierarchy_model(item, tax_id, value) else: if item.parent(): item.hierarchy = ((item.parent().hierarchy, key), tax_id) if not item.childCount() and not item.parent(): item.hierarchy = ((key, ), tax_id) def download_gene_sets(self): self.Error.clear() # reset hierarchy widget state self.hierarchy_widget.clear() # clear data view self.init_item_model() # get all gene sets for selected organism gene_sets = geneset.list_all(organism=self.tax_id) # init progress bar self.progress_bar = ProgressBar(self, iterations=len(gene_sets) * 100) # status message self.setStatusMessage('downloading sets') worker = Worker(download_gene_sets, gene_sets, progress_callback=True) worker.signals.progress.connect(self._progress_advance) worker.signals.result.connect(self.on_gene_sets_download) worker.signals.error.connect(self.handle_error) # move download process to worker thread self.threadpool.start(worker) def display_gene_sets(self): self.init_item_model() self.hierarchy_widget.setDisabled(True) only_selected_hier = self.get_hierarchies(only_selected=True) # init progress bar iterations = sum([ self.progress_bar_iterations[hier] for hier in only_selected_hier ]) self.progress_bar = ProgressBar(self, iterations=iterations) self.setStatusMessage('displaying gene sets') if not only_selected_hier: self.progress_bar.finish() self.setStatusMessage('') self.hierarchy_widget.setDisabled(False) return # save setting on selected hierarchies self.stored_selections = only_selected_hier # save context self.closeContext() for selected_hierarchy in only_selected_hier: self.threadpool.start(self.workers[selected_hierarchy]) self.openContext(self.tax_id) def handle_error(self, ex): self.progress_bar.finish() self.setStatusMessage('') if isinstance(ex, ConnectionError): self.Error.cant_reach_host() print(ex) def set_selected_hierarchies(self): iterator = QTreeWidgetItemIterator(self.hierarchy_widget, QTreeWidgetItemIterator.All) while iterator.value(): # note: if hierarchy value is not a tuple, then this is just top level qTreeWidgetItem that # holds subcategories. We don't want to display all sets from category if type(iterator.value().hierarchy) is not str: if iterator.value().hierarchy in self.stored_selections: iterator.value().setCheckState(0, Qt.Checked) else: iterator.value().setCheckState(0, Qt.Unchecked) iterator += 1 # if no items are checked, we check first one at random if len(self.get_hierarchies(only_selected=True)) == 0: iterator = QTreeWidgetItemIterator( self.hierarchy_widget, QTreeWidgetItemIterator.NotChecked) while iterator.value(): if type(iterator.value().hierarchy) is not str: iterator.value().setCheckState(0, Qt.Checked) return iterator += 1 def get_hierarchies(self, **kwargs): """ return selected hierarchy """ only_selected = kwargs.get('only_selected', None) sets_to_display = list() if only_selected: iterator = QTreeWidgetItemIterator(self.hierarchy_widget, QTreeWidgetItemIterator.Checked) else: iterator = QTreeWidgetItemIterator(self.hierarchy_widget) while iterator.value(): # note: if hierarchy value is not a tuple, then this is just top level qTreeWidgetItem that # holds subcategories. We don't want to display all sets from category if type(iterator.value().hierarchy) is not str: if not only_selected: sets_to_display.append(iterator.value().hierarchy) else: if not iterator.value().isDisabled(): sets_to_display.append(iterator.value().hierarchy) iterator += 1 return sets_to_display def commit(self): selection_model = self.data_view.selectionModel() if selection_model: # genes_from_set = selection_model.selectedRows(GENES) matched_genes = selection_model.selectedRows(MATCHED) if matched_genes and self.input_genes: genes = [ model_index.data(Qt.UserRole) for model_index in matched_genes ] output_genes = [ gene_name for gene_name in list(set.union(*genes)) ] self.num_of_sel_genes = len(output_genes) self.update_info_box() if self.use_attr_names: selected = [ column for column in self.input_data.domain.attributes if self.gene_id_attribute in column.attributes and str(column.attributes[ self.gene_id_attribute]) in output_genes ] domain = Domain(selected, self.input_data.domain.class_vars, self.input_data.domain.metas) new_data = self.input_data.from_table( domain, self.input_data) self.Outputs.matched_genes.send(new_data) else: selected_rows = [] for row_index, row in enumerate(self.input_data): gene_in_row = str(row[self.gene_id_column]) if gene_in_row in self.input_genes and gene_in_row in output_genes: selected_rows.append(row_index) if selected_rows: selected = self.input_data[selected_rows] else: selected = None self.Outputs.matched_genes.send(selected) def setup_gui(self): # control area info_box = vBox(self.controlArea, 'Input info') self.input_info = widgetLabel(info_box) hierarchy_box = widgetBox(self.controlArea, "Entity Sets") self.hierarchy_widget = QTreeWidget(self) self.hierarchy_widget.setEditTriggers(QTreeView.NoEditTriggers) self.hierarchy_widget.setHeaderLabels(HIERARCHY_HEADER_LABELS) self.hierarchy_widget.itemClicked.connect(self.display_gene_sets) hierarchy_box.layout().addWidget(self.hierarchy_widget) self.commit_button = auto_commit(self.controlArea, self, "auto_commit", "&Commit", box=False) # rubber(self.controlArea) # main area self.filter_proxy_model = QSortFilterProxyModel(self.data_view) self.filter_proxy_model.setFilterKeyColumn(3) self.data_view = QTreeView() self.data_view.setModel(self.filter_proxy_model) self.data_view.setAlternatingRowColors(True) self.data_view.sortByColumn(2, Qt.DescendingOrder) self.data_view.setSortingEnabled(True) self.data_view.setSelectionMode(QTreeView.ExtendedSelection) self.data_view.setEditTriggers(QTreeView.NoEditTriggers) self.data_view.viewport().setMouseTracking(True) self.data_view.setItemDelegateForColumn( TERM, LinkStyledItemDelegate(self.data_view)) self.data_view.selectionModel().selectionChanged.connect(self.commit) self.lineEdit_filter = lineEdit(self.mainArea, self, 'search_pattern', 'Filter gene sets:') self.lineEdit_filter.setPlaceholderText('search pattern ...') self.lineEdit_filter.textChanged.connect( self.filter_proxy_model.setFilterRegExp) self.mainArea.layout().addWidget(self.data_view) def init_item_model(self): if self.data_model: self.data_model.clear() self.filter_proxy_model.setSourceModel(None) else: self.data_model = QStandardItemModel() self.data_model.setSortRole(Qt.UserRole) self.data_model.setHorizontalHeaderLabels(DATA_HEADER_LABELS) def sizeHint(self): return QSize(1280, 960)