Merge pull request #45 from rflynn/py3qt5

Py3qt5
This commit is contained in:
darrenl 2017-01-09 14:18:29 +08:00 committed by GitHub
commit 43b730b6e6
13 changed files with 563 additions and 101 deletions

View File

@ -1,14 +1,293 @@
language: python
python:
- "2.7"
# vim: set ts=2 et:
# run xvfb with 32-bit color
# xvfb-run -s '-screen 0 1600x1200x24+32' command_goes_here
matrix:
include:
# Python 2.7 + QT4
- os: linux
dist: trusty
sudo: required
language: generic
python: "2.7"
env:
- QT=4
addons:
apt:
packages:
- cmake
- python-qt4
- pyqt4-dev-tools
- xvfb
before_install:
- sudo pip install lxml
- make qt4py2
- xvfb-run make testpy2
# command to install dependencies
#install: "pip install --user -r requirements.txt"
# command to run tests
script: make all
# Python 2.7 + QT4
- os: linux
dist: trusty
sudo: required
language: generic
python: "2.7"
env:
- QT=4
- CONDA=4.2.0
addons:
apt:
packages:
- cmake
#- python-qt4
#- pyqt4-dev-tools
- xvfb
before_install:
# ref: https://www.continuum.io/downloads
- curl -O https://repo.continuum.io/archive/Anaconda2-4.2.0-Linux-x86_64.sh
# ref: http://conda.pydata.org/docs/help/silent.html
- /bin/bash Anaconda2-4.2.0-Linux-x86_64.sh -b -p $HOME/anaconda2
- export PATH="$HOME/anaconda2/bin:$PATH"
# ref: http://stackoverflow.com/questions/21637922/how-to-install-pyqt4-in-anaconda
- conda create -y -n labelImg-py2qt4 python=2.7
- source activate labelImg-py2qt4
- conda install -y pyqt=4
- conda install -y lxml
- make qt4py2
- xvfb-run make testpy2
# Python 2 + QT5
# disabled; can't get it to work
#- os: linux
# dist: trusty
# sudo: required
# language: generic
# python: "2.7"
# env:
# - QT=5
# addons:
# apt:
# packages:
# - cmake
# - pyqt5-dev-tools
# - xvfb
# before_install:
# - sudo apt-get update
# - sudo apt-get install -y python-pip
# - sudo pip install lxml
# - pyrcc5 --help || true # does QT5 support python2 out of the box?
# - make qt5py3
# - xvfb-run make testpy2
# Python 3 + QT4
- os: linux
dist: trusty
sudo: required
language: generic
python: "3.5"
env:
- QT=4
addons:
apt:
packages:
- cmake
- python3-pyqt4
- pyqt4-dev-tools
- xvfb
before_install:
- sudo apt-get update
- sudo apt-get install -y python3-pip
- sudo pip3 install lxml
- make qt4py3
- xvfb-run make testpy3
# Python 3 + QT5
- os: linux
dist: trusty
sudo: required
language: generic
python: "3.5"
env:
- QT=5
addons:
apt:
packages:
- cmake
- pyqt5-dev-tools
- xvfb
before_install:
- sudo apt-get update
- sudo apt-get install -y python3-pip
- sudo pip3 install lxml
- make qt5py3
- xvfb-run make testpy3
# Python 3 + QT5
- os: linux
dist: trusty
sudo: required
language: generic
python: "3.5"
env:
- QT=5
- CONDA=4.2.0
addons:
apt:
packages:
- cmake
- xvfb
before_install:
# ref: https://www.continuum.io/downloads
- curl -O https://repo.continuum.io/archive/Anaconda3-4.2.0-Linux-x86_64.sh
# ref: http://conda.pydata.org/docs/help/silent.html
- /bin/bash Anaconda3-4.2.0-Linux-x86_64.sh -b -p $HOME/anaconda3
- export PATH="$HOME/anaconda3/bin:$PATH"
# ref: http://stackoverflow.com/questions/21637922/how-to-install-pyqt4-in-anaconda
- conda create -y -n labelImg-py3qt5 python=3.5
- source activate labelImg-py3qt5
- conda install -y pyqt=5
- conda install -y lxml
- make qt5py3
- xvfb-run make testpy3
# OS X 10.10 Python 3 + QT5
- os: osx
osx_image: xcode6.4 # Xcode 6.4, OS X 10.10
sudo: required
language: generic
python: "3.6"
env:
- QT=5
before_install:
#- brew update
- brew install libxml2
- brew install pyqt5
- which python3 pip3
- python3 --version
#- sudo -H pip3 install --user --upgrade lxml # pyqt5 installs python3.x, which installs pip3
- sudo -H easy_install-3.6 lxml || true
- python3 -c 'import sys; print(sys.path)'
- python3 -c 'import lxml'
- make qt5py3
- python3 -c 'help("modules")'
- make testpy3 # FIXME: does not work, segfault on travis-ci
# OS X 10.11 Python 3 + QT5
- os: osx
osx_image: xcode8 # Xcode 8, OS X 10.11
sudo: required
language: generic
python: "3.6"
env:
- QT=5
before_install:
#- brew update
- brew install libxml2
- brew install pyqt5
- which python3 pip3
- python3 --version
#- sudo -H pip3 install --user --upgrade lxml # pyqt5 installs python3.x, which installs pip3
- sudo -H easy_install-3.6 lxml || true
- python3 -c 'import sys; print(sys.path)'
- python3 -c 'import lxml'
- make qt5py3
- python3 -c 'help("modules")'
- make testpy3 # FIXME: does not work, segfault on travis-ci
# OS X 10.12 Python 3 + QT5
- os: osx
osx_image: xcode8.2 # OS X 10.12
sudo: required
language: generic
python: "3.6"
env:
- QT=5
before_install:
#- brew update
- brew install libxml2
- brew install pyqt5
- which python3 pip3
- python3 --version
#- sudo -H pip3 install --user --upgrade lxml # pyqt5 installs python3.x, which installs pip3
- sudo -H easy_install-3.6 lxml || true
- python3 -c 'import sys; print(sys.path)'
- python3 -c 'import lxml'
- make qt5py3
- python3 -c 'help("modules")'
#- make testpy3 # FIXME: does not work, segfault on travis-ci
# just make sure the app runs... :-/
- ( python3 labelImg.py ) & sleep 10; kill $!
# XXX: building QT4 from source takes forever...
# OS X 10.11 Python 2 + QT4
#- os: osx
# osx_image: xcode7.3 # OS X 10.11
# sudo: required
# language: generic
# python: "2.7"
# env:
# - QT=4
# before_install:
# - brew install libxml2
# # build PyQT4...
# - curl -L -O https://sourceforge.net/projects/pyqt/files/sip/sip-4.19/sip-4.19.tar.gz
# - tar zxvf sip-4.19.tar.gz
# - (cd sip-4.19 && python configure.py -d /Library/Python/2.7/site-packages --arch x86_64 && make && sudo make install)
# # NOTE: produces insane amounts of output...
# - brew install -v cartr/qt4/qt --with-qt3support --without-webkit | sed -e's/ .* / ... /'
# - brew linkapps qt
# - curl -L -O http://sourceforge.net/projects/pyqt/files/PyQt4/PyQt-4.12/PyQt4_gpl_mac-4.12.tar.gz
# - tar zxvf PyQt4_gpl_mac-4.12.tar.gz
# - cd PyQt4_gpl_mac-4.12
# - python configure.py --help
# - python configure.py -d /Library/Python/2.7/site-packages --use-arch=x86_64 --confirm-license
# - make
# - sudo make install
# - cd -
# - which python pip
# - python --version
# - sudo -H easy_install-2.7 lxml || true
# - python -c 'import sys; print(sys.path)'
# - python -c 'import lxml'
# - make qt4py2
# - python -c 'help("modules")'
# - make testpy2
# OS X 10.11 Python 3 + QT4
#- os: osx
# osx_image: xcode7.3 # OS X 10.11
# sudo: required
# language: generic
# python: "3.6"
# env:
# - QT=4
# before_install:
# - brew install libxml2
# - brew install python3
# - which python pip python3 pip3 || true
# - curl -L -O https://sourceforge.net/projects/pyqt/files/sip/sip-4.19/sip-4.19.tar.gz
# - tar zxvf sip-4.19.tar.gz
# - ( cd sip-4.19 && python3 configure.py -d /Library/Python/3.6/site-packages --arch x86_64 && make && sudo make install )
# # NOTE: produces insane amounts of output...
# - brew install -v cartr/qt4/qt --with-qt3support --without-webkit | sed -e's/ .* / ... /'
# - brew linkapps qt
# - curl -L -O http://sourceforge.net/projects/pyqt/files/PyQt4/PyQt-4.12/PyQt4_gpl_mac-4.12.tar.gz
# - tar zxvf PyQt4_gpl_mac-4.12.tar.gz
# - cd PyQt4_gpl_mac-4.12
# - python3 configure.py --help
# - python3 configure.py -d /Library/Python/3.6/site-packages --use-arch=x86_64 --confirm-license
# - make
# - sudo make install
# - cd -
# - which python3 pip3
# - python3 --version
# - sudo -H easy_install-3.6 lxml || true
# - python3 -c 'import sys; print(sys.path)'
# - python3 -c 'import lxml'
# - make qt4py3
# - python3 -c 'help("modules")'
# - make testpy3
script:
- exit 0

View File

@ -1,6 +1,26 @@
# ex: set ts=8 noet:
all: resources.py
all: qt4
%.py: %.qrc
pyrcc4 -o $@ $<
test: testpy2
testpy2:
python -m unittest discover tests
testpy3:
python3 -m unittest discover tests
qt4: qt4py2
qt5: qt4py3
qt4py2:
pyrcc4 -py2 -o resources.py resources.qrc
qt4py3:
pyrcc4 -py3 -o resources.py resources.qrc
qt5py3:
pyrcc5 -o resources.py resources.qrc
.PHONY: test

View File

@ -10,6 +10,17 @@ import subprocess
from functools import partial
from collections import defaultdict
try:
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
except ImportError:
# needed for py3+qt4
# ref: http://pyqt.sourceforge.net/Docs/PyQt4/incompatible_apis.html
# ref: http://stackoverflow.com/questions/21217399/pyqt4-qtcore-qvariant-object-instead-of-a-string
if sys.version_info.major >= 3:
import sip
sip.setapi('QVariant', 2)
from PyQt4.QtGui import *
from PyQt4.QtCore import *
@ -29,6 +40,24 @@ __appname__ = 'labelImg'
### Utility functions and classes.
def u(x):
'''py2/py3 unicode helper'''
try:
return x.decode('utf8') # py2
except AttributeError:
return x # py3
def have_qstring():
'''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.'))
def util_qt_strlistclass():
return QStringList if have_qstring() else list
class WindowMixin(object):
def menu(self, title, actions=None):
menu = self.menuBar().addMenu(title)
@ -47,8 +76,16 @@ class WindowMixin(object):
return toolbar
# PyQt5: TypeError: unhashable type: 'QListWidgetItem'
class HashableQListWidgetItem(QListWidgetItem):
def __init__(self, *args):
super(HashableQListWidgetItem, self).__init__(*args)
def __hash__(self):
return hash(id(self))
class MainWindow(QMainWindow, WindowMixin):
FIT_WINDOW, FIT_WIDTH, MANUAL_ZOOM = range(3)
FIT_WINDOW, FIT_WIDTH, MANUAL_ZOOM = list(range(3))
def __init__(self, filename=None):
super(MainWindow, self).__init__()
@ -323,39 +360,65 @@ class MainWindow(QMainWindow, WindowMixin):
# XXX: Could be completely declarative.
# Restore application settings.
if have_qstring():
types = {
'filename': QString,
'recentFiles': QStringList,
'window/size': QSize,
'window/position': QPoint,
'window/geometry': QByteArray,
'line/color': QColor,
'fill/color': QColor,
'advanced': bool,
# Docks and toolbars:
'window/state': QByteArray,
'savedir': QString,
'lastOpenDir': QString,
}
else:
types = {
'filename': str,
'recentFiles': list,
'window/size': QSize,
'window/position': QPoint,
'window/geometry': QByteArray,
'line/color': QColor,
'fill/color': QColor,
'advanced': bool,
# Docks and toolbars:
'window/state': QByteArray,
'savedir': str,
'lastOpenDir': str,
}
self.settings = settings = Settings(types)
self.recentFiles = list(settings['recentFiles'])
self.recentFiles = list(settings.get('recentFiles', []))
size = settings.get('window/size', QSize(600, 500))
position = settings.get('window/position', QPoint(0, 0))
self.resize(size)
self.move(position)
saveDir = settings.get('savedir', None)
self.lastOpenDir = settings.get('lastOpenDir', None)
if os.path.exists(unicode(saveDir)):
self.defaultSaveDir = unicode(saveDir)
if os.path.exists(str(saveDir)):
self.defaultSaveDir = str(saveDir)
self.statusBar().showMessage('%s started. Annotation will be saved to %s' %(__appname__, self.defaultSaveDir))
self.statusBar().show()
# or simply:
#self.restoreGeometry(settings['window/geometry']
self.restoreState(settings['window/state'])
self.restoreState(settings.get('window/state', QByteArray()))
self.lineColor = QColor(settings.get('line/color', Shape.line_color))
self.fillColor = QColor(settings.get('fill/color', Shape.fill_color))
Shape.line_color = self.lineColor
Shape.fill_color = self.fillColor
if settings.get('advanced', QVariant()).toBool():
def xbool(x):
if isinstance(x, QVariant):
return x.toBool()
return bool(x)
if xbool(settings.get('advanced', False)):
self.actions.advancedMode.setChecked(True)
self.toggleAdvancedMode()
@ -472,7 +535,7 @@ class MainWindow(QMainWindow, WindowMixin):
self.actions.editMode.setEnabled(not drawing)
if not drawing and self.beginner():
# Cancel creation.
print 'Cancel creation.'
print('Cancel creation.')
self.canvas.setEditing(True)
self.canvas.restoreCursor()
self.actions.create.setEnabled(True)
@ -493,7 +556,7 @@ class MainWindow(QMainWindow, WindowMixin):
def updateFileMenu(self):
current = self.filename
def exists(filename):
return os.path.exists(unicode(filename))
return os.path.exists(filename)
menu = self.menus.recentFiles
menu.clear()
files = [f for f in self.recentFiles if f != current and exists(f)]
@ -518,7 +581,7 @@ class MainWindow(QMainWindow, WindowMixin):
# Tzutalin 20160906 : Add file list and dock to move faster
def fileitemDoubleClicked(self, item=None):
currIndex = self.mImgList.index(str(item.text()))
currIndex = self.mImgList.index(item.text())
if currIndex < len(self.mImgList):
filename = self.mImgList[currIndex]
if filename:
@ -531,7 +594,7 @@ class MainWindow(QMainWindow, WindowMixin):
else:
shape = self.canvas.selectedShape
if shape:
self.labelList.setItemSelected(self.shapesToItems[shape], True)
self.shapesToItems[shape].setSelected(True)
else:
self.labelList.clearSelection()
self.actions.delete.setEnabled(selected)
@ -541,7 +604,7 @@ class MainWindow(QMainWindow, WindowMixin):
self.actions.shapeFillColor.setEnabled(selected)
def addLabel(self, shape):
item = QListWidgetItem(shape.label)
item = HashableQListWidgetItem(shape.label)
item.setFlags(item.flags() | Qt.ItemIsUserCheckable)
item.setCheckState(Qt.Checked)
self.itemsToShapes[item] = shape
@ -574,7 +637,7 @@ class MainWindow(QMainWindow, WindowMixin):
def saveLabels(self, filename):
lf = LabelFile()
def format_shape(s):
return dict(label=unicode(s.label),
return dict(label=s.label,
line_color=s.line_color.getRgb()\
if s.line_color != self.lineColor else None,
fill_color=s.fill_color.getRgb()\
@ -585,16 +648,16 @@ class MainWindow(QMainWindow, WindowMixin):
# Can add differrent annotation formats here
try:
if self.usingPascalVocFormat is True:
print 'savePascalVocFormat save to:' + filename
lf.savePascalVocFormat(filename, shapes, unicode(self.filename), self.imageData,
print('savePascalVocFormat save to:' + filename)
lf.savePascalVocFormat(filename, shapes, str(self.filename), self.imageData,
self.lineColor.getRgb(), self.fillColor.getRgb())
else:
lf.save(filename, shapes, unicode(self.filename), self.imageData,
lf.save(filename, shapes, str(self.filename), self.imageData,
self.lineColor.getRgb(), self.fillColor.getRgb())
self.labelFile = lf
self.filename = filename
return True
except LabelFileError, e:
except LabelFileError as e:
self.errorMessage(u'Error saving label data',
u'<b>%s</b>' % e)
return False
@ -612,9 +675,9 @@ class MainWindow(QMainWindow, WindowMixin):
def labelItemChanged(self, item):
shape = self.itemsToShapes[item]
label = unicode(item.text())
label = item.text()
if label != shape.label:
shape.label = unicode(item.text())
shape.label = item.text()
self.setDirty()
else: # User probably changed item visibility
self.canvas.setShapeVisible(shape, item.checkState() == Qt.Checked)
@ -677,7 +740,7 @@ class MainWindow(QMainWindow, WindowMixin):
self.adjustScale()
def togglePolygons(self, value):
for item, shape in self.itemsToShapes.iteritems():
for item, shape in self.itemsToShapes.items():
item.setCheckState(Qt.Checked if value else Qt.Unchecked)
def loadFile(self, filename=None):
@ -685,21 +748,20 @@ class MainWindow(QMainWindow, WindowMixin):
self.resetState()
self.canvas.setEnabled(False)
if filename is None:
filename = self.settings['filename']
filename = unicode(filename)
filename = self.settings.get('filename')
# Tzutalin 20160906 : Add file list and dock to move faster
# Highlight the file item
if filename and self.fileListWidget.count() > 0:
index = self.mImgList.index(filename)
fileWidgetItem = self.fileListWidget.item(index)
self.fileListWidget.setItemSelected(fileWidgetItem, True)
fileWidgetItem.setSelected(True)
if QFile.exists(filename):
if filename and QFile.exists(filename):
if LabelFile.isLabelFile(filename):
try:
self.labelFile = LabelFile(filename)
except LabelFileError, e:
except LabelFileError as e:
self.errorMessage(u'Error opening file',
(u"<p><b>%s</b></p>"
u"<p>Make sure <i>%s</i> is a valid label file.")\
@ -720,7 +782,7 @@ class MainWindow(QMainWindow, WindowMixin):
u"<p>Make sure <i>%s</i> is a valid image file." % filename)
self.status("Error reading %s" % filename)
return False
self.status("Loaded %s" % os.path.basename(unicode(filename)))
self.status("Loaded %s" % os.path.basename(str(filename)))
self.image = image
self.filename = filename
self.canvas.loadPixmap(QPixmap.fromImage(image))
@ -782,7 +844,7 @@ class MainWindow(QMainWindow, WindowMixin):
s = self.settings
# If it loads images from dir, don't load it at the begining
if self.dirname is None:
s['filename'] = self.filename if self.filename else QString()
s['filename'] = self.filename if self.filename else ''
else:
s['filename'] = ''
@ -820,17 +882,18 @@ class MainWindow(QMainWindow, WindowMixin):
for file in files:
if file.lower().endswith(tuple(extensions)):
relatviePath = os.path.join(root, file)
images.append(os.path.abspath(relatviePath))
path = u(os.path.abspath(relatviePath))
images.append(path)
images.sort(key=lambda x: x.lower())
return images
def changeSavedir(self, _value=False):
if self.defaultSaveDir is not None:
path = unicode(self.defaultSaveDir)
path = str(self.defaultSaveDir)
else:
path = '.'
dirpath = unicode(QFileDialog.getExistingDirectory(self,
dirpath = str(QFileDialog.getExistingDirectory(self,
'%s - Save to the directory' % __appname__, path, QFileDialog.ShowDirsOnly
| QFileDialog.DontResolveSymlinks))
@ -844,14 +907,14 @@ class MainWindow(QMainWindow, WindowMixin):
if self.filename is None:
return
path = os.path.dirname(unicode(self.filename))\
path = os.path.dirname(str(self.filename))\
if self.filename else '.'
if self.usingPascalVocFormat:
formats = ['*.%s' % unicode(fmt).lower()\
formats = ['*.%s' % str(fmt).lower()\
for fmt in QImageReader.supportedImageFormats()]
filters = "Open Annotation XML file (%s)" % \
' '.join(formats + ['*.xml'])
filename = unicode(QFileDialog.getOpenFileName(self,
filename = str(QFileDialog.getOpenFileName(self,
'%s - Choose a xml file' % __appname__, path, filters))
self.loadPascalXMLByFilename(filename)
@ -859,13 +922,13 @@ class MainWindow(QMainWindow, WindowMixin):
if not self.mayContinue():
return
path = os.path.dirname(unicode(self.filename))\
path = os.path.dirname(self.filename)\
if self.filename else '.'
if self.lastOpenDir is not None and len(self.lastOpenDir) > 1:
path = self.lastOpenDir
dirpath = unicode(QFileDialog.getExistingDirectory(self,
dirpath = str(QFileDialog.getExistingDirectory(self,
'%s - Open Directory' % __appname__, path, QFileDialog.ShowDirsOnly
| QFileDialog.DontResolveSymlinks))
@ -921,13 +984,13 @@ class MainWindow(QMainWindow, WindowMixin):
def openFile(self, _value=False):
if not self.mayContinue():
return
path = os.path.dirname(unicode(self.filename))\
path = os.path.dirname(str(self.filename))\
if self.filename else '.'
formats = ['*.%s' % unicode(fmt).lower()\
formats = ['*.%s' % str(fmt).lower()\
for fmt in QImageReader.supportedImageFormats()]
filters = "Image & Label files (%s)" % \
' '.join(formats + ['*%s' % LabelFile.suffix])
filename = unicode(QFileDialog.getOpenFileName(self,
filename = str(QFileDialog.getOpenFileName(self,
'%s - Choose Image or Label file' % __appname__, path, filters))
if filename:
self.loadFile(filename)
@ -936,7 +999,7 @@ class MainWindow(QMainWindow, WindowMixin):
assert not self.image.isNull(), "cannot save empty image"
if self.hasLabels():
if self.defaultSaveDir is not None and len(str(self.defaultSaveDir)):
print 'handle the image:' + self.filename
print('handle the image:' + self.filename)
imgFileName = os.path.basename(self.filename)
savedFileName = os.path.splitext(imgFileName)[0] + LabelFile.suffix
savedPath = os.path.join(str(self.defaultSaveDir), savedFileName)
@ -957,7 +1020,6 @@ class MainWindow(QMainWindow, WindowMixin):
dlg = QFileDialog(self, caption, openDialogPath, filters)
dlg.setDefaultSuffix(LabelFile.suffix[1:])
dlg.setAcceptMode(QFileDialog.AcceptSave)
dlg.setConfirmOverwrite(True)
filenameWithoutExtension = os.path.splitext(self.filename)[0]
dlg.selectFile(filenameWithoutExtension)
dlg.setOption(QFileDialog.DontUseNativeDialog, False)
@ -1002,7 +1064,7 @@ class MainWindow(QMainWindow, WindowMixin):
'<p><b>%s</b></p>%s' % (title, message))
def currentPath(self):
return os.path.dirname(unicode(self.filename)) if self.filename else '.'
return os.path.dirname(str(self.filename)) if self.filename else '.'
def chooseColor1(self):
color = self.colorDialog.getColor(self.lineColor, u'Choose line color',
@ -1098,10 +1160,20 @@ class Settings(object):
def _cast(self, key, value):
# XXX: Very nasty way of converting types to QVariant methods :P
t = self.types[key]
if t != QVariant:
print('_cast', key, repr(value))
t = self.types.get(key)
print('t', t)
if t is not None and t != QVariant:
if t is str:
return str(value)
else:
# XXX: awful
try:
method = getattr(QVariant, re.sub('^Q', 'to', t.__name__, count=1))
return method(value)
except AttributeError as e:
print(e)
return value
return value
@ -1115,13 +1187,21 @@ def read(filename, default=None):
except:
return default
def main(argv):
"""Standard boilerplate Qt application code."""
def get_main_app(argv=[]):
"""
Standard boilerplate Qt application code.
Do everything but app.exec_() -- so that we can test the application in one thread
"""
app = QApplication(argv)
app.setApplicationName(__appname__)
app.setWindowIcon(newIcon("app"))
win = MainWindow(argv[1] if len(argv) == 2 else None)
win.show()
return app, win
def main(argv):
'''construct main app and run it'''
app, _win = get_main_app(argv)
return app.exec_()
if __name__ == '__main__':

View File

@ -1,5 +1,12 @@
try:
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
except ImportError:
from PyQt4.QtGui import *
from PyQt4.QtCore import *
#from PyQt4.QtOpenGL import *
from shape import Shape
@ -20,7 +27,7 @@ class Canvas(QWidget):
shapeMoved = pyqtSignal()
drawingPolygon = pyqtSignal(bool)
CREATE, EDIT = range(2)
CREATE, EDIT = list(range(2))
epsilon = 11.0
@ -85,7 +92,7 @@ class Canvas(QWidget):
def mouseMoveEvent(self, ev):
"""Update line with last point and current coordinates."""
pos = self.transformPos(ev.posF())
pos = self.transformPos(ev.pos())
self.restoreCursor()
@ -169,7 +176,8 @@ class Canvas(QWidget):
self.hVertex, self.hShape = None, None
def mousePressEvent(self, ev):
pos = self.transformPos(ev.posF())
pos = self.transformPos(ev.pos())
if ev.button() == Qt.LeftButton:
if self.drawing():
if self.current and self.current.reachMaxPoints() is False:
@ -456,12 +464,14 @@ class Canvas(QWidget):
return QPointF(min(max(0, x2), max(x3, x4)), y3)
return QPointF(x, y)
def intersectingEdges(self, (x1, y1), (x2, y2), points):
def intersectingEdges(self, x1y1, x2y2, points):
"""For each edge formed by `points', yield the intersection
with the line segment `(x1,y1) - (x2,y2)`, if it exists.
Also return the distance of `(x2,y2)' to the middle of the
edge along with its index, so that the one closest can be chosen."""
for i in xrange(4):
x1, y1 = x1y1
x2, y2 = x2y2
for i in range(4):
x3, y3 = points[i]
x4, y4 = points[(i+1) % 4]
denom = (y4-y3) * (x2 - x1) - (x4 - x3) * (y2 - y1)
@ -506,7 +516,7 @@ class Canvas(QWidget):
def keyPressEvent(self, ev):
key = ev.key()
if key == Qt.Key_Escape and self.current:
print 'ESC press'
print('ESC press')
self.current = None
self.drawingPolygon.emit(False)
self.update()

View File

@ -1,3 +1,8 @@
try:
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import QColorDialog, QDialogButtonBox
except ImportError:
from PyQt4.QtGui import *
from PyQt4.QtCore import *

View File

@ -1,3 +1,8 @@
try:
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
except ImportError:
from PyQt4.QtGui import *
from PyQt4.QtCore import *
@ -32,11 +37,20 @@ class LabelDialog(QDialog):
self.setLayout(layout)
def validate(self):
try:
if self.edit.text().trimmed():
self.accept()
except AttributeError:
# PyQt5: AttributeError: 'str' object has no attribute 'trimmed'
if self.edit.text().strip():
self.accept()
def postProcess(self):
try:
self.edit.setText(self.edit.text().trimmed())
except AttributeError:
# PyQt5: AttributeError: 'str' object has no attribute 'trimmed'
self.edit.setText(self.edit.text())
def popUp(self, text='', move=True):
self.edit.setText(text)
@ -47,6 +61,10 @@ class LabelDialog(QDialog):
return self.edit.text() if self.exec_() else None
def listItemClick(self, tQListWidgetItem):
try:
text = tQListWidgetItem.text().trimmed()
except AttributeError:
# PyQt5: AttributeError: 'str' object has no attribute 'trimmed'
text = tQListWidgetItem.text().strip()
self.edit.setText(text)
self.validate()

View File

@ -1,7 +1,11 @@
# Copyright (c) 2016 Tzutalin
# Create by TzuTaLin <tzu.ta.lin@gmail.com>
try:
from PyQt5.QtGui import QImage
except ImportError:
from PyQt4.QtGui import QImage
from base64 import b64encode, b64decode
from pascal_voc_io import PascalVocWriter
import os.path

View File

@ -1,5 +1,10 @@
from math import sqrt
try:
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
except ImportError:
from PyQt4.QtGui import *
from PyQt4.QtCore import *

View File

@ -74,7 +74,11 @@ class PascalVocWriter:
for each_object in self.boxlist:
object_item = SubElement(top, 'object')
name = SubElement(object_item, 'name')
try:
name.text = unicode(each_object['name'])
except NameError:
# Py3: NameError: name 'unicode' is not defined
name.text = each_object['name']
pose = SubElement(object_item, 'pose')
pose.text = "Unspecified"
truncated = SubElement(object_item, 'truncated')
@ -101,7 +105,7 @@ class PascalVocWriter:
out_file = open(targetFile, 'w')
prettifyResult = self.prettify(root)
out_file.write(prettifyResult)
out_file.write(prettifyResult.decode('utf8'))
out_file.close()

View File

@ -1,6 +1,11 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
try:
from PyQt5.QtGui import *
from PyQt5.QtCore import *
except ImportError:
from PyQt4.QtGui import *
from PyQt4.QtCore import *

View File

@ -1,6 +1,12 @@
try:
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
except ImportError:
from PyQt4.QtGui import *
from PyQt4.QtCore import *
class ToolBar(QToolBar):
def __init__(self, title):
super(ToolBar, self).__init__(title)

View File

@ -1,3 +1,8 @@
try:
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
except ImportError:
from PyQt4.QtGui import *
from PyQt4.QtCore import *

21
tests/test.py Normal file
View File

@ -0,0 +1,21 @@
from unittest import TestCase
from labelImg import get_main_app
class TestMainWindow(TestCase):
app = None
win = None
def setUp(self):
self.app, self.win = get_main_app()
def tearDown(self):
self.win.close()
self.app.quit()
def test_noop(self):
pass