Commit 58ed8ee7 authored by Luke Campagnola's avatar Luke Campagnola
Browse files

ViewBox overhaul. Cleaned up code pathway:

  - setRange now only affects target range
  - updateViewRange only affects view range
  - updateMatrix only affects childGroup transform
  - updateMatrix is only called before a render

Pathway now looks like:
        setRange -> updateViewRange -> matrix dirty -> ...
                                    -> sigRangeChanged

        ... -> prepareForPaint -> updateAutoRange, updateMatrix if dirty
parents 5ef8b1d0 31928e70
......@@ -27,7 +27,7 @@ arr += np.random.normal(size=(100,100))
## create GUI
app = QtGui.QApplication([])
w = pg.GraphicsWindow(size=(800,800), border=True)
w = pg.GraphicsWindow(size=(1000,800), border=True)
w.setWindowTitle('pyqtgraph example: ROI Examples')
text = """Data Selection From Image.<br>\n
......
......@@ -13,7 +13,8 @@ from pyqtgraph.widgets.RemoteGraphicsView import RemoteGraphicsView
app = pg.mkQApp()
## Create the widget
v = RemoteGraphicsView(debug=False)
v = RemoteGraphicsView(debug=False) # setting debug=True causes both processes to print information
# about interprocess communication
v.show()
v.setWindowTitle('pyqtgraph example: RemoteGraphicsView')
......
......@@ -23,7 +23,9 @@ p2 = win.addPlot(row=2, col=0)
region = pg.LinearRegionItem()
region.setZValue(10)
p2.addItem(region)
# Add the LinearRegionItem to the ViewBox, but tell the ViewBox to exclude this
# item when doing auto-range calculations.
p2.addItem(region, ignoreBounds=True)
#pg.dbg()
p1.setAutoVisible(y=True)
......
......@@ -139,7 +139,7 @@ def importModules(path, globals, locals, excludes=()):
d = os.path.join(os.path.split(globals['__file__'])[0], path)
files = set()
for f in frozenSupport.listdir(d):
if frozenSupport.isdir(os.path.join(d, f)) and f != '__pycache__':
if frozenSupport.isdir(os.path.join(d, f)) and f not in ['__pycache__', 'tests']:
files.add(f)
elif f[-3:] == '.py' and f != '__init__.py':
files.add(f[:-3])
......@@ -311,13 +311,15 @@ def image(*args, **kargs):
return w
show = image ## for backward compatibility
def dbg():
def dbg(*args, **kwds):
"""
Create a console window and begin watching for exceptions.
All arguments are passed to :func:`ConsoleWidget.__init__() <pyqtgraph.console.ConsoleWidget.__init__>`.
"""
mkQApp()
from . import console
c = console.ConsoleWidget()
c = console.ConsoleWidget(*args, **kwds)
c.catchAllExceptions()
c.show()
global consoles
......
......@@ -12,6 +12,7 @@ class GraphicsObject(GraphicsItem, QtGui.QGraphicsObject):
"""
_qtBaseClass = QtGui.QGraphicsObject
def __init__(self, *args):
self.__inform_view_on_changes = True
QtGui.QGraphicsObject.__init__(self, *args)
self.setFlag(self.ItemSendsGeometryChanges)
GraphicsItem.__init__(self)
......@@ -20,7 +21,7 @@ class GraphicsObject(GraphicsItem, QtGui.QGraphicsObject):
ret = QtGui.QGraphicsObject.itemChange(self, change, value)
if change in [self.ItemParentHasChanged, self.ItemSceneHasChanged]:
self.parentChanged()
if change in [self.ItemPositionHasChanged, self.ItemTransformHasChanged]:
if self.__inform_view_on_changes and change in [self.ItemPositionHasChanged, self.ItemTransformHasChanged]:
self.informViewBoundsChanged()
## workaround for pyqt bug:
......
......@@ -1320,7 +1320,6 @@ class Handle(UIGraphicsItem):
## determine rotation of transform
#m = self.sceneTransform() ## Qt bug: do not access sceneTransform() until we know this object has a scene.
#mi = m.inverted()[0]
dt = self.deviceTransform()
if dt is None:
......@@ -1339,10 +1338,10 @@ class Handle(UIGraphicsItem):
return dti.map(tr.map(self.path))
def viewRangeChanged(self):
GraphicsObject.viewRangeChanged(self)
def viewTransformChanged(self):
GraphicsObject.viewTransformChanged(self)
self._shape = None ## invalidate shape, recompute later if requested.
#self.updateShape()
self.update()
#def itemChange(self, change, value):
#if change == self.ItemScenePositionHasChanged:
......
This diff is collapsed.
"""
ViewBox test cases:
* call setRange then resize; requested range must be fully visible
* lockAspect works correctly for arbitrary aspect ratio
* autoRange works correctly with aspect locked
* call setRange with aspect locked, then resize
* AutoRange with all the bells and whistles
* item moves / changes transformation / changes bounds
* pan only
* fractional range
"""
import pyqtgraph as pg
app = pg.mkQApp()
imgData = pg.np.zeros((10, 10))
imgData[0] = 3
imgData[-1] = 3
imgData[:,0] = 3
imgData[:,-1] = 3
def testLinkWithAspectLock():
global win, vb
win = pg.GraphicsWindow()
vb = win.addViewBox(name="image view")
vb.setAspectLocked()
vb.enableAutoRange(x=False, y=False)
p1 = win.addPlot(name="plot 1")
p2 = win.addPlot(name="plot 2", row=1, col=0)
win.ci.layout.setRowFixedHeight(1, 150)
win.ci.layout.setColumnFixedWidth(1, 150)
def viewsMatch():
r0 = pg.np.array(vb.viewRange())
r1 = pg.np.array(p1.vb.viewRange()[1])
r2 = pg.np.array(p2.vb.viewRange()[1])
match = (abs(r0[1]-r1) <= (abs(r1) * 0.001)).all() and (abs(r0[0]-r2) <= (abs(r2) * 0.001)).all()
return match
p1.setYLink(vb)
p2.setXLink(vb)
print "link views match:", viewsMatch()
win.show()
print "show views match:", viewsMatch()
img = pg.ImageItem(imgData)
vb.addItem(img)
vb.autoRange()
p1.plot(x=imgData.sum(axis=0), y=range(10))
p2.plot(x=range(10), y=imgData.sum(axis=1))
print "add items views match:", viewsMatch()
#p1.setAspectLocked()
#grid = pg.GridItem()
#vb.addItem(grid)
pg.QtGui.QApplication.processEvents()
pg.QtGui.QApplication.processEvents()
#win.resize(801, 600)
def testAspectLock():
global win, vb
win = pg.GraphicsWindow()
vb = win.addViewBox(name="image view")
vb.setAspectLocked()
img = pg.ImageItem(imgData)
vb.addItem(img)
#app.processEvents()
#print "init views match:", viewsMatch()
#p2.setYRange(-300, 300)
#print "setRange views match:", viewsMatch()
#app.processEvents()
#print "setRange views match (after update):", viewsMatch()
#print "--lock aspect--"
#p1.setAspectLocked(True)
#print "lockAspect views match:", viewsMatch()
#p2.setYRange(-200, 200)
#print "setRange views match:", viewsMatch()
#app.processEvents()
#print "setRange views match (after update):", viewsMatch()
#win.resize(100, 600)
#app.processEvents()
#vb.setRange(xRange=[-10, 10], padding=0)
#app.processEvents()
#win.resize(600, 100)
#app.processEvents()
#print vb.viewRange()
if __name__ == '__main__':
testLinkWithAspectLock()
......@@ -20,10 +20,8 @@ if __name__ == '__main__':
if opts.pop('pyside', False):
import PySide
#import pyqtgraph
#import pyqtgraph.multiprocess.processes
targetStr = opts.pop('targetStr')
target = pickle.loads(targetStr) ## unpickling the target should import everything we need
#target(name, port, authkey, ppid)
target(**opts) ## Send all other options to the target function
sys.exit(0)
......@@ -48,9 +48,10 @@ class Process(RemoteEventHandler):
it must be picklable (bound methods are not).
copySysPath If True, copy the contents of sys.path to the remote process
debug If True, print detailed information about communication
with the child process.
with the child process. Note that this option may cause
strange behavior on some systems due to a python bug:
http://bugs.python.org/issue3905
============ =============================================================
"""
if target is None:
target = startEventLoop
......@@ -81,8 +82,14 @@ class Process(RemoteEventHandler):
self.debugMsg('Starting child process (%s %s)' % (executable, bootstrap))
## note: we need all three streams to have their own PIPE due to this bug:
## http://bugs.python.org/issue3905
self.proc = subprocess.Popen((executable, bootstrap), stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
## http://bugs.python.org/issue3905
if debug is True: # when debugging, we need to keep the usual stdout
stdout = sys.stdout
stderr = sys.stderr
else:
stdout = subprocess.PIPE
stderr = subprocess.PIPE
self.proc = subprocess.Popen((executable, bootstrap), stdin=subprocess.PIPE, stdout=stdout, stderr=stderr)
targetStr = pickle.dumps(target) ## double-pickle target so that child has a chance to
## set its sys.path properly before unpickling the target
......
......@@ -147,6 +147,11 @@ class GraphicsView(QtGui.QGraphicsView):
#print "GV: paint", ev.rect()
return QtGui.QGraphicsView.paintEvent(self, ev)
def render(self, *args, **kwds):
self.scene().prepareForPaint()
return QtGui.QGraphicsView.render(self, *args, **kwds)
def close(self):
self.centralWidget = None
self.scene().clear()
......
......@@ -18,12 +18,15 @@ class RemoteGraphicsView(QtGui.QWidget):
"""
def __init__(self, parent=None, *args, **kwds):
"""
The keyword arguments 'debug' and 'name', if specified, are passed to QtProcess.__init__().
"""
self._img = None
self._imgReq = None
self._sizeHint = (640,480) ## no clue why this is needed, but it seems to be the default sizeHint for GraphicsView.
## without it, the widget will not compete for space against another GraphicsView.
QtGui.QWidget.__init__(self)
self._proc = mp.QtProcess(debug=kwds.pop('debug', False))
self._proc = mp.QtProcess(debug=kwds.pop('debug', False), name=kwds.pop('name', None))
self.pg = self._proc._import('pyqtgraph')
self.pg.setConfigOptions(**self.pg.CONFIG_OPTIONS)
rpgRemote = self._proc._import('pyqtgraph.widgets.RemoteGraphicsView')
......@@ -123,6 +126,7 @@ class Renderer(GraphicsView):
def __init__(self, *args, **kwds):
## Create shared memory for rendered image
#pg.dbg(namespace={'r': self})
if sys.platform.startswith('win'):
self.shmtag = "pyqtgraph_shmem_" + ''.join([chr((random.getrandbits(20)%25) + 97) for i in range(20)])
self.shm = mmap.mmap(-1, mmap.PAGESIZE, self.shmtag) # use anonymous mmap on windows
......@@ -184,7 +188,11 @@ class Renderer(GraphicsView):
self.img = QtGui.QImage(ch, self.width(), self.height(), QtGui.QImage.Format_ARGB32)
else:
address = ctypes.addressof(ctypes.c_char.from_buffer(self.shm, 0))
self.img = QtGui.QImage(sip.voidptr(address), self.width(), self.height(), QtGui.QImage.Format_ARGB32)
try:
self.img = QtGui.QImage(sip.voidptr(address), self.width(), self.height(), QtGui.QImage.Format_ARGB32)
except TypeError:
# different versions of pyqt have different requirements here..
self.img = QtGui.QImage(memoryview(buffer(self.shm)), self.width(), self.height(), QtGui.QImage.Format_ARGB32)
self.img.fill(0xffffffff)
p = QtGui.QPainter(self.img)
self.render(p, self.viewRect(), self.rect())
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment