q3dviewer 1.0.8__py3-none-any.whl → 1.1.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/base_glwidget.py +62 -10
- q3dviewer/base_item.py +15 -0
- q3dviewer/custom_items/axis_item.py +31 -94
- q3dviewer/custom_items/cloud_item.py +36 -30
- q3dviewer/custom_items/frame_item.py +56 -36
- q3dviewer/custom_items/gaussian_item.py +3 -3
- q3dviewer/custom_items/grid_item.py +88 -37
- q3dviewer/custom_items/image_item.py +1 -2
- q3dviewer/custom_items/line_item.py +4 -5
- q3dviewer/gau_io.py +0 -168
- q3dviewer/glwidget.py +22 -16
- q3dviewer/test/test_interpolation.py +58 -0
- q3dviewer/test/test_rendering.py +72 -0
- q3dviewer/tools/cinematographer.py +367 -0
- q3dviewer/tools/cloud_viewer.py +2 -2
- q3dviewer/tools/film_maker.py +421 -0
- q3dviewer/tools/lidar_calib.py +11 -22
- q3dviewer/tools/lidar_cam_calib.py +9 -20
- q3dviewer/utils/maths.py +155 -5
- q3dviewer/viewer.py +30 -7
- q3dviewer-1.1.0.dist-info/METADATA +214 -0
- q3dviewer-1.1.0.dist-info/RECORD +45 -0
- {q3dviewer-1.0.8.dist-info → q3dviewer-1.1.0.dist-info}/entry_points.txt +1 -1
- q3dviewer/basic_window.py +0 -228
- q3dviewer/cloud_viewer.py +0 -74
- q3dviewer/custom_items/camera_frame_item.py +0 -173
- q3dviewer/custom_items/trajectory_item.py +0 -79
- q3dviewer/utils.py +0 -71
- q3dviewer-1.0.8.dist-info/METADATA +0 -21
- q3dviewer-1.0.8.dist-info/RECORD +0 -46
- {q3dviewer-1.0.8.dist-info → q3dviewer-1.1.0.dist-info}/LICENSE +0 -0
- {q3dviewer-1.0.8.dist-info → q3dviewer-1.1.0.dist-info}/WHEEL +0 -0
- {q3dviewer-1.0.8.dist-info → q3dviewer-1.1.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,421 @@
|
|
|
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
|
+
import numpy as np
|
|
9
|
+
import q3dviewer as q3d
|
|
10
|
+
from PySide6.QtWidgets import QVBoxLayout, QListWidget, QListWidgetItem, QPushButton, QDoubleSpinBox, QCheckBox, QLineEdit, QMessageBox, QLabel, QHBoxLayout, QDockWidget, QWidget, QComboBox
|
|
11
|
+
from PySide6.QtCore import QTimer
|
|
12
|
+
from q3dviewer.tools.cloud_viewer import ProgressDialog, FileLoaderThread
|
|
13
|
+
from PySide6 import QtCore
|
|
14
|
+
from PySide6.QtGui import QKeyEvent
|
|
15
|
+
from q3dviewer import GLWidget
|
|
16
|
+
import imageio.v2 as imageio
|
|
17
|
+
import os
|
|
18
|
+
|
|
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
|
+
|
|
29
|
+
class KeyFrame:
|
|
30
|
+
def __init__(self, Twc, lin_vel=10, ang_vel=np.pi/3, stop_time=0):
|
|
31
|
+
self.Twc = Twc
|
|
32
|
+
self.lin_vel = lin_vel
|
|
33
|
+
self.ang_vel = ang_vel # rad/s
|
|
34
|
+
self.stop_time = stop_time
|
|
35
|
+
self.item = q3d.FrameItem(Twc, width=3, color='#0000FF')
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class CustomGLWidget(GLWidget):
|
|
39
|
+
def __init__(self, viewer):
|
|
40
|
+
super().__init__()
|
|
41
|
+
self.viewer = viewer # Add a viewer handle
|
|
42
|
+
|
|
43
|
+
def keyPressEvent(self, ev: QKeyEvent):
|
|
44
|
+
if ev.key() == QtCore.Qt.Key_Space:
|
|
45
|
+
self.viewer.add_key_frame()
|
|
46
|
+
elif ev.key() == QtCore.Qt.Key_Delete:
|
|
47
|
+
self.viewer.del_key_frame()
|
|
48
|
+
elif ev.key() == QtCore.Qt.Key_C:
|
|
49
|
+
self.viewer.dock.show()
|
|
50
|
+
super().keyPressEvent(ev)
|
|
51
|
+
|
|
52
|
+
class CMMViewer(q3d.Viewer):
|
|
53
|
+
"""
|
|
54
|
+
This class is a subclass of Viewer, which is used to create a cloud movie maker.
|
|
55
|
+
"""
|
|
56
|
+
def __init__(self, **kwargs):
|
|
57
|
+
self.key_frames = []
|
|
58
|
+
self.video_path = os.path.join(os.path.expanduser("~"), "output.mp4")
|
|
59
|
+
super().__init__(**kwargs, gl_widget_class=lambda: CustomGLWidget(self))
|
|
60
|
+
# for drop cloud file
|
|
61
|
+
self.setAcceptDrops(True)
|
|
62
|
+
|
|
63
|
+
def add_control_panel(self, main_layout):
|
|
64
|
+
"""
|
|
65
|
+
Add a control panel to the viewer.
|
|
66
|
+
"""
|
|
67
|
+
# Create a vertical layout for the settings
|
|
68
|
+
setting_layout = QVBoxLayout()
|
|
69
|
+
|
|
70
|
+
# Buttons to add and delete key frames
|
|
71
|
+
add_button = QPushButton("Add Key Frame (Key Space)")
|
|
72
|
+
add_button.clicked.connect(self.add_key_frame)
|
|
73
|
+
setting_layout.addWidget(add_button)
|
|
74
|
+
del_button = QPushButton("Delete Key Frame (Key Delete)")
|
|
75
|
+
del_button.clicked.connect(self.del_key_frame)
|
|
76
|
+
setting_layout.addWidget(del_button)
|
|
77
|
+
|
|
78
|
+
# Add play/stop button
|
|
79
|
+
self.play_button = QPushButton("Play")
|
|
80
|
+
self.play_button.clicked.connect(self.toggle_playback)
|
|
81
|
+
setting_layout.addWidget(self.play_button)
|
|
82
|
+
|
|
83
|
+
# add a timer to play the frames
|
|
84
|
+
self.timer = QTimer()
|
|
85
|
+
self.timer.timeout.connect(self.play_frames)
|
|
86
|
+
self.current_frame_index = 0
|
|
87
|
+
self.is_playing = False
|
|
88
|
+
self.is_recording = False
|
|
89
|
+
|
|
90
|
+
# Add record checkbox
|
|
91
|
+
self.record_checkbox = QCheckBox("Record")
|
|
92
|
+
self.record_checkbox.stateChanged.connect(self.toggle_recording)
|
|
93
|
+
setting_layout.addWidget(self.record_checkbox)
|
|
94
|
+
|
|
95
|
+
# Add video path setting
|
|
96
|
+
video_path_layout = QHBoxLayout()
|
|
97
|
+
label_video_path = QLabel("Video Path:")
|
|
98
|
+
video_path_layout.addWidget(label_video_path)
|
|
99
|
+
self.video_path_edit = QLineEdit()
|
|
100
|
+
self.video_path_edit.setText(self.video_path)
|
|
101
|
+
self.video_path_edit.textChanged.connect(self.update_video_path)
|
|
102
|
+
video_path_layout.addWidget(self.video_path_edit)
|
|
103
|
+
setting_layout.addLayout(video_path_layout)
|
|
104
|
+
|
|
105
|
+
# Add codec setting
|
|
106
|
+
codec_layout = QHBoxLayout()
|
|
107
|
+
label_codec = QLabel("Codec:")
|
|
108
|
+
codec_layout.addWidget(label_codec)
|
|
109
|
+
self.codec_combo = QComboBox()
|
|
110
|
+
self.codec_combo.addItems(["mjpeg", "mpeg4", "libx264", "libx265"])
|
|
111
|
+
codec_layout.addWidget(self.codec_combo)
|
|
112
|
+
setting_layout.addLayout(codec_layout)
|
|
113
|
+
|
|
114
|
+
# Add a list of key frames
|
|
115
|
+
self.frame_list = QListWidget()
|
|
116
|
+
setting_layout.addWidget(self.frame_list)
|
|
117
|
+
self.frame_list.itemSelectionChanged.connect(self.on_select_frame)
|
|
118
|
+
self.frame_list.itemDoubleClicked.connect(self.on_double_click_frame)
|
|
119
|
+
self.installEventFilter(self)
|
|
120
|
+
|
|
121
|
+
# Add spin boxes for linear / angular velocity and stop time
|
|
122
|
+
self.lin_vel_spinbox = QDoubleSpinBox()
|
|
123
|
+
self.lin_vel_spinbox.setPrefix("Linear Velocity (m/s): ")
|
|
124
|
+
self.lin_vel_spinbox.setRange(0, 1000)
|
|
125
|
+
self.lin_vel_spinbox.valueChanged.connect(self.set_frame_lin_vel)
|
|
126
|
+
setting_layout.addWidget(self.lin_vel_spinbox)
|
|
127
|
+
|
|
128
|
+
self.lin_ang_spinbox = QDoubleSpinBox()
|
|
129
|
+
self.lin_ang_spinbox.setPrefix("Angular Velocity (deg/s): ")
|
|
130
|
+
self.lin_ang_spinbox.setRange(0, 360)
|
|
131
|
+
self.lin_ang_spinbox.valueChanged.connect(self.set_frame_ang_vel)
|
|
132
|
+
setting_layout.addWidget(self.lin_ang_spinbox)
|
|
133
|
+
|
|
134
|
+
self.stop_time_spinbox = QDoubleSpinBox()
|
|
135
|
+
self.stop_time_spinbox.setPrefix("Stop Time: ")
|
|
136
|
+
self.stop_time_spinbox.setRange(0, 100)
|
|
137
|
+
self.stop_time_spinbox.valueChanged.connect(self.set_frame_stop_time)
|
|
138
|
+
setting_layout.addWidget(self.stop_time_spinbox)
|
|
139
|
+
|
|
140
|
+
setting_layout.setAlignment(QtCore.Qt.AlignTop)
|
|
141
|
+
|
|
142
|
+
# Create a dock widget for the settings
|
|
143
|
+
dock_widget = QDockWidget("Settings", self)
|
|
144
|
+
dock_widget.setWidget(QWidget())
|
|
145
|
+
dock_widget.widget().setLayout(setting_layout)
|
|
146
|
+
self.addDockWidget(QtCore.Qt.RightDockWidgetArea, dock_widget)
|
|
147
|
+
|
|
148
|
+
# Add the dock widget to the main layout
|
|
149
|
+
main_layout.addWidget(dock_widget)
|
|
150
|
+
self.dock = dock_widget
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def update_video_path(self, path):
|
|
154
|
+
self.video_path = path
|
|
155
|
+
|
|
156
|
+
def add_key_frame(self):
|
|
157
|
+
view_matrix = self.glwidget.view_matrix
|
|
158
|
+
# Get camera pose in world frame
|
|
159
|
+
Twc = np.linalg.inv(view_matrix)
|
|
160
|
+
if self.key_frames:
|
|
161
|
+
prev = self.key_frames[-1]
|
|
162
|
+
key_frame = KeyFrame(Twc,
|
|
163
|
+
lin_vel=prev.lin_vel,
|
|
164
|
+
ang_vel=prev.ang_vel,
|
|
165
|
+
stop_time=prev.stop_time)
|
|
166
|
+
else:
|
|
167
|
+
key_frame = KeyFrame(Twc)
|
|
168
|
+
self.key_frames.append(key_frame)
|
|
169
|
+
# visualize this key frame using FrameItem
|
|
170
|
+
self.glwidget.add_item(key_frame.item)
|
|
171
|
+
# move the camera back to 0.5 meter, let the user see the frame
|
|
172
|
+
# self.glwidget.update_dist(0.5)
|
|
173
|
+
# Add the key frame to the Qt ListWidget
|
|
174
|
+
item = QListWidgetItem(f"Frame {len(self.key_frames)}")
|
|
175
|
+
self.frame_list.addItem(item)
|
|
176
|
+
self.frame_list.setCurrentRow(len(self.key_frames) - 1)
|
|
177
|
+
|
|
178
|
+
def del_key_frame(self):
|
|
179
|
+
current_index = self.frame_list.currentRow()
|
|
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()
|
|
188
|
+
# Update frame labels
|
|
189
|
+
for i in range(len(self.key_frames)):
|
|
190
|
+
self.frame_list.item(i).setText(f"Frame {i + 1}")
|
|
191
|
+
|
|
192
|
+
def on_select_frame(self):
|
|
193
|
+
current = self.frame_list.currentRow()
|
|
194
|
+
if current < 0:
|
|
195
|
+
return
|
|
196
|
+
for i, frame in enumerate(self.key_frames):
|
|
197
|
+
if i == current:
|
|
198
|
+
# Highlight the selected frame
|
|
199
|
+
frame.item.set_color('#FF0000')
|
|
200
|
+
frame.item.set_line_width(5)
|
|
201
|
+
# show current frame's parameters in the spinboxes
|
|
202
|
+
self.lin_vel_spinbox.setValue(frame.lin_vel)
|
|
203
|
+
self.lin_ang_spinbox.setValue(np.rad2deg(frame.ang_vel))
|
|
204
|
+
self.stop_time_spinbox.setValue(frame.stop_time)
|
|
205
|
+
else:
|
|
206
|
+
frame.item.set_color('#0000FF')
|
|
207
|
+
frame.item.set_line_width(3)
|
|
208
|
+
|
|
209
|
+
def set_frame_lin_vel(self, value):
|
|
210
|
+
current_index = self.frame_list.currentRow()
|
|
211
|
+
if current_index < 0:
|
|
212
|
+
return
|
|
213
|
+
self.key_frames[current_index].lin_vel = value
|
|
214
|
+
|
|
215
|
+
def set_frame_ang_vel(self, value):
|
|
216
|
+
current_index = self.frame_list.currentRow()
|
|
217
|
+
if current_index < 0:
|
|
218
|
+
return
|
|
219
|
+
self.key_frames[current_index].ang_vel = np.deg2rad(value)
|
|
220
|
+
|
|
221
|
+
def set_frame_stop_time(self, value):
|
|
222
|
+
current_index = self.frame_list.currentRow()
|
|
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
|
+
|
|
236
|
+
|
|
237
|
+
def create_frames(self):
|
|
238
|
+
"""
|
|
239
|
+
Create the frames for playback by interpolating between key frames.
|
|
240
|
+
"""
|
|
241
|
+
self.frames = []
|
|
242
|
+
dt = 1 / float(self.update_interval)
|
|
243
|
+
for i in range(len(self.key_frames) - 1):
|
|
244
|
+
current_frame = self.key_frames[i]
|
|
245
|
+
if current_frame.stop_time > 0:
|
|
246
|
+
num_steps = int(current_frame.stop_time / dt)
|
|
247
|
+
for j in range(num_steps):
|
|
248
|
+
self.frames.append([i, current_frame.Twc])
|
|
249
|
+
next_frame = self.key_frames[i + 1]
|
|
250
|
+
Ts = q3d.interpolate_pose(current_frame.Twc, next_frame.Twc,
|
|
251
|
+
current_frame.lin_vel,
|
|
252
|
+
current_frame.ang_vel,
|
|
253
|
+
dt)
|
|
254
|
+
for T in Ts:
|
|
255
|
+
self.frames.append([i, T])
|
|
256
|
+
|
|
257
|
+
print(f"Total frames: {len(self.frames)}")
|
|
258
|
+
print(f"Total time: {len(self.frames) * dt:.2f} seconds")
|
|
259
|
+
|
|
260
|
+
def toggle_playback(self):
|
|
261
|
+
if self.is_playing:
|
|
262
|
+
self.stop_playback()
|
|
263
|
+
else:
|
|
264
|
+
self.start_playback()
|
|
265
|
+
|
|
266
|
+
def start_playback(self):
|
|
267
|
+
if self.key_frames:
|
|
268
|
+
self.create_frames()
|
|
269
|
+
self.current_frame_index = 0
|
|
270
|
+
self.timer.start(self.update_interval) # Adjust the interval as needed
|
|
271
|
+
self.is_playing = True
|
|
272
|
+
self.play_button.setStyleSheet("")
|
|
273
|
+
self.play_button.setText("Stop")
|
|
274
|
+
self.record_checkbox.setEnabled(False)
|
|
275
|
+
if self.is_recording is True:
|
|
276
|
+
self.start_recording()
|
|
277
|
+
|
|
278
|
+
def stop_playback(self):
|
|
279
|
+
self.timer.stop()
|
|
280
|
+
self.is_playing = False
|
|
281
|
+
self.play_button.setStyleSheet("")
|
|
282
|
+
self.play_button.setText("Play")
|
|
283
|
+
self.record_checkbox.setEnabled(True)
|
|
284
|
+
self.frame_list.setCurrentRow(len(self.key_frames) - 1)
|
|
285
|
+
if self.is_recording:
|
|
286
|
+
self.stop_recording()
|
|
287
|
+
|
|
288
|
+
def play_frames(self):
|
|
289
|
+
"""
|
|
290
|
+
callback function for the timer to play the frames
|
|
291
|
+
"""
|
|
292
|
+
# play the frames
|
|
293
|
+
if self.current_frame_index < len(self.frames):
|
|
294
|
+
key_id, Tcw = self.frames[self.current_frame_index]
|
|
295
|
+
self.glwidget.set_view_matrix(np.linalg.inv(Tcw))
|
|
296
|
+
self.frame_list.setCurrentRow(key_id)
|
|
297
|
+
self.current_frame_index += 1
|
|
298
|
+
if self.is_recording:
|
|
299
|
+
self.record_frame()
|
|
300
|
+
else:
|
|
301
|
+
self.stop_playback()
|
|
302
|
+
|
|
303
|
+
def toggle_recording(self, state):
|
|
304
|
+
if state == 2:
|
|
305
|
+
self.is_recording = True
|
|
306
|
+
else:
|
|
307
|
+
self.is_recording = False
|
|
308
|
+
|
|
309
|
+
def start_recording(self):
|
|
310
|
+
self.is_recording = True
|
|
311
|
+
self.frames_to_record = []
|
|
312
|
+
video_path = self.video_path_edit.text()
|
|
313
|
+
codec = self.codec_combo.currentText()
|
|
314
|
+
self.play_button.setStyleSheet("background-color: red")
|
|
315
|
+
self.play_button.setText("Recording")
|
|
316
|
+
self.writer = imageio.get_writer(video_path,
|
|
317
|
+
fps=self.update_interval,
|
|
318
|
+
codec=codec) # quality=10
|
|
319
|
+
# disable the all the frame_item while recording
|
|
320
|
+
for frame in self.key_frames:
|
|
321
|
+
frame.item.hide()
|
|
322
|
+
self.dock.hide() # Hide the dock while recording
|
|
323
|
+
|
|
324
|
+
def stop_recording(self, save_movie=True):
|
|
325
|
+
self.is_recording = False
|
|
326
|
+
self.record_checkbox.setChecked(False)
|
|
327
|
+
# enable the all the frame_item after recording
|
|
328
|
+
for frame in self.key_frames:
|
|
329
|
+
frame.item.show()
|
|
330
|
+
if hasattr(self, 'writer') and save_movie:
|
|
331
|
+
self.writer.close()
|
|
332
|
+
self.show_save_message()
|
|
333
|
+
self.dock.show() # Show the dock when recording stops
|
|
334
|
+
|
|
335
|
+
def show_save_message(self):
|
|
336
|
+
msg_box = QMessageBox()
|
|
337
|
+
msg_box.setIcon(QMessageBox.Information)
|
|
338
|
+
msg_box.setWindowTitle("Video Saved")
|
|
339
|
+
msg_box.setText(f"Video saved to {self.video_path_edit.text()}")
|
|
340
|
+
msg_box.setStandardButtons(QMessageBox.Ok)
|
|
341
|
+
msg_box.exec()
|
|
342
|
+
|
|
343
|
+
def record_frame(self):
|
|
344
|
+
frame = self.glwidget.capture_frame()
|
|
345
|
+
# make sure the frame size is multiple of 16
|
|
346
|
+
height, width, _ = frame.shape
|
|
347
|
+
if height % 16 != 0 or width % 16 != 0:
|
|
348
|
+
frame = frame[:-(height % 16), :-(width % 16), :]
|
|
349
|
+
frame = np.ascontiguousarray(frame)
|
|
350
|
+
self.frames_to_record.append(frame)
|
|
351
|
+
try:
|
|
352
|
+
self.writer.append_data(frame)
|
|
353
|
+
except Exception as e:
|
|
354
|
+
print("Don't change the window size during recording.")
|
|
355
|
+
self.stop_recording(False) # Stop recording without saving
|
|
356
|
+
self.stop_playback()
|
|
357
|
+
|
|
358
|
+
def eventFilter(self, obj, event):
|
|
359
|
+
if event.type() == QtCore.QEvent.KeyPress:
|
|
360
|
+
if event.key() == QtCore.Qt.Key_Delete:
|
|
361
|
+
self.del_key_frame()
|
|
362
|
+
return True
|
|
363
|
+
return super().eventFilter(obj, event)
|
|
364
|
+
|
|
365
|
+
def dragEnterEvent(self, event):
|
|
366
|
+
if event.mimeData().hasUrls():
|
|
367
|
+
event.accept()
|
|
368
|
+
else:
|
|
369
|
+
event.ignore()
|
|
370
|
+
|
|
371
|
+
def dropEvent(self, event):
|
|
372
|
+
"""
|
|
373
|
+
Overwrite the drop event to open the cloud file.
|
|
374
|
+
"""
|
|
375
|
+
self.progress_dialog = ProgressDialog(self)
|
|
376
|
+
self.progress_dialog.show()
|
|
377
|
+
files = event.mimeData().urls()
|
|
378
|
+
self.progress_thread = FileLoaderThread(self, files)
|
|
379
|
+
self['cloud'].load(files[0].toLocalFile(), append=False)
|
|
380
|
+
self.progress_thread.progress.connect(self.file_loading_progress)
|
|
381
|
+
self.progress_thread.finished.connect(self.file_loading_finished)
|
|
382
|
+
self.progress_thread.start()
|
|
383
|
+
|
|
384
|
+
def file_loading_progress(self, value):
|
|
385
|
+
self.progress_dialog.set_value(value)
|
|
386
|
+
|
|
387
|
+
def file_loading_finished(self):
|
|
388
|
+
self.progress_dialog.close()
|
|
389
|
+
|
|
390
|
+
def open_cloud_file(self, file, append=False):
|
|
391
|
+
cloud_item = self['cloud']
|
|
392
|
+
if cloud_item is None:
|
|
393
|
+
print("Can't find clouditem.")
|
|
394
|
+
return
|
|
395
|
+
cloud = cloud_item.load(file, append=append)
|
|
396
|
+
center = np.nanmean(cloud['xyz'].astype(np.float64), axis=0)
|
|
397
|
+
self.glwidget.set_cam_position(center=center)
|
|
398
|
+
|
|
399
|
+
def main():
|
|
400
|
+
import argparse
|
|
401
|
+
parser = argparse.ArgumentParser()
|
|
402
|
+
parser.add_argument("--path", help="the cloud file path")
|
|
403
|
+
args = parser.parse_args()
|
|
404
|
+
app = q3d.QApplication(['Film Maker'])
|
|
405
|
+
viewer = CMMViewer(name='Film Maker', update_interval=30)
|
|
406
|
+
cloud_item = q3d.CloudIOItem(size=0.1, point_type='SPHERE', alpha=0.5, depth_test=True)
|
|
407
|
+
grid_item = q3d.GridItem(size=1000, spacing=20)
|
|
408
|
+
|
|
409
|
+
viewer.add_items(
|
|
410
|
+
{'cloud': cloud_item, 'grid': grid_item})
|
|
411
|
+
|
|
412
|
+
if args.path:
|
|
413
|
+
pcd_fn = args.path
|
|
414
|
+
viewer.open_cloud_file(pcd_fn)
|
|
415
|
+
|
|
416
|
+
viewer.show()
|
|
417
|
+
app.exec()
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
if __name__ == '__main__':
|
|
421
|
+
main()
|
q3dviewer/tools/lidar_calib.py
CHANGED
|
@@ -43,7 +43,7 @@ class CustomDoubleSpinBox(QDoubleSpinBox):
|
|
|
43
43
|
return float(text)
|
|
44
44
|
|
|
45
45
|
|
|
46
|
-
class
|
|
46
|
+
class LiDARCalibViewer(q3d.Viewer):
|
|
47
47
|
def __init__(self, **kwargs):
|
|
48
48
|
self.t01 = np.array([0, 0, 0])
|
|
49
49
|
self.R01 = np.eye(3)
|
|
@@ -51,15 +51,15 @@ class ViewerWithPanel(q3d.Viewer):
|
|
|
51
51
|
self.radius = 0.2
|
|
52
52
|
super().__init__(**kwargs)
|
|
53
53
|
|
|
54
|
-
def
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
54
|
+
def default_gl_setting(self, glwidget):
|
|
55
|
+
# Set camera position and background color
|
|
56
|
+
glwidget.set_bg_color('#ffffff')
|
|
57
|
+
glwidget.set_cam_position(distance=5)
|
|
58
|
+
|
|
59
|
+
def add_control_panel(self, main_layout):
|
|
60
60
|
# Create a vertical layout for the settings
|
|
61
61
|
setting_layout = QVBoxLayout()
|
|
62
|
-
|
|
62
|
+
setting_layout.setAlignment(QtCore.Qt.AlignTop)
|
|
63
63
|
# Add XYZ spin boxes
|
|
64
64
|
label_xyz = QLabel("Set XYZ:")
|
|
65
65
|
setting_layout.addWidget(label_xyz)
|
|
@@ -135,19 +135,8 @@ class ViewerWithPanel(q3d.Viewer):
|
|
|
135
135
|
self.box_pitch.valueChanged.connect(self.update_rpy)
|
|
136
136
|
self.box_yaw.valueChanged.connect(self.update_rpy)
|
|
137
137
|
|
|
138
|
-
# Add a stretch to push the widgets to the top
|
|
139
|
-
setting_layout.addStretch(1)
|
|
140
|
-
|
|
141
|
-
self.glwidget = q3d.GLWidget()
|
|
142
138
|
main_layout.addLayout(setting_layout)
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
timer = QtCore.QTimer(self)
|
|
146
|
-
timer.setInterval(20) # period, in milliseconds
|
|
147
|
-
timer.timeout.connect(self.update)
|
|
148
|
-
self.glwidget.set_cam_position(distance=5)
|
|
149
|
-
self.glwidget.set_bg_color('#ffffff')
|
|
150
|
-
timer.start()
|
|
139
|
+
|
|
151
140
|
|
|
152
141
|
def update_radius(self):
|
|
153
142
|
self.radius = self.box_radius.value()
|
|
@@ -271,8 +260,8 @@ def main():
|
|
|
271
260
|
args = parser.parse_args()
|
|
272
261
|
|
|
273
262
|
app = q3d.QApplication(["LiDAR Calib"])
|
|
274
|
-
viewer =
|
|
275
|
-
grid_item = q3d.GridItem(size=10, spacing=1, color=
|
|
263
|
+
viewer = LiDARCalibViewer(name='LiDAR Calib')
|
|
264
|
+
grid_item = q3d.GridItem(size=10, spacing=1, color='#00000040')
|
|
276
265
|
scan0_item = q3d.CloudItem(
|
|
277
266
|
size=2, alpha=1, color_mode='FLAT', color='#ff0000')
|
|
278
267
|
scan1_item = q3d.CloudItem(
|
|
@@ -36,7 +36,7 @@ class CustomDoubleSpinBox(QDoubleSpinBox):
|
|
|
36
36
|
return float(text)
|
|
37
37
|
|
|
38
38
|
|
|
39
|
-
class
|
|
39
|
+
class LidarCamViewer(q3d.Viewer):
|
|
40
40
|
def __init__(self, **kwargs):
|
|
41
41
|
# b: camera body frame
|
|
42
42
|
# c: camera image frame
|
|
@@ -54,15 +54,15 @@ class ViewerWithPanel(q3d.Viewer):
|
|
|
54
54
|
self.en_rgb = False
|
|
55
55
|
super().__init__(**kwargs)
|
|
56
56
|
|
|
57
|
-
def
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
center_widget.setLayout(main_layout)
|
|
57
|
+
def default_gl_setting(self, glwidget):
|
|
58
|
+
# Set camera position and background color
|
|
59
|
+
glwidget.set_bg_color('#ffffff')
|
|
60
|
+
glwidget.set_cam_position(distance=5)
|
|
62
61
|
|
|
62
|
+
def add_control_panel(self, main_layout):
|
|
63
63
|
# Create a vertical layout for the settings
|
|
64
64
|
setting_layout = QVBoxLayout()
|
|
65
|
-
|
|
65
|
+
setting_layout.setAlignment(QtCore.Qt.AlignTop)
|
|
66
66
|
# Add a checkbox for RGB
|
|
67
67
|
self.checkbox_rgb = QCheckBox("Enable RGB Cloud")
|
|
68
68
|
self.checkbox_rgb.setChecked(False)
|
|
@@ -148,19 +148,8 @@ class ViewerWithPanel(q3d.Viewer):
|
|
|
148
148
|
self.box_pitch.valueChanged.connect(self.update_rpy)
|
|
149
149
|
self.box_yaw.valueChanged.connect(self.update_rpy)
|
|
150
150
|
|
|
151
|
-
# Add a stretch to push the widgets to the top
|
|
152
|
-
setting_layout.addStretch(1)
|
|
153
|
-
|
|
154
|
-
self.glwidget = q3d.GLWidget()
|
|
155
151
|
main_layout.addLayout(setting_layout)
|
|
156
|
-
main_layout.addWidget(self.glwidget, 1)
|
|
157
152
|
|
|
158
|
-
timer = QtCore.QTimer(self)
|
|
159
|
-
timer.setInterval(20) # period, in milliseconds
|
|
160
|
-
timer.timeout.connect(self.update)
|
|
161
|
-
self.glwidget.set_cam_position(distance=5)
|
|
162
|
-
self.glwidget.set_bg_color('#ffffff')
|
|
163
|
-
timer.start()
|
|
164
153
|
|
|
165
154
|
def update_point_size(self):
|
|
166
155
|
self.psize = self.box_psize.value()
|
|
@@ -292,8 +281,8 @@ def main():
|
|
|
292
281
|
args = parser.parse_args()
|
|
293
282
|
|
|
294
283
|
app = q3d.QApplication(['LiDAR Cam Calib'])
|
|
295
|
-
viewer =
|
|
296
|
-
grid_item = q3d.GridItem(size=10, spacing=1, color=
|
|
284
|
+
viewer = LidarCamViewer(name='LiDAR Cam Calib')
|
|
285
|
+
grid_item = q3d.GridItem(size=10, spacing=1, color='#00000040')
|
|
297
286
|
scan_item = q3d.CloudItem(size=2, alpha=1, color_mode='I')
|
|
298
287
|
img_item = q3d.ImageItem(pos=np.array([0, 0]), size=np.array([800, 600]))
|
|
299
288
|
viewer.add_items({'scan': scan_item, 'grid': grid_item, 'img': img_item})
|