Merge pull request #1903 from pijyoi/cleanup_glv

Cleanup GLViewWidget
This commit is contained in:
Ogi Moore 2021-07-17 21:59:40 -07:00 committed by GitHub
commit 58aac09387
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 79 additions and 129 deletions

View File

@ -13,9 +13,9 @@ import numpy as np
app = pg.mkQApp("GLBarGraphItem Example")
w = gl.GLViewWidget()
w.opts['distance'] = 40
w.show()
w.setWindowTitle('pyqtgraph example: GLBarGraphItem')
w.setCameraPosition(distance=40)
gx = gl.GLGridItem()
gx.rotate(90, 0, 1, 0)

View File

@ -15,9 +15,9 @@ import numpy as np
app = pg.mkQApp("GLImageItem Example")
w = gl.GLViewWidget()
w.opts['distance'] = 200
w.show()
w.setWindowTitle('pyqtgraph example: GLImageItem')
w.setCameraPosition(distance=200)
## create volume data set to slice three images from
shape = (100,100,70)

View File

@ -13,9 +13,9 @@ import numpy as np
app = pg.mkQApp("GLLinePlotItem Example")
w = gl.GLViewWidget()
w.opts['distance'] = 40
w.show()
w.setWindowTitle('pyqtgraph example: GLLinePlotItem')
w.setCameraPosition(distance=40)
gx = gl.GLGridItem()
gx.rotate(90, 0, 1, 0)

View File

@ -15,9 +15,9 @@ import numpy as np
app = pg.mkQApp("GLScatterPlotItem Example")
w = gl.GLViewWidget()
w.opts['distance'] = 20
w.show()
w.setWindowTitle('pyqtgraph example: GLScatterPlotItem')
w.setCameraPosition(distance=20)
g = gl.GLGridItem()
w.addItem(g)

View File

@ -11,9 +11,9 @@ import pyqtgraph.opengl as gl
pg.mkQApp("GLViewWidget Example")
w = gl.GLViewWidget()
w.opts['distance'] = 20
w.show()
w.setWindowTitle('pyqtgraph example: GLViewWidget')
w.setCameraPosition(distance=20)
ax = gl.GLAxisItem()
ax.setSize(5,5,5)

View File

@ -14,9 +14,9 @@ from pyqtgraph import functions as fn
app = pg.mkQApp("GLVolumeItem Example")
w = gl.GLViewWidget()
w.opts['distance'] = 200
w.show()
w.setWindowTitle('pyqtgraph example: GLVolumeItem')
w.setCameraPosition(distance=200)
g = gl.GLGridItem()
g.scale(10, 10, 1)

View File

@ -4,6 +4,7 @@ import OpenGL.GL.framebufferobjects as glfbo
import numpy as np
from .. import Vector
from .. import functions as fn
from .. import getConfigOption
import warnings
from math import cos, sin, tan, radians
##Vector = QtGui.QVector3D
@ -21,10 +22,8 @@ class GLViewWidget(QtWidgets.QOpenGLWidget):
================ ==============================================================
**Arguments:**
parent (QObject, optional): Parent QObject. Defaults to None.
devicePixelRatio (float, optional): High-DPI displays Qt5 should automatically
detect the correct resolution. For Qt4, specify the
``devicePixelRatio`` argument when initializing the widget
(usually this value is 1-2). Defaults to None.
devicePixelRatio No longer in use. High-DPI displays should automatically
detect the correct resolution.
rotationMethod (str): Mechanimsm to drive the rotation method, options are
'euler' and 'quaternion'. Defaults to 'euler'.
================ ==============================================================
@ -34,8 +33,8 @@ class GLViewWidget(QtWidgets.QOpenGLWidget):
self.setFocusPolicy(QtCore.Qt.FocusPolicy.ClickFocus)
if rotationMethod not in {"euler", "quaternion"}:
raise RuntimeError("Rotation method should be either 'euler' or 'quaternion'")
if rotationMethod not in ["euler", "quaternion"]:
raise ValueError("Rotation method should be either 'euler' or 'quaternion'")
self.opts = {
'center': Vector(0,0,0), ## will always appear at the center of the widget
@ -46,7 +45,7 @@ class GLViewWidget(QtWidgets.QOpenGLWidget):
'azimuth': 45, ## camera's azimuthal angle in degrees
## (rotation around z-axis 0 points along x-axis)
'viewport': None, ## glViewport params; None == whole widget
'devicePixelRatio': devicePixelRatio,
## note that 'viewport' is in device pixels
'rotationMethod': rotationMethod
}
self.reset()
@ -56,8 +55,6 @@ class GLViewWidget(QtWidgets.QOpenGLWidget):
self.keysPressed = {}
self.keyTimer = QtCore.QTimer()
self.keyTimer.timeout.connect(self.evalKeyState)
self.makeCurrent()
def _updateScreen(self, screen):
self._updatePixelRatio()
@ -74,14 +71,13 @@ class GLViewWidget(QtWidgets.QOpenGLWidget):
window.screenChanged.connect(self._updateScreen)
self._updateScreen(window.screen())
def width(self):
dpr = self.devicePixelRatio()
return int(super().width() * dpr)
def height(self):
dpr = self.devicePixelRatio()
return int(super().height() * dpr)
def deviceWidth(self):
dpr = self.devicePixelRatioF()
return int(self.width() * dpr)
def deviceHeight(self):
dpr = self.devicePixelRatioF()
return int(self.height() * dpr)
def reset(self):
"""
@ -94,20 +90,15 @@ class GLViewWidget(QtWidgets.QOpenGLWidget):
self.opts['azimuth'] = 45 ## camera's azimuthal angle in degrees
## (rotation around z-axis 0 points along x-axis)
self.opts['viewport'] = None ## glViewport params; None == whole widget
self.setBackgroundColor('k')
self.setBackgroundColor(getConfigOption('background'))
def addItem(self, item):
self.items.append(item)
if self.isValid():
self.makeCurrent()
try:
item.initialize()
except:
self.checkOpenGLVersion('Error while adding item %s to GLViewWidget.' % str(item))
item.initialize()
item._setView(self)
#print "set view", item, self, item.view()
self.update()
def removeItem(self, item):
@ -131,6 +122,14 @@ class GLViewWidget(QtWidgets.QOpenGLWidget):
"""
Initialize items that were not initialized during addItem().
"""
ctx = self.context()
fmt = ctx.format()
if ctx.isOpenGLES() or fmt.version() < (2, 0):
verString = glGetString(GL_VERSION)
raise RuntimeError(
"pyqtgraph.opengl: Requires >= OpenGL 2.0 (not ES); Found %s" % verString
)
for item in self.items:
if not item.isInitialized():
item.initialize()
@ -146,33 +145,18 @@ class GLViewWidget(QtWidgets.QOpenGLWidget):
def getViewport(self):
vp = self.opts['viewport']
if vp is None:
return (0, 0, self.width(), self.height())
return (0, 0, self.deviceWidth(), self.deviceHeight())
else:
dpr = self.devicePixelRatio()
return tuple([int(x * dpr) for x in vp])
return vp
def devicePixelRatio(self):
dpr = self.opts['devicePixelRatio']
if dpr is not None:
return dpr
return self.devicePixelRatioF()
def resizeGL(self, w, h):
pass
#glViewport(*self.getViewport())
#self.update()
def setProjection(self, region=None):
m = self.projectionMatrix(region)
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
a = np.array(m.copyDataTo()).reshape((4,4))
glMultMatrixf(a.transpose())
glLoadMatrixf(m.data())
def projectionMatrix(self, region=None):
if region is None:
region = (0, 0, self.width(), self.height())
region = (0, 0, self.deviceWidth(), self.deviceHeight())
x0, y0, w, h = self.getViewport()
dist = self.opts['distance']
@ -194,11 +178,9 @@ class GLViewWidget(QtWidgets.QOpenGLWidget):
return tr
def setModelview(self):
glMatrixMode(GL_MODELVIEW)
glLoadIdentity()
m = self.viewMatrix()
a = np.array(m.copyDataTo()).reshape((4,4))
glMultMatrixf(a.transpose())
glMatrixMode(GL_MODELVIEW)
glLoadMatrixf(m.data())
def viewMatrix(self):
tr = QtGui.QMatrix4x4()
@ -218,7 +200,7 @@ class GLViewWidget(QtWidgets.QOpenGLWidget):
Return a list of the items displayed in the region (x, y, w, h)
relative to the widget.
"""
region = (region[0], self.height()-(region[1]+region[3]), region[2], region[3])
region = (region[0], self.deviceHeight()-(region[1]+region[3]), region[2], region[3])
#buf = np.zeros(100000, dtype=np.uint)
buf = glSelectBuffer(100000)
@ -273,14 +255,7 @@ class GLViewWidget(QtWidgets.QOpenGLWidget):
except:
from .. import debug
debug.printExc()
msg = "Error while drawing item %s." % str(item)
ver = glGetString(GL_VERSION)
if ver is not None:
ver = ver.split()[0]
if int(ver.split(b'.')[0]) < 2:
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)
else:
print(msg)
print("Error while drawing item %s." % str(item))
finally:
glPopAttrib()
@ -297,21 +272,40 @@ class GLViewWidget(QtWidgets.QOpenGLWidget):
glPopMatrix()
def setCameraPosition(self, pos=None, distance=None, elevation=None, azimuth=None, rotation=None):
if rotation is not None:
# Alternatively, we could define that rotation overrides elevation and azimuth
if elevation is not None:
raise ValueError("cannot set both rotation and elevation")
if azimuth is not None:
raise ValueError("cannot set both rotation and azimuth")
if pos is not None:
self.opts['center'] = pos
if distance is not None:
self.opts['distance'] = distance
if rotation is not None:
# set with quaternion
self.opts['rotation'] = rotation
if self.opts['rotationMethod'] == "quaternion":
# note that "quaternion" mode modifies only opts['rotation']
if elevation is not None or azimuth is not None:
eu = self.opts['rotation'].toEulerAngles()
if azimuth is not None:
eu.setZ(-azimuth-90)
if elevation is not None:
eu.setX(elevation-90)
self.opts['rotation'] = QtGui.QQuaternion.fromEulerAngles(eu)
if rotation is not None:
self.opts['rotation'] = rotation
else:
# set with elevation-azimuth, restored for compatibility
eu = self.opts['rotation'].toEulerAngles()
if azimuth is not None:
eu.setZ(-azimuth-90)
# note that "euler" mode modifies only opts['elevation'] and opts['azimuth']
if elevation is not None:
eu.setX(elevation-90)
self.opts['rotation'] = QtGui.QQuaternion.fromEulerAngles(eu)
self.opts['elevation'] = elevation
if azimuth is not None:
self.opts['azimuth'] = azimuth
if rotation is not None:
eu = rotation.toEulerAngles()
self.opts['elevation'] = eu.x() + 90
self.opts['azimuth'] = -eu.z() - 90
self.update()
def cameraPosition(self):
@ -492,7 +486,7 @@ class GLViewWidget(QtWidgets.QOpenGLWidget):
return
try:
del self.keysPressed[ev.key()]
except:
except KeyError:
self.keysPressed = {}
self.evalKeyState()
@ -516,54 +510,11 @@ class GLViewWidget(QtWidgets.QOpenGLWidget):
else:
self.keyTimer.stop()
def checkOpenGLVersion(self, msg):
"""
Give exception additional context about version support.
Only to be called from within exception handler.
As this check is only performed on error,
unsupported versions might still work!
"""
# Check for unsupported version
verString = glGetString(GL_VERSION)
ver = verString.split()[0]
# If not OpenGL ES...
if str(ver.split(b'.')[0]).isdigit():
verNumber = int(ver.split(b'.')[0])
# ...and version is supported:
if verNumber >= 2:
# OpenGL version is fine, raise the original exception
raise
# Print original exception
from .. import debug
debug.printExc()
# Notify about unsupported version
raise Exception(
msg + "\n" + \
"pyqtgraph.opengl: Requires >= OpenGL 2.0 (not ES); Found %s" % verString
)
def readQImage(self):
"""
Read the current buffer pixels out as a QImage.
"""
w = self.width()
h = self.height()
self.repaint()
pixels = np.empty((h, w, 4), dtype=np.ubyte)
pixels[:] = 128
pixels[...,0] = 50
pixels[...,3] = 255
glReadPixels(0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, pixels)
pixels = pixels[::-1].copy() # flip vertical
qimg = fn.ndarray_to_qimage(pixels, QtGui.QImage.Format.Format_RGBA8888)
return qimg
return self.grabFramebuffer()
def renderToArray(self, size, format=GL_BGRA, type=GL_UNSIGNED_BYTE, textureSize=1024, padding=256):
w,h = map(int, size)
@ -573,7 +524,7 @@ class GLViewWidget(QtWidgets.QOpenGLWidget):
fb = None
depth_buf = None
try:
output = np.empty((w, h, 4), dtype=np.ubyte)
output = np.empty((h, w, 4), dtype=np.ubyte)
fb = glfbo.glGenFramebuffers(1)
glfbo.glBindFramebuffer(glfbo.GL_FRAMEBUFFER, fb )
@ -586,9 +537,9 @@ class GLViewWidget(QtWidgets.QOpenGLWidget):
## Test texture dimensions first
glTexImage2D(GL_PROXY_TEXTURE_2D, 0, GL_RGBA, texwidth, texwidth, 0, GL_RGBA, GL_UNSIGNED_BYTE, None)
if glGetTexLevelParameteriv(GL_PROXY_TEXTURE_2D, 0, GL_TEXTURE_WIDTH) == 0:
raise Exception("OpenGL failed to create 2D texture (%dx%d); too large for this hardware." % shape[:2])
## create teture
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, texwidth, texwidth, 0, GL_RGBA, GL_UNSIGNED_BYTE, data.transpose((1,0,2)))
raise RuntimeError("OpenGL failed to create 2D texture (%dx%d); too large for this hardware." % data.shape[:2])
## create texture
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, texwidth, texwidth, 0, GL_RGBA, GL_UNSIGNED_BYTE, data)
# Create depth buffer
depth_buf = glGenRenderbuffers(1)
@ -614,8 +565,8 @@ class GLViewWidget(QtWidgets.QOpenGLWidget):
## read texture back to array
data = glGetTexImage(GL_TEXTURE_2D, 0, format, type)
data = np.fromstring(data, dtype=np.ubyte).reshape(texwidth,texwidth,4).transpose(1,0,2)[:, ::-1]
output[x+padding:x2-padding, y+padding:y2-padding] = data[padding:w2-padding, -(h2-padding):-padding]
data = np.frombuffer(data, dtype=np.ubyte).reshape(texwidth,texwidth,4)[::-1, ...]
output[y+padding:y2-padding, x+padding:x2-padding] = data[-(h2-padding):-padding, padding:w2-padding]
finally:
self.opts['viewport'] = None

View File

@ -99,5 +99,5 @@ class GLImageItem(GLGraphicsItem):
glTexCoord2f(0,1)
glVertex3f(0, self.data.shape[1], 0)
glEnd()
glDisable(GL_TEXTURE_3D)
glDisable(GL_TEXTURE_2D)

View File

@ -1,8 +1,8 @@
from OpenGL.GL import *
import numpy as np
from pyqtgraph.Qt import QtCore, QtGui
from pyqtgraph.opengl.GLGraphicsItem import GLGraphicsItem
import pyqtgraph.functions as fn
from ...Qt import QtCore, QtGui
from .. GLGraphicsItem import GLGraphicsItem
from ... import functions as fn
__all__ = ['GLTextItem']
@ -39,7 +39,7 @@ class GLTextItem(GLGraphicsItem):
args = ['pos', 'color', 'text', 'font']
for k in kwds.keys():
if k not in args:
raise ArgumentError('Invalid keyword argument: %s (allowed arguments are %s)' % (k, str(args)))
raise ValueError('Invalid keyword argument: %s (allowed arguments are %s)' % (k, str(args)))
for arg in args:
if arg in kwds:
value = kwds[arg]
@ -65,12 +65,11 @@ class GLTextItem(GLGraphicsItem):
modelview = glGetDoublev(GL_MODELVIEW_MATRIX)
projection = glGetDoublev(GL_PROJECTION_MATRIX)
viewport = glGetIntegerv(GL_VIEWPORT)
viewport = [0, 0, self.view().width(), self.view().height()]
text_pos = self.__project(self.pos, modelview, projection, viewport)
text_pos.setY(viewport[3] - text_pos.y())
text_pos /= self.view().devicePixelRatio()
painter = QtGui.QPainter(self.view())
painter.setPen(self.color)

View File

@ -162,4 +162,4 @@ if HAVE_OPENGL:
glTexCoord2f(0, 0)
glVertex3f(-1, 1, 0)
glEnd()
glDisable(GL_TEXTURE_3D)
glDisable(GL_TEXTURE_2D)