Skip to content

Commit

Permalink
Add skeletons in datumaro format (#47)
Browse files Browse the repository at this point in the history
- Added support for import and export of skeletons in Datumaro format
- Added tests
  • Loading branch information
Eldies committed Jul 12, 2024
1 parent eea5fd3 commit 2a4d9db
Show file tree
Hide file tree
Showing 7 changed files with 331 additions and 10 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
(<https://github.com/cvat-ai/datumaro/pull/39>)
- An option to specify scale factor in `resize` transform
(<https://github.com/cvat-ai/datumaro/pull/46>)
- Skeleton support in datumaro format
(<https://github.com/cvat-ai/datumaro/pull/47>)

### Changed
- `env.detect_dataset()` now returns a list of detected formats at all recursion levels
Expand Down
8 changes: 5 additions & 3 deletions datumaro/components/annotation.py
Original file line number Diff line number Diff line change
Expand Up @@ -708,9 +708,11 @@ class Category:
@classmethod
def from_iterable(
cls,
iterable: Union[
Tuple[int, List[str]],
Tuple[int, List[str], Set[Tuple[int, int]]],
iterable: Iterable[
Union[
Tuple[int, List[str]],
Tuple[int, List[str], Set[Tuple[int, int]]],
],
],
) -> PointsCategories:
"""
Expand Down
41 changes: 38 additions & 3 deletions datumaro/plugins/datumaro_format/converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

from datumaro.components.annotation import (
Annotation,
AnnotationType,
Bbox,
Caption,
Cuboid3d,
Expand All @@ -28,11 +29,12 @@
Polygon,
PolyLine,
RleMask,
Skeleton,
_Shape,
)
from datumaro.components.converter import Converter
from datumaro.components.dataset import ItemStatus
from datumaro.components.extractor import DEFAULT_SUBSET_NAME, DatasetItem
from datumaro.components.extractor import DEFAULT_SUBSET_NAME, CategoriesInfo, DatasetItem
from datumaro.components.media import Image, MediaElement, PointCloud
from datumaro.util import cast, dump_json_file

Expand Down Expand Up @@ -143,11 +145,13 @@ def add_item(self, item: DatasetItem):
converted_ann = self._convert_caption_object(ann)
elif isinstance(ann, Cuboid3d):
converted_ann = self._convert_cuboid_3d_object(ann)
elif isinstance(ann, Skeleton):
converted_ann = self._convert_skeleton_object(ann)
else:
raise NotImplementedError()
annotations.append(converted_ann)

def add_categories(self, categories):
def add_categories(self, categories: CategoriesInfo):
for ann_type, desc in categories.items():
if isinstance(desc, LabelCategories):
converted_desc = self._convert_label_categories(desc)
Expand All @@ -162,7 +166,7 @@ def add_categories(self, categories):
def write(self, ann_file):
dump_json_file(ann_file, self._data)

def _convert_annotation(self, obj):
def _convert_annotation(self, obj: Annotation) -> dict:
assert isinstance(obj, Annotation)

ann_json = {
Expand Down Expand Up @@ -266,6 +270,37 @@ def _convert_cuboid_3d_object(self, obj):
)
return converted

def _convert_skeleton_object(self, obj: Skeleton) -> dict:
label_ordering = [
item["labels"]
for item in self.categories[AnnotationType.points.name]["items"]
if item["label_id"] == obj.label
][0]

def get_label_position(label_id):
return label_ordering.index(
self.categories[AnnotationType.label.name]["labels"][label_id]["name"]
)

points = [0.0, 0.0, Points.Visibility.absent.value] * len(label_ordering)
points_attributes = [{}] * len(label_ordering)
for element in obj.elements:
assert len(element.points) == 2 and len(element.visibility) == 1
position = get_label_position(element.label)
points[position * 3 : position * 3 + 3] = [
element.points[0],
element.points[1],
element.visibility[0].value,
]
points_attributes[position] = element.attributes

return dict(
self._convert_annotation(obj),
label_id=cast(obj.label, int),
points=points,
points_attributes=points_attributes,
)

def _convert_attribute_categories(self, attributes):
return sorted(attributes)

Expand Down
52 changes: 48 additions & 4 deletions datumaro/plugins/datumaro_format/extractor.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
# SPDX-License-Identifier: MIT

import os.path as osp
from typing import List, Union

from datumaro.components.annotation import (
AnnotationType,
Expand All @@ -17,12 +18,13 @@
Polygon,
PolyLine,
RleMask,
Skeleton,
)
from datumaro.components.errors import DatasetImportError
from datumaro.components.errors import DatasetImportError, InvalidAnnotationError
from datumaro.components.extractor import DatasetItem, Importer, SourceExtractor
from datumaro.components.format_detection import FormatDetectionContext
from datumaro.components.media import Image, MediaElement, PointCloud
from datumaro.util import parse_json, parse_json_file
from datumaro.util import parse_json, parse_json_file, take_by

from .format import DatumaroPath

Expand Down Expand Up @@ -150,8 +152,7 @@ def _load_items(self, parsed):

return items

@staticmethod
def _load_annotations(item):
def _load_annotations(self, item: dict):
parsed = item["annotations"]
loaded = []

Expand Down Expand Up @@ -251,11 +252,54 @@ def _load_annotations(item):
)
)

elif ann_type == AnnotationType.skeleton:
loaded.append(
Skeleton(
elements=self._load_skeleton_elements_annotations(ann, label_id, points),
label=label_id,
id=ann_id,
attributes=attributes,
group=group,
z_order=z_order,
)
)

else:
raise NotImplementedError()

return loaded

def _load_skeleton_elements_annotations(
self, ann: dict, label_id: int, points: List[Union[float, int]]
) -> List[Points]:
if len(points) % 3 != 0:
raise InvalidAnnotationError(
f"Points have invalid value count {len(points)}, "
"which is not divisible by 3. Expected (x, y, visibility) triplets."
)
points_attributes = ann.get("points_attributes")
if len(points) != len(points_attributes) * 3:
raise InvalidAnnotationError(
f"Points and Points_attributes lengths ({len(points)}, {len(points_attributes)}) do not match, "
"for each triplet (x, y, visibility) in points there should be one dict in points_attributes."
)

label_category = self._categories[AnnotationType.label]
sub_labels = self._categories[AnnotationType.points].items[label_id].labels
return [
Points(
points=[x, y],
visibility=[v],
label=label_category.find(
name=sub_label, parent=label_category.items[label_id].name
)[0],
attributes=attrs,
)
for (x, y, v), sub_label, attrs in zip(
take_by(points, 3), sub_labels, points_attributes
)
]


class DatumaroImporter(Importer):
@classmethod
Expand Down
100 changes: 100 additions & 0 deletions tests/assets/datumaro_dataset/with_skeleton/annotations/default.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
{
"info": {},
"categories": {
"label": {
"labels": [
{
"name": "skeleton-label",
"parent": "",
"attributes": []
},
{
"name": "point1-label",
"parent": "skeleton-label",
"attributes": []
},
{
"name": "point3-label",
"parent": "skeleton-label",
"attributes": []
},
{
"name": "point2-label",
"parent": "skeleton-label",
"attributes": []
}
],
"attributes": [
"occluded",
"point2-attribute-text",
"point3-attribute-checkbox"
]
},
"points": {
"items": [
{
"label_id": 0,
"labels": [
"point1-label",
"point3-label",
"point2-label"
],
"joints": [
[
2,
3
],
[
1,
2
]
]
}
]
}
},
"items": [
{
"id": "100",
"annotations": [
{
"id": 0,
"type": "skeleton",
"attributes": {
"occluded": false,
"keyframe": false
},
"group": 0,
"label_id": 0,
"points": [
0.9,
3.53,
2,
2.45,
7.6,
2,
5.2,
2.5,
2
],
"points_attributes": [
{},
{
"point3-attribute-checkbox": true
},
{
"point2-attribute-text": "some text"
}
]
}
],
"image": {
"path": "100.jpg",
"size": [
10,
6
]
}
}
]
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 2a4d9db

Please sign in to comment.