Daniel Gil-U Fuhge
add model files
e17e8cc
raw
history blame
11.8 kB
"""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
@staticmethod
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())
@property
def x(self):
return self.pos[0]
@property
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)
@staticmethod
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()
@property
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
@property
def size(self):
return self.wh
@property
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
@staticmethod
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
@property
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])
@staticmethod
def from_tensor(vector: torch.Tensor):
return Angle(vector.item())
@staticmethod
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)
@staticmethod
def from_tensor(vector: torch.Tensor):
return Flag(vector.item())