PyQt5 5.15 引入 float 坐标,labelImg 没跟进,需要修补(yolo2026)

PyQt5 5.15 引入 float 坐标,labelImg 没跟进,需要修补(yolo2026) highlight: atelier-cave-darkPyQt5 5.15 引入 float 坐标labelImg 没跟进就要修补。如果未来新版本修复了则不需要。File /Users/dengzemiao/Desktop/Project/python/duanju_yolo26/venv/lib/python3.12/site-packages/libs/canvas.py, line 530, in paintEvent p.drawLine(self.prev_point.x(), 0, self.prev_point.x(), self.pixmap.height()) TypeError: arguments did not match any overloaded call: drawLine(self, l: QLineF): argument 1 has unexpected type float drawLine(self, line: QLine): argument 1 has unexpected type float drawLine(self, x1: int, y1: int, x2: int, y2: int): argument 1 has unexpected type float drawLine(self, p1: QPoint, p2: QPoint): argument 1 has unexpected type float drawLine(self, p1: Union[QPointF, QPoint], p2: Union[QPointF, QPoint]): argument 1 has unexpected type float 2026-03-27 17:56:51.153 python3[21025:997963] error messaging the mach port for IMKCFRunLoopWakeUpReliable Traceback (most recent call last): File /Users/dengzemiao/Desktop/Project/python/duanju_yolo26/venv/lib/python3.12/site-packages/labelImg/labelImg.py, line 965, in scroll_request bar.setValue(bar.value() bar.singleStep() * units) TypeError: setValue(self, a0: int): argument 1 has unexpected type float [1] 21025 abort labelImgPythonPyQt5labelImg是否需要修补3.9 / 3.10≤ 5.14.x1.8.6不需要3.10 / 3.115.15.x1.8.6需要3.125.15.x1.8.6需要修复路径/Users/xxx/Desktop/Project/python/yolo26/venv/lib/python3.12/site-packages/libs/canvas.py主要就是canvas.py文件。使用下面的代码替换掉当前项目内canvas.py文件的代码下面的代码是修复好的代码。当前版本信息Python3.12.10PyQt55.15.11labelImg1.8.6try: from PyQt5.QtGui import * from PyQt5.QtCore import * from PyQt5.QtWidgets import * except ImportError: from PyQt4.QtGui import * from PyQt4.QtCore import * # from PyQt4.QtOpenGL import * 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(QGLWidget): 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.selected_shape None # save the selected shape here self.selected_shape_copy None self.drawing_line_color QColor(0, 0, 255) self.drawing_rect_color QColor(0, 0, 255) self.line Shape(line_colorself.drawing_line_color) self.prev_point QPointF() self.offsets QPointF(), QPointF() self.scale 1.0 self.label_font_size 8 self.pixmap QPixmap() self.visible {} self._hide_background False self.hide_background False self.h_shape None self.h_vertex 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.draw_square False # initialisation for panning self.pan_initial_pos QPoint() def set_drawing_color(self, qcolor): self.drawing_line_color qcolor self.drawing_rect_color qcolor def enterEvent(self, ev): self.override_cursor(self._cursor) def leaveEvent(self, ev): self.restore_cursor() def focusOutEvent(self, ev): self.restore_cursor() 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 set_editing(self, valueTrue): self.mode self.EDIT if value else self.CREATE if not value: # Create self.un_highlight() self.de_select_shape() self.prev_point QPointF() self.repaint() def un_highlight(self): if self.h_shape: self.h_shape.highlight_clear() self.h_vertex self.h_shape None def selected_vertex(self): return self.h_vertex is not None def mouseMoveEvent(self, ev): Update line with last point and current coordinates. pos self.transform_pos(ev.pos()) # Update coordinates in status bar if image is opened window self.parent().window() if window.file_path is not None: self.parent().window().label_coordinates.setText( X: %d; Y: %d % (pos.x(), pos.y())) # Polygon drawing. if self.drawing(): self.override_cursor(CURSOR_DRAW) if self.current: # Display annotation width and height while drawing current_width abs(self.current[0].x() - pos.x()) current_height abs(self.current[0].y() - pos.y()) self.parent().window().label_coordinates.setText( Width: %d, Height: %d / X: %d; Y: %d % (current_width, current_height, pos.x(), pos.y())) color self.drawing_line_color if self.out_of_pixmap(pos): # Dont 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.close_enough(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.override_cursor(CURSOR_POINT) self.current.highlight_vertex(0, Shape.NEAR_VERTEX) if self.draw_square: init_pos self.current[0] min_x init_pos.x() min_y init_pos.y() min_size min(abs(pos.x() - min_x), abs(pos.y() - min_y)) direction_x -1 if pos.x() - min_x 0 else 1 direction_y -1 if pos.y() - min_y 0 else 1 self.line[1] QPointF(min_x direction_x * min_size, min_y direction_y * min_size) else: self.line[1] pos self.line.line_color color self.prev_point QPointF() self.current.highlight_clear() else: self.prev_point pos self.repaint() return # Polygon copy moving. if Qt.RightButton ev.buttons(): if self.selected_shape_copy and self.prev_point: self.override_cursor(CURSOR_MOVE) self.bounded_move_shape(self.selected_shape_copy, pos) self.repaint() elif self.selected_shape: self.selected_shape_copy self.selected_shape.copy() self.repaint() return # Polygon/Vertex moving. if Qt.LeftButton ev.buttons(): if self.selected_vertex(): self.bounded_move_vertex(pos) self.shapeMoved.emit() self.repaint() # Display annotation width and height while moving vertex point1 self.h_shape[1] point3 self.h_shape[3] current_width abs(point1.x() - point3.x()) current_height abs(point1.y() - point3.y()) self.parent().window().label_coordinates.setText( Width: %d, Height: %d / X: %d; Y: %d % (current_width, current_height, pos.x(), pos.y())) elif self.selected_shape and self.prev_point: self.override_cursor(CURSOR_MOVE) self.bounded_move_shape(self.selected_shape, pos) self.shapeMoved.emit() self.repaint() # Display annotation width and height while moving shape point1 self.selected_shape[1] point3 self.selected_shape[3] current_width abs(point1.x() - point3.x()) current_height abs(point1.y() - point3.y()) self.parent().window().label_coordinates.setText( Width: %d, Height: %d / X: %d; Y: %d % (current_width, current_height, pos.x(), pos.y())) 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 possibilities: # - 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.nearest_vertex(pos, self.epsilon) if index is not None: if self.selected_vertex(): self.h_shape.highlight_clear() self.h_vertex, self.h_shape index, shape shape.highlight_vertex(index, shape.MOVE_VERTEX) self.override_cursor(CURSOR_POINT) self.setToolTip(Click drag to move point) self.setStatusTip(self.toolTip()) self.update() break elif shape.contains_point(pos): if self.selected_vertex(): self.h_shape.highlight_clear() self.h_vertex, self.h_shape None, shape self.setToolTip( Click drag to move shape %s % shape.label) self.setStatusTip(self.toolTip()) self.override_cursor(CURSOR_GRAB) self.update() # Display annotation width and height while hovering inside point1 self.h_shape[1] point3 self.h_shape[3] current_width abs(point1.x() - point3.x()) current_height abs(point1.y() - point3.y()) self.parent().window().label_coordinates.setText( Width: %d, Height: %d / X: %d; Y: %d % (current_width, current_height, pos.x(), pos.y())) break else: # Nothing found, clear highlights, reset state. if self.h_shape: self.h_shape.highlight_clear() self.update() self.h_vertex, self.h_shape None, None self.override_cursor(CURSOR_DEFAULT) def mousePressEvent(self, ev): pos self.transform_pos(ev.pos()) if ev.button() Qt.LeftButton: if self.drawing(): self.handle_drawing(pos) else: selection self.select_shape_point(pos) self.prev_point pos if selection is None: # pan QApplication.setOverrideCursor(QCursor(Qt.OpenHandCursor)) self.pan_initial_pos pos elif ev.button() Qt.RightButton and self.editing(): self.select_shape_point(pos) self.prev_point pos self.update() def mouseReleaseEvent(self, ev): if ev.button() Qt.RightButton: menu self.menus[bool(self.selected_shape_copy)] self.restore_cursor() if not menu.exec_(self.mapToGlobal(ev.pos()))\ and self.selected_shape_copy: # Cancel the move by deleting the shadow copy. self.selected_shape_copy None self.repaint() elif ev.button() Qt.LeftButton and self.selected_shape: if self.selected_vertex(): self.override_cursor(CURSOR_POINT) else: self.override_cursor(CURSOR_GRAB) elif ev.button() Qt.LeftButton: pos self.transform_pos(ev.pos()) if self.drawing(): self.handle_drawing(pos) else: # pan QApplication.restoreOverrideCursor() def end_move(self, copyFalse): assert self.selected_shape and self.selected_shape_copy shape self.selected_shape_copy # del shape.fill_color # del shape.line_color if copy: self.shapes.append(shape) self.selected_shape.selected False self.selected_shape shape self.repaint() else: self.selected_shape.points [p for p in shape.points] self.selected_shape_copy None def hide_background_shapes(self, value): self.hide_background value if self.selected_shape: # Only hide other shapes if there is a current selection. # Otherwise the user will not be able to select a shape. self.set_hiding(True) self.repaint() def handle_drawing(self, pos): if self.current and self.current.reach_max_points() is False: init_pos self.current[0] min_x init_pos.x() min_y init_pos.y() target_pos self.line[1] max_x target_pos.x() max_y target_pos.y() self.current.add_point(QPointF(max_x, min_y)) self.current.add_point(target_pos) self.current.add_point(QPointF(min_x, max_y)) self.finalise() elif not self.out_of_pixmap(pos): self.current Shape() self.current.add_point(pos) self.line.points [pos, pos] self.set_hiding() self.drawingPolygon.emit(True) self.update() def set_hiding(self, enableTrue): self._hide_background self.hide_background if enable else False def can_close_shape(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.can_close_shape() and len(self.current) 3: self.current.pop_point() self.finalise() def select_shape(self, shape): self.de_select_shape() shape.selected True self.selected_shape shape self.set_hiding() self.selectionChanged.emit(True) self.update() def select_shape_point(self, point): Select the first shape created which contains this point. self.de_select_shape() if self.selected_vertex(): # A vertex is marked for selection. index, shape self.h_vertex, self.h_shape shape.highlight_vertex(index, shape.MOVE_VERTEX) self.select_shape(shape) return self.h_vertex for shape in reversed(self.shapes): if self.isVisible(shape) and shape.contains_point(point): self.select_shape(shape) self.calculate_offsets(shape, point) return self.selected_shape return None def calculate_offsets(self, shape, point): rect shape.bounding_rect() 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 snap_point_to_canvas(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 bounded_move_vertex(self, pos): index, shape self.h_vertex, self.h_shape point shape[index] if self.out_of_pixmap(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.draw_square: 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())) direction_x -1 if pos.x() - opposite_point.x() 0 else 1 direction_y -1 if pos.y() - opposite_point.y() 0 else 1 shift_pos QPointF(opposite_point.x() direction_x * min_size - point.x(), opposite_point.y() direction_y * min_size - point.y()) else: shift_pos pos - point shape.move_vertex_by(index, shift_pos) left_index (index 1) % 4 right_index (index 3) % 4 left_shift None right_shift None if index % 2 0: right_shift QPointF(shift_pos.x(), 0) left_shift QPointF(0, shift_pos.y()) else: left_shift QPointF(shift_pos.x(), 0) right_shift QPointF(0, shift_pos.y()) shape.move_vertex_by(right_index, right_shift) shape.move_vertex_by(left_index, left_shift) def bounded_move_shape(self, shape, pos): if self.out_of_pixmap(pos): return False # No need to move o1 pos self.offsets[0] if self.out_of_pixmap(o1): pos - QPointF(min(0, o1.x()), min(0, o1.y())) o2 pos self.offsets[1] if self.out_of_pixmap(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 shapes area for some reason. XXX # self.calculateOffsets(self.selectedShape, pos) dp pos - self.prev_point if dp: shape.move_by(dp) self.prev_point pos return True return False def de_select_shape(self): if self.selected_shape: self.selected_shape.selected False self.selected_shape None self.set_hiding(False) self.selectionChanged.emit(False) self.update() def delete_selected(self): if self.selected_shape: shape self.selected_shape self.shapes.remove(self.selected_shape) self.selected_shape None self.update() return shape def copy_selected_shape(self): if self.selected_shape: shape self.selected_shape.copy() self.de_select_shape() self.shapes.append(shape) shape.selected True self.selected_shape shape self.bounded_shift_shape(shape) return shape def bounded_shift_shape(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.calculate_offsets(shape, point) self.prev_point point if not self.bounded_move_shape(shape, point - offset): self.bounded_move_shape(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.offset_to_center()) p.drawPixmap(0, 0, self.pixmap) Shape.scale self.scale Shape.label_font_size self.label_font_size for shape in self.shapes: if (shape.selected or not self._hide_background) and self.isVisible(shape): shape.fill shape.selected or shape self.h_shape shape.paint(p) if self.current: self.current.paint(p) self.line.paint(p) if self.selected_shape_copy: self.selected_shape_copy.paint(p) # Paint rect if self.current is not None and len(self.line) 2: left_top self.line[0] right_bottom self.line[1] rect_width right_bottom.x() - left_top.x() rect_height right_bottom.y() - left_top.y() p.setPen(self.drawing_rect_color) brush QBrush(Qt.BDiagPattern) p.setBrush(brush) p.drawRect(int(left_top.x()), int(left_top.y()), int(rect_width), int(rect_height)) if self.drawing() and not self.prev_point.isNull() and not self.out_of_pixmap(self.prev_point): p.setPen(QColor(0, 0, 0)) p.drawLine(int(self.prev_point.x()), 0, int(self.prev_point.x()), self.pixmap.height()) p.drawLine(0, int(self.prev_point.y()), self.pixmap.width(), int(self.prev_point.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 transform_pos(self, point): Convert from widget-logical coordinates to painter-logical coordinates. return point / self.scale - self.offset_to_center() def offset_to_center(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 out_of_pixmap(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.set_hiding(False) self.newShape.emit() self.update() def close_enough(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.can_close_shape(): self.finalise() elif key Qt.Key_Left and self.selected_shape: self.move_one_pixel(Left) elif key Qt.Key_Right and self.selected_shape: self.move_one_pixel(Right) elif key Qt.Key_Up and self.selected_shape: self.move_one_pixel(Up) elif key Qt.Key_Down and self.selected_shape: self.move_one_pixel(Down) def move_one_pixel(self, direction): # print(self.selectedShape.points) if direction Left and not self.move_out_of_bound(QPointF(-1.0, 0)): # print(move Left one pixel) self.selected_shape.points[0] QPointF(-1.0, 0) self.selected_shape.points[1] QPointF(-1.0, 0) self.selected_shape.points[2] QPointF(-1.0, 0) self.selected_shape.points[3] QPointF(-1.0, 0) elif direction Right and not self.move_out_of_bound(QPointF(1.0, 0)): # print(move Right one pixel) self.selected_shape.points[0] QPointF(1.0, 0) self.selected_shape.points[1] QPointF(1.0, 0) self.selected_shape.points[2] QPointF(1.0, 0) self.selected_shape.points[3] QPointF(1.0, 0) elif direction Up and not self.move_out_of_bound(QPointF(0, -1.0)): # print(move Up one pixel) self.selected_shape.points[0] QPointF(0, -1.0) self.selected_shape.points[1] QPointF(0, -1.0) self.selected_shape.points[2] QPointF(0, -1.0) self.selected_shape.points[3] QPointF(0, -1.0) elif direction Down and not self.move_out_of_bound(QPointF(0, 1.0)): # print(move Down one pixel) self.selected_shape.points[0] QPointF(0, 1.0) self.selected_shape.points[1] QPointF(0, 1.0) self.selected_shape.points[2] QPointF(0, 1.0) self.selected_shape.points[3] QPointF(0, 1.0) self.shapeMoved.emit() self.repaint() def move_out_of_bound(self, step): points [p1 p2 for p1, p2 in zip(self.selected_shape.points, [step] * 4)] return True in map(self.out_of_pixmap, points) def set_last_label(self, text, line_colorNone, fill_colorNone): 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 undo_last_line(self): assert self.shapes self.current self.shapes.pop() self.current.set_open() self.line.points [self.current[-1], self.current[0]] self.drawingPolygon.emit(True) def reset_all_lines(self): assert self.shapes self.current self.shapes.pop() self.current.set_open() self.line.points [self.current[-1], self.current[0]] self.drawingPolygon.emit(True) self.current None self.drawingPolygon.emit(False) self.update() def load_pixmap(self, pixmap): self.pixmap pixmap self.shapes [] self.repaint() def load_shapes(self, shapes): self.shapes list(shapes) self.current None self.repaint() def set_shape_visible(self, shape, value): self.visible[shape] value self.repaint() def current_cursor(self): cursor QApplication.overrideCursor() if cursor is not None: cursor cursor.shape() return cursor def override_cursor(self, cursor): self._cursor cursor if self.current_cursor() is None: QApplication.setOverrideCursor(cursor) else: QApplication.changeOverrideCursor(cursor) def restore_cursor(self): QApplication.restoreOverrideCursor() def reset_state(self): self.restore_cursor() self.pixmap None self.update() def set_drawing_shape_to_square(self, status): self.draw_square status