q3dviewer 1.0.7__py3-none-any.whl → 1.0.9__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 +53 -8
- q3dviewer/base_item.py +15 -0
- q3dviewer/custom_items/axis_item.py +33 -96
- q3dviewer/custom_items/cloud_item.py +42 -31
- 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 -17
- q3dviewer/test/test_interpolation.py +58 -0
- q3dviewer/test/test_rendering.py +72 -0
- q3dviewer/tools/cinematographer.py +367 -0
- q3dviewer/tools/film_maker.py +395 -0
- q3dviewer/tools/lidar_calib.py +11 -22
- q3dviewer/tools/lidar_cam_calib.py +9 -20
- q3dviewer/tools/ros_viewer.py +7 -8
- q3dviewer/utils/maths.py +155 -5
- q3dviewer/viewer.py +30 -7
- {q3dviewer-1.0.7.dist-info → q3dviewer-1.0.9.dist-info}/METADATA +8 -15
- q3dviewer-1.0.9.dist-info/RECORD +45 -0
- {q3dviewer-1.0.7.dist-info → q3dviewer-1.0.9.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.7.dist-info/RECORD +0 -46
- {q3dviewer-1.0.7.dist-info → q3dviewer-1.0.9.dist-info}/LICENSE +0 -0
- {q3dviewer-1.0.7.dist-info → q3dviewer-1.0.9.dist-info}/WHEEL +0 -0
- {q3dviewer-1.0.7.dist-info → q3dviewer-1.0.9.dist-info}/top_level.txt +0 -0
q3dviewer/utils/maths.py
CHANGED
|
@@ -3,9 +3,145 @@ Copyright 2024 Panasonic Advanced Technology Development Co.,Ltd. (Liu Yang)
|
|
|
3
3
|
Distributed under MIT license. See LICENSE for more information.
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
|
+
"""
|
|
7
|
+
Math proofs and implementations:
|
|
8
|
+
https://github.com/scomup/MathematicalRobotics.git
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
|
|
6
12
|
import numpy as np
|
|
7
13
|
|
|
8
14
|
|
|
15
|
+
_epsilon_ = 1e-5
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def skew(vector):
|
|
19
|
+
return np.array([[0, -vector[2], vector[1]],
|
|
20
|
+
[vector[2], 0, -vector[0]],
|
|
21
|
+
[-vector[1], vector[0], 0]])
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def expSO3(omega):
|
|
25
|
+
"""
|
|
26
|
+
Exponential map of SO3
|
|
27
|
+
The proof is shown in 3d_rotation_group.md (10)
|
|
28
|
+
"""
|
|
29
|
+
theta2 = omega.dot(omega)
|
|
30
|
+
theta = np.sqrt(theta2)
|
|
31
|
+
nearZero = theta2 <= _epsilon_
|
|
32
|
+
W = skew(omega)
|
|
33
|
+
if (nearZero):
|
|
34
|
+
return np.eye(3) + W
|
|
35
|
+
else:
|
|
36
|
+
K = W/theta
|
|
37
|
+
KK = K.dot(K)
|
|
38
|
+
sin_theta = np.sin(theta)
|
|
39
|
+
one_minus_cos = 1 - np.cos(theta)
|
|
40
|
+
R = np.eye(3) + sin_theta * K + one_minus_cos * KK # rotation.md (10)
|
|
41
|
+
return R
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def logSO3(R):
|
|
45
|
+
"""
|
|
46
|
+
Logarithm map of SO3
|
|
47
|
+
The proof is shown in rotation.md (14)
|
|
48
|
+
"""
|
|
49
|
+
R11, R12, R13 = R[0, :]
|
|
50
|
+
R21, R22, R23 = R[1, :]
|
|
51
|
+
R31, R32, R33 = R[2, :]
|
|
52
|
+
tr = np.trace(R)
|
|
53
|
+
omega = np.zeros(3)
|
|
54
|
+
v = np.array([R32 - R23, R13 - R31, R21 - R12])
|
|
55
|
+
# when trace == -1, i.e., the theta is approx to +-pi, +-3pi, +-5pi, etc.
|
|
56
|
+
# we do something special
|
|
57
|
+
if (tr + 1.0 < 1e-3):
|
|
58
|
+
if (R33 > R22 and R33 > R11):
|
|
59
|
+
# R33 is largest
|
|
60
|
+
# sin(theta) approx to sgn_w*pi-theta, cos(theta) approx to -1
|
|
61
|
+
W = R21 - R12 # 2*r3*sin(theta) = 2*r3*(sgn_w*pi-theta)
|
|
62
|
+
Q1 = R31 + R13 # 4 * r1*r3
|
|
63
|
+
Q2 = R23 + R32 # 4 * r2*r3
|
|
64
|
+
Q3 = 2.0 + 2.0 * R33 # 4 * r3*r3
|
|
65
|
+
r = np.sqrt(Q3) # 2 * r3
|
|
66
|
+
one_over_r = 1 / r # 1 / (2*r3)
|
|
67
|
+
norm = np.sqrt(Q1*Q1 + Q2*Q2 + Q3*Q3 + W*W) # 4*r3
|
|
68
|
+
sgn_w = np.sign(W) # get the sgn of theta
|
|
69
|
+
mag = np.pi - (2 * sgn_w * W) / norm # theta*sgn_w
|
|
70
|
+
scale = 0.5 * mag * one_over_r # theta * sgn_w / (4*r3)
|
|
71
|
+
# omega = theta * [4*r1*r3, 4*r2*r3, 4*r3*r3]/ (4*r3)
|
|
72
|
+
omega = sgn_w * scale * np.array([Q1, Q2, Q3])
|
|
73
|
+
elif (R22 > R11):
|
|
74
|
+
# R22 is the largest
|
|
75
|
+
W = R13 - R31 # 2*r2*sin(theta) = 2*r2*(sgn_w*pi-theta)
|
|
76
|
+
Q1 = R12 + R21 # 4 * r2*r1
|
|
77
|
+
Q2 = 2.0 + 2.0 * R22 # 4 * r2*r2
|
|
78
|
+
Q3 = R23 + R32 # 4 * r2*r3
|
|
79
|
+
r = np.sqrt(Q2)
|
|
80
|
+
one_over_r = 1 / r
|
|
81
|
+
norm = np.sqrt(Q1*Q1 + Q2*Q2 + Q3*Q3 + W*W)
|
|
82
|
+
sgn_w = np.sign(W)
|
|
83
|
+
mag = np.pi - (2 * sgn_w * W) / norm
|
|
84
|
+
scale = 0.5 * one_over_r * mag
|
|
85
|
+
omega = sgn_w * scale * np.array([Q1, Q2, Q3])
|
|
86
|
+
else:
|
|
87
|
+
# R11 is the largest
|
|
88
|
+
W = R32 - R23 # 2*r1*sin(theta) = 2*r1*(sgn_w*pi-theta)
|
|
89
|
+
Q1 = 2.0 + 2.0 * R11 # 4 * r1*r1
|
|
90
|
+
Q2 = R12 + R21 # 4 * r1*r2
|
|
91
|
+
Q3 = R31 + R13 # 4 * r1*r3
|
|
92
|
+
r = np.sqrt(Q1)
|
|
93
|
+
one_over_r = 1 / r
|
|
94
|
+
norm = np.sqrt(Q1*Q1 + Q2*Q2 + Q3*Q3 + W*W)
|
|
95
|
+
sgn_w = np.sign(W)
|
|
96
|
+
mag = np.pi - (2 * sgn_w * W) / norm
|
|
97
|
+
scale = 0.5 * one_over_r * mag
|
|
98
|
+
omega = sgn_w * scale * np.array([Q1, Q2, Q3])
|
|
99
|
+
else:
|
|
100
|
+
magnitude = 0
|
|
101
|
+
tr_3 = tr - 3.0
|
|
102
|
+
if (tr_3 < -1e-6):
|
|
103
|
+
# this is the normal case -1 < trace < 3
|
|
104
|
+
theta = np.arccos((tr - 1.0) / 2.0)
|
|
105
|
+
magnitude = theta / (2.0 * np.sin(theta))
|
|
106
|
+
else:
|
|
107
|
+
# when theta near 0, +-2pi, +-4pi, etc. (trace near 3.0)
|
|
108
|
+
# use Taylor expansion: theta \approx 1/2-(t-3)/12 + O((t-3)^2)
|
|
109
|
+
# see https://github.com/borglab/gtsam/issues/746 for details
|
|
110
|
+
magnitude = 0.5 - tr_3 / 12.0 + tr_3*tr_3/60.0
|
|
111
|
+
omega = magnitude * np.array([R32 - R23, R13 - R31, R21 - R12])
|
|
112
|
+
return omega
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def interpolate_pose(T1, T2, v_max, omega_max, dt=0.02):
|
|
116
|
+
R1, t1 = makeRt(T1)
|
|
117
|
+
R2, t2 = makeRt(T2)
|
|
118
|
+
|
|
119
|
+
# Get transfrom time based on linear velocity
|
|
120
|
+
d = np.linalg.norm(t2 - t1)
|
|
121
|
+
t_lin = d / v_max
|
|
122
|
+
|
|
123
|
+
# Get transform time based on angular velocity
|
|
124
|
+
omega = logSO3(R2 @ R1.T)
|
|
125
|
+
theta = np.linalg.norm(omega)
|
|
126
|
+
t_ang = theta / omega_max
|
|
127
|
+
|
|
128
|
+
# Get total time based on the linear and angular time.
|
|
129
|
+
t_total = max(t_lin, t_ang)
|
|
130
|
+
num_steps = int(np.ceil(t_total / dt))
|
|
131
|
+
|
|
132
|
+
# Generate interpolated transforms
|
|
133
|
+
interpolated_Ts = []
|
|
134
|
+
for i in range(num_steps):
|
|
135
|
+
s = i / num_steps
|
|
136
|
+
t_interp = (1 - s) * t1 + s * t2
|
|
137
|
+
# Interpolate rotation using SO3.
|
|
138
|
+
R_interp = expSO3(s * omega) @ R1
|
|
139
|
+
T_interp = makeT(R_interp, t_interp)
|
|
140
|
+
interpolated_Ts.append(T_interp)
|
|
141
|
+
|
|
142
|
+
return interpolated_Ts
|
|
143
|
+
|
|
144
|
+
|
|
9
145
|
def frustum(left, right, bottom, top, near, far):
|
|
10
146
|
# see https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/glFrustum.xml
|
|
11
147
|
if near <= 0 or far <= 0 or near >= far or left == right or bottom == top:
|
|
@@ -157,11 +293,25 @@ def makeRt(T):
|
|
|
157
293
|
return R, t
|
|
158
294
|
|
|
159
295
|
def hex_to_rgba(hex_color):
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
296
|
+
if not hex_color.startswith("#"):
|
|
297
|
+
print("Invalid hex color string.")
|
|
298
|
+
return (1.0, 1.0, 1.0, 1.0)
|
|
299
|
+
if len(hex_color) == 7:
|
|
300
|
+
color_flat = int(hex_color[1:], 16)
|
|
301
|
+
red = (color_flat >> 16) & 0xFF
|
|
302
|
+
green = (color_flat >> 8) & 0xFF
|
|
303
|
+
blue = color_flat & 0xFF
|
|
304
|
+
return (red / 255.0, green / 255.0, blue / 255.0, 1.0)
|
|
305
|
+
elif len(hex_color) == 9:
|
|
306
|
+
color_flat = int(hex_color[1:], 16)
|
|
307
|
+
red = (color_flat >> 24) & 0xFF
|
|
308
|
+
green = (color_flat >> 16) & 0xFF
|
|
309
|
+
blue = (color_flat >> 8) & 0xFF
|
|
310
|
+
alpha = color_flat & 0xFF
|
|
311
|
+
return (red / 255.0, green / 255.0, blue / 255.0, alpha / 255.0)
|
|
312
|
+
else:
|
|
313
|
+
print("Invalid hex color string.")
|
|
314
|
+
return (1.0, 1.0, 1.0, 1.0)
|
|
165
315
|
|
|
166
316
|
# euler = np.array([1, 0.1, 0.1])
|
|
167
317
|
# euler_angles = matrix_to_euler(euler_to_matrix(euler))
|
q3dviewer/viewer.py
CHANGED
|
@@ -6,7 +6,7 @@ Distributed under MIT license. See LICENSE for more information.
|
|
|
6
6
|
|
|
7
7
|
from q3dviewer.glwidget import *
|
|
8
8
|
import signal
|
|
9
|
-
from PySide6.QtWidgets import QMainWindow, QApplication
|
|
9
|
+
from PySide6.QtWidgets import QMainWindow, QApplication, QHBoxLayout
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
def handler(signal, frame):
|
|
@@ -15,22 +15,45 @@ def handler(signal, frame):
|
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
class Viewer(QMainWindow):
|
|
18
|
-
def __init__(self, name='Viewer', win_size=[1920, 1080]
|
|
18
|
+
def __init__(self, name='Viewer', win_size=[1920, 1080],
|
|
19
|
+
gl_widget_class=GLWidget, update_interval=20):
|
|
19
20
|
signal.signal(signal.SIGINT, handler)
|
|
20
21
|
super(Viewer, self).__init__()
|
|
21
22
|
self.setGeometry(0, 0, win_size[0], win_size[1])
|
|
23
|
+
self.gl_widget_class = gl_widget_class
|
|
22
24
|
self.init_ui()
|
|
25
|
+
self.update_interval = update_interval
|
|
26
|
+
self.add_update_timer()
|
|
23
27
|
self.setWindowTitle(name)
|
|
28
|
+
self.installEventFilter(self)
|
|
24
29
|
|
|
25
30
|
def init_ui(self):
|
|
26
31
|
center_widget = QWidget()
|
|
27
32
|
self.setCentralWidget(center_widget)
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
self.
|
|
33
|
+
main_layout = QHBoxLayout()
|
|
34
|
+
self.add_control_panel(main_layout)
|
|
35
|
+
center_widget.setLayout(main_layout)
|
|
36
|
+
self.glwidget = self.gl_widget_class()
|
|
37
|
+
main_layout.addWidget(self.glwidget, 1)
|
|
38
|
+
self.default_gl_setting(self.glwidget)
|
|
39
|
+
|
|
40
|
+
def add_control_panel(self, main_layout):
|
|
41
|
+
"""
|
|
42
|
+
Override this function to add your own control panel to
|
|
43
|
+
the left side of the main window.
|
|
44
|
+
Don't forget add your own layout to the main_layout.
|
|
45
|
+
"""
|
|
46
|
+
pass
|
|
47
|
+
|
|
48
|
+
def default_gl_setting(self, glwidget):
|
|
49
|
+
"""
|
|
50
|
+
Override this function to set the default opengl setting of the viewer.
|
|
51
|
+
"""
|
|
52
|
+
pass
|
|
53
|
+
|
|
54
|
+
def add_update_timer(self):
|
|
32
55
|
timer = QtCore.QTimer(self)
|
|
33
|
-
timer.setInterval(
|
|
56
|
+
timer.setInterval(self.update_interval) # period, in milliseconds
|
|
34
57
|
timer.timeout.connect(self.update)
|
|
35
58
|
timer.start()
|
|
36
59
|
|
|
@@ -1,21 +1,14 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: q3dviewer
|
|
3
|
-
Version: 1.0.
|
|
4
|
-
|
|
5
|
-
Home-page: UNKNOWN
|
|
6
|
-
Author: UNKNOWN
|
|
7
|
-
Author-email: UNKNOWN
|
|
8
|
-
License: UNKNOWN
|
|
9
|
-
Platform: UNKNOWN
|
|
10
|
-
Requires-Dist: PyOpenGL
|
|
11
|
-
Requires-Dist: laspy
|
|
12
|
-
Requires-Dist: meshio
|
|
3
|
+
Version: 1.0.9
|
|
4
|
+
License-File: LICENSE
|
|
13
5
|
Requires-Dist: numpy
|
|
6
|
+
Requires-Dist: pyside6
|
|
7
|
+
Requires-Dist: PyOpenGL
|
|
14
8
|
Requires-Dist: pillow
|
|
15
|
-
Requires-Dist:
|
|
9
|
+
Requires-Dist: meshio
|
|
16
10
|
Requires-Dist: pypcd4
|
|
17
|
-
Requires-Dist:
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
11
|
+
Requires-Dist: pye57
|
|
12
|
+
Requires-Dist: laspy
|
|
13
|
+
Requires-Dist: imageio
|
|
21
14
|
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
q3dviewer/__init__.py,sha256=rP5XX_x8g7hxIMqNHlU89BN4dt5MSvoYYwip68fCmhc,173
|
|
2
|
+
q3dviewer/base_glwidget.py,sha256=7CJKea2JSP78jsESZJP38yfLhhRINF-VQ9R8sMXwAo0,10697
|
|
3
|
+
q3dviewer/base_item.py,sha256=lzb04oRaS4rRJrAP6C1Bu4ugK237FgupMTB97zjNVFw,1768
|
|
4
|
+
q3dviewer/gau_io.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
+
q3dviewer/glwidget.py,sha256=im8hjVYEL0Zl7fOIHTQMJdWu7WNOHlvTdIDYjebz9WA,4940
|
|
6
|
+
q3dviewer/viewer.py,sha256=LH1INLFhi6pRjzazzQJ0AWT4hgyXI6GnmqoJFUwUZVE,2517
|
|
7
|
+
q3dviewer/custom_items/__init__.py,sha256=gOiAxdjDaAnFL8YbqSEWWWOwUrJfvzP9JLR34sCB9-4,434
|
|
8
|
+
q3dviewer/custom_items/axis_item.py,sha256=PTBSf5DmQI8ieSinYjY_aC7P8q1nzE-2Vc2GNd1O3Os,2568
|
|
9
|
+
q3dviewer/custom_items/cloud_io_item.py,sha256=gjK3n9WKB7JwxC93ijkweEHA5EezpgNJ8KO-PBaDKCs,2835
|
|
10
|
+
q3dviewer/custom_items/cloud_item.py,sha256=7r6s_j1qEUfLe70t-M68-oIrGRrPOUcZp18ML6jST0s,12542
|
|
11
|
+
q3dviewer/custom_items/frame_item.py,sha256=6BOM3MXi-Akv6KUXDC3QYEAXKxwk0Eo1KQn-F7jkqrQ,7559
|
|
12
|
+
q3dviewer/custom_items/gaussian_item.py,sha256=CZoXMmj2JPFfMqu7v4q4dLUI2cg_WfE1DHWGXjEYn6M,9869
|
|
13
|
+
q3dviewer/custom_items/grid_item.py,sha256=20n4TGm5YEaudhnEOCOk-HtsKwxVxaPr8YV36kO04yU,4802
|
|
14
|
+
q3dviewer/custom_items/image_item.py,sha256=ctNR81fVgxkdl2n3U_TPFL6yn086UhNI_A9fFHgkc-4,5491
|
|
15
|
+
q3dviewer/custom_items/line_item.py,sha256=u0oFN2iHzsRHtnbvyvC_iglEkCwEU2NnTuE536sKUAE,4348
|
|
16
|
+
q3dviewer/custom_items/text_item.py,sha256=nuHMVMQrwy50lNk9hxB94criFxbJJK-SYiK2fSXWUMQ,2158
|
|
17
|
+
q3dviewer/shaders/cloud_frag.glsl,sha256=tbCsDUp9YlPe0hRWlFS724SH6TtMeLO-GVYROzEElZg,609
|
|
18
|
+
q3dviewer/shaders/cloud_vert.glsl,sha256=Vxgw-Zrr0knAK0z4qMXKML6IC4EbffKMwYN2TMXROoI,2117
|
|
19
|
+
q3dviewer/shaders/gau_frag.glsl,sha256=5_UY84tWDts59bxP8x4I-wgnzY8aGeGuo28wX--LW7E,975
|
|
20
|
+
q3dviewer/shaders/gau_prep.glsl,sha256=eCT9nm65uz32w8NaDjeGKhyAZh42Aea-QTwr3yQVr9U,7218
|
|
21
|
+
q3dviewer/shaders/gau_vert.glsl,sha256=NNbVhv_JyqZDK9iXAyBAcIHAtim7G9yWbC9IaUfTL1w,1666
|
|
22
|
+
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
|
+
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
|
|
28
|
+
q3dviewer/tools/example_viewer.py,sha256=yeVXT0k4-h1vTLKnGzWADZD3our6XUaYUTy0p5daTkE,959
|
|
29
|
+
q3dviewer/tools/film_maker.py,sha256=MtmmL1-iogSmHXCZc0MLk35_bP-RMBN8twaOtTkv77w,15180
|
|
30
|
+
q3dviewer/tools/gaussian_viewer.py,sha256=vIwWmiFhjNmknrEkBLzt2yiegeH7LP3OeNjnGM6GzaI,1633
|
|
31
|
+
q3dviewer/tools/lidar_calib.py,sha256=M01bGg2mT8LwVcYybolr4UW_UUaR-f-BFciEHtjeK-w,10488
|
|
32
|
+
q3dviewer/tools/lidar_cam_calib.py,sha256=SYNLDvi15MX7Q3aGn771fvu1cES9xeXgP0_WmDq33w4,11200
|
|
33
|
+
q3dviewer/tools/ros_viewer.py,sha256=ARB3I5wohY3maP8dCu0O0hxObd6JFKuK2y7AApVgMWA,2551
|
|
34
|
+
q3dviewer/utils/__init__.py,sha256=irm8Z_bT8l9kzhoMlds2Dal8g4iw4vjmqNPZSs4W6e0,157
|
|
35
|
+
q3dviewer/utils/cloud_io.py,sha256=ttD8FJExdDhXB1Z0Ej9S939i8gcq4JfyqwLXVh8CEFw,11094
|
|
36
|
+
q3dviewer/utils/convert_ros_msg.py,sha256=sAoQfy3qLQKsIArBAVm8H--wlQXOcmkKK3-Ox9UCcrc,1686
|
|
37
|
+
q3dviewer/utils/gl_helper.py,sha256=dRY_kUqyPMr7NTcupUr6_VTvgnj53iE2C0Lk0-oFYsI,1435
|
|
38
|
+
q3dviewer/utils/maths.py,sha256=5TmjWUX1K3UjygXxrUsydjbo7tPzu0gD-yy7qtQUGBU,10588
|
|
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,,
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
[console_scripts]
|
|
2
2
|
cloud_viewer = q3dviewer.tools.cloud_viewer:main
|
|
3
|
+
film_maker = q3dviewer.tools.film_maker:main
|
|
3
4
|
gaussian_viewer = q3dviewer.tools.gaussian_viewer:main
|
|
4
5
|
lidar_calib = q3dviewer.tools.lidar_calib:main
|
|
5
6
|
lidar_cam_calib = q3dviewer.tools.lidar_cam_calib:main
|
|
6
7
|
mesh_viewer = q3dviewer.tools.mesh_viewer:main
|
|
7
8
|
ros_viewer = q3dviewer.tools.ros_viewer:main
|
|
8
|
-
|
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()
|
q3dviewer/cloud_viewer.py
DELETED
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
|
|
3
|
-
import numpy as np
|
|
4
|
-
from q3dviewer.custom_items import *
|
|
5
|
-
from q3dviewer.basic_window import *
|
|
6
|
-
from pypcd4 import PointCloud
|
|
7
|
-
|
|
8
|
-
class CloudViewer(Viewer):
|
|
9
|
-
def __init__(self):
|
|
10
|
-
super(CloudViewer, self).__init__(name="Cloud Viewer")
|
|
11
|
-
self.setAcceptDrops(True)
|
|
12
|
-
|
|
13
|
-
def dragEnterEvent(self, event):
|
|
14
|
-
if event.mimeData().hasUrls():
|
|
15
|
-
event.accept()
|
|
16
|
-
else:
|
|
17
|
-
event.ignore()
|
|
18
|
-
|
|
19
|
-
def dropEvent(self, event):
|
|
20
|
-
for url in event.mimeData().urls():
|
|
21
|
-
file_path = url.toLocalFile()
|
|
22
|
-
self.openCloudFile(file_path)
|
|
23
|
-
|
|
24
|
-
def openCloudFile(self, file):
|
|
25
|
-
cloud_item = self['cloud']
|
|
26
|
-
if cloud_item is None:
|
|
27
|
-
print("Can't find clouditem.")
|
|
28
|
-
return
|
|
29
|
-
if file.endswith('.pcd'):
|
|
30
|
-
pc = PointCloud.from_path(file).pc_data
|
|
31
|
-
if 'rgb' in pc.dtype.names:
|
|
32
|
-
color = pc["rgb"].astype(np.uint32)
|
|
33
|
-
cloud_item.setColorMode('RGB')
|
|
34
|
-
elif 'intensity' in pc.dtype.names:
|
|
35
|
-
color = pc["intensity"].astype(np.uint32)
|
|
36
|
-
cloud_item.setColorMode('I')
|
|
37
|
-
else:
|
|
38
|
-
color = pc['z'].astype(np.uint32)
|
|
39
|
-
cloud_item.setColorMode('#FFFFFF')
|
|
40
|
-
cloud = np.rec.fromarrays(
|
|
41
|
-
[np.stack([pc["x"], pc["y"], pc["z"]], axis=1), color],
|
|
42
|
-
dtype=cloud_item.data_type)
|
|
43
|
-
elif file.endswith('.npy'):
|
|
44
|
-
pc = np.load(file)
|
|
45
|
-
cloud = np.rec.fromarrays(
|
|
46
|
-
[np.stack([pc[:, 0], pc[:, 1], pc[:, 2]], axis=1), pc[:, 3].astype(np.uint32)],
|
|
47
|
-
dtype=cloud_item.data_type)
|
|
48
|
-
cloud_item.setData(data=cloud)
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
def main():
|
|
52
|
-
import argparse
|
|
53
|
-
parser = argparse.ArgumentParser()
|
|
54
|
-
parser.add_argument("--pcd", help="the pcd path")
|
|
55
|
-
args = parser.parse_args()
|
|
56
|
-
app = QApplication([])
|
|
57
|
-
viewer = CloudViewer()
|
|
58
|
-
cloud_item = CloudIOItem(size=1, alpha=0.1)
|
|
59
|
-
axis_item = GLAxisItem(size=0.5, width=5)
|
|
60
|
-
gird_item = GridItem(size=1000, spacing=20)
|
|
61
|
-
# viewer.viewerWidget.setBackgroundColor(255, 255, 255, 255)
|
|
62
|
-
|
|
63
|
-
viewer.addItems({'cloud': cloud_item, 'grid': gird_item, 'axis': axis_item})
|
|
64
|
-
|
|
65
|
-
if args.pcd:
|
|
66
|
-
pcd_fn = args.pcd
|
|
67
|
-
viewer.openCloudFile(pcd_fn)
|
|
68
|
-
|
|
69
|
-
viewer.show()
|
|
70
|
-
app.exec_()
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
if __name__ == '__main__':
|
|
74
|
-
main()
|