q3dviewer 1.1.3__py3-none-any.whl → 1.1.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.
@@ -61,18 +61,28 @@ class CloudIOItem(CloudItem):
61
61
  def load(self, file, append=False):
62
62
  # print("Try to load %s ..." % file)
63
63
  if file.endswith(".pcd"):
64
- cloud, color_mode = load_pcd(file)
64
+ cloud = load_pcd(file)
65
65
  elif file.endswith(".ply"):
66
- cloud, color_mode = load_ply(file)
66
+ cloud = load_ply(file)
67
67
  elif file.endswith(".e57"):
68
- cloud, color_mode = load_e57(file)
68
+ cloud = load_e57(file)
69
69
  elif file.endswith(".las"):
70
- cloud, color_mode = load_las(file)
70
+ cloud = load_las(file)
71
71
  else:
72
72
  print("Not supported file type.")
73
73
  return
74
74
  self.set_data(data=cloud, append=append)
75
- self.set_color_mode(color_mode)
75
+
76
+ has_intensity = cloud['irgb'][0] & 0xff000000 > 0
77
+ has_rgb = cloud['irgb'][0] & 0x00ffffff > 0
78
+
79
+ if has_rgb:
80
+ self.set_color_mode('RGB')
81
+ elif has_intensity:
82
+ self.set_color_mode('I')
83
+ else:
84
+ self.set_color_mode('FLAT')
85
+
76
86
  return cloud
77
87
 
78
88
  def set_path(self, path):
@@ -14,15 +14,13 @@ from PySide6.QtWidgets import QLabel, QLineEdit, QDoubleSpinBox, \
14
14
  from OpenGL.GL import shaders
15
15
  from q3dviewer.utils import *
16
16
  from q3dviewer.utils.range_slider import RangeSlider
17
- from PySide6.QtCore import QRegularExpression
18
- from PySide6.QtGui import QRegularExpressionValidator
19
17
 
20
18
 
21
19
  # draw points with color (x, y, z, color)
22
20
  class CloudItem(BaseItem):
23
21
  def __init__(self, size, alpha,
24
22
  color_mode='I',
25
- color='#ffffff',
23
+ color='white',
26
24
  point_type='PIXEL',
27
25
  depth_test=False):
28
26
  super().__init__()
@@ -34,7 +32,12 @@ class CloudItem(BaseItem):
34
32
  self.point_type = point_type
35
33
  self.mutex = threading.Lock()
36
34
  self.data_type = [('xyz', '<f4', (3,)), ('irgb', '<u4')]
37
- self.flat_rgb = int(color[1:], 16)
35
+ self.color = color
36
+ try:
37
+ self.flat_rgb = text_to_rgba(color, flat=True)
38
+ except ValueError:
39
+ print(f"Invalid color: {color}, please use matplotlib color format")
40
+ exit(1)
38
41
  self.mode_table = {'FLAT': 0, 'I': 1, 'RGB': 2}
39
42
  self.point_type_table = {'PIXEL': 0, 'SQUARE': 1, 'SPHERE': 2}
40
43
  self.color_mode = self.mode_table[color_mode]
@@ -89,14 +92,12 @@ class CloudItem(BaseItem):
89
92
  layout.addWidget(self.combo_color)
90
93
 
91
94
  label_rgb = QLabel("Color:")
95
+ label_rgb.setToolTip("Use hex color, i.e. #FF4500, or named color, i.e. 'red'")
92
96
  layout.addWidget(label_rgb)
93
97
  self.edit_rgb = QLineEdit()
94
- self.edit_rgb.setToolTip("Hex number, i.e. #FF4500")
95
- self.edit_rgb.setText(f"#{self.flat_rgb:06x}")
98
+ self.edit_rgb.setToolTip("Use hex color, i.e. #FF4500, or named color, i.e. 'red'")
99
+ self.edit_rgb.setText(self.color)
96
100
  self.edit_rgb.textChanged.connect(self._on_color)
97
- regex = QRegularExpression(r"^#[0-9A-Fa-f]{6}$")
98
- validator = QRegularExpressionValidator(regex)
99
- self.edit_rgb.setValidator(validator)
100
101
  layout.addWidget(self.edit_rgb)
101
102
 
102
103
  self.slider_v = RangeSlider()
@@ -108,6 +109,7 @@ class CloudItem(BaseItem):
108
109
  "Show front points first (Depth Test)")
109
110
  self.checkbox_depth_test.setChecked(self.depth_test)
110
111
  self.checkbox_depth_test.stateChanged.connect(self.set_depthtest)
112
+ self._on_color_mode(self.color_mode)
111
113
  layout.addWidget(self.checkbox_depth_test)
112
114
 
113
115
  def _on_range(self, lower, upper):
@@ -129,10 +131,9 @@ class CloudItem(BaseItem):
129
131
  if color_mode in {'FLAT', 'RGB', 'I'}:
130
132
  try:
131
133
  self.combo_color.setCurrentIndex(self.mode_table[color_mode])
132
- except RuntimeError:
133
- pass
134
- except ValueError:
135
- pass
134
+ except:
135
+ self.color_mode = self.mode_table[color_mode]
136
+ self.need_update_setting = True
136
137
  else:
137
138
  print(f"Invalid color mode: {color_mode}")
138
139
 
@@ -164,11 +165,10 @@ class CloudItem(BaseItem):
164
165
 
165
166
  def _on_color(self, color):
166
167
  try:
167
- flat_rgb = int(color[1:], 16)
168
- self.flat_rgb = flat_rgb
168
+ self.flat_rgb = text_to_rgba(color, flat=True)
169
169
  self.need_update_setting = True
170
170
  except ValueError:
171
- pass
171
+ print(f"Invalid color: {color}, please use matplotlib color format")
172
172
 
173
173
  def set_size(self, size):
174
174
  self.size = size
@@ -162,13 +162,9 @@ class FrameItem(BaseItem):
162
162
  self.need_updating = False
163
163
 
164
164
  def set_color(self, color):
165
- if isinstance(color, str):
166
- self.rgba = hex_to_rgba(color)
167
- elif isinstance(color, list):
168
- self.rgba = color
169
- elif isinstance(color, tuple):
170
- self.rgba = list(color)
171
- else:
165
+ try:
166
+ self.rgba = text_to_rgba(color)
167
+ except ValueError:
172
168
  raise ValueError("Invalid color format")
173
169
 
174
170
  def set_line_width(self, width):
@@ -6,10 +6,8 @@ Distributed under MIT license. See LICENSE for more information.
6
6
  from q3dviewer.base_item import BaseItem
7
7
  from OpenGL.GL import *
8
8
  from PySide6.QtWidgets import QLabel, QDoubleSpinBox, QLineEdit
9
- from PySide6.QtCore import QRegularExpression
10
- from PySide6.QtGui import QRegularExpressionValidator
11
9
  import numpy as np
12
- from q3dviewer.utils.maths import hex_to_rgba
10
+ from q3dviewer.utils.maths import text_to_rgba
13
11
 
14
12
 
15
13
  class GridItem(BaseItem):
@@ -24,9 +22,9 @@ class GridItem(BaseItem):
24
22
 
25
23
  def set_color(self, color):
26
24
  try:
27
- self.rgba = hex_to_rgba(color)
25
+ self.rgba = text_to_rgba(color)
28
26
  except ValueError:
29
- pass
27
+ raise ValueError("Invalid color format. Use hex format like '#RRGGBB' or '#RRGGBBAA'.")
30
28
 
31
29
  def generate_grid_vertices(self):
32
30
  vertices = []
@@ -41,16 +39,13 @@ class GridItem(BaseItem):
41
39
  def initialize_gl(self):
42
40
  self.vao = glGenVertexArrays(1)
43
41
  vbo = glGenBuffers(1)
44
-
45
42
  glBindVertexArray(self.vao)
46
-
47
43
  glBindBuffer(GL_ARRAY_BUFFER, vbo)
48
44
  glBufferData(GL_ARRAY_BUFFER, self.vertices.nbytes, self.vertices, GL_STATIC_DRAW)
49
-
50
45
  glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, ctypes.c_void_p(0))
51
46
  glEnableVertexAttribArray(0)
52
-
53
47
  glBindVertexArray(0)
48
+ glBindBuffer(GL_ARRAY_BUFFER, 0)
54
49
 
55
50
  def add_setting(self, layout):
56
51
  spinbox_size = QDoubleSpinBox()
@@ -73,6 +68,7 @@ class GridItem(BaseItem):
73
68
  spinbox_offset_x.setPrefix("Offset X: ")
74
69
  spinbox_offset_x.setSingleStep(0.1)
75
70
  spinbox_offset_x.setValue(self.offset[0])
71
+ spinbox_offset_x.setRange(-1000, 1000)
76
72
  spinbox_offset_x.valueChanged.connect(self._on_offset_x)
77
73
  layout.addWidget(spinbox_offset_x)
78
74
 
@@ -80,6 +76,7 @@ class GridItem(BaseItem):
80
76
  spinbox_offset_y.setPrefix("Offset Y: ")
81
77
  spinbox_offset_y.setSingleStep(0.1)
82
78
  spinbox_offset_y.setValue(self.offset[1])
79
+ spinbox_offset_y.setRange(-1000, 1000)
83
80
  spinbox_offset_y.valueChanged.connect(self._on_offset_y)
84
81
  layout.addWidget(spinbox_offset_y)
85
82
 
@@ -87,6 +84,7 @@ class GridItem(BaseItem):
87
84
  spinbox_offset_z.setPrefix("Offset Z: ")
88
85
  spinbox_offset_z.setSingleStep(0.1)
89
86
  spinbox_offset_z.setValue(self.offset[2])
87
+ spinbox_offset_z.setRange(-1000, 1000)
90
88
  spinbox_offset_z.valueChanged.connect(self._on_offset_z)
91
89
  layout.addWidget(spinbox_offset_z)
92
90
 
@@ -8,13 +8,16 @@ from OpenGL.GL import *
8
8
  import numpy as np
9
9
  import threading
10
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
11
+ from q3dviewer.utils.maths import text_to_rgba
14
12
 
15
13
 
16
14
  class LineItem(BaseItem):
17
15
  def __init__(self, width=1, color='#00ff00', line_type='LINE_STRIP'):
16
+ """
17
+ line_type: 'LINE_STRIP' or 'LINES'
18
+ LINE_STRIP: draw a connected line strip
19
+ LINES: draw a series of unconnected lines
20
+ """
18
21
  super(LineItem, self).__init__()
19
22
  self.width = width
20
23
  self.buff = np.empty((0, 3), np.float32)
@@ -23,19 +26,20 @@ class LineItem(BaseItem):
23
26
  self.capacity = 100000
24
27
  self.valid_buff_top = 0
25
28
  self.color = color
26
- self.rgb = hex_to_rgba(color)
29
+ try:
30
+ self.rgb = text_to_rgba(color)
31
+ except ValueError:
32
+ raise ValueError("Invalid color format. Use mathplotlib color format.")
33
+
27
34
  self.line_type = GL_LINE_STRIP if line_type == 'LINE_STRIP' else GL_LINES
28
35
 
29
36
  def add_setting(self, layout):
30
37
  label_color = QLabel("Color:")
31
38
  layout.addWidget(label_color)
32
39
  self.color_edit = QLineEdit()
33
- self.color_edit.setToolTip("Hex number, i.e. #FF4500")
40
+ self.color_edit.setToolTip("mathplotlib color format")
34
41
  self.color_edit.setText(self.color)
35
42
  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
43
  layout.addWidget(self.color_edit)
40
44
 
41
45
  spinbox_width = QDoubleSpinBox()
@@ -48,10 +52,10 @@ class LineItem(BaseItem):
48
52
 
49
53
  def _on_color(self, color):
50
54
  try:
51
- self.rgb = hex_to_rgba(color)
55
+ self.rgb = text_to_rgba(color)
52
56
  self.color = color
53
57
  except ValueError:
54
- pass
58
+ print("Invalid color format. Use mathplotlib color format.")
55
59
 
56
60
  def set_color(self, color):
57
61
  self.color_edit.setText(color)
@@ -6,7 +6,7 @@ Distributed under MIT license. See LICENSE for more information.
6
6
  from PySide6 import QtCore, QtGui
7
7
  from q3dviewer.base_item import BaseItem
8
8
  from OpenGL.GL import *
9
- from q3dviewer.utils.maths import hex_to_rgba
9
+ from q3dviewer.utils.maths import text_to_rgba
10
10
 
11
11
 
12
12
  class Text2DItem(BaseItem):
@@ -16,8 +16,10 @@ class Text2DItem(BaseItem):
16
16
  """All keyword arguments are passed to set_data()"""
17
17
  BaseItem.__init__(self)
18
18
  self.pos = (20, 50)
19
- self.color = '#ffffff'
20
- self.rgb = hex_to_rgba(self.color)
19
+ try:
20
+ self.rgb = text_to_rgba(self.color)
21
+ except ValueError:
22
+ raise ValueError("Invalid color format. Use mathplotlib color format.")
21
23
  self.text = ''
22
24
  self.font = QtGui.QFont('Helvetica', 16)
23
25
  self.set_data(**kwds)
@@ -44,10 +46,10 @@ class Text2DItem(BaseItem):
44
46
 
45
47
  def set_color(self, color):
46
48
  try:
47
- self.rgb = hex_to_rgba(color)
49
+ self.rgb = text_to_rgba(color)
48
50
  self.color = color
49
51
  except ValueError:
50
- pass
52
+ print("Invalid color format. Use mathplotlib color format.")
51
53
 
52
54
  def paint(self):
53
55
  if len(self.text) < 1:
q3dviewer/glwidget.py CHANGED
@@ -5,11 +5,11 @@ Distributed under MIT license. See LICENSE for more information.
5
5
 
6
6
  from PySide6 import QtCore
7
7
  from PySide6.QtWidgets import QWidget, QComboBox, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QCheckBox, QGroupBox
8
- from PySide6.QtGui import QKeyEvent, QVector3D, QRegularExpressionValidator
9
- from PySide6.QtCore import QRegularExpression
8
+ from PySide6.QtGui import QKeyEvent, QVector3D
10
9
  from OpenGL.GL import *
11
10
  import numpy as np
12
11
  from q3dviewer.base_glwidget import BaseGLWidget
12
+ from q3dviewer.utils.maths import text_to_rgba
13
13
 
14
14
  class SettingWindow(QWidget):
15
15
  def __init__(self):
@@ -52,7 +52,7 @@ class GLWidget(BaseGLWidget):
52
52
  def __init__(self):
53
53
  self.followed_name = 'none'
54
54
  self.named_items = {}
55
- self.color_str = '#000000'
55
+ self.color_str = 'black'
56
56
  self.followable_item_name = None
57
57
  self.setting_window = SettingWindow()
58
58
  self.enable_show_center = True
@@ -80,9 +80,6 @@ class GLWidget(BaseGLWidget):
80
80
  color_edit.setToolTip("'using hex color, i.e. #FF4500")
81
81
  color_edit.setText(self.color_str)
82
82
  color_edit.textChanged.connect(self.set_bg_color)
83
- regex = QRegularExpression(r"^#[0-9A-Fa-f]{6}$")
84
- validator = QRegularExpressionValidator(regex)
85
- color_edit.setValidator(validator)
86
83
  layout.addWidget(color_edit)
87
84
 
88
85
  label_focus = QLabel("Set Focus:")
@@ -108,16 +105,13 @@ class GLWidget(BaseGLWidget):
108
105
  if item.__class__.__name__ == 'AxisItem' and not item._disable_setting:
109
106
  self.followable_item_name.append(name)
110
107
 
111
- def set_bg_color(self, color_str):
108
+ def set_bg_color(self, color):
112
109
  try:
113
- color_flat = int(color_str[1:], 16)
114
- red = (color_flat >> 16) & 0xFF
115
- green = (color_flat >> 8) & 0xFF
116
- blue = color_flat & 0xFF
117
- self.color_str = color_str
118
- self.set_color([red, green, blue, 0])
110
+ self.color_str = color
111
+ red, green, blue, alpha = text_to_rgba(color)
112
+ self.set_color([red, green, blue, alpha])
119
113
  except ValueError:
120
- return
114
+ print("Invalid color format. Use mathplotlib color format.")
121
115
 
122
116
  def add_item_with_name(self, name, item):
123
117
  self.named_items.update({name: item})
@@ -151,7 +151,6 @@ class CMMViewer(q3d.Viewer):
151
151
  main_layout.addWidget(dock_widget)
152
152
  self.dock = dock_widget
153
153
 
154
-
155
154
  def update_video_path(self, path):
156
155
  self.video_path = path
157
156
 
@@ -235,7 +234,6 @@ class CMMViewer(q3d.Viewer):
235
234
  self.glwidget.set_cam_position(center=center,
236
235
  euler=euler)
237
236
 
238
-
239
237
  def create_frames(self):
240
238
  """
241
239
  Create the frames for playback by interpolating between key frames.
@@ -24,17 +24,14 @@ def load_ply(file):
24
24
  xyz = mesh.points
25
25
  rgb = np.zeros([xyz.shape[0]], dtype=np.uint32)
26
26
  intensity = np.zeros([xyz.shape[0]], dtype=np.uint32)
27
- color_mode = 'FLAT'
28
27
  if "intensity" in mesh.point_data:
29
28
  intensity = mesh.point_data["intensity"]
30
- color_mode = 'I'
31
29
  if "rgb" in mesh.point_data:
32
30
  rgb = mesh.point_data["rgb"]
33
- color_mode = 'RGB'
34
31
  irgb = (intensity << 24) | rgb
35
32
  dtype = [('xyz', '<f4', (3,)), ('irgb', '<u4')]
36
33
  cloud = np.rec.fromarrays([xyz, irgb], dtype=dtype)
37
- return cloud, color_mode
34
+ return cloud
38
35
 
39
36
 
40
37
  def save_pcd(cloud, save_path):
@@ -51,8 +48,35 @@ def save_pcd(cloud, save_path):
51
48
  i = (cloud['irgb'] & 0xFF000000) >> 24
52
49
  rgb = cloud['irgb'] & 0x00FFFFFF
53
50
 
54
- dtype = [('xyz', '<f4', (3,)), ('intensity', '<u4'), ('rgb', '<u4')]
55
- tmp = np.rec.fromarrays([cloud['xyz'], i, rgb], dtype=dtype)
51
+ # check no rgb value
52
+ if np.max(rgb) == 0:
53
+ fields = ('x', 'y', 'z', 'intensity')
54
+ metadata = MetaData.model_validate(
55
+ {
56
+ "fields": fields,
57
+ "size": [4, 4, 4, 4],
58
+ "type": ['F', 'F', 'F', 'U'],
59
+ "count": [1, 1, 1, 1],
60
+ "width": cloud.shape[0],
61
+ "points": cloud.shape[0],
62
+ })
63
+
64
+ dtype = [('xyz', '<f4', (3,)), ('intensity', '<u4')]
65
+ tmp = np.rec.fromarrays([cloud['xyz'], i], dtype=dtype)
66
+ PointCloud(metadata, tmp).save(save_path)
67
+ else:
68
+ fields = ('x', 'y', 'z', 'intensity', 'rgb')
69
+ metadata = MetaData.model_validate(
70
+ {
71
+ "fields": fields,
72
+ "size": [4, 4, 4, 4, 4],
73
+ "type": ['F', 'F', 'F', 'U', 'U'],
74
+ "count": [1, 1, 1, 1, 1],
75
+ "width": cloud.shape[0],
76
+ "points": cloud.shape[0],
77
+ })
78
+ dtype = [('xyz', '<f4', (3,)), ('intensity', '<u4'), ('rgb', '<u4')]
79
+ tmp = np.rec.fromarrays([cloud['xyz'], i, rgb], dtype=dtype)
56
80
 
57
81
  PointCloud(metadata, tmp).save(save_path)
58
82
 
@@ -62,17 +86,14 @@ def load_pcd(file):
62
86
  pc = PointCloud.from_path(file).pc_data
63
87
  rgb = np.zeros([pc.shape[0]], dtype=np.uint32)
64
88
  intensity = np.zeros([pc.shape[0]], dtype=np.uint32)
65
- color_mode = 'FLAT'
66
89
  if 'intensity' in pc.dtype.names:
67
90
  intensity = pc['intensity'].astype(np.uint32)
68
- color_mode = 'I'
69
91
  if 'rgb' in pc.dtype.names:
70
92
  rgb = pc['rgb'].astype(np.uint32)
71
- color_mode = 'RGB'
72
93
  irgb = (intensity << 24) | rgb
73
94
  xyz = np.stack([pc['x'], pc['y'], pc['z']], axis=1)
74
95
  cloud = np.rec.fromarrays([xyz, irgb], dtype=dtype)
75
- return cloud, color_mode
96
+ return cloud
76
97
 
77
98
 
78
99
  def save_e57(cloud, save_path):
@@ -100,23 +121,20 @@ def load_e57(file_path):
100
121
  z = scans["cartesianZ"]
101
122
  rgb = np.zeros([x.shape[0]], dtype=np.uint32)
102
123
  intensity = np.zeros([x.shape[0]], dtype=np.uint32)
103
- color_mode = 'FLAT'
104
124
  if "intensity" in scans:
105
125
  intensity = scans["intensity"].astype(np.uint32)
106
- color_mode = 'I'
107
126
  if all([x in scans for x in ["colorRed", "colorGreen", "colorBlue"]]):
108
127
  r = scans["colorRed"].astype(np.uint32)
109
128
  g = scans["colorGreen"].astype(np.uint32)
110
129
  b = scans["colorBlue"].astype(np.uint32)
111
130
  rgb = (r << 16) | (g << 8) | b
112
- color_mode = 'RGB'
113
131
  irgb = (intensity << 24) | rgb
114
132
  dtype = [('xyz', '<f4', (3,)), ('irgb', '<u4')]
115
133
  cloud = np.rec.fromarrays(
116
134
  [np.stack([x, y, z], axis=1), irgb],
117
135
  dtype=dtype)
118
136
  e57.close()
119
- return cloud, color_mode
137
+ return cloud
120
138
 
121
139
 
122
140
  def load_las(file):
@@ -124,27 +142,24 @@ def load_las(file):
124
142
  las = f.read()
125
143
  xyz = np.vstack((las.x, las.y, las.z)).transpose()
126
144
  dimensions = list(las.point_format.dimension_names)
127
- color_mode = 'FLAT'
128
145
  rgb = np.zeros([las.x.shape[0]], dtype=np.uint32)
129
146
  intensity = np.zeros([las.x.shape[0]], dtype=np.uint32)
130
147
  if 'intensity' in dimensions:
131
148
  intensity = las.intensity.astype(np.uint32)
132
- color_mode = 'I'
133
149
  if 'red' in dimensions and 'green' in dimensions and 'blue' in dimensions:
134
150
  red = las.red
135
151
  green = las.green
136
152
  blue = las.blue
137
153
  max_val = np.max([red, green, blue])
138
154
  if red.dtype == np.dtype('uint16') and max_val > 255:
139
- red = (red / 65535.0 * 255).astype(np.uint32)
140
- green = (green / 65535.0 * 255).astype(np.uint32)
141
- blue = (blue / 65535.0 * 255).astype(np.uint32)
155
+ red = (red / 255).astype(np.uint32)
156
+ green = (green / 255).astype(np.uint32)
157
+ blue = (blue / 255).astype(np.uint32)
142
158
  rgb = (red << 16) | (green << 8) | blue
143
- color_mode = 'RGB'
144
- color = (intensity << 24) | rgb
159
+ color = ((intensity / 255)<< 24) | rgb
145
160
  dtype = [('xyz', '<f4', (3,)), ('irgb', '<u4')]
146
161
  cloud = np.rec.fromarrays([xyz, color], dtype=dtype)
147
- return cloud, color_mode
162
+ return cloud
148
163
 
149
164
  def save_las(cloud, save_path):
150
165
  header = laspy.LasHeader(point_format=3, version="1.2")
@@ -152,10 +167,10 @@ def save_las(cloud, save_path):
152
167
  las.x = cloud['xyz'][:, 0]
153
168
  las.y = cloud['xyz'][:, 1]
154
169
  las.z = cloud['xyz'][:, 2]
155
- las.red = (cloud['irgb'] >> 16) & 0xFF
156
- las.green = (cloud['irgb'] >> 8) & 0xFF
157
- las.blue = cloud['irgb'] & 0xFF
158
- las.intensity = cloud['irgb'] >> 24
170
+ las.red = ((cloud['irgb'] >> 16) & 0xFF) * 255
171
+ las.green = ((cloud['irgb'] >> 8) & 0xFF) * 255
172
+ las.blue = (cloud['irgb'] & 0xFF)
173
+ las.intensity = (cloud['irgb'] >> 24) * 255
159
174
  las.write(save_path)
160
175
 
161
176
 
q3dviewer/utils/maths.py CHANGED
@@ -10,6 +10,7 @@ https://github.com/scomup/MathematicalRobotics.git
10
10
 
11
11
 
12
12
  import numpy as np
13
+ from matplotlib.colors import to_rgba
13
14
 
14
15
 
15
16
  _epsilon_ = 1e-5
@@ -292,26 +293,27 @@ def makeRt(T):
292
293
  t = T[0:3, 3]
293
294
  return R, t
294
295
 
295
- def hex_to_rgba(hex_color):
296
- if not hex_color.startswith("#"):
297
- print("Invalid hex color string.")
298
- return (1.0, 1.0, 1.0, 1.0)
299
- if len(hex_color) == 7:
300
- color_flat = int(hex_color[1:], 16)
301
- red = (color_flat >> 16) & 0xFF
302
- green = (color_flat >> 8) & 0xFF
303
- blue = color_flat & 0xFF
304
- return (red / 255.0, green / 255.0, blue / 255.0, 1.0)
305
- elif len(hex_color) == 9:
306
- color_flat = int(hex_color[1:], 16)
307
- red = (color_flat >> 24) & 0xFF
308
- green = (color_flat >> 16) & 0xFF
309
- blue = (color_flat >> 8) & 0xFF
310
- alpha = color_flat & 0xFF
311
- return (red / 255.0, green / 255.0, blue / 255.0, alpha / 255.0)
296
+
297
+ def text_to_rgba(color_text, flat=False):
298
+ """
299
+ Convert a color text to an RGBA tuple.
300
+
301
+ :param color_text: e.g. '#FF0000', '#FF0000FF', 'red', 'green', 'blue', 'yellow',
302
+ 'black', 'white', 'magenta', 'cyan', 'r', 'g', 'b', 'y', 'k', 'w', 'm', 'c'
303
+ :return: RGBA tuple, e.g. (1.0, 0.0, 0.0, 1.0)
304
+ """
305
+ rgba = to_rgba(color_text)
306
+ if flat:
307
+ r, g, b, _ = (np.array(rgba)*255).astype(np.uint32)
308
+ falt_rgb = ((r << 16) & 0xFF0000) | \
309
+ ((g << 8) & 0x00FF00) | \
310
+ ((b << 0) & 0x0000FF)
311
+ return falt_rgb
312
312
  else:
313
- print("Invalid hex color string.")
314
- return (1.0, 1.0, 1.0, 1.0)
313
+ return rgba
314
+ # except ValueError as e:
315
+ # raise ValueError(f"Invalid color text '{color_text}': {e}")
316
+
315
317
 
316
318
  # euler = np.array([1, 0.1, 0.1])
317
319
  # euler_angles = matrix_to_euler(euler_to_matrix(euler))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: q3dviewer
3
- Version: 1.1.3
3
+ Version: 1.1.5
4
4
  Summary: A library designed for quickly deploying a 3D viewer.
5
5
  Home-page: https://github.com/scomup/q3dviewer
6
6
  Author: Liu Yang
@@ -14,16 +14,25 @@ Requires-Dist: PyOpenGL
14
14
  Requires-Dist: imageio
15
15
  Requires-Dist: imageio[ffmpeg]
16
16
  Requires-Dist: laspy
17
+ Requires-Dist: matplotlib
17
18
  Requires-Dist: meshio
18
19
  Requires-Dist: numpy
19
20
  Requires-Dist: pye57
20
21
  Requires-Dist: pypcd4
21
22
  Requires-Dist: pyside6
22
23
 
23
- ## q3dviewer
24
+
25
+ ![q3dviewer Logo](imgs/logo.png)
26
+
27
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
28
+ [![PyPI version](https://badge.fury.io/py/q3dviewer.svg)](https://badge.fury.io/py/q3dviewer)
24
29
 
25
30
  `q3dviewer` is a library designed for quickly deploying a 3D viewer. It is based on Qt (PySide6) and provides efficient OpenGL items for displaying 3D objects (e.g., point clouds, cameras, and 3D Gaussians). You can use it to visualize your 3D data or set up an efficient viewer application. It is inspired by PyQtGraph but focuses more on efficient 3D rendering.
26
31
 
32
+
33
+ To show how to use `q3dviewer` as a library, we also provide some [very useful tools](#tools).
34
+
35
+
27
36
  ## Installation
28
37
 
29
38
  To install `q3dviewer`, execute the following command in your terminal on either Linux or Windows:
@@ -52,10 +61,10 @@ Once installed, you can directly use the following tools:
52
61
 
53
62
  ### 1. Cloud Viewer
54
63
 
55
- A tool for visualizing point cloud files. Launch it by executing the following command in your terminal:
64
+ A tool for visualizing point cloud files (LAS, PCD, PLY, E57). Launch it by executing the following command in your terminal:
56
65
 
57
66
  ```sh
58
- cloud_viewer # The viewer will be displayed
67
+ cloud_viewer
59
68
  ```
60
69
 
61
70
  *Alternatively*, if the path is not set (though it's not recommended):
@@ -64,7 +73,13 @@ cloud_viewer # The viewer will be displayed
64
73
  python3 -m q3dviewer.tools.cloud_viewer
65
74
  ```
66
75
 
67
- After the viewer launches, you can drag and drop files onto the window to display the point clouds. Multiple files can be dropped simultaneously to view them together. Supported formats include LAS, PCD, PLY, and E57.
76
+ **Basic Operations**
77
+ * Load files: Drag and drop point cloud files onto the window (multiple files are OK).
78
+ * `M` key: Display the visualization settings screen for point clouds, background color, etc.
79
+ * `Left mouse button` & `W, A, S, D` keys: Move the viewpoint on the horizontal plane.
80
+ * `Z, X` keys: Move in the direction the screen is facing.
81
+ * `Right mouse button` & `Arrow` keys: Rotate the viewpoint while keeping the screen center unchanged.
82
+ * `Shift` + `Right mouse button` & `Arrow` keys: Rotate the viewpoint while keeping the camera position unchanged.
68
83
 
69
84
  For example, you can download and view point clouds of Tokyo in LAS format from the following link:
70
85
 
@@ -90,15 +105,21 @@ ros_viewer
90
105
  Would you like to create a video from point cloud data? With Film Maker, you can easily create videos with simple operations. Just edit keyframes using the user-friendly GUI, and the software will automatically interpolate the keyframes to generate the video.
91
106
 
92
107
  ```sh
93
- film_maker # drag and drop your cloud file to the window
108
+ film_maker
94
109
  ```
95
110
 
111
+ **Basic Operations**
112
+ * File loading & viewpoint movement: Same as Cloud_Viewer
96
113
  * Space key to add a keyframe.
97
114
  * Delete key to remove a keyframe.
115
+ * Play button: Automatically play the video (pressing again will stop playback)
116
+ * Record checkbox: When checked, actions will be automatically recorded during playback
98
117
 
99
118
  Film Maker GUI:
100
119
 
101
- ![Screenshot from 2025-02-02 18-20-51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149168/a1a6ad63-237c-482e-439d-e760223c59ca.png)
120
+ ![film_maker_demo.gif](imgs/film_maker_demo.gif)
121
+
122
+ The demo video demonstrating how to use Film Maker utilizes the [cloud data of Kyobashi Station Area](https://www.geospatial.jp/ckan/dataset/kyoubasiekisyuuhen_las) located in Osaka, Japan.
102
123
 
103
124
  ### 4. Gaussian Viewer
104
125
 
@@ -4,20 +4,20 @@ q3dviewer/base_item.py,sha256=lzb04oRaS4rRJrAP6C1Bu4ugK237FgupMTB97zjNVFw,1768
4
4
  q3dviewer/basic_window.py,sha256=CFErOPRMysFcCqq3vhDsQ-xZzLArO3m1yABCTIhq5do,7946
5
5
  q3dviewer/cloud_viewer.py,sha256=IxxrB6Sl6aPCr9P9QzKHGkMcDP_DjsBWbkmIbsAoIM4,2358
6
6
  q3dviewer/gau_io.py,sha256=S6NmqL5vSMgHVxKR-eu4CWCqgZeWBJKYRoOMAwr8Xbo,5890
7
- q3dviewer/glwidget.py,sha256=im8hjVYEL0Zl7fOIHTQMJdWu7WNOHlvTdIDYjebz9WA,4940
7
+ q3dviewer/glwidget.py,sha256=2scii9IeY3ion12lFgWtGXO9JrlUD3tfp3zXt7gnFoQ,4689
8
8
  q3dviewer/utils.py,sha256=evF0d-v17hbTmquC24fmMIp9CsXpUnSQZr4MVy2sfao,2426
9
9
  q3dviewer/viewer.py,sha256=LH1INLFhi6pRjzazzQJ0AWT4hgyXI6GnmqoJFUwUZVE,2517
10
10
  q3dviewer/custom_items/__init__.py,sha256=gOiAxdjDaAnFL8YbqSEWWWOwUrJfvzP9JLR34sCB9-4,434
11
11
  q3dviewer/custom_items/axis_item.py,sha256=PTBSf5DmQI8ieSinYjY_aC7P8q1nzE-2Vc2GNd1O3Os,2568
12
12
  q3dviewer/custom_items/camera_frame_item.py,sha256=VBsr3Avly_YWXViIh4DJkGc_HJt227GeOYLpGtbYTOw,5605
13
- q3dviewer/custom_items/cloud_io_item.py,sha256=gjK3n9WKB7JwxC93ijkweEHA5EezpgNJ8KO-PBaDKCs,2835
14
- q3dviewer/custom_items/cloud_item.py,sha256=s46TxNDw7E6DW3V-ek_PF7egB3kRbnqpOBNUKdaC8IY,12620
15
- q3dviewer/custom_items/frame_item.py,sha256=6BOM3MXi-Akv6KUXDC3QYEAXKxwk0Eo1KQn-F7jkqrQ,7559
13
+ q3dviewer/custom_items/cloud_io_item.py,sha256=l7FGKb1s3kEFw1GT8Bro5oJfu4reuvunHJkM1pmpxz4,3038
14
+ q3dviewer/custom_items/cloud_item.py,sha256=JtjehxPYuzypVg_8ENfZvN7yEVO-Ac7GE8Dt3jzP6yk,12763
15
+ q3dviewer/custom_items/frame_item.py,sha256=9g4SxIGZCWFWaX5Xfd9G-P63VlonysxEC239lk7dRRg,7408
16
16
  q3dviewer/custom_items/gaussian_item.py,sha256=CZoXMmj2JPFfMqu7v4q4dLUI2cg_WfE1DHWGXjEYn6M,9869
17
- q3dviewer/custom_items/grid_item.py,sha256=20n4TGm5YEaudhnEOCOk-HtsKwxVxaPr8YV36kO04yU,4802
17
+ q3dviewer/custom_items/grid_item.py,sha256=58QUKli_9NEtYchulZt5Xhqk6k2DaL5RlEMaYyt7MSE,4965
18
18
  q3dviewer/custom_items/image_item.py,sha256=3FYAVlNLEILKZplkt2wbL8y16ke124GmwxcSmWmJY8Q,5357
19
- q3dviewer/custom_items/line_item.py,sha256=u0oFN2iHzsRHtnbvyvC_iglEkCwEU2NnTuE536sKUAE,4348
20
- q3dviewer/custom_items/text_item.py,sha256=nuHMVMQrwy50lNk9hxB94criFxbJJK-SYiK2fSXWUMQ,2158
19
+ q3dviewer/custom_items/line_item.py,sha256=np8sORS5kZFWptOUa_Vlq1p7b9rMK3CpGsBd77Qpo2M,4445
20
+ q3dviewer/custom_items/text_item.py,sha256=G3PPpN_0yjUMWJKvNG5QFgqxmE6LUEW5VIUDdi3kwuk,2314
21
21
  q3dviewer/custom_items/trajectory_item.py,sha256=uoKQSrTs_m_m1M8iNAm3peiXnZ9uVPsYQLYas3Gksjg,2754
22
22
  q3dviewer/shaders/cloud_frag.glsl,sha256=tbCsDUp9YlPe0hRWlFS724SH6TtMeLO-GVYROzEElZg,609
23
23
  q3dviewer/shaders/cloud_vert.glsl,sha256=Vxgw-Zrr0knAK0z4qMXKML6IC4EbffKMwYN2TMXROoI,2117
@@ -28,20 +28,20 @@ q3dviewer/shaders/sort_by_key.glsl,sha256=CA2zOcbyDGYAJSJEUvgjUqNshg9NAehf8ipL3J
28
28
  q3dviewer/tools/__init__.py,sha256=01wG7BGM6VX0QyFBKsqPmyf2e-vrmV_N3-mo-VQ1VBg,20
29
29
  q3dviewer/tools/cloud_viewer.py,sha256=10f2LSWpmsXzxrGobXw188doVjJbgBfPoqZPUi35EtI,3867
30
30
  q3dviewer/tools/example_viewer.py,sha256=yeVXT0k4-h1vTLKnGzWADZD3our6XUaYUTy0p5daTkE,959
31
- q3dviewer/tools/film_maker.py,sha256=KXl0H03vnS7eAFw7nSRvTG3J0Y7gYt6YFUYaoxJf3xM,16600
31
+ q3dviewer/tools/film_maker.py,sha256=w97LMMmSpjTOMdG2j0ucK2kDoU3k__746jRVh2fJfvc,16598
32
32
  q3dviewer/tools/gaussian_viewer.py,sha256=vIwWmiFhjNmknrEkBLzt2yiegeH7LP3OeNjnGM6GzaI,1633
33
33
  q3dviewer/tools/lidar_calib.py,sha256=M01bGg2mT8LwVcYybolr4UW_UUaR-f-BFciEHtjeK-w,10488
34
34
  q3dviewer/tools/lidar_cam_calib.py,sha256=SYNLDvi15MX7Q3aGn771fvu1cES9xeXgP0_WmDq33w4,11200
35
35
  q3dviewer/tools/ros_viewer.py,sha256=ARB3I5wohY3maP8dCu0O0hxObd6JFKuK2y7AApVgMWA,2551
36
36
  q3dviewer/utils/__init__.py,sha256=irm8Z_bT8l9kzhoMlds2Dal8g4iw4vjmqNPZSs4W6e0,157
37
- q3dviewer/utils/cloud_io.py,sha256=ttD8FJExdDhXB1Z0Ej9S939i8gcq4JfyqwLXVh8CEFw,11094
37
+ q3dviewer/utils/cloud_io.py,sha256=xnsT3VEKS435BE1DwhciRHicwzuC-8-JASkImT8pKNk,11681
38
38
  q3dviewer/utils/convert_ros_msg.py,sha256=sAoQfy3qLQKsIArBAVm8H--wlQXOcmkKK3-Ox9UCcrc,1686
39
39
  q3dviewer/utils/gl_helper.py,sha256=dRY_kUqyPMr7NTcupUr6_VTvgnj53iE2C0Lk0-oFYsI,1435
40
- q3dviewer/utils/maths.py,sha256=5TmjWUX1K3UjygXxrUsydjbo7tPzu0gD-yy7qtQUGBU,10588
40
+ q3dviewer/utils/maths.py,sha256=tHx2q_qAFPQQoFJbnwB0Ts-xtEibeGAhJrMHOH6aCPk,10575
41
41
  q3dviewer/utils/range_slider.py,sha256=jZJQL-uQgnpgLvtYSWpKTrJlLkt3aqNpaRQAePEpNd0,3174
42
- q3dviewer-1.1.3.dist-info/LICENSE,sha256=81cMOyNfw8KLb1JnPYngGHJ5W83gSbZEBU9MEP3tl-E,1124
43
- q3dviewer-1.1.3.dist-info/METADATA,sha256=AXXroAQRcP2jMPxZCzpiR0tu-Ykj2lHUg28rQQZvM6I,7010
44
- q3dviewer-1.1.3.dist-info/WHEEL,sha256=g4nMs7d-Xl9-xC9XovUrsDHGXt-FT0E17Yqo92DEfvY,92
45
- q3dviewer-1.1.3.dist-info/entry_points.txt,sha256=EOjker7XYaBk70ffvNB_knPcfA33Bnlg21ZjEeM1EyI,362
46
- q3dviewer-1.1.3.dist-info/top_level.txt,sha256=HFFDCbGu28txcGe2HPc46A7EPaguBa_b5oH7bufmxHM,10
47
- q3dviewer-1.1.3.dist-info/RECORD,,
42
+ q3dviewer-1.1.5.dist-info/LICENSE,sha256=81cMOyNfw8KLb1JnPYngGHJ5W83gSbZEBU9MEP3tl-E,1124
43
+ q3dviewer-1.1.5.dist-info/METADATA,sha256=6DZkBF6Jsghr4qu0K2Wp-gzjP8qoflKCvSk6qi2EoP4,7984
44
+ q3dviewer-1.1.5.dist-info/WHEEL,sha256=g4nMs7d-Xl9-xC9XovUrsDHGXt-FT0E17Yqo92DEfvY,92
45
+ q3dviewer-1.1.5.dist-info/entry_points.txt,sha256=EOjker7XYaBk70ffvNB_knPcfA33Bnlg21ZjEeM1EyI,362
46
+ q3dviewer-1.1.5.dist-info/top_level.txt,sha256=HFFDCbGu28txcGe2HPc46A7EPaguBa_b5oH7bufmxHM,10
47
+ q3dviewer-1.1.5.dist-info/RECORD,,