This commit is contained in:
Yiyi Liao
2019-06-13 16:25:11 +02:00
parent 26157cbb80
commit f5e5c4bd3f
84 changed files with 31343 additions and 2 deletions
+23
View File
@@ -0,0 +1,23 @@
# import os
# this_dir = os.path.dirname(__file__)
# print(this_dir)
# import sys
# sys.path.append(this_dir)
# set matplotlib backend depending on env
import os
import matplotlib
if os.name == 'posix' and "DISPLAY" not in os.environ:
matplotlib.use('Agg')
from . import geometry
from . import plt
from . import plt2d
from . import plt3d
from . import metric
from . import table
from . import utils
from . import io3d
from . import gtimer
from . import cmap
from . import args
+71
View File
@@ -0,0 +1,71 @@
import argparse
import os
from .utils import str2bool
def parse_args():
parser = argparse.ArgumentParser()
#
parser.add_argument('--output_dir',
help='Output directory',
default='./output', type=str)
parser.add_argument('--loss',
help='Train with \'ph\' for the first stage without geometric loss, \
train with \'phge\' for the second stage with geometric loss',
default='ph', choices=['ph','phge'], type=str)
parser.add_argument('--data_type',
default='syn', choices=['syn'], type=str)
#
parser.add_argument('--cmd',
help='Start training or test',
default='resume', choices=['retrain', 'resume', 'retest', 'test_init'], type=str)
parser.add_argument('--epoch',
help='If larger than -1, retest on the specified epoch',
default=-1, type=int)
parser.add_argument('--epochs',
help='Training epochs',
default=100, type=int)
#
parser.add_argument('--ms',
help='If true, use multiscale loss',
default=True, type=str2bool)
parser.add_argument('--pattern_path',
help='Path of the pattern image',
default='./data/kinect_patttern.png', type=str)
#
parser.add_argument('--dp_weight',
help='Weight of the disparity loss',
default=0.02, type=float)
parser.add_argument('--ge_weight',
help='Weight of the geometric loss',
default=0.1, type=float)
#
parser.add_argument('--lcn_radius',
help='Radius of the window for LCN pre-processing',
default=5, type=int)
parser.add_argument('--max_disp',
help='Maximum disparity',
default=128, type=int)
#
parser.add_argument('--track_length',
help='Track length for geometric loss',
default=2, type=int)
#
parser.add_argument('--blend_im',
help='Parameter for adding texture',
default=0.6, type=float)
args = parser.parse_args()
args.exp_name = get_exp_name(args)
return args
def get_exp_name(args):
name = f"exp_{args.data_type}"
return name
+93
View File
@@ -0,0 +1,93 @@
import numpy as np
_color_map_errors = np.array([
[149, 54, 49], #0: log2(x) = -infinity
[180, 117, 69], #0.0625: log2(x) = -4
[209, 173, 116], #0.125: log2(x) = -3
[233, 217, 171], #0.25: log2(x) = -2
[248, 243, 224], #0.5: log2(x) = -1
[144, 224, 254], #1.0: log2(x) = 0
[97, 174, 253], #2.0: log2(x) = 1
[67, 109, 244], #4.0: log2(x) = 2
[39, 48, 215], #8.0: log2(x) = 3
[38, 0, 165], #16.0: log2(x) = 4
[38, 0, 165] #inf: log2(x) = inf
]).astype(float)
def color_error_image(errors, scale=1, mask=None, BGR=True):
"""
Color an input error map.
Arguments:
errors -- HxW numpy array of errors
[scale=1] -- scaling the error map (color change at unit error)
[mask=None] -- zero-pixels are masked white in the result
[BGR=True] -- toggle between BGR and RGB
Returns:
colored_errors -- HxWx3 numpy array visualizing the errors
"""
errors_flat = errors.flatten()
errors_color_indices = np.clip(np.log2(errors_flat / scale + 1e-5) + 5, 0, 9)
i0 = np.floor(errors_color_indices).astype(int)
f1 = errors_color_indices - i0.astype(float)
colored_errors_flat = _color_map_errors[i0, :] * (1-f1).reshape(-1,1) + _color_map_errors[i0+1, :] * f1.reshape(-1,1)
if mask is not None:
colored_errors_flat[mask.flatten() == 0] = 255
if not BGR:
colored_errors_flat = colored_errors_flat[:,[2,1,0]]
return colored_errors_flat.reshape(errors.shape[0], errors.shape[1], 3).astype(np.int)
_color_map_depths = np.array([
[0, 0, 0], # 0.000
[0, 0, 255], # 0.114
[255, 0, 0], # 0.299
[255, 0, 255], # 0.413
[0, 255, 0], # 0.587
[0, 255, 255], # 0.701
[255, 255, 0], # 0.886
[255, 255, 255], # 1.000
[255, 255, 255], # 1.000
]).astype(float)
_color_map_bincenters = np.array([
0.0,
0.114,
0.299,
0.413,
0.587,
0.701,
0.886,
1.000,
2.000, # doesn't make a difference, just strictly higher than 1
])
def color_depth_map(depths, scale=None):
"""
Color an input depth map.
Arguments:
depths -- HxW numpy array of depths
[scale=None] -- scaling the values (defaults to the maximum depth)
Returns:
colored_depths -- HxWx3 numpy array visualizing the depths
"""
if scale is None:
scale = depths.max()
values = np.clip(depths.flatten() / scale, 0, 1)
# for each value, figure out where they fit in in the bincenters: what is the last bincenter smaller than this value?
lower_bin = ((values.reshape(-1, 1) >= _color_map_bincenters.reshape(1,-1)) * np.arange(0,9)).max(axis=1)
lower_bin_value = _color_map_bincenters[lower_bin]
higher_bin_value = _color_map_bincenters[lower_bin + 1]
alphas = (values - lower_bin_value) / (higher_bin_value - lower_bin_value)
colors = _color_map_depths[lower_bin] * (1-alphas).reshape(-1,1) + _color_map_depths[lower_bin + 1] * alphas.reshape(-1,1)
return colors.reshape(depths.shape[0], depths.shape[1], 3).astype(np.uint8)
#from utils.debug import save_color_numpy
#save_color_numpy(color_depth_map(np.matmul(np.ones((100,1)), np.arange(0,1200).reshape(1,1200)), scale=1000))
+800
View File
@@ -0,0 +1,800 @@
import numpy as np
import scipy.spatial
import scipy.linalg
def nullspace(A, atol=1e-13, rtol=0):
u, s, vh = np.linalg.svd(A)
tol = max(atol, rtol * s[0])
nnz = (s >= tol).sum()
ns = vh[nnz:].conj().T
return ns
def nearest_orthogonal_matrix(R):
U,S,Vt = np.linalg.svd(R)
return U @ np.eye(3,dtype=R.dtype) @ Vt
def power_iters(A, n_iters=10):
b = np.random.uniform(-1,1, size=(A.shape[0], A.shape[1], 1))
for iter in range(n_iters):
b = A @ b
b = b / np.linalg.norm(b, axis=1, keepdims=True)
return b
def rayleigh_quotient(A, b):
return (b.transpose(0,2,1) @ A @ b) / (b.transpose(0,2,1) @ b)
def cross_prod_mat(x):
x = x.reshape(-1,3)
X = np.empty((x.shape[0],3,3), dtype=x.dtype)
X[:,0,0] = 0
X[:,0,1] = -x[:,2]
X[:,0,2] = x[:,1]
X[:,1,0] = x[:,2]
X[:,1,1] = 0
X[:,1,2] = -x[:,0]
X[:,2,0] = -x[:,1]
X[:,2,1] = x[:,0]
X[:,2,2] = 0
return X.squeeze()
def hat_operator(x):
return cross_prod_mat(x)
def vee_operator(X):
X = X.reshape(-1,3,3)
x = np.empty((X.shape[0], 3), dtype=X.dtype)
x[:,0] = X[:,2,1]
x[:,1] = X[:,0,2]
x[:,2] = X[:,1,0]
return x.squeeze()
def rot_x(x, dtype=np.float32):
x = np.array(x, copy=False)
x = x.reshape(-1,1)
R = np.zeros((x.shape[0],3,3), dtype=dtype)
R[:,0,0] = 1
R[:,1,1] = np.cos(x).ravel()
R[:,1,2] = -np.sin(x).ravel()
R[:,2,1] = np.sin(x).ravel()
R[:,2,2] = np.cos(x).ravel()
return R.squeeze()
def rot_y(y, dtype=np.float32):
y = np.array(y, copy=False)
y = y.reshape(-1,1)
R = np.zeros((y.shape[0],3,3), dtype=dtype)
R[:,0,0] = np.cos(y).ravel()
R[:,0,2] = np.sin(y).ravel()
R[:,1,1] = 1
R[:,2,0] = -np.sin(y).ravel()
R[:,2,2] = np.cos(y).ravel()
return R.squeeze()
def rot_z(z, dtype=np.float32):
z = np.array(z, copy=False)
z = z.reshape(-1,1)
R = np.zeros((z.shape[0],3,3), dtype=dtype)
R[:,0,0] = np.cos(z).ravel()
R[:,0,1] = -np.sin(z).ravel()
R[:,1,0] = np.sin(z).ravel()
R[:,1,1] = np.cos(z).ravel()
R[:,2,2] = 1
return R.squeeze()
def xyz_from_rotm(R):
R = R.reshape(-1,3,3)
xyz = np.empty((R.shape[0],3), dtype=R.dtype)
for bidx in range(R.shape[0]):
if R[bidx,0,2] < 1:
if R[bidx,0,2] > -1:
xyz[bidx,1] = np.arcsin(R[bidx,0,2])
xyz[bidx,0] = np.arctan2(-R[bidx,1,2], R[bidx,2,2])
xyz[bidx,2] = np.arctan2(-R[bidx,0,1], R[bidx,0,0])
else:
xyz[bidx,1] = -np.pi/2
xyz[bidx,0] = -np.arctan2(R[bidx,1,0],R[bidx,1,1])
xyz[bidx,2] = 0
else:
xyz[bidx,1] = np.pi/2
xyz[bidx,0] = np.arctan2(R[bidx,1,0], R[bidx,1,1])
xyz[bidx,2] = 0
return xyz.squeeze()
def zyx_from_rotm(R):
R = R.reshape(-1,3,3)
zyx = np.empty((R.shape[0],3), dtype=R.dtype)
for bidx in range(R.shape[0]):
if R[bidx,2,0] < 1:
if R[bidx,2,0] > -1:
zyx[bidx,1] = np.arcsin(-R[bidx,2,0])
zyx[bidx,0] = np.arctan2(R[bidx,1,0], R[bidx,0,0])
zyx[bidx,2] = np.arctan2(R[bidx,2,1], R[bidx,2,2])
else:
zyx[bidx,1] = np.pi / 2
zyx[bidx,0] = -np.arctan2(-R[bidx,1,2], R[bidx,1,1])
zyx[bidx,2] = 0
else:
zyx[bidx,1] = -np.pi / 2
zyx[bidx,0] = np.arctan2(-R[bidx,1,2], R[bidx,1,1])
zyx[bidx,2] = 0
return zyx.squeeze()
def rotm_from_xyz(xyz):
xyz = np.array(xyz, copy=False).reshape(-1,3)
return (rot_x(xyz[:,0]) @ rot_y(xyz[:,1]) @ rot_z(xyz[:,2])).squeeze()
def rotm_from_zyx(zyx):
zyx = np.array(zyx, copy=False).reshape(-1,3)
return (rot_z(zyx[:,0]) @ rot_y(zyx[:,1]) @ rot_x(zyx[:,2])).squeeze()
def rotm_from_quat(q):
q = q.reshape(-1,4)
w, x, y, z = q[:,0], q[:,1], q[:,2], q[:,3]
R = np.array([
[1 - 2*y*y - 2*z*z, 2*x*y - 2*z*w, 2*x*z + 2*y*w],
[2*x*y + 2*z*w, 1 - 2*x*x - 2*z*z, 2*y*z - 2*x*w],
[2*x*z - 2*y*w, 2*y*z + 2*x*w, 1 - 2*x*x - 2*y*y]
], dtype=q.dtype)
R = R.transpose((2,0,1))
return R.squeeze()
def rotm_from_axisangle(a):
# exponential
a = a.reshape(-1,3)
phi = np.linalg.norm(a, axis=1).reshape(-1,1,1)
iphi = np.zeros_like(phi)
np.divide(1, phi, out=iphi, where=phi != 0)
A = cross_prod_mat(a) * iphi
R = np.eye(3, dtype=a.dtype) + np.sin(phi) * A + (1 - np.cos(phi)) * A @ A
return R.squeeze()
def rotm_from_lookat(dir, up=None):
dir = dir.reshape(-1,3)
if up is None:
up = np.zeros_like(dir)
up[:,1] = 1
dir /= np.linalg.norm(dir, axis=1, keepdims=True)
up /= np.linalg.norm(up, axis=1, keepdims=True)
x = dir[:,None,:] @ cross_prod_mat(up).transpose(0,2,1)
y = x @ cross_prod_mat(dir).transpose(0,2,1)
x = x.squeeze()
y = y.squeeze()
x /= np.linalg.norm(x, axis=1, keepdims=True)
y /= np.linalg.norm(y, axis=1, keepdims=True)
R = np.empty((dir.shape[0],3,3), dtype=dir.dtype)
R[:,0,0] = x[:,0]
R[:,0,1] = y[:,0]
R[:,0,2] = dir[:,0]
R[:,1,0] = x[:,1]
R[:,1,1] = y[:,1]
R[:,1,2] = dir[:,1]
R[:,2,0] = x[:,2]
R[:,2,1] = y[:,2]
R[:,2,2] = dir[:,2]
return R.transpose(0,2,1).squeeze()
def rotm_distance_identity(R0, R1):
# https://link.springer.com/article/10.1007%2Fs10851-009-0161-2
# in [0, 2*sqrt(2)]
R0 = R0.reshape(-1,3,3)
R1 = R1.reshape(-1,3,3)
dists = np.linalg.norm(np.eye(3,dtype=R0.dtype) - R0 @ R1.transpose(0,2,1), axis=(1,2))
return dists.squeeze()
def rotm_distance_geodesic(R0, R1):
# https://link.springer.com/article/10.1007%2Fs10851-009-0161-2
# in [0, pi)
R0 = R0.reshape(-1,3,3)
R1 = R1.reshape(-1,3,3)
RtR = R0 @ R1.transpose(0,2,1)
aa = axisangle_from_rotm(RtR)
S = cross_prod_mat(aa).reshape(-1,3,3)
dists = np.linalg.norm(S, axis=(1,2))
return dists.squeeze()
def axisangle_from_rotm(R):
# logarithm of rotation matrix
# R = R.reshape(-1,3,3)
# tr = np.trace(R, axis1=1, axis2=2)
# phi = np.arccos(np.clip((tr - 1) / 2, -1, 1))
# scale = np.zeros_like(phi)
# div = 2 * np.sin(phi)
# np.divide(phi, div, out=scale, where=np.abs(div) > 1e-6)
# A = (R - R.transpose(0,2,1)) * scale.reshape(-1,1,1)
# aa = np.stack((A[:,2,1], A[:,0,2], A[:,1,0]), axis=1)
# return aa.squeeze()
R = R.reshape(-1,3,3)
omega = np.empty((R.shape[0], 3), dtype=R.dtype)
omega[:,0] = R[:,2,1] - R[:,1,2]
omega[:,1] = R[:,0,2] - R[:,2,0]
omega[:,2] = R[:,1,0] - R[:,0,1]
r = np.linalg.norm(omega, axis=1).reshape(-1,1)
t = np.trace(R, axis1=1, axis2=2).reshape(-1,1)
omega = np.arctan2(r, t-1) * omega
aa = np.zeros_like(omega)
np.divide(omega, r, out=aa, where=r != 0)
return aa.squeeze()
def axisangle_from_quat(q):
q = q.reshape(-1,4)
phi = 2 * np.arccos(q[:,0])
denom = np.zeros_like(q[:,0])
np.divide(1, np.sqrt(1 - q[:,0]**2), out=denom, where=q[:,0] != 1)
axis = q[:,1:] * denom.reshape(-1,1)
denom = np.linalg.norm(axis, axis=1).reshape(-1,1)
a = np.zeros_like(axis)
np.divide(phi.reshape(-1,1) * axis, denom, out=a, where=denom != 0)
aa = a.astype(q.dtype)
return aa.squeeze()
def axisangle_apply(aa, x):
# working only with single aa and single x at the moment
xshape = x.shape
aa = aa.reshape(3,)
x = x.reshape(3,)
phi = np.linalg.norm(aa)
e = np.zeros_like(aa)
np.divide(aa, phi, out=e, where=phi != 0)
xr = np.cos(phi) * x + np.sin(phi) * np.cross(e, x) + (1 - np.cos(phi)) * (e.T @ x) * e
return xr.reshape(xshape)
def exp_so3(R):
w = axisangle_from_rotm(R)
return w
def log_so3(w):
R = rotm_from_axisangle(w)
return R
def exp_se3(R, t):
R = R.reshape(-1,3,3)
t = t.reshape(-1,3)
w = exp_so3(R).reshape(-1,3)
phi = np.linalg.norm(w, axis=1).reshape(-1,1,1)
A = cross_prod_mat(w)
Vi = np.eye(3, dtype=R.dtype) - A/2 + (1 - (phi * np.sin(phi) / (2 * (1 - np.cos(phi))))) / phi**2 * A @ A
u = t.reshape(-1,1,3) @ Vi.transpose(0,2,1)
# v = (u, w)
v = np.empty((R.shape[0],6), dtype=R.dtype)
v[:,:3] = u.squeeze()
v[:,3:] = w
return v.squeeze()
def log_se3(v):
# v = (u, w)
v = v.reshape(-1,6)
u = v[:,:3]
w = v[:,3:]
R = log_so3(w)
phi = np.linalg.norm(w, axis=1).reshape(-1,1,1)
A = cross_prod_mat(w)
V = np.eye(3, dtype=v.dtype) + (1 - np.cos(phi)) / phi**2 * A + (phi - np.sin(phi)) / phi**3 * A @ A
t = u.reshape(-1,1,3) @ V.transpose(0,2,1)
return R.squeeze(), t.squeeze()
def quat_from_rotm(R):
R = R.reshape(-1,3,3)
q = np.empty((R.shape[0], 4,), dtype=R.dtype)
q[:,0] = np.sqrt( np.maximum(0, 1 + R[:,0,0] + R[:,1,1] + R[:,2,2]) )
q[:,1] = np.sqrt( np.maximum(0, 1 + R[:,0,0] - R[:,1,1] - R[:,2,2]) )
q[:,2] = np.sqrt( np.maximum(0, 1 - R[:,0,0] + R[:,1,1] - R[:,2,2]) )
q[:,3] = np.sqrt( np.maximum(0, 1 - R[:,0,0] - R[:,1,1] + R[:,2,2]) )
q[:,1] *= np.sign(q[:,1] * (R[:,2,1] - R[:,1,2]))
q[:,2] *= np.sign(q[:,2] * (R[:,0,2] - R[:,2,0]))
q[:,3] *= np.sign(q[:,3] * (R[:,1,0] - R[:,0,1]))
q /= np.linalg.norm(q,axis=1,keepdims=True)
return q.squeeze()
def quat_from_axisangle(a):
a = a.reshape(-1, 3)
phi = np.linalg.norm(a, axis=1)
iphi = np.zeros_like(phi)
np.divide(1, phi, out=iphi, where=phi != 0)
a = a * iphi.reshape(-1,1)
theta = phi / 2.0
r = np.cos(theta)
stheta = np.sin(theta)
q = np.stack((r, stheta*a[:,0], stheta*a[:,1], stheta*a[:,2]), axis=1)
q /= np.linalg.norm(q, axis=1).reshape(-1,1)
return q.squeeze()
def quat_identity(n=1, dtype=np.float32):
q = np.zeros((n,4), dtype=dtype)
q[:,0] = 1
return q.squeeze()
def quat_conjugate(q):
shape = q.shape
q = q.reshape(-1,4).copy()
q[:,1:] *= -1
return q.reshape(shape)
def quat_product(q1, q2):
# q1 . q2 is equivalent to R(q1) @ R(q2)
shape = q1.shape
q1, q2 = q1.reshape(-1,4), q2.reshape(-1, 4)
q = np.empty((max(q1.shape[0], q2.shape[0]), 4), dtype=q1.dtype)
a1,b1,c1,d1 = q1[:,0], q1[:,1], q1[:,2], q1[:,3]
a2,b2,c2,d2 = q2[:,0], q2[:,1], q2[:,2], q2[:,3]
q[:,0] = a1 * a2 - b1 * b2 - c1 * c2 - d1 * d2
q[:,1] = a1 * b2 + b1 * a2 + c1 * d2 - d1 * c2
q[:,2] = a1 * c2 - b1 * d2 + c1 * a2 + d1 * b2
q[:,3] = a1 * d2 + b1 * c2 - c1 * b2 + d1 * a2
return q.squeeze()
def quat_apply(q, x):
xshape = x.shape
x = x.reshape(-1, 3)
qshape = q.shape
q = q.reshape(-1, 4)
p = np.empty((x.shape[0], 4), dtype=x.dtype)
p[:,0] = 0
p[:,1:] = x
r = quat_product(quat_product(q, p), quat_conjugate(q))
if r.ndim == 1:
return r[1:].reshape(xshape)
else:
return r[:,1:].reshape(xshape)
def quat_random(rng=None, n=1):
# http://planning.cs.uiuc.edu/node198.html
if rng is not None:
u = rng.uniform(0, 1, size=(3,n))
else:
u = np.random.uniform(0, 1, size=(3,n))
q = np.array((
np.sqrt(1 - u[0]) * np.sin(2 * np.pi * u[1]),
np.sqrt(1 - u[0]) * np.cos(2 * np.pi * u[1]),
np.sqrt(u[0]) * np.sin(2 * np.pi * u[2]),
np.sqrt(u[0]) * np.cos(2 * np.pi * u[2])
)).T
q /= np.linalg.norm(q,axis=1,keepdims=True)
return q.squeeze()
def quat_distance_angle(q0, q1):
# https://math.stackexchange.com/questions/90081/quaternion-distance
# https://link.springer.com/article/10.1007%2Fs10851-009-0161-2
q0 = q0.reshape(-1,4)
q1 = q1.reshape(-1,4)
dists = np.arccos(np.clip(2 * np.sum(q0 * q1, axis=1)**2 - 1, -1, 1))
return dists
def quat_distance_normdiff(q0, q1):
# https://link.springer.com/article/10.1007%2Fs10851-009-0161-2
# \phi_4
# [0, 1]
q0 = q0.reshape(-1,4)
q1 = q1.reshape(-1,4)
return 1 - np.sum(q0 * q1, axis=1)**2
def quat_distance_mineucl(q0, q1):
# https://link.springer.com/article/10.1007%2Fs10851-009-0161-2
# http://users.cecs.anu.edu.au/~trumpf/pubs/Hartley_Trumpf_Dai_Li.pdf
q0 = q0.reshape(-1,4)
q1 = q1.reshape(-1,4)
diff0 = ((q0 - q1)**2).sum(axis=1)
diff1 = ((q0 + q1)**2).sum(axis=1)
return np.minimum(diff0, diff1)
def quat_slerp_space(q0, q1, num=100, endpoint=True):
q0 = q0.ravel()
q1 = q1.ravel()
dot = q0.dot(q1)
if dot < 0:
q1 *= -1
dot *= -1
t = np.linspace(0, 1, num=num, endpoint=endpoint, dtype=q0.dtype)
t = t.reshape((-1,1))
if dot > 0.9995:
ret = q0 + t * (q1 - q0)
return ret
dot = np.clip(dot, -1, 1)
theta0 = np.arccos(dot)
theta = theta0 * t
s0 = np.cos(theta) - dot * np.sin(theta) / np.sin(theta0)
s1 = np.sin(theta) / np.sin(theta0)
return (s0 * q0) + (s1 * q1)
def cart_to_spherical(x):
shape = x.shape
x = x.reshape(-1,3)
y = np.empty_like(x)
y[:,0] = np.linalg.norm(x, axis=1) # r
y[:,1] = np.arccos(x[:,2] / y[:,0]) # theta
y[:,2] = np.arctan2(x[:,1], x[:,0]) # phi
return y.reshape(shape)
def spherical_to_cart(x):
shape = x.shape
x = x.reshape(-1,3)
y = np.empty_like(x)
y[:,0] = x[:,0] * np.sin(x[:,1]) * np.cos(x[:,2])
y[:,1] = x[:,0] * np.sin(x[:,1]) * np.sin(x[:,2])
y[:,2] = x[:,0] * np.cos(x[:,1])
return y.reshape(shape)
def spherical_random(r=1, n=1):
# http://mathworld.wolfram.com/SpherePointPicking.html
# https://math.stackexchange.com/questions/1585975/how-to-generate-random-points-on-a-sphere
x = np.empty((n,3))
x[:,0] = r
x[:,1] = 2 * np.pi * np.random.uniform(0,1, size=(n,))
x[:,2] = np.arccos(2 * np.random.uniform(0,1, size=(n,)) - 1)
return x.squeeze()
def color_pcl(pcl, K, im, color_axis=0, as_int=True, invalid_color=[0,0,0]):
uvd = K @ pcl.T
uvd /= uvd[2]
uvd = np.round(uvd).astype(np.int32)
mask = np.logical_and(uvd[0] >= 0, uvd[1] >= 0)
color = np.empty((pcl.shape[0], 3), dtype=im.dtype)
if color_axis == 0:
mask = np.logical_and(mask, uvd[0] < im.shape[2])
mask = np.logical_and(mask, uvd[1] < im.shape[1])
uvd = uvd[:,mask]
color[mask,:] = im[:,uvd[1],uvd[0]].T
elif color_axis == 2:
mask = np.logical_and(mask, uvd[0] < im.shape[1])
mask = np.logical_and(mask, uvd[1] < im.shape[0])
uvd = uvd[:,mask]
color[mask,:] = im[uvd[1],uvd[0], :]
else:
raise Exception('invalid color_axis')
color[np.logical_not(mask),:3] = invalid_color
if as_int:
color = (255.0 * color).astype(np.int32)
return color
def center_pcl(pcl, robust=False, copy=False, axis=1):
if copy:
pcl = pcl.copy()
if robust:
mu = np.median(pcl, axis=axis, keepdims=True)
else:
mu = np.mean(pcl, axis=axis, keepdims=True)
return pcl - mu
def to_homogeneous(x):
# return np.hstack((x, np.ones((x.shape[0],1),dtype=x.dtype)))
return np.concatenate((x, np.ones((*x.shape[:-1],1),dtype=x.dtype)), axis=-1)
def from_homogeneous(x):
return x[:,:-1] / x[:,-1]
def project_uvn(uv, Ki=None):
if uv.shape[1] == 2:
uvn = to_homogeneous(uv)
else:
uvn = uv
if uvn.shape[1] != 3:
raise Exception('uv should have shape Nx2 or Nx3')
if Ki is None:
return uvn
else:
return uvn @ Ki.T
def project_uvd(uv, depth, K=np.eye(3), R=np.eye(3), t=np.zeros((3,1)), ignore_negative_depth=True, return_uvn=False):
Ki = np.linalg.inv(K)
if ignore_negative_depth:
mask = depth >= 0
uv = uv[mask,:]
d = depth[mask]
else:
d = depth.ravel()
uv1 = to_homogeneous(uv)
uvn1 = uv1 @ Ki.T
xyz = d.reshape(-1,1) * uvn1
xyz = (xyz - t.reshape((1,3))) @ R
if return_uvn:
return xyz, uvn1
else:
return xyz
def project_depth(depth, K, R=np.eye(3,3), t=np.zeros((3,1)), ignore_negative_depth=True, return_uvn=False):
u, v = np.meshgrid(range(depth.shape[1]), range(depth.shape[0]))
uv = np.hstack((u.reshape(-1,1), v.reshape(-1,1)))
return project_uvd(uv, depth.ravel(), K, R, t, ignore_negative_depth, return_uvn)
def project_xyz(xyz, K=np.eye(3), R=np.eye(3,3), t=np.zeros((3,1))):
uvd = K @ (R @ xyz.T + t.reshape((3,1)))
uvd[:2] /= uvd[2]
return uvd[:2].T, uvd[2]
def relative_motion(R0, t0, R1, t1, Rt_from_global=True):
t0 = t0.reshape((3,1))
t1 = t1.reshape((3,1))
if Rt_from_global:
Rr = R1 @ R0.T
tr = t1 - Rr @ t0
else:
Rr = R1.T @ R0
tr = R1.T @ (t0 - t1)
return Rr, tr.ravel()
def translation_to_cameracenter(R, t):
t = t.reshape(-1,3,1)
R = R.reshape(-1,3,3)
C = -R.transpose(0,2,1) @ t
return C.squeeze()
def cameracenter_to_translation(R, C):
C = C.reshape(-1,3,1)
R = R.reshape(-1,3,3)
t = -R @ C
return t.squeeze()
def decompose_projection_matrix(P, return_t=True):
if P.shape[0] != 3 or P.shape[1] != 4:
raise Exception('P has to be 3x4')
M = P[:, :3]
C = -np.linalg.inv(M) @ P[:, 3:]
R,K = np.linalg.qr(np.flipud(M).T)
K = np.flipud(K.T)
K = np.fliplr(K)
R = np.flipud(R.T)
T = np.diag(np.sign(np.diag(K)))
K = K @ T
R = T @ R
if np.linalg.det(R) < 0:
R *= -1
K /= K[2,2]
if return_t:
return K, R, cameracenter_to_translation(R, C)
else:
return K, R, C
def compose_projection_matrix(K=np.eye(3), R=np.eye(3,3), t=np.zeros((3,1))):
return K @ np.hstack((R, t.reshape((3,1))))
def point_plane_distance(pts, plane):
pts = pts.reshape(-1,3)
return np.abs(np.sum(plane[:3] * pts, axis=1) + plane[3]) / np.linalg.norm(plane[:3])
def fit_plane(pts):
pts = pts.reshape(-1,3)
center = np.mean(pts, axis=0)
A = pts - center
u, s, vh = np.linalg.svd(A, full_matrices=False)
# if pts.shape[0] > 100:
# import ipdb; ipdb.set_trace()
plane = np.array([*vh[2], -vh[2].dot(center)])
return plane
def tetrahedron(dtype=np.float32):
verts = np.array([
(np.sqrt(8/9), 0, -1/3), (-np.sqrt(2/9), np.sqrt(2/3), -1/3),
(-np.sqrt(2/9), -np.sqrt(2/3), -1/3), (0, 0, 1)], dtype=dtype)
faces = np.array([(0,1,2), (0,2,3), (0,1,3), (1,2,3)], dtype=np.int32)
normals = -np.mean(verts, axis=0) + verts
normals /= np.linalg.norm(normals, axis=1).reshape(-1,1)
return verts, faces, normals
def cube(dtype=np.float32):
verts = np.array([
[-0.5,-0.5,-0.5], [-0.5,0.5,-0.5], [0.5,0.5,-0.5], [0.5,-0.5,-0.5],
[-0.5,-0.5,0.5], [-0.5,0.5,0.5], [0.5,0.5,0.5], [0.5,-0.5,0.5]], dtype=dtype)
faces = np.array([
(0,1,2), (0,2,3), (4,5,6), (4,6,7),
(0,4,7), (0,7,3), (1,5,6), (1,6,2),
(3,2,6), (3,6,7), (0,1,5), (0,5,4)], dtype=np.int32)
normals = -np.mean(verts, axis=0) + verts
normals /= np.linalg.norm(normals, axis=1).reshape(-1,1)
return verts, faces, normals
def octahedron(dtype=np.float32):
verts = np.array([
(+1,0,0), (0,+1,0), (0,0,+1),
(-1,0,0), (0,-1,0), (0,0,-1)], dtype=dtype)
faces = np.array([
(0,1,2), (1,2,3), (3,2,4), (4,2,0),
(0,1,5), (1,5,3), (3,5,4), (4,5,0)], dtype=np.int32)
normals = -np.mean(verts, axis=0) + verts
normals /= np.linalg.norm(normals, axis=1).reshape(-1,1)
return verts, faces, normals
def icosahedron(dtype=np.float32):
p = (1 + np.sqrt(5)) / 2
verts = np.array([
(-1,0,p), (1,0,p), (1,0,-p), (-1,0,-p),
(0,-p,1), (0,p,1), (0,p,-1), (0,-p,-1),
(-p,-1,0), (p,-1,0), (p,1,0), (-p,1,0)
], dtype=dtype)
faces = np.array([
(0,1,4), (0,1,5), (1,4,9), (1,9,10), (1,10,5), (0,4,8), (0,8,11), (0,11,5),
(5,6,11), (5,6,10), (4,7,8), (4,7,9),
(3,2,6), (3,2,7), (2,6,10), (2,10,9), (2,9,7), (3,6,11), (3,11,8), (3,8,7),
], dtype=np.int32)
normals = -np.mean(verts, axis=0) + verts
normals /= np.linalg.norm(normals, axis=1).reshape(-1,1)
return verts, faces, normals
def xyplane(dtype=np.float32, z=0, interleaved=False):
if interleaved:
eps = 1e-6
verts = np.array([
(-1,-1,z), (-1,1,z), (1,1,z),
(1-eps,1,z), (1-eps,-1,z), (-1-eps,-1,z)], dtype=dtype)
faces = np.array([(0,1,2), (3,4,5)], dtype=np.int32)
else:
verts = np.array([(-1,-1,z), (-1,1,z), (1,1,z), (1,-1,z)], dtype=dtype)
faces = np.array([(0,1,2), (0,2,3)], dtype=np.int32)
normals = np.zeros_like(verts)
normals[:,2] = -1
return verts, faces, normals
def mesh_independent_verts(verts, faces, normals=None):
new_verts = []
new_normals = []
for f in faces:
new_verts.append(verts[f[0]])
new_verts.append(verts[f[1]])
new_verts.append(verts[f[2]])
if normals is not None:
new_normals.append(normals[f[0]])
new_normals.append(normals[f[1]])
new_normals.append(normals[f[2]])
new_verts = np.array(new_verts)
new_faces = np.arange(0, faces.size, dtype=faces.dtype).reshape(-1,3)
if normals is None:
return new_verts, new_faces
else:
new_normals = np.array(new_normals)
return new_verts, new_faces, new_normals
def stack_mesh(verts, faces):
n_verts = 0
mfaces = []
for idx, f in enumerate(faces):
mfaces.append(f + n_verts)
n_verts += verts[idx].shape[0]
verts = np.vstack(verts)
faces = np.vstack(mfaces)
return verts, faces
def normalize_mesh(verts):
# all the verts have unit distance to the center (0,0,0)
return verts / np.linalg.norm(verts, axis=1, keepdims=True)
def mesh_triangle_areas(verts, faces):
a = verts[faces[:,0]]
b = verts[faces[:,1]]
c = verts[faces[:,2]]
x = np.empty_like(a)
x = a - b
y = a - c
t = np.empty_like(a)
t[:,0] = (x[:,1] * y[:,2] - x[:,2] * y[:,1]);
t[:,1] = (x[:,2] * y[:,0] - x[:,0] * y[:,2]);
t[:,2] = (x[:,0] * y[:,1] - x[:,1] * y[:,0]);
return np.linalg.norm(t, axis=1) / 2
def subdivde_mesh(verts_in, faces_in, n=1):
for iter in range(n):
verts = []
for v in verts_in:
verts.append(v)
faces = []
verts_dict = {}
for f in faces_in:
f = np.sort(f)
i0,i1,i2 = f
v0,v1,v2 = verts_in[f]
k = i0*len(verts_in)+i1
if k in verts_dict:
i01 = verts_dict[k]
else:
i01 = len(verts)
verts_dict[k] = i01
v01 = (v0 + v1) / 2
verts.append(v01)
k = i0*len(verts_in)+i2
if k in verts_dict:
i02 = verts_dict[k]
else:
i02 = len(verts)
verts_dict[k] = i02
v02 = (v0 + v2) / 2
verts.append(v02)
k = i1*len(verts_in)+i2
if k in verts_dict:
i12 = verts_dict[k]
else:
i12 = len(verts)
verts_dict[k] = i12
v12 = (v1 + v2) / 2
verts.append(v12)
faces.append((i0,i01,i02))
faces.append((i01,i1,i12))
faces.append((i12,i2,i02))
faces.append((i01,i12,i02))
verts_in = np.array(verts, dtype=verts_in.dtype)
faces_in = np.array(faces, dtype=np.int32)
return verts_in, faces_in
def mesh_adjust_winding_order(verts, faces, normals):
n0 = normals[faces[:,0]]
n1 = normals[faces[:,1]]
n2 = normals[faces[:,2]]
fnormals = (n0 + n1 + n2) / 3
v0 = verts[faces[:,0]]
v1 = verts[faces[:,1]]
v2 = verts[faces[:,2]]
e0 = v1 - v0
e1 = v2 - v0
fn = np.cross(e0, e1)
dot = np.sum(fnormals * fn, axis=1)
ma = dot < 0
nfaces = faces.copy()
nfaces[ma,1], nfaces[ma,2] = nfaces[ma,2], nfaces[ma,1]
return nfaces
def pcl_to_shapecl(verts, colors=None, shape='cube', width=1.0):
if shape == 'tetrahedron':
cverts, cfaces, _ = tetrahedron()
elif shape == 'cube':
cverts, cfaces, _ = cube()
elif shape == 'octahedron':
cverts, cfaces, _ = octahedron()
elif shape == 'icosahedron':
cverts, cfaces, _ = icosahedron()
else:
raise Exception('invalid shape')
sverts = np.tile(cverts, (verts.shape[0], 1))
sverts *= width
sverts += np.repeat(verts, cverts.shape[0], axis=0)
sfaces = np.tile(cfaces, (verts.shape[0], 1))
sfoffset = cverts.shape[0] * np.arange(0, verts.shape[0])
sfaces += np.repeat(sfoffset, cfaces.shape[0]).reshape(-1,1)
if colors is not None:
scolors = np.repeat(colors, cverts.shape[0], axis=0)
else:
scolors = None
return sverts, sfaces, scolors
+32
View File
@@ -0,0 +1,32 @@
import numpy as np
from . import utils
class StopWatch(utils.StopWatch):
def __del__(self):
print('='*80)
print('gtimer:')
total = ', '.join(['%s: %f[s]' % (k,v) for k,v in self.get(reduce=np.sum).items()])
print(f' [total] {total}')
mean = ', '.join(['%s: %f[s]' % (k,v) for k,v in self.get(reduce=np.mean).items()])
print(f' [mean] {mean}')
median = ', '.join(['%s: %f[s]' % (k,v) for k,v in self.get(reduce=np.median).items()])
print(f' [median] {median}')
print('='*80)
GTIMER = StopWatch()
def start(name):
GTIMER.start(name)
def stop(name):
GTIMER.stop(name)
class Ctx(object):
def __init__(self, name):
self.name = name
def __enter__(self):
start(self.name)
def __exit__(self, *args):
stop(self.name)
+267
View File
@@ -0,0 +1,267 @@
import struct
import numpy as np
import collections
def _write_ply_point(fp, x,y,z, color=None, normal=None, binary=False):
args = [x,y,z]
if color is not None:
args += [int(color[0]), int(color[1]), int(color[2])]
if normal is not None:
args += [normal[0],normal[1],normal[2]]
if binary:
fmt = '<fff'
if color is not None:
fmt = fmt + 'BBB'
if normal is not None:
fmt = fmt + 'fff'
fp.write(struct.pack(fmt, *args))
else:
fmt = '%f %f %f'
if color is not None:
fmt = fmt + ' %d %d %d'
if normal is not None:
fmt = fmt + ' %f %f %f'
fmt += '\n'
fp.write(fmt % tuple(args))
def _write_ply_triangle(fp, i0,i1,i2, binary):
if binary:
fp.write(struct.pack('<Biii', 3,i0,i1,i2))
else:
fp.write('3 %d %d %d\n' % (i0,i1,i2))
def _write_ply_header_line(fp, str, binary):
if binary:
fp.write(str.encode())
else:
fp.write(str)
def write_ply(path, verts, trias=None, color=None, normals=None, binary=False):
if verts.shape[1] != 3:
raise Exception('verts has to be of shape Nx3')
if trias is not None and trias.shape[1] != 3:
raise Exception('trias has to be of shape Nx3')
if color is not None and not callable(color) and not isinstance(color, np.ndarray) and color.shape[1] != 3:
raise Exception('color has to be of shape Nx3 or a callable')
mode = 'wb' if binary else 'w'
with open(path, mode) as fp:
_write_ply_header_line(fp, "ply\n", binary)
if binary:
_write_ply_header_line(fp, "format binary_little_endian 1.0\n", binary)
else:
_write_ply_header_line(fp, "format ascii 1.0\n", binary)
_write_ply_header_line(fp, "element vertex %d\n" % (verts.shape[0]), binary)
_write_ply_header_line(fp, "property float32 x\n", binary)
_write_ply_header_line(fp, "property float32 y\n", binary)
_write_ply_header_line(fp, "property float32 z\n", binary)
if color is not None:
_write_ply_header_line(fp, "property uchar red\n", binary)
_write_ply_header_line(fp, "property uchar green\n", binary)
_write_ply_header_line(fp, "property uchar blue\n", binary)
if normals is not None:
_write_ply_header_line(fp, "property float32 nx\n", binary)
_write_ply_header_line(fp, "property float32 ny\n", binary)
_write_ply_header_line(fp, "property float32 nz\n", binary)
if trias is not None:
_write_ply_header_line(fp, "element face %d\n" % (trias.shape[0]), binary)
_write_ply_header_line(fp, "property list uchar int32 vertex_indices\n", binary)
_write_ply_header_line(fp, "end_header\n", binary)
for vidx, v in enumerate(verts):
if color is not None:
if callable(color):
c = color(vidx)
elif color.shape[0] > 1:
c = color[vidx]
else:
c = color[0]
else:
c = None
if normals is None:
n = None
else:
n = normals[vidx]
_write_ply_point(fp, v[0],v[1],v[2], c, n, binary)
if trias is not None:
for t in trias:
_write_ply_triangle(fp, t[0],t[1],t[2], binary)
def faces_to_triangles(faces):
new_faces = []
for f in faces:
if f[0] == 3:
new_faces.append([f[1], f[2], f[3]])
elif f[0] == 4:
new_faces.append([f[1], f[2], f[3]])
new_faces.append([f[3], f[4], f[1]])
else:
raise Exception('unknown face count %d', f[0])
return new_faces
def read_ply(path):
with open(path, 'rb') as f:
# parse header
line = f.readline().decode().strip()
if line != 'ply':
raise Exception('Header error')
n_verts = 0
n_faces = 0
vert_types = {}
vert_bin_format = []
vert_bin_len = 0
vert_bin_cols = 0
line = f.readline().decode()
parse_vertex_prop = False
while line.strip() != 'end_header':
if 'format' in line:
if 'ascii' in line:
binary = False
elif 'binary_little_endian' in line:
binary = True
else:
raise Exception('invalid ply format')
if 'element face' in line:
splits = line.strip().split(' ')
n_faces = int(splits[-1])
parse_vertex_prop = False
if 'element camera' in line:
parse_vertex_prop = False
if 'element vertex' in line:
splits = line.strip().split(' ')
n_verts = int(splits[-1])
parse_vertex_prop = True
if parse_vertex_prop and 'property' in line:
prop = line.strip().split()
if prop[1] == 'float':
vert_bin_format.append('f4')
vert_bin_len += 4
vert_bin_cols += 1
elif prop[1] == 'uchar':
vert_bin_format.append('B')
vert_bin_len += 1
vert_bin_cols += 1
else:
raise Exception('invalid property')
vert_types[prop[2]] = len(vert_types)
line = f.readline().decode()
# parse content
if binary:
sz = n_verts * vert_bin_len
fmt = ','.join(vert_bin_format)
verts = np.ndarray(shape=(1, n_verts), dtype=np.dtype(fmt), buffer=f.read(sz))
verts = verts[0].astype(vert_bin_cols*'f4,').view(dtype='f4').reshape((n_verts,-1))
faces = []
for idx in range(n_faces):
fmt = '<Biii'
length = struct.calcsize(fmt)
dat = f.read(length)
vals = struct.unpack(fmt, dat)
faces.append(vals)
faces = faces_to_triangles(faces)
faces = np.array(faces, dtype=np.int32)
else:
verts = []
for idx in range(n_verts):
vals = [float(v) for v in f.readline().decode().strip().split(' ')]
verts.append(vals)
verts = np.array(verts, dtype=np.float32)
faces = []
for idx in range(n_faces):
splits = f.readline().decode().strip().split(' ')
n_face_verts = int(splits[0])
vals = [int(v) for v in splits[0:n_face_verts+1]]
faces.append(vals)
faces = faces_to_triangles(faces)
faces = np.array(faces, dtype=np.int32)
xyz = None
if 'x' in vert_types and 'y' in vert_types and 'z' in vert_types:
xyz = verts[:,[vert_types['x'], vert_types['y'], vert_types['z']]]
colors = None
if 'red' in vert_types and 'green' in vert_types and 'blue' in vert_types:
colors = verts[:,[vert_types['red'], vert_types['green'], vert_types['blue']]]
colors /= 255
normals = None
if 'nx' in vert_types and 'ny' in vert_types and 'nz' in vert_types:
normals = verts[:,[vert_types['nx'], vert_types['ny'], vert_types['nz']]]
return xyz, faces, colors, normals
def _read_obj_split_f(s):
parts = s.split('/')
vidx = int(parts[0]) - 1
if len(parts) >= 2 and len(parts[1]) > 0:
tidx = int(parts[1]) - 1
else:
tidx = -1
if len(parts) >= 3 and len(parts[2]) > 0:
nidx = int(parts[2]) - 1
else:
nidx = -1
return vidx, tidx, nidx
def read_obj(path):
with open(path, 'r') as fp:
lines = fp.readlines()
verts = []
colors = []
fnorms = []
fnorm_map = collections.defaultdict(list)
faces = []
for line in lines:
line = line.strip()
if line.startswith('#') or len(line) == 0:
continue
parts = line.split()
if line.startswith('v '):
parts = parts[1:]
x,y,z = float(parts[0]), float(parts[1]), float(parts[2])
if len(parts) == 4 or len(parts) == 7:
w = float(parts[3])
x,y,z = x/w, y/w, z/w
verts.append((x,y,z))
if len(parts) >= 6:
r,g,b = float(parts[-3]), float(parts[-2]), float(parts[-1])
rgb.append((r,g,b))
elif line.startswith('vn '):
parts = parts[1:]
x,y,z = float(parts[0]), float(parts[1]), float(parts[2])
fnorms.append((x,y,z))
elif line.startswith('f '):
parts = parts[1:]
if len(parts) != 3:
raise Exception('only triangle meshes supported atm')
vidx0, tidx0, nidx0 = _read_obj_split_f(parts[0])
vidx1, tidx1, nidx1 = _read_obj_split_f(parts[1])
vidx2, tidx2, nidx2 = _read_obj_split_f(parts[2])
faces.append((vidx0, vidx1, vidx2))
if nidx0 >= 0:
fnorm_map[vidx0].append( nidx0 )
if nidx1 >= 0:
fnorm_map[vidx1].append( nidx1 )
if nidx2 >= 0:
fnorm_map[vidx2].append( nidx2 )
verts = np.array(verts)
colors = np.array(colors)
fnorms = np.array(fnorms)
faces = np.array(faces)
# face normals to vertex normals
norms = np.zeros_like(verts)
for vidx in fnorm_map.keys():
ind = fnorm_map[vidx]
norms[vidx] = fnorms[ind].sum(axis=0)
N = np.linalg.norm(norms, axis=1, keepdims=True)
np.divide(norms, N, out=norms, where=N != 0)
return verts, faces, colors, norms
+248
View File
@@ -0,0 +1,248 @@
import numpy as np
from . import geometry
def _process_inputs(estimate, target, mask):
if estimate.shape != target.shape:
raise Exception('estimate and target have to be same shape')
if mask is None:
mask = np.ones(estimate.shape, dtype=np.bool)
else:
mask = mask != 0
if estimate.shape != mask.shape:
raise Exception('estimate and mask have to be same shape')
return estimate, target, mask
def mse(estimate, target, mask=None):
estimate, target, mask = _process_inputs(estimate, target, mask)
m = np.sum((estimate[mask] - target[mask])**2) / mask.sum()
return m
def rmse(estimate, target, mask=None):
return np.sqrt(mse(estimate, target, mask))
def mae(estimate, target, mask=None):
estimate, target, mask = _process_inputs(estimate, target, mask)
m = np.abs(estimate[mask] - target[mask]).sum() / mask.sum()
return m
def outlier_fraction(estimate, target, mask=None, threshold=0):
estimate, target, mask = _process_inputs(estimate, target, mask)
diff = np.abs(estimate[mask] - target[mask])
m = (diff > threshold).sum() / mask.sum()
return m
class Metric(object):
def __init__(self, str_prefix=''):
self.str_prefix = str_prefix
self.reset()
def reset(self):
pass
def add(self, es, ta, ma=None):
pass
def get(self):
return {}
def items(self):
return self.get().items()
def __str__(self):
return ', '.join([f'{self.str_prefix}{key}={value:.5f}' for key, value in self.get().items()])
class MultipleMetric(Metric):
def __init__(self, *metrics, **kwargs):
self.metrics = [*metrics]
super().__init__(**kwargs)
def reset(self):
for m in self.metrics:
m.reset()
def add(self, es, ta, ma=None):
for m in self.metrics:
m.add(es, ta, ma)
def get(self):
ret = {}
for m in self.metrics:
vals = m.get()
for k in vals:
ret[k] = vals[k]
return ret
def __str__(self):
return '\n'.join([str(m) for m in self.metrics])
class BaseDistanceMetric(Metric):
def __init__(self, name='', **kwargs):
super().__init__(**kwargs)
self.name = name
def reset(self):
self.dists = []
def add(self, es, ta, ma=None):
pass
def get(self):
dists = np.hstack(self.dists)
return {
f'dist{self.name}_mean': float(np.mean(dists)),
f'dist{self.name}_std': float(np.std(dists)),
f'dist{self.name}_median': float(np.median(dists)),
f'dist{self.name}_q10': float(np.percentile(dists, 10)),
f'dist{self.name}_q90': float(np.percentile(dists, 90)),
f'dist{self.name}_min': float(np.min(dists)),
f'dist{self.name}_max': float(np.max(dists)),
}
class DistanceMetric(BaseDistanceMetric):
def __init__(self, vec_length, p=2, **kwargs):
super().__init__(name=f'{p}', **kwargs)
self.vec_length = vec_length
self.p = p
def add(self, es, ta, ma=None):
if es.shape != ta.shape or es.shape[1] != self.vec_length or es.ndim != 2:
print(es.shape, ta.shape)
raise Exception('es and ta have to be of shape Nxdim')
if ma is not None:
es = es[ma != 0]
ta = ta[ma != 0]
dist = np.linalg.norm(es - ta, ord=self.p, axis=1)
self.dists.append( dist )
class OutlierFractionMetric(DistanceMetric):
def __init__(self, thresholds, *args, **kwargs):
super().__init__(*args, **kwargs)
self.thresholds = thresholds
def get(self):
dists = np.hstack(self.dists)
ret = {}
for t in self.thresholds:
ma = dists > t
ret[f'of{t}'] = float(ma.sum() / ma.size)
return ret
class RelativeDistanceMetric(BaseDistanceMetric):
def __init__(self, vec_length, p=2, **kwargs):
super().__init__(name=f'rel{p}', **kwargs)
self.vec_length = vec_length
self.p = p
def add(self, es, ta, ma=None):
if es.shape != ta.shape or es.shape[1] != self.vec_length or es.ndim != 2:
raise Exception('es and ta have to be of shape Nxdim')
dist = np.linalg.norm(es - ta, ord=self.p, axis=1)
denom = np.linalg.norm(ta, ord=self.p, axis=1)
dist /= denom
if ma is not None:
dist = dist[ma != 0]
self.dists.append( dist )
class RotmDistanceMetric(BaseDistanceMetric):
def __init__(self, type='identity', **kwargs):
super().__init__(name=type, **kwargs)
self.type = type
def add(self, es, ta, ma=None):
if es.shape != ta.shape or es.shape[1] != 3 or es.shape[2] != 3 or es.ndim != 3:
print(es.shape, ta.shape)
raise Exception('es and ta have to be of shape Nx3x3')
if ma is not None:
raise Exception('mask is not implemented')
if self.type == 'identity':
self.dists.append( geometry.rotm_distance_identity(es, ta) )
elif self.type == 'geodesic':
self.dists.append( geometry.rotm_distance_geodesic_unit_sphere(es, ta) )
else:
raise Exception('invalid distance type')
class QuaternionDistanceMetric(BaseDistanceMetric):
def __init__(self, type='angle', **kwargs):
super().__init__(name=type, **kwargs)
self.type = type
def add(self, es, ta, ma=None):
if es.shape != ta.shape or es.shape[1] != 4 or es.ndim != 2:
print(es.shape, ta.shape)
raise Exception('es and ta have to be of shape Nx4')
if ma is not None:
raise Exception('mask is not implemented')
if self.type == 'angle':
self.dists.append( geometry.quat_distance_angle(es, ta) )
elif self.type == 'mineucl':
self.dists.append( geometry.quat_distance_mineucl(es, ta) )
elif self.type == 'normdiff':
self.dists.append( geometry.quat_distance_normdiff(es, ta) )
else:
raise Exception('invalid distance type')
class BinaryAccuracyMetric(Metric):
def __init__(self, thresholds=np.linspace(0.0, 1.0, num=101, dtype=np.float64)[:-1], **kwargs):
self.thresholds = thresholds
super().__init__(**kwargs)
def reset(self):
self.tps = [0 for wp in self.thresholds]
self.fps = [0 for wp in self.thresholds]
self.fns = [0 for wp in self.thresholds]
self.tns = [0 for wp in self.thresholds]
self.n_pos = 0
self.n_neg = 0
def add(self, es, ta, ma=None):
if ma is not None:
raise Exception('mask is not implemented')
es = es.ravel()
ta = ta.ravel()
if es.shape[0] != ta.shape[0]:
raise Exception('invalid shape of es, or ta')
if es.min() < 0 or es.max() > 1:
raise Exception('estimate has wrong value range')
ta_p = (ta == 1)
ta_n = (ta == 0)
es_p = es[ta_p]
es_n = es[ta_n]
for idx, wp in enumerate(self.thresholds):
wp = np.asscalar(wp)
self.tps[idx] += (es_p > wp).sum()
self.fps[idx] += (es_n > wp).sum()
self.fns[idx] += (es_p <= wp).sum()
self.tns[idx] += (es_n <= wp).sum()
self.n_pos += ta_p.sum()
self.n_neg += ta_n.sum()
def get(self):
tps = np.array(self.tps).astype(np.float32)
fps = np.array(self.fps).astype(np.float32)
fns = np.array(self.fns).astype(np.float32)
tns = np.array(self.tns).astype(np.float32)
wp = self.thresholds
ret = {}
precisions = np.divide(tps, tps + fps, out=np.zeros_like(tps), where=tps + fps != 0)
recalls = np.divide(tps, tps + fns, out=np.zeros_like(tps), where=tps + fns != 0) # tprs
fprs = np.divide(fps, fps + tns, out=np.zeros_like(tps), where=fps + tns != 0)
precisions = np.r_[0, precisions, 1]
recalls = np.r_[1, recalls, 0]
fprs = np.r_[1, fprs, 0]
ret['auc'] = float(-np.trapz(recalls, fprs))
ret['prauc'] = float(-np.trapz(precisions, recalls))
ret['ap'] = float(-(np.diff(recalls) * precisions[:-1]).sum())
accuracies = np.divide(tps + tns, tps + tns + fps + fns)
aacc = np.mean(accuracies)
for t in np.linspace(0,1,num=11)[1:-1]:
idx = np.argmin(np.abs(t - wp))
ret[f'acc{wp[idx]:.2f}'] = float(accuracies[idx])
return ret
+99
View File
@@ -0,0 +1,99 @@
import numpy as np
import matplotlib as mpl
from matplotlib import _pylab_helpers
from matplotlib.rcsetup import interactive_bk as _interactive_bk
import matplotlib.pyplot as plt
import os
import time
def save(path, remove_axis=False, dpi=300, fig=None):
if fig is None:
fig = plt.gcf()
dirname = os.path.dirname(path)
if dirname != '' and not os.path.exists(dirname):
os.makedirs(dirname)
if remove_axis:
for ax in fig.axes:
ax.axis('off')
ax.margins(0,0)
fig.subplots_adjust(top=1, bottom=0, right=1, left=0, hspace=0, wspace=0)
for ax in fig.axes:
ax.xaxis.set_major_locator(plt.NullLocator())
ax.yaxis.set_major_locator(plt.NullLocator())
fig.savefig(path, dpi=dpi, bbox_inches='tight', pad_inches=0)
def color_map(im_, cmap='viridis', vmin=None, vmax=None):
cm = plt.get_cmap(cmap)
im = im_.copy()
if vmin is None:
vmin = np.nanmin(im)
if vmax is None:
vmax = np.nanmax(im)
mask = np.logical_not(np.isfinite(im))
im[mask] = vmin
im = (im.clip(vmin, vmax) - vmin) / (vmax - vmin)
im = cm(im)
im = im[...,:3]
for c in range(3):
im[mask, c] = 1
return im
def interactive_legend(leg=None, fig=None, all_axes=True):
if leg is None:
leg = plt.legend()
if fig is None:
fig = plt.gcf()
if all_axes:
axs = fig.get_axes()
else:
axs = [fig.gca()]
# lined = dict()
# lines = ax.lines
# for legline, origline in zip(leg.get_lines(), ax.lines):
# legline.set_picker(5)
# lined[legline] = origline
lined = dict()
for lidx, legline in enumerate(leg.get_lines()):
legline.set_picker(5)
lined[legline] = [ax.lines[lidx] for ax in axs]
def onpick(event):
if event.mouseevent.dblclick:
tmp = [(k,v) for k,v in lined.items()]
else:
tmp = [(event.artist, lined[event.artist])]
for legline, origline in tmp:
for ol in origline:
vis = not ol.get_visible()
ol.set_visible(vis)
if vis:
legline.set_alpha(1.0)
else:
legline.set_alpha(0.2)
fig.canvas.draw()
fig.canvas.mpl_connect('pick_event', onpick)
def non_annoying_pause(interval, focus_figure=False):
# https://github.com/matplotlib/matplotlib/issues/11131
backend = mpl.rcParams['backend']
if backend in _interactive_bk:
figManager = _pylab_helpers.Gcf.get_active()
if figManager is not None:
canvas = figManager.canvas
if canvas.figure.stale:
canvas.draw()
if focus_figure:
plt.show(block=False)
canvas.start_event_loop(interval)
return
time.sleep(interval)
def remove_all_ticks(fig=None):
if fig is None:
fig = plt.gcf()
for ax in fig.axes:
ax.axes.get_xaxis().set_visible(False)
ax.axes.get_yaxis().set_visible(False)
+57
View File
@@ -0,0 +1,57 @@
import numpy as np
import matplotlib.pyplot as plt
from . import geometry
def image_matrix(ims, bgval=0):
n = ims.shape[0]
m = int( np.ceil(np.sqrt(n)) )
h = ims.shape[1]
w = ims.shape[2]
mat = np.empty((m*h, m*w), dtype=ims.dtype)
mat.fill(bgval)
idx = 0
for r in range(m):
for c in range(m):
if idx < n:
mat[r*h:(r+1)*h, c*w:(c+1)*w] = ims[idx]
idx += 1
return mat
def image_cat(ims, vertical=False):
offx = [0]
offy = [0]
if vertical:
width = max([im.shape[1] for im in ims])
offx += [0 for im in ims[:-1]]
offy += [im.shape[0] for im in ims[:-1]]
height = sum([im.shape[0] for im in ims])
else:
height = max([im.shape[0] for im in ims])
offx += [im.shape[1] for im in ims[:-1]]
offy += [0 for im in ims[:-1]]
width = sum([im.shape[1] for im in ims])
offx = np.cumsum(offx)
offy = np.cumsum(offy)
im = np.zeros((height,width,*ims[0].shape[2:]), dtype=ims[0].dtype)
for im0, ox, oy in zip(ims, offx, offy):
im[oy:oy + im0.shape[0], ox:ox + im0.shape[1]] = im0
return im, offx, offy
def line(li, h, w, ax=None, *args, **kwargs):
if ax is None:
ax = plt.gca()
xs = (-li[2] - li[1] * np.array((0, h-1))) / li[0]
ys = (-li[2] - li[0] * np.array((0, w-1))) / li[1]
pts = np.array([(0,ys[0]), (w-1, ys[1]), (xs[0], 0), (xs[1], h-1)])
pts = pts[np.logical_and(np.logical_and(pts[:,0] >= 0, pts[:,0] < w), np.logical_and(pts[:,1] >= 0, pts[:,1] < h))]
ax.plot(pts[:,0], pts[:,1], *args, **kwargs)
def depthshow(depth, *args, ax=None, **kwargs):
if ax is None:
ax = plt.gca()
d = depth.copy()
d[d < 0] = np.NaN
ax.imshow(d, *args, **kwargs)
+38
View File
@@ -0,0 +1,38 @@
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from . import geometry
def ax3d(fig=None):
if fig is None:
fig = plt.gcf()
return fig.add_subplot(111, projection='3d')
def plot_camera(ax=None, R=np.eye(3), t=np.zeros((3,)), size=25, marker_C='.', color='b', linestyle='-', linewidth=0.1, label=None, **kwargs):
if ax is None:
ax = plt.gca()
C0 = geometry.translation_to_cameracenter(R, t).ravel()
C1 = C0 + R.T.dot( np.array([[-size],[-size],[3*size]], dtype=np.float32) ).ravel()
C2 = C0 + R.T.dot( np.array([[-size],[+size],[3*size]], dtype=np.float32) ).ravel()
C3 = C0 + R.T.dot( np.array([[+size],[+size],[3*size]], dtype=np.float32) ).ravel()
C4 = C0 + R.T.dot( np.array([[+size],[-size],[3*size]], dtype=np.float32) ).ravel()
if marker_C != '':
ax.plot([C0[0]], [C0[1]], [C0[2]], marker=marker_C, color=color, label=label, **kwargs)
ax.plot([C0[0], C1[0]], [C0[1], C1[1]], [C0[2], C1[2]], color=color, label='_nolegend_', linestyle=linestyle, linewidth=linewidth, **kwargs)
ax.plot([C0[0], C2[0]], [C0[1], C2[1]], [C0[2], C2[2]], color=color, label='_nolegend_', linestyle=linestyle, linewidth=linewidth, **kwargs)
ax.plot([C0[0], C3[0]], [C0[1], C3[1]], [C0[2], C3[2]], color=color, label='_nolegend_', linestyle=linestyle, linewidth=linewidth, **kwargs)
ax.plot([C0[0], C4[0]], [C0[1], C4[1]], [C0[2], C4[2]], color=color, label='_nolegend_', linestyle=linestyle, linewidth=linewidth, **kwargs)
ax.plot([C1[0], C2[0], C3[0], C4[0], C1[0]], [C1[1], C2[1], C3[1], C4[1], C1[1]], [C1[2], C2[2], C3[2], C4[2], C1[2]], color=color, label='_nolegend_', linestyle=linestyle, linewidth=linewidth, **kwargs)
def axis_equal(ax=None):
if ax is None:
ax = plt.gca()
extents = np.array([getattr(ax, 'get_{}lim'.format(dim))() for dim in 'xyz'])
sz = extents[:,1] - extents[:,0]
centers = np.mean(extents, axis=1)
maxsize = max(abs(sz))
r = maxsize/2
for ctr, dim in zip(centers, 'xyz'):
getattr(ax, 'set_{}lim'.format(dim))(ctr - r, ctr + r)
+445
View File
@@ -0,0 +1,445 @@
import numpy as np
import pandas as pd
import enum
import itertools
class Table(object):
def __init__(self, n_cols):
self.n_cols = n_cols
self.rows = []
self.aligns = ['r' for c in range(n_cols)]
def get_cell_align(self, r, c):
align = self.rows[r].cells[c].align
if align is None:
return self.aligns[c]
else:
return align
def add_row(self, row):
if row.ncols() != self.n_cols:
raise Exception(f'row has invalid number of cols, {row.ncols()} vs. {self.n_cols}')
self.rows.append(row)
def empty_row(self):
return Row.Empty(self.n_cols)
def expand_rows(self, n_add_cols=1):
if n_add_cols < 0: raise Exception('n_add_cols has to be positive')
self.n_cols += n_add_cols
for row in self.rows:
row.cells.extend([Cell() for cidx in range(n_add_cols)])
def add_block(self, data, row=-1, col=0, fmt=None, expand=False):
if row < 0: row = len(self.rows)
while len(self.rows) < row + len(data):
self.add_row(self.empty_row())
for r in range(len(data)):
cols = data[r]
if col + len(cols) > self.n_cols:
if expand:
self.expand_rows(col + len(cols) - self.n_cols)
else:
raise Exception('number of cols does not fit in table')
for c in range(len(cols)):
self.rows[row+r].cells[col+c] = Cell(data[r][c], fmt)
class Row(object):
def __init__(self, cells, pre_separator=None, post_separator=None):
self.cells = cells
self.pre_separator = pre_separator
self.post_separator = post_separator
@classmethod
def Empty(cls, n_cols):
return Row([Cell() for c in range(n_cols)])
def add_cell(self, cell):
self.cells.append(cell)
def ncols(self):
return sum([c.span for c in self.cells])
class Color(object):
def __init__(self, color=(0,0,0), fmt='rgb'):
if fmt == 'rgb':
self.color = color
elif fmt == 'RGB':
self.color = tuple(c / 255 for c in color)
else:
return Exception('invalid color format')
def as_rgb(self):
return self.color
def as_RGB(self):
return tuple(int(c * 255) for c in self.color)
@classmethod
def rgb(cls, r, g, b):
return Color(color=(r,g,b), fmt='rgb')
@classmethod
def RGB(cls, r, g, b):
return Color(color=(r,g,b), fmt='RGB')
class CellFormat(object):
def __init__(self, fmt='%s', fgcolor=None, bgcolor=None, bold=False):
self.fmt = fmt
self.fgcolor = fgcolor
self.bgcolor = bgcolor
self.bold = bold
class Cell(object):
def __init__(self, data=None, fmt=None, span=1, align=None):
self.data = data
if fmt is None:
fmt = CellFormat()
self.fmt = fmt
self.span = span
self.align = align
def __str__(self):
return self.fmt.fmt % self.data
class Separator(enum.Enum):
HEAD = 1
BOTTOM = 2
INNER = 3
class Renderer(object):
def __init__(self):
pass
def cell_str_len(self, cell):
return len(str(cell))
def col_widths(self, table):
widths = [0 for c in range(table.n_cols)]
for row in table.rows:
cidx = 0
for cell in row.cells:
if cell.span == 1:
strlen = self.cell_str_len(cell)
widths[cidx] = max(widths[cidx], strlen)
cidx += cell.span
return widths
def render(self, table):
raise NotImplementedError('not implemented')
def __call__(self, table):
return self.render(table)
def render_to_file_comment(self):
return ''
def render_to_file(self, path, table):
txt = self.render(table)
with open(path, 'w') as fp:
fp.write(txt)
class TerminalRenderer(Renderer):
def __init__(self, col_sep=' '):
super().__init__()
self.col_sep = col_sep
def render_cell(self, table, row, col, widths):
cell = table.rows[row].cells[col]
str = cell.fmt.fmt % cell.data
str_width = len(str)
cell_width = sum([widths[idx] for idx in range(col, col+cell.span)])
cell_width += len(self.col_sep) * (cell.span - 1)
if len(str) > cell_width:
str = str[:cell_width]
if cell.fmt.bold:
# str = sty.ef.bold + str + sty.rs.bold_dim
# str = sty.ef.bold + str + sty.rs.bold
pass
if cell.fmt.fgcolor is not None:
# color = cell.fmt.fgcolor.as_RGB()
# str = sty.fg(*color) + str + sty.rs.fg
pass
if str_width < cell_width:
n_ws = (cell_width - str_width)
if table.get_cell_align(row, col) == 'r':
str = ' '*n_ws + str
elif table.get_cell_align(row, col) == 'l':
str = str + ' '*n_ws
elif table.get_cell_align(row, col) == 'c':
n_ws1 = n_ws // 2
n_ws0 = n_ws - n_ws1
str = ' '*n_ws0 + str + ' '*n_ws1
if cell.fmt.bgcolor is not None:
# color = cell.fmt.bgcolor.as_RGB()
# str = sty.bg(*color) + str + sty.rs.bg
pass
return str
def render_separator(self, separator, tab, col_widths, total_width):
if separator == Separator.HEAD:
return '='*total_width
elif separator == Separator.INNER:
return '-'*total_width
elif separator == Separator.BOTTOM:
return '='*total_width
def render(self, table):
widths = self.col_widths(table)
total_width = sum(widths) + len(self.col_sep) * (table.n_cols - 1)
lines = []
for ridx, row in enumerate(table.rows):
if row.pre_separator is not None:
sepline = self.render_separator(row.pre_separator, table, widths, total_width)
if len(sepline) > 0:
lines.append(sepline)
line = []
for cidx, cell in enumerate(row.cells):
line.append(self.render_cell(table, ridx, cidx, widths))
lines.append(self.col_sep.join(line))
if row.post_separator is not None:
sepline = self.render_separator(row.post_separator, table, widths, total_width)
if len(sepline) > 0:
lines.append(sepline)
return '\n'.join(lines)
class MarkdownRenderer(TerminalRenderer):
def __init__(self):
super().__init__(col_sep='|')
self.printed_color_warning = False
def print_color_warning(self):
if not self.printed_color_warning:
print('[WARNING] MarkdownRenderer does not support color yet')
self.printed_color_warning = True
def cell_str_len(self, cell):
strlen = len(str(cell))
if cell.fmt.bold:
strlen += 4
strlen = max(5, strlen)
return strlen
def render_cell(self, table, row, col, widths):
cell = table.rows[row].cells[col]
str = cell.fmt.fmt % cell.data
if cell.fmt.bold:
str = f'**{str}**'
str_width = len(str)
cell_width = sum([widths[idx] for idx in range(col, col+cell.span)])
cell_width += len(self.col_sep) * (cell.span - 1)
if len(str) > cell_width:
str = str[:cell_width]
else:
n_ws = (cell_width - str_width)
if table.get_cell_align(row, col) == 'r':
str = ' '*n_ws + str
elif table.get_cell_align(row, col) == 'l':
str = str + ' '*n_ws
elif table.get_cell_align(row, col) == 'c':
n_ws1 = n_ws // 2
n_ws0 = n_ws - n_ws1
str = ' '*n_ws0 + str + ' '*n_ws1
if col == 0: str = self.col_sep + str
if col == table.n_cols - 1: str += self.col_sep
if cell.fmt.fgcolor is not None:
self.print_color_warning()
if cell.fmt.bgcolor is not None:
self.print_color_warning()
return str
def render_separator(self, separator, tab, widths, total_width):
sep = ''
if separator == Separator.INNER:
sep = self.col_sep
for idx, width in enumerate(widths):
csep = '-' * (width - 2)
if tab.get_cell_align(1, idx) == 'r':
csep = '-' + csep + ':'
elif tab.get_cell_align(1, idx) == 'l':
csep = ':' + csep + '-'
elif tab.get_cell_align(1, idx) == 'c':
csep = ':' + csep + ':'
sep += csep + self.col_sep
return sep
class LatexRenderer(Renderer):
def __init__(self):
super().__init__()
def render_cell(self, table, row, col):
cell = table.rows[row].cells[col]
str = cell.fmt.fmt % cell.data
if cell.fmt.bold:
str = '{\\bf '+ str + '}'
if cell.fmt.fgcolor is not None:
color = cell.fmt.fgcolor.as_rgb()
str = f'{{\\color[rgb]{{{color[0]},{color[1]},{color[2]}}} ' + str + '}'
if cell.fmt.bgcolor is not None:
color = cell.fmt.bgcolor.as_rgb()
str = f'\\cellcolor[rgb]{{{color[0]},{color[1]},{color[2]}}} ' + str
align = table.get_cell_align(row, col)
if cell.span != 1 or align != table.aligns[col]:
str = f'\\multicolumn{{{cell.span}}}{{{align}}}{{{str}}}'
return str
def render_separator(self, separator):
if separator == Separator.HEAD:
return '\\toprule'
elif separator == Separator.INNER:
return '\\midrule'
elif separator == Separator.BOTTOM:
return '\\bottomrule'
def render(self, table):
lines = ['\\begin{tabular}{' + ''.join(table.aligns) + '}']
for ridx, row in enumerate(table.rows):
if row.pre_separator is not None:
lines.append(self.render_separator(row.pre_separator))
line = []
for cidx, cell in enumerate(row.cells):
line.append(self.render_cell(table, ridx, cidx))
lines.append(' & '.join(line) + ' \\\\')
if row.post_separator is not None:
lines.append(self.render_separator(row.post_separator))
lines.append('\\end{tabular}')
return '\n'.join(lines)
class HtmlRenderer(Renderer):
def __init__(self, html_class='result_table'):
super().__init__()
self.html_class = html_class
def render_cell(self, table, row, col):
cell = table.rows[row].cells[col]
str = cell.fmt.fmt % cell.data
styles = []
if cell.fmt.bold:
styles.append('font-weight: bold;')
if cell.fmt.fgcolor is not None:
color = cell.fmt.fgcolor.as_RGB()
styles.append(f'color: rgb({color[0]},{color[1]},{color[2]});')
if cell.fmt.bgcolor is not None:
color = cell.fmt.bgcolor.as_RGB()
styles.append(f'background-color: rgb({color[0]},{color[1]},{color[2]});')
align = table.get_cell_align(row, col)
if align == 'l': align = 'left'
elif align == 'r': align = 'right'
elif align == 'c': align = 'center'
else: raise Exception('invalid align')
styles.append(f'text-align: {align};')
row = table.rows[row]
if row.pre_separator is not None:
styles.append(f'border-top: {self.render_separator(row.pre_separator)};')
if row.post_separator is not None:
styles.append(f'border-bottom: {self.render_separator(row.post_separator)};')
style = ' '.join(styles)
str = f' <td style="{style}" colspan="{cell.span}">{str}</td>\n'
return str
def render_separator(self, separator):
if separator == Separator.HEAD:
return '1.5pt solid black'
elif separator == Separator.INNER:
return '0.75pt solid black'
elif separator == Separator.BOTTOM:
return '1.5pt solid black'
def render(self, table):
lines = [f'<table width="100%" style="border-collapse: collapse" class={self.html_class}>']
for ridx, row in enumerate(table.rows):
line = [f' <tr>\n']
for cidx, cell in enumerate(row.cells):
line.append(self.render_cell(table, ridx, cidx))
line.append(' </tr>\n')
lines.append(' '.join(line))
lines.append('</table>')
return '\n'.join(lines)
def pandas_to_table(rowname, colname, valname, data, val_cell_fmt=CellFormat(fmt='%.4f'), best_val_cell_fmt=CellFormat(fmt='%.4f', bold=True), best_is_max=[]):
rnames = data[rowname].unique()
cnames = data[colname].unique()
tab = Table(1+len(cnames))
header = [Cell('', align='r')]
header.extend([Cell(h, align='r') for h in cnames])
header = Row(header, pre_separator=Separator.HEAD, post_separator=Separator.INNER)
tab.add_row(header)
for rname in rnames:
cells = [Cell(rname, align='l')]
for cname in cnames:
cdata = data[data[colname] == cname]
if cname in best_is_max:
bestval = cdata[valname].max()
val = cdata[cdata[rowname] == rname][valname].max()
else:
bestval = cdata[valname].min()
val = cdata[cdata[rowname] == rname][valname].min()
if val == bestval:
fmt = best_val_cell_fmt
else:
fmt = val_cell_fmt
cells.append(Cell(val, align='r', fmt=fmt))
tab.add_row(Row(cells))
tab.rows[-1].post_separator = Separator.BOTTOM
return tab
if __name__ == '__main__':
# df = pd.read_pickle('full.df')
# best_is_max = ['movF0.5', 'movF1.0']
# tab = pandas_to_table(rowname='method', colname='metric', valname='val', data=df, best_is_max=best_is_max)
# renderer = TerminalRenderer()
# print(renderer(tab))
tab = Table(7)
# header = Row([Cell('header', span=7, align='c')], pre_separator=Separator.HEAD, post_separator=Separator.INNER)
# tab.add_row(header)
# header2 = Row([Cell('thisisaverylongheader', span=4, align='c'), Cell('vals2', span=3, align='c')], post_separator=Separator.INNER)
# tab.add_row(header2)
tab.add_row(Row([Cell(f'c{c}') for c in range(7)]))
tab.rows[-1].post_separator = Separator.INNER
tab.add_block(np.arange(15*7).reshape(15,7))
tab.rows[4].cells[2].fmt = CellFormat(bold=True)
tab.rows[2].cells[1].fmt = CellFormat(fgcolor=Color.rgb(0.2,0.6,0.1))
tab.rows[2].cells[2].fmt = CellFormat(bgcolor=Color.rgb(0.7,0.1,0.5))
tab.rows[5].cells[3].fmt = CellFormat(bold=True,bgcolor=Color.rgb(0.7,0.1,0.5),fgcolor=Color.rgb(0.1,0.1,0.1))
tab.rows[-1].post_separator = Separator.BOTTOM
renderer = TerminalRenderer()
print(renderer(tab))
renderer = MarkdownRenderer()
print(renderer(tab))
# renderer = HtmlRenderer()
# html_tab = renderer(tab)
# print(html_tab)
# with open('test.html', 'w') as fp:
# fp.write(html_tab)
# import latex
# renderer = LatexRenderer()
# ltx_tab = renderer(tab)
# print(ltx_tab)
# with open('test.tex', 'w') as fp:
# latex.write_doc_prefix(fp, document_class='article')
# fp.write('this is text that should appear before the table and should be long enough to wrap around.\n'*40)
# fp.write('\\begin{table}')
# fp.write(ltx_tab)
# fp.write('\\end{table}')
# fp.write('this is text that should appear after the table and should be long enough to wrap around.\n'*40)
# latex.write_doc_suffix(fp)
+86
View File
@@ -0,0 +1,86 @@
import numpy as np
import pandas as pd
import time
from collections import OrderedDict
import argparse
import os
import re
import pickle
import subprocess
def str2bool(v):
if v.lower() in ('yes', 'true', 't', 'y', '1'):
return True
elif v.lower() in ('no', 'false', 'f', 'n', '0'):
return False
else:
raise argparse.ArgumentTypeError('Boolean value expected.')
class StopWatch(object):
def __init__(self):
self.timings = OrderedDict()
self.starts = {}
def start(self, name):
self.starts[name] = time.time()
def stop(self, name):
if name not in self.timings:
self.timings[name] = []
self.timings[name].append(time.time() - self.starts[name])
def get(self, name=None, reduce=np.sum):
if name is not None:
return reduce(self.timings[name])
else:
ret = {}
for k in self.timings:
ret[k] = reduce(self.timings[k])
return ret
def __repr__(self):
return ', '.join(['%s: %f[s]' % (k,v) for k,v in self.get().items()])
def __str__(self):
return ', '.join(['%s: %f[s]' % (k,v) for k,v in self.get().items()])
class ETA(object):
def __init__(self, length):
self.length = length
self.start_time = time.time()
self.current_idx = 0
self.current_time = time.time()
def update(self, idx):
self.current_idx = idx
self.current_time = time.time()
def get_elapsed_time(self):
return self.current_time - self.start_time
def get_item_time(self):
return self.get_elapsed_time() / (self.current_idx + 1)
def get_remaining_time(self):
return self.get_item_time() * (self.length - self.current_idx + 1)
def format_time(self, seconds):
minutes, seconds = divmod(seconds, 60)
hours, minutes = divmod(minutes, 60)
hours = int(hours)
minutes = int(minutes)
return f'{hours:02d}:{minutes:02d}:{seconds:05.2f}'
def get_elapsed_time_str(self):
return self.format_time(self.get_elapsed_time())
def get_remaining_time_str(self):
return self.format_time(self.get_remaining_time())
def git_hash(cwd=None):
ret = subprocess.run(['git', 'describe', '--always'], cwd=cwd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
hash = ret.stdout
if hash is not None and 'fatal' not in hash.decode():
return hash.decode().strip()
else:
return None