OpenLABEL format
OpenLABEL is a standardized annotation format developed by ASAM. It comes with in-depth documentation of the format as well as a json schema, which can be used to ensure that the format is valid.
Even though OpenLABEL is a strict format there is still some room for interpretation. In this section we try to clarify some of these parts and explain the choices that we have made within the standard.
Rotation of Cuboids
The rotation is such that the y-axis is facing forwards, with a rotation order of XYZ
.
This means that a cuboid with a heading (yaw) equal to 0 is aligned with the y-axis in the positive direction along the axis.
This is somewhat different compared to the
ISO 8855
standard, where the forward direction is along the x-axis. Conversion to ISO 8855 can then be done by applying a
rotation around the z-axis and changing sx
and sy
in the following way
import math
from typing import List
from scipy.spatial.transform import Rotation
def convert_to_iso8855(val: List[float]) -> List[float]:
""" Converts cuboid values to ISO 8855 """
[x, y, z, qx, qy, qz, qw, sx, sy, sz] = val
rotation_1 = Rotation.from_quat([qx, qy, qz, qw])
rotation_2 = Rotation.from_rotvec([0, 0, math.pi / 2])
rot_object = rotation_1 * rotation_2
[qx, qy, qz, qw] = rot_object.as_quat()
return [x, y, z, qx, qy, qz, qw, sy, sx, sz]
Non-sequences are sequences with one frame
Due to reasons of simplicity we have made the choice to treat non-sequences in the same way as sequences. This
means that non-sequences are represented as a sequence with only one frame. Only data such as name
and type
are defined
in the top level element keys. All other information is stored under frames, see example below
{
"objects": {
"0": {
"name": "car-0",
"type": "Car"
}
},
"frames": {
"0": {
"objects": {
"0": {"object_data": {...}}
}
}
}
}
Stream is just another text property
The stream
property is used to indicate which stream/sensor/source that the geometry och property was annotated in.
For example here is an object with a point that has been annotated in a stream with the name Camera
. Note that all
corresponding attributes for the geometry have also been annotated in the same stream.
{
"object_data": {
"point2d": [
{
"name": "point-4d2d325f",
"val": [300.5300, 286.4396],
"attributes": {
"text": [
{"name": "stream", "val": "Camera"},
{"name": "Color", "val": "Black"}
]
}
}
]
}
}
Relations
Some changes were made regarding how to represent certain types of relations on 2022-04-08. Contact Kognic in case your annotations were produced before this date, but you wish to include these changes anyways.
We consider two types of relations; unidirectional relations between two objects and group relations.
In addition to these, there is a need to represent false relations, i.e. relation properties that are not actually
pointers to other objects but rather take values such as Inconclusive
, Nothing
or Unclear
.
Relations are unidirectional
Relations are unidirectional, meaning that if an object, object1
, has a relation to another object, object2
, it does
not mean that object2
has a relation to object1
. Below follows an example where car-0
is following car-1
and
it is unclear whether car-2
is following another car or not.
Representing false relations using the relation uid is deprecated and has moved to the use of actions (see the next section)
{
"objects": {
"0": {"name": "car-0", "type": "Car"},
"1": {"name": "car-1", "type": "Car"},
"2": {"name": "car-2", "type": "Car"}
},
"relations": {
"0": {
"name": "0",
"type": "isFollowing",
"rdf_subjects": [{"type": "object", "uid": "0"}],
"rdf_objects": [{"type": "object", "uid": "1"}]
},
"1": {
"name": "1",
"type": "isFollowing",
"rdf_subjects": [{"type": "object", "uid": "2"}],
"rdf_objects": [{"type": "object", "uid": "Unclear"}]
}
}
}
Actions are used to represent false relations (new since 2022-04-08)
In the Kognic Platform, there is support for assigning values to relations that are not actually references to other
objects. Examples are Inconclusive
and Nothing
. Actions are used to represent these in the following way, where the
name of the action determines the value and the type determines the property name.
{
"objects": {
"0": {"name": "lane-0", "type": "Lane"}
},
"relations": {
"0": {
"name": "0",
"type": "isSubjectOfAction",
"rdf_subjects": [{"type": "object", "uid": "0"}],
"rdf_objects": [{"type": "action", "uid": "0"}]
}
},
"actions": {
"0": {"name": "Nothing", "type": "is_pulling_or_pushing"}
}
}
Groups are represented as actions
The group concept has been deprecated in favor of single relations between objects. This means that annotations produced after 2022-04-08 will no longer contain the group concept
Group relations are relations where objects can be seen as belonging to a group. There is then a need for an abstract
concept that describes the group. OpenLABEL suggests the use of actions for this in such a way that each object in the
group has a relation of type isSubjectOfAction
to this action. Below follows an example where two lane-0
and lane-1
belong to the same road, while it is unclear whether lane-2
belongs to a road.
{
"objects": {
"0": {"name": "lane-0", "type": "Lane"},
"1": {"name": "lane-1", "type": "Lane"},
"2": {"name": "lane-2", "type": "Lane"}
},
"relations": {
"0": {
"name": "0",
"type": "isSubjectOfAction",
"rdf_subjects": [{"type": "object", "uid": "0"}],
"rdf_objects": [{"type": "action", "uid": "0"}]
},
"1": {
"name": "1",
"type": "isSubjectOfAction",
"rdf_subjects": [{"type": "object", "uid": "1"}],
"rdf_objects": [{"type": "action", "uid": "0"}]
},
"2": {
"name": "2",
"type": "isSubjectOfAction",
"rdf_subjects": [{"type": "object", "uid": "1"}],
"rdf_objects": [{"type": "action", "uid": "1"}]
}
},
"actions": {
"0": {"name": "", "type": "Road"},
"1": {"name": "Unclear", "type": "Road"}
}
}
Stream specific relations
If a relation is stream specific, there will be a property stream_relations
denoting which stream the list of relations belong to.
{ // frames.0
// ...
"frame_properties": {
"streams": {
"CAMERA_FRONT": {
"description": null,
"stream_properties": {
"stream_relations": {
"1": {}
}
}
}
}
},
"relations": {
"0": {}
}
}
Representing polygons
Polygons are described by a list of Poly2d
objects in OpenLABEL. One of these represents the exterior while the others
represent potential holes and this is determined by the boolean property is_hole
. Below follows an example of a polygon
with one hole.
{
"object_data": {
"poly2d": [
{
"name": "poly1",
"mode": "MODE_POLY2D_ABSOLUTE",
"val": [...],
"attributes": { "boolean": [{"name": "is_hole", "val": false}] }
},
{
"name": "poly2",
"mode": "MODE_POLY2D_ABSOLUTE",
"val": [...],
"attributes": { "boolean": [{"name": "is_hole", "val": true}] }
}
]
}
}
The value MODE_POLY2D_ABSOLUTE
is the only supported value for mode
. Absolute mode means that the values in val
are interpreted as pixel coordinates (not as values relative to the first coordinate pair).
Representing multi-polygons
Multi-polygons are simply lists of polygons, so we describe these in a similar way with lists
of Poly2d
objects with the property is_hole
. However, we also add one additional property polygon_id
that
determines which polygon a Poly2d
object belongs to in the multi-polygon. Below follows an example of a multi-polygon
with two polygons with one hole each.
{
"object_data": {
"poly2d": [
{
"name": "poly1",
"mode": "MODE_POLY2D_ABSOLUTE",
"val": [...],
"attributes": {
"text": [{"name": "polygon_id", "val": "1"}],
"boolean": [{"name": "is_hole", "val": false}]
}
},
{
"name": "poly2",
"mode": "MODE_POLY2D_ABSOLUTE",
"val": [...],
"attributes": {
"text": [{"name": "polygon_id", "val": "1"}],
"boolean": [{"name": "is_hole", "val": true}]
}
},
{
"name": "poly3",
"mode": "MODE_POLY2D_ABSOLUTE",
"val": [...],
"attributes": {
"text": [{"name": "polygon_id", "val": "2"}],
"boolean": [{"name": "is_hole", "val": false}]
}
},
{
"name": "poly4",
"mode": "MODE_POLY2D_ABSOLUTE",
"val": [...],
"attributes": {
"text": [{"name": "polygon_id", "val": "2"}],
"boolean": [{"name": "is_hole", "val": true}]
}
}
]
}
}
The value MODE_POLY2D_ABSOLUTE
is the only supported value for mode
. Absolute mode means that the values in val
are interpreted as pixel coordinates (not as values relative to the first coordinate pair).
Representing curves
The name of the interpolation method has changed from interpolation-method
to interpolation_method
. However, old annotations
might still contain the old name
Curves are represented using the poly2d
geometry and the interpolation method is specified as a text property in
the following way.
{
"poly2d": [
{
"closed": false,
"mode": "MODE_POLY2D_ABSOLUTE",
"name": "curve-d633ca89",
"val": [...],
"attributes": {
"text": [
{
"name": "interpolation_method",
"val": "natural-cubic-spline"
}
]
}
}
]
}
The value MODE_POLY2D_ABSOLUTE
is the only supported value for mode
. Absolute mode means that the values in val
are interpreted as pixel coordinates (not as values relative to the first coordinate pair).
The property interpolation_method
is mandatory and determines how the nodes should be associated to each other. The following values are supported:
natural-cubic-spline
catmull-rom-0.5
polyline
Representing 3D lanes
A 3D lane is represented as two lines in 3D (poly3d
), one to the right and the other to the left. The text property
lane_edge
determines whether the line is to the right or to the left. The lines will always have closed set to false.
{
"object_data": {
"poly3d": [
{
"attributes": {
"text": [
{ "name": "lane_edge", "val": "left" },
{ "name": "stream", "val": "lidar" }
]
},
"closed": false,
"name": "",
"val": [
1.2647494200238287, -51.51747573498745, -2.315540290283199,
1.0807419132566136, -48.91298533071834, -2.313640304199211,
-0.0892715141237751, -34.705936676401016, -2.235569814758307,
-0.4442893388935316, -29.60917111552865, -2.1894531147766174,
-1.0952988968721313, -17.193981050037397, -2.1397902661132875
]
},
{
"attributes": {
"text": [
{ "name": "lane_edge", "val": "right" },
{ "name": "stream", "val": "lidar" }
]
},
"closed": false,
"name": "",
"val": [
1.5845765823868767, -51.49487958011918, -2.315540290283199,
1.4004322100638888, -48.888528958803036, -2.313640304199211,
0.23043085215069048, -34.68163859008775, -2.235569814758307,
-0.12426061849402326, -29.589636067040036, -2.1894531147766174
]
}
]
}
}
Representing 2D points
A 2D point is represented as a single point2d. Each point2d
has an optional point_class
attribute.
For single points this may be ommited, but if set it must be equal to the type
of the object.
This attribute is reserved for future use on other point-based geometries.
{
"openlabel": {
...,
"frames": {
"0": {
"objects": {
"a940239d-ff27-4480-8294-c482977a1b32": {
"object_data": {
"point2d": [
{
"attributes": {
"text": [
{ "name": "point_class", "val": "APoint" },
{ "name": "stream", "val": "stream1" }
]
},
...
}
]
}
},
"e027e626-eb7a-4a8e-a9ae-083464e137d1": {
"object_data": {
"point2d": [
{
"attributes": {
"text": [
{ "name": "stream", "val": "stream1" }
]
},
....
}
]
}
}
}
}
},
"metadata": {...},
"objects": {
"a940239d-ff27-4480-8294-c482977a1b32": {
...
"type": "APoint"
},
"e027e626-eb7a-4a8e-a9ae-083464e137d1": {
...
"type": "AnotherPoint"
}
},
...
}
}
Representing groups of 2d points
A group of points is used when multiple points refere to the same object.
The attribute point_class
is required for each of the points in the point group, and the point_class
has to be different from the object type.
The point_class
value "line_reference_point"
is reserved for future use cases.
Representing Geometry Collections
Introduced in kognic_format_version
2.2
Related documentation for the task view https://docs.kognic.com/class-groups
A collection of geometries as described in the link above will be represented as having a reserved relation as type geometry_collection
.
{
"openlabel": {
"frames": {
"0": {
"objects": {
"516b6045-87e8-40e4-a104-5eaa600e8e3a": {
"object_data": {
"bbox": [
{
"name": "bbox-abc123",
"val": [
...
]
}
]
}
},
"fe07e9cf-f42c-4b48-b4d8-bab75b7e9827": {
"object_data": {
"poly2d": [
{
"name": "curve-abc123",
"val": [
...
]
}
]
}
},
"329508b7-729c-4298-8141-f329dbc32ad0": {
"object_data": {
"poly2d": [
{
"name": "curve-abc123",
"val": [
...
]
}
]
}
},
"4c321584-0e88-4578-b0f0-b5e8c974244b": {
"object_data": {
"text": [
{
"name": "lane",
"val": "right"
}
]
}
}
}
}
},
"metadata": {
"schema_version": "1.0.0",
"kognic_format_version": "2.2",
"uuid": "63698712-b18e-426b-9ad5-1b178cc29838"
},
"objects": {
"516b6045-87e8-40e4-a104-5eaa600e8e3a": {
"name": "516b6045-87e8-40e4-a104-5eaa600e8e3a",
"object_data": {
...
},
"type": "some_bbox"
},
"fe07e9cf-f42c-4b48-b4d8-bab75b7e9827": {
"name": "fe07e9cf-f42c-4b48-b4d8-bab75b7e9827",
"object_data": {
...
},
"type": "some_line"
},
"329508b7-729c-4298-8141-f329dbc32ad0": {
"name": "329508b7-729c-4298-8141-f329dbc32ad0",
"object_data": {
...
},
"type": "some_line"
},
"4c321584-0e88-4578-b0f0-b5e8c974244b": {
"name": "4c321584-0e88-4578-b0f0-b5e8c974244b",
"object_data": {
...
},
"type": "some_collection"
}
},
"relations": {
"0": {
"name": "0",
"rdf_objects": [
{
"uid": "516b6045-87e8-40e4-a104-5eaa600e8e3a",
"type": "object"
},
{
"uid": "fe07e9cf-f42c-4b48-b4d8-bab75b7e9827",
"type": "object"
},
{
"uid": "329508b7-729c-4298-8141-f329dbc32ad0",
"type": "object"
}
],
"rdf_subjects": [
{
"uid": "4c321584-0e88-4578-b0f0-b5e8c974244b",
"type": "object"
}
],
"type": "geometry_collection"
}
}
}
}