Example #1
0
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
Example #2
0
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)
Example #3
0
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
Example #4
0
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)
Example #5
0
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)