canvas.py 42 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115
  1. import imgviz
  2. from qtpy import QtCore
  3. from qtpy import QtGui
  4. from qtpy import QtWidgets
  5. import labelme.ai
  6. import labelme.utils
  7. from labelme import QT5
  8. from labelme.logger import logger
  9. from labelme.shape import Shape
  10. # TODO(unknown):
  11. # - [maybe] Find optimal epsilon value.
  12. CURSOR_DEFAULT = QtCore.Qt.ArrowCursor
  13. CURSOR_POINT = QtCore.Qt.PointingHandCursor
  14. CURSOR_DRAW = QtCore.Qt.CrossCursor
  15. CURSOR_MOVE = QtCore.Qt.ClosedHandCursor
  16. CURSOR_GRAB = QtCore.Qt.OpenHandCursor
  17. MOVE_SPEED = 5.0
  18. class Canvas(QtWidgets.QWidget):
  19. zoomRequest = QtCore.Signal(int, QtCore.QPoint)
  20. scrollRequest = QtCore.Signal(int, int)
  21. newShape = QtCore.Signal()
  22. selectionChanged = QtCore.Signal(list)
  23. shapeMoved = QtCore.Signal()
  24. drawingPolygon = QtCore.Signal(bool)
  25. vertexSelected = QtCore.Signal(bool)
  26. mouseMoved = QtCore.Signal(QtCore.QPointF)
  27. CREATE, EDIT = 0, 1
  28. # polygon, rectangle, line, or point
  29. _createMode = "polygon"
  30. _fill_drawing = False
  31. def __init__(self, *args, **kwargs):
  32. self.epsilon = kwargs.pop("epsilon", 10.0)
  33. self.model_path=kwargs.pop("model_path",None)
  34. self.double_click = kwargs.pop("double_click", "close")
  35. if self.double_click not in [None, "close"]:
  36. raise ValueError(
  37. "Unexpected value for double_click event: {}".format(self.double_click)
  38. )
  39. self.num_backups = kwargs.pop("num_backups", 10)
  40. self._crosshair = kwargs.pop(
  41. "crosshair",
  42. {
  43. "polygon": False,
  44. "rectangle": True,
  45. "circle": False,
  46. "line": False,
  47. "point": False,
  48. "linestrip": False,
  49. "ai_polygon": False,
  50. "ai_mask": False,
  51. },
  52. )
  53. super(Canvas, self).__init__(*args, **kwargs)
  54. # Initialise local state.
  55. self.mode = self.EDIT
  56. self.shapes = []
  57. self.shapesBackups = []
  58. self.current = None
  59. self.selectedShapes = [] # save the selected shapes here
  60. self.selectedShapesCopy = []
  61. # self.line represents:
  62. # - createMode == 'polygon': edge from last point to current
  63. # - createMode == 'rectangle': diagonal line of the rectangle
  64. # - createMode == 'line': the line
  65. # - createMode == 'point': the point
  66. self.line = Shape()
  67. self.prevPoint = QtCore.QPoint()
  68. self.prevMovePoint = QtCore.QPoint()
  69. self.offsets = QtCore.QPoint(), QtCore.QPoint()
  70. self.scale = 1.0
  71. self.pixmap = QtGui.QPixmap()
  72. self.visible = {}
  73. self._hideBackround = False
  74. self.hideBackround = False
  75. self.hShape = None
  76. self.prevhShape = None
  77. self.hVertex = None
  78. self.prevhVertex = None
  79. self.hEdge = None
  80. self.prevhEdge = None
  81. self.movingShape = False
  82. self.snapping = True
  83. self.hShapeIsSelected = False
  84. self._painter = QtGui.QPainter()
  85. self._cursor = CURSOR_DEFAULT
  86. self.draw_pred=False
  87. self.pred_bbox_points=None
  88. self.current_bbox_point=None
  89. # Menus:
  90. # 0: right-click without selection and dragging of shapes
  91. # 1: right-click with selection and dragging of shapes
  92. self.menus = (QtWidgets.QMenu(), QtWidgets.QMenu())
  93. # Set widget options.
  94. self.setMouseTracking(True)
  95. self.setFocusPolicy(QtCore.Qt.WheelFocus)
  96. self._ai_model = None
  97. def fillDrawing(self):
  98. return self._fill_drawing
  99. def setFillDrawing(self, value):
  100. self._fill_drawing = value
  101. @property
  102. def createMode(self):
  103. return self._createMode
  104. @createMode.setter
  105. def createMode(self, value):
  106. if value not in [
  107. "polygon",
  108. "rectangle",
  109. "circle",
  110. "line",
  111. "point",
  112. "linestrip",
  113. "ai_polygon",
  114. "ai_mask",
  115. ]:
  116. raise ValueError("Unsupported createMode: %s" % value)
  117. self._createMode = value
  118. def initializeAiModel(self,name,weight_path=None):
  119. if name not in [model.name for model in labelme.ai.MODELS]:
  120. raise ValueError("Unsupported ai model: %s" % name)
  121. model = [model for model in labelme.ai.MODELS if model.name == name][0]
  122. if self._ai_model is not None and self._ai_model.name == model.name:
  123. logger.debug("AI model is already initialized: %r" % model.name)
  124. else:
  125. logger.debug("Initializing AI model: %r" % model.name)
  126. self._ai_model = model(weight_path)
  127. if self.pixmap is None:
  128. logger.warning("Pixmap is not set yet")
  129. return
  130. self._ai_model.set_image(
  131. image=labelme.utils.img_qt_to_arr(self.pixmap.toImage())
  132. # image=self.pixmap.toImage()
  133. )
  134. def storeShapes(self):
  135. shapesBackup = []
  136. for shape in self.shapes:
  137. shapesBackup.append(shape.copy())
  138. if len(self.shapesBackups) > self.num_backups:
  139. self.shapesBackups = self.shapesBackups[-self.num_backups - 1 :]
  140. self.shapesBackups.append(shapesBackup)
  141. @property
  142. def isShapeRestorable(self):
  143. # We save the state AFTER each edit (not before) so for an
  144. # edit to be undoable, we expect the CURRENT and the PREVIOUS state
  145. # to be in the undo stack.
  146. if len(self.shapesBackups) < 2:
  147. return False
  148. return True
  149. def restoreShape(self):
  150. # This does _part_ of the job of restoring shapes.
  151. # The complete process is also done in app.py::undoShapeEdit
  152. # and app.py::loadShapes and our own Canvas::loadShapes function.
  153. if not self.isShapeRestorable:
  154. return
  155. print(f"shape is restorable")
  156. self.shapesBackups.pop() # latest
  157. # The application will eventually call Canvas.loadShapes which will
  158. # push this right back onto the stack.
  159. shapesBackup = self.shapesBackups.pop()
  160. self.shapes = shapesBackup
  161. self.selectedShapes = []
  162. for shape in self.shapes:
  163. shape.selected = False
  164. self.update()
  165. def enterEvent(self, ev):
  166. self.overrideCursor(self._cursor)
  167. def leaveEvent(self, ev):
  168. self.unHighlight()
  169. self.restoreCursor()
  170. def focusOutEvent(self, ev):
  171. self.restoreCursor()
  172. def isVisible(self, shape):
  173. return self.visible.get(shape, True)
  174. def drawing(self):
  175. return self.mode == self.CREATE
  176. def editing(self):
  177. return self.mode == self.EDIT
  178. def setEditing(self, value=True):
  179. self.mode = self.EDIT if value else self.CREATE
  180. if self.mode == self.EDIT:
  181. # CREATE -> EDIT
  182. self.repaint() # clear crosshair
  183. else:
  184. # EDIT -> CREATE
  185. self.unHighlight()
  186. self.deSelectShape()
  187. def unHighlight(self):
  188. if self.hShape:
  189. self.hShape.highlightClear()
  190. self.update()
  191. self.prevhShape = self.hShape
  192. self.prevhVertex = self.hVertex
  193. self.prevhEdge = self.hEdge
  194. self.hShape = self.hVertex = self.hEdge = None
  195. def selectedVertex(self):
  196. return self.hVertex is not None
  197. def selectedEdge(self):
  198. return self.hEdge is not None
  199. def mouseMoveEvent(self, ev):
  200. """Update line with last point and current coordinates."""
  201. try:
  202. if QT5:
  203. pos = self.transformPos(ev.localPos())
  204. else:
  205. pos = self.transformPos(ev.posF())
  206. except AttributeError:
  207. return
  208. self.mouseMoved.emit(pos)
  209. self.prevMovePoint = pos
  210. self.restoreCursor()
  211. is_shift_pressed = ev.modifiers() & QtCore.Qt.ShiftModifier
  212. # Polygon drawing.
  213. if self.drawing():
  214. if self.createMode in ["ai_polygon", "ai_mask"]:
  215. self.line.shape_type = "points"
  216. else:
  217. self.line.shape_type = self.createMode
  218. self.overrideCursor(CURSOR_DRAW)
  219. if not self.current:
  220. self.repaint() # draw crosshair
  221. return
  222. if self.outOfPixmap(pos):
  223. # Don't allow the user to draw outside the pixmap.
  224. # Project the point to the pixmap's edges.
  225. pos = self.intersectionPoint(self.current[-1], pos)
  226. elif (
  227. self.snapping
  228. and len(self.current) > 1
  229. and self.createMode == "polygon"
  230. and self.closeEnough(pos, self.current[0])
  231. ):
  232. # Attract line to starting point and
  233. # colorise to alert the user.
  234. pos = self.current[0]
  235. self.overrideCursor(CURSOR_POINT)
  236. self.current.highlightVertex(0, Shape.NEAR_VERTEX)
  237. if self.createMode in ["polygon", "linestrip"]:
  238. self.line.points = [self.current[-1], pos]
  239. self.line.point_labels = [1, 1]
  240. elif self.createMode in ["ai_polygon", "ai_mask"]:
  241. self.line.points = [self.current.points[-1], pos]
  242. self.line.point_labels = [
  243. self.current.point_labels[-1],
  244. 0 if is_shift_pressed else 1,
  245. ]
  246. elif self.createMode == "rectangle":
  247. self.line.points = [self.current[0], pos]
  248. self.line.point_labels = [1, 1]
  249. self.line.close()
  250. elif self.createMode == "circle":
  251. self.line.points = [self.current[0], pos]
  252. self.line.point_labels = [1, 1]
  253. self.line.shape_type = "circle"
  254. elif self.createMode == "line":
  255. self.line.points = [self.current[0], pos]
  256. self.line.point_labels = [1, 1]
  257. self.line.close()
  258. elif self.createMode == "point":
  259. self.line.points = [self.current[0]]
  260. self.line.point_labels = [1]
  261. self.line.close()
  262. assert len(self.line.points) == len(self.line.point_labels)
  263. self.repaint()
  264. self.current.highlightClear()
  265. return
  266. # Polygon copy moving.
  267. if QtCore.Qt.RightButton & ev.buttons():
  268. if self.selectedShapesCopy and self.prevPoint:
  269. self.overrideCursor(CURSOR_MOVE)
  270. self.boundedMoveShapes(self.selectedShapesCopy, pos)
  271. self.repaint()
  272. elif self.selectedShapes:
  273. self.selectedShapesCopy = [s.copy() for s in self.selectedShapes]
  274. self.repaint()
  275. return
  276. # Polygon/Vertex moving.
  277. if QtCore.Qt.LeftButton & ev.buttons():
  278. if self.selectedVertex():
  279. self.boundedMoveVertex(pos)
  280. self.repaint()
  281. self.movingShape = True
  282. elif self.selectedShapes and self.prevPoint:
  283. self.overrideCursor(CURSOR_MOVE)
  284. self.boundedMoveShapes(self.selectedShapes, pos)
  285. self.repaint()
  286. self.movingShape = True
  287. return
  288. # Just hovering over the canvas, 2 possibilities:
  289. # - Highlight shapes
  290. # - Highlight vertex
  291. # Update shape/vertex fill and tooltip value accordingly.
  292. self.setToolTip(self.tr("Image"))
  293. for shape in reversed([s for s in self.shapes if self.isVisible(s)]):
  294. # Look for a nearby vertex to highlight. If that fails,
  295. # check if we happen to be inside a shape.
  296. index = shape.nearestVertex(pos, self.epsilon / self.scale)
  297. index_edge = shape.nearestEdge(pos, self.epsilon / self.scale)
  298. if index is not None:
  299. if self.selectedVertex():
  300. self.hShape.highlightClear()
  301. self.prevhVertex = self.hVertex = index
  302. self.prevhShape = self.hShape = shape
  303. self.prevhEdge = self.hEdge
  304. self.hEdge = None
  305. shape.highlightVertex(index, shape.MOVE_VERTEX)
  306. self.overrideCursor(CURSOR_POINT)
  307. self.setToolTip(self.tr("Click & drag to move point"))
  308. self.setStatusTip(self.toolTip())
  309. self.update()
  310. break
  311. elif index_edge is not None and shape.canAddPoint():
  312. if self.selectedVertex():
  313. self.hShape.highlightClear()
  314. self.prevhVertex = self.hVertex
  315. self.hVertex = None
  316. self.prevhShape = self.hShape = shape
  317. self.prevhEdge = self.hEdge = index_edge
  318. self.overrideCursor(CURSOR_POINT)
  319. self.setToolTip(self.tr("Click to create point"))
  320. self.setStatusTip(self.toolTip())
  321. self.update()
  322. break
  323. elif len(shape.points)!=0 and shape.containsPoint(pos) :
  324. if self.selectedVertex():
  325. self.hShape.highlightClear()
  326. self.prevhVertex = self.hVertex
  327. self.hVertex = None
  328. self.prevhShape = self.hShape = shape
  329. self.prevhEdge = self.hEdge
  330. self.hEdge = None
  331. self.setToolTip(
  332. self.tr("Click & drag to move shape '%s'") % shape.label
  333. )
  334. self.setStatusTip(self.toolTip())
  335. self.overrideCursor(CURSOR_GRAB)
  336. self.update()
  337. break
  338. else: # Nothing found, clear highlights, reset state.
  339. self.unHighlight()
  340. self.vertexSelected.emit(self.hVertex is not None)
  341. def addPointToEdge(self):
  342. shape = self.prevhShape
  343. index = self.prevhEdge
  344. point = self.prevMovePoint
  345. if shape is None or index is None or point is None:
  346. return
  347. shape.insertPoint(index, point)
  348. shape.highlightVertex(index, shape.MOVE_VERTEX)
  349. self.hShape = shape
  350. self.hVertex = index
  351. self.hEdge = None
  352. self.movingShape = True
  353. def removeSelectedPoint(self):
  354. shape = self.prevhShape
  355. index = self.prevhVertex
  356. if shape is None or index is None:
  357. return
  358. shape.removePoint(index)
  359. shape.highlightClear()
  360. self.hShape = shape
  361. self.prevhVertex = None
  362. self.movingShape = True # Save changes
  363. def mousePressEvent(self, ev):
  364. if QT5:
  365. pos = self.transformPos(ev.localPos())
  366. else:
  367. pos = self.transformPos(ev.posF())
  368. is_shift_pressed = ev.modifiers() & QtCore.Qt.ShiftModifier
  369. if ev.button() == QtCore.Qt.LeftButton:
  370. if self.drawing():
  371. if self.current:
  372. # Add point to existing shape.
  373. if self.createMode == "polygon":
  374. self.current.addPoint(self.line[1])
  375. self.line[0] = self.current[-1]
  376. if self.current.isClosed():
  377. self.finalise()
  378. elif self.createMode in ["rectangle", "circle", "line"]:
  379. assert len(self.current.points) == 1
  380. self.current.points = self.line.points
  381. self.finalise()
  382. elif self.createMode == "linestrip":
  383. self.current.addPoint(self.line[1])
  384. self.line[0] = self.current[-1]
  385. if int(ev.modifiers()) == QtCore.Qt.ControlModifier:
  386. self.finalise()
  387. elif self.createMode in ["ai_polygon", "ai_mask"]:
  388. self.current.addPoint(
  389. self.line.points[1],
  390. label=self.line.point_labels[1],
  391. )
  392. self.line.points[0] = self.current.points[-1]
  393. self.line.point_labels[0] = self.current.point_labels[-1]
  394. if ev.modifiers() & QtCore.Qt.ControlModifier:
  395. self.finalise()
  396. elif not self.outOfPixmap(pos):
  397. # Create new shape.
  398. self.current = Shape(
  399. shape_type="points"
  400. if self.createMode in ["ai_polygon", "ai_mask"]
  401. else self.createMode
  402. )
  403. self.current.addPoint(pos, label=0 if is_shift_pressed else 1)
  404. if self.createMode == "point":
  405. self.finalise()
  406. elif (
  407. self.createMode in ["ai_polygon", "ai_mask"]
  408. and ev.modifiers() & QtCore.Qt.ControlModifier
  409. ):
  410. self.finalise()
  411. else:
  412. if self.createMode == "circle":
  413. self.current.shape_type = "circle"
  414. self.line.points = [pos, pos]
  415. if (
  416. self.createMode in ["ai_polygon", "ai_mask"]
  417. and is_shift_pressed
  418. ):
  419. self.line.point_labels = [0, 0]
  420. else:
  421. self.line.point_labels = [1, 1]
  422. self.setHiding()
  423. self.drawingPolygon.emit(True)
  424. self.update()
  425. elif self.editing():
  426. if self.selectedEdge():
  427. self.addPointToEdge()
  428. elif (
  429. self.selectedVertex()
  430. and int(ev.modifiers()) == QtCore.Qt.ShiftModifier
  431. ):
  432. # Delete point if: left-click + SHIFT on a point
  433. self.removeSelectedPoint()
  434. group_mode = int(ev.modifiers()) == QtCore.Qt.ControlModifier
  435. self.selectShapePoint(pos, multiple_selection_mode=group_mode)
  436. self.prevPoint = pos
  437. self.repaint()
  438. elif ev.button() == QtCore.Qt.RightButton and self.editing():
  439. group_mode = int(ev.modifiers()) == QtCore.Qt.ControlModifier
  440. if not self.selectedShapes or (
  441. self.hShape is not None and self.hShape not in self.selectedShapes
  442. ):
  443. self.selectShapePoint(pos, multiple_selection_mode=group_mode)
  444. self.repaint()
  445. self.prevPoint = pos
  446. def mouseReleaseEvent(self, ev):
  447. if ev.button() == QtCore.Qt.RightButton:
  448. menu = self.menus[len(self.selectedShapesCopy) > 0]
  449. self.restoreCursor()
  450. if not menu.exec_(self.mapToGlobal(ev.pos())) and self.selectedShapesCopy:
  451. # Cancel the move by deleting the shadow copy.
  452. self.selectedShapesCopy = []
  453. self.repaint()
  454. elif ev.button() == QtCore.Qt.LeftButton:
  455. if self.editing():
  456. if (
  457. self.hShape is not None
  458. and self.hShapeIsSelected
  459. and not self.movingShape
  460. ):
  461. self.selectionChanged.emit(
  462. [x for x in self.selectedShapes if x != self.hShape]
  463. )
  464. if self.movingShape and self.hShape:
  465. index = self.shapes.index(self.hShape)
  466. if self.shapesBackups[-1][index].points != self.shapes[index].points:
  467. self.storeShapes()
  468. self.shapeMoved.emit()
  469. self.movingShape = False
  470. def endMove(self, copy):
  471. assert self.selectedShapes and self.selectedShapesCopy
  472. assert len(self.selectedShapesCopy) == len(self.selectedShapes)
  473. if copy:
  474. for i, shape in enumerate(self.selectedShapesCopy):
  475. self.shapes.append(shape)
  476. self.selectedShapes[i].selected = False
  477. self.selectedShapes[i] = shape
  478. else:
  479. for i, shape in enumerate(self.selectedShapesCopy):
  480. self.selectedShapes[i].points = shape.points
  481. self.selectedShapesCopy = []
  482. self.repaint()
  483. self.storeShapes()
  484. return True
  485. def hideBackroundShapes(self, value):
  486. self.hideBackround = value
  487. if self.selectedShapes:
  488. # Only hide other shapes if there is a current selection.
  489. # Otherwise the user will not be able to select a shape.
  490. self.setHiding(True)
  491. self.update()
  492. def setHiding(self, enable=True):
  493. self._hideBackround = self.hideBackround if enable else False
  494. def canCloseShape(self):
  495. return self.drawing() and (
  496. (self.current and len(self.current) > 2)
  497. or self.createMode in ["ai_polygon", "ai_mask"]
  498. )
  499. def mouseDoubleClickEvent(self, ev):
  500. if self.double_click != "close":
  501. return
  502. if (
  503. self.createMode == "polygon" and self.canCloseShape()
  504. ) or self.createMode in ["ai_polygon", "ai_mask"]:
  505. self.finalise()
  506. def selectShapes(self, shapes):
  507. self.setHiding()
  508. self.selectionChanged.emit(shapes)
  509. self.update()
  510. def selectShapePoint(self, point, multiple_selection_mode):
  511. """Select the first shape created which contains this point."""
  512. if self.selectedVertex(): # A vertex is marked for selection.
  513. index, shape = self.hVertex, self.hShape
  514. shape.highlightVertex(index, shape.MOVE_VERTEX)
  515. else:
  516. for shape in reversed(self.shapes):
  517. if self.isVisible(shape) and shape.containsPoint(point):
  518. self.setHiding()
  519. if shape not in self.selectedShapes:
  520. if multiple_selection_mode:
  521. self.selectionChanged.emit(self.selectedShapes + [shape])
  522. else:
  523. self.selectionChanged.emit([shape])
  524. self.hShapeIsSelected = False
  525. else:
  526. self.hShapeIsSelected = True
  527. self.calculateOffsets(point)
  528. return
  529. self.deSelectShape()
  530. def calculateOffsets(self, point):
  531. left = self.pixmap.width() - 1
  532. right = 0
  533. top = self.pixmap.height() - 1
  534. bottom = 0
  535. for s in self.selectedShapes:
  536. rect = s.boundingRect()
  537. if rect.left() < left:
  538. left = rect.left()
  539. if rect.right() > right:
  540. right = rect.right()
  541. if rect.top() < top:
  542. top = rect.top()
  543. if rect.bottom() > bottom:
  544. bottom = rect.bottom()
  545. x1 = left - point.x()
  546. y1 = top - point.y()
  547. x2 = right - point.x()
  548. y2 = bottom - point.y()
  549. self.offsets = QtCore.QPointF(x1, y1), QtCore.QPointF(x2, y2)
  550. def boundedMoveVertex(self, pos):
  551. index, shape = self.hVertex, self.hShape
  552. point = shape[index]
  553. if self.outOfPixmap(pos):
  554. pos = self.intersectionPoint(point, pos)
  555. shape.moveVertexBy(index, pos - point)
  556. def boundedMoveShapes(self, shapes, pos):
  557. if self.outOfPixmap(pos):
  558. return False # No need to move
  559. o1 = pos + self.offsets[0]
  560. if self.outOfPixmap(o1):
  561. pos -= QtCore.QPointF(min(0, o1.x()), min(0, o1.y()))
  562. o2 = pos + self.offsets[1]
  563. if self.outOfPixmap(o2):
  564. pos += QtCore.QPointF(
  565. min(0, self.pixmap.width() - o2.x()),
  566. min(0, self.pixmap.height() - o2.y()),
  567. )
  568. # XXX: The next line tracks the new position of the cursor
  569. # relative to the shape, but also results in making it
  570. # a bit "shaky" when nearing the border and allows it to
  571. # go outside of the shape's area for some reason.
  572. # self.calculateOffsets(self.selectedShapes, pos)
  573. dp = pos - self.prevPoint
  574. if dp:
  575. for shape in shapes:
  576. shape.moveBy(dp)
  577. self.prevPoint = pos
  578. return True
  579. return False
  580. def deSelectShape(self):
  581. if self.selectedShapes:
  582. self.setHiding(False)
  583. self.selectionChanged.emit([])
  584. self.hShapeIsSelected = False
  585. self.update()
  586. def deleteSelected(self):
  587. deleted_shapes = []
  588. if self.selectedShapes:
  589. for shape in self.selectedShapes:
  590. self.shapes.remove(shape)
  591. deleted_shapes.append(shape)
  592. self.storeShapes()
  593. self.selectedShapes = []
  594. self.update()
  595. return deleted_shapes
  596. def deleteShape(self, shape):
  597. if shape in self.selectedShapes:
  598. self.selectedShapes.remove(shape)
  599. if shape in self.shapes:
  600. self.shapes.remove(shape)
  601. self.storeShapes()
  602. self.update()
  603. def duplicateSelectedShapes(self):
  604. if self.selectedShapes:
  605. self.selectedShapesCopy = [s.copy() for s in self.selectedShapes]
  606. self.boundedShiftShapes(self.selectedShapesCopy)
  607. self.endMove(copy=True)
  608. return self.selectedShapes
  609. def boundedShiftShapes(self, shapes):
  610. # Try to move in one direction, and if it fails in another.
  611. # Give up if both fail.
  612. point = shapes[0][0]
  613. offset = QtCore.QPointF(2.0, 2.0)
  614. self.offsets = QtCore.QPoint(), QtCore.QPoint()
  615. self.prevPoint = point
  616. if not self.boundedMoveShapes(shapes, point - offset):
  617. self.boundedMoveShapes(shapes, point + offset)
  618. def paintEvent(self, event):
  619. if not self.pixmap:
  620. return super(Canvas, self).paintEvent(event)
  621. p = self._painter
  622. p.begin(self)
  623. p.setRenderHint(QtGui.QPainter.Antialiasing)
  624. p.setRenderHint(QtGui.QPainter.HighQualityAntialiasing)
  625. p.setRenderHint(QtGui.QPainter.SmoothPixmapTransform)
  626. p.scale(self.scale, self.scale)
  627. p.translate(self.offsetToCenter())
  628. p.drawPixmap(0, 0, self.pixmap)
  629. # draw crosshair
  630. if (
  631. self._crosshair[self._createMode]
  632. and self.drawing()
  633. and self.prevMovePoint
  634. and not self.outOfPixmap(self.prevMovePoint)
  635. ):
  636. p.setPen(QtGui.QColor(0, 0, 0))
  637. p.drawLine(
  638. 0,
  639. int(self.prevMovePoint.y()),
  640. self.width() - 1,
  641. int(self.prevMovePoint.y()),
  642. )
  643. p.drawLine(
  644. int(self.prevMovePoint.x()),
  645. 0,
  646. int(self.prevMovePoint.x()),
  647. self.height() - 1,
  648. )
  649. Shape.scale = self.scale
  650. for shape in self.shapes:
  651. if (shape.selected or not self._hideBackround) and self.isVisible(shape):
  652. shape.fill = shape.selected or shape == self.hShape
  653. shape.paint(p)
  654. if self.current:
  655. self.current.paint(p)
  656. assert len(self.line.points) == len(self.line.point_labels)
  657. self.line.paint(p)
  658. if self.selectedShapesCopy:
  659. for s in self.selectedShapesCopy:
  660. s.paint(p)
  661. # if(self.draw_pred and self.current is not None):
  662. # print("pred mode on")
  663. # for bbox_points in self.pred_bbox_points:
  664. # drawing_shape = self.current.copy()
  665. # drawing_shape.setShapeRefined(
  666. # shape_type="polygon",
  667. # points=[QtCore.QPointF(point[0], point[1]) for point in bbox_points],
  668. # point_labels=[1]*len(bbox_points)
  669. # )
  670. # drawing_shape.fill = self.fillDrawing()
  671. # drawing_shape.selected = True
  672. # drawing_shape.paint(p)
  673. # self.draw_pred=False
  674. if (
  675. self.fillDrawing()
  676. and self.createMode == "polygon"
  677. and self.current is not None
  678. and len(self.current.points) >= 2
  679. ):
  680. drawing_shape = self.current.copy()
  681. if drawing_shape.fill_color.getRgb()[3] == 0:
  682. logger.warning(
  683. "fill_drawing=true, but fill_color is transparent,"
  684. " so forcing to be opaque."
  685. )
  686. drawing_shape.fill_color.setAlpha(64)
  687. drawing_shape.addPoint(self.line[1])
  688. drawing_shape.fill = True
  689. drawing_shape.paint(p)
  690. elif self.createMode == "ai_polygon" and self.current is not None:
  691. drawing_shape = self.current.copy()
  692. drawing_shape.addPoint(
  693. point=self.line.points[1],
  694. label=self.line.point_labels[1],
  695. )
  696. points = self._ai_model.predict_polygon_from_points(
  697. points=[[point.x(), point.y()] for point in drawing_shape.points],
  698. point_labels=drawing_shape.point_labels,
  699. )
  700. if len(points) > 2:
  701. drawing_shape.setShapeRefined(
  702. shape_type="polygon",
  703. points=[QtCore.QPointF(point[0], point[1]) for point in points],
  704. point_labels=[1] * len(points),
  705. )
  706. drawing_shape.fill = self.fillDrawing()
  707. drawing_shape.selected = True
  708. drawing_shape.paint(p)
  709. elif self.createMode == "ai_mask" and self.current is not None:
  710. drawing_shape = self.current.copy()
  711. drawing_shape.addPoint(
  712. point=self.line.points[1],
  713. label=self.line.point_labels[1],
  714. )
  715. mask = self._ai_model.predict_mask_from_points(
  716. points=[[point.x(), point.y()] for point in drawing_shape.points],
  717. point_labels=drawing_shape.point_labels,
  718. )
  719. y1, x1, y2, x2 = imgviz.instances.masks_to_bboxes([mask])[0].astype(int)
  720. drawing_shape.setShapeRefined(
  721. shape_type="mask",
  722. points=[QtCore.QPointF(x1, y1), QtCore.QPointF(x2, y2)],
  723. point_labels=[1, 1],
  724. mask=mask[y1 : y2 + 1, x1 : x2 + 1],
  725. )
  726. drawing_shape.selected = True
  727. drawing_shape.paint(p)
  728. p.end()
  729. def transformPos(self, point):
  730. """Convert from widget-logical coordinates to painter-logical ones."""
  731. return point / self.scale - self.offsetToCenter()
  732. def offsetToCenter(self):
  733. s = self.scale
  734. area = super(Canvas, self).size()
  735. w, h = self.pixmap.width() * s, self.pixmap.height() * s
  736. aw, ah = area.width(), area.height()
  737. x = (aw - w) / (2 * s) if aw > w else 0
  738. y = (ah - h) / (2 * s) if ah > h else 0
  739. return QtCore.QPointF(x, y)
  740. def outOfPixmap(self, p):
  741. w, h = self.pixmap.width(), self.pixmap.height()
  742. return not (0 <= p.x() <= w - 1 and 0 <= p.y() <= h - 1)
  743. def finalise(self):
  744. if(self.current is None):
  745. return
  746. if self.createMode == "ai_polygon":
  747. # convert points to polygon by an AI model
  748. assert self.current.shape_type == "points"
  749. points = self._ai_model.predict_polygon_from_points(
  750. points=[[point.x(), point.y()] for point in self.current.points],
  751. point_labels=self.current.point_labels,
  752. )
  753. self.current.setShapeRefined(
  754. points=[QtCore.QPointF(point[0], point[1]) for point in points],
  755. point_labels=[1] * len(points),
  756. shape_type="polygon",
  757. )
  758. elif self.createMode == "ai_mask":
  759. # convert points to mask by an AI model
  760. assert self.current.shape_type == "points"
  761. mask = self._ai_model.predict_mask_from_points(
  762. points=[[point.x(), point.y()] for point in self.current.points],
  763. point_labels=self.current.point_labels,
  764. )
  765. y1, x1, y2, x2 = imgviz.instances.masks_to_bboxes([mask])[0].astype(int)
  766. self.current.setShapeRefined(
  767. shape_type="mask",
  768. points=[QtCore.QPointF(x1, y1), QtCore.QPointF(x2, y2)],
  769. point_labels=[1, 1],
  770. mask=mask[y1 : y2 + 1, x1 : x2 + 1],
  771. )
  772. elif self.pred_bbox_points is not None and self.draw_pred:
  773. print("pred mode on")
  774. current_copy=self.current.copy()
  775. for bbox_point in self.pred_bbox_points:
  776. drawing_shape=current_copy.copy()
  777. drawing_shape.setShapeRefined(
  778. shape_type="polygon",
  779. points=[QtCore.QPointF(point[0], point[1]) for point in bbox_point],
  780. point_labels=[1]*len(bbox_point)
  781. )
  782. drawing_shape.close()
  783. self.shapes.append(drawing_shape)
  784. self.storeShapes()
  785. self.update()
  786. self.newShape.emit()
  787. current_copy.close()
  788. current_copy=None
  789. if(self.current):
  790. self.current.close()
  791. self.current = None
  792. self.setHiding(False)
  793. self.draw_pred=False
  794. return
  795. self.current.close()
  796. self.shapes.append(self.current)
  797. self.storeShapes()
  798. self.current = None
  799. self.setHiding(False)
  800. self.newShape.emit()
  801. self.update()
  802. def closeEnough(self, p1, p2):
  803. # d = distance(p1 - p2)
  804. # m = (p1-p2).manhattanLength()
  805. # print "d %.2f, m %d, %.2f" % (d, m, d - m)
  806. # divide by scale to allow more precision when zoomed in
  807. return labelme.utils.distance(p1 - p2) < (self.epsilon / self.scale)
  808. def intersectionPoint(self, p1, p2):
  809. # Cycle through each image edge in clockwise fashion,
  810. # and find the one intersecting the current line segment.
  811. # http://paulbourke.net/geometry/lineline2d/
  812. size = self.pixmap.size()
  813. points = [
  814. (0, 0),
  815. (size.width() - 1, 0),
  816. (size.width() - 1, size.height() - 1),
  817. (0, size.height() - 1),
  818. ]
  819. # x1, y1 should be in the pixmap, x2, y2 should be out of the pixmap
  820. x1 = min(max(p1.x(), 0), size.width() - 1)
  821. y1 = min(max(p1.y(), 0), size.height() - 1)
  822. x2, y2 = p2.x(), p2.y()
  823. d, i, (x, y) = min(self.intersectingEdges((x1, y1), (x2, y2), points))
  824. x3, y3 = points[i]
  825. x4, y4 = points[(i + 1) % 4]
  826. if (x, y) == (x1, y1):
  827. # Handle cases where previous point is on one of the edges.
  828. if x3 == x4:
  829. return QtCore.QPointF(x3, min(max(0, y2), max(y3, y4)))
  830. else: # y3 == y4
  831. return QtCore.QPointF(min(max(0, x2), max(x3, x4)), y3)
  832. return QtCore.QPointF(x, y)
  833. def intersectingEdges(self, point1, point2, points):
  834. """Find intersecting edges.
  835. For each edge formed by `points', yield the intersection
  836. with the line segment `(x1,y1) - (x2,y2)`, if it exists.
  837. Also return the distance of `(x2,y2)' to the middle of the
  838. edge along with its index, so that the one closest can be chosen.
  839. """
  840. (x1, y1) = point1
  841. (x2, y2) = point2
  842. for i in range(4):
  843. x3, y3 = points[i]
  844. x4, y4 = points[(i + 1) % 4]
  845. denom = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1)
  846. nua = (x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)
  847. nub = (x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)
  848. if denom == 0:
  849. # This covers two cases:
  850. # nua == nub == 0: Coincident
  851. # otherwise: Parallel
  852. continue
  853. ua, ub = nua / denom, nub / denom
  854. if 0 <= ua <= 1 and 0 <= ub <= 1:
  855. x = x1 + ua * (x2 - x1)
  856. y = y1 + ua * (y2 - y1)
  857. m = QtCore.QPointF((x3 + x4) / 2, (y3 + y4) / 2)
  858. d = labelme.utils.distance(m - QtCore.QPointF(x2, y2))
  859. yield d, i, (x, y)
  860. # These two, along with a call to adjustSize are required for the
  861. # scroll area.
  862. def sizeHint(self):
  863. return self.minimumSizeHint()
  864. def minimumSizeHint(self):
  865. if self.pixmap:
  866. return self.scale * self.pixmap.size()
  867. return super(Canvas, self).minimumSizeHint()
  868. def wheelEvent(self, ev):
  869. if QT5:
  870. mods = ev.modifiers()
  871. delta = ev.angleDelta()
  872. if QtCore.Qt.ControlModifier == int(mods):
  873. # with Ctrl/Command key
  874. # zoom
  875. self.zoomRequest.emit(delta.y(), ev.pos())
  876. else:
  877. # scroll
  878. self.scrollRequest.emit(delta.x(), QtCore.Qt.Horizontal)
  879. self.scrollRequest.emit(delta.y(), QtCore.Qt.Vertical)
  880. else:
  881. if ev.orientation() == QtCore.Qt.Vertical:
  882. mods = ev.modifiers()
  883. if QtCore.Qt.ControlModifier == int(mods):
  884. # with Ctrl/Command key
  885. self.zoomRequest.emit(ev.delta(), ev.pos())
  886. else:
  887. self.scrollRequest.emit(
  888. ev.delta(),
  889. QtCore.Qt.Horizontal
  890. if (QtCore.Qt.ShiftModifier == int(mods))
  891. else QtCore.Qt.Vertical,
  892. )
  893. else:
  894. self.scrollRequest.emit(ev.delta(), QtCore.Qt.Horizontal)
  895. ev.accept()
  896. def moveByKeyboard(self, offset):
  897. if self.selectedShapes:
  898. self.boundedMoveShapes(self.selectedShapes, self.prevPoint + offset)
  899. self.repaint()
  900. self.movingShape = True
  901. def keyPressEvent(self, ev):
  902. modifiers = ev.modifiers()
  903. key = ev.key()
  904. if self.drawing():
  905. if key == QtCore.Qt.Key_Escape and self.current:
  906. self.current = None
  907. self.drawingPolygon.emit(False)
  908. self.update()
  909. elif key == QtCore.Qt.Key_Return and self.canCloseShape():
  910. self.finalise()
  911. elif modifiers == QtCore.Qt.AltModifier:
  912. self.snapping = False
  913. elif key == QtCore.Qt.Key_V:
  914. if self._ai_model is None and self.model_path is not None:
  915. print(f"model got initialized")
  916. self.initializeAiModel("BarcodePredict(ov)",self.model_path)
  917. self.current = Shape(
  918. shape_type="points"
  919. if self.createMode in ["ai_polygon", "ai_mask"]
  920. else self.createMode
  921. )
  922. if self._ai_model is not None:
  923. self.pred_bbox_points=self._ai_model.predict_polygon_from_points()
  924. self.draw_pred=True
  925. self.finalise()
  926. elif self.editing():
  927. if key == QtCore.Qt.Key_Up:
  928. self.moveByKeyboard(QtCore.QPointF(0.0, -MOVE_SPEED))
  929. elif key == QtCore.Qt.Key_Down:
  930. self.moveByKeyboard(QtCore.QPointF(0.0, MOVE_SPEED))
  931. elif key == QtCore.Qt.Key_Left:
  932. self.moveByKeyboard(QtCore.QPointF(-MOVE_SPEED, 0.0))
  933. elif key == QtCore.Qt.Key_Right:
  934. self.moveByKeyboard(QtCore.QPointF(MOVE_SPEED, 0.0))
  935. def keyReleaseEvent(self, ev):
  936. modifiers = ev.modifiers()
  937. if self.drawing():
  938. if int(modifiers) == 0:
  939. self.snapping = True
  940. elif self.editing():
  941. if self.movingShape and self.selectedShapes:
  942. index = self.shapes.index(self.selectedShapes[0])
  943. if self.shapesBackups[-1][index].points != self.shapes[index].points:
  944. self.storeShapes()
  945. self.shapeMoved.emit()
  946. self.movingShape = False
  947. def setLastLabel(self, text, flags):
  948. assert text
  949. if(self.shapes is None or len(self.shapes)==0):
  950. return
  951. self.shapes[-1].label = text
  952. self.shapes[-1].flags = flags
  953. self.shapesBackups.pop()
  954. self.storeShapes()
  955. return self.shapes[-1]
  956. def undoLastLine(self):
  957. if(self.shapes is None or len(self.shapes)==0):
  958. return
  959. self.current = self.shapes.pop()
  960. self.current.setOpen()
  961. self.current.restoreShapeRaw()
  962. if self.createMode in ["polygon", "linestrip"] and self.draw_pred is False:
  963. self.line.points = [self.current[-1], self.current[0]]
  964. elif self.createMode in ["rectangle", "line", "circle"]:
  965. self.current.points = self.current.points[0:1]
  966. elif self.createMode == "point":
  967. self.current = None
  968. self.drawingPolygon.emit(True)
  969. def undoLastPoint(self):
  970. if not self.current or self.current.isClosed():
  971. return
  972. self.current.popPoint()
  973. if len(self.current) > 0:
  974. self.line[0] = self.current[-1]
  975. else:
  976. self.current = None
  977. self.drawingPolygon.emit(False)
  978. self.update()
  979. def loadPixmap(self, pixmap, clear_shapes=True):
  980. self.pixmap = pixmap
  981. if self._ai_model:
  982. self._ai_model.set_image(
  983. image=labelme.utils.img_qt_to_arr(self.pixmap.toImage())
  984. )
  985. if clear_shapes:
  986. self.shapes = []
  987. self.update()
  988. def loadShapes(self, shapes, replace=True):
  989. if replace:
  990. self.shapes = list(shapes)
  991. else:
  992. self.shapes.extend(shapes)
  993. self.storeShapes()
  994. self.current = None
  995. self.hShape = None
  996. self.hVertex = None
  997. self.hEdge = None
  998. self.update()
  999. def setShapeVisible(self, shape, value):
  1000. self.visible[shape] = value
  1001. self.update()
  1002. def overrideCursor(self, cursor):
  1003. self.restoreCursor()
  1004. self._cursor = cursor
  1005. QtWidgets.QApplication.setOverrideCursor(cursor)
  1006. def restoreCursor(self):
  1007. QtWidgets.QApplication.restoreOverrideCursor()
  1008. def resetState(self):
  1009. self.restoreCursor()
  1010. self.pixmap = None
  1011. self.shapesBackups = []
  1012. self.update()