Various performance improvements to pg.Point (#1741)
This change makes use of QPointF methods which perform faster than the python equivalent methods. Furthermore, some tests are added. * Set __slots__ to empty tuple for pg.Point * Make Point.angle() behave as Vector.angle()
This commit is contained in:
parent
fe66f3fd12
commit
a534132c62
@ -56,7 +56,7 @@ def update():
|
|||||||
p2 = pts[i+1]
|
p2 = pts[i+1]
|
||||||
v2 = p2 - p1
|
v2 = p2 - p1
|
||||||
t = p1 - pts[0]
|
t = p1 - pts[0]
|
||||||
r = v2.angle(v1)
|
r = v1.angle(v2)
|
||||||
s = v2.length() / l1
|
s = v2.length() / l1
|
||||||
trs.append(pg.SRTTransform({'pos': t, 'scale': (s, s), 'angle': r}))
|
trs.append(pg.SRTTransform({'pos': t, 'scale': (s, s), 'angle': r}))
|
||||||
|
|
||||||
|
@ -12,21 +12,23 @@ from math import atan2, hypot, degrees
|
|||||||
class Point(QtCore.QPointF):
|
class Point(QtCore.QPointF):
|
||||||
"""Extension of QPointF which adds a few missing methods."""
|
"""Extension of QPointF which adds a few missing methods."""
|
||||||
|
|
||||||
|
__slots__ = ()
|
||||||
|
|
||||||
def __init__(self, *args):
|
def __init__(self, *args):
|
||||||
if len(args) == 1:
|
if len(args) == 1:
|
||||||
if isinstance(args[0], QtCore.QSizeF):
|
if isinstance(args[0], (QtCore.QSize, QtCore.QSizeF)):
|
||||||
QtCore.QPointF.__init__(self, float(args[0].width()), float(args[0].height()))
|
super().__init__(float(args[0].width()), float(args[0].height()))
|
||||||
return
|
return
|
||||||
elif isinstance(args[0], float) or isinstance(args[0], int):
|
elif isinstance(args[0], (int, float)):
|
||||||
QtCore.QPointF.__init__(self, float(args[0]), float(args[0]))
|
super().__init__(float(args[0]), float(args[0]))
|
||||||
return
|
return
|
||||||
elif hasattr(args[0], '__getitem__'):
|
elif hasattr(args[0], '__getitem__'):
|
||||||
QtCore.QPointF.__init__(self, float(args[0][0]), float(args[0][1]))
|
super().__init__(float(args[0][0]), float(args[0][1]))
|
||||||
return
|
return
|
||||||
elif len(args) == 2:
|
elif len(args) == 2:
|
||||||
QtCore.QPointF.__init__(self, args[0], args[1])
|
super().__init__(args[0], args[1])
|
||||||
return
|
return
|
||||||
QtCore.QPointF.__init__(self, *args)
|
super().__init__(*args)
|
||||||
|
|
||||||
def __len__(self):
|
def __len__(self):
|
||||||
return 2
|
return 2
|
||||||
@ -42,6 +44,10 @@ class Point(QtCore.QPointF):
|
|||||||
else:
|
else:
|
||||||
raise IndexError("Point has no index %s" % str(i))
|
raise IndexError("Point has no index %s" % str(i))
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
yield(self.x())
|
||||||
|
yield(self.y())
|
||||||
|
|
||||||
def __setitem__(self, i, x):
|
def __setitem__(self, i, x):
|
||||||
if i == 0:
|
if i == 0:
|
||||||
return self.setX(x)
|
return self.setX(x)
|
||||||
@ -87,47 +93,67 @@ class Point(QtCore.QPointF):
|
|||||||
return self._math_('__pow__', a)
|
return self._math_('__pow__', a)
|
||||||
|
|
||||||
def _math_(self, op, x):
|
def _math_(self, op, x):
|
||||||
x = Point(x)
|
if not isinstance(x, QtCore.QPointF):
|
||||||
return Point(getattr(self[0], op)(x[0]), getattr(self[1], op)(x[1]))
|
x = Point(x)
|
||||||
|
return Point(getattr(self.x(), op)(x.x()), getattr(self.y(), op)(x.y()))
|
||||||
|
|
||||||
def length(self):
|
def length(self):
|
||||||
"""Returns the vector length of this Point."""
|
"""Returns the vector length of this Point."""
|
||||||
return hypot(self[0], self[1]) # length
|
return hypot(self.x(), self.y()) # length
|
||||||
|
|
||||||
def norm(self):
|
def norm(self):
|
||||||
"""Returns a vector in the same direction with unit length."""
|
"""Returns a vector in the same direction with unit length."""
|
||||||
return self / self.length()
|
return self / self.length()
|
||||||
|
|
||||||
def angle(self, a):
|
def angle(self, a, units="degrees"):
|
||||||
"""Returns the angle in degrees between this vector and the vector a."""
|
"""
|
||||||
rads = atan2(self.y(), self.x()) - atan2(a.y(), a.x())
|
Returns the angle in degrees between this vector and the vector a.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
a : Point, QPointF or QPoint
|
||||||
|
The Point to return the angle with
|
||||||
|
units : str, optional
|
||||||
|
The units with which to compute the angle with, "degrees" or "radians",
|
||||||
|
default "degrees"
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
float
|
||||||
|
The angle between the two points
|
||||||
|
"""
|
||||||
|
rads = atan2(a.y(), a.x()) - atan2(self.y(), self.x())
|
||||||
|
if units == "radians":
|
||||||
|
return rads
|
||||||
return degrees(rads)
|
return degrees(rads)
|
||||||
|
|
||||||
def dot(self, a):
|
def dot(self, a):
|
||||||
"""Returns the dot product of a and this Point."""
|
"""Returns the dot product of a and this Point."""
|
||||||
a = Point(a)
|
if not isinstance(a, QtCore.QPointF):
|
||||||
return self[0]*a[0] + self[1]*a[1]
|
a = Point(a)
|
||||||
|
return Point.dotProduct(self, a)
|
||||||
|
|
||||||
def cross(self, a):
|
def cross(self, a):
|
||||||
a = Point(a)
|
if not isinstance(a, QtCore.QPointF):
|
||||||
return self[0]*a[1] - self[1]*a[0]
|
a = Point(a)
|
||||||
|
return self.x() * a.y() - self.y() * a.x()
|
||||||
|
|
||||||
def proj(self, b):
|
def proj(self, b):
|
||||||
"""Return the projection of this vector onto the vector b"""
|
"""Return the projection of this vector onto the vector b"""
|
||||||
b1 = b / b.length()
|
b1 = b.norm()
|
||||||
return self.dot(b1) * b1
|
return self.dot(b1) * b1
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "Point(%f, %f)" % (self[0], self[1])
|
return "Point(%f, %f)" % (self.x(), self.y())
|
||||||
|
|
||||||
def min(self):
|
def min(self):
|
||||||
return min(self[0], self[1])
|
return min(self.x(), self.y())
|
||||||
|
|
||||||
def max(self):
|
def max(self):
|
||||||
return max(self[0], self[1])
|
return max(self.x(), self.y())
|
||||||
|
|
||||||
def copy(self):
|
def copy(self):
|
||||||
return Point(self)
|
return Point(self)
|
||||||
|
|
||||||
def toQPoint(self):
|
def toQPoint(self):
|
||||||
return QtCore.QPoint(int(self[0]), int(self[1]))
|
return self.toPoint()
|
||||||
|
@ -66,8 +66,7 @@ class SRTTransform(QtGui.QTransform):
|
|||||||
dp3 = Point(p3-p1)
|
dp3 = Point(p3-p1)
|
||||||
|
|
||||||
## detect flipped axes
|
## detect flipped axes
|
||||||
if dp2.angle(dp3) > 0:
|
if dp3.angle(dp2, units="radians") > 0:
|
||||||
#da = 180
|
|
||||||
da = 0
|
da = 0
|
||||||
sy = -1.0
|
sy = -1.0
|
||||||
else:
|
else:
|
||||||
|
@ -439,13 +439,10 @@ class GraphicsItem(object):
|
|||||||
if relativeItem is None:
|
if relativeItem is None:
|
||||||
relativeItem = self.parentItem()
|
relativeItem = self.parentItem()
|
||||||
|
|
||||||
|
|
||||||
tr = self.itemTransform(relativeItem)
|
tr = self.itemTransform(relativeItem)
|
||||||
if isinstance(tr, tuple): ## difference between pyside and pyqt
|
if isinstance(tr, tuple): ## difference between pyside and pyqt
|
||||||
tr = tr[0]
|
tr = tr[0]
|
||||||
#vec = tr.map(Point(1,0)) - tr.map(Point(0,0))
|
|
||||||
vec = tr.map(QtCore.QLineF(0,0,1,0))
|
vec = tr.map(QtCore.QLineF(0,0,1,0))
|
||||||
#return Point(vec).angle(Point(1,0))
|
|
||||||
return vec.angleTo(QtCore.QLineF(vec.p1(), vec.p1()+QtCore.QPointF(1,0)))
|
return vec.angleTo(QtCore.QLineF(vec.p1(), vec.p1()+QtCore.QPointF(1,0)))
|
||||||
|
|
||||||
#def itemChange(self, change, value):
|
#def itemChange(self, change, value):
|
||||||
|
@ -17,7 +17,7 @@ import numpy as np
|
|||||||
#from numpy.linalg import norm
|
#from numpy.linalg import norm
|
||||||
from ..Point import Point
|
from ..Point import Point
|
||||||
from ..SRTTransform import SRTTransform
|
from ..SRTTransform import SRTTransform
|
||||||
from math import atan2, cos, sin, hypot, radians
|
from math import atan2, cos, degrees, sin, hypot
|
||||||
from .. import functions as fn
|
from .. import functions as fn
|
||||||
from .GraphicsObject import GraphicsObject
|
from .GraphicsObject import GraphicsObject
|
||||||
from .UIGraphicsItem import UIGraphicsItem
|
from .UIGraphicsItem import UIGraphicsItem
|
||||||
@ -936,7 +936,7 @@ class ROI(GraphicsObject):
|
|||||||
return
|
return
|
||||||
|
|
||||||
## determine new rotation angle, constrained if necessary
|
## determine new rotation angle, constrained if necessary
|
||||||
ang = newState['angle'] - lp0.angle(lp1)
|
ang = newState['angle'] - lp1.angle(lp0)
|
||||||
if ang is None: ## this should never happen..
|
if ang is None: ## this should never happen..
|
||||||
return
|
return
|
||||||
if self.rotateSnap or (modifiers & QtCore.Qt.ControlModifier):
|
if self.rotateSnap or (modifiers & QtCore.Qt.ControlModifier):
|
||||||
@ -972,7 +972,7 @@ class ROI(GraphicsObject):
|
|||||||
except OverflowError:
|
except OverflowError:
|
||||||
return
|
return
|
||||||
|
|
||||||
ang = newState['angle'] - lp0.angle(lp1)
|
ang = newState['angle'] - lp1.angle(lp0)
|
||||||
if ang is None:
|
if ang is None:
|
||||||
return
|
return
|
||||||
if self.rotateSnap or (modifiers & QtCore.Qt.ControlModifier):
|
if self.rotateSnap or (modifiers & QtCore.Qt.ControlModifier):
|
||||||
@ -1663,12 +1663,11 @@ class LineROI(ROI):
|
|||||||
pos2 = Point(pos2)
|
pos2 = Point(pos2)
|
||||||
d = pos2-pos1
|
d = pos2-pos1
|
||||||
l = d.length()
|
l = d.length()
|
||||||
ang = Point(1, 0).angle(d)
|
ra = d.angle(Point(1, 0), units="radians")
|
||||||
ra = radians(ang if ang is not None else 0.)
|
|
||||||
c = Point(-width/2. * sin(ra), -width/2. * cos(ra))
|
c = Point(-width/2. * sin(ra), -width/2. * cos(ra))
|
||||||
pos1 = pos1 + c
|
pos1 = pos1 + c
|
||||||
|
|
||||||
ROI.__init__(self, pos1, size=Point(l, width), angle=ang, **args)
|
ROI.__init__(self, pos1, size=Point(l, width), angle=degrees(ra), **args)
|
||||||
self.addScaleRotateHandle([0, 0.5], [1, 0.5])
|
self.addScaleRotateHandle([0, 0.5], [1, 0.5])
|
||||||
self.addScaleRotateHandle([1, 0.5], [0, 0.5])
|
self.addScaleRotateHandle([1, 0.5], [0, 0.5])
|
||||||
self.addScaleHandle([0.5, 1], [0.5, 0.5])
|
self.addScaleHandle([0.5, 1], [0.5, 0.5])
|
||||||
@ -2359,7 +2358,7 @@ class RulerROI(LineSegmentROI):
|
|||||||
|
|
||||||
vec = Point(h2) - Point(h1)
|
vec = Point(h2) - Point(h1)
|
||||||
length = vec.length()
|
length = vec.length()
|
||||||
angle = vec.angle(Point(1, 0))
|
angle = Point(1, 0).angle(vec)
|
||||||
|
|
||||||
pvec = p2 - p1
|
pvec = p2 - p1
|
||||||
pvecT = Point(pvec.y(), -pvec.x())
|
pvecT = Point(pvec.y(), -pvec.x())
|
||||||
|
63
pyqtgraph/tests/test_Point.py
Normal file
63
pyqtgraph/tests/test_Point.py
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
import pytest
|
||||||
|
import pyqtgraph as pg
|
||||||
|
from pyqtgraph.Qt import QtCore
|
||||||
|
import math
|
||||||
|
|
||||||
|
angles = [
|
||||||
|
((1, 0), (0, 1), 90),
|
||||||
|
((0, 1), (1, 0), -90),
|
||||||
|
((-1, 0), (-1, 0), 0),
|
||||||
|
((0, -1), (0, 1), 180),
|
||||||
|
]
|
||||||
|
@pytest.mark.parametrize("p1, p2, angle", angles)
|
||||||
|
def test_Point_angle(p1, p2, angle):
|
||||||
|
p1 = pg.Point(*p1)
|
||||||
|
p2 = pg.Point(*p2)
|
||||||
|
assert p1.angle(p2) == angle
|
||||||
|
|
||||||
|
|
||||||
|
inits = [
|
||||||
|
(QtCore.QSizeF(1, 0), (1.0, 0.0)),
|
||||||
|
((0, -1), (0.0, -1.0)),
|
||||||
|
([1, 1], (1.0, 1.0)),
|
||||||
|
]
|
||||||
|
@pytest.mark.parametrize("initArgs, positions", inits)
|
||||||
|
def test_Point_init(initArgs, positions):
|
||||||
|
if isinstance(initArgs, QtCore.QSizeF):
|
||||||
|
point = pg.Point(initArgs)
|
||||||
|
else:
|
||||||
|
point = pg.Point(*initArgs)
|
||||||
|
assert (point.x(), point.y()) == positions
|
||||||
|
|
||||||
|
lengths = [
|
||||||
|
((0, 1), 1),
|
||||||
|
((1, 0), 1),
|
||||||
|
((0, 0), 0),
|
||||||
|
((1, 1), math.sqrt(2)),
|
||||||
|
((-1, -1), math.sqrt(2))
|
||||||
|
]
|
||||||
|
@pytest.mark.parametrize("initArgs, length", lengths)
|
||||||
|
def test_Point_length(initArgs, length):
|
||||||
|
point = pg.Point(initArgs)
|
||||||
|
assert point.length() == length
|
||||||
|
|
||||||
|
min_max = [
|
||||||
|
((0, 1), 0, 1),
|
||||||
|
((1, 0), 0, 1),
|
||||||
|
((-math.inf, 0), -math.inf, 0),
|
||||||
|
((0, math.inf), 0, math.inf)
|
||||||
|
]
|
||||||
|
@pytest.mark.parametrize("initArgs, min_, max_", min_max)
|
||||||
|
def test_Point_min_max(initArgs, min_, max_):
|
||||||
|
point = pg.Point(initArgs)
|
||||||
|
assert min(point) == min_
|
||||||
|
assert max(point) == max_
|
||||||
|
|
||||||
|
projections = [
|
||||||
|
((0, 1), (1, 0), (1, 1))
|
||||||
|
]
|
||||||
|
@pytest.mark.parametrize("p1_arg, p2_arg, projection", projections)
|
||||||
|
def test_Point_projection(p1_arg, p2_arg, projection):
|
||||||
|
p1 = pg.Point(p1_arg)
|
||||||
|
p2 = pg.Point(p2_arg)
|
||||||
|
p1.proj(p2) == projection
|
Loading…
Reference in New Issue
Block a user