diff --git a/labelme/canvas.py b/labelme/canvas.py index a668c8472b250c7733365cab6e28a4536cf56819..902bbacac07264cdbe79380c778767ac0b063e83 100644 --- a/labelme/canvas.py +++ b/labelme/canvas.py @@ -52,6 +52,7 @@ class Canvas(QtWidgets.QWidget): self.hideBackround = False self.hShape = None self.hVertex = None + self.addVertex = None self.movingShape = False self._painter = QtGui.QPainter() self._cursor = CURSOR_DEFAULT @@ -116,6 +117,9 @@ class Canvas(QtWidgets.QWidget): def selectedVertex(self): return self.hVertex is not None + def addedVertex(self): + return self.addVertex is not None + def mouseMoveEvent(self, ev): """Update line with last point and current coordinates.""" if QT5: @@ -183,6 +187,7 @@ class Canvas(QtWidgets.QWidget): # Look for a nearby vertex to highlight. If that fails, # check if we happen to be inside a shape. index = shape.nearestVertex(pos, self.epsilon) + index2 = shape.nearestEdge(pos, self.epsilon) if index is not None: if self.selectedVertex(): self.hShape.highlightClear() @@ -193,10 +198,20 @@ class Canvas(QtWidgets.QWidget): self.setStatusTip(self.toolTip()) self.update() break + elif index2 is not None: + if self.selectedVertex(): + self.hShape.highlightClear() + self.addVertex, self.hShape = index2, shape + self.hVertex = None + self.overrideCursor(CURSOR_DRAW) + self.setToolTip("Click & drag to add point") + self.setStatusTip(self.toolTip()) + self.update() + break elif shape.containsPoint(pos): if self.selectedVertex(): self.hShape.highlightClear() - self.hVertex, self.hShape = None, shape + self.hVertex, self.addVertex, self.hShape = None, None, shape self.setToolTip( "Click & drag to move shape '%s'" % shape.label) self.setStatusTip(self.toolTip()) @@ -305,6 +320,13 @@ class Canvas(QtWidgets.QWidget): index, shape = self.hVertex, self.hShape shape.highlightVertex(index, shape.MOVE_VERTEX) return + if self.addedVertex() and self.hShape is not None: + index2, shape = self.addVertex, self.hShape + shape.insertPoint(index2, point) + shape.highlightVertex(index2, shape.MOVE_VERTEX) + self.addVertex = None + self.hVertex = index2 + return for shape in reversed(self.shapes): if self.isVisible(shape) and shape.containsPoint(point): shape.selected = True diff --git a/labelme/lib.py b/labelme/lib.py index 1394715e69e254ecaaf4bdee5565b2122e4dcd17..e8873dfaa81866e7b5a973ab49546fffc1aca68f 100644 --- a/labelme/lib.py +++ b/labelme/lib.py @@ -1,5 +1,7 @@ from math import sqrt import os.path as osp +from numpy.linalg import norm +import numpy as np from qtpy import QtCore from qtpy import QtGui @@ -68,6 +70,18 @@ def distance(p): return sqrt(p.x() * p.x() + p.y() * p.y()) +def distancetoline(point, line): + p1, p2 = line + p1 = np.array([p1.x(), p1.y()]) + p2 = np.array([p2.x(), p2.y()]) + p3 = np.array([point.x(), point.y()]) + if np.dot((p3 - p1), (p2 - p1)) < 0: + return norm(p3 - p1) + if np.dot((p3 - p2), (p1 - p2)) < 0: + return norm(p3 - p2) + return norm(np.cross(p2 - p1, p1 - p3)) / norm(p2 - p1) + + def fmtShortcut(text): mod, key = text.split('+', 1) return '%s+%s' % (mod, key) diff --git a/labelme/shape.py b/labelme/shape.py index 349bbaa1aabc760c409946d461b391cc4fd17792..0045763a0e5f6e5d5d94a70fef5aa5dc326f63be 100644 --- a/labelme/shape.py +++ b/labelme/shape.py @@ -1,6 +1,7 @@ from qtpy import QtGui from labelme.lib import distance +from labelme.lib import distancetoline from labelme import logger @@ -71,6 +72,9 @@ class Shape(object): return self.points.pop() return None + def insertPoint(self, i, point): + self.points.insert(i, point) + def isClosed(self): return self._closed @@ -137,6 +141,17 @@ class Shape(object): min_i = i return min_i + def nearestEdge(self, point, epsilon): + min_distance = float('inf') + post_i = None + for i in range(len(self.points)): + line = [self.points[i - 1], self.points[i]] + dist = distancetoline(point, line) + if dist <= epsilon and dist < min_distance: + min_distance = dist + post_i = i + return post_i + def containsPoint(self, point): return self.makePath().contains(point)