Skip to content
Merged
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
15 changes: 14 additions & 1 deletion darwin/datatypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -602,7 +602,20 @@ def make_instance_id(value: int) -> SubAnnotation:
return SubAnnotation("instance_id", value)


def make_attributes(attributes: Any) -> SubAnnotation:
def make_attributes(attributes: List[str]) -> SubAnnotation:
"""
Creates and returns an attributes sub-annotation.

Parameters
----------
value: List[str]
A list of attributes. Example: ``["orange", "big"]``.

Returns
-------
SubAnnotation
An attributes ``SubAnnotation``.
"""
return SubAnnotation("attributes", attributes)


Expand Down
97 changes: 83 additions & 14 deletions darwin/importer/formats/superannotate.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
AnnotationFile,
CuboidData,
Point,
SubAnnotation,
make_attributes,
make_bounding_box,
make_cuboid,
make_ellipse,
Expand All @@ -24,6 +26,8 @@
superannotate_export,
)

AttributeGroup = Dict[str, Union[str, int]]


def parse_path(path: Path) -> Optional[AnnotationFile]:
"""
Expand Down Expand Up @@ -158,8 +162,13 @@ def _to_keypoint_annotation(point: Dict[str, Any], classes: List[Dict[str, Any]]
y: float = cast(float, point.get("y"))
class_id: int = cast(int, point.get("classId"))

name = _find_class_name(class_id, classes)
return make_keypoint(f"{name}-point", x, y)
instance_class: Dict[str, Any] = _find_class(class_id, classes)
name: str = str(instance_class.get("name"))
attributes: Optional[SubAnnotation] = _get_attributes(point, instance_class)
subannotations: Optional[List[SubAnnotation]] = None
if attributes:
subannotations = [attributes]
return make_keypoint(f"{name}-point", x, y, subannotations)


def _to_bbox_annotation(bbox: Dict[str, Any], classes: List[Dict[str, Any]]) -> Annotation:
Expand All @@ -170,8 +179,14 @@ def _to_bbox_annotation(bbox: Dict[str, Any], classes: List[Dict[str, Any]]) ->
h: float = abs(cast(float, points.get("y1")) - cast(float, points.get("y2")))
class_id: int = cast(int, bbox.get("classId"))

name = _find_class_name(class_id, classes)
return make_bounding_box(f"{name}-bbox", x, y, w, h)
instance_class: Dict[str, Any] = _find_class(class_id, classes)
name: str = str(instance_class.get("name"))
attributes: Optional[SubAnnotation] = _get_attributes(bbox, instance_class)
subannotations: Optional[List[SubAnnotation]] = None
if attributes:
subannotations = [attributes]

return make_bounding_box(f"{name}-bbox", x, y, w, h, subannotations)


def _to_ellipse_annotation(ellipse: Dict[str, Any], classes: List[Dict[str, Any]]) -> Annotation:
Expand All @@ -181,8 +196,14 @@ def _to_ellipse_annotation(ellipse: Dict[str, Any], classes: List[Dict[str, Any]
ellipse_data: Dict[str, Union[float, Point]] = {"angle": angle, "center": center, "radius": radius}
class_id: int = cast(int, ellipse.get("classId"))

name = _find_class_name(class_id, classes)
return make_ellipse(f"{name}-ellipse", ellipse_data)
instance_class: Dict[str, Any] = _find_class(class_id, classes)
name: str = str(instance_class.get("name"))
attributes: Optional[SubAnnotation] = _get_attributes(ellipse, instance_class)
subannotations: Optional[List[SubAnnotation]] = None
if attributes:
subannotations = [attributes]

return make_ellipse(f"{name}-ellipse", ellipse_data, subannotations)


def _to_cuboid_annotation(cuboid: Dict[str, Any], classes: List[Dict[str, Any]]) -> Annotation:
Expand All @@ -208,37 +229,85 @@ def _to_cuboid_annotation(cuboid: Dict[str, Any], classes: List[Dict[str, Any]])
}
class_id: int = cast(int, cuboid.get("classId"))

name = _find_class_name(class_id, classes)
return make_cuboid(f"{name}-cuboid", cuboid_data)
instance_class: Dict[str, Any] = _find_class(class_id, classes)
name: str = str(instance_class.get("name"))
attributes: Optional[SubAnnotation] = _get_attributes(cuboid, instance_class)
subannotations: Optional[List[SubAnnotation]] = None
if attributes:
subannotations = [attributes]

return make_cuboid(f"{name}-cuboid", cuboid_data, subannotations)


def _to_polygon_annotation(polygon: Dict[str, Any], classes: List[Dict[str, Any]]) -> Annotation:
data: List[float] = cast(List[float], polygon.get("points"))
class_id: int = cast(int, polygon.get("classId"))
name: str = _find_class_name(class_id, classes)
instance_class: Dict[str, Any] = _find_class(class_id, classes)
name: str = str(instance_class.get("name"))
points: List[Point] = _map_to_list(_tuple_to_point, _group_to_list(data, 2, 0))

return make_polygon(f"{name}-polygon", points)
attributes: Optional[SubAnnotation] = _get_attributes(polygon, instance_class)
subannotations: Optional[List[SubAnnotation]] = None
if attributes:
subannotations = [attributes]
return make_polygon(f"{name}-polygon", points, None, subannotations)


def _to_line_annotation(line: Dict[str, Any], classes: List[Dict[str, Any]]) -> Annotation:
data: List[float] = cast(List[float], line.get("points"))
class_id: int = cast(int, line.get("classId"))
name: str = _find_class_name(class_id, classes)
instance_class: Dict[str, Any] = _find_class(class_id, classes)
name: str = str(instance_class.get("name"))
points: List[Point] = _map_to_list(_tuple_to_point, _group_to_list(data, 2, 0))
attributes: Optional[SubAnnotation] = _get_attributes(line, instance_class)
subannotations: Optional[List[SubAnnotation]] = None
if attributes:
subannotations = [attributes]

return make_line(f"{name}-polyline", points)
return make_line(f"{name}-polyline", points, subannotations)


def _find_class_name(class_id: int, classes: List[Dict[str, Any]]) -> str:
def _find_class(class_id: int, classes: List[Dict[str, Any]]) -> Dict[str, Any]:
obj: Optional[Dict[str, Any]] = next((class_obj for class_obj in classes if class_obj.get("id") == class_id), None)

if obj is None:
raise ValueError(
f"No class with id '{class_id}' was found in {classes}.\nCannot continue import, pleaase check your 'classes.json' file."
)

return str(obj.get("name"))
return obj


def _get_attributes(instance: Dict[str, Any], instance_class: Dict[str, Any]) -> Optional[SubAnnotation]:
attribute_info: List[Dict[str, int]] = cast(List[Dict[str, int]], instance.get("attributes"))
groups: List[Dict[str, Any]] = cast(List[Dict[str, Any]], instance_class.get("attribute_groups"))
all_attributes: List[str] = []

for info in attribute_info:
info_group_id: int = cast(int, info.get("groupId"))
attribute_id: int = cast(int, info.get("id"))

for group in groups:
group_id: int = cast(int, group.get("id"))

if info_group_id != group_id:
continue

group_attributes: List[AttributeGroup] = cast(List[AttributeGroup], group.get("attributes"))
attribute: Optional[AttributeGroup] = next(
(attribute for attribute in group_attributes if attribute.get("id") == attribute_id), None
)

if attribute is None:
raise ValueError(f"No attribute data found for {info}.")

final_attribute: str = f"{str(group.get('name'))}:{str(attribute.get('name'))}"
all_attributes.append(final_attribute)

if all_attributes == []:
return None

return make_attributes(all_attributes)


def _get_class(annotation: Annotation) -> AnnotationClass:
Expand Down
82 changes: 66 additions & 16 deletions darwin/importer/formats/superannotate_schemas.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,33 @@
attributes = {
"type": "array",
"items": {
"type": "object",
"properties": {"id": {"type": "integer"}, "groupId": {"type": "integer"}},
"required": ["id", "groupId"],
},
}

bbox = {
"$id": "https://darwin.v7labs.com/schemas/supperannotate/bounding_box",
"description": "Schema of a Bounding Box",
"title": "Bounding Box",
"default": {"type": "bbox", "points": {"x1": 1223.1, "x2": 1420.2, "y1": 607.3, "y2": 1440,}, "classId": 1,},
"examples": [{"type": "bbox", "points": {"x1": 587.5, "x2": 1420.2, "y1": 607.3, "y2": 1440,}, "classId": 1,}],
"default": {
"type": "bbox",
"points": {"x1": 1223.1, "x2": 1420.2, "y1": 607.3, "y2": 1440,},
"classId": 1,
"attributes": [],
},
"examples": [
{
"type": "bbox",
"points": {"x1": 587.5, "x2": 1420.2, "y1": 607.3, "y2": 1440,},
"classId": 1,
"attributes": [{"id": 1, "groupId": 2}],
}
],
"type": "object",
"properties": {
"attributes": attributes,
"classId": {"type": "integer"},
"type": {"enum": ["bbox"]},
"points": {
Expand All @@ -19,7 +41,7 @@
"required": ["x1", "x2", "y1", "y2"],
},
},
"required": ["points", "type", "classId"],
"required": ["points", "type", "classId", "attributes"],
}

polygon = {
Expand All @@ -28,16 +50,17 @@
"title": "Polygon",
"default": {"type": "polygon", "points": [1, 2, 3, 4], "classId": 1},
"examples": [
{"type": "polygon", "points": [1, 2, 3, 4], "classId": 1},
{"type": "polygon", "points": [], "classId": 1},
{"type": "polygon", "points": [1, 2, 3, 4], "classId": 1, "attributes": [{"id": 1, "groupId": 2}]},
{"type": "polygon", "points": [], "classId": 1, "attributes": []},
],
"type": "object",
"properties": {
"attributes": attributes,
"classId": {"type": "integer"},
"points": {"type": "array", "items": {"type": "number"}},
"type": {"enum": ["polygon"]},
},
"required": ["points", "type", "classId"],
"required": ["points", "type", "classId", "attributes"],
}

polyline = {
Expand All @@ -46,16 +69,17 @@
"title": "Polyline",
"default": {"type": "polyline", "points": [1, 2, 3, 4], "classId": 1},
"examples": [
{"type": "polyline", "points": [1, 2, 3, 4], "classId": 1},
{"type": "polyline", "points": [], "classId": 1},
{"type": "polyline", "points": [1, 2, 3, 4], "classId": 1, "attributes": [{"id": 1, "groupId": 2}]},
{"type": "polyline", "points": [], "classId": 1, "attributes": []},
],
"type": "object",
"properties": {
"attributes": attributes,
"classId": {"type": "integer"},
"points": {"type": "array", "items": {"type": "number"}},
"type": {"enum": ["polyline"]},
},
"required": ["points", "type", "classId"],
"required": ["points", "type", "classId", "attributes"],
}

cuboid = {
Expand All @@ -71,6 +95,7 @@
"r2": {"x": 1603.4, "y": 1440},
},
"classId": 1,
"attributes": [{"id": 1, "groupId": 2}],
},
"examples": [
{
Expand All @@ -82,10 +107,12 @@
"r2": {"x": 1603.4, "y": 1440},
},
"classId": 1,
"attributes": [],
}
],
"type": "object",
"properties": {
"attributes": attributes,
"classId": {"type": "integer"},
"type": {"enum": ["cuboid"]},
"points": {
Expand Down Expand Up @@ -115,19 +142,38 @@
"required": ["f1", "f2", "r1", "r2"],
},
},
"required": ["points", "type", "classId"],
"required": ["points", "type", "classId", "attributes"],
}

ellipse = {
"$id": "https://darwin.v7labs.com/schemas/supperannotate/ellipse",
"description": "Schema of an Ellipse",
"title": "Ellipse",
"default": {"type": "ellipse", "cx": 377.46, "cy": 806.18, "rx": 316.36, "ry": 134.18, "angle": 0, "classId": 1},
"default": {
"type": "ellipse",
"cx": 377.46,
"cy": 806.18,
"rx": 316.36,
"ry": 134.18,
"angle": 0,
"classId": 1,
"attributes": [],
},
"examples": [
{"type": "ellipse", "cx": 377.46, "cy": 806.18, "rx": 316.36, "ry": 134.18, "angle": 14.66, "classId": 1}
{
"type": "ellipse",
"cx": 377.46,
"cy": 806.18,
"rx": 316.36,
"ry": 134.18,
"angle": 14.66,
"classId": 1,
"attributes": [{"id": 1, "groupId": 2}],
}
],
"type": "object",
"properties": {
"attributes": attributes,
"classId": {"type": "integer"},
"cx": {"type": "number"},
"cy": {"type": "number"},
Expand All @@ -136,23 +182,27 @@
"angle": {"type": "number"},
"type": {"enum": ["ellipse"]},
},
"required": ["cx", "cy", "rx", "ry", "angle", "type", "classId"],
"required": ["cx", "cy", "rx", "ry", "angle", "type", "classId", "attributes"],
}

point = {
"$id": "https://darwin.v7labs.com/schemas/supperannotate/point",
"description": "Schema of a Point",
"title": "Point",
"default": {"type": "point", "x": 1.2, "y": 2.5, "classId": 1},
"examples": [{"type": "point", "x": 1.2, "y": 2.5, "classId": 1}, {"type": "point", "x": 0, "y": 1, "classId": 2}],
"default": {"type": "point", "x": 1.2, "y": 2.5, "classId": 1, "attributes": []},
"examples": [
{"type": "point", "x": 1.2, "y": 2.5, "classId": 1, "attributes": []},
{"type": "point", "x": 0, "y": 1, "classId": 2, "attributes": [{"id": 1, "groupId": 2}]},
],
"type": "object",
"properties": {
"attributes": attributes,
"classId": {"type": "integer"},
"x": {"type": "number"},
"y": {"type": "number"},
"type": {"enum": ["point"]},
},
"required": ["x", "y", "type", "classId"],
"required": ["x", "y", "type", "classId", "attributes"],
}


Expand Down
16 changes: 9 additions & 7 deletions darwin/importer/importer.py
Original file line number Diff line number Diff line change
Expand Up @@ -273,17 +273,19 @@ def _handle_subs(
if sub.annotation_type == "text":
data["text"] = {"text": sub.data}
elif sub.annotation_type == "attributes":
data["attributes"] = {
"attributes": [
attributes[annotation_class_id][attr]
for attr in sub.data
if annotation_class_id in attributes and attr in attributes[annotation_class_id]
]
}
attributes_with_key = []
for attr in sub.data:
if annotation_class_id in attributes and attr in attributes[annotation_class_id]:
attributes_with_key.append(attributes[annotation_class_id][attr])
else:
print(f"The attribute '{attr}' for class '{annotation.annotation_class.name}' was not imported.")

data["attributes"] = {"attributes": attributes_with_key}
elif sub.annotation_type == "instance_id":
data["instance_id"] = {"value": sub.data}
else:
data[sub.annotation_type] = sub.data

return data


Expand Down
Loading