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.
- q3dviewer/custom_items/cloud_io_item.py +15 -5
- q3dviewer/custom_items/cloud_item.py +16 -16
- q3dviewer/custom_items/frame_item.py +3 -7
- q3dviewer/custom_items/grid_item.py +7 -9
- q3dviewer/custom_items/line_item.py +14 -10
- q3dviewer/custom_items/text_item.py +7 -5
- q3dviewer/glwidget.py +8 -14
- q3dviewer/tools/film_maker.py +0 -2
- q3dviewer/utils/cloud_io.py +41 -26
- q3dviewer/utils/maths.py +21 -19
- {q3dviewer-1.1.3.dist-info → q3dviewer-1.1.5.dist-info}/METADATA +28 -7
- {q3dviewer-1.1.3.dist-info → q3dviewer-1.1.5.dist-info}/RECORD +16 -16
- {q3dviewer-1.1.3.dist-info → q3dviewer-1.1.5.dist-info}/LICENSE +0 -0
- {q3dviewer-1.1.3.dist-info → q3dviewer-1.1.5.dist-info}/WHEEL +0 -0
- {q3dviewer-1.1.3.dist-info → q3dviewer-1.1.5.dist-info}/entry_points.txt +0 -0
- {q3dviewer-1.1.3.dist-info → q3dviewer-1.1.5.dist-info}/top_level.txt +0 -0
|
@@ -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
|
|
64
|
+
cloud = load_pcd(file)
|
|
65
65
|
elif file.endswith(".ply"):
|
|
66
|
-
cloud
|
|
66
|
+
cloud = load_ply(file)
|
|
67
67
|
elif file.endswith(".e57"):
|
|
68
|
-
cloud
|
|
68
|
+
cloud = load_e57(file)
|
|
69
69
|
elif file.endswith(".las"):
|
|
70
|
-
cloud
|
|
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
|
-
|
|
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='
|
|
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.
|
|
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("
|
|
95
|
-
self.edit_rgb.setText(
|
|
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
|
|
133
|
-
|
|
134
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
166
|
-
self.rgba =
|
|
167
|
-
|
|
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
|
|
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 =
|
|
25
|
+
self.rgba = text_to_rgba(color)
|
|
28
26
|
except ValueError:
|
|
29
|
-
|
|
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
|
|
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
|
-
|
|
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("
|
|
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 =
|
|
55
|
+
self.rgb = text_to_rgba(color)
|
|
52
56
|
self.color = color
|
|
53
57
|
except ValueError:
|
|
54
|
-
|
|
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
|
|
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
|
-
|
|
20
|
-
|
|
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 =
|
|
49
|
+
self.rgb = text_to_rgba(color)
|
|
48
50
|
self.color = color
|
|
49
51
|
except ValueError:
|
|
50
|
-
|
|
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
|
|
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 = '
|
|
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,
|
|
108
|
+
def set_bg_color(self, color):
|
|
112
109
|
try:
|
|
113
|
-
|
|
114
|
-
red = (
|
|
115
|
-
green
|
|
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
|
-
|
|
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})
|
q3dviewer/tools/film_maker.py
CHANGED
|
@@ -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.
|
q3dviewer/utils/cloud_io.py
CHANGED
|
@@ -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
|
|
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
|
-
|
|
55
|
-
|
|
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
|
|
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
|
|
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 /
|
|
140
|
-
green = (green /
|
|
141
|
-
blue = (blue /
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
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
|
-
|
|
314
|
-
|
|
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
|
+
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
|
-
|
|
24
|
+
|
|
25
|
+

|
|
26
|
+
|
|
27
|
+
[](https://opensource.org/licenses/MIT)
|
|
28
|
+
[](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
|
|
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
|
-
|
|
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
|
|
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
|
-

|
|
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=
|
|
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=
|
|
14
|
-
q3dviewer/custom_items/cloud_item.py,sha256=
|
|
15
|
-
q3dviewer/custom_items/frame_item.py,sha256=
|
|
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=
|
|
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=
|
|
20
|
-
q3dviewer/custom_items/text_item.py,sha256=
|
|
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=
|
|
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=
|
|
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=
|
|
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.
|
|
43
|
-
q3dviewer-1.1.
|
|
44
|
-
q3dviewer-1.1.
|
|
45
|
-
q3dviewer-1.1.
|
|
46
|
-
q3dviewer-1.1.
|
|
47
|
-
q3dviewer-1.1.
|
|
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,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|