class OWConcordance(OWWidget): name = "Concordance" description = "Display the context of the word." icon = "icons/Concordance.svg" priority = 520 class Inputs: corpus = Input("Corpus", Corpus) query_word = Input("Query Word", Topic) class Outputs: selected_documents = Output("Selected Documents", Corpus) concordances = Output("Concordances", Corpus) settingsHandler = PerfectDomainContextHandler( match_values = PerfectDomainContextHandler.MATCH_VALUES_ALL ) autocommit = Setting(True) context_width = Setting(5) word = ContextSetting("", exclude_metas=False) selected_rows = Setting([], schema_only=True) class Warning(OWWidget.Warning): multiple_words_on_input = Msg("Multiple query words on input. " "Only the first one is considered!") def __init__(self): super().__init__() self.corpus = None # Corpus self.n_matching = '' # Info on docs matching the word self.n_tokens = '' # Info on tokens self.n_types = '' # Info on types (unique tokens) self.is_word_on_input = False # Info attributes info_box = gui.widgetBox(self.controlArea, 'Info') gui.label(info_box, self, 'Tokens: %(n_tokens)s') gui.label(info_box, self, 'Types: %(n_types)s') gui.label(info_box, self, 'Matching: %(n_matching)s') # Width parameter gui.spin(self.controlArea, self, 'context_width', 3, 10, box=True, label="Number of words:", callback=self.set_width) gui.rubber(self.controlArea) # Search c_box = gui.widgetBox(self.mainArea, orientation="vertical") self.input = gui.lineEdit( c_box, self, 'word', orientation=Qt.Horizontal, sizePolicy=QSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Fixed), label='Query:', callback=self.set_word, callbackOnType=True) self.input.setFocus() # Concordances view self.conc_view = QTableView() self.model = ConcordanceModel() self.conc_view.setModel(self.model) self.conc_view.setWordWrap(False) self.conc_view.setSelectionBehavior(QTableView.SelectRows) self.conc_view.setSelectionModel(DocumentSelectionModel(self.model)) self.conc_view.setItemDelegate(HorizontalGridDelegate()) self.conc_view.selectionModel().selectionChanged.connect(self.selection_changed) self.conc_view.horizontalHeader().hide() self.conc_view.setShowGrid(False) self.mainArea.layout().addWidget(self.conc_view) self.set_width() # Auto-commit box gui.auto_commit(self.controlArea, self, 'autocommit', 'Commit', 'Auto commit is on') def sizeHint(self): # pragma: no cover return QSize(600, 400) def set_width(self): sel = self.conc_view.selectionModel().selection() self.model.set_width(self.context_width) if sel: self.conc_view.selectionModel().select(sel, QItemSelectionModel.SelectCurrent | QItemSelectionModel.Rows) def selection_changed(self): selection = self.conc_view.selectionModel().selection() self.selected_rows = sorted(set(cell.row() for cell in selection.indexes())) self.commit() def set_selection(self, selection): if selection: sel = QItemSelection() for row in selection: index = self.conc_view.model().index(row, 0) sel.select(index, index) self.conc_view.selectionModel().select(sel, QItemSelectionModel.SelectCurrent | QItemSelectionModel.Rows) @Inputs.corpus def set_corpus(self, data=None): self.closeContext() self.corpus = data if data is None: # data removed, clear selection self.selected_rows = [] if not self.is_word_on_input: self.word = "" self.openContext(self.corpus) self.model.set_corpus(self.corpus) self.set_word() @Inputs.query_word def set_word_from_input(self, topic): self.Warning.multiple_words_on_input.clear() if self.is_word_on_input: # word changed, clear selection self.selected_rows = [] self.is_word_on_input = topic is not None and len(topic) > 0 self.input.setEnabled(not self.is_word_on_input) if self.is_word_on_input: if len(topic) > 1: self.Warning.multiple_words_on_input() self.word = topic.metas[0, 0] self.set_word() def set_word(self): self.selected_rows = [] self.model.set_word(self.word) self.update_widget() self.commit() def handleNewSignals(self): self.set_selection(self.selected_rows) def resize_columns(self): col_width = (self.conc_view.width() - self.conc_view.columnWidth(1)) / 2 - 12 self.conc_view.setColumnWidth(0, col_width) self.conc_view.setColumnWidth(2, col_width) def resizeEvent(self, event): # pragma: no cover super().resizeEvent(event) self.resize_columns() def update_widget(self): self.conc_view.resizeColumnToContents(1) self.resize_columns() self.conc_view.resizeRowsToContents() if self.corpus is not None: self.n_matching = '{}/{}'.format( self.model.matching_docs() if self.word else 0, len(self.corpus)) self.n_tokens = self.model.n_tokens self.n_types = self.model.n_types else: self.n_matching = '' self.n_tokens = '' self.n_types = '' def commit(self): selected_docs = sorted(set(self.model.word_index[row][0] for row in self.selected_rows)) concordance = self.model.get_data() if selected_docs: selected = self.corpus[selected_docs] self.Outputs.selected_documents.send(selected) else: self.Outputs.selected_documents.send(None) self.Outputs.concordances.send(concordance) def send_report(self): view = self.conc_view model = self.conc_view.model() self.report_items("Concordances", ( ("Query", model.word), ("Tokens", model.n_tokens), ("Types", model.n_types), ("Matching", self.n_matching), )) self.report_table(view)
class OWConcordance(OWWidget): name = "Concordance" description = "Display the context of the word." icon = "icons/Concordance.svg" priority = 520 class Inputs: corpus = Input("Corpus", Corpus) query_word = Input("Query Word", Topic) class Outputs: selected_documents = Output("Selected Documents", Corpus) concordances = Output("Concordances", Corpus) settingsHandler = PerfectDomainContextHandler( match_values=PerfectDomainContextHandler.MATCH_VALUES_ALL) autocommit = Setting(True) context_width = Setting(5) word = ContextSetting("", exclude_metas=False) selected_rows = Setting([], schema_only=True) class Warning(OWWidget.Warning): multiple_words_on_input = Msg("Multiple query words on input. " "Only the first one is considered!") def __init__(self): super().__init__() self.corpus = None # Corpus self.n_matching = '' # Info on docs matching the word self.n_tokens = '' # Info on tokens self.n_types = '' # Info on types (unique tokens) self.is_word_on_input = False # Info attributes info_box = gui.widgetBox(self.controlArea, 'Info') gui.label(info_box, self, 'Tokens: %(n_tokens)s') gui.label(info_box, self, 'Types: %(n_types)s') gui.label(info_box, self, 'Matching: %(n_matching)s') # Width parameter gui.spin(self.controlArea, self, 'context_width', 3, 10, box=True, label="Number of words:", callback=self.set_width) gui.rubber(self.controlArea) # Search c_box = gui.widgetBox(self.mainArea, orientation="vertical") self.input = gui.lineEdit(c_box, self, 'word', orientation=Qt.Horizontal, sizePolicy=QSizePolicy( QSizePolicy.MinimumExpanding, QSizePolicy.Fixed), label='Query:', callback=self.set_word, callbackOnType=True) self.input.setFocus() # Concordances view self.conc_view = QTableView() self.model = ConcordanceModel() self.conc_view.setModel(self.model) self.conc_view.setWordWrap(False) self.conc_view.setSelectionBehavior(QTableView.SelectRows) self.conc_view.setSelectionModel(DocumentSelectionModel(self.model)) self.conc_view.setItemDelegate(HorizontalGridDelegate()) self.conc_view.selectionModel().selectionChanged.connect( self.selection_changed) self.conc_view.horizontalHeader().hide() self.conc_view.setShowGrid(False) self.mainArea.layout().addWidget(self.conc_view) self.set_width() # Auto-commit box gui.auto_commit(self.controlArea, self, 'autocommit', 'Commit', 'Auto commit is on') def sizeHint(self): # pragma: no cover return QSize(600, 400) def set_width(self): sel = self.conc_view.selectionModel().selection() self.model.set_width(self.context_width) if sel: self.conc_view.selectionModel().select( sel, QItemSelectionModel.SelectCurrent | QItemSelectionModel.Rows) def selection_changed(self): selection = self.conc_view.selectionModel().selection() self.selected_rows = sorted( set(cell.row() for cell in selection.indexes())) self.commit() def set_selection(self, selection): if selection: sel = QItemSelection() for row in selection: index = self.conc_view.model().index(row, 0) sel.select(index, index) self.conc_view.selectionModel().select( sel, QItemSelectionModel.SelectCurrent | QItemSelectionModel.Rows) @Inputs.corpus def set_corpus(self, data=None): self.closeContext() self.corpus = data if data is None: # data removed, clear selection self.selected_rows = [] if not self.is_word_on_input: self.word = "" self.openContext(self.corpus) self.model.set_corpus(self.corpus) self.set_word() @Inputs.query_word def set_word_from_input(self, topic): self.Warning.multiple_words_on_input.clear() if self.is_word_on_input: # word changed, clear selection self.selected_rows = [] self.is_word_on_input = topic is not None and len(topic) > 0 self.input.setEnabled(not self.is_word_on_input) if self.is_word_on_input: if len(topic) > 1: self.Warning.multiple_words_on_input() self.word = topic.metas[0, 0] self.set_word() def set_word(self): self.selected_rows = [] self.model.set_word(self.word) self.update_widget() self.commit() def handleNewSignals(self): self.set_selection(self.selected_rows) def resize_columns(self): col_width = (self.conc_view.width() - self.conc_view.columnWidth(1)) / 2 - 12 self.conc_view.setColumnWidth(0, col_width) self.conc_view.setColumnWidth(2, col_width) def resizeEvent(self, event): # pragma: no cover super().resizeEvent(event) self.resize_columns() def update_widget(self): self.conc_view.resizeColumnToContents(1) self.resize_columns() self.conc_view.resizeRowsToContents() if self.corpus is not None: self.n_matching = '{}/{}'.format( self.model.matching_docs() if self.word else 0, len(self.corpus)) self.n_tokens = self.model.n_tokens self.n_types = self.model.n_types else: self.n_matching = '' self.n_tokens = '' self.n_types = '' def commit(self): selected_docs = sorted( set(self.model.word_index[row][0] for row in self.selected_rows)) concordance = self.model.get_data() if selected_docs: selected = self.corpus[selected_docs] self.Outputs.selected_documents.send(selected) else: self.Outputs.selected_documents.send(None) self.Outputs.concordances.send(concordance) def send_report(self): view = self.conc_view model = self.conc_view.model() self.report_items("Concordances", ( ("Query", model.word), ("Tokens", model.n_tokens), ("Types", model.n_types), ("Matching", self.n_matching), )) self.report_table(view)
class OWGEODatasets(OWWidget, ConcurrentWidgetMixin): name = "GEO Data Sets" description = "Access to Gene Expression Omnibus data sets." icon = "icons/OWGEODatasets.svg" priority = 2 class Outputs: gds_data = Output("Expression Data", Table) search_pattern = Setting('') auto_commit = Setting(True) genes_as_rows = Setting(False) mergeSpots = Setting(True) selected_gds = Setting(None) gdsSelectionStates = Setting({}) splitter_settings = Setting(( b'\x00\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x01\xea\x00\x00\x00\xd7\x01\x00\x00\x00\x07\x01\x00\x00\x00\x02', b'\x00\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x01\xb5\x00\x00\x02\x10\x01\x00\x00\x00\x07\x01\x00\x00\x00\x01', )) def __init__(self): OWWidget.__init__(self) ConcurrentWidgetMixin.__init__(self) self.gds_info: Optional[GDSInfo] = GDSInfo( ) # TODO: handle possible exceptions self.gds_data: Optional[Table] = None # Control area box = widgetBox(self.controlArea, 'Info', addSpace=True) self.infoBox = widgetLabel(box, 'Initializing\n\n') box = widgetBox(self.controlArea, 'Output', addSpace=True) radioButtonsInBox(box, self, 'genes_as_rows', ['Samples in rows', 'Genes in rows'], callback=self._run) separator(box) rubber(self.controlArea) auto_commit(self.controlArea, self, 'auto_commit', '&Commit', box=False) # Main Area # Filter widget self.filter = lineEdit(self.mainArea, self, 'search_pattern', 'Filter:', callbackOnType=True, callback=self._apply_filter) self.mainArea.layout().addWidget(self.filter) splitter_vertical = QSplitter(Qt.Vertical, self.mainArea) self.mainArea.layout().addWidget(splitter_vertical) # set table view self.table_view = QTableView(splitter_vertical) self.table_view.setShowGrid(False) self.table_view.setSortingEnabled(True) self.table_view.sortByColumn(1, Qt.AscendingOrder) self.table_view.setAlternatingRowColors(True) self.table_view.verticalHeader().setVisible(False) self.table_view.setSelectionBehavior(QAbstractItemView.SelectRows) self.table_view.setSelectionMode(QAbstractItemView.SingleSelection) self.table_view.viewport().setMouseTracking(True) self.table_view.setSizeAdjustPolicy( QAbstractScrollArea.AdjustToContents) self.table_model = GEODatasetsModel() self.table_model.initialize(self.gds_info) self.table_view.setModel(self.table_model) self.table_model.show_table() self.table_view.horizontalHeader().setStretchLastSection(True) self.table_view.resizeColumnsToContents() 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) # set item delegates self.table_view.setItemDelegateForColumn( self.table_model.pubmedid_col, LinkStyledItemDelegate(self.table_view)) self.table_view.setItemDelegateForColumn( self.table_model.gds_id_col, LinkStyledItemDelegate(self.table_view)) self.table_view.setItemDelegateForColumn( self.table_model.indicator_col, IndicatorItemDelegate(self.table_view, role=Qt.DisplayRole), ) splitter_horizontal = QSplitter(Qt.Horizontal, splitter_vertical) # Description Widget box = widgetBox(splitter_horizontal, 'Description') self.description_widget = widgetLabel(box, '') self.description_widget.setWordWrap(True) rubber(box) # Sample Annotations Widget box = widgetBox(splitter_horizontal, 'Sample Annotations') self.annotations_widget = QTreeWidget(box) self.annotations_widget.setHeaderLabels( ['Type (Sample annotations)', 'Sample count']) self.annotations_widget.setRootIsDecorated(True) box.layout().addWidget(self.annotations_widget) self._annotations_updating = False self.annotations_widget.itemChanged.connect( self.on_annotation_selection_changed) self.splitters = splitter_vertical, splitter_horizontal for sp, setting in zip(self.splitters, self.splitter_settings): sp.splitterMoved.connect(self._splitter_moved) sp.restoreState(setting) self.table_view.selectionModel().selectionChanged.connect( self.on_gds_selection_changed) self._apply_filter() self._run() def _splitter_moved(self, *args): self.splitter_settings = [ bytes(sp.saveState()) for sp in self.splitters ] def _set_description_widget(self): self.description_widget.setText( self.selected_gds.get('description', 'Description not available.')) def _set_annotations_widget(self, gds): self._annotations_updating = True self.annotations_widget.clear() annotations = defaultdict(set) subsets_count = {} for desc in gds['subsets']: annotations[desc['type']].add(desc['description']) subsets_count[desc['description']] = str(len(desc['sample_id'])) for _type, subsets in annotations.items(): key = (gds["name"], _type) parent = QTreeWidgetItem(self.annotations_widget, [_type]) parent.key = key for subset in subsets: key = (gds['name'], _type, subset) item = QTreeWidgetItem( parent, [subset, subsets_count.get(subset, '')]) item.setFlags(item.flags() | Qt.ItemIsUserCheckable) item.setCheckState( 0, self.gdsSelectionStates.get(key, Qt.Checked)) item.key = key self._annotations_updating = False self.annotations_widget.expandAll() for i in range(self.annotations_widget.columnCount()): self.annotations_widget.resizeColumnToContents(i) def _set_selection(self): if self.selected_gds is not None: index = self.table_model.get_row_index( self.selected_gds.get('name')) if index is not None: self.table_view.selectionModel().blockSignals(True) self.table_view.selectRow(index) self._handle_selection_changed() self.table_view.selectionModel().blockSignals(False) def _handle_selection_changed(self): if self.table_model.table is not None: selection = self.table_view.selectionModel().selectedRows( self.table_model.gds_id_col) selected_gds_name = selection[0].data( ) if len(selection) > 0 else None if selected_gds_name: self.selected_gds = self.table_model.info.get( selected_gds_name) self._set_annotations_widget(self.selected_gds) self._set_description_widget() else: self.annotations_widget.clear() self.description_widget.clear() self.update_info() def _apply_filter(self): if self.table_model.table is not None: self.table_model.show_table( filter_pattern=str(self.search_pattern)) self._set_selection() self.update_info() def _run(self): if self.selected_gds is not None: self.gds_data = None self.start(run_download_task, self.selected_gds.get('name'), self.get_selected_samples(), self.genes_as_rows) def on_gds_selection_changed(self): self._handle_selection_changed() self._run() def on_annotation_selection_changed(self): if self._annotations_updating: return for i in range(self.annotations_widget.topLevelItemCount()): item = self.annotations_widget.topLevelItem(i) if 'key' in item.__dict__: self.gdsSelectionStates[item.key] = item.checkState(0) for j in range(item.childCount()): child = item.child(j) if 'key' in child.__dict__: self.gdsSelectionStates[child.key] = child.checkState(0) self._run() def update_info(self): all_gds = len(self.table_model.info) text = "{} datasets\n{} datasets cached\n".format( all_gds, len(local_files.listfiles())) filtered = self.table_view.model().rowCount() if all_gds != filtered: text += "{} after filtering".format(filtered) self.infoBox.setText(text) def get_selected_samples(self): """ Return the currently selected sample annotations. The return value is a list of selected (sample type, sample value) tuples. .. note:: if some Sample annotation type has no selected values. this method will return all values for it. TODO: this could probably be simplified. """ def childiter(item): """ Iterate over the children of an QTreeWidgetItem instance. """ for i in range(item.childCount()): yield item.child(i) samples = [] unused_types = [] used_types = [] for stype in childiter(self.annotations_widget.invisibleRootItem()): selected_values = [] all_values = [] for sval in childiter(stype): value = (str(stype.text(0)), str(sval.text(0))) if self.gdsSelectionStates.get(sval.key, True): selected_values.append(value) all_values.append(value) if selected_values: samples.extend(selected_values) used_types.append(str(stype.text(0))) else: # If no sample of sample type is selected we don't filter on it. samples.extend(all_values) unused_types.append(str(stype.text(0))) _samples = defaultdict(list) for sample, sample_type in samples: _samples[sample].append(sample_type) return _samples def commit(self): self.Outputs.gds_data.send(self.gds_data) def on_done(self, result: Result): assert isinstance(result.gds_dataset, Table) self.gds_data = result.gds_dataset self.commit() if self.gds_info: self.table_model.initialize(self.gds_info) self._apply_filter() def on_partial_result(self, result: Any) -> None: pass def onDeleteWidget(self): self.shutdown() super().onDeleteWidget() def send_report(self): self.report_items( "GEO Dataset", [ ("ID", self.selected_gds['name']), ("Title", self.selected_gds['title']), ("Organism", self.selected_gds['sample_organism']), ], ) self.report_items( "Data", [ ("Samples", self.selected_gds['sample_count']), ("Features", self.selected_gds['variables']), ("Genes", self.selected_gds['genes']), ], ) self.report_name("Sample annotations") subsets = defaultdict(list) for subset in self.selected_gds['subsets']: subsets[subset['type']].append( (subset['description'], len(subset['sample_id']))) self.report_html += "<ul>" for _type in subsets: self.report_html += "<b>" + _type + ":</b></br>" for desc, count in subsets[_type]: self.report_html += 9 * " " + "<b>{}:</b> {}</br>".format( desc, count) self.report_html += "</ul>"