Source code for supervisely.geometry.vector_geometry

# coding: utf-8

# docs
from __future__ import annotations
from copy import deepcopy
import cv2
import numpy as np
from typing import List, Tuple, Dict, Optional, Union, Iterable
from supervisely.geometry.image_rotator import ImageRotator

from supervisely.geometry.constants import (
    EXTERIOR,
    INTERIOR,
    POINTS,
    GEOMETRY_SHAPE,
    GEOMETRY_TYPE,
)
from supervisely.geometry.geometry import Geometry
from supervisely.geometry.point_location import PointLocation, points_to_row_col_list
from supervisely.geometry.rectangle import Rectangle


[docs]class VectorGeometry(Geometry): """ VectorGeometry is a base class of geometry for a single :class:`Label<supervisely.annotation.label.Label>`. :class:`VectorGeometry<VectorGeometry>` class object is immutable. :param exterior: Exterior coordinates, object contour is defined with these points (used for :class:`Polygon<supervisely.geometry.polygon.Polygon>`). :type exterior: List[PointLocation], List[List[int, int]], List[Tuple[int, int] :param interior: Interior coordinates, object holes is defined with these points (used for :class:`Polygon<supervisely.geometry.polygon.Polygon>`). :type interior: List[List[PointLocation]], List[List[List[int, int]]], List[List[Tuple[int, int]]] :param sly_id: VectorGeometry ID in Supervisely server. :type sly_id: int, optional :param class_id: ID of :class:`ObjClass<supervisely.annotation.obj_class.ObjClass>` to which VectorGeometry belongs. :type class_id: int, optional :param labeler_login: Login of the user who created VectorGeometry. :type labeler_login: str, optional :param updated_at: Date and Time when VectorGeometry was modified last. Date Format: Year:Month:Day:Hour:Minute:Seconds. Example: '2021-01-22T19:37:50.158Z'. :type updated_at: str, optional :param created_at: Date and Time when VectorGeometry was created. Date Format is the same as in "updated_at" parameter. :type created_at: str, optional :raises: :class:`TypeError`, if exterior or interior parameters are not a list of PointLocation objects :Usage example: .. code-block:: python import supervisely as sly exterior = [sly.PointLocation(730, 2104), sly.PointLocation(2479, 402), sly.PointLocation(3746, 1646)] # or exterior = [[730, 2104], [2479, 402], [3746, 1646]] # or exterior = [(730, 2104), (2479, 402), (3746, 1646)] interior = [[sly.PointLocation(1907, 1255), sly.PointLocation(2468, 875), sly.PointLocation(2679, 1577)]] # or interior = [[[730, 2104], [2479, 402], [3746, 1646]]] # or interior = [[(730, 2104), (2479, 402), (3746, 1646)]] figure = sly.Polygon(exterior, interior) """ def __init__( self, exterior: Union[ List[PointLocation], List[List[int, int]], List[Tuple[int, int]] ], interior: Union[ List[List[PointLocation]], List[List[List[int, int]]], List[List[Tuple[int, int]]], ] = [], sly_id: Optional[int] = None, class_id: Optional[int] = None, labeler_login: Optional[int] = None, updated_at: Optional[str] = None, created_at: Optional[str] = None, ): result_exterior = [] if not isinstance(exterior, list): raise TypeError('Argument "exterior" must be a list of coordinates') for p in exterior: if isinstance(p, PointLocation): result_exterior.append(p) elif isinstance(p, tuple) and len(p) == 2: result_exterior.append(PointLocation(p[0], p[1])) elif isinstance(p, list) and len(p) == 2: result_exterior.append(PointLocation(p[0], p[1])) else: raise TypeError( 'Type of items (coordinates) in list "exterior" have to be tuple(int, int) or list[int, int] or PointLocation(row, col)' ) result_interior = [] if not isinstance(interior, list): raise TypeError( 'Argument "interior" must be a list of lists with coordinates' ) for coords in interior: if not isinstance(interior, list): raise TypeError('"interior" coords must be a list of coordinates') p_coords = [] for p in coords: if isinstance(p, PointLocation): p_coords.append(p) elif isinstance(p, tuple) and len(p) == 2: p_coords.append(PointLocation(p[0], p[1])) elif isinstance(p, list) and len(p) == 2: p_coords.append(PointLocation(p[0], p[1])) else: raise TypeError( 'Type of items (coordinates) in list "interior" have to be tuple(int, int) or list[int, int] or PointLocation(row, col)' ) result_interior.append(p_coords) self._exterior = deepcopy(result_exterior) self._interior = deepcopy(result_interior) super().__init__( sly_id=sly_id, class_id=class_id, labeler_login=labeler_login, updated_at=updated_at, created_at=created_at, )
[docs] def to_json(self) -> Dict: """ Convert the VectorGeometry 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 figure_json = figure.to_json() print(figure_json) # Output: { # "points": { # "exterior": [ # [2104, 730], # [402, 2479], # [1646, 3746] # ], # "interior": [ # [ # [1255, 1907], # [875, 2468], # [1577, 2679] # ] # ] # } # } """ packed_obj = { POINTS: { EXTERIOR: points_to_row_col_list( self._exterior, flip_row_col_order=True ), INTERIOR: [ points_to_row_col_list(i, flip_row_col_order=True) for i in self._interior ], }, GEOMETRY_SHAPE: self.geometry_name(), GEOMETRY_TYPE: self.geometry_name(), } self._add_creation_info(packed_obj) return packed_obj
@property def exterior(self) -> List[PointLocation]: """ VectorGeometry exterior points. :return: VectorGeometry exterior points :rtype: :class:`List[PointLocation]<supervisely.geometry.point_location.PointLocation>` :Usage example: .. code-block:: python exterior = figure.exterior """ return deepcopy(self._exterior) @property def exterior_np(self) -> np.ndarray: """ Converts exterior attribute of VectorGeometry to numpy array. :return: Numpy array :rtype: :class:`np.ndarray` :Usage example: .. code-block:: python print(figure.exterior_np) # Output: # [[ 730 2104] # [2479 402] # [3746 1646]] """ return np.array(points_to_row_col_list(self._exterior), dtype=np.int64) @property def interior(self) -> List[List[PointLocation]]: """ VectorGeometry interior points. :return: VectorGeometry interior points :rtype: :class:`List[List[PointLocation]]<supervisely.geometry.point_location.PointLocation>` :Usage example: .. code-block:: python interior = figure.interior """ return deepcopy(self._interior) @property def interior_np(self): """ Converts interior attribute of VectorGeometry to numpy array. :return: Numpy array :rtype: :class:`List[np.ndarray]` :Usage example: .. code-block:: python print(figure.interior_np) # Output: # [array([[1907, 1255], # [2468, 875], # [2679, 1577]])] """ return [ np.array(points_to_row_col_list(i), dtype=np.int64) for i in self._interior ] def _transform(self, transform_fn): """ """ result = deepcopy(self) result._exterior = [transform_fn(p) for p in self._exterior] result._interior = [[transform_fn(p) for p in i] for i in self._interior] return result
[docs] def resize( self, in_size: Tuple[int, int], out_size: Tuple[int, int] ) -> VectorGeometry: """ Resizes current VectorGeometry. :param in_size: Input image size (height, width) to which belongs VectorGeometry. :type in_size: Tuple[int, int] :param out_size: Desired output image size (height, width) to which belongs VectorGeometry. :type out_size: Tuple[int, int] :return: VectorGeometry object :rtype: :class:`VectorGeometry<VectorGeometry>` :Usage Example: .. code-block:: python # Remember that VectorGeometry class object is immutable, and we need to assign new instance of VectorGeometry to a new variable in_height, in_width = 300, 400 out_height, out_width = 600, 800 resize_figure = figure.resize((in_height, in_width), (out_height, out_width)) """ return self._transform(lambda p: p.resize(in_size, out_size))
[docs] def scale(self, factor: float) -> VectorGeometry: """ Scales current VectorGeometry. :param factor: Scale parameter. :type factor: float :return: VectorGeometry object :rtype: :class:`VectorGeometry<VectorGeometry>` :Usage Example: .. code-block:: python # Remember that VectorGeometry class object is immutable, and we need to assign new instance of VectorGeometry to a new variable scale_figure = figure.scale(0.75) """ return self._transform(lambda p: p.scale(factor))
[docs] def translate(self, drow: int, dcol: int) -> VectorGeometry: """ Translates current VectorGeometry. :param drow: Horizontal shift. :type drow: int :param dcol: Vertical shift. :type dcol: int :return: VectorGeometry object :rtype: :class:`VectorGeometry<VectorGeometry>` :Usage Example: .. code-block:: python # Remember that VectorGeometry class object is immutable, and we need to assign new instance of VectorGeometry to a new variable translate_figure = figure.translate(150, 250) """ return self._transform(lambda p: p.translate(drow, dcol))
[docs] def rotate(self, rotator: ImageRotator) -> VectorGeometry: """ Rotates current VectorGeometry. :param rotator: ImageRotator object for rotate. :type rotator: ImageRotator :return: VectorGeometry object :rtype: :class:`VectorGeometry<VectorGeometry>` :Usage Example: .. code-block:: python from supervisely.geometry.image_rotator import ImageRotator # Remember that VectorGeometry class object is immutable, and we need to assign new instance of VectorGeometry to a new variable height, width = 300, 400 rotator = ImageRotator((height, width), 25) rotate_figure = figure.rotate(rotator) """ return self._transform(lambda p: p.rotate(rotator))
[docs] def fliplr(self, img_size: Tuple[int, int]) -> VectorGeometry: """ Flips current VectorGeometry in horizontal. :param img_size: Input image size (height, width) to which belongs VectorGeometry. :type img_size: Tuple[int, int] :return: VectorGeometry object :rtype: :class:`VectorGeometry<VectorGeometry>` :Usage Example: .. code-block:: python # Remember that VectorGeometry class object is immutable, and we need to assign new instance of VectorGeometry to a new variable height, width = 300, 400 fliplr_figure = figure.fliplr((height, width)) """ return self._transform(lambda p: p.fliplr(img_size))
[docs] def flipud(self, img_size: Tuple[int, int]) -> VectorGeometry: """ Flips current VectorGeometry in vertical. :param img_size: Input image size (height, width) to which belongs VectorGeometry. :type img_size: Tuple[int, int] :return: VectorGeometry object :rtype: :class:`VectorGeometry<VectorGeometry>` :Usage Example: .. code-block:: python # Remember that VectorGeometry class object is immutable, and we need to assign new instance of VectorGeometry to a new variable height, width = 300, 400 flipud_figure = figure.flipud((height, width)) """ return self._transform(lambda p: p.flipud(img_size))
[docs] def to_bbox(self) -> Rectangle: """ Creates Rectangle object from current VectorGeometry. :return: Rectangle object :rtype: :class:`Rectangle<supervisely.geometry.rectangle.Rectangle>` :Usage Example: .. code-block:: python rectangle = figure.to_bbox() """ exterior_np = self.exterior_np rows, cols = exterior_np[:, 0], exterior_np[:, 1] return Rectangle( top=round(min(rows).item()), left=round(min(cols).item()), bottom=round(max(rows).item()), right=round(max(cols).item()), )
def _draw_impl(self, bitmap, color, thickness=1, config=None): """ :param bitmap: np.ndarray :param color: [R, G, B] :param thickness: used only in Polyline and Point """ self._draw_contour_impl(bitmap, color, thickness, config=config) def _draw_contour_impl(self, bitmap, color, thickness=1, config=None): """Draws the figure contour on a given bitmap canvas :param bitmap: np.ndarray :param color: [R, G, B] :param thickness: (int) """ raise NotImplementedError() @staticmethod def _approx_ring_dp(ring, epsilon, closed): """ """ new_ring = cv2.approxPolyDP(ring.astype(np.int32), epsilon, closed) new_ring = np.squeeze(new_ring, 1) if len(new_ring) < 3 and closed: new_ring = ring.astype(np.int32) return new_ring
[docs] def approx_dp(self, epsilon): """ """ raise NotImplementedError()