From 3abd685a8d20b6d00ddd240ad47815ec42e88b6b Mon Sep 17 00:00:00 2001 From: Thibaut Mattio Date: Wed, 8 Mar 2017 11:01:36 +0800 Subject: [PATCH] Implement verified feature When pressing space, the user can flag the image as verified, a green background will appear. This is used when creating a dataset automatically, the user can then through all the pictures and flag them instead of annotate them. --- labelImg.py | 40 +++++++++++++++++++++++++++++++--------- libs/canvas.py | 15 +++++++++++++-- libs/labelFile.py | 12 ++++++++---- libs/pascal_voc_io.py | 10 ++++++++++ 4 files changed, 62 insertions(+), 15 deletions(-) mode change 100755 => 100644 labelImg.py diff --git a/labelImg.py b/labelImg.py old mode 100755 new mode 100644 index eac0ac76..a6c81e3f --- a/labelImg.py +++ b/labelImg.py @@ -212,6 +212,9 @@ class MainWindow(QMainWindow, WindowMixin): openPrevImg = action('&Prev Image', self.openPrevImg, 'a', 'prev', u'Open Prev') + verify = action('&Verify Image', self.verifyImg, + 'space', 'verify', u'Verify Image') + save = action('&Save', self.saveFile, 'Ctrl+S', 'save', u'Save labels to file', enabled=False) saveAs = action('&Save As', self.saveFileAs, @@ -353,7 +356,7 @@ class MainWindow(QMainWindow, WindowMixin): self.tools = self.toolbar('Tools') self.actions.beginner = ( - open, opendir, openNextImg, openPrevImg, save, None, create, copy, delete, None, + open, opendir, openNextImg, openPrevImg, verify, save, None, create, copy, delete, None, zoomIn, zoom, zoomOut, fitWindow, fitWidth) self.actions.advanced = ( @@ -658,7 +661,9 @@ class MainWindow(QMainWindow, WindowMixin): def saveLabels(self, annotationFilePath): annotationFilePath = u(annotationFilePath) - lf = LabelFile() + if self.labelFile is None: + self.labelFile = LabelFile() + self.labelFile.verified = self.canvas.verified def format_shape(s): return dict(label=s.label, @@ -674,12 +679,11 @@ class MainWindow(QMainWindow, WindowMixin): if self.usingPascalVocFormat is True: print ('Img: ' + self.filePath + ' -> Its xml: ' + annotationFilePath) - lf.savePascalVocFormat(annotationFilePath, shapes, self.filePath, self.imageData, - self.lineColor.getRgb(), self.fillColor.getRgb()) + self.labelFile.savePascalVocFormat(annotationFilePath, shapes, self.filePath, self.imageData, + self.lineColor.getRgb(), self.fillColor.getRgb()) else: - lf.save(annotationFilePath, shapes, self.filePath, self.imageData, - self.lineColor.getRgb(), self.fillColor.getRgb()) - self.labelFile = lf + self.labelFile.save(annotationFilePath, shapes, self.filePath, self.imageData, + self.lineColor.getRgb(), self.fillColor.getRgb()) return True except LabelFileError as e: self.errorMessage(u'Error saving label data', @@ -829,7 +833,9 @@ class MainWindow(QMainWindow, WindowMixin): xmlPath = os.path.join(self.defaultSaveDir, basename) self.loadPascalXMLByFilename(xmlPath) - self.canvas.setFocus() + self.setWindowTitle('{} - {}'.format(__appname__, filePath)) + + self.canvas.setFocus(True) return True return False @@ -970,6 +976,21 @@ class MainWindow(QMainWindow, WindowMixin): item = QListWidgetItem(imgPath) self.fileListWidget.addItem(item) + def verifyImg(self, _value=False): + # Proceding next image without dialog if having any label + if self.filePath is not None: + try: + self.labelFile.toggleVerify() + except AttributeError: + # If the labelling file does not exist yet, create if and + # re-save it with the verified attribute. + self.saveFile() + self.labelFile.toggleVerify() + + self.canvas.verified = self.labelFile.verified + self.paintCanvas() + self.saveFile() + def openPrevImg(self, _value=False): if not self.mayContinue(): return @@ -1034,7 +1055,7 @@ class MainWindow(QMainWindow, WindowMixin): self._saveFile(savedPath) else: self._saveFile(self.filePath if self.labelFile - else self.saveFileDialog()) + else self.saveFileDialog()) def saveFileAs(self, _value=False): assert not self.image.isNull(), "cannot save empty image" @@ -1156,6 +1177,7 @@ class MainWindow(QMainWindow, WindowMixin): tVocParseReader = PascalVocReader(xmlPath) shapes = tVocParseReader.getShapes() self.loadLabels(shapes) + self.canvas.verified = tVocParseReader.verified class Settings(object): diff --git a/libs/canvas.py b/libs/canvas.py index 431ee87d..6e3404bd 100644 --- a/libs/canvas.py +++ b/libs/canvas.py @@ -59,6 +59,7 @@ class Canvas(QWidget): # Set widget options. self.setMouseTracking(True) self.setFocusPolicy(Qt.WheelFocus) + self.verified = False def enterEvent(self, ev): self.overrideCursor(self._cursor) @@ -184,7 +185,7 @@ class Canvas(QWidget): if ev.button() == Qt.LeftButton: if self.drawing(): - self.handleDrawing(pos) + self.handleDrawing(pos) else: self.selectShapePoint(pos) self.prevPoint = pos @@ -208,7 +209,7 @@ class Canvas(QWidget): elif ev.button() == Qt.LeftButton: pos = self.transformPos(ev.pos()) if self.drawing(): - self.handleDrawing(pos) + self.handleDrawing(pos) def endMove(self, copy=False): assert self.selectedShape and self.selectedShapeCopy @@ -419,6 +420,16 @@ class Canvas(QWidget): p.setBrush(brush) p.drawRect(leftTop.x(), leftTop.y(), rectWidth, rectHeight) + self.setAutoFillBackground(True) + if self.verified: + pal = self.palette() + pal.setColor(self.backgroundRole(), QColor(184, 239, 38, 128)) + self.setPalette(pal) + else: + pal = self.palette() + pal.setColor(self.backgroundRole(), QColor(232, 232, 232, 255)) + self.setPalette(pal) + p.end() def transformPos(self, point): diff --git a/libs/labelFile.py b/libs/labelFile.py index 2847a3b1..6b12e460 100644 --- a/libs/labelFile.py +++ b/libs/labelFile.py @@ -24,8 +24,7 @@ class LabelFile(object): self.shapes = () self.imagePath = None self.imageData = None - if filename is not None: - self.load(filename) + self.verified = False def savePascalVocFormat(self, filename, shapes, imagePath, imageData, lineColor=None, fillColor=None, databaseSrc=None): @@ -41,6 +40,8 @@ class LabelFile(object): 1 if image.isGrayscale() else 3] writer = PascalVocWriter(imgFolderName, imgFileNameWithoutExt, imageShape, localImgPath=imagePath) + writer.verified = self.verified + for shape in shapes: points = shape['points'] label = shape['label'] @@ -50,6 +51,9 @@ class LabelFile(object): writer.save(targetFile=filename) return + def toggleVerify(self): + self.verified = not self.verified + @staticmethod def isLabelFile(filename): fileSuffix = os.path.splitext(filename)[1].lower() @@ -72,10 +76,10 @@ class LabelFile(object): # Martin Kersner, 2015/11/12 # 0-valued coordinates of BB caused an error while # training faster-rcnn object detector. - if (xmin < 1): + if xmin < 1: xmin = 1 - if (ymin < 1): + if ymin < 1: ymin = 1 return (int(xmin), int(ymin), int(xmax), int(ymax)) diff --git a/libs/pascal_voc_io.py b/libs/pascal_voc_io.py index ec7d02dd..3197cb0a 100644 --- a/libs/pascal_voc_io.py +++ b/libs/pascal_voc_io.py @@ -19,6 +19,7 @@ class PascalVocWriter: self.imgSize = imgSize self.boxlist = [] self.localImgPath = localImgPath + self.verified = False def prettify(self, elem): """ @@ -39,6 +40,8 @@ class PascalVocWriter: return None top = Element('annotation') + top.set('verified', 'yes' if self.verified else 'no') + folder = SubElement(top, 'folder') folder.text = self.foldername @@ -119,6 +122,7 @@ class PascalVocReader: # [labbel, [(x1,y1), (x2,y2), (x3,y3), (x4,y4)], color, color] self.shapes = [] self.filepath = filepath + self.verified = False self.parseXML() def getShapes(self): @@ -137,6 +141,12 @@ class PascalVocReader: parser = etree.XMLParser(encoding='utf-8') xmltree = ElementTree.parse(self.filepath, parser=parser).getroot() filename = xmltree.find('filename').text + try: + verified = xmltree.attrib['verified'] + if verified == 'yes': + self.verified = True + except AttributeError: + self.verified = False for object_iter in xmltree.findall('object'): bndbox = object_iter.find("bndbox")