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.
Files changed (43) hide show
  1. q3dviewer/__init__.py +5 -0
  2. q3dviewer/base_glwidget.py +256 -0
  3. q3dviewer/base_item.py +57 -0
  4. q3dviewer/custom_items/__init__.py +9 -0
  5. q3dviewer/custom_items/axis_item.py +148 -0
  6. q3dviewer/custom_items/cloud_io_item.py +79 -0
  7. q3dviewer/custom_items/cloud_item.py +314 -0
  8. q3dviewer/custom_items/frame_item.py +194 -0
  9. q3dviewer/custom_items/gaussian_item.py +254 -0
  10. q3dviewer/custom_items/grid_item.py +88 -0
  11. q3dviewer/custom_items/image_item.py +172 -0
  12. q3dviewer/custom_items/line_item.py +120 -0
  13. q3dviewer/custom_items/text_item.py +63 -0
  14. q3dviewer/gau_io.py +0 -0
  15. q3dviewer/glwidget.py +131 -0
  16. q3dviewer/shaders/cloud_frag.glsl +28 -0
  17. q3dviewer/shaders/cloud_vert.glsl +72 -0
  18. q3dviewer/shaders/gau_frag.glsl +42 -0
  19. q3dviewer/shaders/gau_prep.glsl +249 -0
  20. q3dviewer/shaders/gau_vert.glsl +77 -0
  21. q3dviewer/shaders/sort_by_key.glsl +56 -0
  22. q3dviewer/tools/__init__.py +1 -0
  23. q3dviewer/tools/cloud_viewer.py +123 -0
  24. q3dviewer/tools/example_viewer.py +47 -0
  25. q3dviewer/tools/gaussian_viewer.py +60 -0
  26. q3dviewer/tools/lidar_calib.py +294 -0
  27. q3dviewer/tools/lidar_cam_calib.py +314 -0
  28. q3dviewer/tools/ros_viewer.py +85 -0
  29. q3dviewer/utils/__init__.py +4 -0
  30. q3dviewer/utils/cloud_io.py +323 -0
  31. q3dviewer/utils/convert_ros_msg.py +49 -0
  32. q3dviewer/utils/gl_helper.py +40 -0
  33. q3dviewer/utils/maths.py +168 -0
  34. q3dviewer/utils/range_slider.py +86 -0
  35. q3dviewer/viewer.py +58 -0
  36. q3dviewer-1.0.5.dist-info/LICENSE +21 -0
  37. {q3dviewer-1.0.3.dist-info → q3dviewer-1.0.5.dist-info}/METADATA +7 -4
  38. q3dviewer-1.0.5.dist-info/RECORD +41 -0
  39. q3dviewer-1.0.5.dist-info/top_level.txt +1 -0
  40. q3dviewer-1.0.3.dist-info/RECORD +0 -5
  41. q3dviewer-1.0.3.dist-info/top_level.txt +0 -1
  42. {q3dviewer-1.0.3.dist-info → q3dviewer-1.0.5.dist-info}/WHEEL +0 -0
  43. {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)