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)
This commit is contained in:
Ogi Moore 2021-04-20 21:42:01 -07:00
parent b0769f4be9
commit 314121192a
9 changed files with 40 additions and 26 deletions

View File

@ -60,12 +60,10 @@ def siScale(x, minVal=1e-25, allowUnicode=True):
if isinstance(x, decimal.Decimal): if isinstance(x, decimal.Decimal):
x = float(x) x = float(x)
try: try:
if np.isnan(x) or np.isinf(x): if not math.isfinite(x):
return(1, '') return(1, '')
except: except:
print(x, type(x))
raise raise
if abs(x) < minVal: if abs(x) < minVal:
m = 0 m = 0
@ -295,9 +293,7 @@ def mkColor(*args):
else: else:
raise TypeError(err) raise TypeError(err)
args = [r,g,b,a] args = [int(a) if math.isfinite(a) else 0 for a in (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))
return QtGui.QColor(*args) 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. 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 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 3. Tests for equivalence using ==, but silently ignores some common exceptions that can occur
(AtrtibuteError, ValueError). (AtrtibuteError, ValueError).
4. When comparing arrays, returns False if the array shapes are not the same. 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') # The above catches np.nan, but not float('nan')
if isinstance(a, float) and isinstance(b, float): 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 return True
# Avoid comparing large arrays against scalars; this is expensive and we know it should return False. # Avoid comparing large arrays against scalars; this is expensive and we know it should return False.

View File

@ -4,7 +4,7 @@ from ..python2_3 import asUnicode
import numpy as np import numpy as np
from ..Point import Point from ..Point import Point
from .. import debug as debug from .. import debug as debug
from math import ceil, floor, log, log10 from math import ceil, floor, log, log10, isfinite
import sys import sys
import weakref import weakref
from .. import functions as fn from .. import functions as fn
@ -512,7 +512,7 @@ class AxisItem(GraphicsWidget):
def setRange(self, mn, mx): def setRange(self, mn, mx):
"""Set the range of values displayed by the axis. """Set the range of values displayed by the axis.
Usually this is handled automatically by linking the axis to a ViewBox with :func:`linkToView <pyqtgraph.AxisItem.linkToView>`""" Usually this is handled automatically by linking the axis to a ViewBox with :func:`linkToView <pyqtgraph.AxisItem.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))) raise Exception("Not setting range to [%s, %s]" % (str(mn), str(mx)))
self.range = [mn, mx] self.range = [mn, mx]
if self.autoSIPrefix: if self.autoSIPrefix:
@ -767,7 +767,7 @@ class AxisItem(GraphicsWidget):
## remove any ticks that were present in higher levels ## 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 ## 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. ## 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]) allValues = np.concatenate([allValues, values])
ticks.append((spacing/self.scale, values)) ticks.append((spacing/self.scale, values))

View File

@ -1,4 +1,5 @@
from ..Qt import QtGui, QtCore from ..Qt import QtGui, QtCore
import math
import numpy as np import numpy as np
from ..colormap import ColorMap from ..colormap import ColorMap
from .GraphicsObject import GraphicsObject from .GraphicsObject import GraphicsObject
@ -94,7 +95,7 @@ class NonUniformImage(GraphicsObject):
value = 0.0 value = 0.0
elif np.isposinf(value): elif np.isposinf(value):
value = 1.0 value = 1.0
elif np.isnan(value): elif math.isnan(value):
continue # ignore NaN continue # ignore NaN
else: else:
value = (value - mn) / (mx - mn) # normalize value = (value - mn) / (mx - mn) # normalize

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from ..Qt import QtCore, QtGui, QtWidgets from ..Qt import QtCore, QtGui, QtWidgets
HAVE_OPENGL = hasattr(QtWidgets, 'QOpenGLWidget') HAVE_OPENGL = hasattr(QtWidgets, 'QOpenGLWidget')
import math
import warnings import warnings
import numpy as np import numpy as np
from .GraphicsObject import GraphicsObject from .GraphicsObject import GraphicsObject
@ -131,6 +131,8 @@ class PlotCurveItem(GraphicsObject):
elif ax == 1: elif ax == 1:
d = y d = y
d2 = x d2 = x
else:
raise ValueError("Invalid axis value")
## If an orthogonal range is specified, mask the data now ## If an orthogonal range is specified, mask the data now
if orthoRange is not None: if orthoRange is not None:
@ -149,7 +151,7 @@ class PlotCurveItem(GraphicsObject):
# All-NaN data is acceptable; Explicit numpy warning is not needed. # All-NaN data is acceptable; Explicit numpy warning is not needed.
warnings.simplefilter("ignore") warnings.simplefilter("ignore")
b = (np.nanmin(d), np.nanmax(d)) 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) mask = np.isfinite(d)
d = d[mask] d = d[mask]
if len(d) == 0: if len(d) == 0:

View File

@ -1,5 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import warnings import warnings
import math
import numpy as np import numpy as np
from .. import metaarray as metaarray from .. import metaarray as metaarray
from ..Qt import QtCore from ..Qt import QtCore
@ -660,7 +661,7 @@ class PlotDataItem(GraphicsObject):
eps = np.finfo(y.dtype).eps eps = np.finfo(y.dtype).eps
else: else:
eps = 1 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'] ds = self.opts['downsample']
if not isinstance(ds, int): 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. # All-NaN data is handled by returning None; Explicit numpy warning is not needed.
warnings.simplefilter("ignore") warnings.simplefilter("ignore")
ymin = np.nanmin(self.yData) ymin = np.nanmin(self.yData)
if np.isnan( ymin ): if math.isnan( ymin ):
return None # most likely case for all-NaN data return None # most likely case for all-NaN data
xmin = np.nanmin(self.xData) xmin = np.nanmin(self.xData)
if np.isnan( xmin ): if math.isnan( xmin ):
return None # less likely case for all-NaN data return None # less likely case for all-NaN data
ymax = np.nanmax(self.yData) ymax = np.nanmax(self.yData)
xmax = np.nanmax(self.xData) xmax = np.nanmax(self.xData)

View File

@ -1425,7 +1425,7 @@ class Handle(UIGraphicsItem):
menu = self.scene().addParentContextMenus(self, self.getMenu(), ev) menu = self.scene().addParentContextMenus(self, self.getMenu(), ev)
## Make sure it is still ok to remove this handle ## 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) self.removeAction.setEnabled(removeAllowed)
pos = ev.screenPos() pos = ev.screenPos()
menu.popup(QtCore.QPoint(pos.x(), pos.y())) menu.popup(QtCore.QPoint(pos.x(), pos.y()))

View File

@ -6,6 +6,7 @@ try:
except ImportError: except ImportError:
imap = map imap = map
import itertools import itertools
import math
import numpy as np import numpy as np
import weakref import weakref
from ..Qt import QtGui, QtCore, QT_LIB from ..Qt import QtGui, QtCore, QT_LIB
@ -116,7 +117,7 @@ def renderSymbol(symbol, size, pen, brush, device=None):
for more information). for more information).
""" """
## Render a spot with the given parameters to a pixmap ## 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: if device is None:
device = QtGui.QImage(int(size+penPxWidth), int(size+penPxWidth), QtGui.QImage.Format_ARGB32) device = QtGui.QImage(int(size+penPxWidth), int(size+penPxWidth), QtGui.QImage.Format_ARGB32)
device.fill(0) device.fill(0)
@ -950,6 +951,8 @@ class ScatterPlotItem(GraphicsObject):
elif ax == 1: elif ax == 1:
d = self.data['y'] d = self.data['y']
d2 = self.data['x'] d2 = self.data['x']
else:
raise ValueError("Invalid axis value")
if orthoRange is not None: if orthoRange is not None:
mask = (d2 >= orthoRange[0]) * (d2 <= orthoRange[1]) mask = (d2 >= orthoRange[0]) * (d2 <= orthoRange[1])

View File

@ -1,6 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import weakref import weakref
import sys import sys
import math
from copy import deepcopy from copy import deepcopy
import numpy as np import numpy as np
from ...Qt import QtGui, QtCore from ...Qt import QtGui, QtCore
@ -552,7 +553,7 @@ class ViewBox(GraphicsWidget):
xpad = 0.0 xpad = 0.0
# Make sure no nan/inf get through # 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))) raise Exception("Cannot set range [%s, %s]" % (str(mn), str(mx)))
# Apply padding # Apply padding
@ -903,11 +904,11 @@ class ViewBox(GraphicsWidget):
targetRect[ax] = childRange[ax] targetRect[ax] = childRange[ax]
args['xRange' if ax == 0 else 'yRange'] = targetRect[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']: for k in ['xRange', 'yRange']:
if k in args: if k in args:
if not np.all(np.isfinite(args[k])): if not math.isfinite(args[k][0]) or not math.isfinite(args[k][1]):
r = args.pop(k) _ = args.pop(k)
#print("Warning: %s is invalid: %s" % (k, str(r)) #print("Warning: %s is invalid: %s" % (k, str(r))
if len(args) == 0: if len(args) == 0:
@ -1369,10 +1370,20 @@ class ViewBox(GraphicsWidget):
xr = item.dataBounds(0, frac=frac[0], orthoRange=orthoRange[0]) xr = item.dataBounds(0, frac=frac[0], orthoRange=orthoRange[0])
yr = item.dataBounds(1, frac=frac[1], orthoRange=orthoRange[1]) yr = item.dataBounds(1, frac=frac[1], orthoRange=orthoRange[1])
pxPad = 0 if not hasattr(item, 'pixelPadding') else item.pixelPadding() 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 useX = False
xr = (0,0) 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 useY = False
yr = (0,0) yr = (0,0)

View File

@ -214,7 +214,7 @@ class RangeColorMapItem(ptree.types.SimpleParameter):
cmap = self.value() cmap = self.value()
colors = cmap.map(scaled, mode='float') colors = cmap.map(scaled, mode='float')
mask = np.isnan(data) | np.isinf(data) mask = np.invert(np.isfinite(data))
nanColor = self['NaN'] nanColor = self['NaN']
nanColor = (nanColor.red()/255., nanColor.green()/255., nanColor.blue()/255., nanColor.alpha()/255.) nanColor = (nanColor.red()/255., nanColor.green()/255., nanColor.blue()/255., nanColor.alpha()/255.)
colors[mask] = nanColor colors[mask] = nanColor