`__
+
diff --git a/labelImg-master/__init__.py b/labelImg-master/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/labelImg-master/build-tools/.gitignore b/labelImg-master/build-tools/.gitignore
new file mode 100644
index 0000000..e05bf6b
--- /dev/null
+++ b/labelImg-master/build-tools/.gitignore
@@ -0,0 +1,12 @@
+*.spec
+build
+dist
+pyinstaller
+python-2.*
+pywin32*
+virtual-wine
+venv_wine
+PyQt4-*
+lxml-*
+windows_v*
+linux_v*
diff --git a/labelImg-master/build-tools/README.md b/labelImg-master/build-tools/README.md
new file mode 100644
index 0000000..8e4cc5d
--- /dev/null
+++ b/labelImg-master/build-tools/README.md
@@ -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
+```
diff --git a/labelImg-master/build-tools/build-for-macos.sh b/labelImg-master/build-tools/build-for-macos.sh
new file mode 100644
index 0000000..dfe926b
--- /dev/null
+++ b/labelImg-master/build-tools/build-for-macos.sh
@@ -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'
diff --git a/labelImg-master/build-tools/build-for-pypi.sh b/labelImg-master/build-tools/build-for-pypi.sh
new file mode 100644
index 0000000..d5ad5c7
--- /dev/null
+++ b/labelImg-master/build-tools/build-for-pypi.sh
@@ -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
diff --git a/labelImg-master/build-tools/build-ubuntu-binary.sh b/labelImg-master/build-tools/build-ubuntu-binary.sh
new file mode 100644
index 0000000..dbe5302
--- /dev/null
+++ b/labelImg-master/build-tools/build-ubuntu-binary.sh
@@ -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
diff --git a/labelImg-master/build-tools/build-windows-binary.sh b/labelImg-master/build-tools/build-windows-binary.sh
new file mode 100644
index 0000000..566e88f
--- /dev/null
+++ b/labelImg-master/build-tools/build-windows-binary.sh
@@ -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
diff --git a/labelImg-master/build-tools/envsetup.sh b/labelImg-master/build-tools/envsetup.sh
new file mode 100644
index 0000000..242b0c5
--- /dev/null
+++ b/labelImg-master/build-tools/envsetup.sh
@@ -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
+
diff --git a/labelImg-master/build-tools/run-in-container.sh b/labelImg-master/build-tools/run-in-container.sh
new file mode 100644
index 0000000..980699d
--- /dev/null
+++ b/labelImg-master/build-tools/run-in-container.sh
@@ -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
+
diff --git a/labelImg-master/data/predefined_classes.txt b/labelImg-master/data/predefined_classes.txt
new file mode 100644
index 0000000..bc2eef1
--- /dev/null
+++ b/labelImg-master/data/predefined_classes.txt
@@ -0,0 +1,15 @@
+dog
+person
+cat
+tv
+car
+meatballs
+marinara sauce
+tomato soup
+chicken noodle soup
+french onion soup
+chicken breast
+ribs
+pulled pork
+hamburger
+cavity
\ No newline at end of file
diff --git a/labelImg-master/demo/demo.jpg b/labelImg-master/demo/demo.jpg
new file mode 100644
index 0000000..c339af4
Binary files /dev/null and b/labelImg-master/demo/demo.jpg differ
diff --git a/labelImg-master/demo/demo3.jpg b/labelImg-master/demo/demo3.jpg
new file mode 100644
index 0000000..8d58a44
Binary files /dev/null and b/labelImg-master/demo/demo3.jpg differ
diff --git a/labelImg-master/demo/demo4.png b/labelImg-master/demo/demo4.png
new file mode 100644
index 0000000..dc21998
Binary files /dev/null and b/labelImg-master/demo/demo4.png differ
diff --git a/labelImg-master/demo/demo5.png b/labelImg-master/demo/demo5.png
new file mode 100644
index 0000000..0a857a0
Binary files /dev/null and b/labelImg-master/demo/demo5.png differ
diff --git a/labelImg-master/issue_template.md b/labelImg-master/issue_template.md
new file mode 100644
index 0000000..5222ada
--- /dev/null
+++ b/labelImg-master/issue_template.md
@@ -0,0 +1,7 @@
+
+
+- **OS:**
+- **PyQt version:**
diff --git a/labelImg-master/labelImg.py b/labelImg-master/labelImg.py
new file mode 100644
index 0000000..15630f3
--- /dev/null
+++ b/labelImg-master/labelImg.py
@@ -0,0 +1,1482 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+import codecs
+# import distutils.spawn
+import os.path
+import platform
+import re
+import sys
+import subprocess
+
+from functools import partial
+from collections import defaultdict
+
+try:
+ from PyQt5.QtGui import *
+ from PyQt5.QtCore import *
+ from PyQt5.QtWidgets import *
+except ImportError:
+ # needed for py3+qt4
+ # Ref:
+ # http://pyqt.sourceforge.net/Docs/PyQt4/incompatible_apis.html
+ # 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 PyQt5.QtGui import *
+ from PyQt5.QtCore import *
+
+from libs.resources import *
+from libs.constants import *
+from libs.utils import *
+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
+from libs.colorDialog import ColorDialog
+from libs.labelFile import LabelFile, LabelFileError
+from libs.toolBar import ToolBar
+from libs.pascal_voc_io import PascalVocReader
+from libs.pascal_voc_io import XML_EXT
+from libs.yolo_io import YoloReader
+from libs.yolo_io import TXT_EXT
+from libs.ustr import ustr
+from libs.hashableQListWidgetItem import HashableQListWidgetItem
+
+__appname__ = 'labelImg'
+
+class WindowMixin(object):
+
+ def menu(self, title, actions=None):
+ menu = self.menuBar().addMenu(title)
+ if actions:
+ addActions(menu, actions)
+ return menu
+
+ def toolbar(self, title, actions=None):
+ toolbar = ToolBar(title)
+ toolbar.setObjectName(u'%sToolBar' % title)
+ # toolbar.setOrientation(Qt.Vertical)
+ toolbar.setToolButtonStyle(Qt.ToolButtonTextUnderIcon)
+ if actions:
+ addActions(toolbar, actions)
+ self.addToolBar(Qt.LeftToolBarArea, toolbar)
+ return toolbar
+
+
+class MainWindow(QMainWindow, WindowMixin):
+ FIT_WINDOW, FIT_WIDTH, MANUAL_ZOOM = list(range(3))
+
+ def __init__(self, defaultFilename=None, defaultPrefdefClassFile=None, defaultSaveDir=None):
+ super(MainWindow, self).__init__()
+ self.setWindowTitle(__appname__)
+
+ # Load setting in the main thread
+ self.settings = Settings()
+ 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
+ self.usingYoloFormat = False
+
+ # For loading all image under a directory
+ self.mImgList = []
+ self.dirname = None
+ self.labelHist = []
+ self.lastOpenDir = None
+
+ # Whether we need to save or not.
+ self.dirty = False
+
+ self._noSelectionSlot = False
+ self._beginner = True
+ self.screencastViewer = self.getAvailableScreencastViewer()
+ self.screencast = "https://youtu.be/p0nR2YsCY_U"
+
+ # Load predefined classes to the list
+ self.loadPredefinedClasses(defaultPrefdefClassFile)
+
+ # Main widgets and related state.
+ self.labelDialog = LabelDialog(parent=self, listItem=self.labelHist)
+
+ self.itemsToShapes = {}
+ self.shapesToItems = {}
+ self.prevLabelText = ''
+
+ listLayout = QVBoxLayout()
+ listLayout.setContentsMargins(0, 0, 0, 0)
+
+ # Create a widget for using default label
+ self.useDefaultLabelCheckbox = QCheckBox(getStr('useDefaultLabel'))
+ self.useDefaultLabelCheckbox.setChecked(False)
+ self.defaultLabelTextLine = QLineEdit()
+ useDefaultLabelQHBoxLayout = QHBoxLayout()
+ useDefaultLabelQHBoxLayout.addWidget(self.useDefaultLabelCheckbox)
+ useDefaultLabelQHBoxLayout.addWidget(self.defaultLabelTextLine)
+ useDefaultLabelContainer = QWidget()
+ useDefaultLabelContainer.setLayout(useDefaultLabelQHBoxLayout)
+
+ # Create a widget for edit and diffc button
+ self.diffcButton = QCheckBox(getStr('useDifficult'))
+ self.diffcButton.setChecked(False)
+ self.diffcButton.stateChanged.connect(self.btnstate)
+ self.editButton = QToolButton()
+ self.editButton.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
+
+ # Add some of widgets to listLayout
+ listLayout.addWidget(self.editButton)
+ listLayout.addWidget(self.diffcButton)
+ listLayout.addWidget(useDefaultLabelContainer)
+
+ # Create and add a widget for showing current label items
+ self.labelList = QListWidget()
+ labelListContainer = QWidget()
+ labelListContainer.setLayout(listLayout)
+ self.labelList.itemActivated.connect(self.labelSelectionChanged)
+ self.labelList.itemSelectionChanged.connect(self.labelSelectionChanged)
+ self.labelList.itemDoubleClicked.connect(self.editLabel)
+ # Connect to itemChanged to detect checkbox changes.
+ self.labelList.itemChanged.connect(self.labelItemChanged)
+ listLayout.addWidget(self.labelList)
+
+ self.dock = QDockWidget(getStr('boxLabelText'), self)
+ self.dock.setObjectName(getStr('labels'))
+ self.dock.setWidget(labelListContainer)
+
+ self.fileListWidget = QListWidget()
+ self.fileListWidget.itemDoubleClicked.connect(self.fileitemDoubleClicked)
+ filelistLayout = QVBoxLayout()
+ filelistLayout.setContentsMargins(0, 0, 0, 0)
+ filelistLayout.addWidget(self.fileListWidget)
+ fileListContainer = QWidget()
+ fileListContainer.setLayout(filelistLayout)
+ self.filedock = QDockWidget(getStr('fileList'), self)
+ self.filedock.setObjectName(getStr('files'))
+ self.filedock.setWidget(fileListContainer)
+
+ self.zoomWidget = ZoomWidget()
+ self.colorDialog = ColorDialog(parent=self)
+
+ self.canvas = Canvas(parent=self)
+ self.canvas.zoomRequest.connect(self.zoomRequest)
+ self.canvas.setDrawingShapeToSquare(settings.get(SETTING_DRAW_SQUARE, False))
+
+ scroll = QScrollArea()
+ scroll.setWidget(self.canvas)
+ scroll.setWidgetResizable(True)
+ self.scrollBars = {
+ Qt.Vertical: scroll.verticalScrollBar(),
+ Qt.Horizontal: scroll.horizontalScrollBar()
+ }
+ self.scrollArea = scroll
+ self.canvas.scrollRequest.connect(self.scrollRequest)
+
+ self.canvas.newShape.connect(self.newShape)
+ self.canvas.shapeMoved.connect(self.setDirty)
+ self.canvas.selectionChanged.connect(self.shapeSelectionChanged)
+ self.canvas.drawingPolygon.connect(self.toggleDrawingSensitive)
+
+ self.setCentralWidget(scroll)
+ self.addDockWidget(Qt.RightDockWidgetArea, self.dock)
+ self.addDockWidget(Qt.RightDockWidgetArea, self.filedock)
+ self.filedock.setFeatures(QDockWidget.DockWidgetFloatable)
+
+ self.dockFeatures = QDockWidget.DockWidgetClosable | QDockWidget.DockWidgetFloatable
+ self.dock.setFeatures(self.dock.features() ^ self.dockFeatures)
+
+ # Actions
+ action = partial(newAction, self)
+ quit = action(getStr('quit'), self.close,
+ 'Ctrl+Q', 'quit', getStr('quitApp'))
+
+ open = action(getStr('openFile'), self.openFile,
+ 'Ctrl+O', 'open', getStr('openFileDetail'))
+
+ opendir = action(getStr('openDir'), self.openDirDialog,
+ 'Ctrl+u', 'open', getStr('openDir'))
+
+ changeSavedir = action(getStr('changeSaveDir'), self.changeSavedirDialog,
+ 'Ctrl+r', 'open', getStr('changeSavedAnnotationDir'))
+
+ openAnnotation = action(getStr('openAnnotation'), self.openAnnotationDialog,
+ 'Ctrl+Shift+O', 'open', getStr('openAnnotationDetail'))
+
+ openNextImg = action(getStr('nextImg'), self.openNextImg,
+ 'd', 'next', getStr('nextImgDetail'))
+
+ openPrevImg = action(getStr('prevImg'), self.openPrevImg,
+ 'a', 'prev', getStr('prevImgDetail'))
+
+ verify = action(getStr('verifyImg'), self.verifyImg,
+ 'space', 'verify', getStr('verifyImgDetail'))
+
+ save = action(getStr('save'), self.saveFile,
+ 'Ctrl+S', 'save', getStr('saveDetail'), enabled=False)
+
+ save_format = action('&PascalVOC', self.change_format,
+ 'Ctrl+', 'format_voc', getStr('changeSaveFormat'), enabled=True)
+
+ saveAs = action(getStr('saveAs'), self.saveFileAs,
+ 'Ctrl+Shift+S', 'save-as', getStr('saveAsDetail'), enabled=False)
+
+ close = action(getStr('closeCur'), self.closeFile, 'Ctrl+W', 'close', getStr('closeCurDetail'))
+
+ resetAll = action(getStr('resetAll'), self.resetAll, None, 'resetall', getStr('resetAllDetail'))
+
+ color1 = action(getStr('boxLineColor'), self.chooseColor1,
+ 'Ctrl+L', 'color_line', getStr('boxLineColorDetail'))
+
+ 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(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(getStr('advancedMode'), self.toggleAdvancedMode,
+ 'Ctrl+Shift+A', 'expert', getStr('advancedModeDetail'),
+ checkable=True)
+
+ hideAll = action('&Hide\nRectBox', partial(self.togglePolygons, False),
+ 'Ctrl+H', 'hide', getStr('hideAllBoxDetail'),
+ enabled=False)
+ showAll = action('&Show\nRectBox', partial(self.togglePolygons, True),
+ 'Ctrl+A', 'hide', getStr('showAllBoxDetail'),
+ enabled=False)
+
+ 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)
+ self.zoomWidget.setWhatsThis(
+ u"Zoom in or out of the image. Also accessible with"
+ " %s and %s from the canvas." % (fmtShortcut("Ctrl+[-+]"),
+ fmtShortcut("Ctrl+Wheel")))
+ self.zoomWidget.setEnabled(False)
+
+ 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(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,
+ zoomOrg, fitWindow, fitWidth)
+ self.zoomMode = self.MANUAL_ZOOM
+ self.scalers = {
+ self.FIT_WINDOW: self.scaleFitWindow,
+ self.FIT_WIDTH: self.scaleFitWidth,
+ # Set to one to scale to 100% when loading files.
+ self.MANUAL_ZOOM: lambda: 1,
+ }
+
+ edit = action(getStr('editLabel'), self.editLabel,
+ 'Ctrl+E', 'edit', getStr('editLabelDetail'),
+ enabled=False)
+ self.editButton.setDefaultAction(edit)
+
+ shapeLineColor = action(getStr('shapeLineColor'), self.chshapeLineColor,
+ icon='color_line', tip=getStr('shapeLineColorDetail'),
+ enabled=False)
+ shapeFillColor = action(getStr('shapeFillColor'), self.chshapeFillColor,
+ icon='color', tip=getStr('shapeFillColorDetail'),
+ enabled=False)
+
+ labels = self.dock.toggleViewAction()
+ labels.setText(getStr('showHide'))
+ labels.setShortcut('Ctrl+Shift+L')
+
+ # Label list context menu.
+ labelMenu = QMenu()
+ addActions(labelMenu, (edit, delete))
+ self.labelList.setContextMenuPolicy(Qt.CustomContextMenu)
+ self.labelList.customContextMenuRequested.connect(
+ self.popLabelListMenu)
+
+ # Draw squares/rectangles
+ self.drawSquaresOption = QAction('Draw Squares', self)
+ self.drawSquaresOption.setShortcut('Ctrl+Shift+R')
+ self.drawSquaresOption.setCheckable(True)
+ self.drawSquaresOption.setChecked(settings.get(SETTING_DRAW_SQUARE, False))
+ self.drawSquaresOption.triggered.connect(self.toogleDrawSquare)
+
+ # Store actions for further handling.
+ self.actions = struct(save=save, save_format=save_format, saveAs=saveAs, open=open, close=close, resetAll = resetAll,
+ lineColor=color1, create=create, delete=delete, edit=edit, copy=copy,
+ createMode=createMode, editMode=editMode, advancedMode=advancedMode,
+ shapeLineColor=shapeLineColor, shapeFillColor=shapeFillColor,
+ zoom=zoom, zoomIn=zoomIn, zoomOut=zoomOut, zoomOrg=zoomOrg,
+ fitWindow=fitWindow, fitWidth=fitWidth,
+ zoomActions=zoomActions,
+ fileMenuActions=(
+ open, opendir, save, saveAs, close, resetAll, quit),
+ beginner=(), advanced=(),
+ editMenu=(edit, copy, delete,
+ None, color1, self.drawSquaresOption),
+ beginnerContext=(create, edit, copy, delete),
+ advancedContext=(createMode, editMode, edit, copy,
+ delete, shapeLineColor, shapeFillColor),
+ onLoadActive=(
+ close, create, createMode, editMode),
+ onShapesPresent=(saveAs, hideAll, showAll))
+
+ self.menus = struct(
+ file=self.menu('&File'),
+ edit=self.menu('&Edit'),
+ view=self.menu('&View'),
+ help=self.menu('&Help'),
+ recentFiles=QMenu('Open &Recent'),
+ labelList=labelMenu)
+
+ # Auto saving : Enable auto saving if pressing next
+ 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(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 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))
+ addActions(self.menus.help, (help, showInfo))
+ addActions(self.menus.view, (
+ self.autoSaving,
+ self.singleClassMode,
+ self.displayLabelOption,
+ labels, advancedMode, None,
+ hideAll, showAll, None,
+ zoomIn, zoomOut, zoomOrg, None,
+ fitWindow, fitWidth))
+
+ self.menus.file.aboutToShow.connect(self.updateFileMenu)
+
+ # Custom context menu for the canvas widget:
+ addActions(self.canvas.menus[0], self.actions.beginnerContext)
+ addActions(self.canvas.menus[1], (
+ action('&Copy here', self.copyShape),
+ action('&Move here', self.moveShape)))
+
+ self.tools = self.toolbar('Tools')
+ self.actions.beginner = (
+ open, opendir, changeSavedir, openNextImg, openPrevImg, verify, save, save_format, None, create, copy, delete, None,
+ zoomIn, zoom, zoomOut, fitWindow, fitWidth)
+
+ self.actions.advanced = (
+ open, opendir, changeSavedir, openNextImg, openPrevImg, save, save_format, None,
+ createMode, editMode, None,
+ hideAll, showAll)
+
+ self.statusBar().showMessage('%s started.' % __appname__)
+ self.statusBar().show()
+
+ # Application state.
+ self.image = QImage()
+ self.filePath = ustr(defaultFilename)
+ self.recentFiles = []
+ self.maxRecent = 7
+ self.lineColor = None
+ self.fillColor = None
+ self.zoom_level = 100
+ self.fit_window = False
+ # Add Chris
+ self.difficult = False
+
+ ## Fix the compatible issue for qt4 and qt5. Convert the QStringList to python list
+ if settings.get(SETTING_RECENT_FILES):
+ if have_qstring():
+ recentFileQStringList = settings.get(SETTING_RECENT_FILES)
+ self.recentFiles = [ustr(i) for i in recentFileQStringList]
+ else:
+ self.recentFiles = recentFileQStringList = settings.get(SETTING_RECENT_FILES)
+
+ size = settings.get(SETTING_WIN_SIZE, QSize(600, 500))
+ position = QPoint(0, 0)
+ saved_position = settings.get(SETTING_WIN_POSE, position)
+ # Fix the multiple monitors issue
+ for i in range(QApplication.desktop().screenCount()):
+ if QApplication.desktop().availableGeometry(i).contains(saved_position):
+ position = saved_position
+ break
+ self.resize(size)
+ self.move(position)
+ saveDir = ustr(settings.get(SETTING_SAVE_DIR, None))
+ self.lastOpenDir = ustr(settings.get(SETTING_LAST_OPEN_DIR, None))
+ if self.defaultSaveDir is None and saveDir is not None and os.path.exists(saveDir):
+ self.defaultSaveDir = saveDir
+ self.statusBar().showMessage('%s started. Annotation will be saved to %s' %
+ (__appname__, self.defaultSaveDir))
+ self.statusBar().show()
+
+ self.restoreState(settings.get(SETTING_WIN_STATE, QByteArray()))
+ Shape.line_color = self.lineColor = QColor(settings.get(SETTING_LINE_COLOR, DEFAULT_LINE_COLOR))
+ Shape.fill_color = self.fillColor = QColor(settings.get(SETTING_FILL_COLOR, DEFAULT_FILL_COLOR))
+ self.canvas.setDrawingColor(self.lineColor)
+ # Add chris
+ Shape.difficult = self.difficult
+
+ def xbool(x):
+ if isinstance(x, QVariant):
+ return x.toBool()
+ return bool(x)
+
+ if xbool(settings.get(SETTING_ADVANCE_MODE, False)):
+ self.actions.advancedMode.setChecked(True)
+ self.toggleAdvancedMode()
+
+ # Populate the File menu dynamically.
+ self.updateFileMenu()
+
+ # Since loading the file may take some time, make sure it runs in the background.
+ if self.filePath and os.path.isdir(self.filePath):
+ self.queueEvent(partial(self.importDirImages, self.filePath or ""))
+ elif self.filePath:
+ self.queueEvent(partial(self.loadFile, self.filePath or ""))
+
+ # Callbacks:
+ self.zoomWidget.valueChanged.connect(self.paintCanvas)
+
+ self.populateModeActions()
+
+ # Display cursor coordinates at the right of status bar
+ self.labelCoordinates = QLabel('')
+ self.statusBar().addPermanentWidget(self.labelCoordinates)
+
+ # Open Dir if deafult file
+ if self.filePath and os.path.isdir(self.filePath):
+ self.openDirDialog(dirpath=self.filePath, silent=True)
+
+ def keyReleaseEvent(self, event):
+ if event.key() == Qt.Key_Control:
+ self.canvas.setDrawingShapeToSquare(False)
+
+ def keyPressEvent(self, event):
+ if event.key() == Qt.Key_Control:
+ # Draw rectangle if Ctrl is pressed
+ self.canvas.setDrawingShapeToSquare(True)
+
+ ## Support Functions ##
+ def set_format(self, save_format):
+ if save_format == FORMAT_PASCALVOC:
+ self.actions.save_format.setText(FORMAT_PASCALVOC)
+ self.actions.save_format.setIcon(newIcon("format_voc"))
+ self.usingPascalVocFormat = True
+ self.usingYoloFormat = False
+ LabelFile.suffix = XML_EXT
+
+ elif save_format == FORMAT_YOLO:
+ self.actions.save_format.setText(FORMAT_YOLO)
+ self.actions.save_format.setIcon(newIcon("format_yolo"))
+ self.usingPascalVocFormat = False
+ self.usingYoloFormat = True
+ LabelFile.suffix = TXT_EXT
+
+ def change_format(self):
+ if self.usingPascalVocFormat: self.set_format(FORMAT_YOLO)
+ elif self.usingYoloFormat: self.set_format(FORMAT_PASCALVOC)
+
+ def noShapes(self):
+ return not self.itemsToShapes
+
+ def toggleAdvancedMode(self, value=True):
+ self._beginner = not value
+ self.canvas.setEditing(True)
+ self.populateModeActions()
+ self.editButton.setVisible(not value)
+ if value:
+ self.actions.createMode.setEnabled(True)
+ self.actions.editMode.setEnabled(False)
+ self.dock.setFeatures(self.dock.features() | self.dockFeatures)
+ else:
+ self.dock.setFeatures(self.dock.features() ^ self.dockFeatures)
+
+ def populateModeActions(self):
+ if self.beginner():
+ tool, menu = self.actions.beginner, self.actions.beginnerContext
+ else:
+ tool, menu = self.actions.advanced, self.actions.advancedContext
+ self.tools.clear()
+ addActions(self.tools, tool)
+ self.canvas.menus[0].clear()
+ addActions(self.canvas.menus[0], menu)
+ self.menus.edit.clear()
+ actions = (self.actions.create,) if self.beginner()\
+ else (self.actions.createMode, self.actions.editMode)
+ addActions(self.menus.edit, actions + self.actions.editMenu)
+
+ def setBeginner(self):
+ self.tools.clear()
+ addActions(self.tools, self.actions.beginner)
+
+ def setAdvanced(self):
+ self.tools.clear()
+ addActions(self.tools, self.actions.advanced)
+
+ def setDirty(self):
+ self.dirty = True
+ self.actions.save.setEnabled(True)
+
+ def setClean(self):
+ self.dirty = False
+ self.actions.save.setEnabled(False)
+ self.actions.create.setEnabled(True)
+
+ def toggleActions(self, value=True):
+ """Enable/Disable widgets which depend on an opened image."""
+ for z in self.actions.zoomActions:
+ z.setEnabled(value)
+ for action in self.actions.onLoadActive:
+ action.setEnabled(value)
+
+ def queueEvent(self, function):
+ QTimer.singleShot(0, function)
+
+ def status(self, message, delay=5000):
+ self.statusBar().showMessage(message, delay)
+
+ def resetState(self):
+ self.itemsToShapes.clear()
+ self.shapesToItems.clear()
+ self.labelList.clear()
+ self.filePath = None
+ self.imageData = None
+ self.labelFile = None
+ self.canvas.resetState()
+ self.labelCoordinates.clear()
+
+ def currentItem(self):
+ items = self.labelList.selectedItems()
+ if items:
+ return items[0]
+ return None
+
+ def addRecentFile(self, filePath):
+ if filePath in self.recentFiles:
+ self.recentFiles.remove(filePath)
+ elif len(self.recentFiles) >= self.maxRecent:
+ self.recentFiles.pop()
+ self.recentFiles.insert(0, filePath)
+
+ def beginner(self):
+ return self._beginner
+
+ def advanced(self):
+ return not self.beginner()
+
+ def getAvailableScreencastViewer(self):
+ osName = platform.system()
+
+ if osName == 'Windows':
+ return ['C:\\Program Files\\Internet Explorer\\iexplore.exe']
+ elif osName == 'Linux':
+ return ['xdg-open']
+ elif osName == 'Darwin':
+ return ['open']
+
+ ## Callbacks ##
+ def showTutorialDialog(self):
+ subprocess.Popen(self.screencastViewer + [self.screencast])
+
+ def showInfoDialog(self):
+ msg = u'Name:{0} \nApp Version:{1} \n{2} '.format(__appname__, __version__, sys.version_info)
+ QMessageBox.information(self, u'Information', msg)
+
+ def createShape(self):
+ assert self.beginner()
+ self.canvas.setEditing(False)
+ self.actions.create.setEnabled(False)
+
+ def toggleDrawingSensitive(self, drawing=True):
+ """In the middle of drawing, toggling between modes should be disabled."""
+ self.actions.editMode.setEnabled(not drawing)
+ if not drawing and self.beginner():
+ # Cancel creation.
+ print('Cancel creation.')
+ self.canvas.setEditing(True)
+ self.canvas.restoreCursor()
+ self.actions.create.setEnabled(True)
+
+ def toggleDrawMode(self, edit=True):
+ self.canvas.setEditing(edit)
+ self.actions.createMode.setEnabled(edit)
+ self.actions.editMode.setEnabled(not edit)
+
+ def setCreateMode(self):
+ assert self.advanced()
+ self.toggleDrawMode(False)
+
+ def setEditMode(self):
+ assert self.advanced()
+ self.toggleDrawMode(True)
+ self.labelSelectionChanged()
+
+ def updateFileMenu(self):
+ currFilePath = self.filePath
+
+ def exists(filename):
+ return os.path.exists(filename)
+ menu = self.menus.recentFiles
+ menu.clear()
+ files = [f for f in self.recentFiles if f !=
+ currFilePath and exists(f)]
+ for i, f in enumerate(files):
+ icon = newIcon('labels')
+ action = QAction(
+ icon, '&%d %s' % (i + 1, QFileInfo(f).fileName()), self)
+ action.triggered.connect(partial(self.loadRecent, f))
+ menu.addAction(action)
+
+ def popLabelListMenu(self, point):
+ self.menus.labelList.exec_(self.labelList.mapToGlobal(point))
+
+ def editLabel(self):
+ if not self.canvas.editing():
+ return
+ item = self.currentItem()
+ if not item:
+ return
+ text = self.labelDialog.popUp(item.text())
+ if text is not None:
+ item.setText(text)
+ item.setBackground(generateColorByText(text))
+ self.setDirty()
+
+ # Tzutalin 20160906 : Add file list and dock to move faster
+ def fileitemDoubleClicked(self, item=None):
+ currIndex = self.mImgList.index(ustr(item.text()))
+ if currIndex < len(self.mImgList):
+ filename = self.mImgList[currIndex]
+ if filename:
+ self.loadFile(filename)
+
+ # Add chris
+ def btnstate(self, item= None):
+ """ Function to handle difficult examples
+ Update on each object """
+ if not self.canvas.editing():
+ return
+
+ item = self.currentItem()
+ if not item: # If not selected Item, take the first one
+ item = self.labelList.item(self.labelList.count()-1)
+
+ difficult = self.diffcButton.isChecked()
+
+ try:
+ shape = self.itemsToShapes[item]
+ except:
+ pass
+ # Checked and Update
+ try:
+ if difficult != shape.difficult:
+ shape.difficult = difficult
+ self.setDirty()
+ else: # User probably changed item visibility
+ self.canvas.setShapeVisible(shape, item.checkState() == Qt.Checked)
+ except:
+ pass
+
+ # React to canvas signals.
+ def shapeSelectionChanged(self, selected=False):
+ if self._noSelectionSlot:
+ self._noSelectionSlot = False
+ else:
+ shape = self.canvas.selectedShape
+ if shape:
+ self.shapesToItems[shape].setSelected(True)
+ else:
+ self.labelList.clearSelection()
+ self.actions.delete.setEnabled(selected)
+ self.actions.copy.setEnabled(selected)
+ self.actions.edit.setEnabled(selected)
+ self.actions.shapeLineColor.setEnabled(selected)
+ self.actions.shapeFillColor.setEnabled(selected)
+
+ def addLabel(self, shape):
+ shape.paintLabel = self.displayLabelOption.isChecked()
+ item = HashableQListWidgetItem(shape.label)
+ item.setFlags(item.flags() | Qt.ItemIsUserCheckable)
+ item.setCheckState(Qt.Checked)
+ item.setBackground(generateColorByText(shape.label))
+ self.itemsToShapes[item] = shape
+ self.shapesToItems[shape] = item
+ self.labelList.addItem(item)
+ for action in self.actions.onShapesPresent:
+ action.setEnabled(True)
+
+ def remLabel(self, shape):
+ if shape is None:
+ # print('rm empty label')
+ return
+ item = self.shapesToItems[shape]
+ self.labelList.takeItem(self.labelList.row(item))
+ del self.shapesToItems[shape]
+ del self.itemsToShapes[item]
+
+ def loadLabels(self, shapes):
+ s = []
+ for label, points, line_color, fill_color, difficult in shapes:
+ shape = Shape(label=label)
+ for x, y in points:
+
+ # Ensure the labels are within the bounds of the image. If not, fix them.
+ x, y, snapped = self.canvas.snapPointToCanvas(x, y)
+ if snapped:
+ self.setDirty()
+
+ shape.addPoint(QPointF(x, y))
+ shape.difficult = difficult
+ shape.close()
+ s.append(shape)
+
+ if line_color:
+ shape.line_color = QColor(*line_color)
+ else:
+ shape.line_color = generateColorByText(label)
+
+ if fill_color:
+ shape.fill_color = QColor(*fill_color)
+ else:
+ shape.fill_color = generateColorByText(label)
+
+ self.addLabel(shape)
+
+ self.canvas.loadShapes(s)
+
+ def saveLabels(self, annotationFilePath):
+ annotationFilePath = ustr(annotationFilePath)
+ if self.labelFile is None:
+ self.labelFile = LabelFile()
+ self.labelFile.verified = self.canvas.verified
+
+ def format_shape(s):
+ return dict(label=s.label,
+ line_color=s.line_color.getRgb(),
+ fill_color=s.fill_color.getRgb(),
+ points=[(p.x(), p.y()) for p in s.points],
+ # add chris
+ difficult = s.difficult)
+
+ shapes = [format_shape(shape) for shape in self.canvas.shapes]
+ # Can add differrent annotation formats here
+ try:
+ if self.usingPascalVocFormat is True:
+ if annotationFilePath[-4:].lower() != ".xml":
+ annotationFilePath += XML_EXT
+ self.labelFile.savePascalVocFormat(annotationFilePath, shapes, self.filePath, self.imageData,
+ self.lineColor.getRgb(), self.fillColor.getRgb())
+ elif self.usingYoloFormat is True:
+ if annotationFilePath[-4:].lower() != ".txt":
+ annotationFilePath += TXT_EXT
+ self.labelFile.saveYoloFormat(annotationFilePath, shapes, self.filePath, self.imageData, self.labelHist,
+ self.lineColor.getRgb(), self.fillColor.getRgb())
+ else:
+ self.labelFile.save(annotationFilePath, shapes, self.filePath, self.imageData,
+ self.lineColor.getRgb(), self.fillColor.getRgb())
+ print('Image:{0} -> Annotation:{1}'.format(self.filePath, annotationFilePath))
+ return True
+ except LabelFileError as e:
+ self.errorMessage(u'Error saving label data', u'%s' % e)
+ return False
+
+ def copySelectedShape(self):
+ self.addLabel(self.canvas.copySelectedShape())
+ # fix copy and delete
+ self.shapeSelectionChanged(True)
+
+ def labelSelectionChanged(self):
+ item = self.currentItem()
+ if item and self.canvas.editing():
+ self._noSelectionSlot = True
+ self.canvas.selectShape(self.itemsToShapes[item])
+ shape = self.itemsToShapes[item]
+ # Add Chris
+ self.diffcButton.setChecked(shape.difficult)
+
+ def labelItemChanged(self, item):
+ shape = self.itemsToShapes[item]
+ label = item.text()
+ if label != shape.label:
+ shape.label = item.text()
+ shape.line_color = generateColorByText(shape.label)
+ self.setDirty()
+ else: # User probably changed item visibility
+ self.canvas.setShapeVisible(shape, item.checkState() == Qt.Checked)
+
+ # Callback functions:
+ def newShape(self):
+ """Pop-up and give focus to the label editor.
+
+ position MUST be in global coordinates.
+ """
+ if not self.useDefaultLabelCheckbox.isChecked() or not self.defaultLabelTextLine.text():
+ if len(self.labelHist) > 0:
+ self.labelDialog = LabelDialog(
+ parent=self, listItem=self.labelHist)
+
+ # Sync single class mode from PR#106
+ if self.singleClassMode.isChecked() and self.lastLabel:
+ text = self.lastLabel
+ else:
+ text = self.labelDialog.popUp(text=self.prevLabelText)
+ self.lastLabel = text
+ else:
+ text = self.defaultLabelTextLine.text()
+
+ # Add Chris
+ self.diffcButton.setChecked(False)
+ if text is not None:
+ self.prevLabelText = text
+ generate_color = generateColorByText(text)
+ shape = self.canvas.setLastLabel(text, generate_color, generate_color)
+ self.addLabel(shape)
+ if self.beginner(): # Switch to edit mode.
+ self.canvas.setEditing(True)
+ self.actions.create.setEnabled(True)
+ else:
+ self.actions.editMode.setEnabled(True)
+ self.setDirty()
+
+ if text not in self.labelHist:
+ self.labelHist.append(text)
+ else:
+ # self.canvas.undoLastLine()
+ self.canvas.resetAllLines()
+
+ def scrollRequest(self, delta, orientation):
+ units = - delta / (8 * 15)
+ bar = self.scrollBars[orientation]
+ bar.setValue(bar.value() + bar.singleStep() * units)
+
+ def setZoom(self, value):
+ self.actions.fitWidth.setChecked(False)
+ self.actions.fitWindow.setChecked(False)
+ self.zoomMode = self.MANUAL_ZOOM
+ self.zoomWidget.setValue(value)
+
+ def addZoom(self, increment=10):
+ self.setZoom(self.zoomWidget.value() + increment)
+
+ def zoomRequest(self, delta):
+ # get the current scrollbar positions
+ # calculate the percentages ~ coordinates
+ h_bar = self.scrollBars[Qt.Horizontal]
+ v_bar = self.scrollBars[Qt.Vertical]
+
+ # get the current maximum, to know the difference after zooming
+ h_bar_max = h_bar.maximum()
+ v_bar_max = v_bar.maximum()
+
+ # get the cursor position and canvas size
+ # calculate the desired movement from 0 to 1
+ # where 0 = move left
+ # 1 = move right
+ # up and down analogous
+ cursor = QCursor()
+ pos = cursor.pos()
+ relative_pos = QWidget.mapFromGlobal(self, pos)
+
+ cursor_x = relative_pos.x()
+ cursor_y = relative_pos.y()
+
+ w = self.scrollArea.width()
+ h = self.scrollArea.height()
+
+ # the scaling from 0 to 1 has some padding
+ # you don't have to hit the very leftmost pixel for a maximum-left movement
+ margin = 0.1
+ move_x = (cursor_x - margin * w) / (w - 2 * margin * w)
+ move_y = (cursor_y - margin * h) / (h - 2 * margin * h)
+
+ # clamp the values from 0 to 1
+ move_x = min(max(move_x, 0), 1)
+ move_y = min(max(move_y, 0), 1)
+
+ # zoom in
+ units = delta / (8 * 15)
+ scale = 10
+ self.addZoom(scale * units)
+
+ # get the difference in scrollbar values
+ # this is how far we can move
+ d_h_bar_max = h_bar.maximum() - h_bar_max
+ d_v_bar_max = v_bar.maximum() - v_bar_max
+
+ # get the new scrollbar values
+ new_h_bar_value = h_bar.value() + move_x * d_h_bar_max
+ new_v_bar_value = v_bar.value() + move_y * d_v_bar_max
+
+ h_bar.setValue(new_h_bar_value)
+ v_bar.setValue(new_v_bar_value)
+
+ def setFitWindow(self, value=True):
+ if value:
+ self.actions.fitWidth.setChecked(False)
+ self.zoomMode = self.FIT_WINDOW if value else self.MANUAL_ZOOM
+ self.adjustScale()
+
+ def setFitWidth(self, value=True):
+ if value:
+ self.actions.fitWindow.setChecked(False)
+ self.zoomMode = self.FIT_WIDTH if value else self.MANUAL_ZOOM
+ self.adjustScale()
+
+ def togglePolygons(self, value):
+ for item, shape in self.itemsToShapes.items():
+ item.setCheckState(Qt.Checked if value else Qt.Unchecked)
+
+ def loadFile(self, filePath=None):
+ """Load the specified file, or the last opened file if None."""
+ self.resetState()
+ self.canvas.setEnabled(False)
+ if filePath is None:
+ filePath = self.settings.get(SETTING_FILENAME)
+
+ # Make sure that filePath is a regular python string, rather than QString
+ filePath = ustr(filePath)
+
+ # Fix bug: An index error after select a directory when open a new file.
+ unicodeFilePath = ustr(filePath)
+ unicodeFilePath = os.path.abspath(unicodeFilePath)
+ # Tzutalin 20160906 : Add file list and dock to move faster
+ # Highlight the file item
+ if unicodeFilePath and self.fileListWidget.count() > 0:
+ if unicodeFilePath in self.mImgList:
+ index = self.mImgList.index(unicodeFilePath)
+ fileWidgetItem = self.fileListWidget.item(index)
+ fileWidgetItem.setSelected(True)
+ else:
+ self.fileListWidget.clear()
+ self.mImgList.clear()
+
+ if unicodeFilePath and os.path.exists(unicodeFilePath):
+ if LabelFile.isLabelFile(unicodeFilePath):
+ try:
+ self.labelFile = LabelFile(unicodeFilePath)
+ except LabelFileError as e:
+ self.errorMessage(u'Error opening file',
+ (u"%s
"
+ u"Make sure %s is a valid label file.")
+ % (e, unicodeFilePath))
+ self.status("Error reading %s" % unicodeFilePath)
+ return False
+ self.imageData = self.labelFile.imageData
+ self.lineColor = QColor(*self.labelFile.lineColor)
+ self.fillColor = QColor(*self.labelFile.fillColor)
+ self.canvas.verified = self.labelFile.verified
+ else:
+ # Load image:
+ # read data first and store for saving into label file.
+ self.imageData = read(unicodeFilePath, None)
+ self.labelFile = None
+ self.canvas.verified = False
+
+ image = QImage.fromData(self.imageData)
+ if image.isNull():
+ self.errorMessage(u'Error opening file',
+ u"
Make sure %s is a valid image file." % unicodeFilePath)
+ self.status("Error reading %s" % unicodeFilePath)
+ return False
+ self.status("Loaded %s" % os.path.basename(unicodeFilePath))
+ self.image = image
+ self.filePath = unicodeFilePath
+ self.canvas.loadPixmap(QPixmap.fromImage(image))
+ if self.labelFile:
+ self.loadLabels(self.labelFile.shapes)
+ self.setClean()
+ self.canvas.setEnabled(True)
+ self.adjustScale(initial=True)
+ self.paintCanvas()
+ self.addRecentFile(self.filePath)
+ self.toggleActions(True)
+
+ # Label xml file and show bound box according to its filename
+ # if self.usingPascalVocFormat is True:
+ if self.defaultSaveDir is not None:
+ basename = os.path.basename(
+ os.path.splitext(self.filePath)[0])
+ xmlPath = os.path.join(self.defaultSaveDir, basename + XML_EXT)
+ txtPath = os.path.join(self.defaultSaveDir, basename + TXT_EXT)
+
+ """Annotation file priority:
+ PascalXML > YOLO
+ """
+ if os.path.isfile(xmlPath):
+ self.loadPascalXMLByFilename(xmlPath)
+ elif os.path.isfile(txtPath):
+ self.loadYOLOTXTByFilename(txtPath)
+ else:
+ xmlPath = os.path.splitext(filePath)[0] + XML_EXT
+ txtPath = os.path.splitext(filePath)[0] + TXT_EXT
+ if os.path.isfile(xmlPath):
+ self.loadPascalXMLByFilename(xmlPath)
+ elif os.path.isfile(txtPath):
+ self.loadYOLOTXTByFilename(txtPath)
+
+ self.setWindowTitle(__appname__ + ' ' + filePath)
+
+ # Default : select last item if there is at least one item
+ if self.labelList.count():
+ self.labelList.setCurrentItem(self.labelList.item(self.labelList.count()-1))
+ self.labelList.item(self.labelList.count()-1).setSelected(True)
+
+ self.canvas.setFocus(True)
+ return True
+ return False
+
+ def resizeEvent(self, event):
+ if self.canvas and not self.image.isNull()\
+ and self.zoomMode != self.MANUAL_ZOOM:
+ self.adjustScale()
+ super(MainWindow, self).resizeEvent(event)
+
+ def paintCanvas(self):
+ assert not self.image.isNull(), "cannot paint null image"
+ self.canvas.scale = 0.01 * self.zoomWidget.value()
+ self.canvas.adjustSize()
+ self.canvas.update()
+
+ def adjustScale(self, initial=False):
+ value = self.scalers[self.FIT_WINDOW if initial else self.zoomMode]()
+ self.zoomWidget.setValue(int(100 * value))
+
+ def scaleFitWindow(self):
+ """Figure out the size of the pixmap in order to fit the main widget."""
+ e = 2.0 # So that no scrollbars are generated.
+ w1 = self.centralWidget().width() - e
+ h1 = self.centralWidget().height() - e
+ a1 = w1 / h1
+ # Calculate a new scale value based on the pixmap's aspect ratio.
+ w2 = self.canvas.pixmap.width() - 0.0
+ h2 = self.canvas.pixmap.height() - 0.0
+ a2 = w2 / h2
+ return w1 / w2 if a2 >= a1 else h1 / h2
+
+ def scaleFitWidth(self):
+ # The epsilon does not seem to work too well here.
+ w = self.centralWidget().width() - 2.0
+ return w / self.canvas.pixmap.width()
+
+ def closeEvent(self, event):
+ if not self.mayContinue():
+ event.ignore()
+ settings = self.settings
+ # If it loads images from dir, don't load it at the begining
+ if self.dirname is None:
+ settings[SETTING_FILENAME] = self.filePath if self.filePath else ''
+ else:
+ settings[SETTING_FILENAME] = ''
+
+ settings[SETTING_WIN_SIZE] = self.size()
+ settings[SETTING_WIN_POSE] = self.pos()
+ settings[SETTING_WIN_STATE] = self.saveState()
+ settings[SETTING_LINE_COLOR] = self.lineColor
+ settings[SETTING_FILL_COLOR] = self.fillColor
+ settings[SETTING_RECENT_FILES] = self.recentFiles
+ settings[SETTING_ADVANCE_MODE] = not self._beginner
+ if self.defaultSaveDir and os.path.exists(self.defaultSaveDir):
+ settings[SETTING_SAVE_DIR] = ustr(self.defaultSaveDir)
+ else:
+ 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_AUTO_SAVE] = self.autoSaving.isChecked()
+ settings[SETTING_SINGLE_CLASS] = self.singleClassMode.isChecked()
+ settings[SETTING_PAINT_LABEL] = self.displayLabelOption.isChecked()
+ settings[SETTING_DRAW_SQUARE] = self.drawSquaresOption.isChecked()
+ settings.save()
+
+ def loadRecent(self, filename):
+ if self.mayContinue():
+ self.loadFile(filename)
+
+ def scanAllImages(self, folderPath):
+ extensions = ['.%s' % fmt.data().decode("ascii").lower() for fmt in QImageReader.supportedImageFormats()]
+ images = []
+
+ for root, dirs, files in os.walk(folderPath):
+ for file in files:
+ if file.lower().endswith(tuple(extensions)):
+ relativePath = os.path.join(root, file)
+ path = ustr(os.path.abspath(relativePath))
+ images.append(path)
+ natural_sort(images, key=lambda x: x.lower())
+ return images
+
+ def changeSavedirDialog(self, _value=False):
+ if self.defaultSaveDir is not None:
+ path = ustr(self.defaultSaveDir)
+ else:
+ path = '.'
+
+ dirpath = ustr(QFileDialog.getExistingDirectory(self,
+ '%s - Save annotations to the directory' % __appname__, path, QFileDialog.ShowDirsOnly
+ | QFileDialog.DontResolveSymlinks))
+
+ if dirpath is not None and len(dirpath) > 1:
+ self.defaultSaveDir = dirpath
+
+ self.statusBar().showMessage('%s . Annotation will be saved to %s' %
+ ('Change saved folder', self.defaultSaveDir))
+ self.statusBar().show()
+
+ def openAnnotationDialog(self, _value=False):
+ if self.filePath is None:
+ self.statusBar().showMessage('Please select image first')
+ self.statusBar().show()
+ return
+
+ path = os.path.dirname(ustr(self.filePath))\
+ if self.filePath else '.'
+ if self.usingPascalVocFormat:
+ filters = "Open Annotation XML file (%s)" % ' '.join(['*.xml'])
+ filename = ustr(QFileDialog.getOpenFileName(self,'%s - Choose a xml file' % __appname__, path, filters))
+ if filename:
+ if isinstance(filename, (tuple, list)):
+ filename = filename[0]
+ self.loadPascalXMLByFilename(filename)
+
+ def openDirDialog(self, _value=False, dirpath=None, silent=False):
+ if not self.mayContinue():
+ return
+
+ defaultOpenDirPath = dirpath if dirpath else '.'
+ if self.lastOpenDir and os.path.exists(self.lastOpenDir):
+ defaultOpenDirPath = self.lastOpenDir
+ else:
+ defaultOpenDirPath = os.path.dirname(self.filePath) if self.filePath else '.'
+ if silent!=True :
+ targetDirPath = ustr(QFileDialog.getExistingDirectory(self,
+ '%s - Open Directory' % __appname__, defaultOpenDirPath,
+ QFileDialog.ShowDirsOnly | QFileDialog.DontResolveSymlinks))
+ else:
+ targetDirPath = ustr(defaultOpenDirPath)
+
+ self.importDirImages(targetDirPath)
+
+ def importDirImages(self, dirpath):
+ if not self.mayContinue() or not dirpath:
+ return
+
+ self.lastOpenDir = dirpath
+ self.dirname = dirpath
+ self.filePath = None
+ self.fileListWidget.clear()
+ self.mImgList = self.scanAllImages(dirpath)
+ self.openNextImg()
+ for imgPath in self.mImgList:
+ item = QListWidgetItem(imgPath)
+ self.fileListWidget.addItem(item)
+
+ def verifyImg(self, _value=False):
+ # Proceding next image without dialog if having any label
+ if self.filePath is not None:
+ try:
+ self.labelFile.toggleVerify()
+ except AttributeError:
+ # If the labelling file does not exist yet, create if and
+ # re-save it with the verified attribute.
+ self.saveFile()
+ if self.labelFile != None:
+ self.labelFile.toggleVerify()
+ else:
+ return
+
+ self.canvas.verified = self.labelFile.verified
+ self.paintCanvas()
+ self.saveFile()
+
+ def openPrevImg(self, _value=False):
+ # Proceding prev image without dialog if having any label
+ if self.autoSaving.isChecked():
+ if self.defaultSaveDir is not None:
+ if self.dirty is True:
+ self.saveFile()
+ else:
+ self.changeSavedirDialog()
+ return
+
+ if not self.mayContinue():
+ return
+
+ if len(self.mImgList) <= 0:
+ return
+
+ if self.filePath is None:
+ return
+
+ currIndex = self.mImgList.index(self.filePath)
+ if currIndex - 1 >= 0:
+ filename = self.mImgList[currIndex - 1]
+ if filename:
+ self.loadFile(filename)
+
+ def openNextImg(self, _value=False):
+ # Proceding prev image without dialog if having any label
+ if self.autoSaving.isChecked():
+ if self.defaultSaveDir is not None:
+ if self.dirty is True:
+ self.saveFile()
+ else:
+ self.changeSavedirDialog()
+ return
+
+ if not self.mayContinue():
+ return
+
+ if len(self.mImgList) <= 0:
+ return
+
+ filename = None
+ if self.filePath is None:
+ filename = self.mImgList[0]
+ else:
+ currIndex = self.mImgList.index(self.filePath)
+ if currIndex + 1 < len(self.mImgList):
+ filename = self.mImgList[currIndex + 1]
+
+ if filename:
+ self.loadFile(filename)
+
+ def openFile(self, _value=False):
+ if not self.mayContinue():
+ return
+ path = os.path.dirname(ustr(self.filePath)) if self.filePath else '.'
+ formats = ['*.%s' % fmt.data().decode("ascii").lower() for fmt in QImageReader.supportedImageFormats()]
+ filters = "Image & Label files (%s)" % ' '.join(formats + ['*%s' % LabelFile.suffix])
+ filename = QFileDialog.getOpenFileName(self, '%s - Choose Image or Label file' % __appname__, path, filters)
+ if filename:
+ if isinstance(filename, (tuple, list)):
+ filename = filename[0]
+ self.loadFile(filename)
+
+ def saveFile(self, _value=False):
+ if self.defaultSaveDir is not None and len(ustr(self.defaultSaveDir)):
+ if self.filePath:
+ imgFileName = os.path.basename(self.filePath)
+ savedFileName = os.path.splitext(imgFileName)[0]
+ savedPath = os.path.join(ustr(self.defaultSaveDir), savedFileName)
+ self._saveFile(savedPath)
+ else:
+ imgFileDir = os.path.dirname(self.filePath)
+ imgFileName = os.path.basename(self.filePath)
+ savedFileName = os.path.splitext(imgFileName)[0]
+ savedPath = os.path.join(imgFileDir, savedFileName)
+ self._saveFile(savedPath if self.labelFile
+ else self.saveFileDialog(removeExt=False))
+
+ def saveFileAs(self, _value=False):
+ assert not self.image.isNull(), "cannot save empty image"
+ self._saveFile(self.saveFileDialog())
+
+ def saveFileDialog(self, removeExt=True):
+ caption = '%s - Choose File' % __appname__
+ filters = 'File (*%s)' % LabelFile.suffix
+ openDialogPath = self.currentPath()
+ dlg = QFileDialog(self, caption, openDialogPath, filters)
+ dlg.setDefaultSuffix(LabelFile.suffix[1:])
+ dlg.setAcceptMode(QFileDialog.AcceptSave)
+ filenameWithoutExtension = os.path.splitext(self.filePath)[0]
+ dlg.selectFile(filenameWithoutExtension)
+ dlg.setOption(QFileDialog.DontUseNativeDialog, False)
+ if dlg.exec_():
+ fullFilePath = ustr(dlg.selectedFiles()[0])
+ if removeExt:
+ return os.path.splitext(fullFilePath)[0] # Return file path without the extension.
+ else:
+ return fullFilePath
+ return ''
+
+ def _saveFile(self, annotationFilePath):
+ if annotationFilePath and self.saveLabels(annotationFilePath):
+ self.setClean()
+ self.statusBar().showMessage('Saved to %s' % annotationFilePath)
+ self.statusBar().show()
+
+ def closeFile(self, _value=False):
+ if not self.mayContinue():
+ return
+ self.resetState()
+ self.setClean()
+ self.toggleActions(False)
+ self.canvas.setEnabled(False)
+ self.actions.saveAs.setEnabled(False)
+
+ def resetAll(self):
+ self.settings.reset()
+ self.close()
+ proc = QProcess()
+ proc.startDetached(os.path.abspath(__file__))
+
+ def mayContinue(self):
+ return not (self.dirty and not self.discardChangesDialog())
+
+ def discardChangesDialog(self):
+ yes, no = QMessageBox.Yes, QMessageBox.No
+ msg = u'You have unsaved changes, proceed anyway?'
+ return yes == QMessageBox.warning(self, u'Attention', msg, yes | no)
+
+ def errorMessage(self, title, message):
+ return QMessageBox.critical(self, title,
+ '
%s
%s' % (title, message))
+
+ def currentPath(self):
+ return os.path.dirname(self.filePath) if self.filePath else '.'
+
+ def chooseColor1(self):
+ color = self.colorDialog.getColor(self.lineColor, u'Choose line color',
+ default=DEFAULT_LINE_COLOR)
+ if color:
+ self.lineColor = color
+ Shape.line_color = color
+ self.canvas.setDrawingColor(color)
+ self.canvas.update()
+ self.setDirty()
+
+ def deleteSelectedShape(self):
+ self.remLabel(self.canvas.deleteSelected())
+ self.setDirty()
+ if self.noShapes():
+ for action in self.actions.onShapesPresent:
+ action.setEnabled(False)
+
+ def chshapeLineColor(self):
+ color = self.colorDialog.getColor(self.lineColor, u'Choose line color',
+ default=DEFAULT_LINE_COLOR)
+ if color:
+ self.canvas.selectedShape.line_color = color
+ self.canvas.update()
+ self.setDirty()
+
+ def chshapeFillColor(self):
+ color = self.colorDialog.getColor(self.fillColor, u'Choose fill color',
+ default=DEFAULT_FILL_COLOR)
+ if color:
+ self.canvas.selectedShape.fill_color = color
+ self.canvas.update()
+ self.setDirty()
+
+ def copyShape(self):
+ self.canvas.endMove(copy=True)
+ self.addLabel(self.canvas.selectedShape)
+ self.setDirty()
+
+ def moveShape(self):
+ self.canvas.endMove(copy=False)
+ self.setDirty()
+
+ def loadPredefinedClasses(self, predefClassesFile):
+ if os.path.exists(predefClassesFile) is True:
+ with codecs.open(predefClassesFile, 'r', 'utf8') as f:
+ for line in f:
+ line = line.strip()
+ if self.labelHist is None:
+ self.labelHist = [line]
+ else:
+ self.labelHist.append(line)
+
+ def loadPascalXMLByFilename(self, xmlPath):
+ if self.filePath is None:
+ return
+ if os.path.isfile(xmlPath) is False:
+ return
+
+ self.set_format(FORMAT_PASCALVOC)
+
+ tVocParseReader = PascalVocReader(xmlPath)
+ shapes = tVocParseReader.getShapes()
+ self.loadLabels(shapes)
+ self.canvas.verified = tVocParseReader.verified
+
+ def loadYOLOTXTByFilename(self, txtPath):
+ if self.filePath is None:
+ return
+ if os.path.isfile(txtPath) is False:
+ return
+
+ self.set_format(FORMAT_YOLO)
+ tYoloParseReader = YoloReader(txtPath, self.image)
+ shapes = tYoloParseReader.getShapes()
+ print (shapes)
+ self.loadLabels(shapes)
+ self.canvas.verified = tYoloParseReader.verified
+
+ def togglePaintLabelsOption(self):
+ for shape in self.canvas.shapes:
+ shape.paintLabel = self.displayLabelOption.isChecked()
+
+ def toogleDrawSquare(self):
+ self.canvas.setDrawingShapeToSquare(self.drawSquaresOption.isChecked())
+
+def inverted(color):
+ return QColor(*[255 - v for v in color.getRgb()])
+
+
+def read(filename, default=None):
+ try:
+ with open(filename, 'rb') as f:
+ return f.read()
+ except:
+ return default
+
+
+def get_main_app(argv=[]):
+ """
+ Standard boilerplate Qt application code.
+ Do everything but app.exec_() -- so that we can test the application in one thread
+ """
+ app = QApplication(argv)
+ app.setApplicationName(__appname__)
+ app.setWindowIcon(newIcon("app"))
+ # Tzutalin 201705+: Accept extra agruments to change predefined class file
+ # Usage : labelImg.py image predefClassFile saveDir
+ win = MainWindow(argv[1] if len(argv) >= 2 else None,
+ argv[2] if len(argv) >= 3 else os.path.join(
+ os.path.dirname(sys.argv[0]),
+ 'data', 'predefined_classes.txt'),
+ argv[3] if len(argv) >= 4 else None)
+ win.show()
+ return app, win
+
+
+def main():
+ '''construct main app and run it'''
+ app, _win = get_main_app(sys.argv)
+ return app.exec_()
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/labelImg-master/libs/__init__.py b/labelImg-master/libs/__init__.py
new file mode 100644
index 0000000..d4e244b
--- /dev/null
+++ b/labelImg-master/libs/__init__.py
@@ -0,0 +1,2 @@
+__version_info__ = ('1', '8', '2')
+__version__ = '.'.join(__version_info__)
diff --git a/labelImg-master/libs/canvas.py b/labelImg-master/libs/canvas.py
new file mode 100644
index 0000000..211c390
--- /dev/null
+++ b/labelImg-master/libs/canvas.py
@@ -0,0 +1,724 @@
+
+try:
+ from PyQt5.QtGui import *
+ from PyQt5.QtCore import *
+ from PyQt5.QtWidgets import *
+except ImportError:
+ from PyQt4.QtGui import *
+ from PyQt4.QtCore import *
+
+#from PyQt4.QtOpenGL import *
+
+from libs.shape import Shape
+from libs.utils import distance
+
+CURSOR_DEFAULT = Qt.ArrowCursor
+CURSOR_POINT = Qt.PointingHandCursor
+CURSOR_DRAW = Qt.CrossCursor
+CURSOR_MOVE = Qt.ClosedHandCursor
+CURSOR_GRAB = Qt.OpenHandCursor
+
+# class Canvas(QGLWidget):
+
+
+class Canvas(QWidget):
+ zoomRequest = pyqtSignal(int)
+ scrollRequest = pyqtSignal(int, int)
+ newShape = pyqtSignal()
+ selectionChanged = pyqtSignal(bool)
+ shapeMoved = pyqtSignal()
+ drawingPolygon = pyqtSignal(bool)
+
+ CREATE, EDIT = list(range(2))
+
+ epsilon = 11.0
+
+ def __init__(self, *args, **kwargs):
+ super(Canvas, self).__init__(*args, **kwargs)
+ # Initialise local state.
+ self.mode = self.EDIT
+ self.shapes = []
+ self.current = None
+ self.selectedShape = None # save the selected shape here
+ self.selectedShapeCopy = None
+ self.drawingLineColor = QColor(0, 0, 255)
+ self.drawingRectColor = QColor(0, 0, 255)
+ self.line = Shape(line_color=self.drawingLineColor)
+ self.prevPoint = QPointF()
+ self.offsets = QPointF(), QPointF()
+ self.scale = 1.0
+ self.pixmap = QPixmap()
+ self.visible = {}
+ self._hideBackround = False
+ self.hideBackround = False
+ self.hShape = None
+ self.hVertex = None
+ self._painter = QPainter()
+ self._cursor = CURSOR_DEFAULT
+ # Menus:
+ self.menus = (QMenu(), QMenu())
+ # Set widget options.
+ self.setMouseTracking(True)
+ self.setFocusPolicy(Qt.WheelFocus)
+ self.verified = False
+ self.drawSquare = False
+
+ def setDrawingColor(self, qColor):
+ self.drawingLineColor = qColor
+ self.drawingRectColor = qColor
+
+ def enterEvent(self, ev):
+ self.overrideCursor(self._cursor)
+
+ def leaveEvent(self, ev):
+ self.restoreCursor()
+
+ def focusOutEvent(self, ev):
+ self.restoreCursor()
+
+ def isVisible(self, shape):
+ return self.visible.get(shape, True)
+
+ def drawing(self):
+ return self.mode == self.CREATE
+
+ def editing(self):
+ return self.mode == self.EDIT
+
+ def setEditing(self, value=True):
+ self.mode = self.EDIT if value else self.CREATE
+ if not value: # Create
+ self.unHighlight()
+ self.deSelectShape()
+ self.prevPoint = QPointF()
+ self.repaint()
+
+ def unHighlight(self):
+ if self.hShape:
+ self.hShape.highlightClear()
+ self.hVertex = self.hShape = None
+
+ def selectedVertex(self):
+ return self.hVertex is not None
+
+ def mouseMoveEvent(self, ev):
+ """Update line with last point and current coordinates."""
+ pos = self.transformPos(ev.pos())
+
+ # Update coordinates in status bar if image is opened
+ window = self.parent().window()
+ if window.filePath is not None:
+ self.parent().window().labelCoordinates.setText(
+ 'X: %d; Y: %d' % (pos.x(), pos.y()))
+
+ # Polygon drawing.
+ if self.drawing():
+ self.overrideCursor(CURSOR_DRAW)
+ if self.current:
+ color = self.drawingLineColor
+ if self.outOfPixmap(pos):
+ # Don't allow the user to draw outside the pixmap.
+ # Project the point to the pixmap's edges.
+ pos = self.intersectionPoint(self.current[-1], pos)
+ elif len(self.current) > 1 and self.closeEnough(pos, self.current[0]):
+ # Attract line to starting point and colorise to alert the
+ # user:
+ pos = self.current[0]
+ color = self.current.line_color
+ self.overrideCursor(CURSOR_POINT)
+ self.current.highlightVertex(0, Shape.NEAR_VERTEX)
+
+ if self.drawSquare:
+ initPos = self.current[0]
+ minX = initPos.x()
+ minY = initPos.y()
+ min_size = min(abs(pos.x() - minX), abs(pos.y() - minY))
+ directionX = -1 if pos.x() - minX < 0 else 1
+ directionY = -1 if pos.y() - minY < 0 else 1
+ self.line[1] = QPointF(minX + directionX * min_size, minY + directionY * min_size)
+ else:
+ self.line[1] = pos
+
+ self.line.line_color = color
+ self.prevPoint = QPointF()
+ self.current.highlightClear()
+ else:
+ self.prevPoint = pos
+ self.repaint()
+ return
+
+ # Polygon copy moving.
+ if Qt.RightButton & ev.buttons():
+ if self.selectedShapeCopy and self.prevPoint:
+ self.overrideCursor(CURSOR_MOVE)
+ self.boundedMoveShape(self.selectedShapeCopy, pos)
+ self.repaint()
+ elif self.selectedShape:
+ self.selectedShapeCopy = self.selectedShape.copy()
+ self.repaint()
+ return
+
+ # Polygon/Vertex moving.
+ if Qt.LeftButton & ev.buttons():
+ if self.selectedVertex():
+ self.boundedMoveVertex(pos)
+ self.shapeMoved.emit()
+ self.repaint()
+ elif self.selectedShape and self.prevPoint:
+ self.overrideCursor(CURSOR_MOVE)
+ self.boundedMoveShape(self.selectedShape, pos)
+ self.shapeMoved.emit()
+ self.repaint()
+ return
+
+ # Just hovering over the canvas, 2 posibilities:
+ # - Highlight shapes
+ # - Highlight vertex
+ # Update shape/vertex fill and tooltip value accordingly.
+ self.setToolTip("Image")
+ for shape in reversed([s for s in self.shapes if self.isVisible(s)]):
+ # Look for a nearby vertex to highlight. If that fails,
+ # check if we happen to be inside a shape.
+ index = shape.nearestVertex(pos, self.epsilon)
+ if index is not None:
+ if self.selectedVertex():
+ self.hShape.highlightClear()
+ self.hVertex, self.hShape = index, shape
+ shape.highlightVertex(index, shape.MOVE_VERTEX)
+ self.overrideCursor(CURSOR_POINT)
+ self.setToolTip("Click & drag to move point")
+ self.setStatusTip(self.toolTip())
+ self.update()
+ break
+ elif shape.containsPoint(pos):
+ if self.selectedVertex():
+ self.hShape.highlightClear()
+ self.hVertex, self.hShape = None, shape
+ self.setToolTip(
+ "Click & drag to move shape '%s'" % shape.label)
+ self.setStatusTip(self.toolTip())
+ self.overrideCursor(CURSOR_GRAB)
+ self.update()
+ break
+ else: # Nothing found, clear highlights, reset state.
+ if self.hShape:
+ self.hShape.highlightClear()
+ self.update()
+ self.hVertex, self.hShape = None, None
+ self.overrideCursor(CURSOR_DEFAULT)
+
+ def mousePressEvent(self, ev):
+ pos = self.transformPos(ev.pos())
+
+ if ev.button() == Qt.LeftButton:
+ if self.drawing():
+ self.handleDrawing(pos)
+ else:
+ self.selectShapePoint(pos)
+ self.prevPoint = pos
+ self.repaint()
+ elif ev.button() == Qt.RightButton and self.editing():
+ self.selectShapePoint(pos)
+ self.prevPoint = pos
+ self.repaint()
+
+ def mouseReleaseEvent(self, ev):
+ if ev.button() == Qt.RightButton:
+ menu = self.menus[bool(self.selectedShapeCopy)]
+ self.restoreCursor()
+ if not menu.exec_(self.mapToGlobal(ev.pos()))\
+ and self.selectedShapeCopy:
+ # Cancel the move by deleting the shadow copy.
+ self.selectedShapeCopy = None
+ self.repaint()
+ elif ev.button() == Qt.LeftButton and self.selectedShape:
+ if self.selectedVertex():
+ self.overrideCursor(CURSOR_POINT)
+ else:
+ self.overrideCursor(CURSOR_GRAB)
+ elif ev.button() == Qt.LeftButton:
+ pos = self.transformPos(ev.pos())
+ if self.drawing():
+ self.handleDrawing(pos)
+
+ def endMove(self, copy=False):
+ assert self.selectedShape and self.selectedShapeCopy
+ shape = self.selectedShapeCopy
+ #del shape.fill_color
+ #del shape.line_color
+ if copy:
+ self.shapes.append(shape)
+ self.selectedShape.selected = False
+ self.selectedShape = shape
+ self.repaint()
+ else:
+ self.selectedShape.points = [p for p in shape.points]
+ self.selectedShapeCopy = None
+
+ def hideBackroundShapes(self, value):
+ self.hideBackround = value
+ if self.selectedShape:
+ # Only hide other shapes if there is a current selection.
+ # Otherwise the user will not be able to select a shape.
+ self.setHiding(True)
+ self.repaint()
+
+ def handleDrawing(self, pos):
+ if self.current and self.current.reachMaxPoints() is False:
+ initPos = self.current[0]
+ minX = initPos.x()
+ minY = initPos.y()
+ targetPos = self.line[1]
+ maxX = targetPos.x()
+ maxY = targetPos.y()
+ self.current.addPoint(QPointF(maxX, minY))
+ self.current.addPoint(targetPos)
+ self.current.addPoint(QPointF(minX, maxY))
+ self.finalise()
+ elif not self.outOfPixmap(pos):
+ self.current = Shape()
+ self.current.addPoint(pos)
+ self.line.points = [pos, pos]
+ self.setHiding()
+ self.drawingPolygon.emit(True)
+ self.update()
+
+ def setHiding(self, enable=True):
+ self._hideBackround = self.hideBackround if enable else False
+
+ def canCloseShape(self):
+ return self.drawing() and self.current and len(self.current) > 2
+
+ def mouseDoubleClickEvent(self, ev):
+ # We need at least 4 points here, since the mousePress handler
+ # adds an extra one before this handler is called.
+ if self.canCloseShape() and len(self.current) > 3:
+ self.current.popPoint()
+ self.finalise()
+
+ def selectShape(self, shape):
+ self.deSelectShape()
+ shape.selected = True
+ self.selectedShape = shape
+ self.setHiding()
+ self.selectionChanged.emit(True)
+ self.update()
+
+ def selectShapePoint(self, point):
+ """Select the first shape created which contains this point."""
+ self.deSelectShape()
+ if self.selectedVertex(): # A vertex is marked for selection.
+ index, shape = self.hVertex, self.hShape
+ shape.highlightVertex(index, shape.MOVE_VERTEX)
+ self.selectShape(shape)
+ return
+ for shape in reversed(self.shapes):
+ if self.isVisible(shape) and shape.containsPoint(point):
+ self.selectShape(shape)
+ self.calculateOffsets(shape, point)
+ return
+
+ def calculateOffsets(self, shape, point):
+ rect = shape.boundingRect()
+ x1 = rect.x() - point.x()
+ y1 = rect.y() - point.y()
+ x2 = (rect.x() + rect.width()) - point.x()
+ y2 = (rect.y() + rect.height()) - point.y()
+ self.offsets = QPointF(x1, y1), QPointF(x2, y2)
+
+ def snapPointToCanvas(self, x, y):
+ """
+ Moves a point x,y to within the boundaries of the canvas.
+ :return: (x,y,snapped) where snapped is True if x or y were changed, False if not.
+ """
+ if x < 0 or x > self.pixmap.width() or y < 0 or y > self.pixmap.height():
+ x = max(x, 0)
+ y = max(y, 0)
+ x = min(x, self.pixmap.width())
+ y = min(y, self.pixmap.height())
+ return x, y, True
+
+ return x, y, False
+
+ def boundedMoveVertex(self, pos):
+ index, shape = self.hVertex, self.hShape
+ point = shape[index]
+ if self.outOfPixmap(pos):
+ pos = self.intersectionPoint(point, pos)
+
+ if self.drawSquare:
+ opposite_point_index = (index + 2) % 4
+ opposite_point = shape[opposite_point_index]
+
+ min_size = min(abs(pos.x() - opposite_point.x()), abs(pos.y() - opposite_point.y()))
+ directionX = -1 if pos.x() - opposite_point.x() < 0 else 1
+ directionY = -1 if pos.y() - opposite_point.y() < 0 else 1
+ shiftPos = QPointF(opposite_point.x() + directionX * min_size - point.x(),
+ opposite_point.y() + directionY * min_size - point.y())
+ else:
+ shiftPos = pos - point
+
+ shape.moveVertexBy(index, shiftPos)
+
+ lindex = (index + 1) % 4
+ rindex = (index + 3) % 4
+ lshift = None
+ rshift = None
+ if index % 2 == 0:
+ rshift = QPointF(shiftPos.x(), 0)
+ lshift = QPointF(0, shiftPos.y())
+ else:
+ lshift = QPointF(shiftPos.x(), 0)
+ rshift = QPointF(0, shiftPos.y())
+ shape.moveVertexBy(rindex, rshift)
+ shape.moveVertexBy(lindex, lshift)
+
+ def boundedMoveShape(self, shape, pos):
+ if self.outOfPixmap(pos):
+ return False # No need to move
+ o1 = pos + self.offsets[0]
+ if self.outOfPixmap(o1):
+ pos -= QPointF(min(0, o1.x()), min(0, o1.y()))
+ o2 = pos + self.offsets[1]
+ if self.outOfPixmap(o2):
+ pos += QPointF(min(0, self.pixmap.width() - o2.x()),
+ min(0, self.pixmap.height() - o2.y()))
+ # The next line tracks the new position of the cursor
+ # relative to the shape, but also results in making it
+ # a bit "shaky" when nearing the border and allows it to
+ # go outside of the shape's area for some reason. XXX
+ #self.calculateOffsets(self.selectedShape, pos)
+ dp = pos - self.prevPoint
+ if dp:
+ shape.moveBy(dp)
+ self.prevPoint = pos
+ return True
+ return False
+
+ def deSelectShape(self):
+ if self.selectedShape:
+ self.selectedShape.selected = False
+ self.selectedShape = None
+ self.setHiding(False)
+ self.selectionChanged.emit(False)
+ self.update()
+
+ def deleteSelected(self):
+ if self.selectedShape:
+ shape = self.selectedShape
+ self.shapes.remove(self.selectedShape)
+ self.selectedShape = None
+ self.update()
+ return shape
+
+ def copySelectedShape(self):
+ if self.selectedShape:
+ shape = self.selectedShape.copy()
+ self.deSelectShape()
+ self.shapes.append(shape)
+ shape.selected = True
+ self.selectedShape = shape
+ self.boundedShiftShape(shape)
+ return shape
+
+ def boundedShiftShape(self, shape):
+ # Try to move in one direction, and if it fails in another.
+ # Give up if both fail.
+ point = shape[0]
+ offset = QPointF(2.0, 2.0)
+ self.calculateOffsets(shape, point)
+ self.prevPoint = point
+ if not self.boundedMoveShape(shape, point - offset):
+ self.boundedMoveShape(shape, point + offset)
+
+ def paintEvent(self, event):
+ if not self.pixmap:
+ return super(Canvas, self).paintEvent(event)
+
+ p = self._painter
+ p.begin(self)
+ p.setRenderHint(QPainter.Antialiasing)
+ p.setRenderHint(QPainter.HighQualityAntialiasing)
+ p.setRenderHint(QPainter.SmoothPixmapTransform)
+
+ p.scale(self.scale, self.scale)
+ p.translate(self.offsetToCenter())
+
+ p.drawPixmap(0, 0, self.pixmap)
+ Shape.scale = self.scale
+ for shape in self.shapes:
+ if (shape.selected or not self._hideBackround) and self.isVisible(shape):
+ shape.fill = shape.selected or shape == self.hShape
+ shape.paint(p)
+ if self.current:
+ self.current.paint(p)
+ self.line.paint(p)
+ if self.selectedShapeCopy:
+ self.selectedShapeCopy.paint(p)
+
+ # Paint rect
+ if self.current is not None and len(self.line) == 2:
+ leftTop = self.line[0]
+ rightBottom = self.line[1]
+ rectWidth = rightBottom.x() - leftTop.x()
+ rectHeight = rightBottom.y() - leftTop.y()
+ p.setPen(self.drawingRectColor)
+ brush = QBrush(Qt.BDiagPattern)
+ p.setBrush(brush)
+ p.drawRect(leftTop.x(), leftTop.y(), rectWidth, rectHeight)
+
+ if self.drawing() and not self.prevPoint.isNull() and not self.outOfPixmap(self.prevPoint):
+ p.setPen(QColor(0, 0, 0))
+ p.drawLine(self.prevPoint.x(), 0, self.prevPoint.x(), self.pixmap.height())
+ p.drawLine(0, self.prevPoint.y(), self.pixmap.width(), self.prevPoint.y())
+
+ self.setAutoFillBackground(True)
+ if self.verified:
+ pal = self.palette()
+ pal.setColor(self.backgroundRole(), QColor(184, 239, 38, 128))
+ self.setPalette(pal)
+ else:
+ pal = self.palette()
+ pal.setColor(self.backgroundRole(), QColor(232, 232, 232, 255))
+ self.setPalette(pal)
+
+ p.end()
+
+ def transformPos(self, point):
+ """Convert from widget-logical coordinates to painter-logical coordinates."""
+ return point / self.scale - self.offsetToCenter()
+
+ def offsetToCenter(self):
+ s = self.scale
+ area = super(Canvas, self).size()
+ w, h = self.pixmap.width() * s, self.pixmap.height() * s
+ aw, ah = area.width(), area.height()
+ x = (aw - w) / (2 * s) if aw > w else 0
+ y = (ah - h) / (2 * s) if ah > h else 0
+ return QPointF(x, y)
+
+ def outOfPixmap(self, p):
+ w, h = self.pixmap.width(), self.pixmap.height()
+ return not (0 <= p.x() <= w and 0 <= p.y() <= h)
+
+ def finalise(self):
+ assert self.current
+ if self.current.points[0] == self.current.points[-1]:
+ self.current = None
+ self.drawingPolygon.emit(False)
+ self.update()
+ return
+
+ self.current.close()
+ self.shapes.append(self.current)
+ self.current = None
+ self.setHiding(False)
+ self.newShape.emit()
+ self.update()
+
+ def closeEnough(self, p1, p2):
+ #d = distance(p1 - p2)
+ #m = (p1-p2).manhattanLength()
+ # print "d %.2f, m %d, %.2f" % (d, m, d - m)
+ return distance(p1 - p2) < self.epsilon
+
+ def intersectionPoint(self, p1, p2):
+ # Cycle through each image edge in clockwise fashion,
+ # and find the one intersecting the current line segment.
+ # http://paulbourke.net/geometry/lineline2d/
+ size = self.pixmap.size()
+ points = [(0, 0),
+ (size.width(), 0),
+ (size.width(), size.height()),
+ (0, size.height())]
+ x1, y1 = p1.x(), p1.y()
+ x2, y2 = p2.x(), p2.y()
+ d, i, (x, y) = min(self.intersectingEdges((x1, y1), (x2, y2), points))
+ x3, y3 = points[i]
+ x4, y4 = points[(i + 1) % 4]
+ if (x, y) == (x1, y1):
+ # Handle cases where previous point is on one of the edges.
+ if x3 == x4:
+ return QPointF(x3, min(max(0, y2), max(y3, y4)))
+ else: # y3 == y4
+ return QPointF(min(max(0, x2), max(x3, x4)), y3)
+
+ # Ensure the labels are within the bounds of the image. If not, fix them.
+ x, y, _ = self.snapPointToCanvas(x, y)
+
+ return QPointF(x, y)
+
+ def intersectingEdges(self, x1y1, x2y2, points):
+ """For each edge formed by `points', yield the intersection
+ with the line segment `(x1,y1) - (x2,y2)`, if it exists.
+ Also return the distance of `(x2,y2)' to the middle of the
+ edge along with its index, so that the one closest can be chosen."""
+ x1, y1 = x1y1
+ x2, y2 = x2y2
+ for i in range(4):
+ x3, y3 = points[i]
+ x4, y4 = points[(i + 1) % 4]
+ denom = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1)
+ nua = (x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)
+ nub = (x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)
+ if denom == 0:
+ # This covers two cases:
+ # nua == nub == 0: Coincident
+ # otherwise: Parallel
+ continue
+ ua, ub = nua / denom, nub / denom
+ if 0 <= ua <= 1 and 0 <= ub <= 1:
+ x = x1 + ua * (x2 - x1)
+ y = y1 + ua * (y2 - y1)
+ m = QPointF((x3 + x4) / 2, (y3 + y4) / 2)
+ d = distance(m - QPointF(x2, y2))
+ yield d, i, (x, y)
+
+ # These two, along with a call to adjustSize are required for the
+ # scroll area.
+ def sizeHint(self):
+ return self.minimumSizeHint()
+
+ def minimumSizeHint(self):
+ if self.pixmap:
+ return self.scale * self.pixmap.size()
+ return super(Canvas, self).minimumSizeHint()
+
+ def wheelEvent(self, ev):
+ qt_version = 4 if hasattr(ev, "delta") else 5
+ if qt_version == 4:
+ if ev.orientation() == Qt.Vertical:
+ v_delta = ev.delta()
+ h_delta = 0
+ else:
+ h_delta = ev.delta()
+ v_delta = 0
+ else:
+ delta = ev.angleDelta()
+ h_delta = delta.x()
+ v_delta = delta.y()
+
+ mods = ev.modifiers()
+ if Qt.ControlModifier == int(mods) and v_delta:
+ self.zoomRequest.emit(v_delta)
+ else:
+ v_delta and self.scrollRequest.emit(v_delta, Qt.Vertical)
+ h_delta and self.scrollRequest.emit(h_delta, Qt.Horizontal)
+ ev.accept()
+
+ def keyPressEvent(self, ev):
+ key = ev.key()
+ if key == Qt.Key_Escape and self.current:
+ print('ESC press')
+ self.current = None
+ self.drawingPolygon.emit(False)
+ self.update()
+ elif key == Qt.Key_Return and self.canCloseShape():
+ self.finalise()
+ elif key == Qt.Key_Left and self.selectedShape:
+ self.moveOnePixel('Left')
+ elif key == Qt.Key_Right and self.selectedShape:
+ self.moveOnePixel('Right')
+ elif key == Qt.Key_Up and self.selectedShape:
+ self.moveOnePixel('Up')
+ elif key == Qt.Key_Down and self.selectedShape:
+ self.moveOnePixel('Down')
+
+ def moveOnePixel(self, direction):
+ # print(self.selectedShape.points)
+ if direction == 'Left' and not self.moveOutOfBound(QPointF(-1.0, 0)):
+ # print("move Left one pixel")
+ self.selectedShape.points[0] += QPointF(-1.0, 0)
+ self.selectedShape.points[1] += QPointF(-1.0, 0)
+ self.selectedShape.points[2] += QPointF(-1.0, 0)
+ self.selectedShape.points[3] += QPointF(-1.0, 0)
+ elif direction == 'Right' and not self.moveOutOfBound(QPointF(1.0, 0)):
+ # print("move Right one pixel")
+ self.selectedShape.points[0] += QPointF(1.0, 0)
+ self.selectedShape.points[1] += QPointF(1.0, 0)
+ self.selectedShape.points[2] += QPointF(1.0, 0)
+ self.selectedShape.points[3] += QPointF(1.0, 0)
+ elif direction == 'Up' and not self.moveOutOfBound(QPointF(0, -1.0)):
+ # print("move Up one pixel")
+ self.selectedShape.points[0] += QPointF(0, -1.0)
+ self.selectedShape.points[1] += QPointF(0, -1.0)
+ self.selectedShape.points[2] += QPointF(0, -1.0)
+ self.selectedShape.points[3] += QPointF(0, -1.0)
+ elif direction == 'Down' and not self.moveOutOfBound(QPointF(0, 1.0)):
+ # print("move Down one pixel")
+ self.selectedShape.points[0] += QPointF(0, 1.0)
+ self.selectedShape.points[1] += QPointF(0, 1.0)
+ self.selectedShape.points[2] += QPointF(0, 1.0)
+ self.selectedShape.points[3] += QPointF(0, 1.0)
+ self.shapeMoved.emit()
+ self.repaint()
+
+ def moveOutOfBound(self, step):
+ points = [p1+p2 for p1, p2 in zip(self.selectedShape.points, [step]*4)]
+ return True in map(self.outOfPixmap, points)
+
+ def setLastLabel(self, text, line_color = None, fill_color = None):
+ assert text
+ self.shapes[-1].label = text
+ if line_color:
+ self.shapes[-1].line_color = line_color
+
+ if fill_color:
+ self.shapes[-1].fill_color = fill_color
+
+ return self.shapes[-1]
+
+ def undoLastLine(self):
+ assert self.shapes
+ self.current = self.shapes.pop()
+ self.current.setOpen()
+ self.line.points = [self.current[-1], self.current[0]]
+ self.drawingPolygon.emit(True)
+
+ def resetAllLines(self):
+ assert self.shapes
+ self.current = self.shapes.pop()
+ self.current.setOpen()
+ self.line.points = [self.current[-1], self.current[0]]
+ self.drawingPolygon.emit(True)
+ self.current = None
+ self.drawingPolygon.emit(False)
+ self.update()
+
+ def loadPixmap(self, pixmap):
+ self.pixmap = pixmap
+ self.shapes = []
+ self.repaint()
+
+ def loadShapes(self, shapes):
+ self.shapes = list(shapes)
+ self.current = None
+ self.repaint()
+
+ def setShapeVisible(self, shape, value):
+ self.visible[shape] = value
+ self.repaint()
+
+ def currentCursor(self):
+ cursor = QApplication.overrideCursor()
+ if cursor is not None:
+ cursor = cursor.shape()
+ return cursor
+
+ def overrideCursor(self, cursor):
+ self._cursor = cursor
+ if self.currentCursor() is None:
+ QApplication.setOverrideCursor(cursor)
+ else:
+ QApplication.changeOverrideCursor(cursor)
+
+ def restoreCursor(self):
+ QApplication.restoreOverrideCursor()
+
+ def resetState(self):
+ self.restoreCursor()
+ self.pixmap = None
+ self.update()
+
+ def setDrawingShapeToSquare(self, status):
+ self.drawSquare = status
diff --git a/labelImg-master/libs/colorDialog.py b/labelImg-master/libs/colorDialog.py
new file mode 100644
index 0000000..d5d9475
--- /dev/null
+++ b/labelImg-master/libs/colorDialog.py
@@ -0,0 +1,37 @@
+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)
diff --git a/labelImg-master/libs/constants.py b/labelImg-master/libs/constants.py
new file mode 100644
index 0000000..f231ec8
--- /dev/null
+++ b/labelImg-master/libs/constants.py
@@ -0,0 +1,18 @@
+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'
+DEFAULT_ENCODING = 'utf-8'
diff --git a/labelImg-master/libs/hashableQListWidgetItem.py b/labelImg-master/libs/hashableQListWidgetItem.py
new file mode 100644
index 0000000..ac7818a
--- /dev/null
+++ b/labelImg-master/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/labelImg-master/libs/labelDialog.py b/labelImg-master/libs/labelDialog.py
new file mode 100644
index 0000000..b59de08
--- /dev/null
+++ b/labelImg-master/libs/labelDialog.py
@@ -0,0 +1,83 @@
+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()
+ 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())
+ except AttributeError:
+ # PyQt5: AttributeError: 'str' object has no attribute 'trimmed'
+ self.edit.setText(self.edit.text())
+
+ def popUp(self, text='', move=True):
+ self.edit.setText(text)
+ self.edit.setSelection(0, len(text))
+ self.edit.setFocus(Qt.PopupFocusReason)
+ if move:
+ self.move(QCursor.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()
diff --git a/labelImg-master/libs/labelFile.py b/labelImg-master/libs/labelFile.py
new file mode 100644
index 0000000..9a3c54e
--- /dev/null
+++ b/labelImg-master/libs/labelFile.py
@@ -0,0 +1,146 @@
+# Copyright (c) 2016 Tzutalin
+# Create by TzuTaLin
+
+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
+import os.path
+import sys
+
+
+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))
diff --git a/labelImg-master/libs/pascal_voc_io.py b/labelImg-master/libs/pascal_voc_io.py
new file mode 100644
index 0000000..627e315
--- /dev/null
+++ b/labelImg-master/libs/pascal_voc_io.py
@@ -0,0 +1,171 @@
+#!/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
diff --git a/labelImg-master/libs/resources.qrc b/labelImg-master/libs/resources.qrc
new file mode 100644
index 0000000..37d2f2d
--- /dev/null
+++ b/labelImg-master/libs/resources.qrc
@@ -0,0 +1,38 @@
+
+
+
+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
+resources/strings/strings-zh-CN.properties
+
+
diff --git a/labelImg-master/libs/settings.py b/labelImg-master/libs/settings.py
new file mode 100644
index 0000000..e3eea73
--- /dev/null
+++ b/labelImg-master/libs/settings.py
@@ -0,0 +1,46 @@
+import pickle
+import os
+import sys
+
+
+class Settings(object):
+ def __init__(self):
+ # Be default, the home will be in the same folder as labelImg
+ home = os.path.expanduser("~")
+ self.data = {}
+ self.path = os.path.join(home, '.labelImgSettings.pkl')
+
+ def __setitem__(self, key, value):
+ self.data[key] = value
+
+ def __getitem__(self, key):
+ return self.data[key]
+
+ def get(self, key, default=None):
+ if key in self.data:
+ return self.data[key]
+ return default
+
+ def save(self):
+ if self.path:
+ with open(self.path, 'wb') as f:
+ pickle.dump(self.data, f, pickle.HIGHEST_PROTOCOL)
+ return True
+ return False
+
+ def load(self):
+ try:
+ if os.path.exists(self.path):
+ with open(self.path, 'rb') as f:
+ self.data = pickle.load(f)
+ return True
+ except:
+ print('Loading setting failed')
+ return False
+
+ def reset(self):
+ if os.path.exists(self.path):
+ os.remove(self.path)
+ print('Remove setting pkl file ${0}'.format(self.path))
+ self.data = {}
+ self.path = None
diff --git a/labelImg-master/libs/shape.py b/labelImg-master/libs/shape.py
new file mode 100644
index 0000000..c72a68f
--- /dev/null
+++ b/labelImg-master/libs/shape.py
@@ -0,0 +1,205 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+
+try:
+ from PyQt5.QtGui import *
+ from PyQt5.QtCore import *
+except ImportError:
+ from PyQt4.QtGui import *
+ from PyQt4.QtCore import *
+
+from libs.utils import distance
+import sys
+
+DEFAULT_LINE_COLOR = QColor(0, 255, 0, 128)
+DEFAULT_FILL_COLOR = QColor(255, 0, 0, 128)
+DEFAULT_SELECT_LINE_COLOR = QColor(255, 255, 255)
+DEFAULT_SELECT_FILL_COLOR = QColor(0, 128, 255, 155)
+DEFAULT_VERTEX_FILL_COLOR = QColor(0, 255, 0, 255)
+DEFAULT_HVERTEX_FILL_COLOR = QColor(255, 0, 0)
+MIN_Y_LABEL = 10
+
+
+class Shape(object):
+ P_SQUARE, P_ROUND = range(2)
+
+ MOVE_VERTEX, NEAR_VERTEX = range(2)
+
+ # The following class variables influence the drawing
+ # of _all_ shape objects.
+ line_color = DEFAULT_LINE_COLOR
+ fill_color = DEFAULT_FILL_COLOR
+ select_line_color = DEFAULT_SELECT_LINE_COLOR
+ select_fill_color = DEFAULT_SELECT_FILL_COLOR
+ vertex_fill_color = DEFAULT_VERTEX_FILL_COLOR
+ hvertex_fill_color = DEFAULT_HVERTEX_FILL_COLOR
+ point_type = P_ROUND
+ point_size = 8
+ scale = 1.0
+
+ def __init__(self, label=None, line_color=None, difficult=False, paintLabel=False):
+ self.label = label
+ self.points = []
+ self.fill = False
+ self.selected = False
+ self.difficult = difficult
+ self.paintLabel = paintLabel
+
+ self._highlightIndex = None
+ self._highlightMode = self.NEAR_VERTEX
+ self._highlightSettings = {
+ self.NEAR_VERTEX: (4, self.P_ROUND),
+ self.MOVE_VERTEX: (1.5, self.P_SQUARE),
+ }
+
+ self._closed = False
+
+ if line_color is not None:
+ # Override the class line_color attribute
+ # with an object attribute. Currently this
+ # is used for drawing the pending line a different color.
+ self.line_color = line_color
+
+ def close(self):
+ self._closed = True
+
+ def reachMaxPoints(self):
+ if len(self.points) >= 4:
+ return True
+ return False
+
+ def addPoint(self, point):
+ if not self.reachMaxPoints():
+ self.points.append(point)
+
+ def popPoint(self):
+ if self.points:
+ return self.points.pop()
+ return None
+
+ def isClosed(self):
+ return self._closed
+
+ def setOpen(self):
+ self._closed = False
+
+ def paint(self, painter):
+ if self.points:
+ color = self.select_line_color if self.selected else self.line_color
+ pen = QPen(color)
+ # Try using integer sizes for smoother drawing(?)
+ pen.setWidth(max(1, int(round(2.0 / self.scale))))
+ painter.setPen(pen)
+
+ line_path = QPainterPath()
+ vrtx_path = QPainterPath()
+
+ line_path.moveTo(self.points[0])
+ # Uncommenting the following line will draw 2 paths
+ # for the 1st vertex, and make it non-filled, which
+ # may be desirable.
+ #self.drawVertex(vrtx_path, 0)
+
+ for i, p in enumerate(self.points):
+ line_path.lineTo(p)
+ self.drawVertex(vrtx_path, i)
+ if self.isClosed():
+ line_path.lineTo(self.points[0])
+
+ painter.drawPath(line_path)
+ painter.drawPath(vrtx_path)
+ painter.fillPath(vrtx_path, self.vertex_fill_color)
+
+ # Draw text at the top-left
+ if self.paintLabel:
+ min_x = sys.maxsize
+ min_y = sys.maxsize
+ for point in self.points:
+ min_x = min(min_x, point.x())
+ min_y = min(min_y, point.y())
+ if min_x != sys.maxsize and min_y != sys.maxsize:
+ font = QFont()
+ font.setPointSize(8)
+ font.setBold(True)
+ painter.setFont(font)
+ if(self.label == None):
+ self.label = ""
+ if(min_y < MIN_Y_LABEL):
+ min_y += MIN_Y_LABEL
+ painter.drawText(min_x, min_y, self.label)
+
+ if self.fill:
+ color = self.select_fill_color if self.selected else self.fill_color
+ painter.fillPath(line_path, color)
+
+ def drawVertex(self, path, i):
+ d = self.point_size / self.scale
+ shape = self.point_type
+ point = self.points[i]
+ if i == self._highlightIndex:
+ size, shape = self._highlightSettings[self._highlightMode]
+ d *= size
+ if self._highlightIndex is not None:
+ self.vertex_fill_color = self.hvertex_fill_color
+ else:
+ self.vertex_fill_color = Shape.vertex_fill_color
+ if shape == self.P_SQUARE:
+ path.addRect(point.x() - d / 2, point.y() - d / 2, d, d)
+ elif shape == self.P_ROUND:
+ path.addEllipse(point, d / 2.0, d / 2.0)
+ else:
+ assert False, "unsupported vertex shape"
+
+ def nearestVertex(self, point, epsilon):
+ for i, p in enumerate(self.points):
+ if distance(p - point) <= epsilon:
+ return i
+ return None
+
+ def containsPoint(self, point):
+ return self.makePath().contains(point)
+
+ def makePath(self):
+ path = QPainterPath(self.points[0])
+ for p in self.points[1:]:
+ path.lineTo(p)
+ return path
+
+ def boundingRect(self):
+ return self.makePath().boundingRect()
+
+ def moveBy(self, offset):
+ self.points = [p + offset for p in self.points]
+
+ def moveVertexBy(self, i, offset):
+ self.points[i] = self.points[i] + offset
+
+ def highlightVertex(self, i, action):
+ self._highlightIndex = i
+ self._highlightMode = action
+
+ def highlightClear(self):
+ self._highlightIndex = None
+
+ def copy(self):
+ shape = Shape("%s" % self.label)
+ shape.points = [p for p in self.points]
+ shape.fill = self.fill
+ shape.selected = self.selected
+ shape._closed = self._closed
+ if self.line_color != Shape.line_color:
+ shape.line_color = self.line_color
+ if self.fill_color != Shape.fill_color:
+ shape.fill_color = self.fill_color
+ shape.difficult = self.difficult
+ return shape
+
+ def __len__(self):
+ return len(self.points)
+
+ def __getitem__(self, key):
+ return self.points[key]
+
+ def __setitem__(self, key, value):
+ self.points[key] = value
diff --git a/labelImg-master/libs/stringBundle.py b/labelImg-master/libs/stringBundle.py
new file mode 100644
index 0000000..a84f169
--- /dev/null
+++ b/labelImg-master/libs/stringBundle.py
@@ -0,0 +1,73 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+import re
+import os
+import sys
+import locale
+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=None):
+ if localeStr is None:
+ try:
+ localeStr = locale.getlocale()[0] if locale.getlocale() and len(
+ locale.getlocale()) > 0 else os.getenv('LANG')
+ except:
+ print('Invalid locale')
+ localeStr = 'en'
+
+ 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)
+ if localeStr is not None:
+ # 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/labelImg-master/libs/toolBar.py b/labelImg-master/libs/toolBar.py
new file mode 100644
index 0000000..b11887e
--- /dev/null
+++ b/labelImg-master/libs/toolBar.py
@@ -0,0 +1,39 @@
+try:
+ from PyQt5.QtGui import *
+ from PyQt5.QtCore import *
+ from PyQt5.QtWidgets import *
+except ImportError:
+ from PyQt4.QtGui import *
+ from PyQt4.QtCore import *
+
+
+class ToolBar(QToolBar):
+
+ def __init__(self, title):
+ super(ToolBar, self).__init__(title)
+ layout = self.layout()
+ m = (0, 0, 0, 0)
+ layout.setSpacing(0)
+ layout.setContentsMargins(*m)
+ self.setContentsMargins(*m)
+ self.setWindowFlags(self.windowFlags() | Qt.FramelessWindowHint)
+
+ def addAction(self, action):
+ if isinstance(action, QWidgetAction):
+ return super(ToolBar, self).addAction(action)
+ btn = ToolButton()
+ btn.setDefaultAction(action)
+ btn.setToolButtonStyle(self.toolButtonStyle())
+ self.addWidget(btn)
+
+
+class ToolButton(QToolButton):
+ """ToolBar companion class which ensures all buttons have the same size."""
+ minSize = (60, 60)
+
+ def minimumSizeHint(self):
+ ms = super(ToolButton, self).minimumSizeHint()
+ w1, h1 = ms.width(), ms.height()
+ w2, h2 = self.minSize
+ ToolButton.minSize = max(w1, w2), max(h1, h2)
+ return QSize(*ToolButton.minSize)
diff --git a/labelImg-master/libs/ustr.py b/labelImg-master/libs/ustr.py
new file mode 100644
index 0000000..511e68b
--- /dev/null
+++ b/labelImg-master/libs/ustr.py
@@ -0,0 +1,17 @@
+import sys
+from libs.constants import DEFAULT_ENCODING
+
+def ustr(x):
+ '''py2/py3 unicode helper'''
+
+ if sys.version_info < (3, 0, 0):
+ from PyQt4.QtCore import QString
+ if type(x) == str:
+ return x.decode(DEFAULT_ENCODING)
+ if type(x) == QString:
+ #https://blog.csdn.net/friendan/article/details/51088476
+ #https://blog.csdn.net/xxm524/article/details/74937308
+ return unicode(x.toUtf8(), DEFAULT_ENCODING, 'ignore')
+ return x
+ else:
+ return x
diff --git a/labelImg-master/libs/utils.py b/labelImg-master/libs/utils.py
new file mode 100644
index 0000000..dacf682
--- /dev/null
+++ b/labelImg-master/libs/utils.py
@@ -0,0 +1,103 @@
+from math import sqrt
+from libs.ustr import ustr
+import hashlib
+import re
+import sys
+
+try:
+ from PyQt5.QtGui import *
+ from PyQt5.QtCore import *
+ from PyQt5.QtWidgets import *
+except ImportError:
+ from PyQt4.QtGui import *
+ from PyQt4.QtCore import *
+
+
+def newIcon(icon):
+ return QIcon(':/' + icon)
+
+
+def newButton(text, icon=None, slot=None):
+ b = QPushButton(text)
+ if icon is not None:
+ b.setIcon(newIcon(icon))
+ if slot is not None:
+ b.clicked.connect(slot)
+ return b
+
+
+def newAction(parent, text, slot=None, shortcut=None, icon=None,
+ tip=None, checkable=False, enabled=True):
+ """Create a new action and assign callbacks, shortcuts, etc."""
+ a = QAction(text, parent)
+ if icon is not None:
+ a.setIcon(newIcon(icon))
+ if shortcut is not None:
+ if isinstance(shortcut, (list, tuple)):
+ a.setShortcuts(shortcut)
+ else:
+ a.setShortcut(shortcut)
+ if tip is not None:
+ a.setToolTip(tip)
+ a.setStatusTip(tip)
+ if slot is not None:
+ a.triggered.connect(slot)
+ if checkable:
+ a.setCheckable(True)
+ a.setEnabled(enabled)
+ return a
+
+
+def addActions(widget, actions):
+ for action in actions:
+ if action is None:
+ widget.addSeparator()
+ elif isinstance(action, QMenu):
+ widget.addMenu(action)
+ else:
+ widget.addAction(action)
+
+
+def labelValidator():
+ return QRegExpValidator(QRegExp(r'^[^ \t].+'), None)
+
+
+class struct(object):
+
+ def __init__(self, **kwargs):
+ self.__dict__.update(kwargs)
+
+
+def distance(p):
+ return sqrt(p.x() * p.x() + p.y() * p.y())
+
+
+def fmtShortcut(text):
+ mod, key = text.split('+', 1)
+ return '%s+%s' % (mod, key)
+
+
+def generateColorByText(text):
+ s = ustr(text)
+ hashCode = int(hashlib.sha256(s.encode('utf-8')).hexdigest(), 16)
+ r = int((hashCode / 255) % 255)
+ g = int((hashCode / 65025) % 255)
+ b = int((hashCode / 16581375) % 255)
+ return QColor(r, g, b, 100)
+
+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
+
+def natural_sort(list, key=lambda s:s):
+ """
+ Sort the list into natural alphanumeric order.
+ """
+ def get_alphanum_key_func(key):
+ convert = lambda text: int(text) if text.isdigit() else text
+ return lambda s: [convert(c) for c in re.split('([0-9]+)', key(s))]
+ sort_key = get_alphanum_key_func(key)
+ list.sort(key=sort_key)
diff --git a/labelImg-master/libs/yolo_io.py b/labelImg-master/libs/yolo_io.py
new file mode 100644
index 0000000..d494485
--- /dev/null
+++ b/labelImg-master/libs/yolo_io.py
@@ -0,0 +1,146 @@
+#!/usr/bin/env python
+# -*- coding: utf8 -*-
+import sys
+import os
+from xml.etree import ElementTree
+from xml.etree.ElementTree import Element, SubElement
+from lxml import etree
+import codecs
+from libs.constants import DEFAULT_ENCODING
+
+TXT_EXT = '.txt'
+ENCODE_METHOD = DEFAULT_ENCODING
+
+class YOLOWriter:
+
+ 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 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 BndBox2YoloLine(self, box, classList=[]):
+ xmin = box['xmin']
+ xmax = box['xmax']
+ ymin = box['ymin']
+ ymax = box['ymax']
+
+ xcen = float((xmin + xmax)) / 2 / self.imgSize[1]
+ ycen = float((ymin + ymax)) / 2 / self.imgSize[0]
+
+ w = float((xmax - xmin)) / self.imgSize[1]
+ h = float((ymax - ymin)) / self.imgSize[0]
+
+ # PR387
+ boxName = box['name']
+ if boxName not in classList:
+ classList.append(boxName)
+
+ classIndex = classList.index(boxName)
+
+ return classIndex, xcen, ycen, w, h
+
+ def save(self, classList=[], targetFile=None):
+
+ out_file = None #Update yolo .txt
+ out_class_file = None #Update class list .txt
+
+ if targetFile is None:
+ out_file = open(
+ self.filename + TXT_EXT, 'w', encoding=ENCODE_METHOD)
+ classesFile = os.path.join(os.path.dirname(os.path.abspath(self.filename)), "classes.txt")
+ out_class_file = open(classesFile, 'w')
+
+ else:
+ out_file = codecs.open(targetFile, 'w', encoding=ENCODE_METHOD)
+ classesFile = os.path.join(os.path.dirname(os.path.abspath(targetFile)), "classes.txt")
+ out_class_file = open(classesFile, 'w')
+
+
+ for box in self.boxlist:
+ classIndex, xcen, ycen, w, h = self.BndBox2YoloLine(box, classList)
+ # print (classIndex, xcen, ycen, w, h)
+ out_file.write("%d %.6f %.6f %.6f %.6f\n" % (classIndex, xcen, ycen, w, h))
+
+ # print (classList)
+ # print (out_class_file)
+ for c in classList:
+ out_class_file.write(c+'\n')
+
+ out_class_file.close()
+ out_file.close()
+
+
+
+class YoloReader:
+
+ def __init__(self, filepath, image, classListPath=None):
+ # shapes type:
+ # [labbel, [(x1,y1), (x2,y2), (x3,y3), (x4,y4)], color, color, difficult]
+ self.shapes = []
+ self.filepath = filepath
+
+ if classListPath is None:
+ dir_path = os.path.dirname(os.path.realpath(self.filepath))
+ self.classListPath = os.path.join(dir_path, "classes.txt")
+ else:
+ self.classListPath = classListPath
+
+ # print (filepath, self.classListPath)
+
+ classesFile = open(self.classListPath, 'r')
+ self.classes = classesFile.read().strip('\n').split('\n')
+
+ # print (self.classes)
+
+ imgSize = [image.height(), image.width(),
+ 1 if image.isGrayscale() else 3]
+
+ self.imgSize = imgSize
+
+ self.verified = False
+ # try:
+ self.parseYoloFormat()
+ # except:
+ # pass
+
+ def getShapes(self):
+ return self.shapes
+
+ def addShape(self, label, xmin, ymin, xmax, ymax, difficult):
+
+ points = [(xmin, ymin), (xmax, ymin), (xmax, ymax), (xmin, ymax)]
+ self.shapes.append((label, points, None, None, difficult))
+
+ def yoloLine2Shape(self, classIndex, xcen, ycen, w, h):
+ label = self.classes[int(classIndex)]
+
+ xmin = max(float(xcen) - float(w) / 2, 0)
+ xmax = min(float(xcen) + float(w) / 2, 1)
+ ymin = max(float(ycen) - float(h) / 2, 0)
+ ymax = min(float(ycen) + float(h) / 2, 1)
+
+ xmin = int(self.imgSize[1] * xmin)
+ xmax = int(self.imgSize[1] * xmax)
+ ymin = int(self.imgSize[0] * ymin)
+ ymax = int(self.imgSize[0] * ymax)
+
+ return label, xmin, ymin, xmax, ymax
+
+ def parseYoloFormat(self):
+ bndBoxFile = open(self.filepath, 'r')
+ for bndBox in bndBoxFile:
+ classIndex, xcen, ycen, w, h = bndBox.split(' ')
+ label, xmin, ymin, xmax, ymax = self.yoloLine2Shape(classIndex, xcen, ycen, w, h)
+
+ # Caveat: difficult flag is discarded when saved as yolo format.
+ self.addShape(label, xmin, ymin, xmax, ymax, False)
diff --git a/labelImg-master/libs/zoomWidget.py b/labelImg-master/libs/zoomWidget.py
new file mode 100644
index 0000000..d33de0d
--- /dev/null
+++ b/labelImg-master/libs/zoomWidget.py
@@ -0,0 +1,26 @@
+try:
+ from PyQt5.QtGui import *
+ from PyQt5.QtCore import *
+ from PyQt5.QtWidgets import *
+except ImportError:
+ from PyQt4.QtGui import *
+ from PyQt4.QtCore import *
+
+
+class ZoomWidget(QSpinBox):
+
+ def __init__(self, value=100):
+ super(ZoomWidget, self).__init__()
+ self.setButtonSymbols(QAbstractSpinBox.NoButtons)
+ self.setRange(1, 500)
+ self.setSuffix(' %')
+ self.setValue(value)
+ self.setToolTip(u'Zoom Level')
+ self.setStatusTip(self.toolTip())
+ self.setAlignment(Qt.AlignCenter)
+
+ def minimumSizeHint(self):
+ height = super(ZoomWidget, self).minimumSizeHint().height()
+ fm = QFontMetrics(self.font())
+ width = fm.width(str(self.maximum()))
+ return QSize(width, height)
diff --git a/labelImg-master/requirements/requirements-linux-python3.txt b/labelImg-master/requirements/requirements-linux-python3.txt
new file mode 100644
index 0000000..787a7fd
--- /dev/null
+++ b/labelImg-master/requirements/requirements-linux-python3.txt
@@ -0,0 +1,2 @@
+pyqt5==5.10.1
+lxml==4.2.4
diff --git a/labelImg-master/resources.qrc b/labelImg-master/resources.qrc
new file mode 100644
index 0000000..37d2f2d
--- /dev/null
+++ b/labelImg-master/resources.qrc
@@ -0,0 +1,38 @@
+
+
+
+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
+resources/strings/strings-zh-CN.properties
+
+
diff --git a/labelImg-master/resources/icons/app.icns b/labelImg-master/resources/icons/app.icns
new file mode 100644
index 0000000..9409991
Binary files /dev/null and b/labelImg-master/resources/icons/app.icns differ
diff --git a/labelImg-master/resources/icons/app.png b/labelImg-master/resources/icons/app.png
new file mode 100644
index 0000000..f2dc33e
Binary files /dev/null and b/labelImg-master/resources/icons/app.png differ
diff --git a/labelImg-master/resources/icons/app.svg b/labelImg-master/resources/icons/app.svg
new file mode 100644
index 0000000..b691a2e
--- /dev/null
+++ b/labelImg-master/resources/icons/app.svg
@@ -0,0 +1,30 @@
+
+
+
diff --git a/labelImg-master/resources/icons/cancel.png b/labelImg-master/resources/icons/cancel.png
new file mode 100644
index 0000000..8fbfab8
Binary files /dev/null and b/labelImg-master/resources/icons/cancel.png differ
diff --git a/labelImg-master/resources/icons/close.png b/labelImg-master/resources/icons/close.png
new file mode 100644
index 0000000..aa52a8d
Binary files /dev/null and b/labelImg-master/resources/icons/close.png differ
diff --git a/labelImg-master/resources/icons/color.png b/labelImg-master/resources/icons/color.png
new file mode 100644
index 0000000..1a1e1ad
Binary files /dev/null and b/labelImg-master/resources/icons/color.png differ
diff --git a/labelImg-master/resources/icons/color_line.png b/labelImg-master/resources/icons/color_line.png
new file mode 100644
index 0000000..6ef10bf
Binary files /dev/null and b/labelImg-master/resources/icons/color_line.png differ
diff --git a/labelImg-master/resources/icons/copy.png b/labelImg-master/resources/icons/copy.png
new file mode 100644
index 0000000..a4c9bdd
Binary files /dev/null and b/labelImg-master/resources/icons/copy.png differ
diff --git a/labelImg-master/resources/icons/delete.png b/labelImg-master/resources/icons/delete.png
new file mode 100644
index 0000000..a1a4074
Binary files /dev/null and b/labelImg-master/resources/icons/delete.png differ
diff --git a/labelImg-master/resources/icons/done.png b/labelImg-master/resources/icons/done.png
new file mode 100644
index 0000000..d8a03f4
Binary files /dev/null and b/labelImg-master/resources/icons/done.png differ
diff --git a/labelImg-master/resources/icons/done.svg b/labelImg-master/resources/icons/done.svg
new file mode 100644
index 0000000..aa8fd28
--- /dev/null
+++ b/labelImg-master/resources/icons/done.svg
@@ -0,0 +1,400 @@
+
+
+
+
diff --git a/labelImg-master/resources/icons/edit.png b/labelImg-master/resources/icons/edit.png
new file mode 100644
index 0000000..3677539
Binary files /dev/null and b/labelImg-master/resources/icons/edit.png differ
diff --git a/labelImg-master/resources/icons/expert1.png b/labelImg-master/resources/icons/expert1.png
new file mode 100644
index 0000000..2813645
Binary files /dev/null and b/labelImg-master/resources/icons/expert1.png differ
diff --git a/labelImg-master/resources/icons/expert2.png b/labelImg-master/resources/icons/expert2.png
new file mode 100644
index 0000000..7c470b6
Binary files /dev/null and b/labelImg-master/resources/icons/expert2.png differ
diff --git a/labelImg-master/resources/icons/eye.png b/labelImg-master/resources/icons/eye.png
new file mode 100644
index 0000000..c4b6550
Binary files /dev/null and b/labelImg-master/resources/icons/eye.png differ
diff --git a/labelImg-master/resources/icons/feBlend-icon.png b/labelImg-master/resources/icons/feBlend-icon.png
new file mode 100644
index 0000000..1c1aca8
Binary files /dev/null and b/labelImg-master/resources/icons/feBlend-icon.png differ
diff --git a/labelImg-master/resources/icons/file.png b/labelImg-master/resources/icons/file.png
new file mode 100644
index 0000000..1ec0515
Binary files /dev/null and b/labelImg-master/resources/icons/file.png differ
diff --git a/labelImg-master/resources/icons/fit-width.png b/labelImg-master/resources/icons/fit-width.png
new file mode 100644
index 0000000..0a54907
Binary files /dev/null and b/labelImg-master/resources/icons/fit-width.png differ
diff --git a/labelImg-master/resources/icons/fit-window.png b/labelImg-master/resources/icons/fit-window.png
new file mode 100644
index 0000000..585e970
Binary files /dev/null and b/labelImg-master/resources/icons/fit-window.png differ
diff --git a/labelImg-master/resources/icons/fit.png b/labelImg-master/resources/icons/fit.png
new file mode 100644
index 0000000..9e0e817
Binary files /dev/null and b/labelImg-master/resources/icons/fit.png differ
diff --git a/labelImg-master/resources/icons/format_voc.png b/labelImg-master/resources/icons/format_voc.png
new file mode 100644
index 0000000..cb15e43
Binary files /dev/null and b/labelImg-master/resources/icons/format_voc.png differ
diff --git a/labelImg-master/resources/icons/format_yolo.png b/labelImg-master/resources/icons/format_yolo.png
new file mode 100644
index 0000000..ca9acc7
Binary files /dev/null and b/labelImg-master/resources/icons/format_yolo.png differ
diff --git a/labelImg-master/resources/icons/help.png b/labelImg-master/resources/icons/help.png
new file mode 100644
index 0000000..93bf094
Binary files /dev/null and b/labelImg-master/resources/icons/help.png differ
diff --git a/labelImg-master/resources/icons/labels.png b/labelImg-master/resources/icons/labels.png
new file mode 100644
index 0000000..c82ffb7
Binary files /dev/null and b/labelImg-master/resources/icons/labels.png differ
diff --git a/labelImg-master/resources/icons/labels.svg b/labelImg-master/resources/icons/labels.svg
new file mode 100644
index 0000000..652cef3
--- /dev/null
+++ b/labelImg-master/resources/icons/labels.svg
@@ -0,0 +1,819 @@
+
+
+
+
\ No newline at end of file
diff --git a/labelImg-master/resources/icons/new.png b/labelImg-master/resources/icons/new.png
new file mode 100644
index 0000000..dd795cf
Binary files /dev/null and b/labelImg-master/resources/icons/new.png differ
diff --git a/labelImg-master/resources/icons/next.png b/labelImg-master/resources/icons/next.png
new file mode 100644
index 0000000..163a343
Binary files /dev/null and b/labelImg-master/resources/icons/next.png differ
diff --git a/labelImg-master/resources/icons/objects.png b/labelImg-master/resources/icons/objects.png
new file mode 100644
index 0000000..593bb6d
Binary files /dev/null and b/labelImg-master/resources/icons/objects.png differ
diff --git a/labelImg-master/resources/icons/open.png b/labelImg-master/resources/icons/open.png
new file mode 100644
index 0000000..45fa288
Binary files /dev/null and b/labelImg-master/resources/icons/open.png differ
diff --git a/labelImg-master/resources/icons/open.svg b/labelImg-master/resources/icons/open.svg
new file mode 100644
index 0000000..48e7a34
--- /dev/null
+++ b/labelImg-master/resources/icons/open.svg
@@ -0,0 +1,577 @@
+
+
+
\ No newline at end of file
diff --git a/labelImg-master/resources/icons/prev.png b/labelImg-master/resources/icons/prev.png
new file mode 100644
index 0000000..31b4545
Binary files /dev/null and b/labelImg-master/resources/icons/prev.png differ
diff --git a/labelImg-master/resources/icons/quit.png b/labelImg-master/resources/icons/quit.png
new file mode 100644
index 0000000..7445887
Binary files /dev/null and b/labelImg-master/resources/icons/quit.png differ
diff --git a/labelImg-master/resources/icons/resetall.png b/labelImg-master/resources/icons/resetall.png
new file mode 100644
index 0000000..acc12c3
Binary files /dev/null and b/labelImg-master/resources/icons/resetall.png differ
diff --git a/labelImg-master/resources/icons/save-as.png b/labelImg-master/resources/icons/save-as.png
new file mode 100644
index 0000000..1b5d900
Binary files /dev/null and b/labelImg-master/resources/icons/save-as.png differ
diff --git a/labelImg-master/resources/icons/save-as.svg b/labelImg-master/resources/icons/save-as.svg
new file mode 100644
index 0000000..c8441a1
--- /dev/null
+++ b/labelImg-master/resources/icons/save-as.svg
@@ -0,0 +1,1358 @@
+
+
+
+
diff --git a/labelImg-master/resources/icons/save.png b/labelImg-master/resources/icons/save.png
new file mode 100644
index 0000000..daba865
Binary files /dev/null and b/labelImg-master/resources/icons/save.png differ
diff --git a/labelImg-master/resources/icons/save.svg b/labelImg-master/resources/icons/save.svg
new file mode 100644
index 0000000..5533e48
--- /dev/null
+++ b/labelImg-master/resources/icons/save.svg
@@ -0,0 +1,679 @@
+
+
+
+
diff --git a/labelImg-master/resources/icons/undo-cross.png b/labelImg-master/resources/icons/undo-cross.png
new file mode 100644
index 0000000..7d57dcb
Binary files /dev/null and b/labelImg-master/resources/icons/undo-cross.png differ
diff --git a/labelImg-master/resources/icons/undo.png b/labelImg-master/resources/icons/undo.png
new file mode 100644
index 0000000..b2ac62b
Binary files /dev/null and b/labelImg-master/resources/icons/undo.png differ
diff --git a/labelImg-master/resources/icons/verify.png b/labelImg-master/resources/icons/verify.png
new file mode 100644
index 0000000..3f4a3b5
Binary files /dev/null and b/labelImg-master/resources/icons/verify.png differ
diff --git a/labelImg-master/resources/icons/zoom-in.png b/labelImg-master/resources/icons/zoom-in.png
new file mode 100644
index 0000000..1ac4864
Binary files /dev/null and b/labelImg-master/resources/icons/zoom-in.png differ
diff --git a/labelImg-master/resources/icons/zoom-out.png b/labelImg-master/resources/icons/zoom-out.png
new file mode 100644
index 0000000..d67a87d
Binary files /dev/null and b/labelImg-master/resources/icons/zoom-out.png differ
diff --git a/labelImg-master/resources/icons/zoom.png b/labelImg-master/resources/icons/zoom.png
new file mode 100644
index 0000000..8265f27
Binary files /dev/null and b/labelImg-master/resources/icons/zoom.png differ
diff --git a/labelImg-master/resources/strings/strings-zh-CN.properties b/labelImg-master/resources/strings/strings-zh-CN.properties
new file mode 100644
index 0000000..5459a0e
--- /dev/null
+++ b/labelImg-master/resources/strings/strings-zh-CN.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=隐藏所有区块
diff --git a/labelImg-master/resources/strings/strings-zh-TW.properties b/labelImg-master/resources/strings/strings-zh-TW.properties
new file mode 100644
index 0000000..c09f369
--- /dev/null
+++ b/labelImg-master/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/labelImg-master/resources/strings/strings.properties b/labelImg-master/resources/strings/strings.properties
new file mode 100644
index 0000000..94525fe
--- /dev/null
+++ b/labelImg-master/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 Width
+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 label
+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/labelImg-master/setup.cfg b/labelImg-master/setup.cfg
new file mode 100644
index 0000000..3b273b6
--- /dev/null
+++ b/labelImg-master/setup.cfg
@@ -0,0 +1,8 @@
+[bumpversion]
+commit = True
+tag = True
+
+[bumpversion:file:setup.py]
+
+[bdist_wheel]
+universal = 1
diff --git a/labelImg-master/setup.py b/labelImg-master/setup.py
new file mode 100644
index 0000000..a765bf6
--- /dev/null
+++ b/labelImg-master/setup.py
@@ -0,0 +1,126 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+from setuptools import setup, find_packages, Command
+from sys import platform as _platform
+from shutil import rmtree
+import sys
+import os
+
+here = os.path.abspath(os.path.dirname(__file__))
+NAME = 'labelImg'
+REQUIRES_PYTHON = '>=3.0.0'
+REQUIRED_DEP = ['pyqt5', 'lxml']
+about = {}
+
+with open(os.path.join(here, 'libs', '__init__.py')) as f:
+ exec(f.read(), about)
+
+with open('README.rst') as readme_file:
+ readme = readme_file.read()
+
+with open('HISTORY.rst') as history_file:
+ history = history_file.read()
+
+
+# OS specific settings
+SET_REQUIRES = []
+if _platform == "linux" or _platform == "linux2":
+ # linux
+ print('linux')
+elif _platform == "darwin":
+ # MAC OS X
+ SET_REQUIRES.append('py2app')
+
+required_packages = find_packages()
+required_packages.append('labelImg')
+
+APP = [NAME + '.py']
+OPTIONS = {
+ 'argv_emulation': True,
+ 'iconfile': 'resources/icons/app.icns'
+}
+
+class UploadCommand(Command):
+ """Support setup.py upload."""
+
+ description=readme + '\n\n' + history,
+
+ user_options = []
+
+ @staticmethod
+ def status(s):
+ """Prints things in bold."""
+ print('\033[1m{0}\033[0m'.format(s))
+
+ def initialize_options(self):
+ pass
+
+ def finalize_options(self):
+ pass
+
+ def run(self):
+ try:
+ self.status('Removing previous builds…')
+ rmtree(os.path.join(here, 'dist'))
+ except OSError:
+ self.status('Fail to remove previous builds..')
+ pass
+
+ self.status('Building Source and Wheel (universal) distribution…')
+ os.system(
+ '{0} setup.py sdist bdist_wheel --universal'.format(sys.executable))
+
+ self.status('Uploading the package to PyPI via Twine…')
+ os.system('twine upload dist/*')
+
+ self.status('Pushing git tags…')
+ os.system('git tag -d v{0}'.format(about['__version__']))
+ os.system('git tag v{0}'.format(about['__version__']))
+ # os.system('git push --tags')
+
+ sys.exit()
+
+
+setup(
+ app=APP,
+ name=NAME,
+ version=about['__version__'],
+ description="LabelImg is a graphical image annotation tool and label object bounding boxes in images",
+ long_description=readme + '\n\n' + history,
+ author="TzuTa Lin",
+ author_email='tzu.ta.lin@gmail.com',
+ url='https://github.com/tzutalin/labelImg',
+ python_requires=REQUIRES_PYTHON,
+ package_dir={'labelImg': '.'},
+ packages=required_packages,
+ entry_points={
+ 'console_scripts': [
+ 'labelImg=labelImg.labelImg:main'
+ ]
+ },
+ include_package_data=True,
+ install_requires=REQUIRED_DEP,
+ license="MIT license",
+ zip_safe=False,
+ keywords='labelImg labelTool development annotation deeplearning',
+ classifiers=[
+ 'Development Status :: 5 - Production/Stable',
+ 'Intended Audience :: Developers',
+ 'License :: OSI Approved :: MIT License',
+ 'Natural Language :: English',
+ 'Programming Language :: Python :: 3',
+ 'Programming Language :: Python :: 3.3',
+ 'Programming Language :: Python :: 3.4',
+ 'Programming Language :: Python :: 3.5',
+ 'Programming Language :: Python :: 3.6',
+ 'Programming Language :: Python :: 3.7',
+ ],
+ package_data={'data/predefined_classes.txt': ['data/predefined_classes.txt']},
+ options={'py2app': OPTIONS},
+ setup_requires=SET_REQUIRES,
+ # $ setup.py publish support.
+ cmdclass={
+ 'upload': UploadCommand,
+ }
+)
diff --git a/labelImg-master/tests/.gitignore b/labelImg-master/tests/.gitignore
new file mode 100644
index 0000000..a6535f3
--- /dev/null
+++ b/labelImg-master/tests/.gitignore
@@ -0,0 +1 @@
+test.xml
diff --git a/labelImg-master/tests/test.512.512.bmp b/labelImg-master/tests/test.512.512.bmp
new file mode 100644
index 0000000..fe7415d
Binary files /dev/null and b/labelImg-master/tests/test.512.512.bmp differ
diff --git a/labelImg-master/tests/test_io.py b/labelImg-master/tests/test_io.py
new file mode 100644
index 0000000..7067b40
--- /dev/null
+++ b/labelImg-master/tests/test_io.py
@@ -0,0 +1,32 @@
+import os
+import sys
+import unittest
+
+class TestPascalVocRW(unittest.TestCase):
+
+ def test_upper(self):
+ dir_name = os.path.abspath(os.path.dirname(__file__))
+ libs_path = os.path.join(dir_name, '..', 'libs')
+ sys.path.insert(0, libs_path)
+ from pascal_voc_io import PascalVocWriter
+ from pascal_voc_io import PascalVocReader
+
+ # Test Write/Read
+ writer = PascalVocWriter('tests', 'test', (512, 512, 1), localImgPath='tests/test.512.512.bmp')
+ difficult = 1
+ writer.addBndBox(60, 40, 430, 504, 'person', difficult)
+ writer.addBndBox(113, 40, 450, 403, 'face', difficult)
+ writer.save('tests/test.xml')
+
+ reader = PascalVocReader('tests/test.xml')
+ shapes = reader.getShapes()
+
+ personBndBox = shapes[0]
+ face = shapes[1]
+ self.assertEqual(personBndBox[0], 'person')
+ self.assertEqual(personBndBox[1], [(60, 40), (430, 40), (430, 504), (60, 504)])
+ self.assertEqual(face[0], 'face')
+ self.assertEqual(face[1], [(113, 40), (450, 40), (450, 403), (113, 403)])
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/labelImg-master/tests/test_qt.py b/labelImg-master/tests/test_qt.py
new file mode 100644
index 0000000..869094d
--- /dev/null
+++ b/labelImg-master/tests/test_qt.py
@@ -0,0 +1,20 @@
+
+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
diff --git a/labelImg-master/tests/test_settings.py b/labelImg-master/tests/test_settings.py
new file mode 100644
index 0000000..4df06d5
--- /dev/null
+++ b/labelImg-master/tests/test_settings.py
@@ -0,0 +1,33 @@
+#!/usr/bin/env python
+import os
+import sys
+import time
+import unittest
+
+__author__ = 'TzuTaLin'
+
+dir_name = os.path.abspath(os.path.dirname(__file__))
+libs_path = os.path.join(dir_name, '..', 'libs')
+sys.path.insert(0, libs_path)
+from settings import Settings
+
+class TestSettings(unittest.TestCase):
+
+ def test_basic(self):
+ settings = Settings()
+ settings['test0'] = 'hello'
+ settings['test1'] = 10
+ settings['test2'] = [0, 2, 3]
+ self.assertEqual(settings.get('test3', 3), 3)
+ self.assertEqual(settings.save(), True)
+
+ settings.load()
+ self.assertEqual(settings.get('test0'), 'hello')
+ self.assertEqual(settings.get('test1'), 10)
+
+ settings.reset()
+
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/labelImg-master/tests/test_stringBundle.py b/labelImg-master/tests/test_stringBundle.py
new file mode 100644
index 0000000..d4eb7f2
--- /dev/null
+++ b/labelImg-master/tests/test_stringBundle.py
@@ -0,0 +1,29 @@
+import os
+import sys
+import unittest
+import resources
+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')
+
+ def test_setInvaleLocaleToEnv_printErrorMsg(self):
+ prev_lc = os.environ['LC_ALL']
+ prev_lang = os.environ['LANG']
+ os.environ['LC_ALL'] = 'UTF-8'
+ os.environ['LANG'] = 'UTF-8'
+ strBundle = StringBundle.getBundle()
+ self.assertEqual(strBundle.getString("openDir"), 'Open Dir', 'Fail to load the default bundle')
+ os.environ['LC_ALL'] = prev_lc
+ os.environ['LANG'] = prev_lang
+
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/labelImg-master/tests/test_utils.py b/labelImg-master/tests/test_utils.py
new file mode 100644
index 0000000..1b0a6c4
--- /dev/null
+++ b/labelImg-master/tests/test_utils.py
@@ -0,0 +1,22 @@
+import os
+import sys
+import unittest
+from libs.utils import struct, newAction, newIcon, addActions, fmtShortcut, generateColorByText, natural_sort
+
+class TestUtils(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)
+
+ def test_nautalSort_noError(self):
+ l1 = ['f1', 'f11', 'f3' ]
+ exptected_l1 = ['f1', 'f3', 'f11']
+ natural_sort(l1)
+ for idx, val in enumerate(l1):
+ self.assertTrue(val == exptected_l1[idx])
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/labelImg-master/tests/臉書.jpg b/labelImg-master/tests/臉書.jpg
new file mode 100644
index 0000000..f600309
Binary files /dev/null and b/labelImg-master/tests/臉書.jpg differ
diff --git a/res/1.mp4 b/res/1.mp4
new file mode 100644
index 0000000..b2722ad
Binary files /dev/null and b/res/1.mp4 differ
diff --git a/res/2.mp4 b/res/2.mp4
new file mode 100644
index 0000000..966620a
Binary files /dev/null and b/res/2.mp4 differ
diff --git a/res/video.mp4 b/res/video.mp4
new file mode 100644
index 0000000..82e80ee
Binary files /dev/null and b/res/video.mp4 differ
diff --git a/runs/detect/predict/test.avi b/runs/detect/predict/test.avi
new file mode 100644
index 0000000..ad5eb76
Binary files /dev/null and b/runs/detect/predict/test.avi differ
diff --git a/runs/detect/predict10/1.avi b/runs/detect/predict10/1.avi
new file mode 100644
index 0000000..091a27d
Binary files /dev/null and b/runs/detect/predict10/1.avi differ
diff --git a/runs/detect/predict11/2.avi b/runs/detect/predict11/2.avi
new file mode 100644
index 0000000..453115f
Binary files /dev/null and b/runs/detect/predict11/2.avi differ
diff --git a/runs/detect/predict12/2.avi b/runs/detect/predict12/2.avi
new file mode 100644
index 0000000..453115f
Binary files /dev/null and b/runs/detect/predict12/2.avi differ
diff --git a/runs/detect/predict13/2.avi b/runs/detect/predict13/2.avi
new file mode 100644
index 0000000..453115f
Binary files /dev/null and b/runs/detect/predict13/2.avi differ
diff --git a/runs/detect/predict14/2.avi b/runs/detect/predict14/2.avi
new file mode 100644
index 0000000..05d0392
Binary files /dev/null and b/runs/detect/predict14/2.avi differ
diff --git a/runs/detect/predict15/2.avi b/runs/detect/predict15/2.avi
new file mode 100644
index 0000000..453115f
Binary files /dev/null and b/runs/detect/predict15/2.avi differ
diff --git a/runs/detect/predict16/2.avi b/runs/detect/predict16/2.avi
new file mode 100644
index 0000000..453115f
Binary files /dev/null and b/runs/detect/predict16/2.avi differ
diff --git a/runs/detect/predict2/test.avi b/runs/detect/predict2/test.avi
new file mode 100644
index 0000000..ad5eb76
Binary files /dev/null and b/runs/detect/predict2/test.avi differ
diff --git a/runs/detect/predict3/test.avi b/runs/detect/predict3/test.avi
new file mode 100644
index 0000000..04db8ad
Binary files /dev/null and b/runs/detect/predict3/test.avi differ
diff --git a/runs/detect/predict4/video.avi b/runs/detect/predict4/video.avi
new file mode 100644
index 0000000..75ed7ac
Binary files /dev/null and b/runs/detect/predict4/video.avi differ
diff --git a/runs/detect/predict5/video.avi b/runs/detect/predict5/video.avi
new file mode 100644
index 0000000..75ed7ac
Binary files /dev/null and b/runs/detect/predict5/video.avi differ
diff --git a/runs/detect/predict6/video.avi b/runs/detect/predict6/video.avi
new file mode 100644
index 0000000..13d65db
Binary files /dev/null and b/runs/detect/predict6/video.avi differ
diff --git a/runs/detect/predict7/video.avi b/runs/detect/predict7/video.avi
new file mode 100644
index 0000000..72d4342
Binary files /dev/null and b/runs/detect/predict7/video.avi differ
diff --git a/runs/detect/predict8/video.avi b/runs/detect/predict8/video.avi
new file mode 100644
index 0000000..2e4264e
Binary files /dev/null and b/runs/detect/predict8/video.avi differ
diff --git a/runs/detect/predict9/1.avi b/runs/detect/predict9/1.avi
new file mode 100644
index 0000000..091a27d
Binary files /dev/null and b/runs/detect/predict9/1.avi differ
diff --git a/runs/detect/train/args.yaml b/runs/detect/train/args.yaml
new file mode 100644
index 0000000..6eb5cb3
--- /dev/null
+++ b/runs/detect/train/args.yaml
@@ -0,0 +1,105 @@
+task: detect
+mode: train
+model: yolov8n.pt
+data: data.yaml
+epochs: 50
+time: null
+patience: 100
+batch: 8
+imgsz: 640
+save: true
+save_period: -1
+cache: false
+device: cpu
+workers: 8
+project: null
+name: train
+exist_ok: false
+pretrained: true
+optimizer: auto
+verbose: true
+seed: 0
+deterministic: true
+single_cls: false
+rect: false
+cos_lr: false
+close_mosaic: 10
+resume: false
+amp: true
+fraction: 1.0
+profile: false
+freeze: null
+multi_scale: false
+overlap_mask: true
+mask_ratio: 4
+dropout: 0.0
+val: true
+split: val
+save_json: false
+conf: null
+iou: 0.7
+max_det: 300
+half: false
+dnn: false
+plots: true
+source: null
+vid_stride: 1
+stream_buffer: false
+visualize: false
+augment: false
+agnostic_nms: false
+classes: null
+retina_masks: false
+embed: null
+show: false
+save_frames: false
+save_txt: false
+save_conf: false
+save_crop: false
+show_labels: true
+show_conf: true
+show_boxes: true
+line_width: null
+format: torchscript
+keras: false
+optimize: false
+int8: false
+dynamic: false
+simplify: true
+opset: null
+workspace: null
+nms: false
+lr0: 0.01
+lrf: 0.01
+momentum: 0.937
+weight_decay: 0.0005
+warmup_epochs: 3.0
+warmup_momentum: 0.8
+warmup_bias_lr: 0.1
+box: 7.5
+cls: 0.5
+dfl: 1.5
+pose: 12.0
+kobj: 1.0
+nbs: 64
+hsv_h: 0.015
+hsv_s: 0.7
+hsv_v: 0.4
+degrees: 0.0
+translate: 0.1
+scale: 0.5
+shear: 0.0
+perspective: 0.0
+flipud: 0.0
+fliplr: 0.5
+bgr: 0.0
+mosaic: 1.0
+mixup: 0.0
+cutmix: 0.0
+copy_paste: 0.0
+copy_paste_mode: flip
+auto_augment: randaugment
+erasing: 0.4
+cfg: null
+tracker: botsort.yaml
+save_dir: C:\workspace\le-yolo\runs\detect\train
diff --git a/runs/detect/train2/args.yaml b/runs/detect/train2/args.yaml
new file mode 100644
index 0000000..f914118
--- /dev/null
+++ b/runs/detect/train2/args.yaml
@@ -0,0 +1,105 @@
+task: detect
+mode: train
+model: yolov8n.pt
+data: data.yaml
+epochs: 50
+time: null
+patience: 100
+batch: 8
+imgsz: 640
+save: true
+save_period: -1
+cache: false
+device: cpu
+workers: 8
+project: null
+name: train2
+exist_ok: false
+pretrained: true
+optimizer: auto
+verbose: true
+seed: 0
+deterministic: true
+single_cls: false
+rect: false
+cos_lr: false
+close_mosaic: 10
+resume: false
+amp: true
+fraction: 1.0
+profile: false
+freeze: null
+multi_scale: false
+overlap_mask: true
+mask_ratio: 4
+dropout: 0.0
+val: true
+split: val
+save_json: false
+conf: null
+iou: 0.7
+max_det: 300
+half: false
+dnn: false
+plots: true
+source: null
+vid_stride: 1
+stream_buffer: false
+visualize: false
+augment: false
+agnostic_nms: false
+classes: null
+retina_masks: false
+embed: null
+show: false
+save_frames: false
+save_txt: false
+save_conf: false
+save_crop: false
+show_labels: true
+show_conf: true
+show_boxes: true
+line_width: null
+format: torchscript
+keras: false
+optimize: false
+int8: false
+dynamic: false
+simplify: true
+opset: null
+workspace: null
+nms: false
+lr0: 0.01
+lrf: 0.01
+momentum: 0.937
+weight_decay: 0.0005
+warmup_epochs: 3.0
+warmup_momentum: 0.8
+warmup_bias_lr: 0.1
+box: 7.5
+cls: 0.5
+dfl: 1.5
+pose: 12.0
+kobj: 1.0
+nbs: 64
+hsv_h: 0.015
+hsv_s: 0.7
+hsv_v: 0.4
+degrees: 0.0
+translate: 0.1
+scale: 0.5
+shear: 0.0
+perspective: 0.0
+flipud: 0.0
+fliplr: 0.5
+bgr: 0.0
+mosaic: 1.0
+mixup: 0.0
+cutmix: 0.0
+copy_paste: 0.0
+copy_paste_mode: flip
+auto_augment: randaugment
+erasing: 0.4
+cfg: null
+tracker: botsort.yaml
+save_dir: C:\workspace\le-yolo\runs\detect\train2
diff --git a/runs/detect/train3/F1_curve.png b/runs/detect/train3/F1_curve.png
new file mode 100644
index 0000000..74dcb96
Binary files /dev/null and b/runs/detect/train3/F1_curve.png differ
diff --git a/runs/detect/train3/PR_curve.png b/runs/detect/train3/PR_curve.png
new file mode 100644
index 0000000..025142d
Binary files /dev/null and b/runs/detect/train3/PR_curve.png differ
diff --git a/runs/detect/train3/P_curve.png b/runs/detect/train3/P_curve.png
new file mode 100644
index 0000000..fa09717
Binary files /dev/null and b/runs/detect/train3/P_curve.png differ
diff --git a/runs/detect/train3/R_curve.png b/runs/detect/train3/R_curve.png
new file mode 100644
index 0000000..5904085
Binary files /dev/null and b/runs/detect/train3/R_curve.png differ
diff --git a/runs/detect/train3/args.yaml b/runs/detect/train3/args.yaml
new file mode 100644
index 0000000..edd8e4f
--- /dev/null
+++ b/runs/detect/train3/args.yaml
@@ -0,0 +1,105 @@
+task: detect
+mode: train
+model: yolov8n.pt
+data: data.yaml
+epochs: 50
+time: null
+patience: 100
+batch: 8
+imgsz: 640
+save: true
+save_period: -1
+cache: false
+device: cpu
+workers: 8
+project: null
+name: train3
+exist_ok: false
+pretrained: true
+optimizer: auto
+verbose: true
+seed: 0
+deterministic: true
+single_cls: false
+rect: false
+cos_lr: false
+close_mosaic: 10
+resume: false
+amp: true
+fraction: 1.0
+profile: false
+freeze: null
+multi_scale: false
+overlap_mask: true
+mask_ratio: 4
+dropout: 0.0
+val: true
+split: val
+save_json: false
+conf: null
+iou: 0.7
+max_det: 300
+half: false
+dnn: false
+plots: true
+source: null
+vid_stride: 1
+stream_buffer: false
+visualize: false
+augment: false
+agnostic_nms: false
+classes: null
+retina_masks: false
+embed: null
+show: false
+save_frames: false
+save_txt: false
+save_conf: false
+save_crop: false
+show_labels: true
+show_conf: true
+show_boxes: true
+line_width: null
+format: torchscript
+keras: false
+optimize: false
+int8: false
+dynamic: false
+simplify: true
+opset: null
+workspace: null
+nms: false
+lr0: 0.01
+lrf: 0.01
+momentum: 0.937
+weight_decay: 0.0005
+warmup_epochs: 3.0
+warmup_momentum: 0.8
+warmup_bias_lr: 0.1
+box: 7.5
+cls: 0.5
+dfl: 1.5
+pose: 12.0
+kobj: 1.0
+nbs: 64
+hsv_h: 0.015
+hsv_s: 0.7
+hsv_v: 0.4
+degrees: 0.0
+translate: 0.1
+scale: 0.5
+shear: 0.0
+perspective: 0.0
+flipud: 0.0
+fliplr: 0.5
+bgr: 0.0
+mosaic: 1.0
+mixup: 0.0
+cutmix: 0.0
+copy_paste: 0.0
+copy_paste_mode: flip
+auto_augment: randaugment
+erasing: 0.4
+cfg: null
+tracker: botsort.yaml
+save_dir: C:\workspace\le-yolo\runs\detect\train3
diff --git a/runs/detect/train3/confusion_matrix.png b/runs/detect/train3/confusion_matrix.png
new file mode 100644
index 0000000..fbc20c5
Binary files /dev/null and b/runs/detect/train3/confusion_matrix.png differ
diff --git a/runs/detect/train3/confusion_matrix_normalized.png b/runs/detect/train3/confusion_matrix_normalized.png
new file mode 100644
index 0000000..0d37ba0
Binary files /dev/null and b/runs/detect/train3/confusion_matrix_normalized.png differ
diff --git a/runs/detect/train3/labels.jpg b/runs/detect/train3/labels.jpg
new file mode 100644
index 0000000..e572540
Binary files /dev/null and b/runs/detect/train3/labels.jpg differ
diff --git a/runs/detect/train3/results.csv b/runs/detect/train3/results.csv
new file mode 100644
index 0000000..4d3283f
--- /dev/null
+++ b/runs/detect/train3/results.csv
@@ -0,0 +1,51 @@
+epoch,time,train/box_loss,train/cls_loss,train/dfl_loss,metrics/precision(B),metrics/recall(B),metrics/mAP50(B),metrics/mAP50-95(B),val/box_loss,val/cls_loss,val/dfl_loss,lr/pg0,lr/pg1,lr/pg2
+1,3.15281,1.10022,3.14022,1.2255,0.00333,0.88889,0.32571,0.29217,0.34034,3.11736,0.87949,0,0,0
+2,5.68083,1.05538,3.31638,1.20438,0.00333,0.88889,0.37584,0.34515,0.32069,3.10227,0.87873,1.9604e-05,1.9604e-05,1.9604e-05
+3,8.15579,0.94798,3.16007,1.04138,0.00333,0.88889,0.63964,0.58705,0.30532,3.09222,0.87571,3.8416e-05,3.8416e-05,3.8416e-05
+4,10.715,0.62586,3.14025,1.02961,0.00333,0.88889,0.72823,0.65649,0.30679,3.0871,0.86894,5.6436e-05,5.6436e-05,5.6436e-05
+5,13.1396,0.86929,2.84358,1.05884,0.00333,0.88889,0.8302,0.75392,0.29772,3.107,0.86258,7.3664e-05,7.3664e-05,7.3664e-05
+6,15.5778,0.76563,2.79469,1.06727,0.00333,0.88889,0.8632,0.7838,0.32053,3.06857,0.86553,9.01e-05,9.01e-05,9.01e-05
+7,17.9613,0.64827,2.61013,1.00925,0.00333,0.88889,0.8852,0.81237,0.34311,3.04094,0.86747,0.000105744,0.000105744,0.000105744
+8,20.3928,0.72143,2.1563,0.96856,0.00333,0.88889,0.8852,0.79883,0.36531,3.0106,0.86746,0.000120596,0.000120596,0.000120596
+9,22.7815,0.57035,1.99286,0.90814,0.00333,0.88889,0.8852,0.79883,0.36531,3.0106,0.86746,0.000134656,0.000134656,0.000134656
+10,25.2309,0.69121,1.91028,1.01505,0.00333,0.88889,0.8852,0.79883,0.3861,2.97352,0.87094,0.000147924,0.000147924,0.000147924
+11,27.6395,0.88369,1.77167,1.19643,0.00333,0.88889,0.8852,0.79883,0.3861,2.97352,0.87094,0.0001604,0.0001604,0.0001604
+12,30.1606,0.60341,1.56535,0.93827,0.00333,0.88889,0.8852,0.78783,0.40306,2.94302,0.86504,0.000172084,0.000172084,0.000172084
+13,32.6564,0.67771,1.58706,0.9812,0.00333,0.88889,0.8852,0.78783,0.40306,2.94302,0.86504,0.000182976,0.000182976,0.000182976
+14,35.1385,0.95266,1.55727,1.14234,0.00333,0.88889,0.8852,0.7808,0.42724,2.8288,0.86476,0.000193076,0.000193076,0.000193076
+15,37.5563,0.59165,1.64354,0.83242,0.00333,0.88889,0.8852,0.7808,0.42724,2.8288,0.86476,0.000202384,0.000202384,0.000202384
+16,40.0186,0.88703,1.38812,1.05784,0.93683,0.88889,0.8852,0.77212,0.42412,2.73737,0.86377,0.0002109,0.0002109,0.0002109
+17,42.4647,0.59867,1.32607,1.00033,0.93683,0.88889,0.8852,0.77212,0.42412,2.73737,0.86377,0.000218624,0.000218624,0.000218624
+18,44.9617,0.76615,1.33177,1.07213,1,0.83494,0.8852,0.74988,0.43705,2.65651,0.86286,0.000225556,0.000225556,0.000225556
+19,47.3372,0.52303,1.70157,0.8597,1,0.83494,0.8852,0.74988,0.43705,2.65651,0.86286,0.000231696,0.000231696,0.000231696
+20,49.8584,0.67289,2.50893,0.92172,1,0.81512,0.8852,0.79322,0.45192,2.58511,0.86661,0.000237044,0.000237044,0.000237044
+21,52.3133,0.72742,1.44767,1.03485,1,0.81512,0.8852,0.79322,0.45192,2.58511,0.86661,0.0002416,0.0002416,0.0002416
+22,54.8088,0.68092,1.43855,0.91056,1,0.81606,0.8852,0.76464,0.45326,2.61417,0.87402,0.000245364,0.000245364,0.000245364
+23,57.2028,0.52225,1.19582,0.86461,1,0.81606,0.8852,0.76464,0.45326,2.61417,0.87402,0.000248336,0.000248336,0.000248336
+24,59.6553,0.67045,1.53593,0.99181,1,0.81606,0.8852,0.76464,0.45326,2.61417,0.87402,0.000250516,0.000250516,0.000250516
+25,62.237,0.64146,1.52373,1.03151,1,0.78759,0.8852,0.79185,0.48735,2.70008,0.8891,0.000251904,0.000251904,0.000251904
+26,64.7911,0.91542,1.33012,1.04646,1,0.78759,0.8852,0.79185,0.48735,2.70008,0.8891,0.0002525,0.0002525,0.0002525
+27,67.3228,0.88468,1.28592,1.09112,1,0.78759,0.8852,0.79185,0.48735,2.70008,0.8891,0.000252304,0.000252304,0.000252304
+28,69.8307,0.76852,1.27101,0.95144,1,0.63539,0.8852,0.78625,0.48011,2.75817,0.89035,0.000251316,0.000251316,0.000251316
+29,72.4024,0.87868,1.35438,1.03544,1,0.63539,0.8852,0.78625,0.48011,2.75817,0.89035,0.000249536,0.000249536,0.000249536
+30,74.8523,0.70824,1.13341,0.91661,1,0.63539,0.8852,0.78625,0.48011,2.75817,0.89035,0.000246964,0.000246964,0.000246964
+31,77.3209,0.72848,1.43246,1.02215,1,0.48756,0.8852,0.8292,0.42791,2.78051,0.88405,0.0002436,0.0002436,0.0002436
+32,79.9742,0.9278,1.75649,1.28212,1,0.48756,0.8852,0.8292,0.42791,2.78051,0.88405,0.000239444,0.000239444,0.000239444
+33,82.3529,0.57168,1.12277,0.86624,1,0.48756,0.8852,0.8292,0.42791,2.78051,0.88405,0.000234496,0.000234496,0.000234496
+34,85.0071,0.48654,1.12103,0.87536,1,0.49647,0.8852,0.82658,0.37727,2.78461,0.87961,0.000228756,0.000228756,0.000228756
+35,87.3931,0.64266,1.23656,1.04788,1,0.49647,0.8852,0.82658,0.37727,2.78461,0.87961,0.000222224,0.000222224,0.000222224
+36,89.7618,0.6237,1.33377,0.97587,1,0.49647,0.8852,0.82658,0.37727,2.78461,0.87961,0.0002149,0.0002149,0.0002149
+37,92.2967,0.76488,1.68602,0.96816,1,0.49647,0.8852,0.82658,0.37727,2.78461,0.87961,0.000206784,0.000206784,0.000206784
+38,94.8909,0.80571,1.48616,1.12267,1,0.66807,0.8852,0.86322,0.33862,2.77101,0.87143,0.000197876,0.000197876,0.000197876
+39,97.3842,0.54691,1.20211,0.91226,1,0.66807,0.8852,0.86322,0.33862,2.77101,0.87143,0.000188176,0.000188176,0.000188176
+40,100.002,0.67186,1.2534,0.87116,1,0.66807,0.8852,0.86322,0.33862,2.77101,0.87143,0.000177684,0.000177684,0.000177684
+41,102.416,0.43224,1.31894,0.78367,1,0.66807,0.8852,0.86322,0.33862,2.77101,0.87143,0.0001664,0.0001664,0.0001664
+42,104.939,0.3859,1.4498,0.84241,0.99496,0.88889,0.8852,0.86322,0.36192,2.6923,0.86417,0.000154324,0.000154324,0.000154324
+43,107.366,0.43439,1.36382,0.83218,0.99496,0.88889,0.8852,0.86322,0.36192,2.6923,0.86417,0.000141456,0.000141456,0.000141456
+44,109.79,0.39154,1.27984,0.74676,0.99496,0.88889,0.8852,0.86322,0.36192,2.6923,0.86417,0.000127796,0.000127796,0.000127796
+45,112.242,0.4359,1.38331,0.93381,0.99496,0.88889,0.8852,0.86322,0.36192,2.6923,0.86417,0.000113344,0.000113344,0.000113344
+46,114.805,0.37316,1.30903,0.88283,0.95971,0.88889,0.8852,0.84438,0.35247,2.59497,0.85796,9.81e-05,9.81e-05,9.81e-05
+47,117.219,0.44499,1.36407,0.76849,0.95971,0.88889,0.8852,0.84438,0.35247,2.59497,0.85796,8.2064e-05,8.2064e-05,8.2064e-05
+48,119.642,0.47196,1.32602,0.86557,0.95971,0.88889,0.8852,0.84438,0.35247,2.59497,0.85796,6.5236e-05,6.5236e-05,6.5236e-05
+49,122.033,0.36545,1.31759,0.79923,0.95971,0.88889,0.8852,0.84438,0.35247,2.59497,0.85796,4.7616e-05,4.7616e-05,4.7616e-05
+50,124.471,0.3751,1.37416,0.70301,0.96003,0.88889,0.8852,0.84438,0.34377,2.50209,0.85536,2.9204e-05,2.9204e-05,2.9204e-05
diff --git a/runs/detect/train3/results.png b/runs/detect/train3/results.png
new file mode 100644
index 0000000..b224862
Binary files /dev/null and b/runs/detect/train3/results.png differ
diff --git a/runs/detect/train3/train_batch0.jpg b/runs/detect/train3/train_batch0.jpg
new file mode 100644
index 0000000..a790255
Binary files /dev/null and b/runs/detect/train3/train_batch0.jpg differ
diff --git a/runs/detect/train3/train_batch1.jpg b/runs/detect/train3/train_batch1.jpg
new file mode 100644
index 0000000..7bccf1d
Binary files /dev/null and b/runs/detect/train3/train_batch1.jpg differ
diff --git a/runs/detect/train3/train_batch2.jpg b/runs/detect/train3/train_batch2.jpg
new file mode 100644
index 0000000..db0de92
Binary files /dev/null and b/runs/detect/train3/train_batch2.jpg differ
diff --git a/runs/detect/train3/train_batch40.jpg b/runs/detect/train3/train_batch40.jpg
new file mode 100644
index 0000000..6fe7149
Binary files /dev/null and b/runs/detect/train3/train_batch40.jpg differ
diff --git a/runs/detect/train3/train_batch41.jpg b/runs/detect/train3/train_batch41.jpg
new file mode 100644
index 0000000..c66b8d8
Binary files /dev/null and b/runs/detect/train3/train_batch41.jpg differ
diff --git a/runs/detect/train3/train_batch42.jpg b/runs/detect/train3/train_batch42.jpg
new file mode 100644
index 0000000..6107a07
Binary files /dev/null and b/runs/detect/train3/train_batch42.jpg differ
diff --git a/runs/detect/train3/val_batch0_labels.jpg b/runs/detect/train3/val_batch0_labels.jpg
new file mode 100644
index 0000000..5dded87
Binary files /dev/null and b/runs/detect/train3/val_batch0_labels.jpg differ
diff --git a/runs/detect/train3/val_batch0_pred.jpg b/runs/detect/train3/val_batch0_pred.jpg
new file mode 100644
index 0000000..41f8dd9
Binary files /dev/null and b/runs/detect/train3/val_batch0_pred.jpg differ
diff --git a/runs/detect/train3/weights/best.pt b/runs/detect/train3/weights/best.pt
new file mode 100644
index 0000000..350a384
Binary files /dev/null and b/runs/detect/train3/weights/best.pt differ
diff --git a/runs/detect/train3/weights/last.pt b/runs/detect/train3/weights/last.pt
new file mode 100644
index 0000000..dc8be8d
Binary files /dev/null and b/runs/detect/train3/weights/last.pt differ
diff --git a/runs/detect/train4/args.yaml b/runs/detect/train4/args.yaml
new file mode 100644
index 0000000..88ca9f4
--- /dev/null
+++ b/runs/detect/train4/args.yaml
@@ -0,0 +1,105 @@
+task: detect
+mode: train
+model: yolov8n.pt
+data: data.yaml
+epochs: 200
+time: null
+patience: 100
+batch: 8
+imgsz: 640
+save: true
+save_period: -1
+cache: false
+device: cpu
+workers: 8
+project: null
+name: train4
+exist_ok: false
+pretrained: true
+optimizer: auto
+verbose: true
+seed: 0
+deterministic: true
+single_cls: false
+rect: false
+cos_lr: false
+close_mosaic: 10
+resume: false
+amp: true
+fraction: 1.0
+profile: false
+freeze: null
+multi_scale: false
+overlap_mask: true
+mask_ratio: 4
+dropout: 0.0
+val: true
+split: val
+save_json: false
+conf: null
+iou: 0.7
+max_det: 300
+half: false
+dnn: false
+plots: true
+source: null
+vid_stride: 1
+stream_buffer: false
+visualize: false
+augment: false
+agnostic_nms: false
+classes: null
+retina_masks: false
+embed: null
+show: false
+save_frames: false
+save_txt: false
+save_conf: false
+save_crop: false
+show_labels: true
+show_conf: true
+show_boxes: true
+line_width: null
+format: torchscript
+keras: false
+optimize: false
+int8: false
+dynamic: false
+simplify: true
+opset: null
+workspace: null
+nms: false
+lr0: 0.01
+lrf: 0.01
+momentum: 0.937
+weight_decay: 0.0005
+warmup_epochs: 3.0
+warmup_momentum: 0.8
+warmup_bias_lr: 0.1
+box: 7.5
+cls: 0.5
+dfl: 1.5
+pose: 12.0
+kobj: 1.0
+nbs: 64
+hsv_h: 0.015
+hsv_s: 0.7
+hsv_v: 0.4
+degrees: 0.0
+translate: 0.1
+scale: 0.5
+shear: 0.0
+perspective: 0.0
+flipud: 0.0
+fliplr: 0.5
+bgr: 0.0
+mosaic: 1.0
+mixup: 0.0
+cutmix: 0.0
+copy_paste: 0.0
+copy_paste_mode: flip
+auto_augment: randaugment
+erasing: 0.4
+cfg: null
+tracker: botsort.yaml
+save_dir: C:\workspace\le-yolo\runs\detect\train4
diff --git a/runs/detect/train4/labels.jpg b/runs/detect/train4/labels.jpg
new file mode 100644
index 0000000..e572540
Binary files /dev/null and b/runs/detect/train4/labels.jpg differ
diff --git a/runs/detect/train5/F1_curve.png b/runs/detect/train5/F1_curve.png
new file mode 100644
index 0000000..344a3eb
Binary files /dev/null and b/runs/detect/train5/F1_curve.png differ
diff --git a/runs/detect/train5/PR_curve.png b/runs/detect/train5/PR_curve.png
new file mode 100644
index 0000000..025142d
Binary files /dev/null and b/runs/detect/train5/PR_curve.png differ
diff --git a/runs/detect/train5/P_curve.png b/runs/detect/train5/P_curve.png
new file mode 100644
index 0000000..a831e9a
Binary files /dev/null and b/runs/detect/train5/P_curve.png differ
diff --git a/runs/detect/train5/R_curve.png b/runs/detect/train5/R_curve.png
new file mode 100644
index 0000000..5e8d4c2
Binary files /dev/null and b/runs/detect/train5/R_curve.png differ
diff --git a/runs/detect/train5/args.yaml b/runs/detect/train5/args.yaml
new file mode 100644
index 0000000..363ce0f
--- /dev/null
+++ b/runs/detect/train5/args.yaml
@@ -0,0 +1,105 @@
+task: detect
+mode: train
+model: C:\workspace\le-yolo\runs\detect\train3\weights\last.pt
+data: data.yaml
+epochs: 200
+time: null
+patience: 100
+batch: 8
+imgsz: 640
+save: true
+save_period: -1
+cache: false
+device: cpu
+workers: 8
+project: null
+name: train5
+exist_ok: false
+pretrained: true
+optimizer: auto
+verbose: true
+seed: 0
+deterministic: true
+single_cls: false
+rect: false
+cos_lr: false
+close_mosaic: 10
+resume: false
+amp: true
+fraction: 1.0
+profile: false
+freeze: null
+multi_scale: false
+overlap_mask: true
+mask_ratio: 4
+dropout: 0.0
+val: true
+split: val
+save_json: false
+conf: null
+iou: 0.7
+max_det: 300
+half: false
+dnn: false
+plots: true
+source: null
+vid_stride: 1
+stream_buffer: false
+visualize: false
+augment: false
+agnostic_nms: false
+classes: null
+retina_masks: false
+embed: null
+show: false
+save_frames: false
+save_txt: false
+save_conf: false
+save_crop: false
+show_labels: true
+show_conf: true
+show_boxes: true
+line_width: null
+format: torchscript
+keras: false
+optimize: false
+int8: false
+dynamic: false
+simplify: true
+opset: null
+workspace: null
+nms: false
+lr0: 0.01
+lrf: 0.01
+momentum: 0.937
+weight_decay: 0.0005
+warmup_epochs: 3.0
+warmup_momentum: 0.8
+warmup_bias_lr: 0.1
+box: 7.5
+cls: 0.5
+dfl: 1.5
+pose: 12.0
+kobj: 1.0
+nbs: 64
+hsv_h: 0.015
+hsv_s: 0.7
+hsv_v: 0.4
+degrees: 0.0
+translate: 0.1
+scale: 0.5
+shear: 0.0
+perspective: 0.0
+flipud: 0.0
+fliplr: 0.5
+bgr: 0.0
+mosaic: 1.0
+mixup: 0.0
+cutmix: 0.0
+copy_paste: 0.0
+copy_paste_mode: flip
+auto_augment: randaugment
+erasing: 0.4
+cfg: null
+tracker: botsort.yaml
+save_dir: C:\workspace\le-yolo\runs\detect\train5
diff --git a/runs/detect/train5/confusion_matrix.png b/runs/detect/train5/confusion_matrix.png
new file mode 100644
index 0000000..2a6cc84
Binary files /dev/null and b/runs/detect/train5/confusion_matrix.png differ
diff --git a/runs/detect/train5/confusion_matrix_normalized.png b/runs/detect/train5/confusion_matrix_normalized.png
new file mode 100644
index 0000000..a324049
Binary files /dev/null and b/runs/detect/train5/confusion_matrix_normalized.png differ
diff --git a/runs/detect/train5/labels.jpg b/runs/detect/train5/labels.jpg
new file mode 100644
index 0000000..e572540
Binary files /dev/null and b/runs/detect/train5/labels.jpg differ
diff --git a/runs/detect/train5/results.csv b/runs/detect/train5/results.csv
new file mode 100644
index 0000000..0a7d6ee
--- /dev/null
+++ b/runs/detect/train5/results.csv
@@ -0,0 +1,103 @@
+epoch,time,train/box_loss,train/cls_loss,train/dfl_loss,metrics/precision(B),metrics/recall(B),metrics/mAP50(B),metrics/mAP50-95(B),val/box_loss,val/cls_loss,val/dfl_loss,lr/pg0,lr/pg1,lr/pg2
+1,2.67339,0.61832,1.14057,0.96964,0.96233,0.88889,0.8852,0.84438,0.34289,2.48256,0.85487,0,0,0
+2,5.09004,0.45762,1.18623,0.84615,0.96579,0.88889,0.8852,0.86322,0.32791,2.46007,0.85592,1.9901e-05,1.9901e-05,1.9901e-05
+3,7.60049,0.4178,1.17018,0.82961,0.96868,0.88889,0.8852,0.86322,0.32163,2.4327,0.85697,3.9604e-05,3.9604e-05,3.9604e-05
+4,9.98038,0.48083,1.22269,0.89059,0.96965,0.88889,0.8852,0.86047,0.31544,2.3946,0.85823,5.9109e-05,5.9109e-05,5.9109e-05
+5,12.3866,0.6278,1.10782,0.90417,0.97657,0.88889,0.8852,0.86047,0.32674,2.35093,0.86008,7.8416e-05,7.8416e-05,7.8416e-05
+6,14.8973,0.52602,0.91438,0.90707,0.98043,0.88889,0.8852,0.84444,0.34292,2.31656,0.86257,9.7525e-05,9.7525e-05,9.7525e-05
+7,17.2429,0.49335,1.07923,0.87905,0.98309,0.88889,0.8852,0.84628,0.34967,2.25899,0.86559,0.000116436,0.000116436,0.000116436
+8,19.6462,0.61429,0.96989,0.91982,0.98432,0.88889,0.8852,0.82474,0.34933,2.19648,0.8656,0.000135149,0.000135149,0.000135149
+9,21.982,0.50596,1.10315,0.92002,0.98432,0.88889,0.8852,0.82474,0.34933,2.19648,0.8656,0.000153664,0.000153664,0.000153664
+10,24.3517,0.59467,0.94249,0.9794,0.98466,0.88889,0.8852,0.84261,0.34808,2.08523,0.8623,0.000171981,0.000171981,0.000171981
+11,26.6746,0.69522,1.21661,1.09188,0.98466,0.88889,0.8852,0.84261,0.34808,2.08523,0.8623,0.0001901,0.0001901,0.0001901
+12,29.0535,0.48624,0.98387,0.88991,0.98579,0.88889,0.8852,0.83436,0.33667,1.96997,0.85881,0.000208021,0.000208021,0.000208021
+13,31.4143,0.55648,0.92089,0.93619,0.98579,0.88889,0.8852,0.83436,0.33667,1.96997,0.85881,0.000225744,0.000225744,0.000225744
+14,33.8123,0.74386,1.10125,0.97377,0.9828,0.88889,0.8852,0.83711,0.35397,1.97892,0.85376,0.000243269,0.000243269,0.000243269
+15,36.1456,0.55277,1.25464,0.84283,0.9828,0.88889,0.8852,0.83711,0.35397,1.97892,0.85376,0.000260596,0.000260596,0.000260596
+16,38.531,0.54716,0.9451,0.87762,0.97588,0.88889,0.8852,0.83095,0.38255,2.17418,0.85391,0.000277725,0.000277725,0.000277725
+17,40.8926,0.58252,1.02719,0.98344,0.97588,0.88889,0.8852,0.83095,0.38255,2.17418,0.85391,0.000294656,0.000294656,0.000294656
+18,43.3687,0.6243,1.0316,0.94663,0.96132,0.88889,0.8852,0.83095,0.39044,2.38818,0.85494,0.000311389,0.000311389,0.000311389
+19,45.7729,0.50991,1.2308,0.91477,0.96132,0.88889,0.8852,0.83095,0.39044,2.38818,0.85494,0.000327924,0.000327924,0.000327924
+20,48.1666,0.58582,1.58938,0.89876,0.9945,0.88889,0.8852,0.81727,0.41818,2.53039,0.85505,0.000344261,0.000344261,0.000344261
+21,50.5222,0.63541,1.13028,0.97161,0.9945,0.88889,0.8852,0.81727,0.41818,2.53039,0.85505,0.0003604,0.0003604,0.0003604
+22,52.8816,0.56815,1.11015,0.86219,0.97676,0.88889,0.8852,0.82472,0.41382,2.50032,0.8548,0.000376341,0.000376341,0.000376341
+23,55.241,0.4446,1.04105,0.83186,0.97676,0.88889,0.8852,0.82472,0.41382,2.50032,0.8548,0.000392084,0.000392084,0.000392084
+24,57.6428,0.81079,1.48197,1.17129,0.97676,0.88889,0.8852,0.82472,0.41382,2.50032,0.8548,0.000407629,0.000407629,0.000407629
+25,60.0587,0.60764,1.29201,0.94437,0.97093,0.88889,0.8852,0.82336,0.38947,2.26571,0.85077,0.000422976,0.000422976,0.000422976
+26,62.4506,0.74966,1.05885,1.00084,0.97093,0.88889,0.8852,0.82336,0.38947,2.26571,0.85077,0.000438125,0.000438125,0.000438125
+27,64.8378,0.76867,1.13755,1.02766,0.97093,0.88889,0.8852,0.82336,0.38947,2.26571,0.85077,0.000453076,0.000453076,0.000453076
+28,67.2118,0.82856,1.12362,0.95532,0.98111,0.88889,0.8852,0.83095,0.3931,2.00783,0.85204,0.000467829,0.000467829,0.000467829
+29,69.5655,0.84951,1.21617,1.07707,0.98111,0.88889,0.8852,0.83095,0.3931,2.00783,0.85204,0.000482384,0.000482384,0.000482384
+30,71.9275,0.67293,1.02036,0.90876,0.98111,0.88889,0.8852,0.83095,0.3931,2.00783,0.85204,0.000496741,0.000496741,0.000496741
+31,74.3812,0.65062,1.14422,0.94948,0.98933,0.88889,0.8852,0.81668,0.39975,1.78119,0.85198,0.0005109,0.0005109,0.0005109
+32,76.7801,0.97271,1.56493,1.44105,0.98933,0.88889,0.8852,0.81668,0.39975,1.78119,0.85198,0.000524861,0.000524861,0.000524861
+33,79.1052,0.55073,0.92717,0.85531,0.98933,0.88889,0.8852,0.81668,0.39975,1.78119,0.85198,0.000538624,0.000538624,0.000538624
+34,81.4605,0.52801,0.94671,0.90402,0.99072,0.88889,0.8852,0.82311,0.41385,1.60565,0.85071,0.000552189,0.000552189,0.000552189
+35,83.7832,0.73578,1.26274,1.14028,0.99072,0.88889,0.8852,0.82311,0.41385,1.60565,0.85071,0.000565556,0.000565556,0.000565556
+36,86.1307,0.68725,1.08364,1.00251,0.99072,0.88889,0.8852,0.82311,0.41385,1.60565,0.85071,0.000578725,0.000578725,0.000578725
+37,88.5128,0.71518,1.32409,0.96695,0.99072,0.88889,0.8852,0.82311,0.41385,1.60565,0.85071,0.000591696,0.000591696,0.000591696
+38,90.8977,0.85454,1.37877,1.16789,0.99131,0.88889,0.8852,0.80329,0.40134,1.53915,0.85021,0.000604469,0.000604469,0.000604469
+39,93.3448,0.73548,1.07552,0.9398,0.99131,0.88889,0.8852,0.80329,0.40134,1.53915,0.85021,0.000617044,0.000617044,0.000617044
+40,95.7953,0.5952,1.13214,0.83742,0.99131,0.88889,0.8852,0.80329,0.40134,1.53915,0.85021,0.000629421,0.000629421,0.000629421
+41,98.1229,0.79572,1.11991,1.15744,0.99131,0.88889,0.8852,0.80329,0.40134,1.53915,0.85021,0.0006416,0.0006416,0.0006416
+42,100.504,0.66331,1.12817,0.96887,0.9921,0.88889,0.8852,0.75364,0.51466,1.41164,0.87532,0.000653581,0.000653581,0.000653581
+43,102.883,0.88604,1.34558,1.10748,0.9921,0.88889,0.8852,0.75364,0.51466,1.41164,0.87532,0.000665364,0.000665364,0.000665364
+44,105.238,0.66965,1.06127,0.9416,0.9921,0.88889,0.8852,0.75364,0.51466,1.41164,0.87532,0.000676949,0.000676949,0.000676949
+45,107.578,0.6744,1.28697,1.02179,0.9921,0.88889,0.8852,0.75364,0.51466,1.41164,0.87532,0.000688336,0.000688336,0.000688336
+46,109.975,0.89497,1.07277,1.19768,0.99181,0.88889,0.8852,0.73423,0.62066,1.4192,0.90695,0.000699525,0.000699525,0.000699525
+47,112.307,1.21509,1.61059,1.5069,0.99181,0.88889,0.8852,0.73423,0.62066,1.4192,0.90695,0.000710516,0.000710516,0.000710516
+48,114.664,0.7325,1.13365,1.06054,0.99181,0.88889,0.8852,0.73423,0.62066,1.4192,0.90695,0.000721309,0.000721309,0.000721309
+49,117.077,0.87712,1.49915,1.13423,0.99181,0.88889,0.8852,0.73423,0.62066,1.4192,0.90695,0.000731904,0.000731904,0.000731904
+50,119.513,0.7641,1.00751,0.93622,0.99336,0.88889,0.8852,0.76757,0.6285,1.40914,0.91582,0.000742301,0.000742301,0.000742301
+51,121.859,0.80487,1.02137,1.0223,0.99336,0.88889,0.8852,0.76757,0.6285,1.40914,0.91582,0.0007525,0.0007525,0.0007525
+52,124.239,0.8842,1.52877,1.06398,0.99336,0.88889,0.8852,0.76757,0.6285,1.40914,0.91582,0.000762501,0.000762501,0.000762501
+53,126.624,0.67371,1.15463,0.90841,0.99336,0.88889,0.8852,0.76757,0.6285,1.40914,0.91582,0.000772304,0.000772304,0.000772304
+54,129.016,0.7695,1.11711,1.00695,0.99336,0.88889,0.8852,0.76757,0.6285,1.40914,0.91582,0.000781909,0.000781909,0.000781909
+55,131.462,0.97449,1.18503,1.21886,0.99378,0.88889,0.8852,0.78791,0.60719,1.31667,0.91882,0.000791316,0.000791316,0.000791316
+56,133.943,0.73369,1.35327,1.03889,0.99378,0.88889,0.8852,0.78791,0.60719,1.31667,0.91882,0.000800525,0.000800525,0.000800525
+57,136.325,0.63543,1.16751,0.87116,0.99378,0.88889,0.8852,0.78791,0.60719,1.31667,0.91882,0.000809536,0.000809536,0.000809536
+58,138.714,0.89618,1.25217,1.05405,0.99378,0.88889,0.8852,0.78791,0.60719,1.31667,0.91882,0.000818349,0.000818349,0.000818349
+59,141.155,0.65537,1.04452,0.93955,0.99378,0.88889,0.8852,0.78791,0.60719,1.31667,0.91882,0.000826964,0.000826964,0.000826964
+60,143.531,0.69248,1.14361,1.04093,0.99406,0.88889,0.8852,0.77223,0.51287,1.13532,0.88077,0.000835381,0.000835381,0.000835381
+61,145.916,0.72916,0.90805,1.05146,0.99406,0.88889,0.8852,0.77223,0.51287,1.13532,0.88077,0.0008436,0.0008436,0.0008436
+62,148.317,0.84334,1.20647,1.08022,0.99406,0.88889,0.8852,0.77223,0.51287,1.13532,0.88077,0.000851621,0.000851621,0.000851621
+63,150.689,0.53598,0.99282,0.94661,0.99406,0.88889,0.8852,0.77223,0.51287,1.13532,0.88077,0.000859444,0.000859444,0.000859444
+64,153.111,0.66993,1.12479,1.04425,0.99406,0.88889,0.8852,0.77223,0.51287,1.13532,0.88077,0.000867069,0.000867069,0.000867069
+65,155.577,0.68368,1.21123,0.99139,0.99406,0.88889,0.8852,0.76712,0.55903,1.1558,0.89368,0.000874496,0.000874496,0.000874496
+66,157.915,0.68925,0.9485,0.97626,0.99406,0.88889,0.8852,0.76712,0.55903,1.1558,0.89368,0.000881725,0.000881725,0.000881725
+67,160.283,0.59629,0.95086,0.89794,0.99406,0.88889,0.8852,0.76712,0.55903,1.1558,0.89368,0.000888756,0.000888756,0.000888756
+68,162.64,0.62357,0.97354,0.93467,0.99406,0.88889,0.8852,0.76712,0.55903,1.1558,0.89368,0.000895589,0.000895589,0.000895589
+69,164.974,0.53405,1.1027,0.90809,0.99406,0.88889,0.8852,0.76712,0.55903,1.1558,0.89368,0.000902224,0.000902224,0.000902224
+70,167.31,0.6005,1.18297,0.99935,0.99406,0.88889,0.8852,0.76712,0.55903,1.1558,0.89368,0.000908661,0.000908661,0.000908661
+71,169.777,0.61019,0.96051,0.9094,0.99388,0.88889,0.8852,0.80761,0.54084,1.20126,0.89822,0.0009149,0.0009149,0.0009149
+72,172.154,0.52546,1.30039,0.88774,0.99388,0.88889,0.8852,0.80761,0.54084,1.20126,0.89822,0.000920941,0.000920941,0.000920941
+73,174.492,0.67895,1.29729,0.96617,0.99388,0.88889,0.8852,0.80761,0.54084,1.20126,0.89822,0.000926784,0.000926784,0.000926784
+74,176.901,0.69609,1.11204,0.99213,0.99388,0.88889,0.8852,0.80761,0.54084,1.20126,0.89822,0.000932429,0.000932429,0.000932429
+75,179.293,0.68095,0.90637,0.96327,0.99388,0.88889,0.8852,0.80761,0.54084,1.20126,0.89822,0.000937876,0.000937876,0.000937876
+76,181.644,0.75062,1.06108,1.00375,0.99388,0.88889,0.8852,0.80761,0.54084,1.20126,0.89822,0.000943125,0.000943125,0.000943125
+77,184.038,0.81906,1.34644,1.04751,0.99182,0.88889,0.8852,0.81786,0.41751,1.27595,0.87693,0.000948176,0.000948176,0.000948176
+78,186.421,0.8321,1.09199,1.11162,0.99182,0.88889,0.8852,0.81786,0.41751,1.27595,0.87693,0.000953029,0.000953029,0.000953029
+79,188.813,0.69429,1.29677,0.94483,0.99182,0.88889,0.8852,0.81786,0.41751,1.27595,0.87693,0.000957684,0.000957684,0.000957684
+80,191.139,0.49987,0.99018,0.93999,0.99182,0.88889,0.8852,0.81786,0.41751,1.27595,0.87693,0.000962141,0.000962141,0.000962141
+81,193.482,0.68197,1.00514,0.96103,0.99182,0.88889,0.8852,0.81786,0.41751,1.27595,0.87693,0.0009664,0.0009664,0.0009664
+82,195.866,0.77105,1.04411,1.06886,0.99182,0.88889,0.8852,0.81786,0.41751,1.27595,0.87693,0.000970461,0.000970461,0.000970461
+83,198.181,0.82641,1.21712,1.0154,0.99182,0.88889,0.8852,0.81786,0.41751,1.27595,0.87693,0.000974324,0.000974324,0.000974324
+84,200.596,0.91184,1.13117,1.09616,0.991,0.88889,0.8852,0.80668,0.44144,1.35018,0.87153,0.000977989,0.000977989,0.000977989
+85,202.924,0.59474,1.10096,0.99698,0.991,0.88889,0.8852,0.80668,0.44144,1.35018,0.87153,0.000981456,0.000981456,0.000981456
+86,205.255,0.70448,1.08273,0.97212,0.991,0.88889,0.8852,0.80668,0.44144,1.35018,0.87153,0.000984725,0.000984725,0.000984725
+87,207.635,0.60008,0.94615,0.88845,0.991,0.88889,0.8852,0.80668,0.44144,1.35018,0.87153,0.000987796,0.000987796,0.000987796
+88,209.989,0.7716,1.10441,1.07113,0.991,0.88889,0.8852,0.80668,0.44144,1.35018,0.87153,0.000990669,0.000990669,0.000990669
+89,212.338,0.54266,1.20955,0.94432,0.991,0.88889,0.8852,0.80668,0.44144,1.35018,0.87153,0.000993344,0.000993344,0.000993344
+90,214.92,0.78635,1.09836,1.05601,0.991,0.88889,0.8852,0.80668,0.44144,1.35018,0.87153,0.000995821,0.000995821,0.000995821
+91,217.279,0.77563,1.22971,0.93955,0.99365,0.88889,0.8852,0.75795,0.55018,1.1417,0.88082,0.0009981,0.0009981,0.0009981
+92,219.608,0.62199,0.98525,0.92761,0.99365,0.88889,0.8852,0.75795,0.55018,1.1417,0.88082,0.00100018,0.00100018,0.00100018
+93,221.969,0.68844,0.90696,0.99478,0.99365,0.88889,0.8852,0.75795,0.55018,1.1417,0.88082,0.00100206,0.00100206,0.00100206
+94,224.335,0.54038,1.2907,0.94759,0.99365,0.88889,0.8852,0.75795,0.55018,1.1417,0.88082,0.00100375,0.00100375,0.00100375
+95,226.67,0.62397,1.04703,0.91073,0.99365,0.88889,0.8852,0.75795,0.55018,1.1417,0.88082,0.00100524,0.00100524,0.00100524
+96,229.006,0.57449,0.87945,0.85397,0.99365,0.88889,0.8852,0.75795,0.55018,1.1417,0.88082,0.00100653,0.00100653,0.00100653
+97,231.402,0.69251,1.29495,1.05802,0.99365,0.88889,0.8852,0.75795,0.55018,1.1417,0.88082,0.00100762,0.00100762,0.00100762
+98,233.865,0.87854,1.19071,1.09647,0.99365,0.88889,0.8852,0.75795,0.55018,1.1417,0.88082,0.00100851,0.00100851,0.00100851
+99,236.318,0.87895,1.10823,1.12946,0.99541,0.88889,0.8852,0.78294,0.54804,1.15956,0.8686,0.0010092,0.0010092,0.0010092
+100,238.748,0.88589,1.00296,0.96315,0.99541,0.88889,0.8852,0.78294,0.54804,1.15956,0.8686,0.0010097,0.0010097,0.0010097
+101,241.16,0.8676,1.29074,1.10989,0.99541,0.88889,0.8852,0.78294,0.54804,1.15956,0.8686,0.00101,0.00101,0.00101
+102,243.523,0.75596,1.1178,1.046,0.99541,0.88889,0.8852,0.78294,0.54804,1.15956,0.8686,0.0010001,0.0010001,0.0010001
diff --git a/runs/detect/train5/results.png b/runs/detect/train5/results.png
new file mode 100644
index 0000000..251bae6
Binary files /dev/null and b/runs/detect/train5/results.png differ
diff --git a/runs/detect/train5/train_batch0.jpg b/runs/detect/train5/train_batch0.jpg
new file mode 100644
index 0000000..a790255
Binary files /dev/null and b/runs/detect/train5/train_batch0.jpg differ
diff --git a/runs/detect/train5/train_batch1.jpg b/runs/detect/train5/train_batch1.jpg
new file mode 100644
index 0000000..7bccf1d
Binary files /dev/null and b/runs/detect/train5/train_batch1.jpg differ
diff --git a/runs/detect/train5/train_batch2.jpg b/runs/detect/train5/train_batch2.jpg
new file mode 100644
index 0000000..db0de92
Binary files /dev/null and b/runs/detect/train5/train_batch2.jpg differ
diff --git a/runs/detect/train5/val_batch0_labels.jpg b/runs/detect/train5/val_batch0_labels.jpg
new file mode 100644
index 0000000..5dded87
Binary files /dev/null and b/runs/detect/train5/val_batch0_labels.jpg differ
diff --git a/runs/detect/train5/val_batch0_pred.jpg b/runs/detect/train5/val_batch0_pred.jpg
new file mode 100644
index 0000000..a44bdd6
Binary files /dev/null and b/runs/detect/train5/val_batch0_pred.jpg differ
diff --git a/runs/detect/train5/weights/best.pt b/runs/detect/train5/weights/best.pt
new file mode 100644
index 0000000..ca48b9b
Binary files /dev/null and b/runs/detect/train5/weights/best.pt differ
diff --git a/runs/detect/train5/weights/last.pt b/runs/detect/train5/weights/last.pt
new file mode 100644
index 0000000..7ec828a
Binary files /dev/null and b/runs/detect/train5/weights/last.pt differ
diff --git a/runs/detect/train6/F1_curve.png b/runs/detect/train6/F1_curve.png
new file mode 100644
index 0000000..c9d0086
Binary files /dev/null and b/runs/detect/train6/F1_curve.png differ
diff --git a/runs/detect/train6/PR_curve.png b/runs/detect/train6/PR_curve.png
new file mode 100644
index 0000000..724a8ef
Binary files /dev/null and b/runs/detect/train6/PR_curve.png differ
diff --git a/runs/detect/train6/P_curve.png b/runs/detect/train6/P_curve.png
new file mode 100644
index 0000000..2dc6201
Binary files /dev/null and b/runs/detect/train6/P_curve.png differ
diff --git a/runs/detect/train6/R_curve.png b/runs/detect/train6/R_curve.png
new file mode 100644
index 0000000..824e1b1
Binary files /dev/null and b/runs/detect/train6/R_curve.png differ
diff --git a/runs/detect/train6/args.yaml b/runs/detect/train6/args.yaml
new file mode 100644
index 0000000..680a29c
--- /dev/null
+++ b/runs/detect/train6/args.yaml
@@ -0,0 +1,105 @@
+task: detect
+mode: train
+model: yolov8n.pt
+data: data.yaml
+epochs: 500
+time: null
+patience: 100
+batch: 3
+imgsz: 640
+save: true
+save_period: -1
+cache: false
+device: cpu
+workers: 8
+project: null
+name: train6
+exist_ok: false
+pretrained: true
+optimizer: auto
+verbose: true
+seed: 0
+deterministic: true
+single_cls: false
+rect: false
+cos_lr: false
+close_mosaic: 10
+resume: false
+amp: true
+fraction: 1.0
+profile: false
+freeze: null
+multi_scale: false
+overlap_mask: true
+mask_ratio: 4
+dropout: 0.0
+val: true
+split: val
+save_json: false
+conf: null
+iou: 0.7
+max_det: 300
+half: false
+dnn: false
+plots: true
+source: null
+vid_stride: 1
+stream_buffer: false
+visualize: false
+augment: false
+agnostic_nms: false
+classes: null
+retina_masks: false
+embed: null
+show: false
+save_frames: false
+save_txt: false
+save_conf: false
+save_crop: false
+show_labels: true
+show_conf: true
+show_boxes: true
+line_width: null
+format: torchscript
+keras: false
+optimize: false
+int8: false
+dynamic: false
+simplify: true
+opset: null
+workspace: null
+nms: false
+lr0: 0.01
+lrf: 0.01
+momentum: 0.937
+weight_decay: 0.0005
+warmup_epochs: 3.0
+warmup_momentum: 0.8
+warmup_bias_lr: 0.1
+box: 7.5
+cls: 0.5
+dfl: 1.5
+pose: 12.0
+kobj: 1.0
+nbs: 64
+hsv_h: 0.015
+hsv_s: 0.7
+hsv_v: 0.4
+degrees: 0.0
+translate: 0.1
+scale: 0.5
+shear: 0.0
+perspective: 0.0
+flipud: 0.0
+fliplr: 0.5
+bgr: 0.0
+mosaic: 1.0
+mixup: 0.0
+cutmix: 0.0
+copy_paste: 0.0
+copy_paste_mode: flip
+auto_augment: randaugment
+erasing: 0.4
+cfg: null
+tracker: botsort.yaml
+save_dir: C:\workspace\le-yolo\runs\detect\train6
diff --git a/runs/detect/train6/confusion_matrix.png b/runs/detect/train6/confusion_matrix.png
new file mode 100644
index 0000000..fbc20c5
Binary files /dev/null and b/runs/detect/train6/confusion_matrix.png differ
diff --git a/runs/detect/train6/confusion_matrix_normalized.png b/runs/detect/train6/confusion_matrix_normalized.png
new file mode 100644
index 0000000..0d37ba0
Binary files /dev/null and b/runs/detect/train6/confusion_matrix_normalized.png differ
diff --git a/runs/detect/train6/labels.jpg b/runs/detect/train6/labels.jpg
new file mode 100644
index 0000000..e572540
Binary files /dev/null and b/runs/detect/train6/labels.jpg differ
diff --git a/runs/detect/train6/results.csv b/runs/detect/train6/results.csv
new file mode 100644
index 0000000..bce040d
--- /dev/null
+++ b/runs/detect/train6/results.csv
@@ -0,0 +1,105 @@
+epoch,time,train/box_loss,train/cls_loss,train/dfl_loss,metrics/precision(B),metrics/recall(B),metrics/mAP50(B),metrics/mAP50-95(B),val/box_loss,val/cls_loss,val/dfl_loss,lr/pg0,lr/pg1,lr/pg2
+1,2.67795,1.27905,3.24362,1.30371,0.00333,0.88889,0.40865,0.37899,0.32191,3.10663,0.84784,4e-05,4e-05,4e-05
+2,5.09254,0.98756,3.0412,1.11673,0.00333,0.88889,0.57671,0.52573,0.30795,3.10622,0.83504,9.9802e-05,9.9802e-05,9.9802e-05
+3,7.5974,0.72408,3.03488,0.95692,0.00333,0.88889,0.80252,0.75001,0.35484,3.10151,0.837,0.000159366,0.000159366,0.000159366
+4,9.98193,0.4863,3.05383,0.93557,0.00333,0.88889,0.87298,0.81759,0.39024,3.08366,0.83707,0.000218693,0.000218693,0.000218693
+5,12.2816,0.97693,2.29936,1.05502,0.00333,0.88889,0.8852,0.76467,0.46905,3.03797,0.84486,0.000277782,0.000277782,0.000277782
+6,14.5462,0.62948,2.82315,0.95893,0.00333,0.88889,0.8852,0.76602,0.53693,2.97087,0.85383,0.000336634,0.000336634,0.000336634
+7,16.7704,0.64673,1.78792,0.94692,0.00333,0.88889,0.8852,0.76602,0.53693,2.97087,0.85383,0.000395248,0.000395248,0.000395248
+8,19.1617,0.90428,1.51959,0.97618,0.95687,0.88889,0.8852,0.78745,0.57408,2.80877,0.8614,0.000453624,0.000453624,0.000453624
+9,21.3984,0.73933,1.45439,1.00473,0.95687,0.88889,0.8852,0.78745,0.57408,2.80877,0.8614,0.000511763,0.000511763,0.000511763
+10,23.6597,0.72383,1.48773,1.07725,0.97649,0.88889,0.8852,0.72079,0.76262,2.50584,0.93079,0.000569664,0.000569664,0.000569664
+11,25.9231,0.91272,2.0963,1.24114,0.97649,0.88889,0.8852,0.72079,0.76262,2.50584,0.93079,0.000627328,0.000627328,0.000627328
+12,28.252,0.72902,1.30912,0.95611,1,0.56488,0.87298,0.68585,0.92343,2.8316,1.00301,0.000684754,0.000684754,0.000684754
+13,30.5298,0.77018,1.375,1.01699,1,0.56488,0.87298,0.68585,0.92343,2.8316,1.00301,0.000741942,0.000741942,0.000741942
+14,32.7901,1.08536,1.89609,1.28064,1,0.56488,0.87298,0.68585,0.92343,2.8316,1.00301,0.000798893,0.000798893,0.000798893
+15,35.0072,0.71429,2.20852,0.88781,1,0.56488,0.87298,0.68585,0.92343,2.8316,1.00301,0.000855606,0.000855606,0.000855606
+16,37.2745,1.00485,1.73181,1.11101,1,0.29103,0.78218,0.49263,1.50615,3.56637,1.26244,0.000912082,0.000912082,0.000912082
+17,39.5884,0.98953,1.75123,1.19853,1,0.29103,0.78218,0.49263,1.50615,3.56637,1.26244,0.00096832,0.00096832,0.00096832
+18,41.8368,1.10986,1.98043,1.20789,1,0.29103,0.78218,0.49263,1.50615,3.56637,1.26244,0.00102432,0.00102432,0.00102432
+19,44.0787,0.93739,2.3785,1.20116,1,0.29103,0.78218,0.49263,1.50615,3.56637,1.26244,0.00108008,0.00108008,0.00108008
+20,46.3797,1.16955,2.52993,1.34473,1,0.53034,0.74233,0.57976,1.25833,3.20967,1.25833,0.00113561,0.00113561,0.00113561
+21,48.685,0.96654,1.89043,1.24844,1,0.53034,0.74233,0.57976,1.25833,3.20967,1.25833,0.0011909,0.0011909,0.0011909
+22,50.9478,1.02031,1.68544,0.98893,1,0.53034,0.74233,0.57976,1.25833,3.20967,1.25833,0.00124595,0.00124595,0.00124595
+23,53.21,0.70895,1.87494,0.89338,1,0.53034,0.74233,0.57976,1.25833,3.20967,1.25833,0.00130076,0.00130076,0.00130076
+24,55.5248,0.83497,2.50946,1.22273,1,0.53034,0.74233,0.57976,1.25833,3.20967,1.25833,0.00135533,0.00135533,0.00135533
+25,57.8665,1.0376,2.35996,1.22457,0.97493,0.55556,0.78131,0.53135,1.195,3.78545,1.11106,0.00140967,0.00140967,0.00140967
+26,60.2004,1.16131,1.77476,1.16964,0.97493,0.55556,0.78131,0.53135,1.195,3.78545,1.11106,0.00146377,0.00146377,0.00146377
+27,62.5187,0.99319,2.0313,1.11383,0.97493,0.55556,0.78131,0.53135,1.195,3.78545,1.11106,0.00151763,0.00151763,0.00151763
+28,65.0548,0.96165,1.62008,1.08608,0.97493,0.55556,0.78131,0.53135,1.195,3.78545,1.11106,0.00157126,0.00157126,0.00157126
+29,67.345,1.27797,2.2427,1.43302,0.97493,0.55556,0.78131,0.53135,1.195,3.78545,1.11106,0.00162464,0.00162464,0.00162464
+30,69.6022,1.01235,1.92345,1.10763,0.97493,0.55556,0.78131,0.53135,1.195,3.78545,1.11106,0.00167779,0.00167779,0.00167779
+31,71.8442,0.97522,1.80498,1.03414,0.97493,0.55556,0.78131,0.53135,1.195,3.78545,1.11106,0.0017307,0.0017307,0.0017307
+32,74.1916,1.3933,2.62316,1.55349,1,0.88116,0.8852,0.66362,0.91472,2.17002,1.42451,0.00178338,0.00178338,0.00178338
+33,76.4383,0.90452,1.41513,0.981,1,0.88116,0.8852,0.66362,0.91472,2.17002,1.42451,0.00183581,0.00183581,0.00183581
+34,78.6722,0.92794,1.73043,1.10102,1,0.88116,0.8852,0.66362,0.91472,2.17002,1.42451,0.00186932,0.00186932,0.00186932
+35,80.9255,1.23513,2.17191,1.31006,1,0.88116,0.8852,0.66362,0.91472,2.17002,1.42451,0.00186536,0.00186536,0.00186536
+36,83.1627,0.97986,2.02621,1.14236,1,0.88116,0.8852,0.66362,0.91472,2.17002,1.42451,0.0018614,0.0018614,0.0018614
+37,85.4793,0.88313,2.19899,1.0059,1,0.88116,0.8852,0.66362,0.91472,2.17002,1.42451,0.00185744,0.00185744,0.00185744
+38,87.7531,1.48866,2.56755,1.6594,1,0.88116,0.8852,0.66362,0.91472,2.17002,1.42451,0.00185348,0.00185348,0.00185348
+39,90.0454,1.18119,1.69549,1.16404,0.85558,0.77778,0.84145,0.58203,0.92895,2.02279,1.27738,0.00184952,0.00184952,0.00184952
+40,92.2773,1.18817,1.82216,1.30513,0.85558,0.77778,0.84145,0.58203,0.92895,2.02279,1.27738,0.00184556,0.00184556,0.00184556
+41,94.5209,1.1457,1.9247,1.42785,0.85558,0.77778,0.84145,0.58203,0.92895,2.02279,1.27738,0.0018416,0.0018416,0.0018416
+42,96.7818,0.95592,1.74857,1.1364,0.85558,0.77778,0.84145,0.58203,0.92895,2.02279,1.27738,0.00183764,0.00183764,0.00183764
+43,99.0475,0.92277,2.98682,1.10743,0.85558,0.77778,0.84145,0.58203,0.92895,2.02279,1.27738,0.00183368,0.00183368,0.00183368
+44,101.333,1.04883,2.03719,1.08668,0.85558,0.77778,0.84145,0.58203,0.92895,2.02279,1.27738,0.00182972,0.00182972,0.00182972
+45,103.579,1.20426,2.03691,1.17871,0.85558,0.77778,0.84145,0.58203,0.92895,2.02279,1.27738,0.00182576,0.00182576,0.00182576
+46,105.888,1.16457,1.82163,1.25208,0.63211,0.66667,0.73389,0.51108,1.39313,2.39865,1.7205,0.0018218,0.0018218,0.0018218
+47,108.158,1.41602,2.29267,1.32991,0.63211,0.66667,0.73389,0.51108,1.39313,2.39865,1.7205,0.00181784,0.00181784,0.00181784
+48,110.421,1.29142,1.96265,1.22785,0.63211,0.66667,0.73389,0.51108,1.39313,2.39865,1.7205,0.00181388,0.00181388,0.00181388
+49,112.743,1.16174,2.07302,1.23214,0.63211,0.66667,0.73389,0.51108,1.39313,2.39865,1.7205,0.00180992,0.00180992,0.00180992
+50,115.044,1.21833,1.81467,1.16257,0.63211,0.66667,0.73389,0.51108,1.39313,2.39865,1.7205,0.00180596,0.00180596,0.00180596
+51,117.345,1.05494,1.7611,1.16658,0.63211,0.66667,0.73389,0.51108,1.39313,2.39865,1.7205,0.001802,0.001802,0.001802
+52,119.599,1.18708,2.69756,1.15335,0.63211,0.66667,0.73389,0.51108,1.39313,2.39865,1.7205,0.00179804,0.00179804,0.00179804
+53,121.899,1.18044,1.82261,1.1866,0.72636,0.88518,0.75131,0.54765,0.96058,2.20226,1.0733,0.00179408,0.00179408,0.00179408
+54,124.236,1.03304,1.74743,1.22446,0.72636,0.88518,0.75131,0.54765,0.96058,2.20226,1.0733,0.00179012,0.00179012,0.00179012
+55,126.564,1.14346,1.78663,1.23261,0.72636,0.88518,0.75131,0.54765,0.96058,2.20226,1.0733,0.00178616,0.00178616,0.00178616
+56,129,1.09489,2.11696,1.16755,0.72636,0.88518,0.75131,0.54765,0.96058,2.20226,1.0733,0.0017822,0.0017822,0.0017822
+57,131.354,0.91336,1.70765,1.15637,0.72636,0.88518,0.75131,0.54765,0.96058,2.20226,1.0733,0.00177824,0.00177824,0.00177824
+58,133.682,1.15293,1.86471,1.26461,0.72636,0.88518,0.75131,0.54765,0.96058,2.20226,1.0733,0.00177428,0.00177428,0.00177428
+59,136.345,1.01331,1.69096,1.09072,0.72636,0.88518,0.75131,0.54765,0.96058,2.20226,1.0733,0.00177032,0.00177032,0.00177032
+60,138.726,0.87277,1.63231,1.06982,0.88175,0.88889,0.8852,0.62629,0.92977,2.26573,0.99996,0.00176636,0.00176636,0.00176636
+61,141.514,0.94482,1.21048,1.12625,0.88175,0.88889,0.8852,0.62629,0.92977,2.26573,0.99996,0.0017624,0.0017624,0.0017624
+62,144.088,1.35551,1.9419,1.32975,0.88175,0.88889,0.8852,0.62629,0.92977,2.26573,0.99996,0.00175844,0.00175844,0.00175844
+63,146.655,0.81021,1.53806,1.12253,0.88175,0.88889,0.8852,0.62629,0.92977,2.26573,0.99996,0.00175448,0.00175448,0.00175448
+64,149.268,1.04121,1.87248,1.31633,0.88175,0.88889,0.8852,0.62629,0.92977,2.26573,0.99996,0.00175052,0.00175052,0.00175052
+65,152.09,1.09547,1.80434,1.21025,0.88175,0.88889,0.8852,0.62629,0.92977,2.26573,0.99996,0.00174656,0.00174656,0.00174656
+66,154.617,0.8121,1.31265,1.04187,0.88175,0.88889,0.8852,0.62629,0.92977,2.26573,0.99996,0.0017426,0.0017426,0.0017426
+67,157.26,0.84264,1.51601,1.02304,0.98143,0.88889,0.8852,0.63635,0.97678,1.97834,1.15336,0.00173864,0.00173864,0.00173864
+68,159.778,0.78871,1.41874,1.02518,0.98143,0.88889,0.8852,0.63635,0.97678,1.97834,1.15336,0.00173468,0.00173468,0.00173468
+69,162.29,0.98977,1.85231,1.24652,0.98143,0.88889,0.8852,0.63635,0.97678,1.97834,1.15336,0.00173072,0.00173072,0.00173072
+70,164.792,0.9729,2.21472,1.12597,0.98143,0.88889,0.8852,0.63635,0.97678,1.97834,1.15336,0.00172676,0.00172676,0.00172676
+71,167.349,0.86683,1.51254,1.02215,0.98143,0.88889,0.8852,0.63635,0.97678,1.97834,1.15336,0.0017228,0.0017228,0.0017228
+72,170.034,0.94062,1.9615,1.18005,0.98143,0.88889,0.8852,0.63635,0.97678,1.97834,1.15336,0.00171884,0.00171884,0.00171884
+73,172.637,1.1132,2.73009,1.04916,0.98143,0.88889,0.8852,0.63635,0.97678,1.97834,1.15336,0.00171488,0.00171488,0.00171488
+74,175.685,0.80801,1.70499,1.04335,0.98147,0.88889,0.8852,0.74296,0.73088,2.01762,1.01098,0.00171092,0.00171092,0.00171092
+75,178.093,1.11995,1.53155,1.1913,0.98147,0.88889,0.8852,0.74296,0.73088,2.01762,1.01098,0.00170696,0.00170696,0.00170696
+76,180.533,0.89758,1.61335,1.13305,0.98147,0.88889,0.8852,0.74296,0.73088,2.01762,1.01098,0.001703,0.001703,0.001703
+77,182.926,0.93648,1.71941,1.11532,0.98147,0.88889,0.8852,0.74296,0.73088,2.01762,1.01098,0.00169904,0.00169904,0.00169904
+78,185.231,1.21461,1.56689,1.35139,0.98147,0.88889,0.8852,0.74296,0.73088,2.01762,1.01098,0.00169508,0.00169508,0.00169508
+79,187.507,1.08127,1.77739,1.15528,0.98147,0.88889,0.8852,0.74296,0.73088,2.01762,1.01098,0.00169112,0.00169112,0.00169112
+80,189.784,0.81228,1.4656,1.01938,0.98147,0.88889,0.8852,0.74296,0.73088,2.01762,1.01098,0.00168716,0.00168716,0.00168716
+81,192.085,0.92497,1.42455,1.08237,0.99042,0.88889,0.8852,0.70304,0.71864,1.6568,0.98498,0.0016832,0.0016832,0.0016832
+82,194.363,0.91327,1.60324,1.18723,0.99042,0.88889,0.8852,0.70304,0.71864,1.6568,0.98498,0.00167924,0.00167924,0.00167924
+83,196.627,1.09651,1.65158,1.16131,0.99042,0.88889,0.8852,0.70304,0.71864,1.6568,0.98498,0.00167528,0.00167528,0.00167528
+84,198.925,0.90448,1.88114,1.01694,0.99042,0.88889,0.8852,0.70304,0.71864,1.6568,0.98498,0.00167132,0.00167132,0.00167132
+85,201.164,0.95253,1.56788,1.18512,0.99042,0.88889,0.8852,0.70304,0.71864,1.6568,0.98498,0.00166736,0.00166736,0.00166736
+86,203.416,1.01586,1.65221,1.1115,0.99042,0.88889,0.8852,0.70304,0.71864,1.6568,0.98498,0.0016634,0.0016634,0.0016634
+87,205.679,0.93381,1.33418,1.03337,0.99042,0.88889,0.8852,0.70304,0.71864,1.6568,0.98498,0.00165944,0.00165944,0.00165944
+88,208.054,1.12109,1.65396,1.25144,0.99149,0.88889,0.8852,0.70619,0.69207,1.7073,0.94201,0.00165548,0.00165548,0.00165548
+89,210.317,0.96261,1.41334,1.14778,0.99149,0.88889,0.8852,0.70619,0.69207,1.7073,0.94201,0.00165152,0.00165152,0.00165152
+90,212.571,0.98662,1.35466,1.30028,0.99149,0.88889,0.8852,0.70619,0.69207,1.7073,0.94201,0.00164756,0.00164756,0.00164756
+91,214.821,0.95476,1.37853,1.11952,0.99149,0.88889,0.8852,0.70619,0.69207,1.7073,0.94201,0.0016436,0.0016436,0.0016436
+92,217.097,0.76714,1.34939,1.0739,0.99149,0.88889,0.8852,0.70619,0.69207,1.7073,0.94201,0.00163964,0.00163964,0.00163964
+93,219.363,1.07072,1.34053,1.16561,0.99149,0.88889,0.8852,0.70619,0.69207,1.7073,0.94201,0.00163568,0.00163568,0.00163568
+94,221.64,0.73114,1.7112,1.0523,0.99149,0.88889,0.8852,0.70619,0.69207,1.7073,0.94201,0.00163172,0.00163172,0.00163172
+95,223.93,0.8394,1.51995,1.02761,0.9914,0.88889,0.8852,0.73166,0.69271,1.40691,0.93966,0.00162776,0.00162776,0.00162776
+96,226.291,0.69965,1.11519,0.98087,0.9914,0.88889,0.8852,0.73166,0.69271,1.40691,0.93966,0.0016238,0.0016238,0.0016238
+97,228.564,1.00733,2.0208,1.31328,0.9914,0.88889,0.8852,0.73166,0.69271,1.40691,0.93966,0.00161984,0.00161984,0.00161984
+98,230.865,0.76349,1.50017,1.05159,0.9914,0.88889,0.8852,0.73166,0.69271,1.40691,0.93966,0.00161588,0.00161588,0.00161588
+99,233.153,1.09349,1.51612,1.41843,0.9914,0.88889,0.8852,0.73166,0.69271,1.40691,0.93966,0.00161192,0.00161192,0.00161192
+100,235.443,0.93364,1.19268,1.0842,0.9914,0.88889,0.8852,0.73166,0.69271,1.40691,0.93966,0.00160796,0.00160796,0.00160796
+101,237.737,1.16238,1.82491,1.23051,0.9914,0.88889,0.8852,0.73166,0.69271,1.40691,0.93966,0.001604,0.001604,0.001604
+102,240.043,1.00923,1.42276,1.13879,0.99335,0.88889,0.8852,0.74276,0.70275,1.55305,0.95414,0.00160004,0.00160004,0.00160004
+103,242.485,0.91573,1.41443,1.07295,0.99335,0.88889,0.8852,0.74276,0.70275,1.55305,0.95414,0.00159608,0.00159608,0.00159608
+104,244.775,0.89041,1.45741,1.08817,0.99335,0.88889,0.8852,0.74276,0.70275,1.55305,0.95414,0.00159212,0.00159212,0.00159212
diff --git a/runs/detect/train6/results.png b/runs/detect/train6/results.png
new file mode 100644
index 0000000..cf3c24d
Binary files /dev/null and b/runs/detect/train6/results.png differ
diff --git a/runs/detect/train6/train_batch0.jpg b/runs/detect/train6/train_batch0.jpg
new file mode 100644
index 0000000..fb646bf
Binary files /dev/null and b/runs/detect/train6/train_batch0.jpg differ
diff --git a/runs/detect/train6/train_batch1.jpg b/runs/detect/train6/train_batch1.jpg
new file mode 100644
index 0000000..56b467d
Binary files /dev/null and b/runs/detect/train6/train_batch1.jpg differ
diff --git a/runs/detect/train6/train_batch2.jpg b/runs/detect/train6/train_batch2.jpg
new file mode 100644
index 0000000..375d69c
Binary files /dev/null and b/runs/detect/train6/train_batch2.jpg differ
diff --git a/runs/detect/train6/val_batch0_labels.jpg b/runs/detect/train6/val_batch0_labels.jpg
new file mode 100644
index 0000000..f82cba8
Binary files /dev/null and b/runs/detect/train6/val_batch0_labels.jpg differ
diff --git a/runs/detect/train6/val_batch0_pred.jpg b/runs/detect/train6/val_batch0_pred.jpg
new file mode 100644
index 0000000..d69a420
Binary files /dev/null and b/runs/detect/train6/val_batch0_pred.jpg differ
diff --git a/runs/detect/train6/val_batch1_labels.jpg b/runs/detect/train6/val_batch1_labels.jpg
new file mode 100644
index 0000000..6e75b37
Binary files /dev/null and b/runs/detect/train6/val_batch1_labels.jpg differ
diff --git a/runs/detect/train6/val_batch1_pred.jpg b/runs/detect/train6/val_batch1_pred.jpg
new file mode 100644
index 0000000..1bc164d
Binary files /dev/null and b/runs/detect/train6/val_batch1_pred.jpg differ
diff --git a/runs/detect/train6/weights/best.pt b/runs/detect/train6/weights/best.pt
new file mode 100644
index 0000000..57f6ab3
Binary files /dev/null and b/runs/detect/train6/weights/best.pt differ
diff --git a/runs/detect/train6/weights/last.pt b/runs/detect/train6/weights/last.pt
new file mode 100644
index 0000000..43dbd92
Binary files /dev/null and b/runs/detect/train6/weights/last.pt differ
diff --git a/src/data.yaml b/src/data.yaml
new file mode 100644
index 0000000..ee8e68a
--- /dev/null
+++ b/src/data.yaml
@@ -0,0 +1,5 @@
+path: C:\workspace\le-yolo\data
+train: images/train
+val: images/val
+nc: 1
+names: [ 'A' ]
\ No newline at end of file
diff --git a/src/test.py b/src/test.py
new file mode 100644
index 0000000..da51b99
--- /dev/null
+++ b/src/test.py
@@ -0,0 +1,3 @@
+from ultralytics import YOLO
+model = YOLO(r"C:\workspace\le-yolo\runs\detect\train6\weights\best.pt")
+results = model.predict("../res/2.mp4", show=True, save=True)
\ No newline at end of file
diff --git a/src/train.py b/src/train.py
new file mode 100644
index 0000000..2a00257
--- /dev/null
+++ b/src/train.py
@@ -0,0 +1,4 @@
+from ultralytics import YOLO
+model = YOLO("yolov8n.pt")
+model.train(data="data.yaml", epochs=500, batch=3, device='cpu')
+print('训练完成')
\ No newline at end of file