Spaces:
Running
on
A10G
Running
on
A10G
#!/usr/bin/python | |
# | |
# Classes to store, read, and write annotations | |
# | |
from __future__ import print_function, absolute_import, division | |
import os | |
import json | |
import numpy as np | |
from collections import namedtuple | |
# get current date and time | |
import datetime | |
import locale | |
from abc import ABCMeta, abstractmethod | |
from .box3dImageTransform import Camera | |
# A point in a polygon | |
Point = namedtuple('Point', ['x', 'y']) | |
class CsObjectType(): | |
"""Type of an object""" | |
POLY = 1 # polygon | |
BBOX2D = 2 # bounding box | |
BBOX3D = 3 # 3d bounding box | |
IGNORE2D = 4 # 2d ignore region | |
class CsObject: | |
"""Abstract base class for annotation objects""" | |
__metaclass__ = ABCMeta | |
def __init__(self, objType): | |
self.objectType = objType | |
# the label | |
self.label = "" | |
# If deleted or not | |
self.deleted = 0 | |
# If verified or not | |
self.verified = 0 | |
# The date string | |
self.date = "" | |
# The username | |
self.user = "" | |
# Draw the object | |
# Not read from or written to JSON | |
# Set to False if deleted object | |
# Might be set to False by the application for other reasons | |
self.draw = True | |
def __str__(self): pass | |
def fromJsonText(self, jsonText, objId=-1): pass | |
def toJsonText(self): pass | |
def updateDate(self): | |
try: | |
locale.setlocale(locale.LC_ALL, 'en_US.utf8') | |
except locale.Error: | |
locale.setlocale(locale.LC_ALL, 'en_US') | |
except locale.Error: | |
locale.setlocale(locale.LC_ALL, 'us_us.utf8') | |
except locale.Error: | |
locale.setlocale(locale.LC_ALL, 'us_us') | |
except Exception: | |
pass | |
self.date = datetime.datetime.now().strftime("%d-%b-%Y %H:%M:%S") | |
# Mark the object as deleted | |
def delete(self): | |
self.deleted = 1 | |
self.draw = False | |
class CsPoly(CsObject): | |
"""Class that contains the information of a single annotated object as polygon""" | |
# Constructor | |
def __init__(self): | |
CsObject.__init__(self, CsObjectType.POLY) | |
# the polygon as list of points | |
self.polygon = [] | |
# the object ID | |
self.id = -1 | |
def __str__(self): | |
polyText = "" | |
if self.polygon: | |
if len(self.polygon) <= 4: | |
for p in self.polygon: | |
polyText += '({},{}) '.format(p.x, p.y) | |
else: | |
polyText += '({},{}) ({},{}) ... ({},{}) ({},{})'.format( | |
self.polygon[0].x, self.polygon[0].y, | |
self.polygon[1].x, self.polygon[1].y, | |
self.polygon[-2].x, self.polygon[-2].y, | |
self.polygon[-1].x, self.polygon[-1].y) | |
else: | |
polyText = "none" | |
text = "Object: {} - {}".format(self.label, polyText) | |
return text | |
def fromJsonText(self, jsonText, objId=-1): | |
self.id = objId | |
self.label = str(jsonText['label']) | |
self.polygon = [Point(p[0], p[1]) for p in jsonText['polygon']] | |
if 'deleted' in jsonText.keys(): | |
self.deleted = jsonText['deleted'] | |
else: | |
self.deleted = 0 | |
if 'verified' in jsonText.keys(): | |
self.verified = jsonText['verified'] | |
else: | |
self.verified = 1 | |
if 'user' in jsonText.keys(): | |
self.user = jsonText['user'] | |
else: | |
self.user = '' | |
if 'date' in jsonText.keys(): | |
self.date = jsonText['date'] | |
else: | |
self.date = '' | |
if self.deleted == 1: | |
self.draw = False | |
else: | |
self.draw = True | |
def toJsonText(self): | |
objDict = {} | |
objDict['label'] = self.label | |
objDict['id'] = self.id | |
objDict['deleted'] = self.deleted | |
objDict['verified'] = self.verified | |
objDict['user'] = self.user | |
objDict['date'] = self.date | |
objDict['polygon'] = [] | |
for pt in self.polygon: | |
objDict['polygon'].append([pt.x, pt.y]) | |
return objDict | |
class CsBbox2d(CsObject): | |
"""Class that contains the information of a single annotated object as bounding box""" | |
# Constructor | |
def __init__(self): | |
CsObject.__init__(self, CsObjectType.BBOX2D) | |
# the polygon as list of points | |
self.bbox_amodal_xywh = [] | |
self.bbox_modal_xywh = [] | |
# the ID of the corresponding object | |
self.instanceId = -1 | |
# the label of the corresponding object | |
self.label = "" | |
def __str__(self): | |
bboxAmodalText = "" | |
bboxAmodalText += '[(x1: {}, y1: {}), (w: {}, h: {})]'.format( | |
self.bbox_amodal_xywh[0], self.bbox_amodal_xywh[1], self.bbox_amodal_xywh[2], self.bbox_amodal_xywh[3]) | |
bboxModalText = "" | |
bboxModalText += '[(x1: {}, y1: {}), (w: {}, h: {})]'.format( | |
self.bbox_modal_xywh[0], self.bbox_modal_xywh[1], self.bbox_modal_xywh[2], self.bbox_modal_xywh[3]) | |
text = "Object: {}\n - Amodal {}\n - Modal {}".format( | |
self.label, bboxAmodalText, bboxModalText) | |
return text | |
def setAmodalBox(self, bbox_amodal): | |
# sets the amodal box if required | |
self.bbox_amodal_xywh = [ | |
bbox_amodal[0], | |
bbox_amodal[1], | |
bbox_amodal[2] - bbox_amodal[0], | |
bbox_amodal[3] - bbox_amodal[1] | |
] | |
# access 2d boxes in [xmin, ymin, xmax, ymax] format | |
def bbox_amodal(self): | |
"""Returns the 2d box as [xmin, ymin, xmax, ymax]""" | |
return [ | |
self.bbox_amodal_xywh[0], | |
self.bbox_amodal_xywh[1], | |
self.bbox_amodal_xywh[0] + self.bbox_amodal_xywh[2], | |
self.bbox_amodal_xywh[1] + self.bbox_amodal_xywh[3] | |
] | |
def bbox_modal(self): | |
"""Returns the 2d box as [xmin, ymin, xmax, ymax]""" | |
return [ | |
self.bbox_modal_xywh[0], | |
self.bbox_modal_xywh[1], | |
self.bbox_modal_xywh[0] + self.bbox_modal_xywh[2], | |
self.bbox_modal_xywh[1] + self.bbox_modal_xywh[3] | |
] | |
def fromJsonText(self, jsonText, objId=-1): | |
# try to load from cityperson format | |
if 'bbox' in jsonText.keys() and 'bboxVis' in jsonText.keys(): | |
self.bbox_amodal_xywh = jsonText['bbox'] | |
self.bbox_modal_xywh = jsonText['bboxVis'] | |
# both modal and amodal boxes are provided | |
elif "modal" in jsonText.keys() and "amodal" in jsonText.keys(): | |
self.bbox_amodal_xywh = jsonText['amodal'] | |
self.bbox_modal_xywh = jsonText['modal'] | |
# only amodal boxes are provided | |
else: | |
self.bbox_modal_xywh = jsonText['amodal'] | |
self.bbox_amodal_xywh = jsonText['amodal'] | |
# load label and instanceId if available | |
if 'label' in jsonText.keys() and 'instanceId' in jsonText.keys(): | |
self.label = str(jsonText['label']) | |
self.instanceId = jsonText['instanceId'] | |
def toJsonText(self): | |
objDict = {} | |
objDict['label'] = self.label | |
objDict['instanceId'] = self.instanceId | |
objDict['modal'] = self.bbox_modal_xywh | |
objDict['amodal'] = self.bbox_amodal_xywh | |
return objDict | |
class CsBbox3d(CsObject): | |
"""Class that contains the information of a single annotated object as 3D bounding box""" | |
# Constructor | |
def __init__(self): | |
CsObject.__init__(self, CsObjectType.BBOX3D) | |
self.bbox_2d = None | |
self.center = [] | |
self.dims = [] | |
self.rotation = [] | |
self.instanceId = -1 | |
self.label = "" | |
self.score = -1. | |
def __str__(self): | |
bbox2dText = str(self.bbox_2d) | |
bbox3dText = "" | |
bbox3dText += '\n - Center (x/y/z) [m]: {}/{}/{}'.format( | |
self.center[0], self.center[1], self.center[2]) | |
bbox3dText += '\n - Dimensions (l/w/h) [m]: {}/{}/{}'.format( | |
self.dims[0], self.dims[1], self.dims[2]) | |
bbox3dText += '\n - Rotation: {}/{}/{}/{}'.format( | |
self.rotation[0], self.rotation[1], self.rotation[2], self.rotation[3]) | |
text = "Object: {}\n2D {}\n - 3D {}".format( | |
self.label, bbox2dText, bbox3dText) | |
return text | |
def fromJsonText(self, jsonText, objId=-1): | |
# load 2D box | |
self.bbox_2d = CsBbox2d() | |
self.bbox_2d.fromJsonText(jsonText['2d']) | |
self.center = jsonText['3d']['center'] | |
self.dims = jsonText['3d']['dimensions'] | |
self.rotation = jsonText['3d']['rotation'] | |
self.label = jsonText['label'] | |
self.score = jsonText['score'] | |
if 'instanceId' in jsonText.keys(): | |
self.instanceId = jsonText['instanceId'] | |
def toJsonText(self): | |
objDict = {} | |
objDict['label'] = self.label | |
objDict['instanceId'] = self.instanceId | |
objDict['2d']['amodal'] = self.bbox_2d.bbox_amodal_xywh | |
objDict['2d']['modal'] = self.bbox_2d.bbox_modal_xywh | |
objDict['3d']['center'] = self.center | |
objDict['3d']['dimensions'] = self.dims | |
objDict['3d']['rotation'] = self.rotation | |
return objDict | |
def depth(self): | |
# returns the BEV depth | |
return np.sqrt(self.center[0]**2 + self.center[1]**2).astype(int) | |
class CsIgnore2d(CsObject): | |
"""Class that contains the information of a single annotated 2d ignore region""" | |
# Constructor | |
def __init__(self): | |
CsObject.__init__(self, CsObjectType.IGNORE2D) | |
self.bbox_xywh = [] | |
self.label = "" | |
self.instanceId = -1 | |
def __str__(self): | |
bbox2dText = "" | |
bbox2dText += 'Ignore Region: (x1: {}, y1: {}), (w: {}, h: {})'.format( | |
self.bbox_xywh[0], self.bbox_xywh[1], self.bbox_xywh[2], self.bbox_xywh[3]) | |
return bbox2dText | |
def fromJsonText(self, jsonText, objId=-1): | |
self.bbox_xywh = jsonText['2d'] | |
if 'label' in jsonText.keys(): | |
self.label = jsonText['label'] | |
if 'instanceId' in jsonText.keys(): | |
self.instanceId = jsonText['instanceId'] | |
def toJsonText(self): | |
objDict = {} | |
objDict['label'] = self.label | |
objDict['instanceId'] = self.instanceId | |
objDict['2d'] = self.bbox_xywh | |
return objDict | |
def bbox(self): | |
"""Returns the 2d box as [xmin, ymin, xmax, ymax]""" | |
return [ | |
self.bbox_xywh[0], | |
self.bbox_xywh[1], | |
self.bbox_xywh[0] + self.bbox_xywh[2], | |
self.bbox_xywh[1] + self.bbox_xywh[3] | |
] | |
# Extend api to be compatible to bbox2d | |
def bbox_amodal_xywh(self): | |
return self.bbox_xywh | |
def bbox_modal_xywh(self): | |
return self.bbox_xywh | |
class Annotation: | |
"""The annotation of a whole image (doesn't support mixed annotations, i.e. combining CsPoly and CsBbox2d)""" | |
# Constructor | |
def __init__(self, objType=CsObjectType.POLY): | |
# the width of that image and thus of the label image | |
self.imgWidth = 0 | |
# the height of that image and thus of the label image | |
self.imgHeight = 0 | |
# the list of objects | |
self.objects = [] | |
# the camera calibration | |
self.camera = None | |
assert objType in CsObjectType.__dict__.values() | |
self.objectType = objType | |
def toJson(self): | |
return json.dumps(self, default=lambda o: o.__dict__, sort_keys=True, indent=4) | |
def fromJsonText(self, jsonText): | |
jsonDict = json.loads(jsonText) | |
self.imgWidth = int(jsonDict['imgWidth']) | |
self.imgHeight = int(jsonDict['imgHeight']) | |
self.objects = [] | |
# load objects | |
if self.objectType != CsObjectType.IGNORE2D: | |
for objId, objIn in enumerate(jsonDict['objects']): | |
if self.objectType == CsObjectType.POLY: | |
obj = CsPoly() | |
elif self.objectType == CsObjectType.BBOX2D: | |
obj = CsBbox2d() | |
elif self.objectType == CsObjectType.BBOX3D: | |
obj = CsBbox3d() | |
obj.fromJsonText(objIn, objId) | |
self.objects.append(obj) | |
# load ignores | |
if 'ignore' in jsonDict.keys(): | |
for ignoreId, ignoreIn in enumerate(jsonDict['ignore']): | |
obj = CsIgnore2d() | |
obj.fromJsonText(ignoreIn, ignoreId) | |
self.objects.append(obj) | |
# load camera calibration | |
if 'sensor' in jsonDict.keys(): | |
self.camera = Camera(fx=jsonDict['sensor']['fx'], | |
fy=jsonDict['sensor']['fy'], | |
u0=jsonDict['sensor']['u0'], | |
v0=jsonDict['sensor']['v0'], | |
sensor_T_ISO_8855=jsonDict['sensor']['sensor_T_ISO_8855']) | |
def toJsonText(self): | |
jsonDict = {} | |
jsonDict['imgWidth'] = self.imgWidth | |
jsonDict['imgHeight'] = self.imgHeight | |
jsonDict['objects'] = [] | |
for obj in self.objects: | |
objDict = obj.toJsonText() | |
jsonDict['objects'].append(objDict) | |
return jsonDict | |
# Read a json formatted polygon file and return the annotation | |
def fromJsonFile(self, jsonFile): | |
if not os.path.isfile(jsonFile): | |
print('Given json file not found: {}'.format(jsonFile)) | |
return | |
with open(jsonFile, 'r') as f: | |
jsonText = f.read() | |
self.fromJsonText(jsonText) | |
def toJsonFile(self, jsonFile): | |
with open(jsonFile, 'w') as f: | |
f.write(self.toJson()) | |
# a dummy example | |
if __name__ == "__main__": | |
obj = CsPoly() | |
obj.label = 'car' | |
obj.polygon.append(Point(0, 0)) | |
obj.polygon.append(Point(1, 0)) | |
obj.polygon.append(Point(1, 1)) | |
obj.polygon.append(Point(0, 1)) | |
print(type(obj).__name__) | |
print(obj) | |