Source code for supervisely.geometry.alpha_mask

# coding: utf-8

# docs
from __future__ import annotations

import base64
import io
import zlib
from typing import Optional

import cv2
import numpy as np
from PIL import Image

from supervisely.geometry.bitmap import Bitmap, _find_mask_tight_bbox
from supervisely.geometry.bitmap_base import BitmapBase
from supervisely.geometry.image_rotator import ImageRotator
from supervisely.geometry.point_location import PointLocation
from supervisely.imaging.image import read


[docs] class AlphaMask(Bitmap): """Bitmap mask with per-pixel alpha; used for semi-transparent segmentation. Immutable."""
[docs] @staticmethod def geometry_name(): """ Returns the name of the geometry. :returns: name of the geometry :rtype: str """ return "alpha_mask"
def __init__( self, data: np.ndarray, origin: Optional[PointLocation] = 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, extra_validation: Optional[bool] = True, ): """ AlphaMask geometry for a single :class:`~supervisely.annotation.label.Label`. :class:`~supervisely.geometry.alpha_mask.AlphaMask` object is immutable. :param data: AlphaMask mask data. Must be a numpy array with values in range [0, 255]. :type data: np.ndarray :param origin: :class:`~supervisely.geometry.point_location.PointLocation`: top, left corner of AlphaMask. Position of the AlphaMask within image. :type origin: :class:`~supervisely.geometry.point_location.PointLocation`, optional :param sly_id: AlphaMask ID in Supervisely server. :type sly_id: int, optional :param class_id: ID of ObjClass to which AlphaMask belongs. :type class_id: int, optional :param labeler_login: Login of the user who created :class:`~supervisely.geometry.alpha_mask.AlphaMask`. :type labeler_login: str, optional :param updated_at: Date and Time when AlphaMask 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 AlphaMask was created. Date Format is the same as in "updated_at" parameter. :type created_at: str, optional :param extra_validation: If True, additional validation is performed. Throws a ValueError if values of the data are not in the range [0, 255]. If True it will affect performance. :type extra_validation: bool, optional :raises ValueError, if data values are not in the range [0, 255]. :Usage Example: .. code-block:: python import supervisely as sly # Create simple alpha mask: mask = np.array([[0, 0, 0, 0, 0], [0, 50, 50, 50, 0], [0, 50, 0, 50, 0], [0, 50, 50, 50, 0], [0, 0, 0, 0, 0]], dtype=np.uint8) figure = sly.AlphaMask(mask) origin = figure.origin.to_json() print(json.dumps(origin, indent=4)) # Output: { # "points": { # "exterior": [ # [ # 1, # 1 # ] # ], # "interior": [] # } # Create alpha mask from PNG image: img = sly.imaging.image.read(os.path.join(os.getcwd(), 'black_white.png')) mask = img[:, :, 3] figure = sly.AlphaMask(mask) """ if data.dtype != np.uint8: if data.dtype == np.bool: data = data.astype(np.uint8) * 255 else: data = np.array(data, dtype=np.uint8) if extra_validation: if not np.all(np.isin(data, range(256))): max_val = np.max(data) min_val = np.min(data) if max_val > 255 or min_val < 0: raise ValueError( f"Alpha mask data values must be in range [0, 255]. Instead got min: {min_val}, max: {max_val}." ) # Call base constructor first to do the basic dimensionality checks. BitmapBase.__init__( self, data=data, origin=origin, expected_data_dims=2, sly_id=sly_id, class_id=class_id, labeler_login=labeler_login, updated_at=updated_at, created_at=created_at, ) if not np.any(data): raise ValueError( "Creating a alpha mask with an empty mask (no pixels set to True) is not supported." ) data_tight_bbox = _find_mask_tight_bbox(self._data) self._origin = self._origin.translate(drow=data_tight_bbox.top, dcol=data_tight_bbox.left) self._data = data_tight_bbox.get_cropped_numpy_slice(self._data)
[docs] def rotate(self, rotator: ImageRotator) -> AlphaMask: """ Rotates current AlphaMask. :param rotator: ImageRotator for AlphaMask rotation. :type rotator: :class:`~supervisely.geometry.image_rotator.ImageRotator` :returns: Alpha mask :rtype: :class:`~supervisely.geometry.alpha_mask.AlphaMask` :Usage Example: .. code-block:: python import supervisely as sly from supervisely.geometry.image_rotator import ImageRotator height, width = ann.img_size rotator = ImageRotator((height, width), 25) # Remember that AlphaMask class object is immutable, and we need to assign new instance of AlphaMask to a new variable rotate_figure = figure.rotate(rotator) """ full_img_mask = np.zeros(rotator.src_imsize, dtype=np.uint8) self.draw(full_img_mask, 255) # TODO this may break for one-pixel masks (it can disappear during rotation). Instead, rotate every pixel # individually and set it in the resulting alpha mask. new_mask = rotator.rotate_img(full_img_mask, use_inter_nearest=True) return AlphaMask(data=new_mask)
def _draw_impl(self, bitmap, color, thickness=1, config=None): """Draws the AlphaMask on a bitmap.""" channels = bitmap.shape[2] if len(bitmap.shape) == 3 else 1 non_zero_values = self.data > 0 alpha = self.data / 255.0 if channels >= 3: if isinstance(color, int): color = [color] * 3 temp_mask = np.zeros(self.data.shape + (3,), dtype=np.uint8) temp_mask[non_zero_values] = color for i in range(3): canvas = self.to_bbox().get_cropped_numpy_slice(bitmap)[:, :, i] canvas[non_zero_values] = (canvas * (1 - alpha) + alpha * temp_mask[:, :, i])[ non_zero_values ] elif channels == 1: temp_mask = np.zeros(self.data.shape, dtype=np.uint8) temp_mask[non_zero_values] = color canvas = self.to_bbox().get_cropped_numpy_slice(bitmap) canvas[non_zero_values] = (canvas * (1 - alpha) + alpha * temp_mask)[non_zero_values] @property def area(self) -> float: """ AlphaMask area. :returns: Area of current :class:`~supervisely.geometry.alpha_mask.AlphaMask` :rtype: float :Usage Example: .. code-block:: python print(figure.area) # Output: 88101.0 """ return float(np.count_nonzero(self._data))
[docs] @staticmethod def data_2_base64(mask: np.ndarray) -> str: """ Convert numpy array to base64 encoded string. :param mask: Bool numpy array. :type mask: np.ndarray :returns: Base64 encoded string :rtype: str :Usage Example: .. code-block:: python import os from dotenv import load_dotenv import supervisely as sly # Load secrets and create API object from .env file (recommended) # Learn more here: https://developer.supervisely.com/getting-started/basics-of-authentication if sly.is_development(): load_dotenv(os.path.expanduser("~/supervisely.env")) api = sly.Api.from_env() # Get annotation from API 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) # Get AlphaMask from annotation for label in ann.labels: if type(label.geometry) == sly.AlphaMask: figure = label.geometry encoded_string = sly.AlphaMask.data_2_base64(figure.data) print(encoded_string) # 'eJzrDPBz5+WS4mJgYOD19HAJAtLMIMwIInOeqf8BUmwBPiGuQPr///9Lb86/C2QxlgT5BTM4PLuRBuTwebo4hlTMSa44sKHhISMDuxpTYrr03F6gDIOnq5/LOqeEJgDM5ht6' """ # img_pil = Image.fromarray(mask) img_pil = Image.fromarray(np.array(mask, dtype=np.uint8), mode="L") bytes_io = io.BytesIO() img_pil.save(bytes_io, format="PNG", transparency=0, optimize=0) bytes_enc = bytes_io.getvalue() return base64.b64encode(zlib.compress(bytes_enc)).decode("utf-8")
[docs] @staticmethod def base64_2_data(s: str) -> np.ndarray: """ Convert base64 encoded string to numpy array. Supports both compressed and uncompressed masks. :param s: Input base64 encoded string. :type s: str :returns: numpy array :rtype: :class:`np.ndarray` :Usage Example: .. code-block:: python import supervisely as sly encoded_string = 'eJzrDPBz5+WS4mJgYOD19HAJAtLMIMwIInOeqf8BUmwBPiGuQPr///9Lb86/C2QxlgT5BTM4PLuRBuTwebo4hlTMSa44sKHhISMDuxpTYrr03F6gDIOnq5/LOqeEJgDM5ht6' figure_data = sly.AlphaMask.base64_2_data(encoded_string) print(figure_data) uncompressed_string = 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA' mask = sly.AlphaMask.base64_2_data(uncompressed_string) print(mask) """ try: z = zlib.decompress(base64.b64decode(s)) except zlib.error: # If the string is not compressed, we'll not use zlib. img = Image.open(io.BytesIO(base64.b64decode(s))) return np.array(img) n = np.frombuffer(z, np.uint8) imdecoded = cv2.imdecode(n, cv2.IMREAD_GRAYSCALE) # pylint: disable=no-member if (len(imdecoded.shape) == 3) and (imdecoded.shape[2] == 4): mask = imdecoded[:, :, 3] # pylint: disable=unsubscriptable-object if (len(imdecoded.shape) == 3) and (imdecoded.shape[2] == 1): mask = imdecoded[:, :, 0] # pylint: disable=unsubscriptable-object elif len(imdecoded.shape) == 2: mask = imdecoded else: raise RuntimeError("Wrong internal mask format.") return mask
[docs] @classmethod def allowed_transforms(cls): """Returns the allowed transforms for the AlphaMask.""" from supervisely.geometry.any_geometry import AnyGeometry from supervisely.geometry.bitmap import Bitmap from supervisely.geometry.polygon import Polygon from supervisely.geometry.rectangle import Rectangle return [AnyGeometry, Bitmap, Polygon, Rectangle]
[docs] @classmethod def from_path(cls, path: str) -> AlphaMask: """ Read alpha_channel from image by path. :param path: Path to image :type path: str :returns: Alpha mask :rtype: :class:`~supervisely.geometry.alpha_mask.AlphaMask` """ img = cv2.imread(path, cv2.IMREAD_UNCHANGED) # pylint: disable=no-member if len(img.shape) == 2: return AlphaMask(img) elif img.shape[2] == 1: return AlphaMask(img[:, :, 0]) elif img.shape[2] == 4: img = cv2.cvtColor(img, cv2.COLOR_BGRA2RGBA) # pylint: disable=no-member return AlphaMask(img[:, :, 3]) else: img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # pylint: disable=no-member return AlphaMask(img)