# coding: utf-8
# docs
from __future__ import annotations
from copy import deepcopy
from math import ceil, floor
from typing import TYPE_CHECKING, Dict, List, Optional, Tuple, Union
import cv2
import numpy as np
from supervisely.geometry import validation
from supervisely.geometry.constants import (
CLASS_ID,
CREATED_AT,
EXTERIOR,
ID,
INTERIOR,
LABELER_LOGIN,
POINTS,
UPDATED_AT,
)
from supervisely.geometry.geometry import Geometry
from supervisely.geometry.point_location import PointLocation, points_to_row_col_list
if TYPE_CHECKING:
from supervisely.geometry.image_rotator import ImageRotator
[docs]
class Rectangle(Geometry):
"""Axis-aligned 2D bounding box (top, left, bottom, right). Immutable."""
[docs]
@staticmethod
def geometry_name():
"""
Returns the name of the geometry.
:returns: name of the geometry
:rtype: str
"""
return "rectangle"
def __init__(
self,
top: int,
left: int,
bottom: int,
right: 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,
):
"""
Axis-aligned 2D bounding box.
:param top: Top edge (min row).
:type top: int or float
:param left: Left edge (min col).
:type left: int or float
:param bottom: Bottom edge (max row).
:type bottom: int or float
:param right: Right edge (max col).
:type right: int or float
:param sly_id: Server-side rectangle ID.
:type sly_id: int, optional
:param class_id: ObjClass ID.
:type class_id: int, optional
:param labeler_login: Login of user who created the rectangle.
:type labeler_login: str, optional
:param updated_at: Last modification timestamp (ISO format).
:type updated_at: str, optional
:param created_at: Creation timestamp (ISO format).
:type created_at: str, optional
:raises ValueError: If top > bottom or left > right.
:Usage Example:
.. code-block:: python
import supervisely as sly
rect = sly.Rectangle(100, 100, 700, 900)
"""
if top > bottom:
raise ValueError(
'Rectangle "top" argument must have less or equal value then "bottom"!'
)
if left > right:
raise ValueError(
'Rectangle "left" argument must have less or equal value then "right"!'
)
super().__init__(
sly_id=sly_id,
class_id=class_id,
labeler_login=labeler_login,
updated_at=updated_at,
created_at=created_at,
)
self._points = [
PointLocation(row=top, col=left),
PointLocation(row=bottom, col=right),
]
[docs]
def to_json(self) -> Dict:
"""
Convert the Rectangle to a json dict. Read more about `Supervisely format <https://docs.supervisely.com/data-organization/00_ann_format_navi>`_.
:returns: Rectangle in json format as a dict.
:rtype: dict
:Usage Example:
.. code-block:: python
figure_json = figure.to_json()
print(figure_json)
# Output: {
# "points": {
# "exterior": [
# [100, 100],
# [900, 700]
# ],
# "interior": []
# }
# }
"""
packed_obj = {
POINTS: {
EXTERIOR: points_to_row_col_list(self._points, flip_row_col_order=True),
INTERIOR: [],
}
}
self._add_creation_info(packed_obj)
return packed_obj
[docs]
@classmethod
def from_json(cls, data: Dict) -> Rectangle:
"""
Convert a json dict to Rectangle. Read more about `Supervisely format <https://docs.supervisely.com/data-organization/00_ann_format_navi>`_.
:param data: Rectangle in json format as a dict.
:type data: dict
:returns: Rectangle from json.
:rtype: :class:`~supervisely.geometry.rectangle.Rectangle`
:Usage Example:
.. code-block:: python
import supervisely as sly
figure_json = {
"points": {
"exterior": [
[100, 100],
[900, 700]
],
"interior": []
}
}
figure = sly.Rectangle.from_json(figure_json)
"""
validation.validate_geometry_points_fields(data)
labeler_login = data.get(LABELER_LOGIN, None)
updated_at = data.get(UPDATED_AT, None)
created_at = data.get(CREATED_AT, None)
sly_id = data.get(ID, None)
class_id = data.get(CLASS_ID, None)
exterior = data[POINTS][EXTERIOR]
if len(exterior) != 2:
raise ValueError(
'"exterior" field must contain exactly two points to create Rectangle object.'
)
[top, bottom] = sorted([exterior[0][1], exterior[1][1]])
[left, right] = sorted([exterior[0][0], exterior[1][0]])
return cls(
top=top,
left=left,
bottom=bottom,
right=right,
sly_id=sly_id,
class_id=class_id,
labeler_login=labeler_login,
updated_at=updated_at,
created_at=created_at,
)
[docs]
def crop(self, other: Rectangle) -> List[Rectangle]:
"""
Crops current Rectangle.
:param other: Rectangle to crop from.
:type other: :class:`~supervisely.geometry.rectangle.Rectangle`
:returns: List of Rectangles from Rectangle.
:rtype: List[:class:`~supervisely.geometry.rectangle.Rectangle`]
:Usage Example:
.. code-block:: python
import supervisely as sly
crop_figures = figure.crop(sly.Rectangle(0, 0, 300, 350))
"""
top = max(self.top, other.top)
left = max(self.left, other.left)
bottom = min(self.bottom, other.bottom)
right = min(self.right, other.right)
is_valid = (bottom >= top) and (left <= right)
return [Rectangle(top=top, left=left, bottom=bottom, right=right)] if is_valid else []
def _transform(self, transform_fn):
""" """
transformed_corners = [transform_fn(p) for p in self.corners]
rows, cols = zip(*points_to_row_col_list(transformed_corners))
return Rectangle(
top=round(min(rows)),
left=round(min(cols)),
bottom=round(max(rows)),
right=round(max(cols)),
)
@property
def corners(
self,
) -> List[PointLocation, PointLocation, PointLocation, PointLocation]:
"""
Get list of Rectangle corners.
:returns: List of Rectangle corners.
:rtype: List[:class:`~supervisely.geometry.point_location.PointLocation`]
:Usage Example:
.. code-block:: python
corners = figure.corners
for corner in corners:
print(corner.row, corner.col)
# Output:
# 100 100
# 100 900
# 700 900
# 700 100
"""
return [
PointLocation(row=self.top, col=self.left),
PointLocation(row=self.top, col=self.right),
PointLocation(row=self.bottom, col=self.right),
PointLocation(row=self.bottom, col=self.left),
]
[docs]
def rotate(self, rotator: ImageRotator) -> Rectangle:
"""
Rotates current Rectangle.
:param rotator: Class for object rotation.
:type rotator: :class:`~supervisely.geometry.image_rotator.ImageRotator`
:returns: Rotated Rectangle.
:rtype: :class:`~supervisely.geometry.rectangle.Rectangle`
:Usage Example:
.. code-block:: python
from supervisely.geometry.image_rotator import ImageRotator
# Remember that Rectangle class object is immutable, and we need to assign new instance of Rectangle to a new variable
height, width = 300, 400
rotator = ImageRotator((height, width), 25)
rotate_figure = figure.rotate(rotator)
"""
return self._transform(lambda p: rotator.transform_point(p))
[docs]
def resize(self, in_size: Tuple[int, int], out_size: Tuple[int, int]) -> Rectangle:
"""
Resizes current Rectangle.
:param in_size: Input image size (height, width) to which belongs Rectangle.
:type in_size: Tuple[int, int]
:param out_size: Desired output image size (height, width) to which belongs Rectangle.
:type out_size: Tuple[int, int]
:returns: Resized Rectangle.
:rtype: :class:`~supervisely.geometry.rectangle.Rectangle`
:Usage Example:
.. code-block:: python
# Remember that Rectangle class object is immutable, and we need to assign new instance of Rectangle 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) -> Rectangle:
"""
Scales current Rectangle.
:param factor: Scale parameter.
:type factor: float
:returns: Scaled Rectangle.
:rtype: :class:`~supervisely.geometry.rectangle.Rectangle`
:Usage Example:
.. code-block:: python
# Remember that Rectangle class object is immutable, and we need to assign new instance of Rectangle 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) -> Rectangle:
"""
Translates current Rectangle.
:param drow: Horizontal shift.
:type drow: int
:param dcol: Vertical shift.
:type dcol: int
:returns: Translated Rectangle.
:rtype: :class:`~supervisely.geometry.rectangle.Rectangle`
:Usage Example:
.. code-block:: python
# Remember that Rectangle class object is immutable, and we need to assign new instance of Rectangle to a new variable
translate_figure = figure.translate(150, 250)
"""
return self._transform(lambda p: p.translate(drow, dcol))
[docs]
def fliplr(self, img_size: Tuple[int, int]) -> Rectangle:
"""
Flips current Rectangle in horizontal.
:param img_size: Input image size (height, width) to which belongs Rectangle.
:type img_size: Tuple[int, int]
:returns: Flipped Rectangle in horizontal.
:rtype: :class:`~supervisely.geometry.rectangle.Rectangle`
:Usage Example:
.. code-block:: python
# Remember that Rectangle class object is immutable, and we need to assign new instance of Rectangle to a new variable
height, width = 300, 400
fliplr_figure = figure.fliplr((height, width))
"""
img_width = img_size[1]
return Rectangle(
top=self.top,
left=(img_width - self.right),
bottom=self.bottom,
right=(img_width - self.left),
)
[docs]
def flipud(self, img_size: Tuple[int, int]) -> Rectangle:
"""
Flips current Rectangle in vertical.
:param img_size: Input image size (height, width) to which belongs Rectangle.
:type img_size: Tuple[int, int]
:returns: Flipped Rectangle in vertical.
:rtype: :class:`~supervisely.geometry.rectangle.Rectangle`
:Usage Example:
.. code-block:: python
# Remember that Rectangle class object is immutable, and we need to assign new instance of Rectangle to a new variable
height, width = 300, 400
flipud_figure = figure.flipud((height, width))
"""
img_height = img_size[0]
return Rectangle(
top=(img_height - self.bottom),
left=self.left,
bottom=(img_height - self.top),
right=self.right,
)
def _draw_impl(self, bitmap: np.ndarray, color, thickness=1, config=None):
""" """
self._draw_contour_impl(bitmap, color, thickness=cv2.FILLED, config=config) # due to cv2
def _draw_contour_impl(self, bitmap, color, thickness=1, config=None):
""" """
cv2.rectangle(
bitmap,
pt1=(self.left, self.top),
pt2=(self.right, self.bottom),
color=color,
thickness=thickness,
)
[docs]
def to_bbox(self) -> Rectangle:
"""
Makes a copy of Rectangle.
:returns: Copy of Rectangle.
:rtype: :class:`~supervisely.geometry.rectangle.Rectangle`
:Usage Example:
.. code-block:: python
# Remember that Rectangle class object is immutable, and we need to assign new instance of Rectangle to a new variable
new_figure = figure.to_bbox()
"""
return self.clone()
@property
def area(self) -> float:
"""
Rectangle area.
:returns: Area of current Rectangle.
:rtype: float
:Usage Example:
.. code-block:: python
print(figure.area)
# Output: 7288.0
"""
return float(self.width * self.height)
[docs]
@classmethod
def from_array(cls, arr: np.ndarray) -> Rectangle:
"""
Create Rectangle with given array shape.
:param arr: Numpy array.
:type arr: np.ndarray
:returns: Rectangle from array.
:rtype: :class:`~supervisely.geometry.rectangle.Rectangle`
:Usage Example:
.. code-block:: python
import supervisely as sly
np_array = np.zeros((300, 400))
figure_from_np = sly.Rectangle.from_array(np_array)
"""
return cls(top=0, left=0, bottom=arr.shape[0] - 1, right=arr.shape[1] - 1)
# TODO re-evaluate whether we need this, looks trivial.
[docs]
@classmethod
def from_size(cls, size: Tuple[int, int]) -> Rectangle:
"""
Create Rectangle with given size shape.
:param size: Input size.
:type size: Tuple[int, int]
:returns: Rectangle from size.
:rtype: :class:`~supervisely.geometry.rectangle.Rectangle`
:Usage Example:
.. code-block:: python
import supervisely as sly
size = (300, 400)
figure_from_size = sly.Rectangle.from_size(size)
"""
return cls(0, 0, size[0] - 1, size[1] - 1)
[docs]
@classmethod
def from_geometries_list(cls, geometries: List[Geometry]) -> Rectangle:
"""
Create Rectangle from given geometry objects.
:param geometries: List of geometry type objects: :class:`~supervisely.geometry.bitmap.Bitmap>`, :class:`~supervisely.geometry.cuboid.Cuboid>`, :class:`~supervisely.geometry.point.Point>`, :class:`~supervisely.geometry.polygon.Polygon>`, :class:`~supervisely.geometry.polyline.Polyline>`, :class:`~supervisely.geometry.rectangle.Rectangle>`, :class:`~supervisely.geometry.graph.GraphNodes>`.
:type geometries: List[:class:`~supervisely.geometry.geometry.Geometry`]
:returns: Rectangle from given geometry objects.
:rtype: :class:`~supervisely.geometry.rectangle.Rectangle`
:Usage Example:
.. code-block:: python
import supervisely as sly
geom_objs = [sly.Point(100, 200), sly.Polyline([sly.PointLocation(730, 2104), sly.PointLocation(2479, 402)])]
figure_from_geom_objs = sly.Rectangle.from_geometries_list(geom_objs)
"""
if geometries is None or len(geometries) == 0:
raise ValueError("No geometries provided to create a Rectangle.")
bboxes = [g.to_bbox() for g in geometries]
top = min(bbox.top for bbox in bboxes)
left = min(bbox.left for bbox in bboxes)
bottom = max(bbox.bottom for bbox in bboxes)
right = max(bbox.right for bbox in bboxes)
return cls(top=top, left=left, bottom=bottom, right=right)
@property
def left(self) -> int:
"""
Minimal horizontal value of Rectangle.
:returns: Minimal horizontal value
:rtype: int
:Usage Example:
.. code-block:: python
print(figure.left)
# Output: 100
"""
return self._points[0].col
@property
def right(self) -> int:
"""
Maximal horizontal value of Rectangle.
:returns: Maximal horizontal value
:rtype: int
:Usage Example:
.. code-block:: python
print(figure.right)
# Output: 900
"""
return self._points[1].col
@property
def top(self) -> int:
"""
Minimal vertical value of Rectangle.
:returns: Minimal vertical value
:rtype: int
:Usage Example:
.. code-block:: python
print(rectangle.top)
# Output: 100
"""
return self._points[0].row
@property
def bottom(self) -> int:
"""
Maximal vertical value of Rectangle.
:returns: Maximal vertical value
:rtype: int
:Usage Example:
.. code-block:: python
print(figure.bottom)
# Output: 700
"""
return self._points[1].row
@property
def center(self) -> PointLocation:
"""
Center of Rectangle.
:returns: Center of Rectangle.
:rtype: :class:`~supervisely.geometry.point_location.PointLocation`
:Usage Example:
.. code-block:: python
center = figure.center()
"""
return PointLocation(row=(self.top + self.bottom) // 2, col=(self.left + self.right) // 2)
@property
def width(self) -> int:
"""
Width of Rectangle.
:returns: Width
:rtype: int
:Usage Example:
.. code-block:: python
print(figure.width)
# Output: 801
"""
return self.right - self.left + 1
@property
def height(self) -> int:
"""
Height of Rectangle
:returns: Height
:rtype: int
:Usage Example:
.. code-block:: python
print(figure.height)
# Output: 601
"""
return self.bottom - self.top + 1
[docs]
def contains(self, rect: Rectangle) -> bool:
"""
Checks if Rectangle contains a given Rectangle object.
:param rect: Rectangle to check if it contains.
:type rect: :class:`~supervisely.geometry.rectangle.Rectangle`
:returns: True if Rectangle contains given Rectangle, otherwise False
:rtype: bool
:Usage Example:
.. code-block:: python
import supervisely as sly
rect = sly.Rectangle(200, 250, 400, 500))
print(figure.contains(rect))
# Output: True
"""
return (
self.left <= rect.left
and self.right >= rect.right
and self.top <= rect.top
and self.bottom >= rect.bottom
)
[docs]
def contains_point_location(self, pt: PointLocation) -> bool:
"""
Checks if Rectangle contains a given PointLocation object.
:param pt: PointLocation to check if it contains.
:type pt: :class:`~supervisely.geometry.point_location.PointLocation`
:returns: True if Rectangle contains given PointLocation, otherwise False
:rtype: bool
:Usage Example:
.. code-block:: python
import supervisely as sly
pt = sly.PointLocation(250, 300))
print(figure.contains_point_location(pt))
# Output: True
"""
return (self.left <= pt.col <= self.right) and (self.top <= pt.row <= self.bottom)
[docs]
def to_size(self) -> Tuple[int, int]:
"""
Height and width of Rectangle.
:returns: Height and width of Rectangle.
:rtype: :class:`Tuple[int, int]`
:Usage Example:
.. code-block:: python
height, width = figure.to_size()
print(height, width)
# Output: 700 900
"""
return self.height, self.width
[docs]
def get_cropped_numpy_slice(self, data: np.ndarray) -> np.ndarray:
"""
Slice of given numpy array with Rectangle.
:param data: Numpy array.
:type data: np.ndarray
:returns: Sliced numpy array
:rtype: :class:`np.ndarray<np.ndarray>`
:Usage Example:
.. code-block:: python
np_slice = np.zeros((200, 500))
mask_slice = figure.get_cropped_numpy_slice(np_slice)
print(mask_slice.shape)
# Output: (199, 499)
"""
return data[self.top : (self.bottom + 1), self.left : (self.right + 1), ...]
[docs]
def intersects_with(self, rect: Rectangle) -> bool:
"""
Checks intersects Rectangle with given Rectangle object or not.
:param rect: Rectangle to check if it intersects with.
:type rect: :class:`~supervisely.geometry.rectangle.Rectangle`
:returns: True if given Rectangle intersects with Rectangle, otherwise False
:rtype: bool
:Usage Example:
.. code-block:: python
import supervisely as sly
rect = sly.Rectangle(90, 90, 400, 500)
print(figure.intersects_with(rect))
# Output: True
"""
if self.left > rect.right or self.right < rect.left:
return False
if self.top > rect.bottom or self.bottom < rect.top:
return False
return True
@classmethod
def _round_subpixel_coordinates(
cls,
top: Union[int, float],
left: Union[int, float],
bottom: Union[int, float],
right: Union[int, float],
) -> Tuple[int, int, int, int]:
"""
Apply rounding logic to the Rectangle coordinates. Coordinates will remain in subpixel precision.
Top will be rounded down if the decimal part is lesser than 0.7, it will include the vertical pixel if it is in the range, otherwise it will be rounded up and this pixel will not be included.
Left will be rounded down if the decimal part is lesser than 0.7, it will include the horizontal pixel if it is in the range, otherwise it will be rounded up and this pixel will not be included.
Bottom will be rounded up if the decimal part is greater than 0.3, it will include the vertical pixel if it is in the range, otherwise it will be rounded down and this pixel will not be included.
Right will be rounded up if the decimal part is greater than 0.3, it will include the horizontal pixel if it is in the range, otherwise it will be rounded down and this pixel will not be included.
Example:
Input coordinates:
- top = 1.55, left = 1.74, bottom = 4.63, right = 3.76
Output coordinates:
- top = 1, left = 2, bottom = 5, right = 4
- top will be rounded down to 2, left will be rounded down to 2, bottom will be rounded up to 6, right will be rounded down to 6
Subpixel coordinate system:
0 1 2 3 4 5
0 +---+---+---+---+---+
| | | | | |
1 +---+---+---+---+---+
| | | x | x | |
2 +---+---+---+---+---+
| | | x | x | |
3 +---+---+---+---+---+
| | | x | x | |
4 +---+---+---+---+---+
| | | x | x | |
5 +---+---+---+---+---+
x x
:param top: Minimal vertical value of Rectangle.
:type top: Union[int, float]
:param left: Minimal horizontal value of Rectangle.
:type left: Union[int, float]
:param bottom: Maximal vertical value of Rectangle.
:type bottom: Union[int, float]
:param right: Maximal horizontal value of Rectangle.
:type right: Union[int, float]
:returns: Rounded Rectangle coordinates
:rtype: Tuple[int, int, int, int]
"""
RIGHT_OVERLAP = 0.3
LEFT_OVERLAP = 1 - RIGHT_OVERLAP
# Check case if all coords in 1 pixel range
if int(top) == int(bottom) and int(left) == int(right):
return int(top), int(left), int(bottom), int(right)
if top % 1 >= LEFT_OVERLAP:
top = ceil(top)
else:
top = floor(top)
if left % 1 >= LEFT_OVERLAP:
left = ceil(left)
else:
left = floor(left)
if bottom % 1 >= RIGHT_OVERLAP:
bottom = ceil(bottom)
else:
bottom = floor(bottom)
if right % 1 >= RIGHT_OVERLAP:
right = ceil(right)
else:
right = floor(right)
return top, left, bottom, right
@classmethod
def _to_pixel_coordinate_system_json(cls, data: Dict, image_size: List[int]) -> Dict:
"""
Convert Rectangle from subpixel precision to pixel precision by subtracting a subpixel offset from the coordinates.
Points order in json format: [[left, top], [right, bottom]]
In the labeling tool, labels are created with subpixel precision,
which means that the coordinates of the rectangle corners (top, left and bottom, right) can have decimal values representing fractions of a pixel.
However, in Supervisely SDK, geometry coordinates are represented using pixel precision, where the coordinates are integers representing whole pixels.
Example:
Step 1. Input coordinates:
- top = 1.55, left = 1.74, bottom = 4.63, right = 3.76
Step 2. Round the coordinates (still remain in subpixel precision):
- top = 1, left = 2, bottom = 5, right = 4
- top will be rounded down to 2, left will be rounded down to 2, bottom will be rounded up to 6, right will be rounded down to 6
Draw coordinates in pixel coordinate system:
0 1 2 3 4 5
0 +---+---+---+---+---+
| | | | | |
1 +---+---+---+---+---+
| | | x | x | |
2 +---+---+---+---+---+
| | | x | x | |
3 +---+---+---+---+---+
| | | x | x | |
4 +---+---+---+---+---+
| | | x | x | |
5 +---+---+---+---+---+
x x
Step 3. Convert to pixel coordinates by subtracting a subpixel offset:
- top = 1, left = 2, bottom = 4, right = 3
Draw coordinates in pixel coordinate system:
0 1 2 3 4 5
0 +---+---+---+---+---+
| | | | | |
1 +---+---+---+---+---+
| | | x | x | |
2 +---+---+---+---+---+
| | | x | x | |
3 +---+---+---+---+---+
| | | x | x | |
4 +---+---+---+---+---+
| | | x | x | |
5 +---+---+---+---+---+
:param data: Json data with geometry config.
:type data: dict
:param image_size: Image size in pixels (height, width).
:type image_size: List[int]
:returns: Json data with coordinates converted to pixel coordinate system.
:rtype: dict
"""
data = deepcopy(data) # Avoid modifying the original data
height, width = image_size[:2]
exterior = data[POINTS][EXTERIOR]
[top, bottom] = sorted([exterior[0][1], exterior[1][1]])
[left, right] = sorted([exterior[0][0], exterior[1][0]])
top, left, bottom, right = cls._round_subpixel_coordinates(top, left, bottom, right)
right = max(left, right - 1)
bottom = max(top, bottom - 1)
data[POINTS][EXTERIOR] = [[left, top], [right, bottom]]
return data
@classmethod
def _to_subpixel_coordinate_system_json(cls, data: Dict) -> Dict:
"""
Convert Rectangle from pixel precision to subpixel precision by adding a subpixel offset to the coordinates.
Points order in json format: [[left, top], [right, bottom]]
In the labeling tool, labels are created with subpixel precision,
which means that the coordinates of the rectangle corners (top, left and bottom, right) can have decimal values representing fractions of a pixel.
However, in Supervisely SDK, geometry coordinates are represented using pixel precision, where the coordinates are integers representing whole pixels.
:param data: Json data with geometry config.
:type data: dict
:returns: Json data with coordinates converted to subpixel coordinate system.
:rtype: dict
"""
data = deepcopy(data) # Avoid modifying the original data
exterior = data[POINTS][EXTERIOR]
[top, bottom] = sorted([exterior[0][1], exterior[1][1]])
[left, right] = sorted([exterior[0][0], exterior[1][0]])
right = max(left, right + 1)
bottom = max(top, bottom + 1)
data[POINTS][EXTERIOR] = [[left, top], [right, bottom]]
return data
# def _to_pixel_coordinate_system(self) -> Rectangle:
# """
# Convert Rectangle from subpixel precision to pixel precision by subtracting a subpixel offset from the coordinates.
# In the labeling tool, labels are created with subpixel precision,
# which means that the coordinates of the rectangle corners (top, left and bottom, right) can have decimal values representing fractions of a pixel.
# However, in Supervisely SDK, geometry coordinates are represented using pixel precision, where the coordinates are integers representing whole pixels.
# Example:
# Step 1. Input coordinates:
# - top = 1.55, left = 1.74, bottom = 4.63, right = 3.76
# Step 2. Round the coordinates:
# - top = 1, left = 2, bottom = 5, right = 4
# - top will be rounded down to 2, left will be rounded down to 2, bottom will be rounded up to 6, right will be rounded down to 6
# Draw coordinates in pixel coordinate system:
# 0 1 2 3 4 5
# 0 +---+---+---+---+---+
# | | | | | |
# 1 +---+---+---+---+---+
# | | | x | x | |
# 2 +---+---+---+---+---+
# | | | x | x | |
# 3 +---+---+---+---+---+
# | | | x | x | |
# 4 +---+---+---+---+---+
# | | | x | x | |
# 5 +---+---+---+---+---+
# x x
# Step 3. Convert to pixel coordinates by subtracting a subpixel offset:
# - top = 1, left = 2, bottom = 4, right = 3
# Draw coordinates in pixel coordinate system:
# 0 1 2 3 4 5
# 0 +---+---+---+---+---+
# | | | | | |
# 1 +---+---+---+---+---+
# | | | x | x | |
# 2 +---+---+---+---+---+
# | | | x | x | |
# 3 +---+---+---+---+---+
# | | | x | x | |
# 4 +---+---+---+---+---+
# | | | x | x | |
# 5 +---+---+---+---+---+
# :returns: New instance of Rectangle object with corners in pixel format.
# :rtype: :class:`~supervisely.geometry.rectangle.Rectangle`
# """
# left = floor(self.left)
# top = floor(self.top)
# right = max(left, floor(self.right) - 1)
# bottom = max(top, floor(self.bottom) - 1)
# return Rectangle(
# top=top,
# left=left,
# bottom=bottom,
# right=right,
# sly_id=self.sly_id,
# class_id=self.class_id,
# labeler_login=self.labeler_login,
# updated_at=self.updated_at,
# created_at=self.created_at,
# )
# def _to_subpixel_coordinate_system(self) -> Rectangle:
# """
# Convert Rectangle from pixel precision to subpixel precision by adding a subpixel offset to the coordinates.
# In the labeling tool, labels are created with subpixel precision,
# which means that the coordinates of the rectangle corners (top, left and bottom, right) can have decimal values representing fractions of a pixel.
# However, in Supervisely SDK, geometry coordinates are represented using pixel precision, where the coordinates are integers representing whole pixels.
# :returns: New instance of Rectangle object with corners in subpixel format.
# :rtype: :class:`~supervisely.geometry.rectangle.Rectangle`
# """
# left = self.left
# top = self.top
# right = self.right + 1
# bottom = self.bottom + 1
# return Rectangle(
# top=top,
# left=left,
# bottom=bottom,
# right=right,
# sly_id=self.sly_id,
# class_id=self.class_id,
# labeler_login=self.labeler_login,
# updated_at=self.updated_at,
# created_at=self.created_at,
# )