q3dviewer 1.0.9__py3-none-any.whl → 1.1.1__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 +20 -4
- 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/cloud_viewer.py +2 -2
- q3dviewer/tools/film_maker.py +47 -21
- q3dviewer/utils.py +71 -0
- q3dviewer-1.1.1.dist-info/METADATA +217 -0
- {q3dviewer-1.0.9.dist-info → q3dviewer-1.1.1.dist-info}/RECORD +16 -14
- {q3dviewer-1.0.9.dist-info → q3dviewer-1.1.1.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.0.9.dist-info/METADATA +0 -14
- {q3dviewer-1.0.9.dist-info → q3dviewer-1.1.1.dist-info}/LICENSE +0 -0
- {q3dviewer-1.0.9.dist-info → q3dviewer-1.1.1.dist-info}/WHEEL +0 -0
- {q3dviewer-1.0.9.dist-info → q3dviewer-1.1.1.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/cloud_viewer.py
CHANGED
|
@@ -52,7 +52,7 @@ class FileLoaderThread(QThread):
|
|
|
52
52
|
self.viewer.progress_dialog.set_file_name(file_path)
|
|
53
53
|
cloud = cloud_item.load(file_path, append=(i > 0))
|
|
54
54
|
center = np.nanmean(cloud['xyz'].astype(np.float64), axis=0)
|
|
55
|
-
self.viewer.glwidget.set_cam_position(
|
|
55
|
+
self.viewer.glwidget.set_cam_position(center=center)
|
|
56
56
|
self.progress.emit(int((i + 1) / len(self.files) * 100))
|
|
57
57
|
self.finished.emit()
|
|
58
58
|
|
|
@@ -94,7 +94,7 @@ class CloudViewer(q3d.Viewer):
|
|
|
94
94
|
return
|
|
95
95
|
cloud = cloud_item.load(file, append=append)
|
|
96
96
|
center = np.nanmean(cloud['xyz'].astype(np.float64), axis=0)
|
|
97
|
-
self.glwidget.set_cam_position(
|
|
97
|
+
self.glwidget.set_cam_position(center=center)
|
|
98
98
|
|
|
99
99
|
|
|
100
100
|
def main():
|
q3dviewer/tools/film_maker.py
CHANGED
|
@@ -17,11 +17,20 @@ import imageio.v2 as imageio
|
|
|
17
17
|
import os
|
|
18
18
|
|
|
19
19
|
|
|
20
|
+
def recover_center_euler(Twc, dist):
|
|
21
|
+
Rwc = Twc[:3, :3] # Extract rotation
|
|
22
|
+
twc = Twc[:3, 3] # Extract translation
|
|
23
|
+
tco = np.array([0, 0, dist]) # Camera frame origin
|
|
24
|
+
two = twc - Rwc @ tco # Compute center
|
|
25
|
+
euler = q3d.matrix_to_euler(Rwc)
|
|
26
|
+
return two, euler
|
|
27
|
+
|
|
28
|
+
|
|
20
29
|
class KeyFrame:
|
|
21
30
|
def __init__(self, Twc, lin_vel=10, ang_vel=np.pi/3, stop_time=0):
|
|
22
31
|
self.Twc = Twc
|
|
23
32
|
self.lin_vel = lin_vel
|
|
24
|
-
self.ang_vel = ang_vel
|
|
33
|
+
self.ang_vel = ang_vel # rad/s
|
|
25
34
|
self.stop_time = stop_time
|
|
26
35
|
self.item = q3d.FrameItem(Twc, width=3, color='#0000FF')
|
|
27
36
|
|
|
@@ -106,6 +115,7 @@ class CMMViewer(q3d.Viewer):
|
|
|
106
115
|
self.frame_list = QListWidget()
|
|
107
116
|
setting_layout.addWidget(self.frame_list)
|
|
108
117
|
self.frame_list.itemSelectionChanged.connect(self.on_select_frame)
|
|
118
|
+
self.frame_list.itemDoubleClicked.connect(self.on_double_click_frame)
|
|
109
119
|
self.installEventFilter(self)
|
|
110
120
|
|
|
111
121
|
# Add spin boxes for linear / angular velocity and stop time
|
|
@@ -147,19 +157,19 @@ class CMMViewer(q3d.Viewer):
|
|
|
147
157
|
view_matrix = self.glwidget.view_matrix
|
|
148
158
|
# Get camera pose in world frame
|
|
149
159
|
Twc = np.linalg.inv(view_matrix)
|
|
150
|
-
# Add the key frame to the end of the list
|
|
151
160
|
if self.key_frames:
|
|
152
161
|
prev = self.key_frames[-1]
|
|
153
|
-
key_frame = KeyFrame(Twc,
|
|
162
|
+
key_frame = KeyFrame(Twc,
|
|
154
163
|
lin_vel=prev.lin_vel,
|
|
155
|
-
ang_vel=prev.ang_vel
|
|
164
|
+
ang_vel=prev.ang_vel,
|
|
165
|
+
stop_time=prev.stop_time)
|
|
156
166
|
else:
|
|
157
167
|
key_frame = KeyFrame(Twc)
|
|
158
168
|
self.key_frames.append(key_frame)
|
|
159
169
|
# visualize this key frame using FrameItem
|
|
160
170
|
self.glwidget.add_item(key_frame.item)
|
|
161
171
|
# move the camera back to 0.5 meter, let the user see the frame
|
|
162
|
-
self.glwidget.update_dist(0.5)
|
|
172
|
+
# self.glwidget.update_dist(0.5)
|
|
163
173
|
# Add the key frame to the Qt ListWidget
|
|
164
174
|
item = QListWidgetItem(f"Frame {len(self.key_frames)}")
|
|
165
175
|
self.frame_list.addItem(item)
|
|
@@ -167,19 +177,22 @@ class CMMViewer(q3d.Viewer):
|
|
|
167
177
|
|
|
168
178
|
def del_key_frame(self):
|
|
169
179
|
current_index = self.frame_list.currentRow()
|
|
170
|
-
if current_index
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
180
|
+
if current_index < 0:
|
|
181
|
+
return
|
|
182
|
+
self.glwidget.remove_item(self.key_frames[current_index].item)
|
|
183
|
+
self.key_frames.pop(current_index)
|
|
184
|
+
self.frame_list.itemSelectionChanged.disconnect(self.on_select_frame)
|
|
185
|
+
self.frame_list.takeItem(current_index)
|
|
186
|
+
self.frame_list.itemSelectionChanged.connect(self.on_select_frame)
|
|
187
|
+
self.on_select_frame()
|
|
177
188
|
# Update frame labels
|
|
178
189
|
for i in range(len(self.key_frames)):
|
|
179
190
|
self.frame_list.item(i).setText(f"Frame {i + 1}")
|
|
180
191
|
|
|
181
192
|
def on_select_frame(self):
|
|
182
193
|
current = self.frame_list.currentRow()
|
|
194
|
+
if current < 0:
|
|
195
|
+
return
|
|
183
196
|
for i, frame in enumerate(self.key_frames):
|
|
184
197
|
if i == current:
|
|
185
198
|
# Highlight the selected frame
|
|
@@ -195,18 +208,31 @@ class CMMViewer(q3d.Viewer):
|
|
|
195
208
|
|
|
196
209
|
def set_frame_lin_vel(self, value):
|
|
197
210
|
current_index = self.frame_list.currentRow()
|
|
198
|
-
if current_index
|
|
199
|
-
|
|
211
|
+
if current_index < 0:
|
|
212
|
+
return
|
|
213
|
+
self.key_frames[current_index].lin_vel = value
|
|
200
214
|
|
|
201
215
|
def set_frame_ang_vel(self, value):
|
|
202
216
|
current_index = self.frame_list.currentRow()
|
|
203
|
-
if current_index
|
|
204
|
-
|
|
217
|
+
if current_index < 0:
|
|
218
|
+
return
|
|
219
|
+
self.key_frames[current_index].ang_vel = np.deg2rad(value)
|
|
205
220
|
|
|
206
221
|
def set_frame_stop_time(self, value):
|
|
207
222
|
current_index = self.frame_list.currentRow()
|
|
208
|
-
if current_index
|
|
209
|
-
|
|
223
|
+
if current_index < 0:
|
|
224
|
+
return
|
|
225
|
+
self.key_frames[current_index].stop_time = value
|
|
226
|
+
|
|
227
|
+
def on_double_click_frame(self, item):
|
|
228
|
+
current_index = self.frame_list.row(item)
|
|
229
|
+
if current_index < 0:
|
|
230
|
+
return
|
|
231
|
+
Twc = self.key_frames[current_index].Twc
|
|
232
|
+
center, euler = recover_center_euler(Twc, self.glwidget.dist)
|
|
233
|
+
self.glwidget.set_cam_position(center=center,
|
|
234
|
+
euler=euler)
|
|
235
|
+
|
|
210
236
|
|
|
211
237
|
def create_frames(self):
|
|
212
238
|
"""
|
|
@@ -243,8 +269,8 @@ class CMMViewer(q3d.Viewer):
|
|
|
243
269
|
self.current_frame_index = 0
|
|
244
270
|
self.timer.start(self.update_interval) # Adjust the interval as needed
|
|
245
271
|
self.is_playing = True
|
|
246
|
-
self.play_button.setStyleSheet("")
|
|
247
|
-
self.play_button.setText("
|
|
272
|
+
self.play_button.setStyleSheet("background-color: red")
|
|
273
|
+
self.play_button.setText("Playing")
|
|
248
274
|
self.record_checkbox.setEnabled(False)
|
|
249
275
|
if self.is_recording is True:
|
|
250
276
|
self.start_recording()
|
|
@@ -368,7 +394,7 @@ class CMMViewer(q3d.Viewer):
|
|
|
368
394
|
return
|
|
369
395
|
cloud = cloud_item.load(file, append=append)
|
|
370
396
|
center = np.nanmean(cloud['xyz'].astype(np.float64), axis=0)
|
|
371
|
-
self.glwidget.set_cam_position(
|
|
397
|
+
self.glwidget.set_cam_position(center=center)
|
|
372
398
|
|
|
373
399
|
def main():
|
|
374
400
|
import argparse
|
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
|
+
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: q3dviewer
|
|
3
|
+
Version: 1.1.1
|
|
4
|
+
Summary: A library designed for quickly deploying a 3D viewer.
|
|
5
|
+
Home-page: https://github.com/scomup/q3dviewer
|
|
6
|
+
Author: Liu Yang
|
|
7
|
+
License: UNKNOWN
|
|
8
|
+
Platform: UNKNOWN
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
Requires-Dist: PyOpenGL
|
|
14
|
+
Requires-Dist: imageio
|
|
15
|
+
Requires-Dist: imageio[ffmpeg]
|
|
16
|
+
Requires-Dist: laspy
|
|
17
|
+
Requires-Dist: meshio
|
|
18
|
+
Requires-Dist: numpy
|
|
19
|
+
Requires-Dist: pillow
|
|
20
|
+
Requires-Dist: pye57
|
|
21
|
+
Requires-Dist: pypcd4
|
|
22
|
+
Requires-Dist: pyside6
|
|
23
|
+
|
|
24
|
+
## q3dviewer
|
|
25
|
+
|
|
26
|
+
`q3dviewer` is a library designed for quickly deploying a 3D viewer. It is based on Qt (PySide6) and provides efficient OpenGL items for displaying 3D objects (e.g., point clouds, cameras, and 3D Gaussians). You can use it to visualize your 3D data or set up an efficient viewer application. It is inspired by PyQtGraph but focuses more on efficient 3D rendering.
|
|
27
|
+
|
|
28
|
+
## Installation
|
|
29
|
+
|
|
30
|
+
To install `q3dviewer`, execute the following command in your terminal on either Linux or Windows:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pip install q3dviewer
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Note for Windows Users
|
|
37
|
+
|
|
38
|
+
- Ensure that you have a Python 3 environment set up:
|
|
39
|
+
- Download and install Python 3 from the [official Python website](https://www.python.org/downloads/).
|
|
40
|
+
- During installation, make sure to check the "Add Python to PATH" option.
|
|
41
|
+
|
|
42
|
+
### Note for Linux Users
|
|
43
|
+
|
|
44
|
+
If you encounter an error related to loading the shared library `libxcb-cursor.so.0` on Ubuntu 20.04 or 22.04, please install `libxcb-cursor0`:
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
sudo apt-get install libxcb-cursor0
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Tools
|
|
51
|
+
|
|
52
|
+
Once installed, you can directly use the following tools:
|
|
53
|
+
|
|
54
|
+
### 1. Cloud Viewer
|
|
55
|
+
|
|
56
|
+
A tool for visualizing point cloud files. Launch it by executing the following command in your terminal:
|
|
57
|
+
|
|
58
|
+
```sh
|
|
59
|
+
cloud_viewer # The viewer will be displayed
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
*Alternatively*, if the path is not set (though it's not recommended):
|
|
63
|
+
|
|
64
|
+
```sh
|
|
65
|
+
python3 -m q3dviewer.tools.cloud_viewer
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
After the viewer launches, you can drag and drop files onto the window to display the point clouds. Multiple files can be dropped simultaneously to view them together. Supported formats include LAS, PCD, PLY, and E57.
|
|
69
|
+
|
|
70
|
+
For example, you can download and view point clouds of Tokyo in LAS format from the following link:
|
|
71
|
+
|
|
72
|
+
[Tokyo Point Clouds](https://www.geospatial.jp/ckan/dataset/tokyopc-23ku-2024/resource/7807d6d1-29f3-4b36-b0c8-f7aa0ea2cff3)
|
|
73
|
+
|
|
74
|
+

|
|
75
|
+
|
|
76
|
+
Press `M` on your keyboard to display a menu on the screen, where you can modify visualization settings for each item. For example, you can adjust various settings such as shape, size, color, and transparency for `CloudItem`.
|
|
77
|
+
|
|
78
|
+

|
|
79
|
+
|
|
80
|
+
### 2. ROS Viewer
|
|
81
|
+
|
|
82
|
+
A high-performance SLAM viewer compatible with ROS, serving as an alternative to RVIZ.
|
|
83
|
+
|
|
84
|
+
```sh
|
|
85
|
+
roscore &
|
|
86
|
+
ros_viewer
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### 3. Film Maker
|
|
90
|
+
|
|
91
|
+
Would you like to create a video from point cloud data? With Film Maker, you can easily create videos with simple operations. Just edit keyframes using the user-friendly GUI, and the software will automatically interpolate the keyframes to generate the video.
|
|
92
|
+
|
|
93
|
+
```sh
|
|
94
|
+
film_maker # drag and drop your cloud file to the window
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
* Space key to add a keyframe.
|
|
98
|
+
* Delete key to remove a keyframe.
|
|
99
|
+
|
|
100
|
+
Film Maker GUI:
|
|
101
|
+
|
|
102
|
+

|
|
103
|
+
|
|
104
|
+
### 4. Gaussian Viewer
|
|
105
|
+
|
|
106
|
+
A simple viewer for 3D Gaussians. See [EasyGaussianSplatting](https://github.com/scomup/EasyGaussianSplatting) for more information.
|
|
107
|
+
|
|
108
|
+
```sh
|
|
109
|
+
gaussian_viewer # Drag and drop your Gaussian file onto the window
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+

|
|
113
|
+
|
|
114
|
+
### 5. LiDAR-LiDAR Calibration Tools
|
|
115
|
+
|
|
116
|
+
A tool to compute the relative pose between two LiDARs. It allows for both manual adjustment in the settings screen and automatic calibration.
|
|
117
|
+
|
|
118
|
+
```sh
|
|
119
|
+
lidar_calib --lidar0=/YOUR_LIDAR0_TOPIC --lidar1=/YOUR_LIDAR1_TOPIC
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+

|
|
123
|
+
|
|
124
|
+
### 6. LiDAR-Camera Calibration Tools
|
|
125
|
+
|
|
126
|
+
A tool for calculating the relative pose between a LiDAR and a camera. It allows for manual adjustment in the settings screen and real-time verification of LiDAR point projection onto images.
|
|
127
|
+
|
|
128
|
+
```sh
|
|
129
|
+
lidar_cam_calib --lidar=/YOUR_LIDAR_TOPIC --camera=/YOUR_CAMERA_TOPIC --camera_info=/YOUR_CAMERA_INFO_TOPIC
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+

|
|
133
|
+
|
|
134
|
+
## Using as a Library
|
|
135
|
+
|
|
136
|
+
Using the examples above, you can easily customize and develop your own 3D viewer with `q3dviewer`. Below is a coding example.
|
|
137
|
+
|
|
138
|
+
### Custom 3D Viewer
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
#!/usr/bin/env python3
|
|
142
|
+
|
|
143
|
+
import q3dviewer as q3d # Import q3dviewer
|
|
144
|
+
|
|
145
|
+
def main():
|
|
146
|
+
# Create a Qt application
|
|
147
|
+
app = q3d.QApplication([])
|
|
148
|
+
|
|
149
|
+
# Create various 3D items
|
|
150
|
+
axis_item = q3d.AxisItem(size=0.5, width=5)
|
|
151
|
+
grid_item = q3d.GridItem(size=10, spacing=1)
|
|
152
|
+
|
|
153
|
+
# Create a viewer
|
|
154
|
+
viewer = q3d.Viewer(name='example')
|
|
155
|
+
|
|
156
|
+
# Add items to the viewer
|
|
157
|
+
viewer.add_items({
|
|
158
|
+
'grid': grid_item,
|
|
159
|
+
'axis': axis_item,
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
# Show the viewer & run the Qt application
|
|
163
|
+
viewer.show()
|
|
164
|
+
app.exec()
|
|
165
|
+
|
|
166
|
+
if __name__ == '__main__':
|
|
167
|
+
main()
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
`q3dviewer` provides the following 3D items:
|
|
171
|
+
|
|
172
|
+
- **AxisItem**: Displays coordinate axes or the origin position.
|
|
173
|
+
- **CloudItem**: Displays point clouds.
|
|
174
|
+
- **CloudIOItem**: Displays point clouds with input/output capabilities.
|
|
175
|
+
- **GaussianItem**: Displays 3D Gaussians.
|
|
176
|
+
- **GridItem**: Displays grids.
|
|
177
|
+
- **ImageItem**: Displays 2D images.
|
|
178
|
+
- **Text2DItem**: Displays 2D text.
|
|
179
|
+
- **LineItem**: Displays lines or trajectories.
|
|
180
|
+
|
|
181
|
+
### Developing Custom Items
|
|
182
|
+
|
|
183
|
+
In addition to the standard 3D items provided, you can visualize custom 3D items with simple coding. Below is a sample:
|
|
184
|
+
|
|
185
|
+
```python
|
|
186
|
+
from OpenGL.GL import *
|
|
187
|
+
import numpy as np
|
|
188
|
+
import q3dviewer as q3d
|
|
189
|
+
from PySide6.QtWidgets import QLabel, QSpinBox
|
|
190
|
+
|
|
191
|
+
class YourItem(q3d.BaseItem):
|
|
192
|
+
def __init__(self):
|
|
193
|
+
super(YourItem, self).__init__()
|
|
194
|
+
# Necessary initialization
|
|
195
|
+
|
|
196
|
+
def add_setting(self, layout):
|
|
197
|
+
# Initialize the settings screen
|
|
198
|
+
label = QLabel("Add your setting:")
|
|
199
|
+
layout.addWidget(label)
|
|
200
|
+
box = QSpinBox()
|
|
201
|
+
layout.addWidget(box)
|
|
202
|
+
|
|
203
|
+
def set_data(self, data):
|
|
204
|
+
# Obtain the data you want to visualize
|
|
205
|
+
pass
|
|
206
|
+
|
|
207
|
+
def initialize_gl(self):
|
|
208
|
+
# OpenGL initialization settings (if needed)
|
|
209
|
+
pass
|
|
210
|
+
|
|
211
|
+
def paint(self):
|
|
212
|
+
# Visualize 3D objects using OpenGL
|
|
213
|
+
pass
|
|
214
|
+
```
|
|
215
|
+
|
|
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/
|
|
27
|
-
q3dviewer/tools/cloud_viewer.py,sha256=lNvOD0XWDAfgdqc2aJGigcCJsaWUS4qxadjFwf59-OY,3861
|
|
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=PkkMW6__ml7850U2UV2G1Pn4Ahk4EWa7edcvI_5-Z4Y,16028
|
|
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.
|
|
41
|
-
q3dviewer-1.
|
|
42
|
-
q3dviewer-1.
|
|
43
|
-
q3dviewer-1.
|
|
44
|
-
q3dviewer-1.
|
|
45
|
-
q3dviewer-1.
|
|
42
|
+
q3dviewer-1.1.1.dist-info/LICENSE,sha256=81cMOyNfw8KLb1JnPYngGHJ5W83gSbZEBU9MEP3tl-E,1124
|
|
43
|
+
q3dviewer-1.1.1.dist-info/METADATA,sha256=wSqlVSSanm719PxnxIE7VOStyogcvsVwK1QobHCOLQI,7032
|
|
44
|
+
q3dviewer-1.1.1.dist-info/WHEEL,sha256=g4nMs7d-Xl9-xC9XovUrsDHGXt-FT0E17Yqo92DEfvY,92
|
|
45
|
+
q3dviewer-1.1.1.dist-info/entry_points.txt,sha256=EOjker7XYaBk70ffvNB_knPcfA33Bnlg21ZjEeM1EyI,362
|
|
46
|
+
q3dviewer-1.1.1.dist-info/top_level.txt,sha256=HFFDCbGu28txcGe2HPc46A7EPaguBa_b5oH7bufmxHM,10
|
|
47
|
+
q3dviewer-1.1.1.dist-info/RECORD,,
|