class LineScanPlot(QWidget, OWComponent, SelectionGroupMixin,
                   ImageColorSettingMixin, ImageZoomMixin):

    attr_x = ContextSetting(None)
    gamma = Setting(0)

    selection_changed = Signal()

    def __init__(self, parent):
        QWidget.__init__(self)
        OWComponent.__init__(self, parent)
        SelectionGroupMixin.__init__(self)
        ImageColorSettingMixin.__init__(self)

        self.parent = parent

        self.selection_type = SELECTMANY
        self.saving_enabled = True
        self.selection_enabled = True
        self.viewtype = INDIVIDUAL  # required bt InteractiveViewBox
        self.highlighted = None
        self.data_points = None
        self.data_imagepixels = None

        self.plotview = pg.PlotWidget(background="w", viewBox=InteractiveViewBox(self))
        self.plot = self.plotview.getPlotItem()

        self.plot.scene().installEventFilter(
            HelpEventDelegate(self.help_event, self))

        layout = QVBoxLayout()
        self.setLayout(layout)
        self.layout().setContentsMargins(0, 0, 0, 0)
        self.layout().addWidget(self.plotview)

        self.img = ImageItemNan()
        self.img.setOpts(axisOrder='row-major')
        self.plot.addItem(self.img)
        self.plot.scene().sigMouseMoved.connect(self.plot.vb.mouseMovedEvent)

        layout = QGridLayout()
        self.plotview.setLayout(layout)
        self.button = QPushButton("Menu", self.plotview)
        self.button.setAutoDefault(False)

        layout.setRowStretch(1, 1)
        layout.setColumnStretch(1, 1)
        layout.addWidget(self.button, 0, 0)
        view_menu = MenuFocus(self)
        self.button.setMenu(view_menu)

        # prepare interface according to the new context
        self.parent.contextAboutToBeOpened.connect(lambda x: self.init_interface_data(x[0]))

        self.add_zoom_actions(view_menu)

        common_options = dict(
            labelWidth=50, orientation=Qt.Horizontal, sendSelectedValue=True,
            valueType=str)

        choose_xy = QWidgetAction(self)
        box = gui.vBox(self)
        box.setFocusPolicy(Qt.TabFocus)
        self.xy_model = DomainModel(DomainModel.METAS | DomainModel.CLASSES,
                                    valid_types=DomainModel.PRIMITIVE,
                                    placeholder="Position (index)")
        self.cb_attr_x = gui.comboBox(
            box, self, "attr_x", label="Axis x:", callback=self.update_attr,
            model=self.xy_model, **common_options)

        box.setFocusProxy(self.cb_attr_x)

        box.layout().addWidget(self.color_settings_box())

        choose_xy.setDefaultWidget(box)
        view_menu.addAction(choose_xy)

        self.lsx = None  # info about the X axis
        self.lsy = None  # info about the Y axis

        self.data = None
        self.data_ids = {}

    def init_interface_data(self, data):
        same_domain = (self.data and data and
                       data.domain == self.data.domain)
        if not same_domain:
            self.init_attr_values(data)

    def help_event(self, ev):
        pos = self.plot.vb.mapSceneToView(ev.scenePos())
        sel, wavenumber_ind = self._points_at_pos(pos)
        prepared = []
        if sel is not None:
            prepared.append(str(self.wavenumbers[wavenumber_ind]))
            for d in self.data[sel]:
                variables = [v for v in self.data.domain.metas + self.data.domain.class_vars
                             if v not in [self.attr_x]]
                features = ['{} = {}'.format(attr.name, d[attr]) for attr in variables]
                features.append('value = {}'.format(d[wavenumber_ind]))
                prepared.append("\n".join(features))
        text = "\n\n".join(prepared)
        if text:
            text = ('<span style="white-space:pre">{}</span>'
                    .format(escape(text)))
            QToolTip.showText(ev.screenPos(), text, widget=self.plotview)
            return True
        else:
            return False

    def update_attr(self):
        self.update_view()

    def init_attr_values(self, data):
        domain = data.domain if data is not None else None
        self.xy_model.set_domain(domain)
        self.attr_x = self.xy_model[0] if self.xy_model else None

    def set_data(self, data):
        if data:
            self.data = data
            self.data_ids = {e: i for i, e in enumerate(data.ids)}
            self.restore_selection_settings()
        else:
            self.data = None
            self.data_ids = {}

    def update_view(self):
        self.img.clear()
        self.img.setSelection(None)
        self.lsx = None
        self.lsy = None
        self.wavenumbers = None
        self.data_xs = None
        self.data_imagepixels = None
        if self.data and len(self.data.domain.attributes):
            if self.attr_x is not None:
                xat = self.data.domain[self.attr_x]
                ndom = Domain([xat])
                datam = Table(ndom, self.data)
                coorx = datam.X[:, 0]
            else:
                coorx = np.arange(len(self.data))
            self.lsx = lsx = values_to_linspace(coorx)
            self.data_xs = coorx

            self.wavenumbers = wavenumbers = getx(self.data)
            self.lsy = lsy = values_to_linspace(wavenumbers)

            # set data
            imdata = np.ones((lsy[2], lsx[2])) * float("nan")
            xindex = index_values(coorx, lsx)
            yindex = index_values(wavenumbers, lsy)
            for xind, d in zip(xindex, self.data.X):
                imdata[yindex, xind] = d

            self.data_imagepixels = xindex

            self.img.setImage(imdata, autoLevels=False)
            self.img.setLevels([0, 1])
            self.update_levels()
            self.update_color_schema()

            # shift centres of the pixels so that the axes are useful
            shiftx = _shift(lsx)
            shifty = _shift(lsy)
            left = lsx[0] - shiftx
            bottom = lsy[0] - shifty
            width = (lsx[1]-lsx[0]) + 2*shiftx
            height = (lsy[1]-lsy[0]) + 2*shifty
            self.img.setRect(QRectF(left, bottom, width, height))

            self.refresh_img_selection()

    def refresh_img_selection(self):
        selected_px = np.zeros((self.lsy[2], self.lsx[2]), dtype=np.uint8)
        selected_px[:, self.data_imagepixels] = self.selection_group
        self.img.setSelection(selected_px)

    def make_selection(self, selected, add):
        """Add selected indices to the selection."""
        add_to_group, add_group, remove = selection_modifiers()
        if self.data and self.lsx and self.lsy:
            if add_to_group:  # both keys - need to test it before add_group
                selnum = np.max(self.selection_group)
            elif add_group:
                selnum = np.max(self.selection_group) + 1
            elif remove:
                selnum = 0
            else:
                self.selection_group *= 0
                selnum = 1
            if selected is not None:
                self.selection_group[selected] = selnum
            self.refresh_img_selection()
        self.prepare_settings_for_saving()
        self.selection_changed.emit()

    def _points_at_pos(self, pos):
        if self.data and self.lsx and self.lsy:
            x, y = pos.x(), pos.y()
            x_distance = np.abs(self.data_xs - x)
            sel = (x_distance < _shift(self.lsx))
            wavenumber_distance = np.abs(self.wavenumbers - y)
            wavenumber_ind = np.argmin(wavenumber_distance)
            return sel, wavenumber_ind
        return None, None

    def select_by_click(self, pos, add):
        sel, _ = self._points_at_pos(pos)
        self.make_selection(sel, add)
Ejemplo n.º 2
0
class ImagePlot(QWidget, OWComponent):

    attr_x = ContextSetting(None)
    attr_y = ContextSetting(None)
    gamma = Setting(0)
    threshold_low = Setting(0.0)
    threshold_high = Setting(1.0)
    palette_index = Setting(0)

    def __init__(self, parent, select_fn=None):
        QWidget.__init__(self)
        OWComponent.__init__(self, parent)

        self.parent = parent

        self.select_fn = select_fn

        self.selection_type = SELECTMANY
        self.saving_enabled = hasattr(self.parent, "save_graph")
        self.selection_enabled = True
        self.viewtype = INDIVIDUAL  # required bt InteractiveViewBox
        self.highlighted = None
        self.data_points = None
        self.data_values = None
        self.data_imagepixels = None
        self.selection = None

        self.plotview = pg.PlotWidget(background="w", viewBox=InteractiveViewBox(self))
        self.plot = self.plotview.getPlotItem()

        self.plot.scene().installEventFilter(
            HelpEventDelegate(self.help_event, self))

        layout = QVBoxLayout()
        self.setLayout(layout)
        self.layout().setContentsMargins(0, 0, 0, 0)
        self.layout().addWidget(self.plotview)

        self.img = ImageItemNan()
        self.img.setOpts(axisOrder='row-major')
        self.plot.addItem(self.img)
        self.plot.vb.setAspectLocked()
        self.plot.scene().sigMouseMoved.connect(self.plot.vb.mouseMovedEvent)

        layout = QGridLayout()
        self.plotview.setLayout(layout)
        self.button = QPushButton("View", self.plotview)
        self.button.setAutoDefault(False)

        layout.setRowStretch(1, 1)
        layout.setColumnStretch(1, 1)
        layout.addWidget(self.button, 0, 0)
        view_menu = MenuFocus(self)
        self.button.setMenu(view_menu)

        actions = []

        zoom_in = QAction(
            "Zoom in", self, triggered=self.plot.vb.set_mode_zooming
        )
        zoom_in.setShortcuts([Qt.Key_Z, QKeySequence(QKeySequence.ZoomIn)])
        zoom_in.setShortcutContext(Qt.WidgetWithChildrenShortcut)
        actions.append(zoom_in)
        zoom_fit = QAction(
            "Zoom to fit", self,
            triggered=lambda x: (self.plot.vb.autoRange(), self.plot.vb.set_mode_panning())
        )
        zoom_fit.setShortcuts([Qt.Key_Backspace, QKeySequence(Qt.ControlModifier | Qt.Key_0)])
        zoom_fit.setShortcutContext(Qt.WidgetWithChildrenShortcut)
        actions.append(zoom_fit)
        select_square = QAction(
            "Select (square)", self, triggered=self.plot.vb.set_mode_select_square,
        )
        select_square.setShortcuts([Qt.Key_S])
        select_square.setShortcutContext(Qt.WidgetWithChildrenShortcut)
        actions.append(select_square)


        select_polygon = QAction(
            "Select (polygon)", self, triggered=self.plot.vb.set_mode_select_polygon,
        )
        select_polygon.setShortcuts([Qt.Key_P])
        select_polygon.setShortcutContext(Qt.WidgetWithChildrenShortcut)
        actions.append(select_polygon)

        if self.saving_enabled:
            save_graph = QAction(
                "Save graph", self, triggered=self.save_graph,
            )
            save_graph.setShortcuts([QKeySequence(Qt.ControlModifier | Qt.Key_I)])
            actions.append(save_graph)

        view_menu.addActions(actions)
        self.addActions(actions)

        common_options = dict(
            labelWidth=50, orientation=Qt.Horizontal, sendSelectedValue=True,
            valueType=str)

        choose_xy = QWidgetAction(self)
        box = gui.vBox(self)
        box.setFocusPolicy(Qt.TabFocus)
        self.xy_model = DomainModel(DomainModel.METAS | DomainModel.CLASSES, valid_types=DomainModel.PRIMITIVE)
        self.cb_attr_x = gui.comboBox(
            box, self, "attr_x", label="Axis x:", callback=self.update_attr,
            model=self.xy_model, **common_options)
        self.cb_attr_y = gui.comboBox(
            box, self, "attr_y", label="Axis y:", callback=self.update_attr,
            model=self.xy_model, **common_options)
        box.setFocusProxy(self.cb_attr_x)

        self.color_cb = gui.comboBox(box, self, "palette_index", label="Color:",
                                     labelWidth=50, orientation=Qt.Horizontal)
        self.color_cb.setIconSize(QSize(64, 16))
        palettes = _color_palettes

        self.palette_index = min(self.palette_index, len(palettes) - 1)

        model = color_palette_model(palettes, self.color_cb.iconSize())
        model.setParent(self)
        self.color_cb.setModel(model)
        self.color_cb.activated.connect(self.update_color_schema)

        self.color_cb.setCurrentIndex(self.palette_index)

        form = QFormLayout(
            formAlignment=Qt.AlignLeft,
            labelAlignment=Qt.AlignLeft,
            fieldGrowthPolicy=QFormLayout.AllNonFixedFieldsGrow
        )

        lowslider = gui.hSlider(
            box, self, "threshold_low", minValue=0.0, maxValue=1.0,
            step=0.05, ticks=True, intOnly=False,
            createLabel=False, callback=self.update_color_schema)
        highslider = gui.hSlider(
            box, self, "threshold_high", minValue=0.0, maxValue=1.0,
            step=0.05, ticks=True, intOnly=False,
            createLabel=False, callback=self.update_color_schema)

        form.addRow("Low:", lowslider)
        form.addRow("High:", highslider)

        box.layout().addLayout(form)

        choose_xy.setDefaultWidget(box)
        view_menu.addAction(choose_xy)

        self.markings_integral = []

        self.lsx = None  # info about the X axis
        self.lsy = None  # info about the Y axis

        self.data = None
        self.data_ids = {}

    def help_event(self, ev):
        pos = self.plot.vb.mapSceneToView(ev.scenePos())
        sel = self._points_at_pos(pos)
        prepared = []
        if sel is not None:
            data, vals, points = self.data[sel], self.data_values[sel], self.data_points[sel]
            for d, v, p in zip(data, vals, points):
                basic = "({}, {}): {}".format(p[0], p[1], v)
                variables = [ v for v in self.data.domain.metas + self.data.domain.class_vars
                              if v not in [self.attr_x, self.attr_y]]
                features = ['{} = {}'.format(attr.name, d[attr]) for attr in variables]
                prepared.append("\n".join([basic] + features))
        text = "\n\n".join(prepared)
        if text:
            text = ('<span style="white-space:pre">{}</span>'
                    .format(escape(text)))
            QToolTip.showText(ev.screenPos(), text, widget=self.plotview)
            return True
        else:
            return False

    def update_color_schema(self):
        if not self.threshold_low < self.threshold_high:
            # TODO this belongs here, not in the parent
            self.parent.Warning.threshold_error()
            return
        else:
            self.parent.Warning.threshold_error.clear()
        data = self.color_cb.itemData(self.palette_index, role=Qt.UserRole)
        _, colors = max(data.items())
        cols = color_palette_table(
            colors, threshold_low=self.threshold_low,
            threshold_high=self.threshold_high)

        self.img.setLookupTable(cols)

        # use defined discrete palette
        if self.parent.value_type == 1:
            dat = self.data.domain[self.parent.attr_value]
            if isinstance(dat, DiscreteVariable):
                self.img.setLookupTable(dat.colors)

    def update_attr(self):
        self.update_view()

    def init_attr_values(self):
        domain = self.data.domain if self.data is not None else None
        self.xy_model.set_domain(domain)
        self.attr_x = self.xy_model[0] if self.xy_model else None
        self.attr_y = self.xy_model[1] if len(self.xy_model) >= 2 \
            else self.attr_x

    def save_graph(self):
        self.parent.save_graph()

    def set_data(self, data):
        self.img.clear()
        if data is not None:
            same_domain = (self.data and
                           data.domain.checksum() == self.data.domain.checksum())
            self.data = data
            self.data_ids = {e: i for i, e in enumerate(data.ids)}
            if not same_domain:
                self.init_attr_values()
        else:
            self.data = None
            self.data_ids = {}

    def set_integral_limits(self):
        self.update_view()

    def refresh_markings(self, di):
        refresh_integral_markings(di, self.markings_integral, self.parent.curveplot)

    def update_view(self):
        self.img.clear()
        self.img.setSelection(None)
        self.lsx = None
        self.lsy = None
        self.data_points = None
        self.data_values = None
        self.data_imagepixels = None
        if self.data and self.attr_x and self.attr_y:
            xat = self.data.domain[self.attr_x]
            yat = self.data.domain[self.attr_y]

            ndom = Orange.data.Domain([xat, yat])
            datam = Orange.data.Table(ndom, self.data)
            coorx = datam.X[:, 0]
            coory = datam.X[:, 1]
            self.data_points = datam.X
            self.lsx = lsx = values_to_linspace(coorx)
            self.lsy = lsy = values_to_linspace(coory)
            if lsx[-1] * lsy[-1] > IMAGE_TOO_BIG:
                self.parent.Error.image_too_big(lsx[-1], lsy[-1])
                return
            else:
                self.parent.Error.image_too_big.clear()

            di = {}
            if self.parent.value_type == 0:  # integrals
                imethod = self.parent.integration_methods[self.parent.integration_method]

                l1, l2, l3 = self.parent.lowlim, self.parent.highlim, self.parent.choose

                gx = getx(self.data)

                if l1 is None:
                    l1 = min(gx) - 1
                if l2 is None:
                    l2 = max(gx) + 1

                l1, l2 = min(l1, l2), max(l1, l2)

                if l3 is None:
                    l3 = (l1 + l2)/2

                if imethod != Integrate.PeakAt:
                    datai = Integrate(methods=imethod, limits=[[l1, l2]])(self.data)
                else:
                    datai = Integrate(methods=imethod, limits=[[l3, l3]])(self.data)

                if self.parent.curveplot.selected_indices:
                    # curveplot can have a subset of curves on the input> match IDs
                    ind = list(self.parent.curveplot.selected_indices)[0]
                    dind = self.data_ids[self.parent.curveplot.data[ind].id]
                    di = datai.domain.attributes[0].compute_value.draw_info(self.data[dind:dind+1])
                d = datai.X[:, 0]
            else:
                dat = self.data.domain[self.parent.attr_value]
                ndom = Orange.data.Domain([dat])
                d = Orange.data.Table(ndom, self.data).X[:, 0]
            self.refresh_markings(di)

            # set data
            imdata = np.ones((lsy[2], lsx[2])) * float("nan")

            # if previous or saved selection is valid for this data set keep it
            if self.selection is None or len(self.selection) != len(self.data):
                self.selection = np.zeros(len(self.data), dtype="bool")

            xindex = index_values(coorx, lsx)
            yindex = index_values(coory, lsy)
            imdata[yindex, xindex] = d
            self.data_values = d
            self.data_imagepixels = np.vstack((yindex, xindex)).T

            levels = get_levels(imdata)
            self.update_color_schema()

            self.img.setImage(imdata, levels=levels)

            # shift centres of the pixels so that the axes are useful
            shiftx = _shift(lsx)
            shifty = _shift(lsy)
            left = lsx[0] - shiftx
            bottom = lsy[0] - shifty
            width = (lsx[1]-lsx[0]) + 2*shiftx
            height = (lsy[1]-lsy[0]) + 2*shifty
            self.img.setRect(QRectF(left, bottom, width, height))

            self.send_selection()
            self.refresh_img_selection()

    def refresh_img_selection(self):
        selected_px = np.zeros((self.lsy[2], self.lsx[2]), dtype=bool)
        selected_px_ind = self.data_imagepixels[self.selection]
        selected_px[selected_px_ind[:, 0], selected_px_ind[:, 1]] = 1
        self.img.setSelection(selected_px)

    def make_selection(self, selected, add):
        """Add selected indices to the selection."""
        if self.data and self.lsx and self.lsy:
            if selected is None and not add:
                self.selection *= False  # set all to False
            elif selected is not None:
                if add:
                    self.selection = np.logical_or(self.selection, selected)
                else:
                    self.selection = selected
            self.refresh_img_selection()
        self.send_selection()

    def send_selection(self):
        if self.data and self.selection is not None:
            selected = np.where(self.selection)[0]
        else:
            selected = []
        if self.select_fn:
            self.select_fn(selected)

    def select_square(self, p1, p2, add):
        """ Select elements within a square drawn by the user.
        A selection needs to contain whole pixels """
        x1, y1 = p1.x(), p1.y()
        x2, y2 = p2.x(), p2.y()
        polygon = [QPointF(x1, y1), QPointF(x2, y1), QPointF(x2, y2), QPointF(x1, y2), QPointF(x1, y1)]
        self.select_polygon(polygon, add)

    def select_polygon(self, polygon, add):
        """ Select by a polygon which has to contain whole pixels. """
        if self.data and self.lsx and self.lsy:
            polygon = [(p.x(), p.y()) for p in polygon]
            # a polygon should contain all pixel
            shiftx = _shift(self.lsx)
            shifty = _shift(self.lsy)
            points_edges = [self.data_points + [[shiftx, shifty]],
                            self.data_points + [[-shiftx, shifty]],
                            self.data_points + [[shiftx, -shifty]],
                            self.data_points + [[-shiftx, -shifty]]]
            inp = in_polygon(points_edges[0], polygon)
            for p in points_edges[1:]:
                inp *= in_polygon(p, polygon)
            self.make_selection(inp, add)

    def _points_at_pos(self, pos):
        if self.data and self.lsx and self.lsy:
            x, y = pos.x(), pos.y()
            distance = np.abs(self.data_points - [[x, y]])
            sel = (distance[:, 0] < _shift(self.lsx)) * (distance[:, 1] < _shift(self.lsy))
            return sel

    def select_by_click(self, pos, add):
        sel = self._points_at_pos(pos)
        self.make_selection(sel, add)
Ejemplo n.º 3
0
class ImagePlot(QWidget, OWComponent, SelectionGroupMixin,
                ImageColorSettingMixin, ImageZoomMixin, ConcurrentMixin):

    attr_x = ContextSetting(None)
    attr_y = ContextSetting(None)
    gamma = Setting(0)

    selection_changed = Signal()
    image_updated = Signal()

    def __init__(self, parent):
        QWidget.__init__(self)
        OWComponent.__init__(self, parent)
        SelectionGroupMixin.__init__(self)
        ImageColorSettingMixin.__init__(self)
        ImageZoomMixin.__init__(self)
        ConcurrentMixin.__init__(self)
        self.parent = parent

        self.selection_type = SELECTMANY
        self.saving_enabled = True
        self.selection_enabled = True
        self.viewtype = INDIVIDUAL  # required bt InteractiveViewBox
        self.highlighted = None
        self.data_points = None
        self.data_values = None
        self.data_imagepixels = None
        self.data_valid_positions = None

        self.plotview = pg.GraphicsLayoutWidget()
        self.plot = pg.PlotItem(background="w",
                                viewBox=InteractiveViewBox(self))
        self.plotview.addItem(self.plot)

        self.legend = ImageColorLegend()
        self.plotview.addItem(self.legend)

        self.plot.scene().installEventFilter(
            HelpEventDelegate(self.help_event, self))

        layout = QVBoxLayout()
        self.setLayout(layout)
        self.layout().setContentsMargins(0, 0, 0, 0)
        self.layout().addWidget(self.plotview)

        self.img = ImageItemNan()
        self.img.setOpts(axisOrder='row-major')
        self.plot.addItem(self.img)
        self.vis_img = pg.ImageItem()
        self.vis_img.setOpts(axisOrder='row-major')
        self.plot.vb.setAspectLocked()
        self.plot.scene().sigMouseMoved.connect(self.plot.vb.mouseMovedEvent)

        layout = QGridLayout()
        self.plotview.setLayout(layout)
        self.button = QPushButton("Menu", self.plotview)
        self.button.setAutoDefault(False)

        layout.setRowStretch(1, 1)
        layout.setColumnStretch(1, 1)
        layout.addWidget(self.button, 0, 0)
        view_menu = MenuFocus(self)
        self.button.setMenu(view_menu)

        # prepare interface according to the new context
        self.parent.contextAboutToBeOpened.connect(
            lambda x: self.init_interface_data(x[0]))

        actions = []

        self.add_zoom_actions(view_menu)

        select_square = QAction(
            "Select (square)",
            self,
            triggered=self.plot.vb.set_mode_select_square,
        )
        select_square.setShortcuts([Qt.Key_S])
        select_square.setShortcutContext(Qt.WidgetWithChildrenShortcut)
        actions.append(select_square)

        select_polygon = QAction(
            "Select (polygon)",
            self,
            triggered=self.plot.vb.set_mode_select_polygon,
        )
        select_polygon.setShortcuts([Qt.Key_P])
        select_polygon.setShortcutContext(Qt.WidgetWithChildrenShortcut)
        actions.append(select_polygon)

        if self.saving_enabled:
            save_graph = QAction(
                "Save graph",
                self,
                triggered=self.save_graph,
            )
            save_graph.setShortcuts(
                [QKeySequence(Qt.ControlModifier | Qt.Key_I)])
            actions.append(save_graph)

        view_menu.addActions(actions)
        self.addActions(actions)

        common_options = dict(labelWidth=50,
                              orientation=Qt.Horizontal,
                              sendSelectedValue=True)

        choose_xy = QWidgetAction(self)
        box = gui.vBox(self)
        box.setFocusPolicy(Qt.TabFocus)
        self.xy_model = DomainModel(DomainModel.METAS | DomainModel.CLASSES,
                                    valid_types=DomainModel.PRIMITIVE)
        self.cb_attr_x = gui.comboBox(box,
                                      self,
                                      "attr_x",
                                      label="Axis x:",
                                      callback=self.update_attr,
                                      model=self.xy_model,
                                      **common_options)
        self.cb_attr_y = gui.comboBox(box,
                                      self,
                                      "attr_y",
                                      label="Axis y:",
                                      callback=self.update_attr,
                                      model=self.xy_model,
                                      **common_options)
        box.setFocusProxy(self.cb_attr_x)

        box.layout().addWidget(self.color_settings_box())

        choose_xy.setDefaultWidget(box)
        view_menu.addAction(choose_xy)

        self.lsx = None  # info about the X axis
        self.lsy = None  # info about the Y axis

        self.data = None
        self.data_ids = {}

    def init_interface_data(self, data):
        same_domain = (self.data and data and data.domain == self.data.domain)
        if not same_domain:
            self.init_attr_values(data)

    def help_event(self, ev):
        pos = self.plot.vb.mapSceneToView(ev.scenePos())
        sel = self._points_at_pos(pos)
        prepared = []
        if sel is not None:
            data, vals, points = self.data[sel], self.data_values[
                sel], self.data_points[sel]
            for d, v, p in zip(data, vals, points):
                basic = "({}, {}): {}".format(p[0], p[1], v)
                variables = [
                    v for v in self.data.domain.metas +
                    self.data.domain.class_vars
                    if v not in [self.attr_x, self.attr_y]
                ]
                features = [
                    '{} = {}'.format(attr.name, d[attr]) for attr in variables
                ]
                prepared.append("\n".join([basic] + features))
        text = "\n\n".join(prepared)
        if text:
            text = ('<span style="white-space:pre">{}</span>'.format(
                escape(text)))
            QToolTip.showText(ev.screenPos(), text, widget=self.plotview)
            return True
        else:
            return False

    def update_attr(self):
        self.update_view()

    def init_attr_values(self, data):
        domain = data.domain if data is not None else None
        self.xy_model.set_domain(domain)
        self.attr_x = self.xy_model[0] if self.xy_model else None
        self.attr_y = self.xy_model[1] if len(self.xy_model) >= 2 \
            else self.attr_x

    def save_graph(self):
        saveplot.save_plot(self.plotview, self.parent.graph_writers)

    def set_data(self, data):
        if data:
            self.data = data
            self.data_ids = {e: i for i, e in enumerate(data.ids)}
            self.restore_selection_settings()
        else:
            self.data = None
            self.data_ids = {}

    def refresh_img_selection(self):
        selected_px = np.zeros((self.lsy[2], self.lsx[2]), dtype=np.uint8)
        selected_px[self.data_imagepixels[self.data_valid_positions, 0],
                    self.data_imagepixels[self.data_valid_positions, 1]] = \
            self.selection_group[self.data_valid_positions]
        self.img.setSelection(selected_px)

    def make_selection(self, selected):
        """Add selected indices to the selection."""
        add_to_group, add_group, remove = selection_modifiers()
        if self.data and self.lsx and self.lsy:
            if add_to_group:  # both keys - need to test it before add_group
                selnum = np.max(self.selection_group)
            elif add_group:
                selnum = np.max(self.selection_group) + 1
            elif remove:
                selnum = 0
            else:
                self.selection_group *= 0
                selnum = 1
            if selected is not None:
                self.selection_group[selected] = selnum
            self.refresh_img_selection()
        self.prepare_settings_for_saving()
        self.selection_changed.emit()

    def select_square(self, p1, p2):
        """ Select elements within a square drawn by the user.
        A selection needs to contain whole pixels """
        x1, y1 = p1.x(), p1.y()
        x2, y2 = p2.x(), p2.y()
        polygon = [
            QPointF(x1, y1),
            QPointF(x2, y1),
            QPointF(x2, y2),
            QPointF(x1, y2),
            QPointF(x1, y1)
        ]
        self.select_polygon(polygon)

    def select_polygon(self, polygon):
        """ Select by a polygon which has to contain whole pixels. """
        if self.data and self.lsx and self.lsy:
            polygon = [(p.x(), p.y()) for p in polygon]
            # a polygon should contain all pixel
            shiftx = _shift(self.lsx)
            shifty = _shift(self.lsy)
            points_edges = [
                self.data_points + [[shiftx, shifty]],
                self.data_points + [[-shiftx, shifty]],
                self.data_points + [[shiftx, -shifty]],
                self.data_points + [[-shiftx, -shifty]]
            ]
            inp = in_polygon(points_edges[0], polygon)
            for p in points_edges[1:]:
                inp *= in_polygon(p, polygon)
            self.make_selection(inp)

    def _points_at_pos(self, pos):
        if self.data and self.lsx and self.lsy:
            x, y = pos.x(), pos.y()
            distance = np.abs(self.data_points - [[x, y]])
            sel = (distance[:, 0] < _shift(self.lsx)) * (distance[:, 1] <
                                                         _shift(self.lsy))
            return sel

    def select_by_click(self, pos):
        sel = self._points_at_pos(pos)
        self.make_selection(sel)

    def update_view(self):
        self.cancel()
        self.parent.Error.image_too_big.clear()
        self.parent.Information.not_shown.clear()
        self.img.clear()
        self.img.setSelection(None)
        self.legend.set_colors(None)
        self.lsx = None
        self.lsy = None
        self.data_points = None
        self.data_values = None
        self.data_imagepixels = None
        self.data_valid_positions = None

        if self.data and self.attr_x and self.attr_y:
            self.start(self.compute_image, self.data, self.attr_x, self.attr_y,
                       self.parent.image_values(),
                       self.parent.image_values_fixed_levels())
        else:
            self.image_updated.emit()

    def set_visible_image(self, img: np.ndarray, rect: QRectF):
        self.vis_img.setImage(img)
        self.vis_img.setRect(rect)

    def show_visible_image(self):
        if self.vis_img not in self.plot.items:
            self.plot.addItem(self.vis_img)

    def hide_visible_image(self):
        self.plot.removeItem(self.vis_img)

    def set_visible_image_opacity(self, opacity: int):
        """Opacity is an alpha channel intensity integer from 0 to 255"""
        self.vis_img.setOpacity(opacity / 255)

    def set_visible_image_comp_mode(self, comp_mode: QPainter.CompositionMode):
        self.vis_img.setCompositionMode(comp_mode)

    @staticmethod
    def compute_image(data: Orange.data.Table, attr_x, attr_y, image_values,
                      image_values_fixed_levels, state: TaskState):
        def progress_interrupt(i: float):
            if state.is_interruption_requested():
                raise InterruptException

        class Result():
            pass

        res = Result()

        xat = data.domain[attr_x]
        yat = data.domain[attr_y]

        def extract_col(data, var):
            nd = Domain([var])
            d = data.transform(nd)
            return d.X[:, 0]

        progress_interrupt(0)

        res.coorx = extract_col(data, xat)
        res.coory = extract_col(data, yat)
        res.data_points = np.hstack(
            [res.coorx.reshape(-1, 1),
             res.coory.reshape(-1, 1)])
        res.lsx = lsx = values_to_linspace(res.coorx)
        res.lsy = lsy = values_to_linspace(res.coory)
        res.image_values_fixed_levels = image_values_fixed_levels
        progress_interrupt(0)

        if lsx[-1] * lsy[-1] > IMAGE_TOO_BIG:
            raise ImageTooBigException((lsx[-1], lsy[-1]))

        # the code below does this, but part-wise:
        # d = image_values(data).X[:, 0]
        parts = []
        for slice in split_to_size(len(data), 10000):
            part = image_values(data[slice]).X[:, 0]
            parts.append(part)
            progress_interrupt(0)
        d = np.concatenate(parts)

        res.d = d
        progress_interrupt(0)

        return res

    def on_done(self, res):

        self.lsx, self.lsy = res.lsx, res.lsy
        lsx, lsy = self.lsx, self.lsy

        d = res.d

        self.fixed_levels = res.image_values_fixed_levels

        self.data_points = res.data_points

        xindex, xnan = index_values_nan(res.coorx, self.lsx)
        yindex, ynan = index_values_nan(res.coory, self.lsy)
        self.data_valid_positions = valid = np.logical_not(
            np.logical_or(xnan, ynan))
        invalid_positions = len(d) - np.sum(valid)
        if invalid_positions:
            self.parent.Information.not_shown(invalid_positions)

        imdata = np.ones((lsy[2], lsx[2])) * float("nan")
        imdata[yindex[valid], xindex[valid]] = d[valid]
        self.data_values = d
        self.data_imagepixels = np.vstack((yindex, xindex)).T

        self.img.setImage(imdata, autoLevels=False)
        self.update_levels()
        self.update_color_schema()
        self.update_legend_visible()

        # shift centres of the pixels so that the axes are useful
        shiftx = _shift(lsx)
        shifty = _shift(lsy)
        left = lsx[0] - shiftx
        bottom = lsy[0] - shifty
        width = (lsx[1] - lsx[0]) + 2 * shiftx
        height = (lsy[1] - lsy[0]) + 2 * shifty
        self.img.setRect(QRectF(left, bottom, width, height))

        self.refresh_img_selection()
        self.image_updated.emit()

    def on_partial_result(self, result):
        pass

    def on_exception(self, ex: Exception):
        if isinstance(ex, InterruptException):
            return

        if isinstance(ex, ImageTooBigException):
            self.parent.Error.image_too_big(ex.args[0][0], ex.args[0][1])
            self.image_updated.emit()
        else:
            raise ex
Ejemplo n.º 4
0
class CurvePlot(QWidget, OWComponent):

    label_title = Setting("")
    label_xaxis = Setting("")
    label_yaxis = Setting("")
    range_x1 = Setting(None)
    range_x2 = Setting(None)
    range_y1 = Setting(None)
    range_y2 = Setting(None)
    color_attr = ContextSetting(0)
    invertX = Setting(False)
    selected_indices = Setting(set())
    data_size = Setting(None)  # to invalidate selected_indices

    def __init__(self, parent=None, select=SELECTNONE):
        QWidget.__init__(self)
        OWComponent.__init__(self, parent)

        self.parent = parent

        self.selection_type = select
        self.saving_enabled = hasattr(self.parent, "save_graph")
        self.clear_data(init=True)

        self.plotview = pg.PlotWidget(background="w", viewBox=InteractiveViewBox(self))
        self.plot = self.plotview.getPlotItem()
        self.plot.setDownsampling(auto=True, mode="peak")

        self.markings = []
        self.vLine = pg.InfiniteLine(angle=90, movable=False)
        self.hLine = pg.InfiniteLine(angle=0, movable=False)
        self.proxy = pg.SignalProxy(self.plot.scene().sigMouseMoved, rateLimit=20, slot=self.mouseMoved, delay=0.1)
        self.plot.scene().sigMouseMoved.connect(self.plot.vb.mouseMovedEvent)
        self.plot.vb.sigRangeChanged.connect(self.resized)
        self.pen_mouse = pg.mkPen(color=(0, 0, 255), width=2)
        self.pen_normal = defaultdict(lambda: pg.mkPen(color=(200, 200, 200, 127), width=1))
        self.pen_subset = defaultdict(lambda: pg.mkPen(color=(0, 0, 0, 127), width=1))
        self.pen_selected = defaultdict(lambda: pg.mkPen(color=(0, 0, 0, 127), width=2, style=Qt.DotLine))
        self.label = pg.TextItem("", anchor=(1, 0))
        self.label.setText("", color=(0, 0, 0))
        self.discrete_palette = None
        QPixmapCache.setCacheLimit(max(QPixmapCache.cacheLimit(), 100 * 1024))
        self.curves_cont = PlotCurvesItem()
        self.important_decimals = 4, 4

        self.MOUSE_RADIUS = 20

        self.clear_graph()

        #interface settings
        self.location = True #show current position
        self.markclosest = True #mark
        self.crosshair = True
        self.crosshair_hidden = True
        self.viewtype = INDIVIDUAL

        layout = QVBoxLayout()
        self.setLayout(layout)
        self.layout().setContentsMargins(0, 0, 0, 0)
        self.layout().addWidget(self.plotview)

        actions = []

        zoom_in = QAction(
            "Zoom in", self, triggered=self.plot.vb.set_mode_zooming
        )
        zoom_in.setShortcuts([Qt.Key_Z, QKeySequence(QKeySequence.ZoomIn)])
        actions.append(zoom_in)
        zoom_fit = QAction(
            "Zoom to fit", self,
            triggered=lambda x: (self.plot.vb.autoRange(), self.plot.vb.set_mode_panning())
        )
        zoom_fit.setShortcuts([Qt.Key_Backspace, QKeySequence(Qt.ControlModifier | Qt.Key_0)])
        actions.append(zoom_fit)
        rescale_y = QAction(
            "Rescale Y to fit", self, shortcut=Qt.Key_D,
            triggered=self.rescale_current_view_y
        )
        actions.append(rescale_y)
        view_individual = QAction(
            "Show individual", self, shortcut=Qt.Key_I,
            triggered=lambda x: self.show_individual()
        )
        actions.append(view_individual)
        view_average = QAction(
            "Show averages", self, shortcut=Qt.Key_A,
            triggered=lambda x: self.show_average()
        )
        actions.append(view_average)
        self.show_grid = False
        self.show_grid_a = QAction(
            "Show grid", self, shortcut=Qt.Key_G, checkable=True,
            triggered=self.grid_changed
        )
        actions.append(self.show_grid_a)
        self.invertX_menu = QAction(
            "Invert X", self, shortcut=Qt.Key_X, checkable=True,
            triggered=self.invertX_changed
        )
        actions.append(self.invertX_menu)
        if self.selection_type == SELECTMANY:
            select_curves = QAction(
                "Select (line)", self, triggered=self.plot.vb.set_mode_select,
            )
            select_curves.setShortcuts([Qt.Key_S])
            actions.append(select_curves)
        if self.saving_enabled:
            save_graph = QAction(
                "Save graph", self, triggered=self.save_graph,
            )
            save_graph.setShortcuts([QKeySequence(Qt.ControlModifier | Qt.Key_S)])
            actions.append(save_graph)

        range_menu = MenuFocus("Define view range", self)
        range_action = QWidgetAction(self)
        layout = QGridLayout()
        range_box = gui.widgetBox(self, margin=5, orientation=layout)
        range_box.setFocusPolicy(Qt.TabFocus)
        self.range_e_x1 = lineEditFloatOrNone(None, self, "range_x1", label="e")
        range_box.setFocusProxy(self.range_e_x1)
        self.range_e_x2 = lineEditFloatOrNone(None, self, "range_x2", label="e")
        layout.addWidget(QLabel("X"), 0, 0, Qt.AlignRight)
        layout.addWidget(self.range_e_x1, 0, 1)
        layout.addWidget(QLabel("-"), 0, 2)
        layout.addWidget(self.range_e_x2, 0, 3)
        self.range_e_y1 = lineEditFloatOrNone(None, self, "range_y1", label="e")
        self.range_e_y2 = lineEditFloatOrNone(None, self, "range_y2", label="e")
        layout.addWidget(QLabel("Y"), 1, 0, Qt.AlignRight)
        layout.addWidget(self.range_e_y1, 1, 1)
        layout.addWidget(QLabel("-"), 1, 2)
        layout.addWidget(self.range_e_y2, 1, 3)
        b = gui.button(None, self, "Apply", callback=self.set_limits)
        layout.addWidget(b, 2, 3, Qt.AlignRight)
        range_action.setDefaultWidget(range_box)
        range_menu.addAction(range_action)

        layout = QGridLayout()
        self.plotview.setLayout(layout)
        self.button = QPushButton("View", self.plotview)
        self.button.setAutoDefault(False)
        layout.setRowStretch(1, 1)
        layout.setColumnStretch(1, 1)
        layout.addWidget(self.button, 0, 0)
        view_menu = MenuFocus(self)
        self.button.setMenu(view_menu)
        view_menu.addActions(actions)
        view_menu.addMenu(range_menu)
        self.addActions(actions)

        choose_color_action = QWidgetAction(self)
        choose_color_box = gui.hBox(self)
        choose_color_box.setFocusPolicy(Qt.TabFocus)
        model = VariableListModel()
        self.attrs = []
        model.wrap(self.attrs)
        label = gui.label(choose_color_box, self, "Color by")
        label.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
        self.attrCombo = gui.comboBox(
            choose_color_box, self, value="color_attr", contentsLength=12,
            callback=self.change_color_attr)
        self.attrCombo.setModel(model)
        choose_color_box.setFocusProxy(self.attrCombo)
        choose_color_action.setDefaultWidget(choose_color_box)
        view_menu.addAction(choose_color_action)

        labels_action = QWidgetAction(self)
        layout = QGridLayout()
        labels_box = gui.widgetBox(self, margin=0, orientation=layout)
        t = gui.lineEdit(None, self, "label_title", label="Title:",
                         callback=self.labels_changed, callbackOnType=self.labels_changed)
        layout.addWidget(QLabel("Title:"), 0, 0, Qt.AlignRight)
        layout.addWidget(t, 0, 1)
        t = gui.lineEdit(None, self, "label_xaxis", label="X-axis:",
                         callback=self.labels_changed, callbackOnType=self.labels_changed)
        layout.addWidget(QLabel("X-axis:"), 1, 0, Qt.AlignRight)
        layout.addWidget(t, 1, 1)
        t = gui.lineEdit(None, self, "label_yaxis", label="Y-axis:",
                         callback=self.labels_changed, callbackOnType=self.labels_changed)
        layout.addWidget(QLabel("Y-axis:"), 2, 0, Qt.AlignRight)
        layout.addWidget(t, 2, 1)
        labels_action.setDefaultWidget(labels_box)
        view_menu.addAction(labels_action)
        self.labels_changed()  # apply saved labels

        self.invertX_apply()
        self.plot.vb.set_mode_panning()

        self.reports = {}  # current reports

        self.viewhelpers_show()

    def report(self, reporter, contents):
        self.reports[id(reporter)] = contents

    def report_finished(self, reporter):
        try:
            self.reports.pop(id(reporter))
        except KeyError:
            pass  # ok if it was already removed
        if not self.reports:
            pass

    def set_limits(self):
        vr = self.plot.vb.viewRect()
        x1 = self.range_x1 if self.range_x1 is not None else vr.left()
        x2 = self.range_x2 if self.range_x2 is not None else vr.right()
        y1 = self.range_y1 if self.range_y1 is not None else vr.top()
        y2 = self.range_y2 if self.range_y2 is not None else vr.bottom()
        self.plot.vb.setXRange(x1, x2)
        self.plot.vb.setYRange(y1, y2)

    def labels_changed(self):
        self.plot.setTitle(self.label_title)
        if not self.label_title:
            self.plot.setTitle(None)
        self.plot.setLabels(bottom=self.label_xaxis)
        self.plot.showLabel("bottom", bool(self.label_xaxis))
        self.plot.getAxis("bottom").resizeEvent()  # align text
        self.plot.setLabels(left=self.label_yaxis)
        self.plot.showLabel("left", bool(self.label_yaxis))
        self.plot.getAxis("left").resizeEvent()  # align text

    def grid_changed(self):
        self.show_grid = not self.show_grid
        self.grid_apply()

    def grid_apply(self):
        self.plot.showGrid(self.show_grid, self.show_grid, alpha=0.3)
        self.show_grid_a.setChecked(self.show_grid)

    def invertX_changed(self):
        self.invertX = not self.invertX
        self.invertX_apply()

    def invertX_apply(self):
        self.plot.vb.invertX(self.invertX)
        self.resized()
        # force redraw of axes (to avoid a pyqtgraph bug)
        vr = self.plot.vb.viewRect()
        self.plot.vb.setRange(xRange=(0,1), yRange=(0,1))
        self.plot.vb.setRange(rect=vr)
        self.invertX_menu.setChecked(self.invertX)

    def save_graph(self):
        self.viewhelpers_hide()
        self.plot.showAxis("top", True)
        self.plot.showAxis("right", True)
        self.parent.save_graph()
        self.plot.showAxis("top", False)
        self.plot.showAxis("right", False)
        self.viewhelpers_show()

    def clear_data(self, init=True):
        self.subset_ids = set()
        self.data = None
        self.data_x = None
        self.data_ys = None
        self.sampled_indices = []
        self.sampled_indices_inverse = {}
        self.sampling = None
        if not init:
            self.selection_changed()
        self.discrete_palette = None

    def clear_graph(self):
        # reset caching. if not, it is not cleared when view changing when zoomed
        self.highlighted = None
        self.curves_cont.setCacheMode(QGraphicsItem.NoCache)
        self.curves_cont.setCacheMode(QGraphicsItem.DeviceCoordinateCache)
        self.plot.vb.disableAutoRange()
        self.curves_cont.clear()
        self.curves_cont.update()
        self.plotview.clear()
        self.curves_plotted = []  # currently plotted elements (for rescale)
        self.curves = []  # for finding closest curve
        self.plotview.addItem(self.label, ignoreBounds=True)
        self.highlighted_curve = pg.PlotCurveItem(pen=self.pen_mouse)
        self.highlighted_curve.setZValue(10)
        self.highlighted_curve.hide()
        self.selection_line = pg.PlotCurveItem()
        self.selection_line.setPen(pg.mkPen(color=QColor(Qt.black), width=2, style=Qt.DotLine))
        self.selection_line.setZValue(1e9)
        self.selection_line.hide()
        self.plot.addItem(self.highlighted_curve)
        self.plot.addItem(self.vLine, ignoreBounds=True)
        self.plot.addItem(self.hLine, ignoreBounds=True)
        self.viewhelpers = True
        self.plot.addItem(self.selection_line, ignoreBounds=True)
        self.plot.addItem(self.curves_cont)
        for m in self.markings:
            self.plot.addItem(m, ignoreBounds=True)

    def resized(self):
        vr = self.plot.vb.viewRect()
        xpixel, ypixel = self.plot.vb.viewPixelSize()

        def important_decimals(n):
            return max(-int(math.floor(math.log10(n))) + 1, 0)

        self.important_decimals = important_decimals(xpixel), important_decimals(ypixel)
        if self.invertX:
            self.label.setPos(vr.bottomLeft())
        else:
            self.label.setPos(vr.bottomRight())
        xd, yd = self.important_decimals
        self.range_e_x1.setPlaceholderText(("%0." + str(xd) + "f") % vr.left())
        self.range_e_x2.setPlaceholderText(("%0." + str(xd) + "f") % vr.right())
        self.range_e_y1.setPlaceholderText(("%0." + str(yd) + "f") % vr.top())
        self.range_e_y2.setPlaceholderText(("%0." + str(yd) + "f") % vr.bottom())

    def make_selection(self, data_indices, add=False):
        selected_indices = self.selected_indices
        oldids = selected_indices.copy()
        invd = self.sampled_indices_inverse
        if data_indices is None:
            if not add:
                selected_indices.clear()
                self.set_curve_pens([invd[a] for a in oldids if a in invd])
        else:
            if add:
                selected_indices.update(data_indices)
                self.set_curve_pens([invd[a] for a in data_indices if a in invd])
            else:
                selected_indices.clear()
                selected_indices.update(data_indices)
                self.set_curve_pens([invd[a] for a in (oldids | selected_indices) if a in invd])
        self.selection_changed()

    def selection_changed(self):
        if self.selection_type:
            self.parent.selection_changed()

    def viewhelpers_hide(self):
        self.label.hide()
        self.vLine.hide()
        self.hLine.hide()

    def viewhelpers_show(self):
        self.label.show()
        if self.crosshair and not self.crosshair_hidden:
            self.vLine.show()
            self.hLine.show()
        else:
            self.vLine.hide()
            self.hLine.hide()

    def mouseMoved(self, evt):
        pos = evt[0]
        if self.plot.sceneBoundingRect().contains(pos):
            mousePoint = self.plot.vb.mapSceneToView(pos)
            posx, posy = mousePoint.x(), mousePoint.y()

            labels = []
            for a, vs in sorted(self.reports.items()):
                for v in vs:
                    if isinstance(v, tuple) and len(v) == 2:
                        if v[0] == "x":
                            labels.append(("%0." + str(self.important_decimals[0]) + "f") % v[1])
                            continue
                    labels.append(str(v))
            labels = " ".join(labels)
            self.crosshair_hidden = bool(labels)

            if self.location and not labels:
                fs = "%0." + str(self.important_decimals[0]) + "f %0." + str(self.important_decimals[1]) + "f"
                labels = fs % (posx, posy)
            self.label.setText(labels, color=(0, 0, 0))

            if self.curves and len(self.curves[0][0]): #need non-zero x axis!
                cache = {}
                bd = None
                if self.markclosest and self.plot.vb.action != ZOOMING:
                    xpixel, ypixel = self.plot.vb.viewPixelSize()
                    distances = distancetocurves(self.curves[0], posx, posy, xpixel, ypixel, r=self.MOUSE_RADIUS, cache=cache)
                    try:
                        bd = np.nanargmin(distances)
                        bd = (bd, distances[bd])
                    except ValueError:  # if all distances are NaN
                        pass
                if self.highlighted is not None:
                    self.highlighted = None
                    self.highlighted_curve.hide()
                if bd and bd[1] < self.MOUSE_RADIUS:
                    self.highlighted = bd[0]
                    x = self.curves[0][0]
                    y = self.curves[0][1][self.highlighted]
                    self.highlighted_curve.setData(x=x,y=y)
                    self.highlighted_curve.show()

            self.vLine.setPos(posx)
            self.hLine.setPos(posy)
            self.viewhelpers_show()
        else:
            self.viewhelpers_hide()

    def set_curve_pen(self, idc):
        idcdata = self.sampled_indices[idc]
        insubset = not self.subset_ids or self.data[idcdata].id in self.subset_ids
        inselected = self.selection_type and idcdata in self.selected_indices
        thispen = self.pen_subset if insubset else self.pen_normal
        if inselected:
            thispen = self.pen_selected
        color_var = self._current_color_var()
        value = None if isinstance(color_var, str) else str(self.data[idcdata][color_var])
        self.curves_cont.objs[idc].setPen(thispen[value])
        self.curves_cont.objs[idc].setZValue(int(insubset) + int(inselected))

    def set_curve_pens(self, curves=None):
        if self.viewtype == INDIVIDUAL and self.curves:
            curves = range(len(self.curves[0][1])) if curves is None else curves
            for i in curves:
                self.set_curve_pen(i)
            self.curves_cont.update()

    def add_marking(self, item):
        self.markings.append(item)
        self.plot.addItem(item, ignoreBounds=True)

    def remove_marking(self, item):
        self.plot.removeItem(item)
        self.markings.remove(item)

    def clear_markings(self):
        for m in self.markings:
            self.plot.removeItem(m)
        self.markings = []

    def add_curves(self, x, ys, addc=True):
        """ Add multiple curves with the same x domain. """
        if len(ys) > MAX_INSTANCES_DRAWN:
            self.sampled_indices = sorted(random.Random(0).sample(range(len(ys)), MAX_INSTANCES_DRAWN))
            self.sampling = True
        else:
            self.sampled_indices = list(range(len(ys)))
        random.Random(0).shuffle(self.sampled_indices) #for sequential classes#
        self.sampled_indices_inverse = {s: i for i, s in enumerate(self.sampled_indices)}
        ys = ys[self.sampled_indices]
        self.curves.append((x, ys))
        for y in ys:
            c = pg.PlotCurveItem(x=x, y=y, pen=self.pen_normal[None])
            self.curves_cont.add_curve(c)
        self.curves_plotted = self.curves

    def add_curve(self, x, y, pen=None):
        c = pg.PlotCurveItem(x=x, y=y, pen=pen if pen else self.pen_normal[None])
        self.curves_cont.add_curve(c)
        # for rescale to work correctly
        self.curves_plotted.append((x, np.array([y])))

    def add_fill_curve(self, x, ylow, yhigh, pen):
        phigh = pg.PlotCurveItem(x, yhigh, pen=pen)
        plow = pg.PlotCurveItem(x, ylow, pen=pen)
        color = pen.color()
        color.setAlphaF(0.2)
        cc = pg.mkBrush(color)
        pfill = pg.FillBetweenItem(plow, phigh, brush=cc)
        pfill.setZValue(10)
        self.curves_cont.add_curve(pfill)
        # for zoom to work correctly
        self.curves_plotted.append((x, np.array([ylow, yhigh])))

    def _current_color_var(self):
        color_var = "(Same color)"
        try:
            color_var = self.attrs[self.color_attr]
        except IndexError:
            pass
        return color_var

    def change_color_attr(self):
        self.set_pen_colors()
        self.update_view()

    def set_pen_colors(self):
        self.pen_normal.clear()
        self.pen_subset.clear()
        self.pen_selected.clear()
        color_var = self._current_color_var()
        if color_var != "(Same color)":
            colors = color_var.colors
            discrete_palette = ColorPaletteGenerator(
                number_of_colors=len(colors), rgb_colors=colors)
            for v in color_var.values:
                basecolor = discrete_palette[color_var.to_val(v)]
                basecolor = QColor(basecolor)
                basecolor.setAlphaF(0.9)
                self.pen_subset[v] = pg.mkPen(color=basecolor, width=1)
                self.pen_selected[v] = pg.mkPen(color=basecolor, width=2, style=Qt.DotLine)
                notselcolor = basecolor.lighter(150)
                notselcolor.setAlphaF(0.5)
                self.pen_normal[v] = pg.mkPen(color=notselcolor, width=1)

    def show_individual(self):
        if not self.data:
            return
        self.viewtype = INDIVIDUAL
        self.clear_graph()
        self.add_curves(self.data_x, self.data_ys)
        self.set_curve_pens()
        self.curves_cont.update()

    def rescale_current_view_y(self):
        if self.curves_plotted:
            cache = {}
            qrect = self.plot.vb.targetRect()
            bleft = qrect.left()
            bright = qrect.right()

            ymax = max(np.max(ys[:, searchsorted_cached(cache, x, bleft):
                                 searchsorted_cached(cache, x, bright, side="right")])
                       for x, ys in self.curves_plotted)
            ymin = min(np.min(ys[:, searchsorted_cached(cache, x, bleft):
                                 searchsorted_cached(cache, x, bright, side="right")])
                       for x, ys in self.curves_plotted)

            self.plot.vb.setYRange(ymin, ymax, padding=0.0)
            self.plot.vb.pad_current_view_y()

    def _split_by_color_value(self, data):
        color_var = self._current_color_var()
        rd = defaultdict(list)
        for i, inst in enumerate(data):
            value = None if isinstance(color_var, str) else str(inst[color_var])
            rd[value].append(i)
        return rd

    def show_average(self):
        if not self.data:
            return
        self.viewtype = AVERAGE
        self.clear_graph()
        x = self.data_x
        if self.data:
            ysall = []
            subset_indices = [i for i, id in enumerate(self.data.ids) if id in self.subset_ids]
            dsplit = self._split_by_color_value(self.data)
            for colorv, indices in dsplit.items():
                for part in ["everything", "subset", "selection"]:
                    if part == "everything":
                        ys = self.data_ys[indices]
                        pen = self.pen_normal if subset_indices else self.pen_subset
                    elif part == "selection" and self.selection_type:
                        current_selected = sorted(set(self.selected_indices) & set(indices))
                        if not current_selected:
                            continue
                        ys = self.data_ys[current_selected]
                        pen = self.pen_selected
                    elif part == "subset":
                        current_subset = sorted(set(subset_indices) & set(indices))
                        if not current_subset:
                            continue
                        ys = self.data_ys[current_subset]
                        pen = self.pen_subset
                    std = np.std(ys, axis=0)
                    mean = np.mean(ys, axis=0)
                    ysall.append(mean)
                    penc = QPen(pen[colorv])
                    penc.setWidth(3)
                    self.add_curve(x, mean, pen=penc)
                    self.add_fill_curve(x, mean + std, mean - std, pen=penc)
            self.curves.append((x, np.array(ysall)))
        self.curves_cont.update()

    def update_view(self):
        if self.viewtype == INDIVIDUAL:
            self.show_individual()
        elif self.viewtype == AVERAGE:
            self.show_average()

    def set_data(self, data, rescale="auto"):
        self.clear_graph()
        self.clear_data()
        self.attrs[:] = []
        if data is not None:
            self.attrs[:] = ["(Same color)"] + [
                var for var in chain(data.domain,
                                     data.domain.metas)
                if isinstance(var, str) or var.is_discrete]
            self.color_attr = 0
        self.set_pen_colors()
        if data is not None:
            if rescale == "auto":
                if self.data:
                    rescale = not data.domain == self.data.domain
                else:
                    rescale = True
            self.data = data
            # reset selection if dataset sizes do not match
            if self.selected_indices and \
                    (max(self.selected_indices) >= len(self.data) or self.data_size != len(self.data)):
                self.selected_indices.clear()
            self.data_size = len(self.data)
            # get and sort input data
            x = getx(self.data)
            xsind = np.argsort(x)
            self.data_x = x[xsind]
            self.data_ys = data.X[:, xsind]
            self.update_view()
            if rescale == True:
                self.plot.vb.autoRange()

    def update_display(self):
        self.curves_cont.update()

    def set_data_subset(self, ids):
        self.subset_ids = set(ids) if ids is not None else set()
        self.set_curve_pens()
        self.update_view()
Ejemplo n.º 5
0
class FileLoader(QWidget):
    activated = pyqtSignal()
    file_loaded = pyqtSignal()

    def __init__(self):
        super().__init__()
        self.recent_paths = []

        self.file_combo = QComboBox()
        self.file_combo.setMinimumWidth(80)
        self.file_combo.activated.connect(self._activate)

        self.browse_btn = QPushButton("...")
        icon = self.style().standardIcon(QStyle.SP_DirOpenIcon)
        self.browse_btn.setIcon(icon)
        self.browse_btn.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Fixed)
        self.browse_btn.clicked.connect(self.browse)

        self.load_btn = QPushButton("")
        icon = self.style().standardIcon(QStyle.SP_BrowserReload)
        self.load_btn.setIcon(icon)
        self.load_btn.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Fixed)
        self.load_btn.setAutoDefault(True)
        self.load_btn.clicked.connect(self.file_loaded)

    def browse(self):
        start_file = self.last_path() or os.path.expanduser("~/")
        formats = ["Text files (*.txt)", "All files (*)"]
        file_name, _ = QFileDialog.getOpenFileName(None, "Open...", start_file,
                                                   ";;".join(formats),
                                                   formats[0])
        if not file_name:
            return
        self.add_path(file_name)
        self._activate()

    def _activate(self):
        self.activated.emit()
        self.file_loaded.emit()

    def set_current_file(self, path: str):
        if path:
            self.add_path(path)
            self.file_combo.setCurrentText(path)
        else:
            self.file_combo.setCurrentText("(none)")

    def get_current_file(self) -> Optional[RecentPath]:
        index = self.file_combo.currentIndex()
        if index >= len(self.recent_paths) or index < 0:
            return None
        path = self.recent_paths[index]
        return path if isinstance(path, RecentPath) else None

    def add_path(self, filename: str):
        recent = RecentPath.create(filename, [])
        if recent in self.recent_paths:
            self.recent_paths.remove(recent)
        self.recent_paths.insert(0, recent)
        self.set_file_list()

    def set_file_list(self):
        self.file_combo.clear()
        for i, recent in enumerate(self.recent_paths):
            self.file_combo.addItem(recent.basename)
            self.file_combo.model().item(i).setToolTip(recent.abspath)
            if not os.path.exists(recent.abspath):
                self.file_combo.setItemData(i, QBrush(Qt.red),
                                            Qt.TextColorRole)
        self.file_combo.addItem(_DEFAULT_NONE)

    def last_path(self) -> Optional[str]:
        return self.recent_paths[0].abspath if self.recent_paths else None
class LineScanPlot(QWidget, OWComponent, SelectionGroupMixin,
                   ImageColorSettingMixin, ImageZoomMixin):

    attr_x = ContextSetting(None)
    gamma = Setting(0)

    selection_changed = Signal()

    def __init__(self, parent):
        QWidget.__init__(self)
        OWComponent.__init__(self, parent)
        SelectionGroupMixin.__init__(self)
        ImageColorSettingMixin.__init__(self)

        self.parent = parent

        self.selection_type = SELECTMANY
        self.saving_enabled = True
        self.selection_enabled = True
        self.viewtype = INDIVIDUAL  # required bt InteractiveViewBox
        self.highlighted = None
        self.data_points = None
        self.data_imagepixels = None

        self.plotview = pg.GraphicsLayoutWidget()

        self.plot = pg.PlotItem(background="w",
                                viewBox=InteractiveViewBox(self))
        self.plotview.addItem(self.plot)

        self.legend = ImageColorLegend()
        self.plotview.addItem(self.legend)

        self.plot.scene().installEventFilter(
            HelpEventDelegate(self.help_event, self))

        layout = QVBoxLayout()
        self.setLayout(layout)
        self.layout().setContentsMargins(0, 0, 0, 0)
        self.layout().addWidget(self.plotview)

        self.img = ImageItemNan()
        self.img.setOpts(axisOrder='row-major')
        self.plot.addItem(self.img)
        self.plot.scene().sigMouseMoved.connect(self.plot.vb.mouseMovedEvent)

        layout = QGridLayout()
        self.plotview.setLayout(layout)
        self.button = QPushButton("Menu", self.plotview)
        self.button.setAutoDefault(False)

        layout.setRowStretch(1, 1)
        layout.setColumnStretch(1, 1)
        layout.addWidget(self.button, 0, 0)
        view_menu = MenuFocus(self)
        self.button.setMenu(view_menu)

        # prepare interface according to the new context
        self.parent.contextAboutToBeOpened.connect(
            lambda x: self.init_interface_data(x[0]))

        self.add_zoom_actions(view_menu)

        common_options = dict(labelWidth=50,
                              orientation=Qt.Horizontal,
                              sendSelectedValue=True,
                              valueType=str)

        choose_xy = QWidgetAction(self)
        box = gui.vBox(self)
        box.setFocusPolicy(Qt.TabFocus)
        self.xy_model = DomainModel(DomainModel.METAS | DomainModel.CLASSES,
                                    valid_types=DomainModel.PRIMITIVE,
                                    placeholder="Position (index)")
        self.cb_attr_x = gui.comboBox(box,
                                      self,
                                      "attr_x",
                                      label="Axis x:",
                                      callback=self.update_attr,
                                      model=self.xy_model,
                                      **common_options)

        box.setFocusProxy(self.cb_attr_x)

        box.layout().addWidget(self.setup_color_settings_box())

        choose_xy.setDefaultWidget(box)
        view_menu.addAction(choose_xy)

        self.lsx = None  # info about the X axis
        self.lsy = None  # info about the Y axis

        self.data = None
        self.data_ids = {}

    def init_interface_data(self, data):
        same_domain = (self.data and data and data.domain == self.data.domain)
        if not same_domain:
            self.init_attr_values(data)

    def help_event(self, ev):
        pos = self.plot.vb.mapSceneToView(ev.scenePos())
        sel, wavenumber_ind = self._points_at_pos(pos)
        prepared = []
        if sel is not None:
            prepared.append(str(self.wavenumbers[wavenumber_ind]))
            for d in self.data[sel]:
                variables = [
                    v for v in self.data.domain.metas +
                    self.data.domain.class_vars if v not in [self.attr_x]
                ]
                features = [
                    '{} = {}'.format(attr.name, d[attr]) for attr in variables
                ]
                features.append('value = {}'.format(d[wavenumber_ind]))
                prepared.append("\n".join(features))
        text = "\n\n".join(prepared)
        if text:
            text = ('<span style="white-space:pre">{}</span>'.format(
                escape(text)))
            QToolTip.showText(ev.screenPos(), text, widget=self.plotview)
            return True
        else:
            return False

    def update_attr(self):
        self.update_view()

    def init_attr_values(self, data):
        domain = data.domain if data is not None else None
        self.xy_model.set_domain(domain)
        self.attr_x = self.xy_model[0] if self.xy_model else None

    def set_data(self, data):
        if data:
            self.data = data
            self.data_ids = {e: i for i, e in enumerate(data.ids)}
            self.restore_selection_settings()
        else:
            self.data = None
            self.data_ids = {}

    def update_view(self):
        self.img.clear()
        self.img.setSelection(None)
        self.legend.set_colors(None)
        self.lsx = None
        self.lsy = None
        self.wavenumbers = None
        self.data_xs = None
        self.data_imagepixels = None
        if self.data and len(self.data.domain.attributes):
            if self.attr_x is not None:
                xat = self.data.domain[self.attr_x]
                ndom = Domain([xat])
                datam = self.data.transform(ndom)
                coorx = datam.X[:, 0]
            else:
                coorx = np.arange(len(self.data))
            self.lsx = lsx = values_to_linspace(coorx)
            self.data_xs = coorx

            self.wavenumbers = wavenumbers = getx(self.data)
            self.lsy = lsy = values_to_linspace(wavenumbers)

            # set data
            imdata = np.ones((lsy[2], lsx[2])) * float("nan")
            xindex = index_values(coorx, lsx)
            yindex = index_values(wavenumbers, lsy)
            for xind, d in zip(xindex, self.data.X):
                imdata[yindex, xind] = d

            self.data_imagepixels = xindex

            self.img.setImage(imdata, autoLevels=False)
            self.update_levels()
            self.update_color_schema()

            # shift centres of the pixels so that the axes are useful
            shiftx = _shift(lsx)
            shifty = _shift(lsy)
            left = lsx[0] - shiftx
            bottom = lsy[0] - shifty
            width = (lsx[1] - lsx[0]) + 2 * shiftx
            height = (lsy[1] - lsy[0]) + 2 * shifty
            self.img.setRect(QRectF(left, bottom, width, height))

            self.refresh_img_selection()

    def refresh_img_selection(self):
        selected_px = np.zeros((self.lsy[2], self.lsx[2]), dtype=np.uint8)
        selected_px[:, self.data_imagepixels] = self.selection_group
        self.img.setSelection(selected_px)

    def make_selection(self, selected):
        """Add selected indices to the selection."""
        add_to_group, add_group, remove = selection_modifiers()
        if self.data and self.lsx and self.lsy:
            if add_to_group:  # both keys - need to test it before add_group
                selnum = np.max(self.selection_group)
            elif add_group:
                selnum = np.max(self.selection_group) + 1
            elif remove:
                selnum = 0
            else:
                self.selection_group *= 0
                selnum = 1
            if selected is not None:
                self.selection_group[selected] = selnum
            self.refresh_img_selection()
        self.prepare_settings_for_saving()
        self.selection_changed.emit()

    def _points_at_pos(self, pos):
        if self.data and self.lsx and self.lsy:
            x, y = pos.x(), pos.y()
            x_distance = np.abs(self.data_xs - x)
            sel = (x_distance < _shift(self.lsx))
            wavenumber_distance = np.abs(self.wavenumbers - y)
            wavenumber_ind = np.argmin(wavenumber_distance)
            return sel, wavenumber_ind
        return None, None

    def select_by_click(self, pos):
        sel, _ = self._points_at_pos(pos)
        self.make_selection(sel)
Ejemplo n.º 7
0
class ImagePlot(QWidget, OWComponent, SelectionGroupMixin,
                ImageColorSettingMixin, ImageZoomMixin):

    attr_x = ContextSetting(None)
    attr_y = ContextSetting(None)
    gamma = Setting(0)

    selection_changed = Signal()

    def __init__(self, parent):
        QWidget.__init__(self)
        OWComponent.__init__(self, parent)
        SelectionGroupMixin.__init__(self)
        ImageColorSettingMixin.__init__(self)
        ImageZoomMixin.__init__(self)
        self.parent = parent

        self.selection_type = SELECTMANY
        self.saving_enabled = True
        self.selection_enabled = True
        self.viewtype = INDIVIDUAL  # required bt InteractiveViewBox
        self.highlighted = None
        self.data_points = None
        self.data_values = None
        self.data_imagepixels = None
        self.data_valid_positions = None

        self.plotview = pg.GraphicsLayoutWidget()
        self.plot = pg.PlotItem(background="w",
                                viewBox=InteractiveViewBox(self))
        self.plotview.addItem(self.plot)

        self.legend = ImageColorLegend()
        self.plotview.addItem(self.legend)

        self.plot.scene().installEventFilter(
            HelpEventDelegate(self.help_event, self))

        layout = QVBoxLayout()
        self.setLayout(layout)
        self.layout().setContentsMargins(0, 0, 0, 0)
        self.layout().addWidget(self.plotview)

        self.img = ImageItemNan()
        self.img.setOpts(axisOrder='row-major')
        self.plot.addItem(self.img)
        self.plot.vb.setAspectLocked()
        self.plot.scene().sigMouseMoved.connect(self.plot.vb.mouseMovedEvent)

        layout = QGridLayout()
        self.plotview.setLayout(layout)
        self.button = QPushButton("Menu", self.plotview)
        self.button.setAutoDefault(False)

        layout.setRowStretch(1, 1)
        layout.setColumnStretch(1, 1)
        layout.addWidget(self.button, 0, 0)
        view_menu = MenuFocus(self)
        self.button.setMenu(view_menu)

        # prepare interface according to the new context
        self.parent.contextAboutToBeOpened.connect(
            lambda x: self.init_interface_data(x[0]))

        actions = []

        self.add_zoom_actions(view_menu)

        select_square = QAction(
            "Select (square)",
            self,
            triggered=self.plot.vb.set_mode_select_square,
        )
        select_square.setShortcuts([Qt.Key_S])
        select_square.setShortcutContext(Qt.WidgetWithChildrenShortcut)
        actions.append(select_square)

        select_polygon = QAction(
            "Select (polygon)",
            self,
            triggered=self.plot.vb.set_mode_select_polygon,
        )
        select_polygon.setShortcuts([Qt.Key_P])
        select_polygon.setShortcutContext(Qt.WidgetWithChildrenShortcut)
        actions.append(select_polygon)

        if self.saving_enabled:
            save_graph = QAction(
                "Save graph",
                self,
                triggered=self.save_graph,
            )
            save_graph.setShortcuts(
                [QKeySequence(Qt.ControlModifier | Qt.Key_I)])
            actions.append(save_graph)

        view_menu.addActions(actions)
        self.addActions(actions)

        common_options = dict(labelWidth=50,
                              orientation=Qt.Horizontal,
                              sendSelectedValue=True,
                              valueType=str)

        choose_xy = QWidgetAction(self)
        box = gui.vBox(self)
        box.setFocusPolicy(Qt.TabFocus)
        self.xy_model = DomainModel(DomainModel.METAS | DomainModel.CLASSES,
                                    valid_types=DomainModel.PRIMITIVE)
        self.cb_attr_x = gui.comboBox(box,
                                      self,
                                      "attr_x",
                                      label="Axis x:",
                                      callback=self.update_attr,
                                      model=self.xy_model,
                                      **common_options)
        self.cb_attr_y = gui.comboBox(box,
                                      self,
                                      "attr_y",
                                      label="Axis y:",
                                      callback=self.update_attr,
                                      model=self.xy_model,
                                      **common_options)
        box.setFocusProxy(self.cb_attr_x)

        box.layout().addWidget(self.color_settings_box())

        choose_xy.setDefaultWidget(box)
        view_menu.addAction(choose_xy)

        self.markings_integral = []

        self.lsx = None  # info about the X axis
        self.lsy = None  # info about the Y axis

        self.data = None
        self.data_ids = {}

    def init_interface_data(self, data):
        same_domain = (self.data and data and data.domain == self.data.domain)
        if not same_domain:
            self.init_attr_values(data)

    def help_event(self, ev):
        pos = self.plot.vb.mapSceneToView(ev.scenePos())
        sel = self._points_at_pos(pos)
        prepared = []
        if sel is not None:
            data, vals, points = self.data[sel], self.data_values[
                sel], self.data_points[sel]
            for d, v, p in zip(data, vals, points):
                basic = "({}, {}): {}".format(p[0], p[1], v)
                variables = [
                    v for v in self.data.domain.metas +
                    self.data.domain.class_vars
                    if v not in [self.attr_x, self.attr_y]
                ]
                features = [
                    '{} = {}'.format(attr.name, d[attr]) for attr in variables
                ]
                prepared.append("\n".join([basic] + features))
        text = "\n\n".join(prepared)
        if text:
            text = ('<span style="white-space:pre">{}</span>'.format(
                escape(text)))
            QToolTip.showText(ev.screenPos(), text, widget=self.plotview)
            return True
        else:
            return False

    def update_attr(self):
        self.update_view()

    def init_attr_values(self, data):
        domain = data.domain if data is not None else None
        self.xy_model.set_domain(domain)
        self.attr_x = self.xy_model[0] if self.xy_model else None
        self.attr_y = self.xy_model[1] if len(self.xy_model) >= 2 \
            else self.attr_x

    def save_graph(self):
        saveplot.save_plot(self.plotview, self.parent.graph_writers)

    def set_data(self, data):
        if data:
            self.data = data
            self.data_ids = {e: i for i, e in enumerate(data.ids)}
            self.restore_selection_settings()
        else:
            self.data = None
            self.data_ids = {}

    def refresh_markings(self, di):
        refresh_integral_markings([{
            "draw": di
        }], self.markings_integral, self.parent.curveplot)

    def update_view(self):
        self.parent.Error.image_too_big.clear()
        self.parent.Information.not_shown.clear()
        self.img.clear()
        self.img.setSelection(None)
        self.legend.set_colors(None)
        self.lsx = None
        self.lsy = None
        self.data_points = None
        self.data_values = None
        self.data_imagepixels = None
        self.data_valid_positions = None
        if self.data and self.attr_x and self.attr_y:
            xat = self.data.domain[self.attr_x]
            yat = self.data.domain[self.attr_y]

            ndom = Orange.data.Domain([xat, yat])
            datam = Orange.data.Table(ndom, self.data)
            coorx = datam.X[:, 0]
            coory = datam.X[:, 1]
            self.data_points = datam.X
            self.lsx = lsx = values_to_linspace(coorx)
            self.lsy = lsy = values_to_linspace(coory)
            if lsx[-1] * lsy[-1] > IMAGE_TOO_BIG:
                self.parent.Error.image_too_big(lsx[-1], lsy[-1])
                return

            di = {}
            if self.parent.value_type == 0:  # integrals
                imethod = self.parent.integration_methods[
                    self.parent.integration_method]

                if imethod != Integrate.PeakAt:
                    datai = Integrate(
                        methods=imethod,
                        limits=[[self.parent.lowlim,
                                 self.parent.highlim]])(self.data)
                else:
                    datai = Integrate(
                        methods=imethod,
                        limits=[[self.parent.choose,
                                 self.parent.choose]])(self.data)

                if np.any(self.parent.curveplot.selection_group):
                    # curveplot can have a subset of curves on the input> match IDs
                    ind = np.flatnonzero(
                        self.parent.curveplot.selection_group)[0]
                    dind = self.data_ids[self.parent.curveplot.data[ind].id]
                    di = datai.domain.attributes[0].compute_value.draw_info(
                        self.data[dind:dind + 1])
                d = datai.X[:, 0]
            else:
                dat = self.data.domain[self.parent.attr_value]
                ndom = Orange.data.Domain([dat])
                d = Orange.data.Table(ndom, self.data).X[:, 0]
            self.refresh_markings(di)

            xindex, xnan = index_values_nan(coorx, lsx)
            yindex, ynan = index_values_nan(coory, lsy)
            self.data_valid_positions = valid = np.logical_not(
                np.logical_or(xnan, ynan))
            invalid_positions = len(d) - np.sum(valid)
            if invalid_positions:
                self.parent.Information.not_shown(invalid_positions)

            imdata = np.ones((lsy[2], lsx[2])) * float("nan")
            imdata[yindex[valid], xindex[valid]] = d[valid]
            self.data_values = d
            self.data_imagepixels = np.vstack((yindex, xindex)).T

            self.img.setImage(imdata, autoLevels=False)
            self.update_levels()
            self.update_color_schema()

            # shift centres of the pixels so that the axes are useful
            shiftx = _shift(lsx)
            shifty = _shift(lsy)
            left = lsx[0] - shiftx
            bottom = lsy[0] - shifty
            width = (lsx[1] - lsx[0]) + 2 * shiftx
            height = (lsy[1] - lsy[0]) + 2 * shifty
            self.img.setRect(QRectF(left, bottom, width, height))

            self.refresh_img_selection()

    def refresh_img_selection(self):
        selected_px = np.zeros((self.lsy[2], self.lsx[2]), dtype=np.uint8)
        selected_px[self.data_imagepixels[self.data_valid_positions, 0],
                    self.data_imagepixels[self.data_valid_positions, 1]] = \
            self.selection_group[self.data_valid_positions]
        self.img.setSelection(selected_px)

    def make_selection(self, selected):
        """Add selected indices to the selection."""
        add_to_group, add_group, remove = selection_modifiers()
        if self.data and self.lsx and self.lsy:
            if add_to_group:  # both keys - need to test it before add_group
                selnum = np.max(self.selection_group)
            elif add_group:
                selnum = np.max(self.selection_group) + 1
            elif remove:
                selnum = 0
            else:
                self.selection_group *= 0
                selnum = 1
            if selected is not None:
                self.selection_group[selected] = selnum
            self.refresh_img_selection()
        self.prepare_settings_for_saving()
        self.selection_changed.emit()

    def select_square(self, p1, p2):
        """ Select elements within a square drawn by the user.
        A selection needs to contain whole pixels """
        x1, y1 = p1.x(), p1.y()
        x2, y2 = p2.x(), p2.y()
        polygon = [
            QPointF(x1, y1),
            QPointF(x2, y1),
            QPointF(x2, y2),
            QPointF(x1, y2),
            QPointF(x1, y1)
        ]
        self.select_polygon(polygon)

    def select_polygon(self, polygon):
        """ Select by a polygon which has to contain whole pixels. """
        if self.data and self.lsx and self.lsy:
            polygon = [(p.x(), p.y()) for p in polygon]
            # a polygon should contain all pixel
            shiftx = _shift(self.lsx)
            shifty = _shift(self.lsy)
            points_edges = [
                self.data_points + [[shiftx, shifty]],
                self.data_points + [[-shiftx, shifty]],
                self.data_points + [[shiftx, -shifty]],
                self.data_points + [[-shiftx, -shifty]]
            ]
            inp = in_polygon(points_edges[0], polygon)
            for p in points_edges[1:]:
                inp *= in_polygon(p, polygon)
            self.make_selection(inp)

    def _points_at_pos(self, pos):
        if self.data and self.lsx and self.lsy:
            x, y = pos.x(), pos.y()
            distance = np.abs(self.data_points - [[x, y]])
            sel = (distance[:, 0] < _shift(self.lsx)) * (distance[:, 1] <
                                                         _shift(self.lsy))
            return sel

    def select_by_click(self, pos):
        sel = self._points_at_pos(pos)
        self.make_selection(sel)
Ejemplo n.º 8
0
class ImagePlot(QWidget, OWComponent):

    attr_x = ContextSetting(None)
    attr_y = ContextSetting(None)
    gamma = Setting(0)
    threshold_low = Setting(0.0)
    threshold_high = Setting(1.0)

    def __init__(self, parent):
        QWidget.__init__(self)
        OWComponent.__init__(self, parent)

        self.parent = parent

        self.selection_enabled = False

        self.plotview = pg.PlotWidget(background="w",
                                      viewBox=InteractiveViewBox(self))
        self.plot = self.plotview.getPlotItem()

        layout = QVBoxLayout()
        self.setLayout(layout)
        self.layout().setContentsMargins(0, 0, 0, 0)
        self.layout().addWidget(self.plotview)

        self.img = ImageItemNan()
        self.img.setOpts(axisOrder='row-major')
        self.plot.addItem(self.img)
        self.plot.vb.setAspectLocked()

        layout = QGridLayout()
        self.plotview.setLayout(layout)
        self.button = QPushButton("View", self.plotview)
        self.button.setAutoDefault(False)

        layout.setRowStretch(1, 1)
        layout.setColumnStretch(1, 1)
        layout.addWidget(self.button, 0, 0)
        view_menu = MenuFocus(self)
        self.button.setMenu(view_menu)

        common_options = dict(labelWidth=50,
                              orientation=Qt.Horizontal,
                              sendSelectedValue=True,
                              valueType=str)

        choose_xy = QWidgetAction(self)
        box = gui.vBox(self)
        box.setFocusPolicy(Qt.TabFocus)
        self.xy_model = DomainModel(DomainModel.METAS | DomainModel.CLASSES,
                                    valid_types=DomainModel.PRIMITIVE)
        self.models = [self.xy_model]
        self.cb_attr_x = gui.comboBox(box,
                                      self,
                                      "attr_x",
                                      label="Axis x:",
                                      callback=self.update_attr,
                                      model=self.xy_model,
                                      **common_options)
        self.cb_attr_y = gui.comboBox(box,
                                      self,
                                      "attr_y",
                                      label="Axis y:",
                                      callback=self.update_attr,
                                      model=self.xy_model,
                                      **common_options)
        box.setFocusProxy(self.cb_attr_x)

        form = QFormLayout(formAlignment=Qt.AlignLeft,
                           labelAlignment=Qt.AlignLeft,
                           fieldGrowthPolicy=QFormLayout.AllNonFixedFieldsGrow)

        lowslider = gui.hSlider(box,
                                self,
                                "threshold_low",
                                minValue=0.0,
                                maxValue=1.0,
                                step=0.05,
                                ticks=True,
                                intOnly=False,
                                createLabel=False,
                                callback=self.update_color_schema)
        highslider = gui.hSlider(box,
                                 self,
                                 "threshold_high",
                                 minValue=0.0,
                                 maxValue=1.0,
                                 step=0.05,
                                 ticks=True,
                                 intOnly=False,
                                 createLabel=False,
                                 callback=self.update_color_schema)
        gammaslider = gui.hSlider(box,
                                  self,
                                  "gamma",
                                  minValue=0.0,
                                  maxValue=20.0,
                                  step=1.0,
                                  ticks=True,
                                  intOnly=False,
                                  createLabel=False,
                                  callback=self.update_color_schema)

        form.addRow("Low:", lowslider)
        form.addRow("High:", highslider)
        form.addRow("Gamma:", gammaslider)

        box.layout().addLayout(form)

        choose_xy.setDefaultWidget(box)
        view_menu.addAction(choose_xy)

        self.markings_integral = []

        self.data = None

    def update_color_schema(self):
        if not self.threshold_low < self.threshold_high:
            # TODO this belongs here, not in the parent
            self.parent.Warning.threshold_error()
            return
        else:
            self.parent.Warning.threshold_error.clear()
        # TODO add color chooser
        colors = [(0, 0, 255), (255, 255, 0)]
        cols = color_palette_table(colors,
                                   threshold_low=self.threshold_low,
                                   threshold_high=self.threshold_high,
                                   gamma=self.gamma)
        self.img.setLookupTable(cols)

    def update_attr(self):
        self.show_data()

    def init_attr_values(self):
        domain = self.data and self.data.domain
        for model in self.models:
            model.set_domain(domain)
        self.attr_x = self.xy_model[0] if self.xy_model else None
        self.attr_y = self.xy_model[1] if len(self.xy_model) >= 2 \
            else self.attr_x

    def set_data(self, data):
        self.img.clear()
        if data is not None:
            same_domain = (self.data and data.domain.checksum()
                           == self.data.domain.checksum())
            self.data = data
            if not same_domain:
                self.init_attr_values()
        self.show_data()

    def set_integral_limits(self):
        self.show_data()

    def refresh_markings(self, di):

        for m in self.markings_integral:
            self.parent.curveplot.remove_marking(m)
        self.markings_integral = []

        color = Qt.red

        def add_marking(a):
            self.markings_integral.append(a)
            self.parent.curveplot.add_marking(a)

        if "baseline" in di:
            bs_x, bs_ys = di["baseline"]
            baseline = pg.PlotCurveItem()
            baseline.setPen(
                pg.mkPen(color=QColor(color), width=2, style=Qt.DotLine))
            baseline.setZValue(10)
            baseline.setData(x=bs_x, y=bs_ys[0])
            add_marking(baseline)

        if "curve" in di:
            bs_x, bs_ys = di["curve"]
            curve = pg.PlotCurveItem()
            curve.setPen(pg.mkPen(color=QColor(color), width=2))
            curve.setZValue(10)
            curve.setData(x=bs_x, y=bs_ys[0])
            add_marking(curve)

        if "fill" in di:
            (x1, ys1), (x2, ys2) = di["fill"]
            phigh = pg.PlotCurveItem(x1, ys1[0], pen=None)
            plow = pg.PlotCurveItem(x2, ys2[0], pen=None)
            color = QColor(color)
            color.setAlphaF(0.5)
            cc = pg.mkBrush(color)
            pfill = pg.FillBetweenItem(plow, phigh, brush=cc)
            pfill.setZValue(9)
            add_marking(pfill)

        if "line" in di:
            (x1, y1), (x2, y2) = di["line"]
            line = pg.PlotCurveItem()
            line.setPen(pg.mkPen(color=QColor(color), width=4))
            line.setZValue(10)
            line.setData(x=[x1[0], x2[0]], y=[y1[0], y2[0]])
            add_marking(line)

    def show_data(self):
        self.img.clear()
        if self.data:
            xat = self.data.domain[self.attr_x]
            yat = self.data.domain[self.attr_y]

            ndom = Orange.data.Domain([xat, yat])
            datam = Orange.data.Table(ndom, self.data)
            coorx = datam.X[:, 0]
            coory = datam.X[:, 1]
            lsx = values_to_linspace(coorx)
            lsy = values_to_linspace(coory)

            l1, l2 = self.parent.lowlim, self.parent.highlim

            gx = getx(self.data)

            if l1 is None:
                l1 = min(gx) - 1
            if l2 is None:
                l2 = max(gx) + 1

            l1, l2 = min(l1, l2), max(l1, l2)

            imethod = self.parent.integration_methods[
                self.parent.integration_method]
            datai = Integrate(method=imethod, limits=[[l1, l2]])(self.data)

            di = {}
            if self.parent.curveplot.selected_indices:
                ind = list(self.parent.curveplot.selected_indices)[0]
                di = datai.domain.attributes[0].compute_value.draw_info(
                    self.data[ind:ind + 1])
            self.refresh_markings(di)

            d = datai.X[:, 0]

            # set data
            imdata = np.ones((lsy[2], lsx[2])) * float("nan")
            xindex = index_values(coorx, lsx)
            yindex = index_values(coory, lsy)
            imdata[yindex, xindex] = d

            levels = get_levels(imdata)
            self.update_color_schema()

            self.img.setImage(imdata, levels=levels)

            # shift centres of the pixels so that the axes are useful
            shiftx = (lsx[1] - lsx[0]) / (2 * (lsx[2] - 1))
            shifty = (lsy[1] - lsy[0]) / (2 * (lsy[2] - 1))
            left = lsx[0] - shiftx
            bottom = lsy[0] - shifty
            width = (lsx[1] - lsx[0]) + 2 * shiftx
            height = (lsy[1] - lsy[0]) + 2 * shifty
            self.img.setRect(QRectF(left, bottom, width, height))
Ejemplo n.º 9
0
class CurvePlot(QWidget, OWComponent):
    sample_seed = Setting(0, schema_only=True)
    label_title = Setting("")
    label_xaxis = Setting("")
    label_yaxis = Setting("")
    range_x1 = Setting(None)
    range_x2 = Setting(None)
    range_y1 = Setting(None)
    range_y2 = Setting(None)
    feature_color = ContextSetting(None)

    invertX = Setting(False)
    selected_indices = Setting(set())
    data_size = Setting(None)  # to invalidate selected_indices
    viewtype = Setting(INDIVIDUAL)

    def __init__(self, parent=None, select=SELECTNONE):
        QWidget.__init__(self)
        OWComponent.__init__(self, parent)

        self.parent = parent

        self.selection_type = select
        self.saving_enabled = hasattr(self.parent, "save_graph")
        self.clear_data(init=True)
        self.subset = None  # current subset input
        self.subset_indices = None  # boolean index array with indices in self.data

        self.plotview = pg.PlotWidget(background="w",
                                      viewBox=InteractiveViewBoxC(self))
        self.plot = self.plotview.getPlotItem()
        self.plot.setDownsampling(auto=True, mode="peak")

        self.markings = []
        self.vLine = pg.InfiniteLine(angle=90, movable=False)
        self.hLine = pg.InfiniteLine(angle=0, movable=False)
        self.proxy = pg.SignalProxy(self.plot.scene().sigMouseMoved,
                                    rateLimit=20,
                                    slot=self.mouseMoved,
                                    delay=0.1)
        self.plot.scene().sigMouseMoved.connect(self.plot.vb.mouseMovedEvent)
        self.plot.vb.sigRangeChanged.connect(self.resized)
        self.pen_mouse = pg.mkPen(color=(0, 0, 255), width=2)
        self.pen_normal = defaultdict(
            lambda: pg.mkPen(color=(200, 200, 200, 127), width=1))
        self.pen_subset = defaultdict(
            lambda: pg.mkPen(color=(0, 0, 0, 127), width=1))
        self.pen_selected = defaultdict(
            lambda: pg.mkPen(color=(0, 0, 0, 127), width=2, style=Qt.DotLine))
        self.label = pg.TextItem("", anchor=(1, 0))
        self.label.setText("", color=(0, 0, 0))
        self.discrete_palette = None
        QPixmapCache.setCacheLimit(max(QPixmapCache.cacheLimit(), 100 * 1024))
        self.curves_cont = PlotCurvesItem()
        self.important_decimals = 4, 4

        self.plot.scene().installEventFilter(
            HelpEventDelegate(self.help_event, self))

        # whether to rescale at next update
        self.rescale_next = True

        self.MOUSE_RADIUS = 20

        self.clear_graph()

        # interface settings
        self.location = True  # show current position
        self.markclosest = True  # mark
        self.crosshair = True
        self.crosshair_hidden = True

        layout = QVBoxLayout()
        self.setLayout(layout)
        self.layout().setContentsMargins(0, 0, 0, 0)
        self.layout().addWidget(self.plotview)

        actions = []

        resample_curves = QAction(
            "Resample curves",
            self,
            shortcut=Qt.Key_R,
            triggered=lambda x: self.resample_curves(self.sample_seed + 1))
        actions.append(resample_curves)
        reset_curves = QAction("Resampling reset",
                               self,
                               shortcut=QKeySequence(Qt.ControlModifier
                                                     | Qt.Key_R),
                               triggered=lambda x: self.resample_curves(0))
        actions.append(reset_curves)

        zoom_in = QAction("Zoom in",
                          self,
                          triggered=self.plot.vb.set_mode_zooming)
        zoom_in.setShortcuts([Qt.Key_Z, QKeySequence(QKeySequence.ZoomIn)])
        zoom_in.setShortcutContext(Qt.WidgetWithChildrenShortcut)
        actions.append(zoom_in)
        zoom_fit = QAction(
            "Zoom to fit",
            self,
            triggered=lambda x:
            (self.plot.vb.autoRange(), self.plot.vb.set_mode_panning()))
        zoom_fit.setShortcuts(
            [Qt.Key_Backspace,
             QKeySequence(Qt.ControlModifier | Qt.Key_0)])
        zoom_fit.setShortcutContext(Qt.WidgetWithChildrenShortcut)
        actions.append(zoom_fit)
        rescale_y = QAction("Rescale Y to fit",
                            self,
                            shortcut=Qt.Key_D,
                            triggered=self.rescale_current_view_y)

        actions.append(rescale_y)
        self.view_average_menu = QAction(
            "Show averages",
            self,
            shortcut=Qt.Key_A,
            checkable=True,
            triggered=lambda x: self.viewtype_changed())
        actions.append(self.view_average_menu)

        self.show_grid = False
        self.show_grid_a = QAction("Show grid",
                                   self,
                                   shortcut=Qt.Key_G,
                                   checkable=True,
                                   triggered=self.grid_changed)
        actions.append(self.show_grid_a)
        self.invertX_menu = QAction("Invert X",
                                    self,
                                    shortcut=Qt.Key_X,
                                    checkable=True,
                                    triggered=self.invertX_changed)
        actions.append(self.invertX_menu)
        if self.selection_type == SELECTMANY:
            select_curves = QAction(
                "Select (line)",
                self,
                triggered=self.line_select_start,
            )
            select_curves.setShortcuts([Qt.Key_S])
            select_curves.setShortcutContext(Qt.WidgetWithChildrenShortcut)
            actions.append(select_curves)
        if self.saving_enabled:
            save_graph = QAction(
                "Save graph",
                self,
                triggered=self.save_graph,
            )
            save_graph.setShortcuts(
                [QKeySequence(Qt.ControlModifier | Qt.Key_S)])
            actions.append(save_graph)

        range_menu = MenuFocus("Define view range", self)
        range_action = QWidgetAction(self)
        layout = QGridLayout()
        range_box = gui.widgetBox(self, margin=5, orientation=layout)
        range_box.setFocusPolicy(Qt.TabFocus)
        self.range_e_x1 = lineEditFloatOrNone(None,
                                              self,
                                              "range_x1",
                                              label="e")
        range_box.setFocusProxy(self.range_e_x1)
        self.range_e_x2 = lineEditFloatOrNone(None,
                                              self,
                                              "range_x2",
                                              label="e")
        layout.addWidget(QLabel("X"), 0, 0, Qt.AlignRight)
        layout.addWidget(self.range_e_x1, 0, 1)
        layout.addWidget(QLabel("-"), 0, 2)
        layout.addWidget(self.range_e_x2, 0, 3)
        self.range_e_y1 = lineEditFloatOrNone(None,
                                              self,
                                              "range_y1",
                                              label="e")
        self.range_e_y2 = lineEditFloatOrNone(None,
                                              self,
                                              "range_y2",
                                              label="e")
        layout.addWidget(QLabel("Y"), 1, 0, Qt.AlignRight)
        layout.addWidget(self.range_e_y1, 1, 1)
        layout.addWidget(QLabel("-"), 1, 2)
        layout.addWidget(self.range_e_y2, 1, 3)
        b = gui.button(None, self, "Apply", callback=self.set_limits)
        layout.addWidget(b, 2, 3, Qt.AlignRight)
        range_action.setDefaultWidget(range_box)
        range_menu.addAction(range_action)

        layout = QGridLayout()
        self.plotview.setLayout(layout)
        self.button = QPushButton("View", self.plotview)
        self.button.setAutoDefault(False)
        layout.setRowStretch(1, 1)
        layout.setColumnStretch(1, 1)
        layout.addWidget(self.button, 0, 0)
        view_menu = MenuFocus(self)
        self.button.setMenu(view_menu)
        view_menu.addActions(actions)
        view_menu.addMenu(range_menu)
        self.addActions(actions)

        choose_color_action = QWidgetAction(self)
        choose_color_box = gui.hBox(self)
        choose_color_box.setFocusPolicy(Qt.TabFocus)
        label = gui.label(choose_color_box, self, "Color by")
        label.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
        self.feature_color_model = DomainModel(
            DomainModel.METAS | DomainModel.CLASSES,
            valid_types=(DiscreteVariable, ),
            placeholder="None")
        self.feature_color_combo = gui.comboBox(choose_color_box,
                                                self,
                                                "feature_color",
                                                callback=self.update_view,
                                                model=self.feature_color_model,
                                                valueType=str)
        choose_color_box.setFocusProxy(self.feature_color_combo)
        choose_color_action.setDefaultWidget(choose_color_box)
        view_menu.addAction(choose_color_action)

        cycle_colors = QShortcut(Qt.Key_C,
                                 self,
                                 self.cycle_color_attr,
                                 context=Qt.WidgetWithChildrenShortcut)

        labels_action = QWidgetAction(self)
        layout = QGridLayout()
        labels_box = gui.widgetBox(self, margin=0, orientation=layout)
        t = gui.lineEdit(None,
                         self,
                         "label_title",
                         label="Title:",
                         callback=self.labels_changed,
                         callbackOnType=self.labels_changed)
        layout.addWidget(QLabel("Title:"), 0, 0, Qt.AlignRight)
        layout.addWidget(t, 0, 1)
        t = gui.lineEdit(None,
                         self,
                         "label_xaxis",
                         label="X-axis:",
                         callback=self.labels_changed,
                         callbackOnType=self.labels_changed)
        layout.addWidget(QLabel("X-axis:"), 1, 0, Qt.AlignRight)
        layout.addWidget(t, 1, 1)
        t = gui.lineEdit(None,
                         self,
                         "label_yaxis",
                         label="Y-axis:",
                         callback=self.labels_changed,
                         callbackOnType=self.labels_changed)
        layout.addWidget(QLabel("Y-axis:"), 2, 0, Qt.AlignRight)
        layout.addWidget(t, 2, 1)
        labels_action.setDefaultWidget(labels_box)
        view_menu.addAction(labels_action)
        self.labels_changed()  # apply saved labels

        self.invertX_apply()
        self.plot.vb.set_mode_panning()

        self.reports = {}  # current reports

        self.viewhelpers_show()

    def line_select_start(self):
        if self.viewtype == INDIVIDUAL:
            self.plot.vb.set_mode_select()

    def help_event(self, ev):
        text = ""
        if self.highlighted is not None:
            if self.viewtype == INDIVIDUAL:
                index = self.sampled_indices[self.highlighted]
                variables = self.data.domain.metas + self.data.domain.class_vars
                text += "".join(
                    '{} = {}\n'.format(attr.name, self.data[index][attr])
                    for attr in variables)
            elif self.viewtype == AVERAGE:
                c = self.multiple_curves_info[self.highlighted]
                nc = sum(c[2])
                if c[0] is not None:
                    text += str(c[0]) + " "
                if c[1]:
                    text += "({})".format(c[1])
                if text:
                    text += "\n"
                text += "{} curves".format(nc)
        if text:
            text = text.rstrip()
            text = ('<span style="white-space:pre">{}</span>'.format(
                escape(text)))
            QToolTip.showText(ev.screenPos(), text, widget=self.plotview)
            return True
        else:
            return False

    def report(self, reporter, contents):
        self.reports[id(reporter)] = contents

    def report_finished(self, reporter):
        try:
            self.reports.pop(id(reporter))
        except KeyError:
            pass  # ok if it was already removed
        if not self.reports:
            pass

    def cycle_color_attr(self):
        elements = [(a.name if isinstance(a, Variable) else a)
                    for a in self.feature_color_model]
        currentind = 0
        try:
            currentind = elements.index(self.feature_color)
        except ValueError:
            pass
        next = (currentind + 1) % len(self.feature_color_model)
        self.feature_color = elements[next]
        self.update_view()

    def set_limits(self):
        vr = self.plot.vb.viewRect()
        x1 = self.range_x1 if self.range_x1 is not None else vr.left()
        x2 = self.range_x2 if self.range_x2 is not None else vr.right()
        y1 = self.range_y1 if self.range_y1 is not None else vr.top()
        y2 = self.range_y2 if self.range_y2 is not None else vr.bottom()
        self.plot.vb.setXRange(x1, x2)
        self.plot.vb.setYRange(y1, y2)

    def labels_changed(self):
        self.plot.setTitle(self.label_title)
        if not self.label_title:
            self.plot.setTitle(None)
        self.plot.setLabels(bottom=self.label_xaxis)
        self.plot.showLabel("bottom", bool(self.label_xaxis))
        self.plot.getAxis("bottom").resizeEvent()  # align text
        self.plot.setLabels(left=self.label_yaxis)
        self.plot.showLabel("left", bool(self.label_yaxis))
        self.plot.getAxis("left").resizeEvent()  # align text

    def grid_changed(self):
        self.show_grid = not self.show_grid
        self.grid_apply()

    def grid_apply(self):
        self.plot.showGrid(self.show_grid, self.show_grid, alpha=0.3)
        self.show_grid_a.setChecked(self.show_grid)

    def invertX_changed(self):
        self.invertX = not self.invertX
        self.invertX_apply()

    def invertX_apply(self):
        self.plot.vb.invertX(self.invertX)
        self.resized()
        # force redraw of axes (to avoid a pyqtgraph bug)
        vr = self.plot.vb.viewRect()
        self.plot.vb.setRange(xRange=(0, 1), yRange=(0, 1))
        self.plot.vb.setRange(rect=vr)
        self.invertX_menu.setChecked(self.invertX)

    def save_graph(self):
        self.viewhelpers_hide()
        self.plot.showAxis("top", True)
        self.plot.showAxis("right", True)
        self.parent.save_graph()
        self.plot.showAxis("top", False)
        self.plot.showAxis("right", False)
        self.viewhelpers_show()

    def clear_data(self, init=True):
        self.data = None
        self.data_x = None  # already sorted x-axis
        self.data_xsind = None  # sorting indices for x-axis
        self.sampled_indices = []
        self.sampled_indices_inverse = {}
        self.sampling = None
        if not init:
            self.selection_changed()
        self.discrete_palette = None

    def clear_graph(self):
        # reset caching. if not, it is not cleared when view changing when zoomed
        self.highlighted = None
        self.curves_cont.setCacheMode(QGraphicsItem.NoCache)
        self.curves_cont.setCacheMode(QGraphicsItem.DeviceCoordinateCache)
        self.plot.vb.disableAutoRange()
        self.curves_cont.clear()
        self.curves_cont.update()
        self.plotview.clear()
        self.multiple_curves_info = []
        self.curves_plotted = []  # currently plotted elements (for rescale)
        self.curves = []  # for finding closest curve
        self.plotview.addItem(self.label, ignoreBounds=True)
        self.highlighted_curve = pg.PlotCurveItem(pen=self.pen_mouse)
        self.highlighted_curve.setZValue(10)
        self.highlighted_curve.hide()
        self.plot.addItem(self.highlighted_curve)
        self.plot.addItem(self.vLine, ignoreBounds=True)
        self.plot.addItem(self.hLine, ignoreBounds=True)
        self.viewhelpers = True
        self.plot.addItem(self.curves_cont)
        for m in self.markings:
            self.plot.addItem(m, ignoreBounds=True)

    def resized(self):
        vr = self.plot.vb.viewRect()
        xpixel, ypixel = self.plot.vb.viewPixelSize()

        def important_decimals(n):
            return max(-int(math.floor(math.log10(n))) + 1, 0)

        self.important_decimals = important_decimals(
            xpixel), important_decimals(ypixel)
        if self.invertX:
            self.label.setPos(vr.bottomLeft())
        else:
            self.label.setPos(vr.bottomRight())
        xd, yd = self.important_decimals
        self.range_e_x1.setPlaceholderText(("%0." + str(xd) + "f") % vr.left())
        self.range_e_x2.setPlaceholderText(
            ("%0." + str(xd) + "f") % vr.right())
        self.range_e_y1.setPlaceholderText(("%0." + str(yd) + "f") % vr.top())
        self.range_e_y2.setPlaceholderText(
            ("%0." + str(yd) + "f") % vr.bottom())

    def make_selection(self, data_indices, add=False):
        selected_indices = self.selected_indices
        oldids = selected_indices.copy()
        invd = self.sampled_indices_inverse
        if data_indices is None:
            if not add:
                selected_indices.clear()
                self.set_curve_pens([invd[a] for a in oldids if a in invd])
        else:
            if add:
                selected_indices.update(data_indices)
                self.set_curve_pens(
                    [invd[a] for a in data_indices if a in invd])
            else:
                selected_indices.clear()
                selected_indices.update(data_indices)
                self.set_curve_pens([
                    invd[a] for a in (oldids | selected_indices) if a in invd
                ])
        self.selection_changed()

    def selection_changed(self):
        if self.selection_type:
            self.parent.selection_changed()

    def viewhelpers_hide(self):
        self.label.hide()
        self.vLine.hide()
        self.hLine.hide()

    def viewhelpers_show(self):
        self.label.show()
        if self.crosshair and not self.crosshair_hidden:
            self.vLine.show()
            self.hLine.show()
        else:
            self.vLine.hide()
            self.hLine.hide()

    def mouseMoved(self, evt):
        pos = evt[0]
        if self.plot.sceneBoundingRect().contains(pos):
            mousePoint = self.plot.vb.mapSceneToView(pos)
            posx, posy = mousePoint.x(), mousePoint.y()

            labels = []
            for a, vs in sorted(self.reports.items()):
                for v in vs:
                    if isinstance(v, tuple) and len(v) == 2:
                        if v[0] == "x":
                            labels.append(
                                ("%0." + str(self.important_decimals[0]) + "f")
                                % v[1])
                            continue
                    labels.append(str(v))
            labels = " ".join(labels)
            self.crosshair_hidden = bool(labels)

            if self.location and not labels:
                fs = "%0." + str(self.important_decimals[0]) + "f %0." + str(
                    self.important_decimals[1]) + "f"
                labels = fs % (posx, posy)
            self.label.setText(labels, color=(0, 0, 0))

            if self.curves and len(self.curves[0][0]):  # need non-zero x axis!
                cache = {}
                bd = None
                if self.markclosest and self.plot.vb.action != ZOOMING:
                    xpixel, ypixel = self.plot.vb.viewPixelSize()
                    distances = distancetocurves(self.curves[0],
                                                 posx,
                                                 posy,
                                                 xpixel,
                                                 ypixel,
                                                 r=self.MOUSE_RADIUS,
                                                 cache=cache)
                    try:
                        mindi = np.nanargmin(distances)
                        if distances[mindi] < self.MOUSE_RADIUS:
                            bd = mindi
                    except ValueError:  # if all distances are NaN
                        pass
                if self.highlighted != bd:
                    QToolTip.hideText()
                if self.highlighted is not None and bd is None:
                    self.highlighted = None
                    self.highlighted_curve.hide()
                if bd is not None:
                    self.highlighted = bd
                    x = self.curves[0][0]
                    y = self.curves[0][1][self.highlighted]
                    self.highlighted_curve.setData(x=x, y=y)
                    self.highlighted_curve.show()

            self.vLine.setPos(posx)
            self.hLine.setPos(posy)
            self.viewhelpers_show()
        else:
            self.viewhelpers_hide()

    def set_curve_pen(self, idc):
        idcdata = self.sampled_indices[idc]
        insubset = self.subset_indices[idcdata]
        inselected = self.selection_type and idcdata in self.selected_indices
        have_subset = np.any(self.subset_indices)
        thispen = self.pen_subset if insubset or not have_subset else self.pen_normal
        if inselected:
            thispen = self.pen_selected
        color_var = self._current_color_var()
        value = None if color_var is None else str(
            self.data[idcdata][color_var])
        self.curves_cont.objs[idc].setPen(thispen[value])
        self.curves_cont.objs[idc].setZValue(int(insubset) + int(inselected))

    def set_curve_pens(self, curves=None):
        if self.viewtype == INDIVIDUAL and self.curves:
            curves = range(len(
                self.curves[0][1])) if curves is None else curves
            for i in curves:
                self.set_curve_pen(i)
            self.curves_cont.update()

    def add_marking(self, item):
        self.markings.append(item)
        self.plot.addItem(item, ignoreBounds=True)

    def remove_marking(self, item):
        self.plot.removeItem(item)
        self.markings.remove(item)

    def clear_markings(self):
        for m in self.markings:
            self.plot.removeItem(m)
        self.markings = []

    def add_curves(self, x, ys, addc=True):
        """ Add multiple curves with the same x domain. """
        if len(ys) > MAX_INSTANCES_DRAWN:
            sample_selection = random.Random(self.sample_seed).sample(
                range(len(ys)), MAX_INSTANCES_DRAWN)

            # with random selection also show at most MAX_INSTANCES_DRAW elements from the subset
            subset = set(np.where(self.subset_indices)[0])
            subset_to_show = subset - set(sample_selection)
            subset_additional = MAX_INSTANCES_DRAWN - (len(subset) -
                                                       len(subset_to_show))
            if len(subset_to_show) > subset_additional:
                subset_to_show = random.Random(self.sample_seed).sample(
                    subset_to_show, subset_additional)

            self.sampled_indices = sorted(sample_selection +
                                          list(subset_to_show))
            self.sampling = True
        else:
            self.sampled_indices = list(range(len(ys)))
        random.Random(self.sample_seed).shuffle(
            self.sampled_indices)  # for sequential classes#
        self.sampled_indices_inverse = {
            s: i
            for i, s in enumerate(self.sampled_indices)
        }
        ys = self.data.X[self.sampled_indices][:, self.data_xsind]
        self.curves.append((x, ys))
        for y in ys:
            c = pg.PlotCurveItem(x=x, y=y, pen=self.pen_normal[None])
            self.curves_cont.add_curve(c)
        self.curves_plotted = self.curves

    def add_curve(self, x, y, pen=None):
        c = pg.PlotCurveItem(x=x,
                             y=y,
                             pen=pen if pen else self.pen_normal[None])
        self.curves_cont.add_curve(c)
        # for rescale to work correctly
        self.curves_plotted.append((x, np.array([y])))

    def add_fill_curve(self, x, ylow, yhigh, pen):
        phigh = pg.PlotCurveItem(x, yhigh, pen=pen)
        plow = pg.PlotCurveItem(x, ylow, pen=pen)
        color = pen.color()
        color.setAlphaF(0.2)
        cc = pg.mkBrush(color)
        pfill = pg.FillBetweenItem(plow, phigh, brush=cc)
        pfill.setZValue(10)
        self.curves_cont.add_bounds(phigh)
        self.curves_cont.add_bounds(plow)
        self.curves_cont.add_curve(pfill, ignore_bounds=True)
        # for zoom to work correctly
        self.curves_plotted.append((x, np.array([ylow, yhigh])))

    def _current_color_var(self):
        color_var = None
        if self.feature_color and self.data:
            color_var = self.data.domain[self.feature_color]
        return color_var

    def set_pen_colors(self):
        self.pen_normal.clear()
        self.pen_subset.clear()
        self.pen_selected.clear()
        color_var = self._current_color_var()
        if color_var is not None:
            colors = color_var.colors
            discrete_palette = ColorPaletteGenerator(
                number_of_colors=len(colors), rgb_colors=colors)
            for v in color_var.values:
                basecolor = discrete_palette[color_var.to_val(v)]
                basecolor = QColor(basecolor)
                basecolor.setAlphaF(0.9)
                self.pen_subset[v] = pg.mkPen(color=basecolor, width=1)
                self.pen_selected[v] = pg.mkPen(color=basecolor,
                                                width=2,
                                                style=Qt.DotLine)
                notselcolor = basecolor.lighter(150)
                notselcolor.setAlphaF(0.5)
                self.pen_normal[v] = pg.mkPen(color=notselcolor, width=1)

    def show_individual(self):
        self.view_average_menu.setChecked(False)
        self.set_pen_colors()
        self.clear_graph()
        self.viewtype = INDIVIDUAL
        if not self.data:
            return
        self.add_curves(self.data_x, self.data.X)
        self.set_curve_pens()
        self.curves_cont.update()
        self.plot.vb.set_mode_panning()

    def resample_curves(self, seed):
        self.sample_seed = seed
        self.update_view()

    def rescale_current_view_y(self):
        if self.curves_plotted:
            cache = {}
            qrect = self.plot.vb.targetRect()
            bleft = qrect.left()
            bright = qrect.right()

            ymax = max(
                np.max(ys[:,
                          searchsorted_cached(cache, x, bleft):
                          searchsorted_cached(cache, x, bright, side="right")])
                for x, ys in self.curves_plotted)
            ymin = min(
                np.min(ys[:,
                          searchsorted_cached(cache, x, bleft):
                          searchsorted_cached(cache, x, bright, side="right")])
                for x, ys in self.curves_plotted)

            self.plot.vb.setYRange(ymin, ymax, padding=0.0)
            self.plot.vb.pad_current_view_y()

    def _split_by_color_value(self, data):
        color_var = self._current_color_var()
        rd = {}
        if color_var is None:
            rd[None] = np.full((len(data.X), ), True, dtype=bool)
        else:
            cvd = Orange.data.Table(Orange.data.Domain([color_var]), data)
            for v in range(len(color_var.values)):
                v1 = np.in1d(cvd.X, v)
                if np.any(v1):
                    rd[color_var.values[v]] = v1
            nanind = np.isnan(cvd.X)
            if np.any(nanind):
                rd[None] = nanind
        return rd

    def viewtype_changed(self):
        if self.viewtype == AVERAGE:
            self.viewtype = INDIVIDUAL
        else:
            self.viewtype = AVERAGE
        self.update_view()

    def show_average(self):
        self.view_average_menu.setChecked(True)
        self.set_pen_colors()
        self.clear_graph()
        self.viewtype = AVERAGE
        if not self.data:
            return
        x = self.data_x
        if self.data:
            ysall = []
            cinfo = []
            selected_indices = np.full(self.data_size, False, dtype=bool)
            selected_indices[list(self.selected_indices)] = True
            dsplit = self._split_by_color_value(self.data)
            for colorv, indices in dsplit.items():
                for part in [None, "subset", "selection"]:
                    if part is None:
                        part_selection = indices
                        pen = self.pen_normal if np.any(
                            self.subset_indices) else self.pen_subset
                    elif part == "selection" and self.selection_type:
                        part_selection = indices & selected_indices
                        pen = self.pen_selected
                    elif part == "subset":
                        part_selection = indices & self.subset_indices
                        pen = self.pen_subset
                    if np.any(part_selection):
                        ys = self.data.X[part_selection]
                        std = np.nanstd(ys, axis=0)
                        mean = np.nanmean(ys, axis=0)
                        std = std[self.data_xsind]
                        mean = mean[self.data_xsind]
                        ysall.append(mean)
                        penc = QPen(pen[colorv])
                        penc.setWidth(3)
                        self.add_curve(x, mean, pen=penc)
                        self.add_fill_curve(x,
                                            mean + std,
                                            mean - std,
                                            pen=penc)
                        cinfo.append((colorv, part, part_selection))
            self.curves.append((x, np.array(ysall)))
            self.multiple_curves_info = cinfo
        self.curves_cont.update()
        self.plot.vb.set_mode_panning()

    def update_view(self):
        if self.viewtype == INDIVIDUAL:
            self.show_individual()
        elif self.viewtype == AVERAGE:
            self.show_average()
        if self.rescale_next:
            self.plot.vb.autoRange()

    def set_data(self, data):
        old_domain = self.data.domain if self.data else None
        self.clear_data()
        domain = data.domain if data is not None else None
        self.feature_color_model.set_domain(domain)
        if old_domain and domain != old_domain:  # do not reset feature_color
            self.feature_color = self.feature_color_model[
                0] if self.feature_color_model else None
        if data is not None:
            if self.data:
                self.rescale_next = not data.domain == self.data.domain
            else:
                self.rescale_next = True
            self.data = data
            # reset selection if dataset sizes do not match
            if self.selected_indices and \
                    (max(self.selected_indices) >= len(self.data) or self.data_size != len(self.data)):
                self.selected_indices.clear()
            self.data_size = len(self.data)
            # get and sort input data
            x = getx(self.data)
            xsind = np.argsort(x)
            self.data_x = x[xsind]
            self.data_xsind = xsind
            self._set_subset_indices(
            )  # refresh subset indices according to the current subset

    def _set_subset_indices(self):
        ids = self.subset
        if ids is None:
            ids = []
        if self.data:
            self.subset_indices = np.in1d(self.data.ids, ids)

    def set_data_subset(self, ids):
        self.subset = ids
        self._set_subset_indices()
        self.update_view()

    def select_by_click(self, pos, add):
        clicked_curve = self.highlighted
        if clicked_curve is not None:
            if self.viewtype == INDIVIDUAL:
                self.make_selection([self.sampled_indices[clicked_curve]], add)
            elif self.viewtype == AVERAGE:
                sel = np.where(self.multiple_curves_info[clicked_curve][2])[0]
                self.make_selection(sel, add)
        else:
            self.make_selection(None, add)
        if self.viewtype == AVERAGE:
            # reset average view
            self.show_average()

    def select_line(self, startp, endp, add):
        intersected = self.intersect_curves((startp.x(), startp.y()),
                                            (endp.x(), endp.y()))
        self.make_selection(intersected if len(intersected) else None, add)

    def intersect_curves(self, q1, q2):
        x, ys = self.data_x, self.data.X
        if len(x) < 2:
            return []
        x1, x2 = min(q1[0], q2[0]), max(q1[0], q2[0])
        xmin = closestindex(x, x1)
        xmax = closestindex(x, x2, side="right")
        xmin = max(0, xmin - 1)
        xmax = xmax + 2
        sel = np.flatnonzero(
            intersect_curves_chunked(x, ys, self.data_xsind, q1, q2, xmin,
                                     xmax))
        return sel
Ejemplo n.º 10
0
class ImagePlot(QWidget, OWComponent):

    attr_x = ContextSetting(None)
    attr_y = ContextSetting(None)
    gamma = Setting(0)
    threshold_low = Setting(0.0)
    threshold_high = Setting(1.0)

    def __init__(self, parent, select_fn=None):
        QWidget.__init__(self)
        OWComponent.__init__(self, parent)

        self.parent = parent

        self.select_fn = select_fn

        self.selection_type = SELECTMANY
        self.selection_enabled = True
        self.viewtype = INDIVIDUAL  # required bt InteractiveViewBox
        self.highlighted = None
        self.selection_matrix = None
        self.selection_indices = None

        self.plotview = pg.PlotWidget(background="w",
                                      viewBox=InteractiveViewBox(self))
        self.plot = self.plotview.getPlotItem()

        layout = QVBoxLayout()
        self.setLayout(layout)
        self.layout().setContentsMargins(0, 0, 0, 0)
        self.layout().addWidget(self.plotview)

        self.img = ImageItemNan()
        self.img.setOpts(axisOrder='row-major')
        self.plot.addItem(self.img)
        self.plot.vb.setAspectLocked()
        self.plot.scene().sigMouseMoved.connect(self.plot.vb.mouseMovedEvent)

        layout = QGridLayout()
        self.plotview.setLayout(layout)
        self.button = QPushButton("View", self.plotview)
        self.button.setAutoDefault(False)

        layout.setRowStretch(1, 1)
        layout.setColumnStretch(1, 1)
        layout.addWidget(self.button, 0, 0)
        view_menu = MenuFocus(self)
        self.button.setMenu(view_menu)

        actions = []

        zoom_in = QAction("Zoom in",
                          self,
                          triggered=self.plot.vb.set_mode_zooming)
        zoom_in.setShortcuts([Qt.Key_Z, QKeySequence(QKeySequence.ZoomIn)])
        zoom_in.setShortcutContext(Qt.WidgetWithChildrenShortcut)
        actions.append(zoom_in)
        zoom_fit = QAction(
            "Zoom to fit",
            self,
            triggered=lambda x:
            (self.plot.vb.autoRange(), self.plot.vb.set_mode_panning()))
        zoom_fit.setShortcuts(
            [Qt.Key_Backspace,
             QKeySequence(Qt.ControlModifier | Qt.Key_0)])
        zoom_fit.setShortcutContext(Qt.WidgetWithChildrenShortcut)
        actions.append(zoom_fit)
        select_square = QAction(
            "Select (square)",
            self,
            triggered=self.plot.vb.set_mode_select_square,
        )
        select_square.setShortcuts([Qt.Key_S])
        select_square.setShortcutContext(Qt.WidgetWithChildrenShortcut)
        actions.append(select_square)

        view_menu.addActions(actions)
        self.addActions(actions)

        common_options = dict(labelWidth=50,
                              orientation=Qt.Horizontal,
                              sendSelectedValue=True,
                              valueType=str)

        choose_xy = QWidgetAction(self)
        box = gui.vBox(self)
        box.setFocusPolicy(Qt.TabFocus)
        self.xy_model = DomainModel(DomainModel.METAS | DomainModel.CLASSES,
                                    valid_types=DomainModel.PRIMITIVE)
        self.models = [self.xy_model]
        self.cb_attr_x = gui.comboBox(box,
                                      self,
                                      "attr_x",
                                      label="Axis x:",
                                      callback=self.update_attr,
                                      model=self.xy_model,
                                      **common_options)
        self.cb_attr_y = gui.comboBox(box,
                                      self,
                                      "attr_y",
                                      label="Axis y:",
                                      callback=self.update_attr,
                                      model=self.xy_model,
                                      **common_options)
        box.setFocusProxy(self.cb_attr_x)

        form = QFormLayout(formAlignment=Qt.AlignLeft,
                           labelAlignment=Qt.AlignLeft,
                           fieldGrowthPolicy=QFormLayout.AllNonFixedFieldsGrow)

        lowslider = gui.hSlider(box,
                                self,
                                "threshold_low",
                                minValue=0.0,
                                maxValue=1.0,
                                step=0.05,
                                ticks=True,
                                intOnly=False,
                                createLabel=False,
                                callback=self.update_color_schema)
        highslider = gui.hSlider(box,
                                 self,
                                 "threshold_high",
                                 minValue=0.0,
                                 maxValue=1.0,
                                 step=0.05,
                                 ticks=True,
                                 intOnly=False,
                                 createLabel=False,
                                 callback=self.update_color_schema)

        form.addRow("Low:", lowslider)
        form.addRow("High:", highslider)

        box.layout().addLayout(form)

        choose_xy.setDefaultWidget(box)
        view_menu.addAction(choose_xy)

        self.markings_integral = []

        self.lsx = None  # info about the X axis
        self.lsy = None  # info about the Y axis

        self.data = None
        self.data_ids = {}

    def update_color_schema(self):
        if not self.threshold_low < self.threshold_high:
            # TODO this belongs here, not in the parent
            self.parent.Warning.threshold_error()
            return
        else:
            self.parent.Warning.threshold_error.clear()
        # TODO add color chooser
        # bgy color scheme
        colors = np.array(colorcet.linear_bgy_10_95_c74) * 255
        cols = color_palette_table(colors,
                                   threshold_low=self.threshold_low,
                                   threshold_high=self.threshold_high)

        self.img.setLookupTable(cols)

        # use defined discrete palette
        if self.parent.value_type == 1:
            dat = self.data.domain[self.parent.attr_value]
            if isinstance(dat, DiscreteVariable):
                self.img.setLookupTable(dat.colors)

    def update_attr(self):
        self.update_view()

    def init_attr_values(self):
        domain = self.data.domain if self.data is not None else None
        for model in self.models:
            model.set_domain(domain)
        self.attr_x = self.xy_model[0] if self.xy_model else None
        self.attr_y = self.xy_model[1] if len(self.xy_model) >= 2 \
            else self.attr_x

    def set_data(self, data):
        self.img.clear()
        if data is not None:
            same_domain = (self.data and data.domain.checksum()
                           == self.data.domain.checksum())
            self.data = data
            self.data_ids = {e: i for i, e in enumerate(data.ids)}
            if not same_domain:
                self.init_attr_values()
        else:
            self.data = None
            self.data_ids = {}

    def set_integral_limits(self):
        self.update_view()

    def refresh_markings(self, di):

        for m in self.markings_integral:
            self.parent.curveplot.remove_marking(m)
        self.markings_integral = []

        if di is None:
            return  # nothing to draw

        color = Qt.red

        def add_marking(a):
            self.markings_integral.append(a)
            self.parent.curveplot.add_marking(a)

        if "baseline" in di:
            bs_x, bs_ys = di["baseline"]
            baseline = pg.PlotCurveItem()
            baseline.setPen(
                pg.mkPen(color=QColor(color), width=2, style=Qt.DotLine))
            baseline.setZValue(10)
            baseline.setData(x=bs_x, y=bs_ys[0])
            add_marking(baseline)

        if "curve" in di:
            bs_x, bs_ys = di["curve"]
            curve = pg.PlotCurveItem()
            curve.setPen(pg.mkPen(color=QColor(color), width=2))
            curve.setZValue(10)
            curve.setData(x=bs_x, y=bs_ys[0])
            add_marking(curve)

        if "fill" in di:
            (x1, ys1), (x2, ys2) = di["fill"]
            phigh = pg.PlotCurveItem(x1, ys1[0], pen=None)
            plow = pg.PlotCurveItem(x2, ys2[0], pen=None)
            color = QColor(color)
            color.setAlphaF(0.5)
            cc = pg.mkBrush(color)
            pfill = pg.FillBetweenItem(plow, phigh, brush=cc)
            pfill.setZValue(9)
            add_marking(pfill)

        if "line" in di:
            (x1, y1), (x2, y2) = di["line"]
            line = pg.PlotCurveItem()
            line.setPen(pg.mkPen(color=QColor(color), width=4))
            line.setZValue(10)
            line.setData(x=[x1[0], x2[0]], y=[y1[0], y2[0]])
            add_marking(line)

    def update_view(self):
        self.img.clear()
        self.img.setSelection(None)
        self.lsx = None
        self.lsy = None
        self.selection_matrix = None
        self.selection_indices = None
        if self.data and self.attr_x and self.attr_y:
            xat = self.data.domain[self.attr_x]
            yat = self.data.domain[self.attr_y]

            ndom = Orange.data.Domain([xat, yat])
            datam = Orange.data.Table(ndom, self.data)
            coorx = datam.X[:, 0]
            coory = datam.X[:, 1]
            self.lsx = lsx = values_to_linspace(coorx)
            self.lsy = lsy = values_to_linspace(coory)
            if lsx[-1] * lsy[-1] > IMAGE_TOO_BIG:
                self.parent.Error.image_too_big(lsx[-1], lsy[-1])
                return
            else:
                self.parent.Error.image_too_big.clear()

            di = {}
            if self.parent.value_type == 0:  # integrals
                imethod = self.parent.integration_methods[
                    self.parent.integration_method]

                l1, l2, l3 = self.parent.lowlim, self.parent.highlim, self.parent.choose

                gx = getx(self.data)

                if l1 is None:
                    l1 = min(gx) - 1
                if l2 is None:
                    l2 = max(gx) + 1

                l1, l2 = min(l1, l2), max(l1, l2)

                if l3 is None:
                    l3 = (l1 + l2) / 2

                if imethod != Integrate.PeakAt:
                    datai = Integrate(method=imethod, limits=[[l1,
                                                               l2]])(self.data)
                else:
                    datai = Integrate(method=imethod, limits=[[l3,
                                                               l3]])(self.data)

                if self.parent.curveplot.selected_indices:
                    # curveplot can have a subset of curves on the input> match IDs
                    ind = list(self.parent.curveplot.selected_indices)[0]
                    dind = self.data_ids[self.parent.curveplot.data[ind].id]
                    di = datai.domain.attributes[0].compute_value.draw_info(
                        self.data[dind:dind + 1])
                d = datai.X[:, 0]
            else:
                dat = self.data.domain[self.parent.attr_value]
                ndom = Orange.data.Domain([dat])
                d = Orange.data.Table(ndom, self.data).X[:, 0]
            self.refresh_markings(di)

            # set data
            imdata = np.ones((lsy[2], lsx[2])) * float("nan")
            self.selection_indices = np.ones((lsy[2], lsx[2]), dtype=int) * -1
            self.selection_matrix = np.zeros((lsy[2], lsx[2]), dtype=bool)
            xindex = index_values(coorx, lsx)
            yindex = index_values(coory, lsy)
            imdata[yindex, xindex] = d
            self.selection_indices[yindex, xindex] = np.arange(0,
                                                               len(d),
                                                               dtype=int)

            levels = get_levels(imdata)
            self.update_color_schema()

            self.img.setImage(imdata, levels=levels)

            # shift centres of the pixels so that the axes are useful
            shiftx = _shift(lsx)
            shifty = _shift(lsy)
            left = lsx[0] - shiftx
            bottom = lsy[0] - shifty
            width = (lsx[1] - lsx[0]) + 2 * shiftx
            height = (lsy[1] - lsy[0]) + 2 * shifty
            self.img.setRect(QRectF(left, bottom, width, height))

    def make_selection(self, selected, add):
        """Add selected indices to the selection."""
        if self.data and self.selection_matrix is not None:
            if selected is None and not add:
                self.selection_matrix[:, :] = 0
            elif selected is not None:
                if add:
                    self.selection_matrix = np.logical_or(
                        self.selection_matrix, selected)
                else:
                    self.selection_matrix = selected
            self.img.setSelection(self.selection_matrix)
        self.send_selection()

    def send_selection(self):
        if self.data and self.selection_matrix is not None:
            selected = self.selection_indices[np.where(self.selection_matrix)]
            selected = selected[selected >= 0]  # filter undefined values
            selected.sort()
        else:
            selected = []
        if self.select_fn:
            self.select_fn(selected)

    def select_square(self, p1, p2, add):
        """ Select elements within a square drawn by the user.
        A selection square needs to contain whole pixels """
        if self.data and self.lsx and self.lsy:
            # get edges
            x1, x2 = min(p1.x(), p2.x()), max(p1.x(), p2.x())
            y1, y2 = min(p1.y(), p2.y()), max(p1.y(), p2.y())

            # here we change edges of the square so that next
            # pixel centers need to be in the square x1, x2, y1, y2
            shiftx = _shift(self.lsx)
            shifty = _shift(self.lsy)
            x1 += shiftx
            x2 -= shiftx
            y1 += shifty
            y2 -= shifty

            # get locations in image pixels
            x1 = location_values(x1, self.lsx)
            x2 = location_values(x2, self.lsx)
            y1 = location_values(y1, self.lsy)
            y2 = location_values(y2, self.lsy)

            # pixel centre need to within the square to be selected
            x1, x2 = np.ceil(x1).astype(int), np.floor(x2).astype(int)
            y1, y2 = np.ceil(y1).astype(int), np.floor(y2).astype(int)

            # select a range
            x1 = max(x1, 0)
            y1 = max(y1, 0)
            x2 = max(x2 + 1, 0)
            y2 = max(y2 + 1, 0)
            select_data = np.zeros((self.lsy[2], self.lsx[2]), dtype=bool)
            select_data[y1:y2, x1:x2] = 1
            self.make_selection(select_data, add)

    def select_by_click(self, pos, add):
        if self.data:
            x, y = pos.x(), pos.y()

            x = location_values(x, self.lsy)
            y = location_values(y, self.lsy)

            x = np.round(x).astype(int)
            y = np.round(y).astype(int)

            if 0 <= x < self.lsx[2] and 0 <= y < self.lsy[2]:
                select_data = np.zeros((self.lsy[2], self.lsx[2]), dtype=bool)
                select_data[y, x] = 1
                self.make_selection(select_data, add)
            else:
                self.make_selection(None, add)
Ejemplo n.º 11
0
class ImagePlot(QWidget, OWComponent, SelectionGroupMixin,
                ImageColorSettingMixin, ImageZoomMixin):

    attr_x = ContextSetting(None)
    attr_y = ContextSetting(None)
    gamma = Setting(0)

    selection_changed = Signal()

    def __init__(self, parent):
        QWidget.__init__(self)
        OWComponent.__init__(self, parent)
        SelectionGroupMixin.__init__(self)
        ImageColorSettingMixin.__init__(self)
        ImageZoomMixin.__init__(self)

        self.parent = parent

        self.selection_type = SELECTMANY
        self.saving_enabled = True
        self.selection_enabled = True
        self.viewtype = INDIVIDUAL  # required bt InteractiveViewBox
        self.highlighted = None
        self.data_points = None
        self.data_values = None
        self.data_imagepixels = None

        self.plotview = pg.PlotWidget(background="w", viewBox=InteractiveViewBox(self))
        self.plot = self.plotview.getPlotItem()

        self.plot.scene().installEventFilter(
            HelpEventDelegate(self.help_event, self))

        layout = QVBoxLayout()
        self.setLayout(layout)
        self.layout().setContentsMargins(0, 0, 0, 0)
        self.layout().addWidget(self.plotview)

        self.img = ImageItemNan()
        self.img.setOpts(axisOrder='row-major')
        self.plot.addItem(self.img)
        self.plot.vb.setAspectLocked()
        self.plot.scene().sigMouseMoved.connect(self.plot.vb.mouseMovedEvent)

        layout = QGridLayout()
        self.plotview.setLayout(layout)
        self.button = QPushButton("Menu", self.plotview)
        self.button.setAutoDefault(False)

        layout.setRowStretch(1, 1)
        layout.setColumnStretch(1, 1)
        layout.addWidget(self.button, 0, 0)
        view_menu = MenuFocus(self)
        self.button.setMenu(view_menu)

        # prepare interface according to the new context
        self.parent.contextAboutToBeOpened.connect(lambda x: self.init_interface_data(x[0]))

        actions = []

        self.add_zoom_actions(view_menu)

        select_square = QAction(
            "Select (square)", self, triggered=self.plot.vb.set_mode_select_square,
        )
        select_square.setShortcuts([Qt.Key_S])
        select_square.setShortcutContext(Qt.WidgetWithChildrenShortcut)
        actions.append(select_square)

        select_polygon = QAction(
            "Select (polygon)", self, triggered=self.plot.vb.set_mode_select_polygon,
        )
        select_polygon.setShortcuts([Qt.Key_P])
        select_polygon.setShortcutContext(Qt.WidgetWithChildrenShortcut)
        actions.append(select_polygon)

        if self.saving_enabled:
            save_graph = QAction(
                "Save graph", self, triggered=self.save_graph,
            )
            save_graph.setShortcuts([QKeySequence(Qt.ControlModifier | Qt.Key_I)])
            actions.append(save_graph)

        view_menu.addActions(actions)
        self.addActions(actions)

        common_options = dict(
            labelWidth=50, orientation=Qt.Horizontal, sendSelectedValue=True,
            valueType=str)

        choose_xy = QWidgetAction(self)
        box = gui.vBox(self)
        box.setFocusPolicy(Qt.TabFocus)
        self.xy_model = DomainModel(DomainModel.METAS | DomainModel.CLASSES,
                                    valid_types=DomainModel.PRIMITIVE)
        self.cb_attr_x = gui.comboBox(
            box, self, "attr_x", label="Axis x:", callback=self.update_attr,
            model=self.xy_model, **common_options)
        self.cb_attr_y = gui.comboBox(
            box, self, "attr_y", label="Axis y:", callback=self.update_attr,
            model=self.xy_model, **common_options)
        box.setFocusProxy(self.cb_attr_x)

        box.layout().addWidget(self.color_settings_box())

        choose_xy.setDefaultWidget(box)
        view_menu.addAction(choose_xy)

        self.markings_integral = []

        self.lsx = None  # info about the X axis
        self.lsy = None  # info about the Y axis

        self.data = None
        self.data_ids = {}

    def init_interface_data(self, data):
        same_domain = (self.data and data and
                       data.domain == self.data.domain)
        if not same_domain:
            self.init_attr_values(data)

    def help_event(self, ev):
        pos = self.plot.vb.mapSceneToView(ev.scenePos())
        sel = self._points_at_pos(pos)
        prepared = []
        if sel is not None:
            data, vals, points = self.data[sel], self.data_values[sel], self.data_points[sel]
            for d, v, p in zip(data, vals, points):
                basic = "({}, {}): {}".format(p[0], p[1], v)
                variables = [v for v in self.data.domain.metas + self.data.domain.class_vars
                             if v not in [self.attr_x, self.attr_y]]
                features = ['{} = {}'.format(attr.name, d[attr]) for attr in variables]
                prepared.append("\n".join([basic] + features))
        text = "\n\n".join(prepared)
        if text:
            text = ('<span style="white-space:pre">{}</span>'
                    .format(escape(text)))
            QToolTip.showText(ev.screenPos(), text, widget=self.plotview)
            return True
        else:
            return False

    def update_attr(self):
        self.update_view()

    def init_attr_values(self, data):
        domain = data.domain if data is not None else None
        self.xy_model.set_domain(domain)
        self.attr_x = self.xy_model[0] if self.xy_model else None
        self.attr_y = self.xy_model[1] if len(self.xy_model) >= 2 \
            else self.attr_x

    def save_graph(self):
        saveplot.save_plot(self.plotview, self.parent.graph_writers)

    def set_data(self, data):
        if data:
            self.data = data
            self.data_ids = {e: i for i, e in enumerate(data.ids)}
            self.restore_selection_settings()
        else:
            self.data = None
            self.data_ids = {}

    def refresh_markings(self, di):
        refresh_integral_markings([{"draw": di}], self.markings_integral, self.parent.curveplot)

    def update_view(self):
        self.img.clear()
        self.img.setSelection(None)
        self.lsx = None
        self.lsy = None
        self.data_points = None
        self.data_values = None
        self.data_imagepixels = None
        if self.data and self.attr_x and self.attr_y:
            xat = self.data.domain[self.attr_x]
            yat = self.data.domain[self.attr_y]

            ndom = Orange.data.Domain([xat, yat])
            datam = Orange.data.Table(ndom, self.data)
            coorx = datam.X[:, 0]
            coory = datam.X[:, 1]
            self.data_points = datam.X
            self.lsx = lsx = values_to_linspace(coorx)
            self.lsy = lsy = values_to_linspace(coory)
            if lsx[-1] * lsy[-1] > IMAGE_TOO_BIG:
                self.parent.Error.image_too_big(lsx[-1], lsy[-1])
                return
            else:
                self.parent.Error.image_too_big.clear()

            di = {}
            if self.parent.value_type == 0:  # integrals
                imethod = self.parent.integration_methods[self.parent.integration_method]

                if imethod != Integrate.PeakAt:
                    datai = Integrate(methods=imethod,
                                      limits=[[self.parent.lowlim, self.parent.highlim]])(self.data)
                else:
                    datai = Integrate(methods=imethod,
                                      limits=[[self.parent.choose, self.parent.choose]])(self.data)

                if np.any(self.parent.curveplot.selection_group):
                    # curveplot can have a subset of curves on the input> match IDs
                    ind = np.flatnonzero(self.parent.curveplot.selection_group)[0]
                    dind = self.data_ids[self.parent.curveplot.data[ind].id]
                    di = datai.domain.attributes[0].compute_value.draw_info(self.data[dind:dind+1])
                d = datai.X[:, 0]
            else:
                dat = self.data.domain[self.parent.attr_value]
                ndom = Orange.data.Domain([dat])
                d = Orange.data.Table(ndom, self.data).X[:, 0]
            self.refresh_markings(di)

            # set data
            imdata = np.ones((lsy[2], lsx[2])) * float("nan")

            xindex = index_values(coorx, lsx)
            yindex = index_values(coory, lsy)
            imdata[yindex, xindex] = d
            self.data_values = d
            self.data_imagepixels = np.vstack((yindex, xindex)).T

            self.img.setImage(imdata, autoLevels=False)
            self.img.setLevels([0, 1])
            self.update_levels()
            self.update_color_schema()

            # shift centres of the pixels so that the axes are useful
            shiftx = _shift(lsx)
            shifty = _shift(lsy)
            left = lsx[0] - shiftx
            bottom = lsy[0] - shifty
            width = (lsx[1]-lsx[0]) + 2*shiftx
            height = (lsy[1]-lsy[0]) + 2*shifty
            self.img.setRect(QRectF(left, bottom, width, height))

            self.refresh_img_selection()

    def refresh_img_selection(self):
        selected_px = np.zeros((self.lsy[2], self.lsx[2]), dtype=np.uint8)
        selected_px[self.data_imagepixels[:, 0], self.data_imagepixels[:, 1]] = self.selection_group
        self.img.setSelection(selected_px)

    def make_selection(self, selected, add):
        """Add selected indices to the selection."""
        add_to_group, add_group, remove = selection_modifiers()
        if self.data and self.lsx and self.lsy:
            if add_to_group:  # both keys - need to test it before add_group
                selnum = np.max(self.selection_group)
            elif add_group:
                selnum = np.max(self.selection_group) + 1
            elif remove:
                selnum = 0
            else:
                self.selection_group *= 0
                selnum = 1
            if selected is not None:
                self.selection_group[selected] = selnum
            self.refresh_img_selection()
        self.prepare_settings_for_saving()
        self.selection_changed.emit()

    def select_square(self, p1, p2, add):
        """ Select elements within a square drawn by the user.
        A selection needs to contain whole pixels """
        x1, y1 = p1.x(), p1.y()
        x2, y2 = p2.x(), p2.y()
        polygon = [QPointF(x1, y1), QPointF(x2, y1), QPointF(x2, y2), QPointF(x1, y2), QPointF(x1, y1)]
        self.select_polygon(polygon, add)

    def select_polygon(self, polygon, add):
        """ Select by a polygon which has to contain whole pixels. """
        if self.data and self.lsx and self.lsy:
            polygon = [(p.x(), p.y()) for p in polygon]
            # a polygon should contain all pixel
            shiftx = _shift(self.lsx)
            shifty = _shift(self.lsy)
            points_edges = [self.data_points + [[shiftx, shifty]],
                            self.data_points + [[-shiftx, shifty]],
                            self.data_points + [[shiftx, -shifty]],
                            self.data_points + [[-shiftx, -shifty]]]
            inp = in_polygon(points_edges[0], polygon)
            for p in points_edges[1:]:
                inp *= in_polygon(p, polygon)
            self.make_selection(inp, add)

    def _points_at_pos(self, pos):
        if self.data and self.lsx and self.lsy:
            x, y = pos.x(), pos.y()
            distance = np.abs(self.data_points - [[x, y]])
            sel = (distance[:, 0] < _shift(self.lsx)) * (distance[:, 1] < _shift(self.lsy))
            return sel

    def select_by_click(self, pos, add):
        sel = self._points_at_pos(pos)
        self.make_selection(sel, add)