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
This commit is contained in:
Ogi Moore 2021-04-19 22:12:05 -07:00
parent 85c726e49a
commit c4a1cf11a1
17 changed files with 34 additions and 34 deletions

View File

@ -8,6 +8,7 @@ Demonstrates use of GLScatterPlotItem with rapidly-updating plots.
import initExample import initExample
import pyqtgraph as pg import pyqtgraph as pg
from pyqtgraph import functions as fn
from pyqtgraph.Qt import QtCore, QtGui from pyqtgraph.Qt import QtCore, QtGui
import pyqtgraph.opengl as gl import pyqtgraph.opengl as gl
import numpy as np import numpy as np
@ -84,10 +85,10 @@ def update():
global phase, sp2, d2 global phase, sp2, d2
s = -np.cos(d2*2+phase) s = -np.cos(d2*2+phase)
color = np.empty((len(d2),4), dtype=np.float32) color = np.empty((len(d2),4), dtype=np.float32)
color[:,3] = np.clip(s * 0.1, 0, 1) color[:,3] = fn.clip_array(s * 0.1, 0., 1.)
color[:,0] = np.clip(s * 3.0, 0, 1) color[:,0] = fn.clip_array(s * 3.0, 0., 1.)
color[:,1] = np.clip(s * 1.0, 0, 1) color[:,1] = fn.clip_array(s * 1.0, 0., 1.)
color[:,2] = np.clip(s ** 3, 0, 1) color[:,2] = fn.clip_array(s ** 3, 0., 1.)
sp2.setData(color=color) sp2.setData(color=color)
phase -= 0.1 phase -= 0.1

View File

@ -11,7 +11,7 @@ import numpy as np
import pyqtgraph as pg import pyqtgraph as pg
from pyqtgraph.Qt import QtCore, QtGui from pyqtgraph.Qt import QtCore, QtGui
import pyqtgraph.opengl as gl import pyqtgraph.opengl as gl
from pyqtgraph.functions import clip_array from pyqtgraph import functions as fn
app = pg.mkQApp("GLVolumeItem Example") app = pg.mkQApp("GLVolumeItem Example")
w = gl.GLViewWidget() w = gl.GLViewWidget()
@ -40,8 +40,8 @@ def psi(i, j, k, offset=(50,50,100)):
data = np.fromfunction(psi, (100,100,200)) data = np.fromfunction(psi, (100,100,200))
positive = np.log(clip_array(data, 0, data.max())**2) positive = np.log(fn.clip_array(data, np.finfo(data.dtype).eps, data.max())**2)
negative = np.log(clip_array(-data, 0, -data.min())**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 = np.empty(data.shape + (4,), dtype=np.ubyte)
d2[..., 0] = positive * (255./positive.max()) d2[..., 0] = positive * (255./positive.max())

View File

@ -6,14 +6,9 @@ Distributed under MIT/X11 license. See license.txt for more information.
""" """
from .Qt import QtCore from .Qt import QtCore
from . import functions as fn
from math import sin, acos, atan2, inf, pi, hypot 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): class Point(QtCore.QPointF):
"""Extension of QPointF which adds a few missing methods.""" """Extension of QPointF which adds a few missing methods."""
@ -112,7 +107,7 @@ class Point(QtCore.QPointF):
if n1 == 0. or n2 == 0.: if n1 == 0. or n2 == 0.:
return None return None
## Probably this should be done with arctan2 instead.. ## 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) c = self.cross(a)
if c > 0: if c > 0:
ang *= -1. ang *= -1.

View File

@ -4,8 +4,9 @@ Vector.py - Extension of QVector3D which adds a few missing methods.
Copyright 2010 Luke Campagnola Copyright 2010 Luke Campagnola
Distributed under MIT/X11 license. See license.txt for more information. Distributed under MIT/X11 license. See license.txt for more information.
""" """
from math import acos
from .Qt import QtGui, QtCore, QT_LIB from .Qt import QtGui, QtCore, QT_LIB
from . import functions as fn
import numpy as np import numpy as np
class Vector(QtGui.QVector3D): class Vector(QtGui.QVector3D):
@ -88,7 +89,7 @@ class Vector(QtGui.QVector3D):
if n1 == 0. or n2 == 0.: if n1 == 0. or n2 == 0.:
return None return None
## Probably this should be done with arctan2 instead.. ## 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) # c = self.crossProduct(a)
# if c > 0: # if c > 0:
# ang *= -1. # ang *= -1.

View File

@ -1,6 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import numpy as np import numpy as np
from ..Qt import QtGui, QtCore, QtSvg, QT_LIB from ..Qt import QtGui, QtCore, QtSvg, QT_LIB
from .. import functions as fn
from ..graphicsItems.ROI import ROI from ..graphicsItems.ROI import ROI
from .. import SRTTransform, ItemGroup from .. import SRTTransform, ItemGroup
import importlib import importlib
@ -240,7 +241,7 @@ class CanvasItem(QtCore.QObject):
self._graphicsItem.setOpacity(alpha) self._graphicsItem.setOpacity(alpha)
def setAlpha(self, 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): def alpha(self):
return self.alphaSlider.value() / 1023. return self.alphaSlider.value() / 1023.

View File

@ -63,7 +63,7 @@ class CurvePoint(GraphicsObject):
pos = self.property('position') pos = self.property('position')
if 'QVariant' in repr(pos): ## need to support 2 APIs :( if 'QVariant' in repr(pos): ## need to support 2 APIs :(
pos = pos.toDouble()[0] 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 if index != int(index): ## interpolate floating-point values
i1 = int(index) i1 = int(index)

View File

@ -147,7 +147,7 @@ class GridItem(UIGraphicsItem):
continue continue
ppl = dim[ax] / nl[ax] 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'] linePen = self.opts['pen']
lineColor = self.opts['pen'].color() lineColor = self.opts['pen'].color()

View File

@ -593,7 +593,7 @@ class InfLineLabel(TextItem):
return return
rel = self._posToRel(ev.pos()) 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() self.updatePosition()
if ev.isFinish(): if ev.isFinish():
self._moving = False self._moving = False

View File

@ -372,7 +372,7 @@ class PlotItem(GraphicsWidget):
if y is not None: if y is not None:
self.ctrl.yGridCheck.setChecked(y) self.ctrl.yGridCheck.setChecked(y)
if alpha is not None: 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) self.ctrl.gridAlphaSlider.setValue(v)
def close(self): def close(self):

View File

@ -1073,7 +1073,7 @@ class ScatterPlotItem(GraphicsObject):
# Map points using painter's world transform so they are drawn with pixel-valued sizes # 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 = np.vstack([self.data['x'], self.data['y']])
pts = fn.transformCoordinates(p.transform(), pts) 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() p.resetTransform()
if self.opts['useCache'] and self._exportOpts is False: if self.opts['useCache'] and self._exportOpts is False:

View File

@ -666,7 +666,7 @@ class ViewBox(GraphicsWidget):
def suggestPadding(self, axis): def suggestPadding(self, axis):
l = self.width() if axis==0 else self.height() l = self.width() if axis==0 else self.height()
if l > 0: 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: else:
padding = 0.02 padding = 0.02
return padding return padding

View File

@ -17,6 +17,7 @@ from math import log10
import numpy as np import numpy as np
from ..Qt import QtCore, QtGui, QT_LIB from ..Qt import QtCore, QtGui, QT_LIB
from .. import functions as fn
import importlib import importlib
ui_template = importlib.import_module( ui_template = importlib.import_module(
f'.ImageViewTemplate_{QT_LIB.lower()}', package=__package__) f'.ImageViewTemplate_{QT_LIB.lower()}', package=__package__)
@ -513,7 +514,7 @@ class ImageView(QtGui.QWidget):
def setCurrentIndex(self, ind): def setCurrentIndex(self, ind):
"""Set the currently displayed frame index.""" """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 self.ignorePlaying = True
# Implicitly call timeLineChanged # Implicitly call timeLineChanged
self.timeLine.setValue(self.tVals[index]) self.timeLine.setValue(self.tVals[index])

View File

@ -344,7 +344,7 @@ class GLViewWidget(QtWidgets.QOpenGLWidget):
self.opts['rotation'] = q self.opts['rotation'] = q
else: # default euler rotation method else: # default euler rotation method
self.opts['azimuth'] += azim 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() self.update()
def pan(self, dx, dy, dz, relative='global'): def pan(self, dx, dy, dz, relative='global'):

View File

@ -3,7 +3,7 @@ from OpenGL.GL import *
from OpenGL.arrays import vbo from OpenGL.arrays import vbo
from .. GLGraphicsItem import GLGraphicsItem from .. GLGraphicsItem import GLGraphicsItem
from .. import shaders from .. import shaders
from ...functions import clip_array from ... import functions as fn
from ...Qt import QtGui from ...Qt import QtGui
import numpy as np import numpy as np
@ -62,12 +62,12 @@ class GLScatterPlotItem(GLGraphicsItem):
## Generate texture for rendering points ## Generate texture for rendering points
w = 64 w = 64
def fn(x,y): def genTexture(x,y):
r = np.hypot((x-(w-1)/2.), (y-(w-1)/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.)) return 255 * (w / 2 - fn.clip_array(r, w / 2 - 1, w / 2))
pData = np.empty((w, w, 4)) pData = np.empty((w, w, 4))
pData[:] = 255 pData[:] = 255
pData[:,:,3] = np.fromfunction(fn, pData.shape[:2]) pData[:,:,3] = np.fromfunction(genTexture, pData.shape[:2])
pData = pData.astype(np.ubyte) pData = pData.astype(np.ubyte)
if getattr(self, "pointTexture", None) is None: if getattr(self, "pointTexture", None) is None:

View File

@ -2,6 +2,7 @@ from collections import OrderedDict
import numpy as np import numpy as np
import copy import copy
from math import log2 from math import log2
from .. import functions as fn
class SystemSolver(object): class SystemSolver(object):
@ -391,7 +392,7 @@ if __name__ == '__main__':
sh = self.shutter # this raises RuntimeError if shutter has not sh = self.shutter # this raises RuntimeError if shutter has not
# been specified # been specified
ap = 4.0 * (sh / (1./60.)) * (iso / 100.) * (2 ** exp) * (2 ** light) 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: except RuntimeError:
# program mode; we can select a suitable shutter # program mode; we can select a suitable shutter
# value at the same time. # value at the same time.

View File

@ -161,7 +161,7 @@ class ColorMapParameter(ptree.types.GroupParameter):
elif op == 'Set': elif op == 'Set':
colors[mask] = colors2[mask] colors[mask] = colors2[mask]
colors = np.clip(colors, 0, 1) colors = fn.clip_array(colors, 0., 1.)
if mode == 'byte': if mode == 'byte':
colors = (colors * 255).astype(np.ubyte) colors = (colors * 255).astype(np.ubyte)
@ -210,7 +210,7 @@ class RangeColorMapItem(ptree.types.SimpleParameter):
def map(self, data): def map(self, data):
data = data[self.fieldName] 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() cmap = self.value()
colors = cmap.map(scaled, mode='float') colors = cmap.map(scaled, mode='float')

View File

@ -378,7 +378,7 @@ class GraphicsView(QtGui.QGraphicsView):
return return
if ev.buttons() == QtCore.Qt.RightButton: 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 scale = 1.01 ** delta
self.scale(scale[0], scale[1], center=self.mapToScene(self.mousePressPos)) self.scale(scale[0], scale[1], center=self.mapToScene(self.mousePressPos))
self.sigDeviceRangeChanged.emit(self, self.range) self.sigDeviceRangeChanged.emit(self, self.range)