q3dviewer 1.1.0__py3-none-any.whl → 1.1.2__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/base_glwidget.py +10 -1
- q3dviewer/basic_window.py +228 -0
- q3dviewer/cloud_viewer.py +74 -0
- q3dviewer/custom_items/camera_frame_item.py +173 -0
- q3dviewer/custom_items/cloud_item.py +1 -0
- q3dviewer/custom_items/trajectory_item.py +79 -0
- q3dviewer/gau_io.py +168 -0
- q3dviewer/tools/film_maker.py +27 -12
- q3dviewer/utils.py +71 -0
- {q3dviewer-1.1.0.dist-info → q3dviewer-1.1.2.dist-info}/METADATA +12 -9
- {q3dviewer-1.1.0.dist-info → q3dviewer-1.1.2.dist-info}/RECORD +15 -13
- {q3dviewer-1.1.0.dist-info → q3dviewer-1.1.2.dist-info}/entry_points.txt +1 -0
- q3dviewer/test/test_interpolation.py +0 -58
- q3dviewer/test/test_rendering.py +0 -72
- q3dviewer/tools/cinematographer.py +0 -367
- {q3dviewer-1.1.0.dist-info → q3dviewer-1.1.2.dist-info}/LICENSE +0 -0
- {q3dviewer-1.1.0.dist-info → q3dviewer-1.1.2.dist-info}/WHEEL +0 -0
- {q3dviewer-1.1.0.dist-info → q3dviewer-1.1.2.dist-info}/top_level.txt +0 -0
q3dviewer/gau_io.py
CHANGED
|
@@ -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(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(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/tools/film_maker.py
CHANGED
|
@@ -107,7 +107,7 @@ class CMMViewer(q3d.Viewer):
|
|
|
107
107
|
label_codec = QLabel("Codec:")
|
|
108
108
|
codec_layout.addWidget(label_codec)
|
|
109
109
|
self.codec_combo = QComboBox()
|
|
110
|
-
self.codec_combo.addItems(["
|
|
110
|
+
self.codec_combo.addItems(["libx264", "mjpeg", "mpeg4", "libx265"])
|
|
111
111
|
codec_layout.addWidget(self.codec_combo)
|
|
112
112
|
setting_layout.addLayout(codec_layout)
|
|
113
113
|
|
|
@@ -143,6 +143,8 @@ class CMMViewer(q3d.Viewer):
|
|
|
143
143
|
dock_widget = QDockWidget("Settings", self)
|
|
144
144
|
dock_widget.setWidget(QWidget())
|
|
145
145
|
dock_widget.widget().setLayout(setting_layout)
|
|
146
|
+
# Hide close and undock buttons, I don't want the user to close the dock
|
|
147
|
+
dock_widget.setFeatures(QDockWidget.DockWidgetMovable)
|
|
146
148
|
self.addDockWidget(QtCore.Qt.RightDockWidgetArea, dock_widget)
|
|
147
149
|
|
|
148
150
|
# Add the dock widget to the main layout
|
|
@@ -269,8 +271,8 @@ class CMMViewer(q3d.Viewer):
|
|
|
269
271
|
self.current_frame_index = 0
|
|
270
272
|
self.timer.start(self.update_interval) # Adjust the interval as needed
|
|
271
273
|
self.is_playing = True
|
|
272
|
-
self.play_button.setStyleSheet("")
|
|
273
|
-
self.play_button.setText("
|
|
274
|
+
self.play_button.setStyleSheet("background-color: red")
|
|
275
|
+
self.play_button.setText("Playing")
|
|
274
276
|
self.record_checkbox.setEnabled(False)
|
|
275
277
|
if self.is_recording is True:
|
|
276
278
|
self.start_recording()
|
|
@@ -308,14 +310,16 @@ class CMMViewer(q3d.Viewer):
|
|
|
308
310
|
|
|
309
311
|
def start_recording(self):
|
|
310
312
|
self.is_recording = True
|
|
311
|
-
self.
|
|
313
|
+
self.prv_frame_shape = None
|
|
312
314
|
video_path = self.video_path_edit.text()
|
|
313
315
|
codec = self.codec_combo.currentText()
|
|
314
316
|
self.play_button.setStyleSheet("background-color: red")
|
|
315
317
|
self.play_button.setText("Recording")
|
|
316
318
|
self.writer = imageio.get_writer(video_path,
|
|
317
319
|
fps=self.update_interval,
|
|
318
|
-
codec=codec
|
|
320
|
+
codec=codec,
|
|
321
|
+
quality=10,
|
|
322
|
+
pixelformat='yuvj420p')
|
|
319
323
|
# disable the all the frame_item while recording
|
|
320
324
|
for frame in self.key_frames:
|
|
321
325
|
frame.item.hide()
|
|
@@ -323,6 +327,7 @@ class CMMViewer(q3d.Viewer):
|
|
|
323
327
|
|
|
324
328
|
def stop_recording(self, save_movie=True):
|
|
325
329
|
self.is_recording = False
|
|
330
|
+
self.prv_frame_shape = None
|
|
326
331
|
self.record_checkbox.setChecked(False)
|
|
327
332
|
# enable the all the frame_item after recording
|
|
328
333
|
for frame in self.key_frames:
|
|
@@ -342,17 +347,27 @@ class CMMViewer(q3d.Viewer):
|
|
|
342
347
|
|
|
343
348
|
def record_frame(self):
|
|
344
349
|
frame = self.glwidget.capture_frame()
|
|
345
|
-
|
|
350
|
+
|
|
351
|
+
# restart recording if the window size changes
|
|
352
|
+
if self.prv_frame_shape is not None and frame.shape != self.prv_frame_shape:
|
|
353
|
+
self.writer.close()
|
|
354
|
+
self.start_recording()
|
|
355
|
+
return
|
|
356
|
+
self.prv_frame_shape = frame.shape
|
|
357
|
+
|
|
346
358
|
height, width, _ = frame.shape
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
359
|
+
# Adjust frame dimensions to be multiples of 16
|
|
360
|
+
new_height = height - (height % 16)
|
|
361
|
+
new_width = width - (width % 16)
|
|
362
|
+
frame = frame[:new_height, :new_width, :]
|
|
363
|
+
frame = np.ascontiguousarray(frame)
|
|
351
364
|
try:
|
|
352
365
|
self.writer.append_data(frame)
|
|
353
366
|
except Exception as e:
|
|
354
|
-
|
|
355
|
-
|
|
367
|
+
# unexpected error, stop recording without saving
|
|
368
|
+
print("Error while recording:")
|
|
369
|
+
print(e)
|
|
370
|
+
self.stop_recording(False)
|
|
356
371
|
self.stop_playback()
|
|
357
372
|
|
|
358
373
|
def eventFilter(self, obj, event):
|
q3dviewer/utils.py
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import time
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def matrix_to_quaternion(matrix):
|
|
6
|
+
trace = matrix[0, 0] + matrix[1, 1] + matrix[2, 2]
|
|
7
|
+
if trace > 0:
|
|
8
|
+
s = 0.5 / np.sqrt(trace + 1.0)
|
|
9
|
+
w = 0.25 / s
|
|
10
|
+
x = (matrix[2, 1] - matrix[1, 2]) * s
|
|
11
|
+
y = (matrix[0, 2] - matrix[2, 0]) * s
|
|
12
|
+
z = (matrix[1, 0] - matrix[0, 1]) * s
|
|
13
|
+
else:
|
|
14
|
+
if matrix[0, 0] > matrix[1, 1] and matrix[0, 0] > matrix[2, 2]:
|
|
15
|
+
s = 2.0 * np.sqrt(1.0 + matrix[0, 0] - matrix[1, 1] - matrix[2, 2])
|
|
16
|
+
w = (matrix[2, 1] - matrix[1, 2]) / s
|
|
17
|
+
x = 0.25 * s
|
|
18
|
+
y = (matrix[0, 1] + matrix[1, 0]) / s
|
|
19
|
+
z = (matrix[0, 2] + matrix[2, 0]) / s
|
|
20
|
+
elif matrix[1, 1] > matrix[2, 2]:
|
|
21
|
+
s = 2.0 * np.sqrt(1.0 + matrix[1, 1] - matrix[0, 0] - matrix[2, 2])
|
|
22
|
+
w = (matrix[0, 2] - matrix[2, 0]) / s
|
|
23
|
+
x = (matrix[0, 1] + matrix[1, 0]) / s
|
|
24
|
+
y = 0.25 * s
|
|
25
|
+
z = (matrix[1, 2] + matrix[2, 1]) / s
|
|
26
|
+
else:
|
|
27
|
+
s = 2.0 * np.sqrt(1.0 + matrix[2, 2] - matrix[0, 0] - matrix[1, 1])
|
|
28
|
+
w = (matrix[1, 0] - matrix[0, 1]) / s
|
|
29
|
+
x = (matrix[0, 2] + matrix[2, 0]) / s
|
|
30
|
+
y = (matrix[1, 2] + matrix[2, 1]) / s
|
|
31
|
+
z = 0.25 * s
|
|
32
|
+
return np.array([w, x, y, z])
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def quaternion_to_matrix(quaternion):
|
|
36
|
+
_EPS = np.finfo(float).eps * 4.0
|
|
37
|
+
q = np.array(quaternion[:4], dtype=np.float64, copy=True)
|
|
38
|
+
nq = np.dot(q, q)
|
|
39
|
+
if nq < _EPS:
|
|
40
|
+
return np.identity(4)
|
|
41
|
+
q *= np.sqrt(2.0 / nq)
|
|
42
|
+
q = np.outer(q, q)
|
|
43
|
+
return np.array((
|
|
44
|
+
(1.0-q[1, 1]-q[2, 2], q[0, 1]-q[2, 3], q[0, 2]+q[1, 3], 0.0),
|
|
45
|
+
(q[0, 1]+q[2, 3], 1.0-q[0, 0]-q[2, 2], q[1, 2]-q[0, 3], 0.0),
|
|
46
|
+
(q[0, 2]-q[1, 3], q[1, 2]+q[0, 3], 1.0-q[0, 0]-q[1, 1], 0.0),
|
|
47
|
+
(0.0, 0.0, 0.0, 1.0)
|
|
48
|
+
), dtype=np.float64)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def make_transform(pose, rotation):
|
|
52
|
+
transform = np.matrix(np.identity(4, dtype=np.float64))
|
|
53
|
+
transform = quaternion_to_matrix(rotation)
|
|
54
|
+
transform[0:3, 3] = np.transpose(pose)
|
|
55
|
+
return transform
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class FPSMonitor():
|
|
59
|
+
def __init__(self):
|
|
60
|
+
self.stamp_record = []
|
|
61
|
+
|
|
62
|
+
def count(self):
|
|
63
|
+
cur_stamp = time.time()
|
|
64
|
+
self.stamp_record.append(cur_stamp)
|
|
65
|
+
while len(self.stamp_record) > 0:
|
|
66
|
+
if(cur_stamp - self.stamp_record[0] > 1.):
|
|
67
|
+
self.stamp_record.pop(0)
|
|
68
|
+
else:
|
|
69
|
+
break
|
|
70
|
+
return len(self.stamp_record)
|
|
71
|
+
|
|
@@ -1,23 +1,25 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: q3dviewer
|
|
3
|
-
Version: 1.1.
|
|
3
|
+
Version: 1.1.2
|
|
4
4
|
Summary: A library designed for quickly deploying a 3D viewer.
|
|
5
5
|
Home-page: https://github.com/scomup/q3dviewer
|
|
6
6
|
Author: Liu Yang
|
|
7
|
+
License: UNKNOWN
|
|
8
|
+
Platform: UNKNOWN
|
|
7
9
|
Classifier: Programming Language :: Python :: 3
|
|
8
10
|
Classifier: License :: OSI Approved :: MIT License
|
|
9
11
|
Classifier: Operating System :: OS Independent
|
|
10
12
|
Description-Content-Type: text/markdown
|
|
11
|
-
License-File: LICENSE
|
|
12
|
-
Requires-Dist: numpy
|
|
13
|
-
Requires-Dist: pyside6
|
|
14
13
|
Requires-Dist: PyOpenGL
|
|
15
|
-
Requires-Dist:
|
|
14
|
+
Requires-Dist: imageio
|
|
15
|
+
Requires-Dist: imageio[ffmpeg]
|
|
16
|
+
Requires-Dist: laspy
|
|
16
17
|
Requires-Dist: meshio
|
|
17
|
-
Requires-Dist:
|
|
18
|
+
Requires-Dist: numpy
|
|
19
|
+
Requires-Dist: pillow
|
|
18
20
|
Requires-Dist: pye57
|
|
19
|
-
Requires-Dist:
|
|
20
|
-
Requires-Dist:
|
|
21
|
+
Requires-Dist: pypcd4
|
|
22
|
+
Requires-Dist: pyside6
|
|
21
23
|
|
|
22
24
|
## q3dviewer
|
|
23
25
|
|
|
@@ -150,7 +152,7 @@ def main():
|
|
|
150
152
|
|
|
151
153
|
# Create a viewer
|
|
152
154
|
viewer = q3d.Viewer(name='example')
|
|
153
|
-
|
|
155
|
+
|
|
154
156
|
# Add items to the viewer
|
|
155
157
|
viewer.add_items({
|
|
156
158
|
'grid': grid_item,
|
|
@@ -212,3 +214,4 @@ class YourItem(q3d.BaseItem):
|
|
|
212
214
|
```
|
|
213
215
|
|
|
214
216
|
Enjoy using `q3dviewer`!
|
|
217
|
+
|
|
@@ -1,32 +1,34 @@
|
|
|
1
1
|
q3dviewer/__init__.py,sha256=rP5XX_x8g7hxIMqNHlU89BN4dt5MSvoYYwip68fCmhc,173
|
|
2
|
-
q3dviewer/base_glwidget.py,sha256=
|
|
2
|
+
q3dviewer/base_glwidget.py,sha256=tk8icQsRZ3OrSLkHvywmrxElk6jpMooLDZdbZ3sRLnk,11313
|
|
3
3
|
q3dviewer/base_item.py,sha256=lzb04oRaS4rRJrAP6C1Bu4ugK237FgupMTB97zjNVFw,1768
|
|
4
|
-
q3dviewer/
|
|
4
|
+
q3dviewer/basic_window.py,sha256=CFErOPRMysFcCqq3vhDsQ-xZzLArO3m1yABCTIhq5do,7946
|
|
5
|
+
q3dviewer/cloud_viewer.py,sha256=IxxrB6Sl6aPCr9P9QzKHGkMcDP_DjsBWbkmIbsAoIM4,2358
|
|
6
|
+
q3dviewer/gau_io.py,sha256=S6NmqL5vSMgHVxKR-eu4CWCqgZeWBJKYRoOMAwr8Xbo,5890
|
|
5
7
|
q3dviewer/glwidget.py,sha256=im8hjVYEL0Zl7fOIHTQMJdWu7WNOHlvTdIDYjebz9WA,4940
|
|
8
|
+
q3dviewer/utils.py,sha256=evF0d-v17hbTmquC24fmMIp9CsXpUnSQZr4MVy2sfao,2426
|
|
6
9
|
q3dviewer/viewer.py,sha256=LH1INLFhi6pRjzazzQJ0AWT4hgyXI6GnmqoJFUwUZVE,2517
|
|
7
10
|
q3dviewer/custom_items/__init__.py,sha256=gOiAxdjDaAnFL8YbqSEWWWOwUrJfvzP9JLR34sCB9-4,434
|
|
8
11
|
q3dviewer/custom_items/axis_item.py,sha256=PTBSf5DmQI8ieSinYjY_aC7P8q1nzE-2Vc2GNd1O3Os,2568
|
|
12
|
+
q3dviewer/custom_items/camera_frame_item.py,sha256=VBsr3Avly_YWXViIh4DJkGc_HJt227GeOYLpGtbYTOw,5605
|
|
9
13
|
q3dviewer/custom_items/cloud_io_item.py,sha256=gjK3n9WKB7JwxC93ijkweEHA5EezpgNJ8KO-PBaDKCs,2835
|
|
10
|
-
q3dviewer/custom_items/cloud_item.py,sha256=
|
|
14
|
+
q3dviewer/custom_items/cloud_item.py,sha256=s46TxNDw7E6DW3V-ek_PF7egB3kRbnqpOBNUKdaC8IY,12620
|
|
11
15
|
q3dviewer/custom_items/frame_item.py,sha256=6BOM3MXi-Akv6KUXDC3QYEAXKxwk0Eo1KQn-F7jkqrQ,7559
|
|
12
16
|
q3dviewer/custom_items/gaussian_item.py,sha256=CZoXMmj2JPFfMqu7v4q4dLUI2cg_WfE1DHWGXjEYn6M,9869
|
|
13
17
|
q3dviewer/custom_items/grid_item.py,sha256=20n4TGm5YEaudhnEOCOk-HtsKwxVxaPr8YV36kO04yU,4802
|
|
14
18
|
q3dviewer/custom_items/image_item.py,sha256=ctNR81fVgxkdl2n3U_TPFL6yn086UhNI_A9fFHgkc-4,5491
|
|
15
19
|
q3dviewer/custom_items/line_item.py,sha256=u0oFN2iHzsRHtnbvyvC_iglEkCwEU2NnTuE536sKUAE,4348
|
|
16
20
|
q3dviewer/custom_items/text_item.py,sha256=nuHMVMQrwy50lNk9hxB94criFxbJJK-SYiK2fSXWUMQ,2158
|
|
21
|
+
q3dviewer/custom_items/trajectory_item.py,sha256=uoKQSrTs_m_m1M8iNAm3peiXnZ9uVPsYQLYas3Gksjg,2754
|
|
17
22
|
q3dviewer/shaders/cloud_frag.glsl,sha256=tbCsDUp9YlPe0hRWlFS724SH6TtMeLO-GVYROzEElZg,609
|
|
18
23
|
q3dviewer/shaders/cloud_vert.glsl,sha256=Vxgw-Zrr0knAK0z4qMXKML6IC4EbffKMwYN2TMXROoI,2117
|
|
19
24
|
q3dviewer/shaders/gau_frag.glsl,sha256=5_UY84tWDts59bxP8x4I-wgnzY8aGeGuo28wX--LW7E,975
|
|
20
25
|
q3dviewer/shaders/gau_prep.glsl,sha256=eCT9nm65uz32w8NaDjeGKhyAZh42Aea-QTwr3yQVr9U,7218
|
|
21
26
|
q3dviewer/shaders/gau_vert.glsl,sha256=NNbVhv_JyqZDK9iXAyBAcIHAtim7G9yWbC9IaUfTL1w,1666
|
|
22
27
|
q3dviewer/shaders/sort_by_key.glsl,sha256=CA2zOcbyDGYAJSJEUvgjUqNshg9NAehf8ipL3Jsv4qE,1097
|
|
23
|
-
q3dviewer/test/test_interpolation.py,sha256=rR_CXsYFLpn0zO0mHf_jL-naluDBMSky--FviOQga0Q,1657
|
|
24
|
-
q3dviewer/test/test_rendering.py,sha256=gbTcu7-cg20DgC5Zoi17C1s5lBGLfAE1rW9biqPjRsA,2164
|
|
25
28
|
q3dviewer/tools/__init__.py,sha256=01wG7BGM6VX0QyFBKsqPmyf2e-vrmV_N3-mo-VQ1VBg,20
|
|
26
|
-
q3dviewer/tools/cinematographer.py,sha256=o_24SSQ4mF062QQ7Gv3i90v7fA79PcHLB03UHXufuEA,13950
|
|
27
29
|
q3dviewer/tools/cloud_viewer.py,sha256=10f2LSWpmsXzxrGobXw188doVjJbgBfPoqZPUi35EtI,3867
|
|
28
30
|
q3dviewer/tools/example_viewer.py,sha256=yeVXT0k4-h1vTLKnGzWADZD3our6XUaYUTy0p5daTkE,959
|
|
29
|
-
q3dviewer/tools/film_maker.py,sha256=
|
|
31
|
+
q3dviewer/tools/film_maker.py,sha256=KXl0H03vnS7eAFw7nSRvTG3J0Y7gYt6YFUYaoxJf3xM,16600
|
|
30
32
|
q3dviewer/tools/gaussian_viewer.py,sha256=vIwWmiFhjNmknrEkBLzt2yiegeH7LP3OeNjnGM6GzaI,1633
|
|
31
33
|
q3dviewer/tools/lidar_calib.py,sha256=M01bGg2mT8LwVcYybolr4UW_UUaR-f-BFciEHtjeK-w,10488
|
|
32
34
|
q3dviewer/tools/lidar_cam_calib.py,sha256=SYNLDvi15MX7Q3aGn771fvu1cES9xeXgP0_WmDq33w4,11200
|
|
@@ -37,9 +39,9 @@ q3dviewer/utils/convert_ros_msg.py,sha256=sAoQfy3qLQKsIArBAVm8H--wlQXOcmkKK3-Ox9
|
|
|
37
39
|
q3dviewer/utils/gl_helper.py,sha256=dRY_kUqyPMr7NTcupUr6_VTvgnj53iE2C0Lk0-oFYsI,1435
|
|
38
40
|
q3dviewer/utils/maths.py,sha256=5TmjWUX1K3UjygXxrUsydjbo7tPzu0gD-yy7qtQUGBU,10588
|
|
39
41
|
q3dviewer/utils/range_slider.py,sha256=jZJQL-uQgnpgLvtYSWpKTrJlLkt3aqNpaRQAePEpNd0,3174
|
|
40
|
-
q3dviewer-1.1.
|
|
41
|
-
q3dviewer-1.1.
|
|
42
|
-
q3dviewer-1.1.
|
|
43
|
-
q3dviewer-1.1.
|
|
44
|
-
q3dviewer-1.1.
|
|
45
|
-
q3dviewer-1.1.
|
|
42
|
+
q3dviewer-1.1.2.dist-info/LICENSE,sha256=81cMOyNfw8KLb1JnPYngGHJ5W83gSbZEBU9MEP3tl-E,1124
|
|
43
|
+
q3dviewer-1.1.2.dist-info/METADATA,sha256=c8ONBUQAOF1v0UYF_6_w_vZ25fagwH-bL-k0Mj6x71I,7032
|
|
44
|
+
q3dviewer-1.1.2.dist-info/WHEEL,sha256=g4nMs7d-Xl9-xC9XovUrsDHGXt-FT0E17Yqo92DEfvY,92
|
|
45
|
+
q3dviewer-1.1.2.dist-info/entry_points.txt,sha256=EOjker7XYaBk70ffvNB_knPcfA33Bnlg21ZjEeM1EyI,362
|
|
46
|
+
q3dviewer-1.1.2.dist-info/top_level.txt,sha256=HFFDCbGu28txcGe2HPc46A7EPaguBa_b5oH7bufmxHM,10
|
|
47
|
+
q3dviewer-1.1.2.dist-info/RECORD,,
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
|
|
3
|
-
"""
|
|
4
|
-
Copyright 2024 Panasonic Advanced Technology Development Co.,Ltd. (Liu Yang)
|
|
5
|
-
Distributed under MIT license. See LICENSE for more information.
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
"""
|
|
9
|
-
this script tests interpolation of 3D poses.
|
|
10
|
-
"""
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
import numpy as np
|
|
14
|
-
from q3dviewer.utils.maths import expSO3, logSO3, makeT, makeRt
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
def interpolate_pose(T1, T2, v_max, omega_max, dt=0.1):
|
|
18
|
-
R1, t1 = makeRt(T1)
|
|
19
|
-
R2, t2 = makeRt(T2)
|
|
20
|
-
|
|
21
|
-
# Get transform time based on linear velocity
|
|
22
|
-
d = np.linalg.norm(t2 - t1)
|
|
23
|
-
t_lin = d / v_max
|
|
24
|
-
|
|
25
|
-
# Get transform time based on angular velocity
|
|
26
|
-
omega = logSO3(R2 @ R1.T)
|
|
27
|
-
theta = np.linalg.norm(omega)
|
|
28
|
-
t_ang = theta / omega_max
|
|
29
|
-
|
|
30
|
-
# Get total time based on the linear and angular time
|
|
31
|
-
t_total = max(t_lin, t_ang)
|
|
32
|
-
num_steps = int(np.ceil(t_total / dt))
|
|
33
|
-
|
|
34
|
-
# Generate interpolated transforms
|
|
35
|
-
interpolated_Ts = []
|
|
36
|
-
for i in range(num_steps + 1):
|
|
37
|
-
s = i / num_steps
|
|
38
|
-
t_interp = (1 - s) * t1 + s * t2
|
|
39
|
-
# Interpolate rotation using SO3
|
|
40
|
-
R_interp = expSO3(s * omega) @ R1
|
|
41
|
-
T_interp = makeT(R_interp, t_interp)
|
|
42
|
-
interpolated_Ts.append(T_interp)
|
|
43
|
-
|
|
44
|
-
return interpolated_Ts
|
|
45
|
-
|
|
46
|
-
if __name__ == "__main__":
|
|
47
|
-
T1 = np.eye(4) # Identity transformation
|
|
48
|
-
T2 = np.array([[0, -1, 0, 1], [1, 0, 0, 2], [0, 0, 1, 3], [0, 0, 0, 1]]) # Target transformation
|
|
49
|
-
|
|
50
|
-
v_max = 1.0 # Maximum linear velocity (m/s)
|
|
51
|
-
omega_max = np.pi / 4 # Maximum angular velocity (rad/s)
|
|
52
|
-
|
|
53
|
-
# Perform interpolation
|
|
54
|
-
interpolated_poses = interpolate_pose(T1, T2, v_max, omega_max)
|
|
55
|
-
for i, T in enumerate(interpolated_poses):
|
|
56
|
-
print(f"Step {i}:\n{T}\n")
|
|
57
|
-
|
|
58
|
-
|
q3dviewer/test/test_rendering.py
DELETED
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
|
|
3
|
-
"""
|
|
4
|
-
Copyright 2024 Panasonic Advanced Technology Development Co.,Ltd. (Liu Yang)
|
|
5
|
-
Distributed under MIT license. See LICENSE for more information.
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
"""
|
|
9
|
-
this script tests the rendering of a cloud in a camera frame based on the
|
|
10
|
-
camera pose and intrinsic matrix
|
|
11
|
-
"""
|
|
12
|
-
|
|
13
|
-
import numpy as np
|
|
14
|
-
import q3dviewer as q3d
|
|
15
|
-
import cv2
|
|
16
|
-
|
|
17
|
-
cloud, _ = q3d.load_pcd('/home/liu/lab.pcd')
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
Tcw = np.array([[7.07106781e-01, 7.07106781e-01, 0.00000000e+00,
|
|
21
|
-
0.00000000e+00],
|
|
22
|
-
[-3.53553391e-01, 3.53553391e-01, 8.66025404e-01,
|
|
23
|
-
3.55271368e-15],
|
|
24
|
-
[6.12372436e-01, -6.12372436e-01, 5.00000000e-01,
|
|
25
|
-
-4.00000000e+01],
|
|
26
|
-
[0.00000000e+00, 0.00000000e+00, 0.00000000e+00,
|
|
27
|
-
1.00000000e+00]])
|
|
28
|
-
# convert the opengl camera coordinate to the opencv camera coordinate
|
|
29
|
-
Tconv = np.array([[1, 0, 0, 0],
|
|
30
|
-
[0, -1, 0, 0],
|
|
31
|
-
[0, 0, -1, 0],
|
|
32
|
-
[0, 0, 0, 1]])
|
|
33
|
-
|
|
34
|
-
Tcw = Tconv @ Tcw
|
|
35
|
-
|
|
36
|
-
K = np.array([[1.64718029e+03, 0.00000000e+00, 9.51000000e+02],
|
|
37
|
-
[0.00000000e+00, 1.64718036e+03, 5.31000000e+02],
|
|
38
|
-
[0.00000000e+00, 0.00000000e+00, 1.00000000e+00]])
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
def render_frame(cloud, Tcw, K, width, height):
|
|
42
|
-
image = np.zeros((height, width, 3), dtype=np.uint8)
|
|
43
|
-
Rcw, tcw = Tcw[:3, :3], Tcw[:3, 3]
|
|
44
|
-
pc = (Rcw @ cloud['xyz'].T).T + tcw
|
|
45
|
-
uv = (K @ pc.T).T
|
|
46
|
-
uv = uv[:, :2] / uv[:, 2][:, np.newaxis]
|
|
47
|
-
mask = (pc[:, 2] > 0) & (uv[:, 0] > 0) & (
|
|
48
|
-
uv[:, 0] < width) & (uv[:, 1] > 0) & (uv[:, 1] < height)
|
|
49
|
-
uv = uv[mask]
|
|
50
|
-
u = uv[:, 0].astype(int)
|
|
51
|
-
v = uv[:, 1].astype(int)
|
|
52
|
-
rgb = cloud['irgb'][mask]
|
|
53
|
-
r = rgb >> 16 & 0xff
|
|
54
|
-
g = rgb >> 8 & 0xff
|
|
55
|
-
b = rgb & 0xff
|
|
56
|
-
|
|
57
|
-
# Sort by depth to ensure front points are drawn first
|
|
58
|
-
depth = pc[mask, 2]
|
|
59
|
-
sorted_indices = np.argsort(depth)
|
|
60
|
-
u = u[sorted_indices]
|
|
61
|
-
v = v[sorted_indices]
|
|
62
|
-
r = r[sorted_indices]
|
|
63
|
-
g = g[sorted_indices]
|
|
64
|
-
b = b[sorted_indices]
|
|
65
|
-
|
|
66
|
-
image[v, u] = np.stack([b, g, r], axis=1)
|
|
67
|
-
return image
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
image = render_frame(cloud, Tcw, K, 1902, 1062)
|
|
71
|
-
cv2.imshow('image', image)
|
|
72
|
-
cv2.waitKey(0)
|