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,254 @@
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
+ import numpy as np
7
+ from q3dviewer.base_item import BaseItem
8
+ from OpenGL.GL import *
9
+ import numpy as np
10
+ import os
11
+ from PySide6.QtWidgets import QComboBox, QLabel
12
+ from OpenGL.GL import shaders
13
+ from q3dviewer.utils import *
14
+
15
+
16
+ def div_round_up(x, y):
17
+ return int((x + y - 1) / y)
18
+
19
+
20
+ class GaussianItem(BaseItem):
21
+ def __init__(self, **kwds):
22
+ super().__init__()
23
+ self.need_updateGS = False
24
+ self.sh_dim = 0
25
+ self.gs_data = np.empty([0])
26
+ self.prev_Rz = np.array([np.inf, np.inf, np.inf])
27
+ self.path = os.path.dirname(__file__)
28
+ try:
29
+ import torch
30
+ if not torch.cuda.is_available():
31
+ raise ImportError
32
+ self.cuda_pw = None
33
+ self.sort = self.torch_sort
34
+ except ImportError:
35
+ self.sort = self.openg_sort
36
+
37
+ def add_setting(self, layout):
38
+ label1 = QLabel("set render mode:")
39
+ layout.addWidget(label1)
40
+ combo = QComboBox()
41
+ combo.addItem("render normal guassian")
42
+ combo.addItem("render ball")
43
+ combo.addItem("render inverse guassian")
44
+ combo.currentIndexChanged.connect(self.onComboboxSelection)
45
+ layout.addWidget(combo)
46
+
47
+ def onComboboxSelection(self, index):
48
+ glUseProgram(self.program)
49
+ set_uniform(self.program, index, 'render_mod')
50
+ glUseProgram(0)
51
+
52
+ def initialize_gl(self):
53
+ fragment_shader = open(
54
+ self.path + '/../shaders/gau_frag.glsl', 'r').read()
55
+ vertex_shader = open(
56
+ self.path + '/../shaders/gau_vert.glsl', 'r').read()
57
+ sort_shader = open(
58
+ self.path + '/../shaders/sort_by_key.glsl', 'r').read()
59
+ prep_shader = open(self.path + '/../shaders/gau_prep.glsl', 'r').read()
60
+
61
+ self.sort_program = shaders.compileProgram(
62
+ shaders.compileShader(sort_shader, GL_COMPUTE_SHADER))
63
+
64
+ self.prep_program = shaders.compileProgram(
65
+ shaders.compileShader(prep_shader, GL_COMPUTE_SHADER))
66
+
67
+ self.program = shaders.compileProgram(
68
+ shaders.compileShader(vertex_shader, GL_VERTEX_SHADER),
69
+ shaders.compileShader(fragment_shader, GL_FRAGMENT_SHADER),
70
+ )
71
+ self.vao = glGenVertexArrays(1)
72
+
73
+ # trade a gaussian as a square (4 2d points)
74
+ square_vert = np.array([-1, 1, 1, 1, 1, -1, -1, -1], dtype=np.float32)
75
+ indices = np.array([0, 1, 2, 0, 2, 3], dtype=np.uint32)
76
+
77
+ # set the vertices for square
78
+ vbo = glGenBuffers(1)
79
+ glBindVertexArray(self.vao)
80
+ glBindBuffer(GL_ARRAY_BUFFER, vbo)
81
+ glBufferData(GL_ARRAY_BUFFER, square_vert.nbytes,
82
+ square_vert, GL_STATIC_DRAW)
83
+ pos = glGetAttribLocation(self.program, 'vert')
84
+ glVertexAttribPointer(pos, 2, GL_FLOAT, False, 0, None)
85
+ glEnableVertexAttribArray(pos)
86
+ glBindBuffer(GL_ARRAY_BUFFER, 0)
87
+
88
+ # the vert's indices for drawing square
89
+ self.ebo = glGenBuffers(1)
90
+ glBindVertexArray(self.vao)
91
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, self.ebo)
92
+ glBufferData(GL_ELEMENT_ARRAY_BUFFER,
93
+ indices.nbytes, indices, GL_STATIC_DRAW)
94
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)
95
+ glBindVertexArray(0)
96
+
97
+ # add SSBO for gaussian data
98
+ self.ssbo_gs = glGenBuffers(1)
99
+ self.ssbo_gi = glGenBuffers(1)
100
+ self.ssbo_dp = glGenBuffers(1)
101
+ self.ssbo_pp = glGenBuffers(1)
102
+
103
+ width = self.glwidget().current_width()
104
+ height = self.glwidget().current_height()
105
+
106
+ # set constant parameter for gaussian shader
107
+ project_matrix = self.glwidget().get_projection_matrix()
108
+ focal_x = project_matrix[0, 0] * width / 2
109
+ focal_y = project_matrix[1, 1] * height / 2
110
+ glUseProgram(self.prep_program)
111
+ set_uniform(self.prep_program,
112
+ project_matrix, 'projection_matrix')
113
+ set_uniform(self.prep_program, np.array([focal_x, focal_y]), 'focal')
114
+ glUseProgram(0)
115
+
116
+ glUseProgram(self.program)
117
+ set_uniform(self.program, np.array([width, height]), 'win_size')
118
+ set_uniform(self.program, 0, 'render_mod')
119
+ glUseProgram(0)
120
+
121
+ # opengl settings
122
+ glDisable(GL_CULL_FACE)
123
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
124
+
125
+ def updateGS(self):
126
+ if (self.need_updateGS):
127
+ # compute sorting size
128
+ self.num_sort = int(2**np.ceil(np.log2(self.gs_data.shape[0])))
129
+
130
+ # set input gaussian data
131
+ glBindBuffer(GL_SHADER_STORAGE_BUFFER, self.ssbo_gs)
132
+ glBufferData(GL_SHADER_STORAGE_BUFFER, self.gs_data.nbytes,
133
+ self.gs_data.reshape(-1), GL_STATIC_DRAW)
134
+ glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, self.ssbo_gs)
135
+ glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0)
136
+
137
+ # set depth for sorting
138
+ glBindBuffer(GL_SHADER_STORAGE_BUFFER, self.ssbo_dp)
139
+ glBufferData(GL_SHADER_STORAGE_BUFFER,
140
+ self.num_sort * 4, None, GL_STATIC_DRAW)
141
+ glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 2, self.ssbo_dp)
142
+ glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0)
143
+
144
+ # set index for sorting (the index need be initialized)
145
+ gi = np.arange(self.num_sort, dtype=np.uint32)
146
+ glBindBuffer(GL_SHADER_STORAGE_BUFFER, self.ssbo_gi)
147
+ glBufferData(GL_SHADER_STORAGE_BUFFER,
148
+ self.num_sort * 4, gi, GL_STATIC_DRAW)
149
+ glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, self.ssbo_gi)
150
+ glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0)
151
+
152
+ # set preprocess buffer
153
+ # the dim of preprocess data is 12 u(3),
154
+ # covinv(3), color(3), area(2), alpha(1)
155
+ glBindBuffer(GL_SHADER_STORAGE_BUFFER, self.ssbo_pp)
156
+ glBufferData(GL_SHADER_STORAGE_BUFFER,
157
+ self.gs_data.shape[0] * 4 * 12,
158
+ None, GL_STATIC_DRAW)
159
+ glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 3, self.ssbo_pp)
160
+ glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0)
161
+
162
+ glUseProgram(self.prep_program)
163
+ set_uniform(self.prep_program, self.sh_dim, 'sh_dim')
164
+ set_uniform(self.prep_program,
165
+ self.gs_data.shape[0], 'gs_num')
166
+ glUseProgram(0)
167
+ self.need_updateGS = False
168
+
169
+ def paint(self):
170
+ # get current view matrix
171
+ self.view_matrix = self.glwidget().get_view_matrix()
172
+
173
+ # if gaussian data is update, renew vao, ssbo, etc...
174
+ self.updateGS()
175
+
176
+ if (self.gs_data.shape[0] == 0):
177
+ return
178
+
179
+ # preprocess and sort gaussian by compute shader.
180
+ self.preprocessGS()
181
+ self.try_sort()
182
+ glEnable(GL_BLEND)
183
+ # draw by vert shader
184
+ glUseProgram(self.program)
185
+ # bind vao and ebo
186
+ glBindVertexArray(self.vao)
187
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, self.ebo)
188
+ # draw instances
189
+ glDrawElementsInstanced(
190
+ GL_TRIANGLES, 6, GL_UNSIGNED_INT, None, self.gs_data.shape[0])
191
+ # upbind vao and ebo
192
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0)
193
+ glBindVertexArray(0)
194
+ glUseProgram(0)
195
+ glDisable(GL_BLEND)
196
+
197
+ def try_sort(self):
198
+ # don't sort if the depths are not change.
199
+ Rz = self.view_matrix[2, :3]
200
+ if (np.linalg.norm(self.prev_Rz - Rz) > 0.1):
201
+ # import torch
202
+ # torch.cuda.synchronize()
203
+ # start = time.time()
204
+ self.sort()
205
+ self.prev_Rz = Rz
206
+ # torch.cuda.synchronize()
207
+ # end = time.time()
208
+ # time_diff = end - start
209
+ # print(time_diff)
210
+
211
+ def openg_sort(self):
212
+ glUseProgram(self.sort_program)
213
+ # can we move this loop to gpu?
214
+ # level = level*2
215
+ for level in 2**np.arange(1, int(np.ceil(np.log2(self.num_sort))+1)):
216
+ # stage =stage / 2
217
+ for stage in level/2**np.arange(1, np.log2(level)+1):
218
+ set_uniform(self.sort_program, int(level), 'level')
219
+ set_uniform(self.sort_program, int(stage), 'stage')
220
+ glDispatchCompute(div_round_up(self.num_sort//2, 256), 1, 1)
221
+ glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT)
222
+ # glFinish()
223
+ glUseProgram(0)
224
+
225
+ def torch_sort(self):
226
+ import torch
227
+ if self.cuda_pw is None:
228
+ self.cuda_pw = torch.tensor(self.gs_data[:, :3]).cuda()
229
+ Rz = torch.tensor(self.view_matrix[2, :3].astype(np.float32)).cuda()
230
+ depth = Rz @ self.cuda_pw.T
231
+ index = torch.argsort(depth).type(torch.int32).cpu().numpy()
232
+ glBindBuffer(GL_SHADER_STORAGE_BUFFER, self.ssbo_gi)
233
+ glBufferData(GL_SHADER_STORAGE_BUFFER,
234
+ index.nbytes, index, GL_STATIC_DRAW)
235
+ glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, self.ssbo_gi)
236
+ glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0)
237
+ return index
238
+
239
+ def preprocessGS(self):
240
+ glUseProgram(self.prep_program)
241
+ set_uniform(self.prep_program, self.view_matrix, 'view_matrix')
242
+ glDispatchCompute(div_round_up(self.gs_data.shape[0], 256), 1, 1)
243
+ glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT)
244
+ glUseProgram(0)
245
+
246
+ def set_data(self, **kwds):
247
+ if 'gs_data' in kwds:
248
+ self.need_updateGS = False
249
+ gs_data = kwds.pop('gs_data')
250
+ self.gs_data = np.ascontiguousarray(gs_data, dtype=np.float32)
251
+ self.sh_dim = self.gs_data.shape[-1] - (3 + 4 + 3 + 1)
252
+ self.prev_Rz = np.array([np.inf, np.inf, np.inf])
253
+ self.cuda_pw = None
254
+ self.need_updateGS = True
@@ -0,0 +1,88 @@
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
+ from PySide6.QtWidgets import QLabel, QDoubleSpinBox, QLineEdit
9
+ from PySide6.QtCore import QRegularExpression
10
+ from PySide6.QtGui import QRegularExpressionValidator
11
+ import numpy as np
12
+
13
+
14
+ class GridItem(BaseItem):
15
+ def __init__(self, size, spacing, color=np.array([255, 255, 255, 76.5]), offset=np.array([0, 0, 0])):
16
+ super().__init__()
17
+ self.size = size
18
+ self.spacing = spacing
19
+ self.color = color
20
+ self.offset = offset
21
+
22
+ def add_setting(self, layout):
23
+ label_size = QLabel("Set size:")
24
+ layout.addWidget(label_size)
25
+ spinbox_size = QDoubleSpinBox()
26
+ spinbox_size.setSingleStep(1.0)
27
+ layout.addWidget(spinbox_size)
28
+ spinbox_size.setValue(self.size)
29
+ spinbox_size.valueChanged.connect(self.set_size)
30
+ spinbox_size.setRange(0, 100000)
31
+
32
+ label_spacing = QLabel("Set spacing:")
33
+ layout.addWidget(label_spacing)
34
+ spinbox_spacing = QDoubleSpinBox()
35
+ layout.addWidget(spinbox_spacing)
36
+ spinbox_spacing.setSingleStep(0.1)
37
+ spinbox_spacing.setValue(self.spacing)
38
+ spinbox_spacing.valueChanged.connect(self._on_spacing)
39
+ spinbox_spacing.setRange(0, 1000)
40
+
41
+ label_offset = QLabel("Set offset (x;y;z):")
42
+ layout.addWidget(label_offset)
43
+ self.edit_offset = QLineEdit()
44
+ regex = QRegularExpression(r"^-?\d+(\.\d+)?;-?\d+(\.\d+)?;-?\d+(\.\d+)?$")
45
+ validator = QRegularExpressionValidator(regex)
46
+ self.edit_offset.setValidator(validator)
47
+ self.edit_offset.setText(f"{self.offset[0]};{self.offset[1]};{self.offset[2]}")
48
+ self.edit_offset.textChanged.connect(self._on_offset)
49
+ layout.addWidget(self.edit_offset)
50
+
51
+ def set_size(self, size):
52
+ self.size = size
53
+
54
+ def _on_spacing(self, spacing):
55
+ if spacing > 0:
56
+ self.spacing = spacing
57
+
58
+ def _on_offset(self, text):
59
+ try:
60
+ values = list(map(float, text.split(';')))
61
+ if len(values) == 3:
62
+ self.offset = np.array(values)
63
+ else:
64
+ raise ValueError("Offset must have 3 values separated by ';'")
65
+ except ValueError:
66
+ pass
67
+
68
+ def set_offset(self, offset):
69
+ if isinstance(offset, np.ndarray) and offset.shape == (3,):
70
+ self.offset = offset
71
+ self.edit_offset.setText(f"{self.offset[0]};{self.offset[1]};{self.offset[2]}")
72
+ else:
73
+ raise ValueError("Offset must be a numpy array with shape (3,)")
74
+
75
+ def paint(self):
76
+ glEnable(GL_BLEND)
77
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
78
+ glColor4f(self.color[0] / 255.0, self.color[1] / 255.0, self.color[2] / 255.0, self.color[3] / 255.0)
79
+ glBegin(GL_LINES)
80
+ for i in np.arange(-self.size, self.size + self.spacing, self.spacing):
81
+ glVertex3f(i + self.offset[0], -self.size + self.offset[1], self.offset[2])
82
+ glVertex3f(i + self.offset[0], self.size + self.offset[1], self.offset[2])
83
+ glVertex3f(-self.size + self.offset[0], i + self.offset[1], self.offset[2])
84
+ glVertex3f(self.size + self.offset[0], i + self.offset[1], self.offset[2])
85
+ glEnd()
86
+ glDisable(GL_BLEND)
87
+
88
+
@@ -0,0 +1,172 @@
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 PIL import Image as PIL_Image
11
+ from PySide6.QtWidgets import QLabel, QSpinBox, QCheckBox
12
+
13
+
14
+ # Vertex and Fragment shader source code
15
+ vertex_shader_source = """
16
+ #version 330 core
17
+ layout(location = 0) in vec3 position;
18
+ layout(location = 1) in vec2 texCoord;
19
+
20
+ out vec2 TexCoord;
21
+
22
+ uniform mat4 view_matrix;
23
+ uniform mat4 project_matrix;
24
+
25
+ void main()
26
+ {
27
+ gl_Position = 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 ImageItem(BaseItem):
45
+ def __init__(self, pos=np.array([0, 0]), size=np.array([1280/2, 720/2])):
46
+ BaseItem.__init__(self)
47
+ self.pos = pos # bottom-left
48
+ self.size = size
49
+ self.image = np.zeros((self.size[0], self.size[1], 4), dtype=np.uint8)
50
+ self.alpha = 255
51
+
52
+ def initialize_gl(self):
53
+ # Rectangle vertices and texture coordinates
54
+ width = self.glwidget().current_width()
55
+ height = self.glwidget().current_height()
56
+ x0, y0 = self.pos
57
+ x1, y1 = self.pos + self.size
58
+ x0 = x0 / width * 2 - 1
59
+ y0 = y0 / height * 2 - 1
60
+ x1 = x1 / width * 2 - 1
61
+ y1 = y1 / height * 2 - 1
62
+
63
+ self.vertices = np.array([
64
+ # positions # texture coords
65
+ [x0, y0, 0.0, 0.0, 0.0], # bottom-left
66
+ [x1, y0, 0.0, 1.0, 0.0], # bottom-right
67
+ [x1, y1, 0.0, 1.0, 1.0], # top-right
68
+ [x0, y1, 0.0, 0.0, 1.0], # top-left
69
+ ], dtype=np.float32)
70
+
71
+ indices = np.array([
72
+ 0, 1, 2, # first triangle
73
+ 2, 3, 0 # second triangle
74
+ ], dtype=np.uint32)
75
+
76
+ self.vao = glGenVertexArrays(1)
77
+ vbo = glGenBuffers(1)
78
+ ebo = glGenBuffers(1)
79
+
80
+ glBindVertexArray(self.vao)
81
+
82
+ glBindBuffer(GL_ARRAY_BUFFER, vbo)
83
+ glBufferData(GL_ARRAY_BUFFER, self.vertices.itemsize *
84
+ 5 * 4, self.vertices, GL_STATIC_DRAW)
85
+
86
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo)
87
+ glBufferData(GL_ELEMENT_ARRAY_BUFFER,
88
+ indices.nbytes, indices, GL_STATIC_DRAW)
89
+
90
+ # Vertex positions
91
+ glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE,
92
+ 20, ctypes.c_void_p(0))
93
+ glEnableVertexAttribArray(0)
94
+
95
+ # Texture coordinates
96
+ glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE,
97
+ 20, ctypes.c_void_p(12))
98
+ glEnableVertexAttribArray(1)
99
+
100
+ # Compile shaders and create shader program
101
+ self.program = shaders.compileProgram(
102
+ shaders.compileShader(vertex_shader_source, GL_VERTEX_SHADER),
103
+ shaders.compileShader(fragment_shader_source, GL_FRAGMENT_SHADER),
104
+ )
105
+
106
+ self.texture = glGenTextures(1)
107
+ glBindTexture(GL_TEXTURE_2D, self.texture)
108
+ glBindVertexArray(0)
109
+
110
+ def set_data(self, data):
111
+ if isinstance(data, np.ndarray):
112
+ pass
113
+ elif isinstance(data, PIL_Image.Image):
114
+ data = np.array(data)
115
+ else:
116
+ print("not support image type")
117
+ raise NotImplementedError
118
+
119
+ if data.ndim == 2: # Grayscale image
120
+ data = np.stack((data,) * 3 + (np.ones_like(data) * 255,), axis=-1)
121
+ elif data.shape[-1] == 3: # RGB image
122
+ alpha_channel = np.ones(
123
+ (data.shape[0], data.shape[1], 1),
124
+ dtype=data.dtype) * self.alpha
125
+ data = np.concatenate((data, alpha_channel), axis=-1)
126
+ self.image = data
127
+
128
+ def paint(self):
129
+ if self.image is not None:
130
+ img_data = self.image
131
+ img_data = np.flipud(img_data) # Flip the image vertically
132
+ img_data = img_data.tobytes()
133
+ glBindTexture(GL_TEXTURE_2D, self.texture)
134
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, self.image.shape[1],
135
+ self.image.shape[0], 0,
136
+ GL_RGBA, GL_UNSIGNED_BYTE, img_data)
137
+ glGenerateMipmap(GL_TEXTURE_2D)
138
+ glBindTexture(GL_TEXTURE_2D, 0)
139
+ self.image = None
140
+
141
+ glEnable(GL_DEPTH_TEST)
142
+ glEnable(GL_BLEND)
143
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
144
+
145
+ glUseProgram(self.program)
146
+ glBindVertexArray(self.vao)
147
+ glBindTexture(GL_TEXTURE_2D, self.texture)
148
+ glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, None)
149
+ glBindTexture(GL_TEXTURE_2D, 0)
150
+ glBindVertexArray(0)
151
+ glUseProgram(0)
152
+
153
+ glDisable(GL_DEPTH_TEST)
154
+ glDisable(GL_BLEND)
155
+
156
+ def add_setting(self, layout):
157
+ alpha_label = QLabel("Set Alpha:")
158
+ layout.addWidget(alpha_label)
159
+ spinbox_alpha = QSpinBox()
160
+ spinbox_alpha.setSingleStep(1)
161
+ spinbox_alpha.setRange(0, 255)
162
+ spinbox_alpha.setValue(self.alpha)
163
+ spinbox_alpha.valueChanged.connect(self.set_alpha)
164
+ layout.addWidget(spinbox_alpha)
165
+
166
+ checkbox_show = QCheckBox("Show Image")
167
+ checkbox_show.setChecked(True)
168
+ checkbox_show.stateChanged.connect(self.set_visible)
169
+ layout.addWidget(checkbox_show)
170
+
171
+ def set_alpha(self, alpha):
172
+ self.alpha = alpha
@@ -0,0 +1,120 @@
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
+ import threading
10
+ from PySide6.QtWidgets import QLabel, QLineEdit, QDoubleSpinBox
11
+ from PySide6.QtCore import QRegularExpression
12
+ from PySide6.QtGui import QRegularExpressionValidator
13
+ from q3dviewer.utils.maths import hex_to_rgba
14
+
15
+
16
+ class LineItem(BaseItem):
17
+ def __init__(self, width=1, color='#00ff00', line_type='LINE_STRIP'):
18
+ super(LineItem, self).__init__()
19
+ self.width = width
20
+ self.buff = np.empty((0, 3), np.float32)
21
+ self.wait_add_data = None
22
+ self.mutex = threading.Lock()
23
+ self.capacity = 100000
24
+ self.valid_buff_top = 0
25
+ self.color = color
26
+ self.rgb = hex_to_rgba(color)
27
+ self.line_type = GL_LINE_STRIP if line_type == 'LINE_STRIP' else GL_LINES
28
+
29
+ def add_setting(self, layout):
30
+ label_color = QLabel("Set trajectory color:")
31
+ layout.addWidget(label_color)
32
+ self.color_edit = QLineEdit()
33
+ self.color_edit.setToolTip("Hex number, i.e. #FF4500")
34
+ self.color_edit.setText(self.color)
35
+ self.color_edit.textChanged.connect(self._on_color)
36
+ regex = QRegularExpression(r"^#[0-9A-Fa-f]{6}$")
37
+ validator = QRegularExpressionValidator(regex)
38
+ self.color_edit.setValidator(validator)
39
+ layout.addWidget(self.color_edit)
40
+
41
+ label_width = QLabel("Set width:")
42
+ layout.addWidget(label_width)
43
+ spinbox_width = QDoubleSpinBox()
44
+ spinbox_width.setSingleStep(0.1)
45
+ layout.addWidget(spinbox_width)
46
+ spinbox_width.setValue(self.width)
47
+ spinbox_width.valueChanged.connect(self.set_width)
48
+ spinbox_width.setRange(0.1, 10.0)
49
+
50
+ def _on_color(self, color):
51
+ try:
52
+ self.rgb = hex_to_rgba(color)
53
+ self.color = color
54
+ except ValueError:
55
+ pass
56
+
57
+ def set_color(self, color):
58
+ self.color_edit.setText(color)
59
+
60
+ def set_width(self, width):
61
+ self.width = width
62
+
63
+ def set_data(self, data, append=False):
64
+ self.mutex.acquire()
65
+ data = data.astype(np.float32).reshape(-1, 3)
66
+ if (append is False):
67
+ self.wait_add_data = data
68
+ self.add_buff_loc = 0
69
+ else:
70
+ if (self.wait_add_data is None):
71
+ self.wait_add_data = data
72
+ else:
73
+ self.wait_add_data = np.concatenate([self.wait_add_data, data])
74
+ self.add_buff_loc = self.valid_buff_top
75
+ self.mutex.release()
76
+
77
+ def update_render_buffer(self):
78
+ if (self.wait_add_data is None):
79
+ return
80
+ self.mutex.acquire()
81
+
82
+ new_buff_top = self.add_buff_loc + self.wait_add_data.shape[0]
83
+ if new_buff_top > self.buff.shape[0]:
84
+ buff_capacity = self.buff.shape[0]
85
+ while (new_buff_top > buff_capacity):
86
+ buff_capacity += self.capacity
87
+ self.buff = np.empty((buff_capacity, 3), np.float32)
88
+ self.buff[self.add_buff_loc:new_buff_top] = self.wait_add_data
89
+ glBindBuffer(GL_ARRAY_BUFFER, self.vbo)
90
+ glBufferData(GL_ARRAY_BUFFER, self.buff.nbytes,
91
+ self.buff, GL_DYNAMIC_DRAW)
92
+ glBindBuffer(GL_ARRAY_BUFFER, 0)
93
+ else:
94
+ self.buff[self.add_buff_loc:new_buff_top] = self.wait_add_data
95
+ glBindBuffer(GL_ARRAY_BUFFER, self.vbo)
96
+ glBufferSubData(GL_ARRAY_BUFFER, self.add_buff_loc * 12,
97
+ self.wait_add_data.shape[0] * 12,
98
+ self.wait_add_data)
99
+ self.valid_buff_top = new_buff_top
100
+ self.wait_add_data = None
101
+ self.mutex.release()
102
+
103
+ def initialize_gl(self):
104
+ self.vbo = glGenBuffers(1)
105
+
106
+ def paint(self):
107
+ self.update_render_buffer()
108
+ glEnable(GL_BLEND)
109
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
110
+ glBindBuffer(GL_ARRAY_BUFFER, self.vbo)
111
+ glEnableClientState(GL_VERTEX_ARRAY)
112
+ glVertexPointer(3, GL_FLOAT, 0, None)
113
+ glLineWidth(self.width)
114
+ glColor4f(*self.rgb)
115
+
116
+ glDrawArrays(self.line_type, 0, self.valid_buff_top)
117
+ glDisableClientState(GL_VERTEX_ARRAY)
118
+
119
+ glBindBuffer(GL_ARRAY_BUFFER, 0)
120
+ glDisable(GL_BLEND)
@@ -0,0 +1,63 @@
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 PySide6 import QtCore, QtGui
7
+ from q3dviewer.base_item import BaseItem
8
+ from OpenGL.GL import *
9
+ from q3dviewer.utils.maths import hex_to_rgba
10
+
11
+
12
+ class Text2DItem(BaseItem):
13
+ """Draws text over opengl 3D."""
14
+
15
+ def __init__(self, **kwds):
16
+ """All keyword arguments are passed to set_data()"""
17
+ BaseItem.__init__(self)
18
+ self.pos = (20, 50)
19
+ self.color = '#ffffff'
20
+ self.rgb = hex_to_rgba(self.color)
21
+ self.text = ''
22
+ self.font = QtGui.QFont('Helvetica', 16)
23
+ self.set_data(**kwds)
24
+
25
+ def set_data(self, **kwds):
26
+ args = ['pos', 'color', 'text', 'size', 'font']
27
+ for k in kwds.keys():
28
+ if k not in args:
29
+ raise ValueError('Invalid keyword argument: %s\
30
+ (allowed arguments are %s)' % (k, str(args)))
31
+ for arg in args:
32
+ if arg in kwds:
33
+ value = kwds[arg]
34
+ if arg == 'pos':
35
+ self.pos = value
36
+ elif arg == 'color':
37
+ self.set_color(value)
38
+ elif arg == 'font':
39
+ if isinstance(value, QtGui.QFont) is False:
40
+ raise TypeError('"font" must be QFont.')
41
+ elif arg == 'size':
42
+ self.font.setPointSize(value)
43
+ setattr(self, arg, value)
44
+
45
+ def set_color(self, color):
46
+ try:
47
+ self.rgb = hex_to_rgba(color)
48
+ self.color = color
49
+ except ValueError:
50
+ pass
51
+
52
+ def paint(self):
53
+ if len(self.text) < 1:
54
+ return
55
+
56
+ text_pos = QtCore.QPointF(*self.pos)
57
+ painter = QtGui.QPainter(self.glwidget())
58
+ painter.setPen(QtGui.QColor(*[int(c * 255) for c in self.rgb[:3]]))
59
+ painter.setFont(self.font)
60
+ painter.setRenderHints(QtGui.QPainter.RenderHint.Antialiasing |
61
+ QtGui.QPainter.RenderHint.TextAntialiasing)
62
+ painter.drawText(text_pos, self.text)
63
+ painter.end()
q3dviewer/gau_io.py ADDED
File without changes