Source code for supervisely.annotation.annotation

# coding: utf-8
"""annotation for a single image"""

# docs
from __future__ import annotations

import itertools
import json
import operator
from collections import defaultdict
from copy import deepcopy
from typing import TYPE_CHECKING, Callable, Dict, List, Optional, Tuple, Union

import numpy as np
from PIL import Image

from supervisely import logger
from supervisely._utils import take_with_default
from supervisely.annotation.label import Label
from supervisely.annotation.obj_class import ObjClass
from supervisely.annotation.obj_class_collection import ObjClassCollection
from supervisely.annotation.tag import Tag
from supervisely.annotation.tag_collection import TagCollection
from supervisely.geometry.any_geometry import AnyGeometry
from supervisely.geometry.bitmap import Bitmap
from supervisely.geometry.image_rotator import ImageRotator
from supervisely.geometry.multichannel_bitmap import MultichannelBitmap
from supervisely.geometry.polygon import Polygon
from supervisely.geometry.rectangle import Rectangle
from supervisely.imaging import font as sly_font
from supervisely.imaging import image as sly_image
from supervisely.io.fs import ensure_base_path
from supervisely.project.project_meta import ProjectMeta

if TYPE_CHECKING:
    try:
        from imgaug.augmentables.bbs import BoundingBoxesOnImage
        from imgaug.augmentables.segmaps import SegmentationMapsOnImage
    except ModuleNotFoundError:
        pass

ANN_EXT = ".json"


[docs]class AnnotationJsonFields: """ Json fields for :class:`Annotation<supervisely.annotation.annotation.Annotation>` """ IMG_DESCRIPTION = "description" """""" IMG_SIZE = "size" """""" IMG_SIZE_WIDTH = "width" """""" IMG_SIZE_HEIGHT = "height" """""" IMG_TAGS = "tags" """""" LABELS = "objects" """""" CUSTOM_DATA = "customBigData" """""" PROBABILITY_CLASSES = "probabilityClasses" """""" PROBABILITY_LABELS = "probabilityLabels" """""" IMAGE_ID = "imageId" """"""
[docs]class Annotation: """ Annotation for a single image. :class:`Annotation<Annotation>` object is immutable. :param img_size: Size of the image (height, width). :type img_size: Tuple[int, int] or List[int, int] :param labels: List of Label objects. :type labels: List[Label] :param img_tags: TagCollection object or list of Tag objects. :type img_tags: TagCollection or List[Tag] :param img_description: Image description. :type img_description: str, optional :raises: :class:`TypeError`, if image size is not tuple or list :Usage example: .. code-block:: python # Simple Annotation example import supervisely as sly height, width = 500, 700 ann = sly.Annotation((height, width)) # More complex Annotation example # TagCollection meta_lemon = sly.TagMeta('lemon_tag', sly.TagValueType.ANY_STRING) tag_lemon = sly.Tag(meta_lemon, 'Hello') tags = sly.TagCollection([tag_lemon]) # or tags = [tag_lemon] # ObjClass class_lemon = sly.ObjClass('lemon', sly.Rectangle) # Label label_lemon = sly.Label(sly.Rectangle(100, 100, 200, 200), class_lemon) # Annotation height, width = 300, 400 ann = sly.Annotation((height, width), [label_lemon], tags, 'example annotaion') # 'points': {'exterior': [[100, 100], [200, 200]], 'interior': []} # If Label geometry is out of image size bounds, it will be cropped label_lemon = sly.Label(sly.Rectangle(100, 100, 700, 900), class_lemon) height, width = 300, 400 ann = sly.Annotation((height, width), [label_lemon], tags, 'example annotaion') # 'points': {'exterior': [[100, 100], [399, 299]], 'interior': []} """ def __init__( self, img_size: Union[Tuple[int, int], Tuple[None, None]], labels: Optional[List[Label]] = None, img_tags: Optional[Union[TagCollection, List[Tag]]] = None, img_description: Optional[str] = "", pixelwise_scores_labels: Optional[List[Label]] = None, custom_data: Optional[Dict] = None, image_id: Optional[int] = None, ): if not isinstance(img_size, (tuple, list)): raise TypeError( '{!r} has to be a tuple or a list. Given type "{}".'.format( "img_size", type(img_size) ) ) self._img_size = tuple(img_size) if self._img_size.count(None) == 1: raise ValueError("Image resolution (height, width) has to defined both or none of them") self._img_description = img_description if img_tags is None: self._img_tags = TagCollection() elif isinstance(img_tags, list): self._img_tags = TagCollection(img_tags) elif isinstance(img_tags, TagCollection): self._img_tags = img_tags else: raise TypeError(f"img_tags argument has unknown type {type(img_tags)}") self._labels = [] self._add_labels_impl(self._labels, take_with_default(labels, [])) self._pixelwise_scores_labels = [] # @TODO: store pixelwise scores as usual geometry labels self._add_labels_impl( self._pixelwise_scores_labels, take_with_default(pixelwise_scores_labels, []), ) self._custom_data = take_with_default(custom_data, {}) self._image_id = image_id @property def img_size(self) -> Tuple[int, int]: """ Size of the image (height, width). :return: Image size :rtype: :class:`Tuple[int, int]` :Usage example: .. code-block:: python height, width = 300, 400 ann = sly.Annotation((height, width)) print(ann.img_size) # Output: (300, 400) """ return deepcopy(self._img_size) @property def image_id(self) -> int: """ Id of the image. :return: Image id :rtype: :class:`int` :Usage example: .. code-block:: python height, width = 300, 400 image_id = 12345 ann = sly.Annotation((height, width), image_id=image_id) print(ann.image_id) # Output: 12345 """ return self._image_id @property def labels(self) -> List[Label]: """ Labels on annotation. :return: Copy of list with image labels :rtype: :class:`List[Label]<supervisely.annotation.label.Label>` :Usage example: .. code-block:: python # Create Labels and add them to Annotation class_kiwi = sly.ObjClass('kiwi', sly.Rectangle) label_kiwi = sly.Label(sly.Rectangle(0, 0, 300, 300), class_kiwi) class_lemon = sly.ObjClass('lemon', sly.Rectangle) label_lemon = sly.Label(sly.Rectangle(0, 0, 500, 600), class_lemon) labels_arr = [label_kiwi, label_lemon] height, width = 300, 400 ann = sly.Annotation((height, width), labels_arr) # Note that ann.labels return a COPY of list with image labels class_potato = sly.ObjClass('potato', sly.Rectangle) label_potato = sly.Label(sly.Rectangle(0, 0, 200, 400), class_potato) ann.labels.append(label_potato) print(len(ann.labels)) # Output: 2 ann_arr = ann.labels ann_arr.append(label_potato) print(len(ann_arr)) # Output: 3 """ return self._labels.copy() @property def pixelwise_scores_labels(self): """pixelwise_scores_labels""" return self._pixelwise_scores_labels.copy() @property def img_description(self) -> str: """ Image description. :return: Image description :rtype: :class:`str` :Usage example: .. code-block:: python ann = sly.Annotation((500, 700), img_description='Annotation for this image is empty') print(ann.img_description) # Output: Annotation for this image is empty """ return self._img_description @property def img_tags(self) -> TagCollection: """ Image tags. :return: TagCollection object :rtype: :class:`TagCollection<supervisely.annotation.tag_collection.TagCollection>` :Usage example: .. code-block:: python # Create TagCollection meta_weather = sly.TagMeta('weather', sly.TagValueType.ANY_STRING) tag_weather = sly.Tag(meta_weather, 'cloudy') tags = sly.TagCollection([tag_weather]) ann = sly.Annotation((300, 400), img_tags=tags) print(ann.img_tags) # Output: # Tags: # +----------------+------------+--------+ # | Name | Value type | Value | # +----------------+------------+--------+ # | weather | any_string | cloudy | # +----------------+------------+--------+ """ return self._img_tags
[docs] def to_json(self) -> Dict: """ Convert the Annotation 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 ann = sly.Annotation((500, 700)) ann_json = ann.to_json() print(ann_json) # Output: { # "description": "", # "size": { # "height": 500, # "width": 700 # }, # "tags": [], # "objects": [], # "customBigData": {} # } """ height = self.img_size[0] if height is not None: height = int(height) width = self.img_size[1] if width is not None: width = int(width) res = { AnnotationJsonFields.IMG_DESCRIPTION: self.img_description, AnnotationJsonFields.IMG_SIZE: { AnnotationJsonFields.IMG_SIZE_HEIGHT: height, AnnotationJsonFields.IMG_SIZE_WIDTH: width, }, AnnotationJsonFields.IMG_TAGS: self.img_tags.to_json(), AnnotationJsonFields.LABELS: [label.to_json() for label in self.labels], AnnotationJsonFields.CUSTOM_DATA: self.custom_data, } if len(self._pixelwise_scores_labels) > 0: # construct probability classes from labels prob_classes = {} for label in self._pixelwise_scores_labels: # @TODO: hotfix to save geometry as "multichannelBitmap" instead of "bitmap"; use normal classes prob_classes[label.obj_class.name] = label.obj_class.clone( geometry_type=MultichannelBitmap ) # save probabilities probabilities = { AnnotationJsonFields.PROBABILITY_LABELS: [ label.to_json() for label in self._pixelwise_scores_labels ], AnnotationJsonFields.PROBABILITY_CLASSES: ObjClassCollection( list(prob_classes.values()) ).to_json(), } res[AnnotationJsonFields.CUSTOM_DATA].update(probabilities) if self.image_id is not None: res[AnnotationJsonFields.IMAGE_ID] = self.image_id return res
[docs] @classmethod def from_json(cls, data: Dict, project_meta: ProjectMeta) -> Annotation: """ Convert a json dict to Annotation. Read more about `Supervisely format <https://docs.supervise.ly/data-organization/00_ann_format_navi>`_. :param data: Annotation in json format as a dict. :type data: dict :param project_meta: Input :class:`ProjectMeta<supervisely.project.project_meta.ProjectMeta>`. :type project_meta: ProjectMeta :return: Annotation object :rtype: :class:`Annotation<Annotation>` :raises: :class:`Exception` :Usage example: .. code-block:: python import supervisely as sly meta = sly.ProjectMeta() ann_json = { "size": { "height": 500, "width": 700 }, "tags": [], "objects": [] } ann = sly.Annotation.from_json(ann_json, meta) """ img_size_dict = data[AnnotationJsonFields.IMG_SIZE] img_height = img_size_dict[AnnotationJsonFields.IMG_SIZE_HEIGHT] img_width = img_size_dict[AnnotationJsonFields.IMG_SIZE_WIDTH] img_size = (img_height, img_width) try: labels = [ Label.from_json(label_json, project_meta) for label_json in data[AnnotationJsonFields.LABELS] ] except Exception as e: raise RuntimeError( f"Failed to deserialize one of the label from JSON format annotation: \n{repr(e)}" ) custom_data = data.get(AnnotationJsonFields.CUSTOM_DATA, {}) prob_labels = None if ( AnnotationJsonFields.PROBABILITY_LABELS in custom_data and AnnotationJsonFields.PROBABILITY_CLASSES in custom_data ): prob_classes = ObjClassCollection.from_json( custom_data[AnnotationJsonFields.PROBABILITY_CLASSES] ) # @TODO: tony, maybe link with project meta (add probability classes???) prob_project_meta = ProjectMeta(obj_classes=prob_classes) prob_labels = [ Label.from_json(label_json, prob_project_meta) for label_json in custom_data[AnnotationJsonFields.PROBABILITY_LABELS] ] custom_data.pop(AnnotationJsonFields.PROBABILITY_CLASSES) custom_data.pop(AnnotationJsonFields.PROBABILITY_LABELS) image_id = data.get(AnnotationJsonFields.IMAGE_ID, None) return cls( img_size=img_size, labels=labels, img_tags=TagCollection.from_json( data[AnnotationJsonFields.IMG_TAGS], project_meta.tag_metas ), img_description=data.get(AnnotationJsonFields.IMG_DESCRIPTION, ""), pixelwise_scores_labels=prob_labels, custom_data=custom_data, image_id=image_id, )
[docs] @classmethod def load_json_file(cls, path: str, project_meta: ProjectMeta) -> Annotation: """ Loads json file and converts it to Annotation. :param path: Path to the json file. :type path: str :param project_meta: Input ProjectMeta object. :type project_meta: ProjectMeta :return: Annotation object :rtype: :class:`Annotation<Annotation>` :Usage example: .. code-block:: python import supervisely as sly address = 'https://app.supervise.ly/' token = 'Your Supervisely API Token' api = sly.Api(address, token) team_name = 'Vehicle Detection' workspace_name = 'Cities' project_name = 'London' team = api.team.get_info_by_name(team_name) workspace = api.workspace.get_info_by_name(team.id, workspace_name) project = api.project.get_info_by_name(workspace.id, project_name) meta = api.project.get_meta(project.id) # Load json file path = "/home/admin/work/docs/my_dataset/ann/annotation.json" ann = sly.Annotation.load_json_file(path, meta) """ with open(path) as fin: data = json.load(fin) return cls.from_json(data, project_meta)
[docs] def clone( self, img_size: Optional[Tuple[int, int]] = None, labels: Optional[List[Label]] = None, img_tags: Optional[Union[TagCollection, List[Tag]]] = None, img_description: Optional[str] = None, pixelwise_scores_labels: Optional[List[Label]] = None, custom_data: Optional[Dict] = None, image_id: Optional[int] = None, ) -> Annotation: """ Makes a copy of Annotation with new fields, if fields are given, otherwise it will use fields of the original Annotation. :param img_size: Size of the image (height, width). :type img_size: Tuple[int, int] or List[int, int] :param labels: List of Label objects. :type labels: List[Label] :param img_tags: TagCollection object or list of Tag objects. :type img_tags: TagCollection or List[Tag] :param img_description: Image description. :type img_description: str, optional :return: New instance of Annotation :rtype: :class:`Annotation<Annotation>` :Usage Example: .. code-block:: python import supervisely as sly ann = sly.Annotation((300, 400)) # Let's clone our Annotation with Label class_kiwi = sly.ObjClass('kiwi', sly.Rectangle) label_kiwi = sly.Label(sly.Rectangle(0, 0, 300, 300), class_kiwi) # Assign cloned annotation to a new variable ann_clone_1 = ann.clone(labels=[label_kiwi]) # Let's clone our Annotation with Label, TagCollection and description meta_lemon = sly.TagMeta('lemon', sly.TagValueType.ANY_STRING) tag_lemon = sly.Tag(meta_lemon, 'juicy') tags = sly.TagCollection([tag_lemon]) # Assign cloned annotation to a new variable ann_clone_2 = ann.clone(labels=[label_kiwi], img_tags=tags, img_description='Juicy') """ return Annotation( img_size=take_with_default(img_size, self.img_size), labels=take_with_default(labels, self.labels), img_tags=take_with_default(img_tags, self.img_tags), img_description=take_with_default(img_description, self.img_description), pixelwise_scores_labels=take_with_default( pixelwise_scores_labels, self.pixelwise_scores_labels ), custom_data=take_with_default(custom_data, self.custom_data), image_id=take_with_default(image_id, self.image_id), )
def _add_labels_impl(self, dest, labels): """ The function _add_labels_impl extend list of the labels of the current Annotation object :param dest: destination list of the Label class objects :param labels: list of the Label class objects to be added to the destination list :return: list of the Label class objects """ for label in labels: if self.img_size.count(None) == 0: # image has resolution in DB canvas_rect = Rectangle.from_size(self.img_size) dest.extend(label.crop(canvas_rect)) else: # image was uploaded by link and does not have resolution in DB # add label without normalization and validation dest.append(label)
[docs] def add_label(self, label: Label) -> Annotation: """ Clones Annotation and adds a new Label. :param label: Label to be added. :type label: Label :return: New instance of Annotation :rtype: :class:`Annotation<Annotation>` :Usage Example: .. code-block:: python import supervisely as sly ann = sly.Annotation((300, 600)) # Create Label class_kiwi = sly.ObjClass('kiwi', sly.Rectangle) label_kiwi = sly.Label(sly.Rectangle(0, 0, 300, 300), class_kiwi) # Add label to Annotation # Remember that Annotation object is immutable, and we need to assign new instance of Annotation to a new variable new_ann = ann.add_label(label_kiwi) """ return self.add_labels([label])
[docs] def add_labels(self, labels: List[Label]) -> Annotation: """ Clones Annotation and adds a new Labels. :param labels: List of Label objects to be added. :type labels: List[Label] :return: New instance of Annotation :rtype: :class:`Annotation<Annotation>` :Usage Example: .. code-block:: python import supervisely as sly ann = sly.Annotation((300, 600)) # Create Labels class_kiwi = sly.ObjClass('kiwi', sly.Rectangle) label_kiwi = sly.Label(sly.Rectangle(0, 0, 300, 300), class_kiwi) class_lemon = sly.ObjClass('lemon', sly.Rectangle) label_lemon = sly.Label(sly.Rectangle(0, 0, 500, 600), class_lemon) # Add labels to Annotation # Remember that Annotation object is immutable, and we need to assign new instance of Annotation to a new variable new_ann = ann.add_labels([label_kiwi, label_lemon]) """ new_labels = [] self._add_labels_impl(new_labels, labels) return self.clone(labels=[*self._labels, *new_labels])
[docs] def delete_label(self, label: Label) -> Annotation: """ Clones Annotation with removed Label. :param label: Label to be deleted. :type label: Label :raises: :class:`KeyError`, if there is no deleted Label in current Annotation object :return: New instance of Annotation :rtype: :class:`Annotation<Annotation>` :Usage Example: .. code-block:: python import supervisely as sly ann = sly.Annotation((300, 600)) # Create Labels class_kiwi = sly.ObjClass('kiwi', sly.Rectangle) label_kiwi = sly.Label(sly.Rectangle(0, 0, 300, 300), class_kiwi) class_lemon = sly.ObjClass('lemon', sly.Rectangle) label_lemon = sly.Label(sly.Rectangle(0, 0, 500, 600), class_lemon) # Add labels to Annotation ann = ann.add_labels([label_kiwi, label_lemon]) print(len(ann.labels)) # Output: 2 # Run through all labels in Annotation objects for label in ann.labels: if label.obj_class.name == 'lemon': # label obj_class name we want to delete # Remember that Annotation object is immutable, and we need to assign new instance of Annotation to a new variable new_ann = ann.delete_label(label) print(len(new_ann.labels)) # Output: 1 """ retained_labels = [_label for _label in self._labels if _label != label] if len(retained_labels) == len(self._labels): raise KeyError( "Trying to delete a non-existing label of class: {}".format(label.obj_class.name) ) return self.clone(labels=retained_labels)
[docs] def add_pixelwise_score_label(self, label: Label) -> Annotation: """ Add label to the pixelwise_scores_labels and return the copy of the current Annotation object. :param label: Label class object to be added :return: Annotation class object with the new list of the pixelwise_scores_labels """ return self.add_pixelwise_score_labels([label])
[docs] def add_pixelwise_score_labels(self, labels: List[Label]) -> Annotation: """ Add_pixelwise_score_labels extend list of the labels of the pixelwise_scores_labels and return the copy of the current Annotation object. :param labels: list of the Label class objects to be added :return: Annotation class object with the new list of the pixelwise_scores_labels """ new_labels = [] self._add_labels_impl(new_labels, labels) return self.clone(pixelwise_scores_labels=[*self._pixelwise_scores_labels, *new_labels])
[docs] def add_tag(self, tag: Tag) -> Annotation: """ Clones Annotation and adds a new Tag. :param tag: Tag object to be added. :type tag: Tag :return: New instance of Annotation :rtype: :class:`Annotation<Annotation>` :Usage Example: .. code-block:: python import supervisely as sly ann = sly.Annotation((300, 600)) # Create Tag meta_message = sly.TagMeta('Message', sly.TagValueType.ANY_STRING) tag_message = sly.Tag(meta_message, 'Hello') # Add Tag to Annotation # Remember that Annotation object is immutable, and we need to assign new instance of Annotation to a new variable new_ann = ann.add_tag(tag_message) """ return self.clone(img_tags=self._img_tags.add(tag))
[docs] def add_tags(self, tags: List[Tag]) -> Annotation: """ Clones Annotation and adds a new list of Tags. :param tags: List of Tags to be added. :type tags: List[Tag] :return: New instance of Annotation :rtype: :class:`Annotation<Annotation>` :Usage Example: .. code-block:: python import supervisely as sly ann = sly.Annotation((300, 600)) # Create Tags meta_message = sly.TagMeta('Message', sly.TagValueType.ANY_STRING) meta_alert = sly.TagMeta('Alert', sly.TagValueType.NONE) tag_message = sly.Tag(meta_message, 'Hello') tag_alert = sly.Tag(meta_alert) # Add Tags to Annotation # Remember that Annotation object is immutable, and we need to assign new instance of Annotation to a new variable new_ann = ann.add_tags([tag_message, tag_alert]) """ return self.clone(img_tags=self._img_tags.add_items(tags))
[docs] def delete_tags_by_name(self, tag_names: List[str]) -> Annotation: """ Clones Annotation and removes Tags by their names. :param tag_names: List of Tags names to be deleted. :type tag_names: List[str] :return: New instance of Annotation :rtype: :class:`Annotation<Annotation>` :Usage Example: .. code-block:: python import supervisely as sly ann = sly.Annotation((300, 600)) # Create Tags meta_message = sly.TagMeta('Message', sly.TagValueType.ANY_STRING) meta_alert = sly.TagMeta('Alert', sly.TagValueType.NONE) tag_message = sly.Tag(meta_message, 'Hello') tag_alert = sly.Tag(meta_alert) # Add Tags to Annotation tags_ann = ann.add_tags([tag_message, tag_alert]) # Delete Tags from Annotation # Remember that Annotation object is immutable, and we need to assign new instance of Annotation to a new variable new_ann = tags_ann.delete_tags_by_name(['Message', 'Alert']) """ retained_tags = [tag for tag in self._img_tags.items() if tag.meta.name not in tag_names] return self.clone(img_tags=TagCollection(items=retained_tags))
[docs] def delete_tag_by_name(self, tag_name: str) -> Annotation: """ Clones Annotation with removed Tag by it's name. :param tag_name: Tag name to be delete. :type tag_name: str :return: New instance of Annotation :rtype: :class:`Annotation<Annotation>` :Usage Example: .. code-block:: python import supervisely as sly ann = sly.Annotation((300, 600)) # Create Tag meta_alert = sly.TagMeta('Alert', sly.TagValueType.ANY_STRING) tag_alert = sly.Tag(meta_alert, 'Hello') tag_ann = ann.add_tag(tag_alert) # Delete Tag from Annotation # Remember that Annotation object is immutable, and we need to assign new instance of Annotation to a new variable new_ann = tag_ann.delete_tag_by_name('Alert') """ return self.delete_tags_by_name([tag_name])
[docs] def delete_tags(self, tags: List[Tag]) -> Annotation: """ Clones Annotation with removed Tags. :param tags: List of Tags to be deleted. :type tags: List[Tag] :return: New instance of Annotation :rtype: :class:`Annotation<Annotation>` :Usage Example: .. code-block:: python import supervisely as sly ann = sly.Annotation((300, 600)) meta_message = sly.TagMeta('Message', sly.TagValueType.ANY_STRING) meta_alert = sly.TagMeta('Alert', sly.TagValueType.NONE) tag_message = sly.Tag(meta_message, 'Hello') tag_alert = sly.Tag(meta_alert) ann = ann.add_tags([tag_message, tag_alert]) print(len(ann.img_tags)) # Output: 2 new_ann = ann.delete_tags([tag_message, tag_alert]) print(len(new_ann.img_tags)) # Output: 0 """ return self.delete_tags_by_name([tag.meta.name for tag in tags])
[docs] def delete_tag(self, tag: Tag) -> Annotation: """ Clones Annotation with removed Tag. :param tag: Tag to be deleted. :type tag: Tag :return: New instance of Annotation :rtype: :class:`Annotation<Annotation>` :Usage Example: .. code-block:: python import supervisely as sly ann = sly.Annotation((300, 600)) meta_dog = sly.TagMeta('dog', sly.TagValueType.ANY_STRING) meta_cat = sly.TagMeta('cat', sly.TagValueType.NONE) tag_dog = sly.Tag(meta_dog, 'Woof!') tag_cat = sly.Tag(meta_cat) ann = ann.add_tags([tag_dog, tag_cat]) print(len(ann.img_tags)) # Output: 2 new_ann = ann.delete_tag(tag_dog) print(len(new_ann.img_tags)) # Output: 1 """ return self.delete_tags_by_name([tag.meta.name])
[docs] def transform_labels( self, label_transform_fn, new_size: Optional[Tuple[int, int]] = None ) -> Annotation: """ Transform labels and change image size in current Annotation object and return the copy of the current Annotation object. :param label_transform_fn: function for transform labels :param new_size: new image size :return: Annotation class object with new labels and image size """ def _do_transform_labels(src_labels, label_transform_fn): # long easy to debug # result = [] # for label in src_labels: # result.extend(label_transform_fn(label)) # return result # short, hard-to-debug alternative return list(itertools.chain(*[label_transform_fn(label) for label in src_labels])) new_labels = _do_transform_labels(self._labels, label_transform_fn) new_pixelwise_scores_labels = _do_transform_labels( self._pixelwise_scores_labels, label_transform_fn ) return self.clone( img_size=take_with_default(new_size, self.img_size), labels=new_labels, pixelwise_scores_labels=new_pixelwise_scores_labels, )
[docs] def crop_labels(self, rect: Rectangle) -> Annotation: """ Crops Labels of the current Annotation. :param rect: Rectangle object for crop. :type rect: Rectangle :return: New instance of Annotation :rtype: :class:`Annotation<Annotation>` :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 = 888 image_id = 555555 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) # Draw Annotation on image before crop ann.draw_pretty(img, thickness=3) # Crop Labels for current Annotation # Remember that Annotation object is immutable, and we need to assign new instance of Annotation to a new variable cropped_ann = ann.crop_labels(sly.Rectangle(0, 0, 600, 700)) # Draw Annotation on image after crop cropped_ann.draw_pretty(new_img, thickness=3) .. list-table:: * - .. figure:: https://i.imgur.com/6huO1se.jpg Before - .. figure:: https://i.imgur.com/w2wR4h8.jpg After """ def _crop_label(label): return label.crop(rect) return self.transform_labels(_crop_label)
[docs] def relative_crop(self, rect: Rectangle) -> Annotation: """ Crops current Annotation and with image size (height, width) changes. :param rect: Rectangle object for crop. :type rect: Rectangle :return: New instance of Annotation :rtype: :class:`Annotation<Annotation>` :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 = 888 image_id = 555555 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) new_img = sly.imaging.image.crop(new_img, sly.Rectangle(200, 300, 600, 700)) # Draw Annotation on image before relative crop ann.draw_pretty(img, thickness=3) # Relative Crop Labels for current Annotation # Remember that Annotation object is immutable, and we need to assign new instance of Annotation to a new variable r_cropped_ann = ann.relative_crop(sly.Rectangle(200, 300, 600, 700)) # Draw Annotation on image after relative crop r_cropped_ann.draw_pretty(new_img, thickness=3) .. list-table:: * - .. figure:: https://i.imgur.com/23UuNdJ.png Before - .. figure:: https://i.imgur.com/8Z7xVxB.jpg After """ def _crop_label(label): return label.relative_crop(rect) return self.transform_labels(_crop_label, rect.to_size())
[docs] def rotate(self, rotator: ImageRotator) -> Annotation: """ Rotates current Annotation. :param rotator: ImageRotator object. :type rotator: ImageRotator :return: New instance of Annotation :rtype: :class:`Annotation<Annotation>` :Usage Example: .. code-block:: python import supervisely as sly from supervisely.geometry.image_rotator import ImageRotator address = 'https://app.supervise.ly/' token = 'Your Supervisely API Token' api = sly.Api(address, token) # Get image and annotation from API project_id = 888 image_id = 555555 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) new_img = sly.imaging.image.rotate(new_img, 10) # Draw Annotation on image before rotation ann.draw_pretty(img, thickness=3) # Rotate Labels for current Annotation # Remember that Annotation object is immutable, and we need to assign new instance of Annotation to a new variable rotator = ImageRotator(annotation.img_size, 10) rotated_ann = ann.rotate(rotator) # Draw Annotation on image after rotation rotated_ann.draw_pretty(new_img, thickness=3) .. list-table:: * - .. figure:: https://i.imgur.com/6huO1se.jpg Before - .. figure:: https://i.imgur.com/ZQ47cXN.jpg After """ def _rotate_label(label): return [label.rotate(rotator)] return self.transform_labels(_rotate_label, tuple(rotator.new_imsize))
[docs] def resize(self, out_size: Tuple[int, int], skip_empty_masks: bool = False) -> Annotation: """ Resizes current Annotation. :param out_size: Desired output image size (height, width). :type out_size: Tuple[int, int] :param skip_empty_masks: Skip the raising of the error when you have got an empty label mask after a resizing procedure. :type skip_empty_masks: bool :return: New instance of Annotation :rtype: :class: Annotation :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 = 888 image_id = 555555 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) new_img = sly.imaging.image.resize(new_img, (100, 200)) # Draw Annotation on image before resize ann.draw_pretty(img, thickness=3) # Resize # Remember that Annotation object is immutable, and we need to assign new instance of Annotation to a new variable resized_ann = ann.resize((100, 200)) # Draw Annotation on image after resize resized_ann.draw_pretty(new_img, thickness=3) .. list-table:: * - .. figure:: https://i.imgur.com/6huO1se.jpg Before - .. figure:: https://i.imgur.com/RrvNMoV.jpg After """ def _resize_label(label): try: return [label.resize(self.img_size, out_size)] except ValueError: if skip_empty_masks is True: return [] else: raise return self.transform_labels(_resize_label, out_size)
[docs] def scale(self, factor: float) -> Annotation: """ Scales current Annotation with the given factor. :param factor: Scale size. :type factor: float :return: New instance of Annotation :rtype: :class:`Annotation<Annotation>` :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 = 888 image_id = 555555 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) new_img = sly.imaging.image.scale(new_img, 0.55) # Draw Annotation on image before rescale ann.draw_pretty(img, thickness=3) # Scale # Remember that Annotation object is immutable, and we need to assign new instance of Annotation to a new variable rescaled_ann = ann.scale(0.55) # Draw Annotation on image after rescale rescaled_ann.draw_pretty(new_img, thickness=3) .. list-table:: * - .. figure:: https://i.imgur.com/6huO1se.jpg Before - .. figure:: https://i.imgur.com/Ze6uqZ8.jpg After """ def _scale_label(label): return [label.scale(factor)] result_size = ( round(self.img_size[0] * factor), round(self.img_size[1] * factor), ) return self.transform_labels(_scale_label, result_size)
[docs] def fliplr(self) -> Annotation: """ Flips the current Annotation horizontally. :return: New instance of Annotation :rtype: :class:`Annotation<Annotation>` :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 = 888 image_id = 555555 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) new_img = sly.imaging.image.fliplr(new_img) # Draw Annotation on image before horizontal flip ann.draw_pretty(img, thickness=3) # Flip # Remember that Annotation object is immutable, and we need to assign new instance of Annotation to a new variable fliplr_ann = ann.fliplr() # Draw Annotation on image after horizontal flip fliplr_ann.draw_pretty(new_img, thickness=3) .. list-table:: * - .. figure:: https://i.imgur.com/6huO1se.jpg Before - .. figure:: https://i.imgur.com/AQSuqIN.jpg After """ def _fliplr_label(label): return [label.fliplr(self.img_size)] return self.transform_labels(_fliplr_label)
[docs] def flipud(self) -> Annotation: """ Flips the current Annotation vertically. :return: New instance of Annotation :rtype: :class:`Annotation<Annotation>` :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 = 888 image_id = 555555 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) new_img = sly.imaging.image.flipud(new_img) # Draw Annotation on image before vertical flip ann.draw_pretty(img, thickness=3) # Flip # Remember that Annotation object is immutable, and we need to assign new instance of Annotation to a new variable flipud_ann = ann.flipud() # Draw Annotation on image after vertical flip flipud_ann.draw_pretty(new_img, thickness=3) .. list-table:: * - .. figure:: https://i.imgur.com/6huO1se.jpg Before - .. figure:: https://i.imgur.com/NVhvPDb.jpg After """ def _flipud_label(label): return [label.flipud(self.img_size)] return self.transform_labels(_flipud_label)
def _get_thickness(self): h, w = self.img_size step_size = 100 size = min(h, w) + (max(h, w) - min(h, w)) * 0.5 return int(size) // step_size + 1 def _get_font(self): """ 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(self.img_size)) def _draw_tags(self, bitmap): """ The function draws text labels on bitmap from left to right. :param bitmap: target image """ texts = [tag.get_compact_str() for tag in self.img_tags] sly_image.draw_text_sequence( bitmap, texts, (0, 0), sly_image.CornerAnchorMode.TOP_LEFT, font=self._get_font(), )
[docs] def draw( self, bitmap: np.ndarray, color: Optional[List[int, int, int]] = None, thickness: Optional[int] = 1, draw_tags: Optional[bool] = False, fill_rectangles: Optional[bool] = True, draw_class_names: Optional[bool] = False, ) -> None: """ Draws current Annotation on image. Modifies mask. :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 fill_rectangles: Choose False if you want to draw only contours of bboxes. By default, True. :type fill_rectangles: int, optional :param draw_class_names: Determines whether to draw class names on bitmap or not. :type draw_class_names: int, optional :return: :class:`None<None>` :rtype: :class:`NoneType<NoneType>` :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 = 888 image_id = 555555 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) # Draw Annotation on image ann.draw(img) .. image:: https://i.imgur.com/1W1Nfl1.jpg :width: 600 :height: 500 """ tags_font = None if draw_tags is True: tags_font = self._get_font() for label in self._labels: if not fill_rectangles and isinstance(label.geometry, Rectangle): label.draw_contour( bitmap, color=color, thickness=thickness, draw_tags=draw_tags, tags_font=tags_font, draw_class_name=draw_class_names, class_name_font=tags_font, ) continue label.draw( bitmap, color=color, thickness=thickness, draw_tags=draw_tags, tags_font=tags_font, draw_class_name=draw_class_names, class_name_font=tags_font, ) if draw_tags: self._draw_tags(bitmap)
[docs] def draw_contour( self, bitmap: np.ndarray, color: Optional[List[int, int, int]] = None, thickness: Optional[int] = 1, draw_tags: Optional[bool] = False, ) -> None: """ Draws geometry contour of Annotation on image. Modifies mask. :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 :return: :class:`None<None>` :rtype: :class:`NoneType<NoneType>` :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 = 888 image_id = 555555 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) # Draw Annotation contour on image ann.draw_contour(img) .. image:: https://i.imgur.com/F8KGZS4.jpg :width: 600 :height: 500 """ tags_font = None if draw_tags is True: tags_font = self._get_font() for label in self._labels: label.draw_contour( bitmap, color=color, thickness=thickness, draw_tags=draw_tags, tags_font=tags_font, ) if draw_tags: self._draw_tags(bitmap)
[docs] @classmethod def from_img_path(cls, img_path: str) -> Annotation: """ Creates empty Annotation from image. :param img_path: Path to the input image. :type img_path: str :return: Annotation :rtype: :class:`Annotation<Annotation>` :Usage Example: .. code-block:: python import supervisely as sly img_path = "/home/admin/work/docs/my_dataset/img/example.jpeg" ann = sly.Annotation.from_img_path(img_path) """ img = sly_image.read(img_path) img_size = img.shape[:2] return cls(img_size)
[docs] @classmethod def stat_area( cls, render: np.ndarray, names: List[str], colors: List[List[int, int, int]] ) -> Dict[str, float]: """ Get statistics about color area representation on the given render for the current Annotation. :param render: Target render. :type render: np.ndarray :param names: List of color names. :type names: List[str] :param colors: List of :class:`[R, G, B]` colors. :type colors: List[List[int, int, int]] :return: Colors area representation on the given render :rtype: :class:`dict` :raises: :class:`RuntimeError` if len(names) != len(colors) :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 = 888 image_id = 555555 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) class_names = [] class_colors = [] for label in ann.labels: class_names.append(label.obj_class.name) class_colors.append(label.obj_class.color) ann.draw_pretty(img, thickness=3) ann_stats = ann.stat_area(img, class_names, class_colors) print(ann_stats) # Output: { # "lemon":0.45548266166822865, # "kiwi":0.5697047797563262, # "unlabeled":98.97481255857544, # "height":800, # "width":1067, # "channels":3 # } print(stat_area) """ if len(names) != len(colors): raise RuntimeError( "len(names) != len(colors) [{} != {}]".format(len(names), len(colors)) ) result = {} height, width = render.shape[:2] total_pixels = height * width channels = None if len(render.shape) == 2: channels = 1 elif len(render.shape) == 3: channels = render.shape[2] unlabeled_done = False covered_pixels = 0 for name, color in zip(names, colors): col_name = name if name == "unlabeled": unlabeled_done = True class_mask = np.all(render == color, axis=-1).astype("uint8") cnt_pixels = class_mask.sum() covered_pixels += cnt_pixels result[col_name] = cnt_pixels / total_pixels * 100.0 if covered_pixels > total_pixels: raise RuntimeError("Class colors mistake: covered_pixels > total_pixels") if unlabeled_done is False: result["unlabeled"] = (total_pixels - covered_pixels) / total_pixels * 100.0 result["height"] = height result["width"] = width result["channels"] = channels return result
[docs] def stat_class_count(self, class_names: Optional[List[str]] = None) -> defaultdict: """ Get statistics about number of each class in Annotation. :param class_names: List of classes names. :type class_names: List[str], optional :return: Number of each class in Annotation and total number of classes :rtype: :class:`defaultdict` :Usage Example: .. code-block:: python import supervisely as sly # Create object classes class_kiwi = sly.ObjClass('kiwi', sly.Rectangle) class_lemon = sly.ObjClass('lemon', sly.Rectangle) # Create labels label_kiwi = sly.Label(sly.Rectangle(100, 100, 700, 900), class_kiwi) label_lemon = sly.Label(sly.Rectangle(200, 200, 500, 600), class_lemon) labels_arr = [label_kiwi, label_lemon] # Create annotation height, width = 300, 400 ann = sly.Annotation((height, width), labels_arr) stat_class = ann.stat_class_count(['lemon', 'kiwi']) # Output: defaultdict(<class 'int'>, {'lemon': 1, 'kiwi': 1, 'total': 2}) """ total = 0 stat = {name: 0 for name in class_names} for label in self._labels: cur_name = label.obj_class.name if cur_name not in stat: raise KeyError("Class {!r} not found in {}".format(cur_name, class_names)) stat[cur_name] += 1 total += 1 stat["total"] = total return stat
[docs] def draw_class_idx_rgb(self, render: np.ndarray, name_to_index: Dict[str, int]) -> None: """ Draws current Annotation on render. :param render: Target render to draw classes. :type render: np.ndarray :param name_to_index: Dict where keys are class names and values are class indices to draw on render. :type name_to_index: dict :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 = 888 image_id = 555555 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) # Draw Annotation on image name_to_index = {'lemon': 90, 'kiwi': 195} ann.draw_class_idx_rgb(img, name_to_index) .. image:: https://i.imgur.com/ACSaBgw.jpg :width: 600 :height: 500 """ for label in self._labels: class_idx = name_to_index[label.obj_class.name] color = [class_idx, class_idx, class_idx] label.draw(render, color=color, thickness=1)
@property def custom_data(self): """custom_data""" return self._custom_data.copy()
[docs] def filter_labels_by_min_side( self, thresh: int, filter_operator: Optional[Callable] = operator.lt, # operator from the operator module classes: Optional[List[str]] = None, ) -> Annotation: """ Filters Labels of the current Annotation by side. If minimal side is smaller than Label threshold it will be ignored. :param thresh: Side threshold to filter. :type thresh: int :param filter_operator: Type of filter operation. :type filter_operator: operator :param classes: List of Labels names to apply filter. :type classes: List[str] :return: New instance of Annotation :rtype: :class:`Annotation<Annotation>` :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 = 888 image_id = 555555 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) # Filter Labels # Remember that Annotation object is immutable, and we need to assign new instance of Annotation to a new variable filtered_ann = ann.filter_labels_by_min_side(200) # Draw filtered Annotation on image filtered_ann.draw(img) .. list-table:: * - .. figure:: https://i.imgur.com/6huO1se.jpg Before - .. figure:: https://i.imgur.com/uunTbPR.jpg After """ def filter(label): if classes == None or label.obj_class.name in classes: bbox = label.geometry.to_bbox() height_px = bbox.height width_px = bbox.width if filter_operator(min(height_px, width_px), thresh): return [] # action 'delete' return [label] return self.transform_labels(filter)
[docs] def get_label_by_id(self, sly_id: int) -> Union[Label, None]: """ Get Label from current Annotation by sly_id. :param sly_id: Label ID from Supervisely server. :type sly_id: int :return: Label or None :rtype: :class:`Label<supervisely.annotation.label.Label>` or :class:`NoneType` :Usage Example: .. code-block:: python import supervisely as sly address = 'https://app.supervise.ly/' token = 'Your Supervisely API Token' api = sly.Api(address, token) # To get Label ID you must first access ProjectMeta PROJECT_ID = 999 meta_json = api.project.get_meta(PROJECT_ID) meta = sly.ProjectMeta.from_json(meta_json) # Get desired image id to which label belongs to download annotation image_id = 376728 ann_info = api.annotation.download(image_id) ann_json = ann_info.annotation ann = sly.Annotation.from_json(ann_json, meta) # Get Label by it's ID label_by_id = ann.get_label_by_id(sly_id=2263842) print(label_by_id.to_json()) # Output: { # "classTitle":"kiwi", # "description":"", # "tags":[], # "points":{ # "exterior":[ # [481, 549], # [641, 703] # ], # "interior":[] # }, # "labelerLogin":"cxnt", # "updatedAt":"2020-12-11T08:11:43.249Z", # "createdAt":"2020-12-10T09:38:57.969Z", # "id":2263842, # "classId":7370, # "geometryType":"rectangle", # "shape":"rectangle" # } # Returns None if Label ID doesn't exist on the given image ID label_by_id = ann.get_label_by_id(sly_id=9999999) # Output: None """ for label in self._labels: if label.geometry.sly_id == sly_id: return label return None
[docs] def merge(self, other: Annotation) -> Annotation: """ Merge current Annotation with another Annotation. :param other: Annotation to merge. :type other: Annotation :return: New instance of Annotation :rtype: :class:`Annotation<Annotation>` .. code-block:: python import supervisely as sly # Create annotation meta_lemon = sly.TagMeta('lemon_tag', sly.TagValueType.ANY_STRING) tag_lemon = sly.Tag(meta_lemon, 'lemon') tags_lemon = sly.TagCollection([tag_lemon]) class_lemon = sly.ObjClass('lemon', sly.Rectangle) label_lemon = sly.Label(sly.Rectangle(100, 100, 200, 200), class_lemon) height, width = 300, 400 ann_lemon = sly.Annotation((height, width), [label_lemon], tags_lemon) # Create annotation to merge meta_kiwi= sly.TagMeta('kiwi_tag', sly.TagValueType.ANY_STRING) tag_kiwi = sly.Tag(meta_kiwi, 'kiwi') tags_kiwi = sly.TagCollection([tag_kiwi]) class_kiwi = sly.ObjClass('kiwi', sly.Rectangle) label_kiwi = sly.Label(sly.Rectangle(200, 100, 700, 200), class_kiwi) height, width = 700, 500 ann_kiwi = sly.Annotation((height, width), [label_kiwi], tags_kiwi) # Merge annotations ann_merge = ann_lemon.merge(ann_kiwi) for label in ann_merge.labels: print(label.obj_class.name) # Output: lemon # Output: kiwi """ res = self.clone() res = res.add_labels(other.labels) res = res.add_tags(other.img_tags) return res
[docs] def draw_pretty( self, bitmap: np.ndarray, color: Optional[List[int, int, int]] = None, thickness: Optional[int] = None, opacity: Optional[float] = 0.5, draw_tags: Optional[bool] = False, output_path: Optional[str] = None, fill_rectangles: Optional[bool] = True, ) -> None: """ Draws current Annotation on image with contour. Modifies mask. :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 opacity: Opacity of the drawing figure. :type opacity: float, optional :param draw_tags: Determines whether to draw tags on bitmap or not. :type draw_tags: bool, optional :param output_path: Saves modified image to the given path. :type output_path: str, optional :return: :class:`None<None>` :rtype: :class:`NoneType<NoneType>` .. 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 = 888 image_id = 555555 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) # Draw pretty Annotation on image ann.draw_pretty(img, thickness=3) .. image:: https://i.imgur.com/6huO1se.jpg :width: 600 :height: 500 """ if thickness is None: thickness = self._get_thickness() height, width = bitmap.shape[:2] vis_filled = np.zeros((height, width, 3), np.uint8) self.draw( vis_filled, color=color, thickness=thickness, draw_tags=draw_tags, fill_rectangles=fill_rectangles, ) non_empty_pixels = np.tile(np.any(vis_filled != 0, axis=2)[:, :, np.newaxis], (1, 1, 3)) mixes_bitmap = np.where( non_empty_pixels, vis_filled * opacity + bitmap * (1 - opacity), bitmap ).astype(np.uint8) np.copyto(bitmap, mixes_bitmap) if thickness > 0: self.draw_contour(bitmap, color=color, thickness=thickness, draw_tags=draw_tags) if output_path: sly_image.write(output_path, bitmap)
[docs] def to_nonoverlapping_masks(self, mapping: Dict[ObjClass, ObjClass]) -> Annotation: """ Create new annotation with non-overlapping labels masks. Convert classes to Bitmap or skip them. :param mapping: Dict with ObjClasses for mapping. :type mapping: Dict[ObjClass, ObjClass] :return: New instance of Annotation :rtype: :class:`Annotation<Annotation>` .. code-block:: python import supervisely as sly address = 'https://app.supervise.ly/' token = 'Your Supervisely API Token' api = sly.Api(address, token) # Get image annotation from API project_id = 7548 image_id = 2254937 meta_json = api.project.get_meta(project_id) meta = sly.ProjectMeta.from_json(meta_json) ann_info = api.annotation.download(image_id) print(json.dumps(ann_info.annotation, indent=4)) # Output: { # "description": "", # "tags": [], # "size": { # "height": 800, # "width": 1067 # }, # "objects": [ # { # "id": 56656282, # "classId": 122357, # "description": "", # "geometryType": "bitmap", # "labelerLogin": "alex", # "createdAt": "2021-10-15T11:01:40.805Z", # "updatedAt": "2021-10-15T11:01:40.805Z", # "tags": [], # "classTitle": "lemon", # "bitmap": { # "data": "eJwBuwJE/YlQTkcNChoKAAAADUlIRFIAAAE3AAAApgEDAAAAhaFaIwAAAAZQTFRFAAAA..., # "origin": [ # 589, # 372 # ] # } # }, # { # "id": 56656281, # "classId": 122356, # "description": "", # "geometryType": "polygon", # "labelerLogin": "alex", # "createdAt": "2021-10-15T13:22:58.178Z", # "updatedAt": "2021-10-15T13:22:58.178Z", # "tags": [], # "classTitle": "kiwi", # "points": { # "exterior": [ # [ # 719, # 115 # ], # ... # [ # 732, # 123 # ] # ], # "interior": [] # } # }, # { # "id": 56656280, # "classId": 122356, # "description": "", # "geometryType": "polygon", # "labelerLogin": "alex", # "createdAt": "2021-10-15T13:22:58.178Z", # "updatedAt": "2021-10-15T13:22:58.178Z", # "tags": [], # "classTitle": "kiwi", # "points": { # "exterior": [ # [ # 250, # 216 # ], # ... # [ # 278, # 212 # ] # ], # "interior": [] # } # }, # { # "id": 56656279, # "classId": 122356, # "description": "", # "geometryType": "polygon", # "labelerLogin": "alex", # "createdAt": "2021-10-15T13:22:58.177Z", # "updatedAt": "2021-10-15T13:22:58.177Z", # "tags": [], # "classTitle": "kiwi", # "points": { # "exterior": [ # [ # 554, # 581 # ], # ... # [ # 560, # 587 # ] # ], # "interior": [] # } # } # ] # } ann = sly.Annotation.from_json(ann_info.annotation, meta) # Create mapping. Let's check 'kiwi' classes and skip 'lemon' classes. mapping = {} for label in ann.labels: if label.obj_class.name not in mapping: if label.obj_class.name == 'lemon': mapping[label.obj_class] = None else: new_obj_class = sly.ObjClass(label.obj_class.name, Bitmap) mapping[label.obj_class] = new_obj_class nonoverlap_ann = ann.to_nonoverlapping_masks(mapping) print(json.dumps(nonoverlap_ann.to_json(), indent=4)) # Output: { # "description": "", # "size": { # "height": 800, # "width": 1067 # }, # "tags": [], # "objects": [ # { # "classTitle": "kiwi", # "description": "", # "tags": [], # "bitmap": { # "origin": [ # 187, # 396 # ], # "data": "eJwBLALT/YlQTkcNChoKAAAADUlIRFIAAACuAAAAzgEDAAAAnTar9wAAAAZQTFRFAAAA... # }, # "shape": "bitmap", # "geometryType": "bitmap" # }, # { # "classTitle": "kiwi", # "description": "", # "tags": [], # "bitmap": { # "origin": [ # 365, # 385 # ], # "data": "eJwB4gEd/olQTkcNChoKAAAADUlIRFIAAACbAAAAwgEDAAAAZC4i8AAAAAZQTFRFAAAA... # }, # "shape": "bitmap", # "geometryType": "bitmap" # }, # { # "classTitle": "kiwi", # "description": "", # "tags": [], # "bitmap": { # "origin": [ # 469, # 506 # ], # "data": "eJwBHgLh/YlQTkcNChoKAAAADUlIRFIAAAC1AAAArQEDAAAAzBisHAAAAAZQTFRFAAAA... # }, # "shape": "bitmap", # "geometryType": "bitmap" # } # ], # "customBigData": {} # } """ common_img = np.zeros(self.img_size, np.int32) # size is (h, w) for idx, lbl in enumerate(self.labels, start=1): # if mapping[lbl.obj_class] is not None: if isinstance(lbl.geometry, (Bitmap, Polygon)): lbl.draw(common_img, color=idx) # (unique, counts) = np.unique(common_img, return_counts=True) new_labels = [] for idx, lbl in enumerate(self.labels, start=1): dest_class = mapping[lbl.obj_class] if dest_class is None: continue # skip labels mask = common_img == idx if np.any(mask): # figure may be entirely covered by others g = lbl.geometry new_mask = Bitmap(data=mask) new_lbl = lbl.clone(geometry=new_mask, obj_class=dest_class) new_labels.append(new_lbl) new_ann = self.clone(labels=new_labels) return new_ann
[docs] def to_indexed_color_mask( self, mask_path: str, palette: Optional[Image.ADAPTIVE] = Image.ADAPTIVE, # pylint: disable=no-member colors: Optional[int] = 256, ) -> None: """ Draw current Annotation on image and save it in PIL format. :param mask_path: Saves image to the given path. :type mask_path: str :param palette: Palette to use when converting image from mode "RGB" to "P". :type palette: Available palettes are WEB or ADAPTIVE, optional :param colors: Number of colors to use for the ADAPTIVE palette. :type colors: int, optional :return: :class:`None<None>` :rtype: :class:`NoneType<NoneType>` """ mask = np.zeros((self.img_size[0], self.img_size[1], 3), dtype=np.uint8) for label in self.labels: label.geometry.draw(mask, label.obj_class.color) im = Image.fromarray(mask) im = im.convert("P", palette=palette, colors=colors) ensure_base_path(mask_path) im.save(mask_path)
[docs] def add_bg_object(self, bg_obj_class: ObjClass): """add_bg_object""" if bg_obj_class not in [label.obj_class for label in self.labels]: bg_geometry = Rectangle.from_size(self.img_size) bg_geometry = bg_geometry.convert(new_geometry=bg_obj_class.geometry_type)[0] new_label = Label(bg_geometry, bg_obj_class) updated_labels = self.labels updated_labels.insert(0, new_label) return self.clone(labels=updated_labels) else: return self
[docs] def to_segmentation_task(self) -> Annotation: """ Convert Annotation classes by joining labels with same object classes to one label. Applies to Bitmap only. :return: New instance of Annotation :rtype: :class:`Annotation<Annotation>` .. code-block:: python import supervisely as sly address = 'https://app.supervise.ly/' token = 'Your Supervisely API Token' api = sly.Api(address, token) # Get image annotation from API project_id = 7473 image_id = 2223200 meta_json = api.project.get_meta(project_id) meta = sly.ProjectMeta.from_json(meta_json) ann_info = api.annotation.download(image_id) print(json.dumps(ann_info.annotation, indent=4)) # Output: { # "description": "", # "tags": [], # "size": { # "height": 800, # "width": 1067 # }, # "objects": [ # { # "id": 57388829, # "classId": 121405, # "description": "", # "geometryType": "bitmap", # "labelerLogin": "alex", # "createdAt": "2022-01-02T08:06:05.183Z", # "updatedAt": "2022-01-02T08:07:12.219Z", # "tags": [], # "classTitle": "kiwi", # "bitmap": { # "data": "eJyNlWs4lHkYxv/z8qp5NZuYSZes1KZm6CDSTuP0mh2ZWYdmULakdBUzkRqRrDG8M1vZZg8W..., # "origin": [ # 481, # 543 # ] # } # }, # { # "id": 57388831, # "classId": 121404, # "description": "", # "geometryType": "bitmap", # "labelerLogin": "alex", # "createdAt": "2022-01-02T08:06:59.133Z", # "updatedAt": "2022-01-02T08:07:12.219Z", # "tags": [], # "classTitle": "lemon", # "bitmap": { # "data": "eJwdV388k/sXfzwmz3TVNm3l94z5saQoXFSG+c26tM1GYjWUISWKK201SchvIcq30tX2rD..., # "origin": [ # 523, # 119 # ] # } # }, # { # "id": 57388832, # "classId": 121405, # "description": "", # "geometryType": "bitmap", # "labelerLogin": "alex", # "createdAt": "2022-01-02T08:07:12.104Z", # "updatedAt": "2022-01-02T08:07:12.104Z", # "tags": [], # "classTitle": "kiwi", # "bitmap": { # "data": "eJw1VglQU8kWTWISHzH6AzwwIEsSCBr2AApBloAJhB0eCEIYDJFN2WHACC7sRghbIAiIMuKj..., # "origin": [ # 773, # 391 # ] # } # } # ] # } ann = sly.Annotation.from_json(ann_info.annotation, meta) segm_ann = ann.to_segmentation_task() print(json.dumps(segm_ann.to_json(), indent=4)) # Output: { # "description": "", # "size": { # "height": 800, # "width": 1067 # }, # "tags": [], # "objects": [ # { # "classTitle": "kiwi", # "description": "", # "tags": [], # "bitmap": { # "origin": [ # 481, # 391 # ], # "data": "eJwBagSV+4lQTkcNChoKAAAADUlIRFIAAAHpAAABOQEDAAAAjj5K+wAAAAZQTFRFAAAA... # }, # "shape": "bitmap", # "geometryType": "bitmap" # }, # { # "classTitle": "lemon", # "description": "", # "tags": [], # "bitmap": { # "origin": [ # 523, # 119 # ], # "data": "eJwBOAPH/IlQTkcNChoKAAAADUlIRFIAAAEsAAABCQEDAAAAFNKIswAAAAZQTFRFAAAA... # }, # "shape": "bitmap", # "geometryType": "bitmap" # } # ], # "customBigData": {} # } """ class_mask = {} for label in self.labels: if label.obj_class not in class_mask: class_mask[label.obj_class] = np.zeros(self.img_size, np.uint8) label.draw(class_mask[label.obj_class], color=255) new_labels = [] for obj_class, white_mask in class_mask.items(): mask = white_mask == 255 bitmap = Bitmap(data=mask) new_labels.append(Label(geometry=bitmap, obj_class=obj_class)) return self.clone(labels=new_labels)
[docs] def to_detection_task(self, mapping: Dict[ObjClass, ObjClass]) -> Annotation: """ Convert Annotation classes geometries according to mapping dict and checking nonoverlapping masks. Converting possible only to Bitmap or Rectangle. :return: New instance of Annotation :rtype: :class:`Annotation<Annotation>` .. code-block:: python import supervisely as sly address = 'https://app.supervise.ly/' token = 'Your Supervisely API Token' api = sly.Api(address, token) # Get image annotation from API project_id = 7548 image_id = 2254942 meta_json = api.project.get_meta(project_id) meta = sly.ProjectMeta.from_json(meta_json) ann_info = api.annotation.download(image_id) print(json.dumps(ann_info.annotation, indent=4)) # Output: { # "description": "", # "tags": [], # "size": { # "height": 800, # "width": 1067 # }, # "objects": [ # { # "id": 56656282, # "classId": 122357, # "description": "", # "geometryType": "bitmap", # "labelerLogin": "alex", # "createdAt": "2021-10-15T11:01:40.805Z", # "updatedAt": "2021-10-15T11:01:40.805Z", # "tags": [], # "classTitle": "lemon", # "bitmap": { # "data": "eJwBuwJE/YlQTkcNChoKAAAADUlIRFIAAAE3AAAApgEDAAAAhaFaIwAAAAZQTFRFAAAA..., # "origin": [ # 589, # 372 # ] # } # }, # { # "id": 56656281, # "classId": 122356, # "description": "", # "geometryType": "polygon", # "labelerLogin": "alex", # "createdAt": "2021-10-15T13:22:58.178Z", # "updatedAt": "2021-10-15T13:22:58.178Z", # "tags": [], # "classTitle": "kiwi", # "points": { # "exterior": [ # [ # 719, # 115 # ], # ... # [ # 732, # 123 # ] # ], # "interior": [] # } # }, # { # "id": 56656280, # "classId": 122356, # "description": "", # "geometryType": "polygon", # "labelerLogin": "alex", # "createdAt": "2021-10-15T13:22:58.178Z", # "updatedAt": "2021-10-15T13:22:58.178Z", # "tags": [], # "classTitle": "kiwi", # "points": { # "exterior": [ # [ # 250, # 216 # ], # ... # [ # 278, # 212 # ] # ], # "interior": [] # } # }, # { # "id": 56656279, # "classId": 122356, # "description": "", # "geometryType": "polygon", # "labelerLogin": "alex", # "createdAt": "2021-10-15T13:22:58.177Z", # "updatedAt": "2021-10-15T13:22:58.177Z", # "tags": [], # "classTitle": "kiwi", # "points": { # "exterior": [ # [ # 554, # 581 # ], # ... # [ # 560, # 587 # ] # ], # "interior": [] # } # } # ] # } ann = sly.Annotation.from_json(ann_info.annotation, meta) # Create mapping for classes converting. Let's convert classes to Rectangle. mapping = {} for label in ann.labels: if label.obj_class.name not in mapping: new_obj_class = sly.ObjClass(label.obj_class.name, Rectangle) mapping[label.obj_class] = new_obj_class det_ann = ann.to_detection_task(mapping) print(json.dumps(det_ann.to_json(), indent=4)) # Output: { # "description": "", # "size": { # "height": 800, # "width": 1067 # }, # "tags": [], # "objects": [ # { # "classTitle": "lemon", # "description": "", # "tags": [], # "points": { # "exterior": [ # [ # 589, # 372 # ], # [ # 899, # 537 # ] # ], # "interior": [] # }, # "geometryType": "rectangle", # "shape": "rectangle" # }, # { # "classTitle": "kiwi", # "description": "", # "tags": [], # "points": { # "exterior": [ # [ # 612, # 110 # ], # [ # 765, # 282 # ] # ], # "interior": [] # }, # "geometryType": "rectangle", # "shape": "rectangle" # }, # { # "classTitle": "kiwi", # "description": "", # "tags": [], # "points": { # "exterior": [ # [ # 196, # 212 # ], # [ # 352, # 380 # ] # ], # "interior": [] # }, # "geometryType": "rectangle", # "shape": "rectangle" # }, # { # "classTitle": "kiwi", # "description": "", # "tags": [], # "points": { # "exterior": [ # [ # 425, # 561 # ], # [ # 576, # 705 # ] # ], # "interior": [] # }, # "geometryType": "rectangle", # "shape": "rectangle" # } # ], # "customBigData": {} # } """ aux_mapping = mapping.copy() to_render_mapping = {} to_render_labels = [] other_labels = [] _polygons_to_bitmaps_classes = {} for lbl in self.labels: if type(lbl.geometry) in [Bitmap, Polygon]: to_render_labels.append(lbl) if type(lbl.geometry) is Polygon: new_class = _polygons_to_bitmaps_classes.get(lbl.obj_class.name, None) if new_class is None: new_class = lbl.obj_class.clone(geometry_type=Bitmap) _polygons_to_bitmaps_classes[lbl.obj_class.name] = new_class aux_mapping[new_class] = aux_mapping[lbl.obj_class] to_render_mapping[lbl.obj_class] = new_class else: to_render_mapping[lbl.obj_class] = lbl.obj_class else: other_labels.append(lbl) ann_raster = self.clone(labels=to_render_labels) ann_raster = ann_raster.to_nonoverlapping_masks(to_render_mapping) new_labels = [] for lbl in [*ann_raster.labels, *other_labels]: dest_class = aux_mapping[lbl.obj_class] if dest_class is None: continue # skip labels if dest_class == lbl.obj_class and lbl.obj_class.geometry_type != AnyGeometry: new_labels.append(lbl) else: bbox = lbl.geometry.to_bbox() new_lbl = lbl.clone(geometry=bbox, obj_class=dest_class) new_labels.append(new_lbl) new_ann = self.clone(labels=new_labels) return new_ann
[docs] def masks_to_imgaug( self, class_to_index: Dict[str, int] ) -> Union[SegmentationMapsOnImage, None]: """ Convert current annotation objects masks to SegmentationMapsOnImage format. :param class_to_index: Dict matching class name to index. :type class_to_index: dict :return: SegmentationMapsOnImage, otherwise :class:`None` :rtype: :class:`SegmentationMapsOnImage` or :class:`NoneType` """ try: from imgaug.augmentables.segmaps import SegmentationMapsOnImage except ModuleNotFoundError as e: logger.error( f'{e}. Try to install extra dependencies. Run "pip install supervisely[aug]"' ) raise e h = self.img_size[0] w = self.img_size[1] mask = np.zeros((h, w, 1), dtype=np.int32) for index, label in enumerate(self.labels, start=1): label: Label if type(label.geometry) == Bitmap: if class_to_index is not None: label.draw(mask, class_to_index[label.obj_class.name]) else: label.draw(mask, index) segmaps = None if np.any(mask): segmaps = SegmentationMapsOnImage(mask, shape=self.img_size) return segmaps
[docs] def bboxes_to_imgaug(self) -> Union[BoundingBoxesOnImage, None]: """ Convert current annotation objects boxes to BoundingBoxesOnImage format. :return: BoundingBoxesOnImage, otherwise :class:`None` :rtype: :class:`BoundingBoxesOnImage` or :class:`NoneType` """ try: from imgaug.augmentables.bbs import BoundingBox, BoundingBoxesOnImage except ModuleNotFoundError as e: logger.error( f'{e}. Try to install extra dependencies. Run "pip install supervisely[aug]"' ) raise e boxes = [] for label in self.labels: if type(label.geometry) == Rectangle: rect: Rectangle = label.geometry boxes.append( BoundingBox( x1=rect.left, y1=rect.top, x2=rect.right, y2=rect.bottom, label=label.obj_class.name, ) ) bbs = None if len(boxes) > 0: bbs = BoundingBoxesOnImage(boxes, shape=self.img_size) return bbs
[docs] @classmethod def from_imgaug( cls, img: np.ndarray, ia_boxes: Optional[List[BoundingBoxesOnImage]] = None, ia_masks: Optional[List[SegmentationMapsOnImage]] = None, index_to_class: Optional[Dict[int, str]] = None, meta: Optional[ProjectMeta] = None, ) -> Annotation: """ Create Annotation from image and SegmentationMapsOnImage, BoundingBoxesOnImage data or ProjectMeta. :param img: Image in numpy format. :type img: np.ndarray :param ia_boxes: List of BoundingBoxesOnImage data. :type ia_boxes: List[BoundingBoxesOnImage], optional :param ia_masks: List of SegmentationMapsOnImage data. :type ia_masks: List[SegmentationMapsOnImage], optional :param index_to_class: Dictionary specifying index match of class name. :type index_to_class: Dict[int, str], optional :param meta: ProjectMeta. :type meta: ProjectMeta, optional :raises: :class:`ValueError`, if ia_boxes or ia_masks and meta is None :raises: :class:`KeyError`, if processed ObjClass not found in meta :return: Annotation object :rtype: :class:`Annotation<Annotation>` """ if ((ia_boxes is not None) or (ia_masks is not None)) and meta is None: raise ValueError("Project meta has to be provided") labels = [] if ia_boxes is not None: for ia_box in ia_boxes: obj_class = meta.get_obj_class(ia_box.label) if obj_class is None: raise KeyError("Class {!r} not found in project meta".format(ia_box.label)) lbl = Label( Rectangle(top=ia_box.y1, left=ia_box.x1, bottom=ia_box.y2, right=ia_box.x2), obj_class, ) labels.append(lbl) if ia_masks is not None: if index_to_class is None: raise ValueError( "mapping from index to class name is needed to transform masks to SLY format" ) class_mask = ia_masks.get_arr() # mask = white_mask == 255 (unique, counts) = np.unique(class_mask, return_counts=True) for index, count in zip(unique, counts): if index == 0: continue mask = class_mask == index bitmap = Bitmap(data=mask[:, :, 0]) restore_class = meta.get_obj_class(index_to_class[index]) labels.append(Label(geometry=bitmap, obj_class=restore_class)) return cls(img_size=img.shape[:2], labels=labels)
[docs] def is_empty(self) -> bool: """ Check whether annotation contains labels or tags, or not. :returns: True if annotation is empty, False otherwise. :rtype: :class:`bool` """ if len(self.labels) == 0 and len(self.img_tags) == 0: return True else: return False
[docs] def filter_labels_by_classes(self, keep_classes: List[str]) -> Annotation: """ Filter annotation labels by given classes names. :param keep_classes: List with classes names. :type keep_classes: List[str] :return: New instance of Annotation :rtype: :class:`Annotation<Annotation>` .. code-block:: python import supervisely as sly address = 'https://app.supervise.ly/' token = 'Your Supervisely API Token' api = sly.Api(address, token) # Get image annotation from API project_id = 7473 image_id = 2223200 meta_json = api.project.get_meta(project_id) meta = sly.ProjectMeta.from_json(meta_json) ann_info = api.annotation.download(image_id) print(json.dumps(ann_info.annotation, indent=4)) # Output: { # "description": "", # "tags": [], # "size": { # "height": 800, # "width": 1067 # }, # "objects": [ # { # "id": 57388829, # "classId": 121405, # "description": "", # "geometryType": "bitmap", # "labelerLogin": "alex", # "createdAt": "2022-01-02T08:06:05.183Z", # "updatedAt": "2022-01-02T08:07:12.219Z", # "tags": [], # "classTitle": "kiwi", # "bitmap": { # "data": "eJyNlWs4lHkYxv/z8qp5NZuYSZes1KZm6CDSTuP0mh2ZWYdmULakdBUzkRqRrDG8M1vZZg8WIy..., # "origin": [ # 481, # 543 # ] # } # }, # { # "id": 57388831, # "classId": 121404, # "description": "", # "geometryType": "bitmap", # "labelerLogin": "alex", # "createdAt": "2022-01-02T08:06:59.133Z", # "updatedAt": "2022-01-02T08:07:12.219Z", # "tags": [], # "classTitle": "lemon", # "bitmap": { # "data": "eJwdV388k/sXfzwmz3TVNm3l94z5saQoXFSG+c26tM1GYjWUISWKK201SchvIcq30tX2rDuTdE..., # "origin": [ # 523, # 119 # ] # } # }, # { # "id": 57388832, # "classId": 121405, # "description": "", # "geometryType": "bitmap", # "labelerLogin": "alex", # "createdAt": "2022-01-02T08:07:12.104Z", # "updatedAt": "2022-01-02T08:07:12.104Z", # "tags": [], # "classTitle": "kiwi", # "bitmap": { # "data": "eJw1VglQU8kWTWISHzH6AzwwIEsSCBr2AApBloAJhB0eCEIYDJFN2WHACC7sRghbIAiIMuKj..., # "origin": [ # 773, # 391 # ] # } # } # ] # } ann = sly.Annotation.from_json(ann_info.annotation, meta) # Let's filter 'lemon' class keep_classes = ['lemon'] filter_ann = ann.filter_labels_by_classes(keep_classes) print(json.dumps(filter_ann.to_json(), indent=4)) # Output: { # "description": "", # "size": { # "height": 800, # "width": 1067 # }, # "tags": [], # "objects": [ # { # "classTitle": "lemon", # "description": "", # "tags": [], # "bitmap": { # "origin": [ # 523, # 119 # ], # "data": "eJwBOAPH/IlQTkcNChoKAAAADUlIRFIAAAEsAAABCQEDAAAAFNKIswAAAAZQTFRFAAAA..., # }, # "shape": "bitmap", # "geometryType": "bitmap", # "labelerLogin": "alex", # "updatedAt": "2022-01-02T08:07:12.219Z", # "createdAt": "2022-01-02T08:06:59.133Z", # "id": 57388831, # "classId": 121404 # } # ], # "customBigData": {} # } """ new_labels = [] for lbl in self.labels: if lbl.obj_class.name in keep_classes: new_labels.append(lbl.clone()) return self.clone(labels=new_labels)
[docs] def get_bindings(self) -> Dict[str, List[Label]]: """Returns dictionary with bindings keys as keys and list of labels as values. :return: Dictionary with bindings keys as keys and list of labels as values. :rtype: Dict[str, List[Label]] """ d = defaultdict(list) for label in self.labels: # if label.binding_key is not None: d[label.binding_key].append(label) return d
[docs] def discard_bindings(self) -> None: """Remove binding keys from all labels.""" for label in self.labels: label.binding_key = None