Source code for supervisely.geometry.bitmap_base

# coding: utf-8
from __future__ import annotations
import numpy as np
from typing import Tuple, Dict, Optional

from supervisely.geometry.constants import DATA, ORIGIN, GEOMETRY_SHAPE, GEOMETRY_TYPE, \
                                               LABELER_LOGIN, UPDATED_AT, CREATED_AT, ID, CLASS_ID
from supervisely.geometry.geometry import Geometry
from supervisely.geometry.point_location import PointLocation
from supervisely.geometry.rectangle import Rectangle
from supervisely.imaging.image import resize_inter_nearest, restore_proportional_size


if not hasattr(np, 'bool'): np.bool = np.bool_

# TODO: rename to resize_bitmap_and_origin
def resize_origin_and_bitmap(origin: PointLocation, bitmap: np.ndarray, in_size: Tuple[int, int], out_size: Tuple[int, int]) -> Tuple[PointLocation, np.ndarray]:
    """
    Change PointLocation and resize numpy array to match a certain size.

    :param origin: PointLocation to resize.
    :type origin: PointLocation
    :param bitmap: Numpy array to resize.
    :type bitmap: np.ndarray
    :param in_size: Input image size (height, width) to which belongs :class:`PointLocation<supervisely.geometry.point_location.PointLocation>` object and numpy array.
    :type in_size: Tuple[int, int]
    :param out_size: Desired output image size (height, width) to which belongs :class:`PointLocation<supervisely.geometry.point_location.PointLocation>` object and numpy array.
    :type out_size: Tuple[int, int]

    :return: PointLocation object and numpy array
    :rtype: :class:`PointLocation<supervisely.geometry.point_location.PointLocation>`, :class:`np.ndarray`
    :Usage Example:

     .. code-block:: python

        resize_origin, resize_bitmap = resize_origin_and_bitmap(origin, bitmap, (400, 500), (800, 1000))
    """
    new_size = restore_proportional_size(in_size=in_size, out_size=out_size)

    row_scale = new_size[0] / in_size[0]
    col_scale = new_size[1] / in_size[1]

    # TODO: Double check (+restore_proportional_size) or not? bitmap.shape and in_size are equal?
    # Make sure the resulting size has at least one pixel in every direction (i.e. limit the shrinkage to avoid having
    # empty bitmaps as a result).
    scaled_rows = max(round(bitmap.shape[0] * row_scale), 1)
    scaled_cols = max(round(bitmap.shape[1] * col_scale), 1)

    scaled_origin = PointLocation(row=round(origin.row * row_scale), col=round(origin.col * col_scale))
    scaled_bitmap = resize_inter_nearest(bitmap, (scaled_rows, scaled_cols))
    return scaled_origin, scaled_bitmap


[docs]class BitmapBase(Geometry): """ BitmapBase is a base class of :class:`Bitmap<supervisely.geometry.bitmap.Bitmap>` geometry. :class:`BitmapBase<BitmapBase>` class object is immutable. :param data: Bitmap mask data. :type data: np.ndarray :param origin: :class:`PointLocation<supervisely.geometry.point_location.PointLocation>`: top, left corner of Bitmap. Position of the Bitmap within image. :type origin: PointLocation, optional :param expected_data_dims: Number of dimensions of data numpy array. :type expected_data_dims: int, optional :param sly_id: Bitmap ID in Supervisely server. :type sly_id: int, optional :param class_id: ID of :class:`ObjClass<supervisely.annotation.obj_class.ObjClass>` to which Bitmap belongs. :type class_id: int, optional :param labeler_login: Login of the user who created Bitmap. :type labeler_login: str, optional :param updated_at: Date and Time when Bitmap 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 Bitmap was created. Date Format is the same as in "updated_at" parameter. :type created_at: str, optional :Usage example: Example of creating and using see in :class:`Bitmap<supervisely.geometry.bitmap.Bitmap>`. """ def __init__(self, data: np.ndarray, origin: Optional[PointLocation] = None, expected_data_dims: Optional[int] = None, 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): super().__init__(sly_id=sly_id, class_id=class_id, labeler_login=labeler_login, updated_at=updated_at, created_at=created_at) if origin is None: origin = PointLocation(row=0, col=0) if not isinstance(origin, PointLocation): raise TypeError('BitmapBase "origin" argument must be "PointLocation" object!') if not isinstance(data, np.ndarray): raise TypeError('BitmapBase "data" argument must be numpy array object!') data_dims = len(data.shape) if expected_data_dims is not None and data_dims != expected_data_dims: raise ValueError(f'BitmapBase "data" argument must be a {expected_data_dims}-dimensional numpy array. Instead got {data_dims} dimensions') self._origin = origin.clone() self._data = data.copy() @classmethod def _impl_json_class_name(cls): """Descendants must implement this to return key string to look up serialized representation in a JSON dict.""" raise NotImplementedError()
[docs] @staticmethod def base64_2_data(s: str) -> np.ndarray: """ """ raise NotImplementedError()
[docs] @staticmethod def data_2_base64(data: np.ndarray) -> str: """ """ raise NotImplementedError()
[docs] def to_json(self) -> Dict: """ Convert the BitmapBase 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 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.bool_) figure = sly.Bitmap(mask) figure_json = figure.to_json() print(json.dumps(figure_json, indent=4)) # Output: { # "bitmap": { # "origin": [1, 1], # "data": "eJzrDPBz5+WS4mJgYOD19HAJAtLMIMwIInOeqf8BUmwBPiGuQPr///9Lb86/C2QxlgT5BTM4PLuRBuTwebo4hlTMSa44sKHhISMDuxpTYrr03F6gDIOnq5/LOqeEJgDM5ht6" # }, # "shape": "bitmap", # "geometryType": "bitmap" # } """ res = { self._impl_json_class_name(): { ORIGIN: [self.origin.col, self.origin.row], DATA: self.data_2_base64(self.data) }, GEOMETRY_SHAPE: self.geometry_name(), GEOMETRY_TYPE: self.geometry_name(), } self._add_creation_info(res) return res
[docs] @classmethod def from_json(cls, json_data: Dict) -> BitmapBase: """ Convert a json dict to BitmapBase. Read more about `Supervisely format <https://docs.supervise.ly/data-organization/00_ann_format_navi>`_. :param data: Bitmap in json format as a dict. :type data: dict :return: BitmapBase object :rtype: :class:`BitmapBase<BitmapBase>` :Usage example: .. code-block:: python import supervisely as sly figure_json = { "bitmap": { "origin": [1, 1], "data": "eJzrDPBz5+WS4mJgYOD19HAJAtLMIMwIInOeqf8BUmwBPiGuQPr///9Lb86/C2QxlgT5BTM4PLuRBuTwebo4hlTMSa44sKHhISMDuxpTYrr03F6gDIOnq5/LOqeEJgDM5ht6" }, "shape": "bitmap", "geometryType": "bitmap" } figure = sly.Bitmap.from_json(figure_json) """ json_root_key = cls._impl_json_class_name() if json_root_key not in json_data: raise ValueError( 'Data must contain {} field to create MultichannelBitmap object.'.format(json_root_key)) if ORIGIN not in json_data[json_root_key] or DATA not in json_data[json_root_key]: raise ValueError('{} field must contain {} and {} fields to create MultichannelBitmap object.'.format( json_root_key, ORIGIN, DATA)) col, row = json_data[json_root_key][ORIGIN] data = cls.base64_2_data(json_data[json_root_key][DATA]) labeler_login = json_data.get(LABELER_LOGIN, None) updated_at = json_data.get(UPDATED_AT, None) created_at = json_data.get(CREATED_AT, None) sly_id = json_data.get(ID, None) class_id = json_data.get(CLASS_ID, None) return cls(data=data, origin=PointLocation(row=row, col=col), sly_id=sly_id, class_id=class_id, labeler_login=labeler_login, updated_at=updated_at, created_at=created_at)
@property def origin(self) -> PointLocation: """ Position of the Bitmap within image. :return: Top, left corner of Bitmap. :rtype: :class:`PointLocation<supervisely.geometry.point_location.PointLocation>` """ return self._origin.clone() @property def data(self) -> np.ndarray: """ Get mask data of Bitmap. :return: Data of Bitmap. :rtype: :class:`np.ndarray` """ return self._data.copy()
[docs] def translate(self, drow: int, dcol: int) -> BitmapBase: """ Translate current Bitmap. :param drow: Horizontal shift. :type drow: int :param dcol: Vertical shift. :type dcol: int :return: BitmapBase object :rtype: :class:`BitmapBase<BitmapBase>` :Usage Example: .. code-block:: python # Remember that Bitmap class object is immutable, and we need to assign new instance of Bitmap to a new variable translate_figure = figure.translate(150, 250) """ translated_origin = self.origin.translate(drow, dcol) return self.__class__(data=self.data, origin=translated_origin)
[docs] def fliplr(self, img_size: Tuple[int, int]) -> BitmapBase: """ Flip current Bitmap in horizontal. :param img_size: :class:`Annotation.img_size<supervisely.annotation.annotation.Annotation.img_size>` which belongs Bitmap. :type img_size: Tuple[int, int] :return: BitmapBase object :rtype: :class:`BitmapBase<BitmapBase>` :Usage Example: .. code-block:: python # Remember that Bitmap class object is immutable, and we need to assign new instance of Bitmap to a new variable height, width = 300, 400 fliplr_figure = figure.fliplr((height, width)) """ flipped_mask = np.flip(self.data, axis=1) flipped_origin = PointLocation(row=self.origin.row, col=(img_size[1] - flipped_mask.shape[1] - self.origin.col)) return self.__class__(data=flipped_mask, origin=flipped_origin)
[docs] def flipud(self, img_size: Tuple[int, int]) -> BitmapBase: """ Flip current Bitmap in vertical. :param img_size: :class:`Annotation.img_size<supervisely.annotation.annotation.Annotation.img_size>` which belongs Bitmap. :type img_size: Tuple[int, int] :return: BitmapBase object :rtype: :class:`BitmapBase<BitmapBase>` :Usage Example: .. code-block:: python # Remember that Bitmap class object is immutable, and we need to assign new instance of Bitmap to a new variable height, width = 300, 400 flipud_figure = figure.flipud((height, width)) """ flipped_mask = np.flip(self.data, axis=0) flipped_origin = PointLocation(row=(img_size[0] - flipped_mask.shape[0] - self.origin.row), col=self.origin.col) return self.__class__(data=flipped_mask, origin=flipped_origin)
[docs] def scale(self, factor: float) -> BitmapBase: """ Scale current Bitmap. :param factor: Scale parameter. :type factor: float :return: BitmapBase object :rtype: :class:`BitmapBase<BitmapBase>` :Usage Example: .. code-block:: python # Remember that Bitmap class object is immutable, and we need to assign new instance of Bitmap to a new variable scale_figure = figure.scale(0.75) """ new_rows = round(self._data.shape[0] * factor) new_cols = round(self._data.shape[1] * factor) mask = self._resize_mask(self.data, new_rows, new_cols) origin = self.origin.scale(factor) return self.__class__(data=mask, origin=origin)
@staticmethod def _resize_mask(mask, out_rows, out_cols): """ """ return resize_inter_nearest(mask.astype(np.uint8), (out_rows, out_cols)).astype(np.bool)
[docs] def to_bbox(self) -> Rectangle: """ Create :class:`Rectangle<supervisely.geometry.rectangle.Rectangle>` object from current Bitmap. :return: Rectangle object :rtype: :class:`Rectangle<supervisely.geometry.rectangle.Rectangle>` :Usage Example: .. code-block:: python rectangle = figure.to_bbox() """ return Rectangle.from_array(self._data).translate(drow=self._origin.row, dcol=self._origin.col)