Source code for supervisely.mesh_annotation.mesh_label

# coding: utf-8
from __future__ import annotations

import uuid
from copy import deepcopy
from typing import Dict, List, Optional, Union
from uuid import UUID

from supervisely._utils import take_with_default
from supervisely.annotation.json_geometries_map import GET_GEOMETRY_FROM_STR
from supervisely.annotation.label import LabelJsonFields, LabelingStatus
from supervisely.annotation.obj_class import ObjClass
from supervisely.api.module_api import ApiField
from supervisely.geometry.any_geometry import AnyGeometry
from supervisely.geometry.constants import (
    CLASS_ID,
    CREATED_AT,
    GEOMETRY_SHAPE,
    GEOMETRY_TYPE,
    LABELER_LOGIN,
    UPDATED_AT,
)
from supervisely.geometry.geometry import Geometry
from supervisely.mesh_annotation.constants import KEY, TAGS
from supervisely.mesh_annotation.mesh_tag import MeshTag
from supervisely.mesh_annotation.mesh_tag_collection import MeshTagCollection
from supervisely.project.project_meta import ProjectMeta


[docs] class MeshLabel: """Single geometry-backed label in a mesh annotation.""" def __init__( self, geometry: Geometry, obj_class: ObjClass, tags: Optional[Union[MeshTagCollection, List[MeshTag]]] = None, description: Optional[str] = "", key: Optional[UUID] = None, sly_id: Optional[int] = None, class_id: Optional[int] = None, labeler_login: Optional[str] = None, updated_at: Optional[str] = None, created_at: Optional[str] = None, custom_data: Optional[Dict] = None, priority: Optional[int] = None, status: Optional[LabelingStatus] = None, ): """Initialize a single geometry-backed annotation object. :param geometry: Geometry of the label. :type geometry: :class:`~supervisely.geometry.geometry.Geometry` :param obj_class: Object class the label belongs to. :type obj_class: :class:`~supervisely.annotation.obj_class.ObjClass` :param tags: Tags attached to the label. :type tags: Optional[Union[:class:`~supervisely.mesh_annotation.mesh_tag_collection.MeshTagCollection`, List[:class:`~supervisely.mesh_annotation.mesh_tag.MeshTag`]]] :param description: Free-text description of the label. :type description: Optional[str] :param key: Unique identifier of the label. Generated automatically if not provided. :type key: Optional[uuid.UUID] :param sly_id: Label ID in Supervisely. :type sly_id: Optional[int] :param class_id: Object class ID in Supervisely. :type class_id: Optional[int] :param labeler_login: Login of the user who created the label. :type labeler_login: Optional[str] :param updated_at: Timestamp of the last update. :type updated_at: Optional[str] :param created_at: Timestamp of creation. :type created_at: Optional[str] :param custom_data: Arbitrary user data attached to the label. :type custom_data: Optional[dict] :param priority: Drawing/processing priority of the label. :type priority: Optional[int] :param status: Labeling status (defaults to ``LabelingStatus.MANUAL``). :type status: Optional[:class:`~supervisely.annotation.label.LabelingStatus`] :raises TypeError: If the geometry type does not match the object class geometry type. """ self._geometry = geometry self._obj_class = obj_class self._tags = take_with_default(tags, MeshTagCollection()) if isinstance(self._tags, list): self._tags = MeshTagCollection(self._tags) self._description = take_with_default(description, "") self._key = take_with_default(key, uuid.uuid4()) self._sly_id = sly_id self._class_id = class_id self.labeler_login = labeler_login self.updated_at = updated_at self.created_at = created_at self._custom_data = deepcopy(take_with_default(custom_data, {})) self._priority = priority self._status = take_with_default(status, LabelingStatus.MANUAL) self._nn_created, self._nn_updated = LabelingStatus.to_flags(self._status) self._validate_geometry_type() self._validate_geometry() def _validate_geometry_type(self) -> None: """Check that the geometry type matches the object class geometry type. :raises TypeError: If the types do not match (unless the class accepts ``AnyGeometry``). """ if self.obj_class.geometry_type is AnyGeometry: return if type(self.geometry) is not self.obj_class.geometry_type: raise TypeError( "Geometry type {!r} does not match object class geometry type {!r}.".format( type(self.geometry), self.obj_class.geometry_type ) ) def _validate_geometry(self) -> None: """Validate the geometry against the object class geometry name and config.""" self.geometry.validate( self.obj_class.geometry_type.geometry_name(), self.obj_class.geometry_config, ) def _add_creation_info(self, data: Dict) -> None: """Add labeler login and creation/update timestamps to ``data`` if set (in place).""" if self.labeler_login is not None: data[LABELER_LOGIN] = self.labeler_login if self.updated_at is not None: data[UPDATED_AT] = self.updated_at if self.created_at is not None: data[CREATED_AT] = self.created_at @property def geometry(self) -> Geometry: """Geometry of the label. :rtype: :class:`~supervisely.geometry.geometry.Geometry` """ return self._geometry @property def obj_class(self) -> ObjClass: """Object class the label belongs to. :rtype: :class:`~supervisely.annotation.obj_class.ObjClass` """ return self._obj_class @property def tags(self) -> MeshTagCollection: """Tags attached to the label (returned as a copy). :rtype: :class:`~supervisely.mesh_annotation.mesh_tag_collection.MeshTagCollection` """ return self._tags.clone() @property def description(self) -> str: """Free-text description of the label. :rtype: str """ return self._description @property def custom_data(self) -> Dict: """Arbitrary user data attached to the label (returned as a deep copy). :rtype: dict """ return deepcopy(self._custom_data) @property def priority(self) -> Optional[int]: """Drawing/processing priority of the label. :rtype: Optional[int] """ return self._priority @property def sly_id(self) -> Optional[int]: """Label ID in Supervisely. :rtype: Optional[int] """ return self._sly_id @property def class_id(self) -> Optional[int]: """Object class ID in Supervisely. :rtype: Optional[int] """ return self._class_id @property def status(self) -> LabelingStatus: """Labeling status of the label. :rtype: :class:`~supervisely.annotation.label.LabelingStatus` """ return self._status
[docs] def key(self) -> uuid.UUID: """Return the unique identifier of the label. :returns: Label key. :rtype: uuid.UUID """ return self._key
[docs] def to_json(self) -> Dict: """Serialize the label to a JSON-serializable dict. :returns: Dict with key, object class name, description, tags, geometry and metadata. :rtype: dict """ geometry_json = deepcopy(self.geometry.to_json()) geometry_json.pop(GEOMETRY_TYPE, None) geometry_json.pop(GEOMETRY_SHAPE, None) data_json = { KEY: self.key().hex, LabelJsonFields.OBJ_CLASS_NAME: self.obj_class.name, LabelJsonFields.DESCRIPTION: self.description, TAGS: self.tags.to_json(), ApiField.GEOMETRY_TYPE: self.geometry.geometry_name(), ApiField.GEOMETRY: geometry_json, ApiField.NN_CREATED: self._nn_created, ApiField.NN_UPDATED: self._nn_updated, } class_id = self.class_id if self.class_id is not None else self.obj_class.sly_id if class_id is not None: data_json[CLASS_ID] = class_id if self.sly_id is not None: data_json[LabelJsonFields.ID] = self.sly_id if self.priority is not None: data_json[ApiField.PRIORITY] = self.priority if self.custom_data: data_json[ApiField.CUSTOM_DATA] = self.custom_data self._add_creation_info(data_json) return data_json
[docs] @classmethod def from_json( cls, data: Dict, project_meta: ProjectMeta, ) -> "MeshLabel": """Deserialize a mesh label from a JSON dict. :param data: Mesh label in JSON format. :type data: dict :param project_meta: Project meta used to resolve the object class and tag metas. :type project_meta: :class:`~supervisely.project.project_meta.ProjectMeta` :returns: Deserialized mesh label. :rtype: :class:`~supervisely.mesh_annotation.mesh_label.MeshLabel` :raises RuntimeError: If the object class is missing from the project meta or geometry is absent. """ 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 MeshLabel from JSON: class name " f"{obj_class_name!r} was not found in the given project meta." ) geometry_type = data[ApiField.GEOMETRY_TYPE] geometry_json = data.get(ApiField.GEOMETRY) if not isinstance(geometry_json, dict): raise RuntimeError("Mesh label can not be deserialized without geometry.") if obj_class.geometry_type is AnyGeometry: geometry_cls = GET_GEOMETRY_FROM_STR(geometry_type) else: geometry_cls = obj_class.geometry_type geometry = geometry_cls.from_json(geometry_json) key = uuid.UUID(data[KEY]) if KEY in data else uuid.uuid4() label_id = data.get(LabelJsonFields.ID) nn_created = data.get(ApiField.NN_CREATED, False) nn_updated = data.get(ApiField.NN_UPDATED, False) return cls( geometry=geometry, obj_class=obj_class, tags=MeshTagCollection.from_json(data.get(TAGS, []), project_meta.tag_metas), description=data.get(LabelJsonFields.DESCRIPTION, ""), key=key, sly_id=label_id, class_id=data.get(CLASS_ID, data.get(LabelJsonFields.OBJ_CLASS_ID)), labeler_login=data.get(LABELER_LOGIN), updated_at=data.get(UPDATED_AT), created_at=data.get(CREATED_AT), custom_data=data.get(ApiField.CUSTOM_DATA), priority=data.get(ApiField.PRIORITY), status=LabelingStatus.from_flags(nn_created, nn_updated), )
[docs] def clone( self, geometry: Optional[Geometry] = None, obj_class: Optional[ObjClass] = None, tags: Optional[Union[MeshTagCollection, List[MeshTag]]] = None, description: Optional[str] = None, key: Optional[UUID] = None, sly_id: Optional[int] = None, class_id: Optional[int] = None, labeler_login: Optional[str] = None, updated_at: Optional[str] = None, created_at: Optional[str] = None, custom_data: Optional[Dict] = None, priority: Optional[int] = None, status: Optional[LabelingStatus] = None, ) -> "MeshLabel": """Return a copy of the label with the given fields overridden. Any argument left as ``None`` keeps the current value of the label. :param geometry: New geometry. :type geometry: Optional[:class:`~supervisely.geometry.geometry.Geometry`] :param obj_class: New object class. :type obj_class: Optional[:class:`~supervisely.annotation.obj_class.ObjClass`] :param tags: New tags. :type tags: Optional[Union[:class:`~supervisely.mesh_annotation.mesh_tag_collection.MeshTagCollection`, List[:class:`~supervisely.mesh_annotation.mesh_tag.MeshTag`]]] :param description: New description. :type description: Optional[str] :param key: New unique identifier. :type key: Optional[uuid.UUID] :param sly_id: New label ID in Supervisely. :type sly_id: Optional[int] :param class_id: New object class ID in Supervisely. :type class_id: Optional[int] :param labeler_login: New labeler login. :type labeler_login: Optional[str] :param updated_at: New update timestamp. :type updated_at: Optional[str] :param created_at: New creation timestamp. :type created_at: Optional[str] :param custom_data: New custom data. :type custom_data: Optional[dict] :param priority: New priority. :type priority: Optional[int] :param status: New labeling status. :type status: Optional[:class:`~supervisely.annotation.label.LabelingStatus`] :returns: New mesh label with the overridden fields. :rtype: :class:`~supervisely.mesh_annotation.mesh_label.MeshLabel` """ return MeshLabel( geometry=take_with_default(geometry, self.geometry), obj_class=take_with_default(obj_class, self.obj_class), tags=take_with_default(tags, self.tags), description=take_with_default(description, self.description), key=take_with_default(key, self.key()), sly_id=take_with_default(sly_id, self.sly_id), 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), custom_data=take_with_default(custom_data, self.custom_data), priority=take_with_default(priority, self.priority), status=take_with_default(status, self.status), )