q3dviewer 1.0.9__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.
@@ -238,12 +238,19 @@ class BaseGLWidget(QtOpenGLWidgets.QOpenGLWidget):
238
238
  return Tcw
239
239
 
240
240
  def set_cam_position(self, **kwargs):
241
- pos = kwargs.get('pos', None)
241
+ center = kwargs.get('center', None)
242
242
  distance = kwargs.get('distance', None)
243
- if pos is not None:
244
- self.set_center(pos)
243
+ euler = kwargs.get('euler', None)
244
+ if center is not None:
245
+ self.set_center(center)
245
246
  if distance is not None:
246
247
  self.set_dist(distance)
248
+ if euler is not None:
249
+ self.set_euler(euler)
250
+
251
+ def set_euler(self, euler):
252
+ self.euler = euler
253
+ self.view_need_update = True
247
254
 
248
255
  def set_color(self, color):
249
256
  self.color = color
@@ -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
  """
@@ -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
@@ -0,0 +1,214 @@
1
+ Metadata-Version: 2.1
2
+ Name: q3dviewer
3
+ Version: 1.1.0
4
+ Summary: A library designed for quickly deploying a 3D viewer.
5
+ Home-page: https://github.com/scomup/q3dviewer
6
+ Author: Liu Yang
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: License :: OSI Approved :: MIT License
9
+ Classifier: Operating System :: OS Independent
10
+ Description-Content-Type: text/markdown
11
+ License-File: LICENSE
12
+ Requires-Dist: numpy
13
+ Requires-Dist: pyside6
14
+ Requires-Dist: PyOpenGL
15
+ Requires-Dist: pillow
16
+ Requires-Dist: meshio
17
+ Requires-Dist: pypcd4
18
+ Requires-Dist: pye57
19
+ Requires-Dist: laspy
20
+ Requires-Dist: imageio
21
+
22
+ ## q3dviewer
23
+
24
+ `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.
25
+
26
+ ## Installation
27
+
28
+ To install `q3dviewer`, execute the following command in your terminal on either Linux or Windows:
29
+
30
+ ```bash
31
+ pip install q3dviewer
32
+ ```
33
+
34
+ ### Note for Windows Users
35
+
36
+ - Ensure that you have a Python 3 environment set up:
37
+ - Download and install Python 3 from the [official Python website](https://www.python.org/downloads/).
38
+ - During installation, make sure to check the "Add Python to PATH" option.
39
+
40
+ ### Note for Linux Users
41
+
42
+ 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`:
43
+
44
+ ```bash
45
+ sudo apt-get install libxcb-cursor0
46
+ ```
47
+
48
+ ## Tools
49
+
50
+ Once installed, you can directly use the following tools:
51
+
52
+ ### 1. Cloud Viewer
53
+
54
+ A tool for visualizing point cloud files. Launch it by executing the following command in your terminal:
55
+
56
+ ```sh
57
+ cloud_viewer # The viewer will be displayed
58
+ ```
59
+
60
+ *Alternatively*, if the path is not set (though it's not recommended):
61
+
62
+ ```sh
63
+ python3 -m q3dviewer.tools.cloud_viewer
64
+ ```
65
+
66
+ 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.
67
+
68
+ For example, you can download and view point clouds of Tokyo in LAS format from the following link:
69
+
70
+ [Tokyo Point Clouds](https://www.geospatial.jp/ckan/dataset/tokyopc-23ku-2024/resource/7807d6d1-29f3-4b36-b0c8-f7aa0ea2cff3)
71
+
72
+ ![Cloud Viewer Screenshot](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149168/03c981c6-1aec-e5b9-4536-e07e1e56ff29.png)
73
+
74
+ 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`.
75
+
76
+ ![Cloud Viewer Settings](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149168/deeb996a-e419-58f4-6bc2-535099b1b73a.png)
77
+
78
+ ### 2. ROS Viewer
79
+
80
+ A high-performance SLAM viewer compatible with ROS, serving as an alternative to RVIZ.
81
+
82
+ ```sh
83
+ roscore &
84
+ ros_viewer
85
+ ```
86
+
87
+ ### 3. Film Maker
88
+
89
+ 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.
90
+
91
+ ```sh
92
+ film_maker # drag and drop your cloud file to the window
93
+ ```
94
+
95
+ * Space key to add a keyframe.
96
+ * Delete key to remove a keyframe.
97
+
98
+ Film Maker GUI:
99
+
100
+ ![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)
101
+
102
+ ### 4. Gaussian Viewer
103
+
104
+ A simple viewer for 3D Gaussians. See [EasyGaussianSplatting](https://github.com/scomup/EasyGaussianSplatting) for more information.
105
+
106
+ ```sh
107
+ gaussian_viewer # Drag and drop your Gaussian file onto the window
108
+ ```
109
+
110
+ ![Gaussian Viewer GIF](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149168/441e6f5a-214d-f7c1-11bf-5fa79e63b38e.gif)
111
+
112
+ ### 5. LiDAR-LiDAR Calibration Tools
113
+
114
+ A tool to compute the relative pose between two LiDARs. It allows for both manual adjustment in the settings screen and automatic calibration.
115
+
116
+ ```sh
117
+ lidar_calib --lidar0=/YOUR_LIDAR0_TOPIC --lidar1=/YOUR_LIDAR1_TOPIC
118
+ ```
119
+
120
+ ![LiDAR Calibration](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149168/5a8a9903-a42a-8322-1d23-0cbecd3fa99a.png)
121
+
122
+ ### 6. LiDAR-Camera Calibration Tools
123
+
124
+ 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.
125
+
126
+ ```sh
127
+ lidar_cam_calib --lidar=/YOUR_LIDAR_TOPIC --camera=/YOUR_CAMERA_TOPIC --camera_info=/YOUR_CAMERA_INFO_TOPIC
128
+ ```
129
+
130
+ ![LiDAR-Camera Calibration](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149168/f8359820-2ae7-aa37-6577-0fa035f4dd95.png)
131
+
132
+ ## Using as a Library
133
+
134
+ Using the examples above, you can easily customize and develop your own 3D viewer with `q3dviewer`. Below is a coding example.
135
+
136
+ ### Custom 3D Viewer
137
+
138
+ ```python
139
+ #!/usr/bin/env python3
140
+
141
+ import q3dviewer as q3d # Import q3dviewer
142
+
143
+ def main():
144
+ # Create a Qt application
145
+ app = q3d.QApplication([])
146
+
147
+ # Create various 3D items
148
+ axis_item = q3d.AxisItem(size=0.5, width=5)
149
+ grid_item = q3d.GridItem(size=10, spacing=1)
150
+
151
+ # Create a viewer
152
+ viewer = q3d.Viewer(name='example')
153
+
154
+ # Add items to the viewer
155
+ viewer.add_items({
156
+ 'grid': grid_item,
157
+ 'axis': axis_item,
158
+ })
159
+
160
+ # Show the viewer & run the Qt application
161
+ viewer.show()
162
+ app.exec()
163
+
164
+ if __name__ == '__main__':
165
+ main()
166
+ ```
167
+
168
+ `q3dviewer` provides the following 3D items:
169
+
170
+ - **AxisItem**: Displays coordinate axes or the origin position.
171
+ - **CloudItem**: Displays point clouds.
172
+ - **CloudIOItem**: Displays point clouds with input/output capabilities.
173
+ - **GaussianItem**: Displays 3D Gaussians.
174
+ - **GridItem**: Displays grids.
175
+ - **ImageItem**: Displays 2D images.
176
+ - **Text2DItem**: Displays 2D text.
177
+ - **LineItem**: Displays lines or trajectories.
178
+
179
+ ### Developing Custom Items
180
+
181
+ In addition to the standard 3D items provided, you can visualize custom 3D items with simple coding. Below is a sample:
182
+
183
+ ```python
184
+ from OpenGL.GL import *
185
+ import numpy as np
186
+ import q3dviewer as q3d
187
+ from PySide6.QtWidgets import QLabel, QSpinBox
188
+
189
+ class YourItem(q3d.BaseItem):
190
+ def __init__(self):
191
+ super(YourItem, self).__init__()
192
+ # Necessary initialization
193
+
194
+ def add_setting(self, layout):
195
+ # Initialize the settings screen
196
+ label = QLabel("Add your setting:")
197
+ layout.addWidget(label)
198
+ box = QSpinBox()
199
+ layout.addWidget(box)
200
+
201
+ def set_data(self, data):
202
+ # Obtain the data you want to visualize
203
+ pass
204
+
205
+ def initialize_gl(self):
206
+ # OpenGL initialization settings (if needed)
207
+ pass
208
+
209
+ def paint(self):
210
+ # Visualize 3D objects using OpenGL
211
+ pass
212
+ ```
213
+
214
+ Enjoy using `q3dviewer`!
@@ -1,5 +1,5 @@
1
1
  q3dviewer/__init__.py,sha256=rP5XX_x8g7hxIMqNHlU89BN4dt5MSvoYYwip68fCmhc,173
2
- q3dviewer/base_glwidget.py,sha256=7CJKea2JSP78jsESZJP38yfLhhRINF-VQ9R8sMXwAo0,10697
2
+ q3dviewer/base_glwidget.py,sha256=k4fVwV2A_0CS7zZgHargbKE7push-l2UN7rmr2Z9vFo,10916
3
3
  q3dviewer/base_item.py,sha256=lzb04oRaS4rRJrAP6C1Bu4ugK237FgupMTB97zjNVFw,1768
4
4
  q3dviewer/gau_io.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
5
  q3dviewer/glwidget.py,sha256=im8hjVYEL0Zl7fOIHTQMJdWu7WNOHlvTdIDYjebz9WA,4940
@@ -24,9 +24,9 @@ q3dviewer/test/test_interpolation.py,sha256=rR_CXsYFLpn0zO0mHf_jL-naluDBMSky--Fv
24
24
  q3dviewer/test/test_rendering.py,sha256=gbTcu7-cg20DgC5Zoi17C1s5lBGLfAE1rW9biqPjRsA,2164
25
25
  q3dviewer/tools/__init__.py,sha256=01wG7BGM6VX0QyFBKsqPmyf2e-vrmV_N3-mo-VQ1VBg,20
26
26
  q3dviewer/tools/cinematographer.py,sha256=o_24SSQ4mF062QQ7Gv3i90v7fA79PcHLB03UHXufuEA,13950
27
- q3dviewer/tools/cloud_viewer.py,sha256=lNvOD0XWDAfgdqc2aJGigcCJsaWUS4qxadjFwf59-OY,3861
27
+ q3dviewer/tools/cloud_viewer.py,sha256=10f2LSWpmsXzxrGobXw188doVjJbgBfPoqZPUi35EtI,3867
28
28
  q3dviewer/tools/example_viewer.py,sha256=yeVXT0k4-h1vTLKnGzWADZD3our6XUaYUTy0p5daTkE,959
29
- q3dviewer/tools/film_maker.py,sha256=MtmmL1-iogSmHXCZc0MLk35_bP-RMBN8twaOtTkv77w,15180
29
+ q3dviewer/tools/film_maker.py,sha256=CC4RuK0bE_-0kcdU6xyzqR47pB4TEcm0g4LlDrFs3qY,16004
30
30
  q3dviewer/tools/gaussian_viewer.py,sha256=vIwWmiFhjNmknrEkBLzt2yiegeH7LP3OeNjnGM6GzaI,1633
31
31
  q3dviewer/tools/lidar_calib.py,sha256=M01bGg2mT8LwVcYybolr4UW_UUaR-f-BFciEHtjeK-w,10488
32
32
  q3dviewer/tools/lidar_cam_calib.py,sha256=SYNLDvi15MX7Q3aGn771fvu1cES9xeXgP0_WmDq33w4,11200
@@ -37,9 +37,9 @@ q3dviewer/utils/convert_ros_msg.py,sha256=sAoQfy3qLQKsIArBAVm8H--wlQXOcmkKK3-Ox9
37
37
  q3dviewer/utils/gl_helper.py,sha256=dRY_kUqyPMr7NTcupUr6_VTvgnj53iE2C0Lk0-oFYsI,1435
38
38
  q3dviewer/utils/maths.py,sha256=5TmjWUX1K3UjygXxrUsydjbo7tPzu0gD-yy7qtQUGBU,10588
39
39
  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,,
40
+ q3dviewer-1.1.0.dist-info/LICENSE,sha256=81cMOyNfw8KLb1JnPYngGHJ5W83gSbZEBU9MEP3tl-E,1124
41
+ q3dviewer-1.1.0.dist-info/METADATA,sha256=hiWxWsKDWmCaXB-lJYhi-kutcydCeafxfzDSgeQv5Y8,6991
42
+ q3dviewer-1.1.0.dist-info/WHEEL,sha256=g4nMs7d-Xl9-xC9XovUrsDHGXt-FT0E17Yqo92DEfvY,92
43
+ q3dviewer-1.1.0.dist-info/entry_points.txt,sha256=aeUdGH7UIgMZEMFUc-0xPZWspY95GoPdZcZuLceq85g,361
44
+ q3dviewer-1.1.0.dist-info/top_level.txt,sha256=HFFDCbGu28txcGe2HPc46A7EPaguBa_b5oH7bufmxHM,10
45
+ q3dviewer-1.1.0.dist-info/RECORD,,
@@ -1,14 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: q3dviewer
3
- Version: 1.0.9
4
- License-File: LICENSE
5
- Requires-Dist: numpy
6
- Requires-Dist: pyside6
7
- Requires-Dist: PyOpenGL
8
- Requires-Dist: pillow
9
- Requires-Dist: meshio
10
- Requires-Dist: pypcd4
11
- Requires-Dist: pye57
12
- Requires-Dist: laspy
13
- Requires-Dist: imageio
14
-