parent
e47254fc6e
commit
0fe693575f
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,12 @@
|
||||
*.spec
|
||||
build
|
||||
dist
|
||||
pyinstaller
|
||||
python-2.*
|
||||
pywin32*
|
||||
virtual-wine
|
||||
venv_wine
|
||||
PyQt4-*
|
||||
lxml-*
|
||||
windows_v*
|
||||
linux_v*
|
@ -0,0 +1,35 @@
|
||||
### Deploy to PyPI
|
||||
|
||||
```
|
||||
cd [ROOT]
|
||||
sh build-tools/build-for-pypi.sh
|
||||
```
|
||||
|
||||
### Build for Ubuntu
|
||||
|
||||
```
|
||||
cd build-tools
|
||||
sh run-in-container.sh
|
||||
sh envsetup.sh
|
||||
sh build-ubuntu-binary.sh
|
||||
```
|
||||
|
||||
### Build for Windows
|
||||
|
||||
```
|
||||
cd build-tools
|
||||
sh run-in-container.sh
|
||||
sh envsetup.sh
|
||||
sh build-windows-binary.sh
|
||||
```
|
||||
|
||||
### Build for macOS High Sierra
|
||||
```
|
||||
cd build-tools
|
||||
./build-for-macos.sh
|
||||
```
|
||||
|
||||
Note: If there are some problems, try to
|
||||
```
|
||||
sudo rm -rf virtual-wne venv_wine
|
||||
```
|
@ -0,0 +1,30 @@
|
||||
#!/bin/sh
|
||||
|
||||
brew install python@2
|
||||
pip install --upgrade virtualenv
|
||||
|
||||
# clone labelimg source
|
||||
rm -rf /tmp/labelImgSetup
|
||||
mkdir /tmp/labelImgSetup
|
||||
cd /tmp/labelImgSetup
|
||||
curl https://codeload.github.com/tzutalin/labelImg/zip/master --output labelImg.zip
|
||||
unzip labelImg.zip
|
||||
rm labelImg.zip
|
||||
|
||||
# setup python3 space
|
||||
virtualenv --system-site-packages -p python3 /tmp/labelImgSetup/labelImg-py3
|
||||
source /tmp/labelImgSetup/labelImg-py3/bin/activate
|
||||
cd labelImg-master
|
||||
|
||||
# build labelImg app
|
||||
pip install py2app
|
||||
pip install PyQt5 lxml
|
||||
make qt5py3
|
||||
rm -rf build dist
|
||||
python setup.py py2app -A
|
||||
mv "/tmp/labelImgSetup/labelImg-master/dist/labelImg.app" /Applications
|
||||
# deactivate python3
|
||||
deactivate
|
||||
cd ../
|
||||
rm -rf /tmp/labelImgSetup
|
||||
echo 'DONE'
|
@ -0,0 +1,17 @@
|
||||
#!/bin/sh
|
||||
# Packaging and Release
|
||||
docker run --workdir=$(pwd)/ --volume="/home/$USER:/home/$USER" tzutalin/py2qt4 /bin/sh -c 'make qt4py2; make test;sudo python setup.py sdist;sudo python setup.py install'
|
||||
|
||||
while true; do
|
||||
read -p "Do you wish to deploy this to PyPI(twine upload dist/* or pip install dist/*)?" yn
|
||||
case $yn in
|
||||
[Yy]* ) docker run -it --rm --workdir=$(pwd)/ --volume="/home/$USER:/home/$USER" tzutalin/py2qt4; break;;
|
||||
[Nn]* ) exit;;
|
||||
* ) echo "Please answer yes or no.";;
|
||||
esac
|
||||
done
|
||||
# python setup.py register
|
||||
# python setup.py sdist upload
|
||||
# Net pypi: twine upload dist/*
|
||||
|
||||
# Test before upladoing: pip install dist/labelImg.tar.gz
|
@ -0,0 +1,24 @@
|
||||
#!/bin/bash
|
||||
### Ubuntu use pyinstall v3.0
|
||||
THIS_SCRIPT_PATH=`readlink -f $0`
|
||||
THIS_SCRIPT_DIR=`dirname ${THIS_SCRIPT_PATH}`
|
||||
cd pyinstaller
|
||||
git checkout v3.2
|
||||
cd ${THIS_SCRIPT_DIR}
|
||||
|
||||
rm -r build
|
||||
rm -r dist
|
||||
rm labelImg.spec
|
||||
python pyinstaller/pyinstaller.py --hidden-import=xml \
|
||||
--hidden-import=xml.etree \
|
||||
--hidden-import=xml.etree.ElementTree \
|
||||
--hidden-import=lxml.etree \
|
||||
-D -F -n labelImg -c "../labelImg.py" -p ../libs -p ../
|
||||
|
||||
FOLDER=$(git describe --abbrev=0 --tags)
|
||||
FOLDER="linux_"$FOLDER
|
||||
rm -rf "$FOLDER"
|
||||
mkdir "$FOLDER"
|
||||
cp dist/labelImg $FOLDER
|
||||
cp -rf ../data $FOLDER/data
|
||||
zip "$FOLDER.zip" -r $FOLDER
|
@ -0,0 +1,32 @@
|
||||
#!/bin/bash
|
||||
### Window requires pyinstall v2.1
|
||||
wine msiexec -i python-2.7.8.msi
|
||||
wine pywin32-218.win32-py2.7.exe
|
||||
wine PyQt4-4.11.4-gpl-Py2.7-Qt4.8.7-x32.exe
|
||||
wine lxml-3.7.3.win32-py2.7.exe
|
||||
|
||||
THIS_SCRIPT_PATH=`readlink -f $0`
|
||||
THIS_SCRIPT_DIR=`dirname ${THIS_SCRIPT_PATH}`
|
||||
cd pyinstaller
|
||||
git checkout v2.1
|
||||
cd ${THIS_SCRIPT_DIR}
|
||||
echo ${THIS_SCRIPT_DIR}
|
||||
|
||||
#. venv_wine/bin/activate
|
||||
rm -r build
|
||||
rm -r dist
|
||||
rm labelImg.spec
|
||||
|
||||
wine c:/Python27/python.exe pyinstaller/pyinstaller.py --hidden-import=xml \
|
||||
--hidden-import=xml.etree \
|
||||
--hidden-import=xml.etree.ElementTree \
|
||||
--hidden-import=lxml.etree \
|
||||
-D -F -n labelImg -c "../labelImg.py" -p ../libs -p ../
|
||||
|
||||
FOLDER=$(git describe --abbrev=0 --tags)
|
||||
FOLDER="windows_"$FOLDER
|
||||
rm -rf "$FOLDER"
|
||||
mkdir "$FOLDER"
|
||||
cp dist/labelImg.exe $FOLDER
|
||||
cp -rf ../data $FOLDER/data
|
||||
zip "$FOLDER.zip" -r $FOLDER
|
@ -0,0 +1,53 @@
|
||||
#!/bin/sh
|
||||
|
||||
THIS_SCRIPT_PATH=`readlink -f $0`
|
||||
THIS_SCRIPT_DIR=`dirname ${THIS_SCRIPT_PATH}`
|
||||
#OS Ubuntu 14.04
|
||||
### Common packages for linux/windows
|
||||
if [ ! -e "pyinstaller" ]; then
|
||||
git clone https://github.com/pyinstaller/pyinstaller
|
||||
cd pyinstaller
|
||||
git checkout v2.1 -b v2.1
|
||||
cd ${THIS_SCRIPT_DIR}
|
||||
fi
|
||||
|
||||
echo "Going to clone and download packages for building windows"
|
||||
#Pacakges
|
||||
#> pyinstaller (2.1)
|
||||
#> wine (1.6.2)
|
||||
#> virtual-wine (0.1)
|
||||
#> python-2.7.8.msi
|
||||
#> pywin32-218.win32-py2.7.exe
|
||||
|
||||
## tool to install on Ubuntu
|
||||
#$ sudo apt-get install wine
|
||||
|
||||
### Clone a repo to create virtual wine env
|
||||
if [ ! -e "virtual-wine" ]; then
|
||||
git clone https://github.com/htgoebel/virtual-wine.git
|
||||
fi
|
||||
|
||||
apt-get install scons
|
||||
### Create virtual env
|
||||
rm -rf venv_wine
|
||||
./virtual-wine/vwine-setup venv_wine
|
||||
#### Active virutal env
|
||||
. venv_wine/bin/activate
|
||||
|
||||
### Use wine to install packages to virtual env
|
||||
if [ ! -e "python-2.7.8.msi" ]; then
|
||||
wget "https://www.python.org/ftp/python/2.7.8/python-2.7.8.msi"
|
||||
fi
|
||||
|
||||
if [ ! -e "pywin32-218.win32-py2.7.exe" ]; then
|
||||
wget "http://nchc.dl.sourceforge.net/project/pywin32/pywin32/Build%20218/pywin32-218.win32-py2.7.exe"
|
||||
fi
|
||||
|
||||
if [ ! -e "PyQt4-4.11.4-gpl-Py2.7-Qt4.8.7-x32.exe" ]; then
|
||||
wget "http://nchc.dl.sourceforge.net/project/pyqt/PyQt4/PyQt-4.11.4/PyQt4-4.11.4-gpl-Py2.7-Qt4.8.7-x32.exe"
|
||||
fi
|
||||
|
||||
if [ ! -e "lxml-3.7.3.win32-py2.7.exe" ]; then
|
||||
wget "https://pypi.python.org/packages/a3/f6/a28c5cf63873f6c55a3eb7857b736379229b85ba918261d2e88cf886905e/lxml-3.7.3.win32-py2.7.exe#md5=a0f746355876aca4ca5371cb0f1d13ce"
|
||||
fi
|
||||
|
@ -0,0 +1,13 @@
|
||||
#!/bin/sh
|
||||
docker run -it \
|
||||
--user $(id -u) \
|
||||
-e DISPLAY=unix$DISPLAY \
|
||||
--workdir=$(pwd) \
|
||||
--volume="/home/$USER:/home/$USER" \
|
||||
--volume="/etc/group:/etc/group:ro" \
|
||||
--volume="/etc/passwd:/etc/passwd:ro" \
|
||||
--volume="/etc/shadow:/etc/shadow:ro" \
|
||||
--volume="/etc/sudoers.d:/etc/sudoers.d:ro" \
|
||||
-v /tmp/.X11-unix:/tmp/.X11-unix \
|
||||
tzutalin/py2qt4
|
||||
|
@ -0,0 +1,46 @@
|
||||
# Copyright (c) <2015-Present> Tzutalin
|
||||
# Copyright (C) 2013 MIT, Computer Science and Artificial Intelligence Laboratory. Bryan Russell, Antonio Torralba,
|
||||
# William T. Freeman. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
||||
# associated documentation files (the "Software"), to deal in the Software without restriction, including without
|
||||
# limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
# Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
# The above copyright notice and this permission notice shall be included in all copies or substantial portions of
|
||||
# the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
|
||||
# NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
||||
# CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
|
||||
import sys
|
||||
try:
|
||||
from PyQt5.QtWidgets import QWidget, QHBoxLayout, QComboBox
|
||||
except ImportError:
|
||||
# needed for py3+qt4
|
||||
# Ref:
|
||||
# http://pyqt.sourceforge.net/Docs/PyQt4/incompatible_apis.html
|
||||
# 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 QWidget, QHBoxLayout, QComboBox
|
||||
|
||||
|
||||
class ComboBox(QWidget):
|
||||
def __init__(self, parent=None, items=[]):
|
||||
super(ComboBox, self).__init__(parent)
|
||||
|
||||
layout = QHBoxLayout()
|
||||
self.cb = QComboBox()
|
||||
self.items = items
|
||||
self.cb.addItems(self.items)
|
||||
|
||||
self.cb.currentIndexChanged.connect(parent.comboSelectionChanged)
|
||||
|
||||
layout.addWidget(self.cb)
|
||||
self.setLayout(layout)
|
||||
|
||||
def update_items(self, items):
|
||||
self.items = items
|
||||
|
||||
self.cb.clear()
|
||||
self.cb.addItems(self.items)
|
After Width: | Height: | Size: 2.4 MiB |
After Width: | Height: | Size: 6.4 KiB |
@ -0,0 +1,2 @@
|
||||
__version_info__ = ('1', '0', '0')
|
||||
__version__ = '.'.join(__version_info__)
|
@ -0,0 +1,150 @@
|
||||
try:
|
||||
from PyQt5.QtGui import *
|
||||
from PyQt5.QtCore import *
|
||||
from PyQt5.QtWidgets import *
|
||||
except ImportError:
|
||||
from PyQt4.QtGui import *
|
||||
from PyQt4.QtCore import *
|
||||
|
||||
import json
|
||||
|
||||
from libs.utils import newIcon
|
||||
|
||||
BB = QDialogButtonBox
|
||||
|
||||
|
||||
class Worker(QThread):
|
||||
progressBarValue = pyqtSignal(int)
|
||||
listValue = pyqtSignal(str)
|
||||
endsignal = pyqtSignal(int, str)
|
||||
handle = 0
|
||||
|
||||
def __init__(self, ocr, mImgList, mainThread, model):
|
||||
super(Worker, self).__init__()
|
||||
self.ocr = ocr
|
||||
self.mImgList = mImgList
|
||||
self.mainThread = mainThread
|
||||
self.model = model
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
findex = 0
|
||||
for Imgpath in self.mImgList:
|
||||
if self.handle == 0:
|
||||
self.listValue.emit(Imgpath)
|
||||
if self.model == 'paddle':
|
||||
self.result_dic = self.ocr.ocr(Imgpath, cls=True, det=True)
|
||||
|
||||
# 结果保存
|
||||
if self.result_dic is None or len(self.result_dic) == 0:
|
||||
print('Can not recognise file is : ', Imgpath)
|
||||
pass
|
||||
else:
|
||||
for res in self.result_dic:
|
||||
chars = res[1][0]
|
||||
cond = res[1][1]
|
||||
posi = res[0]
|
||||
self.listValue.emit("文字:" + chars + " 置信度:" + str(cond) + " 坐标:" + json.dumps(posi))
|
||||
self.mainThread.result_dic = self.result_dic
|
||||
self.mainThread.filePath = Imgpath
|
||||
# 保存
|
||||
self.mainThread.saveFile(mode='Auto')
|
||||
findex += 1
|
||||
self.progressBarValue.emit(findex)
|
||||
else:
|
||||
break
|
||||
self.endsignal.emit(0, "readAll")
|
||||
self.exec()
|
||||
except Exception as e:
|
||||
print(e)
|
||||
raise
|
||||
|
||||
|
||||
class AutoDialog(QDialog):
|
||||
|
||||
def __init__(self, text="Enter object label", parent=None, ocr=None, mImgList=None, lenbar=0):
|
||||
super(AutoDialog, self).__init__(parent)
|
||||
self.setFixedWidth(1000)
|
||||
self.parent = parent
|
||||
self.ocr = ocr
|
||||
self.mImgList = mImgList
|
||||
self.pb = QProgressBar()
|
||||
self.pb.setRange(0, lenbar)
|
||||
self.pb.setValue(0)
|
||||
|
||||
layout = QVBoxLayout()
|
||||
layout.addWidget(self.pb)
|
||||
self.model = 'paddle'
|
||||
self.listWidget = QListWidget(self)
|
||||
layout.addWidget(self.listWidget)
|
||||
|
||||
self.buttonBox = bb = BB(BB.Ok | BB.Cancel, Qt.Horizontal, self)
|
||||
bb.button(BB.Ok).setIcon(newIcon('done'))
|
||||
bb.button(BB.Cancel).setIcon(newIcon('undo'))
|
||||
bb.accepted.connect(self.validate)
|
||||
bb.rejected.connect(self.reject)
|
||||
layout.addWidget(bb)
|
||||
bb.button(BB.Ok).setEnabled(False)
|
||||
|
||||
self.setLayout(layout)
|
||||
self.setWindowTitle("自动标注中")
|
||||
self.setWindowModality(Qt.ApplicationModal)
|
||||
|
||||
# self.setWindowFlags(Qt.WindowCloseButtonHint)
|
||||
|
||||
self.thread_1 = Worker(self.ocr, self.mImgList, self.parent, 'paddle')
|
||||
self.thread_1.progressBarValue.connect(self.handleProgressBarSingal)
|
||||
self.thread_1.listValue.connect(self.handleListWidgetSingal)
|
||||
self.thread_1.endsignal.connect(self.handleEndsignalSignal)
|
||||
|
||||
def handleProgressBarSingal(self, i):
|
||||
self.pb.setValue(i)
|
||||
|
||||
def handleListWidgetSingal(self, i):
|
||||
self.listWidget.addItem(i)
|
||||
titem = self.listWidget.item(self.listWidget.count() - 1)
|
||||
self.listWidget.scrollToItem(titem)
|
||||
|
||||
def handleEndsignalSignal(self, i, str):
|
||||
if i == 0 and str == "readAll":
|
||||
self.buttonBox.button(BB.Ok).setEnabled(True)
|
||||
self.buttonBox.button(BB.Cancel).setEnabled(False)
|
||||
|
||||
def reject(self):
|
||||
print("reject")
|
||||
self.thread_1.handle = -1
|
||||
self.thread_1.quit()
|
||||
# del self.thread_1
|
||||
# if self.thread_1.isRunning():
|
||||
# self.thread_1.terminate()
|
||||
# self.thread_1.quit()
|
||||
# super(AutoDialog,self).reject()
|
||||
while not self.thread_1.isFinished():
|
||||
pass
|
||||
self.accept()
|
||||
|
||||
def validate(self):
|
||||
self.accept()
|
||||
|
||||
def postProcess(self):
|
||||
try:
|
||||
self.edit.setText(self.edit.text().trimmed())
|
||||
# print(self.edit.text())
|
||||
except AttributeError:
|
||||
# PyQt5: AttributeError: 'str' object has no attribute 'trimmed'
|
||||
self.edit.setText(self.edit.text())
|
||||
print(self.edit.text())
|
||||
|
||||
def popUp(self):
|
||||
self.thread_1.start()
|
||||
return 1 if self.exec_() else None
|
||||
|
||||
def closeEvent(self, event):
|
||||
print("???")
|
||||
# if self.thread_1.isRunning():
|
||||
# self.thread_1.quit()
|
||||
#
|
||||
# # self._thread.terminate()
|
||||
# # del self.thread_1
|
||||
# super(AutoDialog, self).closeEvent(event)
|
||||
self.reject()
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,49 @@
|
||||
# Copyright (c) <2015-Present> Tzutalin
|
||||
# Copyright (C) 2013 MIT, Computer Science and Artificial Intelligence Laboratory. Bryan Russell, Antonio Torralba,
|
||||
# William T. Freeman. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
||||
# associated documentation files (the "Software"), to deal in the Software without restriction, including without
|
||||
# limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
# Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
# The above copyright notice and this permission notice shall be included in all copies or substantial portions of
|
||||
# the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
|
||||
# NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
||||
# CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
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 *
|
||||
|
||||
BB = QDialogButtonBox
|
||||
|
||||
|
||||
class ColorDialog(QColorDialog):
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(ColorDialog, self).__init__(parent)
|
||||
self.setOption(QColorDialog.ShowAlphaChannel)
|
||||
# The Mac native dialog does not support our restore button.
|
||||
self.setOption(QColorDialog.DontUseNativeDialog)
|
||||
# Add a restore defaults button.
|
||||
# The default is set at invocation time, so that it
|
||||
# works across dialogs for different elements.
|
||||
self.default = None
|
||||
self.bb = self.layout().itemAt(1).widget()
|
||||
self.bb.addButton(BB.RestoreDefaults)
|
||||
self.bb.clicked.connect(self.checkRestore)
|
||||
|
||||
def getColor(self, value=None, title=None, default=None):
|
||||
self.default = default
|
||||
if title:
|
||||
self.setWindowTitle(title)
|
||||
if value:
|
||||
self.setCurrentColor(value)
|
||||
return self.currentColor() if self.exec_() else None
|
||||
|
||||
def checkRestore(self, button):
|
||||
if self.bb.buttonRole(button) & BB.ResetRole and self.default:
|
||||
self.setCurrentColor(self.default)
|
@ -0,0 +1,31 @@
|
||||
# Copyright (c) <2015-Present> Tzutalin
|
||||
# Copyright (C) 2013 MIT, Computer Science and Artificial Intelligence Laboratory. Bryan Russell, Antonio Torralba,
|
||||
# William T. Freeman. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
||||
# associated documentation files (the "Software"), to deal in the Software without restriction, including without
|
||||
# limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
# Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
# The above copyright notice and this permission notice shall be included in all copies or substantial portions of
|
||||
# the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
|
||||
# NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
||||
# CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
SETTING_FILENAME = 'filename'
|
||||
SETTING_RECENT_FILES = 'recentFiles'
|
||||
SETTING_WIN_SIZE = 'window/size'
|
||||
SETTING_WIN_POSE = 'window/position'
|
||||
SETTING_WIN_GEOMETRY = 'window/geometry'
|
||||
SETTING_LINE_COLOR = 'line/color'
|
||||
SETTING_FILL_COLOR = 'fill/color'
|
||||
SETTING_ADVANCE_MODE = 'advanced'
|
||||
SETTING_WIN_STATE = 'window/state'
|
||||
SETTING_SAVE_DIR = 'savedir'
|
||||
SETTING_PAINT_LABEL = 'paintlabel'
|
||||
SETTING_LAST_OPEN_DIR = 'lastOpenDir'
|
||||
SETTING_AUTO_SAVE = 'autosave'
|
||||
SETTING_SINGLE_CLASS = 'singleclass'
|
||||
FORMAT_PASCALVOC='PascalVOC'
|
||||
FORMAT_YOLO='YOLO'
|
||||
SETTING_DRAW_SQUARE = 'draw/square'
|
||||
SETTING_LABEL_FILE_FORMAT= 'labelFileFormat'
|
||||
DEFAULT_ENCODING = 'utf-8'
|
@ -0,0 +1,143 @@
|
||||
# Copyright (c) <2015-Present> Tzutalin
|
||||
# Copyright (C) 2013 MIT, Computer Science and Artificial Intelligence Laboratory. Bryan Russell, Antonio Torralba,
|
||||
# William T. Freeman. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
||||
# associated documentation files (the "Software"), to deal in the Software without restriction, including without
|
||||
# limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
# Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
# The above copyright notice and this permission notice shall be included in all copies or substantial portions of
|
||||
# the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
|
||||
# NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
||||
# CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf8 -*-
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from libs.constants import DEFAULT_ENCODING
|
||||
import os
|
||||
|
||||
JSON_EXT = '.json'
|
||||
ENCODE_METHOD = DEFAULT_ENCODING
|
||||
|
||||
|
||||
class CreateMLWriter:
|
||||
def __init__(self, foldername, filename, imgsize, shapes, outputfile, databasesrc='Unknown', localimgpath=None):
|
||||
self.foldername = foldername
|
||||
self.filename = filename
|
||||
self.databasesrc = databasesrc
|
||||
self.imgsize = imgsize
|
||||
self.boxlist = []
|
||||
self.localimgpath = localimgpath
|
||||
self.verified = False
|
||||
self.shapes = shapes
|
||||
self.outputfile = outputfile
|
||||
|
||||
def write(self):
|
||||
if os.path.isfile(self.outputfile):
|
||||
with open(self.outputfile, "r") as file:
|
||||
input_data = file.read()
|
||||
outputdict = json.loads(input_data)
|
||||
else:
|
||||
outputdict = []
|
||||
|
||||
outputimagedict = {
|
||||
"image": self.filename,
|
||||
"annotations": []
|
||||
}
|
||||
|
||||
for shape in self.shapes:
|
||||
points = shape["points"]
|
||||
|
||||
x1 = points[0][0]
|
||||
y1 = points[0][1]
|
||||
x2 = points[1][0]
|
||||
y2 = points[2][1]
|
||||
|
||||
height, width, x, y = self.calculate_coordinates(x1, x2, y1, y2)
|
||||
|
||||
shapedict = {
|
||||
"label": shape["label"],
|
||||
"coordinates": {
|
||||
"x": x,
|
||||
"y": y,
|
||||
"width": width,
|
||||
"height": height
|
||||
}
|
||||
}
|
||||
outputimagedict["annotations"].append(shapedict)
|
||||
|
||||
# check if image already in output
|
||||
exists = False
|
||||
for i in range(0, len(outputdict)):
|
||||
if outputdict[i]["image"] == outputimagedict["image"]:
|
||||
exists = True
|
||||
outputdict[i] = outputimagedict
|
||||
break
|
||||
|
||||
if not exists:
|
||||
outputdict.append(outputimagedict)
|
||||
|
||||
Path(self.outputfile).write_text(json.dumps(outputdict), ENCODE_METHOD)
|
||||
|
||||
def calculate_coordinates(self, x1, x2, y1, y2):
|
||||
if x1 < x2:
|
||||
xmin = x1
|
||||
xmax = x2
|
||||
else:
|
||||
xmin = x2
|
||||
xmax = x1
|
||||
if y1 < y2:
|
||||
ymin = y1
|
||||
ymax = y2
|
||||
else:
|
||||
ymin = y2
|
||||
ymax = y1
|
||||
width = xmax - xmin
|
||||
if width < 0:
|
||||
width = width * -1
|
||||
height = ymax - ymin
|
||||
# x and y from center of rect
|
||||
x = xmin + width / 2
|
||||
y = ymin + height / 2
|
||||
return height, width, x, y
|
||||
|
||||
|
||||
class CreateMLReader:
|
||||
def __init__(self, jsonpath, filepath):
|
||||
self.jsonpath = jsonpath
|
||||
self.shapes = []
|
||||
self.verified = False
|
||||
self.filename = filepath.split("/")[-1:][0]
|
||||
try:
|
||||
self.parse_json()
|
||||
except ValueError:
|
||||
print("JSON decoding failed")
|
||||
|
||||
def parse_json(self):
|
||||
with open(self.jsonpath, "r") as file:
|
||||
inputdata = file.read()
|
||||
|
||||
outputdict = json.loads(inputdata)
|
||||
self.verified = True
|
||||
|
||||
if len(self.shapes) > 0:
|
||||
self.shapes = []
|
||||
for image in outputdict:
|
||||
if image["image"] == self.filename:
|
||||
for shape in image["annotations"]:
|
||||
self.add_shape(shape["label"], shape["coordinates"])
|
||||
|
||||
def add_shape(self, label, bndbox):
|
||||
xmin = bndbox["x"] - (bndbox["width"] / 2)
|
||||
ymin = bndbox["y"] - (bndbox["height"] / 2)
|
||||
|
||||
xmax = bndbox["x"] + (bndbox["width"] / 2)
|
||||
ymax = bndbox["y"] + (bndbox["height"] / 2)
|
||||
|
||||
points = [(xmin, ymin), (xmax, ymin), (xmax, ymax), (xmin, ymax)]
|
||||
self.shapes.append((label, points, None, None, True))
|
||||
|
||||
def get_shapes(self):
|
||||
return self.shapes
|
@ -0,0 +1,40 @@
|
||||
# Copyright (c) <2015-Present> Tzutalin
|
||||
# Copyright (C) 2013 MIT, Computer Science and Artificial Intelligence Laboratory. Bryan Russell, Antonio Torralba,
|
||||
# William T. Freeman. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
||||
# associated documentation files (the "Software"), to deal in the Software without restriction, including without
|
||||
# limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
# Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
# The above copyright notice and this permission notice shall be included in all copies or substantial portions of
|
||||
# the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
|
||||
# NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
||||
# CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
import sys
|
||||
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
|
||||
# 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 *
|
||||
|
||||
# PyQt5: TypeError: unhashable type: 'QListWidgetItem'
|
||||
|
||||
|
||||
class HashableQListWidgetItem(QListWidgetItem):
|
||||
|
||||
def __init__(self, *args):
|
||||
super(HashableQListWidgetItem, self).__init__(*args)
|
||||
|
||||
def __hash__(self):
|
||||
return hash(id(self))
|
@ -0,0 +1,107 @@
|
||||
# Copyright (c) <2015-Present> Tzutalin
|
||||
# Copyright (C) 2013 MIT, Computer Science and Artificial Intelligence Laboratory. Bryan Russell, Antonio Torralba,
|
||||
# William T. Freeman. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
||||
# associated documentation files (the "Software"), to deal in the Software without restriction, including without
|
||||
# limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
# Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
# The above copyright notice and this permission notice shall be included in all copies or substantial portions of
|
||||
# the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
|
||||
# NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
||||
# CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
try:
|
||||
from PyQt5.QtGui import *
|
||||
from PyQt5.QtCore import *
|
||||
from PyQt5.QtWidgets import *
|
||||
except ImportError:
|
||||
from PyQt4.QtGui import *
|
||||
from PyQt4.QtCore import *
|
||||
|
||||
from libs.utils import newIcon, labelValidator
|
||||
|
||||
BB = QDialogButtonBox
|
||||
|
||||
|
||||
class LabelDialog(QDialog):
|
||||
|
||||
def __init__(self, text="Enter object label", parent=None, listItem=None):
|
||||
super(LabelDialog, self).__init__(parent)
|
||||
|
||||
self.edit = QLineEdit() # OLD
|
||||
# self.edit = QTextEdit()
|
||||
self.edit.setText(text)
|
||||
# self.edit.setValidator(labelValidator()) # 验证有效性
|
||||
self.edit.editingFinished.connect(self.postProcess)
|
||||
|
||||
model = QStringListModel()
|
||||
model.setStringList(listItem)
|
||||
completer = QCompleter()
|
||||
completer.setModel(model)
|
||||
self.edit.setCompleter(completer)
|
||||
|
||||
layout = QVBoxLayout()
|
||||
layout.addWidget(self.edit)
|
||||
self.buttonBox = bb = BB(BB.Ok | BB.Cancel, Qt.Horizontal, self)
|
||||
bb.button(BB.Ok).setIcon(newIcon('done'))
|
||||
bb.button(BB.Cancel).setIcon(newIcon('undo'))
|
||||
bb.accepted.connect(self.validate)
|
||||
bb.rejected.connect(self.reject)
|
||||
layout.addWidget(bb)
|
||||
|
||||
# if listItem is not None and len(listItem) > 0:
|
||||
# self.listWidget = QListWidget(self)
|
||||
# for item in listItem:
|
||||
# self.listWidget.addItem(item)
|
||||
# self.listWidget.itemClicked.connect(self.listItemClick)
|
||||
# self.listWidget.itemDoubleClicked.connect(self.listItemDoubleClick)
|
||||
# layout.addWidget(self.listWidget)
|
||||
|
||||
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())
|
||||
# print(self.edit.text())
|
||||
except AttributeError:
|
||||
# PyQt5: AttributeError: 'str' object has no attribute 'trimmed'
|
||||
self.edit.setText(self.edit.text())
|
||||
print(self.edit.text())
|
||||
|
||||
def popUp(self, text='', move=True):
|
||||
self.edit.setText(text)
|
||||
self.edit.setSelection(0, len(text))
|
||||
self.edit.setFocus(Qt.PopupFocusReason)
|
||||
if move:
|
||||
cursor_pos = QCursor.pos()
|
||||
parent_bottomRight = self.parentWidget().geometry()
|
||||
max_x = parent_bottomRight.x() + parent_bottomRight.width() - self.sizeHint().width()
|
||||
max_y = parent_bottomRight.y() + parent_bottomRight.height() - self.sizeHint().height()
|
||||
max_global = self.parentWidget().mapToGlobal(QPoint(max_x, max_y))
|
||||
if cursor_pos.x() > max_global.x():
|
||||
cursor_pos.setX(max_global.x())
|
||||
if cursor_pos.y() > max_global.y():
|
||||
cursor_pos.setY(max_global.y())
|
||||
self.move(cursor_pos)
|
||||
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)
|
||||
|
||||
def listItemDoubleClick(self, tQListWidgetItem):
|
||||
self.listItemClick(tQListWidgetItem)
|
||||
self.validate()
|
@ -0,0 +1,152 @@
|
||||
# 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 libs.pascal_voc_io import PascalVocWriter
|
||||
from libs.yolo_io import YOLOWriter
|
||||
from libs.pascal_voc_io import XML_EXT
|
||||
from enum import Enum
|
||||
import os.path
|
||||
import sys
|
||||
|
||||
|
||||
class LabelFileFormat(Enum):
|
||||
PASCAL_VOC= 1
|
||||
YOLO = 2
|
||||
|
||||
|
||||
class LabelFileError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class LabelFile(object):
|
||||
# It might be changed as window creates. By default, using XML ext
|
||||
# suffix = '.lif'
|
||||
suffix = XML_EXT
|
||||
|
||||
def __init__(self, filename=None):
|
||||
self.shapes = ()
|
||||
self.imagePath = None
|
||||
self.imageData = None
|
||||
self.verified = False
|
||||
|
||||
def savePascalVocFormat(self, filename, shapes, imagePath, imageData,
|
||||
lineColor=None, fillColor=None, databaseSrc=None):
|
||||
imgFolderPath = os.path.dirname(imagePath)
|
||||
imgFolderName = os.path.split(imgFolderPath)[-1]
|
||||
imgFileName = os.path.basename(imagePath)
|
||||
#imgFileNameWithoutExt = os.path.splitext(imgFileName)[0]
|
||||
# Read from file path because self.imageData might be empty if saving to
|
||||
# Pascal format
|
||||
image = QImage()
|
||||
image.load(imagePath)
|
||||
imageShape = [image.height(), image.width(),
|
||||
1 if image.isGrayscale() else 3]
|
||||
writer = PascalVocWriter(imgFolderName, imgFileName,
|
||||
imageShape, localImgPath=imagePath)
|
||||
writer.verified = self.verified
|
||||
|
||||
for shape in shapes:
|
||||
points = shape['points']
|
||||
label = shape['label']
|
||||
# Add Chris
|
||||
difficult = int(shape['difficult'])
|
||||
bndbox = LabelFile.convertPoints2BndBox(points)
|
||||
writer.addBndBox(bndbox[0], bndbox[1], bndbox[2], bndbox[3], label, difficult)
|
||||
|
||||
writer.save(targetFile=filename)
|
||||
return
|
||||
|
||||
def saveYoloFormat(self, filename, shapes, imagePath, imageData, classList,
|
||||
lineColor=None, fillColor=None, databaseSrc=None):
|
||||
imgFolderPath = os.path.dirname(imagePath)
|
||||
imgFolderName = os.path.split(imgFolderPath)[-1]
|
||||
imgFileName = os.path.basename(imagePath)
|
||||
#imgFileNameWithoutExt = os.path.splitext(imgFileName)[0]
|
||||
# Read from file path because self.imageData might be empty if saving to
|
||||
# Pascal format
|
||||
image = QImage()
|
||||
image.load(imagePath)
|
||||
imageShape = [image.height(), image.width(),
|
||||
1 if image.isGrayscale() else 3]
|
||||
writer = YOLOWriter(imgFolderName, imgFileName,
|
||||
imageShape, localImgPath=imagePath)
|
||||
writer.verified = self.verified
|
||||
|
||||
for shape in shapes:
|
||||
points = shape['points']
|
||||
label = shape['label']
|
||||
# Add Chris
|
||||
difficult = int(shape['difficult'])
|
||||
bndbox = LabelFile.convertPoints2BndBox(points)
|
||||
writer.addBndBox(bndbox[0], bndbox[1], bndbox[2], bndbox[3], label, difficult)
|
||||
|
||||
writer.save(targetFile=filename, classList=classList)
|
||||
return
|
||||
|
||||
def toggleVerify(self):
|
||||
self.verified = not self.verified
|
||||
|
||||
''' ttf is disable
|
||||
def load(self, filename):
|
||||
import json
|
||||
with open(filename, 'rb') as f:
|
||||
data = json.load(f)
|
||||
imagePath = data['imagePath']
|
||||
imageData = b64decode(data['imageData'])
|
||||
lineColor = data['lineColor']
|
||||
fillColor = data['fillColor']
|
||||
shapes = ((s['label'], s['points'], s['line_color'], s['fill_color'])\
|
||||
for s in data['shapes'])
|
||||
# Only replace data after everything is loaded.
|
||||
self.shapes = shapes
|
||||
self.imagePath = imagePath
|
||||
self.imageData = imageData
|
||||
self.lineColor = lineColor
|
||||
self.fillColor = fillColor
|
||||
|
||||
def save(self, filename, shapes, imagePath, imageData, lineColor=None, fillColor=None):
|
||||
import json
|
||||
with open(filename, 'wb') as f:
|
||||
json.dump(dict(
|
||||
shapes=shapes,
|
||||
lineColor=lineColor, fillColor=fillColor,
|
||||
imagePath=imagePath,
|
||||
imageData=b64encode(imageData)),
|
||||
f, ensure_ascii=True, indent=2)
|
||||
'''
|
||||
|
||||
@staticmethod
|
||||
def isLabelFile(filename):
|
||||
fileSuffix = os.path.splitext(filename)[1].lower()
|
||||
return fileSuffix == LabelFile.suffix
|
||||
|
||||
@staticmethod
|
||||
def convertPoints2BndBox(points):
|
||||
xmin = float('inf')
|
||||
ymin = float('inf')
|
||||
xmax = float('-inf')
|
||||
ymax = float('-inf')
|
||||
for p in points:
|
||||
x = p[0]
|
||||
y = p[1]
|
||||
xmin = min(x, xmin)
|
||||
ymin = min(y, ymin)
|
||||
xmax = max(x, xmax)
|
||||
ymax = max(y, ymax)
|
||||
|
||||
# Martin Kersner, 2015/11/12
|
||||
# 0-valued coordinates of BB caused an error while
|
||||
# training faster-rcnn object detector.
|
||||
if xmin < 1:
|
||||
xmin = 1
|
||||
|
||||
if ymin < 1:
|
||||
ymin = 1
|
||||
|
||||
return (int(xmin), int(ymin), int(xmax), int(ymax))
|
@ -0,0 +1,183 @@
|
||||
# Copyright (c) <2015-Present> Tzutalin
|
||||
# Copyright (C) 2013 MIT, Computer Science and Artificial Intelligence Laboratory. Bryan Russell, Antonio Torralba,
|
||||
# William T. Freeman. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
||||
# associated documentation files (the "Software"), to deal in the Software without restriction, including without
|
||||
# limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
|
||||
# Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
# The above copyright notice and this permission notice shall be included in all copies or substantial portions of
|
||||
# the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
|
||||
# NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
# SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
||||
# CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
# THE SOFTWARE.
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf8 -*-
|
||||
import sys
|
||||
from xml.etree import ElementTree
|
||||
from xml.etree.ElementTree import Element, SubElement
|
||||
from lxml import etree
|
||||
import codecs
|
||||
from libs.constants import DEFAULT_ENCODING
|
||||
from libs.ustr import ustr
|
||||
|
||||
|
||||
XML_EXT = '.xml'
|
||||
ENCODE_METHOD = DEFAULT_ENCODING
|
||||
|
||||
class PascalVocWriter:
|
||||
|
||||
def __init__(self, foldername, filename, imgSize,databaseSrc='Unknown', localImgPath=None):
|
||||
self.foldername = foldername
|
||||
self.filename = filename
|
||||
self.databaseSrc = databaseSrc
|
||||
self.imgSize = imgSize
|
||||
self.boxlist = []
|
||||
self.localImgPath = localImgPath
|
||||
self.verified = False
|
||||
|
||||
def prettify(self, elem):
|
||||
"""
|
||||
Return a pretty-printed XML string for the Element.
|
||||
"""
|
||||
rough_string = ElementTree.tostring(elem, 'utf8')
|
||||
root = etree.fromstring(rough_string)
|
||||
return etree.tostring(root, pretty_print=True, encoding=ENCODE_METHOD).replace(" ".encode(), "\t".encode())
|
||||
# minidom does not support UTF-8
|
||||
'''reparsed = minidom.parseString(rough_string)
|
||||
return reparsed.toprettyxml(indent="\t", encoding=ENCODE_METHOD)'''
|
||||
|
||||
def genXML(self):
|
||||
"""
|
||||
Return XML root
|
||||
"""
|
||||
# Check conditions
|
||||
if self.filename is None or \
|
||||
self.foldername is None or \
|
||||
self.imgSize is None:
|
||||
return None
|
||||
|
||||
top = Element('annotation')
|
||||
if self.verified:
|
||||
top.set('verified', 'yes')
|
||||
|
||||
folder = SubElement(top, 'folder')
|
||||
folder.text = self.foldername
|
||||
|
||||
filename = SubElement(top, 'filename')
|
||||
filename.text = self.filename
|
||||
|
||||
if self.localImgPath is not None:
|
||||
localImgPath = SubElement(top, 'path')
|
||||
localImgPath.text = self.localImgPath
|
||||
|
||||
source = SubElement(top, 'source')
|
||||
database = SubElement(source, 'database')
|
||||
database.text = self.databaseSrc
|
||||
|
||||
size_part = SubElement(top, 'size')
|
||||
width = SubElement(size_part, 'width')
|
||||
height = SubElement(size_part, 'height')
|
||||
depth = SubElement(size_part, 'depth')
|
||||
width.text = str(self.imgSize[1])
|
||||
height.text = str(self.imgSize[0])
|
||||
if len(self.imgSize) == 3:
|
||||
depth.text = str(self.imgSize[2])
|
||||
else:
|
||||
depth.text = '1'
|
||||
|
||||
segmented = SubElement(top, 'segmented')
|
||||
segmented.text = '0'
|
||||
return top
|
||||
|
||||
def addBndBox(self, xmin, ymin, xmax, ymax, name, difficult):
|
||||
bndbox = {'xmin': xmin, 'ymin': ymin, 'xmax': xmax, 'ymax': ymax}
|
||||
bndbox['name'] = name
|
||||
bndbox['difficult'] = difficult
|
||||
self.boxlist.append(bndbox)
|
||||
|
||||
def appendObjects(self, top):
|
||||
for each_object in self.boxlist:
|
||||
object_item = SubElement(top, 'object')
|
||||
name = SubElement(object_item, 'name')
|
||||
name.text = ustr(each_object['name'])
|
||||
pose = SubElement(object_item, 'pose')
|
||||
pose.text = "Unspecified"
|
||||
truncated = SubElement(object_item, 'truncated')
|
||||
if int(float(each_object['ymax'])) == int(float(self.imgSize[0])) or (int(float(each_object['ymin']))== 1):
|
||||
truncated.text = "1" # max == height or min
|
||||
elif (int(float(each_object['xmax']))==int(float(self.imgSize[1]))) or (int(float(each_object['xmin']))== 1):
|
||||
truncated.text = "1" # max == width or min
|
||||
else:
|
||||
truncated.text = "0"
|
||||
difficult = SubElement(object_item, 'difficult')
|
||||
difficult.text = str( bool(each_object['difficult']) & 1 )
|
||||
bndbox = SubElement(object_item, 'bndbox')
|
||||
xmin = SubElement(bndbox, 'xmin')
|
||||
xmin.text = str(each_object['xmin'])
|
||||
ymin = SubElement(bndbox, 'ymin')
|
||||
ymin.text = str(each_object['ymin'])
|
||||
xmax = SubElement(bndbox, 'xmax')
|
||||
xmax.text = str(each_object['xmax'])
|
||||
ymax = SubElement(bndbox, 'ymax')
|
||||
ymax.text = str(each_object['ymax'])
|
||||
|
||||
def save(self, targetFile=None):
|
||||
root = self.genXML()
|
||||
self.appendObjects(root)
|
||||
out_file = None
|
||||
if targetFile is None:
|
||||
out_file = codecs.open(
|
||||
self.filename + XML_EXT, 'w', encoding=ENCODE_METHOD)
|
||||
else:
|
||||
out_file = codecs.open(targetFile, 'w', encoding=ENCODE_METHOD)
|
||||
|
||||
prettifyResult = self.prettify(root)
|
||||
out_file.write(prettifyResult.decode('utf8'))
|
||||
out_file.close()
|
||||
|
||||
|
||||
class PascalVocReader:
|
||||
|
||||
def __init__(self, filepath):
|
||||
# shapes type:
|
||||
# [labbel, [(x1,y1), (x2,y2), (x3,y3), (x4,y4)], color, color, difficult]
|
||||
self.shapes = []
|
||||
self.filepath = filepath
|
||||
self.verified = False
|
||||
try:
|
||||
self.parseXML()
|
||||
except:
|
||||
pass
|
||||
|
||||
def getShapes(self):
|
||||
return self.shapes
|
||||
|
||||
def addShape(self, label, bndbox, difficult):
|
||||
xmin = int(float(bndbox.find('xmin').text))
|
||||
ymin = int(float(bndbox.find('ymin').text))
|
||||
xmax = int(float(bndbox.find('xmax').text))
|
||||
ymax = int(float(bndbox.find('ymax').text))
|
||||
points = [(xmin, ymin), (xmax, ymin), (xmax, ymax), (xmin, ymax)]
|
||||
self.shapes.append((label, points, None, None, difficult))
|
||||
|
||||
def parseXML(self):
|
||||
assert self.filepath.endswith(XML_EXT), "Unsupport file format"
|
||||
parser = etree.XMLParser(encoding=ENCODE_METHOD)
|
||||
xmltree = ElementTree.parse(self.filepath, parser=parser).getroot()
|
||||
filename = xmltree.find('filename').text
|
||||
try:
|
||||
verified = xmltree.attrib['verified']
|
||||
if verified == 'yes':
|
||||
self.verified = True
|
||||
except KeyError:
|
||||
self.verified = False
|
||||
|
||||
for object_iter in xmltree.findall('object'):
|
||||
bndbox = object_iter.find("bndbox")
|
||||
label = object_iter.find('name').text
|
||||
# Add chris
|
||||
difficult = False
|
||||
if object_iter.find('difficult') is not None:
|
||||
difficult = bool(int(object_iter.find('difficult').text))
|
||||
self.addShape(label, bndbox, difficult)
|
||||
return True
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue