├── .gitignore
├── .travis.yml
├── LICENSE.md
├── Makefile
├── README.md
├── _init_path.py
├── contributors.txt
├── data
├── predefined_classes.txt
├── predefined_cls_classes.txt
└── predefined_sub_classes.txt.example
├── freeze_build.py
├── icons
├── 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
├── help.png
├── labels.png
├── labels.svg
├── new.png
├── next.png
├── objects.png
├── open.png
├── open.svg
├── prev.png
├── quit.png
├── save-as.png
├── save-as.svg
├── save.png
├── save.svg
├── undo-cross.png
├── undo.png
├── zoom-in.png
├── zoom-out.png
└── zoom.png
├── labelImg.py
├── labelImg.spec
├── libs
├── ImageManagement.py
├── __init__.py
├── appSettings.py
├── canvas.py
├── colorDialog.py
├── configs.py
├── constants.py
├── labelDialog.py
├── labelFile.py
├── lib.py
├── pascalVocIO.py
├── remoteDialog.py
├── saveMaskImage.py
├── settingDialog.py
├── shape.py
├── toolBar.py
├── ustr.py
└── zoomWidget.py
├── resources.py
├── resources.qrc
├── screenshot
├── bbox_label.jpg
├── brush_task.jpg
├── cls_task.jpg
├── parse_label.jpg
├── remote_settings.JPG
└── setting_panel.jpg
└── scrips
├── __init__.py
└── generate_image.py
/.gitignore:
--------------------------------------------------------------------------------
1 | icons/.DS_Store
2 | database/
3 | *.pyc
4 | *.pkl
5 | .*.swp
6 |
7 | build/
8 | dist/
9 |
10 | tags
11 | test.py
12 | .idea/
13 | .vscode/
14 | tools/
15 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: python
2 | python:
3 | - "2.7"
4 |
5 | addons:
6 | apt:
7 | packages:
8 | - cmake
9 | - pyqt4-dev-tools
10 |
11 | # command to install dependencies
12 | #install: "pip install --user -r requirements.txt"
13 | # command to run tests
14 | script: make all
15 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Copyright (c) <2016-2017> lzx1413
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 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 |
2 | all: resources.py
3 |
4 | %.py: %.qrc
5 | pyrcc4 -o $@ $<
6 |
7 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # LabelImg
2 |
3 | [](https://travis-ci.org/lzx1413/labelImgPlus)
4 |
5 | LabelImg is a graphical image annotation tool.
6 |
7 | It is written in Python and uses Qt for its graphical interface.
8 |
9 | The annotation file will be saved as an XML file. The annotation format is PASCAL VOC format, and the format is the same as [ImageNet](http://www.image-net.org/)
10 |
11 | task mode change
12 |
13 | 
14 |
15 | DET mode
16 |
17 | 
18 |
19 | SEG mode
20 |
21 | 
22 |
23 | CLS mode
24 |
25 | 
26 |
27 | Brush SEG mode(in development: brush branch)
28 |
29 | 
30 |
31 | ## Release software for windows
32 | [baiduyun](https://pan.baidu.com/s/1iREYsJiQCzPPZGf6dFvauw)
33 | [googledriver](https://drive.google.com/open?id=118bUKQGlfwLgRTpptNzgBijJneInvw7T)
34 |
35 | ## Build source and use it
36 |
37 | Requires at least [Python 2.6](http://www.python.org/getit/) and has been tested with [PyQt4.8](http://www.riverbankcomputing.co.uk/software/pyqt/intro).
38 |
39 | In order to build the resource and assets, you need to install pyqt4-dev-tools:
40 |
41 | * Ubuntu
42 |
43 | `sudo apt-get install pyqt4-dev-tools`
44 | * Mac
45 | install pyqt4 with [instructions](https://robonobodojo.wordpress.com/2017/02/08/installing-pyqt4-on-mac-osx/)
46 |
47 | `sudo apt-get install python-opencv`
48 |
49 | `pip install lxml`
50 |
51 | `pip install qdarkstyle`
52 |
53 | `./labelImg.py`
54 |
55 | * Windows
56 |
57 | Need to download and setup [Python 2.6](https://www.python.org/downloads/windows/) or later and [PyQt4](https://www.riverbankcomputing.com/software/pyqt/download),lxml,qdarkstyle.
58 | Open cmd and go to $labelImg,
59 |
60 | `$ pyrcc4 -o resources.py resources.qrc`
61 |
62 | `$ python labelImg.py`
63 |
64 | ## Default file framework
65 |
66 | |---Images
67 |
68 | |---images_1
69 |
70 | |---images_2
71 |
72 | |---Annotation
73 |
74 | |---images_1
75 |
76 | |---images_2
77 |
78 | the file containing annotations will be created by default.
79 |
80 | ## Usage
81 | After cloning the code, you should run `$ make all` to generate the resource file.
82 |
83 | You can then start annotating by running `$ ./labelImg.py`. For usage
84 | instructions you can see [Here](https://youtu.be/p0nR2YsCY_U)
85 |
86 | At the moment annotations are saved as an XML file. The format is PASCAL VOC format, and the format is the same as [ImageNet](http://www.image-net.org/)
87 |
88 | You can also see [ImageNet Utils](https://github.com/tzutalin/ImageNet_Utils) to download image, create a label text for machine learning, etc
89 |
90 | ### Label and parsing
91 |
92 | support rectangle label and parsing labels
93 |
94 | ### Create pre-defined classes
95 |
96 | You can edit the [data/predefined_classes.txt](https://github.com/tzutalin/labelImg/blob/master/data/predefined_classes.txt) to load pre-defined classes
97 |
98 | You also can create labels with two levels in [data/predefined_sub_classes.txt](https://github.com/lzx1413/labelImg/blob/master/data/predefined_sub_classes.txt)
99 |
100 | And the labels will be ranked by the frequency you use it.
101 |
102 | ### General steps from scratch
103 |
104 | * Build and launch: `$ make all; python labelImg.py`
105 |
106 | * Click 'Change default saved annotation folder' in Menu/File
107 |
108 | * Click 'Open Dir'
109 |
110 | * Click 'Create RectBox'
111 |
112 | The annotation will be saved to the folder you specifiy
113 |
114 | ### Hotkeys
115 |
116 | * Ctrl + r : Change the defult target dir which saving annotation files
117 |
118 | * Ctrl + n : Create a bounding box
119 |
120 | * Ctrl + s : Save
121 |
122 | * Right : Next image
123 |
124 | * Left : Previous image
125 |
126 | ### Online image data mode
127 |
128 | the server have to make the images in a folder that clint can get from http/https with **get** function
129 |
130 | * settings
131 |
132 | open File -->RemoteDBSettings(ctrl+m) like that
133 |
134 | 
135 |
136 | the remote image list is a file contenting the name of the images (a line is a image) .
137 |
138 | the image will be cached in a folder created in the software file named database/pics/XXXX and this will take a lot of memory if there are a lot of images,and this will be modified in the future.
139 |
140 | open File -->ChangedDefaultSavedAnnotationDir(ctrl+r) to set the folder to save the results
141 |
142 | 2. if your settings are right,you will find the **Get Images** button becomes enabled and click it ,then you can annotate the images as before
143 |
144 | ### Change list
145 | * 18-08-19 py3 pyqt5 supported in the branch py3
146 |
147 | * 17-08-14 add class label function
148 |
149 | ### Todo list
150 | * support pyqt5 and python 3
151 | * add more functions while adding parsing labels
152 | * refine the setting functions
153 |
154 | ### How to contribute
155 | Send a pull request
156 |
157 | ### License
158 | [License](LICENSE.md)
159 |
--------------------------------------------------------------------------------
/_init_path.py:
--------------------------------------------------------------------------------
1 | """Set up paths"""
2 | import sys
3 |
4 | def add_path(path):
5 | if path not in sys.path:
6 | sys.path.insert(0, path)
7 |
8 | add_path('libs')
9 |
--------------------------------------------------------------------------------
/contributors.txt:
--------------------------------------------------------------------------------
1 | TzuTa Lin
2 | lzx1413
3 | [LabelMe](http://labelme2.csail.mit.edu/Release3.0/index.php)
4 |
5 |
--------------------------------------------------------------------------------
/data/predefined_classes.txt:
--------------------------------------------------------------------------------
1 | 1
2 | 2
3 | 3
4 | 4
5 | 5
6 | 6
7 | 7
8 | 8
9 | 9
10 | 0
11 | A
12 | B
13 | C
14 | D
15 | H
16 | X
17 | -
18 | =
--------------------------------------------------------------------------------
/data/predefined_cls_classes.txt:
--------------------------------------------------------------------------------
1 | dog
2 | cat
3 | people
4 |
--------------------------------------------------------------------------------
/data/predefined_sub_classes.txt.example:
--------------------------------------------------------------------------------
1 | body: hair face neck arm leg hand skin foot
2 | upper cloth: Apparel blazer blouse cardigan coat jacket hoodie jumper shirt sweater sweatshirt top t-shirt vest suit intimate
3 | trouser: jeans pants leggings panties romper shorts tights
4 | shoe: boots clogs flats heels loafers pumps sandals shoes sneakers wedges
5 | accessory: accessory bracelet decor earrings necklace Pearl ring sunglasses glasses watch
6 | overcoat: bodysuit swimwear
7 | cloth: bag belt bra cape gloves hat purse satchel scarf socks tie stockings denim
8 | dress: dress skirt
9 | others: case cream others wallet souvenir sister black
--------------------------------------------------------------------------------
/freeze_build.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | from subprocess import call
3 | call(["pyinstaller", "--onefile", "--windowed", "labelImg.py"])
4 |
5 | # Now it is a workaround. It should use hook file
6 | def readlines(filename):
7 | result = []
8 | with open(filename, "r") as ins:
9 | for line in ins:
10 | result.append(line)
11 | return result
12 |
13 | lines = readlines('labelImg.spec')
14 | for ind, line in enumerate(lines):
15 | if 'hiddenimports' in line:
16 | lines[ind] = "\t\t\t hiddenimports = ['cv2', 'json', 'lxml.etree', 'lxml', 'etree', 'xml.etree.ElementTree'],\n"
17 | print lines[ind]
18 |
19 | FILE = open('labelImg.spec', "w")
20 | FILE.writelines(lines)
21 | FILE.close()
22 |
23 | call(["pyinstaller", "labelImg.spec"])
24 |
--------------------------------------------------------------------------------
/icons/cancel.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lzx1413/LabelImgTool/264aa97dd74a29b45513d311c21975fa26db39dd/icons/cancel.png
--------------------------------------------------------------------------------
/icons/close.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lzx1413/LabelImgTool/264aa97dd74a29b45513d311c21975fa26db39dd/icons/close.png
--------------------------------------------------------------------------------
/icons/color.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lzx1413/LabelImgTool/264aa97dd74a29b45513d311c21975fa26db39dd/icons/color.png
--------------------------------------------------------------------------------
/icons/color_line.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lzx1413/LabelImgTool/264aa97dd74a29b45513d311c21975fa26db39dd/icons/color_line.png
--------------------------------------------------------------------------------
/icons/copy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lzx1413/LabelImgTool/264aa97dd74a29b45513d311c21975fa26db39dd/icons/copy.png
--------------------------------------------------------------------------------
/icons/delete.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lzx1413/LabelImgTool/264aa97dd74a29b45513d311c21975fa26db39dd/icons/delete.png
--------------------------------------------------------------------------------
/icons/done.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lzx1413/LabelImgTool/264aa97dd74a29b45513d311c21975fa26db39dd/icons/done.png
--------------------------------------------------------------------------------
/icons/done.svg:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
401 |
--------------------------------------------------------------------------------
/icons/edit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lzx1413/LabelImgTool/264aa97dd74a29b45513d311c21975fa26db39dd/icons/edit.png
--------------------------------------------------------------------------------
/icons/expert1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lzx1413/LabelImgTool/264aa97dd74a29b45513d311c21975fa26db39dd/icons/expert1.png
--------------------------------------------------------------------------------
/icons/expert2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lzx1413/LabelImgTool/264aa97dd74a29b45513d311c21975fa26db39dd/icons/expert2.png
--------------------------------------------------------------------------------
/icons/eye.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lzx1413/LabelImgTool/264aa97dd74a29b45513d311c21975fa26db39dd/icons/eye.png
--------------------------------------------------------------------------------
/icons/feBlend-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lzx1413/LabelImgTool/264aa97dd74a29b45513d311c21975fa26db39dd/icons/feBlend-icon.png
--------------------------------------------------------------------------------
/icons/file.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lzx1413/LabelImgTool/264aa97dd74a29b45513d311c21975fa26db39dd/icons/file.png
--------------------------------------------------------------------------------
/icons/fit-width.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lzx1413/LabelImgTool/264aa97dd74a29b45513d311c21975fa26db39dd/icons/fit-width.png
--------------------------------------------------------------------------------
/icons/fit-window.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lzx1413/LabelImgTool/264aa97dd74a29b45513d311c21975fa26db39dd/icons/fit-window.png
--------------------------------------------------------------------------------
/icons/fit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lzx1413/LabelImgTool/264aa97dd74a29b45513d311c21975fa26db39dd/icons/fit.png
--------------------------------------------------------------------------------
/icons/help.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lzx1413/LabelImgTool/264aa97dd74a29b45513d311c21975fa26db39dd/icons/help.png
--------------------------------------------------------------------------------
/icons/labels.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lzx1413/LabelImgTool/264aa97dd74a29b45513d311c21975fa26db39dd/icons/labels.png
--------------------------------------------------------------------------------
/icons/labels.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/icons/new.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lzx1413/LabelImgTool/264aa97dd74a29b45513d311c21975fa26db39dd/icons/new.png
--------------------------------------------------------------------------------
/icons/next.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lzx1413/LabelImgTool/264aa97dd74a29b45513d311c21975fa26db39dd/icons/next.png
--------------------------------------------------------------------------------
/icons/objects.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lzx1413/LabelImgTool/264aa97dd74a29b45513d311c21975fa26db39dd/icons/objects.png
--------------------------------------------------------------------------------
/icons/open.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lzx1413/LabelImgTool/264aa97dd74a29b45513d311c21975fa26db39dd/icons/open.png
--------------------------------------------------------------------------------
/icons/open.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/icons/prev.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lzx1413/LabelImgTool/264aa97dd74a29b45513d311c21975fa26db39dd/icons/prev.png
--------------------------------------------------------------------------------
/icons/quit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lzx1413/LabelImgTool/264aa97dd74a29b45513d311c21975fa26db39dd/icons/quit.png
--------------------------------------------------------------------------------
/icons/save-as.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lzx1413/LabelImgTool/264aa97dd74a29b45513d311c21975fa26db39dd/icons/save-as.png
--------------------------------------------------------------------------------
/icons/save.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lzx1413/LabelImgTool/264aa97dd74a29b45513d311c21975fa26db39dd/icons/save.png
--------------------------------------------------------------------------------
/icons/save.svg:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
680 |
--------------------------------------------------------------------------------
/icons/undo-cross.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lzx1413/LabelImgTool/264aa97dd74a29b45513d311c21975fa26db39dd/icons/undo-cross.png
--------------------------------------------------------------------------------
/icons/undo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lzx1413/LabelImgTool/264aa97dd74a29b45513d311c21975fa26db39dd/icons/undo.png
--------------------------------------------------------------------------------
/icons/zoom-in.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lzx1413/LabelImgTool/264aa97dd74a29b45513d311c21975fa26db39dd/icons/zoom-in.png
--------------------------------------------------------------------------------
/icons/zoom-out.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lzx1413/LabelImgTool/264aa97dd74a29b45513d311c21975fa26db39dd/icons/zoom-out.png
--------------------------------------------------------------------------------
/icons/zoom.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lzx1413/LabelImgTool/264aa97dd74a29b45513d311c21975fa26db39dd/icons/zoom.png
--------------------------------------------------------------------------------
/labelImg.spec:
--------------------------------------------------------------------------------
1 | # -*- mode: python -*-
2 |
3 | block_cipher = None
4 |
5 |
6 | a = Analysis(['labelImg.py'],
7 | pathex=['D:\\workplace\\labelImg'],
8 | binaries=[],
9 | datas=[],
10 | hiddenimports=[],
11 | hookspath=[],
12 | runtime_hooks=[],
13 | excludes=[],
14 | win_no_prefer_redirects=False,
15 | win_private_assemblies=False,
16 | cipher=block_cipher)
17 | pyz = PYZ(a.pure, a.zipped_data,
18 | cipher=block_cipher)
19 | exe = EXE(pyz,
20 | a.scripts,
21 | a.binaries,
22 | a.zipfiles,
23 | a.datas,
24 | name='labelImg',
25 | debug=False,
26 | strip=False,
27 | upx=True,
28 | console=True )
29 |
--------------------------------------------------------------------------------
/libs/ImageManagement.py:
--------------------------------------------------------------------------------
1 | import urllib
2 | import threading
3 | import os
4 |
5 |
6 | class loadImageThread(threading.Thread):
7 |
8 | def __init__(self, website, image_list, dowloaded_list, FilePath):
9 | threading.Thread.__init__(self)
10 | self.website = str(website)
11 | self.filepath = FilePath
12 | self.image_list = image_list
13 | self.mDowloaded_list = dowloaded_list
14 |
15 | def run(self):
16 | for image_url in self.image_list:
17 | print self.website + image_url
18 | urllib.urlretrieve(
19 | self.website + image_url,
20 | self.filepath + image_url)
21 | self.mDowloaded_list.append(
22 | os.path.abspath(
23 | self.filepath + image_url))
24 |
25 |
26 | def loadOnlineImgMul(
27 | website,
28 | image_list,
29 | thread_num,
30 | dowloaded_image_list,
31 | FilePath):
32 | if len(image_list) / thread_num == 0:
33 | thread_num = 1
34 | if thread_num == 1:
35 | num_per_thread = len(image_list)
36 | else:
37 | num_per_thread = len(image_list) / thread_num
38 | for i in xrange(thread_num + 1):
39 | if (i + 1) * num_per_thread > len(image_list):
40 | t = loadImageThread(
41 | website,
42 | image_list[
43 | i * num_per_thread:-1],
44 | dowloaded_image_list,
45 | FilePath)
46 | t.start()
47 | else:
48 | t = loadImageThread(
49 | website,
50 | image_list[
51 | i *
52 | num_per_thread:(
53 | i +
54 | 1) *
55 | num_per_thread],
56 | dowloaded_image_list,
57 | FilePath)
58 | t.start()
59 |
--------------------------------------------------------------------------------
/libs/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lzx1413/LabelImgTool/264aa97dd74a29b45513d311c21975fa26db39dd/libs/__init__.py
--------------------------------------------------------------------------------
/libs/appSettings.py:
--------------------------------------------------------------------------------
1 | import pickle
2 | import os
3 |
4 | class APPSettings(object):
5 | def __init__(self):
6 | self.data = {}
7 | self.path = '.settings.pkl'
8 |
9 | def __setitem__(self, key, value):
10 | self.data[key] = value
11 |
12 | def __getitem__(self, key):
13 | return self.data[key]
14 |
15 | def get(self, key, default=None):
16 | if key in self.data:
17 | return self.data[key]
18 | return default
19 |
20 | def save(self):
21 | with open(self.path, 'wb') as f:
22 | pickle.dump(self.data, f, pickle.HIGHEST_PROTOCOL)
23 | return True
24 | return False
25 |
26 | def load(self):
27 | if os.path.exists(self.path):
28 | with open(self.path, 'rb') as f:
29 | self.data = pickle.load(f)
30 | return True
31 | return False
32 |
33 |
--------------------------------------------------------------------------------
/libs/canvas.py:
--------------------------------------------------------------------------------
1 | from PyQt4.QtGui import *
2 | from PyQt4.QtCore import *
3 | #from PyQt4.QtOpenGL import *
4 |
5 | from shape import Shape
6 | from lib import distance
7 |
8 | CURSOR_DEFAULT = Qt.ArrowCursor
9 | CURSOR_POINT = Qt.PointingHandCursor
10 | CURSOR_DRAW = Qt.CrossCursor
11 | CURSOR_MOVE = Qt.ClosedHandCursor
12 | CURSOR_GRAB = Qt.OpenHandCursor
13 |
14 | # class Canvas(QGLWidget):
15 |
16 |
17 | class Canvas(QWidget):
18 | zoomRequest = pyqtSignal(int)
19 | scrollRequest = pyqtSignal(int, int)
20 | newShape = pyqtSignal()
21 | selectionChanged = pyqtSignal(bool)
22 | shapeMoved = pyqtSignal()
23 | drawingPolygon = pyqtSignal(bool)
24 |
25 | CREATE, EDIT = range(2)
26 | RECT_SHAPE, POLYGON_SHAPE = range(2)
27 |
28 | epsilon = 11.0
29 |
30 | def __init__(self, *args, **kwargs):
31 | super(Canvas, self).__init__(*args, **kwargs)
32 | # Initialise local state.
33 | self.shape_type = self.POLYGON_SHAPE
34 | self.brush_point = None
35 | self.task_mode = 3
36 | self.erase_mode = False
37 | self.current_brush_path = None
38 | self.mask_Image = None
39 | self.brush_color =QColor(255,0,0,255)
40 | self.brush_size = 10
41 | self.brush = QPainter();
42 | self.mode = self.EDIT
43 | self.shapes = []
44 | self.current = None
45 | self.selectedShape = None # save the selected shape here
46 | self.selectedShapeCopy = None
47 | self.lineColor = QColor(0, 0, 255)
48 | self.line = Shape(line_color=self.lineColor)
49 | self.prevPoint = QPointF()
50 | self.offsets = QPointF(), QPointF()
51 | self.scale = 1.0
52 | self.bg_image = QImage()
53 | self.visible = {}
54 | self._hideBackround = False
55 | self.hideBackround = False
56 | self.hShape = None
57 | self.hVertex = None
58 | self._painter = QPainter(self)
59 | self.font_size = 50
60 | self._cursor = CURSOR_DEFAULT
61 | # Menus:
62 | self.menus = (QMenu(), QMenu())
63 | # Set widget options.
64 | self.setMouseTracking(True)
65 | self.setFocusPolicy(Qt.WheelFocus)
66 |
67 | def set_shape_type(self, type):
68 | if type == 0:
69 | self.shape_type = self.RECT_SHAPE
70 | self.line.set_shape_type(type)
71 | return True
72 | elif type == 1:
73 | self.shape_type = self.POLYGON_SHAPE
74 | self.line.set_shape_type(type)
75 | return True
76 | else:
77 | print "not support the shape type: " + str(type)
78 | return False
79 |
80 | def enterEvent(self, ev):
81 | self.overrideCursor(self._cursor)
82 | def get_mask_image(self):
83 | return self.mask_pixmap
84 |
85 | def leaveEvent(self, ev):
86 | self.restoreCursor()
87 |
88 | def focusOutEvent(self, ev):
89 | self.restoreCursor()
90 |
91 | def isVisible(self, shape):
92 | return self.visible.get(shape, True)
93 |
94 | def drawing(self):
95 | return self.mode == self.CREATE
96 |
97 | def editing(self):
98 | return self.mode == self.EDIT
99 |
100 | def setEditing(self, value=True):
101 | self.mode = self.EDIT if value else self.CREATE
102 | if not value: # Create
103 | self.unHighlight()
104 | self.deSelectShape()
105 |
106 | def unHighlight(self):
107 | if self.hShape:
108 | self.hShape.highlightClear()
109 | self.hVertex = self.hShape = None
110 |
111 | def selectedVertex(self):
112 | return self.hVertex is not None
113 |
114 | def mouseMoveEvent(self, ev):
115 | """Update line with last point and current coordinates."""
116 | pos = self.transformPos(ev.posF())
117 | self.restoreCursor()
118 | if self.task_mode == 3:
119 | self.brush_point = pos
120 |
121 | if Qt.LeftButton & ev.buttons():
122 | if self.outOfPixmap(pos):
123 | return
124 | if not self.current_brush_path:
125 | self.current_brush_path = QPainterPath()
126 | self.current_brush_path.moveTo(pos)
127 | else:
128 | self.current_brush_path.lineTo(pos)
129 | self.repaint()
130 | return
131 |
132 |
133 | # Polygon drawing.
134 | if self.drawing():
135 | self.overrideCursor(CURSOR_DRAW)
136 | if self.current:
137 | color = self.lineColor
138 | if self.outOfPixmap(pos):
139 | # Don't allow the user to draw outside the pixmap.
140 | # Project the point to the pixmap's edges.
141 | pos = self.intersectionPoint(self.current[-1], pos)
142 | elif len(self.current) > 1 and self.closeEnough(pos, self.current[0]):
143 | # Attract line to starting point and colorise to alert the
144 | # user:
145 | pos = self.current[0]
146 | color = self.current.line_color
147 | self.overrideCursor(CURSOR_POINT)
148 | self.current.highlightVertex(0, Shape.NEAR_VERTEX)
149 | self.line[1] = pos
150 | self.line.line_color = color
151 | self.repaint()
152 | self.current.highlightClear()
153 | return
154 |
155 | # Polygon copy moving.
156 | if Qt.RightButton & ev.buttons():
157 | if self.selectedShapeCopy and self.prevPoint:
158 | self.overrideCursor(CURSOR_MOVE)
159 | self.boundedMoveShape(self.selectedShapeCopy, pos)
160 | self.repaint()
161 | elif self.selectedShape:
162 | self.selectedShapeCopy = self.selectedShape.copy()
163 | self.repaint()
164 | return
165 |
166 | # Polygon/Vertex moving.
167 | if Qt.LeftButton & ev.buttons():
168 | if self.selectedVertex():
169 | self.boundedMoveVertex(pos)
170 | self.shapeMoved.emit()
171 | self.repaint()
172 | elif self.selectedShape and self.prevPoint:
173 | self.overrideCursor(CURSOR_MOVE)
174 | self.boundedMoveShape(self.selectedShape, pos)
175 | self.shapeMoved.emit()
176 | self.repaint()
177 | return
178 |
179 | # Just hovering over the canvas, 2 posibilities:
180 | # - Highlight shapes
181 | # - Highlight vertex
182 | # Update shape/vertex fill and tooltip value accordingly.
183 | self.setToolTip("Image")
184 | for shape in reversed([s for s in self.shapes if self.isVisible(s)]):
185 | # Look for a nearby vertex to highlight. If that fails,
186 | # check if we happen to be inside a shape.
187 | index = shape.nearestVertex(pos, self.epsilon)
188 | if index is not None:
189 | if self.selectedVertex():
190 | self.hShape.highlightClear()
191 | self.hVertex, self.hShape = index, shape
192 | shape.highlightVertex(index, shape.MOVE_VERTEX)
193 | self.overrideCursor(CURSOR_POINT)
194 | self.setToolTip("Click & drag to move point")
195 | self.setStatusTip(self.toolTip())
196 | self.update()
197 | break
198 | elif shape.containsPoint(pos):
199 | if self.selectedVertex():
200 | self.hShape.highlightClear()
201 | self.hVertex, self.hShape = None, shape
202 | self.setToolTip(
203 | "Click & drag to move shape '%s'" %
204 | shape.label)
205 | self.setStatusTip(self.toolTip())
206 | self.overrideCursor(CURSOR_GRAB)
207 | self.update()
208 | break
209 | else: # Nothing found, clear highlights, reset state.
210 | if self.hShape:
211 | self.hShape.highlightClear()
212 | self.update()
213 | self.hVertex, self.hShape = None, None
214 |
215 | def mousePressEvent(self, ev):
216 | pos = self.transformPos(ev.posF())
217 | if ev.button() == Qt.LeftButton:
218 | if self.drawing():
219 | if self.shape_type == self.POLYGON_SHAPE and self.current:
220 | self.current.addPoint(self.line[1])
221 | self.line[0] = self.current[-1]
222 | if self.current.isClosed():
223 | self.finalise()
224 | elif self.shape_type == self.RECT_SHAPE and self.current and self.current.reachMaxPoints() is False:
225 | initPos = self.current[0]
226 | minX = initPos.x()
227 | minY = initPos.y()
228 | targetPos = self.line[1]
229 | maxX = targetPos.x()
230 | maxY = targetPos.y()
231 | self.current.addPoint(QPointF(minX, maxY))
232 | self.current.addPoint(targetPos)
233 | self.current.addPoint(QPointF(maxX, minY))
234 | self.current.addPoint(initPos)
235 | self.line[0] = self.current[-1]
236 | if self.current.isClosed():
237 | self.finalise()
238 | elif not self.outOfPixmap(pos):
239 | self.current = Shape(shape_type=self.shape_type)
240 | self.current.addPoint(pos)
241 | self.line.points = [pos, pos]
242 | self.setHiding()
243 | self.drawingPolygon.emit(True)
244 | self.update()
245 | else:
246 | self.selectShapePoint(pos)
247 | self.prevPoint = pos
248 | self.repaint()
249 | elif ev.button() == Qt.RightButton and self.editing():
250 | self.selectShapePoint(pos)
251 | self.prevPoint = pos
252 | self.repaint()
253 |
254 | def mouseReleaseEvent(self, ev):
255 | if ev.button() == Qt.RightButton:
256 | menu = self.menus[bool(self.selectedShapeCopy)]
257 | self.restoreCursor()
258 | if not menu.exec_(self.mapToGlobal(ev.pos()))\
259 | and self.selectedShapeCopy:
260 | # Cancel the move by deleting the shadow copy.
261 | self.selectedShapeCopy = None
262 | self.repaint()
263 | elif ev.button() == Qt.LeftButton and self.selectedShape:
264 | self.overrideCursor(CURSOR_GRAB)
265 | elif ev.button() == Qt.LeftButton and self.task_mode == 3 and self.current_brush_path:
266 | self.current_brush_path = None
267 |
268 | def endMove(self, copy=False):
269 | assert self.selectedShape and self.selectedShapeCopy
270 | shape = self.selectedShapeCopy
271 | #del shape.fill_color
272 | #del shape.line_color
273 | if copy:
274 | self.shapes.append(shape)
275 | self.selectedShape.selected = False
276 | self.selectedShape = shape
277 | self.repaint()
278 | else:
279 | shape.label = self.selectedShape.label
280 | self.deleteSelected()
281 | self.shapes.append(shape)
282 | self.selectedShapeCopy = None
283 |
284 | def hideBackroundShapes(self, value):
285 | self.hideBackround = value
286 | if self.selectedShape:
287 | # Only hide other shapes if there is a current selection.
288 | # Otherwise the user will not be able to select a shape.
289 | self.setHiding(True)
290 | self.repaint()
291 |
292 | def setHiding(self, enable=True):
293 | self._hideBackround = self.hideBackround if enable else False
294 |
295 | def canCloseShape(self):
296 | return self.drawing() and self.current and len(self.current) > 2
297 |
298 | def mouseDoubleClickEvent(self, ev):
299 | # We need at least 4 points here, since the mousePress handler
300 | # adds an extra one before this handler is called.
301 | if self.canCloseShape() and len(self.current) > 3:
302 | self.current.popPoint()
303 | self.finalise()
304 |
305 | def selectShape(self, shape):
306 | self.deSelectShape()
307 | shape.selected = True
308 | self.selectedShape = shape
309 | self.setHiding()
310 | self.selectionChanged.emit(True)
311 | self.update()
312 |
313 | def selectShapePoint(self, point):
314 | """Select the first shape created which contains this point."""
315 | self.deSelectShape()
316 | if self.selectedVertex(): # A vertex is marked for selection.
317 | index, shape = self.hVertex, self.hShape
318 | shape.highlightVertex(index, shape.MOVE_VERTEX)
319 | return
320 | for shape in reversed(self.shapes):
321 | if self.isVisible(shape) and shape.containsPoint(point):
322 | shape.selected = True
323 | self.selectedShape = shape
324 | self.calculateOffsets(shape, point)
325 | self.setHiding()
326 | self.selectionChanged.emit(True)
327 | return
328 |
329 | def calculateOffsets(self, shape, point):
330 | rect = shape.boundingRect()
331 | x1 = rect.x() - point.x()
332 | y1 = rect.y() - point.y()
333 | x2 = (rect.x() + rect.width()) - point.x()
334 | y2 = (rect.y() + rect.height()) - point.y()
335 | self.offsets = QPointF(x1, y1), QPointF(x2, y2)
336 |
337 | def boundedMoveVertex(self, pos):
338 | index, shape = self.hVertex, self.hShape
339 | point = shape[index]
340 | if self.outOfPixmap(pos):
341 | pos = self.intersectionPoint(point, pos)
342 |
343 | shiftPos = pos - point
344 | shape.moveVertexBy(index, shiftPos)
345 |
346 | if self.shape_type == self.RECT_SHAPE:
347 | lindex = (index + 1) % 4
348 | rindex = (index + 3) % 4
349 | lshift = None
350 | rshift = None
351 | if index % 2 == 0:
352 | lshift = QPointF(shiftPos.x(), 0)
353 | rshift = QPointF(0, shiftPos.y())
354 | else:
355 | rshift = QPointF(shiftPos.x(), 0)
356 | lshift = QPointF(0, shiftPos.y())
357 | shape.moveVertexBy(rindex, rshift)
358 | shape.moveVertexBy(lindex, lshift)
359 |
360 | def boundedMoveShape(self, shape, pos):
361 | if self.outOfPixmap(pos):
362 | return False # No need to move
363 | o1 = pos + self.offsets[0]
364 | if self.outOfPixmap(o1):
365 | pos -= QPointF(min(0, o1.x()), min(0, o1.y()))
366 | o2 = pos + self.offsets[1]
367 | if self.outOfPixmap(o2):
368 | pos += QPointF(min(0, self.bg_image.width() - o2.x()),
369 | min(0, self.bg_image.height() - o2.y()))
370 | # The next line tracks the new position of the cursor
371 | # relative to the shape, but also results in making it
372 | # a bit "shaky" when nearing the border and allows it to
373 | # go outside of the shape's area for some reason. XXX
374 | #self.calculateOffsets(self.selectedShape, pos)
375 | dp = pos - self.prevPoint
376 | if dp:
377 | shape.moveBy(dp)
378 | self.prevPoint = pos
379 | return True
380 | return False
381 |
382 | def deSelectShape(self):
383 | if self.selectedShape:
384 | self.selectedShape.selected = False
385 | self.selectedShape = None
386 | self.setHiding(False)
387 | self.selectionChanged.emit(False)
388 | self.update()
389 |
390 | def deleteSelected(self):
391 | if self.selectedShape:
392 | shape = self.selectedShape
393 | self.shapes.remove(self.selectedShape)
394 | self.selectedShape = None
395 | self.update()
396 | return shape
397 |
398 | def copySelectedShape(self):
399 | if self.selectedShape:
400 | shape = self.selectedShape.copy()
401 | self.deSelectShape()
402 | self.shapes.append(shape)
403 | shape.selected = True
404 | self.selectedShape = shape
405 | self.boundedShiftShape(shape)
406 | return shape
407 |
408 | def boundedShiftShape(self, shape):
409 | # Try to move in one direction, and if it fails in another.
410 | # Give up if both fail.
411 | point = shape[0]
412 | offset = QPointF(2.0, 2.0)
413 | self.calculateOffsets(shape, point)
414 | self.prevPoint = point
415 | if not self.boundedMoveShape(shape, point - offset):
416 | self.boundedMoveShape(shape, point + offset)
417 |
418 | def paintEvent(self, event):
419 | if not self.bg_image:
420 | return super(Canvas, self).paintEvent(event)
421 |
422 | p = self._painter
423 | p.begin(self)
424 | p.setFont(QFont('Times', self.font_size, QFont.Bold))
425 | p.setRenderHint(QPainter.Antialiasing)
426 | p.setRenderHint(QPainter.HighQualityAntialiasing)
427 | p.setRenderHint(QPainter.SmoothPixmapTransform)
428 |
429 | p.scale(self.scale, self.scale)
430 | p.translate(self.offsetToCenter())
431 |
432 | p.drawImage(0, 0, self.bg_image)
433 | #print self.brush_point.x(),self.brush_point.y()
434 | if self.task_mode == 3:
435 | p.setOpacity(0.3)
436 | p.drawImage(0,0,self.mask_pixmap)
437 | if self.brush_point:
438 | p.drawEllipse(self.brush_point,self.brush_size/2,self.brush_size/2)
439 | if self.current_brush_path:
440 | if self.mask_pixmap.isNull():
441 | self.mask_pixmap = QImage(self.bg_image.size(), QImage.Format_ARGB32)
442 | self.mask_pixmap.fill(QColor(255,255,255,0))
443 | self.brush.begin(self.mask_pixmap)
444 | brush_pen = QPen()
445 | self.brush.setCompositionMode(QPainter.CompositionMode_Source)
446 | brush_pen.setColor(self.brush_color)
447 | brush_pen.setWidth(self.brush_size)
448 | brush_pen.setCapStyle(Qt.RoundCap)
449 | brush_pen.setJoinStyle(Qt.RoundJoin)
450 | self.brush.setPen(brush_pen)
451 | self.brush.drawPath(self.current_brush_path)
452 | self.brush.end()
453 | Shape.scale = self.scale
454 | for shape in self.shapes:
455 | if shape.fill_color:
456 | shape.fill = True
457 | shape.paint(p)
458 | elif (shape.selected or not self._hideBackround) and self.isVisible(shape):
459 | shape.fill = shape.selected or shape == self.hShape
460 | shape.paint(p)
461 | if self.current:
462 | self.current.paint(p)
463 | self.line.paint(p)
464 | if self.selectedShapeCopy:
465 | self.selectedShapeCopy.paint(p)
466 | # Paint rect
467 | if self.current is not None and len(self.line) == 2:
468 | leftTop = self.line[0]
469 | rightBottom = self.line[1]
470 | rectWidth = rightBottom.x() - leftTop.x()
471 | rectHeight = rightBottom.y() - leftTop.y()
472 | color = QColor(0, 220, 0)
473 | p.setPen(color)
474 | brush = QBrush(Qt.BDiagPattern)
475 | p.setBrush(brush)
476 | if self.shape_type == self.RECT_SHAPE:
477 | p.drawRect(leftTop.x(), leftTop.y(), rectWidth, rectHeight)
478 |
479 | p.end()
480 |
481 | def transformPos(self, point):
482 | """Convert from widget-logical coordinates to painter-logical coordinates."""
483 | return point / self.scale - self.offsetToCenter()
484 |
485 | def offsetToCenter(self):
486 | s = self.scale
487 | area = super(Canvas, self).size()
488 | if self.bg_image:
489 | w, h = self.bg_image.width() * s, self.bg_image.height() * s
490 | else:
491 | w,h = 100,100
492 | aw, ah = area.width(), area.height()
493 | x = (aw - w) / (2 * s) if aw > w else 0
494 | y = (ah - h) / (2 * s) if ah > h else 0
495 | return QPointF(x, y)
496 |
497 | def outOfPixmap(self, p):
498 | w, h = self.bg_image.width(), self.bg_image.height()
499 | return not (0 <= p.x() <= w and 0 <= p.y() <= h)
500 |
501 | def finalise(self):
502 | assert self.current
503 | self.current.close()
504 | self.shapes.append(self.current)
505 | self.current = None
506 | self.setHiding(False)
507 | self.newShape.emit()
508 | self.update()
509 |
510 | def closeEnough(self, p1, p2):
511 | #d = distance(p1 - p2)
512 | #m = (p1-p2).manhattanLength()
513 | # print "d %.2f, m %d, %.2f" % (d, m, d - m)
514 | return distance(p1 - p2) < self.epsilon
515 |
516 | def intersectionPoint(self, p1, p2):
517 | # Cycle through each image edge in clockwise fashion,
518 | # and find the one intersecting the current line segment.
519 | # http://paulbourke.net/geometry/lineline2d/
520 | size = self.bg_image.size()
521 | points = [(0, 0),
522 | (size.width(), 0),
523 | (size.width(), size.height()),
524 | (0, size.height())]
525 | x1, y1 = p1.x(), p1.y()
526 | x2, y2 = p2.x(), p2.y()
527 | d, i, (x, y) = min(self.intersectingEdges((x1, y1), (x2, y2), points))
528 | x3, y3 = points[i]
529 | x4, y4 = points[(i + 1) % 4]
530 | if (x, y) == (x1, y1):
531 | # Handle cases where previous point is on one of the edges.
532 | if x3 == x4:
533 | return QPointF(x3, min(max(0, y2), max(y3, y4)))
534 | else: # y3 == y4
535 | return QPointF(min(max(0, x2), max(x3, x4)), y3)
536 | return QPointF(x, y)
537 |
538 | def intersectingEdges(self, xxx_todo_changeme, xxx_todo_changeme1, points):
539 | """For each edge formed by `points', yield the intersection
540 | with the line segment `(x1,y1) - (x2,y2)`, if it exists.
541 | Also return the distance of `(x2,y2)' to the middle of the
542 | edge along with its index, so that the one closest can be chosen."""
543 | (x1, y1) = xxx_todo_changeme
544 | (x2, y2) = xxx_todo_changeme1
545 | for i in xrange(4):
546 | x3, y3 = points[i]
547 | x4, y4 = points[(i + 1) % 4]
548 | denom = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1)
549 | nua = (x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)
550 | nub = (x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)
551 | if denom == 0:
552 | # This covers two cases:
553 | # nua == nub == 0: Coincident
554 | # otherwise: Parallel
555 | continue
556 | ua, ub = nua / denom, nub / denom
557 | if 0 <= ua <= 1 and 0 <= ub <= 1:
558 | x = x1 + ua * (x2 - x1)
559 | y = y1 + ua * (y2 - y1)
560 | m = QPointF((x3 + x4) / 2, (y3 + y4) / 2)
561 | d = distance(m - QPointF(x2, y2))
562 | yield d, i, (x, y)
563 |
564 | # These two, along with a call to adjustSize are required for the
565 | # scroll area.
566 | def sizeHint(self):
567 | return self.minimumSizeHint()
568 |
569 | def minimumSizeHint(self):
570 | if self.bg_image:
571 | return self.scale * self.bg_image.size()
572 | return super(Canvas, self).minimumSizeHint()
573 |
574 | def wheelEvent(self, ev):
575 | if ev.orientation() == Qt.Vertical:
576 | mods = ev.modifiers()
577 | if Qt.ControlModifier == int(mods):
578 | self.zoomRequest.emit(ev.delta())
579 | else:
580 | self.scrollRequest.emit(
581 | ev.delta(), Qt.Horizontal if (
582 | Qt.ShiftModifier == int(mods)) else Qt.Vertical)
583 | else:
584 | self.scrollRequest.emit(ev.delta(), Qt.Horizontal)
585 | ev.accept()
586 |
587 | def keyPressEvent(self, ev):
588 | key = ev.key()
589 | if key == Qt.Key_Escape and self.current:
590 | print 'ESC press'
591 | self.current = None
592 | self.drawingPolygon.emit(False)
593 | self.update()
594 | elif key == Qt.Key_Return and self.canCloseShape():
595 | self.finalise()
596 |
597 | def setLastLabel(self, text):
598 | assert text
599 | self.shapes[-1].label = text
600 | return self.shapes[-1]
601 |
602 | def undoLastLine(self):
603 | assert self.shapes
604 | self.current = self.shapes.pop()
605 | self.current.setOpen()
606 | self.line.points = [self.current[-1], self.current[0]]
607 | self.drawingPolygon.emit(True)
608 |
609 | def resetAllLines(self):
610 | assert self.shapes
611 | self.current = self.shapes.pop()
612 | self.current.setOpen()
613 | self.line.points = [self.current[-1], self.current[0]]
614 | self.drawingPolygon.emit(True)
615 | self.current = None
616 | self.drawingPolygon.emit(False)
617 | self.update()
618 |
619 | def loadMaskmap(self,mask):
620 | self.mask_pixmap = mask
621 | self.repaint()
622 | def loadPixmap(self, pixmap):
623 | self.bg_image = pixmap
624 | self.shapes = []
625 | self.mask_pixmap = QImage(self.bg_image.size(), QImage.Format_ARGB32)
626 | self.mask_pixmap.fill(QColor(255,255,255,0))
627 | self.repaint()
628 |
629 | def loadShapes(self, shapes):
630 | self.shapes = list(shapes)
631 | self.shape_type = shapes[0].get_shape_type()
632 | print self.shape_type
633 | self.current = None
634 | self.repaint()
635 |
636 | def setShapeVisible(self, shape, value):
637 | self.visible[shape] = value
638 | self.repaint()
639 |
640 | def overrideCursor(self, cursor):
641 | self.restoreCursor()
642 | self._cursor = cursor
643 | QApplication.setOverrideCursor(cursor)
644 |
645 | def restoreCursor(self):
646 | QApplication.restoreOverrideCursor()
647 |
648 | def resetState(self):
649 | self.restoreCursor()
650 | self.bg_image = None
651 | self.update()
652 |
--------------------------------------------------------------------------------
/libs/colorDialog.py:
--------------------------------------------------------------------------------
1 | from PyQt4.QtGui import *
2 | from PyQt4.QtCore import *
3 |
4 | BB = QDialogButtonBox
5 |
6 |
7 | class ColorDialog(QColorDialog):
8 |
9 | def __init__(self, parent=None):
10 | super(ColorDialog, self).__init__(parent)
11 | self.setOption(QColorDialog.ShowAlphaChannel)
12 | # The Mac native dialog does not support our restore button.
13 | self.setOption(QColorDialog.DontUseNativeDialog)
14 | # Add a restore defaults button.
15 | # The default is set at invocation time, so that it
16 | # works across dialogs for different elements.
17 | self.default = None
18 | self.bb = self.layout().itemAt(1).widget()
19 | self.bb.addButton(BB.RestoreDefaults)
20 | self.bb.clicked.connect(self.checkRestore)
21 |
22 | def getColor(self, value=None, title=None, default=None):
23 | self.default = default
24 | if title:
25 | self.setWindowTitle(title)
26 | if value:
27 | self.setCurrentColor(value)
28 | return self.currentColor() if self.exec_() else None
29 |
30 | def checkRestore(self, button):
31 | if self.bb.buttonRole(button) & BB.ResetRole and self.default:
32 | self.setCurrentColor(self.default)
33 |
--------------------------------------------------------------------------------
/libs/configs.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lzx1413/LabelImgTool/264aa97dd74a29b45513d311c21975fa26db39dd/libs/configs.py
--------------------------------------------------------------------------------
/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_LAST_OPEN_DIR = 'lastOpenDir'
12 | SETTING_TASK_MODE = 'task_mode'
13 | SETTING_LABEL_FONT_SIZE = 'det/label_font_size'
14 | COLORMAP = {0: [0, 0, 0], 1: [120, 120, 120], 2: [180, 120, 120], 3: [6, 230, 230], 4: [80, 50, 50], 5: [4, 200, 3],
15 | 6: [120, 120, 80], 7: [140, 140, 140], 8: [204, 5, 255], 9: [230, 230, 230], 10: [4, 250, 7],
16 | 11: [224, 5, 255], 12: [235, 255, 7], 13: [150, 5, 61], 14: [120, 120, 70], 15: [8, 255, 51],
17 | 16: [255, 6, 82], 17: [143, 255, 140], 18: [204, 255, 4], 19: [255, 51, 7], 20: [204, 70, 3],
18 | 21: [0, 102, 200], 22: [61, 230, 250], 23: [255, 6, 51], 24: [11, 102, 255], 25: [255, 7, 71],
19 | 26: [255, 9, 224], 27: [9, 7, 230], 28: [220, 220, 220], 29: [255, 9, 92], 30: [112, 9, 255],
20 | 31: [8, 255, 214], 32: [7, 255, 224], 33: [255, 184, 6], 34: [10, 255, 71], 35: [255, 41, 10],
21 | 36: [7, 255, 255], 37: [224, 255, 8], 38: [102, 8, 255], 39: [255, 61, 6], 40: [255, 194, 7],
22 | 41: [255, 122, 8], 42: [0, 255, 20], 43: [255, 8, 41], 44: [255, 5, 153], 45: [6, 51, 255],
23 | 46: [235, 12, 255], 47: [160, 150, 20], 48: [0, 163, 255], 49: [140, 140, 140], 50: [250, 10, 15],
24 | 51: [20, 255, 0], 52: [31, 255, 0], 53: [255, 31, 0], 54: [255, 224, 0], 55: [153, 255, 0],
25 | 56: [0, 0, 255], 57: [255, 71, 0], 58: [0, 235, 255], 59: [0, 173, 255], 60: [31, 0, 255],
26 | 61: [11, 200, 200], 62: [255, 82, 0], 63: [0, 255, 245], 64: [0, 61, 255], 65: [0, 255, 112],
27 | 66: [0, 255, 133], 67: [255, 0, 0], 68: [255, 163, 0], 69: [255, 102, 0], 70: [194, 255, 0],
28 | 71: [0, 143, 255], 72: [51, 255, 0], 73: [0, 82, 255], 74: [0, 255, 41], 75: [0, 255, 173],
29 | 76: [10, 0, 255], 77: [173, 255, 0], 78: [0, 255, 153], 79: [255, 92, 0], 80: [255, 0, 255],
30 | 81: [255, 0, 245], 82: [255, 0, 102], 83: [255, 173, 0], 84: [255, 0, 20], 85: [255, 184, 184],
31 | 86: [0, 31, 255], 87: [0, 255, 61], 88: [0, 71, 255], 89: [255, 0, 204], 90: [0, 255, 194],
32 | 91: [0, 255, 82], 92: [0, 10, 255], 93: [0, 112, 255], 94: [51, 0, 255], 95: [0, 194, 255],
33 | 96: [0, 122, 255], 97: [0, 255, 163], 98: [255, 153, 0], 99: [0, 255, 10], 100: [255, 112, 0],
34 | 101: [143, 255, 0], 102: [82, 0, 255], 103: [163, 255, 0], 104: [255, 235, 0], 105: [8, 184, 170],
35 | 106: [133, 0, 255], 107: [0, 255, 92], 108: [184, 0, 255], 109: [255, 0, 31], 110: [0, 184, 255],
36 | 111: [0, 214, 255], 112: [255, 0, 112], 113: [92, 255, 0], 114: [0, 224, 255], 115: [112, 224, 255],
37 | 116: [70, 184, 160], 117: [163, 0, 255], 118: [153, 0, 255], 119: [71, 255, 0], 120: [255, 0, 163],
38 | 121: [255, 204, 0], 122: [255, 0, 143], 123: [0, 255, 235], 124: [133, 255, 0], 125: [255, 0, 235],
39 | 126: [245, 0, 255], 127: [255, 0, 122], 128: [255, 245, 0], 129: [10, 190, 212], 130: [214, 255, 0],
40 | 131: [0, 204, 255], 132: [20, 0, 255], 133: [255, 255, 0], 134: [0, 153, 255], 135: [0, 41, 255],
41 | 136: [0, 255, 204], 137: [41, 0, 255], 138: [41, 255, 0], 139: [173, 0, 255], 140: [0, 245, 255],
42 | 141: [71, 0, 255], 142: [122, 0, 255], 143: [0, 255, 184], 144: [0, 92, 255], 145: [184, 255, 0],
43 | 146: [0, 133, 255], 147: [255, 214, 0], 148: [25, 194, 194], 149: [102, 255, 0], 150: [92, 0, 255],
44 | 255: [255, 255, 255]}
--------------------------------------------------------------------------------
/libs/labelDialog.py:
--------------------------------------------------------------------------------
1 | from PyQt4.QtGui import *
2 | from PyQt4.QtCore import *
3 |
4 | from lib import newIcon, labelValidator
5 |
6 | BB = QDialogButtonBox
7 |
8 |
9 | class SubListWidget(QDialog):
10 |
11 | def __init__(self, parent=None, listItem=None):
12 | self.setWindowTitle('select a label')
13 | self.select_text = None
14 | super(SubListWidget, self).__init__(parent)
15 | self.listwidget = QListWidget(self)
16 | layout = QVBoxLayout()
17 | if listItem is not None and len(listItem) > 0:
18 | for item in listItem:
19 | self.listwidget.addItem(item)
20 | layout.addWidget(self.listwidget)
21 | self.setLayout(layout)
22 | self.listwidget.itemDoubleClicked.connect(self.listItemDoubleClicked)
23 | self.move(QCursor.pos())
24 |
25 | def get_select_item(self):
26 | return self.select_text if self.exec_() else None
27 |
28 | def listItemDoubleClicked(self, tQListWidgetItem):
29 | text = tQListWidgetItem.text().trimmed()
30 | self.select_text = text
31 | print text
32 | if text is not None:
33 | self.accept()
34 |
35 |
36 | class LabelDialog(QDialog):
37 |
38 | def __init__(
39 | self,
40 | text="Enter object label",
41 | parent=None,
42 | listItem=None,
43 | sub_label_items=None,
44 | label_fre_dic=None):
45 | super(LabelDialog, self).__init__(parent)
46 | self.edit = QLineEdit()
47 | self.edit.setText(text)
48 | self.edit.setValidator(labelValidator())
49 | self.edit.editingFinished.connect(self.postProcess)
50 | layout = QVBoxLayout()
51 | self.label_fre_dic = label_fre_dic
52 | layout.addWidget(self.edit)
53 | self.buttonBox = bb = BB(BB.Ok | BB.Cancel, Qt.Horizontal, self)
54 | bb.button(BB.Ok).setIcon(newIcon('done'))
55 | bb.button(BB.Cancel).setIcon(newIcon('undo'))
56 | bb.accepted.connect(self.validate)
57 | bb.rejected.connect(self.reject)
58 | layout.addWidget(bb)
59 | if sub_label_items:
60 | self.sub_labels_dic = sub_label_items
61 | self.sublistwidget = SubListWidget()
62 | if self.sub_labels_dic.keys() is not None and len(self.sub_labels_dic.keys()) > 0:
63 | self.listWidget = QListWidget(self)
64 | keys = sorted(self.sub_labels_dic.keys())
65 | for item in keys:
66 | self.listWidget.addItem(item)
67 | self.listWidget.itemClicked.connect(self.listItemClicked)
68 | layout.addWidget(self.listWidget)
69 | elif listItem:
70 | sorted_labels = []
71 | if self.label_fre_dic:
72 | print label_fre_dic
73 | sorted_labels = sorted(
74 | self.label_fre_dic,
75 | key=self.label_fre_dic.get,
76 | reverse=True)
77 | if listItem is not None and len(listItem) > 0:
78 | self.listWidget = QListWidget(self)
79 | for item in sorted_labels:
80 | self.listWidget.addItem(item)
81 | self.listWidget.itemDoubleClicked.connect(
82 | self.listItemDoubleClicked)
83 | layout.addWidget(self.listWidget)
84 | self.setLayout(layout)
85 |
86 | def validate(self):
87 | if self.edit.text().trimmed():
88 | self.accept()
89 |
90 | def postProcess(self):
91 | self.edit.setText(self.edit.text().trimmed())
92 |
93 | def popUp(self, text='', move=True):
94 | self.edit.setText(text)
95 | self.edit.setSelection(0, len(text))
96 | self.edit.setFocus(Qt.PopupFocusReason)
97 | if move:
98 | self.move(QCursor.pos())
99 | return self.edit.text() if self.exec_() else None
100 |
101 | def sublistwidgetclicked(self, tQListWidgetItem):
102 | print tQListWidgetItem.text().trimmed()
103 | print 'doubleclicked'
104 |
105 | def listItemDoubleClicked(self, tQListWidgetItem):
106 | text = tQListWidgetItem.text().trimmed()
107 | self.edit.setText(text)
108 | self.validate()
109 |
110 | def listItemClicked(self, tQListWidgetItem):
111 | self.sublistwidget.close()
112 | labels = self.sub_labels_dic[str(tQListWidgetItem.text().trimmed())]
113 | label_dic = {}
114 | for label in labels:
115 | if label in self.label_fre_dic:
116 | label_dic[label] = self.label_fre_dic[label]
117 | else:
118 | label_dic[label] = 0
119 | sorted_labels = sorted(label_dic, key=label_dic.get, reverse=True)
120 | self.sublistwidget = SubListWidget(listItem=sorted_labels, parent=self)
121 | self.sublistwidget.show()
122 | self.edit.setText(self.sublistwidget.get_select_item())
123 | self.validate()
124 |
--------------------------------------------------------------------------------
/libs/labelFile.py:
--------------------------------------------------------------------------------
1 | import os.path
2 | import sys
3 | from pascalVocIO import PascalVocWriter
4 | from base64 import b64encode, b64decode
5 |
6 |
7 | class LabelFileError(Exception):
8 | pass
9 |
10 |
11 | class LabelFile(object):
12 | # It might be changed as window creates
13 | suffix = '.lif'
14 |
15 | def __init__(self, filename=None):
16 | self.shapes = ()
17 | self.imagePath = None
18 | self.imageData = None
19 | if filename is not None:
20 | self.load(filename)
21 |
22 | def savePascalVocFormat(
23 | self,
24 | savefilename,
25 | image_size,
26 | shapes,
27 | imagePath=None,
28 | databaseSrc=None,
29 | shape_type_='RECT'):
30 | imgFolderPath = os.path.dirname(imagePath)
31 | imgFolderName = os.path.split(imgFolderPath)[-1]
32 | imgFileName = os.path.basename(imagePath)
33 | imgFileNameWithoutExt = os.path.splitext(imgFileName)[0]
34 |
35 | #img = cv2.imread(imagePath)
36 | writer = PascalVocWriter(
37 | imgFolderName,
38 | imgFileNameWithoutExt,
39 | image_size,
40 | localImgPath=imagePath,
41 | shape_type=shape_type_)
42 | bSave = False
43 | for shape in shapes:
44 | points = shape['points']
45 | label = shape['label']
46 | if shape['shape_type'] == 0:
47 | print 'add rects'
48 | bndbox = LabelFile.convertPoints2BndBox(points)
49 | writer.addBndBox(
50 | bndbox[0],
51 | bndbox[1],
52 | bndbox[2],
53 | bndbox[3],
54 | label)
55 | if shape['shape_type'] == 1:
56 | print 'add polygons'
57 | writer.addPolygon(points, label,instance_id=shape['instance_id'])
58 |
59 | bSave = True
60 |
61 | if bSave:
62 | writer.save(targetFile=savefilename)
63 | return
64 |
65 | @staticmethod
66 | def isLabelFile(filename):
67 | fileSuffix = os.path.splitext(filename)[1].lower()
68 | return fileSuffix == LabelFile.suffix
69 |
70 | @staticmethod
71 | def convertPoints2BndBox(points):
72 | xmin = sys.maxsize
73 | ymin = sys.maxsize
74 | xmax = -sys.maxsize
75 | ymax = -sys.maxsize
76 | for p in points:
77 | x = p[0]
78 | y = p[1]
79 | xmin = min(x, xmin)
80 | ymin = min(y, ymin)
81 | xmax = max(x, xmax)
82 | ymax = max(y, ymax)
83 |
84 | # Martin Kersner, 2015/11/12
85 | # 0-valued coordinates of BB caused an error while
86 | # training faster-rcnn object detector.
87 | if (xmin < 1):
88 | xmin = 1
89 |
90 | if (ymin < 1):
91 | ymin = 1
92 |
93 | return (int(xmin), int(ymin), int(xmax), int(ymax))
94 |
--------------------------------------------------------------------------------
/libs/lib.py:
--------------------------------------------------------------------------------
1 | from math import sqrt
2 |
3 | from PyQt4.QtGui import *
4 | from PyQt4.QtCore import *
5 |
6 |
7 | def newIcon(icon):
8 | return QIcon(':/' + icon)
9 |
10 |
11 | def newButton(text, icon=None, slot=None):
12 | b = QPushButton(text)
13 | if icon is not None:
14 | b.setIcon(newIcon(icon))
15 | if slot is not None:
16 | b.clicked.connect(slot)
17 | return b
18 |
19 |
20 | def newAction(parent, text, slot=None, shortcut=None, icon=None,
21 | tip=None, checkable=False, enabled=True):
22 | """Create a new action and assign callbacks, shortcuts, etc."""
23 | a = QAction(text, parent)
24 | if icon is not None:
25 | a.setIcon(newIcon(icon))
26 | if shortcut is not None:
27 | if isinstance(shortcut, (list, tuple)):
28 | a.setShortcuts(shortcut)
29 | else:
30 | a.setShortcut(shortcut)
31 | if tip is not None:
32 | a.setToolTip(tip)
33 | a.setStatusTip(tip)
34 | if slot is not None:
35 | a.triggered.connect(slot)
36 | if checkable:
37 | a.setCheckable(True)
38 | a.setEnabled(enabled)
39 | return a
40 |
41 |
42 | def addActions(widget, actions):
43 | for action in actions:
44 | if action is None:
45 | widget.addSeparator()
46 | elif isinstance(action, QMenu):
47 | widget.addMenu(action)
48 | else:
49 | widget.addAction(action)
50 |
51 |
52 | def labelValidator():
53 | return QRegExpValidator(QRegExp(r'^[^ \t].+'), None)
54 |
55 |
56 | class struct(object):
57 |
58 | def __init__(self, **kwargs):
59 | self.__dict__.update(kwargs)
60 |
61 |
62 | def distance(p):
63 | return sqrt(p.x() * p.x() + p.y() * p.y())
64 |
65 |
66 | def fmtShortcut(text):
67 | mod, key = text.split('+', 1)
68 | return '%s+%s' % (mod, key)
69 |
--------------------------------------------------------------------------------
/libs/pascalVocIO.py:
--------------------------------------------------------------------------------
1 | import sys
2 | from xml.etree import ElementTree
3 | from xml.etree.ElementTree import Element, SubElement
4 | from xml.dom import minidom
5 | from lxml import etree
6 |
7 |
8 | class PascalVocWriter:
9 |
10 | def __init__(
11 | self,
12 | foldername,
13 | filename,
14 | imgSize,
15 | databaseSrc='Unknown',
16 | localImgPath=None,
17 | shape_type=None):
18 | self.foldername = foldername
19 | self.filename = filename
20 | self.databaseSrc = databaseSrc
21 | self.imgSize = imgSize
22 | self.boxlist = []
23 | self.localImgPath = localImgPath
24 | self.shape_type = shape_type
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)
33 |
34 | def genXML(self):
35 | """
36 | Return XML root
37 | """
38 | # Check conditions
39 | '''
40 | if self.filename is None or \
41 | self.foldername is None or \
42 | self.imgSize is None or \
43 | len(self.boxlist) <= 0:
44 | '''
45 | if self.filename is None or \
46 | len(self.boxlist) <= 0:
47 | return None
48 |
49 | top = Element('annotation')
50 | folder = SubElement(top, 'folder')
51 | folder.text = self.foldername
52 |
53 | filename = SubElement(top, 'filename')
54 | filename.text = self.filename
55 |
56 | localImgPath = SubElement(top, 'path')
57 | self.localImgPath = self.localImgPath.split('/')[-1]
58 | localImgPath.text = self.localImgPath
59 |
60 | source = SubElement(top, 'source')
61 | database = SubElement(source, 'database')
62 | database.text = self.databaseSrc
63 |
64 | if self.imgSize:
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.imgSize[1])
70 | height.text = str(self.imgSize[0])
71 | if len(self.imgSize) == 3:
72 | depth.text = str(self.imgSize[2])
73 | else:
74 | depth.text = '1'
75 |
76 | segmented = SubElement(top, 'segmented')
77 | segmented.text = '0'
78 | shape_type = SubElement(top, 'shape_type')
79 | shape_type.text = self.shape_type
80 | return top
81 |
82 | def addBndBox(self, xmin, ymin, xmax, ymax, name):
83 | bndbox = {'xmin': xmin, 'ymin': ymin, 'xmax': xmax, 'ymax': ymax}
84 | bndbox['name'] = name
85 | self.boxlist.append(bndbox)
86 |
87 | def addPolygon(self, shape, name,instance_id):
88 | polygon = {}
89 | i = 0
90 | for point in shape:
91 | polygon[i] = point
92 | i = i + 1
93 | polygon['name'] = name
94 | polygon['point_num'] = str(len(shape))
95 | polygon['instance_id'] = instance_id
96 | self.boxlist.append(polygon)
97 |
98 | def appendObjects(self, top):
99 | for each_object in self.boxlist:
100 | print(each_object)
101 | object_item = SubElement(top, 'object')
102 | if each_object['name']:
103 | name = SubElement(object_item, 'name')
104 | name.text = unicode(each_object['name'])
105 | pose = SubElement(object_item, 'pose')
106 | pose.text = "Unspecified"
107 | if 'instance_id' in each_object.keys():
108 | instance_id = SubElement(object_item,'instance_id')
109 | instance_id.text = str(each_object['instance_id'])
110 | truncated = SubElement(object_item, 'truncated')
111 | truncated.text = "0"
112 | difficult = SubElement(object_item, 'difficult')
113 | difficult.text = "0"
114 | if self.shape_type == 'RECT':
115 | bndbox = SubElement(object_item, 'bndbox')
116 | xmin = SubElement(bndbox, 'xmin')
117 | xmin.text = str(each_object['xmin'])
118 | ymin = SubElement(bndbox, 'ymin')
119 | ymin.text = str(each_object['ymin'])
120 | xmax = SubElement(bndbox, 'xmax')
121 | xmax.text = str(each_object['xmax'])
122 | ymax = SubElement(bndbox, 'ymax')
123 | ymax.text = str(each_object['ymax'])
124 | elif self.shape_type == 'POLYGON':
125 | polygon = SubElement(object_item, 'polygon')
126 | for i in xrange(int(each_object['point_num'])):
127 | point = SubElement(polygon, 'point' + str(i))
128 | point.text = str(
129 | int(each_object[i][0])) + ',' + str(int(each_object[i][1]))
130 | print i, point.text
131 |
132 | def save(self, targetFile=None):
133 | root = self.genXML()
134 | self.appendObjects(root)
135 | out_file = None
136 | if targetFile is None:
137 | out_file = open(self.filename + '.xml', 'w')
138 | else:
139 | out_file = open(targetFile, 'w')
140 | out_file.write(self.prettify(root))
141 | # out_file.write(root)
142 | out_file.close()
143 |
144 |
145 | class PascalVocReader:
146 |
147 | def __init__(self, filepath):
148 | # shapes type:
149 | ## [labbel, [(x1,y1), (x2,y2), (x3,y3), (x4,y4)], color, color]
150 | self.shapes = []
151 | self.filepath = filepath
152 | self.shape_type = None
153 | self.image_size = []
154 | self.parseXML()
155 |
156 | def getShapes(self):
157 | return self.shapes
158 |
159 | def getShapeType(self):
160 | return self.shape_type
161 |
162 | def addPolygonShape(self,label,points,instance_id = 0):
163 | points = [(point[0],point[1]) for point in points]
164 | self.shapes.append((label,points,None,None,1,instance_id))
165 | def get_img_size(self):
166 | if self.image_size:
167 | return self.image_size
168 | def addShape(self, label, rect,instance_id = 0):
169 | xmin = rect[0]
170 | ymin = rect[1]
171 | xmax = rect[2]
172 | ymax = rect[3]
173 | points = [(xmin, ymin), (xmin, ymax), (xmax, ymax), (xmax, ymin)]
174 | self.shapes.append((label, points, None, None, 0,instance_id))
175 |
176 | def parseXML(self):
177 | assert self.filepath.endswith('.xml'), "Unsupport file format"
178 | parser = etree.XMLParser(encoding='utf-8')
179 | xmltree = ElementTree.parse(self.filepath,parser=parser).getroot()
180 | filename = xmltree.find('filename').text
181 | if xmltree.find('shape_type') is not None:
182 | self.shape_type = xmltree.find('shape_type').text
183 | else:
184 | self.shape_type = 'RECT'
185 | self.image_size.append(int(xmltree.find('size').find('width').text))
186 | self.image_size.append(int(xmltree.find('size').find('height').text))
187 | if self.shape_type == 'RECT':
188 | for object_iter in xmltree.findall('object'):
189 | rects = []
190 | bndbox = object_iter.find("bndbox")
191 | rects.append([int(it.text) for it in bndbox])
192 | label = object_iter.find('name').text
193 | for rect in rects:
194 | self.addShape(label, rect)
195 | return True
196 | elif self.shape_type == 'POLYGON':
197 | for object_iter in xmltree.findall('object'):
198 | points = []
199 | polygons = object_iter.find("polygon")
200 | label = object_iter.find('name').text
201 | for point in polygons:
202 | point = point.text.split(',')
203 | point = [int(dot) for dot in point]
204 | points.append(point)
205 | if object_iter.find('instance_id') is not None:
206 | instance_id = int(object_iter.find('instance_id').text)
207 | self.addPolygonShape(label, points,instance_id)
208 | else:
209 | print 'unsupportable shape type'
210 |
211 |
212 | # tempParseReader = PascalVocReader('test.xml')
213 | # print tempParseReader.getShapes()
214 | """
215 | # Test
216 | tmp = PascalVocWriter('temp','test', (10,20,3))
217 | tmp.addBndBox(10,10,20,30,'chair')
218 | tmp.addBndBox(1,1,600,600,'car')
219 | tmp.save()
220 | """
221 |
--------------------------------------------------------------------------------
/libs/remoteDialog.py:
--------------------------------------------------------------------------------
1 | from PyQt4 import QtGui, QtCore
2 | import socket
3 | import re
4 |
5 |
6 | class SetRemoteDialog(QtGui.QDialog):
7 | remote_mode = True
8 | remote_url = ""
9 | dowload_thead_num = 4
10 | server_image_list = None
11 |
12 | def __init__(self, parent=None):
13 | QtGui.QDialog.__init__(self, parent)
14 | self.resize(320, 100)
15 | self.setWindowTitle('set remote db')
16 | self.remote_cb = QtGui.QCheckBox("use remote database")
17 | if self.__class__.remote_mode:
18 | self.remote_cb.toggle()
19 | self.remote_cb.stateChanged.connect(self.set_remote_mode)
20 | grid = QtGui.QGridLayout()
21 | grid.addWidget(self.remote_cb, 0, 0, 1, 1)
22 | grid.addWidget(
23 | QtGui.QLabel(
24 | u'dowload image thread num',
25 | parent=self),
26 | 1,
27 | 0,
28 | 1,
29 | 1)
30 | self.thread_num = QtGui.QSpinBox()
31 | self.thread_num.setRange(1, 10)
32 | self.thread_num.setValue(self.__class__.dowload_thead_num)
33 | self.thread_num.valueChanged.connect(self.set_thread_num)
34 | grid.addWidget(self.thread_num, 1, 1, 1, 1)
35 | grid.addWidget(
36 | QtGui.QLabel(
37 | u'remote db url[123.57.438.245/]',
38 | parent=self),
39 | 2,
40 | 0,
41 | 1,
42 | 1)
43 | self.remote_url_line = QtGui.QLineEdit(parent=self)
44 | if self.__class__.remote_url:
45 | self.remote_url_line.setText(self.__class__.remote_url)
46 | grid.addWidget(self.remote_url_line, 2, 1, 1, 1)
47 | grid.addWidget(
48 | QtGui.QLabel(
49 | u'remote image list',
50 | parent=self),
51 | 3,
52 | 0,
53 | 1,
54 | 1)
55 | self.server_image_list = QtGui.QLineEdit(parent=self)
56 | if self.__class__.server_image_list:
57 | self.server_image_list.setText(self.__class__.server_image_list)
58 | grid.addWidget(self.server_image_list, 3, 1, 1, 1)
59 | buttonBox = QtGui.QDialogButtonBox(parent=self)
60 | buttonBox.setOrientation(QtCore.Qt.Horizontal)
61 | buttonBox.setStandardButtons(
62 | QtGui.QDialogButtonBox.Cancel | QtGui.QDialogButtonBox.Ok)
63 | buttonBox.accepted.connect(self.accept)
64 | buttonBox.rejected.connect(self.reject)
65 | layout = QtGui.QVBoxLayout()
66 | layout.addLayout(grid)
67 | spacerItem = QtGui.QSpacerItem(
68 | 20, 48, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
69 | layout.addItem(spacerItem)
70 | layout.addWidget(buttonBox)
71 | self.setLayout(layout)
72 |
73 | def test_remote_url(self, url, port):
74 | try:
75 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
76 | sock.settimeout(5)
77 | sock.connect((url, port))
78 | return True
79 | except socket.error as e:
80 | return False
81 | finally:
82 | sock.close()
83 |
84 | def set_remote_mode(self, state):
85 | if state == QtCore.Qt.Checked:
86 | self.__class__.remote_mode = True
87 | else:
88 | self.__class__.remote_mode = False
89 |
90 | def set_thread_num(self, num):
91 | self.__class__.dowload_thead_num = num
92 |
93 | def get_thread_num(self):
94 | return self.__class__.dowload_thead_num
95 |
96 | def is_in_remote_mode(self):
97 | return self.__class__.remote_mode
98 |
99 | def get_server_image_list(self):
100 | if self.server_image_list is not None:
101 | return self.server_image_list.text()
102 | else:
103 | QtGui.QMessageBox.about(
104 | self,
105 | "server image list!",
106 | "the server image list is None!")
107 |
108 | def get_remote_url(self):
109 | origin_url = self.remote_url_line.text()
110 | if re.match(r'\w.+$', origin_url):
111 | if self.test_remote_url(origin_url.split('/')[0], 80):
112 | self.__class__.remote_url = origin_url
113 | return self.__class__.remote_url
114 | else:
115 | QtGui.QMessageBox.about(
116 | self, "server connect error!", "can not connect the server")
117 |
118 | else:
119 | QtGui.QMessageBox.about(
120 | self,
121 | "url format error!",
122 | "the url is not in the correct format \n such as 1.1.1.1/sf/")
123 |
--------------------------------------------------------------------------------
/libs/saveMaskImage.py:
--------------------------------------------------------------------------------
1 | '''
2 | this file define a class to save the result of the mask of parse
3 | the mask will be save as a gray image using different color to represent different
4 | object
5 | '''
6 | import numpy as np
7 | import logging
8 | from PIL import Image,ImageDraw
9 |
10 |
11 | class label_mask_writer:
12 |
13 | def __init__(
14 | self,
15 | label_num_dict,
16 | save_file_path,
17 | image_height,
18 | image_width):
19 | self.label_num_dict = label_num_dict
20 |
21 | self.save_file_path = save_file_path
22 | self.image_height = image_height
23 | self.image_width = image_width
24 | self.labels = []
25 | self.shapes = []
26 |
27 | def save_mask_image(self, shapes):
28 | for shape in shapes:
29 | self.add_mask_label(shape['label'])
30 | self.add_shape_points(shape['points'])
31 | image = self.get_mask_image()
32 | image.save(self.save_file_path, 'PNG')
33 |
34 | def add_mask_label(self, label):
35 | self.labels.append(label)
36 |
37 | def add_shape_points(self, shape_points):
38 | self.shapes.append(shape_points)
39 |
40 | def get_mask_image(self):
41 | '''
42 | convert label and shapes to gray image mask
43 | :return: gray image mask
44 | '''
45 | assert len(self.labels) == len(self.shapes)
46 | mask_bg = Image.new('L',(self.image_width,self.image_height))
47 | mask_draw = ImageDraw.Draw(mask_bg)
48 | if self.labels:
49 | index = 0
50 | for label in self.labels:
51 | color = self.label_num_dict[label]
52 | vertex = self.shapes[index]
53 | mask_draw.polygon(vertex,color)
54 | index += 1
55 | else:
56 | logging.error('there are no shapes to save !')
57 | return mask_bg
58 |
--------------------------------------------------------------------------------
/libs/settingDialog.py:
--------------------------------------------------------------------------------
1 | from PyQt4 import QtGui, QtCore
2 | import socket
3 | import re
4 |
5 |
6 | class SettingDialog(QtGui.QDialog):
7 | enable_color_map = True
8 | label_font_size = 10
9 | task_mode = 0 #0=det, 1=seg, 2=cls
10 | instance_seg_flag = False
11 |
12 |
13 | def __init__(self, parent,config):
14 | QtGui.QDialog.__init__(self, parent)
15 | self.resize(320, 240)
16 | self.__class__.task_mode = config['task_mode']
17 | self.__class__.label_font_size = config['label_font_size']
18 | self.init_UI()
19 | def createModeGroup(self):
20 | '''
21 | set the trask mode setting group
22 | :return: mode group
23 | '''
24 | self.modegroupBox = QtGui.QGroupBox("& Task Mode")
25 | self.modegroupBox.setCheckable(True)
26 | self.modegroupBox.setChecked(True)
27 | self.CLS_mode_rb = QtGui.QRadioButton("CLS Mode")
28 | self.CLS_mode_rb.clicked.connect(self.CLS_model_selected)
29 | self.DET_mode_rb = QtGui.QRadioButton("DET Mode")
30 | self.DET_mode_rb.clicked.connect(self.DET_model_selected)
31 | self.SEG_mode_rb = QtGui.QRadioButton("SEG Mode")
32 | self.SEG_mode_rb.clicked.connect(self.SEG_model_selected)
33 | self.BRU_mode_rb = QtGui.QRadioButton("BRU Mode")
34 | self.BRU_mode_rb.clicked.connect(self.BRU_model_selected)
35 |
36 | vbox = QtGui.QVBoxLayout()
37 | vbox.addWidget(self.CLS_mode_rb)
38 | vbox.addWidget(self.DET_mode_rb)
39 | vbox.addWidget(self.SEG_mode_rb)
40 | vbox.addWidget(self.BRU_mode_rb)
41 | vbox.addStretch(True)
42 | self.modegroupBox.setLayout(vbox)
43 | return self.modegroupBox
44 |
45 | def createDEToptGroup(self):
46 | self.detgroupBox = QtGui.QGroupBox("& DET options")
47 | self.enable_show_label_cb = QtGui.QCheckBox('enable show label name')
48 |
49 |
50 | self.label_font_size_sl = QtGui.QSlider(QtCore.Qt.Horizontal)
51 | self.label_font_size_sl.setRange(5,50)
52 | self.label_font_size_sp = QtGui.QSpinBox()
53 | self.label_font_size_sp.setRange(5,50)
54 | QtCore.QObject.connect(self.label_font_size_sl, QtCore.SIGNAL("valueChanged(int)"),
55 |
56 | self.label_font_size_sp, QtCore.SLOT("setValue(int)"))
57 | self.label_font_size_sl.valueChanged.connect(self.change_label_font_size)
58 | self.label_font_size_sl.setValue(self.__class__.label_font_size)
59 | vbox = QtGui.QVBoxLayout()
60 | vbox.addWidget(self.enable_show_label_cb)
61 | vbox.addWidget(QtGui.QLabel('label font size'))
62 | vbox.addWidget(self.label_font_size_sl)
63 | vbox.addWidget(self.label_font_size_sp)
64 | vbox.addStretch()
65 | self.detgroupBox.setLayout(vbox)
66 | return self.detgroupBox
67 |
68 | def createCLSoptGroup(self):
69 | self.clsgroupBox = QtGui.QGroupBox("& CLS options")
70 | #self.single_label_rb = QtGui.QRadioButton("single label")
71 | #self.multi_label_rb = QtGui.QRadioButton("multi label")
72 | vbox = QtGui.QVBoxLayout()
73 | #vbox.addWidget(self.single_label_rb)
74 | #vbox.addWidget(self.multi_label_rb)
75 | vbox.addStretch(True)
76 | self.clsgroupBox.setLayout(vbox)
77 | return self.clsgroupBox
78 | def createBRUoptGroup(self):
79 | self.brugroupBox = QtGui.QGroupBox("& Brush options")
80 | #self.single_label_rb = QtGui.QRadioButton("single label")
81 | #self.multi_label_rb = QtGui.QRadioButton("multi label")
82 | vbox = QtGui.QVBoxLayout()
83 | #vbox.addWidget(self.single_label_rb)
84 | #vbox.addWidget(self.multi_label_rb)
85 | vbox.addStretch(True)
86 | self.brugroupBox.setLayout(vbox)
87 | return self.brugroupBox
88 |
89 | def createSEGoptGroup(self):
90 | self.seggroupBox = QtGui.QGroupBox("& SEG options")
91 | self.enable_color_map_cb = QtGui.QCheckBox('enable color map')
92 | self.instance_seg_label_cb = QtGui.QCheckBox('set instance seg')
93 | self.instance_seg_label_cb.setChecked(self.__class__.instance_seg_flag)
94 | self.instance_seg_label_cb.stateChanged.connect(self.change_instance_seg_label)
95 | if self.__class__.enable_color_map:
96 | self.enable_color_map_cb.toggle()
97 | self.enable_color_map_cb.stateChanged.connect(
98 | self.change_color_enable_state)
99 | if self.__class__.enable_color_map:
100 | self.enable_color_map_cb.setChecked(True)
101 | vbox = QtGui.QVBoxLayout()
102 | vbox.addWidget(self.enable_color_map_cb)
103 | vbox.addWidget(self.instance_seg_label_cb)
104 | vbox.addStretch(True)
105 | self.seggroupBox.setLayout(vbox)
106 | return self.seggroupBox
107 |
108 |
109 | def init_UI(self):
110 | main_v_layout = QtGui.QVBoxLayout()
111 |
112 | grid = QtGui.QGridLayout()
113 | grid.addWidget(self.createModeGroup(),0,0)
114 | grid.addWidget(self.createDEToptGroup(),1,0)
115 | grid.addWidget(self.createCLSoptGroup(),2,0)
116 | grid.addWidget(self.createSEGoptGroup(),3,0)
117 | grid.addWidget(self.createBRUoptGroup(),4,0)
118 | if self.__class__.task_mode == 0:
119 | self.DET_mode_rb.setChecked(True)
120 | self.DET_model_selected()
121 | elif self.__class__.task_mode == 1:
122 | self.SEG_mode_rb.setChecked(True)
123 | self.SEG_model_selected()
124 | elif self.__class__.task_mode == 2:
125 | self.CLS_mode_rb.setChecked(True)
126 | self.CLS_model_selected()
127 | elif self.__class__.task_mode == 3:
128 | self.BRU_mode_rb.setChecked(True)
129 | self.BRU_model_selected()
130 | buttonBox = QtGui.QDialogButtonBox(parent=self)
131 | buttonBox.setOrientation(QtCore.Qt.Horizontal)
132 | buttonBox.setStandardButtons(
133 | QtGui.QDialogButtonBox.Cancel | QtGui.QDialogButtonBox.Ok)
134 | buttonBox.accepted.connect(self.accept)
135 | buttonBox.rejected.connect(self.reject)
136 | main_v_layout.addLayout(grid)
137 | spacerItem = QtGui.QSpacerItem(
138 | 20, 48, QtGui.QSizePolicy.Minimum, QtGui.QSizePolicy.Expanding)
139 | main_v_layout.addItem(spacerItem)
140 | main_v_layout.addWidget(buttonBox)
141 | self.setLayout(main_v_layout)
142 |
143 | def CLS_model_selected(self):
144 | self.__class__.task_mode = 2
145 | self.clsgroupBox.setDisabled(False)
146 | self.detgroupBox.setDisabled(True)
147 | self.seggroupBox.setDisabled(True)
148 | self.brugroupBox.setDisabled(True)
149 |
150 | def DET_model_selected(self):
151 | self.__class__.task_mode = 0
152 | self.detgroupBox.setDisabled(False)
153 | self.clsgroupBox.setDisabled(True)
154 | self.seggroupBox.setDisabled(True)
155 | self.brugroupBox.setDisabled(True)
156 |
157 | def SEG_model_selected(self):
158 | self.__class__.task_mode = 1
159 | self.seggroupBox.setDisabled(False)
160 | self.detgroupBox.setDisabled(True)
161 | self.clsgroupBox.setDisabled(True)
162 | self.brugroupBox.setDisabled(True)
163 |
164 | def BRU_model_selected(self):
165 | self.__class__.task_mode = 3
166 | self.brugroupBox.setDisabled(False)
167 | self.seggroupBox.setDisabled(True)
168 | self.detgroupBox.setDisabled(True)
169 | self.clsgroupBox.setDisabled(True)
170 |
171 | def change_color_enable_state(self, state):
172 | if state == QtCore.Qt.Checked:
173 | self.__class__.enable_color_map = True
174 | else:
175 | self.__class__.enable_color_map = False
176 | def change_instance_seg_label(self,state):
177 | if state == QtCore.Qt.Checked:
178 | self.__class__.instance_seg_flag = True
179 | else:
180 | self.__class__.instance_seg_flag = False
181 | def change_label_font_size(self,value):
182 | self.__class__.label_font_size = value
183 |
184 | def get_color_map_state(self):
185 | return self.__class__.enable_color_map
186 |
187 | def get_setting_state(self):
188 | if self.__class__.task_mode == 0:
189 | return {'mode': 0,'enable_color_map':self.__class__.enable_color_map,'label_font_size': self.__class__.label_font_size}
190 |
191 | elif self.__class__.task_mode == 1:
192 | return {'mode': 1,'enable_color_map':self.__class__.enable_color_map,'instance_seg_flag':self.instance_seg_flag}
193 |
194 | elif self.__class__.task_mode == 2:
195 | return {'mode': 2}
196 | elif self.__class__.task_mode == 3:
197 | return {'mode': 3}
198 |
199 |
--------------------------------------------------------------------------------
/libs/shape.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | # -*- coding: utf-8 -*-
3 |
4 | from PyQt4.QtGui import *
5 | from PyQt4.QtCore import *
6 |
7 | from lib import distance
8 |
9 | DEFAULT_LINE_COLOR = QColor(0, 255, 0, 128)
10 | DEFAULT_FILL_COLOR = QColor(255, 0, 0, 10)
11 | DEFAULT_SELECT_LINE_COLOR = QColor(255, 255, 255)
12 | DEFAULT_SELECT_FILL_COLOR = QColor(0, 128, 255, 155)
13 | DEFAULT_VERTEX_FILL_COLOR = QColor(0, 255, 0, 255)
14 | DEFAULT_HVERTEX_FILL_COLOR = QColor(255, 0, 0)
15 |
16 |
17 | class Shape(object):
18 | P_SQUARE, P_ROUND = range(2)
19 | RECT_SHAPE, POLYGON_SHAPE = range(2)
20 | MOVE_VERTEX, NEAR_VERTEX = range(2)
21 |
22 | # The following class variables influence the drawing
23 | # of _all_ shape objects.
24 | line_color = DEFAULT_LINE_COLOR
25 | fill_color = DEFAULT_FILL_COLOR
26 | select_line_color = DEFAULT_SELECT_LINE_COLOR
27 | select_fill_color = DEFAULT_SELECT_FILL_COLOR
28 | vertex_fill_color = DEFAULT_VERTEX_FILL_COLOR
29 | hvertex_fill_color = DEFAULT_HVERTEX_FILL_COLOR
30 | point_type = P_ROUND
31 | point_size = 8
32 | scale = 1.0
33 | label_font_size = 10
34 |
35 | def __init__(self, label=None, shape_type=0, line_color=None,instance_id = 0):
36 | self.label = label
37 | self.instance_id = instance_id
38 | self.points = []
39 | self.fill = False
40 | self.selected = False
41 | self.shape_type = self.RECT_SHAPE
42 | self.max_piont_num = 4
43 | if shape_type == 1:
44 | self.shape_type = self.POLYGON_SHAPE
45 | self.max_piont_num = 100
46 | self._highlightIndex = None
47 | self._highlightMode = self.NEAR_VERTEX
48 | self._highlightSettings = {
49 | self.NEAR_VERTEX: (4, self.P_ROUND),
50 | self.MOVE_VERTEX: (1.5, self.P_SQUARE),
51 | }
52 |
53 | self._closed = False
54 |
55 | if line_color is not None:
56 | # Override the class line_color attribute
57 | # with an object attribute. Currently this
58 | # is used for drawing the pending line a different color.
59 | self.line_color = line_color
60 |
61 | def set_shape_type(self, type):
62 | self.shape_type = type
63 |
64 | def set_instance_id(self,id):
65 | self.instance_id = id
66 | def get_shape_type(self):
67 | return self.shape_type
68 |
69 | def close(self):
70 | assert len(self.points) > 2
71 | self._closed = True
72 | print len(self.points)
73 |
74 | def isRect(self):
75 | return self.shape_type == self.RECT_SHAPE
76 |
77 | def isPolygon(self):
78 | return self.shape_type == self.POLYGON_SHAPE
79 |
80 | def reachMaxPoints(self):
81 | if len(self.points) >= self.max_piont_num:
82 | return True
83 | return False
84 |
85 | def addPoint(self, point):
86 | if self.points and point == self.points[0]:
87 | self.close()
88 | else:
89 | self.points.append(point)
90 |
91 | def popPoint(self):
92 | if self.points:
93 | return self.points.pop()
94 | return None
95 |
96 | def isClosed(self):
97 | return self._closed
98 |
99 | def setOpen(self):
100 | self._closed = False
101 |
102 | def paint(self, painter):
103 | color = self.select_line_color if self.selected else self.line_color
104 | pen = QPen(color)
105 | # Try using integer sizes for smoother drawing(?)
106 | pen.setWidth(max(1, int(round(2.0 / self.scale))))
107 | painter.setPen(pen)
108 | painter.setFont(QFont(painter.font().family(), self.__class__.label_font_size, QFont.Bold))
109 |
110 | line_path = QPainterPath()
111 | vrtx_path = QPainterPath()
112 |
113 | line_path.moveTo(self.points[0])
114 | # Uncommenting the following line will draw 2 paths
115 | # for the 1st vertex, and make it non-filled, which
116 | # may be desirable.
117 | self.drawVertex(vrtx_path, 0)
118 |
119 | for i, p in enumerate(self.points):
120 | line_path.lineTo(p)
121 | self.drawVertex(vrtx_path, i)
122 | if self.isClosed():
123 | line_path.lineTo(self.points[0])
124 | painter.drawPath(line_path)
125 | painter.drawPath(vrtx_path)
126 | painter.fillPath(vrtx_path, self.vertex_fill_color)
127 | if self.fill:
128 | color = self.select_fill_color if self.selected else self.fill_color
129 | if isinstance(color,list):
130 | color=QColor(*color)
131 | painter.fillPath(line_path, color)
132 | if self.label is not None and self.shape_type == self.RECT_SHAPE:
133 | #painter.setBrush(QColor(255,255,255))
134 | top_left_point = QPointF(self.points[0].x(),self.points[0].y()-int(self.__class__.label_font_size*1.15))
135 | label_bg = QRectF(top_left_point,self.points[3])
136 | painter.drawRect(label_bg)
137 | painter.drawText(self.points[0], self.label)
138 |
139 | def drawVertex(self, path, i):
140 | d = self.point_size / self.scale
141 | shape = self.point_type
142 | point = self.points[i]
143 | if i == self._highlightIndex:
144 | size, shape = self._highlightSettings[self._highlightMode]
145 | d *= size
146 | if self._highlightIndex is not None:
147 | self.vertex_fill_color = self.hvertex_fill_color
148 | else:
149 | self.vertex_fill_color = Shape.vertex_fill_color
150 | if shape == self.P_SQUARE:
151 | path.addRect(point.x() - d / 2, point.y() - d / 2, d, d)
152 | elif shape == self.P_ROUND:
153 | path.addEllipse(point, d / 2.0, d / 2.0)
154 | else:
155 | assert False, "unsupported vertex shape"
156 |
157 | def nearestVertex(self, point, epsilon):
158 | for i, p in enumerate(self.points):
159 | if distance(p - point) <= epsilon:
160 | return i
161 | return None
162 |
163 | def containsPoint(self, point):
164 | return self.makePath().contains(point)
165 |
166 | def makePath(self):
167 | path = QPainterPath(self.points[0])
168 | for p in self.points[1:]:
169 | path.lineTo(p)
170 | return path
171 |
172 | def boundingRect(self):
173 | return self.makePath().boundingRect()
174 |
175 | def moveBy(self, offset):
176 | self.points = [p + offset for p in self.points]
177 |
178 | def moveVertexBy(self, i, offset):
179 | self.points[i] = self.points[i] + offset
180 |
181 | def highlightVertex(self, i, action):
182 | self._highlightIndex = i
183 | self._highlightMode = action
184 |
185 | def highlightClear(self):
186 | self._highlightIndex = None
187 |
188 | def copy(self):
189 | shape = Shape(self.label, self.shape_type, self.line_color)
190 | shape.points = [p for p in self.points]
191 | shape.fill = self.fill
192 | shape.selected = self.selected
193 | shape._closed = self._closed
194 | if self.line_color != shape.line_color:
195 | shape.line_color = self.line_color
196 | if self.fill_color != shape.fill_color:
197 | shape.fill_color = self.fill_color
198 | return shape
199 |
200 | def __len__(self):
201 | return len(self.points)
202 |
203 | def __getitem__(self, key):
204 | return self.points[key]
205 |
206 | def __setitem__(self, key, value):
207 | self.points[key] = value
208 |
--------------------------------------------------------------------------------
/libs/toolBar.py:
--------------------------------------------------------------------------------
1 | from PyQt4.QtGui import *
2 | from PyQt4.QtCore import *
3 |
4 |
5 | class ToolBar(QToolBar):
6 |
7 | def __init__(self, title):
8 | super(ToolBar, self).__init__(title)
9 | layout = self.layout()
10 | m = (0, 0, 0, 0)
11 | layout.setSpacing(0)
12 | layout.setContentsMargins(*m)
13 | self.setContentsMargins(*m)
14 | self.setWindowFlags(self.windowFlags() | Qt.FramelessWindowHint)
15 |
16 | def addAction(self, action):
17 | if isinstance(action, QWidgetAction):
18 | return super(ToolBar, self).addAction(action)
19 | btn = ToolButton()
20 | btn.setDefaultAction(action)
21 | btn.setToolButtonStyle(self.toolButtonStyle())
22 | self.addWidget(btn)
23 |
24 |
25 | class ToolButton(QToolButton):
26 | """ToolBar companion class which ensures all buttons have the same size."""
27 | minSize = (60, 60)
28 |
29 | def minimumSizeHint(self):
30 | ms = super(ToolButton, self).minimumSizeHint()
31 | w1, h1 = ms.width(), ms.height()
32 | w2, h2 = self.minSize
33 | ToolButton.minSize = max(w1, w2), max(h1, h2)
34 | return QSize(*ToolButton.minSize)
35 |
--------------------------------------------------------------------------------
/libs/ustr.py:
--------------------------------------------------------------------------------
1 | import sys
2 |
3 | def ustr(x):
4 | '''py2/py3 unicode helper'''
5 |
6 | if sys.version_info < (3, 0, 0):
7 | from PyQt4.QtCore import QString
8 | if type(x) == str:
9 | return x.decode('utf-8')
10 | if type(x) == QString:
11 | return unicode(x)
12 | return x
13 | else:
14 | return x # py3
--------------------------------------------------------------------------------
/libs/zoomWidget.py:
--------------------------------------------------------------------------------
1 | from PyQt4.QtGui import *
2 | from PyQt4.QtCore import *
3 |
4 | class ZoomWidget(QSpinBox):
5 |
6 | def __init__(self, value=100):
7 | super(ZoomWidget, self).__init__()
8 | self.setButtonSymbols(QAbstractSpinBox.NoButtons)
9 | self.setRange(1, 500)
10 | self.setSuffix(' %')
11 | self.setValue(value)
12 | self.setToolTip(u'Zoom Level')
13 | self.setStatusTip(self.toolTip())
14 | self.setAlignment(Qt.AlignCenter)
15 |
16 | def minimumSizeHint(self):
17 | height = super(ZoomWidget, self).minimumSizeHint().height()
18 | fm = QFontMetrics(self.font())
19 | width = fm.width(str(self.maximum()))
20 | return QSize(width, height)
21 |
--------------------------------------------------------------------------------
/resources.qrc:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | icons/help.png
5 | icons/expert2.png
6 | icons/expert2.png
7 | icons/done.png
8 | icons/file.png
9 | icons/labels.png
10 | icons/objects.png
11 | icons/close.png
12 | icons/fit-width.png
13 | icons/fit-window.png
14 | icons/undo.png
15 | icons/eye.png
16 | icons/quit.png
17 | icons/copy.png
18 | icons/edit.png
19 | icons/open.png
20 | icons/save.png
21 | icons/save-as.png
22 | icons/color.png
23 | icons/color_line.png
24 | icons/zoom.png
25 | icons/zoom-in.png
26 | icons/zoom-out.png
27 | icons/cancel.png
28 | icons/next.png
29 | icons/prev.png
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/screenshot/bbox_label.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lzx1413/LabelImgTool/264aa97dd74a29b45513d311c21975fa26db39dd/screenshot/bbox_label.jpg
--------------------------------------------------------------------------------
/screenshot/brush_task.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lzx1413/LabelImgTool/264aa97dd74a29b45513d311c21975fa26db39dd/screenshot/brush_task.jpg
--------------------------------------------------------------------------------
/screenshot/cls_task.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lzx1413/LabelImgTool/264aa97dd74a29b45513d311c21975fa26db39dd/screenshot/cls_task.jpg
--------------------------------------------------------------------------------
/screenshot/parse_label.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lzx1413/LabelImgTool/264aa97dd74a29b45513d311c21975fa26db39dd/screenshot/parse_label.jpg
--------------------------------------------------------------------------------
/screenshot/remote_settings.JPG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lzx1413/LabelImgTool/264aa97dd74a29b45513d311c21975fa26db39dd/screenshot/remote_settings.JPG
--------------------------------------------------------------------------------
/screenshot/setting_panel.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lzx1413/LabelImgTool/264aa97dd74a29b45513d311c21975fa26db39dd/screenshot/setting_panel.jpg
--------------------------------------------------------------------------------
/scrips/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lzx1413/LabelImgTool/264aa97dd74a29b45513d311c21975fa26db39dd/scrips/__init__.py
--------------------------------------------------------------------------------
/scrips/generate_image.py:
--------------------------------------------------------------------------------
1 | #coding:utf-8
2 | import json
3 | import os
4 |
5 | from libs import saveMaskImage
6 | from libs.pascalVocIO import PascalVocReader
7 |
8 |
9 | def get_name_dic(file_path):
10 | with open(file_path) as infile:
11 | label_num_dic = json.load(infile)
12 | return label_num_dic
13 | '''
14 | items = infile.readlines()
15 | index = 0
16 | for item in items:
17 | item = item.strip().split(' ')
18 | for it in item:
19 | label_num_dic[it] = index
20 | index +=1
21 | '''
22 |
23 | def get_image(filename,label_num_dic = None):
24 | tVocParseReader = PascalVocReader(filename)
25 | raw_shapes = tVocParseReader.getShapes()
26 | print raw_shapes
27 | def format_shape(s):
28 | label,points,aa,bb,c = s
29 | return dict(label=unicode(label),
30 | points=[(int(p[0]), int(p[1])) for p in points])
31 |
32 | shapes = [format_shape(shape) for shape in raw_shapes]
33 | image_size = tVocParseReader.get_img_size()
34 | result_path = '/mask'+filename.split('/')[1].split('.')[0]+'.png'
35 | mask_writer = saveMaskImage.label_mask_writer(label_num_dic, result_path, image_size[0],
36 | image_size[1])
37 | mask_writer.save_mask_image(shapes)
38 |
39 | if __name__ == '__main__':
40 | file_list = os.listdir('img_addition')
41 | print file_list
42 | label_num_dic = get_name_dic('label_num_dic.json')
43 | for file_name in file_list:
44 | get_image('img_addition/'+file_name,label_num_dic)
45 |
--------------------------------------------------------------------------------