q3dviewer 1.0.0__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/__init__.py +4 -0
- q3dviewer/basic_viewer.py +56 -0
- q3dviewer/custom_items/__init__.py +9 -0
- q3dviewer/custom_items/axis_item.py +73 -0
- q3dviewer/custom_items/camera_frame_item.py +173 -0
- q3dviewer/custom_items/cloud_io_item.py +83 -0
- q3dviewer/custom_items/cloud_item.py +297 -0
- q3dviewer/custom_items/gaussian_item.py +267 -0
- q3dviewer/custom_items/grid_item.py +37 -0
- q3dviewer/custom_items/image_item.py +169 -0
- q3dviewer/custom_items/text_item.py +53 -0
- q3dviewer/custom_items/trajectory_item.py +78 -0
- q3dviewer/gau_io.py +168 -0
- q3dviewer/utils.py +146 -0
- q3dviewer/viewer_widget.py +183 -0
- q3dviewer-1.0.0.dist-info/METADATA +10 -0
- q3dviewer-1.0.0.dist-info/RECORD +20 -0
- q3dviewer-1.0.0.dist-info/WHEEL +5 -0
- q3dviewer-1.0.0.dist-info/entry_points.txt +7 -0
- q3dviewer-1.0.0.dist-info/top_level.txt +1 -0
q3dviewer/__init__.py
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
from q3dviewer.viewer_widget import *
|
|
4
|
+
import signal
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def handler(signal, frame):
|
|
9
|
+
QApplication.quit()
|
|
10
|
+
print("Force close by Ctrl+C")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Viewer(QMainWindow):
|
|
14
|
+
def __init__(self, name='Viewer', win_size=[1920, 1080], vw=ViewWidget):
|
|
15
|
+
signal.signal(signal.SIGINT, handler)
|
|
16
|
+
super(Viewer, self).__init__()
|
|
17
|
+
self.vw = vw
|
|
18
|
+
self.setGeometry(0, 0, win_size[0], win_size[1])
|
|
19
|
+
self.initUI()
|
|
20
|
+
self.setWindowTitle(name)
|
|
21
|
+
|
|
22
|
+
def initUI(self):
|
|
23
|
+
centerWidget = QWidget()
|
|
24
|
+
self.setCentralWidget(centerWidget)
|
|
25
|
+
layout = QVBoxLayout()
|
|
26
|
+
centerWidget.setLayout(layout)
|
|
27
|
+
self.viewerWidget = self.vw()
|
|
28
|
+
layout.addWidget(self.viewerWidget, 1)
|
|
29
|
+
timer = QtCore.QTimer(self)
|
|
30
|
+
timer.setInterval(20) # period, in milliseconds
|
|
31
|
+
timer.timeout.connect(self.update)
|
|
32
|
+
self.viewerWidget.setCameraPosition(distance=40)
|
|
33
|
+
timer.start()
|
|
34
|
+
|
|
35
|
+
def addItems(self, named_items: dict):
|
|
36
|
+
for name, item in named_items.items():
|
|
37
|
+
self.viewerWidget.addItem(name, item)
|
|
38
|
+
|
|
39
|
+
def __getitem__(self, name: str):
|
|
40
|
+
if name in self.viewerWidget.named_items:
|
|
41
|
+
return self.viewerWidget.named_items[name]
|
|
42
|
+
else:
|
|
43
|
+
return None
|
|
44
|
+
|
|
45
|
+
def update(self):
|
|
46
|
+
# force update by timer
|
|
47
|
+
self.viewerWidget.update()
|
|
48
|
+
|
|
49
|
+
def closeEvent(self, event):
|
|
50
|
+
event.accept()
|
|
51
|
+
QApplication.quit()
|
|
52
|
+
|
|
53
|
+
def show(self):
|
|
54
|
+
self.viewerWidget.setting_window.addSetting(
|
|
55
|
+
"main win", self.viewerWidget)
|
|
56
|
+
super().show()
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
from q3dviewer.custom_items.axis_item import *
|
|
2
|
+
from q3dviewer.custom_items.cloud_item import *
|
|
3
|
+
from q3dviewer.custom_items.cloud_io_item import *
|
|
4
|
+
from q3dviewer.custom_items.gaussian_item import *
|
|
5
|
+
from q3dviewer.custom_items.camera_frame_item import *
|
|
6
|
+
from q3dviewer.custom_items.grid_item import *
|
|
7
|
+
from q3dviewer.custom_items.text_item import *
|
|
8
|
+
from q3dviewer.custom_items.image_item import *
|
|
9
|
+
from q3dviewer.custom_items.trajectory_item import *
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
import pyqtgraph.opengl as gl
|
|
5
|
+
from OpenGL.GL import *
|
|
6
|
+
from OpenGL.GLU import *
|
|
7
|
+
from OpenGL.GLUT import *
|
|
8
|
+
import numpy as np
|
|
9
|
+
from PyQt5.QtWidgets import QLabel, QDoubleSpinBox
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class GLAxisItem(gl.GLGraphicsItem.GLGraphicsItem):
|
|
13
|
+
def __init__(self, size=1., width=5, glOptions='translucent'):
|
|
14
|
+
gl.GLGraphicsItem.GLGraphicsItem.__init__(self)
|
|
15
|
+
self.size = size
|
|
16
|
+
self.width = width
|
|
17
|
+
self.org = np.array([0, 0, 0, 1])
|
|
18
|
+
self.axis_x = np.array([self.size, 0, 0, 1])
|
|
19
|
+
self.axis_y = np.array([0, self.size, 0, 1])
|
|
20
|
+
self.axis_z = np.array([0, 0, self.size, 1])
|
|
21
|
+
self.setGLOptions(glOptions)
|
|
22
|
+
self.T = np.eye(4)
|
|
23
|
+
self.settings = []
|
|
24
|
+
|
|
25
|
+
def addSetting(self, layout):
|
|
26
|
+
label1 = QLabel("Set size:")
|
|
27
|
+
layout.addWidget(label1)
|
|
28
|
+
box1 = QDoubleSpinBox()
|
|
29
|
+
box1.setSingleStep(0.1)
|
|
30
|
+
layout.addWidget(box1)
|
|
31
|
+
box1.setValue(self.size)
|
|
32
|
+
box1.valueChanged.connect(self.setSize)
|
|
33
|
+
box1.setRange(0.0, 100)
|
|
34
|
+
|
|
35
|
+
label2 = QLabel("Set Width:")
|
|
36
|
+
layout.addWidget(label2)
|
|
37
|
+
box2 = QDoubleSpinBox()
|
|
38
|
+
layout.addWidget(box2)
|
|
39
|
+
box2.setSingleStep(0.1)
|
|
40
|
+
box2.setValue(self.width)
|
|
41
|
+
box2.valueChanged.connect(self.setWidth)
|
|
42
|
+
box2.setRange(0, 1000)
|
|
43
|
+
|
|
44
|
+
def setSize(self, size):
|
|
45
|
+
self.size = size
|
|
46
|
+
self.axis_x = np.array([self.size, 0, 0, 1])
|
|
47
|
+
self.axis_y = np.array([0, self.size, 0, 1])
|
|
48
|
+
self.axis_z = np.array([0, 0, self.size, 1])
|
|
49
|
+
|
|
50
|
+
def setWidth(self, width):
|
|
51
|
+
self.width = width
|
|
52
|
+
|
|
53
|
+
def setTransform(self, T):
|
|
54
|
+
self.T = T
|
|
55
|
+
|
|
56
|
+
def paint(self):
|
|
57
|
+
org = self.T.dot(self.org)
|
|
58
|
+
axis_x = self.T.dot(self.axis_x)
|
|
59
|
+
axis_y = self.T.dot(self.axis_y)
|
|
60
|
+
axis_z = self.T.dot(self.axis_z)
|
|
61
|
+
self.setupGLState()
|
|
62
|
+
glLineWidth(self.width)
|
|
63
|
+
glBegin(GL_LINES)
|
|
64
|
+
glColor4f(0, 0, 1, 1) # z is blue
|
|
65
|
+
glVertex3f(org[0], org[1], org[2])
|
|
66
|
+
glVertex3f(axis_z[0], axis_z[1], axis_z[2])
|
|
67
|
+
glColor4f(0, 1, 0, 1) # y is green
|
|
68
|
+
glVertex3f(org[0], org[1], org[2])
|
|
69
|
+
glVertex3f(axis_y[0], axis_y[1], axis_y[2])
|
|
70
|
+
glColor4f(1, 0, 0, 1) # x is red
|
|
71
|
+
glVertex3f(org[0], org[1], org[2])
|
|
72
|
+
glVertex3f(axis_x[0], axis_x[1], axis_x[2])
|
|
73
|
+
glEnd()
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import pyqtgraph.opengl as gl
|
|
2
|
+
from OpenGL.GL import *
|
|
3
|
+
import numpy as np
|
|
4
|
+
from OpenGL.GL import shaders
|
|
5
|
+
from PIL import Image as PIL_Image
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
# Vertex and Fragment shader source code
|
|
9
|
+
vertex_shader_source = """
|
|
10
|
+
#version 330 core
|
|
11
|
+
layout(location = 0) in vec3 position;
|
|
12
|
+
layout(location = 1) in vec2 texCoord;
|
|
13
|
+
|
|
14
|
+
out vec2 TexCoord;
|
|
15
|
+
|
|
16
|
+
uniform mat4 view_matrix;
|
|
17
|
+
uniform mat4 project_matrix;
|
|
18
|
+
|
|
19
|
+
void main()
|
|
20
|
+
{
|
|
21
|
+
gl_Position = project_matrix * view_matrix * vec4(position, 1.0);
|
|
22
|
+
TexCoord = texCoord;
|
|
23
|
+
}
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
fragment_shader_source = """
|
|
27
|
+
#version 330 core
|
|
28
|
+
in vec2 TexCoord;
|
|
29
|
+
out vec4 color;
|
|
30
|
+
uniform sampler2D ourTexture;
|
|
31
|
+
void main()
|
|
32
|
+
{
|
|
33
|
+
color = texture(ourTexture, TexCoord);
|
|
34
|
+
}
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def set_uniform_mat4(shader, content, name):
|
|
39
|
+
content = content.T
|
|
40
|
+
glUniformMatrix4fv(
|
|
41
|
+
glGetUniformLocation(shader, name),
|
|
42
|
+
1,
|
|
43
|
+
GL_FALSE,
|
|
44
|
+
content.astype(np.float32)
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class GLCameraFrameItem(gl.GLGraphicsItem.GLGraphicsItem):
|
|
49
|
+
def __init__(self, T=np.eye(4), size=1, width=3, path=None):
|
|
50
|
+
gl.GLGraphicsItem.GLGraphicsItem.__init__(self)
|
|
51
|
+
self.size = size
|
|
52
|
+
self.width = width
|
|
53
|
+
self.T = T
|
|
54
|
+
self.path = path
|
|
55
|
+
|
|
56
|
+
def initializeGL(self):
|
|
57
|
+
# Rectangle vertices and texture coordinates
|
|
58
|
+
hsize = self.size / 2
|
|
59
|
+
self.vertices = np.array([
|
|
60
|
+
# positions # texture coords
|
|
61
|
+
[-hsize, -hsize, 0.0, 0.0, 0.0], # bottom-left
|
|
62
|
+
[hsize, -hsize, 0.0, 1.0, 0.0], # bottom-right
|
|
63
|
+
[hsize, hsize, 0.0, 1.0, 1.0], # top-right
|
|
64
|
+
[-hsize, hsize, 0.0, 0.0, 1.0], # top-left
|
|
65
|
+
[0, 0, -hsize * 0.66, 0.0, 0.0], # top-left
|
|
66
|
+
], dtype=np.float32)
|
|
67
|
+
|
|
68
|
+
R = self.T[:3, :3]
|
|
69
|
+
t = self.T[:3, 3]
|
|
70
|
+
self.vertices[:, :3] = (
|
|
71
|
+
R @ self.vertices[:, :3].T + t[:, np.newaxis]).T
|
|
72
|
+
|
|
73
|
+
self.focal_p = np.array([0, 0, hsize * 0.66])
|
|
74
|
+
|
|
75
|
+
indices = np.array([
|
|
76
|
+
0, 1, 2, # first triangle
|
|
77
|
+
2, 3, 0 # second triangle
|
|
78
|
+
], dtype=np.uint32)
|
|
79
|
+
|
|
80
|
+
self.vao = glGenVertexArrays(1)
|
|
81
|
+
vbo = glGenBuffers(1)
|
|
82
|
+
ebo = glGenBuffers(1)
|
|
83
|
+
|
|
84
|
+
glBindVertexArray(self.vao)
|
|
85
|
+
|
|
86
|
+
glBindBuffer(GL_ARRAY_BUFFER, vbo)
|
|
87
|
+
glBufferData(GL_ARRAY_BUFFER, self.vertices.itemsize *
|
|
88
|
+
5 * 4, self.vertices, GL_STATIC_DRAW)
|
|
89
|
+
|
|
90
|
+
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo)
|
|
91
|
+
glBufferData(GL_ELEMENT_ARRAY_BUFFER,
|
|
92
|
+
indices.nbytes, indices, GL_STATIC_DRAW)
|
|
93
|
+
|
|
94
|
+
# Vertex positions
|
|
95
|
+
|
|
96
|
+
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE,
|
|
97
|
+
20, ctypes.c_void_p(0))
|
|
98
|
+
glEnableVertexAttribArray(0)
|
|
99
|
+
# Texture coordinates
|
|
100
|
+
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE,
|
|
101
|
+
20, ctypes.c_void_p(12))
|
|
102
|
+
glEnableVertexAttribArray(1)
|
|
103
|
+
|
|
104
|
+
project_matrix = np.array(self._GLGraphicsItem__view.projectionMatrix().data(),
|
|
105
|
+
np.float32).reshape([4, 4]).T
|
|
106
|
+
# Compile shaders and create shader program
|
|
107
|
+
self.program = shaders.compileProgram(
|
|
108
|
+
shaders.compileShader(vertex_shader_source, GL_VERTEX_SHADER),
|
|
109
|
+
shaders.compileShader(fragment_shader_source, GL_FRAGMENT_SHADER),
|
|
110
|
+
)
|
|
111
|
+
glUseProgram(self.program)
|
|
112
|
+
set_uniform_mat4(self.program, project_matrix, 'project_matrix')
|
|
113
|
+
glUseProgram(0)
|
|
114
|
+
|
|
115
|
+
self.texture = glGenTextures(1)
|
|
116
|
+
glBindTexture(GL_TEXTURE_2D, self.texture)
|
|
117
|
+
|
|
118
|
+
# Load image
|
|
119
|
+
image = PIL_Image.open(self.path)
|
|
120
|
+
# image = image.transpose(Image.FLIP_TOP_BOTTOM)
|
|
121
|
+
img_data = image.convert("RGBA").tobytes()
|
|
122
|
+
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image.width,
|
|
123
|
+
image.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, img_data)
|
|
124
|
+
glGenerateMipmap(GL_TEXTURE_2D)
|
|
125
|
+
glBindTexture(GL_TEXTURE_2D, 0)
|
|
126
|
+
glBindVertexArray(0)
|
|
127
|
+
|
|
128
|
+
def setTransform(self, T):
|
|
129
|
+
self.T = T
|
|
130
|
+
|
|
131
|
+
def paint(self):
|
|
132
|
+
self.view_matrix = np.array(
|
|
133
|
+
self._GLGraphicsItem__view.viewMatrix().data(), np.float32).reshape([4, 4]).T
|
|
134
|
+
project_matrix = np.array(self._GLGraphicsItem__view.projectionMatrix(
|
|
135
|
+
).data(), np.float32).reshape([4, 4]).T
|
|
136
|
+
|
|
137
|
+
glEnable(GL_DEPTH_TEST)
|
|
138
|
+
glEnable(GL_BLEND)
|
|
139
|
+
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
|
|
140
|
+
|
|
141
|
+
glUseProgram(self.program)
|
|
142
|
+
set_uniform_mat4(self.program, self.view_matrix, 'view_matrix')
|
|
143
|
+
set_uniform_mat4(self.program, project_matrix, 'project_matrix')
|
|
144
|
+
glBindVertexArray(self.vao)
|
|
145
|
+
glBindTexture(GL_TEXTURE_2D, self.texture)
|
|
146
|
+
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, None)
|
|
147
|
+
glBindTexture(GL_TEXTURE_2D, 0)
|
|
148
|
+
glBindVertexArray(0)
|
|
149
|
+
glUseProgram(0)
|
|
150
|
+
|
|
151
|
+
glLineWidth(self.width)
|
|
152
|
+
glBegin(GL_LINES)
|
|
153
|
+
glColor4f(1, 1, 1, 1) # z is blue
|
|
154
|
+
glVertex3f(*self.vertices[0, :3])
|
|
155
|
+
glVertex3f(*self.vertices[1, :3])
|
|
156
|
+
glVertex3f(*self.vertices[1, :3])
|
|
157
|
+
glVertex3f(*self.vertices[2, :3])
|
|
158
|
+
glVertex3f(*self.vertices[2, :3])
|
|
159
|
+
glVertex3f(*self.vertices[3, :3])
|
|
160
|
+
glVertex3f(*self.vertices[3, :3])
|
|
161
|
+
glVertex3f(*self.vertices[0, :3])
|
|
162
|
+
glVertex3f(*self.vertices[4, :3])
|
|
163
|
+
glVertex3f(*self.vertices[0, :3])
|
|
164
|
+
glVertex3f(*self.vertices[4, :3])
|
|
165
|
+
glVertex3f(*self.vertices[1, :3])
|
|
166
|
+
glVertex3f(*self.vertices[4, :3])
|
|
167
|
+
glVertex3f(*self.vertices[2, :3])
|
|
168
|
+
glVertex3f(*self.vertices[4, :3])
|
|
169
|
+
glVertex3f(*self.vertices[3, :3])
|
|
170
|
+
glEnd()
|
|
171
|
+
|
|
172
|
+
glDisable(GL_DEPTH_TEST)
|
|
173
|
+
glDisable(GL_BLEND)
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
from q3dviewer.custom_items.cloud_item import CloudItem
|
|
4
|
+
from pypcd4 import PointCloud, MetaData
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
import os
|
|
7
|
+
from PyQt5.QtWidgets import QPushButton, QLabel, QLineEdit, QMessageBox
|
|
8
|
+
import numpy as np
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class CloudIOItem(CloudItem):
|
|
12
|
+
"""
|
|
13
|
+
add save/load function to CloudItem
|
|
14
|
+
"""
|
|
15
|
+
def __init__(self, **kwargs):
|
|
16
|
+
super().__init__(**kwargs)
|
|
17
|
+
self.save_path = str(Path(os.getcwd(), "data.pcd"))
|
|
18
|
+
|
|
19
|
+
def addSetting(self, layout):
|
|
20
|
+
super().addSetting(layout)
|
|
21
|
+
|
|
22
|
+
label4 = QLabel("Save Path:")
|
|
23
|
+
layout.addWidget(label4)
|
|
24
|
+
box4 = QLineEdit()
|
|
25
|
+
box4.setText(self.save_path)
|
|
26
|
+
box4.textChanged.connect(self.setPath)
|
|
27
|
+
layout.addWidget(box4)
|
|
28
|
+
save_button = QPushButton("Save Cloud")
|
|
29
|
+
save_button.clicked.connect(self.save)
|
|
30
|
+
layout.addWidget(save_button)
|
|
31
|
+
|
|
32
|
+
def save(self):
|
|
33
|
+
cloud = self.buff[:self.valid_buff_top]
|
|
34
|
+
if self.save_path.endswith(".pcd"):
|
|
35
|
+
fields = ("x", "y", "z", "rgb")
|
|
36
|
+
metadata = MetaData.model_validate(
|
|
37
|
+
{
|
|
38
|
+
"fields": fields,
|
|
39
|
+
"size": [4, 4, 4, 4],
|
|
40
|
+
"type": ['F', 'F', 'F', 'U'],
|
|
41
|
+
"count": [1, 1, 1, 1],
|
|
42
|
+
"width": cloud.shape[0],
|
|
43
|
+
"points": cloud.shape[0],
|
|
44
|
+
})
|
|
45
|
+
pc = PointCloud(metadata, cloud)
|
|
46
|
+
try:
|
|
47
|
+
pc.save(self.save_path)
|
|
48
|
+
save_msg = QMessageBox()
|
|
49
|
+
save_msg.setIcon(QMessageBox.Information)
|
|
50
|
+
save_msg.setWindowTitle("save")
|
|
51
|
+
save_msg.setStandardButtons(QMessageBox.Ok)
|
|
52
|
+
save_msg.setText("save cloud to %s" % self.save_path)
|
|
53
|
+
except:
|
|
54
|
+
save_msg.setText("Cannot save to %s" % self.save_path)
|
|
55
|
+
save_msg.exec()
|
|
56
|
+
|
|
57
|
+
elif self.save_path.endswith(".ply"):
|
|
58
|
+
print("Not implment yet!")
|
|
59
|
+
else:
|
|
60
|
+
print("Not supported cloud file type!")
|
|
61
|
+
|
|
62
|
+
def load(self, file):
|
|
63
|
+
print("Try to load %s ..." % file)
|
|
64
|
+
pc = PointCloud.from_path(file).pc_data
|
|
65
|
+
|
|
66
|
+
try:
|
|
67
|
+
color = pc["rgb"].astype(np.uint32)
|
|
68
|
+
self.setColorMode('RGB')
|
|
69
|
+
except ValueError:
|
|
70
|
+
try:
|
|
71
|
+
color = pc["intensity"].astype(np.uint32)
|
|
72
|
+
self.setColorMode('I')
|
|
73
|
+
except ValueError:
|
|
74
|
+
color = pc['z'].astype(np.uint32)
|
|
75
|
+
self.setColorMode('#FFFFFF')
|
|
76
|
+
|
|
77
|
+
cloud = np.rec.fromarrays(
|
|
78
|
+
[np.stack([pc["x"], pc["y"], pc["z"]], axis=1), color],
|
|
79
|
+
dtype=self.data_type)
|
|
80
|
+
self.setData(data=cloud)
|
|
81
|
+
|
|
82
|
+
def setPath(self, path):
|
|
83
|
+
self.save_path = path
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
import pyqtgraph.opengl as gl
|
|
5
|
+
from OpenGL.GL import *
|
|
6
|
+
from OpenGL.GLU import *
|
|
7
|
+
from OpenGL.GLUT import *
|
|
8
|
+
import numpy as np
|
|
9
|
+
import threading
|
|
10
|
+
from PyQt5.QtWidgets import QLabel, QLineEdit, QDoubleSpinBox, QSpinBox
|
|
11
|
+
from PyQt5.QtGui import QValidator
|
|
12
|
+
from OpenGL.GL import shaders
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
vertex_shader = """
|
|
16
|
+
#version 330 core
|
|
17
|
+
|
|
18
|
+
layout (location = 0) in vec3 position;
|
|
19
|
+
layout (location = 1) in uint value;
|
|
20
|
+
|
|
21
|
+
uniform mat4 view_matrix;
|
|
22
|
+
uniform mat4 projection_matrix;
|
|
23
|
+
uniform float alpha;
|
|
24
|
+
uniform int color_mode;
|
|
25
|
+
uniform float vmin = 0;
|
|
26
|
+
uniform float vmax = 255;
|
|
27
|
+
out vec4 color;
|
|
28
|
+
|
|
29
|
+
vec3 getRainbowColor(uint value_raw) {
|
|
30
|
+
// Normalize value to [0, 1]
|
|
31
|
+
float range = vmax - vmin;
|
|
32
|
+
float value = 1.0 - (float(value_raw) - vmin) / range;
|
|
33
|
+
value = clamp(value, 0.0, 1.0);
|
|
34
|
+
// Convert value to hue in the range [0, 1]
|
|
35
|
+
float hue = value * 5.0 + 1.0;
|
|
36
|
+
int i = int(floor(hue));
|
|
37
|
+
float f = hue - float(i);
|
|
38
|
+
if (mod(i, 2) == 0) f = 1.0 - f; // if i is even
|
|
39
|
+
float n = 1.0 - f;
|
|
40
|
+
|
|
41
|
+
// Determine RGB components based on hue value
|
|
42
|
+
vec3 color;
|
|
43
|
+
if (i <= 1) color = vec3(n, 0.0, 1.0);
|
|
44
|
+
else if (i == 2) color = vec3(0.0, n, 1.0);
|
|
45
|
+
else if (i == 3) color = vec3(0.0, 1.0, n);
|
|
46
|
+
else if (i == 4) color = vec3(n, 1.0, 0.0);
|
|
47
|
+
else if (i >= 5) color = vec3(1.0, n, 0.0);
|
|
48
|
+
|
|
49
|
+
return color;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
void main()
|
|
53
|
+
{
|
|
54
|
+
vec4 pw = vec4(position, 1.0);
|
|
55
|
+
vec4 pc = view_matrix * pw;
|
|
56
|
+
gl_Position = projection_matrix * pc;
|
|
57
|
+
vec3 c = vec3(1.0, 1.0, 1.0);
|
|
58
|
+
if (color_mode == -1)
|
|
59
|
+
{
|
|
60
|
+
c = getRainbowColor(value);
|
|
61
|
+
}
|
|
62
|
+
else if(color_mode == -2)
|
|
63
|
+
{
|
|
64
|
+
c.z = float(value & uint(0x000000FF))/255.;
|
|
65
|
+
c.y = float((value & uint(0x0000FF00)) >> 8)/255.;
|
|
66
|
+
c.x = float((value & uint(0x00FF0000)) >> 16)/255.;
|
|
67
|
+
}
|
|
68
|
+
else if(color_mode == -3)
|
|
69
|
+
{
|
|
70
|
+
uint intensity = value >> 24;
|
|
71
|
+
c = getRainbowColor(intensity);
|
|
72
|
+
}
|
|
73
|
+
else
|
|
74
|
+
{
|
|
75
|
+
c.z = float( uint(color_mode) & uint(0x000000FF))/255.;
|
|
76
|
+
c.y = float((uint(color_mode) & uint(0x0000FF00)) >> 8)/255.;
|
|
77
|
+
c.x = float((uint(color_mode) & uint(0x00FF0000)) >> 16)/255.;
|
|
78
|
+
}
|
|
79
|
+
color = vec4(c, alpha);
|
|
80
|
+
}
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
fragment_shader = """
|
|
84
|
+
#version 330 core
|
|
85
|
+
|
|
86
|
+
in vec4 color;
|
|
87
|
+
|
|
88
|
+
out vec4 finalColor;
|
|
89
|
+
|
|
90
|
+
void main()
|
|
91
|
+
{
|
|
92
|
+
finalColor = color;
|
|
93
|
+
}
|
|
94
|
+
"""
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def set_uniform_mat4(shader, content, name):
|
|
98
|
+
content = content.T
|
|
99
|
+
glUniformMatrix4fv(
|
|
100
|
+
glGetUniformLocation(shader, name),
|
|
101
|
+
1,
|
|
102
|
+
GL_FALSE,
|
|
103
|
+
content.astype(np.float32)
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
# draw points with color (x, y, z, color)
|
|
108
|
+
class CloudItem(gl.GLGraphicsItem.GLGraphicsItem):
|
|
109
|
+
def __init__(self, size, alpha, color_mode='I'):
|
|
110
|
+
super().__init__()
|
|
111
|
+
self.valid_buff_top = 0
|
|
112
|
+
self.add_buff_loc = 0
|
|
113
|
+
self.alpha = alpha
|
|
114
|
+
self.size = size
|
|
115
|
+
self.mutex = threading.Lock()
|
|
116
|
+
self.data_type = [('xyz', '<f4', (3,)), ('color', '<u4')]
|
|
117
|
+
self.color_mode = color_mode
|
|
118
|
+
self.setColorMode(color_mode)
|
|
119
|
+
self.CAPACITY = 10000000 # 10MB * 3 (x,y,z, color) * 4
|
|
120
|
+
self.vmax = 255
|
|
121
|
+
self.buff = np.empty((0), self.data_type)
|
|
122
|
+
self.wait_add_data = None
|
|
123
|
+
self.need_update_setting = True
|
|
124
|
+
|
|
125
|
+
def addSetting(self, layout):
|
|
126
|
+
label1 = QLabel("Set Size:")
|
|
127
|
+
layout.addWidget(label1)
|
|
128
|
+
box1 = QSpinBox()
|
|
129
|
+
box1.setSingleStep(1)
|
|
130
|
+
layout.addWidget(box1)
|
|
131
|
+
box1.setValue(self.size)
|
|
132
|
+
box1.valueChanged.connect(self.setSize)
|
|
133
|
+
box1.setRange(0, 100)
|
|
134
|
+
|
|
135
|
+
label2 = QLabel("Set Alpha:")
|
|
136
|
+
layout.addWidget(label2)
|
|
137
|
+
box2 = QDoubleSpinBox()
|
|
138
|
+
layout.addWidget(box2)
|
|
139
|
+
box2.setSingleStep(0.01)
|
|
140
|
+
box2.setValue(self.alpha)
|
|
141
|
+
box2.valueChanged.connect(self.setAlpha)
|
|
142
|
+
box2.setRange(0, 1)
|
|
143
|
+
|
|
144
|
+
label3 = QLabel("Set ColorMode:")
|
|
145
|
+
label3.setToolTip(
|
|
146
|
+
"'I': intensity mode; 'IRGB': IRGB mode; 'RGB': rgb mode; '#xxxxxx'; matplotlib color, i.e. #FF4500;")
|
|
147
|
+
layout.addWidget(label3)
|
|
148
|
+
box3 = QLineEdit()
|
|
149
|
+
box3.setToolTip(
|
|
150
|
+
"'I': intensity mode; 'IRGB': IRGB mode; 'RGB': rgb mode; '#xxxxxx'; matplotlib color, i.e. #FF4500;")
|
|
151
|
+
|
|
152
|
+
box3.setText(str(self.color_mode))
|
|
153
|
+
box3.textChanged.connect(self.setColorMode)
|
|
154
|
+
layout.addWidget(box3)
|
|
155
|
+
|
|
156
|
+
def setAlpha(self, alpha):
|
|
157
|
+
self.alpha = alpha
|
|
158
|
+
self.need_update_setting = True
|
|
159
|
+
|
|
160
|
+
def setVmax(self, vmax):
|
|
161
|
+
self.vmax = vmax
|
|
162
|
+
self.need_update_setting = True
|
|
163
|
+
|
|
164
|
+
def setColorMode(self, color_mode):
|
|
165
|
+
"""
|
|
166
|
+
intensity mode: -1;
|
|
167
|
+
rgb mode: -2;
|
|
168
|
+
matplotlib color: i.e. '#FF4500';
|
|
169
|
+
"""
|
|
170
|
+
if (type(color_mode) == str):
|
|
171
|
+
if color_mode.startswith("#"):
|
|
172
|
+
try:
|
|
173
|
+
self.color_mode_int = int(color_mode[1:], 16)
|
|
174
|
+
except ValueError:
|
|
175
|
+
return
|
|
176
|
+
elif color_mode == 'RGB':
|
|
177
|
+
self.color_mode_int = -2
|
|
178
|
+
elif color_mode == 'IRGB':
|
|
179
|
+
self.color_mode_int = -3
|
|
180
|
+
elif color_mode == 'I':
|
|
181
|
+
self.color_mode_int = -1
|
|
182
|
+
else:
|
|
183
|
+
return
|
|
184
|
+
self.color_mode = color_mode
|
|
185
|
+
self.need_update_setting = True
|
|
186
|
+
|
|
187
|
+
def setSize(self, size):
|
|
188
|
+
self.size = size
|
|
189
|
+
|
|
190
|
+
def clear(self):
|
|
191
|
+
data = np.empty((0), self.data_type)
|
|
192
|
+
self.setData(data)
|
|
193
|
+
|
|
194
|
+
def setData(self, data, append=False):
|
|
195
|
+
if data.dtype is np.dtype('float32') or data.dtype is np.dtype('float64'):
|
|
196
|
+
xyz = data[:, :3]
|
|
197
|
+
if (data.shape[1] == 4):
|
|
198
|
+
color = data[:, 3].view(np.uint32)
|
|
199
|
+
else:
|
|
200
|
+
color = np.zeros(data.shape[0], dtype=np.uint32)
|
|
201
|
+
data = np.rec.fromarrays(
|
|
202
|
+
[xyz, color],
|
|
203
|
+
dtype=self.data_type)
|
|
204
|
+
self.mutex.acquire()
|
|
205
|
+
if (append is False):
|
|
206
|
+
self.wait_add_data = data
|
|
207
|
+
self.add_buff_loc = 0
|
|
208
|
+
else:
|
|
209
|
+
if (self.wait_add_data is None):
|
|
210
|
+
self.wait_add_data = data
|
|
211
|
+
else:
|
|
212
|
+
self.wait_add_data = np.concatenate([self.wait_add_data, data])
|
|
213
|
+
self.add_buff_loc = self.valid_buff_top
|
|
214
|
+
self.mutex.release()
|
|
215
|
+
|
|
216
|
+
def updateSetting(self):
|
|
217
|
+
if (self.need_update_setting is False):
|
|
218
|
+
return
|
|
219
|
+
glUseProgram(self.program)
|
|
220
|
+
glUniform1i(glGetUniformLocation(
|
|
221
|
+
self.program, "color_mode"), self.color_mode_int)
|
|
222
|
+
glUniform1f(glGetUniformLocation(self.program, "vmax"), self.vmax)
|
|
223
|
+
glUniform1f(glGetUniformLocation(self.program, "alpha"), self.alpha)
|
|
224
|
+
glUseProgram(0)
|
|
225
|
+
self.need_update_setting = False
|
|
226
|
+
|
|
227
|
+
def updateRenderBuffer(self):
|
|
228
|
+
# is not new data dont update buff
|
|
229
|
+
if (self.wait_add_data is None):
|
|
230
|
+
return
|
|
231
|
+
self.mutex.acquire()
|
|
232
|
+
|
|
233
|
+
new_buff_top = self.add_buff_loc + self.wait_add_data.shape[0]
|
|
234
|
+
if new_buff_top > self.buff.shape[0]:
|
|
235
|
+
# if need to update buff capacity, create new cpu buff and new vbo
|
|
236
|
+
buff_capacity = self.buff.shape[0]
|
|
237
|
+
while (new_buff_top > buff_capacity):
|
|
238
|
+
buff_capacity += self.CAPACITY
|
|
239
|
+
print("Update capacity to %d" % buff_capacity)
|
|
240
|
+
new_buff = np.empty((buff_capacity), self.data_type)
|
|
241
|
+
new_buff[:self.add_buff_loc] = self.buff[:self.add_buff_loc]
|
|
242
|
+
new_buff[self.add_buff_loc:new_buff_top] = self.wait_add_data
|
|
243
|
+
self.buff = new_buff
|
|
244
|
+
glBindBuffer(GL_ARRAY_BUFFER, self.vbo)
|
|
245
|
+
glBufferData(GL_ARRAY_BUFFER, self.buff.nbytes,
|
|
246
|
+
self.buff, GL_DYNAMIC_DRAW)
|
|
247
|
+
glBindBuffer(GL_ARRAY_BUFFER, 0)
|
|
248
|
+
else:
|
|
249
|
+
self.buff[self.add_buff_loc:new_buff_top] = self.wait_add_data
|
|
250
|
+
glBindBuffer(GL_ARRAY_BUFFER, self.vbo)
|
|
251
|
+
glBufferSubData(GL_ARRAY_BUFFER, self.add_buff_loc * 16,
|
|
252
|
+
self.wait_add_data.shape[0] * 16, self.wait_add_data)
|
|
253
|
+
self.valid_buff_top = new_buff_top
|
|
254
|
+
self.wait_add_data = None
|
|
255
|
+
self.mutex.release()
|
|
256
|
+
|
|
257
|
+
def initializeGL(self):
|
|
258
|
+
self.program = shaders.compileProgram(
|
|
259
|
+
shaders.compileShader(vertex_shader, GL_VERTEX_SHADER),
|
|
260
|
+
shaders.compileShader(fragment_shader, GL_FRAGMENT_SHADER),
|
|
261
|
+
)
|
|
262
|
+
# Bind attribute locations
|
|
263
|
+
# set constant parameter for cloud shader
|
|
264
|
+
self.setAlpha(self.alpha)
|
|
265
|
+
self.setColorMode(self.color_mode)
|
|
266
|
+
self.setVmax(self.vmax)
|
|
267
|
+
self.vbo = glGenBuffers(1)
|
|
268
|
+
|
|
269
|
+
def paint(self):
|
|
270
|
+
self.setupGLState()
|
|
271
|
+
self.updateRenderBuffer()
|
|
272
|
+
self.updateSetting()
|
|
273
|
+
glEnable(GL_BLEND)
|
|
274
|
+
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
|
|
275
|
+
glUseProgram(self.program)
|
|
276
|
+
glBindBuffer(GL_ARRAY_BUFFER, self.vbo)
|
|
277
|
+
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 16, ctypes.c_void_p(0))
|
|
278
|
+
glVertexAttribPointer(
|
|
279
|
+
1, 1, GL_FLOAT, GL_UNSIGNED_INT, 16, ctypes.c_void_p(12))
|
|
280
|
+
glEnableVertexAttribArray(0)
|
|
281
|
+
glEnableVertexAttribArray(1)
|
|
282
|
+
|
|
283
|
+
view_matrix = np.array(
|
|
284
|
+
self._GLGraphicsItem__view.viewMatrix().data(), np.float32).reshape([4, 4]).T
|
|
285
|
+
set_uniform_mat4(self.program, view_matrix, 'view_matrix')
|
|
286
|
+
project_matrix = np.array(self._GLGraphicsItem__view.projectionMatrix(
|
|
287
|
+
).data(), np.float32).reshape([4, 4]).T
|
|
288
|
+
set_uniform_mat4(self.program, project_matrix, 'projection_matrix')
|
|
289
|
+
|
|
290
|
+
glPointSize(self.size)
|
|
291
|
+
glDrawArrays(GL_POINTS, 0, self.valid_buff_top)
|
|
292
|
+
|
|
293
|
+
# unbind VBO
|
|
294
|
+
glDisableVertexAttribArray(0)
|
|
295
|
+
glDisableVertexAttribArray(1)
|
|
296
|
+
glBindBuffer(GL_ARRAY_BUFFER, 0)
|
|
297
|
+
glUseProgram(0)
|