q3dviewer 1.0.9__py3-none-any.whl → 1.1.1__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 +20 -4
- 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 +1 -0
- q3dviewer/custom_items/trajectory_item.py +79 -0
- q3dviewer/gau_io.py +168 -0
- q3dviewer/tools/cloud_viewer.py +2 -2
- q3dviewer/tools/film_maker.py +47 -21
- q3dviewer/utils.py +71 -0
- q3dviewer-1.1.1.dist-info/METADATA +217 -0
- {q3dviewer-1.0.9.dist-info → q3dviewer-1.1.1.dist-info}/RECORD +16 -14
- {q3dviewer-1.0.9.dist-info → q3dviewer-1.1.1.dist-info}/entry_points.txt +1 -0
- q3dviewer/test/test_interpolation.py +0 -58
- q3dviewer/test/test_rendering.py +0 -72
- q3dviewer/tools/cinematographer.py +0 -367
- q3dviewer-1.0.9.dist-info/METADATA +0 -14
- {q3dviewer-1.0.9.dist-info → q3dviewer-1.1.1.dist-info}/LICENSE +0 -0
- {q3dviewer-1.0.9.dist-info → q3dviewer-1.1.1.dist-info}/WHEEL +0 -0
- {q3dviewer-1.0.9.dist-info → q3dviewer-1.1.1.dist-info}/top_level.txt +0 -0
q3dviewer/base_glwidget.py
CHANGED
|
@@ -195,33 +195,42 @@ class BaseGLWidget(QtOpenGLWidgets.QOpenGLWidget):
|
|
|
195
195
|
# Handle rotation keys
|
|
196
196
|
if QtCore.Qt.Key_Up in self.active_keys:
|
|
197
197
|
self.rotate(radians(rot_speed), 0, 0)
|
|
198
|
+
self.view_need_update = True
|
|
198
199
|
if QtCore.Qt.Key_Down in self.active_keys:
|
|
199
200
|
self.rotate(radians(-rot_speed), 0, 0)
|
|
201
|
+
self.view_need_update = True
|
|
200
202
|
if QtCore.Qt.Key_Left in self.active_keys:
|
|
201
203
|
self.rotate(0, 0, radians(rot_speed))
|
|
204
|
+
self.view_need_update = True
|
|
202
205
|
if QtCore.Qt.Key_Right in self.active_keys:
|
|
203
206
|
self.rotate(0, 0, radians(-rot_speed))
|
|
207
|
+
self.view_need_update = True
|
|
204
208
|
# Handle zoom keys
|
|
205
209
|
xz_keys = {QtCore.Qt.Key_Z, QtCore.Qt.Key_X}
|
|
206
210
|
if self.active_keys & xz_keys:
|
|
207
211
|
Rwc = euler_to_matrix(self.euler)
|
|
208
212
|
if QtCore.Qt.Key_Z in self.active_keys:
|
|
209
213
|
self.center += Rwc @ np.array([0, 0, -trans_speed])
|
|
214
|
+
self.view_need_update = True
|
|
210
215
|
if QtCore.Qt.Key_X in self.active_keys:
|
|
211
216
|
self.center += Rwc @ np.array([0, 0, trans_speed])
|
|
217
|
+
self.view_need_update = True
|
|
212
218
|
# Handle translation keys on the z plane
|
|
213
219
|
dir_keys = {QtCore.Qt.Key_W, QtCore.Qt.Key_S, QtCore.Qt.Key_A, QtCore.Qt.Key_D}
|
|
214
220
|
if self.active_keys & dir_keys:
|
|
215
221
|
Rz = euler_to_matrix([0, 0, self.euler[2]])
|
|
216
222
|
if QtCore.Qt.Key_W in self.active_keys:
|
|
217
223
|
self.center += Rz @ np.array([0, trans_speed, 0])
|
|
224
|
+
self.view_need_update = True
|
|
218
225
|
if QtCore.Qt.Key_S in self.active_keys:
|
|
219
226
|
self.center += Rz @ np.array([0, -trans_speed, 0])
|
|
227
|
+
self.view_need_update = True
|
|
220
228
|
if QtCore.Qt.Key_A in self.active_keys:
|
|
221
229
|
self.center += Rz @ np.array([-trans_speed, 0, 0])
|
|
230
|
+
self.view_need_update = True
|
|
222
231
|
if QtCore.Qt.Key_D in self.active_keys:
|
|
223
232
|
self.center += Rz @ np.array([trans_speed, 0, 0])
|
|
224
|
-
|
|
233
|
+
self.view_need_update = True
|
|
225
234
|
|
|
226
235
|
def update_model_view(self):
|
|
227
236
|
glMatrixMode(GL_MODELVIEW)
|
|
@@ -238,12 +247,19 @@ class BaseGLWidget(QtOpenGLWidgets.QOpenGLWidget):
|
|
|
238
247
|
return Tcw
|
|
239
248
|
|
|
240
249
|
def set_cam_position(self, **kwargs):
|
|
241
|
-
|
|
250
|
+
center = kwargs.get('center', None)
|
|
242
251
|
distance = kwargs.get('distance', None)
|
|
243
|
-
|
|
244
|
-
|
|
252
|
+
euler = kwargs.get('euler', None)
|
|
253
|
+
if center is not None:
|
|
254
|
+
self.set_center(center)
|
|
245
255
|
if distance is not None:
|
|
246
256
|
self.set_dist(distance)
|
|
257
|
+
if euler is not None:
|
|
258
|
+
self.set_euler(euler)
|
|
259
|
+
|
|
260
|
+
def set_euler(self, euler):
|
|
261
|
+
self.euler = euler
|
|
262
|
+
self.view_need_update = True
|
|
247
263
|
|
|
248
264
|
def set_color(self, color):
|
|
249
265
|
self.color = color
|
|
@@ -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)
|
|
@@ -67,6 +67,7 @@ class CloudItem(BaseItem):
|
|
|
67
67
|
self.box_size.setValue(self.size)
|
|
68
68
|
self.box_size.setRange(0, 100)
|
|
69
69
|
self.box_size.valueChanged.connect(self.set_size)
|
|
70
|
+
self._on_point_type_selection(self.point_type_table[self.point_type])
|
|
70
71
|
layout.addWidget(self.box_size)
|
|
71
72
|
|
|
72
73
|
box_alpha = QDoubleSpinBox()
|
|
@@ -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)
|