From fa826bd4cbf4342badf91692cf460a0253edeadd Mon Sep 17 00:00:00 2001 From: KIU Shueng Chuan Date: Mon, 9 Aug 2021 22:19:52 +0800 Subject: [PATCH 1/6] add benchmarks/arrayToQPath.py --- benchmarks/arrayToQPath.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 benchmarks/arrayToQPath.py diff --git a/benchmarks/arrayToQPath.py b/benchmarks/arrayToQPath.py new file mode 100644 index 00000000..67fd13ee --- /dev/null +++ b/benchmarks/arrayToQPath.py @@ -0,0 +1,30 @@ +import numpy as np +import pyqtgraph as pg + +rng = np.random.default_rng(12345) + +class _TimeSuite: + params = ([10_000, 100_000, 1_000_000], ['all', 'finite', 'pairs', 'array']) + + def setup(self, nelems, connect): + self.xdata = np.arange(nelems, dtype=np.float64) + self.ydata = rng.standard_normal(nelems, dtype=np.float64) + if connect == 'array': + self.connect_array = np.ones(nelems, dtype=bool) + if self.have_nonfinite: + self.ydata[::5000] = np.nan + + def time_test(self, nelems, connect): + if connect == 'array': + connect = self.connect_array + pg.arrayToQPath(self.xdata, self.ydata, connect=connect) + +class TimeSuiteAllFinite(_TimeSuite): + def __init__(self): + super().__init__() + self.have_nonfinite = False + +class TimeSuiteWithNonFinite(_TimeSuite): + def __init__(self): + super().__init__() + self.have_nonfinite = True From 7e31a5e4ee1f24786cca83035bb8da0895fbd24b Mon Sep 17 00:00:00 2001 From: KIU Shueng Chuan Date: Mon, 9 Aug 2021 14:58:18 +0800 Subject: [PATCH 2/6] setup codepath for connect=='all' and all-finite "all-finite" includes the case where user requests to skip finiteCheck. --- pyqtgraph/functions.py | 55 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 49 insertions(+), 6 deletions(-) diff --git a/pyqtgraph/functions.py b/pyqtgraph/functions.py index c4d7c1ee..53ccb2f7 100644 --- a/pyqtgraph/functions.py +++ b/pyqtgraph/functions.py @@ -1935,11 +1935,14 @@ def arrayToQPath(x, y, connect='all', finiteCheck=True): # make connect argument contain only str type connect_array, connect = connect, 'array' + isfinite = None + all_isfinite = None + if finiteCheck or connect == 'finite': + isfinite = np.isfinite(x) & np.isfinite(y) + use_qpolygonf = connect == 'all' - isfinite = None if connect == 'finite': - isfinite = np.isfinite(x) & np.isfinite(y) if not finiteCheck: # if user specified to skip finite check, then that forces use_qpolygonf use_qpolygonf = True @@ -1950,9 +1953,49 @@ def arrayToQPath(x, y, connect='all', finiteCheck=True): if nonfinite_cnt / n < 2 / 100: use_qpolygonf = True finiteCheck = False - if nonfinite_cnt == 0: + all_isfinite = nonfinite_cnt == 0 + if all_isfinite: connect = 'all' + if hasattr(path, 'reserve'): # Qt 5.13 + path.reserve(n) + + while connect == 'all': + if finiteCheck: + # non-finite value patching operates on the whole array + # so if there are any non-finite values, we bail out + if all_isfinite is None: + all_isfinite = np.all(isfinite) + if not all_isfinite: + break + chunksize = 10000 + numchunks = (n + chunksize - 1) // chunksize + if numchunks <= 2: + break + subpoly = QtGui.QPolygonF() + subpath = None + for idx in range(numchunks): + xchunk = x[idx*chunksize:(idx+1)*chunksize] + ychunk = y[idx*chunksize:(idx+1)*chunksize] + currsize = xchunk.size + if currsize != subpoly.size(): + if hasattr(subpoly, 'resize'): + subpoly.resize(currsize) + else: + subpoly.fill(QtCore.QPointF(), currsize) + subarr = ndarray_from_qpolygonf(subpoly) + subarr[:, 0] = xchunk + subarr[:, 1] = ychunk + if subpath is None: + subpath = QtGui.QPainterPath() + subpath.addPolygon(subpoly) + path.connectPath(subpath) + if hasattr(subpath, 'clear'): # Qt 5.13 + subpath.clear() + else: + subpath = None + return path + if use_qpolygonf: backstore = create_qpolygonf(n) arr = np.frombuffer(ndarray_from_qpolygonf(backstore), dtype=[('x', 'f8'), ('y', 'f8')]) @@ -1974,9 +2017,9 @@ def arrayToQPath(x, y, connect='all', finiteCheck=True): # https://github.com/qt/qtbase/commit/c04bd30de072793faee5166cff866a4c4e0a9dd7 # We therefore replace non-finite values if finiteCheck: - if isfinite is None: - isfinite = np.isfinite(x) & np.isfinite(y) - if not np.all(isfinite): + if all_isfinite is None: + all_isfinite = np.all(isfinite) + if not all_isfinite: # credit: Divakar https://stackoverflow.com/a/41191127/643629 mask = ~isfinite idx = np.arange(len(x)) From 679c1002c1b2a8dbbb44045b97b21430b478a425 Mon Sep 17 00:00:00 2001 From: KIU Shueng Chuan Date: Wed, 11 Aug 2021 06:55:21 +0800 Subject: [PATCH 3/6] break up arrayToQPath this restructuring removes inter-dependencies between the various modes. this makes it easier to reason about and modify the various codepaths. --- pyqtgraph/functions.py | 320 +++++++++++++++++++++++------------------ 1 file changed, 184 insertions(+), 136 deletions(-) diff --git a/pyqtgraph/functions.py b/pyqtgraph/functions.py index 53ccb2f7..76347438 100644 --- a/pyqtgraph/functions.py +++ b/pyqtgraph/functions.py @@ -1870,6 +1870,153 @@ def downsample(data, n, axis=0, xvals='subsample'): return MetaArray(d2, info=info) +def _fill_nonfinite(arr, isfinite): + # the presence of inf/nans result in an empty QPainterPath being generated + # this behavior started in Qt 5.12.3 and was introduced in this commit + # https://github.com/qt/qtbase/commit/c04bd30de072793faee5166cff866a4c4e0a9dd7 + # We therefore replace non-finite values + + # credit: Divakar https://stackoverflow.com/a/41191127/643629 + mask = ~isfinite + idx = np.arange(len(arr)) + idx[mask] = -1 + np.maximum.accumulate(idx, out=idx) + first = np.searchsorted(idx, 0) + if first < len(arr): + # Replace all non-finite entries from beginning of arr with the first finite one + idx[:first] = first + arr[:] = arr[:][idx] + + +def _arrayToQPath_all(x, y, finiteCheck): + n = x.shape[0] + if n == 0: + return QtGui.QPainterPath() + + if finiteCheck: + isfinite = np.isfinite(x) & np.isfinite(y) + all_isfinite = np.all(isfinite) + + chunksize = 10000 + numchunks = (n + chunksize - 1) // chunksize + minchunks = 3 + + if numchunks < minchunks: + # too few chunks, batching would be a pessimization + poly = create_qpolygonf(n) + arr = ndarray_from_qpolygonf(poly) + arr[:, 0] = x + arr[:, 1] = y + + if finiteCheck and not all_isfinite: + _fill_nonfinite(arr, isfinite) + + path = QtGui.QPainterPath() + path.addPolygon(poly) + return path + + if finiteCheck and not all_isfinite: + # this section here is inefficient as a full copy of the input data is made. + arr = np.column_stack((x, y)) + _fill_nonfinite(arr, isfinite) + x = arr[:, 0] + y = arr[:, 1] + + # at this point, we have: + # 1) numchunks >= minchunks + # 2) x, y filled with finite only + + path = QtGui.QPainterPath() + if hasattr(path, 'reserve'): # Qt 5.13 + path.reserve(n) + subpoly = QtGui.QPolygonF() + subpath = None + for idx in range(numchunks): + xchunk = x[idx*chunksize:(idx+1)*chunksize] + ychunk = y[idx*chunksize:(idx+1)*chunksize] + currsize = xchunk.size + if currsize != subpoly.size(): + if hasattr(subpoly, 'resize'): + subpoly.resize(currsize) + else: + subpoly.fill(QtCore.QPointF(), currsize) + subarr = ndarray_from_qpolygonf(subpoly) + subarr[:, 0] = xchunk + subarr[:, 1] = ychunk + if subpath is None: + subpath = QtGui.QPainterPath() + subpath.addPolygon(subpoly) + path.connectPath(subpath) + if hasattr(subpath, 'clear'): # Qt 5.13 + subpath.clear() + else: + subpath = None + return path + + +def _arrayToQPath_finite(x, y, isfinite=None): + n = x.shape[0] + if n == 0: + return QtGui.QPainterPath() + + if isfinite is None: + isfinite = np.isfinite(x) & np.isfinite(y) + + path = QtGui.QPainterPath() + if hasattr(path, 'reserve'): # Qt 5.13 + path.reserve(n) + + sidx = np.nonzero(~isfinite)[0] + 1 + # note: the chunks are views + xchunks = np.split(x, sidx) + ychunks = np.split(y, sidx) + chunks = list(zip(xchunks, ychunks)) + + # create a single polygon able to hold the largest chunk + maxlen = max(len(chunk) for chunk in xchunks) + subpoly = create_qpolygonf(maxlen) + subarr = ndarray_from_qpolygonf(subpoly) + + # resize and fill do not change the capacity + if hasattr(subpoly, 'resize'): + subpoly_resize = subpoly.resize + else: + # PyQt will be less efficient + subpoly_resize = lambda n, v=QtCore.QPointF() : subpoly.fill(v, n) + + # notes: + # - we backfill the non-finite in order to get the same image as the + # old codepath on the CI. somehow P1--P2 gets rendered differently + # from P1--P2--P2 + # - we do not generate MoveTo(s) that are not followed by a LineTo, + # thus the QPainterPath can be different from the old codepath's + + # all chunks except the last chunk have a trailing non-finite + for xchunk, ychunk in chunks[:-1]: + lc = len(xchunk) + if lc <= 1: + # len 1 means we have a string of non-finite + continue + subpoly_resize(lc) + subarr[:lc, 0] = xchunk + subarr[:lc, 1] = ychunk + subarr[lc-1] = subarr[lc-2] # fill non-finite with its neighbour + path.addPolygon(subpoly) + + # handle last chunk, which is either all-finite or empty + for xchunk, ychunk in chunks[-1:]: + lc = len(xchunk) + if lc <= 1: + # can't draw a line with just 1 point + continue + subpoly_resize(lc) + subarr[:lc, 0] = xchunk + subarr[:lc, 1] = ychunk + path.addPolygon(subpoly) + + return path + + def arrayToQPath(x, y, connect='all', finiteCheck=True): """ Convert an array of x,y coordinates to QPainterPath as efficiently as @@ -1925,10 +2072,9 @@ def arrayToQPath(x, y, connect='all', finiteCheck=True): This binary format may change in future versions of Qt """ - path = QtGui.QPainterPath() n = x.shape[0] if n == 0: - return path + return QtGui.QPainterPath() connect_array = None if isinstance(connect, np.ndarray): @@ -1936,167 +2082,69 @@ def arrayToQPath(x, y, connect='all', finiteCheck=True): connect_array, connect = connect, 'array' isfinite = None - all_isfinite = None - if finiteCheck or connect == 'finite': - isfinite = np.isfinite(x) & np.isfinite(y) - - use_qpolygonf = connect == 'all' if connect == 'finite': if not finiteCheck: - # if user specified to skip finite check, then that forces use_qpolygonf - use_qpolygonf = True + # if user specified to skip finite check, then we skip the heuristic + return _arrayToQPath_finite(x, y) + + # otherwise use a heuristic + # if non-finite aren't that many, then use_qpolyponf + isfinite = np.isfinite(x) & np.isfinite(y) + nonfinite_cnt = n - np.sum(isfinite) + all_isfinite = nonfinite_cnt == 0 + if all_isfinite: + # delegate to connect='all' + connect = 'all' + finiteCheck = False + elif nonfinite_cnt / n < 2 / 100: + return _arrayToQPath_finite(x, y, isfinite) else: - # otherwise use a heuristic - # if non-finite aren't that many, then use_qpolyponf - nonfinite_cnt = n - np.sum(isfinite) - if nonfinite_cnt / n < 2 / 100: - use_qpolygonf = True - finiteCheck = False - all_isfinite = nonfinite_cnt == 0 - if all_isfinite: - connect = 'all' + # delegate to connect=ndarray + # finiteCheck=True, all_isfinite=False + connect = 'array' + connect_array = isfinite - if hasattr(path, 'reserve'): # Qt 5.13 - path.reserve(n) + if connect == 'all': + return _arrayToQPath_all(x, y, finiteCheck) - while connect == 'all': - if finiteCheck: - # non-finite value patching operates on the whole array - # so if there are any non-finite values, we bail out - if all_isfinite is None: - all_isfinite = np.all(isfinite) - if not all_isfinite: - break - chunksize = 10000 - numchunks = (n + chunksize - 1) // chunksize - if numchunks <= 2: - break - subpoly = QtGui.QPolygonF() - subpath = None - for idx in range(numchunks): - xchunk = x[idx*chunksize:(idx+1)*chunksize] - ychunk = y[idx*chunksize:(idx+1)*chunksize] - currsize = xchunk.size - if currsize != subpoly.size(): - if hasattr(subpoly, 'resize'): - subpoly.resize(currsize) - else: - subpoly.fill(QtCore.QPointF(), currsize) - subarr = ndarray_from_qpolygonf(subpoly) - subarr[:, 0] = xchunk - subarr[:, 1] = ychunk - if subpath is None: - subpath = QtGui.QPainterPath() - subpath.addPolygon(subpoly) - path.connectPath(subpath) - if hasattr(subpath, 'clear'): # Qt 5.13 - subpath.clear() - else: - subpath = None - return path - - if use_qpolygonf: - backstore = create_qpolygonf(n) - arr = np.frombuffer(ndarray_from_qpolygonf(backstore), dtype=[('x', 'f8'), ('y', 'f8')]) - else: - backstore = QtCore.QByteArray() - backstore.resize(4 + n*20 + 8) # contents uninitialized - backstore.replace(0, 4, struct.pack('>i', n)) - # cStart, fillRule (Qt.FillRule.OddEvenFill) - backstore.replace(4+n*20, 8, struct.pack('>ii', 0, 0)) - arr = np.frombuffer(backstore, dtype=[('c', '>i4'), ('x', '>f8'), ('y', '>f8')], - count=n, offset=4) + backstore = QtCore.QByteArray() + backstore.resize(4 + n*20 + 8) # contents uninitialized + backstore.replace(0, 4, struct.pack('>i', n)) + # cStart, fillRule (Qt.FillRule.OddEvenFill) + backstore.replace(4+n*20, 8, struct.pack('>ii', 0, 0)) + arr = np.frombuffer(backstore, dtype=[('c', '>i4'), ('x', '>f8'), ('y', '>f8')], + count=n, offset=4) # Fill array with vertex values arr['x'] = x arr['y'] = y - # the presence of inf/nans result in an empty QPainterPath being generated - # this behavior started in Qt 5.12.3 and was introduced in this commit - # https://github.com/qt/qtbase/commit/c04bd30de072793faee5166cff866a4c4e0a9dd7 - # We therefore replace non-finite values if finiteCheck: - if all_isfinite is None: + if isfinite is None: + isfinite = np.isfinite(x) & np.isfinite(y) all_isfinite = np.all(isfinite) if not all_isfinite: - # credit: Divakar https://stackoverflow.com/a/41191127/643629 - mask = ~isfinite - idx = np.arange(len(x)) - idx[mask] = -1 - np.maximum.accumulate(idx, out=idx) - first = np.searchsorted(idx, 0) - if first < len(x): - # Replace all non-finite entries from beginning of arr with the first finite one - idx[:first] = first - arr[:] = arr[:][idx] + _fill_nonfinite(arr, isfinite) # decide which points are connected by lines - if connect == 'all': - path.addPolygon(backstore) - return path - elif connect == 'pairs': + if connect == 'pairs': arr['c'][0::2] = 0 arr['c'][1::2] = 1 # connect every 2nd point to every 1st one - elif connect == 'finite': + elif connect == 'array': # Let's call a point with either x or y being nan is an invalid point. # A point will anyway not connect to an invalid point regardless of the # 'c' value of the invalid point. Therefore, we should set 'c' to 0 for # the next point of an invalid point. - if not use_qpolygonf: - arr['c'][:1] = 0 # the first vertex has no previous vertex to connect - arr['c'][1:] = isfinite[:-1] - else: - sidx = np.nonzero(~isfinite)[0] + 1 - chunks = np.split(arr, sidx) # note: the chunks are views - - # create a single polygon able to hold the largest chunk - maxlen = max(len(chunk) for chunk in chunks) - subpoly = create_qpolygonf(maxlen) - subarr = np.frombuffer(ndarray_from_qpolygonf(subpoly), dtype=arr.dtype) - - # resize and fill do not change the capacity - if hasattr(subpoly, 'resize'): - subpoly_resize = subpoly.resize - else: - # PyQt will be less efficient - subpoly_resize = lambda n, v=QtCore.QPointF() : subpoly.fill(v, n) - - # notes: - # - we backfill the non-finite in order to get the same image as the - # old codepath on the CI. somehow P1--P2 gets rendered differently - # from P1--P2--P2 - # - we do not generate MoveTo(s) that are not followed by a LineTo, - # thus the QPainterPath can be different from the old codepath's - - # all chunks except the last chunk have a trailing non-finite - for chunk in chunks[:-1]: - lc = len(chunk) - if lc <= 1: - # len 1 means we have a string of non-finite - continue - subpoly_resize(lc) - subarr[:lc] = chunk - subarr[lc-1] = subarr[lc-2] # fill non-finite with its neighbour - path.addPolygon(subpoly) - - # handle last chunk, which is either all-finite or empty - for chunk in chunks[-1:]: - lc = len(chunk) - if lc <= 1: - # can't draw a line with just 1 point - continue - subpoly_resize(lc) - subarr[:lc] = chunk - path.addPolygon(subpoly) - - return path - elif connect == 'array': arr['c'][:1] = 0 # the first vertex has no previous vertex to connect arr['c'][1:] = connect_array[:-1] else: raise ValueError('connect argument must be "all", "pairs", "finite", or array') + path = QtGui.QPainterPath() + if hasattr(path, 'reserve'): # Qt 5.13 + path.reserve(n) + ds = QtCore.QDataStream(backstore) ds >> path return path From 44e32f00c7d2876463d351c341c5538638761ae9 Mon Sep 17 00:00:00 2001 From: KIU Shueng Chuan Date: Wed, 11 Aug 2021 21:14:32 +0800 Subject: [PATCH 4/6] _fill_nonfinite() -> _compute_backfill_indices() this allows us to defer the backfilling --- pyqtgraph/functions.py | 63 +++++++++++++++++++++++++----------------- 1 file changed, 38 insertions(+), 25 deletions(-) diff --git a/pyqtgraph/functions.py b/pyqtgraph/functions.py index 76347438..36d85531 100644 --- a/pyqtgraph/functions.py +++ b/pyqtgraph/functions.py @@ -1870,7 +1870,7 @@ def downsample(data, n, axis=0, xvals='subsample'): return MetaArray(d2, info=info) -def _fill_nonfinite(arr, isfinite): +def _compute_backfill_indices(isfinite): # the presence of inf/nans result in an empty QPainterPath being generated # this behavior started in Qt 5.12.3 and was introduced in this commit # https://github.com/qt/qtbase/commit/c04bd30de072793faee5166cff866a4c4e0a9dd7 @@ -1878,14 +1878,16 @@ def _fill_nonfinite(arr, isfinite): # credit: Divakar https://stackoverflow.com/a/41191127/643629 mask = ~isfinite - idx = np.arange(len(arr)) + idx = np.arange(len(isfinite)) idx[mask] = -1 np.maximum.accumulate(idx, out=idx) first = np.searchsorted(idx, 0) - if first < len(arr): + if first < len(isfinite): # Replace all non-finite entries from beginning of arr with the first finite one idx[:first] = first - arr[:] = arr[:][idx] + return idx + else: + return None def _arrayToQPath_all(x, y, finiteCheck): @@ -1905,26 +1907,29 @@ def _arrayToQPath_all(x, y, finiteCheck): # too few chunks, batching would be a pessimization poly = create_qpolygonf(n) arr = ndarray_from_qpolygonf(poly) - arr[:, 0] = x - arr[:, 1] = y + backfill_idx = None if finiteCheck and not all_isfinite: - _fill_nonfinite(arr, isfinite) + backfill_idx = _compute_backfill_indices(isfinite) + + if backfill_idx is None: + arr[:, 0] = x + arr[:, 1] = y + else: + arr[:, 0] = x[backfill_idx] + arr[:, 1] = y[backfill_idx] path = QtGui.QPainterPath() + if hasattr(path, 'reserve'): # Qt 5.13 + path.reserve(n) path.addPolygon(poly) return path + backfill_idx = None if finiteCheck and not all_isfinite: - # this section here is inefficient as a full copy of the input data is made. - arr = np.column_stack((x, y)) - _fill_nonfinite(arr, isfinite) - x = arr[:, 0] - y = arr[:, 1] + backfill_idx = _compute_backfill_indices(isfinite) - # at this point, we have: - # 1) numchunks >= minchunks - # 2) x, y filled with finite only + # at this point, we have numchunks >= minchunks path = QtGui.QPainterPath() if hasattr(path, 'reserve'): # Qt 5.13 @@ -1932,17 +1937,21 @@ def _arrayToQPath_all(x, y, finiteCheck): subpoly = QtGui.QPolygonF() subpath = None for idx in range(numchunks): - xchunk = x[idx*chunksize:(idx+1)*chunksize] - ychunk = y[idx*chunksize:(idx+1)*chunksize] - currsize = xchunk.size + sl = slice(idx*chunksize, min((idx+1)*chunksize, n)) + currsize = sl.stop - sl.start if currsize != subpoly.size(): if hasattr(subpoly, 'resize'): subpoly.resize(currsize) else: subpoly.fill(QtCore.QPointF(), currsize) subarr = ndarray_from_qpolygonf(subpoly) - subarr[:, 0] = xchunk - subarr[:, 1] = ychunk + if backfill_idx is None: + subarr[:, 0] = x[sl] + subarr[:, 1] = y[sl] + else: + bfv = backfill_idx[sl] # view + subarr[:, 0] = x[bfv] + subarr[:, 1] = y[bfv] if subpath is None: subpath = QtGui.QPainterPath() subpath.addPolygon(subpoly) @@ -2116,16 +2125,20 @@ def arrayToQPath(x, y, connect='all', finiteCheck=True): arr = np.frombuffer(backstore, dtype=[('c', '>i4'), ('x', '>f8'), ('y', '>f8')], count=n, offset=4) - # Fill array with vertex values - arr['x'] = x - arr['y'] = y - + backfill_idx = None if finiteCheck: if isfinite is None: isfinite = np.isfinite(x) & np.isfinite(y) all_isfinite = np.all(isfinite) if not all_isfinite: - _fill_nonfinite(arr, isfinite) + backfill_idx = _compute_backfill_indices(isfinite) + + if backfill_idx is None: + arr['x'] = x + arr['y'] = y + else: + arr['x'] = x[backfill_idx] + arr['y'] = y[backfill_idx] # decide which points are connected by lines if connect == 'pairs': From e541c9ba1e03096af305d8436cde473105d8e772 Mon Sep 17 00:00:00 2001 From: KIU Shueng Chuan Date: Fri, 13 Aug 2021 18:53:58 +0800 Subject: [PATCH 5/6] hoist duplicate code --- pyqtgraph/functions.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/pyqtgraph/functions.py b/pyqtgraph/functions.py index 36d85531..92ad3250 100644 --- a/pyqtgraph/functions.py +++ b/pyqtgraph/functions.py @@ -1903,15 +1903,15 @@ def _arrayToQPath_all(x, y, finiteCheck): numchunks = (n + chunksize - 1) // chunksize minchunks = 3 + backfill_idx = None + if finiteCheck and not all_isfinite: + backfill_idx = _compute_backfill_indices(isfinite) + if numchunks < minchunks: # too few chunks, batching would be a pessimization poly = create_qpolygonf(n) arr = ndarray_from_qpolygonf(poly) - backfill_idx = None - if finiteCheck and not all_isfinite: - backfill_idx = _compute_backfill_indices(isfinite) - if backfill_idx is None: arr[:, 0] = x arr[:, 1] = y @@ -1925,10 +1925,6 @@ def _arrayToQPath_all(x, y, finiteCheck): path.addPolygon(poly) return path - backfill_idx = None - if finiteCheck and not all_isfinite: - backfill_idx = _compute_backfill_indices(isfinite) - # at this point, we have numchunks >= minchunks path = QtGui.QPainterPath() From 0cd926ff8ea38607bfddf73bc99a618bd59bfef7 Mon Sep 17 00:00:00 2001 From: KIU Shueng Chuan Date: Sat, 14 Aug 2021 00:00:59 +0800 Subject: [PATCH 6/6] merge finiteCheck branches --- pyqtgraph/functions.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/pyqtgraph/functions.py b/pyqtgraph/functions.py index 92ad3250..29992352 100644 --- a/pyqtgraph/functions.py +++ b/pyqtgraph/functions.py @@ -1895,18 +1895,16 @@ def _arrayToQPath_all(x, y, finiteCheck): if n == 0: return QtGui.QPainterPath() + backfill_idx = None if finiteCheck: isfinite = np.isfinite(x) & np.isfinite(y) - all_isfinite = np.all(isfinite) + if not np.all(isfinite): + backfill_idx = _compute_backfill_indices(isfinite) chunksize = 10000 numchunks = (n + chunksize - 1) // chunksize minchunks = 3 - backfill_idx = None - if finiteCheck and not all_isfinite: - backfill_idx = _compute_backfill_indices(isfinite) - if numchunks < minchunks: # too few chunks, batching would be a pessimization poly = create_qpolygonf(n)