q3dviewer 1.0.3__py3-none-any.whl → 1.0.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. q3dviewer/__init__.py +5 -0
  2. q3dviewer/base_glwidget.py +256 -0
  3. q3dviewer/base_item.py +57 -0
  4. q3dviewer/custom_items/__init__.py +9 -0
  5. q3dviewer/custom_items/axis_item.py +148 -0
  6. q3dviewer/custom_items/cloud_io_item.py +79 -0
  7. q3dviewer/custom_items/cloud_item.py +314 -0
  8. q3dviewer/custom_items/frame_item.py +194 -0
  9. q3dviewer/custom_items/gaussian_item.py +254 -0
  10. q3dviewer/custom_items/grid_item.py +88 -0
  11. q3dviewer/custom_items/image_item.py +172 -0
  12. q3dviewer/custom_items/line_item.py +120 -0
  13. q3dviewer/custom_items/text_item.py +63 -0
  14. q3dviewer/gau_io.py +0 -0
  15. q3dviewer/glwidget.py +131 -0
  16. q3dviewer/shaders/cloud_frag.glsl +28 -0
  17. q3dviewer/shaders/cloud_vert.glsl +72 -0
  18. q3dviewer/shaders/gau_frag.glsl +42 -0
  19. q3dviewer/shaders/gau_prep.glsl +249 -0
  20. q3dviewer/shaders/gau_vert.glsl +77 -0
  21. q3dviewer/shaders/sort_by_key.glsl +56 -0
  22. q3dviewer/tools/__init__.py +1 -0
  23. q3dviewer/tools/cloud_viewer.py +123 -0
  24. q3dviewer/tools/example_viewer.py +47 -0
  25. q3dviewer/tools/gaussian_viewer.py +60 -0
  26. q3dviewer/tools/lidar_calib.py +294 -0
  27. q3dviewer/tools/lidar_cam_calib.py +314 -0
  28. q3dviewer/tools/ros_viewer.py +85 -0
  29. q3dviewer/utils/__init__.py +4 -0
  30. q3dviewer/utils/cloud_io.py +323 -0
  31. q3dviewer/utils/convert_ros_msg.py +49 -0
  32. q3dviewer/utils/gl_helper.py +40 -0
  33. q3dviewer/utils/maths.py +168 -0
  34. q3dviewer/utils/range_slider.py +86 -0
  35. q3dviewer/viewer.py +58 -0
  36. q3dviewer-1.0.5.dist-info/LICENSE +21 -0
  37. {q3dviewer-1.0.3.dist-info → q3dviewer-1.0.5.dist-info}/METADATA +7 -4
  38. q3dviewer-1.0.5.dist-info/RECORD +41 -0
  39. q3dviewer-1.0.5.dist-info/top_level.txt +1 -0
  40. q3dviewer-1.0.3.dist-info/RECORD +0 -5
  41. q3dviewer-1.0.3.dist-info/top_level.txt +0 -1
  42. {q3dviewer-1.0.3.dist-info → q3dviewer-1.0.5.dist-info}/WHEEL +0 -0
  43. {q3dviewer-1.0.3.dist-info → q3dviewer-1.0.5.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,323 @@
1
+ """
2
+ Copyright 2024 Panasonic Advanced Technology Development Co.,Ltd. (Liu Yang)
3
+ Distributed under MIT license. See LICENSE for more information.
4
+ """
5
+
6
+ import numpy as np
7
+ import meshio
8
+ from pypcd4 import PointCloud, MetaData
9
+ from pye57 import E57
10
+ import laspy
11
+
12
+
13
+ def save_ply(cloud, save_path):
14
+ xyz = cloud['xyz']
15
+ i = (cloud['irgb'] & 0xFF000000) >> 24
16
+ rgb = cloud['irgb'] & 0x00FFFFFF
17
+ mesh = meshio.Mesh(points=xyz, cells=[], point_data={
18
+ "rgb": rgb, "intensity": i})
19
+ mesh.write(save_path, file_format="ply")
20
+
21
+
22
+ def load_ply(file):
23
+ mesh = meshio.read(file)
24
+ xyz = mesh.points
25
+ rgb = np.zeros([xyz.shape[0]], dtype=np.uint32)
26
+ intensity = np.zeros([xyz.shape[0]], dtype=np.uint32)
27
+ color_mode = 'FLAT'
28
+ if "intensity" in mesh.point_data:
29
+ intensity = mesh.point_data["intensity"]
30
+ color_mode = 'I'
31
+ if "rgb" in mesh.point_data:
32
+ rgb = mesh.point_data["rgb"]
33
+ color_mode = 'RGB'
34
+ irgb = (intensity << 24) | rgb
35
+ dtype = [('xyz', '<f4', (3,)), ('irgb', '<u4')]
36
+ cloud = np.rec.fromarrays([xyz, irgb], dtype=dtype)
37
+ return cloud, color_mode
38
+
39
+
40
+ def save_pcd(cloud, save_path):
41
+ fields = ('x', 'y', 'z', 'intensity', 'rgb')
42
+ metadata = MetaData.model_validate(
43
+ {
44
+ "fields": fields,
45
+ "size": [4, 4, 4, 4, 4],
46
+ "type": ['F', 'F', 'F', 'U', 'U'],
47
+ "count": [1, 1, 1, 1, 1],
48
+ "width": cloud.shape[0],
49
+ "points": cloud.shape[0],
50
+ })
51
+ i = (cloud['irgb'] & 0xFF000000) >> 24
52
+ rgb = cloud['irgb'] & 0x00FFFFFF
53
+
54
+ dtype = [('xyz', '<f4', (3,)), ('intensity', '<u4'), ('rgb', '<u4')]
55
+ tmp = np.rec.fromarrays([cloud['xyz'], i, rgb], dtype=dtype)
56
+
57
+ PointCloud(metadata, tmp).save(save_path)
58
+
59
+
60
+ def load_pcd(file):
61
+ dtype = [('xyz', '<f4', (3,)), ('irgb', '<u4')]
62
+ pc = PointCloud.from_path(file).pc_data
63
+ rgb = np.zeros([pc.shape[0]], dtype=np.uint32)
64
+ intensity = np.zeros([pc.shape[0]], dtype=np.uint32)
65
+ color_mode = 'FLAT'
66
+ if 'intensity' in pc.dtype.names:
67
+ intensity = pc['intensity'].astype(np.uint32)
68
+ color_mode = 'I'
69
+ if 'rgb' in pc.dtype.names:
70
+ rgb = pc['rgb'].astype(np.uint32)
71
+ color_mode = 'RGB'
72
+ irgb = (intensity << 24) | rgb
73
+ xyz = np.stack([pc['x'], pc['y'], pc['z']], axis=1)
74
+ cloud = np.rec.fromarrays([xyz, irgb], dtype=dtype)
75
+ return cloud, color_mode
76
+
77
+
78
+ def save_e57(cloud, save_path):
79
+ e57 = E57(save_path, mode='w')
80
+ x = cloud['xyz'][:, 0]
81
+ y = cloud['xyz'][:, 1]
82
+ z = cloud['xyz'][:, 2]
83
+ i = (cloud['irgb'] & 0xFF000000) >> 24
84
+ r = (cloud['irgb'] & 0x00FF0000) >> 16
85
+ g = (cloud['irgb'] & 0x0000FF00) >> 8
86
+ b = (cloud['irgb'] & 0x000000ff)
87
+ data = {"cartesianX": x, "cartesianY": y, "cartesianZ": z,
88
+ "intensity": i,
89
+ "colorRed": r, "colorGreen": g, "colorBlue": b}
90
+ e57.write_scan_raw(data)
91
+ e57.close()
92
+
93
+
94
+ def load_e57(file_path):
95
+ e57 = E57(file_path, mode="r")
96
+ scans = e57.read_scan(0, ignore_missing_fields=True,
97
+ intensity=True, colors=True)
98
+ x = scans["cartesianX"]
99
+ y = scans["cartesianY"]
100
+ z = scans["cartesianZ"]
101
+ rgb = np.zeros([x.shape[0]], dtype=np.uint32)
102
+ intensity = np.zeros([x.shape[0]], dtype=np.uint32)
103
+ color_mode = 'FLAT'
104
+ if "intensity" in scans:
105
+ intensity = scans["intensity"].astype(np.uint32)
106
+ color_mode = 'I'
107
+ if all([x in scans for x in ["colorRed", "colorGreen", "colorBlue"]]):
108
+ r = scans["colorRed"].astype(np.uint32)
109
+ g = scans["colorGreen"].astype(np.uint32)
110
+ b = scans["colorBlue"].astype(np.uint32)
111
+ rgb = (r << 16) | (g << 8) | b
112
+ color_mode = 'RGB'
113
+ irgb = (intensity << 24) | rgb
114
+ dtype = [('xyz', '<f4', (3,)), ('irgb', '<u4')]
115
+ cloud = np.rec.fromarrays(
116
+ [np.stack([x, y, z], axis=1), irgb],
117
+ dtype=dtype)
118
+ e57.close()
119
+ return cloud, color_mode
120
+
121
+
122
+ def load_las(file):
123
+ with laspy.open(file) as f:
124
+ las = f.read()
125
+ xyz = np.vstack((las.x, las.y, las.z)).transpose()
126
+ dimensions = list(las.point_format.dimension_names)
127
+ color_mode = 'FLAT'
128
+ rgb = np.zeros([las.x.shape[0]], dtype=np.uint32)
129
+ intensity = np.zeros([las.x.shape[0]], dtype=np.uint32)
130
+ if 'intensity' in dimensions:
131
+ intensity = las.intensity.astype(np.uint32)
132
+ color_mode = 'I'
133
+ if 'red' in dimensions and 'green' in dimensions and 'blue' in dimensions:
134
+ red = las.red
135
+ green = las.green
136
+ blue = las.blue
137
+ max_val = np.max([red, green, blue])
138
+ 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)
142
+ rgb = (red << 16) | (green << 8) | blue
143
+ color_mode = 'RGB'
144
+ color = (intensity << 24) | rgb
145
+ dtype = [('xyz', '<f4', (3,)), ('irgb', '<u4')]
146
+ cloud = np.rec.fromarrays([xyz, color], dtype=dtype)
147
+ return cloud, color_mode
148
+
149
+ def save_las(cloud, save_path):
150
+ header = laspy.LasHeader(point_format=3, version="1.2")
151
+ las = laspy.LasData(header)
152
+ las.x = cloud['xyz'][:, 0]
153
+ las.y = cloud['xyz'][:, 1]
154
+ 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
159
+ las.write(save_path)
160
+
161
+
162
+ def gsdata_type(sh_dim):
163
+ return [('pw', '<f4', (3,)),
164
+ ('rot', '<f4', (4,)),
165
+ ('scale', '<f4', (3,)),
166
+ ('alpha', '<f4'),
167
+ ('sh', '<f4', (sh_dim))]
168
+
169
+
170
+ def matrix_to_quaternion_wxyz(matrices):
171
+ m00, m01, m02 = matrices[:, 0, 0], matrices[:, 0, 1], matrices[:, 0, 2]
172
+ m10, m11, m12 = matrices[:, 1, 0], matrices[:, 1, 1], matrices[:, 1, 2]
173
+ m20, m21, m22 = matrices[:, 2, 0], matrices[:, 2, 1], matrices[:, 2, 2]
174
+ t = 1 + m00 + m11 + m22
175
+ s = np.ones_like(m00)
176
+ w = np.ones_like(m00)
177
+ x = np.ones_like(m00)
178
+ y = np.ones_like(m00)
179
+ z = np.ones_like(m00)
180
+
181
+ t_positive = t > 0.0000001
182
+ s[t_positive] = 0.5 / np.sqrt(t[t_positive])
183
+ w[t_positive] = 0.25 / s[t_positive]
184
+ x[t_positive] = (m21[t_positive] - m12[t_positive]) * s[t_positive]
185
+ y[t_positive] = (m02[t_positive] - m20[t_positive]) * s[t_positive]
186
+ z[t_positive] = (m10[t_positive] - m01[t_positive]) * s[t_positive]
187
+
188
+ c1 = np.logical_and(m00 > m11, m00 > m22)
189
+ cond1 = np.logical_and(np.logical_not(t_positive),
190
+ np.logical_and(m00 > m11, m00 > m22))
191
+
192
+ s[cond1] = 2.0 * np.sqrt(1.0 + m00[cond1] - m11[cond1] - m22[cond1])
193
+ w[cond1] = (m21[cond1] - m12[cond1]) / s[cond1]
194
+ x[cond1] = 0.25 * s[cond1]
195
+ y[cond1] = (m01[cond1] + m10[cond1]) / s[cond1]
196
+ z[cond1] = (m02[cond1] + m20[cond1]) / s[cond1]
197
+
198
+ c2 = np.logical_and(np.logical_not(c1), m11 > m22)
199
+ cond2 = np.logical_and(np.logical_not(t_positive), c2)
200
+ s[cond2] = 2.0 * np.sqrt(1.0 + m11[cond2] - m00[cond2] - m22[cond2])
201
+ w[cond2] = (m02[cond2] - m20[cond2]) / s[cond2]
202
+ x[cond2] = (m01[cond2] + m10[cond2]) / s[cond2]
203
+ y[cond2] = 0.25 * s[cond2]
204
+ z[cond2] = (m12[cond2] + m21[cond2]) / s[cond2]
205
+
206
+ c3 = np.logical_and(np.logical_not(c1), np.logical_not(c2))
207
+ cond3 = np.logical_and(np.logical_not(t_positive), c3)
208
+ s[cond3] = 2.0 * np.sqrt(1.0 + m22[cond3] - m00[cond3] - m11[cond3])
209
+ w[cond3] = (m10[cond3] - m01[cond3]) / s[cond3]
210
+ x[cond3] = (m02[cond3] + m20[cond3]) / s[cond3]
211
+ y[cond3] = (m12[cond3] + m21[cond3]) / s[cond3]
212
+ z[cond3] = 0.25 * s[cond3]
213
+ return np.array([w, x, y, z]).T
214
+
215
+
216
+ def load_gs_ply(path, T=None):
217
+ mesh = meshio.read(path)
218
+ vertices = mesh.points
219
+ pws = vertices[:, :3]
220
+
221
+ alphas = mesh.point_data['opacity']
222
+ alphas = 1 / (1 + np.exp(-alphas))
223
+
224
+ scales = np.vstack((mesh.point_data['scale_0'],
225
+ mesh.point_data['scale_1'],
226
+ mesh.point_data['scale_2'])).T
227
+
228
+ rots = np.vstack((mesh.point_data['rot_0'],
229
+ mesh.point_data['rot_1'],
230
+ mesh.point_data['rot_2'],
231
+ mesh.point_data['rot_3'])).T
232
+ rots /= np.linalg.norm(rots, axis=1)[:, np.newaxis]
233
+
234
+ sh_dim = len(mesh.point_data) - 11
235
+ shs = np.zeros([pws.shape[0], sh_dim])
236
+ shs[:, 0] = mesh.point_data['f_dc_0']
237
+ shs[:, 1] = mesh.point_data['f_dc_1']
238
+ shs[:, 2] = mesh.point_data['f_dc_2']
239
+
240
+ sh_rest_dim = sh_dim - 3
241
+ if sh_rest_dim > 0:
242
+ for i in range(sh_rest_dim):
243
+ name = f"f_rest_{i}"
244
+ shs[:, 3 + i] = mesh.point_data[name]
245
+ shs[:, 3:] = shs[:, 3:].reshape(-1, 3, sh_rest_dim // 3).transpose([0, 2, 1]).reshape(-1, sh_rest_dim)
246
+
247
+ pws = pws.astype(np.float32)
248
+ rots = rots.astype(np.float32)
249
+ scales = np.exp(scales).astype(np.float32)
250
+ alphas = alphas.astype(np.float32)
251
+ shs = shs.astype(np.float32)
252
+
253
+ dtypes = gsdata_type(sh_dim)
254
+
255
+ gs = np.rec.fromarrays(
256
+ [pws, rots, scales, alphas, shs], dtype=dtypes)
257
+
258
+ return gs
259
+
260
+
261
+ def rotate_gaussian(T, gs):
262
+ # Transform to world
263
+ pws = (T @ gs['pw'].T).T
264
+ w = gs['rot'][:, 0]
265
+ x = gs['rot'][:, 1]
266
+ y = gs['rot'][:, 2]
267
+ z = gs['rot'][:, 3]
268
+ R = np.array([
269
+ [1.0 - 2*(y**2 + z**2), 2*(x*y - z*w), 2*(x * z + y * w)],
270
+ [2*(x*y + z*w), 1.0 - 2*(x**2 + z**2), 2*(y*z - x*w)],
271
+ [2*(x*z - y*w), 2*(y*z + x*w), 1.0 - 2*(x**2 + y**2)]
272
+ ]).transpose(2, 0, 1)
273
+ R_new = T @ R
274
+ rots = matrix_to_quaternion_wxyz(R_new)
275
+ gs['pw'] = pws
276
+ gs['rot'] = rots
277
+ return gs
278
+
279
+
280
+ def load_gs(fn):
281
+ if fn.endswith('.ply'):
282
+ return load_gs_ply(fn)
283
+ elif fn.endswith('.npy'):
284
+ return np.load(fn)
285
+ else:
286
+ print("%s is not a supported file." % fn)
287
+ exit(0)
288
+
289
+
290
+ def save_gs(fn, gs):
291
+ np.save(fn, gs)
292
+
293
+
294
+ def get_example_gs():
295
+ gs_data = np.array([[0., 0., 0., # xyz
296
+ 1., 0., 0., 0., # rot
297
+ 0.05, 0.05, 0.05, # size
298
+ 1.,
299
+ 1.772484, -1.772484, 1.772484],
300
+ [1., 0., 0.,
301
+ 1., 0., 0., 0.,
302
+ 0.2, 0.05, 0.05,
303
+ 1.,
304
+ 1.772484, -1.772484, -1.772484],
305
+ [0., 1., 0.,
306
+ 1., 0., 0., 0.,
307
+ 0.05, 0.2, 0.05,
308
+ 1.,
309
+ -1.772484, 1.772484, -1.772484],
310
+ [0., 0., 1.,
311
+ 1., 0., 0., 0.,
312
+ 0.05, 0.05, 0.2,
313
+ 1.,
314
+ -1.772484, -1.772484, 1.772484]
315
+ ], dtype=np.float32)
316
+ dtypes = gsdata_type(3)
317
+ gs = np.frombuffer(gs_data.tobytes(), dtype=dtypes)
318
+ return gs
319
+
320
+
321
+ if __name__ == "__main__":
322
+ gs = load_gs("/home/liu/tmp.ply")
323
+ print(gs.shape)
@@ -0,0 +1,49 @@
1
+ """
2
+ Copyright 2024 Panasonic Advanced Technology Development Co.,Ltd. (Liu Yang)
3
+ Distributed under MIT license. See LICENSE for more information.
4
+ """
5
+
6
+ import numpy as np
7
+ from pypcd4 import PointCloud
8
+ from q3dviewer.utils.maths import make_transform
9
+
10
+
11
+ def convert_pointcloud2_msg(msg):
12
+ pc = PointCloud.from_msg(msg).pc_data
13
+ data_type = [('xyz', '<f4', (3,)), ('irgb', '<u4')]
14
+ rgb = np.zeros([pc.shape[0]], dtype=np.uint32)
15
+ intensity = np.zeros([pc.shape[0]], dtype=np.uint32)
16
+ fields = ['xyz']
17
+ if 'intensity' in pc.dtype.names:
18
+ intensity = pc['intensity'].astype(np.uint32)
19
+ fields.append('intensity')
20
+ if 'rgb' in pc.dtype.names:
21
+ rgb = pc['rgb'].view(np.uint32)
22
+ fields.append('rgb')
23
+ irgb = (intensity << 24) | rgb
24
+ xyz = np.stack([pc['x'], pc['y'], pc['z']], axis=1)
25
+ cloud = np.rec.fromarrays([xyz, irgb], dtype=data_type)
26
+ stamp = msg.header.stamp.to_sec()
27
+ return cloud, fields, stamp
28
+
29
+ def convert_odometry_msg(msg):
30
+ pose = np.array(
31
+ [msg.pose.pose.position.x,
32
+ msg.pose.pose.position.y,
33
+ msg.pose.pose.position.z])
34
+ rotation = np.array([
35
+ msg.pose.pose.orientation.x,
36
+ msg.pose.pose.orientation.y,
37
+ msg.pose.pose.orientation.z,
38
+ msg.pose.pose.orientation.w])
39
+ transform = make_transform(pose, rotation)
40
+ stamp = msg.header.stamp.to_sec()
41
+ return transform, stamp
42
+
43
+ def convert_image_msg(msg):
44
+ image = np.frombuffer(msg.data, dtype=np.uint8).reshape(
45
+ msg.height, msg.width, -1)
46
+ if (msg.encoding == 'bgr8'):
47
+ image = image[:, :, ::-1] # convert bgr to rgb
48
+ stamp = msg.header.stamp.to_sec()
49
+ return image, stamp
@@ -0,0 +1,40 @@
1
+ """
2
+ Copyright 2024 Panasonic Advanced Technology Development Co.,Ltd. (Liu Yang)
3
+ Distributed under MIT license. See LICENSE for more information.
4
+ """
5
+
6
+ from OpenGL.GL import *
7
+ import numpy as np
8
+
9
+
10
+ def set_uniform(shader, content, name):
11
+ location = glGetUniformLocation(shader, name)
12
+ if location == -1:
13
+ raise ValueError(
14
+ f"Uniform '{name}' not found in shader program {shader}.")
15
+
16
+ if isinstance(content, int):
17
+ glUniform1i(location, content)
18
+ elif isinstance(content, float):
19
+ glUniform1f(location, content)
20
+ elif isinstance(content, np.ndarray):
21
+ if content.ndim == 1:
22
+ if content.shape[0] == 2:
23
+ glUniform2f(location, *content)
24
+ elif content.shape[0] == 3:
25
+ glUniform3f(location, *content)
26
+ else:
27
+ raise ValueError(
28
+ f"Unsupported 1D array size: {content.shape}.")
29
+ elif content.ndim == 2:
30
+ if content.shape == (4, 4):
31
+ glUniformMatrix4fv(location, 1, GL_FALSE,
32
+ content.T.astype(np.float32))
33
+ else:
34
+ raise ValueError(
35
+ f"Unsupported 2D array size: {content.shape}.")
36
+ else:
37
+ raise ValueError(f"Unsupported array dimension: {content.ndim}.")
38
+ else:
39
+ raise TypeError(
40
+ f"Unsupported type for uniform '{name}': {type(content)}.")
@@ -0,0 +1,168 @@
1
+ """
2
+ Copyright 2024 Panasonic Advanced Technology Development Co.,Ltd. (Liu Yang)
3
+ Distributed under MIT license. See LICENSE for more information.
4
+ """
5
+
6
+ import numpy as np
7
+
8
+
9
+ def frustum(left, right, bottom, top, near, far):
10
+ # see https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/glFrustum.xml
11
+ if near <= 0 or far <= 0 or near >= far or left == right or bottom == top:
12
+ print("Invalid frustum parameters.")
13
+ return None
14
+ matrix = np.zeros((4, 4), dtype=np.float32)
15
+ matrix[0, 0] = 2.0 * near / (right - left)
16
+ matrix[0, 2] = (right + left) / (right - left)
17
+ matrix[1, 1] = 2.0 * near / (top - bottom)
18
+ matrix[1, 2] = (top + bottom) / (top - bottom)
19
+ matrix[2, 2] = -(far + near) / (far - near)
20
+ matrix[2, 3] = -2.0 * far * near / (far - near)
21
+ matrix[3, 2] = -1.0
22
+ return matrix
23
+
24
+
25
+ def rainbow(scalars, scalar_min=0, scalar_max=255):
26
+ range = scalar_max - scalar_min
27
+ values = 1.0 - (scalars - scalar_min) / range
28
+ # values = (scalars - scalar_min) / range # using inverted color
29
+ colors = np.zeros([scalars.shape[0], 3], dtype=np.float32)
30
+ values = np.clip(values, 0, 1)
31
+
32
+ h = values * 5.0 + 1.0
33
+ i = np.floor(h).astype(int)
34
+ f = h - i
35
+ f[np.logical_not(i % 2)] = 1 - f[np.logical_not(i % 2)]
36
+ n = 1 - f
37
+
38
+ # idx = i <= 1
39
+ colors[i <= 1, 0] = n[i <= 1] * 255
40
+ colors[i <= 1, 1] = 0
41
+ colors[i <= 1, 2] = 255
42
+
43
+ colors[i == 2, 0] = 0
44
+ colors[i == 2, 1] = n[i == 2] * 255
45
+ colors[i == 2, 2] = 255
46
+
47
+ colors[i == 3, 0] = 0
48
+ colors[i == 3, 1] = 255
49
+ colors[i == 3, 2] = n[i == 3] * 255
50
+
51
+ colors[i == 4, 0] = n[i == 4] * 255
52
+ colors[i == 4, 1] = 255
53
+ colors[i == 4, 2] = 0
54
+
55
+ colors[i >= 5, 0] = 255
56
+ colors[i >= 5, 1] = n[i >= 5] * 255
57
+ colors[i >= 5, 2] = 0
58
+ return colors
59
+
60
+
61
+ def euler_to_matrix(rpy):
62
+ roll, pitch, yaw = rpy
63
+ Rx = np.array([[1, 0, 0],
64
+ [0, np.cos(roll), -np.sin(roll)],
65
+ [0, np.sin(roll), np.cos(roll)]])
66
+ Ry = np.array([[np.cos(pitch), 0, np.sin(pitch)],
67
+ [0, 1, 0],
68
+ [-np.sin(pitch), 0, np.cos(pitch)]])
69
+ Rz = np.array([[np.cos(yaw), -np.sin(yaw), 0],
70
+ [np.sin(yaw), np.cos(yaw), 0],
71
+ [0, 0, 1]])
72
+ R = Rz @ Ry @ Rx
73
+ return R
74
+
75
+
76
+ def matrix_to_euler(R):
77
+ sy = np.sqrt(R[0, 0]**2 + R[1, 0]**2)
78
+ singular = sy < 1e-6 # Check for gimbal lock
79
+ if not singular:
80
+ roll = np.arctan2(R[2, 1], R[2, 2]) # X-axis rotation
81
+ pitch = np.arctan2(-R[2, 0], sy) # Y-axis rotation
82
+ yaw = np.arctan2(R[1, 0], R[0, 0]) # Z-axis rotation
83
+ else:
84
+ # Gimbal lock case
85
+ roll = np.arctan2(-R[1, 2], R[1, 1])
86
+ pitch = np.arctan2(-R[2, 0], sy)
87
+ yaw = 0 # Arbitrarily set yaw to 0
88
+
89
+ return np.array([roll, pitch, yaw])
90
+
91
+
92
+ def matrix_to_quaternion(matrix):
93
+ trace = matrix[0, 0] + matrix[1, 1] + matrix[2, 2]
94
+ if trace > 0:
95
+ s = 0.5 / np.sqrt(trace + 1.0)
96
+ w = 0.25 / s
97
+ x = (matrix[2, 1] - matrix[1, 2]) * s
98
+ y = (matrix[0, 2] - matrix[2, 0]) * s
99
+ z = (matrix[1, 0] - matrix[0, 1]) * s
100
+ else:
101
+ if matrix[0, 0] > matrix[1, 1] and matrix[0, 0] > matrix[2, 2]:
102
+ s = 2.0 * np.sqrt(1.0 + matrix[0, 0] - matrix[1, 1] - matrix[2, 2])
103
+ w = (matrix[2, 1] - matrix[1, 2]) / s
104
+ x = 0.25 * s
105
+ y = (matrix[0, 1] + matrix[1, 0]) / s
106
+ z = (matrix[0, 2] + matrix[2, 0]) / s
107
+ elif matrix[1, 1] > matrix[2, 2]:
108
+ s = 2.0 * np.sqrt(1.0 + matrix[1, 1] - matrix[0, 0] - matrix[2, 2])
109
+ w = (matrix[0, 2] - matrix[2, 0]) / s
110
+ x = (matrix[0, 1] + matrix[1, 0]) / s
111
+ y = 0.25 * s
112
+ z = (matrix[1, 2] + matrix[2, 1]) / s
113
+ else:
114
+ s = 2.0 * np.sqrt(1.0 + matrix[2, 2] - matrix[0, 0] - matrix[1, 1])
115
+ w = (matrix[1, 0] - matrix[0, 1]) / s
116
+ x = (matrix[0, 2] + matrix[2, 0]) / s
117
+ y = (matrix[1, 2] + matrix[2, 1]) / s
118
+ z = 0.25 * s
119
+ return np.array([x, y, z, w])
120
+
121
+
122
+ def quaternion_to_matrix(quaternion):
123
+ x, y, z, w = quaternion
124
+ q = np.array(quaternion[:4], dtype=np.float64, copy=True)
125
+ n = np.linalg.norm(q)
126
+ if np.any(n == 0.0):
127
+ raise ZeroDivisionError("bad quaternion input")
128
+ else:
129
+ m = np.empty((3, 3))
130
+ m[0, 0] = 1.0 - 2*(y**2 + z**2)/n
131
+ m[0, 1] = 2*(x*y - z*w)/n
132
+ m[0, 2] = 2*(x*z + y*w)/n
133
+ m[1, 0] = 2*(x*y + z*w)/n
134
+ m[1, 1] = 1.0 - 2*(x**2 + z**2)/n
135
+ m[1, 2] = 2*(y*z - x*w)/n
136
+ m[2, 0] = 2*(x*z - y*w)/n
137
+ m[2, 1] = 2*(y*z + x*w)/n
138
+ m[2, 2] = 1.0 - 2*(x**2 + y**2)/n
139
+ return m
140
+
141
+
142
+ def make_transform(pose, rotation):
143
+ transform = np.eye(4)
144
+ transform[0:3, 0:3] = quaternion_to_matrix(rotation)
145
+ transform[0:3, 3] = pose
146
+ return transform
147
+
148
+ def makeT(R, t):
149
+ T = np.eye(4)
150
+ T[0:3, 0:3] = R
151
+ T[0:3, 3] = t
152
+ return T
153
+
154
+ def makeRt(T):
155
+ R = T[0:3, 0:3]
156
+ t = T[0:3, 3]
157
+ return R, t
158
+
159
+ def hex_to_rgba(hex_color):
160
+ color_flat = int(hex_color[1:], 16)
161
+ red = (color_flat >> 16) & 0xFF
162
+ green = (color_flat >> 8) & 0xFF
163
+ blue = color_flat & 0xFF
164
+ return (red / 255.0, green / 255.0, blue / 255.0, 1.0)
165
+
166
+ # euler = np.array([1, 0.1, 0.1])
167
+ # euler_angles = matrix_to_euler(euler_to_matrix(euler))
168
+ # print("Euler Angles:", euler_angles)
@@ -0,0 +1,86 @@
1
+ """
2
+ Copyright 2024 Panasonic Advanced Technology Development Co.,Ltd. (Liu Yang)
3
+ Distributed under MIT license. See LICENSE for more information.
4
+ """
5
+
6
+ from PySide6.QtCore import Qt, Signal
7
+ from PySide6.QtGui import QPainter, QColor
8
+ from PySide6.QtWidgets import QSlider
9
+
10
+
11
+ class RangeSlider(QSlider):
12
+ # Signal emitted when the range changes
13
+ rangeChanged = Signal(int, int)
14
+
15
+ def __init__(self, orientation=Qt.Horizontal,
16
+ parent=None, vmin=0, vmax=255):
17
+ super().__init__(orientation, parent)
18
+ self.setMinimum(vmin)
19
+ self.setMaximum(vmax)
20
+ self.lower_value = vmin
21
+ self.upper_value = vmax
22
+ self.active_handle = None
23
+ self.setTickPosition(QSlider.NoTicks) # Hide original ticks
24
+ # Hide slider handle
25
+ self.setStyleSheet("QSlider::handle { background: transparent; }")
26
+
27
+ def mousePressEvent(self, event):
28
+ """Override to handle which handle is selected."""
29
+ pos = self.pixelPosToValue(event.pos())
30
+ if abs(pos - self.lower_value) < abs(pos - self.upper_value):
31
+ self.active_handle = "lower"
32
+ else:
33
+ self.active_handle = "upper"
34
+
35
+ def mouseMoveEvent(self, event):
36
+ """Override to update handle positions."""
37
+ if event.buttons() != Qt.LeftButton:
38
+ return
39
+
40
+ pos = self.pixelPosToValue(event.pos())
41
+ if self.active_handle == "lower":
42
+ self.lower_value = max(
43
+ self.minimum(), min(pos, self.upper_value - 1))
44
+ elif self.active_handle == "upper":
45
+ self.upper_value = min(
46
+ self.maximum(), max(pos, self.lower_value + 1))
47
+ self.rangeChanged.emit(self.lower_value, self.upper_value)
48
+ self.update()
49
+
50
+ def paintEvent(self, event):
51
+ """Override to paint custom range handles."""
52
+ painter = QPainter(self)
53
+
54
+ # Draw the range bar
55
+ bar_color = QColor(200, 200, 200) # Gray bar
56
+ highlight_color = QColor(100, 100, 255) # Blue for selected range
57
+ painter.setPen(Qt.NoPen)
58
+
59
+ bar_height = 6
60
+ bar_y = self.height() // 2 - bar_height // 2
61
+ painter.setBrush(bar_color)
62
+ painter.drawRect(0, bar_y, self.width(), bar_height)
63
+
64
+ # Draw the selected range
65
+ lower_x = int(self.valueToPixelPos(self.lower_value))
66
+ upper_x = int(self.valueToPixelPos(self.upper_value))
67
+ painter.setBrush(highlight_color)
68
+ painter.drawRect(lower_x, bar_y, upper_x - lower_x, bar_height)
69
+
70
+ # Draw the range handles
71
+ handle_color = QColor(50, 50, 255) # Blue handles
72
+ painter.setBrush(handle_color)
73
+ painter.drawEllipse(lower_x - 5, bar_y - 4, 12, 12)
74
+ painter.drawEllipse(upper_x - 5, bar_y - 4, 12, 12)
75
+
76
+ painter.end()
77
+
78
+ def pixelPosToValue(self, pos):
79
+ """Convert pixel position to slider value."""
80
+ return self.minimum() + (self.maximum() - self.minimum()) \
81
+ * pos.x() / self.width()
82
+
83
+ def valueToPixelPos(self, value):
84
+ """Convert slider value to pixel position."""
85
+ return self.width() * (value - self.minimum()) /\
86
+ (self.maximum() - self.minimum())