From b0a3849960325f2fca4b1336d68d936fd4ea7314 Mon Sep 17 00:00:00 2001 From: Ogi Moore Date: Sat, 17 Apr 2021 20:08:57 -0700 Subject: [PATCH 01/14] Use math module methods for singular values Using numpy methods that are intended for vectorized operations is substantially slower than using the math module, so when feasible the math module methods should be used. --- examples/GLIsosurface.py | 11 +++----- examples/GLVolumeItem.py | 2 +- examples/GLshaders.py | 4 +-- examples/optics/pyoptic.py | 36 ++++++++++++------------- pyqtgraph/Point.py | 10 +++---- pyqtgraph/SRTTransform.py | 3 ++- pyqtgraph/SRTTransform3D.py | 3 ++- pyqtgraph/graphicsItems/CurvePoint.py | 12 +++++---- pyqtgraph/graphicsItems/InfiniteLine.py | 3 ++- pyqtgraph/graphicsItems/ROI.py | 22 +++++++-------- pyqtgraph/graphicsItems/TextItem.py | 4 +-- 11 files changed, 53 insertions(+), 57 deletions(-) diff --git a/examples/GLIsosurface.py b/examples/GLIsosurface.py index 16045a78..3da8dfa5 100644 --- a/examples/GLIsosurface.py +++ b/examples/GLIsosurface.py @@ -7,6 +7,7 @@ This example uses the isosurface function to convert a scalar field ## Add path to library (just for examples; you do not need this) import initExample +import numpy as np from pyqtgraph.Qt import QtCore, QtGui import pyqtgraph as pg import pyqtgraph.opengl as gl @@ -22,23 +23,17 @@ g = gl.GLGridItem() g.scale(2,2,1) w.addItem(g) -import numpy as np - ## Define a scalar field from which we will generate an isosurface def psi(i, j, k, offset=(25, 25, 50)): x = i-offset[0] y = j-offset[1] z = k-offset[2] - th = np.arctan2(z, (x**2+y**2)**0.5) + th = np.arctan2(z, np.sqrt(x**2+y**2)) phi = np.arctan2(y, x) - r = (x**2 + y**2 + z **2)**0.5 + r = np.sqrt(x**2 + y**2 + z **2) a0 = 1 - #ps = (1./81.) * (2./np.pi)**0.5 * (1./a0)**(3/2) * (6 - r/a0) * (r/a0) * np.exp(-r/(3*a0)) * np.cos(th) ps = (1./81.) * 1./(6.*np.pi)**0.5 * (1./a0)**(3/2) * (r/a0)**2 * np.exp(-r/(3*a0)) * (3 * np.cos(th)**2 - 1) - return ps - - #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..") diff --git a/examples/GLVolumeItem.py b/examples/GLVolumeItem.py index 5d7e6601..601ddfa1 100644 --- a/examples/GLVolumeItem.py +++ b/examples/GLVolumeItem.py @@ -7,6 +7,7 @@ Demonstrates GLVolumeItem for displaying volumetric data. ## Add path to library (just for examples; you do not need this) import initExample +import numpy as np import pyqtgraph as pg from pyqtgraph.Qt import QtCore, QtGui import pyqtgraph.opengl as gl @@ -23,7 +24,6 @@ g = gl.GLGridItem() g.scale(10, 10, 1) w.addItem(g) -import numpy as np ## Hydrogen electron probability density def psi(i, j, k, offset=(50,50,100)): x = i-offset[0] diff --git a/examples/GLshaders.py b/examples/GLshaders.py index 4e1acc51..38e6988d 100644 --- a/examples/GLshaders.py +++ b/examples/GLshaders.py @@ -9,6 +9,7 @@ used to affect the appearance of a surface. ## Add path to library (just for examples; you do not need this) import initExample +import numpy as np from pyqtgraph.Qt import QtCore, QtGui import pyqtgraph as pg import pyqtgraph.opengl as gl @@ -23,9 +24,6 @@ g = gl.GLGridItem() g.scale(2,2,1) w.addItem(g) -import numpy as np - - md = gl.MeshData.sphere(rows=10, cols=20) x = np.linspace(-8, 8, 6) diff --git a/examples/optics/pyoptic.py b/examples/optics/pyoptic.py index f70edd2a..811ae665 100644 --- a/examples/optics/pyoptic.py +++ b/examples/optics/pyoptic.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +from math import atan2, asin, sin, cos, sqrt, pi import pyqtgraph as pg from pyqtgraph.Qt import QtGui, QtCore import numpy as np @@ -49,7 +50,7 @@ class GlassDB: B = list(map(float, [info['B1'], info['B2'], info['B3']])) C = list(map(float, [info['C1'], info['C2'], info['C3']])) w2 = (wl/1000.)**2 - n = np.sqrt(1.0 + (B[0]*w2 / (w2-C[0])) + (B[1]*w2 / (w2-C[1])) + (B[2]*w2 / (w2-C[2]))) + n = sqrt(1.0 + (B[0]*w2 / (w2-C[0])) + (B[1]*w2 / (w2-C[1])) + (B[2]*w2 / (w2-C[2]))) cache[wl] = n return cache[wl] @@ -249,10 +250,14 @@ class Lens(Optic): p1 = surface.mapToItem(ray, p1) rd = ray['dir'] - a1 = np.arctan2(rd[1], rd[0]) - ar = a1 - ai + np.arcsin((np.sin(ai) * ray['ior'] / ior)) + + a1 = atan2(rd[1], rd[0]) + try: + ar = a1 - ai + asin((sin(ai) * ray['ior'] / ior)) + except ValueError: + ar = np.nan ray.setEnd(p1) - dp = Point(np.cos(ar), np.sin(ar)) + dp = Point(cos(ar), sin(ar)) ray = Ray(parent=ray, ior=ior, dir=dp) return [ray] @@ -279,10 +284,10 @@ class Mirror(Optic): if p1 is not None: p1 = surface.mapToItem(ray, p1) rd = ray['dir'] - a1 = np.arctan2(rd[1], rd[0]) - ar = a1 + np.pi - 2*ai + a1 = atan2(rd[1], rd[0]) + ar = a1 + pi - 2*ai ray.setEnd(p1) - dp = Point(np.cos(ar), np.sin(ar)) + dp = Point(cos(ar), sin(ar)) ray = Ray(parent=ray, dir=dp) else: ray.setEnd(None) @@ -374,7 +379,7 @@ class CircleSurface(pg.GraphicsObject): ## half-height of surface can't be larger than radius h2 = min(h2, abs(r)) arc = QtCore.QRectF(0, -r, r*2, r*2) - a1 = np.arcsin(h2/r) * 180. / np.pi + a1 = asin(h2/r) * 180. / pi a2 = -2*a1 a1 += 180. self.path.arcMoveTo(arc, a1) @@ -406,7 +411,7 @@ class CircleSurface(pg.GraphicsObject): if abs(y) > h: return None, None else: - return (Point(0, y), np.arctan2(dir[1], dir[0])) + return (Point(0, y), atan2(dir[1], dir[0])) else: #print " curve" ## find intersection of circle and line (quadratic formula) @@ -436,19 +441,12 @@ class CircleSurface(pg.GraphicsObject): pt = Point(x2, y2) if not br.contains(x2+r, y2): return None, None - raise Exception("No intersection!") - norm = np.arctan2(pt[1], pt[0]) + norm = atan2(pt[1], pt[0]) if r < 0: - norm += np.pi - #print " norm:", norm*180/3.1415 + norm += pi dp = p - pt - #print " dp:", dp - ang = np.arctan2(dp[1], dp[0]) - #print " ang:", ang*180/3.1415 - #print " ai:", (ang-norm)*180/3.1415 - - #print " intersection:", pt + ang = atan2(dp[1], dp[0]) return pt + Point(r, 0), ang-norm diff --git a/pyqtgraph/Point.py b/pyqtgraph/Point.py index fea37dda..148178f9 100644 --- a/pyqtgraph/Point.py +++ b/pyqtgraph/Point.py @@ -6,7 +6,7 @@ Distributed under MIT/X11 license. See license.txt for more information. """ from .Qt import QtCore -import numpy as np +from math import sin, acos, atan2, inf, pi def clip(x, mn, mx): if x > mx: @@ -109,9 +109,9 @@ class Point(QtCore.QPointF): return (self[0]**2 + self[1]**2) ** 0.5 except OverflowError: try: - return self[1] / np.sin(np.arctan2(self[1], self[0])) + return self[1] / sin(atan2(self[1], self[0])) except OverflowError: - return np.inf + return inf def norm(self): """Returns a vector in the same direction with unit length.""" @@ -124,11 +124,11 @@ class Point(QtCore.QPointF): if n1 == 0. or n2 == 0.: return None ## Probably this should be done with arctan2 instead.. - ang = np.arccos(clip(self.dot(a) / (n1 * n2), -1.0, 1.0)) ### in radians + ang = acos(clip(self.dot(a) / (n1 * n2), -1.0, 1.0)) ### in radians c = self.cross(a) if c > 0: ang *= -1. - return ang * 180. / np.pi + return ang * 180. / pi def dot(self, a): """Returns the dot product of a and this Point.""" diff --git a/pyqtgraph/SRTTransform.py b/pyqtgraph/SRTTransform.py index 35ec0625..81bfb24d 100644 --- a/pyqtgraph/SRTTransform.py +++ b/pyqtgraph/SRTTransform.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +from math import atan2, pi from .Qt import QtCore, QtGui from .Point import Point import numpy as np @@ -76,7 +77,7 @@ class SRTTransform(QtGui.QTransform): self._state = { 'pos': Point(p1), 'scale': Point(dp2.length(), dp3.length() * sy), - 'angle': (np.arctan2(dp2[1], dp2[0]) * 180. / np.pi) + da + 'angle': (atan2(dp2[1], dp2[0]) * 180. / pi) + da } self.update() diff --git a/pyqtgraph/SRTTransform3D.py b/pyqtgraph/SRTTransform3D.py index 7d458edd..438a410c 100644 --- a/pyqtgraph/SRTTransform3D.py +++ b/pyqtgraph/SRTTransform3D.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +from math import atan2, pi from .Qt import QtCore, QtGui from .Vector import Vector from .Transform3D import Transform3D @@ -164,7 +165,7 @@ class SRTTransform3D(Transform3D): sin = (r-r.T)[rInd] / (2. * sign * axis[axisInd]) ## finally, we get the complete angle from arctan(sin/cos) - self._state['angle'] = np.arctan2(sin, cos) * 180 / np.pi + self._state['angle'] = atan2(sin, cos) * 180 / pi if self._state['angle'] == 0: self._state['axis'] = (0,0,1) diff --git a/pyqtgraph/graphicsItems/CurvePoint.py b/pyqtgraph/graphicsItems/CurvePoint.py index 368a0a82..9e4d3718 100644 --- a/pyqtgraph/graphicsItems/CurvePoint.py +++ b/pyqtgraph/graphicsItems/CurvePoint.py @@ -1,5 +1,7 @@ +from math import atan2, pi from ..Qt import QtGui, QtCore from . import ArrowItem +from ..functions import clip_scalar import numpy as np from ..Point import Point import weakref @@ -65,22 +67,22 @@ class CurvePoint(GraphicsObject): if index != int(index): ## interpolate floating-point values i1 = int(index) - i2 = np.clip(i1+1, 0, len(x)-1) + i2 = clip_scalar(i1+1, 0, len(x)-1) s2 = index-i1 s1 = 1.0-s2 newPos = (x[i1]*s1+x[i2]*s2, y[i1]*s1+y[i2]*s2) else: index = int(index) - i1 = np.clip(index-1, 0, len(x)-1) - i2 = np.clip(index+1, 0, len(x)-1) + i1 = clip_scalar(index-1, 0, len(x)-1) + i2 = clip_scalar(index+1, 0, len(x)-1) newPos = (x[index], y[index]) p1 = self.parentItem().mapToScene(QtCore.QPointF(x[i1], y[i1])) p2 = self.parentItem().mapToScene(QtCore.QPointF(x[i2], y[i2])) - ang = np.arctan2(p2.y()-p1.y(), p2.x()-p1.x()) ## returns radians + ang = atan2(p2.y()-p1.y(), p2.x()-p1.x()) ## returns radians self.resetTransform() if self._rotate: - self.setRotation(180 + np.rad2deg(ang)) ## takes degrees + self.setRotation(180 + ang * (180. / pi)) QtGui.QGraphicsItem.setPos(self, *newPos) return True diff --git a/pyqtgraph/graphicsItems/InfiniteLine.py b/pyqtgraph/graphicsItems/InfiniteLine.py index 74a8da95..ce96d00b 100644 --- a/pyqtgraph/graphicsItems/InfiniteLine.py +++ b/pyqtgraph/graphicsItems/InfiniteLine.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +from math import atan2, pi from ..Qt import QtGui, QtCore from ..Point import Point from .GraphicsObject import GraphicsObject @@ -359,7 +360,7 @@ class InfiniteLine(GraphicsObject): up = tr.map(Point(left, 1)) dif = end - start length = Point(dif).length() - angle = np.arctan2(dif.y(), dif.x()) * 180 / np.pi + angle = atan2(dif.y(), dif.x()) * 180 / pi p.translate(start) p.rotate(angle) diff --git a/pyqtgraph/graphicsItems/ROI.py b/pyqtgraph/graphicsItems/ROI.py index c2cb49ed..566651b8 100644 --- a/pyqtgraph/graphicsItems/ROI.py +++ b/pyqtgraph/graphicsItems/ROI.py @@ -17,7 +17,7 @@ import numpy as np #from numpy.linalg import norm from ..Point import * from ..SRTTransform import SRTTransform -from math import cos, sin +from math import atan2, cos, sin, pi, sqrt from .. import functions as fn from .GraphicsObject import GraphicsObject from .UIGraphicsItem import UIGraphicsItem @@ -1247,8 +1247,8 @@ class ROI(GraphicsObject): vx = img.mapToData(self.mapToItem(img, QtCore.QPointF(1, 0))) - origin vy = img.mapToData(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) + lvx = sqrt(vx.x()**2 + vx.y()**2) + lvy = sqrt(vy.x()**2 + vy.y()**2) ##img.width is number of pixels, not width of item. ##need pxWidth and pxHeight instead of pxLen ? sx = 1.0 / lvx @@ -1332,8 +1332,8 @@ class Handle(UIGraphicsItem): properties of the ROI they are attached to. """ types = { ## defines number of sides, start angle for each handle type - 't': (4, np.pi/4), - 'f': (4, np.pi/4), + 't': (4, pi/4), + 'f': (4, pi/4), 's': (4, 0), 'r': (12, 0), 'sr': (12, 0), @@ -1481,7 +1481,7 @@ class Handle(UIGraphicsItem): size = self.radius self.path = QtGui.QPainterPath() ang = self.startAng - dt = 2*np.pi / self.sides + dt = 2 * pi / self.sides for i in range(0, self.sides+1): x = size * cos(ang) y = size * sin(ang) @@ -1518,13 +1518,13 @@ class Handle(UIGraphicsItem): return None v = dt.map(QtCore.QPointF(1, 0)) - dt.map(QtCore.QPointF(0, 0)) - va = np.arctan2(v.y(), v.x()) + va = atan2(v.y(), v.x()) dti = fn.invertQTransform(dt) devPos = dt.map(QtCore.QPointF(0,0)) tr = QtGui.QTransform() tr.translate(devPos.x(), devPos.y()) - tr.rotate(va * 180. / 3.1415926) + tr.rotate(va * 180. / pi) return dti.map(tr.map(self.path)) @@ -1663,7 +1663,7 @@ class LineROI(ROI): d = pos2-pos1 l = d.length() ang = Point(1, 0).angle(d) - ra = ang * np.pi / 180. + ra = ang * pi / 180. c = Point(-width/2. * sin(ra), -width/2. * cos(ra)) pos1 = pos1 + c @@ -1914,7 +1914,7 @@ class EllipseROI(ROI): center = br.center() r1 = br.width() / 2. r2 = br.height() / 2. - theta = np.linspace(0, 2*np.pi, 24) + theta = np.linspace(0, 2 * pi, 24) x = center.x() + r1 * np.cos(theta) y = center.y() + r2 * np.sin(theta) path.moveTo(x[0], y[0]) @@ -2396,7 +2396,7 @@ class TriangleROI(ROI): def __init__(self, pos, size, **args): ROI.__init__(self, pos, [size, size], **args) self.aspectLocked = True - angles = np.linspace(0, np.pi * 4 / 3, 3) + angles = np.linspace(0, pi * 4 / 3, 3) verticies = (np.array((np.sin(angles), np.cos(angles))).T + 1.0) / 2.0 self.poly = QtGui.QPolygonF() for pt in verticies: diff --git a/pyqtgraph/graphicsItems/TextItem.py b/pyqtgraph/graphicsItems/TextItem.py index 8dcb04f6..6b2dfdd5 100644 --- a/pyqtgraph/graphicsItems/TextItem.py +++ b/pyqtgraph/graphicsItems/TextItem.py @@ -1,4 +1,4 @@ -import numpy as np +from math import pi, atan2 from ..Qt import QtCore, QtGui from ..Point import Point from .. import functions as fn @@ -208,7 +208,7 @@ class TextItem(GraphicsObject): angle = -self.angle if self.rotateAxis is not None: d = pt.map(self.rotateAxis) - pt.map(Point(0, 0)) - a = np.arctan2(d.y(), d.x()) * 180 / np.pi + a = atan2(d.y(), d.x()) * 180 / pi angle += a t.rotate(angle) self.setTransform(t) From f8cefa628482420a3d4df5ad778444554ed69b69 Mon Sep 17 00:00:00 2001 From: Ogi Moore Date: Sat, 17 Apr 2021 22:38:21 -0700 Subject: [PATCH 02/14] Use hypot method to avoid over/underflow errors Use hypot instead of manual calculation --- examples/GLIsosurface.py | 2 +- examples/GLLinePlotItem.py | 4 ++-- examples/GLVolumeItem.py | 7 ++----- examples/optics/pyoptic.py | 7 +++---- pyqtgraph/Point.py | 18 +++--------------- pyqtgraph/graphicsItems/ArrowItem.py | 3 ++- pyqtgraph/graphicsItems/GraphicsItem.py | 3 ++- pyqtgraph/graphicsItems/ROI.py | 8 ++++---- pyqtgraph/opengl/items/GLScatterPlotItem.py | 8 ++++---- pyqtgraph/widgets/JoystickButton.py | 7 ++++--- 10 files changed, 27 insertions(+), 40 deletions(-) diff --git a/examples/GLIsosurface.py b/examples/GLIsosurface.py index 3da8dfa5..0f773885 100644 --- a/examples/GLIsosurface.py +++ b/examples/GLIsosurface.py @@ -28,7 +28,7 @@ def psi(i, j, k, offset=(25, 25, 50)): x = i-offset[0] y = j-offset[1] z = k-offset[2] - th = np.arctan2(z, np.sqrt(x**2+y**2)) + th = np.arctan2(z, np.hypot(x, y)) phi = np.arctan2(y, x) r = np.sqrt(x**2 + y**2 + z **2) a0 = 1 diff --git a/examples/GLLinePlotItem.py b/examples/GLLinePlotItem.py index b375c1c1..9be9c07c 100644 --- a/examples/GLLinePlotItem.py +++ b/examples/GLLinePlotItem.py @@ -30,14 +30,14 @@ gz.translate(0, 0, -10) w.addItem(gz) def fn(x, y): - return np.cos((x**2 + y**2)**0.5) + return np.cos(np.hypot(x, y)) n = 51 y = np.linspace(-10,10,n) x = np.linspace(-10,10,100) for i in range(n): yi = np.array([y[i]]*100) - d = (x**2 + yi**2)**0.5 + d = np.hypot(x, yi) z = 10 * np.cos(d) / (d+1) pts = np.vstack([x,yi,z]).transpose() plt = gl.GLLinePlotItem(pos=pts, color=pg.glColor((i,n*1.3)), width=(i+1)/10., antialias=True) diff --git a/examples/GLVolumeItem.py b/examples/GLVolumeItem.py index 601ddfa1..ae2e90ff 100644 --- a/examples/GLVolumeItem.py +++ b/examples/GLVolumeItem.py @@ -29,16 +29,13 @@ def psi(i, j, k, offset=(50,50,100)): x = i-offset[0] y = j-offset[1] z = k-offset[2] - th = np.arctan2(z, (x**2+y**2)**0.5) + th = np.arctan2(z, np.hypot(x, y)) phi = np.arctan2(y, x) - r = (x**2 + y**2 + z **2)**0.5 + r = np.sqrt(x**2 + y**2 + z **2) a0 = 2 #ps = (1./81.) * (2./np.pi)**0.5 * (1./a0)**(3/2) * (6 - r/a0) * (r/a0) * np.exp(-r/(3*a0)) * np.cos(th) ps = (1./81.) * 1./(6.*np.pi)**0.5 * (1./a0)**(3/2) * (r/a0)**2 * np.exp(-r/(3*a0)) * (3 * np.cos(th)**2 - 1) - return ps - - #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 data = np.fromfunction(psi, (100,100,200)) diff --git a/examples/optics/pyoptic.py b/examples/optics/pyoptic.py index 811ae665..442a4f5d 100644 --- a/examples/optics/pyoptic.py +++ b/examples/optics/pyoptic.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from math import atan2, asin, sin, cos, sqrt, pi +from math import atan2, asin, sin, cos, sqrt, pi, hypot import pyqtgraph as pg from pyqtgraph.Qt import QtGui, QtCore import numpy as np @@ -417,7 +417,7 @@ class CircleSurface(pg.GraphicsObject): ## find intersection of circle and line (quadratic formula) dx = dir[0] dy = dir[1] - dr = (dx**2 + dy**2) ** 0.5 + dr = hypot(dx, dy) # length D = p[0] * (p[1]+dy) - (p[0]+dx) * p[1] idr2 = 1.0 / dr**2 disc = r**2 * dr**2 - D**2 @@ -428,8 +428,7 @@ class CircleSurface(pg.GraphicsObject): sgn = -1 else: sgn = 1 - - + br = self.path.boundingRect() x1 = (D*dy + sgn*dx*disc2) * idr2 y1 = (-D*dx + abs(dy)*disc2) * idr2 diff --git a/pyqtgraph/Point.py b/pyqtgraph/Point.py index 148178f9..78a3a681 100644 --- a/pyqtgraph/Point.py +++ b/pyqtgraph/Point.py @@ -6,7 +6,7 @@ Distributed under MIT/X11 license. See license.txt for more information. """ from .Qt import QtCore -from math import sin, acos, atan2, inf, pi +from math import sin, acos, atan2, inf, pi, hypot def clip(x, mn, mx): if x > mx: @@ -93,25 +93,13 @@ class Point(QtCore.QPointF): return self._math_('__pow__', a) def _math_(self, op, x): - #print "point math:", op - #try: - #fn = getattr(QtCore.QPointF, op) - #pt = fn(self, x) - #print fn, pt, self, x - #return Point(pt) - #except AttributeError: x = Point(x) return Point(getattr(self[0], op)(x[0]), getattr(self[1], op)(x[1])) def length(self): """Returns the vector length of this Point.""" - try: - return (self[0]**2 + self[1]**2) ** 0.5 - except OverflowError: - try: - return self[1] / sin(atan2(self[1], self[0])) - except OverflowError: - return inf + return hypot(self[0], self[1]) # length + def norm(self): """Returns a vector in the same direction with unit length.""" diff --git a/pyqtgraph/graphicsItems/ArrowItem.py b/pyqtgraph/graphicsItems/ArrowItem.py index 77b6c44c..2d79d360 100644 --- a/pyqtgraph/graphicsItems/ArrowItem.py +++ b/pyqtgraph/graphicsItems/ArrowItem.py @@ -1,3 +1,4 @@ +from math import hypot from ..Qt import QtGui, QtCore from .. import functions as fn import numpy as np @@ -135,7 +136,7 @@ class ArrowItem(QtGui.QGraphicsPathItem): pad = 0 if self.opts['pxMode']: br = self.boundingRect() - pad += (br.width()**2 + br.height()**2) ** 0.5 + pad += hypot(br.width(), br.height()) pen = self.pen() if pen.isCosmetic(): pad += max(1, pen.width()) * 0.7072 diff --git a/pyqtgraph/graphicsItems/GraphicsItem.py b/pyqtgraph/graphicsItems/GraphicsItem.py index 61f48a45..273d419f 100644 --- a/pyqtgraph/graphicsItems/GraphicsItem.py +++ b/pyqtgraph/graphicsItems/GraphicsItem.py @@ -1,4 +1,5 @@ import warnings +from math import hypot from collections import OrderedDict from functools import reduce from ..Qt import QtGui, QtCore, isQObjectAlive @@ -308,7 +309,7 @@ class GraphicsItem(object): v = self.pixelVectors() if v == (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 (hypot(v[0].x(), v[0].y()), hypot(v[1].x(), v[1].y())) # lengths def pixelWidth(self): ## deprecated diff --git a/pyqtgraph/graphicsItems/ROI.py b/pyqtgraph/graphicsItems/ROI.py index 566651b8..d7037582 100644 --- a/pyqtgraph/graphicsItems/ROI.py +++ b/pyqtgraph/graphicsItems/ROI.py @@ -17,7 +17,7 @@ import numpy as np #from numpy.linalg import norm from ..Point import * from ..SRTTransform import SRTTransform -from math import atan2, cos, sin, pi, sqrt +from math import atan2, cos, sin, pi, sqrt, hypot from .. import functions as fn from .GraphicsObject import GraphicsObject from .UIGraphicsItem import UIGraphicsItem @@ -1247,8 +1247,8 @@ class ROI(GraphicsObject): vx = img.mapToData(self.mapToItem(img, QtCore.QPointF(1, 0))) - origin vy = img.mapToData(self.mapToItem(img, QtCore.QPointF(0, 1))) - origin - lvx = sqrt(vx.x()**2 + vx.y()**2) - lvy = sqrt(vy.x()**2 + vy.y()**2) + lvx = hypot(vx.x(), vx.y()) # length + lvy = hypot(vy.x(), vy.y()) # length ##img.width is number of pixels, not width of item. ##need pxWidth and pxHeight instead of pxLen ? sx = 1.0 / lvx @@ -1887,7 +1887,7 @@ class EllipseROI(ROI): h = arr.shape[axes[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)) + mask = np.fromfunction(lambda x,y: np.hypot(((x+0.5)/(w/2.)-1), ((y+0.5)/(h/2.)-1)) < 1, (w, h)) # reshape to match array axes if axes[0] > axes[1]: diff --git a/pyqtgraph/opengl/items/GLScatterPlotItem.py b/pyqtgraph/opengl/items/GLScatterPlotItem.py index 5d81515b..09e7ec2f 100644 --- a/pyqtgraph/opengl/items/GLScatterPlotItem.py +++ b/pyqtgraph/opengl/items/GLScatterPlotItem.py @@ -3,7 +3,8 @@ from OpenGL.GL import * from OpenGL.arrays import vbo from .. GLGraphicsItem import GLGraphicsItem from .. import shaders -from ... import QtGui +from ...functions import clip_array +from ...Qt import QtGui import numpy as np __all__ = ['GLScatterPlotItem'] @@ -62,12 +63,11 @@ class GLScatterPlotItem(GLGraphicsItem): ## Generate texture for rendering points w = 64 def fn(x,y): - r = ((x-(w-1)/2.)**2 + (y-(w-1)/2.)**2) ** 0.5 - return 255 * (w/2. - np.clip(r, w/2.-1.0, w/2.)) + r = np.hypot((x-(w-1)/2.), (y-(w-1)/2.)) + return 255 * (w/2. - clip_array(r, w/2.-1.0, w/2.)) pData = np.empty((w, w, 4)) pData[:] = 255 pData[:,:,3] = np.fromfunction(fn, pData.shape[:2]) - #print pData.shape, pData.min(), pData.max() pData = pData.astype(np.ubyte) if getattr(self, "pointTexture", None) is None: diff --git a/pyqtgraph/widgets/JoystickButton.py b/pyqtgraph/widgets/JoystickButton.py index 4e4a335f..53674df2 100644 --- a/pyqtgraph/widgets/JoystickButton.py +++ b/pyqtgraph/widgets/JoystickButton.py @@ -1,4 +1,5 @@ -from ..Qt import QtGui, QtCore +from math import hypot +from ..Qt import QtGui, QtCore, mkQApp __all__ = ['JoystickButton'] @@ -41,7 +42,7 @@ class JoystickButton(QtGui.QPushButton): def setState(self, *xy): xy = list(xy) - d = (xy[0]**2 + xy[1]**2)**0.5 + d = hypot(xy[0], xy[1]) # length nxy = [0, 0] for i in [0,1]: if xy[i] == 0: @@ -84,7 +85,7 @@ class JoystickButton(QtGui.QPushButton): if __name__ == '__main__': - app = pg.mkQApp() + app = mkQApp() w = QtGui.QMainWindow() b = JoystickButton() w.setCentralWidget(b) From f60fa3b5346e7e989b89d89fd4d8729b4b38c057 Mon Sep 17 00:00:00 2001 From: Ogi Moore Date: Sun, 18 Apr 2021 10:16:09 -0700 Subject: [PATCH 03/14] Use clip_array instead of np.clip in GLVolumeItem --- examples/GLVolumeItem.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/examples/GLVolumeItem.py b/examples/GLVolumeItem.py index ae2e90ff..42350844 100644 --- a/examples/GLVolumeItem.py +++ b/examples/GLVolumeItem.py @@ -11,6 +11,7 @@ import numpy as np import pyqtgraph as pg from pyqtgraph.Qt import QtCore, QtGui import pyqtgraph.opengl as gl +from pyqtgraph.functions import clip_array app = pg.mkQApp("GLVolumeItem Example") w = gl.GLViewWidget() @@ -39,8 +40,8 @@ def psi(i, j, k, offset=(50,50,100)): data = np.fromfunction(psi, (100,100,200)) -positive = np.log(np.clip(data, 0, data.max())**2) -negative = np.log(np.clip(-data, 0, -data.min())**2) +positive = np.log(clip_array(data, 0, data.max())**2) +negative = np.log(clip_array(-data, 0, -data.min())**2) d2 = np.empty(data.shape + (4,), dtype=np.ubyte) d2[..., 0] = positive * (255./positive.max()) From b01e0e08958e6c2be723febabaa284d351973e6b Mon Sep 17 00:00:00 2001 From: Ogi Moore Date: Sun, 18 Apr 2021 22:48:20 -0700 Subject: [PATCH 04/14] Remove unused function and change line calc Using @pijyoi's suggestion regarding line calculation broadcasting --- examples/GLLinePlotItem.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/examples/GLLinePlotItem.py b/examples/GLLinePlotItem.py index 9be9c07c..82e1f6fb 100644 --- a/examples/GLLinePlotItem.py +++ b/examples/GLLinePlotItem.py @@ -29,19 +29,16 @@ gz = gl.GLGridItem() gz.translate(0, 0, -10) w.addItem(gz) -def fn(x, y): - return np.cos(np.hypot(x, y)) - n = 51 y = np.linspace(-10,10,n) x = np.linspace(-10,10,100) for i in range(n): - yi = np.array([y[i]]*100) + yi = y[i] d = np.hypot(x, yi) z = 10 * np.cos(d) / (d+1) - pts = np.vstack([x,yi,z]).transpose() + pts = np.column_stack([x, np.full_like(x, yi), z]) plt = gl.GLLinePlotItem(pos=pts, color=pg.glColor((i,n*1.3)), width=(i+1)/10., antialias=True) w.addItem(plt) - + if __name__ == '__main__': pg.mkQApp().exec_() From 85c726e49a484c014a9c9848bb4b8540015322f7 Mon Sep 17 00:00:00 2001 From: Ogi Moore Date: Sun, 18 Apr 2021 23:16:45 -0700 Subject: [PATCH 05/14] Replace uses of np.log on scalers with math.log --- pyqtgraph/functions.py | 2 +- pyqtgraph/graphicsItems/AxisItem.py | 15 ++++++++------- pyqtgraph/graphicsItems/GridItem.py | 8 +------- pyqtgraph/imageview/ImageView.py | 3 ++- pyqtgraph/parametertree/SystemSolver.py | 3 ++- 5 files changed, 14 insertions(+), 17 deletions(-) diff --git a/pyqtgraph/functions.py b/pyqtgraph/functions.py index 6b9916c1..2a4ef281 100644 --- a/pyqtgraph/functions.py +++ b/pyqtgraph/functions.py @@ -71,7 +71,7 @@ def siScale(x, minVal=1e-25, allowUnicode=True): m = 0 x = 0 else: - m = int(np.clip(np.floor(np.log(abs(x))/np.log(1000)), -9.0, 9.0)) + m = int(clip_scalar(math.floor(math.log(abs(x))/math.log(1000)), -9.0, 9.0)) if m == 0: pref = '' diff --git a/pyqtgraph/graphicsItems/AxisItem.py b/pyqtgraph/graphicsItems/AxisItem.py index 37490d9d..d907558a 100644 --- a/pyqtgraph/graphicsItems/AxisItem.py +++ b/pyqtgraph/graphicsItems/AxisItem.py @@ -4,6 +4,7 @@ from ..python2_3 import asUnicode import numpy as np from ..Point import Point from .. import debug as debug +from math import ceil, floor, log, log10 import sys import weakref from .. import functions as fn @@ -680,13 +681,13 @@ class AxisItem(GraphicsWidget): return [] ## decide optimal minor tick spacing in pixels (this is just aesthetics) - optimalTickCount = max(2., np.log(size)) + optimalTickCount = max(2., log(size)) ## optimal minor tick spacing optimalSpacing = dif / optimalTickCount ## the largest power-of-10 spacing which is smaller than optimal - p10unit = 10 ** np.floor(np.log10(optimalSpacing)) + p10unit = 10 ** floor(log10(optimalSpacing)) ## Determine major/minor tick spacings which flank the optimal spacing. intervals = np.array([1., 2., 10., 20., 100.]) * p10unit @@ -758,7 +759,7 @@ class AxisItem(GraphicsWidget): spacing, offset = tickLevels[i] ## determine starting tick - start = (np.ceil((minVal-offset) / spacing) * spacing) + offset + start = (ceil((minVal-offset) / spacing) * spacing) + offset ## determine number of ticks num = int((maxVal-start) / spacing) + 1 @@ -795,8 +796,8 @@ class AxisItem(GraphicsWidget): ticks.append((spacing, t)) if len(ticks) < 3: - v1 = int(np.floor(minVal)) - v2 = int(np.ceil(maxVal)) + v1 = int(floor(minVal)) + v2 = int(ceil(maxVal)) #major = list(range(v1+1, v2)) minor = [] @@ -822,7 +823,7 @@ class AxisItem(GraphicsWidget): if self.logMode: return self.logTickStrings(values, scale, spacing) - places = max(0, np.ceil(-np.log10(spacing*scale))) + places = max(0, ceil(-log10(spacing*scale))) strings = [] for v in values: vs = v * scale @@ -969,7 +970,7 @@ class AxisItem(GraphicsWidget): if lineAlpha is None: lineAlpha = 255 / (i+1) if self.grid is not False: - lineAlpha *= self.grid/255. * np.clip((0.05 * lengthInPixels / (len(ticks)+1)), 0., 1.) + lineAlpha *= self.grid/255. * fn.clip_scalar((0.05 * lengthInPixels / (len(ticks)+1)), 0., 1.) elif isinstance(lineAlpha, float): lineAlpha *= 255 lineAlpha = max(0, int(round(lineAlpha))) diff --git a/pyqtgraph/graphicsItems/GridItem.py b/pyqtgraph/graphicsItems/GridItem.py index 4cb30bf8..9004ac94 100644 --- a/pyqtgraph/graphicsItems/GridItem.py +++ b/pyqtgraph/graphicsItems/GridItem.py @@ -126,8 +126,7 @@ class GridItem(UIGraphicsItem): for i in range(self.grid_depth - 1, -1, -1): dist = br-ul nlTarget = 10.**i - - d = 10. ** np.floor(np.log10(abs(dist/nlTarget))+0.5) + d = 10. ** np.floor(np.log10(np.abs(dist/nlTarget))+0.5) for ax in range(0,2): ts = self.opts['tickSpacing'][ax] try: @@ -141,11 +140,6 @@ class GridItem(UIGraphicsItem): br1 = np.ceil(br / d) * d dist = br1-ul1 nl = (dist / d) + 0.5 - #print "level", i - #print " dim", dim - #print " dist", dist - #print " d", d - #print " nl", nl for ax in range(0,2): ## Draw grid for both axes if i >= len(self.opts['tickSpacing'][ax]): continue diff --git a/pyqtgraph/imageview/ImageView.py b/pyqtgraph/imageview/ImageView.py index 62c030a4..3e0cb39b 100644 --- a/pyqtgraph/imageview/ImageView.py +++ b/pyqtgraph/imageview/ImageView.py @@ -13,6 +13,7 @@ Widget used for displaying 2D or 3D data. Features: - Image normalization through a variety of methods """ import os, sys +from math import log10 import numpy as np from ..Qt import QtCore, QtGui, QT_LIB @@ -807,7 +808,7 @@ class ImageView(QtGui.QWidget): img = self.getProcessedImage() if self.hasTimeAxis(): base, ext = os.path.splitext(fileName) - fmt = "%%s%%0%dd%%s" % int(np.log10(img.shape[0])+1) + fmt = "%%s%%0%dd%%s" % int(log10(img.shape[0])+1) for i in range(img.shape[0]): self.imageItem.setImage(img[i], autoLevels=False) self.imageItem.save(fmt % (base, i, ext)) diff --git a/pyqtgraph/parametertree/SystemSolver.py b/pyqtgraph/parametertree/SystemSolver.py index cac42483..71f051e6 100644 --- a/pyqtgraph/parametertree/SystemSolver.py +++ b/pyqtgraph/parametertree/SystemSolver.py @@ -1,6 +1,7 @@ from collections import OrderedDict import numpy as np import copy +from math import log2 class SystemSolver(object): @@ -409,7 +410,7 @@ if __name__ == '__main__': fl = self.flash bal = (4.0 / ap) * (sh / (1./60.)) * (iso / 100.) * (2 ** light) - return np.log2(bal) + return log2(bal) camera = Camera() From c4a1cf11a166201f3da1b99705d7cb8acea0d602 Mon Sep 17 00:00:00 2001 From: Ogi Moore Date: Mon, 19 Apr 2021 22:12:05 -0700 Subject: [PATCH 06/14] Use clip_array or clip_scalar instead of np.clip Significant performance issues have been identified with np.clip and thus clip_array was created to speed up the operation. In addition clip_scalar was created to clip a scalar value between two other values this commit replaces many uses of np.clip from operating on scalars to using clip_scalar instead --- examples/GLScatterPlotItem.py | 9 +++++---- examples/GLVolumeItem.py | 6 +++--- pyqtgraph/Point.py | 9 ++------- pyqtgraph/Vector.py | 5 +++-- pyqtgraph/canvas/CanvasItem.py | 3 ++- pyqtgraph/graphicsItems/CurvePoint.py | 2 +- pyqtgraph/graphicsItems/GridItem.py | 2 +- pyqtgraph/graphicsItems/InfiniteLine.py | 2 +- pyqtgraph/graphicsItems/PlotItem/PlotItem.py | 2 +- pyqtgraph/graphicsItems/ScatterPlotItem.py | 2 +- pyqtgraph/graphicsItems/ViewBox/ViewBox.py | 2 +- pyqtgraph/imageview/ImageView.py | 5 +++-- pyqtgraph/opengl/GLViewWidget.py | 2 +- pyqtgraph/opengl/items/GLScatterPlotItem.py | 8 ++++---- pyqtgraph/parametertree/SystemSolver.py | 3 ++- pyqtgraph/widgets/ColorMapWidget.py | 4 ++-- pyqtgraph/widgets/GraphicsView.py | 2 +- 17 files changed, 34 insertions(+), 34 deletions(-) diff --git a/examples/GLScatterPlotItem.py b/examples/GLScatterPlotItem.py index c279eadf..1d8d5e06 100644 --- a/examples/GLScatterPlotItem.py +++ b/examples/GLScatterPlotItem.py @@ -8,6 +8,7 @@ Demonstrates use of GLScatterPlotItem with rapidly-updating plots. import initExample import pyqtgraph as pg +from pyqtgraph import functions as fn from pyqtgraph.Qt import QtCore, QtGui import pyqtgraph.opengl as gl import numpy as np @@ -84,10 +85,10 @@ def update(): global phase, sp2, d2 s = -np.cos(d2*2+phase) color = np.empty((len(d2),4), dtype=np.float32) - color[:,3] = np.clip(s * 0.1, 0, 1) - color[:,0] = np.clip(s * 3.0, 0, 1) - color[:,1] = np.clip(s * 1.0, 0, 1) - color[:,2] = np.clip(s ** 3, 0, 1) + color[:,3] = fn.clip_array(s * 0.1, 0., 1.) + color[:,0] = fn.clip_array(s * 3.0, 0., 1.) + color[:,1] = fn.clip_array(s * 1.0, 0., 1.) + color[:,2] = fn.clip_array(s ** 3, 0., 1.) sp2.setData(color=color) phase -= 0.1 diff --git a/examples/GLVolumeItem.py b/examples/GLVolumeItem.py index 42350844..99856a00 100644 --- a/examples/GLVolumeItem.py +++ b/examples/GLVolumeItem.py @@ -11,7 +11,7 @@ import numpy as np import pyqtgraph as pg from pyqtgraph.Qt import QtCore, QtGui import pyqtgraph.opengl as gl -from pyqtgraph.functions import clip_array +from pyqtgraph import functions as fn app = pg.mkQApp("GLVolumeItem Example") w = gl.GLViewWidget() @@ -40,8 +40,8 @@ def psi(i, j, k, offset=(50,50,100)): data = np.fromfunction(psi, (100,100,200)) -positive = np.log(clip_array(data, 0, data.max())**2) -negative = np.log(clip_array(-data, 0, -data.min())**2) +positive = np.log(fn.clip_array(data, np.finfo(data.dtype).eps, data.max())**2) +negative = np.log(fn.clip_array(-data, -np.finfo(data.dtype).eps, -data.min())**2) d2 = np.empty(data.shape + (4,), dtype=np.ubyte) d2[..., 0] = positive * (255./positive.max()) diff --git a/pyqtgraph/Point.py b/pyqtgraph/Point.py index 78a3a681..4ad2c7f9 100644 --- a/pyqtgraph/Point.py +++ b/pyqtgraph/Point.py @@ -6,14 +6,9 @@ Distributed under MIT/X11 license. See license.txt for more information. """ from .Qt import QtCore +from . import functions as fn from math import sin, acos, atan2, inf, pi, hypot -def clip(x, mn, mx): - if x > mx: - return mx - if x < mn: - return mn - return x class Point(QtCore.QPointF): """Extension of QPointF which adds a few missing methods.""" @@ -112,7 +107,7 @@ class Point(QtCore.QPointF): if n1 == 0. or n2 == 0.: return None ## Probably this should be done with arctan2 instead.. - ang = acos(clip(self.dot(a) / (n1 * n2), -1.0, 1.0)) ### in radians + ang = acos(fn.clip_scalar(self.dot(a) / (n1 * n2), -1.0, 1.0)) ### in radians c = self.cross(a) if c > 0: ang *= -1. diff --git a/pyqtgraph/Vector.py b/pyqtgraph/Vector.py index a9d28e41..c10a1f91 100644 --- a/pyqtgraph/Vector.py +++ b/pyqtgraph/Vector.py @@ -4,8 +4,9 @@ Vector.py - Extension of QVector3D which adds a few missing methods. Copyright 2010 Luke Campagnola Distributed under MIT/X11 license. See license.txt for more information. """ - +from math import acos from .Qt import QtGui, QtCore, QT_LIB +from . import functions as fn import numpy as np class Vector(QtGui.QVector3D): @@ -88,7 +89,7 @@ class Vector(QtGui.QVector3D): if n1 == 0. or n2 == 0.: return None ## Probably this should be done with arctan2 instead.. - ang = np.arccos(np.clip(QtGui.QVector3D.dotProduct(self, a) / (n1 * n2), -1.0, 1.0)) ### in radians + ang = acos(fn.clip_scalar(QtGui.QVector3D.dotProduct(self, a) / (n1 * n2), -1.0, 1.0)) ### in radians # c = self.crossProduct(a) # if c > 0: # ang *= -1. diff --git a/pyqtgraph/canvas/CanvasItem.py b/pyqtgraph/canvas/CanvasItem.py index 88612055..0cd86d0e 100644 --- a/pyqtgraph/canvas/CanvasItem.py +++ b/pyqtgraph/canvas/CanvasItem.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- import numpy as np from ..Qt import QtGui, QtCore, QtSvg, QT_LIB +from .. import functions as fn from ..graphicsItems.ROI import ROI from .. import SRTTransform, ItemGroup import importlib @@ -240,7 +241,7 @@ class CanvasItem(QtCore.QObject): self._graphicsItem.setOpacity(alpha) def setAlpha(self, alpha): - self.alphaSlider.setValue(int(np.clip(alpha * 1023, 0, 1023))) + self.alphaSlider.setValue(int(fn.clip_scalar(alpha * 1023, 0, 1023))) def alpha(self): return self.alphaSlider.value() / 1023. diff --git a/pyqtgraph/graphicsItems/CurvePoint.py b/pyqtgraph/graphicsItems/CurvePoint.py index 9e4d3718..077c6839 100644 --- a/pyqtgraph/graphicsItems/CurvePoint.py +++ b/pyqtgraph/graphicsItems/CurvePoint.py @@ -63,7 +63,7 @@ class CurvePoint(GraphicsObject): pos = self.property('position') if 'QVariant' in repr(pos): ## need to support 2 APIs :( pos = pos.toDouble()[0] - index = (len(x)-1) * np.clip(pos, 0.0, 1.0) + index = (len(x)-1) * clip_scalar(pos, 0.0, 1.0) if index != int(index): ## interpolate floating-point values i1 = int(index) diff --git a/pyqtgraph/graphicsItems/GridItem.py b/pyqtgraph/graphicsItems/GridItem.py index 9004ac94..312d7b62 100644 --- a/pyqtgraph/graphicsItems/GridItem.py +++ b/pyqtgraph/graphicsItems/GridItem.py @@ -147,7 +147,7 @@ class GridItem(UIGraphicsItem): continue ppl = dim[ax] / nl[ax] - c = np.clip(5 * (ppl-3), 0., 50.).astype(int) + c = int(fn.clip_scalar(5 * (ppl-3), 0, 50)) linePen = self.opts['pen'] lineColor = self.opts['pen'].color() diff --git a/pyqtgraph/graphicsItems/InfiniteLine.py b/pyqtgraph/graphicsItems/InfiniteLine.py index ce96d00b..54b2d759 100644 --- a/pyqtgraph/graphicsItems/InfiniteLine.py +++ b/pyqtgraph/graphicsItems/InfiniteLine.py @@ -593,7 +593,7 @@ class InfLineLabel(TextItem): return rel = self._posToRel(ev.pos()) - self.orthoPos = np.clip(self._startPosition + rel - self._cursorOffset, 0, 1) + self.orthoPos = fn.clip_scalar(self._startPosition + rel - self._cursorOffset, 0., 1.) self.updatePosition() if ev.isFinish(): self._moving = False diff --git a/pyqtgraph/graphicsItems/PlotItem/PlotItem.py b/pyqtgraph/graphicsItems/PlotItem/PlotItem.py index a2affcf7..8da9ba39 100644 --- a/pyqtgraph/graphicsItems/PlotItem/PlotItem.py +++ b/pyqtgraph/graphicsItems/PlotItem/PlotItem.py @@ -372,7 +372,7 @@ class PlotItem(GraphicsWidget): if y is not None: self.ctrl.yGridCheck.setChecked(y) if alpha is not None: - v = np.clip(alpha, 0, 1)*self.ctrl.gridAlphaSlider.maximum() + v = fn.clip_scalar(alpha, 0., 1.)*self.ctrl.gridAlphaSlider.maximum() self.ctrl.gridAlphaSlider.setValue(v) def close(self): diff --git a/pyqtgraph/graphicsItems/ScatterPlotItem.py b/pyqtgraph/graphicsItems/ScatterPlotItem.py index 3d35f8fc..c32686dd 100644 --- a/pyqtgraph/graphicsItems/ScatterPlotItem.py +++ b/pyqtgraph/graphicsItems/ScatterPlotItem.py @@ -1073,7 +1073,7 @@ class ScatterPlotItem(GraphicsObject): # Map points using painter's world transform so they are drawn with pixel-valued sizes pts = np.vstack([self.data['x'], self.data['y']]) pts = fn.transformCoordinates(p.transform(), pts) - pts = np.clip(pts, -2 ** 30, 2 ** 30) # prevent Qt segmentation fault. + pts = fn.clip_array(pts, -2 ** 30, 2 ** 30) # prevent Qt segmentation fault. p.resetTransform() if self.opts['useCache'] and self._exportOpts is False: diff --git a/pyqtgraph/graphicsItems/ViewBox/ViewBox.py b/pyqtgraph/graphicsItems/ViewBox/ViewBox.py index 6725d78b..bdfa1bbd 100644 --- a/pyqtgraph/graphicsItems/ViewBox/ViewBox.py +++ b/pyqtgraph/graphicsItems/ViewBox/ViewBox.py @@ -666,7 +666,7 @@ class ViewBox(GraphicsWidget): def suggestPadding(self, axis): l = self.width() if axis==0 else self.height() if l > 0: - padding = np.clip(1./(l**0.5), 0.02, 0.1) + padding = fn.clip_scalar(1./(l**0.5), 0.02, 0.1) else: padding = 0.02 return padding diff --git a/pyqtgraph/imageview/ImageView.py b/pyqtgraph/imageview/ImageView.py index 3e0cb39b..c7f0b66d 100644 --- a/pyqtgraph/imageview/ImageView.py +++ b/pyqtgraph/imageview/ImageView.py @@ -17,10 +17,11 @@ from math import log10 import numpy as np from ..Qt import QtCore, QtGui, QT_LIB +from .. import functions as fn import importlib ui_template = importlib.import_module( f'.ImageViewTemplate_{QT_LIB.lower()}', package=__package__) - + from ..graphicsItems.ImageItem import * from ..graphicsItems.ROI import * from ..graphicsItems.LinearRegionItem import * @@ -513,7 +514,7 @@ class ImageView(QtGui.QWidget): def setCurrentIndex(self, ind): """Set the currently displayed frame index.""" - index = np.clip(ind, 0, self.getProcessedImage().shape[self.axes['t']]-1) + index = fn.clip_scalar(ind, 0, self.getProcessedImage().shape[self.axes['t']]-1) self.ignorePlaying = True # Implicitly call timeLineChanged self.timeLine.setValue(self.tVals[index]) diff --git a/pyqtgraph/opengl/GLViewWidget.py b/pyqtgraph/opengl/GLViewWidget.py index 6dcbf8b1..66daa134 100644 --- a/pyqtgraph/opengl/GLViewWidget.py +++ b/pyqtgraph/opengl/GLViewWidget.py @@ -344,7 +344,7 @@ class GLViewWidget(QtWidgets.QOpenGLWidget): self.opts['rotation'] = q else: # default euler rotation method self.opts['azimuth'] += azim - self.opts['elevation'] = np.clip(self.opts['elevation'] + elev, -90, 90) + self.opts['elevation'] = fn.clip_scalar(self.opts['elevation'] + elev, -90., 90.) self.update() def pan(self, dx, dy, dz, relative='global'): diff --git a/pyqtgraph/opengl/items/GLScatterPlotItem.py b/pyqtgraph/opengl/items/GLScatterPlotItem.py index 09e7ec2f..834ede0b 100644 --- a/pyqtgraph/opengl/items/GLScatterPlotItem.py +++ b/pyqtgraph/opengl/items/GLScatterPlotItem.py @@ -3,7 +3,7 @@ from OpenGL.GL import * from OpenGL.arrays import vbo from .. GLGraphicsItem import GLGraphicsItem from .. import shaders -from ...functions import clip_array +from ... import functions as fn from ...Qt import QtGui import numpy as np @@ -62,12 +62,12 @@ class GLScatterPlotItem(GLGraphicsItem): ## Generate texture for rendering points w = 64 - def fn(x,y): + def genTexture(x,y): r = np.hypot((x-(w-1)/2.), (y-(w-1)/2.)) - return 255 * (w/2. - clip_array(r, w/2.-1.0, w/2.)) + return 255 * (w / 2 - fn.clip_array(r, w / 2 - 1, w / 2)) pData = np.empty((w, w, 4)) pData[:] = 255 - pData[:,:,3] = np.fromfunction(fn, pData.shape[:2]) + pData[:,:,3] = np.fromfunction(genTexture, pData.shape[:2]) pData = pData.astype(np.ubyte) if getattr(self, "pointTexture", None) is None: diff --git a/pyqtgraph/parametertree/SystemSolver.py b/pyqtgraph/parametertree/SystemSolver.py index 71f051e6..de177a37 100644 --- a/pyqtgraph/parametertree/SystemSolver.py +++ b/pyqtgraph/parametertree/SystemSolver.py @@ -2,6 +2,7 @@ from collections import OrderedDict import numpy as np import copy from math import log2 +from .. import functions as fn class SystemSolver(object): @@ -391,7 +392,7 @@ if __name__ == '__main__': sh = self.shutter # this raises RuntimeError if shutter has not # been specified ap = 4.0 * (sh / (1./60.)) * (iso / 100.) * (2 ** exp) * (2 ** light) - ap = np.clip(ap, 2.0, 16.0) + ap = fn.clip_scalar(ap, 2.0, 16.0) except RuntimeError: # program mode; we can select a suitable shutter # value at the same time. diff --git a/pyqtgraph/widgets/ColorMapWidget.py b/pyqtgraph/widgets/ColorMapWidget.py index 5d1e5681..b37fb67c 100644 --- a/pyqtgraph/widgets/ColorMapWidget.py +++ b/pyqtgraph/widgets/ColorMapWidget.py @@ -161,7 +161,7 @@ class ColorMapParameter(ptree.types.GroupParameter): elif op == 'Set': colors[mask] = colors2[mask] - colors = np.clip(colors, 0, 1) + colors = fn.clip_array(colors, 0., 1.) if mode == 'byte': colors = (colors * 255).astype(np.ubyte) @@ -210,7 +210,7 @@ class RangeColorMapItem(ptree.types.SimpleParameter): def map(self, data): data = data[self.fieldName] - scaled = np.clip((data-self['Min']) / (self['Max']-self['Min']), 0, 1) + scaled = fn.clip_array((data-self['Min']) / (self['Max']-self['Min']), 0, 1) cmap = self.value() colors = cmap.map(scaled, mode='float') diff --git a/pyqtgraph/widgets/GraphicsView.py b/pyqtgraph/widgets/GraphicsView.py index 3054f0e7..56304f51 100644 --- a/pyqtgraph/widgets/GraphicsView.py +++ b/pyqtgraph/widgets/GraphicsView.py @@ -378,7 +378,7 @@ class GraphicsView(QtGui.QGraphicsView): return if ev.buttons() == QtCore.Qt.RightButton: - delta = Point(np.clip(delta[0], -50, 50), np.clip(-delta[1], -50, 50)) + delta = Point(fn.clip_scalar(delta[0], -50, 50), fn.clip_scalar(-delta[1], -50, 50)) scale = 1.01 ** delta self.scale(scale[0], scale[1], center=self.mapToScene(self.mousePressPos)) self.sigDeviceRangeChanged.emit(self, self.range) From 1138c67d45197f3075efbd2a03571f7dba48e1ce Mon Sep 17 00:00:00 2001 From: Ogi Moore Date: Mon, 19 Apr 2021 22:18:59 -0700 Subject: [PATCH 07/14] Use math module trig functions for scalars This commit replaces the use of np.sin/np.cos/np.tan uses throughout the library with math.sin/math.cos/math.tan for scalar values --- pyqtgraph/Point.py | 19 ++++--------------- pyqtgraph/functions.py | 6 +++--- pyqtgraph/opengl/GLViewWidget.py | 21 +++++++++++---------- 3 files changed, 18 insertions(+), 28 deletions(-) diff --git a/pyqtgraph/Point.py b/pyqtgraph/Point.py index 4ad2c7f9..bdd8bc33 100644 --- a/pyqtgraph/Point.py +++ b/pyqtgraph/Point.py @@ -6,8 +6,7 @@ Distributed under MIT/X11 license. See license.txt for more information. """ from .Qt import QtCore -from . import functions as fn -from math import sin, acos, atan2, inf, pi, hypot +from math import atan2, hypot, degrees class Point(QtCore.QPointF): @@ -95,23 +94,14 @@ class Point(QtCore.QPointF): """Returns the vector length of this Point.""" return hypot(self[0], self[1]) # length - def norm(self): """Returns a vector in the same direction with unit length.""" return self / self.length() def angle(self, a): """Returns the angle in degrees between this vector and the vector a.""" - n1 = self.length() - n2 = a.length() - if n1 == 0. or n2 == 0.: - return None - ## Probably this should be done with arctan2 instead.. - ang = acos(fn.clip_scalar(self.dot(a) / (n1 * n2), -1.0, 1.0)) ### in radians - c = self.cross(a) - if c > 0: - ang *= -1. - return ang * 180. / pi + rads = atan2(self.y(), self.x()) - atan2(a.y(), a.x()) + return degrees(rads) def dot(self, a): """Returns the dot product of a and this Point.""" @@ -129,8 +119,7 @@ class Point(QtCore.QPointF): def __repr__(self): return "Point(%f, %f)" % (self[0], self[1]) - - + def min(self): return min(self[0], self[1]) diff --git a/pyqtgraph/functions.py b/pyqtgraph/functions.py index 2a4ef281..6fd64244 100644 --- a/pyqtgraph/functions.py +++ b/pyqtgraph/functions.py @@ -432,16 +432,16 @@ def makeArrowPath(headLen=20, headWidth=None, tipAngle=20, tailLen=20, tailWidth If *tailLen* is None, no tail will be drawn. """ if headWidth is None: - headWidth = headLen * np.tan(tipAngle * 0.5 * np.pi/180.) + headWidth = headLen * math.tan(tipAngle * 0.5 * math.pi / 180.) path = QtGui.QPainterPath() path.moveTo(0,0) path.lineTo(headLen, -headWidth) if tailLen is None: - innerY = headLen - headWidth * np.tan(baseAngle*np.pi/180.) + innerY = headLen - headWidth * math.tan(baseAngle * math.pi / 180.) path.lineTo(innerY, 0) else: tailWidth *= 0.5 - innerY = headLen - (headWidth-tailWidth) * np.tan(baseAngle*np.pi/180.) + innerY = headLen - (headWidth-tailWidth) * math.tan(baseAngle* math.pi / 180.) path.lineTo(innerY, -tailWidth) path.lineTo(headLen + tailLen, -tailWidth) path.lineTo(headLen + tailLen, tailWidth) diff --git a/pyqtgraph/opengl/GLViewWidget.py b/pyqtgraph/opengl/GLViewWidget.py index 66daa134..be78113e 100644 --- a/pyqtgraph/opengl/GLViewWidget.py +++ b/pyqtgraph/opengl/GLViewWidget.py @@ -5,6 +5,7 @@ import numpy as np from .. import Vector from .. import functions as fn import warnings +from math import cos, sin, tan ##Vector = QtGui.QVector3D ShareWidget = None @@ -183,7 +184,7 @@ class GLViewWidget(QtWidgets.QOpenGLWidget): nearClip = dist * 0.001 farClip = dist * 1000. - r = nearClip * np.tan(fov * 0.5 * np.pi / 180.) + r = nearClip * tan(fov * 0.5 * np.pi / 180.) t = r * h / w ## Note that X0 and width in these equations must be the values used in viewport @@ -328,9 +329,9 @@ class GLViewWidget(QtWidgets.QOpenGLWidget): elev = self.opts['elevation'] * np.pi / 180 azim = self.opts['azimuth'] * np.pi / 180 pos = Vector( - center.x() + dist * np.cos(elev) * np.cos(azim), - center.y() + dist * np.cos(elev) * np.sin(azim), - center.z() + dist * np.sin(elev) + center.x() + dist * cos(elev) * cos(azim), + center.y() + dist * cos(elev) * sin(azim), + center.z() + dist * sin(elev) ) return pos @@ -387,7 +388,7 @@ class GLViewWidget(QtWidgets.QOpenGLWidget): cPos = self.cameraPosition() cVec = self.opts['center'] - cPos dist = cVec.length() ## distance from camera to center - xDist = dist * 2. * np.tan(0.5 * self.opts['fov'] * np.pi / 180.) ## approx. width of view at distance of center point + xDist = dist * 2. * tan(0.5 * self.opts['fov'] * np.pi / 180.) ## approx. width of view at distance of center point xScale = xDist / self.width() zVec = QtGui.QVector3D(0,0,1) xVec = QtGui.QVector3D.crossProduct(zVec, cVec).normalized() @@ -412,11 +413,11 @@ class GLViewWidget(QtWidgets.QOpenGLWidget): azim = np.radians(self.opts['azimuth']) fov = np.radians(self.opts['fov']) dist = (self.opts['center'] - self.cameraPosition()).length() - fov_factor = np.tan(fov / 2) * 2 + fov_factor = tan(fov / 2) * 2 scale_factor = dist * fov_factor / self.width() - z = scale_factor * np.cos(elev) * dy - x = scale_factor * (np.sin(azim) * dx - np.sin(elev) * np.cos(azim) * dy) - y = scale_factor * (np.cos(azim) * dx + np.sin(elev) * np.sin(azim) * dy) + z = scale_factor * cos(elev) * dy + x = scale_factor * (sin(azim) * dx - sin(elev) * cos(azim) * dy) + y = scale_factor * (cos(azim) * dx + sin(elev) * sin(azim) * dy) self.opts['center'] += QtGui.QVector3D(x, -y, z) else: raise ValueError("relative argument must be global, view, or view-upright") @@ -434,7 +435,7 @@ class GLViewWidget(QtWidgets.QOpenGLWidget): dist = ((pos-cam)**2).sum(axis=-1)**0.5 else: dist = (pos-cam).length() - xDist = dist * 2. * np.tan(0.5 * self.opts['fov'] * np.pi / 180.) + xDist = dist * 2. * tan(0.5 * self.opts['fov'] * np.pi / 180.) return xDist / self.width() def mousePressEvent(self, ev): From b0769f4be95883818de5a2ec7dcd96404bec4d5a Mon Sep 17 00:00:00 2001 From: Ogi Moore Date: Mon, 19 Apr 2021 22:43:44 -0700 Subject: [PATCH 08/14] Use math.radians and math.degrees Many places in the library were doing radian to degree conversion via the manual calculation. Doing timeit benchmarks on my system, I am able to get a 4x speedup by using math.degrees or math.radians instead --- pyqtgraph/Point.py | 1 - pyqtgraph/SRTTransform.py | 4 ++-- pyqtgraph/SRTTransform3D.py | 4 ++-- pyqtgraph/Vector.py | 6 +++--- pyqtgraph/functions.py | 6 +++--- pyqtgraph/graphicsItems/CurvePoint.py | 6 +++--- pyqtgraph/graphicsItems/InfiniteLine.py | 4 ++-- pyqtgraph/graphicsItems/ROI.py | 9 +++------ pyqtgraph/graphicsItems/TargetItem.py | 4 ++-- pyqtgraph/graphicsItems/TextItem.py | 4 ++-- pyqtgraph/opengl/GLViewWidget.py | 18 +++++++++--------- 11 files changed, 31 insertions(+), 35 deletions(-) diff --git a/pyqtgraph/Point.py b/pyqtgraph/Point.py index bdd8bc33..6f56a035 100644 --- a/pyqtgraph/Point.py +++ b/pyqtgraph/Point.py @@ -8,7 +8,6 @@ Distributed under MIT/X11 license. See license.txt for more information. from .Qt import QtCore from math import atan2, hypot, degrees - class Point(QtCore.QPointF): """Extension of QPointF which adds a few missing methods.""" diff --git a/pyqtgraph/SRTTransform.py b/pyqtgraph/SRTTransform.py index 81bfb24d..a340f8b6 100644 --- a/pyqtgraph/SRTTransform.py +++ b/pyqtgraph/SRTTransform.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from math import atan2, pi +from math import atan2, degrees from .Qt import QtCore, QtGui from .Point import Point import numpy as np @@ -77,7 +77,7 @@ class SRTTransform(QtGui.QTransform): self._state = { 'pos': Point(p1), 'scale': Point(dp2.length(), dp3.length() * sy), - 'angle': (atan2(dp2[1], dp2[0]) * 180. / pi) + da + 'angle': degrees(atan2(dp2[1], dp2[0])) + da } self.update() diff --git a/pyqtgraph/SRTTransform3D.py b/pyqtgraph/SRTTransform3D.py index 438a410c..2fd3ff87 100644 --- a/pyqtgraph/SRTTransform3D.py +++ b/pyqtgraph/SRTTransform3D.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from math import atan2, pi +from math import atan2, degrees from .Qt import QtCore, QtGui from .Vector import Vector from .Transform3D import Transform3D @@ -165,7 +165,7 @@ class SRTTransform3D(Transform3D): sin = (r-r.T)[rInd] / (2. * sign * axis[axisInd]) ## finally, we get the complete angle from arctan(sin/cos) - self._state['angle'] = atan2(sin, cos) * 180 / pi + self._state['angle'] = degrees(atan2(sin, cos)) if self._state['angle'] == 0: self._state['axis'] = (0,0,1) diff --git a/pyqtgraph/Vector.py b/pyqtgraph/Vector.py index c10a1f91..2c838d64 100644 --- a/pyqtgraph/Vector.py +++ b/pyqtgraph/Vector.py @@ -4,7 +4,7 @@ Vector.py - Extension of QVector3D which adds a few missing methods. Copyright 2010 Luke Campagnola Distributed under MIT/X11 license. See license.txt for more information. """ -from math import acos +from math import acos, degrees from .Qt import QtGui, QtCore, QT_LIB from . import functions as fn import numpy as np @@ -89,11 +89,11 @@ class Vector(QtGui.QVector3D): if n1 == 0. or n2 == 0.: return None ## Probably this should be done with arctan2 instead.. - ang = acos(fn.clip_scalar(QtGui.QVector3D.dotProduct(self, a) / (n1 * n2), -1.0, 1.0)) ### in radians + rads = acos(fn.clip_scalar(QtGui.QVector3D.dotProduct(self, a) / (n1 * n2), -1.0, 1.0)) ### in radians # c = self.crossProduct(a) # if c > 0: # ang *= -1. - return ang * 180. / np.pi + return degrees(rads) def __abs__(self): return Vector(abs(self.x()), abs(self.y()), abs(self.z())) diff --git a/pyqtgraph/functions.py b/pyqtgraph/functions.py index 6fd64244..b50f84bc 100644 --- a/pyqtgraph/functions.py +++ b/pyqtgraph/functions.py @@ -432,16 +432,16 @@ def makeArrowPath(headLen=20, headWidth=None, tipAngle=20, tailLen=20, tailWidth If *tailLen* is None, no tail will be drawn. """ if headWidth is None: - headWidth = headLen * math.tan(tipAngle * 0.5 * math.pi / 180.) + headWidth = headLen * math.tan(math.radians(tipAngle * 0.5)) path = QtGui.QPainterPath() path.moveTo(0,0) path.lineTo(headLen, -headWidth) if tailLen is None: - innerY = headLen - headWidth * math.tan(baseAngle * math.pi / 180.) + innerY = headLen - headWidth * math.tan(math.radians(baseAngle)) path.lineTo(innerY, 0) else: tailWidth *= 0.5 - innerY = headLen - (headWidth-tailWidth) * math.tan(baseAngle* math.pi / 180.) + innerY = headLen - (headWidth-tailWidth) * math.tan(math.radians(baseAngle)) path.lineTo(innerY, -tailWidth) path.lineTo(headLen + tailLen, -tailWidth) path.lineTo(headLen + tailLen, tailWidth) diff --git a/pyqtgraph/graphicsItems/CurvePoint.py b/pyqtgraph/graphicsItems/CurvePoint.py index 077c6839..66117165 100644 --- a/pyqtgraph/graphicsItems/CurvePoint.py +++ b/pyqtgraph/graphicsItems/CurvePoint.py @@ -1,4 +1,4 @@ -from math import atan2, pi +from math import atan2, degrees from ..Qt import QtGui, QtCore from . import ArrowItem from ..functions import clip_scalar @@ -79,10 +79,10 @@ class CurvePoint(GraphicsObject): p1 = self.parentItem().mapToScene(QtCore.QPointF(x[i1], y[i1])) p2 = self.parentItem().mapToScene(QtCore.QPointF(x[i2], y[i2])) - ang = atan2(p2.y()-p1.y(), p2.x()-p1.x()) ## returns radians + rads = atan2(p2.y()-p1.y(), p2.x()-p1.x()) ## returns radians self.resetTransform() if self._rotate: - self.setRotation(180 + ang * (180. / pi)) + self.setRotation(180 + degrees(rads)) QtGui.QGraphicsItem.setPos(self, *newPos) return True diff --git a/pyqtgraph/graphicsItems/InfiniteLine.py b/pyqtgraph/graphicsItems/InfiniteLine.py index 54b2d759..c64a4684 100644 --- a/pyqtgraph/graphicsItems/InfiniteLine.py +++ b/pyqtgraph/graphicsItems/InfiniteLine.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from math import atan2, pi +from math import atan2, degrees from ..Qt import QtGui, QtCore from ..Point import Point from .GraphicsObject import GraphicsObject @@ -360,7 +360,7 @@ class InfiniteLine(GraphicsObject): up = tr.map(Point(left, 1)) dif = end - start length = Point(dif).length() - angle = atan2(dif.y(), dif.x()) * 180 / pi + angle = degrees(atan2(dif.y(), dif.x())) p.translate(start) p.rotate(angle) diff --git a/pyqtgraph/graphicsItems/ROI.py b/pyqtgraph/graphicsItems/ROI.py index d7037582..c5926050 100644 --- a/pyqtgraph/graphicsItems/ROI.py +++ b/pyqtgraph/graphicsItems/ROI.py @@ -17,7 +17,7 @@ import numpy as np #from numpy.linalg import norm from ..Point import * from ..SRTTransform import SRTTransform -from math import atan2, cos, sin, pi, sqrt, hypot +from math import atan2, cos, sin, pi, sqrt, hypot, radians, degrees from .. import functions as fn from .GraphicsObject import GraphicsObject from .UIGraphicsItem import UIGraphicsItem @@ -1524,7 +1524,7 @@ class Handle(UIGraphicsItem): devPos = dt.map(QtCore.QPointF(0,0)) tr = QtGui.QTransform() tr.translate(devPos.x(), devPos.y()) - tr.rotate(va * 180. / pi) + tr.rotate(degrees(va)) return dti.map(tr.map(self.path)) @@ -1663,7 +1663,7 @@ class LineROI(ROI): d = pos2-pos1 l = d.length() ang = Point(1, 0).angle(d) - ra = ang * pi / 180. + ra = radians(ang if ang is not None else 0.) c = Point(-width/2. * sin(ra), -width/2. * cos(ra)) pos1 = pos1 + c @@ -1671,9 +1671,6 @@ class LineROI(ROI): 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 MultiRectROI(QtGui.QGraphicsObject): diff --git a/pyqtgraph/graphicsItems/TargetItem.py b/pyqtgraph/graphicsItems/TargetItem.py index 26f40b9b..67f5920d 100644 --- a/pyqtgraph/graphicsItems/TargetItem.py +++ b/pyqtgraph/graphicsItems/TargetItem.py @@ -1,4 +1,4 @@ -from math import atan2, pi +from math import atan2, pi, degrees from ..Qt import QtGui, QtCore from ..Point import Point @@ -243,7 +243,7 @@ class TargetItem(UIGraphicsItem): tr = QtGui.QTransform() tr.translate(devPos.x(), devPos.y()) va = atan2(v.y(), v.x()) - tr.rotate(va * 180.0 / pi) + tr.rotate(degrees(va)) tr.scale(self.scale, self.scale) return dti.map(tr.map(self._path)) diff --git a/pyqtgraph/graphicsItems/TextItem.py b/pyqtgraph/graphicsItems/TextItem.py index 6b2dfdd5..2af87d5d 100644 --- a/pyqtgraph/graphicsItems/TextItem.py +++ b/pyqtgraph/graphicsItems/TextItem.py @@ -1,4 +1,4 @@ -from math import pi, atan2 +from math import atan2, degrees from ..Qt import QtCore, QtGui from ..Point import Point from .. import functions as fn @@ -208,7 +208,7 @@ class TextItem(GraphicsObject): angle = -self.angle if self.rotateAxis is not None: d = pt.map(self.rotateAxis) - pt.map(Point(0, 0)) - a = atan2(d.y(), d.x()) * 180 / pi + a = degrees(atan2(d.y(), d.x())) angle += a t.rotate(angle) self.setTransform(t) diff --git a/pyqtgraph/opengl/GLViewWidget.py b/pyqtgraph/opengl/GLViewWidget.py index be78113e..0f380ec9 100644 --- a/pyqtgraph/opengl/GLViewWidget.py +++ b/pyqtgraph/opengl/GLViewWidget.py @@ -5,7 +5,7 @@ import numpy as np from .. import Vector from .. import functions as fn import warnings -from math import cos, sin, tan +from math import cos, sin, tan, degrees, radians ##Vector = QtGui.QVector3D ShareWidget = None @@ -184,7 +184,7 @@ class GLViewWidget(QtWidgets.QOpenGLWidget): nearClip = dist * 0.001 farClip = dist * 1000. - r = nearClip * tan(fov * 0.5 * np.pi / 180.) + r = nearClip * tan(0.5 * radians(fov)) t = r * h / w ## Note that X0 and width in these equations must be the values used in viewport @@ -326,8 +326,8 @@ class GLViewWidget(QtWidgets.QOpenGLWidget): pos = center - self.opts['rotation'].rotatedVector( Vector(0,0,dist) ) else: # using 'euler' rotation method - elev = self.opts['elevation'] * np.pi / 180 - azim = self.opts['azimuth'] * np.pi / 180 + elev = radians(self.opts['elevation']) + azim = radians(self.opts['azimuth']) pos = Vector( center.x() + dist * cos(elev) * cos(azim), center.y() + dist * cos(elev) * sin(azim), @@ -388,7 +388,7 @@ class GLViewWidget(QtWidgets.QOpenGLWidget): cPos = self.cameraPosition() cVec = self.opts['center'] - cPos dist = cVec.length() ## distance from camera to center - xDist = dist * 2. * tan(0.5 * self.opts['fov'] * np.pi / 180.) ## approx. width of view at distance of center point + xDist = dist * 2. * tan(0.5 * radians(self.opts['fov'])) ## approx. width of view at distance of center point xScale = xDist / self.width() zVec = QtGui.QVector3D(0,0,1) xVec = QtGui.QVector3D.crossProduct(zVec, cVec).normalized() @@ -409,9 +409,9 @@ class GLViewWidget(QtWidgets.QOpenGLWidget): # apply translation self.opts['center'] += scale_factor * (xv*-dx + yv*dy + zv*dz) else: # use default euler rotation method - elev = np.radians(self.opts['elevation']) - azim = np.radians(self.opts['azimuth']) - fov = np.radians(self.opts['fov']) + elev = radians(self.opts['elevation']) + azim = radians(self.opts['azimuth']) + fov = radians(self.opts['fov']) dist = (self.opts['center'] - self.cameraPosition()).length() fov_factor = tan(fov / 2) * 2 scale_factor = dist * fov_factor / self.width() @@ -435,7 +435,7 @@ class GLViewWidget(QtWidgets.QOpenGLWidget): dist = ((pos-cam)**2).sum(axis=-1)**0.5 else: dist = (pos-cam).length() - xDist = dist * 2. * tan(0.5 * self.opts['fov'] * np.pi / 180.) + xDist = dist * 2. * tan(0.5 * radians(self.opts['fov'])) return xDist / self.width() def mousePressEvent(self, ev): From 314121192ad8661f2db0be0221bfeacfbb50a736 Mon Sep 17 00:00:00 2001 From: Ogi Moore Date: Tue, 20 Apr 2021 21:42:01 -0700 Subject: [PATCH 09/14] Use math module for isfinite or isnan for scalars Various places in the library attempt to check if scalars are finite via numpy methods, which are intended to be used on numpy arrays. Using the math module equivalent functions on scalars is significantly faster. In a few places, I also use numpy methods explicitly (np.all vs. all) --- pyqtgraph/functions.py | 12 ++++------- pyqtgraph/graphicsItems/AxisItem.py | 6 +++--- pyqtgraph/graphicsItems/NonUniformImage.py | 3 ++- pyqtgraph/graphicsItems/PlotCurveItem.py | 6 ++++-- pyqtgraph/graphicsItems/PlotDataItem.py | 7 ++++--- pyqtgraph/graphicsItems/ROI.py | 2 +- pyqtgraph/graphicsItems/ScatterPlotItem.py | 5 ++++- pyqtgraph/graphicsItems/ViewBox/ViewBox.py | 23 ++++++++++++++++------ pyqtgraph/widgets/ColorMapWidget.py | 2 +- 9 files changed, 40 insertions(+), 26 deletions(-) diff --git a/pyqtgraph/functions.py b/pyqtgraph/functions.py index b50f84bc..d629faf9 100644 --- a/pyqtgraph/functions.py +++ b/pyqtgraph/functions.py @@ -60,12 +60,10 @@ def siScale(x, minVal=1e-25, allowUnicode=True): if isinstance(x, decimal.Decimal): x = float(x) - try: - if np.isnan(x) or np.isinf(x): + if not math.isfinite(x): return(1, '') except: - print(x, type(x)) raise if abs(x) < minVal: m = 0 @@ -295,9 +293,7 @@ def mkColor(*args): else: raise TypeError(err) - args = [r,g,b,a] - args = [0 if np.isnan(a) or np.isinf(a) else a for a in args] - args = list(map(int, args)) + args = [int(a) if math.isfinite(a) else 0 for a in (r, g, b, a)] return QtGui.QColor(*args) @@ -458,7 +454,7 @@ def eq(a, b): 1. Returns True if a IS b, even if a==b still evaluates to False. 2. While a is b will catch the case with np.nan values, special handling is done for distinct - float('nan') instances using np.isnan. + float('nan') instances using math.isnan. 3. Tests for equivalence using ==, but silently ignores some common exceptions that can occur (AtrtibuteError, ValueError). 4. When comparing arrays, returns False if the array shapes are not the same. @@ -472,7 +468,7 @@ def eq(a, b): # The above catches np.nan, but not float('nan') if isinstance(a, float) and isinstance(b, float): - if np.isnan(a) and np.isnan(b): + if math.isnan(a) and math.isnan(b): return True # Avoid comparing large arrays against scalars; this is expensive and we know it should return False. diff --git a/pyqtgraph/graphicsItems/AxisItem.py b/pyqtgraph/graphicsItems/AxisItem.py index d907558a..28459923 100644 --- a/pyqtgraph/graphicsItems/AxisItem.py +++ b/pyqtgraph/graphicsItems/AxisItem.py @@ -4,7 +4,7 @@ from ..python2_3 import asUnicode import numpy as np from ..Point import Point from .. import debug as debug -from math import ceil, floor, log, log10 +from math import ceil, floor, log, log10, isfinite import sys import weakref from .. import functions as fn @@ -512,7 +512,7 @@ class AxisItem(GraphicsWidget): def setRange(self, mn, mx): """Set the range of values displayed by the axis. Usually this is handled automatically by linking the axis to a ViewBox with :func:`linkToView `""" - if any(np.isinf((mn, mx))) or any(np.isnan((mn, mx))): + if not isfinite(mn) or not isfinite(mx): raise Exception("Not setting range to [%s, %s]" % (str(mn), str(mx))) self.range = [mn, mx] if self.autoSIPrefix: @@ -767,7 +767,7 @@ class AxisItem(GraphicsWidget): ## remove any ticks that were present in higher levels ## we assume here that if the difference between a tick value and a previously seen tick value ## is less than spacing/100, then they are 'equal' and we can ignore the new tick. - values = list(filter(lambda x: all(np.abs(allValues-x) > spacing/self.scale*0.01), values)) + values = list(filter(lambda x: np.all(np.abs(allValues-x) > spacing/self.scale*0.01), values)) allValues = np.concatenate([allValues, values]) ticks.append((spacing/self.scale, values)) diff --git a/pyqtgraph/graphicsItems/NonUniformImage.py b/pyqtgraph/graphicsItems/NonUniformImage.py index 2b697767..fc42bf27 100644 --- a/pyqtgraph/graphicsItems/NonUniformImage.py +++ b/pyqtgraph/graphicsItems/NonUniformImage.py @@ -1,4 +1,5 @@ from ..Qt import QtGui, QtCore +import math import numpy as np from ..colormap import ColorMap from .GraphicsObject import GraphicsObject @@ -94,7 +95,7 @@ class NonUniformImage(GraphicsObject): value = 0.0 elif np.isposinf(value): value = 1.0 - elif np.isnan(value): + elif math.isnan(value): continue # ignore NaN else: value = (value - mn) / (mx - mn) # normalize diff --git a/pyqtgraph/graphicsItems/PlotCurveItem.py b/pyqtgraph/graphicsItems/PlotCurveItem.py index 39003813..06f9bdee 100644 --- a/pyqtgraph/graphicsItems/PlotCurveItem.py +++ b/pyqtgraph/graphicsItems/PlotCurveItem.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- from ..Qt import QtCore, QtGui, QtWidgets HAVE_OPENGL = hasattr(QtWidgets, 'QOpenGLWidget') - +import math import warnings import numpy as np from .GraphicsObject import GraphicsObject @@ -131,6 +131,8 @@ class PlotCurveItem(GraphicsObject): elif ax == 1: d = y d2 = x + else: + raise ValueError("Invalid axis value") ## If an orthogonal range is specified, mask the data now if orthoRange is not None: @@ -149,7 +151,7 @@ class PlotCurveItem(GraphicsObject): # All-NaN data is acceptable; Explicit numpy warning is not needed. warnings.simplefilter("ignore") b = (np.nanmin(d), np.nanmax(d)) - if any(np.isinf(b)): + if math.isinf(b[0]) or math.isinf(b[1]): mask = np.isfinite(d) d = d[mask] if len(d) == 0: diff --git a/pyqtgraph/graphicsItems/PlotDataItem.py b/pyqtgraph/graphicsItems/PlotDataItem.py index 2c07798b..49d8ef61 100644 --- a/pyqtgraph/graphicsItems/PlotDataItem.py +++ b/pyqtgraph/graphicsItems/PlotDataItem.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- import warnings +import math import numpy as np from .. import metaarray as metaarray from ..Qt import QtCore @@ -660,7 +661,7 @@ class PlotDataItem(GraphicsObject): eps = np.finfo(y.dtype).eps else: eps = 1 - y = np.sign(y) * np.log10(np.abs(y)+eps) + y = np.copysign(np.log10(np.abs(y)+eps), y) ds = self.opts['downsample'] if not isinstance(ds, int): @@ -799,10 +800,10 @@ class PlotDataItem(GraphicsObject): # All-NaN data is handled by returning None; Explicit numpy warning is not needed. warnings.simplefilter("ignore") ymin = np.nanmin(self.yData) - if np.isnan( ymin ): + if math.isnan( ymin ): return None # most likely case for all-NaN data xmin = np.nanmin(self.xData) - if np.isnan( xmin ): + if math.isnan( xmin ): return None # less likely case for all-NaN data ymax = np.nanmax(self.yData) xmax = np.nanmax(self.xData) diff --git a/pyqtgraph/graphicsItems/ROI.py b/pyqtgraph/graphicsItems/ROI.py index c5926050..fa63295d 100644 --- a/pyqtgraph/graphicsItems/ROI.py +++ b/pyqtgraph/graphicsItems/ROI.py @@ -1425,7 +1425,7 @@ class Handle(UIGraphicsItem): menu = self.scene().addParentContextMenus(self, self.getMenu(), ev) ## Make sure it is still ok to remove this handle - removeAllowed = all([r.checkRemoveHandle(self) for r in self.rois]) + removeAllowed = all(r.checkRemoveHandle(self) for r in self.rois) self.removeAction.setEnabled(removeAllowed) pos = ev.screenPos() menu.popup(QtCore.QPoint(pos.x(), pos.y())) diff --git a/pyqtgraph/graphicsItems/ScatterPlotItem.py b/pyqtgraph/graphicsItems/ScatterPlotItem.py index c32686dd..080a8b24 100644 --- a/pyqtgraph/graphicsItems/ScatterPlotItem.py +++ b/pyqtgraph/graphicsItems/ScatterPlotItem.py @@ -6,6 +6,7 @@ try: except ImportError: imap = map import itertools +import math import numpy as np import weakref from ..Qt import QtGui, QtCore, QT_LIB @@ -116,7 +117,7 @@ def renderSymbol(symbol, size, pen, brush, device=None): for more information). """ ## Render a spot with the given parameters to a pixmap - penPxWidth = max(np.ceil(pen.widthF()), 1) + penPxWidth = max(math.ceil(pen.widthF()), 1) if device is None: device = QtGui.QImage(int(size+penPxWidth), int(size+penPxWidth), QtGui.QImage.Format_ARGB32) device.fill(0) @@ -950,6 +951,8 @@ class ScatterPlotItem(GraphicsObject): elif ax == 1: d = self.data['y'] d2 = self.data['x'] + else: + raise ValueError("Invalid axis value") if orthoRange is not None: mask = (d2 >= orthoRange[0]) * (d2 <= orthoRange[1]) diff --git a/pyqtgraph/graphicsItems/ViewBox/ViewBox.py b/pyqtgraph/graphicsItems/ViewBox/ViewBox.py index bdfa1bbd..ac771c1c 100644 --- a/pyqtgraph/graphicsItems/ViewBox/ViewBox.py +++ b/pyqtgraph/graphicsItems/ViewBox/ViewBox.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- import weakref import sys +import math from copy import deepcopy import numpy as np from ...Qt import QtGui, QtCore @@ -552,7 +553,7 @@ class ViewBox(GraphicsWidget): xpad = 0.0 # Make sure no nan/inf get through - if not all(np.isfinite([mn, mx])): + if not math.isfinite(mn) or not math.isfinite(mx): raise Exception("Cannot set range [%s, %s]" % (str(mn), str(mx))) # Apply padding @@ -903,11 +904,11 @@ class ViewBox(GraphicsWidget): targetRect[ax] = childRange[ax] args['xRange' if ax == 0 else 'yRange'] = targetRect[ax] - # check for and ignore bad ranges + # check for and ignore bad ranges for k in ['xRange', 'yRange']: if k in args: - if not np.all(np.isfinite(args[k])): - r = args.pop(k) + if not math.isfinite(args[k][0]) or not math.isfinite(args[k][1]): + _ = args.pop(k) #print("Warning: %s is invalid: %s" % (k, str(r)) if len(args) == 0: @@ -1369,10 +1370,20 @@ class ViewBox(GraphicsWidget): xr = item.dataBounds(0, frac=frac[0], orthoRange=orthoRange[0]) yr = item.dataBounds(1, frac=frac[1], orthoRange=orthoRange[1]) pxPad = 0 if not hasattr(item, 'pixelPadding') else item.pixelPadding() - if xr is None or (xr[0] is None and xr[1] is None) or np.isnan(xr).any() or np.isinf(xr).any(): + if ( + xr is None or + (xr[0] is None and xr[1] is None) or + not math.isfinite(xr[0]) or + not math.isfinite(xr[1]) + ): useX = False xr = (0,0) - if yr is None or (yr[0] is None and yr[1] is None) or np.isnan(yr).any() or np.isinf(yr).any(): + if ( + yr is None or + (yr[0] is None and yr[1] is None) or + not math.isfinite(yr[0]) or + not math.isfinite(yr[1]) + ): useY = False yr = (0,0) diff --git a/pyqtgraph/widgets/ColorMapWidget.py b/pyqtgraph/widgets/ColorMapWidget.py index b37fb67c..9f2fafe8 100644 --- a/pyqtgraph/widgets/ColorMapWidget.py +++ b/pyqtgraph/widgets/ColorMapWidget.py @@ -214,7 +214,7 @@ class RangeColorMapItem(ptree.types.SimpleParameter): cmap = self.value() colors = cmap.map(scaled, mode='float') - mask = np.isnan(data) | np.isinf(data) + mask = np.invert(np.isfinite(data)) nanColor = self['NaN'] nanColor = (nanColor.red()/255., nanColor.green()/255., nanColor.blue()/255., nanColor.alpha()/255.) colors[mask] = nanColor From 3ea92736b8c7465e68598da1b735f6fe0389f804 Mon Sep 17 00:00:00 2001 From: Ogi Moore Date: Tue, 20 Apr 2021 22:25:23 -0700 Subject: [PATCH 10/14] Do not use list comprehensions with any or all python any and all are able to break early the moment they come across a member of the iterable that meets the condition; but having a list comprehension nested within breaks that ability to exit early, as the list comprehension has to finish being constructed first before it can be evaluated --- pyqtgraph/debug.py | 4 ++-- pyqtgraph/dockarea/Container.py | 2 +- pyqtgraph/imageview/ImageView.py | 2 +- pyqtgraph/metaarray/MetaArray.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pyqtgraph/debug.py b/pyqtgraph/debug.py index 15d1fb3c..37b612cd 100644 --- a/pyqtgraph/debug.py +++ b/pyqtgraph/debug.py @@ -202,7 +202,7 @@ def findRefPath(startObj, endObj, maxLen=8, restart=True, seen={}, path=None, ig #print prefix+" FRAME" continue try: - if any([r is x for x in path]): + if any(r is x for x in path): #print prefix+" LOOP", objChainString([r]+path) continue except: @@ -282,7 +282,7 @@ def refPathString(chain): o2 = chain[i] cont = False if isinstance(o1, list) or isinstance(o1, tuple): - if any([o2 is x for x in o1]): + if any(o2 is x for x in o1): s += "[%d]" % o1.index(o2) continue #print " not list" diff --git a/pyqtgraph/dockarea/Container.py b/pyqtgraph/dockarea/Container.py index 04b775f9..34f16288 100644 --- a/pyqtgraph/dockarea/Container.py +++ b/pyqtgraph/dockarea/Container.py @@ -126,7 +126,7 @@ class SplitContainer(Container, QtGui.QSplitter): def saveState(self): sizes = self.sizes() - if all([x == 0 for x in sizes]): + if all(x == 0 for x in sizes): sizes = [10] * len(sizes) return {'sizes': sizes} diff --git a/pyqtgraph/imageview/ImageView.py b/pyqtgraph/imageview/ImageView.py index c7f0b66d..6f60170c 100644 --- a/pyqtgraph/imageview/ImageView.py +++ b/pyqtgraph/imageview/ImageView.py @@ -274,7 +274,7 @@ class ImageView(QtGui.QWidget): if not isinstance(img, np.ndarray): required = ['dtype', 'max', 'min', 'ndim', 'shape', 'size'] - if not all([hasattr(img, attr) for attr in required]): + if not all(hasattr(img, attr) for attr in required): raise TypeError("Image must be NumPy array or any object " "that provides compatible attributes/methods:\n" " %s" % str(required)) diff --git a/pyqtgraph/metaarray/MetaArray.py b/pyqtgraph/metaarray/MetaArray.py index 169ff43c..b07f66ab 100644 --- a/pyqtgraph/metaarray/MetaArray.py +++ b/pyqtgraph/metaarray/MetaArray.py @@ -124,7 +124,7 @@ class MetaArray(object): nameTypes = [basestring, tuple] @staticmethod def isNameType(var): - return any([isinstance(var, t) for t in MetaArray.nameTypes]) + return any(isinstance(var, t) for t in MetaArray.nameTypes) ## methods to wrap from embedded ndarray / HDF5 From 6a386e723bc0d666595678387d7cd98894c2b0e4 Mon Sep 17 00:00:00 2001 From: Ogi Moore Date: Wed, 21 Apr 2021 00:28:19 -0700 Subject: [PATCH 11/14] Fix overflow warning in QColor by using np.isfinite --- pyqtgraph/functions.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/pyqtgraph/functions.py b/pyqtgraph/functions.py index d629faf9..abbd77d1 100644 --- a/pyqtgraph/functions.py +++ b/pyqtgraph/functions.py @@ -266,6 +266,8 @@ def mkColor(*args): g = int(c[2:4], 16) b = int(c[4:6], 16) a = int(c[6:8], 16) + else: + raise ValueError(f"Unknown how to convert string {c} to color") elif isinstance(args[0], QtGui.QColor): return QtGui.QColor(args[0]) elif np.issubdtype(type(args[0]), np.floating): @@ -273,10 +275,10 @@ def mkColor(*args): a = 255 elif hasattr(args[0], '__len__'): if len(args[0]) == 3: - (r, g, b) = args[0] + r, g, b = args[0] a = 255 elif len(args[0]) == 4: - (r, g, b, a) = args[0] + r, g, b, a = args[0] elif len(args[0]) == 2: return intColor(*args[0]) else: @@ -286,14 +288,13 @@ def mkColor(*args): else: raise TypeError(err) elif len(args) == 3: - (r, g, b) = args + r, g, b = args a = 255 elif len(args) == 4: - (r, g, b, a) = args + r, g, b, a = args else: raise TypeError(err) - - args = [int(a) if math.isfinite(a) else 0 for a in (r, g, b, a)] + args = [int(a) if np.isfinite(a) else 0 for a in (r, g, b, a)] return QtGui.QColor(*args) From e1415cb3a85bec883b6952c859fea8e30de00178 Mon Sep 17 00:00:00 2001 From: Ogi Moore Date: Fri, 23 Apr 2021 10:55:37 -0700 Subject: [PATCH 12/14] Use np.pi or math.pi instead of just pi --- examples/optics/pyoptic.py | 8 ++++---- pyqtgraph/graphicsItems/ROI.py | 12 +++++++----- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/examples/optics/pyoptic.py b/examples/optics/pyoptic.py index 442a4f5d..99221bd7 100644 --- a/examples/optics/pyoptic.py +++ b/examples/optics/pyoptic.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -from math import atan2, asin, sin, cos, sqrt, pi, hypot +from math import atan2, asin, sin, cos, degrees, sqrt, hypot import pyqtgraph as pg from pyqtgraph.Qt import QtGui, QtCore import numpy as np @@ -285,7 +285,7 @@ class Mirror(Optic): p1 = surface.mapToItem(ray, p1) rd = ray['dir'] a1 = atan2(rd[1], rd[0]) - ar = a1 + pi - 2*ai + ar = a1 + np.pi - 2 * ai ray.setEnd(p1) dp = Point(cos(ar), sin(ar)) ray = Ray(parent=ray, dir=dp) @@ -379,7 +379,7 @@ class CircleSurface(pg.GraphicsObject): ## half-height of surface can't be larger than radius h2 = min(h2, abs(r)) arc = QtCore.QRectF(0, -r, r*2, r*2) - a1 = asin(h2/r) * 180. / pi + a1 = degrees(asin(h2/r)) a2 = -2*a1 a1 += 180. self.path.arcMoveTo(arc, a1) @@ -443,7 +443,7 @@ class CircleSurface(pg.GraphicsObject): norm = atan2(pt[1], pt[0]) if r < 0: - norm += pi + norm += np.pi dp = p - pt ang = atan2(dp[1], dp[0]) return pt + Point(r, 0), ang-norm diff --git a/pyqtgraph/graphicsItems/ROI.py b/pyqtgraph/graphicsItems/ROI.py index fa63295d..7121947d 100644 --- a/pyqtgraph/graphicsItems/ROI.py +++ b/pyqtgraph/graphicsItems/ROI.py @@ -17,7 +17,7 @@ import numpy as np #from numpy.linalg import norm from ..Point import * from ..SRTTransform import SRTTransform -from math import atan2, cos, sin, pi, sqrt, hypot, radians, degrees +from math import atan2, cos, sin, hypot, radians, degrees from .. import functions as fn from .GraphicsObject import GraphicsObject from .UIGraphicsItem import UIGraphicsItem @@ -1332,8 +1332,8 @@ class Handle(UIGraphicsItem): properties of the ROI they are attached to. """ types = { ## defines number of sides, start angle for each handle type - 't': (4, pi/4), - 'f': (4, pi/4), + 't': (4, np.pi/4), + 'f': (4, np.pi/4), 's': (4, 0), 'r': (12, 0), 'sr': (12, 0), @@ -1481,7 +1481,7 @@ class Handle(UIGraphicsItem): size = self.radius self.path = QtGui.QPainterPath() ang = self.startAng - dt = 2 * pi / self.sides + dt = 2 * np.pi / self.sides for i in range(0, self.sides+1): x = size * cos(ang) y = size * sin(ang) @@ -1911,7 +1911,7 @@ class EllipseROI(ROI): center = br.center() r1 = br.width() / 2. r2 = br.height() / 2. - theta = np.linspace(0, 2 * pi, 24) + theta = np.linspace(0, 2 * np.pi, 24) x = center.x() + r1 * np.cos(theta) y = center.y() + r2 * np.sin(theta) path.moveTo(x[0], y[0]) @@ -2394,6 +2394,8 @@ class TriangleROI(ROI): ROI.__init__(self, pos, [size, size], **args) self.aspectLocked = True angles = np.linspace(0, pi * 4 / 3, 3) + ROI.__init__(self, pos, [size, size], aspectLocked=True, **args) + angles = np.linspace(0, np.pi * 4 / 3, 3) verticies = (np.array((np.sin(angles), np.cos(angles))).T + 1.0) / 2.0 self.poly = QtGui.QPolygonF() for pt in verticies: From 329423f525808be59cba4dbb4ed12272e5e5d54b Mon Sep 17 00:00:00 2001 From: Ogi Moore Date: Fri, 23 Apr 2021 11:22:46 -0700 Subject: [PATCH 13/14] Use QTransform.rotateRadians when appropriate In a few places in the library, we are doing the conversion from radians to degrees just so we can call QTransform.rotate(), but there is a QTransform.rotateRadians() method which would be more suitable, thus making it so we do not have to handle the conversions ourselves --- pyqtgraph/graphicsItems/ROI.py | 2 +- pyqtgraph/graphicsItems/TargetItem.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyqtgraph/graphicsItems/ROI.py b/pyqtgraph/graphicsItems/ROI.py index 7121947d..08ff0480 100644 --- a/pyqtgraph/graphicsItems/ROI.py +++ b/pyqtgraph/graphicsItems/ROI.py @@ -1524,7 +1524,7 @@ class Handle(UIGraphicsItem): devPos = dt.map(QtCore.QPointF(0,0)) tr = QtGui.QTransform() tr.translate(devPos.x(), devPos.y()) - tr.rotate(degrees(va)) + tr.rotateRadians(va) return dti.map(tr.map(self.path)) diff --git a/pyqtgraph/graphicsItems/TargetItem.py b/pyqtgraph/graphicsItems/TargetItem.py index 67f5920d..40fcdfc0 100644 --- a/pyqtgraph/graphicsItems/TargetItem.py +++ b/pyqtgraph/graphicsItems/TargetItem.py @@ -243,7 +243,7 @@ class TargetItem(UIGraphicsItem): tr = QtGui.QTransform() tr.translate(devPos.x(), devPos.y()) va = atan2(v.y(), v.x()) - tr.rotate(degrees(va)) + tr.rotateRadians(va) tr.scale(self.scale, self.scale) return dti.map(tr.map(self._path)) From 03ea36845443451407b1381e6d9c36be5a706375 Mon Sep 17 00:00:00 2001 From: Ogi Moore Date: Tue, 20 Apr 2021 23:40:03 -0700 Subject: [PATCH 14/14] Clean up Errors for CodeQL analyzer CodeQL identified some errors and warnings, which this commit cleans up. --- examples/GLIsosurface.py | 2 -- examples/GLVolumeItem.py | 3 --- pyqtgraph/Point.py | 1 + pyqtgraph/Vector.py | 1 - pyqtgraph/canvas/CanvasItem.py | 3 +-- pyqtgraph/functions.py | 3 --- pyqtgraph/graphicsItems/CurvePoint.py | 1 - pyqtgraph/graphicsItems/ROI.py | 28 ++++++++++------------ pyqtgraph/graphicsItems/TargetItem.py | 3 +-- pyqtgraph/graphicsItems/ViewBox/ViewBox.py | 1 - pyqtgraph/imageview/ImageView.py | 2 +- pyqtgraph/opengl/GLViewWidget.py | 2 +- pyqtgraph/parametertree/SystemSolver.py | 5 ---- pyqtgraph/widgets/GraphicsView.py | 3 --- 14 files changed, 17 insertions(+), 41 deletions(-) diff --git a/examples/GLIsosurface.py b/examples/GLIsosurface.py index 0f773885..1afe797c 100644 --- a/examples/GLIsosurface.py +++ b/examples/GLIsosurface.py @@ -8,7 +8,6 @@ This example uses the isosurface function to convert a scalar field import initExample import numpy as np -from pyqtgraph.Qt import QtCore, QtGui import pyqtgraph as pg import pyqtgraph.opengl as gl @@ -29,7 +28,6 @@ def psi(i, j, k, offset=(25, 25, 50)): y = j-offset[1] z = k-offset[2] th = np.arctan2(z, np.hypot(x, y)) - phi = np.arctan2(y, x) r = np.sqrt(x**2 + y**2 + z **2) a0 = 1 ps = (1./81.) * 1./(6.*np.pi)**0.5 * (1./a0)**(3/2) * (r/a0)**2 * np.exp(-r/(3*a0)) * (3 * np.cos(th)**2 - 1) diff --git a/examples/GLVolumeItem.py b/examples/GLVolumeItem.py index 99856a00..ba67514b 100644 --- a/examples/GLVolumeItem.py +++ b/examples/GLVolumeItem.py @@ -9,7 +9,6 @@ import initExample import numpy as np import pyqtgraph as pg -from pyqtgraph.Qt import QtCore, QtGui import pyqtgraph.opengl as gl from pyqtgraph import functions as fn @@ -31,10 +30,8 @@ def psi(i, j, k, offset=(50,50,100)): y = j-offset[1] z = k-offset[2] th = np.arctan2(z, np.hypot(x, y)) - phi = np.arctan2(y, x) r = np.sqrt(x**2 + y**2 + z **2) a0 = 2 - #ps = (1./81.) * (2./np.pi)**0.5 * (1./a0)**(3/2) * (6 - r/a0) * (r/a0) * np.exp(-r/(3*a0)) * np.cos(th) ps = (1./81.) * 1./(6.*np.pi)**0.5 * (1./a0)**(3/2) * (r/a0)**2 * np.exp(-r/(3*a0)) * (3 * np.cos(th)**2 - 1) return ps diff --git a/pyqtgraph/Point.py b/pyqtgraph/Point.py index 6f56a035..bdd8bc33 100644 --- a/pyqtgraph/Point.py +++ b/pyqtgraph/Point.py @@ -8,6 +8,7 @@ Distributed under MIT/X11 license. See license.txt for more information. from .Qt import QtCore from math import atan2, hypot, degrees + class Point(QtCore.QPointF): """Extension of QPointF which adds a few missing methods.""" diff --git a/pyqtgraph/Vector.py b/pyqtgraph/Vector.py index 2c838d64..a64e8968 100644 --- a/pyqtgraph/Vector.py +++ b/pyqtgraph/Vector.py @@ -7,7 +7,6 @@ Distributed under MIT/X11 license. See license.txt for more information. from math import acos, degrees from .Qt import QtGui, QtCore, QT_LIB from . import functions as fn -import numpy as np class Vector(QtGui.QVector3D): """Extension of QVector3D which adds a few helpful methods.""" diff --git a/pyqtgraph/canvas/CanvasItem.py b/pyqtgraph/canvas/CanvasItem.py index 0cd86d0e..3883919e 100644 --- a/pyqtgraph/canvas/CanvasItem.py +++ b/pyqtgraph/canvas/CanvasItem.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- -import numpy as np -from ..Qt import QtGui, QtCore, QtSvg, QT_LIB +from ..Qt import QtGui, QtCore, QT_LIB from .. import functions as fn from ..graphicsItems.ROI import ROI from .. import SRTTransform, ItemGroup diff --git a/pyqtgraph/functions.py b/pyqtgraph/functions.py index abbd77d1..45043a4c 100644 --- a/pyqtgraph/functions.py +++ b/pyqtgraph/functions.py @@ -67,10 +67,8 @@ def siScale(x, minVal=1e-25, allowUnicode=True): raise if abs(x) < minVal: m = 0 - x = 0 else: m = int(clip_scalar(math.floor(math.log(abs(x))/math.log(1000)), -9.0, 9.0)) - if m == 0: pref = '' elif m < -8 or m > 8: @@ -82,7 +80,6 @@ def siScale(x, minVal=1e-25, allowUnicode=True): pref = SI_PREFIXES_ASCII[m+8] m1 = -3*m p = 10.**m1 - return (p, pref) diff --git a/pyqtgraph/graphicsItems/CurvePoint.py b/pyqtgraph/graphicsItems/CurvePoint.py index 66117165..c7052ddb 100644 --- a/pyqtgraph/graphicsItems/CurvePoint.py +++ b/pyqtgraph/graphicsItems/CurvePoint.py @@ -2,7 +2,6 @@ from math import atan2, degrees from ..Qt import QtGui, QtCore from . import ArrowItem from ..functions import clip_scalar -import numpy as np from ..Point import Point import weakref from .GraphicsObject import GraphicsObject diff --git a/pyqtgraph/graphicsItems/ROI.py b/pyqtgraph/graphicsItems/ROI.py index 08ff0480..6c5f8418 100644 --- a/pyqtgraph/graphicsItems/ROI.py +++ b/pyqtgraph/graphicsItems/ROI.py @@ -15,9 +15,9 @@ of how to build an ROI at the bottom of the file. from ..Qt import QtCore, QtGui import numpy as np #from numpy.linalg import norm -from ..Point import * +from ..Point import Point from ..SRTTransform import SRTTransform -from math import atan2, cos, sin, hypot, radians, degrees +from math import atan2, cos, sin, hypot, radians from .. import functions as fn from .GraphicsObject import GraphicsObject from .UIGraphicsItem import UIGraphicsItem @@ -141,12 +141,13 @@ class ROI(GraphicsObject): maxBounds=None, snapSize=1.0, scaleSnap=False, translateSnap=False, rotateSnap=False, parent=None, pen=None, hoverPen=None, handlePen=None, handleHoverPen=None, - movable=True, rotatable=True, resizable=True, removable=False): + movable=True, rotatable=True, resizable=True, removable=False, + aspectLocked=False): GraphicsObject.__init__(self, parent) self.setAcceptedMouseButtons(QtCore.Qt.NoButton) pos = Point(pos) size = Point(size) - self.aspectLocked = False + self.aspectLocked = aspectLocked self.translatable = movable self.rotatable = rotatable self.resizable = resizable @@ -678,7 +679,7 @@ class ROI(GraphicsObject): The format returned is a list of (name, pos) tuples. """ - if index == None: + if index is None: positions = [] for h in self.handles: positions.append((h['name'], h['pos'])) @@ -691,7 +692,7 @@ class ROI(GraphicsObject): The format returned is a list of (name, pos) tuples. """ - if index == None: + if index is None: positions = [] for h in self.handles: positions.append((h['name'], h['item'].scenePos())) @@ -1298,7 +1299,7 @@ class ROI(GraphicsObject): """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: + if relativeTo is None: relativeTo = self.preMoveState st = self.getState() @@ -1941,8 +1942,7 @@ class CircleROI(EllipseROI): if radius is None: raise TypeError("Must provide either size or radius.") size = (radius*2, radius*2) - EllipseROI.__init__(self, pos, size, **args) - self.aspectLocked = True + EllipseROI.__init__(self, pos, size, aspectLocked=True, **args) def _addHandles(self): self.addScaleHandle([0.5*2.**-0.5 + 0.5, 0.5*2.**-0.5 + 0.5], [0.5, 0.5]) @@ -2307,16 +2307,15 @@ class CrosshairROI(ROI): """A crosshair ROI whose position is at the center of the crosshairs. By default, it is scalable, rotatable and translatable.""" def __init__(self, pos=None, size=None, **kargs): - if size == None: + if size is None: size=[1,1] - if pos == None: + if pos is None: pos = [0,0] self._shape = None - ROI.__init__(self, pos, size, **kargs) + ROI.__init__(self, pos, size, aspectLocked=True, **kargs) self.sigRegionChanged.connect(self.invalidate) self.addScaleRotateHandle(Point(1, 0), Point(0, 0)) - self.aspectLocked = True def invalidate(self): self._shape = None @@ -2391,9 +2390,6 @@ class TriangleROI(ROI): """ def __init__(self, pos, size, **args): - ROI.__init__(self, pos, [size, size], **args) - self.aspectLocked = True - angles = np.linspace(0, pi * 4 / 3, 3) ROI.__init__(self, pos, [size, size], aspectLocked=True, **args) angles = np.linspace(0, np.pi * 4 / 3, 3) verticies = (np.array((np.sin(angles), np.cos(angles))).T + 1.0) / 2.0 diff --git a/pyqtgraph/graphicsItems/TargetItem.py b/pyqtgraph/graphicsItems/TargetItem.py index 40fcdfc0..1a4d5144 100644 --- a/pyqtgraph/graphicsItems/TargetItem.py +++ b/pyqtgraph/graphicsItems/TargetItem.py @@ -1,5 +1,4 @@ -from math import atan2, pi, degrees - +from math import atan2 from ..Qt import QtGui, QtCore from ..Point import Point from .. import functions as fn diff --git a/pyqtgraph/graphicsItems/ViewBox/ViewBox.py b/pyqtgraph/graphicsItems/ViewBox/ViewBox.py index ac771c1c..8d60c577 100644 --- a/pyqtgraph/graphicsItems/ViewBox/ViewBox.py +++ b/pyqtgraph/graphicsItems/ViewBox/ViewBox.py @@ -550,7 +550,6 @@ class ViewBox(GraphicsWidget): dy = 1 mn -= dy*0.5 mx += dy*0.5 - xpad = 0.0 # Make sure no nan/inf get through if not math.isfinite(mn) or not math.isfinite(mx): diff --git a/pyqtgraph/imageview/ImageView.py b/pyqtgraph/imageview/ImageView.py index 6f60170c..f934b36d 100644 --- a/pyqtgraph/imageview/ImageView.py +++ b/pyqtgraph/imageview/ImageView.py @@ -12,7 +12,7 @@ Widget used for displaying 2D or 3D data. Features: - ROI plotting - Image normalization through a variety of methods """ -import os, sys +import os from math import log10 import numpy as np diff --git a/pyqtgraph/opengl/GLViewWidget.py b/pyqtgraph/opengl/GLViewWidget.py index 0f380ec9..a309d7a0 100644 --- a/pyqtgraph/opengl/GLViewWidget.py +++ b/pyqtgraph/opengl/GLViewWidget.py @@ -5,7 +5,7 @@ import numpy as np from .. import Vector from .. import functions as fn import warnings -from math import cos, sin, tan, degrees, radians +from math import cos, sin, tan, radians ##Vector = QtGui.QVector3D ShareWidget = None diff --git a/pyqtgraph/parametertree/SystemSolver.py b/pyqtgraph/parametertree/SystemSolver.py index de177a37..d7b6bef8 100644 --- a/pyqtgraph/parametertree/SystemSolver.py +++ b/pyqtgraph/parametertree/SystemSolver.py @@ -398,9 +398,6 @@ if __name__ == '__main__': # value at the same time. sh = (1./60.) raise - - - return ap def _balance(self): @@ -408,8 +405,6 @@ if __name__ == '__main__': light = self.lightMeter sh = self.shutter ap = self.aperture - fl = self.flash - bal = (4.0 / ap) * (sh / (1./60.)) * (iso / 100.) * (2 ** light) return log2(bal) diff --git a/pyqtgraph/widgets/GraphicsView.py b/pyqtgraph/widgets/GraphicsView.py index 56304f51..d2b0f553 100644 --- a/pyqtgraph/widgets/GraphicsView.py +++ b/pyqtgraph/widgets/GraphicsView.py @@ -7,10 +7,7 @@ Distributed under MIT/X11 license. See license.txt for more information. from ..Qt import QtCore, QtGui, QtWidgets, QT_LIB from ..Point import Point -import sys, os -from .FileDialog import FileDialog from ..GraphicsScene import GraphicsScene -import numpy as np from .. import functions as fn from .. import debug as debug from .. import getConfigOption