# coding: utf-8
from __future__ import annotations
import uuid
from typing import Dict, List, Optional
from bidict import bidict
from supervisely._utils import take_with_default
from supervisely.annotation.label import LabelJsonFields
from supervisely.annotation.obj_class import ObjClass
from supervisely.collection.key_indexed_collection import KeyObject
from supervisely.geometry.constants import (
CLASS_ID,
CREATED_AT,
LABELER_LOGIN,
UPDATED_AT,
)
from supervisely.project.project_meta import ProjectMeta
from supervisely.project.project_settings import LabelingInterface
from supervisely.video_annotation.constants import ID, KEY, OBJECTS_MAP
from supervisely.video_annotation.key_id_map import KeyIdMap
from supervisely.video_annotation.video_tag import VideoTag
from supervisely.video_annotation.video_tag_collection import VideoTagCollection
[docs]
class VideoObject(KeyObject):
"""Tracked object across video frames; obj_class plus optional tags. Immutable."""
def __init__(
self,
obj_class: ObjClass,
tags: Optional[VideoTagCollection] = None,
key: Optional[uuid.UUID] = None,
class_id: Optional[int] = None,
labeler_login: Optional[str] = None,
updated_at: Optional[str] = None,
created_at: Optional[str] = None,
):
"""
:param obj_class: Object class (e.g. 'car' with Rectangle geometry).
:type obj_class: :class:`~supervisely.annotation.obj_class.ObjClass`
:param tags: Tags for this object.
:type tags: :class:`~supervisely.video_annotation.video_tag_collection.VideoTagCollection`, optional
:param key: UUID key. Auto-generated if not provided.
:type key: uuid.UUID, optional
:param class_id: Server-side class ID.
:type class_id: int, optional
:param labeler_login: Login of user who created the object.
:type labeler_login: str, optional
:param updated_at: Last modification timestamp.
:type updated_at: str, optional
:param created_at: Creation timestamp.
:type created_at: str, optional
:Usage Example:
.. code-block:: python
import supervisely as sly
obj_class_car = sly.ObjClass('car', sly.Rectangle)
video_obj_car = sly.VideoObject(obj_class_car)
video_obj_car_json = video_obj_car.to_json()
print(video_obj_car_json)
"""
self.labeler_login = labeler_login
self.updated_at = updated_at
self.created_at = created_at
self._obj_class = obj_class
self._key = take_with_default(key, uuid.uuid4())
self._tags = take_with_default(tags, VideoTagCollection())
self._class_id = take_with_default(class_id, None)
def _add_creation_info(self, d):
if self.labeler_login is not None:
d[LABELER_LOGIN] = self.labeler_login
if self.updated_at is not None:
d[UPDATED_AT] = self.updated_at
if self.created_at is not None:
d[CREATED_AT] = self.created_at
@property
def obj_class(self) -> ObjClass:
"""
ObjClass of the current VideoObject.
:returns: ObjClass object
:rtype: :class:`~supervisely.annotation.obj_class.ObjClass`
:Usage Example:
.. code-block:: python
import supervisely as sly
obj_class_car = sly.ObjClass('car', sly.Rectangle)
video_obj_car = sly.VideoObject(obj_class_car)
obj_car_json = video_obj_car.obj_class.to_json()
print(obj_car_json)
# Output: {
# "title": "car",
# "shape": "rectangle",
# "color": "#8A0F7B",
# "geometry_config": {},
# "hotkey": ""
# }
"""
return self._obj_class
[docs]
def key(self) -> uuid.UUID:
"""
Object key.
:returns: Object key
:rtype: :class:`~supervisely.video_annotation.key_id_map.KeyIdMap`
:rtype: uuid.UUID
:Usage Example:
.. code-block:: python
import supervisely as sly
obj_class_car = sly.ObjClass('car', sly.Rectangle)
video_obj_car = sly.VideoObject(obj_class_car)
key = video_obj_car.key()
print(key)
# Output: 158e6cf4f4ac4c639fc6994aad127c16
"""
return self._key
@property
def tags(self) -> VideoTagCollection:
"""
VideoTagCollection of the current VideoObject.
:returns: VideoTagCollection object
:rtype: :class:`~supervisely.video_annotation.video_tag_collection.VideoTagCollection`
:Usage Example:
.. code-block:: python
import supervisely as sly
# Create VideoTagCollection
meta_car = sly.TagMeta('car_tag', sly.TagValueType.ANY_STRING)
from supervisely.video_annotation.video_tag import VideoTag
car_tag = VideoTag(meta_car, value='acura')
from supervisely.video_annotation.video_tag_collection import VideoTagCollection
video_tags = VideoTagCollection([car_tag])
# Create VideoObject
obj_class_car = sly.ObjClass('car', sly.Rectangle)
video_obj_car = sly.VideoObject(obj_class_car, video_tags)
# Get VideoTagCollection
tags = video_obj_car.tags
tags_json = tags.to_json()
print(tags_json)
# Output: [
# {
# "name": "car_tag",
# "value": "acura",
# "key": "4f82fbcab74c44259d7a0e29d604602e"
# }
# ]
"""
return self._tags.clone()
@property
def class_id(self) -> int:
"""
Object class ID.
:returns: Object class ID.
:rtype: int
:Usage Example:
.. code-block:: python
import supervisely as sly
obj_class_car = sly.ObjClass('car', sly.Rectangle)
video_obj_car = sly.VideoObject(obj_class_car)
class_id = video_obj_car.class_id
"""
return self._class_id
[docs]
def add_tag(self, tag: VideoTag) -> VideoObject:
"""
Adds VideoTag to the current VideoObject.
:param tag: VideoTag to be added.
:type tag: :class:`~supervisely.video_annotation.video_tag.VideoTag`
:returns: VideoObject object
:rtype: :class:`~supervisely.video_annotation.video_object.VideoObject`
:Usage Example:
.. code-block:: python
import supervisely as sly
# Create VideoObject
obj_class_car = sly.ObjClass('car', sly.Rectangle)
video_obj_car = sly.VideoObject(obj_class_car)
# Create VideoTag
meta_car = sly.TagMeta('car_tag', sly.TagValueType.ANY_STRING)
from supervisely.video_annotation.video_tag import VideoTag
car_tag = VideoTag(meta_car, value='acura')
# Add VideoTag
new_obj_car = video_obj_car.add_tag(car_tag)
new_obj_car_json = new_obj_car.to_json()
print(new_obj_car_json)
# Output: {
# "key": "1ab52285ee634c93b724fa655b785eae",
# "classTitle": "car",
# "tags": [
# {
# "name": "car_tag",
# "value": "acura",
# "key": "d9e52b275e074c538f162a6d679aed9e"
# }
# ]
# }
"""
return self.clone(tags=self._tags.add(tag))
[docs]
def to_json(self, key_id_map: Optional[KeyIdMap] = None) -> Dict:
"""
Convert the VideoObject to a json dict. Read more about `Supervisely format <https://docs.supervisely.com/data-organization/00_ann_format_navi>`_.
:param key_id_map: KeyIdMap object.
:type key_id_map: :class:`~supervisely.video_annotation.key_id_map.KeyIdMap`, optional
:returns: Json format as a dict
:rtype: dict
:Usage Example:
.. code-block:: python
import supervisely as sly
obj_class_car = sly.ObjClass('vehicle', sly.Rectangle)
video_obj_car = sly.VideoObject(obj_class_car)
obj_car_json = video_obj_car.to_json()
print(obj_car_json)
# Output: {
# "key": "ce26e77a45bc45e88e3e17da1672d01f",
# "classTitle": "vehicle",
# "tags": []
# }
"""
data_json = {
KEY: self.key().hex,
LabelJsonFields.OBJ_CLASS_NAME: self.obj_class.name,
LabelJsonFields.TAGS: self.tags.to_json(key_id_map),
}
if key_id_map is not None:
item_id = key_id_map.get_object_id(self.key())
if item_id is not None:
data_json[ID] = item_id
self._add_creation_info(data_json)
return data_json
[docs]
@classmethod
def from_json(
cls, data: Dict, project_meta: ProjectMeta, key_id_map: Optional[KeyIdMap] = None
) -> VideoObject:
"""
Convert a json dict to VideoObject. Read more about `Supervisely format <https://docs.supervisely.com/data-organization/00_ann_format_navi>`_.
:param data: Dict in json format.
:type data: dict
:param project_meta: Input ProjectMeta object.
:type project_meta: :class:`~supervisely.project.project_meta.ProjectMeta`
:param key_id_map: KeyIdMap object.
:type key_id_map: :class:`~supervisely.video_annotation.key_id_map.KeyIdMap`, optional
:raises RuntimeError: if object class name is not found in the given project meta
:returns: VideoObject object
:rtype: :class:`~supervisely.video_annotation.video_object.VideoObject`
:Usage Example:
.. code-block:: python
import supervisely as sly
obj_car_json = {"classTitle": "vehicle", "tags": []}
obj_class_car = sly.ObjClass('vehicle', sly.Rectangle)
obj_collection = sly.ObjClassCollection([obj_class_car])
meta = sly.ProjectMeta(obj_classes=obj_collection)
video_obj_from_json = sly.VideoObject.from_json(obj_car_json, meta)
"""
obj_class_name = data[LabelJsonFields.OBJ_CLASS_NAME]
obj_class = project_meta.get_obj_class(obj_class_name)
if obj_class is None:
raise RuntimeError(
f"Failed to deserialize a object from JSON: class name {obj_class_name!r} "
f"was not found in the given project meta."
)
is_multiview = False
try:
if project_meta.labeling_interface == LabelingInterface.MULTIVIEW:
is_multiview = True
except AttributeError:
is_multiview = False
raw_id = data.get(ID, None)
if not is_multiview:
key = uuid.UUID(data[KEY]) if KEY in data else uuid.uuid4()
if key_id_map is not None:
key_id_map.add_object(key, raw_id)
else:
if KEY in data:
key = uuid.UUID(data[KEY])
elif key_id_map is not None and isinstance(raw_id, int):
existing_key = key_id_map.get_object_key(raw_id)
if isinstance(existing_key, uuid.UUID):
key = existing_key
else:
key = uuid.uuid4()
else:
key = uuid.uuid4()
if key_id_map is not None and isinstance(raw_id, int):
if key_id_map.get_object_id(key) is None:
key_id_map.add_object(key, raw_id)
class_id = data.get(CLASS_ID, None)
labeler_login = data.get(LABELER_LOGIN, None)
updated_at = data.get(UPDATED_AT, None)
created_at = data.get(CREATED_AT, None)
return cls(
obj_class=obj_class,
key=key,
tags=VideoTagCollection.from_json(data[LabelJsonFields.TAGS], project_meta.tag_metas),
class_id=class_id,
labeler_login=labeler_login,
updated_at=updated_at,
created_at=created_at,
)
[docs]
def clone(
self,
obj_class: Optional[ObjClass] = None,
tags: Optional[VideoTagCollection] = None,
key: Optional[uuid.UUID] = None,
class_id: Optional[int] = None,
labeler_login: Optional[str] = None,
updated_at: Optional[str] = None,
created_at: Optional[str] = None,
) -> VideoObject:
"""
Makes a copy of VideoObject with new fields, if fields are given, otherwise it will use fields of the original VideoObject.
:param obj_class: ObjClass object.
:type obj_class: :class:`~supervisely.annotation.obj_class.ObjClass`, optional
:param tags: VideoTagCollection object.
:type tags: :class:`~supervisely.video_annotation.video_tag_collection.VideoTagCollection`, optional
:param key: UUID key associated with the object.
:type key: uuid.UUID, optional
:param class_id: ID of ObjClass to which VideoObject belongs.
:type class_id: int, optional
:param labeler_login: Login of the user who created VideoObject.
:type labeler_login: str, optional
:param updated_at: Date and Time when VideoObject was modified last. Date Format: Year:Month:Day:Hour:Minute:Seconds. Example: '2021-01-22T19:37:50.158Z'.
:type updated_at: str, optional
:param created_at: Date and Time when VideoObject was created. Date Format is the same as in "updated_at" parameter.
:type created_at: str, optional
:returns: VideoObject object
:rtype: :class:`~supervisely.video_annotation.video_object.VideoObject`
:Usage Example:
.. code-block:: python
import supervisely as sly
from supervisely.video_annotation.video_tag import VideoTag
from supervisely.video_annotation.video_tag_collection import VideoTagCollection
obj_class_car = sly.ObjClass('vehicle', sly.Rectangle)
video_obj_car = sly.VideoObject(obj_class_car)
# New ObjClass to clone
obj_class_bus = sly.ObjClass('bus', sly.Rectangle)
# New VideoTagCollection to clone
meta_car = sly.TagMeta('car_tag', sly.TagValueType.ANY_STRING)
car_tag = VideoTag(meta_car, value='acura')
tags = VideoTagCollection([car_tag])
# Clone
new_obj_vehicle = video_obj_car.clone(obj_class=obj_class_bus, tags=tags)
new_obj_vehicle_json = new_obj_vehicle.to_json()
print(new_obj_vehicle_json)
# Output: {
# "key": "39ae5b9ce1ca405c9f53544374b3f5be",
# "classTitle": "bus",
# "tags": [
# {
# "name": "car_tag",
# "value": "acura",
# "key": "3119b6e38fe24fe7a220e881154fd9ba"
# }
# ]
# }
"""
return self.__class__(
obj_class=take_with_default(obj_class, self.obj_class),
key=take_with_default(key, self._key),
tags=take_with_default(tags, self.tags),
class_id=take_with_default(class_id, self.class_id),
labeler_login=take_with_default(labeler_login, self.labeler_login),
updated_at=take_with_default(updated_at, self.updated_at),
created_at=take_with_default(created_at, self.created_at),
)