class SettingChangedEvent(QEvent): """ A settings has changed. This event is sent by Settings instance to itself when a setting for a key has changed. """ SettingChanged = QEvent.registerEventType() """Setting was changed""" SettingAdded = QEvent.registerEventType() """A setting was added""" SettingRemoved = QEvent.registerEventType() """A setting was removed""" def __init__(self, etype, key, value=None, oldValue=None): """ Initialize the event instance """ super().__init__(etype) self.__key = key self.__value = value self.__oldValue = oldValue def key(self): return self.__key def value(self): return self.__value def oldValue(self): return self.__oldValue
class ExecuteCallEvent(QEvent): """ Represents an function call from the event loop (used by :class:`Task` to schedule the :func:`Task.run` method to be invoked) """ ExecuteCall = QEvent.registerEventType() def __init__(self): QEvent.__init__(self, ExecuteCallEvent.ExecuteCall)
class StateChangedEvent(QEvent): """ Represents a change in the internal state of a :class:`Future`. """ StateChanged = QEvent.registerEventType() def __init__(self, state): QEvent.__init__(self, StateChangedEvent.StateChanged) self._state = state def state(self): """ Return the new state (Future.Pending, Future.Cancelled, ...). """ return self._state
class OWFreeViz(widget.OWWidget): name = "FreeViz" description = "Displays FreeViz projection" icon = "icons/Freeviz.svg" priority = 240 class Inputs: data = Input("Data", Table, default=True) data_subset = Input("Data Subset", Table) class Outputs: selected_data = Output("Selected Data", Table, default=True) annotated_data = Output(ANNOTATED_DATA_SIGNAL_NAME, Table) components = Output("Components", Table) #: Initialization type Circular, Random = 0, 1 jitter_sizes = [0, 0.1, 0.5, 1, 2] settings_version = 2 settingsHandler = settings.DomainContextHandler() radius = settings.Setting(0) initialization = settings.Setting(Circular) auto_commit = settings.Setting(True) resolution = 256 graph = settings.SettingProvider(OWFreeVizGraph) ReplotRequest = QEvent.registerEventType() graph_name = "graph.plot_widget.plotItem" class Warning(widget.OWWidget.Warning): sparse_not_supported = widget.Msg("Sparse data is ignored.") class Error(widget.OWWidget.Error): no_class_var = widget.Msg("Need a class variable") not_enough_class_vars = widget.Msg("Needs discrete class variable " \ "with at lest 2 values") features_exceeds_instances = widget.Msg("Algorithm should not be used when " \ "number of features exceeds the number " \ "of instances.") too_many_data_instances = widget.Msg("Cannot handle so large data.") no_valid_data = widget.Msg("No valid data.") def __init__(self): super().__init__() self.data = None self.subset_data = None self._subset_mask = None self._validmask = None self._X = None self._Y = None self._selection = None self.__replot_requested = False self.variable_x = ContinuousVariable("freeviz-x") self.variable_y = ContinuousVariable("freeviz-y") box0 = gui.vBox(self.mainArea, True, margin=0) self.graph = OWFreeVizGraph(self, box0, "Plot", view_box=FreeVizInteractiveViewBox) box0.layout().addWidget(self.graph.plot_widget) plot = self.graph.plot_widget box = gui.widgetBox(self.controlArea, "Optimization", spacing=10) form = QFormLayout(labelAlignment=Qt.AlignLeft, formAlignment=Qt.AlignLeft, fieldGrowthPolicy=QFormLayout.AllNonFixedFieldsGrow, verticalSpacing=10) form.addRow( "Initialization", gui.comboBox(box, self, "initialization", items=["Circular", "Random"], callback=self.reset_initialization)) box.layout().addLayout(form) self.btn_start = gui.button(widget=box, master=self, label="Optimize", callback=self.toogle_start, enabled=False) self.viewbox = plot.getViewBox() self.replot = None g = self.graph.gui g.point_properties_box(self.controlArea) self.models = g.points_models box = gui.widgetBox(self.controlArea, "Show anchors") self.rslider = gui.hSlider(box, self, "radius", minValue=0, maxValue=100, step=5, label="Radius", createLabel=False, ticks=True, callback=self.update_radius) self.rslider.setTickInterval(0) self.rslider.setPageStep(10) box = gui.vBox(self.controlArea, "Plot Properties") g.add_widgets([g.JitterSizeSlider], box) g.add_widgets([g.ShowLegend, g.ClassDensity, g.LabelOnlySelected], box) self.graph.box_zoom_select(self.controlArea) self.controlArea.layout().addStretch(100) self.icons = gui.attributeIconDict p = self.graph.plot_widget.palette() self.graph.set_palette(p) gui.auto_commit(self.controlArea, self, "auto_commit", "Send Selection", "Send Automatically") self.graph.zoom_actions(self) # FreeViz self._loop = AsyncUpdateLoop(parent=self) self._loop.yielded.connect(self.__set_projection) self._loop.finished.connect(self.__freeviz_finished) self._loop.raised.connect(self.__on_error) self._new_plotdata() def keyPressEvent(self, event): super().keyPressEvent(event) self.graph.update_tooltip(event.modifiers()) def keyReleaseEvent(self, event): super().keyReleaseEvent(event) self.graph.update_tooltip(event.modifiers()) def update_radius(self): # Update the anchor/axes visibility assert not self.plotdata is None if self.plotdata.hidecircle is None: return minradius = self.radius / 100 + 1e-5 for anchor, item in zip(self.plotdata.anchors, self.plotdata.anchoritem): item.setVisible(np.linalg.norm(anchor) > minradius) self.plotdata.hidecircle.setRect( QRectF(-minradius, -minradius, 2 * minradius, 2 * minradius)) def toogle_start(self): if self._loop.isRunning(): self._loop.cancel() if isinstance(self, OWFreeViz): self.btn_start.setText("Optimize") self.progressBarFinished(processEvents=False) else: self._start() def _start(self): """ Start the projection optimization. """ assert not self.plotdata is None X, Y = self.plotdata.X, self.plotdata.Y anchors = self.plotdata.anchors def update_freeviz(interval, initial): anchors = initial while True: res = FreeViz.freeviz(X, Y, scale=False, center=False, initial=anchors, maxiter=interval) _, anchors_new = res[:2] yield res[:2] if np.allclose(anchors, anchors_new, rtol=1e-5, atol=1e-4): return anchors = anchors_new interval = 10 # TODO self._loop.setCoroutine(update_freeviz(interval, anchors)) self.btn_start.setText("Stop") self.progressBarInit(processEvents=False) self.setBlocking(True) self.setStatusMessage("Optimizing") def reset_initialization(self): """ Reset the current 'anchor' initialization, and restart the optimization if necessary. """ running = self._loop.isRunning() if running: self._loop.cancel() if self.data is not None: self._clear_plot() self.setup_plot() if running: self._start() def __set_projection(self, res): # Set/update the projection matrix and coordinate embeddings # assert self.plotdata is not None, "__set_projection call unexpected" assert not self.plotdata is None increment = 1 # TODO self.progressBarAdvance(increment * 100. / MAX_ITERATIONS, processEvents=False) # TODO embedding_coords, projection = res self.plotdata.embedding_coords = embedding_coords self.plotdata.anchors = projection self._update_xy() self.update_radius() self.update_density() def __freeviz_finished(self): # Projection optimization has finished self.btn_start.setText("Optimize") self.setStatusMessage("") self.setBlocking(False) self.progressBarFinished(processEvents=False) self.commit() def __on_error(self, err): sys.excepthook(type(err), err, getattr(err, "__traceback__")) def _update_xy(self): # Update the plotted embedding coordinates self.graph.plot_widget.clear() coords = self.plotdata.embedding_coords radius = np.max(np.linalg.norm(coords, axis=1)) self.plotdata.embedding_coords = coords / radius self.plot( show_anchors=(len(self.data.domain.attributes) < MAX_ANCHORS)) def _new_plotdata(self): self.plotdata = namespace( validmask=None, embedding_coords=None, anchors=[], anchoritem=[], X=None, Y=None, indicators=[], hidecircle=None, data=None, items=[], topattrs=None, rand=None, selection=None, # np.array ) def _anchor_circle(self): # minimum visible anchor radius (radius) minradius = self.radius / 100 + 1e-5 for item in chain(self.plotdata.anchoritem, self.plotdata.items): self.viewbox.removeItem(item) self.plotdata.anchoritem = [] self.plotdata.items = [] for anchor, var in zip(self.plotdata.anchors, self.data.domain.attributes): if True or np.linalg.norm(anchor) > minradius: axitem = AnchorItem( line=QLineF(0, 0, *anchor), text=var.name, ) axitem.setVisible(np.linalg.norm(anchor) > minradius) axitem.setPen(pg.mkPen((100, 100, 100))) axitem.setArrowVisible(True) self.plotdata.anchoritem.append(axitem) self.viewbox.addItem(axitem) hidecircle = QGraphicsEllipseItem() hidecircle.setRect( QRectF(-minradius, -minradius, 2 * minradius, 2 * minradius)) _pen = QPen(Qt.lightGray, 1) _pen.setCosmetic(True) hidecircle.setPen(_pen) self.viewbox.addItem(hidecircle) self.plotdata.items.append(hidecircle) self.plotdata.hidecircle = hidecircle def update_colors(self): pass def sizeHint(self): return QSize(800, 500) def _clear(self): """ Clear/reset the widget state """ self._loop.cancel() self.data = None self._selection = None self._clear_plot() def _clear_plot(self): for item in chain(self.plotdata.anchoritem, self.plotdata.items): self.viewbox.removeItem(item) self.graph.plot_widget.clear() self._new_plotdata() def init_attr_values(self): self.graph.set_domain(self.data) @Inputs.data def set_data(self, data): self.clear_messages() self._clear() self.closeContext() if data is not None: if data and data.is_sparse(): self.Warning.sparse_not_supported() data = None elif data.domain.class_var is None: self.Error.no_class_var() data = None elif data.domain.class_var.is_discrete and \ len(data.domain.class_var.values) < 2: self.Error.not_enough_class_vars() data = None if data and len(data.domain.attributes) > data.X.shape[0]: self.Error.features_exceeds_instances() data = None if data is not None: valid_instances_count = self._prepare_freeviz_data(data) if valid_instances_count > MAX_INSTANCES: self.Error.too_many_data_instances() data = None elif valid_instances_count == 0: self.Error.no_valid_data() data = None self.data = data self.init_attr_values() if data is not None: self.cb_class_density.setEnabled(data.domain.has_discrete_class) self.openContext(data) self.btn_start.setEnabled(True) else: self.btn_start.setEnabled(False) self._X = self._Y = None self.graph.new_data(None, None) @Inputs.data_subset def set_subset_data(self, subset): self.subset_data = subset self.plotdata.subset_mask = None self.controls.graph.alpha_value.setEnabled(subset is None) def handleNewSignals(self): if all(v is not None for v in [self.data, self.subset_data]): dataids = self.data.ids.ravel() subsetids = np.unique(self.subset_data.ids) self._subset_mask = np.in1d(dataids, subsetids, assume_unique=True) if self._X is not None: self.setup_plot(True) self.commit() def customEvent(self, event): if event.type() == OWFreeViz.ReplotRequest: self.__replot_requested = False self.setup_plot() else: super().customEvent(event) def _prepare_freeviz_data(self, data): X = data.X Y = data.Y mask = np.bitwise_or.reduce(np.isnan(X), axis=1) mask |= np.isnan(Y) validmask = ~mask X = X[validmask, :] Y = Y[validmask] if not len(X): self._X = None return 0 if data.domain.class_var.is_discrete: Y = Y.astype(int) X = (X - np.mean(X, axis=0)) span = np.ptp(X, axis=0) X[:, span > 0] /= span[span > 0].reshape(1, -1) self._X = X self._Y = Y self._validmask = validmask return len(X) def setup_plot(self, reset_view=True): assert not self._X is None self.graph.jitter_continuous = True self.__replot_requested = False X = self.plotdata.X = self._X self.plotdata.Y = self._Y self.plotdata.validmask = self._validmask self.plotdata.selection = self._selection if self._selection is not None else \ np.zeros(len(self._validmask), dtype=np.uint8) anchors = self.plotdata.anchors if len(anchors) == 0: if self.initialization == self.Circular: anchors = FreeViz.init_radial(X.shape[1]) else: anchors = FreeViz.init_random(X.shape[1], 2) EX = np.dot(X, anchors) c = np.zeros((X.shape[0], X.shape[1])) for i in range(X.shape[0]): c[i] = np.argsort((np.power(X[i] * anchors[:, 0], 2) + np.power(X[i] * anchors[:, 1], 2)))[::-1] self.plotdata.topattrs = np.array(c, dtype=int)[:, :10] radius = np.max(np.linalg.norm(EX, axis=1)) self.plotdata.anchors = anchors coords = (EX / radius) self.plotdata.embedding_coords = coords if reset_view: self.viewbox.setRange(RANGE) self.viewbox.setAspectLocked(True, 1) self.plot(reset_view=reset_view) def randomize_indices(self): X = self._X self.plotdata.rand = np.random.choice(len(X), MAX_POINTS, replace=False) \ if len(X) > MAX_POINTS else None def manual_move_anchor(self, show_anchors=True): self.__replot_requested = False X = self.plotdata.X = self._X anchors = self.plotdata.anchors validmask = self.plotdata.validmask EX = np.dot(X, anchors) data_x = self.data.X[validmask] data_y = self.data.Y[validmask] radius = np.max(np.linalg.norm(EX, axis=1)) if self.plotdata.rand is not None: rand = self.plotdata.rand EX = EX[rand] data_x = data_x[rand] data_y = data_y[rand] selection = self.plotdata.selection[validmask] selection = selection[rand] else: selection = self.plotdata.selection[validmask] coords = (EX / radius) if show_anchors: self._anchor_circle() attributes = () + self.data.domain.attributes + (self.variable_x, self.variable_y) domain = Domain(attributes=attributes, class_vars=self.data.domain.class_vars) data = Table.from_numpy(domain, X=np.hstack((data_x, coords)), Y=data_y) self.graph.new_data(data, None) self.graph.selection = selection self.graph.update_data(self.variable_x, self.variable_y, reset_view=False) def plot(self, reset_view=False, show_anchors=True): if show_anchors: self._anchor_circle() attributes = () + self.data.domain.attributes + (self.variable_x, self.variable_y) domain = Domain(attributes=attributes, class_vars=self.data.domain.class_vars, metas=self.data.domain.metas) mask = self.plotdata.validmask array = np.zeros((len(self.data), 2), dtype=np.float) array[mask] = self.plotdata.embedding_coords data = self.data.transform(domain) data[:, self.variable_x] = array[:, 0].reshape(-1, 1) data[:, self.variable_y] = array[:, 1].reshape(-1, 1) subset_data = data[self._subset_mask & mask]\ if self._subset_mask is not None and len(self._subset_mask) else None self.plotdata.data = data self.graph.new_data(data[mask], subset_data) if self.plotdata.selection is not None: self.graph.selection = self.plotdata.selection[ self.plotdata.validmask] self.graph.update_data(self.variable_x, self.variable_y, reset_view=reset_view) def reset_graph_data(self, *_): if self.data is not None: self.graph.rescale_data() self._update_graph() def _update_graph(self, reset_view=True, **_): self.graph.zoomStack = [] assert not self.graph.data is None self.graph.update_data(self.variable_x, self.variable_y, reset_view) def update_density(self): if self.graph.data is None: return self._update_graph(reset_view=False) def selection_changed(self): if self.graph.selection is not None: pd = self.plotdata pd.selection[pd.validmask] = self.graph.selection self._selection = pd.selection self.commit() def prepare_data(self): pass def commit(self): selected = annotated = components = None graph = self.graph if self.data is not None and self.plotdata.validmask is not None: name = self.data.name metas = () + self.data.domain.metas + (self.variable_x, self.variable_y) domain = Domain(attributes=self.data.domain.attributes, class_vars=self.data.domain.class_vars, metas=metas) data = self.plotdata.data.transform(domain) validmask = self.plotdata.validmask mask = np.array(validmask, dtype=int) mask[mask == 1] = graph.selection if graph.selection is not None \ else [False * len(mask)] selection = np.array( [], dtype=np.uint8) if mask is None else np.flatnonzero(mask) if len(selection): selected = data[selection] selected.name = name + ": selected" selected.attributes = self.data.attributes if graph.selection is not None and np.max(graph.selection) > 1: annotated = create_groups_table(data, mask) else: annotated = create_annotated_table(data, selection) annotated.attributes = self.data.attributes annotated.name = name + ": annotated" comp_domain = Domain(self.data.domain.attributes, metas=[StringVariable(name='component')]) metas = np.array([["FreeViz 1"], ["FreeViz 2"]]) components = Table.from_numpy(comp_domain, X=self.plotdata.anchors.T, metas=metas) components.name = name + ": components" self.Outputs.selected_data.send(selected) self.Outputs.annotated_data.send(annotated) self.Outputs.components.send(components) def send_report(self): if self.data is None: return def name(var): return var and var.name caption = report.render_items_vert( (("Color", name(self.graph.attr_color)), ("Label", name(self.graph.attr_label)), ("Shape", name(self.graph.attr_shape)), ("Size", name(self.graph.attr_size)), ("Jittering", self.graph.jitter_size != 0 and "{} %".format(self.graph.jitter_size)))) self.report_plot() if caption: self.report_caption(caption)
class FutureWatcher(QObject): """ A `FutureWatcher` class provides a convenient interface to the :class:`Future` instance using Qt's signals. :param :class:`Future` future: A :class:`Future` instance to watch. :param :class:`QObject` parent: Object's parent instance. """ #: Emitted when the future is done (cancelled or finished) done = Signal(Future) #: Emitted when the future is finished (i.e. returned a result #: or raised an exception - but not if cancelled) finished = Signal(Future) #: Emitted when the future was cancelled cancelled = Signal(Future) #: Emitted with the future's result when successfully finished. resultReady = Signal(object) #: Emitted with the future's exception when finished with an exception. exceptionReady = Signal(BaseException) # A private event type used to notify the watcher of a Future's completion __FutureDone = QEvent.registerEventType() def __init__(self, future, parent=None, **kwargs): super().__init__(parent, **kwargs) self.__future = None if future is not None: self.setFuture(future) def setFuture(self, future): # type: (Future) -> None """ Set the future to watch. Raise a `RuntimeError` if a future is already set. Parameters ---------- future : Future """ if self.__future is not None: raise RuntimeError("Future already set") self.__future = future selfweakref = weakref.ref(self) def on_done(f): assert f is future selfref = selfweakref() if selfref is None: return try: QCoreApplication.postEvent(selfref, QEvent(FutureWatcher.__FutureDone)) except RuntimeError: # Ignore RuntimeErrors (when C++ side of QObject is deleted) # (? Use QObject.destroyed and remove the done callback ?) pass future.add_done_callback(on_done) def future(self): # type: () -> Future """ Return the future instance. """ return self.__future def isCancelled(self): warnings.warn("isCancelled is deprecated", DeprecationWarning, stacklevel=2) return self.__future.cancelled() def isDone(self): warnings.warn("isDone is deprecated", DeprecationWarning, stacklevel=2) return self.__future.done() def result(self): # type: () -> Any """ Return the future's result. Note ---- This method is non-blocking. If the future has not yet completed it will raise an error. """ try: return self.__future.result(timeout=0) except TimeoutError: raise RuntimeError("Future is not yet done") def exception(self): # type: () -> Optional[BaseException] """ Return the future's exception. Note ---- This method is non-blocking. If the future has not yet completed it will raise an error. """ try: return self.__future.exception(timeout=0) except TimeoutError: raise RuntimeError("Future is not yet done") def __emitSignals(self): assert self.__future is not None assert self.__future.done() if self.__future.cancelled(): self.cancelled.emit(self.__future) self.done.emit(self.__future) elif self.__future.done(): self.finished.emit(self.__future) self.done.emit(self.__future) if self.__future.exception(): self.exceptionReady.emit(self.__future.exception()) else: self.resultReady.emit(self.__future.result()) else: assert False def customEvent(self, event): # Reimplemented. if event.type() == FutureWatcher.__FutureDone: self.__emitSignals() super().customEvent(event)