Source code for otx.api.entities.annotation

"""This module define the annotation entity."""
# Copyright (C) 2021-2022 Intel Corporation
# SPDX-License-Identifier: Apache-2.0
#

import abc
import datetime
from enum import Enum
from typing import Dict, List, Optional, Set

from bson import ObjectId

from otx.api.entities.id import ID
from otx.api.entities.label import LabelEntity
from otx.api.entities.scored_label import ScoredLabel
from otx.api.entities.shapes.shape import ShapeEntity
from otx.api.utils.time_utils import now


[docs] class Annotation(metaclass=abc.ABCMeta): """Base class for annotation objects. Args: shape (ShapeEntity): the shape of the annotation labels (List[ScoredLabel]): the labels of the annotation id (Optional[ID]): the id of the annotation """ # pylint: disable=redefined-builtin; def __init__(self, shape: ShapeEntity, labels: List[ScoredLabel], id: Optional[ID] = None): self.__id_ = ID(ObjectId()) if id is None else id self.__shape = shape self.__labels = labels def __repr__(self): """String representation of the annotation.""" return ( f"{self.__class__.__name__}(" f"shape={self.shape}, " f"labels={self.get_labels(include_empty=True)}, " f"id={self.id_})" ) @property def id_(self): """Returns the id for the annotation.""" return self.__id_ @id_.setter def id_(self, value): self.__id_ = value @property def id(self): """DEPRECATED.""" return self.__id_ @id.setter def id(self, value): """DEPRECATED.""" self.__id_ = value @property def shape(self) -> ShapeEntity: """Returns the shape that is in the annotation.""" return self.__shape @shape.setter def shape(self, value) -> None: self.__shape = value
[docs] def get_labels(self, include_empty: bool = False) -> List[ScoredLabel]: """Get scored labels that are assigned to this annotation. Args: include_empty (bool): set to True to include empty label (if exists) in the output. Defaults to False. Returns: List of labels in annotation """ return [label for label in self.__labels if include_empty or (not label.is_empty)]
[docs] def get_label_ids(self, include_empty: bool = False) -> Set[ID]: """Get a set of ID's of labels that are assigned to this annotation. Args: include_empty (bool): set to True to include empty label (if exists) in the output. Defaults to False. Returns: Set of label id's in annotation """ return {label.id_ for label in self.__labels if include_empty or (not label.is_empty)}
[docs] def append_label(self, label: ScoredLabel) -> None: """Appends the scored label to the annotation. Args: label (ScoredLabel): the scored label to be appended to the annotation """ self.__labels.append(label)
[docs] def set_labels(self, labels: List[ScoredLabel]) -> None: """Sets the labels of the annotation to be the input of the function. Args: labels (List[ScoredLabel]): the scored labels to be set as annotation labels """ self.__labels = labels
def __eq__(self, other: object) -> bool: """Checks if the two annotations are equal. Args: other (Annotation): Annotation to compare with. Returns: bool: True if the two annotations are equal, False otherwise. """ if isinstance(other, Annotation): return ( self.id_ == other.id_ and self.get_labels(True) == other.get_labels(True) and self.shape == other.shape ) return False
[docs] class AnnotationSceneKind(Enum): """AnnotationSceneKinds for an Annotation object.""" #: NONE represents NULLAnnotationScene's (See :class:`NullAnnotationScene`) NONE = 0 #: ANNOTATION represents user annotation ANNOTATION = 1 #: PREDICTION represents analysis result, which will be shown to the user PREDICTION = 2 #: EVALUATION represents analysis result for evaluation purposes, which will NOT be shown to the user EVALUATION = 3 #: INTERMEDIATE represents intermediary state. #: This is used when the analysis is being transferred from one task to another. #: This will not be shown to the user. #: This state will be changed to either PREDICTION or EVALUATION at the end of analysis process. INTERMEDIATE = 4 #: TASK_PREDICTION represents analysis results for a single task TASK_PREDICTION = 5 def __str__(self): """String representation of the AnnotationSceneKind.""" return str(self.name)
[docs] class AnnotationSceneEntity(metaclass=abc.ABCMeta): """This class represents a user annotation or a result (prediction). It serves as a collection of shapes, with a relation to the media entity. Example: Creating an annotation: >>> from otx.api.entities.annotation import Annotation, AnnotationSceneEntity, AnnotationSceneKind >>> from otx.api.entities.shapes.rectangle import Rectangle >>> box = Rectangle(x1=0.0, y1=0.0, x2=0.5, y2=0.5) # Box covering top-left quart of image >>> AnnotationSceneEntity(annotations=[Annotation(shape=box, labels=[])], kind=AnnotationSceneKind.ANNOTATION) Args: annotations (List[Annotation]): List of annotations in the scene kind (AnnotationSceneKind): Kind of the annotation scene. E.g. `AnnotationSceneKind.ANNOTATION`. editor (str): The user that made this annotation scene object. creation_date (Optional[datetime.datetime]): Creation date of annotation scene entity. If None, current time is used. Defaults to None. id (Optional[ID]): ID of AnnotationSceneEntity. If None a new `ID` is created. Defaults to None. """ # pylint: disable=too-many-arguments, redefined-builtin def __init__( self, annotations: List[Annotation], kind: AnnotationSceneKind, editor: str = "", creation_date: Optional[datetime.datetime] = None, id: Optional[ID] = None, ): self.__annotations = annotations self.__kind = kind self.__editor = editor self.__creation_date = now() if creation_date is None else creation_date self.__id_ = ID() if id is None else id def __repr__(self): """String representation of the annotation scene.""" return ( f"{self.__class__.__name__}(" f"annotations={self.annotations}, " f"kind={self.kind}, " f"editor={self.editor_name}, " f"creation_date={self.creation_date}, " f"id={self.id_})" ) @property def id_(self) -> ID: """Returns the ID of the AnnotationSceneEntity.""" return self.__id_ @id_.setter def id_(self, value) -> None: self.__id_ = value @property def id(self): """DEPRECATED.""" return self.__id_ @id.setter def id(self, value): """DEPRECATED.""" self.__id_ = value @property def kind(self) -> AnnotationSceneKind: """Returns the AnnotationSceneKind of the AnnotationSceneEntity.""" return self.__kind @kind.setter def kind(self, value) -> None: self.__kind = value @property def editor_name(self) -> str: """Returns the editor's name that made the AnnotationSceneEntity object.""" return self.__editor @editor_name.setter def editor_name(self, value) -> None: self.__editor = value @property def creation_date(self) -> datetime.datetime: """Returns the creation date of the AnnotationSceneEntity object.""" return self.__creation_date @creation_date.setter def creation_date(self, value) -> None: self.__creation_date = value @property def annotations(self) -> List[Annotation]: """Return the Annotations that are present in the AnnotationSceneEntity.""" return self.__annotations @annotations.setter def annotations(self, value: List[Annotation]): self.__annotations = value @property def shapes(self) -> List[ShapeEntity]: """Returns all shapes that are inside the annotations of the AnnotationSceneEntity.""" return [annotation.shape for annotation in self.annotations]
[docs] def contains_any(self, labels: List[LabelEntity]) -> bool: """Checks whether the annotation contains any labels in the input parameter. Args: labels (List[LabelEntity]): List of labels to compare to. Returns: bool: True if there is any intersection between self.get_labels(include_empty=True) with labels. """ label_names = {label.name for label in labels} return len({label.name for label in self.get_labels(include_empty=True)}.intersection(label_names)) != 0
[docs] def append_annotation(self, annotation: Annotation) -> None: """Appends the passed annotation to the list of annotations present in the AnnotationSceneEntity object.""" self.annotations.append(annotation)
[docs] def append_annotations(self, annotations: List[Annotation]) -> None: """Adds a list of annotations to the annotation scene.""" self.annotations.extend(annotations)
[docs] def get_labels(self, include_empty: bool = False) -> List[LabelEntity]: """Returns a list of unique labels which appear in this annotation scene. Args: include_empty (bool): Set to True to include empty label (if exists) in the output. Defaults to False. Returns: List[LabelEntity]: a list of labels which appear in this annotation. """ labels: Dict[str, LabelEntity] = {} for annotation in self.annotations: for label in annotation.get_labels(include_empty=include_empty): id_ = label.id_ if id_ not in labels: labels[id_] = label.get_label() return list(labels.values())
[docs] def get_label_ids(self, include_empty: bool = False) -> Set[ID]: """Returns a set of the ID's of unique labels which appear in this annotation scene. Args: include_empty (bool): Set to True to include empty label (if exists) in the output. Defaults to False. Returns: Set[ID]: a set of the ID's of labels which appear in this annotation. """ output: Set[ID] = set() for annotation in self.annotations: output.update(set(annotation.get_label_ids(include_empty=include_empty))) return output
[docs] class NullAnnotationSceneEntity(AnnotationSceneEntity): """Represents 'AnnotationSceneEntity not found.""" def __init__(self) -> None: super().__init__( id=ID(), kind=AnnotationSceneKind.NONE, editor="", creation_date=datetime.datetime.now(), annotations=[], ) def __repr__(self): """String representation NullAnnotationSceneEntity.""" return "NullAnnotationSceneEntity()"