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

View File

@ -57,3 +57,8 @@ class Vector(QtGui.QVector3D):
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':
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)
#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))
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.
======================== ===========================================================
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,8 +700,18 @@ 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):
"""
Return a dictionary with parameters for rebuilding the gradient. Keys will include:
@ -723,11 +745,12 @@ 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):
@ -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

@ -434,3 +434,17 @@ class GraphicsItem(object):
Called whenever the transformation matrix of the view has changed.
"""
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())
@ -527,6 +529,7 @@ class PlotItem(GraphicsWidget):
#item.sigPlotChanged.connect(self.plotChanged)
#self.plotChanged()
def addDataItem(self, item, *args):
print("PlotItem.addDataItem is deprecated. Use addItem instead.")
self.addItem(item, *args)
@ -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,6 +370,8 @@ 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)
## transform p0 and p1 into parent's coordinates (same as scene coords if there is no parent). I forget why.
p0 = self.mapSceneToParent(p0)
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)
## 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):
@ -1358,8 +1368,15 @@ class LineROI(ROI):
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,6 +1418,12 @@ 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 = []
@ -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,6 +523,14 @@ class ViewBox(GraphicsWidget):
self.updateAutoRange()
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()
if not any(self.state['autoRange']):
return
@ -588,6 +599,8 @@ class ViewBox(GraphicsWidget):
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)
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,9 +181,13 @@ 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()
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)
with self.shader:
glCallList(self.triList)
shaders.glUseProgram(0)
#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,33 +71,55 @@ 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 )
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_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:
with self.shader:
#glUniform1i(self.shader.uniform('texture'), 0) ## inform the shader which texture to use
glEnableClientState(GL_VERTEX_ARRAY)
try:
glVertexPointerf(self.pos)
if isinstance(self.color, np.ndarray):
glEnableClientState(GL_COLOR_ARRAY)
glColorPointerf(self.color)
else:
if isinstance(self.color, QtGui.QColor):
@ -99,39 +127,50 @@ class GLScatterPlotItem(GLGraphicsItem):
else:
glColor4f(*self.color)
if isinstance(self.size, np.ndarray):
raise Exception('Array size not yet supported in pxMode (hopefully soon)')
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
glPointSize(self.size)
glEnableClientState(GL_VERTEX_ARRAY)
glEnableClientState(GL_COLOR_ARRAY)
glNormalPointerf(norm)
else:
glNormal3f(self.size,0,0)
#glPointSize(self.size)
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)):
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.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
if isinstance(self.size, np.ndarray):
size = self.size[i]
else:
size = self.size
#pxSize = self.view().pixelSize(QtGui.QVector3D(*pos))
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()
#glPointSize(size / pxSize)
#glBegin( GL_POINTS )
#glColor4f(*color) # x is blue
##glNormal3f(size, 0, 0)
#glVertex3f(*pos)
#glEnd()

View File

@ -3,10 +3,11 @@ from OpenGL.GL import shaders
## For centralizing and managing vertex/fragment shader programs.
Shaders = {
'balloon': ( ## increases fragment alpha as the normal turns orthogonal to the view
"""
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);
@ -16,26 +17,93 @@ Shaders = {
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();
}
CompiledShaders = {}
"""),
#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:
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]
CompiledShaderPrograms = {}
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 = {}
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):