Multiprocess / RemoteGraphicsView now works in Windows

Bugfix in GraphicsItem.pixelVectors
Fixes for PySide and Python3
This commit is contained in:
Luke Campagnola 2013-01-12 19:01:52 -05:00
commit ba7e4af422
18 changed files with 160 additions and 109 deletions

View File

@ -8,32 +8,32 @@ import time
print "\n=================\nStart Process"
print("\n=================\nStart Process")
proc = mp.Process()
import os
print "parent:", os.getpid(), "child:", proc.proc.pid
print "started"
print("parent:", os.getpid(), "child:", proc.proc.pid)
print("started")
rnp = proc._import('numpy')
arr = rnp.array([1,2,3,4])
print repr(arr)
print str(arr)
print "return value:", repr(arr.mean(_returnType='value'))
print "return proxy:", repr(arr.mean(_returnType='proxy'))
print "return auto: ", repr(arr.mean(_returnType='auto'))
print(repr(arr))
print(str(arr))
print("return value:", repr(arr.mean(_returnType='value')))
print( "return proxy:", repr(arr.mean(_returnType='proxy')))
print( "return auto: ", repr(arr.mean(_returnType='auto')))
proc.join()
print "process finished"
print( "process finished")
print "\n=================\nStart ForkedProcess"
print( "\n=================\nStart ForkedProcess")
proc = mp.ForkedProcess()
rnp = proc._import('numpy')
arr = rnp.array([1,2,3,4])
print repr(arr)
print str(arr)
print repr(arr.mean())
print( repr(arr))
print( str(arr))
print( repr(arr.mean()))
proc.join()
print "process finished"
print( "process finished")
@ -42,10 +42,10 @@ import pyqtgraph as pg
from pyqtgraph.Qt import QtCore, QtGui
app = pg.QtGui.QApplication([])
print "\n=================\nStart QtProcess"
print( "\n=================\nStart QtProcess")
import sys
if (sys.flags.interactive != 1):
print " (not interactive; remote process will exit immediately.)"
print( " (not interactive; remote process will exit immediately.)")
proc = mp.QtProcess()
d1 = proc.transfer(np.random.normal(size=1000))
d2 = proc.transfer(np.random.normal(size=1000))

View File

@ -5,7 +5,7 @@ import pyqtgraph.multiprocess as mp
import pyqtgraph as pg
import time
print "\n=================\nParallelize"
print( "\n=================\nParallelize")
## Do a simple task:
## for x in range(N):
@ -36,7 +36,7 @@ with pg.ProgressDialog('processing serially..', maximum=len(tasks)) as dlg:
dlg += 1
if dlg.wasCanceled():
raise Exception('processing canceled')
print "Serial time: %0.2f" % (time.time() - start)
print( "Serial time: %0.2f" % (time.time() - start))
### Use parallelize, but force a single worker
### (this simulates the behavior seen on windows, which lacks os.fork)
@ -47,8 +47,8 @@ with mp.Parallelize(enumerate(tasks), results=results2, workers=1, progressDialo
for j in xrange(size):
tot += j * x
tasker.results[i] = tot
print "\nParallel time, 1 worker: %0.2f" % (time.time() - start)
print "Results match serial: ", results2 == results
print( "\nParallel time, 1 worker: %0.2f" % (time.time() - start))
print( "Results match serial: %s" % str(results2 == results))
### Use parallelize with multiple workers
start = time.time()
@ -58,6 +58,6 @@ with mp.Parallelize(enumerate(tasks), results=results3, progressDialog='processi
for j in xrange(size):
tot += j * x
tasker.results[i] = tot
print "\nParallel time, %d workers: %0.2f" % (mp.Parallelize.suggestedWorkerCount(), time.time() - start)
print "Results match serial: ", results3 == results
print( "\nParallel time, %d workers: %0.2f" % (mp.Parallelize.suggestedWorkerCount(), time.time() - start))
print( "Results match serial: %s" % str(results3 == results))

View File

@ -4,7 +4,7 @@ PyQtGraph - Scientific Graphics and GUI Library for Python
www.pyqtgraph.org
"""
__version__ = None
__version__ = '0.9.5'
### import all the goodies and add some helper functions for easy CLI use
@ -16,6 +16,9 @@ from .Qt import QtGui
#if QtGui.QApplication.instance() is None:
#app = QtGui.QApplication([])
import numpy ## pyqtgraph requires numpy
## (import here to avoid massive error dump later on if numpy is not available)
import os, sys
## check python version
@ -49,6 +52,8 @@ CONFIG_OPTIONS = {
'background': (0, 0, 0), ## default background for GraphicsWidget
'antialias': False,
'editorCommand': None, ## command used to invoke code editor from ConsoleWidgets
'useWeave': True, ## Use weave to speed up some operations, if it is available
'weaveDebug': False, ## Print full error message if weave compile fails
}

View File

@ -393,7 +393,7 @@ class Profiler:
if self.delayed:
self.msgs.append(msg2)
else:
print msg2
print(msg2)
self.t0 = ptime.time()
self.t1 = self.t0
@ -410,7 +410,7 @@ class Profiler:
if self.delayed:
self.msgs.append(msg2)
else:
print msg2
print(msg2)
self.t1 = ptime.time() ## don't measure time it took to print
def finish(self, msg=None):
@ -425,10 +425,10 @@ class Profiler:
self.msgs.append(msg)
if self.depth == 0:
for line in self.msgs:
print line
print(line)
Profiler.msgs = []
else:
print msg
print(msg)
Profiler.depth = self.depth
self.finished = True

View File

@ -220,7 +220,7 @@ def _generateItemSvg(item, nodes=None, root=None):
## get list of sub-groups
g2 = [n for n in g1.childNodes if isinstance(n, xml.Element) and n.tagName == 'g']
except:
print doc.toxml()
print(doc.toxml())
raise

View File

@ -236,7 +236,7 @@ class EvalNode(Node):
text = fn + "\n".join([" "+l for l in str(self.text.toPlainText()).split('\n')]) + run
exec(text)
except:
print "Error processing node:", self.name()
print("Error processing node: %s" % self.name())
raise
return output

View File

@ -23,6 +23,7 @@ SI_PREFIXES_ASCII = 'yzafpnum kMGTPEZY'
from .Qt import QtGui, QtCore, USE_PYSIDE
from pyqtgraph import getConfigOption
import numpy as np
import decimal, re
import ctypes
@ -30,9 +31,10 @@ import ctypes
try:
import scipy.ndimage
HAVE_SCIPY = True
WEAVE_DEBUG = getConfigOption('weaveDebug')
try:
import scipy.weave
USE_WEAVE = True
USE_WEAVE = getConfigOption('useWeave')
except:
USE_WEAVE = False
except ImportError:
@ -631,7 +633,8 @@ def rescaleData(data, scale, offset, dtype=None):
data = newData.reshape(data.shape)
except:
if USE_WEAVE:
debug.printExc("Error; disabling weave.")
if WEAVE_DEBUG:
debug.printExc("Error; disabling weave.")
USE_WEAVE = False
#p = np.poly1d([scale, -offset*scale])
@ -795,7 +798,7 @@ def makeARGB(data, lut=None, levels=None, scale=None, useRGBA=False):
if levels.shape != (data.shape[-1], 2):
raise Exception('levels must have shape (data.shape[-1], 2)')
else:
print levels
print(levels)
raise Exception("levels argument must be 1D or 2D.")
#levels = np.array(levels)
#if levels.shape == (2,):
@ -1257,7 +1260,7 @@ def isocurve(data, level, connected=False, extendToEdge=False, path=False):
points[b[1]].append([b,a])
## rearrange into chains
for k in points.keys():
for k in list(points.keys()):
try:
chains = points[k]
except KeyError: ## already used this point elsewhere
@ -1871,4 +1874,4 @@ def pseudoScatter(data, spacing=None, shuffle=True):
yvals[i] = y
return yvals[np.argsort(inds)] ## un-shuffle values before returning
return yvals[np.argsort(inds)] ## un-shuffle values before returning

View File

@ -4,7 +4,7 @@ from pyqtgraph.Point import Point
import pyqtgraph.functions as fn
import weakref
from pyqtgraph.pgcollections import OrderedDict
import operator
import operator, sys
class FiniteCache(OrderedDict):
"""Caches a finite number of objects, removing
@ -17,7 +17,7 @@ class FiniteCache(OrderedDict):
self.pop(item, None) # make sure item is added to end
OrderedDict.__setitem__(self, item, val)
while len(self) > self._length:
del self[self.keys()[0]]
del self[list(self.keys())[0]]
def __getitem__(self, item):
val = dict.__getitem__(self, item)
@ -197,14 +197,14 @@ class GraphicsItem(object):
## check local cache
if direction is None and dt == self._pixelVectorCache[0]:
return self._pixelVectorCache[1]
return tuple(map(Point, self._pixelVectorCache[1])) ## return a *copy*
## check global cache
key = (dt.m11(), dt.m21(), dt.m31(), dt.m12(), dt.m22(), dt.m32(), dt.m31(), dt.m32())
pv = self._pixelVectorGlobalCache.get(key, None)
if pv is not None:
self._pixelVectorCache = [dt, pv]
return pv
return tuple(map(Point,pv)) ## return a *copy*
if direction is None:
@ -547,4 +547,4 @@ class GraphicsItem(object):
#def update(self):
#self._qtBaseClass.update(self)
#print "Update:", self
#print "Update:", self

View File

@ -1783,8 +1783,7 @@ class LineSegmentROI(ROI):
dh = h2-h1
if dh.length() == 0:
return p
pxv = self.pixelVectors(h2-h1)[1]
pxv = self.pixelVectors(dh)[1]
if pxv is None:
return p
@ -1809,7 +1808,7 @@ class LineSegmentROI(ROI):
for i in range(len(imgPts)-1):
d = Point(imgPts[i+1] - imgPts[i])
o = Point(imgPts[i])
r = fn.affineSlice(data, shape=(int(d.length()),), vectors=[d.norm()], origin=o, axes=axes, order=1)
r = fn.affineSlice(data, shape=(int(d.length()),), vectors=[Point(d.norm())], origin=o, axes=axes, order=1)
rgns.append(r)
return np.concatenate(rgns, axis=axes[0])

View File

@ -41,7 +41,7 @@ def drawSymbol(painter, symbol, size, pen, brush):
if isinstance(symbol, basestring):
symbol = Symbols[symbol]
if np.isscalar(symbol):
symbol = Symbols.values()[symbol % len(Symbols)]
symbol = list(Symbols.values())[symbol % len(Symbols)]
painter.drawPath(symbol)

View File

@ -1198,7 +1198,7 @@ class ViewBox(GraphicsWidget):
if ViewBox is None: ## can happen as python is shutting down
return
## Called with ID and name of view (the view itself is no longer available)
for v in ViewBox.AllViews.keys():
for v in list(ViewBox.AllViews.keys()):
if id(v) == vid:
ViewBox.AllViews.pop(v)
break

View File

@ -2,15 +2,23 @@
import sys, pickle, os
if __name__ == '__main__':
os.setpgrp() ## prevents signals (notably keyboard interrupt) being forwarded from parent to this process
name, port, authkey, targetStr, path = pickle.load(sys.stdin)
if hasattr(os, 'setpgrp'):
os.setpgrp() ## prevents signals (notably keyboard interrupt) being forwarded from parent to this process
if sys.version[0] == '3':
name, port, authkey, ppid, targetStr, path, pyside = pickle.load(sys.stdin.buffer)
else:
name, port, authkey, ppid, targetStr, path, pyside = pickle.load(sys.stdin)
#print "key:", ' '.join([str(ord(x)) for x in authkey])
if path is not None:
## rewrite sys.path without assigning a new object--no idea who already has a reference to the existing list.
while len(sys.path) > 0:
sys.path.pop()
sys.path.extend(path)
if pyside:
import PySide
#import pyqtgraph
#import pyqtgraph.multiprocess.processes
target = pickle.loads(targetStr) ## unpickling the target should import everything we need
target(name, port, authkey)
target(name, port, authkey, ppid)
sys.exit(0)

View File

@ -1,6 +1,6 @@
import os, sys, time, multiprocessing, re
from processes import ForkedProcess
from remoteproxy import ClosedError
from .processes import ForkedProcess
from .remoteproxy import ClosedError
class CanceledError(Exception):
"""Raised when the progress dialog is canceled during a processing operation."""
@ -19,7 +19,7 @@ class Parallelize(object):
for task in tasks:
result = processTask(task)
results.append(result)
print results
print(results)
## Here is the parallelized version:
@ -30,7 +30,7 @@ class Parallelize(object):
for task in tasker:
result = processTask(task)
tasker.results.append(result)
print results
print(results)
The only major caveat is that *result* in the example above must be picklable,

View File

@ -1,7 +1,11 @@
from remoteproxy import RemoteEventHandler, ClosedError, NoResultError, LocalObjectProxy, ObjectProxy
from .remoteproxy import RemoteEventHandler, ClosedError, NoResultError, LocalObjectProxy, ObjectProxy
import subprocess, atexit, os, sys, time, random, socket, signal
import cPickle as pickle
import multiprocessing.connection
from pyqtgraph.Qt import USE_PYSIDE
try:
import cPickle as pickle
except ImportError:
import pickle
__all__ = ['Process', 'QtProcess', 'ForkedProcess', 'ClosedError', 'NoResultError']
@ -54,12 +58,13 @@ class Process(RemoteEventHandler):
executable = sys.executable
## random authentication key
authkey = ''.join([chr(random.getrandbits(7)) for i in range(20)])
authkey = os.urandom(20)
#print "key:", ' '.join([str(ord(x)) for x in authkey])
## Listen for connection from remote process (and find free port number)
port = 10000
while True:
try:
## hmac authentication appears to be broken on windows (says AuthenticationError: digest received was wrong)
l = multiprocessing.connection.Listener(('localhost', int(port)), authkey=authkey)
break
except socket.error as ex:
@ -73,7 +78,11 @@ class Process(RemoteEventHandler):
self.proc = subprocess.Popen((executable, bootstrap), stdin=subprocess.PIPE)
targetStr = pickle.dumps(target) ## double-pickle target so that child has a chance to
## set its sys.path properly before unpickling the target
pickle.dump((name+'_child', port, authkey, targetStr, sysPath), self.proc.stdin)
pid = os.getpid() # we must sent pid to child because windows does not have getppid
pyside = USE_PYSIDE
## Send everything the remote process needs to start correctly
pickle.dump((name+'_child', port, authkey, pid, targetStr, sysPath, pyside), self.proc.stdin)
self.proc.stdin.close()
## open connection for remote process
@ -92,10 +101,11 @@ class Process(RemoteEventHandler):
time.sleep(0.05)
def startEventLoop(name, port, authkey):
def startEventLoop(name, port, authkey, ppid):
conn = multiprocessing.connection.Client(('localhost', int(port)), authkey=authkey)
global HANDLER
HANDLER = RemoteEventHandler(conn, name, os.getppid())
#ppid = 0 if not hasattr(os, 'getppid') else os.getppid()
HANDLER = RemoteEventHandler(conn, name, ppid)
while True:
try:
HANDLER.processRequests() # exception raised when the loop should exit
@ -161,6 +171,7 @@ class ForkedProcess(RemoteEventHandler):
proxyId = LocalObjectProxy.registerObject(v)
proxyIDs[k] = proxyId
ppid = os.getpid() # write this down now; windows doesn't have getppid
pid = os.fork()
if pid == 0:
self.isParent = False
@ -200,9 +211,9 @@ class ForkedProcess(RemoteEventHandler):
if 'random' in sys.modules:
sys.modules['random'].seed(os.getpid() ^ int(time.time()*10000%10000))
RemoteEventHandler.__init__(self, remoteConn, name+'_child', pid=os.getppid())
#ppid = 0 if not hasattr(os, 'getppid') else os.getppid()
RemoteEventHandler.__init__(self, remoteConn, name+'_child', pid=ppid)
ppid = os.getppid()
self.forkedProxies = {}
for name, proxyId in proxyIDs.iteritems():
self.forkedProxies[name] = ObjectProxy(ppid, proxyId=proxyId, typeStr=repr(preProxy[name]))
@ -228,7 +239,7 @@ class ForkedProcess(RemoteEventHandler):
except ClosedError:
break
except:
print "Error occurred in forked event loop:"
print("Error occurred in forked event loop:")
sys.excepthook(*sys.exc_info())
sys.exit(0)
@ -293,7 +304,7 @@ class QtProcess(Process):
btn.show()
def slot():
print 'slot invoked on parent process'
print('slot invoked on parent process')
btn.clicked.connect(proxy(slot)) # be sure to send a proxy of the slot
"""
@ -318,7 +329,7 @@ class QtProcess(Process):
except ClosedError:
self.timer.stop()
def startQtEventLoop(name, port, authkey):
def startQtEventLoop(name, port, authkey, ppid):
conn = multiprocessing.connection.Client(('localhost', int(port)), authkey=authkey)
from pyqtgraph.Qt import QtGui, QtCore
#from PyQt4 import QtGui, QtCore
@ -330,7 +341,8 @@ def startQtEventLoop(name, port, authkey):
## until it is explicitly closed by the parent process.
global HANDLER
HANDLER = RemoteQtEventHandler(conn, name, os.getppid())
#ppid = 0 if not hasattr(os, 'getppid') else os.getppid()
HANDLER = RemoteQtEventHandler(conn, name, ppid)
HANDLER.startEventTimer()
app.exec_()

View File

@ -1,6 +1,11 @@
import os, __builtin__, time, sys, traceback, weakref
import cPickle as pickle
import os, time, sys, traceback, weakref
import numpy as np
try:
import __builtin__ as builtins
import cPickle as pickle
except ImportError:
import builtins
import pickle
class ClosedError(Exception):
"""Raised when an event handler receives a request to close the connection
@ -68,7 +73,7 @@ class RemoteEventHandler(object):
try:
return cls.handlers[pid]
except:
print pid, cls.handlers
print(pid, cls.handlers)
raise
def getProxyOption(self, opt):
@ -103,7 +108,7 @@ class RemoteEventHandler(object):
else:
raise
except:
print "Error in process %s" % self.name
print("Error in process %s" % self.name)
sys.excepthook(*sys.exc_info())
return numProcessed
@ -181,7 +186,7 @@ class RemoteEventHandler(object):
elif cmd == 'import':
name = opts['module']
fromlist = opts.get('fromlist', [])
mod = __builtin__.__import__(name, fromlist=fromlist)
mod = builtins.__import__(name, fromlist=fromlist)
if len(fromlist) == 0:
parts = name.lstrip('.').split('.')
@ -239,7 +244,7 @@ class RemoteEventHandler(object):
self.send(request='result', reqId=reqId, callSync='off', opts=dict(result=result))
def replyError(self, reqId, *exc):
print "error:", self.name, reqId, exc[1]
print("error: %s %s %s" % (self.name, str(reqId), str(exc[1])))
excStr = traceback.format_exception(*exc)
try:
self.send(request='error', reqId=reqId, callSync='off', opts=dict(exception=exc[1], excString=excStr))
@ -352,9 +357,9 @@ class RemoteEventHandler(object):
try:
optStr = pickle.dumps(opts)
except:
print "==== Error pickling this object: ===="
print opts
print "======================================="
print("==== Error pickling this object: ====")
print(opts)
print("=======================================")
raise
nByteMsgs = 0
@ -404,12 +409,12 @@ class RemoteEventHandler(object):
#print ''.join(result)
exc, excStr = result
if exc is not None:
print "===== Remote process raised exception on request: ====="
print ''.join(excStr)
print "===== Local Traceback to request follows: ====="
print("===== Remote process raised exception on request: =====")
print(''.join(excStr))
print("===== Local Traceback to request follows: =====")
raise exc
else:
print ''.join(excStr)
print(''.join(excStr))
raise Exception("Error getting result. See above for exception from remote process.")
else:
@ -535,7 +540,7 @@ class Request(object):
raise ClosedError()
time.sleep(0.005)
if timeout >= 0 and time.time() - start > timeout:
print "Request timed out:", self.description
print("Request timed out: %s" % self.description)
import traceback
traceback.print_stack()
raise NoResultError()

View File

@ -6,10 +6,10 @@ class GLTest(QtOpenGL.QGLWidget):
def __init__(self):
QtOpenGL.QGLWidget.__init__(self)
self.makeCurrent()
print "GL version:", glGetString(GL_VERSION)
print "MAX_TEXTURE_SIZE:", glGetIntegerv(GL_MAX_TEXTURE_SIZE)
print "MAX_3D_TEXTURE_SIZE:", glGetIntegerv(GL_MAX_3D_TEXTURE_SIZE)
print "Extensions:", glGetString(GL_EXTENSIONS)
print("GL version:" + glGetString(GL_VERSION))
print("MAX_TEXTURE_SIZE: %d" % glGetIntegerv(GL_MAX_TEXTURE_SIZE))
print("MAX_3D_TEXTURE_SIZE: %d" % glGetIntegerv(GL_MAX_3D_TEXTURE_SIZE))
print("Extensions: " + glGetString(GL_EXTENSIONS))
GLTest()

View File

@ -267,14 +267,14 @@ class A(object):
object.__init__(self)
self.msg = msg
def fn(self, pfx = ""):
print pfx+"A class:", self.__class__, id(self.__class__)
print pfx+" %%s: %d" %% self.msg
print(pfx+"A class: %%s %%s" %% (str(self.__class__), str(id(self.__class__))))
print(pfx+" %%s: %d" %% self.msg)
class B(A):
def fn(self, pfx=""):
print pfx+"B class:", self.__class__, id(self.__class__)
print pfx+" %%s: %d" %% self.msg
print pfx+" calling superclass.. (%%s)" %% id(A)
print(pfx+"B class:", self.__class__, id(self.__class__))
print(pfx+" %%s: %d" %% self.msg)
print(pfx+" calling superclass.. (%%s)" %% id(A) )
A.fn(self, " ")
"""
@ -294,7 +294,7 @@ class C(A):
A.__init__(self, msg + "(init from C)")
def fn():
print "fn: %s"
print("fn: %s")
"""
open(modFile1, 'w').write(modCode1%(1,1))

View File

@ -3,7 +3,7 @@ import pyqtgraph.multiprocess as mp
import pyqtgraph as pg
from .GraphicsView import GraphicsView
import numpy as np
import mmap, tempfile, ctypes, atexit
import mmap, tempfile, ctypes, atexit, sys, random
__all__ = ['RemoteGraphicsView']
@ -27,13 +27,15 @@ class RemoteGraphicsView(QtGui.QWidget):
rpgRemote = self._proc._import('pyqtgraph.widgets.RemoteGraphicsView')
self._view = rpgRemote.Renderer(*args, **kwds)
self._view._setProxyOptions(deferGetattr=True)
self.setFocusPolicy(self._view.focusPolicy())
self.setFocusPolicy(QtCore.Qt.FocusPolicy(self._view.focusPolicy()))
self.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding)
self.setMouseTracking(True)
self.shm = None
shmFileName = self._view.shmFileName()
self.shmFile = open(shmFileName, 'r')
self.shm = mmap.mmap(self.shmFile.fileno(), mmap.PAGESIZE, mmap.MAP_SHARED, mmap.PROT_READ)
if 'win' in sys.platform:
self.shmtag = shmFileName
else:
self.shmFile = open(shmFileName, 'r')
self._view.sceneRendered.connect(mp.proxy(self.remoteSceneChanged)) #, callSync='off'))
## Note: we need synchronous signals
@ -53,11 +55,16 @@ class RemoteGraphicsView(QtGui.QWidget):
return QtCore.QSize(*self._sizeHint)
def remoteSceneChanged(self, data):
w, h, size = data
w, h, size, newfile = data
#self._sizeHint = (whint, hhint)
if self.shm.size != size:
self.shm.close()
self.shm = mmap.mmap(self.shmFile.fileno(), size, mmap.MAP_SHARED, mmap.PROT_READ)
if self.shm is None or self.shm.size != size:
if self.shm is not None:
self.shm.close()
if 'win' in sys.platform:
self.shmtag = newfile ## on windows, we create a new tag for every resize
self.shm = mmap.mmap(-1, size, self.shmtag) ## can't use tmpfile on windows because the file can only be opened once.
else:
self.shm = mmap.mmap(self.shmFile.fileno(), size, mmap.MAP_SHARED, mmap.PROT_READ)
self.shm.seek(0)
self._img = QtGui.QImage(self.shm.read(w*h*4), w, h, QtGui.QImage.Format_ARGB32)
self.update()
@ -112,13 +119,14 @@ class Renderer(GraphicsView):
def __init__(self, *args, **kwds):
## Create shared memory for rendered image
#fd = os.open('/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)
#fh.flush()
fd = self.shmFile.fileno()
self.shm = mmap.mmap(fd, mmap.PAGESIZE, mmap.MAP_SHARED, mmap.PROT_WRITE)
if 'win' in sys.platform:
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
else:
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)
atexit.register(self.close)
GraphicsView.__init__(self, *args, **kwds)
@ -130,10 +138,14 @@ class Renderer(GraphicsView):
def close(self):
self.shm.close()
self.shmFile.close()
if 'win' not in sys.platform:
self.shmFile.close()
def shmFileName(self):
return self.shmFile.name
if 'win' in sys.platform:
return self.shmtag
else:
return self.shmFile.name
def update(self):
self.img = None
@ -152,7 +164,14 @@ class Renderer(GraphicsView):
return
size = self.width() * self.height() * 4
if size > self.shm.size():
self.shm.resize(size)
if 'win' in sys.platform:
## windows says "WindowsError: [Error 87] the parameter is incorrect" if we try to resize the mmap
self.shm.close()
## it also says (sometimes) 'access is denied' if we try to reuse the tag.
self.shmtag = "pyqtgraph_shmem_" + ''.join([chr((random.getrandbits(20)%25) + 97) for i in range(20)])
self.shm = mmap.mmap(-1, size, self.shmtag)
else:
self.shm.resize(size)
address = ctypes.addressof(ctypes.c_char.from_buffer(self.shm, 0))
## render the scene directly to shared memory
@ -161,7 +180,7 @@ class Renderer(GraphicsView):
p = QtGui.QPainter(self.img)
self.render(p, self.viewRect(), self.rect())
p.end()
self.sceneRendered.emit((self.width(), self.height(), self.shm.size()))
self.sceneRendered.emit((self.width(), self.height(), self.shm.size(), self.shmFileName()))
def mousePressEvent(self, typ, pos, gpos, btn, btns, mods):
typ = QtCore.QEvent.Type(typ)
@ -202,4 +221,4 @@ class Renderer(GraphicsView):