Reduce reallocation in dynamic range limiter (#1556)

* change imports in cupy module to be local

* reduced re-allocation in dynamic range limiter, part 1

* minor cleanup
This commit is contained in:
Nils Nemitz 2021-02-10 13:36:31 +09:00 committed by GitHub
parent aed2382e38
commit 33365fb730
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -166,7 +166,9 @@ class PlotDataItem(GraphicsObject):
self.scatter.sigClicked.connect(self.scatterClicked) self.scatter.sigClicked.connect(self.scatterClicked)
self.scatter.sigHovered.connect(self.scatterHovered) self.scatter.sigHovered.connect(self.scatterHovered)
self._viewRangeWasChanged = False
self._dataRect = None self._dataRect = None
self._drlLastClip = (0.0, 0.0) # holds last clipping points of dynamic range limiter
#self.clear() #self.clear()
self.opts = { self.opts = {
'connect': 'all', 'connect': 'all',
@ -200,6 +202,7 @@ class PlotDataItem(GraphicsObject):
'autoDownsampleFactor': 5., # draw ~5 samples per pixel 'autoDownsampleFactor': 5., # draw ~5 samples per pixel
'clipToView': False, 'clipToView': False,
'dynamicRangeLimit': 1e6, 'dynamicRangeLimit': 1e6,
'dynamicRangeHyst': 3.0,
'data': None, 'data': None,
} }
@ -400,19 +403,28 @@ class PlotDataItem(GraphicsObject):
self.xDisp = self.yDisp = None self.xDisp = self.yDisp = None
self.updateItems() self.updateItems()
def setDynamicRangeLimit(self, limit): def setDynamicRangeLimit(self, limit=1e06, hysteresis=3.):
""" """
Limit the off-screen positions of data points at large magnification Limit the off-screen positions of data points at large magnification
This avoids errors with plots not displaying because their visibility is incorrectly determined. The default setting repositions far-off points to be within +-1E+06 times the viewport height. This avoids errors with plots not displaying because their visibility is incorrectly determined. The default setting repositions far-off points to be within +-1E+06 times the viewport height.
=============== ================================================================ =============== ================================================================
**Arguments:** **Arguments:**
limit (float or None) Maximum allowed vertical distance of plotted limit (float or None) Any data outside the range of limit * hysteresis
points in units of viewport height. will be constrained to the limit value limit.
All values are relative to the viewport height.
'None' disables the check for a minimal increase in performance. 'None' disables the check for a minimal increase in performance.
Default is 1E+06. Default is 1E+06.
hysteresis (float) Hysteresis factor that controls how much change
in zoom level (vertical height) is allowed before recalculating
Default is 3.0
=============== ================================================================ =============== ================================================================
""" """
if hysteresis < 1.0:
hysteresis = 1.0
self.opts['dynamicRangeHyst'] = hysteresis
if limit == self.opts['dynamicRangeLimit']: if limit == self.opts['dynamicRangeLimit']:
return # avoid update if there is no change return # avoid update if there is no change
self.opts['dynamicRangeLimit'] = limit # can be None self.opts['dynamicRangeLimit'] = limit # can be None
@ -592,7 +604,7 @@ class PlotDataItem(GraphicsObject):
if self.xData is None: if self.xData is None:
return (None, None) return (None, None)
if self.xDisp is None: if self.xDisp is None or self._viewRangeWasChanged:
x = self.xData x = self.xData
y = self.yData y = self.yData
@ -687,14 +699,41 @@ class PlotDataItem(GraphicsObject):
data_range = self.dataRect() data_range = self.dataRect()
if data_range is not None: if data_range is not None:
view_height = view_range.height() view_height = view_range.height()
lim = self.opts['dynamicRangeLimit'] limit = self.opts['dynamicRangeLimit']
if data_range.height() > lim * view_height: hyst = self.opts['dynamicRangeHyst']
min_val = view_range.top() - lim * view_height # never clip data if it fits into +/- (extended) limit * view height
max_val = view_range.bottom() + lim * view_height if data_range.height() > 2 * hyst * limit * view_height:
y = np.clip(y, a_min=min_val, a_max=max_val) # check if cached display data can be reused:
if self.yDisp is not None: # top is minimum value, bottom is maximum value
# how many multiples of the current view height does the clipped plot extend to the top and bottom?
top_exc =-(self._drlLastClip[0]-view_range.bottom()) / view_height
bot_exc = (self._drlLastClip[1]-view_range.top() ) / view_height
# print(top_exc, bot_exc, hyst)
if ( top_exc >= limit / hyst and top_exc <= limit * hyst
and bot_exc >= limit / hyst and bot_exc <= limit * hyst ):
# restore cached values
x = self.xDisp
y = self.yDisp
else:
min_val = view_range.bottom() - limit * view_height
max_val = view_range.top() + limit * view_height
if( min_val >= self._drlLastClip[0]
and max_val <= self._drlLastClip[1] ):
# if we need to clip further, we can work in-place on the output buffer
# print('in-place:', end='')
np.clip(self.yDisp, out=self.yDisp, a_min=min_val, a_max=max_val)
x = self.xDisp
y = self.yDisp
else:
# otherwise we need to recopy from the full data
# print('alloc:', end='')
y = np.clip(y, a_min=min_val, a_max=max_val)
# print('{:.1e}<->{:.1e}'.format( min_val, max_val ))
self._drlLastClip = (min_val, max_val)
self.xDisp = x self.xDisp = x
self.yDisp = y self.yDisp = y
self._viewRangeWasChanged = False
return self.xDisp, self.yDisp return self.xDisp, self.yDisp
def dataRect(self): def dataRect(self):
@ -797,12 +836,15 @@ class PlotDataItem(GraphicsObject):
def viewRangeChanged(self): def viewRangeChanged(self):
# view range has changed; re-plot if needed # view range has changed; re-plot if needed
self._viewRangeWasChanged = True
if( self.opts['clipToView'] if( self.opts['clipToView']
or self.opts['autoDownsample'] or self.opts['autoDownsample']
or self.opts['dynamicRangeLimit'] is not None
): ):
self.xDisp = self.yDisp = None self.xDisp = self.yDisp = None
self.updateItems() self.updateItems()
elif self.opts['dynamicRangeLimit'] is not None:
# do not discard cached display data
self.updateItems()
def _fourierTransform(self, x, y): def _fourierTransform(self, x, y):
## Perform fourier transform. If x values are not sampled uniformly, ## Perform fourier transform. If x values are not sampled uniformly,