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.
libs/labelFile.py ADDED
@@ -0,0 +1,174 @@
1
+ # Copyright (c) 2016 Tzutalin
2
+ # Create by TzuTaLin <tzu.ta.lin@gmail.com>
3
+
4
+ try:
5
+ from PyQt5.QtGui import QImage
6
+ except ImportError:
7
+ from PyQt4.QtGui import QImage
8
+
9
+ import os.path
10
+ from enum import Enum
11
+
12
+ from libs.create_ml_io import CreateMLWriter
13
+ from libs.pascal_voc_io import PascalVocWriter
14
+ from libs.pascal_voc_io import XML_EXT
15
+ from libs.yolo_io import YOLOWriter
16
+
17
+
18
+ class LabelFileFormat(Enum):
19
+ PASCAL_VOC = 1
20
+ YOLO = 2
21
+ CREATE_ML = 3
22
+
23
+
24
+ class LabelFileError(Exception):
25
+ pass
26
+
27
+
28
+ class LabelFile(object):
29
+ # It might be changed as window creates. By default, using XML ext
30
+ # suffix = '.lif'
31
+ suffix = XML_EXT
32
+
33
+ def __init__(self, filename=None):
34
+ self.shapes = ()
35
+ self.image_path = None
36
+ self.image_data = None
37
+ self.verified = False
38
+
39
+ def save_create_ml_format(self, filename, shapes, image_path, image_data, class_list, line_color=None, fill_color=None, database_src=None):
40
+ img_folder_name = os.path.basename(os.path.dirname(image_path))
41
+ img_file_name = os.path.basename(image_path)
42
+
43
+ image = QImage()
44
+ image.load(image_path)
45
+ image_shape = [image.height(), image.width(),
46
+ 1 if image.isGrayscale() else 3]
47
+ writer = CreateMLWriter(img_folder_name, img_file_name,
48
+ image_shape, shapes, filename, local_img_path=image_path)
49
+ writer.verified = self.verified
50
+ writer.write()
51
+ return
52
+
53
+
54
+ def save_pascal_voc_format(self, filename, shapes, image_path, image_data,
55
+ line_color=None, fill_color=None, database_src=None):
56
+ img_folder_path = os.path.dirname(image_path)
57
+ img_folder_name = os.path.split(img_folder_path)[-1]
58
+ img_file_name = os.path.basename(image_path)
59
+ # imgFileNameWithoutExt = os.path.splitext(img_file_name)[0]
60
+ # Read from file path because self.imageData might be empty if saving to
61
+ # Pascal format
62
+ if isinstance(image_data, QImage):
63
+ image = image_data
64
+ else:
65
+ image = QImage()
66
+ image.load(image_path)
67
+ image_shape = [image.height(), image.width(),
68
+ 1 if image.isGrayscale() else 3]
69
+ writer = PascalVocWriter(img_folder_name, img_file_name,
70
+ image_shape, local_img_path=image_path)
71
+ writer.verified = self.verified
72
+
73
+ for shape in shapes:
74
+ points = shape['points']
75
+ label = shape['label']
76
+ # Add Chris
77
+ difficult = int(shape['difficult'])
78
+ bnd_box = LabelFile.convert_points_to_bnd_box(points)
79
+ writer.add_bnd_box(bnd_box[0], bnd_box[1], bnd_box[2], bnd_box[3], label, difficult)
80
+
81
+ writer.save(target_file=filename)
82
+ return
83
+
84
+ def save_yolo_format(self, filename, shapes, image_path, image_data, class_list,
85
+ line_color=None, fill_color=None, database_src=None):
86
+ img_folder_path = os.path.dirname(image_path)
87
+ img_folder_name = os.path.split(img_folder_path)[-1]
88
+ img_file_name = os.path.basename(image_path)
89
+ # imgFileNameWithoutExt = os.path.splitext(img_file_name)[0]
90
+ # Read from file path because self.imageData might be empty if saving to
91
+ # Pascal format
92
+ if isinstance(image_data, QImage):
93
+ image = image_data
94
+ else:
95
+ image = QImage()
96
+ image.load(image_path)
97
+ image_shape = [image.height(), image.width(),
98
+ 1 if image.isGrayscale() else 3]
99
+ writer = YOLOWriter(img_folder_name, img_file_name,
100
+ image_shape, local_img_path=image_path)
101
+ writer.verified = self.verified
102
+
103
+ for shape in shapes:
104
+ points = shape['points']
105
+ label = shape['label']
106
+ # Add Chris
107
+ difficult = int(shape['difficult'])
108
+ bnd_box = LabelFile.convert_points_to_bnd_box(points)
109
+ writer.add_bnd_box(bnd_box[0], bnd_box[1], bnd_box[2], bnd_box[3], label, difficult)
110
+
111
+ writer.save(target_file=filename, class_list=class_list)
112
+ return
113
+
114
+ def toggle_verify(self):
115
+ self.verified = not self.verified
116
+
117
+ ''' ttf is disable
118
+ def load(self, filename):
119
+ import json
120
+ with open(filename, 'rb') as f:
121
+ data = json.load(f)
122
+ imagePath = data['imagePath']
123
+ imageData = b64decode(data['imageData'])
124
+ lineColor = data['lineColor']
125
+ fillColor = data['fillColor']
126
+ shapes = ((s['label'], s['points'], s['line_color'], s['fill_color'])\
127
+ for s in data['shapes'])
128
+ # Only replace data after everything is loaded.
129
+ self.shapes = shapes
130
+ self.imagePath = imagePath
131
+ self.imageData = imageData
132
+ self.lineColor = lineColor
133
+ self.fillColor = fillColor
134
+
135
+ def save(self, filename, shapes, imagePath, imageData, lineColor=None, fillColor=None):
136
+ import json
137
+ with open(filename, 'wb') as f:
138
+ json.dump(dict(
139
+ shapes=shapes,
140
+ lineColor=lineColor, fillColor=fillColor,
141
+ imagePath=imagePath,
142
+ imageData=b64encode(imageData)),
143
+ f, ensure_ascii=True, indent=2)
144
+ '''
145
+
146
+ @staticmethod
147
+ def is_label_file(filename):
148
+ file_suffix = os.path.splitext(filename)[1].lower()
149
+ return file_suffix == LabelFile.suffix
150
+
151
+ @staticmethod
152
+ def convert_points_to_bnd_box(points):
153
+ x_min = float('inf')
154
+ y_min = float('inf')
155
+ x_max = float('-inf')
156
+ y_max = float('-inf')
157
+ for p in points:
158
+ x = p[0]
159
+ y = p[1]
160
+ x_min = min(x, x_min)
161
+ y_min = min(y, y_min)
162
+ x_max = max(x, x_max)
163
+ y_max = max(y, y_max)
164
+
165
+ # Martin Kersner, 2015/11/12
166
+ # 0-valued coordinates of BB caused an error while
167
+ # training faster-rcnn object detector.
168
+ if x_min < 1:
169
+ x_min = 1
170
+
171
+ if y_min < 1:
172
+ y_min = 1
173
+
174
+ return int(x_min), int(y_min), int(x_max), int(y_max)
libs/lightWidget.py ADDED
@@ -0,0 +1,33 @@
1
+ try:
2
+ from PyQt5.QtGui import *
3
+ from PyQt5.QtCore import *
4
+ from PyQt5.QtWidgets import *
5
+ except ImportError:
6
+ from PyQt4.QtGui import *
7
+ from PyQt4.QtCore import *
8
+
9
+
10
+ class LightWidget(QSpinBox):
11
+
12
+ def __init__(self, title, value=50):
13
+ super(LightWidget, self).__init__()
14
+ self.setButtonSymbols(QAbstractSpinBox.NoButtons)
15
+ self.setRange(0, 100)
16
+ self.setSuffix(' %')
17
+ self.setValue(value)
18
+ self.setToolTip(title)
19
+ self.setStatusTip(self.toolTip())
20
+ self.setAlignment(Qt.AlignCenter)
21
+
22
+ def minimumSizeHint(self):
23
+ height = super(LightWidget, self).minimumSizeHint().height()
24
+ fm = QFontMetrics(self.font())
25
+ width = fm.width(str(self.maximum()))
26
+ return QSize(width, height)
27
+
28
+ def color(self):
29
+ if self.value() == 50:
30
+ return None
31
+
32
+ strength = int(self.value()/100 * 255 + 0.5)
33
+ return QColor(strength, strength, strength)
libs/pascal_voc_io.py ADDED
@@ -0,0 +1,171 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf8 -*-
3
+ import sys
4
+ from xml.etree import ElementTree
5
+ from xml.etree.ElementTree import Element, SubElement
6
+ from lxml import etree
7
+ import codecs
8
+ from libs.constants import DEFAULT_ENCODING
9
+ from libs.ustr import ustr
10
+
11
+
12
+ XML_EXT = '.xml'
13
+ ENCODE_METHOD = DEFAULT_ENCODING
14
+
15
+ class PascalVocWriter:
16
+
17
+ def __init__(self, folder_name, filename, img_size, database_src='Unknown', local_img_path=None):
18
+ self.folder_name = folder_name
19
+ self.filename = filename
20
+ self.database_src = database_src
21
+ self.img_size = img_size
22
+ self.box_list = []
23
+ self.local_img_path = local_img_path
24
+ self.verified = False
25
+
26
+ def prettify(self, elem):
27
+ """
28
+ Return a pretty-printed XML string for the Element.
29
+ """
30
+ rough_string = ElementTree.tostring(elem, 'utf8')
31
+ root = etree.fromstring(rough_string)
32
+ return etree.tostring(root, pretty_print=True, encoding=ENCODE_METHOD).replace(" ".encode(), "\t".encode())
33
+ # minidom does not support UTF-8
34
+ # reparsed = minidom.parseString(rough_string)
35
+ # return reparsed.toprettyxml(indent="\t", encoding=ENCODE_METHOD)
36
+
37
+ def gen_xml(self):
38
+ """
39
+ Return XML root
40
+ """
41
+ # Check conditions
42
+ if self.filename is None or \
43
+ self.folder_name is None or \
44
+ self.img_size is None:
45
+ return None
46
+
47
+ top = Element('annotation')
48
+ if self.verified:
49
+ top.set('verified', 'yes')
50
+
51
+ folder = SubElement(top, 'folder')
52
+ folder.text = self.folder_name
53
+
54
+ filename = SubElement(top, 'filename')
55
+ filename.text = self.filename
56
+
57
+ if self.local_img_path is not None:
58
+ local_img_path = SubElement(top, 'path')
59
+ local_img_path.text = self.local_img_path
60
+
61
+ source = SubElement(top, 'source')
62
+ database = SubElement(source, 'database')
63
+ database.text = self.database_src
64
+
65
+ size_part = SubElement(top, 'size')
66
+ width = SubElement(size_part, 'width')
67
+ height = SubElement(size_part, 'height')
68
+ depth = SubElement(size_part, 'depth')
69
+ width.text = str(self.img_size[1])
70
+ height.text = str(self.img_size[0])
71
+ if len(self.img_size) == 3:
72
+ depth.text = str(self.img_size[2])
73
+ else:
74
+ depth.text = '1'
75
+
76
+ segmented = SubElement(top, 'segmented')
77
+ segmented.text = '0'
78
+ return top
79
+
80
+ def add_bnd_box(self, x_min, y_min, x_max, y_max, name, difficult):
81
+ bnd_box = {'xmin': x_min, 'ymin': y_min, 'xmax': x_max, 'ymax': y_max}
82
+ bnd_box['name'] = name
83
+ bnd_box['difficult'] = difficult
84
+ self.box_list.append(bnd_box)
85
+
86
+ def append_objects(self, top):
87
+ for each_object in self.box_list:
88
+ object_item = SubElement(top, 'object')
89
+ name = SubElement(object_item, 'name')
90
+ name.text = ustr(each_object['name'])
91
+ pose = SubElement(object_item, 'pose')
92
+ pose.text = "Unspecified"
93
+ truncated = SubElement(object_item, 'truncated')
94
+ if int(float(each_object['ymax'])) == int(float(self.img_size[0])) or (int(float(each_object['ymin'])) == 1):
95
+ truncated.text = "1" # max == height or min
96
+ elif (int(float(each_object['xmax'])) == int(float(self.img_size[1]))) or (int(float(each_object['xmin'])) == 1):
97
+ truncated.text = "1" # max == width or min
98
+ else:
99
+ truncated.text = "0"
100
+ difficult = SubElement(object_item, 'difficult')
101
+ difficult.text = str(bool(each_object['difficult']) & 1)
102
+ bnd_box = SubElement(object_item, 'bndbox')
103
+ x_min = SubElement(bnd_box, 'xmin')
104
+ x_min.text = str(each_object['xmin'])
105
+ y_min = SubElement(bnd_box, 'ymin')
106
+ y_min.text = str(each_object['ymin'])
107
+ x_max = SubElement(bnd_box, 'xmax')
108
+ x_max.text = str(each_object['xmax'])
109
+ y_max = SubElement(bnd_box, 'ymax')
110
+ y_max.text = str(each_object['ymax'])
111
+
112
+ def save(self, target_file=None):
113
+ root = self.gen_xml()
114
+ self.append_objects(root)
115
+ out_file = None
116
+ if target_file is None:
117
+ out_file = codecs.open(
118
+ self.filename + XML_EXT, 'w', encoding=ENCODE_METHOD)
119
+ else:
120
+ out_file = codecs.open(target_file, 'w', encoding=ENCODE_METHOD)
121
+
122
+ prettify_result = self.prettify(root)
123
+ out_file.write(prettify_result.decode('utf8'))
124
+ out_file.close()
125
+
126
+
127
+ class PascalVocReader:
128
+
129
+ def __init__(self, file_path):
130
+ # shapes type:
131
+ # [labbel, [(x1,y1), (x2,y2), (x3,y3), (x4,y4)], color, color, difficult]
132
+ self.shapes = []
133
+ self.file_path = file_path
134
+ self.verified = False
135
+ try:
136
+ self.parse_xml()
137
+ except:
138
+ pass
139
+
140
+ def get_shapes(self):
141
+ return self.shapes
142
+
143
+ def add_shape(self, label, bnd_box, difficult):
144
+ x_min = int(float(bnd_box.find('xmin').text))
145
+ y_min = int(float(bnd_box.find('ymin').text))
146
+ x_max = int(float(bnd_box.find('xmax').text))
147
+ y_max = int(float(bnd_box.find('ymax').text))
148
+ points = [(x_min, y_min), (x_max, y_min), (x_max, y_max), (x_min, y_max)]
149
+ self.shapes.append((label, points, None, None, difficult))
150
+
151
+ def parse_xml(self):
152
+ assert self.file_path.endswith(XML_EXT), "Unsupported file format"
153
+ parser = etree.XMLParser(encoding=ENCODE_METHOD)
154
+ xml_tree = ElementTree.parse(self.file_path, parser=parser).getroot()
155
+ filename = xml_tree.find('filename').text
156
+ try:
157
+ verified = xml_tree.attrib['verified']
158
+ if verified == 'yes':
159
+ self.verified = True
160
+ except KeyError:
161
+ self.verified = False
162
+
163
+ for object_iter in xml_tree.findall('object'):
164
+ bnd_box = object_iter.find("bndbox")
165
+ label = object_iter.find('name').text
166
+ # Add chris
167
+ difficult = False
168
+ if object_iter.find('difficult') is not None:
169
+ difficult = bool(int(object_iter.find('difficult').text))
170
+ self.add_shape(label, bnd_box, difficult)
171
+ return True