q3dviewer 1.0.8__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 +31 -94
- q3dviewer/custom_items/cloud_item.py +36 -30
- q3dviewer/custom_items/frame_item.py +56 -36
- q3dviewer/custom_items/gaussian_item.py +3 -3
- q3dviewer/custom_items/grid_item.py +88 -37
- q3dviewer/custom_items/image_item.py +1 -2
- q3dviewer/custom_items/line_item.py +4 -5
- q3dviewer/gau_io.py +0 -168
- q3dviewer/glwidget.py +22 -16
- q3dviewer/test/test_interpolation.py +58 -0
- q3dviewer/test/test_rendering.py +72 -0
- q3dviewer/tools/cinematographer.py +367 -0
- q3dviewer/tools/film_maker.py +395 -0
- q3dviewer/tools/lidar_calib.py +11 -22
- q3dviewer/tools/lidar_cam_calib.py +9 -20
- q3dviewer/utils/maths.py +155 -5
- q3dviewer/viewer.py +30 -7
- {q3dviewer-1.0.8.dist-info → q3dviewer-1.0.9.dist-info}/METADATA +8 -15
- q3dviewer-1.0.9.dist-info/RECORD +45 -0
- {q3dviewer-1.0.8.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.8.dist-info/RECORD +0 -46
- {q3dviewer-1.0.8.dist-info → q3dviewer-1.0.9.dist-info}/LICENSE +0 -0
- {q3dviewer-1.0.8.dist-info → q3dviewer-1.0.9.dist-info}/WHEEL +0 -0
- {q3dviewer-1.0.8.dist-info → q3dviewer-1.0.9.dist-info}/top_level.txt +0 -0
|
@@ -9,80 +9,131 @@ from PySide6.QtWidgets import QLabel, QDoubleSpinBox, QLineEdit
|
|
|
9
9
|
from PySide6.QtCore import QRegularExpression
|
|
10
10
|
from PySide6.QtGui import QRegularExpressionValidator
|
|
11
11
|
import numpy as np
|
|
12
|
+
from q3dviewer.utils.maths import hex_to_rgba
|
|
12
13
|
|
|
13
14
|
|
|
14
15
|
class GridItem(BaseItem):
|
|
15
|
-
def __init__(self, size, spacing, color=
|
|
16
|
+
def __init__(self, size=100, spacing=20, color='#ffffff40', offset=np.array([0., 0., 0.])):
|
|
16
17
|
super().__init__()
|
|
17
18
|
self.size = size
|
|
18
19
|
self.spacing = spacing
|
|
19
|
-
self.color = color
|
|
20
20
|
self.offset = offset
|
|
21
|
+
self.need_update_grid = True
|
|
22
|
+
self.set_color(color)
|
|
23
|
+
self.vertices = self.generate_grid_vertices()
|
|
24
|
+
|
|
25
|
+
def set_color(self, color):
|
|
26
|
+
try:
|
|
27
|
+
self.rgba = hex_to_rgba(color)
|
|
28
|
+
except ValueError:
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
def generate_grid_vertices(self):
|
|
32
|
+
vertices = []
|
|
33
|
+
x, y, z = self.offset
|
|
34
|
+
half_size = self.size / 2 # Keep as float
|
|
35
|
+
for i in np.arange(-half_size, half_size + self.spacing, self.spacing):
|
|
36
|
+
vertices.extend([i + x, -half_size + y, z, i + x, half_size + y, z]) # Grid lines parallel to Y axis
|
|
37
|
+
vertices.extend([-half_size + x, i + y, z, half_size + x, i + y, z]) # Grid lines parallel to X axis
|
|
38
|
+
vertices = np.array(vertices, dtype=np.float32)
|
|
39
|
+
return vertices
|
|
40
|
+
|
|
41
|
+
def initialize_gl(self):
|
|
42
|
+
self.vao = glGenVertexArrays(1)
|
|
43
|
+
vbo = glGenBuffers(1)
|
|
44
|
+
|
|
45
|
+
glBindVertexArray(self.vao)
|
|
46
|
+
|
|
47
|
+
glBindBuffer(GL_ARRAY_BUFFER, vbo)
|
|
48
|
+
glBufferData(GL_ARRAY_BUFFER, self.vertices.nbytes, self.vertices, GL_STATIC_DRAW)
|
|
49
|
+
|
|
50
|
+
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, ctypes.c_void_p(0))
|
|
51
|
+
glEnableVertexAttribArray(0)
|
|
52
|
+
|
|
53
|
+
glBindVertexArray(0)
|
|
21
54
|
|
|
22
55
|
def add_setting(self, layout):
|
|
23
|
-
label_size = QLabel("Set size:")
|
|
24
|
-
layout.addWidget(label_size)
|
|
25
56
|
spinbox_size = QDoubleSpinBox()
|
|
57
|
+
spinbox_size.setPrefix("Size: ")
|
|
26
58
|
spinbox_size.setSingleStep(1.0)
|
|
27
|
-
layout.addWidget(spinbox_size)
|
|
28
59
|
spinbox_size.setValue(self.size)
|
|
29
|
-
spinbox_size.valueChanged.connect(self.set_size)
|
|
30
60
|
spinbox_size.setRange(0, 100000)
|
|
61
|
+
spinbox_size.valueChanged.connect(self.set_size)
|
|
62
|
+
layout.addWidget(spinbox_size)
|
|
31
63
|
|
|
32
|
-
label_spacing = QLabel("Set spacing:")
|
|
33
|
-
layout.addWidget(label_spacing)
|
|
34
64
|
spinbox_spacing = QDoubleSpinBox()
|
|
35
|
-
|
|
65
|
+
spinbox_spacing.setPrefix("Spacing: ")
|
|
36
66
|
spinbox_spacing.setSingleStep(0.1)
|
|
37
67
|
spinbox_spacing.setValue(self.spacing)
|
|
38
|
-
spinbox_spacing.valueChanged.connect(self._on_spacing)
|
|
39
68
|
spinbox_spacing.setRange(0, 1000)
|
|
69
|
+
spinbox_spacing.valueChanged.connect(self._on_spacing)
|
|
70
|
+
layout.addWidget(spinbox_spacing)
|
|
40
71
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
72
|
+
spinbox_offset_x = QDoubleSpinBox()
|
|
73
|
+
spinbox_offset_x.setPrefix("Offset X: ")
|
|
74
|
+
spinbox_offset_x.setSingleStep(0.1)
|
|
75
|
+
spinbox_offset_x.setValue(self.offset[0])
|
|
76
|
+
spinbox_offset_x.valueChanged.connect(self._on_offset_x)
|
|
77
|
+
layout.addWidget(spinbox_offset_x)
|
|
78
|
+
|
|
79
|
+
spinbox_offset_y = QDoubleSpinBox()
|
|
80
|
+
spinbox_offset_y.setPrefix("Offset Y: ")
|
|
81
|
+
spinbox_offset_y.setSingleStep(0.1)
|
|
82
|
+
spinbox_offset_y.setValue(self.offset[1])
|
|
83
|
+
spinbox_offset_y.valueChanged.connect(self._on_offset_y)
|
|
84
|
+
layout.addWidget(spinbox_offset_y)
|
|
85
|
+
|
|
86
|
+
spinbox_offset_z = QDoubleSpinBox()
|
|
87
|
+
spinbox_offset_z.setPrefix("Offset Z: ")
|
|
88
|
+
spinbox_offset_z.setSingleStep(0.1)
|
|
89
|
+
spinbox_offset_z.setValue(self.offset[2])
|
|
90
|
+
spinbox_offset_z.valueChanged.connect(self._on_offset_z)
|
|
91
|
+
layout.addWidget(spinbox_offset_z)
|
|
50
92
|
|
|
51
93
|
def set_size(self, size):
|
|
52
94
|
self.size = size
|
|
95
|
+
self.need_update_grid = True
|
|
53
96
|
|
|
54
97
|
def _on_spacing(self, spacing):
|
|
55
98
|
if spacing > 0:
|
|
56
99
|
self.spacing = spacing
|
|
100
|
+
self.need_update_grid = True
|
|
57
101
|
|
|
58
|
-
def
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
102
|
+
def _on_offset_x(self, value):
|
|
103
|
+
self.offset[0] = value
|
|
104
|
+
print(self.offset)
|
|
105
|
+
self.need_update_grid = True
|
|
106
|
+
|
|
107
|
+
def _on_offset_y(self, value):
|
|
108
|
+
self.offset[1] = value
|
|
109
|
+
self.need_update_grid = True
|
|
110
|
+
|
|
111
|
+
def _on_offset_z(self, value):
|
|
112
|
+
self.offset[2] = value
|
|
113
|
+
self.need_update_grid = True
|
|
67
114
|
|
|
68
115
|
def set_offset(self, offset):
|
|
69
116
|
if isinstance(offset, np.ndarray) and offset.shape == (3,):
|
|
70
117
|
self.offset = offset
|
|
71
|
-
self.
|
|
118
|
+
self.need_update_grid = True
|
|
72
119
|
else:
|
|
73
120
|
raise ValueError("Offset must be a numpy array with shape (3,)")
|
|
74
121
|
|
|
75
122
|
def paint(self):
|
|
123
|
+
if self.need_update_grid:
|
|
124
|
+
self.vertices = self.generate_grid_vertices()
|
|
125
|
+
self.initialize_gl()
|
|
126
|
+
self.need_update_grid = False
|
|
127
|
+
|
|
76
128
|
glEnable(GL_BLEND)
|
|
77
129
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
glEnd()
|
|
130
|
+
|
|
131
|
+
glLineWidth(1)
|
|
132
|
+
glColor4f(*self.rgba)
|
|
133
|
+
glBindVertexArray(self.vao)
|
|
134
|
+
glDrawArrays(GL_LINES, 0, len(self.vertices) // 3)
|
|
135
|
+
glBindVertexArray(0)
|
|
136
|
+
glLineWidth(1)
|
|
86
137
|
glDisable(GL_BLEND)
|
|
87
138
|
|
|
88
139
|
|
|
@@ -154,9 +154,8 @@ class ImageItem(BaseItem):
|
|
|
154
154
|
glDisable(GL_BLEND)
|
|
155
155
|
|
|
156
156
|
def add_setting(self, layout):
|
|
157
|
-
alpha_label = QLabel("Set Alpha:")
|
|
158
|
-
layout.addWidget(alpha_label)
|
|
159
157
|
spinbox_alpha = QSpinBox()
|
|
158
|
+
spinbox_alpha.setPrefix("Alpha: ")
|
|
160
159
|
spinbox_alpha.setSingleStep(1)
|
|
161
160
|
spinbox_alpha.setRange(0, 255)
|
|
162
161
|
spinbox_alpha.setValue(self.alpha)
|
|
@@ -27,7 +27,7 @@ class LineItem(BaseItem):
|
|
|
27
27
|
self.line_type = GL_LINE_STRIP if line_type == 'LINE_STRIP' else GL_LINES
|
|
28
28
|
|
|
29
29
|
def add_setting(self, layout):
|
|
30
|
-
label_color = QLabel("
|
|
30
|
+
label_color = QLabel("Color:")
|
|
31
31
|
layout.addWidget(label_color)
|
|
32
32
|
self.color_edit = QLineEdit()
|
|
33
33
|
self.color_edit.setToolTip("Hex number, i.e. #FF4500")
|
|
@@ -38,14 +38,13 @@ class LineItem(BaseItem):
|
|
|
38
38
|
self.color_edit.setValidator(validator)
|
|
39
39
|
layout.addWidget(self.color_edit)
|
|
40
40
|
|
|
41
|
-
label_width = QLabel("Set width:")
|
|
42
|
-
layout.addWidget(label_width)
|
|
43
41
|
spinbox_width = QDoubleSpinBox()
|
|
42
|
+
spinbox_width.setPrefix("Width: ")
|
|
44
43
|
spinbox_width.setSingleStep(0.1)
|
|
45
|
-
layout.addWidget(spinbox_width)
|
|
46
44
|
spinbox_width.setValue(self.width)
|
|
47
|
-
spinbox_width.valueChanged.connect(self.set_width)
|
|
48
45
|
spinbox_width.setRange(0.1, 10.0)
|
|
46
|
+
spinbox_width.valueChanged.connect(self.set_width)
|
|
47
|
+
layout.addWidget(spinbox_width)
|
|
49
48
|
|
|
50
49
|
def _on_color(self, color):
|
|
51
50
|
try:
|
q3dviewer/gau_io.py
CHANGED
|
@@ -1,168 +0,0 @@
|
|
|
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)
|
q3dviewer/glwidget.py
CHANGED
|
@@ -4,7 +4,7 @@ Distributed under MIT license. See LICENSE for more information.
|
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
6
|
from PySide6 import QtCore
|
|
7
|
-
from PySide6.QtWidgets import QWidget, QComboBox, QVBoxLayout, QHBoxLayout,
|
|
7
|
+
from PySide6.QtWidgets import QWidget, QComboBox, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QCheckBox, QGroupBox
|
|
8
8
|
from PySide6.QtGui import QKeyEvent, QVector3D, QRegularExpressionValidator
|
|
9
9
|
from PySide6.QtCore import QRegularExpression
|
|
10
10
|
from OpenGL.GL import *
|
|
@@ -17,11 +17,9 @@ class SettingWindow(QWidget):
|
|
|
17
17
|
self.combo_items = QComboBox()
|
|
18
18
|
self.combo_items.currentIndexChanged.connect(self.on_combo_selection)
|
|
19
19
|
main_layout = QVBoxLayout()
|
|
20
|
-
|
|
21
|
-
10, 10, QSizePolicy.Minimum, QSizePolicy.Expanding)
|
|
20
|
+
main_layout.setAlignment(QtCore.Qt.AlignTop)
|
|
22
21
|
main_layout.addWidget(self.combo_items)
|
|
23
22
|
self.layout = QVBoxLayout()
|
|
24
|
-
self.layout.addItem(self.stretch)
|
|
25
23
|
main_layout.addLayout(self.layout)
|
|
26
24
|
self.setLayout(main_layout)
|
|
27
25
|
self.setWindowTitle("Setting Window")
|
|
@@ -39,17 +37,15 @@ class SettingWindow(QWidget):
|
|
|
39
37
|
child.widget().deleteLater()
|
|
40
38
|
|
|
41
39
|
def on_combo_selection(self, index):
|
|
42
|
-
self.layout.removeItem(self.stretch)
|
|
43
40
|
# remove all setting of previous widget
|
|
44
41
|
self.clear_setting()
|
|
45
|
-
|
|
46
42
|
key = list(self.items.keys())
|
|
47
43
|
item = self.items[key[index]]
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
44
|
+
group_box = QGroupBox()
|
|
45
|
+
group_layout = QVBoxLayout()
|
|
46
|
+
item.add_setting(group_layout)
|
|
47
|
+
group_box.setLayout(group_layout)
|
|
48
|
+
self.layout.addWidget(group_box)
|
|
53
49
|
|
|
54
50
|
|
|
55
51
|
class GLWidget(BaseGLWidget):
|
|
@@ -57,7 +53,7 @@ class GLWidget(BaseGLWidget):
|
|
|
57
53
|
self.followed_name = 'none'
|
|
58
54
|
self.named_items = {}
|
|
59
55
|
self.color_str = '#000000'
|
|
60
|
-
self.followable_item_name =
|
|
56
|
+
self.followable_item_name = None
|
|
61
57
|
self.setting_window = SettingWindow()
|
|
62
58
|
self.enable_show_center = True
|
|
63
59
|
super(GLWidget, self).__init__()
|
|
@@ -73,7 +69,8 @@ class GLWidget(BaseGLWidget):
|
|
|
73
69
|
|
|
74
70
|
def update(self):
|
|
75
71
|
if self.followed_name != 'none':
|
|
76
|
-
|
|
72
|
+
new_center = self.named_items[self.followed_name].T[:3, 3]
|
|
73
|
+
self.set_center(new_center)
|
|
77
74
|
super().update()
|
|
78
75
|
|
|
79
76
|
def add_setting(self, layout):
|
|
@@ -90,6 +87,10 @@ class GLWidget(BaseGLWidget):
|
|
|
90
87
|
|
|
91
88
|
label_focus = QLabel("Set Focus:")
|
|
92
89
|
combo_focus = QComboBox()
|
|
90
|
+
|
|
91
|
+
if self.followable_item_name is None:
|
|
92
|
+
self.initial_followable()
|
|
93
|
+
|
|
93
94
|
for name in self.followable_item_name:
|
|
94
95
|
combo_focus.addItem(name)
|
|
95
96
|
combo_focus.currentIndexChanged.connect(self.on_followable_selection)
|
|
@@ -101,6 +102,12 @@ class GLWidget(BaseGLWidget):
|
|
|
101
102
|
checkbox_show_center.stateChanged.connect(self.change_show_center)
|
|
102
103
|
layout.addWidget(checkbox_show_center)
|
|
103
104
|
|
|
105
|
+
def initial_followable(self):
|
|
106
|
+
self.followable_item_name = ['none']
|
|
107
|
+
for name, item in self.named_items.items():
|
|
108
|
+
if item.__class__.__name__ == 'AxisItem' and not item._disable_setting:
|
|
109
|
+
self.followable_item_name.append(name)
|
|
110
|
+
|
|
104
111
|
def set_bg_color(self, color_str):
|
|
105
112
|
try:
|
|
106
113
|
color_flat = int(color_str[1:], 16)
|
|
@@ -114,9 +121,8 @@ class GLWidget(BaseGLWidget):
|
|
|
114
121
|
|
|
115
122
|
def add_item_with_name(self, name, item):
|
|
116
123
|
self.named_items.update({name: item})
|
|
117
|
-
if
|
|
118
|
-
self.
|
|
119
|
-
self.setting_window.add_setting(name, item)
|
|
124
|
+
if not item._disable_setting:
|
|
125
|
+
self.setting_window.add_setting(name, item)
|
|
120
126
|
super().add_item(item)
|
|
121
127
|
|
|
122
128
|
def open_setting_window(self):
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Copyright 2024 Panasonic Advanced Technology Development Co.,Ltd. (Liu Yang)
|
|
5
|
+
Distributed under MIT license. See LICENSE for more information.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
"""
|
|
9
|
+
this script tests interpolation of 3D poses.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
import numpy as np
|
|
14
|
+
from q3dviewer.utils.maths import expSO3, logSO3, makeT, makeRt
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def interpolate_pose(T1, T2, v_max, omega_max, dt=0.1):
|
|
18
|
+
R1, t1 = makeRt(T1)
|
|
19
|
+
R2, t2 = makeRt(T2)
|
|
20
|
+
|
|
21
|
+
# Get transform time based on linear velocity
|
|
22
|
+
d = np.linalg.norm(t2 - t1)
|
|
23
|
+
t_lin = d / v_max
|
|
24
|
+
|
|
25
|
+
# Get transform time based on angular velocity
|
|
26
|
+
omega = logSO3(R2 @ R1.T)
|
|
27
|
+
theta = np.linalg.norm(omega)
|
|
28
|
+
t_ang = theta / omega_max
|
|
29
|
+
|
|
30
|
+
# Get total time based on the linear and angular time
|
|
31
|
+
t_total = max(t_lin, t_ang)
|
|
32
|
+
num_steps = int(np.ceil(t_total / dt))
|
|
33
|
+
|
|
34
|
+
# Generate interpolated transforms
|
|
35
|
+
interpolated_Ts = []
|
|
36
|
+
for i in range(num_steps + 1):
|
|
37
|
+
s = i / num_steps
|
|
38
|
+
t_interp = (1 - s) * t1 + s * t2
|
|
39
|
+
# Interpolate rotation using SO3
|
|
40
|
+
R_interp = expSO3(s * omega) @ R1
|
|
41
|
+
T_interp = makeT(R_interp, t_interp)
|
|
42
|
+
interpolated_Ts.append(T_interp)
|
|
43
|
+
|
|
44
|
+
return interpolated_Ts
|
|
45
|
+
|
|
46
|
+
if __name__ == "__main__":
|
|
47
|
+
T1 = np.eye(4) # Identity transformation
|
|
48
|
+
T2 = np.array([[0, -1, 0, 1], [1, 0, 0, 2], [0, 0, 1, 3], [0, 0, 0, 1]]) # Target transformation
|
|
49
|
+
|
|
50
|
+
v_max = 1.0 # Maximum linear velocity (m/s)
|
|
51
|
+
omega_max = np.pi / 4 # Maximum angular velocity (rad/s)
|
|
52
|
+
|
|
53
|
+
# Perform interpolation
|
|
54
|
+
interpolated_poses = interpolate_pose(T1, T2, v_max, omega_max)
|
|
55
|
+
for i, T in enumerate(interpolated_poses):
|
|
56
|
+
print(f"Step {i}:\n{T}\n")
|
|
57
|
+
|
|
58
|
+
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
"""
|
|
4
|
+
Copyright 2024 Panasonic Advanced Technology Development Co.,Ltd. (Liu Yang)
|
|
5
|
+
Distributed under MIT license. See LICENSE for more information.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
"""
|
|
9
|
+
this script tests the rendering of a cloud in a camera frame based on the
|
|
10
|
+
camera pose and intrinsic matrix
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import numpy as np
|
|
14
|
+
import q3dviewer as q3d
|
|
15
|
+
import cv2
|
|
16
|
+
|
|
17
|
+
cloud, _ = q3d.load_pcd('/home/liu/lab.pcd')
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
Tcw = np.array([[7.07106781e-01, 7.07106781e-01, 0.00000000e+00,
|
|
21
|
+
0.00000000e+00],
|
|
22
|
+
[-3.53553391e-01, 3.53553391e-01, 8.66025404e-01,
|
|
23
|
+
3.55271368e-15],
|
|
24
|
+
[6.12372436e-01, -6.12372436e-01, 5.00000000e-01,
|
|
25
|
+
-4.00000000e+01],
|
|
26
|
+
[0.00000000e+00, 0.00000000e+00, 0.00000000e+00,
|
|
27
|
+
1.00000000e+00]])
|
|
28
|
+
# convert the opengl camera coordinate to the opencv camera coordinate
|
|
29
|
+
Tconv = np.array([[1, 0, 0, 0],
|
|
30
|
+
[0, -1, 0, 0],
|
|
31
|
+
[0, 0, -1, 0],
|
|
32
|
+
[0, 0, 0, 1]])
|
|
33
|
+
|
|
34
|
+
Tcw = Tconv @ Tcw
|
|
35
|
+
|
|
36
|
+
K = np.array([[1.64718029e+03, 0.00000000e+00, 9.51000000e+02],
|
|
37
|
+
[0.00000000e+00, 1.64718036e+03, 5.31000000e+02],
|
|
38
|
+
[0.00000000e+00, 0.00000000e+00, 1.00000000e+00]])
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def render_frame(cloud, Tcw, K, width, height):
|
|
42
|
+
image = np.zeros((height, width, 3), dtype=np.uint8)
|
|
43
|
+
Rcw, tcw = Tcw[:3, :3], Tcw[:3, 3]
|
|
44
|
+
pc = (Rcw @ cloud['xyz'].T).T + tcw
|
|
45
|
+
uv = (K @ pc.T).T
|
|
46
|
+
uv = uv[:, :2] / uv[:, 2][:, np.newaxis]
|
|
47
|
+
mask = (pc[:, 2] > 0) & (uv[:, 0] > 0) & (
|
|
48
|
+
uv[:, 0] < width) & (uv[:, 1] > 0) & (uv[:, 1] < height)
|
|
49
|
+
uv = uv[mask]
|
|
50
|
+
u = uv[:, 0].astype(int)
|
|
51
|
+
v = uv[:, 1].astype(int)
|
|
52
|
+
rgb = cloud['irgb'][mask]
|
|
53
|
+
r = rgb >> 16 & 0xff
|
|
54
|
+
g = rgb >> 8 & 0xff
|
|
55
|
+
b = rgb & 0xff
|
|
56
|
+
|
|
57
|
+
# Sort by depth to ensure front points are drawn first
|
|
58
|
+
depth = pc[mask, 2]
|
|
59
|
+
sorted_indices = np.argsort(depth)
|
|
60
|
+
u = u[sorted_indices]
|
|
61
|
+
v = v[sorted_indices]
|
|
62
|
+
r = r[sorted_indices]
|
|
63
|
+
g = g[sorted_indices]
|
|
64
|
+
b = b[sorted_indices]
|
|
65
|
+
|
|
66
|
+
image[v, u] = np.stack([b, g, r], axis=1)
|
|
67
|
+
return image
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
image = render_frame(cloud, Tcw, K, 1902, 1062)
|
|
71
|
+
cv2.imshow('image', image)
|
|
72
|
+
cv2.waitKey(0)
|