text: from enum import Enum
  from functools import partial
  from PyQt5 import QtWidgets, QtGui, QtCore
  
  
  class GripItem(QtWidgets.QGraphicsPathItem):
      circle = QtGui.QPainterPath()
      circle.addEllipse(QtCore.QRectF(-10, -10, 20, 20))
      square = QtGui.QPainterPath()
      square.addRect(QtCore.QRectF(-15, -15, 30, 30))
  
      def __init__(self, annotation_item, index):
          super(GripItem, self).__init__()
          self.m_annotation_item = annotation_item
          self.m_index = index
  
          self.setPath(GripItem.circle)
          self.setBrush(QtGui.QColor("green"))
          self.setPen(QtGui.QPen(QtGui.QColor("green"), 2))
          self.setFlag(QtWidgets.QGraphicsItem.ItemIsSelectable, True)
          self.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable, True)
          self.setFlag(QtWidgets.QGraphicsItem.ItemSendsGeometryChanges, True)
          self.setAcceptHoverEvents(True)
          self.setZValue(11)
          self.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor))
  
      def hoverEnterEvent(self, event):
          self.setPath(GripItem.square)
          self.setBrush(QtGui.QColor("red"))
          super(GripItem, self).hoverEnterEvent(event)
  
      def hoverLeaveEvent(self, event):
          self.setPath(GripItem.circle)
          self.setBrush(QtGui.QColor("green"))
          super(GripItem, self).hoverLeaveEvent(event)
  
      def mouseReleaseEvent(self, event):
          self.setSelected(False)
          super(GripItem, self).mouseReleaseEvent(event)
  
      def itemChange(self, change, value):
          if change == QtWidgets.QGraphicsItem.ItemPositionChange and self.isEnabled():
              self.m_annotation_item.movePoint(self.m_index, value)
          return super(GripItem, self).itemChange(change, value)
  
  
  class PolygonAnnotation(QtWidgets.QGraphicsPolygonItem):
      def __init__(self, parent=None):
          super(PolygonAnnotation, self).__init__(parent)
          self.m_points = []
          self.setZValue(10)
          self.setPen(QtGui.QPen(QtGui.QColor("green"), 2))
          self.setAcceptHoverEvents(True)
  
          self.setFlag(QtWidgets.QGraphicsItem.ItemIsSelectable, True)
          self.setFlag(QtWidgets.QGraphicsItem.ItemIsMovable, True)
          self.setFlag(QtWidgets.QGraphicsItem.ItemSendsGeometryChanges, True)
  
          self.setCursor(QtGui.QCursor(QtCore.Qt.PointingHandCursor))
  
          self.m_items = []
  
      def number_of_points(self):
          return len(self.m_items)
  
      def addPoint(self, p):
          self.m_points.append(p)
          self.setPolygon(QtGui.QPolygonF(self.m_points))
          item = GripItem(self, len(self.m_points) - 1)
          self.scene().addItem(item)
          self.m_items.append(item)
          item.setPos(p)
  
      def removeLastPoint(self):
          if self.m_points:
              self.m_points.pop()
              self.setPolygon(QtGui.QPolygonF(self.m_points))
              it = self.m_items.pop()
              self.scene().removeItem(it)
              del it
  
      def movePoint(self, i, p):
          if 0 <= i < len(self.m_points):
              self.m_points[i] = self.mapFromScene(p)
              self.setPolygon(QtGui.QPolygonF(self.m_points))
  
      def move_item(self, index, pos):
          if 0 <= index < len(self.m_items):
              item = self.m_items[index]
              item.setEnabled(False)
              item.setPos(pos)
              item.setEnabled(True)
  
      def itemChange(self, change, value):
          if change == QtWidgets.QGraphicsItem.ItemPositionHasChanged:
              for i, point in enumerate(self.m_points):
                  self.move_item(i, self.mapToScene(point))
          return super(PolygonAnnotation, self).itemChange(change, value)
  
      def hoverEnterEvent(self, event):
          self.setBrush(QtGui.QColor(255, 0, 0, 100))
          super(PolygonAnnotation, self).hoverEnterEvent(event)
  
      def hoverLeaveEvent(self, event):
          self.setBrush(QtGui.QBrush(QtCore.Qt.NoBrush))
          super(PolygonAnnotation, self).hoverLeaveEvent(event)
  
  
  class Instructions(Enum):
      No_Instruction = 0
      Polygon_Instruction = 1
  
  
  class AnnotationScene(QtWidgets.QGraphicsScene):
      def __init__(self, parent=None):
          super(AnnotationScene, self).__init__(parent)
          self.image_item = QtWidgets.QGraphicsPixmapItem()
          self.image_item.setCursor(QtGui.QCursor(QtCore.Qt.CrossCursor))
          self.addItem(self.image_item)
          self.current_instruction = Instructions.No_Instruction
  
      def load_image(self, filename):
          self.image_item.setPixmap(QtGui.QPixmap(filename))
          self.setSceneRect(self.image_item.boundingRect())
  
      def setCurrentInstruction(self, instruction):
          self.current_instruction = instruction
          self.polygon_item = PolygonAnnotation()
          self.addItem(self.polygon_item)
  
      def mousePressEvent(self, event):
          if self.current_instruction == Instructions.Polygon_Instruction:
              self.polygon_item.removeLastPoint()
              self.polygon_item.addPoint(event.scenePos())
              # movable element
              self.polygon_item.addPoint(event.scenePos())
          super(AnnotationScene, self).mousePressEvent(event)
  
      def mouseMoveEvent(self, event):
          if self.current_instruction == Instructions.Polygon_Instruction:
              self.polygon_item.movePoint(self.polygon_item.number_of_points()-1, event.scenePos())
          super(AnnotationScene, self).mouseMoveEvent(event)
  
  
  class AnnotationView(QtWidgets.QGraphicsView):
      factor = 2.0
  
      def __init__(self, parent=None):
          super(AnnotationView, self).__init__(parent)
          self.setRenderHints(QtGui.QPainter.Antialiasing | QtGui.QPainter.SmoothPixmapTransform)
          self.setMouseTracking(True)
          QtWidgets.QShortcut(QtGui.QKeySequence.ZoomIn, self, activated=self.zoomIn)
          QtWidgets.QShortcut(QtGui.QKeySequence.ZoomOut, self, activated=self.zoomOut)
  
      @QtCore.pyqtSlot()
      def zoomIn(self):
          self.zoom(AnnotationView.factor)
  
      @QtCore.pyqtSlot()
      def zoomOut(self):
          self.zoom(1 / AnnotationView.factor)
  
      def zoom(self, f):
          self.scale(f, f)
          if self.scene() is not None:
              self.centerOn(self.scene().image_item)
  
  
  class AnnotationWindow(QtWidgets.QMainWindow):
      def __init__(self, parent=None):
          super(AnnotationWindow, self).__init__(parent)
          self.m_view = AnnotationView()
          self.m_scene = AnnotationScene(self)
          self.m_view.setScene(self.m_scene)
  
          self.setCentralWidget(self.m_view)
          self.create_menus()
  
          QtWidgets.QShortcut(QtCore.Qt.Key_Escape, self, activated=partial(self.m_scene.setCurrentInstruction, Instructions.No_Instruction))
  
      def create_menus(self):
          menu_file = self.menuBar().addMenu("File")
          load_image_action = menu_file.addAction("&Load Image")
          load_image_action.triggered.connect(self.load_image)
  
          menu_instructions = self.menuBar().addMenu("Intructions")
          polygon_action = menu_instructions.addAction("Polygon")
          polygon_action.triggered.connect(partial(self.m_scene.setCurrentInstruction, Instructions.Polygon_Instruction))
  
      @QtCore.pyqtSlot()
      def load_image(self):
          filename, _ = QtWidgets.QFileDialog.getOpenFileName(self,
              "Open Image",
              QtCore.QStandardPaths.writableLocation(QtCore.QStandardPaths.PicturesLocation), #QtCore.QDir.currentPath(),
              "Image Files (*.png *.jpg *.bmp)")
          if filename:
              self.m_scene.load_image(filename)
              self.m_view.fitInView(self.m_scene.image_item, QtCore.Qt.KeepAspectRatio)
              self.m_view.centerOn(self.m_scene.image_item)
  
  
  if __name__ == '__main__':
      import sys
  
      app = QtWidgets.QApplication(sys.argv)
      w = AnnotationWindow()
      w.resize(640, 480)
      w.show()
      sys.exit(app.exec_())