q3dviewer 1.1.7__py3-none-any.whl → 1.1.8__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/.vscode/c_cpp_properties.json +30 -0
- q3dviewer/.vscode/settings.json +10 -0
- q3dviewer/base_glwidget.py +56 -1
- q3dviewer/custom_items/__init__.py +1 -0
- q3dviewer/custom_items/cloud_io_item.py +16 -1
- q3dviewer/custom_items/cloud_item.py +31 -20
- q3dviewer/custom_items/text3d_item.py +120 -0
- q3dviewer/custom_items/text_item.py +19 -4
- q3dviewer/gau_io.py +0 -168
- q3dviewer/glwidget.py +23 -3
- q3dviewer/shaders/cloud_frag.glsl +1 -1
- q3dviewer/shaders/cloud_vert.glsl +4 -4
- q3dviewer/shaders/gau_frag.glsl +1 -1
- q3dviewer/shaders/gau_prep.glsl +1 -1
- q3dviewer/shaders/gau_vert.glsl +1 -1
- q3dviewer/shaders/sort_by_key.glsl +1 -1
- q3dviewer/test/test_interpolation.py +58 -0
- q3dviewer/test/test_rendering.py +73 -0
- q3dviewer/tools/cinematographer.py +367 -0
- q3dviewer/tools/cloud_viewer.py +79 -3
- q3dviewer/tools/example_viewer.py +8 -28
- q3dviewer/tools/film_maker.py +1 -1
- q3dviewer/tools/lidar_cam_calib.py +8 -8
- q3dviewer/utils/convert_ros_msg.py +49 -6
- {q3dviewer-1.1.7.dist-info → q3dviewer-1.1.8.dist-info}/METADATA +2 -1
- q3dviewer-1.1.8.dist-info/RECORD +50 -0
- {q3dviewer-1.1.7.dist-info → q3dviewer-1.1.8.dist-info}/WHEEL +1 -1
- q3dviewer/basic_window.py +0 -228
- q3dviewer/cloud_viewer.py +0 -74
- q3dviewer/custom_items/camera_frame_item.py +0 -173
- q3dviewer/custom_items/trajectory_item.py +0 -79
- q3dviewer/utils.py +0 -71
- q3dviewer-1.1.7.dist-info/RECORD +0 -49
- {q3dviewer-1.1.7.dist-info → q3dviewer-1.1.8.dist-info}/LICENSE +0 -0
- {q3dviewer-1.1.7.dist-info → q3dviewer-1.1.8.dist-info}/entry_points.txt +0 -0
- {q3dviewer-1.1.7.dist-info → q3dviewer-1.1.8.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"configurations": [
|
|
3
|
+
{
|
|
4
|
+
"browse": {
|
|
5
|
+
"databaseFilename": "${default}",
|
|
6
|
+
"limitSymbolsToIncludedHeaders": false
|
|
7
|
+
},
|
|
8
|
+
"includePath": [
|
|
9
|
+
"/home/liu/catkin_ws/devel/include/**",
|
|
10
|
+
"/opt/ros/noetic/include/**",
|
|
11
|
+
"/home/liu/catkin_ws/src/marker_test/apriltag_ros/apriltag_ros/include/**",
|
|
12
|
+
"/home/liu/catkin_ws/src/BALM/include/**",
|
|
13
|
+
"/home/liu/catkin_ws/src/FAST-LIVO/include/**",
|
|
14
|
+
"/home/liu/catkin_ws/src/grid2dlib/include/**",
|
|
15
|
+
"/home/liu/catkin_ws/src/lidar_cam_calib/include/**",
|
|
16
|
+
"/home/liu/catkin_ws/src/livox_laser_simulation/include/**",
|
|
17
|
+
"/home/liu/catkin_ws/src/usb_cam/include/**",
|
|
18
|
+
"/home/liu/catkin_ws/src/rpg_vikit/vikit_common/include/**",
|
|
19
|
+
"/home/liu/catkin_ws/src/rpg_vikit/vikit_ros/include/**",
|
|
20
|
+
"/usr/include/**"
|
|
21
|
+
],
|
|
22
|
+
"name": "ROS",
|
|
23
|
+
"intelliSenseMode": "gcc-x64",
|
|
24
|
+
"compilerPath": "/usr/bin/gcc",
|
|
25
|
+
"cStandard": "gnu11",
|
|
26
|
+
"cppStandard": "c++14"
|
|
27
|
+
}
|
|
28
|
+
],
|
|
29
|
+
"version": 4
|
|
30
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"python.autoComplete.extraPaths": [
|
|
3
|
+
"/home/liu/catkin_ws/devel/lib/python3/dist-packages",
|
|
4
|
+
"/opt/ros/noetic/lib/python3/dist-packages"
|
|
5
|
+
],
|
|
6
|
+
"python.analysis.extraPaths": [
|
|
7
|
+
"/home/liu/catkin_ws/devel/lib/python3/dist-packages",
|
|
8
|
+
"/opt/ros/noetic/lib/python3/dist-packages"
|
|
9
|
+
]
|
|
10
|
+
}
|
q3dviewer/base_glwidget.py
CHANGED
|
@@ -89,6 +89,9 @@ class BaseGLWidget(QOpenGLWidget):
|
|
|
89
89
|
the method is herted from QOpenGLWidget,
|
|
90
90
|
and it is called when the widget is first shown.
|
|
91
91
|
"""
|
|
92
|
+
glEnable(GL_DEPTH_TEST)
|
|
93
|
+
glDepthFunc(GL_LESS)
|
|
94
|
+
|
|
92
95
|
for item in self.items:
|
|
93
96
|
item.initialize()
|
|
94
97
|
# initialize the projection matrix and model view matrix
|
|
@@ -302,7 +305,7 @@ class BaseGLWidget(QOpenGLWidget):
|
|
|
302
305
|
near = dist * 0.001
|
|
303
306
|
far = dist * 10000.
|
|
304
307
|
r = near * tan(0.5 * radians(self._fov))
|
|
305
|
-
t = r * h / w
|
|
308
|
+
t = r * h / max(w, 1)
|
|
306
309
|
matrix = frustum(-r, r, -t, t, near, far)
|
|
307
310
|
return matrix
|
|
308
311
|
|
|
@@ -349,3 +352,55 @@ class BaseGLWidget(QOpenGLWidget):
|
|
|
349
352
|
frame = np.frombuffer(pixels, dtype=np.uint8).reshape(height, width, 3)
|
|
350
353
|
frame = np.flip(frame, 0)
|
|
351
354
|
return frame
|
|
355
|
+
|
|
356
|
+
def depth_to_meters(self, depth_buffer):
|
|
357
|
+
"""
|
|
358
|
+
Convert normalized depth buffer values [0,1] to actual distances in meters.
|
|
359
|
+
|
|
360
|
+
Args:
|
|
361
|
+
depth_buffer: numpy array with depth values in range [0,1]
|
|
362
|
+
|
|
363
|
+
Returns:
|
|
364
|
+
numpy array with distances in meters
|
|
365
|
+
"""
|
|
366
|
+
# Get near and far clipping planes
|
|
367
|
+
near = self.dist * 0.001
|
|
368
|
+
far = self.dist * 10000.
|
|
369
|
+
|
|
370
|
+
# Convert from normalized depth [0,1] to linear depth in meters
|
|
371
|
+
# OpenGL depth buffer formula: depth = (1/z - 1/near) / (1/far - 1/near)
|
|
372
|
+
# Solving for z: z = 1 / (depth * (1/far - 1/near) + 1/near)
|
|
373
|
+
|
|
374
|
+
# Avoid division by zero for depth = 1.0 (far plane)
|
|
375
|
+
depth_clamped = np.clip(depth_buffer, 0.0, 0.999999)
|
|
376
|
+
|
|
377
|
+
linear_depth = 1.0 / (depth_clamped * (1.0/far - 1.0/near) + 1.0/near)
|
|
378
|
+
|
|
379
|
+
return linear_depth
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
def get_point(self, x, y):
|
|
383
|
+
self.makeCurrent() # Ensure the OpenGL context is current
|
|
384
|
+
width = self.current_width()
|
|
385
|
+
height = self.current_height()
|
|
386
|
+
|
|
387
|
+
gl_y = height - y - 1
|
|
388
|
+
z = glReadPixels(x, gl_y, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT)
|
|
389
|
+
z = np.frombuffer(z, dtype=np.float32)[0]
|
|
390
|
+
if z == 1.0 or z == 0.0:
|
|
391
|
+
return None
|
|
392
|
+
|
|
393
|
+
# Retrieve OpenGL matrices (column-major), convert to numpy arrays and transpose
|
|
394
|
+
view = np.array(glGetFloatv(GL_MODELVIEW_MATRIX), dtype=np.float32).reshape((4,4)).T
|
|
395
|
+
proj = np.array(glGetFloatv(GL_PROJECTION_MATRIX), dtype=np.float32).reshape((4,4)).T
|
|
396
|
+
|
|
397
|
+
# Convert screen (x, y, z) to normalized device coordinates (NDC)
|
|
398
|
+
ndc_x = (x / width) * 2.0 - 1.0
|
|
399
|
+
ndc_y = (gl_y / height) * 2.0 - 1.0
|
|
400
|
+
ndc_z = 2.0 * z - 1.0
|
|
401
|
+
ndc = np.array([ndc_x, ndc_y, ndc_z, 1.0], dtype=np.float32)
|
|
402
|
+
|
|
403
|
+
inv_projview = np.linalg.inv(proj @ view)
|
|
404
|
+
world_p = inv_projview @ ndc
|
|
405
|
+
world_p /= world_p[3]
|
|
406
|
+
return world_p[:3]
|
|
@@ -7,3 +7,4 @@ from q3dviewer.custom_items.grid_item import GridItem
|
|
|
7
7
|
from q3dviewer.custom_items.text_item import Text2DItem
|
|
8
8
|
from q3dviewer.custom_items.image_item import ImageItem
|
|
9
9
|
from q3dviewer.custom_items.line_item import LineItem
|
|
10
|
+
from q3dviewer.custom_items.text3d_item import Text3DItem
|
|
@@ -11,7 +11,22 @@ from q3dviewer.utils.cloud_io import save_pcd, save_ply, save_e57, save_las, loa
|
|
|
11
11
|
|
|
12
12
|
class CloudIOItem(CloudItem):
|
|
13
13
|
"""
|
|
14
|
-
|
|
14
|
+
A OpenGL point cloud item with input/output capabilities.
|
|
15
|
+
Attributes:
|
|
16
|
+
size (float): The size of each point. When `point_type` is 'PIXEL', this is the size in screen pixels.
|
|
17
|
+
When `point_type` is 'SQUARE' or 'SPHERE', this is the size in centimeters in 3D space.
|
|
18
|
+
alpha (float): The transparency of the points, in the range [0, 1], where 0 is fully transparent and 1 is fully opaque.
|
|
19
|
+
color_mode (str): The coloring mode for the points.
|
|
20
|
+
- 'FLAT': Single flat color for all points (uses the `color` attribute).
|
|
21
|
+
- 'I': Color by intensity.
|
|
22
|
+
- 'RGB': Per-point RGB color.
|
|
23
|
+
- 'GRAY': Per-point grayscale color.
|
|
24
|
+
color (str or tuple): The flat color to use when `color_mode` is 'FLAT'. Accepts any valid matplotlib color (e.g., 'red', '#FF4500', (1.0, 0.5, 0.0)).
|
|
25
|
+
point_type (str): The type/rendering style of each point:
|
|
26
|
+
- 'PIXEL': Draw each point as a square pixel on the screen.
|
|
27
|
+
- 'SQUARE': Draw each point as a square in 3D space.
|
|
28
|
+
- 'SPHERE': Draw each point as a sphere in 3D space.
|
|
29
|
+
depth_test (bool): Whether to enable depth testing. If True, points closer to the camera will appear in front of farther ones.
|
|
15
30
|
"""
|
|
16
31
|
def __init__(self, **kwargs):
|
|
17
32
|
super().__init__(**kwargs)
|
|
@@ -11,7 +11,7 @@ from OpenGL.GL import shaders
|
|
|
11
11
|
|
|
12
12
|
import threading
|
|
13
13
|
import os
|
|
14
|
-
from q3dviewer.Qt.QtWidgets import QLabel, QLineEdit, QDoubleSpinBox, QComboBox, QCheckBox
|
|
14
|
+
from q3dviewer.Qt.QtWidgets import QLabel, QLineEdit, QDoubleSpinBox, QSpinBox, QComboBox, QCheckBox
|
|
15
15
|
from q3dviewer.utils.range_slider import RangeSlider
|
|
16
16
|
from q3dviewer.utils import set_uniform
|
|
17
17
|
from q3dviewer.utils import text_to_rgba
|
|
@@ -20,6 +20,25 @@ from q3dviewer.Qt import Q3D_DEBUG
|
|
|
20
20
|
|
|
21
21
|
# draw points with color (x, y, z, color)
|
|
22
22
|
class CloudItem(BaseItem):
|
|
23
|
+
"""
|
|
24
|
+
A OpenGL point cloud item.
|
|
25
|
+
|
|
26
|
+
Attributes:
|
|
27
|
+
size (float): The size of each point. When `point_type` is 'PIXEL', this is the size in screen pixels.
|
|
28
|
+
When `point_type` is 'SQUARE' or 'SPHERE', this is the size in centimeters in 3D space.
|
|
29
|
+
alpha (float): The transparency of the points, in the range [0, 1], where 0 is fully transparent and 1 is fully opaque.
|
|
30
|
+
color_mode (str): The coloring mode for the points.
|
|
31
|
+
- 'FLAT': Single flat color for all points (uses the `color` attribute).
|
|
32
|
+
- 'I': Color by intensity.
|
|
33
|
+
- 'RGB': Per-point RGB color.
|
|
34
|
+
- 'GRAY': Per-point grayscale color.
|
|
35
|
+
color (str or tuple): The flat color to use when `color_mode` is 'FLAT'. Accepts any valid matplotlib color (e.g., 'red', '#FF4500', (1.0, 0.5, 0.0)).
|
|
36
|
+
point_type (str): The type/rendering style of each point:
|
|
37
|
+
- 'PIXEL': Draw each point as a square pixel on the screen.
|
|
38
|
+
- 'SQUARE': Draw each point as a square in 3D space.
|
|
39
|
+
- 'SPHERE': Draw each point as a sphere in 3D space.
|
|
40
|
+
depth_test (bool): Whether to enable depth testing. If True, points closer to the camera will appear in front of farther ones.
|
|
41
|
+
"""
|
|
23
42
|
def __init__(self, size, alpha,
|
|
24
43
|
color_mode='I',
|
|
25
44
|
color='white',
|
|
@@ -65,11 +84,10 @@ class CloudItem(BaseItem):
|
|
|
65
84
|
combo_ptype.currentIndexChanged.connect(self._on_point_type_selection)
|
|
66
85
|
layout.addWidget(combo_ptype)
|
|
67
86
|
|
|
68
|
-
self.box_size =
|
|
87
|
+
self.box_size = QSpinBox()
|
|
69
88
|
self.box_size.setPrefix("Size: ")
|
|
70
89
|
self.box_size.setSingleStep(1)
|
|
71
|
-
self.box_size.
|
|
72
|
-
self.box_size.setValue(self.size)
|
|
90
|
+
self.box_size.setValue(int(self.size))
|
|
73
91
|
self.box_size.setRange(0, 100)
|
|
74
92
|
self.box_size.valueChanged.connect(self.set_size)
|
|
75
93
|
self._on_point_type_selection(self.point_type_table[self.point_type])
|
|
@@ -147,17 +165,10 @@ class CloudItem(BaseItem):
|
|
|
147
165
|
self.point_type = list(self.point_type_table.keys())[index]
|
|
148
166
|
if self.point_type == 'PIXEL':
|
|
149
167
|
self.box_size.setPrefix("Set size (pixel): ")
|
|
150
|
-
self.box_size.setDecimals(0)
|
|
151
|
-
self.box_size.setSingleStep(1)
|
|
152
|
-
self.size = np.ceil(self.size)
|
|
153
|
-
self.box_size.setValue(self.size)
|
|
154
168
|
else:
|
|
155
|
-
self.box_size.setPrefix("Set size (
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
if self.size >= 1:
|
|
159
|
-
self.size = self.size * 0.01
|
|
160
|
-
self.box_size.setValue(self.size)
|
|
169
|
+
self.box_size.setPrefix("Set size (cm): ")
|
|
170
|
+
# self.size = 1
|
|
171
|
+
# self.box_size.setValue(self.size)
|
|
161
172
|
self.need_update_setting = True
|
|
162
173
|
|
|
163
174
|
def set_alpha(self, alpha):
|
|
@@ -226,7 +237,7 @@ class CloudItem(BaseItem):
|
|
|
226
237
|
set_uniform(self.program, float(self.vmax), 'vmax')
|
|
227
238
|
set_uniform(self.program, float(self.vmin), 'vmin')
|
|
228
239
|
set_uniform(self.program, float(self.alpha), 'alpha')
|
|
229
|
-
set_uniform(self.program,
|
|
240
|
+
set_uniform(self.program, int(self.size), 'point_size')
|
|
230
241
|
set_uniform(self.program, int(self.point_type_table[self.point_type]), 'point_type')
|
|
231
242
|
glUseProgram(0)
|
|
232
243
|
self.need_update_setting = False
|
|
@@ -296,10 +307,12 @@ class CloudItem(BaseItem):
|
|
|
296
307
|
glEnable(GL_BLEND)
|
|
297
308
|
glEnable(GL_PROGRAM_POINT_SIZE)
|
|
298
309
|
glEnable(GL_POINT_SPRITE)
|
|
299
|
-
|
|
300
|
-
|
|
310
|
+
glEnable(GL_DEPTH_TEST)
|
|
311
|
+
|
|
312
|
+
if not self.depth_test:
|
|
313
|
+
glDepthFunc(GL_ALWAYS) # Always pass depth test but still write depth
|
|
301
314
|
else:
|
|
302
|
-
|
|
315
|
+
glDepthFunc(GL_LESS) # Normal depth testing
|
|
303
316
|
|
|
304
317
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
|
|
305
318
|
glUseProgram(self.program)
|
|
@@ -329,5 +342,3 @@ class CloudItem(BaseItem):
|
|
|
329
342
|
glDisable(GL_POINT_SPRITE)
|
|
330
343
|
glDisable(GL_PROGRAM_POINT_SIZE)
|
|
331
344
|
glDisable(GL_BLEND)
|
|
332
|
-
if self.depth_test:
|
|
333
|
-
glDisable(GL_DEPTH_TEST) # Disable depth testing if it was enabled
|
|
@@ -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
|
+
|
|
7
|
+
from turtle import position
|
|
8
|
+
from q3dviewer.base_item import BaseItem
|
|
9
|
+
from OpenGL.GL import *
|
|
10
|
+
from OpenGL.GLUT import glutBitmapCharacter, glutInit
|
|
11
|
+
from OpenGL.GLUT import (
|
|
12
|
+
GLUT_BITMAP_HELVETICA_10,
|
|
13
|
+
GLUT_BITMAP_HELVETICA_12,
|
|
14
|
+
GLUT_BITMAP_HELVETICA_18,
|
|
15
|
+
# GLUT_BITMAP_TIMES_ROMAN_10,
|
|
16
|
+
GLUT_BITMAP_TIMES_ROMAN_24,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
def get_glut_font(font_size):
|
|
20
|
+
# Map requested font_size to a GLUT font object
|
|
21
|
+
if font_size <= 10:
|
|
22
|
+
return GLUT_BITMAP_HELVETICA_10
|
|
23
|
+
elif font_size <= 12:
|
|
24
|
+
return GLUT_BITMAP_HELVETICA_12
|
|
25
|
+
elif font_size <= 18:
|
|
26
|
+
return GLUT_BITMAP_HELVETICA_18
|
|
27
|
+
elif font_size <= 24:
|
|
28
|
+
return GLUT_BITMAP_TIMES_ROMAN_24
|
|
29
|
+
else:
|
|
30
|
+
return GLUT_BITMAP_TIMES_ROMAN_24 # Largest available
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# draw points with color (x, y, z, color)
|
|
34
|
+
class Text3DItem(BaseItem):
|
|
35
|
+
"""
|
|
36
|
+
A OpenGL 3d text and mark item.
|
|
37
|
+
|
|
38
|
+
Attributes:
|
|
39
|
+
data: list storing text data. Each element is a dict with keys:
|
|
40
|
+
'text': str, the text to display
|
|
41
|
+
'position': (x, y, z), the 3D position of the text
|
|
42
|
+
'color': (r, g, b, a), the color of the text
|
|
43
|
+
'font_size': float, the font size of the text
|
|
44
|
+
'point_size': float, size of point to draw at position (0 for no point)
|
|
45
|
+
'line_width': float, width of line to draw between points (0 for no line)
|
|
46
|
+
|
|
47
|
+
"""
|
|
48
|
+
def __init__(self, data=[]):
|
|
49
|
+
super().__init__()
|
|
50
|
+
self._disable_setting = True
|
|
51
|
+
self.data_list = data # map of {'text': str, 'position': (x,y,z), 'color': (r,g,b,a), 'size': float}
|
|
52
|
+
|
|
53
|
+
def add_setting(self, layout):
|
|
54
|
+
pass # No settings for Text3DItem
|
|
55
|
+
|
|
56
|
+
def set_data(self, data, append=False):
|
|
57
|
+
if not append:
|
|
58
|
+
self.data_list = []
|
|
59
|
+
self.data_list.extend(data)
|
|
60
|
+
|
|
61
|
+
def clear_data(self):
|
|
62
|
+
self.data_list = []
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def initialize_gl(self):
|
|
66
|
+
glutInit()
|
|
67
|
+
# super().initialize_gl()
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def paint(self):
|
|
71
|
+
for item in self.data_list:
|
|
72
|
+
# Handle both dictionary and string formats
|
|
73
|
+
if isinstance(item, dict):
|
|
74
|
+
text = item.get('text', '')
|
|
75
|
+
pos = item.get('position', (0.0, 0.0, 0.0))
|
|
76
|
+
font_size = item.get('font_size', 24)
|
|
77
|
+
color = item.get('color', (1.0, 1.0, 1.0, 1.0))
|
|
78
|
+
point_size = item.get('point_size', 0.0)
|
|
79
|
+
elif isinstance(item, str):
|
|
80
|
+
# If item is a string, treat it as text with default position and color
|
|
81
|
+
text = item
|
|
82
|
+
pos = (0.0, 0.0, 0.0)
|
|
83
|
+
color = (1.0, 1.0, 1.0, 1.0)
|
|
84
|
+
font_size = 24
|
|
85
|
+
point_size = 0.0
|
|
86
|
+
else:
|
|
87
|
+
print(f"Warning: Unsupported item type: {type(item)}")
|
|
88
|
+
continue
|
|
89
|
+
|
|
90
|
+
glColor4f(*color)
|
|
91
|
+
offset = 0.02
|
|
92
|
+
pos_text = (pos[0] + offset, pos[1]+ offset, pos[2]+ offset)
|
|
93
|
+
glRasterPos3f(*pos_text)
|
|
94
|
+
|
|
95
|
+
if point_size > 0.0:
|
|
96
|
+
# draw a point at the position
|
|
97
|
+
glPointSize(point_size)
|
|
98
|
+
glBegin(GL_POINTS)
|
|
99
|
+
glVertex3f(*pos)
|
|
100
|
+
glEnd()
|
|
101
|
+
font = get_glut_font(font_size)
|
|
102
|
+
for ch in text:
|
|
103
|
+
glutBitmapCharacter(font, ord(ch))
|
|
104
|
+
|
|
105
|
+
# draw lines between points
|
|
106
|
+
for i in range(len(self.data_list) - 1):
|
|
107
|
+
item1 = self.data_list[i]
|
|
108
|
+
item2 = self.data_list[i + 1]
|
|
109
|
+
if isinstance(item1, dict) and isinstance(item2, dict):
|
|
110
|
+
pos1 = item1.get('position', (0.0, 0.0, 0.0))
|
|
111
|
+
pos2 = item2.get('position', (0.0, 0.0, 0.0))
|
|
112
|
+
line_width = item1.get('line_width', 0.0)
|
|
113
|
+
color = item1.get('color', (1.0, 1.0, 1.0, 1.0))
|
|
114
|
+
if line_width > 0.0:
|
|
115
|
+
glColor4f(*color)
|
|
116
|
+
glLineWidth(line_width)
|
|
117
|
+
glBegin(GL_LINES)
|
|
118
|
+
glVertex3f(*pos1)
|
|
119
|
+
glVertex3f(*pos2)
|
|
120
|
+
glEnd()
|
|
@@ -9,7 +9,16 @@ from q3dviewer.utils import text_to_rgba
|
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
class Text2DItem(BaseItem):
|
|
12
|
-
"""
|
|
12
|
+
"""
|
|
13
|
+
A OpenGL 2D text item.
|
|
14
|
+
|
|
15
|
+
Attributes:
|
|
16
|
+
pos: (x, y), the 2D position of the text in pixels
|
|
17
|
+
text: str, the text to display
|
|
18
|
+
font: QFont, the font of the text
|
|
19
|
+
color: str, the color of the text in matplotlib format
|
|
20
|
+
size: int, the font size of the text
|
|
21
|
+
"""
|
|
13
22
|
|
|
14
23
|
def __init__(self, **kwds):
|
|
15
24
|
"""All keyword arguments are passed to set_data()"""
|
|
@@ -17,11 +26,17 @@ class Text2DItem(BaseItem):
|
|
|
17
26
|
self.pos = (20, 50)
|
|
18
27
|
self.text = ''
|
|
19
28
|
self.font = QtGui.QFont('Helvetica', 16)
|
|
20
|
-
|
|
21
|
-
self.rgb = text_to_rgba('w')
|
|
29
|
+
self.rgb = text_to_rgba('white')
|
|
22
30
|
if 'pos' in kwds:
|
|
23
31
|
self.pos = kwds['pos']
|
|
24
|
-
|
|
32
|
+
if 'text' in kwds:
|
|
33
|
+
self.text = kwds['text']
|
|
34
|
+
if 'font' in kwds:
|
|
35
|
+
self.font = kwds['font']
|
|
36
|
+
if 'color' in kwds:
|
|
37
|
+
self.set_color(kwds['color'])
|
|
38
|
+
if 'size' in kwds:
|
|
39
|
+
self.font.setPointSize(kwds['size'])
|
|
25
40
|
|
|
26
41
|
def set_data(self, **kwds):
|
|
27
42
|
args = ['pos', 'color', 'text', 'size', 'font']
|
q3dviewer/gau_io.py
CHANGED
|
@@ -1,168 +0,0 @@
|
|
|
1
|
-
import numpy as np
|
|
2
|
-
from plyfile import PlyData
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
def gsdata_type(sh_dim):
|
|
6
|
-
return [('pw', '<f4', (3,)),
|
|
7
|
-
('rot', '<f4', (4,)),
|
|
8
|
-
('scale', '<f4', (3,)),
|
|
9
|
-
('alpha', '<f4'),
|
|
10
|
-
('sh', '<f4', (sh_dim))]
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
def matrix_to_quaternion(matrices):
|
|
14
|
-
m00, m01, m02 = matrices[:, 0, 0], matrices[:, 0, 1], matrices[:, 0, 2]
|
|
15
|
-
m10, m11, m12 = matrices[:, 1, 0], matrices[:, 1, 1], matrices[:, 1, 2]
|
|
16
|
-
m20, m21, m22 = matrices[:, 2, 0], matrices[:, 2, 1], matrices[:, 2, 2]
|
|
17
|
-
t = 1 + m00 + m11 + m22
|
|
18
|
-
s = np.ones_like(m00)
|
|
19
|
-
w = np.ones_like(m00)
|
|
20
|
-
x = np.ones_like(m00)
|
|
21
|
-
y = np.ones_like(m00)
|
|
22
|
-
z = np.ones_like(m00)
|
|
23
|
-
|
|
24
|
-
t_positive = t > 0.0000001
|
|
25
|
-
s[t_positive] = 0.5 / np.sqrt(t[t_positive])
|
|
26
|
-
w[t_positive] = 0.25 / s[t_positive]
|
|
27
|
-
x[t_positive] = (m21[t_positive] - m12[t_positive]) * s[t_positive]
|
|
28
|
-
y[t_positive] = (m02[t_positive] - m20[t_positive]) * s[t_positive]
|
|
29
|
-
z[t_positive] = (m10[t_positive] - m01[t_positive]) * s[t_positive]
|
|
30
|
-
|
|
31
|
-
c1 = np.logical_and(m00 > m11, m00 > m22)
|
|
32
|
-
cond1 = np.logical_and(np.logical_not(t_positive), np.logical_and(m00 > m11, m00 > m22))
|
|
33
|
-
|
|
34
|
-
s[cond1] = 2.0 * np.sqrt(1.0 + m00[cond1] - m11[cond1] - m22[cond1])
|
|
35
|
-
w[cond1] = (m21[cond1] - m12[cond1]) / s[cond1]
|
|
36
|
-
x[cond1] = 0.25 * s[cond1]
|
|
37
|
-
y[cond1] = (m01[cond1] + m10[cond1]) / s[cond1]
|
|
38
|
-
z[cond1] = (m02[cond1] + m20[cond1]) / s[cond1]
|
|
39
|
-
|
|
40
|
-
c2 = np.logical_and(np.logical_not(c1), m11 > m22)
|
|
41
|
-
cond2 = np.logical_and(np.logical_not(t_positive), c2)
|
|
42
|
-
s[cond2] = 2.0 * np.sqrt(1.0 + m11[cond2] - m00[cond2] - m22[cond2])
|
|
43
|
-
w[cond2] = (m02[cond2] - m20[cond2]) / s[cond2]
|
|
44
|
-
x[cond2] = (m01[cond2] + m10[cond2]) / s[cond2]
|
|
45
|
-
y[cond2] = 0.25 * s[cond2]
|
|
46
|
-
z[cond2] = (m12[cond2] + m21[cond2]) / s[cond2]
|
|
47
|
-
|
|
48
|
-
c3 = np.logical_and(np.logical_not(c1), np.logical_not(c2))
|
|
49
|
-
cond3 = np.logical_and(np.logical_not(t_positive), c3)
|
|
50
|
-
s[cond3] = 2.0 * np.sqrt(1.0 + m22[cond3] - m00[cond3] - m11[cond3])
|
|
51
|
-
w[cond3] = (m10[cond3] - m01[cond3]) / s[cond3]
|
|
52
|
-
x[cond3] = (m02[cond3] + m20[cond3]) / s[cond3]
|
|
53
|
-
y[cond3] = (m12[cond3] + m21[cond3]) / s[cond3]
|
|
54
|
-
z[cond3] = 0.25 * s[cond3]
|
|
55
|
-
return np.array([w, x, y, z]).T
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
def load_ply(path, T=None):
|
|
59
|
-
plydata = PlyData.read(path)
|
|
60
|
-
pws = np.stack((np.asarray(plydata.elements[0]["x"]),
|
|
61
|
-
np.asarray(plydata.elements[0]["y"]),
|
|
62
|
-
np.asarray(plydata.elements[0]["z"])), axis=1)
|
|
63
|
-
|
|
64
|
-
alphas = np.asarray(plydata.elements[0]["opacity"])
|
|
65
|
-
alphas = 1/(1 + np.exp(-alphas))
|
|
66
|
-
|
|
67
|
-
scales = np.stack((np.asarray(plydata.elements[0]["scale_0"]),
|
|
68
|
-
np.asarray(plydata.elements[0]["scale_1"]),
|
|
69
|
-
np.asarray(plydata.elements[0]["scale_2"])), axis=1)
|
|
70
|
-
|
|
71
|
-
rots = np.stack((np.asarray(plydata.elements[0]["rot_0"]),
|
|
72
|
-
np.asarray(plydata.elements[0]["rot_1"]),
|
|
73
|
-
np.asarray(plydata.elements[0]["rot_2"]),
|
|
74
|
-
np.asarray(plydata.elements[0]["rot_3"])), axis=1)
|
|
75
|
-
|
|
76
|
-
rots /= np.linalg.norm(rots, axis=1)[:, np.newaxis]
|
|
77
|
-
|
|
78
|
-
sh_dim = len(plydata.elements[0][0])-14
|
|
79
|
-
shs = np.zeros([pws.shape[0], sh_dim])
|
|
80
|
-
shs[:, 0] = np.asarray(plydata.elements[0]["f_dc_0"])
|
|
81
|
-
shs[:, 1] = np.asarray(plydata.elements[0]["f_dc_1"])
|
|
82
|
-
shs[:, 2] = np.asarray(plydata.elements[0]["f_dc_2"])
|
|
83
|
-
|
|
84
|
-
sh_rest_dim = sh_dim - 3
|
|
85
|
-
for i in range(sh_rest_dim):
|
|
86
|
-
name = "f_rest_%d" % i
|
|
87
|
-
shs[:, 3 + i] = np.asarray(plydata.elements[0][name])
|
|
88
|
-
|
|
89
|
-
shs[:, 3:] = shs[:, 3:].reshape(-1, 3, sh_rest_dim//3).transpose([0, 2, 1]).reshape(-1, sh_rest_dim)
|
|
90
|
-
|
|
91
|
-
pws = pws.astype(np.float32)
|
|
92
|
-
rots = rots.astype(np.float32)
|
|
93
|
-
scales = np.exp(scales)
|
|
94
|
-
scales = scales.astype(np.float32)
|
|
95
|
-
alphas = alphas.astype(np.float32)
|
|
96
|
-
shs = shs.astype(np.float32)
|
|
97
|
-
|
|
98
|
-
dtypes = gsdata_type(sh_dim)
|
|
99
|
-
|
|
100
|
-
gs = np.rec.fromarrays(
|
|
101
|
-
[pws, rots, scales, alphas, shs], dtype=dtypes)
|
|
102
|
-
|
|
103
|
-
return gs
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
def rotate_gaussian(T, gs):
|
|
107
|
-
# Transform to world
|
|
108
|
-
pws = (T @ gs['pw'].T).T
|
|
109
|
-
w = gs['rot'][:, 0]
|
|
110
|
-
x = gs['rot'][:, 1]
|
|
111
|
-
y = gs['rot'][:, 2]
|
|
112
|
-
z = gs['rot'][:, 3]
|
|
113
|
-
R = np.array([
|
|
114
|
-
[1.0 - 2*(y**2 + z**2), 2*(x*y - z*w), 2*(x * z + y * w)],
|
|
115
|
-
[2*(x*y + z*w), 1.0 - 2*(x**2 + z**2), 2*(y*z - x*w)],
|
|
116
|
-
[2*(x*z - y*w), 2*(y*z + x*w), 1.0 - 2*(x**2 + y**2)]
|
|
117
|
-
]).transpose(2, 0, 1)
|
|
118
|
-
R_new = T @ R
|
|
119
|
-
rots = matrix_to_quaternion(R_new)
|
|
120
|
-
gs['pw'] = pws
|
|
121
|
-
gs['rot'] = rots
|
|
122
|
-
return gs
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
def load_gs(fn):
|
|
126
|
-
if fn.endswith('.ply'):
|
|
127
|
-
return load_ply(fn)
|
|
128
|
-
elif fn.endswith('.npy'):
|
|
129
|
-
return np.load(fn)
|
|
130
|
-
else:
|
|
131
|
-
print("%s is not a supported file." % fn)
|
|
132
|
-
exit(0)
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
def save_gs(fn, gs):
|
|
136
|
-
np.save(fn, gs)
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
def get_example_gs():
|
|
140
|
-
gs_data = np.array([[0., 0., 0., # xyz
|
|
141
|
-
1., 0., 0., 0., # rot
|
|
142
|
-
0.05, 0.05, 0.05, # size
|
|
143
|
-
1.,
|
|
144
|
-
1.772484, -1.772484, 1.772484],
|
|
145
|
-
[1., 0., 0.,
|
|
146
|
-
1., 0., 0., 0.,
|
|
147
|
-
0.2, 0.05, 0.05,
|
|
148
|
-
1.,
|
|
149
|
-
1.772484, -1.772484, -1.772484],
|
|
150
|
-
[0., 1., 0.,
|
|
151
|
-
1., 0., 0., 0.,
|
|
152
|
-
0.05, 0.2, 0.05,
|
|
153
|
-
1.,
|
|
154
|
-
-1.772484, 1.772484, -1.772484],
|
|
155
|
-
[0., 0., 1.,
|
|
156
|
-
1., 0., 0., 0.,
|
|
157
|
-
0.05, 0.05, 0.2,
|
|
158
|
-
1.,
|
|
159
|
-
-1.772484, -1.772484, 1.772484]
|
|
160
|
-
], dtype=np.float32)
|
|
161
|
-
dtypes = gsdata_type(3)
|
|
162
|
-
gs = np.frombuffer(gs_data.tobytes(), dtype=dtypes)
|
|
163
|
-
return gs
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
if __name__ == "__main__":
|
|
167
|
-
gs = load_gs("/home/liu/workspace/EasyGaussianSplatting/data/final.npy")
|
|
168
|
-
print(gs.shape)
|
q3dviewer/glwidget.py
CHANGED
|
@@ -9,6 +9,7 @@ from q3dviewer.Qt.QtGui import QKeyEvent
|
|
|
9
9
|
from q3dviewer.base_glwidget import BaseGLWidget
|
|
10
10
|
from q3dviewer.utils import text_to_rgba
|
|
11
11
|
|
|
12
|
+
|
|
12
13
|
class SettingWindow(QWidget):
|
|
13
14
|
def __init__(self):
|
|
14
15
|
super().__init__()
|
|
@@ -57,10 +58,11 @@ class GLWidget(BaseGLWidget):
|
|
|
57
58
|
super(GLWidget, self).__init__()
|
|
58
59
|
|
|
59
60
|
def keyPressEvent(self, ev: QKeyEvent):
|
|
60
|
-
if ev.key() == QtCore.Qt.Key_M: # setting
|
|
61
|
+
if ev.key() == QtCore.Qt.Key_M: # setting menu
|
|
61
62
|
print("Open setting windows")
|
|
62
|
-
self.open_setting_window()
|
|
63
|
-
|
|
63
|
+
self.open_setting_window()
|
|
64
|
+
else:
|
|
65
|
+
super().keyPressEvent(ev)
|
|
64
66
|
|
|
65
67
|
def on_followable_selection(self, index):
|
|
66
68
|
self.followed_name = self.followable_item_name[index]
|
|
@@ -126,3 +128,21 @@ class GLWidget(BaseGLWidget):
|
|
|
126
128
|
|
|
127
129
|
def change_show_center(self, state):
|
|
128
130
|
self.enable_show_center = state
|
|
131
|
+
|
|
132
|
+
def get_camera_pose(self):
|
|
133
|
+
"""Get current camera pose parameters"""
|
|
134
|
+
camera_pose = {
|
|
135
|
+
'center': self.center.tolist() if hasattr(self.center, 'tolist') else list(self.center),
|
|
136
|
+
'euler': self.euler.tolist() if hasattr(self.euler, 'tolist') else list(self.euler),
|
|
137
|
+
'distance': float(self.dist),
|
|
138
|
+
}
|
|
139
|
+
return camera_pose
|
|
140
|
+
|
|
141
|
+
def set_camera_pose(self, config):
|
|
142
|
+
"""Set camera pose from parameters"""
|
|
143
|
+
if 'center' in config and 'euler' in config and 'distance' in config:
|
|
144
|
+
self.set_center(config['center'])
|
|
145
|
+
self.set_euler(config['euler'])
|
|
146
|
+
self.set_dist(config['distance'])
|
|
147
|
+
else:
|
|
148
|
+
print("Invalid camera pose config")
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
+
#version 330 core
|
|
1
2
|
/*
|
|
2
3
|
Copyright 2024 Panasonic Advanced Technology Development Co.,Ltd. (Liu Yang)
|
|
3
4
|
Distributed under MIT license. See LICENSE for more information.
|
|
4
5
|
*/
|
|
5
6
|
|
|
6
|
-
#version 330 core
|
|
7
7
|
|
|
8
8
|
layout (location = 0) in vec3 position;
|
|
9
9
|
layout (location = 1) in uint value;
|
|
@@ -17,7 +17,7 @@ uniform float vmin = 0;
|
|
|
17
17
|
uniform float vmax = 255;
|
|
18
18
|
uniform float focal = 1000;
|
|
19
19
|
uniform int point_type = 0; // 0 pixel, 1 flat square, 2 sphere
|
|
20
|
-
uniform
|
|
20
|
+
uniform int point_size = 1; // World size for each point (pixel or cm)
|
|
21
21
|
out vec4 color;
|
|
22
22
|
|
|
23
23
|
vec3 getRainbowColor(uint value_raw) {
|
|
@@ -47,9 +47,9 @@ void main()
|
|
|
47
47
|
|
|
48
48
|
// Calculate point size in pixels based on distance
|
|
49
49
|
if (point_type == 0)
|
|
50
|
-
gl_PointSize =
|
|
50
|
+
gl_PointSize = float(point_size);
|
|
51
51
|
else
|
|
52
|
-
gl_PointSize = point_size / gl_Position.w * focal;
|
|
52
|
+
gl_PointSize = (float(point_size) * 0.01) / gl_Position.w * focal;
|
|
53
53
|
vec3 c = vec3(1.0, 1.0, 1.0);
|
|
54
54
|
if (color_mode == 1)
|
|
55
55
|
{
|
q3dviewer/shaders/gau_frag.glsl
CHANGED