diff --git a/GraphicsScene/GraphicsScene.py b/GraphicsScene/GraphicsScene.py index cd3c2350..c80e97e5 100644 --- a/GraphicsScene/GraphicsScene.py +++ b/GraphicsScene/GraphicsScene.py @@ -1,11 +1,17 @@ -from pyqtgraph.Qt import QtCore, QtGui, QtOpenGL, QtSvg +from pyqtgraph.Qt import QtCore, QtGui +#try: + #from PyQt4 import QtOpenGL + #HAVE_OPENGL = True +#except ImportError: + #HAVE_OPENGL = False + import weakref from pyqtgraph.Point import Point import pyqtgraph.functions as fn import pyqtgraph.ptime as ptime -from mouseEvents import * +from .mouseEvents import * import pyqtgraph.debug as debug -import exportDialog +from . import exportDialog try: import sip @@ -216,7 +222,7 @@ class GraphicsScene(QtGui.QGraphicsScene): items = self.itemsNearEvent(event) self.sigMouseHover.emit(items) - prevItems = self.hoverItems.keys() + prevItems = list(self.hoverItems.keys()) for item in items: if hasattr(item, 'hoverEvent'): @@ -352,7 +358,7 @@ class GraphicsScene(QtGui.QGraphicsScene): items = QtGui.QGraphicsScene.items(self, *args) ## PyQt bug: items() returns a list of QGraphicsItem instances. If the item is subclassed from QGraphicsObject, ## then the object returned will be different than the actual item that was originally added to the scene - items2 = map(self.translateGraphicsItem, items) + items2 = list(map(self.translateGraphicsItem, items)) #if HAVE_SIP and isinstance(self, sip.wrapper): #items2 = [] #for i in items: @@ -374,7 +380,7 @@ class GraphicsScene(QtGui.QGraphicsScene): #i2 = GraphicsScene._addressCache.get(addr, i) ##print i, "==>", i2 #items2.append(i2) - items2 = map(self.translateGraphicsItem, items) + items2 = list(map(self.translateGraphicsItem, items)) #print 'items:', items return items2 @@ -430,7 +436,7 @@ class GraphicsScene(QtGui.QGraphicsScene): return 0 return item.zValue() + absZValue(item.parentItem()) - items2.sort(lambda a,b: cmp(absZValue(b), absZValue(a))) + sortList(items2, lambda a,b: cmp(absZValue(b), absZValue(a))) return items2 @@ -543,7 +549,7 @@ class GraphicsScene(QtGui.QGraphicsScene): @staticmethod def translateGraphicsItems(items): - return map(GraphicsScene.translateGraphicsItem, items) + return list(map(GraphicsScene.translateGraphicsItem, items)) diff --git a/GraphicsScene/__init__.py b/GraphicsScene/__init__.py index ed246252..abe42c6f 100644 --- a/GraphicsScene/__init__.py +++ b/GraphicsScene/__init__.py @@ -1 +1 @@ -from GraphicsScene import * +from .GraphicsScene import * diff --git a/GraphicsScene/exportDialog.py b/GraphicsScene/exportDialog.py index 72809d44..227926c1 100644 --- a/GraphicsScene/exportDialog.py +++ b/GraphicsScene/exportDialog.py @@ -1,4 +1,4 @@ -import exportDialogTemplate +from . import exportDialogTemplate from pyqtgraph.Qt import QtCore, QtGui import pyqtgraph as pg import pyqtgraph.exporters as exporters diff --git a/Point.py b/Point.py index 68980745..ea35d119 100644 --- a/Point.py +++ b/Point.py @@ -5,7 +5,7 @@ Copyright 2010 Luke Campagnola Distributed under MIT/X11 license. See license.txt for more infomation. """ -from Qt import QtCore +from .Qt import QtCore import numpy as np def clip(x, mn, mx): diff --git a/Qt.py b/Qt.py index eb63b7c1..89bf00d6 100644 --- a/Qt.py +++ b/Qt.py @@ -1,6 +1,15 @@ ## Do all Qt imports from here to allow easier PyQt / PySide compatibility #from PySide import QtGui, QtCore, QtOpenGL, QtSvg -from PyQt4 import QtGui, QtCore, QtOpenGL, QtSvg +from PyQt4 import QtGui, QtCore +try: + from PyQt4 import QtSvg +except ImportError: + pass +try: + from PyQt4 import QtOpenGL +except ImportError: + pass + if not hasattr(QtCore, 'Signal'): QtCore.Signal = QtCore.pyqtSignal diff --git a/SignalProxy.py b/SignalProxy.py index a4ace84f..6f9b9112 100644 --- a/SignalProxy.py +++ b/SignalProxy.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- -from Qt import QtCore -from ptime import time -import ThreadsafeTimer +from .Qt import QtCore +from .ptime import time +from . import ThreadsafeTimer __all__ = ['SignalProxy'] @@ -99,7 +99,7 @@ class SignalProxy(QtCore.QObject): if __name__ == '__main__': - from Qt import QtGui + from .Qt import QtGui app = QtGui.QApplication([]) win = QtGui.QMainWindow() spin = QtGui.QSpinBox() @@ -107,9 +107,9 @@ if __name__ == '__main__': win.show() def fn(*args): - print "Raw signal:", args + print("Raw signal:", args) def fn2(*args): - print "Delayed signal:", args + print("Delayed signal:", args) spin.valueChanged.connect(fn) diff --git a/Transform.py b/Transform.py index a7776696..616b28a0 100644 --- a/Transform.py +++ b/Transform.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -from Qt import QtCore, QtGui -from Point import Point +from .Qt import QtCore, QtGui +from .Point import Point import numpy as np class Transform(QtGui.QTransform): @@ -141,9 +141,9 @@ class Transform(QtGui.QTransform): return np.array([[self.m11(), self.m12(), self.m13()],[self.m21(), self.m22(), self.m23()],[self.m31(), self.m32(), self.m33()]]) if __name__ == '__main__': - import widgets + from . import widgets import GraphicsView - from functions import * + from .functions import * app = QtGui.QApplication([]) win = QtGui.QMainWindow() win.show() @@ -189,23 +189,23 @@ if __name__ == '__main__': tr3 = QtGui.QTransform() tr3.translate(20, 0) tr3.rotate(45) - print "QTransform -> Transform:", Transform(tr3) + print("QTransform -> Transform:", Transform(tr3)) - print "tr1:", tr1 + print("tr1:", tr1) tr2.translate(20, 0) tr2.rotate(45) - print "tr2:", tr2 + print("tr2:", tr2) dt = tr2/tr1 - print "tr2 / tr1 = ", dt + print("tr2 / tr1 = ", dt) - print "tr2 * tr1 = ", tr2*tr1 + print("tr2 * tr1 = ", tr2*tr1) tr4 = Transform() tr4.scale(-1, 1) tr4.rotate(30) - print "tr1 * tr4 = ", tr1*tr4 + print("tr1 * tr4 = ", tr1*tr4) w1 = widgets.TestROI((19,19), (22, 22), invertible=True) #w2 = widgets.TestROI((0,0), (150, 150)) diff --git a/WidgetGroup.py b/WidgetGroup.py index 48a3778e..6e183b16 100644 --- a/WidgetGroup.py +++ b/WidgetGroup.py @@ -8,7 +8,7 @@ This class addresses the problem of having to save and restore the state of a large group of widgets. """ -from Qt import QtCore, QtGui +from .Qt import QtCore, QtGui import weakref, inspect @@ -24,7 +24,7 @@ def restoreSplitter(w, s): elif type(s) is str: w.restoreState(QtCore.QByteArray.fromPercentEncoding(s)) else: - print "Can't configure QSplitter using object of type", type(s) + print("Can't configure QSplitter using object of type", type(s)) if w.count() > 0: ## make sure at least one item is not collapsed for i in w.sizes(): if i > 0: @@ -44,7 +44,7 @@ def comboState(w): except AttributeError: pass if data is None: - return unicode(w.itemText(ind)) + return asUnicode(w.itemText(ind)) else: return data @@ -133,7 +133,7 @@ class WidgetGroup(QtCore.QObject): for w in widgetList: self.addWidget(*w) elif isinstance(widgetList, dict): - for name, w in widgetList.iteritems(): + for name, w in widgetList.items(): self.addWidget(w, name) elif widgetList is None: return @@ -258,7 +258,7 @@ class WidgetGroup(QtCore.QObject): ## if the getter function provided in the interface is a bound method, ## then just call the method directly. Otherwise, pass in the widget as the first arg ## to the function. - if inspect.ismethod(getFunc) and getFunc.im_self is not None: + if inspect.ismethod(getFunc) and getFunc.__self__ is not None: val = getFunc() else: val = getFunc(w) @@ -284,7 +284,7 @@ class WidgetGroup(QtCore.QObject): ## if the setter function provided in the interface is a bound method, ## then just call the method directly. Otherwise, pass in the widget as the first arg ## to the function. - if inspect.ismethod(setFunc) and setFunc.im_self is not None: + if inspect.ismethod(setFunc) and setFunc.__self__ is not None: setFunc(v) else: setFunc(w, v) diff --git a/__init__.py b/__init__.py index 2f5e781c..6cdb82af 100644 --- a/__init__.py +++ b/__init__.py @@ -3,21 +3,25 @@ ## 'Qt' is a local module; it is intended mainly to cover up the differences ## between PyQt4 and PySide. -from Qt import QtGui +from .Qt import QtGui -## not really safe. +## not really safe--If we accidentally create another QApplication, the process hangs (and it is very difficult to trace the cause) #if QtGui.QApplication.instance() is None: #app = QtGui.QApplication([]) -## in general openGL is poorly supported in Qt. -## we only enable it where the performance benefit is critical. -## Note this only applies to 2D graphics; 3D graphics always use OpenGL. import sys ## check python version -if sys.version_info[0] != 2 or sys.version_info[1] != 7: +if sys.version_info[0] < 2 or (sys.version_info[0] == 2 and sys.version_info[1] != 7): raise Exception("Pyqtgraph requires Python version 2.7 (this is %d.%d)" % (sys.version_info[0], sys.version_info[1])) +## helpers for 2/3 compatibility +from . import python2_3 + + +## in general openGL is poorly supported in Qt. +## we only enable it where the performance benefit is critical. +## Note this only applies to 2D graphics; 3D graphics always use OpenGL. if 'linux' in sys.platform: ## linux has numerous bugs in opengl implementation useOpenGL = False elif 'darwin' in sys.platform: ## openGL greatly speeds up display on mac @@ -27,8 +31,11 @@ else: CONFIG_OPTIONS = { 'useOpenGL': useOpenGL, ## by default, this is platform-dependent (see widgets/GraphicsView). Set to True or False to explicitly enable/disable opengl. - 'leftButtonPan': True ## if false, left button drags a rubber band for zooming in viewbox -} + 'leftButtonPan': True, ## if false, left button drags a rubber band for zooming in viewbox + 'foregroundColor': (200,200,200), + 'backgroundColor': (0,0,0), + 'antialias': False, +} def setConfigOption(opt, value): CONFIG_OPTIONS[opt] = value @@ -47,13 +54,15 @@ def renamePyc(startDir): printed = False startDir = os.path.abspath(startDir) for path, dirs, files in os.walk(startDir): + if '__pycache__' in path: + continue for f in files: fileName = os.path.join(path, f) base, ext = os.path.splitext(fileName) py = base + ".py" if ext == '.pyc' and not os.path.isfile(py): if not printed: - print "NOTE: Renaming orphaned .pyc files:" + print("NOTE: Renaming orphaned .pyc files:") printed = True n = 1 while True: @@ -61,8 +70,8 @@ def renamePyc(startDir): if not os.path.exists(name2): break n += 1 - print " " + fileName + " ==>" - print " " + name2 + print(" " + fileName + " ==>") + print(" " + name2) os.rename(fileName, name2) import os @@ -78,7 +87,7 @@ def importAll(path, excludes=()): d = os.path.join(os.path.split(__file__)[0], path) files = [] for f in os.listdir(d): - if os.path.isdir(os.path.join(d, f)): + if os.path.isdir(os.path.join(d, f)) and f != '__pycache__': files.append(f) elif f[-3:] == '.py' and f != '__init__.py': files.append(f[:-3]) @@ -98,14 +107,14 @@ def importAll(path, excludes=()): importAll('graphicsItems') importAll('widgets', excludes=['MatplotlibWidget']) -from imageview import * -from WidgetGroup import * -from Point import Point -from Transform import Transform -from functions import * -from graphicsWindows import * -from SignalProxy import * -from ptime import time +from .imageview import * +from .WidgetGroup import * +from .Point import Point +from .Transform import Transform +from .functions import * +from .graphicsWindows import * +from .SignalProxy import * +from .ptime import time diff --git a/canvas/Canvas.py b/canvas/Canvas.py index 75f19502..b7a27105 100644 --- a/canvas/Canvas.py +++ b/canvas/Canvas.py @@ -5,7 +5,7 @@ if __name__ == '__main__': sys.path = [os.path.dirname(md), os.path.join(md, '..', '..', '..')] + sys.path #print md -from CanvasTemplate import * +from .CanvasTemplate import * #from pyqtgraph.GraphicsView import GraphicsView #import pyqtgraph.graphicsItems as graphicsItems #from pyqtgraph.PlotWidget import PlotWidget @@ -18,9 +18,9 @@ import numpy as np from pyqtgraph import debug #import pyqtgraph as pg import weakref -from CanvasManager import CanvasManager +from .CanvasManager import CanvasManager #import items -from CanvasItem import CanvasItem, GroupCanvasItem +from .CanvasItem import CanvasItem, GroupCanvasItem class Canvas(QtGui.QWidget): @@ -154,7 +154,7 @@ class Canvas(QtGui.QWidget): if parent is None: tree = li.treeWidget() if tree is None: - print "Skipping item", i, i.name + print("Skipping item", i, i.name) continue tree.removeTopLevelItem(li) else: @@ -365,7 +365,7 @@ class Canvas(QtGui.QWidget): parent = parent.listItem ## set Z value above all other siblings if none was specified - siblings = [parent.child(i).canvasItem for i in xrange(parent.childCount())] + siblings = [parent.child(i).canvasItem for i in range(parent.childCount())] z = citem.zValue() if z is None: zvals = [i.zValue() for i in siblings] @@ -462,7 +462,7 @@ class Canvas(QtGui.QWidget): item.canvasItem.setParentItem(self.view.childGroup) else: item.canvasItem.setParentItem(parent.canvasItem) - siblings = [parent.child(i).canvasItem for i in xrange(parent.childCount())] + siblings = [parent.child(i).canvasItem for i in range(parent.childCount())] zvals = [i.zValue() for i in siblings] zvals.sort(reverse=True) diff --git a/canvas/CanvasItem.py b/canvas/CanvasItem.py index 67b442af..1c89e87a 100644 --- a/canvas/CanvasItem.py +++ b/canvas/CanvasItem.py @@ -2,7 +2,7 @@ from pyqtgraph.Qt import QtGui, QtCore, QtSvg from pyqtgraph.graphicsItems.ROI import ROI import pyqtgraph as pg -import TransformGuiTemplate +from . import TransformGuiTemplate from pyqtgraph import debug class SelectBox(ROI): diff --git a/canvas/CanvasManager.py b/canvas/CanvasManager.py index 0c64e274..e89ec00f 100644 --- a/canvas/CanvasManager.py +++ b/canvas/CanvasManager.py @@ -36,7 +36,7 @@ class CanvasManager(QtCore.QObject): self.sigCanvasListChanged.emit() def listCanvases(self): - return self.canvases.keys() + return list(self.canvases.keys()) def getCanvas(self, name): return self.canvases[name] diff --git a/canvas/CanvasTemplate.py b/canvas/CanvasTemplate.py index 3fe789c0..40a2511b 100644 --- a/canvas/CanvasTemplate.py +++ b/canvas/CanvasTemplate.py @@ -96,5 +96,5 @@ class Ui_Form(object): self.reflectSelectionBtn.setText(QtGui.QApplication.translate("Form", "MirrorXY", None, QtGui.QApplication.UnicodeUTF8)) from pyqtgraph.widgets.GraphicsView import GraphicsView -from CanvasManager import CanvasCombo +from .CanvasManager import CanvasCombo from pyqtgraph.widgets.TreeWidget import TreeWidget diff --git a/canvas/__init__.py b/canvas/__init__.py index d7d3058e..f649d0a1 100644 --- a/canvas/__init__.py +++ b/canvas/__init__.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- -from Canvas import * -from CanvasItem import * \ No newline at end of file +from .Canvas import * +from .CanvasItem import * \ No newline at end of file diff --git a/configfile.py b/configfile.py index 6b01d0cf..f91774aa 100644 --- a/configfile.py +++ b/configfile.py @@ -12,7 +12,7 @@ as it can be converted to/from a string using repr and eval. import re, os, sys from collections import OrderedDict GLOBAL_PATH = None # so not thread safe. -import units +from . import units class ParseError(Exception): def __init__(self, message, lineNum, line, fileName=None): @@ -51,7 +51,7 @@ def readConfigFile(fname): try: #os.chdir(newDir) ## bad. fd = open(fname) - s = unicode(fd.read(), 'UTF-8') + s = asUnicode(fd.read(), 'UTF-8') fd.close() s = s.replace("\r\n", "\n") s = s.replace("\r", "\n") @@ -60,7 +60,7 @@ def readConfigFile(fname): sys.exc_info()[1].fileName = fname raise except: - print "Error while reading config file %s:"% fname + print("Error while reading config file %s:"% fname) raise #finally: #os.chdir(cwd) @@ -78,10 +78,10 @@ def genString(data, indent=''): for k in data: sk = str(k) if len(sk) == 0: - print data + print(data) raise Exception('blank dict keys not allowed (see data above)') if sk[0] == ' ' or ':' in sk: - print data + print(data) raise Exception('dict keys must not contain ":" or start with spaces [offending key is "%s"]' % sk) if isinstance(data[k], dict): s += indent + sk + ':\n' @@ -95,7 +95,7 @@ def parseString(lines, start=0): data = OrderedDict() if isinstance(lines, basestring): lines = lines.split('\n') - lines = filter(lambda l: re.search(r'\S', l) and not re.match(r'\s*#', l), lines) ## remove empty lines + lines = [l for l in lines if re.search(r'\S', l) and not re.match(r'\s*#', l)] ## remove empty lines indent = measureIndent(lines[start]) ln = start - 1 @@ -189,13 +189,13 @@ key2: ##comment """ tf.write(cf) tf.close() - print "=== Test:===" + print("=== Test:===") num = 1 for line in cf.split('\n'): - print "%02d %s" % (num, line) + print("%02d %s" % (num, line)) num += 1 - print cf - print "============" + print(cf) + print("============") data = readConfigFile(fn) - print data + print(data) os.remove(fn) \ No newline at end of file diff --git a/debug.py b/debug.py index 13fdbee4..ba1eb4ed 100644 --- a/debug.py +++ b/debug.py @@ -6,9 +6,9 @@ Distributed under MIT/X11 license. See license.txt for more infomation. """ import sys, traceback, time, gc, re, types, weakref, inspect, os, cProfile -import ptime +from . import ptime from numpy import ndarray -from Qt import QtCore, QtGui +from .Qt import QtCore, QtGui __ftraceDepth = 0 def ftrace(func): @@ -18,13 +18,13 @@ def ftrace(func): def w(*args, **kargs): global __ftraceDepth pfx = " " * __ftraceDepth - print pfx + func.__name__ + " start" + print(pfx + func.__name__ + " start") __ftraceDepth += 1 try: rv = func(*args, **kargs) finally: __ftraceDepth -= 1 - print pfx + func.__name__ + " done" + print(pfx + func.__name__ + " done") return rv return w @@ -39,20 +39,20 @@ def printExc(msg='', indent=4, prefix='|'): """Print an error message followed by an indented exception backtrace (This function is intended to be called within except: blocks)""" exc = getExc(indent, prefix + ' ') - print "[%s] %s\n" % (time.strftime("%H:%M:%S"), msg) - print " "*indent + prefix + '='*30 + '>>' - print exc - print " "*indent + prefix + '='*30 + '<<' + print("[%s] %s\n" % (time.strftime("%H:%M:%S"), msg)) + print(" "*indent + prefix + '='*30 + '>>') + print(exc) + print(" "*indent + prefix + '='*30 + '<<') def printTrace(msg='', indent=4, prefix='|'): """Print an error message followed by an indented stack trace""" trace = backtrace(1) #exc = getExc(indent, prefix + ' ') - print "[%s] %s\n" % (time.strftime("%H:%M:%S"), msg) - print " "*indent + prefix + '='*30 + '>>' + print("[%s] %s\n" % (time.strftime("%H:%M:%S"), msg)) + print(" "*indent + prefix + '='*30 + '>>') for line in trace.split('\n'): - print " "*indent + prefix + " " + line - print " "*indent + prefix + '='*30 + '<<' + print(" "*indent + prefix + " " + line) + print(" "*indent + prefix + '='*30 + '<<') def backtrace(skip=0): @@ -107,12 +107,12 @@ def findRefPath(startObj, endObj, maxLen=8, restart=True, seen={}, path=None, ig #print prefix+" LOOP", objChainString([r]+path) continue except: - print r - print path + print(r) + print(path) raise if r is startObj: refs.append([r]) - print refPathString([startObj]+path) + print(refPathString([startObj]+path)) continue if maxLen == 0: #print prefix+" END:", objChainString([r]+path) @@ -125,7 +125,7 @@ def findRefPath(startObj, endObj, maxLen=8, restart=True, seen={}, path=None, ig if cache[0] >= maxLen: tree = cache[1] for p in tree: - print refPathString(p+path) + print(refPathString(p+path)) except KeyError: pass @@ -147,14 +147,14 @@ def findRefPath(startObj, endObj, maxLen=8, restart=True, seen={}, path=None, ig def objString(obj): """Return a short but descriptive string for any object""" try: - if type(obj) in [int, long, float]: + if type(obj) in [int, float]: return str(obj) elif isinstance(obj, dict): if len(obj) > 5: - return "" % (",".join(obj.keys()[:5])) + return "" % (",".join(list(obj.keys())[:5])) else: - return "" % (",".join(obj.keys())) - elif isinstance(obj, basestring): + return "" % (",".join(list(obj.keys()))) + elif isinstance(obj, str): if len(obj) > 50: return '"%s..."' % obj[:50] else: @@ -261,19 +261,19 @@ def objectSize(obj, ignore=None, verbose=False, depth=0, recursive=False): if recursive: if type(obj) in [list, tuple]: if verbose: - print indent+"list:" + print(indent+"list:") for o in obj: s = objectSize(o, ignore=ignore, verbose=verbose, depth=depth+1) if verbose: - print indent+' +', s + print(indent+' +', s) size += s elif isinstance(obj, dict): if verbose: - print indent+"list:" + print(indent+"list:") for k in obj: s = objectSize(obj[k], ignore=ignore, verbose=verbose, depth=depth+1) if verbose: - print indent+' +', k, s + print(indent+' +', k, s) size += s #elif isinstance(obj, QtCore.QObject): #try: @@ -291,7 +291,7 @@ def objectSize(obj, ignore=None, verbose=False, depth=0, recursive=False): #if isinstance(obj, types.InstanceType): gc.collect() if verbose: - print indent+'attrs:' + print(indent+'attrs:') for k in dir(obj): if k in ['__dict__']: continue @@ -311,7 +311,7 @@ def objectSize(obj, ignore=None, verbose=False, depth=0, recursive=False): s = objectSize(o, ignore=ignore, verbose=verbose, depth=depth+1) size += s if verbose: - print indent + " +", k, s + print(indent + " +", k, s) #else: #if verbose: #print indent + ' -', k, len(refs) @@ -349,8 +349,8 @@ class GarbageWatcher: for k in self.objs: dead.remove(k) alive.append(k) - print "Deleted objects:", dead - print "Live objects:", alive + print("Deleted objects:", dead) + print("Live objects:", alive) def __getitem__(self, item): return self.objs[item] @@ -379,20 +379,20 @@ class Profiler: self.t0 = ptime.time() self.t1 = self.t0 self.msg = " "*self.depth + msg - print self.msg, ">>> Started" + print(self.msg, ">>> Started") def mark(self, msg=''): if self.disabled: return t1 = ptime.time() - print " "+self.msg, msg, "%gms" % ((t1-self.t1)*1000) + print(" "+self.msg, msg, "%gms" % ((t1-self.t1)*1000)) self.t1 = t1 def finish(self): if self.disabled: return t1 = ptime.time() - print self.msg, '<<< Finished, total time:', "%gms" % ((t1-self.t0)*1000) + print(self.msg, '<<< Finished, total time:', "%gms" % ((t1-self.t0)*1000)) def __del__(self): Profiler.depth -= 1 @@ -419,7 +419,7 @@ def _getr(slist, olist, first=True): oid = id(e) typ = type(e) - if oid in olist or typ is int or typ is long: ## or e in olist: ## since we're excluding all ints, there is no longer a need to check for olist keys + if oid in olist or typ is int: ## or e in olist: ## since we're excluding all ints, there is no longer a need to check for olist keys continue olist[oid] = e if first and (i%1000) == 0: @@ -565,23 +565,23 @@ class ObjTracker: self.persistentRefs.update(persistentRefs) - print "----------- Count changes since start: ----------" + print("----------- Count changes since start: ----------") c1 = count.copy() for k in self.startCount: c1[k] = c1.get(k, 0) - self.startCount[k] - typs = c1.keys() + typs = list(c1.keys()) typs.sort(lambda a,b: cmp(c1[a], c1[b])) for t in typs: if c1[t] == 0: continue num = "%d" % c1[t] - print " " + num + " "*(10-len(num)) + str(t) + print(" " + num + " "*(10-len(num)) + str(t)) - print "----------- %d Deleted since last diff: ------------" % len(delRefs) + print("----------- %d Deleted since last diff: ------------" % len(delRefs)) self.report(delRefs, objs, **kargs) - print "----------- %d Created since last diff: ------------" % len(createRefs) + print("----------- %d Created since last diff: ------------" % len(createRefs)) self.report(createRefs, objs, **kargs) - print "----------- %d Created since start (persistent): ------------" % len(persistentRefs) + print("----------- %d Created since start (persistent): ------------" % len(persistentRefs)) self.report(persistentRefs, objs, **kargs) @@ -600,14 +600,14 @@ class ObjTracker: return type(o) is cls or id(o) in cls.allObjs def collect(self): - print "Collecting list of all objects..." + print("Collecting list of all objects...") gc.collect() objs = get_all_objects() frame = sys._getframe() del objs[id(frame)] ## ignore the current frame del objs[id(frame.f_code)] - ignoreTypes = [int, long] + ignoreTypes = [int] refs = {} count = {} for k in objs: @@ -628,7 +628,7 @@ class ObjTracker: ObjTracker.allObjs[id(typStr)] = None count[typ] = count.get(typ, 0) + 1 - print "All objects: %d Tracked objects: %d" % (len(objs), len(refs)) + print("All objects: %d Tracked objects: %d" % (len(objs), len(refs))) return refs, count, objs def forgetRef(self, ref): @@ -669,14 +669,14 @@ class ObjTracker: rev[typ].append(oid) c = count.get(typ, [0,0]) count[typ] = [c[0]+1, c[1]+objectSize(obj)] - typs = count.keys() + typs = list(count.keys()) typs.sort(lambda a,b: cmp(count[a][1], count[b][1])) for t in typs: line = " %d\t%d\t%s" % (count[t][0], count[t][1], t) if showIDs: line += "\t"+",".join(map(str,rev[t])) - print line + print(line) def findTypes(self, refs, regex): allObjs = get_all_objects() @@ -709,21 +709,21 @@ def describeObj(obj, depth=4, path=None, ignore=None): for ref in refs: if id(ref) in ignore: continue - if id(ref) in map(id, path): - print "Cyclic reference: " + refPathString([ref]+path) + if id(ref) in list(map(id, path)): + print("Cyclic reference: " + refPathString([ref]+path)) printed = True continue newPath = [ref]+path if len(newPath) >= depth: refStr = refPathString(newPath) if '[_]' not in refStr: ## ignore '_' references generated by the interactive shell - print refStr + print(refStr) printed = True else: describeObj(ref, depth, newPath, ignore) printed = True if not printed: - print "Dead end: " + refPathString(path) + print("Dead end: " + refPathString(path)) @@ -773,18 +773,18 @@ def searchRefs(obj, *args): ignore[id(refs)] = None refs = [r for r in refs if id(r) not in ignore] elif a == 't': - print map(typeStr, refs) + print(list(map(typeStr, refs))) elif a == 'i': - print map(id, refs) + print(list(map(id, refs))) elif a == 'l': def slen(o): if hasattr(o, '__len__'): return len(o) else: return None - print map(slen, refs) + print(list(map(slen, refs))) elif a == 'o': - print obj + print(obj) elif a == 'ro': return obj elif a == 'rr': @@ -820,14 +820,14 @@ def findObj(regex): def listRedundantModules(): """List modules that have been imported more than once via different paths.""" mods = {} - for name, mod in sys.modules.iteritems(): + for name, mod in sys.modules.items(): if not hasattr(mod, '__file__'): continue mfile = os.path.abspath(mod.__file__) if mfile[-1] == 'c': mfile = mfile[:-1] if mfile in mods: - print "module at %s has 2 names: %s, %s" % (mfile, name, mods[mfile]) + print("module at %s has 2 names: %s, %s" % (mfile, name, mods[mfile])) else: mods[mfile] = name @@ -841,7 +841,7 @@ def walkQObjectTree(obj, counts=None, verbose=False, depth=0): """ if verbose: - print " "*depth + typeStr(obj) + print(" "*depth + typeStr(obj)) report = False if counts is None: counts = {} @@ -871,12 +871,12 @@ def qObjectReport(verbose=False): QObjCache[oid] += " " + obj.text() except: pass - print "check obj", oid, unicode(QObjCache[oid]) + print("check obj", oid, str(QObjCache[oid])) if obj.parent() is None: walkQObjectTree(obj, count, verbose) - typs = count.keys() + typs = list(count.keys()) typs.sort() for t in typs: - print count[t], "\t", t + print(count[t], "\t", t) diff --git a/dockarea/Container.py b/dockarea/Container.py index a254d474..83610937 100644 --- a/dockarea/Container.py +++ b/dockarea/Container.py @@ -264,4 +264,4 @@ class TContainer(Container, QtGui.QWidget): QtGui.QStackedWidget.childEvent(self.stack, ev) Container.childEvent(self, ev) -import Dock +from . import Dock diff --git a/dockarea/Dock.py b/dockarea/Dock.py index 3b058ba4..acfa94f6 100644 --- a/dockarea/Dock.py +++ b/dockarea/Dock.py @@ -1,6 +1,6 @@ from pyqtgraph.Qt import QtCore, QtGui -from DockDrop import * +from .DockDrop import * from pyqtgraph.widgets.VerticalLabel import VerticalLabel class Dock(QtGui.QWidget, DockDrop): diff --git a/dockarea/DockArea.py b/dockarea/DockArea.py index 88e699d5..819726c3 100644 --- a/dockarea/DockArea.py +++ b/dockarea/DockArea.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- from pyqtgraph.Qt import QtCore, QtGui -from Container import * -from DockDrop import * +from .Container import * +from .DockDrop import * import pyqtgraph.debug as debug import weakref @@ -202,7 +202,7 @@ class DockArea(Container, QtGui.QWidget, DockDrop): a.win.setGeometry(*s[1]) ## 4) Add any remaining docks to the bottom - for d in docks.itervalues(): + for d in docks.values(): self.moveDock(d, 'below', None) #print "\nKill old containers:" diff --git a/dockarea/DockDrop.py b/dockarea/DockDrop.py index 339b28fd..6e526b17 100644 --- a/dockarea/DockDrop.py +++ b/dockarea/DockDrop.py @@ -126,4 +126,4 @@ class DropAreaOverlay(QtGui.QWidget): p.setPen(QtGui.QPen(QtGui.QColor(50, 50, 150), 3)) p.drawRect(rgn) -import Dock \ No newline at end of file +from . import Dock \ No newline at end of file diff --git a/dockarea/__init__.py b/dockarea/__init__.py index 88d12393..f67c50c3 100644 --- a/dockarea/__init__.py +++ b/dockarea/__init__.py @@ -1,2 +1,2 @@ -from DockArea import DockArea -from Dock import Dock \ No newline at end of file +from .DockArea import DockArea +from .Dock import Dock \ No newline at end of file diff --git a/dockarea/__main__.py b/dockarea/__main__.py index 86e8c9d1..ae794dd7 100644 --- a/dockarea/__main__.py +++ b/dockarea/__main__.py @@ -7,15 +7,15 @@ sys.path.insert(0, p) from pyqtgraph.Qt import QtCore, QtGui -from DockArea import * -from Dock import * +from .DockArea import * +from .Dock import * app = QtGui.QApplication([]) win = QtGui.QMainWindow() area = DockArea() win.setCentralWidget(area) win.resize(800,800) -from Dock import Dock +from .Dock import Dock d1 = Dock("Dock1", size=(200,200)) d2 = Dock("Dock2", size=(100,100)) d3 = Dock("Dock3", size=(1,1)) @@ -32,7 +32,7 @@ area.addDock(d6, 'top', d4) area.moveDock(d6, 'above', d4) d3.hideTitleBar() -print "===build complete====" +print("===build complete====") for d in [d1, d2, d3, d4, d5]: w = QtGui.QWidget() @@ -51,7 +51,7 @@ import pyqtgraph as pg p = pg.PlotWidget() d6.addWidget(p) -print "===widgets added===" +print("===widgets added===") #s = area.saveState() diff --git a/documentation/source/conf.py b/documentation/source/conf.py index 6df8d7b9..8145ba4a 100644 --- a/documentation/source/conf.py +++ b/documentation/source/conf.py @@ -41,8 +41,8 @@ source_suffix = '.rst' master_doc = 'index' # General information about the project. -project = u'pyqtgraph' -copyright = u'2011, Luke Campagnola' +project = 'pyqtgraph' +copyright = '2011, Luke Campagnola' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -179,8 +179,8 @@ htmlhelp_basename = 'pyqtgraphdoc' # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ - ('index', 'pyqtgraph.tex', u'pyqtgraph Documentation', - u'Luke Campagnola', 'manual'), + ('index', 'pyqtgraph.tex', 'pyqtgraph Documentation', + 'Luke Campagnola', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of @@ -212,6 +212,6 @@ latex_documents = [ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - ('index', 'pyqtgraph', u'pyqtgraph Documentation', - [u'Luke Campagnola'], 1) + ('index', 'pyqtgraph', 'pyqtgraph Documentation', + ['Luke Campagnola'], 1) ] diff --git a/examples/GLMeshItem.py b/examples/GLMeshItem.py index 07f9f949..15d6bc33 100644 --- a/examples/GLMeshItem.py +++ b/examples/GLMeshItem.py @@ -37,12 +37,12 @@ def psi(i, j, k, offset=(25, 25, 50)): #return ((1./81.) * (1./np.pi)**0.5 * (1./a0)**(3/2) * (r/a0)**2 * (r/a0) * np.exp(-r/(3*a0)) * np.sin(th) * np.cos(th) * np.exp(2 * 1j * phi))**2 -print "Generating scalar field.." +print("Generating scalar field..") data = np.abs(np.fromfunction(psi, (50,50,100))) #data = np.fromfunction(lambda i,j,k: np.sin(0.2*((i-25)**2+(j-15)**2+k**2)**0.5), (50,50,50)); -print "Generating isosurface.." +print("Generating isosurface..") faces = pg.isosurface(data, data.max()/4.) m = gl.GLMeshItem(faces) w.addItem(m) diff --git a/examples/GraphicsScene.py b/examples/GraphicsScene.py index 9fc97876..2518f50c 100644 --- a/examples/GraphicsScene.py +++ b/examples/GraphicsScene.py @@ -26,9 +26,9 @@ class Obj(QtGui.QGraphicsObject): def mouseClickEvent(self, ev): if ev.double(): - print "double click" + print("double click") else: - print "click" + print("click") ev.accept() #def mouseDragEvent(self, ev): @@ -48,7 +48,7 @@ obj2 = Obj() win.addItem(obj2) def clicked(): - print "button click" + print("button click") btn = QtGui.QPushButton("BTN") btn.clicked.connect(clicked) prox = QtGui.QGraphicsProxyWidget() diff --git a/examples/MultiPlotWidget.py b/examples/MultiPlotWidget.py index 2175241d..61ebab03 100644 --- a/examples/MultiPlotWidget.py +++ b/examples/MultiPlotWidget.py @@ -13,7 +13,7 @@ from pyqtgraph import MultiPlotWidget try: from metaarray import * except: - print "MultiPlot is only used with MetaArray for now (and you do not have the metaarray package)" + print("MultiPlot is only used with MetaArray for now (and you do not have the metaarray package)") exit() app = QtGui.QApplication([]) diff --git a/examples/PlotWidget.py b/examples/PlotWidget.py index 05da37a2..f1cc4466 100644 --- a/examples/PlotWidget.py +++ b/examples/PlotWidget.py @@ -70,7 +70,7 @@ curve.setPen('w') ## white pen curve.setShadowPen(pg.mkPen((70,70,30), width=6, cosmetic=True)) def clicked(): - print "curve clicked" + print("curve clicked") curve.sigClicked.connect(clicked) lr = pg.LinearRegionItem([1, 30], bounds=[0,100], movable=True) @@ -79,8 +79,6 @@ line = pg.InfiniteLine(angle=90, movable=True) pw3.addItem(line) line.setBounds([0,200]) -import initExample ## Add path to library (just for examples; you do not need this) - ## Start Qt event loop unless running in interactive mode or using pyside. import sys if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): diff --git a/examples/ScatterPlot.py b/examples/ScatterPlot.py index c4a8cd80..39ee77cc 100755 --- a/examples/ScatterPlot.py +++ b/examples/ScatterPlot.py @@ -21,7 +21,7 @@ w2.setAspectLocked(True) view.nextRow() w3 = view.addPlot() w4 = view.addPlot() -print "Generating data, this takes a few seconds..." +print("Generating data, this takes a few seconds...") ## There are a few different ways we can draw scatter plots; each is optimized for different types of data: @@ -43,7 +43,7 @@ def clicked(plot, points): global lastClicked for p in lastClicked: p.resetPen() - print "clicked points", points + print("clicked points", points) for p in points: p.setPen('b', width=2) lastClicked = points @@ -57,7 +57,7 @@ s1.sigClicked.connect(clicked) s2 = pg.ScatterPlotItem(size=10, pen=pg.mkPen('w'), pxMode=True) pos = np.random.normal(size=(2,n), scale=1e-5) -spots = [{'pos': pos[:,i], 'data': 1, 'brush':pg.intColor(i, n), 'symbol': i%5, 'size': 5+i/10.} for i in xrange(n)] +spots = [{'pos': pos[:,i], 'data': 1, 'brush':pg.intColor(i, n), 'symbol': i%5, 'size': 5+i/10.} for i in range(n)] s2.addPoints(spots) w2.addItem(s2) w2.setRange(s2.boundingRect()) diff --git a/examples/ViewBox.py b/examples/ViewBox.py index 13c8ce1c..ddc12bda 100755 --- a/examples/ViewBox.py +++ b/examples/ViewBox.py @@ -71,7 +71,7 @@ l.addItem(xScale, 1, 1) yScale = pg.AxisItem(orientation='left', linkView=vb) l.addItem(yScale, 0, 0) -xScale.setLabel(text=u"X Axis", units="s") +xScale.setLabel(text="X Axis", units="s") yScale.setLabel('Y Axis', units='V') def rand(n): diff --git a/examples/__main__.py b/examples/__main__.py index 49c4ae50..75d3301e 100644 --- a/examples/__main__.py +++ b/examples/__main__.py @@ -38,7 +38,7 @@ examples = OrderedDict([ #('SpinBox', '../widgets/SpinBox.py'), ('TreeWidget', '../widgets/TreeWidget.py'), ('DataTreeWidget', '../widgets/DataTreeWidget.py'), - ('GradientWidget', '../widgets/GradientWidget.py'), + ('GradientWidget', 'GradientWidget.py'), #('TableWidget', '../widgets/TableWidget.py'), ('ColorButton', '../widgets/ColorButton.py'), #('CheckTable', '../widgets/CheckTable.py'), @@ -75,7 +75,7 @@ class ExampleLoader(QtGui.QMainWindow): def populateTree(self, root, examples): - for key, val in examples.iteritems(): + for key, val in examples.items(): item = QtGui.QTreeWidgetItem([key]) if isinstance(val, basestring): item.file = val diff --git a/exporters/CSVExporter.py b/exporters/CSVExporter.py index 3955174c..5e949be6 100644 --- a/exporters/CSVExporter.py +++ b/exporters/CSVExporter.py @@ -1,6 +1,6 @@ import pyqtgraph as pg from pyqtgraph.Qt import QtGui, QtCore -from Exporter import Exporter +from .Exporter import Exporter from pyqtgraph.parametertree import Parameter diff --git a/exporters/ImageExporter.py b/exporters/ImageExporter.py index a392b2dc..e27d48ff 100644 --- a/exporters/ImageExporter.py +++ b/exporters/ImageExporter.py @@ -1,4 +1,4 @@ -from Exporter import Exporter +from .Exporter import Exporter from pyqtgraph.parametertree import Parameter from pyqtgraph.Qt import QtGui, QtCore, QtSvg import pyqtgraph as pg diff --git a/exporters/Matplotlib.py b/exporters/Matplotlib.py index 71164b8e..76f878d2 100644 --- a/exporters/Matplotlib.py +++ b/exporters/Matplotlib.py @@ -1,6 +1,6 @@ import pyqtgraph as pg from pyqtgraph.Qt import QtGui, QtCore -from Exporter import Exporter +from .Exporter import Exporter __all__ = ['MatplotlibExporter'] diff --git a/exporters/PrintExporter.py b/exporters/PrintExporter.py index 12468e9c..4a47af2e 100644 --- a/exporters/PrintExporter.py +++ b/exporters/PrintExporter.py @@ -1,4 +1,4 @@ -from Exporter import Exporter +from .Exporter import Exporter from pyqtgraph.parametertree import Parameter from pyqtgraph.Qt import QtGui, QtCore, QtSvg import re diff --git a/exporters/SVGExporter.py b/exporters/SVGExporter.py index 9158d00c..0538aae7 100644 --- a/exporters/SVGExporter.py +++ b/exporters/SVGExporter.py @@ -1,4 +1,4 @@ -from Exporter import Exporter +from .Exporter import Exporter from pyqtgraph.parametertree import Parameter from pyqtgraph.Qt import QtGui, QtCore, QtSvg import re @@ -57,7 +57,7 @@ class SVGExporter(Exporter): if m is not None: #print "Matched group:", line g = m.groups() - matrix = map(float, g[2].split(',')) + matrix = list(map(float, g[2].split(','))) #print "matrix:", matrix scale = max(abs(matrix[0]), abs(matrix[3])) if scale == 0 or scale == 1.0: diff --git a/exporters/__init__.py b/exporters/__init__.py index e6ac379c..99fbdf20 100644 --- a/exporters/__init__.py +++ b/exporters/__init__.py @@ -4,7 +4,7 @@ import os, sys d = os.path.split(__file__)[0] files = [] for f in os.listdir(d): - if os.path.isdir(os.path.join(d, f)): + if os.path.isdir(os.path.join(d, f)) and f != '__pycache__': files.append(f) elif f[-3:] == '.py' and f not in ['__init__.py', 'Exporter.py']: files.append(f[:-3]) diff --git a/flowchart/Flowchart.py b/flowchart/Flowchart.py index 71551ba4..18f1f211 100644 --- a/flowchart/Flowchart.py +++ b/flowchart/Flowchart.py @@ -3,24 +3,24 @@ from pyqtgraph.Qt import QtCore, QtGui #from PyQt4 import QtCore, QtGui #from PySide import QtCore, QtGui #import Node as NodeMod -from Node import * +from .Node import * #import functions from collections import OrderedDict from pyqtgraph.widgets.TreeWidget import * #from .. DataTreeWidget import * -import FlowchartTemplate -import FlowchartCtrlTemplate -from Terminal import Terminal +from . import FlowchartTemplate +from . import FlowchartCtrlTemplate +from .Terminal import Terminal from numpy import ndarray -import library +from . import library from pyqtgraph.debug import printExc import pyqtgraph.configfile as configfile import pyqtgraph.dockarea as dockarea import pyqtgraph as pg -import FlowchartGraphicsView +from . import FlowchartGraphicsView def strDict(d): - return dict([(str(k), v) for k, v in d.iteritems()]) + return dict([(str(k), v) for k, v in d.items()]) def toposort(deps, nodes=None, seen=None, stack=None, depth=0): @@ -33,7 +33,7 @@ def toposort(deps, nodes=None, seen=None, stack=None, depth=0): if nodes is None: ## run through deps to find nodes that are not depended upon rem = set() - for dep in deps.itervalues(): + for dep in deps.values(): rem |= set(dep) nodes = set(deps.keys()) - rem if seen is None: @@ -99,7 +99,7 @@ class Flowchart(Node): self.viewBox.autoRange(padding = 0.04) - for name, opts in terminals.iteritems(): + for name, opts in terminals.items(): self.addTerminal(name, **opts) def setInput(self, **args): @@ -267,7 +267,7 @@ class Flowchart(Node): #print "ORDER:", order ## Record inputs given to process() - for n, t in self.inputNode.outputs().iteritems(): + for n, t in self.inputNode.outputs().items(): if n not in args: raise Exception("Parameter %s required to process this chart." % n) data[t] = args[n] @@ -285,8 +285,8 @@ class Flowchart(Node): ## get input and output terminals for this node - outs = node.outputs().values() - ins = node.inputs().values() + outs = list(node.outputs().values()) + ins = list(node.inputs().values()) ## construct input value dictionary args = {} @@ -308,7 +308,7 @@ class Flowchart(Node): else: result = node.process(display=False, **args) except: - print "Error processing node %s. Args are: %s" % (str(node), str(args)) + print("Error processing node %s. Args are: %s" % (str(node), str(args))) raise for out in outs: #print " Output:", out, out.name() @@ -316,7 +316,7 @@ class Flowchart(Node): try: data[out] = result[out.name()] except: - print out, out.name() + print(out, out.name()) raise elif c == 'd': ## delete a terminal result (no longer needed; may be holding a lot of memory) #print "===> delete", arg @@ -334,9 +334,9 @@ class Flowchart(Node): ## first collect list of nodes/terminals and their dependencies deps = {} tdeps = {} ## {terminal: [nodes that depend on terminal]} - for name, node in self._nodes.iteritems(): + for name, node in self._nodes.items(): deps[node] = node.dependentNodes() - for t in node.outputs().itervalues(): + for t in node.outputs().values(): tdeps[t] = t.dependentNodes() #print "DEPS:", deps @@ -350,7 +350,7 @@ class Flowchart(Node): ## determine when it is safe to delete terminal values dels = [] - for t, nodes in tdeps.iteritems(): + for t, nodes in tdeps.items(): lastInd = 0 lastNode = None for n in nodes: ## determine which node is the last to be processed according to order @@ -385,9 +385,9 @@ class Flowchart(Node): self.processing = True try: deps = {} - for name, node in self._nodes.iteritems(): + for name, node in self._nodes.items(): deps[node] = [] - for t in node.outputs().itervalues(): + for t in node.outputs().values(): deps[node].extend(t.dependentNodes()) ## determine order of updates @@ -401,9 +401,9 @@ class Flowchart(Node): #print "Order:", order for node in order[1:]: #print "Processing node", node - for term in node.inputs().values(): + for term in list(node.inputs().values()): #print " checking terminal", term - deps = term.connections().keys() + deps = list(term.connections().keys()) update = False for d in deps: if d in terms: @@ -446,9 +446,9 @@ class Flowchart(Node): def listConnections(self): conn = set() - for n in self._nodes.itervalues(): + for n in self._nodes.values(): terms = n.outputs() - for n, t in terms.iteritems(): + for n, t in terms.items(): for c in t.connections(): conn.add((t, c)) return conn @@ -459,7 +459,7 @@ class Flowchart(Node): state['connects'] = [] state['terminals'] = self.saveTerminals() - for name, node in self._nodes.iteritems(): + for name, node in self._nodes.items(): cls = type(node) if hasattr(cls, 'nodeName'): clsName = cls.nodeName @@ -503,8 +503,8 @@ class Flowchart(Node): try: self.connectTerminals(self._nodes[n1][t1], self._nodes[n2][t2]) except: - print self._nodes[n1].terminals - print self._nodes[n2].terminals + print(self._nodes[n1].terminals) + print(self._nodes[n2].terminals) printExc("Error connecting terminals %s.%s - %s.%s:" % (n1, t1, n2, t2)) @@ -554,7 +554,7 @@ class Flowchart(Node): self.sigFileSaved.emit(fileName) def clear(self): - for n in self._nodes.values(): + for n in list(self._nodes.values()): if n is self.inputNode or n is self.outputNode: continue n.close() ## calls self.nodeClosed(n) by signal @@ -583,7 +583,7 @@ class FlowchartGraphicsItem(GraphicsObject): inp = self.chart.inputs() dy = bounds.height() / (len(inp)+1) y = dy - for n, t in inp.iteritems(): + for n, t in inp.items(): item = t.graphicsItem() self.terminals[n] = item item.setParentItem(self) @@ -592,7 +592,7 @@ class FlowchartGraphicsItem(GraphicsObject): out = self.chart.outputs() dy = bounds.height() / (len(out)+1) y = dy - for n, t in out.iteritems(): + for n, t in out.items(): item = t.graphicsItem() self.terminals[n] = item item.setParentItem(self) @@ -840,7 +840,7 @@ class FlowchartWidget(dockarea.DockArea): def buildMenu(self, pos=None): self.nodeMenu = QtGui.QMenu() self.subMenus = [] - for section, nodes in library.getNodeTree().iteritems(): + for section, nodes in library.getNodeTree().items(): menu = QtGui.QMenu(section) self.nodeMenu.addMenu(menu) for name in nodes: diff --git a/flowchart/FlowchartTemplate.py b/flowchart/FlowchartTemplate.py index ec8823f1..b92af212 100644 --- a/flowchart/FlowchartTemplate.py +++ b/flowchart/FlowchartTemplate.py @@ -56,4 +56,4 @@ class Ui_Form(object): Form.setWindowTitle(QtGui.QApplication.translate("Form", "Form", None, QtGui.QApplication.UnicodeUTF8)) from pyqtgraph.widgets.DataTreeWidget import DataTreeWidget -from FlowchartGraphicsView import FlowchartGraphicsView +from .FlowchartGraphicsView import FlowchartGraphicsView diff --git a/flowchart/Node.py b/flowchart/Node.py index 43f617d4..a4204592 100644 --- a/flowchart/Node.py +++ b/flowchart/Node.py @@ -3,17 +3,17 @@ from pyqtgraph.Qt import QtCore, QtGui #from PySide import QtCore, QtGui from pyqtgraph.graphicsItems.GraphicsObject import GraphicsObject import pyqtgraph.functions as fn -from Terminal import * +from .Terminal import * from collections import OrderedDict from pyqtgraph.debug import * import numpy as np #from pyqtgraph.ObjectWorkaround import QObjectWorkaround -from eq import * +from .eq import * #TETRACYCLINE = True def strDict(d): - return dict([(str(k), v) for k, v in d.iteritems()]) + return dict([(str(k), v) for k, v in d.items()]) class Node(QtCore.QObject): @@ -41,7 +41,7 @@ class Node(QtCore.QObject): self.exception = None if terminals is None: return - for name, opts in terminals.iteritems(): + for name, opts in terminals.items(): self.addTerminal(name, **opts) @@ -156,7 +156,7 @@ class Node(QtCore.QObject): def dependentNodes(self): """Return the list of nodes which provide direct input to this node""" nodes = set() - for t in self.inputs().itervalues(): + for t in self.inputs().values(): nodes |= set([i.node() for i in t.inputTerminals()]) return nodes #return set([t.inputTerminals().node() for t in self.listInputs().itervalues()]) @@ -180,7 +180,7 @@ class Node(QtCore.QObject): """Set the values on input terminals. For most nodes, this will happen automatically through Terminal.inputChanged. This is normally only used for nodes with no connected inputs.""" changed = False - for k, v in args.iteritems(): + for k, v in args.items(): term = self._inputs[k] oldVal = term.value() if not eq(oldVal, v): @@ -191,13 +191,13 @@ class Node(QtCore.QObject): def inputValues(self): vals = {} - for n, t in self.inputs().iteritems(): + for n, t in self.inputs().items(): vals[n] = t.value() return vals def outputValues(self): vals = {} - for n, t in self.outputs().iteritems(): + for n, t in self.outputs().items(): vals[n] = t.value() return vals @@ -224,12 +224,12 @@ class Node(QtCore.QObject): self.setOutput(**out) else: self.setOutputNoSignal(**out) - for n,t in self.inputs().iteritems(): + for n,t in self.inputs().items(): t.setValueAcceptable(True) self.clearException() except: #printExc( "Exception while processing %s:" % self.name()) - for n,t in self.outputs().iteritems(): + for n,t in self.outputs().items(): t.setValue(None) self.setException(sys.exc_info()) @@ -239,7 +239,7 @@ class Node(QtCore.QObject): def processBypassed(self, args): result = {} - for term in self.outputs().values(): + for term in list(self.outputs().values()): byp = term.bypassValue() if byp is None: result[term.name()] = None @@ -253,7 +253,7 @@ class Node(QtCore.QObject): self.sigOutputChanged.emit(self) ## triggers flowchart to propagate new data def setOutputNoSignal(self, **vals): - for k, v in vals.iteritems(): + for k, v in vals.items(): term = self.outputs()[k] term.setValue(v) #targets = term.connections() @@ -287,15 +287,15 @@ class Node(QtCore.QObject): def saveTerminals(self): terms = OrderedDict() - for n, t in self.terminals.iteritems(): + for n, t in self.terminals.items(): terms[n] = (t.saveState()) return terms def restoreTerminals(self, state): - for name in self.terminals.keys(): + for name in list(self.terminals.keys()): if name not in state: self.removeTerminal(name) - for name, opts in state.iteritems(): + for name, opts in state.items(): if name in self.terminals: continue try: @@ -306,7 +306,7 @@ class Node(QtCore.QObject): def clearTerminals(self): - for t in self.terminals.itervalues(): + for t in self.terminals.values(): t.close() self.terminals = OrderedDict() self._inputs = {} @@ -410,7 +410,7 @@ class NodeGraphicsItem(GraphicsObject): inp = self.node.inputs() dy = bounds.height() / (len(inp)+1) y = dy - for i, t in inp.iteritems(): + for i, t in inp.items(): item = t.graphicsItem() item.setParentItem(self) #item.setZValue(self.zValue()+1) @@ -422,7 +422,7 @@ class NodeGraphicsItem(GraphicsObject): out = self.node.outputs() dy = bounds.height() / (len(out)+1) y = dy - for i, t in out.iteritems(): + for i, t in out.items(): item = t.graphicsItem() item.setParentItem(self) item.setZValue(self.zValue()) @@ -508,7 +508,7 @@ class NodeGraphicsItem(GraphicsObject): def itemChange(self, change, val): if change == self.ItemPositionHasChanged: - for k, t in self.terminals.iteritems(): + for k, t in self.terminals.items(): t[1].nodeMoved() return QtGui.QGraphicsItem.itemChange(self, change, val) diff --git a/flowchart/Terminal.py b/flowchart/Terminal.py index a9addfaf..77f5b72c 100644 --- a/flowchart/Terminal.py +++ b/flowchart/Terminal.py @@ -5,7 +5,7 @@ from pyqtgraph.graphicsItems.GraphicsObject import GraphicsObject import pyqtgraph.functions as fn from pyqtgraph.Point import Point #from PySide import QtCore, QtGui -from eq import * +from .eq import * class Terminal: def __init__(self, node, name, io, optional=False, multi=False, pos=None, renamable=False, removable=False, multiable=False, bypass=None): @@ -192,7 +192,7 @@ class Terminal: raise Exception("Can't connect to terminal on same node.") for t in [self, term]: if t.isInput() and not t._multi and len(t.connections()) > 0: - raise Exception("Cannot connect %s <-> %s: Terminal %s is already connected to %s (and does not allow multiple connections)" % (self, term, t, t.connections().keys())) + raise Exception("Cannot connect %s <-> %s: Terminal %s is already connected to %s (and does not allow multiple connections)" % (self, term, t, list(t.connections().keys()))) #if self.hasInput() and term.hasInput(): #raise Exception('Target terminal already has input') @@ -244,7 +244,7 @@ class Terminal: def disconnectAll(self): - for t in self._connections.keys(): + for t in list(self._connections.keys()): self.disconnectFrom(t) def recolor(self, color=None, recurse=True): @@ -372,7 +372,7 @@ class TerminalGraphicsItem(GraphicsObject): self.updateConnections() def updateConnections(self): - for t, c in self.term.connections().iteritems(): + for t, c in self.term.connections().items(): c.updateLine() def mousePressEvent(self, ev): @@ -484,7 +484,7 @@ class TerminalGraphicsItem(GraphicsObject): return self.mapToView(self.mapFromItem(self.box, self.box.boundingRect().center())) def nodeMoved(self): - for t, item in self.term.connections().iteritems(): + for t, item in self.term.connections().items(): item.updateLine() diff --git a/flowchart/__init__.py b/flowchart/__init__.py index 24f562f4..46e04db0 100644 --- a/flowchart/__init__.py +++ b/flowchart/__init__.py @@ -1,4 +1,4 @@ # -*- coding: utf-8 -*- -from Flowchart import * +from .Flowchart import * -from library import getNodeType, registerNodeType, getNodeTree \ No newline at end of file +from .library import getNodeType, registerNodeType, getNodeTree \ No newline at end of file diff --git a/flowchart/eq.py b/flowchart/eq.py index f2f744e4..f929a938 100644 --- a/flowchart/eq.py +++ b/flowchart/eq.py @@ -10,8 +10,8 @@ def eq(a, b): except AttributeError: return False except: - print "a:", str(type(a)), str(a) - print "b:", str(type(b)), str(b) + print("a:", str(type(a)), str(a)) + print("b:", str(type(b)), str(b)) raise t = type(e) if t is bool: diff --git a/flowchart/library/Data.py b/flowchart/library/Data.py index a129fc95..e24ba121 100644 --- a/flowchart/library/Data.py +++ b/flowchart/library/Data.py @@ -2,13 +2,13 @@ from ..Node import Node from pyqtgraph.Qt import QtGui, QtCore import numpy as np -from common import * +from .common import * from pyqtgraph.Transform import Transform from pyqtgraph.Point import Point from pyqtgraph.widgets.TreeWidget import TreeWidget from pyqtgraph.graphicsItems.LinearRegionItem import LinearRegionItem -import functions +from . import functions try: import metaarray @@ -55,7 +55,7 @@ class ColumnSelectNode(Node): cols = set(cols[ax]) break else: - cols = data.dtype.fields.keys() + cols = list(data.dtype.fields.keys()) rem = set() for c in self.columns: @@ -124,11 +124,11 @@ class RegionSelectNode(CtrlNode): self.ctrls['movable'].toggled.connect(self.movableToggled) def displayToggled(self, b): - for item in self.items.itervalues(): + for item in self.items.values(): item.setVisible(b) def movableToggled(self, b): - for item in self.items.itervalues(): + for item in self.items.values(): item.setMovable(b) @@ -225,7 +225,7 @@ class EvalNode(Node): text = str(self.text.toPlainText()) if text != self.lastText: self.lastText = text - print "eval node update" + print("eval node update") self.update() return QtGui.QTextEdit.focusOutEvent(self.text, ev) diff --git a/flowchart/library/Display.py b/flowchart/library/Display.py index e87ff645..7979d7a7 100644 --- a/flowchart/library/Display.py +++ b/flowchart/library/Display.py @@ -7,7 +7,7 @@ from pyqtgraph.graphicsItems.ScatterPlotItem import ScatterPlotItem from pyqtgraph.graphicsItems.PlotCurveItem import PlotCurveItem from pyqtgraph import PlotDataItem -from common import * +from .common import * import numpy as np class PlotWidgetNode(Node): @@ -37,7 +37,7 @@ class PlotWidgetNode(Node): if display: #self.plot.clearPlots() items = set() - for name, vals in In.iteritems(): + for name, vals in In.items(): if vals is None: continue if type(vals) is not list: @@ -61,14 +61,14 @@ class PlotWidgetNode(Node): item = self.plot.plot(val) self.items[vid] = item items.add(vid) - for vid in self.items.keys(): + for vid in list(self.items.keys()): if vid not in items: #print "remove", self.items[vid] self.plot.removeItem(self.items[vid]) del self.items[vid] def processBypassed(self, args): - for item in self.items.values(): + for item in list(self.items.values()): self.plot.removeItem(item) self.items = {} @@ -101,7 +101,7 @@ class CanvasNode(Node): def process(self, In, display=True): if display: items = set() - for name, vals in In.iteritems(): + for name, vals in In.items(): if vals is None: continue if type(vals) is not list: @@ -116,7 +116,7 @@ class CanvasNode(Node): item = val self.items[vid] = item items.add(vid) - for vid in self.items.keys(): + for vid in list(self.items.keys()): if vid not in items: #print "remove", self.items[vid] self.canvas.removeItem(self.items[vid]) @@ -214,16 +214,16 @@ class ScatterPlot(CtrlNode): def updateKeys(self, data): if isinstance(data, dict): - keys = data.keys() + keys = list(data.keys()) elif isinstance(data, list) or isinstance(data, tuple): keys = data elif isinstance(data, np.ndarray) or isinstance(data, np.void): keys = data.dtype.names else: - print "Unknown data type:", type(data), data + print("Unknown data type:", type(data), data) return - for c in self.ctrls.itervalues(): + for c in self.ctrls.values(): c.blockSignals(True) for c in [self.ctrls['x'], self.ctrls['y'], self.ctrls['size']]: cur = str(c.currentText()) @@ -234,7 +234,7 @@ class ScatterPlot(CtrlNode): c.setCurrentIndex(c.count()-1) for c in [self.ctrls['color'], self.ctrls['border']]: c.setArgList(keys) - for c in self.ctrls.itervalues(): + for c in self.ctrls.values(): c.blockSignals(False) self.keys = keys diff --git a/flowchart/library/Filters.py b/flowchart/library/Filters.py index 6badff83..8816ab58 100644 --- a/flowchart/library/Filters.py +++ b/flowchart/library/Filters.py @@ -4,8 +4,8 @@ from ..Node import Node from scipy.signal import detrend from scipy.ndimage import median_filter, gaussian_filter #from pyqtgraph.SignalProxy import SignalProxy -import functions -from common import * +from . import functions +from .common import * import numpy as np try: diff --git a/flowchart/library/__init__.py b/flowchart/library/__init__.py index 1efc4ea1..be51925f 100644 --- a/flowchart/library/__init__.py +++ b/flowchart/library/__init__.py @@ -67,7 +67,7 @@ def loadLibrary(reloadLibs=False, libPath=None): for f in os.listdir(libPath): pathName, ext = os.path.splitext(f) - if ext != '.py' or '__init__' in pathName: + if ext != '.py' or '__init__' in pathName or '__pycache__' in pathName: continue try: #print "importing from", f diff --git a/flowchart/library/functions.py b/flowchart/library/functions.py index c2d32572..37969cea 100644 --- a/flowchart/library/functions.py +++ b/flowchart/library/functions.py @@ -258,9 +258,9 @@ def concatenateColumns(data): try: out[name] = element[name] except: - print "Column:", name - print "Input shape:", element.shape, element.dtype - print "Output shape:", out.shape, out.dtype + print("Column:", name) + print("Input shape:", element.shape, element.dtype) + print("Output shape:", out.shape, out.dtype) raise else: name, type, d = element @@ -279,7 +279,7 @@ def suggestDType(x): return x.dtype elif isinstance(x, float): return float - elif isinstance(x, int) or isinstance(x, long): + elif isinstance(x, int): return int #elif isinstance(x, basestring): ## don't try to guess correct string length; use object instead. #return '%s" % (style, s) + return asUnicode("%s") % (style, s) def setHeight(self, h=None): if h is None: @@ -364,12 +364,12 @@ class AxisItem(GraphicsWidget): def logTickValues(self, minVal, maxVal, size): v1 = int(np.floor(minVal)) v2 = int(np.ceil(maxVal)) - major = range(v1+1, v2) + major = list(range(v1+1, v2)) minor = [] for v in range(v1, v2): minor.extend(v + np.log10(np.arange(1, 10))) - minor = filter(lambda x: x>minVal and xminVal and xd') or struct.pack('>i') + if sys.version_info.major == 2: + n = x.shape[0] + # create empty array, pad with extra space on either end + arr = np.empty(n+2, dtype=[('x', '>f8'), ('y', '>f8'), ('c', '>i4')]) + # write first two integers + prof.mark('allocate empty') + arr.data[12:20] = struct.pack('>ii', n, 0) + prof.mark('pack header') + # Fill array with vertex values + arr[1:-1]['x'] = x + arr[1:-1]['y'] = y + arr[1:-1]['c'] = 1 + prof.mark('fill array') + # write last 0 + lastInd = 20*(n+1) + arr.data[lastInd:lastInd+4] = struct.pack('>i', 0) + prof.mark('footer') + # create datastream object and stream into path + buf = QtCore.QByteArray(arr.data[12:lastInd+4]) # I think one unnecessary copy happens here + prof.mark('create buffer') + ds = QtCore.QDataStream(buf) + prof.mark('create datastream') + ds >> path + prof.mark('load') + + prof.finish() + else: + path.moveTo(x[0], y[0]) + for i in range(1, y.shape[0]): + path.lineTo(x[i], y[i]) - n = x.shape[0] - # create empty array, pad with extra space on either end - arr = np.empty(n+2, dtype=[('x', '>f8'), ('y', '>f8'), ('c', '>i4')]) - # write first two integers - prof.mark('allocate empty') - arr.data[12:20] = struct.pack('>ii', n, 0) - prof.mark('pack header') - # Fill array with vertex values - arr[1:-1]['x'] = x - arr[1:-1]['y'] = y - arr[1:-1]['c'] = 1 - prof.mark('fill array') - # write last 0 - lastInd = 20*(n+1) - arr.data[lastInd:lastInd+4] = struct.pack('>i', 0) - prof.mark('footer') - # create datastream object and stream into path - buf = QtCore.QByteArray(arr.data[12:lastInd+4]) # I think one unnecessary copy happens here - prof.mark('create buffer') - ds = QtCore.QDataStream(buf) - prof.mark('create datastream') - ds >> path - prof.mark('load') - - prof.finish() return path diff --git a/graphicsItems/PlotDataItem.py b/graphicsItems/PlotDataItem.py index 9f5fa1a3..7dc97768 100644 --- a/graphicsItems/PlotDataItem.py +++ b/graphicsItems/PlotDataItem.py @@ -5,9 +5,9 @@ except: HAVE_METAARRAY = False from pyqtgraph.Qt import QtCore -from GraphicsObject import GraphicsObject -from PlotCurveItem import PlotCurveItem -from ScatterPlotItem import ScatterPlotItem +from .GraphicsObject import GraphicsObject +from .PlotCurveItem import PlotCurveItem +from .ScatterPlotItem import ScatterPlotItem import numpy as np import scipy import pyqtgraph.functions as fn @@ -307,7 +307,7 @@ class PlotDataItem(GraphicsObject): if 'brush' in kargs: kargs['fillBrush'] = kargs['brush'] - for k in self.opts.keys(): + for k in list(self.opts.keys()): if k in kargs: self.opts[k] = kargs[k] diff --git a/graphicsItems/PlotItem/PlotItem.py b/graphicsItems/PlotItem/PlotItem.py index 4b40f34d..00b3333e 100644 --- a/graphicsItems/PlotItem/PlotItem.py +++ b/graphicsItems/PlotItem/PlotItem.py @@ -17,7 +17,7 @@ This class is very heavily featured: display, power spectrum, svg/png export, plot linking, and more. """ #from graphicsItems import * -from plotConfigTemplate import * +from .plotConfigTemplate import * from pyqtgraph.Qt import QtGui, QtCore, QtSvg import pyqtgraph.functions as fn from pyqtgraph.widgets.FileDialog import FileDialog @@ -314,7 +314,7 @@ class PlotItem(GraphicsWidget): #self.registerPlot(name) if labels is None: labels = {} - for label in self.scales.keys(): + for label in list(self.scales.keys()): if label in kargs: labels[label] = kargs[label] del kargs[label] @@ -703,7 +703,7 @@ class PlotItem(GraphicsWidget): """ Enable auto-scaling. The plot will continuously scale to fit the boundaries of its data. """ - print "Warning: enableAutoScale is deprecated. Use enableAutoRange(axis, enable) instead." + print("Warning: enableAutoScale is deprecated. Use enableAutoRange(axis, enable) instead.") self.vb.enableAutoRange(self.vb.XYAxes) #self.ctrl.xAutoRadio.setChecked(True) #self.ctrl.yAutoRadio.setChecked(True) @@ -815,11 +815,11 @@ class PlotItem(GraphicsWidget): #self.plotChanged() def addDataItem(self, item, *args): - print "PlotItem.addDataItem is deprecated. Use addItem instead." + print("PlotItem.addDataItem is deprecated. Use addItem instead.") self.addItem(item, *args) def addCurve(self, c, params=None): - print "PlotItem.addCurve is deprecated. Use addItem instead." + print("PlotItem.addCurve is deprecated. Use addItem instead.") self.addItem(c, params) def removeItem(self, item): @@ -970,7 +970,7 @@ class PlotItem(GraphicsWidget): #print "paramList:", self.paramList for c in self.curves: #print " curve:", c - for p in self.itemMeta.get(c, {}).keys(): + for p in list(self.itemMeta.get(c, {}).keys()): #print " param:", p if type(p) is tuple: p = '.'.join(p) @@ -1054,7 +1054,7 @@ class PlotItem(GraphicsWidget): #fh.write('\n' % color) fh.write('') @@ -1121,7 +1121,7 @@ class PlotItem(GraphicsWidget): if m is not None: #print "Matched group:", line g = m.groups() - matrix = map(float, g[2].split(',')) + matrix = list(map(float, g[2].split(','))) #print "matrix:", matrix scale = max(abs(matrix[0]), abs(matrix[3])) if scale == 0 or scale == 1.0: @@ -1361,7 +1361,7 @@ class PlotItem(GraphicsWidget): def _checkScaleKey(self, key): if key not in self.scales: - raise Exception("Scale '%s' not found. Scales are: %s" % (key, str(self.scales.keys()))) + raise Exception("Scale '%s' not found. Scales are: %s" % (key, str(list(self.scales.keys())))) def getScale(self, key): return self.getAxis(key) @@ -1426,7 +1426,7 @@ class PlotItem(GraphicsWidget): self.showAxis(axis, False) def showScale(self, *args, **kargs): - print "Deprecated. use showAxis() instead" + print("Deprecated. use showAxis() instead") return self.showAxis(*args, **kargs) def hideButtons(self): diff --git a/graphicsItems/PlotItem/__init__.py b/graphicsItems/PlotItem/__init__.py index e0db43af..d797978c 100644 --- a/graphicsItems/PlotItem/__init__.py +++ b/graphicsItems/PlotItem/__init__.py @@ -1 +1 @@ -from PlotItem import PlotItem +from .PlotItem import PlotItem diff --git a/graphicsItems/ROI.py b/graphicsItems/ROI.py index 5c5f930a..a6aa542e 100755 --- a/graphicsItems/ROI.py +++ b/graphicsItems/ROI.py @@ -1,1467 +1,1467 @@ -# -*- coding: utf-8 -*- -""" -ROI.py - Interactive graphics items for GraphicsView (ROI widgets) -Copyright 2010 Luke Campagnola -Distributed under MIT/X11 license. See license.txt for more infomation. - -Implements a series of graphics items which display movable/scalable/rotatable shapes -for use as region-of-interest markers. ROI class automatically handles extraction -of array data from ImageItems. - -The ROI class is meant to serve as the base for more specific types; see several examples -of how to build an ROI at the bottom of the file. -""" - -from pyqtgraph.Qt import QtCore, QtGui -#if not hasattr(QtCore, 'Signal'): - #QtCore.Signal = QtCore.pyqtSignal -import numpy as np -from numpy.linalg import norm -import scipy.ndimage as ndimage -from pyqtgraph.Point import * -from pyqtgraph.Transform import Transform -from math import cos, sin -import pyqtgraph.functions as fn -from GraphicsObject import GraphicsObject -from UIGraphicsItem import UIGraphicsItem - -__all__ = [ - 'ROI', - 'TestROI', 'RectROI', 'EllipseROI', 'CircleROI', 'PolygonROI', - 'LineROI', 'MultiLineROI', 'LineSegmentROI', 'SpiralROI', -] - - -def rectStr(r): - return "[%f, %f] + [%f, %f]" % (r.x(), r.y(), r.width(), r.height()) - -class ROI(GraphicsObject): - """Generic region-of-interest widget. - Can be used for implementing many types of selection box with rotate/translate/scale handles. - """ - - sigRegionChangeFinished = QtCore.Signal(object) - sigRegionChangeStarted = QtCore.Signal(object) - sigRegionChanged = QtCore.Signal(object) - sigHoverEvent = QtCore.Signal(object) - - def __init__(self, pos, size=Point(1, 1), angle=0.0, invertible=False, maxBounds=None, snapSize=1.0, scaleSnap=False, translateSnap=False, rotateSnap=False, parent=None, pen=None, movable=True): - #QObjectWorkaround.__init__(self) - GraphicsObject.__init__(self, parent) - pos = Point(pos) - size = Point(size) - self.aspectLocked = False - self.translatable = movable - self.rotateAllowed = True - - self.freeHandleMoved = False ## keep track of whether free handles have moved since last change signal was emitted. - self.mouseHovering = False - if pen is None: - pen = (255, 255, 255) - self.setPen(pen) - - self.handlePen = QtGui.QPen(QtGui.QColor(150, 255, 255)) - self.handles = [] - self.state = {'pos': Point(0,0), 'size': Point(1,1), 'angle': 0} ## angle is in degrees for ease of Qt integration - self.lastState = None - self.setPos(pos) - self.setAngle(angle) - self.setSize(size) - self.setZValue(10) - self.isMoving = False - - self.handleSize = 5 - self.invertible = invertible - self.maxBounds = maxBounds - - self.snapSize = snapSize - self.translateSnap = translateSnap - self.rotateSnap = rotateSnap - self.scaleSnap = scaleSnap - #self.setFlag(self.ItemIsSelectable, True) - - def getState(self): - return self.stateCopy() - - def stateCopy(self): - sc = {} - sc['pos'] = Point(self.state['pos']) - sc['size'] = Point(self.state['size']) - sc['angle'] = self.state['angle'] - return sc - - def saveState(self): - """Return the state of the widget in a format suitable for storing to disk. (Points are converted to tuple)""" - state = {} - state['pos'] = tuple(self.state['pos']) - state['size'] = tuple(self.state['size']) - state['angle'] = self.state['angle'] - return state - - def setState(self, state, update=True): - self.setPos(state['pos'], update=False) - self.setSize(state['size'], update=False) - self.setAngle(state['angle'], update=update) - - def setZValue(self, z): - QtGui.QGraphicsItem.setZValue(self, z) - for h in self.handles: - h['item'].setZValue(z+1) - - def parentBounds(self): - return self.mapToParent(self.boundingRect()).boundingRect() - - def setPen(self, pen): - self.pen = fn.mkPen(pen) - self.currentPen = self.pen - self.update() - - def size(self): - return self.getState()['size'] - - def pos(self): - return self.getState()['pos'] - - def angle(self): - return self.getState()['angle'] - - def setPos(self, pos, update=True, finish=True): - """Set the position of the ROI (in the parent's coordinate system). - By default, this will cause both sigStateChanged and sigStateChangeFinished to be emitted. - - If finish is False, then sigStateChangeFinished will not be emitted. You can then use - stateChangeFinished() to cause the signal to be emitted after a series of state changes. - - If update is False, the state change will be remembered but not processed and no signals - will be emitted. You can then use stateChanged() to complete the state change. This allows - multiple change functions to be called sequentially while minimizing processing overhead - and repeated signals. Setting update=False also forces finish=False. - """ - - pos = Point(pos) - self.state['pos'] = pos - QtGui.QGraphicsItem.setPos(self, pos) - if update: - self.stateChanged(finish=finish) - - def setSize(self, size, update=True, finish=True): - """Set the size of the ROI. May be specified as a QPoint, Point, or list of two values. - See setPos() for an explanation of the update and finish arguments. - """ - size = Point(size) - self.prepareGeometryChange() - self.state['size'] = size - if update: - self.stateChanged(finish=finish) - - def setAngle(self, angle, update=True, finish=True): - """Set the angle of rotation (in degrees) for this ROI. - See setPos() for an explanation of the update and finish arguments. - """ - self.state['angle'] = angle - tr = QtGui.QTransform() - #tr.rotate(-angle * 180 / np.pi) - tr.rotate(angle) - self.setTransform(tr) - if update: - self.stateChanged(finish=finish) - - def scale(self, s, center=[0,0], update=True, finish=True): - """ - Resize the ROI by scaling relative to *center*. - See setPos() for an explanation of the *update* and *finish* arguments. - """ - c = self.mapToScene(Point(center) * self.state['size']) - self.prepareGeometryChange() - newSize = self.state['size'] * s - c1 = self.mapToScene(Point(center) * newSize) - newPos = self.state['pos'] + c - c1 - - self.setSize(newSize, update=False) - self.setPos(self.state['pos'], update=update, finish=finish) - - - def translate(self, *args, **kargs): - """ - Move the ROI to a new position. - Accepts either (x, y, snap) or ([x,y], snap) as arguments - If the ROI is bounded and the move would exceed boundaries, then the ROI - is moved to the nearest acceptable position instead. - - snap can be: - None (default): use self.translateSnap and self.snapSize to determine whether/how to snap - False: do not snap - Point(w,h) snap to rectangular grid with spacing (w,h) - True: snap using self.snapSize (and ignoring self.translateSnap) - - Also accepts *update* and *finish* arguments (see setPos() for a description of these). - """ - - if len(args) == 1: - pt = args[0] - else: - pt = args - - newState = self.stateCopy() - newState['pos'] = newState['pos'] + pt - - ## snap position - #snap = kargs.get('snap', None) - #if (snap is not False) and not (snap is None and self.translateSnap is False): - - snap = kargs.get('snap', None) - if snap is None: - snap = self.translateSnap - if snap is not False: - newState['pos'] = self.getSnapPosition(newState['pos'], snap=snap) - - #d = ev.scenePos() - self.mapToScene(self.pressPos) - if self.maxBounds is not None: - r = self.stateRect(newState) - #r0 = self.sceneTransform().mapRect(self.boundingRect()) - d = Point(0,0) - if self.maxBounds.left() > r.left(): - d[0] = self.maxBounds.left() - r.left() - elif self.maxBounds.right() < r.right(): - d[0] = self.maxBounds.right() - r.right() - if self.maxBounds.top() > r.top(): - d[1] = self.maxBounds.top() - r.top() - elif self.maxBounds.bottom() < r.bottom(): - d[1] = self.maxBounds.bottom() - r.bottom() - newState['pos'] += d - - #self.state['pos'] = newState['pos'] - update = kargs.get('update', True) - finish = kargs.get('finish', True) - self.setPos(newState['pos'], update=update, finish=finish) - #if 'update' not in kargs or kargs['update'] is True: - #self.stateChanged() - - def rotate(self, angle, update=True, finish=True): - self.setAngle(self.angle()+angle, update=update, finish=finish) - - - def addTranslateHandle(self, pos, axes=None, item=None, name=None): - pos = Point(pos) - return self.addHandle({'name': name, 'type': 't', 'pos': pos, 'item': item}) - - def addFreeHandle(self, pos, axes=None, item=None, name=None): - pos = Point(pos) - return self.addHandle({'name': name, 'type': 'f', 'pos': pos, 'item': item}) - - def addScaleHandle(self, pos, center, axes=None, item=None, name=None, lockAspect=False): - pos = Point(pos) - center = Point(center) - info = {'name': name, 'type': 's', 'center': center, 'pos': pos, 'item': item, 'lockAspect': lockAspect} - if pos.x() == center.x(): - info['xoff'] = True - if pos.y() == center.y(): - info['yoff'] = True - return self.addHandle(info) - - def addRotateHandle(self, pos, center, item=None, name=None): - pos = Point(pos) - center = Point(center) - return self.addHandle({'name': name, 'type': 'r', 'center': center, 'pos': pos, 'item': item}) - - def addScaleRotateHandle(self, pos, center, item=None, name=None): - pos = Point(pos) - center = Point(center) - if pos[0] != center[0] and pos[1] != center[1]: - raise Exception("Scale/rotate handles must have either the same x or y coordinate as their center point.") - return self.addHandle({'name': name, 'type': 'sr', 'center': center, 'pos': pos, 'item': item}) - - def addRotateFreeHandle(self, pos, center, axes=None, item=None, name=None): - pos = Point(pos) - center = Point(center) - return self.addHandle({'name': name, 'type': 'rf', 'center': center, 'pos': pos, 'item': item}) - - def addHandle(self, info): - if not info.has_key('item') or info['item'] is None: - #print "BEFORE ADD CHILD:", self.childItems() - h = Handle(self.handleSize, typ=info['type'], pen=self.handlePen, parent=self) - #print "AFTER ADD CHILD:", self.childItems() - h.setPos(info['pos'] * self.state['size']) - info['item'] = h - else: - h = info['item'] - iid = len(self.handles) - h.connectROI(self, iid) - #h.mouseMoveEvent = lambda ev: self.pointMoveEvent(iid, ev) - #h.mousePressEvent = lambda ev: self.pointPressEvent(iid, ev) - #h.mouseReleaseEvent = lambda ev: self.pointReleaseEvent(iid, ev) - self.handles.append(info) - h.setZValue(self.zValue()+1) - #if self.isSelected(): - #h.show() - #else: - #h.hide() - return h - - def getLocalHandlePositions(self, index=None): - """Returns the position of a handle in ROI coordinates""" - if index == None: - positions = [] - for h in self.handles: - positions.append((h['name'], h['pos'])) - return positions - else: - return (self.handles[index]['name'], self.handles[index]['pos']) - - def getSceneHandlePositions(self, index = None): - if index == None: - positions = [] - for h in self.handles: - positions.append((h['name'], h['item'].scenePos())) - return positions - else: - return (self.handles[index]['name'], self.handles[index]['item'].scenePos()) - - - def mapSceneToParent(self, pt): - return self.mapToParent(self.mapFromScene(pt)) - - def setSelected(self, s): - QtGui.QGraphicsItem.setSelected(self, s) - #print "select", self, s - if s: - for h in self.handles: - h['item'].show() - else: - for h in self.handles: - h['item'].hide() - - - def hoverEvent(self, ev): - if self.translatable and (not ev.isExit()) and ev.acceptDrags(QtCore.Qt.LeftButton): - self.setMouseHover(True) - self.sigHoverEvent.emit(self) - else: - self.setMouseHover(False) - - def setMouseHover(self, hover): - ## Inform the ROI that the mouse is(not) hovering over it - if self.mouseHovering == hover: - return - self.mouseHovering = hover - if hover: - self.currentPen = fn.mkPen(255, 255, 0) - else: - self.currentPen = self.pen - self.update() - - - def mouseDragEvent(self, ev): - if ev.isStart(): - #p = ev.pos() - #if not self.isMoving and not self.shape().contains(p): - #ev.ignore() - #return - if ev.button() == QtCore.Qt.LeftButton: - self.setSelected(True) - if self.translatable: - self.isMoving = True - self.preMoveState = self.getState() - self.cursorOffset = self.pos() - self.mapToParent(ev.buttonDownPos()) - self.sigRegionChangeStarted.emit(self) - ev.accept() - else: - ev.ignore() - - elif ev.isFinish(): - if self.translatable: - if self.isMoving: - self.stateChangeFinished() - self.isMoving = False - return - - if self.translatable and self.isMoving and ev.buttons() == QtCore.Qt.LeftButton: - snap = True if (ev.modifiers() & QtCore.Qt.ControlModifier) else None - newPos = self.mapToParent(ev.pos()) + self.cursorOffset - self.translate(newPos - self.pos(), snap=snap, finish=False) - - def mouseClickEvent(self, ev): - if ev.button() == QtCore.Qt.RightButton: - if self.isMoving: - ev.accept() - self.cancelMove() - else: - ev.ignore() - - def cancelMove(self): - self.isMoving = False - self.setState(self.preMoveState) - - - #def pointDragEvent(self, pt, ev): - ### just for handling drag start/stop. - ### drag moves are handled through movePoint() - - #if ev.isStart(): - #self.isMoving = True - #self.preMoveState = self.getState() - - #self.sigRegionChangeStarted.emit(self) - #elif ev.isFinish(): - #self.isMoving = False - #self.sigRegionChangeFinished.emit(self) - #return - - - #def pointPressEvent(self, pt, ev): - ##print "press" - #self.isMoving = True - #self.preMoveState = self.getState() - - ##self.emit(QtCore.SIGNAL('regionChangeStarted'), self) - #self.sigRegionChangeStarted.emit(self) - ##self.pressPos = self.mapFromScene(ev.scenePos()) - ##self.pressHandlePos = self.handles[pt]['item'].pos() - - #def pointReleaseEvent(self, pt, ev): - ##print "release" - #self.isMoving = False - ##self.emit(QtCore.SIGNAL('regionChangeFinished'), self) - #self.sigRegionChangeFinished.emit(self) - - #def pointMoveEvent(self, pt, ev): - #self.movePoint(pt, ev.scenePos(), ev.modifiers()) - - - def checkPointMove(self, pt, pos, modifiers): - """When handles move, they must ask the ROI if the move is acceptable. - By default, this always returns True. Subclasses may wish override. - """ - return True - - - def movePoint(self, pt, pos, modifiers=QtCore.Qt.KeyboardModifier(), finish=True): - ## 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() - h = self.handles[pt] - p0 = self.mapToScene(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) - p1 = self.mapSceneToParent(p1) - - ## Handles with a 'center' need to know their local position relative to the center point (lp0, lp1) - if h.has_key('center'): - c = h['center'] - cs = c * self.state['size'] - lp0 = self.mapFromParent(p0) - cs - lp1 = self.mapFromParent(p1) - cs - - if h['type'] == 't': - snap = True if (modifiers & QtCore.Qt.ControlModifier) else None - #if self.translateSnap or (): - #snap = Point(self.snapSize, self.snapSize) - self.translate(p1-p0, snap=snap, update=False) - - elif h['type'] == 'f': - h['item'].setPos(self.mapFromScene(pos)) - self.freeHandleMoved = True - #self.sigRegionChanged.emit(self) ## should be taken care of by call to stateChanged() - - elif h['type'] == 's': - ## If a handle and its center have the same x or y value, we can't scale across that axis. - if h['center'][0] == h['pos'][0]: - lp1[0] = 0 - if h['center'][1] == h['pos'][1]: - lp1[1] = 0 - - ## snap - if self.scaleSnap or (modifiers & QtCore.Qt.ControlModifier): - lp1[0] = round(lp1[0] / self.snapSize) * self.snapSize - lp1[1] = round(lp1[1] / self.snapSize) * self.snapSize - - ## preserve aspect ratio (this can override snapping) - if h['lockAspect'] or (modifiers & QtCore.Qt.AltModifier): - #arv = Point(self.preMoveState['size']) - - lp1 = lp1.proj(lp0) - - ## determine scale factors and new size of ROI - hs = h['pos'] - c - if hs[0] == 0: - hs[0] = 1 - if hs[1] == 0: - hs[1] = 1 - newSize = lp1 / hs - - ## Perform some corrections and limit checks - if newSize[0] == 0: - newSize[0] = newState['size'][0] - if newSize[1] == 0: - newSize[1] = newState['size'][1] - if not self.invertible: - if newSize[0] < 0: - newSize[0] = newState['size'][0] - if newSize[1] < 0: - newSize[1] = newState['size'][1] - if self.aspectLocked: - newSize[0] = newSize[1] - - ## Move ROI so the center point occupies the same scene location after the scale - s0 = c * self.state['size'] - s1 = c * newSize - cc = self.mapToParent(s0 - s1) - self.mapToParent(Point(0, 0)) - - ## update state, do more boundary checks - newState['size'] = newSize - newState['pos'] = newState['pos'] + cc - if self.maxBounds is not None: - r = self.stateRect(newState) - if not self.maxBounds.contains(r): - return - - self.setPos(newState['pos'], update=False) - self.setSize(newState['size'], update=False) - - elif h['type'] in ['r', 'rf']: - if h['type'] == 'rf': - self.freeHandleMoved = True - - if not self.rotateAllowed: - return - ## If the handle is directly over its center point, we can't compute an angle. - if lp1.length() == 0 or lp0.length() == 0: - return - - ## determine new rotation angle, constrained if necessary - ang = newState['angle'] - lp0.angle(lp1) - if ang is None: ## this should never happen.. - return - if self.rotateSnap or (modifiers & QtCore.Qt.ControlModifier): - ang = round(ang / 15.) * 15. ## 180/12 = 15 - - ## create rotation transform - tr = QtGui.QTransform() - tr.rotate(ang) - - ## move ROI so that center point remains stationary after rotate - cc = self.mapToParent(cs) - (tr.map(cs) + self.state['pos']) - newState['angle'] = ang - newState['pos'] = newState['pos'] + cc - - ## check boundaries, update - if self.maxBounds is not None: - r = self.stateRect(newState) - if not self.maxBounds.contains(r): - return - #self.setTransform(tr) - self.setPos(newState['pos'], update=False) - self.setAngle(ang, update=False) - #self.state = newState - - ## If this is a free-rotate handle, its distance from the center may change. - - if h['type'] == 'rf': - h['item'].setPos(self.mapFromScene(p1)) ## changes ROI coordinates of handle - - elif h['type'] == 'sr': - if h['center'][0] == h['pos'][0]: - scaleAxis = 1 - else: - scaleAxis = 0 - - if lp1.length() == 0 or lp0.length() == 0: - return - - ang = newState['angle'] - lp0.angle(lp1) - if ang is None: - return - if self.rotateSnap or (modifiers & QtCore.Qt.ControlModifier): - #ang = round(ang / (np.pi/12.)) * (np.pi/12.) - ang = round(ang / 15.) * 15. - - hs = abs(h['pos'][scaleAxis] - c[scaleAxis]) - newState['size'][scaleAxis] = lp1.length() / hs - #if self.scaleSnap or (modifiers & QtCore.Qt.ControlModifier): - if self.scaleSnap: ## use CTRL only for angular snap here. - newState['size'][scaleAxis] = round(newState['size'][scaleAxis] / self.snapSize) * self.snapSize - if newState['size'][scaleAxis] == 0: - newState['size'][scaleAxis] = 1 - - c1 = c * newState['size'] - tr = QtGui.QTransform() - tr.rotate(ang) - - cc = self.mapToParent(cs) - (tr.map(c1) + self.state['pos']) - newState['angle'] = ang - newState['pos'] = newState['pos'] + cc - if self.maxBounds is not None: - r = self.stateRect(newState) - if not self.maxBounds.contains(r): - return - #self.setTransform(tr) - #self.setPos(newState['pos'], update=False) - #self.prepareGeometryChange() - #self.state = newState - self.setState(newState, update=False) - - self.stateChanged(finish=finish) - - def stateChanged(self, finish=True): - """Process changes to the state of the ROI. - If there are any changes, then the positions of handles are updated accordingly - and sigRegionChanged is emitted. If finish is True, then - sigRegionChangeFinished will also be emitted.""" - - changed = False - if self.lastState is None: - changed = True - else: - for k in self.state.keys(): - if self.state[k] != self.lastState[k]: - changed = True - self.lastState = self.stateCopy() - - if changed: - ## Move all handles to match the current configuration of the ROI - for h in self.handles: - if h['item'] in self.childItems(): - p = h['pos'] - h['item'].setPos(h['pos'] * self.state['size']) - - self.update() - self.sigRegionChanged.emit(self) - elif self.freeHandleMoved: - self.sigRegionChanged.emit(self) - - self.freeHandleMoved = False - - if finish: - self.stateChangeFinished() - - def stateChangeFinished(self): - self.sigRegionChangeFinished.emit(self) - - def stateRect(self, state): - r = QtCore.QRectF(0, 0, state['size'][0], state['size'][1]) - tr = QtGui.QTransform() - #tr.rotate(-state['angle'] * 180 / np.pi) - tr.rotate(-state['angle']) - r = tr.mapRect(r) - return r.adjusted(state['pos'][0], state['pos'][1], state['pos'][0], state['pos'][1]) - - - def getSnapPosition(self, pos, snap=None): - ## Given that pos has been requested, return the nearest snap-to position - ## optionally, snap may be passed in to specify a rectangular snap grid. - ## override this function for more interesting snap functionality.. - - if snap is None or snap is True: - if self.snapSize is None: - return pos - snap = Point(self.snapSize, self.snapSize) - - return Point( - round(pos[0] / snap[0]) * snap[0], - round(pos[1] / snap[1]) * snap[1] - ) - - - def boundingRect(self): - return QtCore.QRectF(0, 0, self.state['size'][0], self.state['size'][1]).normalized() - - def paint(self, p, opt, widget): - p.save() - r = self.boundingRect() - p.setRenderHint(QtGui.QPainter.Antialiasing) - p.setPen(self.currentPen) - p.translate(r.left(), r.top()) - p.scale(r.width(), r.height()) - p.drawRect(0, 0, 1, 1) - p.restore() - - def getArraySlice(self, data, img, axes=(0,1), returnSlice=True): - """Return a tuple of slice objects that can be used to slice the region from data covered by this ROI. - Also returns the transform which maps the ROI into data coordinates. - - If returnSlice is set to False, the function returns a pair of tuples with the values that would have - been used to generate the slice objects. ((ax0Start, ax0Stop), (ax1Start, ax1Stop))""" - #print "getArraySlice" - - ## Determine shape of array along ROI axes - dShape = (data.shape[axes[0]], data.shape[axes[1]]) - #print " dshape", dShape - - ## Determine transform that maps ROI bounding box to image coordinates - tr = self.sceneTransform() * img.sceneTransform().inverted()[0] - - ## Modify transform to scale from image coords to data coords - #m = QtGui.QTransform() - tr.scale(float(dShape[0]) / img.width(), float(dShape[1]) / img.height()) - #tr = tr * m - - ## Transform ROI bounds into data bounds - dataBounds = tr.mapRect(self.boundingRect()) - #print " boundingRect:", self.boundingRect() - #print " dataBounds:", dataBounds - - ## Intersect transformed ROI bounds with data bounds - intBounds = dataBounds.intersect(QtCore.QRectF(0, 0, dShape[0], dShape[1])) - #print " intBounds:", intBounds - - ## Determine index values to use when referencing the array. - bounds = ( - (int(min(intBounds.left(), intBounds.right())), int(1+max(intBounds.left(), intBounds.right()))), - (int(min(intBounds.bottom(), intBounds.top())), int(1+max(intBounds.bottom(), intBounds.top()))) - ) - #print " bounds:", bounds - - if returnSlice: - ## Create slice objects - sl = [slice(None)] * data.ndim - sl[axes[0]] = slice(*bounds[0]) - sl[axes[1]] = slice(*bounds[1]) - return tuple(sl), tr - else: - return bounds, tr - - - 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.""" - - - shape = self.state['size'] - - origin = self.mapToItem(img, QtCore.QPointF(0, 0)) - - ## vx and vy point in the directions of the slice axes, but must be scaled properly - vx = self.mapToItem(img, QtCore.QPointF(1, 0)) - origin - vy = self.mapToItem(img, QtCore.QPointF(0, 1)) - origin - - lvx = np.sqrt(vx.x()**2 + vx.y()**2) - lvy = np.sqrt(vy.x()**2 + vy.y()**2) - pxLen = img.width() / float(data.shape[axes[0]]) - sx = pxLen / lvx - sy = pxLen / lvy - - vectors = ((vx.x()*sx, vx.y()*sx), (vy.x()*sy, vy.y()*sy)) - shape = self.state['size'] - shape = [abs(shape[0]/sx), abs(shape[1]/sy)] - - origin = (origin.x(), origin.y()) - - #print "shape", shape, "vectors", vectors, "origin", origin - - return fn.affineSlice(data, shape=shape, vectors=vectors, origin=origin, axes=axes, order=1) - - ### transpose data so x and y are the first 2 axes - #trAx = range(0, data.ndim) - #trAx.remove(axes[0]) - #trAx.remove(axes[1]) - #tr1 = tuple(axes) + tuple(trAx) - #arr = data.transpose(tr1) - - ### Determine the minimal area of the data we will need - #(dataBounds, roiDataTransform) = self.getArraySlice(data, img, returnSlice=False, axes=axes) - - ### Pad data boundaries by 1px if possible - #dataBounds = ( - #(max(dataBounds[0][0]-1, 0), min(dataBounds[0][1]+1, arr.shape[0])), - #(max(dataBounds[1][0]-1, 0), min(dataBounds[1][1]+1, arr.shape[1])) - #) - - ### Extract minimal data from array - #arr1 = arr[dataBounds[0][0]:dataBounds[0][1], dataBounds[1][0]:dataBounds[1][1]] - - ### Update roiDataTransform to reflect this extraction - #roiDataTransform *= QtGui.QTransform().translate(-dataBounds[0][0], -dataBounds[1][0]) - #### (roiDataTransform now maps from ROI coords to extracted data coords) - - - ### Rotate array - #if abs(self.state['angle']) > 1e-5: - #arr2 = ndimage.rotate(arr1, self.state['angle'] * 180 / np.pi, order=1) - - ### update data transforms to reflect this rotation - #rot = QtGui.QTransform().rotate(self.state['angle'] * 180 / np.pi) - #roiDataTransform *= rot - - ### The rotation also causes a shift which must be accounted for: - #dataBound = QtCore.QRectF(0, 0, arr1.shape[0], arr1.shape[1]) - #rotBound = rot.mapRect(dataBound) - #roiDataTransform *= QtGui.QTransform().translate(-rotBound.left(), -rotBound.top()) - - #else: - #arr2 = arr1 - - - - #### Shift off partial pixels - ## 1. map ROI into current data space - #roiBounds = roiDataTransform.mapRect(self.boundingRect()) - - ## 2. Determine amount to shift data - #shift = (int(roiBounds.left()) - roiBounds.left(), int(roiBounds.bottom()) - roiBounds.bottom()) - #if abs(shift[0]) > 1e-6 or abs(shift[1]) > 1e-6: - ## 3. pad array with 0s before shifting - #arr2a = np.zeros((arr2.shape[0]+2, arr2.shape[1]+2) + arr2.shape[2:], dtype=arr2.dtype) - #arr2a[1:-1, 1:-1] = arr2 - - ## 4. shift array and udpate transforms - #arr3 = ndimage.shift(arr2a, shift + (0,)*(arr2.ndim-2), order=1) - #roiDataTransform *= QtGui.QTransform().translate(1+shift[0], 1+shift[1]) - #else: - #arr3 = arr2 - - - #### Extract needed region from rotated/shifted array - ## 1. map ROI into current data space (round these values off--they should be exact integer values at this point) - #roiBounds = roiDataTransform.mapRect(self.boundingRect()) - ##print self, roiBounds.height() - ##import traceback - ##traceback.print_stack() - - #roiBounds = QtCore.QRect(round(roiBounds.left()), round(roiBounds.top()), round(roiBounds.width()), round(roiBounds.height())) - - ##2. intersect ROI with data bounds - #dataBounds = roiBounds.intersect(QtCore.QRect(0, 0, arr3.shape[0], arr3.shape[1])) - - ##3. Extract data from array - #db = dataBounds - #bounds = ( - #(db.left(), db.right()+1), - #(db.top(), db.bottom()+1) - #) - #arr4 = arr3[bounds[0][0]:bounds[0][1], bounds[1][0]:bounds[1][1]] - - #### Create zero array in size of ROI - #arr5 = np.zeros((roiBounds.width(), roiBounds.height()) + arr4.shape[2:], dtype=arr4.dtype) - - ### Fill array with ROI data - #orig = Point(dataBounds.topLeft() - roiBounds.topLeft()) - #subArr = arr5[orig[0]:orig[0]+arr4.shape[0], orig[1]:orig[1]+arr4.shape[1]] - #subArr[:] = arr4[:subArr.shape[0], :subArr.shape[1]] - - - ### figure out the reverse transpose order - #tr2 = np.array(tr1) - #for i in range(0, len(tr2)): - #tr2[tr1[i]] = i - #tr2 = tuple(tr2) - - ### Untranspose array before returning - #return arr5.transpose(tr2) - - def getGlobalTransform(self, relativeTo=None): - """Return global transformation (rotation angle+translation) required to move from relative state to current state. If relative state isn't specified, - then we use the state of the ROI when mouse is pressed.""" - if relativeTo == None: - relativeTo = self.preMoveState - st = self.getState() - - ## this is only allowed because we will be comparing the two - relativeTo['scale'] = relativeTo['size'] - st['scale'] = st['size'] - - - - t1 = Transform(relativeTo) - t2 = Transform(st) - return t2/t1 - - - #st = self.getState() - - ### rotation - #ang = (st['angle']-relativeTo['angle']) * 180. / 3.14159265358 - #rot = QtGui.QTransform() - #rot.rotate(-ang) - - ### We need to come up with a universal transformation--one that can be applied to other objects - ### such that all maintain alignment. - ### More specifically, we need to turn the ROI's position and angle into - ### a rotation _around the origin_ and a translation. - - #p0 = Point(relativeTo['pos']) - - ### base position, rotated - #p1 = rot.map(p0) - - #trans = Point(st['pos']) - p1 - #return trans, ang - - def applyGlobalTransform(self, tr): - st = self.getState() - - st['scale'] = st['size'] - st = Transform(st) - st = (st * tr).saveState() - st['size'] = st['scale'] - self.setState(st) - - -class Handle(UIGraphicsItem): - - types = { ## defines number of sides, start angle for each handle type - 't': (4, np.pi/4), - 'f': (4, np.pi/4), - 's': (4, 0), - 'r': (12, 0), - 'sr': (12, 0), - 'rf': (12, 0), - } - - def __init__(self, radius, typ=None, pen=(200, 200, 220), parent=None): - #print " create item with parent", parent - #self.bounds = QtCore.QRectF(-1e-10, -1e-10, 2e-10, 2e-10) - #self.setFlags(self.ItemIgnoresTransformations | self.ItemSendsScenePositionChanges) - self.roi = [] - self.radius = radius - self.typ = typ - self.pen = fn.mkPen(pen) - self.currentPen = self.pen - self.pen.setWidth(0) - self.pen.setCosmetic(True) - self.isMoving = False - self.sides, self.startAng = self.types[typ] - self.buildPath() - self._shape = None - - UIGraphicsItem.__init__(self, parent=parent) - #self.updateShape() - self.setZValue(11) - - def connectROI(self, roi, i): - self.roi.append((roi, i)) - - #def boundingRect(self): - #return self.bounds - - def hoverEvent(self, ev): - if (not ev.isExit()) and ev.acceptDrags(QtCore.Qt.LeftButton): - self.currentPen = fn.mkPen(255, 255,0) - else: - self.currentPen = self.pen - self.update() - - - - def mouseClickEvent(self, ev): - ## right-click cancels drag - if ev.button() == QtCore.Qt.RightButton and self.isMoving: - self.isMoving = False ## prevents any further motion - self.movePoint(self.startPos, finish=True) - #for r in self.roi: - #r[0].cancelMove() - ev.accept() - - - def mouseDragEvent(self, ev): - if ev.button() != QtCore.Qt.LeftButton: - return - ev.accept() - - ## Inform ROIs that a drag is happening - ## note: the ROI is informed that the handle has moved using ROI.movePoint - ## this is for other (more nefarious) purposes. - #for r in self.roi: - #r[0].pointDragEvent(r[1], ev) - - if ev.isFinish(): - if self.isMoving: - for r in self.roi: - r[0].stateChangeFinished() - self.isMoving = False - elif ev.isStart(): - self.isMoving = True - self.startPos = self.scenePos() - self.cursorOffset = self.scenePos() - ev.buttonDownScenePos() - - if self.isMoving: ## note: isMoving may become False in mid-drag due to right-click. - pos = ev.scenePos() + self.cursorOffset - self.movePoint(pos, ev.modifiers(), finish=False) - - def movePoint(self, pos, modifiers=QtCore.Qt.KeyboardModifier(), finish=True): - for r in self.roi: - if not r[0].checkPointMove(r[1], pos, modifiers): - return - #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.roi: - r[0].movePoint(r[1], pos, modifiers, finish=finish) - - def buildPath(self): - size = self.radius - self.path = QtGui.QPainterPath() - ang = self.startAng - dt = 2*np.pi / self.sides - for i in range(0, self.sides+1): - x = size * cos(ang) - y = size * sin(ang) - ang += dt - if i == 0: - self.path.moveTo(x, y) - else: - self.path.lineTo(x, y) - - def paint(self, p, opt, widget): - ### determine rotation of transform - #m = self.sceneTransform() - ##mi = m.inverted()[0] - #v = m.map(QtCore.QPointF(1, 0)) - m.map(QtCore.QPointF(0, 0)) - #va = np.arctan2(v.y(), v.x()) - - ### Determine length of unit vector in painter's coords - ##size = mi.map(Point(self.radius, self.radius)) - mi.map(Point(0, 0)) - ##size = (size.x()*size.x() + size.y() * size.y()) ** 0.5 - #size = self.radius - - #bounds = QtCore.QRectF(-size, -size, size*2, size*2) - #if bounds != self.bounds: - #self.bounds = bounds - #self.prepareGeometryChange() - p.setRenderHints(p.Antialiasing, True) - p.setPen(self.currentPen) - - #p.rotate(va * 180. / 3.1415926) - #p.drawPath(self.path) - p.drawPath(self.shape()) - #ang = self.startAng + va - #dt = 2*np.pi / self.sides - #for i in range(0, self.sides): - #x1 = size * cos(ang) - #y1 = size * sin(ang) - #x2 = size * cos(ang+dt) - #y2 = size * sin(ang+dt) - #ang += dt - #p.drawLine(Point(x1, y1), Point(x2, y2)) - - def shape(self): - if self._shape is None: - s = self.generateShape() - if s is None: - return self.shape - self._shape = s - self.prepareGeometryChange() - return self._shape - - def boundingRect(self): - return self.shape().boundingRect() - - def generateShape(self): - ## 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: - self._shape = self.path - return None - - v = dt.map(QtCore.QPointF(1, 0)) - dt.map(QtCore.QPointF(0, 0)) - va = np.arctan2(v.y(), v.x()) - - dti = dt.inverted()[0] - devPos = dt.map(QtCore.QPointF(0,0)) - tr = QtGui.QTransform() - tr.translate(devPos.x(), devPos.y()) - tr.rotate(va * 180. / 3.1415926) - - return dti.map(tr.map(self.path)) - - - def viewChangedEvent(self): - self._shape = None ## invalidate shape, recompute later if requested. - #self.updateShape() - - #def itemChange(self, change, value): - #if change == self.ItemScenePositionHasChanged: - #self.updateShape() - - -class TestROI(ROI): - def __init__(self, pos, size, **args): - #QtGui.QGraphicsRectItem.__init__(self, pos[0], pos[1], size[0], size[1]) - ROI.__init__(self, pos, size, **args) - #self.addTranslateHandle([0, 0]) - self.addTranslateHandle([0.5, 0.5]) - self.addScaleHandle([1, 1], [0, 0]) - self.addScaleHandle([0, 0], [1, 1]) - self.addScaleRotateHandle([1, 0.5], [0.5, 0.5]) - self.addScaleHandle([0.5, 1], [0.5, 0.5]) - self.addRotateHandle([1, 0], [0, 0]) - self.addRotateHandle([0, 1], [1, 1]) - - - -class RectROI(ROI): - def __init__(self, pos, size, centered=False, sideScalers=False, **args): - #QtGui.QGraphicsRectItem.__init__(self, 0, 0, size[0], size[1]) - ROI.__init__(self, pos, size, **args) - if centered: - center = [0.5, 0.5] - else: - center = [0, 0] - - #self.addTranslateHandle(center) - self.addScaleHandle([1, 1], center) - if sideScalers: - self.addScaleHandle([1, 0.5], [center[0], 0.5]) - self.addScaleHandle([0.5, 1], [0.5, center[1]]) - -class LineROI(ROI): - def __init__(self, pos1, pos2, width, **args): - pos1 = Point(pos1) - pos2 = Point(pos2) - d = pos2-pos1 - l = d.length() - ang = Point(1, 0).angle(d) - ra = ang * np.pi / 180. - c = Point(-width/2. * sin(ra), -width/2. * cos(ra)) - pos1 = pos1 + c - - ROI.__init__(self, pos1, size=Point(l, width), angle=ang, **args) - self.addScaleRotateHandle([0, 0.5], [1, 0.5]) - self.addScaleRotateHandle([1, 0.5], [0, 0.5]) - self.addScaleHandle([0.5, 1], [0.5, 0.5]) - - -class MultiLineROI(QtGui.QGraphicsObject): - - sigRegionChangeFinished = QtCore.Signal(object) - sigRegionChangeStarted = QtCore.Signal(object) - sigRegionChanged = QtCore.Signal(object) - - def __init__(self, points, width, pen=None, **args): - QtGui.QGraphicsObject.__init__(self) - self.pen = pen - self.roiArgs = args - 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 - #self.addToGroup(l) - #l.connect(l, QtCore.SIGNAL('regionChanged'), self.roiChangedEvent) - l.sigRegionChanged.connect(self.roiChangedEvent) - #l.connect(l, QtCore.SIGNAL('regionChangeStarted'), self.roiChangeStartedEvent) - l.sigRegionChangeStarted.connect(self.roiChangeStartedEvent) - #l.connect(l, QtCore.SIGNAL('regionChangeFinished'), self.roiChangeFinishedEvent) - l.sigRegionChangeFinished.connect(self.roiChangeFinishedEvent) - - def paint(self, *args): - pass - - def boundingRect(self): - return QtCore.QRectF() - - def roiChangedEvent(self): - w = self.lines[0].state['size'][1] - for l in self.lines[1:]: - w0 = l.state['size'][1] - l.scale([1.0, w/w0], center=[0.5,0.5]) - #self.emit(QtCore.SIGNAL('regionChanged'), self) - self.sigRegionChanged.emit(self) - - def roiChangeStartedEvent(self): - #self.emit(QtCore.SIGNAL('regionChangeStarted'), self) - self.sigRegionChangeStarted.emit(self) - - def roiChangeFinishedEvent(self): - #self.emit(QtCore.SIGNAL('regionChangeFinished'), self) - self.sigRegionChangeFinished.emit(self) - - - def getArrayRegion(self, arr, img=None, axes=(0,1)): - rgns = [] - for l in self.lines: - rgn = l.getArrayRegion(arr, img, axes=axes) - if rgn is None: - continue - #return None - rgns.append(rgn) - #print l.state['size'] - - ## make sure orthogonal axis is the same size - ## (sometimes fp errors cause differences) - ms = min([r.shape[axes[1]] for r in rgns]) - sl = [slice(None)] * rgns[0].ndim - sl[axes[1]] = slice(0,ms) - rgns = [r[sl] for r in rgns] - #print [r.shape for r in rgns], axes - - return np.concatenate(rgns, axis=axes[0]) - - -class EllipseROI(ROI): - def __init__(self, pos, size, **args): - #QtGui.QGraphicsRectItem.__init__(self, 0, 0, size[0], size[1]) - ROI.__init__(self, pos, size, **args) - self.addRotateHandle([1.0, 0.5], [0.5, 0.5]) - self.addScaleHandle([0.5*2.**-0.5 + 0.5, 0.5*2.**-0.5 + 0.5], [0.5, 0.5]) - - def paint(self, p, opt, widget): - r = self.boundingRect() - p.setRenderHint(QtGui.QPainter.Antialiasing) - p.setPen(self.currentPen) - - p.scale(r.width(), r.height())## workaround for GL bug - r = QtCore.QRectF(r.x()/r.width(), r.y()/r.height(), 1,1) - - p.drawEllipse(r) - - def getArrayRegion(self, arr, img=None): - arr = ROI.getArrayRegion(self, arr, img) - if arr is None or arr.shape[0] == 0 or arr.shape[1] == 0: - return None - w = arr.shape[0] - h = arr.shape[1] - ## generate an ellipsoidal mask - mask = np.fromfunction(lambda x,y: (((x+0.5)/(w/2.)-1)**2+ ((y+0.5)/(h/2.)-1)**2)**0.5 < 1, (w, h)) - - return arr * mask - - def shape(self): - self.path = QtGui.QPainterPath() - self.path.addEllipse(self.boundingRect()) - return self.path - - -class CircleROI(EllipseROI): - def __init__(self, pos, size, **args): - ROI.__init__(self, pos, size, **args) - self.aspectLocked = True - #self.addTranslateHandle([0.5, 0.5]) - self.addScaleHandle([0.5*2.**-0.5 + 0.5, 0.5*2.**-0.5 + 0.5], [0.5, 0.5]) - -class PolygonROI(ROI): - def __init__(self, positions, pos=None, **args): - if pos is None: - pos = [0,0] - ROI.__init__(self, pos, [1,1], **args) - #ROI.__init__(self, positions[0]) - for p in positions: - self.addFreeHandle(p) - self.setZValue(1000) - - 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) - for i in range(len(self.handles)): - h1 = self.handles[i]['item'].pos() - h2 = self.handles[i-1]['item'].pos() - p.drawLine(h1, h2) - - def boundingRect(self): - 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() - p.moveTo(self.handles[0]['item'].pos()) - for i in range(len(self.handles)): - p.lineTo(self.handles[i]['item'].pos()) - 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 - - -class LineSegmentROI(ROI): - """ - ROI subclass with two or more freely-moving handles connecting lines. - """ - def __init__(self, positions, pos=None, **args): - if pos is None: - pos = [0,0] - ROI.__init__(self, pos, [1,1], **args) - #ROI.__init__(self, positions[0]) - for p in positions: - self.addFreeHandle(p) - self.setZValue(1000) - - 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) - 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): - 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() - p.moveTo(self.handles[0]['item'].pos()) - for i in range(len(self.handles)): - p.lineTo(self.handles[i]['item'].pos()) - 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. - Since this pulls 1D data from a 2D coordinate system, the return value will have ndim = data.ndim-1 - """ - - - #shape = self.state['size'] - - #origin = self.mapToItem(img, QtCore.QPointF(0, 0)) - - ## vx and vy point in the directions of the slice axes, but must be scaled properly - #vx = self.mapToItem(img, QtCore.QPointF(1, 0)) - origin - #vy = self.mapToItem(img, QtCore.QPointF(0, 1)) - origin - - imgPts = [self.mapToItem(img, h['item'].pos()) for h in self.handles] - rgns = [] - 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) - rgns.append(r) - - return np.concatenate(rgns, axis=axes[0]) - - - #lvx = np.sqrt(vx.x()**2 + vx.y()**2) - #lvy = np.sqrt(vy.x()**2 + vy.y()**2) - #pxLen = img.width() / float(data.shape[axes[0]]) - #sx = pxLen / lvx - #sy = pxLen / lvy - - #vectors = ((vx.x()*sx, vx.y()*sx), (vy.x()*sy, vy.y()*sy)) - #shape = self.state['size'] - #shape = [abs(shape[0]/sx), abs(shape[1]/sy)] - - #origin = (origin.x(), origin.y()) - - ##print "shape", shape, "vectors", vectors, "origin", origin - - #return fn.affineSlice(data, shape=shape, vectors=vectors, origin=origin, axes=axes, order=1) - - -class SpiralROI(ROI): - def __init__(self, pos=None, size=None, **args): - if size == None: - size = [100e-6,100e-6] - if pos == None: - pos = [0,0] - ROI.__init__(self, pos, size, **args) - self.translateSnap = False - self.addFreeHandle([0.25,0], name='a') - self.addRotateFreeHandle([1,0], [0,0], name='r') - #self.getRadius() - #QtCore.connect(self, QtCore.SIGNAL('regionChanged'), self. - - - def getRadius(self): - radius = Point(self.handles[1]['item'].pos()).length() - #r2 = radius[1] - #r3 = r2[0] - return radius - - def boundingRect(self): - r = self.getRadius() - 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 stateChanged(self): - ROI.stateChanged(self) - if len(self.handles) > 1: - self.path = QtGui.QPainterPath() - h0 = Point(self.handles[0]['item'].pos()).length() - a = h0/(2.0*np.pi) - theta = 30.0*(2.0*np.pi)/360.0 - self.path.moveTo(QtCore.QPointF(a*theta*cos(theta), a*theta*sin(theta))) - x0 = a*theta*cos(theta) - y0 = a*theta*sin(theta) - radius = self.getRadius() - theta += 20.0*(2.0*np.pi)/360.0 - i = 0 - while Point(x0, y0).length() < radius and i < 1000: - x1 = a*theta*cos(theta) - y1 = a*theta*sin(theta) - self.path.lineTo(QtCore.QPointF(x1,y1)) - theta += 20.0*(2.0*np.pi)/360.0 - x0 = x1 - y0 = y1 - i += 1 - - - return self.path - - - def shape(self): - p = QtGui.QPainterPath() - p.addEllipse(self.boundingRect()) - return p - - def paint(self, p, *args): - p.setRenderHint(QtGui.QPainter.Antialiasing) - #path = self.shape() - p.setPen(self.currentPen) - p.drawPath(self.path) - p.setPen(QtGui.QPen(QtGui.QColor(255,0,0))) - p.drawPath(self.shape()) - p.setPen(QtGui.QPen(QtGui.QColor(0,0,255))) - p.drawRect(self.boundingRect()) - - - - - +# -*- coding: utf-8 -*- +""" +ROI.py - Interactive graphics items for GraphicsView (ROI widgets) +Copyright 2010 Luke Campagnola +Distributed under MIT/X11 license. See license.txt for more infomation. + +Implements a series of graphics items which display movable/scalable/rotatable shapes +for use as region-of-interest markers. ROI class automatically handles extraction +of array data from ImageItems. + +The ROI class is meant to serve as the base for more specific types; see several examples +of how to build an ROI at the bottom of the file. +""" + +from pyqtgraph.Qt import QtCore, QtGui +#if not hasattr(QtCore, 'Signal'): + #QtCore.Signal = QtCore.pyqtSignal +import numpy as np +from numpy.linalg import norm +import scipy.ndimage as ndimage +from pyqtgraph.Point import * +from pyqtgraph.Transform import Transform +from math import cos, sin +import pyqtgraph.functions as fn +from .GraphicsObject import GraphicsObject +from .UIGraphicsItem import UIGraphicsItem + +__all__ = [ + 'ROI', + 'TestROI', 'RectROI', 'EllipseROI', 'CircleROI', 'PolygonROI', + 'LineROI', 'MultiLineROI', 'LineSegmentROI', 'SpiralROI', +] + + +def rectStr(r): + return "[%f, %f] + [%f, %f]" % (r.x(), r.y(), r.width(), r.height()) + +class ROI(GraphicsObject): + """Generic region-of-interest widget. + Can be used for implementing many types of selection box with rotate/translate/scale handles. + """ + + sigRegionChangeFinished = QtCore.Signal(object) + sigRegionChangeStarted = QtCore.Signal(object) + sigRegionChanged = QtCore.Signal(object) + sigHoverEvent = QtCore.Signal(object) + + def __init__(self, pos, size=Point(1, 1), angle=0.0, invertible=False, maxBounds=None, snapSize=1.0, scaleSnap=False, translateSnap=False, rotateSnap=False, parent=None, pen=None, movable=True): + #QObjectWorkaround.__init__(self) + GraphicsObject.__init__(self, parent) + pos = Point(pos) + size = Point(size) + self.aspectLocked = False + self.translatable = movable + self.rotateAllowed = True + + self.freeHandleMoved = False ## keep track of whether free handles have moved since last change signal was emitted. + self.mouseHovering = False + if pen is None: + pen = (255, 255, 255) + self.setPen(pen) + + self.handlePen = QtGui.QPen(QtGui.QColor(150, 255, 255)) + self.handles = [] + self.state = {'pos': Point(0,0), 'size': Point(1,1), 'angle': 0} ## angle is in degrees for ease of Qt integration + self.lastState = None + self.setPos(pos) + self.setAngle(angle) + self.setSize(size) + self.setZValue(10) + self.isMoving = False + + self.handleSize = 5 + self.invertible = invertible + self.maxBounds = maxBounds + + self.snapSize = snapSize + self.translateSnap = translateSnap + self.rotateSnap = rotateSnap + self.scaleSnap = scaleSnap + #self.setFlag(self.ItemIsSelectable, True) + + def getState(self): + return self.stateCopy() + + def stateCopy(self): + sc = {} + sc['pos'] = Point(self.state['pos']) + sc['size'] = Point(self.state['size']) + sc['angle'] = self.state['angle'] + return sc + + def saveState(self): + """Return the state of the widget in a format suitable for storing to disk. (Points are converted to tuple)""" + state = {} + state['pos'] = tuple(self.state['pos']) + state['size'] = tuple(self.state['size']) + state['angle'] = self.state['angle'] + return state + + def setState(self, state, update=True): + self.setPos(state['pos'], update=False) + self.setSize(state['size'], update=False) + self.setAngle(state['angle'], update=update) + + def setZValue(self, z): + QtGui.QGraphicsItem.setZValue(self, z) + for h in self.handles: + h['item'].setZValue(z+1) + + def parentBounds(self): + return self.mapToParent(self.boundingRect()).boundingRect() + + def setPen(self, pen): + self.pen = fn.mkPen(pen) + self.currentPen = self.pen + self.update() + + def size(self): + return self.getState()['size'] + + def pos(self): + return self.getState()['pos'] + + def angle(self): + return self.getState()['angle'] + + def setPos(self, pos, update=True, finish=True): + """Set the position of the ROI (in the parent's coordinate system). + By default, this will cause both sigStateChanged and sigStateChangeFinished to be emitted. + + If finish is False, then sigStateChangeFinished will not be emitted. You can then use + stateChangeFinished() to cause the signal to be emitted after a series of state changes. + + If update is False, the state change will be remembered but not processed and no signals + will be emitted. You can then use stateChanged() to complete the state change. This allows + multiple change functions to be called sequentially while minimizing processing overhead + and repeated signals. Setting update=False also forces finish=False. + """ + + pos = Point(pos) + self.state['pos'] = pos + QtGui.QGraphicsItem.setPos(self, pos) + if update: + self.stateChanged(finish=finish) + + def setSize(self, size, update=True, finish=True): + """Set the size of the ROI. May be specified as a QPoint, Point, or list of two values. + See setPos() for an explanation of the update and finish arguments. + """ + size = Point(size) + self.prepareGeometryChange() + self.state['size'] = size + if update: + self.stateChanged(finish=finish) + + def setAngle(self, angle, update=True, finish=True): + """Set the angle of rotation (in degrees) for this ROI. + See setPos() for an explanation of the update and finish arguments. + """ + self.state['angle'] = angle + tr = QtGui.QTransform() + #tr.rotate(-angle * 180 / np.pi) + tr.rotate(angle) + self.setTransform(tr) + if update: + self.stateChanged(finish=finish) + + def scale(self, s, center=[0,0], update=True, finish=True): + """ + Resize the ROI by scaling relative to *center*. + See setPos() for an explanation of the *update* and *finish* arguments. + """ + c = self.mapToScene(Point(center) * self.state['size']) + self.prepareGeometryChange() + newSize = self.state['size'] * s + c1 = self.mapToScene(Point(center) * newSize) + newPos = self.state['pos'] + c - c1 + + self.setSize(newSize, update=False) + self.setPos(self.state['pos'], update=update, finish=finish) + + + def translate(self, *args, **kargs): + """ + Move the ROI to a new position. + Accepts either (x, y, snap) or ([x,y], snap) as arguments + If the ROI is bounded and the move would exceed boundaries, then the ROI + is moved to the nearest acceptable position instead. + + snap can be: + None (default): use self.translateSnap and self.snapSize to determine whether/how to snap + False: do not snap + Point(w,h) snap to rectangular grid with spacing (w,h) + True: snap using self.snapSize (and ignoring self.translateSnap) + + Also accepts *update* and *finish* arguments (see setPos() for a description of these). + """ + + if len(args) == 1: + pt = args[0] + else: + pt = args + + newState = self.stateCopy() + newState['pos'] = newState['pos'] + pt + + ## snap position + #snap = kargs.get('snap', None) + #if (snap is not False) and not (snap is None and self.translateSnap is False): + + snap = kargs.get('snap', None) + if snap is None: + snap = self.translateSnap + if snap is not False: + newState['pos'] = self.getSnapPosition(newState['pos'], snap=snap) + + #d = ev.scenePos() - self.mapToScene(self.pressPos) + if self.maxBounds is not None: + r = self.stateRect(newState) + #r0 = self.sceneTransform().mapRect(self.boundingRect()) + d = Point(0,0) + if self.maxBounds.left() > r.left(): + d[0] = self.maxBounds.left() - r.left() + elif self.maxBounds.right() < r.right(): + d[0] = self.maxBounds.right() - r.right() + if self.maxBounds.top() > r.top(): + d[1] = self.maxBounds.top() - r.top() + elif self.maxBounds.bottom() < r.bottom(): + d[1] = self.maxBounds.bottom() - r.bottom() + newState['pos'] += d + + #self.state['pos'] = newState['pos'] + update = kargs.get('update', True) + finish = kargs.get('finish', True) + self.setPos(newState['pos'], update=update, finish=finish) + #if 'update' not in kargs or kargs['update'] is True: + #self.stateChanged() + + def rotate(self, angle, update=True, finish=True): + self.setAngle(self.angle()+angle, update=update, finish=finish) + + + def addTranslateHandle(self, pos, axes=None, item=None, name=None): + pos = Point(pos) + return self.addHandle({'name': name, 'type': 't', 'pos': pos, 'item': item}) + + def addFreeHandle(self, pos, axes=None, item=None, name=None): + pos = Point(pos) + return self.addHandle({'name': name, 'type': 'f', 'pos': pos, 'item': item}) + + def addScaleHandle(self, pos, center, axes=None, item=None, name=None, lockAspect=False): + pos = Point(pos) + center = Point(center) + info = {'name': name, 'type': 's', 'center': center, 'pos': pos, 'item': item, 'lockAspect': lockAspect} + if pos.x() == center.x(): + info['xoff'] = True + if pos.y() == center.y(): + info['yoff'] = True + return self.addHandle(info) + + def addRotateHandle(self, pos, center, item=None, name=None): + pos = Point(pos) + center = Point(center) + return self.addHandle({'name': name, 'type': 'r', 'center': center, 'pos': pos, 'item': item}) + + def addScaleRotateHandle(self, pos, center, item=None, name=None): + pos = Point(pos) + center = Point(center) + if pos[0] != center[0] and pos[1] != center[1]: + raise Exception("Scale/rotate handles must have either the same x or y coordinate as their center point.") + return self.addHandle({'name': name, 'type': 'sr', 'center': center, 'pos': pos, 'item': item}) + + def addRotateFreeHandle(self, pos, center, axes=None, item=None, name=None): + pos = Point(pos) + center = Point(center) + return self.addHandle({'name': name, 'type': 'rf', 'center': center, 'pos': pos, 'item': item}) + + def addHandle(self, info): + if 'item' not in info or info['item'] is None: + #print "BEFORE ADD CHILD:", self.childItems() + h = Handle(self.handleSize, typ=info['type'], pen=self.handlePen, parent=self) + #print "AFTER ADD CHILD:", self.childItems() + h.setPos(info['pos'] * self.state['size']) + info['item'] = h + else: + h = info['item'] + iid = len(self.handles) + h.connectROI(self, iid) + #h.mouseMoveEvent = lambda ev: self.pointMoveEvent(iid, ev) + #h.mousePressEvent = lambda ev: self.pointPressEvent(iid, ev) + #h.mouseReleaseEvent = lambda ev: self.pointReleaseEvent(iid, ev) + self.handles.append(info) + h.setZValue(self.zValue()+1) + #if self.isSelected(): + #h.show() + #else: + #h.hide() + return h + + def getLocalHandlePositions(self, index=None): + """Returns the position of a handle in ROI coordinates""" + if index == None: + positions = [] + for h in self.handles: + positions.append((h['name'], h['pos'])) + return positions + else: + return (self.handles[index]['name'], self.handles[index]['pos']) + + def getSceneHandlePositions(self, index = None): + if index == None: + positions = [] + for h in self.handles: + positions.append((h['name'], h['item'].scenePos())) + return positions + else: + return (self.handles[index]['name'], self.handles[index]['item'].scenePos()) + + + def mapSceneToParent(self, pt): + return self.mapToParent(self.mapFromScene(pt)) + + def setSelected(self, s): + QtGui.QGraphicsItem.setSelected(self, s) + #print "select", self, s + if s: + for h in self.handles: + h['item'].show() + else: + for h in self.handles: + h['item'].hide() + + + def hoverEvent(self, ev): + if self.translatable and (not ev.isExit()) and ev.acceptDrags(QtCore.Qt.LeftButton): + self.setMouseHover(True) + self.sigHoverEvent.emit(self) + else: + self.setMouseHover(False) + + def setMouseHover(self, hover): + ## Inform the ROI that the mouse is(not) hovering over it + if self.mouseHovering == hover: + return + self.mouseHovering = hover + if hover: + self.currentPen = fn.mkPen(255, 255, 0) + else: + self.currentPen = self.pen + self.update() + + + def mouseDragEvent(self, ev): + if ev.isStart(): + #p = ev.pos() + #if not self.isMoving and not self.shape().contains(p): + #ev.ignore() + #return + if ev.button() == QtCore.Qt.LeftButton: + self.setSelected(True) + if self.translatable: + self.isMoving = True + self.preMoveState = self.getState() + self.cursorOffset = self.pos() - self.mapToParent(ev.buttonDownPos()) + self.sigRegionChangeStarted.emit(self) + ev.accept() + else: + ev.ignore() + + elif ev.isFinish(): + if self.translatable: + if self.isMoving: + self.stateChangeFinished() + self.isMoving = False + return + + if self.translatable and self.isMoving and ev.buttons() == QtCore.Qt.LeftButton: + snap = True if (ev.modifiers() & QtCore.Qt.ControlModifier) else None + newPos = self.mapToParent(ev.pos()) + self.cursorOffset + self.translate(newPos - self.pos(), snap=snap, finish=False) + + def mouseClickEvent(self, ev): + if ev.button() == QtCore.Qt.RightButton: + if self.isMoving: + ev.accept() + self.cancelMove() + else: + ev.ignore() + + def cancelMove(self): + self.isMoving = False + self.setState(self.preMoveState) + + + #def pointDragEvent(self, pt, ev): + ### just for handling drag start/stop. + ### drag moves are handled through movePoint() + + #if ev.isStart(): + #self.isMoving = True + #self.preMoveState = self.getState() + + #self.sigRegionChangeStarted.emit(self) + #elif ev.isFinish(): + #self.isMoving = False + #self.sigRegionChangeFinished.emit(self) + #return + + + #def pointPressEvent(self, pt, ev): + ##print "press" + #self.isMoving = True + #self.preMoveState = self.getState() + + ##self.emit(QtCore.SIGNAL('regionChangeStarted'), self) + #self.sigRegionChangeStarted.emit(self) + ##self.pressPos = self.mapFromScene(ev.scenePos()) + ##self.pressHandlePos = self.handles[pt]['item'].pos() + + #def pointReleaseEvent(self, pt, ev): + ##print "release" + #self.isMoving = False + ##self.emit(QtCore.SIGNAL('regionChangeFinished'), self) + #self.sigRegionChangeFinished.emit(self) + + #def pointMoveEvent(self, pt, ev): + #self.movePoint(pt, ev.scenePos(), ev.modifiers()) + + + def checkPointMove(self, pt, pos, modifiers): + """When handles move, they must ask the ROI if the move is acceptable. + By default, this always returns True. Subclasses may wish override. + """ + return True + + + def movePoint(self, pt, pos, modifiers=QtCore.Qt.KeyboardModifier(), finish=True): + ## 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() + h = self.handles[pt] + p0 = self.mapToScene(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) + 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: + c = h['center'] + cs = c * self.state['size'] + lp0 = self.mapFromParent(p0) - cs + lp1 = self.mapFromParent(p1) - cs + + if h['type'] == 't': + snap = True if (modifiers & QtCore.Qt.ControlModifier) else None + #if self.translateSnap or (): + #snap = Point(self.snapSize, self.snapSize) + self.translate(p1-p0, snap=snap, update=False) + + elif h['type'] == 'f': + h['item'].setPos(self.mapFromScene(pos)) + self.freeHandleMoved = True + #self.sigRegionChanged.emit(self) ## should be taken care of by call to stateChanged() + + elif h['type'] == 's': + ## If a handle and its center have the same x or y value, we can't scale across that axis. + if h['center'][0] == h['pos'][0]: + lp1[0] = 0 + if h['center'][1] == h['pos'][1]: + lp1[1] = 0 + + ## snap + if self.scaleSnap or (modifiers & QtCore.Qt.ControlModifier): + lp1[0] = round(lp1[0] / self.snapSize) * self.snapSize + lp1[1] = round(lp1[1] / self.snapSize) * self.snapSize + + ## preserve aspect ratio (this can override snapping) + if h['lockAspect'] or (modifiers & QtCore.Qt.AltModifier): + #arv = Point(self.preMoveState['size']) - + lp1 = lp1.proj(lp0) + + ## determine scale factors and new size of ROI + hs = h['pos'] - c + if hs[0] == 0: + hs[0] = 1 + if hs[1] == 0: + hs[1] = 1 + newSize = lp1 / hs + + ## Perform some corrections and limit checks + if newSize[0] == 0: + newSize[0] = newState['size'][0] + if newSize[1] == 0: + newSize[1] = newState['size'][1] + if not self.invertible: + if newSize[0] < 0: + newSize[0] = newState['size'][0] + if newSize[1] < 0: + newSize[1] = newState['size'][1] + if self.aspectLocked: + newSize[0] = newSize[1] + + ## Move ROI so the center point occupies the same scene location after the scale + s0 = c * self.state['size'] + s1 = c * newSize + cc = self.mapToParent(s0 - s1) - self.mapToParent(Point(0, 0)) + + ## update state, do more boundary checks + newState['size'] = newSize + newState['pos'] = newState['pos'] + cc + if self.maxBounds is not None: + r = self.stateRect(newState) + if not self.maxBounds.contains(r): + return + + self.setPos(newState['pos'], update=False) + self.setSize(newState['size'], update=False) + + elif h['type'] in ['r', 'rf']: + if h['type'] == 'rf': + self.freeHandleMoved = True + + if not self.rotateAllowed: + return + ## If the handle is directly over its center point, we can't compute an angle. + if lp1.length() == 0 or lp0.length() == 0: + return + + ## determine new rotation angle, constrained if necessary + ang = newState['angle'] - lp0.angle(lp1) + if ang is None: ## this should never happen.. + return + if self.rotateSnap or (modifiers & QtCore.Qt.ControlModifier): + ang = round(ang / 15.) * 15. ## 180/12 = 15 + + ## create rotation transform + tr = QtGui.QTransform() + tr.rotate(ang) + + ## move ROI so that center point remains stationary after rotate + cc = self.mapToParent(cs) - (tr.map(cs) + self.state['pos']) + newState['angle'] = ang + newState['pos'] = newState['pos'] + cc + + ## check boundaries, update + if self.maxBounds is not None: + r = self.stateRect(newState) + if not self.maxBounds.contains(r): + return + #self.setTransform(tr) + self.setPos(newState['pos'], update=False) + self.setAngle(ang, update=False) + #self.state = newState + + ## If this is a free-rotate handle, its distance from the center may change. + + if h['type'] == 'rf': + h['item'].setPos(self.mapFromScene(p1)) ## changes ROI coordinates of handle + + elif h['type'] == 'sr': + if h['center'][0] == h['pos'][0]: + scaleAxis = 1 + else: + scaleAxis = 0 + + if lp1.length() == 0 or lp0.length() == 0: + return + + ang = newState['angle'] - lp0.angle(lp1) + if ang is None: + return + if self.rotateSnap or (modifiers & QtCore.Qt.ControlModifier): + #ang = round(ang / (np.pi/12.)) * (np.pi/12.) + ang = round(ang / 15.) * 15. + + hs = abs(h['pos'][scaleAxis] - c[scaleAxis]) + newState['size'][scaleAxis] = lp1.length() / hs + #if self.scaleSnap or (modifiers & QtCore.Qt.ControlModifier): + if self.scaleSnap: ## use CTRL only for angular snap here. + newState['size'][scaleAxis] = round(newState['size'][scaleAxis] / self.snapSize) * self.snapSize + if newState['size'][scaleAxis] == 0: + newState['size'][scaleAxis] = 1 + + c1 = c * newState['size'] + tr = QtGui.QTransform() + tr.rotate(ang) + + cc = self.mapToParent(cs) - (tr.map(c1) + self.state['pos']) + newState['angle'] = ang + newState['pos'] = newState['pos'] + cc + if self.maxBounds is not None: + r = self.stateRect(newState) + if not self.maxBounds.contains(r): + return + #self.setTransform(tr) + #self.setPos(newState['pos'], update=False) + #self.prepareGeometryChange() + #self.state = newState + self.setState(newState, update=False) + + self.stateChanged(finish=finish) + + def stateChanged(self, finish=True): + """Process changes to the state of the ROI. + If there are any changes, then the positions of handles are updated accordingly + and sigRegionChanged is emitted. If finish is True, then + sigRegionChangeFinished will also be emitted.""" + + changed = False + if self.lastState is None: + changed = True + else: + for k in list(self.state.keys()): + if self.state[k] != self.lastState[k]: + changed = True + self.lastState = self.stateCopy() + + if changed: + ## Move all handles to match the current configuration of the ROI + for h in self.handles: + if h['item'] in self.childItems(): + p = h['pos'] + h['item'].setPos(h['pos'] * self.state['size']) + + self.update() + self.sigRegionChanged.emit(self) + elif self.freeHandleMoved: + self.sigRegionChanged.emit(self) + + self.freeHandleMoved = False + + if finish: + self.stateChangeFinished() + + def stateChangeFinished(self): + self.sigRegionChangeFinished.emit(self) + + def stateRect(self, state): + r = QtCore.QRectF(0, 0, state['size'][0], state['size'][1]) + tr = QtGui.QTransform() + #tr.rotate(-state['angle'] * 180 / np.pi) + tr.rotate(-state['angle']) + r = tr.mapRect(r) + return r.adjusted(state['pos'][0], state['pos'][1], state['pos'][0], state['pos'][1]) + + + def getSnapPosition(self, pos, snap=None): + ## Given that pos has been requested, return the nearest snap-to position + ## optionally, snap may be passed in to specify a rectangular snap grid. + ## override this function for more interesting snap functionality.. + + if snap is None or snap is True: + if self.snapSize is None: + return pos + snap = Point(self.snapSize, self.snapSize) + + return Point( + round(pos[0] / snap[0]) * snap[0], + round(pos[1] / snap[1]) * snap[1] + ) + + + def boundingRect(self): + return QtCore.QRectF(0, 0, self.state['size'][0], self.state['size'][1]).normalized() + + def paint(self, p, opt, widget): + p.save() + r = self.boundingRect() + p.setRenderHint(QtGui.QPainter.Antialiasing) + p.setPen(self.currentPen) + p.translate(r.left(), r.top()) + p.scale(r.width(), r.height()) + p.drawRect(0, 0, 1, 1) + p.restore() + + def getArraySlice(self, data, img, axes=(0,1), returnSlice=True): + """Return a tuple of slice objects that can be used to slice the region from data covered by this ROI. + Also returns the transform which maps the ROI into data coordinates. + + If returnSlice is set to False, the function returns a pair of tuples with the values that would have + been used to generate the slice objects. ((ax0Start, ax0Stop), (ax1Start, ax1Stop))""" + #print "getArraySlice" + + ## Determine shape of array along ROI axes + dShape = (data.shape[axes[0]], data.shape[axes[1]]) + #print " dshape", dShape + + ## Determine transform that maps ROI bounding box to image coordinates + tr = self.sceneTransform() * img.sceneTransform().inverted()[0] + + ## Modify transform to scale from image coords to data coords + #m = QtGui.QTransform() + tr.scale(float(dShape[0]) / img.width(), float(dShape[1]) / img.height()) + #tr = tr * m + + ## Transform ROI bounds into data bounds + dataBounds = tr.mapRect(self.boundingRect()) + #print " boundingRect:", self.boundingRect() + #print " dataBounds:", dataBounds + + ## Intersect transformed ROI bounds with data bounds + intBounds = dataBounds.intersect(QtCore.QRectF(0, 0, dShape[0], dShape[1])) + #print " intBounds:", intBounds + + ## Determine index values to use when referencing the array. + bounds = ( + (int(min(intBounds.left(), intBounds.right())), int(1+max(intBounds.left(), intBounds.right()))), + (int(min(intBounds.bottom(), intBounds.top())), int(1+max(intBounds.bottom(), intBounds.top()))) + ) + #print " bounds:", bounds + + if returnSlice: + ## Create slice objects + sl = [slice(None)] * data.ndim + sl[axes[0]] = slice(*bounds[0]) + sl[axes[1]] = slice(*bounds[1]) + return tuple(sl), tr + else: + return bounds, tr + + + 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.""" + + + shape = self.state['size'] + + origin = self.mapToItem(img, QtCore.QPointF(0, 0)) + + ## vx and vy point in the directions of the slice axes, but must be scaled properly + vx = self.mapToItem(img, QtCore.QPointF(1, 0)) - origin + vy = self.mapToItem(img, QtCore.QPointF(0, 1)) - origin + + lvx = np.sqrt(vx.x()**2 + vx.y()**2) + lvy = np.sqrt(vy.x()**2 + vy.y()**2) + pxLen = img.width() / float(data.shape[axes[0]]) + sx = pxLen / lvx + sy = pxLen / lvy + + vectors = ((vx.x()*sx, vx.y()*sx), (vy.x()*sy, vy.y()*sy)) + shape = self.state['size'] + shape = [abs(shape[0]/sx), abs(shape[1]/sy)] + + origin = (origin.x(), origin.y()) + + #print "shape", shape, "vectors", vectors, "origin", origin + + return fn.affineSlice(data, shape=shape, vectors=vectors, origin=origin, axes=axes, order=1) + + ### transpose data so x and y are the first 2 axes + #trAx = range(0, data.ndim) + #trAx.remove(axes[0]) + #trAx.remove(axes[1]) + #tr1 = tuple(axes) + tuple(trAx) + #arr = data.transpose(tr1) + + ### Determine the minimal area of the data we will need + #(dataBounds, roiDataTransform) = self.getArraySlice(data, img, returnSlice=False, axes=axes) + + ### Pad data boundaries by 1px if possible + #dataBounds = ( + #(max(dataBounds[0][0]-1, 0), min(dataBounds[0][1]+1, arr.shape[0])), + #(max(dataBounds[1][0]-1, 0), min(dataBounds[1][1]+1, arr.shape[1])) + #) + + ### Extract minimal data from array + #arr1 = arr[dataBounds[0][0]:dataBounds[0][1], dataBounds[1][0]:dataBounds[1][1]] + + ### Update roiDataTransform to reflect this extraction + #roiDataTransform *= QtGui.QTransform().translate(-dataBounds[0][0], -dataBounds[1][0]) + #### (roiDataTransform now maps from ROI coords to extracted data coords) + + + ### Rotate array + #if abs(self.state['angle']) > 1e-5: + #arr2 = ndimage.rotate(arr1, self.state['angle'] * 180 / np.pi, order=1) + + ### update data transforms to reflect this rotation + #rot = QtGui.QTransform().rotate(self.state['angle'] * 180 / np.pi) + #roiDataTransform *= rot + + ### The rotation also causes a shift which must be accounted for: + #dataBound = QtCore.QRectF(0, 0, arr1.shape[0], arr1.shape[1]) + #rotBound = rot.mapRect(dataBound) + #roiDataTransform *= QtGui.QTransform().translate(-rotBound.left(), -rotBound.top()) + + #else: + #arr2 = arr1 + + + + #### Shift off partial pixels + ## 1. map ROI into current data space + #roiBounds = roiDataTransform.mapRect(self.boundingRect()) + + ## 2. Determine amount to shift data + #shift = (int(roiBounds.left()) - roiBounds.left(), int(roiBounds.bottom()) - roiBounds.bottom()) + #if abs(shift[0]) > 1e-6 or abs(shift[1]) > 1e-6: + ## 3. pad array with 0s before shifting + #arr2a = np.zeros((arr2.shape[0]+2, arr2.shape[1]+2) + arr2.shape[2:], dtype=arr2.dtype) + #arr2a[1:-1, 1:-1] = arr2 + + ## 4. shift array and udpate transforms + #arr3 = ndimage.shift(arr2a, shift + (0,)*(arr2.ndim-2), order=1) + #roiDataTransform *= QtGui.QTransform().translate(1+shift[0], 1+shift[1]) + #else: + #arr3 = arr2 + + + #### Extract needed region from rotated/shifted array + ## 1. map ROI into current data space (round these values off--they should be exact integer values at this point) + #roiBounds = roiDataTransform.mapRect(self.boundingRect()) + ##print self, roiBounds.height() + ##import traceback + ##traceback.print_stack() + + #roiBounds = QtCore.QRect(round(roiBounds.left()), round(roiBounds.top()), round(roiBounds.width()), round(roiBounds.height())) + + ##2. intersect ROI with data bounds + #dataBounds = roiBounds.intersect(QtCore.QRect(0, 0, arr3.shape[0], arr3.shape[1])) + + ##3. Extract data from array + #db = dataBounds + #bounds = ( + #(db.left(), db.right()+1), + #(db.top(), db.bottom()+1) + #) + #arr4 = arr3[bounds[0][0]:bounds[0][1], bounds[1][0]:bounds[1][1]] + + #### Create zero array in size of ROI + #arr5 = np.zeros((roiBounds.width(), roiBounds.height()) + arr4.shape[2:], dtype=arr4.dtype) + + ### Fill array with ROI data + #orig = Point(dataBounds.topLeft() - roiBounds.topLeft()) + #subArr = arr5[orig[0]:orig[0]+arr4.shape[0], orig[1]:orig[1]+arr4.shape[1]] + #subArr[:] = arr4[:subArr.shape[0], :subArr.shape[1]] + + + ### figure out the reverse transpose order + #tr2 = np.array(tr1) + #for i in range(0, len(tr2)): + #tr2[tr1[i]] = i + #tr2 = tuple(tr2) + + ### Untranspose array before returning + #return arr5.transpose(tr2) + + def getGlobalTransform(self, relativeTo=None): + """Return global transformation (rotation angle+translation) required to move from relative state to current state. If relative state isn't specified, + then we use the state of the ROI when mouse is pressed.""" + if relativeTo == None: + relativeTo = self.preMoveState + st = self.getState() + + ## this is only allowed because we will be comparing the two + relativeTo['scale'] = relativeTo['size'] + st['scale'] = st['size'] + + + + t1 = Transform(relativeTo) + t2 = Transform(st) + return t2/t1 + + + #st = self.getState() + + ### rotation + #ang = (st['angle']-relativeTo['angle']) * 180. / 3.14159265358 + #rot = QtGui.QTransform() + #rot.rotate(-ang) + + ### We need to come up with a universal transformation--one that can be applied to other objects + ### such that all maintain alignment. + ### More specifically, we need to turn the ROI's position and angle into + ### a rotation _around the origin_ and a translation. + + #p0 = Point(relativeTo['pos']) + + ### base position, rotated + #p1 = rot.map(p0) + + #trans = Point(st['pos']) - p1 + #return trans, ang + + def applyGlobalTransform(self, tr): + st = self.getState() + + st['scale'] = st['size'] + st = Transform(st) + st = (st * tr).saveState() + st['size'] = st['scale'] + self.setState(st) + + +class Handle(UIGraphicsItem): + + types = { ## defines number of sides, start angle for each handle type + 't': (4, np.pi/4), + 'f': (4, np.pi/4), + 's': (4, 0), + 'r': (12, 0), + 'sr': (12, 0), + 'rf': (12, 0), + } + + def __init__(self, radius, typ=None, pen=(200, 200, 220), parent=None): + #print " create item with parent", parent + #self.bounds = QtCore.QRectF(-1e-10, -1e-10, 2e-10, 2e-10) + #self.setFlags(self.ItemIgnoresTransformations | self.ItemSendsScenePositionChanges) + self.roi = [] + self.radius = radius + self.typ = typ + self.pen = fn.mkPen(pen) + self.currentPen = self.pen + self.pen.setWidth(0) + self.pen.setCosmetic(True) + self.isMoving = False + self.sides, self.startAng = self.types[typ] + self.buildPath() + self._shape = None + + UIGraphicsItem.__init__(self, parent=parent) + #self.updateShape() + self.setZValue(11) + + def connectROI(self, roi, i): + self.roi.append((roi, i)) + + #def boundingRect(self): + #return self.bounds + + def hoverEvent(self, ev): + if (not ev.isExit()) and ev.acceptDrags(QtCore.Qt.LeftButton): + self.currentPen = fn.mkPen(255, 255,0) + else: + self.currentPen = self.pen + self.update() + + + + def mouseClickEvent(self, ev): + ## right-click cancels drag + if ev.button() == QtCore.Qt.RightButton and self.isMoving: + self.isMoving = False ## prevents any further motion + self.movePoint(self.startPos, finish=True) + #for r in self.roi: + #r[0].cancelMove() + ev.accept() + + + def mouseDragEvent(self, ev): + if ev.button() != QtCore.Qt.LeftButton: + return + ev.accept() + + ## Inform ROIs that a drag is happening + ## note: the ROI is informed that the handle has moved using ROI.movePoint + ## this is for other (more nefarious) purposes. + #for r in self.roi: + #r[0].pointDragEvent(r[1], ev) + + if ev.isFinish(): + if self.isMoving: + for r in self.roi: + r[0].stateChangeFinished() + self.isMoving = False + elif ev.isStart(): + self.isMoving = True + self.startPos = self.scenePos() + self.cursorOffset = self.scenePos() - ev.buttonDownScenePos() + + if self.isMoving: ## note: isMoving may become False in mid-drag due to right-click. + pos = ev.scenePos() + self.cursorOffset + self.movePoint(pos, ev.modifiers(), finish=False) + + def movePoint(self, pos, modifiers=QtCore.Qt.KeyboardModifier(), finish=True): + for r in self.roi: + if not r[0].checkPointMove(r[1], pos, modifiers): + return + #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.roi: + r[0].movePoint(r[1], pos, modifiers, finish=finish) + + def buildPath(self): + size = self.radius + self.path = QtGui.QPainterPath() + ang = self.startAng + dt = 2*np.pi / self.sides + for i in range(0, self.sides+1): + x = size * cos(ang) + y = size * sin(ang) + ang += dt + if i == 0: + self.path.moveTo(x, y) + else: + self.path.lineTo(x, y) + + def paint(self, p, opt, widget): + ### determine rotation of transform + #m = self.sceneTransform() + ##mi = m.inverted()[0] + #v = m.map(QtCore.QPointF(1, 0)) - m.map(QtCore.QPointF(0, 0)) + #va = np.arctan2(v.y(), v.x()) + + ### Determine length of unit vector in painter's coords + ##size = mi.map(Point(self.radius, self.radius)) - mi.map(Point(0, 0)) + ##size = (size.x()*size.x() + size.y() * size.y()) ** 0.5 + #size = self.radius + + #bounds = QtCore.QRectF(-size, -size, size*2, size*2) + #if bounds != self.bounds: + #self.bounds = bounds + #self.prepareGeometryChange() + p.setRenderHints(p.Antialiasing, True) + p.setPen(self.currentPen) + + #p.rotate(va * 180. / 3.1415926) + #p.drawPath(self.path) + p.drawPath(self.shape()) + #ang = self.startAng + va + #dt = 2*np.pi / self.sides + #for i in range(0, self.sides): + #x1 = size * cos(ang) + #y1 = size * sin(ang) + #x2 = size * cos(ang+dt) + #y2 = size * sin(ang+dt) + #ang += dt + #p.drawLine(Point(x1, y1), Point(x2, y2)) + + def shape(self): + if self._shape is None: + s = self.generateShape() + if s is None: + return self.shape + self._shape = s + self.prepareGeometryChange() + return self._shape + + def boundingRect(self): + return self.shape().boundingRect() + + def generateShape(self): + ## 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: + self._shape = self.path + return None + + v = dt.map(QtCore.QPointF(1, 0)) - dt.map(QtCore.QPointF(0, 0)) + va = np.arctan2(v.y(), v.x()) + + dti = dt.inverted()[0] + devPos = dt.map(QtCore.QPointF(0,0)) + tr = QtGui.QTransform() + tr.translate(devPos.x(), devPos.y()) + tr.rotate(va * 180. / 3.1415926) + + return dti.map(tr.map(self.path)) + + + def viewChangedEvent(self): + self._shape = None ## invalidate shape, recompute later if requested. + #self.updateShape() + + #def itemChange(self, change, value): + #if change == self.ItemScenePositionHasChanged: + #self.updateShape() + + +class TestROI(ROI): + def __init__(self, pos, size, **args): + #QtGui.QGraphicsRectItem.__init__(self, pos[0], pos[1], size[0], size[1]) + ROI.__init__(self, pos, size, **args) + #self.addTranslateHandle([0, 0]) + self.addTranslateHandle([0.5, 0.5]) + self.addScaleHandle([1, 1], [0, 0]) + self.addScaleHandle([0, 0], [1, 1]) + self.addScaleRotateHandle([1, 0.5], [0.5, 0.5]) + self.addScaleHandle([0.5, 1], [0.5, 0.5]) + self.addRotateHandle([1, 0], [0, 0]) + self.addRotateHandle([0, 1], [1, 1]) + + + +class RectROI(ROI): + def __init__(self, pos, size, centered=False, sideScalers=False, **args): + #QtGui.QGraphicsRectItem.__init__(self, 0, 0, size[0], size[1]) + ROI.__init__(self, pos, size, **args) + if centered: + center = [0.5, 0.5] + else: + center = [0, 0] + + #self.addTranslateHandle(center) + self.addScaleHandle([1, 1], center) + if sideScalers: + self.addScaleHandle([1, 0.5], [center[0], 0.5]) + self.addScaleHandle([0.5, 1], [0.5, center[1]]) + +class LineROI(ROI): + def __init__(self, pos1, pos2, width, **args): + pos1 = Point(pos1) + pos2 = Point(pos2) + d = pos2-pos1 + l = d.length() + ang = Point(1, 0).angle(d) + ra = ang * np.pi / 180. + c = Point(-width/2. * sin(ra), -width/2. * cos(ra)) + pos1 = pos1 + c + + ROI.__init__(self, pos1, size=Point(l, width), angle=ang, **args) + self.addScaleRotateHandle([0, 0.5], [1, 0.5]) + self.addScaleRotateHandle([1, 0.5], [0, 0.5]) + self.addScaleHandle([0.5, 1], [0.5, 0.5]) + + +class MultiLineROI(QtGui.QGraphicsObject): + + sigRegionChangeFinished = QtCore.Signal(object) + sigRegionChangeStarted = QtCore.Signal(object) + sigRegionChanged = QtCore.Signal(object) + + def __init__(self, points, width, pen=None, **args): + QtGui.QGraphicsObject.__init__(self) + self.pen = pen + self.roiArgs = args + 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 + #self.addToGroup(l) + #l.connect(l, QtCore.SIGNAL('regionChanged'), self.roiChangedEvent) + l.sigRegionChanged.connect(self.roiChangedEvent) + #l.connect(l, QtCore.SIGNAL('regionChangeStarted'), self.roiChangeStartedEvent) + l.sigRegionChangeStarted.connect(self.roiChangeStartedEvent) + #l.connect(l, QtCore.SIGNAL('regionChangeFinished'), self.roiChangeFinishedEvent) + l.sigRegionChangeFinished.connect(self.roiChangeFinishedEvent) + + def paint(self, *args): + pass + + def boundingRect(self): + return QtCore.QRectF() + + def roiChangedEvent(self): + w = self.lines[0].state['size'][1] + for l in self.lines[1:]: + w0 = l.state['size'][1] + l.scale([1.0, w/w0], center=[0.5,0.5]) + #self.emit(QtCore.SIGNAL('regionChanged'), self) + self.sigRegionChanged.emit(self) + + def roiChangeStartedEvent(self): + #self.emit(QtCore.SIGNAL('regionChangeStarted'), self) + self.sigRegionChangeStarted.emit(self) + + def roiChangeFinishedEvent(self): + #self.emit(QtCore.SIGNAL('regionChangeFinished'), self) + self.sigRegionChangeFinished.emit(self) + + + def getArrayRegion(self, arr, img=None, axes=(0,1)): + rgns = [] + for l in self.lines: + rgn = l.getArrayRegion(arr, img, axes=axes) + if rgn is None: + continue + #return None + rgns.append(rgn) + #print l.state['size'] + + ## make sure orthogonal axis is the same size + ## (sometimes fp errors cause differences) + ms = min([r.shape[axes[1]] for r in rgns]) + sl = [slice(None)] * rgns[0].ndim + sl[axes[1]] = slice(0,ms) + rgns = [r[sl] for r in rgns] + #print [r.shape for r in rgns], axes + + return np.concatenate(rgns, axis=axes[0]) + + +class EllipseROI(ROI): + def __init__(self, pos, size, **args): + #QtGui.QGraphicsRectItem.__init__(self, 0, 0, size[0], size[1]) + ROI.__init__(self, pos, size, **args) + self.addRotateHandle([1.0, 0.5], [0.5, 0.5]) + self.addScaleHandle([0.5*2.**-0.5 + 0.5, 0.5*2.**-0.5 + 0.5], [0.5, 0.5]) + + def paint(self, p, opt, widget): + r = self.boundingRect() + p.setRenderHint(QtGui.QPainter.Antialiasing) + p.setPen(self.currentPen) + + p.scale(r.width(), r.height())## workaround for GL bug + r = QtCore.QRectF(r.x()/r.width(), r.y()/r.height(), 1,1) + + p.drawEllipse(r) + + def getArrayRegion(self, arr, img=None): + arr = ROI.getArrayRegion(self, arr, img) + if arr is None or arr.shape[0] == 0 or arr.shape[1] == 0: + return None + w = arr.shape[0] + h = arr.shape[1] + ## generate an ellipsoidal mask + mask = np.fromfunction(lambda x,y: (((x+0.5)/(w/2.)-1)**2+ ((y+0.5)/(h/2.)-1)**2)**0.5 < 1, (w, h)) + + return arr * mask + + def shape(self): + self.path = QtGui.QPainterPath() + self.path.addEllipse(self.boundingRect()) + return self.path + + +class CircleROI(EllipseROI): + def __init__(self, pos, size, **args): + ROI.__init__(self, pos, size, **args) + self.aspectLocked = True + #self.addTranslateHandle([0.5, 0.5]) + self.addScaleHandle([0.5*2.**-0.5 + 0.5, 0.5*2.**-0.5 + 0.5], [0.5, 0.5]) + +class PolygonROI(ROI): + def __init__(self, positions, pos=None, **args): + if pos is None: + pos = [0,0] + ROI.__init__(self, pos, [1,1], **args) + #ROI.__init__(self, positions[0]) + for p in positions: + self.addFreeHandle(p) + self.setZValue(1000) + + 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) + for i in range(len(self.handles)): + h1 = self.handles[i]['item'].pos() + h2 = self.handles[i-1]['item'].pos() + p.drawLine(h1, h2) + + def boundingRect(self): + 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() + p.moveTo(self.handles[0]['item'].pos()) + for i in range(len(self.handles)): + p.lineTo(self.handles[i]['item'].pos()) + 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 + + +class LineSegmentROI(ROI): + """ + ROI subclass with two or more freely-moving handles connecting lines. + """ + def __init__(self, positions, pos=None, **args): + if pos is None: + pos = [0,0] + ROI.__init__(self, pos, [1,1], **args) + #ROI.__init__(self, positions[0]) + for p in positions: + self.addFreeHandle(p) + self.setZValue(1000) + + 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) + 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): + 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() + p.moveTo(self.handles[0]['item'].pos()) + for i in range(len(self.handles)): + p.lineTo(self.handles[i]['item'].pos()) + 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. + Since this pulls 1D data from a 2D coordinate system, the return value will have ndim = data.ndim-1 + """ + + + #shape = self.state['size'] + + #origin = self.mapToItem(img, QtCore.QPointF(0, 0)) + + ## vx and vy point in the directions of the slice axes, but must be scaled properly + #vx = self.mapToItem(img, QtCore.QPointF(1, 0)) - origin + #vy = self.mapToItem(img, QtCore.QPointF(0, 1)) - origin + + imgPts = [self.mapToItem(img, h['item'].pos()) for h in self.handles] + rgns = [] + 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) + rgns.append(r) + + return np.concatenate(rgns, axis=axes[0]) + + + #lvx = np.sqrt(vx.x()**2 + vx.y()**2) + #lvy = np.sqrt(vy.x()**2 + vy.y()**2) + #pxLen = img.width() / float(data.shape[axes[0]]) + #sx = pxLen / lvx + #sy = pxLen / lvy + + #vectors = ((vx.x()*sx, vx.y()*sx), (vy.x()*sy, vy.y()*sy)) + #shape = self.state['size'] + #shape = [abs(shape[0]/sx), abs(shape[1]/sy)] + + #origin = (origin.x(), origin.y()) + + ##print "shape", shape, "vectors", vectors, "origin", origin + + #return fn.affineSlice(data, shape=shape, vectors=vectors, origin=origin, axes=axes, order=1) + + +class SpiralROI(ROI): + def __init__(self, pos=None, size=None, **args): + if size == None: + size = [100e-6,100e-6] + if pos == None: + pos = [0,0] + ROI.__init__(self, pos, size, **args) + self.translateSnap = False + self.addFreeHandle([0.25,0], name='a') + self.addRotateFreeHandle([1,0], [0,0], name='r') + #self.getRadius() + #QtCore.connect(self, QtCore.SIGNAL('regionChanged'), self. + + + def getRadius(self): + radius = Point(self.handles[1]['item'].pos()).length() + #r2 = radius[1] + #r3 = r2[0] + return radius + + def boundingRect(self): + r = self.getRadius() + 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 stateChanged(self): + ROI.stateChanged(self) + if len(self.handles) > 1: + self.path = QtGui.QPainterPath() + h0 = Point(self.handles[0]['item'].pos()).length() + a = h0/(2.0*np.pi) + theta = 30.0*(2.0*np.pi)/360.0 + self.path.moveTo(QtCore.QPointF(a*theta*cos(theta), a*theta*sin(theta))) + x0 = a*theta*cos(theta) + y0 = a*theta*sin(theta) + radius = self.getRadius() + theta += 20.0*(2.0*np.pi)/360.0 + i = 0 + while Point(x0, y0).length() < radius and i < 1000: + x1 = a*theta*cos(theta) + y1 = a*theta*sin(theta) + self.path.lineTo(QtCore.QPointF(x1,y1)) + theta += 20.0*(2.0*np.pi)/360.0 + x0 = x1 + y0 = y1 + i += 1 + + + return self.path + + + def shape(self): + p = QtGui.QPainterPath() + p.addEllipse(self.boundingRect()) + return p + + def paint(self, p, *args): + p.setRenderHint(QtGui.QPainter.Antialiasing) + #path = self.shape() + p.setPen(self.currentPen) + p.drawPath(self.path) + p.setPen(QtGui.QPen(QtGui.QColor(255,0,0))) + p.drawPath(self.shape()) + p.setPen(QtGui.QPen(QtGui.QColor(0,0,255))) + p.drawRect(self.boundingRect()) + + + + + \ No newline at end of file diff --git a/graphicsItems/ScaleBar.py b/graphicsItems/ScaleBar.py index be84f822..08fa94c3 100644 --- a/graphicsItems/ScaleBar.py +++ b/graphicsItems/ScaleBar.py @@ -1,5 +1,5 @@ from pyqtgraph.Qt import QtGui, QtCore -from UIGraphicsItem import * +from .UIGraphicsItem import * import numpy as np import pyqtgraph.functions as fn diff --git a/graphicsItems/ScatterPlotItem.py b/graphicsItems/ScatterPlotItem.py index cafca2a6..2d7a168b 100644 --- a/graphicsItems/ScatterPlotItem.py +++ b/graphicsItems/ScatterPlotItem.py @@ -1,8 +1,8 @@ from pyqtgraph.Qt import QtGui, QtCore from pyqtgraph.Point import Point import pyqtgraph.functions as fn -from GraphicsItem import GraphicsItem -from GraphicsObject import GraphicsObject +from .GraphicsItem import GraphicsItem +from .GraphicsObject import GraphicsObject import numpy as np import scipy.stats import weakref @@ -26,7 +26,7 @@ coords = { (0.05, -0.05), (0.05, -0.5), (-0.05, -0.5), (-0.05, -0.05) ], } -for k, c in coords.iteritems(): +for k, c in coords.items(): Symbols[k].moveTo(*c[0]) for x,y in c[1:]: Symbols[k].lineTo(x, y) @@ -190,7 +190,7 @@ class ScatterPlotItem(GraphicsObject): if 'spots' in kargs: spots = kargs['spots'] - for i in xrange(len(spots)): + for i in range(len(spots)): spot = spots[i] for k in spot: #if k == 'pen': @@ -518,7 +518,7 @@ class SpotItem(GraphicsItem): symbol = self._plot.opts['symbol'] try: n = int(symbol) - symbol = Symbols.keys()[n % len(Symbols)] + symbol = list(Symbols.keys())[n % len(Symbols)] except: pass return symbol diff --git a/graphicsItems/TextItem.py b/graphicsItems/TextItem.py index 734de9c3..b7d0f3b7 100644 --- a/graphicsItems/TextItem.py +++ b/graphicsItems/TextItem.py @@ -1,6 +1,6 @@ from pyqtgraph.Qt import QtCore, QtGui import pyqtgraph as pg -from UIGraphicsItem import * +from .UIGraphicsItem import * class TextItem(UIGraphicsItem): """ diff --git a/graphicsItems/UIGraphicsItem.py b/graphicsItems/UIGraphicsItem.py index c1a2d47c..cc105751 100644 --- a/graphicsItems/UIGraphicsItem.py +++ b/graphicsItems/UIGraphicsItem.py @@ -1,6 +1,6 @@ from pyqtgraph.Qt import QtGui, QtCore import weakref -from GraphicsObject import GraphicsObject +from .GraphicsObject import GraphicsObject __all__ = ['UIGraphicsItem'] class UIGraphicsItem(GraphicsObject): diff --git a/graphicsItems/VTickGroup.py b/graphicsItems/VTickGroup.py index 1c9c4f87..85ed596e 100644 --- a/graphicsItems/VTickGroup.py +++ b/graphicsItems/VTickGroup.py @@ -6,7 +6,7 @@ if __name__ == '__main__': from pyqtgraph.Qt import QtGui, QtCore import pyqtgraph.functions as fn import weakref -from UIGraphicsItem import UIGraphicsItem +from .UIGraphicsItem import UIGraphicsItem __all__ = ['VTickGroup'] class VTickGroup(UIGraphicsItem): diff --git a/graphicsItems/ViewBox/ViewBox.py b/graphicsItems/ViewBox/ViewBox.py index 04fda9e3..8278326a 100644 --- a/graphicsItems/ViewBox/ViewBox.py +++ b/graphicsItems/ViewBox/ViewBox.py @@ -278,7 +278,7 @@ class ViewBox(GraphicsWidget): vr1 = self.state['viewRange'][1] return QtCore.QRectF(vr0[0], vr1[0], vr0[1]-vr0[0], vr1[1] - vr1[0]) except: - print "make qrectf failed:", self.state['viewRange'] + print("make qrectf failed:", self.state['viewRange']) raise #def viewportTransform(self): @@ -299,7 +299,7 @@ class ViewBox(GraphicsWidget): tr1 = self.state['targetRange'][1] return QtCore.QRectF(tr0[0], tr1[0], tr0[1]-tr0[0], tr1[1] - tr1[0]) except: - print "make qrectf failed:", self.state['targetRange'] + print("make qrectf failed:", self.state['targetRange']) raise def setRange(self, rect=None, xRange=None, yRange=None, padding=0.02, update=True, disableAutoRange=True): @@ -330,7 +330,7 @@ class ViewBox(GraphicsWidget): raise Exception("Must specify at least one of rect, xRange, or yRange.") changed = [False, False] - for ax, range in changes.iteritems(): + for ax, range in changes.items(): mn = min(range) mx = max(range) if mn == mx: ## If we requested 0 range, try to preserve previous scale. Otherwise just pick an arbitrary scale. @@ -366,7 +366,7 @@ class ViewBox(GraphicsWidget): if update: self.updateMatrix(changed) - for ax, range in changes.iteritems(): + for ax, range in changes.items(): link = self.state['linkedViews'][ax] if link is not None: link.linkedViewChanged(self, ax) @@ -1105,8 +1105,9 @@ class ViewBox(GraphicsWidget): return wins + alpha ## make a sorted list of all named views - nv = ViewBox.NamedViews.values() - nv.sort(cmpViews) + nv = list(ViewBox.NamedViews.values()) + + sortList(nv, cmpViews) ## see pyqtgraph.python2_3.sortList if self in nv: nv.remove(self) @@ -1121,4 +1122,4 @@ class ViewBox(GraphicsWidget): -from ViewBoxMenu import ViewBoxMenu +from .ViewBoxMenu import ViewBoxMenu diff --git a/graphicsItems/ViewBox/ViewBoxMenu.py b/graphicsItems/ViewBox/ViewBoxMenu.py index cc0a9bbf..73bd2874 100644 --- a/graphicsItems/ViewBox/ViewBoxMenu.py +++ b/graphicsItems/ViewBox/ViewBoxMenu.py @@ -1,6 +1,6 @@ from pyqtgraph.Qt import QtCore, QtGui from pyqtgraph.WidgetGroup import WidgetGroup -from axisCtrlTemplate import Ui_Form as AxisCtrlTemplate +from .axisCtrlTemplate import Ui_Form as AxisCtrlTemplate class ViewBoxMenu(QtGui.QMenu): def __init__(self, view): @@ -87,7 +87,7 @@ class ViewBoxMenu(QtGui.QMenu): def setExportMethods(self, methods): self.exportMethods = methods self.export.clear() - for opt, fn in methods.iteritems(): + for opt, fn in methods.items(): self.export.addAction(opt, self.exportMethod) @@ -224,7 +224,7 @@ class ViewBoxMenu(QtGui.QMenu): views = [''] + views for i in [0,1]: c = self.ctrl[i].linkCombo - current = unicode(c.currentText()) + current = asUnicode(c.currentText()) c.blockSignals(True) changed = True try: @@ -241,6 +241,6 @@ class ViewBoxMenu(QtGui.QMenu): c.setCurrentIndex(0) c.currentIndexChanged.emit(c.currentIndex()) -from ViewBox import ViewBox +from .ViewBox import ViewBox \ No newline at end of file diff --git a/graphicsItems/ViewBox/__init__.py b/graphicsItems/ViewBox/__init__.py index 448283ef..685a314d 100644 --- a/graphicsItems/ViewBox/__init__.py +++ b/graphicsItems/ViewBox/__init__.py @@ -1 +1 @@ -from ViewBox import ViewBox +from .ViewBox import ViewBox diff --git a/graphicsWindows.py b/graphicsWindows.py index f2cf87f4..9e5090e1 100644 --- a/graphicsWindows.py +++ b/graphicsWindows.py @@ -5,11 +5,11 @@ Copyright 2010 Luke Campagnola Distributed under MIT/X11 license. See license.txt for more infomation. """ -from Qt import QtCore, QtGui -from widgets.PlotWidget import * -from imageview import * -from widgets.GraphicsLayoutWidget import GraphicsLayoutWidget -from widgets.GraphicsView import GraphicsView +from .Qt import QtCore, QtGui +from .widgets.PlotWidget import * +from .imageview import * +from .widgets.GraphicsLayoutWidget import GraphicsLayoutWidget +from .widgets.GraphicsView import GraphicsView QAPP = None def mkQApp(): diff --git a/imageview/ImageView.py b/imageview/ImageView.py index 26fbfdb8..1b68fe60 100644 --- a/imageview/ImageView.py +++ b/imageview/ImageView.py @@ -13,7 +13,7 @@ Widget used for displaying 2D or 3D data. Features: - Image normalization through a variety of methods """ -from ImageViewTemplate import * +from .ImageViewTemplate import * from pyqtgraph.graphicsItems.ImageItem import * from pyqtgraph.graphicsItems.ROI import * from pyqtgraph.graphicsItems.LinearRegionItem import * @@ -322,7 +322,7 @@ class ImageView(QtGui.QWidget): if self.imageDisp is None: image = self.normalize(self.image) self.imageDisp = image - self.levelMin, self.levelMax = map(float, ImageView.quickMinMax(self.imageDisp)) + self.levelMin, self.levelMax = list(map(float, ImageView.quickMinMax(self.imageDisp))) self.ui.histogram.setHistogramRange(self.levelMin, self.levelMax) return self.imageDisp @@ -383,7 +383,7 @@ class ImageView(QtGui.QWidget): def evalKeyState(self): if len(self.keysPressed) == 1: - key = self.keysPressed.keys()[0] + key = list(self.keysPressed.keys())[0] if key == QtCore.Qt.Key_Right: self.play(20) self.jumpFrames(1) @@ -526,7 +526,7 @@ class ImageView(QtGui.QWidget): if image.ndim == 3: self.roiCurve.setData(y=data, x=self.tVals) else: - self.roiCurve.setData(y=data, x=range(len(data))) + self.roiCurve.setData(y=data, x=list(range(len(data)))) #self.ui.roiPlot.replot() diff --git a/imageview/__init__.py b/imageview/__init__.py index 7bbbe122..0060e823 100644 --- a/imageview/__init__.py +++ b/imageview/__init__.py @@ -3,4 +3,4 @@ Widget used for display and analysis of 2D and 3D image data. Includes ROI plotting over time and image normalization. """ -from ImageView import ImageView +from .ImageView import ImageView diff --git a/metaarray/MetaArray.py b/metaarray/MetaArray.py index 5f8d32e6..159a9bf9 100644 --- a/metaarray/MetaArray.py +++ b/metaarray/MetaArray.py @@ -13,6 +13,7 @@ More info at http://www.scipy.org/Cookbook/MetaArray import numpy as np import types, copy, threading, os, re import pickle +from functools import reduce #import traceback ## By default, the library will use HDF5 when writing files. @@ -39,7 +40,7 @@ def axis(name=None, cols=None, values=None, units=None): if cols is not None: ax['cols'] = [] for c in cols: - if type(c) != types.ListType and type(c) != types.TupleType: + if type(c) != list and type(c) != tuple: c = [c] col = {} for i in range(0,len(c)): @@ -111,7 +112,7 @@ class MetaArray(np.ndarray): def __new__(subtype, data=None, file=None, info=None, dtype=None, copy=False, **kwargs): if data is not None: - if type(data) is types.TupleType: + if type(data) is tuple: subarr = np.empty(data, dtype=dtype) else: subarr = np.array(data, dtype=dtype, copy=copy) @@ -134,14 +135,14 @@ class MetaArray(np.ndarray): info[i] = {} else: raise Exception("Axis specification must be Dict or None") - if i < subarr.ndim and info[i].has_key('values'): - if type(info[i]['values']) is types.ListType: + if i < subarr.ndim and 'values' in info[i]: + if type(info[i]['values']) is list: info[i]['values'] = np.array(info[i]['values']) elif type(info[i]['values']) is not np.ndarray: raise Exception("Axis values must be specified as list or ndarray") if info[i]['values'].ndim != 1 or info[i]['values'].shape[0] != subarr.shape[i]: raise Exception("Values array for axis %d has incorrect shape. (given %s, but should be %s)" % (i, str(info[i]['values'].shape), str((subarr.shape[i],)))) - if i < subarr.ndim and info[i].has_key('cols'): + if i < subarr.ndim and 'cols' in info[i]: if not isinstance(info[i]['cols'], list): info[i]['cols'] = list(info[i]['cols']) if len(info[i]['cols']) != subarr.shape[i]: @@ -272,7 +273,7 @@ class MetaArray(np.ndarray): try: return np.ndarray.__setitem__(self.view(np.ndarray), nInd, val) except: - print self, nInd, val + print(self, nInd, val) raise #def __getattr__(self, attr): @@ -283,7 +284,7 @@ class MetaArray(np.ndarray): def axisValues(self, axis): """Return the list of values for an axis""" ax = self._interpretAxis(axis) - if self._info[ax].has_key('values'): + if 'values' in self._info[ax]: return self._info[ax]['values'] else: raise Exception('Array axis %s (%d) has no associated values.' % (str(axis), ax)) @@ -294,21 +295,21 @@ class MetaArray(np.ndarray): def axisHasValues(self, axis): ax = self._interpretAxis(axis) - return self._info[ax].has_key('values') + return 'values' in self._info[ax] def axisHasColumns(self, axis): ax = self._interpretAxis(axis) - return self._info[ax].has_key('cols') + return 'cols' in self._info[ax] def axisUnits(self, axis): """Return the units for axis""" ax = self._info[self._interpretAxis(axis)] - if ax.has_key('units'): + if 'units' in ax: return ax['units'] def hasColumn(self, axis, col): ax = self._info[self._interpretAxis(axis)] - if ax.has_key('cols'): + if 'cols' in ax: for c in ax['cols']: if c['name'] == col: return True @@ -339,7 +340,7 @@ class MetaArray(np.ndarray): def columnUnits(self, axis, column): """Return the units for column in axis""" ax = self._info[self._interpretAxis(axis)] - if ax.has_key('cols'): + if 'cols' in ax: for c in ax['cols']: if c['name'] == column: return c['units'] @@ -353,10 +354,10 @@ class MetaArray(np.ndarray): keyList = self[key] order = keyList.argsort() - if type(axis) == types.IntType: + if type(axis) == int: ind = [slice(None)]*axis ind.append(order) - elif type(axis) == types.StringType: + elif isinstance(axis, basestring): ind = (slice(axis, order),) return self[tuple(ind)] @@ -422,7 +423,7 @@ class MetaArray(np.ndarray): return tuple(nInd) def _interpretAxis(self, axis): - if type(axis) in [types.StringType, types.TupleType]: + if isinstance(axis, basestring) or isinstance(axis, tuple): return self._getAxis(axis) else: return axis @@ -510,15 +511,15 @@ class MetaArray(np.ndarray): def _getAxis(self, name): for i in range(0, len(self._info)): axis = self._info[i] - if axis.has_key('name') and axis['name'] == name: + if 'name' in axis and axis['name'] == name: return i raise Exception("No axis named %s.\n info=%s" % (name, self._info)) def _getIndex(self, axis, name): ax = self._info[axis] - if ax is not None and ax.has_key('cols'): + if ax is not None and 'cols' in ax: for i in range(0, len(ax['cols'])): - if ax['cols'][i].has_key('name') and ax['cols'][i]['name'] == name: + if 'name' in ax['cols'][i] and ax['cols'][i]['name'] == name: return i raise Exception("Axis %d has no column named %s.\n info=%s" % (axis, name, self._info)) @@ -527,16 +528,16 @@ class MetaArray(np.ndarray): def _axisSlice(self, i, cols): #print "axisSlice", i, cols - if self._info[i].has_key('cols') or self._info[i].has_key('values'): + if 'cols' in self._info[i] or 'values' in self._info[i]: ax = self._axisCopy(i) - if ax.has_key('cols'): + if 'cols' in ax: #print " slicing columns..", array(ax['cols']), cols sl = np.array(ax['cols'])[cols] if isinstance(sl, np.ndarray): sl = list(sl) ax['cols'] = sl #print " result:", ax['cols'] - if ax.has_key('values'): + if 'values' in ax: ax['values'] = np.array(ax['values'])[cols] else: ax = self._info[i] @@ -617,14 +618,14 @@ class MetaArray(np.ndarray): order = args order = [self._interpretAxis(ax) for ax in order] - infoOrder = order + range(len(order), len(self._info)) + infoOrder = order + list(range(len(order), len(self._info))) info = [self._info[i] for i in infoOrder] - order = order + range(len(order), self.ndim) + order = order + list(range(len(order), self.ndim)) try: return MetaArray(self.view(np.ndarray).transpose(order), info=info) except: - print order + print(order) raise #### File I/O Routines @@ -652,7 +653,7 @@ class MetaArray(np.ndarray): ## read in axis values for any axis that specifies a length frameSize = 1 for ax in meta['info']: - if ax.has_key('values_len'): + if 'values_len' in ax: ax['values'] = np.fromstring(fd.read(ax['values_len']), dtype=ax['values_type']) frameSize *= ax['values_len'] del ax['values_len'] @@ -675,7 +676,7 @@ class MetaArray(np.ndarray): ## read in axis values for any axis that specifies a length for i in range(len(meta['info'])): ax = meta['info'][i] - if ax.has_key('values_len'): + if 'values_len' in ax: if ax['values_len'] == 'dynamic': if dynAxis is not None: raise Exception("MetaArray has more than one dynamic axis! (this is not allowed)") @@ -779,7 +780,7 @@ class MetaArray(np.ndarray): f = h5py.File(fileName, 'r') ver = f.attrs['MetaArray'] if ver > MetaArray.version: - print "Warning: This file was written with MetaArray version %s, but you are using version %s. (Will attempt to read anyway)" % (str(ver), str(MetaArray.version)) + print("Warning: This file was written with MetaArray version %s, but you are using version %s. (Will attempt to read anyway)" % (str(ver), str(MetaArray.version))) meta = MetaArray.readHDF5Meta(f['info']) if mmap: @@ -974,12 +975,12 @@ class MetaArray(np.ndarray): else: gr.attrs['_metaType_'] = 'tuple' #n = int(np.log10(len(data))) + 1 - for i in xrange(len(data)): + for i in range(len(data)): self.writeHDF5Meta(gr, str(i), data[i], **dsOpts) elif isinstance(data, dict): gr = root.create_group(name) gr.attrs['_metaType_'] = 'dict' - for k, v in data.iteritems(): + for k, v in data.items(): self.writeHDF5Meta(gr, k, v, **dsOpts) elif isinstance(data, int) or isinstance(data, float) or isinstance(data, np.integer) or isinstance(data, np.floating): root.attrs[name] = data @@ -987,7 +988,7 @@ class MetaArray(np.ndarray): try: ## strings, bools, None are stored as repr() strings root.attrs[name] = repr(data) except: - print "Can not store meta data of type '%s' in HDF5. (key is '%s')" % (str(type(data)), str(name)) + print("Can not store meta data of type '%s' in HDF5. (key is '%s')" % (str(type(data)), str(name))) raise @@ -1053,7 +1054,7 @@ class MetaArray(np.ndarray): if fileName is not None: file = open(fileName, 'w') ret = '' - if self._info[0].has_key('cols'): + if 'cols' in self._info[0]: s = ','.join([x['name'] for x in self._info[0]['cols']]) + '\n' if fileName is not None: file.write(s) @@ -1177,91 +1178,91 @@ if __name__ == '__main__': ma = MetaArray(arr, info=info) - print "==== Original Array =======" - print ma - print "\n\n" + print("==== Original Array =======") + print(ma) + print("\n\n") #### Tests follow: #### Index/slice tests: check that all values and meta info are correct after slice - print "\n -- normal integer indexing\n" + print("\n -- normal integer indexing\n") - print "\n ma[1]" - print ma[1] + print("\n ma[1]") + print(ma[1]) - print "\n ma[1, 2:4]" - print ma[1, 2:4] + print("\n ma[1, 2:4]") + print(ma[1, 2:4]) - print "\n ma[1, 1:5:2]" - print ma[1, 1:5:2] + print("\n ma[1, 1:5:2]") + print(ma[1, 1:5:2]) - print "\n -- named axis indexing\n" + print("\n -- named axis indexing\n") - print "\n ma['Axis2':3]" - print ma['Axis2':3] + print("\n ma['Axis2':3]") + print(ma['Axis2':3]) - print "\n ma['Axis2':3:5]" - print ma['Axis2':3:5] + print("\n ma['Axis2':3:5]") + print(ma['Axis2':3:5]) - print "\n ma[1, 'Axis2':3]" - print ma[1, 'Axis2':3] + print("\n ma[1, 'Axis2':3]") + print(ma[1, 'Axis2':3]) - print "\n ma[:, 'Axis2':3]" - print ma[:, 'Axis2':3] + print("\n ma[:, 'Axis2':3]") + print(ma[:, 'Axis2':3]) - print "\n ma['Axis2':3, 'Axis4':0:2]" - print ma['Axis2':3, 'Axis4':0:2] + print("\n ma['Axis2':3, 'Axis4':0:2]") + print(ma['Axis2':3, 'Axis4':0:2]) - print "\n -- column name indexing\n" + print("\n -- column name indexing\n") - print "\n ma['Axis3':'Ax3Col1']" - print ma['Axis3':'Ax3Col1'] + print("\n ma['Axis3':'Ax3Col1']") + print(ma['Axis3':'Ax3Col1']) - print "\n ma['Axis3':('Ax3','Col3')]" - print ma['Axis3':('Ax3','Col3')] + print("\n ma['Axis3':('Ax3','Col3')]") + print(ma['Axis3':('Ax3','Col3')]) - print "\n ma[:, :, 'Ax3Col2']" - print ma[:, :, 'Ax3Col2'] + print("\n ma[:, :, 'Ax3Col2']") + print(ma[:, :, 'Ax3Col2']) - print "\n ma[:, :, ('Ax3','Col3')]" - print ma[:, :, ('Ax3','Col3')] + print("\n ma[:, :, ('Ax3','Col3')]") + print(ma[:, :, ('Ax3','Col3')]) - print "\n -- axis value range indexing\n" + print("\n -- axis value range indexing\n") - print "\n ma['Axis2':1.5:4.5]" - print ma['Axis2':1.5:4.5] + print("\n ma['Axis2':1.5:4.5]") + print(ma['Axis2':1.5:4.5]) - print "\n ma['Axis4':1.15:1.45]" - print ma['Axis4':1.15:1.45] + print("\n ma['Axis4':1.15:1.45]") + print(ma['Axis4':1.15:1.45]) - print "\n ma['Axis4':1.15:1.25]" - print ma['Axis4':1.15:1.25] + print("\n ma['Axis4':1.15:1.25]") + print(ma['Axis4':1.15:1.25]) - print "\n -- list indexing\n" + print("\n -- list indexing\n") - print "\n ma[:, [0,2,4]]" - print ma[:, [0,2,4]] + print("\n ma[:, [0,2,4]]") + print(ma[:, [0,2,4]]) - print "\n ma['Axis4':[0,2,4]]" - print ma['Axis4':[0,2,4]] + print("\n ma['Axis4':[0,2,4]]") + print(ma['Axis4':[0,2,4]]) - print "\n ma['Axis3':[0, ('Ax3','Col3')]]" - print ma['Axis3':[0, ('Ax3','Col3')]] + print("\n ma['Axis3':[0, ('Ax3','Col3')]]") + print(ma['Axis3':[0, ('Ax3','Col3')]]) - print "\n -- boolean indexing\n" + print("\n -- boolean indexing\n") - print "\n ma[:, array([True, True, False, True, False])]" - print ma[:, np.array([True, True, False, True, False])] + print("\n ma[:, array([True, True, False, True, False])]") + print(ma[:, np.array([True, True, False, True, False])]) - print "\n ma['Axis4':array([True, False, False, False])]" - print ma['Axis4':np.array([True, False, False, False])] + print("\n ma['Axis4':array([True, False, False, False])]") + print(ma['Axis4':np.array([True, False, False, False])]) @@ -1278,18 +1279,18 @@ if __name__ == '__main__': #### File I/O tests - print "\n================ File I/O Tests ===================\n" + print("\n================ File I/O Tests ===================\n") import tempfile tf = tempfile.mktemp() tf = 'test.ma' # write whole array - print "\n -- write/read test" + print("\n -- write/read test") ma.write(tf) ma2 = MetaArray(file=tf) #print ma2 - print "\nArrays are equivalent:", (ma == ma2).all() + print("\nArrays are equivalent:", (ma == ma2).all()) #print "Meta info is equivalent:", ma.infoCopy() == ma2.infoCopy() os.remove(tf) @@ -1298,7 +1299,7 @@ if __name__ == '__main__': # append mode - print "\n================append test (%s)===============" % tf + print("\n================append test (%s)===============" % tf) ma['Axis2':0:2].write(tf, appendAxis='Axis2') for i in range(2,ma.shape[1]): ma['Axis2':[i]].write(tf, appendAxis='Axis2') @@ -1306,7 +1307,7 @@ if __name__ == '__main__': ma2 = MetaArray(file=tf) #print ma2 - print "\nArrays are equivalent:", (ma == ma2).all() + print("\nArrays are equivalent:", (ma == ma2).all()) #print "Meta info is equivalent:", ma.infoCopy() == ma2.infoCopy() os.remove(tf) @@ -1314,9 +1315,9 @@ if __name__ == '__main__': ## memmap test - print "\n==========Memmap test============" + print("\n==========Memmap test============") ma.write(tf, mappable=True) ma2 = MetaArray(file=tf, mmap=True) - print "\nArrays are equivalent:", (ma == ma2).all() + print("\nArrays are equivalent:", (ma == ma2).all()) os.remove(tf) \ No newline at end of file diff --git a/metaarray/__init__.py b/metaarray/__init__.py index e931109d..a12f40d5 100644 --- a/metaarray/__init__.py +++ b/metaarray/__init__.py @@ -1 +1 @@ -from MetaArray import * +from .MetaArray import * diff --git a/opengl/GLViewWidget.py b/opengl/GLViewWidget.py index 6aeeabfe..c68e8726 100644 --- a/opengl/GLViewWidget.py +++ b/opengl/GLViewWidget.py @@ -102,7 +102,7 @@ class GLViewWidget(QtOpenGL.QGLWidget): except: import sys sys.excepthook(*sys.exc_info()) - print "Error while drawing item", i + print("Error while drawing item", i) finally: glPopAttrib(GL_ALL_ATTRIB_BITS) else: diff --git a/opengl/MeshData.py b/opengl/MeshData.py index 8cd97c3e..f5024f90 100644 --- a/opengl/MeshData.py +++ b/opengl/MeshData.py @@ -133,7 +133,7 @@ class MeshData(object): faceNorms = self.faceNormals() vertFaces = self.vertexFaces() self._vertexNormals = [] - for vindex in xrange(len(self._vertexes)): + for vindex in range(len(self._vertexes)): #print vertFaces[vindex] norms = [faceNorms[findex] for findex in vertFaces[vindex]] norm = QtGui.QVector3D() diff --git a/opengl/__init__.py b/opengl/__init__.py index 3d501e9d..bb9f25b1 100644 --- a/opengl/__init__.py +++ b/opengl/__init__.py @@ -1,11 +1,11 @@ -from GLViewWidget import GLViewWidget +from .GLViewWidget import GLViewWidget import os def importAll(path): d = os.path.join(os.path.split(__file__)[0], path) files = [] for f in os.listdir(d): - if os.path.isdir(os.path.join(d, f)): + if os.path.isdir(os.path.join(d, f)) and f != '__pycache__': files.append(f) elif f[-3:] == '.py' and f != '__init__.py': files.append(f[:-3]) diff --git a/opengl/items/GLVolumeItem.py b/opengl/items/GLVolumeItem.py index 0a3a421b..a16dedb4 100644 --- a/opengl/items/GLVolumeItem.py +++ b/opengl/items/GLVolumeItem.py @@ -105,7 +105,7 @@ class GLVolumeItem(GLGraphicsItem): vp[3][imax[0]] = 0 vp[3][imax[1]] = self.data.shape[imax[1]] slices = self.data.shape[ax] * self.sliceDensity - r = range(slices) + r = list(range(slices)) if d == -1: r = r[::-1] diff --git a/parametertree/Parameter.py b/parametertree/Parameter.py index e10147fd..1c92457e 100644 --- a/parametertree/Parameter.py +++ b/parametertree/Parameter.py @@ -1,6 +1,6 @@ from pyqtgraph.Qt import QtGui, QtCore import collections, os, weakref, re -from ParameterItem import ParameterItem +from .ParameterItem import ParameterItem PARAM_TYPES = {} diff --git a/parametertree/ParameterTree.py b/parametertree/ParameterTree.py index c190a15f..22c54d77 100644 --- a/parametertree/ParameterTree.py +++ b/parametertree/ParameterTree.py @@ -76,9 +76,9 @@ class ParameterTree(TreeWidget): index = root.indexOfChild(startItem) - 1 if forward: - inds = range(index, root.childCount()) + inds = list(range(index, root.childCount())) else: - inds = range(index, -1, -1) + inds = list(range(index, -1, -1)) for i in inds: item = root.child(i) diff --git a/parametertree/__init__.py b/parametertree/__init__.py index b5912f57..acdb7a37 100644 --- a/parametertree/__init__.py +++ b/parametertree/__init__.py @@ -1,5 +1,5 @@ -from Parameter import Parameter, registerParameterType -from ParameterTree import ParameterTree -from ParameterItem import ParameterItem +from .Parameter import Parameter, registerParameterType +from .ParameterTree import ParameterTree +from .ParameterItem import ParameterItem -import parameterTypes as types \ No newline at end of file +from . import parameterTypes as types \ No newline at end of file diff --git a/parametertree/__main__.py b/parametertree/__main__.py index b82453c5..c1b3ad09 100644 --- a/parametertree/__main__.py +++ b/parametertree/__main__.py @@ -111,9 +111,9 @@ params = [ #p = pTypes.ParameterSet("params", params) p = Parameter(name='params', type='group', children=params) def change(param, changes): - print "tree changes:" + print("tree changes:") for param, change, data in changes: - print " [" + '.'.join(p.childPath(param))+ "] ", change, data + print(" [" + '.'.join(p.childPath(param))+ "] ", change, data) p.sigTreeStateChanged.connect(change) diff --git a/parametertree/parameterTypes.py b/parametertree/parameterTypes.py index 6d93470a..a9fbd413 100644 --- a/parametertree/parameterTypes.py +++ b/parametertree/parameterTypes.py @@ -1,6 +1,6 @@ from pyqtgraph.Qt import QtCore, QtGui -from Parameter import Parameter, registerParameterType -from ParameterItem import ParameterItem +from .Parameter import Parameter, registerParameterType +from .ParameterItem import ParameterItem from pyqtgraph.widgets.SpinBox import SpinBox from pyqtgraph.widgets.ColorButton import ColorButton import pyqtgraph as pg @@ -105,8 +105,8 @@ class WidgetParameterItem(ParameterItem): elif t == 'str': w = QtGui.QLineEdit() w.sigChanged = w.editingFinished - w.value = lambda: unicode(w.text()) - w.setValue = lambda v: w.setText(unicode(v)) + w.value = lambda: asUnicode(w.text()) + w.setValue = lambda v: w.setText(asUnicode(v)) w.sigChanging = w.textChanged elif t == 'color': w = ColorButton() @@ -117,7 +117,7 @@ class WidgetParameterItem(ParameterItem): self.hideWidget = False w.setFlat(True) else: - raise Exception("Unknown type '%s'" % unicode(t)) + raise Exception("Unknown type '%s'" % asUnicode(t)) return w def widgetEventFilter(self, obj, ev): @@ -165,11 +165,11 @@ class WidgetParameterItem(ParameterItem): value = self.param.value() opts = self.param.opts if isinstance(self.widget, QtGui.QAbstractSpinBox): - text = unicode(self.widget.lineEdit().text()) + text = asUnicode(self.widget.lineEdit().text()) elif isinstance(self.widget, QtGui.QComboBox): text = self.widget.currentText() else: - text = unicode(value) + text = asUnicode(value) self.displayLabel.setText(text) def widgetValueChanged(self): @@ -342,7 +342,7 @@ class GroupParameterItem(ParameterItem): """ if self.addWidget.currentIndex() == 0: return - typ = unicode(self.addWidget.currentText()) + typ = asUnicode(self.addWidget.currentText()) self.param.addNew(typ) self.addWidget.setCurrentIndex(0) @@ -400,7 +400,7 @@ class ListParameterItem(WidgetParameterItem): def value(self): #vals = self.param.opts['limits'] - key = unicode(self.widget.currentText()) + key = asUnicode(self.widget.currentText()) #if isinstance(vals, dict): #return vals[key] #else: @@ -431,18 +431,18 @@ class ListParameterItem(WidgetParameterItem): self.forward = collections.OrderedDict() ## name: value self.reverse = collections.OrderedDict() ## value: name if isinstance(limits, dict): - for k, v in limits.iteritems(): + for k, v in limits.items(): self.forward[k] = v self.reverse[v] = k else: for v in limits: - n = unicode(v) + n = asUnicode(v) self.forward[n] = v self.reverse[v] = n try: self.widget.blockSignals(True) - val = unicode(self.widget.currentText()) + val = asUnicode(self.widget.currentText()) self.widget.clear() for k in self.forward: self.widget.addItem(k) @@ -469,19 +469,19 @@ class ListParameter(Parameter): self.forward = collections.OrderedDict() ## name: value self.reverse = collections.OrderedDict() ## value: name if isinstance(limits, dict): - for k, v in limits.iteritems(): + for k, v in limits.items(): self.forward[k] = v self.reverse[v] = k else: for v in limits: - n = unicode(v) + n = asUnicode(v) self.forward[n] = v self.reverse[v] = n Parameter.setLimits(self, limits) #print self.name(), self.value(), limits if self.value() not in self.reverse and len(self.reverse) > 0: - self.setValue(self.reverse.keys()[0]) + self.setValue(list(self.reverse.keys())[0]) registerParameterType('list', ListParameter, override=True) diff --git a/rebuildUi.py b/rebuildUi.py index 78295f4a..ee6ccd7f 100644 --- a/rebuildUi.py +++ b/rebuildUi.py @@ -13,4 +13,4 @@ for path, sd, files in os.walk('.'): ui = os.path.join(path, f) py = os.path.join(path, base + '.py') os.system('%s %s > %s' % (uic, ui, py)) - print py + print(py) diff --git a/reload.py b/reload.py index e1a8179d..85892c2d 100644 --- a/reload.py +++ b/reload.py @@ -22,15 +22,19 @@ Does NOT: """ -import inspect, os, sys, __builtin__, gc, traceback -from debug import printExc +import inspect, os, sys, gc, traceback +try: + import __builtin__ as builtins +except ImportError: + import builtins +from .debug import printExc def reloadAll(prefix=None, debug=False): """Automatically reload everything whose __file__ begins with prefix. - Skips reload if the file has not been updated (if .pyc is newer than .py) - if prefix is None, checks all loaded modules """ - for modName, mod in sys.modules.items(): ## don't use iteritems; size may change during reload + for modName, mod in list(sys.modules.items()): ## don't use iteritems; size may change during reload if not inspect.ismodule(mod): continue if modName == '__main__': @@ -65,11 +69,11 @@ def reload(module, debug=False, lists=False, dicts=False): - Requires that class and function names have not changed """ if debug: - print "Reloading", module + print("Reloading", module) ## make a copy of the old module dictionary, reload, then grab the new module dictionary for comparison oldDict = module.__dict__.copy() - __builtin__.reload(module) + builtins.reload(module) newDict = module.__dict__ ## Allow modules access to the old dictionary after they reload @@ -85,7 +89,7 @@ def reload(module, debug=False, lists=False, dicts=False): if inspect.isclass(old): if debug: - print " Updating class %s.%s (0x%x -> 0x%x)" % (module.__name__, k, id(old), id(new)) + print(" Updating class %s.%s (0x%x -> 0x%x)" % (module.__name__, k, id(old), id(new))) updateClass(old, new, debug) elif inspect.isfunction(old): @@ -94,7 +98,7 @@ def reload(module, debug=False, lists=False, dicts=False): extra = "" if depth > 0: extra = " (and %d previous versions)" % depth - print " Updating function %s.%s%s" % (module.__name__, k, extra) + print(" Updating function %s.%s%s" % (module.__name__, k, extra)) elif lists and isinstance(old, list): l = old.len() old.extend(new) @@ -150,7 +154,7 @@ def updateClass(old, new, debug): if isinstance(ref, old) and ref.__class__ is old: ref.__class__ = new if debug: - print " Changed class for", safeStr(ref) + print(" Changed class for", safeStr(ref)) elif inspect.isclass(ref) and issubclass(ref, old) and old in ref.__bases__: ind = ref.__bases__.index(old) @@ -166,12 +170,12 @@ def updateClass(old, new, debug): ## (and I presume this may slow things down?) ref.__bases__ = ref.__bases__[:ind] + (new,old) + ref.__bases__[ind+1:] if debug: - print " Changed superclass for", safeStr(ref) + print(" Changed superclass for", safeStr(ref)) #else: #if debug: #print " Ignoring reference", type(ref) except: - print "Error updating reference (%s) for class change (%s -> %s)" % (safeStr(ref), safeStr(old), safeStr(new)) + print("Error updating reference (%s) for class change (%s -> %s)" % (safeStr(ref), safeStr(old), safeStr(new))) raise ## update all class methods to use new code. @@ -184,23 +188,23 @@ def updateClass(old, new, debug): na = getattr(new, attr) except AttributeError: if debug: - print " Skipping method update for %s; new class does not have this attribute" % attr + print(" Skipping method update for %s; new class does not have this attribute" % attr) continue - if hasattr(oa, 'im_func') and hasattr(na, 'im_func') and oa.im_func is not na.im_func: - depth = updateFunction(oa.im_func, na.im_func, debug) + if hasattr(oa, 'im_func') and hasattr(na, 'im_func') and oa.__func__ is not na.__func__: + depth = updateFunction(oa.__func__, na.__func__, debug) #oa.im_class = new ## bind old method to new class ## not allowed if debug: extra = "" if depth > 0: extra = " (and %d previous versions)" % depth - print " Updating method %s%s" % (attr, extra) + print(" Updating method %s%s" % (attr, extra)) ## And copy in new functions that didn't exist previously for attr in dir(new): if not hasattr(old, attr): if debug: - print " Adding missing attribute", attr + print(" Adding missing attribute", attr) setattr(old, attr, getattr(new, attr)) ## finally, update any previous versions still hanging around.. @@ -240,7 +244,7 @@ if __name__ == '__main__': btn = Btn() except: raise - print "Error; skipping Qt tests" + print("Error; skipping Qt tests") doQtTest = False @@ -291,7 +295,7 @@ def fn(): open(modFile2, 'w').write(modCode2%"message 1") import test1.test1 as test1 import test2 - print "Test 1 originals:" + print("Test 1 originals:") A1 = test1.A B1 = test1.B a1 = test1.A("a1") @@ -304,17 +308,17 @@ def fn(): from test2 import fn, C if doQtTest: - print "Button test before:" + print("Button test before:") btn.sig.connect(fn) btn.sig.connect(a1.fn) btn.emit() #btn.sig.emit() - print "" + print("") #print "a1.fn referrers:", sys.getrefcount(a1.fn.im_func), gc.get_referrers(a1.fn.im_func) - print "Test2 before reload:" + print("Test2 before reload:") fn() oldfn = fn @@ -325,39 +329,39 @@ def fn(): os.remove(modFile1+'c') open(modFile1, 'w').write(modCode1%(2,2)) - print "\n----RELOAD test1-----\n" + print("\n----RELOAD test1-----\n") reloadAll(os.path.abspath(__file__)[:10], debug=True) - print "Subclass test:" + print("Subclass test:") c2 = test2.C('c2') c2.fn() os.remove(modFile2+'c') open(modFile2, 'w').write(modCode2%"message 2") - print "\n----RELOAD test2-----\n" + print("\n----RELOAD test2-----\n") reloadAll(os.path.abspath(__file__)[:10], debug=True) if doQtTest: - print "Button test after:" + print("Button test after:") btn.emit() #btn.sig.emit() #print "a1.fn referrers:", sys.getrefcount(a1.fn.im_func), gc.get_referrers(a1.fn.im_func) - print "Test2 after reload:" + print("Test2 after reload:") fn() test2.a1.fn() test2.b1.fn() - print "\n==> Test 1 Old instances:" + print("\n==> Test 1 Old instances:") a1.fn() b1.fn() c1.fn() #print "function IDs a1 bound method: %d a1 func: %d a1 class: %d b1 func: %d b1 class: %d" % (id(a1.fn), id(a1.fn.im_func), id(a1.fn.im_class), id(b1.fn.im_func), id(b1.fn.im_class)) - print "\n==> Test 1 New instances:" + print("\n==> Test 1 New instances:") a2 = test1.A("a2") b2 = test1.B("b2") a2.fn() @@ -374,32 +378,32 @@ def fn(): open(modFile1, 'w').write(modCode1%(3,3)) open(modFile2, 'w').write(modCode2%"message 3") - print "\n----RELOAD-----\n" + print("\n----RELOAD-----\n") reloadAll(os.path.abspath(__file__)[:10], debug=True) if doQtTest: - print "Button test after:" + print("Button test after:") btn.emit() #btn.sig.emit() #print "a1.fn referrers:", sys.getrefcount(a1.fn.im_func), gc.get_referrers(a1.fn.im_func) - print "Test2 after reload:" + print("Test2 after reload:") fn() test2.a1.fn() test2.b1.fn() - print "\n==> Test 1 Old instances:" + print("\n==> Test 1 Old instances:") a1.fn() b1.fn() - print "function IDs a1 bound method: %d a1 func: %d a1 class: %d b1 func: %d b1 class: %d" % (id(a1.fn), id(a1.fn.im_func), id(a1.fn.im_class), id(b1.fn.im_func), id(b1.fn.im_class)) + print("function IDs a1 bound method: %d a1 func: %d a1 class: %d b1 func: %d b1 class: %d" % (id(a1.fn), id(a1.fn.__func__), id(a1.fn.__self__.__class__), id(b1.fn.__func__), id(b1.fn.__self__.__class__))) - print "\n==> Test 1 New instances:" + print("\n==> Test 1 New instances:") a2 = test1.A("a2") b2 = test1.B("b2") a2.fn() b2.fn() - print "function IDs a1 bound method: %d a1 func: %d a1 class: %d b1 func: %d b1 class: %d" % (id(a1.fn), id(a1.fn.im_func), id(a1.fn.im_class), id(b1.fn.im_func), id(b1.fn.im_class)) + print("function IDs a1 bound method: %d a1 func: %d a1 class: %d b1 func: %d b1 class: %d" % (id(a1.fn), id(a1.fn.__func__), id(a1.fn.__self__.__class__), id(b1.fn.__func__), id(b1.fn.__self__.__class__))) os.remove(modFile1) diff --git a/widgets/CheckTable.py b/widgets/CheckTable.py index 0c72ad67..e133390d 100644 --- a/widgets/CheckTable.py +++ b/widgets/CheckTable.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- from pyqtgraph.Qt import QtGui, QtCore -import VerticalLabel +from . import VerticalLabel __all__ = ['CheckTable'] diff --git a/widgets/ColorButton.py b/widgets/ColorButton.py index 4a7d4a13..407a4200 100644 --- a/widgets/ColorButton.py +++ b/widgets/ColorButton.py @@ -91,9 +91,9 @@ if __name__ == '__main__': win.show() def change(btn): - print "change", btn.color() + print("change", btn.color()) def done(btn): - print "done", btn.color() + print("done", btn.color()) btn.sigColorChanging.connect(change) btn.sigColorChanged.connect(done) diff --git a/widgets/DataTreeWidget.py b/widgets/DataTreeWidget.py index 8a310af6..c3622403 100644 --- a/widgets/DataTreeWidget.py +++ b/widgets/DataTreeWidget.py @@ -49,7 +49,7 @@ class DataTreeWidget(QtGui.QTreeWidget): parent.addChild(node) if isinstance(data, types.TracebackType): ## convert traceback to a list of strings - data = map(str.strip, traceback.format_list(traceback.extract_tb(data))) + data = list(map(str.strip, traceback.format_list(traceback.extract_tb(data)))) elif HAVE_METAARRAY and isinstance(data, metaarray.MetaArray): data = { 'data': data.view(np.ndarray), diff --git a/widgets/GradientWidget.py b/widgets/GradientWidget.py index bcc50b69..287a2635 100644 --- a/widgets/GradientWidget.py +++ b/widgets/GradientWidget.py @@ -1,12 +1,6 @@ # -*- coding: utf-8 -*- -if __name__ == '__main__': - import os, sys - path = os.path.join(os.path.dirname(__file__), '..', '..') - sys.path = [path] + sys.path - - from pyqtgraph.Qt import QtGui, QtCore -from GraphicsView import GraphicsView +from .GraphicsView import GraphicsView from pyqtgraph.graphicsItems.GradientEditorItem import GradientEditorItem import weakref import numpy as np @@ -581,41 +575,3 @@ class GradientWidget(GraphicsView): - -if __name__ == '__main__': - app = QtGui.QApplication([]) - w = QtGui.QMainWindow() - w.show() - w.resize(400,400) - cw = QtGui.QWidget() - w.setCentralWidget(cw) - - l = QtGui.QGridLayout() - l.setSpacing(0) - cw.setLayout(l) - - w1 = GradientWidget(orientation='top') - w2 = GradientWidget(orientation='right', allowAdd=False) - #w2.setTickColor(1, QtGui.QColor(255,255,255)) - w3 = GradientWidget(orientation='bottom') - w4 = GradientWidget(orientation='left') - label = QtGui.QLabel(""" - - Click a triangle to change its color - - Drag triangles to move - - Click in an empty area to add a new color - (adding is disabled for the right-side widget) - - Right click a triangle to remove - """) - - l.addWidget(w1, 0, 1) - l.addWidget(w2, 1, 2) - l.addWidget(w3, 2, 1) - l.addWidget(w4, 1, 0) - l.addWidget(label, 1, 1) - - ## Start Qt event loop unless running in interactive mode. - import sys - if sys.flags.interactive != 1: - app.exec_() - - \ No newline at end of file diff --git a/widgets/GraphicsLayoutWidget.py b/widgets/GraphicsLayoutWidget.py index b08e4383..7c93c32d 100644 --- a/widgets/GraphicsLayoutWidget.py +++ b/widgets/GraphicsLayoutWidget.py @@ -1,6 +1,6 @@ from pyqtgraph.Qt import QtGui from pyqtgraph.graphicsItems.GraphicsLayout import GraphicsLayout -from GraphicsView import GraphicsView +from .GraphicsView import GraphicsView __all__ = ['GraphicsLayoutWidget'] class GraphicsLayoutWidget(GraphicsView): diff --git a/widgets/GraphicsView.py b/widgets/GraphicsView.py index bc3228dc..e12228c8 100644 --- a/widgets/GraphicsView.py +++ b/widgets/GraphicsView.py @@ -5,14 +5,21 @@ Copyright 2010 Luke Campagnola Distributed under MIT/X11 license. See license.txt for more infomation. """ -from pyqtgraph.Qt import QtCore, QtGui, QtOpenGL, QtSvg +from pyqtgraph.Qt import QtCore, QtGui + +try: + from pyqtgraph.Qt import QtOpenGL + HAVE_OPENGL = True +except ImportError: + HAVE_OPENGL = False + #from numpy import vstack #import time from pyqtgraph.Point import Point #from vector import * import sys, os #import debug -from FileDialog import FileDialog +from .FileDialog import FileDialog from pyqtgraph.GraphicsScene import GraphicsScene import numpy as np import pyqtgraph.functions as fn @@ -110,6 +117,8 @@ class GraphicsView(QtGui.QGraphicsView): def useOpenGL(self, b=True): if b: + if not HAVE_OPENGL: + raise Exception("Requested to use OpenGL with QGraphicsView, but QtOpenGL module is not available.") v = QtOpenGL.QGLWidget() else: v = QtGui.QWidget() diff --git a/widgets/HistogramLUTWidget.py b/widgets/HistogramLUTWidget.py index f05d3f1c..5d6f3d44 100644 --- a/widgets/HistogramLUTWidget.py +++ b/widgets/HistogramLUTWidget.py @@ -4,7 +4,7 @@ This is a wrapper around HistogramLUTItem """ from pyqtgraph.Qt import QtGui, QtCore -from GraphicsView import GraphicsView +from .GraphicsView import GraphicsView from pyqtgraph.graphicsItems.HistogramLUTItem import HistogramLUTItem __all__ = ['HistogramLUTWidget'] diff --git a/widgets/JoystickButton.py b/widgets/JoystickButton.py index 0ef5f2ee..39326d14 100644 --- a/widgets/JoystickButton.py +++ b/widgets/JoystickButton.py @@ -1,93 +1,93 @@ -from pyqtgraph.Qt import QtGui, QtCore - - -__all__ = ['JoystickButton'] - -class JoystickButton(QtGui.QPushButton): - sigStateChanged = QtCore.Signal(object, object) ## self, state - - def __init__(self, parent=None): - QtGui.QPushButton.__init__(self, parent) - self.radius = 200 - self.setCheckable(True) - self.state = None - self.setState(0,0) - - - def mousePressEvent(self, ev): - self.setChecked(True) - self.pressPos = ev.pos() - ev.accept() - - def mouseMoveEvent(self, ev): - dif = ev.pos()-self.pressPos - self.setState(dif.x(), -dif.y()) - - def mouseReleaseEvent(self, ev): - self.setChecked(False) - self.setState(0,0) - - def wheelEvent(self, ev): - ev.accept() - - - def doubleClickEvent(self, ev): - ev.accept() - - def getState(self): - return self.state - - def setState(self, *xy): - xy = list(xy) - d = (xy[0]**2 + xy[1]**2)**0.5 - nxy = [0,0] - for i in [0,1]: - if xy[i] == 0: - nxy[i] = 0 - else: - nxy[i] = xy[i]/d - - if d > self.radius: - d = self.radius - d = (d/self.radius)**2 - xy = [nxy[0]*d, nxy[1]*d] - - w2 = self.width()/2. - h2 = self.height()/2 - self.spotPos = QtCore.QPoint(w2*(1+xy[0]), h2*(1-xy[1])) - self.update() - if self.state == xy: - return - self.state = xy - self.sigStateChanged.emit(self, self.state) - - def paintEvent(self, ev): - QtGui.QPushButton.paintEvent(self, ev) - p = QtGui.QPainter(self) - p.setBrush(QtGui.QBrush(QtGui.QColor(0,0,0))) - p.drawEllipse(self.spotPos.x()-3,self.spotPos.y()-3,6,6) - - def resizeEvent(self, ev): - self.setState(*self.state) - QtGui.QPushButton.resizeEvent(self, ev) - - - -if __name__ == '__main__': - app = QtGui.QApplication([]) - w = QtGui.QMainWindow() - b = JoystickButton() - w.setCentralWidget(b) - w.show() - w.resize(100, 100) - - def fn(b, s): - print "state changed:", s - - b.sigStateChanged.connect(fn) - - ## Start Qt event loop unless running in interactive mode. - import sys - if sys.flags.interactive != 1: - app.exec_() +from pyqtgraph.Qt import QtGui, QtCore + + +__all__ = ['JoystickButton'] + +class JoystickButton(QtGui.QPushButton): + sigStateChanged = QtCore.Signal(object, object) ## self, state + + def __init__(self, parent=None): + QtGui.QPushButton.__init__(self, parent) + self.radius = 200 + self.setCheckable(True) + self.state = None + self.setState(0,0) + + + def mousePressEvent(self, ev): + self.setChecked(True) + self.pressPos = ev.pos() + ev.accept() + + def mouseMoveEvent(self, ev): + dif = ev.pos()-self.pressPos + self.setState(dif.x(), -dif.y()) + + def mouseReleaseEvent(self, ev): + self.setChecked(False) + self.setState(0,0) + + def wheelEvent(self, ev): + ev.accept() + + + def doubleClickEvent(self, ev): + ev.accept() + + def getState(self): + return self.state + + def setState(self, *xy): + xy = list(xy) + d = (xy[0]**2 + xy[1]**2)**0.5 + nxy = [0,0] + for i in [0,1]: + if xy[i] == 0: + nxy[i] = 0 + else: + nxy[i] = xy[i]/d + + if d > self.radius: + d = self.radius + d = (d/self.radius)**2 + xy = [nxy[0]*d, nxy[1]*d] + + w2 = self.width()/2. + h2 = self.height()/2 + self.spotPos = QtCore.QPoint(w2*(1+xy[0]), h2*(1-xy[1])) + self.update() + if self.state == xy: + return + self.state = xy + self.sigStateChanged.emit(self, self.state) + + def paintEvent(self, ev): + QtGui.QPushButton.paintEvent(self, ev) + p = QtGui.QPainter(self) + p.setBrush(QtGui.QBrush(QtGui.QColor(0,0,0))) + p.drawEllipse(self.spotPos.x()-3,self.spotPos.y()-3,6,6) + + def resizeEvent(self, ev): + self.setState(*self.state) + QtGui.QPushButton.resizeEvent(self, ev) + + + +if __name__ == '__main__': + app = QtGui.QApplication([]) + w = QtGui.QMainWindow() + b = JoystickButton() + w.setCentralWidget(b) + w.show() + w.resize(100, 100) + + def fn(b, s): + print("state changed:", s) + + b.sigStateChanged.connect(fn) + + ## Start Qt event loop unless running in interactive mode. + import sys + if sys.flags.interactive != 1: + app.exec_() \ No newline at end of file diff --git a/widgets/MultiPlotWidget.py b/widgets/MultiPlotWidget.py index 00e2c2d8..400bee92 100644 --- a/widgets/MultiPlotWidget.py +++ b/widgets/MultiPlotWidget.py @@ -5,9 +5,8 @@ Copyright 2010 Luke Campagnola Distributed under MIT/X11 license. See license.txt for more infomation. """ -from GraphicsView import GraphicsView +from .GraphicsView import GraphicsView import pyqtgraph.graphicsItems.MultiPlotItem as MultiPlotItem -import exceptions __all__ = ['MultiPlotWidget'] class MultiPlotWidget(GraphicsView): @@ -26,7 +25,7 @@ class MultiPlotWidget(GraphicsView): m = getattr(self.mPlotItem, attr) if hasattr(m, '__call__'): return m - raise exceptions.NameError(attr) + raise NameError(attr) def widgetGroupInterface(self): return (None, MultiPlotWidget.saveState, MultiPlotWidget.restoreState) diff --git a/widgets/PlotWidget.py b/widgets/PlotWidget.py index 3bb1f636..82e4701d 100644 --- a/widgets/PlotWidget.py +++ b/widgets/PlotWidget.py @@ -6,9 +6,8 @@ Distributed under MIT/X11 license. See license.txt for more infomation. """ from pyqtgraph.Qt import QtCore, QtGui -from GraphicsView import * +from .GraphicsView import * from pyqtgraph.graphicsItems.PlotItem import * -import exceptions __all__ = ['PlotWidget'] class PlotWidget(GraphicsView): @@ -67,7 +66,7 @@ class PlotWidget(GraphicsView): m = getattr(self.plotItem, attr) if hasattr(m, '__call__'): return m - raise exceptions.NameError(attr) + raise NameError(attr) def viewRangeChanged(self, view, range): #self.emit(QtCore.SIGNAL('viewChanged'), *args) diff --git a/widgets/RawImageWidget.py b/widgets/RawImageWidget.py index 84c061a1..ea5c98a0 100644 --- a/widgets/RawImageWidget.py +++ b/widgets/RawImageWidget.py @@ -1,4 +1,9 @@ -from pyqtgraph.Qt import QtCore, QtGui, QtOpenGL +from pyqtgraph.Qt import QtCore, QtGui +try: + from pyqtgraph.Qt import QtOpenGL + HAVE_OPENGL = True +except ImportError: + HAVE_OPENGL = False import pyqtgraph.functions as fn import numpy as np @@ -53,27 +58,27 @@ class RawImageWidget(QtGui.QWidget): #p.drawPixmap(self.rect(), self.pixmap) p.end() +if HAVE_OPENGL: + class RawImageGLWidget(QtOpenGL.QGLWidget): + """ + Similar to RawImageWidget, but uses a GL widget to do all drawing. + Generally this will be about as fast as using GraphicsView + ImageItem, + but performance may vary on some platforms. + """ + def __init__(self, parent=None, scaled=False): + QtOpenGL.QGLWidget.__init__(self, parent=None) + self.scaled = scaled + self.image = None -class RawImageGLWidget(QtOpenGL.QGLWidget): - """ - Similar to RawImageWidget, but uses a GL widget to do all drawing. - Generally this will be about as fast as using GraphicsView + ImageItem, - but performance may vary on some platforms. - """ - def __init__(self, parent=None, scaled=False): - QtOpenGL.QGLWidget.__init__(self, parent=None) - self.scaled = scaled - self.image = None - - def setImage(self, img): - self.image = fn.makeQImage(img) - self.update() + def setImage(self, img): + self.image = fn.makeQImage(img) + self.update() - def paintEvent(self, ev): - if self.image is None: - return - p = QtGui.QPainter(self) - p.drawImage(self.rect(), self.image) - p.end() + def paintEvent(self, ev): + if self.image is None: + return + p = QtGui.QPainter(self) + p.drawImage(self.rect(), self.image) + p.end() diff --git a/widgets/SpinBox.py b/widgets/SpinBox.py index 9d1fe5a9..cc505c5c 100644 --- a/widgets/SpinBox.py +++ b/widgets/SpinBox.py @@ -115,7 +115,7 @@ class SpinBox(QtGui.QAbstractSpinBox): self.decOpts = ['step', 'minStep'] - self.val = D(unicode(value)) ## Value is precise decimal. Ordinary math not allowed. + self.val = D(asUnicode(value)) ## Value is precise decimal. Ordinary math not allowed. self.updateText() self.skipValidate = False self.setCorrectionMode(self.CorrectToPreviousValue) @@ -144,7 +144,7 @@ class SpinBox(QtGui.QAbstractSpinBox): #else: #self.opts[k][i] = D(unicode(opts[k][i])) elif k in ['step', 'minStep']: - self.opts[k] = D(unicode(opts[k])) + self.opts[k] = D(asUnicode(opts[k])) elif k == 'value': pass ## don't set value until bounds have been set else: @@ -182,7 +182,7 @@ class SpinBox(QtGui.QAbstractSpinBox): def setMaximum(self, m, update=True): """Set the maximum allowed value (or None for no limit)""" if m is not None: - m = D(unicode(m)) + m = D(asUnicode(m)) self.opts['bounds'][1] = m if update: self.setValue() @@ -190,7 +190,7 @@ class SpinBox(QtGui.QAbstractSpinBox): def setMinimum(self, m, update=True): """Set the minimum allowed value (or None for no limit)""" if m is not None: - m = D(unicode(m)) + m = D(asUnicode(m)) self.opts['bounds'][0] = m if update: self.setValue() @@ -208,7 +208,7 @@ class SpinBox(QtGui.QAbstractSpinBox): #val = val.toDouble()[0] self.setValue(val) else: - print "Warning: SpinBox.setProperty('%s', ..) not supported." % prop + print("Warning: SpinBox.setProperty('%s', ..) not supported." % prop) def setSuffix(self, suf): self.setOpts(suffix=suf) @@ -252,7 +252,7 @@ class SpinBox(QtGui.QAbstractSpinBox): if self.opts['int']: value = int(value) - value = D(unicode(value)) + value = D(asUnicode(value)) if value == self.val: return prev = self.val @@ -364,7 +364,7 @@ class SpinBox(QtGui.QAbstractSpinBox): try: ## first make sure we didn't mess with the suffix suff = self.opts.get('suffix', '') - if len(suff) > 0 and unicode(strn)[-len(suff):] != suff: + if len(suff) > 0 and asUnicode(strn)[-len(suff):] != suff: #print '"%s" != "%s"' % (unicode(strn)[-len(suff):], suff) ret = QtGui.QValidator.Invalid @@ -452,7 +452,7 @@ class SpinBox(QtGui.QAbstractSpinBox): def editingFinishedEvent(self): """Edit has finished; set value.""" #print "Edit finished." - if unicode(self.lineEdit().text()) == self.lastText: + if asUnicode(self.lineEdit().text()) == self.lastText: #print "no text change." return try: @@ -493,11 +493,11 @@ if __name__ == '__main__': def valueChanged(sb): #sb = QtCore.QObject.sender() - print str(sb) + " valueChanged: %s" % str(sb.value()) + print(str(sb) + " valueChanged: %s" % str(sb.value())) def valueChanging(sb, value): #sb = QtCore.QObject.sender() - print str(sb) + " valueChanging: %s" % str(sb.value()) + print(str(sb) + " valueChanging: %s" % str(sb.value())) def mkWin(): win = QtGui.QMainWindow() diff --git a/widgets/TableWidget.py b/widgets/TableWidget.py index 00376240..e57e90c3 100644 --- a/widgets/TableWidget.py +++ b/widgets/TableWidget.py @@ -56,7 +56,7 @@ class TableWidget(QtGui.QTableWidget): return it0 = fn0(data) try: - first = it0.next() + first = next(it0) except StopIteration: return #if type(first) == type(np.float64(1)): @@ -93,26 +93,26 @@ class TableWidget(QtGui.QTableWidget): if isinstance(data, list): return lambda d: d.__iter__(), None elif isinstance(data, dict): - return lambda d: d.itervalues(), map(str, data.keys()) + return lambda d: iter(d.values()), list(map(str, data.keys())) elif HAVE_METAARRAY and isinstance(data, metaarray.MetaArray): if data.axisHasColumns(0): - header = [str(data.columnName(0, i)) for i in xrange(data.shape[0])] + header = [str(data.columnName(0, i)) for i in range(data.shape[0])] elif data.axisHasValues(0): - header = map(str, data.xvals(0)) + header = list(map(str, data.xvals(0))) else: header = None return self.iterFirstAxis, header elif isinstance(data, np.ndarray): return self.iterFirstAxis, None elif isinstance(data, np.void): - return self.iterate, map(str, data.dtype.names) + return self.iterate, list(map(str, data.dtype.names)) elif data is None: return (None,None) else: raise Exception("Don't know how to iterate over data type: %s" % str(type(data))) def iterFirstAxis(self, data): - for i in xrange(data.shape[0]): + for i in range(data.shape[0]): yield data[i] def iterate(self, data): ## for numpy.void, which can be iterated but mysteriously has no __iter__ (??) @@ -131,7 +131,7 @@ class TableWidget(QtGui.QTableWidget): def setRow(self, row, vals): if row > self.rowCount()-1: self.setRowCount(row+1) - for col in xrange(self.columnCount()): + for col in range(self.columnCount()): val = vals[col] if isinstance(val, float) or isinstance(val, np.floating): s = "%0.3g" % val @@ -147,38 +147,38 @@ class TableWidget(QtGui.QTableWidget): """Convert entire table (or just selected area) into tab-separated text values""" if useSelection: selection = self.selectedRanges()[0] - rows = range(selection.topRow(), selection.bottomRow()+1) - columns = range(selection.leftColumn(), selection.rightColumn()+1) + rows = list(range(selection.topRow(), selection.bottomRow()+1)) + columns = list(range(selection.leftColumn(), selection.rightColumn()+1)) else: - rows = range(self.rowCount()) - columns = range(self.columnCount()) + rows = list(range(self.rowCount())) + columns = list(range(self.columnCount())) data = [] if self.horizontalHeadersSet: row = [] if self.verticalHeadersSet: - row.append(u'') + row.append(asUnicode('')) for c in columns: - row.append(unicode(self.horizontalHeaderItem(c).text())) + row.append(asUnicode(self.horizontalHeaderItem(c).text())) data.append(row) for r in rows: row = [] if self.verticalHeadersSet: - row.append(unicode(self.verticalHeaderItem(r).text())) + row.append(asUnicode(self.verticalHeaderItem(r).text())) for c in columns: item = self.item(r, c) if item is not None: - row.append(unicode(item.value)) + row.append(asUnicode(item.value)) else: - row.append(u'') + row.append(asUnicode('')) data.append(row) - s = u'' + s = '' for row in data: - s += (u'\t'.join(row) + u'\n') + s += ('\t'.join(row) + '\n') return s def copySel(self): @@ -226,7 +226,7 @@ if __name__ == '__main__': ll = [[1,2,3,4,5]] * 20 ld = [{'x': 1, 'y': 2, 'z': 3}] * 20 - dl = {'x': range(20), 'y': range(20), 'z': range(20)} + dl = {'x': list(range(20)), 'y': list(range(20)), 'z': list(range(20))} a = np.ones((20, 5)) ra = np.ones((20,), dtype=[('x', int), ('y', int), ('z', int)]) diff --git a/widgets/ValueLabel.py b/widgets/ValueLabel.py index c072b62a..9c6885ce 100644 --- a/widgets/ValueLabel.py +++ b/widgets/ValueLabel.py @@ -1,6 +1,7 @@ from pyqtgraph.Qt import QtCore, QtGui from pyqtgraph.ptime import time import pyqtgraph as pg +from functools import reduce __all__ = ['ValueLabel']