Move graphics compression routines to new module
parent
d737adb1b8
commit
b318ebc221
|
@ -0,0 +1,129 @@
|
||||||
|
# piker: trading gear for hackers
|
||||||
|
# Copyright (C) Tyler Goodlet (in stewardship for pikers)
|
||||||
|
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Affero General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
import numpy as np
|
||||||
|
from numba import (
|
||||||
|
jit, float64, optional, int64,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def downsample(
|
||||||
|
x: np.ndarray,
|
||||||
|
y: np.ndarray,
|
||||||
|
bins: int,
|
||||||
|
method: str = 'peak',
|
||||||
|
|
||||||
|
) -> tuple[np.ndarray, np.ndarray]:
|
||||||
|
'''
|
||||||
|
Downsample x/y data for lesser curve graphics gen.
|
||||||
|
|
||||||
|
The "peak" method is originally copied verbatim from
|
||||||
|
``pyqtgraph.PlotDataItem.getDisplayDataset()``.
|
||||||
|
|
||||||
|
'''
|
||||||
|
# py3.10 syntax
|
||||||
|
match method:
|
||||||
|
case 'peak':
|
||||||
|
ds = bins
|
||||||
|
n = len(x) // ds
|
||||||
|
x1 = np.empty((n, 2))
|
||||||
|
# start of x-values; try to select a somewhat centered point
|
||||||
|
stx = ds//2
|
||||||
|
x1[:] = x[stx:stx+n*ds:ds, np.newaxis]
|
||||||
|
x = x1.reshape(n*2)
|
||||||
|
y1 = np.empty((n, 2))
|
||||||
|
y2 = y[:n*ds].reshape((n, ds))
|
||||||
|
y1[:, 0] = y2.max(axis=1)
|
||||||
|
y1[:, 1] = y2.min(axis=1)
|
||||||
|
y = y1.reshape(n*2)
|
||||||
|
|
||||||
|
case '4px':
|
||||||
|
|
||||||
|
# Ex. from infinite on downsampling viewable graphics.
|
||||||
|
# "one thing i remembered about the binning - if you are
|
||||||
|
# picking a range within your timeseries the start and end bin
|
||||||
|
# should be one more bin size outside the visual range, then
|
||||||
|
# you get better visual fidelity at the edges of the graph"
|
||||||
|
# "i didn't show it in the sample code, but it's accounted for
|
||||||
|
# in the start and end indices and number of bins"
|
||||||
|
|
||||||
|
def build_subchart(
|
||||||
|
self,
|
||||||
|
subchart,
|
||||||
|
width, # width of screen?
|
||||||
|
chart_type,
|
||||||
|
lower, # x start?
|
||||||
|
upper, # x end?
|
||||||
|
xvals,
|
||||||
|
yvals
|
||||||
|
):
|
||||||
|
pts_per_pixel = len(xvals) / width
|
||||||
|
if pts_per_pixel > 1:
|
||||||
|
|
||||||
|
# this is mutated in-place
|
||||||
|
data = np.zeros((width, 4), yvals.dtype)
|
||||||
|
bins = np.zeros(width, xvals.dtype)
|
||||||
|
|
||||||
|
nb = subset_by_x(
|
||||||
|
xvals,
|
||||||
|
yvals,
|
||||||
|
bins,
|
||||||
|
data,
|
||||||
|
lower,
|
||||||
|
# this is scaling the x-range by
|
||||||
|
# the width of the screen?
|
||||||
|
(upper-lower)/float(width),
|
||||||
|
)
|
||||||
|
|
||||||
|
return x, y
|
||||||
|
|
||||||
|
|
||||||
|
@jit(nopython=True)
|
||||||
|
def subset_by_x(
|
||||||
|
|
||||||
|
xs: np.ndarray,
|
||||||
|
ys: np.ndarray,
|
||||||
|
bins: np.ndarray,
|
||||||
|
data: np.ndarray,
|
||||||
|
x_start: int,
|
||||||
|
step: float,
|
||||||
|
|
||||||
|
) -> int:
|
||||||
|
count = len(xs)
|
||||||
|
# nbins = len(bins)
|
||||||
|
bincount = 0
|
||||||
|
x_left = start
|
||||||
|
# Find the first bin
|
||||||
|
while xs[0] >= x_left + step:
|
||||||
|
x_left += step
|
||||||
|
bins[bincount] = x_left
|
||||||
|
data[bincount] = ys[0]
|
||||||
|
|
||||||
|
for i in range(count):
|
||||||
|
x = xs[i]
|
||||||
|
y = ys[i]
|
||||||
|
if x < x_left + step: # Interval is [bin, bin+1)
|
||||||
|
data[bincount, 1] = min(y, data[bincount, 1])
|
||||||
|
data[bincount, 2] = max(y, data[bincount, 2])
|
||||||
|
data[bincount, 3] = y
|
||||||
|
else:
|
||||||
|
# Find the next bin
|
||||||
|
while x >= x_left + step:
|
||||||
|
x_left += step
|
||||||
|
bincount += 1
|
||||||
|
bins[bincount] = x_left
|
||||||
|
data[bincount] = y
|
||||||
|
|
||||||
|
return bincount
|
|
@ -1,5 +1,5 @@
|
||||||
# piker: trading gear for hackers
|
# piker: trading gear for hackers
|
||||||
# Copyright (C) Tyler Goodlet (in stewardship for piker0)
|
# Copyright (C) Tyler Goodlet (in stewardship for pikers)
|
||||||
|
|
||||||
# This program is free software: you can redistribute it and/or modify
|
# This program is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU Affero General Public License as published by
|
# it under the terms of the GNU Affero General Public License as published by
|
||||||
|
@ -95,38 +95,6 @@ _line_styles: dict[str, int] = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def downsample(
|
|
||||||
x: np.ndarray,
|
|
||||||
y: np.ndarray,
|
|
||||||
bins: int,
|
|
||||||
method: str = 'peak',
|
|
||||||
|
|
||||||
) -> tuple[np.ndarray, np.ndarray]:
|
|
||||||
'''
|
|
||||||
Downsample x/y data for lesser curve graphics gen.
|
|
||||||
|
|
||||||
The "peak" method is originally copied verbatim from
|
|
||||||
``pyqtgraph.PlotDataItem.getDisplayDataset()``.
|
|
||||||
|
|
||||||
'''
|
|
||||||
match method:
|
|
||||||
case 'peak':
|
|
||||||
ds = bins
|
|
||||||
n = len(x) // ds
|
|
||||||
x1 = np.empty((n, 2))
|
|
||||||
# start of x-values; try to select a somewhat centered point
|
|
||||||
stx = ds//2
|
|
||||||
x1[:] = x[stx:stx+n*ds:ds, np.newaxis]
|
|
||||||
x = x1.reshape(n*2)
|
|
||||||
y1 = np.empty((n, 2))
|
|
||||||
y2 = y[:n*ds].reshape((n, ds))
|
|
||||||
y1[:, 0] = y2.max(axis=1)
|
|
||||||
y1[:, 1] = y2.min(axis=1)
|
|
||||||
y = y1.reshape(n*2)
|
|
||||||
|
|
||||||
return x, y
|
|
||||||
|
|
||||||
|
|
||||||
# TODO: got a feeling that dropping this inheritance gets us even more speedups
|
# TODO: got a feeling that dropping this inheritance gets us even more speedups
|
||||||
class FastAppendCurve(pg.PlotCurveItem):
|
class FastAppendCurve(pg.PlotCurveItem):
|
||||||
'''
|
'''
|
||||||
|
@ -305,8 +273,6 @@ class FastAppendCurve(pg.PlotCurveItem):
|
||||||
finiteCheck=False,
|
finiteCheck=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
path = self.path
|
|
||||||
|
|
||||||
# other merging ideas:
|
# other merging ideas:
|
||||||
# https://stackoverflow.com/questions/8936225/how-to-merge-qpainterpaths
|
# https://stackoverflow.com/questions/8936225/how-to-merge-qpainterpaths
|
||||||
if self._step_mode:
|
if self._step_mode:
|
||||||
|
|
Loading…
Reference in New Issue