q3dviewer 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- q3dviewer/__init__.py +4 -0
- q3dviewer/basic_viewer.py +56 -0
- q3dviewer/custom_items/__init__.py +9 -0
- q3dviewer/custom_items/axis_item.py +73 -0
- q3dviewer/custom_items/camera_frame_item.py +173 -0
- q3dviewer/custom_items/cloud_io_item.py +83 -0
- q3dviewer/custom_items/cloud_item.py +297 -0
- q3dviewer/custom_items/gaussian_item.py +267 -0
- q3dviewer/custom_items/grid_item.py +37 -0
- q3dviewer/custom_items/image_item.py +169 -0
- q3dviewer/custom_items/text_item.py +53 -0
- q3dviewer/custom_items/trajectory_item.py +78 -0
- q3dviewer/gau_io.py +168 -0
- q3dviewer/utils.py +146 -0
- q3dviewer/viewer_widget.py +183 -0
- q3dviewer-1.0.0.dist-info/METADATA +10 -0
- q3dviewer-1.0.0.dist-info/RECORD +20 -0
- q3dviewer-1.0.0.dist-info/WHEEL +5 -0
- q3dviewer-1.0.0.dist-info/entry_points.txt +7 -0
- q3dviewer-1.0.0.dist-info/top_level.txt +1 -0
q3dviewer/gau_io.py
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from plyfile import PlyData
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def gsdata_type(sh_dim):
|
|
6
|
+
return [('pw', '<f4', (3,)),
|
|
7
|
+
('rot', '<f4', (4,)),
|
|
8
|
+
('scale', '<f4', (3,)),
|
|
9
|
+
('alpha', '<f4'),
|
|
10
|
+
('sh', '<f4', (sh_dim))]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def matrix_to_quaternion_wxyz(matrices):
|
|
14
|
+
m00, m01, m02 = matrices[:, 0, 0], matrices[:, 0, 1], matrices[:, 0, 2]
|
|
15
|
+
m10, m11, m12 = matrices[:, 1, 0], matrices[:, 1, 1], matrices[:, 1, 2]
|
|
16
|
+
m20, m21, m22 = matrices[:, 2, 0], matrices[:, 2, 1], matrices[:, 2, 2]
|
|
17
|
+
t = 1 + m00 + m11 + m22
|
|
18
|
+
s = np.ones_like(m00)
|
|
19
|
+
w = np.ones_like(m00)
|
|
20
|
+
x = np.ones_like(m00)
|
|
21
|
+
y = np.ones_like(m00)
|
|
22
|
+
z = np.ones_like(m00)
|
|
23
|
+
|
|
24
|
+
t_positive = t > 0.0000001
|
|
25
|
+
s[t_positive] = 0.5 / np.sqrt(t[t_positive])
|
|
26
|
+
w[t_positive] = 0.25 / s[t_positive]
|
|
27
|
+
x[t_positive] = (m21[t_positive] - m12[t_positive]) * s[t_positive]
|
|
28
|
+
y[t_positive] = (m02[t_positive] - m20[t_positive]) * s[t_positive]
|
|
29
|
+
z[t_positive] = (m10[t_positive] - m01[t_positive]) * s[t_positive]
|
|
30
|
+
|
|
31
|
+
c1 = np.logical_and(m00 > m11, m00 > m22)
|
|
32
|
+
cond1 = np.logical_and(np.logical_not(t_positive), np.logical_and(m00 > m11, m00 > m22))
|
|
33
|
+
|
|
34
|
+
s[cond1] = 2.0 * np.sqrt(1.0 + m00[cond1] - m11[cond1] - m22[cond1])
|
|
35
|
+
w[cond1] = (m21[cond1] - m12[cond1]) / s[cond1]
|
|
36
|
+
x[cond1] = 0.25 * s[cond1]
|
|
37
|
+
y[cond1] = (m01[cond1] + m10[cond1]) / s[cond1]
|
|
38
|
+
z[cond1] = (m02[cond1] + m20[cond1]) / s[cond1]
|
|
39
|
+
|
|
40
|
+
c2 = np.logical_and(np.logical_not(c1), m11 > m22)
|
|
41
|
+
cond2 = np.logical_and(np.logical_not(t_positive), c2)
|
|
42
|
+
s[cond2] = 2.0 * np.sqrt(1.0 + m11[cond2] - m00[cond2] - m22[cond2])
|
|
43
|
+
w[cond2] = (m02[cond2] - m20[cond2]) / s[cond2]
|
|
44
|
+
x[cond2] = (m01[cond2] + m10[cond2]) / s[cond2]
|
|
45
|
+
y[cond2] = 0.25 * s[cond2]
|
|
46
|
+
z[cond2] = (m12[cond2] + m21[cond2]) / s[cond2]
|
|
47
|
+
|
|
48
|
+
c3 = np.logical_and(np.logical_not(c1), np.logical_not(c2))
|
|
49
|
+
cond3 = np.logical_and(np.logical_not(t_positive), c3)
|
|
50
|
+
s[cond3] = 2.0 * np.sqrt(1.0 + m22[cond3] - m00[cond3] - m11[cond3])
|
|
51
|
+
w[cond3] = (m10[cond3] - m01[cond3]) / s[cond3]
|
|
52
|
+
x[cond3] = (m02[cond3] + m20[cond3]) / s[cond3]
|
|
53
|
+
y[cond3] = (m12[cond3] + m21[cond3]) / s[cond3]
|
|
54
|
+
z[cond3] = 0.25 * s[cond3]
|
|
55
|
+
return np.array([w, x, y, z]).T
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def load_ply(path, T=None):
|
|
59
|
+
plydata = PlyData.read(path)
|
|
60
|
+
pws = np.stack((np.asarray(plydata.elements[0]["x"]),
|
|
61
|
+
np.asarray(plydata.elements[0]["y"]),
|
|
62
|
+
np.asarray(plydata.elements[0]["z"])), axis=1)
|
|
63
|
+
|
|
64
|
+
alphas = np.asarray(plydata.elements[0]["opacity"])
|
|
65
|
+
alphas = 1/(1 + np.exp(-alphas))
|
|
66
|
+
|
|
67
|
+
scales = np.stack((np.asarray(plydata.elements[0]["scale_0"]),
|
|
68
|
+
np.asarray(plydata.elements[0]["scale_1"]),
|
|
69
|
+
np.asarray(plydata.elements[0]["scale_2"])), axis=1)
|
|
70
|
+
|
|
71
|
+
rots = np.stack((np.asarray(plydata.elements[0]["rot_0"]),
|
|
72
|
+
np.asarray(plydata.elements[0]["rot_1"]),
|
|
73
|
+
np.asarray(plydata.elements[0]["rot_2"]),
|
|
74
|
+
np.asarray(plydata.elements[0]["rot_3"])), axis=1)
|
|
75
|
+
|
|
76
|
+
rots /= np.linalg.norm(rots, axis=1)[:, np.newaxis]
|
|
77
|
+
|
|
78
|
+
sh_dim = len(plydata.elements[0][0])-14
|
|
79
|
+
shs = np.zeros([pws.shape[0], sh_dim])
|
|
80
|
+
shs[:, 0] = np.asarray(plydata.elements[0]["f_dc_0"])
|
|
81
|
+
shs[:, 1] = np.asarray(plydata.elements[0]["f_dc_1"])
|
|
82
|
+
shs[:, 2] = np.asarray(plydata.elements[0]["f_dc_2"])
|
|
83
|
+
|
|
84
|
+
sh_rest_dim = sh_dim - 3
|
|
85
|
+
for i in range(sh_rest_dim):
|
|
86
|
+
name = "f_rest_%d" % i
|
|
87
|
+
shs[:, 3 + i] = np.asarray(plydata.elements[0][name])
|
|
88
|
+
|
|
89
|
+
shs[:, 3:] = shs[:, 3:].reshape(-1, 3, sh_rest_dim//3).transpose([0, 2, 1]).reshape(-1, sh_rest_dim)
|
|
90
|
+
|
|
91
|
+
pws = pws.astype(np.float32)
|
|
92
|
+
rots = rots.astype(np.float32)
|
|
93
|
+
scales = np.exp(scales)
|
|
94
|
+
scales = scales.astype(np.float32)
|
|
95
|
+
alphas = alphas.astype(np.float32)
|
|
96
|
+
shs = shs.astype(np.float32)
|
|
97
|
+
|
|
98
|
+
dtypes = gsdata_type(sh_dim)
|
|
99
|
+
|
|
100
|
+
gs = np.rec.fromarrays(
|
|
101
|
+
[pws, rots, scales, alphas, shs], dtype=dtypes)
|
|
102
|
+
|
|
103
|
+
return gs
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def rotate_gaussian(T, gs):
|
|
107
|
+
# Transform to world
|
|
108
|
+
pws = (T @ gs['pw'].T).T
|
|
109
|
+
w = gs['rot'][:, 0]
|
|
110
|
+
x = gs['rot'][:, 1]
|
|
111
|
+
y = gs['rot'][:, 2]
|
|
112
|
+
z = gs['rot'][:, 3]
|
|
113
|
+
R = np.array([
|
|
114
|
+
[1.0 - 2*(y**2 + z**2), 2*(x*y - z*w), 2*(x * z + y * w)],
|
|
115
|
+
[2*(x*y + z*w), 1.0 - 2*(x**2 + z**2), 2*(y*z - x*w)],
|
|
116
|
+
[2*(x*z - y*w), 2*(y*z + x*w), 1.0 - 2*(x**2 + y**2)]
|
|
117
|
+
]).transpose(2, 0, 1)
|
|
118
|
+
R_new = T @ R
|
|
119
|
+
rots = matrix_to_quaternion_wxyz(R_new)
|
|
120
|
+
gs['pw'] = pws
|
|
121
|
+
gs['rot'] = rots
|
|
122
|
+
return gs
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def load_gs(fn):
|
|
126
|
+
if fn.endswith('.ply'):
|
|
127
|
+
return load_ply(fn)
|
|
128
|
+
elif fn.endswith('.npy'):
|
|
129
|
+
return np.load(fn)
|
|
130
|
+
else:
|
|
131
|
+
print("%s is not a supported file." % fn)
|
|
132
|
+
exit(0)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def save_gs(fn, gs):
|
|
136
|
+
np.save(fn, gs)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def get_example_gs():
|
|
140
|
+
gs_data = np.array([[0., 0., 0., # xyz
|
|
141
|
+
1., 0., 0., 0., # rot
|
|
142
|
+
0.05, 0.05, 0.05, # size
|
|
143
|
+
1.,
|
|
144
|
+
1.772484, -1.772484, 1.772484],
|
|
145
|
+
[1., 0., 0.,
|
|
146
|
+
1., 0., 0., 0.,
|
|
147
|
+
0.2, 0.05, 0.05,
|
|
148
|
+
1.,
|
|
149
|
+
1.772484, -1.772484, -1.772484],
|
|
150
|
+
[0., 1., 0.,
|
|
151
|
+
1., 0., 0., 0.,
|
|
152
|
+
0.05, 0.2, 0.05,
|
|
153
|
+
1.,
|
|
154
|
+
-1.772484, 1.772484, -1.772484],
|
|
155
|
+
[0., 0., 1.,
|
|
156
|
+
1., 0., 0., 0.,
|
|
157
|
+
0.05, 0.05, 0.2,
|
|
158
|
+
1.,
|
|
159
|
+
-1.772484, -1.772484, 1.772484]
|
|
160
|
+
], dtype=np.float32)
|
|
161
|
+
dtypes = gsdata_type(3)
|
|
162
|
+
gs = np.frombuffer(gs_data.tobytes(), dtype=dtypes)
|
|
163
|
+
return gs
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
if __name__ == "__main__":
|
|
167
|
+
gs = load_gs("/home/liu/workspace/EasyGaussianSplatting/data/final.npy")
|
|
168
|
+
print(gs.shape)
|
q3dviewer/utils.py
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import time
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def rainbow(scalars, scalar_min=0, scalar_max=255):
|
|
6
|
+
range = scalar_max - scalar_min
|
|
7
|
+
values = 1.0 - (scalars - scalar_min) / range
|
|
8
|
+
# values = (scalars - scalar_min) / range # using inverted color
|
|
9
|
+
colors = np.zeros([scalars.shape[0], 3], dtype=np.float32)
|
|
10
|
+
values = np.clip(values, 0, 1)
|
|
11
|
+
|
|
12
|
+
h = values * 5.0 + 1.0
|
|
13
|
+
i = np.floor(h).astype(int)
|
|
14
|
+
f = h - i
|
|
15
|
+
f[np.logical_not(i % 2)] = 1 - f[np.logical_not(i % 2)]
|
|
16
|
+
n = 1 - f
|
|
17
|
+
|
|
18
|
+
# idx = i <= 1
|
|
19
|
+
colors[i <= 1, 0] = n[i <= 1] * 255
|
|
20
|
+
colors[i <= 1, 1] = 0
|
|
21
|
+
colors[i <= 1, 2] = 255
|
|
22
|
+
|
|
23
|
+
colors[i == 2, 0] = 0
|
|
24
|
+
colors[i == 2, 1] = n[i == 2] * 255
|
|
25
|
+
colors[i == 2, 2] = 255
|
|
26
|
+
|
|
27
|
+
colors[i == 3, 0] = 0
|
|
28
|
+
colors[i == 3, 1] = 255
|
|
29
|
+
colors[i == 3, 2] = n[i == 3] * 255
|
|
30
|
+
|
|
31
|
+
colors[i == 4, 0] = n[i == 4] * 255
|
|
32
|
+
colors[i == 4, 1] = 255
|
|
33
|
+
colors[i == 4, 2] = 0
|
|
34
|
+
|
|
35
|
+
colors[i >= 5, 0] = 255
|
|
36
|
+
colors[i >= 5, 1] = n[i >= 5] * 255
|
|
37
|
+
colors[i >= 5, 2] = 0
|
|
38
|
+
return colors
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def euler_to_matrix(rpy):
|
|
42
|
+
roll, pitch, yaw = rpy
|
|
43
|
+
Rx = np.array([[1, 0, 0],
|
|
44
|
+
[0, np.cos(roll), -np.sin(roll)],
|
|
45
|
+
[0, np.sin(roll), np.cos(roll)]])
|
|
46
|
+
Ry = np.array([[np.cos(pitch), 0, np.sin(pitch)],
|
|
47
|
+
[0, 1, 0],
|
|
48
|
+
[-np.sin(pitch), 0, np.cos(pitch)]])
|
|
49
|
+
Rz = np.array([[np.cos(yaw), -np.sin(yaw), 0],
|
|
50
|
+
[np.sin(yaw), np.cos(yaw), 0],
|
|
51
|
+
[0, 0, 1]])
|
|
52
|
+
R = Rz @ Ry @ Rx
|
|
53
|
+
return R
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def matrix_to_euler(R):
|
|
57
|
+
sy = np.sqrt(R[0, 0]**2 + R[1, 0]**2)
|
|
58
|
+
singular = sy < 1e-6 # Check for gimbal lock
|
|
59
|
+
if not singular:
|
|
60
|
+
roll = np.arctan2(R[2, 1], R[2, 2]) # X-axis rotation
|
|
61
|
+
pitch = np.arctan2(-R[2, 0], sy) # Y-axis rotation
|
|
62
|
+
yaw = np.arctan2(R[1, 0], R[0, 0]) # Z-axis rotation
|
|
63
|
+
else:
|
|
64
|
+
# Gimbal lock case
|
|
65
|
+
roll = np.arctan2(-R[1, 2], R[1, 1])
|
|
66
|
+
pitch = np.arctan2(-R[2, 0], sy)
|
|
67
|
+
yaw = 0 # Arbitrarily set yaw to 0
|
|
68
|
+
|
|
69
|
+
return np.array([roll, pitch, yaw])
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def matrix_to_quaternion(matrix):
|
|
73
|
+
trace = matrix[0, 0] + matrix[1, 1] + matrix[2, 2]
|
|
74
|
+
if trace > 0:
|
|
75
|
+
s = 0.5 / np.sqrt(trace + 1.0)
|
|
76
|
+
w = 0.25 / s
|
|
77
|
+
x = (matrix[2, 1] - matrix[1, 2]) * s
|
|
78
|
+
y = (matrix[0, 2] - matrix[2, 0]) * s
|
|
79
|
+
z = (matrix[1, 0] - matrix[0, 1]) * s
|
|
80
|
+
else:
|
|
81
|
+
if matrix[0, 0] > matrix[1, 1] and matrix[0, 0] > matrix[2, 2]:
|
|
82
|
+
s = 2.0 * np.sqrt(1.0 + matrix[0, 0] - matrix[1, 1] - matrix[2, 2])
|
|
83
|
+
w = (matrix[2, 1] - matrix[1, 2]) / s
|
|
84
|
+
x = 0.25 * s
|
|
85
|
+
y = (matrix[0, 1] + matrix[1, 0]) / s
|
|
86
|
+
z = (matrix[0, 2] + matrix[2, 0]) / s
|
|
87
|
+
elif matrix[1, 1] > matrix[2, 2]:
|
|
88
|
+
s = 2.0 * np.sqrt(1.0 + matrix[1, 1] - matrix[0, 0] - matrix[2, 2])
|
|
89
|
+
w = (matrix[0, 2] - matrix[2, 0]) / s
|
|
90
|
+
x = (matrix[0, 1] + matrix[1, 0]) / s
|
|
91
|
+
y = 0.25 * s
|
|
92
|
+
z = (matrix[1, 2] + matrix[2, 1]) / s
|
|
93
|
+
else:
|
|
94
|
+
s = 2.0 * np.sqrt(1.0 + matrix[2, 2] - matrix[0, 0] - matrix[1, 1])
|
|
95
|
+
w = (matrix[1, 0] - matrix[0, 1]) / s
|
|
96
|
+
x = (matrix[0, 2] + matrix[2, 0]) / s
|
|
97
|
+
y = (matrix[1, 2] + matrix[2, 1]) / s
|
|
98
|
+
z = 0.25 * s
|
|
99
|
+
return np.array([x, y, z, w])
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def quaternion_to_matrix(quaternion):
|
|
103
|
+
x, y, z, w = quaternion
|
|
104
|
+
q = np.array(quaternion[:4], dtype=np.float64, copy=True)
|
|
105
|
+
n = np.linalg.norm(q)
|
|
106
|
+
if np.any(n == 0.0):
|
|
107
|
+
raise ZeroDivisionError("bad quaternion input")
|
|
108
|
+
else:
|
|
109
|
+
m = np.empty((3, 3))
|
|
110
|
+
m[0, 0] = 1.0 - 2*(y**2 + z**2)/n
|
|
111
|
+
m[0, 1] = 2*(x*y - z*w)/n
|
|
112
|
+
m[0, 2] = 2*(x*z + y*w)/n
|
|
113
|
+
m[1, 0] = 2*(x*y + z*w)/n
|
|
114
|
+
m[1, 1] = 1.0 - 2*(x**2 + z**2)/n
|
|
115
|
+
m[1, 2] = 2*(y*z - x*w)/n
|
|
116
|
+
m[2, 0] = 2*(x*z - y*w)/n
|
|
117
|
+
m[2, 1] = 2*(y*z + x*w)/n
|
|
118
|
+
m[2, 2] = 1.0 - 2*(x**2 + y**2)/n
|
|
119
|
+
return m
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def make_transform(pose, rotation):
|
|
123
|
+
transform = np.eye(4)
|
|
124
|
+
transform[0:3, 0:3] = quaternion_to_matrix(rotation)
|
|
125
|
+
transform[0:3, 3] = pose
|
|
126
|
+
return transform
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class FPSMonitor():
|
|
130
|
+
def __init__(self):
|
|
131
|
+
self.stamp_record = []
|
|
132
|
+
|
|
133
|
+
def count(self):
|
|
134
|
+
cur_stamp = time.time()
|
|
135
|
+
self.stamp_record.append(cur_stamp)
|
|
136
|
+
while len(self.stamp_record) > 0:
|
|
137
|
+
if(cur_stamp - self.stamp_record[0] > 1.):
|
|
138
|
+
self.stamp_record.pop(0)
|
|
139
|
+
else:
|
|
140
|
+
break
|
|
141
|
+
return len(self.stamp_record)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
# euler = np.array([1, 0.1, 0.1])
|
|
145
|
+
# euler_angles = matrix_to_euler(euler_to_matrix(euler))
|
|
146
|
+
# print("Euler Angles:", euler_angles)
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
import pyqtgraph.opengl as gl
|
|
5
|
+
from pyqtgraph.Qt import QtCore
|
|
6
|
+
from PyQt5.QtWidgets import QWidget, QComboBox, QVBoxLayout, QHBoxLayout, \
|
|
7
|
+
QSizePolicy, QSpacerItem, QMainWindow
|
|
8
|
+
from OpenGL.GL import *
|
|
9
|
+
from PyQt5.QtGui import QKeyEvent, QVector3D
|
|
10
|
+
from PyQt5.QtWidgets import QApplication, QWidget
|
|
11
|
+
import numpy as np
|
|
12
|
+
from PyQt5.QtWidgets import QLabel, QLineEdit, \
|
|
13
|
+
QDoubleSpinBox, QSpinBox, QCheckBox
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class SettingWindow(QWidget):
|
|
17
|
+
def __init__(self):
|
|
18
|
+
super().__init__()
|
|
19
|
+
self.combo_items = QComboBox()
|
|
20
|
+
self.combo_items.currentIndexChanged.connect(self.onComboboxSelection)
|
|
21
|
+
main_layout = QVBoxLayout()
|
|
22
|
+
self.stretch = QSpacerItem(
|
|
23
|
+
10, 10, QSizePolicy.Minimum, QSizePolicy.Expanding)
|
|
24
|
+
main_layout.addWidget(self.combo_items)
|
|
25
|
+
self.layout = QVBoxLayout()
|
|
26
|
+
self.layout.addItem(self.stretch)
|
|
27
|
+
main_layout.addLayout(self.layout)
|
|
28
|
+
self.setLayout(main_layout)
|
|
29
|
+
self.setWindowTitle("Setting Window")
|
|
30
|
+
self.setGeometry(200, 200, 300, 200)
|
|
31
|
+
self.items = {}
|
|
32
|
+
|
|
33
|
+
def addSetting(self, name, item):
|
|
34
|
+
self.items.update({name: item})
|
|
35
|
+
self.combo_items.addItem("%s(%s)" % (name, item.__class__.__name__))
|
|
36
|
+
|
|
37
|
+
def clearSetting(self):
|
|
38
|
+
while self.layout.count():
|
|
39
|
+
child = self.layout.takeAt(0)
|
|
40
|
+
if child.widget():
|
|
41
|
+
child.widget().deleteLater()
|
|
42
|
+
|
|
43
|
+
def onComboboxSelection(self, index):
|
|
44
|
+
self.layout.removeItem(self.stretch)
|
|
45
|
+
# remove all setting of previous widget
|
|
46
|
+
self.clearSetting()
|
|
47
|
+
|
|
48
|
+
key = list(self.items.keys())
|
|
49
|
+
try:
|
|
50
|
+
item = self.items[key[index]]
|
|
51
|
+
item.addSetting(self.layout)
|
|
52
|
+
self.layout.addItem(self.stretch)
|
|
53
|
+
except AttributeError:
|
|
54
|
+
print("%s: No setting." % (item.__class__.__name__))
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class ViewWidget(gl.GLViewWidget):
|
|
58
|
+
def __init__(self):
|
|
59
|
+
self.followed_name = 'none'
|
|
60
|
+
self.named_items = {}
|
|
61
|
+
self.color = '#000000'
|
|
62
|
+
self.followable_item_name = ['none']
|
|
63
|
+
self.setting_window = SettingWindow()
|
|
64
|
+
super(ViewWidget, self).__init__()
|
|
65
|
+
|
|
66
|
+
def onFollowableSelection(self, index):
|
|
67
|
+
self.followed_name = self.followable_item_name[index]
|
|
68
|
+
|
|
69
|
+
def update(self):
|
|
70
|
+
if self.followed_name != 'none':
|
|
71
|
+
pos = self.named_items[self.followed_name].T[:3, 3]
|
|
72
|
+
self.opts['center'] = QVector3D(pos[0], pos[1], pos[2])
|
|
73
|
+
super().update()
|
|
74
|
+
|
|
75
|
+
def addSetting(self, layout):
|
|
76
|
+
label1 = QLabel("Set background color:")
|
|
77
|
+
label1.setToolTip("using '#xxxxxx', i.e. #FF4500")
|
|
78
|
+
box1 = QLineEdit()
|
|
79
|
+
box1.setToolTip("'using '#xxxxxx', i.e. #FF4500")
|
|
80
|
+
box1.setText(str(self.color))
|
|
81
|
+
box1.textChanged.connect(self.setBKColor)
|
|
82
|
+
layout.addWidget(label1)
|
|
83
|
+
layout.addWidget(box1)
|
|
84
|
+
label2 = QLabel("Set Focus:")
|
|
85
|
+
combo2 = QComboBox()
|
|
86
|
+
for name in self.followable_item_name:
|
|
87
|
+
combo2.addItem(name)
|
|
88
|
+
combo2.currentIndexChanged.connect(self.onFollowableSelection)
|
|
89
|
+
layout.addWidget(label2)
|
|
90
|
+
layout.addWidget(combo2)
|
|
91
|
+
|
|
92
|
+
def setBKColor(self, color):
|
|
93
|
+
if (type(color) != str):
|
|
94
|
+
return
|
|
95
|
+
if color.startswith("#"):
|
|
96
|
+
try:
|
|
97
|
+
self.setBackgroundColor(color)
|
|
98
|
+
self.color = color
|
|
99
|
+
except ValueError:
|
|
100
|
+
return
|
|
101
|
+
|
|
102
|
+
def addItem(self, name, item):
|
|
103
|
+
self.named_items.update({name: item})
|
|
104
|
+
if (item.__class__.__name__ == 'GLAxisItem'):
|
|
105
|
+
self.followable_item_name.append(name)
|
|
106
|
+
self.setting_window.addSetting(name, item)
|
|
107
|
+
super().addItem(item)
|
|
108
|
+
|
|
109
|
+
def mouseReleaseEvent(self, ev):
|
|
110
|
+
if hasattr(self, 'mousePos'):
|
|
111
|
+
delattr(self, 'mousePos')
|
|
112
|
+
|
|
113
|
+
def mouseMoveEvent(self, ev):
|
|
114
|
+
lpos = ev.localPos()
|
|
115
|
+
if not hasattr(self, 'mousePos'):
|
|
116
|
+
self.mousePos = lpos
|
|
117
|
+
diff = lpos - self.mousePos
|
|
118
|
+
self.mousePos = lpos
|
|
119
|
+
if ev.buttons() == QtCore.Qt.MouseButton.RightButton:
|
|
120
|
+
self.orbit(-diff.x(), diff.y())
|
|
121
|
+
elif ev.buttons() == QtCore.Qt.MouseButton.LeftButton:
|
|
122
|
+
pitch_abs = np.abs(self.opts['elevation'])
|
|
123
|
+
camera_mode = 'view-upright'
|
|
124
|
+
if (pitch_abs <= 45.0 or pitch_abs == 90):
|
|
125
|
+
camera_mode = 'view'
|
|
126
|
+
self.pan(diff.x(), diff.y(), 0, relative=camera_mode)
|
|
127
|
+
|
|
128
|
+
def keyPressEvent(self, ev: QKeyEvent):
|
|
129
|
+
step = 10
|
|
130
|
+
zoom_delta = 20
|
|
131
|
+
speed = 2
|
|
132
|
+
self.projectionMatrix().data()
|
|
133
|
+
|
|
134
|
+
pitch_abs = np.abs(self.opts['elevation'])
|
|
135
|
+
camera_mode = 'view-upright'
|
|
136
|
+
if (pitch_abs <= 45.0 or pitch_abs == 90):
|
|
137
|
+
camera_mode = 'view'
|
|
138
|
+
|
|
139
|
+
if ev.key() == QtCore.Qt.Key_M: # setting meun
|
|
140
|
+
print("Open setting windows")
|
|
141
|
+
self.openSettingWindow()
|
|
142
|
+
elif ev.key() == QtCore.Qt.Key_R:
|
|
143
|
+
print("Clear viewer")
|
|
144
|
+
for item in self.named_items.values():
|
|
145
|
+
try:
|
|
146
|
+
item.clear()
|
|
147
|
+
except AttributeError:
|
|
148
|
+
pass
|
|
149
|
+
elif ev.key() == QtCore.Qt.Key_Up:
|
|
150
|
+
if ev.modifiers() & QtCore.Qt.KeyboardModifier.ControlModifier:
|
|
151
|
+
self.pan(0, +step, 0, relative=camera_mode)
|
|
152
|
+
else:
|
|
153
|
+
self.orbit(azim=0, elev=-speed)
|
|
154
|
+
elif ev.key() == QtCore.Qt.Key_Down:
|
|
155
|
+
if ev.modifiers() & QtCore.Qt.KeyboardModifier.ControlModifier:
|
|
156
|
+
self.pan(0, -step, 0, relative=camera_mode)
|
|
157
|
+
else:
|
|
158
|
+
self.orbit(azim=0, elev=speed)
|
|
159
|
+
elif ev.key() == QtCore.Qt.Key_Left:
|
|
160
|
+
if ev.modifiers() & QtCore.Qt.KeyboardModifier.ControlModifier:
|
|
161
|
+
self.pan(+step, 0, 0, relative=camera_mode)
|
|
162
|
+
else:
|
|
163
|
+
self.orbit(azim=speed, elev=0)
|
|
164
|
+
|
|
165
|
+
elif ev.key() == QtCore.Qt.Key_Right:
|
|
166
|
+
if ev.modifiers() & QtCore.Qt.KeyboardModifier.ControlModifier:
|
|
167
|
+
self.pan(-step, 0, 0, relative=camera_mode)
|
|
168
|
+
else:
|
|
169
|
+
self.orbit(azim=-speed, elev=0)
|
|
170
|
+
|
|
171
|
+
elif ev.key() == QtCore.Qt.Key_Z:
|
|
172
|
+
self.opts['distance'] *= 0.999**(+zoom_delta)
|
|
173
|
+
elif ev.key() == QtCore.Qt.Key_X:
|
|
174
|
+
self.opts['distance'] *= 0.999**(-zoom_delta)
|
|
175
|
+
else:
|
|
176
|
+
super().keyPressEvent(ev)
|
|
177
|
+
|
|
178
|
+
def openSettingWindow(self):
|
|
179
|
+
if self.setting_window.isVisible():
|
|
180
|
+
self.setting_window.raise_()
|
|
181
|
+
|
|
182
|
+
else:
|
|
183
|
+
self.setting_window.show()
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
q3dviewer/__init__.py,sha256=BCrGWEtwjdvK4CHgp1e5Wkyhabk6WdvY_7FKJdGJwGE,142
|
|
2
|
+
q3dviewer/basic_viewer.py,sha256=A7bMxutP1grnqE5A4tjODGrzNfFq6-c6MlNYj8t54WU,1609
|
|
3
|
+
q3dviewer/gau_io.py,sha256=fKry1eyeFdw_JO5JZqU3l66dzt43NGpHhX89ER_y38A,5900
|
|
4
|
+
q3dviewer/utils.py,sha256=xzUAXUhgxoJyPrnivnyrJWoQQg9LrDPhxgZo1Cp35kQ,4596
|
|
5
|
+
q3dviewer/viewer_widget.py,sha256=repcGWZZXRxua8C-3CYHumvkVoPQl8ycFPTBx5zovRg,6561
|
|
6
|
+
q3dviewer/custom_items/__init__.py,sha256=Isb3HPtymuxEev1hc81x51inzH6epomtBHBQsPp4gZY,446
|
|
7
|
+
q3dviewer/custom_items/axis_item.py,sha256=Er8HRSnOscROZ7fOZEESnrZp62A5kY4oqP-GCAXczYU,2353
|
|
8
|
+
q3dviewer/custom_items/camera_frame_item.py,sha256=hvpZ3UEnkbs1nHNr2R7huuy1MdvBmwQPiqqKImWWG8c,5622
|
|
9
|
+
q3dviewer/custom_items/cloud_io_item.py,sha256=nbZbZLX72CWNFrInpxwysA2usbCkgAUrwZEOOVWx9Mc,2816
|
|
10
|
+
q3dviewer/custom_items/cloud_item.py,sha256=gOIun2VOfarA4Xo_2vdyWpIII0vvWVcQ4VqCZsYj584,9711
|
|
11
|
+
q3dviewer/custom_items/gaussian_item.py,sha256=3YXdJxHNSdk0D2HS2VJvuGTUjbi9D_UcrWEXSzxP7BY,10177
|
|
12
|
+
q3dviewer/custom_items/grid_item.py,sha256=WuNzIHBhjAQ6e4fP5aBp5jSgYbrY7wrbP9PhRptuz7s,1150
|
|
13
|
+
q3dviewer/custom_items/image_item.py,sha256=qOT0r6p8_NcF5GVeJrrR8rJNe68U_jW1qrUDZ05O5hI,5312
|
|
14
|
+
q3dviewer/custom_items/text_item.py,sha256=lbJaXfpeyyxr6lhlMjypk4a079-aUpsrXFjGi-BTzPU,1872
|
|
15
|
+
q3dviewer/custom_items/trajectory_item.py,sha256=9gM5voiYk0j4JMEp3drF7VFYSPsJcEK7mibdx45OQDs,2778
|
|
16
|
+
q3dviewer-1.0.0.dist-info/METADATA,sha256=YkgPKWnfC-R9Q26DXkrAwih_KwyqAeAPVbOgzRwiMIk,189
|
|
17
|
+
q3dviewer-1.0.0.dist-info/WHEEL,sha256=g4nMs7d-Xl9-xC9XovUrsDHGXt-FT0E17Yqo92DEfvY,92
|
|
18
|
+
q3dviewer-1.0.0.dist-info/entry_points.txt,sha256=6zSxtJ5cjcpc32sR_wkRqOaeiJFpEuLnJQckZE3niU0,316
|
|
19
|
+
q3dviewer-1.0.0.dist-info/top_level.txt,sha256=HFFDCbGu28txcGe2HPc46A7EPaguBa_b5oH7bufmxHM,10
|
|
20
|
+
q3dviewer-1.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
[console_scripts]
|
|
2
|
+
cloud_viewer = q3dviewer.tools.cloud_viewer:main
|
|
3
|
+
gaussian_viewer = q3dviewer.tools.gaussian_viewer:main
|
|
4
|
+
lidar_calib = q3dviewer.tools.lidar_calib:main
|
|
5
|
+
lidar_cam_calib = q3dviewer.tools.lidar_cam_calib:main
|
|
6
|
+
mesh_viewer = q3dviewer.tools.mesh_viewer:main
|
|
7
|
+
ros_viewer = q3dviewer.tools.ros_viewer:main
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
q3dviewer
|