Advertisement
Guest User

Untitled

a guest
Dec 12th, 2019
88
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 18.20 KB | None | 0 0
  1. from ..Qt import QtCore, QtGui, QtOpenGL, USE_PYQT5
  2. from OpenGL.GL import *
  3. import OpenGL.GL.framebufferobjects as glfbo
  4. import numpy as np
  5. from .. import Vector
  6. from .. import functions as fn
  7.  
  8. ##Vector = QtGui.QVector3D
  9.  
  10. ShareWidget = None
  11.  
  12. class GLViewWidget(QtOpenGL.QGLWidget):
  13. """
  14. Basic widget for displaying 3D data
  15. - Rotation/scale controls
  16. - Axis/grid display
  17. - Export options
  18.  
  19. """
  20.  
  21. def __init__(self, parent=None):
  22. global ShareWidget
  23.  
  24. if ShareWidget is None:
  25. ## create a dummy widget to allow sharing objects (textures, shaders, etc) between views
  26. ShareWidget = QtOpenGL.QGLWidget()
  27.  
  28. QtOpenGL.QGLWidget.__init__(self, parent, ShareWidget)
  29.  
  30. self.setFocusPolicy(QtCore.Qt.ClickFocus)
  31.  
  32. self.opts = {
  33. 'center': Vector(0,0,0), ## will always appear at the center of the widget
  34. 'distance': 10.0, ## distance of camera from center
  35. 'fov': 60, ## horizontal field of view in degrees
  36. 'elevation': 30, ## camera's angle of elevation in degrees
  37. 'azimuth': 45, ## camera's azimuthal angle in degrees
  38. ## (rotation around z-axis 0 points along x-axis)
  39. 'viewport': None, ## glViewport params; None == whole widget
  40. }
  41. self.setBackgroundColor('k')
  42. self.items = []
  43. self.noRepeatKeys = [QtCore.Qt.Key_Right, QtCore.Qt.Key_Left, QtCore.Qt.Key_Up, QtCore.Qt.Key_Down, QtCore.Qt.Key_PageUp, QtCore.Qt.Key_PageDown]
  44. self.keysPressed = {}
  45. self.keyTimer = QtCore.QTimer()
  46. self.keyTimer.timeout.connect(self.evalKeyState)
  47.  
  48. self.makeCurrent()
  49.  
  50. def addItem(self, item):
  51. self.items.append(item)
  52. if hasattr(item, 'initializeGL'):
  53. self.makeCurrent()
  54. try:
  55. item.initializeGL()
  56. except:
  57. self.checkOpenGLVersion('Error while adding item %s to GLViewWidget.' % str(item))
  58.  
  59. item._setView(self)
  60. #print "set view", item, self, item.view()
  61. self.update()
  62.  
  63. def removeItem(self, item):
  64. self.items.remove(item)
  65. item._setView(None)
  66. self.update()
  67.  
  68.  
  69. def initializeGL(self):
  70. self.resizeGL(self.width(), self.height())
  71.  
  72. def setBackgroundColor(self, *args, **kwds):
  73. """
  74. Set the background color of the widget. Accepts the same arguments as
  75. pg.mkColor() and pg.glColor().
  76. """
  77. self.opts['bgcolor'] = fn.glColor(*args, **kwds)
  78. self.update()
  79.  
  80. def getViewport(self):
  81. vp = self.opts['viewport']
  82. if vp is None:
  83. return (0, 0, self.width(), self.height())
  84. else:
  85. return vp
  86.  
  87. def resizeGL(self, w, h):
  88. pass
  89. #glViewport(*self.getViewport())
  90. #self.update()
  91.  
  92. def setProjection(self, region=None):
  93. m = self.projectionMatrix(region)
  94. glMatrixMode(GL_PROJECTION)
  95. glLoadIdentity()
  96. a = np.array(m.copyDataTo()).reshape((4,4))
  97. glMultMatrixf(a.transpose())
  98.  
  99. def projectionMatrix(self, region=None):
  100. # Xw = (Xnd + 1) * width/2 + X
  101. if region is None:
  102. region = (0, 0, self.width(), self.height())
  103.  
  104. x0, y0, w, h = self.getViewport()
  105. dist = self.opts['distance']
  106. fov = self.opts['fov']
  107. nearClip = dist * 0.001
  108. farClip = dist * 1000.
  109.  
  110. r = nearClip * np.tan(fov * 0.5 * np.pi / 180.)
  111. t = r * h / w
  112.  
  113. # convert screen coordinates (region) to normalized device coordinates
  114. # Xnd = (Xw - X0) * 2/width - 1
  115. ## Note that X0 and width in these equations must be the values used in viewport
  116. left = r * ((region[0]-x0) * (2.0/w) - 1)
  117. right = r * ((region[0]+region[2]-x0) * (2.0/w) - 1)
  118. bottom = t * ((region[1]-y0) * (2.0/h) - 1)
  119. top = t * ((region[1]+region[3]-y0) * (2.0/h) - 1)
  120.  
  121. tr = QtGui.QMatrix4x4()
  122. tr.frustum(left, right, bottom, top, nearClip, farClip)
  123. return tr
  124.  
  125. def setModelview(self):
  126. glMatrixMode(GL_MODELVIEW)
  127. glLoadIdentity()
  128. m = self.viewMatrix()
  129. a = np.array(m.copyDataTo()).reshape((4,4))
  130. glMultMatrixf(a.transpose())
  131.  
  132. def viewMatrix(self):
  133. tr = QtGui.QMatrix4x4()
  134. tr.translate( 0.0, 0.0, -self.opts['distance'])
  135. tr.rotate(self.opts['elevation']-90, 1, 0, 0)
  136. tr.rotate(self.opts['azimuth']+90, 0, 0, -1)
  137. center = self.opts['center']
  138. tr.translate(-center.x(), -center.y(), -center.z())
  139. return tr
  140.  
  141. def itemsAt(self, region=None):
  142. """
  143. Return a list of the items displayed in the region (x, y, w, h)
  144. relative to the widget.
  145. """
  146. region = (region[0], self.height()-(region[1]+region[3]), region[2], region[3])
  147.  
  148. #buf = np.zeros(100000, dtype=np.uint)
  149. buf = glSelectBuffer(100000)
  150. try:
  151. glRenderMode(GL_SELECT)
  152. glInitNames()
  153. glPushName(0)
  154. self._itemNames = {}
  155. self.paintGL(region=region, useItemNames=True)
  156.  
  157. finally:
  158. hits = glRenderMode(GL_RENDER)
  159.  
  160. items = [(h.near, h.names[0]) for h in hits]
  161. items.sort(key=lambda i: i[0])
  162. return [self._itemNames[i[1]] for i in items]
  163.  
  164. def paintGL(self, region=None, viewport=None, useItemNames=False):
  165. """
  166. viewport specifies the arguments to glViewport. If None, then we use self.opts['viewport']
  167. region specifies the sub-region of self.opts['viewport'] that should be rendered.
  168. Note that we may use viewport != self.opts['viewport'] when exporting.
  169. """
  170. if viewport is None:
  171. glViewport(*self.getViewport())
  172. else:
  173. glViewport(*viewport)
  174. self.setProjection(region=region)
  175. self.setModelview()
  176. bgcolor = self.opts['bgcolor']
  177. glClearColor(*bgcolor)
  178. glClear( GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT )
  179. self.drawItemTree(useItemNames=useItemNames)
  180.  
  181. def drawItemTree(self, item=None, useItemNames=False):
  182. if item is None:
  183. items = [x for x in self.items if x.parentItem() is None]
  184. else:
  185. items = item.childItems()
  186. items.append(item)
  187. items.sort(key=lambda a: a.depthValue())
  188. for i in items:
  189. if not i.visible():
  190. continue
  191. if i is item:
  192. try:
  193. glPushAttrib(GL_ALL_ATTRIB_BITS)
  194. if useItemNames:
  195. glLoadName(i._id)
  196. self._itemNames[i._id] = i
  197. i.paint()
  198. except:
  199. from .. import debug
  200. debug.printExc()
  201. msg = "Error while drawing item %s." % str(item)
  202. ver = glGetString(GL_VERSION)
  203. if ver is not None:
  204. ver = ver.split()[0]
  205. if int(ver.split(b'.')[0]) < 2:
  206. print(msg + " The original exception is printed above; however, pyqtgraph requires OpenGL version 2.0 or greater for many of its 3D features and your OpenGL version is %s. Installing updated display drivers may resolve this issue." % ver)
  207. else:
  208. print(msg)
  209.  
  210. finally:
  211. glPopAttrib()
  212. else:
  213. glMatrixMode(GL_MODELVIEW)
  214. glPushMatrix()
  215. try:
  216. tr = i.transform()
  217. a = np.array(tr.copyDataTo()).reshape((4,4))
  218. glMultMatrixf(a.transpose())
  219. self.drawItemTree(i, useItemNames=useItemNames)
  220. finally:
  221. glMatrixMode(GL_MODELVIEW)
  222. glPopMatrix()
  223.  
  224. def setCameraPosition(self, pos=None, distance=None, elevation=None, azimuth=None):
  225. if distance is not None:
  226. self.opts['distance'] = distance
  227. if elevation is not None:
  228. self.opts['elevation'] = elevation
  229. if azimuth is not None:
  230. self.opts['azimuth'] = azimuth
  231. self.update()
  232.  
  233.  
  234.  
  235. def cameraPosition(self):
  236. """Return current position of camera based on center, dist, elevation, and azimuth"""
  237. center = self.opts['center']
  238. dist = self.opts['distance']
  239. elev = self.opts['elevation'] * np.pi/180.
  240. azim = self.opts['azimuth'] * np.pi/180.
  241.  
  242. pos = Vector(
  243. center.x() + dist * np.cos(elev) * np.cos(azim),
  244. center.y() + dist * np.cos(elev) * np.sin(azim),
  245. center.z() + dist * np.sin(elev)
  246. )
  247.  
  248. return pos
  249.  
  250. def orbit(self, azim, elev):
  251. """Orbits the camera around the center position. *azim* and *elev* are given in degrees."""
  252. self.opts['azimuth'] += azim
  253. #self.opts['elevation'] += elev
  254. self.opts['elevation'] = np.clip(self.opts['elevation'] + elev, -90, 90)
  255. self.update()
  256.  
  257. def pan(self, dx, dy, dz, relative=False):
  258. """
  259. Moves the center (look-at) position while holding the camera in place.
  260.  
  261. If relative=True, then the coordinates are interpreted such that x
  262. if in the global xy plane and points to the right side of the view, y is
  263. in the global xy plane and orthogonal to x, and z points in the global z
  264. direction. Distances are scaled roughly such that a value of 1.0 moves
  265. by one pixel on screen.
  266.  
  267. """
  268. if not relative:
  269. self.opts['center'] += QtGui.QVector3D(dx, dy, dz)
  270. else:
  271. cPos = self.cameraPosition()
  272. cVec = self.opts['center'] - cPos
  273. dist = cVec.length() ## distance from camera to center
  274. xDist = dist * 2. * np.tan(0.5 * self.opts['fov'] * np.pi / 180.) ## approx. width of view at distance of center point
  275. xScale = xDist / self.width()
  276. zVec = QtGui.QVector3D(0,0,1)
  277. xVec = QtGui.QVector3D.crossProduct(zVec, cVec).normalized()
  278. yVec = QtGui.QVector3D.crossProduct(xVec, zVec).normalized()
  279. self.opts['center'] = self.opts['center'] + xVec * xScale * dx + yVec * xScale * dy + zVec * xScale * dz
  280. self.update()
  281.  
  282. def pixelSize(self, pos):
  283. """
  284. Return the approximate size of a screen pixel at the location pos
  285. Pos may be a Vector or an (N,3) array of locations
  286. """
  287. cam = self.cameraPosition()
  288. if isinstance(pos, np.ndarray):
  289. cam = np.array(cam).reshape((1,)*(pos.ndim-1)+(3,))
  290. dist = ((pos-cam)**2).sum(axis=-1)**0.5
  291. else:
  292. dist = (pos-cam).length()
  293. xDist = dist * 2. * np.tan(0.5 * self.opts['fov'] * np.pi / 180.)
  294. return xDist / self.width()
  295.  
  296. def mousePressEvent(self, ev):
  297. self.mousePos = ev.pos()
  298.  
  299. def mouseMoveEvent(self, ev):
  300. diff = ev.pos() - self.mousePos
  301. self.mousePos = ev.pos()
  302.  
  303. if ev.buttons() == QtCore.Qt.LeftButton:
  304. self.orbit(-diff.x(), diff.y())
  305. #print self.opts['azimuth'], self.opts['elevation']
  306. elif ev.buttons() == QtCore.Qt.MidButton:
  307. if (ev.modifiers() & QtCore.Qt.ControlModifier):
  308. self.pan(diff.x(), 0, diff.y(), relative=True)
  309. else:
  310. self.pan(diff.x(), diff.y(), 0, relative=True)
  311.  
  312. def mouseReleaseEvent(self, ev):
  313. pass
  314. # Example item selection code:
  315. #region = (ev.pos().x()-5, ev.pos().y()-5, 10, 10)
  316. #print(self.itemsAt(region))
  317.  
  318. ## debugging code: draw the picking region
  319. #glViewport(*self.getViewport())
  320. #glClear( GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT )
  321. #region = (region[0], self.height()-(region[1]+region[3]), region[2], region[3])
  322. #self.paintGL(region=region)
  323. #self.swapBuffers()
  324.  
  325.  
  326. def wheelEvent(self, ev):
  327. delta = 0
  328. if not USE_PYQT5:
  329. delta = ev.delta()
  330. else:
  331. delta = ev.angleDelta().x()
  332. if delta == 0:
  333. delta = ev.angleDelta().y()
  334. if (ev.modifiers() & QtCore.Qt.ControlModifier):
  335. self.opts['fov'] *= 0.999**delta
  336. else:
  337. self.opts['distance'] *= 0.999**delta
  338. self.update()
  339.  
  340. def keyPressEvent(self, ev):
  341. if ev.key() in self.noRepeatKeys:
  342. ev.accept()
  343. if ev.isAutoRepeat():
  344. return
  345. self.keysPressed[ev.key()] = 1
  346. self.evalKeyState()
  347.  
  348. def keyReleaseEvent(self, ev):
  349. if ev.key() in self.noRepeatKeys:
  350. ev.accept()
  351. if ev.isAutoRepeat():
  352. return
  353. try:
  354. del self.keysPressed[ev.key()]
  355. except:
  356. self.keysPressed = {}
  357. self.evalKeyState()
  358.  
  359. def evalKeyState(self):
  360. speed = 2.0
  361. if len(self.keysPressed) > 0:
  362. for key in self.keysPressed:
  363. if key == QtCore.Qt.Key_Right:
  364. self.orbit(azim=-speed, elev=0)
  365. elif key == QtCore.Qt.Key_Left:
  366. self.orbit(azim=speed, elev=0)
  367. elif key == QtCore.Qt.Key_Up:
  368. self.orbit(azim=0, elev=-speed)
  369. elif key == QtCore.Qt.Key_Down:
  370. self.orbit(azim=0, elev=speed)
  371. elif key == QtCore.Qt.Key_PageUp:
  372. pass
  373. elif key == QtCore.Qt.Key_PageDown:
  374. pass
  375. self.keyTimer.start(16)
  376. else:
  377. self.keyTimer.stop()
  378.  
  379. def checkOpenGLVersion(self, msg):
  380. ## Only to be called from within exception handler.
  381. ver = glGetString(GL_VERSION).split()[0]
  382. if int(ver.split('.')[0]) < 2:
  383. from .. import debug
  384. pyqtgraph.debug.printExc()
  385. raise Exception(msg + " The original exception is printed above; however, pyqtgraph requires OpenGL version 2.0 or greater for many of its 3D features and your OpenGL version is %s. Installing updated display drivers may resolve this issue." % ver)
  386. else:
  387. raise
  388.  
  389.  
  390.  
  391. def readQImage(self):
  392. """
  393. Read the current buffer pixels out as a QImage.
  394. """
  395. w = self.width()
  396. h = self.height()
  397. self.repaint()
  398. pixels = np.empty((h, w, 4), dtype=np.ubyte)
  399. pixels[:] = 128
  400. pixels[...,0] = 50
  401. pixels[...,3] = 255
  402.  
  403. glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, pixels)
  404.  
  405. # swap B,R channels for Qt
  406. tmp = pixels[...,0].copy()
  407. pixels[...,0] = pixels[...,2]
  408. pixels[...,2] = tmp
  409. pixels = pixels[::-1] # flip vertical
  410.  
  411. img = fn.makeQImage(pixels, transpose=False)
  412. return img
  413.  
  414.  
  415. def renderToArray(self, size, format=GL_BGRA, type=GL_UNSIGNED_BYTE, textureSize=1024, padding=256):
  416. w,h = map(int, size)
  417.  
  418. self.makeCurrent()
  419. tex = None
  420. fb = None
  421. try:
  422. output = np.empty((w, h, 4), dtype=np.ubyte)
  423. fb = glfbo.glGenFramebuffers(1)
  424. glfbo.glBindFramebuffer(glfbo.GL_FRAMEBUFFER, fb )
  425.  
  426. glEnable(GL_TEXTURE_2D)
  427. tex = glGenTextures(1)
  428. glBindTexture(GL_TEXTURE_2D, tex)
  429. texwidth = textureSize
  430. data = np.zeros((texwidth,texwidth,4), dtype=np.ubyte)
  431.  
  432. ## Test texture dimensions first
  433. glTexImage2D(GL_PROXY_TEXTURE_2D, 0, GL_RGBA, texwidth, texwidth, 0, GL_RGBA, GL_UNSIGNED_BYTE, None)
  434. if glGetTexLevelParameteriv(GL_PROXY_TEXTURE_2D, 0, GL_TEXTURE_WIDTH) == 0:
  435. raise Exception("OpenGL failed to create 2D texture (%dx%d); too large for this hardware." % shape[:2])
  436. ## create teture
  437. glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, texwidth, texwidth, 0, GL_RGBA, GL_UNSIGNED_BYTE, data.transpose((1,0,2)))
  438.  
  439. self.opts['viewport'] = (0, 0, w, h) # viewport is the complete image; this ensures that paintGL(region=...)
  440. # is interpreted correctly.
  441. p2 = 2 * padding
  442. for x in range(-padding, w-padding, texwidth-p2):
  443. for y in range(-padding, h-padding, texwidth-p2):
  444. x2 = min(x+texwidth, w+padding)
  445. y2 = min(y+texwidth, h+padding)
  446. w2 = x2-x
  447. h2 = y2-y
  448.  
  449. ## render to texture
  450. glfbo.glFramebufferTexture2D(glfbo.GL_FRAMEBUFFER, glfbo.GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0)
  451.  
  452. self.paintGL(region=(x, h-y-h2, w2, h2), viewport=(0, 0, w2, h2)) # only render sub-region
  453.  
  454. ## read texture back to array
  455. data = glGetTexImage(GL_TEXTURE_2D, 0, format, type)
  456. data = np.fromstring(data, dtype=np.ubyte).reshape(texwidth,texwidth,4).transpose(1,0,2)[:, ::-1]
  457. output[x+padding:x2-padding, y+padding:y2-padding] = data[padding:w2-padding, -(h2-padding):-padding]
  458.  
  459. finally:
  460. self.opts['viewport'] = None
  461. glfbo.glBindFramebuffer(glfbo.GL_FRAMEBUFFER, 0)
  462. glBindTexture(GL_TEXTURE_2D, 0)
  463. if tex is not None:
  464. glDeleteTextures([tex])
  465. if fb is not None:
  466. glfbo.glDeleteFramebuffers([fb])
  467.  
  468. return output
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement