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.
This commit is contained in:
Thibaut Mattio 2017-03-08 11:01:36 +08:00
parent ce853362e5
commit 3abd685a8d
4 changed files with 62 additions and 15 deletions

34
labelImg.py Executable file → Normal file
View File

@ -212,6 +212,9 @@ class MainWindow(QMainWindow, WindowMixin):
openPrevImg = action('&Prev Image', self.openPrevImg, openPrevImg = action('&Prev Image', self.openPrevImg,
'a', 'prev', u'Open Prev') 'a', 'prev', u'Open Prev')
verify = action('&Verify Image', self.verifyImg,
'space', 'verify', u'Verify Image')
save = action('&Save', self.saveFile, save = action('&Save', self.saveFile,
'Ctrl+S', 'save', u'Save labels to file', enabled=False) 'Ctrl+S', 'save', u'Save labels to file', enabled=False)
saveAs = action('&Save As', self.saveFileAs, saveAs = action('&Save As', self.saveFileAs,
@ -353,7 +356,7 @@ class MainWindow(QMainWindow, WindowMixin):
self.tools = self.toolbar('Tools') self.tools = self.toolbar('Tools')
self.actions.beginner = ( 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) zoomIn, zoom, zoomOut, fitWindow, fitWidth)
self.actions.advanced = ( self.actions.advanced = (
@ -658,7 +661,9 @@ class MainWindow(QMainWindow, WindowMixin):
def saveLabels(self, annotationFilePath): def saveLabels(self, annotationFilePath):
annotationFilePath = u(annotationFilePath) annotationFilePath = u(annotationFilePath)
lf = LabelFile() if self.labelFile is None:
self.labelFile = LabelFile()
self.labelFile.verified = self.canvas.verified
def format_shape(s): def format_shape(s):
return dict(label=s.label, return dict(label=s.label,
@ -674,12 +679,11 @@ class MainWindow(QMainWindow, WindowMixin):
if self.usingPascalVocFormat is True: if self.usingPascalVocFormat is True:
print ('Img: ' + self.filePath + print ('Img: ' + self.filePath +
' -> Its xml: ' + annotationFilePath) ' -> Its xml: ' + annotationFilePath)
lf.savePascalVocFormat(annotationFilePath, shapes, self.filePath, self.imageData, self.labelFile.savePascalVocFormat(annotationFilePath, shapes, self.filePath, self.imageData,
self.lineColor.getRgb(), self.fillColor.getRgb()) self.lineColor.getRgb(), self.fillColor.getRgb())
else: else:
lf.save(annotationFilePath, shapes, self.filePath, self.imageData, self.labelFile.save(annotationFilePath, shapes, self.filePath, self.imageData,
self.lineColor.getRgb(), self.fillColor.getRgb()) self.lineColor.getRgb(), self.fillColor.getRgb())
self.labelFile = lf
return True return True
except LabelFileError as e: except LabelFileError as e:
self.errorMessage(u'Error saving label data', self.errorMessage(u'Error saving label data',
@ -829,7 +833,9 @@ class MainWindow(QMainWindow, WindowMixin):
xmlPath = os.path.join(self.defaultSaveDir, basename) xmlPath = os.path.join(self.defaultSaveDir, basename)
self.loadPascalXMLByFilename(xmlPath) self.loadPascalXMLByFilename(xmlPath)
self.canvas.setFocus() self.setWindowTitle('{} - {}'.format(__appname__, filePath))
self.canvas.setFocus(True)
return True return True
return False return False
@ -970,6 +976,21 @@ class MainWindow(QMainWindow, WindowMixin):
item = QListWidgetItem(imgPath) item = QListWidgetItem(imgPath)
self.fileListWidget.addItem(item) 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): def openPrevImg(self, _value=False):
if not self.mayContinue(): if not self.mayContinue():
return return
@ -1156,6 +1177,7 @@ class MainWindow(QMainWindow, WindowMixin):
tVocParseReader = PascalVocReader(xmlPath) tVocParseReader = PascalVocReader(xmlPath)
shapes = tVocParseReader.getShapes() shapes = tVocParseReader.getShapes()
self.loadLabels(shapes) self.loadLabels(shapes)
self.canvas.verified = tVocParseReader.verified
class Settings(object): class Settings(object):

View File

@ -59,6 +59,7 @@ class Canvas(QWidget):
# Set widget options. # Set widget options.
self.setMouseTracking(True) self.setMouseTracking(True)
self.setFocusPolicy(Qt.WheelFocus) self.setFocusPolicy(Qt.WheelFocus)
self.verified = False
def enterEvent(self, ev): def enterEvent(self, ev):
self.overrideCursor(self._cursor) self.overrideCursor(self._cursor)
@ -419,6 +420,16 @@ class Canvas(QWidget):
p.setBrush(brush) p.setBrush(brush)
p.drawRect(leftTop.x(), leftTop.y(), rectWidth, rectHeight) 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() p.end()
def transformPos(self, point): def transformPos(self, point):

View File

@ -24,8 +24,7 @@ class LabelFile(object):
self.shapes = () self.shapes = ()
self.imagePath = None self.imagePath = None
self.imageData = None self.imageData = None
if filename is not None: self.verified = False
self.load(filename)
def savePascalVocFormat(self, filename, shapes, imagePath, imageData, def savePascalVocFormat(self, filename, shapes, imagePath, imageData,
lineColor=None, fillColor=None, databaseSrc=None): lineColor=None, fillColor=None, databaseSrc=None):
@ -41,6 +40,8 @@ class LabelFile(object):
1 if image.isGrayscale() else 3] 1 if image.isGrayscale() else 3]
writer = PascalVocWriter(imgFolderName, imgFileNameWithoutExt, writer = PascalVocWriter(imgFolderName, imgFileNameWithoutExt,
imageShape, localImgPath=imagePath) imageShape, localImgPath=imagePath)
writer.verified = self.verified
for shape in shapes: for shape in shapes:
points = shape['points'] points = shape['points']
label = shape['label'] label = shape['label']
@ -50,6 +51,9 @@ class LabelFile(object):
writer.save(targetFile=filename) writer.save(targetFile=filename)
return return
def toggleVerify(self):
self.verified = not self.verified
@staticmethod @staticmethod
def isLabelFile(filename): def isLabelFile(filename):
fileSuffix = os.path.splitext(filename)[1].lower() fileSuffix = os.path.splitext(filename)[1].lower()
@ -72,10 +76,10 @@ class LabelFile(object):
# Martin Kersner, 2015/11/12 # Martin Kersner, 2015/11/12
# 0-valued coordinates of BB caused an error while # 0-valued coordinates of BB caused an error while
# training faster-rcnn object detector. # training faster-rcnn object detector.
if (xmin < 1): if xmin < 1:
xmin = 1 xmin = 1
if (ymin < 1): if ymin < 1:
ymin = 1 ymin = 1
return (int(xmin), int(ymin), int(xmax), int(ymax)) return (int(xmin), int(ymin), int(xmax), int(ymax))

View File

@ -19,6 +19,7 @@ class PascalVocWriter:
self.imgSize = imgSize self.imgSize = imgSize
self.boxlist = [] self.boxlist = []
self.localImgPath = localImgPath self.localImgPath = localImgPath
self.verified = False
def prettify(self, elem): def prettify(self, elem):
""" """
@ -39,6 +40,8 @@ class PascalVocWriter:
return None return None
top = Element('annotation') top = Element('annotation')
top.set('verified', 'yes' if self.verified else 'no')
folder = SubElement(top, 'folder') folder = SubElement(top, 'folder')
folder.text = self.foldername folder.text = self.foldername
@ -119,6 +122,7 @@ class PascalVocReader:
# [labbel, [(x1,y1), (x2,y2), (x3,y3), (x4,y4)], color, color] # [labbel, [(x1,y1), (x2,y2), (x3,y3), (x4,y4)], color, color]
self.shapes = [] self.shapes = []
self.filepath = filepath self.filepath = filepath
self.verified = False
self.parseXML() self.parseXML()
def getShapes(self): def getShapes(self):
@ -137,6 +141,12 @@ class PascalVocReader:
parser = etree.XMLParser(encoding='utf-8') parser = etree.XMLParser(encoding='utf-8')
xmltree = ElementTree.parse(self.filepath, parser=parser).getroot() xmltree = ElementTree.parse(self.filepath, parser=parser).getroot()
filename = xmltree.find('filename').text 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'): for object_iter in xmltree.findall('object'):
bndbox = object_iter.find("bndbox") bndbox = object_iter.find("bndbox")