q3dviewer 1.1.6__py3-none-any.whl → 1.1.8__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/.vscode/c_cpp_properties.json +30 -0
- q3dviewer/.vscode/settings.json +10 -0
- q3dviewer/base_glwidget.py +56 -1
- q3dviewer/custom_items/__init__.py +1 -0
- q3dviewer/custom_items/cloud_io_item.py +16 -1
- q3dviewer/custom_items/cloud_item.py +37 -22
- q3dviewer/custom_items/text3d_item.py +120 -0
- q3dviewer/custom_items/text_item.py +19 -4
- q3dviewer/gau_io.py +0 -168
- q3dviewer/glwidget.py +23 -3
- q3dviewer/shaders/cloud_frag.glsl +1 -1
- q3dviewer/shaders/cloud_vert.glsl +13 -4
- q3dviewer/shaders/gau_frag.glsl +1 -1
- q3dviewer/shaders/gau_prep.glsl +1 -1
- q3dviewer/shaders/gau_vert.glsl +1 -1
- q3dviewer/shaders/sort_by_key.glsl +1 -1
- q3dviewer/test/test_interpolation.py +58 -0
- q3dviewer/test/test_rendering.py +73 -0
- q3dviewer/tools/cinematographer.py +367 -0
- q3dviewer/tools/cloud_viewer.py +79 -3
- q3dviewer/tools/example_viewer.py +8 -28
- q3dviewer/tools/film_maker.py +1 -1
- q3dviewer/tools/lidar_cam_calib.py +10 -11
- q3dviewer/utils/convert_ros_msg.py +49 -6
- q3dviewer/viewer.py +4 -1
- {q3dviewer-1.1.6.dist-info → q3dviewer-1.1.8.dist-info}/METADATA +4 -3
- q3dviewer-1.1.8.dist-info/RECORD +50 -0
- {q3dviewer-1.1.6.dist-info → q3dviewer-1.1.8.dist-info}/WHEEL +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.1.6.dist-info/RECORD +0 -49
- {q3dviewer-1.1.6.dist-info → q3dviewer-1.1.8.dist-info}/LICENSE +0 -0
- {q3dviewer-1.1.6.dist-info → q3dviewer-1.1.8.dist-info}/entry_points.txt +0 -0
- {q3dviewer-1.1.6.dist-info → q3dviewer-1.1.8.dist-info}/top_level.txt +0 -0
q3dviewer/tools/cloud_viewer.py
CHANGED
|
@@ -8,7 +8,9 @@ Distributed under MIT license. See LICENSE for more information.
|
|
|
8
8
|
import numpy as np
|
|
9
9
|
import q3dviewer as q3d
|
|
10
10
|
from q3dviewer.Qt.QtWidgets import QVBoxLayout, QProgressBar, QDialog, QLabel
|
|
11
|
-
from q3dviewer.Qt.QtCore import QThread, Signal
|
|
11
|
+
from q3dviewer.Qt.QtCore import QThread, Signal, Qt
|
|
12
|
+
from q3dviewer.Qt.QtGui import QKeyEvent
|
|
13
|
+
from q3dviewer import GLWidget
|
|
12
14
|
|
|
13
15
|
|
|
14
16
|
class ProgressDialog(QDialog):
|
|
@@ -57,9 +59,57 @@ class FileLoaderThread(QThread):
|
|
|
57
59
|
self.finished.emit()
|
|
58
60
|
|
|
59
61
|
|
|
62
|
+
class CustomGLWidget(GLWidget):
|
|
63
|
+
def __init__(self, viewer):
|
|
64
|
+
super().__init__()
|
|
65
|
+
self.viewer = viewer
|
|
66
|
+
self.selected_points = []
|
|
67
|
+
|
|
68
|
+
def mousePressEvent(self, event):
|
|
69
|
+
# ctrl + left click to add select point
|
|
70
|
+
# ctrl + right click to remove a point from selected points
|
|
71
|
+
if event.button() == Qt.LeftButton and event.modifiers() & Qt.ControlModifier:
|
|
72
|
+
x, y = event.x(), event.y()
|
|
73
|
+
p = self.get_point(x, y)
|
|
74
|
+
if p is not None:
|
|
75
|
+
self.selected_points.append(p)
|
|
76
|
+
self.update_marker()
|
|
77
|
+
elif event.button() == Qt.RightButton and event.modifiers() & Qt.ControlModifier:
|
|
78
|
+
if len(self.selected_points) > 0:
|
|
79
|
+
self.selected_points.pop()
|
|
80
|
+
self.update_marker()
|
|
81
|
+
super().mouseReleaseEvent(event)
|
|
82
|
+
|
|
83
|
+
def update_marker(self):
|
|
84
|
+
marks = []
|
|
85
|
+
for p in self.selected_points:
|
|
86
|
+
m = {'text': '',
|
|
87
|
+
'position': p,
|
|
88
|
+
'color': (0.0, 1.0, 0.0, 1.0),
|
|
89
|
+
'font_size': 16,
|
|
90
|
+
'point_size': 5.0,
|
|
91
|
+
'line_width': 1.0}
|
|
92
|
+
marks.append(m)
|
|
93
|
+
|
|
94
|
+
# calculate distances between consecutive points
|
|
95
|
+
self.viewer['marker'].set_data(data=marks, append=False)
|
|
96
|
+
if len(self.selected_points) >= 2:
|
|
97
|
+
total_dists = 0.0
|
|
98
|
+
for i in range(1, len(self.selected_points)):
|
|
99
|
+
p1 = self.selected_points[i - 1]
|
|
100
|
+
p2 = self.selected_points[i]
|
|
101
|
+
dist = np.linalg.norm(np.array(p2) - np.array(p1))
|
|
102
|
+
total_dists += dist
|
|
103
|
+
self.viewer['text'].set_data(text=f'Total Distance: {total_dists:.2f} meter')
|
|
104
|
+
else:
|
|
105
|
+
self.viewer['text'].set_data(text='')
|
|
106
|
+
|
|
107
|
+
|
|
60
108
|
class CloudViewer(q3d.Viewer):
|
|
61
109
|
def __init__(self, **kwargs):
|
|
62
|
-
|
|
110
|
+
gl_widget_class=lambda: CustomGLWidget(self)
|
|
111
|
+
super(CloudViewer, self).__init__(
|
|
112
|
+
**kwargs, gl_widget_class=gl_widget_class)
|
|
63
113
|
self.setAcceptDrops(True)
|
|
64
114
|
|
|
65
115
|
def dragEnterEvent(self, event):
|
|
@@ -96,8 +146,28 @@ class CloudViewer(q3d.Viewer):
|
|
|
96
146
|
center = np.nanmean(cloud['xyz'].astype(np.float64), axis=0)
|
|
97
147
|
self.glwidget.set_cam_position(center=center)
|
|
98
148
|
|
|
149
|
+
# print a quick help message
|
|
150
|
+
def print_help():
|
|
151
|
+
# ANSI color codes
|
|
152
|
+
GREEN = '\033[92m'
|
|
153
|
+
BLUE = '\033[94m'
|
|
154
|
+
BOLD = '\033[1m'
|
|
155
|
+
END = '\033[0m'
|
|
156
|
+
|
|
157
|
+
help_msg = f"""
|
|
158
|
+
{BOLD}Cloud Viewer Help:{END}
|
|
159
|
+
{GREEN}• Drag and drop cloud files into the viewer to load them.{END}
|
|
160
|
+
{GREEN}• Measure distance between points:{END}
|
|
161
|
+
{BLUE}- Hold Ctrl and left-click to select points on the cloud.{END}
|
|
162
|
+
{BLUE}- Hold Ctrl and right-click to remove the last selected point.{END}
|
|
163
|
+
{BLUE}- The total distance between selected points will be displayed.{END}
|
|
164
|
+
{GREEN}• Press 'M' to open the settings window.{END}
|
|
165
|
+
{BLUE}- Use the settings window to adjust item properties.{END}
|
|
166
|
+
"""
|
|
167
|
+
print(help_msg)
|
|
99
168
|
|
|
100
169
|
def main():
|
|
170
|
+
print_help()
|
|
101
171
|
import argparse
|
|
102
172
|
parser = argparse.ArgumentParser()
|
|
103
173
|
parser.add_argument("--path", help="the cloud file path")
|
|
@@ -107,9 +177,15 @@ def main():
|
|
|
107
177
|
cloud_item = q3d.CloudIOItem(size=1, alpha=0.1)
|
|
108
178
|
axis_item = q3d.AxisItem(size=0.5, width=5)
|
|
109
179
|
grid_item = q3d.GridItem(size=1000, spacing=20)
|
|
180
|
+
marker_item = q3d.Text3DItem() # Changed from CloudItem to Text3DItem
|
|
181
|
+
text_item = q3d.Text2DItem(pos=(20, 40), text="", color='lime', size=16)
|
|
110
182
|
|
|
111
183
|
viewer.add_items(
|
|
112
|
-
{'
|
|
184
|
+
{'marker': marker_item,
|
|
185
|
+
'cloud': cloud_item,
|
|
186
|
+
'grid': grid_item,
|
|
187
|
+
'axis': axis_item,
|
|
188
|
+
'text': text_item})
|
|
113
189
|
|
|
114
190
|
if args.path:
|
|
115
191
|
pcd_fn = args.path
|
|
@@ -1,47 +1,27 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
import q3dviewer as q3d
|
|
5
|
-
from q3dviewer.Qt.QtCore import QTimer
|
|
6
|
-
|
|
7
|
-
i = 0.0
|
|
8
|
-
|
|
9
|
-
def update(viewer):
|
|
10
|
-
global i
|
|
11
|
-
i += 0.05
|
|
12
|
-
new = np.array([np.sin(i) * i, np.cos(i) * i, i])
|
|
13
|
-
viewer['traj'].set_data(new, append=True)
|
|
14
|
-
viewer.update()
|
|
15
|
-
return i
|
|
16
|
-
|
|
3
|
+
import q3dviewer as q3d # Import q3dviewer
|
|
17
4
|
|
|
18
5
|
def main():
|
|
19
|
-
#
|
|
6
|
+
# Create a Qt application
|
|
20
7
|
app = q3d.QApplication([])
|
|
21
8
|
|
|
22
|
-
#
|
|
9
|
+
# Create various 3D items
|
|
23
10
|
axis_item = q3d.AxisItem(size=0.5, width=5)
|
|
24
11
|
grid_item = q3d.GridItem(size=10, spacing=1)
|
|
25
|
-
traj_item = q3d.LineItem(width=2)
|
|
26
12
|
|
|
27
|
-
#
|
|
13
|
+
# Create a viewer
|
|
28
14
|
viewer = q3d.Viewer(name='example')
|
|
29
|
-
|
|
30
|
-
#
|
|
15
|
+
|
|
16
|
+
# Add items to the viewer
|
|
31
17
|
viewer.add_items({
|
|
32
18
|
'grid': grid_item,
|
|
33
19
|
'axis': axis_item,
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
# dynamic update by timer
|
|
37
|
-
timer = QTimer()
|
|
38
|
-
timer.timeout.connect(lambda: update(viewer))
|
|
39
|
-
timer.start(100)
|
|
20
|
+
})
|
|
40
21
|
|
|
41
|
-
#
|
|
22
|
+
# Show the viewer & run the Qt application
|
|
42
23
|
viewer.show()
|
|
43
24
|
app.exec()
|
|
44
25
|
|
|
45
|
-
|
|
46
26
|
if __name__ == '__main__':
|
|
47
27
|
main()
|
q3dviewer/tools/film_maker.py
CHANGED
|
@@ -417,7 +417,7 @@ def main():
|
|
|
417
417
|
args = parser.parse_args()
|
|
418
418
|
app = q3d.QApplication(['Film Maker'])
|
|
419
419
|
viewer = CMMViewer(name='Film Maker', update_interval=30)
|
|
420
|
-
cloud_item = q3d.CloudIOItem(size=
|
|
420
|
+
cloud_item = q3d.CloudIOItem(size=1, point_type='SPHERE', alpha=0.5, depth_test=True)
|
|
421
421
|
grid_item = q3d.GridItem(size=1000, spacing=20)
|
|
422
422
|
|
|
423
423
|
viewer.add_items(
|
|
@@ -17,8 +17,7 @@ import cv2
|
|
|
17
17
|
import argparse
|
|
18
18
|
from q3dviewer.utils.convert_ros_msg import convert_pointcloud2_msg, convert_image_msg
|
|
19
19
|
from q3dviewer.utils.maths import euler_to_matrix, matrix_to_quaternion, matrix_to_euler
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
from q3dviewer.utils.helpers import rainbow
|
|
22
21
|
clouds = []
|
|
23
22
|
remap_info = None
|
|
24
23
|
K = None
|
|
@@ -188,11 +187,11 @@ class LidarCamViewer(q3d.Viewer):
|
|
|
188
187
|
def camera_info_cb(data):
|
|
189
188
|
global remap_info, K
|
|
190
189
|
if remap_info is None: # Initialize only once
|
|
191
|
-
K = np.array(
|
|
192
|
-
D = np.array(
|
|
190
|
+
K = np.array([2.4480877131144061e+02, 0., 4.7808179466584482e+02, 0., 2.4540886113128877e+02, 2.8080868163348435e+02, 0., 0., 1.]).reshape(3, 3)
|
|
191
|
+
D = np.array([0,0,0,0,0])
|
|
193
192
|
rospy.loginfo("Camera intrinsic parameters set")
|
|
194
|
-
height =
|
|
195
|
-
width =
|
|
193
|
+
height = 540
|
|
194
|
+
width = 960
|
|
196
195
|
mapx, mapy = cv2.initUndistortRectifyMap(
|
|
197
196
|
K, D, None, K, (width, height), cv2.CV_32FC1)
|
|
198
197
|
remap_info = [mapx, mapy]
|
|
@@ -254,7 +253,7 @@ def image_cb(data):
|
|
|
254
253
|
intensity = cloud_local['irgb'][u_mask][valid_points] >> 24
|
|
255
254
|
vmin = viewer['scan'].vmin
|
|
256
255
|
vmax = viewer['scan'].vmax
|
|
257
|
-
intensity_color =
|
|
256
|
+
intensity_color = rainbow(intensity, scalar_min=vmin, scalar_max=vmax).astype(np.uint8)
|
|
258
257
|
draw_image = image_un.copy()
|
|
259
258
|
draw_image = draw_larger_points(draw_image, u, intensity_color, radius)
|
|
260
259
|
rgb = image_un[u[:, 1], u[:, 0]]
|
|
@@ -288,14 +287,14 @@ def main():
|
|
|
288
287
|
scan_item = q3d.CloudItem(size=2, alpha=1, color_mode='I')
|
|
289
288
|
img_item = q3d.ImageItem(pos=np.array([0, 0]), size=np.array([800, 600]))
|
|
290
289
|
viewer.add_items({'scan': scan_item, 'grid': grid_item, 'img': img_item})
|
|
290
|
+
camera_info_cb(None)
|
|
291
291
|
|
|
292
292
|
rospy.init_node('lidar_cam_calib', anonymous=True)
|
|
293
293
|
|
|
294
294
|
# Use topic names from arguments
|
|
295
|
-
rospy.Subscriber(
|
|
296
|
-
rospy.Subscriber(
|
|
297
|
-
rospy.Subscriber(args.camera_info, CameraInfo,
|
|
298
|
-
camera_info_cb, queue_size=1)
|
|
295
|
+
rospy.Subscriber("cloud", PointCloud2, scan_cb, queue_size=1)
|
|
296
|
+
rospy.Subscriber("/usb_cam/image_raw", Image, image_cb, queue_size=1)
|
|
297
|
+
# rospy.Subscriber(args.camera_info, CameraInfo, camera_info_cb, queue_size=1)
|
|
299
298
|
|
|
300
299
|
viewer.show()
|
|
301
300
|
app.exec()
|
|
@@ -4,18 +4,59 @@ Distributed under MIT license. See LICENSE for more information.
|
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
6
|
import numpy as np
|
|
7
|
-
from pypcd4 import PointCloud
|
|
8
7
|
from q3dviewer.utils.maths import make_transform
|
|
9
8
|
|
|
9
|
+
def get_dtype(msg):
|
|
10
|
+
dtype_map = {
|
|
11
|
+
1: 'i1', # INT8
|
|
12
|
+
2: 'u1', # UINT8
|
|
13
|
+
3: 'i2', # INT16
|
|
14
|
+
4: 'u2', # UINT16
|
|
15
|
+
5: 'i4', # INT32
|
|
16
|
+
6: 'u4', # UINT32
|
|
17
|
+
7: 'f4', # FLOAT32
|
|
18
|
+
8: 'f8' # FLOAT64
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
point_step = msg.point_step
|
|
22
|
+
dtype_list = []
|
|
23
|
+
for field in msg.fields:
|
|
24
|
+
dtype_str = dtype_map.get(field.datatype, 'f4')
|
|
25
|
+
dtype_list.append((field.name, dtype_str, field.offset))
|
|
26
|
+
|
|
27
|
+
dtype_list.sort(key=lambda x: x[2])
|
|
28
|
+
|
|
29
|
+
structured_dtype = []
|
|
30
|
+
last_offset = 0
|
|
31
|
+
|
|
32
|
+
for field_name, field_dtype, offset in dtype_list:
|
|
33
|
+
if offset > last_offset:
|
|
34
|
+
padding_size = offset - last_offset
|
|
35
|
+
if padding_size > 0:
|
|
36
|
+
structured_dtype.append((f'pad{last_offset}', f'u1', padding_size))
|
|
37
|
+
|
|
38
|
+
structured_dtype.append((field_name, field_dtype))
|
|
39
|
+
last_offset = offset + np.dtype(field_dtype).itemsize
|
|
40
|
+
|
|
41
|
+
if point_step > last_offset:
|
|
42
|
+
final_padding = point_step - last_offset
|
|
43
|
+
structured_dtype.append((f'pad{last_offset}', f'u1', final_padding))
|
|
44
|
+
return structured_dtype
|
|
45
|
+
|
|
10
46
|
|
|
11
47
|
def convert_pointcloud2_msg(msg):
|
|
12
|
-
|
|
48
|
+
# Build dtype with proper offsets for PointCloud2 message
|
|
49
|
+
structured_dtype = get_dtype(msg)
|
|
50
|
+
pc = np.frombuffer(msg.data, dtype=structured_dtype)
|
|
51
|
+
# pc = PointCloud.from_msg(msg).pc_data
|
|
13
52
|
data_type = [('xyz', '<f4', (3,)), ('irgb', '<u4')]
|
|
14
53
|
rgb = np.zeros([pc.shape[0]], dtype=np.uint32)
|
|
15
54
|
intensity = np.zeros([pc.shape[0]], dtype=np.uint32)
|
|
16
55
|
fields = ['xyz']
|
|
17
56
|
if 'intensity' in pc.dtype.names:
|
|
18
|
-
intensity = pc['intensity']
|
|
57
|
+
intensity = pc['intensity']
|
|
58
|
+
intensity = np.clip(intensity, 0, 255)
|
|
59
|
+
intensity = intensity.astype(np.uint32)
|
|
19
60
|
fields.append('intensity')
|
|
20
61
|
if 'rgb' in pc.dtype.names:
|
|
21
62
|
rgb = pc['rgb'].view(np.uint32)
|
|
@@ -42,10 +83,12 @@ def convert_odometry_msg(msg):
|
|
|
42
83
|
return transform, stamp
|
|
43
84
|
|
|
44
85
|
|
|
45
|
-
def convert_image_msg(msg):
|
|
86
|
+
def convert_image_msg(msg, bgr=False):
|
|
46
87
|
image = np.frombuffer(msg.data, dtype=np.uint8).reshape(
|
|
47
88
|
msg.height, msg.width, -1)
|
|
48
|
-
if
|
|
49
|
-
|
|
89
|
+
# if bgr is True return BGR image, else return RGB image
|
|
90
|
+
if (bgr and msg.encoding == 'rgb8') or (not bgr and msg.encoding == 'bgr8'):
|
|
91
|
+
image = image[:, :, ::-1]
|
|
92
|
+
|
|
50
93
|
stamp = msg.header.stamp.to_sec()
|
|
51
94
|
return image, stamp
|
q3dviewer/viewer.py
CHANGED
|
@@ -17,7 +17,7 @@ def handler(signal, frame):
|
|
|
17
17
|
class Viewer(QMainWindow):
|
|
18
18
|
def __init__(self, name='Viewer', win_size=[1920, 1080],
|
|
19
19
|
gl_widget_class=GLWidget, update_interval=20):
|
|
20
|
-
|
|
20
|
+
self.set_quit_handler(handler)
|
|
21
21
|
super(Viewer, self).__init__()
|
|
22
22
|
self.setGeometry(0, 0, win_size[0], win_size[1])
|
|
23
23
|
self.gl_widget_class = gl_widget_class
|
|
@@ -27,6 +27,9 @@ class Viewer(QMainWindow):
|
|
|
27
27
|
self.setWindowTitle(name)
|
|
28
28
|
self.installEventFilter(self)
|
|
29
29
|
|
|
30
|
+
def set_quit_handler(self, handler):
|
|
31
|
+
signal.signal(signal.SIGINT, handler)
|
|
32
|
+
|
|
30
33
|
def init_ui(self):
|
|
31
34
|
center_widget = QWidget()
|
|
32
35
|
self.setCentralWidget(center_widget)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: q3dviewer
|
|
3
|
-
Version: 1.1.
|
|
3
|
+
Version: 1.1.8
|
|
4
4
|
Summary: A library designed for quickly deploying a 3D viewer.
|
|
5
5
|
Home-page: https://github.com/scomup/q3dviewer
|
|
6
6
|
Author: Liu Yang
|
|
@@ -26,7 +26,7 @@ Requires-Dist: matplotlib
|
|
|
26
26
|
[](https://opensource.org/licenses/MIT)
|
|
27
27
|
[](https://badge.fury.io/py/q3dviewer)
|
|
28
28
|
|
|
29
|
-
`q3dviewer` is a library designed for quickly deploying a 3D viewer. It is based on Qt
|
|
29
|
+
`q3dviewer` is a library designed for quickly deploying a 3D viewer. It is based on Qt 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.
|
|
30
30
|
|
|
31
31
|
|
|
32
32
|
To show how to use `q3dviewer` as a library, we also provide some [very useful tools](#tools).
|
|
@@ -195,6 +195,7 @@ if __name__ == '__main__':
|
|
|
195
195
|
- **GridItem**: Displays grids.
|
|
196
196
|
- **ImageItem**: Displays 2D images.
|
|
197
197
|
- **Text2DItem**: Displays 2D text.
|
|
198
|
+
- **Text3DItem**: Displays 3D test and mark.
|
|
198
199
|
- **LineItem**: Displays lines or trajectories.
|
|
199
200
|
|
|
200
201
|
### Developing Custom Items
|
|
@@ -205,7 +206,7 @@ In addition to the standard 3D items provided, you can visualize custom 3D items
|
|
|
205
206
|
from OpenGL.GL import *
|
|
206
207
|
import numpy as np
|
|
207
208
|
import q3dviewer as q3d
|
|
208
|
-
from
|
|
209
|
+
from q3dviewer.Qt.QtWidgets import QLabel, QSpinBox
|
|
209
210
|
|
|
210
211
|
class YourItem(q3d.BaseItem):
|
|
211
212
|
def __init__(self):
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
q3dviewer/__init__.py,sha256=cjyfUE5zK6xohDGDQIWfb0DKkWChVznBd7CrVLg7whQ,376
|
|
2
|
+
q3dviewer/base_glwidget.py,sha256=0AWYwOntyCSterxOebTg_VwStPYV6k1H_BDRGHl41bo,14382
|
|
3
|
+
q3dviewer/base_item.py,sha256=63MarHyoWszPL40ox-vPoOAQ1N4ypekOjoRARdPik-E,1755
|
|
4
|
+
q3dviewer/gau_io.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
+
q3dviewer/glwidget.py,sha256=EmrxPtVQ8RdPK5INKMlpKpVfX0KCfjSKRdGf4cSB1f0,5405
|
|
6
|
+
q3dviewer/viewer.py,sha256=Vq3ucDlBcBBoiVVGmqG1sRjhLePl50heblx6wJpsc1A,2603
|
|
7
|
+
q3dviewer/.vscode/c_cpp_properties.json,sha256=rcEXwMKO_Fve86bpEeEhoWRKzdLSVuG5dbNCtE5U3Fw,1037
|
|
8
|
+
q3dviewer/.vscode/settings.json,sha256=3G4gE-fW3vfObgaZrlJzAG7vym2pIfr0fNnL8Jo3RJo,322
|
|
9
|
+
q3dviewer/Qt/__init__.py,sha256=CcwS6oSXBXTMr58JNbRNYcPMVubDD2jiPtJ55DoLm8o,2199
|
|
10
|
+
q3dviewer/custom_items/__init__.py,sha256=cwcNn40MzYXVO9eSzpRknJdzZNKPf2kaEMZy5DCNYcE,567
|
|
11
|
+
q3dviewer/custom_items/axis_item.py,sha256=-WM2urosqV847zpTpOtxdLjb7y9NJqFCH13qqodcCTg,2572
|
|
12
|
+
q3dviewer/custom_items/cloud_io_item.py,sha256=Haz-SOUUCPDSHgmKyyyFfP7LXBSEiN4r8xmchQwCm-k,4721
|
|
13
|
+
q3dviewer/custom_items/cloud_item.py,sha256=NkZ5rFM_hVxBPMZYml3oX2CI5d0ApJY1wet8I1eIssE,14024
|
|
14
|
+
q3dviewer/custom_items/frame_item.py,sha256=bUzww3tSDah0JZeqtU6_cYHhhTVWzXhJVMcAa5pCXHI,7458
|
|
15
|
+
q3dviewer/custom_items/gaussian_item.py,sha256=JMubpahkTPh0E8ShL3FLTahv0e35ODzjgK5K1i0YXSU,9884
|
|
16
|
+
q3dviewer/custom_items/grid_item.py,sha256=LDB_MYACoxld-xvz01_MfAf12vLcRkH7R_WtGHHdSgk,4945
|
|
17
|
+
q3dviewer/custom_items/image_item.py,sha256=k7HNTqdL2ckTbxMx7A7eKaP4aksZ85-pBjNdbpm6PXM,5355
|
|
18
|
+
q3dviewer/custom_items/line_item.py,sha256=rel-lx8AgjDY7qyIecHxHQZzaswRn2ZTiOIjB_0Mrqo,4444
|
|
19
|
+
q3dviewer/custom_items/text3d_item.py,sha256=mQX-8ldUsPcM3ef77fevsXqVJY0_lnNsxCGrtpF4b-A,4167
|
|
20
|
+
q3dviewer/custom_items/text_item.py,sha256=toeGjBu7RtT8CMUuaDWnmXPnA1UKHhnCzUNeonGczSo,2703
|
|
21
|
+
q3dviewer/shaders/cloud_frag.glsl,sha256=psKVt9qI6BW0bCqOk4lcKqUd6XgYGtdFigyN9OdYSNI,609
|
|
22
|
+
q3dviewer/shaders/cloud_vert.glsl,sha256=gKI6EJrzX5ga2W2yjU6x7Wjz7Cu2Y-wrPl4g10RfTLM,2376
|
|
23
|
+
q3dviewer/shaders/gau_frag.glsl,sha256=vWt5I3Ojrc2PCxRlBJGyJhujbveSicMA54T01Fk293A,975
|
|
24
|
+
q3dviewer/shaders/gau_prep.glsl,sha256=0BiWhYCQGeX2iN-e7m3dy1xWXqWrErErRAzHlcmWHF0,7218
|
|
25
|
+
q3dviewer/shaders/gau_vert.glsl,sha256=_rkm51zaWgPDJ-otJL-WX12fDvnPBOTooVfqo21Rexs,1666
|
|
26
|
+
q3dviewer/shaders/sort_by_key.glsl,sha256=M5RK6uRDp40vVH6XtBIrdJTcYatqXyZwd6kCzEa2DZg,1097
|
|
27
|
+
q3dviewer/test/test_interpolation.py,sha256=rR_CXsYFLpn0zO0mHf_jL-naluDBMSky--FviOQga0Q,1657
|
|
28
|
+
q3dviewer/test/test_rendering.py,sha256=CrJkJjxkcizZxC4MVyDuJjY_41-eeiD5u0vD_8VFHgU,2206
|
|
29
|
+
q3dviewer/tools/__init__.py,sha256=01wG7BGM6VX0QyFBKsqPmyf2e-vrmV_N3-mo-VQ1VBg,20
|
|
30
|
+
q3dviewer/tools/cinematographer.py,sha256=o_24SSQ4mF062QQ7Gv3i90v7fA79PcHLB03UHXufuEA,13950
|
|
31
|
+
q3dviewer/tools/cloud_viewer.py,sha256=UsKALGiFR2Ck___LSGNPIZ3PPUlECO20jVkcP5g6yyc,6807
|
|
32
|
+
q3dviewer/tools/example_viewer.py,sha256=C867mLnCBjawS6LGgRsJ_c6-6wztfL9vOBQt85KbbdU,572
|
|
33
|
+
q3dviewer/tools/film_maker.py,sha256=xLFgRhFWoMQ37qlvcu1lXWaTWXMNRYlRcZFfHW5JtmQ,16676
|
|
34
|
+
q3dviewer/tools/gaussian_viewer.py,sha256=vIwWmiFhjNmknrEkBLzt2yiegeH7LP3OeNjnGM6GzaI,1633
|
|
35
|
+
q3dviewer/tools/lidar_calib.py,sha256=hHnsSaQh_Pkdh8tPntt0MgEW26nQyAdC_HQHq4I3sw8,10562
|
|
36
|
+
q3dviewer/tools/lidar_cam_calib.py,sha256=4CDcZZiFZDeKo2Y2_lXF9tfbiF9dPsz0OjppQdxQsU4,11430
|
|
37
|
+
q3dviewer/tools/ros_viewer.py,sha256=ARB3I5wohY3maP8dCu0O0hxObd6JFKuK2y7AApVgMWA,2551
|
|
38
|
+
q3dviewer/utils/__init__.py,sha256=dwTNAAebTiKY4ygv2G1O-w6-TbJnmnNVO2UfJXvJhaQ,107
|
|
39
|
+
q3dviewer/utils/cloud_io.py,sha256=xarfYakY0zgKwvZkgKSPO6b4DEo42hsq3mcvCbK64yg,12134
|
|
40
|
+
q3dviewer/utils/convert_ros_msg.py,sha256=lNbLIawJfwp3VzygdW3dUXkfSG8atg_CoZbQFmt8H70,3142
|
|
41
|
+
q3dviewer/utils/gl_helper.py,sha256=dRY_kUqyPMr7NTcupUr6_VTvgnj53iE2C0Lk0-oFYsI,1435
|
|
42
|
+
q3dviewer/utils/helpers.py,sha256=SqR4YTQZi13FKbkVUYgodXce1JJ_YmrHEIRkUmnIUas,3085
|
|
43
|
+
q3dviewer/utils/maths.py,sha256=zHaPtvVZIuo8xepIXCMeSL9tpx8FahUrq0l4K1oXrBk,8834
|
|
44
|
+
q3dviewer/utils/range_slider.py,sha256=9djTxuzmzH54DgSwAljRpLGjsrIJ0hTxhaxFjPxsk8g,4007
|
|
45
|
+
q3dviewer-1.1.8.dist-info/LICENSE,sha256=81cMOyNfw8KLb1JnPYngGHJ5W83gSbZEBU9MEP3tl-E,1124
|
|
46
|
+
q3dviewer-1.1.8.dist-info/METADATA,sha256=oAsXecjioM4FjtdSDR-8nOjte6sAMxyA0nAklwItxsQ,8014
|
|
47
|
+
q3dviewer-1.1.8.dist-info/WHEEL,sha256=g4nMs7d-Xl9-xC9XovUrsDHGXt-FT0E17Yqo92DEfvY,92
|
|
48
|
+
q3dviewer-1.1.8.dist-info/entry_points.txt,sha256=aeUdGH7UIgMZEMFUc-0xPZWspY95GoPdZcZuLceq85g,361
|
|
49
|
+
q3dviewer-1.1.8.dist-info/top_level.txt,sha256=HFFDCbGu28txcGe2HPc46A7EPaguBa_b5oH7bufmxHM,10
|
|
50
|
+
q3dviewer-1.1.8.dist-info/RECORD,,
|
q3dviewer/basic_window.py
DELETED
|
@@ -1,228 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
|
|
3
|
-
import numpy as np
|
|
4
|
-
import pyqtgraph.opengl as gl
|
|
5
|
-
from pyqtgraph.Qt import QtCore
|
|
6
|
-
from PyQt5.QtWidgets import QWidget, QComboBox, QVBoxLayout, QHBoxLayout, QSizePolicy,\
|
|
7
|
-
QSpacerItem, QMainWindow
|
|
8
|
-
from OpenGL.GL import *
|
|
9
|
-
from PyQt5.QtGui import QKeyEvent, QVector3D
|
|
10
|
-
from PyQt5.QtWidgets import QApplication, QWidget
|
|
11
|
-
import numpy as np
|
|
12
|
-
import signal
|
|
13
|
-
import sys
|
|
14
|
-
from PyQt5.QtWidgets import QLabel, QLineEdit, QDoubleSpinBox, QSpinBox
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
class SettingWindow(QWidget):
|
|
18
|
-
def __init__(self):
|
|
19
|
-
super().__init__()
|
|
20
|
-
self.combo_items = QComboBox()
|
|
21
|
-
self.combo_items.currentIndexChanged.connect(self.onComboboxSelection)
|
|
22
|
-
main_layout = QVBoxLayout()
|
|
23
|
-
self.stretch = QSpacerItem(
|
|
24
|
-
10, 10, QSizePolicy.Minimum, QSizePolicy.Expanding)
|
|
25
|
-
main_layout.addWidget(self.combo_items)
|
|
26
|
-
self.layout = QVBoxLayout()
|
|
27
|
-
self.layout.addItem(self.stretch)
|
|
28
|
-
main_layout.addLayout(self.layout)
|
|
29
|
-
self.setLayout(main_layout)
|
|
30
|
-
self.setWindowTitle("Setting Window")
|
|
31
|
-
self.setGeometry(200, 200, 300, 200)
|
|
32
|
-
self.items = {}
|
|
33
|
-
|
|
34
|
-
def addSetting(self, name, item):
|
|
35
|
-
self.items.update({name: item})
|
|
36
|
-
self.combo_items.addItem("%s(%s)" % (name, item.__class__.__name__))
|
|
37
|
-
|
|
38
|
-
def clearSetting(self):
|
|
39
|
-
while self.layout.count():
|
|
40
|
-
child = self.layout.takeAt(0)
|
|
41
|
-
if child.widget():
|
|
42
|
-
child.widget().deleteLater()
|
|
43
|
-
|
|
44
|
-
def onComboboxSelection(self, index):
|
|
45
|
-
self.layout.removeItem(self.stretch)
|
|
46
|
-
# remove all setting of previous widget
|
|
47
|
-
self.clearSetting()
|
|
48
|
-
|
|
49
|
-
key = list(self.items.keys())
|
|
50
|
-
try:
|
|
51
|
-
item = self.items[key[index]]
|
|
52
|
-
item.addSetting(self.layout)
|
|
53
|
-
self.layout.addItem(self.stretch)
|
|
54
|
-
except AttributeError:
|
|
55
|
-
print("%s: No setting." % (item.__class__.__name__))
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
class ViewWidget(gl.GLViewWidget):
|
|
59
|
-
def __init__(self):
|
|
60
|
-
self.followed_name = 'none'
|
|
61
|
-
self.named_items = {}
|
|
62
|
-
self.color = '#000000'
|
|
63
|
-
self.followable_item_name = ['none']
|
|
64
|
-
self.setting_window = SettingWindow()
|
|
65
|
-
super(ViewWidget, self).__init__()
|
|
66
|
-
|
|
67
|
-
def onFollowableSelection(self, index):
|
|
68
|
-
self.followed_name = self.followable_item_name[index]
|
|
69
|
-
|
|
70
|
-
def update(self):
|
|
71
|
-
if self.followed_name != 'none':
|
|
72
|
-
pos = self.named_items[self.followed_name].T[:3, 3]
|
|
73
|
-
self.opts['center'] = QVector3D(pos[0], pos[1], pos[2])
|
|
74
|
-
super().update()
|
|
75
|
-
|
|
76
|
-
def addSetting(self, layout):
|
|
77
|
-
label1 = QLabel("Set background color:")
|
|
78
|
-
label1.setToolTip("using '#xxxxxx', i.e. #FF4500")
|
|
79
|
-
box1 = QLineEdit()
|
|
80
|
-
box1.setToolTip("'using '#xxxxxx', i.e. #FF4500")
|
|
81
|
-
box1.setText(str(self.color))
|
|
82
|
-
box1.textChanged.connect(self.setBKColor)
|
|
83
|
-
layout.addWidget(label1)
|
|
84
|
-
layout.addWidget(box1)
|
|
85
|
-
label2 = QLabel("Set Focus:")
|
|
86
|
-
combo2 = QComboBox()
|
|
87
|
-
for name in self.followable_item_name:
|
|
88
|
-
combo2.addItem(name)
|
|
89
|
-
combo2.currentIndexChanged.connect(self.onFollowableSelection)
|
|
90
|
-
layout.addWidget(label2)
|
|
91
|
-
layout.addWidget(combo2)
|
|
92
|
-
|
|
93
|
-
def setBKColor(self, color):
|
|
94
|
-
if (type(color) != str):
|
|
95
|
-
return
|
|
96
|
-
if color.startswith("#"):
|
|
97
|
-
try:
|
|
98
|
-
self.setBackgroundColor(color)
|
|
99
|
-
self.color = color
|
|
100
|
-
except ValueError:
|
|
101
|
-
return
|
|
102
|
-
|
|
103
|
-
def addItem(self, name, item):
|
|
104
|
-
self.named_items.update({name: item})
|
|
105
|
-
if (item.__class__.__name__ == 'GLAxisItem'):
|
|
106
|
-
self.followable_item_name.append(name)
|
|
107
|
-
self.setting_window.addSetting(name, item)
|
|
108
|
-
super().addItem(item)
|
|
109
|
-
|
|
110
|
-
def mouseReleaseEvent(self, ev):
|
|
111
|
-
if hasattr(self, 'mousePos'):
|
|
112
|
-
delattr(self, 'mousePos')
|
|
113
|
-
|
|
114
|
-
def mouseMoveEvent(self, ev):
|
|
115
|
-
lpos = ev.localPos()
|
|
116
|
-
if not hasattr(self, 'mousePos'):
|
|
117
|
-
self.mousePos = lpos
|
|
118
|
-
diff = lpos - self.mousePos
|
|
119
|
-
self.mousePos = lpos
|
|
120
|
-
if ev.buttons() == QtCore.Qt.MouseButton.RightButton:
|
|
121
|
-
self.orbit(-diff.x(), diff.y())
|
|
122
|
-
elif ev.buttons() == QtCore.Qt.MouseButton.LeftButton:
|
|
123
|
-
pitch_abs = np.abs(self.opts['elevation'])
|
|
124
|
-
camera_mode = 'view-upright'
|
|
125
|
-
if(pitch_abs <= 45.0 or pitch_abs == 90):
|
|
126
|
-
camera_mode = 'view'
|
|
127
|
-
self.pan(diff.x(), diff.y(), 0, relative=camera_mode)
|
|
128
|
-
|
|
129
|
-
def keyPressEvent(self, ev: QKeyEvent):
|
|
130
|
-
step = 10
|
|
131
|
-
zoom_delta = 20
|
|
132
|
-
speed = 2
|
|
133
|
-
self.projectionMatrix().data()
|
|
134
|
-
|
|
135
|
-
pitch_abs = np.abs(self.opts['elevation'])
|
|
136
|
-
camera_mode = 'view-upright'
|
|
137
|
-
if(pitch_abs <= 45.0 or pitch_abs == 90):
|
|
138
|
-
camera_mode = 'view'
|
|
139
|
-
|
|
140
|
-
if ev.key() == QtCore.Qt.Key_M: # setting meun
|
|
141
|
-
print("Open setting windows")
|
|
142
|
-
self.openSettingWindow()
|
|
143
|
-
elif ev.key() == QtCore.Qt.Key_R:
|
|
144
|
-
print("Clear viewer")
|
|
145
|
-
for item in self.named_items.values():
|
|
146
|
-
try:
|
|
147
|
-
item.clear()
|
|
148
|
-
except:
|
|
149
|
-
pass
|
|
150
|
-
elif ev.key() == QtCore.Qt.Key_Up:
|
|
151
|
-
if ev.modifiers() & QtCore.Qt.KeyboardModifier.ControlModifier:
|
|
152
|
-
self.pan(0, +step, 0, relative=camera_mode)
|
|
153
|
-
else:
|
|
154
|
-
self.orbit(azim=0, elev=-speed)
|
|
155
|
-
elif ev.key() == QtCore.Qt.Key_Down:
|
|
156
|
-
if ev.modifiers() & QtCore.Qt.KeyboardModifier.ControlModifier:
|
|
157
|
-
self.pan(0, -step, 0, relative=camera_mode)
|
|
158
|
-
else:
|
|
159
|
-
self.orbit(azim=0, elev=speed)
|
|
160
|
-
elif ev.key() == QtCore.Qt.Key_Left:
|
|
161
|
-
if ev.modifiers() & QtCore.Qt.KeyboardModifier.ControlModifier:
|
|
162
|
-
self.pan(+step, 0, 0, relative=camera_mode)
|
|
163
|
-
else:
|
|
164
|
-
self.orbit(azim=speed, elev=0)
|
|
165
|
-
|
|
166
|
-
elif ev.key() == QtCore.Qt.Key_Right:
|
|
167
|
-
if ev.modifiers() & QtCore.Qt.KeyboardModifier.ControlModifier:
|
|
168
|
-
self.pan(-step, 0, 0, relative=camera_mode)
|
|
169
|
-
else:
|
|
170
|
-
self.orbit(azim=-speed, elev=0)
|
|
171
|
-
|
|
172
|
-
elif ev.key() == QtCore.Qt.Key_Z:
|
|
173
|
-
self.opts['distance'] *= 0.999**(+zoom_delta)
|
|
174
|
-
elif ev.key() == QtCore.Qt.Key_X:
|
|
175
|
-
self.opts['distance'] *= 0.999**(-zoom_delta)
|
|
176
|
-
else:
|
|
177
|
-
super().keyPressEvent(ev)
|
|
178
|
-
|
|
179
|
-
def openSettingWindow(self):
|
|
180
|
-
if self.setting_window.isVisible():
|
|
181
|
-
self.setting_window.raise_()
|
|
182
|
-
|
|
183
|
-
else:
|
|
184
|
-
self.setting_window.show()
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
class Viewer(QMainWindow):
|
|
188
|
-
def __init__(self, name='Viewer', win_size=[1920, 1080], vw=ViewWidget):
|
|
189
|
-
signal.signal(signal.SIGINT, signal.SIG_DFL)
|
|
190
|
-
super(Viewer, self).__init__()
|
|
191
|
-
self.vw = vw
|
|
192
|
-
self.setGeometry(0, 0, win_size[0], win_size[1])
|
|
193
|
-
self.initUI()
|
|
194
|
-
self.setWindowTitle(name)
|
|
195
|
-
|
|
196
|
-
def initUI(self):
|
|
197
|
-
centerWidget = QWidget()
|
|
198
|
-
self.setCentralWidget(centerWidget)
|
|
199
|
-
layout = QVBoxLayout()
|
|
200
|
-
centerWidget.setLayout(layout)
|
|
201
|
-
self.viewerWidget = self.vw()
|
|
202
|
-
layout.addWidget(self.viewerWidget, 1)
|
|
203
|
-
timer = QtCore.QTimer(self)
|
|
204
|
-
timer.setInterval(20) # period, in milliseconds
|
|
205
|
-
timer.timeout.connect(self.update)
|
|
206
|
-
self.viewerWidget.setCameraPosition(distance=40)
|
|
207
|
-
timer.start()
|
|
208
|
-
|
|
209
|
-
def addItems(self, named_items: dict):
|
|
210
|
-
for name, item in named_items.items():
|
|
211
|
-
self.viewerWidget.addItem(name, item)
|
|
212
|
-
|
|
213
|
-
def __getitem__(self, name: str):
|
|
214
|
-
if name in self.viewerWidget.named_items:
|
|
215
|
-
return self.viewerWidget.named_items[name]
|
|
216
|
-
else:
|
|
217
|
-
return None
|
|
218
|
-
|
|
219
|
-
def update(self):
|
|
220
|
-
# force update by timer
|
|
221
|
-
self.viewerWidget.update()
|
|
222
|
-
|
|
223
|
-
def closeEvent(self, _):
|
|
224
|
-
sys.exit(0)
|
|
225
|
-
|
|
226
|
-
def show(self):
|
|
227
|
-
self.viewerWidget.setting_window.addSetting("main win", self.viewerWidget)
|
|
228
|
-
super().show()
|