q3dviewer 1.0.3__py3-none-any.whl → 1.0.5__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/__init__.py +5 -0
- q3dviewer/base_glwidget.py +256 -0
- q3dviewer/base_item.py +57 -0
- q3dviewer/custom_items/__init__.py +9 -0
- q3dviewer/custom_items/axis_item.py +148 -0
- q3dviewer/custom_items/cloud_io_item.py +79 -0
- q3dviewer/custom_items/cloud_item.py +314 -0
- q3dviewer/custom_items/frame_item.py +194 -0
- q3dviewer/custom_items/gaussian_item.py +254 -0
- q3dviewer/custom_items/grid_item.py +88 -0
- q3dviewer/custom_items/image_item.py +172 -0
- q3dviewer/custom_items/line_item.py +120 -0
- q3dviewer/custom_items/text_item.py +63 -0
- q3dviewer/gau_io.py +0 -0
- q3dviewer/glwidget.py +131 -0
- q3dviewer/shaders/cloud_frag.glsl +28 -0
- q3dviewer/shaders/cloud_vert.glsl +72 -0
- q3dviewer/shaders/gau_frag.glsl +42 -0
- q3dviewer/shaders/gau_prep.glsl +249 -0
- q3dviewer/shaders/gau_vert.glsl +77 -0
- q3dviewer/shaders/sort_by_key.glsl +56 -0
- q3dviewer/tools/__init__.py +1 -0
- q3dviewer/tools/cloud_viewer.py +123 -0
- q3dviewer/tools/example_viewer.py +47 -0
- q3dviewer/tools/gaussian_viewer.py +60 -0
- q3dviewer/tools/lidar_calib.py +294 -0
- q3dviewer/tools/lidar_cam_calib.py +314 -0
- q3dviewer/tools/ros_viewer.py +85 -0
- q3dviewer/utils/__init__.py +4 -0
- q3dviewer/utils/cloud_io.py +323 -0
- q3dviewer/utils/convert_ros_msg.py +49 -0
- q3dviewer/utils/gl_helper.py +40 -0
- q3dviewer/utils/maths.py +168 -0
- q3dviewer/utils/range_slider.py +86 -0
- q3dviewer/viewer.py +58 -0
- q3dviewer-1.0.5.dist-info/LICENSE +21 -0
- {q3dviewer-1.0.3.dist-info → q3dviewer-1.0.5.dist-info}/METADATA +7 -4
- q3dviewer-1.0.5.dist-info/RECORD +41 -0
- q3dviewer-1.0.5.dist-info/top_level.txt +1 -0
- q3dviewer-1.0.3.dist-info/RECORD +0 -5
- q3dviewer-1.0.3.dist-info/top_level.txt +0 -1
- {q3dviewer-1.0.3.dist-info → q3dviewer-1.0.5.dist-info}/WHEEL +0 -0
- {q3dviewer-1.0.3.dist-info → q3dviewer-1.0.5.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Copyright 2024 Panasonic Advanced Technology Development Co.,Ltd. (Liu Yang)
|
|
3
|
+
Distributed under MIT license. See LICENSE for more information.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
from q3dviewer.base_item import BaseItem
|
|
9
|
+
from OpenGL.GL import *
|
|
10
|
+
import threading
|
|
11
|
+
import os
|
|
12
|
+
from PySide6.QtWidgets import QLabel, QLineEdit, QDoubleSpinBox, \
|
|
13
|
+
QComboBox, QCheckBox
|
|
14
|
+
from OpenGL.GL import shaders
|
|
15
|
+
from q3dviewer.utils import *
|
|
16
|
+
from q3dviewer.utils.range_slider import RangeSlider
|
|
17
|
+
from PySide6.QtCore import QRegularExpression
|
|
18
|
+
from PySide6.QtGui import QRegularExpressionValidator
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# draw points with color (x, y, z, color)
|
|
22
|
+
class CloudItem(BaseItem):
|
|
23
|
+
def __init__(self, size, alpha, color_mode='I', color='#ffffff', point_type='PIXEL'):
|
|
24
|
+
super().__init__()
|
|
25
|
+
self.STRIDE = 16 # stride of cloud array
|
|
26
|
+
self.valid_buff_top = 0
|
|
27
|
+
self.add_buff_loc = 0
|
|
28
|
+
self.alpha = alpha
|
|
29
|
+
self.size = size
|
|
30
|
+
self.point_type = point_type
|
|
31
|
+
self.mutex = threading.Lock()
|
|
32
|
+
self.data_type = [('xyz', '<f4', (3,)), ('irgb', '<u4')]
|
|
33
|
+
self.flat_rgb = int(color[1:], 16)
|
|
34
|
+
self.mode_table = {'FLAT': 0, 'I': 1, 'RGB': 2}
|
|
35
|
+
self.point_type_table = {'PIXEL': 0, 'SQUARE': 1, 'SPHERE': 2}
|
|
36
|
+
self.color_mode = self.mode_table[color_mode]
|
|
37
|
+
self.CAPACITY = 10000000 # 10MB * 3 (x,y,z, color) * 4
|
|
38
|
+
self.vmin = 0
|
|
39
|
+
self.vmax = 255
|
|
40
|
+
self.buff = np.empty((0), self.data_type)
|
|
41
|
+
self.wait_add_data = None
|
|
42
|
+
self.need_update_setting = True
|
|
43
|
+
self.max_cloud_size = 300000000
|
|
44
|
+
# Enable depth test when full opaque
|
|
45
|
+
self.depth_test_enabled = (alpha == 1)
|
|
46
|
+
self.path = os.path.dirname(__file__)
|
|
47
|
+
|
|
48
|
+
def add_setting(self, layout):
|
|
49
|
+
label_ptype = QLabel("Set point display type:")
|
|
50
|
+
layout.addWidget(label_ptype)
|
|
51
|
+
combo_ptype = QComboBox()
|
|
52
|
+
combo_ptype.addItem("pixels")
|
|
53
|
+
combo_ptype.addItem("flat squares")
|
|
54
|
+
combo_ptype.addItem("spheres")
|
|
55
|
+
combo_ptype.setCurrentIndex(self.point_type_table[self.point_type])
|
|
56
|
+
combo_ptype.currentIndexChanged.connect(self._on_point_type_selection)
|
|
57
|
+
layout.addWidget(combo_ptype)
|
|
58
|
+
self.label_size = QLabel("Set size: (pixel)")
|
|
59
|
+
layout.addWidget(self.label_size)
|
|
60
|
+
self.box_size = QDoubleSpinBox()
|
|
61
|
+
self.box_size.setSingleStep(1)
|
|
62
|
+
self.box_size.setDecimals(0)
|
|
63
|
+
layout.addWidget(self.box_size)
|
|
64
|
+
self.box_size.setValue(self.size)
|
|
65
|
+
self.box_size.valueChanged.connect(self.set_size)
|
|
66
|
+
self.box_size.setRange(0, 100)
|
|
67
|
+
|
|
68
|
+
label_alpha = QLabel("Set Alpha:")
|
|
69
|
+
layout.addWidget(label_alpha)
|
|
70
|
+
box_alpha = QDoubleSpinBox()
|
|
71
|
+
layout.addWidget(box_alpha)
|
|
72
|
+
box_alpha.setSingleStep(0.01)
|
|
73
|
+
box_alpha.setValue(self.alpha)
|
|
74
|
+
box_alpha.valueChanged.connect(self.set_alpha)
|
|
75
|
+
box_alpha.setRange(0, 1)
|
|
76
|
+
|
|
77
|
+
label_color = QLabel("Set ColorMode:")
|
|
78
|
+
layout.addWidget(label_color)
|
|
79
|
+
self.combo_color = QComboBox()
|
|
80
|
+
self.combo_color.addItem("flat color")
|
|
81
|
+
self.combo_color.addItem("intensity")
|
|
82
|
+
self.combo_color.addItem("RGB")
|
|
83
|
+
self.combo_color.setCurrentIndex(self.color_mode)
|
|
84
|
+
self.combo_color.currentIndexChanged.connect(self._on_color_mode)
|
|
85
|
+
layout.addWidget(self.combo_color)
|
|
86
|
+
|
|
87
|
+
self.edit_rgb = QLineEdit()
|
|
88
|
+
self.edit_rgb.setToolTip("Hex number, i.e. #FF4500")
|
|
89
|
+
self.edit_rgb.setText(f"#{self.flat_rgb:06x}")
|
|
90
|
+
self.edit_rgb.textChanged.connect(self._on_color)
|
|
91
|
+
regex = QRegularExpression(r"^#[0-9A-Fa-f]{6}$")
|
|
92
|
+
validator = QRegularExpressionValidator(regex)
|
|
93
|
+
self.edit_rgb.setValidator(validator)
|
|
94
|
+
layout.addWidget(self.edit_rgb)
|
|
95
|
+
|
|
96
|
+
self.slider_v = RangeSlider()
|
|
97
|
+
self.slider_v.setRange(0, 255)
|
|
98
|
+
self.slider_v.rangeChanged.connect(self._on_range)
|
|
99
|
+
|
|
100
|
+
layout.addWidget(self.slider_v)
|
|
101
|
+
self.combo_color.setCurrentIndex(self.color_mode)
|
|
102
|
+
|
|
103
|
+
# Add a checkbox for enabling/disabling depth test
|
|
104
|
+
self.checkbox_depth_test = QCheckBox(
|
|
105
|
+
"Show front points first (Depth Test)")
|
|
106
|
+
self.checkbox_depth_test.setChecked(self.depth_test_enabled)
|
|
107
|
+
self.checkbox_depth_test.stateChanged.connect(self.set_depthtest)
|
|
108
|
+
layout.addWidget(self.checkbox_depth_test)
|
|
109
|
+
|
|
110
|
+
def _on_range(self, lower, upper):
|
|
111
|
+
self.vmin = lower
|
|
112
|
+
self.vmax = upper
|
|
113
|
+
self.need_update_setting = True
|
|
114
|
+
|
|
115
|
+
def _on_color_mode(self, index):
|
|
116
|
+
self.color_mode = index
|
|
117
|
+
self.edit_rgb.hide()
|
|
118
|
+
self.slider_v.hide()
|
|
119
|
+
if (index == self.mode_table['FLAT']): # flat color
|
|
120
|
+
self.edit_rgb.show()
|
|
121
|
+
elif (index == self.mode_table['I']): # flat color
|
|
122
|
+
self.slider_v.show()
|
|
123
|
+
self.need_update_setting = True
|
|
124
|
+
|
|
125
|
+
def set_color_mode(self, color_mode):
|
|
126
|
+
if color_mode in {'FLAT', 'RGB', 'I'}:
|
|
127
|
+
self.combo_color.setCurrentIndex(self.mode_table[color_mode])
|
|
128
|
+
else:
|
|
129
|
+
print(f"Invalid color mode: {color_mode}")
|
|
130
|
+
|
|
131
|
+
def _on_point_type_selection(self, index):
|
|
132
|
+
self.point_type = list(self.point_type_table.keys())[index]
|
|
133
|
+
if self.point_type == 'PIXEL':
|
|
134
|
+
self.label_size.setText("Set size: (pixel)")
|
|
135
|
+
self.box_size.setDecimals(0)
|
|
136
|
+
self.box_size.setSingleStep(1)
|
|
137
|
+
self.box_size.setValue(1)
|
|
138
|
+
self.size = 1
|
|
139
|
+
else:
|
|
140
|
+
self.label_size.setText("Set size: (meter)")
|
|
141
|
+
self.box_size.setDecimals(2)
|
|
142
|
+
self.box_size.setSingleStep(0.01)
|
|
143
|
+
self.box_size.setValue(0.01)
|
|
144
|
+
self.size = 0.01
|
|
145
|
+
self.need_update_setting = True
|
|
146
|
+
|
|
147
|
+
def set_alpha(self, alpha):
|
|
148
|
+
self.alpha = alpha
|
|
149
|
+
self.need_update_setting = True
|
|
150
|
+
|
|
151
|
+
def set_flat_rgb(self, color):
|
|
152
|
+
try:
|
|
153
|
+
self.edit_rgb.setText(color)
|
|
154
|
+
except ValueError:
|
|
155
|
+
pass
|
|
156
|
+
|
|
157
|
+
def _on_color(self, color):
|
|
158
|
+
try:
|
|
159
|
+
flat_rgb = int(color[1:], 16)
|
|
160
|
+
self.flat_rgb = flat_rgb
|
|
161
|
+
self.need_update_setting = True
|
|
162
|
+
except ValueError:
|
|
163
|
+
pass
|
|
164
|
+
|
|
165
|
+
def set_size(self, size):
|
|
166
|
+
self.size = size
|
|
167
|
+
self.need_update_setting = True
|
|
168
|
+
|
|
169
|
+
def set_depthtest(self, state):
|
|
170
|
+
self.depth_test_enabled = state
|
|
171
|
+
|
|
172
|
+
def clear(self):
|
|
173
|
+
data = np.empty((0), self.data_type)
|
|
174
|
+
self.set_data(data)
|
|
175
|
+
|
|
176
|
+
def set_data(self, data, append=False):
|
|
177
|
+
if not isinstance(data, np.ndarray):
|
|
178
|
+
raise ValueError("Input data must be a numpy array.")
|
|
179
|
+
|
|
180
|
+
if data.dtype in {np.dtype('float32'), np.dtype('float64')}:
|
|
181
|
+
if data.size == 0:
|
|
182
|
+
data = np.empty((0), self.data_type)
|
|
183
|
+
elif data.ndim == 2 and data.shape[1] >= 3:
|
|
184
|
+
xyz = data[:, :3]
|
|
185
|
+
if data.shape[1] >= 4:
|
|
186
|
+
color = data[:, 3].view(np.uint32)
|
|
187
|
+
else:
|
|
188
|
+
color = np.zeros(data.shape[0], dtype=np.uint32)
|
|
189
|
+
data = np.rec.fromarrays(
|
|
190
|
+
[xyz, color[:data.shape[0]]], dtype=self.data_type)
|
|
191
|
+
|
|
192
|
+
with self.mutex:
|
|
193
|
+
if append:
|
|
194
|
+
if self.wait_add_data is None:
|
|
195
|
+
self.wait_add_data = data
|
|
196
|
+
else:
|
|
197
|
+
self.wait_add_data = np.concatenate(
|
|
198
|
+
[self.wait_add_data, data])
|
|
199
|
+
self.add_buff_loc = self.valid_buff_top
|
|
200
|
+
else:
|
|
201
|
+
self.wait_add_data = data
|
|
202
|
+
self.add_buff_loc = 0
|
|
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
|
+
|
|
216
|
+
def update_setting(self):
|
|
217
|
+
if (self.need_update_setting is False):
|
|
218
|
+
return
|
|
219
|
+
glUseProgram(self.program)
|
|
220
|
+
set_uniform(self.program, int(self.flat_rgb), 'flat_rgb')
|
|
221
|
+
set_uniform(self.program, int(self.color_mode), 'color_mode')
|
|
222
|
+
set_uniform(self.program, float(self.vmax), 'vmax')
|
|
223
|
+
set_uniform(self.program, float(self.vmin), 'vmin')
|
|
224
|
+
set_uniform(self.program, float(self.alpha), 'alpha')
|
|
225
|
+
set_uniform(self.program, float(self.size), 'point_size')
|
|
226
|
+
set_uniform(self.program, int(self.point_type_table[self.point_type]), 'point_type')
|
|
227
|
+
glUseProgram(0)
|
|
228
|
+
self.need_update_setting = False
|
|
229
|
+
|
|
230
|
+
def update_render_buffer(self):
|
|
231
|
+
# Ensure there is data waiting to be added to the buffer
|
|
232
|
+
if (self.wait_add_data is None):
|
|
233
|
+
return
|
|
234
|
+
# Acquire lock to update the buffer safely
|
|
235
|
+
self.mutex.acquire()
|
|
236
|
+
|
|
237
|
+
new_buff_top = self.add_buff_loc + self.wait_add_data.shape[0]
|
|
238
|
+
if new_buff_top > self.buff.shape[0]:
|
|
239
|
+
# if need to update buff capacity, create new cpu buff and new vbo
|
|
240
|
+
buff_capacity = self.buff.shape[0]
|
|
241
|
+
while (new_buff_top > buff_capacity):
|
|
242
|
+
buff_capacity += self.CAPACITY
|
|
243
|
+
print("[Cloud Item] Update capacity to %d" % buff_capacity)
|
|
244
|
+
new_buff = np.empty((buff_capacity), self.data_type)
|
|
245
|
+
new_buff[:self.add_buff_loc] = self.buff[:self.add_buff_loc]
|
|
246
|
+
new_buff[self.add_buff_loc:new_buff_top] = self.wait_add_data
|
|
247
|
+
self.buff = new_buff
|
|
248
|
+
glBindBuffer(GL_ARRAY_BUFFER, self.vbo)
|
|
249
|
+
glBufferData(GL_ARRAY_BUFFER, self.buff.nbytes,
|
|
250
|
+
self.buff, GL_DYNAMIC_DRAW)
|
|
251
|
+
glBindBuffer(GL_ARRAY_BUFFER, 0)
|
|
252
|
+
else:
|
|
253
|
+
self.buff[self.add_buff_loc:new_buff_top] = self.wait_add_data
|
|
254
|
+
glBindBuffer(GL_ARRAY_BUFFER, self.vbo)
|
|
255
|
+
glBufferSubData(GL_ARRAY_BUFFER, self.add_buff_loc * self.STRIDE,
|
|
256
|
+
self.wait_add_data.shape[0] * self.STRIDE,
|
|
257
|
+
self.wait_add_data)
|
|
258
|
+
self.valid_buff_top = new_buff_top
|
|
259
|
+
self.wait_add_data = None
|
|
260
|
+
self.mutex.release()
|
|
261
|
+
|
|
262
|
+
def initialize_gl(self):
|
|
263
|
+
vertex_shader = open(self.path + '/../shaders/cloud_vert.glsl', 'r').read()
|
|
264
|
+
fragment_shader = open(self.path + '/../shaders/cloud_frag.glsl', 'r').read()
|
|
265
|
+
self.program = shaders.compileProgram(
|
|
266
|
+
shaders.compileShader(vertex_shader, GL_VERTEX_SHADER),
|
|
267
|
+
shaders.compileShader(fragment_shader, GL_FRAGMENT_SHADER),
|
|
268
|
+
)
|
|
269
|
+
self.max_cloud_size = glGetIntegerv(
|
|
270
|
+
GL_MAX_SHADER_STORAGE_BLOCK_SIZE) // self.STRIDE
|
|
271
|
+
# Bind attribute locations
|
|
272
|
+
self.vbo = glGenBuffers(1)
|
|
273
|
+
|
|
274
|
+
def paint(self):
|
|
275
|
+
self.update_render_buffer()
|
|
276
|
+
self.update_setting()
|
|
277
|
+
glEnable(GL_BLEND)
|
|
278
|
+
glEnable(GL_PROGRAM_POINT_SIZE)
|
|
279
|
+
glEnable(GL_POINT_SPRITE)
|
|
280
|
+
if self.depth_test_enabled:
|
|
281
|
+
glEnable(GL_DEPTH_TEST)
|
|
282
|
+
else:
|
|
283
|
+
glDisable(GL_DEPTH_TEST)
|
|
284
|
+
|
|
285
|
+
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
|
|
286
|
+
glUseProgram(self.program)
|
|
287
|
+
glBindBuffer(GL_ARRAY_BUFFER, self.vbo)
|
|
288
|
+
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE,
|
|
289
|
+
self.STRIDE, ctypes.c_void_p(0))
|
|
290
|
+
glVertexAttribPointer(
|
|
291
|
+
1, 1, GL_FLOAT, GL_UNSIGNED_INT, self.STRIDE, ctypes.c_void_p(12))
|
|
292
|
+
glEnableVertexAttribArray(0)
|
|
293
|
+
glEnableVertexAttribArray(1)
|
|
294
|
+
|
|
295
|
+
view_matrix = self.glwidget().get_view_matrix()
|
|
296
|
+
set_uniform(self.program, view_matrix, 'view_matrix')
|
|
297
|
+
project_matrix = self.glwidget().get_projection_matrix()
|
|
298
|
+
set_uniform(self.program, project_matrix, 'projection_matrix')
|
|
299
|
+
width = self.glwidget().current_width()
|
|
300
|
+
focal = project_matrix[0, 0] * width / 2
|
|
301
|
+
set_uniform(self.program, float(focal), 'focal')
|
|
302
|
+
|
|
303
|
+
glDrawArrays(GL_POINTS, 0, self.valid_buff_top)
|
|
304
|
+
|
|
305
|
+
# unbind VBO
|
|
306
|
+
glDisableVertexAttribArray(0)
|
|
307
|
+
glDisableVertexAttribArray(1)
|
|
308
|
+
glBindBuffer(GL_ARRAY_BUFFER, 0)
|
|
309
|
+
glUseProgram(0)
|
|
310
|
+
glDisable(GL_POINT_SPRITE)
|
|
311
|
+
glDisable(GL_PROGRAM_POINT_SIZE)
|
|
312
|
+
glDisable(GL_BLEND)
|
|
313
|
+
if self.depth_test_enabled:
|
|
314
|
+
glDisable(GL_DEPTH_TEST) # Disable depth testing if it was enabled
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Copyright 2024 Panasonic Advanced Technology Development Co.,Ltd. (Liu Yang)
|
|
3
|
+
Distributed under MIT license. See LICENSE for more information.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from q3dviewer.base_item import BaseItem
|
|
7
|
+
from OpenGL.GL import *
|
|
8
|
+
import numpy as np
|
|
9
|
+
from OpenGL.GL import shaders
|
|
10
|
+
from q3dviewer.utils import *
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# Vertex and Fragment shader source code
|
|
14
|
+
vertex_shader_source = """
|
|
15
|
+
#version 330 core
|
|
16
|
+
layout(location = 0) in vec3 position;
|
|
17
|
+
layout(location = 1) in vec2 texCoord;
|
|
18
|
+
|
|
19
|
+
out vec2 TexCoord;
|
|
20
|
+
|
|
21
|
+
uniform mat4 view_matrix;
|
|
22
|
+
uniform mat4 project_matrix;
|
|
23
|
+
uniform mat4 model_matrix;
|
|
24
|
+
|
|
25
|
+
void main()
|
|
26
|
+
{
|
|
27
|
+
gl_Position = project_matrix * view_matrix * model_matrix * vec4(position, 1.0);
|
|
28
|
+
TexCoord = texCoord;
|
|
29
|
+
}
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
fragment_shader_source = """
|
|
33
|
+
#version 330 core
|
|
34
|
+
in vec2 TexCoord;
|
|
35
|
+
out vec4 color;
|
|
36
|
+
uniform sampler2D ourTexture;
|
|
37
|
+
void main()
|
|
38
|
+
{
|
|
39
|
+
color = texture(ourTexture, TexCoord);
|
|
40
|
+
}
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class FrameItem(BaseItem):
|
|
45
|
+
def __init__(self, T=np.eye(4), size=(1, 0.8), width=3, img=None):
|
|
46
|
+
BaseItem.__init__(self)
|
|
47
|
+
self.w, self.h = size
|
|
48
|
+
self.width = width
|
|
49
|
+
self.T = T
|
|
50
|
+
self.img = img
|
|
51
|
+
self.texture = None
|
|
52
|
+
self.need_updating = False
|
|
53
|
+
|
|
54
|
+
def initialize_gl(self):
|
|
55
|
+
# Rectangle vertices and texture coordinates
|
|
56
|
+
hw = self.w / 2
|
|
57
|
+
hh = self.h / 2
|
|
58
|
+
self.vertices = np.array([
|
|
59
|
+
# positions # texture coords
|
|
60
|
+
[-hw, -hh, 0.0, 0.0, 0.0], # bottom-left
|
|
61
|
+
[hw, -hh, 0.0, 1.0, 0.0], # bottom-right
|
|
62
|
+
[hw, hh, 0.0, 1.0, 1.0], # top-right
|
|
63
|
+
[-hw, hh, 0.0, 0.0, 1.0], # top-left
|
|
64
|
+
[0, 0, -hh * 0.66, 0.0, 0.0], # top-left
|
|
65
|
+
], dtype=np.float32)
|
|
66
|
+
|
|
67
|
+
self.indices = np.array([
|
|
68
|
+
0, 1, 2, # first triangle
|
|
69
|
+
2, 3, 0 # second triangle
|
|
70
|
+
], dtype=np.uint32)
|
|
71
|
+
|
|
72
|
+
self.vao = glGenVertexArrays(1)
|
|
73
|
+
self.vbo = glGenBuffers(1)
|
|
74
|
+
self.ebo = glGenBuffers(1)
|
|
75
|
+
|
|
76
|
+
glBindVertexArray(self.vao)
|
|
77
|
+
|
|
78
|
+
glBindBuffer(GL_ARRAY_BUFFER, self.vbo)
|
|
79
|
+
glBufferData(GL_ARRAY_BUFFER, self.vertices.itemsize *
|
|
80
|
+
5 * 4, self.vertices, GL_STATIC_DRAW)
|
|
81
|
+
|
|
82
|
+
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, self.ebo)
|
|
83
|
+
glBufferData(GL_ELEMENT_ARRAY_BUFFER,
|
|
84
|
+
self.indices.nbytes, self.indices, GL_STATIC_DRAW)
|
|
85
|
+
|
|
86
|
+
# Vertex positions
|
|
87
|
+
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE,
|
|
88
|
+
20, ctypes.c_void_p(0))
|
|
89
|
+
glEnableVertexAttribArray(0)
|
|
90
|
+
# Texture coordinates
|
|
91
|
+
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE,
|
|
92
|
+
20, ctypes.c_void_p(12))
|
|
93
|
+
glEnableVertexAttribArray(1)
|
|
94
|
+
project_matrix = self.glwidget().get_projection_matrix()
|
|
95
|
+
# Compile shaders and create shader program
|
|
96
|
+
self.program = shaders.compileProgram(
|
|
97
|
+
shaders.compileShader(vertex_shader_source, GL_VERTEX_SHADER),
|
|
98
|
+
shaders.compileShader(fragment_shader_source, GL_FRAGMENT_SHADER),
|
|
99
|
+
)
|
|
100
|
+
glUseProgram(self.program)
|
|
101
|
+
set_uniform(self.program, np.eye(4), 'model_matrix')
|
|
102
|
+
set_uniform(self.program, project_matrix, 'project_matrix')
|
|
103
|
+
glUseProgram(0)
|
|
104
|
+
self.texture = glGenTextures(1)
|
|
105
|
+
self.set_data(img=self.img)
|
|
106
|
+
|
|
107
|
+
# Define line vertices
|
|
108
|
+
self.line_vertices = np.array([
|
|
109
|
+
self.vertices[0, :3], self.vertices[1, :3],
|
|
110
|
+
self.vertices[1, :3], self.vertices[2, :3],
|
|
111
|
+
self.vertices[2, :3], self.vertices[3, :3],
|
|
112
|
+
self.vertices[3, :3], self.vertices[0, :3],
|
|
113
|
+
self.vertices[4, :3], self.vertices[0, :3],
|
|
114
|
+
self.vertices[4, :3], self.vertices[1, :3],
|
|
115
|
+
self.vertices[4, :3], self.vertices[2, :3],
|
|
116
|
+
self.vertices[4, :3], self.vertices[3, :3]
|
|
117
|
+
], dtype=np.float32)
|
|
118
|
+
|
|
119
|
+
self.line_vbo = glGenBuffers(1)
|
|
120
|
+
glBindBuffer(GL_ARRAY_BUFFER, self.line_vbo)
|
|
121
|
+
glBufferData(GL_ARRAY_BUFFER, self.line_vertices.nbytes, self.line_vertices, GL_STATIC_DRAW)
|
|
122
|
+
glBindBuffer(GL_ARRAY_BUFFER, 0)
|
|
123
|
+
|
|
124
|
+
glBindVertexArray(0)
|
|
125
|
+
|
|
126
|
+
def set_transform(self, T):
|
|
127
|
+
self.T = T
|
|
128
|
+
|
|
129
|
+
def set_data(self, img=None, transform=None):
|
|
130
|
+
if transform is not None:
|
|
131
|
+
self.T = transform
|
|
132
|
+
self.img = img
|
|
133
|
+
self.need_updating = True
|
|
134
|
+
|
|
135
|
+
def update_img_buffer(self):
|
|
136
|
+
if self.img is not None and self.need_updating:
|
|
137
|
+
self.texture = glGenTextures(1)
|
|
138
|
+
glBindTexture(GL_TEXTURE_2D, self.texture)
|
|
139
|
+
# glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT)
|
|
140
|
+
# glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT)
|
|
141
|
+
# glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
|
|
142
|
+
# glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
|
|
143
|
+
if self.img.ndim == 2:
|
|
144
|
+
# Convert grayscale to RGBA
|
|
145
|
+
self.img = np.stack((self.img,) * 3 + (np.ones_like(self.img) * 255,), axis=-1)
|
|
146
|
+
elif self.img.shape[2] == 3:
|
|
147
|
+
# Add an alpha channel
|
|
148
|
+
alpha_channel = np.ones((self.img.shape[0], self.img.shape[1], 1), dtype=np.uint8) * 255
|
|
149
|
+
self.img = np.concatenate((self.img, alpha_channel), axis=2)
|
|
150
|
+
|
|
151
|
+
img = np.ascontiguousarray(self.img, dtype=np.uint8)
|
|
152
|
+
|
|
153
|
+
# Load image
|
|
154
|
+
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, img.shape[1],
|
|
155
|
+
img.shape[0], 0, GL_RGBA, GL_UNSIGNED_BYTE, img)
|
|
156
|
+
glGenerateMipmap(GL_TEXTURE_2D)
|
|
157
|
+
glBindTexture(GL_TEXTURE_2D, 0)
|
|
158
|
+
self.need_updating = False
|
|
159
|
+
|
|
160
|
+
def paint(self):
|
|
161
|
+
self.view_matrix = self.glwidget().get_view_matrix()
|
|
162
|
+
project_matrix = self.glwidget().get_projection_matrix()
|
|
163
|
+
self.update_img_buffer()
|
|
164
|
+
|
|
165
|
+
glEnable(GL_DEPTH_TEST)
|
|
166
|
+
glEnable(GL_BLEND)
|
|
167
|
+
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
|
|
168
|
+
|
|
169
|
+
glUseProgram(self.program)
|
|
170
|
+
set_uniform(self.program, self.view_matrix, 'view_matrix')
|
|
171
|
+
set_uniform(self.program, project_matrix, 'project_matrix')
|
|
172
|
+
set_uniform(self.program, self.T, 'model_matrix')
|
|
173
|
+
glBindVertexArray(self.vao)
|
|
174
|
+
|
|
175
|
+
if self.texture is not None:
|
|
176
|
+
glBindTexture(GL_TEXTURE_2D, self.texture)
|
|
177
|
+
glMultMatrixf(self.T.T) # Apply the transformation matrix
|
|
178
|
+
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, None)
|
|
179
|
+
glBindTexture(GL_TEXTURE_2D, 0)
|
|
180
|
+
|
|
181
|
+
glBindVertexArray(0)
|
|
182
|
+
glUseProgram(0)
|
|
183
|
+
|
|
184
|
+
glLineWidth(self.width)
|
|
185
|
+
glColor4f(1, 1, 1, 1) # Set the color before drawing the lines
|
|
186
|
+
glBindBuffer(GL_ARRAY_BUFFER, self.line_vbo)
|
|
187
|
+
glEnableClientState(GL_VERTEX_ARRAY)
|
|
188
|
+
glVertexPointer(3, GL_FLOAT, 0, None)
|
|
189
|
+
glDrawArrays(GL_LINES, 0, len(self.line_vertices))
|
|
190
|
+
glDisableClientState(GL_VERTEX_ARRAY)
|
|
191
|
+
glBindBuffer(GL_ARRAY_BUFFER, 0)
|
|
192
|
+
|
|
193
|
+
glDisable(GL_DEPTH_TEST)
|
|
194
|
+
glDisable(GL_BLEND)
|