#
source:
orange/Orange/OrangeWidgets/VisualizeQt/OWSphereviz3D.py
@
11474:df0622184ee6

Revision 11474:df0622184ee6, 10.4 KB checked in by markotoplak, 12 months ago (diff) |
---|

Rev | Line | |
---|---|---|

[8735] | 1 | ''' |

[8732] | 2 | <name>Sphereviz 3D</name> |

[11474] | 3 | <icon>icons/Radviz.svg</icon> |

[8732] | 4 | <priority>2000</priority> |

[8735] | 5 | ''' |

[8732] | 6 | |

[8928] | 7 | import os |

[8847] | 8 | from math import pi |

9 | from random import choice | |

10 | ||

[8732] | 11 | from plot.owplot3d import * |

[8928] | 12 | from plot.owopenglrenderer import VertexBuffer |

[8735] | 13 | from plot.primitives import parse_obj |

[8732] | 14 | from OWLinProjQt import * |

[8747] | 15 | from OWLinProj3DPlot import OWLinProj3DPlot |

[8735] | 16 | |

[8742] | 17 | import orange |

18 | Discrete = orange.VarTypes.Discrete | |

19 | Continuous = orange.VarTypes.Continuous | |

20 | ||

[8747] | 21 | class OWSphereviz3DPlot(OWLinProj3DPlot): |

[8771] | 22 | def __init__(self, widget, parent=None, name='SpherevizPlot'): |

[8747] | 23 | OWLinProj3DPlot.__init__(self, widget, parent, name) |

[8732] | 24 | |

[8847] | 25 | self.camera_angle = 90 |

26 | self.camera_type = 0 # Default, center, attribute | |

[9045] | 27 | self.show_anchor_grid = False |

[8847] | 28 | |

[8810] | 29 | def _build_anchor_grid(self): |

30 | lines = [] | |

31 | num_parts = 30 | |

[8975] | 32 | anchors = numpy.array([a[:3] for a in self.anchor_data]) |

[8810] | 33 | for anchor in self.anchor_data: |

[8975] | 34 | a0 = numpy.array(anchor[:3]) |

[8810] | 35 | neighbours = anchors.copy() |

36 | neighbours = [(((n-a0)**2).sum(), n) for n in neighbours] | |

37 | neighbours.sort(key=lambda e: e[0]) | |

38 | for i in range(1, min(len(anchors), 4)): | |

39 | difference = neighbours[i][1]-a0 | |

40 | for j in range(num_parts): | |

41 | lines.extend(normalize(a0 + difference*j/float(num_parts))) | |

42 | lines.extend(normalize(a0 + difference*(j+1)/float(num_parts))) | |

43 | ||

[8928] | 44 | self._grid_buffer = VertexBuffer(numpy.array(lines, numpy.float32), [(3, GL_FLOAT)]) |

[8810] | 45 | |

[8928] | 46 | def update_data(self, labels=None, setAnchors=0, **args): |

[8810] | 47 | OWLinProj3DPlot.updateData(self, labels, setAnchors, **args) |

48 | ||

49 | if self.anchor_data: | |

50 | self._build_anchor_grid() | |

51 | ||

52 | self.updateGL() | |

[8742] | 53 | |

[8928] | 54 | updateData = update_data |

55 | ||

[9015] | 56 | def setData(self, data, subset_data=None, **args): |

57 | OWLinProj3DPlot.set_data(self, data, subset_data, **args) | |

[8732] | 58 | |

[8810] | 59 | # No need to generate backgroud grid sphere geometry more than once |

[8928] | 60 | if hasattr(self, '_sphere_buffer'): |

[8732] | 61 | return |

62 | ||

[8928] | 63 | self.makeCurrent() |

64 | ||

[8810] | 65 | lines = [] |

66 | num_parts = 30 | |

67 | num_horizontal_rings = 20 | |

68 | num_vertical_rings = 24 | |

69 | r = 1. | |

70 | ||

71 | for i in range(num_horizontal_rings): | |

72 | z_offset = float(i) * 2 / num_horizontal_rings - 1. | |

73 | r = (1. - z_offset**2)**0.5 | |

74 | for j in range(num_parts): | |

75 | angle_z_0 = float(j) * 2 * pi / num_parts | |

76 | angle_z_1 = float(j+1) * 2 * pi / num_parts | |

77 | lines.extend([sin(angle_z_0)*r, z_offset, cos(angle_z_0)*r, | |

78 | sin(angle_z_1)*r, z_offset, cos(angle_z_1)*r]) | |

79 | ||

80 | for i in range(num_vertical_rings): | |

81 | r = 1. | |

82 | phi = 2 * i * pi / num_vertical_rings | |

83 | for j in range(num_parts): | |

84 | theta_0 = (j) * pi / num_parts | |

85 | theta_1 = (j+1) * pi / num_parts | |

86 | lines.extend([sin(theta_0)*cos(phi)*r, cos(theta_0)*r, sin(theta_0)*sin(phi)*r, | |

87 | sin(theta_1)*cos(phi)*r, cos(theta_1)*r, sin(theta_1)*sin(phi)*r]) | |

[8732] | 88 | |

[8928] | 89 | self._sphere_buffer = VertexBuffer(numpy.array(lines, numpy.float32), [(3, GL_FLOAT)]) |

[8732] | 90 | |

[8928] | 91 | self._sphere_shader = QtOpenGL.QGLShaderProgram() |

92 | self._sphere_shader.addShaderFromSourceFile(QtOpenGL.QGLShader.Vertex, | |

93 | os.path.join(os.path.dirname(__file__), 'sphere.vs')) | |

94 | self._sphere_shader.addShaderFromSourceFile(QtOpenGL.QGLShader.Fragment, | |

95 | os.path.join(os.path.dirname(__file__), 'sphere.fs')) | |

[8732] | 96 | |

[8928] | 97 | self._sphere_shader.bindAttributeLocation('position', 0) |

[8732] | 98 | |

[8928] | 99 | if not self._sphere_shader.link(): |

[8732] | 100 | print('Failed to link sphere shader!') |

101 | ||

[8928] | 102 | ## Cones |

103 | cone_data = parse_obj('cone_hq.obj') | |

104 | vertices = [] | |

105 | for v0, v1, v2, n0, n1, n2 in cone_data: | |

106 | vertices.extend([v0[0],v0[1],v0[2], n0[0],n0[1],n0[2], | |

107 | v1[0],v1[1],v1[2], n1[0],n1[1],n1[2], | |

108 | v2[0],v2[1],v2[2], n2[0],n2[1],n2[2]]) | |

[8810] | 109 | |

[8928] | 110 | self._cone_buffer = VertexBuffer(numpy.array(vertices, numpy.float32), |

111 | [(3, GL_FLOAT), | |

112 | (3, GL_FLOAT)]) | |

[8810] | 113 | |

[8928] | 114 | self._cone_shader = QtOpenGL.QGLShaderProgram() |

115 | self._cone_shader.addShaderFromSourceFile(QtOpenGL.QGLShader.Vertex, | |

116 | os.path.join(os.path.dirname(__file__), 'cone.vs')) | |

117 | self._cone_shader.addShaderFromSourceFile(QtOpenGL.QGLShader.Fragment, | |

118 | os.path.join(os.path.dirname(__file__), 'cone.fs')) | |

[8810] | 119 | |

[8928] | 120 | self._cone_shader.bindAttributeLocation('position', 0) |

121 | self._cone_shader.bindAttributeLocation('normal', 1) | |

[8810] | 122 | |

[8928] | 123 | if not self._cone_shader.link(): |

124 | print('Failed to link cone shader!') | |

[8810] | 125 | |

[8928] | 126 | ## Another dummy shader (anchor grid) |

127 | self._grid_shader = QtOpenGL.QGLShaderProgram() | |

128 | self._grid_shader.addShaderFromSourceFile(QtOpenGL.QGLShader.Vertex, | |

129 | os.path.join(os.path.dirname(__file__), 'grid.vs')) | |

130 | self._grid_shader.addShaderFromSourceFile(QtOpenGL.QGLShader.Fragment, | |

131 | os.path.join(os.path.dirname(__file__), 'grid.fs')) | |

[8810] | 132 | |

[8928] | 133 | self._grid_shader.bindAttributeLocation('position', 0) |

[8810] | 134 | |

[8928] | 135 | if not self._grid_shader.link(): |

[8810] | 136 | print('Failed to link grid shader!') |

137 | ||

[8969] | 138 | self.before_draw_callback = self.before_draw |

[8732] | 139 | |

[8847] | 140 | def update_camera_type(self): |

141 | if self.camera_type == 2: | |

142 | self._random_anchor = choice(self.anchor_data) | |

143 | self.update() | |

144 | ||

[8747] | 145 | def before_draw(self): |

[8967] | 146 | view = QMatrix4x4() |

[8847] | 147 | if self.camera_type == 2: |

[8967] | 148 | view.lookAt( |

[8847] | 149 | QVector3D(self._random_anchor[0], self._random_anchor[1], self._random_anchor[2]), |

[8975] | 150 | self.camera, |

[8847] | 151 | QVector3D(0, 1, 0)) |

152 | projection = QMatrix4x4() | |

153 | projection.perspective(self.camera_angle, float(self.width()) / self.height(), | |

154 | 0.01, 5.) | |

155 | self.projection = projection | |

156 | elif self.camera_type == 1: | |

[8967] | 157 | view.lookAt( |

[8747] | 158 | QVector3D(0, 0, 0), |

[8975] | 159 | self.camera * self.camera_distance, |

[8747] | 160 | QVector3D(0, 1, 0)) |

161 | projection = QMatrix4x4() | |

[8847] | 162 | projection.perspective(self.camera_angle, float(self.width()) / self.height(), |

[8837] | 163 | 0.01, 5.) |

[8747] | 164 | self.projection = projection |

[8742] | 165 | else: |

[8967] | 166 | view.lookAt( |

[8975] | 167 | self.camera * self.camera_distance, |

[8747] | 168 | QVector3D(0, 0, 0), |

169 | QVector3D(0, 1, 0)) | |

[8967] | 170 | self.view = view |

[8748] | 171 | |

[8928] | 172 | self._draw_sphere() |

[8732] | 173 | |

[8810] | 174 | # Qt text rendering classes still use deprecated OpenGL functionality |

[8748] | 175 | glEnable(GL_DEPTH_TEST) |

176 | glDisable(GL_BLEND) | |

[8810] | 177 | glMatrixMode(GL_PROJECTION) |

178 | glLoadIdentity() | |

179 | glMultMatrixd(numpy.array(self.projection.data(), dtype=float)) | |

180 | glMatrixMode(GL_MODELVIEW) | |

181 | glLoadIdentity() | |

[8967] | 182 | glMultMatrixd(numpy.array(self.view.data(), dtype=float)) |

183 | glMultMatrixd(numpy.array(self.model.data(), dtype=float)) | |

[8748] | 184 | |

185 | if self.showAnchors: | |

186 | for anchor in self.anchor_data: | |

187 | x, y, z, label = anchor | |

188 | ||

189 | direction = QVector3D(x, y, z) | |

190 | up = QVector3D(0, 1, 0) | |

191 | right = QVector3D.crossProduct(direction, up).normalized() | |

192 | up = QVector3D.crossProduct(right, direction) | |

193 | rotation = QMatrix4x4() | |

194 | rotation.setColumn(0, QVector4D(right, 0)) | |

195 | rotation.setColumn(1, QVector4D(up, 0)) | |

196 | rotation.setColumn(2, QVector4D(direction, 0)) | |

197 | ||

[8967] | 198 | model = QMatrix4x4() |

199 | model.translate(x, y, z) | |

200 | model = model * rotation | |

201 | model.rotate(-90, 1, 0, 0) | |

202 | model.translate(0, -0.05, 0) | |

203 | model.scale(0.02, 0.02, 0.02) | |

[8748] | 204 | |

[8928] | 205 | self._cone_shader.bind() |

206 | self._cone_shader.setUniformValue('projection', self.projection) | |

[8967] | 207 | self._cone_shader.setUniformValue('modelview', self.view * model) |

[8928] | 208 | self._cone_buffer.draw() |

209 | self._cone_shader.release() | |

[8748] | 210 | |

[8810] | 211 | self.qglColor(self._theme.axis_values_color) |

[8748] | 212 | self.renderText(x*1.2, y*1.2, z*1.2, label) |

213 | ||

[8928] | 214 | if self.anchor_data and not hasattr(self, '_grid_buffer'): |

[8810] | 215 | self._build_anchor_grid() |

216 | ||

217 | # Draw grid between anchors | |

[9045] | 218 | if self.show_anchor_grid: |

219 | self._grid_shader.bind() | |

220 | self._grid_shader.setUniformValue('projection', self.projection) | |

221 | self._grid_shader.setUniformValue('modelview', self.view * self.model) | |

222 | self._grid_shader.setUniformValue('color', self._theme.axis_color) | |

223 | self._grid_buffer.draw(GL_LINES) | |

224 | self._grid_shader.release() | |

[8748] | 225 | |

[8751] | 226 | self._draw_value_lines() |

227 | ||

[8928] | 228 | def _draw_sphere(self): |

[8732] | 229 | glDisable(GL_DEPTH_TEST) |

230 | glEnable(GL_BLEND) | |

231 | glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) | |

232 | ||

[8928] | 233 | self._sphere_shader.bind() |

234 | self._sphere_shader.setUniformValue('projection', self.projection) | |

[8967] | 235 | self._sphere_shader.setUniformValue('modelview', self.view * self.model) |

[8975] | 236 | self._sphere_shader.setUniformValue('cam_position', self.camera * self.camera_distance) |

[8928] | 237 | self._sphere_shader.setUniformValue('use_transparency', self.camera_type == 0) |

238 | self._sphere_buffer.draw(GL_LINES) | |

239 | self._sphere_shader.release() | |

[8732] | 240 | |

[8743] | 241 | def mouseMoveEvent(self, event): |

[8847] | 242 | self.invert_mouse_x = self.camera_type != 0 |

[8747] | 243 | OWLinProj3DPlot.mouseMoveEvent(self, event) |

[8732] | 244 | |

245 | class OWSphereviz3D(OWLinProjQt): | |

246 | def __init__(self, parent=None, signalManager=None): | |

247 | OWLinProjQt.__init__(self, parent, signalManager, "Sphereviz 3D", graphClass=OWSphereviz3DPlot) | |

248 | ||

[9546] | 249 | self.inputs = [("Data", ExampleTable, self.setData, Default), |

250 | ("Data Subset", ExampleTable, self.setSubsetData), | |

251 | ("Features", AttributeList, self.setShownAttributes), | |

[8732] | 252 | ("Evaluation Results", orngTest.ExperimentResults, self.setTestResults), |

253 | ("VizRank Learner", orange.Learner, self.setVizRankLearner)] | |

[9546] | 254 | self.outputs = [("Selected Data", ExampleTable), |

255 | ("Other Data", ExampleTable), | |

256 | ("Features", AttributeList)] | |

[8732] | 257 | self.resize(1000, 600) |

258 | ||

259 | if __name__ == '__main__': | |

260 | app = QApplication(sys.argv) | |

261 | viz = OWSphereviz3D() | |

262 | viz.show() | |

263 | data = orange.ExampleTable('../../doc/datasets/iris') | |

264 | viz.setData(data) | |

265 | viz.handleNewSignals() | |

266 | app.exec_() |

**Note:**See TracBrowser for help on using the repository browser.