Skip to content
This repository was archived by the owner on Feb 29, 2024. It is now read-only.

Commit 8baba7e

Browse files
add truncated flag
1 parent 7cbe685 commit 8baba7e

File tree

8 files changed

+154
-56
lines changed

8 files changed

+154
-56
lines changed

labelImg.py

Lines changed: 51 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -128,13 +128,27 @@ def __init__(self, defaultFilename=None, defaultPrefdefClassFile=None, defaultSa
128128
useDefaultLabelContainer = QWidget()
129129
useDefaultLabelContainer.setLayout(useDefaultLabelQHBoxLayout)
130130

131-
# Create a widget for edit and diffc button
132-
self.diffcButton = QCheckBox(getStr('useDifficult'))
133-
self.diffcButton.setChecked(False)
134-
self.diffcButton.stateChanged.connect(self.btnstate)
135131
self.editButton = QToolButton()
136132
self.editButton.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
137133

134+
# Create Flag checkboxes list
135+
self.flagGroupBox = QGroupBox(self)
136+
self.flagGroupBox.setTitle('Flags')
137+
flagslayout = QVBoxLayout(self)
138+
self.flagGroupBox.setLayout(flagslayout)
139+
self.flagButtons = []
140+
141+
self.diffcButton = QCheckBox(getStr('useDifficult'))
142+
self.diffcButton.setChecked(False)
143+
# calling stateChanged is inappropriate!
144+
self.diffcButton.clicked.connect(self.btnstate)
145+
self.flagButtons.append(self.diffcButton)
146+
147+
self.truncButton = QCheckBox(getStr('useTruncated'))
148+
self.truncButton.setChecked(False)
149+
self.truncButton.clicked.connect(self.btnstate)
150+
self.flagButtons.append(self.truncButton)
151+
138152
# *
139153
# * dhzs 2017-12-2 add copy button
140154
# *
@@ -143,9 +157,13 @@ def __init__(self, defaultFilename=None, defaultPrefdefClassFile=None, defaultSa
143157
self.copy_prev_button.clicked.connect(self.copyPreviousBoundingBoxes)
144158
listLayout.addWidget(self.copy_prev_button)
145159

160+
# add flag buttons to flagsGroupBox
161+
for flagbtn in self.flagButtons:
162+
flagslayout.addWidget(flagbtn)
163+
146164
# Add some of widgets to listLayout
147165
listLayout.addWidget(self.editButton)
148-
listLayout.addWidget(self.diffcButton)
166+
listLayout.addWidget(self.flagGroupBox)
149167
listLayout.addWidget(useDefaultLabelContainer)
150168

151169
# Create and add combobox for showing unique labels in group
@@ -446,8 +464,6 @@ def getFormatMeta(format):
446464
self.fillColor = None
447465
self.zoom_level = 100
448466
self.fit_window = False
449-
# Add Chris
450-
self.difficult = False
451467

452468
## Fix the compatible issue for qt4 and qt5. Convert the QStringList to python list
453469
if settings.get(SETTING_RECENT_FILES):
@@ -479,8 +495,7 @@ def getFormatMeta(format):
479495
Shape.line_color = self.lineColor = QColor(settings.get(SETTING_LINE_COLOR, DEFAULT_LINE_COLOR))
480496
Shape.fill_color = self.fillColor = QColor(settings.get(SETTING_FILL_COLOR, DEFAULT_FILL_COLOR))
481497
self.canvas.setDrawingColor(self.lineColor)
482-
# Add chris
483-
Shape.difficult = self.difficult
498+
Shape.flags = self.flags
484499

485500
def xbool(x):
486501
if isinstance(x, QVariant):
@@ -730,6 +745,20 @@ def fileitemDoubleClicked(self, item=None):
730745
if filename:
731746
self.loadFile(filename)
732747

748+
@property
749+
def flags(self):
750+
"""
751+
Returns
752+
-------
753+
dict
754+
key is a flag name
755+
value is a Bool
756+
"""
757+
return {flagbtn.text(): flagbtn.isChecked() for flagbtn in self.flagButtons}
758+
759+
def setFlagsChecked(self, flags):
760+
pass
761+
733762
# Add chris
734763
def btnstate(self, item= None):
735764
""" Function to handle difficult examples
@@ -741,16 +770,15 @@ def btnstate(self, item= None):
741770
if not item: # If not selected Item, take the first one
742771
item = self.labelList.item(self.labelList.count()-1)
743772

744-
difficult = self.diffcButton.isChecked()
773+
flags = self.flags
745774

746775
try:
747776
shape = self.itemsToShapes[item]
748777
except:
749778
pass
750779
# Checked and Update
751780
try:
752-
if difficult != shape.difficult:
753-
shape.difficult = difficult
781+
if shape.setChangedFlags(flags):
754782
self.setDirty()
755783
else: # User probably changed item visibility
756784
self.canvas.setShapeVisible(shape, item.checkState() == Qt.Checked)
@@ -798,7 +826,7 @@ def remLabel(self, shape):
798826

799827
def loadLabels(self, shapes):
800828
s = []
801-
for label, points, line_color, fill_color, difficult in shapes:
829+
for label, points, line_color, fill_color, flags in shapes:
802830
shape = Shape(label=label)
803831
for x, y in points:
804832

@@ -808,7 +836,7 @@ def loadLabels(self, shapes):
808836
self.setDirty()
809837

810838
shape.addPoint(QPointF(x, y))
811-
shape.difficult = difficult
839+
shape.flags = flags
812840
shape.close()
813841
s.append(shape)
814842

@@ -848,11 +876,10 @@ def format_shape(s):
848876
line_color=s.line_color.getRgb(),
849877
fill_color=s.fill_color.getRgb(),
850878
points=[(p.x(), p.y()) for p in s.points],
851-
# add chris
852-
difficult = s.difficult)
879+
flags=s.flags)
853880

854881
shapes = [format_shape(shape) for shape in self.canvas.shapes]
855-
# Can add differrent annotation formats here
882+
# Can add different annotation formats here
856883
try:
857884
if self.labelFileFormat == LabelFileFormat.PASCAL_VOC:
858885
if annotationFilePath[-4:].lower() != ".xml":
@@ -899,9 +926,11 @@ def labelSelectionChanged(self):
899926
self._noSelectionSlot = True
900927
self.canvas.selectShape(self.itemsToShapes[item])
901928
shape = self.itemsToShapes[item]
902-
# Add Chris
903-
self.diffcButton.setChecked(shape.difficult)
904-
929+
for flagbtn in self.flagButtons:
930+
if hasattr(shape, flagbtn.text()):
931+
flagbtn.setChecked(getattr(shape, flagbtn.text()))
932+
else:
933+
flagbtn.setChecked(False)
905934
def labelItemChanged(self, item):
906935
shape = self.itemsToShapes[item]
907936
label = item.text()
@@ -932,8 +961,8 @@ def newShape(self):
932961
else:
933962
text = self.defaultLabelTextLine.text()
934963

935-
# Add Chris
936-
self.diffcButton.setChecked(False)
964+
for flagbtn in self.flagButtons:
965+
flagbtn.setChecked(False)
937966
if text is not None:
938967
self.prevLabelText = text
939968
generate_color = generateColorByText(text)
@@ -1283,7 +1312,6 @@ def importDirImages(self, dirpath):
12831312
self.lastOpenDir = dirpath
12841313
self.dirname = dirpath
12851314
self.filePath = None
1286-
self.fileListWidget.clear()
12871315
self.mImgList = self.scanAllImages(dirpath)
12881316
self.openNextImg()
12891317
for imgPath in self.mImgList:

labelImg.spec

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# -*- mode: python ; coding: utf-8 -*-
2+
3+
block_cipher = None
4+
5+
6+
a = Analysis(['labelImg.py'],
7+
pathex=['./libs', './', 'C:\\Users\\Administrator\\Anaconda3\\envs\\labelimg\\Lib\\site-packages\\PyQt5\\Qt\\bin', 'O:\\Documents\\programs\\software\\labelImg_copyfunc'],
8+
binaries=[],
9+
datas=[],
10+
hiddenimports=['xml', 'xml.etree', 'xml.etree.ElementTree', 'lxml.etree', 'PyQt5.sip'],
11+
hookspath=[],
12+
runtime_hooks=[],
13+
excludes=[],
14+
win_no_prefer_redirects=False,
15+
win_private_assemblies=False,
16+
cipher=block_cipher,
17+
noarchive=False)
18+
pyz = PYZ(a.pure, a.zipped_data,
19+
cipher=block_cipher)
20+
exe = EXE(pyz,
21+
a.scripts,
22+
a.binaries,
23+
a.zipfiles,
24+
a.datas,
25+
[],
26+
name='labelImg',
27+
debug=False,
28+
bootloader_ignore_signals=False,
29+
strip=False,
30+
upx=True,
31+
upx_exclude=[],
32+
runtime_tmpdir=None,
33+
console=True )

libs/create_ml_io.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ def add_shape(self, label, bndbox):
125125
ymax = bndbox["y"] + (bndbox["height"] / 2)
126126

127127
points = [(xmin, ymin), (xmax, ymin), (xmax, ymax), (xmin, ymax)]
128-
self.shapes.append((label, points, None, None, True))
128+
self.shapes.append((label, points, None, None, {'difficult': False, 'truncated': False}))
129129

130130
def get_shapes(self):
131131
return self.shapes

libs/labelFile.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -77,10 +77,10 @@ def savePascalVocFormat(self, filename, shapes, imagePath, imageData,
7777
for shape in shapes:
7878
points = shape['points']
7979
label = shape['label']
80-
# Add Chris
81-
difficult = int(shape['difficult'])
80+
81+
flags = shape['flags']
8282
bndbox = LabelFile.convertPoints2BndBox(points)
83-
writer.addBndBox(bndbox[0], bndbox[1], bndbox[2], bndbox[3], label, difficult)
83+
writer.addBndBox(bndbox[0], bndbox[1], bndbox[2], bndbox[3], label, flags)
8484

8585
writer.save(targetFile=filename)
8686
return
@@ -107,10 +107,10 @@ def saveYoloFormat(self, filename, shapes, imagePath, imageData, classList,
107107
for shape in shapes:
108108
points = shape['points']
109109
label = shape['label']
110-
# Add Chris
111-
difficult = int(shape['difficult'])
110+
111+
flags = shape['flags']
112112
bndbox = LabelFile.convertPoints2BndBox(points)
113-
writer.addBndBox(bndbox[0], bndbox[1], bndbox[2], bndbox[3], label, difficult)
113+
writer.addBndBox(bndbox[0], bndbox[1], bndbox[2], bndbox[3], label, flags)
114114

115115
writer.save(targetFile=filename, classList=classList)
116116
return

libs/pascal_voc_io.py

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -77,10 +77,10 @@ def genXML(self):
7777
segmented.text = '0'
7878
return top
7979

80-
def addBndBox(self, xmin, ymin, xmax, ymax, name, difficult):
80+
def addBndBox(self, xmin, ymin, xmax, ymax, name, flags):
8181
bndbox = {'xmin': xmin, 'ymin': ymin, 'xmax': xmax, 'ymax': ymax}
8282
bndbox['name'] = name
83-
bndbox['difficult'] = difficult
83+
bndbox['flags'] = flags
8484
self.boxlist.append(bndbox)
8585

8686
def appendObjects(self, top):
@@ -90,15 +90,11 @@ def appendObjects(self, top):
9090
name.text = ustr(each_object['name'])
9191
pose = SubElement(object_item, 'pose')
9292
pose.text = "Unspecified"
93-
truncated = SubElement(object_item, 'truncated')
94-
if int(float(each_object['ymax'])) == int(float(self.imgSize[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.imgSize[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 )
93+
94+
for key, val in each_object['flags'].items():
95+
element = SubElement(object_item, key)
96+
element.text = str( bool(val) & 1 )
97+
10298
bndbox = SubElement(object_item, 'bndbox')
10399
xmin = SubElement(bndbox, 'xmin')
104100
xmin.text = str(each_object['xmin'])
@@ -128,7 +124,7 @@ class PascalVocReader:
128124

129125
def __init__(self, filepath):
130126
# shapes type:
131-
# [labbel, [(x1,y1), (x2,y2), (x3,y3), (x4,y4)], color, color, difficult]
127+
# [labbel, [(x1,y1), (x2,y2), (x3,y3), (x4,y4)], color, color, flags]
132128
self.shapes = []
133129
self.filepath = filepath
134130
self.verified = False
@@ -140,13 +136,13 @@ def __init__(self, filepath):
140136
def getShapes(self):
141137
return self.shapes
142138

143-
def addShape(self, label, bndbox, difficult):
139+
def addShape(self, label, bndbox, flags):
144140
xmin = int(float(bndbox.find('xmin').text))
145141
ymin = int(float(bndbox.find('ymin').text))
146142
xmax = int(float(bndbox.find('xmax').text))
147143
ymax = int(float(bndbox.find('ymax').text))
148144
points = [(xmin, ymin), (xmax, ymin), (xmax, ymax), (xmin, ymax)]
149-
self.shapes.append((label, points, None, None, difficult))
145+
self.shapes.append((label, points, None, None, flags))
150146

151147
def parseXML(self):
152148
assert self.filepath.endswith(XML_EXT), "Unsupport file format"
@@ -163,9 +159,12 @@ def parseXML(self):
163159
for object_iter in xmltree.findall('object'):
164160
bndbox = object_iter.find("bndbox")
165161
label = object_iter.find('name').text
166-
# Add chris
162+
167163
difficult = False
168164
if object_iter.find('difficult') is not None:
169165
difficult = bool(int(object_iter.find('difficult').text))
170-
self.addShape(label, bndbox, difficult)
166+
truncated = False
167+
if object_iter.find('truncated') is not None:
168+
truncated = bool(int(object_iter.find('truncated').text))
169+
self.addShape(label, bndbox, flags={'difficult': difficult, 'truncated': truncated})
171170
return True

libs/shape.py

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,12 @@ class Shape(object):
3838
scale = 1.0
3939
labelFontSize = 8
4040

41-
def __init__(self, label=None, line_color=None, difficult=False, paintLabel=False):
41+
def __init__(self, label=None, line_color=None, flags={'difficult': False, 'truncated': False}, paintLabel=False):
4242
self.label = label
4343
self.points = []
4444
self.fill = False
4545
self.selected = False
46-
self.difficult = difficult
46+
self.flags = flags
4747
self.paintLabel = paintLabel
4848

4949
self._highlightIndex = None
@@ -193,9 +193,30 @@ def copy(self):
193193
shape.line_color = self.line_color
194194
if self.fill_color != Shape.fill_color:
195195
shape.fill_color = self.fill_color
196-
shape.difficult = self.difficult
196+
shape.flags = self.flags
197197
return shape
198198

199+
200+
def setChangedFlags(self, newflags):
201+
"""
202+
Set changed flags and return the boolean representing whether to have changed flags
203+
Returns
204+
-------
205+
Bool
206+
Whether to have changed flags
207+
"""
208+
isChanged = False
209+
for newkey, newval in newflags.items():
210+
if newkey not in self.flags.keys() or self.flags[newkey] != newval:
211+
isChanged = True
212+
break
213+
214+
if isChanged:
215+
self.flags = newflags
216+
217+
return isChanged
218+
219+
199220
def __len__(self):
200221
return len(self.points)
201222

@@ -204,3 +225,19 @@ def __getitem__(self, key):
204225

205226
def __setitem__(self, key, value):
206227
self.points[key] = value
228+
229+
# allow to access the flag's key as Shape's attribute
230+
def __getattr__(self, item):
231+
if item in self.flags.keys():
232+
return self.flags[item]
233+
else:
234+
raise AttributeError('Shape has no attribute \'{}\''.format(item))
235+
236+
def __setattr__(self, key, value):
237+
try:
238+
super().__setattr__(key, value)
239+
except AttributeError:
240+
if key in self.flags.keys():
241+
self.flags[key] = value
242+
else:
243+
raise AttributeError('Shape has no attribute \'{}\''.format(key))

0 commit comments

Comments
 (0)