q3dviewer 1.1.7__py3-none-any.whl → 1.1.9__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/Qt/__init__.py +1 -0
- q3dviewer/base_glwidget.py +83 -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 +152 -0
- q3dviewer/custom_items/text_item.py +19 -4
- 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/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.9.dist-info}/METADATA +12 -9
- q3dviewer-1.1.9.dist-info/RECORD +44 -0
- {q3dviewer-1.1.7.dist-info → q3dviewer-1.1.9.dist-info}/WHEEL +1 -1
- {q3dviewer-1.1.7.dist-info → q3dviewer-1.1.9.dist-info}/entry_points.txt +1 -0
- 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/gau_io.py +0 -168
- q3dviewer/utils.py +0 -71
- q3dviewer-1.1.7.dist-info/RECORD +0 -49
- {q3dviewer-1.1.7.dist-info → q3dviewer-1.1.9.dist-info}/LICENSE +0 -0
- {q3dviewer-1.1.7.dist-info → q3dviewer-1.1.9.dist-info}/top_level.txt +0 -0
q3dviewer/Qt/__init__.py
CHANGED
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,82 @@ 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, x0, y0, radius=5):
|
|
383
|
+
"""
|
|
384
|
+
Get the 3D point in world coordinates corresponding to the given
|
|
385
|
+
screen coordinates (x0, y0). It searches within a radius around the
|
|
386
|
+
given pixel to find a valid depth value.
|
|
387
|
+
"""
|
|
388
|
+
self.makeCurrent() # Ensure the OpenGL context is current
|
|
389
|
+
width = self.current_width()
|
|
390
|
+
height = self.current_height()
|
|
391
|
+
|
|
392
|
+
# Scale mouse coordinates by device pixel ratio for PySide6 compatibility
|
|
393
|
+
pixel_ratio = self.devicePixelRatioF()
|
|
394
|
+
|
|
395
|
+
points = []
|
|
396
|
+
for dx in range(-radius, radius + 1):
|
|
397
|
+
for dy in range(-radius, radius + 1):
|
|
398
|
+
if dx * dx + dy * dy <= radius * radius:
|
|
399
|
+
points.append((x0 + dx, y0 + dy))
|
|
400
|
+
points = sorted(points, key=lambda p: (p[0]-x0)**2 + (p[1]-y0)**2)
|
|
401
|
+
|
|
402
|
+
print("points to check:", len(points))
|
|
403
|
+
|
|
404
|
+
gl_y0 = height - y0 - 1
|
|
405
|
+
z = 1.0
|
|
406
|
+
for x, y in points:
|
|
407
|
+
x = int(x * pixel_ratio)
|
|
408
|
+
y = int(y * pixel_ratio)
|
|
409
|
+
|
|
410
|
+
gl_y = height - y - 1
|
|
411
|
+
z = glReadPixels(x, gl_y, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT)
|
|
412
|
+
z = np.frombuffer(z, dtype=np.float32)[0]
|
|
413
|
+
if z != 1.0 and z != 0.0:
|
|
414
|
+
print("dist to p:", np.sqrt((x - x0)**2 + (y - y0)**2))
|
|
415
|
+
break
|
|
416
|
+
|
|
417
|
+
if z == 1.0 or z == 0.0:
|
|
418
|
+
return None
|
|
419
|
+
|
|
420
|
+
# Retrieve OpenGL matrices (column-major), convert to numpy arrays and transpose
|
|
421
|
+
view = np.array(glGetFloatv(GL_MODELVIEW_MATRIX), dtype=np.float32).reshape((4,4)).T
|
|
422
|
+
proj = np.array(glGetFloatv(GL_PROJECTION_MATRIX), dtype=np.float32).reshape((4,4)).T
|
|
423
|
+
|
|
424
|
+
# Convert screen (x, y, z) to normalized device coordinates (NDC)
|
|
425
|
+
ndc_x = (x0 / width) * 2.0 - 1.0
|
|
426
|
+
ndc_y = (gl_y0 / height) * 2.0 - 1.0
|
|
427
|
+
ndc_z = 2.0 * z - 1.0
|
|
428
|
+
ndc = np.array([ndc_x, ndc_y, ndc_z, 1.0], dtype=np.float32)
|
|
429
|
+
|
|
430
|
+
inv_projview = np.linalg.inv(proj @ view)
|
|
431
|
+
world_p = inv_projview @ ndc
|
|
432
|
+
world_p /= world_p[3]
|
|
433
|
+
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,152 @@
|
|
|
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 q3dviewer.base_item import BaseItem
|
|
8
|
+
from OpenGL.GL import *
|
|
9
|
+
import numpy as np
|
|
10
|
+
|
|
11
|
+
try:
|
|
12
|
+
from OpenGL.GLUT import glutBitmapCharacter, glutInit
|
|
13
|
+
from OpenGL.GLUT import (
|
|
14
|
+
GLUT_BITMAP_HELVETICA_10,
|
|
15
|
+
GLUT_BITMAP_HELVETICA_12,
|
|
16
|
+
GLUT_BITMAP_HELVETICA_18,
|
|
17
|
+
GLUT_BITMAP_TIMES_ROMAN_24,
|
|
18
|
+
)
|
|
19
|
+
GLUT_AVAILABLE = True
|
|
20
|
+
except ImportError:
|
|
21
|
+
GLUT_AVAILABLE = False
|
|
22
|
+
print("Warning: GLUT not available. Text will not be rendered.")
|
|
23
|
+
|
|
24
|
+
def get_glut_font(font_size):
|
|
25
|
+
"""Get GLUT font based on size, only if GLUT is available"""
|
|
26
|
+
if not GLUT_AVAILABLE:
|
|
27
|
+
return None
|
|
28
|
+
|
|
29
|
+
# Map requested font_size to a GLUT font object
|
|
30
|
+
if font_size <= 10:
|
|
31
|
+
return GLUT_BITMAP_HELVETICA_10
|
|
32
|
+
elif font_size <= 12:
|
|
33
|
+
return GLUT_BITMAP_HELVETICA_12
|
|
34
|
+
elif font_size <= 18:
|
|
35
|
+
return GLUT_BITMAP_HELVETICA_18
|
|
36
|
+
elif font_size <= 24:
|
|
37
|
+
return GLUT_BITMAP_TIMES_ROMAN_24
|
|
38
|
+
else:
|
|
39
|
+
return GLUT_BITMAP_TIMES_ROMAN_24 # Largest available
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
# draw points with color (x, y, z, color)
|
|
43
|
+
class Text3DItem(BaseItem):
|
|
44
|
+
"""
|
|
45
|
+
A OpenGL 3d text and mark item.
|
|
46
|
+
|
|
47
|
+
Attributes:
|
|
48
|
+
data: list storing text data. Each element is a dict with keys:
|
|
49
|
+
'text': str, the text to display
|
|
50
|
+
'position': (x, y, z), the 3D position of the text
|
|
51
|
+
'color': (r, g, b, a), the color of the text
|
|
52
|
+
'font_size': float, the font size of the text
|
|
53
|
+
'point_size': float, size of point to draw at position (0 for no point)
|
|
54
|
+
'line_width': float, width of line to draw between points (0 for no line)
|
|
55
|
+
|
|
56
|
+
"""
|
|
57
|
+
def __init__(self, data=[]):
|
|
58
|
+
super().__init__()
|
|
59
|
+
self._disable_setting = True
|
|
60
|
+
self.data_list = data # map of {'text': str, 'position': (x,y,z), 'color': (r,g,b,a), 'size': float}
|
|
61
|
+
|
|
62
|
+
def add_setting(self, layout):
|
|
63
|
+
pass # No settings for Text3DItem
|
|
64
|
+
|
|
65
|
+
def set_data(self, data, append=False):
|
|
66
|
+
if not append:
|
|
67
|
+
self.data_list = []
|
|
68
|
+
self.data_list.extend(data)
|
|
69
|
+
|
|
70
|
+
def clear_data(self):
|
|
71
|
+
self.data_list = []
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def initialize_gl(self):
|
|
75
|
+
"""Initialize OpenGL resources, with GLUT fallback handling"""
|
|
76
|
+
global GLUT_AVAILABLE
|
|
77
|
+
if GLUT_AVAILABLE:
|
|
78
|
+
try:
|
|
79
|
+
# Check if glutInit is actually callable before calling it
|
|
80
|
+
if hasattr(glutInit, '__call__') and bool(glutInit):
|
|
81
|
+
glutInit()
|
|
82
|
+
else:
|
|
83
|
+
print("Warning: glutInit not callable, using fallback text rendering")
|
|
84
|
+
GLUT_AVAILABLE = False
|
|
85
|
+
except Exception as e:
|
|
86
|
+
print(f"Warning: GLUT initialization failed: {e}. Using fallback text rendering.")
|
|
87
|
+
GLUT_AVAILABLE = False
|
|
88
|
+
# super().initialize_gl()
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def paint(self):
|
|
92
|
+
for item in self.data_list:
|
|
93
|
+
# Handle both dictionary and string formats
|
|
94
|
+
if isinstance(item, dict):
|
|
95
|
+
text = item.get('text', '')
|
|
96
|
+
pos = item.get('position', (0.0, 0.0, 0.0))
|
|
97
|
+
font_size = item.get('font_size', 24)
|
|
98
|
+
color = item.get('color', (1.0, 1.0, 1.0, 1.0))
|
|
99
|
+
point_size = item.get('point_size', 0.0)
|
|
100
|
+
elif isinstance(item, str):
|
|
101
|
+
# If item is a string, treat it as text with default position and color
|
|
102
|
+
text = item
|
|
103
|
+
pos = (0.0, 0.0, 0.0)
|
|
104
|
+
color = (1.0, 1.0, 1.0, 1.0)
|
|
105
|
+
font_size = 24
|
|
106
|
+
point_size = 0.0
|
|
107
|
+
else:
|
|
108
|
+
print(f"Warning: Unsupported item type: {type(item)}")
|
|
109
|
+
continue
|
|
110
|
+
|
|
111
|
+
# Convert numpy array to tuple if needed
|
|
112
|
+
if isinstance(pos, np.ndarray):
|
|
113
|
+
pos = tuple(pos.astype(float))
|
|
114
|
+
|
|
115
|
+
glColor4f(*color)
|
|
116
|
+
|
|
117
|
+
if point_size > 0.0:
|
|
118
|
+
# draw a point at the position
|
|
119
|
+
glPointSize(point_size)
|
|
120
|
+
glBegin(GL_POINTS)
|
|
121
|
+
glVertex3f(*pos)
|
|
122
|
+
glEnd()
|
|
123
|
+
|
|
124
|
+
# Text rendering with GLUT fallback
|
|
125
|
+
if GLUT_AVAILABLE:
|
|
126
|
+
offset = 0.02
|
|
127
|
+
pos_text = (pos[0] + offset, pos[1] + offset, pos[2] + offset)
|
|
128
|
+
glRasterPos3f(*pos_text)
|
|
129
|
+
font = get_glut_font(font_size)
|
|
130
|
+
if font is not None:
|
|
131
|
+
try:
|
|
132
|
+
for ch in text:
|
|
133
|
+
glutBitmapCharacter(font, ord(ch))
|
|
134
|
+
except Exception as e:
|
|
135
|
+
print(f"Error rendering text '{text}': {e}")
|
|
136
|
+
|
|
137
|
+
# draw lines between points
|
|
138
|
+
for i in range(len(self.data_list) - 1):
|
|
139
|
+
item1 = self.data_list[i]
|
|
140
|
+
item2 = self.data_list[i + 1]
|
|
141
|
+
if isinstance(item1, dict) and isinstance(item2, dict):
|
|
142
|
+
pos1 = item1.get('position', (0.0, 0.0, 0.0))
|
|
143
|
+
pos2 = item2.get('position', (0.0, 0.0, 0.0))
|
|
144
|
+
line_width = item1.get('line_width', 0.0)
|
|
145
|
+
color = item1.get('color', (1.0, 1.0, 1.0, 1.0))
|
|
146
|
+
if line_width > 0.0:
|
|
147
|
+
glColor4f(*color)
|
|
148
|
+
glLineWidth(line_width)
|
|
149
|
+
glBegin(GL_LINES)
|
|
150
|
+
glVertex3f(*pos1)
|
|
151
|
+
glVertex3f(*pos2)
|
|
152
|
+
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/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
q3dviewer/shaders/gau_prep.glsl
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
+
#version 430 core
|
|
1
2
|
/*
|
|
2
3
|
We proprocess gaussian using compute shader.
|
|
3
4
|
this file is modified from GaussianSplattingViewer licensed under the MIT License.
|
|
4
5
|
see https://github.com/limacv/GaussianSplattingViewer/blob/main/shaders/gau_vert.glsl
|
|
5
6
|
*/
|
|
6
7
|
|
|
7
|
-
#version 430 core
|
|
8
8
|
|
|
9
9
|
layout(local_size_x = 256, local_size_y = 1, local_size_z = 1) in;
|
|
10
10
|
|
q3dviewer/shaders/gau_vert.glsl
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
#version 430 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.
|
|
@@ -5,7 +6,6 @@ Distributed under MIT license. See LICENSE for more information.
|
|
|
5
6
|
|
|
6
7
|
//draw 2d gaussian using proprocess data.
|
|
7
8
|
|
|
8
|
-
#version 430 core
|
|
9
9
|
|
|
10
10
|
#define OFFSET_PREP_U 0
|
|
11
11
|
#define OFFSET_PREP_COVINV 3
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
#version 430 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.
|
|
@@ -8,7 +9,6 @@ opengl compute shader.
|
|
|
8
9
|
sort guassian by depth using bitonic sorter
|
|
9
10
|
*/
|
|
10
11
|
|
|
11
|
-
#version 430 core
|
|
12
12
|
|
|
13
13
|
layout(local_size_x = 256, local_size_y = 1, local_size_z = 1) in;
|
|
14
14
|
|