merge from luke

This commit is contained in:
Megan Kratz 2012-11-10 11:27:33 -05:00
commit 2ca4cddff7
22 changed files with 698 additions and 392 deletions

View File

@ -6,12 +6,12 @@ import pyqtgraph as pg
import numpy as np
import scipy.linalg
class SRTTransform3D(QtGui.QMatrix4x4):
class SRTTransform3D(pg.Transform3D):
"""4x4 Transform matrix that can always be represented as a combination of 3 matrices: scale * rotate * translate
This transform has no shear; angles are always preserved.
"""
def __init__(self, init=None):
QtGui.QMatrix4x4.__init__(self)
pg.Transform3D.__init__(self)
self.reset()
if init is None:
return
@ -190,11 +190,11 @@ class SRTTransform3D(QtGui.QMatrix4x4):
self.update()
def update(self):
QtGui.QMatrix4x4.setToIdentity(self)
pg.Transform3D.setToIdentity(self)
## modifications to the transform are multiplied on the right, so we need to reverse order here.
QtGui.QMatrix4x4.translate(self, *self._state['pos'])
QtGui.QMatrix4x4.rotate(self, self._state['angle'], *self._state['axis'])
QtGui.QMatrix4x4.scale(self, *self._state['scale'])
pg.Transform3D.translate(self, *self._state['pos'])
pg.Transform3D.rotate(self, self._state['angle'], *self._state['axis'])
pg.Transform3D.scale(self, *self._state['scale'])
def __repr__(self):
return str(self.saveState())

35
Transform3D.py Normal file
View File

@ -0,0 +1,35 @@
# -*- coding: utf-8 -*-
from .Qt import QtCore, QtGui
import pyqtgraph as pg
import numpy as np
class Transform3D(QtGui.QMatrix4x4):
"""
Extension of QMatrix4x4 with some helpful methods added.
"""
def __init__(self, *args):
QtGui.QMatrix4x4.__init__(self, *args)
def matrix(self, nd=3):
if nd == 3:
return np.array(self.copyDataTo()).reshape(4,4)
elif nd == 2:
m = np.array(self.copyDataTo()).reshape(4,4)
m[2] = m[3]
m[:,2] = m[:,3]
return m[:3,:3]
else:
raise Exception("Argument 'nd' must be 2 or 3")
def map(self, obj):
"""
Extends QMatrix4x4.map() to allow mapping (3, ...) arrays of coordinates
"""
if isinstance(obj, np.ndarray) and obj.ndim >= 2 and obj.shape[0] in (2,3):
return pg.transformCoordinates(self, obj)
else:
return QtGui.QMatrix4x4.map(self, obj)
def inverted(self):
inv, b = QtGui.QMatrix4x4.inverted(self)
return Transform3D(inv), b

123
Vector.py
View File

@ -1,59 +1,64 @@
# -*- coding: utf-8 -*-
"""
Vector.py - Extension of QVector3D which adds a few missing methods.
Copyright 2010 Luke Campagnola
Distributed under MIT/X11 license. See license.txt for more infomation.
"""
from .Qt import QtGui, QtCore
import numpy as np
class Vector(QtGui.QVector3D):
"""Extension of QVector3D which adds a few helpful methods."""
def __init__(self, *args):
if len(args) == 1:
if isinstance(args[0], QtCore.QSizeF):
QtGui.QVector3D.__init__(self, float(args[0].width()), float(args[0].height()), 0)
return
elif isinstance(args[0], QtCore.QPoint) or isinstance(args[0], QtCore.QPointF):
QtGui.QVector3D.__init__(self, float(args[0].x()), float(args[0].y()), 0)
elif hasattr(args[0], '__getitem__'):
vals = list(args[0])
if len(vals) == 2:
vals.append(0)
if len(vals) != 3:
raise Exception('Cannot init Vector with sequence of length %d' % len(args[0]))
QtGui.QVector3D.__init__(self, *vals)
return
elif len(args) == 2:
QtGui.QVector3D.__init__(self, args[0], args[1], 0)
return
QtGui.QVector3D.__init__(self, *args)
def __len__(self):
return 3
#def __reduce__(self):
#return (Point, (self.x(), self.y()))
def __getitem__(self, i):
if i == 0:
return self.x()
elif i == 1:
return self.y()
elif i == 2:
return self.z()
else:
raise IndexError("Point has no index %s" % str(i))
def __setitem__(self, i, x):
if i == 0:
return self.setX(x)
elif i == 1:
return self.setY(x)
elif i == 2:
return self.setZ(x)
else:
raise IndexError("Point has no index %s" % str(i))
# -*- coding: utf-8 -*-
"""
Vector.py - Extension of QVector3D which adds a few missing methods.
Copyright 2010 Luke Campagnola
Distributed under MIT/X11 license. See license.txt for more infomation.
"""
from .Qt import QtGui, QtCore
import numpy as np
class Vector(QtGui.QVector3D):
"""Extension of QVector3D which adds a few helpful methods."""
def __init__(self, *args):
if len(args) == 1:
if isinstance(args[0], QtCore.QSizeF):
QtGui.QVector3D.__init__(self, float(args[0].width()), float(args[0].height()), 0)
return
elif isinstance(args[0], QtCore.QPoint) or isinstance(args[0], QtCore.QPointF):
QtGui.QVector3D.__init__(self, float(args[0].x()), float(args[0].y()), 0)
elif hasattr(args[0], '__getitem__'):
vals = list(args[0])
if len(vals) == 2:
vals.append(0)
if len(vals) != 3:
raise Exception('Cannot init Vector with sequence of length %d' % len(args[0]))
QtGui.QVector3D.__init__(self, *vals)
return
elif len(args) == 2:
QtGui.QVector3D.__init__(self, args[0], args[1], 0)
return
QtGui.QVector3D.__init__(self, *args)
def __len__(self):
return 3
#def __reduce__(self):
#return (Point, (self.x(), self.y()))
def __getitem__(self, i):
if i == 0:
return self.x()
elif i == 1:
return self.y()
elif i == 2:
return self.z()
else:
raise IndexError("Point has no index %s" % str(i))
def __setitem__(self, i, x):
if i == 0:
return self.setX(x)
elif i == 1:
return self.setY(x)
elif i == 2:
return self.setZ(x)
else:
raise IndexError("Point has no index %s" % str(i))
def __iter__(self):
yield(self.x())
yield(self.y())
yield(self.z())

View File

@ -165,6 +165,7 @@ from .WidgetGroup import *
from .Point import Point
from .Vector import Vector
from .SRTTransform import SRTTransform
from .Transform3D import Transform3D
from .SRTTransform3D import SRTTransform3D
from .functions import *
from .graphicsWindows import *

View File

@ -208,6 +208,10 @@ class DockArea(Container, QtGui.QWidget, DockDrop):
def restoreState(self, state):
"""
Restore Dock configuration as generated by saveState.
Note that this function does not create any Docks--it will only
restore the arrangement of an existing set of Docks.
"""
## 1) make dict of all docks and list of existing containers
@ -240,8 +244,11 @@ class DockArea(Container, QtGui.QWidget, DockDrop):
typ, contents, state = state
pfx = " " * depth
if typ == 'dock':
obj = docks[contents]
del docks[contents]
try:
obj = docks[contents]
del docks[contents]
except KeyError:
raise Exception('Cannot restore dock state; no dock with name "%s"' % contents)
else:
obj = self.makeContainer(typ)
@ -270,7 +277,7 @@ class DockArea(Container, QtGui.QWidget, DockDrop):
if isinstance(obj, Dock):
d[obj.name()] = obj
else:
elif obj is not None:
c.append(obj)
for i in range(obj.count()):
o2 = obj.widget(i)

View File

@ -15,48 +15,91 @@ w.show()
g = gl.GLGridItem()
w.addItem(g)
#pos = np.empty((53, 3))
#size = np.empty((53))
#color = np.empty((53, 4))
#pos[0] = (1,0,0); size[0] = 0.5; color[0] = (1.0, 0.0, 0.0, 0.5)
#pos[1] = (0,1,0); size[1] = 0.2; color[1] = (0.0, 0.0, 1.0, 0.5)
#pos[2] = (0,0,1); size[2] = 2./3.; color[2] = (0.0, 1.0, 0.0, 0.5)
#z = 0.5
#d = 6.0
#for i in range(3,53):
#pos[i] = (0,0,z)
#size[i] = 2./d
#color[i] = (0.0, 1.0, 0.0, 0.5)
#z *= 0.5
#d *= 2.0
##
## First example is a set of points with pxMode=False
## These demonstrate the ability to have points with real size down to a very small scale
##
pos = np.empty((53, 3))
size = np.empty((53))
color = np.empty((53, 4))
pos[0] = (1,0,0); size[0] = 0.5; color[0] = (1.0, 0.0, 0.0, 0.5)
pos[1] = (0,1,0); size[1] = 0.2; color[1] = (0.0, 0.0, 1.0, 0.5)
pos[2] = (0,0,1); size[2] = 2./3.; color[2] = (0.0, 1.0, 0.0, 0.5)
z = 0.5
d = 6.0
for i in range(3,53):
pos[i] = (0,0,z)
size[i] = 2./d
color[i] = (0.0, 1.0, 0.0, 0.5)
z *= 0.5
d *= 2.0
#sp = gl.GLScatterPlotItem(pos=pos, sizes=size, colors=color, pxMode=False)
sp1 = gl.GLScatterPlotItem(pos=pos, size=size, color=color, pxMode=False)
sp1.translate(5,5,0)
w.addItem(sp1)
pos = (np.random.random(size=(100000,3)) * 10) - 5
##
## Second example shows a volume of points with rapidly updating color
## and pxMode=True
##
pos = np.random.random(size=(100000,3))
pos *= [10,-10,10]
pos[0] = (0,0,0)
color = np.ones((pos.shape[0], 4))
d = (pos**2).sum(axis=1)**0.5
color[:,3] = np.clip(-np.cos(d*2) * 0.2, 0, 1)
sp = gl.GLScatterPlotItem(pos=pos, color=color, size=5)
d2 = (pos**2).sum(axis=1)**0.5
size = np.random.random(size=pos.shape[0])*10
sp2 = gl.GLScatterPlotItem(pos=pos, color=(1,1,1,1), size=size)
phase = 0.
w.addItem(sp2)
##
## Third example shows a grid of points with rapidly updating position
## and pxMode = False
##
pos3 = np.zeros((100,100,3))
pos3[:,:,:2] = np.mgrid[:100, :100].transpose(1,2,0) * [-0.1,0.1]
pos3 = pos3.reshape(10000,3)
d3 = (pos3**2).sum(axis=1)**0.5
sp3 = gl.GLScatterPlotItem(pos=pos3, color=(1,1,1,.3), size=0.1, pxMode=False)
w.addItem(sp3)
def update():
global phase, color, sp, d
s = -np.cos(d*2+phase)
color[:,3] = np.clip(s * 0.2, 0, 1)
## update volume colors
global phase, sp2, d2
s = -np.cos(d2*2+phase)
color = np.empty((len(d2),4), dtype=np.float32)
color[:,3] = np.clip(s * 0.1, 0, 1)
color[:,0] = np.clip(s * 3.0, 0, 1)
color[:,1] = np.clip(s * 1.0, 0, 1)
color[:,2] = np.clip(s ** 3, 0, 1)
sp.setData(color=color)
sp2.setData(color=color)
phase -= 0.1
## update surface positions and colors
global sp3, d3, pos3
z = -np.cos(d3*2+phase)
pos3[:,2] = z
color = np.empty((len(d3),4), dtype=np.float32)
color[:,3] = 0.3
color[:,0] = np.clip(z * 3.0, 0, 1)
color[:,1] = np.clip(z * 1.0, 0, 1)
color[:,2] = np.clip(z ** 3, 0, 1)
sp3.setData(pos=pos3, color=color)
t = QtCore.QTimer()
t.timeout.connect(update)
t.start(50)
w.addItem(sp)
## Start Qt event loop unless running in interactive mode.
if sys.flags.interactive != 1:

View File

@ -36,12 +36,16 @@ img1a = pg.ImageItem(arr)
v1a.addItem(img1a)
img1b = pg.ImageItem()
v1b.addItem(img1b)
v1a.disableAutoRange('xy')
v1b.disableAutoRange('xy')
v1a.autoRange()
v1b.autoRange()
rois = []
rois.append(pg.RectROI([20, 20], [20, 20], pen=(0,9)))
rois[-1].addRotateHandle([1,0], [0.5, 0.5])
rois.append(pg.LineROI([0, 60], [20, 80], width=5, pen=(1,9)))
rois.append(pg.MultiLineROI([[20, 90], [50, 60], [60, 90]], width=5, pen=(2,9)))
rois.append(pg.MultiRectROI([[20, 90], [50, 60], [60, 90]], width=5, pen=(2,9)))
rois.append(pg.EllipseROI([60, 10], [30, 20], pen=(3,9)))
rois.append(pg.CircleROI([80, 50], [20, 20], pen=(4,9)))
#rois.append(pg.LineSegmentROI([[110, 50], [20, 20]], pen=(5,9)))
@ -70,6 +74,10 @@ r2a = pg.PolyLineROI([[0,0], [10,10], [10,30], [30,10]], closed=True)
v2a.addItem(r2a)
r2b = pg.PolyLineROI([[0,-20], [10,-10], [10,-30]], closed=False)
v2a.addItem(r2b)
v2a.disableAutoRange('xy')
#v2b.disableAutoRange('xy')
v2a.autoRange()
#v2b.autoRange()
text = """Building custom ROI types<Br>
ROIs can be built with a variety of different handle types<br>
@ -107,6 +115,9 @@ r3b.addRotateHandle([0, 1], [1, 0])
r3b.addScaleRotateHandle([0, 0.5], [0.5, 0.5])
r3b.addScaleRotateHandle([1, 0.5], [0.5, 0.5])
v3.disableAutoRange('xy')
v3.autoRange()
text = """Transforming objects with ROI"""
w4 = w.addLayout(row=1, col=1)
@ -121,6 +132,9 @@ img4 = pg.ImageItem(arr)
v4.addItem(r4)
img4.setParentItem(r4)
v4.disableAutoRange('xy')
v4.autoRange()

View File

@ -165,6 +165,9 @@ class TickSliderItem(GraphicsWidget):
tick.setPos(pos)
self.ticks[tick] = float(newX) / self.length
def tickMoveFinished(self, tick):
pass
def tickClicked(self, tick, ev):
if ev.button() == QtCore.Qt.RightButton:
self.removeTick(tick)
@ -340,16 +343,18 @@ class GradientEditorItem(TickSliderItem):
customizable by the user. :class: `GradientWidget <pyqtgraph.GradientWidget>` provides a widget
with a GradientEditorItem that can be added to a GUI.
======================== ===========================================================
================================ ===========================================================
**Signals**
sigGradientChanged(self) Signal is emitted anytime the gradient changes. The signal
is emitted in real time while ticks are being dragged or
colors are being changed.
======================== ===========================================================
sigGradientChanged(self) Signal is emitted anytime the gradient changes. The signal
is emitted in real time while ticks are being dragged or
colors are being changed.
sigGradientChangeFinished(self) Signal is emitted when the gradient is finished changing.
================================ ===========================================================
"""
sigGradientChanged = QtCore.Signal(object)
sigGradientChangeFinished = QtCore.Signal(object)
def __init__(self, *args, **kargs):
"""
@ -381,6 +386,7 @@ class GradientEditorItem(TickSliderItem):
self.colorDialog.currentColorChanged.connect(self.currentColorChanged)
self.colorDialog.rejected.connect(self.currentColorRejected)
self.colorDialog.accepted.connect(self.currentColorAccepted)
self.backgroundRect.setParentItem(self)
self.gradRect.setParentItem(self)
@ -508,6 +514,9 @@ class GradientEditorItem(TickSliderItem):
self.setTickColor(self.currentTick, self.currentTickColor)
self.updateGradient()
def currentColorAccepted(self):
self.sigGradientChangeFinished.emit(self)
def tickClicked(self, tick, ev):
#private
if ev.button() == QtCore.Qt.LeftButton:
@ -533,6 +542,9 @@ class GradientEditorItem(TickSliderItem):
TickSliderItem.tickMoved(self, tick, pos)
self.updateGradient()
def tickMoveFinished(self, tick):
self.sigGradientChangeFinished.emit(self)
def getGradient(self):
"""Return a QLinearGradient object."""
@ -669,7 +681,7 @@ class GradientEditorItem(TickSliderItem):
TickSliderItem.mouseReleaseEvent(self, ev)
self.updateGradient()
def addTick(self, x, color=None, movable=True):
def addTick(self, x, color=None, movable=True, finish=True):
"""
Add a tick to the gradient. Return the tick.
@ -688,7 +700,17 @@ class GradientEditorItem(TickSliderItem):
t = TickSliderItem.addTick(self, x, color=color, movable=movable)
t.colorChangeAllowed = True
t.removeAllowed = True
if finish:
self.sigGradientChangeFinished.emit(self)
return t
def removeTick(self, tick, finish=True):
TickSliderItem.removeTick(self, tick)
if finish:
self.sigGradientChangeFinished.emit(self)
def saveState(self):
"""
@ -723,13 +745,14 @@ class GradientEditorItem(TickSliderItem):
## public
self.setColorMode(state['mode'])
for t in list(self.ticks.keys()):
self.removeTick(t)
self.removeTick(t, finish=False)
for t in state['ticks']:
c = QtGui.QColor(*t[1])
self.addTick(t[0], c)
self.addTick(t[0], c, finish=False)
self.updateGradient()
self.sigGradientChangeFinished.emit(self)
class Tick(GraphicsObject):
## private class
@ -791,6 +814,7 @@ class Tick(GraphicsObject):
if ev.isFinish():
self.moving = False
self.sigMoved.emit(self)
self.view().tickMoveFinished(self)
def mouseClickEvent(self, ev):
if ev.button() == QtCore.Qt.RightButton and self.moving:

View File

@ -433,4 +433,18 @@ class GraphicsItem(object):
"""
Called whenever the transformation matrix of the view has changed.
"""
pass
pass
#def prepareGeometryChange(self):
#self._qtBaseClass.prepareGeometryChange(self)
#self.informViewBoundsChanged()
def informViewBoundsChanged(self):
"""
Inform this item's container ViewBox that the bounds of this item have changed.
This is used by ViewBox to react if auto-range is enabled.
"""
view = self.getViewBox()
if view is not None and hasattr(view, 'implements') and view.implements('ViewBox'):
view.itemBoundsChanged(self) ## inform view so it can update its range if it wants

View File

@ -11,10 +11,13 @@ class GraphicsObject(GraphicsItem, QtGui.QGraphicsObject):
_qtBaseClass = QtGui.QGraphicsObject
def __init__(self, *args):
QtGui.QGraphicsObject.__init__(self, *args)
self.setFlag(self.ItemSendsGeometryChanges)
GraphicsItem.__init__(self)
def itemChange(self, change, value):
ret = QtGui.QGraphicsObject.itemChange(self, change, value)
if change in [self.ItemParentHasChanged, self.ItemSceneHasChanged]:
self._updateView()
if change in [self.ItemPositionHasChanged, self.ItemTransformHasChanged]:
self.informViewBoundsChanged()
return ret

View File

@ -361,9 +361,11 @@ class PlotDataItem(GraphicsObject):
self.updateItems()
prof.mark('update items')
view = self.getViewBox()
if view is not None:
view.itemBoundsChanged(self) ## inform view so it can update its range if it wants
self.sigPlotChanged.emit(self)
prof.mark('emit')
prof.finish()

View File

@ -506,12 +506,14 @@ class PlotItem(GraphicsWidget):
self.curves.append(item)
#self.addItem(c)
if hasattr(item, 'setLogMode'):
item.setLogMode(self.ctrl.logXCheck.isChecked(), self.ctrl.logYCheck.isChecked())
if isinstance(item, PlotDataItem):
## configure curve for this plot
(alpha, auto) = self.alphaState()
item.setAlpha(alpha, auto)
item.setFftMode(self.ctrl.fftCheck.isChecked())
item.setLogMode(self.ctrl.logXCheck.isChecked(), self.ctrl.logYCheck.isChecked())
item.setDownsampling(self.downsampleMode())
item.setPointMode(self.pointMode())
@ -526,6 +528,7 @@ class PlotItem(GraphicsWidget):
#c.connect(c, QtCore.SIGNAL('plotChanged'), self.plotChanged)
#item.sigPlotChanged.connect(self.plotChanged)
#self.plotChanged()
def addDataItem(self, item, *args):
print("PlotItem.addDataItem is deprecated. Use addItem instead.")
@ -878,8 +881,9 @@ class PlotItem(GraphicsWidget):
def updateLogMode(self):
x = self.ctrl.logXCheck.isChecked()
y = self.ctrl.logYCheck.isChecked()
for c in self.curves:
c.setLogMode(x,y)
for i in self.items:
if hasattr(i, 'setLogMode'):
i.setLogMode(x,y)
self.getAxis('bottom').setLogMode(x)
self.getAxis('top').setLogMode(x)
self.getAxis('left').setLogMode(y)

View File

@ -28,7 +28,7 @@ from .UIGraphicsItem import UIGraphicsItem
__all__ = [
'ROI',
'TestROI', 'RectROI', 'EllipseROI', 'CircleROI', 'PolygonROI',
'LineROI', 'MultiLineROI', 'LineSegmentROI', 'PolyLineROI', 'SpiralROI',
'LineROI', 'MultiLineROI', 'MultiRectROI', 'LineSegmentROI', 'PolyLineROI', 'SpiralROI',
]
@ -370,7 +370,9 @@ class ROI(GraphicsObject):
else:
return (self.handles[index]['name'], self.handles[index]['item'].scenePos())
def getHandles(self):
return [h['item'] for h in self.handles]
def mapSceneToParent(self, pt):
return self.mapToParent(self.mapFromScene(pt))
@ -538,19 +540,27 @@ class ROI(GraphicsObject):
return True
def movePoint(self, handle, pos, modifiers=QtCore.Qt.KeyboardModifier(), finish=True):
def movePoint(self, handle, pos, modifiers=QtCore.Qt.KeyboardModifier(), finish=True, coords='parent'):
## called by Handles when they are moved.
## pos is the new position of the handle in scene coords, as requested by the handle.
newState = self.stateCopy()
index = self.indexOfHandle(handle)
h = self.handles[index]
p0 = self.mapToScene(h['pos'] * self.state['size'])
p0 = self.mapToParent(h['pos'] * self.state['size'])
p1 = Point(pos)
if coords == 'parent':
pass
elif coords == 'scene':
p1 = self.mapSceneToParent(p1)
else:
raise Exception("New point location must be given in either 'parent' or 'scene' coordinates.")
## transform p0 and p1 into parent's coordinates (same as scene coords if there is no parent). I forget why.
p0 = self.mapSceneToParent(p0)
p1 = self.mapSceneToParent(p1)
#p0 = self.mapSceneToParent(p0)
#p1 = self.mapSceneToParent(p1)
## Handles with a 'center' need to know their local position relative to the center point (lp0, lp1)
if 'center' in h:
@ -566,8 +576,8 @@ class ROI(GraphicsObject):
self.translate(p1-p0, snap=snap, update=False)
elif h['type'] == 'f':
newPos = self.mapFromScene(pos)
h['item'].setPos(self.mapFromScene(pos))
newPos = self.mapFromParent(p1)
h['item'].setPos(newPos)
h['pos'] = newPos
self.freeHandleMoved = True
#self.sigRegionChanged.emit(self) ## should be taken care of by call to stateChanged()
@ -1212,7 +1222,7 @@ class Handle(UIGraphicsItem):
#print "point moved; inform %d ROIs" % len(self.roi)
# A handle can be used by multiple ROIs; tell each to update its handle position
for r in self.rois:
r.movePoint(self, pos, modifiers, finish=finish)
r.movePoint(self, pos, modifiers, finish=finish, coords='scene')
def buildPath(self):
size = self.radius
@ -1264,9 +1274,9 @@ class Handle(UIGraphicsItem):
if self._shape is None:
s = self.generateShape()
if s is None:
return self.shape
return self.path
self._shape = s
self.prepareGeometryChange()
self.prepareGeometryChange() ## beware--this can cause the view to adjust, which would immediately invalidate the shape.
return self._shape
def boundingRect(self):
@ -1357,9 +1367,16 @@ class LineROI(ROI):
self.addScaleRotateHandle([1, 0.5], [0, 0.5])
self.addScaleHandle([0.5, 1], [0.5, 0.5])
class MultiLineROI(QtGui.QGraphicsObject):
class MultiRectROI(QtGui.QGraphicsObject):
"""
Chain of rectangular ROIs connected by handles.
This is generally used to mark a curved path through
an image similarly to PolyLineROI. It differs in that each segment
of the chain is rectangular instead of linear and thus has width.
"""
sigRegionChangeFinished = QtCore.Signal(object)
sigRegionChangeStarted = QtCore.Signal(object)
sigRegionChanged = QtCore.Signal(object)
@ -1368,27 +1385,17 @@ class MultiLineROI(QtGui.QGraphicsObject):
QtGui.QGraphicsObject.__init__(self)
self.pen = pen
self.roiArgs = args
self.lines = []
if len(points) < 2:
raise Exception("Must start with at least 2 points")
self.lines = []
self.lines.append(ROI([0, 0], [1, 5], parent=self, pen=pen, **args))
self.lines[-1].addScaleHandle([0.5, 1], [0.5, 0.5])
h = self.lines[-1].addScaleRotateHandle([0, 0.5], [1, 0.5])
h.movePoint(points[0])
h.movePoint(points[0])
for i in range(1, len(points)):
h = self.lines[-1].addScaleRotateHandle([1, 0.5], [0, 0.5])
if i < len(points)-1:
self.lines.append(ROI([0, 0], [1, 5], parent=self, pen=pen, **args))
self.lines[-1].addScaleRotateHandle([0, 0.5], [1, 0.5], item=h)
h.movePoint(points[i])
h.movePoint(points[i])
for l in self.lines:
l.translatable = False
l.sigRegionChanged.connect(self.roiChangedEvent)
l.sigRegionChangeStarted.connect(self.roiChangeStartedEvent)
l.sigRegionChangeFinished.connect(self.roiChangeFinishedEvent)
## create first segment
self.addSegment(points[1], connectTo=points[0], scaleHandle=True)
## create remaining segments
for p in points[2:]:
self.addSegment(p)
def paint(self, *args):
pass
@ -1411,7 +1418,13 @@ class MultiLineROI(QtGui.QGraphicsObject):
def roiChangeFinishedEvent(self):
self.sigRegionChangeFinished.emit(self)
def getHandlePositions(self):
"""Return the positions of all handles in local coordinates."""
pos = [self.mapFromScene(self.lines[0].getHandles()[0].scenePos())]
for l in self.lines:
pos.append(self.mapFromScene(l.getHandles()[1].scenePos()))
return pos
def getArrayRegion(self, arr, img=None, axes=(0,1)):
rgns = []
for l in self.lines:
@ -1432,6 +1445,59 @@ class MultiLineROI(QtGui.QGraphicsObject):
return np.concatenate(rgns, axis=axes[0])
def addSegment(self, pos=(0,0), scaleHandle=False, connectTo=None):
"""
Add a new segment to the ROI connecting from the previous endpoint to *pos*.
(pos is specified in the parent coordinate system of the MultiRectROI)
"""
## by default, connect to the previous endpoint
if connectTo is None:
connectTo = self.lines[-1].getHandles()[1]
## create new ROI
newRoi = ROI((0,0), [1, 5], parent=self, pen=self.pen, **self.roiArgs)
self.lines.append(newRoi)
## Add first SR handle
if isinstance(connectTo, Handle):
self.lines[-1].addScaleRotateHandle([0, 0.5], [1, 0.5], item=connectTo)
newRoi.movePoint(connectTo, connectTo.scenePos(), coords='scene')
else:
h = self.lines[-1].addScaleRotateHandle([0, 0.5], [1, 0.5])
newRoi.movePoint(h, connectTo, coords='scene')
## add second SR handle
h = self.lines[-1].addScaleRotateHandle([1, 0.5], [0, 0.5])
newRoi.movePoint(h, pos)
## optionally add scale handle (this MUST come after the two SR handles)
if scaleHandle:
newRoi.addScaleHandle([0.5, 1], [0.5, 0.5])
newRoi.translatable = False
newRoi.sigRegionChanged.connect(self.roiChangedEvent)
newRoi.sigRegionChangeStarted.connect(self.roiChangeStartedEvent)
newRoi.sigRegionChangeFinished.connect(self.roiChangeFinishedEvent)
self.sigRegionChanged.emit(self)
def removeSegment(self, index=-1):
"""Remove a segment from the ROI."""
roi = self.lines[index]
self.lines.pop(index)
self.scene().removeItem(roi)
roi.sigRegionChanged.disconnect(self.roiChangedEvent)
roi.sigRegionChangeStarted.disconnect(self.roiChangeStartedEvent)
roi.sigRegionChangeFinished.disconnect(self.roiChangeFinishedEvent)
self.sigRegionChanged.emit(self)
class MultiLineROI(MultiRectROI):
def __init__(self, *args, **kwds):
MultiRectROI.__init__(self, *args, **kwds)
print("Warning: MultiLineROI has been renamed to MultiRectROI. (and MultiLineROI may be redefined in the future)")
class EllipseROI(ROI):
def __init__(self, pos, size, **args):
@ -1475,6 +1541,8 @@ class CircleROI(EllipseROI):
self.addScaleHandle([0.5*2.**-0.5 + 0.5, 0.5*2.**-0.5 + 0.5], [0.5, 0.5])
class PolygonROI(ROI):
## deprecated. Use PloyLineROI instead.
def __init__(self, positions, pos=None, **args):
if pos is None:
pos = [0,0]
@ -1483,16 +1551,17 @@ class PolygonROI(ROI):
for p in positions:
self.addFreeHandle(p)
self.setZValue(1000)
print("Warning: PolygonROI is deprecated. Use PolyLineROI instead.")
def listPoints(self):
return [p['item'].pos() for p in self.handles]
def movePoint(self, *args, **kargs):
ROI.movePoint(self, *args, **kargs)
self.prepareGeometryChange()
for h in self.handles:
h['pos'] = h['item'].pos()
#def movePoint(self, *args, **kargs):
#ROI.movePoint(self, *args, **kargs)
#self.prepareGeometryChange()
#for h in self.handles:
#h['pos'] = h['item'].pos()
def paint(self, p, *args):
p.setRenderHint(QtGui.QPainter.Antialiasing)
@ -1687,103 +1756,33 @@ class LineSegmentROI(ROI):
ROI.__init__(self, pos, [1,1], **args)
#ROI.__init__(self, positions[0])
if len(positions) > 2:
raise Exception("LineSegmentROI can only be defined by 2 positions. This is an API change.")
raise Exception("LineSegmentROI must be defined by exactly 2 positions. For more points, use PolyLineROI.")
for i, p in enumerate(positions):
self.addFreeHandle(p, item=handles[i])
#self.setZValue(1000)
#self.parentROI = None
#self.hasParentROI = False
#self.setAcceptsHandles(acceptsHandles)
#def setParentROI(self, parent):
#self.parentROI = parent
#if parent != None:
#self.hasParentROI = True
#else:
#self.hasParentROI = False
#def setAcceptsHandles(self, b):
#if b:
#self.setAcceptedMouseButtons(QtCore.Qt.LeftButton)
#else:
#self.setAcceptedMouseButtons(QtCore.Qt.NoButton)
#def close(self):
##for h in self.handles:
##if len(h['item'].roi) == 1:
##h['item'].scene().removeItem(h['item'])
##elif h['item'].parentItem() == self:
##h['item'].setParentItem(self.parentItem())
#self.scene().removeItem(self)
#def handleRemoved(self, handle):
#self.parentROI.handleRemoved(self, handle)
#def hoverEvent(self, ev):
#if (self.translatable or self.acceptsHandles) and (not ev.isExit()) and ev.acceptDrags(QtCore.Qt.LeftButton):
##print " setHover: True"
#self.setMouseHover(True)
#self.sigHoverEvent.emit(self)
#else:
##print " setHover: False"
#self.setMouseHover(False)
#def mouseClickEvent(self, ev):
#ROI.mouseClickEvent(self, ev) ## only checks for Right-clicks (for now anyway)
#if ev.button() == QtCore.Qt.LeftButton:
#if self.acceptsHandles:
#ev.accept()
#self.newHandleRequested(ev.pos()) ## ev.pos is the position in this item's coordinates
#else:
#ev.ignore()
#def newHandleRequested(self, evPos):
#print "newHandleRequested"
#if evPos - self.handles[0].pos() == Point(0.,0.) or evPos-handles[1].pos() == Point(0.,0.):
# return
#self.parentROI.newHandleRequested(self, self.mapToParent(evPos)) ## so now evPos should be passed in in the parents coordinate system
def listPoints(self):
return [p['item'].pos() for p in self.handles]
#def movePoint(self, *args, **kargs):
#ROI.movePoint(self, *args, **kargs)
#self.prepareGeometryChange()
#for h in self.handles:
#h['pos'] = h['item'].pos()
def paint(self, p, *args):
p.setRenderHint(QtGui.QPainter.Antialiasing)
p.setPen(self.currentPen)
h1 = self.handles[0]['item'].pos()
h2 = self.handles[1]['item'].pos()
p.drawLine(h1, h2)
#p.setPen(fn.mkPen('w'))
#p.drawPath(self.shape())
#for i in range(len(self.handles)-1):
#h1 = self.handles[i]['item'].pos()
#h2 = self.handles[i+1]['item'].pos()
#p.drawLine(h1, h2)
def boundingRect(self):
return self.shape().boundingRect()
#r = QtCore.QRectF()
#for h in self.handles:
#r |= self.mapFromItem(h['item'], h['item'].boundingRect()).boundingRect() ## |= gives the union of the two QRectFs
#return r
def shape(self):
p = QtGui.QPainterPath()
#pw, ph = self.pixelSize()
#pHyp = 4 * (pw**2 + ph**2)**0.5
h1 = self.handles[0]['item'].pos()
h2 = self.handles[1]['item'].pos()
dh = h2-h1
if dh.length() == 0:
return p
pxv = self.pixelVectors(h2-h1)[1]
if pxv is None:
@ -1799,14 +1798,6 @@ class LineSegmentROI(ROI):
return p
#def stateCopy(self):
#sc = {}
#sc['pos'] = Point(self.state['pos'])
#sc['size'] = Point(self.state['size'])
#sc['angle'] = self.state['angle']
##sc['handles'] = self.handles
#return sc
def getArrayRegion(self, data, img, axes=(0,1)):
"""
Use the position of this ROI relative to an imageItem to pull a slice from an array.
@ -1849,11 +1840,11 @@ class SpiralROI(ROI):
return QtCore.QRectF(-r*1.1, -r*1.1, 2.2*r, 2.2*r)
#return self.bounds
def movePoint(self, *args, **kargs):
ROI.movePoint(self, *args, **kargs)
self.prepareGeometryChange()
for h in self.handles:
h['pos'] = h['item'].pos()/self.state['size'][0]
#def movePoint(self, *args, **kargs):
#ROI.movePoint(self, *args, **kargs)
#self.prepareGeometryChange()
#for h in self.handles:
#h['pos'] = h['item'].pos()/self.state['size'][0]
def stateChanged(self):
ROI.stateChanged(self)

View File

@ -109,6 +109,7 @@ class ViewBox(GraphicsWidget):
'background': None,
}
self._updatingRange = False ## Used to break recursive loops. See updateAutoRange.
self.setFlag(self.ItemClipsChildrenToShape)
@ -340,6 +341,7 @@ class ViewBox(GraphicsWidget):
============= =====================================================================
"""
changes = {}
if rect is not None:
@ -471,6 +473,7 @@ class ViewBox(GraphicsWidget):
#if not enable:
#import traceback
#traceback.print_stack()
if enable is True:
enable = 1.0
@ -520,74 +523,84 @@ class ViewBox(GraphicsWidget):
self.updateAutoRange()
def updateAutoRange(self):
targetRect = self.viewRange()
if not any(self.state['autoRange']):
## Break recursive loops when auto-ranging.
## This is needed because some items change their size in response
## to a view change.
if self._updatingRange:
return
fractionVisible = self.state['autoRange'][:]
for i in [0,1]:
if type(fractionVisible[i]) is bool:
fractionVisible[i] = 1.0
childRange = None
order = [0,1]
if self.state['autoVisibleOnly'][0] is True:
order = [1,0]
args = {}
for ax in order:
if self.state['autoRange'][ax] is False:
continue
if self.state['autoVisibleOnly'][ax]:
oRange = [None, None]
oRange[ax] = targetRect[1-ax]
childRange = self.childrenBounds(frac=fractionVisible, orthoRange=oRange)
self._updatingRange = True
try:
targetRect = self.viewRange()
if not any(self.state['autoRange']):
return
else:
if childRange is None:
childRange = self.childrenBounds(frac=fractionVisible)
## Make corrections to range
xr = childRange[ax]
if xr is not None:
if self.state['autoPan'][ax]:
x = sum(xr) * 0.5
#x = childRect.center().x()
w2 = (targetRect[ax][1]-targetRect[ax][0]) / 2.
#childRect.setLeft(x-w2)
#childRect.setRight(x+w2)
childRange[ax] = [x-w2, x+w2]
else:
#wp = childRect.width() * 0.02
wp = (xr[1] - xr[0]) * 0.02
#childRect = childRect.adjusted(-wp, 0, wp, 0)
childRange[ax][0] -= wp
childRange[ax][1] += wp
#targetRect[ax][0] = childRect.left()
#targetRect[ax][1] = childRect.right()
targetRect[ax] = childRange[ax]
args['xRange' if ax == 0 else 'yRange'] = targetRect[ax]
#else:
### Make corrections to Y range
#if self.state['autoPan'][1]:
#y = childRect.center().y()
#h2 = (targetRect[1][1]-targetRect[1][0]) / 2.
#childRect.setTop(y-h2)
#childRect.setBottom(y+h2)
#else:
#hp = childRect.height() * 0.02
#childRect = childRect.adjusted(0, -hp, 0, hp)
fractionVisible = self.state['autoRange'][:]
for i in [0,1]:
if type(fractionVisible[i]) is bool:
fractionVisible[i] = 1.0
childRange = None
order = [0,1]
if self.state['autoVisibleOnly'][0] is True:
order = [1,0]
args = {}
for ax in order:
if self.state['autoRange'][ax] is False:
continue
if self.state['autoVisibleOnly'][ax]:
oRange = [None, None]
oRange[ax] = targetRect[1-ax]
childRange = self.childrenBounds(frac=fractionVisible, orthoRange=oRange)
#targetRect[1][0] = childRect.top()
#targetRect[1][1] = childRect.bottom()
#args['yRange'] = targetRect[1]
if len(args) == 0:
return
args['padding'] = 0
args['disableAutoRange'] = False
#self.setRange(xRange=targetRect[0], yRange=targetRect[1], padding=0, disableAutoRange=False)
self.setRange(**args)
else:
if childRange is None:
childRange = self.childrenBounds(frac=fractionVisible)
## Make corrections to range
xr = childRange[ax]
if xr is not None:
if self.state['autoPan'][ax]:
x = sum(xr) * 0.5
#x = childRect.center().x()
w2 = (targetRect[ax][1]-targetRect[ax][0]) / 2.
#childRect.setLeft(x-w2)
#childRect.setRight(x+w2)
childRange[ax] = [x-w2, x+w2]
else:
#wp = childRect.width() * 0.02
wp = (xr[1] - xr[0]) * 0.02
#childRect = childRect.adjusted(-wp, 0, wp, 0)
childRange[ax][0] -= wp
childRange[ax][1] += wp
#targetRect[ax][0] = childRect.left()
#targetRect[ax][1] = childRect.right()
targetRect[ax] = childRange[ax]
args['xRange' if ax == 0 else 'yRange'] = targetRect[ax]
#else:
### Make corrections to Y range
#if self.state['autoPan'][1]:
#y = childRect.center().y()
#h2 = (targetRect[1][1]-targetRect[1][0]) / 2.
#childRect.setTop(y-h2)
#childRect.setBottom(y+h2)
#else:
#hp = childRect.height() * 0.02
#childRect = childRect.adjusted(0, -hp, 0, hp)
#targetRect[1][0] = childRect.top()
#targetRect[1][1] = childRect.bottom()
#args['yRange'] = targetRect[1]
if len(args) == 0:
return
args['padding'] = 0
args['disableAutoRange'] = False
#self.setRange(xRange=targetRect[0], yRange=targetRect[1], padding=0, disableAutoRange=False)
self.setRange(**args)
finally:
self._updatingRange = False
def setXLink(self, view):
"""Link this view's X axis to another view. (see LinkView)"""

View File

@ -1,4 +1,5 @@
from pyqtgraph.Qt import QtGui, QtCore
from pyqtgraph import Transform3D
class GLGraphicsItem(QtCore.QObject):
def __init__(self, parentItem=None):
@ -6,7 +7,7 @@ class GLGraphicsItem(QtCore.QObject):
self.__parent = None
self.__view = None
self.__children = set()
self.__transform = QtGui.QMatrix4x4()
self.__transform = Transform3D()
self.__visible = True
self.setParentItem(parentItem)
self.setDepthValue(0)
@ -50,7 +51,7 @@ class GLGraphicsItem(QtCore.QObject):
return self.__depthValue
def setTransform(self, tr):
self.__transform = tr
self.__transform = Transform3D(tr)
self.update()
def resetTransform(self):
@ -73,12 +74,22 @@ class GLGraphicsItem(QtCore.QObject):
def transform(self):
return self.__transform
def viewTransform(self):
tr = self.__transform
p = self
while True:
p = p.parentItem()
if p is None:
break
tr = p.transform() * tr
return Transform3D(tr)
def translate(self, dx, dy, dz, local=False):
"""
Translate the object by (*dx*, *dy*, *dz*) in its parent's coordinate system.
If *local* is True, then translation takes place in local coordinates.
"""
tr = QtGui.QMatrix4x4()
tr = Transform3D()
tr.translate(dx, dy, dz)
self.applyTransform(tr, local=local)
@ -88,7 +99,7 @@ class GLGraphicsItem(QtCore.QObject):
*angle* is in degrees.
"""
tr = QtGui.QMatrix4x4()
tr = Transform3D()
tr.rotate(angle, x, y, z)
self.applyTransform(tr, local=local)
@ -97,7 +108,7 @@ class GLGraphicsItem(QtCore.QObject):
Scale the object by (*dx*, *dy*, *dz*) in its local coordinate system.
If *local* is False, then scale takes place in the parent's coordinates.
"""
tr = QtGui.QMatrix4x4()
tr = Transform3D()
tr.scale(x, y, z)
self.applyTransform(tr, local=local)
@ -138,8 +149,29 @@ class GLGraphicsItem(QtCore.QObject):
return
v.updateGL()
def mapToParent(self, point):
tr = self.transform()
if tr is None:
return point
return tr.map(point)
def mapFromParent(self, point):
tr = self.transform()
if tr is None:
return point
return tr.inverted()[0].map(point)
return tr.inverted()[0].map(point)
def mapToView(self, point):
tr = self.viewTransform()
if tr is None:
return point
return tr.map(point)
def mapFromView(self, point):
tr = self.viewTransform()
if tr is None:
return point
return tr.inverted()[0].map(point)

View File

@ -1,8 +1,8 @@
from pyqtgraph.Qt import QtCore, QtGui, QtOpenGL
from OpenGL.GL import *
import numpy as np
Vector = QtGui.QVector3D
from pyqtgraph import Vector
##Vector = QtGui.QVector3D
class GLViewWidget(QtOpenGL.QGLWidget):
"""
@ -181,10 +181,14 @@ class GLViewWidget(QtOpenGL.QGLWidget):
def pixelSize(self, pos):
"""
Return the approximate size of a screen pixel at the location pos
Pos may be a Vector or an (N,3) array of locations
"""
cam = self.cameraPosition()
dist = (pos-cam).length()
if isinstance(pos, np.ndarray) and pos.ndim == 2:
cam = np.array(cam).reshape(1,3)
dist = ((pos-cam)**2).sum(axis=1)**0.5
else:
dist = (pos-cam).length()
xDist = dist * 2. * np.tan(0.5 * self.opts['fov'] * np.pi / 180.)
return xDist / self.width()

View File

@ -28,7 +28,7 @@ class GLMeshItem(GLGraphicsItem):
GLGraphicsItem.__init__(self)
def initializeGL(self):
self.shader = shaders.getShader('balloon')
self.shader = shaders.getShaderProgram('balloon')
l = glGenLists(1)
self.triList = l
@ -72,7 +72,9 @@ class GLMeshItem(GLGraphicsItem):
def paint(self):
shaders.glUseProgram(self.shader)
glCallList(self.triList)
shaders.glUseProgram(0)
with self.shader:
glCallList(self.triList)
#shaders.glUseProgram(self.shader)
#glCallList(self.triList)
#shaders.glUseProgram(0)
#glCallList(self.meshList)

View File

@ -1,5 +1,7 @@
from OpenGL.GL import *
from OpenGL.arrays import vbo
from .. GLGraphicsItem import GLGraphicsItem
from .. import shaders
from pyqtgraph import QtGui
import numpy as np
@ -14,6 +16,7 @@ class GLScatterPlotItem(GLGraphicsItem):
self.size = 10
self.color = [1.0,1.0,1.0,0.5]
self.pxMode = True
#self.vbo = {} ## VBO does not appear to improve performance very much.
self.setData(**kwds)
def setData(self, **kwds):
@ -39,13 +42,16 @@ class GLScatterPlotItem(GLGraphicsItem):
for k in kwds.keys():
if k not in args:
raise Exception('Invalid keyword argument: %s (allowed arguments are %s)' % (k, str(args)))
self.pos = kwds.get('pos', self.pos)
self.color = kwds.get('color', self.color)
self.size = kwds.get('size', self.size)
args.remove('pxMode')
for arg in args:
if arg in kwds:
setattr(self, arg, kwds[arg])
#self.vbo.pop(arg, None)
self.pxMode = kwds.get('pxMode', self.pxMode)
self.update()
def initializeGL(self):
## Generate texture for rendering points
@ -65,75 +71,108 @@ 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)
def paint(self):
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
self.shader = shaders.getShaderProgram('point_sprite')
#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 )
glEnable( GL_POINT_SMOOTH )
glDisable( GL_DEPTH_TEST )
#glEnable( GL_POINT_SMOOTH )
glHint(GL_POINT_SMOOTH_HINT, GL_NICEST)
#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()
glEnable(GL_POINT_SPRITE)
glActiveTexture(GL_TEXTURE0)
glEnable( GL_TEXTURE_2D )
glBindTexture(GL_TEXTURE_2D, self.pointTexture)
glTexEnvi(GL_POINT_SPRITE, GL_COORD_REPLACE, GL_TRUE)
#glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE) ## use texture color exactly
glTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ) ## texture modulates current color
#glTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ) ## texture modulates current color
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)
glEnable(GL_PROGRAM_POINT_SIZE)
if self.pxMode:
glVertexPointerf(self.pos)
if isinstance(self.color, np.ndarray):
glColorPointerf(self.color)
else:
if isinstance(self.color, QtGui.QColor):
glColor4f(*fn.glColor(self.color))
else:
glColor4f(*self.color)
if isinstance(self.size, np.ndarray):
raise Exception('Array size not yet supported in pxMode (hopefully soon)')
glPointSize(self.size)
with self.shader:
#glUniform1i(self.shader.uniform('texture'), 0) ## inform the shader which texture to use
glEnableClientState(GL_VERTEX_ARRAY)
glEnableClientState(GL_COLOR_ARRAY)
glDrawArrays(GL_POINTS, 0, len(self.pos))
else:
try:
glVertexPointerf(self.pos)
for i in range(len(self.pos)):
pos = self.pos[i]
if isinstance(self.color, np.ndarray):
color = self.color[i]
glEnableClientState(GL_COLOR_ARRAY)
glColorPointerf(self.color)
else:
color = self.color
if isinstance(self.color, QtGui.QColor):
color = fn.glColor(self.color)
if isinstance(self.size, np.ndarray):
size = self.size[i]
else:
size = self.size
pxSize = self.view().pixelSize(QtGui.QVector3D(*pos))
if isinstance(self.color, QtGui.QColor):
glColor4f(*fn.glColor(self.color))
else:
glColor4f(*self.color)
glPointSize(size / pxSize)
glBegin( GL_POINTS )
glColor4f(*color) # x is blue
#glNormal3f(size, 0, 0)
glVertex3f(*pos)
glEnd()
if not self.pxMode or isinstance(self.size, np.ndarray):
glEnableClientState(GL_NORMAL_ARRAY)
norm = np.empty(self.pos.shape)
if self.pxMode:
norm[:,0] = self.size
else:
gpos = self.mapToView(self.pos.transpose()).transpose()
pxSize = self.view().pixelSize(gpos)
norm[:,0] = self.size / pxSize
glNormalPointerf(norm)
else:
glNormal3f(self.size,0,0)
#glPointSize(self.size)
glDrawArrays(GL_POINTS, 0, len(self.pos))
finally:
glDisableClientState(GL_NORMAL_ARRAY)
glDisableClientState(GL_VERTEX_ARRAY)
glDisableClientState(GL_COLOR_ARRAY)
#posVBO.unbind()
#for i in range(len(self.pos)):
#pos = self.pos[i]
#if isinstance(self.color, np.ndarray):
#color = self.color[i]
#else:
#color = self.color
#if isinstance(self.color, QtGui.QColor):
#color = fn.glColor(self.color)
#if isinstance(self.size, np.ndarray):
#size = self.size[i]
#else:
#size = self.size
#pxSize = self.view().pixelSize(QtGui.QVector3D(*pos))
#glPointSize(size / pxSize)
#glBegin( GL_POINTS )
#glColor4f(*color) # x is blue
##glNormal3f(size, 0, 0)
#glVertex3f(*pos)
#glEnd()

View File

@ -3,39 +3,107 @@ from OpenGL.GL import shaders
## 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
VertexShader("""
varying vec3 normal;
void main() {
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();
}
"""),
FragmentShader("""
varying vec3 normal;
void main() {
vec4 color = gl_Color;
color.w = min(color.w + 2.0 * color.w * pow(normal.x*normal.x + normal.y*normal.y, 5.0), 1.0);
gl_FragColor = color;
}
""")
]),
ShaderProgram('point_sprite', [ ## 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
## http://stackoverflow.com/questions/3497068/textured-points-in-opengl-es-2-0
##
##
VertexShader("""
void main() {
gl_FrontColor=gl_Color;
gl_PointSize = gl_Normal.x;
gl_Position = ftransform();
}
"""),
#FragmentShader("""
##version 120
#uniform sampler2D texture;
#void main ( )
#{
#gl_FragColor = texture2D(texture, gl_PointCoord) * gl_Color;
#}
#""")
]),
]
Shaders = {
'balloon': ( ## increases fragment alpha as the normal turns orthogonal to the view
"""
varying vec3 normal;
void main() {
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();
}
""",
"""
varying vec3 normal;
void main() {
vec4 color = gl_Color;
color.w = min(color.w + 2.0 * color.w * pow(normal.x*normal.x + normal.y*normal.y, 5.0), 1.0);
gl_FragColor = color;
}
"""
),
}
CompiledShaders = {}
CompiledShaderPrograms = {}
def getShader(name):
global Shaders, CompiledShaders
def getShaderProgram(name):
return ShaderProgram.names[name]
class VertexShader:
def __init__(self, code):
self.code = code
self.compiled = None
def shader(self):
if self.compiled is None:
self.compiled = shaders.compileShader(self.code, GL_VERTEX_SHADER)
return self.compiled
class FragmentShader:
def __init__(self, code):
self.code = code
self.compiled = None
def shader(self):
if self.compiled is None:
self.compiled = shaders.compileShader(self.code, GL_FRAGMENT_SHADER)
return self.compiled
class ShaderProgram:
names = {}
if name not in CompiledShaders:
vshader, fshader = Shaders[name]
vcomp = shaders.compileShader(vshader, GL_VERTEX_SHADER)
fcomp = shaders.compileShader(fshader, GL_FRAGMENT_SHADER)
prog = shaders.compileProgram(vcomp, fcomp)
CompiledShaders[name] = prog, vcomp, fcomp
return CompiledShaders[name][0]
def __init__(self, name, shaders):
self.name = name
ShaderProgram.names[name] = self
self.shaders = shaders
self.prog = 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
return self.prog
def __enter__(self):
glUseProgram(self.program())
def __exit__(self, *args):
glUseProgram(0)
def uniform(self, name):
"""Return the location integer for a uniform variable in this program"""
return glGetUniformLocation(self.program(), name)
initShaders()

View File

@ -516,6 +516,7 @@ class ListParameter(Parameter):
if opts.get('limits', None) is None:
opts['limits'] = []
Parameter.__init__(self, **opts)
self.setLimits(opts['limits'])
def setLimits(self, limits):
self.forward, self.reverse = self.mapping(limits)

View File

@ -11,6 +11,7 @@ __all__ = ['TickSlider', 'GradientWidget', 'BlackWhiteSlider']
class GradientWidget(GraphicsView):
sigGradientChanged = QtCore.Signal(object)
sigGradientChangeFinished = QtCore.Signal(object)
def __init__(self, parent=None, orientation='bottom', *args, **kargs):
GraphicsView.__init__(self, parent, useOpenGL=False, background=None)
@ -18,6 +19,7 @@ class GradientWidget(GraphicsView):
kargs['tickPen'] = 'k'
self.item = GradientEditorItem(*args, **kargs)
self.item.sigGradientChanged.connect(self.sigGradientChanged)
self.item.sigGradientChangeFinished.connect(self.sigGradientChangeFinished)
self.setCentralItem(self.item)
self.setOrientation(orientation)
self.setCacheMode(self.CacheNone)

View File

@ -201,8 +201,10 @@ class TreeWidget(QtGui.QTreeWidget):
for item in items:
self.prepareMove(item)
QtGui.QTreeWidget.clear(self)
for item in items:
self.informTreeWidgetChange(item)
## Why do we want to do this? It causes RuntimeErrors.
#for item in items:
#self.informTreeWidgetChange(item)
class TreeWidgetItem(QtGui.QTreeWidgetItem):