コード例 #1
0
ファイル: label_six.py プロジェクト: ScCcWe/See_Then_Use
    def forward_json(self):
        json_path = self.seleDir + '\\' + self.dir_list[
            self.dir_show_num].split('.', 2)[0] + '.json'

        if os.path.exists(json_path):
            with open(json_path, "r", encoding="utf-8") as f:
                data = json.load(f)
                # self.tempDict = data[0]['labels']
                # print(self.tempDict)
                # print(data)
                for shape in data:
                    points = shape['position']
                    label = shape['labels']
                    start_point = QtCore.QPointF(points[0]['x'],
                                                 points[0]['y'])  # 左上
                    end_point = QtCore.QPointF(points[1]['x'],
                                               points[1]['y'])  # 右下

                    two_points = Shape()
                    two_points.addPoint(start_point)
                    two_points.addPoint(end_point)
                    four_points = self.canvas.points_to_point_four(
                        copy.deepcopy(two_points))

                    with_points_shape = Shape()
                    with_points_shape.points = four_points
                    with_points_shape.close()  # 闭合最后一条线
                    self.canvas.shapes.append(with_points_shape)
                    self.canvas.shapes[-1].label = label
                self.canvas.repaint()
        else:
            self.tempDict = copy.deepcopy(self.exampleDict)
コード例 #2
0
    def loadLabels(self, shapes):
        s = []
        for label, points, line_color, fill_color, difficult in shapes:
            shape = Shape(label=label)
            for x, y in points:

                # Ensure the labels are within the bounds of the image.
                # If not, fix them.
                x, y, snapped = self.canvas.snapPointToCanvas(x, y)
                if snapped:
                    self.setDirty()

                shape.addPoint(QPointF(x, y))
            shape.difficult = difficult
            shape.close()
            s.append(shape)

            if line_color:
                shape.line_color = QColor(*line_color)
            else:
                shape.line_color = generateColorByText(label)

            if fill_color:
                shape.fill_color = QColor(*fill_color)
            else:
                shape.fill_color = generateColorByText(label)

            self.addLabel(shape)
        # self.updateComboBox()
        self.canvas.loadShapes(s)
コード例 #3
0
    def load_record(self):
        if not self.may_continue():
            return
        self.set_clean()
        self.clear_labels()
        item = self.recordList.selectedItems()[0]
        if not item:
            return
        self.current_record = item.text()
        if self.stream_enabled:
            self._stop_video()

        component = self.componentList.selectedItems()[0].text()
        filename = item.text().replace('№', '')
        f = filename.split(' ')
        filename = '{}{:04d}'.format(f[0], int(f[1]))
        path = os.path.join(self.path, component, 'records', filename)

        img_path = path + '.jpg'
        txt_path = path + '.txt'
        self.frame = cv2.imread(img_path)
        if self.frame is None:
            return
        self.frame = cv2.cvtColor(self.frame, cv2.COLOR_BGR2RGB)
        self.canvas.loadPixmap(
            QPixmap.fromImage(qimage2ndarray.array2qimage(self.frame)))
        self.canvas.adjustSize()

        with open(txt_path, 'r') as bndboxes_file:
            for box in bndboxes_file:
                index, xcen, ycen, w, h = box.strip().split(' ')
                label = self.database_handler.classes[int(index)]
                xmin, ymin, xmax, ymax = yolo2points(xcen, ycen, w, h,
                                                     self.frame.shape[1],
                                                     self.frame.shape[0])
                points = [(xmin, ymin), (xmax, ymin), (xmax, ymax),
                          (xmin, ymax)]

                shape = Shape(label=label)
                for x, y in points:
                    x, y, snapped = self.canvas.snapPointToCanvas(x, y)
                    if snapped:
                        self.set_dirty()
                    shape.addPoint(QPointF(x, y))
                shape.difficult = False
                shape.fill_color = generate_color_by_text(label)
                shape.line_color = generate_color_by_text(label)
                shape.close()
                self.shapes.append(shape)
                self.add_label(shape)
        self.canvas.loadShapes(self.shapes)
コード例 #4
0
def loadLabels(shapes):
    s = []
    for label, points, line_color, fill_color, difficult in shapes:
        shape = Shape(label=label)
        for x, y in points:
            # Ensure the labels are within the bounds of the image. If not, fix them.
            # x, y, snapped = self.canvas.snapPointToCanvas(x, y)
            # if snapped:
                # self.setDirty()

            shape.addPoint(QPointF(x, y))
        shape.difficult = difficult
        shape.close()
        s.append(shape)

    return s
コード例 #5
0
def randomTable(num):
    # Create a black image
    img = np.zeros((512, 512, 3), np.uint8)
    img.fill(255)
    left = random.randint(20, 50)
    top = random.randint(30, 50)
    width = random.randint(100, 150)
    height = random.randint(30, 70)

    row = random.randint(2, 4)
    column = random.randint(2, 3)

    if row == 1 and column == 1:
        width, height = width * 2, height * 2

    rectShapes = []

    # print("image:{}, row num={}, column num ={} ".format(num, row, column))

    for r in range(0, row):
        for c in range(0, column):
            start_point = (left + c * width, top + r * height)
            end_point = (left + (c + 1) * width, top + (r + 1) * height)

            # 画框
            thickness = [1, 2]  # 表格线粗细随机
            cv.rectangle(img, start_point, end_point, BLACK,
                         random.choice(thickness))

            points = [start_point, end_point]
            shape = Shape(label='rect')
            for x, y in points:
                shape.addPoint(Point(x, y))
            shape.close()
            rectShapes.append(shape)

    imagePath = 'VOC2007/JPEGImages/'
    xmlPath = 'VOC2007/Annotations/'
    fileName = imagePath + num + IMAGE_EXT
    xmlName = xmlPath + num + XML_EXT
    # print('fileName=', fileName)

    cv.imwrite(fileName, img)

    savePascalVocFormat(xmlName, rectShapes, imagePath, img)
コード例 #6
0
    def createBox(self, pos=None):
        if pos is None:
            pos = self.mousepos

        if self.quickbox:
            if not self.outOfPixmap(pos):
                shape = Shape(label="sugar-beet")
                dim = self.box_size / 2
                x = pos.x()
                y = pos.y()
                points = ((x - dim, y - dim), (x + dim, y - dim),
                          (x + dim, y + dim), (x - dim, y + dim))
                for p in points:
                    shape.addPoint(QPointF(*p))
                shape.close()
                self.shapes.append(shape)
                self.boxCreated.emit()
                return True
        return False
コード例 #7
0
    def loadLabels(self, shapes):
        s = []

        for label, points, line_color, fill_color, difficult in shapes:
            shape = Shape(label=label)

            for x, y in points:
                shape.addPoint(QPointF(x, y))

            shape.difficult = difficult
            shape.close()
            s.append(shape)

            self.addLabel(shape)

            if line_color:
                shape.line_color = QColor(*line_color)

            if fill_color:
                shape.fill_color = QColor(*fill_color)

        self.canvas.loadShapes(s)
コード例 #8
0
ファイル: canvas.py プロジェクト: fisis/PTF
 def add_shape(self, bbox, label, bbox_source, id_number):
     is_gate = label == "gate"
     shape = Shape(label, bbox_source=bbox_source, id_number=id_number, is_gate=is_gate)
     xmin, ymin, xmax, ymax = bbox
     if is_gate:
         shape.addPoint(QPointF(xmin, ymin))
         shape.addPoint(QPointF(xmax, ymax))
     else:
         shape.addPoint(QPointF(xmin, ymin))
         shape.addPoint(QPointF(xmax, ymin))
         shape.addPoint(QPointF(xmax, ymax))
         shape.addPoint(QPointF(xmin, ymax))
     shape.close()
     self.main_window.addLabel(shape)
     self.shapes.append(shape)
コード例 #9
0
ファイル: canvas.py プロジェクト: fisis/PTF
class Canvas(QWidget):
    """
    :type main_window: MainWindow
    """

    zoomRequest = pyqtSignal(int)
    scrollRequest = pyqtSignal(int, int)
    newShape = pyqtSignal()
    selectionChanged = pyqtSignal(bool)
    shapeMoved = pyqtSignal()
    drawingPolygon = pyqtSignal(bool)

    CREATE, EDIT = list(range(2))

    epsilon = 11.0

    def __init__(self, main_window, *args, **kwargs):
        super(Canvas, self).__init__(*args, **kwargs)
        # Initialise local state.
        self.mode = self.EDIT
        self.shapes = []
        self.current = None
        self.selectedShape = None  # save the selected shape here
        self.selectedShapeCopy = None
        self.lineColor = QColor(0, 0, 255)
        self.line = Shape()
        self.prevPoint = QPointF()
        self.offsets = QPointF(), QPointF()
        self.scale = 1.0
        self.pixmap = QPixmap()
        self.visible = {}
        self._hideBackround = False
        self.hideBackround = False
        self.hShape = None
        self.hVertex = None
        self._painter = QPainter()
        self._cursor = CURSOR_DEFAULT
        # Menus:
        self.menus = (QMenu(), QMenu())
        # Set widget options.
        self.setMouseTracking(True)
        self.setFocusPolicy(Qt.WheelFocus)
        self.verified = False

        self.main_window = main_window
        self.has_text = False
        self.is_gate = False

        self.last_click_t = 0

    def enterEvent(self, ev):
        self.overrideCursor(self._cursor)

    def leaveEvent(self, ev):
        self.restoreCursor()

    def focusOutEvent(self, ev):
        self.restoreCursor()

    def isVisible(self, shape):
        return self.visible.get(shape, True)

    def drawing(self):
        return self.mode == self.CREATE

    def editing(self):
        return self.mode == self.EDIT

    def setEditing(self, value=True, is_gate=False, id_number=None):
        self.mode = self.EDIT if value else self.CREATE
        if not value:  # Create
            self.unHighlight()
            self.deSelectShape()
            self.is_gate = is_gate
            self.id_number = id_number
        self.prevPoint = QPointF()
        self.repaint()

    def unHighlight(self):
        if self.hShape:
            self.hShape.highlightClear()
        self.hVertex = self.hShape = None

    def selectedVertex(self):
        return self.hVertex is not None

    def mouseMoveEvent(self, ev):
        """Update line with last point and current coordinates."""
        pos = self.transformPos(ev.pos())

        # Polygon drawing.
        if self.drawing():
            self.overrideCursor(CURSOR_DRAW)
            if self.current:
                color = self.lineColor
                if self.outOfPixmap(pos):
                    # Don't allow the user to draw outside the pixmap.
                    # Project the point to the pixmap's edges.
                    pos = self.intersectionPoint(self.current[-1], pos)
                elif len(self.current) > 1 and self.closeEnough(pos, self.current[0]):
                    # Attract line to starting point and colorise to alert the
                    # user:
                    pos = self.current[0]
                    color = self.current.line_color
                    self.overrideCursor(CURSOR_POINT)
                    self.current.highlightVertex(0, Shape.NEAR_VERTEX)
                self.line[1] = pos
                self.line.line_color = color
                self.prevPoint = QPointF()
                self.current.highlightClear()
            else:
                self.prevPoint = pos
            self.repaint()
            return

        # Polygon copy moving.
        if Qt.RightButton & ev.buttons():
            if self.selectedShapeCopy and self.prevPoint:
                self.overrideCursor(CURSOR_MOVE)
                self.boundedMoveShape(self.selectedShapeCopy, pos)
                self.repaint()
            elif self.selectedShape:
                self.selectedShapeCopy = self.selectedShape.copy()
                self.repaint()
            return

        # Polygon/Vertex moving.
        if Qt.LeftButton & ev.buttons():
            if self.selectedVertex():
                self.boundedMoveVertex(pos)
                self.shapeMoved.emit()
                self.repaint()
            elif self.selectedShape and self.prevPoint:
                self.overrideCursor(CURSOR_MOVE)
                self.boundedMoveShape(self.selectedShape, pos)
                self.shapeMoved.emit()
                self.repaint()
            return

        # Just hovering over the canvas, 2 posibilities:
        # - Highlight shapes
        # - Highlight vertex
        # Update shape/vertex fill and tooltip value accordingly.
        self.setToolTip("Image")
        for shape in reversed([s for s in self.shapes if self.isVisible(s)]):
            # Look for a nearby vertex to highlight. If that fails,
            # check if we happen to be inside a shape.
            index = shape.nearestVertex(pos, self.epsilon)
            if index is not None:
                if self.selectedVertex():
                    self.hShape.highlightClear()
                self.hVertex, self.hShape = index, shape
                shape.highlightVertex(index, shape.MOVE_VERTEX)
                self.overrideCursor(CURSOR_POINT)
                self.setToolTip("Click & drag to move point")
                self.setStatusTip(self.toolTip())
                self.update()
                break
            elif shape.containsPoint(pos):
                if self.selectedVertex():
                    self.hShape.highlightClear()
                self.hVertex, self.hShape = None, shape
                self.setToolTip(
                    "Click & drag to move shape '%s'\n"
                    "Double click to add mask" % shape.label)
                self.setStatusTip(self.toolTip())
                self.overrideCursor(CURSOR_GRAB)
                self.update()
                break
        else:  # Nothing found, clear highlights, reset state.
            if self.hShape:
                self.hShape.highlightClear()
                self.update()
            self.hVertex, self.hShape = None, None
            self.overrideCursor(CURSOR_DEFAULT)

    def mousePressEvent(self, ev):
        pos = self.transformPos(ev.pos())

        if ev.button() == Qt.LeftButton:
            if self.drawing():
                self.handleDrawing(pos)
            else:
                self.selectShapePoint(pos)
                self.prevPoint = pos
                self.repaint()
        elif ev.button() == Qt.RightButton and self.editing():
            self.selectShapePoint(pos)
            self.prevPoint = pos
            self.repaint()

    def mouseReleaseEvent(self, ev):
        if ev.button() == Qt.RightButton:
            menu = self.menus[bool(self.selectedShapeCopy)]
            self.restoreCursor()
            if not menu.exec_(self.mapToGlobal(ev.pos())) \
                    and self.selectedShapeCopy:
                # Cancel the move by deleting the shadow copy.
                self.selectedShapeCopy = None
                self.repaint()
        elif ev.button() == Qt.LeftButton and self.selectedShape:
            if self.selectedVertex():
                self.overrideCursor(CURSOR_POINT)
                # else:
                #     click_t = time.time()
                #     if self.last_click_t:
                #         click_interval = click_t - self.last_click_t
                #         self.last_click_t = 0
                #         if click_interval < 1:
                #             # print('Double click detected')
                #             self.selectedShape.addMask(self.main_window.image,
                #                                        self.main_window.args.mask_disp_size,
                #                                        self.main_window.args.mask_del_thresh,
                #                                        self.main_window.args.mask_magnified_window)
                #             # self.selectedShape.addMask(self.main_window.image)
                #     else:
                #         self.last_click_t = click_t

                self.overrideCursor(CURSOR_GRAB)
        elif ev.button() == Qt.LeftButton:
            pos = self.transformPos(ev.pos())
            if self.drawing():
                self.handleDrawing(pos)

    def endMove(self, copy=False):
        assert self.selectedShape and self.selectedShapeCopy
        shape = self.selectedShapeCopy
        # del shape.fill_color
        # del shape.line_color
        if copy:
            self.shapes.append(shape)
            self.selectedShape.selected = False
            self.selectedShape = shape
            self.repaint()
        else:
            self.selectedShape.points = [p for p in shape.points]
        self.selectedShapeCopy = None

    def hideBackroundShapes(self, value):
        self.hideBackround = value
        if self.selectedShape:
            # Only hide other shapes if there is a current selection.
            # Otherwise the user will not be able to select a shape.
            self.setHiding(True)
            self.repaint()

    def handleDrawing(self, pos):
        if self.current and self.current.reachMaxPoints() is False:
            if self.is_gate:
                targetPos = self.line[1]
                self.current.addPoint(targetPos)
            else:
                initPos = self.current[0]
                minX = initPos.x()
                minY = initPos.y()
                targetPos = self.line[1]
                maxX = targetPos.x()
                maxY = targetPos.y()
                self.current.addPoint(QPointF(maxX, minY))
                self.current.addPoint(targetPos)
                self.current.addPoint(QPointF(minX, maxY))
            self.finalise()
        elif not self.outOfPixmap(pos):
            self.current = Shape(is_gate=self.is_gate, id_number=self.id_number)
            self.current.addPoint(pos)
            self.line.points = [pos, pos]
            self.setHiding()
            self.drawingPolygon.emit(True)
            self.update()

    def setHiding(self, enable=True):
        self._hideBackround = self.hideBackround if enable else False

    def canCloseShape(self):
        return self.drawing() and self.current and len(self.current) > 2

    def mouseDoubleClickEvent(self, ev):
        # We need at least 4 points here, since the mousePress handler
        # adds an extra one before this handler is called.
        if self.canCloseShape() and len(self.current) > 3:
            self.current.popPoint()
            self.finalise()
        elif self.selectedShape is not None:
            self.selectedShape.addMask(self.main_window.image_np,
                                       self.main_window.params.mask,
                                       self.main_window.augment,
                                       self.main_window.hed_net
                                       )

    def selectShape(self, shape):
        self.deSelectShape()
        shape.selected = True
        self.selectedShape = shape
        self.setHiding()
        self.selectionChanged.emit(True)
        self.update()

    def selectShapePoint(self, point):
        """Select the first shape created which contains this point."""
        self.deSelectShape()
        if self.selectedVertex():  # A vertex is marked for selection.
            index, shape = self.hVertex, self.hShape
            shape.highlightVertex(index, shape.MOVE_VERTEX)
            self.selectShape(shape)
            return
        for shape in reversed(self.shapes):
            if self.isVisible(shape) and shape.containsPoint(point):
                self.selectShape(shape)
                self.calculateOffsets(shape, point)
                return

    def calculateOffsets(self, shape, point):
        rect = shape.boundingRect()
        x1 = rect.x() - point.x()
        y1 = rect.y() - point.y()
        x2 = (rect.x() + rect.width()) - point.x()
        y2 = (rect.y() + rect.height()) - point.y()
        self.offsets = QPointF(x1, y1), QPointF(x2, y2)

    def boundedMoveVertex(self, pos):
        index, shape = self.hVertex, self.hShape
        point = shape[index]
        if self.outOfPixmap(pos):
            try:
                pos = self.intersectionPoint(point, pos)
            except ValueError:
                return

        shiftPos = pos - point
        shape.moveVertexBy(index, shiftPos)

        if shape.is_gate:
            _shift = QPointF(shiftPos.x(), shiftPos.y())
            shape.moveVertexBy(index, _shift)
        else:
            lindex = (index + 1) % 4
            rindex = (index + 3) % 4
            lshift = None
            rshift = None
            if index % 2 == 0:
                rshift = QPointF(shiftPos.x(), 0)
                lshift = QPointF(0, shiftPos.y())
            else:
                lshift = QPointF(shiftPos.x(), 0)
                rshift = QPointF(0, shiftPos.y())
            shape.moveVertexBy(rindex, rshift)
            shape.moveVertexBy(lindex, lshift)

    def boundedMoveShape(self, shape, pos):
        if self.outOfPixmap(pos):
            return False  # No need to move
        o1 = pos + self.offsets[0]
        if self.outOfPixmap(o1):
            pos -= QPointF(min(0, o1.x()), min(0, o1.y()))
        o2 = pos + self.offsets[1]
        if self.outOfPixmap(o2):
            pos += QPointF(min(0, self.pixmap.width() - o2.x()),
                           min(0, self.pixmap.height() - o2.y()))
        # The next line tracks the new position of the cursor
        # relative to the shape, but also results in making it
        # a bit "shaky" when nearing the border and allows it to
        # go outside of the shape's area for some reason. XXX
        # self.calculateOffsets(self.selectedShape, pos)
        dp = pos - self.prevPoint
        if dp:
            shape.moveBy(dp)
            self.prevPoint = pos
            return True
        return False

    def deSelectShape(self):
        if self.selectedShape:
            self.selectedShape.selected = False
            self.selectedShape = None
            self.setHiding(False)
            self.selectionChanged.emit(False)
            self.update()

    def deleteSelected(self):
        if self.selectedShape:
            shape = self.selectedShape
            self.shapes.remove(self.selectedShape)
            self.selectedShape = None
            self.update()
            return shape

    def copySelectedShape(self):
        if self.selectedShape:
            shape = self.selectedShape.copy()
            self.deSelectShape()
            self.shapes.append(shape)
            shape.selected = True
            self.selectedShape = shape
            self.boundedShiftShape(shape)
            return shape

    def boundedShiftShape(self, shape):
        # Try to move in one direction, and if it fails in another.
        # Give up if both fail.
        point = shape[0]
        offset = QPointF(2.0, 2.0)
        self.calculateOffsets(shape, point)
        self.prevPoint = point
        if not self.boundedMoveShape(shape, point - offset):
            self.boundedMoveShape(shape, point + offset)

    def paintGates(self, painter, gates):
        for gate in gates:
            points = gate['points']
            gate_id = gate['id']

            color = self.select_line_color if self.selected else self.line_color
            pen = QPen(color)
            # Try using integer sizes for smoother drawing(?)
            pen.setWidth(max(1, int(round(2.0 / self.scale))))
            painter.setPen(pen)

            line_path = QPainterPath()
            vrtx_path = QPainterPath()

            line_path.moveTo(points[0])
            # Uncommenting the following line will draw 2 paths
            # for the 1st vertex, and make it non-filled, which
            # may be desirable.
            # self.drawVertex(vrtx_path, 0)

            for i, p in enumerate(points):
                line_path.lineTo(p)
                self.drawVertex(vrtx_path, i)
            if self.isClosed():
                line_path.lineTo(points[0])

            painter.drawPath(line_path)
            painter.drawPath(vrtx_path)
            painter.fillPath(vrtx_path, self.vertex_fill_color)
            if self.fill:
                color = self.select_fill_color if self.selected else self.fill_color
                painter.fillPath(line_path, color)

            if self.has_text:
                self.drawText(painter)

    def paintEvent(self, event):
        if not self.pixmap:
            return super(Canvas, self).paintEvent(event)

        p = self._painter
        p.begin(self)
        p.setRenderHint(QPainter.Antialiasing)
        p.setRenderHint(QPainter.HighQualityAntialiasing)
        p.setRenderHint(QPainter.SmoothPixmapTransform)

        p.scale(self.scale, self.scale)
        p.translate(self.offsetToCenter())

        p.drawPixmap(0, 0, self.pixmap)
        Shape.scale = self.scale
        for shape in self.shapes:
            if (shape.selected or not self._hideBackround) and self.isVisible(shape):
                shape.fill = shape.selected or shape == self.hShape
                shape.paint(p)
        # if self.main_window.gates is not None:
        #     self.paintGates(p, self.main_window.gates)
        if self.current:
            self.current.paint(p)
            self.line.paint(p)
        if self.selectedShapeCopy:
            self.selectedShapeCopy.paint(p)

        # Paint rect
        if self.current is not None and len(self.line) == 2:
            leftTop = self.line[0]
            rightBottom = self.line[1]

            color = QColor(0, 220, 0)
            p.setPen(color)
            brush = QBrush(Qt.BDiagPattern)
            p.setBrush(brush)

            if self.is_gate:
                p.drawLine(leftTop.x(), leftTop.y(), rightBottom.x(), rightBottom.y())
            else:
                rectWidth = rightBottom.x() - leftTop.x()
                rectHeight = rightBottom.y() - leftTop.y()
                p.drawRect(leftTop.x(), leftTop.y(), rectWidth, rectHeight)

        if self.drawing() and not self.prevPoint.isNull() \
                and not self.outOfPixmap(self.prevPoint):
            p.setPen(QColor(0, 0, 0))
            p.drawLine(self.prevPoint.x(), 0, self.prevPoint.x(), self.pixmap.height())
            p.drawLine(0, self.prevPoint.y(), self.pixmap.width(), self.prevPoint.y())

        self.setAutoFillBackground(True)
        if self.verified:
            pal = self.palette()
            pal.setColor(self.backgroundRole(), QColor(184, 239, 38, 128))
            self.setPalette(pal)
        else:
            pal = self.palette()
            pal.setColor(self.backgroundRole(), QColor(232, 232, 232, 255))
            self.setPalette(pal)

        p.end()

    def transformPos(self, point):
        """Convert from widget-logical coordinates to painter-logical coordinates."""
        return point / self.scale - self.offsetToCenter()

    def offsetToCenter(self):
        s = self.scale
        area = super(Canvas, self).size()
        w, h = self.pixmap.width() * s, self.pixmap.height() * s
        aw, ah = area.width(), area.height()
        x = (aw - w) / (2 * s) if aw > w else 0
        y = (ah - h) / (2 * s) if ah > h else 0
        return QPointF(x, y)

    def outOfPixmap(self, p):
        w, h = self.pixmap.width(), self.pixmap.height()
        return not (0 <= p.x() <= w and 0 <= p.y() <= h)

    def finalise(self):
        assert self.current
        if self.current.points[0] == self.current.points[-1]:
            self.current = None
            self.drawingPolygon.emit(False)
            self.update()
            return

        self.current.close()
        self.shapes.append(self.current)
        self.current = None
        self.setHiding(False)
        self.newShape.emit()
        self.update()

    def closeEnough(self, p1, p2):
        # d = distance(p1 - p2)
        # m = (p1-p2).manhattanLength()
        # print "d %.2f, m %d, %.2f" % (d, m, d - m)
        return distance(p1 - p2) < self.epsilon

    def intersectionPoint(self, p1, p2):
        # Cycle through each image edge in clockwise fashion,
        # and find the one intersecting the current line segment.
        # http://paulbourke.net/geometry/lineline2d/
        size = self.pixmap.size()
        points = [(0, 0),
                  (size.width(), 0),
                  (size.width(), size.height()),
                  (0, size.height())]
        x1, y1 = p1.x(), p1.y()
        x2, y2 = p2.x(), p2.y()
        d, i, (x, y) = min(self.intersectingEdges((x1, y1), (x2, y2), points))
        x3, y3 = points[i]
        x4, y4 = points[(i + 1) % 4]
        if (x, y) == (x1, y1):
            # Handle cases where previous point is on one of the edges.
            if x3 == x4:
                return QPointF(x3, min(max(0, y2), max(y3, y4)))
            else:  # y3 == y4
                return QPointF(min(max(0, x2), max(x3, x4)), y3)
        return QPointF(x, y)

    def intersectingEdges(self, x1y1, x2y2, points):
        """For each edge formed by `points', yield the intersection
        with the line segment `(x1,y1) - (x2,y2)`, if it exists.
        Also return the distance of `(x2,y2)' to the middle of the
        edge along with its index, so that the one closest can be chosen."""
        x1, y1 = x1y1
        x2, y2 = x2y2
        for i in range(4):
            x3, y3 = points[i]
            x4, y4 = points[(i + 1) % 4]
            denom = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1)
            nua = (x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)
            nub = (x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)
            if denom == 0:
                # This covers two cases:
                #   nua == nub == 0: Coincident
                #   otherwise: Parallel
                continue
            ua, ub = nua / denom, nub / denom
            if 0 <= ua <= 1 and 0 <= ub <= 1:
                x = x1 + ua * (x2 - x1)
                y = y1 + ua * (y2 - y1)
                m = QPointF((x3 + x4) / 2, (y3 + y4) / 2)
                d = distance(m - QPointF(x2, y2))
                yield d, i, (x, y)

    # These two, along with a call to adjustSize are required for the
    # scroll area.
    def sizeHint(self):
        return self.minimumSizeHint()

    def minimumSizeHint(self):
        if self.pixmap:
            return self.scale * self.pixmap.size()
        return super(Canvas, self).minimumSizeHint()

    def wheelEvent(self, ev):
        qt_version = 4 if hasattr(ev, "delta") else 5
        if qt_version == 4:
            if ev.orientation() == Qt.Vertical:
                v_delta = ev.delta()
                h_delta = 0
            else:
                h_delta = ev.delta()
                v_delta = 0
        else:
            delta = ev.angleDelta()
            h_delta = delta.x()
            v_delta = delta.y()

        mods = ev.modifiers()
        if Qt.ControlModifier == int(mods) and v_delta:
            self.zoomRequest.emit(v_delta)
        else:
            v_delta and self.scrollRequest.emit(v_delta, Qt.Vertical)
            h_delta and self.scrollRequest.emit(h_delta, Qt.Horizontal)
        ev.accept()

    def keyPressEvent(self, ev):
        key = ev.key()
        if key == Qt.Key_Escape and self.current:
            print('ESC press')
            self.current = None
            self.drawingPolygon.emit(False)
            self.update()
        elif key == Qt.Key_Return and self.canCloseShape():
            self.finalise()
        elif key == Qt.Key_Left and self.selectedShape:
            self.moveOnePixel('Left')
        elif key == Qt.Key_Right and self.selectedShape:
            self.moveOnePixel('Right')
        elif key == Qt.Key_Up and self.selectedShape:
            self.moveOnePixel('Up')
        elif key == Qt.Key_Down and self.selectedShape:
            self.moveOnePixel('Down')

    def moveOnePixel(self, direction):
        # print(self.selectedShape.points)
        if direction == 'Left' and not self.moveOutOfBound(QPointF(-1.0, 0)):
            # print("move Left one pixel")
            self.selectedShape.points[0] += QPointF(-1.0, 0)
            self.selectedShape.points[1] += QPointF(-1.0, 0)
            if not self.selectedShape.is_gate:
                self.selectedShape.points[2] += QPointF(-1.0, 0)
                self.selectedShape.points[3] += QPointF(-1.0, 0)
        elif direction == 'Right' and not self.moveOutOfBound(QPointF(1.0, 0)):
            # print("move Right one pixel")
            self.selectedShape.points[0] += QPointF(1.0, 0)
            self.selectedShape.points[1] += QPointF(1.0, 0)
            if not self.selectedShape.is_gate:
                self.selectedShape.points[2] += QPointF(1.0, 0)
                self.selectedShape.points[3] += QPointF(1.0, 0)
        elif direction == 'Up' and not self.moveOutOfBound(QPointF(0, -1.0)):
            # print("move Up one pixel")
            self.selectedShape.points[0] += QPointF(0, -1.0)
            self.selectedShape.points[1] += QPointF(0, -1.0)
            if not self.selectedShape.is_gate:
                self.selectedShape.points[2] += QPointF(0, -1.0)
                self.selectedShape.points[3] += QPointF(0, -1.0)
        elif direction == 'Down' and not self.moveOutOfBound(QPointF(0, 1.0)):
            # print("move Down one pixel")
            self.selectedShape.points[0] += QPointF(0, 1.0)
            self.selectedShape.points[1] += QPointF(0, 1.0)
            if not self.selectedShape.is_gate:
                self.selectedShape.points[2] += QPointF(0, 1.0)
                self.selectedShape.points[3] += QPointF(0, 1.0)
        self.shapeMoved.emit()
        self.repaint()

    def moveOutOfBound(self, step):
        points = [p1 + p2 for p1, p2 in zip(self.selectedShape.points, [step] * 4)]
        return True in map(self.outOfPixmap, points)

    def setLastLabel(self, text):
        assert text
        self.shapes[-1].label = text
        return self.shapes[-1]

    def undoLastLine(self):
        assert self.shapes
        self.current = self.shapes.pop()
        self.current.setOpen()
        self.line.points = [self.current[-1], self.current[0]]
        self.drawingPolygon.emit(True)

    def resetAllLines(self):
        assert self.shapes
        self.current = self.shapes.pop()
        self.current.setOpen()
        self.line.points = [self.current[-1], self.current[0]]
        self.drawingPolygon.emit(True)
        self.current = None
        self.drawingPolygon.emit(False)
        self.update()

    def loadPixmap(self, pixmap):
        self.pixmap = pixmap
        self.shapes = []
        self.repaint()

    def loadShapes(self, shapes):
        self.shapes = list(shapes)
        self.set_show_text()
        self.current = None
        self.repaint()

    def setShapeVisible(self, shape, value):
        self.visible[shape] = value
        self.repaint()

    def currentCursor(self):
        cursor = QApplication.overrideCursor()
        if cursor is not None:
            cursor = cursor.shape()
        return cursor

    def overrideCursor(self, cursor):
        self._cursor = cursor
        if self.currentCursor() is None:
            QApplication.setOverrideCursor(cursor)
        else:
            QApplication.changeOverrideCursor(cursor)

    def restoreCursor(self):
        QApplication.restoreOverrideCursor()

    def resetState(self):
        self.restoreCursor()
        self.pixmap = None
        self.update()

    def add_shape(self, bbox, label, bbox_source, id_number):
        is_gate = label == "gate"
        shape = Shape(label, bbox_source=bbox_source, id_number=id_number, is_gate=is_gate)
        xmin, ymin, xmax, ymax = bbox
        if is_gate:
            shape.addPoint(QPointF(xmin, ymin))
            shape.addPoint(QPointF(xmax, ymax))
        else:
            shape.addPoint(QPointF(xmin, ymin))
            shape.addPoint(QPointF(xmax, ymin))
            shape.addPoint(QPointF(xmax, ymax))
            shape.addPoint(QPointF(xmin, ymax))
        shape.close()
        self.main_window.addLabel(shape)
        self.shapes.append(shape)

    def set_show_text(self, _status=None):
        if _status is None:
            _status = self.has_text
        self.has_text = _status
        for shape in self.shapes:
            shape.has_text = _status
コード例 #10
0
ファイル: canvas.py プロジェクト: walldas/labelImg_last_one
class Canvas(QWidget):
    zoomRequest = pyqtSignal(int)
    scrollRequest = pyqtSignal(int, int)
    newShape = pyqtSignal()
    selectionChanged = pyqtSignal(bool)
    shapeMoved = pyqtSignal()
    drawingPolygon = pyqtSignal(bool)

    CREATE, EDIT = list(range(2))

    epsilon = 11.0

    def __init__(self, *args, **kwargs):
        super(Canvas, self).__init__(*args, **kwargs)
        # Initialise local state.
        self.mode = self.EDIT
        self.shapes = []
        self.current = None
        self.selectedShape = None  # save the selected shape here
        self.selectedShapeCopy = None
        self.lineColor = QColor(0, 0, 255)
        self.line = Shape(line_color=self.lineColor)
        self.line2 = []
        self.prevPoint = QPointF()
        self.offsets = QPointF(), QPointF()
        self.scale = 1.0
        self.pixmap = QPixmap()
        self.visible = {}
        self._hideBackround = False
        self.hideBackround = False
        self.hShape = None
        self.tetragonMode = False
        self.hVertex = None
        self.rotationPoint = None
        self._painter = QPainter()
        self._cursor = CURSOR_DEFAULT
        self.draw3DMode = False
        self.clicksCount = 0
        self.sk = 0

        # Menus:
        self.menus = (QMenu(), QMenu())
        # Set widget options.
        self.setMouseTracking(True)
        self.setFocusPolicy(Qt.WheelFocus)
        self.verified = False

    def enterEvent(self, ev):
        self.overrideCursor(self._cursor)

    def leaveEvent(self, ev):
        self.restoreCursor()

    def focusOutEvent(self, ev):
        self.restoreCursor()

    def isVisible(self, shape):
        return self.visible.get(shape, True)

    def drawing(self):
        return self.mode == self.CREATE

    def editing(self):
        return self.mode == self.EDIT

    def setEditing(self, value=True):
        self.mode = self.EDIT if value else self.CREATE
        if not value:  # Create
            self.unHighlight()
            self.deSelectShape()

    def unHighlight(self):
        if self.hShape:
            self.hShape.highlightClear()
        self.hVertex = self.hShape = None

    def deSelectShape(self):
        if self.selectedShape:
            self.lastSelected = self.selectedShape
            self.selectedShape.selected = False
            self.selectedShape = None
            self.setHiding(False)
            self.selectionChanged.emit(False)
            self.update()

    def selectedVertex(self):
        return self.hVertex is not None

    def selectedRotationPoint(self):
        return self.rotationPoint is not None

    def pointsCountingFor3DShape(self, pos):
        pos = QPointF(round(pos.x()), round(pos.y()))
        self.clicksCount += 1
        if self.clicksCount == 4:
            self.draw3DMode = False
            self.finalise()

    def addVirtualPoint(self, pointA, pointB, pos):
        deltaX = pointA.x() - pointB.x()
        deltaY = pointA.y() - pointB.y()
        delta = QPointF(deltaX, deltaY)
        self.current.addPoint(pos - delta)

    def mouseMoveEvent(self, ev):
        """Update line with last point and current coordinates."""
        pos = self.transformPos(ev.pos())
        self.restoreCursor()

        #  3D drawing while drawing
        if self.draw3DMode:
            self.overrideCursor(CURSOR_DRAW)
            pos = QPointF(round(pos.x()), round(pos.y()))
            if self.clicksCount == 0:
                if len(self.current.points) == 0:
                    self.current.addPoint(pos)
                elif len(self.current.points) == 1:
                    self.current.points[0] = pos
            if self.clicksCount == 1:
                if len(self.current.points) == 1:
                    self.current.addPoint(pos)
                elif len(self.current.points) == 2:
                    self.current.points[1] = pos

            if self.clicksCount == 2:
                if len(self.current.points) == 2:
                    self.current.addPoint(pos)
                    self.addVirtualPoint(self.current[1], self.current[0], pos)
                elif len(self.current.points) == 4:
                    del self.current.points[3]
                    del self.current.points[2]
                    self.current.addPoint(pos)
                    self.addVirtualPoint(self.current[1], self.current[0], pos)
            if self.clicksCount == 3:
                if len(self.current.points) == 4:
                    self.addVirtualPoint(self.current[1], self.current[0], pos)
                    self.current.addPoint(pos)
                    self.addVirtualPoint(self.current[2], self.current[1], pos)
                    self.addVirtualPoint(self.current[2], self.current[0], pos)
                elif len(self.current.points) == 8:
                    del self.current.points[7]
                    del self.current.points[6]
                    del self.current.points[5]
                    del self.current.points[4]
                    self.addVirtualPoint(self.current[1], self.current[0], pos)
                    self.current.addPoint(pos)
                    self.addVirtualPoint(self.current[2], self.current[1], pos)
                    self.addVirtualPoint(self.current[2], self.current[0], pos)

            self.repaint()
            return

        # Polygon drawing.
        if self.drawing():
            self.overrideCursor(CURSOR_DRAW)
            if self.current:
                color = self.lineColor
                if self.outOfPixmap(pos):
                    # Don't allow the user to draw outside the pixmap.
                    # Project the point to the pixmap's edges.
                    pos = self.intersectionPoint(self.current[-1], pos)
                elif len(self.current) > 1 and self.closeEnough(
                        pos, self.current[0]):
                    # Attract line to starting point and colorise to alert the
                    # user:
                    pos = self.current[0]
                    color = self.current.line_color
                    self.overrideCursor(CURSOR_POINT)
                    self.current.highlightVertex(0, Shape.NEAR_VERTEX)

                self.line[1] = pos
                self.line.line_color = color
                self.repaint()
                self.current.highlightClear()

            return

        # Polygon copy moving.
        if Qt.RightButton & ev.buttons():
            if self.selectedShapeCopy and self.prevPoint:
                self.overrideCursor(CURSOR_MOVE)
                self.boundedMoveShape(self.selectedShapeCopy, pos)
                self.repaint()
            elif self.selectedShape:
                self.selectedShapeCopy = self.selectedShape.copy()
                self.repaint()
            return

        # Polygon/Vertex moving.
        if Qt.LeftButton & ev.buttons():
            if self.selectedVertex():
                self.boundedMoveVertex(pos)
                self.shapeMoved.emit()
                self.repaint()
            elif self.selectedShape and self.prevPoint:
                self.overrideCursor(CURSOR_MOVE)
                self.boundedMoveShape(self.selectedShape, pos)
                self.shapeMoved.emit()
                self.repaint()
            elif self.selectedRotationPoint:
                self.overrideCursor(CURSOR_MOVE)
                try:
                    self.rotateShape(self.hShape, pos)
                except:
                    pass
                self.shapeMoved.emit()
                self.repaint()
            return

        # Just hovering over the canvas, 2 posibilities:
        # - Highlight shapes
        # - Highlight vertex
        # Update shape/vertex fill and tooltip value accordingly.
        # self.setToolTip("Image")
        for shape in reversed([s for s in self.shapes if self.isVisible(s)]):
            # Look for a nearby vertex to highlight. If that fails,
            # check if we happen to be inside a shape.
            # print(shape.points)
            index = shape.nearestVertex(pos, self.epsilon)
            # print(overTheDot)
            if index is not None:
                # print(index)
                if self.selectedVertex():
                    self.hShape.highlightClear()
                self.hVertex, self.hShape = index, shape
                shape.highlightVertex(index, shape.MOVE_VERTEX)
                self.overrideCursor(CURSOR_POINT)
                # self.setToolTip("Click & drag to move point")
                # self.setStatusTip(self.toolTip())
                self.update()
                break
            elif shape.overRotationPoint(pos, self.epsilon):
                self.hShape = self.selectedShape
                self.overrideCursor(CURSOR_POINT)
                # self.setToolTip("Click & drag to rotate label")
                # self.setStatusTip(self.toolTip())
                self.update()
                break
            elif shape.containsPoint(pos):
                # print(shape.points)
                if self.selectedVertex():
                    self.hShape.highlightClear()
                self.hVertex, self.hShape = None, shape
                # self.setToolTip("Click & drag to move shape '%s'" % shape.label)
                # self.setStatusTip(self.toolTip())
                self.overrideCursor(CURSOR_GRAB)
                self.update()
                break
        else:  # Nothing found, clear highlights, reset state.

            if self.hShape:
                self.hShape.highlightClear()
                self.update()
            self.hVertex, self.hShape = None, None

    def getRotatedShape(self, shape, angle):
        return [
            Shape.rotatePoint(self, shape.centerPoint, point, angle)
            for point in shape.points
        ]

    def rotateShape(self, shape, pos):
        zeroPoint = shape.centerPoint + QPointF(0, -10)
        deg = self.angleBetween3Points(zeroPoint, shape.centerPoint, pos)
        delta = -(shape.deg - deg)
        if delta != 0:
            rotatedShape = self.getRotatedShape(shape, delta)
            if self.shapeOutOfPixmap(rotatedShape) == False:
                shape.points = rotatedShape
                shape.deg += delta

    def angleBetween3Points(self, p0, c, p1):
        p0c = math.sqrt(
            math.pow(c.x() - p0.x(), 2) +
            math.pow(c.y() - p0.y(), 2))  # p0->c (b)
        p1c = math.sqrt(
            math.pow(c.x() - p1.x(), 2) +
            math.pow(c.y() - p1.y(), 2))  # p1->c (a)
        p0p1 = math.sqrt(
            math.pow(p1.x() - p0.x(), 2) +
            math.pow(p1.y() - p0.y(), 2))  # p0->p1 (c)
        ratio = math.acos(
            (p1c * p1c + p0c * p0c - p0p1 * p0p1) / (2 * p1c * p0c))
        deg = round(((ratio * 180) / math.pi))
        if c.x() < p1.x():
            deg = 360 - deg
            if deg == 360:
                return 0
        return deg

    def mousePressEvent(self, ev):
        pos = self.transformPos(ev.pos())

        if ev.button() == Qt.LeftButton:
            if self.drawing():
                self.handleDrawing(pos)
            elif self.draw3DMode:
                self.pointsCountingFor3DShape(pos)
                self.repaint()
            else:
                self.selectShapePoint(pos)
                self.prevPoint = pos
                self.repaint()
        elif ev.button() == Qt.RightButton and self.editing():
            self.selectShapePoint(pos)
            self.prevPoint = pos
            self.repaint()

    def mouseReleaseEvent(self, ev):
        if ev.button() == Qt.RightButton:
            menu = self.menus[bool(self.selectedShapeCopy)]
            self.restoreCursor()
            if not menu.exec_(self.mapToGlobal(ev.pos()))\
               and self.selectedShapeCopy:
                # Cancel the move by deleting the shadow copy.
                self.selectedShapeCopy = None
                self.repaint()
        elif self.draw3DMode:
            self.overrideCursor(CURSOR_DRAW)
        elif ev.button() == Qt.LeftButton and self.selectedShape:
            self.overrideCursor(CURSOR_GRAB)
        elif ev.button() == Qt.LeftButton:
            pos = self.transformPos(ev.pos())
            if self.drawing():
                self.handleDrawing(pos)

    def endMove(self, copy=False):
        assert self.selectedShape and self.selectedShapeCopy
        shape = self.selectedShapeCopy
        #del shape.fill_color
        #del shape.line_color
        if copy:
            self.shapes.append(shape)
            self.selectedShape.selected = False
            self.selectedShape = shape
            self.repaint()
        else:
            self.selectedShape.points = [p for p in shape.points]
        self.selectedShapeCopy = None

    def hideBackroundShapes(self, value):
        self.hideBackround = value
        if self.selectedShape:
            # Only hide other shapes if there is a current selection.
            # Otherwise the user will not be able to select a shape.
            self.setHiding(True)
            self.repaint()

    def handleDrawing(self, pos):
        if self.current and self.current.reachMaxPoints() is False:
            initPos = self.current[0]
            minX = initPos.x()
            minY = initPos.y()
            targetPos = self.line[1]
            maxX = targetPos.x()
            maxY = targetPos.y()
            self.current.addPoint(QPointF(maxX, minY))
            self.current.addPoint(targetPos)
            self.current.addPoint(QPointF(minX, maxY))
            self.current.addPoint(initPos)
            self.line[0] = self.current[-1]
            if self.current.isClosed():
                self.finalise()
        elif not self.outOfPixmap(pos):
            self.current = Shape()
            self.current.addPoint(pos)
            if self.tetragonMode == True:
                self.current.tetragon = True
            self.line.points = [pos, pos]
            self.setHiding()
            self.drawingPolygon.emit(True)
            self.update()

    def setHiding(self, enable=True):
        self._hideBackround = self.hideBackround if enable else False

    def canCloseShape(self):
        return self.drawing() and self.current and len(self.current) > 2

    def mouseDoubleClickEvent(self, ev):
        # We need at least 4 points here, since the mousePress handler
        # adds an extra one before this handler is called.
        if self.canCloseShape() and len(self.current) > 3:
            self.current.popPoint()
            self.finalise()

    def selectShape(self, shape):
        self.deSelectShape()
        shape.selected = True
        self.selectedShape = shape
        self.setHiding()
        self.selectionChanged.emit(True)
        self.update()

    def transformTetragon(self, points):
        arrX = []
        arrY = []
        for x in points:
            arrX.append(x.x())
            arrY.append(x.y())
        return [min(arrX), min(arrY), max(arrX), max(arrY)]

    def selectShapePoint(self, point):
        """Select the first shape created which contains this point."""
        self.deSelectShape()
        if self.selectedVertex():  # A vertex is marked for selection.
            index, shape = self.hVertex, self.hShape
            shape.highlightVertex(index, shape.MOVE_VERTEX)
            return
        for shape in reversed(self.shapes):
            if self.isVisible(shape) and shape.containsPoint(point):
                shape.selected = True
                self.selectedShape = shape
                self.calculateOffsets(shape, point)
                self.setHiding()
                self.selectionChanged.emit(True)
                return

    def calculateOffsets(self, shape, point):
        rect = shape.boundingRect()
        x1 = rect.x() - point.x()
        y1 = rect.y() - point.y()
        x2 = (rect.x() + rect.width()) - point.x()
        y2 = (rect.y() + rect.height()) - point.y()
        self.offsets = QPointF(x1, y1), QPointF(x2, y2)

    def boundedMoveVertex(self, pos):
        index, shape = self.hVertex, self.hShape
        if self.shapeOutOfPixmap(shape.points):
            #this is pre fix. For bug when shape reach Picmap edge.
            return
        if shape.shape3D:
            # 3D editing OFF
            return
        rotatedAxis = False
        if shape.deg > 0 and not shape.tetragon:
            rotatedAxis = True
            shape.points = self.getRotatedShape(shape, -shape.deg)
            pos = Shape.rotatePoint(self, shape.centerPoint, pos, -shape.deg)
        point = shape[index]
        if self.outOfPixmap(pos):
            if self.shapeOutOfPixmap(shape.points) == False:
                pos = self.intersectionPoint(point, pos)
        shiftPos = pos - point
        shape.moveVertexBy(index, shiftPos)
        lindex = (index + 1) % 4
        rindex = (index + 3) % 4
        lshift = None
        rshift = None
        if index % 2 == 0:
            #lyginiai index
            if shape.tetragon == True:
                rshift = QPointF(0, 0)
                lshift = QPointF(0, 0)
            elif shape.tetragon == False:
                rshift = QPointF(shiftPos.x(), 0)
                lshift = QPointF(0, shiftPos.y())
        else:
            #nelyginiai index
            if shape.tetragon == True:
                lshift = QPointF(0, 0)
                rshift = QPointF(0, 0)
            elif shape.tetragon == False:
                lshift = QPointF(shiftPos.x(), 0)
                rshift = QPointF(0, shiftPos.y())
        shape.moveVertexBy(rindex, rshift)
        shape.moveVertexBy(lindex, lshift)
        if rotatedAxis:
            shape.points = self.getRotatedShape(shape, shape.deg)
            pos = Shape.rotatePoint(self, shape.centerPoint, pos, shape.deg)

    def boundedMoveShape(self, shape, pos):
        if self.outOfPixmap(pos):
            return False  # No need to move
        o1 = pos + self.offsets[0]
        if self.outOfPixmap(o1):
            pos -= QPointF(min(0, o1.x()), min(0, o1.y()))
        o2 = pos + self.offsets[1]
        if self.outOfPixmap(o2):
            pos += QPointF(min(0,
                               self.pixmap.width() - o2.x()),
                           min(0,
                               self.pixmap.height() - o2.y()))
        # The next line tracks the new position of the cursor
        # relative to the shape, but also results in making it
        # a bit "shaky" when nearing the border and allows it to
        # go outside of the shape's area for some reason. XXX
        #self.calculateOffsets(self.selectedShape, pos)
        dp = pos - self.prevPoint
        if dp:
            shape.moveBy(dp)
            self.prevPoint = pos
            return True
        return False

    def deleteSelected(self):
        if self.selectedShape:
            shape = self.selectedShape
            self.shapes.remove(self.selectedShape)
            self.selectedShape = None
            self.update()
            return shape

    def copySelectedShape(self):
        if self.selectedShape:
            shape = self.selectedShape.copy()
            self.deSelectShape()
            self.shapes.append(shape)
            shape.selected = True
            self.selectedShape = shape
            self.boundedShiftShape(shape)
            return shape

    def boundedShiftShape(self, shape):
        # Try to move in one direction, and if it fails in another.
        # Give up if both fail.
        point = shape[0]
        offset = QPointF(2.0, 2.0)
        self.calculateOffsets(shape, point)
        self.prevPoint = point
        if not self.boundedMoveShape(shape, point - offset):
            self.boundedMoveShape(shape, point + offset)

    def paintEvent(self, event):
        if not self.pixmap:
            return super(Canvas, self).paintEvent(event)

        p = self._painter
        p.begin(self)
        p.setRenderHint(QPainter.Antialiasing)
        p.setRenderHint(QPainter.HighQualityAntialiasing)
        p.setRenderHint(QPainter.SmoothPixmapTransform)

        p.scale(self.scale, self.scale)
        p.translate(self.offsetToCenter())

        p.drawPixmap(0, 0, self.pixmap)
        Shape.scale = self.scale
        for shape in self.shapes:
            if (shape.selected
                    or not self._hideBackround) and self.isVisible(shape):
                shape.fill = shape.selected or shape == self.hShape
                shape.paint(p)
        if self.current:
            self.current.paint(p)
            self.line.paint(p)

        if self.selectedShapeCopy:
            self.selectedShapeCopy.paint(p)

        # Paint rect
        if self.current is not None and len(
                self.line) == 2 and self.draw3DMode == False:
            leftTop = self.line[0]
            rightBottom = self.line[1]
            rectWidth = rightBottom.x() - leftTop.x()
            rectHeight = rightBottom.y() - leftTop.y()
            color = QColor(0, 220, 0)
            p.setPen(color)
            brush = QBrush(Qt.BDiagPattern)
            p.setBrush(brush)
            p.drawRect(leftTop.x(), leftTop.y(), rectWidth, rectHeight)

        self.setAutoFillBackground(True)
        if self.verified:
            pal = self.palette()
            pal.setColor(self.backgroundRole(), QColor(184, 239, 38, 128))
            self.setPalette(pal)
        else:
            pal = self.palette()
            pal.setColor(self.backgroundRole(), QColor(232, 232, 232, 255))
            self.setPalette(pal)

        p.end()

    def transformPos(self, point):
        """Convert from widget-logical coordinates to painter-logical coordinates."""
        return point / self.scale - self.offsetToCenter()

    def offsetToCenter(self):
        s = self.scale
        area = super(Canvas, self).size()
        w, h = self.pixmap.width() * s, self.pixmap.height() * s
        aw, ah = area.width(), area.height()
        x = (aw - w) / (2 * s) if aw > w else 0
        y = (ah - h) / (2 * s) if ah > h else 0
        return QPointF(x, y)

    def outOfPixmap(self, p):
        w, h = self.pixmap.width(), self.pixmap.height()
        return not (0 <= p.x() <= w and 0 <= p.y() <= h)

    def shapeOutOfPixmap(self, points):
        for p in points:
            if self.outOfPixmap(p):
                return True
        return False

    def finalise(self):
        assert self.current
        self.current.close()
        self.shapes.append(self.current)
        self.current = None
        self.setHiding(False)
        self.newShape.emit()
        self.update()

    def closeEnough(self, p1, p2):
        #d = distance(p1 - p2)
        #m = (p1-p2).manhattanLength()
        # print "d %.2f, m %d, %.2f" % (d, m, d - m)
        return distance(p1 - p2) < self.epsilon

    def intersectionPoint(self, p1, p2):
        # Cycle through each image edge in clockwise fashion,
        # and find the one intersecting the current line segment.
        # http://paulbourke.net/geometry/lineline2d/
        size = self.pixmap.size()
        points = [(0, 0), (size.width(), 0), (size.width(), size.height()),
                  (0, size.height())]
        x1, y1 = p1.x(), p1.y()
        x2, y2 = p2.x(), p2.y()
        d, i, (x, y) = min(self.intersectingEdges((x1, y1), (x2, y2), points))
        x3, y3 = points[i]
        x4, y4 = points[(i + 1) % 4]
        if (x, y) == (x1, y1):
            # Handle cases where previous point is on one of the edges.
            if x3 == x4:
                return QPointF(x3, min(max(0, y2), max(y3, y4)))
            else:  # y3 == y4
                return QPointF(min(max(0, x2), max(x3, x4)), y3)
        return QPointF(x, y)

    def intersectingEdges(self, x1y1, x2y2, points):
        """For each edge formed by `points', yield the intersection
        with the line segment `(x1,y1) - (x2,y2)`, if it exists.
        Also return the distance of `(x2,y2)' to the middle of the
        edge along with its index, so that the one closest can be chosen."""
        x1, y1 = x1y1
        x2, y2 = x2y2
        for i in range(4):
            x3, y3 = points[i]
            x4, y4 = points[(i + 1) % 4]
            denom = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1)
            nua = (x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)
            nub = (x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)

            if denom == 0:
                # This covers two cases:
                #   nua == nub == 0: Coincident
                #   otherwise: Parallel
                continue
            ua, ub = nua / denom, nub / denom
            if 0 <= ua <= 1 and 0 <= ub <= 1:
                x = x1 + ua * (x2 - x1)
                y = y1 + ua * (y2 - y1)
                m = QPointF((x3 + x4) / 2, (y3 + y4) / 2)
                d = distance(m - QPointF(x2, y2))
                yield d, i, (x, y)

    # These two, along with a call to adjustSize are required for the
    # scroll area.
    def sizeHint(self):
        return self.minimumSizeHint()

    def minimumSizeHint(self):
        if self.pixmap:
            return self.scale * self.pixmap.size()
        return super(Canvas, self).minimumSizeHint()

    def wheelEvent(self, ev):
        qt_version = 4 if hasattr(ev, "delta") else 5
        if qt_version == 4:
            if ev.orientation() == Qt.Vertical:
                v_delta = ev.delta()
                h_delta = 0
                print(0)
            else:
                h_delta = ev.delta()
                v_delta = 0
                print(1)
        else:
            delta = ev.angleDelta()
            h_delta = delta.x()
            v_delta = delta.y()
            print(2)

        mods = ev.modifiers()

        if Qt.ControlModifier == int(mods) and v_delta:
            self.zoomRequest.emit(v_delta)
            print(3)
            # print(Qt.ControlModifier)
        else:
            print(4)
            v_delta and self.scrollRequest.emit(v_delta, Qt.Vertical)
            h_delta and self.scrollRequest.emit(h_delta, Qt.Horizontal)
        # print(v_delta)
        # print(h_delta)
        ev.accept()

    def keyPressEvent(self, ev):
        key = ev.key()
        if key == Qt.Key_Escape and self.current:
            print('ESC press')
            self.current = None
            self.drawingPolygon.emit(False)
            self.update()
        elif key == Qt.Key_Return and self.canCloseShape():
            self.finalise()
        elif key == Qt.Key_Left and self.selectedShape:
            self.moveOnePixel('Left')
        elif key == Qt.Key_Right and self.selectedShape:
            self.moveOnePixel('Right')
        elif key == Qt.Key_Up and self.selectedShape:
            self.moveOnePixel('Up')
        elif key == Qt.Key_Down and self.selectedShape:
            self.moveOnePixel('Down')

    def moveOnePixel(self, direction):
        # print(self.selectedShape.points)
        if direction == 'Left' and not self.moveOutOfBound(QPointF(-1.0, 0)):
            # print("move Left one pixel")
            self.selectedShape.points[0] += QPointF(-1.0, 0)
            self.selectedShape.points[1] += QPointF(-1.0, 0)
            self.selectedShape.points[2] += QPointF(-1.0, 0)
            self.selectedShape.points[3] += QPointF(-1.0, 0)
        elif direction == 'Right' and not self.moveOutOfBound(QPointF(1.0, 0)):
            # print("move Right one pixel")
            self.selectedShape.points[0] += QPointF(1.0, 0)
            self.selectedShape.points[1] += QPointF(1.0, 0)
            self.selectedShape.points[2] += QPointF(1.0, 0)
            self.selectedShape.points[3] += QPointF(1.0, 0)
        elif direction == 'Up' and not self.moveOutOfBound(QPointF(0, -1.0)):
            # print("move Up one pixel")
            self.selectedShape.points[0] += QPointF(0, -1.0)
            self.selectedShape.points[1] += QPointF(0, -1.0)
            self.selectedShape.points[2] += QPointF(0, -1.0)
            self.selectedShape.points[3] += QPointF(0, -1.0)
        elif direction == 'Down' and not self.moveOutOfBound(QPointF(0, 1.0)):
            # print("move Down one pixel")
            self.selectedShape.points[0] += QPointF(0, 1.0)
            self.selectedShape.points[1] += QPointF(0, 1.0)
            self.selectedShape.points[2] += QPointF(0, 1.0)
            self.selectedShape.points[3] += QPointF(0, 1.0)
        self.shapeMoved.emit()
        self.repaint()

    def moveOutOfBound(self, step):
        points = [
            p1 + p2 for p1, p2 in zip(self.selectedShape.points, [step] * 4)
        ]
        return True in map(self.outOfPixmap, points)

    def setLastLabel(self, text):
        assert text
        self.shapes[-1].label = text
        return self.shapes[-1]

    def undoLastLine(self):
        assert self.shapes
        self.current = self.shapes.pop()
        self.current.setOpen()
        self.line.points = [self.current[-1], self.current[0]]
        self.drawingPolygon.emit(True)

    def resetAllLines(self):
        assert self.shapes
        self.current = self.shapes.pop()
        self.current.setOpen()
        self.line.points = [self.current[-1], self.current[0]]
        self.drawingPolygon.emit(True)
        self.current = None
        self.drawingPolygon.emit(False)
        self.update()

    def loadPixmap(self, pixmap):
        self.pixmap = pixmap
        self.shapes = []
        self.repaint()

    def loadShapes(self, shapes):
        self.shapes = list(shapes)
        self.current = None
        self.repaint()

    def setShapeVisible(self, shape, value):
        self.visible[shape] = value
        self.repaint()

    def overrideCursor(self, cursor):
        self.restoreCursor()
        self._cursor = cursor
        QApplication.setOverrideCursor(cursor)

    def restoreCursor(self):
        QApplication.restoreOverrideCursor()

    def resetState(self):
        self.restoreCursor()
        self.pixmap = None
        self.update()
コード例 #11
0
class Canvas(QWidget):
    # 自定义的信号
    zoomRequest = pyqtSignal(int)               # 缩放图像释放的信号
    scrollRequest = pyqtSignal(int, int)        # 在放大的图像上滚动滚轮以显示不同位置释放的信号
    newShape = pyqtSignal()                     # 创建新bbox的信号
    selectionChanged = pyqtSignal(bool)         # 改变bbox的边界
    shapeMoved = pyqtSignal()                   # 改变bbox的位置
    drawingPolygon = pyqtSignal(bool)           # 鼠标拖动绘制信号

    CREATE, EDIT = list(range(2))       # 两种模式,CREATE模式为0,EDIT模式为1

    epsilon = 11.0                      # TODO 这是啥

    def __init__(self, *args, **kwargs):
        super(Canvas, self).__init__(*args, **kwargs)
        # Initialise local state.
        self.mode = self.EDIT           # 默认为EDIT模式
        self.shapes = []            # 用于存储当前图像上的bbox,内容为current(libs.shape.py中Shape()类型的数据结构)
        self.current = None         # 用于存储当前绘制的bbox的四个角的坐标的数据
        self.selectedShape = None  # save the selected shape here
        self.selectedShapeCopy = None
        self.drawingLineColor = QColor(0, 0, 255)
        self.drawingRectColor = QColor(0, 0, 255)
        self.line = Shape(line_color=self.drawingLineColor)
        self.prevPoint = QPointF()  # 这个是存储上一次的按键位置吗
        self.offsets = QPointF(), QPointF()
        self.scale = 1.0            # 图像缩放的比例
        self.pixmap = QPixmap()     # 显示图像的控件
        self.visible = {}           # TODO 这是啥
        self._hideBackround = False # TODO 这两个是啥?
        self.hideBackround = False
        self.hShape = None
        self.hVertex = None
        self._painter = QPainter()
        self._cursor = CURSOR_DEFAULT   # 鼠标指针形状,默认就是正常的指针
        # Menus:
        self.menus = (QMenu(), QMenu()) # 不知道这是啥
        # Set widget options.
        self.setMouseTracking(True)     # 鼠标追踪,要想实现mouseMoveEvent,则需要开启鼠标追踪
        self.setFocusPolicy(Qt.WheelFocus)  # 与焦点设置有关,tab+鼠标滚轮选择焦点
        self.verified = False           # TODO 这个是啥
        self.drawSquare = False         # TODO 盲猜点Create RectBox设为True,画完BBox后变为False

        #initialisation for panning
        self.pan_initial_pos = QPoint() # QPoint代表一个坐标点

    def setDrawingColor(self, qColor):
        self.drawingLineColor = qColor
        self.drawingRectColor = qColor

    def enterEvent(self, ev):
        self.overrideCursor(self._cursor)

    def leaveEvent(self, ev):
        self.restoreCursor()

    def focusOutEvent(self, ev):
        self.restoreCursor()

    def isVisible(self, shape):
        return self.visible.get(shape, True)

    def drawing(self):
        return self.mode == self.CREATE

    def editing(self):
        return self.mode == self.EDIT

    def setEditing(self, value=True):
        self.mode = self.EDIT if value else self.CREATE
        if not value:  # Create
            self.unHighlight()
            self.deSelectShape()
        self.prevPoint = QPointF()
        self.repaint()

    def unHighlight(self):
        if self.hShape:
            self.hShape.highlightClear()
        self.hVertex = self.hShape = None

    def selectedVertex(self):
        return self.hVertex is not None

    def mouseMoveEvent(self, ev):   # 移动鼠标时即刻追踪
        """Update line with last point and current coordinates."""
        pos = self.transformPos(ev.pos())

        # Update coordinates in status bar if image is opened
        window = self.parent().window()
        if window.filePath is not None:
            self.parent().window().labelCoordinates.setText(
                'X: %d; Y: %d' % (pos.x(), pos.y()))

        # Polygon drawing.
        if self.drawing():
            self.overrideCursor(CURSOR_DRAW)
            if self.current:
                # Display annotation width and height while drawing
                currentWidth = abs(self.current[0].x() - pos.x())
                currentHeight = abs(self.current[0].y() - pos.y())
                self.parent().window().labelCoordinates.setText(
                        'Width: %d, Height: %d / X: %d; Y: %d' % (currentWidth, currentHeight, pos.x(), pos.y()))

                color = self.drawingLineColor
                if self.outOfPixmap(pos):
                    # Don't allow the user to draw outside the pixmap.
                    # Clip the coordinates to 0 or max,
                    # if they are outside the range [0, max]
                    size = self.pixmap.size()
                    clipped_x = min(max(0, pos.x()), size.width())
                    clipped_y = min(max(0, pos.y()), size.height())
                    pos = QPointF(clipped_x, clipped_y)
                elif len(self.current) > 1 and self.closeEnough(pos, self.current[0]):
                    # Attract line to starting point and colorise to alert the
                    # user:
                    pos = self.current[0]
                    color = self.current.line_color
                    self.overrideCursor(CURSOR_POINT)
                    self.current.highlightVertex(0, Shape.NEAR_VERTEX)

                if self.drawSquare:
                    initPos = self.current[0]
                    minX = initPos.x()
                    minY = initPos.y()
                    min_size = min(abs(pos.x() - minX), abs(pos.y() - minY))
                    directionX = -1 if pos.x() - minX < 0 else 1
                    directionY = -1 if pos.y() - minY < 0 else 1
                    self.line[1] = QPointF(minX + directionX * min_size, minY + directionY * min_size)
                else:
                    self.line[1] = pos

                self.line.line_color = color
                self.prevPoint = QPointF()
                self.current.highlightClear()
            else:
                self.prevPoint = pos
            self.repaint()
            return

        # Polygon copy moving.
        if Qt.RightButton & ev.buttons():
            if self.selectedShapeCopy and self.prevPoint:
                self.overrideCursor(CURSOR_MOVE)
                self.boundedMoveShape(self.selectedShapeCopy, pos)
                self.repaint()
            elif self.selectedShape:
                self.selectedShapeCopy = self.selectedShape.copy()
                self.repaint()
            return

        # Polygon/Vertex moving.
        if Qt.LeftButton & ev.buttons():
            if self.selectedVertex():
                self.boundedMoveVertex(pos)
                self.shapeMoved.emit()
                self.repaint()
            elif self.selectedShape and self.prevPoint:
                self.overrideCursor(CURSOR_MOVE)
                self.boundedMoveShape(self.selectedShape, pos)
                self.shapeMoved.emit()
                self.repaint()
            else:
                #pan
                delta_x = pos.x() - self.pan_initial_pos.x()
                delta_y = pos.y() - self.pan_initial_pos.y()
                self.scrollRequest.emit(delta_x, Qt.Horizontal)
                self.scrollRequest.emit(delta_y, Qt.Vertical)
                self.update()
            return

        # Just hovering over the canvas, 2 posibilities:
        # - Highlight shapes
        # - Highlight vertex
        # Update shape/vertex fill and tooltip value accordingly.
        self.setToolTip("Image")
        for shape in reversed([s for s in self.shapes if self.isVisible(s)]):
            # Look for a nearby vertex to highlight. If that fails,
            # check if we happen to be inside a shape.
            index = shape.nearestVertex(pos, self.epsilon)
            if index is not None:
                if self.selectedVertex():
                    self.hShape.highlightClear()
                self.hVertex, self.hShape = index, shape
                shape.highlightVertex(index, shape.MOVE_VERTEX)
                self.overrideCursor(CURSOR_POINT)
                self.setToolTip("Click & drag to move point")
                self.setStatusTip(self.toolTip())
                self.update()
                break
            elif shape.containsPoint(pos):
                if self.selectedVertex():
                    self.hShape.highlightClear()
                self.hVertex, self.hShape = None, shape
                self.setToolTip(
                    "Click & drag to move shape '%s'" % shape.label)
                self.setStatusTip(self.toolTip())
                self.overrideCursor(CURSOR_GRAB)
                self.update()
                break
        else:  # Nothing found, clear highlights, reset state.
            if self.hShape:
                self.hShape.highlightClear()
                self.update()
            self.hVertex, self.hShape = None, None
            self.overrideCursor(CURSOR_DEFAULT)

    def mousePressEvent(self, ev):  # 按下鼠标事件,开始拖动bbox
        pos = self.transformPos(ev.pos())       # TODO:【重要函数】用ev.pos()获得在缩放了的图像上的按键位置,在用transformPos变换到真实的坐标

        if ev.button() == Qt.LeftButton:    # 如果按的是左键
            if self.drawing():
                self.handleDrawing(pos)         # TODO:【重点函数】获得按下鼠标左键时的坐标位置,传给handleDrawing函数,开始绘制BBox
            else:
                selection = self.selectShapePoint(pos)
                self.prevPoint = pos

                if selection is None:
                    #pan
                    QApplication.setOverrideCursor(QCursor(Qt.OpenHandCursor))
                    self.pan_initial_pos = pos

        elif ev.button() == Qt.RightButton and self.editing():  # 如果按的是右键,并且处于EDIT模式
            self.selectShapePoint(pos)  # 根据右键按在了图像上的位置,来判断是在哪个bbox上右击
            self.prevPoint = pos
        self.update()

    def mouseReleaseEvent(self, ev):    # 松开鼠标事件
        if ev.button() == Qt.RightButton:   # 如果是按右键释放的
            menu = self.menus[bool(self.selectedShapeCopy)]
            self.restoreCursor()
            if not menu.exec_(self.mapToGlobal(ev.pos()))\
               and self.selectedShapeCopy:
                # Cancel the move by deleting the shadow copy.
                self.selectedShapeCopy = None
                self.repaint()
        elif ev.button() == Qt.LeftButton and self.selectedShape:   # 如果是左键点在了已经创建好的bbox上
            if self.selectedVertex():   # TODO 这是啥
                self.overrideCursor(CURSOR_POINT)
            else:
                self.overrideCursor(CURSOR_GRAB)
        elif ev.button() == Qt.LeftButton:      # 其他情况,当前是在绘制新的bbox
            pos = self.transformPos(ev.pos())   # 获得鼠标位置在图像上的真实坐标
            if self.drawing():  # 如果当前是画图模式
                self.handleDrawing(pos)     # 处理绘图功能
            else:
                #pan
                QApplication.restoreOverrideCursor()

    def endMove(self, copy=False):
        assert self.selectedShape and self.selectedShapeCopy
        shape = self.selectedShapeCopy
        #del shape.fill_color
        #del shape.line_color
        if copy:
            self.shapes.append(shape)
            self.selectedShape.selected = False
            self.selectedShape = shape
            self.repaint()
        else:
            self.selectedShape.points = [p for p in shape.points]
        self.selectedShapeCopy = None

    def hideBackroundShapes(self, value):
        self.hideBackround = value
        if self.selectedShape:
            # Only hide other shapes if there is a current selection.
            # Otherwise the user will not be able to select a shape.
            self.setHiding(True)
            self.repaint()

    def handleDrawing(self, pos):
        if self.current and self.current.reachMaxPoints() is False:
            initPos = self.current[0]
            minX = initPos.x()
            minY = initPos.y()
            targetPos = self.line[1]
            maxX = targetPos.x()
            maxY = targetPos.y()    # 根据释放鼠标左键时光标所在图像上的坐标,生成bbox的另外三个角的坐标,并存入self.current中
            self.current.addPoint(QPointF(maxX, minY))
            self.current.addPoint(targetPos)
            self.current.addPoint(QPointF(minX, maxY))
            self.finalise()
        elif not self.outOfPixmap(pos):
            self.current = Shape()
            self.current.addPoint(pos)
            self.line.points = [pos, pos]
            self.setHiding()
            self.drawingPolygon.emit(True)
            self.update()

    def setHiding(self, enable=True):
        self._hideBackround = self.hideBackround if enable else False

    def canCloseShape(self):
        return self.drawing() and self.current and len(self.current) > 2

    def mouseDoubleClickEvent(self, ev):
        # We need at least 4 points here, since the mousePress handler
        # adds an extra one before this handler is called.
        if self.canCloseShape() and len(self.current) > 3:
            self.current.popPoint()
            self.finalise()

    def selectShape(self, shape):
        self.deSelectShape()
        shape.selected = True
        self.selectedShape = shape
        self.setHiding()
        self.selectionChanged.emit(True)
        self.update()

    def selectShapePoint(self, point):
        """Select the first shape created which contains this point."""
        self.deSelectShape()    # 没有选中bbox时的处理
        if self.selectedVertex():  # A vertex is marked for selection.
            index, shape = self.hVertex, self.hShape
            shape.highlightVertex(index, shape.MOVE_VERTEX)
            self.selectShape(shape)
            return self.hVertex
        for shape in reversed(self.shapes):
            if self.isVisible(shape) and shape.containsPoint(point):
                self.selectShape(shape)
                self.calculateOffsets(shape, point)
                return self.selectedShape
        return None

    def calculateOffsets(self, shape, point):
        rect = shape.boundingRect()
        x1 = rect.x() - point.x()
        y1 = rect.y() - point.y()
        x2 = (rect.x() + rect.width()) - point.x()
        y2 = (rect.y() + rect.height()) - point.y()
        self.offsets = QPointF(x1, y1), QPointF(x2, y2)

    def snapPointToCanvas(self, x, y):
        """
        Moves a point x,y to within the boundaries of the canvas.
        :return: (x,y,snapped) where snapped is True if x or y were changed, False if not.
        """
        if x < 0 or x > self.pixmap.width() or y < 0 or y > self.pixmap.height():
            x = max(x, 0)
            y = max(y, 0)
            x = min(x, self.pixmap.width())
            y = min(y, self.pixmap.height())
            return x, y, True

        return x, y, False

    def boundedMoveVertex(self, pos):
        index, shape = self.hVertex, self.hShape
        point = shape[index]
        if self.outOfPixmap(pos):
            size = self.pixmap.size()
            clipped_x = min(max(0, pos.x()), size.width())
            clipped_y = min(max(0, pos.y()), size.height())
            pos = QPointF(clipped_x, clipped_y)

        if self.drawSquare:
            opposite_point_index = (index + 2) % 4
            opposite_point = shape[opposite_point_index]

            min_size = min(abs(pos.x() - opposite_point.x()), abs(pos.y() - opposite_point.y()))
            directionX = -1 if pos.x() - opposite_point.x() < 0 else 1
            directionY = -1 if pos.y() - opposite_point.y() < 0 else 1
            shiftPos = QPointF(opposite_point.x() + directionX * min_size - point.x(),
                               opposite_point.y() + directionY * min_size - point.y())
        else:
            shiftPos = pos - point

        shape.moveVertexBy(index, shiftPos)

        lindex = (index + 1) % 4
        rindex = (index + 3) % 4
        lshift = None
        rshift = None
        if index % 2 == 0:
            rshift = QPointF(shiftPos.x(), 0)
            lshift = QPointF(0, shiftPos.y())
        else:
            lshift = QPointF(shiftPos.x(), 0)
            rshift = QPointF(0, shiftPos.y())
        shape.moveVertexBy(rindex, rshift)
        shape.moveVertexBy(lindex, lshift)

    def boundedMoveShape(self, shape, pos):
        if self.outOfPixmap(pos):
            return False  # No need to move
        o1 = pos + self.offsets[0]
        if self.outOfPixmap(o1):
            pos -= QPointF(min(0, o1.x()), min(0, o1.y()))
        o2 = pos + self.offsets[1]
        if self.outOfPixmap(o2):
            pos += QPointF(min(0, self.pixmap.width() - o2.x()),
                           min(0, self.pixmap.height() - o2.y()))
        # The next line tracks the new position of the cursor
        # relative to the shape, but also results in making it
        # a bit "shaky" when nearing the border and allows it to
        # go outside of the shape's area for some reason. XXX
        #self.calculateOffsets(self.selectedShape, pos)
        dp = pos - self.prevPoint
        if dp:
            shape.moveBy(dp)
            self.prevPoint = pos
            return True
        return False

    def deSelectShape(self):
        if self.selectedShape:
            self.selectedShape.selected = False
            self.selectedShape = None
            self.setHiding(False)
            self.selectionChanged.emit(False)
            self.update()

    def deleteSelected(self):
        if self.selectedShape:
            shape = self.selectedShape
            self.shapes.remove(self.selectedShape)
            self.selectedShape = None
            self.update()
            return shape

    def copySelectedShape(self):
        if self.selectedShape:
            shape = self.selectedShape.copy()
            self.deSelectShape()
            self.shapes.append(shape)
            shape.selected = True
            self.selectedShape = shape
            self.boundedShiftShape(shape)
            return shape

    def boundedShiftShape(self, shape):
        # Try to move in one direction, and if it fails in another.
        # Give up if both fail.
        point = shape[0]
        offset = QPointF(2.0, 2.0)
        self.calculateOffsets(shape, point)
        self.prevPoint = point
        if not self.boundedMoveShape(shape, point - offset):
            self.boundedMoveShape(shape, point + offset)

    def paintEvent(self, event):
        if not self.pixmap:
            return super(Canvas, self).paintEvent(event)

        p = self._painter
        p.begin(self)
        p.setRenderHint(QPainter.Antialiasing)
        p.setRenderHint(QPainter.HighQualityAntialiasing)
        p.setRenderHint(QPainter.SmoothPixmapTransform)

        p.scale(self.scale, self.scale)
        p.translate(self.offsetToCenter())

        p.drawPixmap(0, 0, self.pixmap)
        Shape.scale = self.scale
        for shape in self.shapes:
            if (shape.selected or not self._hideBackround) and self.isVisible(shape):
                shape.fill = shape.selected or shape == self.hShape
                shape.paint(p)
        if self.current:
            self.current.paint(p)
            self.line.paint(p)
        if self.selectedShapeCopy:
            self.selectedShapeCopy.paint(p)

        # Paint rect
        if self.current is not None and len(self.line) == 2:
            leftTop = self.line[0]
            rightBottom = self.line[1]
            rectWidth = rightBottom.x() - leftTop.x()
            rectHeight = rightBottom.y() - leftTop.y()
            p.setPen(self.drawingRectColor)
            brush = QBrush(Qt.BDiagPattern)
            p.setBrush(brush)
            p.drawRect(leftTop.x(), leftTop.y(), rectWidth, rectHeight)

        if self.drawing() and not self.prevPoint.isNull() and not self.outOfPixmap(self.prevPoint):
            p.setPen(QColor(0, 0, 0))
            p.drawLine(self.prevPoint.x(), 0, self.prevPoint.x(), self.pixmap.height())
            p.drawLine(0, self.prevPoint.y(), self.pixmap.width(), self.prevPoint.y())

        self.setAutoFillBackground(True)
        if self.verified:
            pal = self.palette()
            pal.setColor(self.backgroundRole(), QColor(184, 239, 38, 128))
            self.setPalette(pal)
        else:
            pal = self.palette()
            pal.setColor(self.backgroundRole(), QColor(232, 232, 232, 255))
            self.setPalette(pal)

        p.end()

    def transformPos(self, point):
        """Convert from widget-logical coordinates to painter-logical coordinates."""
        return point / self.scale - self.offsetToCenter()

    def offsetToCenter(self):
        s = self.scale
        area = super(Canvas, self).size()
        w, h = self.pixmap.width() * s, self.pixmap.height() * s
        aw, ah = area.width(), area.height()
        x = (aw - w) / (2 * s) if aw > w else 0
        y = (ah - h) / (2 * s) if ah > h else 0
        return QPointF(x, y)

    def outOfPixmap(self, p):
        w, h = self.pixmap.width(), self.pixmap.height()
        return not (0 <= p.x() <= w and 0 <= p.y() <= h)

    def finalise(self): # TODO:【重要函数】得到按下鼠标和释放鼠标的坐标时,开始绘制bbox
        assert self.current
        if self.current.points[0] == self.current.points[-1]:   # 如果按下和释放鼠标为同一个位置,则不绘制bbox
            self.current = None
            self.drawingPolygon.emit(False)
            self.update()
            return

        self.current.close()
        self.shapes.append(self.current)
        self.current = None     # 绘制完一个bbox后,重新初始化current
        self.setHiding(False)
        self.newShape.emit()    # 弹出填写标注label的对话框
        self.update()

    def closeEnough(self, p1, p2):
        #d = distance(p1 - p2)
        #m = (p1-p2).manhattanLength()
        # print "d %.2f, m %d, %.2f" % (d, m, d - m)
        return distance(p1 - p2) < self.epsilon

    # These two, along with a call to adjustSize are required for the
    # scroll area.
    def sizeHint(self):
        return self.minimumSizeHint()

    def minimumSizeHint(self):
        if self.pixmap:
            return self.scale * self.pixmap.size()
        return super(Canvas, self).minimumSizeHint()

    def wheelEvent(self, ev):
        qt_version = 4 if hasattr(ev, "delta") else 5   # ev应该是一种获取滚动的具体情况的数据类型
        if qt_version == 4:
            if ev.orientation() == Qt.Vertical:
                v_delta = ev.delta()
                h_delta = 0
            else:
                h_delta = ev.delta()
                v_delta = 0
        else:
            delta = ev.angleDelta()     # 应该是用来表示滑动的距离的距离,单位为度的1/8,大多数鼠标以15度为步进工作
            h_delta = delta.x()         # 水平滚动距离(按住alt键后)
            v_delta = delta.y()         # 垂直滚动距离(默认)

        mods = ev.modifiers()           # 用来判断缩放时候是否按住了组合键
        if Qt.ControlModifier == int(mods) and v_delta: # 如果按住了ctrl键并滚动,则代表是缩放功能
            self.zoomRequest.emit(v_delta)
        else:
            v_delta and self.scrollRequest.emit(v_delta, Qt.Vertical)   # 如果垂直滚动有数值,即没有按住alt键,则释放垂直滚动信号
            h_delta and self.scrollRequest.emit(h_delta, Qt.Horizontal) # 如果水平滚动有数值,即按住了alt键,则释放水平滚动信号
        ev.accept() # 这是啥,但感觉不重要

    def keyPressEvent(self, ev):
        key = ev.key()
        if key == Qt.Key_Escape and self.current:
            print('ESC press')
            self.current = None
            self.drawingPolygon.emit(False)
            self.update()
        elif key == Qt.Key_Return and self.canCloseShape():
            self.finalise()
        elif key == Qt.Key_Left and self.selectedShape:
            self.moveOnePixel('Left')
        elif key == Qt.Key_Right and self.selectedShape:
            self.moveOnePixel('Right')
        elif key == Qt.Key_Up and self.selectedShape:
            self.moveOnePixel('Up')
        elif key == Qt.Key_Down and self.selectedShape:
            self.moveOnePixel('Down')

    def moveOnePixel(self, direction):
        # print(self.selectedShape.points)
        if direction == 'Left' and not self.moveOutOfBound(QPointF(-1.0, 0)):
            # print("move Left one pixel")
            self.selectedShape.points[0] += QPointF(-1.0, 0)
            self.selectedShape.points[1] += QPointF(-1.0, 0)
            self.selectedShape.points[2] += QPointF(-1.0, 0)
            self.selectedShape.points[3] += QPointF(-1.0, 0)
        elif direction == 'Right' and not self.moveOutOfBound(QPointF(1.0, 0)):
            # print("move Right one pixel")
            self.selectedShape.points[0] += QPointF(1.0, 0)
            self.selectedShape.points[1] += QPointF(1.0, 0)
            self.selectedShape.points[2] += QPointF(1.0, 0)
            self.selectedShape.points[3] += QPointF(1.0, 0)
        elif direction == 'Up' and not self.moveOutOfBound(QPointF(0, -1.0)):
            # print("move Up one pixel")
            self.selectedShape.points[0] += QPointF(0, -1.0)
            self.selectedShape.points[1] += QPointF(0, -1.0)
            self.selectedShape.points[2] += QPointF(0, -1.0)
            self.selectedShape.points[3] += QPointF(0, -1.0)
        elif direction == 'Down' and not self.moveOutOfBound(QPointF(0, 1.0)):
            # print("move Down one pixel")
            self.selectedShape.points[0] += QPointF(0, 1.0)
            self.selectedShape.points[1] += QPointF(0, 1.0)
            self.selectedShape.points[2] += QPointF(0, 1.0)
            self.selectedShape.points[3] += QPointF(0, 1.0)
        self.shapeMoved.emit()
        self.repaint()

    def moveOutOfBound(self, step):
        points = [p1+p2 for p1, p2 in zip(self.selectedShape.points, [step]*4)]
        return True in map(self.outOfPixmap, points)

    def setLastLabel(self, text, line_color  = None, fill_color = None):
        assert text
        self.shapes[-1].label = text
        if line_color:
            self.shapes[-1].line_color = line_color

        if fill_color:
            self.shapes[-1].fill_color = fill_color

        return self.shapes[-1]

    def undoLastLine(self):
        assert self.shapes
        self.current = self.shapes.pop()
        self.current.setOpen()
        self.line.points = [self.current[-1], self.current[0]]
        self.drawingPolygon.emit(True)

    def resetAllLines(self):
        assert self.shapes
        self.current = self.shapes.pop()
        self.current.setOpen()
        self.line.points = [self.current[-1], self.current[0]]
        self.drawingPolygon.emit(True)
        self.current = None
        self.drawingPolygon.emit(False)
        self.update()

    def loadPixmap(self, pixmap):       # TODO 重要函数,加载图像
        self.pixmap = pixmap
        self.shapes = []
        self.repaint()

    def loadShapes(self, shapes):
        self.shapes = list(shapes)
        self.current = None
        self.repaint()

    def setShapeVisible(self, shape, value):
        self.visible[shape] = value
        self.repaint()

    def currentCursor(self):
        cursor = QApplication.overrideCursor()
        if cursor is not None:
            cursor = cursor.shape()
        return cursor

    def overrideCursor(self, cursor):
        self._cursor = cursor
        if self.currentCursor() is None:
            QApplication.setOverrideCursor(cursor)
        else:
            QApplication.changeOverrideCursor(cursor)

    def restoreCursor(self):
        QApplication.restoreOverrideCursor()

    def resetState(self):
        self.restoreCursor()
        self.pixmap = None
        self.update()

    def setDrawingShapeToSquare(self, status):
        self.drawSquare = status
コード例 #12
0
class Canvas(QWidget):
    zoomRequest = pyqtSignal(int)
    scrollRequest = pyqtSignal(int, int)
    newShape = pyqtSignal()
    selectionChanged = pyqtSignal(bool)
    shapeMoved = pyqtSignal()
    drawingPolygon = pyqtSignal(bool)

    CREATE, EDIT = list(range(2))

    epsilon = 11.0

    FIXED_WIDTH = 192
    FIXED_HEIGHT = 192

    def __init__(self, *args, **kwargs):
        super(Canvas, self).__init__(*args, **kwargs)
        # Initialise local state.
        self.mode = self.EDIT
        self.shapes = []
        self.current = None
        self.selectedShape = None  # save the selected shape here
        self.selectedShapeCopy = None
        self.drawingLineColor = QColor(0, 0, 255)
        self.drawingRectColor = QColor(0, 0, 255)
        self.line = Shape(line_color=self.drawingLineColor)
        self.prevPoint = QPointF()
        self.offsets = QPointF(), QPointF()
        self.scale = 1.0
        self.pixmap = QPixmap()
        self.visible = {}
        self._hideBackround = False
        self.hideBackround = False
        self.hShape = None
        self.hVertex = None
        self._painter = QPainter()
        self._cursor = CURSOR_DEFAULT
        # Menus:
        self.menus = (QMenu(), QMenu())
        # Set widget options.
        self.setMouseTracking(True)
        self.setFocusPolicy(Qt.WheelFocus)
        self.verified = False
        self.drawSquare = False
        self.drawRegion = True  # Add by yuan : set drawRegion flag to true
        self.fileName = 'unknow'

    def setDrawingColor(self, qColor):
        self.drawingLineColor = qColor
        self.drawingRectColor = qColor

    def enterEvent(self, ev):
        self.overrideCursor(self._cursor)

    def leaveEvent(self, ev):
        self.restoreCursor()

    def focusOutEvent(self, ev):
        self.restoreCursor()

    def isVisible(self, shape):
        return self.visible.get(shape, True)

    def drawing(self):
        return self.mode == self.CREATE

    def editing(self):
        return self.mode == self.EDIT

    def setEditing(self, value=True):
        self.mode = self.EDIT if value else self.CREATE
        if not value:  # Create
            self.unHighlight()
            self.deSelectShape()
        self.prevPoint = QPointF()
        self.repaint()

    def unHighlight(self):
        if self.hShape:
            self.hShape.highlightClear()
        self.hVertex = self.hShape = None

    def selectedVertex(self):
        return self.hVertex is not None

    def mouseMoveEvent(self, ev):
        """Update line with last point and current coordinates."""
        pos = self.transformPos(ev.pos())

        # Update coordinates in status bar if image is opened
        window = self.parent().window()
        if window.filePath is not None:
            self.parent().window().labelCoordinates.setText('X: %d; Y: %d' %
                                                            (pos.x(), pos.y()))

        # Polygon drawing.
        if self.drawing():
            self.overrideCursor(CURSOR_DRAW)
            if self.current:
                color = self.drawingLineColor
                if self.outOfPixmap(pos):
                    # Don't allow the user to draw outside the pixmap.
                    # Project the point to the pixmap's edges.
                    pos = self.intersectionPoint(self.current[-1], pos)
                elif len(self.current) > 1 and self.closeEnough(
                        pos, self.current[0]):
                    # Attract line to starting point and colorise to alert the
                    # user:
                    pos = self.current[0]
                    color = self.current.line_color
                    self.overrideCursor(CURSOR_POINT)
                    self.current.highlightVertex(0, Shape.NEAR_VERTEX)

                # add by yuan ==> draw region
                if self.drawRegion:
                    # pos_tmp添加到self.pos_xy中
                    # pos_tmp = (pos.x(),  pos.y())
                    if self.current:
                        self.current.addPoint(pos)
                        #self.line[1] = pos
                else:
                    if self.drawSquare:
                        initPos = self.current[0]
                        minX = initPos.x()
                        minY = initPos.y()
                        min_size = min(abs(pos.x() - minX),
                                       abs(pos.y() - minY))
                        directionX = -1 if pos.x() - minX < 0 else 1
                        directionY = -1 if pos.y() - minY < 0 else 1
                        self.line[1] = QPointF(minX + directionX * min_size,
                                               minY + directionY * min_size)
                    else:
                        self.line[1] = pos

                self.line.line_color = color
                self.prevPoint = QPointF()
                self.current.highlightClear()
            else:
                self.prevPoint = pos
            self.repaint()
            return

        # Polygon copy moving.
        if Qt.RightButton & ev.buttons():
            if self.selectedShapeCopy and self.prevPoint:
                self.overrideCursor(CURSOR_MOVE)
                self.boundedMoveShape(self.selectedShapeCopy, pos)
                self.repaint()
            elif self.selectedShape:
                self.selectedShapeCopy = self.selectedShape.copy()
                self.repaint()
            return

        # Polygon/Vertex moving.
        if Qt.LeftButton & ev.buttons():
            if self.selectedVertex():
                self.boundedMoveVertex(pos)
                self.shapeMoved.emit()
                self.repaint()
            elif self.selectedShape and self.prevPoint:
                self.overrideCursor(CURSOR_MOVE)
                self.boundedMoveShape(self.selectedShape, pos)
                self.shapeMoved.emit()
                self.repaint()
            return

        # Just hovering over the canvas, 2 posibilities:
        # - Highlight shapes
        # - Highlight vertex
        # Update shape/vertex fill and tooltip value accordingly.
        self.setToolTip("Image")
        for shape in reversed([s for s in self.shapes if self.isVisible(s)]):
            # Look for a nearby vertex to highlight. If that fails,
            # check if we happen to be inside a shape.
            index = shape.nearestVertex(pos, self.epsilon)
            if index is not None:
                if self.selectedVertex():
                    self.hShape.highlightClear()
                self.hVertex, self.hShape = index, shape
                shape.highlightVertex(index, shape.MOVE_VERTEX)
                self.overrideCursor(CURSOR_POINT)
                self.setToolTip("Click & drag to move point")
                self.setStatusTip(self.toolTip())
                self.update()
                break
            elif shape.containsPoint(pos):
                if self.selectedVertex():
                    self.hShape.highlightClear()
                self.hVertex, self.hShape = None, shape
                self.setToolTip("Click & drag to move shape '%s'" %
                                shape.label)
                self.setStatusTip(self.toolTip())
                self.overrideCursor(CURSOR_GRAB)
                self.update()
                break
        else:  # Nothing found, clear highlights, reset state.
            if self.hShape:
                self.hShape.highlightClear()
                self.update()
            self.hVertex, self.hShape = None, None
            self.overrideCursor(CURSOR_DEFAULT)

    def mousePressEvent(self, ev):
        pos = self.transformPos(ev.pos())
        if self.drawRegion:
            # pos_tmp添加到self.pos_xy中
            if ev.button() == Qt.LeftButton:
                if self.drawing():
                    self.handleDrawing(pos)
                    #self.pos_xy.append(pos)
                else:
                    self.selectShapePoint(pos)
                    self.prevPoint = pos
                    self.repaint()
            elif ev.button() == Qt.RightButton and self.editing():
                self.selectShapePoint(pos)
                self.prevPoint = pos
                self.repaint()
        else:
            if ev.button() == Qt.LeftButton:
                if self.drawing():
                    self.handleDrawing(pos)
                else:
                    self.selectShapePoint(pos)
                    self.prevPoint = pos
                    self.repaint()
            elif ev.button() == Qt.RightButton and self.editing():
                self.selectShapePoint(pos)
                self.prevPoint = pos
                self.repaint()

    def mouseReleaseEvent(self, ev):
        if self.drawRegion:
            if ev.button() == Qt.RightButton:
                menu = self.menus[bool(self.selectedShapeCopy)]
                self.restoreCursor()
                if not menu.exec_(self.mapToGlobal(ev.pos())) \
                        and self.selectedShapeCopy:
                    # Cancel the move by deleting the shadow copy.
                    self.selectedShapeCopy = None
                    self.repaint()
            elif ev.button() == Qt.LeftButton and self.selectedShape:
                if self.selectedVertex():
                    self.overrideCursor(CURSOR_POINT)
                else:
                    self.overrideCursor(CURSOR_GRAB)
            elif ev.button() == Qt.LeftButton:
                pos = self.transformPos(ev.pos())
                if self.drawing():
                    self.handleDrawing(pos)
        else:
            if ev.button() == Qt.RightButton:
                menu = self.menus[bool(self.selectedShapeCopy)]
                self.restoreCursor()
                if not menu.exec_(self.mapToGlobal(ev.pos()))\
                    and self.selectedShapeCopy:
                    # Cancel the move by deleting the shadow copy.
                    self.selectedShapeCopy = None
                    self.repaint()
            elif ev.button() == Qt.LeftButton and self.selectedShape:
                if self.selectedVertex():
                    self.overrideCursor(CURSOR_POINT)
                else:
                    self.overrideCursor(CURSOR_GRAB)
            elif ev.button() == Qt.LeftButton:
                pos = self.transformPos(ev.pos())
                if self.drawing():
                    self.handleDrawing(pos)

    def endMove(self, copy=False):
        assert self.selectedShape and self.selectedShapeCopy
        shape = self.selectedShapeCopy
        #del shape.fill_color
        #del shape.line_color
        if copy:
            self.shapes.append(shape)
            self.selectedShape.selected = False
            self.selectedShape = shape
            self.repaint()
        else:
            self.selectedShape.points = [p for p in shape.points]
        self.selectedShapeCopy = None

    def hideBackroundShapes(self, value):
        self.hideBackround = value
        if self.selectedShape:
            # Only hide other shapes if there is a current selection.
            # Otherwise the user will not be able to select a shape.
            self.setHiding(True)
            self.repaint()

    def handleDrawing(self, pos):
        if self.drawRegion:
            if self.current:
                #initPos = self.current[0]
                self.current.addPoint(self.line[1])
                self.finalise_region()
            elif not self.outOfPixmap(pos):
                self.current = Shape(drawRegion=True)
                self.current.addPoint(pos)
                self.line.points = [pos, pos]
                self.setHiding()
                self.drawingPolygon.emit(True)
                self.update()
        else:
            if self.current and self.current.reachMaxPoints() is False:
                initPos = self.current[0]
                minX = initPos.x()
                minY = initPos.y()

                if Canvas.FIXED_HEIGHT == 0 and Canvas.FIXED_WIDTH == 0:
                    targetPos = self.line[1]
                    maxX = targetPos.x()
                    maxY = targetPos.y()
                else:
                    self.current.clearPoints()
                    orig_minX = minX
                    orig_minY = minY
                    minX = minX - Canvas.FIXED_WIDTH / 2
                    minY = minY - Canvas.FIXED_HEIGHT / 2
                    maxX = minX + Canvas.FIXED_WIDTH
                    maxY = minY + Canvas.FIXED_HEIGHT
                    # lef scale
                    if minX < 25:
                        maxX = maxX + (25 - minX)
                        minX = 25

                    if maxX > 740:
                        minX = minX - (maxX - 740)
                        maxX = 740

                    # top
                    if minY < 155:
                        maxY = maxY + (155 - minY)
                        minY = 155
                    #
                    if maxY > 540:
                        minY = minY - (maxY - 540)
                        maxY = 540

                    if orig_minX <= 378:  # left image
                        if minY <= 200 and maxX > 310:  #avoid color bar
                            if orig_minY < 200:
                                minX = minX - (maxX - 310)
                                maxX = 310
                            else:
                                minX = minX - (maxX - 378)
                                maxX = 378
                                maxY = maxY - (minY - 200)
                                minY = 200
                        elif maxX > 378:
                            minX = minX - (maxX - 378)
                            maxX = 378
                    else:  # right image
                        if minY <= 200 and maxX > 690:  # avoid color bar
                            if orig_minY < 200:
                                minX = minX - (maxX - 690)
                                maxX = 690
                            else:
                                minX = minX - (maxX - 740)
                                maxX = 740
                                maxY = maxY - (minY - 192)
                                minY = 200
                        else:
                            if maxX > 740:
                                minX = minX - (maxX - 740)
                                maxX = 740

                            if minX < 378:
                                maxX = maxX + (378 - minX)
                                minX = 378

                            if minY < 170:
                                maxY = maxY + (170 - minY)
                                minY = 170

                    self.current.addPoint(QPointF(minX, minY))
                    targetPos = QPointF(maxX, maxY)
                    self.line[1] = targetPos

                self.current.addPoint(QPointF(maxX, minY))
                self.current.addPoint(targetPos)
                self.current.addPoint(QPointF(minX, maxY))
                self.finalise()
            elif not self.outOfPixmap(pos):
                self.current = Shape()
                self.current.addPoint(pos)
                self.line.points = [pos, pos]
                self.setHiding()
                self.drawingPolygon.emit(True)
                self.update()

    def setHiding(self, enable=True):
        self._hideBackround = self.hideBackround if enable else False

    def canCloseShape(self):
        return self.drawing() and self.current and len(self.current) > 2

    def mouseDoubleClickEvent(self, ev):
        # We need at least 4 points here, since the mousePress handler
        # adds an extra one before this handler is called.
        if self.canCloseShape() and len(self.current) > 3:
            self.current.popPoint()
            self.finalise()

    def selectShape(self, shape):
        self.deSelectShape()
        shape.selected = True
        self.drawRegion = shape.drawRegion  # add by yuan
        self.selectedShape = shape
        self.setHiding()
        self.selectionChanged.emit(True)
        self.update()

    def selectShapePoint(self, point):
        """Select the first shape created which contains this point."""
        self.deSelectShape()
        if self.selectedVertex():  # A vertex is marked for selection.
            index, shape = self.hVertex, self.hShape
            shape.highlightVertex(index, shape.MOVE_VERTEX)
            self.selectShape(shape)
            return
        for shape in reversed(self.shapes):
            # if not shape.drawRegion:
            if self.isVisible(shape) and shape.containsPoint(point):
                self.selectShape(shape)
                self.calculateOffsets(shape, point)
                return

    def calculateOffsets(self, shape, point):
        rect = shape.boundingRect()
        x1 = rect.x() - point.x()
        y1 = rect.y() - point.y()
        x2 = (rect.x() + rect.width()) - point.x()
        y2 = (rect.y() + rect.height()) - point.y()
        self.offsets = QPointF(x1, y1), QPointF(x2, y2)

    def boundedMoveVertex(self, pos):
        index, shape = self.hVertex, self.hShape
        point = shape[index]
        if self.outOfPixmap(pos):
            pos = self.intersectionPoint(point, pos)

        if self.drawSquare:
            opposite_point_index = (index + 2) % 4
            opposite_point = shape[opposite_point_index]

            min_size = min(abs(pos.x() - opposite_point.x()),
                           abs(pos.y() - opposite_point.y()))
            directionX = -1 if pos.x() - opposite_point.x() < 0 else 1
            directionY = -1 if pos.y() - opposite_point.y() < 0 else 1
            shiftPos = QPointF(
                opposite_point.x() + directionX * min_size - point.x(),
                opposite_point.y() + directionY * min_size - point.y())
        else:
            shiftPos = pos - point

        shape.moveVertexBy(index, shiftPos)

        lindex = (index + 1) % 4
        rindex = (index + 3) % 4
        lshift = None
        rshift = None
        if index % 2 == 0:
            rshift = QPointF(shiftPos.x(), 0)
            lshift = QPointF(0, shiftPos.y())
        else:
            lshift = QPointF(shiftPos.x(), 0)
            rshift = QPointF(0, shiftPos.y())
        shape.moveVertexBy(rindex, rshift)
        shape.moveVertexBy(lindex, lshift)

    def boundedMoveShape(self, shape, pos):
        if self.outOfPixmap(pos):
            return False  # No need to move
        o1 = pos + self.offsets[0]
        if self.outOfPixmap(o1):
            pos -= QPointF(min(0, o1.x()), min(0, o1.y()))
        o2 = pos + self.offsets[1]
        if self.outOfPixmap(o2):
            pos += QPointF(min(0,
                               self.pixmap.width() - o2.x()),
                           min(0,
                               self.pixmap.height() - o2.y()))
        # The next line tracks the new position of the cursor
        # relative to the shape, but also results in making it
        # a bit "shaky" when nearing the border and allows it to
        # go outside of the shape's area for some reason. XXX
        #self.calculateOffsets(self.selectedShape, pos)
        dp = pos - self.prevPoint
        if dp:
            shape.moveBy(dp)
            self.prevPoint = pos
            return True
        return False

    def deSelectShape(self):
        if self.selectedShape:
            self.selectedShape.selected = False
            self.selectedShape = None
            self.setHiding(False)
            self.selectionChanged.emit(False)
            self.update()

    def deleteSelected(self):
        if self.selectedShape:
            shape = self.selectedShape
            self.shapes.remove(self.selectedShape)
            self.selectedShape = None
            self.update()
            return shape

    def copySelectedShape(self):
        if self.selectedShape:
            shape = self.selectedShape.copy()
            self.deSelectShape()
            self.shapes.append(shape)
            shape.selected = True
            self.selectedShape = shape
            self.boundedShiftShape(shape)
            return shape

    def boundedShiftShape(self, shape):
        # Try to move in one direction, and if it fails in another.
        # Give up if both fail.
        point = shape[0]
        offset = QPointF(2.0, 2.0)
        self.calculateOffsets(shape, point)
        self.prevPoint = point
        if not self.boundedMoveShape(shape, point - offset):
            self.boundedMoveShape(shape, point + offset)

    def paintEvent(self, event):
        if not self.pixmap:
            return super(Canvas, self).paintEvent(event)

        p = self._painter
        p.begin(self)
        p.setRenderHint(QPainter.Antialiasing)
        p.setRenderHint(QPainter.HighQualityAntialiasing)
        p.setRenderHint(QPainter.SmoothPixmapTransform)

        p.scale(self.scale, self.scale)
        p.translate(self.offsetToCenter())
        p.drawPixmap(0, 0, self.pixmap)

        Shape.scale = self.scale
        for shape in self.shapes:
            if (shape.selected
                    or not self._hideBackround) and self.isVisible(shape):
                shape.fill = shape.selected or shape == self.hShape
                shape.paint(p)

        # Paint rect
        # add by yuan ==> draw region
        if self.drawRegion:
            if self.current:
                self.current.paint(p)
        else:
            #Shape.scale = self.scale
            #for shape in self.shapes:
            #    if (shape.selected or not self._hideBackround) and self.isVisible(shape):
            #        shape.fill = shape.selected or shape == self.hShape
            #        shape.paint(p)

            if self.current:
                self.current.paint(p)
                self.line.paint(p)
            if self.selectedShapeCopy:
                self.selectedShapeCopy.paint(p)

            if self.current is not None and len(self.line) == 2:
                leftTop = self.line[0]
                rightBottom = self.line[1]
                rectWidth = rightBottom.x() - leftTop.x()
                rectHeight = rightBottom.y() - leftTop.y()
                p.setPen(self.drawingRectColor)
                brush = QBrush(Qt.BDiagPattern)
                p.setBrush(brush)
                p.drawRect(leftTop.x(), leftTop.y(), rectWidth, rectHeight)

            if self.drawing() and not self.prevPoint.isNull(
            ) and not self.outOfPixmap(self.prevPoint):
                p.setPen(QColor(0, 0, 0))
                p.drawLine(self.prevPoint.x(), 0, self.prevPoint.x(),
                           self.pixmap.height())
                p.drawLine(0, self.prevPoint.y(), self.pixmap.width(),
                           self.prevPoint.y())

            self.setAutoFillBackground(True)
            if self.verified:
                pal = self.palette()
                pal.setColor(self.backgroundRole(), QColor(184, 239, 38, 128))
                self.setPalette(pal)
            else:
                pal = self.palette()
                pal.setColor(self.backgroundRole(), QColor(232, 232, 232, 255))
                self.setPalette(pal)

        p.end()

    def transformPos(self, point):
        """Convert from widget-logical coordinates to painter-logical coordinates."""
        return point / self.scale - self.offsetToCenter()

    def offsetToCenter(self):
        s = self.scale
        area = super(Canvas, self).size()
        w, h = self.pixmap.width() * s, self.pixmap.height() * s
        aw, ah = area.width(), area.height()
        x = (aw - w) / (2 * s) if aw > w else 0
        y = (ah - h) / (2 * s) if ah > h else 0
        return QPointF(x, y)

    def outOfPixmap(self, p):
        w, h = self.pixmap.width(), self.pixmap.height()
        return not (0 <= p.x() <= w and 0 <= p.y() <= h)

    # add by yuan
    def finalise_region(self):
        assert self.current
        # first equlas last is closed region
        if len(self.current.points) >= 10:
            if self.current.points[0] == self.current.points[-1]:
                self.current.close()
                self.shapes.append(self.current)
                self.current = None
                self.setHiding(False)
                self.drawingPolygon.emit(False)
                self.newShape.emit()  # notify to create labedialog
                self.update()

    def finalise(self):
        assert self.current
        # first equlas last is closed region
        if self.current.points[0] == self.current.points[-1]:
            self.current = None
            self.drawingPolygon.emit(False)
            self.update()
            return

        self.current.close()
        self.shapes.append(self.current)
        self.current = None
        self.setHiding(False)
        self.newShape.emit()  # notify to create labedialog
        self.update()

    def closeEnough(self, p1, p2):
        #d = distance(p1 - p2)
        #m = (p1-p2).manhattanLength()
        # print "d %.2f, m %d, %.2f" % (d, m, d - m)
        return distance(p1 - p2) < self.epsilon

    def intersectionPoint(self, p1, p2):
        # Cycle through each image edge in clockwise fashion,
        # and find the one intersecting the current line segment.
        # http://paulbourke.net/geometry/lineline2d/
        size = self.pixmap.size()
        points = [(0, 0), (size.width(), 0), (size.width(), size.height()),
                  (0, size.height())]
        x1, y1 = p1.x(), p1.y()
        x2, y2 = p2.x(), p2.y()
        d, i, (x, y) = min(self.intersectingEdges((x1, y1), (x2, y2), points))
        x3, y3 = points[i]
        x4, y4 = points[(i + 1) % 4]
        if (x, y) == (x1, y1):
            # Handle cases where previous point is on one of the edges.
            if x3 == x4:
                return QPointF(x3, min(max(0, y2), max(y3, y4)))
            else:  # y3 == y4
                return QPointF(min(max(0, x2), max(x3, x4)), y3)
        return QPointF(x, y)

    def intersectingEdges(self, x1y1, x2y2, points):
        """For each edge formed by `points', yield the intersection
        with the line segment `(x1,y1) - (x2,y2)`, if it exists.
        Also return the distance of `(x2,y2)' to the middle of the
        edge along with its index, so that the one closest can be chosen."""
        x1, y1 = x1y1
        x2, y2 = x2y2
        for i in range(4):
            x3, y3 = points[i]
            x4, y4 = points[(i + 1) % 4]
            denom = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1)
            nua = (x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)
            nub = (x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)
            if denom == 0:
                # This covers two cases:
                #   nua == nub == 0: Coincident
                #   otherwise: Parallel
                continue
            ua, ub = nua / denom, nub / denom
            if 0 <= ua <= 1 and 0 <= ub <= 1:
                x = x1 + ua * (x2 - x1)
                y = y1 + ua * (y2 - y1)
                m = QPointF((x3 + x4) / 2, (y3 + y4) / 2)
                d = distance(m - QPointF(x2, y2))
                yield d, i, (x, y)

    # add by yuan
    def covert2Rectangle(self, points):
        xmin = float('inf')
        ymin = float('inf')
        xmax = float('-inf')
        ymax = float('-inf')
        for p in points:
            x = p.x()
            y = p.y()
            xmin = min(x, xmin)
            ymin = min(y, ymin)
            xmax = max(x, xmax)
            ymax = max(y, ymax)

        # Martin Kersner, 2015/11/12
        # 0-valued coordinates of BB caused an error while
        # training faster-rcnn object detector.
        if xmin < 1:
            xmin = 1

        if ymin < 1:
            ymin = 1

        width = int(xmax - xmin)
        if width < 1:
            width = 1

        height = int(ymax - ymin)
        if height < 1:
            height = 1
        return (int(xmin), int(ymin), width, height)

    def storePoint(self, points):
        xmin = float('inf')
        ymin = float('inf')
        xmax = float('-inf')
        ymax = float('-inf')
        for p in points:
            x = p.x()
            y = p.y()
            xmin = min(x, xmin)
            ymin = min(y, ymin)
            xmax = max(x, xmax)
            ymax = max(y, ymax)

        # Martin Kersner, 2015/11/12
        # 0-valued coordinates of BB caused an error while
        # training faster-rcnn object detector.
        if xmin < 1:
            xmin = 1

        if ymin < 1:
            ymin = 1

        return (int(xmin), int(ymin), int(xmax), int(ymax))

    # add by yuan
    def saveSplitImage(self, shape, split_img, ismask, image_side, serial_num):
        print("begin - saveSplitImage")
        label_name = 'unknow'
        if not shape.label is None:
            label_name = str(shape.label)

        #self.fileName
        if ismask:
            img_file = '{}_{}_{}_{}_mask.jpg'.format(label_name, self.fileName,
                                                     image_side, serial_num)
        else:
            img_file = '{}_{}_{}_{}.jpg'.format(label_name, self.fileName,
                                                image_side, serial_num)

        img_path = path.abspath('breast dataset/train dataset')
        if not path.exists(img_path):
            mkdir(img_path)

        split_img.save(path.join(img_path, img_file), 'JPG', 100)

        print("end - saveSplitImage ")

    def saveSplitHSImage(self, shape, split_img, image_side, serial_num):
        print("begin - saveSplitHSImage")

        label_name = 'unknow'
        if not shape.label is None:
            label_name = str(shape.label)

        # self.fileName
        img_file = '{}_{}_{}_{}_HS.jpg'.format(label_name, self.fileName,
                                               image_side, serial_num)
        afimg_file = '{}_{}_{}_{}_after.jpg'.format(label_name, self.fileName,
                                                    image_side, serial_num)
        beimg_file = '{}_{}_{}_{}_before.jpg'.format(label_name, self.fileName,
                                                     image_side, serial_num)
        img_path = path.abspath('breast dataset/train HSdataset')
        if not path.exists(img_path):
            mkdir(img_path)

        splitimg = split_img.toImage()
        splitimg = splitimg.convertToFormat(QImage.Format_Grayscale8)
        splitimg = qp.qimageview(splitimg)
        splitimg = np.stack((splitimg, ) * 3, axis=-1)

        #print (splitimg)
        min_gray = np.min(splitimg)
        max_gray = np.max(splitimg)
        # print (img[0][:10])
        print('min:', min_gray, 'max:', max_gray)
        x_axis = list(range(0, 255))
        #y_axis = splitimg.flatten().tolist()
        plt.figure()
        plt.hist(splitimg.flatten().tolist(), bins=x_axis)
        plt.title('Original Histogram - Min:{} Max:{}'.format(
            min_gray, max_gray))
        plt.savefig(path.join(img_path, beimg_file))

        scale = 255 / (max_gray - min_gray)
        print(scale)
        trasfor_img = (splitimg - min_gray)
        splitimg = trasfor_img * scale
        splitimg = splitimg.astype(int)

        #splitimg.save(path.join(img_path, img_file), 'JPG', 100)
        cv2.imwrite(path.join(img_path, img_file), splitimg,
                    [cv2.IMWRITE_JPEG_QUALITY, 100])

        min_gray = np.min(splitimg)
        max_gray = np.max(splitimg)
        print('min:', min_gray, 'max:', max_gray)

        plt.figure()
        plt.hist(splitimg.flatten().tolist(), bins=x_axis)
        plt.title('Histogram Stretching - Min:{} Max:{}'.format(
            min_gray, max_gray))
        plt.savefig(path.join(img_path, afimg_file))

        print("end - saveHSSplitImage ")

    def saveSelectedShape(self):
        print('begin - saveSelectedShape')
        #select_shape =   self.selectedShape
        # the tiles are known to divide evenly
        # width = self.pixmap.width()  height = self.pixmap.height()
        str_time = strftime("%m%d%H%M%S", gmtime())

        shape = self.selectedShape
        imgside = 'right'

        if shape.drawRegion:
            print("start - saveRegionByOutsideRectangle")
            rect = self.storePoint(shape.points)
            rect = self.findFitRectangleShape(rect)
            split_img = self.pixmap.copy(rect[0], rect[1], rect[2], rect[3])

            if rect[0] < 380:
                imgside = 'left'

            self.saveSplitImage(shape, split_img, False, imgside, str_time)
            self.saveSplitHSImage(shape, split_img, imgside, str_time)
            self.saveRegionByOutsideRectangle(shape, rect, str_time)
            print("end - saveRegionByOutsideRectangle")
        else:
            print("start - findContainRegion")
            rect = self.covert2Rectangle(shape.points)
            split_img = self.pixmap.copy(rect[0], rect[1], rect[2], rect[3])

            if rect[0] < 380:
                imgside = 'left'

            self.saveSplitImage(shape, split_img, False, imgside, str_time)
            self.saveSplitHSImage(shape, split_img, imgside, str_time)
            self.findContainRegion(shape, rect, str_time)
            print("end - findContainRegion")

        QMessageBox.information(self, "Save Image", "Save finished!")

    # add by yuan
    def findFitRectangleShape(self, rectangle):
        min_rectx = 0
        min_recty = 0
        max_rectx = float('inf')
        max_recty = float('inf')

        isfindrect = False
        for shape in self.shapes:
            if not shape.drawRegion:
                rect = self.storePoint(shape.points)
                if rect[0] <= rectangle[0] and rect[1] <= rectangle[1]:
                    if rect[2] >= rectangle[2] and rect[3] >= rectangle[3]:
                        if rect[0] >= min_rectx and rect[1] >= min_recty:
                            if rect[2] <= max_rectx and rect[3] <= max_recty:
                                min_rectx = rect[0]
                                min_recty = rect[1]
                                max_rectx = rect[2]
                                max_recty = rect[3]
                                isfindrect = True

        if isfindrect:
            rect_x = min_rectx
            rect_y = min_recty
            rect_width = int(max_rectx - min_rectx)
            rect_height = int(max_recty - min_recty)
        else:
            rect_x = rectangle[0] - 5
            if rect_x <= 0:
                rect_x = 1

            rect_y = rectangle[1] - 5
            if rect_y <= 0:
                rect_y = 1

            rect_width = int(rectangle[2] - rectangle[0]) + 10
            rect_height = int(rectangle[3] - rectangle[1]) + 10

        return (rect_x, rect_y, rect_width, rect_height)

    def saveRegionByOutsideRectangle(self, shape, rectangle, str_time):
        print('begin - ')
        pix = QPixmap(self.pixmap.width(), self.pixmap.height())

        pp = QPainter()
        pp.begin(pix)

        pp.setPen(QPen(QColor(0, 0, 0)))
        pp.setBrush(QBrush(QColor(255, 255, 255)))

        polygon = QPolygonF()
        for point in shape:
            polygon.append(QPointF(point.x(), point.y()))

        print('draw polygon')
        pp.drawPolygon(polygon)
        pp.end()

        print('copy image')
        pix = pix.copy(rectangle[0], rectangle[1], rectangle[2], rectangle[3])
        print('save image')

        imgside = 'right'
        if rectangle[0] < 380:
            imgside = 'left'

        self.saveSplitImage(shape, pix, True, imgside, str_time)
        print('end - ')

    # add by yuan => save mask by rectangle shape
    def findContainRegion(self, shape, rectangle, str_time):
        pix = QPixmap(self.pixmap.width(), self.pixmap.height())
        #size = pix.size()

        pp = QPainter()
        pp.begin(pix)
        pp.setPen(QPen(QColor(0, 0, 0)))
        pp.setBrush(QBrush(QColor(255, 255, 255)))

        polygon = QPolygonF()

        for s in self.shapes:
            if s.drawRegion:
                polygon.clear()
                for point in s:
                    polygon.append(QPointF(point.x(), point.y()))
                pp.drawPolygon(polygon)
        pp.end()

        pix = pix.copy(rectangle[0], rectangle[1], rectangle[2], rectangle[3])

        imgside = 'right'
        if rectangle[0] < 380:
            imgside = 'left'

        self.saveSplitImage(shape, pix, True, imgside, str_time)

    # These two, along with a call to adjustSize are required for the
    # scroll area.
    def sizeHint(self):
        return self.minimumSizeHint()

    def minimumSizeHint(self):
        if self.pixmap:
            return self.scale * self.pixmap.size()
        return super(Canvas, self).minimumSizeHint()

    def wheelEvent(self, ev):
        qt_version = 4 if hasattr(ev, "delta") else 5
        if qt_version == 4:
            if ev.orientation() == Qt.Vertical:
                v_delta = ev.delta()
                h_delta = 0
            else:
                h_delta = ev.delta()
                v_delta = 0
        else:
            delta = ev.angleDelta()
            h_delta = delta.x()
            v_delta = delta.y()

        mods = ev.modifiers()
        if Qt.ControlModifier == int(mods) and v_delta:
            self.zoomRequest.emit(v_delta)
        else:
            v_delta and self.scrollRequest.emit(v_delta, Qt.Vertical)
            h_delta and self.scrollRequest.emit(h_delta, Qt.Horizontal)
        ev.accept()

    def keyPressEvent(self, ev):
        key = ev.key()
        if key == Qt.Key_Escape and self.current:
            print('ESC press')
            self.current = None
            self.drawingPolygon.emit(False)
            self.update()
        elif key == Qt.Key_Return and self.canCloseShape():
            self.finalise()
        elif key == Qt.Key_Left and self.selectedShape:
            self.moveOnePixel('Left')
        elif key == Qt.Key_Right and self.selectedShape:
            self.moveOnePixel('Right')
        elif key == Qt.Key_Up and self.selectedShape:
            self.moveOnePixel('Up')
        elif key == Qt.Key_Down and self.selectedShape:
            self.moveOnePixel('Down')

    def moveOnePixel(self, direction):
        # print(self.selectedShape.points)
        if direction == 'Left' and not self.moveOutOfBound(QPointF(-1.0, 0)):
            # print("move Left one pixel")
            self.selectedShape.points[0] += QPointF(-1.0, 0)
            self.selectedShape.points[1] += QPointF(-1.0, 0)
            self.selectedShape.points[2] += QPointF(-1.0, 0)
            self.selectedShape.points[3] += QPointF(-1.0, 0)
        elif direction == 'Right' and not self.moveOutOfBound(QPointF(1.0, 0)):
            # print("move Right one pixel")
            self.selectedShape.points[0] += QPointF(1.0, 0)
            self.selectedShape.points[1] += QPointF(1.0, 0)
            self.selectedShape.points[2] += QPointF(1.0, 0)
            self.selectedShape.points[3] += QPointF(1.0, 0)
        elif direction == 'Up' and not self.moveOutOfBound(QPointF(0, -1.0)):
            # print("move Up one pixel")
            self.selectedShape.points[0] += QPointF(0, -1.0)
            self.selectedShape.points[1] += QPointF(0, -1.0)
            self.selectedShape.points[2] += QPointF(0, -1.0)
            self.selectedShape.points[3] += QPointF(0, -1.0)
        elif direction == 'Down' and not self.moveOutOfBound(QPointF(0, 1.0)):
            # print("move Down one pixel")
            self.selectedShape.points[0] += QPointF(0, 1.0)
            self.selectedShape.points[1] += QPointF(0, 1.0)
            self.selectedShape.points[2] += QPointF(0, 1.0)
            self.selectedShape.points[3] += QPointF(0, 1.0)
        self.shapeMoved.emit()
        self.repaint()

    def moveOutOfBound(self, step):
        points = [
            p1 + p2 for p1, p2 in zip(self.selectedShape.points, [step] * 4)
        ]
        return True in map(self.outOfPixmap, points)

    def setLastLabel(self, text, line_color=None, fill_color=None):
        assert text
        self.shapes[-1].label = text
        if line_color:
            self.shapes[-1].line_color = line_color

        if fill_color:
            self.shapes[-1].fill_color = fill_color

        return self.shapes[-1]

    def undoLastLine(self):
        assert self.shapes
        self.current = self.shapes.pop()
        self.current.setOpen()
        self.line.points = [self.current[-1], self.current[0]]
        self.drawingPolygon.emit(True)

    def resetAllLines(self):
        assert self.shapes
        self.current = self.shapes.pop()
        self.current.setOpen()
        self.line.points = [self.current[-1], self.current[0]]
        self.drawingPolygon.emit(True)
        self.current = None
        self.drawingPolygon.emit(False)
        self.update()

    def loadPixmap(self, pixmap):
        self.pixmap = pixmap
        self.shapes = []
        self.repaint()

    def loadShapes(self, shapes):
        self.shapes = list(shapes)
        self.current = None
        self.repaint()

    def setShapeVisible(self, shape, value):
        self.visible[shape] = value
        self.repaint()

    def currentCursor(self):
        cursor = QApplication.overrideCursor()
        if cursor is not None:
            cursor = cursor.shape()
        return cursor

    def overrideCursor(self, cursor):
        self._cursor = cursor
        if self.currentCursor() is None:
            QApplication.setOverrideCursor(cursor)
        else:
            QApplication.changeOverrideCursor(cursor)

    def restoreCursor(self):
        QApplication.restoreOverrideCursor()

    def resetState(self):
        self.restoreCursor()
        self.pixmap = None
        self.update()

    def setDrawingShapeToSquare(self, status):
        self.drawSquare = status

    def setDrawRegion(self, status):
        self.drawRegion = status
コード例 #13
0
class Canvas(QWidget):
    zoomRequest = pyqtSignal(int)  #zoom需求的自定义信号
    scrollRequest = pyqtSignal(int, int)  #scroll的自定义信号
    newShape = pyqtSignal()
    selectionChanged = pyqtSignal(bool)
    shapeMoved = pyqtSignal()
    Point_Change = pyqtSignal(int, bool, list)
    Point_Vis_Change = pyqtSignal(int)
    Point_Error = pyqtSignal(bool, int)
    Parse_Error = pyqtSignal(bool, int)
    drawingPolygon = pyqtSignal(bool)
    CREATE, EDIT = range(2)
    RECT_SHAPE, POLYGON_SHAPE = range(2)  #矩形,多边形

    epsilon = 11.0

    def __init__(self, *args, **kwargs):
        super(Canvas,
              self).__init__(*args,
                             **kwargs)  #*args将输入的参数存放为元组,**kwargs将输入的参数存放为字典
        # Initialise local state.
        PP = Predefined_Points()
        self.shape_type = self.POLYGON_SHAPE
        self.image = QImage()
        self.brush_point = None
        self.task_mode = 3
        self.erase_mode = False
        self.current_brush_path = None
        self.mask_Image = None
        self.brush_color = QColor(255, 0, 0, 255)
        self.brush_size = 10
        self.brush = QPainter()
        self.mode = self.EDIT
        self.shapes = []
        self.current = None
        self.selectedShape = None  # save the selected shape here
        self.selectedShapeCopy = None
        self.lineColor = QColor(0, 0, 255)
        self.line = Shape(line_color=self.lineColor)
        self.prevPoint = QPointF()
        self.offsets = QPointF(), QPointF()
        self.scale = 1.2
        self.bg_image = QImage()
        self.visible = {}

        self._hideBackround = False
        self.hideBackround = False
        self.hShape = None
        self.hVertex = None
        self._painter = QPainter(self)
        self.font_size = 50
        self._cursor = CURSOR_DEFAULT
        # Menus:
        self.menus = (QMenu(), QMenu())
        # Set widget options.
        self.setMouseTracking(True)
        self.setFocusPolicy(Qt.WheelFocus)
        ##point
        self.point_point = None
        self.point_point_list = []
        self.point_point_list_tmp = []
        self.point_dex = None
        self.point_color = [
            QColor(r, g, b) for r in [0, 160, 120, 30, 40]
            for g in [0, 160, 120, 30, 90] for b in [0, 160, 120, 30]
        ]
        self.point_move = None
        self.point_path = None
        self.point_selecteditem = None
        self.point_delete = False
        self.point_modified = False
        self.point_shape = {}
        self.point_link = PP.define_points_links()
        self.point_num = max(np.array(self.point_link)[:, 1])
        # self.point_visible = {i:True for i in range(len(self.point_link))}
        self.point_visible = {i: True for i in range(self.point_num + 1)}
        self.point_cover = {i: 2 for i in range(self.point_num)}
        self.point_deletedid = []
        self.point_ids = []
        self.point_all_deleted = False
        self.point_save = []
        self.point_link_save = []
        self.point_rect = []
        self.point_rects = []
        self.point_rects_index = 0
        self.point_next_rect = False
        self.point_shape = {}
        self.point_changed = False
        self.point_cover_change = False
        self.point_cover_change_dex = 0
        self.point_cover_dict = {}
        #parse
        self.parse_rects = {}
        self.parse_rects_index = 0
        self.parse_rects_num = 0
        self.parse_shapes = {}

        self.xuxian = None
        self.start = False
        self.started = False

    def set_shape_type(self, type):
        if type == 0:
            self.shape_type = self.RECT_SHAPE
            self.line.set_shape_type(type)
            return True
        elif type == 1:
            self.shape_type = self.POLYGON_SHAPE
            self.line.set_shape_type(type)
            return True
        else:
            print("not support the shape type: " + str(type))
            return False

    def enterEvent(self, ev):
        self.overrideCursor(self._cursor)

    def get_mask_image(self):
        return self.mask_pixmap

    def leaveEvent(self, ev):
        self.restoreCursor()

    def focusOutEvent(self, ev):
        self.restoreCursor()

    def isVisible(self, shape):
        return self.visible.get(shape, True)

    def drawing(self):
        return self.mode == self.CREATE

    def editing(self):
        return self.mode == self.EDIT

    def setEditing(self, value=True):
        self.mode = self.EDIT if value else self.CREATE
        if not value:  # Create
            self.unHighlight()
            self.deSelectShape()

    def unHighlight(self):
        if self.hShape:
            self.hShape.highlightClear()
        self.hVertex = self.hShape = None

    def selectedVertex(self):
        return self.hVertex is not None

    def mouseMoveEvent(self, ev):  #这个函数是最重要的函数
        """Update line with last point and current coordinates."""
        pos = self.transformPos(ev.pos())  #点一下return一个pos
        self.restoreCursor()  #鼠标图标

        if not self.outOfPixmap(pos) and self.start and not self.started and (
                self.task_mode == 0 or self.task_mode == 5):
            self.xuxian = pos
            self.repaint()

        if self.task_mode == 3:
            self.brush_point = pos
            if Qt.LeftButton & ev.buttons():  #左鼠标点击
                if self.outOfPixmap(pos):  #超出图像范围
                    return
                if not self.current_brush_path:
                    self.current_brush_path = QPainterPath()
                    self.current_brush_path.moveTo(pos)
                else:
                    self.current_brush_path.lineTo(pos)
            self.repaint()
            return
        if self.task_mode == 4:
            self.point_point = pos
            if len(self.point_rects) == 1:
                self.draw_point_single_img(self.point_point_list, 0)
            else:
                if self.point_rects_index > 0:
                    self.draw_point_single_img(self.point_point_list,
                                               self.point_rects_index)
            #     else:
            #         self.draw_point_single_img(self.point_point_list, self.point_rects_index)

            for i, p in enumerate(self.point_point_list):
                if p and distance(p - pos) <= 5:
                    self.point_dex = i + 1  #这里加一的目的是为了绘制的时候 不会出现id=0的情况,因此self.point_dex最小为1 而不是0
                    self.Point_Change.emit(i, True, [])  # point change  发射信号
            self.repaint()
            return

        # Polygon drawing.
        if self.drawing():
            self.start = True
            self.overrideCursor(CURSOR_DRAW)
            if self.current:
                color = self.lineColor
                if self.outOfPixmap(pos):
                    # Don't allow the user to draw outside the pixmap.
                    # Project the point to the pixmap's edges.
                    pos = self.intersectionPoint(self.current[-1], pos)
                elif len(self.current) > 1 and self.closeEnough(
                        pos, self.current[0]):
                    # Attract line to starting point and colorise to alert the
                    # user:
                    pos = self.current[0]
                    color = self.current.line_color
                    self.overrideCursor(CURSOR_POINT)
                    self.current.highlightVertex(0, Shape.NEAR_VERTEX)
                #add xu xian
                #

                self.line[1] = pos
                self.line.line_color = color
                self.repaint()
                self.current.highlightClear()
            return
        # Polygon copy moving.
        if Qt.RightButton & ev.buttons():
            if self.selectedShapeCopy and self.prevPoint:
                self.overrideCursor(CURSOR_MOVE)
                self.boundedMoveShape(self.selectedShapeCopy, pos)
                self.repaint()
            elif self.selectedShape:
                self.selectedShapeCopy = self.selectedShape.copy()
                self.repaint()
            return

        # Polygon/Vertex moving.
        if Qt.LeftButton & ev.buttons():
            if self.selectedVertex():
                self.boundedMoveVertex(pos)
                self.shapeMoved.emit()
                self.repaint()
            elif self.selectedShape and self.prevPoint:
                self.overrideCursor(CURSOR_MOVE)
                self.boundedMoveShape(self.selectedShape, pos)
                self.shapeMoved.emit()
                self.repaint()
            return

        # Just hovering over the canvas, 2 posibilities:
        # - Highlight shapes
        # - Highlight vertex
        # Update shape/vertex fill and tooltip value accordingly.
        self.setToolTip("Image")
        for shape in reversed([s for s in self.shapes if self.isVisible(s)]):
            # Look for a nearby vertex to highlight. If that fails,
            # check if we happen to be inside a shape.
            index = shape.nearestVertex(pos, self.epsilon)
            if index is not None:
                if self.selectedVertex():
                    self.hShape.highlightClear()
                self.hVertex, self.hShape = index, shape
                shape.highlightVertex(index, shape.MOVE_VERTEX)
                self.overrideCursor(CURSOR_POINT)
                self.setToolTip("Click & drag to move point")  #移动点
                self.setStatusTip(self.toolTip())
                self.update()
                break
            elif shape.containsPoint(pos):
                if self.selectedVertex():
                    self.hShape.highlightClear()
                self.hVertex, self.hShape = None, shape
                self.setToolTip("Click & drag to move shape '%s'" %
                                shape.label)
                self.setStatusTip(self.toolTip())
                self.overrideCursor(CURSOR_GRAB)
                self.update()
                break
        else:  # Nothing found, clear highlights, reset state.
            if self.hShape:
                self.hShape.highlightClear()
                self.update()
            self.hVertex, self.hShape = None, None

    def mousePressEvent(self, ev):
        mods = ev.modifiers()
        pos = self.transformPos(ev.pos())
        task_mode = self.task_mode
        if self.start == True and task_mode == 0:
            self.started = True
            self.xuxian = None

        if ev.button() == Qt.LeftButton:
            if self.drawing():  #
                if self.shape_type == self.POLYGON_SHAPE and self.current:
                    self.current.addPoint(self.line[1])
                    self.line[0] = self.current[-1]
                    if self.current.isClosed():
                        self.finalise()

                elif self.shape_type == self.RECT_SHAPE and self.current and self.current.reachMaxPoints(
                ) is False:

                    if self.task_mode == 5 and len(self.shapes) >= 3:
                        self.current = None
                        self.xuxian = None
                        self.start = False
                        self.started = False
                        QMessageBox.about(
                            self, "About",
                            self.tr('<p><b>%s</b></p>%s <p>%s</p>' %
                                    ('注意标注已经为', str(3) + '个', '只可修改')))
                        return

                        # self.repaint()
                    initPos = self.current[0]
                    minX = initPos.x()
                    minY = initPos.y()
                    targetPos = self.line[1]
                    maxX = targetPos.x()
                    maxY = targetPos.y()
                    self.current.addPoint(QPointF(minX, maxY))
                    self.current.addPoint(targetPos)
                    self.current.addPoint(QPointF(maxX, minY))
                    self.current.addPoint(initPos)
                    self.line[0] = self.current[-1]
                    if self.current.isClosed():
                        self.finalise()

                elif not self.outOfPixmap(pos):
                    self.current = Shape(shape_type=self.shape_type)
                    self.current.addPoint(pos)
                    self.line.points = [pos, pos]
                    self.setHiding()
                    self.drawingPolygon.emit(True)
                    self.update()

            elif self.task_mode == 4:
                self.point_changed = True

                if self.point_modified:
                    self.point_point_list[self.point_modified - 1] = pos
                    self.point_modified = False
                    self.repaint()
                else:
                    distances = []
                    self.point_point_list.append(pos)
                    # if not len(self.point_point_list)>self.point_num :
                    if Qt.LeftButton & ev.buttons():  # 左鼠标点击
                        if self.outOfPixmap(pos):  # 超出图像范围
                            return
                        elif len(self.point_point_list) > 1:
                            if self.point_point_list[
                                    -1] and self.point_point_list[-2]:
                                if distance(self.point_point_list[-1] -
                                            self.point_point_list[-2]) <= 5:
                                    self.point_move = True
                                    del self.point_point_list[-1]
                            for i, p in enumerate(self.point_point_list[:-2]):
                                if p:
                                    distances.append(distance(p - pos))
                            distances.sort()
                            print('distances')
                            if len(distances) >= 1:
                                if distances[0] <= 5:  #注意 一次只能删除一个点
                                    if distances[0] <= 5:
                                        self.point_move = True  #这里给出可移动的指令
                                    del self.point_point_list[-1]
                        if self.point_move:
                            self.point_point = pos
                    self.overrideCursor(Qt.CrossCursor)
                    self.repaint()
                    return
            else:
                self.selectShapePoint(pos)
                self.prevPoint = pos
                self.repaint()

        elif ev.button() == Qt.RightButton and self.editing():
            self.selectShapePoint(pos)
            self.prevPoint = pos
            self.repaint()  #这里只是传点吧

    def mouseReleaseEvent(self, ev):
        if ev.button() == Qt.RightButton:
            menu = self.menus[bool(self.selectedShapeCopy)]
            self.restoreCursor()
            if not menu.exec_(self.mapToGlobal(ev.pos()))\
               and self.selectedShapeCopy:
                # Cancel the move by deleting the shadow copy.
                self.selectedShapeCopy = None
                self.repaint()
        elif ev.button() == Qt.LeftButton and self.selectedShape:
            self.overrideCursor(CURSOR_GRAB)
        elif ev.button(
        ) == Qt.LeftButton and self.task_mode == 3 and self.current_brush_path:
            self.current_brush_path = None
        elif ev.button(
        ) == Qt.LeftButton and self.task_mode == 4 and self.point_move:  #删除点
            # del self.point_point_list[self.point_dex]
            self.point_changed = True
            self.point_point_list[self.point_dex - 1] = self.point_point
            self.point_move = False
            self.repaint()

    def endMove(self, copy=False):
        assert self.selectedShape and self.selectedShapeCopy
        shape = self.selectedShapeCopy
        print("end move")
        if copy:
            self.shapes.append(shape)
            self.selectedShape.selected = False
            self.selectedShape = shape
            self.repaint()
        else:
            shape.label = self.selectedShape.label
            shape.ignore = self.selectedShape.ignore
            self.deleteSelected()
            self.shapes.append(shape)
        self.selectedShapeCopy = None

    def hideBackroundShapes(self, value):
        self.hideBackround = value
        if self.selectedShape:
            # Only hide other shapes if there is a current selection.
            # Otherwise the user will not be able to select a shape.
            self.setHiding(True)
            self.repaint()

    def setHiding(self, enable=True):
        self._hideBackround = self.hideBackround if enable else False

    def canCloseShape(self):
        return self.drawing() and self.current and len(self.current) > 2

    def mouseDoubleClickEvent(self, ev):
        # We need at least 4 points here, since the mousePress handler
        # adds an extra one before this handler is called.
        if self.canCloseShape() and len(self.current) > 3:
            self.current.popPoint()
            self.finalise()

    def selectShape(self, shape):
        self.deSelectShape()
        shape.selected = True
        self.selectedShape = shape
        self.setHiding()
        self.selectionChanged.emit(True)
        self.update()

    def selectShapePoint(self, point):
        """Select the first shape created which contains this point."""
        self.deSelectShape()
        if self.selectedVertex():  # A vertex is marked for selection.
            index, shape = self.hVertex, self.hShape
            shape.highlightVertex(index, shape.MOVE_VERTEX)
            return
        for shape in reversed(self.shapes):
            if self.isVisible(shape) and shape.containsPoint(point):
                shape.selected = True
                self.selectedShape = shape
                self.calculateOffsets(shape, point)
                self.setHiding()
                self.selectionChanged.emit(True)
                return

    def calculateOffsets(self, shape, point):
        rect = shape.boundingRect()
        x1 = rect.x() - point.x()
        y1 = rect.y() - point.y()
        x2 = (rect.x() + rect.width()) - point.x()
        y2 = (rect.y() + rect.height()) - point.y()
        self.offsets = QPointF(x1, y1), QPointF(x2, y2)

    def boundedMoveVertex(self, pos):

        index, shape = self.hVertex, self.hShape
        point = shape[index]

        if self.outOfPixmap(pos):
            pos = self.intersectionPoint(point, pos)

        shiftPos = pos - point  #point 是之前画好的点 pos为当前的点
        shape.moveVertexBy(index, shiftPos)

        if self.shape_type == self.RECT_SHAPE:
            lindex = (index + 1) % 4
            rindex = (index + 3) % 4
            lshift = None
            rshift = None
            if index % 2 == 0:
                lshift = QPointF(shiftPos.x(), 0)
                rshift = QPointF(0, shiftPos.y())
            else:
                rshift = QPointF(shiftPos.x(), 0)
                lshift = QPointF(0, shiftPos.y())
            shape.moveVertexBy(rindex, rshift)
            shape.moveVertexBy(lindex, lshift)

    def boundedMoveShape(self, shape, pos):
        if self.outOfPixmap(pos):
            return False  # No need to move
        o1 = pos + self.offsets[0]
        if self.outOfPixmap(o1):
            pos -= QPointF(min(0, o1.x()), min(0, o1.y()))
        o2 = pos + self.offsets[1]
        if self.outOfPixmap(o2):
            pos += QPointF(min(0,
                               self.bg_image.width() - o2.x()),
                           min(0,
                               self.bg_image.height() - o2.y()))
        # The next line tracks the new position of the cursor
        # relative to the shape, but also results in making it
        # a bit "shaky" when nearing the border and allows it to
        # go outside of the shape's area for some reason. XXX
        #self.calculateOffsets(self.selectedShape, pos)
        dp = pos - self.prevPoint
        if dp:
            shape.moveBy(dp)
            self.prevPoint = pos
            return True
        return False

    def deSelectShape(self):
        if self.selectedShape:
            self.selectedShape.selected = False
            self.selectedShape = None
            self.setHiding(False)
            self.selectionChanged.emit(False)
            self.update()

    def deleteSelected(self):
        if self.selectedShape:
            shape = self.selectedShape
            self.shapes.remove(self.selectedShape)
            self.selectedShape = None
            self.update()
            return shape

    def copySelectedShape(self):
        if self.selectedShape:
            shape = self.selectedShape.copy()
            self.deSelectShape()
            self.shapes.append(shape)
            shape.selected = True
            self.selectedShape = shape
            self.boundedShiftShape(shape)
            return shape

    def boundedShiftShape(self, shape):
        # Try to move in one direction, and if it fails in another.
        # Give up if both fail.
        point = shape[0]
        offset = QPointF(2.0, 2.0)
        self.calculateOffsets(shape, point)
        self.prevPoint = point
        if not self.boundedMoveShape(shape, point - offset):
            self.boundedMoveShape(shape, point + offset)

    def paintEvent(self, event):  #所有的绘制操作都在paintEvent中完成
        if not self.bg_image:
            return super(Canvas, self).paintEvent(event)

        p = self._painter
        p.begin(self)  #都在begin()和end()间完成
        p.setFont(QFont('Times', self.font_size, QFont.Bold))
        p.setRenderHint(QPainter.Antialiasing)
        p.setRenderHint(QPainter.HighQualityAntialiasing)
        p.setRenderHint(QPainter.SmoothPixmapTransform)
        p.scale(self.scale, self.scale)
        p.translate(self.offsetToCenter())

        p.drawImage(0, 0, self.bg_image)
        if self.task_mode == 3:
            p.setOpacity(0.3)
            p.drawImage(0, 0, self.mask_pixmap)
            if self.brush_point:
                p.drawEllipse(self.brush_point, self.brush_size / 2,
                              self.brush_size / 2)  #椭圆
            if self.current_brush_path:
                if self.mask_pixmap.isNull():
                    self.mask_pixmap = QImage(self.bg_image.size(),
                                              QImage.Format_ARGB32)
                    self.mask_pixmap.fill(QColor(255, 255, 255, 0))
                self.brush.begin(self.mask_pixmap)
                brush_pen = QPen()
                self.brush.setCompositionMode(QPainter.CompositionMode_Source)
                brush_pen.setColor(self.brush_color)
                brush_pen.setWidth(self.brush_size)
                brush_pen.setCapStyle(Qt.RoundCap)
                brush_pen.setJoinStyle(Qt.RoundJoin)
                self.brush.setPen(brush_pen)
                self.brush.drawPath(self.current_brush_path)
                self.brush.end()
        if self.task_mode == 4:
            self.point_save = self.point_point_list
            w, h = self.bg_image.width(), self.bg_image.height()
            size = 4 * min(w // 200, h // 200)
            if self.point_delete:
                print('delete point')
                self.point_point_list[self.point_delete - 1] = None
                self.point_delete = False
            if self.point_rects:
                if self.point_rects_index == len(self.point_rects):
                    self.point_rects_index = 0
                x, y, w, h = self.point_rects[self.point_rects_index]
                p.setPen(QColor(255, 0, 0))
                p.drawRect(x, y, w, h)

            if len(self.point_point_list) > 0:
                for i, point in enumerate(self.point_point_list):
                    if point and self.point_visible[i]:
                        p.setBrush(self.point_color[i])
                        p.setPen(self.point_color[i])
                        p.drawEllipse(float(point.x() - size // 2),
                                      float(point.y() - size // 2), size, size)
                        if i in self.point_cover:
                            if self.point_cover[i] == 1:
                                p.setPen(QColor(255, 0, 0))
                                p.setFont(QFont('SimSun', size))
                                p.drawText(
                                    QRect(point.x(), point.y(), size, size),
                                    Qt.AlignLeft | Qt.AlignVCenter, '1')

            if self.point_dex:  #注意这里剔除了0 0是none  高亮标注的操作  包括move
                p.setPen(QColor(255, 0, 0))
                p.setBrush(QColor(0, 0, 0, 0))
                try:
                    if self.point_point_list[self.point_dex - 1]:
                        p.drawRect(
                            float(self.point_point_list[self.point_dex -
                                                        1].x() - size // 2),
                            float(self.point_point_list[self.point_dex -
                                                        1].y() - size // 2),
                            size, size)
                    if self.point_move:  #移动
                        p.setPen(QColor(255, 0, 0))
                        p.drawLine(self.point_point_list[self.point_dex - 1],
                                   self.point_point)
                    if len(self.point_point_list) > 1:  #画线
                        for i in self.point_link:  # 每次都要遍历所有的Link
                            if i[0] - 1 in range(
                                    len(self.point_point_list
                                        )) and i[1] - 1 in range(
                                            len(self.point_point_list)):
                                pen = QPen(Qt.red, 2)
                                p.setPen(pen)
                                if i not in self.point_link_save:
                                    self.point_link_save.append(i)
                                if self.point_point_list[
                                        i[0] -
                                        1] and self.point_point_list[i[1] - 1]:
                                    p.drawLine(self.point_point_list[i[0] - 1],
                                               self.point_point_list[i[1] - 1])
                except IndexError:
                    pass
        if self.task_mode == 5:  #这里融合了point模型
            # 这个是用来画instances的框
            if self.parse_rects:
                p.setPen(QColor(255, 0, 0))

                x, y, w, h = self.parse_rects[self.parse_rects_index]
                p.setPen(QColor(255, 0, 0))
                p.drawRect(x, y, w, h)
        if self.task_mode == 0 or self.task_mode == 5:
            #这个部分用来添加框的虚线
            w, h = self.bg_image.width(), self.bg_image.height()
            size = min(w // 200, h // 200)
            pen = QPen(Qt.green, size, Qt.SolidLine)
            pen.setStyle(Qt.DashDotLine)
            p.setPen(pen)
            if self.xuxian:
                p.drawLine(self.xuxian.x(), self.xuxian.y(), self.xuxian.x(),
                           self.xuxian.y() + h - 20)
                p.drawLine(self.xuxian.x(), self.xuxian.y(),
                           self.xuxian.x() + w - 10, self.xuxian.y())
        Shape.scale = self.scale
        for shape in self.shapes:
            if shape.fill_color:
                shape.fill = True
                shape.paint(p)  ###这里是其他模式的调用shape中的Paint函数
            elif (shape.selected
                  or not self._hideBackround) and self.isVisible(shape):
                shape.fill = shape.selected or shape == self.hShape
                shape.paint(p)
        if self.current:
            self.current.paint(p)
            self.line.paint(p)
        if self.selectedShapeCopy:
            self.selectedShapeCopy.paint(p)
        # Paint rect
        if self.current is not None and len(self.line) == 2:
            leftTop = self.line[0]
            rightBottom = self.line[1]
            rectWidth = rightBottom.x() - leftTop.x()
            rectHeight = rightBottom.y() - leftTop.y()
            color = QColor(0, 220, 0)
            p.setPen(color)
            brush = QBrush(Qt.BDiagPattern)
            p.setBrush(brush)
            if self.shape_type == self.RECT_SHAPE:
                p.drawRect(leftTop.x(), leftTop.y(), rectWidth, rectHeight)
        p.end()  #

    # parse mode
    def parse_next_rect(self):
        print("canvas_parse_shapes", self.parse_shapes)
        #这个函数的主要作用是下一个框的保存值
        #先保存每个object的相应任务的值
        if self.parse_rects_index != 0:
            #这里的parse_rects_index都是表示的是下一个框的index
            self.parse_shapes[self.parse_rects_index - 1] = self.shapes
        else:
            self.parse_shapes[self.parse_rects_num - 1] = self.shapes
        #再看看能不能赋值,不能赋值就直接给空值就可以
        if self.parse_rects_index in self.parse_shapes:
            self.shapes = self.parse_shapes[self.parse_rects_index]
        else:
            self.shapes = []
        #最后重新绘制即可,这里是一定要加上的,这里加了上面的就不用加了。
        self.repaint()

    def parse_new_bbox(self):
        #这个函数主要是对新的操作所产生的shape做存值的处理
        #注意新的操作至少要写分为三个操作:1、新增2、删除3、移动等
        if self.parse_rects_index != 0:
            # 这里的parse_rects_index都是表示的是下个框的index
            self.parse_shapes[self.parse_rects_index] = self.shapes
        elif len(self.parse_shapes) > 0 and not self.parse_rects_index == 0:
            self.parse_shapes[self.parse_rects_num - 1] = self.shapes
        # 0的特殊性
        else:
            self.parse_shapes[0] = self.shapes

    def parse_clear(self):
        #这个函数的所有的定义的初始化变量都要重新初始化
        #在主函数里写在nextimg preimg
        self.parse_shapes = {}
        self.shapes = []
        self.repaint()
        self.parse_rects_index = 0

    # point mode
    def deletepoint(self, i):
        self.point_delete = i

    def point_finish(self):
        del self.point_point_list[-1]
        self.point_dex = self.point_num - 1
        QMessageBox.about(
            self, "About",
            self.tr('<p><b>%s</b></p>%s <p>%s</p>' %
                    ('注意标注已经为', str(self.point_num + 1) + '个', '只可修改')))
        self.repaint()

    def point_change(self, id, visible):
        # self.point_changed=True
        if id:
            if visible:
                self.point_visible[id] = True
                if id <= len(self.point_point_list):  #当前的点在point list 并且可视化
                    self.point_dex = id
                    self.repaint()
                else:  #当前的点不在point list 但是想可视
                    self.point_dex = self.point_dex
                    # self.Point_Error.emit(True,id)
            else:  #不可见  那个这个点的值就是None
                if id <= len(self.point_point_list):  #当前的点在point list 并且不可视
                    pass  #
                    # print('v1')
                    # self.point_point_list[id] =self.point_point
                    # self.point_visible[id]=True
                    # self.repaint()
                else:
                    try:
                        self.point_point_list[id] = None
                        self.point_visible[id] = False
                        self.repaint()
                        print('pointlink delete')
                    except IndexError:
                        pass
        else:  #针对 全选
            pass

    def point_modify(self, i):
        if i in range(len(self.point_point_list)):
            print('modified', i)
            self.point_modified = i + 1

    def point_all_delete(self):
        self.point_changed = True
        self.point_all_deleted = True
        l = [i for i in range(len(self.point_point_list))]
        # self.point_cover = {i: 2 for i in range(len(self.point_link)-2)}
        self.point_point_list = []
        self.point_dex = 0
        self.Point_Change.emit(0, True, l)  #调到第一个参数
        # self.point_shape[self.point_rects_index]=[]
        self.repaint()

    def point_load_point_shape(self, point_list):
        point = []
        print('canvas point_shape', point_list)
        for i, ll in enumerate(point_list):
            print("ll")
            if not ll:
                print("list", ll)
                self.point_shape[i] = []
                self.point_dex = 1
                self.repaint()
            else:
                for p in ll:
                    point.append(QPointF(int(p[0]), int(p[1])))
                self.point_shape[i] = point
                point = []
            if len(self.point_shape[0]) > 0:
                self.point_point_list = self.point_shape[
                    self.point_rects_index]

            self.point_dex = len(self.point_point_list)
            self.repaint()

    def point_load(self, point_list):  #transform list to qpointf list
        point = []
        rect_dex = 0
        if not point_list:
            self.point_point_list = []
            self.point_dex = 1
            self.repaint()
        else:
            for p in point_list:
                point.append(QPointF(int(p[0]), int(p[1])))
            self.point_point_list = point
            self.point_shape[self.point_rects_index] = self.point_point_list
            self.point_dex = len(self.point_point_list)
            self.repaint()

    def point_rect_points(self, x, y, w, h):
        self.point_rects.append([x, y, w, h])

    # next rect
    def draw_rects(self, rects):
        #主要对xml分析后进来的rects处理
        self.parse_rects = rects
        self.parse_rects_num = len(self.parse_rects)
        self.repaint()

    def draw_point_single_img(self, point_point_list, i):
        self.point_shape[i] = self.point_point_list

    def draw_next_rect(self):
        point_point_list = []
        if self.point_changed:
            if len(self.point_shape) <= len(self.point_rects):
                self.point_shape[
                    self.point_rects_index] = self.point_point_list
        self.point_rects_index += 1
        if self.point_rects_index == len(self.point_rects):
            self.point_rects_index = 0
        self.point_point_list = []  # .clear 清内存 最好不要这样
        self.point_dex = 0
        if self.point_rects_index in self.point_shape:
            point_point_list = self.point_shape[self.point_rects_index]
        else:
            pass
        if point_point_list and isinstance(point_point_list[0],
                                           QPointF):  #绘制过程
            self.point_point_list = point_point_list
            self.point_dex = len(self.point_point_list)
            self.repaint()
        else:  #加载过程
            print("point_point_list", point_point_list)
            self.point_load(point_point_list)

    ##############
    def transformPos(self, point):
        """Convert from widget-logical coordinates to painter-logical coordinates."""
        return point / self.scale - self.offsetToCenter()

    def offsetToCenter(self):
        s = self.scale
        area = super(Canvas, self).size()
        if self.bg_image:
            w, h = self.bg_image.width() * s, self.bg_image.height() * s
        else:
            w, h = 100, 100
        aw, ah = area.width(), area.height()
        x = (aw - w) / (2 * s) if aw > w else 0
        y = (ah - h) / (2 * s) if ah > h else 0
        return QPointF(x, y)

    def outOfPixmap(self, p):
        if self.bg_image:
            w, h = self.bg_image.width(), self.bg_image.height()
            return not (0 <= p.x() <= w and 0 <= p.y() <= h)

    def finalise(self):  #完成
        assert self.current
        self.current.close()
        self.shapes.append(self.current)
        self.current = None
        self.setHiding(False)
        self.newShape.emit()
        self.update()
        self.xuxian = None
        self.start = False
        self.started = False

    def closeEnough(self, p1, p2):
        #d = distance(p1 - p2)
        #m = (p1-p2).manhattanLength()
        # print "d %.2f, m %d, %.2f" % (d, m, d - m)
        return distance(p1 - p2) < self.epsilon

    def intersectionPoint(self, p1, p2):
        # Cycle through each image edge in clockwise fashion,
        # and find the one intersecting the current line segment.
        # http://paulbourke.net/geometry/lineline2d/
        size = self.bg_image.size()
        points = [(0, 0), (size.width(), 0), (size.width(), size.height()),
                  (0, size.height())]
        x1, y1 = p1.x(), p1.y()
        x2, y2 = p2.x(), p2.y()
        try:
            d, i, (x,
                   y) = min(self.intersectingEdges((x1, y1), (x2, y2), points))
        except:
            pass
        x3, y3 = points[i]
        x4, y4 = points[(i + 1) % 4]
        if (x, y) == (x1, y1):
            # Handle cases where previous point is on one of the edges.
            if x3 == x4:
                return QPointF(x3, min(max(0, y2), max(y3, y4)))
            else:  # y3 == y4
                return QPointF(min(max(0, x2), max(x3, x4)), y3)
        return QPointF(x, y)

    def intersectingEdges(self, xxx_todo_changeme, xxx_todo_changeme1, points):
        """For each edge formed by `points', yield the intersection
        with the line segment `(x1,y1) - (x2,y2)`, if it exists.
        Also return the distance of `(x2,y2)' to the middle of the
        edge along with its index, so that the one closest can be chosen."""
        (x1, y1) = xxx_todo_changeme
        (x2, y2) = xxx_todo_changeme1
        for i in range(4):
            x3, y3 = points[i]
            x4, y4 = points[(i + 1) % 4]
            denom = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1)
            nua = (x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)
            nub = (x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)
            if denom == 0:
                # This covers two cases:
                #   nua == nub == 0: Coincident
                #   otherwise: Parallel
                continue
            ua, ub = nua / denom, nub / denom
            if 0 <= ua <= 1 and 0 <= ub <= 1:
                x = x1 + ua * (x2 - x1)
                y = y1 + ua * (y2 - y1)
                m = QPointF((x3 + x4) / 2, (y3 + y4) / 2)
                d = distance(m - QPointF(x2, y2))
                yield d, i, (x, y)

    # These two, along with a call to adjustSize are required for the
    # scroll area.
    def sizeHint(self):
        return self.minimumSizeHint()

    def minimumSizeHint(self):
        if self.bg_image:
            return self.scale * self.bg_image.size()
        return super(Canvas, self).minimumSizeHint()

    def wheelEvent(self, ev):
        mods = ev.modifiers()
        if Qt.ControlModifier == int(mods):  # ctrl键
            self.zoomRequest.emit(ev.angleDelta().y())
        else:
            ev.angleDelta().y() and self.scrollRequest.emit(
                ev.angleDelta().y(), Qt.Horizontal if
                (Qt.ShiftModifier == int(mods)) else Qt.Vertical)
            ev.angleDelta().x() and self.scrollRequest.emit(
                ev.angleDelta().x(), Qt.Horizontal)
        ev.accept()

    def keyPressEvent(self, ev):  #键盘事件
        key = ev.key()
        if self.task_mode == 4:
            if key == Qt.Key_0:
                self.point_point_list.append(None)
                self.point_visible[self.point_dex] = False
                self.Point_Change.emit(
                    self.point_dex + 1, False, []
                )  #当前点的下一个点,自动移动到当前点的下一个点   第一个参数为选择的item的id,第二个参数为非选择状态,第三个参数为全删除的当前的list
                self.point_dex += 1  #point_dex 从1开始表示当前点的dex (包括指向点和下一个即将绘制的点)
            elif key == Qt.Key_1:  #在这里对全局变量进行键盘判断,不需要self.repaint()
                self.point_cover[self.point_dex - 1] = 1
                self.point_cover_change = True
            elif key == Qt.Key_2:
                if self.point_cover[self.point_dex - 1] == 1:
                    self.point_cover[self.point_dex - 1] = 2
                self.point_cover_change = True

        if key == Qt.Key_Escape and self.current:
            print('ESC press')
            self.current = None
            #虚线的初始化
            self.xuxian = None
            self.start = False
            self.started = False
            ###########
            self.drawingPolygon.emit(False)
            self.update()
        elif key == Qt.Key_Return and self.canCloseShape():
            self.finalise()

    def setLastLabel(self, text):
        assert text
        self.shapes[-1].label = text
        return self.shapes[-1]

    def setSelectedShape(self, ignore):
        """
        :param ignore:
        :return: you can set everything from the label main function
        """
        if self.selectedShape:
            self.selectedShape.ignore = ignore
            self.repaint()

    def undoLastLine(self):
        assert self.shapes
        self.current = self.shapes.pop()
        self.current.setOpen()
        self.line.points = [self.current[-1], self.current[0]]
        self.drawingPolygon.emit(True)

    def resetAllLines(self):
        assert self.shapes
        self.current = self.shapes.pop()
        self.current.setOpen()
        self.line.points = [self.current[-1], self.current[0]]
        self.drawingPolygon.emit(True)
        self.current = None
        self.drawingPolygon.emit(False)
        self.update()

    def loadMaskmap(self, mask):
        self.mask_pixmap = mask
        self.repaint()

    def loadPixmap(self, pixmap):
        self.bg_image = pixmap
        self.shapes = []
        self.mask_pixmap = QImage(self.bg_image.size(), QImage.Format_ARGB32)
        self.mask_pixmap.fill(QColor(255, 255, 255, 0))
        self.repaint()

    def loadShapes(self, shapes):
        self.shapes = list(shapes)

        self.shape_type = shapes[0].get_shape_type()
        self.current = None
        self.repaint()

    def setShapeVisible(self, shape, value):
        self.visible[shape] = value
        self.repaint()

    def overrideCursor(self, cursor):
        self.restoreCursor()
        self._cursor = cursor
        QApplication.setOverrideCursor(cursor)

    def restoreCursor(self):
        QApplication.restoreOverrideCursor()

    def resetState(self):
        self.restoreCursor()
        # self.bg_image = None
        self.update()

    def update_image(self, img):
        self.bg_image = img
        self.repaint()
コード例 #14
0
class Canvas(QWidget):
    zoomRequest = pyqtSignal(int)
    scrollRequest = pyqtSignal(int, int)
    newShape = pyqtSignal()
    selectionChanged = pyqtSignal(bool)
    shapeMoved = pyqtSignal()
    drawingPolygon = pyqtSignal(bool)
    saveFileSignal = pyqtSignal()

    CREATE, EDIT = list(range(2))

    epsilon = 11.0

    def __init__(self, *args, **kwargs):
        super(Canvas, self).__init__(*args, **kwargs)
        # Initialise local state.
        self.mode = self.EDIT
        self.contourMode = False
        self.shapes = []
        self.current = None
        self.selectedShape = None  # save the selected shape here
        self.selectedShapeCopy = None
        self.globalMousePos = None
        self.memIdx = list()
        self.currentIdx = 0  # only needed for toggling between multiple overlaying cells
        # self.drawingLineColor = QColor(0, 0, 255)
        # self.drawingRectColor = QColor(0, 0, 255)
        self.drawingLineColor = QColor(255, 255, 255, 255)
        self.drawingRectColor = QColor(255, 255, 255, 255)
        self.drawingContourColor = QColor(255, 0, 0)
        self.line = Shape(line_color=self.drawingLineColor)
        self.prevPoint = QPointF()
        self.offsets = QPointF(), QPointF()
        self.scale = 1.0
        self.pixmap = QPixmap()
        self.visible = {}
        self._hideBackround = False
        self.hideBackround = False
        self.hShape = None
        self.hVertex = None
        self._painter = QPainter()
        self._cursor = CURSOR_DEFAULT
        # Menus:
        self.menus = (QMenu(), QMenu())
        # Set widget options.
        self.setMouseTracking(True)
        self.setFocusPolicy(Qt.WheelFocus)
        self.verified = False
        self.showContourOverlay = False

    def setDrawingColor(self, qColor):
        self.drawingLineColor = qColor
        self.drawingRectColor = qColor

    def enterEvent(self, ev):
        self.overrideCursor(self._cursor)

    def leaveEvent(self, ev):
        self.restoreCursor()

    def focusOutEvent(self, ev):
        self.restoreCursor()

    def isVisible(self, shape):
        return self.visible.get(shape, True)

    def drawing(self):
        return self.mode == self.CREATE

    def editing(self):
        return self.mode == self.EDIT

    def setEditing(self, value=True):
        self.mode = self.EDIT if value else self.CREATE
        if not value:  # Create
            self.unHighlight()
            self.deSelectShape()
        self.prevPoint = QPointF()
        self.repaint()

    def unHighlight(self):
        if self.hShape:
            self.hShape.highlightClear()
        self.hVertex = self.hShape = None

    def selectedVertex(self):
        return self.hVertex is not None

    def mouseMoveEvent(self, ev):
        """Update line with last point and current coordinates."""
        pos = self.transformPos(ev.pos())
        self.globalMousePos = pos
        # Update coordinates in status bar if image is opened
        window = self.parent().window()
        if window.filePath is not None:
            self.parent().window().labelCoordinates.setText(
                'X: %d; Y: %d' % (pos.x(), pos.y()))

        # Polygon drawing.
        if self.drawing():
            self.overrideCursor(CURSOR_DRAW)
            if self.current:
                color = self.drawingLineColor
                if self.outOfPixmap(pos):
                    # Don't allow the user to draw outside the pixmap.
                    # Project the point to the pixmap's edges.
                    pos = self.intersectionPoint(self.current[-1], pos)
                elif len(self.current) > 1 and self.closeEnough(pos, self.current[0]):
                    # Attract line to starting point and colorise to alert the
                    # user:
                    pos = self.current[0]
                    color = self.current.line_color
                    self.overrideCursor(CURSOR_POINT)
                    self.current.highlightVertex(0, Shape.NEAR_VERTEX)
                self.line[1] = pos
                self.line.line_color = color
                self.prevPoint = QPointF()
                self.current.highlightClear()
            else:
                self.prevPoint = pos
            self.repaint()
            return

        # Polygon copy moving.
        if not self.contourMode:
            if Qt.RightButton & ev.buttons():
                if self.selectedShapeCopy and self.prevPoint:
                    self.overrideCursor(CURSOR_MOVE)
                    self.boundedMoveShape(self.selectedShapeCopy, pos)
                    self.repaint()
                elif self.selectedShape:
                    self.selectedShapeCopy = self.selectedShape.copy()
                    self.repaint()
                return

        # Polygon/Vertex moving.
        if not self.contourMode:
            if Qt.LeftButton & ev.buttons():
                if self.selectedVertex():
                    self.boundedMoveVertex(pos)
                    self.shapeMoved.emit()
                    self.repaint()
                elif self.selectedShape and self.prevPoint:
                    self.overrideCursor(CURSOR_MOVE)
                    self.boundedMoveShape(self.selectedShape, pos)
                    self.shapeMoved.emit()
                    # self.selectedShape.contour_points = list()
                    self.repaint()
                return
        
        if self.contourMode and self.selectedShape:
            if Qt.LeftButton & ev.buttons():
                self.overrideCursor(CURSOR_MOVE)
                if self.cntOldidx is not None:
                    shapeOrigin = self.selectedShape.points[0]
                    cntOld = self.selectedShape.contour_points[self.cntOldidx]
                    cntNew = int(pos.y() - shapeOrigin.y()), int(pos.x() - shapeOrigin.x())
                    delta_y, delta_x = cntNew[0] - cntOld[0], cntNew[1] - cntOld[1]
                    # print(delta_y, delta_x)
                    mods = ev.modifiers()
                    if Qt.ControlModifier == int(mods):
                        for i, p in enumerate(self.selectedShape.contour_points):
                            self.selectedShape.contour_points[i] = (p[0] + delta_y, p[1] + delta_x)
                    else:
                        for i in range(self.cntOldidx-3, self.cntOldidx+4, 1):
                            i %= len(self.selectedShape.contour_points)
                            p = self.selectedShape.contour_points[i]
                            d = np.sqrt((p[0]-cntOld[0])**2 + (p[1]-cntOld[1])**2)
                            f = 1/(1 + 1.8 * d) 
                            self.selectedShape.contour_points[i] = (p[0] + delta_y * f, p[1] + delta_x * f)
                        # print(p, f, self.selectedShape.contour_points[i])
            self.repaint()
            return
        # Just hovering over the canvas, 2 posibilities:
        # - Highlight shapes
        # - Highlight vertex
        # Update shape/vertex fill and tooltip value accordingly.
        # self.setToolTip('')
        for shape in reversed([s for s in self.shapes if self.isVisible(s)]):
            # Look for a nearby vertex to highlight. If that fails,
            # check if we happen to be inside a shape.
            index = shape.nearestVertex(pos, self.epsilon)
            if index is not None:
                if self.selectedVertex():
                    self.hShape.highlightClear()
                self.hVertex, self.hShape = index, shape
                shape.highlightVertex(index, shape.MOVE_VERTEX)
                self.overrideCursor(CURSOR_POINT)
                # self.setToolTip("Click & drag to move point")
                # self.setStatusTip(self.toolTip())
                self.update()
                break
            elif shape.containsPoint(pos):
                if self.selectedVertex():
                    self.hShape.highlightClear()
                self.hVertex, self.hShape = None, shape
                # self.setToolTip("Click & drag to move shape '%s'" % shape.label)
                # self.setStatusTip(self.toolTip())
                self.overrideCursor(CURSOR_GRAB)
                self.update()
                break
        else:  # Nothing found, clear highlights, reset state.
            if self.hShape:
                self.hShape.highlightClear()
                self.update()
            self.hVertex, self.hShape = None, None
            self.overrideCursor(CURSOR_DEFAULT)

    def mousePressEvent(self, ev):
        pos = self.transformPos(ev.pos())

        if ev.button() == Qt.LeftButton:
            if self.drawing():
                self.handleDrawing(pos)
            elif self.contourMode and self.showContourOverlay:
                self.unHighlight()
                xmin, ymin = int(self.selectedShape.points[0].x()), int(self.selectedShape.points[0].y())
                punktmenge = list()
                begin = pos.y() - ymin, pos.x() - xmin
                d_s = list()
                for p in self.selectedShape.contour_points: 
                    d = np.sqrt((p[0] - begin[0])**2 + (p[1] - begin[1])**2)
                    # print(d)
                    if d < 10:
                        punktmenge.append(p)
                        d_s.append(d)
                try:
                    nearest = punktmenge[np.argmin(d_s)]
                    self.cntOldidx = self.selectedShape.contour_points.index(nearest)
                    # sprint(nearest, self.cntOldidx)
                except Exception as e:
                    self.cntOldidx = None
                    # print(e)
            else:
                self.selectShapePoint(pos)
                self.prevPoint = pos
                self.repaint()
        elif ev.button() == Qt.RightButton and self.editing():
            self.selectShapePoint(pos)
            self.prevPoint = pos
            self.repaint()

    def mouseReleaseEvent(self, ev):
        pos = self.transformPos(ev.pos())
        if ev.button() == Qt.RightButton:
            menu = self.menus[bool(self.selectedShapeCopy)]
            self.restoreCursor()
            if not menu.exec_(self.mapToGlobal(ev.pos()))\
               and self.selectedShapeCopy:
                # Cancel the move by deleting the shadow copy.
                self.selectedShapeCopy = None
                self.repaint()

        elif ev.button() == Qt.LeftButton and self.selectedShape:
            if self.contourMode and self.showContourOverlay:
                # print('MouseReleaseEvent from contourMode')
                # print(pos)
                if self.cntOldidx is not None:
                    shapeOrigin = self.selectedShape.points[0]
                    cntOld = self.selectedShape.contour_points[self.cntOldidx]
                    cntNew = int(pos.y() - shapeOrigin.y()), int(pos.x() - shapeOrigin.x())
                    delta_y, delta_x = cntNew[0] - cntOld[0], cntNew[1] - cntOld[1]
                    # print(delta_y, delta_x)
                    for i in range(self.cntOldidx-3, self.cntOldidx+4, 1):
                        i %= len(self.selectedShape.contour_points)
                        p = self.selectedShape.contour_points[i]
                        d = np.sqrt((p[0]-cntOld[0])**2 + (p[1]-cntOld[1])**2)
                        f = 1/(1 + 1.4 * d) 
                        self.selectedShape.contour_points[i] = (p[0] + delta_y * f, p[1] + delta_x * f)
                        # print(p, f, self.selectedShape.contour_points[i])
            self.repaint()

            if self.selectedVertex():
                self.overrideCursor(CURSOR_POINT)
            else:
                self.overrideCursor(CURSOR_GRAB)
        elif ev.button() == Qt.LeftButton:
            pos = self.transformPos(ev.pos())
            if self.drawing():
                self.handleDrawing(pos)

    def endMove(self, copy=False):
        assert self.selectedShape and self.selectedShapeCopy
        shape = self.selectedShapeCopy
        #del shape.fill_color
        #del shape.line_color
        if copy:
            self.shapes.append(shape)
            self.selectedShape.selected = False
            self.selectedShape = shape
            self.repaint()
        else:
            self.selectedShape.points = [p for p in shape.points]
        self.selectedShapeCopy = None

    def hideBackroundShapes(self, value):
        self.hideBackround = value
        if self.selectedShape:
            # Only hide other shapes if there is a current selection.
            # Otherwise the user will not be able to select a shape.
            self.setHiding(True)
            self.repaint()

    def handleDrawing(self, pos):
        if self.current and self.current.reachMaxPoints() is False:
            initPos = self.current[0]
            minX = initPos.x()
            minY = initPos.y()
            targetPos = self.line[1]
            maxX = targetPos.x()
            maxY = targetPos.y()
            self.current.addPoint(QPointF(maxX, minY))
            self.current.addPoint(targetPos)
            self.current.addPoint(QPointF(minX, maxY))
            self.finalise()
        elif not self.outOfPixmap(pos):
            self.current = Shape()
            self.current.addPoint(pos)
            self.line.points = [pos, pos]
            self.setHiding()
            self.drawingPolygon.emit(True)
            self.update()

    def setHiding(self, enable=True):
        self._hideBackround = self.hideBackround if enable else False

    def canCloseShape(self):
        return self.drawing() and self.current and len(self.current) > 2

    def mouseDoubleClickEvent(self, ev):
        # We need at least 4 points here, since the mousePress handler
        # adds an extra one before this handler is called.
        if self.canCloseShape() and len(self.current) > 3:
            self.current.popPoint()
            self.finalise()

    def selectShape(self, shape):
        self.deSelectShape()
        shape.selected = True
        self.selectedShape = shape
        self.setHiding()
        self.selectionChanged.emit(True)
        # print(shape.points)
        # print(shape.contour_points)
        self.update()

    def selectShapePoint(self, point):
        """Select the first shape created which contains this point."""
        self.deSelectShape()
        if self.selectedVertex():  # A vertex is marked for selection.
            index, shape = self.hVertex, self.hShape
            shape.highlightVertex(index, shape.MOVE_VERTEX)
            self.selectShape(shape)
            return
        for shape in reversed(self.shapes):
            if self.isVisible(shape) and shape.containsPoint(point):
                self.selectShape(shape)
                self.calculateOffsets(shape, point)
                return

    def calculateOffsets(self, shape, point):
        rect = shape.boundingRect()
        x1 = rect.x() - point.x()
        y1 = rect.y() - point.y()
        x2 = (rect.x() + rect.width()) - point.x()
        y2 = (rect.y() + rect.height()) - point.y()
        self.offsets = QPointF(x1, y1), QPointF(x2, y2)

    def boundedMoveVertex(self, pos):
        index, shape = self.hVertex, self.hShape
        point = shape[index]
        if self.outOfPixmap(pos):
            pos = self.intersectionPoint(point, pos)

        shiftPos = pos - point
        shape.moveVertexBy(index, shiftPos)

        lindex = (index + 1) % 4
        rindex = (index + 3) % 4
        lshift = None
        rshift = None
        if index % 2 == 0:
            rshift = QPointF(shiftPos.x(), 0)
            lshift = QPointF(0, shiftPos.y())
        else:
            lshift = QPointF(shiftPos.x(), 0)
            rshift = QPointF(0, shiftPos.y())
        shape.moveVertexBy(rindex, rshift)
        shape.moveVertexBy(lindex, lshift)

    def boundedMoveShape(self, shape, pos):
        if self.outOfPixmap(pos):
            return False  # No need to move
        o1 = pos + self.offsets[0]
        if self.outOfPixmap(o1):
            pos -= QPointF(min(0, o1.x()), min(0, o1.y()))
        o2 = pos + self.offsets[1]
        if self.outOfPixmap(o2):
            pos += QPointF(min(0, self.pixmap.width() - o2.x()),
                           min(0, self.pixmap.height() - o2.y()))
        # The next line tracks the new position of the cursor
        # relative to the shape, but also results in making it
        # a bit "shaky" when nearing the border and allows it to
        # go outside of the shape's area for some reason. XXX
        #self.calculateOffsets(self.selectedShape, pos)
        dp = pos - self.prevPoint
        if dp:
            shape.moveBy(dp)
            self.prevPoint = pos
            return True
        return False

    def deSelectShape(self):
        if self.selectedShape and not self.contourMode:
            self.selectedShape.selected = False
            self.selectedShape = None
            self.setHiding(False)
            self.selectionChanged.emit(False)
            self.update()

    def deleteSelected(self):
        if self.selectedShape and not self.contourMode:
            shape = self.selectedShape
            if self.selectedShape in self.shapes:
                print('Box wurde gelöscht')
                self.shapes.remove(self.selectedShape)
            else:
                print('Zu löschen Box wurde nicht gefunden {0}{1}{2}{3}\n'.format(xmin, xmax, ymin, ymax))
            self.selectedShape = None
            self.update()
            return shape

    def copySelectedShape(self):
        if self.selectedShape:
            shape = self.selectedShape.copy()
            self.deSelectShape()
            self.shapes.append(shape)
            shape.selected = True
            self.selectedShape = shape
            self.boundedShiftShape(shape)
            return shape

    def boundedShiftShape(self, shape):
        # Try to move in one direction, and if it fails in another.
        # Give up if both fail.
        point = shape[0]
        offset = QPointF(2.0, 2.0)
        self.calculateOffsets(shape, point)
        self.prevPoint = point
        if not self.boundedMoveShape(shape, point - offset):
            self.boundedMoveShape(shape, point + offset)

    def paintEvent(self, event):
        if not self.pixmap:
            return super(Canvas, self).paintEvent(event)

        p = self._painter
        p.begin(self)
        p.setRenderHint(QPainter.Antialiasing)
        p.setRenderHint(QPainter.HighQualityAntialiasing)
        p.setRenderHint(QPainter.SmoothPixmapTransform)

        p.scale(self.scale, self.scale)
        p.translate(self.offsetToCenter())

        p.drawPixmap(0, 0, self.pixmap)
        Shape.scale = self.scale
        for shape in self.shapes:
            if (shape.selected or not self._hideBackround) and self.isVisible(shape):
                shape.fill = shape.selected or shape == self.hShape
                shape.paint(p)
        if self.current:
            self.current.paint(p)
            self.line.paint(p)
        if self.selectedShapeCopy:
            self.selectedShapeCopy.paint(p)

        # Paint rect
        if self.current is not None and len(self.line) == 2:
            leftTop = self.line[0]
            rightBottom = self.line[1]
            rectWidth = rightBottom.x() - leftTop.x()
            rectHeight = rightBottom.y() - leftTop.y()
            p.setPen(self.drawingRectColor)
            brush = QBrush(Qt.BDiagPattern)
            p.setBrush(brush)
            p.drawRect(leftTop.x(), leftTop.y(), rectWidth, rectHeight)

        if self.drawing() and not self.prevPoint.isNull() and not self.outOfPixmap(self.prevPoint):
            p.setPen(QColor(0, 0, 0))
            p.drawLine(self.prevPoint.x(), 0, self.prevPoint.x(), self.pixmap.height())
            p.drawLine(0, self.prevPoint.y(), self.pixmap.width(), self.prevPoint.y())

        self.setAutoFillBackground(True)
        if self.verified:
            pal = self.palette()
            pal.setColor(self.backgroundRole(), QColor(184, 239, 38, 128))
            self.setPalette(pal)
        else:
            pal = self.palette()
            pal.setColor(self.backgroundRole(), QColor(232, 232, 232, 255))
            self.setPalette(pal)

        if self.showContourOverlay:
            # print('showing contour with Qt')
            p.setPen(self.drawingContourColor)
            brush = QBrush(QColor('transparent'))
            p.setBrush(brush)
            for shape in self.shapes:
                p.drawPolygon(self.createPoly(shape))
            if self.contourMode and self.selectedShape:
                pen = QPen(QColor('black'))
                pen.setWidth(4)
                p.setPen(pen)
                xmin, ymin = int(self.selectedShape.points[0].x()), int(self.selectedShape.points[0].y())
                for i in self.selectedShape.contour_points:
                    p.drawPoint(QPointF(i[1]+xmin, i[0]+ymin))
        p.end()

    def createPoly(self, shape):
            polygon = QPolygonF()
            xmin, ymin = int(shape.points[0].x()), int(shape.points[0].y())
            for p in shape.contour_points:
                polygon.append(QPointF(p[1]+xmin, p[0]+ymin))  
            return polygon

    def transformPos(self, point):
        """Convert from widget-logical coordinates to painter-logical coordinates."""
        return point / self.scale - self.offsetToCenter()

    def offsetToCenter(self):
        s = self.scale
        area = super(Canvas, self).size()
        w, h = self.pixmap.width() * s, self.pixmap.height() * s
        aw, ah = area.width(), area.height()
        x = (aw - w) / (2 * s) if aw > w else 0
        y = (ah - h) / (2 * s) if ah > h else 0
        return QPointF(x, y)

    def outOfPixmap(self, p):
        w, h = self.pixmap.width(), self.pixmap.height()
        return not (0 <= p.x() <= w and 0 <= p.y() <= h)

    def finalise(self):
        assert self.current
        if self.current.points[0] == self.current.points[-1]:
            self.current = None
            self.drawingPolygon.emit(False)
            self.update()
            return

        self.current.close()
        self.shapes.append(self.current)
        self.current = None
        self.setHiding(False)
        self.newShape.emit()
        self.update()

    def closeEnough(self, p1, p2):
        #d = distance(p1 - p2)
        #m = (p1-p2).manhattanLength()
        # print "d %.2f, m %d, %.2f" % (d, m, d - m)
        return distance(p1 - p2) < self.epsilon

    def intersectionPoint(self, p1, p2):
        # Cycle through each image edge in clockwise fashion,
        # and find the one intersecting the current line segment.
        # http://paulbourke.net/geometry/lineline2d/
        size = self.pixmap.size()
        points = [(0, 0),
                  (size.width(), 0),
                  (size.width(), size.height()),
                  (0, size.height())]
        x1, y1 = p1.x(), p1.y()
        x2, y2 = p2.x(), p2.y()
        d, i, (x, y) = min(self.intersectingEdges((x1, y1), (x2, y2), points))
        x3, y3 = points[i]
        x4, y4 = points[(i + 1) % 4]
        if (x, y) == (x1, y1):
            # Handle cases where previous point is on one of the edges.
            if x3 == x4:
                return QPointF(x3, min(max(0, y2), max(y3, y4)))
            else:  # y3 == y4
                return QPointF(min(max(0, x2), max(x3, x4)), y3)
        return QPointF(x, y)

    def intersectingEdges(self, x1y1, x2y2, points):
        """For each edge formed by `points', yield the intersection
        with the line segment `(x1,y1) - (x2,y2)`, if it exists.
        Also return the distance of `(x2,y2)' to the middle of the
        edge along with its index, so that the one closest can be chosen."""
        x1, y1 = x1y1
        x2, y2 = x2y2
        for i in range(4):
            x3, y3 = points[i]
            x4, y4 = points[(i + 1) % 4]
            denom = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1)
            nua = (x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)
            nub = (x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)
            if denom == 0:
                # This covers two cases:
                #   nua == nub == 0: Coincident
                #   otherwise: Parallel
                continue
            ua, ub = nua / denom, nub / denom
            if 0 <= ua <= 1 and 0 <= ub <= 1:
                x = x1 + ua * (x2 - x1)
                y = y1 + ua * (y2 - y1)
                m = QPointF((x3 + x4) / 2, (y3 + y4) / 2)
                d = distance(m - QPointF(x2, y2))
                yield d, i, (x, y)

    # These two, along with a call to adjustSize are required for the
    # scroll area.
    def sizeHint(self):
        return self.minimumSizeHint()

    def minimumSizeHint(self):
        if self.pixmap:
            return self.scale * self.pixmap.size()
        return super(Canvas, self).minimumSizeHint()

    def wheelEvent(self, ev):
        qt_version = 4 if hasattr(ev, "delta") else 5
        if qt_version == 4:
            if ev.orientation() == Qt.Vertical:
                v_delta = ev.delta()
                h_delta = 0
            else:
                h_delta = ev.delta()
                v_delta = 0
        else:
            delta = ev.angleDelta()
            h_delta = delta.x()
            v_delta = delta.y()

        mods = ev.modifiers()
        if Qt.ControlModifier == int(mods) and v_delta:
            self.zoomRequest.emit(v_delta)
        else:
            v_delta and self.scrollRequest.emit(v_delta, Qt.Vertical)
            h_delta and self.scrollRequest.emit(h_delta, Qt.Horizontal)
        ev.accept()

    def keyPressEvent(self, ev):
        key = ev.key()
        if key == Qt.Key_Escape and self.current:
            print('ESC press')
            self.current = None
            self.drawingPolygon.emit(False)
            self.update()
        elif key == Qt.Key_Return and self.canCloseShape():
            self.finalise()
        elif key == Qt.Key_Left and self.selectedShape:
            self.moveOnePixel('Left')
        elif key == Qt.Key_Right and self.selectedShape:
            self.moveOnePixel('Right')
        elif key == Qt.Key_Up and self.selectedShape:
            self.moveOnePixel('Up')
        elif key == Qt.Key_Down and self.selectedShape:
            self.moveOnePixel('Down')
        elif key == Qt.Key_S and self.selectedShape:
            if(not self.selectedShape.contourEdited):
                self.selectedShape.contourEdited = True
                self.deSelectShape()
                self.update()
        elif key == Qt.Key_E and self.selectedShape and self.showContourOverlay:
            self.contourMode = True
            self.unHighlight()
            # print('Entering contour mode')
        elif key == Qt.Key_Q and self.selectedShape and self.contourMode:
            self.contourMode = False
            self.selectedShape.contourEdited = True
            self.deSelectShape()
            # print('Leaving contour mode')
            self.saveFileSignal.emit()
        elif key == Qt.Key_N and self.selectedShape and self.contourMode:
            if not self.selectedShape.contour_points:
                self.selectedShape.contour_points = self.genContourInShape(self.selectedShape)
                self.update()
        elif key == Qt.Key_R and self.selectedShape and self.contourMode:
            self.selectedShape.contour_points = list()
            self.update()
        elif key == Qt.Key_F and not self.contourMode:
            pos = self.globalMousePos
            shapesIdx = list()
            for s in self.shapes:
                if s.containsPoint(pos):
                    shapesIdx.append(self.shapes.index(s))
            if len(shapesIdx) >= 2:
                if sorted(self.memIdx) != sorted(shapesIdx):
                    self.memIdx = shapesIdx
                    self.currentIdx = 0
                else: 
                    self.currentIdx += 1
                i = self.memIdx[self.currentIdx%len(self.memIdx)]
                if self.selectedShape != self.shapes[i]: 
                    self.deSelectShape()
                    self.selectShape(self.shapes[i])
                    self.update()
        return

    def genContourInShape(self, shape):
        genContour = list()
        xmin, xmax, ymin, ymax = shape.points[0].x(), shape.points[2].x(), shape.points[0].y(), shape.points[2].y()
        height = xmax - xmin
        width = ymax - ymin
        r = (xmax - xmin) // 2
        w = 360/16
        for i in range(16):
            t = w*i
            x = r*math.cos(math.radians(t))
            y = r*math.sin(math.radians(t))
            genContour.append((width/2 + x, height/2 + y))
        return genContour


    def moveOnePixel(self, direction):
        # print(self.selectedShape.points)
        if direction == 'Left' and not self.moveOutOfBound(QPointF(-1.0, 0)):
            # print("move Left one pixel")
            self.selectedShape.points[0] += QPointF(-1.0, 0)
            self.selectedShape.points[1] += QPointF(-1.0, 0)
            self.selectedShape.points[2] += QPointF(-1.0, 0)
            self.selectedShape.points[3] += QPointF(-1.0, 0)
        elif direction == 'Right' and not self.moveOutOfBound(QPointF(1.0, 0)):
            # print("move Right one pixel")
            self.selectedShape.points[0] += QPointF(1.0, 0)
            self.selectedShape.points[1] += QPointF(1.0, 0)
            self.selectedShape.points[2] += QPointF(1.0, 0)
            self.selectedShape.points[3] += QPointF(1.0, 0)
        elif direction == 'Up' and not self.moveOutOfBound(QPointF(0, -1.0)):
            # print("move Up one pixel")
            self.selectedShape.points[0] += QPointF(0, -1.0)
            self.selectedShape.points[1] += QPointF(0, -1.0)
            self.selectedShape.points[2] += QPointF(0, -1.0)
            self.selectedShape.points[3] += QPointF(0, -1.0)
        elif direction == 'Down' and not self.moveOutOfBound(QPointF(0, 1.0)):
            # print("move Down one pixel")
            self.selectedShape.points[0] += QPointF(0, 1.0)
            self.selectedShape.points[1] += QPointF(0, 1.0)
            self.selectedShape.points[2] += QPointF(0, 1.0)
            self.selectedShape.points[3] += QPointF(0, 1.0)
        self.shapeMoved.emit()
        self.repaint()

    def moveOutOfBound(self, step):
        points = [p1+p2 for p1, p2 in zip(self.selectedShape.points, [step]*4)]
        return True in map(self.outOfPixmap, points)

    def setLastLabel(self, text, line_color  = None, fill_color = None):
        assert text
        self.shapes[-1].label = text
        if line_color:
            self.shapes[-1].line_color = line_color
        
        if fill_color:
            self.shapes[-1].fill_color = fill_color

        return self.shapes[-1]

    def undoLastLine(self):
        assert self.shapes
        self.current = self.shapes.pop()
        self.current.setOpen()
        self.line.points = [self.current[-1], self.current[0]]
        self.drawingPolygon.emit(True)

    def resetAllLines(self):
        assert self.shapes
        self.current = self.shapes.pop()
        self.current.setOpen()
        self.line.points = [self.current[-1], self.current[0]]
        self.drawingPolygon.emit(True)
        self.current = None
        self.drawingPolygon.emit(False)
        self.update()

    def loadPixmap(self, pixmap):
        self.pixmap = pixmap
        self.shapes = []
        self.repaint()

    def loadShapes(self, shapes):
        self.shapes = list(shapes)
        self.current = None
        self.repaint()

    def setShapeVisible(self, shape, value):
        self.visible[shape] = value
        self.repaint()

    def currentCursor(self):
        cursor = QApplication.overrideCursor()
        if cursor is not None:
            cursor = cursor.shape()
        return cursor

    def overrideCursor(self, cursor):
        self._cursor = cursor
        if self.currentCursor() is None:
            QApplication.setOverrideCursor(cursor)
        else:
            QApplication.changeOverrideCursor(cursor)

    def restoreCursor(self):
        QApplication.restoreOverrideCursor()

    def resetState(self):
        self.restoreCursor()
        self.pixmap = None
        self.memIdx = list()
        self.currentIdx = 0
        self.update()
コード例 #15
0
class Canvas(QtWidgets.QWidget):
    zoomRequest = QtCore.pyqtSignal(int)
    scrollRequest = QtCore.pyqtSignal(int, int)
    newShape = QtCore.pyqtSignal()
    selectionChanged = QtCore.pyqtSignal(bool)
    shapeMoved = QtCore.pyqtSignal()
    drawingPolygon = QtCore.pyqtSignal(bool)

    CREATE, EDIT = 0, 1

    epsilon = 11.0

    def __init__(self, *args, **kwargs):
        super(Canvas, self).__init__(*args, **kwargs)
        # Initialise local state.
        self.mode = self.EDIT
        self.shapes = []
        self.current = None
        self.selectedShape = None  # save the selected shape here
        self.selectedShapeCopy = None
        self.lineColor = QtGui.QColor(0, 0, 255)
        self.drawingLineColor = QtGui.QColor(0, 0, 255)
        self.drawingRectColor = QtGui.QColor(0, 0, 255)
        self.line = Shape(line_color=self.drawingLineColor)
        self.prevPoint = QtCore.QPointF()
        self.offsets = QtCore.QPointF(), QtCore.QPointF()
        self.scale = 1.0
        self.pixmap = QtGui.QPixmap()
        self.visible = {}
        self._hideBackround = False
        self.hideBackround = False
        self.hShape = None
        self.hVertex = None
        self._painter = QtGui.QPainter()
        self._cursor = CURSOR_DEFAULT
        # Menus:
        self.menus = (QtWidgets.QMenu(), QtWidgets.QMenu())
        # Set widget options.
        self.setMouseTracking(True)
        self.setFocusPolicy(QtCore.Qt.WheelFocus)
        self.verified = False

    def setDrawingColor(self, qColor):
        self.drawingLineColor = qColor

        # print(self.drawingLineColor.getRgb())
        self.drawingRectColor = qColor
        for shape in self.shapes:
            shape.line_color = qColor
        self.repaint()
        # self.line.line_color = qColor
        # self.lineColor = qColor
        # self.current.line_color = qColor

    def enterEvent(self, ev):
        self.overrideCursor(self._cursor)

    def leaveEvent(self, ev):
        self.restoreCursor()

    def focusOutEvent(self, ev):
        self.restoreCursor()

    def isVisible(self, shape):
        return self.visible.get(shape, True)

    def drawing(self):
        return self.mode == self.CREATE

    def editing(self):
        return self.mode == self.EDIT

    def setEditing(self, value=True):
        self.mode = self.EDIT if value else self.CREATE
        if not value:  # Create
            self.unHighlight()
            self.deSelectShape()

    def unHighlight(self):
        if self.hShape:
            self.hShape.highlightClear()
        self.hVertex = self.hShape = None

    def selectedVertex(self):
        return self.hVertex is not None

    def mouseMoveEvent(self, ev):
        """Update line with last point and current coordinates."""
        if PYQT5:
            pos = self.transformPos(ev.pos())
        else:
            pos = self.transformPos(ev.posF())

        self.restoreCursor()

        # Polygon drawing.
        if self.drawing():
            self.overrideCursor(CURSOR_DRAW)
            if self.current:
                color = self.drawingLineColor
                # print("a",self.drawingLineColor.getRgb())
                if self.outOfPixmap(pos):
                    # Don't allow the user to draw outside the pixmap.
                    # Project the point to the pixmap's edges.
                    pos = self.intersectionPoint(self.current[-1], pos)
                elif len(self.current) > 1 and \
                        self.closeEnough(pos, self.current[0]):
                    # Attract line to starting point and
                    # colorise to alert the user.
                    pos = self.current[0]
                    color = self.current.line_color
                    # print("*********",color.getRgb())
                    self.overrideCursor(CURSOR_POINT)
                    self.current.highlightVertex(0, Shape.NEAR_VERTEX)
                self.line[1] = pos
                self.line.line_color = color
                # print("b",self.line.line_color.getRgb())
                self.repaint()
                self.current.highlightClear()
            return

        # Polygon copy moving.
        if QtCore.Qt.RightButton & ev.buttons():
            if self.selectedShapeCopy and self.prevPoint:
                self.overrideCursor(CURSOR_MOVE)
                self.boundedMoveShape(self.selectedShapeCopy, pos)
                self.repaint()
            elif self.selectedShape:
                self.selectedShapeCopy = self.selectedShape.copy()
                self.repaint()
            return

        # Polygon/Vertex moving.
        if QtCore.Qt.LeftButton & ev.buttons():
            if self.selectedVertex():
                self.boundedMoveVertex(pos)
                self.shapeMoved.emit()
                self.repaint()
            elif self.selectedShape and self.prevPoint:
                self.overrideCursor(CURSOR_MOVE)
                self.boundedMoveShape(self.selectedShape, pos)
                self.shapeMoved.emit()
                self.repaint()
            return

        # Just hovering over the canvas, 2 posibilities:
        # - Highlight shapes
        # - Highlight vertex
        # Update shape/vertex fill and tooltip value accordingly.
        self.setToolTip("Image")
        for shape in reversed([s for s in self.shapes if self.isVisible(s)]):
            # Look for a nearby vertex to highlight. If that fails,
            # check if we happen to be inside a shape.
            index = shape.nearestVertex(pos, self.epsilon)
            if index is not None:
                if self.selectedVertex():
                    self.hShape.highlightClear()
                self.hVertex, self.hShape = index, shape
                shape.highlightVertex(index, shape.MOVE_VERTEX)
                self.overrideCursor(CURSOR_POINT)
                self.setToolTip("Click & drag to move point")
                self.setStatusTip(self.toolTip())
                self.update()
                break
            elif shape.containsPoint(pos):
                if self.selectedVertex():
                    self.hShape.highlightClear()
                self.hVertex, self.hShape = None, shape
                self.setToolTip("Click & drag to move shape '%s'" %
                                shape.label)
                self.setStatusTip(self.toolTip())
                self.overrideCursor(CURSOR_GRAB)
                self.update()
                break
        else:  # Nothing found, clear highlights, reset state.
            if self.hShape:
                self.hShape.highlightClear()
                self.update()
            self.hVertex, self.hShape = None, None

    def mousePressEvent(self, ev):
        if PYQT5:
            pos = self.transformPos(ev.pos())
        else:
            pos = self.transformPos(ev.posF())
        if ev.button() == QtCore.Qt.LeftButton:
            if self.drawing():
                if self.current:
                    try:
                        self.current.addPoint(self.line[1])
                    except Exception as e:
                        print(e, file=sys.stderr)
                        return
                    self.line[0] = self.current[-1]
                    if self.current.isClosed():
                        self.finalise()
                elif not self.outOfPixmap(pos):
                    self.current = Shape()
                    self.current.addPoint(pos)
                    self.line.points = [pos, pos]
                    self.setHiding()
                    self.drawingPolygon.emit(True)
                    self.update()
            else:
                self.selectShapePoint(pos)
                self.prevPoint = pos
                self.repaint()
        elif ev.button() == QtCore.Qt.RightButton and self.editing():
            self.selectShapePoint(pos)
            self.prevPoint = pos
            self.repaint()

    def mouseReleaseEvent(self, ev):
        if ev.button() == QtCore.Qt.RightButton:
            menu = self.menus[bool(self.selectedShapeCopy)]
            self.restoreCursor()
            if not menu.exec_(self.mapToGlobal(ev.pos()))\
               and self.selectedShapeCopy:
                # Cancel the move by deleting the shadow copy.
                self.selectedShapeCopy = None
                self.repaint()
        elif ev.button() == QtCore.Qt.LeftButton and self.selectedShape:
            self.overrideCursor(CURSOR_GRAB)

    def endMove(self, copy=False):
        assert self.selectedShape and self.selectedShapeCopy
        shape = self.selectedShapeCopy
        # del shape.fill_color
        # del shape.line_color
        if copy:
            self.shapes.append(shape)
            self.selectedShape.selected = False
            self.selectedShape = shape
            self.repaint()
        else:
            shape.label = self.selectedShape.label
            self.deleteSelected()
            self.shapes.append(shape)
        self.selectedShapeCopy = None

    def hideBackroundShapes(self, value):
        self.hideBackround = value
        if self.selectedShape:
            # Only hide other shapes if there is a current selection.
            # Otherwise the user will not be able to select a shape.
            self.setHiding(True)
            self.repaint()

    def setHiding(self, enable=True):
        self._hideBackround = self.hideBackround if enable else False

    def canCloseShape(self):
        return self.drawing() and self.current and len(self.current) > 2

    def mouseDoubleClickEvent(self, ev):
        # We need at least 4 points here, since the mousePress handler
        # adds an extra one before this handler is called.
        if self.canCloseShape() and len(self.current) > 3:
            self.current.popPoint()
            self.finalise()

    def selectShape(self, shape):
        self.deSelectShape()
        shape.selected = True
        self.selectedShape = shape
        self.setHiding()
        self.selectionChanged.emit(True)
        self.update()

    def selectShapePoint(self, point):
        """Select the first shape created which contains this point."""
        self.deSelectShape()
        if self.selectedVertex():  # A vertex is marked for selection.
            index, shape = self.hVertex, self.hShape
            shape.highlightVertex(index, shape.MOVE_VERTEX)
            return
        for shape in reversed(self.shapes):
            if self.isVisible(shape) and shape.containsPoint(point):
                shape.selected = True
                self.selectedShape = shape
                self.calculateOffsets(shape, point)
                self.setHiding()
                self.selectionChanged.emit(True)
                return

    def calculateOffsets(self, shape, point):
        rect = shape.boundingRect()
        x1 = rect.x() - point.x()
        y1 = rect.y() - point.y()
        x2 = (rect.x() + rect.width()) - point.x()
        y2 = (rect.y() + rect.height()) - point.y()
        self.offsets = QtCore.QPointF(x1, y1), QtCore.QPointF(x2, y2)

    def boundedMoveVertex(self, pos):
        index, shape = self.hVertex, self.hShape
        point = shape[index]
        if self.outOfPixmap(pos):
            pos = self.intersectionPoint(point, pos)
        shape.moveVertexBy(index, pos - point)

    def boundedMoveShape(self, shape, pos):
        if self.outOfPixmap(pos):
            return False  # No need to move
        o1 = pos + self.offsets[0]
        if self.outOfPixmap(o1):
            pos -= QtCore.QPointF(min(0, o1.x()), min(0, o1.y()))
        o2 = pos + self.offsets[1]
        if self.outOfPixmap(o2):
            pos += QtCore.QPointF(min(0,
                                      self.pixmap.width() - o2.x()),
                                  min(0,
                                      self.pixmap.height() - o2.y()))
        # XXX: The next line tracks the new position of the cursor
        # relative to the shape, but also results in making it
        # a bit "shaky" when nearing the border and allows it to
        # go outside of the shape's area for some reason.
        # self.calculateOffsets(self.selectedShape, pos)
        dp = pos - self.prevPoint
        if dp:
            shape.moveBy(dp)
            self.prevPoint = pos
            return True
        return False

    def deSelectShape(self):
        if self.selectedShape:
            self.selectedShape.selected = False
            self.selectedShape = None
            self.setHiding(False)
            self.selectionChanged.emit(False)
            self.update()

    def deleteSelected(self):
        if self.selectedShape:
            shape = self.selectedShape
            self.shapes.remove(self.selectedShape)
            self.selectedShape = None
            self.update()
            return shape

    def copySelectedShape(self):
        if self.selectedShape:
            shape = self.selectedShape.copy()
            self.deSelectShape()
            self.shapes.append(shape)
            shape.selected = True
            self.selectedShape = shape
            self.boundedShiftShape(shape)
            return shape

    def boundedShiftShape(self, shape):
        # Try to move in one direction, and if it fails in another.
        # Give up if both fail.
        point = shape[0]
        offset = QtCore.QPointF(2.0, 2.0)
        self.calculateOffsets(shape, point)
        self.prevPoint = point
        if not self.boundedMoveShape(shape, point - offset):
            self.boundedMoveShape(shape, point + offset)

    def paintEvent(self, event):
        if not self.pixmap:
            return super(Canvas, self).paintEvent(event)
        p = self._painter
        p.begin(self)
        p.setRenderHint(QtGui.QPainter.Antialiasing)
        p.setRenderHint(QtGui.QPainter.HighQualityAntialiasing)
        p.setRenderHint(QtGui.QPainter.SmoothPixmapTransform)

        p.scale(self.scale, self.scale)
        p.translate(self.offsetToCenter())

        p.drawPixmap(0, 0, self.pixmap)
        Shape.scale = self.scale
        # print(len(self.shapes),86210)
        for shape in self.shapes:
            if not shape.hided:
                if (shape.selected or not self._hideBackround) and \
                        self.isVisible(shape):
                    shape.fill = shape.selected or shape == self.hShape
                    shape.paint(p)
            # if (shape.selected or not self._hideBackround) and \
            #         self.isVisible(shape):
            #     shape.fill = shape.selected or shape == self.hShape
            #     shape.paint(p)
        if self.current:
            self.current.line_color = self.drawingLineColor
            self.current.paint(p)
            self.line.paint(p)
        if self.selectedShapeCopy:
            self.selectedShapeCopy.paint(p)

        p.end()

    def transformPos(self, point):
        """Convert from widget-logical coordinates to painter-logical ones."""
        return point / self.scale - self.offsetToCenter()

    def offsetToCenter(self):
        s = self.scale
        area = super(Canvas, self).size()
        w, h = self.pixmap.width() * s, self.pixmap.height() * s
        aw, ah = area.width(), area.height()
        x = (aw - w) / (2 * s) if aw > w else 0
        y = (ah - h) / (2 * s) if ah > h else 0
        return QtCore.QPointF(x, y)

    def outOfPixmap(self, p):
        w, h = self.pixmap.width(), self.pixmap.height()
        return not (0 <= p.x() <= w and 0 <= p.y() <= h)

    def finalise(self):
        assert self.current
        self.current.close()
        self.shapes.append(self.current)
        self.current = None
        self.setHiding(False)
        self.newShape.emit()
        self.update()

    def closeEnough(self, p1, p2):
        # d = distance(p1 - p2)
        # m = (p1-p2).manhattanLength()
        # print "d %.2f, m %d, %.2f" % (d, m, d - m)
        return distance(p1 - p2) < self.epsilon

    def intersectionPoint(self, p1, p2):
        # Cycle through each image edge in clockwise fashion,
        # and find the one intersecting the current line segment.
        # http://paulbourke.net/geometry/lineline2d/
        size = self.pixmap.size()
        # print(size.width(),size.height())
        points = [(0, 0), (size.width(), 0), (size.width(), size.height()),
                  (0, size.height())]
        x1, y1 = p1.x(), p1.y()
        x2, y2 = p2.x(), p2.y()
        d, i, (x, y) = min(self.intersectingEdges((x1, y1), (x2, y2), points))
        x3, y3 = points[i]
        x4, y4 = points[(i + 1) % 4]
        if (x, y) == (x1, y1):
            # Handle cases where previous point is on one of the edges.
            if x3 == x4:
                return QtCore.QPointF(x3, min(max(0, y2), max(y3, y4)))
            else:  # y3 == y4
                return QtCore.QPointF(min(max(0, x2), max(x3, x4)), y3)
        return QtCore.QPointF(x, y)

    def intersectingEdges(self, point1, point2, points):
        """Find intersecting edges.

        For each edge formed by `points', yield the intersection
        with the line segment `(x1,y1) - (x2,y2)`, if it exists.
        Also return the distance of `(x2,y2)' to the middle of the
        edge along with its index, so that the one closest can be chosen.
        """
        (x1, y1) = point1
        (x2, y2) = point2
        for i in range(4):
            x3, y3 = points[i]
            x4, y4 = points[(i + 1) % 4]
            denom = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1)
            nua = (x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)
            nub = (x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)
            if denom == 0:
                # This covers two cases:
                #   nua == nub == 0: Coincident
                #   otherwise: Parallel
                continue
            ua, ub = nua / denom, nub / denom
            if 0 <= ua <= 1 and 0 <= ub <= 1:
                x = x1 + ua * (x2 - x1)
                y = y1 + ua * (y2 - y1)
                m = QtCore.QPointF((x3 + x4) / 2, (y3 + y4) / 2)
                d = distance(m - QtCore.QPointF(x2, y2))
                yield d, i, (x, y)

    # These two, along with a call to adjustSize are required for the
    # scroll area.
    def sizeHint(self):
        return self.minimumSizeHint()

    def minimumSizeHint(self):
        if self.pixmap:
            return self.scale * self.pixmap.size()
        return super(Canvas, self).minimumSizeHint()

    def wheelEvent(self, ev):
        if PYQT5:
            mods = ev.modifiers()
            delta = ev.pixelDelta()
            if QtCore.Qt.ControlModifier == int(mods):
                # with Ctrl/Command key
                # zoom
                self.zoomRequest.emit(delta.y())
            else:
                # scroll
                self.scrollRequest.emit(delta.x(), QtCore.Qt.Horizontal)
                self.scrollRequest.emit(delta.y(), QtCore.Qt.Vertical)
        else:
            if ev.orientation() == QtCore.Qt.Vertical:
                mods = ev.modifiers()
                if QtCore.Qt.ControlModifier == int(mods):
                    # with Ctrl/Command key
                    self.zoomRequest.emit(ev.delta())
                else:
                    self.scrollRequest.emit(
                        ev.delta(), QtCore.Qt.Horizontal if
                        (QtCore.Qt.ShiftModifier
                         == int(mods)) else QtCore.Qt.Vertical)
            else:
                self.scrollRequest.emit(ev.delta(), QtCore.Qt.Horizontal)
        ev.accept()

    def keyPressEvent(self, ev):
        key = ev.key()
        if key == QtCore.Qt.Key_Escape and self.current:
            self.current = None
            self.drawingPolygon.emit(False)
            self.update()
        elif key == QtCore.Qt.Key_Return and self.canCloseShape():
            self.finalise()

    def setLastLabel(self, text):
        assert text
        self.shapes[-1].label = text
        return self.shapes[-1]

    def undoLastLine(self):
        assert self.shapes
        self.current = self.shapes.pop()
        self.current.setOpen()
        self.line.points = [self.current[-1], self.current[0]]
        self.drawingPolygon.emit(True)

    def loadPixmap(self, pixmap):
        self.pixmap = pixmap
        # self.shapes = []
        self.repaint()

    def loadShapes(self, shapes):
        self.shapes = list(shapes)
        self.current = None
        self.repaint()

    def setShapeVisible(self, shape, value):
        self.visible[shape] = value
        self.repaint()

    def overrideCursor(self, cursor):
        self.restoreCursor()
        self._cursor = cursor
        QtWidgets.QApplication.setOverrideCursor(cursor)

    def restoreCursor(self):
        QtWidgets.QApplication.restoreOverrideCursor()

    def resetState(self):
        self.restoreCursor()
        self.pixmap = None
        self.update()
コード例 #16
0
class Canvas(QWidget):
    zoomRequest = pyqtSignal(int)
    scrollRequest = pyqtSignal(int, int)
    newShape = pyqtSignal()
    selectionChanged = pyqtSignal(bool)
    shapeMoved = pyqtSignal()
    shapeMovesFinished = pyqtSignal()
    drawingPolygon = pyqtSignal(bool)

    CREATE, EDIT, CREATEELLIPSE = list(range(3))

    epsilon = 11.0

    def __init__(self, *args, **kwargs):
        super(Canvas, self).__init__(*args, **kwargs)
        # Initialise local state.
        if 'parent' in kwargs.keys():
            self.parent = kwargs['parent']
        self.mode = self.EDIT
        self.shapes = []
        self.current = None
        self.selectedShape = None  # save the selected shape here
        # self.curr_dt = None

        self.selectedShapeCopy = None
        self.drawingLineColor = QColor(0, 0, 255)
        self.drawingRectColor = QColor(0, 0, 255)
        # self.line = Shape(line_color=self.drawingLineColor, parent_canvas=self)
        self.line = Shape(parent_canvas=self)
        self.prevPoint = QPointF()
        self.offsets = QPointF(), QPointF()
        self.scale = 1.0
        self.pixmap = QPixmap()
        self.visible = {}
        self._hideBackround = False
        self.hideBackround = False
        self.hShape = None
        self.hVertex = None
        self._painter = QPainter()
        self._cursor = CURSOR_DEFAULT
        # Menus:
        self.menus = (QMenu(), QMenu())
        # Set widget options.
        self.setMouseTracking(True)
        self.setFocusPolicy(Qt.WheelFocus)
        self.verified = False

    def setDrawingColor(self, qColor):
        self.drawingLineColor = qColor
        self.drawingRectColor = qColor

    def enterEvent(self, ev):
        self.overrideCursor(self._cursor)

    def leaveEvent(self, ev):
        self.restoreCursor()

    def focusOutEvent(self, ev):
        self.restoreCursor()

    def isVisible(self, shape):
        return self.visible.get(shape, True)

    def drawing(self):
        return self.mode == self.CREATE

    def drawing_ellipse(self):
        return self.mode == self.CREATEELLIPSE

    def editing(self):
        return self.mode == self.EDIT

    def setEditing(self, value=True):
        self.mode = self.EDIT if value else self.CREATEELLIPSE
        if not value:  # Create
            self.unHighlight()
            self.deSelectShape()
        self.prevPoint = QPointF()
        self.repaint()

    def unHighlight(self):
        if self.hShape:
            self.hShape.highlightClear()
        self.hVertex = self.hShape = None

    def selectedVertex(self):
        return self.hVertex is not None

    def mouseMoveEvent(self, ev):
        """Update line with last point and current coordinates."""
        pos = self.transformPos(ev.pos())
        posLat, posLon = self.transformToLatLon(pos)
        self.mousePosLatLon = QPointF(posLon, posLat)

        # print(posLat, posLon)

        # Update coordinates in status bar if image is opened
        window = self.parent.window()
        if window.filePath is not None:
            self.parent.window().labelCoordinates.setText(
                # 'X: %d; Y: %d' % (pos.x(), pos.y()))
                'lat: %f; lon: %f' % (posLat, posLon))

        # Polygon drawing.
        if self.drawing_ellipse():
            return
        elif self.drawing():
            self.overrideCursor(CURSOR_DRAW)
            if self.current:
                color = self.drawingLineColor
                if self.outOfPixmap(pos):
                    # Don't allow the user to draw outside the pixmap.
                    # Project the point to the pixmap's edges.
                    pos = self.intersectionPoint(self.current[-1], pos)
                elif len(self.current) > 1 and self.closeEnough(
                        pos, self.current[0]):
                    # Attract line to starting point and colorise to alert the
                    # user:
                    pos = self.current[0]
                    color = self.current.line_color
                    self.overrideCursor(CURSOR_POINT)
                    self.current.highlightVertex(0, Shape.NEAR_VERTEX)
                self.line[1] = pos
                self.line.line_color = color
                self.prevPoint = QPointF()
                self.current.highlightClear()
            else:
                self.prevPoint = pos
            self.repaint()
            return

        # Polygon copy moving.
        if Qt.RightButton & ev.buttons():
            if self.selectedShapeCopy and self.prevPoint:
                self.overrideCursor(CURSOR_MOVE)
                self.boundedMoveShape(self.selectedShapeCopy, pos)
                self.repaint()
            elif self.selectedShape:
                self.selectedShapeCopy = self.selectedShape.copy()
                self.repaint()
            return

        # Polygon/Vertex moving.
        if Qt.LeftButton & ev.buttons():
            if self.selectedVertex():
                self.boundedMoveVertex(pos)
                self.shapeMoved.emit()
                self.repaint()
            elif self.selectedShape and self.prevPoint:
                self.overrideCursor(CURSOR_MOVE)
                self.boundedMoveShape(self.selectedShape, pos)
                self.shapeMoved.emit()
                self.repaint()

            self.recalculateMovedLatlonPointsSelectedShape()
            return

        # Just hovering over the canvas, 2 posibilities:
        # - Highlight shapes
        # - Highlight vertex
        # Update shape/vertex fill and tooltip value accordingly.
        # self.setToolTip("Image")
        # self.setToolTip('X: %d; Y: %d' % (pos.x(), pos.y()))

        try:
            bmhelper = self.parent.window().basemaphelper
            valueStr = bmhelper.getValueStr_AtCoordinates(
                self.mousePosLatLon.x(), self.mousePosLatLon.y())
            # valueStr = 'unknown'
        except:
            valueStr = 'unknown'

        self.setToolTip('lat: %f; lon: %f; value: %s' %
                        (posLat, posLon, valueStr))

        for shape in reversed([s for s in self.shapes if self.isVisible(s)]):
            # Look for a nearby vertex to highlight. If that fails,
            # check if we happen to be inside a shape.
            index = shape.nearestVertex(pos, self.epsilon)
            if index is not None:
                if self.selectedVertex():
                    self.hShape.highlightClear()
                self.hVertex, self.hShape = index, shape
                shape.highlightVertex(index, shape.MOVE_VERTEX)
                self.overrideCursor(CURSOR_POINT)
                self.setToolTip("Click & drag to move point")
                self.setStatusTip(self.toolTip())
                self.update()
                break
            elif shape.containsPoint(pos):
                if self.selectedVertex():
                    self.hShape.highlightClear()
                self.hVertex, self.hShape = None, shape
                self.setToolTip("Click & drag to move shape '%s'" %
                                shape.label)
                self.setStatusTip(self.toolTip())
                self.overrideCursor(CURSOR_GRAB)
                self.update()
                break
        else:  # Nothing found, clear highlights, reset state.
            if self.hShape:
                self.hShape.highlightClear()
                self.update()
            self.hVertex, self.hShape = None, None
            self.overrideCursor(CURSOR_DEFAULT)

        self.recalculateMovedLatlonPointsSelectedShape()

    def mousePressEvent(self, ev):
        pos = self.transformPos(ev.pos())

        if ev.button() == Qt.LeftButton:
            if self.drawing() | self.drawing_ellipse():
                self.handleDrawing(pos)
            else:
                self.selectShapePoint(pos)
                self.prevPoint = pos
                self.repaint()
        elif ev.button() == Qt.RightButton and self.editing():
            self.selectShapePoint(pos)
            self.prevPoint = pos
            self.repaint()

        self.recalculateMovedLatlonPointsSelectedShape()

    def mouseReleaseEvent(self, ev):
        if ev.button() == Qt.RightButton:
            menu = self.menus[bool(self.selectedShapeCopy)]
            self.restoreCursor()
            if not menu.exec_(self.mapToGlobal(
                    ev.pos())) and self.selectedShapeCopy:
                # Cancel the move by deleting the shadow copy.
                self.selectedShapeCopy = None
                self.repaint()
        elif ev.button() == Qt.LeftButton and self.selectedShape:
            if self.selectedVertex():
                self.overrideCursor(CURSOR_POINT)
            else:
                self.overrideCursor(CURSOR_GRAB)
            self.shapeMovesFinished.emit()
        elif ev.button() == Qt.LeftButton:
            pos = self.transformPos(ev.pos())
            if self.drawing():
                self.handleDrawing(pos)

        self.recalculateMovedLatlonPointsSelectedShape()

    # def endMove(self, copy=False):
    #     assert self.selectedShape and self.selectedShapeCopy
    #     shape = self.selectedShapeCopy
    #     if copy:
    #         self.shapes.append(shape)
    #         self.selectedShape.selected = False
    #         self.selectedShape = shape
    #         self.repaint()
    #     else:
    #         self.selectedShape.points = [p for p in shape.points]
    #     self.selectedShapeCopy = None
    #     self.recalculateMovedLatlonPointsSelectedShape()
    def endMove(self):
        assert self.selectedShape and self.selectedShapeCopy
        shape = self.selectedShapeCopy
        self.selectedShape.points = [p for p in shape.points]
        self.selectedShapeCopy = None
        self.recalculateMovedLatlonPointsSelectedShape()

    def hideBackroundShapes(self, value):
        self.hideBackround = value
        if self.selectedShape:
            # Only hide other shapes if there is a current selection.
            # Otherwise the user will not be able to select a shape.
            self.setHiding(True)
            self.repaint()

    def handleDrawing(self, pos):
        if self.current:
            self.current.addPoint(pos, self.transformToLatLon(pos, True))
            if len(self.current) == 3:
                self.current.close()
                self.finalise()
        elif not self.outOfPixmap(pos):
            self.current = Shape(parent_canvas=self)

            self.current.addPoint(pos, self.transformToLatLon(pos, True))

            self.recalculateMovedLatlonPointsSelectedShape()

            self.update()

    def setHiding(self, enable=True):
        self._hideBackround = self.hideBackround if enable else False

    def canCloseShape(self):
        return self.drawing() and self.current and len(self.current) > 2

    def mouseDoubleClickEvent(self, ev):
        # We need at least 4 points here, since the mousePress handler
        # adds an extra one before this handler is called.
        if self.canCloseShape() and len(self.current) > 3:
            self.current.popPoint()
            self.finalise()

    def selectShape(self, shape):
        self.deSelectShape()
        shape.selected = True
        self.selectedShape = shape
        self.setHiding()
        self.selectionChanged.emit(True)
        self.update()

    def selectShapePoint(self, point):
        """Select the first shape created which contains this point."""
        self.deSelectShape()
        if self.selectedVertex():  # A vertex is marked for selection.
            index, shape = self.hVertex, self.hShape
            shape.highlightVertex(index, shape.MOVE_VERTEX)
            self.selectShape(shape)
            return
        for shape in reversed(self.shapes):
            if self.isVisible(shape) and shape.containsPoint(point):
                self.selectShape(shape)
                self.calculateOffsets(shape, point)
                return

    def calculateOffsets(self, shape, point):
        rect = shape.boundingRect()
        x1 = rect.x() - point.x()
        y1 = rect.y() - point.y()
        x2 = (rect.x() + rect.width()) - point.x()
        y2 = (rect.y() + rect.height()) - point.y()
        self.offsets = QPointF(x1, y1), QPointF(x2, y2)

    def boundedMoveVertex(self, pos):
        index, shape = self.hVertex, self.hShape
        point = shape[index]
        if self.outOfPixmap(pos):
            pos = self.intersectionPoint(point, pos)

        shiftPos = pos - point
        shape.moveVertexBy(index, shiftPos)

    def boundedMoveShape(self, shape, pos):
        if self.outOfPixmap(pos):
            return False  # No need to move
        o1 = pos + self.offsets[0]
        if self.outOfPixmap(o1):
            pos -= QPointF(min(0, o1.x()), min(0, o1.y()))
        o2 = pos + self.offsets[1]
        if self.outOfPixmap(o2):
            pos += QPointF(min(0,
                               self.pixmap.width() - o2.x()),
                           min(0,
                               self.pixmap.height() - o2.y()))
        # The next line tracks the new position of the cursor
        # relative to the shape, but also results in making it
        # a bit "shaky" when nearing the border and allows it to
        # go outside of the shape's area for some reason. XXX
        #self.calculateOffsets(self.selectedShape, pos)
        dp = pos - self.prevPoint
        if dp:
            shape.moveBy(dp)
            self.prevPoint = pos
            return True
        return False

    def deSelectShape(self):
        if self.selectedShape:
            self.selectedShape.selected = False
            self.selectedShape = None
            self.setHiding(False)
            self.selectionChanged.emit(False)
            self.update()

    def deleteSelected(self):
        if self.selectedShape:
            shape = self.selectedShape
            self.shapes.remove(self.selectedShape)
            self.selectedShape = None
            self.update()
            return shape

    # def copySelectedShape(self):
    #     if self.selectedShape:
    #         shape = self.selectedShape.copy()
    #         self.deSelectShape()
    #         self.shapes.append(shape)
    #         shape.selected = True
    #         self.selectedShape = shape
    #         self.boundedShiftShape(shape)
    #         return shape

    def boundedShiftShape(self, shape):
        # Try to move in one direction, and if it fails in another.
        # Give up if both fail.
        point = shape[0]
        offset = QPointF(2.0, 2.0)
        self.calculateOffsets(shape, point)
        self.prevPoint = point
        if not self.boundedMoveShape(shape, point - offset):
            self.boundedMoveShape(shape, point + offset)

    def paintEvent(self, event):
        if not self.pixmap:
            return super(Canvas, self).paintEvent(event)

        p = self._painter
        p.begin(self)
        p.setRenderHint(QPainter.Antialiasing)
        p.setRenderHint(QPainter.HighQualityAntialiasing)
        p.setRenderHint(QPainter.SmoothPixmapTransform)

        p.scale(self.scale, self.scale)
        p.translate(self.offsetToCenter())

        p.drawPixmap(0, 0, self.pixmap)
        Shape.scale = self.scale
        for shape in self.shapes:
            if (shape.selected
                    or not self._hideBackround) and self.isVisible(shape):
                shape.fill = shape.selected or shape == self.hShape
                # shape.paint(p)
                shape.paint()
        if self.current:
            # self.current.paint(p)
            # self.line.paint(p)
            self.current.paint()
            # self.line.paint(self.current._painter)
        if self.selectedShapeCopy:
            # self.selectedShapeCopy.paint(p)
            self.selectedShapeCopy.paint()

        # Paint rect
        if self.current is not None and len(self.line) == 2:
            leftTop = self.line[0]
            rightBottom = self.line[1]
            rectWidth = rightBottom.x() - leftTop.x()
            rectHeight = rightBottom.y() - leftTop.y()
            p.setPen(self.drawingRectColor)
            brush = QBrush(Qt.BDiagPattern)
            p.setBrush(brush)
            p.drawRect(leftTop.x(), leftTop.y(), rectWidth, rectHeight)

        if self.drawing() and not self.prevPoint.isNull(
        ) and not self.outOfPixmap(self.prevPoint):
            p.setPen(QColor(0, 0, 0))
            p.drawLine(self.prevPoint.x(), 0, self.prevPoint.x(),
                       self.pixmap.height())
            p.drawLine(0, self.prevPoint.y(), self.pixmap.width(),
                       self.prevPoint.y())

        self.setAutoFillBackground(True)
        if self.verified:
            pal = self.palette()
            pal.setColor(self.backgroundRole(), QColor(184, 239, 38, 128))
            self.setPalette(pal)
        else:
            pal = self.palette()
            pal.setColor(self.backgroundRole(), QColor(232, 232, 232, 255))
            self.setPalette(pal)

        p.end()

    def transformPos(self, point):
        """Convert from widget-logical coordinates to painter-logical coordinates."""
        return point / self.scale - self.offsetToCenter()

    def transformToLatLon(self, PicturePoint, outputQPointF=False):
        lat, lon = 0., 0.
        try:
            bmhelper = self.parent.window().basemaphelper
            # bm = bmhelper.bm
            x_pic, y_pic = PicturePoint.x(), PicturePoint.y()
            # for cylindrical projections ONLY
            lon = (x_pic / self.pixmap.width()) * (
                bmhelper.urcrnrlon - bmhelper.llcrnrlon) + bmhelper.llcrnrlon
            lat = ((self.pixmap.height() - y_pic) / self.pixmap.height()) * (
                bmhelper.urcrnrlat - bmhelper.llcrnrlat) + bmhelper.llcrnrlat
        except:
            pass
        if outputQPointF:
            return QPointF(lon, lat)
        else:
            return lat, lon

    def transformLatLonToPixmapCoordinates(self, lon, lat):
        x_pic, y_pic = 0., 0.
        try:
            bmhelper = self.parent.window().basemaphelper
            # bm = bmhelper.bm
            pixmap_width = self.pixmap.width()
            pixmap_height = self.pixmap.height()
            # for cylindrical projections ONLY
            y_pic = pixmap_height * (1. - (lat - bmhelper.llcrnrlat) /
                                     (bmhelper.urcrnrlat - bmhelper.llcrnrlat))
            x_pic = pixmap_width * (lon - bmhelper.llcrnrlon) / (
                bmhelper.urcrnrlon - bmhelper.llcrnrlon)
        except:
            pass
        return x_pic, y_pic

    def recalculateMovedLatlonPointsSelectedShape(self):
        if self.selectedShape:
            self.selectedShape.latlonPoints = []
            for pt in self.selectedShape.points:
                latlonPt = self.transformToLatLon(pt, outputQPointF=True)
                self.selectedShape.latlonPoints.append(latlonPt)

    def offsetToCenter(self):
        s = self.scale
        area = super(Canvas, self).size()
        w, h = self.pixmap.width() * s, self.pixmap.height() * s
        aw, ah = area.width(), area.height()
        x = (aw - w) / (2 * s) if aw > w else 0
        y = (ah - h) / (2 * s) if ah > h else 0
        return QPointF(x, y)

    def outOfPixmap(self, p):
        w, h = self.pixmap.width(), self.pixmap.height()
        return not (0 <= p.x() <= w and 0 <= p.y() <= h)

    def finalise(self):
        assert self.current
        if self.current.points[0] == self.current.points[-1]:
            self.current = None
            self.drawingPolygon.emit(False)
            self.update()
            return

        self.current.close()
        self.shapes.append(self.current)
        self.current = None
        self.setHiding(False)
        self.newShape.emit()
        self.update()

    def closeEnough(self, p1, p2):
        return distance(p1 - p2) < self.epsilon

    def intersectionPoint(self, p1, p2):
        # Cycle through each image edge in clockwise fashion,
        # and find the one intersecting the current line segment.
        # http://paulbourke.net/geometry/lineline2d/
        size = self.pixmap.size()
        points = [(0, 0), (size.width(), 0), (size.width(), size.height()),
                  (0, size.height())]
        x1, y1 = p1.x(), p1.y()
        x2, y2 = p2.x(), p2.y()
        d, i, (x, y) = min(self.intersectingEdges((x1, y1), (x2, y2), points))
        x3, y3 = points[i]
        x4, y4 = points[(i + 1) % 4]
        if (x, y) == (x1, y1):
            # Handle cases where previous point is on one of the edges.
            if x3 == x4:
                return QPointF(x3, min(max(0, y2), max(y3, y4)))
            else:  # y3 == y4
                return QPointF(min(max(0, x2), max(x3, x4)), y3)
        return QPointF(x, y)

    def intersectingEdges(self, x1y1, x2y2, points):
        """For each edge formed by `points', yield the intersection
        with the line segment `(x1,y1) - (x2,y2)`, if it exists.
        Also return the distance of `(x2,y2)' to the middle of the
        edge along with its index, so that the one closest can be chosen."""
        x1, y1 = x1y1
        x2, y2 = x2y2
        for i in range(4):
            x3, y3 = points[i]
            x4, y4 = points[(i + 1) % 4]
            denom = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1)
            nua = (x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)
            nub = (x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)
            if denom == 0:
                # This covers two cases:
                #   nua == nub == 0: Coincident
                #   otherwise: Parallel
                continue
            ua, ub = nua / denom, nub / denom
            if 0 <= ua <= 1 and 0 <= ub <= 1:
                x = x1 + ua * (x2 - x1)
                y = y1 + ua * (y2 - y1)
                m = QPointF((x3 + x4) / 2, (y3 + y4) / 2)
                d = distance(m - QPointF(x2, y2))
                yield d, i, (x, y)

    # These two, along with a call to adjustSize are required for the
    # scroll area.
    def sizeHint(self):
        return self.minimumSizeHint()

    def minimumSizeHint(self):
        if self.pixmap:
            return self.scale * self.pixmap.size()
        return super(Canvas, self).minimumSizeHint()

    def wheelEvent(self, ev):
        qt_version = 4 if hasattr(ev, "delta") else 5
        if qt_version == 4:
            if ev.orientation() == Qt.Vertical:
                v_delta = ev.delta()
                h_delta = 0
            else:
                h_delta = ev.delta()
                v_delta = 0
        else:
            delta = ev.angleDelta()
            h_delta = delta.x()
            v_delta = delta.y()

        mods = ev.modifiers()
        if Qt.ControlModifier == int(mods) and v_delta:
            self.zoomRequest.emit(v_delta)
        else:
            v_delta and self.scrollRequest.emit(v_delta, Qt.Vertical)
            h_delta and self.scrollRequest.emit(h_delta, Qt.Horizontal)
        ev.accept()

    def keyPressEvent(self, ev):
        key = ev.key()
        if key == Qt.Key_Escape and self.current:
            print('ESC press')
            self.current = None
            self.drawingPolygon.emit(False)
            self.update()
        elif key == Qt.Key_Return and self.canCloseShape():
            self.finalise()
        elif key == Qt.Key_Left and self.selectedShape:
            self.moveOnePixel('Left')
        elif key == Qt.Key_Right and self.selectedShape:
            self.moveOnePixel('Right')
        elif key == Qt.Key_Up and self.selectedShape:
            self.moveOnePixel('Up')
        elif key == Qt.Key_Down and self.selectedShape:
            self.moveOnePixel('Down')

    def moveOnePixel(self, direction):
        # print(self.selectedShape.points)
        if direction == 'Left' and not self.moveOutOfBound(QPointF(-1.0, 0)):
            # print("move Left one pixel")
            self.selectedShape.points[0] += QPointF(-1.0, 0)
            self.selectedShape.points[1] += QPointF(-1.0, 0)
            self.selectedShape.points[2] += QPointF(-1.0, 0)
        elif direction == 'Right' and not self.moveOutOfBound(QPointF(1.0, 0)):
            # print("move Right one pixel")
            self.selectedShape.points[0] += QPointF(1.0, 0)
            self.selectedShape.points[1] += QPointF(1.0, 0)
            self.selectedShape.points[2] += QPointF(1.0, 0)
        elif direction == 'Up' and not self.moveOutOfBound(QPointF(0, -1.0)):
            # print("move Up one pixel")
            self.selectedShape.points[0] += QPointF(0, -1.0)
            self.selectedShape.points[1] += QPointF(0, -1.0)
            self.selectedShape.points[2] += QPointF(0, -1.0)
        elif direction == 'Down' and not self.moveOutOfBound(QPointF(0, 1.0)):
            # print("move Down one pixel")
            self.selectedShape.points[0] += QPointF(0, 1.0)
            self.selectedShape.points[1] += QPointF(0, 1.0)
            self.selectedShape.points[2] += QPointF(0, 1.0)

        self.recalculateMovedLatlonPointsSelectedShape()

        self.shapeMoved.emit()
        self.repaint()

    def moveOutOfBound(self, step):
        points = [
            p1 + p2 for p1, p2 in zip(self.selectedShape.points, [step] * 4)
        ]
        self.recalculateMovedLatlonPointsSelectedShape()
        return True in map(self.outOfPixmap, points)

    def setLastLabel(self, text, line_color=None, fill_color=None):
        assert text
        self.shapes[-1].label.name = text
        if line_color:
            self.shapes[-1].line_color = line_color

        if fill_color:
            self.shapes[-1].fill_color = fill_color

        return self.shapes[-1]

    def undoLastLine(self):
        assert self.shapes
        self.current = self.shapes.pop()
        self.current.setOpen()
        self.line.points = [self.current[-1], self.current[0]]
        self.drawingPolygon.emit(True)

    def resetAllLines(self):
        assert self.shapes
        self.current = self.shapes.pop()
        self.current.setOpen()
        self.line.points = [self.current[-1], self.current[0]]
        self.drawingPolygon.emit(True)
        self.current = None
        self.drawingPolygon.emit(False)
        self.update()

    def loadPixmap(self, pixmap, clearShapes=True):
        self.pixmap = pixmap
        if clearShapes:
            self.shapes = []
        self.repaint()

    def loadShapes(self, shapes, extend=False):
        if extend:
            self.shapes.extend(list(shapes))
        else:
            self.shapes = list(shapes)
        self.current = None
        self.repaint()

    def loadBasemapShapes(self, bmShapes):
        self.bmShapes = list(bmShapes)

    def setShapeVisible(self, shape, value):
        self.visible[shape] = value
        self.repaint()

    def switchBasemapAndMainShapes(self):
        self.temporaryShapes = self.shapes
        self.selectedTemporaryShape = self.selectedShape
        self.shapes = self.bmShapes
        self.selectedShape = self.selectedBmShape
        self.current = None
        self.repaint()

    def switchBackBasemapAndMainShapes(self):
        self.shapes = self.temporaryShapes
        self.selectedShape = self.selectedTemporaryShape
        self.current = None
        self.repaint()

    def currentCursor(self):
        cursor = QApplication.overrideCursor()
        if cursor is not None:
            cursor = cursor.shape()
        return cursor

    def overrideCursor(self, cursor):
        self._cursor = cursor
        if self.currentCursor() is None:
            QApplication.setOverrideCursor(cursor)
        else:
            QApplication.changeOverrideCursor(cursor)

    def restoreCursor(self):
        QApplication.restoreOverrideCursor()

    def resetState(self):
        self.restoreCursor()
        self.pixmap = None
        self.update()
コード例 #17
0
class Canvas(QWidget):
    drawingPolygon = pyqtSignal(bool)
    newShape = pyqtSignal()
    selectionChanged = pyqtSignal(bool)
    shapeMoved = pyqtSignal()
    finishedDrawing = pyqtSignal()

    epsilon = 11.0

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.__pixmap = QPixmap()
        self.__mode = PMode.IDLE
        self.__prevmode = PMode.IDLE
        self.__current = None
        self.__hShape = None
        self.__hVertex = None
        self.__shapes = []
        self.__drawingLineColor = QColor(45, 168, 179)
        self.__drawingRectColor = QColor(45, 168, 179)
        self.__line = Shape(line_color=self.drawingLineColor)
        self.__prevPoint = QPointF()
        self.__selectedShape = None
        self.__verified = False
        self.__hideBackground = False
        self.__toggleBackground = False
        self.__visible = {}
        self.scale = 1.0
        self._painter = QPainter()
        self._cursor = CURSOR_DEFAULT

        # Load string bundle for i18n
        self.__stringBundle = StringBundle.getBundle()

        self.setMouseTracking(True)
        self.setFocusPolicy(Qt.WheelFocus)

    ###########################################################################
    #                                S T A T E                                #
    ###########################################################################

    def resetState(self):
        self.restoreCursor()
        self.pixmap = None
        self.update()

    def updateInfoR(self, pos):
        currentMode = self.mode.name
        window = self.parent().window()
        if window.filePath is not None:
            self.parent().window().labelCoordinates.setText(
                'MODE: %s X: %d; Y: %d' % (currentMode, pos.x(), pos.y()))

    ###########################################################################
    #                                 E D I T                                 #
    ###########################################################################

    def setEditing(self, value=True):
        self.mode = PMode.EDIT if value else PMode.CREATE

    def drawing(self):
        return self.mode == PMode.CREATE

    def editing(self):
        return self.mode == PMode.EDIT

    ###########################################################################
    #                                P O S E S                                #
    ###########################################################################

    def transformPos(self, point):
        # Convert from widget-logical coord to painter-logical coord.
        return point / self.scale - self.offsetToCenter()

    def offsetToCenter(self):
        s = self.scale
        area = super(Canvas, self).size()
        w, h = self.pixmap.width() * s, self.pixmap.height() * s
        aw, ah = area.width(), area.height()
        x = (aw - w) / (2 * s) if aw > w else 0
        y = (ah - h) / (2 * s) if ah > h else 0
        return QPointF(x, y)

    ###########################################################################
    #                               C U R S O R                               #
    ###########################################################################

    def currentCursor(self):
        cursor = QApplication.overrideCursor()
        if cursor is not None:
            cursor = cursor.shape()
        return cursor

    def overrideCursor(self, cursor):
        self._cursor = cursor
        if self.currentCursor() is None:
            QApplication.setOverrideCursor(cursor)
        else:
            QApplication.changeOverrideCursor(cursor)

    def restoreCursor(self):
        QApplication.restoreOverrideCursor()

    def outOfPixmap(self, p):
        w, h = self.pixmap.width(), self.pixmap.height()
        return not (0 <= p.x() <= w and 0 <= p.y() <= h)

    ###########################################################################
    #                               S H A P E S                               #
    ###########################################################################

    def selectShape(self, shape):
        self.deSelectShape()
        shape.selected = True
        self.selectedShape = shape
        # self.setHiding()
        self.selectionChanged.emit(True)
        self.update()

    def selectShapePoint(self, point):
        """Select the first shape created which contains this point."""
        self.deSelectShape()
        if self.selectedVertex():  # A vertex is marked for selection.
            index, shape = self.hVertex, self.hShape
            shape.highlightVertex(index, shape.MOVE_VERTEX)
            self.selectShape(shape)
            return
        for shape in reversed(self.shapes):
            if self.isVisible(shape) and shape.containsPoint(point):
                self.selectShape(shape)
                self.calculateOffsets(shape, point)
                return

    def boundedMoveVertex(self, pos):
        index, shape = self.hVertex, self.hShape
        point = shape[index]
        if self.outOfPixmap(pos):
            size = self.pixmap.size()
            clipped_x = min(max(0, pos.x()), size.width())
            clipped_y = min(max(0, pos.y()), size.height())
            pos = QPointF(clipped_x, clipped_y)

        shiftPos = pos - point

        shape.moveVertexBy(index, shiftPos)

        lindex = (index + 1) % 4
        rindex = (index + 3) % 4
        lshift = None
        rshift = None
        if index % 2 == 0:
            rshift = QPointF(shiftPos.x(), 0)
            lshift = QPointF(0, shiftPos.y())
        else:
            lshift = QPointF(shiftPos.x(), 0)
            rshift = QPointF(0, shiftPos.y())
        shape.moveVertexBy(rindex, rshift)
        shape.moveVertexBy(lindex, lshift)

    def boundedMoveShape(self, shape, pos):
        if self.outOfPixmap(pos):
            return False  # No need to move
        o1 = pos + self.offsets[0]
        if self.outOfPixmap(o1):
            pos -= QPointF(min(0, o1.x()), min(0, o1.y()))
        o2 = pos + self.offsets[1]
        if self.outOfPixmap(o2):
            pos += QPointF(min(0,
                               self.pixmap.width() - o2.x()),
                           min(0,
                               self.pixmap.height() - o2.y()))
        # The next line tracks the new position of the cursor
        # relative to the shape, but also results in making it
        # a bit "shaky" when nearing the border and allows it to
        # go outside of the shape's area for some reason. XXX
        # self.calculateOffsets(self.selectedShape, pos)

        dp = pos - self.prevPoint
        if dp:
            shape.moveBy(dp)
            self.prevPoint = pos
            return True
        return False

    def deSelectShape(self):
        if self.selectedShape:
            self.selectedShape.selected = False
            self.selectedShape = None
            self.setHiding(False)
            self.selectionChanged.emit(False)
            self.update()

    def deleteSelected(self):
        if self.selectedShape:
            shape = self.selectedShape
            self.shapes.remove(self.selectedShape)
            self.selectedShape = None
            self.update()
            return shape

    def selectedVertex(self):
        return self.hVertex is not None

    def calculateOffsets(self, shape, point):
        rect = shape.boundingRect()
        x1 = rect.x() - point.x()
        y1 = rect.y() - point.y()
        x2 = (rect.x() + rect.width()) - point.x()
        y2 = (rect.y() + rect.height()) - point.y()
        self.offsets = QPointF(x1, y1), QPointF(x2, y2)

    def setShapeVisible(self, shape, value):
        self.visible[shape] = value
        self.repaint()

    ###########################################################################
    #                              D R A W I N G                              #
    ###########################################################################

    def loadPixmap(self, pixmap):
        self.pixmap = pixmap
        self.shapes = []
        self.repaint()

    def loadShapes(self, shapes):
        self.shapes = list(shapes)
        self.current = None
        self.repaint()

    def hideBackroundShapes(self, value):
        self.toggleBackground = value
        if self.selectedShape:
            # Only hide other shapes if there is a current selection.
            # Otherwise the user will not be able to select a shape.
            self.setHiding(True)
            self.repaint()

    def handleDrawing(self, pos):
        if self.current and self.current.reachMaxPoints() is False:
            initPos = self.current[0]
            minX = initPos.x()
            minY = initPos.y()
            targetPos = self.line[1]
            maxX = targetPos.x()
            maxY = targetPos.y()
            self.current.addPoint(QPointF(maxX, minY))
            self.current.addPoint(targetPos)
            self.current.addPoint(QPointF(minX, maxY))
            self.finalise()
        elif not self.outOfPixmap(pos):
            self.current = Shape()
            self.current.addPoint(pos)
            self.line.points = [pos, pos]
            self.setHiding()
            self.drawingPolygon.emit(True)
            self.update()

    def setHiding(self, enable=True):
        self.hideBackground = self.toggleBackground if enable else False

    def finalise(self):
        assert self.current
        if self.current.points[0] == self.current.points[-1]:
            self.current = None
            self.drawingPolygon.emit(False)
            self.update()
            return
        self.current.close()
        self.shapes.append(self.current)
        self.current = None
        self.setHiding(False)
        self.newShape.emit()
        self.update()

    def undoLastLine(self):
        assert self.shapes
        self.current = self.shapes.pop()
        self.current.setOpen()
        self.line.points = [self.current[-1], self.current[0]]
        self.drawingPolygon.emit(True)

    def resetAllLines(self):
        assert self.shapes
        self.current = self.shapes.pop()
        self.current.setOpen()
        self.line.points = [self.current[-1], self.current[0]]
        self.drawingPolygon.emit(True)
        self.current = None
        self.drawingPolygon.emit(False)
        self.update()

    def closeEnough(self, p1, p2):
        return distance(p1 - p2) < self.epsilon

    def isVisible(self, shape):
        return self.visible.get(shape, True)

    def setDrawingColor(self, qColor):
        self.drawingLineColor = qColor
        self.drawingRectColor = qColor

    def snapPointToCanvas(self, x, y):
        """
        Moves a point x,y to within the boundaries of the canvas.
        :return: (x,y,snapped) where snapped is True if x or y were changed,
        False if not.
        """
        if x < 0 or x > self.pixmap.width() or \
           y < 0 or y > self.pixmap.height():
            x = max(x, 0)
            y = max(y, 0)
            x = min(x, self.pixmap.width())
            y = min(y, self.pixmap.height())
            return x, y, True

        return x, y, False

    ###########################################################################
    #                               E V E N T S                               #
    ###########################################################################

    def enterEvent(self, ev):
        self.overrideCursor(self._cursor)

    def leaveEvent(self, ev):
        self.restoreCursor()

    def keyPressEvent(self, ev: QKeyEvent) -> None:
        if ev.key() == Qt.Key_Control:
            if self.mode == PMode.CREATE:
                self.setEditing(True)

    def keyReleaseEvent(self, ev: QKeyEvent) -> None:
        if ev.key() == Qt.Key_Control:
            if self.mode == PMode.EDIT:
                self.setEditing(False)
                if self.hShape:
                    self.hShape.highlightClear()
                self.update()
                self.hVertex, self.hShape = None, None

    def mouseMoveEvent(self, ev):
        """Update line with last point and current coordinates."""
        pos = self.transformPos(ev.pos())

        # Update coordinates in status bar if image is opened
        self.updateInfoR(pos)

        # Polygon drawing.
        if self.drawing():
            self.overrideCursor(CURSOR_DRAW)
            if self.hShape:
                self.hShape.highlightClear()
            if self.current:
                # Display annotation width and height while drawing
                currentWidth = abs(self.current[0].x() - pos.x())
                currentHeight = abs(self.current[0].y() - pos.y())
                self.parent().window().labelCoordinates.setText(
                    'Width: %d, Height: %d / X: %d; Y: %d' %
                    (currentWidth, currentHeight, pos.x(), pos.y()))

                color = self.drawingLineColor
                if self.outOfPixmap(pos):
                    # Don't allow the user to draw outside the pixmap.
                    # Clip the coordinates to 0 or max,
                    # if they are outside the range [0, max]
                    size = self.pixmap.size()
                    clipped_x = min(max(0, pos.x()), size.width())
                    clipped_y = min(max(0, pos.y()), size.height())
                    pos = QPointF(clipped_x, clipped_y)
                elif len(self.current) > 1 and \
                        self.closeEnough(pos, self.current[0]):
                    # Attract line to starting point and colorise to alert the
                    # user:
                    pos = self.current[0]
                    color = self.current.line_color
                    self.overrideCursor(CURSOR_POINT)
                    self.current.highlightVertex(0, Shape.NEAR_VERTEX)

                # if self.drawSquare:
                if False:
                    initPos = self.current[0]
                    minX = initPos.x()
                    minY = initPos.y()
                    min_size = min(abs(pos.x() - minX), abs(pos.y() - minY))
                    directionX = -1 if pos.x() - minX < 0 else 1
                    directionY = -1 if pos.y() - minY < 0 else 1
                    self.line[1] = QPointF(minX + directionX * min_size,
                                           minY + directionY * min_size)
                else:
                    self.line[1] = pos

                self.line.line_color = color
                self.prevPoint = QPointF()
                self.current.highlightClear()
            else:
                self.prevPoint = pos
            self.repaint()
            return

        if self.editing():
            # Polygon copy moving.
            if Qt.RightButton & ev.buttons() and False:
                if self.selectedShapeCopy and self.prevPoint:
                    self.overrideCursor(CURSOR_MOVE)
                    self.boundedMoveShape(self.selectedShapeCopy, pos)
                    self.repaint()
                elif self.selectedShape:
                    self.selectedShapeCopy = self.selectedShape.copy()
                    self.repaint()
                return

            # Polygon/Vertex moving.
            if Qt.LeftButton & ev.buttons():
                if self.selectedVertex():
                    self.boundedMoveVertex(pos)
                    self.shapeMoved.emit()
                    self.repaint()
                elif self.selectedShape and self.prevPoint:
                    self.overrideCursor(CURSOR_MOVE)
                    self.boundedMoveShape(self.selectedShape, pos)
                    self.shapeMoved.emit()
                    self.repaint()
                return

            for shape in reversed(
                [s for s in self.shapes if self.isVisible(s)]):
                # Look for a nearby vertex to highlight. If that fails,
                # check if we happen to be inside a shape.
                index = shape.nearestVertex(pos, self.epsilon)
                if index is not None:
                    if self.selectedVertex():
                        self.hShape.highlightClear()
                    self.hVertex, self.hShape = index, shape
                    shape.highlightVertex(index, shape.MOVE_VERTEX)
                    self.overrideCursor(CURSOR_POINT)
                    self.setToolTip("Click & drag to move point")
                    self.update()
                    break
                elif shape.containsPoint(pos):
                    if self.selectedVertex():
                        self.hShape.highlightClear()
                    self.hVertex, self.hShape = None, shape
                    self.setToolTip("Click & drag to move shape '%s'" %
                                    shape.label)
                    self.overrideCursor(CURSOR_GRAB)
                    self.update()
                    break
            else:  # Nothing found, clear highlights, reset state.
                if self.hShape:
                    self.hShape.highlightClear()
                self.update()
                self.hVertex, self.hShape = None, None
                self.overrideCursor(CURSOR_DEFAULT)

    def mousePressEvent(self, ev):
        pos = self.transformPos(ev.pos())

        if Qt.LeftButton == ev.button():
            if self.drawing():
                self.handleDrawing(pos)
                self.deSelectShape()
                if self.selectedVertex():
                    self.hShape.highlightClear()
                self.update()
            else:
                self.selectShapePoint(pos)
                self.prevPoint = pos
                self.repaint()
        else:
            pass

    def mouseReleaseEvent(self, ev):
        # if Qt.RightButton == ev.button():
        #     menu = self.menus[bool(self.selectedShapeCopy)]
        #     self.restoreCursor()
        #     if not menu.exec_(self.mapToGlobal(ev.pos()))\
        #        and self.selectedShapeCopy:
        #         # Cancel the move by deleting the shadow copy.
        #         self.selectedShapeCopy = None
        #         self.repaint()
        #     pass
        if Qt.LeftButton == ev.button() and self.selectedShape:
            if self.selectedVertex():
                self.overrideCursor(CURSOR_POINT)
            else:
                self.overrideCursor(CURSOR_GRAB)
        elif Qt.LeftButton == ev.button():
            pos = self.transformPos(ev.pos())
            if self.drawing():
                self.finishedDrawing.emit()
                self.handleDrawing(pos)

    def paintEvent(self, event):
        if not self.pixmap:
            return super(Canvas, self).paintEvent(event)

        p = self._painter
        p.begin(self)
        # #^^vv^^vv^^vv^^vv^^vv^^vv^^vv^^vv^^vv^^vv^^vv^^vv^^vv^^vv^^vv^^vv^# #
        # --------------------------- begin paint --------------------------- #
        # #^^vv^^vv^^vv^^vv^^vv^^vv^^vv^^vv^^vv^^vv^^vv^^vv^^vv^^vv^^vv^^vv^# #
        p.setRenderHint(QPainter.Antialiasing)
        p.setRenderHint(QPainter.HighQualityAntialiasing)
        p.setRenderHint(QPainter.SmoothPixmapTransform)

        p.scale(self.scale, self.scale)
        p.translate(self.offsetToCenter())

        p.drawPixmap(0, 0, self.pixmap)

        Shape.scale = self.scale
        for shape in self.shapes:
            if (shape.selected or not self.hideBackground) \
                    and self.isVisible(shape):
                shape.fill = shape.selected or shape == self.hShape
                shape.paint(p)
        if self.current:
            self.current.paint(p)
            self.line.paint(p)
        # if self.selectedShapeCopy:
        # self.selectedShapeCopy.paint(p)

        # Paint rect
        if self.current is not None and len(self.line) == 2:
            leftTop = self.line[0]
            rightBottom = self.line[1]
            rectWidth = rightBottom.x() - leftTop.x()
            rectHeight = rightBottom.y() - leftTop.y()
            p.setPen(self.drawingRectColor)
            brush = QBrush(Qt.BDiagPattern)
            p.setBrush(brush)
            p.drawRect(leftTop.x(), leftTop.y(), rectWidth, rectHeight)

        if self.drawing() and not self.prevPoint.isNull() \
                and not self.outOfPixmap(self.prevPoint):
            p.setPen(QColor(0, 0, 0))
            p.drawLine(self.prevPoint.x(), 0, self.prevPoint.x(),
                       self.pixmap.height())
            p.drawLine(0, self.prevPoint.y(), self.pixmap.width(),
                       self.prevPoint.y())

        self.setAutoFillBackground(True)
        if self.verified:
            pal = self.palette()
            pal.setColor(self.backgroundRole(), QColor(184, 239, 38, 128))
            self.setPalette(pal)
        else:
            pal = self.palette()
            pal.setColor(self.backgroundRole(), QColor(232, 232, 232, 255))
            self.setPalette(pal)
        # #^^vv^^vv^^vv^^vv^^vv^^vv^^vv^^vv^^vv^^vv^^vv^^vv^^vv^^vv^^vv^^vv^# #
        # ---------------------------- end paint ---------------------------- #
        # #^^vv^^vv^^vv^^vv^^vv^^vv^^vv^^vv^^vv^^vv^^vv^^vv^^vv^^vv^^vv^^vv^# #
        p.end()

    ###########################################################################
    #                               G E T T E R                               #
    ###########################################################################

    def __getStr(self, strId):
        return self.__stringBundle.getString(strId)

    def __getVerified(self):
        return self.__verified

    def __getPixmap(self):
        return self.__pixmap

    def __getShapes(self):
        return self.__shapes

    def __getMode(self):
        return self.__mode

    def __getCurrent(self):
        return self.__current

    def __getLine(self):
        return self.__line

    def __getPrevPoint(self):
        return self.__prevPoint

    def __getDrawingLineColor(self):
        return self.__drawingLineColor

    def __getDrawingRectColor(self):
        return self.__drawingRectColor

    def __getSelectedShape(self):
        return self.__selectedShape

    def __getHideBackground(self):
        return self.__hideBackground

    def __getToggleBackground(self):
        return self.__toggleBackground

    def __getVisible(self):
        return self.__visible

    def __getHShape(self):
        return self.__hShape

    def __getHVertex(self):
        return self.__hVertex

    ###########################################################################
    #                               S E T T E R                               #
    ###########################################################################

    def setLastLabel(self, text, line_color=None, fill_color=None):
        assert text
        self.shapes[-1].label = text
        if line_color:
            self.shapes[-1].line_color = line_color

        if fill_color:
            self.shapes[-1].fill_color = fill_color

        return self.shapes[-1]

    def __setVerified(self, x):
        if isinstance(x, bool):
            self.__verified = x
        else:
            raise ValueError(x, self.__getStr('boolE'))

    def __setPixmap(self, x):
        if isinstance(x, QPixmap) or x is None:
            self.__pixmap = x
        else:
            raise ValueError(x, self.__getStr('pixmapE'))

    def __setShapes(self, x):
        self.__shapes = x

    def __setMode(self, x):
        if isinstance(x, PMode):
            self.__mode = x
        else:
            raise ValueError(x, self.__getStr('modeE'))

    def __setCurrent(self, x):
        if isinstance(x, Shape) or x is None:
            self.__current = x
        else:
            raise ValueError(x, self.__getStr('shapeE'))

    def __setLine(self, x):
        if isinstance(x, Shape) or x is None:
            self.__line = x
        else:
            raise ValueError(x, self.__getStr('shapeE'))

    def __setPrevPoint(self, x):
        if isinstance(x, QPointF):
            self.__prevPoint = x
        else:
            raise ValueError(x, self.__getStr('qpointfE'))

    def __setdrawingLineColor(self, x):
        if isinstance(x, QColor):
            self.__drawingLineColor = x
        else:
            raise ValueError(x, self.__getStr('colorE'))

    def __setdrawingRectColor(self, x):
        if isinstance(x, QColor):
            self.__drawingRectColor = x
        else:
            raise ValueError(x, self.__getStr('colorE'))

    def __setSelectedShape(self, x):
        if isinstance(x, Shape) or x is None:
            self.__selectedShape = x
        else:
            raise ValueError(x, self.__getStr('shapeE'))

    def __setHideBackground(self, x):
        if isinstance(x, bool):
            self.__hideBackground = x
        else:
            raise ValueError(x, self.__getStr('boolE'))

    def __setToggleBackground(self, x):
        if isinstance(x, bool):
            self.__toggleBackground = x
        else:
            raise ValueError(x, self.__getStr('boolE'))

    def __setVisible(self, x):
        if isinstance(x, bool):
            self.__visible = x
        else:
            raise ValueError(x, self.__getStr('dictE'))

    def __setHShape(self, x):
        if isinstance(x, Shape) or x is None:
            self.__hShape = x
        else:
            raise ValueError(x, self.__getStr('shapeE'))

    def __setHVertex(self, x):
        if isinstance(x, int) or x is None:
            self.__hVertex = x
        else:
            raise ValueError(x, self.__getStr('intE'))

    ###########################################################################
    #                           P R O P E R T I E S                           #
    ###########################################################################

    verified = property(__getVerified, __setVerified)
    pixmap = property(__getPixmap, __setPixmap)
    shapes = property(__getShapes, __setShapes)
    mode = property(__getMode, __setMode)
    current = property(__getCurrent, __setCurrent)
    line = property(__getLine, __setLine)
    prevPoint = property(__getPrevPoint, __setPrevPoint)
    drawingLineColor = property(__getDrawingLineColor, __setdrawingLineColor)
    drawingRectColor = property(__getDrawingRectColor, __setdrawingRectColor)
    selectedShape = property(__getSelectedShape, __setSelectedShape)
    hideBackground = property(__getHideBackground, __setHideBackground)
    toggleBackground = property(__getToggleBackground, __setToggleBackground)
    visible = property(__getVisible, __setVisible)
    hShape = property(__getHShape, __setHShape)
    hVertex = property(__getHVertex, __setHVertex)
コード例 #18
0
ファイル: canvas.py プロジェクト: mmcgg/OpenCV-1
class Canvas(QWidget):
    zoomRequest = pyqtSignal(int)
    scrollRequest = pyqtSignal(int, int)
    newShape = pyqtSignal()
    selectionChanged = pyqtSignal(bool)
    shapeMoved = pyqtSignal()
    drawingPolygon = pyqtSignal(bool)

    # CREATE, EDIT = list(range(2))

    #byMe
    CREATE, EDIT, POLYGON = list(range(3))
    #

    epsilon = 11.0

    def __init__(self, *args, **kwargs):
        super(Canvas, self).__init__(*args, **kwargs)
        # Initialise local state.
        self.mode = self.EDIT
        self.shapes = []
        self.current = None
        self.selectedShape = None  # save the selected shape here
        self.selectedShapeCopy = None
        self.drawingLineColor = QColor(0, 0, 255)
        self.drawingRectColor = QColor(0, 0, 255)
        self.line = Shape(line_color=self.drawingLineColor)
        self.prevPoint = QPointF()
        self.offsets = QPointF(), QPointF()
        self.scale = 1.0
        self.pixmap = QPixmap()
        self.visible = {}
        self._hideBackround = False
        self.hideBackround = False
        self.hShape = None
        self.hVertex = None

        self.hPolygon = None
        self.hVertexPolygon = None
        self.selectedPolygon = None
        self.selectedPolygonCopy = None

        self._painter = QPainter()
        self._endPaint = False
        self._cursor = CURSOR_DEFAULT
        # Menus:
        self.menus = (QMenu(), QMenu())
        # Set widget options.
        self.setMouseTracking(True)
        self.setFocusPolicy(Qt.WheelFocus)
        self.verified = False
        self.drawSquare = False

        #===byMe==========
        self.text = []
        self.locText = []
        self.drawingTextColor = Qt.red
        self.fontText = QFont("Arial", 20)
        # self.fontText.setItalic(True)
        self.polyPrev = QPointF()
        self.polyNext = QPointF()
        self.polygons = []
        self.currentPolygon = None

        #===byMe==========

    def setDrawingColor(self, qColor):
        self.drawingLineColor = qColor
        self.drawingRectColor = qColor

    def enterEvent(self, ev):
        self.overrideCursor(self._cursor)

    def leaveEvent(self, ev):
        self.restoreCursor()

    def focusOutEvent(self, ev):
        self.restoreCursor()

    def isVisible(self, shape):
        return self.visible.get(shape, True)

    def drawing(self):
        return self.mode == self.CREATE

    def editing(self):
        return self.mode == self.EDIT

    def drawPolygon(self):
        return self.mode == self.POLYGON

    def setDrawPolygon(self):
        self.mode = self.POLYGON

    def setEditing(self, value=True):
        self.mode = self.EDIT if value else self.CREATE
        if not value:  # Create
            self.unHighlight()
            self.deSelectShape()
        self.prevPoint = QPointF()
        self.repaint()

    def unHighlight(self):
        if self.hShape:
            self.hShape.highlightClear()
        self.hVertex = self.hShape = None

    def selectedVertex(self):
        return self.hVertex is not None

    def mouseMoveEvent(self, ev):
        """Update line with last point and current coordinates."""
        pos = self.transformPos(ev.pos())

        # Update coordinates in status bar if image is opened
        window = self.parent().window()
        if window.filePath is not None:
            self.parent().window().labelCoordinates.setText('X: %d; Y: %d' %
                                                            (pos.x(), pos.y()))

        #byMe
        if self.drawPolygon():
            self.overrideCursor(CURSOR_DRAW_POLYGON)
            self.polyNext = pos
            if self.currentPolygon:
                if self.outOfPixmap(pos):
                    # Don't allow the user to draw outside the pixmap.
                    # Project the point to the pixmap's edges.
                    # pos = self.intersectionPoint(self.currentPolygon[-1], pos)
                    pass
                elif len(self.currentPolygon) > 1 and self.closeEnough(
                        pos, self.currentPolygon[0]):
                    # self.currentPolygon.highlightVertex(0, Shape.NEAR_VERTEX)
                    pass

                # self.currentPolygon.highlightClear()

            self.repaint()
            return
        #
        # rect drawing.
        if self.drawing():
            self.overrideCursor(CURSOR_DRAW)
            if self.current:
                color = self.drawingLineColor
                if self.outOfPixmap(pos):
                    # Don't allow the user to draw outside the pixmap.
                    # Project the point to the pixmap's edges.
                    pos = self.intersectionPoint(self.current[-1], pos)
                elif len(self.current) > 1 and self.closeEnough(
                        pos, self.current[0]):
                    # Attract line to starting point and colorise to alert the
                    # user:
                    pos = self.current[0]
                    color = self.current.line_color
                    self.overrideCursor(CURSOR_POINT)
                    self.current.highlightVertex(0, Shape.NEAR_VERTEX)

                if self.drawSquare:
                    initPos = self.current[0]
                    minX = initPos.x()
                    minY = initPos.y()
                    min_size = min(abs(pos.x() - minX), abs(pos.y() - minY))
                    directionX = -1 if pos.x() - minX < 0 else 1
                    directionY = -1 if pos.y() - minY < 0 else 1
                    self.line[1] = QPointF(minX + directionX * min_size,
                                           minY + directionY * min_size)
                else:
                    self.line[1] = pos

                self.line.line_color = color
                self.prevPoint = QPointF()
                self.current.highlightClear()
            else:
                self.prevPoint = pos
            self.repaint()
            return

        # Polygon copy moving.
        if Qt.RightButton & ev.buttons():
            if self.selectedShapeCopy and self.prevPoint:
                self.overrideCursor(CURSOR_MOVE)
                self.boundedMoveShape(self.selectedShapeCopy, pos)
                self.repaint()
            elif self.selectedShape:
                self.selectedShapeCopy = self.selectedShape.copy()
                self.repaint()

            #byMe ======
            if self.selectedPolygonCopy and self.polyPrev:
                self.overrideCursor(CURSOR_MOVE)
                self.boundedMovePolygon(self.selectedPolygonCopy, pos)
                self.repaint()
            elif self.selectedPolygon:
                self.selectedPolygonCopy = self.selectedPolygon.copy()
                self.repaint()
            #=========
            return

        # Polygon/Vertex moving.
        if Qt.LeftButton & ev.buttons():
            if self.selectedVertex():
                self.boundedMoveVertex(pos)
                self.shapeMoved.emit()
                self.repaint()
            elif self.selectedShape and self.prevPoint:
                self.overrideCursor(CURSOR_MOVE)
                self.boundedMoveShape(self.selectedShape, pos)
                self.shapeMoved.emit()
                self.repaint()

            #byMe=====
            if self.selectedVertexPolygon():
                self.boundedMoveVertexPolygon(pos)
                # self.shapeMoved.emit()
                self.repaint()
            elif self.selectedPolygon and self.polyPrev:
                self.overrideCursor(CURSOR_MOVE)
                self.boundedMovePolygon(self.selectedPolygon, pos)
                # self.shapeMoved.emit()
                self.repaint()
            #=============
            return

        # Just hovering over the canvas, 2 posibilities:
        # - Highlight shapes
        # - Highlight vertex
        # Update shape/vertex fill and tooltip value accordingly.
        self.setToolTip("Image")

        bNearestPoit = False
        bContains = False

        for polygon in self.polygons:
            # Look for a nearby vertex to highlight. If that fails,
            # check if we happen to be inside a shape.
            index = polygon.nearestVertex(pos, self.epsilon)
            if index is not None:
                # if len
                bNearestPoit = True
                # if self.selectedVertex():
                # self.hShape.highlightClear()
                self.hVertexPolygon, self.hPolygon = index, polygon
                polygon.highlightVertex(index, polygon.MOVE_VERTEX)
                self.overrideCursor(CURSOR_POINT)
                self.setToolTip("Click & drag to move point")
                self.setStatusTip(self.toolTip())
                self.update()

            elif polygon.containsPoint(pos):
                bContains = True
                # polygon.highlightClear()
                # if self.selectedVertex():
                # self.hShape.highlightClear()
                self.hVertexPolygon, self.hPolygon = None, polygon
                polygon.fill_color = QColor(50, 200, 0, 128)
                polygon.fill = True
                self.setToolTip("Click & drag to move polygon")
                self.setStatusTip(self.toolTip())
                self.overrideCursor(CURSOR_GRAB)
                self.update()
                break

        else:  # Nothing found, clear highlights, reset state.
            if not bNearestPoit and not bContains:
                if self.hPolygon:
                    self.hPolygon.fill = False
                    self.hPolygon.highlightClear()
                    self.update()
                self.hVertexPolygon, self.hPolygon = None, None
                self.overrideCursor(CURSOR_DEFAULT)

        for shape in reversed([s for s in self.shapes if self.isVisible(s)]):
            # Look for a nearby vertex to highlight. If that fails,
            # check if we happen to be inside a shape.
            index = shape.nearestVertex(pos, self.epsilon)
            if index is not None:
                bNearestPoit = True
                if self.selectedVertex():
                    self.hShape.highlightClear()
                self.hVertex, self.hShape = index, shape
                shape.highlightVertex(index, shape.MOVE_VERTEX)
                self.overrideCursor(CURSOR_POINT)
                self.setToolTip("Click & drag to move point")
                self.setStatusTip(self.toolTip())
                self.update()
                break
            elif shape.containsPoint(pos):
                bContains = True
                if self.selectedVertex():
                    self.hShape.highlightClear()
                self.hVertex, self.hShape = None, shape
                self.setToolTip("Click & drag to move shape '%s'" %
                                shape.label)
                self.setStatusTip(self.toolTip())
                self.overrideCursor(CURSOR_GRAB)
                self.update()
                break
        else:  # Nothing found, clear highlights, reset state.
            if not bNearestPoit and not bContains:
                if self.hShape:
                    self.hShape.highlightClear()
                    self.update()
                self.hVertex, self.hShape = None, None
                self.overrideCursor(CURSOR_DEFAULT)

    def mousePressEvent(self, ev):
        pos = self.transformPos(ev.pos())

        if ev.button() == Qt.LeftButton:
            # byme
            if self.drawPolygon():
                self.handleDrawingPolygon(pos, self.epsilon / self.scale)
            else:
                self.selectPolygonPoint(pos)
                self.polyPrev = pos

            if self.drawing():
                self.handleDrawing(pos)
            else:
                self.selectShapePoint(pos)
                self.prevPoint = pos
                self.repaint()
        elif ev.button() == Qt.RightButton:
            self.selectShapePoint(pos)
            self.prevPoint = pos
            self.repaint()

    def mouseReleaseEvent(self, ev):
        if ev.button() == Qt.RightButton:
            menu = self.menus[bool(self.selectedShapeCopy)]
            self.restoreCursor()
            if not menu.exec_(self.mapToGlobal(ev.pos()))\
               and self.selectedShapeCopy:
                # Cancel the move by deleting the shadow copy.
                self.selectedShapeCopy = None
                self.repaint()
        elif ev.button() == Qt.LeftButton and self.selectedShape:
            if self.selectedVertex():
                self.overrideCursor(CURSOR_POINT)
            else:
                self.overrideCursor(CURSOR_GRAB)
        elif ev.button() == Qt.LeftButton:
            pos = self.transformPos(ev.pos())
            if self.drawing():
                self.handleDrawing(pos)

    def endMove(self, copy=False):
        assert self.selectedShape and self.selectedShapeCopy
        shape = self.selectedShapeCopy
        #del shape.fill_color
        #del shape.line_color
        if copy:
            self.shapes.append(shape)
            self.selectedShape.selected = False
            self.selectedShape = shape
            self.repaint()
        else:
            self.selectedShape.points = [p for p in shape.points]
        self.selectedShapeCopy = None

    def hideBackroundShapes(self, value):
        self.hideBackround = value
        if self.selectedShape:
            # Only hide other shapes if there is a current selection.
            # Otherwise the user will not be able to select a shape.
            self.setHiding(True)
            self.repaint()

    #====byme
    def handleDrawingPolygon(self, pos, epsilon=1):
        self.polyPrev = pos
        if self.currentPolygon is None:
            self.currentPolygon = Polygon()
            self.currentPolygon.addPoint(pos)
            # self.currentPolygon.highlightVertex(0,self.currentPolygon.MOVE_VERTEX)
        else:
            self.currentPolygon.addPoint(pos)
            if self.currentPolygon.reachMaxPoints(epsilon):
                self.currentPolygon.popPoint()
                self.currentPolygon._closed = True
                self.polygons.append(self.currentPolygon)
                # for i in range(len(self.currentPolygon)):
                #     self.currentPolygon.highlightVertex(i,self.currentPolygon.MOVE_VERTEX)
                self.currentPolygon = None
                self.mode = self.EDIT

            # self.polyPoints.append(pos)
        # self.update()
        pass

    #===========
    def handleDrawing(self, pos):
        if self.current and self.current.reachMaxPoints() is False:
            initPos = self.current[0]
            minX = initPos.x()
            minY = initPos.y()
            targetPos = self.line[1]
            maxX = targetPos.x()
            maxY = targetPos.y()
            self.current.addPoint(QPointF(maxX, minY))
            self.current.addPoint(targetPos)
            self.current.addPoint(QPointF(minX, maxY))
            # self.parent().window().showRectOfShape(self.current)
            self.finalise()
        elif not self.outOfPixmap(pos):
            self.current = Shape()
            self.current.addPoint(pos)
            self.line.points = [pos, pos]
            self.setHiding()
            self.drawingPolygon.emit(True)
            self.update()

    def setHiding(self, enable=True):
        self._hideBackround = self.hideBackround if enable else False

    def canCloseShape(self):
        return self.drawing() and self.current and len(self.current) > 2

    def mouseDoubleClickEvent(self, ev):
        # We need at least 4 points here, since the mousePress handler
        # adds an extra one before this handler is called.
        if self.canCloseShape() and len(self.current) > 3:
            self.current.popPoint()
            self.finalise()

    def selectShape(self, shape):
        self.deSelectShape()
        shape.selected = True
        self.selectedShape = shape
        self.setHiding()
        self.selectionChanged.emit(True)
        self.update()

    def selectShapePoint(self, point):
        """Select the first shape created which contains this point."""
        self.deSelectShape()
        if self.selectedVertex():  # A vertex is marked for selection.
            index, shape = self.hVertex, self.hShape
            shape.highlightVertex(index, shape.MOVE_VERTEX)
            self.selectShape(shape)
            return
        for shape in reversed(self.shapes):
            if self.isVisible(shape) and shape.containsPoint(point):
                self.selectShape(shape)
                self.calculateOffsets(shape, point)
                return

    #byMe========
    def selectedVertexPolygon(self):
        return self.hVertexPolygon is not None

    def deSelectPolygon(self):
        if self.selectedPolygon:
            self.selectedPolygon.selected = False
            self.selectedPolygon = None
            self.setHiding(False)
            # self.selectionChanged.emit(False)
            self.update()

    def selectPolygon(self, polygon):
        self.deSelectPolygon()
        polygon.selected = True
        self.selectedPolygon = polygon
        self.setHiding()
        # self.selectionChanged.emit(True)
        self.update()

    def selectPolygonPoint(self, point):
        """Select the first shape created which contains this point."""
        self.deSelectPolygon()
        if self.selectedVertexPolygon():  # A vertex is marked for selection.
            index, polygon = self.hVertex, self.hPolygon
            polygon.highlightVertex(index, polygon.MOVE_VERTEX)
            self.selectPolygon(polygon)
            return
        for polygon in reversed(self.polygons):
            if polygon.containsPoint(point):
                self.selectPolygon(polygon)
                self.calculateOffsets(polygon, point)
                return

    #===============

    def calculateOffsets(self, shape, point):
        rect = shape.boundingRect()
        x1 = rect.x() - point.x()
        y1 = rect.y() - point.y()
        x2 = (rect.x() + rect.width()) - point.x()
        y2 = (rect.y() + rect.height()) - point.y()
        self.offsets = QPointF(x1, y1), QPointF(x2, y2)

    def snapPointToCanvas(self, x, y):
        """
        Moves a point x,y to within the boundaries of the canvas.
        :return: (x,y,snapped) where snapped is True if x or y were changed, False if not.
        """
        if x < 0 or x > self.pixmap.width() or y < 0 or y > self.pixmap.height(
        ):
            x = max(x, 0)
            y = max(y, 0)
            x = min(x, self.pixmap.width())
            y = min(y, self.pixmap.height())
            return x, y, True

        return x, y, False

    def boundedMoveVertex(self, pos):
        index, shape = self.hVertex, self.hShape
        point = shape[index]
        if self.outOfPixmap(pos):
            pos = self.intersectionPoint(point, pos)

        if self.drawSquare:
            opposite_point_index = (index + 2) % 4
            opposite_point = shape[opposite_point_index]

            min_size = min(abs(pos.x() - opposite_point.x()),
                           abs(pos.y() - opposite_point.y()))
            directionX = -1 if pos.x() - opposite_point.x() < 0 else 1
            directionY = -1 if pos.y() - opposite_point.y() < 0 else 1
            shiftPos = QPointF(
                opposite_point.x() + directionX * min_size - point.x(),
                opposite_point.y() + directionY * min_size - point.y())
        else:
            shiftPos = pos - point

        shape.moveVertexBy(index, shiftPos)

        lindex = (index + 1) % 4
        rindex = (index + 3) % 4
        lshift = None
        rshift = None
        if index % 2 == 0:
            rshift = QPointF(shiftPos.x(), 0)
            lshift = QPointF(0, shiftPos.y())
        else:
            lshift = QPointF(shiftPos.x(), 0)
            rshift = QPointF(0, shiftPos.y())
        shape.moveVertexBy(rindex, rshift)
        shape.moveVertexBy(lindex, lshift)

    def boundedMoveShape(self, shape, pos):
        if self.outOfPixmap(pos):
            return False  # No need to move
        o1 = pos + self.offsets[0]
        if self.outOfPixmap(o1):
            pos -= QPointF(min(0, o1.x()), min(0, o1.y()))
        o2 = pos + self.offsets[1]
        if self.outOfPixmap(o2):
            pos += QPointF(min(0,
                               self.pixmap.width() - o2.x()),
                           min(0,
                               self.pixmap.height() - o2.y()))
        # The next line tracks the new position of the cursor
        # relative to the shape, but also results in making it
        # a bit "shaky" when nearing the border and allows it to
        # go outside of the shape's area for some reason. XXX
        #self.calculateOffsets(self.selectedShape, pos)
        dp = pos - self.prevPoint
        if dp:
            shape.moveBy(dp)
            self.prevPoint = pos
            return True
        return False

    #byMe Move Polygon
    def boundedMoveVertexPolygon(self, pos):
        index, shape = self.hVertexPolygon, self.hPolygon
        point = shape[index]
        if self.outOfPixmap(pos):
            pos = self.intersectionPoint(point, pos)

        if self.drawSquare:
            opposite_point_index = (index + 2) % 4
            opposite_point = shape[opposite_point_index]

            min_size = min(abs(pos.x() - opposite_point.x()),
                           abs(pos.y() - opposite_point.y()))
            directionX = -1 if pos.x() - opposite_point.x() < 0 else 1
            directionY = -1 if pos.y() - opposite_point.y() < 0 else 1
            shiftPos = QPointF(
                opposite_point.x() + directionX * min_size - point.x(),
                opposite_point.y() + directionY * min_size - point.y())
        else:
            shiftPos = pos - point

        shape.moveVertexBy(index, shiftPos)

        lindex = (index + 1) % len(shape)
        rindex = (index + 3) % len(shape)
        lshift = None
        rshift = None
        if index % 2 == 0:
            rshift = QPointF(shiftPos.x(), 0)
            lshift = QPointF(0, shiftPos.y())
        else:
            lshift = QPointF(shiftPos.x(), 0)
            rshift = QPointF(0, shiftPos.y())
        shape.moveVertexBy(rindex, rshift)
        shape.moveVertexBy(lindex, lshift)

    def boundedMovePolygon(self, shape, pos):
        if self.outOfPixmap(pos):
            return False  # No need to move
        o1 = pos + self.offsets[0]
        if self.outOfPixmap(o1):
            pos -= QPointF(min(0, o1.x()), min(0, o1.y()))
        o2 = pos + self.offsets[1]
        if self.outOfPixmap(o2):
            pos += QPointF(min(0,
                               self.pixmap.width() - o2.x()),
                           min(0,
                               self.pixmap.height() - o2.y()))
        # The next line tracks the new position of the cursor
        # relative to the shape, but also results in making it
        # a bit "shaky" when nearing the border and allows it to
        # go outside of the shape's area for some reason. XXX
        #self.calculateOffsets(self.selectedShape, pos)
        dp = pos - self.polyPrev
        if dp:
            shape.moveBy(dp)
            self.polyPrev = pos
            return True
        return False

    #==================

    def deSelectShape(self):
        if self.selectedShape:
            self.selectedShape.selected = False
            self.selectedShape = None
            self.setHiding(False)
            self.selectionChanged.emit(False)
            self.update()

    def deleteSelected(self):
        if self.selectedShape:
            shape = self.selectedShape
            self.shapes.remove(self.selectedShape)
            self.selectedShape = None
            self.update()
            return shape

    def copySelectedShape(self):
        if self.selectedShape:
            shape = self.selectedShape.copy()
            self.deSelectShape()
            self.shapes.append(shape)
            shape.selected = True
            self.selectedShape = shape
            self.boundedShiftShape(shape)
            return shape

    def boundedShiftShape(self, shape):
        # Try to move in one direction, and if it fails in another.
        # Give up if both fail.
        point = shape[0]
        offset = QPointF(2.0, 2.0)
        self.calculateOffsets(shape, point)
        self.prevPoint = point
        if not self.boundedMoveShape(shape, point - offset):
            self.boundedMoveShape(shape, point + offset)

    def paintEvent(self, event):
        if not self.pixmap:
            return super(Canvas, self).paintEvent(event)

        self._endPaint = True
        p = self._painter
        p.begin(self)
        p.setRenderHint(QPainter.Antialiasing)
        p.setRenderHint(QPainter.HighQualityAntialiasing)
        p.setRenderHint(QPainter.SmoothPixmapTransform)

        p.scale(self.scale, self.scale)
        p.translate(self.offsetToCenter())

        p.drawPixmap(0, 0, self.pixmap)
        Shape.scale = self.scale
        #byMe
        Polygon.scale = self.scale
        #
        for shape in self.shapes:
            if (shape.selected
                    or not self._hideBackround) and self.isVisible(shape):
                shape.fill = shape.selected or shape == self.hShape
                shape.paint(p)
        if self.current:
            self.current.paint(p)
            self.line.paint(p)
        if self.selectedShapeCopy:
            self.selectedShapeCopy.paint(p)

        #===byMe==========
        #paint text
        for txt, loc in zip(self.text, self.locText):
            if loc is not None and txt is not None:
                p.setPen(self.drawingTextColor)
                p.setFont(self.fontText)
                if isinstance(loc, QPoint):
                    p.drawText(loc, txt)
                else:
                    p.drawText(loc, Qt.AlignLeft, txt)

        #paint polygons
        for polygon in self.polygons:
            # if (polygon.selected or not self._hideBackround) and self.isVisible(polygon):
            # polygon.fill = True
            polygon.paint(p)
        if self.currentPolygon:
            self.currentPolygon.paint(p)

        # paint polygon
        if self.currentPolygon is not None:
            pen = QPen(self.drawingRectColor)
            pen.setWidth(max(1, int(round(2.0 / self.scale))))
            p.setPen(pen)
            p.drawLine(self.polyPrev, self.polyNext)
        #     p.drawEllipse(self.polyNext,self.currentPolygon.point_size/2,self.currentPolygon.point_size/2)
        #     # p.setBrush(Qt.BDiagPattern)
        #     # points = self.currentPolygon.points
        #     # p.drawPolygon(QPolygonF(points))
        #     # p.setPen(Qt.black)

        #=============
        # Paint rect
        if self.current is not None and len(self.line) == 2:
            leftTop = self.line[0]
            rightBottom = self.line[1]
            rectWidth = rightBottom.x() - leftTop.x()
            rectHeight = rightBottom.y() - leftTop.y()
            # p.setPen(self.drawingRectColor)
            pen = QPen(self.drawingRectColor)
            pen.setWidth(max(1, int(round(2.0 / self.scale))))
            p.setPen(pen)
            brush = QBrush(Qt.BDiagPattern)
            p.setBrush(brush)
            p.drawRect(leftTop.x(), leftTop.y(), rectWidth, rectHeight)

        if self.drawing() and not self.prevPoint.isNull(
        ) and not self.outOfPixmap(self.prevPoint):
            pen = QPen(QColor(self.drawingRectColor))
            pen.setWidth(max(1, int(round(2.0 / self.scale))))
            p.setPen(pen)
            p.drawLine(self.prevPoint.x(), 0, self.prevPoint.x(),
                       self.pixmap.height())
            p.drawLine(0, self.prevPoint.y(), self.pixmap.width(),
                       self.prevPoint.y())

        self.setAutoFillBackground(True)
        if self.verified:
            pal = self.palette()
            pal.setColor(self.backgroundRole(), QColor(184, 239, 38, 128))
            self.setPalette(pal)
        else:
            pal = self.palette()
            pal.setColor(self.backgroundRole(), QColor(232, 232, 232, 255))
            self.setPalette(pal)

        p.end()
        self._endPaint = False

    def transformPos(self, point):
        """Convert from widget-logical coordinates to painter-logical coordinates."""
        return point / self.scale - self.offsetToCenter()

    def offsetToCenter(self):
        s = self.scale
        area = super(Canvas, self).size()
        w, h = self.pixmap.width() * s, self.pixmap.height() * s
        aw, ah = area.width(), area.height()
        x = (aw - w) / (2 * s) if aw > w else 0
        y = (ah - h) / (2 * s) if ah > h else 0
        return QPointF(x, y)

    def outOfPixmap(self, p):
        w, h = self.pixmap.width(), self.pixmap.height()
        return not (0 <= p.x() <= w and 0 <= p.y() <= h)

    def finalise(self):
        assert self.current
        if self.current.points[0] == self.current.points[-1]:
            self.current = None
            self.drawingPolygon.emit(False)
            self.update()
            return

        self.current.close()
        self.shapes.append(self.current)
        self.current = None
        self.setHiding(False)
        self.newShape.emit()
        self.update()

    def closeEnough(self, p1, p2):
        #d = distance(p1 - p2)
        #m = (p1-p2).manhattanLength()
        # print "d %.2f, m %d, %.2f" % (d, m, d - m)
        return distance(p1 - p2) < self.epsilon

    def intersectionPoint(self, p1, p2):
        # Cycle through each image edge in clockwise fashion,
        # and find the one intersecting the current line segment.
        # http://paulbourke.net/geometry/lineline2d/
        size = self.pixmap.size()
        points = [(0, 0), (size.width(), 0), (size.width(), size.height()),
                  (0, size.height())]
        x1, y1 = p1.x(), p1.y()
        x2, y2 = p2.x(), p2.y()

        intersectingEdges = self.intersectingEdges((x1, y1), (x2, y2), points)

        d, i, (x, y) = min(intersectingEdges)

        x3, y3 = points[i]
        x4, y4 = points[(i + 1) % 4]
        if (x, y) == (x1, y1):
            # Handle cases where previous point is on one of the edges.
            if x3 == x4:
                return QPointF(x3, min(max(0, y2), max(y3, y4)))
            else:  # y3 == y4
                return QPointF(min(max(0, x2), max(x3, x4)), y3)

        # Ensure the labels are within the bounds of the image. If not, fix them.
        x, y, _ = self.snapPointToCanvas(x, y)

        return QPointF(x, y)

    def intersectingEdges(self, x1y1, x2y2, points):
        """For each edge formed by `points', yield the intersection
        with the line segment `(x1,y1) - (x2,y2)`, if it exists.
        Also return the distance of `(x2,y2)' to the middle of the
        edge along with its index, so that the one closest can be chosen."""
        x1, y1 = x1y1
        x2, y2 = x2y2
        for i in range(4):
            x3, y3 = points[i]
            x4, y4 = points[(i + 1) % 4]
            denom = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1)
            nua = (x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)
            nub = (x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)
            if denom == 0:
                # This covers two cases:
                #   nua == nub == 0: Coincident
                #   otherwise: Parallel
                continue
            ua, ub = nua / denom, nub / denom
            if 0 <= ua <= 1 and 0 <= ub <= 1:
                x = x1 + ua * (x2 - x1)
                y = y1 + ua * (y2 - y1)
                m = QPointF((x3 + x4) / 2, (y3 + y4) / 2)
                d = distance(m - QPointF(x2, y2))
                yield d, i, (x, y)

    # These two, along with a call to adjustSize are required for the
    # scroll area.
    def sizeHint(self):
        return self.minimumSizeHint()

    def minimumSizeHint(self):
        if self.pixmap:
            return self.scale * self.pixmap.size()
        return super(Canvas, self).minimumSizeHint()

    def wheelEvent(self, ev):
        qt_version = 4 if hasattr(ev, "delta") else 5
        if qt_version == 4:
            if ev.orientation() == Qt.Vertical:
                v_delta = ev.delta()
                h_delta = 0
            else:
                h_delta = ev.delta()
                v_delta = 0
        else:
            delta = ev.angleDelta()
            h_delta = delta.x()
            v_delta = delta.y()

        mods = ev.modifiers()
        if Qt.ControlModifier == int(mods) and v_delta:
            self.zoomRequest.emit(v_delta)
        else:
            v_delta and self.scrollRequest.emit(v_delta, Qt.Vertical)
            h_delta and self.scrollRequest.emit(h_delta, Qt.Horizontal)
        ev.accept()

    def keyPressEvent(self, ev):
        key = ev.key()
        if key == Qt.Key_Escape and self.current:
            print('ESC press')
            self.current = None
            self.drawingPolygon.emit(False)
            self.update()
        elif key == Qt.Key_Return and self.canCloseShape():
            self.finalise()
        elif key == Qt.Key_Left and self.selectedShape:
            self.moveOnePixel('Left')
        elif key == Qt.Key_Right and self.selectedShape:
            self.moveOnePixel('Right')
        elif key == Qt.Key_Up and self.selectedShape:
            self.moveOnePixel('Up')
        elif key == Qt.Key_Down and self.selectedShape:
            self.moveOnePixel('Down')
        # elif key == Qt.Key_Delete:
        #     self.parent().window().deleteSelectedShape()

    def moveOnePixel(self, direction):
        # print(self.selectedShape.points)
        if direction == 'Left' and not self.moveOutOfBound(QPointF(-1.0, 0)):
            # print("move Left one pixel")
            self.selectedShape.points[0] += QPointF(-1.0, 0)
            self.selectedShape.points[1] += QPointF(-1.0, 0)
            self.selectedShape.points[2] += QPointF(-1.0, 0)
            self.selectedShape.points[3] += QPointF(-1.0, 0)
        elif direction == 'Right' and not self.moveOutOfBound(QPointF(1.0, 0)):
            # print("move Right one pixel")
            self.selectedShape.points[0] += QPointF(1.0, 0)
            self.selectedShape.points[1] += QPointF(1.0, 0)
            self.selectedShape.points[2] += QPointF(1.0, 0)
            self.selectedShape.points[3] += QPointF(1.0, 0)
        elif direction == 'Up' and not self.moveOutOfBound(QPointF(0, -1.0)):
            # print("move Up one pixel")
            self.selectedShape.points[0] += QPointF(0, -1.0)
            self.selectedShape.points[1] += QPointF(0, -1.0)
            self.selectedShape.points[2] += QPointF(0, -1.0)
            self.selectedShape.points[3] += QPointF(0, -1.0)
        elif direction == 'Down' and not self.moveOutOfBound(QPointF(0, 1.0)):
            # print("move Down one pixel")
            self.selectedShape.points[0] += QPointF(0, 1.0)
            self.selectedShape.points[1] += QPointF(0, 1.0)
            self.selectedShape.points[2] += QPointF(0, 1.0)
            self.selectedShape.points[3] += QPointF(0, 1.0)
        self.shapeMoved.emit()
        self.repaint()

    def moveOutOfBound(self, step):
        points = [
            p1 + p2 for p1, p2 in zip(self.selectedShape.points, [step] * 4)
        ]
        return True in map(self.outOfPixmap, points)

    def setLastLabel(self,
                     text,
                     line_color=None,
                     fill_color=None,
                     params=None):
        assert text
        self.shapes[-1].label = text
        if line_color:
            self.shapes[-1].line_color = line_color

        if fill_color:
            self.shapes[-1].fill_color = fill_color

        if params:
            self.shapes[-1].paramsVision = params

        return self.shapes[-1]

    def undoLastLine(self):
        assert self.shapes
        self.current = self.shapes.pop()
        self.current.setOpen()
        self.line.points = [self.current[-1], self.current[0]]
        self.drawingPolygon.emit(True)

    def resetAllLines(self):
        assert self.shapes
        self.current = self.shapes.pop()
        self.current.setOpen()
        self.line.points = [self.current[-1], self.current[0]]
        self.drawingPolygon.emit(True)
        self.current = None
        self.drawingPolygon.emit(False)
        self.update()

    def loadPixmap(self, pixmap):
        self.pixmap = pixmap
        # self.shapes = []
        self.repaint()

    def loadShapes(self, shapes):
        self.shapes = list(shapes)
        self.current = None
        self.repaint()

    def setShapeVisible(self, shape, value):
        self.visible[shape] = value
        self.repaint()

    def currentCursor(self):
        cursor = QApplication.overrideCursor()
        if cursor is not None:
            cursor = cursor.shape()
        return cursor

    def overrideCursor(self, cursor):
        self._cursor = cursor
        if self.currentCursor() is None:
            QApplication.setOverrideCursor(cursor)
        else:
            QApplication.changeOverrideCursor(cursor)

    def restoreCursor(self):
        QApplication.restoreOverrideCursor()

    def resetState(self):
        self.restoreCursor()
        self.pixmap = None
        self.update()

    def setDrawingShapeToSquare(self, status):
        self.drawSquare = status
コード例 #19
0
def randomTable(num):
    # Create a black image
    img = np.zeros((512, 512, 3), np.uint8)
    img.fill(255)
    left = random.randint(20, 50)
    top = random.randint(50, 60)
    width = random.randint(40, 50)
    height = random.randint(30, 70)

    row = random.randint(2, 5)
    column = random.randint(4, 8)

    if row == 1 and column == 1:
        width, height = width * 2, height * 2

    rectShapes = []

    # print("image:{}, row num={}, column num ={} ".format(num, row, column))

    for r in range(0, row):
        if r % 2 == 0:
            cell_width = column * width
            start_point = (left, top + r * height)
            text_start_point = (start_point[0] + int(width * 0.2),
                                start_point[1] + int(height * 0.2))
            end_point = (left + cell_width, top + (r + 1) * height)

            # PIL image转换成array
            img = Image.fromarray(np.uint8(img))
            draw = ImageDraw.Draw(img)
            word_height = int(height * 0.6)
            FONT = ImageFont.truetype('fonts/simhei.ttf', word_height)

            # 字数量
            word_sum = int(cell_width / word_height - 1)
            random_text = randomText(word_sum)
            text_end_point = (text_start_point[0] + word_height * word_sum,
                              text_start_point[1] + word_height)
            # 填字
            draw.text(text_start_point, random_text, BLACK, font=FONT)

            # 标注文字
            text_points = [text_start_point, text_end_point]
            text_shape = Shape(label='text')
            for x, y in text_points:
                text_shape.addPoint(Point(x, y))
            text_shape.close()
            rectShapes.append(text_shape)

            # array转换成image
            img = np.asarray(img)
            # 画框
            thickness = [1, 2]  # 表格线粗细随机
            # cv.rectangle(img, start_point, end_point, BLACK, random.choice(thickness))

            # 用线画矩形
            top_right_point = (end_point[0], start_point[1])
            bottom_left_point = (start_point[0], end_point[1])
            cv.line(img, start_point, top_right_point, BLACK,
                    1)  # draw top line
            cv.line(img, bottom_left_point, end_point, BLACK,
                    1)  # draw bottom line
            cv.line(img, start_point, bottom_left_point, BLACK,
                    1)  # draw left line
            cv.line(img, top_right_point, end_point, BLACK,
                    1)  # draw right line

            points = [start_point, end_point]
            shape = Shape(label='rect')
            for x, y in points:
                shape.addPoint(Point(x, y))
            shape.close()
            rectShapes.append(shape)
            continue
        for c in range(0, column):
            start_point = (left + c * width, top + r * height)
            text_start_point = (start_point[0] + int(width * 0.2),
                                start_point[1] + int(height * 0.2))
            end_point = (left + (c + 1) * width, top + (r + 1) * height)

            # PIL image转换成array
            img = Image.fromarray(np.uint8(img))
            draw = ImageDraw.Draw(img)
            word_height = int(height * 0.6)
            FONT = ImageFont.truetype('fonts/simhei.ttf', word_height)

            text_end_point = (text_start_point[0] + word_height * 0.75,
                              text_start_point[1] + word_height)
            # 填字
            draw.text(text_start_point, random.choice(TEXT2), BLACK, font=FONT)

            # 标注文字
            text_points = [text_start_point, text_end_point]
            text_shape = Shape(label='text')
            for x, y in text_points:
                text_shape.addPoint(Point(x, y))
            text_shape.close()
            rectShapes.append(text_shape)

            # array转换成image
            img = np.asarray(img)
            # 画框
            thickness = [1, 2]  # 表格线粗细随机
            # cv.rectangle(img, start_point, end_point, BLACK, random.choice(thickness))

            # 用线画矩形
            top_right_point = (end_point[0], start_point[1])
            bottom_left_point = (start_point[0], end_point[1])
            cv.line(img, start_point, top_right_point, BLACK,
                    1)  # draw top line
            cv.line(img, bottom_left_point, end_point, BLACK,
                    1)  # draw bottom line
            cv.line(img, start_point, bottom_left_point, BLACK,
                    1)  # draw left line
            cv.line(img, top_right_point, end_point, BLACK,
                    1)  # draw right line

            points = [start_point, end_point]
            shape = Shape(label='rect')
            for x, y in points:
                shape.addPoint(Point(x, y))
            shape.close()
            rectShapes.append(shape)

    # cv.namedWindow(num, 0)
    # cv.imshow(num, img)
    # cv.waitKey(0)

    imagePath = 'VOC2007/JPEGImages/'
    xmlPath = 'VOC2007/Annotations/'
    fileName = imagePath + num + IMAGE_EXT
    xmlName = xmlPath + num + XML_EXT
    # print('fileName=', fileName)

    noise_percetage = random.uniform(0, .20)
    # print('noise_percetage=', noise_percetage)

    salt_noise_image = SaltAndPepper(img, noise_percetage)  # 添加的椒盐噪声

    gaussian_noise_image = addGaussianNoise(salt_noise_image)  # 添加的高斯噪声

    cv.imwrite(fileName, gaussian_noise_image)

    savePascalVocFormat(xmlName, rectShapes, imagePath, img)
コード例 #20
0
ファイル: canvas.py プロジェクト: juliusHuelsmann/labelImg
    def rotateShape(self, pos, shape, debug=True):
        """
        Rotates a shape by dragging the shape-rotation-button to the position 
        `pos`.

        Checks if the resulting shape is completely inside the image in the 
        image. If not, rotate by an angle that is closest to the desired angle
        but still yielding a shape inside the image.
        """

        # Case 1: Rotate the shape
        vertex_not_rotated = shape.getShapeRotationVertex(False)
        if vertex_not_rotated is not None:
            eucl_sq = lambda a: a.x()**2 + a.y()**2

            # Fetch the original (=not rotated) vertex-position for movement
            # and the center of mass of the shape (once again according to
            # the coordinates that are not rotated)
            vertex_point = vertex_not_rotated[0]
            vertex_mirrored = vertex_not_rotated[2]
            shape_center = shape.getCenter(False)

            # Compute the vector and distance between both aforementioned points
            vec_old_center = vertex_point - shape_center  # - vertex_point
            dist_old_center_square = eucl_sq(vec_old_center)

            # Compute the vector and distance between the new position and the center
            vec_new_center = pos - shape_center  # - pos
            dist_new_center_square = eucl_sq(vec_new_center)

            # Now compute the angle between both the vector pointing to the new
            # position of the rotation vertex and the one pointing to its
            # original position.
            # Make completely sure that no rounding errors can cause
            # mathematical errors for the input value by checking bounds.
            val = QPointF.dotProduct(vec_new_center, vec_old_center) / \
                (dist_new_center_square * dist_old_center_square) **.5
            val = min(max(val, -1), 1)
            angle = acos(val)

            # The direction of movement has to be adapted depending on the
            # current state of the vertex.
            # First condition:  vertex is 'mirrored':
            #                   the initially topmost line is dragged under line
            #                   at the bottom; In this case the sign must be
            #                   swapped.
            # Second condition: as the shape-move vertex is always
            #                   directly above or beneath the shape's center
            #                   it is succicent to check the x coordinate for
            #                   checking if the rotation is 'in the second
            #                   half'. In that case, rotate by 2pi -angle
            transform_angle = lambda a, posx :  \
                    (-1 if vertex_mirrored else 1)  \
                    * (a if (posx >= vertex_point.x()) else 2. * pi - a)
            angle = transform_angle(angle, pos.x())

            # XXX: this checking mechanism does not work entirely (the
            #      distinction of valid angles sometimes does not recognize the
            #      fact that two edges are outside the valid area).
            #      and contains debug code (that inserts vertices to some
            #      positions for debugging) and thus should only be commented
            #      in for finishing the implementation of that feature (in case
            #      it is required)).
            #      If it is not required it should be removed.
            performCheckOfIntervals = False
            if performCheckOfIntervals:
                # Not all angles are valid. Find out which angles are leading to
                # coordiantes outside the image:
                # Step 1)       find (x,y) with \|(x,y) - c \| = \|x_1 - x_3\|
                #               and (x,y) on image's borders
                # Step 2)       find the associated rotation angles and store them
                #               in a sorted way
                width, height = self.pixmap.width(), self.pixmap.height()
                # get the radius of the circle
                p_c = shape.pointsWithoutRotation[0] - shape_center
                len_p_c = eucl_sq(p_c)
                # list all the support vectors indicating image border alongside
                # with their directions
                support_direction = [
                    [QPointF(0, 0), QPointF(width - 1, 0)],
                    [QPointF(0, 0), QPointF(0, height - 1)],
                    [QPointF(width - 1, 0),
                     QPointF(0, height - 1)],
                    [QPointF(0, height - 1),
                     QPointF(width - 1, 0)]
                ]
                forbiddenAngleIntervals = []
                for s, d in support_direction:
                    # find intersections between the circle (defined by the center
                    # and its radius) and the currently considered image border.
                    #
                    # In case there is only one (or none) intersection,
                    # no conditions are imposed in this step on the anlge as the
                    # image borders are selected to be the last line of pixels
                    # inside the image.
                    #
                    # If there are two intersections, the space in between them is
                    # forbidden
                    intersects = Canvas.intersectionLineCircle(
                        s - shape_center, d, sqrt(len_p_c))
                    if intersects is not None:

                        # In case debugging is enabled, add new shapes that show
                        # the intersections with the borders in the image.
                        # Attention: debugging cannot be used in a productive mode.
                        # Results in a bunch of new vertices.
                        if debug:
                            deb = Shape()
                            deb.addPoint(intersects[0] + shape_center)
                            deb.addPoint(intersects[1] + shape_center)
                            deb.close()
                            self.shapes.append(deb)

                            deb = Shape()
                            deb.addPoint(p_c + shape_center)
                            deb.close()
                            self.shapes.append(deb)

                        # the corresponding angle is the angle between the
                        # intersection point and the  vertex_point (shifted by
                        # center)
                        if len(intersects) == 2:

                            angles = [[
                                transform_angle(
                                    acos(
                                        QPointF.dotProduct(
                                            spwr - shape_center, a) /
                                        (len_p_c * eucl_sq(a))**.5), spwr.x())
                                for a in intersects
                            ] for spwr in shape.pointsWithoutRotation]

                            for i, (a, b) in enumerate(angles):
                                # find the min and max value and compute the
                                # min and max value that are still allowed.
                                # if the angle might be affected by them
                                t = 0
                                if a < 0: a += 2 * pi
                                if b < 0: b += 2 * pi
                                mx, mi = max(a, b), min(a, b)
                                if mx - mi > pi:
                                    forbiddenAngleIntervals.append(
                                        [mx, 2 * pi])
                                    forbiddenAngleIntervals.append([0, mi])
                                else:
                                    forbiddenAngleIntervals.append([mi, mx])

                                #if a < b:
                                #    forbiddenAngleIntervals.append([a, b])
                                #elif b < a:
                                #    forbiddenAngleIntervals.append([a, 2*pi])
                                #    forbiddenAngleIntervals.append([0, b])

                                # paint vector (forbidden area) based on the
                                # computed angle
                                if debug:
                                    p1 = Shape.rotatePoint(
                                        shape.pointsWithoutRotation[i],
                                        shape_center, a)
                                    p2 = Shape.rotatePoint(
                                        shape.pointsWithoutRotation[i],
                                        shape_center, b)

                                    deb = Shape()
                                    deb.addPoint(p1)
                                    deb.addPoint(p2)
                                    deb.close()
                                    self.shapes.append(deb)

                # XXX: There most likely is a better solution to this.
                #      The code below is supposed to unite all forbidden intervals.
                #      This is necessary for being able to pick the closest point
                #      to the forbidden area.
                if len(forbiddenAngleIntervals):
                    unionInterval = [forbiddenAngleIntervals[0]]
                    uiid = 0
                    # starts before other.end and stops after other.start
                    checkIntersect = lambda a, b: a[1] >= b[0] and a[0] <= b[1]
                    checkIntersectMutual = lambda a, b: checkIntersect(a, b) \
                            or checkIntersect(b, a)
                    # need to check multiple times as there might be an array that
                    # unites two other arrays.
                    for k in range(len(forbiddenAngleIntervals) - 1):
                        for i in range(1, len(forbiddenAngleIntervals)):
                            # check if there is already is an interval comprising me
                            inters = False
                            for ui in range(len(unionInterval)):
                                # end union > start this
                                if (checkIntersectMutual(
                                        unionInterval[ui],
                                        forbiddenAngleIntervals[i])):
                                    unionInterval[ui][0] = min(
                                        unionInterval[ui][0],
                                        forbiddenAngleIntervals[i][0])
                                    unionInterval[ui][1] = max(
                                        unionInterval[ui][1],
                                        forbiddenAngleIntervals[i][1])
                                    inters = True
                                    break
                            if not inters:
                                unionInterval.append(
                                    forbiddenAngleIntervals[i])

                    print(forbiddenAngleIntervals, unionInterval)

                    # Check if there is some intersection and use the closest point
                    # as corrected angle.
                    if angle < 0: angle += 2 * pi
                    for i in unionInterval:
                        if i[0] < angle and angle < i[1]:
                            angle = i[0] if angle - i[0] < i[1] - angle else i[
                                1]
                            break

            # Apply the rotation for the shape (computes new location of rotated
            # values and stores the current angle for future reference):
            shape.applyRotationAngle(angle, shape_center)
コード例 #21
0
ファイル: canvas.py プロジェクト: juliusHuelsmann/labelImg
class Canvas(QWidget):
    zoomRequest = pyqtSignal(int)
    scrollRequest = pyqtSignal(int, int)
    newShape = pyqtSignal()
    selectionChanged = pyqtSignal(bool)
    shapeMoved = pyqtSignal()
    drawingPolygon = pyqtSignal(bool)

    CREATE, EDIT = list(range(2))

    epsilon = 11.0

    def __init__(self, *args, **kwargs):
        super(Canvas, self).__init__(*args, **kwargs)
        # Initialise local state.
        self.mode = self.EDIT
        self.shapes = []
        self.current = None
        self.selectedShape = None  # save the selected shape here
        self.selectedShapeCopy = None
        self.drawingLineColor = QColor(0, 0, 255)
        self.drawingRectColor = QColor(0, 0, 255)
        self.line = Shape(line_color=self.drawingLineColor)
        self.prevPoint = QPointF()
        self.scale = 1.0
        self.pixmap = QPixmap()
        self.visible = {}
        self._hideBackround = False
        self.hideBackround = False
        self.hShape = None
        self.hVertex = None
        self._painter = QPainter()
        self._cursor = CURSOR_DEFAULT
        # Menus:
        self.menus = (QMenu(), QMenu())
        # Set widget options.
        self.setMouseTracking(True)
        self.setFocusPolicy(Qt.WheelFocus)
        self.verified = False

    def setDrawingColor(self, qColor):
        self.drawingLineColor = qColor
        self.drawingRectColor = qColor

    def enterEvent(self, ev):
        self.overrideCursor(self._cursor)

    def leaveEvent(self, ev):
        self.restoreCursor()

    def focusOutEvent(self, ev):
        self.restoreCursor()

    def isVisible(self, shape):
        return self.visible.get(shape, True)

    def drawing(self):
        return self.mode == self.CREATE

    def editing(self):
        return self.mode == self.EDIT

    def setEditing(self, value=True):
        self.mode = self.EDIT if value else self.CREATE
        if not value:  # Create
            self.unHighlight()
            self.deSelectShape()
        self.prevPoint = QPointF()
        self.repaint()

    def unHighlight(self):
        if self.hShape:
            self.hShape.highlightClear()
        self.hVertex = self.hShape = None

    def selectedVertex(self):
        return self.hVertex is not None

    def mouseMoveEvent(self, ev):
        """Update line with last point and current coordinates."""
        pos = self.transformPos(ev.pos())

        # Update coordinates in status bar if image is opened
        window = self.parent().window()
        if window.filePath is not None:
            self.parent().window().labelCoordinates.setText('X: %d; Y: %d' %
                                                            (pos.x(), pos.y()))

        # Polygon drawing.
        if self.drawing():
            self.overrideCursor(CURSOR_DRAW)
            if self.current:
                color = self.drawingLineColor
                if self.outOfPixmap(pos):
                    # Don't allow the user to draw outside the pixmap.
                    # Project the point to the pixmap's edges.
                    pos = self.intersectionPoint(self.current[-1], pos)
                elif len(self.current) > 1 and self.closeEnough(
                        pos, self.current[0]):
                    # Attract line to starting point and colorise to alert the
                    # user:
                    pos = self.current[0]
                    color = self.current.line_color
                    self.overrideCursor(CURSOR_POINT)
                    self.current.highlightVertex(0, Shape.NEAR_VERTEX)
                self.line[1] = pos
                self.line.line_color = color
                self.prevPoint = QPointF()
                self.current.highlightClear()
            else:
                self.prevPoint = pos
            self.repaint()
            return

        # Polygon copy moving.
        if Qt.RightButton & ev.buttons():
            if self.selectedShapeCopy and self.prevPoint:
                self.overrideCursor(CURSOR_MOVE)
                self.boundedMoveShape(self.selectedShapeCopy, pos)
                self.repaint()
            elif self.selectedShape:
                self.selectedShapeCopy = self.selectedShape.copy()
                self.repaint()
            return

        # Polygon/Vertex moving.
        if Qt.LeftButton & ev.buttons():
            if self.selectedVertex():
                self.boundedMoveVertex(pos)
                self.shapeMoved.emit()
                self.repaint()
            elif self.selectedShape and self.prevPoint:
                self.overrideCursor(CURSOR_MOVE)
                self.boundedMoveShape(self.selectedShape, pos)
                self.shapeMoved.emit()
                self.repaint()
            return

        # Just hovering over the canvas, 2 possibilities:
        # - Highlight shapes
        # - Highlight vertex
        # Update shape/vertex fill and tooltip value accordingly.
        self.setToolTip("Image")

        # Declaration of variables storing selection criterion and information and the selected items themselves:
        vertex_selected = None
        vertex_min_dist = 0
        shape_selected = None
        shape_min_len = 0
        for shape in self.shapes:
            if self.isVisible(shape):

                # Iteratively find the vertex that is closest to the
                # specified point. Vertices are prioritised to shapes
                vertex_info = shape.getClosestVertex(pos, self.epsilon)

                if vertex_info:
                    index, dist = vertex_info
                    if vertex_selected is None or dist < vertex_min_dist:
                        vertex_selected = index, shape
                        vertex_min_dist = dist

                # In case no vertex has been found yet, check if the
                # current shape contains the point. Select the shape
                # with the smallest length (as length indicates whether
                # the shape is likely to to be completely covered by
                # a different shape.
                elif not vertex_selected:
                    path = shape.makePath()
                    contains = path.contains(pos)
                    if contains:
                        length = path.length()
                        if not shape_selected or length < shape_min_len:
                            shape_selected = shape
                            shape_min_len = length

        # update the graphical user interface accordingly
        if vertex_selected is not None:
            if self.selectedVertex():
                self.hShape.highlightClear()

            self.hVertex, self.hShape = vertex_selected
            self.hShape.highlightVertex(self.hVertex, self.hShape.MOVE_VERTEX)
            self.setToolTip("Click & drag to move point")
            if self.hVertex == Shape.INDEX_ROTATION_ENTITY:
                self.overrideCursor(CURSOR_ROTATE)
            else:
                self.overrideCursor(CURSOR_POINT)
            self.setStatusTip(self.toolTip())
            self.update()

        elif shape_selected is not None:

            if self.selectedVertex():
                self.hShape.highlightClear()

            self.hVertex, self.hShape = None, shape_selected
            self.setToolTip("Click & drag to move shape '%s'" %
                            shape_selected.label)
            self.setStatusTip(self.toolTip())
            self.overrideCursor(CURSOR_GRAB)
            self.update()
        else:
            # Nothing found, clear highlights, reset state.
            if self.selectedVertex():
                self.hShape.highlightClear()
            self.hVertex, self.hShape = None, None
            self.overrideCursor(CURSOR_DEFAULT)
            self.update()

    def mousePressEvent(self, ev):
        pos = self.transformPos(ev.pos())

        if ev.button() == Qt.LeftButton:
            if self.drawing():
                self.handleDrawing(pos)
            else:
                self.selectShapePoint(pos)
                self.prevPoint = pos
                self.repaint()
                pass
        elif ev.button() == Qt.RightButton and self.editing():
            self.selectShapePoint(pos)
            self.prevPoint = pos
            self.repaint()

    def mouseReleaseEvent(self, ev):
        if ev.button() == Qt.RightButton:
            menu = self.menus[bool(self.selectedShapeCopy)]
            self.restoreCursor()
            if not menu.exec_(self.mapToGlobal(ev.pos()))\
               and self.selectedShapeCopy:
                # Cancel the move by deleting the shadow copy.
                self.selectedShapeCopy = None
                self.repaint()
        elif ev.button() == Qt.LeftButton and self.selectedShape:
            if self.selectedVertex():
                self.overrideCursor(CURSOR_POINT)
            else:
                self.overrideCursor(CURSOR_GRAB)
        elif ev.button() == Qt.LeftButton:
            pos = self.transformPos(ev.pos())
            if self.drawing():
                self.handleDrawing(pos)

    def endMove(self, copy=False):
        assert self.selectedShape and self.selectedShapeCopy
        shape = self.selectedShapeCopy
        #del shape.fill_color
        #del shape.line_color
        if copy:
            self.shapes.append(shape)
            self.selectedShape.selected = False
            self.selectedShape = shape
            self.repaint()
        else:
            self.selectedShape.points = [p for p in shape.points]
        self.selectedShapeCopy = None

    def hideBackroundShapes(self, value):
        self.hideBackround = value
        if self.selectedShape:
            # Only hide other shapes if there is a current selection.
            # Otherwise the user will not be able to select a shape.
            self.setHiding(True)
            self.repaint()

    def handleDrawing(self, pos):
        if self.current and self.current.reachMaxPoints() is False:
            initPos = self.current[0]
            minX = initPos.x()
            minY = initPos.y()
            targetPos = self.line[1]
            maxX = targetPos.x()
            maxY = targetPos.y()
            self.current.addPoint(QPointF(maxX, minY))
            self.current.addPoint(targetPos)
            self.current.addPoint(QPointF(minX, maxY))
            self.finalise()
        elif not self.outOfPixmap(pos):
            self.current = Shape()
            self.current.addPoint(pos)
            self.line.points = [pos, pos]
            self.setHiding()
            self.drawingPolygon.emit(True)
            self.update()

    def setHiding(self, enable=True):
        self._hideBackround = self.hideBackround if enable else False

    def canCloseShape(self):
        return self.drawing() and self.current and len(self.current) > 2

    def mouseDoubleClickEvent(self, ev):
        # We need at least 4 points here, since the mousePress handler
        # adds an extra one before this handler is called.
        if self.canCloseShape() and len(self.current) > 3:
            self.current.popPoint()
            self.finalise()

    def selectShape(self, shape):
        shape.selected = True
        self.selectedShape = shape
        self.setHiding()
        self.selectionChanged.emit(True)
        self.update()

    def selectShapePoint(self, point):
        """Select the first shape created which contains this point."""
        self.deSelectShape()
        if self.selectedVertex():
            index, shape = self.hVertex, self.hShape
            shape.highlightVertex(index, shape.MOVE_VERTEX)
            self.selectShape(shape)
        elif self.hShape is not None:
            self.selectShape(self.hShape)

    def boundedMoveVertex(self, pos):
        """
        Code executed when dragging a vertex.
        This can imply two different operations:
        1.  Rotation of the vertex
        2.  Stretch the vertex
        :param pos:         the 'new' position of the vertex in question
        """
        index, shape = self.hVertex, self.hShape
        if index == Shape.INDEX_ROTATION_ENTITY:
            self.rotateShape(pos, shape)
        else:
            self.resizeShape(pos, index, shape)

    def getClosestValid(self, point):
        """
        Return the point that is closest to any point inside the rectangle.
        Especially, if the point itself is inside, return the point.
        """
        return QPointF(max(min(point.x(),
                               self.pixmap.width() - 1), 0),
                       max(min(point.y(),
                               self.pixmap.height() - 1), 0))

    def rotateShape(self, pos, shape, debug=True):
        """
        Rotates a shape by dragging the shape-rotation-button to the position 
        `pos`.

        Checks if the resulting shape is completely inside the image in the 
        image. If not, rotate by an angle that is closest to the desired angle
        but still yielding a shape inside the image.
        """

        # Case 1: Rotate the shape
        vertex_not_rotated = shape.getShapeRotationVertex(False)
        if vertex_not_rotated is not None:
            eucl_sq = lambda a: a.x()**2 + a.y()**2

            # Fetch the original (=not rotated) vertex-position for movement
            # and the center of mass of the shape (once again according to
            # the coordinates that are not rotated)
            vertex_point = vertex_not_rotated[0]
            vertex_mirrored = vertex_not_rotated[2]
            shape_center = shape.getCenter(False)

            # Compute the vector and distance between both aforementioned points
            vec_old_center = vertex_point - shape_center  # - vertex_point
            dist_old_center_square = eucl_sq(vec_old_center)

            # Compute the vector and distance between the new position and the center
            vec_new_center = pos - shape_center  # - pos
            dist_new_center_square = eucl_sq(vec_new_center)

            # Now compute the angle between both the vector pointing to the new
            # position of the rotation vertex and the one pointing to its
            # original position.
            # Make completely sure that no rounding errors can cause
            # mathematical errors for the input value by checking bounds.
            val = QPointF.dotProduct(vec_new_center, vec_old_center) / \
                (dist_new_center_square * dist_old_center_square) **.5
            val = min(max(val, -1), 1)
            angle = acos(val)

            # The direction of movement has to be adapted depending on the
            # current state of the vertex.
            # First condition:  vertex is 'mirrored':
            #                   the initially topmost line is dragged under line
            #                   at the bottom; In this case the sign must be
            #                   swapped.
            # Second condition: as the shape-move vertex is always
            #                   directly above or beneath the shape's center
            #                   it is succicent to check the x coordinate for
            #                   checking if the rotation is 'in the second
            #                   half'. In that case, rotate by 2pi -angle
            transform_angle = lambda a, posx :  \
                    (-1 if vertex_mirrored else 1)  \
                    * (a if (posx >= vertex_point.x()) else 2. * pi - a)
            angle = transform_angle(angle, pos.x())

            # XXX: this checking mechanism does not work entirely (the
            #      distinction of valid angles sometimes does not recognize the
            #      fact that two edges are outside the valid area).
            #      and contains debug code (that inserts vertices to some
            #      positions for debugging) and thus should only be commented
            #      in for finishing the implementation of that feature (in case
            #      it is required)).
            #      If it is not required it should be removed.
            performCheckOfIntervals = False
            if performCheckOfIntervals:
                # Not all angles are valid. Find out which angles are leading to
                # coordiantes outside the image:
                # Step 1)       find (x,y) with \|(x,y) - c \| = \|x_1 - x_3\|
                #               and (x,y) on image's borders
                # Step 2)       find the associated rotation angles and store them
                #               in a sorted way
                width, height = self.pixmap.width(), self.pixmap.height()
                # get the radius of the circle
                p_c = shape.pointsWithoutRotation[0] - shape_center
                len_p_c = eucl_sq(p_c)
                # list all the support vectors indicating image border alongside
                # with their directions
                support_direction = [
                    [QPointF(0, 0), QPointF(width - 1, 0)],
                    [QPointF(0, 0), QPointF(0, height - 1)],
                    [QPointF(width - 1, 0),
                     QPointF(0, height - 1)],
                    [QPointF(0, height - 1),
                     QPointF(width - 1, 0)]
                ]
                forbiddenAngleIntervals = []
                for s, d in support_direction:
                    # find intersections between the circle (defined by the center
                    # and its radius) and the currently considered image border.
                    #
                    # In case there is only one (or none) intersection,
                    # no conditions are imposed in this step on the anlge as the
                    # image borders are selected to be the last line of pixels
                    # inside the image.
                    #
                    # If there are two intersections, the space in between them is
                    # forbidden
                    intersects = Canvas.intersectionLineCircle(
                        s - shape_center, d, sqrt(len_p_c))
                    if intersects is not None:

                        # In case debugging is enabled, add new shapes that show
                        # the intersections with the borders in the image.
                        # Attention: debugging cannot be used in a productive mode.
                        # Results in a bunch of new vertices.
                        if debug:
                            deb = Shape()
                            deb.addPoint(intersects[0] + shape_center)
                            deb.addPoint(intersects[1] + shape_center)
                            deb.close()
                            self.shapes.append(deb)

                            deb = Shape()
                            deb.addPoint(p_c + shape_center)
                            deb.close()
                            self.shapes.append(deb)

                        # the corresponding angle is the angle between the
                        # intersection point and the  vertex_point (shifted by
                        # center)
                        if len(intersects) == 2:

                            angles = [[
                                transform_angle(
                                    acos(
                                        QPointF.dotProduct(
                                            spwr - shape_center, a) /
                                        (len_p_c * eucl_sq(a))**.5), spwr.x())
                                for a in intersects
                            ] for spwr in shape.pointsWithoutRotation]

                            for i, (a, b) in enumerate(angles):
                                # find the min and max value and compute the
                                # min and max value that are still allowed.
                                # if the angle might be affected by them
                                t = 0
                                if a < 0: a += 2 * pi
                                if b < 0: b += 2 * pi
                                mx, mi = max(a, b), min(a, b)
                                if mx - mi > pi:
                                    forbiddenAngleIntervals.append(
                                        [mx, 2 * pi])
                                    forbiddenAngleIntervals.append([0, mi])
                                else:
                                    forbiddenAngleIntervals.append([mi, mx])

                                #if a < b:
                                #    forbiddenAngleIntervals.append([a, b])
                                #elif b < a:
                                #    forbiddenAngleIntervals.append([a, 2*pi])
                                #    forbiddenAngleIntervals.append([0, b])

                                # paint vector (forbidden area) based on the
                                # computed angle
                                if debug:
                                    p1 = Shape.rotatePoint(
                                        shape.pointsWithoutRotation[i],
                                        shape_center, a)
                                    p2 = Shape.rotatePoint(
                                        shape.pointsWithoutRotation[i],
                                        shape_center, b)

                                    deb = Shape()
                                    deb.addPoint(p1)
                                    deb.addPoint(p2)
                                    deb.close()
                                    self.shapes.append(deb)

                # XXX: There most likely is a better solution to this.
                #      The code below is supposed to unite all forbidden intervals.
                #      This is necessary for being able to pick the closest point
                #      to the forbidden area.
                if len(forbiddenAngleIntervals):
                    unionInterval = [forbiddenAngleIntervals[0]]
                    uiid = 0
                    # starts before other.end and stops after other.start
                    checkIntersect = lambda a, b: a[1] >= b[0] and a[0] <= b[1]
                    checkIntersectMutual = lambda a, b: checkIntersect(a, b) \
                            or checkIntersect(b, a)
                    # need to check multiple times as there might be an array that
                    # unites two other arrays.
                    for k in range(len(forbiddenAngleIntervals) - 1):
                        for i in range(1, len(forbiddenAngleIntervals)):
                            # check if there is already is an interval comprising me
                            inters = False
                            for ui in range(len(unionInterval)):
                                # end union > start this
                                if (checkIntersectMutual(
                                        unionInterval[ui],
                                        forbiddenAngleIntervals[i])):
                                    unionInterval[ui][0] = min(
                                        unionInterval[ui][0],
                                        forbiddenAngleIntervals[i][0])
                                    unionInterval[ui][1] = max(
                                        unionInterval[ui][1],
                                        forbiddenAngleIntervals[i][1])
                                    inters = True
                                    break
                            if not inters:
                                unionInterval.append(
                                    forbiddenAngleIntervals[i])

                    print(forbiddenAngleIntervals, unionInterval)

                    # Check if there is some intersection and use the closest point
                    # as corrected angle.
                    if angle < 0: angle += 2 * pi
                    for i in unionInterval:
                        if i[0] < angle and angle < i[1]:
                            angle = i[0] if angle - i[0] < i[1] - angle else i[
                                1]
                            break

            # Apply the rotation for the shape (computes new location of rotated
            # values and stores the current angle for future reference):
            shape.applyRotationAngle(angle, shape_center)

    def resizeShape(self, pos, index, shape):
        """
        Resize shape of rectangle, enforcing rectangular form in the original 
        coordinates


        """

        dot = lambda x, y: x.x() * y.x() + x.y() * y.y()
        eucl = lambda a: 1. * a.x()**2 + 1. * a.y()**2
        rot = lambda p, a: Shape.rotatePoint(p, QPointF(0, 0), a)
        rotShapeLine = lambda i, ia, s: rot(
            s.pointsWithoutRotation[ia] - s.pointsWithoutRotation[i], s.
            currentAngle)

        if shape.points[index] != pos:

            # give reasonable names to the vectices that are affected ('left'
            # and 'right' and to the vertex that remains unaffected 'other')
            rindex, lindex, oindex = [(index + o) % 4 for o in [1, 3, 2]]

            # A) compute the offset defining the movement to be applied in this
            #    step at the vertex in question.
            pos = self.getClosestValid(pos)
            offset_rotated = shape.points[index] - pos
            shape.points[index] = pos

            # B) Find new location of affected points (lindex and rindex)
            #    1) w,h = rotate back vector from dragged vertex (=: i) to other
            #       vertex (=: o)
            #    2) cos(w or h), sin(w or h) -> vector from i to lindex (=:l)
            #       or rindex (=: r)
            vec_involved = shape.points[oindex] - shape.points[index]

            size = -rot(vec_involved, -shape.currentAngle)
            w, h = size.x(), size.y()

            vec_il = QPointF(
                cos(shape.currentAngle) * w,
                sin(shape.currentAngle) * w)
            vec_ir = QPointF(-sin(shape.currentAngle) * h,
                             cos(shape.currentAngle) * h)

            # C) Correct locations accordingly
            #    1) compute  position 1, 3
            #    2) compute intersection in rotated space, such that it is
            #       ensured that the resulting values are rounded inside the
            #       coordinates.
            #       Subtract the resulting value directly from i and 1 or 3
            #    3) Use the projection vector to move both the currently
            #       dragged point and the point that is out of bounds to the
            #       last valid location.
            if index % 2 == 0:
                rind = vec_ir
                vec_ir = vec_il
                vec_il = rind
            shape.points[rindex] = shape.points[index] - vec_ir
            shape.points[lindex] = shape.points[index] - vec_il

            # apply the rotation angle to the data that is uk
            shape_center = shape.getCenter(rotated=True)
            shifts = self.checkBorders(shape, index, lindex, vec_ir),  \
                    self.checkBorders(shape, index, rindex, vec_il)
            for shift in shifts:
                if shift is not None:
                    shape.points[index] += shift

            # apply the new coordinates to the latent (unrotated) array
            shape.applyRotationAngle(shape.currentAngle, shape_center, False)

    def checkBorders(self, shape, index, aindex, direction):
        width, height = self.pixmap.width(), self.pixmap.height()
        a1, a2 = shape[aindex].y() < 0, shape[aindex].y() >= height

        if shape[aindex].y() < 0 or shape[aindex].y() >= height:

            lam = self.intersectionParametrized(
                shape[aindex], direction,
                QPointF(0, (shape[aindex].y() >= height) * height),
                QPointF(width, 0))
            if lam is not None:
                shift = lam * direction
                res = shape[aindex] + shift
                if round(res.x()) >= 0 and round(res.x()) < width:
                    shape[aindex] = res
                    return shift

        if shape[aindex].x() < 0 or shape[aindex].x() >= width:

            lam = self.intersectionParametrized(
                shape[aindex], direction,
                QPointF((shape[aindex].x() >= width) * width, 0),
                QPointF(0, height))
            shift = lam * direction
            res = shape[aindex] + shift
            if round(res.y()) >= 0 and round(res.y()) < height:
                shape[aindex] = res
                return shift
        return None

    @staticmethod
    def intersectionParametrized(s_1, d_1, s_2, d_2):
        """
        Return the multiplier \lambda of d_1 such that \exists \mu s.th.
            s_1 + \lambda d_1 = s_2 + \mu d_2.
        Otherwise return None.

        :param s_1: first support vector
        :param d_1: first direction vector that is multiplied by \lambda. \lambda is to be returned
        :param s_2: second support
        :param d_2: second direction. Multiplier is irrelevant and not required.
        :return:    the multiplier \lambda of d_1 such that the two lines intersect
        """
        denominator = d_2.y() * d_1.x() - d_2.x() * d_1.y()
        return None if denominator == 0 else 1. * (
            d_2.x() * (s_1.y() - s_2.y()) - d_2.y() *
            (s_1.x() - s_2.x())) / denominator

    @staticmethod
    def intersectionLineCircle(s, d, radius):
        signStar = lambda x: -1 if x < 0 else 1
        s2 = s + d
        dx = d.x()
        dy = d.y()
        dr = sqrt(dx**2. + dy**2.)
        D = s.x() * s2.y() - s2.x() * s.y()
        delta = radius**2. * dr**2. - D**2.

        if delta >= 0:
            p1 = QPointF(
                (1. * D * dy + signStar(dy) * dx * sqrt(delta)) / dr**2,
                (-1. * D * dx + abs(dy) * sqrt(delta)) / dr**2)
            p2 = QPointF(
                (1. * D * dy - signStar(dy) * dx * sqrt(delta)) / dr**2,
                (-1. * D * dx - abs(dy) * sqrt(delta)) / dr**2)
            if delta == 0: return p1
            return p1, p2
        return None

    def boundedMoveShape(self, shape, pos):
        dp = pos - self.prevPoint
        self.prevPoint = pos
        return self.boundedMoveShapeBy(shape, dp)

    def boundedMoveShapeBy(self, shape, dp):
        minx, miny = shape.points[0].x(), shape.points[0].y()
        maxx, maxy = minx, miny
        for p in shape.points[1:]:
            minx = min(p.x(), minx)
            miny = min(p.y(), miny)
            maxx = max(p.x(), maxx)
            maxy = max(p.y(), maxy)

        maxx = self.pixmap.width() - maxx
        maxy = self.pixmap.height() - maxy
        dp = QPointF(max(min(maxx, dp.x()), -minx),
                     max(min(maxy, dp.y()), -miny))

        if dp:
            shape.shift(dp)
            return True
        return False

    def deSelectShape(self):
        if self.selectedShape:
            self.selectedShape.selected = False
            self.selectedShape = None
            self.setHiding(False)
            self.selectionChanged.emit(False)
            self.update()

    def deleteSelected(self):
        if self.selectedShape:
            shape = self.selectedShape
            self.shapes.remove(self.selectedShape)
            self.selectedShape = None
            self.update()
            return shape

    def copySelectedShape(self):
        if self.selectedShape:
            shape = self.selectedShape.copy()
            self.deSelectShape()
            self.shapes.append(shape)
            shape.selected = True
            self.selectedShape = shape
            self.boundedShiftShape(shape)
            return shape

    def boundedShiftShape(self, shape):
        # Try to move in one direction, and if it fails in another.
        # Give up if both fail.
        point = shape[0]
        offset = QPointF(2.0, 2.0)
        self.prevPoint = point
        if not self.boundedMoveShape(shape, point - offset):
            self.boundedMoveShape(shape, point + offset)

    def paintEvent(self, event):
        if not self.pixmap:
            return super(Canvas, self).paintEvent(event)

        p = self._painter
        p.begin(self)
        p.setRenderHint(QPainter.Antialiasing)
        p.setRenderHint(QPainter.HighQualityAntialiasing)
        p.setRenderHint(QPainter.SmoothPixmapTransform)

        p.scale(self.scale, self.scale)
        p.translate(self.offsetToCenter())

        p.drawPixmap(0, 0, self.pixmap)
        Shape.scale = self.scale
        for shape in self.shapes:
            if (shape.selected
                    or not self._hideBackround) and self.isVisible(shape):
                shape.fill = shape.selected or shape == self.hShape
                shape.paint(p)
        if self.current:
            self.current.paint(p)
            self.line.paint(p)
        if self.selectedShapeCopy:
            self.selectedShapeCopy.paint(p)

        # Paint rect
        if self.current is not None and len(self.line) == 2:
            leftTop = self.line[0]
            rightBottom = self.line[1]
            rectWidth = rightBottom.x() - leftTop.x()
            rectHeight = rightBottom.y() - leftTop.y()
            p.setPen(self.drawingRectColor)
            brush = QBrush(Qt.BDiagPattern)
            p.setBrush(brush)
            p.drawRect(leftTop.x(), leftTop.y(), rectWidth, rectHeight)

        if self.drawing() and not self.prevPoint.isNull(
        ) and not self.outOfPixmap(self.prevPoint):
            p.setPen(QColor(0, 0, 0))
            p.drawLine(self.prevPoint.x(), 0, self.prevPoint.x(),
                       self.pixmap.height())
            p.drawLine(0, self.prevPoint.y(), self.pixmap.width(),
                       self.prevPoint.y())

        self.setAutoFillBackground(True)
        if self.verified:
            pal = self.palette()
            pal.setColor(self.backgroundRole(), QColor(184, 239, 38, 128))
            self.setPalette(pal)
        else:
            pal = self.palette()
            pal.setColor(self.backgroundRole(), QColor(232, 232, 232, 255))
            self.setPalette(pal)

        p.end()

    def transformPos(self, point):
        """Convert from widget-logical coordinates to painter-logical coordinates."""
        return point / self.scale - self.offsetToCenter()

    def offsetToCenter(self):
        s = self.scale
        area = super(Canvas, self).size()
        w, h = self.pixmap.width() * s, self.pixmap.height() * s
        aw, ah = area.width(), area.height()
        x = (aw - w) / (2 * s) if aw > w else 0
        y = (ah - h) / (2 * s) if ah > h else 0
        return QPointF(x, y)

    def outOfPixmap(self, p):
        w, h = self.pixmap.width(), self.pixmap.height()
        return not (0 <= p.x() < w and 0 <= p.y() < h)

    def finalise(self):
        assert self.current
        if self.current.points[0] == self.current.points[-1]:
            self.current = None
            self.drawingPolygon.emit(False)
            self.update()
            return

        self.current.close()
        self.shapes.append(self.current)
        self.current = None
        self.setHiding(False)
        self.newShape.emit()
        self.update()

    def closeEnough(self, p1, p2):
        #d = distance(p1 - p2)
        #m = (p1-p2).manhattanLength()
        # print "d %.2f, m %d, %.2f" % (d, m, d - m)
        return distance(p1 - p2) < self.epsilon

    def intersectionPoint(self, p1, p2):
        # Cycle through each image edge in clockwise fashion,
        # and find the one intersecting the current line segment.
        # http://paulbourke.net/geometry/lineline2d/
        size = self.pixmap.size()
        points = [(0, 0), (size.width(), 0), (size.width(), size.height()),
                  (0, size.height())]
        x1, y1 = p1.x(), p1.y()
        x2, y2 = p2.x(), p2.y()

        d, i, (x, y) = min(self.intersectingEdges((x1, y1), (x2, y2), points))
        x3, y3 = points[i]
        x4, y4 = points[(i + 1) % 4]
        if (x, y) == (x1, y1):
            # Handle cases where previous point is on one of the edges.
            if x3 == x4:
                return QPointF(x3, min(max(0, y2), max(y3, y4)))
            else:  # y3 == y4
                return QPointF(min(max(0, x2), max(x3, x4)), y3)
        return QPointF(x, y)

    def intersectingEdges(self, x1y1, x2y2, points):
        """For each edge formed by `points', yield the intersection
        with the line segment `(x1,y1) - (x2,y2)`, if it exists.
        Also return the distance of `(x2,y2)' to the middle of the
        edge along with its index, so that the one closest can be chosen."""
        x1, y1 = x1y1
        x2, y2 = x2y2
        for i in range(len(points)):
            x3, y3 = points[i]
            x4, y4 = points[(i + 1) % 4]
            denom = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1)
            nua = (x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)
            nub = (x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)
            if denom == 0:
                # This covers two cases:
                #   nua == nub == 0: Coincident
                #   otherwise: Parallel
                continue
            ua, ub = nua / denom, nub / denom
            if 0 <= ua <= 1 and 0 <= ub <= 1:
                x = x1 + ua * (x2 - x1)
                y = y1 + ua * (y2 - y1)
                m = QPointF((x3 + x4) / 2, (y3 + y4) / 2)
                d = distance(m - QPointF(x2, y2))
                yield d, i, (x, y)

    # These two, along with a call to adjustSize are required for the
    # scroll area.
    def sizeHint(self):
        return self.minimumSizeHint()

    def minimumSizeHint(self):
        if self.pixmap:
            return self.scale * self.pixmap.size()
        return super(Canvas, self).minimumSizeHint()

    def wheelEvent(self, ev):
        qt_version = 4 if hasattr(ev, "delta") else 5
        if qt_version == 4:
            if ev.orientation() == Qt.Vertical:
                v_delta = ev.delta()
                h_delta = 0
            else:
                h_delta = ev.delta()
                v_delta = 0
        else:
            delta = ev.angleDelta()
            h_delta = delta.x()
            v_delta = delta.y()

        mods = ev.modifiers()
        if Qt.ControlModifier == int(mods) and v_delta:
            self.zoomRequest.emit(v_delta)
        else:
            v_delta and self.scrollRequest.emit(v_delta, Qt.Vertical)
            h_delta and self.scrollRequest.emit(h_delta, Qt.Horizontal)
        ev.accept()

    def keyPressEvent(self, ev):
        key = ev.key()
        if key == Qt.Key_Escape and self.current:
            print('ESC press')
            self.current = None
            self.drawingPolygon.emit(False)
            self.update()
        elif key == Qt.Key_Return and self.canCloseShape():
            self.finalise()
        elif key == Qt.Key_Left and self.selectedShape:
            self.moveOnePixel(QPointF(-1, 0))
        elif key == Qt.Key_Right and self.selectedShape:
            self.moveOnePixel(QPointF(1, 0))
        elif key == Qt.Key_Up and self.selectedShape:
            self.moveOnePixel(QPointF(0, -1))
        elif key == Qt.Key_Down and self.selectedShape:
            self.moveOnePixel(QPointF(0, 1))

    def moveOnePixel(self, direction):
        self.boundedMoveShapeBy(self.selectedShape, direction)
        self.shapeMoved.emit()
        self.repaint()

    def setLastLabel(self, text, line_color=None, fill_color=None):
        assert text
        self.shapes[-1].label = text
        if line_color:
            self.shapes[-1].line_color = line_color

        if fill_color:
            self.shapes[-1].fill_color = fill_color

        return self.shapes[-1]

    def undoLastLine(self):
        assert self.shapes
        self.current = self.shapes.pop()
        self.current.setOpen()
        self.line.points = [self.current[-1], self.current[0]]
        self.drawingPolygon.emit(True)

    def resetAllLines(self):
        assert self.shapes
        self.current = self.shapes.pop()
        self.current.setOpen()
        self.line.points = [self.current[-1], self.current[0]]
        self.drawingPolygon.emit(True)
        self.current = None
        self.drawingPolygon.emit(False)
        self.update()

    def loadPixmap(self, pixmap):
        self.pixmap = pixmap
        self.shapes = []
        self.repaint()

    def loadShapes(self, shapes):
        self.shapes = list(shapes)
        self.current = None
        self.repaint()

    def setShapeVisible(self, shape, value):
        self.visible[shape] = value
        self.repaint()

    def currentCursor(self):
        cursor = QApplication.overrideCursor()
        if cursor is not None:
            cursor = cursor.shape()
        return cursor

    def overrideCursor(self, cursor):
        self._cursor = cursor
        if self.currentCursor() is None:
            QApplication.setOverrideCursor(cursor)
        else:
            QApplication.changeOverrideCursor(cursor)

    def restoreCursor(self):
        QApplication.restoreOverrideCursor()

    def resetState(self):
        self.restoreCursor()
        self.pixmap = None
        self.update()
コード例 #22
0
class Canvas(QWidget):
    zoomRequest = pyqtSignal(int)
    scrollRequest = pyqtSignal(int, int)
    newShape = pyqtSignal()
    selectionChanged = pyqtSignal(bool)
    shapeMoved = pyqtSignal()
    drawingPolygon = pyqtSignal(bool)

    CREATE, EDIT, POLYGON = list(range(3))

    epsilon = 11.0

    def __init__(self, *args, **kwargs):
        super(Canvas, self).__init__(*args, **kwargs)
        # Initialise local state.
        self.mode = self.EDIT
        self.shapes = []
        self.current = None
        self.selectedShape = None  # save the selected shape here
        self.selectedShapeCopy = None
        self.lineColor = QColor(0, 0, 255)
        self.line = Shape(line_color=self.lineColor)
        self.prevPoint = QPointF()
        self.offsets = QPointF(), QPointF()
        self.scale = 1.0
        self.pixmap = QPixmap()
        self.visible = {}
        self._hideBackround = False
        self.hideBackround = False
        self.hShape = None
        self.hVertex = None
        self._painter = QPainter()
        self._cursor = CURSOR_DEFAULT
        # Menus:
        self.menus = (QMenu(), QMenu())
        # Set widget options.
        self.setMouseTracking(True)
        self.setFocusPolicy(Qt.WheelFocus)
        self.verified = False
        self.actions = None

    def enterEvent(self, ev):
        self.overrideCursor(self._cursor)

    def leaveEvent(self, ev):
        self.restoreCursor()

    def focusOutEvent(self, ev):
        self.restoreCursor()

    def isVisible(self, shape):
        return self.visible.get(shape, True)

    def drawing(self):
        return self.mode == self.CREATE

    def ploygon(self):
        return self.mode == self.POLYGON

    def editing(self):
        return self.mode == self.EDIT

    def setEditing(self, value=True):
        self.mode = self.EDIT if value else self.CREATE
        if not value:  # Create
            self.unHighlight()
            self.deSelectShape()
        if self.mode == self.CREATE:
            self.flag_enter_polygon = False

    def setPolygon(self, actions, filepath, value=True):
        self.mode = self.POLYGON if value else self.EDIT
        self.flag_enter_polygon = True
        if not value:  # Create
            self.unHighlight()
            self.deSelectShape()
        self.actions = actions
        self.filepath = filepath
        self.img_for_grab = cv2.imread(self.filepath)
        self.img2_for_grab = self.img_for_grab.copy()
        self.mask_for_grab = np.zeros(
            self.img_for_grab.shape[:2],
            dtype=np.uint8)  # mask initialized to PR_BG
        self.output = np.zeros(self.img_for_grab.shape,
                               np.uint8)  # output image to be shown

        self.BLUE = [255, 0, 0]  # flag_rectangle color
        self.RED = [0, 0, 255]  # PR BG
        self.GREEN = [0, 255, 0]  # PR FG
        self.BLACK = [0, 0, 0]  # sure BG
        self.WHITE = [255, 255, 255]  # sure FG

        self.DRAW_BG = {'color': self.BLACK, 'val': 0}
        self.DRAW_FG = {'color': self.WHITE, 'val': 1}
        self.DRAW_PR_FG = {'color': self.GREEN, 'val': 3}
        self.DRAW_PR_BG = {'color': self.RED, 'val': 2}

        # setting up flags
        self.rect_for_grab = (0, 0, 1, 1)
        self.value_for_grab = self.DRAW_FG  # flag_drawing initialized to FG
        self.thickness = 1  # brush thickness

    def Segment_Roi(self):
        # self.seg_count = self.seg_count +1
        if self.flag_enter_polygon:
            bgdmodel = np.zeros((1, 65), np.float64)
            fgdmodel = np.zeros((1, 65), np.float64)
            cv2.grabCut(self.img2_for_grab, self.mask_for_grab,
                        self.rect_for_grab, bgdmodel, fgdmodel, 1,
                        cv2.GC_INIT_WITH_RECT)
            cv2.grabCut(self.img2_for_grab, self.mask_for_grab,
                        self.rect_for_grab, bgdmodel, fgdmodel, 1,
                        cv2.GC_INIT_WITH_MASK)

            mask2 = np.where(
                (self.mask_for_grab == 1) + (self.mask_for_grab == 3), 255,
                0).astype('uint8')
            self.output = cv2.bitwise_and(self.img2_for_grab,
                                          self.img2_for_grab,
                                          mask=mask2)
            gray = cv2.cvtColor(self.output, cv2.COLOR_BGR2GRAY)
            ret, binary = cv2.threshold(gray, 1, 255, cv2.THRESH_BINARY)
            contours = cv2.findContours(binary, cv2.RETR_TREE,
                                        cv2.CHAIN_APPROX_SIMPLE)  #,hierarchy
            contours_big_area = []
            max_contours = 0
            max_contours_i = 0
            for i in range(len(contours[1])):
                if len(contours[1][i]) > max_contours:
                    max_contours = len(contours[1][i])
                    max_contours_i = i
            # for i in range(len(contours[1][max_contours_i])):
            contours_tmp = np.array(contours[1][max_contours_i])
            contours_big_area.append(contours_tmp)
            # if self.seg_count == 1:
            self.shapes.remove(self.shapes[len(self.shapes) - 1])  #
            self.update()

            self.contours_t = Shape()
            for i in range(len(contours_big_area[0])):
                self.contours_t.addPoint(
                    QPointF(contours_big_area[0][i][0][0],
                            contours_big_area[0][i][0][1]))
            self.contours_t.addPoint(
                QPointF(contours_big_area[0][0][0][0],
                        contours_big_area[0][0][0][1]))
            self.shapes.append(self.contours_t)
            self.setHiding(False)
            self.newShape.emit()
            self.update()
        else:
            print('Please paint polygon first')

    def unHighlight(self):
        if self.hShape:
            self.hShape.highlightClear()
        self.hVertex = self.hShape = None

    def selectedVertex(self):
        return self.hVertex is not None

    def mouseMoveEvent(self, ev):
        """Update line with last point and current coordinates."""
        pos = self.transformPos(ev.pos())

        self.restoreCursor()
        # print(self.mode)
        # Polygon drawing.
        if not self.editing():

            self.overrideCursor(CURSOR_DRAW)
            if self.current:
                # print('enter mousemove')
                color = self.lineColor
                if self.outOfPixmap(pos):
                    # Don't allow the user to draw outside the pixmap.
                    # Project the point to the pixmap's edges.
                    pos = self.intersectionPoint(self.current[-1], pos)
                elif len(self.current) > 1 and self.closeEnough(
                        pos, self.current[0]):
                    # Attract line to starting point and colorise to alert the
                    # user:
                    pos = self.current[0]
                    color = self.current.line_color
                    self.overrideCursor(CURSOR_POINT)
                    self.current.highlightVertex(0, Shape.NEAR_VERTEX)
                self.line[1] = pos
                self.line.line_color = color
                self.repaint()
                self.current.highlightClear()
            return

        # Polygon copy moving.
        if Qt.RightButton & ev.buttons():
            if self.selectedShapeCopy and self.prevPoint:
                self.overrideCursor(CURSOR_MOVE)
                self.boundedMoveShape(self.selectedShapeCopy, pos)
                self.repaint()
            elif self.selectedShape:
                self.selectedShapeCopy = self.selectedShape.copy()
                self.repaint()
            return

        # Polygon/Vertex moving.
        if Qt.LeftButton & ev.buttons():
            if self.selectedVertex():
                self.boundedMoveVertex(pos)
                self.shapeMoved.emit()
                self.repaint()
            elif self.selectedShape and self.prevPoint:
                self.overrideCursor(CURSOR_MOVE)
                self.boundedMoveShape(self.selectedShape, pos)
                self.shapeMoved.emit()
                self.repaint()
            return

        # Just hovering over the canvas, 2 posibilities:
        # - Highlight shapes
        # - Highlight vertex
        # Update shape/vertex fill and tooltip value accordingly.
        self.setToolTip("Image")
        for shape in reversed([s for s in self.shapes if self.isVisible(s)]):
            # Look for a nearby vertex to highlight. If that fails,
            # check if we happen to be inside a shape.
            index = shape.nearestVertex(pos, self.epsilon)
            if index is not None:
                if self.selectedVertex():
                    self.hShape.highlightClear()
                self.hVertex, self.hShape = index, shape
                shape.highlightVertex(index, shape.MOVE_VERTEX)
                self.overrideCursor(CURSOR_POINT)
                self.setToolTip("Click & drag to move point")
                self.setStatusTip(self.toolTip())
                self.update()
                break
            elif shape.containsPoint(pos):
                if self.selectedVertex():
                    self.hShape.highlightClear()
                self.hVertex, self.hShape = None, shape
                self.setToolTip("Click & drag to move shape '%s'" %
                                shape.label)
                self.setStatusTip(self.toolTip())
                self.overrideCursor(CURSOR_GRAB)
                self.update()
                break
        else:  # Nothing found, clear highlights, reset state.
            if self.hShape:
                self.hShape.highlightClear()
                self.update()
            self.hVertex, self.hShape = None, None

    def mousePressEvent(self, ev):
        pos = self.transformPos(ev.pos())

        if ev.button() == Qt.LeftButton:
            if not self.editing():
                self.handleDrawing(pos)
            else:
                self.selectShapePoint(pos)
                self.prevPoint = pos
                self.repaint()
        elif ev.button() == Qt.RightButton and self.editing():
            self.selectShapePoint(pos)
            self.prevPoint = pos
            self.repaint()

    def mouseReleaseEvent(self, ev):
        if ev.button() == Qt.RightButton:
            menu = self.menus[bool(self.selectedShapeCopy)]
            self.restoreCursor()
            if not menu.exec_(self.mapToGlobal(ev.pos()))\
               and self.selectedShapeCopy:
                # Cancel the move by deleting the shadow copy.
                self.selectedShapeCopy = None
                self.repaint()
        elif ev.button() == Qt.LeftButton and self.selectedShape:
            self.overrideCursor(CURSOR_GRAB)
        elif ev.button() == Qt.LeftButton:
            pos = self.transformPos(ev.pos())
            if self.drawing():
                self.handleDrawing(pos)

    def endMove(self, copy=False):
        assert self.selectedShape and self.selectedShapeCopy
        shape = self.selectedShapeCopy
        #del shape.fill_color
        #del shape.line_color
        if copy:
            self.shapes.append(shape)
            self.selectedShape.selected = False
            self.selectedShape = shape
            self.repaint()
        else:
            self.selectedShape.points = [p for p in shape.points]
        self.selectedShapeCopy = None

    def hideBackroundShapes(self, value):
        self.hideBackround = value
        if self.selectedShape:
            # Only hide other shapes if there is a current selection.
            # Otherwise the user will not be able to select a shape.
            self.setHiding(True)
            self.repaint()

    def handleDrawing(self, pos):
        if self.current:
            #and self.current.reachMaxPoints() is False:
            if self.ploygon():
                self.current.addPoint(self.line[1])
            else:
                initPos = self.current[0]
                minX = initPos.x()
                minY = initPos.y()
                targetPos = self.line[1]
                maxX = targetPos.x()
                maxY = targetPos.y()
                self.current.addPoint(QPointF(maxX, minY))
                self.current.addPoint(targetPos)
                self.current.addPoint(QPointF(minX, maxY))
                self.current.addPoint(initPos)
            self.line[0] = self.current[-1]
            if self.current.isClosed():
                self.finalise()
        elif not self.outOfPixmap(pos):
            self.current = Shape()
            self.current.addPoint(pos)
            self.line.points = [pos, pos]
            self.setHiding()
            self.drawingPolygon.emit(True)
            self.update()

    def setHiding(self, enable=True):
        self._hideBackround = self.hideBackround if enable else False

    def canCloseShape(self):
        return self.drawing() and self.ploygon() and self.current and len(
            self.current) > 2

    def mouseDoubleClickEvent(self, ev):
        # We need at least 4 points here, since the mousePress handler
        # adds an extra one before this handler is called.
        if self.canCloseShape() and len(self.current) > 3:
            self.current.popPoint()
            self.finalise()

    def selectShape(self, shape):
        self.deSelectShape()
        shape.selected = True
        self.selectedShape = shape
        self.setHiding()
        self.selectionChanged.emit(True)
        self.update()

    def selectShapePoint(self, point):
        """Select the first shape created which contains this point."""
        self.deSelectShape()
        if self.selectedVertex():  # A vertex is marked for selection.
            index, shape = self.hVertex, self.hShape
            shape.highlightVertex(index, shape.MOVE_VERTEX)
            return
        for shape in reversed(self.shapes):
            if self.isVisible(shape) and shape.containsPoint(point):
                shape.selected = True
                self.selectedShape = shape
                self.calculateOffsets(shape, point)
                self.setHiding()
                self.selectionChanged.emit(True)
                return

    def calculateOffsets(self, shape, point):
        rect = shape.boundingRect()
        x1 = rect.x() - point.x()
        y1 = rect.y() - point.y()
        x2 = (rect.x() + rect.width()) - point.x()
        y2 = (rect.y() + rect.height()) - point.y()
        self.offsets = QPointF(x1, y1), QPointF(x2, y2)

    def boundedMoveVertex(self, pos):
        index, shape = self.hVertex, self.hShape
        point = shape[index]
        if self.outOfPixmap(pos):
            pos = self.intersectionPoint(point, pos)

        shiftPos = pos - point

        if len(shape) == 4 and (int(shape[0].x()) == int(shape[3].x())
                                and int(shape[0].y()) == int(shape[1].y())):
            lindex = (index + 1) % 4
            rindex = (index + 3) % 4
            lshift = None
            rshift = None
            if index % 2 == 0:
                rshift = QPointF(shiftPos.x(), 0)
                lshift = QPointF(0, shiftPos.y())
            else:
                lshift = QPointF(shiftPos.x(), 0)
                rshift = QPointF(0, shiftPos.y())
            shape.moveVertexBy(rindex, rshift)
            shape.moveVertexBy(lindex, lshift)

        shape.moveVertexBy(index, shiftPos)

    def boundedMoveShape(self, shape, pos):
        if self.outOfPixmap(pos):
            return False  # No need to move
        o1 = pos + self.offsets[0]
        if self.outOfPixmap(o1):
            pos -= QPointF(min(0, o1.x()), min(0, o1.y()))
        o2 = pos + self.offsets[1]
        if self.outOfPixmap(o2):
            pos += QPointF(min(0,
                               self.pixmap.width() - o2.x()),
                           min(0,
                               self.pixmap.height() - o2.y()))
        # The next line tracks the new position of the cursor
        # relative to the shape, but also results in making it
        # a bit "shaky" when nearing the border and allows it to
        # go outside of the shape's area for some reason. XXX
        #self.calculateOffsets(self.selectedShape, pos)
        dp = pos - self.prevPoint
        if dp:
            shape.moveBy(dp)
            self.prevPoint = pos
            return True
        return False

    def deSelectShape(self):
        if self.selectedShape:
            self.selectedShape.selected = False
            self.selectedShape = None
            self.setHiding(False)
            self.selectionChanged.emit(False)
            self.update()

    def deleteSelected(self):
        if self.selectedShape:
            shape = self.selectedShape
            self.shapes.remove(self.selectedShape)
            self.selectedShape = None
            self.update()
            return shape

    def copySelectedShape(self):
        if self.selectedShape:
            shape = self.selectedShape.copy()
            self.deSelectShape()
            self.shapes.append(shape)
            shape.selected = True
            self.selectedShape = shape
            self.boundedShiftShape(shape)
            return shape

    def boundedShiftShape(self, shape):
        # Try to move in one direction, and if it fails in another.
        # Give up if both fail.
        point = shape[0]
        offset = QPointF(2.0, 2.0)
        self.calculateOffsets(shape, point)
        self.prevPoint = point
        if not self.boundedMoveShape(shape, point - offset):
            self.boundedMoveShape(shape, point + offset)

    def paintEvent(self, event):
        if not self.pixmap:
            return super(Canvas, self).paintEvent(event)

        p = self._painter
        p.begin(self)
        p.setRenderHint(QPainter.Antialiasing)
        p.setRenderHint(QPainter.HighQualityAntialiasing)
        p.setRenderHint(QPainter.SmoothPixmapTransform)

        p.scale(self.scale, self.scale)
        p.translate(self.offsetToCenter())

        p.drawPixmap(0, 0, self.pixmap)
        Shape.scale = self.scale
        for shape in self.shapes:
            if (shape.selected
                    or not self._hideBackround) and self.isVisible(shape):
                shape.fill = shape.selected or shape == self.hShape
                shape.paint(p)
        if self.current:
            self.current.paint(p)
            self.line.paint(p)
        if self.selectedShapeCopy:
            self.selectedShapeCopy.paint(p)

        # Paint rect
        if self.drawing():
            if self.current is not None and len(self.line) == 2:
                leftTop = self.line[0]
                rightBottom = self.line[1]
                rectWidth = rightBottom.x() - leftTop.x()
                rectHeight = rightBottom.y() - leftTop.y()
                color = QColor(0, 220, 0)
                p.setPen(color)
                brush = QBrush(Qt.BDiagPattern)
                p.setBrush(brush)
                p.drawRect(leftTop.x(), leftTop.y(), rectWidth, rectHeight)

            self.setAutoFillBackground(True)
            if self.verified:
                pal = self.palette()
                pal.setColor(self.backgroundRole(), QColor(184, 239, 38, 128))
                self.setPalette(pal)
            else:
                pal = self.palette()
                pal.setColor(self.backgroundRole(), QColor(232, 232, 232, 255))
                self.setPalette(pal)

        p.end()

    def transformPos(self, point):
        """Convert from widget-logical coordinates to painter-logical coordinates."""
        return point / self.scale - self.offsetToCenter()

    def offsetToCenter(self):
        s = self.scale
        area = super(Canvas, self).size()
        w, h = self.pixmap.width() * s, self.pixmap.height() * s
        aw, ah = area.width(), area.height()
        x = (aw - w) / (2 * s) if aw > w else 0
        y = (ah - h) / (2 * s) if ah > h else 0
        return QPointF(x, y)

    def outOfPixmap(self, p):
        w, h = self.pixmap.width(), self.pixmap.height()
        return not (0 <= p.x() <= w and 0 <= p.y() <= h)

    def finalise(self):
        assert self.current
        self.current.close()
        self.shapes.append(self.current)
        if self.actions is not None:
            self.actions.Segment_Roi.setEnabled(True)
            #get the point
            xmin = float('inf')
            ymin = float('inf')
            xmax = float('-inf')
            ymax = float('-inf')
            for i in range(len(self.shapes[len(self.shapes) - 1])):
                x = self.shapes[len(self.shapes) - 1][i].x()
                y = self.shapes[len(self.shapes) - 1][i].y()
                xmin = int(min(x, xmin))
                ymin = int(min(y, ymin))
                xmax = int(max(x, xmax))
                ymax = int(max(y, ymax))
            #rect
            self.grab_0_x = xmin
            self.grab_0_y = ymin
            self.grab_2_x = xmax
            self.grab_2_y = ymax
            self.rect_for_grab = (self.grab_0_x, self.grab_0_y,
                                  int(abs(self.grab_0_x - self.grab_2_x)),
                                  int(abs(self.grab_0_y - self.grab_2_y)))
            #bg
            self.value_for_grab = self.DRAW_BG
            for i in range(len(self.shapes[len(self.shapes) - 1])):
                self.bg_0_x = int(self.shapes[len(self.shapes) - 1][i].x())
                self.bg_0_y = int(self.shapes[len(self.shapes) - 1][i].y())

                cv2.circle(self.mask_for_grab, (self.bg_0_x, self.bg_0_y),
                           self.thickness + 2, self.value_for_grab['val'], -1)
        # if self.flag_fg:
        #     self.fg_0_x = int(self.shapes[len(self.shapes)-1][0].x())
        #     self.fg_0_y = int(self.shapes[len(self.shapes)-1][0].y())
        #     self.value_for_grab = self.DRAW_FG
        #     cv2.circle(self.mask_for_grab,(self.fg_0_x,self.fg_0_y),self.thickness+2,self.value_for_grab['val'],-1)

        self.current = None
        self.setHiding(False)
        self.newShape.emit()
        self.update()

    def closeEnough(self, p1, p2):
        #d = distance(p1 - p2)
        #m = (p1-p2).manhattanLength()
        # print "d %.2f, m %d, %.2f" % (d, m, d - m)
        return distance(p1 - p2) < self.epsilon

    def intersectionPoint(self, p1, p2):
        # Cycle through each image edge in clockwise fashion,
        # and find the one intersecting the current line segment.
        # http://paulbourke.net/geometry/lineline2d/
        size = self.pixmap.size()
        points = [(0, 0), (size.width(), 0), (size.width(), size.height()),
                  (0, size.height())]
        x1, y1 = p1.x(), p1.y()
        x2, y2 = p2.x(), p2.y()
        d, i, (x, y) = min(self.intersectingEdges((x1, y1), (x2, y2), points))
        x3, y3 = points[i]
        x4, y4 = points[(i + 1) % 4]
        if (x, y) == (x1, y1):
            # Handle cases where previous point is on one of the edges.
            if x3 == x4:
                return QPointF(x3, min(max(0, y2), max(y3, y4)))
            else:  # y3 == y4
                return QPointF(min(max(0, x2), max(x3, x4)), y3)
        return QPointF(x, y)

    def intersectingEdges(self, x1y1, x2y2, points):
        """For each edge formed by `points', yield the intersection
        with the line segment `(x1,y1) - (x2,y2)`, if it exists.
        Also return the distance of `(x2,y2)' to the middle of the
        edge along with its index, so that the one closest can be chosen."""
        x1, y1 = x1y1
        x2, y2 = x2y2
        for i in range(4):
            x3, y3 = points[i]
            x4, y4 = points[(i + 1) % 4]
            denom = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1)
            nua = (x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)
            nub = (x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)
            if denom == 0:
                # This covers two cases:
                #   nua == nub == 0: Coincident
                #   otherwise: Parallel
                continue
            ua, ub = nua / denom, nub / denom
            if 0 <= ua <= 1 and 0 <= ub <= 1:
                x = x1 + ua * (x2 - x1)
                y = y1 + ua * (y2 - y1)
                m = QPointF((x3 + x4) / 2, (y3 + y4) / 2)
                d = distance(m - QPointF(x2, y2))
                yield d, i, (x, y)

    # These two, along with a call to adjustSize are required for the
    # scroll area.
    def sizeHint(self):
        return self.minimumSizeHint()

    def minimumSizeHint(self):
        if self.pixmap:
            return self.scale * self.pixmap.size()
        return super(Canvas, self).minimumSizeHint()

    def wheelEvent(self, ev):
        qt_version = 4 if hasattr(ev, "delta") else 5
        if qt_version == 4:
            if ev.orientation() == Qt.Vertical:
                v_delta = ev.delta()
                h_delta = 0
            else:
                h_delta = ev.delta()
                v_delta = 0
        else:
            delta = ev.angleDelta()
            h_delta = delta.x()
            v_delta = delta.y()

        mods = ev.modifiers()
        if Qt.ControlModifier == int(mods) and v_delta:
            self.zoomRequest.emit(v_delta)
        else:
            v_delta and self.scrollRequest.emit(v_delta, Qt.Vertical)
            h_delta and self.scrollRequest.emit(h_delta, Qt.Horizontal)
        ev.accept()

    def keyPressEvent(self, ev):
        key = ev.key()
        if key == Qt.Key_Escape and self.current:
            print('ESC press')
            self.current = None
            self.drawingPolygon.emit(False)
            self.update()
        elif key == Qt.Key_Return and self.canCloseShape():
            self.finalise()
        elif key == Qt.Key_Left and self.selectedShape:
            self.moveOnePixel('Left')
        elif key == Qt.Key_Right and self.selectedShape:
            self.moveOnePixel('Right')
        elif key == Qt.Key_Up and self.selectedShape:
            self.moveOnePixel('Up')
        elif key == Qt.Key_Down and self.selectedShape:
            self.moveOnePixel('Down')

    def moveOnePixel(self, direction):
        # print(self.selectedShape.points)
        if direction == 'Left' and not self.moveOutOfBound(QPointF(-1.0, 0)):
            # print("move Left one pixel")
            self.selectedShape.points[0] += QPointF(-1.0, 0)
            self.selectedShape.points[1] += QPointF(-1.0, 0)
            self.selectedShape.points[2] += QPointF(-1.0, 0)
            self.selectedShape.points[3] += QPointF(-1.0, 0)
        elif direction == 'Right' and not self.moveOutOfBound(QPointF(1.0, 0)):
            # print("move Right one pixel")
            self.selectedShape.points[0] += QPointF(1.0, 0)
            self.selectedShape.points[1] += QPointF(1.0, 0)
            self.selectedShape.points[2] += QPointF(1.0, 0)
            self.selectedShape.points[3] += QPointF(1.0, 0)
        elif direction == 'Up' and not self.moveOutOfBound(QPointF(0, -1.0)):
            # print("move Up one pixel")
            self.selectedShape.points[0] += QPointF(0, -1.0)
            self.selectedShape.points[1] += QPointF(0, -1.0)
            self.selectedShape.points[2] += QPointF(0, -1.0)
            self.selectedShape.points[3] += QPointF(0, -1.0)
        elif direction == 'Down' and not self.moveOutOfBound(QPointF(0, 1.0)):
            # print("move Down one pixel")
            self.selectedShape.points[0] += QPointF(0, 1.0)
            self.selectedShape.points[1] += QPointF(0, 1.0)
            self.selectedShape.points[2] += QPointF(0, 1.0)
            self.selectedShape.points[3] += QPointF(0, 1.0)
        self.shapeMoved.emit()
        self.repaint()

    def moveOutOfBound(self, step):
        points = [
            p1 + p2 for p1, p2 in zip(self.selectedShape.points, [step] * 4)
        ]
        return True in map(self.outOfPixmap, points)

    def setLastLabel(self, text):
        assert text
        self.shapes[-1].label = text
        return self.shapes[-1]

    def undoLastLine(self):
        assert self.shapes
        self.current = self.shapes.pop()
        self.current.setOpen()
        self.line.points = [self.current[-1], self.current[0]]
        self.drawingPolygon.emit(True)

    def resetAllLines(self):
        assert self.shapes
        self.current = self.shapes.pop()
        self.current.setOpen()
        self.line.points = [self.current[-1], self.current[0]]
        self.drawingPolygon.emit(True)
        self.current = None
        self.drawingPolygon.emit(False)
        self.update()

    def loadPixmap(self, pixmap):
        self.pixmap = pixmap
        self.shapes = []
        self.repaint()

    def loadShapes(self, shapes):
        self.shapes = list(shapes)
        self.current = None
        self.repaint()

    def setShapeVisible(self, shape, value):
        self.visible[shape] = value
        self.repaint()

    def overrideCursor(self, cursor):
        self.restoreCursor()
        self._cursor = cursor
        QApplication.setOverrideCursor(cursor)

    def restoreCursor(self):
        QApplication.restoreOverrideCursor()

    def resetState(self):
        self.restoreCursor()
        self.pixmap = None
        self.update()
コード例 #23
0
    def loadLabels(self, shapes):
        s = []
        self.initLabelList()
        idx = len(self.labelInfoDict)
        for label, lineWidth, points, ellipse_points, rotate, r1, r2, center in shapes:
            if label not in self.labelInfoDict.keys():
                with open(
                        os.path.join(os.path.dirname(__file__),
                                     "predefined_labels.json")) as f:
                    predefined_json = json.load(f)
                    new_append_predefined_dict = {
                        "name": "",
                        "isPathClosed": True,
                        "type": 0,
                        "enable": True
                    }
                    new_append_predefined_dict['name'] = label
                    if r1:
                        new_append_predefined_dict['type'] = 1
                    predefined_json['labels'].append(
                        new_append_predefined_dict)
                with open(
                        os.path.join(os.path.dirname(__file__),
                                     "predefined_labels.json"), "w") as f:
                    json.dump(predefined_json, f, indent=4)
                self.canvas.LABEL_COLORS.extend(self.canvas.random_colors(N=1))
                self.labelInfoDict[label] = (
                    True, new_append_predefined_dict['type'],
                    self.canvas.LABEL_COLORS[idx])
                item = HashableQListWidgetItem(label)
                item.setFlags(item.flags() | Qt.ItemIsUserCheckable)
                item.setCheckState(Qt.Checked)
                item.setSelected(True)
                self.ui.labelList.addItem(item)
                self.labelToItem[label] = item
                idx = idx + 1

            shape = Shape(label=label)
            shape.lineWidth = lineWidth
            shape.isPathClosed = self.labelInfoDict[label][0]
            shape.d_type = self.labelInfoDict[label][1]
            shape.lineColor = self.labelInfoDict[label][2]
            shape.rotate = rotate
            shape.r1 = r1
            shape.r2 = r2
            shape.center = center

            for x, y in points:
                shape.addPoint(QPointF(x, y))

            for x, y in ellipse_points:
                shape.ellipse_points.append(QPointF(x, y))

            if shape.d_type == self.canvas.Ellipse:
                shape.sampleEllipsePoints()

            shape.close()
            s.append(shape)
            self.addShapeList(shape)

        self.canvas.loadShapes(s)
コード例 #24
0
ファイル: canvas.py プロジェクト: cdicle/labelImg
class Canvas(QWidget):
    zoomRequest = pyqtSignal(int)
    scrollRequest = pyqtSignal(int, int)
    newShape = pyqtSignal()
    selectionChanged = pyqtSignal(bool)
    shapeMoved = pyqtSignal()
    drawingPolygon = pyqtSignal(bool)

    CREATE, EDIT = list(range(2))

    epsilon = 11.0

    def __init__(self, *args, **kwargs):
        super(Canvas, self).__init__(*args, **kwargs)
        # Initialise local state.
        self.mode = self.EDIT
        self.shapes = []
        self.current = None
        self.selectedShape = None  # save the selected shape here
        self.selectedShapeCopy = None
        self.lineColor = QColor(0, 0, 255)
        self.line = Shape(line_color=self.lineColor)
        self.prevPoint = QPointF()
        self.offsets = QPointF(), QPointF()
        self.scale = 1.0
        self.pixmap = QPixmap()
        self.visible = {}
        self._hideBackround = False
        self.hideBackround = False
        self.hShape = None
        self.hVertex = None
        self._painter = QPainter()
        self._cursor = CURSOR_DEFAULT
        # Menus:
        self.menus = (QMenu(), QMenu())
        # Set widget options.
        self.setMouseTracking(True)
        self.setFocusPolicy(Qt.WheelFocus)
        self.verified = False

    def enterEvent(self, ev):
        self.overrideCursor(self._cursor)

    def leaveEvent(self, ev):
        self.restoreCursor()

    def focusOutEvent(self, ev):
        self.restoreCursor()

    def isVisible(self, shape):
        return self.visible.get(shape, True)

    def drawing(self):
        return self.mode == self.CREATE

    def editing(self):
        return self.mode == self.EDIT

    def setEditing(self, value=True):
        self.mode = self.EDIT if value else self.CREATE
        if not value:  # Create
            self.unHighlight()
            self.deSelectShape()
        self.prevPoint = QPointF()
        self.repaint()

    def unHighlight(self):
        if self.hShape:
            self.hShape.highlightClear()
        self.hVertex = self.hShape = None

    def selectedVertex(self):
        return self.hVertex is not None

    def mouseMoveEvent(self, ev):
        """Update line with last point and current coordinates."""
        pos = self.transformPos(ev.pos())

        # Polygon drawing.
        if self.drawing():
            self.overrideCursor(CURSOR_DRAW)
            if self.current:
                color = self.lineColor
                if self.outOfPixmap(pos):
                    # Don't allow the user to draw outside the pixmap.
                    # Project the point to the pixmap's edges.
                    pos = self.intersectionPoint(self.current[-1], pos)
                elif len(self.current) > 1 and self.closeEnough(pos, self.current[0]):
                    # Attract line to starting point and colorise to alert the
                    # user:
                    pos = self.current[0]
                    color = self.current.line_color
                    self.overrideCursor(CURSOR_POINT)
                    self.current.highlightVertex(0, Shape.NEAR_VERTEX)
                self.line[1] = pos
                self.line.line_color = color
                self.prevPoint = QPointF()
                self.current.highlightClear()
            else:
                self.prevPoint = pos
            self.repaint()
            return

        # Polygon copy moving.
        if Qt.RightButton & ev.buttons():
            if self.selectedShapeCopy and self.prevPoint:
                self.overrideCursor(CURSOR_MOVE)
                self.boundedMoveShape(self.selectedShapeCopy, pos)
                self.repaint()
            elif self.selectedShape:
                self.selectedShapeCopy = self.selectedShape.copy()
                self.repaint()
            return

        # Polygon/Vertex moving.
        if Qt.LeftButton & ev.buttons():
            if self.selectedVertex():
                self.boundedMoveVertex(pos)
                self.shapeMoved.emit()
                self.repaint()
            elif self.selectedShape and self.prevPoint:
                self.overrideCursor(CURSOR_MOVE)
                self.boundedMoveShape(self.selectedShape, pos)
                self.shapeMoved.emit()
                self.repaint()
            return

        # Just hovering over the canvas, 2 posibilities:
        # - Highlight shapes
        # - Highlight vertex
        # Update shape/vertex fill and tooltip value accordingly.
        self.setToolTip("Image")
        for shape in reversed([s for s in self.shapes if self.isVisible(s)]):
            # Look for a nearby vertex to highlight. If that fails,
            # check if we happen to be inside a shape.
            index = shape.nearestVertex(pos, self.epsilon)
            if index is not None:
                if self.selectedVertex():
                    self.hShape.highlightClear()
                self.hVertex, self.hShape = index, shape
                shape.highlightVertex(index, shape.MOVE_VERTEX)
                self.overrideCursor(CURSOR_POINT)
                self.setToolTip("Click & drag to move point")
                self.setStatusTip(self.toolTip())
                self.update()
                break
            elif shape.containsPoint(pos):
                if self.selectedVertex():
                    self.hShape.highlightClear()
                self.hVertex, self.hShape = None, shape
                self.setToolTip(
                    "Click & drag to move shape '%s'" % shape.label)
                self.setStatusTip(self.toolTip())
                self.overrideCursor(CURSOR_GRAB)
                self.update()
                break
        else:  # Nothing found, clear highlights, reset state.
            if self.hShape:
                self.hShape.highlightClear()
                self.update()
            self.hVertex, self.hShape = None, None
            self.overrideCursor(CURSOR_DEFAULT)

    def mousePressEvent(self, ev):
        pos = self.transformPos(ev.pos())

        if ev.button() == Qt.LeftButton:
            if self.drawing():
                self.handleDrawing(pos)
            else:
                self.selectShapePoint(pos)
                self.prevPoint = pos
                self.repaint()
        elif ev.button() == Qt.RightButton and self.editing():
            self.selectShapePoint(pos)
            self.prevPoint = pos
            self.repaint()

    def mouseReleaseEvent(self, ev):
        if ev.button() == Qt.RightButton:
            menu = self.menus[bool(self.selectedShapeCopy)]
            self.restoreCursor()
            if not menu.exec_(self.mapToGlobal(ev.pos()))\
               and self.selectedShapeCopy:
                # Cancel the move by deleting the shadow copy.
                self.selectedShapeCopy = None
                self.repaint()
        elif ev.button() == Qt.LeftButton and self.selectedShape:
            if self.selectedVertex():
                self.overrideCursor(CURSOR_POINT)
            else:
                self.overrideCursor(CURSOR_GRAB)
        elif ev.button() == Qt.LeftButton:
            pos = self.transformPos(ev.pos())
            if self.drawing():
                self.handleDrawing(pos)

    def endMove(self, copy=False):
        assert self.selectedShape and self.selectedShapeCopy
        shape = self.selectedShapeCopy
        #del shape.fill_color
        #del shape.line_color
        if copy:
            self.shapes.append(shape)
            self.selectedShape.selected = False
            self.selectedShape = shape
            self.repaint()
        else:
            self.selectedShape.points = [p for p in shape.points]
        self.selectedShapeCopy = None

    def hideBackroundShapes(self, value):
        self.hideBackround = value
        if self.selectedShape:
            # Only hide other shapes if there is a current selection.
            # Otherwise the user will not be able to select a shape.
            self.setHiding(True)
            self.repaint()

    def handleDrawing(self, pos):
        if self.current and self.current.reachMaxPoints() is False:
            initPos = self.current[0]
            minX = initPos.x()
            minY = initPos.y()
            targetPos = self.line[1]
            maxX = targetPos.x()
            maxY = targetPos.y()
            self.current.addPoint(QPointF(maxX, minY))
            self.current.addPoint(targetPos)
            self.current.addPoint(QPointF(minX, maxY))
            self.finalise()
        elif not self.outOfPixmap(pos):
            self.current = Shape()
            self.current.addPoint(pos)
            self.line.points = [pos, pos]
            self.setHiding()
            self.drawingPolygon.emit(True)
            self.update()

    def setHiding(self, enable=True):
        self._hideBackround = self.hideBackround if enable else False

    def canCloseShape(self):
        return self.drawing() and self.current and len(self.current) > 2

    def mouseDoubleClickEvent(self, ev):
        # We need at least 4 points here, since the mousePress handler
        # adds an extra one before this handler is called.
        if self.canCloseShape() and len(self.current) > 3:
            self.current.popPoint()
            self.finalise()

    def selectShape(self, shape):
        self.deSelectShape()
        shape.selected = True
        self.selectedShape = shape
        self.setHiding()
        self.selectionChanged.emit(True)
        self.update()

    def selectShapePoint(self, point):
        """Select the first shape created which contains this point."""
        self.deSelectShape()
        if self.selectedVertex():  # A vertex is marked for selection.
            index, shape = self.hVertex, self.hShape
            shape.highlightVertex(index, shape.MOVE_VERTEX)
            self.selectShape(shape)
            return
        for shape in reversed(self.shapes):
            if self.isVisible(shape) and shape.containsPoint(point):
                self.selectShape(shape)
                self.calculateOffsets(shape, point)
                return

    def calculateOffsets(self, shape, point):
        rect = shape.boundingRect()
        x1 = rect.x() - point.x()
        y1 = rect.y() - point.y()
        x2 = (rect.x() + rect.width()) - point.x()
        y2 = (rect.y() + rect.height()) - point.y()
        self.offsets = QPointF(x1, y1), QPointF(x2, y2)

    def boundedMoveVertex(self, pos):
        index, shape = self.hVertex, self.hShape
        point = shape[index]
        if self.outOfPixmap(pos):
            pos = self.intersectionPoint(point, pos)

        shiftPos = pos - point
        shape.moveVertexBy(index, shiftPos)

        lindex = (index + 1) % 4
        rindex = (index + 3) % 4
        lshift = None
        rshift = None
        if index % 2 == 0:
            rshift = QPointF(shiftPos.x(), 0)
            lshift = QPointF(0, shiftPos.y())
        else:
            lshift = QPointF(shiftPos.x(), 0)
            rshift = QPointF(0, shiftPos.y())
        shape.moveVertexBy(rindex, rshift)
        shape.moveVertexBy(lindex, lshift)

    def boundedMoveShape(self, shape, pos):
        if self.outOfPixmap(pos):
            return False  # No need to move
        o1 = pos + self.offsets[0]
        if self.outOfPixmap(o1):
            pos -= QPointF(min(0, o1.x()), min(0, o1.y()))
        o2 = pos + self.offsets[1]
        if self.outOfPixmap(o2):
            pos += QPointF(min(0, self.pixmap.width() - o2.x()),
                           min(0, self.pixmap.height() - o2.y()))
        # The next line tracks the new position of the cursor
        # relative to the shape, but also results in making it
        # a bit "shaky" when nearing the border and allows it to
        # go outside of the shape's area for some reason. XXX
        #self.calculateOffsets(self.selectedShape, pos)
        dp = pos - self.prevPoint
        if dp:
            shape.moveBy(dp)
            self.prevPoint = pos
            return True
        return False

    def deSelectShape(self):
        if self.selectedShape:
            self.selectedShape.selected = False
            self.selectedShape = None
            self.setHiding(False)
            self.selectionChanged.emit(False)
            self.update()

    def deleteSelected(self):
        if self.selectedShape:
            shape = self.selectedShape
            self.shapes.remove(self.selectedShape)
            self.selectedShape = None
            self.update()
            return shape

    def copySelectedShape(self):
        if self.selectedShape:
            shape = self.selectedShape.copy()
            self.deSelectShape()
            self.shapes.append(shape)
            shape.selected = True
            self.selectedShape = shape
            self.boundedShiftShape(shape)
            return shape

    def boundedShiftShape(self, shape):
        # Try to move in one direction, and if it fails in another.
        # Give up if both fail.
        point = shape[0]
        offset = QPointF(2.0, 2.0)
        self.calculateOffsets(shape, point)
        self.prevPoint = point
        if not self.boundedMoveShape(shape, point - offset):
            self.boundedMoveShape(shape, point + offset)

    def paintEvent(self, event):
        if not self.pixmap:
            return super(Canvas, self).paintEvent(event)

        p = self._painter
        p.begin(self)
        p.setRenderHint(QPainter.Antialiasing)
        p.setRenderHint(QPainter.HighQualityAntialiasing)
        p.setRenderHint(QPainter.SmoothPixmapTransform)

        p.scale(self.scale, self.scale)
        p.translate(self.offsetToCenter())

        p.drawPixmap(0, 0, self.pixmap)
        Shape.scale = self.scale
        for shape in self.shapes:
            if (shape.selected or not self._hideBackround) and self.isVisible(shape):
                shape.fill = shape.selected or shape == self.hShape
                shape.paint(p)
        if self.current:
            self.current.paint(p)
            self.line.paint(p)
        if self.selectedShapeCopy:
            self.selectedShapeCopy.paint(p)

        # Paint rect
        if self.current is not None and len(self.line) == 2:
            leftTop = self.line[0]
            rightBottom = self.line[1]
            rectWidth = rightBottom.x() - leftTop.x()
            rectHeight = rightBottom.y() - leftTop.y()
            color = QColor(0, 220, 0)
            p.setPen(color)
            brush = QBrush(Qt.BDiagPattern)
            p.setBrush(brush)
            p.drawRect(leftTop.x(), leftTop.y(), rectWidth, rectHeight)

        if self.drawing() and not self.prevPoint.isNull() and not self.outOfPixmap(self.prevPoint):
            p.setPen(QColor(0, 0, 0))
            p.drawLine(self.prevPoint.x(), 0, self.prevPoint.x(), self.pixmap.height())
            p.drawLine(0, self.prevPoint.y(), self.pixmap.width(), self.prevPoint.y())

        self.setAutoFillBackground(True)
        if self.verified:
            pal = self.palette()
            pal.setColor(self.backgroundRole(), QColor(184, 239, 38, 128))
            self.setPalette(pal)
        else:
            pal = self.palette()
            pal.setColor(self.backgroundRole(), QColor(232, 232, 232, 255))
            self.setPalette(pal)

        p.end()

    def transformPos(self, point):
        """Convert from widget-logical coordinates to painter-logical coordinates."""
        return point / self.scale - self.offsetToCenter()

    def offsetToCenter(self):
        s = self.scale
        area = super(Canvas, self).size()
        w, h = self.pixmap.width() * s, self.pixmap.height() * s
        aw, ah = area.width(), area.height()
        x = (aw - w) / (2 * s) if aw > w else 0
        y = (ah - h) / (2 * s) if ah > h else 0
        return QPointF(x, y)

    def outOfPixmap(self, p):
        w, h = self.pixmap.width(), self.pixmap.height()
        return not (0 <= p.x() <= w and 0 <= p.y() <= h)

    def finalise(self):
        assert self.current
        if self.current.points[0] == self.current.points[-1]:
            self.current = None
            self.drawingPolygon.emit(False)
            self.update()
            return

        self.current.close()
        self.shapes.append(self.current)
        self.current = None
        self.setHiding(False)
        self.newShape.emit()
        self.update()

    def closeEnough(self, p1, p2):
        #d = distance(p1 - p2)
        #m = (p1-p2).manhattanLength()
        # print "d %.2f, m %d, %.2f" % (d, m, d - m)
        return distance(p1 - p2) < self.epsilon

    def intersectionPoint(self, p1, p2):
        # Cycle through each image edge in clockwise fashion,
        # and find the one intersecting the current line segment.
        # http://paulbourke.net/geometry/lineline2d/
        size = self.pixmap.size()
        points = [(0, 0),
                  (size.width(), 0),
                  (size.width(), size.height()),
                  (0, size.height())]
        x1, y1 = p1.x(), p1.y()
        x2, y2 = p2.x(), p2.y()
        d, i, (x, y) = min(self.intersectingEdges((x1, y1), (x2, y2), points))
        x3, y3 = points[i]
        x4, y4 = points[(i + 1) % 4]
        if (x, y) == (x1, y1):
            # Handle cases where previous point is on one of the edges.
            if x3 == x4:
                return QPointF(x3, min(max(0, y2), max(y3, y4)))
            else:  # y3 == y4
                return QPointF(min(max(0, x2), max(x3, x4)), y3)
        return QPointF(x, y)

    def intersectingEdges(self, x1y1, x2y2, points):
        """For each edge formed by `points', yield the intersection
        with the line segment `(x1,y1) - (x2,y2)`, if it exists.
        Also return the distance of `(x2,y2)' to the middle of the
        edge along with its index, so that the one closest can be chosen."""
        x1, y1 = x1y1
        x2, y2 = x2y2
        for i in range(4):
            x3, y3 = points[i]
            x4, y4 = points[(i + 1) % 4]
            denom = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1)
            nua = (x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)
            nub = (x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)
            if denom == 0:
                # This covers two cases:
                #   nua == nub == 0: Coincident
                #   otherwise: Parallel
                continue
            ua, ub = nua / denom, nub / denom
            if 0 <= ua <= 1 and 0 <= ub <= 1:
                x = x1 + ua * (x2 - x1)
                y = y1 + ua * (y2 - y1)
                m = QPointF((x3 + x4) / 2, (y3 + y4) / 2)
                d = distance(m - QPointF(x2, y2))
                yield d, i, (x, y)

    # These two, along with a call to adjustSize are required for the
    # scroll area.
    def sizeHint(self):
        return self.minimumSizeHint()

    def minimumSizeHint(self):
        if self.pixmap:
            return self.scale * self.pixmap.size()
        return super(Canvas, self).minimumSizeHint()

    def wheelEvent(self, ev):
        qt_version = 4 if hasattr(ev, "delta") else 5
        if qt_version == 4:
            if ev.orientation() == Qt.Vertical:
                v_delta = ev.delta()
                h_delta = 0
            else:
                h_delta = ev.delta()
                v_delta = 0
        else:
            delta = ev.angleDelta()
            h_delta = delta.x()
            v_delta = delta.y()

        mods = ev.modifiers()
        if Qt.ControlModifier == int(mods) and v_delta:
            self.zoomRequest.emit(v_delta)
        else:
            v_delta and self.scrollRequest.emit(v_delta, Qt.Vertical)
            h_delta and self.scrollRequest.emit(h_delta, Qt.Horizontal)
        ev.accept()

    def keyPressEvent(self, ev):
        key = ev.key()
        if key == Qt.Key_Escape and self.current:
            print('ESC press')
            self.current = None
            self.drawingPolygon.emit(False)
            self.update()
        elif key == Qt.Key_Return and self.canCloseShape():
            self.finalise()
        elif key == Qt.Key_Left and self.selectedShape:
            self.moveOnePixel('Left')
        elif key == Qt.Key_Right and self.selectedShape:
            self.moveOnePixel('Right')
        elif key == Qt.Key_Up and self.selectedShape:
            self.moveOnePixel('Up')
        elif key == Qt.Key_Down and self.selectedShape:
            self.moveOnePixel('Down')

    def moveOnePixel(self, direction):
        # print(self.selectedShape.points)
        if direction == 'Left' and not self.moveOutOfBound(QPointF(-1.0, 0)):
            # print("move Left one pixel")
            self.selectedShape.points[0] += QPointF(-1.0, 0)
            self.selectedShape.points[1] += QPointF(-1.0, 0)
            self.selectedShape.points[2] += QPointF(-1.0, 0)
            self.selectedShape.points[3] += QPointF(-1.0, 0)
        elif direction == 'Right' and not self.moveOutOfBound(QPointF(1.0, 0)):
            # print("move Right one pixel")
            self.selectedShape.points[0] += QPointF(1.0, 0)
            self.selectedShape.points[1] += QPointF(1.0, 0)
            self.selectedShape.points[2] += QPointF(1.0, 0)
            self.selectedShape.points[3] += QPointF(1.0, 0)
        elif direction == 'Up' and not self.moveOutOfBound(QPointF(0, -1.0)):
            # print("move Up one pixel")
            self.selectedShape.points[0] += QPointF(0, -1.0)
            self.selectedShape.points[1] += QPointF(0, -1.0)
            self.selectedShape.points[2] += QPointF(0, -1.0)
            self.selectedShape.points[3] += QPointF(0, -1.0)
        elif direction == 'Down' and not self.moveOutOfBound(QPointF(0, 1.0)):
            # print("move Down one pixel")
            self.selectedShape.points[0] += QPointF(0, 1.0)
            self.selectedShape.points[1] += QPointF(0, 1.0)
            self.selectedShape.points[2] += QPointF(0, 1.0)
            self.selectedShape.points[3] += QPointF(0, 1.0)
        self.shapeMoved.emit()
        self.repaint()

    def moveOutOfBound(self, step):
        points = [p1+p2 for p1, p2 in zip(self.selectedShape.points, [step]*4)]
        return True in map(self.outOfPixmap, points)

    def setLastLabel(self, text):
        assert text
        self.shapes[-1].label = text
        return self.shapes[-1]

    def undoLastLine(self):
        assert self.shapes
        self.current = self.shapes.pop()
        self.current.setOpen()
        self.line.points = [self.current[-1], self.current[0]]
        self.drawingPolygon.emit(True)

    def resetAllLines(self):
        assert self.shapes
        self.current = self.shapes.pop()
        self.current.setOpen()
        self.line.points = [self.current[-1], self.current[0]]
        self.drawingPolygon.emit(True)
        self.current = None
        self.drawingPolygon.emit(False)
        self.update()

    def loadPixmap(self, pixmap):
        self.pixmap = pixmap
        self.shapes = []
        self.repaint()

    def loadShapes(self, shapes):
        self.shapes = list(shapes)
        self.current = None
        self.repaint()

    def setShapeVisible(self, shape, value):
        self.visible[shape] = value
        self.repaint()

    def currentCursor(self):
        cursor = QApplication.overrideCursor()
        if cursor is not None:
            cursor = cursor.shape()
        return cursor

    def overrideCursor(self, cursor):
        self._cursor = cursor
        if self.currentCursor() is None:
            QApplication.setOverrideCursor(cursor)
        else:
            QApplication.changeOverrideCursor(cursor)

    def restoreCursor(self):
        QApplication.restoreOverrideCursor()

    def resetState(self):
        self.restoreCursor()
        self.pixmap = None
        self.update()
コード例 #25
0
class Canvas(QWidget):
    """
    Canvas inherits QWidget and is the area that displays the image and handles the drawing, resizing, and 
    moving the shapes. 
    """
    zoomRequest = pyqtSignal(int)
    scrollRequest = pyqtSignal(int, int)
    newShape = pyqtSignal()
    selectionChanged = pyqtSignal(bool)
    shapeMoved = pyqtSignal()
    drawingPolygon = pyqtSignal(bool)

    CREATE, EDIT = list(range(2))

    epsilon = 11.0

    def __init__(self, *args, **kwargs):
        """
        Create a canvas object and setup the instance variables.
        """
        super(Canvas, self).__init__(*args, **kwargs)
        # Initialise local state.
        self.mode = self.EDIT
        self.shapes = []
        self.current = None
        self.selectedShape = None  # save the selected shape here
        self.selectedShapeCopy = None
        self.lineColor = QColor(0, 0, 255)
        self.line = Shape(line_color=self.lineColor)
        self.prevPoint = QPointF()
        self.offsets = QPointF(), QPointF()
        self.scale = 1.0
        self.pixmap = QPixmap()
        self.visible = {}
        self._hideBackground = False
        self.hideBackground = False
        self.hShape = None
        self.hVertex = None
        self._painter = QPainter()
        self._cursor = CURSOR_DEFAULT
        # Menus:
        self.menus = (QMenu(), QMenu())
        # Set widget options.
        self.setMouseTracking(True)
        self.setFocusPolicy(Qt.WheelFocus)

    def enterEvent(self, ev):
        """
        This event is triggered whenever the mouse enters the canvas area.
        :param ev: The event.
        """
        self.overrideCursor(self._cursor)

    def leaveEvent(self, ev):
        """
        This event is triggered whenever the mouse leaves the canvas area.
        :param ev: The event.
        """
        self.restoreCursor()

    def focusOutEvent(self, ev):
        """
        This event is triggered whenever the focus leaves the canvas area.
        :param ev: The event.
        """
        self.restoreCursor()

    def isVisible(self, shape):
        """
        Returns True if the shape is visible.
        :param shape: The shape object
        :return: True if the shape object is visible. False otherwise.
        """
        return self.visible.get(shape, True)

    def drawing(self):
        """
        :return: True if the user is currently drawing a shape. False otherwise.
        """
        return self.mode == self.CREATE

    def editing(self):
        """
        :return: True if the user is currently editing a shape. False otherwise. 
        """
        return self.mode == self.EDIT

    def setEditing(self, value=True):
        """
        Set the canvas to editing mode when the user is resizing or moving a shape.
        :param value: bool that tells if the user is editing or not.
        """
        self.mode = self.EDIT if value else self.CREATE
        if not value:  # Create
            self.unHighlight()
            self.deSelectShape()

    def unHighlight(self):
        """
        Clear the highlighting color from the inside of a shape when the user mouses out of the shape's location.
        """
        if self.hShape:
            self.hShape.highlightClear()
        self.hVertex = self.hShape = None

    def selectedVertex(self):
        """
        :return: The vertex that the user has currently selected.
        """
        return self.hVertex is not None

    def mouseMoveEvent(self, ev):
        """
        This event is triggered whenever the user moves the mouse while drawing, resizing, or moving a box.
        :param ev: The event.
        """
        pos = self.transformPos(ev.pos())

        self.restoreCursor()

        # Polygon drawing.
        if self.drawing():
            self.overrideCursor(CURSOR_DRAW)
            if self.current:
                color = self.lineColor
                if self.outOfPixmap(pos):
                    # Don't allow the user to draw outside the pixmap.
                    # Project the point to the pixmap's edges.
                    pos = self.intersectionPoint(self.current[-1], pos)
                elif len(self.current) > 1 and self.closeEnough(
                        pos, self.current[0]):
                    # Attract line to starting point and colorise to alert the
                    # user:
                    pos = self.current[0]
                    color = self.current.line_color
                    self.overrideCursor(CURSOR_POINT)
                    self.current.highlightVertex(0, Shape.NEAR_VERTEX)
                self.line[1] = pos
                self.line.line_color = color
                self.repaint()
                self.current.highlightClear()
            return

        # Polygon copy moving.
        if Qt.RightButton & ev.buttons():
            if self.selectedShapeCopy and self.prevPoint:
                self.overrideCursor(CURSOR_MOVE)
                self.boundedMoveShape(self.selectedShapeCopy, pos)
                self.repaint()
            elif self.selectedShape:
                self.selectedShapeCopy = self.selectedShape.copy()
                self.repaint()
            return

        # Polygon/Vertex moving.
        if Qt.LeftButton & ev.buttons():
            if self.selectedVertex():
                self.boundedMoveVertex(pos)
                self.shapeMoved.emit()
                self.repaint()
            elif self.selectedShape and self.prevPoint:
                self.overrideCursor(CURSOR_MOVE)
                self.boundedMoveShape(self.selectedShape, pos)
                self.shapeMoved.emit()
                self.repaint()
            return

        # Just hovering over the canvas, 2 posibilities:
        # - Highlight shapes
        # - Highlight vertex
        # Update shape/vertex fill and tooltip value accordingly.

        self.setToolTip("")
        for shape in reversed([s for s in self.shapes if self.isVisible(s)]):
            # Look for a nearby vertex to highlight. If that fails,
            # check if we happen to be inside a shape.
            index = shape.nearestVertex(pos, self.epsilon)
            if index is not None:
                if self.selectedVertex():
                    self.hShape.highlightClear()
                self.hVertex, self.hShape = index, shape
                shape.highlightVertex(index, shape.MOVE_VERTEX)
                self.overrideCursor(CURSOR_POINT)
                self.setToolTip("Click & drag to move point")
                self.setStatusTip(self.toolTip())
                self.update()
                break
            elif shape.containsPoint(pos):
                if self.selectedVertex():
                    self.hShape.highlightClear()
                self.hVertex, self.hShape = None, shape
                self.setToolTip("Click & drag to move shape '%s'" %
                                shape.label)
                self.setStatusTip(self.toolTip())
                self.overrideCursor(CURSOR_GRAB)
                self.update()
                break
        else:  # Nothing found, clear highlights, reset state.
            if self.hShape:
                self.hShape.highlightClear()
                self.update()
            self.hVertex, self.hShape = None, None

    def mousePressEvent(self, ev):
        """
        This event is triggered whenever the user clicks to draw, move, or resize a shape.
        :param ev: The event.
        """
        pos = self.transformPos(ev.pos())

        if ev.button() == Qt.LeftButton:
            if self.drawing():
                self.handleDrawing(pos)
            else:
                self.selectShapePoint(pos)
                self.prevPoint = pos
                self.repaint()
        elif ev.button() == Qt.RightButton and self.editing():
            self.selectShapePoint(pos)
            self.prevPoint = pos
            self.repaint()

    def mouseReleaseEvent(self, ev):
        """
        This event is triggered whenever the user releases the mouse after drawing, moving, or resizing a shape.
        :param ev: The event.
        """
        if ev.button() == Qt.RightButton:
            menu = self.menus[bool(self.selectedShapeCopy)]
            self.restoreCursor()
            if not menu.exec_(self.mapToGlobal(ev.pos()))\
               and self.selectedShapeCopy:
                # Cancel the move by deleting the shadow copy.
                self.selectedShapeCopy = None
                self.repaint()
        elif ev.button() == Qt.LeftButton and self.selectedShape:
            self.overrideCursor(CURSOR_GRAB)
        elif ev.button() == Qt.LeftButton:
            pos = self.transformPos(ev.pos())
            if self.drawing():
                self.handleDrawing(pos)

    def endMove(self, copy=False):
        """
        Called whenever a user has finished moving a box.
        :param copy: bool value that tells whether the user has just duplicated a box or not.
        """
        assert self.selectedShape and self.selectedShapeCopy
        shape = self.selectedShapeCopy
        #del shape.fill_color
        #del shape.line_color
        if copy:
            self.shapes.append(shape)
            self.selectedShape.selected = False
            self.selectedShape = shape
            self.repaint()
        else:
            self.selectedShape.points = [p for p in shape.points]
        self.selectedShapeCopy = None

    def hideBackgroundShapes(self, value):
        """
        Only hide other shapes if there is a current selection.
        """
        self.hideBackground = value
        if self.selectedShape:
            self.setHiding(True)
            self.repaint()

    def handleDrawing(self, pos):
        """
        Handles the drawing of a box.
        :param pos: The position of the box.
        """
        if self.current and self.current.reachMaxPoints() is False:
            initPos = self.current[0]
            minX = initPos.x()
            minY = initPos.y()
            targetPos = self.line[1]
            maxX = targetPos.x()
            maxY = targetPos.y()
            self.current.addPoint(QPointF(maxX, minY))
            self.current.addPoint(targetPos)
            self.current.addPoint(QPointF(minX, maxY))
            self.current.addPoint(initPos)
            self.line[0] = self.current[-1]
            if self.current.isClosed():
                self.finalize()
        elif not self.outOfPixmap(pos):
            self.current = Shape()
            self.current.addPoint(pos)
            self.line.points = [pos, pos]
            self.setHiding()
            self.drawingPolygon.emit(True)
            self.update()

    def setHiding(self, enable=True):
        """
        Hide the background shapes.
        :param enable: bool value that tells whether the shapes can be hidden.
        """
        self._hideBackground = self.hideBackground if enable else False

    def canCloseShape(self):
        """
        :return: True if the shape can be closed (has 4 points).
        """
        return self.drawing() and self.current and len(self.current) > 2

    def mouseDoubleClickEvent(self, ev):
        """
        This event is called whenever the user double clicks on the canvas. 
        :param ev: The event.
        """
        if self.canCloseShape() and len(self.current) > 3:
            self.current.popPoint()
            self.finalize()

    def selectShape(self, shape):
        """
        This is called whenever the user selects a shape.
        :param shape: The shape that the user selects.
        """
        self.deSelectShape()
        shape.selected = True
        self.selectedShape = shape
        self.setHiding()
        self.selectionChanged.emit(True)
        self.update()

    def selectShapePoint(self, point):
        """
        Select the shape which contains the given point.
        :param point: The point the user clicks on.
        """
        self.deSelectShape()
        if self.selectedVertex():  # A vertex is marked for selection.
            index, shape = self.hVertex, self.hShape
            shape.highlightVertex(index, shape.MOVE_VERTEX)
            return
        for shape in reversed(self.shapes):
            if self.isVisible(shape) and shape.containsPoint(point):
                shape.selected = True
                self.selectedShape = shape
                self.calculateOffsets(shape, point)
                self.setHiding()
                self.selectionChanged.emit(True)
                return

    def calculateOffsets(self, shape, point):
        """
        Calculate the offsets between the shape and the point.
        :param shape: The shape.
        :param point: The point.
        """
        rect = shape.boundingRect()
        x1 = rect.x() - point.x()
        y1 = rect.y() - point.y()
        x2 = (rect.x() + rect.width()) - point.x()
        y2 = (rect.y() + rect.height()) - point.y()
        self.offsets = QPointF(x1, y1), QPointF(x2, y2)

    def boundedMoveVertex(self, pos):
        """
        Move the vertex of a shape.
        :param pos: The position that the user moves the vertex to.
        """
        index, shape = self.hVertex, self.hShape
        point = shape[index]
        if self.outOfPixmap(pos):
            pos = self.intersectionPoint(point, pos)

        shiftPos = pos - point
        shape.moveVertexBy(index, shiftPos)

        lindex = (index + 1) % 4
        rindex = (index + 3) % 4
        lshift = None
        rshift = None
        if index % 2 == 0:
            rshift = QPointF(shiftPos.x(), 0)
            lshift = QPointF(0, shiftPos.y())
        else:
            lshift = QPointF(shiftPos.x(), 0)
            rshift = QPointF(0, shiftPos.y())
        shape.moveVertexBy(rindex, rshift)
        shape.moveVertexBy(lindex, lshift)

    def boundedMoveShape(self, shape, pos):
        """
        Move a shape to a new position.
        :param shape: The shape that the user moves.
        :param pos: The position that the user moves the given shape to.
        """
        if self.outOfPixmap(pos):
            return False  # No need to move
        o1 = pos + self.offsets[0]
        if self.outOfPixmap(o1):
            pos -= QPointF(min(0, o1.x()), min(0, o1.y()))
        o2 = pos + self.offsets[1]
        if self.outOfPixmap(o2):
            pos += QPointF(min(0,
                               self.pixmap.width() - o2.x()),
                           min(0,
                               self.pixmap.height() - o2.y()))
        # The next line tracks the new position of the cursor
        # relative to the shape, but also results in making it
        # a bit "shaky" when nearing the border and allows it to
        # go outside of the shape's area for some reason. XXX
        #self.calculateOffsets(self.selectedShape, pos)
        dp = pos - self.prevPoint
        if dp:
            shape.moveBy(dp)
            self.prevPoint = pos
            return True
        return False

    def deSelectShape(self):
        """
        Called when the user clicks off of a selected shape.
        """
        if self.selectedShape:
            self.selectedShape.selected = False
            self.selectedShape = None
            self.setHiding(False)
            self.selectionChanged.emit(False)
            self.update()

    def deleteSelected(self):
        """
        Called whenever the user deletes the selected shape.
        :return: The shape that the user deleted.
        """
        if self.selectedShape:
            shape = self.selectedShape
            self.shapes.remove(self.selectedShape)
            self.selectedShape = None
            self.update()
            return shape

    def copySelectedShape(self):
        """
        Called whenever the user duplicates the selected shape.
        :return: The copied shape.
        """
        if self.selectedShape:
            shape = self.selectedShape.copy()
            self.deSelectShape()
            self.shapes.append(shape)
            shape.selected = True
            self.selectedShape = shape
            self.boundedShiftShape(shape)
            return shape

    def boundedShiftShape(self, shape):
        """
        This is called after the user duplicates a shape. This method automatically translate the new box so that they 
        are not entirely overlapping.
        :param shape: The duplicated shape.
        """
        # Try to move in one direction, and if it fails in another.
        # Give up if both fail.
        point = shape[0]
        offset = QPointF(2.0, 2.0)
        self.calculateOffsets(shape, point)
        self.prevPoint = point
        if not self.boundedMoveShape(shape, point - offset):
            self.boundedMoveShape(shape, point + offset)

    def paintEvent(self, event):
        """
        Updates the entire canvas, including the current bounding boxes, the vertices, and the currently selected
        box.
        :param event: The event. 
        """
        if not self.pixmap:
            return super(Canvas, self).paintEvent(event)

        p = self._painter
        p.begin(self)
        p.setRenderHint(QPainter.Antialiasing)
        p.setRenderHint(QPainter.HighQualityAntialiasing)
        p.setRenderHint(QPainter.SmoothPixmapTransform)

        p.scale(self.scale, self.scale)
        p.translate(self.offsetToCenter())

        p.drawPixmap(0, 0, self.pixmap)
        Shape.scale = self.scale
        for shape in self.shapes:
            if (shape.selected
                    or not self._hideBackground) and self.isVisible(shape):
                shape.fill = shape.selected or shape == self.hShape
                shape.paint(p)
        if self.current:
            self.current.paint(p)
            self.line.paint(p)
        if self.selectedShapeCopy:
            self.selectedShapeCopy.paint(p)

        # Paint rect
        if self.current is not None and len(self.line) == 2:
            leftTop = self.line[0]
            rightBottom = self.line[1]
            rectWidth = rightBottom.x() - leftTop.x()
            rectHeight = rightBottom.y() - leftTop.y()
            color = QColor(0, 220, 0)
            p.setPen(color)
            brush = QBrush(Qt.BDiagPattern)
            p.setBrush(brush)
            p.drawRect(leftTop.x(), leftTop.y(), rectWidth, rectHeight)

        self.setAutoFillBackground(True)

        pal = self.palette()
        pal.setColor(self.backgroundRole(), QColor(232, 232, 232, 255))
        self.setPalette(pal)

        p.end()

    def transformPos(self, point):
        """
        Convert from widget-logical coordinates to painter-logical coordinates.
        :param point: The point to convert. 
        """
        return point / self.scale - self.offsetToCenter()

    def offsetToCenter(self):
        """
        This method helps the image remain in the center during zooming.
        :return the point that is the center of the image.
        """
        s = self.scale
        area = super(Canvas, self).size()
        w, h = self.pixmap.width() * s, self.pixmap.height() * s
        aw, ah = area.width(), area.height()
        x = (aw - w) / (2 * s) if aw > w else 0
        y = (ah - h) / (2 * s) if ah > h else 0
        return QPointF(x, y)

    def outOfPixmap(self, p):
        """
        :param p: The point.
        :return: True if the point p is outside of the image's pixmap. False otherwise.
        """
        w, h = self.pixmap.width(), self.pixmap.height()
        return not (0 <= p.x() <= w and 0 <= p.y() <= h)

    def finalize(self):
        """
        Helper method that's called when a shape is finished being drawn.
        """
        assert self.current
        self.current.close()
        self.shapes.append(self.current)
        self.current = None
        self.setHiding(False)
        self.newShape.emit()
        self.update()

    def closeEnough(self, p1, p2):
        """
        Returns the distance between a point and epsilon. This helps the user select a point of shape even if they're
        a few pixels off, which improves responsiveness.
        :param p1: Point 1.
        :param p2: Point 2.
        :return: True if the distance between p1 and p2 is less that epsilon. False otherwise.
        """
        return distance(p1 - p2) < self.epsilon

    def intersectionPoint(self, p1, p2):
        """
        Cycle through each image edge in clockwise fashion,
        and find the one intersecting the current line segment.
        http://paulbourke.net/geometry/lineline2d/
        :param p1: Point 1.
        :param p2: Point 2.
        :return: The point that intersects with the current line segment.
        """
        size = self.pixmap.size()
        points = [(0, 0), (size.width(), 0), (size.width(), size.height()),
                  (0, size.height())]
        x1, y1 = p1.x(), p1.y()
        x2, y2 = p2.x(), p2.y()
        d, i, (x, y) = min(self.intersectingEdges((x1, y1), (x2, y2), points))
        x3, y3 = points[i]
        x4, y4 = points[(i + 1) % 4]
        if (x, y) == (x1, y1):
            # Handle cases where previous point is on one of the edges.
            if x3 == x4:
                return QPointF(x3, min(max(0, y2), max(y3, y4)))
            else:  # y3 == y4
                return QPointF(min(max(0, x2), max(x3, x4)), y3)
        return QPointF(x, y)

    def intersectingEdges(self, x1y1, x2y2, points):
        """
        For each edge formed by `points', yield the intersection
        with the line segment `(x1,y1) - (x2,y2)`, if it exists.
        Also return the distance of `(x2,y2)' to the middle of the
        edge along with its index, so that the one closest can be chosen.
        :param x1y1: Point 1.
        :param x2y2: Point 2.
        """
        x1, y1 = x1y1
        x2, y2 = x2y2
        for i in range(4):
            x3, y3 = points[i]
            x4, y4 = points[(i + 1) % 4]
            denom = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1)
            nua = (x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)
            nub = (x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)
            if denom == 0:
                # This covers two cases:
                #   nua == nub == 0: Coincident
                #   otherwise: Parallel
                continue
            ua, ub = nua / denom, nub / denom
            if 0 <= ua <= 1 and 0 <= ub <= 1:
                x = x1 + ua * (x2 - x1)
                y = y1 + ua * (y2 - y1)
                m = QPointF((x3 + x4) / 2, (y3 + y4) / 2)
                d = distance(m - QPointF(x2, y2))
                yield d, i, (x, y)

    # These two, along with a call to adjustSize are required for the
    # scroll area.
    def sizeHint(self):
        return self.minimumSizeHint()

    def minimumSizeHint(self):
        """
        Required for the scroll area.
        """
        if self.pixmap:
            return self.scale * self.pixmap.size()
        return super(Canvas, self).minimumSizeHint()

    def wheelEvent(self, ev):
        """
        Event is triggered whenever the mouse wheel is scrolled.
        :param ev: The event.
        """
        qt_version = 4 if hasattr(ev, "delta") else 5
        if qt_version == 4:
            if ev.orientation() == Qt.Vertical:
                v_delta = ev.delta()
                h_delta = 0
            else:
                h_delta = ev.delta()
                v_delta = 0
        else:
            delta = ev.angleDelta()
            h_delta = delta.x()
            v_delta = delta.y()

        mods = ev.modifiers()
        if Qt.ControlModifier == int(mods) and v_delta:
            self.zoomRequest.emit(v_delta)
        else:
            v_delta and self.scrollRequest.emit(v_delta, Qt.Vertical)
            h_delta and self.scrollRequest.emit(h_delta, Qt.Horizontal)
        ev.accept()

    def keyPressEvent(self, ev):
        """
        This event is triggered whenever a key is pressed.
        :param ev: The event.
        """
        key = ev.key()
        if key == Qt.Key_Escape and self.current:
            print('ESC press')
            self.current = None
            self.drawingPolygon.emit(False)
            self.update()
        elif key == Qt.Key_Return and self.canCloseShape():
            self.finalize()
        elif key == Qt.Key_Left and self.selectedShape:
            self.moveOnePixel('Left')
        elif key == Qt.Key_Right and self.selectedShape:
            self.moveOnePixel('Right')
        elif key == Qt.Key_Up and self.selectedShape:
            self.moveOnePixel('Up')
        elif key == Qt.Key_Down and self.selectedShape:
            self.moveOnePixel('Down')

    def moveOnePixel(self, direction):
        """
        This method moves the selected shape one pixel in the given direction.
        :param direction: The direction as a string [Up, Down, Left, Right].
        :return: 
        """
        # print(self.selectedShape.points)
        if direction == 'Left' and not self.moveOutOfBound(QPointF(-1.0, 0)):
            # print("move Left one pixel")
            self.selectedShape.points[0] += QPointF(-1.0, 0)
            self.selectedShape.points[1] += QPointF(-1.0, 0)
            self.selectedShape.points[2] += QPointF(-1.0, 0)
            self.selectedShape.points[3] += QPointF(-1.0, 0)
        elif direction == 'Right' and not self.moveOutOfBound(QPointF(1.0, 0)):
            # print("move Right one pixel")
            self.selectedShape.points[0] += QPointF(1.0, 0)
            self.selectedShape.points[1] += QPointF(1.0, 0)
            self.selectedShape.points[2] += QPointF(1.0, 0)
            self.selectedShape.points[3] += QPointF(1.0, 0)
        elif direction == 'Up' and not self.moveOutOfBound(QPointF(0, -1.0)):
            # print("move Up one pixel")
            self.selectedShape.points[0] += QPointF(0, -1.0)
            self.selectedShape.points[1] += QPointF(0, -1.0)
            self.selectedShape.points[2] += QPointF(0, -1.0)
            self.selectedShape.points[3] += QPointF(0, -1.0)
        elif direction == 'Down' and not self.moveOutOfBound(QPointF(0, 1.0)):
            # print("move Down one pixel")
            self.selectedShape.points[0] += QPointF(0, 1.0)
            self.selectedShape.points[1] += QPointF(0, 1.0)
            self.selectedShape.points[2] += QPointF(0, 1.0)
            self.selectedShape.points[3] += QPointF(0, 1.0)
        self.shapeMoved.emit()
        self.repaint()

    def moveOutOfBound(self, step):
        """
        Move the shape of out bounds.
        :param step: The amount of pixels to move it out of bounds.
        :return: True if any of the points are out of bounds.
        """
        points = [
            p1 + p2 for p1, p2 in zip(self.selectedShape.points, [step] * 4)
        ]
        return True in map(self.outOfPixmap, points)

    def setLastLabel(self, text):
        """
        Set the last label that was given for a shape.
        :param text: The label.
        """
        assert text
        self.shapes[-1].label = text
        return self.shapes[-1]

    def undoLastLine(self):
        assert self.shapes
        self.current = self.shapes.pop()
        self.current.setOpen()
        self.line.points = [self.current[-1], self.current[0]]
        self.drawingPolygon.emit(True)

    def resetAllLines(self):
        """
        Reset all of the lines in the last shape.
        """
        assert self.shapes
        self.current = self.shapes.pop()
        self.current.setOpen()
        self.line.points = [self.current[-1], self.current[0]]
        self.drawingPolygon.emit(True)
        self.current = None
        self.drawingPolygon.emit(False)
        self.update()

    def loadPixmap(self, pixmap):
        """
        Load the pixmap of an image. Repaint the canvas after.
        :param pixmap: The pixmap of an image.
        """
        self.pixmap = pixmap
        self.shapes = []
        self.repaint()

    def loadShapes(self, shapes):
        """
        Load the shapes onto the canvas. Repaint the canvas after loaded.
        :param shapes: The list of shape objects.
        """
        [self.shapes.append(shape) for shape in list(shapes)]
        self.current = None
        self.repaint()

    def setShapeVisible(self, shape, value):
        """
        Set a shape to be visible or hidden.
        :param shape: The shape to hide or shoe.
        :param value: Bool. If true, shape is shown. If false, shape is hidden.
        :return: 
        """
        self.visible[shape] = value
        self.repaint()

    def overrideCursor(self, cursor):
        """
        Override the cursor method of QApplication
        :param cursor: 
        """
        self.restoreCursor()
        self._cursor = cursor
        QApplication.setOverrideCursor(cursor)

    def restoreCursor(self):
        """
        Restore the override cursor of QApplication.
        """
        QApplication.restoreOverrideCursor()

    def resetState(self):
        """
        Reset the canvas.
        """
        self.restoreCursor()
        self.pixmap = None
        self.update()
コード例 #26
0
class Canvas(QWidget):
    zoomRequest = pyqtSignal(int)
    scrollRequest = pyqtSignal(int, int)
    newShape = pyqtSignal()
    selectionChanged = pyqtSignal(bool)
    shapeMoved = pyqtSignal()
    drawingPolygon = pyqtSignal(bool)

    CREATE, EDIT = list(range(2))

    epsilon = 11.0

    def __init__(self, *args, **kwargs):
        super(Canvas, self).__init__(*args, **kwargs)
        # Initialise local state.
        self.mode = self.EDIT
        self.shapes = []
        self.current = None
        self.selectedShape = None  # save the selected shape here
        self.selectedShapeCopy = None
        self.drawingLineColor = QColor(0, 0, 255)
        self.drawingRectColor = QColor(0, 0, 255)
        self.line = Shape(line_color=self.drawingLineColor)
        self.prevPoint = QPointF()
        self.offsets = QPointF(), QPointF()
        self.scale = 1.0
        self.pixmap = QPixmap()
        self.visible = {}
        self._hideBackround = False
        self.hideBackround = False
        self.hShape = None
        self.hVertex = None
        self._painter = QPainter()
        self._cursor = CURSOR_DEFAULT
        # Menus:
        self.menus = (QMenu(), QMenu())
        # Set widget options.
        self.setMouseTracking(True)
        self.setFocusPolicy(Qt.WheelFocus)
        self.verified = False
        self.radius = None

    def setDrawingColor(self, qColor):
        self.drawingLineColor = qColor
        self.drawingRectColor = qColor

    def enterEvent(self, ev):
        self.overrideCursor(self._cursor)

    def leaveEvent(self, ev):
        self.restoreCursor()

    def focusOutEvent(self, ev):
        self.restoreCursor()

    def isVisible(self, shape):
        return self.visible.get(shape, True)

    def drawing(self):
        return self.mode == self.CREATE

    def editing(self):
        return self.mode == self.EDIT

    def setEditing(self, value=True):
        self.mode = self.EDIT if value else self.CREATE
        if not value:  # Create
            self.unHighlight()
            self.deSelectShape()
        self.prevPoint = QPointF()
        self.repaint()

    def unHighlight(self):
        if self.hShape:
            self.hShape.highlightClear()
        self.hVertex = self.hShape = None

    def selectedVertex(self):
        return self.hVertex is not None

    def mouseMoveEvent(self, ev):
        """Update line with last point and current coordinates."""
        pos = self.transformPos(ev.pos())

        # Update coordinates in status bar if image is opened
        window = self.parent().window()
        if window.filePath is not None:
            self.parent().window().labelCoordinates.setText('X: %d; Y: %d' %
                                                            (pos.x(), pos.y()))

        # Polygon drawing.
        if self.drawing():
            self.overrideCursor(CURSOR_DRAW)
            if self.current:
                color = self.drawingLineColor
                if self.outOfPixmap(pos):
                    # Don't allow the user to draw outside the pixmap.
                    # Project the point to the pixmap's edges.
                    pos = self.intersectionPoint(self.current[-1], pos)
                elif len(self.current) > 1 and self.closeEnough(
                        pos, self.current[0]):
                    # Attract line to starting point and colorise to alert the
                    # user:
                    pos = self.current[0]
                    color = self.current.line_color
                    self.overrideCursor(CURSOR_POINT)
                    self.current.highlightVertex(0, Shape.NEAR_VERTEX)
                self.line[1] = pos
                self.line.line_color = color
                self.prevPoint = QPointF()
                self.current.highlightClear()
            else:
                self.prevPoint = pos
            self.repaint()
            return

        # Polygon copy moving.
        if Qt.RightButton & ev.buttons():
            if self.selectedShapeCopy and self.prevPoint:
                self.overrideCursor(CURSOR_MOVE)
                self.boundedMoveShape(self.selectedShapeCopy, pos)
                self.repaint()
            elif self.selectedShape:
                self.selectedShapeCopy = self.selectedShape.copy()
                self.repaint()
            return

        # Polygon/Vertex moving.
        if Qt.LeftButton & ev.buttons():
            if self.selectedVertex():
                radius = (((self.hShape[0].x() - self.hShape[-1].x())**2) +
                          ((self.hShape[0].y() - self.hShape[-1].y())**2))**0.5
                # tmp = self.shapes[0] - self.shapes[-1]
                # dist = (tmp.x() ** 2 + tmp.y() ** 2) ** 0.5
                # print("--------------------press vertex of rectangle---------------", pos)
                if self.hVertex != 4:
                    self.boundedMoveVertex(pos)
                else:
                    # print("self.shapes[-1])kkkkkkkkkkkkkkkkkkkk", self.shapes[-1])
                    # rotate from center
                    self.rotationVertext(0, pos, radius)
                    # self.rotationVertext(1, pos, radius)
                    # self.rotationVertext(2, pos, radius)
                    # self.rotationVertext(3, pos, radius)
                    print(
                        "--------------------rotation from center---------------",
                        pos)
                self.shapeMoved.emit()
                self.repaint()

            elif self.selectedShape and self.prevPoint:
                self.overrideCursor(CURSOR_MOVE)
                self.boundedMoveShape(self.selectedShape, pos)
                self.shapeMoved.emit()
                self.repaint()
                print(
                    "--------------------press and move rectangle---------------"
                )
            return
        self.setToolTip("Image")

        # Just hovering over the canvas, 2 posibilities:
        # - Highlight shapes
        # - Highlight vertex
        # Update shape/vertex fill and tooltip value accordingly.
        i = 0
        for shape in reversed([s for s in self.shapes if self.isVisible(s)]):
            # Look for a nearby vertex to highlight. If that fails,
            # check if we happen to be inside a shape.
            index = shape.nearestVertex(pos, self.epsilon)
            # print(len(s))
            i += 1
            if index is not None:
                if self.selectedVertex():
                    self.hShape.highlightClear()
                self.hVertex, self.hShape = index, shape
                shape.highlightVertex(index, shape.MOVE_VERTEX)
                self.overrideCursor(CURSOR_POINT)
                if self.hVertex != 4:
                    self.setToolTip("Click & drag to move point")
                else:
                    self.setToolTip("Click & drag to rotated")
                self.setStatusTip(self.toolTip())
                self.update()
                print("move a vertext of shape************** self.hVertex: ",
                      self.hVertex)

                break
            elif shape.containsPoint(pos):
                if self.selectedVertex():
                    self.hShape.highlightClear()
                self.hVertex, self.hShape = None, shape
                self.setToolTip("Click & drag to move shape '%s'" %
                                shape.label)
                self.setStatusTip(self.toolTip())
                self.overrideCursor(CURSOR_GRAB)
                self.update()
                break
        else:  # Nothing found, clear highlights, reset state.
            if self.hShape:
                self.hShape.highlightClear()
                self.update()
            self.hVertex, self.hShape = None, None
            self.overrideCursor(CURSOR_DEFAULT)

    def mousePressEvent(self, ev):
        pos = self.transformPos(ev.pos())

        if ev.button() == Qt.LeftButton:
            if self.drawing():
                self.handleDrawing(pos)
                print("-----only click left in rectangle-----")
            else:
                self.selectShapePoint(pos)
                self.prevPoint = pos
                self.repaint()
                # print("-----click left mouse to change place-----")
        elif ev.button() == Qt.RightButton and self.editing():
            self.selectShapePoint(pos)
            self.prevPoint = pos
            self.repaint()
            # print("--------------click right--------------")

    def mouseReleaseEvent(self, ev):
        if ev.button() == Qt.RightButton:
            menu = self.menus[bool(self.selectedShapeCopy)]
            self.restoreCursor()
            if not menu.exec_(self.mapToGlobal(ev.pos()))\
               and self.selectedShapeCopy:
                # Cancel the move by deleting the shadow copy.
                self.selectedShapeCopy = None
                self.repaint()
        elif ev.button() == Qt.LeftButton and self.selectedShape:
            if self.selectedVertex():
                self.overrideCursor(CURSOR_POINT)
            else:
                self.overrideCursor(CURSOR_GRAB)
        elif ev.button() == Qt.LeftButton:
            pos = self.transformPos(ev.pos())
            if self.drawing():
                self.handleDrawing(pos)

    def endMove(self, copy=False):
        assert self.selectedShape and self.selectedShapeCopy
        shape = self.selectedShapeCopy
        #del shape.fill_color
        #del shape.line_color
        if copy:
            self.shapes.append(shape)
            self.selectedShape.selected = False
            self.selectedShape = shape
            self.repaint()
        else:
            self.selectedShape.points = [p for p in shape.points]
        self.selectedShapeCopy = None

    def hideBackroundShapes(self, value):
        self.hideBackround = value
        if self.selectedShape:
            # Only hide other shapes if there is a current selection.
            # Otherwise the user will not be able to select a shape.
            self.setHiding(True)
            self.repaint()

    def handleDrawing(self, pos):
        if self.current and self.current.reachMaxPoints() is False:
            initPos = self.current[0]
            minX = initPos.x()
            minY = initPos.y()
            targetPos = self.line[1]
            maxX = targetPos.x()
            maxY = targetPos.y()
            self.current.addPoint(QPointF(maxX, minY))
            self.current.addPoint(targetPos)
            self.current.addPoint(QPointF(minX, maxY))
            self.current.addPoint(QPointF((maxX + minX) / 2,
                                          (maxY + minY) / 2))
            # self.current_.addPoint(QPointF((maxX + minX) / 2 , (maxY + minY) / 2))
            # print(self.current_.points)
            self.finalise()
        elif not self.outOfPixmap(pos):
            self.current = Shape()
            self.current.addPoint(pos)
            self.line.points = [pos, pos]
            self.setHiding()
            self.drawingPolygon.emit(True)
            self.update()

    def setHiding(self, enable=True):
        self._hideBackround = self.hideBackround if enable else False

    def canCloseShape(self):
        return self.drawing() and self.current and len(self.current) > 2

    def mouseDoubleClickEvent(self, ev):
        # We need at least 4 points here, since the mousePress handler
        # adds an extra one before this handler is called.
        if self.canCloseShape() and len(self.current) > 3:
            self.current.popPoint()
            self.finalise()

    def selectShape(self, shape):
        self.deSelectShape()
        shape.selected = True
        self.selectedShape = shape
        self.setHiding()
        self.selectionChanged.emit(True)
        self.update()

    def selectShapePoint(self, point):
        """Select the first shape created which contains this point."""
        self.deSelectShape()
        if self.selectedVertex():  # A vertex is marked for selection.
            index, shape = self.hVertex, self.hShape
            shape.highlightVertex(index, shape.MOVE_VERTEX)
            self.selectShape(shape)
            return
        for shape in reversed(self.shapes):
            if self.isVisible(shape) and shape.containsPoint(point):
                self.selectShape(shape)
                self.calculateOffsets(shape, point)
                return

    def calculateOffsets(self, shape, point):
        rect = shape.boundingRect()
        x1 = rect.x() - point.x()
        y1 = rect.y() - point.y()
        x2 = (rect.x() + rect.width()) - point.x()
        y2 = (rect.y() + rect.height()) - point.y()
        self.offsets = QPointF(x1, y1), QPointF(x2, y2)

    def boundedMoveVertex(self, pos):
        index, shape = self.hVertex, self.hShape
        point = shape[index]
        if self.outOfPixmap(pos):
            pos = self.intersectionPoint(point, pos)

        shiftPos = pos - point
        # print("point :",point,"shiftPos: ",shiftPos,"pos: ",pos,"index = ",index, "shape = ", len(shape))
        shape.moveVertexBy(index, shiftPos)
        # shape.moveVertexBy(-1, )

        lindex = (index + 1) % 4
        rindex = (index + 3) % 4
        lshift = None
        rshift = None
        if index % 2 == 0:
            rshift = QPointF(shiftPos.x(), 0)
            lshift = QPointF(0, shiftPos.y())
        else:
            lshift = QPointF(shiftPos.x(), 0)
            rshift = QPointF(0, shiftPos.y())
        shape.moveVertexBy(rindex, rshift)
        shape.moveVertexBy(lindex, lshift)

    def rotationVertext(self, pos, radius, i):
        # tmp = self.points[0:4]
        # vector = []
        # dist = []
        # for index, vertext in enumerate(self.points[0:4]):
        #     # self.points[index] = pos-vertext
        #     vector.append(pos - vertext)
        #     dist.append((vector[index].x() ** 2 + vector[index].y() ** 2) ** 0.5)

        shape = self.hShape
        center = shape[-1]
        vector = pos - center
        dist = (vector.x()**2 + vector.y()**2)**0.5
        # follows mouse around
        i = 0
        if dist > 0:
            scalar = radius / dist
            shape[i].setX(int(round(center.x() + vector.x() * scalar)))
            shape[i].setY(int(round(center.y() + vector.y() * scalar)))

        center = shape[-1]
        arage = (shape[0] + shape[1]) / 2
        vector1 = center - arage
        vector2 = center - pos
        tu = vector1.x() * vector2.x() + vector1.y() * vector2.y()
        mau = ((vector1.x()**2 + vector1.y()**2) *
               (vector2.x()**2 + vector2.y()**2))**0.5
        # return np.degrees(np.arccos(tu/mau))

    def boundedMoveShape(self, shape, pos):
        if self.outOfPixmap(pos):
            return False  # No need to move
        o1 = pos + self.offsets[0]
        if self.outOfPixmap(o1):
            pos -= QPointF(min(0, o1.x()), min(0, o1.y()))
        o2 = pos + self.offsets[1]
        if self.outOfPixmap(o2):
            pos += QPointF(min(0,
                               self.pixmap.width() - o2.x()),
                           min(0,
                               self.pixmap.height() - o2.y()))
        # The next line tracks the new position of the cursor
        # relative to the shape, but also results in making it
        # a bit "shaky" when nearing the border and allows it to
        # go outside of the shape's area for some reason. XXX
        #self.calculateOffsets(self.selectedShape, pos)
        dp = pos - self.prevPoint
        if dp:
            shape.moveBy(dp)
            self.prevPoint = pos
            return True
        return False

    def deSelectShape(self):
        if self.selectedShape:
            self.selectedShape.selected = False
            self.selectedShape = None
            self.setHiding(False)
            self.selectionChanged.emit(False)
            self.update()

    def deleteSelected(self):
        if self.selectedShape:
            shape = self.selectedShape
            self.shapes.remove(self.selectedShape)
            self.selectedShape = None
            self.update()
            return shape

    def copySelectedShape(self):
        if self.selectedShape:
            shape = self.selectedShape.copy()
            self.deSelectShape()
            self.shapes.append(shape)
            shape.selected = True
            self.selectedShape = shape
            self.boundedShiftShape(shape)
            return shape

    def boundedShiftShape(self, shape):
        # Try to move in one direction, and if it fails in another.
        # Give up if both fail.
        point = shape[0]
        offset = QPointF(2.0, 2.0)
        self.calculateOffsets(shape, point)
        self.prevPoint = point
        if not self.boundedMoveShape(shape, point - offset):
            self.boundedMoveShape(shape, point + offset)

    def paintEvent(self, event):
        if not self.pixmap:
            return super(Canvas, self).paintEvent(event)
        # print(event,"-------------------")
        p = self._painter
        p.begin(self)
        p.setRenderHint(QPainter.Antialiasing)
        p.setRenderHint(QPainter.HighQualityAntialiasing)
        p.setRenderHint(QPainter.SmoothPixmapTransform)

        p.scale(self.scale, self.scale)
        p.translate(self.offsetToCenter())

        p.drawPixmap(0, 0, self.pixmap)
        Shape.scale = self.scale
        for shape in self.shapes:
            if (shape.selected
                    or not self._hideBackround) and self.isVisible(shape):
                shape.fill = shape.selected or shape == self.hShape
                shape.paint(p)
        if self.current:
            self.current.paint(p)
            self.line.paint(p)
        if self.selectedShapeCopy:
            self.selectedShapeCopy.paint(p)

        # Paint rect
        if self.current is not None and len(self.line) == 2:
            leftTop = self.line[0]
            rightBottom = self.line[1]
            rectWidth = rightBottom.x() - leftTop.x()
            rectHeight = rightBottom.y() - leftTop.y()
            p.setPen(self.drawingRectColor)
            brush = QBrush(Qt.BDiagPattern)
            p.setBrush(brush)
            p.drawRect(leftTop.x(), leftTop.y(), rectWidth, rectHeight)

        if self.drawing() and not self.prevPoint.isNull(
        ) and not self.outOfPixmap(self.prevPoint):
            p.setPen(QColor(0, 0, 0))
            p.drawLine(self.prevPoint.x(), 0, self.prevPoint.x(),
                       self.pixmap.height())
            p.drawLine(0, self.prevPoint.y(), self.pixmap.width(),
                       self.prevPoint.y())

        self.setAutoFillBackground(True)
        if self.verified:
            pal = self.palette()
            pal.setColor(self.backgroundRole(), QColor(184, 239, 38, 128))
            self.setPalette(pal)
        else:
            pal = self.palette()
            pal.setColor(self.backgroundRole(), QColor(232, 232, 232, 255))
            self.setPalette(pal)

        p.end()

    def transformPos(self, point):
        """Convert from widget-logical coordinates to painter-logical coordinates."""
        return point / self.scale - self.offsetToCenter()

    def offsetToCenter(self):
        s = self.scale
        area = super(Canvas, self).size()
        w, h = self.pixmap.width() * s, self.pixmap.height() * s
        aw, ah = area.width(), area.height()
        x = (aw - w) / (2 * s) if aw > w else 0
        y = (ah - h) / (2 * s) if ah > h else 0
        return QPointF(x, y)

    def outOfPixmap(self, p):
        w, h = self.pixmap.width(), self.pixmap.height()
        return not (0 <= p.x() <= w and 0 <= p.y() <= h)

    def finalise(self):
        assert self.current
        if self.current.points[0] == self.current.points[-1]:
            self.current = None
            self.drawingPolygon.emit(False)
            self.update()
            return

        self.current.close()
        self.shapes.append(self.current)
        self.current = None
        self.setHiding(False)
        self.newShape.emit()
        self.update()

    def closeEnough(self, p1, p2):
        #d = distance(p1 - p2)
        #m = (p1-p2).manhattanLength()
        # print "d %.2f, m %d, %.2f" % (d, m, d - m)
        return distance(p1 - p2) < self.epsilon

    def intersectionPoint(self, p1, p2):
        # Cycle through each image edge in clockwise fashion,
        # and find the one intersecting the current line segment.
        # http://paulbourke.net/geometry/lineline2d/
        size = self.pixmap.size()
        points = [(0, 0), (size.width(), 0), (size.width(), size.height()),
                  (0, size.height())]
        x1, y1 = p1.x(), p1.y()
        x2, y2 = p2.x(), p2.y()
        d, i, (x, y) = min(self.intersectingEdges((x1, y1), (x2, y2), points))
        x3, y3 = points[i]
        x4, y4 = points[(i + 1) % 4]
        if (x, y) == (x1, y1):
            # Handle cases where previous point is on one of the edges.
            if x3 == x4:
                return QPointF(x3, min(max(0, y2), max(y3, y4)))
            else:  # y3 == y4
                return QPointF(min(max(0, x2), max(x3, x4)), y3)
        return QPointF(x, y)

    def intersectingEdges(self, x1y1, x2y2, points):
        """For each edge formed by `points', yield the intersection
        with the line segment `(x1,y1) - (x2,y2)`, if it exists.
        Also return the distance of `(x2,y2)' to the middle of the
        edge along with its index, so that the one closest can be chosen."""
        x1, y1 = x1y1
        x2, y2 = x2y2
        for i in range(4):
            x3, y3 = points[i]
            x4, y4 = points[(i + 1) % 4]
            denom = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1)
            nua = (x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)
            nub = (x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)
            if denom == 0:
                # This covers two cases:
                #   nua == nub == 0: Coincident
                #   otherwise: Parallel
                continue
            ua, ub = nua / denom, nub / denom
            if 0 <= ua <= 1 and 0 <= ub <= 1:
                x = x1 + ua * (x2 - x1)
                y = y1 + ua * (y2 - y1)
                m = QPointF((x3 + x4) / 2, (y3 + y4) / 2)
                d = distance(m - QPointF(x2, y2))
                yield d, i, (x, y)

    # These two, along with a call to adjustSize are required for the
    # scroll area.
    def sizeHint(self):
        return self.minimumSizeHint()

    def minimumSizeHint(self):
        if self.pixmap:
            return self.scale * self.pixmap.size()
        return super(Canvas, self).minimumSizeHint()

    def wheelEvent(self, ev):
        qt_version = 4 if hasattr(ev, "delta") else 5
        if qt_version == 4:
            if ev.orientation() == Qt.Vertical:
                v_delta = ev.delta()
                h_delta = 0
            else:
                h_delta = ev.delta()
                v_delta = 0
        else:
            delta = ev.angleDelta()
            h_delta = delta.x()
            v_delta = delta.y()

        mods = ev.modifiers()
        if Qt.ControlModifier == int(mods) and v_delta:
            self.zoomRequest.emit(v_delta)
        else:
            v_delta and self.scrollRequest.emit(v_delta, Qt.Vertical)
            h_delta and self.scrollRequest.emit(h_delta, Qt.Horizontal)
        ev.accept()

    def keyPressEvent(self, ev):
        key = ev.key()
        if key == Qt.Key_Escape and self.current:
            print('ESC press')
            self.current = None
            self.drawingPolygon.emit(False)
            self.update()
        elif key == Qt.Key_Return and self.canCloseShape():
            self.finalise()
        elif key == Qt.Key_Left and self.selectedShape:
            self.moveOnePixel('Left')
        elif key == Qt.Key_Right and self.selectedShape:
            self.moveOnePixel('Right')
        elif key == Qt.Key_Up and self.selectedShape:
            self.moveOnePixel('Up')
        elif key == Qt.Key_Down and self.selectedShape:
            self.moveOnePixel('Down')

    def moveOnePixel(self, direction):
        # print(self.selectedShape.points)
        if direction == 'Left' and not self.moveOutOfBound(QPointF(-1.0, 0)):
            # print("move Left one pixel")
            self.selectedShape.points[0] += QPointF(-1.0, 0)
            self.selectedShape.points[1] += QPointF(-1.0, 0)
            self.selectedShape.points[2] += QPointF(-1.0, 0)
            self.selectedShape.points[3] += QPointF(-1.0, 0)
        elif direction == 'Right' and not self.moveOutOfBound(QPointF(1.0, 0)):
            # print("move Right one pixel")
            self.selectedShape.points[0] += QPointF(1.0, 0)
            self.selectedShape.points[1] += QPointF(1.0, 0)
            self.selectedShape.points[2] += QPointF(1.0, 0)
            self.selectedShape.points[3] += QPointF(1.0, 0)
        elif direction == 'Up' and not self.moveOutOfBound(QPointF(0, -1.0)):
            # print("move Up one pixel")
            self.selectedShape.points[0] += QPointF(0, -1.0)
            self.selectedShape.points[1] += QPointF(0, -1.0)
            self.selectedShape.points[2] += QPointF(0, -1.0)
            self.selectedShape.points[3] += QPointF(0, -1.0)
        elif direction == 'Down' and not self.moveOutOfBound(QPointF(0, 1.0)):
            # print("move Down one pixel")
            self.selectedShape.points[0] += QPointF(0, 1.0)
            self.selectedShape.points[1] += QPointF(0, 1.0)
            self.selectedShape.points[2] += QPointF(0, 1.0)
            self.selectedShape.points[3] += QPointF(0, 1.0)
        self.shapeMoved.emit()
        self.repaint()

    def moveOutOfBound(self, step):
        points = [
            p1 + p2 for p1, p2 in zip(self.selectedShape.points, [step] * 4)
        ]
        return True in map(self.outOfPixmap, points)

    def setLastLabel(self, text, line_color=None, fill_color=None):
        assert text
        self.shapes[-1].label = text
        if line_color:
            self.shapes[-1].line_color = line_color

        if fill_color:
            self.shapes[-1].fill_color = fill_color

        return self.shapes[-1]

    def setLastLabel_2(self,
                       text,
                       line_color=None,
                       fill_color=None,
                       kind="Horizontal"):  # kind = vertical or horizontal
        assert text
        self.shapes[-1].label = text
        self.shapes[-1].kind = kind

        if line_color:
            self.shapes[-1].line_color = line_color

        if fill_color:
            self.shapes[-1].fill_color = fill_color

        # if kind:
        #     self.shapes[-1].kind = kind

        return self.shapes[-1]

    def undoLastLine(self):
        assert self.shapes
        self.current = self.shapes.pop()
        self.current.setOpen()
        self.line.points = [self.current[-1], self.current[0]]
        self.drawingPolygon.emit(True)

    def resetAllLines(self):
        assert self.shapes
        self.current = self.shapes.pop()
        self.current.setOpen()
        self.line.points = [self.current[-1], self.current[0]]
        self.drawingPolygon.emit(True)
        self.current = None
        self.drawingPolygon.emit(False)
        self.update()

    def loadPixmap(self, pixmap):
        self.pixmap = pixmap
        self.shapes = []
        self.repaint()

    def loadShapes(self, shapes):
        self.shapes = list(shapes)
        self.current = None
        self.repaint()

    def setShapeVisible(self, shape, value):
        self.visible[shape] = value
        self.repaint()

    def currentCursor(self):
        cursor = QApplication.overrideCursor()
        if cursor is not None:
            cursor = cursor.shape()
        return cursor

    def overrideCursor(self, cursor):
        self._cursor = cursor
        if self.currentCursor() is None:
            QApplication.setOverrideCursor(cursor)
        else:
            QApplication.changeOverrideCursor(cursor)

    def restoreCursor(self):
        QApplication.restoreOverrideCursor()

    def resetState(self):
        self.restoreCursor()
        self.pixmap = None
        self.update()
コード例 #27
0
def randomTable(num):
    # Create a black image
    img = np.zeros((512, 512, 3), np.uint8)
    img.fill(255)
    left = random.randint(20, 50)
    top = random.randint(50, 50)
    width = random.randint(100, 150)
    height = random.randint(30, 70)

    row = 4  #random.randint(1, 4)
    column = 3  # random.randint(1, 3)

    if row == 1 and column == 1:
        width, height = width * 2, height * 2

    rectShapes = []

    # print("image:{}, row num={}, column num ={} ".format(num, row, column))

    for r in range(0, row):
        for c in range(0, column):
            start_point = (left + c * width, top + r * height)
            text_start_point = (start_point[0] + int(width * 0.1),
                                start_point[1] + int(height * 0.1))
            end_point = (left + (c + 1) * width, top + (r + 1) * height)

            # PIL image转换成array
            img = Image.fromarray(np.uint8(img))
            draw = ImageDraw.Draw(img)
            word_height = int(height * 0.6)
            FONT = ImageFont.truetype('fonts/simhei.ttf', word_height)
            # 填字
            draw.text(text_start_point,
                      randomText(int(width / word_height - 1)),
                      BLACK,
                      font=FONT)
            # array转换成image
            img = np.asarray(img)
            # 画框
            thickness = [1, 2]  # 表格线粗细随机
            cv.rectangle(img, start_point, end_point, BLACK,
                         random.choice(thickness))

            points = [start_point, end_point]
            shape = Shape(label='rect')
            for x, y in points:
                shape.addPoint(Point(x, y))
            shape.close()
            rectShapes.append(shape)

    imagePath = 'VOC2007/JPEGImages/'
    xmlPath = 'VOC2007/Annotations/'
    fileName = imagePath + num + IMAGE_EXT
    xmlName = xmlPath + num + XML_EXT
    # print('fileName=', fileName)

    noise_percetage = random.uniform(0, .25)
    # print('noise_percetage=', noise_percetage)

    salt_noise_image = SaltAndPepper(img, noise_percetage)  # 添加的椒盐噪声

    gaussian_noise_image = addGaussianNoise(salt_noise_image)  # 添加的高斯噪声

    cv.imwrite(fileName, gaussian_noise_image)

    savePascalVocFormat(xmlName, rectShapes, imagePath, img)
コード例 #28
0
ファイル: canvas.py プロジェクト: gachiemchiep/tmp
class Canvas(QWidget):
    zoomRequest = pyqtSignal(int)
    scrollRequest = pyqtSignal(int, int)
    newShape = pyqtSignal()
    selectionChanged = pyqtSignal(bool)
    shapeMoved = pyqtSignal()
    drawingPolygon = pyqtSignal(bool)

    CREATE, EDIT = list(range(2))

    epsilon = 11.0

    def __init__(self, *args, **kwargs):
        super(Canvas, self).__init__(*args, **kwargs)
        # Initialise local state.
        self.mode = self.EDIT
        self.shapes = []
        self.current = None
        self.selectedShape = None  # save the selected shape here
        self.selectedShapeCopy = None
        self.drawingLineColor = QColor(0, 0, 255)
        self.drawingRectColor = QColor(0, 0, 255)
        self.line = Shape(line_color=self.drawingLineColor)
        self.prevPoint = QPointF()
        self.offsets = QPointF(), QPointF()
        self.scale = 1.0
        self.pixmap = QPixmap()
        self.visible = {}
        self._hideBackround = False
        self.hideBackround = False
        self.hShape = None
        self.hVertex = None
        self._painter = QPainter()
        self._cursor = CURSOR_DEFAULT
        # Menus:
        self.menus = (QMenu(), QMenu())
        # Set widget options.
        self.setMouseTracking(True)
        self.setFocusPolicy(Qt.WheelFocus)
        self.verified = False
        self.drawSquare = False

    def setDrawingColor(self, qColor):
        self.drawingLineColor = qColor
        self.drawingRectColor = qColor

    def enterEvent(self, ev):
        self.overrideCursor(self._cursor)

    def leaveEvent(self, ev):
        self.restoreCursor()

    def focusOutEvent(self, ev):
        self.restoreCursor()

    def isVisible(self, shape):
        return self.visible.get(shape, True)

    def drawing(self):
        return self.mode == self.CREATE

    def editing(self):
        return self.mode == self.EDIT

    def setEditing(self, value=True):
        self.mode = self.EDIT if value else self.CREATE
        if not value:  # Create
            self.unHighlight()
            self.deSelectShape()
        self.prevPoint = QPointF()
        self.repaint()

    def unHighlight(self):
        if self.hShape:
            self.hShape.highlightClear()
        self.hVertex = self.hShape = None

    def selectedVertex(self):
        return self.hVertex is not None

    def mouseMoveEvent(self, ev):
        """Update line with last point and current coordinates."""
        pos = self.transformPos(ev.pos())

        # Update coordinates in status bar if image is opened
        window = self.parent().window()
        if window.filePath is not None:
            self.parent().window().labelCoordinates.setText('X: %d; Y: %d' %
                                                            (pos.x(), pos.y()))

        # Polygon drawing.
        if self.drawing():
            self.overrideCursor(CURSOR_DRAW)
            if self.current:
                # Display annotation width and height while drawing
                currentWidth = abs(self.current[0].x() - pos.x())
                currentHeight = abs(self.current[0].y() - pos.y())
                self.parent().window().labelCoordinates.setText(
                    'Width: %d, Height: %d / X: %d; Y: %d' %
                    (currentWidth, currentHeight, pos.x(), pos.y()))

                color = self.drawingLineColor
                if self.outOfPixmap(pos):
                    # Don't allow the user to draw outside the pixmap.
                    # Clip the coordinates to 0 or max,
                    # if they are outside the range [0, max]
                    size = self.pixmap.size()
                    clipped_x = min(max(0, pos.x()), size.width())
                    clipped_y = min(max(0, pos.y()), size.height())
                    pos = QPointF(clipped_x, clipped_y)
                elif len(self.current) > 1 and self.closeEnough(
                        pos, self.current[0]):
                    # Attract line to starting point and colorise to alert the
                    # user:
                    pos = self.current[0]
                    color = self.current.line_color
                    self.overrideCursor(CURSOR_POINT)
                    self.current.highlightVertex(0, Shape.NEAR_VERTEX)

                if self.drawSquare:
                    initPos = self.current[0]
                    minX = initPos.x()
                    minY = initPos.y()
                    min_size = min(abs(pos.x() - minX), abs(pos.y() - minY))
                    directionX = -1 if pos.x() - minX < 0 else 1
                    directionY = -1 if pos.y() - minY < 0 else 1
                    self.line[1] = QPointF(minX + directionX * min_size,
                                           minY + directionY * min_size)
                else:
                    self.line[1] = pos

                self.line.line_color = color
                self.prevPoint = QPointF()
                self.current.highlightClear()
            else:
                self.prevPoint = pos
            self.repaint()
            return

        # Polygon copy moving.
        if Qt.RightButton & ev.buttons():
            if self.selectedShapeCopy and self.prevPoint:
                self.overrideCursor(CURSOR_MOVE)
                self.boundedMoveShape(self.selectedShapeCopy, pos)
                self.repaint()
            elif self.selectedShape:
                self.selectedShapeCopy = self.selectedShape.copy()
                self.repaint()
            return

        # Polygon/Vertex moving.
        if Qt.LeftButton & ev.buttons():
            if self.selectedVertex():
                self.boundedMoveVertex(pos)
                self.shapeMoved.emit()
                self.repaint()
            elif self.selectedShape and self.prevPoint:
                self.overrideCursor(CURSOR_MOVE)
                self.boundedMoveShape(self.selectedShape, pos)
                self.shapeMoved.emit()
                self.repaint()
            return

        # Just hovering over the canvas, 2 posibilities:
        # - Highlight shapes
        # - Highlight vertex
        # Update shape/vertex fill and tooltip value accordingly.
        self.setToolTip("Image")
        for shape in reversed([s for s in self.shapes if self.isVisible(s)]):
            # Look for a nearby vertex to highlight. If that fails,
            # check if we happen to be inside a shape.
            index = shape.nearestVertex(pos, self.epsilon)
            if index is not None:
                if self.selectedVertex():
                    self.hShape.highlightClear()
                self.hVertex, self.hShape = index, shape
                shape.highlightVertex(index, shape.MOVE_VERTEX)
                self.overrideCursor(CURSOR_POINT)
                self.setToolTip("Click & drag to move point")
                self.setStatusTip(self.toolTip())
                self.update()
                break
            elif shape.containsPoint(pos):
                if self.selectedVertex():
                    self.hShape.highlightClear()
                self.hVertex, self.hShape = None, shape
                self.setToolTip("Click & drag to move shape '%s'" %
                                shape.label)
                self.setStatusTip(self.toolTip())
                self.overrideCursor(CURSOR_GRAB)
                self.update()
                break
        else:  # Nothing found, clear highlights, reset state.
            if self.hShape:
                self.hShape.highlightClear()
                self.update()
            self.hVertex, self.hShape = None, None
            self.overrideCursor(CURSOR_DEFAULT)

    def mousePressEvent(self, ev):
        pos = self.transformPos(ev.pos())

        if ev.button() == Qt.LeftButton:
            if self.drawing():
                self.handleDrawing(pos)
            else:
                self.selectShapePoint(pos)
                self.prevPoint = pos
                self.repaint()
        elif ev.button() == Qt.RightButton and self.editing():
            self.selectShapePoint(pos)
            self.prevPoint = pos
            self.repaint()

    def mouseReleaseEvent(self, ev):
        if ev.button() == Qt.RightButton:
            menu = self.menus[bool(self.selectedShapeCopy)]
            self.restoreCursor()
            if not menu.exec_(self.mapToGlobal(ev.pos()))\
               and self.selectedShapeCopy:
                # Cancel the move by deleting the shadow copy.
                self.selectedShapeCopy = None
                self.repaint()
        elif ev.button() == Qt.LeftButton and self.selectedShape:
            if self.selectedVertex():
                self.overrideCursor(CURSOR_POINT)
            else:
                self.overrideCursor(CURSOR_GRAB)
        elif ev.button() == Qt.LeftButton:
            pos = self.transformPos(ev.pos())
            if self.drawing():
                self.handleDrawing(pos)

    def endMove(self, copy=False):
        assert self.selectedShape and self.selectedShapeCopy
        shape = self.selectedShapeCopy
        #del shape.fill_color
        #del shape.line_color
        if copy:
            self.shapes.append(shape)
            self.selectedShape.selected = False
            self.selectedShape = shape
            self.repaint()
        else:
            self.selectedShape.points = [p for p in shape.points]
        self.selectedShapeCopy = None

    def hideBackroundShapes(self, value):
        self.hideBackround = value
        if self.selectedShape:
            # Only hide other shapes if there is a current selection.
            # Otherwise the user will not be able to select a shape.
            self.setHiding(True)
            self.repaint()

    def handleDrawing(self, pos):
        if self.current and self.current.reachMaxPoints() is False:
            initPos = self.current[0]
            minX = initPos.x()
            minY = initPos.y()
            targetPos = self.line[1]
            maxX = targetPos.x()
            maxY = targetPos.y()
            self.current.addPoint(QPointF(maxX, minY))
            self.current.addPoint(targetPos)
            self.current.addPoint(QPointF(minX, maxY))
            self.finalise()
        elif not self.outOfPixmap(pos):
            self.current = Shape()
            self.current.addPoint(pos)
            self.line.points = [pos, pos]
            self.setHiding()
            self.drawingPolygon.emit(True)
            self.update()

    def setHiding(self, enable=True):
        self._hideBackround = self.hideBackround if enable else False

    def canCloseShape(self):
        return self.drawing() and self.current and len(self.current) > 2

    def mouseDoubleClickEvent(self, ev):
        # We need at least 4 points here, since the mousePress handler
        # adds an extra one before this handler is called.
        if self.canCloseShape() and len(self.current) > 3:
            self.current.popPoint()
            self.finalise()

    def selectShape(self, shape):
        self.deSelectShape()
        shape.selected = True
        self.selectedShape = shape
        self.setHiding()
        self.selectionChanged.emit(True)
        self.update()

    def selectShapePoint(self, point):
        """Select the first shape created which contains this point."""
        self.deSelectShape()
        if self.selectedVertex():  # A vertex is marked for selection.
            index, shape = self.hVertex, self.hShape
            shape.highlightVertex(index, shape.MOVE_VERTEX)
            self.selectShape(shape)
            return
        for shape in reversed(self.shapes):
            if self.isVisible(shape) and shape.containsPoint(point):
                self.selectShape(shape)
                self.calculateOffsets(shape, point)
                return

    def calculateOffsets(self, shape, point):
        rect = shape.boundingRect()
        x1 = rect.x() - point.x()
        y1 = rect.y() - point.y()
        x2 = (rect.x() + rect.width()) - point.x()
        y2 = (rect.y() + rect.height()) - point.y()
        self.offsets = QPointF(x1, y1), QPointF(x2, y2)

    def snapPointToCanvas(self, x, y):
        """
        Moves a point x,y to within the boundaries of the canvas.
        :return: (x,y,snapped) where snapped is True if x or y were changed, False if not.
        """
        if x < 0 or x > self.pixmap.width() or y < 0 or y > self.pixmap.height(
        ):
            x = max(x, 0)
            y = max(y, 0)
            x = min(x, self.pixmap.width())
            y = min(y, self.pixmap.height())
            return x, y, True

        return x, y, False

    def boundedMoveVertex(self, pos):
        index, shape = self.hVertex, self.hShape
        point = shape[index]
        if self.outOfPixmap(pos):
            size = self.pixmap.size()
            clipped_x = min(max(0, pos.x()), size.width())
            clipped_y = min(max(0, pos.y()), size.height())
            pos = QPointF(clipped_x, clipped_y)

        if self.drawSquare:
            opposite_point_index = (index + 2) % 4
            opposite_point = shape[opposite_point_index]

            min_size = min(abs(pos.x() - opposite_point.x()),
                           abs(pos.y() - opposite_point.y()))
            directionX = -1 if pos.x() - opposite_point.x() < 0 else 1
            directionY = -1 if pos.y() - opposite_point.y() < 0 else 1
            shiftPos = QPointF(
                opposite_point.x() + directionX * min_size - point.x(),
                opposite_point.y() + directionY * min_size - point.y())
        else:
            shiftPos = pos - point

        shape.moveVertexBy(index, shiftPos)

        lindex = (index + 1) % 4
        rindex = (index + 3) % 4
        lshift = None
        rshift = None
        if index % 2 == 0:
            rshift = QPointF(shiftPos.x(), 0)
            lshift = QPointF(0, shiftPos.y())
        else:
            lshift = QPointF(shiftPos.x(), 0)
            rshift = QPointF(0, shiftPos.y())
        shape.moveVertexBy(rindex, rshift)
        shape.moveVertexBy(lindex, lshift)

    def boundedMoveShape(self, shape, pos):
        if self.outOfPixmap(pos):
            return False  # No need to move
        o1 = pos + self.offsets[0]
        if self.outOfPixmap(o1):
            pos -= QPointF(min(0, o1.x()), min(0, o1.y()))
        o2 = pos + self.offsets[1]
        if self.outOfPixmap(o2):
            pos += QPointF(min(0,
                               self.pixmap.width() - o2.x()),
                           min(0,
                               self.pixmap.height() - o2.y()))
        # The next line tracks the new position of the cursor
        # relative to the shape, but also results in making it
        # a bit "shaky" when nearing the border and allows it to
        # go outside of the shape's area for some reason. XXX
        #self.calculateOffsets(self.selectedShape, pos)
        dp = pos - self.prevPoint
        if dp:
            shape.moveBy(dp)
            self.prevPoint = pos
            return True
        return False

    def deSelectShape(self):
        if self.selectedShape:
            self.selectedShape.selected = False
            self.selectedShape = None
            self.setHiding(False)
            self.selectionChanged.emit(False)
            self.update()

    def deleteSelected(self):
        if self.selectedShape:
            shape = self.selectedShape
            self.shapes.remove(self.selectedShape)
            self.selectedShape = None
            self.update()
            return shape

    def copySelectedShape(self):
        if self.selectedShape:
            shape = self.selectedShape.copy()
            self.deSelectShape()
            self.shapes.append(shape)
            shape.selected = True
            self.selectedShape = shape
            self.boundedShiftShape(shape)
            return shape

    def boundedShiftShape(self, shape):
        # Try to move in one direction, and if it fails in another.
        # Give up if both fail.
        point = shape[0]
        offset = QPointF(2.0, 2.0)
        self.calculateOffsets(shape, point)
        self.prevPoint = point
        if not self.boundedMoveShape(shape, point - offset):
            self.boundedMoveShape(shape, point + offset)

    def paintEvent(self, event):
        if not self.pixmap:
            return super(Canvas, self).paintEvent(event)

        p = self._painter
        p.begin(self)
        p.setRenderHint(QPainter.Antialiasing)
        p.setRenderHint(QPainter.HighQualityAntialiasing)
        p.setRenderHint(QPainter.SmoothPixmapTransform)

        p.scale(self.scale, self.scale)
        p.translate(self.offsetToCenter())

        p.drawPixmap(0, 0, self.pixmap)
        Shape.scale = self.scale
        for shape in self.shapes:
            if (shape.selected
                    or not self._hideBackround) and self.isVisible(shape):
                shape.fill = shape.selected or shape == self.hShape
                shape.paint(p)
        if self.current:
            self.current.paint(p)
            self.line.paint(p)
        if self.selectedShapeCopy:
            self.selectedShapeCopy.paint(p)

        # Paint rect
        if self.current is not None and len(self.line) == 2:
            leftTop = self.line[0]
            rightBottom = self.line[1]
            rectWidth = rightBottom.x() - leftTop.x()
            rectHeight = rightBottom.y() - leftTop.y()
            p.setPen(self.drawingRectColor)
            brush = QBrush(Qt.BDiagPattern)
            p.setBrush(brush)
            p.drawRect(leftTop.x(), leftTop.y(), rectWidth, rectHeight)

        if self.drawing() and not self.prevPoint.isNull(
        ) and not self.outOfPixmap(self.prevPoint):
            p.setPen(QColor(0, 0, 0))
            p.drawLine(self.prevPoint.x(), 0, self.prevPoint.x(),
                       self.pixmap.height())
            p.drawLine(0, self.prevPoint.y(), self.pixmap.width(),
                       self.prevPoint.y())

        self.setAutoFillBackground(True)
        if self.verified:
            pal = self.palette()
            pal.setColor(self.backgroundRole(), QColor(184, 239, 38, 128))
            self.setPalette(pal)
        else:
            pal = self.palette()
            pal.setColor(self.backgroundRole(), QColor(232, 232, 232, 255))
            self.setPalette(pal)

        p.end()

    def transformPos(self, point):
        """Convert from widget-logical coordinates to painter-logical coordinates."""
        return point / self.scale - self.offsetToCenter()

    def offsetToCenter(self):
        s = self.scale
        area = super(Canvas, self).size()
        w, h = self.pixmap.width() * s, self.pixmap.height() * s
        aw, ah = area.width(), area.height()
        x = (aw - w) / (2 * s) if aw > w else 0
        y = (ah - h) / (2 * s) if ah > h else 0
        return QPointF(x, y)

    def outOfPixmap(self, p):
        w, h = self.pixmap.width(), self.pixmap.height()
        return not (0 <= p.x() <= w and 0 <= p.y() <= h)

    def finalise(self):
        assert self.current
        if self.current.points[0] == self.current.points[-1]:
            self.current = None
            self.drawingPolygon.emit(False)
            self.update()
            return

        self.current.close()
        self.shapes.append(self.current)
        self.current = None
        self.setHiding(False)
        self.newShape.emit()
        self.update()

    def closeEnough(self, p1, p2):
        #d = distance(p1 - p2)
        #m = (p1-p2).manhattanLength()
        # print "d %.2f, m %d, %.2f" % (d, m, d - m)
        return distance(p1 - p2) < self.epsilon

    # These two, along with a call to adjustSize are required for the
    # scroll area.
    def sizeHint(self):
        return self.minimumSizeHint()

    def minimumSizeHint(self):
        if self.pixmap:
            return self.scale * self.pixmap.size()
        return super(Canvas, self).minimumSizeHint()

    def wheelEvent(self, ev):
        qt_version = 4 if hasattr(ev, "delta") else 5
        if qt_version == 4:
            if ev.orientation() == Qt.Vertical:
                v_delta = ev.delta()
                h_delta = 0
            else:
                h_delta = ev.delta()
                v_delta = 0
        else:
            delta = ev.angleDelta()
            h_delta = delta.x()
            v_delta = delta.y()

        mods = ev.modifiers()
        if Qt.ControlModifier == int(mods) and v_delta:
            self.zoomRequest.emit(v_delta)
        else:
            v_delta and self.scrollRequest.emit(v_delta, Qt.Vertical)
            h_delta and self.scrollRequest.emit(h_delta, Qt.Horizontal)
        ev.accept()

    def keyPressEvent(self, ev):
        key = ev.key()
        if key == Qt.Key_Escape and self.current:
            print('ESC press')
            self.current = None
            self.drawingPolygon.emit(False)
            self.update()
        elif key == Qt.Key_Return and self.canCloseShape():
            self.finalise()
        elif key == Qt.Key_Left and self.selectedShape:
            self.moveOnePixel('Left')
        elif key == Qt.Key_Right and self.selectedShape:
            self.moveOnePixel('Right')
        elif key == Qt.Key_Up and self.selectedShape:
            self.moveOnePixel('Up')
        elif key == Qt.Key_Down and self.selectedShape:
            self.moveOnePixel('Down')

    def moveOnePixel(self, direction):
        # print(self.selectedShape.points)
        if direction == 'Left' and not self.moveOutOfBound(QPointF(-1.0, 0)):
            # print("move Left one pixel")
            self.selectedShape.points[0] += QPointF(-1.0, 0)
            self.selectedShape.points[1] += QPointF(-1.0, 0)
            self.selectedShape.points[2] += QPointF(-1.0, 0)
            self.selectedShape.points[3] += QPointF(-1.0, 0)
        elif direction == 'Right' and not self.moveOutOfBound(QPointF(1.0, 0)):
            # print("move Right one pixel")
            self.selectedShape.points[0] += QPointF(1.0, 0)
            self.selectedShape.points[1] += QPointF(1.0, 0)
            self.selectedShape.points[2] += QPointF(1.0, 0)
            self.selectedShape.points[3] += QPointF(1.0, 0)
        elif direction == 'Up' and not self.moveOutOfBound(QPointF(0, -1.0)):
            # print("move Up one pixel")
            self.selectedShape.points[0] += QPointF(0, -1.0)
            self.selectedShape.points[1] += QPointF(0, -1.0)
            self.selectedShape.points[2] += QPointF(0, -1.0)
            self.selectedShape.points[3] += QPointF(0, -1.0)
        elif direction == 'Down' and not self.moveOutOfBound(QPointF(0, 1.0)):
            # print("move Down one pixel")
            self.selectedShape.points[0] += QPointF(0, 1.0)
            self.selectedShape.points[1] += QPointF(0, 1.0)
            self.selectedShape.points[2] += QPointF(0, 1.0)
            self.selectedShape.points[3] += QPointF(0, 1.0)
        self.shapeMoved.emit()
        self.repaint()

    def moveOutOfBound(self, step):
        points = [
            p1 + p2 for p1, p2 in zip(self.selectedShape.points, [step] * 4)
        ]
        return True in map(self.outOfPixmap, points)

    def setLastLabel(self, text, line_color=None, fill_color=None):
        assert text
        self.shapes[-1].label = text
        if line_color:
            self.shapes[-1].line_color = line_color

        if fill_color:
            self.shapes[-1].fill_color = fill_color

        return self.shapes[-1]

    def undoLastLine(self):
        assert self.shapes
        self.current = self.shapes.pop()
        self.current.setOpen()
        self.line.points = [self.current[-1], self.current[0]]
        self.drawingPolygon.emit(True)

    def resetAllLines(self):
        assert self.shapes
        self.current = self.shapes.pop()
        self.current.setOpen()
        self.line.points = [self.current[-1], self.current[0]]
        self.drawingPolygon.emit(True)
        self.current = None
        self.drawingPolygon.emit(False)
        self.update()

    def loadPixmap(self, pixmap):
        self.pixmap = pixmap
        self.shapes = []
        self.repaint()

    def loadShapes(self, shapes):
        self.shapes = list(shapes)
        self.current = None
        self.repaint()

    def setShapeVisible(self, shape, value):
        self.visible[shape] = value
        self.repaint()

    def currentCursor(self):
        cursor = QApplication.overrideCursor()
        if cursor is not None:
            cursor = cursor.shape()
        return cursor

    def overrideCursor(self, cursor):
        self._cursor = cursor
        if self.currentCursor() is None:
            QApplication.setOverrideCursor(cursor)
        else:
            QApplication.changeOverrideCursor(cursor)

    def restoreCursor(self):
        QApplication.restoreOverrideCursor()

    def resetState(self):
        self.restoreCursor()
        self.pixmap = None
        self.update()

    def setDrawingShapeToSquare(self, status):
        self.drawSquare = status
コード例 #29
0
ファイル: canvas.py プロジェクト: angelxuhan/Priv-LabelImg
class Canvas(QWidget):
    zoomRequest = pyqtSignal(int)  #zoom需求的自定义信号
    scrollRequest = pyqtSignal(int, int)  #scroll的自定义信号
    newShape = pyqtSignal()
    selectionChanged = pyqtSignal(bool)
    shapeMoved = pyqtSignal()
    drawingPolygon = pyqtSignal(bool)
    CREATE, EDIT = range(2)  #
    RECT_SHAPE, POLYGON_SHAPE = range(2)  #矩形,多边形

    epsilon = 11.0

    def __init__(self, *args, **kwargs):
        super(Canvas,
              self).__init__(*args,
                             **kwargs)  #*args将输入的参数存放为元组,**kwargs将输入的参数存放为字典
        # Initialise local state.
        self.shape_type = self.POLYGON_SHAPE
        self.brush_point = None
        self.task_mode = 3
        self.erase_mode = False
        self.current_brush_path = None
        self.mask_Image = None
        self.brush_color = QColor(255, 0, 0, 255)
        self.brush_size = 10
        self.brush = QPainter()
        self.mode = self.EDIT
        self.shapes = []
        self.current = None
        self.selectedShape = None  # save the selected shape here
        self.selectedShapeCopy = None
        self.lineColor = QColor(0, 0, 255)
        self.line = Shape(line_color=self.lineColor)
        self.prevPoint = QPointF()
        self.offsets = QPointF(), QPointF()
        self.scale = 1.0
        self.bg_image = QImage()
        self.visible = {}
        self._hideBackround = False
        self.hideBackround = False
        self.hShape = None
        self.hVertex = None
        self._painter = QPainter(self)
        self.font_size = 50
        self._cursor = CURSOR_DEFAULT
        # Menus:
        self.menus = (QMenu(), QMenu())
        # Set widget options.
        self.setMouseTracking(True)
        self.setFocusPolicy(Qt.WheelFocus)
        ##point
        self.point_point = None
        self.point_point_list = []
        self.point_dex = None
        self.point_color = [
            QColor(r, g, b) for r in [0, 255, 120, 30]
            for g in [0, 255, 120, 30] for b in [0, 255, 120, 30]
        ]
        self.point_move = None
        self.point_path = None
        self.point_selecteditem = None
        self.point_delete = False

    def set_shape_type(self, type):
        if type == 0:
            self.shape_type = self.RECT_SHAPE
            self.line.set_shape_type(type)
            return True
        elif type == 1:
            self.shape_type = self.POLYGON_SHAPE
            self.line.set_shape_type(type)
            return True
        else:
            print("not support the shape type: " + str(type))
            return False

    def enterEvent(self, ev):
        self.overrideCursor(self._cursor)

    def get_mask_image(self):
        return self.mask_pixmap

    def leaveEvent(self, ev):
        self.restoreCursor()

    def focusOutEvent(self, ev):
        self.restoreCursor()

    def isVisible(self, shape):
        return self.visible.get(shape, True)

    def drawing(self):
        return self.mode == self.CREATE

    def editing(self):
        return self.mode == self.EDIT

    def setEditing(self, value=True):
        self.mode = self.EDIT if value else self.CREATE
        if not value:  # Create
            self.unHighlight()
            self.deSelectShape()

    def unHighlight(self):
        if self.hShape:
            self.hShape.highlightClear()
        self.hVertex = self.hShape = None

    def selectedVertex(self):
        return self.hVertex is not None

    def mouseMoveEvent(self, ev):  #这个函数是最重要的函数
        """Update line with last point and current coordinates."""
        pos = self.transformPos(ev.pos())  #点一下return一个pos
        self.point_point = pos
        self.restoreCursor()  #鼠标图标
        if self.task_mode == 3:
            self.brush_point = pos
            if Qt.LeftButton & ev.buttons():  #左鼠标点击
                if self.outOfPixmap(pos):  #超出图像范围
                    return
                if not self.current_brush_path:
                    self.current_brush_path = QPainterPath()
                    self.current_brush_path.moveTo(pos)
                else:
                    self.current_brush_path.lineTo(pos)
            self.repaint()
            return
        if self.task_mode == 4:
            for i, p in enumerate(self.point_point_list):
                if distance(p - pos) <= 5:
                    self.point_dex = i + 1
            if self.point_delete:
                print('deletepoint')
                self.point_color[self.point_dex - 1] = QColor(0, 0, 0, 0)
                self.point_delete = False
            self.repaint()
            return

        # Polygon drawing.
        if self.drawing():
            self.overrideCursor(CURSOR_DRAW)
            if self.current:
                color = self.lineColor
                if self.outOfPixmap(pos):
                    # Don't allow the user to draw outside the pixmap.
                    # Project the point to the pixmap's edges.
                    pos = self.intersectionPoint(self.current[-1], pos)
                elif len(self.current) > 1 and self.closeEnough(
                        pos, self.current[0]):
                    # Attract line to starting point and colorise to alert the
                    # user:
                    pos = self.current[0]
                    color = self.current.line_color
                    self.overrideCursor(CURSOR_POINT)
                    self.current.highlightVertex(0, Shape.NEAR_VERTEX)
                self.line[1] = pos
                self.line.line_color = color
                self.repaint()
                self.current.highlightClear()
            return

        # Polygon copy moving.
        if Qt.RightButton & ev.buttons():
            if self.selectedShapeCopy and self.prevPoint:
                self.overrideCursor(CURSOR_MOVE)
                self.boundedMoveShape(self.selectedShapeCopy, pos)
                self.repaint()
            elif self.selectedShape:
                self.selectedShapeCopy = self.selectedShape.copy()
                self.repaint()
            return

        # Polygon/Vertex moving.
        if Qt.LeftButton & ev.buttons():
            if self.selectedVertex():
                self.boundedMoveVertex(pos)
                self.shapeMoved.emit()
                self.repaint()
            elif self.selectedShape and self.prevPoint:
                self.overrideCursor(CURSOR_MOVE)
                self.boundedMoveShape(self.selectedShape, pos)
                self.shapeMoved.emit()
                self.repaint()
            return

        # Just hovering over the canvas, 2 posibilities:
        # - Highlight shapes
        # - Highlight vertex
        # Update shape/vertex fill and tooltip value accordingly.
        self.setToolTip("Image")
        for shape in reversed([s for s in self.shapes if self.isVisible(s)]):
            # Look for a nearby vertex to highlight. If that fails,
            # check if we happen to be inside a shape.
            index = shape.nearestVertex(pos, self.epsilon)
            if index is not None:
                if self.selectedVertex():
                    self.hShape.highlightClear()
                self.hVertex, self.hShape = index, shape
                shape.highlightVertex(index, shape.MOVE_VERTEX)
                self.overrideCursor(CURSOR_POINT)
                self.setToolTip("Click & drag to move point")  #移动点
                self.setStatusTip(self.toolTip())
                self.update()
                break
            elif shape.containsPoint(pos):
                if self.selectedVertex():
                    self.hShape.highlightClear()
                self.hVertex, self.hShape = None, shape
                self.setToolTip("Click & drag to move shape '%s'" %
                                shape.label)
                self.setStatusTip(self.toolTip())
                self.overrideCursor(CURSOR_GRAB)
                self.update()
                break
        else:  # Nothing found, clear highlights, reset state.
            if self.hShape:
                self.hShape.highlightClear()
                self.update()
            self.hVertex, self.hShape = None, None

    def mousePressEvent(self, ev):
        mods = ev.modifiers()
        pos = self.transformPos(ev.pos())
        if ev.button() == Qt.LeftButton:
            if self.drawing():  #
                if self.shape_type == self.POLYGON_SHAPE and self.current:
                    self.current.addPoint(self.line[1])
                    self.line[0] = self.current[-1]
                    if self.current.isClosed():
                        self.finalise()
                elif self.shape_type == self.RECT_SHAPE and self.current and self.current.reachMaxPoints(
                ) is False:
                    initPos = self.current[0]
                    minX = initPos.x()
                    minY = initPos.y()
                    targetPos = self.line[1]
                    maxX = targetPos.x()
                    maxY = targetPos.y()
                    self.current.addPoint(QPointF(minX, maxY))
                    self.current.addPoint(targetPos)
                    self.current.addPoint(QPointF(maxX, minY))
                    self.current.addPoint(initPos)
                    self.line[0] = self.current[-1]
                    if self.current.isClosed():
                        self.finalise()

                elif not self.outOfPixmap(pos):
                    self.current = Shape(shape_type=self.shape_type)
                    self.current.addPoint(pos)
                    self.line.points = [pos, pos]
                    self.setHiding()
                    self.drawingPolygon.emit(True)
                    self.update()
            elif self.task_mode == 4:
                distances = []
                self.point_point_list.append(pos)
                if Qt.LeftButton & ev.buttons():  # 左鼠标点击
                    if self.outOfPixmap(pos):  # 超出图像范围
                        return
                    elif len(self.point_point_list) > 1:
                        if distance(self.point_point_list[-1] -
                                    self.point_point_list[-2]) <= 5:
                            self.point_move = True
                            del self.point_point_list[-1]
                        for i, p in enumerate(self.point_point_list[:-2]):
                            distances.append(distance(p - pos))
                        distances.sort()
                        print('distances', distances)
                        if len(distances) >= 1:
                            if distances[0] <= 5:  #注意 一次只能删除一个点
                                print(self.point_point_list[-1])
                                if distances[0] <= 2:
                                    self.point_move = True  #这里给出可移动的指令
                                del self.point_point_list[-1]
                    if self.point_move:
                        self.point_point = pos
                # elif  Qt.RightButton & ev.buttons():
                #     print('dede')
                # for i, p in enumerate(self.point_point_list[:-2]):
                #     if distance(p-pos)<=5:
                #         print('delete point')

                self.overrideCursor(Qt.CrossCursor)
                self.repaint()
                return
            else:
                self.selectShapePoint(pos)
                self.prevPoint = pos
                self.repaint()

        elif ev.button() == Qt.RightButton and self.editing():
            self.selectShapePoint(pos)
            self.prevPoint = pos
            self.repaint()  #这里只是传点吧

    def mouseReleaseEvent(self, ev):
        if ev.button() == Qt.RightButton:
            menu = self.menus[bool(self.selectedShapeCopy)]
            self.restoreCursor()
            if not menu.exec_(self.mapToGlobal(ev.pos()))\
               and self.selectedShapeCopy:
                # Cancel the move by deleting the shadow copy.
                self.selectedShapeCopy = None
                self.repaint()
        elif ev.button() == Qt.LeftButton and self.selectedShape:
            self.overrideCursor(CURSOR_GRAB)
        elif ev.button(
        ) == Qt.LeftButton and self.task_mode == 3 and self.current_brush_path:
            self.current_brush_path = None
        elif ev.button(
        ) == Qt.LeftButton and self.task_mode == 4 and self.point_move:
            # del self.point_point_list[self.point_dex]
            self.point_point_list[self.point_dex - 1] = self.point_point
            self.point_move = False
            self.repaint()

    def deletepoint(self):
        self.point_delete = True

    def endMove(self, copy=False):
        assert self.selectedShape and self.selectedShapeCopy
        shape = self.selectedShapeCopy
        #del shape.fill_color
        #del shape.line_color
        if copy:
            self.shapes.append(shape)
            self.selectedShape.selected = False
            self.selectedShape = shape
            self.repaint()
        else:
            shape.label = self.selectedShape.label
            self.deleteSelected()
            self.shapes.append(shape)
        self.selectedShapeCopy = None

    def hideBackroundShapes(self, value):
        self.hideBackround = value
        if self.selectedShape:
            # Only hide other shapes if there is a current selection.
            # Otherwise the user will not be able to select a shape.
            self.setHiding(True)
            self.repaint()

    def setHiding(self, enable=True):
        self._hideBackround = self.hideBackround if enable else False

    def canCloseShape(self):
        return self.drawing() and self.current and len(self.current) > 2

    def mouseDoubleClickEvent(self, ev):
        # We need at least 4 points here, since the mousePress handler
        # adds an extra one before this handler is called.
        if self.canCloseShape() and len(self.current) > 3:
            self.current.popPoint()
            self.finalise()

    def selectShape(self, shape):
        self.deSelectShape()
        shape.selected = True
        self.selectedShape = shape
        self.setHiding()
        self.selectionChanged.emit(True)
        self.update()

    def selectShapePoint(self, point):
        """Select the first shape created which contains this point."""
        self.deSelectShape()
        if self.selectedVertex():  # A vertex is marked for selection.
            index, shape = self.hVertex, self.hShape
            shape.highlightVertex(index, shape.MOVE_VERTEX)
            return
        for shape in reversed(self.shapes):
            if self.isVisible(shape) and shape.containsPoint(point):
                shape.selected = True
                self.selectedShape = shape
                self.calculateOffsets(shape, point)
                self.setHiding()
                self.selectionChanged.emit(True)
                return

    def calculateOffsets(self, shape, point):
        rect = shape.boundingRect()
        x1 = rect.x() - point.x()
        y1 = rect.y() - point.y()
        x2 = (rect.x() + rect.width()) - point.x()
        y2 = (rect.y() + rect.height()) - point.y()
        self.offsets = QPointF(x1, y1), QPointF(x2, y2)

    def boundedMoveVertex(self, pos):

        index, shape = self.hVertex, self.hShape
        point = shape[index]

        if self.outOfPixmap(pos):
            pos = self.intersectionPoint(point, pos)

        shiftPos = pos - point  #point 是之前画好的点 pos为当前的点
        shape.moveVertexBy(index, shiftPos)

        if self.shape_type == self.RECT_SHAPE:
            lindex = (index + 1) % 4
            rindex = (index + 3) % 4
            lshift = None
            rshift = None
            if index % 2 == 0:
                lshift = QPointF(shiftPos.x(), 0)
                rshift = QPointF(0, shiftPos.y())
            else:
                rshift = QPointF(shiftPos.x(), 0)
                lshift = QPointF(0, shiftPos.y())
            shape.moveVertexBy(rindex, rshift)
            shape.moveVertexBy(lindex, lshift)

    def boundedMoveShape(self, shape, pos):
        if self.outOfPixmap(pos):
            return False  # No need to move
        o1 = pos + self.offsets[0]
        if self.outOfPixmap(o1):
            pos -= QPointF(min(0, o1.x()), min(0, o1.y()))
        o2 = pos + self.offsets[1]
        if self.outOfPixmap(o2):
            pos += QPointF(min(0,
                               self.bg_image.width() - o2.x()),
                           min(0,
                               self.bg_image.height() - o2.y()))
        # The next line tracks the new position of the cursor
        # relative to the shape, but also results in making it
        # a bit "shaky" when nearing the border and allows it to
        # go outside of the shape's area for some reason. XXX
        #self.calculateOffsets(self.selectedShape, pos)
        dp = pos - self.prevPoint
        if dp:
            shape.moveBy(dp)
            self.prevPoint = pos
            return True
        return False

    def deSelectShape(self):
        if self.selectedShape:
            self.selectedShape.selected = False
            self.selectedShape = None
            self.setHiding(False)
            self.selectionChanged.emit(False)
            self.update()

    def deleteSelected(self):
        if self.selectedShape:
            shape = self.selectedShape
            self.shapes.remove(self.selectedShape)
            self.selectedShape = None
            self.update()
            return shape

    def copySelectedShape(self):
        if self.selectedShape:
            shape = self.selectedShape.copy()
            self.deSelectShape()
            self.shapes.append(shape)
            shape.selected = True
            self.selectedShape = shape
            self.boundedShiftShape(shape)
            return shape

    def boundedShiftShape(self, shape):
        # Try to move in one direction, and if it fails in another.
        # Give up if both fail.
        point = shape[0]
        offset = QPointF(2.0, 2.0)
        self.calculateOffsets(shape, point)
        self.prevPoint = point
        if not self.boundedMoveShape(shape, point - offset):
            self.boundedMoveShape(shape, point + offset)

    def paintEvent(self, event):  #所有的绘制操作都在paintEvent中完成
        if not self.bg_image:
            return super(Canvas, self).paintEvent(event)

        p = self._painter
        p.begin(self)  #都在begin()和end()间完成
        p.setFont(QFont('Times', self.font_size, QFont.Bold))
        p.setRenderHint(QPainter.Antialiasing)
        p.setRenderHint(QPainter.HighQualityAntialiasing)
        p.setRenderHint(QPainter.SmoothPixmapTransform)
        p.scale(self.scale, self.scale)
        p.translate(self.offsetToCenter())
        p.drawImage(0, 0, self.bg_image)
        #print self.brush_point.x(),self.brush_point.y()
        if self.task_mode == 3:
            p.setOpacity(0.3)
            p.drawImage(0, 0, self.mask_pixmap)
            if self.brush_point:
                p.drawEllipse(self.brush_point, self.brush_size / 2,
                              self.brush_size / 2)  #椭圆
            if self.current_brush_path:
                if self.mask_pixmap.isNull():
                    self.mask_pixmap = QImage(self.bg_image.size(),
                                              QImage.Format_ARGB32)
                    self.mask_pixmap.fill(QColor(255, 255, 255, 0))
                self.brush.begin(self.mask_pixmap)
                brush_pen = QPen()
                self.brush.setCompositionMode(QPainter.CompositionMode_Source)
                brush_pen.setColor(self.brush_color)
                brush_pen.setWidth(self.brush_size)
                brush_pen.setCapStyle(Qt.RoundCap)
                brush_pen.setJoinStyle(Qt.RoundJoin)
                self.brush.setPen(brush_pen)
                self.brush.drawPath(self.current_brush_path)
                self.brush.end()
        if self.task_mode == 4:
            for i, point in enumerate(self.point_point_list):
                if point:
                    p.setBrush(self.point_color[i])
                    p.setPen(self.point_color[i])
                    p.drawEllipse(float(point.x() - 2), float(point.y() - 2),
                                  4, 4)
            if self.point_dex:  #注意这里剔除了0 0是none
                print(self.point_dex)
                p.setPen(QColor(255, 255, 255))
                p.setBrush(QColor(0, 0, 0, 0))
                p.drawRect(
                    float(self.point_point_list[self.point_dex - 1].x() - 2),
                    float(self.point_point_list[self.point_dex - 1].y() - 2),
                    4, 4)
                if self.point_move:
                    p.setPen(QColor(255, 255, 255))
                    p.drawLine(self.point_point_list[self.point_dex - 1],
                               self.point_point)

        Shape.scale = self.scale
        for shape in self.shapes:
            if shape.fill_color:
                shape.fill = True
                shape.paint(p)  ###这里是其他模式的调用shape中的Paint函数
            elif (shape.selected
                  or not self._hideBackround) and self.isVisible(shape):
                shape.fill = shape.selected or shape == self.hShape
                shape.paint(p)
        if self.current:
            self.current.paint(p)
            self.line.paint(p)
        if self.selectedShapeCopy:
            self.selectedShapeCopy.paint(p)
        # Paint rect
        if self.current is not None and len(self.line) == 2:
            leftTop = self.line[0]
            rightBottom = self.line[1]
            rectWidth = rightBottom.x() - leftTop.x()
            rectHeight = rightBottom.y() - leftTop.y()
            color = QColor(0, 220, 0)
            p.setPen(color)
            brush = QBrush(Qt.BDiagPattern)
            p.setBrush(brush)
            if self.shape_type == self.RECT_SHAPE:
                p.drawRect(leftTop.x(), leftTop.y(), rectWidth, rectHeight)

        p.end()  #

    def transformPos(self, point):
        """Convert from widget-logical coordinates to painter-logical coordinates."""
        return point / self.scale - self.offsetToCenter()

    def offsetToCenter(self):
        s = self.scale
        area = super(Canvas, self).size()
        if self.bg_image:
            w, h = self.bg_image.width() * s, self.bg_image.height() * s
        else:
            w, h = 100, 100
        aw, ah = area.width(), area.height()
        x = (aw - w) / (2 * s) if aw > w else 0
        y = (ah - h) / (2 * s) if ah > h else 0
        return QPointF(x, y)

    def outOfPixmap(self, p):
        w, h = self.bg_image.width(), self.bg_image.height()
        return not (0 <= p.x() <= w and 0 <= p.y() <= h)

    def finalise(self):  #完成
        assert self.current
        self.current.close()
        self.shapes.append(self.current)
        self.current = None
        self.setHiding(False)
        self.newShape.emit()
        self.update()

    def closeEnough(self, p1, p2):
        #d = distance(p1 - p2)
        #m = (p1-p2).manhattanLength()
        # print "d %.2f, m %d, %.2f" % (d, m, d - m)
        return distance(p1 - p2) < self.epsilon

    def intersectionPoint(self, p1, p2):
        # Cycle through each image edge in clockwise fashion,
        # and find the one intersecting the current line segment.
        # http://paulbourke.net/geometry/lineline2d/
        size = self.bg_image.size()
        points = [(0, 0), (size.width(), 0), (size.width(), size.height()),
                  (0, size.height())]
        x1, y1 = p1.x(), p1.y()
        x2, y2 = p2.x(), p2.y()
        d, i, (x, y) = min(self.intersectingEdges((x1, y1), (x2, y2), points))
        x3, y3 = points[i]
        x4, y4 = points[(i + 1) % 4]
        if (x, y) == (x1, y1):
            # Handle cases where previous point is on one of the edges.
            if x3 == x4:
                return QPointF(x3, min(max(0, y2), max(y3, y4)))
            else:  # y3 == y4
                return QPointF(min(max(0, x2), max(x3, x4)), y3)
        return QPointF(x, y)

    def intersectingEdges(self, xxx_todo_changeme, xxx_todo_changeme1, points):
        """For each edge formed by `points', yield the intersection
        with the line segment `(x1,y1) - (x2,y2)`, if it exists.
        Also return the distance of `(x2,y2)' to the middle of the
        edge along with its index, so that the one closest can be chosen."""
        (x1, y1) = xxx_todo_changeme
        (x2, y2) = xxx_todo_changeme1
        for i in range(4):
            x3, y3 = points[i]
            x4, y4 = points[(i + 1) % 4]
            denom = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1)
            nua = (x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)
            nub = (x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)
            if denom == 0:
                # This covers two cases:
                #   nua == nub == 0: Coincident
                #   otherwise: Parallel
                continue
            ua, ub = nua / denom, nub / denom
            if 0 <= ua <= 1 and 0 <= ub <= 1:
                x = x1 + ua * (x2 - x1)
                y = y1 + ua * (y2 - y1)
                m = QPointF((x3 + x4) / 2, (y3 + y4) / 2)
                d = distance(m - QPointF(x2, y2))
                yield d, i, (x, y)

    # These two, along with a call to adjustSize are required for the
    # scroll area.
    def sizeHint(self):
        return self.minimumSizeHint()

    def minimumSizeHint(self):
        if self.bg_image:
            return self.scale * self.bg_image.size()
        return super(Canvas, self).minimumSizeHint()

    def wheelEvent(self, ev):
        mods = ev.modifiers()
        if Qt.ControlModifier == int(mods):  # ctrl键
            self.zoomRequest.emit(ev.angleDelta().y())
        else:
            ev.angleDelta().y() and self.scrollRequest.emit(
                ev.angleDelta().y(), Qt.Horizontal if
                (Qt.ShiftModifier == int(mods)) else Qt.Vertical)
            ev.angleDelta().x() and self.scrollRequest.emit(
                ev.angleDelta().x(), Qt.Horizontal)

        ev.accept()

    def keyPressEvent(self, ev):
        key = ev.key()
        if key == Qt.Key_Escape and self.current:
            print('ESC press')
            self.current = None
            self.drawingPolygon.emit(False)
            self.update()
        elif key == Qt.Key_Return and self.canCloseShape():
            self.finalise()

    def setLastLabel(self, text):
        assert text
        self.shapes[-1].label = text
        return self.shapes[-1]

    def undoLastLine(self):
        assert self.shapes
        self.current = self.shapes.pop()
        self.current.setOpen()
        self.line.points = [self.current[-1], self.current[0]]
        self.drawingPolygon.emit(True)

    def resetAllLines(self):
        assert self.shapes
        self.current = self.shapes.pop()
        self.current.setOpen()
        self.line.points = [self.current[-1], self.current[0]]
        self.drawingPolygon.emit(True)
        self.current = None
        self.drawingPolygon.emit(False)
        self.update()

    def loadMaskmap(self, mask):
        self.mask_pixmap = mask
        self.repaint()

    def loadPixmap(self, pixmap):
        self.bg_image = pixmap
        self.shapes = []
        self.mask_pixmap = QImage(self.bg_image.size(), QImage.Format_ARGB32)
        self.mask_pixmap.fill(QColor(255, 255, 255, 0))
        self.repaint()

    def loadShapes(self, shapes):
        self.shapes = list(shapes)
        self.shape_type = shapes[0].get_shape_type()
        print("shape_type:", self.shape_type)
        self.current = None
        self.repaint()

    def setShapeVisible(self, shape, value):
        self.visible[shape] = value
        self.repaint()

    def overrideCursor(self, cursor):
        self.restoreCursor()
        self._cursor = cursor
        QApplication.setOverrideCursor(cursor)

    def restoreCursor(self):
        QApplication.restoreOverrideCursor()

    def resetState(self):
        self.restoreCursor()
        self.bg_image = None
        self.update()