From 314121192ad8661f2db0be0221bfeacfbb50a736 Mon Sep 17 00:00:00 2001 From: Ogi Moore Date: Tue, 20 Apr 2021 21:42:01 -0700 Subject: [PATCH] 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