class MemUsageDialog(QDialog): def __init__(self, parent=None, update=True): QDialog.__init__(self, parent=parent) layout = QVBoxLayout() self.tree = QTreeWidget() layout.addWidget(self.tree) self.setLayout(layout) self._mgr = CacheMemoryManager() self._tracked_caches = {} # tree setup code self.tree.setHeaderLabels( ["cache", "memory", "roi", "dtype", "type", "info", "id"]) self._idIndex = self.tree.columnCount() - 1 self.tree.setColumnHidden(self._idIndex, True) self.tree.setSortingEnabled(True) self.tree.clear() self._root = TreeNode() # refresh every x seconds (see showEvent()) self.timer = QTimer(self) if update: self.timer.timeout.connect(self._updateReport) def _updateReport(self): # we keep track of dirty reports so we just have to update the tree # instead of reconstructing it reports = [] for c in self._mgr.getFirstClassCaches(): r = MemInfoNode() c.generateReport(r) reports.append(r) self._root.handleChildrenReports(reports, root=self.tree.invisibleRootItem()) def hideEvent(self, event): self.timer.stop() def showEvent(self, show): # update once so we don't have to wait for initial report self._updateReport() # update every 5 sec. self.timer.start(5 * 1000)
class MemUsageDialog(QDialog): def __init__(self, parent=None, update=True): QDialog.__init__(self, parent=parent) layout = QVBoxLayout() self.tree = QTreeWidget() layout.addWidget(self.tree) self.setLayout(layout) self._mgr = CacheMemoryManager() self._tracked_caches = {} # tree setup code self.tree.setHeaderLabels( ["cache", "memory", "roi", "dtype", "type", "info", "id"]) self._idIndex = self.tree.columnCount() - 1 self.tree.setColumnHidden(self._idIndex, True) self.tree.setSortingEnabled(True) self.tree.clear() self._root = TreeNode() # refresh every x seconds (see showEvent()) self.timer = QTimer(self) if update: self.timer.timeout.connect(self._updateReport) def _updateReport(self): # we keep track of dirty reports so we just have to update the tree # instead of reconstructing it reports = [] for c in self._mgr.getFirstClassCaches(): r = MemInfoNode() c.generateReport(r) reports.append(r) self._root.handleChildrenReports( reports, root=self.tree.invisibleRootItem()) def hideEvent(self, event): self.timer.stop() def showEvent(self, show): # update once so we don't have to wait for initial report self._updateReport() # update every 5 sec. self.timer.start(5*1000)
class OWGEODatasets(OWWidget): name = "GEO Data Sets" description = DESCRIPTION icon = "../widgets/icons/GEODataSets.svg" priority = PRIORITY inputs = [] outputs = [("Expression Data", Orange.data.Table)] settingsList = [ "outputRows", "mergeSpots", "gdsSelectionStates", "splitterSettings", "currentGds", "autoCommit", "datasetNames" ] outputRows = Setting(True) mergeSpots = Setting(True) gdsSelectionStates = Setting({}) currentGds = Setting(None) datasetNames = Setting({}) splitterSettings = 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' )) autoCommit = Setting(False) def __init__(self, parent=None, signalManager=None, name=" GEO Data Sets"): OWWidget.__init__(self, parent, signalManager, name) self.selectionChanged = False self.filterString = "" self.datasetName = "" ## GUI box = gui.widgetBox(self.controlArea, "Info", addSpace=True) self.infoBox = gui.widgetLabel(box, "Initializing\n\n") box = gui.widgetBox(self.controlArea, "Output", addSpace=True) gui.radioButtonsInBox(box, self, "outputRows", ["Genes in rows", "Samples in rows"], "Rows", callback=self.commitIf) gui.checkBox(box, self, "mergeSpots", "Merge spots of same gene", callback=self.commitIf) gui.separator(box) self.nameEdit = gui.lineEdit( box, self, "datasetName", "Data set name", tooltip="Override the default output data set name", callback=self.onNameEdited) self.nameEdit.setPlaceholderText("") if sys.version_info < (3, ): box = gui.widgetBox(self.controlArea, "Commit", addSpace=True) self.commitButton = gui.button(box, self, "Commit", callback=self.commit) cb = gui.checkBox(box, self, "autoCommit", "Commit on any change") gui.setStopper(self, self.commitButton, cb, "selectionChanged", self.commit) else: gui.auto_commit(self.controlArea, self, "autoCommit", "Commit", box="Commit") self.commitIf = self.commit gui.rubber(self.controlArea) gui.widgetLabel(self.mainArea, "Filter") self.filterLineEdit = QLineEdit(textChanged=self.filter) self.completer = TokenListCompleter(self, caseSensitivity=Qt.CaseInsensitive) self.filterLineEdit.setCompleter(self.completer) self.mainArea.layout().addWidget(self.filterLineEdit) splitter = QSplitter(Qt.Vertical, self.mainArea) self.mainArea.layout().addWidget(splitter) self.treeWidget = QTreeView(splitter) self.treeWidget.setSelectionMode(QTreeView.SingleSelection) self.treeWidget.setRootIsDecorated(False) self.treeWidget.setSortingEnabled(True) self.treeWidget.setAlternatingRowColors(True) self.treeWidget.setUniformRowHeights(True) self.treeWidget.setEditTriggers(QTreeView.NoEditTriggers) linkdelegate = LinkStyledItemDelegate(self.treeWidget) self.treeWidget.setItemDelegateForColumn(1, linkdelegate) self.treeWidget.setItemDelegateForColumn(8, linkdelegate) self.treeWidget.setItemDelegateForColumn( 0, gui.IndicatorItemDelegate(self.treeWidget, role=Qt.DisplayRole)) proxyModel = MySortFilterProxyModel(self.treeWidget) self.treeWidget.setModel(proxyModel) self.treeWidget.selectionModel().selectionChanged.connect( self.updateSelection) self.treeWidget.viewport().setMouseTracking(True) splitterH = QSplitter(Qt.Horizontal, splitter) box = gui.widgetBox(splitterH, "Description") self.infoGDS = gui.widgetLabel(box, "") self.infoGDS.setWordWrap(True) gui.rubber(box) box = gui.widgetBox(splitterH, "Sample Annotations") self.annotationsTree = QTreeWidget(box) self.annotationsTree.setHeaderLabels( ["Type (Sample annotations)", "Sample count"]) self.annotationsTree.setRootIsDecorated(True) box.layout().addWidget(self.annotationsTree) self.annotationsTree.itemChanged.connect( self.annotationSelectionChanged) self._annotationsUpdating = False self.splitters = splitter, splitterH for sp, setting in zip(self.splitters, self.splitterSettings): sp.splitterMoved.connect(self.splitterMoved) sp.restoreState(setting) self.searchKeys = [ "dataset_id", "title", "platform_organism", "description" ] self.gds = [] self.gds_info = None self.resize(1000, 600) self.setBlocking(True) self.setEnabled(False) self.progressBarInit() self._executor = ThreadExecutor() func = partial(get_gds_model, methodinvoke(self, "_setProgress", (float, ))) self._inittask = Task(function=func) self._inittask.finished.connect(self._initializemodel) self._executor.submit(self._inittask) self._datatask = None @Slot(float) def _setProgress(self, value): self.progressBarValue = value def _initializemodel(self): assert self.thread() is QThread.currentThread() model, self.gds_info, self.gds = self._inittask.result() model.setParent(self) proxy = self.treeWidget.model() proxy.setFilterKeyColumn(0) proxy.setFilterRole(TextFilterRole) proxy.setFilterCaseSensitivity(False) proxy.setFilterFixedString(self.filterString) proxy.setSourceModel(model) proxy.sort(0, Qt.DescendingOrder) self.progressBarFinished() self.setBlocking(False) self.setEnabled(True) filter_items = " ".join(gds[key] for gds in self.gds for key in self.searchKeys) tr_chars = ",.:;!?(){}[]_-+\\|/%#@$^&*<>~`" tr_table = str.maketrans(tr_chars, " " * len(tr_chars)) filter_items = filter_items.translate(tr_table) filter_items = sorted(set(filter_items.split(" "))) filter_items = [item for item in filter_items if len(item) > 3] self.completer.setTokenList(filter_items) if self.currentGds: current_id = self.currentGds["dataset_id"] gdss = [(i, qunpack(proxy.data(proxy.index(i, 1), Qt.DisplayRole))) for i in range(proxy.rowCount())] current = [i for i, data in gdss if data and data == current_id] if current: current_index = proxy.index(current[0], 0) self.treeWidget.selectionModel().select( current_index, QItemSelectionModel.Select | QItemSelectionModel.Rows) self.treeWidget.scrollTo(current_index, QTreeView.PositionAtCenter) for i in range(8): self.treeWidget.resizeColumnToContents(i) self.treeWidget.setColumnWidth( 1, min(self.treeWidget.columnWidth(1), 300)) self.treeWidget.setColumnWidth( 2, min(self.treeWidget.columnWidth(2), 200)) self.updateInfo() def updateInfo(self): gds_info = self.gds_info text = ("%i datasets\n%i datasets cached\n" % (len(gds_info), len(glob.glob(serverfiles.localpath("GEO") + "/GDS*")))) filtered = self.treeWidget.model().rowCount() if len(self.gds) != filtered: text += ("%i after filtering") % filtered self.infoBox.setText(text) def updateSelection(self, *args): current = self.treeWidget.selectedIndexes() mapToSource = self.treeWidget.model().mapToSource current = [mapToSource(index).row() for index in current] if current: self.currentGds = self.gds[current[0]] self.setAnnotations(self.currentGds) self.infoGDS.setText(self.currentGds.get("description", "")) self.nameEdit.setPlaceholderText(self.currentGds["title"]) self.datasetName = \ self.datasetNames.get(self.currentGds["dataset_id"], "") else: self.currentGds = None self.nameEdit.setPlaceholderText("") self.datasetName = "" self.commitIf() def setAnnotations(self, gds): self._annotationsUpdating = True self.annotationsTree.clear() annotations = defaultdict(set) subsetscount = {} for desc in gds["subsets"]: annotations[desc["type"]].add(desc["description"]) subsetscount[desc["description"]] = str(len(desc["sample_id"])) for type, subsets in annotations.items(): key = (gds["dataset_id"], type) subsetItem = QTreeWidgetItem(self.annotationsTree, [type]) subsetItem.setFlags(subsetItem.flags() | Qt.ItemIsUserCheckable | Qt.ItemIsTristate) subsetItem.setCheckState( 0, self.gdsSelectionStates.get(key, Qt.Checked)) subsetItem.key = key for subset in subsets: key = (gds["dataset_id"], type, subset) item = QTreeWidgetItem( subsetItem, [subset, subsetscount.get(subset, "")]) item.setFlags(item.flags() | Qt.ItemIsUserCheckable) item.setCheckState( 0, self.gdsSelectionStates.get(key, Qt.Checked)) item.key = key self._annotationsUpdating = False self.annotationsTree.expandAll() for i in range(self.annotationsTree.columnCount()): self.annotationsTree.resizeColumnToContents(i) def annotationSelectionChanged(self, item, column): if self._annotationsUpdating: return for i in range(self.annotationsTree.topLevelItemCount()): item = self.annotationsTree.topLevelItem(i) self.gdsSelectionStates[item.key] = item.checkState(0) for j in range(item.childCount()): child = item.child(j) self.gdsSelectionStates[child.key] = child.checkState(0) def filter(self): filter_string = unicode(self.filterLineEdit.text()) proxyModel = self.treeWidget.model() if proxyModel: strings = filter_string.lower().strip().split() proxyModel.setFilterFixedStrings(strings) self.updateInfo() def selectedSamples(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. """ samples = [] unused_types = [] used_types = [] for stype in childiter(self.annotationsTree.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))) return samples, used_types def commitIf(self): if self.autoCommit: self.commit() else: self.selectionChanged = True @Slot(int, int) def progressCompleted(self, value, total): if total > 0: self.progressBarSet(100. * value / total, processEvents=False) else: pass # TODO: report 'indeterminate progress' def commit(self): if self.currentGds: self.error(0) sample_type = None self.progressBarInit(processEvents=None) _, groups = self.selectedSamples() if len(groups) == 1 and self.outputRows: sample_type = groups[0] self.setEnabled(False) self.setBlocking(True) progress = methodinvoke(self, "progressCompleted", (int, int)) def get_data(gds_id, report_genes, transpose, sample_type, title): gds_ensure_downloaded(gds_id, progress) gds = geo.GDS(gds_id) data = gds.getdata(report_genes=report_genes, transpose=transpose, sample_type=sample_type) data.name = title return data get_data = partial(get_data, self.currentGds["dataset_id"], report_genes=self.mergeSpots, transpose=self.outputRows, sample_type=sample_type, title=self.datasetName or self.currentGds["title"]) self._datatask = Task(function=get_data) self._datatask.finished.connect(self._on_dataready) self._executor.submit(self._datatask) def _on_dataready(self): self.setEnabled(True) self.setBlocking(False) self.progressBarFinished(processEvents=False) try: data = self._datatask.result() except urlrequest.URLError as error: self.error(0, ("Error while connecting to the NCBI ftp server! " "'%s'" % error)) sys.excepthook(type(error), error, getattr(error, "__traceback__")) return finally: self._datatask = None data_name = data.name samples, _ = self.selectedSamples() self.warning(0) message = None if self.outputRows: def samplesinst(ex): out = [] for meta in data.domain.metas: out.append((meta.name, ex[meta].value)) if data.domain.class_var.name != 'class': out.append((data.domain.class_var.name, ex[data.domain.class_var].value)) return out samples = set(samples) mask = [samples.issuperset(samplesinst(ex)) for ex in data] data = data[numpy.array(mask, dtype=bool)] if len(data) == 0: message = "No samples with selected sample annotations." else: samples = set(samples) domain = Orange.data.Domain([ attr for attr in data.domain.attributes if samples.issuperset(attr.attributes.items()) ], data.domain.class_var, data.domain.metas) # domain.addmetas(data.domain.getmetas()) if len(domain.attributes) == 0: message = "No samples with selected sample annotations." stypes = set(s[0] for s in samples) for attr in domain.attributes: attr.attributes = dict( (key, value) for key, value in attr.attributes.items() if key in stypes) data = Orange.data.Table(domain, data) if message is not None: self.warning(0, message) data_hints.set_hint(data, "taxid", self.currentGds.get("taxid", ""), 10.0) data_hints.set_hint(data, "genesinrows", self.outputRows, 10.0) data.name = data_name self.send("Expression Data", data) model = self.treeWidget.model().sourceModel() row = self.gds.index(self.currentGds) model.setData(model.index(row, 0), " ", Qt.DisplayRole) self.updateInfo() self.selectionChanged = False def splitterMoved(self, *args): self.splitterSettings = [ bytes(sp.saveState()) for sp in self.splitters ] def send_report(self): self.report_items("GEO Dataset", [("ID", self.currentGds['dataset_id']), ("Title", self.currentGds['title']), ("Organism", self.currentGds['sample_organism'])]) self.report_items("Data", [("Samples", self.currentGds['sample_count']), ("Features", self.currentGds['feature_count']), ("Genes", self.currentGds['gene_count'])]) self.report_name("Sample annotations") subsets = defaultdict(list) for subset in self.currentGds['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>" def onDeleteWidget(self): if self._inittask: self._inittask.future().cancel() self._inittask.finished.disconnect(self._initializemodel) if self._datatask: self._datatask.future().cancel() self._datatask.finished.disconnect(self._on_dataready) self._executor.shutdown(wait=False) super(OWGEODatasets, self).onDeleteWidget() def onNameEdited(self): if self.currentGds: gds_id = self.currentGds["dataset_id"] self.datasetNames[gds_id] = unicode(self.nameEdit.text()) self.commitIf()
class OWGEODatasets(OWWidget): name = "GEO Data Sets" description = DESCRIPTION icon = "../widgets/icons/GEODataSets.svg" priority = PRIORITY inputs = [] outputs = [("Expression Data", Orange.data.Table)] settingsList = ["outputRows", "mergeSpots", "gdsSelectionStates", "splitterSettings", "currentGds", "autoCommit", "datasetNames"] outputRows = Setting(False) mergeSpots = Setting(True) gdsSelectionStates = Setting({}) currentGds = Setting(None) datasetNames = Setting({}) splitterSettings = 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') ) autoCommit = Setting(True) def __init__(self, parent=None, signalManager=None, name=" GEO Data Sets"): OWWidget.__init__(self, parent, signalManager, name) self.selectionChanged = False self.filterString = "" self.datasetName = "" ## GUI box = gui.widgetBox(self.controlArea, "Info", addSpace=True) self.infoBox = gui.widgetLabel(box, "Initializing\n\n") box = gui.widgetBox(self.controlArea, "Output", addSpace=True) gui.radioButtonsInBox(box, self, "outputRows", ["Genes or spots", "Samples"], "Rows", callback=self.commitIf) gui.checkBox(box, self, "mergeSpots", "Merge spots of same gene", callback=self.commitIf) gui.separator(box) self.nameEdit = gui.lineEdit( box, self, "datasetName", "Data set name", tooltip="Override the default output data set name", callback=self.onNameEdited ) self.nameEdit.setPlaceholderText("") if sys.version_info < (3, ): box = gui.widgetBox(self.controlArea, "Commit", addSpace=True) self.commitButton = gui.button( box, self, "Commit", callback=self.commit) cb = gui.checkBox(box, self, "autoCommit", "Commit on any change") gui.setStopper(self, self.commitButton, cb, "selectionChanged", self.commit) else: gui.auto_commit(self.controlArea, self, "autoCommit", "Commit", box="Commit") gui.rubber(self.controlArea) # self.filterLineEdit = OWGUIEx.lineEditHint( # self.mainArea, self, "filterString", "Filter", # caseSensitive=False, matchAnywhere=True, # callback=self.filter, delimiters=" ") gui.widgetLabel(self.mainArea, "Filter") self.filterLineEdit = QLineEdit( textChanged=self.filter ) self.completer = QCompleter( caseSensitivity=Qt.CaseInsensitive ) self.completer.setModel(QStringListModel(self)) self.filterLineEdit.setCompleter(self.completer) self.mainArea.layout().addWidget(self.filterLineEdit) splitter = QSplitter(Qt.Vertical, self.mainArea) self.mainArea.layout().addWidget(splitter) self.treeWidget = QTreeView(splitter) self.treeWidget.setSelectionMode(QTreeView.SingleSelection) self.treeWidget.setRootIsDecorated(False) self.treeWidget.setSortingEnabled(True) self.treeWidget.setAlternatingRowColors(True) self.treeWidget.setUniformRowHeights(True) self.treeWidget.setEditTriggers(QTreeView.NoEditTriggers) linkdelegate = LinkStyledItemDelegate(self.treeWidget) self.treeWidget.setItemDelegateForColumn(1, linkdelegate) self.treeWidget.setItemDelegateForColumn(8, linkdelegate) self.treeWidget.setItemDelegateForColumn( 0, gui.IndicatorItemDelegate(self.treeWidget, role=Qt.DisplayRole)) proxyModel = MySortFilterProxyModel(self.treeWidget) self.treeWidget.setModel(proxyModel) self.treeWidget.selectionModel().selectionChanged.connect( self.updateSelection ) self.treeWidget.viewport().setMouseTracking(True) splitterH = QSplitter(Qt.Horizontal, splitter) box = gui.widgetBox(splitterH, "Description") self.infoGDS = gui.widgetLabel(box, "") self.infoGDS.setWordWrap(True) gui.rubber(box) box = gui.widgetBox(splitterH, "Sample Annotations") self.annotationsTree = QTreeWidget(box) self.annotationsTree.setHeaderLabels( ["Type (Sample annotations)", "Sample count"] ) self.annotationsTree.setRootIsDecorated(True) box.layout().addWidget(self.annotationsTree) self.annotationsTree.itemChanged.connect( self.annotationSelectionChanged ) self._annotationsUpdating = False self.splitters = splitter, splitterH for sp, setting in zip(self.splitters, self.splitterSettings): sp.splitterMoved.connect(self.splitterMoved) sp.restoreState(setting) self.searchKeys = ["dataset_id", "title", "platform_organism", "description"] self.gds = [] self.gds_info = None self.resize(1000, 600) self.setBlocking(True) self.setEnabled(False) self.progressBarInit() self._executor = ThreadExecutor() func = partial(get_gds_model, methodinvoke(self, "_setProgress", (float,))) self._inittask = Task(function=func) self._inittask.finished.connect(self._initializemodel) self._executor.submit(self._inittask) self._datatask = None @Slot(float) def _setProgress(self, value): self.progressBarValue = value def _initializemodel(self): assert self.thread() is QThread.currentThread() model, self.gds_info, self.gds = self._inittask.result() model.setParent(self) proxy = self.treeWidget.model() proxy.setFilterKeyColumn(0) proxy.setFilterRole(TextFilterRole) proxy.setFilterCaseSensitivity(False) proxy.setFilterFixedString(self.filterString) proxy.setSourceModel(model) proxy.sort(0, Qt.DescendingOrder) self.progressBarFinished() self.setBlocking(False) self.setEnabled(True) filter_items = " ".join( gds[key] for gds in self.gds for key in self.searchKeys ) tr_chars = ",.:;!?(){}[]_-+\\|/%#@$^&*<>~`" tr_table = str.maketrans(tr_chars, " " * len(tr_chars)) filter_items = filter_items.translate(tr_table) filter_items = sorted(set(filter_items.split(" "))) filter_items = [item for item in filter_items if len(item) > 3] self.completer.model().setStringList(filter_items) # self.filterLineEdit.setItems(filter_items) if self.currentGds: current_id = self.currentGds["dataset_id"] gdss = [(i, qunpack(proxy.data(proxy.index(i, 1), Qt.DisplayRole))) for i in range(proxy.rowCount())] current = [i for i, data in gdss if data and data == current_id] if current: current_index = proxy.index(current[0], 0) self.treeWidget.selectionModel().select( current_index, QItemSelectionModel.Select | QItemSelectionModel.Rows ) self.treeWidget.scrollTo( current_index, QTreeView.PositionAtCenter) for i in range(8): self.treeWidget.resizeColumnToContents(i) self.treeWidget.setColumnWidth( 1, min(self.treeWidget.columnWidth(1), 300)) self.treeWidget.setColumnWidth( 2, min(self.treeWidget.columnWidth(2), 200)) self.updateInfo() def updateInfo(self): gds_info = self.gds_info text = ("%i datasets\n%i datasets cached\n" % (len(gds_info), len(glob.glob(serverfiles.localpath("GEO") + "/GDS*")))) filtered = self.treeWidget.model().rowCount() if len(self.gds) != filtered: text += ("%i after filtering") % filtered self.infoBox.setText(text) def updateSelection(self, *args): current = self.treeWidget.selectedIndexes() mapToSource = self.treeWidget.model().mapToSource current = [mapToSource(index).row() for index in current] if current: self.currentGds = self.gds[current[0]] self.setAnnotations(self.currentGds) self.infoGDS.setText(self.currentGds.get("description", "")) self.nameEdit.setPlaceholderText(self.currentGds["title"]) self.datasetName = \ self.datasetNames.get(self.currentGds["dataset_id"], "") else: self.currentGds = None self.nameEdit.setPlaceholderText("") self.datasetName = "" self.commitIf() def setAnnotations(self, gds): self._annotationsUpdating = True self.annotationsTree.clear() annotations = defaultdict(set) subsetscount = {} for desc in gds["subsets"]: annotations[desc["type"]].add(desc["description"]) subsetscount[desc["description"]] = str(len(desc["sample_id"])) for type, subsets in annotations.items(): key = (gds["dataset_id"], type) subsetItem = QTreeWidgetItem(self.annotationsTree, [type]) subsetItem.setFlags(subsetItem.flags() | Qt.ItemIsUserCheckable | Qt.ItemIsTristate) subsetItem.setCheckState( 0, self.gdsSelectionStates.get(key, Qt.Checked) ) subsetItem.key = key for subset in subsets: key = (gds["dataset_id"], type, subset) item = QTreeWidgetItem( subsetItem, [subset, subsetscount.get(subset, "")] ) item.setFlags(item.flags() | Qt.ItemIsUserCheckable) item.setCheckState( 0, self.gdsSelectionStates.get(key, Qt.Checked) ) item.key = key self._annotationsUpdating = False self.annotationsTree.expandAll() for i in range(self.annotationsTree.columnCount()): self.annotationsTree.resizeColumnToContents(i) def annotationSelectionChanged(self, item, column): if self._annotationsUpdating: return for i in range(self.annotationsTree.topLevelItemCount()): item = self.annotationsTree.topLevelItem(i) self.gdsSelectionStates[item.key] = item.checkState(0) for j in range(item.childCount()): child = item.child(j) self.gdsSelectionStates[child.key] = child.checkState(0) def filter(self): filter_string = unicode(self.filterLineEdit.text()) proxyModel = self.treeWidget.model() if proxyModel: strings = filter_string.lower().strip().split() proxyModel.setFilterFixedStrings(strings) self.updateInfo() def selectedSamples(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. """ samples = [] unused_types = [] used_types = [] for stype in childiter(self.annotationsTree.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))) return samples, used_types def commitIf(self): if self.autoCommit: self.commit() else: self.selectionChanged = True def commit(self): if self.currentGds: self.error(0) sample_type = None self.progressBarInit() self.progressBarSet(10) _, groups = self.selectedSamples() if len(groups) == 1 and self.outputRows: sample_type = groups[0] self.setEnabled(False) self.setBlocking(True) def get_data(gds_id, report_genes, transpose, sample_type, title): gds = geo.GDS(gds_id) data = gds.getdata( report_genes=report_genes, transpose=transpose, sample_type=sample_type ) data.name = title return data get_data = partial( get_data, self.currentGds["dataset_id"], report_genes=self.mergeSpots, transpose=self.outputRows, sample_type=sample_type, title=self.datasetName or self.currentGds["title"] ) self._datatask = Task(function=get_data) self._datatask.finished.connect(self._on_dataready) self._executor.submit(self._datatask) def _on_dataready(self): self.setEnabled(True) self.setBlocking(False) self.progressBarSet(50) try: data = self._datatask.result() except urlrequest.URLError as error: self.error(0, "Error while connecting to the NCBI ftp server! %r" % error) self._datatask = None self.progressBarFinished() return self._datatask = None data_name = data.name samples, _ = self.selectedSamples() self.warning(0) message = None if self.outputRows: def samplesinst(ex): out = [] for meta in data.domain.metas: out.append((meta.name, ex[meta].value)) if data.domain.class_var.name != 'class': out.append((data.domain.class_var.name, ex[data.domain.class_var].value)) return out samples = set(samples) mask = [samples.issuperset(samplesinst(ex)) for ex in data] data = data[numpy.array(mask, dtype=bool)] if len(data) == 0: message = "No samples with selected sample annotations." else: samples = set(samples) domain = Orange.data.Domain( [attr for attr in data.domain.attributes if samples.issuperset(attr.attributes.items())], data.domain.class_var, data.domain.metas ) # domain.addmetas(data.domain.getmetas()) if len(domain.attributes) == 0: message = "No samples with selected sample annotations." stypes = set(s[0] for s in samples) for attr in domain.attributes: attr.attributes = dict( (key, value) for key, value in attr.attributes.items() if key in stypes ) data = Orange.data.Table(domain, data) if message is not None: self.warning(0, message) data_hints.set_hint(data, "taxid", self.currentGds.get("taxid", ""), 10.0) data_hints.set_hint(data, "genesinrows", self.outputRows, 10.0) self.progressBarFinished() data.name = data_name self.send("Expression Data", data) model = self.treeWidget.model().sourceModel() row = self.gds.index(self.currentGds) model.setData(model.index(row, 0), " ", Qt.DisplayRole) self.updateInfo() self.selectionChanged = False def splitterMoved(self, *args): self.splitterSettings = [str(sp.saveState()) for sp in self.splitters] def onDeleteWidget(self): if self._inittask: self._inittask.future().cancel() self._inittask.finished.disconnect(self._initializemodel) if self._datatask: self._datatask.future().cancel() self._datatask.finished.disconnect(self._on_dataready) self._executor.shutdown(wait=False) super(OWGEODatasets, self).onDeleteWidget() def onNameEdited(self): if self.currentGds: gds_id = self.currentGds["dataset_id"] self.datasetNames[gds_id] = unicode(self.nameEdit.text()) self.commitIf()
class OWItemsets(widget.OWWidget): name = 'Frequent Itemsets' description = 'Explore sets of items that frequently appear together.' icon = 'icons/FrequentItemsets.svg' priority = 10 inputs = [("Data", Table, 'set_data')] outputs = [(Output.DATA, Table)] minSupport = settings.Setting(30) maxItemsets = settings.Setting(10000) filterSearch = settings.Setting(True) autoFind = settings.Setting(False) autoSend = settings.Setting(True) filterKeywords = settings.Setting('') filterMinItems = settings.Setting(1) filterMaxItems = settings.Setting(10000) UserAdviceMessages = [ widget.Message( 'Itemset are listed in item-sorted order, i.e. ' 'an itemset containing A and B is only listed once, as ' 'A > B (and not also B > A).', 'itemsets-order', widget.Message.Warning), widget.Message( 'To select all the itemsets that are descendants of ' '(include) some item X (i.e. the whole subtree), you ' 'can fold the subtree at that item and then select it.', 'itemsets-order', widget.Message.Information) ] def __init__(self): self._is_running = False self.isRegexMatch = lambda x: True self.tree = QTreeWidget(self.mainArea, columnCount=2, allColumnsShowFocus=True, alternatingRowColors=True, selectionMode=QTreeWidget.ExtendedSelection, uniformRowHeights=True) self.tree.setHeaderLabels(["Itemsets", "Support", "%"]) self.tree.header().setStretchLastSection(True) self.tree.itemSelectionChanged.connect(self.selectionChanged) self.mainArea.layout().addWidget(self.tree) box = gui.widgetBox(self.controlArea, "Info") self.nItemsets = self.nSelectedExamples = self.nSelectedItemsets = '' gui.label(box, self, "Number of itemsets: %(nItemsets)s") gui.label(box, self, "Selected itemsets: %(nSelectedItemsets)s") gui.label(box, self, "Selected examples: %(nSelectedExamples)s") hbox = gui.widgetBox(box, orientation='horizontal') gui.button(hbox, self, "Expand all", callback=self.tree.expandAll) gui.button(hbox, self, "Collapse all", callback=self.tree.collapseAll) box = gui.widgetBox(self.controlArea, 'Find itemsets') gui.valueSlider(box, self, 'minSupport', values=[.0001, .0005, .001, .005, .01, .05, .1, .5] + list(range(1, 101)), label='Minimal support:', labelFormat="%g%%", callback=lambda: self.find_itemsets()) gui.hSlider(box, self, 'maxItemsets', minValue=10000, maxValue=100000, step=10000, label='Max. number of itemsets:', labelFormat="%d", callback=lambda: self.find_itemsets()) self.button = gui.auto_commit(box, self, 'autoFind', 'Find itemsets', commit=self.find_itemsets) box = gui.widgetBox(self.controlArea, 'Filter itemsets') gui.lineEdit(box, self, 'filterKeywords', 'Contains:', callback=self.filter_change, orientation='horizontal', tooltip='A comma or space-separated list of regular ' 'expressions.') hbox = gui.widgetBox(box, orientation='horizontal') gui.spin(hbox, self, 'filterMinItems', 1, 998, label='Min. items:', callback=self.filter_change) gui.spin(hbox, self, 'filterMaxItems', 2, 999, label='Max. items:', callback=self.filter_change) gui.checkBox(box, self, 'filterSearch', label='Apply these filters in search', tooltip='If checked, the itemsets are filtered according ' 'to these filter conditions already in the search ' 'phase. \nIf unchecked, the only filters applied ' 'during search are the ones above, ' 'and the itemsets are \nfiltered afterwards only for ' 'display, i.e. only the matching itemsets are shown.') gui.rubber(hbox) gui.rubber(self.controlArea) gui.auto_commit(self.controlArea, self, 'autoSend', 'Send selection') self.filter_change() ITEM_DATA_ROLE = Qt.UserRole + 1 def selectionChanged(self): X = self.X mapping = self.onehot_mapping instances = set() where = np.where def whole_subtree(node): yield node for i in range(node.childCount()): yield from whole_subtree(node.child(i)) def itemset(node): while node: yield node.data(0, self.ITEM_DATA_ROLE) node = node.parent() def selection_ranges(node): n_children = node.childCount() if n_children: yield (self.tree.indexFromItem(node.child(0)), self.tree.indexFromItem(node.child(n_children - 1))) for i in range(n_children): yield from selection_ranges(node.child(i)) nSelectedItemsets = 0 item_selection = QItemSelection() for node in self.tree.selectedItems(): nodes = (node, ) if node.isExpanded() else whole_subtree(node) if not node.isExpanded(): for srange in selection_ranges(node): item_selection.select(*srange) for node in nodes: nSelectedItemsets += 1 cols, vals = zip(*(mapping[i] for i in itemset(node))) if issparse(X): rows = (len(cols) == np.bincount( (X[:, cols] != 0).indices, minlength=X.shape[0])).nonzero()[0] else: rows = where((X[:, cols] == vals).all(axis=1))[0] instances.update(rows) self.tree.itemSelectionChanged.disconnect(self.selectionChanged) self.tree.selectionModel().select( item_selection, QItemSelectionModel.Select | QItemSelectionModel.Rows) self.tree.itemSelectionChanged.connect(self.selectionChanged) self.nSelectedExamples = len(instances) self.nSelectedItemsets = nSelectedItemsets self.output = self.data[sorted(instances)] or None self.commit() def commit(self): self.send(Output.DATA, self.output) def filter_change(self): self.warning(9) try: isRegexMatch = self.isRegexMatch = re.compile( '|'.join( i.strip() for i in re.split('(,|\s)+', self.filterKeywords.strip()) if i.strip()), re.IGNORECASE).search except Exception as e: self.warning(9, 'Error in regular expression: {}'.format(e.args[0])) isRegexMatch = self.isRegexMatch = lambda x: True def hide(node, depth, has_kw): if not has_kw: has_kw = isRegexMatch(node.text(0)) hidden = ( sum( hide(node.child(i), depth + 1, has_kw) for i in range(node.childCount())) == node.childCount() if node.childCount() else (not has_kw or not self.filterMinItems <= depth <= self.filterMaxItems)) node.setHidden(hidden) return hidden hide(self.tree.invisibleRootItem(), 0, False) class TreeWidgetItem(QTreeWidgetItem): def data(self, column, role): """Construct lazy tooltips""" if role != Qt.ToolTipRole: return super().data(column, role) tooltip = [] while self: tooltip.append(self.text(0)) self = self.parent() return ' '.join(reversed(tooltip)) def find_itemsets(self): if self.data is None: return if self._is_running: return self._is_running = True data = self.data self.tree.clear() self.tree.setUpdatesEnabled(False) self.tree.blockSignals(True) class ItemDict(dict): def __init__(self, item): self.item = item top = ItemDict(self.tree.invisibleRootItem()) X, mapping = OneHot.encode(data) self.onehot_mapping = mapping ITEM_FMT = '{}' if issparse(data.X) else '{}={}' names = { item: ITEM_FMT.format(var.name, val) for item, var, val in OneHot.decode(mapping.keys(), data, mapping) } nItemsets = 0 filterSearch = self.filterSearch filterMinItems, filterMaxItems = self.filterMinItems, self.filterMaxItems isRegexMatch = self.isRegexMatch # Find itemsets and populate the TreeView with self.progressBar(self.maxItemsets + 1) as progress: for itemset, support in frequent_itemsets(X, self.minSupport / 100): if filterSearch and not filterMinItems <= len( itemset) <= filterMaxItems: continue parent = top first_new_item = None itemset_matches_filter = False for item in sorted(itemset): name = names[item] if filterSearch and not itemset_matches_filter: itemset_matches_filter = isRegexMatch(name) child = parent.get(name) if child is None: try: wi = self.TreeWidgetItem(parent.item, [ name, str(support), '{:.4g}'.format( 100 * support / len(data)) ]) except RuntimeError: # FIXME: When autoFind was in effect and the support # slider was moved, this line excepted with: # RuntimeError: wrapped C/C++ object of type # TreeWidgetItem has been deleted return wi.setData(0, self.ITEM_DATA_ROLE, item) child = parent[name] = ItemDict(wi) if first_new_item is None: first_new_item = (parent, name) parent = child if filterSearch and not itemset_matches_filter: parent, name = first_new_item parent.item.removeChild(parent[name].item) del parent[name].item del parent[name] else: nItemsets += 1 progress.advance() if nItemsets >= self.maxItemsets: break if not filterSearch: self.filter_change() self.nItemsets = nItemsets self.nSelectedItemsets = 0 self.nSelectedExamples = 0 self.tree.expandAll() for i in range(self.tree.columnCount()): self.tree.resizeColumnToContents(i) self.tree.setUpdatesEnabled(True) self.tree.blockSignals(False) self._is_running = False def set_data(self, data): self.data = data is_error = False if data is not None: self.warning(0) self.error(1) self.button.setDisabled(False) self.X = data.X if issparse(data.X): self.X = data.X.tocsc() else: if not data.domain.has_discrete_attributes(): self.error( 1, 'Discrete features required but data has none.') is_error = True self.button.setDisabled(True) elif data.domain.has_continuous_attributes(): self.warning( 0, 'Data has continuous attributes which will be skipped.' ) else: self.output = None self.commit() if self.autoFind and not is_error: self.find_itemsets()
class OWItemsets(widget.OWWidget): name = 'Frequent Itemsets' description = 'Explore sets of items that frequently appear together.' icon = 'icons/FrequentItemsets.svg' priority = 10 inputs = [("Data", Table, 'set_data')] outputs = [(Output.DATA, Table)] minSupport = settings.Setting(30) maxItemsets = settings.Setting(10000) filterSearch = settings.Setting(True) autoFind = settings.Setting(False) autoSend = settings.Setting(True) filterKeywords = settings.Setting('') filterMinItems = settings.Setting(1) filterMaxItems = settings.Setting(10000) UserAdviceMessages = [ widget.Message('Itemset are listed in item-sorted order, i.e. ' 'an itemset containing A and B is only listed once, as ' 'A > B (and not also B > A).', 'itemsets-order', widget.Message.Warning), widget.Message('To select all the itemsets that are descendants of ' '(include) some item X (i.e. the whole subtree), you ' 'can fold the subtree at that item and then select it.', 'itemsets-order', widget.Message.Information) ] def __init__(self): self.tree = QTreeWidget(self.mainArea, columnCount=2, allColumnsShowFocus=True, alternatingRowColors=True, selectionMode=QTreeWidget.ExtendedSelection, uniformRowHeights=True) self.tree.setHeaderLabels(["Itemsets", "Support", "%"]) self.tree.header().setStretchLastSection(True) self.tree.itemSelectionChanged.connect(self.selectionChanged) self.mainArea.layout().addWidget(self.tree) box = gui.widgetBox(self.controlArea, "Info") self.nItemsets = self.nSelectedExamples = self.nSelectedItemsets = '' gui.label(box, self, "Number of itemsets: %(nItemsets)s") gui.label(box, self, "Selected itemsets: %(nSelectedItemsets)s") gui.label(box, self, "Selected examples: %(nSelectedExamples)s") hbox = gui.widgetBox(box, orientation='horizontal') gui.button(hbox, self, "Expand all", callback=self.tree.expandAll) gui.button(hbox, self, "Collapse all", callback=self.tree.collapseAll) box = gui.widgetBox(self.controlArea, 'Find itemsets') gui.hSlider(box, self, 'minSupport', minValue=1, maxValue=100, label='Minimal support:', labelFormat="%d%%", callback=lambda: self.find_itemsets()) gui.hSlider(box, self, 'maxItemsets', minValue=10000, maxValue=100000, step=10000, label='Max. number of itemsets:', labelFormat="%d", callback=lambda: self.find_itemsets()) gui.checkBox(box, self, 'filterSearch', label='Apply below filters in search', tooltip='If checked, the itemsets are filtered according ' 'to below filter conditions already in the search ' 'phase. \nIf unchecked, the only filters applied ' 'during search are the ones above, ' 'and the itemsets are \nfiltered afterwards only for ' 'display, i.e. only the matching itemsets are shown.') self.button = gui.auto_commit( box, self, 'autoFind', 'Find itemsets', commit=self.find_itemsets) box = gui.widgetBox(self.controlArea, 'Filter itemsets') gui.lineEdit(box, self, 'filterKeywords', 'Contains:', callback=self.filter_change, orientation='horizontal', tooltip='A comma or space-separated list of regular ' 'expressions.') hbox = gui.widgetBox(box, orientation='horizontal') gui.spin(hbox, self, 'filterMinItems', 1, 998, label='Min. items:', callback=self.filter_change) gui.spin(hbox, self, 'filterMaxItems', 2, 999, label='Max. items:', callback=self.filter_change) gui.rubber(hbox) gui.rubber(self.controlArea) gui.auto_commit(self.controlArea, self, 'autoSend', 'Send selection') self.filter_change() def sendReport(self): self.reportSettings("Itemset statistics", [("Number of itemsets", self.nItemsets), ("Selected itemsets", self.nSelectedItemsets), ("Covered examples", self.nSelectedExamples), ]) self.reportSection("Itemsets") self.reportRaw(OWReport.reportTree(self.tree)) ITEM_DATA_ROLE = Qt.UserRole + 1 def selectionChanged(self): X = self.data.X mapping = self.onehot_mapping instances = set() where = np.where def whole_subtree(node): yield node for i in range(node.childCount()): yield from whole_subtree(node.child(i)) def itemset(node): while node: yield node.data(0, self.ITEM_DATA_ROLE) node = node.parent() def selection_ranges(node): n_children = node.childCount() if n_children: yield (self.tree.indexFromItem(node.child(0)), self.tree.indexFromItem(node.child(n_children - 1))) for i in range(n_children): yield from selection_ranges(node.child(i)) nSelectedItemsets = 0 item_selection = QItemSelection() for node in self.tree.selectedItems(): nodes = (node,) if node.isExpanded() else whole_subtree(node) if not node.isExpanded(): for srange in selection_ranges(node): item_selection.select(*srange) for node in nodes: nSelectedItemsets += 1 cols, vals = zip(*(mapping[i] for i in itemset(node))) instances.update(where((X[:, cols] == vals).all(axis=1))[0]) self.tree.itemSelectionChanged.disconnect(self.selectionChanged) self.tree.selectionModel().select(item_selection, QItemSelectionModel.Select | QItemSelectionModel.Rows) self.tree.itemSelectionChanged.connect(self.selectionChanged) self.nSelectedExamples = len(instances) self.nSelectedItemsets = nSelectedItemsets self.output = self.data[sorted(instances)] or None self.commit() def commit(self): self.send(Output.DATA, self.output) def filter_change(self): isRegexMatch = self.isRegexMatch = re.compile( '|'.join(i.strip() for i in re.split('(,|\s)+', self.filterKeywords.strip()) if i.strip())).search def hide(node, depth, has_kw): if not has_kw: has_kw = isRegexMatch(node.text(0)) hidden = (sum(hide(node.child(i), depth + 1, has_kw) for i in range(node.childCount())) == node.childCount() if node.childCount() else (not has_kw or not self.filterMinItems <= depth <= self.filterMaxItems)) node.setHidden(hidden) return hidden hide(self.tree.invisibleRootItem(), 0, False) class TreeWidgetItem(QTreeWidgetItem): def data(self, column, role): """Construct lazy tooltips""" if role != Qt.ToolTipRole: return super().data(column, role) tooltip = [] while self: tooltip.append(self.text(0)) self = self.parent() return ' '.join(reversed(tooltip)) def find_itemsets(self): if self.data is None: return data = self.data self.tree.clear() self.tree.setUpdatesEnabled(False) self.tree.blockSignals(True) class ItemDict(dict): def __init__(self, item): self.item = item top = ItemDict(self.tree.invisibleRootItem()) X, mapping = OneHot.encode(data) self.onehot_mapping = mapping names = {item: '{}={}'.format(var.name, val) for item, var, val in OneHot.decode(mapping.keys(), data, mapping)} nItemsets = 0 filterSearch = self.filterSearch filterMinItems, filterMaxItems = self.filterMinItems, self.filterMaxItems isRegexMatch = self.isRegexMatch # Find itemsets and populate the TreeView progress = gui.ProgressBar(self, self.maxItemsets + 1) for itemset, support in frequent_itemsets(X, self.minSupport / 100): if filterSearch and not filterMinItems <= len(itemset) <= filterMaxItems: continue parent = top first_new_item = None itemset_matches_filter = False for item in sorted(itemset): name = names[item] if filterSearch and not itemset_matches_filter: itemset_matches_filter = isRegexMatch(name) child = parent.get(name) if child is None: wi = self.TreeWidgetItem(parent.item, [name, str(support), '{:.1f}'.format(100 * support / len(data))]) wi.setData(0, self.ITEM_DATA_ROLE, item) child = parent[name] = ItemDict(wi) if first_new_item is None: first_new_item = (parent, name) parent = child if filterSearch and not itemset_matches_filter: parent, name = first_new_item parent.item.removeChild(parent[name].item) del parent[name].item del parent[name] else: nItemsets += 1 progress.advance() if nItemsets >= self.maxItemsets: break if not filterSearch: self.filter_change() self.nItemsets = nItemsets self.nSelectedItemsets = 0 self.nSelectedExamples = 0 self.tree.expandAll() for i in range(self.tree.columnCount()): self.tree.resizeColumnToContents(i) self.tree.setUpdatesEnabled(True) self.tree.blockSignals(False) progress.finish() def set_data(self, data): self.data = data if data is not None: self.warning(0, 'Data has continuous attributes which will be skipped.' if data.domain.has_continuous_attributes() else None) self.error(1, 'Discrete features required but data has none.' if not data.domain.has_discrete_attributes() else None) self.button.setDisabled(not data.domain.has_discrete_attributes()) if self.autoFind: self.find_itemsets()
class OWItemsets(widget.OWWidget): name = "Frequent Itemsets" description = "Explore sets of items that frequently appear together." icon = "icons/FrequentItemsets.svg" priority = 10 inputs = [("Data", Table, "set_data")] outputs = [(Output.DATA, Table)] minSupport = settings.Setting(30) maxItemsets = settings.Setting(10000) filterSearch = settings.Setting(True) autoFind = settings.Setting(False) autoSend = settings.Setting(True) filterKeywords = settings.Setting("") filterMinItems = settings.Setting(1) filterMaxItems = settings.Setting(10000) UserAdviceMessages = [ widget.Message( "Itemset are listed in item-sorted order, i.e. " "an itemset containing A and B is only listed once, as " "A > B (and not also B > A).", "itemsets-order", widget.Message.Warning, ), widget.Message( "To select all the itemsets that are descendants of " "(include) some item X (i.e. the whole subtree), you " "can fold the subtree at that item and then select it.", "itemsets-order", widget.Message.Information, ), ] def __init__(self): self._is_running = False self.isRegexMatch = lambda x: True self.tree = QTreeWidget( self.mainArea, columnCount=2, allColumnsShowFocus=True, alternatingRowColors=True, selectionMode=QTreeWidget.ExtendedSelection, uniformRowHeights=True, ) self.tree.setHeaderLabels(["Itemsets", "Support", "%"]) self.tree.header().setStretchLastSection(True) self.tree.itemSelectionChanged.connect(self.selectionChanged) self.mainArea.layout().addWidget(self.tree) box = gui.widgetBox(self.controlArea, "Info") self.nItemsets = self.nSelectedExamples = self.nSelectedItemsets = "" gui.label(box, self, "Number of itemsets: %(nItemsets)s") gui.label(box, self, "Selected itemsets: %(nSelectedItemsets)s") gui.label(box, self, "Selected examples: %(nSelectedExamples)s") hbox = gui.widgetBox(box, orientation="horizontal") gui.button(hbox, self, "Expand all", callback=self.tree.expandAll) gui.button(hbox, self, "Collapse all", callback=self.tree.collapseAll) box = gui.widgetBox(self.controlArea, "Find itemsets") gui.valueSlider( box, self, "minSupport", values=[0.0001, 0.0005, 0.001, 0.005, 0.01, 0.05, 0.1, 0.5] + list(range(1, 101)), label="Minimal support:", labelFormat="%g%%", callback=lambda: self.find_itemsets(), ) gui.hSlider( box, self, "maxItemsets", minValue=10000, maxValue=100000, step=10000, label="Max. number of itemsets:", labelFormat="%d", callback=lambda: self.find_itemsets(), ) self.button = gui.auto_commit(box, self, "autoFind", "Find itemsets", commit=self.find_itemsets) box = gui.widgetBox(self.controlArea, "Filter itemsets") gui.lineEdit( box, self, "filterKeywords", "Contains:", callback=self.filter_change, orientation="horizontal", tooltip="A comma or space-separated list of regular " "expressions.", ) hbox = gui.widgetBox(box, orientation="horizontal") gui.spin(hbox, self, "filterMinItems", 1, 998, label="Min. items:", callback=self.filter_change) gui.spin(hbox, self, "filterMaxItems", 2, 999, label="Max. items:", callback=self.filter_change) gui.checkBox( box, self, "filterSearch", label="Apply these filters in search", tooltip="If checked, the itemsets are filtered according " "to these filter conditions already in the search " "phase. \nIf unchecked, the only filters applied " "during search are the ones above, " "and the itemsets are \nfiltered afterwards only for " "display, i.e. only the matching itemsets are shown.", ) gui.rubber(hbox) gui.rubber(self.controlArea) gui.auto_commit(self.controlArea, self, "autoSend", "Send selection") self.filter_change() ITEM_DATA_ROLE = Qt.UserRole + 1 def selectionChanged(self): X = self.X mapping = self.onehot_mapping instances = set() where = np.where def whole_subtree(node): yield node for i in range(node.childCount()): yield from whole_subtree(node.child(i)) def itemset(node): while node: yield node.data(0, self.ITEM_DATA_ROLE) node = node.parent() def selection_ranges(node): n_children = node.childCount() if n_children: yield (self.tree.indexFromItem(node.child(0)), self.tree.indexFromItem(node.child(n_children - 1))) for i in range(n_children): yield from selection_ranges(node.child(i)) nSelectedItemsets = 0 item_selection = QItemSelection() for node in self.tree.selectedItems(): nodes = (node,) if node.isExpanded() else whole_subtree(node) if not node.isExpanded(): for srange in selection_ranges(node): item_selection.select(*srange) for node in nodes: nSelectedItemsets += 1 cols, vals = zip(*(mapping[i] for i in itemset(node))) if issparse(X): rows = (len(cols) == np.bincount((X[:, cols] != 0).indices, minlength=X.shape[0])).nonzero()[0] else: rows = where((X[:, cols] == vals).all(axis=1))[0] instances.update(rows) self.tree.itemSelectionChanged.disconnect(self.selectionChanged) self.tree.selectionModel().select(item_selection, QItemSelectionModel.Select | QItemSelectionModel.Rows) self.tree.itemSelectionChanged.connect(self.selectionChanged) self.nSelectedExamples = len(instances) self.nSelectedItemsets = nSelectedItemsets self.output = self.data[sorted(instances)] or None self.commit() def commit(self): self.send(Output.DATA, self.output) def filter_change(self): self.warning(9) try: isRegexMatch = self.isRegexMatch = re.compile( "|".join(i.strip() for i in re.split("(,|\s)+", self.filterKeywords.strip()) if i.strip()), re.IGNORECASE, ).search except Exception as e: self.warning(9, "Error in regular expression: {}".format(e.args[0])) isRegexMatch = self.isRegexMatch = lambda x: True def hide(node, depth, has_kw): if not has_kw: has_kw = isRegexMatch(node.text(0)) hidden = ( sum(hide(node.child(i), depth + 1, has_kw) for i in range(node.childCount())) == node.childCount() if node.childCount() else (not has_kw or not self.filterMinItems <= depth <= self.filterMaxItems) ) node.setHidden(hidden) return hidden hide(self.tree.invisibleRootItem(), 0, False) class TreeWidgetItem(QTreeWidgetItem): def data(self, column, role): """Construct lazy tooltips""" if role != Qt.ToolTipRole: return super().data(column, role) tooltip = [] while self: tooltip.append(self.text(0)) self = self.parent() return " ".join(reversed(tooltip)) def find_itemsets(self): if self.data is None: return if self._is_running: return self._is_running = True data = self.data self.tree.clear() self.tree.setUpdatesEnabled(False) self.tree.blockSignals(True) class ItemDict(dict): def __init__(self, item): self.item = item top = ItemDict(self.tree.invisibleRootItem()) X, mapping = OneHot.encode(data) self.onehot_mapping = mapping ITEM_FMT = "{}" if issparse(data.X) else "{}={}" names = { item: ITEM_FMT.format(var.name, val) for item, var, val in OneHot.decode(mapping.keys(), data, mapping) } nItemsets = 0 filterSearch = self.filterSearch filterMinItems, filterMaxItems = self.filterMinItems, self.filterMaxItems isRegexMatch = self.isRegexMatch # Find itemsets and populate the TreeView with self.progressBar(self.maxItemsets + 1) as progress: for itemset, support in frequent_itemsets(X, self.minSupport / 100): if filterSearch and not filterMinItems <= len(itemset) <= filterMaxItems: continue parent = top first_new_item = None itemset_matches_filter = False for item in sorted(itemset): name = names[item] if filterSearch and not itemset_matches_filter: itemset_matches_filter = isRegexMatch(name) child = parent.get(name) if child is None: try: wi = self.TreeWidgetItem( parent.item, [name, str(support), "{:.4g}".format(100 * support / len(data))] ) except RuntimeError: # FIXME: When autoFind was in effect and the support # slider was moved, this line excepted with: # RuntimeError: wrapped C/C++ object of type # TreeWidgetItem has been deleted return wi.setData(0, self.ITEM_DATA_ROLE, item) child = parent[name] = ItemDict(wi) if first_new_item is None: first_new_item = (parent, name) parent = child if filterSearch and not itemset_matches_filter: parent, name = first_new_item parent.item.removeChild(parent[name].item) del parent[name].item del parent[name] else: nItemsets += 1 progress.advance() if nItemsets >= self.maxItemsets: break if not filterSearch: self.filter_change() self.nItemsets = nItemsets self.nSelectedItemsets = 0 self.nSelectedExamples = 0 self.tree.expandAll() for i in range(self.tree.columnCount()): self.tree.resizeColumnToContents(i) self.tree.setUpdatesEnabled(True) self.tree.blockSignals(False) self._is_running = False def set_data(self, data): self.data = data is_error = False if data is not None: self.warning(0) self.error(1) self.button.setDisabled(False) self.X = data.X if issparse(data.X): self.X = data.X.tocsc() else: if not data.domain.has_discrete_attributes(): self.error(1, "Discrete features required but data has none.") is_error = True self.button.setDisabled(True) elif data.domain.has_continuous_attributes(): self.warning(0, "Data has continuous attributes which will be skipped.") else: self.output = None self.commit() if self.autoFind and not is_error: self.find_itemsets()
class MembersWidget(QWidget): def __init__(self, parent, logger): super(MembersWidget, self).__init__(parent) self.logger = logger layout = QVBoxLayout(self) layout.setSpacing(0) self.dropdown_members_dict = {} self.dropdown_members_model = DropdownModel(get_peers(), self.logger) self.dropdown_members = QComboBox(self) self.dropdown_members.setModel(self.dropdown_members_model) topLayout = QHBoxLayout() topLayout.setSpacing(10) topLayout.addWidget(self.dropdown_members, 1) self.requestLogsButton = QPushButton("Request Logfiles", self) topLayout.addWidget(self.requestLogsButton) layout.addLayout(topLayout) layout.addWidget(QLabel("Member Information:", self)) self.memberInformationTable = QTreeWidget(self) self.memberInformationTable.setMaximumHeight(65) self.memberInformationTable.setSelectionMode(QTreeWidget.NoSelection) layout.addWidget(self.memberInformationTable, 0) layout.addWidget(QLabel("Send Message:", self)) sendMessageLayout = QHBoxLayout() sendMessageLayout.setSpacing(10) messageInput = HistoryLineEdit(self, "Enter a message") self.sendMessageButton = QPushButton("Send", self) sendMessageLayout.addWidget(messageInput, 1) sendMessageLayout.addWidget(self.sendMessageButton) layout.addLayout(sendMessageLayout) layout.addWidget(QLabel("Log files:", self)) logSplitter = QSplitter(Qt.Horizontal, self) logListWidget = QWidget(self) logListLayout = QVBoxLayout(logListWidget) logListLayout.setContentsMargins(0, 0, 0, 0) self.log_tree_view = QTreeWidget(logSplitter) self.log_tree_view.setAlternatingRowColors(True) self.log_tree_view.setColumnCount(1) self.log_tree_view.setHeaderHidden(True) self.log_tree_view.setItemsExpandable(False) self.log_tree_view.setIndentation(0) logListLayout.addWidget(self.log_tree_view, 1) logListBottomLayout = QHBoxLayout() self.logSizeLabel = QLabel(logListWidget) logListBottomLayout.addWidget(self.logSizeLabel, 1) self.clearLogsButton = QPushButton("Clear", logListWidget) self.clearLogsButton.setEnabled(False) self.clearLogsButton.clicked.connect(self.clearLogs) logListBottomLayout.addWidget(self.clearLogsButton, 0) logListLayout.addLayout(logListBottomLayout) logSplitter.addWidget(logListWidget) self.log_area = QTextEdit(logListWidget) self.log_area.setLineWrapMode(QTextEdit.WidgetWidth) self.log_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) self.log_area.setReadOnly(True) logSplitter.addWidget(self.log_area) logSplitter.setStretchFactor(0, 0) logSplitter.setStretchFactor(1, 1) layout.addWidget(logSplitter, 1) self.memberSelectionChanged() self.log_tree_view.selectionModel().selectionChanged.connect(self.displaySelectedLogfile) self.dropdown_members.currentIndexChanged.connect(self.memberSelectionChanged) self.requestLogsButton.clicked.connect(self.requestLogClicked) self.sendMessageButton.clicked.connect(partial(self.sendMessageToMember, messageInput)) messageInput.returnPressed.connect(partial(self.sendMessageToMember, messageInput)) get_notification_center().connectPeerAppended(self.dropdown_members_model.externalRowAppended) get_notification_center().connectPeerUpdated(self.dropdown_members_model.externalRowUpdated) get_notification_center().connectPeerRemoved(self.dropdown_members_model.externalRowRemoved) get_notification_center().connectPeerUpdated(self.updateMemberInformation) def destroy_widget(self): get_notification_center().disconnectPeerAppended(self.dropdown_members_model.externalRowAppended) get_notification_center().disconnectPeerUpdated(self.dropdown_members_model.externalRowUpdated) get_notification_center().disconnectPeerRemoved(self.dropdown_members_model.externalRowRemoved) get_notification_center().disconnectPeerUpdated(self.updateMemberInformation) def listLogfiles(self, basePath, sort=None): if sort is None: sort = lambda aFile: -self.getLogNumber(aFile) logList = [ os.path.join(basePath, aFile) for aFile in os.listdir(basePath) if aFile.endswith(".log") and not os.path.isdir(os.path.join(basePath, aFile)) ] return sorted(logList, key=sort) def getNumLogsToKeep(self, oldLogFiles, newLogFiles, logOffset): oldestNew = None for aLogFile in newLogFiles: oldestNew, _ = self.getLogDates(aLogFile) if oldestNew != None: break if oldestNew == None: # new new log file contains timestamps (they are probably all empty) return len(oldLogFiles) numToKeep = 0 while numToKeep < len(oldLogFiles) - logOffset: curTime, _ = self.getLogDates(oldLogFiles[numToKeep]) if curTime == None or curTime < oldestNew: # keep empty log files numToKeep = numToKeep + 1 else: break return numToKeep def getLogDates(self, aLogFile): with codecs.open(aLogFile, "rb", "utf-8") as logContent: logLines = logContent.readlines() firstDate = None for aLine in logLines: firstDate = getLogLineTime(aLine) if firstDate != None: break lastDate = None for aLine in reversed(logLines): lastDate = getLogLineTime(aLine) if lastDate != None: break return firstDate, lastDate def getLogNumber(self, aLogFile): aLogFile = os.path.basename(aLogFile) try: return int(aLogFile[: aLogFile.rfind(".")]) except: return -1 def shiftLogFiles(self, oldLogFiles, numToKeep, shift, logOffset): renamedLogfiles = [] for index, aFile in enumerate(oldLogFiles): logNum = self.getLogNumber(aFile) if logNum < logOffset: # don't touch up-to-date logs break if index < numToKeep: newName = os.path.join(os.path.dirname(aFile), "%d.log" % (logNum + shift)) renamedLogfiles.append((len(oldLogFiles) - index - 1, aFile, newName)) os.rename(aFile, newName) else: os.remove(aFile) return renamedLogfiles def handleNewLogFiles(self, basePath, tmpPath, logOffset=0): oldLogFiles = self.listLogfiles(basePath) newLogFiles = self.listLogfiles(tmpPath) # check how many log files are actually new numToKeep = self.getNumLogsToKeep(oldLogFiles, newLogFiles, logOffset) # rename / remove old log files to make room for the new ones numNew = len(newLogFiles) - (len(oldLogFiles) - logOffset - numToKeep) renamedLogfiles = self.shiftLogFiles(oldLogFiles, numToKeep, numNew, logOffset) # move new log files addedLogfiles = [] for index, aLogFile in enumerate(reversed(newLogFiles)): shutil.move(aLogFile, basePath) if index < numNew: addedLogfiles.append((index + logOffset, os.path.join(basePath, os.path.basename(aLogFile)))) shutil.rmtree(tmpPath, True) return numNew, addedLogfiles, renamedLogfiles def requestFinished(self): self.requestLogsButton.setEnabled(True) self.dropdown_members.setEnabled(True) @loggingSlot(QThread, object) def cb_log_transfer_success(self, thread, path): path = convert_string(path) basePath = os.path.dirname(path) tmpPath = os.path.join(basePath, "tmp") if not os.path.exists(tmpPath): os.makedirs(tmpPath) logsAdded = [] if path.endswith(".tgz"): # extract received log files with contextlib.closing(tarfile.open(path, "r:gz")) as tarContent: tarContent.extractall(tmpPath) _, logsAdded, logsRenamed = self.handleNewLogFiles(basePath, tmpPath) self.requestFinished() else: # log comes from old version logNum = 0 if thread.sender in self.logRequests: logNum, requestTime = self.logRequests[thread.sender] now = datetime.now() td = now - requestTime tdSeconds = (td.microseconds + (td.seconds + td.days * 24 * 3600) * 10 ** 6) / 10 ** 6 if tdSeconds > self.LOG_REQUEST_TIMEOUT: # request timed out or was finished already logNum = 0 shutil.move(path, os.path.join(tmpPath, "%d.log" % logNum)) numNew, logsAdded, logsRenamed = self.handleNewLogFiles(basePath, tmpPath, logNum) if numNew > 0 and logNum < 9: # there might be more new ones self.logRequests[thread.sender] = (logNum + 1, datetime.now()) self.logger.debug("log seems to be new, another!!!") logsAdded.append((logNum + 1, None)) self.request_log(thread.sender, logNum + 1) elif thread.sender in self.logRequests: # request finished del self.logRequests[thread.sender] self.requestFinished() else: self.requestFinished() if len(logsAdded) > 0 or len(logsRenamed) > 0: self.updateLogList(logsAdded, logsRenamed) @loggingSlot(QThread, object) def cb_log_transfer_error(self, _thread, message): if not self.isVisible(): return False self.log_area.setText("Error while getting log (%s)" % message) self.requestFinished() def get_selected_log_member(self): member = convert_string(self.dropdown_members.currentText()) if not member: return None if "(" in member: # member contains name, extract ID member = member[member.rfind("(") + 1 : member.rfind(")")] return member def request_log(self, member=None, logNum=0): if member == None: member = self.get_selected_log_member() if member != None: self.logger.debug("Requesting log %d from %s", logNum, member) get_server().call( "HELO_REQUEST_LOGFILE %s %d" % (DataReceiverThread.getOpenPort(category="log%s" % member), logNum), set([member]), ) else: self.log_area.setText("No Member selected!") @loggingSlot() def requestLogClicked(self): self.requestLogsButton.setEnabled(False) self.dropdown_members.setEnabled(False) self.updateLogList([(0, None)]) self.request_log() def listLogFilesForMember(self, member): if member is None: return [] logDir = os.path.join(get_settings().get_main_config_dir(), "logs", member) if not os.path.exists(logDir): return [] return self.listLogfiles(logDir) def numLogFilesForMember(self, member): return len(self.listLogFilesForMember(member)) def requestTimedOut(self, item): if not sip.isdeleted(item) and item != None and item.data(0, Qt.UserRole) == None: self.log_tree_view.takeTopLevelItem(self.log_tree_view.indexFromItem(item).row()) self.requestFinished() def formatFileSize(self, num): for x in ["Bytes", "KB", "MB", "GB", "TB"]: if num < 1024.0: return "%3.1f %s" % (num, x) num /= 1024.0 def initializeLogItem(self, item, logFile): firstDate, lastDate = self.getLogDates(logFile) text = None tooltip = None if firstDate != None: text = firstDate.strftime("%Y-%m-%d %H:%M:%S") tooltip = u"File: %s\nFirst entry: %s\nLast entry: %s" % ( logFile, firstDate.strftime("%Y-%m-%d %H:%M:%S"), lastDate.strftime("%Y-%m-%d %H:%M:%S"), ) else: timestamp = datetime.fromtimestamp(os.path.getmtime(logFile)).strftime("%Y-%m-%d %H:%M:%S") text = u"%s" % os.path.basename(logFile) tooltip = u"File:%s\nModification Date: %s" % (logFile, timestamp) text = text + "\n%s" % self.formatFileSize(os.path.getsize(logFile)) if tooltip != None: item.setData(0, Qt.ToolTipRole, QVariant(tooltip)) item.setData(0, Qt.UserRole, logFile) item.setData(0, Qt.DisplayRole, QVariant(text)) @loggingSlot() def clearLogs(self): for aLogFile in self.listLogFilesForMember(self.get_selected_log_member()): os.remove(aLogFile) self.updateLogList() def updateLogList(self, logsAdded=None, logsRenamed=None): selectedMember = self.get_selected_log_member() if logsAdded == None: self.log_tree_view.clear() logsAdded = [] for index, logFile in enumerate(reversed(self.listLogFilesForMember(selectedMember))): logsAdded.append((index, logFile)) if len(logsAdded) == 0: self.log_tree_view.clear() self.log_tree_view.addTopLevelItem( QTreeWidgetItem(self.log_tree_view, QStringList("No logs available.")) ) self.log_tree_view.setSelectionMode(QTreeWidget.NoSelection) self.logSizeLabel.setText("No logs") self.clearLogsButton.setEnabled(False) return if logsRenamed != None: for index, oldName, newName in logsRenamed: # index + 1 because of the "requesting" item item = self.log_tree_view.topLevelItem(index + 1) if item != None: itemLogFile = convert_string(item.data(0, Qt.UserRole).toString()) if itemLogFile != oldName: self.logger.warning( "index does not correspond to item in list:\n\t%s\n\t%s", itemLogFile, oldName ) self.initializeLogItem(item, newName) if len(logsAdded) == 0: self.log_tree_view.takeTopLevelItem(0) else: for index, logFile in logsAdded: oldItem = self.log_tree_view.topLevelItem(index) item = None if oldItem != None and oldItem.data(0, Qt.UserRole) == None: # requested item has been received item = oldItem else: item = QTreeWidgetItem() oldItem = None if logFile == None: item.setData(0, Qt.DisplayRole, QVariant("Requesting...")) QTimer.singleShot(6000, partial(self.requestTimedOut, item)) else: self.initializeLogItem(item, logFile) if oldItem == None: # else, the old item is being modified self.log_tree_view.insertTopLevelItem(index, item) self.log_tree_view.setSelectionMode(QTreeWidget.SingleSelection) totalSize = 0 for aLogFile in self.listLogFilesForMember(selectedMember): totalSize += os.path.getsize(aLogFile) self.logSizeLabel.setText("%s consumed" % self.formatFileSize(totalSize)) self.clearLogsButton.setEnabled(True) # self.displaySelectedLogfile() def getSelectedLogContent(self): member = self.get_selected_log_member() if member == None: return "No Log selected." selection = self.log_tree_view.selectedIndexes() if len(selection) is 0: return "No Log selected." logPath = convert_string(selection[0].data(Qt.UserRole).toString()) if logPath == None: return "ERROR: path is None" if not os.path.exists(logPath): return "File not found: " + logPath fcontent = "" try: with codecs.open(logPath, "r", "utf8") as fhandler: fcontent = fhandler.read() except Exception as e: self.logger.exception("Error reading file") fcontent = "Error reading file: %s" % str(e) return fcontent @loggingSlot(QItemSelection, QItemSelection) def displaySelectedLogfile(self, _new, _old): self.log_area.setText(self.getSelectedLogContent()) @loggingSlot(int) def memberSelectionChanged(self, _new=None): self.updateLogList() isMemberSelected = self.get_selected_log_member() != None self.sendMessageButton.setEnabled(isMemberSelected) self.requestLogsButton.setEnabled(isMemberSelected) self.updateMemberInformation() @loggingSlot(object) def sendMessageToMember(self, lineEdit): selectedMember = self.get_selected_log_member() if selectedMember != None: get_server().call(convert_string(lineEdit.text()), set([selectedMember])) lineEdit.clear() @loggingSlot(object, object) def updateMemberInformation(self, peerID=None, peerInfo=None): if peerID != None and peerID != self.get_selected_log_member(): # only update if selected member updated return self.memberInformationTable.clear() if self.get_selected_log_member() == None: self.memberInformationTable.setColumnCount(0) self.memberInformationTable.setHeaderLabel("No member selected.") return if peerInfo == None: peerInfo = get_peers().getPeerInfo(pID=self.get_selected_log_member()) if peerInfo == None: self.memberInformationTable.setColumnCount(0) self.memberInformationTable.setHeaderLabel("No member information available.") return self.memberInformationTable.setColumnCount(len(peerInfo)) headers = sorted(peerInfo.keys(), key=lambda s: s.lower()) self.memberInformationTable.setHeaderLabels(QStringList(headers)) item = QTreeWidgetItem(self.memberInformationTable) for col, header in enumerate(headers): item.setData(col, Qt.DisplayRole, QVariant(peerInfo[header])) for col in range(self.memberInformationTable.columnCount()): self.memberInformationTable.resizeColumnToContents(col)