commit
43b730b6e6
293
.travis.yml
293
.travis.yml
@ -1,14 +1,293 @@
|
|||||||
language: python
|
# vim: set ts=2 et:
|
||||||
python:
|
|
||||||
- "2.7"
|
|
||||||
|
|
||||||
|
# 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:
|
addons:
|
||||||
apt:
|
apt:
|
||||||
packages:
|
packages:
|
||||||
- cmake
|
- cmake
|
||||||
|
- python-qt4
|
||||||
- pyqt4-dev-tools
|
- pyqt4-dev-tools
|
||||||
|
- xvfb
|
||||||
|
before_install:
|
||||||
|
- sudo pip install lxml
|
||||||
|
- make qt4py2
|
||||||
|
- xvfb-run make testpy2
|
||||||
|
|
||||||
# command to install dependencies
|
# Python 2.7 + QT4
|
||||||
#install: "pip install --user -r requirements.txt"
|
- os: linux
|
||||||
# command to run tests
|
dist: trusty
|
||||||
script: make all
|
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
|
||||||
|
|||||||
26
Makefile
26
Makefile
@ -1,6 +1,26 @@
|
|||||||
|
# ex: set ts=8 noet:
|
||||||
|
|
||||||
all: resources.py
|
all: qt4
|
||||||
|
|
||||||
%.py: %.qrc
|
test: testpy2
|
||||||
pyrcc4 -o $@ $<
|
|
||||||
|
|
||||||
|
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
|
||||||
|
|||||||
168
labelImg.py
168
labelImg.py
@ -10,6 +10,17 @@ import subprocess
|
|||||||
from functools import partial
|
from functools import partial
|
||||||
from collections import defaultdict
|
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.QtGui import *
|
||||||
from PyQt4.QtCore import *
|
from PyQt4.QtCore import *
|
||||||
|
|
||||||
@ -29,6 +40,24 @@ __appname__ = 'labelImg'
|
|||||||
|
|
||||||
### Utility functions and classes.
|
### 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):
|
class WindowMixin(object):
|
||||||
def menu(self, title, actions=None):
|
def menu(self, title, actions=None):
|
||||||
menu = self.menuBar().addMenu(title)
|
menu = self.menuBar().addMenu(title)
|
||||||
@ -47,8 +76,16 @@ class WindowMixin(object):
|
|||||||
return toolbar
|
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):
|
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):
|
def __init__(self, filename=None):
|
||||||
super(MainWindow, self).__init__()
|
super(MainWindow, self).__init__()
|
||||||
@ -323,39 +360,65 @@ class MainWindow(QMainWindow, WindowMixin):
|
|||||||
|
|
||||||
# XXX: Could be completely declarative.
|
# XXX: Could be completely declarative.
|
||||||
# Restore application settings.
|
# Restore application settings.
|
||||||
|
|
||||||
|
if have_qstring():
|
||||||
types = {
|
types = {
|
||||||
'filename': QString,
|
'filename': QString,
|
||||||
'recentFiles': QStringList,
|
'recentFiles': QStringList,
|
||||||
'window/size': QSize,
|
'window/size': QSize,
|
||||||
'window/position': QPoint,
|
'window/position': QPoint,
|
||||||
'window/geometry': QByteArray,
|
'window/geometry': QByteArray,
|
||||||
|
'line/color': QColor,
|
||||||
|
'fill/color': QColor,
|
||||||
|
'advanced': bool,
|
||||||
# Docks and toolbars:
|
# Docks and toolbars:
|
||||||
'window/state': QByteArray,
|
'window/state': QByteArray,
|
||||||
'savedir': QString,
|
'savedir': QString,
|
||||||
'lastOpenDir': 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.settings = settings = Settings(types)
|
||||||
self.recentFiles = list(settings['recentFiles'])
|
self.recentFiles = list(settings.get('recentFiles', []))
|
||||||
size = settings.get('window/size', QSize(600, 500))
|
size = settings.get('window/size', QSize(600, 500))
|
||||||
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 = settings.get('savedir', None)
|
saveDir = settings.get('savedir', None)
|
||||||
self.lastOpenDir = settings.get('lastOpenDir', None)
|
self.lastOpenDir = settings.get('lastOpenDir', None)
|
||||||
if os.path.exists(unicode(saveDir)):
|
if os.path.exists(str(saveDir)):
|
||||||
self.defaultSaveDir = unicode(saveDir)
|
self.defaultSaveDir = str(saveDir)
|
||||||
self.statusBar().showMessage('%s started. Annotation will be saved to %s' %(__appname__, self.defaultSaveDir))
|
self.statusBar().showMessage('%s started. Annotation will be saved to %s' %(__appname__, self.defaultSaveDir))
|
||||||
self.statusBar().show()
|
self.statusBar().show()
|
||||||
|
|
||||||
# or simply:
|
# or simply:
|
||||||
#self.restoreGeometry(settings['window/geometry']
|
#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.lineColor = QColor(settings.get('line/color', Shape.line_color))
|
||||||
self.fillColor = QColor(settings.get('fill/color', Shape.fill_color))
|
self.fillColor = QColor(settings.get('fill/color', Shape.fill_color))
|
||||||
Shape.line_color = self.lineColor
|
Shape.line_color = self.lineColor
|
||||||
Shape.fill_color = self.fillColor
|
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.actions.advancedMode.setChecked(True)
|
||||||
self.toggleAdvancedMode()
|
self.toggleAdvancedMode()
|
||||||
|
|
||||||
@ -472,7 +535,7 @@ class MainWindow(QMainWindow, WindowMixin):
|
|||||||
self.actions.editMode.setEnabled(not drawing)
|
self.actions.editMode.setEnabled(not drawing)
|
||||||
if not drawing and self.beginner():
|
if not drawing and self.beginner():
|
||||||
# Cancel creation.
|
# Cancel creation.
|
||||||
print 'Cancel creation.'
|
print('Cancel creation.')
|
||||||
self.canvas.setEditing(True)
|
self.canvas.setEditing(True)
|
||||||
self.canvas.restoreCursor()
|
self.canvas.restoreCursor()
|
||||||
self.actions.create.setEnabled(True)
|
self.actions.create.setEnabled(True)
|
||||||
@ -493,7 +556,7 @@ class MainWindow(QMainWindow, WindowMixin):
|
|||||||
def updateFileMenu(self):
|
def updateFileMenu(self):
|
||||||
current = self.filename
|
current = self.filename
|
||||||
def exists(filename):
|
def exists(filename):
|
||||||
return os.path.exists(unicode(filename))
|
return os.path.exists(filename)
|
||||||
menu = self.menus.recentFiles
|
menu = self.menus.recentFiles
|
||||||
menu.clear()
|
menu.clear()
|
||||||
files = [f for f in self.recentFiles if f != current and exists(f)]
|
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
|
# Tzutalin 20160906 : Add file list and dock to move faster
|
||||||
def fileitemDoubleClicked(self, item=None):
|
def fileitemDoubleClicked(self, item=None):
|
||||||
currIndex = self.mImgList.index(str(item.text()))
|
currIndex = self.mImgList.index(item.text())
|
||||||
if currIndex < len(self.mImgList):
|
if currIndex < len(self.mImgList):
|
||||||
filename = self.mImgList[currIndex]
|
filename = self.mImgList[currIndex]
|
||||||
if filename:
|
if filename:
|
||||||
@ -531,7 +594,7 @@ class MainWindow(QMainWindow, WindowMixin):
|
|||||||
else:
|
else:
|
||||||
shape = self.canvas.selectedShape
|
shape = self.canvas.selectedShape
|
||||||
if shape:
|
if shape:
|
||||||
self.labelList.setItemSelected(self.shapesToItems[shape], True)
|
self.shapesToItems[shape].setSelected(True)
|
||||||
else:
|
else:
|
||||||
self.labelList.clearSelection()
|
self.labelList.clearSelection()
|
||||||
self.actions.delete.setEnabled(selected)
|
self.actions.delete.setEnabled(selected)
|
||||||
@ -541,7 +604,7 @@ class MainWindow(QMainWindow, WindowMixin):
|
|||||||
self.actions.shapeFillColor.setEnabled(selected)
|
self.actions.shapeFillColor.setEnabled(selected)
|
||||||
|
|
||||||
def addLabel(self, shape):
|
def addLabel(self, shape):
|
||||||
item = QListWidgetItem(shape.label)
|
item = HashableQListWidgetItem(shape.label)
|
||||||
item.setFlags(item.flags() | Qt.ItemIsUserCheckable)
|
item.setFlags(item.flags() | Qt.ItemIsUserCheckable)
|
||||||
item.setCheckState(Qt.Checked)
|
item.setCheckState(Qt.Checked)
|
||||||
self.itemsToShapes[item] = shape
|
self.itemsToShapes[item] = shape
|
||||||
@ -574,7 +637,7 @@ class MainWindow(QMainWindow, WindowMixin):
|
|||||||
def saveLabels(self, filename):
|
def saveLabels(self, filename):
|
||||||
lf = LabelFile()
|
lf = LabelFile()
|
||||||
def format_shape(s):
|
def format_shape(s):
|
||||||
return dict(label=unicode(s.label),
|
return dict(label=s.label,
|
||||||
line_color=s.line_color.getRgb()\
|
line_color=s.line_color.getRgb()\
|
||||||
if s.line_color != self.lineColor else None,
|
if s.line_color != self.lineColor else None,
|
||||||
fill_color=s.fill_color.getRgb()\
|
fill_color=s.fill_color.getRgb()\
|
||||||
@ -585,16 +648,16 @@ class MainWindow(QMainWindow, WindowMixin):
|
|||||||
# Can add differrent annotation formats here
|
# Can add differrent annotation formats here
|
||||||
try:
|
try:
|
||||||
if self.usingPascalVocFormat is True:
|
if self.usingPascalVocFormat is True:
|
||||||
print 'savePascalVocFormat save to:' + filename
|
print('savePascalVocFormat save to:' + filename)
|
||||||
lf.savePascalVocFormat(filename, shapes, unicode(self.filename), self.imageData,
|
lf.savePascalVocFormat(filename, shapes, str(self.filename), self.imageData,
|
||||||
self.lineColor.getRgb(), self.fillColor.getRgb())
|
self.lineColor.getRgb(), self.fillColor.getRgb())
|
||||||
else:
|
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.lineColor.getRgb(), self.fillColor.getRgb())
|
||||||
self.labelFile = lf
|
self.labelFile = lf
|
||||||
self.filename = filename
|
self.filename = filename
|
||||||
return True
|
return True
|
||||||
except LabelFileError, e:
|
except LabelFileError as e:
|
||||||
self.errorMessage(u'Error saving label data',
|
self.errorMessage(u'Error saving label data',
|
||||||
u'<b>%s</b>' % e)
|
u'<b>%s</b>' % e)
|
||||||
return False
|
return False
|
||||||
@ -612,9 +675,9 @@ class MainWindow(QMainWindow, WindowMixin):
|
|||||||
|
|
||||||
def labelItemChanged(self, item):
|
def labelItemChanged(self, item):
|
||||||
shape = self.itemsToShapes[item]
|
shape = self.itemsToShapes[item]
|
||||||
label = unicode(item.text())
|
label = item.text()
|
||||||
if label != shape.label:
|
if label != shape.label:
|
||||||
shape.label = unicode(item.text())
|
shape.label = item.text()
|
||||||
self.setDirty()
|
self.setDirty()
|
||||||
else: # User probably changed item visibility
|
else: # User probably changed item visibility
|
||||||
self.canvas.setShapeVisible(shape, item.checkState() == Qt.Checked)
|
self.canvas.setShapeVisible(shape, item.checkState() == Qt.Checked)
|
||||||
@ -677,7 +740,7 @@ class MainWindow(QMainWindow, WindowMixin):
|
|||||||
self.adjustScale()
|
self.adjustScale()
|
||||||
|
|
||||||
def togglePolygons(self, value):
|
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)
|
item.setCheckState(Qt.Checked if value else Qt.Unchecked)
|
||||||
|
|
||||||
def loadFile(self, filename=None):
|
def loadFile(self, filename=None):
|
||||||
@ -685,21 +748,20 @@ class MainWindow(QMainWindow, WindowMixin):
|
|||||||
self.resetState()
|
self.resetState()
|
||||||
self.canvas.setEnabled(False)
|
self.canvas.setEnabled(False)
|
||||||
if filename is None:
|
if filename is None:
|
||||||
filename = self.settings['filename']
|
filename = self.settings.get('filename')
|
||||||
filename = unicode(filename)
|
|
||||||
|
|
||||||
# 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 filename and self.fileListWidget.count() > 0:
|
if filename and self.fileListWidget.count() > 0:
|
||||||
index = self.mImgList.index(filename)
|
index = self.mImgList.index(filename)
|
||||||
fileWidgetItem = self.fileListWidget.item(index)
|
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):
|
if LabelFile.isLabelFile(filename):
|
||||||
try:
|
try:
|
||||||
self.labelFile = LabelFile(filename)
|
self.labelFile = LabelFile(filename)
|
||||||
except LabelFileError, e:
|
except LabelFileError as e:
|
||||||
self.errorMessage(u'Error opening file',
|
self.errorMessage(u'Error opening file',
|
||||||
(u"<p><b>%s</b></p>"
|
(u"<p><b>%s</b></p>"
|
||||||
u"<p>Make sure <i>%s</i> is a valid label file.")\
|
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)
|
u"<p>Make sure <i>%s</i> is a valid image file." % filename)
|
||||||
self.status("Error reading %s" % filename)
|
self.status("Error reading %s" % filename)
|
||||||
return False
|
return False
|
||||||
self.status("Loaded %s" % os.path.basename(unicode(filename)))
|
self.status("Loaded %s" % os.path.basename(str(filename)))
|
||||||
self.image = image
|
self.image = image
|
||||||
self.filename = filename
|
self.filename = filename
|
||||||
self.canvas.loadPixmap(QPixmap.fromImage(image))
|
self.canvas.loadPixmap(QPixmap.fromImage(image))
|
||||||
@ -782,7 +844,7 @@ class MainWindow(QMainWindow, WindowMixin):
|
|||||||
s = self.settings
|
s = self.settings
|
||||||
# If it loads images from dir, don't load it at the begining
|
# If it loads images from dir, don't load it at the begining
|
||||||
if self.dirname is None:
|
if self.dirname is None:
|
||||||
s['filename'] = self.filename if self.filename else QString()
|
s['filename'] = self.filename if self.filename else ''
|
||||||
else:
|
else:
|
||||||
s['filename'] = ''
|
s['filename'] = ''
|
||||||
|
|
||||||
@ -820,17 +882,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)
|
||||||
images.append(os.path.abspath(relatviePath))
|
path = u(os.path.abspath(relatviePath))
|
||||||
|
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 = unicode(self.defaultSaveDir)
|
path = str(self.defaultSaveDir)
|
||||||
else:
|
else:
|
||||||
path = '.'
|
path = '.'
|
||||||
|
|
||||||
dirpath = unicode(QFileDialog.getExistingDirectory(self,
|
dirpath = str(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))
|
||||||
|
|
||||||
@ -844,14 +907,14 @@ class MainWindow(QMainWindow, WindowMixin):
|
|||||||
if self.filename is None:
|
if self.filename is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
path = os.path.dirname(unicode(self.filename))\
|
path = os.path.dirname(str(self.filename))\
|
||||||
if self.filename else '.'
|
if self.filename else '.'
|
||||||
if self.usingPascalVocFormat:
|
if self.usingPascalVocFormat:
|
||||||
formats = ['*.%s' % unicode(fmt).lower()\
|
formats = ['*.%s' % str(fmt).lower()\
|
||||||
for fmt in QImageReader.supportedImageFormats()]
|
for fmt in QImageReader.supportedImageFormats()]
|
||||||
filters = "Open Annotation XML file (%s)" % \
|
filters = "Open Annotation XML file (%s)" % \
|
||||||
' '.join(formats + ['*.xml'])
|
' '.join(formats + ['*.xml'])
|
||||||
filename = unicode(QFileDialog.getOpenFileName(self,
|
filename = str(QFileDialog.getOpenFileName(self,
|
||||||
'%s - Choose a xml file' % __appname__, path, filters))
|
'%s - Choose a xml file' % __appname__, path, filters))
|
||||||
self.loadPascalXMLByFilename(filename)
|
self.loadPascalXMLByFilename(filename)
|
||||||
|
|
||||||
@ -859,13 +922,13 @@ class MainWindow(QMainWindow, WindowMixin):
|
|||||||
if not self.mayContinue():
|
if not self.mayContinue():
|
||||||
return
|
return
|
||||||
|
|
||||||
path = os.path.dirname(unicode(self.filename))\
|
path = os.path.dirname(self.filename)\
|
||||||
if self.filename else '.'
|
if self.filename else '.'
|
||||||
|
|
||||||
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 = unicode(QFileDialog.getExistingDirectory(self,
|
dirpath = str(QFileDialog.getExistingDirectory(self,
|
||||||
'%s - Open Directory' % __appname__, path, QFileDialog.ShowDirsOnly
|
'%s - Open Directory' % __appname__, path, QFileDialog.ShowDirsOnly
|
||||||
| QFileDialog.DontResolveSymlinks))
|
| QFileDialog.DontResolveSymlinks))
|
||||||
|
|
||||||
@ -921,13 +984,13 @@ 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(unicode(self.filename))\
|
path = os.path.dirname(str(self.filename))\
|
||||||
if self.filename else '.'
|
if self.filename else '.'
|
||||||
formats = ['*.%s' % unicode(fmt).lower()\
|
formats = ['*.%s' % str(fmt).lower()\
|
||||||
for fmt in QImageReader.supportedImageFormats()]
|
for fmt in QImageReader.supportedImageFormats()]
|
||||||
filters = "Image & Label files (%s)" % \
|
filters = "Image & Label files (%s)" % \
|
||||||
' '.join(formats + ['*%s' % LabelFile.suffix])
|
' '.join(formats + ['*%s' % LabelFile.suffix])
|
||||||
filename = unicode(QFileDialog.getOpenFileName(self,
|
filename = str(QFileDialog.getOpenFileName(self,
|
||||||
'%s - Choose Image or Label file' % __appname__, path, filters))
|
'%s - Choose Image or Label file' % __appname__, path, filters))
|
||||||
if filename:
|
if filename:
|
||||||
self.loadFile(filename)
|
self.loadFile(filename)
|
||||||
@ -936,7 +999,7 @@ class MainWindow(QMainWindow, WindowMixin):
|
|||||||
assert not self.image.isNull(), "cannot save empty image"
|
assert not self.image.isNull(), "cannot save empty image"
|
||||||
if self.hasLabels():
|
if self.hasLabels():
|
||||||
if self.defaultSaveDir is not None and len(str(self.defaultSaveDir)):
|
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)
|
imgFileName = os.path.basename(self.filename)
|
||||||
savedFileName = os.path.splitext(imgFileName)[0] + LabelFile.suffix
|
savedFileName = os.path.splitext(imgFileName)[0] + LabelFile.suffix
|
||||||
savedPath = os.path.join(str(self.defaultSaveDir), savedFileName)
|
savedPath = os.path.join(str(self.defaultSaveDir), savedFileName)
|
||||||
@ -957,7 +1020,6 @@ class MainWindow(QMainWindow, WindowMixin):
|
|||||||
dlg = QFileDialog(self, caption, openDialogPath, filters)
|
dlg = QFileDialog(self, caption, openDialogPath, filters)
|
||||||
dlg.setDefaultSuffix(LabelFile.suffix[1:])
|
dlg.setDefaultSuffix(LabelFile.suffix[1:])
|
||||||
dlg.setAcceptMode(QFileDialog.AcceptSave)
|
dlg.setAcceptMode(QFileDialog.AcceptSave)
|
||||||
dlg.setConfirmOverwrite(True)
|
|
||||||
filenameWithoutExtension = os.path.splitext(self.filename)[0]
|
filenameWithoutExtension = os.path.splitext(self.filename)[0]
|
||||||
dlg.selectFile(filenameWithoutExtension)
|
dlg.selectFile(filenameWithoutExtension)
|
||||||
dlg.setOption(QFileDialog.DontUseNativeDialog, False)
|
dlg.setOption(QFileDialog.DontUseNativeDialog, False)
|
||||||
@ -1002,7 +1064,7 @@ class MainWindow(QMainWindow, WindowMixin):
|
|||||||
'<p><b>%s</b></p>%s' % (title, message))
|
'<p><b>%s</b></p>%s' % (title, message))
|
||||||
|
|
||||||
def currentPath(self):
|
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):
|
def chooseColor1(self):
|
||||||
color = self.colorDialog.getColor(self.lineColor, u'Choose line color',
|
color = self.colorDialog.getColor(self.lineColor, u'Choose line color',
|
||||||
@ -1098,10 +1160,20 @@ class Settings(object):
|
|||||||
|
|
||||||
def _cast(self, key, value):
|
def _cast(self, key, value):
|
||||||
# XXX: Very nasty way of converting types to QVariant methods :P
|
# XXX: Very nasty way of converting types to QVariant methods :P
|
||||||
t = self.types[key]
|
print('_cast', key, repr(value))
|
||||||
if t != QVariant:
|
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))
|
method = getattr(QVariant, re.sub('^Q', 'to', t.__name__, count=1))
|
||||||
return method(value)
|
return method(value)
|
||||||
|
except AttributeError as e:
|
||||||
|
print(e)
|
||||||
|
return value
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
||||||
@ -1115,13 +1187,21 @@ def read(filename, default=None):
|
|||||||
except:
|
except:
|
||||||
return default
|
return default
|
||||||
|
|
||||||
def main(argv):
|
def get_main_app(argv=[]):
|
||||||
"""Standard boilerplate Qt application code."""
|
"""
|
||||||
|
Standard boilerplate Qt application code.
|
||||||
|
Do everything but app.exec_() -- so that we can test the application in one thread
|
||||||
|
"""
|
||||||
app = QApplication(argv)
|
app = QApplication(argv)
|
||||||
app.setApplicationName(__appname__)
|
app.setApplicationName(__appname__)
|
||||||
app.setWindowIcon(newIcon("app"))
|
app.setWindowIcon(newIcon("app"))
|
||||||
win = MainWindow(argv[1] if len(argv) == 2 else None)
|
win = MainWindow(argv[1] if len(argv) == 2 else None)
|
||||||
win.show()
|
win.show()
|
||||||
|
return app, win
|
||||||
|
|
||||||
|
def main(argv):
|
||||||
|
'''construct main app and run it'''
|
||||||
|
app, _win = get_main_app(argv)
|
||||||
return app.exec_()
|
return app.exec_()
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|||||||
@ -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.QtGui import *
|
||||||
from PyQt4.QtCore import *
|
from PyQt4.QtCore import *
|
||||||
|
|
||||||
#from PyQt4.QtOpenGL import *
|
#from PyQt4.QtOpenGL import *
|
||||||
|
|
||||||
from shape import Shape
|
from shape import Shape
|
||||||
@ -20,7 +27,7 @@ class Canvas(QWidget):
|
|||||||
shapeMoved = pyqtSignal()
|
shapeMoved = pyqtSignal()
|
||||||
drawingPolygon = pyqtSignal(bool)
|
drawingPolygon = pyqtSignal(bool)
|
||||||
|
|
||||||
CREATE, EDIT = range(2)
|
CREATE, EDIT = list(range(2))
|
||||||
|
|
||||||
epsilon = 11.0
|
epsilon = 11.0
|
||||||
|
|
||||||
@ -85,7 +92,7 @@ class Canvas(QWidget):
|
|||||||
|
|
||||||
def mouseMoveEvent(self, ev):
|
def mouseMoveEvent(self, ev):
|
||||||
"""Update line with last point and current coordinates."""
|
"""Update line with last point and current coordinates."""
|
||||||
pos = self.transformPos(ev.posF())
|
pos = self.transformPos(ev.pos())
|
||||||
|
|
||||||
self.restoreCursor()
|
self.restoreCursor()
|
||||||
|
|
||||||
@ -169,7 +176,8 @@ class Canvas(QWidget):
|
|||||||
self.hVertex, self.hShape = None, None
|
self.hVertex, self.hShape = None, None
|
||||||
|
|
||||||
def mousePressEvent(self, ev):
|
def mousePressEvent(self, ev):
|
||||||
pos = self.transformPos(ev.posF())
|
pos = self.transformPos(ev.pos())
|
||||||
|
|
||||||
if ev.button() == Qt.LeftButton:
|
if ev.button() == Qt.LeftButton:
|
||||||
if self.drawing():
|
if self.drawing():
|
||||||
if self.current and self.current.reachMaxPoints() is False:
|
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(min(max(0, x2), max(x3, x4)), y3)
|
||||||
return QPointF(x, y)
|
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
|
"""For each edge formed by `points', yield the intersection
|
||||||
with the line segment `(x1,y1) - (x2,y2)`, if it exists.
|
with the line segment `(x1,y1) - (x2,y2)`, if it exists.
|
||||||
Also return the distance of `(x2,y2)' to the middle of the
|
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."""
|
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]
|
x3, y3 = points[i]
|
||||||
x4, y4 = points[(i+1) % 4]
|
x4, y4 = points[(i+1) % 4]
|
||||||
denom = (y4-y3) * (x2 - x1) - (x4 - x3) * (y2 - y1)
|
denom = (y4-y3) * (x2 - x1) - (x4 - x3) * (y2 - y1)
|
||||||
@ -506,7 +516,7 @@ class Canvas(QWidget):
|
|||||||
def keyPressEvent(self, ev):
|
def keyPressEvent(self, ev):
|
||||||
key = ev.key()
|
key = ev.key()
|
||||||
if key == Qt.Key_Escape and self.current:
|
if key == Qt.Key_Escape and self.current:
|
||||||
print 'ESC press'
|
print('ESC press')
|
||||||
self.current = None
|
self.current = None
|
||||||
self.drawingPolygon.emit(False)
|
self.drawingPolygon.emit(False)
|
||||||
self.update()
|
self.update()
|
||||||
|
|||||||
@ -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.QtGui import *
|
||||||
from PyQt4.QtCore import *
|
from PyQt4.QtCore import *
|
||||||
|
|
||||||
|
|||||||
@ -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.QtGui import *
|
||||||
from PyQt4.QtCore import *
|
from PyQt4.QtCore import *
|
||||||
|
|
||||||
@ -32,11 +37,20 @@ class LabelDialog(QDialog):
|
|||||||
self.setLayout(layout)
|
self.setLayout(layout)
|
||||||
|
|
||||||
def validate(self):
|
def validate(self):
|
||||||
|
try:
|
||||||
if self.edit.text().trimmed():
|
if self.edit.text().trimmed():
|
||||||
self.accept()
|
self.accept()
|
||||||
|
except AttributeError:
|
||||||
|
# PyQt5: AttributeError: 'str' object has no attribute 'trimmed'
|
||||||
|
if self.edit.text().strip():
|
||||||
|
self.accept()
|
||||||
|
|
||||||
def postProcess(self):
|
def postProcess(self):
|
||||||
|
try:
|
||||||
self.edit.setText(self.edit.text().trimmed())
|
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):
|
def popUp(self, text='', move=True):
|
||||||
self.edit.setText(text)
|
self.edit.setText(text)
|
||||||
@ -47,6 +61,10 @@ class LabelDialog(QDialog):
|
|||||||
return self.edit.text() if self.exec_() else None
|
return self.edit.text() if self.exec_() else None
|
||||||
|
|
||||||
def listItemClick(self, tQListWidgetItem):
|
def listItemClick(self, tQListWidgetItem):
|
||||||
|
try:
|
||||||
text = tQListWidgetItem.text().trimmed()
|
text = tQListWidgetItem.text().trimmed()
|
||||||
|
except AttributeError:
|
||||||
|
# PyQt5: AttributeError: 'str' object has no attribute 'trimmed'
|
||||||
|
text = tQListWidgetItem.text().strip()
|
||||||
self.edit.setText(text)
|
self.edit.setText(text)
|
||||||
self.validate()
|
self.validate()
|
||||||
|
|||||||
@ -1,7 +1,11 @@
|
|||||||
# Copyright (c) 2016 Tzutalin
|
# Copyright (c) 2016 Tzutalin
|
||||||
# Create by TzuTaLin <tzu.ta.lin@gmail.com>
|
# Create by TzuTaLin <tzu.ta.lin@gmail.com>
|
||||||
|
|
||||||
|
try:
|
||||||
|
from PyQt5.QtGui import QImage
|
||||||
|
except ImportError:
|
||||||
from PyQt4.QtGui import QImage
|
from PyQt4.QtGui import QImage
|
||||||
|
|
||||||
from base64 import b64encode, b64decode
|
from base64 import b64encode, b64decode
|
||||||
from pascal_voc_io import PascalVocWriter
|
from pascal_voc_io import PascalVocWriter
|
||||||
import os.path
|
import os.path
|
||||||
|
|||||||
@ -1,5 +1,10 @@
|
|||||||
from math import sqrt
|
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.QtGui import *
|
||||||
from PyQt4.QtCore import *
|
from PyQt4.QtCore import *
|
||||||
|
|
||||||
|
|||||||
@ -74,7 +74,11 @@ class PascalVocWriter:
|
|||||||
for each_object in self.boxlist:
|
for each_object in self.boxlist:
|
||||||
object_item = SubElement(top, 'object')
|
object_item = SubElement(top, 'object')
|
||||||
name = SubElement(object_item, 'name')
|
name = SubElement(object_item, 'name')
|
||||||
|
try:
|
||||||
name.text = unicode(each_object['name'])
|
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 = SubElement(object_item, 'pose')
|
||||||
pose.text = "Unspecified"
|
pose.text = "Unspecified"
|
||||||
truncated = SubElement(object_item, 'truncated')
|
truncated = SubElement(object_item, 'truncated')
|
||||||
@ -101,7 +105,7 @@ class PascalVocWriter:
|
|||||||
out_file = open(targetFile, 'w')
|
out_file = open(targetFile, 'w')
|
||||||
|
|
||||||
prettifyResult = self.prettify(root)
|
prettifyResult = self.prettify(root)
|
||||||
out_file.write(prettifyResult)
|
out_file.write(prettifyResult.decode('utf8'))
|
||||||
out_file.close()
|
out_file.close()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,11 @@
|
|||||||
#!/usr/bin/python
|
#!/usr/bin/python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
from PyQt5.QtGui import *
|
||||||
|
from PyQt5.QtCore import *
|
||||||
|
except ImportError:
|
||||||
from PyQt4.QtGui import *
|
from PyQt4.QtGui import *
|
||||||
from PyQt4.QtCore import *
|
from PyQt4.QtCore import *
|
||||||
|
|
||||||
|
|||||||
@ -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.QtGui import *
|
||||||
from PyQt4.QtCore import *
|
from PyQt4.QtCore import *
|
||||||
|
|
||||||
|
|
||||||
class ToolBar(QToolBar):
|
class ToolBar(QToolBar):
|
||||||
def __init__(self, title):
|
def __init__(self, title):
|
||||||
super(ToolBar, self).__init__(title)
|
super(ToolBar, self).__init__(title)
|
||||||
|
|||||||
@ -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.QtGui import *
|
||||||
from PyQt4.QtCore import *
|
from PyQt4.QtCore import *
|
||||||
|
|
||||||
|
|||||||
21
tests/test.py
Normal file
21
tests/test.py
Normal 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
|
||||||
|
|
||||||
Loading…
x
Reference in New Issue
Block a user