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