Source code for supervisely.annotation.label

# coding: utf-8
"""Labeling object for :class:`Annotation<supervisely.annotation.annotation.Annotation>`"""

# docs
from __future__ import annotations

from copy import deepcopy
from typing import Dict, List, Optional, Tuple, Union

import numpy as np
from PIL.ImageFont import FreeTypeFont

from supervisely._utils import take_with_default
from supervisely.annotation.json_geometries_map import GET_GEOMETRY_FROM_STR
from supervisely.annotation.obj_class import ObjClass
from supervisely.annotation.tag import Tag
from supervisely.annotation.tag_collection import TagCollection
from supervisely.geometry.any_geometry import AnyGeometry
from supervisely.geometry.constants import GEOMETRY_SHAPE, GEOMETRY_TYPE
from supervisely.geometry.geometry import Geometry
from supervisely.geometry.image_rotator import ImageRotator
from supervisely.geometry.multichannel_bitmap import MultichannelBitmap
from supervisely.geometry.rectangle import Rectangle
from supervisely.imaging import font as sly_font
from supervisely.imaging import image as sly_image
from supervisely.project.project_meta import ProjectMeta


[docs]class LabelJsonFields: """Json fields for :class:`Annotation<supervisely.annotation.label.Label>`""" OBJ_CLASS_NAME = "classTitle" """""" OBJ_CLASS_ID = "classId" """""" DESCRIPTION = "description" """""" TAGS = "tags" """""" INSTANCE_KEY = "instance" """""" ID = "id" """""" GEOMETRY_TYPE = "geometryType" """""" SMART_TOOL_INPUT = "smartToolInput" """"""
class LabelBase: """ Labeling object for :class:`Annotation<supervisely.annotation.annotation.Annotation>`. :class:`Label<Label>` object is immutable. :param geometry: Label :class:`geometry<supervisely.geometry.geometry.Geometry>`. :type geometry: Geometry :param obj_class: Label :class:`class<supervisely.annotation.obj_class.ObjClass>`. :type obj_class: ObjClass :param tags: Label :class:`tags<supervisely.annotation.tag_collection.TagCollection>`. :type tags: TagCollection or List[Tag] :param description: Label description. :type description: str, optional :param binding_key: Label binding key. :type binding_key: str, optional :param smart_tool_input: Smart Tool parameters that were used for labeling. :type smart_tool_input: dict, optional :param sly_id: Label unique identifier. :type sly_id: int, optional :Usage example: .. code-block:: python import supervisely as sly # Simple Label example class_kiwi = sly.ObjClass('kiwi', sly.Rectangle) figure = sly.Rectangle(0, 0, 300, 300) label_kiwi = sly.Label(figure, class_kiwi) # More complex Label example # Tag meta_kiwi = sly.TagMeta('kiwi_tag', sly.TagValueType.ANY_STRING) tag_kiwi = sly.Tag(meta_kiwi, 'Hello') # ObjClass class_kiwi = sly.ObjClass('kiwi', sly.Rectangle) # Label geometry_figure = sly.Rectangle(0, 0, 300, 300) label = sly.Label(figure, class_kiwi, sly.TagCollection([tag_kiwi]), 'Label description') # or sly.Label(figure, class_kiwi, [tag_kiwi], 'Label description') """ def __init__( self, geometry: Geometry, obj_class: ObjClass, tags: Optional[Union[TagCollection, List[Tag]]] = None, description: Optional[str] = "", binding_key: Optional[str] = None, smart_tool_input: Optional[Dict] = None, sly_id: Optional[int] = None, ): self._geometry = geometry self._obj_class = obj_class self._tags = take_with_default(tags, TagCollection()) self._description = description self._validate_geometry_type() self._validate_geometry() if not isinstance(tags, TagCollection): self._tags = TagCollection(tags) self._binding_key = binding_key self._smart_tool_input = smart_tool_input self._sly_id = sly_id def _validate_geometry(self): """ The function checks the name of the Object for compliance. :return: generate ValueError error if name is mismatch """ self._geometry.validate( self._obj_class.geometry_type.geometry_name(), self.obj_class.geometry_config, ) def _validate_geometry_type(self): raise NotImplementedError() @property def obj_class(self) -> ObjClass: """ ObjClass of the current Label. :return: ObjClass object :rtype: :class:`ObjClass<supervisely.annotation.obj_class.ObjClass>` :Usage example: .. code-block:: python class_dog = sly.ObjClass('dog', sly.Rectangle) label_dog = sly.Label(sly.Rectangle(150, 150, 400, 500), class_dog) label_dog_json = label_dog.obj_class.to_json() print(label_dog_json) # Output: { # "title": "dog", # "shape": "rectangle", # "color": "#0F8A12", # "geometry_config": {}, # "hotkey": "" # } """ return self._obj_class @property def description(self) -> str: """ Description of the current Label. :return: Description :rtype: :class:`str` :Usage example: .. code-block:: python class_dog = sly.ObjClass('dog', sly.Rectangle) label_dog = sly.Label(sly.Rectangle(150, 150, 400, 500), class_dog, description="Insert Label description here") print(label_dog.description) # Output: 'Insert Label description here' """ return self._description @property def geometry(self) -> Geometry: """ Geometry of the current Label. :return: Geometry object :rtype: :class:`Geometry<supervisely.geometry>` :Usage example: .. code-block:: python class_dog = sly.ObjClass('dog', sly.Rectangle) label_dog = sly.Label(sly.Rectangle(150, 150, 400, 500), class_dog) label_json = label_dog.geometry.to_json() print(label_json) # Output: { # "points": { # "exterior": [ # [ # 150, # 150 # ], # [ # 500, # 400 # ] # ], # "interior": [] # } # } """ return self._geometry @property def tags(self) -> TagCollection: """ TagCollection of the current Label. :return: TagCollection object :rtype: :class:`TagCollection<supervisely.annotation.tag.TagCollection>` :Usage example: .. code-block:: python meta_dog = sly.TagMeta('dog', sly.TagValueType.ANY_STRING) tag_dog = sly.Tag(meta_dog, 'Woof') class_dog = sly.ObjClass('dog', sly.Rectangle) label_dog = sly.Label(sly.Rectangle(100, 100, 700, 900), class_dog, sly.TagCollection([tag_dog])) label_dog_json = label_dog.tags.to_json() print(label_dog_json) # Output: [ # { # "name": "dog", # "value": "Woof" # } # ] """ return self._tags.clone() def to_json(self) -> Dict: """ Convert the Label 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 meta_dog = sly.TagMeta('dog', sly.TagValueType.ANY_STRING) tag_dog = sly.Tag(meta_dog, 'Woof') class_dog = sly.ObjClass('dog', sly.Rectangle) label_dog = sly.Label(sly.Rectangle(100, 100, 700, 900), class_dog, sly.TagCollection([tag_dog]), description='Insert Label description here') label_dog_json = label_dog.to_json() print(label_dog_json) # Output: { # "classTitle": "dog", # "description": "", # "tags": [ # { # "name": "dog", # "value": "Woof" # } # ], # "points": { # "exterior": [[100, 100],[900, 700]], # "interior": [] # }, # "geometryType": "rectangle", # "shape": "rectangle" # } """ res = { LabelJsonFields.OBJ_CLASS_NAME: self.obj_class.name, LabelJsonFields.DESCRIPTION: self.description, LabelJsonFields.TAGS: self.tags.to_json(), **self.geometry.to_json(), GEOMETRY_TYPE: self.geometry.geometry_name(), GEOMETRY_SHAPE: self.geometry.geometry_name(), } if self.obj_class.sly_id is not None: res[LabelJsonFields.OBJ_CLASS_ID] = self.obj_class.sly_id if self.binding_key is not None: res[LabelJsonFields.INSTANCE_KEY] = self.binding_key if self._smart_tool_input is not None: res[LabelJsonFields.SMART_TOOL_INPUT] = self._smart_tool_input if self.sly_id is not None: res[LabelJsonFields.ID] = self.sly_id return res @classmethod def from_json(cls, data: Dict, project_meta: ProjectMeta) -> LabelBase: """ Convert a json dict to Label. Read more about `Supervisely format <https://docs.supervise.ly/data-organization/00_ann_format_navi>`_. :param data: Label in json format as a dict. :type data: dict :param project_meta: Input ProjectMeta. :type project_meta: ProjectMeta :return: Label object :rtype: :class:`Label<LabelBase>` :Usage example: .. code-block:: python import supervisely as sly address = 'https://app.supervise.ly/' token = 'Your Supervisely API Token' api = sly.Api(address, token) meta = api.project.get_meta(PROJECT_ID) data = { "classTitle": "dog", "tags": [ { "name": "dog", "value": "Woof" } ], "points": { "exterior": [[100, 100], [900, 700]], "interior": [] } } label_dog = sly.Label.from_json(data, 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 Label object from JSON: " f"label class name {obj_class_name} was not found in the given project meta." ) if obj_class.geometry_type is AnyGeometry: geometry_type_actual = GET_GEOMETRY_FROM_STR( data[GEOMETRY_TYPE] if GEOMETRY_TYPE in data else data[GEOMETRY_SHAPE] ) geometry = geometry_type_actual.from_json(data) else: geometry = obj_class.geometry_type.from_json(data) binding_key = data.get(LabelJsonFields.INSTANCE_KEY) smart_tool_input = data.get(LabelJsonFields.SMART_TOOL_INPUT) return cls( geometry=geometry, obj_class=obj_class, tags=TagCollection.from_json(data[LabelJsonFields.TAGS], project_meta.tag_metas), description=data.get(LabelJsonFields.DESCRIPTION, ""), binding_key=binding_key, smart_tool_input=smart_tool_input, sly_id=data.get(LabelJsonFields.ID), ) @property def sly_id(self) -> Optional[int]: """Returns the unique identifier of the Label on Supervisely platform. NOTE: This can be None, when working with local project. :return: Label unique identifier. :rtype: :class:`int` or :class:`NoneType` """ return self._sly_id def add_tag(self, tag: Tag) -> LabelBase: """ Adds Tag to the current Label. :param tag: Tag to be added. :type tag: Tag :return: Label object :rtype: :class:`Label<LabelBase>` :Usage example: .. code-block:: python import supervisely as sly # Create label class_dog = sly.ObjClass('dog', sly.Rectangle) label_dog = sly.Label(sly.Rectangle(0, 0, 500, 600), class_dog) # Create tag meta_dog = sly.TagMeta('dog', sly.TagValueType.NONE) tag_dog = sly.Tag(meta_dog) # Add Tag # Remember that Label object is immutable, and we need to assign new instance of Label to a new variable new_label = label_dog.add_tag(tag_dog) """ return self.clone(tags=self._tags.add(tag)) def add_tags(self, tags: List[Tag]) -> LabelBase: """ Adds multiple Tags to the current Label. :param tags: List of Tags to be added. :type tags: List[Tag] :return: Label object :rtype: :class:`Label<LabelBase>` :Usage example: .. code-block:: python import supervisely as sly # Create label class_dog = sly.ObjClass('dog', sly.Rectangle) label_dog = sly.Label(sly.Rectangle(0, 0, 500, 600), class_dog) # Create tags meta_dog = sly.TagMeta('dog', sly.TagValueType.NONE) tag_dog = sly.Tag(meta_dog) meta_cat = sly.TagMeta('cat', sly.TagValueType.NONE) tag_cat = sly.Tag(meta_cat) tags_arr = [tag_dog, tag_cat] # Add Tags # Remember that Label object is immutable, and we need to assign new instance of Label to a new variable new_label = label_dog.add_tags(tags_arr) """ return self.clone(tags=self._tags.add_items(tags)) def clone( self, geometry: Optional[Geometry] = None, obj_class: Optional[ObjClass] = None, tags: Optional[Union[TagCollection, List[Tag]]] = None, description: Optional[str] = None, binding_key: Optional[str] = None, smart_tool_input: Optional[Dict] = None, ) -> LabelBase: """ Makes a copy of Label with new fields, if fields are given, otherwise it will use fields of the original Label. :param geometry: Label :class:`geometry<supervisely.geometry.geometry.Geometry>`. :type geometry: Geometry :param obj_class: Label :class:`class<supervisely.annotation.obj_class.ObjClass>`. :type obj_class: ObjClass :param tags: Label :class:`tags<supervisely.annotation.tag.TagCollection>`. :type tags: TagCollection or List[Tag] :param description: Label description. :type description: str, optional :param binding_key: Label binding key. :type binding_key: str, optional :param smart_tool_input: Smart Tool parameters that were used for labeling. :type smart_tool_input: dict, optional :return: New instance of Label :rtype: :class:`Label<LabelBase>` :Usage example: .. code-block:: python import supervisely as sly import numpy as np # Original Label class_dog = sly.ObjClass('dog', sly.Rectangle) label_dog = sly.Label(sly.Rectangle(150, 150, 500, 400), class_dog) # Let's clone our Label, but with different Geometry coordinates # Remember that Label object is immutable, and we need to assign new instance of Label to a new variable clone_label_dog = label_dog.clone(sly.Rectangle(100, 100, 500, 500), class_dog) # Let's clone our Label with new TagCollection and description meta_breed = sly.TagMeta('breed', sly.TagValueType.ANY_STRING) tag_breed = sly.Tag(meta_breed, 'German Shepherd') tags = sly.TagCollection([tag_breed]) # Remember that Label object is immutable, and we need to assign new instance of Label to a new variable clone_label_dog_2 = label_dog.clone(tags=tags, description='Dog breed german shepherd') # Note that you can't clone Label if ObjClass geometry type differ from the new given geometry mask = np.array([[0, 0, 0, 0, 0], [0, 1, 1, 1, 0], [0, 1, 0, 1, 0], [0, 1, 1, 1, 0], [0, 0, 0, 0, 0]], dtype=np.uint8) mask_bool = mask==1 clone_label_dog = label_dog.clone(sly.Label(sly.Bitmap(mask_bool), class_dog)) # In this case RuntimeError will be raised """ return self.__class__( 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), binding_key=take_with_default(binding_key, self.binding_key), smart_tool_input=take_with_default(smart_tool_input, self._smart_tool_input), ) def crop(self, rect: Rectangle) -> List[LabelBase]: """ Clones the current Label and crops it. Mostly used for internal implementation. See usage example in :class:`Annotation<supervisely.annotation.annotation.Annotation.crop_labels>`. :param rect: Rectangle object. :type rect: Rectangle :return: List of Labels with new geometries :rtype: :class:`List[Label]<LabelBase>` """ if rect.contains(self.geometry.to_bbox()): return [self] else: # for compatibility of old slightly invalid annotations, some of them may be out of image bounds. # will correct it automatically result_geometries = self.geometry.crop(rect) if len(result_geometries) == 1: result_geometries[0]._copy_creation_info_inplace(self.geometry) return [self.clone(geometry=result_geometries[0])] else: return [self.clone(geometry=g) for g in self.geometry.crop(rect)] def relative_crop(self, rect: Rectangle) -> List[LabelBase]: """ Clones the current Label and crops it, but return results with coordinates relative to the given Rectangle. Mostly used for internal implementation. See usage example in :class:`Annotation<supervisely.annotation.annotation.Annotation.relative_crop>`. :param rect: Rectangle object. :type rect: Rectangle :return: List of Labels with new geometries :rtype: :class:`List[Label]<LabelBase>` """ return [self.clone(geometry=g) for g in self.geometry.relative_crop(rect)] def rotate(self, rotator: ImageRotator) -> LabelBase: """ Clones the current Label and rotates it. Mostly used for internal implementation. See usage example in :class:`Annotation<supervisely.annotation.annotation.Annotation.rotate>`. :param rotator: ImageRotator object. :type rotator: ImageRotator :return: New instance of Label with rotated geometry :rtype: :class:`Label<LabelBase>` """ return self.clone(geometry=self.geometry.rotate(rotator)) def resize(self, in_size: Tuple[int, int], out_size: Tuple[int, int]) -> LabelBase: """ Clones the current Label and resizes it. Mostly used for internal implementation. See usage example in :class:`Annotation<supervisely.annotation.annotation.Annotation.resize>`. :param in_size: Input image size (height, width) of the Annotation to which Label belongs. :type in_size: Tuple[int, int] :param out_size: Desired output image size (height, width) of the Annotation to which Label belongs. :type out_size: Tuple[int, int] :return: New instance of Label with resized geometry :rtype: :class:`Label<LabelBase>` """ return self.clone(geometry=self.geometry.resize(in_size, out_size)) def scale(self, factor: float) -> LabelBase: """ Clones the current Label and scales it. Mostly used for internal implementation. See usage example in :class:`Annotation<supervisely.annotation.annotation.Annotation.scale>`. :param factor: Scale factor. :type factor: float :return: New instance of Label with scaled geometry :rtype: :class:`Label<LabelBase>` """ return self.clone(geometry=self.geometry.scale(factor)) def translate(self, drow: int, dcol: int) -> LabelBase: """ Clones the current Label and shifts it by a certain number of pixels. Mostly used for internal implementation. :param drow: Horizontal shift. :type drow: int :param dcol: Vertical shift. :type dcol: int :return: New instance of Label with translated geometry :rtype: :class:`Label<LabelBase>` :Usage example: .. code-block:: python import supervisely as sly address = 'https://app.supervise.ly/' token = 'Your Supervisely API Token' api = sly.Api(address, token) # Get image and annotation from API project_id = 117139 image_id = 194190568 meta_json = api.project.get_meta(project_id) meta = sly.ProjectMeta.from_json(meta_json) ann_info = api.annotation.download(image_id) ann = sly.Annotation.from_json(ann_info.annotation, meta) img = api.image.download_np(image_id) new_img = copy.deepcopy(img) ann.draw_pretty(img, thickness=3) # before # Translate label with name 'lemon' new_labels = [] for label in ann.labels: if label.obj_class.name == 'lemon': translate_label = label.translate(250, -350) new_labels.append(translate_label) else: new_labels.append(label) ann = ann.clone(labels=new_labels) ann.draw_pretty(new_img, thickness=3) # after """ return self.clone(geometry=self.geometry.translate(drow=drow, dcol=dcol)) def fliplr(self, img_size: Tuple[int, int]) -> LabelBase: """ Clones the current Label and flips it horizontally. Mostly used for internal implementation. See usage example in :class:`Annotation<supervisely.annotation.annotation.Annotation.fliplr>`. :param img_size: Input image size (height, width) of the Annotation to which Label belongs. :type img_size: Tuple[int, int] :return: New instance of Label with flipped geometry :rtype: :class:`Label<LabelBase>` """ return self.clone(geometry=self.geometry.fliplr(img_size)) def flipud(self, img_size: Tuple[int, int]) -> LabelBase: """ Clones the current Label and flips it vertically. Mostly used for internal implementation. See usage example in :class:`Annotation<supervisely.annotation.annotation.Annotation.flipud>`. :param img_size: Input image size (height, width) of the Annotation to which Label belongs. :type img_size: Tuple[int, int] :return: New instance of Label with flipped geometry :rtype: :class:`Label<LabelBase>` """ return self.clone(geometry=self.geometry.flipud(img_size)) def _get_font(self, img_size): """ The function get size of font for image with given size :return: font for drawing """ return sly_font.get_font(font_size=sly_font.get_readable_font_size(img_size)) def _draw_tags(self, bitmap, font, add_class_name=False): bbox = self.geometry.to_bbox() texts = [t.get_compact_str() for t in self.tags] texts = texts if add_class_name is False else [self.obj_class.name] + texts sly_image.draw_text_sequence( bitmap=bitmap, texts=texts, anchor_point=(bbox.top, bbox.left), corner_snap=sly_image.CornerAnchorMode.BOTTOM_LEFT, font=font, ) def _draw_class_name(self, bitmap, font): bbox = self.geometry.to_bbox() sly_image.draw_text( bitmap, self.obj_class.name, (bbox.top, bbox.left), corner_snap=sly_image.CornerAnchorMode.BOTTOM_LEFT, font=font, ) def draw( self, bitmap: np.ndarray, color: Optional[List[int, int, int]] = None, thickness: Optional[int] = 1, draw_tags: Optional[bool] = False, tags_font: Optional[FreeTypeFont] = None, draw_class_name: Optional[bool] = False, class_name_font: Optional[FreeTypeFont] = None, ) -> None: """ Draws current Label on image. Modifies Mask. Mostly used for internal implementation. See usage example in :class:`Annotation<supervisely.annotation.annotation.Annotation.draw>`. :param bitmap: image. :type bitmap: np.ndarray :param color: Drawing color in :class:`[R, G, B]`. :type color: List[int, int, int], optional :param thickness: Thickness of the drawing figure. :type thickness: int, optional :param draw_tags: Determines whether to draw tags on bitmap or not. :type draw_tags: bool, optional :param tags_font: Font of tags to be drawn, uses `FreeTypeFont <https://pillow.readthedocs.io/en/stable/reference/ImageFont.html#PIL.ImageFont.FreeTypeFont>`_ from `PIL <https://pillow.readthedocs.io/en/stable/index.html>`_. :type tags_font: FreeTypeFont, optional :param draw_class_name: Draw the class name on the bitmap. If the draw_tags parameter is set to True, the class name will use the same font as the tags_font. :type draw_class_name: bool, optional :param class_name_font: Font of class name to be drawn, uses `FreeTypeFont <https://pillow.readthedocs.io/en/stable/reference/ImageFont.html#PIL.ImageFont.FreeTypeFont>`_ from `PIL <https://pillow.readthedocs.io/en/stable/index.html>`_. :type class_name_font: FreeTypeFont, optional :return: :class:`None<None>` :rtype: :class:`NoneType<NoneType>` """ effective_color = take_with_default(color, self.obj_class.color) self.geometry.draw( bitmap, effective_color, thickness, config=self.obj_class.geometry_config ) if draw_tags: if tags_font is None: tags_font = self._get_font(bitmap.shape[:2]) self._draw_tags(bitmap, tags_font, add_class_name=draw_class_name) elif draw_class_name: if class_name_font is None: class_name_font = self._get_font(bitmap.shape[:2]) self._draw_class_name(bitmap, class_name_font) def get_mask(self, img_size: Tuple[int, int]) -> np.ndarray: """Returns 2D boolean mask of the label. With shape as img_size (height, width) and filled with True values inside the label and False values outside. dtype = np.bool shape = img_size :param img_size: size of the image (height, width) :type img_size: Tuple[int, int] :return: 2D boolean mask of the Label :rtype: np.ndarray """ bitmap = np.zeros(img_size + (3,), dtype=np.uint8) self.draw(bitmap, thickness=-1, color=[255, 255, 255]) return np.any(bitmap != 0, axis=-1) def draw_contour( self, bitmap: np.ndarray, color: Optional[List[int, int, int]] = None, thickness: Optional[int] = 1, draw_tags: Optional[bool] = False, tags_font: Optional[FreeTypeFont] = None, draw_class_name: Optional[bool] = False, class_name_font: Optional[FreeTypeFont] = None, ) -> None: """ Draws Label geometry contour on the given image. Modifies mask. Mostly used for internal implementation. See usage example in :class:`Annotation<supervisely.annotation.annotation.Annotation.draw_contour>`. :param bitmap: image. :type bitmap: np.ndarray :param color: Drawing color in :class:`[R, G, B]`. :type color: List[int, int, int], optional :param thickness: Thickness of the drawn contour. :type thickness: int, optional :param draw_tags: Determines whether to draw tags on bitmap or not. :type draw_tags: bool, optional :param tags_font: Font of tags to be drawn, uses `FreeTypeFont <https://pillow.readthedocs.io/en/stable/reference/ImageFont.html#PIL.ImageFont.FreeTypeFont>`_ from `PIL <https://pillow.readthedocs.io/en/stable/index.html>`_. :type tags_font: FreeTypeFont, optional :param draw_class_name: Draw the class name on the bitmap. If the draw_tags parameter is set to True, the class name will use the same font as the tags_font. :type draw_class_name: bool, optional :param class_name_font: Font of class name to be drawn, uses `FreeTypeFont <https://pillow.readthedocs.io/en/stable/reference/ImageFont.html#PIL.ImageFont.FreeTypeFont>`_ from `PIL <https://pillow.readthedocs.io/en/stable/index.html>`_. :type class_name_font: FreeTypeFont, optional :return: :class:`None<None>` :rtype: :class:`NoneType<NoneType>` """ effective_color = take_with_default(color, self.obj_class.color) self.geometry.draw_contour( bitmap, effective_color, thickness, config=self.obj_class.geometry_config ) if draw_tags: if tags_font is None: tags_font = self._get_font(bitmap.shape[:2]) self._draw_tags(bitmap, tags_font, add_class_name=draw_class_name) elif draw_class_name: if class_name_font is None: class_name_font = self._get_font(bitmap.shape[:2]) self._draw_class_name(bitmap, class_name_font) @property def area(self) -> float: """ Label area. :return: Area of current geometry in Label. :rtype: :class:`float` :Usage example: .. code-block:: python import supervisely as sly # Create label class_dog = sly.ObjClass('dog', sly.Rectangle) label_dog = sly.Label(sly.Rectangle(0, 0, 500, 600), class_dog) figure_area = label_dog.area print(figure_area) # Output: 301101.0 """ return self.geometry.area def convert(self, new_obj_class: ObjClass) -> List[LabelBase]: """ Converts Label geometry to another geometry shape. :param new_obj_class: ObjClass with new geometry shape. :type new_obj_class: ObjClass :return: List of Labels with converted geometries :rtype: :class:`List[Label]<LabelBase>` :Usage example: .. code-block:: python import supervisely as sly # Create label class_dog = sly.ObjClass('dog', sly.Rectangle) label_dog = sly.Label(sly.Rectangle(0, 0, 500, 600), class_dog) print(label_dog.geometry.to_json()) # {'geometryType': 'rectangle'} label_cat = sly.ObjClass('cat', sly.Bitmap) convert_label = label_dog.convert(label_cat) for label_bitmap in convert_label: print(label_bitmap.geometry.to_json()) # Output: {'geometryType': 'bitmap'} """ labels = [] geometries = self.geometry.convert(new_obj_class.geometry_type) for g in geometries: labels.append(self.clone(geometry=g, obj_class=new_obj_class)) return labels @property def binding_key(self): return self._binding_key @binding_key.setter def binding_key(self, key: Union[str, None]): if key is not None and type(key) is not str: raise TypeError("Key has to be of type string or None") self._binding_key = key @property def smart_tool_input(self): """ Smart Tool parameters that were used for labeling. Example: { 'crop': [[85.69912274538524, 323.07711452375236], [1108.5635719011857, 1543.1199742240174]], 'visible': True, 'negative': [], 'positive': [[597, 933], [474.5072466934964, 1381.6437133813354]] } """ return self._smart_tool_input @smart_tool_input.setter def smart_tool_input(self, smtool_input: Dict): smtool_input_keys = ["crop", "visible", "negative", "positive"] for k in smtool_input_keys: if k not in smtool_input: raise ValueError(f"Smart tool input has to contain key '{k}'") self._smart_tool_input = smtool_input @property def labeler_login(self): return self.geometry.labeler_login @classmethod def _to_pixel_coordinate_system_json(cls, data: Dict, image_size: List[int]) -> Dict: """ Convert label geometry from subpixel precision to pixel precision by rounding the coordinates. In the labeling tool, labels are created with subpixel precision, which means that the coordinates of the geometry can have decimal values representing fractions of a pixel. However, in Supervisely SDK, geometry coordinates are represented using pixel precision, where the coordinates are integers representing whole pixels. :param data: Label in json format. :type data: :class:`dict` :param image_size: Image size in pixels (height, width). :type image_size: List[int] :return: Json data with coordinates converted to pixel coordinate system. :rtype: :class:`dict` """ data = deepcopy(data) # Avoid modifying the original data if data[LabelJsonFields.GEOMETRY_TYPE] == Rectangle.geometry_name(): data = Rectangle._to_pixel_coordinate_system_json(data, image_size) else: data = Geometry._to_pixel_coordinate_system_json(data, image_size) return data @classmethod def _to_subpixel_coordinate_system_json(cls, data: Dict) -> Dict: """ Convert label geometry from subpixel precision to pixel precision by rounding the coordinates. In the labeling tool, labels are created with subpixel precision, which means that the coordinates of the geometry can have decimal values representing fractions of a pixel. However, in Supervisely SDK, geometry coordinates are represented using pixel precision, where the coordinates are integers representing whole pixels. :param data: Label in json format. :type data: :class:`dict` :return: Json data with coordinates converted to subpixel coordinate system. :rtype: :class:`dict` """ data = deepcopy(data) # Avoid modifying the original data if data[LabelJsonFields.GEOMETRY_TYPE] == Rectangle.geometry_name(): data = Rectangle._to_subpixel_coordinate_system_json(data) else: data = Geometry._to_subpixel_coordinate_system_json(data) return data # def _to_subpixel_coordinate_system(self) -> LabelBase: # """ # Convert label geometry from pixel precision to subpixel precision by adding a subpixel offset to the coordinates. # In the labeling tool, labels are created with subpixel precision, # which means that the coordinates of the geometry can have decimal values representing fractions of a pixel. # However, in Supervisely SDK, geometry coordinates are represented using pixel precision, where the coordinates are integers representing whole pixels. # :return: New instance of Label with subpixel precision geometry # :rtype: :class:`Label<LabelBase>` # """ # new_geometry = self.geometry._to_subpixel_coordinate_system() # label = self.clone(geometry=new_geometry) # return label
[docs]class Label(LabelBase): def _validate_geometry_type(self): """ Checks geometry type for correctness """ if self._obj_class.geometry_type != AnyGeometry: if type(self._geometry) is not self._obj_class.geometry_type: raise RuntimeError( "Input geometry type {!r} != geometry type of ObjClass {}".format( type(self._geometry), self._obj_class.geometry_type ) )
class PixelwiseScoresLabel(LabelBase): def _validate_geometry_type(self): if type(self._geometry) is not MultichannelBitmap: raise RuntimeError( "Input geometry type {!r} != geometry type of ObjClass {}".format( type(self._geometry), MultichannelBitmap ) )