Spaces:
Running
Running
| """This code is taken from <https://github.com/alexandre01/deepsvg> | |
| by Alexandre Carlier, Martin Danelljan, Alexandre Alahi and Radu Timofte | |
| from the paper >https://arxiv.org/pdf/2007.11301.pdf> | |
| """ | |
| from __future__ import annotations | |
| import numpy as np | |
| from enum import Enum | |
| import torch | |
| from typing import List, Union | |
| Num = Union[int, float] | |
| float_type = (int, float, np.float32) | |
| def det(a: Point, b: Point): | |
| return a.pos[0] * b.pos[1] - a.pos[1] * b.pos[0] | |
| def get_rotation_matrix(angle: Union[Angle, float]): | |
| if isinstance(angle, Angle): | |
| theta = angle.rad | |
| else: | |
| theta = angle | |
| c, s = np.cos(theta), np.sin(theta) | |
| rot_m = np.array([[c, -s], | |
| [s, c]], dtype=np.float32) | |
| return rot_m | |
| def union_bbox(bbox_list: List[Bbox]): | |
| res = None | |
| for bbox in bbox_list: | |
| res = bbox.union(res) | |
| return res | |
| class Geom: | |
| def copy(self): | |
| raise NotImplementedError | |
| def to_str(self): | |
| raise NotImplementedError | |
| def to_tensor(self): | |
| raise NotImplementedError | |
| def from_tensor(vector: torch.Tensor): | |
| raise NotImplementedError | |
| def scale(self, factor): | |
| pass | |
| def translate(self, vec): | |
| pass | |
| def rotate(self, angle: Union[Angle, float]): | |
| pass | |
| def numericalize(self, n=256): | |
| raise NotImplementedError | |
| ######### Point | |
| class Point(Geom): | |
| num_args = 2 | |
| def __init__(self, x=None, y=None): | |
| if isinstance(x, np.ndarray): | |
| self.pos = x.astype(np.float32) | |
| elif x is None and y is None: | |
| self.pos = np.array([0., 0.], dtype=np.float32) | |
| elif (isinstance(x, float_type) or x is None) and (isinstance(y, float_type) or y is None): | |
| if x is None: | |
| x = y | |
| if y is None: | |
| y = x | |
| self.pos = np.array([x, y], dtype=np.float32) | |
| else: | |
| raise ValueError() | |
| def copy(self): | |
| return Point(self.pos.copy()) | |
| def x(self): | |
| return self.pos[0] | |
| def y(self): | |
| return self.pos[1] | |
| def xproj(self): | |
| return Point(self.x, 0.) | |
| def yproj(self): | |
| return Point(0., self.y) | |
| def __add__(self, other): | |
| return Point(self.pos + other.pos) | |
| def __sub__(self, other): | |
| return self + other.__neg__() | |
| def __mul__(self, lmbda): | |
| if isinstance(lmbda, Point): | |
| return Point(self.pos * lmbda.pos) | |
| assert isinstance(lmbda, float_type) | |
| return Point(lmbda * self.pos) | |
| def __rmul__(self, lmbda): | |
| return self * lmbda | |
| def __truediv__(self, lmbda): | |
| if isinstance(lmbda, Point): | |
| return Point(self.pos / lmbda.pos) | |
| assert isinstance(lmbda, float_type) | |
| return self * (1 / lmbda) | |
| def __neg__(self): | |
| return self * -1 | |
| def __repr__(self): | |
| return f"P({self.x}, {self.y})" | |
| def to_str(self): | |
| return f"{self.x} {self.y}" | |
| def tolist(self): | |
| return self.pos.tolist() | |
| def to_tensor(self): | |
| return torch.tensor(self.pos) | |
| def from_tensor(vector: torch.Tensor): | |
| return Point(*vector.tolist()) | |
| def translate(self, vec: Point): | |
| self.pos += vec.pos | |
| def matmul(self, m): | |
| return Point(m @ self.pos) | |
| def rotate(self, angle: Union[Angle, float]): | |
| rot_m = get_rotation_matrix(angle) | |
| return self.matmul(rot_m) | |
| def rotate_(self, angle: Union[Angle, float]): | |
| rot_m = get_rotation_matrix(angle) | |
| self.pos = rot_m @ self.pos | |
| def scale(self, factor): | |
| self.pos *= factor | |
| def dot(self, other: Point): | |
| return self.pos.dot(other.pos) | |
| def norm(self): | |
| return float(np.linalg.norm(self.pos)) | |
| def cross(self, other: Point): | |
| return np.cross(self.pos, other.pos) | |
| def dist(self, other: Point): | |
| return (self - other).norm() | |
| def angle(self, other: Point, signed=False): | |
| rad = np.arccos(np.clip(self.normalize().dot(other.normalize()), -1., 1.)) | |
| if signed: | |
| sign = 1 if det(self, other) >= 0 else -1 | |
| rad *= sign | |
| return Angle.Rad(rad) | |
| def distToLine(self, p1: Point, p2: Point): | |
| if p1.isclose(p2): | |
| return self.dist(p1) | |
| return abs((p2 - p1).cross(p1 - self)) / (p2 - p1).norm() | |
| def normalize(self): | |
| return self / self.norm() | |
| def numericalize(self, n=256): | |
| self.pos = self.pos.round().clip(min=0, max=n-1) | |
| def isclose(self, other: Point): | |
| return np.allclose(self.pos, other.pos) | |
| def iszero(self): | |
| return np.all(self.pos == 0) | |
| def pointwise_min(self, other: Point): | |
| return Point(min(self.x, other.x), min(self.y, other.y)) | |
| def pointwise_max(self, other: Point): | |
| return Point(max(self.x, other.x), max(self.y, other.y)) | |
| class Radius(Point): | |
| def __init__(self, *args, **kwargs): | |
| super().__init__(*args, **kwargs) | |
| def copy(self): | |
| return Radius(self.pos.copy()) | |
| def __repr__(self): | |
| return f"Rad({self.pos[0]}, {self.pos[1]})" | |
| def translate(self, vec: Point): | |
| pass | |
| class Size(Point): | |
| def __init__(self, *args, **kwargs): | |
| super().__init__(*args, **kwargs) | |
| def copy(self): | |
| return Size(self.pos.copy()) | |
| def __repr__(self): | |
| return f"Size({self.pos[0]}, {self.pos[1]})" | |
| def max(self): | |
| return self.pos.max() | |
| def min(self): | |
| return self.pos.min() | |
| def translate(self, vec: Point): | |
| pass | |
| ######### Coord | |
| class Coord(Geom): | |
| num_args = 1 | |
| class XY(Enum): | |
| X = "x" | |
| Y = "y" | |
| def __init__(self, coord, xy: XY = XY.X): | |
| self.coord = coord | |
| self.xy = xy | |
| def __repr__(self): | |
| return f"{self.xy.value}({self.coord})" | |
| def to_str(self): | |
| return str(self.coord) | |
| def to_tensor(self): | |
| return torch.tensor([self.coord]) | |
| def __add__(self, other): | |
| if isinstance(other, float_type): | |
| return Coord(self.coord + other, self.xy) | |
| elif isinstance(other, Coord): | |
| if self.xy != other.xy: | |
| raise ValueError() | |
| return Coord(self.coord + other.coord, self.xy) | |
| elif isinstance(other, Point): | |
| return Coord(self.coord + getattr(other, self.xy.value), self.xy) | |
| else: | |
| raise ValueError() | |
| def __sub__(self, other): | |
| return self + other.__neg__() | |
| def __mul__(self, lmbda): | |
| assert isinstance(lmbda, float_type) | |
| return Coord(lmbda * self.coord) | |
| def __neg__(self): | |
| return self * -1 | |
| def scale(self, factor): | |
| self.coord *= factor | |
| def translate(self, vec: Point): | |
| self.coord += getattr(vec, self.xy.value) | |
| def to_point(self, pos: Point, is_absolute=True): | |
| point = pos.copy() if is_absolute else Point(0.) | |
| point.pos[int(self.xy == Coord.XY.Y)] = self.coord | |
| return point | |
| class XCoord(Coord): | |
| def __init__(self, coord): | |
| super().__init__(coord, xy=Coord.XY.X) | |
| def copy(self): | |
| return XCoord(self.coord) | |
| class YCoord(Coord): | |
| def __init__(self, coord): | |
| super().__init__(coord, xy=Coord.XY.Y) | |
| def copy(self): | |
| return YCoord(self.coord) | |
| ######### Bbox | |
| class Bbox(Geom): | |
| num_args = 4 | |
| def __init__(self, x=None, y=None, w=None, h=None): | |
| if isinstance(x, Point) and isinstance(y, Point): | |
| self.xy = x | |
| wh = y - x | |
| self.wh = Size(wh.x, wh.y) | |
| elif (isinstance(x, float_type) or x is None) and (isinstance(y, float_type) or y is None): | |
| if x is None: | |
| x = 0. | |
| if y is None: | |
| y = float(x) | |
| if w is None and h is None: | |
| w, h = float(x), float(y) | |
| x, y = 0., 0. | |
| self.xy = Point(x, y) | |
| self.wh = Size(w, h) | |
| else: | |
| raise ValueError() | |
| def xy2(self): | |
| return self.xy + self.wh | |
| def copy(self): | |
| bbox = Bbox() | |
| bbox.xy = self.xy.copy() | |
| bbox.wh = self.wh.copy() | |
| return bbox | |
| def size(self): | |
| return self.wh | |
| def center(self): | |
| return self.xy + self.wh / 2 | |
| def __repr__(self): | |
| return f"Bbox({self.xy.to_str()} {self.wh.to_str()})" | |
| def to_str(self): | |
| return f"{self.xy.to_str()} {self.wh.to_str()}" | |
| def to_tensor(self): | |
| return torch.tensor([*self.xy.to_tensor(), *self.wh.to_tensor()]) | |
| def make_square(self, min_size=None): | |
| center = self.center | |
| size = self.wh.max() | |
| if min_size is not None: | |
| size = max(size, min_size) | |
| self.wh = Size(size, size) | |
| self.xy = center - self.wh / 2 | |
| return self | |
| def translate(self, vec): | |
| self.xy.translate(vec) | |
| def scale(self, factor): | |
| self.xy.scale(factor) | |
| self.wh.scale(factor) | |
| def union(self, other: Bbox): | |
| if other is None: | |
| return self | |
| return Bbox(self.xy.pointwise_min(other.xy), self.xy2.pointwise_max(other.xy2)) | |
| def intersect(self, other: Bbox): | |
| if other is None: | |
| return self | |
| bbox = Bbox(self.xy.pointwise_max(other.xy), self.xy2.pointwise_min(other.xy2)) | |
| if bbox.wh.x < 0 or bbox.wh.y < 0: | |
| return None | |
| return bbox | |
| def from_points(points: List[Point]): | |
| if not points: | |
| return None | |
| xy = xy2 = points[0] | |
| for p in points[1:]: | |
| xy = xy.pointwise_min(p) | |
| xy2 = xy2.pointwise_max(p) | |
| return Bbox(xy, xy2) | |
| def to_rectangle(self, *args, **kwargs): | |
| from .svg_primitive import SVGRectangle | |
| return SVGRectangle(self.xy, self.wh, *args, **kwargs) | |
| def area(self): | |
| return self.wh.pos.prod() | |
| def overlap(self, other): | |
| inter = self.intersect(other) | |
| if inter is None: | |
| return 0. | |
| return inter.area() / self.area() | |
| ######### Angle | |
| class Angle(Geom): | |
| num_args = 1 | |
| def __init__(self, deg): | |
| self.deg = deg | |
| def rad(self): | |
| return np.deg2rad(self.deg) | |
| def copy(self): | |
| return Angle(self.deg) | |
| def __repr__(self): | |
| return f"α({self.deg})" | |
| def to_str(self): | |
| return str(self.deg) | |
| def to_tensor(self): | |
| return torch.tensor([self.deg]) | |
| def from_tensor(vector: torch.Tensor): | |
| return Angle(vector.item()) | |
| def Rad(rad): | |
| return Angle(np.rad2deg(rad)) | |
| def __add__(self, other: Angle): | |
| return Angle(self.deg + other.deg) | |
| def __sub__(self, other: Angle): | |
| return self + other.__neg__() | |
| def __mul__(self, lmbda): | |
| assert isinstance(lmbda, float_type) | |
| return Angle(lmbda * self.deg) | |
| def __rmul__(self, lmbda): | |
| assert isinstance(lmbda, float_type) | |
| return self * lmbda | |
| def __truediv__(self, lmbda): | |
| assert isinstance(lmbda, float_type) | |
| return self * (1 / lmbda) | |
| def __neg__(self): | |
| return self * -1 | |
| ######### Flag | |
| class Flag(Geom): | |
| num_args = 1 | |
| def __init__(self, flag): | |
| self.flag = int(flag) | |
| def copy(self): | |
| return Flag(self.flag) | |
| def __repr__(self): | |
| return f"flag({self.flag})" | |
| def to_str(self): | |
| return str(self.flag) | |
| def to_tensor(self): | |
| return torch.tensor([self.flag]) | |
| def __invert__(self): | |
| return Flag(1 - self.flag) | |
| def from_tensor(vector: torch.Tensor): | |
| return Flag(vector.item()) | |