import copy
import numpy as np
from . import BaseAnnotationDefinition
from .polygon import Polygon
import warnings
[docs]class Box(BaseAnnotationDefinition):
"""
Box annotation object
Can create a box using 2 point using: "top", "left", "bottom", "right" (to form a box [(left, top), (right, bottom)])
For rotated box add the "angel"
"""
type = "box"
def __init__(self,
left=None, top=None, right=None, bottom=None,
label=None, attributes=None, description=None, angle=None):
"""
Can create a box using 2 point using:
"top", "left", "bottom", "right" (to form a box [(left, top), (right, bottom)])
And for create a rotated box need to add the angle of the rotation
:param left: left x coordinate of the box
:param top: top Y coordinate of the box
:param right: right x coordinate of the box
:param bottom: bottom Y coordinate of the box
:param label: annotation label
:param attributes: a list of attributes for the annotation
:param description:
:param angle: the angle of the rotation in degrees
:return:
"""
super().__init__(description=description, attributes=attributes)
self.angle = angle
self.left = left
self.top = top
self.right = right
self.bottom = bottom
self.top_left = [left, top]
self.top_right = [right, top]
self.bottom_left = [left, bottom]
self.bottom_right = [right, bottom]
self.label = label
self._four_points = self._rotate_around_point() if self.is_rotated else [self.top_left,
self.bottom_left,
self.bottom_right,
self.top_right]
@property
def is_rotated(self):
return self.angle is not None and self.angle != 0
@property
def x(self):
if self._box_points_setting():
return [x_point[0] for x_point in self._four_points]
return [self.left, self.right]
@property
def y(self):
if self._box_points_setting():
return [y_point[1] for y_point in self._four_points]
return [self.top, self.bottom]
@property
def geo(self):
if self._box_points_setting():
res = self._four_points
else:
res = [
[self.left, self.top],
[self.right, self.bottom]
]
return res
def _box_points_setting(self):
res = False
if self._annotation and self._annotation.item:
item = self._annotation.item
project_id = item.project_id if item.project_id else item.project.id
settings_dict = item._client_api.platform_settings.settings.get('4ptBox', None)
if settings_dict is not None:
if project_id in settings_dict:
res = settings_dict.get(project_id, None)
elif '*' in settings_dict:
res = settings_dict.get('*', None)
return res
def _rotate_points(self, points):
angle = np.radians(self.angle)
rotation_matrix = np.asarray([[np.cos(angle), -np.sin(angle)],
[np.sin(angle), np.cos(angle)]])
pts2 = np.asarray([rotation_matrix.dot(pt)[:2] for pt in points])
return pts2
def _translate(self, points, translate_x, translate_y=None):
translation_matrix = np.asarray([[1, 0, translate_x],
[0, 1, translate_y],
[0, 0, 1]])
pts2 = np.asarray([translation_matrix.dot(list(pt) + [1])[:2] for pt in points])
return pts2
def _rotate_around_point(self):
points = copy.deepcopy(self.four_points)
center = [((self.left + self.right) / 2), ((self.top + self.bottom) / 2)]
centerized = self._translate(points, -center[0], -center[1])
rotated = self._rotate_points(centerized)
moved = self._translate(rotated, center[0], center[1])
return moved
@property
def four_points(self):
return [self.top_left, self.bottom_left, self.bottom_right, self.top_right]
[docs] def show(self, image, thickness, with_text, height, width, annotation_format, color, alpha=1):
"""
Show annotation as ndarray
:param image: empty or image to draw on
:param thickness:
:param with_text: not required
:param height: item height
:param width: item width
:param annotation_format: options: list(dl.ViewAnnotationOptions)
:param color: color
:param alpha: opacity value [0 1], default 1
:return: ndarray
"""
try:
import cv2
except (ImportError, ModuleNotFoundError):
self.logger.error(
'Import Error! Cant import cv2. Annotations operations will be limited. import manually and fix errors')
raise
if thickness is None:
thickness = 2
# draw annotation
if self.is_rotated:
points = self._rotate_around_point()
else:
points = self.four_points
# create image to draw on
if alpha != 1:
overlay = image.copy()
else:
overlay = image
# draw annotation
overlay = cv2.drawContours(
image=overlay,
contours=[np.round(points).astype(int)],
contourIdx=-1,
color=color,
thickness=thickness,
lineType=cv2.LINE_AA
)
if not isinstance(color, int) and len(color) == 4 and color[3] != 255:
# add with opacity
image = cv2.addWeighted(src1=overlay,
alpha=alpha,
src2=image,
beta=1 - alpha,
gamma=0)
else:
image = overlay
if with_text:
image = self.add_text_to_image(image=image, annotation=self)
return image
def to_coordinates(self, color):
points = [
[self.left, self.top],
[self.right, self.bottom]
]
pts = [{"x": float(x), "y": float(y), "z": 0} for x, y in points]
if self.angle is not None and self.angle != 0:
pts.append(self.angle)
return pts
@staticmethod
def from_coordinates(coordinates):
return np.asarray([[pt["x"], pt["y"]] for pt in coordinates[:2]])
@classmethod
def from_json(cls, _json):
coordinates = _json.get("coordinates", None) if "coordinates" in _json else _json.get("data", None)
if coordinates is None:
raise ValueError('can not find "coordinates" or "data" in annotation. id: {}'.format(_json["id"]))
geo = cls.from_coordinates(coordinates=coordinates)
left = np.min(geo[:, 0])
top = np.min(geo[:, 1])
right = np.max(geo[:, 0])
bottom = np.max(geo[:, 1])
angel = coordinates[2] if len(coordinates) > 2 and (
isinstance(coordinates[2], float) or isinstance(coordinates[2], int)) else None
return cls(
left=left,
top=top,
right=right,
bottom=bottom,
label=_json["label"],
attributes=_json.get("attributes", None),
angle=angel
)
[docs] @classmethod
def from_segmentation(cls, mask, label, attributes=None):
"""
Convert binary mask to Polygon
:param mask: binary mask (0,1)
:param label: annotation label
:param attributes: annotations list of attributes
:return: Box annotations list to each separated segmentation
"""
polygons = Polygon.from_segmentation(
mask=mask,
label=label,
attributes=attributes,
max_instances=None
)
if not isinstance(polygons, list):
polygons = [polygons]
boxes = [
cls(
left=polygon.left,
top=polygon.top,
right=polygon.right,
bottom=polygon.bottom,
label=label,
attributes=attributes
) for polygon in polygons
]
return boxes