Commit 96202aed authored by Luke Campagnola's avatar Luke Campagnola
Browse files

Basic functionality in RemoteGraphicsView is working.

parent 5b6f77be
......@@ -7,10 +7,9 @@ app = pg.mkQApp()
v = pg.RemoteGraphicsView()
QtGui =
rect = QtGui.QGraphicsRectItem(0,0,10,10)
plt =
plt.plot([1,4,2,3,6,2,3,4,2,3], pen='g')
......@@ -511,24 +511,31 @@ class LocalObjectProxy(object):
del cls.proxiedObjects[pid]
#print "release:", cls.proxiedObjects
def __init__(self, obj):
def __init__(self, obj, **opts):
Create a 'local' proxy object that, when sent to a remote host,
will appear as a normal ObjectProxy to *obj*.
Any extra keyword arguments are passed to proxy._setProxyOptions()
on the remote side.
self.processId = os.getpid()
#self.objectId = id(obj)
self.typeStr = repr(obj)
#self.handler = handler
self.obj = obj
self.opts = opts
def __reduce__(self):
## a proxy is being pickled and sent to a remote process.
## every time this happens, a new proxy will be generated in the remote process,
## so we keep a new ID so we can track when each is released.
pid = LocalObjectProxy.registerObject(self.obj)
return (unpickleObjectProxy, (self.processId, pid, self.typeStr))
return (unpickleObjectProxy, (self.processId, pid, self.typeStr, None, self.opts))
## alias
proxy = LocalObjectProxy
def unpickleObjectProxy(processId, proxyId, typeStr, attributes=None):
def unpickleObjectProxy(processId, proxyId, typeStr, attributes=None, opts=None):
if processId == os.getpid():
obj = LocalObjectProxy.lookupProxyId(proxyId)
if attributes is not None:
......@@ -536,7 +543,10 @@ def unpickleObjectProxy(processId, proxyId, typeStr, attributes=None):
obj = getattr(obj, attr)
return obj
return ObjectProxy(processId, proxyId=proxyId, typeStr=typeStr)
proxy = ObjectProxy(processId, proxyId=proxyId, typeStr=typeStr)
if opts is not None:
return proxy
class ObjectProxy(object):
......@@ -2,11 +2,18 @@ from pyqtgraph.Qt import QtGui, QtCore
import pyqtgraph.multiprocess as mp
import pyqtgraph as pg
import numpy as np
import ctypes, os
import mmap, tempfile, ctypes, atexit
__all__ = ['RemoteGraphicsView']
class RemoteGraphicsView(QtGui.QWidget):
Replacement for GraphicsView that does all scene management and rendering on a remote process,
while displaying on the local widget.
GraphicsItems must be created by proxy to the remote process.
def __init__(self, parent=None, *args, **kwds):
self._img = None
self._imgReq = None
......@@ -16,10 +23,16 @@ class RemoteGraphicsView(QtGui.QWidget):
rpgRemote = self._proc._import('pyqtgraph.widgets.RemoteGraphicsView')
self._view = rpgRemote.Renderer(*args, **kwds)
shmFileName = self._view.shmFileName()
self.shmFile = open(shmFileName, 'r')
self.shm = mmap.mmap(self.shmFile.fileno(), mmap.PAGESIZE, mmap.MAP_SHARED, mmap.PROT_READ)
def scene(self):
return self._view.scene()
for method in ['scene', 'setCentralItem']:
setattr(self, method, getattr(self._view, method))
def resizeEvent(self, ev):
ret = QtGui.QWidget.resizeEvent(self, ev)
......@@ -27,21 +40,63 @@ class RemoteGraphicsView(QtGui.QWidget):
return ret
def remoteSceneChanged(self, data):
self._img = pg.makeQImage(data, alpha=True)
w, h, size = data
if self.shm.size != size:
self.shm = mmap.mmap(self.shmFile.fileno(), size, mmap.MAP_SHARED, mmap.PROT_READ)
self._img = QtGui.QImage(*h*4), w, h, QtGui.QImage.Format_ARGB32)
def paintEvent(self, ev):
if self._img is None:
p = QtGui.QPainter(self)
p.drawImage(self.rect(), self._img, self.rect())
p.drawImage(self.rect(), self._img, QtCore.QRect(0, 0, self._img.width(), self._img.height()))
def mousePressEvent(self, ev):
self._view.mousePressEvent(ev.type(), ev.pos(), ev.globalPos(), ev.button(), int(ev.buttons()), int(ev.modifiers()), _callSync='off')
return QtGui.QWidget.mousePressEvent(self, ev)
def mouseReleaseEvent(self, ev):
self._view.mouseReleaseEvent(ev.type(), ev.pos(), ev.globalPos(), ev.button(), int(ev.buttons()), int(ev.modifiers()), _callSync='off')
return QtGui.QWidget.mouseReleaseEvent(self, ev)
def mouseMoveEvent(self, ev):
self._view.mouseMoveEvent(ev.type(), ev.pos(), ev.globalPos(), ev.button(), int(ev.buttons()), int(ev.modifiers()), _callSync='off')
return QtGui.QWidget.mouseMoveEvent(self, ev)
def wheelEvent(self, ev):
self._view.wheelEvent(ev.pos(), ev.globalPos(),, int(ev.buttons()), int(ev.modifiers()), ev.orientation(), _callSync='off')
return QtGui.QWidget.wheelEvent(self, ev)
def keyEvent(self, ev):
if self._view.keyEvent(ev.type(), int(ev.modifiers()), text, autorep, count):
return QtGui.QWidget.keyEvent(self, ev)
class Renderer(pg.GraphicsView):
sceneRendered = QtCore.Signal(object)
def __init__(self, *args, **kwds):
## Create shared memory for rendered image
#fd ='/tmp/mmaptest', os.O_CREAT | os.O_TRUNC | os.O_RDWR)
#os.write(fd, '\x00' * mmap.PAGESIZE)
self.shmFile = tempfile.NamedTemporaryFile(prefix='pyqtgraph_shmem_')
self.shmFile.write('\x00' * mmap.PAGESIZE)
fd = self.shmFile.fileno()
self.shm = mmap.mmap(fd, mmap.PAGESIZE, mmap.MAP_SHARED, mmap.PROT_WRITE)
pg.GraphicsView.__init__(self, *args, **kwds)
self.img = None
......@@ -49,22 +104,67 @@ class Renderer(pg.GraphicsView):
def close(self):
def shmFileName(self):
def update(self):
self.img = None
return pg.GraphicsView.update(self)
def resize(self, size):
oldSize = self.size()
pg.GraphicsView.resize(self, size)
self.resizeEvent(QtGui.QResizeEvent(size, oldSize))
def renderView(self):
if self.img is None:
self.img = QtGui.QImage(self.width(), self.height(), QtGui.QImage.Format_ARGB32)
## make sure shm is large enough and get its address
size = self.width() * self.height() * 4
if size > self.shm.size():
address = ctypes.addressof(ctypes.c_char.from_buffer(self.shm, 0))
## render the scene directly to shared memory
self.img = QtGui.QImage(address, self.width(), self.height(), QtGui.QImage.Format_ARGB32)
p = QtGui.QPainter(self.img)
self.render(p, self.viewRect(), self.rect())
p.end() = np.fromstring(ctypes.string_at(int(self.img.bits()), self.img.byteCount()), dtype=np.ubyte).reshape(self.height(), self.width(),4).transpose(1,0,2) = ctypes.string_at(int(self.img.bits()), self.img.byteCount())
self.sceneRendered.emit((self.width(), self.height(), self.shm.size()))
def mousePressEvent(self, typ, pos, gpos, btn, btns, mods):
typ = QtCore.QEvent.Type(typ)
btns = QtCore.Qt.MouseButtons(btns)
mods = QtCore.Qt.KeyboardModifiers(mods)
return pg.GraphicsView.mousePressEvent(self, QtGui.QMouseEvent(typ, pos, gpos, btn, btns, mods))
def mouseMoveEvent(self, typ, pos, gpos, btn, btns, mods):
typ = QtCore.QEvent.Type(typ)
btns = QtCore.Qt.MouseButtons(btns)
mods = QtCore.Qt.KeyboardModifiers(mods)
return pg.GraphicsView.mouseMoveEvent(self, QtGui.QMouseEvent(typ, pos, gpos, btn, btns, mods))
def mouseReleaseEvent(self, typ, pos, gpos, btn, btns, mods):
typ = QtCore.QEvent.Type(typ)
btns = QtCore.Qt.MouseButtons(btns)
mods = QtCore.Qt.KeyboardModifiers(mods)
return pg.GraphicsView.mouseReleaseEvent(self, QtGui.QMouseEvent(typ, pos, gpos, btn, btns, mods))
def wheelEvent(self, pos, gpos, d, btns, mods, ori):
btns = QtCore.Qt.MouseButtons(btns)
mods = QtCore.Qt.KeyboardModifiers(mods)
return pg.GraphicsView.wheelEvent(self, QtGui.QWheelEvent(pos, gpos, d, btns, mods, ori))
def keyEvent(self, typ, mods, text, autorep, count):
typ = QtCore.QEvent.Type(typ)
mods = QtCore.Qt.KeyboardModifiers(mods)
pg.GraphicsView.keyEvent(self, QtGui.QKeyEvent(typ, mods, text, autorep, count))
return ev.accepted()
\ No newline at end of file
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