class EditableTreeView(QWidget): dataChanged = Signal() selectionChanged = Signal() def __init__(self, parent=None): super().__init__(parent=parent) self.__stack: List = [] self.__stack_index: int = -1 def push_on_data_changed(_, __, roles): if Qt.EditRole in roles: self._push_data() self.__model = QStandardItemModel() self.__model.dataChanged.connect(self.dataChanged) self.__model.dataChanged.connect(push_on_data_changed) self.__root: QStandardItem = self.__model.invisibleRootItem() self.__tree = TreeView(self.dataChanged) self.__tree.drop_finished.connect(self.dataChanged) self.__tree.drop_finished.connect(self._push_data) self.__tree.setModel(self.__model) self.__tree.selectionModel().selectionChanged.connect( self.selectionChanged) actions_widget = ModelActionsWidget() actions_widget.layout().setSpacing(1) action = QAction("+", self, toolTip="Add a new word") action.triggered.connect(self.__on_add) actions_widget.addAction(action) action = QAction("\N{MINUS SIGN}", self, toolTip="Remove word") action.triggered.connect(self.__on_remove) actions_widget.addAction(action) action = QAction("\N{MINUS SIGN}R", self, toolTip="Remove word recursively (incl. children)") action.triggered.connect(self.__on_remove_recursive) actions_widget.addAction(action) gui.rubber(actions_widget) self.__undo_action = action = QAction("Undo", self, toolTip="Undo") action.triggered.connect(self.__on_undo) actions_widget.addAction(action) self.__redo_action = action = QAction("Redo", self, toolTip="Redo") action.triggered.connect(self.__on_redo) actions_widget.addAction(action) self._enable_undo_redo() layout = QVBoxLayout() layout.setSpacing(1) layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(self.__tree) layout.addWidget(actions_widget) self.setLayout(layout) def __on_add(self): parent: QStandardItem = self.__root selection: List = self.__tree.selectionModel().selectedIndexes() if selection: sel_index: QModelIndex = selection[0] parent: QStandardItem = self.__model.itemFromIndex(sel_index) item = QStandardItem("") parent.appendRow(item) index: QModelIndex = item.index() with disconnected(self.__model.dataChanged, self.dataChanged): self.__model.setItemData(index, {Qt.EditRole: ""}) self.__tree.setCurrentIndex(index) self.__tree.edit(index) def __on_remove_recursive(self): sel_model: QItemSelectionModel = self.__tree.selectionModel() if len(sel_model.selectedIndexes()): while sel_model.selectedIndexes(): index: QModelIndex = sel_model.selectedIndexes()[0] self.__model.removeRow(index.row(), index.parent()) self._push_data() self.dataChanged.emit() def __on_remove(self): sel_model: QItemSelectionModel = self.__tree.selectionModel() if len(sel_model.selectedIndexes()): while sel_model.selectedIndexes(): index: QModelIndex = sel_model.selectedIndexes()[0] # move children to item's parent item: QStandardItem = self.__model.itemFromIndex(index) children = [item.takeChild(i) for i in range(item.rowCount())] parent = item.parent() or self.__root self.__model.removeRow(index.row(), index.parent()) for child in children[::-1]: parent.insertRow(index.row(), child) self.__tree.expandAll() self._push_data() self.dataChanged.emit() def __on_undo(self): self.__stack_index -= 1 self._set_from_stack() def __on_redo(self): self.__stack_index += 1 self._set_from_stack() def get_words(self) -> List: return _model_to_words(self.__root) def get_selected_words(self) -> Set: return set(self.__model.itemFromIndex(index).text() for index in self.__tree.selectionModel().selectedIndexes()) def get_selected_words_with_children(self) -> Set: words = set() for index in self.__tree.selectionModel().selectedIndexes(): item: QStandardItem = self.__model.itemFromIndex(index) words.update(_model_to_words(item)) return words def get_data(self, with_selection=False) -> Union[Dict, OntoType]: selection = self.__tree.selectionModel().selectedIndexes() return _model_to_tree(self.__root, selection, with_selection) def set_data(self, data: Dict, keep_history: bool = False): if not keep_history: self.__stack = [] self.__stack_index = -1 self._set_data(data) self._push_data() def _set_data(self, data: Dict): self.clear() _tree_to_model(data, self.__root, self.__tree.selectionModel()) self.__tree.expandAll() def clear(self): if self.__model.hasChildren(): self.__model.removeRows(0, self.__model.rowCount()) def _enable_undo_redo(self): index = self.__stack_index self.__undo_action.setEnabled(index >= 1) self.__redo_action.setEnabled(index < len(self.__stack) - 1) def _push_data(self): self.__stack_index += 1 self.__stack = self.__stack[:self.__stack_index] self.__stack.append(self.get_data()) self._enable_undo_redo() def _set_from_stack(self): assert self.__stack_index < len(self.__stack) assert self.__stack_index >= 0 self._set_data(self.__stack[self.__stack_index]) self._enable_undo_redo() self.dataChanged.emit()
class OWBatchNorm(OWWidget): name = "Batch Effect Removal" description = "Batch effect normalization on Single Cell data set." icon = "icons/BatchEffectRemoval.svg" priority = 230 class Inputs: data = Input("Data", Table) class Outputs: data = Output("Data", Table) class Error(OWWidget.Error): general_error = Msg({}) discrete_attributes = Msg("Data with discrete attributes " "can not be processed.") class Warning(OWWidget.Warning): missing_values = Msg("Missing values have been replaced with 0.") negative_values = Msg("Unable to use current settings due " "to negative values in data.") resizing_enabled = False want_main_area = False settingsHandler = PerfectDomainContextHandler() batch_vars = ContextSetting([]) link_method = Setting(LinkMethod.IDENTITY_LINK) skip_zeros = Setting(False) auto_commit = Setting(True) def __init__(self, parent=None): super().__init__(parent) self.data = None # Info infobox = gui.widgetBox(self.controlArea, "Info") self.info_label = gui.widgetLabel(infobox, "No data on input.") # Link method method_box = gui.widgetBox(self.controlArea, "Method") gui.comboBox(method_box, self, "link_method", items=LinkMethod.items(), callback=self.__link_method_changed) gui.separator(method_box) self.skip_zeros_check = gui.checkBox( method_box, self, "skip_zeros", "Skip zero expressions", enabled=self.link_method != LinkMethod.LOG_LINK, callback=lambda: self.commit()) # Batch Variable Selection header_shema = (("selected", ""), ("variable", "Variable"), ("count", "#"), ("score", "Score")) header_labels = labels = [label for _, label in header_shema] header = namedtuple("header", [tag for tag, _ in header_shema]) self.Header = header(*[index for index, _ in enumerate(labels)]) batch_box = gui.widgetBox(self.controlArea, "Batch Variable Selection") self.view = QTreeView() self.model = QStandardItemModel() self.model.itemChanged.connect(self.__selected_batch_vars_changed) self.model.setHorizontalHeaderLabels(header_labels) batch_box.layout().addWidget(self.view) self._setup_view() gui.auto_commit(self.controlArea, self, "auto_commit", "Apply", "Apply Automatically") def __link_method_changed(self): enable = self.link_method != LinkMethod.LOG_LINK self.skip_zeros_check.setEnabled(enable) if not enable: self.skip_zeros_check.setChecked(True) self.commit() def __selected_batch_vars_changed(self, item): if item.checkState(): self.batch_vars.append(item.data(VariableRole)) else: self.batch_vars.remove(item.data(VariableRole)) self.commit() def _setup_view(self): self.view.setModel(self.model) self.view.setSelectionMode(QTreeView.NoSelection) self.view.setSortingEnabled(True) self.view.setRootIsDecorated(False) self.view.setItemDelegateForColumn(self.Header.count, IntegralDelegate(self)) self.view.setItemDelegateForColumn(self.Header.score, RealDelegate(self)) self.view.header().setSectionResizeMode(QHeaderView.ResizeToContents) self.view.header().setStretchLastSection(False) self.view.header().setSectionResizeMode(self.Header.variable, QHeaderView.Stretch) self.view.setFocus() @Inputs.data def set_data(self, data): self.closeContext() self.clear() self.data = data self._setup_info_label() self._check_data() self.openContext(data) if self.data is not None: self.batch_vars = [data.domain[v.name] for v in self.batch_vars] self._setup_model() self.commit() def clear(self): self.batch_vars = [] if self.model: n_rows = self.model.rowCount() self.model.removeRows(0, n_rows) def _setup_info_label(self): text = "No data on input." if self.data is not None: domain, attrs = self.data.domain, self.data.domain.attributes text = "{} cells, {} genes\n".format(len(self.data), len(attrs)) text += "{} meta features".format(len(domain.metas)) \ if len(domain.metas) else "(no meta features)" self.info_label.setText(text) def _check_data(self): self.clear_messages() if self.data and self.data.domain.has_discrete_attributes(): self.data = None self.Error.discrete_attributes() if self.data and np.isnan(self.data.X).any(): self.data.X = np.nan_to_num(self.data.X) self.Warning.missing_values() def _setup_model(self): estimator = ScBatchScorer() for var in self.data.domain.class_vars + self.data.domain.metas: if not var.is_primitive(): continue try: score = float(estimator.score_data(self.data, var)) except Exception: score = np.nan self.model.appendRow([ self.__selected_item(var), self.__variable_item(var), self.__count_item(var), self.__score_item(score) ]) def __selected_item(self, var): item = QStandardItem() item.setData(var, VariableRole) item.setCheckable(True) select = var in self.batch_vars item.setCheckState(Qt.Checked if select else Qt.Unchecked) item.setEditable(False) return item def __variable_item(self, var): item = QStandardItem() item.setData(var.name, Qt.DisplayRole) item.setData(gui.attributeIconDict[var], Qt.DecorationRole) item.setEditable(False) return item def __count_item(self, var): item = QStandardItem() if var.is_discrete: item.setData(len(var.values), Qt.DisplayRole) item.setEditable(False) return item def __score_item(self, score): item = QStandardItem() item.setData(score, Qt.DisplayRole) item.setEditable(False) return item def commit(self): data = None self.Error.general_error.clear() self.Warning.negative_values.clear() if self.data is not None: if (self.data.X < 0).any() and self.skip_zeros: self.Warning.negative_values() data = self.data else: try: data = SCBatchNormalizer( LinkMethod.items()[self.link_method], self.skip_zeros, self.batch_vars)(self.data) except Exception as e: self.Error.general_error(str(e)) data = None self.Outputs.data.send(data) def send_report(self): method = LinkMethod.items()[self.link_method] if self.skip_zeros: method += " (Skip zero expressions)" variables = ", ".join([v.name for v in self.batch_vars]) \ if self.batch_vars else "None" self.report_items("", [("Method", method), ("Batch variable selection", variables)])