Esempio n. 1
0
class ImageViewer(QGraphicsView, QObject):
    def __init__(self, parent=None):
        super(ImageViewer, self).__init__(parent)
        self.setRenderHints(QPainter.Antialiasing
                            | QPainter.SmoothPixmapTransform)
        #self.setAlignment(QtCore.Qt.AlignLeft | QtCore.Qt.AlignTop)
        self.setTransformationAnchor(QGraphicsView.AnchorUnderMouse)
        self.setDragMode(QGraphicsView.ScrollHandDrag)
        #self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
        #self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
        self._scene = ImageViewerScene(self)
        self.setScene(self._scene)
        self._create_grid()
        self._create_grid_lines()
        self._pixmap = None
        self._selection_mode = SELECTION_MODE.NONE

        # polygon selection
        _polygon_guide_line_pen = QPen(QtGui.QColor(235, 72, 40))
        _polygon_guide_line_pen.setWidth(2)
        _polygon_guide_line_pen.setStyle(QtCore.Qt.DotLine)
        self._polygon_guide_line = QGraphicsLineItem()
        self._polygon_guide_line.setVisible(False)
        self._polygon_guide_line.setPen(_polygon_guide_line_pen)
        self._scene.addItem(self._polygon_guide_line)
        self._current_polygon = None
        # rectangle selection
        self._box_origin = QPoint()
        self._box_picker = QRubberBand(QRubberBand.Rectangle, self)

        # free selection
        self._current_free_path = None
        self._is_drawing = False
        self._last_point_drawn = QPoint()
        self._current_label = None

    @property
    def current_label(self):
        return self._current_label

    @current_label.setter
    def current_label(self, value):
        self._current_label = value

    @property
    def pixmap(self) -> ImagePixmap:
        return self._pixmap

    @pixmap.setter
    def pixmap(self, value: QPixmap):
        self.selection_mode = SELECTION_MODE.NONE
        self.resetTransform()
        if self.pixmap:
            self._scene.removeItem(self._pixmap)
        self.remove_annotations()
        self._pixmap = ImagePixmap()
        self._pixmap.setPixmap(value)
        self._pixmap.setOffset(-value.width() / 2, -value.height() / 2)
        self._pixmap.setTransformationMode(QtCore.Qt.SmoothTransformation)
        self._pixmap.signals.hoverEnterEventSgn.connect(
            self.pixmap_hoverEnterEvent_slot)
        self._pixmap.signals.hoverLeaveEventSgn.connect(
            self.pixmap_hoverLeaveEvent_slot)
        self._pixmap.signals.hoverMoveEventSgn.connect(
            self.pixmap_hoverMoveEvent_slot)
        self._scene.addItem(self._pixmap)
        # rect=self._scene.addRect(QtCore.QRectF(0,0,100,100), QtGui.QPen(QtGui.QColor("red")))
        # rect.setZValue(1.0)
        self.fit_to_window()

    @property
    def selection_mode(self):
        return self._selection_mode

    @selection_mode.setter
    def selection_mode(self, value):
        self._polygon_guide_line.hide()
        self._current_polygon = None
        self._current_free_path = None
        self._is_drawing = value == SELECTION_MODE.FREE
        if value == SELECTION_MODE.NONE:
            self.enable_items(True)
        else:
            self.enable_items(False)
        self._selection_mode = value

    def remove_annotations(self):
        for item in self._scene.items():
            if isinstance(item, EditableBox):
                self._scene.removeItem(item)
            elif isinstance(item, EditablePolygon):
                item.delete_polygon()

    def remove_annotations_by_label(self, label_name):
        for item in self._scene.items():
            if isinstance(item, EditableBox):
                if item.label and item.label.name == label_name:
                    self._scene.removeItem(item)
            elif isinstance(item, EditablePolygon):
                if item.label and item.label.name == label_name:
                    item.delete_polygon()

    def enable_items(self, value):
        for item in self._scene.items():
            if isinstance(item, EditablePolygon) or isinstance(
                    item, EditableBox):
                item.setEnabled(value)

    def _create_grid(self):
        gridSize = 15
        backgroundPixmap = QtGui.QPixmap(gridSize * 2, gridSize * 2)
        #backgroundPixmap.fill(QtGui.QColor("white"))
        backgroundPixmap.fill(QtGui.QColor(20, 20, 20))
        #backgroundPixmap.fill(QtGui.QColor("powderblue"))
        painter = QtGui.QPainter(backgroundPixmap)
        #backgroundColor=QtGui.QColor("palegoldenrod")
        #backgroundColor=QtGui.QColor(237,237,237)
        backgroundColor = QtGui.QColor(0, 0, 0)
        painter.fillRect(0, 0, gridSize, gridSize, backgroundColor)
        painter.fillRect(gridSize, gridSize, gridSize, gridSize,
                         backgroundColor)
        painter.end()
        self._scene.setBackgroundBrush(QtGui.QBrush(backgroundPixmap))

    def _create_grid_lines(self):
        pen_color = QColor(255, 255, 255, 255)
        pen = QPen(pen_color)
        pen.setWidth(2)
        pen.setStyle(QtCore.Qt.DotLine)
        self.vline = QGraphicsLineItem()
        self.vline.setVisible(False)
        self.vline.setPen(pen)
        self.hline = QGraphicsLineItem()
        self.hline.setVisible(False)
        self.hline.setPen(pen)
        self._scene.addItem(self.vline)
        self._scene.addItem(self.hline)

    def wheelEvent(self, event: QWheelEvent):
        adj = (event.angleDelta().y() / 120) * 0.1
        self.scale(1 + adj, 1 + adj)

    def fit_to_window(self):
        """Fit image within view."""
        if not self.pixmap or not self._pixmap.pixmap():
            return
        #self._pixmap.setTransformationMode(QtCore.Qt.SmoothTransformation)
        self.fitInView(self._pixmap, QtCore.Qt.KeepAspectRatio)

    def show_guide_lines(self):
        if self.hline and self.vline:
            self.hline.show()
            self.vline.show()

    def hide_guide_lines(self):
        if self.hline and self.vline:
            self.hline.hide()
            self.vline.hide()

    def pixmap_hoverEnterEvent_slot(self):
        self.show_guide_lines()

    def pixmap_hoverLeaveEvent_slot(self):
        self.hide_guide_lines()

    def pixmap_hoverMoveEvent_slot(self, evt: QGraphicsSceneHoverEvent, x, y):
        bbox: QRect = self._pixmap.boundingRect()
        offset = QPointF(bbox.width() / 2, bbox.height() / 2)
        self.vline.setLine(x, -offset.y(), x, bbox.height() - offset.y())
        self.vline.setZValue(1)
        self.hline.setLine(-offset.x(), y, bbox.width() - offset.x(), y)
        self.hline.setZValue(1)

    def mouseMoveEvent(self, evt: QtGui.QMouseEvent) -> None:
        if self.selection_mode == SELECTION_MODE.BOX:
            if not self._box_origin.isNull():
                self._box_picker.setGeometry(
                    QRect(self._box_origin, evt.pos()).normalized())
        elif self.selection_mode == SELECTION_MODE.POLYGON:
            if self._current_polygon:
                if self._current_polygon.count > 0:
                    last_point: QPointF = self._current_polygon.last_point
                    self._polygon_guide_line.setZValue(1)
                    self._polygon_guide_line.show()
                    mouse_pos = self.mapToScene(evt.pos())
                    self._polygon_guide_line.setLine(last_point.x(),
                                                     last_point.y(),
                                                     mouse_pos.x(),
                                                     mouse_pos.y())
            else:
                self._polygon_guide_line.hide()

        elif self.selection_mode == SELECTION_MODE.FREE and evt.buttons(
        ) and QtCore.Qt.LeftButton:
            if self._current_free_path:
                painter: QPainterPath = self._current_free_path.path()
                self._last_point_drawn = self.mapToScene(evt.pos())
                painter.lineTo(self._last_point_drawn)
                self._current_free_path.setPath(painter)

        super(ImageViewer, self).mouseMoveEvent(evt)

    def mousePressEvent(self, evt: QtGui.QMouseEvent) -> None:

        if evt.buttons() == QtCore.Qt.LeftButton:
            if self.selection_mode == SELECTION_MODE.BOX:
                self.setDragMode(QGraphicsView.NoDrag)
                self._box_origin = evt.pos()
                self._box_picker.setGeometry(QRect(self._box_origin, QSize()))
                self._box_picker.show()

            elif self._selection_mode == SELECTION_MODE.POLYGON:
                pixmap_rect: QRectF = self._pixmap.boundingRect()
                new_point = self.mapToScene(evt.pos())
                # consider only the points intothe image
                if pixmap_rect.contains(new_point):
                    if self._current_polygon is None:
                        self._current_polygon = EditablePolygon()
                        self._current_polygon.signals.deleted.connect(
                            self.delete_polygon_slot)
                        self._scene.addItem(self._current_polygon)
                        self._current_polygon.addPoint(new_point)
                    else:
                        self._current_polygon.addPoint(new_point)

            elif self._selection_mode == SELECTION_MODE.FREE:
                # start drawing
                new_point = self.mapToScene(evt.pos())
                pixmap_rect: QRectF = self._pixmap.boundingRect()
                # consider only the points intothe image
                if pixmap_rect.contains(new_point):
                    self.setDragMode(QGraphicsView.NoDrag)
                    pen = QPen(QtGui.QColor(235, 72, 40))
                    pen.setWidth(10)
                    self._last_point_drawn = new_point
                    self._current_free_path = QGraphicsPathItem()
                    self._current_free_path.setOpacity(0.6)
                    self._current_free_path.setPen(pen)
                    painter = QPainterPath()
                    painter.moveTo(self._last_point_drawn)
                    self._current_free_path.setPath(painter)
                    self._scene.addItem(self._current_free_path)
        else:
            self.setDragMode(QGraphicsView.ScrollHandDrag)

        super(ImageViewer, self).mousePressEvent(evt)

    def mouseReleaseEvent(self, evt: QtGui.QMouseEvent) -> None:
        if evt.button() == QtCore.Qt.LeftButton:
            if self.selection_mode == SELECTION_MODE.BOX:
                roi: QRect = self._box_picker.geometry()
                roi: QRectF = self.mapToScene(roi).boundingRect()
                pixmap_rect = self._pixmap.boundingRect()
                self._box_picker.hide()
                if pixmap_rect == roi.united(pixmap_rect):
                    rect = EditableBox(roi)
                    rect.label = self.current_label
                    self._scene.addItem(rect)
                    self.selection_mode = SELECTION_MODE.NONE
                    self.setDragMode(QGraphicsView.ScrollHandDrag)

            elif self.selection_mode == SELECTION_MODE.FREE and self._current_free_path:
                # create polygon
                self._current_free_path: QGraphicsPathItem
                path_rect = self._current_free_path.boundingRect()
                pixmap_rect = self._pixmap.boundingRect()
                if pixmap_rect == path_rect.united(pixmap_rect):
                    path = self._current_free_path.path()
                    path_polygon = EditablePolygon()
                    path_polygon.label = self.current_label
                    self._scene.addItem(path_polygon)
                    for i in range(0, path.elementCount(), 10):
                        x, y = path.elementAt(i).x, path.elementAt(i).y
                        path_polygon.addPoint(QPointF(x, y))
                self._scene.removeItem(self._current_free_path)
                self.selection_mode = SELECTION_MODE.NONE
                self.setDragMode(QGraphicsView.ScrollHandDrag)

        super(ImageViewer, self).mouseReleaseEvent(evt)

    def keyPressEvent(self, event: QtGui.QKeyEvent) -> None:
        if self._current_polygon and event.key() == QtCore.Qt.Key_Space:
            points = self._current_polygon.points
            self._current_polygon.label = self.current_label
            self._current_polygon = None
            self.selection_mode = SELECTION_MODE.NONE
            self._polygon_guide_line.hide()
            self.setDragMode(QGraphicsView.ScrollHandDrag)
        super(ImageViewer, self).keyPressEvent(event)

    def delete_polygon_slot(self, polygon: EditablePolygon):
        self._current_polygon = None
        self.selection_mode = SELECTION_MODE.NONE
        self._polygon_guide_line.hide()
Esempio n. 2
0
class ImageViewer(QGraphicsView, QObject):
    points_selection_sgn = pyqtSignal(list)
    key_press_sgn = pyqtSignal(QtGui.QKeyEvent)

    def __init__(self, parent=None):
        super(ImageViewer, self).__init__(parent)
        self.setDragMode(QGraphicsView.ScrollHandDrag)
        self.setRenderHints(QPainter.Antialiasing
                            | QPainter.SmoothPixmapTransform)
        self.setTransformationAnchor(QGraphicsView.AnchorUnderMouse)
        self.setResizeAnchor(QGraphicsView.AnchorUnderMouse)
        self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)
        self.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded)

        self._scene = ImageViewerScene(self)
        self.setScene(self._scene)
        self._image = None
        self._image_original = None
        self._pixmap = None
        self._img_contrast = 1.0
        self._img_brightness = 50.0
        self._img_gamma = 1.0
        self._create_grid()
        self._channels = []
        self._current_tool = SELECTION_TOOL.POINTER
        self._dataset = None

        # create grid lines
        pen_color = QColor(255, 255, 255, 255)
        pen = QPen(pen_color)
        pen.setWidth(2)
        pen.setStyle(QtCore.Qt.DotLine)
        self.vline = QGraphicsLineItem()
        self.vline.setVisible(False)
        self.vline.setPen(pen)
        self.hline = QGraphicsLineItem()
        self.hline.setVisible(False)
        self.hline.setPen(pen)
        self._scene.addItem(self.vline)
        self._scene.addItem(self.hline)
        self._current_label = None

        # rectangle selection tool
        self._rectangle_tool_origin = QPoint()
        self._rectangle_tool_picker = QRubberBand(QRubberBand.Rectangle, self)

        # polygon selection tool
        app = QApplication.instance()
        color = app.palette().color(QPalette.Highlight)
        self._polygon_guide_line_pen = QPen(color)
        self._polygon_guide_line_pen.setWidth(3)
        self._polygon_guide_line_pen.setStyle(QtCore.Qt.DotLine)
        self._polygon_guide_line = QGraphicsLineItem()
        self._polygon_guide_line.setVisible(False)
        self._polygon_guide_line.setPen(self._polygon_guide_line_pen)
        self._scene.addItem(self._polygon_guide_line)
        self._current_polygon = None

        # circle
        self._current_ellipse = None

        # free selection tool
        self._current_free_path = None
        self._is_drawing = False
        self._last_point_drawn = QPoint()
        self._last_click_point = None
        self._free_Path_pen = QPen(color)
        self._free_Path_pen.setWidth(10)

        self._extreme_points = Queue(maxsize=4)

    @property
    def current_label(self):
        return self._current_label

    @current_label.setter
    def current_label(self, value):
        self._current_label = value
        if self._current_label:
            color = QColor(self._current_label.color)
            self._free_Path_pen.setColor(color)
            self._polygon_guide_line_pen.setColor(color)
            self._polygon_guide_line.setPen(self._polygon_guide_line_pen)

    @property
    def dataset(self):
        return self._dataset

    @dataset.setter
    def dataset(self, value):
        self._dataset = value

    @property
    def img_contrast(self):
        return self._img_contrast

    @img_contrast.setter
    def img_contrast(self, value):
        self._img_contrast = value

    @property
    def img_gamma(self):
        return self._img_gamma

    @img_gamma.setter
    def img_gamma(self, value):
        self._img_gamma = value

    @property
    def img_brightness(self):
        return self._img_brightness

    @img_brightness.setter
    def img_brightness(self, value):
        self._img_brightness = value

    @property
    def image(self):
        return self._image

    @image.setter
    def image(self, value):
        self._image = value
        self._image_original = value.copy()
        self.update_viewer()

    @property
    def pixmap(self) -> ImagePixmap:
        return self._pixmap

    @gui_exception
    def update_viewer(self, fit_image=True):
        rgb = cv2.cvtColor(self._image, cv2.COLOR_BGR2RGB)
        rgb = ImageUtilities.adjust_image(rgb, self._img_contrast,
                                          self._img_brightness)
        rgb = ImageUtilities.adjust_gamma(rgb, self._img_gamma)
        pil_image = Image.fromarray(rgb)
        qppixmap_image = pil_image.toqpixmap()
        x, y = -qppixmap_image.width() / 2, -qppixmap_image.height() / 2

        if self._pixmap:
            self._pixmap.resetTransform()
            self._pixmap.setPixmap(qppixmap_image)
            self._pixmap.setOffset(x, y)
        else:
            self._pixmap = ImagePixmap()
            self._pixmap.setPixmap(qppixmap_image)
            self._pixmap.setOffset(x, y)
            self._scene.addItem(self._pixmap)
            self._pixmap.signals.hoverEnterEventSgn.connect(
                self.pixmap_hoverEnterEvent_slot)
            self._pixmap.signals.hoverLeaveEventSgn.connect(
                self.pixmap_hoverLeaveEvent_slot)
            self._pixmap.signals.hoverMoveEventSgn.connect(
                self.pixmap_hoverMoveEvent_slot)
        self._hide_guide_lines()
        if fit_image:
            self.fit_to_window()

    @gui_exception
    def reset_viewer(self):
        self._img_contrast = 1.0
        self._img_brightness = 50.0
        self._img_gamma = 1.0
        self._image = self._image_original.copy()

    @gui_exception
    def equalize_histogram(self):
        self._image = ImageUtilities.histogram_equalization(self._image)

    @gui_exception
    def correct_lightness(self):
        self._image = ImageUtilities.correct_lightness(self._image)

    def clusterize(self, k):
        self._image = ImageUtilities.kmeans(self._image.copy(), k)

    @property
    def current_tool(self):
        return self._current_tool

    @current_tool.setter
    def current_tool(self, value):
        self._polygon_guide_line.hide()
        self._current_polygon = None
        self._current_free_path = None
        self._current_ellipse = None
        self._is_drawing = value == SELECTION_TOOL.FREE
        self._current_tool = value
        self.clear_extreme_points()
        if value == SELECTION_TOOL.POINTER:
            self.enable_items(True)
        else:
            self.enable_items(False)

    def fit_to_window(self):
        if not self._pixmap or not self._pixmap.pixmap():
            return
        self.resetTransform()
        self.setTransform(QtGui.QTransform())
        self.fitInView(self._pixmap, QtCore.Qt.KeepAspectRatio)

    def _create_grid(self, gridSize=15):
        app: QApplication = QApplication.instance()
        curr_theme = "dark"
        if app:
            curr_theme = app.property("theme")
        if curr_theme == "light":
            color1 = QtGui.QColor("white")
            color2 = QtGui.QColor(237, 237, 237)
        else:
            color1 = QtGui.QColor(20, 20, 20)
            color2 = QtGui.QColor(0, 0, 0)
        backgroundPixmap = QtGui.QPixmap(gridSize * 2, gridSize * 2)
        backgroundPixmap.fill(color1)
        painter = QtGui.QPainter(backgroundPixmap)
        painter.fillRect(0, 0, gridSize, gridSize, color2)
        painter.fillRect(gridSize, gridSize, gridSize, gridSize, color2)
        painter.end()
        self._scene.setBackgroundBrush(QtGui.QBrush(backgroundPixmap))

    def wheelEvent(self, event: QWheelEvent):
        adj = (event.angleDelta().y() / 120) * 0.1
        self.scale(1 + adj, 1 + adj)

    @gui_exception
    def keyPressEvent(self, event: QKeyEvent):
        if event.key() == QtCore.Qt.Key_Space:
            image_rect: QRectF = self._pixmap.sceneBoundingRect()
            if self.current_tool == SELECTION_TOOL.POLYGON and self._current_polygon:
                points = self._current_polygon.points
                self._polygon_guide_line.hide()
                self.setDragMode(QGraphicsView.ScrollHandDrag)
                if len(points) <= 2:
                    self._current_polygon.delete_item()
                self.current_tool = SELECTION_TOOL.POINTER
            elif self.current_tool == SELECTION_TOOL.EXTREME_POINTS and \
                    self._extreme_points.full():
                points = []
                image_offset = QPointF(image_rect.width() / 2,
                                       image_rect.height() / 2)
                for pt in self._extreme_points.queue:
                    pt: EditablePolygonPoint
                    center = pt.sceneBoundingRect().center()
                    x = math.floor(center.x() + image_offset.x())
                    y = math.floor(center.y() + image_offset.y())
                    points.append([x, y])
                self.points_selection_sgn.emit(points)
                self.current_tool = SELECTION_TOOL.POINTER
        else:
            event.ignore()

    # guide lines events
    def _show_guide_lines(self):
        if self.hline and self.vline:
            self.hline.show()
            self.vline.show()

    def _hide_guide_lines(self):
        if self.hline and self.vline:
            self.hline.hide()
            self.vline.hide()

    def _update_guide_lines(self, x, y):
        bbox: QRect = self._pixmap.boundingRect()
        offset = QPointF(bbox.width() / 2, bbox.height() / 2)
        self.vline.setLine(x, -offset.y(), x, bbox.height() - offset.y())
        self.vline.setZValue(1)
        self.hline.setLine(-offset.x(), y, bbox.width() - offset.x(), y)
        self.hline.setZValue(1)

    def pixmap_hoverMoveEvent_slot(self, evt: QGraphicsSceneHoverEvent, x, y):
        self._update_guide_lines(x, y)

    def pixmap_hoverEnterEvent_slot(self):
        self._show_guide_lines()

    def pixmap_hoverLeaveEvent_slot(self):
        self._hide_guide_lines()

    def delete_polygon_slot(self, polygon: EditablePolygon):
        self._current_polygon = None
        self.current_tool = SELECTION_TOOL.POINTER
        self._polygon_guide_line.hide()

    @gui_exception
    def mousePressEvent(self, evt: QtGui.QMouseEvent) -> None:
        image_rect: QRectF = self._pixmap.boundingRect()
        mouse_pos = self.mapToScene(evt.pos())
        if evt.buttons() == QtCore.Qt.LeftButton:
            if self.current_tool == SELECTION_TOOL.BOX:
                # create rectangle
                self.setDragMode(QGraphicsView.NoDrag)
                self._rectangle_tool_origin = evt.pos()
                geometry = QRect(self._rectangle_tool_origin, QSize())
                self._rectangle_tool_picker.setGeometry(geometry)
                self._rectangle_tool_picker.show()
            elif self.current_tool == SELECTION_TOOL.POLYGON:
                if image_rect.contains(mouse_pos):
                    if self._current_polygon is None:
                        self._current_polygon = EditablePolygon()
                        self._current_polygon.label = self._current_label
                        self._current_polygon.tag = self._dataset
                        self._current_polygon.signals.deleted.connect(
                            self.delete_polygon_slot)
                        self._scene.addItem(self._current_polygon)
                        self._current_polygon.addPoint(mouse_pos)
                    else:
                        self._current_polygon.addPoint(mouse_pos)
            elif self.current_tool == SELECTION_TOOL.ELLIPSE:
                if image_rect.contains(mouse_pos):
                    self.setDragMode(QGraphicsView.NoDrag)
                    ellipse_rec = QtCore.QRectF(mouse_pos.x(), mouse_pos.y(),
                                                0, 0)
                    self._current_ellipse = EditableEllipse()
                    self._current_ellipse.tag = self.dataset
                    self._current_ellipse.label = self._current_label
                    self._current_ellipse.setRect(ellipse_rec)
                    self._scene.addItem(self._current_ellipse)

            elif self.current_tool == SELECTION_TOOL.FREE:
                # consider only the points into the image
                if image_rect.contains(mouse_pos):
                    self.setDragMode(QGraphicsView.NoDrag)
                    self._last_point_drawn = mouse_pos
                    self._current_free_path = QGraphicsPathItem()
                    self._current_free_path.setOpacity(0.6)
                    self._current_free_path.setPen(self._free_Path_pen)
                    painter = QPainterPath()
                    painter.moveTo(self._last_point_drawn)
                    self._current_free_path.setPath(painter)
                    self._scene.addItem(self._current_free_path)

            elif self.current_tool == SELECTION_TOOL.EXTREME_POINTS:
                if image_rect.contains(mouse_pos):
                    if not self._extreme_points.full():

                        def delete_point(idx):
                            del self._extreme_points.queue[idx]

                        idx = self._extreme_points.qsize()
                        editable_pt = EditablePolygonPoint(idx)
                        editable_pt.signals.deleted.connect(delete_point)
                        editable_pt.setPos(mouse_pos)
                        self._scene.addItem(editable_pt)
                        self._extreme_points.put(editable_pt)

        else:
            self.setDragMode(QGraphicsView.ScrollHandDrag)
        super(ImageViewer, self).mousePressEvent(evt)

    @gui_exception
    def mouseMoveEvent(self, evt: QtGui.QMouseEvent) -> None:
        mouse_pos = self.mapToScene(evt.pos())
        image_rect: QRectF = self._pixmap.boundingRect()
        if self.current_tool == SELECTION_TOOL.BOX:
            if not self._rectangle_tool_origin.isNull():
                geometry = QRect(self._rectangle_tool_origin,
                                 evt.pos()).normalized()
                self._rectangle_tool_picker.setGeometry(geometry)
        elif self.current_tool == SELECTION_TOOL.POLYGON:
            if self._current_polygon and image_rect.contains(mouse_pos):
                if self._current_polygon.count > 0:
                    last_point: QPointF = self._current_polygon.last_point
                    self._polygon_guide_line.setZValue(1)
                    self._polygon_guide_line.show()
                    mouse_pos = self.mapToScene(evt.pos())
                    self._polygon_guide_line.setLine(last_point.x(),
                                                     last_point.y(),
                                                     mouse_pos.x(),
                                                     mouse_pos.y())
            else:
                self._polygon_guide_line.hide()
        elif self.current_tool == SELECTION_TOOL.ELLIPSE:
            if self._current_ellipse and image_rect.contains(mouse_pos):
                ellipse_rect = self._current_ellipse.rect()
                ellipse_pos = QPointF(ellipse_rect.x(), ellipse_rect.y())
                distance = math.hypot(mouse_pos.x() - ellipse_pos.x(),
                                      mouse_pos.y() - ellipse_pos.y())
                ellipse_rect.setWidth(distance)
                ellipse_rect.setHeight(distance)
                self._current_ellipse.setRect(ellipse_rect)
        elif self.current_tool == SELECTION_TOOL.FREE and evt.buttons(
        ) and QtCore.Qt.LeftButton:
            if self._current_free_path and image_rect.contains(mouse_pos):
                painter: QPainterPath = self._current_free_path.path()
                self._last_point_drawn = self.mapToScene(evt.pos())
                painter.lineTo(self._last_point_drawn)
                self._current_free_path.setPath(painter)
        super(ImageViewer, self).mouseMoveEvent(evt)

    @gui_exception
    def mouseReleaseEvent(self, evt: QtGui.QMouseEvent) -> None:
        image_rect: QRectF = self._pixmap.boundingRect()
        if self.current_tool == SELECTION_TOOL.BOX:
            roi: QRect = self._rectangle_tool_picker.geometry()
            roi: QRectF = self.mapToScene(roi).boundingRect()
            self._rectangle_tool_picker.hide()
            if image_rect == roi.united(image_rect):
                rect = EditableBox(roi)
                rect.label = self.current_label
                rect.tag = self._dataset
                self._scene.addItem(rect)
                self.current_tool = SELECTION_TOOL.POINTER
                self.setDragMode(QGraphicsView.ScrollHandDrag)

        elif self.current_tool == SELECTION_TOOL.ELLIPSE and self._current_ellipse:
            roi: QRect = self._current_ellipse.boundingRect()
            if image_rect == roi.united(image_rect):
                self.current_tool = SELECTION_TOOL.POINTER
                self.setDragMode(QGraphicsView.ScrollHandDrag)
            else:
                self._current_ellipse.delete_item()
        elif self.current_tool == SELECTION_TOOL.FREE and self._current_free_path:
            # create polygon
            self._current_free_path: QGraphicsPathItem
            path_rect = self._current_free_path.boundingRect()
            if image_rect == path_rect.united(image_rect):
                path = self._current_free_path.path()
                path_polygon = EditablePolygon()
                path_polygon.tag = self.dataset
                path_polygon.label = self.current_label
                self._scene.addItem(path_polygon)
                for i in range(0, path.elementCount(), 10):
                    x, y = path.elementAt(i).x, path.elementAt(i).y
                    path_polygon.addPoint(QPointF(x, y))
            self._scene.removeItem(self._current_free_path)
            self.current_tool = SELECTION_TOOL.POINTER
            self.setDragMode(QGraphicsView.ScrollHandDrag)
        super(ImageViewer, self).mouseReleaseEvent(evt)

    def remove_annotations(self):
        for item in self._scene.items():
            if isinstance(item, EditableItem):
                item.delete_item()

    def remove_annotations_by_label(self, label_name):
        for item in self._scene.items():
            if isinstance(item, EditableItem):
                if item.label and item.label.name == label_name:
                    item.delete_item()

    def enable_items(self, value):
        for item in self._scene.items():
            if isinstance(item, EditableItem):
                item.setEnabled(value)

    def clear_extreme_points(self):
        if self._extreme_points.qsize() > 0:
            for pt in self._extreme_points.queue:
                self._scene.removeItem(pt)
            self._extreme_points.queue.clear()
Esempio n. 3
0
class NodeItem(QGraphicsObject):
    """
    An widget node item in the canvas.
    """

    #: Signal emitted when the scene position of the node has changed.
    positionChanged = Signal()

    #: Signal emitted when the geometry of the channel anchors changes.
    anchorGeometryChanged = Signal()

    #: Signal emitted when the item has been activated (by a mouse double
    #: click or a keyboard)
    activated = Signal()

    #: The item is under the mouse.
    hovered = Signal()

    #: Span of the anchor in degrees
    ANCHOR_SPAN_ANGLE = 90

    #: Z value of the item
    Z_VALUE = 100

    def __init__(self, widget_description=None, parent=None, **kwargs):
        self.__boundingRect = None

        QGraphicsObject.__init__(self, parent, **kwargs)

        self.setFlag(QGraphicsItem.ItemSendsGeometryChanges, True)
        self.setFlag(QGraphicsItem.ItemHasNoContents, True)
        self.setFlag(QGraphicsItem.ItemIsSelectable, True)
        self.setFlag(QGraphicsItem.ItemIsMovable, True)
        self.setFlag(QGraphicsItem.ItemIsFocusable, True)

        # central body shape item
        self.shapeItem = None

        # in/output anchor items
        self.inputAnchorItem = None
        self.outputAnchorItem = None

        # title text item
        self.captionTextItem = None

        # error, warning, info items
        self.errorItem = None
        self.warningItem = None
        self.infoItem = None

        # background when selected
        self.backgroundItem = None

        self.__title = ""
        self.__processingState = 0
        self.__progress = -1
        self.__statusMessage = ""

        self.__error = None
        self.__warning = None
        self.__info = None

        self.__anchorLayout = None
        self.__animationEnabled = False

        self.setZValue(self.Z_VALUE)
        self.setupGraphics()
        self.setWidgetDescription(widget_description)

    @classmethod
    def from_node(cls, node):
        """
        Create an :class:`NodeItem` instance and initialize it from a
        :class:`SchemeNode` instance.

        """
        self = cls()
        self.setWidgetDescription(node.description)
#        self.setCategoryDescription(node.category)
        return self

    @classmethod
    def from_node_meta(cls, meta_description):
        """
        Create an `NodeItem` instance from a node meta description.
        """
        self = cls()
        self.setWidgetDescription(meta_description)
        return self

    def setupGraphics(self):
        """
        Set up the graphics.
        """
        shape_rect = QRectF(-24, -24, 48, 48)

        self.shapeItem = NodeBodyItem(self)
        self.shapeItem.setShapeRect(shape_rect)
        self.shapeItem.setAnimationEnabled(self.__animationEnabled)

        # Rect for widget's 'ears'.
        anchor_rect = QRectF(-31, -31, 62, 62)
        self.inputAnchorItem = SinkAnchorItem(self)
        input_path = QPainterPath()
        start_angle = 180 - self.ANCHOR_SPAN_ANGLE / 2
        input_path.arcMoveTo(anchor_rect, start_angle)
        input_path.arcTo(anchor_rect, start_angle, self.ANCHOR_SPAN_ANGLE)
        self.inputAnchorItem.setAnchorPath(input_path)

        self.outputAnchorItem = SourceAnchorItem(self)
        output_path = QPainterPath()
        start_angle = self.ANCHOR_SPAN_ANGLE / 2
        output_path.arcMoveTo(anchor_rect, start_angle)
        output_path.arcTo(anchor_rect, start_angle, - self.ANCHOR_SPAN_ANGLE)
        self.outputAnchorItem.setAnchorPath(output_path)

        self.inputAnchorItem.hide()
        self.outputAnchorItem.hide()

        # Title caption item
        self.captionTextItem = NameTextItem(self)

        self.captionTextItem.setPlainText("")
        self.captionTextItem.setPos(0, 33)


        def iconItem(standard_pixmap):
            item = GraphicsIconItem(self, icon=standard_icon(standard_pixmap),
                                    iconSize=QSize(16, 16))
            item.hide()

            return item


        self.errorItem = iconItem(QStyle.SP_MessageBoxCritical)
        self.warningItem = iconItem(QStyle.SP_MessageBoxWarning)
        self.infoItem = iconItem(QStyle.SP_MessageBoxInformation)


        ################################
        # PyQt 5.10 crashes because of this call (2)
        #self.backgroundItem = QGraphicsPathItem(self)
        self.backgroundItem = QGraphicsPathItem(None)
        ################################

        backgroundrect = QPainterPath()
        backgroundrect.addRoundedRect(anchor_rect.adjusted(-4, -2, 4, 2),
                                      5, 5, mode=Qt.AbsoluteSize)
        self.backgroundItem.setPen(QPen(Qt.NoPen))
        self.backgroundItem.setBrush(QPalette().brush(QPalette.Highlight))
        self.backgroundItem.setOpacity(0.5)
        self.backgroundItem.setPath(backgroundrect)
        self.backgroundItem.setZValue(-10)
        self.backgroundItem.setVisible(self.isSelected())

        self.prepareGeometryChange()
        self.__boundingRect = None


    # TODO: Remove the set[Widget|Category]Description. The user should
    # handle setting of icons, title, ...
    def setWidgetDescription(self, desc):
        """
        Set widget description.
        """
        self.widget_description = desc
        if desc is None:
            return

        icon = icon_loader.from_description(desc).get(desc.icon)
        if icon:
            self.setIcon(icon)

        if not self.title():
            self.setTitle(desc.name)

        if desc.inputs:
            self.inputAnchorItem.show()
        if desc.outputs:
            self.outputAnchorItem.show()

        tooltip = NodeItem_toolTipHelper(self)
        self.setToolTip(tooltip)

    def setWidgetCategory(self, desc):
        """
        Set the widget category.
        """
        self.category_description = desc
        if desc and desc.background:
            background = NAMED_COLORS.get(desc.background, desc.background)
            color = QColor(background)
            if color.isValid():
                self.setColor(color)

    def setIcon(self, icon):
        """
        Set the node item's icon (:class:`QIcon`).
        """
        if isinstance(icon, QIcon):
            self.icon_item = GraphicsIconItem(self.shapeItem, icon=icon,
                                              iconSize=QSize(36, 36))
            self.icon_item.setPos(-18, -18)
        else:
            raise TypeError

    def setColor(self, color, selectedColor=None):
        """
        Set the widget color.
        """
        if selectedColor is None:
            selectedColor = saturated(color, 150)
        palette = create_palette(color, selectedColor)
        self.shapeItem.setPalette(palette)

    def setPalette(self, palette):
        # TODO: The palette should override the `setColor`
        raise NotImplementedError

    def setTitle(self, title):
        """
        Set the node title. The title text is displayed at the bottom of the
        node.

        """
        self.__title = title
        self.__updateTitleText()

    def title(self):
        """
        Return the node title.
        """
        return self.__title

    title_ = Property(six.text_type, fget=title, fset=setTitle,
                      doc="Node title text.")

    def setFont(self, font):
        """
        Set the title text font (:class:`QFont`).
        """
        if font != self.font():
            self.prepareGeometryChange()
            self.captionTextItem.setFont(font)
            self.__updateTitleText()

    def font(self):
        """
        Return the title text font.
        """
        return self.captionTextItem.font()

    def setAnimationEnabled(self, enabled):
        """
        Set the node animation enabled state.
        """
        if self.__animationEnabled != enabled:
            self.__animationEnabled = enabled
            self.shapeItem.setAnimationEnabled(enabled)

    def animationEnabled(self):
        """
        Are node animations enabled.
        """
        return self.__animationEnabled

    def setProcessingState(self, state):
        """
        Set the node processing state i.e. the node is processing
        (is busy) or is idle.

        """
        if self.__processingState != state:
            self.__processingState = state
            self.shapeItem.setProcessingState(state)
            if not state:
                # Clear the progress meter.
                self.setProgress(-1)
                if self.__animationEnabled:
                    self.shapeItem.ping()

    def processingState(self):
        """
        The node processing state.
        """
        return self.__processingState

    processingState_ = Property(int, fget=processingState,
                                fset=setProcessingState)

    def setProgress(self, progress):
        """
        Set the node work progress state (number between 0 and 100).
        """
        if progress is None or progress < 0 or not self.__processingState:
            progress = -1

        progress = max(min(progress, 100), -1)
        if self.__progress != progress:
            self.__progress = progress
            self.shapeItem.setProgress(progress)
            self.__updateTitleText()

    def progress(self):
        """
        Return the node work progress state.
        """
        return self.__progress

    progress_ = Property(float, fget=progress, fset=setProgress,
                         doc="Node progress state.")

    def setStatusMessage(self, message):
        """
        Set the node status message text.

        This text is displayed below the node's title.

        """
        if self.__statusMessage != message:
            self.__statusMessage = six.text_type(message)
            self.__updateTitleText()

    def statusMessage(self):
        return self.__statusMessage

    def setStateMessage(self, message):
        """
        Set a state message to display over the item.

        Parameters
        ----------
        message : UserMessage
            Message to display. `message.severity` is used to determine
            the icon and `message.contents` is used as a tool tip.

        """
        # TODO: Group messages by message_id not by severity
        # and deprecate set[Error|Warning|Error]Message
        if message.severity == UserMessage.Info:
            self.setInfoMessage(message.contents)
        elif message.severity == UserMessage.Warning:
            self.setWarningMessage(message.contents)
        elif message.severity == UserMessage.Error:
            self.setErrorMessage(message.contents)

    def setErrorMessage(self, message):
        if self.__error != message:
            self.__error = message
            self.__updateMessages()

    def setWarningMessage(self, message):
        if self.__warning != message:
            self.__warning = message
            self.__updateMessages()

    def setInfoMessage(self, message):
        if self.__info != message:
            self.__info = message
            self.__updateMessages()

    def newInputAnchor(self):
        """
        Create and return a new input :class:`AnchorPoint`.
        """
        if not (self.widget_description and self.widget_description.inputs):
            raise ValueError("Widget has no inputs.")

        anchor = AnchorPoint()
        self.inputAnchorItem.addAnchor(anchor, position=1.0)

        positions = self.inputAnchorItem.anchorPositions()
        positions = uniform_linear_layout(positions)
        self.inputAnchorItem.setAnchorPositions(positions)

        return anchor

    def removeInputAnchor(self, anchor):
        """
        Remove input anchor.
        """
        self.inputAnchorItem.removeAnchor(anchor)

        positions = self.inputAnchorItem.anchorPositions()
        positions = uniform_linear_layout(positions)
        self.inputAnchorItem.setAnchorPositions(positions)

    def newOutputAnchor(self):
        """
        Create and return a new output :class:`AnchorPoint`.
        """
        if not (self.widget_description and self.widget_description.outputs):
            raise ValueError("Widget has no outputs.")

        anchor = AnchorPoint(self)
        self.outputAnchorItem.addAnchor(anchor, position=1.0)

        positions = self.outputAnchorItem.anchorPositions()
        positions = uniform_linear_layout(positions)
        self.outputAnchorItem.setAnchorPositions(positions)

        return anchor

    def removeOutputAnchor(self, anchor):
        """
        Remove output anchor.
        """
        self.outputAnchorItem.removeAnchor(anchor)

        positions = self.outputAnchorItem.anchorPositions()
        positions = uniform_linear_layout(positions)
        self.outputAnchorItem.setAnchorPositions(positions)

    def inputAnchors(self):
        """
        Return a list of all input anchor points.
        """
        return self.inputAnchorItem.anchorPoints()

    def outputAnchors(self):
        """
        Return a list of all output anchor points.
        """
        return self.outputAnchorItem.anchorPoints()

    def setAnchorRotation(self, angle):
        """
        Set the anchor rotation.
        """
        self.inputAnchorItem.setRotation(angle)
        self.outputAnchorItem.setRotation(angle)
        self.anchorGeometryChanged.emit()

    def anchorRotation(self):
        """
        Return the anchor rotation.
        """
        return self.inputAnchorItem.rotation()

    def boundingRect(self):
        # TODO: Important because of this any time the child
        # items change geometry the self.prepareGeometryChange()
        # needs to be called.
        if self.__boundingRect is None:
            self.__boundingRect = self.childrenBoundingRect()
        return self.__boundingRect

    def shape(self):
        # Shape for mouse hit detection.
        # TODO: Should this return the union of all child items?
        return self.shapeItem.shape()

    def __updateTitleText(self):
        """
        Update the title text item.
        """
        text = ['<div align="center">%s' % escape(self.title())]

        status_text = []

        progress_included = False
        if self.__statusMessage:
            msg = escape(self.__statusMessage)
            format_fields = dict(parse_format_fields(msg))
            if "progress" in format_fields and len(format_fields) == 1:
                # Insert progress into the status text format string.
                spec, _ = format_fields["progress"]
                if spec != None:
                    progress_included = True
                    progress_str = "{0:.0f}%".format(self.progress())
                    status_text.append(msg.format(progress=progress_str))
            else:
                status_text.append(msg)

        if self.progress() >= 0 and not progress_included:
            status_text.append("%i%%" % int(self.progress()))

        if status_text:
            text += ["<br/>",
                     '<span style="font-style: italic">',
                     "<br/>".join(status_text),
                     "</span>"]
        text += ["</div>"]
        text = "".join(text)

        # The NodeItems boundingRect could change.
        self.prepareGeometryChange()
        self.__boundingRect = None
        self.captionTextItem.setHtml(text)
        self.captionTextItem.document().adjustSize()
        width = self.captionTextItem.textWidth()
        self.captionTextItem.setPos(-width / 2.0, 33)

    def __updateMessages(self):
        """
        Update message items (position, visibility and tool tips).
        """
        items = [self.errorItem, self.warningItem, self.infoItem]

        messages = [self.__error, self.__warning, self.__info]
        for message, item in zip(messages, items):
            item.setVisible(bool(message))
            item.setToolTip(message or "")

        shown = [item for item in items if item.isVisible()]
        count = len(shown)
        if count:
            spacing = 3
            rects = [item.boundingRect() for item in shown]
            width = sum(rect.width() for rect in rects)
            width += spacing * max(0, count - 1)
            height = max(rect.height() for rect in rects)
            origin = self.shapeItem.boundingRect().top() - spacing - height
            origin = QPointF(-width / 2, origin)
            for item, rect in zip(shown, rects):
                item.setPos(origin)
                origin = origin + QPointF(rect.width() + spacing, 0)

    def mousePressEvent(self, event):
        if self.shapeItem.path().contains(event.pos()):
            return QGraphicsObject.mousePressEvent(self, event)
        else:
            event.ignore()

    def mouseDoubleClickEvent(self, event):
        if self.shapeItem.path().contains(event.pos()):
            QGraphicsObject.mouseDoubleClickEvent(self, event)
            QTimer.singleShot(0, self.activated.emit)
        else:
            event.ignore()

    def contextMenuEvent(self, event):
        if self.shapeItem.path().contains(event.pos()):
            return QGraphicsObject.contextMenuEvent(self, event)
        else:
            event.ignore()

    def focusInEvent(self, event):
        self.shapeItem.setHasFocus(True)
        return QGraphicsObject.focusInEvent(self, event)

    def focusOutEvent(self, event):
        self.shapeItem.setHasFocus(False)
        return QGraphicsObject.focusOutEvent(self, event)

    def itemChange(self, change, value):
        if change == QGraphicsItem.ItemSelectedChange:
            selected = bool(qtcompat.qunwrap(value))
            self.shapeItem.setSelected(selected)
            self.captionTextItem.setSelectionState(selected)
            self.backgroundItem.setVisible(selected)
        elif change == QGraphicsItem.ItemPositionHasChanged:
            self.positionChanged.emit()

        return QGraphicsObject.itemChange(self, change, value)