labelimgplusplus 2.0.0a0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- labelimgplusplus-2.0.0a0.dist-info/LICENSE +9 -0
- labelimgplusplus-2.0.0a0.dist-info/METADATA +282 -0
- labelimgplusplus-2.0.0a0.dist-info/RECORD +30 -0
- labelimgplusplus-2.0.0a0.dist-info/WHEEL +5 -0
- labelimgplusplus-2.0.0a0.dist-info/entry_points.txt +2 -0
- labelimgplusplus-2.0.0a0.dist-info/top_level.txt +1 -0
- libs/__init__.py +2 -0
- libs/canvas.py +748 -0
- libs/colorDialog.py +37 -0
- libs/combobox.py +33 -0
- libs/commands.py +328 -0
- libs/constants.py +26 -0
- libs/create_ml_io.py +135 -0
- libs/default_label_combobox.py +27 -0
- libs/galleryWidget.py +568 -0
- libs/hashableQListWidgetItem.py +28 -0
- libs/labelDialog.py +95 -0
- libs/labelFile.py +174 -0
- libs/lightWidget.py +33 -0
- libs/pascal_voc_io.py +171 -0
- libs/resources.py +4212 -0
- libs/settings.py +45 -0
- libs/shape.py +209 -0
- libs/stringBundle.py +78 -0
- libs/styles.py +82 -0
- libs/toolBar.py +275 -0
- libs/ustr.py +17 -0
- libs/utils.py +119 -0
- libs/yolo_io.py +143 -0
- libs/zoomWidget.py +26 -0
libs/utils.py
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
from math import sqrt
|
|
2
|
+
from libs.ustr import ustr
|
|
3
|
+
import hashlib
|
|
4
|
+
import re
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
try:
|
|
8
|
+
from PyQt5.QtGui import *
|
|
9
|
+
from PyQt5.QtCore import *
|
|
10
|
+
from PyQt5.QtWidgets import *
|
|
11
|
+
QT5 = True
|
|
12
|
+
except ImportError:
|
|
13
|
+
from PyQt4.QtGui import *
|
|
14
|
+
from PyQt4.QtCore import *
|
|
15
|
+
QT5 = False
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def new_icon(icon):
|
|
19
|
+
return QIcon(':/' + icon)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def new_button(text, icon=None, slot=None):
|
|
23
|
+
b = QPushButton(text)
|
|
24
|
+
if icon is not None:
|
|
25
|
+
b.setIcon(new_icon(icon))
|
|
26
|
+
if slot is not None:
|
|
27
|
+
b.clicked.connect(slot)
|
|
28
|
+
return b
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def new_action(parent, text, slot=None, shortcut=None, icon=None,
|
|
32
|
+
tip=None, checkable=False, enabled=True):
|
|
33
|
+
"""Create a new action and assign callbacks, shortcuts, etc."""
|
|
34
|
+
a = QAction(text, parent)
|
|
35
|
+
if icon is not None:
|
|
36
|
+
a.setIcon(new_icon(icon))
|
|
37
|
+
if shortcut is not None:
|
|
38
|
+
if isinstance(shortcut, (list, tuple)):
|
|
39
|
+
a.setShortcuts(shortcut)
|
|
40
|
+
else:
|
|
41
|
+
a.setShortcut(shortcut)
|
|
42
|
+
if tip is not None:
|
|
43
|
+
a.setToolTip(tip)
|
|
44
|
+
a.setStatusTip(tip)
|
|
45
|
+
if slot is not None:
|
|
46
|
+
a.triggered.connect(slot)
|
|
47
|
+
if checkable:
|
|
48
|
+
a.setCheckable(True)
|
|
49
|
+
a.setEnabled(enabled)
|
|
50
|
+
return a
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def add_actions(widget, actions):
|
|
54
|
+
for action in actions:
|
|
55
|
+
if action is None:
|
|
56
|
+
widget.addSeparator()
|
|
57
|
+
elif isinstance(action, QMenu):
|
|
58
|
+
widget.addMenu(action)
|
|
59
|
+
elif isinstance(action, QWidget):
|
|
60
|
+
widget.addWidget(action)
|
|
61
|
+
else:
|
|
62
|
+
widget.addAction(action)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def label_validator():
|
|
66
|
+
return QRegExpValidator(QRegExp(r'^[^ \t].+'), None)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class Struct(object):
|
|
70
|
+
|
|
71
|
+
def __init__(self, **kwargs):
|
|
72
|
+
self.__dict__.update(kwargs)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def distance(p):
|
|
76
|
+
return sqrt(p.x() * p.x() + p.y() * p.y())
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def format_shortcut(text):
|
|
80
|
+
mod, key = text.split('+', 1)
|
|
81
|
+
return '<b>%s</b>+<b>%s</b>' % (mod, key)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def generate_color_by_text(text):
|
|
85
|
+
s = ustr(text)
|
|
86
|
+
hash_code = int(hashlib.sha256(s.encode('utf-8')).hexdigest(), 16)
|
|
87
|
+
r = int((hash_code / 255) % 255)
|
|
88
|
+
g = int((hash_code / 65025) % 255)
|
|
89
|
+
b = int((hash_code / 16581375) % 255)
|
|
90
|
+
return QColor(r, g, b, 100)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def have_qstring():
|
|
94
|
+
"""p3/qt5 get rid of QString wrapper as py3 has native unicode str type"""
|
|
95
|
+
return not (sys.version_info.major >= 3 or QT_VERSION_STR.startswith('5.'))
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def util_qt_strlistclass():
|
|
99
|
+
return QStringList if have_qstring() else list
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def natural_sort(list, key=lambda s:s):
|
|
103
|
+
"""
|
|
104
|
+
Sort the list into natural alphanumeric order.
|
|
105
|
+
"""
|
|
106
|
+
def get_alphanum_key_func(key):
|
|
107
|
+
convert = lambda text: int(text) if text.isdigit() else text
|
|
108
|
+
return lambda s: [convert(c) for c in re.split('([0-9]+)', key(s))]
|
|
109
|
+
sort_key = get_alphanum_key_func(key)
|
|
110
|
+
list.sort(key=sort_key)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
# QT4 has a trimmed method, in QT5 this is called strip
|
|
114
|
+
if QT5:
|
|
115
|
+
def trimmed(text):
|
|
116
|
+
return text.strip()
|
|
117
|
+
else:
|
|
118
|
+
def trimmed(text):
|
|
119
|
+
return text.trimmed()
|
libs/yolo_io.py
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf8 -*-
|
|
3
|
+
import codecs
|
|
4
|
+
import os
|
|
5
|
+
|
|
6
|
+
from libs.constants import DEFAULT_ENCODING
|
|
7
|
+
|
|
8
|
+
TXT_EXT = '.txt'
|
|
9
|
+
ENCODE_METHOD = DEFAULT_ENCODING
|
|
10
|
+
|
|
11
|
+
class YOLOWriter:
|
|
12
|
+
|
|
13
|
+
def __init__(self, folder_name, filename, img_size, database_src='Unknown', local_img_path=None):
|
|
14
|
+
self.folder_name = folder_name
|
|
15
|
+
self.filename = filename
|
|
16
|
+
self.database_src = database_src
|
|
17
|
+
self.img_size = img_size
|
|
18
|
+
self.box_list = []
|
|
19
|
+
self.local_img_path = local_img_path
|
|
20
|
+
self.verified = False
|
|
21
|
+
|
|
22
|
+
def add_bnd_box(self, x_min, y_min, x_max, y_max, name, difficult):
|
|
23
|
+
bnd_box = {'xmin': x_min, 'ymin': y_min, 'xmax': x_max, 'ymax': y_max}
|
|
24
|
+
bnd_box['name'] = name
|
|
25
|
+
bnd_box['difficult'] = difficult
|
|
26
|
+
self.box_list.append(bnd_box)
|
|
27
|
+
|
|
28
|
+
def bnd_box_to_yolo_line(self, box, class_list=[]):
|
|
29
|
+
x_min = box['xmin']
|
|
30
|
+
x_max = box['xmax']
|
|
31
|
+
y_min = box['ymin']
|
|
32
|
+
y_max = box['ymax']
|
|
33
|
+
|
|
34
|
+
x_center = float((x_min + x_max)) / 2 / self.img_size[1]
|
|
35
|
+
y_center = float((y_min + y_max)) / 2 / self.img_size[0]
|
|
36
|
+
|
|
37
|
+
w = float((x_max - x_min)) / self.img_size[1]
|
|
38
|
+
h = float((y_max - y_min)) / self.img_size[0]
|
|
39
|
+
|
|
40
|
+
# PR387
|
|
41
|
+
box_name = box['name']
|
|
42
|
+
if box_name not in class_list:
|
|
43
|
+
class_list.append(box_name)
|
|
44
|
+
|
|
45
|
+
class_index = class_list.index(box_name)
|
|
46
|
+
|
|
47
|
+
return class_index, x_center, y_center, w, h
|
|
48
|
+
|
|
49
|
+
def save(self, class_list=[], target_file=None):
|
|
50
|
+
|
|
51
|
+
out_file = None # Update yolo .txt
|
|
52
|
+
out_class_file = None # Update class list .txt
|
|
53
|
+
|
|
54
|
+
if target_file is None:
|
|
55
|
+
out_file = open(
|
|
56
|
+
self.filename + TXT_EXT, 'w', encoding=ENCODE_METHOD)
|
|
57
|
+
classes_file = os.path.join(os.path.dirname(os.path.abspath(self.filename)), "classes.txt")
|
|
58
|
+
out_class_file = open(classes_file, 'w')
|
|
59
|
+
|
|
60
|
+
else:
|
|
61
|
+
out_file = codecs.open(target_file, 'w', encoding=ENCODE_METHOD)
|
|
62
|
+
classes_file = os.path.join(os.path.dirname(os.path.abspath(target_file)), "classes.txt")
|
|
63
|
+
out_class_file = open(classes_file, 'w')
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
for box in self.box_list:
|
|
67
|
+
class_index, x_center, y_center, w, h = self.bnd_box_to_yolo_line(box, class_list)
|
|
68
|
+
# print (classIndex, x_center, y_center, w, h)
|
|
69
|
+
out_file.write("%d %.6f %.6f %.6f %.6f\n" % (class_index, x_center, y_center, w, h))
|
|
70
|
+
|
|
71
|
+
# print (classList)
|
|
72
|
+
# print (out_class_file)
|
|
73
|
+
for c in class_list:
|
|
74
|
+
out_class_file.write(c+'\n')
|
|
75
|
+
|
|
76
|
+
out_class_file.close()
|
|
77
|
+
out_file.close()
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class YoloReader:
|
|
82
|
+
|
|
83
|
+
def __init__(self, file_path, image, class_list_path=None):
|
|
84
|
+
# shapes type:
|
|
85
|
+
# [labbel, [(x1,y1), (x2,y2), (x3,y3), (x4,y4)], color, color, difficult]
|
|
86
|
+
self.shapes = []
|
|
87
|
+
self.file_path = file_path
|
|
88
|
+
|
|
89
|
+
if class_list_path is None:
|
|
90
|
+
dir_path = os.path.dirname(os.path.realpath(self.file_path))
|
|
91
|
+
self.class_list_path = os.path.join(dir_path, "classes.txt")
|
|
92
|
+
else:
|
|
93
|
+
self.class_list_path = class_list_path
|
|
94
|
+
|
|
95
|
+
# print (file_path, self.class_list_path)
|
|
96
|
+
|
|
97
|
+
classes_file = open(self.class_list_path, 'r')
|
|
98
|
+
self.classes = classes_file.read().strip('\n').split('\n')
|
|
99
|
+
|
|
100
|
+
# print (self.classes)
|
|
101
|
+
|
|
102
|
+
img_size = [image.height(), image.width(),
|
|
103
|
+
1 if image.isGrayscale() else 3]
|
|
104
|
+
|
|
105
|
+
self.img_size = img_size
|
|
106
|
+
|
|
107
|
+
self.verified = False
|
|
108
|
+
# try:
|
|
109
|
+
self.parse_yolo_format()
|
|
110
|
+
# except:
|
|
111
|
+
# pass
|
|
112
|
+
|
|
113
|
+
def get_shapes(self):
|
|
114
|
+
return self.shapes
|
|
115
|
+
|
|
116
|
+
def add_shape(self, label, x_min, y_min, x_max, y_max, difficult):
|
|
117
|
+
|
|
118
|
+
points = [(x_min, y_min), (x_max, y_min), (x_max, y_max), (x_min, y_max)]
|
|
119
|
+
self.shapes.append((label, points, None, None, difficult))
|
|
120
|
+
|
|
121
|
+
def yolo_line_to_shape(self, class_index, x_center, y_center, w, h):
|
|
122
|
+
label = self.classes[int(class_index)]
|
|
123
|
+
|
|
124
|
+
x_min = max(float(x_center) - float(w) / 2, 0)
|
|
125
|
+
x_max = min(float(x_center) + float(w) / 2, 1)
|
|
126
|
+
y_min = max(float(y_center) - float(h) / 2, 0)
|
|
127
|
+
y_max = min(float(y_center) + float(h) / 2, 1)
|
|
128
|
+
|
|
129
|
+
x_min = round(self.img_size[1] * x_min)
|
|
130
|
+
x_max = round(self.img_size[1] * x_max)
|
|
131
|
+
y_min = round(self.img_size[0] * y_min)
|
|
132
|
+
y_max = round(self.img_size[0] * y_max)
|
|
133
|
+
|
|
134
|
+
return label, x_min, y_min, x_max, y_max
|
|
135
|
+
|
|
136
|
+
def parse_yolo_format(self):
|
|
137
|
+
bnd_box_file = open(self.file_path, 'r')
|
|
138
|
+
for bndBox in bnd_box_file:
|
|
139
|
+
class_index, x_center, y_center, w, h = bndBox.strip().split(' ')
|
|
140
|
+
label, x_min, y_min, x_max, y_max = self.yolo_line_to_shape(class_index, x_center, y_center, w, h)
|
|
141
|
+
|
|
142
|
+
# Caveat: difficult flag is discarded when saved as yolo format.
|
|
143
|
+
self.add_shape(label, x_min, y_min, x_max, y_max, False)
|
libs/zoomWidget.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
try:
|
|
2
|
+
from PyQt5.QtGui import *
|
|
3
|
+
from PyQt5.QtCore import *
|
|
4
|
+
from PyQt5.QtWidgets import *
|
|
5
|
+
except ImportError:
|
|
6
|
+
from PyQt4.QtGui import *
|
|
7
|
+
from PyQt4.QtCore import *
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ZoomWidget(QSpinBox):
|
|
11
|
+
|
|
12
|
+
def __init__(self, value=100):
|
|
13
|
+
super(ZoomWidget, self).__init__()
|
|
14
|
+
self.setButtonSymbols(QAbstractSpinBox.NoButtons)
|
|
15
|
+
self.setRange(1, 500)
|
|
16
|
+
self.setSuffix(' %')
|
|
17
|
+
self.setValue(value)
|
|
18
|
+
self.setToolTip(u'Zoom Level')
|
|
19
|
+
self.setStatusTip(self.toolTip())
|
|
20
|
+
self.setAlignment(Qt.AlignCenter)
|
|
21
|
+
|
|
22
|
+
def minimumSizeHint(self):
|
|
23
|
+
height = super(ZoomWidget, self).minimumSizeHint().height()
|
|
24
|
+
fm = QFontMetrics(self.font())
|
|
25
|
+
width = fm.width(str(self.maximum()))
|
|
26
|
+
return QSize(width, height)
|