From eaac0314044b7aed9aa256349b23f908d398bb2e Mon Sep 17 00:00:00 2001 From: tzutalin Date: Sat, 1 Dec 2018 00:20:46 -0800 Subject: [PATCH] Move icons to resource folder, fix the unicode issue, support zh-tw lang --- .gitignore | 2 +- labelImg.py | 173 ++++++++++---------- libs/hashableQListWidgetItem.py | 28 ++++ libs/lib.py | 2 +- libs/stringBundle.py | 63 +++++++ libs/ustr.py | 2 +- resources.qrc | 62 +++---- {icons => resources/icons}/app.icns | Bin {icons => resources/icons}/app.png | Bin {icons => resources/icons}/app.svg | 0 {icons => resources/icons}/cancel.png | Bin {icons => resources/icons}/close.png | Bin {icons => resources/icons}/color.png | Bin {icons => resources/icons}/color_line.png | Bin {icons => resources/icons}/copy.png | Bin {icons => resources/icons}/delete.png | Bin {icons => resources/icons}/done.png | Bin {icons => resources/icons}/done.svg | 0 {icons => resources/icons}/edit.png | Bin {icons => resources/icons}/expert1.png | Bin {icons => resources/icons}/expert2.png | Bin {icons => resources/icons}/eye.png | Bin {icons => resources/icons}/feBlend-icon.png | Bin {icons => resources/icons}/file.png | Bin {icons => resources/icons}/fit-width.png | Bin {icons => resources/icons}/fit-window.png | Bin {icons => resources/icons}/fit.png | Bin {icons => resources/icons}/format_voc.png | Bin {icons => resources/icons}/format_yolo.png | Bin {icons => resources/icons}/help.png | Bin {icons => resources/icons}/labels.png | Bin {icons => resources/icons}/labels.svg | 0 {icons => resources/icons}/new.png | Bin {icons => resources/icons}/next.png | Bin {icons => resources/icons}/objects.png | Bin {icons => resources/icons}/open.png | Bin {icons => resources/icons}/open.svg | 0 {icons => resources/icons}/prev.png | Bin {icons => resources/icons}/quit.png | Bin {icons => resources/icons}/resetall.png | Bin {icons => resources/icons}/save-as.png | Bin {icons => resources/icons}/save-as.svg | 0 {icons => resources/icons}/save.png | Bin {icons => resources/icons}/save.svg | 0 {icons => resources/icons}/undo-cross.png | Bin {icons => resources/icons}/undo.png | Bin {icons => resources/icons}/verify.png | Bin {icons => resources/icons}/zoom-in.png | Bin {icons => resources/icons}/zoom-out.png | Bin {icons => resources/icons}/zoom.png | Bin resources/strings/strings-zh-TW.properties | 65 ++++++++ resources/strings/strings.properties | 65 ++++++++ setup.py | 2 +- tests/test_lib.py | 15 ++ tests/test_stringBundle.py | 17 ++ 55 files changed, 371 insertions(+), 125 deletions(-) create mode 100644 libs/hashableQListWidgetItem.py create mode 100644 libs/stringBundle.py rename {icons => resources/icons}/app.icns (100%) rename {icons => resources/icons}/app.png (100%) rename {icons => resources/icons}/app.svg (100%) rename {icons => resources/icons}/cancel.png (100%) rename {icons => resources/icons}/close.png (100%) rename {icons => resources/icons}/color.png (100%) rename {icons => resources/icons}/color_line.png (100%) rename {icons => resources/icons}/copy.png (100%) rename {icons => resources/icons}/delete.png (100%) rename {icons => resources/icons}/done.png (100%) rename {icons => resources/icons}/done.svg (100%) rename {icons => resources/icons}/edit.png (100%) rename {icons => resources/icons}/expert1.png (100%) rename {icons => resources/icons}/expert2.png (100%) rename {icons => resources/icons}/eye.png (100%) rename {icons => resources/icons}/feBlend-icon.png (100%) rename {icons => resources/icons}/file.png (100%) rename {icons => resources/icons}/fit-width.png (100%) rename {icons => resources/icons}/fit-window.png (100%) rename {icons => resources/icons}/fit.png (100%) rename {icons => resources/icons}/format_voc.png (100%) rename {icons => resources/icons}/format_yolo.png (100%) rename {icons => resources/icons}/help.png (100%) rename {icons => resources/icons}/labels.png (100%) rename {icons => resources/icons}/labels.svg (100%) rename {icons => resources/icons}/new.png (100%) rename {icons => resources/icons}/next.png (100%) rename {icons => resources/icons}/objects.png (100%) rename {icons => resources/icons}/open.png (100%) rename {icons => resources/icons}/open.svg (100%) rename {icons => resources/icons}/prev.png (100%) rename {icons => resources/icons}/quit.png (100%) rename {icons => resources/icons}/resetall.png (100%) rename {icons => resources/icons}/save-as.png (100%) rename {icons => resources/icons}/save-as.svg (100%) rename {icons => resources/icons}/save.png (100%) rename {icons => resources/icons}/save.svg (100%) rename {icons => resources/icons}/undo-cross.png (100%) rename {icons => resources/icons}/undo.png (100%) rename {icons => resources/icons}/verify.png (100%) rename {icons => resources/icons}/zoom-in.png (100%) rename {icons => resources/icons}/zoom-out.png (100%) rename {icons => resources/icons}/zoom.png (100%) create mode 100644 resources/strings/strings-zh-TW.properties create mode 100644 resources/strings/strings.properties create mode 100644 tests/test_lib.py create mode 100644 tests/test_stringBundle.py diff --git a/.gitignore b/.gitignore index 63633bb2..b13f325a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -icons/.DS_Store +resources/icons/.DS_Store resources.py diff --git a/labelImg.py b/labelImg.py index 2fe2db6e..1c8f5547 100755 --- a/labelImg.py +++ b/labelImg.py @@ -32,6 +32,7 @@ from libs.constants import * from libs.lib import struct, newAction, newIcon, addActions, fmtShortcut, generateColorByText from libs.settings import Settings from libs.shape import Shape, DEFAULT_LINE_COLOR, DEFAULT_FILL_COLOR +from libs.stringBundle import StringBundle from libs.canvas import Canvas from libs.zoomWidget import ZoomWidget from libs.labelDialog import LabelDialog @@ -44,6 +45,7 @@ from libs.yolo_io import YoloReader from libs.yolo_io import TXT_EXT from libs.ustr import ustr from libs.version import __version__ +from libs.hashableQListWidgetItem import HashableQListWidgetItem __appname__ = 'labelImg' @@ -76,16 +78,6 @@ 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 = list(range(3)) @@ -98,6 +90,10 @@ class MainWindow(QMainWindow, WindowMixin): self.settings.load() settings = self.settings + # Load string bundle for i18n + self.stringBundle = StringBundle.getBundle() + getStr = lambda strId: self.stringBundle.getString(strId) + # Save as Pascal voc xml self.defaultSaveDir = defaultSaveDir self.usingPascalVocFormat = True @@ -131,7 +127,7 @@ class MainWindow(QMainWindow, WindowMixin): listLayout.setContentsMargins(0, 0, 0, 0) # Create a widget for using default label - self.useDefaultLabelCheckbox = QCheckBox(u'Use default label') + self.useDefaultLabelCheckbox = QCheckBox(getStr('useDefaultLabel')) self.useDefaultLabelCheckbox.setChecked(False) self.defaultLabelTextLine = QLineEdit() useDefaultLabelQHBoxLayout = QHBoxLayout() @@ -141,7 +137,7 @@ class MainWindow(QMainWindow, WindowMixin): useDefaultLabelContainer.setLayout(useDefaultLabelQHBoxLayout) # Create a widget for edit and diffc button - self.diffcButton = QCheckBox(u'difficult') + self.diffcButton = QCheckBox(getStr('useDifficult')) self.diffcButton.setChecked(False) self.diffcButton.stateChanged.connect(self.btnstate) self.editButton = QToolButton() @@ -163,11 +159,10 @@ class MainWindow(QMainWindow, WindowMixin): self.labelList.itemChanged.connect(self.labelItemChanged) listLayout.addWidget(self.labelList) - self.dock = QDockWidget(u'Box Labels', self) - self.dock.setObjectName(u'Labels') + self.dock = QDockWidget(getStr('boxLabelText'), self) + self.dock.setObjectName(getStr('labels')) self.dock.setWidget(labelListContainer) - # Tzutalin 20160906 : Add file list and dock to move faster self.fileListWidget = QListWidget() self.fileListWidget.itemDoubleClicked.connect(self.fileitemDoubleClicked) filelistLayout = QVBoxLayout() @@ -175,8 +170,8 @@ class MainWindow(QMainWindow, WindowMixin): filelistLayout.addWidget(self.fileListWidget) fileListContainer = QWidget() fileListContainer.setLayout(filelistLayout) - self.filedock = QDockWidget(u'File List', self) - self.filedock.setObjectName(u'Files') + self.filedock = QDockWidget(getStr('fileList'), self) + self.filedock.setObjectName(getStr('files')) self.filedock.setWidget(fileListContainer) self.zoomWidget = ZoomWidget() @@ -203,7 +198,6 @@ class MainWindow(QMainWindow, WindowMixin): self.setCentralWidget(scroll) self.addDockWidget(Qt.RightDockWidgetArea, self.dock) - # Tzutalin 20160906 : Add file list and dock to move faster self.addDockWidget(Qt.RightDockWidgetArea, self.filedock) self.filedock.setFeatures(QDockWidget.DockWidgetFloatable) @@ -212,72 +206,72 @@ class MainWindow(QMainWindow, WindowMixin): # Actions action = partial(newAction, self) - quit = action('&Quit', self.close, - 'Ctrl+Q', 'quit', u'Quit application') + quit = action(getStr('quit'), self.close, + 'Ctrl+Q', 'quit', getStr('quitApp')) - open = action('&Open', self.openFile, - 'Ctrl+O', 'open', u'Open image or label file') + open = action(getStr('openFile'), self.openFile, + 'Ctrl+O', 'open', getStr('openFileDetail')) - opendir = action('&Open Dir', self.openDirDialog, - 'Ctrl+u', 'open', u'Open Dir') + opendir = action(getStr('openDir'), self.openDirDialog, + 'Ctrl+u', 'open', getStr('openDir')) - changeSavedir = action('&Change Save Dir', self.changeSavedirDialog, - 'Ctrl+r', 'open', u'Change default saved Annotation dir') + changeSavedir = action(getStr('changeSaveDir'), self.changeSavedirDialog, + 'Ctrl+r', 'open', getStr('changeSavedAnnotationDir')) - openAnnotation = action('&Open Annotation', self.openAnnotationDialog, - 'Ctrl+Shift+O', 'open', u'Open Annotation') + openAnnotation = action(getStr('openAnnotation'), self.openAnnotationDialog, + 'Ctrl+Shift+O', 'open', getStr('openAnnotationDetail')) - openNextImg = action('&Next Image', self.openNextImg, - 'd', 'next', u'Open Next') + openNextImg = action(getStr('nextImg'), self.openNextImg, + 'd', 'next', getStr('nextImgDetail')) - openPrevImg = action('&Prev Image', self.openPrevImg, - 'a', 'prev', u'Open Prev') + openPrevImg = action(getStr('prevImg'), self.openPrevImg, + 'a', 'prev', getStr('prevImgDetail')) - verify = action('&Verify Image', self.verifyImg, - 'space', 'verify', u'Verify Image') + verify = action(getStr('verifyImg'), self.verifyImg, + 'space', 'verify', getStr('verifyImgDetail')) - save = action('&Save', self.saveFile, - 'Ctrl+S', 'save', u'Save labels to file', enabled=False) + save = action(getStr('save'), self.saveFile, + 'Ctrl+S', 'save', getStr('saveDetail'), enabled=False) save_format = action('&PascalVOC', self.change_format, - 'Ctrl+', 'format_voc', u'Change save format', enabled=True) + 'Ctrl+', 'format_voc', getStr('changeSaveFormat'), enabled=True) - saveAs = action('&Save As', self.saveFileAs, - 'Ctrl+Shift+S', 'save-as', u'Save labels to a different file', enabled=False) + saveAs = action(getStr('saveAs'), self.saveFileAs, + 'Ctrl+Shift+S', 'save-as', getStr('saveAsDetail'), enabled=False) - close = action('&Close', self.closeFile, 'Ctrl+W', 'close', u'Close current file') + close = action(getStr('closeCur'), self.closeFile, 'Ctrl+W', 'close', getStr('closeCurDetail')) - resetAll = action('&ResetAll', self.resetAll, None, 'resetall', u'Reset all') + resetAll = action(getStr('resetAll'), self.resetAll, None, 'resetall', getStr('resetAllDetail')) - color1 = action('Box Line Color', self.chooseColor1, - 'Ctrl+L', 'color_line', u'Choose Box line color') + color1 = action(getStr('boxLineColor'), self.chooseColor1, + 'Ctrl+L', 'color_line', getStr('boxLineColorDetail')) - createMode = action('Create\nRectBox', self.setCreateMode, - 'w', 'new', u'Start drawing Boxs', enabled=False) + createMode = action(getStr('crtBox'), self.setCreateMode, + 'w', 'new', getStr('crtBoxDetail'), enabled=False) editMode = action('&Edit\nRectBox', self.setEditMode, 'Ctrl+J', 'edit', u'Move and edit Boxs', enabled=False) - create = action('Create\nRectBox', self.createShape, - 'w', 'new', u'Draw a new Box', enabled=False) - delete = action('Delete\nRectBox', self.deleteSelectedShape, - 'Delete', 'delete', u'Delete', enabled=False) - copy = action('&Duplicate\nRectBox', self.copySelectedShape, - 'Ctrl+D', 'copy', u'Create a duplicate of the selected Box', + create = action(getStr('crtBox'), self.createShape, + 'w', 'new', getStr('crtBoxDetail'), enabled=False) + delete = action(getStr('delBox'), self.deleteSelectedShape, + 'Delete', 'delete', getStr('delBoxDetail'), enabled=False) + copy = action(getStr('dupBox'), self.copySelectedShape, + 'Ctrl+D', 'copy', getStr('dupBoxDetail'), enabled=False) - advancedMode = action('&Advanced Mode', self.toggleAdvancedMode, - 'Ctrl+Shift+A', 'expert', u'Switch to advanced mode', + advancedMode = action(getStr('advancedMode'), self.toggleAdvancedMode, + 'Ctrl+Shift+A', 'expert', getStr('advancedModeDetail'), checkable=True) hideAll = action('&Hide\nRectBox', partial(self.togglePolygons, False), - 'Ctrl+H', 'hide', u'Hide all Boxs', + 'Ctrl+H', 'hide', getStr('hideAllBoxDetail'), enabled=False) showAll = action('&Show\nRectBox', partial(self.togglePolygons, True), - 'Ctrl+A', 'hide', u'Show all Boxs', + 'Ctrl+A', 'hide', getStr('showAllBoxDetail'), enabled=False) - help = action('&Tutorial', self.showTutorialDialog, None, 'help', u'Show demos') - showInfo = action('&Information', self.showInfoDialog, None, 'help', u'Information') + help = action(getStr('tutorial'), self.showTutorialDialog, None, 'help', getStr('tutorialDetail')) + showInfo = action(getStr('info'), self.showInfoDialog, None, 'help', getStr('info')) zoom = QWidgetAction(self) zoom.setDefaultWidget(self.zoomWidget) @@ -287,17 +281,17 @@ class MainWindow(QMainWindow, WindowMixin): fmtShortcut("Ctrl+Wheel"))) self.zoomWidget.setEnabled(False) - zoomIn = action('Zoom &In', partial(self.addZoom, 10), - 'Ctrl++', 'zoom-in', u'Increase zoom level', enabled=False) - zoomOut = action('&Zoom Out', partial(self.addZoom, -10), - 'Ctrl+-', 'zoom-out', u'Decrease zoom level', enabled=False) - zoomOrg = action('&Original size', partial(self.setZoom, 100), - 'Ctrl+=', 'zoom', u'Zoom to original size', enabled=False) - fitWindow = action('&Fit Window', self.setFitWindow, - 'Ctrl+F', 'fit-window', u'Zoom follows window size', + zoomIn = action(getStr('zoomin'), partial(self.addZoom, 10), + 'Ctrl++', 'zoom-in', getStr('zoominDetail'), enabled=False) + zoomOut = action(getStr('zoomout'), partial(self.addZoom, -10), + 'Ctrl+-', 'zoom-out', getStr('zoomoutDetail'), enabled=False) + zoomOrg = action(getStr('originalsize'), partial(self.setZoom, 100), + 'Ctrl+=', 'zoom', getStr('originalsizeDetail'), enabled=False) + fitWindow = action(getStr('fitWin'), self.setFitWindow, + 'Ctrl+F', 'fit-window', getStr('fitWinDetail'), checkable=True, enabled=False) - fitWidth = action('Fit &Width', self.setFitWidth, - 'Ctrl+Shift+F', 'fit-width', u'Zoom follows window width', + fitWidth = action(getStr('fitWidth'), self.setFitWidth, + 'Ctrl+Shift+F', 'fit-width', getStr('fitWidthDetail'), checkable=True, enabled=False) # Group zoom controls into a list for easier toggling. zoomActions = (self.zoomWidget, zoomIn, zoomOut, @@ -310,20 +304,20 @@ class MainWindow(QMainWindow, WindowMixin): self.MANUAL_ZOOM: lambda: 1, } - edit = action('&Edit Label', self.editLabel, - 'Ctrl+E', 'edit', u'Modify the label of the selected Box', + edit = action(getStr('editLabel'), self.editLabel, + 'Ctrl+E', 'edit', getStr('editLabelDetail'), enabled=False) self.editButton.setDefaultAction(edit) - shapeLineColor = action('Shape &Line Color', self.chshapeLineColor, - icon='color_line', tip=u'Change the line color for this specific shape', + shapeLineColor = action(getStr('shapeLineColor'), self.chshapeLineColor, + icon='color_line', tip=getStr('shapeLineColorDetail'), enabled=False) - shapeFillColor = action('Shape &Fill Color', self.chshapeFillColor, - icon='color', tip=u'Change the fill color for this specific shape', + shapeFillColor = action(getStr('shapeFillColor'), self.chshapeFillColor, + icon='color', tip=getStr('shapeFillColorDetail'), enabled=False) labels = self.dock.toggleViewAction() - labels.setText('Show/Hide Label Panel') + labels.setText(getStr('showHide')) labels.setShortcut('Ctrl+Shift+L') # Lavel list context menu. @@ -369,21 +363,21 @@ class MainWindow(QMainWindow, WindowMixin): labelList=labelMenu) # Auto saving : Enable auto saving if pressing next - self.autoSaving = QAction("Auto Saving", self) + self.autoSaving = QAction(getStr('autoSaveMode'), self) self.autoSaving.setCheckable(True) self.autoSaving.setChecked(settings.get(SETTING_AUTO_SAVE, False)) # Sync single class mode from PR#106 - self.singleClassMode = QAction("Single Class Mode", self) + self.singleClassMode = QAction(getStr('singleClsMode'), self) self.singleClassMode.setShortcut("Ctrl+Shift+S") self.singleClassMode.setCheckable(True) self.singleClassMode.setChecked(settings.get(SETTING_SINGLE_CLASS, False)) self.lastLabel = None - # Add option to enable/disable labels being painted at the top of bounding boxes - self.paintLabelsOption = QAction("Paint Labels", self) - self.paintLabelsOption.setShortcut("Ctrl+Shift+P") - self.paintLabelsOption.setCheckable(True) - self.paintLabelsOption.setChecked(settings.get(SETTING_PAINT_LABEL, False)) - self.paintLabelsOption.triggered.connect(self.togglePaintLabelsOption) + # Add option to enable/disable labels being displayed at the top of bounding boxes + self.displayLabelOption = QAction(getStr('displayLabel'), self) + self.displayLabelOption.setShortcut("Ctrl+Shift+P") + self.displayLabelOption.setCheckable(True) + self.displayLabelOption.setChecked(settings.get(SETTING_PAINT_LABEL, False)) + self.displayLabelOption.triggered.connect(self.togglePaintLabelsOption) addActions(self.menus.file, (open, opendir, changeSavedir, openAnnotation, self.menus.recentFiles, save, save_format, saveAs, close, resetAll, quit)) @@ -391,7 +385,7 @@ class MainWindow(QMainWindow, WindowMixin): addActions(self.menus.view, ( self.autoSaving, self.singleClassMode, - self.paintLabelsOption, + self.displayLabelOption, labels, advancedMode, None, hideAll, showAll, None, zoomIn, zoomOut, zoomOrg, None, @@ -739,7 +733,7 @@ class MainWindow(QMainWindow, WindowMixin): self.actions.shapeFillColor.setEnabled(selected) def addLabel(self, shape): - shape.paintLabel = self.paintLabelsOption.isChecked() + shape.paintLabel = self.displayLabelOption.isChecked() item = HashableQListWidgetItem(shape.label) item.setFlags(item.flags() | Qt.ItemIsUserCheckable) item.setCheckState(Qt.Checked) @@ -1112,19 +1106,18 @@ class MainWindow(QMainWindow, WindowMixin): if self.defaultSaveDir and os.path.exists(self.defaultSaveDir): settings[SETTING_SAVE_DIR] = ustr(self.defaultSaveDir) else: - settings[SETTING_SAVE_DIR] = "" + settings[SETTING_SAVE_DIR] = '' if self.lastOpenDir and os.path.exists(self.lastOpenDir): settings[SETTING_LAST_OPEN_DIR] = self.lastOpenDir else: - settings[SETTING_LAST_OPEN_DIR] = "" + settings[SETTING_LAST_OPEN_DIR] = '' settings[SETTING_AUTO_SAVE] = self.autoSaving.isChecked() settings[SETTING_SINGLE_CLASS] = self.singleClassMode.isChecked() - settings[SETTING_PAINT_LABEL] = self.paintLabelsOption.isChecked() + settings[SETTING_PAINT_LABEL] = self.displayLabelOption.isChecked() settings[SETTING_DRAW_SQUARE] = self.drawSquaresOption.isChecked() settings.save() - ## User Dialogs ## def loadRecent(self, filename): if self.mayContinue(): @@ -1195,7 +1188,6 @@ class MainWindow(QMainWindow, WindowMixin): if not self.mayContinue() or not dirpath: return - self.lastOpenDir = dirpath self.dirname = dirpath self.filePath = None @@ -1437,9 +1429,8 @@ class MainWindow(QMainWindow, WindowMixin): self.canvas.verified = tYoloParseReader.verified def togglePaintLabelsOption(self): - paintLabelsOptionChecked = self.paintLabelsOption.isChecked() for shape in self.canvas.shapes: - shape.paintLabel = paintLabelsOptionChecked + shape.paintLabel = self.displayLabelOption.isChecked() def toogleDrawSquare(self): self.canvas.setDrawingShapeToSquare(self.drawSquaresOption.isChecked()) diff --git a/libs/hashableQListWidgetItem.py b/libs/hashableQListWidgetItem.py new file mode 100644 index 00000000..ac7818a9 --- /dev/null +++ b/libs/hashableQListWidgetItem.py @@ -0,0 +1,28 @@ +#!/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)) diff --git a/libs/lib.py b/libs/lib.py index 13673cb8..2a040976 100644 --- a/libs/lib.py +++ b/libs/lib.py @@ -75,7 +75,7 @@ def fmtShortcut(text): def generateColorByText(text): - s = str(ustr(text)) + s = ustr(text) hashCode = int(hashlib.sha256(s.encode('utf-8')).hexdigest(), 16) r = int((hashCode / 255) % 255) g = int((hashCode / 65025) % 255) diff --git a/libs/stringBundle.py b/libs/stringBundle.py new file mode 100644 index 00000000..1fb72a2a --- /dev/null +++ b/libs/stringBundle.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +import re +import resources +import os +import sys +from libs.ustr import ustr +try: + from PyQt5.QtCore import * +except ImportError: + if sys.version_info.major >= 3: + import sip + sip.setapi('QVariant', 2) + from PyQt4.QtCore import * + + +class StringBundle: + + __create_key = object() + + def __init__(self, create_key, localeStr): + assert(create_key == StringBundle.__create_key), "StringBundle must be created using StringBundle.getBundle" + self.idToMessage = {} + paths = self.__createLookupFallbackList(localeStr) + for path in paths: + self.__loadBundle(path) + + @classmethod + def getBundle(cls, localeStr=os.getenv('LANG')): + return StringBundle(cls.__create_key, localeStr) + + def getString(self, stringId): + assert(stringId in self.idToMessage), "Missing string id : " + stringId + return self.idToMessage[stringId] + + def __createLookupFallbackList(self, localeStr): + resultPaths = [] + basePath = ":/strings" + resultPaths.append(basePath) + # Don't follow standard BCP47. Simple fallback + tags = re.split('[^a-zA-Z]', localeStr) + for tag in tags: + lastPath = resultPaths[-1] + resultPaths.append(lastPath + '-' + tag) + + return resultPaths + + def __loadBundle(self, path): + PROP_SEPERATOR = '=' + f = QFile(path) + if f.exists(): + if f.open(QIODevice.ReadOnly | QFile.Text): + text = QTextStream(f) + text.setCodec("UTF-8") + + while not text.atEnd(): + line = ustr(text.readLine()) + key_value = line.split(PROP_SEPERATOR) + key = key_value[0].strip() + value = PROP_SEPERATOR.join(key_value[1:]).strip().strip('"') + self.idToMessage[key] = value + + f.close() diff --git a/libs/ustr.py b/libs/ustr.py index 0fcf78ba..76bdf4cb 100644 --- a/libs/ustr.py +++ b/libs/ustr.py @@ -9,7 +9,7 @@ def ustr(x): if type(x) == str: return x.decode(DEFAULT_ENCODING) if type(x) == QString: - return unicode(x, DEFAULT_ENCODING) + return unicode(x) return x else: return x diff --git a/resources.qrc b/resources.qrc index 0a8c83c0..1c757d95 100644 --- a/resources.qrc +++ b/resources.qrc @@ -1,35 +1,37 @@ -icons/help.png -icons/app.png -icons/expert2.png -icons/done.png -icons/file.png -icons/labels.png -icons/objects.png -icons/close.png -icons/fit-width.png -icons/fit-window.png -icons/undo.png -icons/eye.png -icons/quit.png -icons/copy.png -icons/edit.png -icons/open.png -icons/save.png -icons/format_voc.png -icons/format_yolo.png -icons/save-as.png -icons/color.png -icons/color_line.png -icons/zoom.png -icons/zoom-in.png -icons/zoom-out.png -icons/cancel.png -icons/next.png -icons/prev.png -icons/resetall.png -icons/verify.png +resources/icons/help.png +resources/icons/app.png +resources/icons/expert2.png +resources/icons/done.png +resources/icons/file.png +resources/icons/labels.png +resources/icons/objects.png +resources/icons/close.png +resources/icons/fit-width.png +resources/icons/fit-window.png +resources/icons/undo.png +resources/icons/eye.png +resources/icons/quit.png +resources/icons/copy.png +resources/icons/edit.png +resources/icons/open.png +resources/icons/save.png +resources/icons/format_voc.png +resources/icons/format_yolo.png +resources/icons/save-as.png +resources/icons/color.png +resources/icons/color_line.png +resources/icons/zoom.png +resources/icons/zoom-in.png +resources/icons/zoom-out.png +resources/icons/cancel.png +resources/icons/next.png +resources/icons/prev.png +resources/icons/resetall.png +resources/icons/verify.png +resources/strings/strings.properties +resources/strings/strings-zh-TW.properties diff --git a/icons/app.icns b/resources/icons/app.icns similarity index 100% rename from icons/app.icns rename to resources/icons/app.icns diff --git a/icons/app.png b/resources/icons/app.png similarity index 100% rename from icons/app.png rename to resources/icons/app.png diff --git a/icons/app.svg b/resources/icons/app.svg similarity index 100% rename from icons/app.svg rename to resources/icons/app.svg diff --git a/icons/cancel.png b/resources/icons/cancel.png similarity index 100% rename from icons/cancel.png rename to resources/icons/cancel.png diff --git a/icons/close.png b/resources/icons/close.png similarity index 100% rename from icons/close.png rename to resources/icons/close.png diff --git a/icons/color.png b/resources/icons/color.png similarity index 100% rename from icons/color.png rename to resources/icons/color.png diff --git a/icons/color_line.png b/resources/icons/color_line.png similarity index 100% rename from icons/color_line.png rename to resources/icons/color_line.png diff --git a/icons/copy.png b/resources/icons/copy.png similarity index 100% rename from icons/copy.png rename to resources/icons/copy.png diff --git a/icons/delete.png b/resources/icons/delete.png similarity index 100% rename from icons/delete.png rename to resources/icons/delete.png diff --git a/icons/done.png b/resources/icons/done.png similarity index 100% rename from icons/done.png rename to resources/icons/done.png diff --git a/icons/done.svg b/resources/icons/done.svg similarity index 100% rename from icons/done.svg rename to resources/icons/done.svg diff --git a/icons/edit.png b/resources/icons/edit.png similarity index 100% rename from icons/edit.png rename to resources/icons/edit.png diff --git a/icons/expert1.png b/resources/icons/expert1.png similarity index 100% rename from icons/expert1.png rename to resources/icons/expert1.png diff --git a/icons/expert2.png b/resources/icons/expert2.png similarity index 100% rename from icons/expert2.png rename to resources/icons/expert2.png diff --git a/icons/eye.png b/resources/icons/eye.png similarity index 100% rename from icons/eye.png rename to resources/icons/eye.png diff --git a/icons/feBlend-icon.png b/resources/icons/feBlend-icon.png similarity index 100% rename from icons/feBlend-icon.png rename to resources/icons/feBlend-icon.png diff --git a/icons/file.png b/resources/icons/file.png similarity index 100% rename from icons/file.png rename to resources/icons/file.png diff --git a/icons/fit-width.png b/resources/icons/fit-width.png similarity index 100% rename from icons/fit-width.png rename to resources/icons/fit-width.png diff --git a/icons/fit-window.png b/resources/icons/fit-window.png similarity index 100% rename from icons/fit-window.png rename to resources/icons/fit-window.png diff --git a/icons/fit.png b/resources/icons/fit.png similarity index 100% rename from icons/fit.png rename to resources/icons/fit.png diff --git a/icons/format_voc.png b/resources/icons/format_voc.png similarity index 100% rename from icons/format_voc.png rename to resources/icons/format_voc.png diff --git a/icons/format_yolo.png b/resources/icons/format_yolo.png similarity index 100% rename from icons/format_yolo.png rename to resources/icons/format_yolo.png diff --git a/icons/help.png b/resources/icons/help.png similarity index 100% rename from icons/help.png rename to resources/icons/help.png diff --git a/icons/labels.png b/resources/icons/labels.png similarity index 100% rename from icons/labels.png rename to resources/icons/labels.png diff --git a/icons/labels.svg b/resources/icons/labels.svg similarity index 100% rename from icons/labels.svg rename to resources/icons/labels.svg diff --git a/icons/new.png b/resources/icons/new.png similarity index 100% rename from icons/new.png rename to resources/icons/new.png diff --git a/icons/next.png b/resources/icons/next.png similarity index 100% rename from icons/next.png rename to resources/icons/next.png diff --git a/icons/objects.png b/resources/icons/objects.png similarity index 100% rename from icons/objects.png rename to resources/icons/objects.png diff --git a/icons/open.png b/resources/icons/open.png similarity index 100% rename from icons/open.png rename to resources/icons/open.png diff --git a/icons/open.svg b/resources/icons/open.svg similarity index 100% rename from icons/open.svg rename to resources/icons/open.svg diff --git a/icons/prev.png b/resources/icons/prev.png similarity index 100% rename from icons/prev.png rename to resources/icons/prev.png diff --git a/icons/quit.png b/resources/icons/quit.png similarity index 100% rename from icons/quit.png rename to resources/icons/quit.png diff --git a/icons/resetall.png b/resources/icons/resetall.png similarity index 100% rename from icons/resetall.png rename to resources/icons/resetall.png diff --git a/icons/save-as.png b/resources/icons/save-as.png similarity index 100% rename from icons/save-as.png rename to resources/icons/save-as.png diff --git a/icons/save-as.svg b/resources/icons/save-as.svg similarity index 100% rename from icons/save-as.svg rename to resources/icons/save-as.svg diff --git a/icons/save.png b/resources/icons/save.png similarity index 100% rename from icons/save.png rename to resources/icons/save.png diff --git a/icons/save.svg b/resources/icons/save.svg similarity index 100% rename from icons/save.svg rename to resources/icons/save.svg diff --git a/icons/undo-cross.png b/resources/icons/undo-cross.png similarity index 100% rename from icons/undo-cross.png rename to resources/icons/undo-cross.png diff --git a/icons/undo.png b/resources/icons/undo.png similarity index 100% rename from icons/undo.png rename to resources/icons/undo.png diff --git a/icons/verify.png b/resources/icons/verify.png similarity index 100% rename from icons/verify.png rename to resources/icons/verify.png diff --git a/icons/zoom-in.png b/resources/icons/zoom-in.png similarity index 100% rename from icons/zoom-in.png rename to resources/icons/zoom-in.png diff --git a/icons/zoom-out.png b/resources/icons/zoom-out.png similarity index 100% rename from icons/zoom-out.png rename to resources/icons/zoom-out.png diff --git a/icons/zoom.png b/resources/icons/zoom.png similarity index 100% rename from icons/zoom.png rename to resources/icons/zoom.png diff --git a/resources/strings/strings-zh-TW.properties b/resources/strings/strings-zh-TW.properties new file mode 100644 index 00000000..c09f3691 --- /dev/null +++ b/resources/strings/strings-zh-TW.properties @@ -0,0 +1,65 @@ +saveAsDetail=將標籤保存到其他文件 +changeSaveDir=改變存放目錄 +openFile=開啟檔案 +shapeLineColorDetail=更改線條顏色 +resetAll=重置 +crtBox=創建區塊 +crtBoxDetail=畫一個區塊 +dupBoxDetail=複製區塊 +verifyImg=驗證圖像 +zoominDetail=放大 +verifyImgDetail=驗證圖像 +saveDetail=將標籤存到 +openFileDetail=打開圖像 +fitWidthDetail=調整到窗口寬度 +tutorial=YouTube教學 +editLabel=編輯標籤 +openAnnotationDetail=打開標籤文件 +quit=結束 +shapeFillColorDetail=更改填充顏色 +closeCurDetail=關閉目前檔案 +closeCur=關閉 +fitWin=調整到跟窗口一樣大小 +delBox=刪除選取區塊 +boxLineColorDetail=選擇框線顏色 +originalsize=原始大小 +resetAllDetail=重設所有設定 +zoomoutDetail=畫面放大 +save=儲存 +saveAs=另存為 +fitWinDetail=縮放到窗口一樣 +openDir=開啟目錄 +showHide=顯示/隱藏標籤 +changeSaveFormat=更改儲存格式 +shapeFillColor=填充顏色 +quitApp=離開本程式 +dupBox=複製區塊 +delBoxDetail=刪除區塊 +zoomin=放大畫面 +info=資訊 +openAnnotation=開啟標籤 +prevImgDetail=上一個圖像 +fitWidth=縮放到跟畫面一樣寬 +zoomout=縮小畫面 +changeSavedAnnotationDir=更改預設標籤存的目錄 +nextImgDetail=下一個圖像 +originalsizeDetail=放大到原始大小 +prevImg=上一個圖像 +tutorialDetail=顯示示範內容 +shapeLineColor=形狀線條顏色 +boxLineColor=日期分隔線顏色 +editLabelDetail=修改所選區塊的標籤 +nextImg=下一張圖片 +useDefaultLabel=使用預設標籤 +useDifficult=有難度的 +boxLabelText=區塊的標籤 +labels=標籤 +autoSaveMode=自動儲存模式 +singleClsMode=單一類別模式 +displayLabel=顯示類別 +fileList=檔案清單 +files=檔案 +advancedMode=進階模式 +advancedModeDetail=切到進階模式 +showAllBoxDetail=顯示所有區塊 +hideAllBoxDetail=隱藏所有區塊 \ No newline at end of file diff --git a/resources/strings/strings.properties b/resources/strings/strings.properties new file mode 100644 index 00000000..a8fe9278 --- /dev/null +++ b/resources/strings/strings.properties @@ -0,0 +1,65 @@ +openFile=Open +openFileDetail=Open image or label file +quit=Quit +quitApp=Quit application +openDir=Open Dir +changeSavedAnnotationDir=Change default saved Annotation dir +openAnnotation=Open Annotation +openAnnotationDetail=Open an annotation file +changeSaveDir=Change Save Dir +nextImg=Next Image +nextImgDetail=Open the next Image +prevImg=Prev Image +prevImgDetail=Open the previous Image +verifyImg=Verify Image +verifyImgDetail=Verify Image +save=Save +saveDetail=Save the labels to a file +changeSaveFormat=Change save format +saveAs=Save As +saveAsDetail=Save the labels to a different file +closeCur=Close +closeCurDetail=Close the current file +resetAll=Reset All +resetAllDetail=Reset All +boxLineColor=Box Line Color +boxLineColorDetail=Choose Box line color +crtBox=Create\nRectBox +crtBoxDetail=Draw a new box +delBox=Delete\nRectBox +delBoxDetail=Remove the box +dupBox=Duplicate\nRectBox +dupBoxDetail=Create a duplicate of the selected box +tutorial=Tutorial +tutorialDetail=Show demo +info=Information +zoomin=Zoom In +zoominDetail=Increase zoom level +zoomout=Zoom Out +zoomoutDetail=Decrease zoom level +originalsize=Original size +originalsizeDetail=Zoom to original size +fitWin=Fit Window +fitWinDetail=Zoom follows window size +fitWidth=Fit Widith +fitWidthDetail=Zoom follows window width +editLabel=Edit Label +editLabelDetail=Modify the label of the selected Box +shapeLineColor=Shape Line Color +shapeLineColorDetail=Change the line color for this specific shape +shapeFillColor=Shape Fill Color +shapeFillColorDetail=Change the fill color for this specific shape +showHide=Show/Hide Label Panel +useDefaultLabel=Use default labtel +useDifficult=difficult +boxLabelText=Box Labels +labels=Labels +autoSaveMode=Auto Save mode +singleClsMode=Single Class Mode +displayLabel=Display Labels +fileList=File List +files=Files +advancedMode=Advanced Mode +advancedModeDetail=Swtich to advanced mode +showAllBoxDetail=Show all bounding boxes +hideAllBoxDetail=Hide all bounding boxes diff --git a/setup.py b/setup.py index f1491279..e1485ffb 100644 --- a/setup.py +++ b/setup.py @@ -30,7 +30,7 @@ required_packages.append('labelImg') APP = ['labelImg.py'] OPTIONS = { 'argv_emulation': True, - 'iconfile': 'icons/app.icns' + 'iconfile': 'resources/icons/app.icns' } setup( diff --git a/tests/test_lib.py b/tests/test_lib.py new file mode 100644 index 00000000..547179b9 --- /dev/null +++ b/tests/test_lib.py @@ -0,0 +1,15 @@ +import os +import sys +import unittest +from libs.lib import struct, newAction, newIcon, addActions, fmtShortcut, generateColorByText + +class TestLib(unittest.TestCase): + + def test_generateColorByGivingUniceText_noError(self): + res = generateColorByText(u'\u958B\u555F\u76EE\u9304') + self.assertTrue(res.green() >= 0) + self.assertTrue(res.red() >= 0) + self.assertTrue(res.blue() >= 0) + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_stringBundle.py b/tests/test_stringBundle.py new file mode 100644 index 00000000..2ae63fa7 --- /dev/null +++ b/tests/test_stringBundle.py @@ -0,0 +1,17 @@ +import os +import sys +import unittest +from stringBundle import StringBundle + +class TestStringBundle(unittest.TestCase): + + def test_loadDefaultBundle_withoutError(self): + strBundle = StringBundle.getBundle('en') + self.assertEqual(strBundle.getString("openDir"), 'Open Dir', 'Fail to load the default bundle') + + def test_fallback_withoutError(self): + strBundle = StringBundle.getBundle('zh-TW') + self.assertEqual(strBundle.getString("openDir"), u'\u958B\u555F\u76EE\u9304', 'Fail to load the zh-TW bundle') + +if __name__ == '__main__': + unittest.main()