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() proc = mp.Process()
import os import os
print "parent:", os.getpid(), "child:", proc.proc.pid print("parent:", os.getpid(), "child:", proc.proc.pid)
print "started" print("started")
rnp = proc._import('numpy') rnp = proc._import('numpy')
arr = rnp.array([1,2,3,4]) arr = rnp.array([1,2,3,4])
print repr(arr) print(repr(arr))
print str(arr) print(str(arr))
print "return value:", repr(arr.mean(_returnType='value')) print("return value:", repr(arr.mean(_returnType='value')))
print "return proxy:", repr(arr.mean(_returnType='proxy')) print( "return proxy:", repr(arr.mean(_returnType='proxy')))
print "return auto: ", repr(arr.mean(_returnType='auto')) print( "return auto: ", repr(arr.mean(_returnType='auto')))
proc.join() proc.join()
print "process finished" print( "process finished")
print "\n=================\nStart ForkedProcess" print( "\n=================\nStart ForkedProcess")
proc = mp.ForkedProcess() proc = mp.ForkedProcess()
rnp = proc._import('numpy') rnp = proc._import('numpy')
arr = rnp.array([1,2,3,4]) arr = rnp.array([1,2,3,4])
print repr(arr) print( repr(arr))
print str(arr) print( str(arr))
print repr(arr.mean()) print( repr(arr.mean()))
proc.join() proc.join()
print "process finished" print( "process finished")
@ -42,10 +42,10 @@ import pyqtgraph as pg
from pyqtgraph.Qt import QtCore, QtGui from pyqtgraph.Qt import QtCore, QtGui
app = pg.QtGui.QApplication([]) app = pg.QtGui.QApplication([])
print "\n=================\nStart QtProcess" print( "\n=================\nStart QtProcess")
import sys import sys
if (sys.flags.interactive != 1): if (sys.flags.interactive != 1):
print " (not interactive; remote process will exit immediately.)" print( " (not interactive; remote process will exit immediately.)")
proc = mp.QtProcess() proc = mp.QtProcess()
d1 = proc.transfer(np.random.normal(size=1000)) d1 = proc.transfer(np.random.normal(size=1000))
d2 = 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 pyqtgraph as pg
import time import time
print "\n=================\nParallelize" print( "\n=================\nParallelize")
## Do a simple task: ## Do a simple task:
## for x in range(N): ## for x in range(N):
@ -36,7 +36,7 @@ with pg.ProgressDialog('processing serially..', maximum=len(tasks)) as dlg:
dlg += 1 dlg += 1
if dlg.wasCanceled(): if dlg.wasCanceled():
raise Exception('processing canceled') 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 ### Use parallelize, but force a single worker
### (this simulates the behavior seen on windows, which lacks os.fork) ### (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): for j in xrange(size):
tot += j * x tot += j * x
tasker.results[i] = tot tasker.results[i] = tot
print "\nParallel time, 1 worker: %0.2f" % (time.time() - start) print( "\nParallel time, 1 worker: %0.2f" % (time.time() - start))
print "Results match serial: ", results2 == results print( "Results match serial: %s" % str(results2 == results))
### Use parallelize with multiple workers ### Use parallelize with multiple workers
start = time.time() start = time.time()
@ -58,6 +58,6 @@ with mp.Parallelize(enumerate(tasks), results=results3, progressDialog='processi
for j in xrange(size): for j in xrange(size):
tot += j * x tot += j * x
tasker.results[i] = tot tasker.results[i] = tot
print "\nParallel time, %d workers: %0.2f" % (mp.Parallelize.suggestedWorkerCount(), time.time() - start) print( "\nParallel time, %d workers: %0.2f" % (mp.Parallelize.suggestedWorkerCount(), time.time() - start))
print "Results match serial: ", results3 == results 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 www.pyqtgraph.org
""" """
__version__ = None __version__ = '0.9.5'
### import all the goodies and add some helper functions for easy CLI use ### 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: #if QtGui.QApplication.instance() is None:
#app = QtGui.QApplication([]) #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 import os, sys
## check python version ## check python version
@ -49,6 +52,8 @@ CONFIG_OPTIONS = {
'background': (0, 0, 0), ## default background for GraphicsWidget 'background': (0, 0, 0), ## default background for GraphicsWidget
'antialias': False, 'antialias': False,
'editorCommand': None, ## command used to invoke code editor from ConsoleWidgets '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: if self.delayed:
self.msgs.append(msg2) self.msgs.append(msg2)
else: else:
print msg2 print(msg2)
self.t0 = ptime.time() self.t0 = ptime.time()
self.t1 = self.t0 self.t1 = self.t0
@ -410,7 +410,7 @@ class Profiler:
if self.delayed: if self.delayed:
self.msgs.append(msg2) self.msgs.append(msg2)
else: else:
print msg2 print(msg2)
self.t1 = ptime.time() ## don't measure time it took to print self.t1 = ptime.time() ## don't measure time it took to print
def finish(self, msg=None): def finish(self, msg=None):
@ -425,10 +425,10 @@ class Profiler:
self.msgs.append(msg) self.msgs.append(msg)
if self.depth == 0: if self.depth == 0:
for line in self.msgs: for line in self.msgs:
print line print(line)
Profiler.msgs = [] Profiler.msgs = []
else: else:
print msg print(msg)
Profiler.depth = self.depth Profiler.depth = self.depth
self.finished = True self.finished = True

View File

@ -220,7 +220,7 @@ def _generateItemSvg(item, nodes=None, root=None):
## get list of sub-groups ## get list of sub-groups
g2 = [n for n in g1.childNodes if isinstance(n, xml.Element) and n.tagName == 'g'] g2 = [n for n in g1.childNodes if isinstance(n, xml.Element) and n.tagName == 'g']
except: except:
print doc.toxml() print(doc.toxml())
raise 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 text = fn + "\n".join([" "+l for l in str(self.text.toPlainText()).split('\n')]) + run
exec(text) exec(text)
except: except:
print "Error processing node:", self.name() print("Error processing node: %s" % self.name())
raise raise
return output return output

View File

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

View File

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

View File

@ -1783,8 +1783,7 @@ class LineSegmentROI(ROI):
dh = h2-h1 dh = h2-h1
if dh.length() == 0: if dh.length() == 0:
return p return p
pxv = self.pixelVectors(h2-h1)[1] pxv = self.pixelVectors(dh)[1]
if pxv is None: if pxv is None:
return p return p
@ -1809,7 +1808,7 @@ class LineSegmentROI(ROI):
for i in range(len(imgPts)-1): for i in range(len(imgPts)-1):
d = Point(imgPts[i+1] - imgPts[i]) d = Point(imgPts[i+1] - imgPts[i])
o = Point(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) rgns.append(r)
return np.concatenate(rgns, axis=axes[0]) return np.concatenate(rgns, axis=axes[0])

View File

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

View File

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

View File

@ -2,15 +2,23 @@
import sys, pickle, os import sys, pickle, os
if __name__ == '__main__': if __name__ == '__main__':
if hasattr(os, 'setpgrp'):
os.setpgrp() ## prevents signals (notably keyboard interrupt) being forwarded from parent to this process os.setpgrp() ## prevents signals (notably keyboard interrupt) being forwarded from parent to this process
name, port, authkey, targetStr, path = pickle.load(sys.stdin) 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: if path is not None:
## rewrite sys.path without assigning a new object--no idea who already has a reference to the existing list. ## rewrite sys.path without assigning a new object--no idea who already has a reference to the existing list.
while len(sys.path) > 0: while len(sys.path) > 0:
sys.path.pop() sys.path.pop()
sys.path.extend(path) sys.path.extend(path)
if pyside:
import PySide
#import pyqtgraph #import pyqtgraph
#import pyqtgraph.multiprocess.processes #import pyqtgraph.multiprocess.processes
target = pickle.loads(targetStr) ## unpickling the target should import everything we need target = pickle.loads(targetStr) ## unpickling the target should import everything we need
target(name, port, authkey) target(name, port, authkey, ppid)
sys.exit(0) sys.exit(0)

View File

@ -1,6 +1,6 @@
import os, sys, time, multiprocessing, re import os, sys, time, multiprocessing, re
from processes import ForkedProcess from .processes import ForkedProcess
from remoteproxy import ClosedError from .remoteproxy import ClosedError
class CanceledError(Exception): class CanceledError(Exception):
"""Raised when the progress dialog is canceled during a processing operation.""" """Raised when the progress dialog is canceled during a processing operation."""
@ -19,7 +19,7 @@ class Parallelize(object):
for task in tasks: for task in tasks:
result = processTask(task) result = processTask(task)
results.append(result) results.append(result)
print results print(results)
## Here is the parallelized version: ## Here is the parallelized version:
@ -30,7 +30,7 @@ class Parallelize(object):
for task in tasker: for task in tasker:
result = processTask(task) result = processTask(task)
tasker.results.append(result) tasker.results.append(result)
print results print(results)
The only major caveat is that *result* in the example above must be picklable, 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 subprocess, atexit, os, sys, time, random, socket, signal
import cPickle as pickle
import multiprocessing.connection import multiprocessing.connection
from pyqtgraph.Qt import USE_PYSIDE
try:
import cPickle as pickle
except ImportError:
import pickle
__all__ = ['Process', 'QtProcess', 'ForkedProcess', 'ClosedError', 'NoResultError'] __all__ = ['Process', 'QtProcess', 'ForkedProcess', 'ClosedError', 'NoResultError']
@ -54,12 +58,13 @@ class Process(RemoteEventHandler):
executable = sys.executable executable = sys.executable
## random authentication key ## 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) ## Listen for connection from remote process (and find free port number)
port = 10000 port = 10000
while True: while True:
try: try:
## hmac authentication appears to be broken on windows (says AuthenticationError: digest received was wrong)
l = multiprocessing.connection.Listener(('localhost', int(port)), authkey=authkey) l = multiprocessing.connection.Listener(('localhost', int(port)), authkey=authkey)
break break
except socket.error as ex: except socket.error as ex:
@ -73,7 +78,11 @@ class Process(RemoteEventHandler):
self.proc = subprocess.Popen((executable, bootstrap), stdin=subprocess.PIPE) self.proc = subprocess.Popen((executable, bootstrap), stdin=subprocess.PIPE)
targetStr = pickle.dumps(target) ## double-pickle target so that child has a chance to targetStr = pickle.dumps(target) ## double-pickle target so that child has a chance to
## set its sys.path properly before unpickling the target ## 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() self.proc.stdin.close()
## open connection for remote process ## open connection for remote process
@ -92,10 +101,11 @@ class Process(RemoteEventHandler):
time.sleep(0.05) time.sleep(0.05)
def startEventLoop(name, port, authkey): def startEventLoop(name, port, authkey, ppid):
conn = multiprocessing.connection.Client(('localhost', int(port)), authkey=authkey) conn = multiprocessing.connection.Client(('localhost', int(port)), authkey=authkey)
global HANDLER 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: while True:
try: try:
HANDLER.processRequests() # exception raised when the loop should exit HANDLER.processRequests() # exception raised when the loop should exit
@ -161,6 +171,7 @@ class ForkedProcess(RemoteEventHandler):
proxyId = LocalObjectProxy.registerObject(v) proxyId = LocalObjectProxy.registerObject(v)
proxyIDs[k] = proxyId proxyIDs[k] = proxyId
ppid = os.getpid() # write this down now; windows doesn't have getppid
pid = os.fork() pid = os.fork()
if pid == 0: if pid == 0:
self.isParent = False self.isParent = False
@ -200,9 +211,9 @@ class ForkedProcess(RemoteEventHandler):
if 'random' in sys.modules: if 'random' in sys.modules:
sys.modules['random'].seed(os.getpid() ^ int(time.time()*10000%10000)) 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 = {} self.forkedProxies = {}
for name, proxyId in proxyIDs.iteritems(): for name, proxyId in proxyIDs.iteritems():
self.forkedProxies[name] = ObjectProxy(ppid, proxyId=proxyId, typeStr=repr(preProxy[name])) self.forkedProxies[name] = ObjectProxy(ppid, proxyId=proxyId, typeStr=repr(preProxy[name]))
@ -228,7 +239,7 @@ class ForkedProcess(RemoteEventHandler):
except ClosedError: except ClosedError:
break break
except: except:
print "Error occurred in forked event loop:" print("Error occurred in forked event loop:")
sys.excepthook(*sys.exc_info()) sys.excepthook(*sys.exc_info())
sys.exit(0) sys.exit(0)
@ -293,7 +304,7 @@ class QtProcess(Process):
btn.show() btn.show()
def slot(): 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 btn.clicked.connect(proxy(slot)) # be sure to send a proxy of the slot
""" """
@ -318,7 +329,7 @@ class QtProcess(Process):
except ClosedError: except ClosedError:
self.timer.stop() self.timer.stop()
def startQtEventLoop(name, port, authkey): def startQtEventLoop(name, port, authkey, ppid):
conn = multiprocessing.connection.Client(('localhost', int(port)), authkey=authkey) conn = multiprocessing.connection.Client(('localhost', int(port)), authkey=authkey)
from pyqtgraph.Qt import QtGui, QtCore from pyqtgraph.Qt import QtGui, QtCore
#from PyQt4 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. ## until it is explicitly closed by the parent process.
global HANDLER 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() HANDLER.startEventTimer()
app.exec_() app.exec_()

View File

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

View File

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

View File

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

View File

@ -3,7 +3,7 @@ import pyqtgraph.multiprocess as mp
import pyqtgraph as pg import pyqtgraph as pg
from .GraphicsView import GraphicsView from .GraphicsView import GraphicsView
import numpy as np import numpy as np
import mmap, tempfile, ctypes, atexit import mmap, tempfile, ctypes, atexit, sys, random
__all__ = ['RemoteGraphicsView'] __all__ = ['RemoteGraphicsView']
@ -27,13 +27,15 @@ class RemoteGraphicsView(QtGui.QWidget):
rpgRemote = self._proc._import('pyqtgraph.widgets.RemoteGraphicsView') rpgRemote = self._proc._import('pyqtgraph.widgets.RemoteGraphicsView')
self._view = rpgRemote.Renderer(*args, **kwds) self._view = rpgRemote.Renderer(*args, **kwds)
self._view._setProxyOptions(deferGetattr=True) 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.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding)
self.setMouseTracking(True) self.setMouseTracking(True)
self.shm = None
shmFileName = self._view.shmFileName() shmFileName = self._view.shmFileName()
if 'win' in sys.platform:
self.shmtag = shmFileName
else:
self.shmFile = open(shmFileName, 'r') self.shmFile = open(shmFileName, 'r')
self.shm = mmap.mmap(self.shmFile.fileno(), mmap.PAGESIZE, mmap.MAP_SHARED, mmap.PROT_READ)
self._view.sceneRendered.connect(mp.proxy(self.remoteSceneChanged)) #, callSync='off')) self._view.sceneRendered.connect(mp.proxy(self.remoteSceneChanged)) #, callSync='off'))
## Note: we need synchronous signals ## Note: we need synchronous signals
@ -53,10 +55,15 @@ class RemoteGraphicsView(QtGui.QWidget):
return QtCore.QSize(*self._sizeHint) return QtCore.QSize(*self._sizeHint)
def remoteSceneChanged(self, data): def remoteSceneChanged(self, data):
w, h, size = data w, h, size, newfile = data
#self._sizeHint = (whint, hhint) #self._sizeHint = (whint, hhint)
if self.shm.size != size: if self.shm is None or self.shm.size != size:
if self.shm is not None:
self.shm.close() 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 = mmap.mmap(self.shmFile.fileno(), size, mmap.MAP_SHARED, mmap.PROT_READ)
self.shm.seek(0) self.shm.seek(0)
self._img = QtGui.QImage(self.shm.read(w*h*4), w, h, QtGui.QImage.Format_ARGB32) self._img = QtGui.QImage(self.shm.read(w*h*4), w, h, QtGui.QImage.Format_ARGB32)
@ -112,11 +119,12 @@ class Renderer(GraphicsView):
def __init__(self, *args, **kwds): def __init__(self, *args, **kwds):
## Create shared memory for rendered image ## Create shared memory for rendered image
#fd = os.open('/tmp/mmaptest', os.O_CREAT | os.O_TRUNC | os.O_RDWR) if 'win' in sys.platform:
#os.write(fd, '\x00' * mmap.PAGESIZE) 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 = tempfile.NamedTemporaryFile(prefix='pyqtgraph_shmem_')
self.shmFile.write('\x00' * mmap.PAGESIZE) self.shmFile.write('\x00' * mmap.PAGESIZE)
#fh.flush()
fd = self.shmFile.fileno() fd = self.shmFile.fileno()
self.shm = mmap.mmap(fd, mmap.PAGESIZE, mmap.MAP_SHARED, mmap.PROT_WRITE) self.shm = mmap.mmap(fd, mmap.PAGESIZE, mmap.MAP_SHARED, mmap.PROT_WRITE)
atexit.register(self.close) atexit.register(self.close)
@ -130,9 +138,13 @@ class Renderer(GraphicsView):
def close(self): def close(self):
self.shm.close() self.shm.close()
if 'win' not in sys.platform:
self.shmFile.close() self.shmFile.close()
def shmFileName(self): def shmFileName(self):
if 'win' in sys.platform:
return self.shmtag
else:
return self.shmFile.name return self.shmFile.name
def update(self): def update(self):
@ -152,6 +164,13 @@ class Renderer(GraphicsView):
return return
size = self.width() * self.height() * 4 size = self.width() * self.height() * 4
if size > self.shm.size(): if size > self.shm.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) self.shm.resize(size)
address = ctypes.addressof(ctypes.c_char.from_buffer(self.shm, 0)) address = ctypes.addressof(ctypes.c_char.from_buffer(self.shm, 0))
@ -161,7 +180,7 @@ class Renderer(GraphicsView):
p = QtGui.QPainter(self.img) p = QtGui.QPainter(self.img)
self.render(p, self.viewRect(), self.rect()) self.render(p, self.viewRect(), self.rect())
p.end() 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): def mousePressEvent(self, typ, pos, gpos, btn, btns, mods):
typ = QtCore.QEvent.Type(typ) typ = QtCore.QEvent.Type(typ)