merge from Luke
This commit is contained in:
commit
70fde35e3b
72
examples/GLIsosurface.py
Normal file
72
examples/GLIsosurface.py
Normal 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_()
|
|
@ -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
98
examples/GLSurfacePlot.py
Normal 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
108
examples/GLshaders.py
Normal 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_()
|
|
@ -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.
|
||||
|
|
|
@ -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()],
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
367
functions.py
367
functions.py
|
@ -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)
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
63
graphicsItems/GraphicsWidgetAnchor.py
Normal file
63
graphicsItems/GraphicsWidgetAnchor.py
Normal 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)
|
||||
|
||||
|
|
@ -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)
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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'
|
||||
|
||||
|
|
|
@ -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']
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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"""
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
@ -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 )
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
139
opengl/items/GLSurfacePlotItem.py
Normal file
139
opengl/items/GLSurfacePlotItem.py
Normal 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
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user