Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@ roLabelImg
.. image:: https://img.shields.io/travis/tzutalin/labelImg.svg
:target: https://travis-ci.org/tzutalin/labelImg

Newly Update In 2023.4.5
------------------
- The angle of box is converted to the range of [-pi, pi], positive in counterclockwise direction.
- Add a red arrow to each box to indicate the direction for better visualization.
- Newly add 'Track' button to track the boxes and labels in previous image to next image, then we just need to fine tuning the box rather than redraw the boxes. The function is useful when you label the images which are consecutively shooted.
.. image:: ./demo/track.png

Introduction
------------------
roLabelImg is a graphical image annotation tool can label ROTATED rectangle regions, which is rewrite from 'labelImg'.

The original version 'labelImg''s link is here<https://github.com/tzutalin/labelImg>.
Expand Down
14 changes: 7 additions & 7 deletions data/predefined_classes.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
ship
plane
dog
person
cat
tv
car
postive_face
negative_face
open_eye
closed_eye
open_mouth
closed_mouth
phone_and_hand
Binary file added demo/track.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 4 additions & 2 deletions libs/canvas.py
Original file line number Diff line number Diff line change
Expand Up @@ -845,9 +845,11 @@ def resetAllLines(self):
self.drawingPolygon.emit(False)
self.update()

def loadPixmap(self, pixmap):
def loadPixmap(self, pixmap, trackCuerentLabels=False):
self.pixmap = pixmap
self.shapes = []
if not trackCuerentLabels:
self.shapes = []

self.repaint()

def loadShapes(self, shapes):
Expand Down
26 changes: 26 additions & 0 deletions libs/colors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
try:
from PyQt5.QtGui import *
from PyQt5.QtCore import *
except ImportError:
from PyQt4.QtGui import *
from PyQt4.QtCore import *

class Colors:
# Ultralytics color palette https://ultralytics.com/
def __init__(self):
# hex = matplotlib.colors.TABLEAU_COLORS.values()
hexs = ('00E600', 'FF0000', '0000FF', 'FF1AC6', 'FF9900', '9900FF', 'FF6600', '3DDB86', '1A9334', '00D4BB',
'2C99A8', '00C2FF', '344593', '6473FF', '0018EC', '8438FF', '520085', 'CB38FF', 'FF95C8', 'FF37C7')
self.palette = [self.hex2rgb(f'#{c}') for c in hexs]
self.n = len(self.palette)

def __call__(self, i):
c = self.palette[int(i) % self.n]
return QColor(c[0], c[1], c[2])

@staticmethod
def hex2rgb(h): # rgb order (PIL)
return tuple(int(h[1 + i:1 + i + 2], 16) for i in (0, 2, 4))


colors = Colors() # create instance for 'from utils.plots import colors'
2 changes: 1 addition & 1 deletion libs/labelFile.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,6 @@ def convertPoints2RotatedBndBox(shape):
h = math.sqrt((points[2][0]-points[1][0]) ** 2 +
(points[2][1]-points[1][1]) ** 2)

angle = direction % math.pi
angle = math.pi - direction # % math.pi

return (round(cx,4),round(cy,4),round(w,4),round(h,4),round(angle,6))
8 changes: 4 additions & 4 deletions libs/pascal_voc_io.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ def appendObjects(self, top):
w.text = str(each_object['w'])
h = SubElement(robndbox, 'h')
h.text = str(each_object['h'])
angle = SubElement(robndbox, 'angle')
angle = SubElement(robndbox, 'angle')
angle.text = str(each_object['angle'])

def save(self, targetFile=None):
Expand Down Expand Up @@ -224,7 +224,7 @@ def addRotatedShape(self, label, robndbox, difficult):
cy = float(robndbox.find('cy').text)
w = float(robndbox.find('w').text)
h = float(robndbox.find('h').text)
angle = float(robndbox.find('angle').text)
angle = math.pi - float(robndbox.find('angle').text)

p0x,p0y = self.rotatePoint(cx,cy, cx - w/2, cy - h/2, -angle)
p1x,p1y = self.rotatePoint(cx,cy, cx + w/2, cy - h/2, -angle)
Expand All @@ -235,8 +235,8 @@ def addRotatedShape(self, label, robndbox, difficult):
self.shapes.append((label, points, angle, True, None, None, difficult))

def rotatePoint(self, xc,yc, xp,yp, theta):
xoff = xp-xc;
yoff = yp-yc;
xoff = xp-xc
yoff = yp-yc

cosTheta = math.cos(theta)
sinTheta = math.sin(theta)
Expand Down
77 changes: 68 additions & 9 deletions libs/shape.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,19 @@

from lib import distance
import math
from colors import *
import os
import codecs

DEFAULT_LINE_COLOR = QColor(0, 255, 0, 128)
DEFAULT_FILL_COLOR = QColor(255, 0, 0, 128)
DEFAULT_SELECT_LINE_COLOR = QColor(255, 255, 255)
DIRECTION_ARROW_COLOR = QColor(255, 0, 0)
DEFAULT_SELECT_FILL_COLOR = QColor(0, 128, 255, 155)
DEFAULT_VERTEX_FILL_COLOR = QColor(0, 255, 0, 255)
DEFAULT_HVERTEX_FILL_COLOR = QColor(255, 0, 0)

ARROW_LENGTH = 10
ARROW_ANGLE = 0.5

class Shape(object):
P_SQUARE, P_ROUND = range(2)
Expand Down Expand Up @@ -62,7 +67,18 @@ def __init__(self, label=None, line_color=None,difficult = False):
# with an object attribute. Currently this
# is used for drawing the pending line a different color.
self.line_color = line_color

self.labels = None
self.loadPredefinedClasses(os.path.join('data', 'predefined_classes.txt'))

def loadPredefinedClasses(self, predefClassesFile):
if os.path.exists(predefClassesFile) is True:
with codecs.open(predefClassesFile, 'r', 'utf8') as f:
for line in f:
line = line.strip()
if self.labels is None:
self.labels = [line]
else:
self.labels.append(line)

def rotate(self, theta):
for i, p in enumerate(self.points):
Expand All @@ -71,7 +87,7 @@ def rotate(self, theta):
self.direction = self.direction % (2 * math.pi)

def rotatePoint(self, p, theta):
order = p-self.center;
order = p-self.center
cosTheta = math.cos(theta)
sinTheta = math.sin(theta)
pResx = cosTheta * order.x() + sinTheta * order.y()
Expand Down Expand Up @@ -108,10 +124,16 @@ def setOpen(self):

def paint(self, painter):
if self.points:
color = self.select_line_color if self.selected else self.line_color
label_index = 7
for i in range(len(self.labels)):
if self.label == self.labels[i]:
label_index = i
break
color = colors(label_index)#self.select_line_color if self.selected else self.line_color
#print(self.label, label_index, color)
pen = QPen(color)
# Try using integer sizes for smoother drawing(?)
pen.setWidth(max(1, int(round(2.0 / self.scale))))
pen.setWidth(max(1, int(round(1.0 / self.scale))))
painter.setPen(pen)

line_path = QPainterPath()
Expand All @@ -126,7 +148,15 @@ def paint(self, painter):
for i, p in enumerate(self.points):
line_path.lineTo(p)
# print('shape paint points (%d, %d)' % (p.x(), p.y()))
# if(i == len(self.points) - 1):
# pen.setColor(DIRECTION_ARROW_COLOR)
# painter.setPen(pen)
# cur_line = QLineF()
# cur_line.setP1(self.points[i])
# cur_line.setP2(self.points[(i + 1) % (len(self.points))])
# painter.drawLine(cur_line)
self.drawVertex(vrtx_path, i)

if self.isClosed():
line_path.lineTo(self.points[0])

Expand All @@ -140,9 +170,38 @@ def paint(self, painter):
painter.drawPath(line_path)
painter.drawPath(vrtx_path)
painter.fillPath(vrtx_path, self.vertex_fill_color)
if self.fill:
color = self.select_fill_color if self.selected else self.fill_color
painter.fillPath(line_path, color)


if len(self.points) == 4 and self.isRotated:
pen.setColor(DIRECTION_ARROW_COLOR)
painter.setPen(pen)
center = (self.points[0]+self.points[2]) / 2 # the center of rectangle
toward_point = (self.points[3] + self.points[0]) / 2 # the end point in the direction

arrow_center = QLineF(toward_point, center)
painter.drawLine(arrow_center)

arrow_center.setLength(ARROW_LENGTH)
arrow_center_x = arrow_center.x2() - arrow_center.x1()
arrow_center_y = arrow_center.y2() - arrow_center.y1()

arrow_left_p2_x = arrow_center_x * math.cos(ARROW_ANGLE) - arrow_center_y * math.sin(ARROW_ANGLE)
arrow_left_p2_y = arrow_center_x * math.sin(ARROW_ANGLE) + arrow_center_y * math.cos(ARROW_ANGLE)
arrow_center.setP2(QPointF(arrow_center.x1() + arrow_left_p2_x, arrow_center.y1() + arrow_left_p2_y))
painter.drawLine(arrow_center)

arrow_right_p2_x = arrow_center_x * math.cos(-ARROW_ANGLE) - arrow_center_y * math.sin(-ARROW_ANGLE)
arrow_right_p2_y = arrow_center_x * math.sin(-ARROW_ANGLE) + arrow_center_y * math.cos(-ARROW_ANGLE)
arrow_center.setP2(QPointF(arrow_center.x1() + arrow_right_p2_x, arrow_center.y1() + arrow_right_p2_y))
painter.drawLine(arrow_center)

pen.setColor(color)
painter.setPen(pen)


#if self.fill:
# color = self.select_fill_color if self.selected else self.fill_color
# painter.fillPath(line_path, color)

if self.center is not None:
center_path = QPainterPath()
Expand All @@ -156,7 +215,7 @@ def paint(self, painter):

def paintNormalCenter(self, painter):
if self.center is not None:
center_path = QPainterPath();
center_path = QPainterPath()
d = self.point_size / self.scale
center_path.addRect(self.center.x() - d / 2, self.center.y() - d / 2, d, d)
painter.drawPath(center_path)
Expand Down
Loading