-
问题一:ModuleNotFoundError: No module named 'libs.labelColor'
再使用PPOCRLabel的时候一直在报错如下:Traceback (most recent call last):
File "C:\Python310\lib\runpy.py", line 196, in _run_module_as_main
return _run_code(code, main_globals, None,
File "C:\Python310\lib\runpy.py", line 86, in _run_code
exec(code, run_globals)
File "C:\Python310\Scripts\PPOCRLabel.exe__main__.py", line 4, in
from PPOCRLabel.PPOCRLabel import main
File "C:\Python310\lib\site-packages\PPOCRLabel\PPOCRLabel.py", line 43, in
from libs.labelColor import label_colormap
ModuleNotFoundError: No module named 'libs.labelColor'
后来才发现是因为同时安装了另外一个标注包labelimgPlus,随后将labelimgPlus进行卸载,即可
-
问题二:使用标注工具进行标注的时候出现闪退情况
如下所示:Traceback (most recent call last): File "C:\Python310\lib\site-packages\PPOCRLabel\libs\canvas.py", line 596, in paintEvent p.drawRect(leftTop.x(), leftTop.y(), rectWidth, rectHeight) TypeError: arguments did not match any overloaded call: drawRect(self, rect: QRectF): argument 1 has unexpected type 'float' drawRect(self, x: int, y: int, w: int, h: int): argument 1 has unexpected type 'float' drawRect(self, r: QRect): argument 1 has unexpected type 'float' QBackingStore::endPaint() called with active painter; did you forget to destroy it or call QPainter::end() on it? QPainter::begin: Painter already active
修改文件canvas.py 和 shape.py
-
问题三:显示box编号的时候出现闪退
如下所示:Traceback (most recent call last): File "C:\Python310\lib\site-packages\PPOCRLabel\libs\canvas.py", line 578, in paintEvent shape.paint(p) File "C:\Python310\lib\site-packages\PPOCRLabel\libs\shape.py", line 186, in paint painter.drawText(min_x, min_y, text) TypeError: arguments did not match any overloaded call: drawText(self, p: Union[QPointF, QPoint], s: Optional[str]): argument 1 has unexpected type 'float' drawText(self, rectangle: QRectF, flags: int, text: Optional[str]): argument 1 has unexpected type 'float' drawText(self, rectangle: QRect, flags: int, text: Optional[str]): argument 1 has unexpected type 'float' drawText(self, rectangle: QRectF, text: Optional[str], option: QTextOption = QTextOption()): argument 1 has unexpected type 'float' drawText(self, p: QPoint, s: Optional[str]): argument 1 has unexpected type 'float' drawText(self, x: int, y: int, width: int, height: int, flags: int, text: Optional[str]): argument 1 has unexpected type 'float' drawText(self, x: int, y: int, s: Optional[str]): argument 1 has unexpected type 'float' QBackingStore::endPaint() called with active painter; did you forget to destroy it or call QPainter::end() on it?
最后附上修改过代码的两个文件的,直接替换就行了,
替换目录如下:
C:\Python310\lib\site-packages\PPOCRLabel\libs\
```python
# Copyright (c) <2015-Present> Tzutalin
# Copyright (C) 2013 MIT, Computer Science and Artificial Intelligence Laboratory. Bryan Russell, Antonio Torralba,
# William T. Freeman. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
# associated documentation files (the "Software"), to deal in the Software without restriction, including without
# limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
# Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
# The above copyright notice and this permission notice shall be included in all copies or substantial portions of
# the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
# NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
# SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
# CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
import copy
from PyQt5.QtCore import Qt, pyqtSignal, QPointF, QPoint
from PyQt5.QtGui import QPainter, QBrush, QColor, QPixmap
from PyQt5.QtWidgets import QWidget, QMenu, QApplication
from libs.shape import Shape
from libs.utils import distance
CURSOR_DEFAULT = Qt.ArrowCursor
CURSOR_POINT = Qt.PointingHandCursor
CURSOR_DRAW = Qt.CrossCursor
CURSOR_MOVE = Qt.ClosedHandCursor
CURSOR_GRAB = Qt.OpenHandCursor
class Canvas(QWidget):
zoomRequest = pyqtSignal(int)
scrollRequest = pyqtSignal(int, int)
newShape = pyqtSignal()
# selectionChanged = pyqtSignal(bool)
selectionChanged = pyqtSignal(list)
shapeMoved = pyqtSignal()
drawingPolygon = pyqtSignal(bool)
CREATE, EDIT = list(range(2))
_fill_drawing = False # draw shadows
epsilon = 5.0
def __init__(self, *args, **kwargs):
super(Canvas, self).__init__(*args, **kwargs)
# Initialise local state.
self.mode = self.EDIT
self.shapes = []
self.shapesBackups = []
self.current = None
self.selectedShapes = []
self.selectedShape = None # save the selected shape here
self.selectedShapesCopy = []
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.fourpoint = True # ADD
self.pointnum = 0
self.movingShape = False
self.selectCountShape = False
#initialisation for panning
self.pan_initial_pos = QPoint()
#lockedshapes related
self.lockedShapes = []
self.isInTheSameImage = 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:
self.line.points = [self.current[0], pos]
self.line.close()
elif self.fourpoint:
self.line[0] = self.current[-1]
self.line[1] = pos
else:
self.line[1] = pos # pos is the mouse's current position
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.selectedShapesCopy and self.prevPoint:
self.overrideCursor(CURSOR_MOVE)
self.boundedMoveShape(self.selectedShapesCopy, pos)
self.repaint()
elif self.selectedShapes:
self.selectedShapesCopy = [
s.copy() for s in self.selectedShapes
]
self.repaint()
return
# Polygon/Vertex moving.
if Qt.LeftButton & ev.buttons():
if self.selectedVertex():
self.boundedMoveVertex(pos)
self.shapeMoved.emit()
self.repaint()
self.movingShape = True
elif self.selectedShapes and self.prevPoint:
self.overrideCursor(CURSOR_MOVE)
self.boundedMoveShape(self.selectedShapes, pos)
self.shapeMoved.emit()
self.repaint()
self.movingShape = True
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):
pos = self.transformPos(ev.pos())
if ev.button() == Qt.LeftButton:
if self.drawing():
# self.handleDrawing(pos) # OLD
if self.current:
if self.fourpoint: # ADD IF
# Add point to existing shape.
# print('Adding points in mousePressEvent is ', self.line[1])
self.current.addPoint(self.line[1])
self.line[0] = self.current[-1]
if self.current.isClosed():
# print('1111')
self.finalise()
elif self.drawSquare:
assert len(self.current.points) == 1
self.current.points = self.line.points
self.finalise()
elif not self.outOfPixmap(pos):
# Create new shape.
self.current = Shape()
self.current.addPoint(pos)
self.line.points = [pos, pos]
self.setHiding()
self.drawingPolygon.emit(True)
self.update()
else:
group_mode = int(ev.modifiers()) == Qt.ControlModifier
self.selectShapePoint(pos, multiple_selection_mode=group_mode)
self.prevPoint = pos
self.pan_initial_pos = pos
elif ev.button() == Qt.RightButton and self.editing():
group_mode = int(ev.modifiers()) == Qt.ControlModifier
self.selectShapePoint(pos, multiple_selection_mode=group_mode)
self.prevPoint = pos
self.update()
def mouseReleaseEvent(self, ev):
if ev.button() == Qt.RightButton:
menu = self.menus[bool(self.selectedShapesCopy)]
self.restoreCursor()
if not menu.exec_(self.mapToGlobal(ev.pos()))\
and self.selectedShapesCopy:
# Cancel the move by deleting the shadow copy.
# self.selectedShapeCopy = None
self.selectedShapesCopy = []
self.repaint()
elif ev.button() == Qt.LeftButton and self.selectedShapes:
if self.selectedVertex():
self.overrideCursor(CURSOR_POINT)
else:
self.overrideCursor(CURSOR_GRAB)
elif ev.button() == Qt.LeftButton and not self.fourpoint:
pos = self.transformPos(ev.pos())
if self.drawing():
self.handleDrawing(pos)
else:
#pan
QApplication.restoreOverrideCursor() # ?
if self.movingShape and self.hShape:
if self.hShape in self.shapes:
index = self.shapes.index(self.hShape)
if (
self.shapesBackups[-1][index].points
!= self.shapes[index].points
):
self.storeShapes()
self.shapeMoved.emit() # connect to updateBoxlist in PPOCRLabel.py
self.movingShape = False
def endMove(self, copy=False):
assert self.selectedShapes and self.selectedShapesCopy
assert len(self.selectedShapesCopy) == len(self.selectedShapes)
if copy:
for i, shape in enumerate(self.selectedShapesCopy):
shape.idx = len(self.shapes) # add current box index
self.shapes.append(shape)
self.selectedShapes[i].selected = False
self.selectedShapes[i] = shape
else:
for i, shape in enumerate(self.selectedShapesCopy):
self.selectedShapes[i].points = shape.points
self.selectedShapesCopy = []
self.repaint()
self.storeShapes()
return True
def hideBackroundShapes(self, value):
self.hideBackround = value
if self.selectedShapes:
# 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.fourpoint:
targetPos = self.line[self.pointnum]
self.current.addPoint(targetPos)
print('current points in handleDrawing is ', self.line[self.pointnum])
self.update()
if self.pointnum == 3:
self.finalise()
else:
initPos = self.current[0]
print('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):
print('release')
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:
if not self.fourpoint:
self.current.popPoint()
self.finalise()
def selectShapes(self, shapes):
for s in shapes: s.seleted = True
self.setHiding()
self.selectionChanged.emit(shapes)
self.update()
def selectShapePoint(self, point, multiple_selection_mode):
"""Select the first shape created which contains this point."""
if self.selectedVertex(): # A vertex is marked for selection.
index, shape = self.hVertex, self.hShape
shape.highlightVertex(index, shape.MOVE_VERTEX)
return self.hVertex
else:
for shape in reversed(self.shapes):
if self.isVisible(shape) and shape.containsPoint(point):
self.calculateOffsets(shape, point)
self.setHiding()
if multiple_selection_mode:
if shape not in self.selectedShapes: # list
self.selectionChanged.emit(
self.selectedShapes + [shape]
)
else:
self.selectionChanged.emit([shape])
return
self.deSelectShape()
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
if [shape[0].x(), shape[0].y(), shape[2].x(), shape[2].y()] \
== [shape[3].x(),shape[1].y(),shape[1].x(),shape[3].y()]:
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)
else:
shape.moveVertexBy(index, shiftPos)
def boundedMoveShape(self, shapes, pos):
if type(shapes).__name__ != 'list': shapes = [shapes]
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:
for shape in shapes:
shape.moveBy(dp)
shape.close()
self.prevPoint = pos
return True
return False
def deSelectShape(self):
if self.selectedShapes:
for shape in self.selectedShapes: shape.selected=False
self.setHiding(False)
self.selectionChanged.emit([])
self.update()
def deleteSelected(self):
deleted_shapes = []
if self.selectedShapes:
for shape in self.selectedShapes:
self.shapes.remove(shape)
deleted_shapes.append(shape)
self.storeShapes()
self.selectedShapes = []
self.update()
self.updateShapeIndex()
return deleted_shapes
def storeShapes(self):
shapesBackup = []
for shape in self.shapes:
shapesBackup.append(shape.copy())
if len(self.shapesBackups) >= 10:
self.shapesBackups = self.shapesBackups[-9:]
self.shapesBackups.append(shapesBackup)
def copySelectedShape(self):
if self.selectedShapes:
self.selectedShapesCopy = [s.copy() for s in self.selectedShapes]
self.boundedShiftShapes(self.selectedShapesCopy)
self.endMove(copy=True)
return self.selectedShapes
def boundedShiftShapes(self, shapes):
# Try to move in one direction, and if it fails in another.
# Give up if both fail.
for shape in shapes:
point = shape[0]
offset = QPointF(5.0, 5.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.selectedShapesCopy:
for s in self.selectedShapesCopy:
s.paint(p)
# Paint rect
if self.current is not None and len(self.line) == 2 and not self.fourpoint:
# print('Drawing rect')
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)
p.drawRect(int(leftTop.x()), int(leftTop.y()), int(rectWidth), int(rectHeight))
# ADD:
if (
self.fillDrawing()
and self.fourpoint
and self.current is not None
and len(self.current.points) >= 2
):
print('paint event')
drawing_shape = self.current.copy()
drawing_shape.addPoint(self.line[1])
drawing_shape.fill = True
drawing_shape.paint(p)
if self.drawing() and not self.prevPoint.isNull() and not self.outOfPixmap(self.prevPoint):
p.setPen(QColor(0, 0, 0))
p.drawLine(int(self.prevPoint.x()), 0, int(self.prevPoint.x()), self.pixmap.height())
p.drawLine(0, int(self.prevPoint.y()), self.pixmap.width(), int(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)
# adaptive BBOX label & index font size
if self.pixmap:
h, w = self.pixmap.size().height(), self.pixmap.size().width()
fontszie = int(max(h, w) / 48)
for s in self.shapes:
s.fontsize = fontszie
p.end()
def fillDrawing(self):
return self._fill_drawing
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]:
# print('finalse')
self.current = None
self.drawingPolygon.emit(False)
self.update()
return
self.current.close()
self.current.idx = len(self.shapes) # add current box index
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()
shapesBackup = copy.deepcopy(self.shapes)
if len(shapesBackup) == 0:
return
self.shapesBackups.pop()
self.shapesBackups.append(shapesBackup)
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.selectedShapes:
self.moveOnePixel('Left')
elif key == Qt.Key_Right and self.selectedShapes:
self.moveOnePixel('Right')
elif key == Qt.Key_Up and self.selectedShapes:
self.moveOnePixel('Up')
elif key == Qt.Key_Down and self.selectedShapes:
self.moveOnePixel('Down')
elif key == Qt.Key_X and self.selectedShapes:
for i in range(len(self.selectedShapes)):
self.selectedShape = self.selectedShapes[i]
if self.rotateOutOfBound(0.01):
continue
self.selectedShape.rotate(0.01)
self.shapeMoved.emit()
self.update()
elif key == Qt.Key_C and self.selectedShapes:
for i in range(len(self.selectedShapes)):
self.selectedShape = self.selectedShapes[i]
if self.rotateOutOfBound(-0.01):
continue
self.selectedShape.rotate(-0.01)
self.shapeMoved.emit()
self.update()
def rotateOutOfBound(self, angle):
for shape in range(len(self.selectedShapes)):
self.selectedShape = self.selectedShapes[shape]
for i, p in enumerate(self.selectedShape.points):
if self.outOfPixmap(self.selectedShape.rotatePoint(p, angle)):
return True
return False
def moveOnePixel(self, direction):
# print(self.selectedShape.points)
self.selectCount = len(self.selectedShapes)
self.selectCountShape = True
for i in range(len(self.selectedShapes)):
self.selectedShape = self.selectedShapes[i]
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)
shapesBackup = []
shapesBackup = copy.deepcopy(self.shapes)
self.shapesBackups.append(shapesBackup)
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, key_cls=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 key_cls:
self.shapes[-1].key_cls = key_cls
self.storeShapes()
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 undoLastPoint(self):
if not self.current or self.current.isClosed():
return
self.current.popPoint()
if len(self.current) > 0:
self.line[0] = self.current[-1]
else:
self.current = None
self.drawingPolygon.emit(False)
self.repaint()
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, replace=True):
if replace:
self.shapes = list(shapes)
else:
self.shapes.extend(shapes)
self.current = None
self.hShape = None
self.hVertex = None
# self.hEdge = None
self.storeShapes()
self.updateShapeIndex()
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()
self.shapesBackups = []
def setDrawingShapeToSquare(self, status):
self.drawSquare = status
def restoreShape(self):
if not self.isShapeRestorable:
return
self.shapesBackups.pop() # latest
shapesBackup = self.shapesBackups.pop()
self.shapes = shapesBackup
self.selectedShapes = []
for shape in self.shapes:
shape.selected = False
self.updateShapeIndex()
self.repaint()
@property
def isShapeRestorable(self):
if len(self.shapesBackups) < 2:
return False
return True
def updateShapeIndex(self):
for i in range(len(self.shapes)):
self.shapes[i].idx = i
self.update()
# Copyright (c) <2015-Present> Tzutalin
# Copyright (C) 2013 MIT, Computer Science and Artificial Intelligence Laboratory. Bryan Russell, Antonio Torralba,
# William T. Freeman. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
# associated documentation files (the "Software"), to deal in the Software without restriction, including without
# limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
# Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
# The above copyright notice and this permission notice shall be included in all copies or substantial portions of
# the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
# NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
# SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
# CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
# !/usr/bin/python
# -*- coding: utf-8 -*-
import math
import sys
from PyQt5.QtCore import QPointF
from PyQt5.QtGui import QColor, QPen, QPainterPath, QFont
from libs.utils import distance
DEFAULT_LINE_COLOR = QColor(0, 255, 0, 128)
DEFAULT_FILL_COLOR = QColor(255, 0, 0, 128)
DEFAULT_SELECT_LINE_COLOR = QColor(255, 255, 255)
DEFAULT_SELECT_FILL_COLOR = QColor(0, 128, 255, 155)
DEFAULT_VERTEX_FILL_COLOR = QColor(0, 255, 0, 255)
DEFAULT_HVERTEX_FILL_COLOR = QColor(255, 0, 0)
DEFAULT_LOCK_COLOR = QColor(255, 0, 255)
MIN_Y_LABEL = 10
class Shape(object):
P_SQUARE, P_ROUND = range(2)
MOVE_VERTEX, NEAR_VERTEX = range(2)
# The following class variables influence the drawing
# of _all_ shape objects.
line_color = DEFAULT_LINE_COLOR
fill_color = DEFAULT_FILL_COLOR
select_line_color = DEFAULT_SELECT_LINE_COLOR
select_fill_color = DEFAULT_SELECT_FILL_COLOR
vertex_fill_color = DEFAULT_VERTEX_FILL_COLOR
hvertex_fill_color = DEFAULT_HVERTEX_FILL_COLOR
point_type = P_ROUND
point_size = 8
scale = 1.0
def __init__(self, label=None, line_color=None, difficult=False, key_cls="None", paintLabel=False, paintIdx=False):
self.label = label
self.idx = None # bbox order, only for table annotation
self.points = []
self.fill = False
self.selected = False
self.difficult = difficult
self.key_cls = key_cls
self.paintLabel = paintLabel
self.paintIdx = paintIdx
self.locked = False
self.direction = 0
self.center = None
self.epsilon = 5 # same as canvas
self._highlightIndex = None
self._highlightMode = self.NEAR_VERTEX
self._highlightSettings = {
self.NEAR_VERTEX: (4, self.P_ROUND),
self.MOVE_VERTEX: (1.5, self.P_SQUARE),
}
self.fontsize = 8
self._closed = False
if line_color is not None:
# Override the class line_color attribute
# with an object attribute. Currently this
# is used for drawing the pending line a different color.
self.line_color = line_color
def rotate(self, theta):
for i, p in enumerate(self.points):
self.points[i] = self.rotatePoint(p, theta)
self.direction -= theta
self.direction = self.direction % (2 * math.pi)
def rotatePoint(self, p, theta):
order = p - self.center
cosTheta = math.cos(theta)
sinTheta = math.sin(theta)
pResx = cosTheta * order.x() + sinTheta * order.y()
pResy = - sinTheta * order.x() + cosTheta * order.y()
pRes = QPointF(self.center.x() + pResx, self.center.y() + pResy)
return pRes
def close(self):
self.center = QPointF((self.points[0].x() + self.points[2].x()) / 2,
(self.points[0].y() + self.points[2].y()) / 2)
self._closed = True
def reachMaxPoints(self):
if len(self.points) >= 4:
return True
return False
def addPoint(self, point):
if self.reachMaxPoints() and self.closeEnough(self.points[0], point):
self.close()
else:
self.points.append(point)
def closeEnough(self, p1, p2):
return distance(p1 - p2) < self.epsilon
def popPoint(self):
if self.points:
return self.points.pop()
return None
def isClosed(self):
return self._closed
def setOpen(self):
self._closed = False
def paint(self, painter):
if self.points:
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(self.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(self.points):
line_path.lineTo(p)
self.drawVertex(vrtx_path, i)
if self.isClosed():
line_path.lineTo(self.points[0])
painter.drawPath(line_path)
painter.drawPath(vrtx_path)
painter.fillPath(vrtx_path, self.vertex_fill_color)
# Draw text at the top-left
if self.paintLabel:
min_x = sys.maxsize
min_y = sys.maxsize
for point in self.points:
min_x = min(min_x, point.x())
min_y = min(min_y, point.y())
if min_x != sys.maxsize and min_y != sys.maxsize:
font = QFont()
font.setPointSize(self.fontsize)
font.setBold(True)
painter.setFont(font)
if self.label is None:
self.label = ""
if min_y < MIN_Y_LABEL:
min_y += MIN_Y_LABEL
# painter.drawText(min_x, min_y, self.label)
painter.drawText(int(min_x), int(min_y), self.label)
# Draw number at the top-right
if self.paintIdx:
min_x = sys.maxsize
min_y = sys.maxsize
for point in self.points:
min_x = min(min_x, point.x())
min_y = min(min_y, point.y())
if min_x != sys.maxsize and min_y != sys.maxsize:
font = QFont()
font.setPointSize(self.fontsize)
font.setBold(True)
painter.setFont(font)
text = ''
if self.idx != None:
text = str(self.idx)
if min_y < MIN_Y_LABEL:
min_y += MIN_Y_LABEL
# painter.drawText(min_x, min_y, text)
painter.drawText(int(min_x), int(min_y), text)
if self.fill:
color = self.select_fill_color if self.selected else self.fill_color
painter.fillPath(line_path, color)
def drawVertex(self, path, i):
d = self.point_size / self.scale
shape = self.point_type
point = self.points[i]
if i == self._highlightIndex:
size, shape = self._highlightSettings[self._highlightMode]
d *= size
if self._highlightIndex is not None:
self.vertex_fill_color = self.hvertex_fill_color
else:
self.vertex_fill_color = Shape.vertex_fill_color
if shape == self.P_SQUARE:
path.addRect(point.x() - d / 2, point.y() - d / 2, d, d)
elif shape == self.P_ROUND:
path.addEllipse(point, d / 2.0, d / 2.0)
else:
assert False, "unsupported vertex shape"
def nearestVertex(self, point, epsilon):
for i, p in enumerate(self.points):
if distance(p - point) <= epsilon:
return i
return None
def containsPoint(self, point):
return self.makePath().contains(point)
def makePath(self):
path = QPainterPath(self.points[0])
for p in self.points[1:]:
path.lineTo(p)
return path
def boundingRect(self):
return self.makePath().boundingRect()
def moveBy(self, offset):
self.points = [p + offset for p in self.points]
def moveVertexBy(self, i, offset):
self.points[i] = self.points[i] + offset
def highlightVertex(self, i, action):
self._highlightIndex = i
self._highlightMode = action
def highlightClear(self):
self._highlightIndex = None
def copy(self):
shape = Shape("%s" % self.label)
shape.points = [p for p in self.points]
shape.center = self.center
shape.direction = self.direction
shape.fill = self.fill
shape.selected = self.selected
shape._closed = self._closed
if self.line_color != Shape.line_color:
shape.line_color = self.line_color
if self.fill_color != Shape.fill_color:
shape.fill_color = self.fill_color
shape.difficult = self.difficult
shape.key_cls = self.key_cls
return shape
def __len__(self):
return len(self.points)
def __getitem__(self, key):
return self.points[key]
def __setitem__(self, key, value):
self.points[key] = value
版权声明:《 PPOCRLabel使用问题集锦汇总,方便后面查阅。 》为漠北星火原创文章,转载请注明出处!
最后编辑:2024-4-3 09:04:14