Bugfixes:

- Fixed some floating-point precision issues. (Added a workaround for QTransform.inverted() bug)
  - No longer putting asUnicode inside __builtin__ since this causes problems in some rare circumstances
    (pyshell, lazy import recipe)
  - Minor docstring updates
This commit is contained in:
Luke Campagnola 2012-07-12 15:35:58 -04:00
parent f81e94061f
commit a41d330c29
19 changed files with 82 additions and 27 deletions

View File

@ -1,4 +1,5 @@
from pyqtgraph.Qt import QtCore, QtGui from pyqtgraph.Qt import QtCore, QtGui
from pyqtgraph.python2_3 import sortList
#try: #try:
#from PyQt4 import QtOpenGL #from PyQt4 import QtOpenGL
#HAVE_OPENGL = True #HAVE_OPENGL = True

View File

@ -10,6 +10,7 @@ of a large group of widgets.
from .Qt import QtCore, QtGui from .Qt import QtCore, QtGui
import weakref, inspect import weakref, inspect
from .python2_3 import asUnicode
__all__ = ['WidgetGroup'] __all__ = ['WidgetGroup']

View File

@ -14,7 +14,8 @@ from .Qt import QtGui
import os, sys import os, sys
## check python version ## check python version
if sys.version_info[0] < 2 or (sys.version_info[0] == 2 and sys.version_info[1] != 7): ## Allow anything >= 2.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])) 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 ## helpers for 2/3 compatibility

View File

@ -13,6 +13,7 @@ import re, os, sys
from collections import OrderedDict from collections import OrderedDict
GLOBAL_PATH = None # so not thread safe. GLOBAL_PATH = None # so not thread safe.
from . import units from . import units
from .python2_3 import asUnicode
class ParseError(Exception): class ParseError(Exception):
def __init__(self, message, lineNum, line, fileName=None): def __init__(self, message, lineNum, line, fileName=None):

View File

@ -1,4 +1,5 @@
from pyqtgraph.Qt import QtCore, QtGui from pyqtgraph.Qt import QtCore, QtGui
from pyqtgraph.python2_3 import asUnicode
class CmdInput(QtGui.QLineEdit): class CmdInput(QtGui.QLineEdit):

View File

@ -5,6 +5,7 @@ Copyright 2010 Luke Campagnola
Distributed under MIT/X11 license. See license.txt for more infomation. Distributed under MIT/X11 license. See license.txt for more infomation.
""" """
from .python2_3 import asUnicode
Colors = { Colors = {
'b': (0,0,255,255), 'b': (0,0,255,255),
'g': (0,255,0,255), 'g': (0,255,0,255),
@ -1275,3 +1276,19 @@ def isosurface(data, level):
return facets return facets
def invertQTransform(tr):
"""Return a QTransform that is the inverse of *tr*.
Rasises an exception if tr is not invertible.
Note that this function is preferred over QTransform.inverted() due to
bugs in that method. (specifically, Qt has floating-point precision issues
when determining whether a matrix is invertible)
"""
#return tr.inverted()[0]
arr = np.array([[tr.m11(), tr.m12(), tr.m13()], [tr.m21(), tr.m22(), tr.m23()], [tr.m31(), tr.m32(), tr.m33()]])
inv = scipy.linalg.inv(arr)
return QtGui.QTransform(inv[0,0], inv[0,1], inv[0,2], inv[1,0], inv[1,1], inv[1,2], inv[2,0], inv[2,1])

View File

@ -1,4 +1,5 @@
from pyqtgraph.Qt import QtGui, QtCore from pyqtgraph.Qt import QtGui, QtCore
from pyqtgraph.python2_3 import asUnicode
import numpy as np import numpy as np
from pyqtgraph.Point import Point from pyqtgraph.Point import Point
import pyqtgraph.debug as debug import pyqtgraph.debug as debug

View File

@ -1,4 +1,5 @@
from pyqtgraph.Qt import QtGui, QtCore from pyqtgraph.Qt import QtGui, QtCore
from pyqtgraph.python2_3 import sortList
import pyqtgraph.functions as fn import pyqtgraph.functions as fn
from .GraphicsObject import GraphicsObject from .GraphicsObject import GraphicsObject
from .GraphicsWidget import GraphicsWidget from .GraphicsWidget import GraphicsWidget

View File

@ -1,6 +1,7 @@
from pyqtgraph.Qt import QtGui, QtCore from pyqtgraph.Qt import QtGui, QtCore
from pyqtgraph.GraphicsScene import GraphicsScene from pyqtgraph.GraphicsScene import GraphicsScene
from pyqtgraph.Point import Point from pyqtgraph.Point import Point
import pyqtgraph.functions as fn
import weakref import weakref
class GraphicsItem(object): class GraphicsItem(object):
@ -149,7 +150,9 @@ class GraphicsItem(object):
"""Return vectors in local coordinates representing the width and height of a view pixel. """Return vectors in local coordinates representing the width and height of a view pixel.
If direction is specified, then return vectors parallel and orthogonal to it. If direction is specified, then return vectors parallel and orthogonal to it.
Return (None, None) if pixel size is not yet defined (usually because the item has not yet been displayed).""" Return (None, None) if pixel size is not yet defined (usually because the item has not yet been displayed)
or if pixel size is below floating-point precision limit.
"""
dt = self.deviceTransform() dt = self.deviceTransform()
if dt is None: if dt is None:
@ -157,8 +160,22 @@ class GraphicsItem(object):
if direction is None: if direction is None:
direction = Point(1, 0) direction = Point(1, 0)
if direction.manhattanLength() == 0:
raise Exception("Cannot compute pixel length for 0-length vector.")
## attempt to re-scale direction vector to fit within the precision of the coordinate system
if direction.x() == 0:
r = abs(dt.m32())/(abs(dt.m12()) + abs(dt.m22()))
elif direction.y() == 0:
r = abs(dt.m31())/(abs(dt.m11()) + abs(dt.m21()))
else:
r = ((abs(dt.m32())/(abs(dt.m12()) + abs(dt.m22()))) * (abs(dt.m31())/(abs(dt.m11()) + abs(dt.m21()))))**0.5
direction = direction * r
viewDir = Point(dt.map(direction) - dt.map(Point(0,0))) viewDir = Point(dt.map(direction) - dt.map(Point(0,0)))
if viewDir.manhattanLength() == 0:
return None, None ## pixel size cannot be represented on this scale
orthoDir = Point(viewDir[1], -viewDir[0]) ## orthogonal to line in pixel-space orthoDir = Point(viewDir[1], -viewDir[0]) ## orthogonal to line in pixel-space
try: try:
@ -168,7 +185,7 @@ class GraphicsItem(object):
raise Exception("Invalid direction %s" %direction) raise Exception("Invalid direction %s" %direction)
dti = dt.inverted()[0] dti = fn.invertQTransform(dt)
return Point(dti.map(normView)-dti.map(Point(0,0))), Point(dti.map(normOrtho)-dti.map(Point(0,0))) return Point(dti.map(normView)-dti.map(Point(0,0))), Point(dti.map(normOrtho)-dti.map(Point(0,0)))
#vt = self.deviceTransform() #vt = self.deviceTransform()
@ -194,23 +211,26 @@ class GraphicsItem(object):
def pixelSize(self): def pixelSize(self):
## deprecated
v = self.pixelVectors() v = self.pixelVectors()
if v == (None, None): if v == (None, None):
return None, None return None, None
return (v[0].x()**2+v[0].y()**2)**0.5, (v[1].x()**2+v[1].y()**2)**0.5 return (v[0].x()**2+v[0].y()**2)**0.5, (v[1].x()**2+v[1].y()**2)**0.5
def pixelWidth(self): def pixelWidth(self):
## deprecated
vt = self.deviceTransform() vt = self.deviceTransform()
if vt is None: if vt is None:
return 0 return 0
vt = vt.inverted()[0] vt = fn.invertQTransform(vt)
return Point(vt.map(QtCore.QPointF(1, 0))-vt.map(QtCore.QPointF(0, 0))).length() return Point(vt.map(QtCore.QPointF(1, 0))-vt.map(QtCore.QPointF(0, 0))).length()
def pixelHeight(self): def pixelHeight(self):
## deprecated
vt = self.deviceTransform() vt = self.deviceTransform()
if vt is None: if vt is None:
return 0 return 0
vt = vt.inverted()[0] vt = fn.invertQTransform(vt)
return Point(vt.map(QtCore.QPointF(0, 1))-vt.map(QtCore.QPointF(0, 0))).length() return Point(vt.map(QtCore.QPointF(0, 1))-vt.map(QtCore.QPointF(0, 0))).length()
@ -232,7 +252,7 @@ class GraphicsItem(object):
vt = self.deviceTransform() vt = self.deviceTransform()
if vt is None: if vt is None:
return None return None
vt = vt.inverted()[0] vt = fn.invertQTransform(vt)
return vt.map(obj) return vt.map(obj)
def mapRectToDevice(self, rect): def mapRectToDevice(self, rect):
@ -253,7 +273,7 @@ class GraphicsItem(object):
vt = self.deviceTransform() vt = self.deviceTransform()
if vt is None: if vt is None:
return None return None
vt = vt.inverted()[0] vt = fn.invertQTransform(vt)
return vt.mapRect(rect) return vt.mapRect(rect)
def mapToView(self, obj): def mapToView(self, obj):
@ -272,14 +292,14 @@ class GraphicsItem(object):
vt = self.viewTransform() vt = self.viewTransform()
if vt is None: if vt is None:
return None return None
vt = vt.inverted()[0] vt = fn.invertQTransform(vt)
return vt.map(obj) return vt.map(obj)
def mapRectFromView(self, obj): def mapRectFromView(self, obj):
vt = self.viewTransform() vt = self.viewTransform()
if vt is None: if vt is None:
return None return None
vt = vt.inverted()[0] vt = fn.invertQTransform(vt)
return vt.mapRect(obj) return vt.mapRect(obj)
def pos(self): def pos(self):

View File

@ -2,6 +2,7 @@ from pyqtgraph.Qt import QtGui, QtCore
from .UIGraphicsItem import * from .UIGraphicsItem import *
import numpy as np import numpy as np
from pyqtgraph.Point import Point from pyqtgraph.Point import Point
import pyqtgraph.functions as fn
__all__ = ['GridItem'] __all__ = ['GridItem']
class GridItem(UIGraphicsItem): class GridItem(UIGraphicsItem):
@ -47,7 +48,7 @@ class GridItem(UIGraphicsItem):
p = QtGui.QPainter() p = QtGui.QPainter()
p.begin(self.picture) p.begin(self.picture)
dt = self.viewTransform().inverted()[0] dt = fn.invertQTransform(self.viewTransform())
vr = self.getViewWidget().rect() vr = self.getViewWidget().rect()
unit = self.pixelWidth(), self.pixelHeight() unit = self.pixelWidth(), self.pixelHeight()
dim = [vr.width(), vr.height()] dim = [vr.width(), vr.height()]
@ -112,7 +113,7 @@ class GridItem(UIGraphicsItem):
texts.append((QtCore.QPointF(x, y), "%g"%p1[ax])) texts.append((QtCore.QPointF(x, y), "%g"%p1[ax]))
tr = self.deviceTransform() tr = self.deviceTransform()
#tr.scale(1.5, 1.5) #tr.scale(1.5, 1.5)
p.setWorldTransform(tr.inverted()[0]) p.setWorldTransform(fn.invertQTransform(tr))
for t in texts: for t in texts:
x = tr.map(t[0]) + Point(0.5, 0.5) x = tr.map(t[0]) + Point(0.5, 0.5)
p.drawText(x, t[1]) p.drawText(x, t[1])

View File

@ -1053,6 +1053,7 @@ class PlotItem(GraphicsWidget):
s.hide() s.hide()
def hideAxis(self, axis): def hideAxis(self, axis):
"""Hide one of the PlotItem's axes. ('left', 'bottom', 'right', or 'top')"""
self.showAxis(axis, False) self.showAxis(axis, False)
def showScale(self, *args, **kargs): def showScale(self, *args, **kargs):
@ -1060,6 +1061,7 @@ class PlotItem(GraphicsWidget):
return self.showAxis(*args, **kargs) return self.showAxis(*args, **kargs)
def hideButtons(self): def hideButtons(self):
"""Causes auto-scale button ('A' in lower-left corner) to be hidden for this PlotItem"""
#self.ctrlBtn.hide() #self.ctrlBtn.hide()
self.autoBtn.hide() self.autoBtn.hide()

View File

@ -800,7 +800,7 @@ class ROI(GraphicsObject):
#print " dshape", dShape #print " dshape", dShape
## Determine transform that maps ROI bounding box to image coordinates ## Determine transform that maps ROI bounding box to image coordinates
tr = self.sceneTransform() * img.sceneTransform().inverted()[0] tr = self.sceneTransform() * fn.invertQTransform(img.sceneTransform())
## Modify transform to scale from image coords to data coords ## Modify transform to scale from image coords to data coords
#m = QtGui.QTransform() #m = QtGui.QTransform()
@ -1251,7 +1251,7 @@ class Handle(UIGraphicsItem):
v = dt.map(QtCore.QPointF(1, 0)) - dt.map(QtCore.QPointF(0, 0)) v = dt.map(QtCore.QPointF(1, 0)) - dt.map(QtCore.QPointF(0, 0))
va = np.arctan2(v.y(), v.x()) va = np.arctan2(v.y(), v.x())
dti = dt.inverted()[0] dti = fn.invertQTransform(dt)
devPos = dt.map(QtCore.QPointF(0,0)) devPos = dt.map(QtCore.QPointF(0,0))
tr = QtGui.QTransform() tr = QtGui.QTransform()
tr.translate(devPos.x(), devPos.y()) tr.translate(devPos.x(), devPos.y())

View File

@ -1,6 +1,7 @@
from pyqtgraph.Qt import QtCore, QtGui from pyqtgraph.Qt import QtCore, QtGui
import pyqtgraph as pg import pyqtgraph as pg
from .UIGraphicsItem import * from .UIGraphicsItem import *
import pyqtgraph.functions as fn
class TextItem(UIGraphicsItem): class TextItem(UIGraphicsItem):
""" """
@ -87,7 +88,7 @@ class TextItem(UIGraphicsItem):
if br is None: if br is None:
return return
self.prepareGeometryChange() self.prepareGeometryChange()
self._bounds = self.deviceTransform().inverted()[0].mapRect(br) self._bounds = fn.invertQTransform(self.deviceTransform()).mapRect(br)
#print self._bounds #print self._bounds
def boundingRect(self): def boundingRect(self):

View File

@ -1,4 +1,5 @@
from pyqtgraph.Qt import QtGui, QtCore from pyqtgraph.Qt import QtGui, QtCore
from pyqtgraph.python2_3 import sortList
import numpy as np import numpy as np
from pyqtgraph.Point import Point from pyqtgraph.Point import Point
import pyqtgraph.functions as fn import pyqtgraph.functions as fn
@ -451,10 +452,8 @@ class ViewBox(GraphicsWidget):
center = Point(vr.center()) center = Point(vr.center())
else: else:
center = Point(center) center = Point(center)
tl = center + (vr.topLeft()-center) * scale tl = center + (vr.topLeft()-center) * scale
br = center + (vr.bottomRight()-center) * scale br = center + (vr.bottomRight()-center) * scale
self.setRange(QtCore.QRectF(tl, br), padding=0) self.setRange(QtCore.QRectF(tl, br), padding=0)
def translateBy(self, t): def translateBy(self, t):
@ -764,7 +763,7 @@ class ViewBox(GraphicsWidget):
def mapToView(self, obj): def mapToView(self, obj):
"""Maps from the local coordinates of the ViewBox to the coordinate system displayed inside the ViewBox""" """Maps from the local coordinates of the ViewBox to the coordinate system displayed inside the ViewBox"""
m = self.childTransform().inverted()[0] m = fn.invertQTransform(self.childTransform())
return m.map(obj) return m.map(obj)
def mapFromView(self, obj): def mapFromView(self, obj):
@ -830,7 +829,7 @@ class ViewBox(GraphicsWidget):
mask[axis] = mv mask[axis] = mv
s = ((mask * 0.02) + 1) ** (ev.delta() * self.state['wheelScaleFactor']) # actual scaling factor s = ((mask * 0.02) + 1) ** (ev.delta() * self.state['wheelScaleFactor']) # actual scaling factor
center = Point(self.childGroup.transform().inverted()[0].map(ev.pos())) center = Point(fn.invertQTransform(self.childGroup.transform()).map(ev.pos()))
#center = ev.pos() #center = ev.pos()
self.scaleBy(s, center) self.scaleBy(s, center)
@ -913,8 +912,11 @@ class ViewBox(GraphicsWidget):
dif = np.array([dif.x(), dif.y()]) dif = np.array([dif.x(), dif.y()])
dif[0] *= -1 dif[0] *= -1
s = ((mask * 0.02) + 1) ** dif s = ((mask * 0.02) + 1) ** dif
center = Point(self.childGroup.transform().inverted()[0].map(ev.buttonDownPos(QtCore.Qt.RightButton)))
#center = Point(ev.buttonDownPos(QtCore.Qt.RightButton)) tr = self.childGroup.transform()
tr = fn.invertQTransform(tr)
center = Point(tr.map(ev.buttonDownPos(QtCore.Qt.RightButton)))
self.scaleBy(s, center) self.scaleBy(s, center)
self.sigRangeChangedManually.emit(self.state['mouseEnabled']) self.sigRangeChangedManually.emit(self.state['mouseEnabled'])

View File

@ -1,4 +1,5 @@
from pyqtgraph.Qt import QtCore, QtGui from pyqtgraph.Qt import QtCore, QtGui
from pyqtgraph.python2_3 import asUnicode
from pyqtgraph.WidgetGroup import WidgetGroup from pyqtgraph.WidgetGroup import WidgetGroup
from .axisCtrlTemplate import Ui_Form as AxisCtrlTemplate from .axisCtrlTemplate import Ui_Form as AxisCtrlTemplate
import weakref import weakref

View File

@ -1,4 +1,5 @@
from pyqtgraph.Qt import QtCore, QtGui from pyqtgraph.Qt import QtCore, QtGui
from pyqtgraph.python2_3 import asUnicode
from .Parameter import Parameter, registerParameterType from .Parameter import Parameter, registerParameterType
from .ParameterItem import ParameterItem from .ParameterItem import ParameterItem
from pyqtgraph.widgets.SpinBox import SpinBox from pyqtgraph.widgets.SpinBox import SpinBox

View File

@ -42,8 +42,9 @@ def sortList(l, cmpFunc):
if sys.version_info[0] == 3: if sys.version_info[0] == 3:
import builtins import builtins
builtins.basestring = str builtins.basestring = str
builtins.asUnicode = asUnicode #builtins.asUnicode = asUnicode
builtins.sortList = sortList #builtins.sortList = sortList
basestring = str
def cmp(a,b): def cmp(a,b):
if a>b: if a>b:
return 1 return 1
@ -52,7 +53,7 @@ if sys.version_info[0] == 3:
else: else:
return 0 return 0
builtins.cmp = cmp builtins.cmp = cmp
else: #else: ## don't use __builtin__ -- this confuses things like pyshell and ActiveState's lazy import recipe
import __builtin__ #import __builtin__
__builtin__.asUnicode = asUnicode #__builtin__.asUnicode = asUnicode
__builtin__.sortList = sortList #__builtin__.sortList = sortList

View File

@ -1,5 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from pyqtgraph.Qt import QtGui, QtCore from pyqtgraph.Qt import QtGui, QtCore
from pyqtgraph.python2_3 import asUnicode
from pyqtgraph.SignalProxy import SignalProxy from pyqtgraph.SignalProxy import SignalProxy
import pyqtgraph.functions as fn import pyqtgraph.functions as fn

View File

@ -1,5 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from pyqtgraph.Qt import QtGui, QtCore from pyqtgraph.Qt import QtGui, QtCore
from pyqtgraph.python2_3 import asUnicode
import numpy as np import numpy as np
try: try: