q3dviewer 1.0.5__py3-none-any.whl → 1.0.7__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 +14 -9
- q3dviewer/basic_window.py +228 -0
- q3dviewer/cloud_viewer.py +74 -0
- q3dviewer/custom_items/camera_frame_item.py +173 -0
- q3dviewer/custom_items/cloud_item.py +10 -11
- q3dviewer/custom_items/trajectory_item.py +79 -0
- q3dviewer/gau_io.py +168 -0
- q3dviewer/utils/convert_ros_msg.py +2 -0
- q3dviewer/utils.py +71 -0
- {q3dviewer-1.0.5.dist-info → q3dviewer-1.0.7.dist-info}/METADATA +15 -7
- {q3dviewer-1.0.5.dist-info → q3dviewer-1.0.7.dist-info}/RECORD +15 -10
- {q3dviewer-1.0.5.dist-info → q3dviewer-1.0.7.dist-info}/entry_points.txt +1 -0
- {q3dviewer-1.0.5.dist-info → q3dviewer-1.0.7.dist-info}/LICENSE +0 -0
- {q3dviewer-1.0.5.dist-info → q3dviewer-1.0.7.dist-info}/WHEEL +0 -0
- {q3dviewer-1.0.5.dist-info → q3dviewer-1.0.7.dist-info}/top_level.txt +0 -0
q3dviewer/base_glwidget.py
CHANGED
|
@@ -123,6 +123,7 @@ class BaseGLWidget(QtOpenGLWidgets.QOpenGLWidget):
|
|
|
123
123
|
self.show_center = True
|
|
124
124
|
|
|
125
125
|
def paintGL(self):
|
|
126
|
+
pass
|
|
126
127
|
self.update_model_projection()
|
|
127
128
|
self.update_model_view()
|
|
128
129
|
bgcolor = self.color
|
|
@@ -134,10 +135,12 @@ class BaseGLWidget(QtOpenGLWidgets.QOpenGLWidget):
|
|
|
134
135
|
glMatrixMode(GL_MODELVIEW)
|
|
135
136
|
glPushMatrix()
|
|
136
137
|
glPushAttrib(GL_ALL_ATTRIB_BITS)
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
138
|
+
try:
|
|
139
|
+
item.paint()
|
|
140
|
+
finally:
|
|
141
|
+
glPopAttrib()
|
|
142
|
+
glMatrixMode(GL_MODELVIEW)
|
|
143
|
+
glPopMatrix()
|
|
141
144
|
|
|
142
145
|
# Show center as a point if updated by mouse move event
|
|
143
146
|
if self.enable_show_center and self.show_center:
|
|
@@ -193,12 +196,14 @@ class BaseGLWidget(QtOpenGLWidgets.QOpenGLWidget):
|
|
|
193
196
|
glLoadMatrixf(m.T)
|
|
194
197
|
|
|
195
198
|
def get_view_matrix(self):
|
|
196
|
-
|
|
197
|
-
|
|
199
|
+
two = self.center # the origin(center) in the world frame
|
|
200
|
+
tco = np.array([0, 0, self.dist]) # the origin(center) in camera frame
|
|
198
201
|
Rwc = euler_to_matrix(self.euler)
|
|
199
|
-
twc =
|
|
200
|
-
|
|
201
|
-
|
|
202
|
+
twc = two + Rwc @ tco
|
|
203
|
+
Rcw = Rwc.T
|
|
204
|
+
tcw = -Rcw @ twc
|
|
205
|
+
Tcw = makeT(Rcw, tcw)
|
|
206
|
+
return Tcw
|
|
202
207
|
|
|
203
208
|
def set_cam_position(self, **kwargs):
|
|
204
209
|
pos = kwargs.get('pos', None)
|
|
@@ -0,0 +1,228 @@
|
|
|
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()
|
|
@@ -0,0 +1,74 @@
|
|
|
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()
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import pyqtgraph.opengl as gl
|
|
2
|
+
from OpenGL.GL import *
|
|
3
|
+
import numpy as np
|
|
4
|
+
from OpenGL.GL import shaders
|
|
5
|
+
from PIL import Image
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
# Vertex and Fragment shader source code
|
|
9
|
+
vertex_shader_source = """
|
|
10
|
+
#version 330 core
|
|
11
|
+
layout(location = 0) in vec3 position;
|
|
12
|
+
layout(location = 1) in vec2 texCoord;
|
|
13
|
+
|
|
14
|
+
out vec2 TexCoord;
|
|
15
|
+
|
|
16
|
+
uniform mat4 view_matrix;
|
|
17
|
+
uniform mat4 project_matrix;
|
|
18
|
+
|
|
19
|
+
void main()
|
|
20
|
+
{
|
|
21
|
+
gl_Position = project_matrix * view_matrix * vec4(position, 1.0);
|
|
22
|
+
TexCoord = texCoord;
|
|
23
|
+
}
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
fragment_shader_source = """
|
|
27
|
+
#version 330 core
|
|
28
|
+
in vec2 TexCoord;
|
|
29
|
+
out vec4 color;
|
|
30
|
+
uniform sampler2D ourTexture;
|
|
31
|
+
void main()
|
|
32
|
+
{
|
|
33
|
+
color = texture(ourTexture, TexCoord);
|
|
34
|
+
}
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def set_uniform_mat4(shader, content, name):
|
|
39
|
+
content = content.T
|
|
40
|
+
glUniformMatrix4fv(
|
|
41
|
+
glGetUniformLocation(shader, name),
|
|
42
|
+
1,
|
|
43
|
+
GL_FALSE,
|
|
44
|
+
content.astype(np.float32)
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class GLCameraFrameItem(gl.GLGraphicsItem.GLGraphicsItem):
|
|
49
|
+
def __init__(self, T=np.eye(4), size=1, width=3, path=None):
|
|
50
|
+
gl.GLGraphicsItem.GLGraphicsItem.__init__(self)
|
|
51
|
+
self.size = size
|
|
52
|
+
self.width = width
|
|
53
|
+
self.T = T
|
|
54
|
+
self.path = path
|
|
55
|
+
|
|
56
|
+
def initializeGL(self):
|
|
57
|
+
# Rectangle vertices and texture coordinates
|
|
58
|
+
hsize = self.size / 2
|
|
59
|
+
self.vertices = np.array([
|
|
60
|
+
# positions # texture coords
|
|
61
|
+
[-hsize, -hsize, 0.0, 0.0, 0.0], # bottom-left
|
|
62
|
+
[hsize, -hsize, 0.0, 1.0, 0.0], # bottom-right
|
|
63
|
+
[hsize, hsize, 0.0, 1.0, 1.0], # top-right
|
|
64
|
+
[-hsize, hsize, 0.0, 0.0, 1.0], # top-left
|
|
65
|
+
[0, 0, -hsize * 0.66, 0.0, 0.0], # top-left
|
|
66
|
+
], dtype=np.float32)
|
|
67
|
+
|
|
68
|
+
R = self.T[:3, :3]
|
|
69
|
+
t = self.T[:3, 3]
|
|
70
|
+
self.vertices[:, :3] = (
|
|
71
|
+
R @ self.vertices[:, :3].T + t[:, np.newaxis]).T
|
|
72
|
+
|
|
73
|
+
self.focal_p = np.array([0, 0, hsize * 0.66])
|
|
74
|
+
|
|
75
|
+
indices = np.array([
|
|
76
|
+
0, 1, 2, # first triangle
|
|
77
|
+
2, 3, 0 # second triangle
|
|
78
|
+
], dtype=np.uint32)
|
|
79
|
+
|
|
80
|
+
self.vao = glGenVertexArrays(1)
|
|
81
|
+
vbo = glGenBuffers(1)
|
|
82
|
+
ebo = glGenBuffers(1)
|
|
83
|
+
|
|
84
|
+
glBindVertexArray(self.vao)
|
|
85
|
+
|
|
86
|
+
glBindBuffer(GL_ARRAY_BUFFER, vbo)
|
|
87
|
+
glBufferData(GL_ARRAY_BUFFER, self.vertices.itemsize *
|
|
88
|
+
5 * 4, self.vertices, GL_STATIC_DRAW)
|
|
89
|
+
|
|
90
|
+
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo)
|
|
91
|
+
glBufferData(GL_ELEMENT_ARRAY_BUFFER,
|
|
92
|
+
indices.nbytes, indices, GL_STATIC_DRAW)
|
|
93
|
+
|
|
94
|
+
# Vertex positions
|
|
95
|
+
|
|
96
|
+
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE,
|
|
97
|
+
20, ctypes.c_void_p(0))
|
|
98
|
+
glEnableVertexAttribArray(0)
|
|
99
|
+
# Texture coordinates
|
|
100
|
+
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE,
|
|
101
|
+
20, ctypes.c_void_p(12))
|
|
102
|
+
glEnableVertexAttribArray(1)
|
|
103
|
+
|
|
104
|
+
project_matrix = np.array(self._GLGraphicsItem__view.projectionMatrix().data(),
|
|
105
|
+
np.float32).reshape([4, 4]).T
|
|
106
|
+
# Compile shaders and create shader program
|
|
107
|
+
self.program = shaders.compileProgram(
|
|
108
|
+
shaders.compileShader(vertex_shader_source, GL_VERTEX_SHADER),
|
|
109
|
+
shaders.compileShader(fragment_shader_source, GL_FRAGMENT_SHADER),
|
|
110
|
+
)
|
|
111
|
+
glUseProgram(self.program)
|
|
112
|
+
set_uniform_mat4(self.program, project_matrix, 'project_matrix')
|
|
113
|
+
glUseProgram(0)
|
|
114
|
+
|
|
115
|
+
self.texture = glGenTextures(1)
|
|
116
|
+
glBindTexture(GL_TEXTURE_2D, self.texture)
|
|
117
|
+
|
|
118
|
+
# Load image
|
|
119
|
+
image = Image.open(self.path)
|
|
120
|
+
# image = image.transpose(Image.FLIP_TOP_BOTTOM)
|
|
121
|
+
img_data = image.convert("RGBA").tobytes()
|
|
122
|
+
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image.width,
|
|
123
|
+
image.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, img_data)
|
|
124
|
+
glGenerateMipmap(GL_TEXTURE_2D)
|
|
125
|
+
glBindTexture(GL_TEXTURE_2D, 0)
|
|
126
|
+
glBindVertexArray(0)
|
|
127
|
+
|
|
128
|
+
def setTransform(self, T):
|
|
129
|
+
self.T = T
|
|
130
|
+
|
|
131
|
+
def paint(self):
|
|
132
|
+
self.view_matrix = np.array(
|
|
133
|
+
self._GLGraphicsItem__view.viewMatrix().data(), np.float32).reshape([4, 4]).T
|
|
134
|
+
project_matrix = np.array(self._GLGraphicsItem__view.projectionMatrix(
|
|
135
|
+
).data(), np.float32).reshape([4, 4]).T
|
|
136
|
+
|
|
137
|
+
glEnable(GL_DEPTH_TEST)
|
|
138
|
+
glEnable(GL_BLEND)
|
|
139
|
+
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
|
|
140
|
+
|
|
141
|
+
glUseProgram(self.program)
|
|
142
|
+
set_uniform_mat4(self.program, self.view_matrix, 'view_matrix')
|
|
143
|
+
set_uniform_mat4(self.program, project_matrix, 'project_matrix')
|
|
144
|
+
glBindVertexArray(self.vao)
|
|
145
|
+
glBindTexture(GL_TEXTURE_2D, self.texture)
|
|
146
|
+
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, None)
|
|
147
|
+
glBindTexture(GL_TEXTURE_2D, 0)
|
|
148
|
+
glBindVertexArray(0)
|
|
149
|
+
glUseProgram(0)
|
|
150
|
+
|
|
151
|
+
glLineWidth(self.width)
|
|
152
|
+
glBegin(GL_LINES)
|
|
153
|
+
glColor4f(1, 1, 1, 1) # z is blue
|
|
154
|
+
glVertex3f(*self.vertices[0, :3])
|
|
155
|
+
glVertex3f(*self.vertices[1, :3])
|
|
156
|
+
glVertex3f(*self.vertices[1, :3])
|
|
157
|
+
glVertex3f(*self.vertices[2, :3])
|
|
158
|
+
glVertex3f(*self.vertices[2, :3])
|
|
159
|
+
glVertex3f(*self.vertices[3, :3])
|
|
160
|
+
glVertex3f(*self.vertices[3, :3])
|
|
161
|
+
glVertex3f(*self.vertices[0, :3])
|
|
162
|
+
glVertex3f(*self.vertices[4, :3])
|
|
163
|
+
glVertex3f(*self.vertices[0, :3])
|
|
164
|
+
glVertex3f(*self.vertices[4, :3])
|
|
165
|
+
glVertex3f(*self.vertices[1, :3])
|
|
166
|
+
glVertex3f(*self.vertices[4, :3])
|
|
167
|
+
glVertex3f(*self.vertices[2, :3])
|
|
168
|
+
glVertex3f(*self.vertices[4, :3])
|
|
169
|
+
glVertex3f(*self.vertices[3, :3])
|
|
170
|
+
glEnd()
|
|
171
|
+
|
|
172
|
+
glDisable(GL_DEPTH_TEST)
|
|
173
|
+
glDisable(GL_BLEND)
|
|
@@ -201,17 +201,6 @@ class CloudItem(BaseItem):
|
|
|
201
201
|
self.wait_add_data = data
|
|
202
202
|
self.add_buff_loc = 0
|
|
203
203
|
|
|
204
|
-
while self.add_buff_loc + self.wait_add_data.shape[0] > self.max_cloud_size:
|
|
205
|
-
# Randomly select half of the points
|
|
206
|
-
print(
|
|
207
|
-
"Warning: Buffer size exceeds the maximum cloud size. Randomly selecting half of the points.")
|
|
208
|
-
indices = np.random.choice(
|
|
209
|
-
self.add_buff_loc, self.add_buff_loc // 2, replace=False)
|
|
210
|
-
self.buff[:self.add_buff_loc // 2] = self.buff[indices]
|
|
211
|
-
self.add_buff_loc //= 2
|
|
212
|
-
indices = np.random.choice(
|
|
213
|
-
self.wait_add_data.shape[0], self.wait_add_data.shape[0] // 2, replace=False)
|
|
214
|
-
self.wait_add_data = self.wait_add_data[indices]
|
|
215
204
|
|
|
216
205
|
def update_setting(self):
|
|
217
206
|
if (self.need_update_setting is False):
|
|
@@ -245,6 +234,16 @@ class CloudItem(BaseItem):
|
|
|
245
234
|
new_buff[:self.add_buff_loc] = self.buff[:self.add_buff_loc]
|
|
246
235
|
new_buff[self.add_buff_loc:new_buff_top] = self.wait_add_data
|
|
247
236
|
self.buff = new_buff
|
|
237
|
+
|
|
238
|
+
# if exceed the maximum cloud size, randomly select half of the points
|
|
239
|
+
while self.buff.shape[0] >= self.max_cloud_size:
|
|
240
|
+
print("[Cloud Item] Exceed maximum cloud size, reduce to half")
|
|
241
|
+
new_buff = np.empty((self.buff.shape[0] // 2), self.data_type)
|
|
242
|
+
indices = np.random.choice(new_buff_top, new_buff_top // 2, replace=False)
|
|
243
|
+
new_buff = self.buff[indices]
|
|
244
|
+
new_buff_top = new_buff_top // 2
|
|
245
|
+
self.buff = new_buff
|
|
246
|
+
|
|
248
247
|
glBindBuffer(GL_ARRAY_BUFFER, self.vbo)
|
|
249
248
|
glBufferData(GL_ARRAY_BUFFER, self.buff.nbytes,
|
|
250
249
|
self.buff, GL_DYNAMIC_DRAW)
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import pyqtgraph.opengl as gl
|
|
2
|
+
from OpenGL.GL import *
|
|
3
|
+
import numpy as np
|
|
4
|
+
import threading
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class TrajectoryItem(gl.GLGridItem):
|
|
8
|
+
def __init__(self, width=1, color=(0, 1, 0, 1)):
|
|
9
|
+
super(TrajectoryItem, self).__init__()
|
|
10
|
+
self.width = width
|
|
11
|
+
self.buff = np.empty((0, 3), np.float32)
|
|
12
|
+
self.wait_add_data = None
|
|
13
|
+
self.mutex = threading.Lock()
|
|
14
|
+
self.CAPACITY = 100000
|
|
15
|
+
self.valid_buff_top = 0
|
|
16
|
+
self.color = color
|
|
17
|
+
|
|
18
|
+
def addSetting(self, layout):
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
def setData(self, data, append=True):
|
|
22
|
+
self.mutex.acquire()
|
|
23
|
+
data = data.astype(np.float32).reshape(-1, 3)
|
|
24
|
+
if (append is False):
|
|
25
|
+
self.wait_add_data = data
|
|
26
|
+
self.add_buff_loc = 0
|
|
27
|
+
else:
|
|
28
|
+
if (self.wait_add_data is None):
|
|
29
|
+
self.wait_add_data = data
|
|
30
|
+
else:
|
|
31
|
+
self.wait_add_data = np.concatenate([self.wait_add_data, data])
|
|
32
|
+
self.add_buff_loc = self.valid_buff_top
|
|
33
|
+
self.mutex.release()
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def updateRenderBuffer(self):
|
|
37
|
+
if(self.wait_add_data is None):
|
|
38
|
+
return
|
|
39
|
+
self.mutex.acquire()
|
|
40
|
+
|
|
41
|
+
new_buff_top = self.add_buff_loc + self.wait_add_data.shape[0]
|
|
42
|
+
if new_buff_top > self.buff.shape[0]:
|
|
43
|
+
buff_capacity = self.buff.shape[0]
|
|
44
|
+
while (new_buff_top > buff_capacity):
|
|
45
|
+
buff_capacity += self.CAPACITY
|
|
46
|
+
self.buff = np.empty((buff_capacity, 3), np.float32)
|
|
47
|
+
self.buff[self.add_buff_loc:new_buff_top] = self.wait_add_data
|
|
48
|
+
glBindBuffer(GL_ARRAY_BUFFER, self.vbo)
|
|
49
|
+
glBufferData(GL_ARRAY_BUFFER, self.buff.nbytes, self.buff, GL_DYNAMIC_DRAW)
|
|
50
|
+
glBindBuffer(GL_ARRAY_BUFFER, 0)
|
|
51
|
+
else:
|
|
52
|
+
self.buff[self.add_buff_loc:new_buff_top] = self.wait_add_data
|
|
53
|
+
glBindBuffer(GL_ARRAY_BUFFER, self.vbo)
|
|
54
|
+
glBufferSubData(GL_ARRAY_BUFFER, self.add_buff_loc * 12,
|
|
55
|
+
self.wait_add_data.shape[0] * 12, self.wait_add_data)
|
|
56
|
+
self.valid_buff_top = new_buff_top
|
|
57
|
+
self.wait_add_data = None
|
|
58
|
+
self.mutex.release()
|
|
59
|
+
|
|
60
|
+
def initializeGL(self):
|
|
61
|
+
self.vbo = glGenBuffers(1)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def paint(self):
|
|
65
|
+
self.setupGLState()
|
|
66
|
+
self.updateRenderBuffer()
|
|
67
|
+
glEnable(GL_BLEND)
|
|
68
|
+
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
|
|
69
|
+
glBindBuffer(GL_ARRAY_BUFFER, self.vbo)
|
|
70
|
+
glEnableClientState(GL_VERTEX_ARRAY)
|
|
71
|
+
glVertexPointer(3, GL_FLOAT, 0, None)
|
|
72
|
+
glLineWidth(self.width)
|
|
73
|
+
glColor4f(*self.color) # z is blue
|
|
74
|
+
|
|
75
|
+
glDrawArrays(GL_LINE_STRIP, 0, self.valid_buff_top)
|
|
76
|
+
glDisableClientState(GL_VERTEX_ARRAY)
|
|
77
|
+
|
|
78
|
+
glBindBuffer(GL_ARRAY_BUFFER, 0)
|
|
79
|
+
glUseProgram(0)
|
q3dviewer/gau_io.py
CHANGED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from plyfile import PlyData
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def gsdata_type(sh_dim):
|
|
6
|
+
return [('pw', '<f4', (3,)),
|
|
7
|
+
('rot', '<f4', (4,)),
|
|
8
|
+
('scale', '<f4', (3,)),
|
|
9
|
+
('alpha', '<f4'),
|
|
10
|
+
('sh', '<f4', (sh_dim))]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def matrix_to_quaternion(matrices):
|
|
14
|
+
m00, m01, m02 = matrices[:, 0, 0], matrices[:, 0, 1], matrices[:, 0, 2]
|
|
15
|
+
m10, m11, m12 = matrices[:, 1, 0], matrices[:, 1, 1], matrices[:, 1, 2]
|
|
16
|
+
m20, m21, m22 = matrices[:, 2, 0], matrices[:, 2, 1], matrices[:, 2, 2]
|
|
17
|
+
t = 1 + m00 + m11 + m22
|
|
18
|
+
s = np.ones_like(m00)
|
|
19
|
+
w = np.ones_like(m00)
|
|
20
|
+
x = np.ones_like(m00)
|
|
21
|
+
y = np.ones_like(m00)
|
|
22
|
+
z = np.ones_like(m00)
|
|
23
|
+
|
|
24
|
+
t_positive = t > 0.0000001
|
|
25
|
+
s[t_positive] = 0.5 / np.sqrt(t[t_positive])
|
|
26
|
+
w[t_positive] = 0.25 / s[t_positive]
|
|
27
|
+
x[t_positive] = (m21[t_positive] - m12[t_positive]) * s[t_positive]
|
|
28
|
+
y[t_positive] = (m02[t_positive] - m20[t_positive]) * s[t_positive]
|
|
29
|
+
z[t_positive] = (m10[t_positive] - m01[t_positive]) * s[t_positive]
|
|
30
|
+
|
|
31
|
+
c1 = np.logical_and(m00 > m11, m00 > m22)
|
|
32
|
+
cond1 = np.logical_and(np.logical_not(t_positive), np.logical_and(m00 > m11, m00 > m22))
|
|
33
|
+
|
|
34
|
+
s[cond1] = 2.0 * np.sqrt(1.0 + m00[cond1] - m11[cond1] - m22[cond1])
|
|
35
|
+
w[cond1] = (m21[cond1] - m12[cond1]) / s[cond1]
|
|
36
|
+
x[cond1] = 0.25 * s[cond1]
|
|
37
|
+
y[cond1] = (m01[cond1] + m10[cond1]) / s[cond1]
|
|
38
|
+
z[cond1] = (m02[cond1] + m20[cond1]) / s[cond1]
|
|
39
|
+
|
|
40
|
+
c2 = np.logical_and(np.logical_not(c1), m11 > m22)
|
|
41
|
+
cond2 = np.logical_and(np.logical_not(t_positive), c2)
|
|
42
|
+
s[cond2] = 2.0 * np.sqrt(1.0 + m11[cond2] - m00[cond2] - m22[cond2])
|
|
43
|
+
w[cond2] = (m02[cond2] - m20[cond2]) / s[cond2]
|
|
44
|
+
x[cond2] = (m01[cond2] + m10[cond2]) / s[cond2]
|
|
45
|
+
y[cond2] = 0.25 * s[cond2]
|
|
46
|
+
z[cond2] = (m12[cond2] + m21[cond2]) / s[cond2]
|
|
47
|
+
|
|
48
|
+
c3 = np.logical_and(np.logical_not(c1), np.logical_not(c2))
|
|
49
|
+
cond3 = np.logical_and(np.logical_not(t_positive), c3)
|
|
50
|
+
s[cond3] = 2.0 * np.sqrt(1.0 + m22[cond3] - m00[cond3] - m11[cond3])
|
|
51
|
+
w[cond3] = (m10[cond3] - m01[cond3]) / s[cond3]
|
|
52
|
+
x[cond3] = (m02[cond3] + m20[cond3]) / s[cond3]
|
|
53
|
+
y[cond3] = (m12[cond3] + m21[cond3]) / s[cond3]
|
|
54
|
+
z[cond3] = 0.25 * s[cond3]
|
|
55
|
+
return np.array([w, x, y, z]).T
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def load_ply(path, T=None):
|
|
59
|
+
plydata = PlyData.read(path)
|
|
60
|
+
pws = np.stack((np.asarray(plydata.elements[0]["x"]),
|
|
61
|
+
np.asarray(plydata.elements[0]["y"]),
|
|
62
|
+
np.asarray(plydata.elements[0]["z"])), axis=1)
|
|
63
|
+
|
|
64
|
+
alphas = np.asarray(plydata.elements[0]["opacity"])
|
|
65
|
+
alphas = 1/(1 + np.exp(-alphas))
|
|
66
|
+
|
|
67
|
+
scales = np.stack((np.asarray(plydata.elements[0]["scale_0"]),
|
|
68
|
+
np.asarray(plydata.elements[0]["scale_1"]),
|
|
69
|
+
np.asarray(plydata.elements[0]["scale_2"])), axis=1)
|
|
70
|
+
|
|
71
|
+
rots = np.stack((np.asarray(plydata.elements[0]["rot_0"]),
|
|
72
|
+
np.asarray(plydata.elements[0]["rot_1"]),
|
|
73
|
+
np.asarray(plydata.elements[0]["rot_2"]),
|
|
74
|
+
np.asarray(plydata.elements[0]["rot_3"])), axis=1)
|
|
75
|
+
|
|
76
|
+
rots /= np.linalg.norm(rots, axis=1)[:, np.newaxis]
|
|
77
|
+
|
|
78
|
+
sh_dim = len(plydata.elements[0][0])-14
|
|
79
|
+
shs = np.zeros([pws.shape[0], sh_dim])
|
|
80
|
+
shs[:, 0] = np.asarray(plydata.elements[0]["f_dc_0"])
|
|
81
|
+
shs[:, 1] = np.asarray(plydata.elements[0]["f_dc_1"])
|
|
82
|
+
shs[:, 2] = np.asarray(plydata.elements[0]["f_dc_2"])
|
|
83
|
+
|
|
84
|
+
sh_rest_dim = sh_dim - 3
|
|
85
|
+
for i in range(sh_rest_dim):
|
|
86
|
+
name = "f_rest_%d" % i
|
|
87
|
+
shs[:, 3 + i] = np.asarray(plydata.elements[0][name])
|
|
88
|
+
|
|
89
|
+
shs[:, 3:] = shs[:, 3:].reshape(-1, 3, sh_rest_dim//3).transpose([0, 2, 1]).reshape(-1, sh_rest_dim)
|
|
90
|
+
|
|
91
|
+
pws = pws.astype(np.float32)
|
|
92
|
+
rots = rots.astype(np.float32)
|
|
93
|
+
scales = np.exp(scales)
|
|
94
|
+
scales = scales.astype(np.float32)
|
|
95
|
+
alphas = alphas.astype(np.float32)
|
|
96
|
+
shs = shs.astype(np.float32)
|
|
97
|
+
|
|
98
|
+
dtypes = gsdata_type(sh_dim)
|
|
99
|
+
|
|
100
|
+
gs = np.rec.fromarrays(
|
|
101
|
+
[pws, rots, scales, alphas, shs], dtype=dtypes)
|
|
102
|
+
|
|
103
|
+
return gs
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def rotate_gaussian(T, gs):
|
|
107
|
+
# Transform to world
|
|
108
|
+
pws = (T @ gs['pw'].T).T
|
|
109
|
+
w = gs['rot'][:, 0]
|
|
110
|
+
x = gs['rot'][:, 1]
|
|
111
|
+
y = gs['rot'][:, 2]
|
|
112
|
+
z = gs['rot'][:, 3]
|
|
113
|
+
R = np.array([
|
|
114
|
+
[1.0 - 2*(y**2 + z**2), 2*(x*y - z*w), 2*(x * z + y * w)],
|
|
115
|
+
[2*(x*y + z*w), 1.0 - 2*(x**2 + z**2), 2*(y*z - x*w)],
|
|
116
|
+
[2*(x*z - y*w), 2*(y*z + x*w), 1.0 - 2*(x**2 + y**2)]
|
|
117
|
+
]).transpose(2, 0, 1)
|
|
118
|
+
R_new = T @ R
|
|
119
|
+
rots = matrix_to_quaternion(R_new)
|
|
120
|
+
gs['pw'] = pws
|
|
121
|
+
gs['rot'] = rots
|
|
122
|
+
return gs
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def load_gs(fn):
|
|
126
|
+
if fn.endswith('.ply'):
|
|
127
|
+
return load_ply(fn)
|
|
128
|
+
elif fn.endswith('.npy'):
|
|
129
|
+
return np.load(fn)
|
|
130
|
+
else:
|
|
131
|
+
print("%s is not a supported file." % fn)
|
|
132
|
+
exit(0)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def save_gs(fn, gs):
|
|
136
|
+
np.save(fn, gs)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def get_example_gs():
|
|
140
|
+
gs_data = np.array([[0., 0., 0., # xyz
|
|
141
|
+
1., 0., 0., 0., # rot
|
|
142
|
+
0.05, 0.05, 0.05, # size
|
|
143
|
+
1.,
|
|
144
|
+
1.772484, -1.772484, 1.772484],
|
|
145
|
+
[1., 0., 0.,
|
|
146
|
+
1., 0., 0., 0.,
|
|
147
|
+
0.2, 0.05, 0.05,
|
|
148
|
+
1.,
|
|
149
|
+
1.772484, -1.772484, -1.772484],
|
|
150
|
+
[0., 1., 0.,
|
|
151
|
+
1., 0., 0., 0.,
|
|
152
|
+
0.05, 0.2, 0.05,
|
|
153
|
+
1.,
|
|
154
|
+
-1.772484, 1.772484, -1.772484],
|
|
155
|
+
[0., 0., 1.,
|
|
156
|
+
1., 0., 0., 0.,
|
|
157
|
+
0.05, 0.05, 0.2,
|
|
158
|
+
1.,
|
|
159
|
+
-1.772484, -1.772484, 1.772484]
|
|
160
|
+
], dtype=np.float32)
|
|
161
|
+
dtypes = gsdata_type(3)
|
|
162
|
+
gs = np.frombuffer(gs_data.tobytes(), dtype=dtypes)
|
|
163
|
+
return gs
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
if __name__ == "__main__":
|
|
167
|
+
gs = load_gs("/home/liu/workspace/EasyGaussianSplatting/data/final.npy")
|
|
168
|
+
print(gs.shape)
|
|
@@ -26,6 +26,7 @@ def convert_pointcloud2_msg(msg):
|
|
|
26
26
|
stamp = msg.header.stamp.to_sec()
|
|
27
27
|
return cloud, fields, stamp
|
|
28
28
|
|
|
29
|
+
|
|
29
30
|
def convert_odometry_msg(msg):
|
|
30
31
|
pose = np.array(
|
|
31
32
|
[msg.pose.pose.position.x,
|
|
@@ -40,6 +41,7 @@ def convert_odometry_msg(msg):
|
|
|
40
41
|
stamp = msg.header.stamp.to_sec()
|
|
41
42
|
return transform, stamp
|
|
42
43
|
|
|
44
|
+
|
|
43
45
|
def convert_image_msg(msg):
|
|
44
46
|
image = np.frombuffer(msg.data, dtype=np.uint8).reshape(
|
|
45
47
|
msg.height, msg.width, -1)
|
q3dviewer/utils.py
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import time
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def matrix_to_quaternion(matrix):
|
|
6
|
+
trace = matrix[0, 0] + matrix[1, 1] + matrix[2, 2]
|
|
7
|
+
if trace > 0:
|
|
8
|
+
s = 0.5 / np.sqrt(trace + 1.0)
|
|
9
|
+
w = 0.25 / s
|
|
10
|
+
x = (matrix[2, 1] - matrix[1, 2]) * s
|
|
11
|
+
y = (matrix[0, 2] - matrix[2, 0]) * s
|
|
12
|
+
z = (matrix[1, 0] - matrix[0, 1]) * s
|
|
13
|
+
else:
|
|
14
|
+
if matrix[0, 0] > matrix[1, 1] and matrix[0, 0] > matrix[2, 2]:
|
|
15
|
+
s = 2.0 * np.sqrt(1.0 + matrix[0, 0] - matrix[1, 1] - matrix[2, 2])
|
|
16
|
+
w = (matrix[2, 1] - matrix[1, 2]) / s
|
|
17
|
+
x = 0.25 * s
|
|
18
|
+
y = (matrix[0, 1] + matrix[1, 0]) / s
|
|
19
|
+
z = (matrix[0, 2] + matrix[2, 0]) / s
|
|
20
|
+
elif matrix[1, 1] > matrix[2, 2]:
|
|
21
|
+
s = 2.0 * np.sqrt(1.0 + matrix[1, 1] - matrix[0, 0] - matrix[2, 2])
|
|
22
|
+
w = (matrix[0, 2] - matrix[2, 0]) / s
|
|
23
|
+
x = (matrix[0, 1] + matrix[1, 0]) / s
|
|
24
|
+
y = 0.25 * s
|
|
25
|
+
z = (matrix[1, 2] + matrix[2, 1]) / s
|
|
26
|
+
else:
|
|
27
|
+
s = 2.0 * np.sqrt(1.0 + matrix[2, 2] - matrix[0, 0] - matrix[1, 1])
|
|
28
|
+
w = (matrix[1, 0] - matrix[0, 1]) / s
|
|
29
|
+
x = (matrix[0, 2] + matrix[2, 0]) / s
|
|
30
|
+
y = (matrix[1, 2] + matrix[2, 1]) / s
|
|
31
|
+
z = 0.25 * s
|
|
32
|
+
return np.array([w, x, y, z])
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def quaternion_to_matrix(quaternion):
|
|
36
|
+
_EPS = np.finfo(float).eps * 4.0
|
|
37
|
+
q = np.array(quaternion[:4], dtype=np.float64, copy=True)
|
|
38
|
+
nq = np.dot(q, q)
|
|
39
|
+
if nq < _EPS:
|
|
40
|
+
return np.identity(4)
|
|
41
|
+
q *= np.sqrt(2.0 / nq)
|
|
42
|
+
q = np.outer(q, q)
|
|
43
|
+
return np.array((
|
|
44
|
+
(1.0-q[1, 1]-q[2, 2], q[0, 1]-q[2, 3], q[0, 2]+q[1, 3], 0.0),
|
|
45
|
+
(q[0, 1]+q[2, 3], 1.0-q[0, 0]-q[2, 2], q[1, 2]-q[0, 3], 0.0),
|
|
46
|
+
(q[0, 2]-q[1, 3], q[1, 2]+q[0, 3], 1.0-q[0, 0]-q[1, 1], 0.0),
|
|
47
|
+
(0.0, 0.0, 0.0, 1.0)
|
|
48
|
+
), dtype=np.float64)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def make_transform(pose, rotation):
|
|
52
|
+
transform = np.matrix(np.identity(4, dtype=np.float64))
|
|
53
|
+
transform = quaternion_to_matrix(rotation)
|
|
54
|
+
transform[0:3, 3] = np.transpose(pose)
|
|
55
|
+
return transform
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class FPSMonitor():
|
|
59
|
+
def __init__(self):
|
|
60
|
+
self.stamp_record = []
|
|
61
|
+
|
|
62
|
+
def count(self):
|
|
63
|
+
cur_stamp = time.time()
|
|
64
|
+
self.stamp_record.append(cur_stamp)
|
|
65
|
+
while len(self.stamp_record) > 0:
|
|
66
|
+
if(cur_stamp - self.stamp_record[0] > 1.):
|
|
67
|
+
self.stamp_record.pop(0)
|
|
68
|
+
else:
|
|
69
|
+
break
|
|
70
|
+
return len(self.stamp_record)
|
|
71
|
+
|
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: q3dviewer
|
|
3
|
-
Version: 1.0.
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
Version: 1.0.7
|
|
4
|
+
Summary: UNKNOWN
|
|
5
|
+
Home-page: UNKNOWN
|
|
6
|
+
Author: UNKNOWN
|
|
7
|
+
Author-email: UNKNOWN
|
|
8
|
+
License: UNKNOWN
|
|
9
|
+
Platform: UNKNOWN
|
|
7
10
|
Requires-Dist: PyOpenGL
|
|
8
|
-
Requires-Dist:
|
|
11
|
+
Requires-Dist: laspy
|
|
9
12
|
Requires-Dist: meshio
|
|
10
|
-
Requires-Dist:
|
|
13
|
+
Requires-Dist: numpy
|
|
14
|
+
Requires-Dist: pillow
|
|
11
15
|
Requires-Dist: pye57
|
|
12
|
-
Requires-Dist:
|
|
16
|
+
Requires-Dist: pypcd4
|
|
17
|
+
Requires-Dist: pyside6
|
|
18
|
+
|
|
19
|
+
UNKNOWN
|
|
20
|
+
|
|
13
21
|
|
|
@@ -1,19 +1,24 @@
|
|
|
1
1
|
q3dviewer/__init__.py,sha256=rP5XX_x8g7hxIMqNHlU89BN4dt5MSvoYYwip68fCmhc,173
|
|
2
|
-
q3dviewer/base_glwidget.py,sha256=
|
|
2
|
+
q3dviewer/base_glwidget.py,sha256=i1GOuPJMd_HeOu6-Y0mMe73tM4U0z1gdM-XOJszXGnM,8916
|
|
3
3
|
q3dviewer/base_item.py,sha256=-6GogOKrjVRSTszio9ELPDhehBaUlTyNaag2bUl81l8,1337
|
|
4
|
-
q3dviewer/
|
|
4
|
+
q3dviewer/basic_window.py,sha256=CFErOPRMysFcCqq3vhDsQ-xZzLArO3m1yABCTIhq5do,7946
|
|
5
|
+
q3dviewer/cloud_viewer.py,sha256=IxxrB6Sl6aPCr9P9QzKHGkMcDP_DjsBWbkmIbsAoIM4,2358
|
|
6
|
+
q3dviewer/gau_io.py,sha256=S6NmqL5vSMgHVxKR-eu4CWCqgZeWBJKYRoOMAwr8Xbo,5890
|
|
5
7
|
q3dviewer/glwidget.py,sha256=ppkAJUCCJ4pnLX3j6jCFmJw8O-kCr7UphsEdkGSakpQ,4832
|
|
8
|
+
q3dviewer/utils.py,sha256=evF0d-v17hbTmquC24fmMIp9CsXpUnSQZr4MVy2sfao,2426
|
|
6
9
|
q3dviewer/viewer.py,sha256=Rv_YMo-N2kibh4Bnaz0g32hEtNyn8WXRmoh7GTSPcJg,1708
|
|
7
10
|
q3dviewer/custom_items/__init__.py,sha256=gOiAxdjDaAnFL8YbqSEWWWOwUrJfvzP9JLR34sCB9-4,434
|
|
8
11
|
q3dviewer/custom_items/axis_item.py,sha256=6dWGqBeA3DL-9xmsxdmTSIGLs6BcRbiysBKkkXVb5oI,4456
|
|
12
|
+
q3dviewer/custom_items/camera_frame_item.py,sha256=VBsr3Avly_YWXViIh4DJkGc_HJt227GeOYLpGtbYTOw,5605
|
|
9
13
|
q3dviewer/custom_items/cloud_io_item.py,sha256=gjK3n9WKB7JwxC93ijkweEHA5EezpgNJ8KO-PBaDKCs,2835
|
|
10
|
-
q3dviewer/custom_items/cloud_item.py,sha256=
|
|
14
|
+
q3dviewer/custom_items/cloud_item.py,sha256=u9hHHFjK4Ls6ssFVbBFqdR2xg0QNmgsKOz9jGHSqrt8,12444
|
|
11
15
|
q3dviewer/custom_items/frame_item.py,sha256=YTBOEeRWh95trhAlKoZ9LZ4I2x-A5JjOhNPp4OvD5E0,6983
|
|
12
16
|
q3dviewer/custom_items/gaussian_item.py,sha256=B0mYjlW83Kr6FUtZ6P_f40pjjiASPB8g3CSjMTnjsvY,9857
|
|
13
17
|
q3dviewer/custom_items/grid_item.py,sha256=U4nk16d5Dkbt6U9s23c9PIlG-wPwu15Ho0HsamDuyqM,3436
|
|
14
18
|
q3dviewer/custom_items/image_item.py,sha256=tJryUj5qxaGx1j7W0lbzx-JJmehMPSEqJXPnSkG4FOI,5529
|
|
15
19
|
q3dviewer/custom_items/line_item.py,sha256=ujl78wBlvcotghauAy7ILTa22P_fdrFzrH6RD4TvY3M,4401
|
|
16
20
|
q3dviewer/custom_items/text_item.py,sha256=nuHMVMQrwy50lNk9hxB94criFxbJJK-SYiK2fSXWUMQ,2158
|
|
21
|
+
q3dviewer/custom_items/trajectory_item.py,sha256=uoKQSrTs_m_m1M8iNAm3peiXnZ9uVPsYQLYas3Gksjg,2754
|
|
17
22
|
q3dviewer/shaders/cloud_frag.glsl,sha256=tbCsDUp9YlPe0hRWlFS724SH6TtMeLO-GVYROzEElZg,609
|
|
18
23
|
q3dviewer/shaders/cloud_vert.glsl,sha256=Vxgw-Zrr0knAK0z4qMXKML6IC4EbffKMwYN2TMXROoI,2117
|
|
19
24
|
q3dviewer/shaders/gau_frag.glsl,sha256=5_UY84tWDts59bxP8x4I-wgnzY8aGeGuo28wX--LW7E,975
|
|
@@ -29,13 +34,13 @@ q3dviewer/tools/lidar_cam_calib.py,sha256=kYR41Eq3o7frHLIWqysHUvVbB29GC0sDLzUigw
|
|
|
29
34
|
q3dviewer/tools/ros_viewer.py,sha256=Arpy5Y89h_1aSb8aFbeHJEYFFUOxCNm4QWLh5tKcPQI,2504
|
|
30
35
|
q3dviewer/utils/__init__.py,sha256=irm8Z_bT8l9kzhoMlds2Dal8g4iw4vjmqNPZSs4W6e0,157
|
|
31
36
|
q3dviewer/utils/cloud_io.py,sha256=ttD8FJExdDhXB1Z0Ej9S939i8gcq4JfyqwLXVh8CEFw,11094
|
|
32
|
-
q3dviewer/utils/convert_ros_msg.py,sha256=
|
|
37
|
+
q3dviewer/utils/convert_ros_msg.py,sha256=sAoQfy3qLQKsIArBAVm8H--wlQXOcmkKK3-Ox9UCcrc,1686
|
|
33
38
|
q3dviewer/utils/gl_helper.py,sha256=dRY_kUqyPMr7NTcupUr6_VTvgnj53iE2C0Lk0-oFYsI,1435
|
|
34
39
|
q3dviewer/utils/maths.py,sha256=ghnZQYxQtAQonTqQOmfHAMdSluk0sO1Ps_9bL2vMeb8,5397
|
|
35
40
|
q3dviewer/utils/range_slider.py,sha256=jZJQL-uQgnpgLvtYSWpKTrJlLkt3aqNpaRQAePEpNd0,3174
|
|
36
|
-
q3dviewer-1.0.
|
|
37
|
-
q3dviewer-1.0.
|
|
38
|
-
q3dviewer-1.0.
|
|
39
|
-
q3dviewer-1.0.
|
|
40
|
-
q3dviewer-1.0.
|
|
41
|
-
q3dviewer-1.0.
|
|
41
|
+
q3dviewer-1.0.7.dist-info/LICENSE,sha256=81cMOyNfw8KLb1JnPYngGHJ5W83gSbZEBU9MEP3tl-E,1124
|
|
42
|
+
q3dviewer-1.0.7.dist-info/METADATA,sha256=IP5D90vA9PSGbJAuw2N8a0mceD8YOzx7rIze_lqZGXQ,349
|
|
43
|
+
q3dviewer-1.0.7.dist-info/WHEEL,sha256=g4nMs7d-Xl9-xC9XovUrsDHGXt-FT0E17Yqo92DEfvY,92
|
|
44
|
+
q3dviewer-1.0.7.dist-info/entry_points.txt,sha256=2vsxH5_1WhM-DnDgY0TDbiAGFPFMfI0Wmfs_6iXEf4Y,317
|
|
45
|
+
q3dviewer-1.0.7.dist-info/top_level.txt,sha256=HFFDCbGu28txcGe2HPc46A7EPaguBa_b5oH7bufmxHM,10
|
|
46
|
+
q3dviewer-1.0.7.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|