Source code for supervisely.annotation.tag_meta

# coding: utf-8
"""General information about :class:`Tag<supervisely.annotation.tag.Tag>`"""

from __future__ import annotations
from typing import List, Optional, Dict
from copy import deepcopy
from supervisely.imaging.color import random_rgb, rgb2hex, hex2rgb, _validate_color
from supervisely.io.json import JsonSerializable
from supervisely.collection.key_indexed_collection import KeyObject
from supervisely._utils import take_with_default


[docs]class TagValueType: """ Restricts Tag to have a certain value type. """ NONE = "none" """""" ANY_NUMBER = "any_number" """""" ANY_STRING = "any_string" """""" ONEOF_STRING = "oneof_string" """"""
[docs]class TagMetaJsonFields: """ Json fields for :class:`TagMeta<supervisely.annotation.tag_meta.TagMeta>` """ ID = "id" """""" NAME = "name" """""" VALUE_TYPE = "value_type" """""" VALUES = "values" """""" COLOR = "color" """""" APPLICABLE_TYPE = "applicable_type" """""" HOTKEY = "hotkey" """""" APPLICABLE_CLASSES = "classes" """"""
[docs]class TagApplicableTo: """ Defines Tag applicability only to images, objects or both. """ ALL = "all" # both images and objects """""" IMAGES_ONLY = "imagesOnly" """""" OBJECTS_ONLY = "objectsOnly" """"""
SUPPORTED_TAG_VALUE_TYPES = [ TagValueType.NONE, TagValueType.ANY_NUMBER, TagValueType.ANY_STRING, TagValueType.ONEOF_STRING, ] SUPPORTED_APPLICABLE_TO = [ TagApplicableTo.ALL, TagApplicableTo.IMAGES_ONLY, TagApplicableTo.OBJECTS_ONLY, ]
[docs]class TagMeta(KeyObject, JsonSerializable): """ General information about :class:`Tag<supervisely.annotation.tag>`. :class:`TagMeta<TagMeta>` object is immutable. :param name: Tag name. :type name: str :param value_type: Tag value type. :type value_type: str :param possible_values: List of possible values. :type possible_values: List[str], optional :param color: :class:`[R, G, B]` color, generates random color by default. :type color: List[int, int, int], optional :param sly_id: Tag ID in Supervisely server. :type sly_id: int, optional :param hotkey: Hotkey for Tag in annotation tool UI. :type hotkey: str, optional :param applicable_to: Defines applicability of Tag only to images, objects or both. :type applicable_to: str, optional :param applicable_classes: Defines applicability of Tag only to certain classes. :type applicable_classes: List[str], optional :raises: :class:`ValueError`, if color is not list, or doesn't have exactly 3 values :Usage example: .. code-block:: python import supervisely as sly # TagMeta meta_dog = sly.TagMeta('dog', sly.TagValueType.NONE) # TagMeta applicable only to Images example meta_cat = sly.TagMeta('cat', sly.TagValueType.NONE, applicable_to=sly.TagApplicableTo.IMAGES_ONLY) # TagMeta with string value applicable only to Objects example meta_breed = sly.TagMeta('breed', sly.TagValueType.ANY_STRING, applicable_to=sly.TagApplicableTo.OBJECTS_ONLY) # More complex TagMeta example # Create a list with possible values in order to use "ONEOF_STRING" value type coat_colors = ["brown", "white", "black", "red", "chocolate", "gold", "grey"] # Note that "ONEOF_STRING" value type requires possible values, otherwise ValueError will be raised meta_coat_color = sly.TagMeta('coat color', sly.TagValueType.ONEOF_STRING, coat_colors, [255,120,0], hotkey="M", applicable_to=sly.TagApplicableTo.OBJECTS_ONLY, applicable_classes=["dog", "cat"]) """ def __init__( self, name: str, value_type: str, possible_values: Optional[List[str]] = None, color: Optional[List[int]] = None, sly_id: Optional[int] = None, hotkey: Optional[str] = None, applicable_to: Optional[str] = None, applicable_classes: Optional[List[str]] = None, ): if value_type not in SUPPORTED_TAG_VALUE_TYPES: raise ValueError( "value_type = {!r} is unknown, should be one of {}".format( value_type, SUPPORTED_TAG_VALUE_TYPES ) ) self._name = name self._value_type = value_type self._possible_values = possible_values self._color = random_rgb() if color is None else deepcopy(color) self._sly_id = sly_id self._hotkey = take_with_default(hotkey, "") self._applicable_to = take_with_default(applicable_to, TagApplicableTo.ALL) self._applicable_classes = take_with_default(applicable_classes, []) if self._applicable_to not in SUPPORTED_APPLICABLE_TO: raise ValueError( "applicable_to = {!r} is unknown, should be one of {}".format( self._applicable_to, SUPPORTED_APPLICABLE_TO ) ) if self._value_type == TagValueType.ONEOF_STRING: if self._possible_values is None: raise ValueError( "TagValueType is ONEOF_STRING. List of possible values have to be defined." ) if not all(isinstance(item, str) for item in self._possible_values): raise ValueError( "TagValueType is ONEOF_STRING. All possible values have to be strings" ) elif self._possible_values is not None: raise ValueError( "TagValueType is {!r}. possible_values variable have to be None".format( self._value_type ) ) _validate_color(self._color) @property def name(self) -> str: """ Name. :return: Name :rtype: :class:`str` :Usage example: .. code-block:: python meta_dog = sly.TagMeta('dog', sly.TagValueType.ANY_STRING) print(meta_dog.name) # Output: 'dog' """ return self._name def key(self) -> str: return self.name @property def value_type(self) -> str: """ Value type. See possible value types in :class:`TagValueType<TagValueType>`. :return: Value type :rtype: :class:`str` :Usage example: .. code-block:: python meta_dog = sly.TagMeta('dog', sly.TagValueType.ANY_STRING) meta_dog.value_type == sly.TagValueType.ANY_STRING # True print(meta_dog.value_type) # Output: 'any_string' """ return self._value_type @property def possible_values(self) -> List[str]: """ Possible values of object. This is a required field if object has "oneof_string" value type. :raise: :class:`ValueError` if list of possible values is not defined or TagMeta value_type is not "oneof_string". :return: List of possible values :rtype: :class:`List[str]` :Usage example: .. code-block:: python # List of possible values coat_colors = ["brown", "white", "black", "red", "chocolate", "gold", "grey"] # TagMeta meta_coat_color = sly.TagMeta('coat color', sly.TagValueType.ONEOF_STRING, possible_values=coat_colors) print(meta_coat_color.possible_values) # Output: ['brown', 'white', 'black', 'red', 'chocolate', 'gold', 'grey'] # Note that this is a required field if object has "oneof_string" value type. meta_coat_color = sly.TagMeta('coat color', sly.TagValueType.ONEOF_STRING) # Output: ValueError: TagValueType is ONEOF_STRING. List of possible values have to be defined. """ return self._possible_values.copy() if self._possible_values is not None else None @property def color(self) -> List[int, int, int]: """ :class:`[R,G,B]` color. :return: Color :rtype: :class:`List[int, int, int]` :Usage example: .. code-block:: python meta_dog = sly.TagMeta('dog', sly.TagValueType.NONE, color=[255,120,0]) print(meta_dog.color) # Output: [255,120,0] """ return self._color.copy() @property def sly_id(self) -> int: """ Tag ID in Supervisely server. :return: ID :rtype: :class:`int` :Usage example: .. code-block:: python meta_dog = sly.TagMeta('dog', sly.TagValueType.NONE, sly_id=38584) print(meta_dog.sly_id) # Output: 38584 """ return self._sly_id @property def hotkey(self) -> str: """ Hotkey for Tag in annotation tool UI. :return: Hotkey :rtype: :class:`str` :Usage example: .. code-block:: python meta_dog = sly.TagMeta('dog', sly.TagValueType.NONE, hotkey='M') print(meta_dog.hotkey) # Output: 'M' """ return self._hotkey @property def applicable_to(self) -> str: """ Tag applicability to objects, images, or both. :return: Applicability :rtype: :class:`str` :Usage example: .. code-block:: python meta_dog = sly.TagMeta('dog', sly.TagValueType.NONE, applicable_to=IMAGES_ONLY) print(meta_dog.applicable_to) # Output: 'imagesOnly' """ return self._applicable_to @property def applicable_classes(self) -> List[str]: """ Applicable classes. :returns: List of applicable classes :rtype: :class:`List[str]` :Usage example: .. code-block:: python # Imagine we have 2 ObjClasses in our Project class_car = sly.ObjClass(name='car', geometry_type='rectangle') class_bicycle = sly.ObjClass(name='bicycle', geometry_type='rectangle') # You can put a "string" with ObjClass name or use ObjClass.name meta_vehicle = sly.TagMeta('vehicle', sly.TagValueType.NONE, applicable_classes=["car", class_bicycle.name]) print(meta_vehicle.applicable_classes) # Output: ['car', 'bicycle'] """ return self._applicable_classes
[docs] def to_json(self) -> Dict: """ Convert the TagMeta to a json dict. Read more about `Supervisely format <https://docs.supervise.ly/data-organization/00_ann_format_navi>`_. :return: Json format as a dict :rtype: :class:`dict` :Usage example: .. code-block:: python import supervisely as sly colors = ["brown", "white", "black", "red", "blue", "yellow", "grey"] meta_color = sly.TagMeta('Color', sly.TagValueType.ONEOF_STRING, possible_values=colors, color=[255, 120, 0], hotkey="M", applicable_classes=["car", "bicycle"]) meta_color_json = meta_color.to_json() print(meta_color_json) # Output: { # "name":"Color", # "value_type":"oneof_string", # "color":"#FF7800", # "values":[ # "brown", # "white", # "black", # "red", # "blue", # "yellow", # "grey" # ], # "hotkey":"M", # "applicable_type":"all", # "classes":[ # "car", # "bicycle" # ] # } """ jdict = { TagMetaJsonFields.NAME: self.name, TagMetaJsonFields.VALUE_TYPE: self.value_type, TagMetaJsonFields.COLOR: rgb2hex(self.color), } if self.value_type == TagValueType.ONEOF_STRING: jdict[TagMetaJsonFields.VALUES] = self.possible_values if self.sly_id is not None: jdict[TagMetaJsonFields.ID] = self.sly_id if self._hotkey is not None: jdict[TagMetaJsonFields.HOTKEY] = self.hotkey if self._applicable_to is not None: jdict[TagMetaJsonFields.APPLICABLE_TYPE] = self.applicable_to if self._applicable_classes is not None: jdict[TagMetaJsonFields.APPLICABLE_CLASSES] = self.applicable_classes return jdict
[docs] @classmethod def from_json(cls, data: Dict) -> TagMeta: """ Convert a json dict to TagMeta. Read more about `Supervisely format <https://docs.supervise.ly/data-organization/00_ann_format_navi>`_. :param data: TagMeta in json format as a dict. :type data: dict :return: TagMeta object :rtype: :class:`TagMeta<TagMeta>` :Usage example: .. code-block:: python import supervisely as sly data = { "name":"Color", "value_type":"oneof_string", "color":"#FF7800", "values":[ "brown", "white", "black", "red", "blue", "yellow", "grey" ], "hotkey":"M", "applicable_type":"all", "classes":[ "car", "bicycle" ] } meta_colors = sly.TagMeta.from_json(data) """ if isinstance(data, str): return cls(name=data, value_type=TagValueType.NONE) elif isinstance(data, dict): name = data[TagMetaJsonFields.NAME] value_type = data[TagMetaJsonFields.VALUE_TYPE] values = data.get(TagMetaJsonFields.VALUES) color = data.get(TagMetaJsonFields.COLOR) if color is not None: color = hex2rgb(color) sly_id = data.get(TagMetaJsonFields.ID, None) hotkey = data.get(TagMetaJsonFields.HOTKEY, "") applicable_to = data.get(TagMetaJsonFields.APPLICABLE_TYPE, TagApplicableTo.ALL) applicable_classes = data.get(TagMetaJsonFields.APPLICABLE_CLASSES, []) return cls( name=name, value_type=value_type, possible_values=values, color=color, sly_id=sly_id, hotkey=hotkey, applicable_to=applicable_to, applicable_classes=applicable_classes, ) else: raise ValueError("Tags must be dict or str types.")
[docs] def add_possible_value(self, value: str) -> TagMeta: """ Adds a new value to the list of possible values. :param value: New value that will be added to a list. :type value: str :raises: :class:`ValueError`, if object's value type is not "oneof_string" or already exists in a list :return: New instance of TagMeta :rtype: :class:`TagMeta<TagMeta>` :Usage Example: .. code-block:: python import supervisely as sly #In order to add possible values, you must first initialize a variable where all possible values will be stored if it doesnt exist already colors = ["brown", "white", "black", "red", "chocolate", "gold", "grey"] meta_coat_color = sly.TagMeta('coat color', sly.TagValueType.ONEOF_STRING, possible_values=colors, applicable_classes=["dog", "cat"]) print(meta_coat_color.possible_values) # Output: ['brown', 'white', 'black', 'red', 'chocolate', 'gold', 'grey'] #Now we can add new possible value to our TagMeta # Remember that TagMeta object is immutable, and we need to assign new instance of TagMeta to a new variable meta_coat_color = meta_coat_color.add_possible_value("bald (no coat)") print(meta_coat_color.possible_values) # Output: ['brown', 'white', 'black', 'red', 'chocolate', 'gold', 'grey', 'bald (no coat)'] """ if self.value_type is TagValueType.ONEOF_STRING: if value in self._possible_values: raise ValueError("Value {} already exists for tag {}".format(value, self.name)) else: return self.clone(possible_values=[*self.possible_values, value]) else: raise ValueError( "Tag {!r} has type {!r}. Possible value can be added only to oneof_string".format( self.name, self.value_type ) )
[docs] def is_valid_value(self, value: str) -> bool: """ Checks value against object value type to make sure that value is valid. :param value: Value to check. :type value: str :return: True if value is supported, otherwise False :rtype: :class:`bool` :Usage example: .. code-block:: python import supervisely as sly # Initialize TagMeta meta_dog = sly.TagMeta('dog', sly.TagValueType.ANY_STRING) # Check what value type is in our Tagmeta print(meta_dog.value_type) # Output: 'any_string' # Our TagMeta has 'any_string' value type, it means only 'string' values will work with it # Let's check if value is valid for our TagMeta meta_dog.is_valid_value('Woof!') # True meta_dog.is_valid_value(555) # False # TagMetas with 'any_number' value type are compatible with 'int' and 'float' values meta_quantity = sly.TagMeta('quantity', sly.TagValueType.ANY_NUMBER) meta_quantity.is_valid_value('new string value') # False meta_quantity.is_valid_value(555) # True meta_quantity.is_valid_value(3.14159265359) # True """ if self.value_type == TagValueType.NONE: return value is None elif self.value_type == TagValueType.ANY_NUMBER: return isinstance(value, (int, float)) elif self.value_type == TagValueType.ANY_STRING: return isinstance(value, str) elif self.value_type == TagValueType.ONEOF_STRING: return isinstance(value, str) and (value in self._possible_values) else: raise ValueError("Unsupported TagValueType detected ({})!".format(self.value_type))
def __eq__(self, other: TagMeta) -> bool: """ Checks that 2 TagMetas are equal by their name, value type and possible values. :param other: TagMeta object. :type other: TagMeta :return: True if comparable objects are equal, otherwise False :rtype: :class:`bool` :Usage example: .. code-block:: python import supervisely as sly # Let's create 2 identical TagMetas meta_lemon_1 = sly.TagMeta('Lemon', sly.TagValueType.NONE) meta_lemon_2 = sly.TagMeta('Lemon', sly.TagValueType.NONE) # and 1 different TagMeta and compare them to each other meta_cucumber = sly.TagMeta('Cucumber', sly.TagValueType.ANY_STRING) # Compare identical TagMetas meta_lemon_1 == meta_lemon_2 # True # Compare unidentical TagMetas meta_lemon_1 == meta_cucumber # False """ # TODO compare colors also here (need to check the usages and replace with is_compatible() where appropriate). return ( isinstance(other, TagMeta) and self.name == other.name and self.value_type == other.value_type and self.possible_values == other.possible_values ) def __ne__(self, other: TagMeta) -> bool: """ Checks that 2 TagMetas are opposite. :param other: TagMeta object. :type other: TagMeta :return: True if comparable objects are not equal, otherwise False :rtype: :class:`bool` :Usage example: .. code-block:: python import supervisely as sly # Let's create 2 identical TagMetas meta_lemon_1 = sly.TagMeta('Lemon', sly.TagValueType.NONE) meta_lemon_2 = sly.TagMeta('Lemon', sly.TagValueType.NONE) # and 1 different TagMeta and compare them to each other meta_cucumber = sly.TagMeta('Cucumber', sly.TagValueType.ANY_STRING) # Compare identical TagMetas meta_lemon_1 != meta_lemon_2 # False # Compare unidentical TagMetas meta_lemon_1 != meta_cucumber # True """ return not self == other
[docs] def is_compatible(self, other: TagMeta) -> bool: """is_compatible""" return ( isinstance(other, TagMeta) and self.name == other.name and self.value_type == other.value_type and self.possible_values == other.possible_values )
[docs] def clone( self, name: Optional[str] = None, value_type: Optional[str] = None, possible_values: Optional[List[str]] = None, color: Optional[List[int, int, int]] = None, sly_id: Optional[int] = None, hotkey: Optional[str] = None, applicable_to: Optional[str] = None, applicable_classes: Optional[List[str]] = None, ) -> TagMeta: """ Clone makes a copy of TagMeta with new fields, if fields are given, otherwise it will use original TagMeta fields. :param name: Tag name. :type name: str :param value_type: Tag value type. :type value_type: str :param possible_values: List of possible values. :type possible_values: List[str], optional :param color: :class:`[R, G, B]` color, generates random color by default. :type color: List[int, int, int], optional :param sly_id: Tag ID in Supervisely server. :type sly_id: int, optional :param hotkey: Hotkey for Tag in annotation tool UI. :type hotkey: str, optional :param applicable_to: Defines applicability of Tag only to images, objects or both. :type applicable_to: str, optional :param applicable_classes: Defines applicability of Tag only to certain classes. :type applicable_classes: List[str], optional :return: New instance of TagMeta :rtype: :class:`TagMeta<TagMeta>` :Usage Example: .. code-block:: python import supervisely as sly #Original TagMeta meta_dog_breed = sly.TagMeta('breed', sly.TagValueType.NONE) # TagMetas made of original TagMeta # Remember that TagMeta class object is immutable, and we need to assign new instance of TagMeta to a new variable A_breeds = ["Affenpinscher", "Afghan Hound", "Aidi", "Airedale Terrier", "Akbash Dog", "Akita"] meta_A_breed = meta_dog_breed.clone(value_type=sly.TagValueType.ONEOF_STRING, possible_values=A_breeds, hotkey='A') B_breeds = ["Basset Fauve de Bretagne", "Basset Hound", "Bavarian Mountain Hound", "Beagle", "Beagle-Harrier", "Bearded Collie"] meta_B_breed = meta_A_breed.clone(possible_values=B_breeds, hotkey='B') C_breeds = ["Cairn Terrier", "Canaan Dog", "Canadian Eskimo Dog", "Cane Corso", "Cardigan Welsh Corgi", "Carolina Dog"] meta_C_breed = meta_B_breed.clone(possible_values=C_breeds, hotkey='C') """ return TagMeta( name=take_with_default(name, self.name), value_type=take_with_default(value_type, self.value_type), possible_values=take_with_default(possible_values, self.possible_values), color=take_with_default(color, self.color), sly_id=take_with_default(sly_id, self.sly_id), hotkey=take_with_default(hotkey, self.hotkey), applicable_to=take_with_default(applicable_to, self.applicable_to), applicable_classes=take_with_default(applicable_classes, self.applicable_classes), )
def __str__(self): return ( "{:<7s}{:<24} {:<7s}{:<13} {:<13s}{:<10} {:<13s}{:<10} {:<13s}{:<10} {:<13s}{}".format( "Name:", self.name, "Value type:", self.value_type, "Possible values:", str(self.possible_values), "Hotkey", self.hotkey, "Applicable to", self.applicable_to, "Applicable classes", self.applicable_classes, ) )
[docs] @classmethod def get_header_ptable(cls): """get_header_ptable""" return [ "Name", "Value type", "Possible values", "Hotkey", "Applicable to", "Applicable classes", ]
[docs] def get_row_ptable(self): """get_row_ptable""" return [ self.name, self.value_type, self.possible_values, self.hotkey, self.applicable_to, self.applicable_classes, ]
def _set_id(self, id: int): self._sly_id = id