Source code for scistag.imagestag.bounding

"""
Implements the location, size and bounding definition classes Size2D, Pos2D and
Bounding2D.

These are for example required for painting operations in ImageStag and defining
a Widget's layout and position in SlideStag.
"""

from __future__ import annotations
from dataclasses import dataclass
from typing import Union

from .size2d import Size2D, Size2DTypes
from .pos2d import Pos2D, Pos2DTypes

Bounding2DTypes = Union["Bounding2D",
                        tuple[int, int, int, int],
                        tuple[float, float, float, float],
                        tuple[Pos2DTypes, Pos2DTypes],
                        tuple[Pos2DTypes, Size2DTypes]]
"""
The supported bounding types. Either a Bounding2D, a 4-tuple of either integers 
or floats defining x,y,x2,y2 or a 2-tuple of the supported position and size 
types.

In case of 2-tuples as the type definition is ambiguous here due to Pos2DTypes
and Size2DTypes: If values are passed in the form ((a,b),(c,d)) c and
will be interpreted as width and height. Only if the second tuple element 
is a real object of type :class:`Pos2D` it will be interpreted as such.
"""


[docs]@dataclass class Bounding2D: """ Defines the 2D-bounding of a visual element by it's upper left and lower right corner. """ pos: Pos2D """ The position of the upper left coordinate. x and y should always be equal or smaller than x and y of lr """ lr: Pos2D """ The position of the lower right coordinate. x and y should always be equal or greater than x and y of pos. """ def __init__(self, value: Bounding2DTypes | None = None, pos: Pos2DTypes | None = None, lr: Pos2DTypes | None = None, size: Size2DTypes | None = None): """ :param value: The bounding value (or a bounding to copy) as defined by :class:`Bounding2DTypes`. :param pos: The explicit upper left coordinate :param lr: The explicit lower right coordinate :param size: The size, relative to the upper left coordinate or lower right coordinate. """ if value is not None and isinstance(value, Bounding2D): self.pos = Pos2D(value.pos) self.lr = Pos2D(value.lr) return if pos is not None: # ul + lr or size self.pos = Pos2D(pos) if size is not None: size = size if isinstance(size, Size2D) else Size2D(size) self.lr = Pos2D(x=self.pos.x + size.width, y=self.pos.y + size.height) return if lr is not None: self.lr = Pos2D(lr) return raise ValueError("Neither lr nor size defined but required in" " combination with ul") if lr is not None: # lr + size if size is None: raise ValueError("Neither ul nor size defined but required in " "combination with lr") self.pos = Pos2D(x=lr.x - size.width, y=lr.y - size.height) self.lr = Pos2D(lr) return if not isinstance(value, tuple): raise TypeError("Unsupported data type") if len(value) == 2: # Pos, Pos or Pos, Size tuple self.pos = Pos2D(value[0]) if isinstance(value[1], Pos2D): # Pos Pos self.lr = value[1] else: # Pos Size size = Size2D(value=value[1]) self.lr = Pos2D(x=self.pos.x + size.width, y=self.pos.y + size.height) else: # x,y,x2,y2 if len(value) != 4: raise ValueError("Invalid value size, either provide " "((x,y),(width,height)) or (x,y,x2,y2)") self.pos = Pos2D(x=value[0], y=value[1]) self.lr = Pos2D(x=value[2], y=value[3]) def __str__(self): return f"Bounding2D({self.pos.x},{self.pos.y},{self.lr.x},{self.lr.y})" def __repr__(self): return self.__str__()
[docs] def copy(self) -> Bounding2D: """ Creates a copy of the bounding :return: The copy """ return Bounding2D(self)
[docs] def get_size(self) -> Size2D: """ Returns the element's size as Size2D object :return: The size in pixels """ return Size2D(width=self.lr.x - self.pos.x, height=self.lr.y - self.pos.y)
[docs] def is_empty(self) -> bool: """ Returns if the bounding is zero :return: True if width and height are zero """ return self.lr.x == self.pos.x and self.lr.y == self.pos.y
[docs] def get_size_tuple(self) -> tuple[float, float]: """ Returns the element's size as float tuple :return: The size in pixels """ return (self.lr.x - self.pos.x, self.lr.y - self.pos.y)
[docs] def get_int_size_tuple(self) -> tuple[float, float]: """ Returns the element's size as int tuple :return: The size in pixels """ return (int(round(self.lr.x - self.pos.x)), int(round(self.lr.y - self.pos.y)))
[docs] def width(self) -> float: """ Returns the element's width :return: The width in pixels """ return self.lr.x - self.pos.x
[docs] def height(self) -> float: """ Returns the element's height :return: The height in pixels """ return self.lr.y - self.pos.y
[docs] def to_coord_tuple(self) -> (float, float, float, float): """ Returns the bounding as 1D coordinate tuple :return: A tuple in the format x, y, x2, y2 """ return (self.pos.x, self.pos.y, self.lr.x, self.lr.y)
[docs] def to_int_coord_tuple(self) -> (int, int, int, int): """ Returns the bounding as 1D coordinate tuple :return: A tuple in the format x, y, x2, y2 """ return (int(round(self.pos.x)), int(round(self.pos.y)), int(round(self.lr.x)), int(round(self.lr.y)))
[docs] def to_nested_coord_tuple(self) -> ((float, float), (float, float)): """ Returns the bounding as 1D coordinate tuple :return: A tuple in the format (x, y), (x2, y2) """ return (self.pos.x, self.pos.y), \ (self.lr.x, self.lr.y)
[docs] def to_coord_size_tuple(self) -> (float, float, float, float): """ Returns the bounding as 1D coordinate, width, height tuple :return: A tuple in the format x, y, width, height """ return (self.pos.x, self.pos.y, self.lr.x - self.pos.x, self.lr.y - self.pos.y)
[docs] def to_int_coord_size_tuple(self) -> (int, int, int, int): """ Returns the bounding as 1D coordinate, width, height tuple :return: A tuple in the format x, y, width, height """ return (int(round(self.pos.x)), int(round(self.pos.y)), int(round(self.lr.x - self.pos.x)), int(round(self.lr.y - self.pos.y)))
[docs] def to_nested_coord_size_tuple(self) -> ((float, float), (float, float)): """ Returns the bounding as 1D coordinate, width, height tuple :return: A tuple in the format (x, y), (width, height) """ return (self.pos.x, self.pos.y), ( self.lr.x - self.pos.x, self.lr.y - self.pos.y)
def __eq__(self, other: Bounding2DTypes) -> bool: if not isinstance(other, self.__class__): other = Bounding2D(other) return (self.pos == other.pos and self.lr == other.lr) def __ne__(self, other: Bounding2DTypes) -> bool: if not isinstance(other, self.__class__): other = Bounding2D(other) return (self.pos != other.pos or self.lr != other.lr)
RawBoundingType = tuple[tuple[float, float], tuple[float, float]] """ Defines the raw bounding type with the structure ((x,y),(x2,y2)). """ __all__ = ["Bounding2D", "Bounding2DTypes", "RawBoundingType"]