merge from luke
This commit is contained in:
commit
2ca4cddff7
@ -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
35
Transform3D.py
Normal 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
|
@ -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())
|
||||
|
@ -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 *
|
||||
|
@ -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)
|
||||
|
@ -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:
|
||||
|
@ -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()
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)"""
|
||||
|
@ -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)
|
||||
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
|
@ -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()
|
||||
|
||||
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
"""
|
||||
),
|
||||
}
|
||||
CompiledShaders = {}
|
||||
""")
|
||||
]),
|
||||
ShaderProgram('point_sprite', [ ## allows specifying point size using normal.x
|
||||
## See:
|
||||
##
|
||||
## http://stackoverflow.com/questions/9609423/applying-part-of-a-texture-sprite-sheet-texture-map-to-a-point-sprite-in-ios
|
||||
## http://stackoverflow.com/questions/3497068/textured-points-in-opengl-es-2-0
|
||||
##
|
||||
##
|
||||
VertexShader("""
|
||||
void main() {
|
||||
gl_FrontColor=gl_Color;
|
||||
gl_PointSize = gl_Normal.x;
|
||||
gl_Position = ftransform();
|
||||
}
|
||||
"""),
|
||||
#FragmentShader("""
|
||||
##version 120
|
||||
#uniform sampler2D texture;
|
||||
#void main ( )
|
||||
#{
|
||||
#gl_FragColor = texture2D(texture, gl_PointCoord) * gl_Color;
|
||||
#}
|
||||
#""")
|
||||
]),
|
||||
]
|
||||
|
||||
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()
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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):
|
||||
|
Loading…
Reference in New Issue
Block a user