# 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


class Rectangle(Geometry):
    """Axis-aligned 2D bounding box (top, left, bottom, right). Immutable."""

    @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),
        ]

    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

    @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,
        )

    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),
        ]

    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))

    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))

    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))

    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))

    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),
        )

    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,
        )

    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)

    @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.
    @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)

    @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

    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
        )

    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)

    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

    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), ...]

    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 allowed_transforms(cls):
        """
        Returns the allowed transforms for the Rectangle.
        """
        from supervisely.geometry.alpha_mask import AlphaMask
        from supervisely.geometry.any_geometry import AnyGeometry
        from supervisely.geometry.bitmap import Bitmap
        from supervisely.geometry.polygon import Polygon
        from supervisely.geometry.oriented_bbox import OrientedBBox

        return [AlphaMask, AnyGeometry, Bitmap, Polygon, Rectangle, OrientedBBox]

    @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,
    #     )
