merge from Luke

This commit is contained in:
Megan Kratz 2012-11-27 14:56:02 -05:00
commit 70fde35e3b
29 changed files with 1956 additions and 446 deletions

72
examples/GLIsosurface.py Normal file
View File

@ -0,0 +1,72 @@
# -*- coding: utf-8 -*-
## This example uses the isosurface function to convert a scalar field
## (a hydrogen orbital) into a mesh for 3D display.
## Add path to library (just for examples; you do not need this)
import sys, os
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..'))
from pyqtgraph.Qt import QtCore, QtGui
import pyqtgraph as pg
import pyqtgraph.opengl as gl
app = QtGui.QApplication([])
w = gl.GLViewWidget()
w.show()
w.setCameraPosition(distance=40)
g = gl.GLGridItem()
g.scale(2,2,1)
w.addItem(g)
import numpy as np
## Define a scalar field from which we will generate an isosurface
def psi(i, j, k, offset=(25, 25, 50)):
x = i-offset[0]
y = j-offset[1]
z = k-offset[2]
th = np.arctan2(z, (x**2+y**2)**0.5)
phi = np.arctan2(y, x)
r = (x**2 + y**2 + z **2)**0.5
a0 = 1
#ps = (1./81.) * (2./np.pi)**0.5 * (1./a0)**(3/2) * (6 - r/a0) * (r/a0) * np.exp(-r/(3*a0)) * np.cos(th)
ps = (1./81.) * 1./(6.*np.pi)**0.5 * (1./a0)**(3/2) * (r/a0)**2 * np.exp(-r/(3*a0)) * (3 * np.cos(th)**2 - 1)
return ps
#return ((1./81.) * (1./np.pi)**0.5 * (1./a0)**(3/2) * (r/a0)**2 * (r/a0) * np.exp(-r/(3*a0)) * np.sin(th) * np.cos(th) * np.exp(2 * 1j * phi))**2
print("Generating scalar field..")
data = np.abs(np.fromfunction(psi, (50,50,100)))
print("Generating isosurface..")
verts = pg.isosurface(data, data.max()/4.)
md = gl.MeshData.MeshData(vertexes=verts)
colors = np.ones((md.faceCount(), 4), dtype=float)
colors[:,3] = 0.2
colors[:,2] = np.linspace(0, 1, colors.shape[0])
md.setFaceColors(colors)
m1 = gl.GLMeshItem(meshdata=md, smooth=False, shader='balloon')
m1.setGLOptions('additive')
#w.addItem(m1)
m1.translate(-25, -25, -20)
m2 = gl.GLMeshItem(meshdata=md, smooth=True, shader='balloon')
m2.setGLOptions('additive')
w.addItem(m2)
m2.translate(-25, -25, -50)
## Start Qt event loop unless running in interactive mode.
if sys.flags.interactive != 1:
app.exec_()

View File

@ -1,7 +1,8 @@
# -*- coding: utf-8 -*-
"""
Simple examples demonstrating the use of GLMeshItem.
## This example uses the isosurface function to convert a scalar field
## (a hydrogen orbital) into a mesh for 3D display.
"""
## Add path to library (just for examples; you do not need this)
import sys, os
@ -15,52 +16,117 @@ app = QtGui.QApplication([])
w = gl.GLViewWidget()
w.show()
w.setCameraPosition(distance=40)
g = gl.GLGridItem()
g.scale(2,2,1)
w.addItem(g)
import numpy as np
def psi(i, j, k, offset=(25, 25, 50)):
x = i-offset[0]
y = j-offset[1]
z = k-offset[2]
th = np.arctan2(z, (x**2+y**2)**0.5)
phi = np.arctan2(y, x)
r = (x**2 + y**2 + z **2)**0.5
a0 = 1
#ps = (1./81.) * (2./np.pi)**0.5 * (1./a0)**(3/2) * (6 - r/a0) * (r/a0) * np.exp(-r/(3*a0)) * np.cos(th)
ps = (1./81.) * 1./(6.*np.pi)**0.5 * (1./a0)**(3/2) * (r/a0)**2 * np.exp(-r/(3*a0)) * (3 * np.cos(th)**2 - 1)
## Example 1:
## Array of vertex positions and array of vertex indexes defining faces
## Colors are specified per-face
verts = np.array([
[0, 0, 0],
[2, 0, 0],
[1, 2, 0],
[1, 1, 1],
])
faces = np.array([
[0, 1, 2],
[0, 1, 3],
[0, 2, 3],
[1, 2, 3]
])
colors = np.array([
[1, 0, 0, 0.3],
[0, 1, 0, 0.3],
[0, 0, 1, 0.3],
[1, 1, 0, 0.3]
])
## Mesh item will automatically compute face normals.
m1 = gl.GLMeshItem(vertexes=verts, faces=faces, faceColors=colors, smooth=False)
m1.translate(5, 5, 0)
m1.setGLOptions('additive')
w.addItem(m1)
## Example 2:
## Array of vertex positions, three per face
## Colors are specified per-vertex
verts = verts[faces] ## Same mesh geometry as example 2, but now we are passing in 12 vertexes
colors = np.random.random(size=(verts.shape[0], 3, 4))
#colors[...,3] = 1.0
m2 = gl.GLMeshItem(vertexes=verts, vertexColors=colors, smooth=False, shader='balloon')
m2.translate(-5, 5, 0)
w.addItem(m2)
## Example 3:
## icosahedron
md = gl.MeshData.sphere(rows=10, cols=20)
#colors = np.random.random(size=(md.faceCount(), 4))
#colors[:,3] = 0.3
#colors[100:] = 0.0
colors = np.ones((md.faceCount(), 4), dtype=float)
colors[::2,0] = 0
colors[:,1] = np.linspace(0, 1, colors.shape[0])
md.setFaceColors(colors)
m3 = gl.GLMeshItem(meshdata=md, smooth=False)#, shader='balloon')
#m3.translate(-5, -5, 0)
w.addItem(m3)
#def psi(i, j, k, offset=(25, 25, 50)):
#x = i-offset[0]
#y = j-offset[1]
#z = k-offset[2]
#th = np.arctan2(z, (x**2+y**2)**0.5)
#phi = np.arctan2(y, x)
#r = (x**2 + y**2 + z **2)**0.5
#a0 = 1
##ps = (1./81.) * (2./np.pi)**0.5 * (1./a0)**(3/2) * (6 - r/a0) * (r/a0) * np.exp(-r/(3*a0)) * np.cos(th)
#ps = (1./81.) * 1./(6.*np.pi)**0.5 * (1./a0)**(3/2) * (r/a0)**2 * np.exp(-r/(3*a0)) * (3 * np.cos(th)**2 - 1)
return ps
#return ps
#return ((1./81.) * (1./np.pi)**0.5 * (1./a0)**(3/2) * (r/a0)**2 * (r/a0) * np.exp(-r/(3*a0)) * np.sin(th) * np.cos(th) * np.exp(2 * 1j * phi))**2
##return ((1./81.) * (1./np.pi)**0.5 * (1./a0)**(3/2) * (r/a0)**2 * (r/a0) * np.exp(-r/(3*a0)) * np.sin(th) * np.cos(th) * np.exp(2 * 1j * phi))**2
print("Generating scalar field..")
data = np.abs(np.fromfunction(psi, (50,50,100)))
#print("Generating scalar field..")
#data = np.abs(np.fromfunction(psi, (50,50,100)))
#data = np.fromfunction(lambda i,j,k: np.sin(0.2*((i-25)**2+(j-15)**2+k**2)**0.5), (50,50,50));
print("Generating isosurface..")
faces = pg.isosurface(data, data.max()/4.)
m = gl.GLMeshItem(faces)
w.addItem(m)
m.translate(-25, -25, -50)
##data = np.fromfunction(lambda i,j,k: np.sin(0.2*((i-25)**2+(j-15)**2+k**2)**0.5), (50,50,50));
#print("Generating isosurface..")
#verts = pg.isosurface(data, data.max()/4.)
#md = gl.MeshData.MeshData(vertexes=verts)
#colors = np.ones((md.vertexes(indexed='faces').shape[0], 4), dtype=float)
#colors[:,3] = 0.3
#colors[:,2] = np.linspace(0, 1, colors.shape[0])
#m1 = gl.GLMeshItem(meshdata=md, color=colors, smooth=False)
#w.addItem(m1)
#m1.translate(-25, -25, -20)
#m2 = gl.GLMeshItem(vertexes=verts, color=colors, smooth=True)
#w.addItem(m2)
#m2.translate(-25, -25, -50)
#data = np.zeros((5,5,5))
#data[2,2,1:4] = 1
#data[2,1:4,2] = 1
#data[1:4,2,2] = 1
#tr.translate(-2.5, -2.5, 0)
#data = np.ones((2,2,2))
#data[0, 1, 0] = 0
#faces = pg.isosurface(data, 0.5)
#m = gl.GLMeshItem(faces)
#w.addItem(m)
#m.setTransform(tr)
## Start Qt event loop unless running in interactive mode.
if sys.flags.interactive != 1:

98
examples/GLSurfacePlot.py Normal file
View File

@ -0,0 +1,98 @@
# -*- coding: utf-8 -*-
"""
This example demonstrates the use of GLSurfacePlotItem.
"""
## Add path to library (just for examples; you do not need this)
import sys, os
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..'))
from pyqtgraph.Qt import QtCore, QtGui
import pyqtgraph as pg
import pyqtgraph.opengl as gl
import scipy.ndimage as ndi
import numpy as np
## Create a GL View widget to display data
app = QtGui.QApplication([])
w = gl.GLViewWidget()
w.show()
w.setCameraPosition(distance=50)
## Add a grid to the view
g = gl.GLGridItem()
g.scale(2,2,1)
g.setDepthValue(10) # draw grid after surfaces since they may be translucent
w.addItem(g)
## Simple surface plot example
## x, y values are not specified, so assumed to be 0:50
z = ndi.gaussian_filter(np.random.normal(size=(50,50)), (1,1))
p1 = gl.GLSurfacePlotItem(z=z, shader='shaded', color=(0.5, 0.5, 1, 1))
p1.scale(16./49., 16./49., 1.0)
p1.translate(-18, 2, 0)
w.addItem(p1)
## Saddle example with x and y specified
x = np.linspace(-8, 8, 50)
y = np.linspace(-8, 8, 50)
z = 0.1 * ((x.reshape(50,1) ** 2) - (y.reshape(1,50) ** 2))
p2 = gl.GLSurfacePlotItem(x=x, y=y, z=z, shader='normalColor')
p2.translate(-10,-10,0)
w.addItem(p2)
## Manually specified colors
z = ndi.gaussian_filter(np.random.normal(size=(50,50)), (1,1))
x = np.linspace(-12, 12, 50)
y = np.linspace(-12, 12, 50)
colors = np.ones((50,50,4), dtype=float)
colors[...,0] = np.clip(np.cos(((x.reshape(50,1) ** 2) + (y.reshape(1,50) ** 2)) ** 0.5), 0, 1)
colors[...,1] = colors[...,0]
p3 = gl.GLSurfacePlotItem(z=z, colors=colors.reshape(50*50,4), shader='shaded', smooth=False)
p3.scale(16./49., 16./49., 1.0)
p3.translate(2, -18, 0)
w.addItem(p3)
## Animated example
## compute surface vertex data
cols = 100
rows = 100
x = np.linspace(-8, 8, cols+1).reshape(cols+1,1)
y = np.linspace(-8, 8, rows+1).reshape(1,rows+1)
d = (x**2 + y**2) * 0.1
d2 = d ** 0.5 + 0.1
## precompute height values for all frames
phi = np.arange(0, np.pi*2, np.pi/20.)
z = np.sin(d[np.newaxis,...] + phi.reshape(phi.shape[0], 1, 1)) / d2[np.newaxis,...]
## create a surface plot, tell it to use the 'heightColor' shader
## since this does not require normal vectors to render (thus we
## can set computeNormals=False to save time when the mesh updates)
p4 = gl.GLSurfacePlotItem(x=x[:,0], y = y[0,:], shader='heightColor', computeNormals=False, smooth=False)
p4.shader()['colorMap'] = np.array([0.2, 2, 0.5, 0.2, 1, 1, 0.2, 0, 2])
p4.translate(10, 10, 0)
w.addItem(p4)
index = 0
def update():
global p4, z, index
index -= 1
p4.setData(z=z[index%z.shape[0]])
timer = QtCore.QTimer()
timer.timeout.connect(update)
timer.start(30)
## Start Qt event loop unless running in interactive mode.
if sys.flags.interactive != 1:
app.exec_()

108
examples/GLshaders.py Normal file
View File

@ -0,0 +1,108 @@
# -*- coding: utf-8 -*-
"""
Demonstration of some of the shader programs included with pyqtgraph.
"""
## Add path to library (just for examples; you do not need this)
import sys, os
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..'))
from pyqtgraph.Qt import QtCore, QtGui
import pyqtgraph as pg
import pyqtgraph.opengl as gl
app = QtGui.QApplication([])
w = gl.GLViewWidget()
w.show()
w.setCameraPosition(distance=15, azimuth=-90)
g = gl.GLGridItem()
g.scale(2,2,1)
w.addItem(g)
import numpy as np
md = gl.MeshData.sphere(rows=10, cols=20)
x = np.linspace(-8, 8, 6)
m1 = gl.GLMeshItem(meshdata=md, smooth=True, color=(1, 0, 0, 0.2), shader='balloon', glOptions='additive')
m1.translate(x[0], 0, 0)
m1.scale(1, 1, 2)
w.addItem(m1)
m2 = gl.GLMeshItem(meshdata=md, smooth=True, shader='normalColor', glOptions='opaque')
m2.translate(x[1], 0, 0)
m2.scale(1, 1, 2)
w.addItem(m2)
m3 = gl.GLMeshItem(meshdata=md, smooth=True, shader='viewNormalColor', glOptions='opaque')
m3.translate(x[2], 0, 0)
m3.scale(1, 1, 2)
w.addItem(m3)
m4 = gl.GLMeshItem(meshdata=md, smooth=True, shader='shaded', glOptions='opaque')
m4.translate(x[3], 0, 0)
m4.scale(1, 1, 2)
w.addItem(m4)
m5 = gl.GLMeshItem(meshdata=md, smooth=True, color=(1, 0, 0, 1), shader='edgeHilight', glOptions='opaque')
m5.translate(x[4], 0, 0)
m5.scale(1, 1, 2)
w.addItem(m5)
m6 = gl.GLMeshItem(meshdata=md, smooth=True, color=(1, 0, 0, 1), shader='heightColor', glOptions='opaque')
m6.translate(x[5], 0, 0)
m6.scale(1, 1, 2)
w.addItem(m6)
#def psi(i, j, k, offset=(25, 25, 50)):
#x = i-offset[0]
#y = j-offset[1]
#z = k-offset[2]
#th = np.arctan2(z, (x**2+y**2)**0.5)
#phi = np.arctan2(y, x)
#r = (x**2 + y**2 + z **2)**0.5
#a0 = 1
##ps = (1./81.) * (2./np.pi)**0.5 * (1./a0)**(3/2) * (6 - r/a0) * (r/a0) * np.exp(-r/(3*a0)) * np.cos(th)
#ps = (1./81.) * 1./(6.*np.pi)**0.5 * (1./a0)**(3/2) * (r/a0)**2 * np.exp(-r/(3*a0)) * (3 * np.cos(th)**2 - 1)
#return ps
##return ((1./81.) * (1./np.pi)**0.5 * (1./a0)**(3/2) * (r/a0)**2 * (r/a0) * np.exp(-r/(3*a0)) * np.sin(th) * np.cos(th) * np.exp(2 * 1j * phi))**2
#print("Generating scalar field..")
#data = np.abs(np.fromfunction(psi, (50,50,100)))
##data = np.fromfunction(lambda i,j,k: np.sin(0.2*((i-25)**2+(j-15)**2+k**2)**0.5), (50,50,50));
#print("Generating isosurface..")
#verts = pg.isosurface(data, data.max()/4.)
#md = gl.MeshData.MeshData(vertexes=verts)
#colors = np.ones((md.vertexes(indexed='faces').shape[0], 4), dtype=float)
#colors[:,3] = 0.3
#colors[:,2] = np.linspace(0, 1, colors.shape[0])
#m1 = gl.GLMeshItem(meshdata=md, color=colors, smooth=False)
#w.addItem(m1)
#m1.translate(-25, -25, -20)
#m2 = gl.GLMeshItem(vertexes=verts, color=colors, smooth=True)
#w.addItem(m2)
#m2.translate(-25, -25, -50)
## Start Qt event loop unless running in interactive mode.
if sys.flags.interactive != 1:
app.exec_()

View File

@ -6,13 +6,14 @@ from pyqtgraph.Qt import QtCore, QtGui
plt = pg.plot()
l = pg.LegendItem((100,60), (60,10)) # args are (size, position)
l.setParentItem(plt.graphicsItem()) # Note we do NOT call plt.addItem in this case
plt.addLegend()
#l = pg.LegendItem((100,60), offset=(70,30)) # args are (size, offset)
#l.setParentItem(plt.graphicsItem()) # Note we do NOT call plt.addItem in this case
c1 = plt.plot([1,3,2,4], pen='r')
c2 = plt.plot([2,1,4,3], pen='g')
l.addItem(c1, 'red plot')
l.addItem(c2, 'green plot')
c1 = plt.plot([1,3,2,4], pen='r', name='red plot')
c2 = plt.plot([2,1,4,3], pen='g', fillLevel=0, fillBrush=(255,255,255,30), name='green plot')
#l.addItem(c1, 'red plot')
#l.addItem(c2, 'green plot')
## Start Qt event loop unless running in interactive mode or using pyside.

View File

@ -61,37 +61,41 @@ ui.alphaCheck.toggled.connect(updateLUT)
def updateScale():
global ui
spins = [ui.minSpin1, ui.maxSpin1, ui.minSpin2, ui.maxSpin2, ui.minSpin3, ui.maxSpin3]
if ui.rgbCheck.isChecked():
if ui.rgbLevelsCheck.isChecked():
for s in spins[2:]:
s.setEnabled(True)
else:
for s in spins[2:]:
s.setEnabled(False)
ui.rgbCheck.toggled.connect(updateScale)
ui.rgbLevelsCheck.toggled.connect(updateScale)
cache = {}
def mkData():
global data, cache, ui
dtype = ui.dtypeCombo.currentText()
dtype = (ui.dtypeCombo.currentText(), ui.rgbCheck.isChecked())
if dtype not in cache:
if dtype == 'uint8':
if dtype[0] == 'uint8':
dt = np.uint8
loc = 128
scale = 64
mx = 255
elif dtype == 'uint16':
elif dtype[0] == 'uint16':
dt = np.uint16
loc = 4096
scale = 1024
mx = 2**16
elif dtype == 'float':
elif dtype[0] == 'float':
dt = np.float
loc = 1.0
scale = 0.1
data = np.random.normal(size=(20,512,512), loc=loc, scale=scale)
data = ndi.gaussian_filter(data, (0, 3, 3))
if dtype != 'float':
if ui.rgbCheck.isChecked():
data = np.random.normal(size=(20,512,512,3), loc=loc, scale=scale)
data = ndi.gaussian_filter(data, (0, 6, 6, 0))
else:
data = np.random.normal(size=(20,512,512), loc=loc, scale=scale)
data = ndi.gaussian_filter(data, (0, 6, 6))
if dtype[0] != 'float':
data = np.clip(data, 0, mx)
data = data.astype(dt)
cache[dtype] = data
@ -100,7 +104,7 @@ def mkData():
updateLUT()
mkData()
ui.dtypeCombo.currentIndexChanged.connect(mkData)
ui.rgbCheck.toggled.connect(mkData)
ptr = 0
lastTime = ptime.time()
@ -113,7 +117,7 @@ def update():
useLut = None
if ui.scaleCheck.isChecked():
if ui.rgbCheck.isChecked():
if ui.rgbLevelsCheck.isChecked():
useScale = [
[ui.minSpin1.value(), ui.maxSpin1.value()],
[ui.minSpin2.value(), ui.maxSpin2.value()],

View File

@ -25,7 +25,6 @@
<verstretch>0</verstretch>
</sizepolicy>
</property>
<zorder>fpsLabel</zorder>
</widget>
</item>
<item row="0" column="1">
@ -84,7 +83,7 @@
</widget>
</item>
<item row="3" column="1">
<widget class="QCheckBox" name="rgbCheck">
<widget class="QCheckBox" name="rgbLevelsCheck">
<property name="text">
<string>RGB</string>
</property>
@ -218,6 +217,13 @@
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QCheckBox" name="rgbCheck">
<property name="text">
<string>RGB</string>
</property>
</widget>
</item>
</layout>
</widget>
</widget>

View File

@ -2,7 +2,7 @@
# Form implementation generated from reading ui file './examples/VideoTemplate.ui'
#
# Created: Sun Sep 9 14:41:31 2012
# Created: Sun Nov 4 18:24:20 2012
# by: PyQt4 UI code generator 4.9.1
#
# WARNING! All changes made in this file will be lost!
@ -55,9 +55,9 @@ class Ui_MainWindow(object):
self.scaleCheck = QtGui.QCheckBox(self.centralwidget)
self.scaleCheck.setObjectName(_fromUtf8("scaleCheck"))
self.gridLayout_2.addWidget(self.scaleCheck, 3, 0, 1, 1)
self.rgbCheck = QtGui.QCheckBox(self.centralwidget)
self.rgbCheck.setObjectName(_fromUtf8("rgbCheck"))
self.gridLayout_2.addWidget(self.rgbCheck, 3, 1, 1, 1)
self.rgbLevelsCheck = QtGui.QCheckBox(self.centralwidget)
self.rgbLevelsCheck.setObjectName(_fromUtf8("rgbLevelsCheck"))
self.gridLayout_2.addWidget(self.rgbLevelsCheck, 3, 1, 1, 1)
self.horizontalLayout = QtGui.QHBoxLayout()
self.horizontalLayout.setObjectName(_fromUtf8("horizontalLayout"))
self.minSpin1 = SpinBox(self.centralwidget)
@ -124,6 +124,9 @@ class Ui_MainWindow(object):
self.fpsLabel.setAlignment(QtCore.Qt.AlignCenter)
self.fpsLabel.setObjectName(_fromUtf8("fpsLabel"))
self.gridLayout_2.addWidget(self.fpsLabel, 0, 0, 1, 4)
self.rgbCheck = QtGui.QCheckBox(self.centralwidget)
self.rgbCheck.setObjectName(_fromUtf8("rgbCheck"))
self.gridLayout_2.addWidget(self.rgbCheck, 2, 1, 1, 1)
MainWindow.setCentralWidget(self.centralwidget)
self.retranslateUi(MainWindow)
@ -138,12 +141,13 @@ class Ui_MainWindow(object):
self.dtypeCombo.setItemText(1, QtGui.QApplication.translate("MainWindow", "uint16", None, QtGui.QApplication.UnicodeUTF8))
self.dtypeCombo.setItemText(2, QtGui.QApplication.translate("MainWindow", "float", None, QtGui.QApplication.UnicodeUTF8))
self.scaleCheck.setText(QtGui.QApplication.translate("MainWindow", "Scale Data", None, QtGui.QApplication.UnicodeUTF8))
self.rgbCheck.setText(QtGui.QApplication.translate("MainWindow", "RGB", None, QtGui.QApplication.UnicodeUTF8))
self.rgbLevelsCheck.setText(QtGui.QApplication.translate("MainWindow", "RGB", None, QtGui.QApplication.UnicodeUTF8))
self.label_2.setText(QtGui.QApplication.translate("MainWindow", "<--->", None, QtGui.QApplication.UnicodeUTF8))
self.label_3.setText(QtGui.QApplication.translate("MainWindow", "<--->", None, QtGui.QApplication.UnicodeUTF8))
self.label_4.setText(QtGui.QApplication.translate("MainWindow", "<--->", None, QtGui.QApplication.UnicodeUTF8))
self.lutCheck.setText(QtGui.QApplication.translate("MainWindow", "Use Lookup Table", None, QtGui.QApplication.UnicodeUTF8))
self.alphaCheck.setText(QtGui.QApplication.translate("MainWindow", "alpha", None, QtGui.QApplication.UnicodeUTF8))
self.fpsLabel.setText(QtGui.QApplication.translate("MainWindow", "FPS", None, QtGui.QApplication.UnicodeUTF8))
self.rgbCheck.setText(QtGui.QApplication.translate("MainWindow", "RGB", None, QtGui.QApplication.UnicodeUTF8))
from pyqtgraph import SpinBox, GradientWidget, GraphicsView, RawImageWidget

View File

@ -2,7 +2,7 @@
# Form implementation generated from reading ui file './examples/VideoTemplate.ui'
#
# Created: Sun Sep 9 14:41:31 2012
# Created: Sun Nov 4 18:24:21 2012
# by: pyside-uic 0.2.13 running on PySide 1.1.0
#
# WARNING! All changes made in this file will be lost!
@ -50,9 +50,9 @@ class Ui_MainWindow(object):
self.scaleCheck = QtGui.QCheckBox(self.centralwidget)
self.scaleCheck.setObjectName("scaleCheck")
self.gridLayout_2.addWidget(self.scaleCheck, 3, 0, 1, 1)
self.rgbCheck = QtGui.QCheckBox(self.centralwidget)
self.rgbCheck.setObjectName("rgbCheck")
self.gridLayout_2.addWidget(self.rgbCheck, 3, 1, 1, 1)
self.rgbLevelsCheck = QtGui.QCheckBox(self.centralwidget)
self.rgbLevelsCheck.setObjectName("rgbLevelsCheck")
self.gridLayout_2.addWidget(self.rgbLevelsCheck, 3, 1, 1, 1)
self.horizontalLayout = QtGui.QHBoxLayout()
self.horizontalLayout.setObjectName("horizontalLayout")
self.minSpin1 = SpinBox(self.centralwidget)
@ -119,6 +119,9 @@ class Ui_MainWindow(object):
self.fpsLabel.setAlignment(QtCore.Qt.AlignCenter)
self.fpsLabel.setObjectName("fpsLabel")
self.gridLayout_2.addWidget(self.fpsLabel, 0, 0, 1, 4)
self.rgbCheck = QtGui.QCheckBox(self.centralwidget)
self.rgbCheck.setObjectName("rgbCheck")
self.gridLayout_2.addWidget(self.rgbCheck, 2, 1, 1, 1)
MainWindow.setCentralWidget(self.centralwidget)
self.retranslateUi(MainWindow)
@ -133,12 +136,13 @@ class Ui_MainWindow(object):
self.dtypeCombo.setItemText(1, QtGui.QApplication.translate("MainWindow", "uint16", None, QtGui.QApplication.UnicodeUTF8))
self.dtypeCombo.setItemText(2, QtGui.QApplication.translate("MainWindow", "float", None, QtGui.QApplication.UnicodeUTF8))
self.scaleCheck.setText(QtGui.QApplication.translate("MainWindow", "Scale Data", None, QtGui.QApplication.UnicodeUTF8))
self.rgbCheck.setText(QtGui.QApplication.translate("MainWindow", "RGB", None, QtGui.QApplication.UnicodeUTF8))
self.rgbLevelsCheck.setText(QtGui.QApplication.translate("MainWindow", "RGB", None, QtGui.QApplication.UnicodeUTF8))
self.label_2.setText(QtGui.QApplication.translate("MainWindow", "<--->", None, QtGui.QApplication.UnicodeUTF8))
self.label_3.setText(QtGui.QApplication.translate("MainWindow", "<--->", None, QtGui.QApplication.UnicodeUTF8))
self.label_4.setText(QtGui.QApplication.translate("MainWindow", "<--->", None, QtGui.QApplication.UnicodeUTF8))
self.lutCheck.setText(QtGui.QApplication.translate("MainWindow", "Use Lookup Table", None, QtGui.QApplication.UnicodeUTF8))
self.alphaCheck.setText(QtGui.QApplication.translate("MainWindow", "alpha", None, QtGui.QApplication.UnicodeUTF8))
self.fpsLabel.setText(QtGui.QApplication.translate("MainWindow", "FPS", None, QtGui.QApplication.UnicodeUTF8))
self.rgbCheck.setText(QtGui.QApplication.translate("MainWindow", "RGB", None, QtGui.QApplication.UnicodeUTF8))
from pyqtgraph import SpinBox, GradientWidget, GraphicsView, RawImageWidget

View File

@ -43,9 +43,12 @@ examples = OrderedDict([
])),
('3D Graphics', OrderedDict([
('Volumetric', 'GLVolumeItem.py'),
('Isosurface', 'GLMeshItem.py'),
('Image', 'GLImageItem.py'),
('Isosurface', 'GLIsosurface.py'),
('Surface Plot', 'GLSurfacePlot.py'),
('Scatter Plot', 'GLScatterPlotItem.py'),
('Shaders', 'GLshaders.py'),
('Mesh', 'GLMeshItem.py'),
('Image', 'GLImageItem.py'),
])),
('Widgets', OrderedDict([
('PlotWidget', 'PlotWidget.py'),
@ -127,9 +130,8 @@ class ExampleLoader(QtGui.QMainWindow):
if fn is None:
return
if sys.platform.startswith('win'):
os.spawnl(os.P_NOWAIT, sys.executable, sys.executable, '"' + fn + '"', *extra)
os.spawnl(os.P_NOWAIT, sys.executable, '"'+sys.executable+'"', '"' + fn + '"', *extra)
else:
os.spawnl(os.P_NOWAIT, sys.executable, sys.executable, fn, *extra)

View File

@ -434,8 +434,10 @@ def affineSlice(data, shape, origin, vectors, axes, order=1, returnCoords=False,
## make sure vectors are arrays
vectors = np.array(vectors)
origin = np.array(origin)
if not isinstance(vectors, np.ndarray):
vectors = np.array(vectors)
if not isinstance(origin, np.ndarray):
origin = np.array(origin)
origin.shape = (len(axes),) + (1,)*len(shape)
## Build array of sample locations.
@ -580,171 +582,247 @@ def solveBilinearTransform(points1, points2):
return matrix
def rescaleData(data, scale, offset, dtype=None):
"""Return data rescaled and optionally cast to a new dtype::
data => (data-offset) * scale
Uses scipy.weave (if available) to improve performance.
"""
global USE_WEAVE
if dtype is None:
dtype = data.dtype
try:
if not USE_WEAVE:
raise Exception('Weave is disabled; falling back to slower version.')
newData = np.empty((data.size,), dtype=dtype)
flat = np.ascontiguousarray(data).reshape(data.size)
size = data.size
code = """
double sc = (double)scale;
double off = (double)offset;
for( int i=0; i<size; i++ ) {
newData[i] = ((double)flat[i] - off) * sc;
}
"""
scipy.weave.inline(code, ['flat', 'newData', 'size', 'offset', 'scale'], compiler='gcc')
data = newData.reshape(data.shape)
except:
if USE_WEAVE:
debug.printExc("Error; disabling weave.")
USE_WEAVE = False
#p = np.poly1d([scale, -offset*scale])
#data = p(data).astype(dtype)
d2 = data-offset
d2 *= scale
data = d2.astype(dtype)
return data
def applyLookupTable(data, lut):
"""
Uses values in *data* as indexes to select values from *lut*.
The returned data has shape data.shape + lut.shape[1:]
Uses scipy.weave to improve performance if it is available.
Note: color gradient lookup tables can be generated using GradientWidget.
"""
global USE_WEAVE
if data.dtype.kind not in ('i', 'u'):
data = data.astype(int)
## using np.take appears to be faster than even the scipy.weave method and takes care of clipping as well.
return np.take(lut, data, axis=0, mode='clip')
### old methods:
#data = np.clip(data, 0, lut.shape[0]-1)
#try:
#if not USE_WEAVE:
#raise Exception('Weave is disabled; falling back to slower version.')
### number of values to copy for each LUT lookup
#if lut.ndim == 1:
#ncol = 1
#else:
#ncol = sum(lut.shape[1:])
### output array
#newData = np.empty((data.size, ncol), dtype=lut.dtype)
### flattened input arrays
#flatData = data.flatten()
#flatLut = lut.reshape((lut.shape[0], ncol))
#dataSize = data.size
### strides for accessing each item
#newStride = newData.strides[0] / newData.dtype.itemsize
#lutStride = flatLut.strides[0] / flatLut.dtype.itemsize
#dataStride = flatData.strides[0] / flatData.dtype.itemsize
### strides for accessing individual values within a single LUT lookup
#newColStride = newData.strides[1] / newData.dtype.itemsize
#lutColStride = flatLut.strides[1] / flatLut.dtype.itemsize
#code = """
#for( int i=0; i<dataSize; i++ ) {
#for( int j=0; j<ncol; j++ ) {
#newData[i*newStride + j*newColStride] = flatLut[flatData[i*dataStride]*lutStride + j*lutColStride];
#}
#}
#"""
#scipy.weave.inline(code, ['flatData', 'flatLut', 'newData', 'dataSize', 'ncol', 'newStride', 'lutStride', 'dataStride', 'newColStride', 'lutColStride'])
#newData = newData.reshape(data.shape + lut.shape[1:])
##if np.any(newData != lut[data]):
##print "mismatch!"
#data = newData
#except:
#if USE_WEAVE:
#debug.printExc("Error; disabling weave.")
#USE_WEAVE = False
#data = lut[data]
#return data
def makeRGBA(*args, **kwds):
"""Equivalent to makeARGB(..., useRGBA=True)"""
kwds['useRGBA'] = True
return makeARGB(*args, **kwds)
def makeARGB(data, lut=None, levels=None, useRGBA=False):
def makeARGB(data, lut=None, levels=None, scale=None, useRGBA=False):
"""
Convert a 2D or 3D array into an ARGB array suitable for building QImages
Will optionally do scaling and/or table lookups to determine final colors.
Convert an array of values into an ARGB array suitable for building QImages, OpenGL textures, etc.
Returns the ARGB array (values 0-255) and a boolean indicating whether there is alpha channel data.
This is a two stage process:
1) Rescale the data based on the values in the *levels* argument (min, max).
2) Determine the final output by passing the rescaled values through a lookup table.
Both stages are optional.
============ ==================================================================================
Arguments:
data - 2D or 3D numpy array of int/float types
For 2D arrays (x, y):
* The color will be determined using a lookup table (see argument 'lut').
* If levels are given, the data is rescaled and converted to int
before using the lookup table.
data numpy array of int/float types. If
levels List [min, max]; optionally rescale data before converting through the
lookup table. The data is rescaled such that min->0 and max->*scale*::
For 3D arrays (x, y, rgba):
* The third axis must have length 3 or 4 and will be interpreted as RGBA.
* The 'lut' argument is not allowed.
rescaled = (clip(data, min, max) - min) * (*scale* / (max - min))
lut - Lookup table for 2D data. May be 1D or 2D (N,rgba) and must have dtype=ubyte.
Values in data will be converted to color by indexing directly from lut.
Lookup tables can be built using GradientWidget.
levels - List [min, max]; optionally rescale data before converting through the
lookup table. rescaled = (data-min) * len(lut) / (max-min)
useRGBA - If True, the data is returned in RGBA order (useful for building OpenGL textures). The default is
False, which returns in BGRA order for use with QImage.
It is also possible to use a 2D (N,2) array of values for levels. In this case,
it is assumed that each pair of min,max values in the levels array should be
applied to a different subset of the input data (for example, the input data may
already have RGB values and the levels are used to independently scale each
channel). The use of this feature requires that levels.shape[0] == data.shape[-1].
scale The maximum value to which data will be rescaled before being passed through the
lookup table (or returned if there is no lookup table). By default this will
be set to the length of the lookup table, or 256 is no lookup table is provided.
For OpenGL color specifications (as in GLColor4f) use scale=1.0
lut Optional lookup table (array with dtype=ubyte).
Values in data will be converted to color by indexing directly from lut.
The output data shape will be input.shape + lut.shape[1:].
Note: the output of makeARGB will have the same dtype as the lookup table, so
for conversion to QImage, the dtype must be ubyte.
Lookup tables can be built using GradientWidget.
useRGBA If True, the data is returned in RGBA order (useful for building OpenGL textures).
The default is False, which returns in ARGB order for use with QImage
(Note that 'ARGB' is a term used by the Qt documentation; the _actual_ order
is BGRA).
============ ==================================================================================
"""
prof = debug.Profiler('functions.makeARGB', disabled=True)
if lut is not None and not isinstance(lut, np.ndarray):
lut = np.array(lut)
if levels is not None and not isinstance(levels, np.ndarray):
levels = np.array(levels)
## sanity checks
if data.ndim == 3:
if data.shape[2] not in (3,4):
raise Exception("data.shape[2] must be 3 or 4")
#if lut is not None:
#raise Exception("can not use lookup table with 3D data")
elif data.ndim != 2:
raise Exception("data must be 2D or 3D")
#if data.ndim == 3:
#if data.shape[2] not in (3,4):
#raise Exception("data.shape[2] must be 3 or 4")
##if lut is not None:
##raise Exception("can not use lookup table with 3D data")
#elif data.ndim != 2:
#raise Exception("data must be 2D or 3D")
if lut is not None:
if lut.ndim == 2:
if lut.shape[1] not in (3,4):
raise Exception("lut.shape[1] must be 3 or 4")
elif lut.ndim != 1:
raise Exception("lut must be 1D or 2D")
if lut.dtype != np.ubyte:
raise Exception('lookup table must have dtype=ubyte (got %s instead)' % str(lut.dtype))
#if lut is not None:
##if lut.ndim == 2:
##if lut.shape[1] :
##raise Exception("lut.shape[1] must be 3 or 4")
##elif lut.ndim != 1:
##raise Exception("lut must be 1D or 2D")
#if lut.dtype != np.ubyte:
#raise Exception('lookup table must have dtype=ubyte (got %s instead)' % str(lut.dtype))
if levels is not None:
levels = np.array(levels)
if levels.shape == (2,):
pass
elif levels.shape in [(3,2), (4,2)]:
if data.ndim == 3:
raise Exception("Can not use 2D levels with 3D data.")
if lut is not None:
raise Exception('Can not use 2D levels and lookup table together.')
if levels.ndim == 1:
if len(levels) != 2:
raise Exception('levels argument must have length 2')
elif levels.ndim == 2:
if lut is not None and lut.ndim > 1:
raise Exception('Cannot make ARGB data when bot levels and lut have ndim > 2')
if levels.shape != (data.shape[-1], 2):
raise Exception('levels must have shape (data.shape[-1], 2)')
else:
raise Exception("Levels must have shape (2,) or (3,2) or (4,2)")
print levels
raise Exception("levels argument must be 1D or 2D.")
#levels = np.array(levels)
#if levels.shape == (2,):
#pass
#elif levels.shape in [(3,2), (4,2)]:
#if data.ndim == 3:
#raise Exception("Can not use 2D levels with 3D data.")
#if lut is not None:
#raise Exception('Can not use 2D levels and lookup table together.')
#else:
#raise Exception("Levels must have shape (2,) or (3,2) or (4,2)")
prof.mark('1')
if lut is not None:
lutLength = lut.shape[0]
else:
lutLength = 256
## weave requires contiguous arrays
global USE_WEAVE
if (levels is not None or lut is not None) and USE_WEAVE:
data = np.ascontiguousarray(data)
if scale is None:
if lut is not None:
scale = lut.shape[0]
else:
scale = 255.
## Apply levels if given
if levels is not None:
try: ## use weave to speed up scaling
if not USE_WEAVE:
raise Exception('Weave is disabled; falling back to slower version.')
if levels.ndim == 1:
scale = float(lutLength) / (levels[1]-levels[0])
offset = float(levels[0])
data = rescaleData(data, scale, offset)
else:
if data.ndim == 2:
newData = np.empty(data.shape+(levels.shape[0],), dtype=np.uint32)
for i in range(levels.shape[0]):
scale = float(lutLength / (levels[i,1]-levels[i,0]))
offset = float(levels[i,0])
newData[...,i] = rescaleData(data, scale, offset)
elif data.ndim == 3:
newData = np.empty(data.shape, dtype=np.uint32)
for i in range(data.shape[2]):
scale = float(lutLength / (levels[i,1]-levels[i,0]))
offset = float(levels[i,0])
#print scale, offset, data.shape, newData.shape, levels.shape
newData[...,i] = rescaleData(data[...,i], scale, offset)
data = newData
except:
if USE_WEAVE:
debug.printExc("Error; disabling weave.")
USE_WEAVE = False
if levels.ndim == 1:
if data.ndim == 2:
levels = levels[np.newaxis, np.newaxis, :]
else:
levels = levels[np.newaxis, np.newaxis, np.newaxis, :]
else:
levels = levels[np.newaxis, np.newaxis, ...]
if data.ndim == 2:
data = data[..., np.newaxis]
data = ((data-levels[...,0]) * lutLength) / (levels[...,1]-levels[...,0])
if isinstance(levels, np.ndarray) and levels.ndim == 2:
## we are going to rescale each channel independently
if levels.shape[0] != data.shape[-1]:
raise Exception("When rescaling multi-channel data, there must be the same number of levels as channels (data.shape[-1] == levels.shape[0])")
newData = np.empty(data.shape, dtype=int)
for i in range(data.shape[-1]):
minVal, maxVal = levels[i]
if minVal == maxVal:
maxVal += 1e-16
newData[...,i] = rescaleData(data[...,i], scale/(maxVal-minVal), minVal, dtype=int)
data = newData
else:
minVal, maxVal = levels
if minVal == maxVal:
maxVal += 1e-16
data = rescaleData(data, scale/(maxVal-minVal), minVal, dtype=int)
prof.mark('2')
## apply LUT if given
if lut is not None and data.ndim == 2:
if data.dtype.kind not in ('i', 'u'):
data = data.astype(int)
data = np.clip(data, 0, lutLength-1)
try:
if not USE_WEAVE:
raise Exception('Weave is disabled; falling back to slower version.')
newData = np.empty((data.size,) + lut.shape[1:], dtype=np.uint8)
flat = data.reshape(data.size)
size = data.size
ncol = lut.shape[1]
newStride = newData.strides[0]
newColStride = newData.strides[1]
lutStride = lut.strides[0]
lutColStride = lut.strides[1]
flatStride = flat.strides[0] / flat.dtype.itemsize
#print "newData:", newData.shape, newData.dtype
#print "flat:", flat.shape, flat.dtype, flat.min(), flat.max()
#print "lut:", lut.shape, lut.dtype
#print "size:", size, "ncols:", ncol
#print "strides:", newStride, newColStride, lutStride, lutColStride, flatStride
code = """
for( int i=0; i<size; i++ ) {
for( int j=0; j<ncol; j++ ) {
newData[i*newStride + j*newColStride] = lut[flat[i*flatStride]*lutStride + j*lutColStride];
}
}
"""
scipy.weave.inline(code, ['flat', 'lut', 'newData', 'size', 'ncol', 'newStride', 'lutStride', 'flatStride', 'newColStride', 'lutColStride'])
data = newData.reshape(data.shape + lut.shape[1:])
except:
if USE_WEAVE:
debug.printExc("Error; disabling weave.")
USE_WEAVE = False
data = lut[data]
if lut is not None:
data = applyLookupTable(data, lut)
else:
if data.dtype is not np.ubyte:
data = np.clip(data, 0, 255).astype(np.ubyte)
@ -895,25 +973,6 @@ def imageToArray(img, copy=False, transpose=True):
return arr
def rescaleData(data, scale, offset):
newData = np.empty((data.size,), dtype=np.int)
flat = data.reshape(data.size)
size = data.size
code = """
double sc = (double)scale;
double off = (double)offset;
for( int i=0; i<size; i++ ) {
newData[i] = (int)(((double)flat[i] - off) * sc);
}
"""
scipy.weave.inline(code, ['flat', 'newData', 'size', 'offset', 'scale'], compiler='gcc')
data = newData.reshape(data.shape)
return data
#def isosurface(data, level):
#"""
#Generate isosurface from volumetric data using marching tetrahedra algorithm.
@ -1090,7 +1149,7 @@ def isosurface(data, level):
*data* 3D numpy array of scalar values
*level* The level at which to generate an isosurface
Returns a list of faces; each face is a list of three vertexes and each vertex is a tuple of three floats.
Returns an array of vertex coordinates (N, 3, 3);
This function is SLOW; plenty of room for optimization here.
"""
@ -1457,7 +1516,7 @@ def isosurface(data, level):
pts.append(p)
facets.append(pts)
return facets
return np.array(facets)

View File

@ -166,13 +166,15 @@ class GraphicsItem(object):
## attempt to re-scale direction vector to fit within the precision of the coordinate system
if direction.x() == 0:
r = abs(dt.m32())/(abs(dt.m12()) + abs(dt.m22()))
#r = 1.0/(abs(dt.m12()) + abs(dt.m22()))
elif direction.y() == 0:
r = abs(dt.m31())/(abs(dt.m11()) + abs(dt.m21()))
#r = 1.0/(abs(dt.m11()) + abs(dt.m21()))
else:
r = ((abs(dt.m32())/(abs(dt.m12()) + abs(dt.m22()))) * (abs(dt.m31())/(abs(dt.m11()) + abs(dt.m21()))))**0.5
direction = direction * r
directionr = direction * r
viewDir = Point(dt.map(direction) - dt.map(Point(0,0)))
viewDir = Point(dt.map(directionr) - dt.map(Point(0,0)))
if viewDir.manhattanLength() == 0:
return None, None ## pixel size cannot be represented on this scale
@ -182,7 +184,7 @@ class GraphicsItem(object):
normView = viewDir.norm() ## direction of one pixel orthogonal to line
normOrtho = orthoDir.norm()
except:
raise Exception("Invalid direction %s" %direction)
raise Exception("Invalid direction %s" %directionr)
dti = fn.invertQTransform(dt)

View File

@ -0,0 +1,63 @@
from ..Qt import QtGui, QtCore
from ..Point import Point
class GraphicsWidgetAnchor:
"""
Class used to allow GraphicsWidgets to anchor to a specific position on their
parent.
"""
def __init__(self):
self.__parent = None
self.__parentAnchor = None
self.__itemAnchor = None
self.__offset = (0,0)
if hasattr(self, 'geometryChanged'):
self.geometryChanged.connect(self.__geometryChanged)
def anchor(self, itemPos, parentPos, offset=(0,0)):
"""
Anchors the item at its local itemPos to the item's parent at parentPos.
Both positions are expressed in values relative to the size of the item or parent;
a value of 0 indicates left or top edge, while 1 indicates right or bottom edge.
Optionally, offset may be specified to introduce an absolute offset.
Example: anchor a box such that its upper-right corner is fixed 10px left
and 10px down from its parent's upper-right corner::
box.anchor(itemPos=(1,0), parentPos=(1,0), offset=(-10,10))
"""
parent = self.parentItem()
if parent is None:
raise Exception("Cannot anchor; parent is not set.")
if self.__parent is not parent:
if self.__parent is not None:
self.__parent.geometryChanged.disconnect(self.__geometryChanged)
self.__parent = parent
parent.geometryChanged.connect(self.__geometryChanged)
self.__itemAnchor = itemPos
self.__parentAnchor = parentPos
self.__offset = offset
self.__geometryChanged()
def __geometryChanged(self):
if self.__parent is None:
return
if self.__itemAnchor is None:
return
o = self.mapToParent(Point(0,0))
a = self.boundingRect().bottomRight() * Point(self.__itemAnchor)
a = self.mapToParent(a)
p = self.__parent.boundingRect().bottomRight() * Point(self.__parentAnchor)
off = Point(self.__offset)
pos = p + (o-a) + off
self.setPos(pos)

View File

@ -2,12 +2,14 @@ from .GraphicsWidget import GraphicsWidget
from .LabelItem import LabelItem
from ..Qt import QtGui, QtCore
from .. import functions as fn
from ..Point import Point
from .GraphicsWidgetAnchor import GraphicsWidgetAnchor
__all__ = ['LegendItem']
class LegendItem(GraphicsWidget):
class LegendItem(GraphicsWidget, GraphicsWidgetAnchor):
"""
Displays a legend used for describing the contents of a plot.
LegendItems are most commonly created by calling PlotItem.addLegend().
Note that this item should not be added directly to a PlotItem. Instead,
Make it a direct descendant of the PlotItem::
@ -15,17 +17,45 @@ class LegendItem(GraphicsWidget):
legend.setParentItem(plotItem)
"""
def __init__(self, size, offset):
def __init__(self, size=None, offset=None):
"""
========== ===============================================================
Arguments
size Specifies the fixed size (width, height) of the legend. If
this argument is omitted, the legend will autimatically resize
to fit its contents.
offset Specifies the offset position relative to the legend's parent.
Positive values offset from the left or top; negative values
offset from the right or bottom. If offset is None, the
legend must be anchored manually by calling anchor() or
positioned by calling setPos().
========== ===============================================================
"""
GraphicsWidget.__init__(self)
GraphicsWidgetAnchor.__init__(self)
self.setFlag(self.ItemIgnoresTransformations)
self.layout = QtGui.QGraphicsGridLayout()
self.setLayout(self.layout)
self.items = []
self.size = size
self.offset = offset
self.setGeometry(QtCore.QRectF(self.offset[0], self.offset[1], self.size[0], self.size[1]))
if size is not None:
self.setGeometry(QtCore.QRectF(0, 0, self.size[0], self.size[1]))
def addItem(self, item, title):
def setParentItem(self, p):
ret = GraphicsWidget.setParentItem(self, p)
if self.offset is not None:
offset = Point(self.offset)
anchorx = 1 if offset[0] <= 0 else 0
anchory = 1 if offset[1] <= 0 else 0
anchor = (anchorx, anchory)
self.anchor(itemPos=anchor, parentPos=anchor, offset=offset)
return ret
def addItem(self, item, name):
"""
Add a new entry to the legend.
@ -36,15 +66,30 @@ class LegendItem(GraphicsWidget):
title The title to display for this item. Simple HTML allowed.
=========== ========================================================
"""
label = LabelItem(title)
label = LabelItem(name)
sample = ItemSample(item)
row = len(self.items)
self.items.append((sample, label))
self.layout.addItem(sample, row, 0)
self.layout.addItem(label, row, 1)
self.updateSize()
def updateSize(self):
if self.size is not None:
return
height = 0
width = 0
print "-------"
for sample, label in self.items:
height += max(sample.height(), label.height()) + 3
width = max(width, sample.width()+label.width())
print width, height
print width, height
self.setGeometry(0, 0, width+25, height)
def boundingRect(self):
return QtCore.QRectF(0, 0, self.size[0], self.size[1])
return QtCore.QRectF(0, 0, self.width(), self.height())
def paint(self, p, *args):
p.setPen(fn.mkPen(255,255,255,100))
@ -61,8 +106,16 @@ class ItemSample(GraphicsWidget):
return QtCore.QRectF(0, 0, 20, 20)
def paint(self, p, *args):
p.setPen(fn.mkPen(self.item.opts['pen']))
opts = self.item.opts
if opts.get('fillLevel',None) is not None and opts.get('fillBrush',None) is not None:
p.setBrush(fn.mkBrush(opts['fillBrush']))
p.setPen(fn.mkPen(None))
p.drawPolygon(QtGui.QPolygonF([QtCore.QPointF(2,18), QtCore.QPointF(18,2), QtCore.QPointF(18,18)]))
p.setPen(fn.mkPen(opts['pen']))
p.drawLine(2, 18, 18, 2)

View File

@ -65,6 +65,7 @@ class PlotCurveItem(GraphicsObject):
'fillLevel': None,
'brush': None,
'stepMode': False,
'name': None
}
self.setClickable(kargs.get('clickable', False))
self.setData(*args, **kargs)
@ -238,6 +239,9 @@ class PlotCurveItem(GraphicsObject):
self.fillPath = None
#self.xDisp = self.yDisp = None
if 'name' in kargs:
self.opts['name'] = kargs['name']
if 'pen' in kargs:
self.setPen(kargs['pen'])
if 'shadowPen' in kargs:

View File

@ -317,6 +317,8 @@ class PlotDataItem(GraphicsObject):
## pull in all style arguments.
## Use self.opts to fill in anything not present in kargs.
if 'name' in kargs:
self.opts['name'] = kargs['name']
## if symbol pen/brush are given with no symbol, then assume symbol is 'o'

View File

@ -33,6 +33,7 @@ from .. PlotDataItem import PlotDataItem
from .. ViewBox import ViewBox
from .. AxisItem import AxisItem
from .. LabelItem import LabelItem
from .. LegendItem import LegendItem
from .. GraphicsWidget import GraphicsWidget
from .. ButtonItem import ButtonItem
from pyqtgraph.WidgetGroup import WidgetGroup
@ -528,6 +529,9 @@ class PlotItem(GraphicsWidget):
#c.connect(c, QtCore.SIGNAL('plotChanged'), self.plotChanged)
#item.sigPlotChanged.connect(self.plotChanged)
#self.plotChanged()
name = kargs.get('name', getattr(item, 'opts', {}).get('name', None))
if name is not None and self.legend is not None:
self.legend.addItem(item, name=name)
def addDataItem(self, item, *args):
@ -596,6 +600,16 @@ class PlotItem(GraphicsWidget):
return item
def addLegend(self, size=None, offset=(30, 30)):
"""
Create a new LegendItem and anchor it over the internal ViewBox.
Plots will be automatically displayed in the legend if they
are created with the 'name' argument.
"""
self.legend = LegendItem(size, offset)
self.legend.setParentItem(self.vb)
return self.legend
def scatterPlot(self, *args, **kargs):
if 'pen' in kargs:
kargs['symbolPen'] = kargs['pen']

View File

@ -166,7 +166,7 @@ class ViewBox(GraphicsWidget):
ViewBox.NamedViews[name] = self
ViewBox.updateAllViewLists()
sid = id(self)
self.destroyed.connect(lambda: ViewBox.forgetView(sid, name) if ViewBox is not None else None)
self.destroyed.connect(lambda: ViewBox.forgetView(sid, name) if (ViewBox is not None and 'sid' in locals() and 'name' in locals()) else None)
#self.destroyed.connect(self.unregister)
def unregister(self):
@ -1154,7 +1154,7 @@ class ViewBox(GraphicsWidget):
if any(changed):
self.sigRangeChanged.emit(self, self.state['viewRange'])
self.sigTransformChanged.emit(self)
self.sigTransformChanged.emit(self) ## segfaults here: 1
def paint(self, p, opt, widget):
if self.border is not None:

View File

@ -1,5 +1,31 @@
from pyqtgraph.Qt import QtGui, QtCore
from pyqtgraph import Transform3D
from OpenGL.GL import *
from OpenGL import GL
GLOptions = {
'opaque': {
GL_DEPTH_TEST: True,
GL_BLEND: False,
GL_ALPHA_TEST: False,
GL_CULL_FACE: False,
},
'translucent': {
GL_DEPTH_TEST: True,
GL_BLEND: True,
GL_ALPHA_TEST: False,
GL_CULL_FACE: False,
'glBlendFunc': (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA),
},
'additive': {
GL_DEPTH_TEST: False,
GL_BLEND: True,
GL_ALPHA_TEST: False,
GL_CULL_FACE: False,
'glBlendFunc': (GL_SRC_ALPHA, GL_ONE),
},
}
class GLGraphicsItem(QtCore.QObject):
def __init__(self, parentItem=None):
@ -11,6 +37,7 @@ class GLGraphicsItem(QtCore.QObject):
self.__visible = True
self.setParentItem(parentItem)
self.setDepthValue(0)
self.__glOpts = {}
def setParentItem(self, item):
if self.__parent is not None:
@ -23,7 +50,52 @@ class GLGraphicsItem(QtCore.QObject):
if self.view() is not None:
self.view().removeItem(self)
self.__parent.view().addItem(self)
def setGLOptions(self, opts):
"""
Set the OpenGL state options to use immediately before drawing this item.
(Note that subclasses must call setupGLState before painting for this to work)
The simplest way to invoke this method is to pass in the name of
a predefined set of options (see the GLOptions variable):
============= ======================================================
opaque Enables depth testing and disables blending
translucent Enables depth testing and blending
Elements must be drawn sorted back-to-front for
translucency to work correctly.
additive Disables depth testing, enables blending.
Colors are added together, so sorting is not required.
============= ======================================================
It is also possible to specify any arbitrary settings as a dictionary.
This may consist of {'functionName': (args...)} pairs where functionName must
be a callable attribute of OpenGL.GL, or {GL_STATE_VAR: bool} pairs
which will be interpreted as calls to glEnable or glDisable(GL_STATE_VAR).
For example::
{
GL_ALPHA_TEST: True,
GL_CULL_FACE: False,
'glBlendFunc': (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA),
}
"""
if isinstance(opts, basestring):
opts = GLOptions[opts]
self.__glOpts = opts.copy()
def updateGLOptions(self, opts):
"""
Modify the OpenGL state options to use immediately before drawing this item.
*opts* must be a dictionary as specified by setGLOptions.
Values may also be None, in which case the key will be ignored.
"""
self.__glOpts.update(opts)
def parentItem(self):
return self.__parent
@ -135,13 +207,30 @@ class GLGraphicsItem(QtCore.QObject):
"""
pass
def setupGLState(self):
"""
This method is responsible for preparing the GL state options needed to render
this item (blending, depth testing, etc). The method is called immediately before painting the item.
"""
for k,v in self.__glOpts.items():
if v is None:
continue
if isinstance(k, basestring):
func = getattr(GL, k)
func(*v)
else:
if v is True:
glEnable(k)
else:
glDisable(k)
def paint(self):
"""
Called by the GLViewWidget to draw this item.
It is the responsibility of the item to set up its own modelview matrix,
but the caller will take care of pushing/popping.
"""
pass
self.setupGLState()
def update(self):
v = self.view()

View File

@ -12,8 +12,16 @@ class GLViewWidget(QtOpenGL.QGLWidget):
- Export options
"""
ShareWidget = None
def __init__(self, parent=None):
QtOpenGL.QGLWidget.__init__(self, parent)
if GLViewWidget.ShareWidget is None:
## create a dummy widget to allow sharing objects (textures, shaders, etc) between views
GLViewWidget.ShareWidget = QtOpenGL.QGLWidget()
QtOpenGL.QGLWidget.__init__(self, parent, GLViewWidget.ShareWidget)
self.setFocusPolicy(QtCore.Qt.ClickFocus)
self.opts = {
@ -131,6 +139,16 @@ class GLViewWidget(QtOpenGL.QGLWidget):
glMatrixMode(GL_MODELVIEW)
glPopMatrix()
def setCameraPosition(self, pos=None, distance=None, elevation=None, azimuth=None):
if distance is not None:
self.opts['distance'] = distance
if elevation is not None:
self.opts['elevation'] = elevation
if azimuth is not None:
self.opts['azimuth'] = azimuth
self.update()
def cameraPosition(self):
"""Return current position of camera based on center, dist, elevation, and azimuth"""

View File

@ -1,5 +1,6 @@
from pyqtgraph.Qt import QtGui
import pyqtgraph.functions as fn
import numpy as np
class MeshData(object):
"""
@ -10,148 +11,400 @@ class MeshData(object):
- list of triangles
- colors per vertex, edge, or tri
- normals per vertex or tri
This class handles conversion between the standard [list of vertexes, list of faces]
format (suitable for use with glDrawElements) and 'indexed' [list of vertexes] format
(suitable for use with glDrawArrays). It will automatically compute face normal
vectors as well as averaged vertex normal vectors.
The class attempts to be as efficient as possible in caching conversion results and
avoiding unnecessary conversions.
"""
def __init__(self):
self._vertexes = []
def __init__(self, vertexes=None, faces=None, edges=None, vertexColors=None, faceColors=None):
"""
============= =====================================================
Arguments
vertexes (Nv, 3) array of vertex coordinates.
If faces is not specified, then this will instead be
interpreted as (Nf, 3, 3) array of coordinates.
faces (Nf, 3) array of indexes into the vertex array.
edges [not available yet]
vertexColors (Nv, 4) array of vertex colors.
If faces is not specified, then this will instead be
interpreted as (Nf, 3, 4) array of colors.
faceColors (Nf, 4) array of face colors.
============= =====================================================
All arguments are optional.
"""
self._vertexes = None # (Nv,3) array of vertex coordinates
self._vertexesIndexedByFaces = None # (Nf, 3, 3) array of vertex coordinates
self._vertexesIndexedByEdges = None # (Ne, 2, 3) array of vertex coordinates
## mappings between vertexes, faces, and edges
self._faces = None # Nx3 array of indexes into self._vertexes specifying three vertexes for each face
self._edges = None
self._faces = []
self._vertexFaces = None ## maps vertex ID to a list of face IDs
self._vertexNormals = None
self._faceNormals = None
self._vertexColors = None
self._edgeColors = None
self._faceColors = None
self._meshColor = (1, 1, 1, 0.1) # default color to use if no face/edge/vertex colors are given
self._vertexFaces = None ## maps vertex ID to a list of face IDs (inverse mapping of _faces)
self._vertexEdges = None ## maps vertex ID to a list of edge IDs (inverse mapping of _edges)
def setFaces(self, faces, vertexes=None):
"""
Set the faces in this data set.
Data may be provided either as an Nx3x3 list of floats (9 float coordinate values per face)::
## Per-vertex data
self._vertexNormals = None # (Nv, 3) array of normals, one per vertex
self._vertexNormalsIndexedByFaces = None # (Nf, 3, 3) array of normals
self._vertexColors = None # (Nv, 3) array of colors
self._vertexColorsIndexedByFaces = None # (Nf, 3, 4) array of colors
self._vertexColorsIndexedByEdges = None # (Nf, 2, 4) array of colors
faces = [ [(x, y, z), (x, y, z), (x, y, z)], ... ]
## Per-face data
self._faceNormals = None # (Nf, 3) array of face normals
self._faceNormalsIndexedByFaces = None # (Nf, 3, 3) array of face normals
self._faceColors = None # (Nf, 4) array of face colors
self._faceColorsIndexedByFaces = None # (Nf, 3, 4) array of face colors
self._faceColorsIndexedByEdges = None # (Ne, 2, 4) array of face colors
## Per-edge data
self._edgeColors = None # (Ne, 4) array of edge colors
self._edgeColorsIndexedByEdges = None # (Ne, 2, 4) array of edge colors
#self._meshColor = (1, 1, 1, 0.1) # default color to use if no face/edge/vertex colors are given
if vertexes is not None:
if faces is None:
self.setVertexes(vertexes, indexed='faces')
if vertexColors is not None:
self.setVertexColors(vertexColors, indexed='faces')
if faceColors is not None:
self.setFaceColors(faceColors, indexed='faces')
else:
self.setVertexes(vertexes)
self.setFaces(faces)
if vertexColors is not None:
self.setVertexColors(vertexColors)
if faceColors is not None:
self.setFaceColors(faceColors)
or as an Nx3 list of ints (vertex integers) AND an Mx3 list of floats (3 float coordinate values per vertex)::
faces = [ (p1, p2, p3), ... ]
vertexes = [ (x, y, z), ... ]
#self.setFaces(vertexes=vertexes, faces=faces, vertexColors=vertexColors, faceColors=faceColors)
"""
if vertexes is None:
self._setUnindexedFaces(faces)
#def setFaces(self, vertexes=None, faces=None, vertexColors=None, faceColors=None):
#"""
#Set the faces in this data set.
#Data may be provided either as an Nx3x3 array of floats (9 float coordinate values per face)::
#faces = [ [(x, y, z), (x, y, z), (x, y, z)], ... ]
#or as an Nx3 array of ints (vertex integers) AND an Mx3 array of floats (3 float coordinate values per vertex)::
#faces = [ (p1, p2, p3), ... ]
#vertexes = [ (x, y, z), ... ]
#"""
#if not isinstance(vertexes, np.ndarray):
#vertexes = np.array(vertexes)
#if vertexes.dtype != np.float:
#vertexes = vertexes.astype(float)
#if faces is None:
#self._setIndexedFaces(vertexes, vertexColors, faceColors)
#else:
#self._setUnindexedFaces(faces, vertexes, vertexColors, faceColors)
##print self.vertexes().shape
##print self.faces().shape
#def setMeshColor(self, color):
#"""Set the color of the entire mesh. This removes any per-face or per-vertex colors."""
#color = fn.Color(color)
#self._meshColor = color.glColor()
#self._vertexColors = None
#self._faceColors = None
#def __iter__(self):
#"""Iterate over all faces, yielding a list of three tuples [(position, normal, color), ...] for each face."""
#vnorms = self.vertexNormals()
#vcolors = self.vertexColors()
#for i in range(self._faces.shape[0]):
#face = []
#for j in [0,1,2]:
#vind = self._faces[i,j]
#pos = self._vertexes[vind]
#norm = vnorms[vind]
#if vcolors is None:
#color = self._meshColor
#else:
#color = vcolors[vind]
#face.append((pos, norm, color))
#yield face
#def __len__(self):
#return len(self._faces)
def faces(self):
"""Return an array (Nf, 3) of vertex indexes, three per triangular face in the mesh."""
return self._faces
def setFaces(self, faces):
"""Set the (Nf, 3) array of faces. Each rown in the array contains
three indexes into the vertex array, specifying the three corners
of a triangular face."""
self._faces = faces
self._vertexFaces = None
self._vertexesIndexedByFaces = None
self.resetNormals()
self._vertexColorsIndexedByFaces = None
self._faceColorsIndexedByFaces = None
def vertexes(self, indexed=None):
"""Return an array (N,3) of the positions of vertexes in the mesh.
By default, each unique vertex appears only once in the array.
If indexed is 'faces', then the array will instead contain three vertexes
per face in the mesh (and a single vertex may appear more than once in the array)."""
if indexed is None:
if self._vertexes is None and self._vertexesIndexedByFaces is not None:
self._computeUnindexedVertexes()
return self._vertexes
elif indexed == 'faces':
if self._vertexesIndexedByFaces is None and self._vertexes is not None:
self._vertexesIndexedByFaces = self._vertexes[self.faces()]
return self._vertexesIndexedByFaces
else:
self._setIndexedFaces(faces, vertexes)
raise Exception("Invalid indexing mode. Accepts: None, 'faces'")
def setVertexes(self, verts=None, indexed=None, resetNormals=True):
"""
Set the array (Nv, 3) of vertex coordinates.
If indexed=='faces', then the data must have shape (Nf, 3, 3) and is
assumed to be already indexed as a list of faces.
This will cause any pre-existing normal vectors to be cleared
unless resetNormals=False.
"""
if indexed is None:
if verts is not None:
self._vertexes = verts
self._vertexesIndexedByFaces = None
elif indexed=='faces':
self._vertexes = None
if verts is not None:
self._vertexesIndexedByFaces = verts
else:
raise Exception("Invalid indexing mode. Accepts: None, 'faces'")
if resetNormals:
self.resetNormals()
def setMeshColor(self, color):
"""Set the color of the entire mesh. This removes any per-face or per-vertex colors."""
color = fn.Color(color)
self._meshColor = color.glColor()
self._vertexColors = None
self._faceColors = None
def resetNormals(self):
self._vertexNormals = None
self._vertexNormalsIndexedByFaces = None
self._faceNormals = None
self._faceNormalsIndexedByFaces = None
def hasFaceIndexedData(self):
"""Return True if this object already has vertex positions indexed by face"""
return self._vertexesIndexedByFaces is not None
def _setUnindexedFaces(self, faces):
verts = {}
self._faces = []
def hasEdgeIndexedData(self):
return self._vertexesIndexedByEdges is not None
def hasVertexColor(self):
"""Return True if this data set has vertex color information"""
for v in (self._vertexColors, self._vertexColorsIndexedByFaces, self._vertexColorsIndexedByEdges):
if v is not None:
return True
return False
def hasFaceColor(self):
"""Return True if this data set has face color information"""
for v in (self._faceColors, self._faceColorsIndexedByFaces, self._faceColorsIndexedByEdges):
if v is not None:
return True
return False
def faceNormals(self, indexed=None):
"""
Return an array (Nf, 3) of normal vectors for each face.
If indexed='faces', then instead return an indexed array
(Nf, 3, 3) (this is just the same array with each vector
copied three times).
"""
if self._faceNormals is None:
v = self.vertexes(indexed='faces')
self._faceNormals = np.cross(v[:,1]-v[:,0], v[:,2]-v[:,0])
if indexed is None:
return self._faceNormals
elif indexed == 'faces':
if self._faceNormalsIndexedByFaces is None:
norms = np.empty((self._faceNormals.shape[0], 3, 3))
norms[:] = self._faceNormals[:,np.newaxis,:]
self._faceNormalsIndexedByFaces = norms
return self._faceNormalsIndexedByFaces
else:
raise Exception("Invalid indexing mode. Accepts: None, 'faces'")
def vertexNormals(self, indexed=None):
"""
Return an array of normal vectors.
By default, the array will be (N, 3) with one entry per unique vertex in the mesh.
If indexed is 'faces', then the array will contain three normal vectors per face
(and some vertexes may be repeated).
"""
if self._vertexNormals is None:
faceNorms = self.faceNormals()
vertFaces = self.vertexFaces()
self._vertexNormals = np.empty(self._vertexes.shape, dtype=float)
for vindex in xrange(self._vertexes.shape[0]):
norms = faceNorms[vertFaces[vindex]] ## get all face normals
norm = norms.sum(axis=0) ## sum normals
norm /= (norm**2).sum()**0.5 ## and re-normalize
self._vertexNormals[vindex] = norm
if indexed is None:
return self._vertexNormals
elif indexed == 'faces':
return self._vertexNormals[self.faces()]
else:
raise Exception("Invalid indexing mode. Accepts: None, 'faces'")
def vertexColors(self, indexed=None):
"""
Return an array (Nv, 4) of vertex colors.
If indexed=='faces', then instead return an indexed array
(Nf, 3, 4).
"""
if indexed is None:
return self._vertexColors
elif indexed == 'faces':
if self._vertexColorsIndexedByFaces is None:
self._vertexColorsIndexedByFaces = self._vertexColors[self.faces()]
return self._vertexColorsIndexedByFaces
else:
raise Exception("Invalid indexing mode. Accepts: None, 'faces'")
def setVertexColors(self, colors, indexed=None):
"""
Set the vertex color array (Nv, 4).
If indexed=='faces', then the array will be interpreted
as indexed and should have shape (Nf, 3, 4)
"""
if indexed is None:
self._vertexColors = colors
self._vertexColorsIndexedByFaces = None
elif indexed == 'faces':
self._vertexColors = None
self._vertexColorsIndexedByFaces = colors
else:
raise Exception("Invalid indexing mode. Accepts: None, 'faces'")
def faceColors(self, indexed=None):
"""
Return an array (Nf, 4) of face colors.
If indexed=='faces', then instead return an indexed array
(Nf, 3, 4) (note this is just the same array with each color
repeated three times).
"""
if indexed is None:
return self._faceColors
elif indexed == 'faces':
if self._faceColorsIndexedByFaces is None and self._faceColors is not None:
Nf = self._faceColors.shape[0]
self._faceColorsIndexedByFaces = np.empty((Nf, 3, 4), dtype=self._faceColors.dtype)
self._faceColorsIndexedByFaces[:] = self._faceColors.reshape(Nf, 1, 4)
return self._faceColorsIndexedByFaces
else:
raise Exception("Invalid indexing mode. Accepts: None, 'faces'")
def setFaceColors(self, colors, indexed=None):
"""
Set the face color array (Nf, 4).
If indexed=='faces', then the array will be interpreted
as indexed and should have shape (Nf, 3, 4)
"""
if indexed is None:
self._faceColors = colors
self._faceColorsIndexedByFaces = None
elif indexed == 'faces':
self._faceColors = None
self._faceColorsIndexedByFaces = colors
else:
raise Exception("Invalid indexing mode. Accepts: None, 'faces'")
def faceCount(self):
"""
Return the number of faces in the mesh.
"""
if self._faces is not None:
return self._faces.shape[0]
elif self._vertexesIndexedByFaces is not None:
return self._vertexesIndexedByFaces.shape[0]
def edgeColors(self):
return self._edgeColors
#def _setIndexedFaces(self, faces, vertexColors=None, faceColors=None):
#self._vertexesIndexedByFaces = faces
#self._vertexColorsIndexedByFaces = vertexColors
#self._faceColorsIndexedByFaces = faceColors
def _computeUnindexedVertexes(self):
## Given (Nv, 3, 3) array of vertexes-indexed-by-face, convert backward to unindexed vertexes
## This is done by collapsing into a list of 'unique' vertexes (difference < 1e-14)
## I think generally this should be discouraged..
faces = self._vertexesIndexedByFaces
verts = {} ## used to remember the index of each vertex position
self._faces = np.empty(faces.shape[:2], dtype=np.uint)
self._vertexes = []
self._vertexFaces = []
self._faceNormals = None
self._vertexNormals = None
for face in faces:
for i in xrange(faces.shape[0]):
face = faces[i]
inds = []
for pt in face:
for j in range(face.shape[0]):
pt = face[j]
pt2 = tuple([round(x*1e14) for x in pt]) ## quantize to be sure that nearly-identical points will be merged
index = verts.get(pt2, None)
if index is None:
self._vertexes.append(QtGui.QVector3D(*pt))
#self._vertexes.append(QtGui.QVector3D(*pt))
self._vertexes.append(pt)
self._vertexFaces.append([])
index = len(self._vertexes)-1
verts[pt2] = index
self._vertexFaces[index].append(len(self._faces))
inds.append(index)
self._faces.append(tuple(inds))
self._vertexFaces[index].append(i) # keep track of which vertexes belong to which faces
self._faces[i,j] = index
self._vertexes = np.array(self._vertexes, dtype=float)
def _setIndexedFaces(self, faces, vertexes):
self._vertexes = [QtGui.QVector3D(*v) for v in vertexes]
self._faces = faces
self._edges = None
self._vertexFaces = None
self._faceNormals = None
self._vertexNormals = None
#def _setUnindexedFaces(self, faces, vertexes, vertexColors=None, faceColors=None):
#self._vertexes = vertexes #[QtGui.QVector3D(*v) for v in vertexes]
#self._faces = faces.astype(np.uint)
#self._edges = None
#self._vertexFaces = None
#self._faceNormals = None
#self._vertexNormals = None
#self._vertexColors = vertexColors
#self._faceColors = faceColors
def vertexFaces(self):
"""
Return list mapping each vertex index to a list of face indexes that use the vertex.
"""
if self._vertexFaces is None:
self._vertexFaces = [[]] * len(self._vertexes)
for i, face in enumerate(self._faces):
self._vertexFaces = [None] * len(self.vertexes())
for i in xrange(self._faces.shape[0]):
face = self._faces[i]
for ind in face:
if len(self._vertexFaces[ind]) == 0:
if self._vertexFaces[ind] is None:
self._vertexFaces[ind] = [] ## need a unique/empty list to fill
self._vertexFaces[ind].append(i)
return self._vertexFaces
def __iter__(self):
"""Iterate over all faces, yielding a list of three tuples [(position, normal, color), ...] for each face."""
vnorms = self.vertexNormals()
vcolors = self.vertexColors()
for i in range(len(self._faces)):
face = []
for j in [0,1,2]:
vind = self._faces[i][j]
pos = self._vertexes[vind]
norm = vnorms[vind]
if vcolors is None:
color = self._meshColor
else:
color = vcolors[vind]
face.append((pos, norm, color))
yield face
def faceNormals(self):
"""
Computes and stores normal of each face.
"""
if self._faceNormals is None:
self._faceNormals = []
for i, face in enumerate(self._faces):
## compute face normal
pts = [self._vertexes[vind] for vind in face]
norm = QtGui.QVector3D.crossProduct(pts[1]-pts[0], pts[2]-pts[0])
norm = norm / norm.length() ## don't use .normalized(); doesn't work for small values.
self._faceNormals.append(norm)
return self._faceNormals
def vertexNormals(self):
"""
Assigns each vertex the average of its connected face normals.
If face normals have not been computed yet, then generateFaceNormals will be called.
"""
if self._vertexNormals is None:
faceNorms = self.faceNormals()
vertFaces = self.vertexFaces()
self._vertexNormals = []
for vindex in range(len(self._vertexes)):
#print vertFaces[vindex]
norms = [faceNorms[findex] for findex in vertFaces[vindex]]
norm = QtGui.QVector3D()
for fn in norms:
norm += fn
norm = norm / norm.length() ## don't use .normalize(); doesn't work for small values.
self._vertexNormals.append(norm)
return self._vertexNormals
def vertexColors(self):
return self._vertexColors
def faceColors(self):
return self._faceColors
def edgeColors(self):
return self._edgeColors
#def reverseNormals(self):
#"""
#Reverses the direction of all normal vectors.
@ -168,7 +421,21 @@ class MeshData(object):
def save(self):
"""Serialize this mesh to a string appropriate for disk storage"""
import pickle
names = ['_vertexes', '_edges', '_faces', '_vertexFaces', '_vertexNormals', '_faceNormals', '_vertexColors', '_edgeColors', '_faceColors', '_meshColor']
if self._faces is not None:
names = ['_vertexes', '_faces']
else:
names = ['_vertexesIndexedByFaces']
if self._vertexColors is not None:
names.append('_vertexColors')
elif self._vertexColorsIndexedByFaces is not None:
names.append('_vertexColorsIndexedByFaces')
if self._faceColors is not None:
names.append('_faceColors')
elif self._faceColorsIndexedByFaces is not None:
names.append('_faceColorsIndexedByFaces')
state = {n:getattr(self, n) for n in names}
return pickle.dumps(state)
@ -178,6 +445,45 @@ class MeshData(object):
state = pickle.loads(state)
for k in state:
setattr(self, k, state[k])
def sphere(rows, cols, radius=1.0, offset=True):
"""
Return a MeshData instance with vertexes and faces computed
for a spherical surface.
"""
verts = np.empty((rows+1, cols, 3), dtype=float)
## compute vertexes
phi = (np.arange(rows+1) * np.pi / rows).reshape(rows+1, 1)
s = radius * np.sin(phi)
verts[...,2] = radius * np.cos(phi)
th = ((np.arange(cols) * 2 * np.pi / cols).reshape(1, cols))
if offset:
th = th + ((np.pi / cols) * np.arange(rows+1).reshape(rows+1,1)) ## rotate each row by 1/2 column
verts[...,0] = s * np.cos(th)
verts[...,1] = s * np.sin(th)
verts = verts.reshape((rows+1)*cols, 3)[cols-1:-(cols-1)] ## remove redundant vertexes from top and bottom
## compute faces
faces = np.empty((rows*cols*2, 3), dtype=np.uint)
rowtemplate1 = ((np.arange(cols).reshape(cols, 1) + np.array([[0, 1, 0]])) % cols) + np.array([[0, 0, cols]])
rowtemplate2 = ((np.arange(cols).reshape(cols, 1) + np.array([[0, 1, 1]])) % cols) + np.array([[cols, 0, cols]])
for row in range(rows):
start = row * cols * 2
faces[start:start+cols] = rowtemplate1 + row * cols
faces[start+cols:start+(cols*2)] = rowtemplate2 + row * cols
faces = faces[cols:-cols] ## cut off zero-area triangles at top and bottom
## adjust for redundant vertexes that were removed from top and bottom
vmin = cols-1
faces[faces<vmin] = vmin
faces -= vmin
vmax = verts.shape[0]-1
faces[faces>vmax] = vmax
return MeshData(vertexes=verts, faces=faces)

View File

@ -11,8 +11,9 @@ class GLGridItem(GLGraphicsItem):
Displays a wire-grame grid.
"""
def __init__(self, size=None, color=None):
def __init__(self, size=None, color=None, glOptions='translucent'):
GLGraphicsItem.__init__(self)
self.setGLOptions(glOptions)
if size is None:
size = QtGui.QVector3D(1,1,1)
self.setSize(size=size)
@ -34,10 +35,10 @@ class GLGridItem(GLGraphicsItem):
def paint(self):
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
glEnable( GL_BLEND )
glEnable( GL_ALPHA_TEST )
self.setupGLState()
#glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
#glEnable( GL_BLEND )
#glEnable( GL_ALPHA_TEST )
glEnable( GL_POINT_SMOOTH )
#glDisable( GL_DEPTH_TEST )
glBegin( GL_LINES )

View File

@ -16,65 +16,161 @@ class GLMeshItem(GLGraphicsItem):
Displays a 3D triangle mesh.
"""
def __init__(self, faces, vertexes=None):
def __init__(self, **kwds):
"""
See :class:`MeshData <pyqtgraph.opengl.MeshData>` for initialization arguments.
============== =====================================================
Arguments
meshdata MeshData object from which to determine geometry for
this item.
color Default color used if no vertex or face colors are
specified.
shader Name of shader program to use (None for no shader)
smooth If True, normal vectors are computed for each vertex
and interpolated within each face.
computeNormals If False, then computation of normal vectors is
disabled. This can provide a performance boost for
meshes that do not make use of normals.
============== =====================================================
"""
if isinstance(faces, MeshData):
self.data = faces
else:
self.data = MeshData()
self.data.setFaces(faces, vertexes)
self.opts = {
'meshdata': None,
'color': (1., 1., 1., 1.),
'shader': None,
'smooth': True,
'computeNormals': True,
}
GLGraphicsItem.__init__(self)
glopts = kwds.pop('glOptions', 'opaque')
self.setGLOptions(glopts)
shader = kwds.pop('shader', None)
self.setShader(shader)
def initializeGL(self):
self.shader = shaders.getShaderProgram('balloon')
self.setMeshData(**kwds)
l = glGenLists(1)
self.triList = l
glNewList(l, GL_COMPILE)
## storage for data compiled from MeshData object
self.vertexes = None
self.normals = None
self.colors = None
self.faces = None
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
glEnable( GL_BLEND )
glEnable( GL_ALPHA_TEST )
#glAlphaFunc( GL_ALWAYS,0.5 )
glEnable( GL_POINT_SMOOTH )
glDisable( GL_DEPTH_TEST )
glColor4f(1, 1, 1, .1)
glBegin( GL_TRIANGLES )
for face in self.data:
for (pos, norm, color) in face:
glColor4f(*color)
glNormal3f(norm.x(), norm.y(), norm.z())
glVertex3f(pos.x(), pos.y(), pos.z())
glEnd()
glEndList()
def setShader(self, shader):
self.opts['shader'] = shader
self.update()
def shader(self):
return shaders.getShaderProgram(self.opts['shader'])
#l = glGenLists(1)
#self.meshList = l
#glNewList(l, GL_COMPILE)
#glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
#glEnable( GL_BLEND )
#glEnable( GL_ALPHA_TEST )
##glAlphaFunc( GL_ALWAYS,0.5 )
#glEnable( GL_POINT_SMOOTH )
#glEnable( GL_DEPTH_TEST )
#glColor4f(1, 1, 1, .3)
#glBegin( GL_LINES )
#for f in self.faces:
#for i in [0,1,2]:
#j = (i+1) % 3
#glVertex3f(*f[i])
#glVertex3f(*f[j])
#glEnd()
#glEndList()
def setMeshData(self, **kwds):
"""
Set mesh data for this item. This can be invoked two ways:
1. Specify *meshdata* argument with a new MeshData object
2. Specify keyword arguments to be passed to MeshData(..) to create a new instance.
"""
md = kwds.get('meshdata', None)
if md is None:
opts = {}
for k in ['vertexes', 'faces', 'edges', 'vertexColors', 'faceColors']:
try:
opts[k] = kwds.pop(k)
except KeyError:
pass
md = MeshData(**opts)
self.opts['meshdata'] = md
self.opts.update(kwds)
self.meshDataChanged()
self.update()
def meshDataChanged(self):
"""
This method must be called to inform the item that the MeshData object
has been altered.
"""
self.vertexes = None
self.faces = None
self.normals = None
self.colors = None
self.update()
def parseMeshData(self):
## interpret vertex / normal data before drawing
## This can:
## - automatically generate normals if they were not specified
## - pull vertexes/noormals/faces from MeshData if that was specified
if self.vertexes is not None and self.normals is not None:
return
#if self.opts['normals'] is None:
#if self.opts['meshdata'] is None:
#self.opts['meshdata'] = MeshData(vertexes=self.opts['vertexes'], faces=self.opts['faces'])
if self.opts['meshdata'] is not None:
md = self.opts['meshdata']
if self.opts['smooth'] and not md.hasFaceIndexedData():
self.vertexes = md.vertexes()
if self.opts['computeNormals']:
self.normals = md.vertexNormals()
self.faces = md.faces()
if md.hasVertexColor():
self.colors = md.vertexColors()
if md.hasFaceColor():
self.colors = md.faceColors()
else:
self.vertexes = md.vertexes(indexed='faces')
if self.opts['computeNormals']:
if self.opts['smooth']:
self.normals = md.vertexNormals(indexed='faces')
else:
self.normals = md.faceNormals(indexed='faces')
self.faces = None
if md.hasVertexColor():
self.colors = md.vertexColors(indexed='faces')
elif md.hasFaceColor():
self.colors = md.faceColors(indexed='faces')
return
def paint(self):
with self.shader:
glCallList(self.triList)
#shaders.glUseProgram(self.shader)
#glCallList(self.triList)
#shaders.glUseProgram(0)
#glCallList(self.meshList)
self.setupGLState()
self.parseMeshData()
with self.shader():
verts = self.vertexes
norms = self.normals
color = self.colors
faces = self.faces
if verts is None:
return
glEnableClientState(GL_VERTEX_ARRAY)
try:
glVertexPointerf(verts)
if self.colors is None:
color = self.opts['color']
if isinstance(color, QtGui.QColor):
glColor4f(*fn.glColor(color))
else:
glColor4f(*color)
else:
glEnableClientState(GL_COLOR_ARRAY)
glColorPointerf(color)
if norms is not None:
glEnableClientState(GL_NORMAL_ARRAY)
glNormalPointerf(norms)
if faces is None:
glDrawArrays(GL_TRIANGLES, 0, np.product(verts.shape[:-1]))
else:
faces = faces.astype(np.uint).flatten()
glDrawElements(GL_TRIANGLES, faces.shape[0], GL_UNSIGNED_INT, faces)
finally:
glDisableClientState(GL_NORMAL_ARRAY)
glDisableClientState(GL_VERTEX_ARRAY)
glDisableClientState(GL_COLOR_ARRAY)

View File

@ -12,6 +12,8 @@ class GLScatterPlotItem(GLGraphicsItem):
def __init__(self, **kwds):
GLGraphicsItem.__init__(self)
glopts = kwds.pop('glOptions', 'additive')
self.setGLOptions(glopts)
self.pos = []
self.size = 10
self.color = [1.0,1.0,1.0,0.5]
@ -71,27 +73,27 @@ class GLScatterPlotItem(GLGraphicsItem):
glBindTexture(GL_TEXTURE_2D, self.pointTexture)
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, pData.shape[0], pData.shape[1], 0, GL_RGBA, GL_UNSIGNED_BYTE, pData)
self.shader = shaders.getShaderProgram('point_sprite')
self.shader = shaders.getShaderProgram('pointSprite')
#def getVBO(self, name):
#if name not in self.vbo:
#self.vbo[name] = vbo.VBO(getattr(self, name).astype('f'))
#return self.vbo[name]
def setupGLState(self):
"""Prepare OpenGL state for drawing. This function is called immediately before painting."""
#glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) ## requires z-sorting to render properly.
glBlendFunc(GL_SRC_ALPHA, GL_ONE)
glEnable( GL_BLEND )
glEnable( GL_ALPHA_TEST )
glDisable( GL_DEPTH_TEST )
#def setupGLState(self):
#"""Prepare OpenGL state for drawing. This function is called immediately before painting."""
##glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) ## requires z-sorting to render properly.
#glBlendFunc(GL_SRC_ALPHA, GL_ONE)
#glEnable( GL_BLEND )
#glEnable( GL_ALPHA_TEST )
#glDisable( GL_DEPTH_TEST )
#glEnable( GL_POINT_SMOOTH )
##glEnable( GL_POINT_SMOOTH )
#glHint(GL_POINT_SMOOTH_HINT, GL_NICEST)
#glPointParameterfv(GL_POINT_DISTANCE_ATTENUATION, (0, 0, -1e-3))
#glPointParameterfv(GL_POINT_SIZE_MAX, (65500,))
#glPointParameterfv(GL_POINT_SIZE_MIN, (0,))
##glHint(GL_POINT_SMOOTH_HINT, GL_NICEST)
##glPointParameterfv(GL_POINT_DISTANCE_ATTENUATION, (0, 0, -1e-3))
##glPointParameterfv(GL_POINT_SIZE_MAX, (65500,))
##glPointParameterfv(GL_POINT_SIZE_MIN, (0,))
def paint(self):
self.setupGLState()
@ -139,7 +141,7 @@ class GLScatterPlotItem(GLGraphicsItem):
glNormalPointerf(norm)
else:
glNormal3f(self.size,0,0)
glNormal3f(self.size, 0, 0) ## vertex shader uses norm.x to determine point size
#glPointSize(self.size)
glDrawArrays(GL_POINTS, 0, len(self.pos))
finally:

View File

@ -0,0 +1,139 @@
from OpenGL.GL import *
from GLMeshItem import GLMeshItem
from .. MeshData import MeshData
from pyqtgraph.Qt import QtGui
import pyqtgraph as pg
import numpy as np
__all__ = ['GLSurfacePlotItem']
class GLSurfacePlotItem(GLMeshItem):
"""
**Bases:** :class:`GLMeshItem <pyqtgraph.opengl.GLMeshItem>`
Displays a surface plot on a regular x,y grid
"""
def __init__(self, x=None, y=None, z=None, colors=None, **kwds):
"""
The x, y, z, and colors arguments are passed to setData().
All other keyword arguments are passed to GLMeshItem.__init__().
"""
self._x = None
self._y = None
self._z = None
self._color = None
self._vertexes = None
self._meshdata = MeshData()
GLMeshItem.__init__(self, meshdata=self._meshdata, **kwds)
self.setData(x, y, z, colors)
def setData(self, x=None, y=None, z=None, colors=None):
"""
Update the data in this surface plot.
========== =====================================================================
Arguments
x,y 1D arrays of values specifying the x,y positions of vertexes in the
grid. If these are omitted, then the values will be assumed to be
integers.
z 2D array of height values for each grid vertex.
colors (width, height, 4) array of vertex colors.
========== =====================================================================
All arguments are optional.
Note that if vertex positions are updated, the normal vectors for each triangle must
be recomputed. This is somewhat expensive if the surface was initialized with smooth=False
and very expensive if smooth=True. For faster performance, initialize with
computeNormals=False and use per-vertex colors or a normal-independent shader program.
"""
if x is not None:
if self._x is None or len(x) != len(self._x):
self._vertexes = None
self._x = x
if y is not None:
if self._y is None or len(y) != len(self._y):
self._vertexes = None
self._y = y
if z is not None:
#if self._x is None:
#self._x = np.arange(z.shape[0])
#self._vertexes = None
#if self._y is None:
#self._y = np.arange(z.shape[1])
#self._vertexes = None
if self._x is not None and z.shape[0] != len(self._x):
raise Exception('Z values must have shape (len(x), len(y))')
if self._y is not None and z.shape[1] != len(self._y):
raise Exception('Z values must have shape (len(x), len(y))')
self._z = z
if self._vertexes is not None and self._z.shape != self._vertexes.shape[:2]:
self._vertexes = None
if colors is not None:
self._colors = colors
self._meshdata.setVertexColors(colors)
if self._z is None:
return
updateMesh = False
newVertexes = False
## Generate vertex and face array
if self._vertexes is None:
newVertexes = True
self._vertexes = np.empty((self._z.shape[0], self._z.shape[1], 3), dtype=float)
self.generateFaces()
self._meshdata.setFaces(self._faces)
updateMesh = True
## Copy x, y, z data into vertex array
if newVertexes or x is not None:
if x is None:
if self._x is None:
x = np.arange(self._z.shape[0])
else:
x = self._x
self._vertexes[:, :, 0] = x.reshape(len(x), 1)
updateMesh = True
if newVertexes or y is not None:
if y is None:
if self._y is None:
y = np.arange(self._z.shape[1])
else:
y = self._y
self._vertexes[:, :, 1] = y.reshape(1, len(y))
updateMesh = True
if newVertexes or z is not None:
self._vertexes[...,2] = self._z
updateMesh = True
## Update MeshData
if updateMesh:
self._meshdata.setVertexes(self._vertexes.reshape(self._vertexes.shape[0]*self._vertexes.shape[1], 3))
self.meshDataChanged()
def generateFaces(self):
cols = self._z.shape[0]-1
rows = self._z.shape[1]-1
faces = np.empty((cols*rows*2, 3), dtype=np.uint)
rowtemplate1 = np.arange(cols).reshape(cols, 1) + np.array([[0, 1, cols+1]])
rowtemplate2 = np.arange(cols).reshape(cols, 1) + np.array([[cols+1, 1, cols+2]])
for row in range(rows):
start = row * cols * 2
faces[start:start+cols] = rowtemplate1 + row * (cols+1)
faces[start+cols:start+(cols*2)] = rowtemplate2 + row * (cols+1)
self._faces = faces

View File

@ -13,7 +13,7 @@ class GLVolumeItem(GLGraphicsItem):
"""
def __init__(self, data, sliceDensity=1, smooth=True):
def __init__(self, data, sliceDensity=1, smooth=True, glOptions='translucent'):
"""
============== =======================================================================================
**Arguments:**
@ -27,6 +27,7 @@ class GLVolumeItem(GLGraphicsItem):
self.smooth = smooth
self.data = data
GLGraphicsItem.__init__(self)
self.setGLOptions(glOptions)
def initializeGL(self):
glEnable(GL_TEXTURE_3D)
@ -62,15 +63,16 @@ class GLVolumeItem(GLGraphicsItem):
def paint(self):
self.setupGLState()
glEnable(GL_TEXTURE_3D)
glBindTexture(GL_TEXTURE_3D, self.texture)
glEnable(GL_DEPTH_TEST)
#glEnable(GL_DEPTH_TEST)
#glDisable(GL_CULL_FACE)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
glEnable( GL_BLEND )
glEnable( GL_ALPHA_TEST )
#glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
#glEnable( GL_BLEND )
#glEnable( GL_ALPHA_TEST )
glColor4f(1,1,1,1)
view = self.view()

View File

@ -1,18 +1,22 @@
from OpenGL.GL import *
from OpenGL.GL import shaders
import re
## For centralizing and managing vertex/fragment shader programs.
def initShaders():
global Shaders
Shaders = [
ShaderProgram('balloon', [ ## increases fragment alpha as the normal turns orthogonal to the view
ShaderProgram(None, []),
## increases fragment alpha as the normal turns orthogonal to the view
## this is useful for viewing shells that enclose a volume (such as isosurfaces)
ShaderProgram('balloon', [
VertexShader("""
varying vec3 normal;
void main() {
// compute here for use in fragment shader
normal = normalize(gl_NormalMatrix * gl_Normal);
//vec4 color = normal;
//normal.w = min(color.w + 2.0 * color.w * pow(normal.x*normal.x + normal.y*normal.y, 2.0), 1.0);
gl_FrontColor = gl_Color;
gl_BackColor = gl_Color;
gl_Position = ftransform();
@ -27,7 +31,154 @@ def initShaders():
}
""")
]),
ShaderProgram('point_sprite', [ ## allows specifying point size using normal.x
## colors fragments based on face normals relative to view
## This means that the colors will change depending on how the view is rotated
ShaderProgram('viewNormalColor', [
VertexShader("""
varying vec3 normal;
void main() {
// compute here for use in fragment shader
normal = normalize(gl_NormalMatrix * gl_Normal);
gl_FrontColor = gl_Color;
gl_BackColor = gl_Color;
gl_Position = ftransform();
}
"""),
FragmentShader("""
varying vec3 normal;
void main() {
vec4 color = gl_Color;
color.x = (normal.x + 1) * 0.5;
color.y = (normal.y + 1) * 0.5;
color.z = (normal.z + 1) * 0.5;
gl_FragColor = color;
}
""")
]),
## colors fragments based on absolute face normals.
ShaderProgram('normalColor', [
VertexShader("""
varying vec3 normal;
void main() {
// compute here for use in fragment shader
normal = normalize(gl_Normal);
gl_FrontColor = gl_Color;
gl_BackColor = gl_Color;
gl_Position = ftransform();
}
"""),
FragmentShader("""
varying vec3 normal;
void main() {
vec4 color = gl_Color;
color.x = (normal.x + 1) * 0.5;
color.y = (normal.y + 1) * 0.5;
color.z = (normal.z + 1) * 0.5;
gl_FragColor = color;
}
""")
]),
## very simple simulation of lighting.
## The light source position is always relative to the camera.
ShaderProgram('shaded', [
VertexShader("""
varying vec3 normal;
void main() {
// compute here for use in fragment shader
normal = normalize(gl_NormalMatrix * gl_Normal);
gl_FrontColor = gl_Color;
gl_BackColor = gl_Color;
gl_Position = ftransform();
}
"""),
FragmentShader("""
varying vec3 normal;
void main() {
float p = dot(normal, normalize(vec3(1, -1, -1)));
p = p < 0. ? 0. : p * 0.8;
vec4 color = gl_Color;
color.x = color.x * (0.2 + p);
color.y = color.y * (0.2 + p);
color.z = color.z * (0.2 + p);
gl_FragColor = color;
}
""")
]),
## colors get brighter near edges of object
ShaderProgram('edgeHilight', [
VertexShader("""
varying vec3 normal;
void main() {
// compute here for use in fragment shader
normal = normalize(gl_NormalMatrix * gl_Normal);
gl_FrontColor = gl_Color;
gl_BackColor = gl_Color;
gl_Position = ftransform();
}
"""),
FragmentShader("""
varying vec3 normal;
void main() {
vec4 color = gl_Color;
float s = pow(normal.x*normal.x + normal.y*normal.y, 2.0);
color.x = color.x + s * (1.0-color.x);
color.y = color.y + s * (1.0-color.y);
color.z = color.z + s * (1.0-color.z);
gl_FragColor = color;
}
""")
]),
## colors fragments by z-value.
## This is useful for coloring surface plots by height.
## This shader uses a uniform called "colorMap" to determine how to map the colors:
## red = pow(z * colorMap[0] + colorMap[1], colorMap[2])
## green = pow(z * colorMap[3] + colorMap[4], colorMap[5])
## blue = pow(z * colorMap[6] + colorMap[7], colorMap[8])
## (set the values like this: shader['uniformMap'] = array([...])
ShaderProgram('heightColor', [
VertexShader("""
varying vec4 pos;
void main() {
gl_FrontColor = gl_Color;
gl_BackColor = gl_Color;
pos = gl_Vertex;
gl_Position = ftransform();
}
"""),
FragmentShader("""
#version 140 // required for uniform blocks
uniform float colorMap[9];
varying vec4 pos;
out vec4 gl_FragColor;
in vec4 gl_Color;
void main() {
vec4 color = gl_Color;
color.x = colorMap[0] * (pos.z + colorMap[1]);
if (colorMap[2] != 1.0)
color.x = pow(color.x, colorMap[2]);
color.x = color.x < 0 ? 0 : (color.x > 1 ? 1 : color.x);
color.y = colorMap[3] * (pos.z + colorMap[4]);
if (colorMap[5] != 1.0)
color.y = pow(color.y, colorMap[5]);
color.y = color.y < 0 ? 0 : (color.y > 1 ? 1 : color.y);
color.z = colorMap[6] * (pos.z + colorMap[7]);
if (colorMap[8] != 1.0)
color.z = pow(color.z, colorMap[8]);
color.z = color.z < 0 ? 0 : (color.z > 1 ? 1 : color.z);
color.w = 1.0;
gl_FragColor = color;
}
"""),
], uniforms={'colorMap': [1, 1, 1, 1, 0.5, 1, 1, 0, 1]}),
ShaderProgram('pointSprite', [ ## allows specifying point size using normal.x
## See:
##
## http://stackoverflow.com/questions/9609423/applying-part-of-a-texture-sprite-sheet-texture-map-to-a-point-sprite-in-ios
@ -58,52 +209,186 @@ CompiledShaderPrograms = {}
def getShaderProgram(name):
return ShaderProgram.names[name]
class VertexShader:
def __init__(self, code):
class Shader:
def __init__(self, shaderType, code):
self.shaderType = shaderType
self.code = code
self.compiled = None
def shader(self):
if self.compiled is None:
self.compiled = shaders.compileShader(self.code, GL_VERTEX_SHADER)
try:
self.compiled = shaders.compileShader(self.code, self.shaderType)
except RuntimeError as exc:
## Format compile errors a bit more nicely
if len(exc.args) == 3:
err, code, typ = exc.args
if not err.startswith('Shader compile failure'):
raise
code = code[0].split('\n')
err, c, msgs = err.partition(':')
err = err + '\n'
msgs = msgs.split('\n')
errNums = [()] * len(code)
for i, msg in enumerate(msgs):
msg = msg.strip()
if msg == '':
continue
m = re.match(r'\d+\((\d+)\)', msg)
if m is not None:
line = int(m.groups()[0])
errNums[line-1] = errNums[line-1] + (str(i+1),)
#code[line-1] = '%d\t%s' % (i+1, code[line-1])
err = err + "%d %s\n" % (i+1, msg)
errNums = [','.join(n) for n in errNums]
maxlen = max(map(len, errNums))
code = [errNums[i] + " "*(maxlen-len(errNums[i])) + line for i, line in enumerate(code)]
err = err + '\n'.join(code)
raise Exception(err)
else:
raise
return self.compiled
class FragmentShader:
class VertexShader(Shader):
def __init__(self, code):
self.code = code
self.compiled = None
Shader.__init__(self, GL_VERTEX_SHADER, code)
class FragmentShader(Shader):
def __init__(self, code):
Shader.__init__(self, GL_FRAGMENT_SHADER, code)
def shader(self):
if self.compiled is None:
self.compiled = shaders.compileShader(self.code, GL_FRAGMENT_SHADER)
return self.compiled
class ShaderProgram:
names = {}
def __init__(self, name, shaders):
def __init__(self, name, shaders, uniforms=None):
self.name = name
ShaderProgram.names[name] = self
self.shaders = shaders
self.prog = None
self.blockData = {}
self.uniformData = {}
## parse extra options from the shader definition
if uniforms is not None:
for k,v in uniforms.items():
self[k] = v
def setBlockData(self, blockName, data):
if data is None:
del self.blockData[blockName]
else:
self.blockData[blockName] = data
def setUniformData(self, uniformName, data):
if data is None:
del self.uniformData[uniformName]
else:
self.uniformData[uniformName] = data
def __setitem__(self, item, val):
self.setUniformData(item, val)
def __delitem__(self, item):
self.setUniformData(item, None)
def program(self):
if self.prog is None:
compiled = [s.shader() for s in self.shaders] ## compile all shaders
self.prog = shaders.compileProgram(*compiled) ## compile program
try:
compiled = [s.shader() for s in self.shaders] ## compile all shaders
self.prog = shaders.compileProgram(*compiled) ## compile program
except:
self.prog = -1
raise
return self.prog
def __enter__(self):
glUseProgram(self.program())
if len(self.shaders) > 0 and self.program() != -1:
glUseProgram(self.program())
try:
## load uniform values into program
for uniformName, data in self.uniformData.items():
loc = self.uniform(uniformName)
if loc == -1:
raise Exception('Could not find uniform variable "%s"' % uniformName)
glUniform1fv(loc, len(data), data)
### bind buffer data to program blocks
#if len(self.blockData) > 0:
#bindPoint = 1
#for blockName, data in self.blockData.items():
### Program should have a uniform block declared:
###
### layout (std140) uniform blockName {
### vec4 diffuse;
### };
### pick any-old binding point. (there are a limited number of these per-program
#bindPoint = 1
### get the block index for a uniform variable in the shader
#blockIndex = glGetUniformBlockIndex(self.program(), blockName)
### give the shader block a binding point
#glUniformBlockBinding(self.program(), blockIndex, bindPoint)
### create a buffer
#buf = glGenBuffers(1)
#glBindBuffer(GL_UNIFORM_BUFFER, buf)
#glBufferData(GL_UNIFORM_BUFFER, size, data, GL_DYNAMIC_DRAW)
### also possible to use glBufferSubData to fill parts of the buffer
### bind buffer to the same binding point
#glBindBufferBase(GL_UNIFORM_BUFFER, bindPoint, buf)
except:
glUseProgram(0)
raise
def __exit__(self, *args):
glUseProgram(0)
if len(self.shaders) > 0:
glUseProgram(0)
def uniform(self, name):
"""Return the location integer for a uniform variable in this program"""
return glGetUniformLocation(self.program(), name)
#def uniformBlockInfo(self, blockName):
#blockIndex = glGetUniformBlockIndex(self.program(), blockName)
#count = glGetActiveUniformBlockiv(self.program(), blockIndex, GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS)
#indices = []
#for i in range(count):
#indices.append(glGetActiveUniformBlockiv(self.program(), blockIndex, GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES))
class HeightColorShader(ShaderProgram):
def __enter__(self):
## Program should have a uniform block declared:
##
## layout (std140) uniform blockName {
## vec4 diffuse;
## vec4 ambient;
## };
## pick any-old binding point. (there are a limited number of these per-program
bindPoint = 1
## get the block index for a uniform variable in the shader
blockIndex = glGetUniformBlockIndex(self.program(), "blockName")
## give the shader block a binding point
glUniformBlockBinding(self.program(), blockIndex, bindPoint)
## create a buffer
buf = glGenBuffers(1)
glBindBuffer(GL_UNIFORM_BUFFER, buf)
glBufferData(GL_UNIFORM_BUFFER, size, data, GL_DYNAMIC_DRAW)
## also possible to use glBufferSubData to fill parts of the buffer
## bind buffer to the same binding point
glBindBufferBase(GL_UNIFORM_BUFFER, bindPoint, buf)
initShaders()

View File

@ -460,7 +460,10 @@ class Parameter(QtCore.QObject):
self.childs.pop(self.childs.index(child))
child.parentChanged(None)
self.sigChildRemoved.emit(self, child)
child.sigTreeStateChanged.disconnect(self.treeStateChanged)
try:
child.sigTreeStateChanged.disconnect(self.treeStateChanged)
except TypeError: ## already disconnected
pass
def clearChildren(self):
"""Remove all child parameters."""
@ -550,6 +553,8 @@ class Parameter(QtCore.QObject):
def __getattr__(self, attr):
## Leaving this undocumented because I might like to remove it in the future..
#print type(self), attr
if 'names' not in self.__dict__:
raise AttributeError(attr)
if attr in self.names:
return self.param(attr)
else:

View File

@ -56,8 +56,13 @@ class LayoutWidget(QtGui.QWidget):
"""
Add a widget to the layout and place it in the next available cell (or in the cell specified).
"""
if row is None:
if row == 'next':
self.nextRow()
row = self.currentRow
elif row is None:
row = self.currentRow
if col is None:
col = self.nextCol(colspan)