Ver Fonte

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 há 13 anos atrás
pai
commit
c7471ecb32
3 ficheiros alterados com 101 adições e 93 exclusões
  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)