canvas.py 52 KB

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