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/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)
@@ -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(pos=center)
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(pos=center)
97
+ self.glwidget.set_cam_position(center=center)
98
98
 
99
99
 
100
100
  def main():
@@ -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 # rad/s
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 >= 0:
171
- self.glwidget.remove_item(self.key_frames[current_index].item)
172
- self.key_frames.pop(current_index)
173
- self.frame_list.itemSelectionChanged.disconnect(self.on_select_frame)
174
- self.frame_list.takeItem(current_index)
175
- self.frame_list.itemSelectionChanged.connect(self.on_select_frame)
176
- self.on_select_frame()
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 >= 0:
199
- self.key_frames[current_index].lin_vel = value
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 >= 0:
204
- self.key_frames[current_index].ang_vel = np.deg2rad(value)
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 >= 0:
209
- self.key_frames[current_index].stop_time = value
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("Stop")
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(pos=center)
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
+ ![Cloud Viewer Screenshot](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149168/03c981c6-1aec-e5b9-4536-e07e1e56ff29.png)
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
+ ![Cloud Viewer Settings](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149168/deeb996a-e419-58f4-6bc2-535099b1b73a.png)
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
+ ![Screenshot from 2025-02-02 18-20-51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149168/a1a6ad63-237c-482e-439d-e760223c59ca.png)
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
+ ![Gaussian Viewer GIF](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149168/441e6f5a-214d-f7c1-11bf-5fa79e63b38e.gif)
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
+ ![LiDAR Calibration](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149168/5a8a9903-a42a-8322-1d23-0cbecd3fa99a.png)
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
+ ![LiDAR-Camera Calibration](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149168/f8359820-2ae7-aa37-6577-0fa035f4dd95.png)
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=7CJKea2JSP78jsESZJP38yfLhhRINF-VQ9R8sMXwAo0,10697
2
+ q3dviewer/base_glwidget.py,sha256=tk8icQsRZ3OrSLkHvywmrxElk6jpMooLDZdbZ3sRLnk,11313
3
3
  q3dviewer/base_item.py,sha256=lzb04oRaS4rRJrAP6C1Bu4ugK237FgupMTB97zjNVFw,1768
4
- q3dviewer/gau_io.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
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=7r6s_j1qEUfLe70t-M68-oIrGRrPOUcZp18ML6jST0s,12542
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
- 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=MtmmL1-iogSmHXCZc0MLk35_bP-RMBN8twaOtTkv77w,15180
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.0.9.dist-info/LICENSE,sha256=81cMOyNfw8KLb1JnPYngGHJ5W83gSbZEBU9MEP3tl-E,1124
41
- q3dviewer-1.0.9.dist-info/METADATA,sha256=zD6g_ySQ3hityOMXY5cKo1wpbaYk0whJEVzQIRReqL4,275
42
- q3dviewer-1.0.9.dist-info/WHEEL,sha256=g4nMs7d-Xl9-xC9XovUrsDHGXt-FT0E17Yqo92DEfvY,92
43
- q3dviewer-1.0.9.dist-info/entry_points.txt,sha256=aeUdGH7UIgMZEMFUc-0xPZWspY95GoPdZcZuLceq85g,361
44
- q3dviewer-1.0.9.dist-info/top_level.txt,sha256=HFFDCbGu28txcGe2HPc46A7EPaguBa_b5oH7bufmxHM,10
45
- q3dviewer-1.0.9.dist-info/RECORD,,
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,,
@@ -6,3 +6,4 @@ lidar_calib = q3dviewer.tools.lidar_calib:main
6
6
  lidar_cam_calib = q3dviewer.tools.lidar_cam_calib:main
7
7
  mesh_viewer = q3dviewer.tools.mesh_viewer:main
8
8
  ros_viewer = q3dviewer.tools.ros_viewer:main
9
+