Browse Source

Faster undo, and undo with Autosave

femianjc 4 năm trước cách đây
mục cha
commit
b8ed7ce081
4 tập tin đã thay đổi với 43 bổ sung11 xóa
  1. 4 1
      labelme/app.py
  2. 2 0
      labelme/config/default_config.yaml
  3. 18 2
      labelme/shape.py
  4. 19 8
      labelme/widgets/canvas.py

+ 4 - 1
labelme/app.py

@@ -168,6 +168,7 @@ class MainWindow(QtWidgets.QMainWindow):
         self.canvas = self.labelList.canvas = Canvas(
             epsilon=self._config["epsilon"],
             double_click=self._config["canvas"]["double_click"],
+            num_backups=self._config["canvas"]["num_backups"],
         )
         self.canvas.zoomRequest.connect(self.zoomRequest)
 
@@ -830,6 +831,9 @@ class MainWindow(QtWidgets.QMainWindow):
         utils.addActions(self.menus.edit, actions + self.actions.editMenu)
 
     def setDirty(self):
+        # Even if we autosave the file, we keep the ability to undo
+        self.actions.undo.setEnabled(self.canvas.isShapeRestorable)
+
         if self._config["auto_save"] or self.actions.saveAuto.isChecked():
             label_file = osp.splitext(self.imagePath)[0] + ".json"
             if self.output_dir:
@@ -839,7 +843,6 @@ class MainWindow(QtWidgets.QMainWindow):
             return
         self.dirty = True
         self.actions.save.setEnabled(True)
-        self.actions.undo.setEnabled(self.canvas.isShapeRestorable)
         title = __appname__
         if self.filename is not None:
             title = "{} - {}*".format(title, self.filename)

+ 2 - 0
labelme/config/default_config.yaml

@@ -64,6 +64,8 @@ canvas:
   # None: do nothing
   # close: close polygon
   double_click: close
+  # The max number of edits we can undo
+  num_backups: 10
 
 shortcuts:
   close: Ctrl+W

+ 18 - 2
labelme/shape.py

@@ -21,9 +21,17 @@ DEFAULT_HVERTEX_FILL_COLOR = QtGui.QColor(255, 255, 255, 255)  # hovering
 
 class Shape(object):
 
-    P_SQUARE, P_ROUND = 0, 1
+    # Render handles as squares
+    P_SQUARE = 0
 
-    MOVE_VERTEX, NEAR_VERTEX = 0, 1
+    # Render handles as circles
+    P_ROUND = 1
+
+    # Flag for the handles we would move if dragging
+    MOVE_VERTEX = 0
+
+    # Flag for all other handles on the curent shape
+    NEAR_VERTEX = 1
 
     # The following class variables influence the drawing of all shape objects.
     line_color = DEFAULT_LINE_COLOR
@@ -258,10 +266,18 @@ class Shape(object):
         self.points[i] = self.points[i] + offset
 
     def highlightVertex(self, i, action):
+        """Highlight a vertex appropriately based on the current action
+
+        Args:
+            i (int): The vertex index
+            action (int): The action
+            (see Shape.NEAR_VERTEX and Shape.MOVE_VERTEX)
+        """
         self._highlightIndex = i
         self._highlightMode = action
 
     def highlightClear(self):
+        """Clear the highlighted point"""
         self._highlightIndex = None
 
     def copy(self):

+ 19 - 8
labelme/widgets/canvas.py

@@ -29,6 +29,7 @@ class Canvas(QtWidgets.QWidget):
     edgeSelected = QtCore.Signal(bool, object)
     vertexSelected = QtCore.Signal(bool)
 
+    CREATE, EDIT = 0, 1
     CREATE, EDIT = 0, 1
 
     # polygon, rectangle, line, or point
@@ -45,6 +46,7 @@ class Canvas(QtWidgets.QWidget):
                     self.double_click
                 )
             )
+        self.num_backups = kwargs.pop("num_backups", 10)
         super(Canvas, self).__init__(*args, **kwargs)
         # Initialise local state.
         self.mode = self.EDIT
@@ -111,26 +113,35 @@ class Canvas(QtWidgets.QWidget):
         shapesBackup = []
         for shape in self.shapes:
             shapesBackup.append(shape.copy())
-        if len(self.shapesBackups) >= 10:
-            self.shapesBackups = self.shapesBackups[-9:]
+        if len(self.shapesBackups) > self.num_backups:
+            self.shapesBackups = self.shapesBackups[-self.num_backups - 1 :]
         self.shapesBackups.append(shapesBackup)
 
     @property
     def isShapeRestorable(self):
+        # We save the state AFTER each edit (not before) so for an
+        # edit to be undoable, we expect the CURRENT and the PREVIOUS state
+        # to be in the undo stack.
         if len(self.shapesBackups) < 2:
             return False
         return True
 
     def restoreShape(self):
+        # This does _part_ of the job of restoring shapes.
+        # The complete process is also done in app.py::undoShapeEdit
+        # and app.py::loadShapes and our own Canvas::loadShapes function.
         if not self.isShapeRestorable:
             return
         self.shapesBackups.pop()  # latest
+
+        # The application will eventually call Canvas.loadShapes which will
+        # push this right back onto the stack.
         shapesBackup = self.shapesBackups.pop()
         self.shapes = shapesBackup
         self.selectedShapes = []
         for shape in self.shapes:
             shape.selected = False
-        self.repaint()
+        self.update()
 
     def enterEvent(self, ev):
         self.overrideCursor(self._cursor)
@@ -423,7 +434,7 @@ class Canvas(QtWidgets.QWidget):
             # Only hide other shapes if there is a current selection.
             # Otherwise the user will not be able to select a shape.
             self.setHiding(True)
-            self.repaint()
+            self.update()
 
     def setHiding(self, enable=True):
         self._hideBackround = self.hideBackround if enable else False
@@ -756,13 +767,13 @@ class Canvas(QtWidgets.QWidget):
         else:
             self.current = None
             self.drawingPolygon.emit(False)
-        self.repaint()
+        self.update()
 
     def loadPixmap(self, pixmap, clear_shapes=True):
         self.pixmap = pixmap
         if clear_shapes:
             self.shapes = []
-        self.repaint()
+        self.update()
 
     def loadShapes(self, shapes, replace=True):
         if replace:
@@ -774,11 +785,11 @@ class Canvas(QtWidgets.QWidget):
         self.hShape = None
         self.hVertex = None
         self.hEdge = None
-        self.repaint()
+        self.update()
 
     def setShapeVisible(self, shape, value):
         self.visible[shape] = value
-        self.repaint()
+        self.update()
 
     def overrideCursor(self, cursor):
         self.restoreCursor()