q3dviewer 1.1.6__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.
Files changed (37) hide show
  1. q3dviewer/.vscode/c_cpp_properties.json +30 -0
  2. q3dviewer/.vscode/settings.json +10 -0
  3. q3dviewer/base_glwidget.py +56 -1
  4. q3dviewer/custom_items/__init__.py +1 -0
  5. q3dviewer/custom_items/cloud_io_item.py +16 -1
  6. q3dviewer/custom_items/cloud_item.py +37 -22
  7. q3dviewer/custom_items/text3d_item.py +120 -0
  8. q3dviewer/custom_items/text_item.py +19 -4
  9. q3dviewer/gau_io.py +0 -168
  10. q3dviewer/glwidget.py +23 -3
  11. q3dviewer/shaders/cloud_frag.glsl +1 -1
  12. q3dviewer/shaders/cloud_vert.glsl +13 -4
  13. q3dviewer/shaders/gau_frag.glsl +1 -1
  14. q3dviewer/shaders/gau_prep.glsl +1 -1
  15. q3dviewer/shaders/gau_vert.glsl +1 -1
  16. q3dviewer/shaders/sort_by_key.glsl +1 -1
  17. q3dviewer/test/test_interpolation.py +58 -0
  18. q3dviewer/test/test_rendering.py +73 -0
  19. q3dviewer/tools/cinematographer.py +367 -0
  20. q3dviewer/tools/cloud_viewer.py +79 -3
  21. q3dviewer/tools/example_viewer.py +8 -28
  22. q3dviewer/tools/film_maker.py +1 -1
  23. q3dviewer/tools/lidar_cam_calib.py +10 -11
  24. q3dviewer/utils/convert_ros_msg.py +49 -6
  25. q3dviewer/viewer.py +4 -1
  26. {q3dviewer-1.1.6.dist-info → q3dviewer-1.1.8.dist-info}/METADATA +4 -3
  27. q3dviewer-1.1.8.dist-info/RECORD +50 -0
  28. {q3dviewer-1.1.6.dist-info → q3dviewer-1.1.8.dist-info}/WHEEL +1 -1
  29. q3dviewer/basic_window.py +0 -228
  30. q3dviewer/cloud_viewer.py +0 -74
  31. q3dviewer/custom_items/camera_frame_item.py +0 -173
  32. q3dviewer/custom_items/trajectory_item.py +0 -79
  33. q3dviewer/utils.py +0 -71
  34. q3dviewer-1.1.6.dist-info/RECORD +0 -49
  35. {q3dviewer-1.1.6.dist-info → q3dviewer-1.1.8.dist-info}/LICENSE +0 -0
  36. {q3dviewer-1.1.6.dist-info → q3dviewer-1.1.8.dist-info}/entry_points.txt +0 -0
  37. {q3dviewer-1.1.6.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
+ }
@@ -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
- add save/load function to CloudItem
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',
@@ -40,7 +59,7 @@ class CloudItem(BaseItem):
40
59
  except ValueError:
41
60
  print(f"Invalid color: {color}, please use matplotlib color format")
42
61
  exit(1)
43
- self.mode_table = {'FLAT': 0, 'I': 1, 'RGB': 2}
62
+ self.mode_table = {'FLAT': 0, 'I': 1, 'RGB': 2, 'GRAY': 3}
44
63
  self.point_type_table = {'PIXEL': 0, 'SQUARE': 1, 'SPHERE': 2}
45
64
  self.color_mode = self.mode_table[color_mode]
46
65
  self.CAPACITY = 10000000 # 10MB * 3 (x,y,z, color) * 4
@@ -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 = QDoubleSpinBox()
87
+ self.box_size = QSpinBox()
69
88
  self.box_size.setPrefix("Size: ")
70
89
  self.box_size.setSingleStep(1)
71
- self.box_size.setDecimals(0)
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])
@@ -89,6 +107,7 @@ class CloudItem(BaseItem):
89
107
  self.combo_color.addItem("flat color")
90
108
  self.combo_color.addItem("intensity")
91
109
  self.combo_color.addItem("RGB")
110
+ self.combo_color.addItem("gray")
92
111
  self.combo_color.setCurrentIndex(self.color_mode)
93
112
  self.combo_color.currentIndexChanged.connect(self._on_color_mode)
94
113
  layout.addWidget(self.combo_color)
@@ -127,10 +146,13 @@ class CloudItem(BaseItem):
127
146
  self.edit_rgb.show()
128
147
  elif (index == self.mode_table['I']): # flat color
129
148
  self.slider_v.show()
149
+ elif (index == self.mode_table['GRAY']): # flat color
150
+ self.slider_v.show()
151
+
130
152
  self.need_update_setting = True
131
153
 
132
154
  def set_color_mode(self, color_mode):
133
- if color_mode in {'FLAT', 'RGB', 'I'}:
155
+ if color_mode in {'FLAT', 'RGB', 'I', 'GRAY'}:
134
156
  try:
135
157
  self.combo_color.setCurrentIndex(self.mode_table[color_mode])
136
158
  except:
@@ -143,17 +165,10 @@ class CloudItem(BaseItem):
143
165
  self.point_type = list(self.point_type_table.keys())[index]
144
166
  if self.point_type == 'PIXEL':
145
167
  self.box_size.setPrefix("Set size (pixel): ")
146
- self.box_size.setDecimals(0)
147
- self.box_size.setSingleStep(1)
148
- self.size = np.ceil(self.size)
149
- self.box_size.setValue(self.size)
150
168
  else:
151
- self.box_size.setPrefix("Set size (meter): ")
152
- self.box_size.setDecimals(2)
153
- self.box_size.setSingleStep(0.01)
154
- if self.size >= 1:
155
- self.size = self.size * 0.01
156
- 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)
157
172
  self.need_update_setting = True
158
173
 
159
174
  def set_alpha(self, alpha):
@@ -222,7 +237,7 @@ class CloudItem(BaseItem):
222
237
  set_uniform(self.program, float(self.vmax), 'vmax')
223
238
  set_uniform(self.program, float(self.vmin), 'vmin')
224
239
  set_uniform(self.program, float(self.alpha), 'alpha')
225
- set_uniform(self.program, float(self.size), 'point_size')
240
+ set_uniform(self.program, int(self.size), 'point_size')
226
241
  set_uniform(self.program, int(self.point_type_table[self.point_type]), 'point_type')
227
242
  glUseProgram(0)
228
243
  self.need_update_setting = False
@@ -292,10 +307,12 @@ class CloudItem(BaseItem):
292
307
  glEnable(GL_BLEND)
293
308
  glEnable(GL_PROGRAM_POINT_SIZE)
294
309
  glEnable(GL_POINT_SPRITE)
295
- if self.depth_test:
296
- glEnable(GL_DEPTH_TEST)
310
+ glEnable(GL_DEPTH_TEST)
311
+
312
+ if not self.depth_test:
313
+ glDepthFunc(GL_ALWAYS) # Always pass depth test but still write depth
297
314
  else:
298
- glDisable(GL_DEPTH_TEST)
315
+ glDepthFunc(GL_LESS) # Normal depth testing
299
316
 
300
317
  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
301
318
  glUseProgram(self.program)
@@ -325,5 +342,3 @@ class CloudItem(BaseItem):
325
342
  glDisable(GL_POINT_SPRITE)
326
343
  glDisable(GL_PROGRAM_POINT_SIZE)
327
344
  glDisable(GL_BLEND)
328
- if self.depth_test:
329
- 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
- """Draws text over opengl 3D."""
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
- self.set_data(**kwds)
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 meun
61
+ if ev.key() == QtCore.Qt.Key_M: # setting menu
61
62
  print("Open setting windows")
62
- self.open_setting_window()
63
- super().keyPressEvent(ev)
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
  uniform int point_type;
9
9