Source code for pyqtgraph.opengl.items.GLScatterPlotItem

import enum
import math
import importlib

from OpenGL import GL
from OpenGL.GL import shaders
import numpy as np

from ...Qt import QtGui, QT_LIB
from ..GLGraphicsItem import GLGraphicsItem

if QT_LIB in ["PyQt5", "PySide2"]:
    QtOpenGL = QtGui
else:
    QtOpenGL = importlib.import_module(f"{QT_LIB}.QtOpenGL")

__all__ = ['GLScatterPlotItem']


class DirtyFlag(enum.Flag):
    POSITION = enum.auto()
    COLOR = enum.auto()
    SIZE = enum.auto()


[docs] class GLScatterPlotItem(GLGraphicsItem): """Draws points at a list of 3D positions.""" _shaderProgram = None
[docs] def __init__(self, parentItem=None, **kwds): super().__init__() glopts = kwds.pop('glOptions', 'additive') self.setGLOptions(glopts) self.pos = None self.size = 10 self.color = [1.0,1.0,1.0,0.5] self.pxMode = True self.m_vbo_position = QtOpenGL.QOpenGLBuffer(QtOpenGL.QOpenGLBuffer.Type.VertexBuffer) self.m_vbo_color = QtOpenGL.QOpenGLBuffer(QtOpenGL.QOpenGLBuffer.Type.VertexBuffer) self.m_vbo_size = QtOpenGL.QOpenGLBuffer(QtOpenGL.QOpenGLBuffer.Type.VertexBuffer) self.dirty_bits = DirtyFlag(0) self.setParentItem(parentItem) self.setData(**kwds)
[docs] def setData(self, **kwds): """ Update the data displayed by this item. All arguments are optional; for example it is allowed to update spot positions while leaving colors unchanged, etc. ==================== ================================================== **Arguments:** pos (N,3) array of floats specifying point locations. color (N,4) array of floats (0.0-1.0) specifying spot colors OR a tuple of floats specifying a single color for all spots. size (N,) array of floats specifying spot sizes or a single value to apply to all spots. pxMode If True, spot sizes are expressed in pixels. Otherwise, they are expressed in item coordinates. ==================== ================================================== """ args = ['pos', 'color', 'size', 'pxMode'] for k in kwds.keys(): if k not in args: raise Exception('Invalid keyword argument: %s (allowed arguments are %s)' % (k, str(args))) if 'pos' in kwds: pos = kwds.pop('pos') self.pos = np.ascontiguousarray(pos, dtype=np.float32) self.dirty_bits |= DirtyFlag.POSITION if 'color' in kwds: color = kwds.pop('color') if isinstance(color, np.ndarray): color = np.ascontiguousarray(color, dtype=np.float32) self.dirty_bits |= DirtyFlag.COLOR if isinstance(color, QtGui.QColor): color = color.getRgbF() self.color = color if 'size' in kwds: size = kwds.pop('size') if isinstance(size, np.ndarray): size = np.ascontiguousarray(size, dtype=np.float32) self.dirty_bits |= DirtyFlag.SIZE self.size = size self.pxMode = kwds.get('pxMode', self.pxMode) self.update()
def upload_vbo(self, vbo, arr): if arr is None: vbo.destroy() return if not vbo.isCreated(): vbo.create() vbo.bind() if vbo.size() != arr.nbytes: vbo.allocate(arr, arr.nbytes) else: vbo.write(0, arr, arr.nbytes) vbo.release() @staticmethod def getShaderProgram(): klass = GLScatterPlotItem if klass._shaderProgram is not None: return klass._shaderProgram ctx = QtGui.QOpenGLContext.currentContext() fmt = ctx.format() if ctx.isOpenGLES(): if fmt.version() >= (3, 0): glsl_version = "#version 300 es\n" sources = SHADER_CORE else: glsl_version = "#version 100\n" sources = SHADER_LEGACY else: if fmt.version() >= (3, 1): glsl_version = "#version 140\n" sources = SHADER_CORE else: glsl_version = "#version 120\n" sources = SHADER_LEGACY compiled = [shaders.compileShader([glsl_version, v], k) for k, v in sources.items()] program = shaders.compileProgram(*compiled) # bind generic vertex attrib 0 to "a_position" so that # vertex attrib 0 definitely gets enabled later. GL.glBindAttribLocation(program, 0, "a_position") GL.glBindAttribLocation(program, 1, "a_color") GL.glBindAttribLocation(program, 2, "a_size") GL.glLinkProgram(program) klass._shaderProgram = program return program def paint(self): if self.pos is None: return self.setupGLState() mat_mvp = self.mvpMatrix() mat_mvp = np.array(mat_mvp.data(), dtype=np.float32) mat_modelview = self.modelViewMatrix() mat_modelview = np.array(mat_modelview.data(), dtype=np.float32) view = self.view() if self.pxMode: scale = 0 else: scale = 2.0 * math.tan(math.radians(0.5 * view.opts["fov"])) / view.width() context = QtGui.QOpenGLContext.currentContext() if DirtyFlag.POSITION in self.dirty_bits: self.upload_vbo(self.m_vbo_position, self.pos) if DirtyFlag.COLOR in self.dirty_bits: self.upload_vbo(self.m_vbo_color, self.color) if DirtyFlag.SIZE in self.dirty_bits: self.upload_vbo(self.m_vbo_size, self.size) self.dirty_bits = DirtyFlag(0) if not context.isOpenGLES(): if _is_compatibility_profile(context): GL.glEnable(GL.GL_POINT_SPRITE) GL.glEnable(GL.GL_PROGRAM_POINT_SIZE) program = self.getShaderProgram() enabled_locs = [] loc = 0 self.m_vbo_position.bind() GL.glVertexAttribPointer(loc, 3, GL.GL_FLOAT, False, 0, None) self.m_vbo_position.release() enabled_locs.append(loc) loc = 1 if isinstance(self.color, np.ndarray): self.m_vbo_color.bind() GL.glVertexAttribPointer(loc, 4, GL.GL_FLOAT, False, 0, None) self.m_vbo_color.release() enabled_locs.append(loc) else: GL.glVertexAttrib4f(loc, *self.color) loc = 2 if isinstance(self.size, np.ndarray): self.m_vbo_size.bind() GL.glVertexAttribPointer(loc, 1, GL.GL_FLOAT, False, 0, None) self.m_vbo_size.release() enabled_locs.append(loc) else: GL.glVertexAttrib1f(loc, self.size) for loc in enabled_locs: GL.glEnableVertexAttribArray(loc) with program: loc = GL.glGetUniformLocation(program, "u_mvp") GL.glUniformMatrix4fv(loc, 1, False, mat_mvp) loc = GL.glGetUniformLocation(program, "u_modelview") GL.glUniformMatrix4fv(loc, 1, False, mat_modelview) loc = GL.glGetUniformLocation(program, "u_scale") GL.glUniform1f(loc, scale) GL.glDrawArrays(GL.GL_POINTS, 0, len(self.pos)) for loc in enabled_locs: GL.glDisableVertexAttribArray(loc)
def _is_compatibility_profile(context): # https://stackoverflow.com/questions/73745603/detect-the-opengl-context-profile-before-version-3-2 sformat = context.format() profile = sformat.profile() # >= 3.2 has {Compatibility,Core}Profile # <= 3.1 is NoProfile if profile == sformat.OpenGLContextProfile.CompatibilityProfile: compat = True elif profile == sformat.OpenGLContextProfile.CoreProfile: compat = False else: compat = False version = sformat.version() if version <= (2, 1): compat = True elif version == (3, 0): if sformat.testOption(sformat.FormatOption.DeprecatedFunctions): compat = True elif version == (3, 1): if context.hasExtension(b'GL_ARB_compatibility'): compat = True return compat ## See: ## ## http://stackoverflow.com/questions/9609423/applying-part-of-a-texture-sprite-sheet-texture-map-to-a-point-sprite-in-ios ## http://stackoverflow.com/questions/3497068/textured-points-in-opengl-es-2-0 ## ## SHADER_LEGACY = { GL.GL_VERTEX_SHADER : """ uniform float u_scale; uniform mat4 u_modelview; uniform mat4 u_mvp; attribute vec4 a_position; attribute vec4 a_color; attribute float a_size; varying vec4 v_color; void main() { gl_Position = u_mvp * a_position; v_color = a_color; gl_PointSize = a_size; if (u_scale != 0.0) { // pxMode=False // the modelview matrix transforms the vertex to // camera space, where the camera is at (0, 0, 0). vec4 cpos = u_modelview * a_position; float dist = length(cpos.xyz); // equations: // xDist = dist * 2.0 * tan(0.5 * fov) // pxSize = xDist / view_width // let: // u_scale = 2.0 * tan(0.5 * fov) / view_width // then: // pxSize = dist * u_scale float pxSize = dist * u_scale; gl_PointSize /= pxSize; } } """, GL.GL_FRAGMENT_SHADER : """ #ifdef GL_ES precision mediump float; #endif varying vec4 v_color; void main() { vec2 xy = (gl_PointCoord - 0.5) * 2.0; if (dot(xy, xy) <= 1.0) gl_FragColor = v_color; else discard; } """ } SHADER_CORE = { GL.GL_VERTEX_SHADER : """ uniform float u_scale; uniform mat4 u_modelview; uniform mat4 u_mvp; in vec4 a_position; in vec4 a_color; in float a_size; out vec4 v_color; void main() { gl_Position = u_mvp * a_position; v_color = a_color; gl_PointSize = a_size; if (u_scale != 0.0) { // pxMode=False // the modelview matrix transforms the vertex to // camera space, where the camera is at (0, 0, 0). vec4 cpos = u_modelview * a_position; float dist = length(cpos.xyz); // equations: // xDist = dist * 2.0 * tan(0.5 * fov) // pxSize = xDist / view_width // let: // u_scale = 2.0 * tan(0.5 * fov) / view_width // then: // pxSize = dist * u_scale float pxSize = dist * u_scale; gl_PointSize /= pxSize; } } """, GL.GL_FRAGMENT_SHADER : """ #ifdef GL_ES precision mediump float; #endif in vec4 v_color; out vec4 fragColor; void main() { vec2 xy = (gl_PointCoord - 0.5) * 2.0; if (dot(xy, xy) <= 1.0) fragColor = v_color; else discard; } """ }