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") app = pg.mkQApp("GLBarGraphItem Example")
w = gl.GLViewWidget() w = gl.GLViewWidget()
w.opts['distance'] = 40
w.show() w.show()
w.setWindowTitle('pyqtgraph example: GLBarGraphItem') w.setWindowTitle('pyqtgraph example: GLBarGraphItem')
w.setCameraPosition(distance=40)
gx = gl.GLGridItem() gx = gl.GLGridItem()
gx.rotate(90, 0, 1, 0) gx.rotate(90, 0, 1, 0)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,8 +1,8 @@
from OpenGL.GL import * from OpenGL.GL import *
import numpy as np import numpy as np
from pyqtgraph.Qt import QtCore, QtGui from ...Qt import QtCore, QtGui
from pyqtgraph.opengl.GLGraphicsItem import GLGraphicsItem from .. GLGraphicsItem import GLGraphicsItem
import pyqtgraph.functions as fn from ... import functions as fn
__all__ = ['GLTextItem'] __all__ = ['GLTextItem']
@ -39,7 +39,7 @@ class GLTextItem(GLGraphicsItem):
args = ['pos', 'color', 'text', 'font'] args = ['pos', 'color', 'text', 'font']
for k in kwds.keys(): for k in kwds.keys():
if k not in args: 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: for arg in args:
if arg in kwds: if arg in kwds:
value = kwds[arg] value = kwds[arg]
@ -65,12 +65,11 @@ class GLTextItem(GLGraphicsItem):
modelview = glGetDoublev(GL_MODELVIEW_MATRIX) modelview = glGetDoublev(GL_MODELVIEW_MATRIX)
projection = glGetDoublev(GL_PROJECTION_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 = self.__project(self.pos, modelview, projection, viewport)
text_pos.setY(viewport[3] - text_pos.y()) text_pos.setY(viewport[3] - text_pos.y())
text_pos /= self.view().devicePixelRatio()
painter = QtGui.QPainter(self.view()) painter = QtGui.QPainter(self.view())
painter.setPen(self.color) painter.setPen(self.color)

View File

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