├── .github ├── no-response.yml └── workflows │ └── package.yml ├── .gitignore ├── CONTRIBUTING.rst ├── HISTORY.rst ├── LICENSE ├── MANIFEST.in ├── Makefile ├── README.rst ├── __init__.py ├── build-tools ├── .gitignore ├── README.md ├── build-for-macos.sh ├── build-for-pypi.sh ├── build-ubuntu-binary.sh ├── build-windows-binary.sh ├── envsetup.sh └── run-in-container.sh ├── data └── predefined_classes.txt ├── demo ├── demo.jpg ├── demo3.jpg ├── demo4.png └── demo5.png ├── issue_template.md ├── labelImg.py ├── libs ├── __init__.py ├── canvas.py ├── colorDialog.py ├── combobox.py ├── constants.py ├── create_ml_io.py ├── default_label_combobox.py ├── hashableQListWidgetItem.py ├── labelDialog.py ├── labelFile.py ├── lightWidget.py ├── pascal_voc_io.py ├── settings.py ├── shape.py ├── stringBundle.py ├── toolBar.py ├── ustr.py ├── utils.py ├── yolo_io.py └── zoomWidget.py ├── readme ├── README.jp.rst ├── README.zh.rst └── images │ ├── label-studio-1-6-player-screenshot.png │ └── labelimg.png ├── requirements └── requirements-linux-python3.txt ├── resources.qrc ├── resources ├── icons │ ├── app.icns │ ├── app.png │ ├── app.svg │ ├── cancel.png │ ├── close.png │ ├── color.png │ ├── color_line.png │ ├── copy.png │ ├── delete.png │ ├── done.png │ ├── done.svg │ ├── edit.png │ ├── expert1.png │ ├── expert2.png │ ├── eye.png │ ├── feBlend-icon.png │ ├── file.png │ ├── fit-width.png │ ├── fit-window.png │ ├── fit.png │ ├── format_createml.png │ ├── format_voc.png │ ├── format_yolo.png │ ├── help.png │ ├── labels.png │ ├── labels.svg │ ├── light_brighten.svg │ ├── light_darken.png │ ├── light_darken.svg │ ├── light_lighten.png │ ├── light_reset.png │ ├── light_reset.svg │ ├── new.png │ ├── next.png │ ├── objects.png │ ├── open.png │ ├── open.svg │ ├── prev.png │ ├── quit.png │ ├── resetall.png │ ├── save-as.png │ ├── save-as.svg │ ├── save.png │ ├── save.svg │ ├── undo-cross.png │ ├── undo.png │ ├── verify.png │ ├── zoom-in.png │ ├── zoom-out.png │ └── zoom.png └── strings │ ├── strings-ja-JP.properties │ ├── strings-zh-CN.properties │ ├── strings-zh-TW.properties │ └── strings.properties ├── setup.cfg ├── setup.py ├── tests ├── .gitignore ├── test.512.512.bmp ├── test_io.py ├── test_qt.py ├── test_settings.py ├── test_stringBundle.py ├── test_utils.py └── 臉書.jpg └── tools ├── README.md └── label_to_csv.py /.github/no-response.yml: -------------------------------------------------------------------------------- 1 | daysUntilClose: 14 2 | responseRequiredLabel: more-information-needed 3 | closeComment: > 4 | This issue has been automatically closed because there has been no response 5 | to our request for more information from the original author. With only the 6 | information that is currently in the issue, we don't have enough information 7 | to take action or reproduce the issue. Please reach out if you have or find 8 | the answers we need so that we can investigate further. 9 | -------------------------------------------------------------------------------- /.github/workflows/package.yml: -------------------------------------------------------------------------------- 1 | name: Package 2 | on: [push, pull_request] 3 | 4 | jobs: 5 | package-macos: 6 | runs-on: macos-latest 7 | env: 8 | PIPENV_VENV_IN_PROJECT: 1 9 | PIPENV_IGNORE_VIRTUALENVS: 1 10 | steps: 11 | - uses: actions/checkout@v3 12 | - name: Setup Python Environment 13 | run: | 14 | pip3 install pipenv 15 | pipenv install pyqt5 lxml 16 | pipenv run pip install pyqt5==5.15.6 lxml 17 | - name: Build LabelImg 18 | run: | 19 | pipenv run make qt5py3 20 | rm -rf build dist 21 | - name: Package LabelImg 22 | run: | 23 | pipenv run python setup.py py2app 24 | open dist/labelImg.app 25 | - name: Archive macOS app 26 | run: | 27 | cd dist/ 28 | tar czf labelImg.tgz labelImg.app 29 | - uses: actions/upload-artifact@v3 30 | with: 31 | name: macOS artifact 32 | path: dist/labelImg.tgz 33 | package-windows: 34 | runs-on: windows-latest 35 | steps: 36 | - uses: actions/checkout@v3 37 | - name: Setup Python Environment 38 | run: | 39 | pip3 install pyinstaller pyqt5==5.15.6 lxml 40 | - name: Build LabelImg 41 | run: | 42 | pyrcc5 -o libs/resources.py resources.qrc 43 | - name: Package LabelImg 44 | run: | 45 | pyinstaller --hidden-import=pyqt5 --hidden-import=lxml -F -n "labelImg" -c labelImg.py -p ./libs -p ./ 46 | - uses: actions/upload-artifact@v3 47 | with: 48 | name: Windows artifact 49 | path: dist/labelImg.exe 50 | package-linux: 51 | runs-on: ubuntu-latest 52 | steps: 53 | - uses: actions/checkout@v3 54 | - name: Setup Python Environment 55 | run: | 56 | pip3 install pyinstaller pyqt5==5.15.6 lxml 57 | - name: Build LabelImg 58 | run: | 59 | pyrcc5 -o libs/resources.py resources.qrc 60 | - name: Package LabelImg 61 | run: | 62 | pyinstaller --hidden-import=pyqt5 --hidden-import=lxml -F -n "labelImg" -c labelImg.py -p ./libs -p ./ 63 | - uses: actions/upload-artifact@v3 64 | with: 65 | name: Linux artifact 66 | path: dist/labelImg 67 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | resources/icons/.DS_Store 2 | resources.py 3 | labelImg.egg-info* 4 | 5 | *.pyc 6 | .*.swp 7 | 8 | build/ 9 | dist/ 10 | 11 | tags 12 | cscope* 13 | .ycm_extra_conf.py 14 | .subvimrc 15 | .vscode 16 | *.pkl 17 | Pipfile 18 | *.xml 19 | 20 | # MacOS System-Generated 21 | .DS_Store 22 | .DS_Store? 23 | ._* 24 | .Spotlight-V100 25 | .Trashes 26 | ehthumbs.db 27 | Thumbs.db 28 | 29 | # Byte-compiled / optimized / DLL files 30 | __pycache__/ 31 | *.py[cod] 32 | *$py.class 33 | 34 | # C extensions 35 | *.so 36 | 37 | # IDE 38 | *.DS_Store 39 | *.iml 40 | .idea 41 | .unison* 42 | .attach* 43 | tmp.* 44 | -------------------------------------------------------------------------------- /CONTRIBUTING.rst: -------------------------------------------------------------------------------- 1 | TzuTa Lin 2 | [LabelMe](http://labelme2.csail.mit.edu/Release3.0/index.php) 3 | Ryan Flynn 4 | -------------------------------------------------------------------------------- /HISTORY.rst: -------------------------------------------------------------------------------- 1 | History 2 | ======= 3 | 4 | 1.8.6 (2021-10-10) 5 | ------------------ 6 | 7 | * Display box width and height 8 | 9 | 10 | 1.8.5 (2021-04-11) 11 | ------------------ 12 | 13 | * Merged a couple of PRs 14 | * Fixed issues 15 | * Support CreateML format 16 | 17 | 18 | 1.8.4 (2020-11-04) 19 | ------------------ 20 | 21 | * Merged a couple of PRs 22 | * Fixed issues 23 | 24 | 1.8.2 (2018-12-02) 25 | ------------------ 26 | 27 | * Fix pip depolyment issue 28 | 29 | 30 | 1.8.1 (2018-12-02) 31 | ------------------ 32 | 33 | * Fix issues 34 | * Support zh-Tw strings 35 | 36 | 37 | 1.8.0 (2018-10-21) 38 | ------------------ 39 | 40 | * Support drawing sqaure rect 41 | * Add item single click slot 42 | * Fix issues 43 | 44 | 1.7.0 (2018-05-18) 45 | ------------------ 46 | 47 | * Support YOLO 48 | * Fix minor issues 49 | 50 | 51 | 1.6.1 (2018-04-17) 52 | ------------------ 53 | 54 | * Fix issue 55 | 56 | 1.6.0 (2018-01-29) 57 | ------------------ 58 | 59 | * Add more pre-defined labels 60 | * Show cursor pose in status bar 61 | * Fix minor issues 62 | 63 | 1.5.2 (2017-10-24) 64 | ------------------ 65 | 66 | * Assign different colors to different lablels 67 | 68 | 1.5.1 (2017-9-27) 69 | ------------------ 70 | 71 | * Show a autosaving dialog 72 | 73 | 1.5.0 (2017-9-14) 74 | ------------------ 75 | 76 | * Fix the issues 77 | * Add feature: Draw a box easier 78 | 79 | 80 | 1.4.3 (2017-08-09) 81 | ------------------ 82 | 83 | * Refactor setting 84 | * Fix the issues 85 | 86 | 87 | 1.4.0 (2017-07-07) 88 | ------------------ 89 | 90 | * Add feature: auto saving 91 | * Add feature: single class mode 92 | * Fix the issues 93 | 94 | 1.3.4 (2017-07-07) 95 | ------------------ 96 | 97 | * Fix issues and improve zoom-in 98 | 99 | 1.3.3 (2017-05-31) 100 | ------------------ 101 | 102 | * Fix issues 103 | 104 | 1.3.2 (2017-05-18) 105 | ------------------ 106 | 107 | * Fix issues 108 | 109 | 110 | 1.3.1 (2017-05-11) 111 | ------------------ 112 | 113 | * Fix issues 114 | 115 | 1.3.0 (2017-04-22) 116 | ------------------ 117 | 118 | * Fix issues 119 | * Add difficult tag 120 | * Create new files for pypi 121 | 122 | 1.2.3 (2017-04-22) 123 | ------------------ 124 | 125 | * Fix issues 126 | 127 | 1.2.2 (2017-01-09) 128 | ------------------ 129 | 130 | * Fix issues 131 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) <2015-Present> Tzutalin 2 | 3 | Copyright (C) 2013 MIT, Computer Science and Artificial Intelligence Laboratory. Bryan Russell, Antonio Torralba, William T. Freeman 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include CONTRIBUTING.rst 2 | include HISTORY.rst 3 | include LICENSE 4 | include README.rst 5 | 6 | include resources.qrc 7 | 8 | recursive-include data * 9 | recursive-include icons * 10 | recursive-include libs * 11 | 12 | recursive-exclude build-tools * 13 | recursive-exclude tests * 14 | recursive-exclude * __pycache__ 15 | recursive-exclude * *.py[co] 16 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # ex: set ts=8 noet: 2 | 3 | all: qt5 test 4 | 5 | test: testpy3 6 | 7 | testpy2: 8 | python -m unittest discover tests 9 | 10 | testpy3: 11 | python3 -m unittest discover tests 12 | 13 | qt4: qt4py2 14 | 15 | qt5: qt5py3 16 | 17 | qt4py2: 18 | pyrcc4 -py2 -o libs/resources.py resources.qrc 19 | 20 | qt4py3: 21 | pyrcc4 -py3 -o libs/resources.py resources.qrc 22 | 23 | qt5py3: 24 | pyrcc5 -o libs/resources.py resources.qrc 25 | 26 | clean: 27 | rm -rf ~/.labelImgSettings.pkl *.pyc dist labelImg.egg-info __pycache__ build 28 | 29 | pip_upload: 30 | python3 setup.py upload 31 | 32 | long_description: 33 | restview --long-description 34 | 35 | .PHONY: all 36 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | .. image:: /readme/images/labelimg.png 2 | :target: https://github.com/heartexlabs/label-studio 3 | 4 | Label Studio is a modern, multi-modal data annotation tool 5 | ======= 6 | 7 | LabelImg, the popular image annotation tool created by Tzutalin with the help of dozens contributors, is no longer actively being developed and has become part of the Label Studio community. Check out `Label Studio `__, the most flexible open source data labeling tool for images, text, hypertext, audio, video and time-series data. `Install `__ Label Studio and join the `slack community `__ to get started. 8 | 9 | .. image:: /readme/images/label-studio-1-6-player-screenshot.png 10 | :target: https://github.com/heartexlabs/label-studio 11 | 12 | About LabelImg 13 | ======== 14 | 15 | .. image:: https://img.shields.io/pypi/v/labelimg.svg 16 | :target: https://pypi.python.org/pypi/labelimg 17 | 18 | .. image:: https://img.shields.io/github/workflow/status/tzutalin/labelImg/Package?style=for-the-badge 19 | :alt: GitHub Workflow Status 20 | 21 | .. image:: https://img.shields.io/badge/lang-en-blue.svg 22 | :target: https://github.com/tzutalin/labelImg 23 | 24 | .. image:: https://img.shields.io/badge/lang-zh-green.svg 25 | :target: https://github.com/tzutalin/labelImg/blob/master/readme/README.zh.rst 26 | 27 | .. image:: https://img.shields.io/badge/lang-jp-green.svg 28 | :target: https://github.com/tzutalin/labelImg/blob/master/readme/README.jp.rst 29 | 30 | LabelImg is a graphical image annotation tool. 31 | 32 | It is written in Python and uses Qt for its graphical interface. 33 | 34 | Annotations are saved as XML files in PASCAL VOC format, the format used 35 | by `ImageNet `__. Besides, it also supports YOLO and CreateML formats. 36 | 37 | .. image:: https://raw.githubusercontent.com/tzutalin/labelImg/master/demo/demo3.jpg 38 | :alt: Demo Image 39 | 40 | .. image:: https://raw.githubusercontent.com/tzutalin/labelImg/master/demo/demo.jpg 41 | :alt: Demo Image 42 | 43 | `Watch a demo video `__ 44 | 45 | Installation 46 | ------------------ 47 | 48 | Get from PyPI but only python3.0 or above 49 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 50 | This is the simplest (one-command) install method on modern Linux distributions such as Ubuntu and Fedora. 51 | 52 | .. code:: shell 53 | 54 | pip3 install labelImg 55 | labelImg 56 | labelImg [IMAGE_PATH] [PRE-DEFINED CLASS FILE] 57 | 58 | 59 | Build from source 60 | ~~~~~~~~~~~~~~~~~ 61 | 62 | Linux/Ubuntu/Mac requires at least `Python 63 | 2.6 `__ and has been tested with `PyQt 64 | 4.8 `__. However, `Python 65 | 3 or above `__ and `PyQt5 `__ are strongly recommended. 66 | 67 | 68 | Ubuntu Linux 69 | ^^^^^^^^^^^^ 70 | 71 | Python 3 + Qt5 72 | 73 | .. code:: shell 74 | 75 | sudo apt-get install pyqt5-dev-tools 76 | sudo pip3 install -r requirements/requirements-linux-python3.txt 77 | make qt5py3 78 | python3 labelImg.py 79 | python3 labelImg.py [IMAGE_PATH] [PRE-DEFINED CLASS FILE] 80 | 81 | macOS 82 | ^^^^^ 83 | 84 | Python 3 + Qt5 85 | 86 | .. code:: shell 87 | 88 | brew install qt # Install qt-5.x.x by Homebrew 89 | brew install libxml2 90 | 91 | or using pip 92 | 93 | pip3 install pyqt5 lxml # Install qt and lxml by pip 94 | 95 | make qt5py3 96 | python3 labelImg.py 97 | python3 labelImg.py [IMAGE_PATH] [PRE-DEFINED CLASS FILE] 98 | 99 | 100 | Python 3 Virtualenv (Recommended) 101 | 102 | Virtualenv can avoid a lot of the QT / Python version issues 103 | 104 | .. code:: shell 105 | 106 | brew install python3 107 | pip3 install pipenv 108 | pipenv run pip install pyqt5==5.15.2 lxml 109 | pipenv run make qt5py3 110 | pipenv run python3 labelImg.py 111 | [Optional] rm -rf build dist; pipenv run python setup.py py2app -A;mv "dist/labelImg.app" /Applications 112 | 113 | Note: The Last command gives you a nice .app file with a new SVG Icon in your /Applications folder. You can consider using the script: build-tools/build-for-macos.sh 114 | 115 | 116 | Windows 117 | ^^^^^^^ 118 | 119 | Install `Python `__, 120 | `PyQt5 `__ 121 | and `install lxml `__. 122 | 123 | Open cmd and go to the `labelImg <#labelimg>`__ directory 124 | 125 | .. code:: shell 126 | 127 | pyrcc4 -o libs/resources.py resources.qrc 128 | For pyqt5, pyrcc5 -o libs/resources.py resources.qrc 129 | 130 | python labelImg.py 131 | python labelImg.py [IMAGE_PATH] [PRE-DEFINED CLASS FILE] 132 | 133 | If you want to package it into a separate EXE file 134 | 135 | .. code:: shell 136 | 137 | Install pyinstaller and execute: 138 | 139 | pip install pyinstaller 140 | pyinstaller --hidden-import=pyqt5 --hidden-import=lxml -F -n "labelImg" -c labelImg.py -p ./libs -p ./ 141 | 142 | Windows + Anaconda 143 | ^^^^^^^^^^^^^^^^^^ 144 | 145 | Download and install `Anaconda `__ (Python 3+) 146 | 147 | Open the Anaconda Prompt and go to the `labelImg <#labelimg>`__ directory 148 | 149 | .. code:: shell 150 | 151 | conda install pyqt=5 152 | conda install -c anaconda lxml 153 | pyrcc5 -o libs/resources.py resources.qrc 154 | python labelImg.py 155 | python labelImg.py [IMAGE_PATH] [PRE-DEFINED CLASS FILE] 156 | 157 | Use Docker 158 | ~~~~~~~~~~~~~~~~~ 159 | .. code:: shell 160 | 161 | docker run -it \ 162 | --user $(id -u) \ 163 | -e DISPLAY=unix$DISPLAY \ 164 | --workdir=$(pwd) \ 165 | --volume="/home/$USER:/home/$USER" \ 166 | --volume="/etc/group:/etc/group:ro" \ 167 | --volume="/etc/passwd:/etc/passwd:ro" \ 168 | --volume="/etc/shadow:/etc/shadow:ro" \ 169 | --volume="/etc/sudoers.d:/etc/sudoers.d:ro" \ 170 | -v /tmp/.X11-unix:/tmp/.X11-unix \ 171 | tzutalin/py2qt4 172 | 173 | make qt4py2;./labelImg.py 174 | 175 | You can pull the image which has all of the installed and required dependencies. `Watch a demo video `__ 176 | 177 | 178 | Usage 179 | ----- 180 | 181 | Steps (PascalVOC) 182 | ~~~~~~~~~~~~~~~~~ 183 | 184 | 1. Build and launch using the instructions above. 185 | 2. Click 'Change default saved annotation folder' in Menu/File 186 | 3. Click 'Open Dir' 187 | 4. Click 'Create RectBox' 188 | 5. Click and release left mouse to select a region to annotate the rect 189 | box 190 | 6. You can use right mouse to drag the rect box to copy or move it 191 | 192 | The annotation will be saved to the folder you specify. 193 | 194 | You can refer to the below hotkeys to speed up your workflow. 195 | 196 | Steps (YOLO) 197 | ~~~~~~~~~~~~ 198 | 199 | 1. In ``data/predefined_classes.txt`` define the list of classes that will be used for your training. 200 | 201 | 2. Build and launch using the instructions above. 202 | 203 | 3. Right below "Save" button in the toolbar, click "PascalVOC" button to switch to YOLO format. 204 | 205 | 4. You may use Open/OpenDIR to process single or multiple images. When finished with a single image, click save. 206 | 207 | A txt file of YOLO format will be saved in the same folder as your image with same name. A file named "classes.txt" is saved to that folder too. "classes.txt" defines the list of class names that your YOLO label refers to. 208 | 209 | Note: 210 | 211 | - Your label list shall not change in the middle of processing a list of images. When you save an image, classes.txt will also get updated, while previous annotations will not be updated. 212 | 213 | - You shouldn't use "default class" function when saving to YOLO format, it will not be referred. 214 | 215 | - When saving as YOLO format, "difficult" flag is discarded. 216 | 217 | Create pre-defined classes 218 | ~~~~~~~~~~~~~~~~~~~~~~~~~~ 219 | 220 | You can edit the 221 | `data/predefined\_classes.txt `__ 222 | to load pre-defined classes 223 | 224 | Annotation visualization 225 | ~~~~~~~~~~~~~~~~~~~~~~~~ 226 | 227 | 1. Copy the existing lables file to same folder with the images. The labels file name must be same with image file name. 228 | 229 | 2. Click File and choose 'Open Dir' then Open the image folder. 230 | 231 | 3. Select image in File List, it will appear the bounding box and label for all objects in that image. 232 | 233 | (Choose Display Labels mode in View to show/hide lablels) 234 | 235 | 236 | Hotkeys 237 | ~~~~~~~ 238 | 239 | +--------------------+--------------------------------------------+ 240 | | Ctrl + u | Load all of the images from a directory | 241 | +--------------------+--------------------------------------------+ 242 | | Ctrl + r | Change the default annotation target dir | 243 | +--------------------+--------------------------------------------+ 244 | | Ctrl + s | Save | 245 | +--------------------+--------------------------------------------+ 246 | | Ctrl + d | Copy the current label and rect box | 247 | +--------------------+--------------------------------------------+ 248 | | Ctrl + Shift + d | Delete the current image | 249 | +--------------------+--------------------------------------------+ 250 | | Space | Flag the current image as verified | 251 | +--------------------+--------------------------------------------+ 252 | | w | Create a rect box | 253 | +--------------------+--------------------------------------------+ 254 | | d | Next image | 255 | +--------------------+--------------------------------------------+ 256 | | a | Previous image | 257 | +--------------------+--------------------------------------------+ 258 | | del | Delete the selected rect box | 259 | +--------------------+--------------------------------------------+ 260 | | Ctrl++ | Zoom in | 261 | +--------------------+--------------------------------------------+ 262 | | Ctrl-- | Zoom out | 263 | +--------------------+--------------------------------------------+ 264 | | ↑→↓← | Keyboard arrows to move selected rect box | 265 | +--------------------+--------------------------------------------+ 266 | 267 | **Verify Image:** 268 | 269 | When pressing space, the user can flag the image as verified, a green background will appear. 270 | This is used when creating a dataset automatically, the user can then through all the pictures and flag them instead of annotate them. 271 | 272 | **Difficult:** 273 | 274 | The difficult field is set to 1 indicates that the object has been annotated as "difficult", for example, an object which is clearly visible but difficult to recognize without substantial use of context. 275 | According to your deep neural network implementation, you can include or exclude difficult objects during training. 276 | 277 | How to reset the settings 278 | ~~~~~~~~~~~~~~~~~~~~~~~~~ 279 | 280 | In case there are issues with loading the classes, you can either: 281 | 282 | 1. From the top menu of the labelimg click on Menu/File/Reset All 283 | 2. Remove the `.labelImgSettings.pkl` from your home directory. In Linux and Mac you can do: 284 | `rm ~/.labelImgSettings.pkl` 285 | 286 | 287 | How to contribute 288 | ~~~~~~~~~~~~~~~~~ 289 | 290 | Send a pull request 291 | 292 | License 293 | ~~~~~~~ 294 | `Free software: MIT license `_ 295 | 296 | Citation: Tzutalin. LabelImg. Git code (2015). https://github.com/tzutalin/labelImg 297 | 298 | Related and additional tools 299 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 300 | 301 | 1. `Label Studio `__ to label images, text, audio, video and time-series data for machine learning and AI 302 | 2. `ImageNet Utils `__ to 303 | download image, create a label text for machine learning, etc 304 | 3. `Use Docker to run labelImg `__ 305 | 4. `Generating the PASCAL VOC TFRecord files `__ 306 | 5. `App Icon based on Icon by Nick Roach (GPL) `__ 307 | 6. `Setup python development in vscode `__ 308 | 7. `The link of this project on iHub platform `__ 309 | 8. `Convert annotation files to CSV format or format for Google Cloud AutoML `__ 310 | 311 | 312 | 313 | Stargazers over time 314 | ~~~~~~~~~~~~~~~~~~~~ 315 | 316 | .. image:: https://starchart.cc/tzutalin/labelImg.svg 317 | 318 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HumanSignal/labelImg/b33f965b6d14c14f1e46b247f1bf346e03f2e950/__init__.py -------------------------------------------------------------------------------- /build-tools/.gitignore: -------------------------------------------------------------------------------- 1 | *.spec 2 | build 3 | dist 4 | pyinstaller 5 | python-2.* 6 | pywin32* 7 | virtual-wine 8 | venv_wine 9 | PyQt4-* 10 | lxml-* 11 | windows_v* 12 | linux_v* 13 | -------------------------------------------------------------------------------- /build-tools/README.md: -------------------------------------------------------------------------------- 1 | ### Deploy to PyPI 2 | 3 | ``` 4 | cd [ROOT] 5 | sh build-tools/build-for-pypi.sh 6 | ``` 7 | 8 | ### Build for Ubuntu 9 | 10 | ``` 11 | cd build-tools 12 | sh run-in-container.sh 13 | sh envsetup.sh 14 | sh build-ubuntu-binary.sh 15 | ``` 16 | 17 | ### Build for Windows 18 | 19 | ``` 20 | cd build-tools 21 | sh run-in-container.sh 22 | sh envsetup.sh 23 | sh build-windows-binary.sh 24 | ``` 25 | 26 | ### Build for macOS High Sierra 27 | ``` 28 | cd build-tools 29 | ./build-for-macos.sh 30 | ``` 31 | 32 | Note: If there are some problems, try to 33 | ``` 34 | sudo rm -rf virtual-wne venv_wine 35 | ``` 36 | -------------------------------------------------------------------------------- /build-tools/build-for-macos.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | brew install python@2 4 | pip install --upgrade virtualenv 5 | 6 | # clone labelimg source 7 | rm -rf /tmp/labelImgSetup 8 | mkdir /tmp/labelImgSetup 9 | cd /tmp/labelImgSetup 10 | curl https://codeload.github.com/tzutalin/labelImg/zip/master --output labelImg.zip 11 | unzip labelImg.zip 12 | rm labelImg.zip 13 | 14 | # setup python3 space 15 | virtualenv --system-site-packages -p python3 /tmp/labelImgSetup/labelImg-py3 16 | source /tmp/labelImgSetup/labelImg-py3/bin/activate 17 | cd labelImg-master 18 | 19 | # build labelImg app 20 | pip install py2app 21 | pip install PyQt5 lxml 22 | make qt5py3 23 | rm -rf build dist 24 | python setup.py py2app -A 25 | mv "/tmp/labelImgSetup/labelImg-master/dist/labelImg.app" /Applications 26 | # deactivate python3 27 | deactivate 28 | cd ../ 29 | rm -rf /tmp/labelImgSetup 30 | echo 'DONE' 31 | -------------------------------------------------------------------------------- /build-tools/build-for-pypi.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Packaging and Release 3 | 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' 4 | 5 | while true; do 6 | read -p "Do you wish to deploy this to PyPI(twine upload dist/* or pip install dist/*)?" yn 7 | case $yn in 8 | [Yy]* ) docker run -it --rm --workdir=$(pwd)/ --volume="/home/$USER:/home/$USER" tzutalin/py2qt4; break;; 9 | [Nn]* ) exit;; 10 | * ) echo "Please answer yes or no.";; 11 | esac 12 | done 13 | # python setup.py register 14 | # python setup.py sdist upload 15 | # Net pypi: twine upload dist/* 16 | 17 | # Test before upladoing: pip install dist/labelImg.tar.gz 18 | -------------------------------------------------------------------------------- /build-tools/build-ubuntu-binary.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ### Ubuntu use pyinstall v3.0 3 | THIS_SCRIPT_PATH=`readlink -f $0` 4 | THIS_SCRIPT_DIR=`dirname ${THIS_SCRIPT_PATH}` 5 | cd pyinstaller 6 | git checkout v3.2 7 | cd ${THIS_SCRIPT_DIR} 8 | 9 | rm -r build 10 | rm -r dist 11 | rm labelImg.spec 12 | python pyinstaller/pyinstaller.py --hidden-import=xml \ 13 | --hidden-import=xml.etree \ 14 | --hidden-import=xml.etree.ElementTree \ 15 | --hidden-import=lxml.etree \ 16 | -D -F -n labelImg -c "../labelImg.py" -p ../libs -p ../ 17 | 18 | FOLDER=$(git describe --abbrev=0 --tags) 19 | FOLDER="linux_"$FOLDER 20 | rm -rf "$FOLDER" 21 | mkdir "$FOLDER" 22 | cp dist/labelImg $FOLDER 23 | cp -rf ../data $FOLDER/data 24 | zip "$FOLDER.zip" -r $FOLDER 25 | -------------------------------------------------------------------------------- /build-tools/build-windows-binary.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ### Window requires pyinstall v2.1 3 | wine msiexec -i python-2.7.8.msi 4 | wine pywin32-218.win32-py2.7.exe 5 | wine PyQt4-4.11.4-gpl-Py2.7-Qt4.8.7-x32.exe 6 | wine lxml-3.7.3.win32-py2.7.exe 7 | 8 | THIS_SCRIPT_PATH=`readlink -f $0` 9 | THIS_SCRIPT_DIR=`dirname ${THIS_SCRIPT_PATH}` 10 | cd pyinstaller 11 | git checkout v2.1 12 | cd ${THIS_SCRIPT_DIR} 13 | echo ${THIS_SCRIPT_DIR} 14 | 15 | #. venv_wine/bin/activate 16 | rm -r build 17 | rm -r dist 18 | rm labelImg.spec 19 | 20 | wine c:/Python27/python.exe pyinstaller/pyinstaller.py --hidden-import=xml \ 21 | --hidden-import=xml.etree \ 22 | --hidden-import=xml.etree.ElementTree \ 23 | --hidden-import=lxml.etree \ 24 | -D -F -n labelImg -c "../labelImg.py" -p ../libs -p ../ 25 | 26 | FOLDER=$(git describe --abbrev=0 --tags) 27 | FOLDER="windows_"$FOLDER 28 | rm -rf "$FOLDER" 29 | mkdir "$FOLDER" 30 | cp dist/labelImg.exe $FOLDER 31 | cp -rf ../data $FOLDER/data 32 | zip "$FOLDER.zip" -r $FOLDER 33 | -------------------------------------------------------------------------------- /build-tools/envsetup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | THIS_SCRIPT_PATH=`readlink -f $0` 4 | THIS_SCRIPT_DIR=`dirname ${THIS_SCRIPT_PATH}` 5 | #OS Ubuntu 14.04 6 | ### Common packages for linux/windows 7 | if [ ! -e "pyinstaller" ]; then 8 | git clone https://github.com/pyinstaller/pyinstaller 9 | cd pyinstaller 10 | git checkout v2.1 -b v2.1 11 | cd ${THIS_SCRIPT_DIR} 12 | fi 13 | 14 | echo "Going to clone and download packages for building windows" 15 | #Pacakges 16 | #> pyinstaller (2.1) 17 | #> wine (1.6.2) 18 | #> virtual-wine (0.1) 19 | #> python-2.7.8.msi 20 | #> pywin32-218.win32-py2.7.exe 21 | 22 | ## tool to install on Ubuntu 23 | #$ sudo apt-get install wine 24 | 25 | ### Clone a repo to create virtual wine env 26 | if [ ! -e "virtual-wine" ]; then 27 | git clone https://github.com/htgoebel/virtual-wine.git 28 | fi 29 | 30 | apt-get install scons 31 | ### Create virtual env 32 | rm -rf venv_wine 33 | ./virtual-wine/vwine-setup venv_wine 34 | #### Active virutal env 35 | . venv_wine/bin/activate 36 | 37 | ### Use wine to install packages to virtual env 38 | if [ ! -e "python-2.7.8.msi" ]; then 39 | wget "https://www.python.org/ftp/python/2.7.8/python-2.7.8.msi" 40 | fi 41 | 42 | if [ ! -e "pywin32-218.win32-py2.7.exe" ]; then 43 | wget "http://nchc.dl.sourceforge.net/project/pywin32/pywin32/Build%20218/pywin32-218.win32-py2.7.exe" 44 | fi 45 | 46 | if [ ! -e "PyQt4-4.11.4-gpl-Py2.7-Qt4.8.7-x32.exe" ]; then 47 | 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" 48 | fi 49 | 50 | if [ ! -e "lxml-3.7.3.win32-py2.7.exe" ]; then 51 | wget "https://pypi.python.org/packages/a3/f6/a28c5cf63873f6c55a3eb7857b736379229b85ba918261d2e88cf886905e/lxml-3.7.3.win32-py2.7.exe#md5=a0f746355876aca4ca5371cb0f1d13ce" 52 | fi 53 | 54 | -------------------------------------------------------------------------------- /build-tools/run-in-container.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | docker run -it \ 3 | --user $(id -u) \ 4 | -e DISPLAY=unix$DISPLAY \ 5 | --workdir=$(pwd) \ 6 | --volume="/home/$USER:/home/$USER" \ 7 | --volume="/etc/group:/etc/group:ro" \ 8 | --volume="/etc/passwd:/etc/passwd:ro" \ 9 | --volume="/etc/shadow:/etc/shadow:ro" \ 10 | --volume="/etc/sudoers.d:/etc/sudoers.d:ro" \ 11 | -v /tmp/.X11-unix:/tmp/.X11-unix \ 12 | tzutalin/py2qt4 13 | 14 | -------------------------------------------------------------------------------- /data/predefined_classes.txt: -------------------------------------------------------------------------------- 1 | dog 2 | person 3 | cat 4 | tv 5 | car 6 | meatballs 7 | marinara sauce 8 | tomato soup 9 | chicken noodle soup 10 | french onion soup 11 | chicken breast 12 | ribs 13 | pulled pork 14 | hamburger 15 | cavity -------------------------------------------------------------------------------- /demo/demo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HumanSignal/labelImg/b33f965b6d14c14f1e46b247f1bf346e03f2e950/demo/demo.jpg -------------------------------------------------------------------------------- /demo/demo3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HumanSignal/labelImg/b33f965b6d14c14f1e46b247f1bf346e03f2e950/demo/demo3.jpg -------------------------------------------------------------------------------- /demo/demo4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HumanSignal/labelImg/b33f965b6d14c14f1e46b247f1bf346e03f2e950/demo/demo4.png -------------------------------------------------------------------------------- /demo/demo5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HumanSignal/labelImg/b33f965b6d14c14f1e46b247f1bf346e03f2e950/demo/demo5.png -------------------------------------------------------------------------------- /issue_template.md: -------------------------------------------------------------------------------- 1 | 5 | 6 | - **OS:** 7 | - **PyQt version:** 8 | -------------------------------------------------------------------------------- /libs/__init__.py: -------------------------------------------------------------------------------- 1 | __version_info__ = ('1', '8', '6') 2 | __version__ = '.'.join(__version_info__) 3 | -------------------------------------------------------------------------------- /libs/colorDialog.py: -------------------------------------------------------------------------------- 1 | try: 2 | from PyQt5.QtGui import * 3 | from PyQt5.QtCore import * 4 | from PyQt5.QtWidgets import QColorDialog, QDialogButtonBox 5 | except ImportError: 6 | from PyQt4.QtGui import * 7 | from PyQt4.QtCore import * 8 | 9 | BB = QDialogButtonBox 10 | 11 | 12 | class ColorDialog(QColorDialog): 13 | 14 | def __init__(self, parent=None): 15 | super(ColorDialog, self).__init__(parent) 16 | self.setOption(QColorDialog.ShowAlphaChannel) 17 | # The Mac native dialog does not support our restore button. 18 | self.setOption(QColorDialog.DontUseNativeDialog) 19 | # Add a restore defaults button. 20 | # The default is set at invocation time, so that it 21 | # works across dialogs for different elements. 22 | self.default = None 23 | self.bb = self.layout().itemAt(1).widget() 24 | self.bb.addButton(BB.RestoreDefaults) 25 | self.bb.clicked.connect(self.check_restore) 26 | 27 | def getColor(self, value=None, title=None, default=None): 28 | self.default = default 29 | if title: 30 | self.setWindowTitle(title) 31 | if value: 32 | self.setCurrentColor(value) 33 | return self.currentColor() if self.exec_() else None 34 | 35 | def check_restore(self, button): 36 | if self.bb.buttonRole(button) & BB.ResetRole and self.default: 37 | self.setCurrentColor(self.default) 38 | -------------------------------------------------------------------------------- /libs/combobox.py: -------------------------------------------------------------------------------- 1 | import sys 2 | try: 3 | from PyQt5.QtWidgets import QWidget, QHBoxLayout, QComboBox 4 | except ImportError: 5 | # needed for py3+qt4 6 | # Ref: 7 | # http://pyqt.sourceforge.net/Docs/PyQt4/incompatible_apis.html 8 | # http://stackoverflow.com/questions/21217399/pyqt4-qtcore-qvariant-object-instead-of-a-string 9 | if sys.version_info.major >= 3: 10 | import sip 11 | sip.setapi('QVariant', 2) 12 | from PyQt4.QtGui import QWidget, QHBoxLayout, QComboBox 13 | 14 | 15 | class ComboBox(QWidget): 16 | def __init__(self, parent=None, items=[]): 17 | super(ComboBox, self).__init__(parent) 18 | 19 | layout = QHBoxLayout() 20 | self.cb = QComboBox() 21 | self.items = items 22 | self.cb.addItems(self.items) 23 | 24 | self.cb.currentIndexChanged.connect(parent.combo_selection_changed) 25 | 26 | layout.addWidget(self.cb) 27 | self.setLayout(layout) 28 | 29 | def update_items(self, items): 30 | self.items = items 31 | 32 | self.cb.clear() 33 | self.cb.addItems(self.items) 34 | -------------------------------------------------------------------------------- /libs/constants.py: -------------------------------------------------------------------------------- 1 | SETTING_FILENAME = 'filename' 2 | SETTING_RECENT_FILES = 'recentFiles' 3 | SETTING_WIN_SIZE = 'window/size' 4 | SETTING_WIN_POSE = 'window/position' 5 | SETTING_WIN_GEOMETRY = 'window/geometry' 6 | SETTING_LINE_COLOR = 'line/color' 7 | SETTING_FILL_COLOR = 'fill/color' 8 | SETTING_ADVANCE_MODE = 'advanced' 9 | SETTING_WIN_STATE = 'window/state' 10 | SETTING_SAVE_DIR = 'savedir' 11 | SETTING_PAINT_LABEL = 'paintlabel' 12 | SETTING_LAST_OPEN_DIR = 'lastOpenDir' 13 | SETTING_AUTO_SAVE = 'autosave' 14 | SETTING_SINGLE_CLASS = 'singleclass' 15 | FORMAT_PASCALVOC='PascalVOC' 16 | FORMAT_YOLO='YOLO' 17 | FORMAT_CREATEML='CreateML' 18 | SETTING_DRAW_SQUARE = 'draw/square' 19 | SETTING_LABEL_FILE_FORMAT= 'labelFileFormat' 20 | DEFAULT_ENCODING = 'utf-8' 21 | -------------------------------------------------------------------------------- /libs/create_ml_io.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf8 -*- 3 | import json 4 | from pathlib import Path 5 | 6 | from libs.constants import DEFAULT_ENCODING 7 | import os 8 | 9 | JSON_EXT = '.json' 10 | ENCODE_METHOD = DEFAULT_ENCODING 11 | 12 | 13 | class CreateMLWriter: 14 | def __init__(self, folder_name, filename, img_size, shapes, output_file, database_src='Unknown', local_img_path=None): 15 | self.folder_name = folder_name 16 | self.filename = filename 17 | self.database_src = database_src 18 | self.img_size = img_size 19 | self.box_list = [] 20 | self.local_img_path = local_img_path 21 | self.verified = False 22 | self.shapes = shapes 23 | self.output_file = output_file 24 | 25 | def write(self): 26 | if os.path.isfile(self.output_file): 27 | with open(self.output_file, "r") as file: 28 | input_data = file.read() 29 | output_dict = json.loads(input_data) 30 | else: 31 | output_dict = [] 32 | 33 | output_image_dict = { 34 | "image": self.filename, 35 | "verified": self.verified, 36 | "annotations": [] 37 | } 38 | 39 | for shape in self.shapes: 40 | points = shape["points"] 41 | 42 | x1 = points[0][0] 43 | y1 = points[0][1] 44 | x2 = points[1][0] 45 | y2 = points[2][1] 46 | 47 | height, width, x, y = self.calculate_coordinates(x1, x2, y1, y2) 48 | 49 | shape_dict = { 50 | "label": shape["label"], 51 | "coordinates": { 52 | "x": x, 53 | "y": y, 54 | "width": width, 55 | "height": height 56 | } 57 | } 58 | output_image_dict["annotations"].append(shape_dict) 59 | 60 | # check if image already in output 61 | exists = False 62 | for i in range(0, len(output_dict)): 63 | if output_dict[i]["image"] == output_image_dict["image"]: 64 | exists = True 65 | output_dict[i] = output_image_dict 66 | break 67 | 68 | if not exists: 69 | output_dict.append(output_image_dict) 70 | 71 | Path(self.output_file).write_text(json.dumps(output_dict), ENCODE_METHOD) 72 | 73 | def calculate_coordinates(self, x1, x2, y1, y2): 74 | if x1 < x2: 75 | x_min = x1 76 | x_max = x2 77 | else: 78 | x_min = x2 79 | x_max = x1 80 | if y1 < y2: 81 | y_min = y1 82 | y_max = y2 83 | else: 84 | y_min = y2 85 | y_max = y1 86 | width = x_max - x_min 87 | if width < 0: 88 | width = width * -1 89 | height = y_max - y_min 90 | # x and y from center of rect 91 | x = x_min + width / 2 92 | y = y_min + height / 2 93 | return height, width, x, y 94 | 95 | 96 | class CreateMLReader: 97 | def __init__(self, json_path, file_path): 98 | self.json_path = json_path 99 | self.shapes = [] 100 | self.verified = False 101 | self.filename = os.path.basename(file_path) 102 | try: 103 | self.parse_json() 104 | except ValueError: 105 | print("JSON decoding failed") 106 | 107 | def parse_json(self): 108 | with open(self.json_path, "r") as file: 109 | input_data = file.read() 110 | 111 | # Returns a list 112 | output_list = json.loads(input_data) 113 | 114 | if output_list: 115 | self.verified = output_list[0].get("verified", False) 116 | 117 | if len(self.shapes) > 0: 118 | self.shapes = [] 119 | for image in output_list: 120 | if image["image"] == self.filename: 121 | for shape in image["annotations"]: 122 | self.add_shape(shape["label"], shape["coordinates"]) 123 | 124 | def add_shape(self, label, bnd_box): 125 | x_min = bnd_box["x"] - (bnd_box["width"] / 2) 126 | y_min = bnd_box["y"] - (bnd_box["height"] / 2) 127 | 128 | x_max = bnd_box["x"] + (bnd_box["width"] / 2) 129 | y_max = bnd_box["y"] + (bnd_box["height"] / 2) 130 | 131 | points = [(x_min, y_min), (x_max, y_min), (x_max, y_max), (x_min, y_max)] 132 | self.shapes.append((label, points, None, None, True)) 133 | 134 | def get_shapes(self): 135 | return self.shapes 136 | -------------------------------------------------------------------------------- /libs/default_label_combobox.py: -------------------------------------------------------------------------------- 1 | import sys 2 | try: 3 | from PyQt5.QtWidgets import QWidget, QHBoxLayout, QComboBox 4 | except ImportError: 5 | # needed for py3+qt4 6 | # Ref: 7 | # http://pyqt.sourceforge.net/Docs/PyQt4/incompatible_apis.html 8 | # http://stackoverflow.com/questions/21217399/pyqt4-qtcore-qvariant-object-instead-of-a-string 9 | if sys.version_info.major >= 3: 10 | import sip 11 | sip.setapi('QVariant', 2) 12 | from PyQt4.QtGui import QWidget, QHBoxLayout, QComboBox 13 | 14 | 15 | class DefaultLabelComboBox(QWidget): 16 | def __init__(self, parent=None, items=[]): 17 | super(DefaultLabelComboBox, self).__init__(parent) 18 | 19 | layout = QHBoxLayout() 20 | self.cb = QComboBox() 21 | self.items = items 22 | self.cb.addItems(self.items) 23 | 24 | self.cb.currentIndexChanged.connect(parent.default_label_combo_selection_changed) 25 | 26 | layout.addWidget(self.cb) 27 | self.setLayout(layout) 28 | -------------------------------------------------------------------------------- /libs/hashableQListWidgetItem.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | import sys 4 | try: 5 | from PyQt5.QtGui import * 6 | from PyQt5.QtCore import * 7 | from PyQt5.QtWidgets import * 8 | except ImportError: 9 | # needed for py3+qt4 10 | # Ref: 11 | # http://pyqt.sourceforge.net/Docs/PyQt4/incompatible_apis.html 12 | # http://stackoverflow.com/questions/21217399/pyqt4-qtcore-qvariant-object-instead-of-a-string 13 | if sys.version_info.major >= 3: 14 | import sip 15 | sip.setapi('QVariant', 2) 16 | from PyQt4.QtGui import * 17 | from PyQt4.QtCore import * 18 | 19 | # PyQt5: TypeError: unhashable type: 'QListWidgetItem' 20 | 21 | 22 | class HashableQListWidgetItem(QListWidgetItem): 23 | 24 | def __init__(self, *args): 25 | super(HashableQListWidgetItem, self).__init__(*args) 26 | 27 | def __hash__(self): 28 | return hash(id(self)) 29 | -------------------------------------------------------------------------------- /libs/labelDialog.py: -------------------------------------------------------------------------------- 1 | try: 2 | from PyQt5.QtGui import * 3 | from PyQt5.QtCore import * 4 | from PyQt5.QtWidgets import * 5 | except ImportError: 6 | from PyQt4.QtGui import * 7 | from PyQt4.QtCore import * 8 | 9 | from libs.utils import new_icon, label_validator, trimmed 10 | 11 | BB = QDialogButtonBox 12 | 13 | 14 | class LabelDialog(QDialog): 15 | 16 | def __init__(self, text="Enter object label", parent=None, list_item=None): 17 | super(LabelDialog, self).__init__(parent) 18 | 19 | self.edit = QLineEdit() 20 | self.edit.setText(text) 21 | self.edit.setValidator(label_validator()) 22 | self.edit.editingFinished.connect(self.post_process) 23 | 24 | model = QStringListModel() 25 | model.setStringList(list_item) 26 | completer = QCompleter() 27 | completer.setModel(model) 28 | self.edit.setCompleter(completer) 29 | 30 | self.button_box = bb = BB(BB.Ok | BB.Cancel, Qt.Horizontal, self) 31 | bb.button(BB.Ok).setIcon(new_icon('done')) 32 | bb.button(BB.Cancel).setIcon(new_icon('undo')) 33 | bb.accepted.connect(self.validate) 34 | bb.rejected.connect(self.reject) 35 | 36 | layout = QVBoxLayout() 37 | layout.addWidget(bb, alignment=Qt.AlignmentFlag.AlignLeft) 38 | layout.addWidget(self.edit) 39 | 40 | if list_item is not None and len(list_item) > 0: 41 | self.list_widget = QListWidget(self) 42 | for item in list_item: 43 | self.list_widget.addItem(item) 44 | self.list_widget.itemClicked.connect(self.list_item_click) 45 | self.list_widget.itemDoubleClicked.connect(self.list_item_double_click) 46 | layout.addWidget(self.list_widget) 47 | 48 | self.setLayout(layout) 49 | 50 | def validate(self): 51 | if trimmed(self.edit.text()): 52 | self.accept() 53 | 54 | def post_process(self): 55 | self.edit.setText(trimmed(self.edit.text())) 56 | 57 | def pop_up(self, text='', move=True): 58 | """ 59 | Shows the dialog, setting the current text to `text`, and blocks the caller until the user has made a choice. 60 | If the user entered a label, that label is returned, otherwise (i.e. if the user cancelled the action) 61 | `None` is returned. 62 | """ 63 | self.edit.setText(text) 64 | self.edit.setSelection(0, len(text)) 65 | self.edit.setFocus(Qt.PopupFocusReason) 66 | if move: 67 | cursor_pos = QCursor.pos() 68 | 69 | # move OK button below cursor 70 | btn = self.button_box.buttons()[0] 71 | self.adjustSize() 72 | btn.adjustSize() 73 | offset = btn.mapToGlobal(btn.pos()) - self.pos() 74 | offset += QPoint(btn.size().width() // 4, btn.size().height() // 2) 75 | cursor_pos.setX(max(0, cursor_pos.x() - offset.x())) 76 | cursor_pos.setY(max(0, cursor_pos.y() - offset.y())) 77 | 78 | parent_bottom_right = self.parentWidget().geometry() 79 | max_x = parent_bottom_right.x() + parent_bottom_right.width() - self.sizeHint().width() 80 | max_y = parent_bottom_right.y() + parent_bottom_right.height() - self.sizeHint().height() 81 | max_global = self.parentWidget().mapToGlobal(QPoint(max_x, max_y)) 82 | if cursor_pos.x() > max_global.x(): 83 | cursor_pos.setX(max_global.x()) 84 | if cursor_pos.y() > max_global.y(): 85 | cursor_pos.setY(max_global.y()) 86 | self.move(cursor_pos) 87 | return trimmed(self.edit.text()) if self.exec_() else None 88 | 89 | def list_item_click(self, t_qlist_widget_item): 90 | text = trimmed(t_qlist_widget_item.text()) 91 | self.edit.setText(text) 92 | 93 | def list_item_double_click(self, t_qlist_widget_item): 94 | self.list_item_click(t_qlist_widget_item) 95 | self.validate() 96 | -------------------------------------------------------------------------------- /libs/labelFile.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Tzutalin 2 | # Create by TzuTaLin 3 | 4 | try: 5 | from PyQt5.QtGui import QImage 6 | except ImportError: 7 | from PyQt4.QtGui import QImage 8 | 9 | import os.path 10 | from enum import Enum 11 | 12 | from libs.create_ml_io import CreateMLWriter 13 | from libs.pascal_voc_io import PascalVocWriter 14 | from libs.pascal_voc_io import XML_EXT 15 | from libs.yolo_io import YOLOWriter 16 | 17 | 18 | class LabelFileFormat(Enum): 19 | PASCAL_VOC = 1 20 | YOLO = 2 21 | CREATE_ML = 3 22 | 23 | 24 | class LabelFileError(Exception): 25 | pass 26 | 27 | 28 | class LabelFile(object): 29 | # It might be changed as window creates. By default, using XML ext 30 | # suffix = '.lif' 31 | suffix = XML_EXT 32 | 33 | def __init__(self, filename=None): 34 | self.shapes = () 35 | self.image_path = None 36 | self.image_data = None 37 | self.verified = False 38 | 39 | def save_create_ml_format(self, filename, shapes, image_path, image_data, class_list, line_color=None, fill_color=None, database_src=None): 40 | img_folder_name = os.path.basename(os.path.dirname(image_path)) 41 | img_file_name = os.path.basename(image_path) 42 | 43 | image = QImage() 44 | image.load(image_path) 45 | image_shape = [image.height(), image.width(), 46 | 1 if image.isGrayscale() else 3] 47 | writer = CreateMLWriter(img_folder_name, img_file_name, 48 | image_shape, shapes, filename, local_img_path=image_path) 49 | writer.verified = self.verified 50 | writer.write() 51 | return 52 | 53 | 54 | def save_pascal_voc_format(self, filename, shapes, image_path, image_data, 55 | line_color=None, fill_color=None, database_src=None): 56 | img_folder_path = os.path.dirname(image_path) 57 | img_folder_name = os.path.split(img_folder_path)[-1] 58 | img_file_name = os.path.basename(image_path) 59 | # imgFileNameWithoutExt = os.path.splitext(img_file_name)[0] 60 | # Read from file path because self.imageData might be empty if saving to 61 | # Pascal format 62 | if isinstance(image_data, QImage): 63 | image = image_data 64 | else: 65 | image = QImage() 66 | image.load(image_path) 67 | image_shape = [image.height(), image.width(), 68 | 1 if image.isGrayscale() else 3] 69 | writer = PascalVocWriter(img_folder_name, img_file_name, 70 | image_shape, local_img_path=image_path) 71 | writer.verified = self.verified 72 | 73 | for shape in shapes: 74 | points = shape['points'] 75 | label = shape['label'] 76 | # Add Chris 77 | difficult = int(shape['difficult']) 78 | bnd_box = LabelFile.convert_points_to_bnd_box(points) 79 | writer.add_bnd_box(bnd_box[0], bnd_box[1], bnd_box[2], bnd_box[3], label, difficult) 80 | 81 | writer.save(target_file=filename) 82 | return 83 | 84 | def save_yolo_format(self, filename, shapes, image_path, image_data, class_list, 85 | line_color=None, fill_color=None, database_src=None): 86 | img_folder_path = os.path.dirname(image_path) 87 | img_folder_name = os.path.split(img_folder_path)[-1] 88 | img_file_name = os.path.basename(image_path) 89 | # imgFileNameWithoutExt = os.path.splitext(img_file_name)[0] 90 | # Read from file path because self.imageData might be empty if saving to 91 | # Pascal format 92 | if isinstance(image_data, QImage): 93 | image = image_data 94 | else: 95 | image = QImage() 96 | image.load(image_path) 97 | image_shape = [image.height(), image.width(), 98 | 1 if image.isGrayscale() else 3] 99 | writer = YOLOWriter(img_folder_name, img_file_name, 100 | image_shape, local_img_path=image_path) 101 | writer.verified = self.verified 102 | 103 | for shape in shapes: 104 | points = shape['points'] 105 | label = shape['label'] 106 | # Add Chris 107 | difficult = int(shape['difficult']) 108 | bnd_box = LabelFile.convert_points_to_bnd_box(points) 109 | writer.add_bnd_box(bnd_box[0], bnd_box[1], bnd_box[2], bnd_box[3], label, difficult) 110 | 111 | writer.save(target_file=filename, class_list=class_list) 112 | return 113 | 114 | def toggle_verify(self): 115 | self.verified = not self.verified 116 | 117 | ''' ttf is disable 118 | def load(self, filename): 119 | import json 120 | with open(filename, 'rb') as f: 121 | data = json.load(f) 122 | imagePath = data['imagePath'] 123 | imageData = b64decode(data['imageData']) 124 | lineColor = data['lineColor'] 125 | fillColor = data['fillColor'] 126 | shapes = ((s['label'], s['points'], s['line_color'], s['fill_color'])\ 127 | for s in data['shapes']) 128 | # Only replace data after everything is loaded. 129 | self.shapes = shapes 130 | self.imagePath = imagePath 131 | self.imageData = imageData 132 | self.lineColor = lineColor 133 | self.fillColor = fillColor 134 | 135 | def save(self, filename, shapes, imagePath, imageData, lineColor=None, fillColor=None): 136 | import json 137 | with open(filename, 'wb') as f: 138 | json.dump(dict( 139 | shapes=shapes, 140 | lineColor=lineColor, fillColor=fillColor, 141 | imagePath=imagePath, 142 | imageData=b64encode(imageData)), 143 | f, ensure_ascii=True, indent=2) 144 | ''' 145 | 146 | @staticmethod 147 | def is_label_file(filename): 148 | file_suffix = os.path.splitext(filename)[1].lower() 149 | return file_suffix == LabelFile.suffix 150 | 151 | @staticmethod 152 | def convert_points_to_bnd_box(points): 153 | x_min = float('inf') 154 | y_min = float('inf') 155 | x_max = float('-inf') 156 | y_max = float('-inf') 157 | for p in points: 158 | x = p[0] 159 | y = p[1] 160 | x_min = min(x, x_min) 161 | y_min = min(y, y_min) 162 | x_max = max(x, x_max) 163 | y_max = max(y, y_max) 164 | 165 | # Martin Kersner, 2015/11/12 166 | # 0-valued coordinates of BB caused an error while 167 | # training faster-rcnn object detector. 168 | if x_min < 1: 169 | x_min = 1 170 | 171 | if y_min < 1: 172 | y_min = 1 173 | 174 | return int(x_min), int(y_min), int(x_max), int(y_max) 175 | -------------------------------------------------------------------------------- /libs/lightWidget.py: -------------------------------------------------------------------------------- 1 | try: 2 | from PyQt5.QtGui import * 3 | from PyQt5.QtCore import * 4 | from PyQt5.QtWidgets import * 5 | except ImportError: 6 | from PyQt4.QtGui import * 7 | from PyQt4.QtCore import * 8 | 9 | 10 | class LightWidget(QSpinBox): 11 | 12 | def __init__(self, title, value=50): 13 | super(LightWidget, self).__init__() 14 | self.setButtonSymbols(QAbstractSpinBox.NoButtons) 15 | self.setRange(0, 100) 16 | self.setSuffix(' %') 17 | self.setValue(value) 18 | self.setToolTip(title) 19 | self.setStatusTip(self.toolTip()) 20 | self.setAlignment(Qt.AlignCenter) 21 | 22 | def minimumSizeHint(self): 23 | height = super(LightWidget, self).minimumSizeHint().height() 24 | fm = QFontMetrics(self.font()) 25 | width = fm.width(str(self.maximum())) 26 | return QSize(width, height) 27 | 28 | def color(self): 29 | if self.value() == 50: 30 | return None 31 | 32 | strength = int(self.value()/100 * 255 + 0.5) 33 | return QColor(strength, strength, strength) 34 | -------------------------------------------------------------------------------- /libs/pascal_voc_io.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf8 -*- 3 | import sys 4 | from xml.etree import ElementTree 5 | from xml.etree.ElementTree import Element, SubElement 6 | from lxml import etree 7 | import codecs 8 | from libs.constants import DEFAULT_ENCODING 9 | from libs.ustr import ustr 10 | 11 | 12 | XML_EXT = '.xml' 13 | ENCODE_METHOD = DEFAULT_ENCODING 14 | 15 | class PascalVocWriter: 16 | 17 | def __init__(self, folder_name, filename, img_size, database_src='Unknown', local_img_path=None): 18 | self.folder_name = folder_name 19 | self.filename = filename 20 | self.database_src = database_src 21 | self.img_size = img_size 22 | self.box_list = [] 23 | self.local_img_path = local_img_path 24 | self.verified = False 25 | 26 | def prettify(self, elem): 27 | """ 28 | Return a pretty-printed XML string for the Element. 29 | """ 30 | rough_string = ElementTree.tostring(elem, 'utf8') 31 | root = etree.fromstring(rough_string) 32 | return etree.tostring(root, pretty_print=True, encoding=ENCODE_METHOD).replace(" ".encode(), "\t".encode()) 33 | # minidom does not support UTF-8 34 | # reparsed = minidom.parseString(rough_string) 35 | # return reparsed.toprettyxml(indent="\t", encoding=ENCODE_METHOD) 36 | 37 | def gen_xml(self): 38 | """ 39 | Return XML root 40 | """ 41 | # Check conditions 42 | if self.filename is None or \ 43 | self.folder_name is None or \ 44 | self.img_size is None: 45 | return None 46 | 47 | top = Element('annotation') 48 | if self.verified: 49 | top.set('verified', 'yes') 50 | 51 | folder = SubElement(top, 'folder') 52 | folder.text = self.folder_name 53 | 54 | filename = SubElement(top, 'filename') 55 | filename.text = self.filename 56 | 57 | if self.local_img_path is not None: 58 | local_img_path = SubElement(top, 'path') 59 | local_img_path.text = self.local_img_path 60 | 61 | source = SubElement(top, 'source') 62 | database = SubElement(source, 'database') 63 | database.text = self.database_src 64 | 65 | size_part = SubElement(top, 'size') 66 | width = SubElement(size_part, 'width') 67 | height = SubElement(size_part, 'height') 68 | depth = SubElement(size_part, 'depth') 69 | width.text = str(self.img_size[1]) 70 | height.text = str(self.img_size[0]) 71 | if len(self.img_size) == 3: 72 | depth.text = str(self.img_size[2]) 73 | else: 74 | depth.text = '1' 75 | 76 | segmented = SubElement(top, 'segmented') 77 | segmented.text = '0' 78 | return top 79 | 80 | def add_bnd_box(self, x_min, y_min, x_max, y_max, name, difficult): 81 | bnd_box = {'xmin': x_min, 'ymin': y_min, 'xmax': x_max, 'ymax': y_max} 82 | bnd_box['name'] = name 83 | bnd_box['difficult'] = difficult 84 | self.box_list.append(bnd_box) 85 | 86 | def append_objects(self, top): 87 | for each_object in self.box_list: 88 | object_item = SubElement(top, 'object') 89 | name = SubElement(object_item, 'name') 90 | name.text = ustr(each_object['name']) 91 | pose = SubElement(object_item, 'pose') 92 | pose.text = "Unspecified" 93 | truncated = SubElement(object_item, 'truncated') 94 | if int(float(each_object['ymax'])) == int(float(self.img_size[0])) or (int(float(each_object['ymin'])) == 1): 95 | truncated.text = "1" # max == height or min 96 | elif (int(float(each_object['xmax'])) == int(float(self.img_size[1]))) or (int(float(each_object['xmin'])) == 1): 97 | truncated.text = "1" # max == width or min 98 | else: 99 | truncated.text = "0" 100 | difficult = SubElement(object_item, 'difficult') 101 | difficult.text = str(bool(each_object['difficult']) & 1) 102 | bnd_box = SubElement(object_item, 'bndbox') 103 | x_min = SubElement(bnd_box, 'xmin') 104 | x_min.text = str(each_object['xmin']) 105 | y_min = SubElement(bnd_box, 'ymin') 106 | y_min.text = str(each_object['ymin']) 107 | x_max = SubElement(bnd_box, 'xmax') 108 | x_max.text = str(each_object['xmax']) 109 | y_max = SubElement(bnd_box, 'ymax') 110 | y_max.text = str(each_object['ymax']) 111 | 112 | def save(self, target_file=None): 113 | root = self.gen_xml() 114 | self.append_objects(root) 115 | out_file = None 116 | if target_file is None: 117 | out_file = codecs.open( 118 | self.filename + XML_EXT, 'w', encoding=ENCODE_METHOD) 119 | else: 120 | out_file = codecs.open(target_file, 'w', encoding=ENCODE_METHOD) 121 | 122 | prettify_result = self.prettify(root) 123 | out_file.write(prettify_result.decode('utf8')) 124 | out_file.close() 125 | 126 | 127 | class PascalVocReader: 128 | 129 | def __init__(self, file_path): 130 | # shapes type: 131 | # [labbel, [(x1,y1), (x2,y2), (x3,y3), (x4,y4)], color, color, difficult] 132 | self.shapes = [] 133 | self.file_path = file_path 134 | self.verified = False 135 | try: 136 | self.parse_xml() 137 | except: 138 | pass 139 | 140 | def get_shapes(self): 141 | return self.shapes 142 | 143 | def add_shape(self, label, bnd_box, difficult): 144 | x_min = int(float(bnd_box.find('xmin').text)) 145 | y_min = int(float(bnd_box.find('ymin').text)) 146 | x_max = int(float(bnd_box.find('xmax').text)) 147 | y_max = int(float(bnd_box.find('ymax').text)) 148 | points = [(x_min, y_min), (x_max, y_min), (x_max, y_max), (x_min, y_max)] 149 | self.shapes.append((label, points, None, None, difficult)) 150 | 151 | def parse_xml(self): 152 | assert self.file_path.endswith(XML_EXT), "Unsupported file format" 153 | parser = etree.XMLParser(encoding=ENCODE_METHOD) 154 | xml_tree = ElementTree.parse(self.file_path, parser=parser).getroot() 155 | filename = xml_tree.find('filename').text 156 | try: 157 | verified = xml_tree.attrib['verified'] 158 | if verified == 'yes': 159 | self.verified = True 160 | except KeyError: 161 | self.verified = False 162 | 163 | for object_iter in xml_tree.findall('object'): 164 | bnd_box = object_iter.find("bndbox") 165 | label = object_iter.find('name').text 166 | # Add chris 167 | difficult = False 168 | if object_iter.find('difficult') is not None: 169 | difficult = bool(int(object_iter.find('difficult').text)) 170 | self.add_shape(label, bnd_box, difficult) 171 | return True 172 | -------------------------------------------------------------------------------- /libs/settings.py: -------------------------------------------------------------------------------- 1 | import os 2 | import pickle 3 | 4 | 5 | class Settings(object): 6 | def __init__(self): 7 | # Be default, the home will be in the same folder as labelImg 8 | home = os.path.expanduser("~") 9 | self.data = {} 10 | self.path = os.path.join(home, '.labelImgSettings.pkl') 11 | 12 | def __setitem__(self, key, value): 13 | self.data[key] = value 14 | 15 | def __getitem__(self, key): 16 | return self.data[key] 17 | 18 | def get(self, key, default=None): 19 | if key in self.data: 20 | return self.data[key] 21 | return default 22 | 23 | def save(self): 24 | if self.path: 25 | with open(self.path, 'wb') as f: 26 | pickle.dump(self.data, f, pickle.HIGHEST_PROTOCOL) 27 | return True 28 | return False 29 | 30 | def load(self): 31 | try: 32 | if os.path.exists(self.path): 33 | with open(self.path, 'rb') as f: 34 | self.data = pickle.load(f) 35 | return True 36 | except: 37 | print('Loading setting failed') 38 | return False 39 | 40 | def reset(self): 41 | if os.path.exists(self.path): 42 | os.remove(self.path) 43 | print('Remove setting pkl file ${0}'.format(self.path)) 44 | self.data = {} 45 | self.path = None 46 | -------------------------------------------------------------------------------- /libs/shape.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | 5 | try: 6 | from PyQt5.QtGui import * 7 | from PyQt5.QtCore import * 8 | except ImportError: 9 | from PyQt4.QtGui import * 10 | from PyQt4.QtCore import * 11 | 12 | from libs.utils import distance 13 | import sys 14 | 15 | DEFAULT_LINE_COLOR = QColor(0, 255, 0, 128) 16 | DEFAULT_FILL_COLOR = QColor(255, 0, 0, 128) 17 | DEFAULT_SELECT_LINE_COLOR = QColor(255, 255, 255) 18 | DEFAULT_SELECT_FILL_COLOR = QColor(0, 128, 255, 155) 19 | DEFAULT_VERTEX_FILL_COLOR = QColor(0, 255, 0, 255) 20 | DEFAULT_HVERTEX_FILL_COLOR = QColor(255, 0, 0) 21 | 22 | 23 | class Shape(object): 24 | P_SQUARE, P_ROUND = range(2) 25 | 26 | MOVE_VERTEX, NEAR_VERTEX = range(2) 27 | 28 | # The following class variables influence the drawing 29 | # of _all_ shape objects. 30 | line_color = DEFAULT_LINE_COLOR 31 | fill_color = DEFAULT_FILL_COLOR 32 | select_line_color = DEFAULT_SELECT_LINE_COLOR 33 | select_fill_color = DEFAULT_SELECT_FILL_COLOR 34 | vertex_fill_color = DEFAULT_VERTEX_FILL_COLOR 35 | h_vertex_fill_color = DEFAULT_HVERTEX_FILL_COLOR 36 | point_type = P_ROUND 37 | point_size = 16 38 | scale = 1.0 39 | label_font_size = 8 40 | 41 | def __init__(self, label=None, line_color=None, difficult=False, paint_label=False): 42 | self.label = label 43 | self.points = [] 44 | self.fill = False 45 | self.selected = False 46 | self.difficult = difficult 47 | self.paint_label = paint_label 48 | 49 | self._highlight_index = None 50 | self._highlight_mode = self.NEAR_VERTEX 51 | self._highlight_settings = { 52 | self.NEAR_VERTEX: (4, self.P_ROUND), 53 | self.MOVE_VERTEX: (1.5, self.P_SQUARE), 54 | } 55 | 56 | self._closed = False 57 | 58 | if line_color is not None: 59 | # Override the class line_color attribute 60 | # with an object attribute. Currently this 61 | # is used for drawing the pending line a different color. 62 | self.line_color = line_color 63 | 64 | def close(self): 65 | self._closed = True 66 | 67 | def reach_max_points(self): 68 | if len(self.points) >= 4: 69 | return True 70 | return False 71 | 72 | def add_point(self, point): 73 | if not self.reach_max_points(): 74 | self.points.append(point) 75 | 76 | def pop_point(self): 77 | if self.points: 78 | return self.points.pop() 79 | return None 80 | 81 | def is_closed(self): 82 | return self._closed 83 | 84 | def set_open(self): 85 | self._closed = False 86 | 87 | def paint(self, painter): 88 | if self.points: 89 | color = self.select_line_color if self.selected else self.line_color 90 | pen = QPen(color) 91 | # Try using integer sizes for smoother drawing(?) 92 | pen.setWidth(max(1, int(round(2.0 / self.scale)))) 93 | painter.setPen(pen) 94 | 95 | line_path = QPainterPath() 96 | vertex_path = QPainterPath() 97 | 98 | line_path.moveTo(self.points[0]) 99 | # Uncommenting the following line will draw 2 paths 100 | # for the 1st vertex, and make it non-filled, which 101 | # may be desirable. 102 | # self.drawVertex(vertex_path, 0) 103 | 104 | for i, p in enumerate(self.points): 105 | line_path.lineTo(p) 106 | self.draw_vertex(vertex_path, i) 107 | if self.is_closed(): 108 | line_path.lineTo(self.points[0]) 109 | 110 | painter.drawPath(line_path) 111 | painter.drawPath(vertex_path) 112 | painter.fillPath(vertex_path, self.vertex_fill_color) 113 | 114 | # Draw text at the top-left 115 | if self.paint_label: 116 | min_x = sys.maxsize 117 | min_y = sys.maxsize 118 | min_y_label = int(1.25 * self.label_font_size) 119 | for point in self.points: 120 | min_x = min(min_x, point.x()) 121 | min_y = min(min_y, point.y()) 122 | if min_x != sys.maxsize and min_y != sys.maxsize: 123 | font = QFont() 124 | font.setPointSize(self.label_font_size) 125 | font.setBold(True) 126 | painter.setFont(font) 127 | if self.label is None: 128 | self.label = "" 129 | if min_y < min_y_label: 130 | min_y += min_y_label 131 | painter.drawText(int(min_x), int(min_y), self.label) 132 | 133 | if self.fill: 134 | color = self.select_fill_color if self.selected else self.fill_color 135 | painter.fillPath(line_path, color) 136 | 137 | def draw_vertex(self, path, i): 138 | d = self.point_size / self.scale 139 | shape = self.point_type 140 | point = self.points[i] 141 | if i == self._highlight_index: 142 | size, shape = self._highlight_settings[self._highlight_mode] 143 | d *= size 144 | if self._highlight_index is not None: 145 | self.vertex_fill_color = self.h_vertex_fill_color 146 | else: 147 | self.vertex_fill_color = Shape.vertex_fill_color 148 | if shape == self.P_SQUARE: 149 | path.addRect(point.x() - d / 2, point.y() - d / 2, d, d) 150 | elif shape == self.P_ROUND: 151 | path.addEllipse(point, d / 2.0, d / 2.0) 152 | else: 153 | assert False, "unsupported vertex shape" 154 | 155 | def nearest_vertex(self, point, epsilon): 156 | index = None 157 | for i, p in enumerate(self.points): 158 | dist = distance(p - point) 159 | if dist <= epsilon: 160 | index = i 161 | epsilon = dist 162 | return index 163 | 164 | def contains_point(self, point): 165 | return self.make_path().contains(point) 166 | 167 | def make_path(self): 168 | path = QPainterPath(self.points[0]) 169 | for p in self.points[1:]: 170 | path.lineTo(p) 171 | return path 172 | 173 | def bounding_rect(self): 174 | return self.make_path().boundingRect() 175 | 176 | def move_by(self, offset): 177 | self.points = [p + offset for p in self.points] 178 | 179 | def move_vertex_by(self, i, offset): 180 | self.points[i] = self.points[i] + offset 181 | 182 | def highlight_vertex(self, i, action): 183 | self._highlight_index = i 184 | self._highlight_mode = action 185 | 186 | def highlight_clear(self): 187 | self._highlight_index = None 188 | 189 | def copy(self): 190 | shape = Shape("%s" % self.label) 191 | shape.points = [p for p in self.points] 192 | shape.fill = self.fill 193 | shape.selected = self.selected 194 | shape._closed = self._closed 195 | if self.line_color != Shape.line_color: 196 | shape.line_color = self.line_color 197 | if self.fill_color != Shape.fill_color: 198 | shape.fill_color = self.fill_color 199 | shape.difficult = self.difficult 200 | return shape 201 | 202 | def __len__(self): 203 | return len(self.points) 204 | 205 | def __getitem__(self, key): 206 | return self.points[key] 207 | 208 | def __setitem__(self, key, value): 209 | self.points[key] = value 210 | -------------------------------------------------------------------------------- /libs/stringBundle.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | if items were added in files in the resources/strings folder, 5 | then execute "pyrcc5 resources.qrc -o resources.py" in the root directory 6 | and execute "pyrcc5 ../resources.qrc -o resources.py" in the libs directory 7 | """ 8 | import re 9 | import os 10 | import sys 11 | import locale 12 | from libs.ustr import ustr 13 | 14 | try: 15 | from PyQt5.QtCore import * 16 | except ImportError: 17 | if sys.version_info.major >= 3: 18 | import sip 19 | sip.setapi('QVariant', 2) 20 | from PyQt4.QtCore import * 21 | 22 | 23 | class StringBundle: 24 | 25 | __create_key = object() 26 | 27 | def __init__(self, create_key, locale_str): 28 | assert(create_key == StringBundle.__create_key), "StringBundle must be created using StringBundle.getBundle" 29 | self.id_to_message = {} 30 | paths = self.__create_lookup_fallback_list(locale_str) 31 | for path in paths: 32 | self.__load_bundle(path) 33 | 34 | @classmethod 35 | def get_bundle(cls, locale_str=None): 36 | if locale_str is None: 37 | try: 38 | locale_str = locale.getdefaultlocale()[0] if locale.getdefaultlocale() and len( 39 | locale.getdefaultlocale()) > 0 else os.getenv('LANG') 40 | except: 41 | print('Invalid locale') 42 | locale_str = 'en' 43 | 44 | return StringBundle(cls.__create_key, locale_str) 45 | 46 | def get_string(self, string_id): 47 | assert(string_id in self.id_to_message), "Missing string id : " + string_id 48 | return self.id_to_message[string_id] 49 | 50 | def __create_lookup_fallback_list(self, locale_str): 51 | result_paths = [] 52 | base_path = ":/strings" 53 | result_paths.append(base_path) 54 | if locale_str is not None: 55 | # Don't follow standard BCP47. Simple fallback 56 | tags = re.split('[^a-zA-Z]', locale_str) 57 | for tag in tags: 58 | last_path = result_paths[-1] 59 | result_paths.append(last_path + '-' + tag) 60 | 61 | return result_paths 62 | 63 | def __load_bundle(self, path): 64 | PROP_SEPERATOR = '=' 65 | f = QFile(path) 66 | if f.exists(): 67 | if f.open(QIODevice.ReadOnly | QFile.Text): 68 | text = QTextStream(f) 69 | text.setCodec("UTF-8") 70 | 71 | while not text.atEnd(): 72 | line = ustr(text.readLine()) 73 | key_value = line.split(PROP_SEPERATOR) 74 | key = key_value[0].strip() 75 | value = PROP_SEPERATOR.join(key_value[1:]).strip().strip('"') 76 | self.id_to_message[key] = value 77 | 78 | f.close() 79 | -------------------------------------------------------------------------------- /libs/toolBar.py: -------------------------------------------------------------------------------- 1 | try: 2 | from PyQt5.QtGui import * 3 | from PyQt5.QtCore import * 4 | from PyQt5.QtWidgets import * 5 | except ImportError: 6 | from PyQt4.QtGui import * 7 | from PyQt4.QtCore import * 8 | 9 | 10 | class ToolBar(QToolBar): 11 | 12 | def __init__(self, title): 13 | super(ToolBar, self).__init__(title) 14 | layout = self.layout() 15 | m = (0, 0, 0, 0) 16 | layout.setSpacing(0) 17 | layout.setContentsMargins(*m) 18 | self.setContentsMargins(*m) 19 | self.setWindowFlags(self.windowFlags() | Qt.FramelessWindowHint) 20 | 21 | def addAction(self, action): 22 | if isinstance(action, QWidgetAction): 23 | return super(ToolBar, self).addAction(action) 24 | btn = ToolButton() 25 | btn.setDefaultAction(action) 26 | btn.setToolButtonStyle(self.toolButtonStyle()) 27 | self.addWidget(btn) 28 | 29 | 30 | class ToolButton(QToolButton): 31 | """ToolBar companion class which ensures all buttons have the same size.""" 32 | minSize = (60, 60) 33 | 34 | def minimumSizeHint(self): 35 | ms = super(ToolButton, self).minimumSizeHint() 36 | w1, h1 = ms.width(), ms.height() 37 | w2, h2 = self.minSize 38 | ToolButton.minSize = max(w1, w2), max(h1, h2) 39 | return QSize(*ToolButton.minSize) 40 | -------------------------------------------------------------------------------- /libs/ustr.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from libs.constants import DEFAULT_ENCODING 3 | 4 | def ustr(x): 5 | """py2/py3 unicode helper""" 6 | 7 | if sys.version_info < (3, 0, 0): 8 | from PyQt4.QtCore import QString 9 | if type(x) == str: 10 | return x.decode(DEFAULT_ENCODING) 11 | if type(x) == QString: 12 | # https://blog.csdn.net/friendan/article/details/51088476 13 | # https://blog.csdn.net/xxm524/article/details/74937308 14 | return unicode(x.toUtf8(), DEFAULT_ENCODING, 'ignore') 15 | return x 16 | else: 17 | return x 18 | -------------------------------------------------------------------------------- /libs/utils.py: -------------------------------------------------------------------------------- 1 | from math import sqrt 2 | from libs.ustr import ustr 3 | import hashlib 4 | import re 5 | import sys 6 | 7 | try: 8 | from PyQt5.QtGui import * 9 | from PyQt5.QtCore import * 10 | from PyQt5.QtWidgets import * 11 | QT5 = True 12 | except ImportError: 13 | from PyQt4.QtGui import * 14 | from PyQt4.QtCore import * 15 | QT5 = False 16 | 17 | 18 | def new_icon(icon): 19 | return QIcon(':/' + icon) 20 | 21 | 22 | def new_button(text, icon=None, slot=None): 23 | b = QPushButton(text) 24 | if icon is not None: 25 | b.setIcon(new_icon(icon)) 26 | if slot is not None: 27 | b.clicked.connect(slot) 28 | return b 29 | 30 | 31 | def new_action(parent, text, slot=None, shortcut=None, icon=None, 32 | tip=None, checkable=False, enabled=True): 33 | """Create a new action and assign callbacks, shortcuts, etc.""" 34 | a = QAction(text, parent) 35 | if icon is not None: 36 | a.setIcon(new_icon(icon)) 37 | if shortcut is not None: 38 | if isinstance(shortcut, (list, tuple)): 39 | a.setShortcuts(shortcut) 40 | else: 41 | a.setShortcut(shortcut) 42 | if tip is not None: 43 | a.setToolTip(tip) 44 | a.setStatusTip(tip) 45 | if slot is not None: 46 | a.triggered.connect(slot) 47 | if checkable: 48 | a.setCheckable(True) 49 | a.setEnabled(enabled) 50 | return a 51 | 52 | 53 | def add_actions(widget, actions): 54 | for action in actions: 55 | if action is None: 56 | widget.addSeparator() 57 | elif isinstance(action, QMenu): 58 | widget.addMenu(action) 59 | else: 60 | widget.addAction(action) 61 | 62 | 63 | def label_validator(): 64 | return QRegExpValidator(QRegExp(r'^[^ \t].+'), None) 65 | 66 | 67 | class Struct(object): 68 | 69 | def __init__(self, **kwargs): 70 | self.__dict__.update(kwargs) 71 | 72 | 73 | def distance(p): 74 | return sqrt(p.x() * p.x() + p.y() * p.y()) 75 | 76 | 77 | def format_shortcut(text): 78 | mod, key = text.split('+', 1) 79 | return '%s+%s' % (mod, key) 80 | 81 | 82 | def generate_color_by_text(text): 83 | s = ustr(text) 84 | hash_code = int(hashlib.sha256(s.encode('utf-8')).hexdigest(), 16) 85 | r = int((hash_code / 255) % 255) 86 | g = int((hash_code / 65025) % 255) 87 | b = int((hash_code / 16581375) % 255) 88 | return QColor(r, g, b, 100) 89 | 90 | 91 | def have_qstring(): 92 | """p3/qt5 get rid of QString wrapper as py3 has native unicode str type""" 93 | return not (sys.version_info.major >= 3 or QT_VERSION_STR.startswith('5.')) 94 | 95 | 96 | def util_qt_strlistclass(): 97 | return QStringList if have_qstring() else list 98 | 99 | 100 | def natural_sort(list, key=lambda s:s): 101 | """ 102 | Sort the list into natural alphanumeric order. 103 | """ 104 | def get_alphanum_key_func(key): 105 | convert = lambda text: int(text) if text.isdigit() else text 106 | return lambda s: [convert(c) for c in re.split('([0-9]+)', key(s))] 107 | sort_key = get_alphanum_key_func(key) 108 | list.sort(key=sort_key) 109 | 110 | 111 | # QT4 has a trimmed method, in QT5 this is called strip 112 | if QT5: 113 | def trimmed(text): 114 | return text.strip() 115 | else: 116 | def trimmed(text): 117 | return text.trimmed() 118 | -------------------------------------------------------------------------------- /libs/yolo_io.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf8 -*- 3 | import codecs 4 | import os 5 | 6 | from libs.constants import DEFAULT_ENCODING 7 | 8 | TXT_EXT = '.txt' 9 | ENCODE_METHOD = DEFAULT_ENCODING 10 | 11 | class YOLOWriter: 12 | 13 | def __init__(self, folder_name, filename, img_size, database_src='Unknown', local_img_path=None): 14 | self.folder_name = folder_name 15 | self.filename = filename 16 | self.database_src = database_src 17 | self.img_size = img_size 18 | self.box_list = [] 19 | self.local_img_path = local_img_path 20 | self.verified = False 21 | 22 | def add_bnd_box(self, x_min, y_min, x_max, y_max, name, difficult): 23 | bnd_box = {'xmin': x_min, 'ymin': y_min, 'xmax': x_max, 'ymax': y_max} 24 | bnd_box['name'] = name 25 | bnd_box['difficult'] = difficult 26 | self.box_list.append(bnd_box) 27 | 28 | def bnd_box_to_yolo_line(self, box, class_list=[]): 29 | x_min = box['xmin'] 30 | x_max = box['xmax'] 31 | y_min = box['ymin'] 32 | y_max = box['ymax'] 33 | 34 | x_center = float((x_min + x_max)) / 2 / self.img_size[1] 35 | y_center = float((y_min + y_max)) / 2 / self.img_size[0] 36 | 37 | w = float((x_max - x_min)) / self.img_size[1] 38 | h = float((y_max - y_min)) / self.img_size[0] 39 | 40 | # PR387 41 | box_name = box['name'] 42 | if box_name not in class_list: 43 | class_list.append(box_name) 44 | 45 | class_index = class_list.index(box_name) 46 | 47 | return class_index, x_center, y_center, w, h 48 | 49 | def save(self, class_list=[], target_file=None): 50 | 51 | out_file = None # Update yolo .txt 52 | out_class_file = None # Update class list .txt 53 | 54 | if target_file is None: 55 | out_file = open( 56 | self.filename + TXT_EXT, 'w', encoding=ENCODE_METHOD) 57 | classes_file = os.path.join(os.path.dirname(os.path.abspath(self.filename)), "classes.txt") 58 | out_class_file = open(classes_file, 'w') 59 | 60 | else: 61 | out_file = codecs.open(target_file, 'w', encoding=ENCODE_METHOD) 62 | classes_file = os.path.join(os.path.dirname(os.path.abspath(target_file)), "classes.txt") 63 | out_class_file = open(classes_file, 'w') 64 | 65 | 66 | for box in self.box_list: 67 | class_index, x_center, y_center, w, h = self.bnd_box_to_yolo_line(box, class_list) 68 | # print (classIndex, x_center, y_center, w, h) 69 | out_file.write("%d %.6f %.6f %.6f %.6f\n" % (class_index, x_center, y_center, w, h)) 70 | 71 | # print (classList) 72 | # print (out_class_file) 73 | for c in class_list: 74 | out_class_file.write(c+'\n') 75 | 76 | out_class_file.close() 77 | out_file.close() 78 | 79 | 80 | 81 | class YoloReader: 82 | 83 | def __init__(self, file_path, image, class_list_path=None): 84 | # shapes type: 85 | # [labbel, [(x1,y1), (x2,y2), (x3,y3), (x4,y4)], color, color, difficult] 86 | self.shapes = [] 87 | self.file_path = file_path 88 | 89 | if class_list_path is None: 90 | dir_path = os.path.dirname(os.path.realpath(self.file_path)) 91 | self.class_list_path = os.path.join(dir_path, "classes.txt") 92 | else: 93 | self.class_list_path = class_list_path 94 | 95 | # print (file_path, self.class_list_path) 96 | 97 | classes_file = open(self.class_list_path, 'r') 98 | self.classes = classes_file.read().strip('\n').split('\n') 99 | 100 | # print (self.classes) 101 | 102 | img_size = [image.height(), image.width(), 103 | 1 if image.isGrayscale() else 3] 104 | 105 | self.img_size = img_size 106 | 107 | self.verified = False 108 | # try: 109 | self.parse_yolo_format() 110 | # except: 111 | # pass 112 | 113 | def get_shapes(self): 114 | return self.shapes 115 | 116 | def add_shape(self, label, x_min, y_min, x_max, y_max, difficult): 117 | 118 | points = [(x_min, y_min), (x_max, y_min), (x_max, y_max), (x_min, y_max)] 119 | self.shapes.append((label, points, None, None, difficult)) 120 | 121 | def yolo_line_to_shape(self, class_index, x_center, y_center, w, h): 122 | label = self.classes[int(class_index)] 123 | 124 | x_min = max(float(x_center) - float(w) / 2, 0) 125 | x_max = min(float(x_center) + float(w) / 2, 1) 126 | y_min = max(float(y_center) - float(h) / 2, 0) 127 | y_max = min(float(y_center) + float(h) / 2, 1) 128 | 129 | x_min = round(self.img_size[1] * x_min) 130 | x_max = round(self.img_size[1] * x_max) 131 | y_min = round(self.img_size[0] * y_min) 132 | y_max = round(self.img_size[0] * y_max) 133 | 134 | return label, x_min, y_min, x_max, y_max 135 | 136 | def parse_yolo_format(self): 137 | bnd_box_file = open(self.file_path, 'r') 138 | for bndBox in bnd_box_file: 139 | class_index, x_center, y_center, w, h = bndBox.strip().split(' ') 140 | label, x_min, y_min, x_max, y_max = self.yolo_line_to_shape(class_index, x_center, y_center, w, h) 141 | 142 | # Caveat: difficult flag is discarded when saved as yolo format. 143 | self.add_shape(label, x_min, y_min, x_max, y_max, False) 144 | -------------------------------------------------------------------------------- /libs/zoomWidget.py: -------------------------------------------------------------------------------- 1 | try: 2 | from PyQt5.QtGui import * 3 | from PyQt5.QtCore import * 4 | from PyQt5.QtWidgets import * 5 | except ImportError: 6 | from PyQt4.QtGui import * 7 | from PyQt4.QtCore import * 8 | 9 | 10 | class ZoomWidget(QSpinBox): 11 | 12 | def __init__(self, value=100): 13 | super(ZoomWidget, self).__init__() 14 | self.setButtonSymbols(QAbstractSpinBox.NoButtons) 15 | self.setRange(1, 500) 16 | self.setSuffix(' %') 17 | self.setValue(value) 18 | self.setToolTip(u'Zoom Level') 19 | self.setStatusTip(self.toolTip()) 20 | self.setAlignment(Qt.AlignCenter) 21 | 22 | def minimumSizeHint(self): 23 | height = super(ZoomWidget, self).minimumSizeHint().height() 24 | fm = QFontMetrics(self.font()) 25 | width = fm.width(str(self.maximum())) 26 | return QSize(width, height) 27 | -------------------------------------------------------------------------------- /readme/README.jp.rst: -------------------------------------------------------------------------------- 1 | labelImg 2 | ======== 3 | 4 | .. image:: https://img.shields.io/pypi/v/labelimg.svg 5 | :target: https://pypi.python.org/pypi/labelimg 6 | 7 | .. image:: https://img.shields.io/github/workflow/status/tzutalin/labelImg/Package?style=for-the-badge :alt: GitHub Workflow Status 8 | 9 | 10 | .. image:: https://img.shields.io/badge/lang-en-blue.svg 11 | :target: https://github.com/tzutalin/labelImg 12 | 13 | .. image:: https://img.shields.io/badge/lang-zh-green.svg 14 | :target: https://github.com/tzutalin/labelImg/blob/master/readme/README.zh.rst 15 | 16 | .. image:: https://img.shields.io/badge/lang-jp-green.svg 17 | :target: https://github.com/tzutalin/labelImg/blob/master/readme/README.jp.rst 18 | 19 | .. image:: /resources/icons/app.png 20 | :width: 200px 21 | :align: center 22 | 23 | LabelImgは、PythonとQtを使うアノテーション補助ツールです。 24 | 25 | このツールはPascalVOCフォーマットとYOLOとCreateMLをサポートしています。 26 | 27 | .. image:: https://raw.githubusercontent.com/tzutalin/labelImg/master/demo/demo3.jpg 28 | :alt: Demo Image 29 | 30 | .. image:: https://raw.githubusercontent.com/tzutalin/labelImg/master/demo/demo.jpg 31 | :alt: Demo Image 32 | 33 | `サンプル動画は にあります。`__ 34 | 35 | インストール方法 36 | ------------------- 37 | 38 | 39 | ソースからビルドする 40 | ~~~~~~~~~~~~~~~~~~~~ 41 | 42 | Linuxまたは、Ubuntuまたは、macOSの場合は 43 | 44 | Ubuntuの場合 45 | ^^^^^^^^^^^^ 46 | 47 | Python 3とQt5を使う場合 48 | 49 | .. code:: shell 50 | 51 | sudo apt-get install pyqt5-dev-tools 52 | sudo pip3 install -r requirements/requirements-linux-python3.txt 53 | make qt5py3 54 | python3 labelImg.py 55 | python3 labelImg.py [IMAGE_PATH] [PRE-DEFINED CLASS FILE] 56 | 57 | macOSの場合 58 | ^^^^^^^^^^^ 59 | 60 | Python 3とQt5を使う場合 61 | 62 | .. code:: shell 63 | 64 | brew install qt # Install qt-5.x.x by Homebrew 65 | brew install libxml2 66 | 67 | or using pip 68 | 69 | pip3 install pyqt5 lxml # Install qt and lxml by pip 70 | 71 | make qt5py3 72 | python3 labelImg.py 73 | python3 labelImg.py [IMAGE_PATH] [PRE-DEFINED CLASS FILE] 74 | 75 | 76 | Python 3 Virtualenv (推奨) 77 | 78 | VirtualenvはQtとPythonのバージョン衝突問題を解消できます。 79 | 80 | .. code:: shell 81 | 82 | brew install python3 83 | pip3 install pipenv 84 | pipenv run pip install pyqt5==5.15.2 lxml 85 | pipenv run make qt5py3 86 | pipenv run python3 labelImg.py 87 | [任意で] rm -rf build dist; python setup.py py2app -A;mv "dist/labelImg.app" /Applications 88 | 89 | 90 | 注意:最後のコマンドを実行すると、/ApplicationsフォルダにSVGアイコンを含む.appファイルが生成されます。build-tools/build-for-macos.shというスクリプトの仕様も検討してください。 91 | 92 | 93 | Windowsの場合 94 | ^^^^^^^^^^^^^ 95 | 96 | 最初に`Python `__ と 97 | `PyQt5 `__ と 98 | `install lxml `__ をインストールしてください。 99 | 100 | コマンドプロンプトを起動し `labelImg <#labelimg>`__ がインストールされているフォルダに移動してから以下のコマンドを実行します。 101 | 102 | .. code:: shell 103 | 104 | pyrcc4 -o libs/resources.py resources.qrc 105 | (pyqt5の場合は、 pyrcc5 -o libs/resources.py resources.qrc) 106 | 107 | python labelImg.py 108 | python labelImg.py [画像パス] [定義済みクラスファイル] 109 | 110 | Windows + Anaconda 111 | ^^^^^^^^^^^^^^^^^^ 112 | 113 | `Anaconda `__ をダウンロードしてからインストールしてください。 114 | 115 | Anaconda Promptを起動し `labelImg <#labelimg>`__ インストールされているフォルダに移動してから以下のコマンドを実行します。 116 | 117 | .. code:: shell 118 | 119 | conda install pyqt=5 120 | conda install -c anaconda lxml 121 | pyrcc5 -o libs/resources.py resources.qrc 122 | python labelImg.py 123 | python labelImg.py [IMAGE_PATH] [PRE-DEFINED CLASS FILE] 124 | 125 | PyPIから入手する(Python 3以降のみ) 126 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 127 | 現代的なLinuxディストリビューションの場合は以下のコマンドを入力するだけでインストールできます。 128 | 129 | .. code:: shell 130 | 131 | pip3 install labelImg 132 | labelImg 133 | labelImg [IMAGE_PATH] [PRE-DEFINED CLASS FILE] 134 | 135 | Dockerの場合 136 | ~~~~~~~~~~~~~~~~~ 137 | .. code:: shell 138 | 139 | docker run -it \ 140 | --user $(id -u) \ 141 | -e DISPLAY=unix$DISPLAY \ 142 | --workdir=$(pwd) \ 143 | --volume="/home/$USER:/home/$USER" \ 144 | --volume="/etc/group:/etc/group:ro" \ 145 | --volume="/etc/passwd:/etc/passwd:ro" \ 146 | --volume="/etc/shadow:/etc/shadow:ro" \ 147 | --volume="/etc/sudoers.d:/etc/sudoers.d:ro" \ 148 | -v /tmp/.X11-unix:/tmp/.X11-unix \ 149 | tzutalin/py2qt4 150 | 151 | make qt4py2;./labelImg.py 152 | 153 | あとは`サンプル動画`__ 154 | を見るだけです。 155 | 156 | 157 | 定義済みクラスを作成するには? 158 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 159 | 160 | `data/predefined\_classes.txt `__ 161 | を編集してください。 162 | 163 | ショートカット一覧 164 | ~~~~~~~~~~~~~~~~~~ 165 | 166 | +--------------------+--------------------------------------------+ 167 | | Ctrl + u | そのディレクトリの画像を読み込む | 168 | +--------------------+--------------------------------------------+ 169 | | Ctrl + r | アノテーションの生成ディレクトリを変更 | 170 | +--------------------+--------------------------------------------+ 171 | | Ctrl + s | 保存する | 172 | +--------------------+--------------------------------------------+ 173 | | Ctrl + d | 現在選択している矩形トラベルをコピー | 174 | +--------------------+--------------------------------------------+ 175 | | Ctrl + Shift + d | 現在表示している画像を削除 | 176 | +--------------------+--------------------------------------------+ 177 | | Space | 現在の画像に検証済みフラグを立てる | 178 | +--------------------+--------------------------------------------+ 179 | | w | 矩形を生成する | 180 | +--------------------+--------------------------------------------+ 181 | | d | 次の画像へ移動する | 182 | +--------------------+--------------------------------------------+ 183 | | a | 前の画像へ移動する | 184 | +--------------------+--------------------------------------------+ 185 | | del | 選択した矩形を削除 | 186 | +--------------------+--------------------------------------------+ 187 | | Ctrl++ | 画像を拡大 | 188 | +--------------------+--------------------------------------------+ 189 | | Ctrl-- | 画像を縮小 | 190 | +--------------------+--------------------------------------------+ 191 | | ↑→↓← | 十字キーで矩形を移動する | 192 | +--------------------+--------------------------------------------+ 193 | 194 | 開発に参加するには? 195 | ~~~~~~~~~~~~~~~~~~~~~ 196 | 197 | このリポジトリにPull Request を送ってください。 198 | -------------------------------------------------------------------------------- /readme/README.zh.rst: -------------------------------------------------------------------------------- 1 | LabelImg 2 | ======== 3 | 4 | .. image:: https://img.shields.io/pypi/v/labelimg.svg 5 | :target: https://pypi.python.org/pypi/labelimg 6 | 7 | .. image:: https://img.shields.io/github/workflow/status/tzutalin/labelImg/Package?style=for-the-badge :alt: GitHub Workflow Status 8 | 9 | .. image:: https://img.shields.io/badge/lang-en-blue.svg 10 | :target: https://github.com/tzutalin/labelImg 11 | 12 | .. image:: https://img.shields.io/badge/lang-zh-green.svg 13 | :target: https://github.com/tzutalin/labelImg/blob/master/readme/README.zh.rst 14 | 15 | .. image:: https://img.shields.io/badge/lang-jp-green.svg 16 | :target: https://github.com/tzutalin/labelImg/blob/master/readme/README.jp.rst 17 | 18 | .. image:: /resources/icons/app.png 19 | :width: 200px 20 | :align: center 21 | 22 | LabelImg 是影像標註工具,它是用python 和 QT 寫成的. 23 | 24 | 支持的儲存格式包括PASCAL VOC format, YOLO, createML. 25 | 26 | .. image:: https://raw.githubusercontent.com/tzutalin/labelImg/master/demo/demo3.jpg 27 | :alt: Demo Image 28 | 29 | .. image:: https://raw.githubusercontent.com/tzutalin/labelImg/master/demo/demo.jpg 30 | :alt: Demo Image 31 | 32 | `展示影片 `__ 33 | 34 | 安裝 35 | ------------------ 36 | 37 | 38 | 透過編譯原始碼 39 | ~~~~~~~~~~~~~~~~~ 40 | 41 | Linux/Ubuntu/Mac 需要 Python 和 `PyQt `__ 42 | 43 | Ubuntu Linux 44 | ^^^^^^^^^^^^ 45 | 46 | Python 3 + Qt5 47 | 48 | .. code:: shell 49 | 50 | sudo apt-get install pyqt5-dev-tools 51 | sudo pip3 install -r requirements/requirements-linux-python3.txt 52 | make qt5py3 53 | python3 labelImg.py 54 | python3 labelImg.py [IMAGE_PATH] [PRE-DEFINED CLASS FILE] 55 | 56 | macOS 57 | ^^^^^ 58 | 59 | Python 3 + Qt5 60 | 61 | .. code:: shell 62 | 63 | brew install qt # Install qt-5.x.x by Homebrew 64 | brew install libxml2 65 | 66 | or using pip 67 | 68 | pip3 install pyqt5 lxml # Install qt and lxml by pip 69 | 70 | make qt5py3 71 | python3 labelImg.py 72 | python3 labelImg.py [IMAGE_PATH] [PRE-DEFINED CLASS FILE] 73 | 74 | 75 | Python 3 Virtualenv (推薦方法) 76 | 77 | Virtualenv 可以避免版本和相依性問題 78 | 79 | .. code:: shell 80 | 81 | brew install python3 82 | pip3 install pipenv 83 | pipenv run pip install pyqt5==5.15.2 lxml 84 | pipenv run make qt5py3 85 | pipenv run python3 labelImg.py 86 | [Optional] rm -rf build dist; python setup.py py2app -A;mv "dist/labelImg.app" /Applications 87 | 88 | 89 | Windows 90 | ^^^^^^^ 91 | 92 | 安裝 `Python `__, 93 | `PyQt5 `__ 94 | 和 `install lxml `__. 95 | 96 | 安裝並到 `labelImg <#labelimg>`__ 目錄 97 | 98 | .. code:: shell 99 | 100 | pyrcc4 -o libs/resources.py resources.qrc 101 | For pyqt5, pyrcc5 -o libs/resources.py resources.qrc 102 | 103 | python labelImg.py 104 | python labelImg.py [IMAGE_PATH] [PRE-DEFINED CLASS FILE] 105 | 106 | Windows + Anaconda 107 | ^^^^^^^^^^^^^^^^^^ 108 | 109 | 下載並安裝 `Anaconda `__ (Python 3+) 110 | 111 | 打開 Anaconda Prompt 然後到 `labelImg <#labelimg>`__ 目錄 112 | 113 | .. code:: shell 114 | 115 | conda install pyqt=5 116 | conda install -c anaconda lxml 117 | pyrcc5 -o libs/resources.py resources.qrc 118 | python labelImg.py 119 | python labelImg.py [IMAGE_PATH] [PRE-DEFINED CLASS FILE] 120 | 121 | Get from PyPI but only python3.0 or above 122 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 123 | 124 | .. code:: shell 125 | 126 | pip3 install labelImg 127 | labelImg 128 | labelImg [IMAGE_PATH] [PRE-DEFINED CLASS FILE] 129 | 130 | 131 | Use Docker 132 | ~~~~~~~~~~~~~~~~~ 133 | .. code:: shell 134 | 135 | docker run -it \ 136 | --user $(id -u) \ 137 | -e DISPLAY=unix$DISPLAY \ 138 | --workdir=$(pwd) \ 139 | --volume="/home/$USER:/home/$USER" \ 140 | --volume="/etc/group:/etc/group:ro" \ 141 | --volume="/etc/passwd:/etc/passwd:ro" \ 142 | --volume="/etc/shadow:/etc/shadow:ro" \ 143 | --volume="/etc/sudoers.d:/etc/sudoers.d:ro" \ 144 | -v /tmp/.X11-unix:/tmp/.X11-unix \ 145 | tzutalin/py2qt4 146 | 147 | make qt4py2;./labelImg.py 148 | 149 | `你可以參考影片 `__ 150 | 151 | 152 | 使用方法 153 | ----- 154 | 155 | 你可以先產生標籤 156 | ~~~~~~~~~~~~~~~~~~~~~~~~~~ 157 | 158 | 修改這個檔案 159 | `data/predefined\_classes.txt `__ 160 | 161 | 快捷鍵 162 | ~~~~~~~ 163 | 164 | +--------------------+--------------------------------------------+ 165 | | Ctrl + u | 讀取所有影像從每個目錄 | 166 | +--------------------+--------------------------------------------+ 167 | | Ctrl + r | 改變標示結果的存檔目錄 | 168 | +--------------------+--------------------------------------------+ 169 | | Ctrl + s | 存檔 | 170 | +--------------------+--------------------------------------------+ 171 | | Ctrl + d | 複製目前的標籤和物件的區塊 | 172 | +--------------------+--------------------------------------------+ 173 | | Ctrl + Shift + d | 刪除目前影像 | 174 | +--------------------+--------------------------------------------+ 175 | | Space | 標示目前照片已經處理過 | 176 | +--------------------+--------------------------------------------+ 177 | | w | 產生新的物件區塊 | 178 | +--------------------+--------------------------------------------+ 179 | | d | 下張影像 | 180 | +--------------------+--------------------------------------------+ 181 | | a | 上張影像 | 182 | +--------------------+--------------------------------------------+ 183 | | del | 刪除所選的物件區塊 | 184 | +--------------------+--------------------------------------------+ 185 | | Ctrl++ | 放大影像 | 186 | +--------------------+--------------------------------------------+ 187 | | Ctrl-- | 縮小影像 | 188 | +--------------------+--------------------------------------------+ 189 | | ↑→↓← | 移動所選的物件區塊 | 190 | +--------------------+--------------------------------------------+ 191 | 192 | 如何貢獻 193 | ~~~~~~~~~~~~~~~~~ 194 | 195 | 歡迎上傳程式碼直接貢獻 196 | -------------------------------------------------------------------------------- /readme/images/label-studio-1-6-player-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HumanSignal/labelImg/b33f965b6d14c14f1e46b247f1bf346e03f2e950/readme/images/label-studio-1-6-player-screenshot.png -------------------------------------------------------------------------------- /readme/images/labelimg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HumanSignal/labelImg/b33f965b6d14c14f1e46b247f1bf346e03f2e950/readme/images/labelimg.png -------------------------------------------------------------------------------- /requirements/requirements-linux-python3.txt: -------------------------------------------------------------------------------- 1 | pyqt5==5.14.1 2 | lxml==4.9.1 3 | -------------------------------------------------------------------------------- /resources.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | resources/icons/help.png 5 | resources/icons/app.png 6 | resources/icons/expert2.png 7 | resources/icons/done.png 8 | resources/icons/file.png 9 | resources/icons/labels.png 10 | resources/icons/objects.png 11 | resources/icons/close.png 12 | resources/icons/fit-width.png 13 | resources/icons/fit-window.png 14 | resources/icons/undo.png 15 | resources/icons/eye.png 16 | resources/icons/quit.png 17 | resources/icons/copy.png 18 | resources/icons/edit.png 19 | resources/icons/open.png 20 | resources/icons/save.png 21 | resources/icons/format_voc.png 22 | resources/icons/format_yolo.png 23 | resources/icons/format_createml.png 24 | resources/icons/save-as.png 25 | resources/icons/color.png 26 | resources/icons/color_line.png 27 | resources/icons/zoom.png 28 | resources/icons/zoom-in.png 29 | resources/icons/zoom-out.png 30 | resources/icons/light_reset.png 31 | resources/icons/light_lighten.png 32 | resources/icons/light_darken.png 33 | resources/icons/cancel.png 34 | resources/icons/next.png 35 | resources/icons/prev.png 36 | resources/icons/resetall.png 37 | resources/icons/verify.png 38 | resources/strings/strings.properties 39 | resources/strings/strings-zh-TW.properties 40 | resources/strings/strings-zh-CN.properties 41 | resources/strings/strings-ja-JP.properties 42 | 43 | 44 | -------------------------------------------------------------------------------- /resources/icons/app.icns: -------------------------------------------------------------------------------- 1 | icns -------------------------------------------------------------------------------- /resources/icons/app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HumanSignal/labelImg/b33f965b6d14c14f1e46b247f1bf346e03f2e950/resources/icons/app.png -------------------------------------------------------------------------------- /resources/icons/app.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | Created by potrace 1.15, written by Peter Selinger 2001-2017 9 | 10 | 12 | 22 | 24 | 26 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /resources/icons/cancel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HumanSignal/labelImg/b33f965b6d14c14f1e46b247f1bf346e03f2e950/resources/icons/cancel.png -------------------------------------------------------------------------------- /resources/icons/close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HumanSignal/labelImg/b33f965b6d14c14f1e46b247f1bf346e03f2e950/resources/icons/close.png -------------------------------------------------------------------------------- /resources/icons/color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HumanSignal/labelImg/b33f965b6d14c14f1e46b247f1bf346e03f2e950/resources/icons/color.png -------------------------------------------------------------------------------- /resources/icons/color_line.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HumanSignal/labelImg/b33f965b6d14c14f1e46b247f1bf346e03f2e950/resources/icons/color_line.png -------------------------------------------------------------------------------- /resources/icons/copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HumanSignal/labelImg/b33f965b6d14c14f1e46b247f1bf346e03f2e950/resources/icons/copy.png -------------------------------------------------------------------------------- /resources/icons/delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HumanSignal/labelImg/b33f965b6d14c14f1e46b247f1bf346e03f2e950/resources/icons/delete.png -------------------------------------------------------------------------------- /resources/icons/done.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HumanSignal/labelImg/b33f965b6d14c14f1e46b247f1bf346e03f2e950/resources/icons/done.png -------------------------------------------------------------------------------- /resources/icons/done.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 27 | 29 | 36 | 40 | 44 | 48 | 52 | 56 | 57 | 64 | 68 | 72 | 76 | 80 | 84 | 85 | 92 | 96 | 100 | 104 | 108 | 112 | 113 | 121 | 125 | 129 | 133 | 137 | 141 | 142 | 143 | 145 | 147 | begin='' id='W5M0MpCehiHzreSzNTczkc9d' 148 | 150 | 151 | 153 | 154 | Adobe PDF library 5.00 155 | 156 | 158 | 160 | 162 | 163 | 2003-12-22T22:34:35+02:00 164 | 165 | 2004-04-17T21:25:50Z 166 | 167 | Adobe Illustrator 10.0 168 | 169 | 2004-01-19T17:51:02+01:00 170 | 171 | 172 | 174 | 175 | JPEG 176 | 177 | 256 178 | 179 | 256 180 | 181 | /9j/4AAQSkZJRgABAgEASABIAAD/7QAsUGhvdG9zaG9wIDMuMAA4QklNA+0AAAAAABAASAAAAAEA 182 | AQBIAAAAAQAB/+4ADkFkb2JlAGTAAAAAAf/bAIQABgQEBAUEBgUFBgkGBQYJCwgGBggLDAoKCwoK 183 | DBAMDAwMDAwQDA4PEA8ODBMTFBQTExwbGxscHx8fHx8fHx8fHwEHBwcNDA0YEBAYGhURFRofHx8f 184 | Hx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8f/8AAEQgBAAEAAwER 185 | AAIRAQMRAf/EAaIAAAAHAQEBAQEAAAAAAAAAAAQFAwIGAQAHCAkKCwEAAgIDAQEBAQEAAAAAAAAA 186 | AQACAwQFBgcICQoLEAACAQMDAgQCBgcDBAIGAnMBAgMRBAAFIRIxQVEGE2EicYEUMpGhBxWxQiPB 187 | UtHhMxZi8CRygvElQzRTkqKyY3PCNUQnk6OzNhdUZHTD0uIIJoMJChgZhJRFRqS0VtNVKBry4/PE 188 | 1OT0ZXWFlaW1xdXl9WZ2hpamtsbW5vY3R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo+Ck5SVlpeYmZ 189 | qbnJ2en5KjpKWmp6ipqqusra6voRAAICAQIDBQUEBQYECAMDbQEAAhEDBCESMUEFURNhIgZxgZEy 190 | obHwFMHR4SNCFVJicvEzJDRDghaSUyWiY7LCB3PSNeJEgxdUkwgJChgZJjZFGidkdFU38qOzwygp 191 | 0+PzhJSktMTU5PRldYWVpbXF1eX1RlZmdoaWprbG1ub2R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo 192 | +DlJWWl5iZmpucnZ6fkqOkpaanqKmqq6ytrq+v/aAAwDAQACEQMRAD8A9U4q7FXYq7FXYq7FXYq7 193 | FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7F 194 | XYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX 195 | Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXY 196 | 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 342 | 343 | 344 | 345 | 346 | 348 | 349 | uuid:4b4d592f-95b8-4bcd-a892-74a536c5e52f 350 | 351 | 353 | 354 | image/svg+xml 355 | 356 | 357 | 359 | test.ai 360 | 361 | 362 | 363 | 364 | 365 | end='w' 366 | 367 | 372 | 376 | 380 | 384 | 388 | 392 | 396 | 400 | 401 | -------------------------------------------------------------------------------- /resources/icons/edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HumanSignal/labelImg/b33f965b6d14c14f1e46b247f1bf346e03f2e950/resources/icons/edit.png -------------------------------------------------------------------------------- /resources/icons/expert1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HumanSignal/labelImg/b33f965b6d14c14f1e46b247f1bf346e03f2e950/resources/icons/expert1.png -------------------------------------------------------------------------------- /resources/icons/expert2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HumanSignal/labelImg/b33f965b6d14c14f1e46b247f1bf346e03f2e950/resources/icons/expert2.png -------------------------------------------------------------------------------- /resources/icons/eye.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HumanSignal/labelImg/b33f965b6d14c14f1e46b247f1bf346e03f2e950/resources/icons/eye.png -------------------------------------------------------------------------------- /resources/icons/feBlend-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HumanSignal/labelImg/b33f965b6d14c14f1e46b247f1bf346e03f2e950/resources/icons/feBlend-icon.png -------------------------------------------------------------------------------- /resources/icons/file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HumanSignal/labelImg/b33f965b6d14c14f1e46b247f1bf346e03f2e950/resources/icons/file.png -------------------------------------------------------------------------------- /resources/icons/fit-width.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HumanSignal/labelImg/b33f965b6d14c14f1e46b247f1bf346e03f2e950/resources/icons/fit-width.png -------------------------------------------------------------------------------- /resources/icons/fit-window.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HumanSignal/labelImg/b33f965b6d14c14f1e46b247f1bf346e03f2e950/resources/icons/fit-window.png -------------------------------------------------------------------------------- /resources/icons/fit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HumanSignal/labelImg/b33f965b6d14c14f1e46b247f1bf346e03f2e950/resources/icons/fit.png -------------------------------------------------------------------------------- /resources/icons/format_createml.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HumanSignal/labelImg/b33f965b6d14c14f1e46b247f1bf346e03f2e950/resources/icons/format_createml.png -------------------------------------------------------------------------------- /resources/icons/format_voc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HumanSignal/labelImg/b33f965b6d14c14f1e46b247f1bf346e03f2e950/resources/icons/format_voc.png -------------------------------------------------------------------------------- /resources/icons/format_yolo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HumanSignal/labelImg/b33f965b6d14c14f1e46b247f1bf346e03f2e950/resources/icons/format_yolo.png -------------------------------------------------------------------------------- /resources/icons/help.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HumanSignal/labelImg/b33f965b6d14c14f1e46b247f1bf346e03f2e950/resources/icons/help.png -------------------------------------------------------------------------------- /resources/icons/labels.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HumanSignal/labelImg/b33f965b6d14c14f1e46b247f1bf346e03f2e950/resources/icons/labels.png -------------------------------------------------------------------------------- /resources/icons/light_brighten.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 43 | 47 | 51 | 58 | 65 | 66 | 68 | 73 | 79 | 83 | 87 | 91 | 95 | 99 | 103 | 107 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /resources/icons/light_darken.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HumanSignal/labelImg/b33f965b6d14c14f1e46b247f1bf346e03f2e950/resources/icons/light_darken.png -------------------------------------------------------------------------------- /resources/icons/light_darken.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 40 | 42 | 47 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /resources/icons/light_lighten.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HumanSignal/labelImg/b33f965b6d14c14f1e46b247f1bf346e03f2e950/resources/icons/light_lighten.png -------------------------------------------------------------------------------- /resources/icons/light_reset.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HumanSignal/labelImg/b33f965b6d14c14f1e46b247f1bf346e03f2e950/resources/icons/light_reset.png -------------------------------------------------------------------------------- /resources/icons/light_reset.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 41 | 43 | 48 | 53 | 57 | 62 | 67 | 71 | 75 | 79 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /resources/icons/new.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HumanSignal/labelImg/b33f965b6d14c14f1e46b247f1bf346e03f2e950/resources/icons/new.png -------------------------------------------------------------------------------- /resources/icons/next.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HumanSignal/labelImg/b33f965b6d14c14f1e46b247f1bf346e03f2e950/resources/icons/next.png -------------------------------------------------------------------------------- /resources/icons/objects.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HumanSignal/labelImg/b33f965b6d14c14f1e46b247f1bf346e03f2e950/resources/icons/objects.png -------------------------------------------------------------------------------- /resources/icons/open.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HumanSignal/labelImg/b33f965b6d14c14f1e46b247f1bf346e03f2e950/resources/icons/open.png -------------------------------------------------------------------------------- /resources/icons/open.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | image/svg+xml 42 | 43 | 45 | 52 | 54 | 55 | 56 | 58 | 60 | 67 | 68 | 70 | 77 | 81 | 85 | 89 | 92 | 95 | 98 | 101 | 104 | 105 | 109 | 116 | 120 | 124 | 128 | 132 | 135 | 138 | 141 | 144 | 147 | 150 | 153 | 154 | 158 | 165 | 169 | 173 | 177 | 181 | 185 | 188 | 191 | 194 | 195 | 199 | 206 | 210 | 214 | 218 | 222 | 226 | 229 | 232 | 235 | 236 | 240 | 247 | 251 | 255 | 258 | 261 | 264 | 265 | 269 | 276 | 280 | 284 | 288 | 291 | 294 | 297 | 300 | 303 | 304 | 308 | 315 | 319 | 323 | 327 | 330 | 333 | 336 | 339 | 342 | 343 | 347 | 354 | 358 | 362 | 365 | 368 | 371 | 372 | 376 | 383 | 387 | 391 | 395 | 399 | 403 | 407 | 411 | 415 | 418 | 421 | 424 | 425 | 429 | 433 | 440 | 444 | 448 | 451 | 454 | 457 | 458 | 462 | 466 | 473 | 477 | 481 | 484 | 487 | 490 | 491 | 495 | 502 | 506 | 510 | 514 | 517 | 520 | 523 | 526 | 529 | 530 | 534 | 541 | 545 | 549 | 552 | 555 | 558 | 559 | 563 | 567 | 574 | 575 | 576 | 577 | -------------------------------------------------------------------------------- /resources/icons/prev.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HumanSignal/labelImg/b33f965b6d14c14f1e46b247f1bf346e03f2e950/resources/icons/prev.png -------------------------------------------------------------------------------- /resources/icons/quit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HumanSignal/labelImg/b33f965b6d14c14f1e46b247f1bf346e03f2e950/resources/icons/quit.png -------------------------------------------------------------------------------- /resources/icons/resetall.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HumanSignal/labelImg/b33f965b6d14c14f1e46b247f1bf346e03f2e950/resources/icons/resetall.png -------------------------------------------------------------------------------- /resources/icons/save-as.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HumanSignal/labelImg/b33f965b6d14c14f1e46b247f1bf346e03f2e950/resources/icons/save-as.png -------------------------------------------------------------------------------- /resources/icons/save.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HumanSignal/labelImg/b33f965b6d14c14f1e46b247f1bf346e03f2e950/resources/icons/save.png -------------------------------------------------------------------------------- /resources/icons/undo-cross.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HumanSignal/labelImg/b33f965b6d14c14f1e46b247f1bf346e03f2e950/resources/icons/undo-cross.png -------------------------------------------------------------------------------- /resources/icons/undo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HumanSignal/labelImg/b33f965b6d14c14f1e46b247f1bf346e03f2e950/resources/icons/undo.png -------------------------------------------------------------------------------- /resources/icons/verify.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HumanSignal/labelImg/b33f965b6d14c14f1e46b247f1bf346e03f2e950/resources/icons/verify.png -------------------------------------------------------------------------------- /resources/icons/zoom-in.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HumanSignal/labelImg/b33f965b6d14c14f1e46b247f1bf346e03f2e950/resources/icons/zoom-in.png -------------------------------------------------------------------------------- /resources/icons/zoom-out.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HumanSignal/labelImg/b33f965b6d14c14f1e46b247f1bf346e03f2e950/resources/icons/zoom-out.png -------------------------------------------------------------------------------- /resources/icons/zoom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HumanSignal/labelImg/b33f965b6d14c14f1e46b247f1bf346e03f2e950/resources/icons/zoom.png -------------------------------------------------------------------------------- /resources/strings/strings-ja-JP.properties: -------------------------------------------------------------------------------- 1 | openFile=ファイルを開く 2 | openFileDetail=画像かラベルファイルを開きます。 3 | quit=アプリを終了 4 | quitApp=このアプリを終了します。 5 | openDir=ディレクトリを開く 6 | openAnnotation=アノテーションを開く 7 | openAnnotationDetail=アノテーションファイルを開きます。 8 | changeSaveDir=保存先を変更する 9 | resetAll=すべてリセットする 10 | hideAll=すべての図形を非表示にする 11 | viewAll=すべての図形を表示する 12 | crtBox=矩形を作成する 13 | crtBoxDetail=矩形を描画します。 14 | dupBox=矩形を複製する 15 | delBox=矩形を削除する 16 | editBox=矩形を編集する(&E) 17 | hideAllBox=矩形を非表示にする(&H) 18 | showAllBox=矩形を表示する(&S) 19 | tutorialDefault=チュートリアル動画を見る 20 | tutorialChrome=Chromeでチュートリアル動画を見る 21 | tutorialDetail=LabelImgのデモ動画を見ます。 22 | info=LabelImgについて 23 | verifyImg=画像を検証する 24 | verifyImgDetail=画像を検証します。 25 | save=保存する 26 | saveDetail=現在の編集を保存します。 27 | saveAs=名前を付けて保存 28 | saveAsDetail=ファイルに名前を付けて現在の編集を保存します。 29 | closeCur=このファイルを閉じる 30 | deleteImg=この画像を削除する 31 | resetAll=すべて初期化する 32 | resetAllDetail=データをすべて初期状態に戻します。 33 | boxLineColor=線の色の編集 34 | boxLineColorDetail=色の編集を行います。 35 | chooseLineColor=線の色の編集 36 | chooseFillColor=塗りつぶす色の編集 37 | shortcut=ショートカット一覧を見る(英語) 38 | editLabel=ラベルを編集する 39 | editLabelDetail=選択した矩形のラベル名を変更します。 40 | changeSaveFormat=保存するデータのフォーマットを変更します。 41 | nextImg=次の画像 42 | nextImgDetail=次の画像を開きます。 43 | prevImg=前の画像 44 | prevImgDetail=前の画像を開きます。 45 | zoomin=拡大 46 | zoomout=縮小 47 | useDefaultLabel=指定されたラベル名を使う 48 | advancedMode=上級者向け機能 49 | advancedModeDetail=上級者向け機能を使います。 50 | boxLabelText=矩形ラベル一覧 51 | labels=ラベル一覧 52 | autoSaveMode=自動で保存する 53 | singleClsMode=単一のラベルだけをアノテーションする 54 | displayLabel=ラベルを表示する 55 | menu_file=ファイル(&F) 56 | menu_edit=編集(&E) 57 | menu_view=表示(&V) 58 | menu_help=ヘルプ(&H) 59 | menu_openRecent=最近開いたファイル(&R) 60 | drawSquares=四角形を書く 61 | fitWin=ウィンドウに合わせる 62 | fitWinDetail=ウィンドウサイズに合わせて拡大します。 63 | fitWidth=ウィンドウの幅に合わせる 64 | fitWidthDetail=ウインドウの幅に合わせて拡大します。 65 | lightbrighten=明るくする 66 | lightbrightenDetail=画像の明るさを上げる 67 | lightdarken=暗くする 68 | lightdarkenDetail=画像の明るさを下げる 69 | lightreset=元の明るさ 70 | lightresetDetail=元の明るさを復元する 71 | lightWidgetTitle=画像の明るさ 72 | originalsize=原寸に戻す 73 | displayLabel=ラベルを表示する 74 | fileList=ファイル一覧 75 | files=ファイル 76 | boxLabelText=矩形ラベル 77 | copyPrevBounding=前の画像の矩形ラベルをこの画像にコピー 78 | -------------------------------------------------------------------------------- /resources/strings/strings-zh-CN.properties: -------------------------------------------------------------------------------- 1 | openFile=打开文件 2 | openFileDetail=打开图像文件 3 | quit=退出 4 | quitApp=退出程序 5 | openDir=打开目录 6 | copyPrevBounding=复制当前图像中的上一个边界框 7 | changeSavedAnnotationDir=更改保存标签文件的预设目录 8 | openAnnotation=开启标签 9 | openAnnotationDetail=打开标签文件 10 | changeSaveDir=改变存放目录 11 | nextImg=下一个图像 12 | nextImgDetail=下一个图像 13 | prevImg=上一个图像 14 | prevImgDetail=上一个图像 15 | verifyImg=验证图像 16 | verifyImgDetail=验证图像 17 | save=保存 18 | saveDetail=保存标签文件 19 | changeSaveFormat=更改存储格式 20 | saveAs=另存为 21 | saveAsDetail=將标签保存到其他文件 22 | closeCur=关闭文件 23 | closeCurDetail=关闭当前文件 24 | deleteImg=删除图像 25 | deleteImgDetail=删除当前图像 26 | resetAll=全部重置 27 | resetAllDetail=重置所有设定 28 | boxLineColor=区块线条颜色 29 | boxLineColorDetail=选择线框颜色 30 | crtBox=创建区块 31 | crtBoxDetail=创建一个新的区块 32 | delBox=删除选择的区块 33 | delBoxDetail=删除区块 34 | dupBox=复制区块 35 | dupBoxDetail=复制区块 36 | editBox=编辑区块 37 | editBoxDetail=编辑区块 38 | hideAllBox=隐藏所有区块 39 | showAllBox=显示所有区块 40 | tutorialDefault=YouTube教学 41 | tutorialChrome=YouTube教学(Chrome) 42 | tutorialDetail=显示示范内容 43 | info=版本信息 44 | shortcut=快捷键 45 | zoomin=放大画面 46 | zoominDetail=放大画面 47 | zoomout=缩小画面 48 | zoomoutDetail=缩小画面 49 | originalsize=原始大小 50 | originalsizeDetail=放大到原始大小 51 | fitWin=调整到窗口大小 52 | fitWinDetail=缩放到当前窗口大小 53 | fitWidth=缩放到跟当前画面一样宽 54 | fitWidthDetail=调整宽度适应到窗口宽度 55 | lightbrighten=提亮 56 | lightbrightenDetail=增加图像亮度 57 | lightdarken=变暗 58 | lightdarkenDetail=降低图像亮度 59 | lightreset=原始亮度 60 | lightresetDetail=恢复原来的亮度 61 | lightWidgetTitle=图像亮度 62 | editLabel=编辑标签 63 | editLabelDetail=修改当前所选的区块颜色 64 | shapeLineColor=形状线条颜色 65 | shapeLineColorDetail=更改线条颜色 66 | shapeFillColor=填充颜色 67 | shapeFillColorDetail=更改填充颜色 68 | showHide=显示/隐藏标签 69 | useDefaultLabel=使用预设标签 70 | useDifficult=有难度的 71 | boxLabelText=区块的标签 72 | labels=标签 73 | autoSaveMode=自动保存模式 74 | singleClsMode=单一类别模式 75 | displayLabel=显示类别 76 | fileList=文件列表 77 | files=文件 78 | advancedMode=专家模式 79 | advancedModeDetail=切换到专家模式 80 | showAllBoxDetail=显示所有区块 81 | hideAllBoxDetail=隐藏所有区块 82 | menu_file=文件(&F) 83 | menu_edit=编辑(&E) 84 | menu_view=查看(&V) 85 | menu_help=帮助(&H) 86 | menu_openRecent=最近打开(&R) 87 | chooseLineColor=选择线条颜色 88 | chooseFillColor=选择填充颜色 89 | drawSquares=绘制正方形 90 | -------------------------------------------------------------------------------- /resources/strings/strings-zh-TW.properties: -------------------------------------------------------------------------------- 1 | saveAsDetail=將標籤保存到其他文件 2 | changeSaveDir=改變存放目錄 3 | openFile=開啟檔案 4 | shapeLineColorDetail=更改線條顏色 5 | resetAll=重置 6 | crtBox=創建區塊 7 | crtBoxDetail=畫一個區塊 8 | dupBoxDetail=複製區塊 9 | verifyImg=驗證圖像 10 | zoominDetail=放大 11 | verifyImgDetail=驗證圖像 12 | saveDetail=將標籤存到 13 | openFileDetail=打開圖像 14 | fitWidthDetail=調整到窗口寬度 15 | lightbrighten=變亮 16 | lightbrightenDetail=增加影像亮度 17 | lightdarken=變暗 18 | lightdarkenDetail=降低影像亮度 19 | lightreset=原本亮度 20 | lightresetDetail=恢復原本亮度 21 | lightWidgetTitle=影像量度 22 | tutorialDefault=YouTube教學 23 | tutorialChrome=YouTube教學(Chrome) 24 | info=版本信息 25 | shortcut=快捷鍵 26 | editLabel=編輯標籤 27 | openAnnotationDetail=打開標籤文件 28 | quit=結束 29 | shapeFillColorDetail=更改填充顏色 30 | closeCurDetail=關閉目前檔案 31 | closeCur=關閉 32 | deleteImg=刪除圖像 33 | deleteImgDetail=刪除目前圖像 34 | fitWin=調整到跟窗口一樣大小 35 | delBox=刪除選取區塊 36 | boxLineColorDetail=選擇框線顏色 37 | originalsize=原始大小 38 | resetAllDetail=重設所有設定 39 | zoomoutDetail=畫面放大 40 | save=儲存 41 | saveAs=另存為 42 | fitWinDetail=縮放到窗口一樣 43 | openDir=開啟目錄 44 | copyPrevBounding=複製當前圖像中的上一個邊界框 45 | showHide=顯示/隱藏標籤 46 | changeSaveFormat=更改儲存格式 47 | shapeFillColor=填充顏色 48 | quitApp=離開本程式 49 | dupBox=複製區塊 50 | delBoxDetail=刪除區塊 51 | zoomin=放大畫面 52 | info=資訊 53 | openAnnotation=開啟標籤 54 | prevImgDetail=上一個圖像 55 | fitWidth=縮放到跟畫面一樣寬 56 | zoomout=縮小畫面 57 | changeSavedAnnotationDir=更改預設標籤存的目錄 58 | nextImgDetail=下一個圖像 59 | originalsizeDetail=放大到原始大小 60 | prevImg=上一個圖像 61 | tutorialDetail=顯示示範內容 62 | shapeLineColor=形狀線條顏色 63 | boxLineColor=日期分隔線顏色 64 | editLabelDetail=修改所選區塊的標籤 65 | nextImg=下一張圖片 66 | useDefaultLabel=使用預設標籤 67 | useDifficult=有難度的 68 | boxLabelText=區塊的標籤 69 | labels=標籤 70 | autoSaveMode=自動儲存模式 71 | singleClsMode=單一類別模式 72 | displayLabel=顯示類別 73 | fileList=檔案清單 74 | files=檔案 75 | advancedMode=進階模式 76 | advancedModeDetail=切到進階模式 77 | showAllBoxDetail=顯示所有區塊 78 | hideAllBoxDetail=隱藏所有區塊 79 | menu_file=檔案(&F) 80 | menu_edit=編輯(&E) 81 | menu_view=檢視(&V) 82 | menu_help=說明(&H) 83 | menu_openRecent=最近開啟(&R) 84 | -------------------------------------------------------------------------------- /resources/strings/strings.properties: -------------------------------------------------------------------------------- 1 | openFile=Open 2 | openFileDetail=Open image or label file 3 | quit=Quit 4 | quitApp=Quit application 5 | openDir=Open Dir 6 | copyPrevBounding=Copy previous Bounding Boxes in the current image 7 | changeSavedAnnotationDir=Change default saved Annotation dir 8 | openAnnotation=Open Annotation 9 | openAnnotationDetail=Open an annotation file 10 | changeSaveDir=Change Save Dir 11 | nextImg=Next Image 12 | nextImgDetail=Open the next Image 13 | prevImg=Prev Image 14 | prevImgDetail=Open the previous Image 15 | verifyImg=Verify Image 16 | verifyImgDetail=Verify Image 17 | save=Save 18 | saveDetail=Save the labels to a file 19 | changeSaveFormat=Change save format 20 | saveAs=Save As 21 | saveAsDetail=Save the labels to a different file 22 | closeCur=Close 23 | closeCurDetail=Close the current file 24 | deleteImg=Delete current image 25 | deleteImgDetail=Delete the current image 26 | resetAll=Reset All 27 | resetAllDetail=Reset All 28 | boxLineColor=Box Line Color 29 | boxLineColorDetail=Choose Box line color 30 | crtBox=Create RectBox 31 | crtBoxDetail=Draw a new box 32 | delBox=Delete RectBox 33 | delBoxDetail=Remove the box 34 | dupBox=Duplicate RectBox 35 | dupBoxDetail=Create a duplicate of the selected box 36 | editBox=&Edit RectBox 37 | editBoxDetail=Move and edit Boxs 38 | hideAllBox=&Hide RectBox 39 | showAllBox=&Show RectBox 40 | tutorialDefault=Tutorial 41 | tutorialChrome=Tutorial(Chrome) 42 | tutorialDetail=Show demo 43 | info=Information 44 | shortcut=Keyboard shortcuts 45 | zoomin=Zoom In 46 | zoominDetail=Increase zoom level 47 | zoomout=Zoom Out 48 | zoomoutDetail=Decrease zoom level 49 | originalsize=Original size 50 | originalsizeDetail=Zoom to original size 51 | fitWin=Fit Window 52 | fitWinDetail=Zoom follows window size 53 | fitWidth=Fit Width 54 | fitWidthDetail=Zoom follows window width 55 | lightbrighten=Brighten 56 | lightbrightenDetail=Increase Image Brightness 57 | lightdarken=Darken 58 | lightdarkenDetail=Decrease Image Brightness 59 | lightreset=Original Brightness 60 | lightresetDetail=Restore original brightness 61 | lightWidgetTitle=Image Brightness 62 | editLabel=Edit Label 63 | editLabelDetail=Modify the label of the selected Box 64 | shapeLineColor=Shape Line Color 65 | shapeLineColorDetail=Change the line color for this specific shape 66 | shapeFillColor=Shape Fill Color 67 | shapeFillColorDetail=Change the fill color for this specific shape 68 | showHide=Show/Hide Label Panel 69 | useDefaultLabel=Use default label 70 | useDifficult=difficult 71 | boxLabelText=Box Labels 72 | labels=Labels 73 | autoSaveMode=Auto Save mode 74 | singleClsMode=Single Class Mode 75 | displayLabel=Display Labels 76 | fileList=File List 77 | files=Files 78 | advancedMode=Advanced Mode 79 | advancedModeDetail=Swtich to advanced mode 80 | showAllBoxDetail=Show all bounding boxes 81 | hideAllBoxDetail=Hide all bounding boxes 82 | menu_file=&File 83 | menu_edit=&Edit 84 | menu_view=&View 85 | menu_help=&Help 86 | menu_openRecent=Open &Recent 87 | chooseLineColor=Choose Line Color 88 | chooseFillColor=Choose Fill Color 89 | drawSquares=Draw Squares -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bumpversion] 2 | commit = True 3 | tag = True 4 | 5 | [bumpversion:file:setup.py] 6 | 7 | [bdist_wheel] 8 | universal = 1 9 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from setuptools import setup, find_packages, Command 5 | from sys import platform as _platform 6 | from shutil import rmtree 7 | import sys 8 | import os 9 | 10 | here = os.path.abspath(os.path.dirname(__file__)) 11 | NAME = 'labelImg' 12 | REQUIRES_PYTHON = '>=3.0.0' 13 | REQUIRED_DEP = ['pyqt5', 'lxml'] 14 | about = {} 15 | 16 | with open(os.path.join(here, 'libs', '__init__.py')) as f: 17 | exec(f.read(), about) 18 | 19 | with open("README.rst", "rb") as readme_file: 20 | readme = readme_file.read().decode("UTF-8") 21 | 22 | with open("HISTORY.rst", "rb") as history_file: 23 | history = history_file.read().decode("UTF-8") 24 | 25 | # OS specific settings 26 | SET_REQUIRES = [] 27 | if _platform == "linux" or _platform == "linux2": 28 | # linux 29 | print('linux') 30 | elif _platform == "darwin": 31 | # MAC OS X 32 | SET_REQUIRES.append('py2app') 33 | 34 | required_packages = find_packages() 35 | required_packages.append('labelImg') 36 | 37 | APP = [NAME + '.py'] 38 | OPTIONS = { 39 | 'argv_emulation': True, 40 | 'iconfile': 'resources/icons/app.icns' 41 | } 42 | 43 | class UploadCommand(Command): 44 | """Support setup.py upload.""" 45 | 46 | description=readme + '\n\n' + history, 47 | 48 | user_options = [] 49 | 50 | @staticmethod 51 | def status(s): 52 | """Prints things in bold.""" 53 | print('\033[1m{0}\033[0m'.format(s)) 54 | 55 | def initialize_options(self): 56 | pass 57 | 58 | def finalize_options(self): 59 | pass 60 | 61 | def run(self): 62 | try: 63 | self.status('Removing previous builds…') 64 | rmtree(os.path.join(here, 'dist')) 65 | except OSError: 66 | self.status('Fail to remove previous builds..') 67 | pass 68 | 69 | self.status('Building Source and Wheel (universal) distribution…') 70 | os.system( 71 | '{0} setup.py sdist bdist_wheel --universal'.format(sys.executable)) 72 | 73 | self.status('Uploading the package to PyPI via Twine…') 74 | os.system('twine upload dist/*') 75 | 76 | self.status('Pushing git tags…') 77 | os.system('git tag -d v{0}'.format(about['__version__'])) 78 | os.system('git tag v{0}'.format(about['__version__'])) 79 | # os.system('git push --tags') 80 | 81 | sys.exit() 82 | 83 | 84 | setup( 85 | app=APP, 86 | name=NAME, 87 | version=about['__version__'], 88 | description="LabelImg is a graphical image annotation tool and label object bounding boxes in images", 89 | long_description=readme + '\n\n' + history, 90 | author="TzuTa Lin", 91 | author_email='tzu.ta.lin@gmail.com', 92 | url='https://github.com/tzutalin/labelImg', 93 | python_requires=REQUIRES_PYTHON, 94 | package_dir={'labelImg': '.'}, 95 | packages=required_packages, 96 | entry_points={ 97 | 'console_scripts': [ 98 | 'labelImg=labelImg.labelImg:main' 99 | ] 100 | }, 101 | include_package_data=True, 102 | install_requires=REQUIRED_DEP, 103 | license="MIT license", 104 | zip_safe=False, 105 | keywords='labelImg labelTool development annotation deeplearning', 106 | classifiers=[ 107 | 'Development Status :: 5 - Production/Stable', 108 | 'Intended Audience :: Developers', 109 | 'License :: OSI Approved :: MIT License', 110 | 'Natural Language :: English', 111 | 'Programming Language :: Python :: 3', 112 | 'Programming Language :: Python :: 3.3', 113 | 'Programming Language :: Python :: 3.4', 114 | 'Programming Language :: Python :: 3.5', 115 | 'Programming Language :: Python :: 3.6', 116 | 'Programming Language :: Python :: 3.7', 117 | ], 118 | package_data={'data/predefined_classes.txt': ['data/predefined_classes.txt']}, 119 | options={'py2app': OPTIONS}, 120 | setup_requires=SET_REQUIRES, 121 | # $ setup.py publish support. 122 | cmdclass={ 123 | 'upload': UploadCommand, 124 | } 125 | ) 126 | -------------------------------------------------------------------------------- /tests/.gitignore: -------------------------------------------------------------------------------- 1 | test.xml 2 | -------------------------------------------------------------------------------- /tests/test.512.512.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HumanSignal/labelImg/b33f965b6d14c14f1e46b247f1bf346e03f2e950/tests/test.512.512.bmp -------------------------------------------------------------------------------- /tests/test_io.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import unittest 4 | 5 | class TestPascalVocRW(unittest.TestCase): 6 | 7 | def test_upper(self): 8 | dir_name = os.path.abspath(os.path.dirname(__file__)) 9 | libs_path = os.path.join(dir_name, '..', 'libs') 10 | sys.path.insert(0, libs_path) 11 | from pascal_voc_io import PascalVocWriter 12 | from pascal_voc_io import PascalVocReader 13 | 14 | # Test Write/Read 15 | writer = PascalVocWriter('tests', 'test', (512, 512, 1), local_img_path='tests/test.512.512.bmp') 16 | difficult = 1 17 | writer.add_bnd_box(60, 40, 430, 504, 'person', difficult) 18 | writer.add_bnd_box(113, 40, 450, 403, 'face', difficult) 19 | writer.save('tests/test.xml') 20 | 21 | reader = PascalVocReader('tests/test.xml') 22 | shapes = reader.get_shapes() 23 | 24 | person_bnd_box = shapes[0] 25 | face = shapes[1] 26 | self.assertEqual(person_bnd_box[0], 'person') 27 | self.assertEqual(person_bnd_box[1], [(60, 40), (430, 40), (430, 504), (60, 504)]) 28 | self.assertEqual(face[0], 'face') 29 | self.assertEqual(face[1], [(113, 40), (450, 40), (450, 403), (113, 403)]) 30 | 31 | 32 | class TestCreateMLRW(unittest.TestCase): 33 | 34 | def test_a_write(self): 35 | dir_name = os.path.abspath(os.path.dirname(__file__)) 36 | libs_path = os.path.join(dir_name, '..', 'libs') 37 | sys.path.insert(0, libs_path) 38 | from create_ml_io import CreateMLWriter 39 | 40 | person = {'label': 'person', 'points': ((65, 45), (420, 45), (420, 512), (65, 512))} 41 | face = {'label': 'face', 'points': ((245, 250), (350, 250), (350, 365), (245, 365))} 42 | 43 | expected_width = 105 # 350-245 -> create_ml_io.py ll 46 44 | expected_height = 115 # 365-250 -> create_ml_io.py ll 49 45 | expected_x = 297.5 # 245+105/2 -> create_ml_io.py ll 53 46 | expected_y = 307.5 # 250+115/2 > create_ml_io.py ll 54 47 | 48 | shapes = [person, face] 49 | output_file = dir_name + "/tests.json" 50 | 51 | writer = CreateMLWriter('tests', 'test.512.512.bmp', (512, 512, 1), shapes, output_file, 52 | local_img_path='tests/test.512.512.bmp') 53 | 54 | writer.verified = True 55 | writer.write() 56 | 57 | # check written json 58 | with open(output_file, "r") as file: 59 | input_data = file.read() 60 | 61 | import json 62 | data_dict = json.loads(input_data)[0] 63 | self.assertEqual(True, data_dict['verified'], 'verified tag not reflected') 64 | self.assertEqual('test.512.512.bmp', data_dict['image'], 'filename not correct in .json') 65 | self.assertEqual(2, len(data_dict['annotations']), 'output file contains to less annotations') 66 | face = data_dict['annotations'][1] 67 | self.assertEqual('face', face['label'], 'label name is wrong') 68 | face_coords = face['coordinates'] 69 | self.assertEqual(expected_width, face_coords['width'], 'calculated width is wrong') 70 | self.assertEqual(expected_height, face_coords['height'], 'calculated height is wrong') 71 | self.assertEqual(expected_x, face_coords['x'], 'calculated x is wrong') 72 | self.assertEqual(expected_y, face_coords['y'], 'calculated y is wrong') 73 | 74 | def test_b_read(self): 75 | dir_name = os.path.abspath(os.path.dirname(__file__)) 76 | libs_path = os.path.join(dir_name, '..', 'libs') 77 | sys.path.insert(0, libs_path) 78 | from create_ml_io import CreateMLReader 79 | 80 | output_file = dir_name + "/tests.json" 81 | reader = CreateMLReader(output_file, 'tests/test.512.512.bmp') 82 | shapes = reader.get_shapes() 83 | face = shapes[1] 84 | 85 | self.assertEqual(2, len(shapes), 'shape count is wrong') 86 | self.assertEqual('face', face[0], 'label is wrong') 87 | 88 | face_coords = face[1] 89 | x_min = face_coords[0][0] 90 | x_max = face_coords[1][0] 91 | y_min = face_coords[0][1] 92 | y_max = face_coords[2][1] 93 | 94 | self.assertEqual(245, x_min, 'xmin is wrong') 95 | self.assertEqual(350, x_max, 'xmax is wrong') 96 | self.assertEqual(250, y_min, 'ymin is wrong') 97 | self.assertEqual(365, y_max, 'ymax is wrong') 98 | 99 | 100 | if __name__ == '__main__': 101 | unittest.main() 102 | -------------------------------------------------------------------------------- /tests/test_qt.py: -------------------------------------------------------------------------------- 1 | 2 | from unittest import TestCase 3 | 4 | from labelImg import get_main_app 5 | 6 | 7 | class TestMainWindow(TestCase): 8 | 9 | app = None 10 | win = None 11 | 12 | def setUp(self): 13 | self.app, self.win = get_main_app() 14 | 15 | def tearDown(self): 16 | self.win.close() 17 | self.app.quit() 18 | 19 | def test_noop(self): 20 | pass 21 | -------------------------------------------------------------------------------- /tests/test_settings.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | import time 5 | import unittest 6 | 7 | __author__ = 'TzuTaLin' 8 | 9 | dir_name = os.path.abspath(os.path.dirname(__file__)) 10 | libs_path = os.path.join(dir_name, '..', 'libs') 11 | sys.path.insert(0, libs_path) 12 | from settings import Settings 13 | 14 | class TestSettings(unittest.TestCase): 15 | 16 | def test_basic(self): 17 | settings = Settings() 18 | settings['test0'] = 'hello' 19 | settings['test1'] = 10 20 | settings['test2'] = [0, 2, 3] 21 | self.assertEqual(settings.get('test3', 3), 3) 22 | self.assertEqual(settings.save(), True) 23 | 24 | settings.load() 25 | self.assertEqual(settings.get('test0'), 'hello') 26 | self.assertEqual(settings.get('test1'), 10) 27 | 28 | settings.reset() 29 | 30 | 31 | 32 | if __name__ == '__main__': 33 | unittest.main() 34 | -------------------------------------------------------------------------------- /tests/test_stringBundle.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import unittest 4 | import resources 5 | from stringBundle import StringBundle 6 | 7 | class TestStringBundle(unittest.TestCase): 8 | 9 | def test_loadDefaultBundle_withoutError(self): 10 | str_bundle = StringBundle.get_bundle('en') 11 | self.assertEqual(str_bundle.get_string("openDir"), 'Open Dir', 'Fail to load the default bundle') 12 | 13 | def test_fallback_withoutError(self): 14 | str_bundle = StringBundle.get_bundle('zh-TW') 15 | self.assertEqual(str_bundle.get_string("openDir"), u'\u958B\u555F\u76EE\u9304', 'Fail to load the zh-TW bundle') 16 | 17 | def test_setInvaleLocaleToEnv_printErrorMsg(self): 18 | prev_lc = os.environ['LC_ALL'] 19 | prev_lang = os.environ['LANG'] 20 | os.environ['LC_ALL'] = 'UTF-8' 21 | os.environ['LANG'] = 'UTF-8' 22 | str_bundle = StringBundle.get_bundle() 23 | self.assertEqual(str_bundle.get_string("openDir"), 'Open Dir', 'Fail to load the default bundle') 24 | os.environ['LC_ALL'] = prev_lc 25 | os.environ['LANG'] = prev_lang 26 | 27 | 28 | if __name__ == '__main__': 29 | unittest.main() 30 | -------------------------------------------------------------------------------- /tests/test_utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import unittest 4 | from libs.utils import Struct, new_action, new_icon, add_actions, format_shortcut, generate_color_by_text, natural_sort 5 | 6 | class TestUtils(unittest.TestCase): 7 | 8 | def test_generateColorByGivingUniceText_noError(self): 9 | res = generate_color_by_text(u'\u958B\u555F\u76EE\u9304') 10 | self.assertTrue(res.green() >= 0) 11 | self.assertTrue(res.red() >= 0) 12 | self.assertTrue(res.blue() >= 0) 13 | 14 | def test_nautalSort_noError(self): 15 | l1 = ['f1', 'f11', 'f3'] 16 | expected_l1 = ['f1', 'f3', 'f11'] 17 | natural_sort(l1) 18 | for idx, val in enumerate(l1): 19 | self.assertTrue(val == expected_l1[idx]) 20 | 21 | if __name__ == '__main__': 22 | unittest.main() 23 | -------------------------------------------------------------------------------- /tests/臉書.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HumanSignal/labelImg/b33f965b6d14c14f1e46b247f1bf346e03f2e950/tests/臉書.jpg -------------------------------------------------------------------------------- /tools/README.md: -------------------------------------------------------------------------------- 1 | # Additional tools 2 | 3 | ## Convert the label files to CSV 4 | 5 | ### Introduction 6 | To train the images on [Google Cloud AutoML](https://cloud.google.com/automl), we should prepare the specific csv files follow [this format](https://cloud.google.com/vision/automl/object-detection/docs/csv-format). 7 | 8 | `label_to_csv.py` can convert the `txt` or `xml` label files to csv file. The labels files should strictly follow to below structure. 9 | 10 | ### Structures 11 | * Images 12 | To train the object detection tasks, all the images should upload to the cloud storage and access it by its name. All the images should stay in the **same buckets** in cloud storage. Also, different classes should have their own folder as below. 13 | ``` 14 | (on the cloud storage) 15 | | -- class1 16 | | | -- class1_01.jpg 17 | | | -- class1_02.jpg 18 | | | ... 19 | | -- class2 20 | | | -- class2_01.jpg 21 | | | -- class2_02.jpg 22 | | | ... 23 | | ... 24 | ``` 25 | Note, URI of the `class1_01.jpg` is `gs:///class1/class1_01.jpg` 26 | * Labels 27 | There are four types of training data - `TRAINING`, `VALIDATION`, `TEST` and `UNASSIGNED`. To assign different categories, we should create four directories. 28 | Inside each folder, users should create the class folders with the same name in cloud storage (see below structure). 29 | ``` 30 | labels (on PC) 31 | | -- TRAINING 32 | | | -- class1 33 | | | | -- class1_01.txt (or .xml) 34 | | | | ... 35 | | | -- class2 36 | | | | -- class2_01.txt (or .xml) 37 | | | | ... 38 | | | ... 39 | | -- VALIDATION 40 | | | -- class1 41 | | | | -- class1_02.txt (or .xml) 42 | | | | ... 43 | | | -- class2 44 | | | | -- class2_02.txt (or .xml) 45 | | | | ... 46 | | | ... 47 | | -- TEST 48 | | | (same as TRAINING and VALIDATION) 49 | | -- UNASSIGNED 50 | | | (same as TRAINING and VALIDATION) 51 | ``` 52 | 53 | ### Usage 54 | 55 | To see the argument of `label_to_csv.py`, 56 | ```commandline 57 | python label_to_csv.py -h 58 | ``` 59 | 60 | ```commandline 61 | usage: label_to_csv.py [-h] -p PREFIX -l LOCATION -m MODE [-o OUTPUT] 62 | [-c CLASSES] 63 | 64 | optional arguments: 65 | -h, --help show this help message and exit 66 | -p PREFIX, --prefix PREFIX 67 | Bucket of the cloud storage path 68 | -l LOCATION, --location LOCATION 69 | Parent directory of the label files 70 | -m MODE, --mode MODE 'xml' for converting from xml and 'txt' for converting 71 | from txt 72 | -o OUTPUT, --output OUTPUT 73 | Output name of csv file 74 | -c CLASSES, --classes CLASSES 75 | Label classes path 76 | ``` 77 | 78 | For example, if mine bucket name is **test**, the location of the label directory is **/User/test/labels**, the mode I choose from is **txt**, the output name and the class path is same as default. 79 | ```commandline 80 | python label_to_csv.py \ 81 | -p test\ 82 | -l /User/test/labels \ 83 | -m txt 84 | ``` 85 | 86 | The output file is `res.csv` by default. Afterwards, upload the csv file to the cloud storage and you can start training! 87 | 88 | -------------------------------------------------------------------------------- /tools/label_to_csv.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | Name: label_to_csv.py 6 | Author: Justin Ruan 7 | Contact: justin900429@gmail.com 8 | Time: 2021.02.06 9 | """ 10 | 11 | import os 12 | import argparse 13 | import codecs 14 | 15 | import pandas as pd 16 | 17 | 18 | def txt2csv(location, training_dir, path_prefix): 19 | # Return list 20 | temp_res = [] 21 | 22 | # Run through all the files 23 | for file in os.listdir(location): 24 | # Check the file name ends with txt 25 | # and not class.txt 26 | if (not file.endswith(".txt")) | \ 27 | (file == "classes.txt"): 28 | continue 29 | 30 | # Get the file name 31 | file_whole_name = f"{location}/{file}" 32 | 33 | # Read in txt as csv 34 | df_txt = pd.read_csv(file_whole_name, sep=" ", header=None) 35 | 36 | # Create data for each labels 37 | for index, row in df_txt.iterrows(): 38 | # Temp array for csv, initialized by the training types 39 | temp_csv = [str(training_dir)] 40 | 41 | # gs://prefix/name/{image_name} 42 | cloud_path = f"{path_prefix}/{os.path.splitext(file)[0]}.jpg" 43 | temp_csv.append(cloud_path) 44 | 45 | # Class label 46 | temp_csv.append(class_labels[int(row[0])]) 47 | 48 | # Add the upper left coordinate 49 | x_min = min(max(0.0, row[1] - row[3] / 2), 1.0) 50 | y_min = min(max(0.0, row[2] - row[4] / 2), 1.0) 51 | temp_csv.extend([x_min, y_min]) 52 | 53 | # Add the lower left coordinate (not necessary, left blank) 54 | temp_csv.extend(["", ""]) 55 | 56 | # Add the lower right coordinate 57 | x_max = min(max(0.0, row[1] + row[3] / 2), 1.0) 58 | y_max = min(max(0.0, row[2] + row[4] / 2), 1.0) 59 | temp_csv.extend([x_max, y_max]) 60 | 61 | # Add the upper right coordinate (not necessary, left blank) 62 | temp_csv.extend(["", ""]) 63 | 64 | # Append to the res 65 | temp_res.append(temp_csv) 66 | 67 | return temp_res 68 | 69 | 70 | def xml2csv(location, training_dir, path_prefix): 71 | # To parse the xml files 72 | import xml.etree.ElementTree as ET 73 | 74 | # Return list 75 | temp_res = [] 76 | 77 | # Run through all the files 78 | for file in os.listdir(location): 79 | # Check the file name ends with xml 80 | if not file.endswith(".xml"): 81 | continue 82 | 83 | # Get the file name 84 | file_whole_name = f"{location}/{file}" 85 | 86 | # Open the xml name 87 | tree = ET.parse(file_whole_name) 88 | root = tree.getroot() 89 | 90 | # Get the width, height of images 91 | # to normalize the bounding boxes 92 | size = root.find("size") 93 | width, height = float(size.find("width").text), float(size.find("height").text) 94 | 95 | # Find all the bounding objects 96 | for label_object in root.findall("object"): 97 | # Temp array for csv, initialized by the training types 98 | temp_csv = [str(training_dir)] 99 | 100 | # gs://prefix/name/{image_name} 101 | cloud_path = f"{path_prefix}/{os.path.splitext(file)[0]}.jpg" 102 | temp_csv.append(cloud_path) 103 | 104 | # Class label 105 | temp_csv.append(label_object.find("name").text) 106 | 107 | # Bounding box coordinate 108 | bounding_box = label_object.find("bndbox") 109 | 110 | # Add the upper left coordinate 111 | x_min = float(bounding_box.find("xmin").text) / width 112 | y_min = float(bounding_box.find("ymin").text) / height 113 | temp_csv.extend([x_min, y_min]) 114 | 115 | # Add the lower left coordinate (not necessary, left blank) 116 | temp_csv.extend(["", ""]) 117 | 118 | # Add the lower right coordinate 119 | x_max = float(bounding_box.find("xmax").text) / width 120 | y_max = float(bounding_box.find("ymax").text) / height 121 | temp_csv.extend([x_max, y_max]) 122 | 123 | # Add the upper right coordinate (not necessary, left blank) 124 | temp_csv.extend(["", ""]) 125 | 126 | # Append to the res 127 | temp_res.append(temp_csv) 128 | 129 | return temp_res 130 | 131 | 132 | if __name__ == "__main__": 133 | # Add the argument parse 134 | arg_p = argparse.ArgumentParser() 135 | arg_p.add_argument("-p", "--prefix", 136 | required=True, 137 | type=str, 138 | help="Bucket of the cloud storage path") 139 | arg_p.add_argument("-l", "--location", 140 | type=str, 141 | required=True, 142 | help="Location of the label files") 143 | arg_p.add_argument("-m", "--mode", 144 | type=str, 145 | required=True, 146 | help="'xml' for converting from xml and 'txt' for converting from txt") 147 | arg_p.add_argument("-o", "--output", 148 | type=str, 149 | default="res.csv", 150 | help="Output name of csv file") 151 | arg_p.add_argument("-c", "--classes", 152 | type=str, 153 | default=os.path.join("..", "data", "predefined_classes.txt"), 154 | help="Label classes path") 155 | args = vars(arg_p.parse_args()) 156 | 157 | # Class labels 158 | class_labels = [] 159 | 160 | # Load in the defined classes 161 | if os.path.exists(args["classes"]) is True: 162 | with codecs.open(args["classes"], 'r', 'utf8') as f: 163 | for line in f: 164 | line = line.strip() 165 | class_labels.append(line) 166 | else: # Exit if errors occurred 167 | print(f"File: {args['classes']} not exists") 168 | exit(1) 169 | 170 | # Prefix of the cloud storage 171 | ori_prefix = f"gs://{args['prefix']}" 172 | 173 | # Array for final csv file 174 | res = [] 175 | # Get all the file in dir 176 | for training_type_dir in os.listdir(args["location"]): 177 | # Get the dirname 178 | dir_name = f"{args['location']}/{training_type_dir}" 179 | 180 | # Check whether is dir 181 | if not os.path.isdir(dir_name): 182 | continue 183 | # Process the files 184 | 185 | for class_type_dir in os.listdir(dir_name): 186 | 187 | # Check whether is dir 188 | if not os.path.isdir(dir_name): 189 | continue 190 | 191 | prefix = f"{ori_prefix}/{class_type_dir}" 192 | 193 | # Convert the chosen extension to csv 194 | if args["mode"] == "txt": 195 | res.extend(txt2csv(f"{dir_name}/{class_type_dir}", 196 | training_type_dir, 197 | prefix)) 198 | elif args["mode"] == "xml": 199 | res.extend(xml2csv(f"{dir_name}/{class_type_dir}", 200 | training_type_dir, 201 | prefix)) 202 | else: 203 | print("Wrong argument for convert mode.\n" 204 | "'xml' for converting from xml to csv\n" 205 | "'txt' for converting from txt to csv") 206 | exit(1) 207 | 208 | # Write to the result csv 209 | res_csv = pd.DataFrame(res, 210 | columns=["set", "path", "label", 211 | "x_min", "y_min", 212 | "x_max", "y_min", 213 | "x_max", "y_max", 214 | "x_min", "y_max"]) 215 | res_csv.to_csv("res.csv", index=False, header=False) 216 | --------------------------------------------------------------------------------