Skip to content

Commit 136694c

Browse files
itrushkinwonjuleee
andauthored
Add Cuboid2D annotation (#1601)
<!-- Contributing guide: https://github.com/openvinotoolkit/datumaro/blob/develop/CONTRIBUTING.md --> ### Summary This introduces the new annotation type for 3D bounding box. This annotation is added to the Datumaro format. <!-- Resolves #111 and #222. Depends on #1000 (for series of dependent commits). This PR introduces this capability to make the project better in this and that. - Added this feature - Removed that feature - Fixed the problem #1234 --> ### How to test <!-- Describe the testing procedure for reviewers, if changes are not fully covered by unit tests or manual testing can be complicated. --> ### Checklist <!-- Put an 'x' in all the boxes that apply --> - [x] I have added unit tests to cover my changes.​ - [ ] I have added integration tests to cover my changes.​ - [ ] I have added the description of my changes into [CHANGELOG](https://github.com/openvinotoolkit/datumaro/blob/develop/CHANGELOG.md).​ - [ ] I have updated the [documentation](https://github.com/openvinotoolkit/datumaro/tree/develop/docs) accordingly ### License - [x] I submit _my code changes_ under the same [MIT License](https://github.com/openvinotoolkit/datumaro/blob/develop/LICENSE) that covers the project. Feel free to contact the maintainers if that's a concern. - [ ] I have updated the license header for each file (see an example below). ```python # Copyright (C) 2024 Intel Corporation # # SPDX-License-Identifier: MIT ``` --------- Signed-off-by: Ilya Trushkin <[email protected]> Co-authored-by: Wonju Lee <[email protected]>
1 parent dc63222 commit 136694c

File tree

12 files changed

+208
-11
lines changed

12 files changed

+208
-11
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99
### New features
1010
- Add a new CLI command: datum format
1111
(<https://github.com/openvinotoolkit/datumaro/pull/1570>)
12+
- Add a new Cuboid2D annotation type
13+
(<https://github.com/openvinotoolkit/datumaro/pull/1601>)
1214
- Support language dataset for DmTorchDataset
1315
(<https://github.com/openvinotoolkit/datumaro/pull/1592>)
1416

src/datumaro/components/annotation.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ class AnnotationType(IntEnum):
5050
feature_vector = 13
5151
tabular = 14
5252
rotated_bbox = 15
53+
cuboid_2d = 16
5354

5455

5556
COORDINATE_ROUNDING_DIGITS = 2
@@ -1363,6 +1364,41 @@ def wrap(item, **kwargs):
13631364
return attr.evolve(item, **d)
13641365

13651366

1367+
@attrs(slots=True, init=False, order=False)
1368+
class Cuboid2D(Annotation):
1369+
"""
1370+
Cuboid2D annotation class. This class represents a 3D bounding box defined by its point coordinates
1371+
in the following way:
1372+
[(x1, y1), (x2, y2), (x3, y3), (x4, y4), (x5, y5), (x6, y6), (x7, y7), (x8, y8)].
1373+
1374+
1375+
6---7
1376+
/| /|
1377+
5-+-8 |
1378+
| 2 + 3
1379+
|/ |/
1380+
1---4
1381+
1382+
Attributes:
1383+
_type (AnnotationType): The type of annotation, set to `AnnotationType.bbox`.
1384+
1385+
Methods:
1386+
__init__: Initializes the Cuboid2D with its coordinates.
1387+
wrap: Creates a new Bbox instance with updated attributes.
1388+
"""
1389+
1390+
_type = AnnotationType.cuboid_2d
1391+
points = field(default=None)
1392+
label: Optional[int] = field(
1393+
converter=attr.converters.optional(int), default=None, kw_only=True
1394+
)
1395+
z_order: int = field(default=0, validator=default_if_none(int), kw_only=True)
1396+
1397+
def __init__(self, _points: Iterable[Tuple[float, float]], *args, **kwargs):
1398+
kwargs.pop("points", None) # comes from wrap()
1399+
self.__attrs_init__(points=_points, *args, **kwargs)
1400+
1401+
13661402
@attrs(slots=True, order=False)
13671403
class PointsCategories(Categories):
13681404
"""

src/datumaro/components/annotations/matcher.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
"ImageAnnotationMatcher",
3636
"HashKeyMatcher",
3737
"FeatureVectorMatcher",
38+
"Cuboid2DMatcher",
3839
]
3940

4041

@@ -378,3 +379,8 @@ def distance(self, a, b):
378379
b = Points([p for pt in b.as_polygon() for p in pt])
379380

380381
return OKS(a, b, sigma=self.sigma)
382+
383+
384+
@attrs
385+
class Cuboid2DMatcher(ShapeMatcher):
386+
pass

src/datumaro/components/annotations/merger.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
AnnotationMatcher,
1313
BboxMatcher,
1414
CaptionsMatcher,
15+
Cuboid2DMatcher,
1516
Cuboid3dMatcher,
1617
FeatureVectorMatcher,
1718
HashKeyMatcher,
@@ -210,3 +211,8 @@ class TabularMerger(AnnotationMerger, TabularMatcher):
210211
@attrs
211212
class RotatedBboxMerger(_ShapeMerger, RotatedBboxMatcher):
212213
pass
214+
215+
216+
@attrs
217+
class Cuboid2DMerger(_ShapeMerger, Cuboid2DMatcher):
218+
pass

src/datumaro/components/merge/intersect_merge.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
AnnotationMerger,
2020
BboxMerger,
2121
CaptionsMerger,
22+
Cuboid2DMerger,
2223
Cuboid3dMerger,
2324
EllipseMerger,
2425
FeatureVectorMerger,
@@ -455,6 +456,8 @@ def _for_type(t, **kwargs):
455456
return _make(TabularMerger, **kwargs)
456457
elif t is AnnotationType.rotated_bbox:
457458
return _make(RotatedBboxMerger, **kwargs)
459+
elif t is AnnotationType.cuboid_2d:
460+
return _make(Cuboid2DMerger, **kwargs)
458461
else:
459462
raise NotImplementedError("Type %s is not supported" % t)
460463

src/datumaro/components/visualizer.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
AnnotationType,
2020
Bbox,
2121
Caption,
22+
Cuboid2D,
2223
Cuboid3d,
2324
DepthAnnotation,
2425
Ellipse,
@@ -661,6 +662,39 @@ def _draw_cuboid_3d(
661662
) -> None:
662663
raise NotImplementedError(f"{ann.type} is not implemented yet.")
663664

665+
def _draw_cuboid_2d(
666+
self,
667+
ann: Cuboid2D,
668+
label_categories: Optional[LabelCategories],
669+
fig: Figure,
670+
ax: Axes,
671+
context: List,
672+
) -> None:
673+
import matplotlib.patches as patches
674+
675+
points = ann.points
676+
color = self._get_color(ann)
677+
label_text = label_categories[ann.label].name if label_categories is not None else ann.label
678+
679+
# Define the faces based on vertex indices
680+
681+
faces = [
682+
[points[i] for i in [0, 1, 2, 3]], # Bottom face
683+
[points[i] for i in [4, 5, 6, 7]], # Top face
684+
[points[i] for i in [0, 1, 5, 4]], # Front face
685+
[points[i] for i in [1, 2, 6, 5]], # Right face
686+
[points[i] for i in [2, 3, 7, 6]], # Back face
687+
[points[i] for i in [3, 0, 4, 7]], # Left face
688+
]
689+
ax.text(points[0][0], points[0][1] - self.text_y_offset, label_text, color=color)
690+
691+
# Draw each face
692+
for face in faces:
693+
polygon = patches.Polygon(
694+
face, fill=False, linewidth=self.bbox_linewidth, edgecolor=color
695+
)
696+
ax.add_patch(polygon)
697+
664698
def _draw_super_resolution_annotation(
665699
self,
666700
ann: SuperResolutionAnnotation,

src/datumaro/plugins/data_formats/datumaro/base.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
AnnotationType,
1212
Bbox,
1313
Caption,
14+
Cuboid2D,
1415
Cuboid3d,
1516
Ellipse,
1617
GroupType,
@@ -378,6 +379,18 @@ def _load_annotations(self, item: Dict):
378379

379380
elif ann_type == AnnotationType.hash_key:
380381
continue
382+
elif ann_type == AnnotationType.cuboid_2d:
383+
loaded.append(
384+
Cuboid2D(
385+
list(map(tuple, points)),
386+
label=label_id,
387+
id=ann_id,
388+
attributes=attributes,
389+
group=group,
390+
object_id=object_id,
391+
z_order=z_order,
392+
)
393+
)
381394
else:
382395
raise NotImplementedError()
383396
except Exception as e:

src/datumaro/plugins/data_formats/datumaro/exporter.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
Annotation,
2121
Bbox,
2222
Caption,
23+
Cuboid2D,
2324
Cuboid3d,
2425
Ellipse,
2526
HashKey,
@@ -311,6 +312,8 @@ def _gen_item_desc(self, item: DatasetItem, *args, **kwargs) -> Dict:
311312
converted_ann = self._convert_ellipse_object(ann)
312313
elif isinstance(ann, HashKey):
313314
continue
315+
elif isinstance(ann, Cuboid2D):
316+
converted_ann = self._convert_cuboid_2d_object(ann)
314317
else:
315318
raise NotImplementedError()
316319
annotations.append(converted_ann)
@@ -435,6 +438,18 @@ def _convert_cuboid_3d_object(self, obj):
435438
def _convert_ellipse_object(self, obj: Ellipse):
436439
return self._convert_shape_object(obj)
437440

441+
def _convert_cuboid_2d_object(self, obj: Cuboid2D):
442+
converted = self._convert_annotation(obj)
443+
444+
converted.update(
445+
{
446+
"label_id": cast(obj.label, int),
447+
"points": obj.points,
448+
"z_order": obj.z_order,
449+
}
450+
)
451+
return converted
452+
438453

439454
class _StreamSubsetWriter(_SubsetWriter):
440455
def __init__(

src/datumaro/plugins/data_formats/datumaro_binary/mapper/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
"CaptionMapper",
2323
"Cuboid3dMapper",
2424
"EllipseMapper",
25+
"Cuboid2DMapper",
2526
# common
2627
"Mapper",
2728
"DictMapper",

src/datumaro/plugins/data_formats/datumaro_binary/mapper/annotation.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
AnnotationType,
1313
Bbox,
1414
Caption,
15+
Cuboid2D,
1516
Cuboid3d,
1617
Ellipse,
1718
Label,
@@ -270,6 +271,33 @@ def backward(cls, _bytes: bytes, offset: int = 0) -> Tuple[Ellipse, int]:
270271
return Ellipse(x, y, x2, y2, **shape_dict), offset
271272

272273

274+
class Cuboid2DMapper(AnnotationMapper):
275+
ann_type = AnnotationType.cuboid_2d
276+
277+
@classmethod
278+
def forward(cls, ann: Shape) -> bytes:
279+
_bytearray = bytearray()
280+
_bytearray.extend(struct.pack("<Bqqi", ann.type, ann.id, ann.group, ann.object_id))
281+
_bytearray.extend(DictMapper.forward(ann.attributes))
282+
_bytearray.extend(
283+
struct.pack("<ii", _ShapeMapper.forward_optional_label(ann.label), ann.z_order)
284+
)
285+
_bytearray.extend(
286+
FloatListMapper.forward([coord for point in ann.points for coord in point])
287+
)
288+
return bytes(_bytearray)
289+
290+
@classmethod
291+
def backward(cls, _bytes: bytes, offset: int = 0) -> Tuple[Ellipse, int]:
292+
ann_dict, offset = super().backward_dict(_bytes, offset)
293+
label, z_order = struct.unpack_from("<ii", _bytes, offset)
294+
offset += 8
295+
points, offset = FloatListMapper.backward(_bytes, offset)
296+
points = list(zip(points[::2], points[1::2]))
297+
298+
return Cuboid2D(points, label=label, z_order=z_order, **ann_dict), offset
299+
300+
273301
class AnnotationListMapper(Mapper):
274302
backward_map = {
275303
AnnotationType.label: LabelMapper.backward,
@@ -281,6 +309,7 @@ class AnnotationListMapper(Mapper):
281309
AnnotationType.caption: CaptionMapper.backward,
282310
AnnotationType.cuboid_3d: Cuboid3dMapper.backward,
283311
AnnotationType.ellipse: EllipseMapper.backward,
312+
AnnotationType.cuboid_2d: Cuboid2DMapper.backward,
284313
}
285314

286315
@classmethod
@@ -307,6 +336,8 @@ def forward(cls, anns: List[Annotation]) -> bytes:
307336
_bytearray.extend(Cuboid3dMapper.forward(ann))
308337
elif isinstance(ann, Ellipse):
309338
_bytearray.extend(EllipseMapper.forward(ann))
339+
elif isinstance(ann, Cuboid2D):
340+
_bytearray.extend(Cuboid2DMapper.forward(ann))
310341
else:
311342
raise NotImplementedError()
312343

0 commit comments

Comments
 (0)