Add ustr.py, add more test, and fix build error for py3

This commit is contained in:
tzutalin 2017-05-03 16:13:08 +08:00
parent 4ee8287e51
commit 9a7ecb4a07
4 changed files with 75 additions and 62 deletions

View File

@ -16,8 +16,8 @@ try:
from PyQt5.QtWidgets import * from PyQt5.QtWidgets import *
except ImportError: except ImportError:
# needed for py3+qt4 # needed for py3+qt4
# ref: http://pyqt.sourceforge.net/Docs/PyQt4/incompatible_apis.html # Ref:
# ref: # http://pyqt.sourceforge.net/Docs/PyQt4/incompatible_apis.html
# http://stackoverflow.com/questions/21217399/pyqt4-qtcore-qvariant-object-instead-of-a-string # http://stackoverflow.com/questions/21217399/pyqt4-qtcore-qvariant-object-instead-of-a-string
if sys.version_info.major >= 3: if sys.version_info.major >= 3:
import sip import sip
@ -37,24 +37,13 @@ from labelFile import LabelFile, LabelFileError
from toolBar import ToolBar from toolBar import ToolBar
from pascal_voc_io import PascalVocReader from pascal_voc_io import PascalVocReader
from pascal_voc_io import XML_EXT from pascal_voc_io import XML_EXT
from ustr import ustr
__appname__ = 'labelImg' __appname__ = 'labelImg'
# Utility functions and classes. # Utility functions and classes.
def u(x):
'''py2/py3 unicode helper'''
if sys.version_info < (3, 0, 0):
if type(x) == str:
return x.decode('utf-8')
if type(x) == QString:
return unicode(x)
return x
else:
return x # py3
def have_qstring(): def have_qstring():
'''p3/qt5 get rid of QString wrapper as py3 has native unicode str type''' '''p3/qt5 get rid of QString wrapper as py3 has native unicode str type'''
return not (sys.version_info.major >= 3 or QT_VERSION_STR.startswith('5.')) return not (sys.version_info.major >= 3 or QT_VERSION_STR.startswith('5.'))
@ -378,7 +367,7 @@ class MainWindow(QMainWindow, WindowMixin):
# Application state. # Application state.
self.image = QImage() self.image = QImage()
self.filePath = u(defaultFilename) self.filePath = ustr(defaultFilename)
self.recentFiles = [] self.recentFiles = []
self.maxRecent = 7 self.maxRecent = 7
self.lineColor = None self.lineColor = None
@ -427,8 +416,8 @@ class MainWindow(QMainWindow, WindowMixin):
position = settings.get('window/position', QPoint(0, 0)) position = settings.get('window/position', QPoint(0, 0))
self.resize(size) self.resize(size)
self.move(position) self.move(position)
saveDir = u(settings.get('savedir', None)) saveDir = ustr(settings.get('savedir', None))
self.lastOpenDir = u(settings.get('lastOpenDir', None)) self.lastOpenDir = ustr(settings.get('lastOpenDir', None))
if os.path.exists(saveDir): if os.path.exists(saveDir):
self.defaultSaveDir = saveDir self.defaultSaveDir = saveDir
self.statusBar().showMessage('%s started. Annotation will be saved to %s' % self.statusBar().showMessage('%s started. Annotation will be saved to %s' %
@ -616,7 +605,7 @@ class MainWindow(QMainWindow, WindowMixin):
# Tzutalin 20160906 : Add file list and dock to move faster # Tzutalin 20160906 : Add file list and dock to move faster
def fileitemDoubleClicked(self, item=None): def fileitemDoubleClicked(self, item=None):
currIndex = self.mImgList.index(u(item.text())) currIndex = self.mImgList.index(ustr(item.text()))
if currIndex < len(self.mImgList): if currIndex < len(self.mImgList):
filename = self.mImgList[currIndex] filename = self.mImgList[currIndex]
if filename: if filename:
@ -703,7 +692,7 @@ class MainWindow(QMainWindow, WindowMixin):
self.canvas.loadShapes(s) self.canvas.loadShapes(s)
def saveLabels(self, annotationFilePath): def saveLabels(self, annotationFilePath):
annotationFilePath = u(annotationFilePath) annotationFilePath = ustr(annotationFilePath)
if self.labelFile is None: if self.labelFile is None:
self.labelFile = LabelFile() self.labelFile = LabelFile()
self.labelFile.verified = self.canvas.verified self.labelFile.verified = self.canvas.verified
@ -829,7 +818,7 @@ class MainWindow(QMainWindow, WindowMixin):
if filePath is None: if filePath is None:
filePath = self.settings.get('filename') filePath = self.settings.get('filename')
unicodeFilePath = u(filePath) unicodeFilePath = ustr(filePath)
# Tzutalin 20160906 : Add file list and dock to move faster # Tzutalin 20160906 : Add file list and dock to move faster
# Highlight the file item # Highlight the file item
if unicodeFilePath and self.fileListWidget.count() > 0: if unicodeFilePath and self.fileListWidget.count() > 0:
@ -949,7 +938,7 @@ class MainWindow(QMainWindow, WindowMixin):
s['recentFiles'] = self.recentFiles s['recentFiles'] = self.recentFiles
s['advanced'] = not self._beginner s['advanced'] = not self._beginner
if self.defaultSaveDir is not None and len(self.defaultSaveDir) > 1: if self.defaultSaveDir is not None and len(self.defaultSaveDir) > 1:
s['savedir'] = str(self.defaultSaveDir) s['savedir'] = ustr(self.defaultSaveDir)
else: else:
s['savedir'] = "" s['savedir'] = ""
@ -972,18 +961,18 @@ class MainWindow(QMainWindow, WindowMixin):
for file in files: for file in files:
if file.lower().endswith(tuple(extensions)): if file.lower().endswith(tuple(extensions)):
relatviePath = os.path.join(root, file) relatviePath = os.path.join(root, file)
path = u(os.path.abspath(relatviePath)) path = ustr(os.path.abspath(relatviePath))
images.append(path) images.append(path)
images.sort(key=lambda x: x.lower()) images.sort(key=lambda x: x.lower())
return images return images
def changeSavedir(self, _value=False): def changeSavedir(self, _value=False):
if self.defaultSaveDir is not None: if self.defaultSaveDir is not None:
path = str(self.defaultSaveDir) path = ustr(self.defaultSaveDir)
else: else:
path = '.' path = '.'
dirpath = str(QFileDialog.getExistingDirectory(self, dirpath = ustr(QFileDialog.getExistingDirectory(self,
'%s - Save to the directory' % __appname__, path, QFileDialog.ShowDirsOnly '%s - Save to the directory' % __appname__, path, QFileDialog.ShowDirsOnly
| QFileDialog.DontResolveSymlinks)) | QFileDialog.DontResolveSymlinks))
@ -998,7 +987,7 @@ class MainWindow(QMainWindow, WindowMixin):
if self.filePath is None: if self.filePath is None:
return return
path = os.path.dirname(u(self.filePath))\ path = os.path.dirname(ustr(self.filePath))\
if self.filePath else '.' if self.filePath else '.'
if self.usingPascalVocFormat: if self.usingPascalVocFormat:
filters = "Open Annotation XML file (%s)" % \ filters = "Open Annotation XML file (%s)" % \
@ -1019,7 +1008,7 @@ class MainWindow(QMainWindow, WindowMixin):
if self.lastOpenDir is not None and len(self.lastOpenDir) > 1: if self.lastOpenDir is not None and len(self.lastOpenDir) > 1:
path = self.lastOpenDir path = self.lastOpenDir
dirpath = u(QFileDialog.getExistingDirectory(self, dirpath = ustr(QFileDialog.getExistingDirectory(self,
'%s - Open Directory' % __appname__, path, QFileDialog.ShowDirsOnly '%s - Open Directory' % __appname__, path, QFileDialog.ShowDirsOnly
| QFileDialog.DontResolveSymlinks)) | QFileDialog.DontResolveSymlinks))
@ -1092,7 +1081,7 @@ class MainWindow(QMainWindow, WindowMixin):
def openFile(self, _value=False): def openFile(self, _value=False):
if not self.mayContinue(): if not self.mayContinue():
return return
path = os.path.dirname(u(self.filePath)) if self.filePath else '.' path = os.path.dirname(ustr(self.filePath)) if self.filePath else '.'
formats = ['*.%s' % fmt.data().decode("ascii").lower() for fmt in QImageReader.supportedImageFormats()] formats = ['*.%s' % fmt.data().decode("ascii").lower() for fmt in QImageReader.supportedImageFormats()]
filters = "Image & Label files (%s)" % ' '.join(formats + ['*%s' % LabelFile.suffix]) filters = "Image & Label files (%s)" % ' '.join(formats + ['*%s' % LabelFile.suffix])
filename = QFileDialog.getOpenFileName(self, '%s - Choose Image or Label file' % __appname__, path, filters) filename = QFileDialog.getOpenFileName(self, '%s - Choose Image or Label file' % __appname__, path, filters)
@ -1102,13 +1091,12 @@ class MainWindow(QMainWindow, WindowMixin):
self.loadFile(filename) self.loadFile(filename)
def saveFile(self, _value=False): def saveFile(self, _value=False):
if self.defaultSaveDir is not None and len(str(self.defaultSaveDir)): if self.defaultSaveDir is not None and len(ustr(self.defaultSaveDir)):
# print('handle the image:' + self.filePath) # print('handle the image:' + self.filePath)
if self.filePath:
imgFileName = os.path.basename(self.filePath) imgFileName = os.path.basename(self.filePath)
savedFileName = os.path.splitext( savedFileName = os.path.splitext(imgFileName)[0] + LabelFile.suffix
imgFileName)[0] + LabelFile.suffix savedPath = os.path.join(ustr(self.defaultSaveDir), savedFileName)
savedPath = os.path.join(
str(self.defaultSaveDir), savedFileName)
self._saveFile(savedPath) self._saveFile(savedPath)
else: else:
self._saveFile(self.filePath if self.labelFile self._saveFile(self.filePath if self.labelFile
@ -1260,7 +1248,7 @@ class Settings(object):
t = self.types.get(key) t = self.types.get(key)
if t is not None and t != QVariant: if t is not None and t != QVariant:
if t is str: if t is str:
return str(value) return ustr(value)
else: else:
try: try:
method = getattr(QVariant, re.sub( method = getattr(QVariant, re.sub(

View File

@ -89,12 +89,10 @@ class PascalVocWriter:
pose = SubElement(object_item, 'pose') pose = SubElement(object_item, 'pose')
pose.text = "Unspecified" pose.text = "Unspecified"
truncated = SubElement(object_item, 'truncated') truncated = SubElement(object_item, 'truncated')
# max == height or min
if int(each_object['ymax']) == int(self.imgSize[0]) or (int(each_object['ymin'])== 1): if int(each_object['ymax']) == int(self.imgSize[0]) or (int(each_object['ymin'])== 1):
truncated.text = "1" truncated.text = "1" # max == height or min
# max == width or min
elif (int(each_object['xmax'])==int(self.imgSize[1])) or (int(each_object['xmin'])== 1): elif (int(each_object['xmax'])==int(self.imgSize[1])) or (int(each_object['xmin'])== 1):
truncated.text = "1" truncated.text = "1" # max == width or min
else: else:
truncated.text = "0" truncated.text = "0"
difficult = SubElement(object_item, 'Difficult') difficult = SubElement(object_item, 'Difficult')
@ -143,11 +141,10 @@ class PascalVocReader:
xmax = int(bndbox.find('xmax').text) xmax = int(bndbox.find('xmax').text)
ymax = int(bndbox.find('ymax').text) ymax = int(bndbox.find('ymax').text)
points = [(xmin, ymin), (xmax, ymin), (xmax, ymax), (xmin, ymax)] points = [(xmin, ymin), (xmax, ymin), (xmax, ymax), (xmin, ymax)]
self.shapes.append((label, points, None, None, difficult)) self.shapes.append((label, points, None, None, difficult))
def parseXML(self): def parseXML(self):
assert self.filepath.endswith('.xml'), "Unsupport file format" assert self.filepath.endswith(XML_EXT), "Unsupport file format"
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

14
libs/ustr.py Normal file
View File

@ -0,0 +1,14 @@
import sys
def ustr(x):
'''py2/py3 unicode helper'''
if sys.version_info < (3, 0, 0):
from PyQt4.QtCore import QString
if type(x) == str:
return x.decode('utf-8')
if type(x) == QString:
return unicode(x)
return x
else:
return x # py3

View File

@ -1,8 +1,12 @@
import _init_path
from unittest import TestCase from unittest import TestCase
from labelImg import get_main_app from labelImg import get_main_app
from pascal_voc_io import PascalVocWriter
from pascal_voc_io import PascalVocReader
class TestMainWindow(TestCase): class TestMainWindow(TestCase):
@ -18,3 +22,13 @@ class TestMainWindow(TestCase):
def test_noop(self): def test_noop(self):
pass pass
# Test Write/Read
writer = PascalVocWriter('tests', 'test', (512, 512, 1), localImgPath='tests/test.bmp')
difficult = 1
writer.addBndBox(60, 40, 430, 504, 'person', difficult)
writer.addBndBox(113, 40, 450, 403, 'face', difficult)
writer.save('tests/test.xml')
reader = PascalVocReader('tests/test.xml')
shapes = reader.getShapes()