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 numpy as np
import scipy.linalg 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 """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. This transform has no shear; angles are always preserved.
""" """
def __init__(self, init=None): def __init__(self, init=None):
QtGui.QMatrix4x4.__init__(self) pg.Transform3D.__init__(self)
self.reset() self.reset()
if init is None: if init is None:
return return
@ -190,11 +190,11 @@ class SRTTransform3D(QtGui.QMatrix4x4):
self.update() self.update()
def update(self): 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. ## modifications to the transform are multiplied on the right, so we need to reverse order here.
QtGui.QMatrix4x4.translate(self, *self._state['pos']) pg.Transform3D.translate(self, *self._state['pos'])
QtGui.QMatrix4x4.rotate(self, self._state['angle'], *self._state['axis']) pg.Transform3D.rotate(self, self._state['angle'], *self._state['axis'])
QtGui.QMatrix4x4.scale(self, *self._state['scale']) pg.Transform3D.scale(self, *self._state['scale'])
def __repr__(self): def __repr__(self):
return str(self.saveState()) 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

View File

@ -57,3 +57,8 @@ class Vector(QtGui.QVector3D):
else: else:
raise IndexError("Point has no index %s" % str(i)) 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 .Point import Point
from .Vector import Vector from .Vector import Vector
from .SRTTransform import SRTTransform from .SRTTransform import SRTTransform
from .Transform3D import Transform3D
from .SRTTransform3D import SRTTransform3D from .SRTTransform3D import SRTTransform3D
from .functions import * from .functions import *
from .graphicsWindows import * from .graphicsWindows import *

View File

@ -208,6 +208,10 @@ class DockArea(Container, QtGui.QWidget, DockDrop):
def restoreState(self, state): def restoreState(self, state):
""" """
Restore Dock configuration as generated by saveState. 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 ## 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 typ, contents, state = state
pfx = " " * depth pfx = " " * depth
if typ == 'dock': if typ == 'dock':
try:
obj = docks[contents] obj = docks[contents]
del docks[contents] del docks[contents]
except KeyError:
raise Exception('Cannot restore dock state; no dock with name "%s"' % contents)
else: else:
obj = self.makeContainer(typ) obj = self.makeContainer(typ)
@ -270,7 +277,7 @@ class DockArea(Container, QtGui.QWidget, DockDrop):
if isinstance(obj, Dock): if isinstance(obj, Dock):
d[obj.name()] = obj d[obj.name()] = obj
else: elif obj is not None:
c.append(obj) c.append(obj)
for i in range(obj.count()): for i in range(obj.count()):
o2 = obj.widget(i) o2 = obj.widget(i)

View File

@ -15,48 +15,91 @@ w.show()
g = gl.GLGridItem() g = gl.GLGridItem()
w.addItem(g) 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 ## First example is a set of points with pxMode=False
#for i in range(3,53): ## These demonstrate the ability to have points with real size down to a very small scale
#pos[i] = (0,0,z) ##
#size[i] = 2./d pos = np.empty((53, 3))
#color[i] = (0.0, 1.0, 0.0, 0.5) size = np.empty((53))
#z *= 0.5 color = np.empty((53, 4))
#d *= 2.0 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)
#sp = gl.GLScatterPlotItem(pos=pos, sizes=size, colors=color, pxMode=False) 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
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)) color = np.ones((pos.shape[0], 4))
d = (pos**2).sum(axis=1)**0.5 d2 = (pos**2).sum(axis=1)**0.5
color[:,3] = np.clip(-np.cos(d*2) * 0.2, 0, 1) size = np.random.random(size=pos.shape[0])*10
sp = gl.GLScatterPlotItem(pos=pos, color=color, size=5) sp2 = gl.GLScatterPlotItem(pos=pos, color=(1,1,1,1), size=size)
phase = 0. 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(): def update():
global phase, color, sp, d ## update volume colors
s = -np.cos(d*2+phase) global phase, sp2, d2
color[:,3] = np.clip(s * 0.2, 0, 1) 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[:,0] = np.clip(s * 3.0, 0, 1)
color[:,1] = np.clip(s * 1.0, 0, 1) color[:,1] = np.clip(s * 1.0, 0, 1)
color[:,2] = np.clip(s ** 3, 0, 1) color[:,2] = np.clip(s ** 3, 0, 1)
sp2.setData(color=color)
sp.setData(color=color)
phase -= 0.1 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 = QtCore.QTimer()
t.timeout.connect(update) t.timeout.connect(update)
t.start(50) t.start(50)
w.addItem(sp)
## Start Qt event loop unless running in interactive mode. ## Start Qt event loop unless running in interactive mode.
if sys.flags.interactive != 1: if sys.flags.interactive != 1:

View File

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

View File

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

View File

@ -434,3 +434,17 @@ class GraphicsItem(object):
Called whenever the transformation matrix of the view has changed. 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 _qtBaseClass = QtGui.QGraphicsObject
def __init__(self, *args): def __init__(self, *args):
QtGui.QGraphicsObject.__init__(self, *args) QtGui.QGraphicsObject.__init__(self, *args)
self.setFlag(self.ItemSendsGeometryChanges)
GraphicsItem.__init__(self) GraphicsItem.__init__(self)
def itemChange(self, change, value): def itemChange(self, change, value):
ret = QtGui.QGraphicsObject.itemChange(self, change, value) ret = QtGui.QGraphicsObject.itemChange(self, change, value)
if change in [self.ItemParentHasChanged, self.ItemSceneHasChanged]: if change in [self.ItemParentHasChanged, self.ItemSceneHasChanged]:
self._updateView() self._updateView()
if change in [self.ItemPositionHasChanged, self.ItemTransformHasChanged]:
self.informViewBoundsChanged()
return ret return ret

View File

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

View File

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

View File

@ -28,7 +28,7 @@ from .UIGraphicsItem import UIGraphicsItem
__all__ = [ __all__ = [
'ROI', 'ROI',
'TestROI', 'RectROI', 'EllipseROI', 'CircleROI', 'PolygonROI', 'TestROI', 'RectROI', 'EllipseROI', 'CircleROI', 'PolygonROI',
'LineROI', 'MultiLineROI', 'LineSegmentROI', 'PolyLineROI', 'SpiralROI', 'LineROI', 'MultiLineROI', 'MultiRectROI', 'LineSegmentROI', 'PolyLineROI', 'SpiralROI',
] ]
@ -370,6 +370,8 @@ class ROI(GraphicsObject):
else: else:
return (self.handles[index]['name'], self.handles[index]['item'].scenePos()) 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): def mapSceneToParent(self, pt):
return self.mapToParent(self.mapFromScene(pt)) return self.mapToParent(self.mapFromScene(pt))
@ -538,19 +540,27 @@ class ROI(GraphicsObject):
return True 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. ## called by Handles when they are moved.
## pos is the new position of the handle in scene coords, as requested by the handle. ## pos is the new position of the handle in scene coords, as requested by the handle.
newState = self.stateCopy() newState = self.stateCopy()
index = self.indexOfHandle(handle) index = self.indexOfHandle(handle)
h = self.handles[index] h = self.handles[index]
p0 = self.mapToScene(h['pos'] * self.state['size']) p0 = self.mapToParent(h['pos'] * self.state['size'])
p1 = Point(pos) p1 = Point(pos)
## transform p0 and p1 into parent's coordinates (same as scene coords if there is no parent). I forget why. if coords == 'parent':
p0 = self.mapSceneToParent(p0) pass
elif coords == 'scene':
p1 = self.mapSceneToParent(p1) 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)
## Handles with a 'center' need to know their local position relative to the center point (lp0, lp1) ## Handles with a 'center' need to know their local position relative to the center point (lp0, lp1)
if 'center' in h: if 'center' in h:
@ -566,8 +576,8 @@ class ROI(GraphicsObject):
self.translate(p1-p0, snap=snap, update=False) self.translate(p1-p0, snap=snap, update=False)
elif h['type'] == 'f': elif h['type'] == 'f':
newPos = self.mapFromScene(pos) newPos = self.mapFromParent(p1)
h['item'].setPos(self.mapFromScene(pos)) h['item'].setPos(newPos)
h['pos'] = newPos h['pos'] = newPos
self.freeHandleMoved = True self.freeHandleMoved = True
#self.sigRegionChanged.emit(self) ## should be taken care of by call to stateChanged() #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) #print "point moved; inform %d ROIs" % len(self.roi)
# A handle can be used by multiple ROIs; tell each to update its handle position # A handle can be used by multiple ROIs; tell each to update its handle position
for r in self.rois: for r in self.rois:
r.movePoint(self, pos, modifiers, finish=finish) r.movePoint(self, pos, modifiers, finish=finish, coords='scene')
def buildPath(self): def buildPath(self):
size = self.radius size = self.radius
@ -1264,9 +1274,9 @@ class Handle(UIGraphicsItem):
if self._shape is None: if self._shape is None:
s = self.generateShape() s = self.generateShape()
if s is None: if s is None:
return self.shape return self.path
self._shape = s self._shape = s
self.prepareGeometryChange() self.prepareGeometryChange() ## beware--this can cause the view to adjust, which would immediately invalidate the shape.
return self._shape return self._shape
def boundingRect(self): def boundingRect(self):
@ -1358,8 +1368,15 @@ class LineROI(ROI):
self.addScaleHandle([0.5, 1], [0.5, 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) sigRegionChangeFinished = QtCore.Signal(object)
sigRegionChangeStarted = QtCore.Signal(object) sigRegionChangeStarted = QtCore.Signal(object)
sigRegionChanged = QtCore.Signal(object) sigRegionChanged = QtCore.Signal(object)
@ -1368,27 +1385,17 @@ class MultiLineROI(QtGui.QGraphicsObject):
QtGui.QGraphicsObject.__init__(self) QtGui.QGraphicsObject.__init__(self)
self.pen = pen self.pen = pen
self.roiArgs = args self.roiArgs = args
self.lines = []
if len(points) < 2: if len(points) < 2:
raise Exception("Must start with at least 2 points") 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: ## create first segment
l.translatable = False self.addSegment(points[1], connectTo=points[0], scaleHandle=True)
l.sigRegionChanged.connect(self.roiChangedEvent)
l.sigRegionChangeStarted.connect(self.roiChangeStartedEvent) ## create remaining segments
l.sigRegionChangeFinished.connect(self.roiChangeFinishedEvent) for p in points[2:]:
self.addSegment(p)
def paint(self, *args): def paint(self, *args):
pass pass
@ -1411,6 +1418,12 @@ class MultiLineROI(QtGui.QGraphicsObject):
def roiChangeFinishedEvent(self): def roiChangeFinishedEvent(self):
self.sigRegionChangeFinished.emit(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)): def getArrayRegion(self, arr, img=None, axes=(0,1)):
rgns = [] rgns = []
@ -1432,6 +1445,59 @@ class MultiLineROI(QtGui.QGraphicsObject):
return np.concatenate(rgns, axis=axes[0]) 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): class EllipseROI(ROI):
def __init__(self, pos, size, **args): 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]) self.addScaleHandle([0.5*2.**-0.5 + 0.5, 0.5*2.**-0.5 + 0.5], [0.5, 0.5])
class PolygonROI(ROI): class PolygonROI(ROI):
## deprecated. Use PloyLineROI instead.
def __init__(self, positions, pos=None, **args): def __init__(self, positions, pos=None, **args):
if pos is None: if pos is None:
pos = [0,0] pos = [0,0]
@ -1483,16 +1551,17 @@ class PolygonROI(ROI):
for p in positions: for p in positions:
self.addFreeHandle(p) self.addFreeHandle(p)
self.setZValue(1000) self.setZValue(1000)
print("Warning: PolygonROI is deprecated. Use PolyLineROI instead.")
def listPoints(self): def listPoints(self):
return [p['item'].pos() for p in self.handles] return [p['item'].pos() for p in self.handles]
def movePoint(self, *args, **kargs): #def movePoint(self, *args, **kargs):
ROI.movePoint(self, *args, **kargs) #ROI.movePoint(self, *args, **kargs)
self.prepareGeometryChange() #self.prepareGeometryChange()
for h in self.handles: #for h in self.handles:
h['pos'] = h['item'].pos() #h['pos'] = h['item'].pos()
def paint(self, p, *args): def paint(self, p, *args):
p.setRenderHint(QtGui.QPainter.Antialiasing) p.setRenderHint(QtGui.QPainter.Antialiasing)
@ -1687,103 +1756,33 @@ class LineSegmentROI(ROI):
ROI.__init__(self, pos, [1,1], **args) ROI.__init__(self, pos, [1,1], **args)
#ROI.__init__(self, positions[0]) #ROI.__init__(self, positions[0])
if len(positions) > 2: 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): for i, p in enumerate(positions):
self.addFreeHandle(p, item=handles[i]) 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): def listPoints(self):
return [p['item'].pos() for p in self.handles] 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): def paint(self, p, *args):
p.setRenderHint(QtGui.QPainter.Antialiasing) p.setRenderHint(QtGui.QPainter.Antialiasing)
p.setPen(self.currentPen) p.setPen(self.currentPen)
h1 = self.handles[0]['item'].pos() h1 = self.handles[0]['item'].pos()
h2 = self.handles[1]['item'].pos() h2 = self.handles[1]['item'].pos()
p.drawLine(h1, h2) 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): def boundingRect(self):
return self.shape().boundingRect() 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): def shape(self):
p = QtGui.QPainterPath() p = QtGui.QPainterPath()
#pw, ph = self.pixelSize()
#pHyp = 4 * (pw**2 + ph**2)**0.5
h1 = self.handles[0]['item'].pos() h1 = self.handles[0]['item'].pos()
h2 = self.handles[1]['item'].pos() h2 = self.handles[1]['item'].pos()
dh = h2-h1
if dh.length() == 0:
return p
pxv = self.pixelVectors(h2-h1)[1] pxv = self.pixelVectors(h2-h1)[1]
if pxv is None: if pxv is None:
@ -1799,14 +1798,6 @@ class LineSegmentROI(ROI):
return p 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)): 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. 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 QtCore.QRectF(-r*1.1, -r*1.1, 2.2*r, 2.2*r)
#return self.bounds #return self.bounds
def movePoint(self, *args, **kargs): #def movePoint(self, *args, **kargs):
ROI.movePoint(self, *args, **kargs) #ROI.movePoint(self, *args, **kargs)
self.prepareGeometryChange() #self.prepareGeometryChange()
for h in self.handles: #for h in self.handles:
h['pos'] = h['item'].pos()/self.state['size'][0] #h['pos'] = h['item'].pos()/self.state['size'][0]
def stateChanged(self): def stateChanged(self):
ROI.stateChanged(self) ROI.stateChanged(self)

View File

@ -109,6 +109,7 @@ class ViewBox(GraphicsWidget):
'background': None, 'background': None,
} }
self._updatingRange = False ## Used to break recursive loops. See updateAutoRange.
self.setFlag(self.ItemClipsChildrenToShape) self.setFlag(self.ItemClipsChildrenToShape)
@ -340,6 +341,7 @@ class ViewBox(GraphicsWidget):
============= ===================================================================== ============= =====================================================================
""" """
changes = {} changes = {}
if rect is not None: if rect is not None:
@ -471,6 +473,7 @@ class ViewBox(GraphicsWidget):
#if not enable: #if not enable:
#import traceback #import traceback
#traceback.print_stack() #traceback.print_stack()
if enable is True: if enable is True:
enable = 1.0 enable = 1.0
@ -520,6 +523,14 @@ class ViewBox(GraphicsWidget):
self.updateAutoRange() self.updateAutoRange()
def updateAutoRange(self): def updateAutoRange(self):
## 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
self._updatingRange = True
try:
targetRect = self.viewRange() targetRect = self.viewRange()
if not any(self.state['autoRange']): if not any(self.state['autoRange']):
return return
@ -588,6 +599,8 @@ class ViewBox(GraphicsWidget):
args['disableAutoRange'] = False args['disableAutoRange'] = False
#self.setRange(xRange=targetRect[0], yRange=targetRect[1], padding=0, disableAutoRange=False) #self.setRange(xRange=targetRect[0], yRange=targetRect[1], padding=0, disableAutoRange=False)
self.setRange(**args) self.setRange(**args)
finally:
self._updatingRange = False
def setXLink(self, view): def setXLink(self, view):
"""Link this view's X axis to another view. (see LinkView)""" """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.Qt import QtGui, QtCore
from pyqtgraph import Transform3D
class GLGraphicsItem(QtCore.QObject): class GLGraphicsItem(QtCore.QObject):
def __init__(self, parentItem=None): def __init__(self, parentItem=None):
@ -6,7 +7,7 @@ class GLGraphicsItem(QtCore.QObject):
self.__parent = None self.__parent = None
self.__view = None self.__view = None
self.__children = set() self.__children = set()
self.__transform = QtGui.QMatrix4x4() self.__transform = Transform3D()
self.__visible = True self.__visible = True
self.setParentItem(parentItem) self.setParentItem(parentItem)
self.setDepthValue(0) self.setDepthValue(0)
@ -50,7 +51,7 @@ class GLGraphicsItem(QtCore.QObject):
return self.__depthValue return self.__depthValue
def setTransform(self, tr): def setTransform(self, tr):
self.__transform = tr self.__transform = Transform3D(tr)
self.update() self.update()
def resetTransform(self): def resetTransform(self):
@ -73,12 +74,22 @@ class GLGraphicsItem(QtCore.QObject):
def transform(self): def transform(self):
return self.__transform 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): def translate(self, dx, dy, dz, local=False):
""" """
Translate the object by (*dx*, *dy*, *dz*) in its parent's coordinate system. Translate the object by (*dx*, *dy*, *dz*) in its parent's coordinate system.
If *local* is True, then translation takes place in local coordinates. If *local* is True, then translation takes place in local coordinates.
""" """
tr = QtGui.QMatrix4x4() tr = Transform3D()
tr.translate(dx, dy, dz) tr.translate(dx, dy, dz)
self.applyTransform(tr, local=local) self.applyTransform(tr, local=local)
@ -88,7 +99,7 @@ class GLGraphicsItem(QtCore.QObject):
*angle* is in degrees. *angle* is in degrees.
""" """
tr = QtGui.QMatrix4x4() tr = Transform3D()
tr.rotate(angle, x, y, z) tr.rotate(angle, x, y, z)
self.applyTransform(tr, local=local) 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. 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. If *local* is False, then scale takes place in the parent's coordinates.
""" """
tr = QtGui.QMatrix4x4() tr = Transform3D()
tr.scale(x, y, z) tr.scale(x, y, z)
self.applyTransform(tr, local=local) self.applyTransform(tr, local=local)
@ -138,8 +149,29 @@ class GLGraphicsItem(QtCore.QObject):
return return
v.updateGL() v.updateGL()
def mapToParent(self, point):
tr = self.transform()
if tr is None:
return point
return tr.map(point)
def mapFromParent(self, point): def mapFromParent(self, point):
tr = self.transform() tr = self.transform()
if tr is None: if tr is None:
return point 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 pyqtgraph.Qt import QtCore, QtGui, QtOpenGL
from OpenGL.GL import * from OpenGL.GL import *
import numpy as np import numpy as np
from pyqtgraph import Vector
Vector = QtGui.QVector3D ##Vector = QtGui.QVector3D
class GLViewWidget(QtOpenGL.QGLWidget): class GLViewWidget(QtOpenGL.QGLWidget):
""" """
@ -181,9 +181,13 @@ class GLViewWidget(QtOpenGL.QGLWidget):
def pixelSize(self, pos): def pixelSize(self, pos):
""" """
Return the approximate size of a screen pixel at the location 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() cam = self.cameraPosition()
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() dist = (pos-cam).length()
xDist = dist * 2. * np.tan(0.5 * self.opts['fov'] * np.pi / 180.) xDist = dist * 2. * np.tan(0.5 * self.opts['fov'] * np.pi / 180.)
return xDist / self.width() return xDist / self.width()

View File

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

View File

@ -1,5 +1,7 @@
from OpenGL.GL import * from OpenGL.GL import *
from OpenGL.arrays import vbo
from .. GLGraphicsItem import GLGraphicsItem from .. GLGraphicsItem import GLGraphicsItem
from .. import shaders
from pyqtgraph import QtGui from pyqtgraph import QtGui
import numpy as np import numpy as np
@ -14,6 +16,7 @@ class GLScatterPlotItem(GLGraphicsItem):
self.size = 10 self.size = 10
self.color = [1.0,1.0,1.0,0.5] self.color = [1.0,1.0,1.0,0.5]
self.pxMode = True self.pxMode = True
#self.vbo = {} ## VBO does not appear to improve performance very much.
self.setData(**kwds) self.setData(**kwds)
def setData(self, **kwds): def setData(self, **kwds):
@ -39,13 +42,16 @@ class GLScatterPlotItem(GLGraphicsItem):
for k in kwds.keys(): for k in kwds.keys():
if k not in args: if k not in args:
raise Exception('Invalid keyword argument: %s (allowed arguments are %s)' % (k, str(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) args.remove('pxMode')
self.size = kwds.get('size', self.size) 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.pxMode = kwds.get('pxMode', self.pxMode)
self.update() self.update()
def initializeGL(self): def initializeGL(self):
## Generate texture for rendering points ## Generate texture for rendering points
@ -65,33 +71,55 @@ class GLScatterPlotItem(GLGraphicsItem):
glBindTexture(GL_TEXTURE_2D, self.pointTexture) 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) glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, pData.shape[0], pData.shape[1], 0, GL_RGBA, GL_UNSIGNED_BYTE, pData)
def paint(self): self.shader = shaders.getShaderProgram('point_sprite')
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
#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_BLEND )
glEnable( GL_ALPHA_TEST ) glEnable( GL_ALPHA_TEST )
glEnable( GL_POINT_SMOOTH ) glDisable( GL_DEPTH_TEST )
glHint(GL_POINT_SMOOTH_HINT, GL_NICEST) #glEnable( GL_POINT_SMOOTH )
#glHint(GL_POINT_SMOOTH_HINT, GL_NICEST)
#glPointParameterfv(GL_POINT_DISTANCE_ATTENUATION, (0, 0, -1e-3)) #glPointParameterfv(GL_POINT_DISTANCE_ATTENUATION, (0, 0, -1e-3))
#glPointParameterfv(GL_POINT_SIZE_MAX, (65500,)) #glPointParameterfv(GL_POINT_SIZE_MAX, (65500,))
#glPointParameterfv(GL_POINT_SIZE_MIN, (0,)) #glPointParameterfv(GL_POINT_SIZE_MIN, (0,))
def paint(self):
self.setupGLState()
glEnable(GL_POINT_SPRITE) glEnable(GL_POINT_SPRITE)
glActiveTexture(GL_TEXTURE0) glActiveTexture(GL_TEXTURE0)
glEnable( GL_TEXTURE_2D ) glEnable( GL_TEXTURE_2D )
glBindTexture(GL_TEXTURE_2D, self.pointTexture) glBindTexture(GL_TEXTURE_2D, self.pointTexture)
glTexEnvi(GL_POINT_SPRITE, GL_COORD_REPLACE, GL_TRUE) glTexEnvi(GL_POINT_SPRITE, GL_COORD_REPLACE, GL_TRUE)
#glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE) ## use texture color exactly #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_MIN_FILTER, GL_LINEAR)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_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_S, GL_CLAMP_TO_EDGE)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE) glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)
glEnable(GL_PROGRAM_POINT_SIZE)
if self.pxMode:
with self.shader:
#glUniform1i(self.shader.uniform('texture'), 0) ## inform the shader which texture to use
glEnableClientState(GL_VERTEX_ARRAY)
try:
glVertexPointerf(self.pos) glVertexPointerf(self.pos)
if isinstance(self.color, np.ndarray): if isinstance(self.color, np.ndarray):
glEnableClientState(GL_COLOR_ARRAY)
glColorPointerf(self.color) glColorPointerf(self.color)
else: else:
if isinstance(self.color, QtGui.QColor): if isinstance(self.color, QtGui.QColor):
@ -99,39 +127,50 @@ class GLScatterPlotItem(GLGraphicsItem):
else: else:
glColor4f(*self.color) glColor4f(*self.color)
if isinstance(self.size, np.ndarray): if not self.pxMode or isinstance(self.size, np.ndarray):
raise Exception('Array size not yet supported in pxMode (hopefully soon)') 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
glPointSize(self.size) glNormalPointerf(norm)
glEnableClientState(GL_VERTEX_ARRAY) else:
glEnableClientState(GL_COLOR_ARRAY) glNormal3f(self.size,0,0)
#glPointSize(self.size)
glDrawArrays(GL_POINTS, 0, len(self.pos)) glDrawArrays(GL_POINTS, 0, len(self.pos))
else: 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]
for i in range(len(self.pos)): #if isinstance(self.color, np.ndarray):
pos = self.pos[i] #color = self.color[i]
#else:
#color = self.color
#if isinstance(self.color, QtGui.QColor):
#color = fn.glColor(self.color)
if isinstance(self.color, np.ndarray): #if isinstance(self.size, np.ndarray):
color = self.color[i] #size = self.size[i]
else: #else:
color = self.color #size = self.size
if isinstance(self.color, QtGui.QColor):
color = fn.glColor(self.color)
if isinstance(self.size, np.ndarray): #pxSize = self.view().pixelSize(QtGui.QVector3D(*pos))
size = self.size[i]
else:
size = self.size
pxSize = self.view().pixelSize(QtGui.QVector3D(*pos)) #glPointSize(size / pxSize)
#glBegin( GL_POINTS )
glPointSize(size / pxSize) #glColor4f(*color) # x is blue
glBegin( GL_POINTS ) ##glNormal3f(size, 0, 0)
glColor4f(*color) # x is blue #glVertex3f(*pos)
#glNormal3f(size, 0, 0) #glEnd()
glVertex3f(*pos)
glEnd()

View File

@ -3,10 +3,11 @@ from OpenGL.GL import shaders
## For centralizing and managing vertex/fragment shader programs. ## For centralizing and managing vertex/fragment shader programs.
def initShaders():
Shaders = { global Shaders
'balloon': ( ## increases fragment alpha as the normal turns orthogonal to the view Shaders = [
""" ShaderProgram('balloon', [ ## increases fragment alpha as the normal turns orthogonal to the view
VertexShader("""
varying vec3 normal; varying vec3 normal;
void main() { void main() {
normal = normalize(gl_NormalMatrix * gl_Normal); normal = normalize(gl_NormalMatrix * gl_Normal);
@ -16,26 +17,93 @@ Shaders = {
gl_BackColor = gl_Color; gl_BackColor = gl_Color;
gl_Position = ftransform(); gl_Position = ftransform();
} }
""", """),
""" FragmentShader("""
varying vec3 normal; varying vec3 normal;
void main() { void main() {
vec4 color = gl_Color; 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); 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; gl_FragColor = color;
} }
""" """)
), ]),
} ShaderProgram('point_sprite', [ ## allows specifying point size using normal.x
CompiledShaders = {} ## 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;
#}
#""")
]),
]
def getShader(name):
global Shaders, CompiledShaders
if name not in CompiledShaders: CompiledShaderPrograms = {}
vshader, fshader = Shaders[name]
vcomp = shaders.compileShader(vshader, GL_VERTEX_SHADER) def getShaderProgram(name):
fcomp = shaders.compileShader(fshader, GL_FRAGMENT_SHADER) return ShaderProgram.names[name]
prog = shaders.compileProgram(vcomp, fcomp)
CompiledShaders[name] = prog, vcomp, fcomp class VertexShader:
return CompiledShaders[name][0] 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 = {}
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: if opts.get('limits', None) is None:
opts['limits'] = [] opts['limits'] = []
Parameter.__init__(self, **opts) Parameter.__init__(self, **opts)
self.setLimits(opts['limits'])
def setLimits(self, limits): def setLimits(self, limits):
self.forward, self.reverse = self.mapping(limits) self.forward, self.reverse = self.mapping(limits)

View File

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

View File

@ -201,8 +201,10 @@ class TreeWidget(QtGui.QTreeWidget):
for item in items: for item in items:
self.prepareMove(item) self.prepareMove(item)
QtGui.QTreeWidget.clear(self) 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): class TreeWidgetItem(QtGui.QTreeWidgetItem):