فهرست منبع

Bound shape moves inside pixmap area

Bound movement of shapes inside the pixmap area. Movement could be a bit
more accurate, but it currently breaks bounding.

Refactor some of the shape and main window code accordingly.
Michael Pitidis 13 سال پیش
والد
کامیت
c7471ecb32
3فایلهای تغییر یافته به همراه101 افزوده شده و 93 حذف شده
  1. 62 43
      canvas.py
  2. 18 27
      labelme.py
  3. 21 23
      shape.py

+ 62 - 43
canvas.py

@@ -26,15 +26,15 @@ class Canvas(QWidget):
         self.lineColor = QColor(0, 0, 255)
         self.line = Shape(line_color=self.lineColor)
         self.mouseButtonIsPressed=False #when it is true and shape is selected , move the shape with the mouse move event
-        self.prevPoint=QPoint()
+        self.prevPoint = QPointF()
+        self.offsets = QPointF(), QPointF()
         self.scale = 1.0
         self.pixmap = None
         self.hideShapesWhenNew=False
         # Set widget options.
         self.setMouseTracking(True)
         self.setFocusPolicy(Qt.WheelFocus)
-        
-    
+
     def editing(self):
         return self.mode == self.EDIT
 
@@ -43,32 +43,27 @@ class Canvas(QWidget):
 
     def mouseMoveEvent(self, ev):
         """Update line with last point and current coordinates."""
-        if ev.buttons() & Qt.RightButton:
+        pos = self.transformPos(ev.posF())
+
+        # Polygon copy moving.
+        if ev.button() == Qt.RightButton:
             if self.selectedShapeCopy:
                 if self.prevPoint:
-                    point=QPoint(self.prevPoint)
-                    dx= ev.x()-point.x()
-                    dy=ev.y()-point.y()
-                    self.selectedShapeCopy.moveBy(dx,dy)
+                    self.selectedShapeCopy.moveBy(pos - self.prevPoint)
                     self.repaint()
-                self.prevPoint=ev.pos()
+                self.prevPoint = pos
             elif self.selectedShape:
-                newShape=Shape()
-                for point in self.selectedShape.points:
-                    newShape.addPoint(point)
-                self.selectedShapeCopy=newShape
-                self.selectedShapeCopy.fill=True
+                self.selectedShapeCopy = self.selectedShape.copy()
                 self.repaint()
             return
 
-        pos = self.transformPos(ev.posF())
         # Polygon drawing.
         if self.current and self.editing():
             color = self.lineColor
             if self.outOfPixmap(pos):
                 # Don't allow the user to draw outside the pixmap.
                 # Project the point to the pixmap's edges.
-                pos = self.intersectionPoint(pos)
+                pos = self.intersectionPoint(self.current[-1], pos)
             elif len(self.current) > 1 and self.closeEnough(pos, self.current[0]):
                 # Attract line to starting point and colorise to alert the user:
                 # TODO: I would also like to highlight the pixel somehow.
@@ -77,12 +72,10 @@ class Canvas(QWidget):
             self.line[1] = pos
             self.line.line_color = color
             self.repaint()
+
+        # Polygon moving.
         elif self.selectedShape and self.prevPoint:
-            prev = self.prevPoint
-            dx = pos.x() - prev.x()
-            dy = pos.y() - prev.y()
-            self.selectedShape.moveBy(dx, dy)
-            self.prevPoint = pos
+            self.boundedMoveShape(pos)
             self.repaint()
 
     def mousePressEvent(self, ev):
@@ -116,8 +109,6 @@ class Canvas(QWidget):
             # with shapes created the normal way.
             self.current.addPoint(self.current[0])
             self.finalise(ev)
-       
-        
 
     def selectShape(self, point):
         """Select the first shape created which contains this point."""
@@ -126,20 +117,57 @@ class Canvas(QWidget):
             if shape.containsPoint(point):
                 shape.selected = True
                 self.selectedShape = shape
-                return self.repaint()
+                self.calculateOffsets(shape, point)
+                self.repaint()
+                return
+
+    def calculateOffsets(self, shape, point):
+        rect = shape.boundingRect()
+        x1 = rect.x() - point.x()
+        y1 = rect.y() - point.y()
+        x2 = (rect.x() + rect.width()) - point.x()
+        y2 = (rect.y() + rect.height()) - point.y()
+        self.offsets = QPointF(x1, y1), QPointF(x2, y2)
+
+    def boundedMoveShape(self, pos):
+        if self.outOfPixmap(pos):
+            return # No need to move
+        o1 = pos + self.offsets[0]
+        if self.outOfPixmap(o1):
+            pos -= QPointF(min(0, o1.x()), min(0, o1.y()))
+        o2 = pos + self.offsets[1]
+        if self.outOfPixmap(o2):
+            pos += QPointF(min(0, self.pixmap.width() - o2.x()),
+                           min(0, self.pixmap.height()- o2.y()))
+        # The next line tracks the new position of the cursor
+        # relative to the shape, but also results in making it
+        # a bit "shaky" when nearing the border and allows it to
+        # go outside of the shape's area for some reason. XXX
+        #self.calculateOffsets(self.selectedShape, pos)
+        self.selectedShape.moveBy(pos - self.prevPoint)
+        self.prevPoint = pos
 
     def deSelectShape(self):
         if self.selectedShape:
             self.selectedShape.selected = False
-            self.selectedShape=None
-            self.repaint()
+            self.selectedShape = None
 
     def deleteSelected(self):
         if self.selectedShape:
              self.shapes.remove(self.selectedShape)
-             self.selectedShape=None
+             self.selectedShape = None
              self.repaint()
 
+    def copySelectedShape(self):
+        if self.selectedShape:
+            shape = self.selectedShape.copy()
+            self.shapes.append(shape)
+            self.selectedShape = shape
+            self.deSelectShape()
+            self.repaint()
+            return shape
+
+
     def paintEvent(self, event):
         if not self.pixmap:
             return super(Canvas, self).paintEvent(event)
@@ -198,7 +226,7 @@ class Canvas(QWidget):
         #print "d %.2f, m %d, %.2f" % (d, m, d - m)
         return distance(p1 - p2) < self.epsilon
 
-    def intersectionPoint(self, mousePos):
+    def intersectionPoint(self, p1, p2):
         # Cycle through each image edge in clockwise fashion,
         # and find the one intersecting the current line segment.
         # http://paulbourke.net/geometry/lineline2d/
@@ -207,8 +235,8 @@ class Canvas(QWidget):
                   (size.width(), 0),
                   (size.width(), size.height()),
                   (0, size.height())]
-        x1, y1 = self.current[-1].x(), self.current[-1].y()
-        x2, y2 = mousePos.x(), mousePos.y()
+        x1, y1 = p1.x(), p1.y()
+        x2, y2 = p2.x(), p2.y()
         d, i, (x, y) = min(self.intersectingEdges((x1, y1), (x2, y2), points))
         x3, y3 = points[i]
         x4, y4 = points[(i+1)%4]
@@ -286,23 +314,14 @@ class Canvas(QWidget):
         self.line.points = [self.current[-1], pos]
         self.setEditing()
 
-    def copySelectedShape(self):
-        if self.selectedShape:
-            newShape=self.selectedShape.copy()
-            self.shapes.append(newShape)
-            self.deSelectShape()
-            self.shapes[-1].selected=True
-            self.selectedShape=self.shapes[-1]
-            self.repaint()
-            return self.selectedShape.label
-            
     def deleteLastShape(self):
         assert self.shapes
         self.shapes.pop()
 
-    
-    
-            
+
+def pp(p):
+    return '%.2f, %.2f' % (p.x(), p.y())
+
 def distance(p):
     return sqrt(p.x() * p.x() + p.y() * p.y())
 

+ 18 - 27
labelme.py

@@ -69,10 +69,7 @@ class MainWindow(QMainWindow, WindowMixin):
 
         self.canvas = Canvas()
         #self.canvas.setAlignment(Qt.AlignCenter)
-        
 
-
-        
         self.canvas.setContextMenuPolicy(Qt.ActionsContextMenu)
         self.canvas.zoomRequest.connect(self.zoomRequest)
 
@@ -104,12 +101,12 @@ class MainWindow(QMainWindow, WindowMixin):
                 'Ctrl+C', 'copy', u'Copy')
         delete = action('&Delete', self.deleteSelectedShape,
                 'Ctrl+D', 'delete', u'Delete')
-        hideWhenNew=action('&Hide Labels\n When Adding \n new',self.hideWhenNew,
-        'Ctrl+H','Hide',u'Hide',checkable=True)
+        hide = action('&Hide labels', self.hideLabelsToggle,
+                'Ctrl+H', 'hide', u'Hide background labels when drawing',
+                checkable=True)
 
-        
         self.canvas.setContextMenuPolicy( Qt.CustomContextMenu )
-        self.connect(self.canvas, SIGNAL('customContextMenuRequested(const QPoint&)'), self.on_context_menu)
+        self.canvas.customContextMenuRequested.connect(self.popContextMenu)
 
         # Popup Menu
         self.popMenu = QMenu(self )
@@ -136,7 +133,7 @@ class MainWindow(QMainWindow, WindowMixin):
         addActions(self.menus.view, (labels,))
 
         self.tools = self.toolbar('Tools')
-        addActions(self.tools, (open, color, None, label, delete,hideWhenNew, None,
+        addActions(self.tools, (open, color, None, label, delete, hide, None,
             zoom, fit_window, None, quit))
 
 
@@ -180,22 +177,18 @@ class MainWindow(QMainWindow, WindowMixin):
 
         # Callbacks:
         self.zoom_widget.editingFinished.connect(self.paintCanvas)
-        
 
-    def on_context_menu(self, point):
-         self.popMenu.exec_( self.canvas.mapToGlobal(point))
-         
-    def addLabel(self, label, shape):
-        item = QListWidgetItem(label)
+    def popContextMenu(self, point):
+        self.popMenu.exec_(self.canvas.mapToGlobal(point))
+
+    def addLabel(self, shape):
+        item = QListWidgetItem(shape.label)
         self.labels[item] = shape
         self.labelList.addItem(item)
-        
+
     def copySelectedShape(self):
-        #shape=self.canvas.selectedShape()
-        label="copy"
-        label=self.canvas.copySelectedShape()
-        self.addLabel(label,self.canvas.shapes[-1])
-        
+        self.addLabel(self.copySelectedShape())
+
     def highlightLabel(self, item):
         if self.highlighted:
             self.highlighted.fill_color = Shape.fill_color
@@ -212,10 +205,7 @@ class MainWindow(QMainWindow, WindowMixin):
         """
         action = self.label.popUp(position)
         if action == self.label.OK:
-            label = self.label.text()
-            shape = self.canvas.setLastLabel(label)
-            self.addLabel(label, shape)
-            # TODO: Add to list of labels.
+            self.addLabel(self.canvas.setLastLabel(self.label.text()))
         elif action == self.label.UNDO:
             self.canvas.undoLastLine()
         elif action == self.label.DELETE:
@@ -242,9 +232,10 @@ class MainWindow(QMainWindow, WindowMixin):
 
     def queueEvent(self, function):
         QTimer.singleShot(0, function)
-        
-    def hideWhenNew(self):
-        self.canvas.hideShapesWhenNew =not self.canvas.hideShapesWhenNew
+
+    def hideLabelsToggle(self, value):
+        self.canvas.hideShapesWhenNew = value
+
     def loadFile(self, filename=None):
         """Load the specified file, or the last opened file if None."""
         if filename is None:

+ 21 - 23
shape.py

@@ -29,8 +29,7 @@ class Shape(object):
             # with an object attribute. Currently this
             # is used for drawing the pending line a different color.
             self.line_color = line_color
-   
-        
+
     def addPoint(self, point):
         self.points.append(point)
 
@@ -75,29 +74,28 @@ class Shape(object):
             path.addEllipse(point, d/2.0, d/2.0)
 
     def containsPoint(self, point):
-        path = QPainterPath(QPointF(self.points[0]))
+        return self.makePath().contains(point)
+
+    def makePath(self):
+        path = QPainterPath(self.points[0])
         for p in self.points[1:]:
-            path.lineTo(QPointF(p))
-        return path.contains(QPointF(point))
-
-    def moveBy(self,dx,dy):
-        index=0
-        for point in self.points:
-           newXPos= point.x()+dx
-           newYPos=point.y()+dy
-           self.points[index]=QPoint(newXPos,newYPos)
-           index +=1
-           
+            path.lineTo(p)
+        return path
+
+    def boundingRect(self):
+        return self.makePath().boundingRect()
+
+    def moveBy(self, offset):
+        self.points = [p + offset for p in self.points]
+
     def copy(self):
-        shapeCopy=Shape()
-        for point in self.points:
-            shapeCopy.points.append(point)
-            shapeCopy.label=self.label
-            shapeCopy.fill = self.fill
-            shapeCopy.selected = self.selected
-        return shapeCopy
-    
-    
+        shape = Shape()
+        shape.points= [p for p in self.points]
+        shape.label = "Copy of %s" % self.label
+        shape.fill = self.fill
+        shape.selected = self.selected
+        return shape
+
     def __len__(self):
         return len(self.points)