diff --git a/piker/ui/_flows.py b/piker/ui/_flows.py index f8a4a4c0..d793b6d0 100644 --- a/piker/ui/_flows.py +++ b/piker/ui/_flows.py @@ -31,7 +31,6 @@ from typing import ( import msgspec import numpy as np -from numpy.lib import recfunctions as rfn import pyqtgraph as pg from PyQt5.QtGui import QPainterPath from PyQt5.QtCore import QLineF @@ -44,9 +43,21 @@ from .._profile import ( # ms_slower_then, ) from ._pathops import ( + by_index_and_key, + + # Plain OHLC renderer gen_ohlc_qpath, + + # OHLC -> line renderer ohlc_to_line, + update_ohlc_to_line, + ohlc_flat_to_xy, + + # step curve renderer to_step_format, + update_step_xy, + step_to_xy, + xy_downsample, ) from ._ohlc import ( @@ -75,55 +86,6 @@ log = get_logger(__name__) # flows: dict[str, np.ndarray] = {} -def update_ohlc_to_line( - src_shm: ShmArray, - array_key: str, - src_update: np.ndarray, - slc: slice, - ln: int, - first: int, - last: int, - is_append: bool, - -) -> np.ndarray: - - fields = ['open', 'high', 'low', 'close'] - return ( - rfn.structured_to_unstructured(src_update[fields]), - slc, - ) - - -def ohlc_flat_to_xy( - r: Renderer, - array: np.ndarray, - array_key: str, - vr: tuple[int, int], - -) -> tuple[ - np.ndarray, - np.nd.array, - str, -]: - # TODO: in the case of an existing ``.update_xy()`` - # should we be passing in array as an xy arrays tuple? - - # 2 more datum-indexes to capture zero at end - x_flat = r.x_data[r._xy_first:r._xy_last] - y_flat = r.y_data[r._xy_first:r._xy_last] - - # slice to view - ivl, ivr = vr - x_iv_flat = x_flat[ivl:ivr] - y_iv_flat = y_flat[ivl:ivr] - - # reshape to 1d for graphics rendering - y_iv = y_iv_flat.reshape(-1) - x_iv = x_iv_flat.reshape(-1) - - return x_iv, y_iv, 'all' - - def render_baritems( flow: Flow, graphics: BarItems, @@ -253,77 +215,6 @@ def render_baritems( ) -def update_step_xy( - src_shm: ShmArray, - array_key: str, - y_update: np.ndarray, - slc: slice, - ln: int, - first: int, - last: int, - is_append: bool, - -) -> np.ndarray: - - # for a step curve we slice from one datum prior - # to the current "update slice" to get the previous - # "level". - if is_append: - start = max(last - 1, 0) - end = src_shm._last.value - new_y = src_shm._array[start:end][array_key] - slc = slice(start, end) - - else: - new_y = y_update - - return ( - np.broadcast_to( - new_y[:, None], (new_y.size, 2), - ), - slc, - ) - - -def step_to_xy( - r: Renderer, - array: np.ndarray, - array_key: str, - vr: tuple[int, int], - -) -> tuple[ - np.ndarray, - np.nd.array, - str, -]: - - # 2 more datum-indexes to capture zero at end - x_step = r.x_data[r._xy_first:r._xy_last+2] - y_step = r.y_data[r._xy_first:r._xy_last+2] - - lasts = array[['index', array_key]] - last = lasts[array_key][-1] - y_step[-1] = last - - # slice out in-view data - ivl, ivr = vr - ys_iv = y_step[ivl:ivr+1] - xs_iv = x_step[ivl:ivr+1] - - # flatten to 1d - y_iv = ys_iv.reshape(ys_iv.size) - x_iv = xs_iv.reshape(xs_iv.size) - - # print( - # f'ys_iv : {ys_iv[-s:]}\n' - # f'y_iv: {y_iv[-s:]}\n' - # f'xs_iv: {xs_iv[-s:]}\n' - # f'x_iv: {x_iv[-s:]}\n' - # ) - - return x_iv, y_iv, 'all' - - class Flow(msgspec.Struct): # , frozen=True): ''' (Financial Signal-)Flow compound type which wraps a real-time @@ -786,20 +677,6 @@ class Flow(msgspec.Struct): # , frozen=True): g.update() -def by_index_and_key( - renderer: Renderer, - array: np.ndarray, - array_key: str, - vr: tuple[int, int], - -) -> tuple[ - np.ndarray, - np.ndarray, - np.ndarray, -]: - return array['index'], array[array_key], 'all' - - class Renderer(msgspec.Struct): flow: Flow @@ -1210,7 +1087,7 @@ class Renderer(msgspec.Struct): and do_append and not should_redraw ): - # print(f'{array_key} append len: {append_length}') + print(f'{array_key} append len: {append_length}') new_x = x_out[-append_length - 2:] # slice_to_head] new_y = y_out[-append_length - 2:] # slice_to_head] profiler('sliced append path') @@ -1236,6 +1113,7 @@ class Renderer(msgspec.Struct): profiler('generated append qpath') if use_fpath: + print(f'{self.flow.name}: FAST PATH') # an attempt at trying to make append-updates faster.. if fast_path is None: fast_path = append_path diff --git a/piker/ui/_pathops.py b/piker/ui/_pathops.py index bbdde19a..5e630570 100644 --- a/piker/ui/_pathops.py +++ b/piker/ui/_pathops.py @@ -19,10 +19,12 @@ Super fast ``QPainterPath`` generation related operator routines. """ from __future__ import annotations from typing import ( - # Optional, + Optional, + Callable, TYPE_CHECKING, ) +import msgspec import numpy as np from numpy.lib import recfunctions as rfn from numba import njit, float64, int64 # , optional @@ -42,6 +44,56 @@ if TYPE_CHECKING: from ._flows import Renderer +def by_index_and_key( + renderer: Renderer, + array: np.ndarray, + array_key: str, + vr: tuple[int, int], + +) -> tuple[ + np.ndarray, + np.ndarray, + np.ndarray, +]: + return array['index'], array[array_key], 'all' + + +class IncrementalFormatter(msgspec.Struct): + + shm: ShmArray + + # optional pre-graphics xy formatted data which + # is incrementally updated in sync with the source data. + allocate_xy_nd: Optional[Callable[ + [int, slice], + tuple[np.ndarray, np.nd.array] + ]] = None + + incr_update_xy_nd: Optional[Callable[ + [int, slice], None] + ] = None + + # default just returns index, and named array from data + format_xy_nd_to_1d: Callable[ + [np.ndarray, str], + tuple[np.ndarray] + ] = by_index_and_key + + x_nd: Optional[np.ndarray] = None + y_nd: Optional[np.ndarray] = None + + x_1d: Optional[np.ndarray] = None + y_1d: Optional[np.ndarray] = None + + # indexes which slice into the above arrays (which are allocated + # based on source data shm input size) and allow retrieving + # incrementally updated data. + # _xy_first: int = 0 + # _xy_last: int = 0 + xy_nd_start: int = 0 + xy_nd_end: int = 0 + + def xy_downsample( x, y, @@ -214,6 +266,55 @@ def ohlc_to_line( ) +def update_ohlc_to_line( + src_shm: ShmArray, + array_key: str, + src_update: np.ndarray, + slc: slice, + ln: int, + first: int, + last: int, + is_append: bool, + +) -> np.ndarray: + + fields = ['open', 'high', 'low', 'close'] + return ( + rfn.structured_to_unstructured(src_update[fields]), + slc, + ) + + +def ohlc_flat_to_xy( + r: Renderer, + array: np.ndarray, + array_key: str, + vr: tuple[int, int], + +) -> tuple[ + np.ndarray, + np.nd.array, + str, +]: + # TODO: in the case of an existing ``.update_xy()`` + # should we be passing in array as an xy arrays tuple? + + # 2 more datum-indexes to capture zero at end + x_flat = r.x_data[r._xy_first:r._xy_last] + y_flat = r.y_data[r._xy_first:r._xy_last] + + # slice to view + ivl, ivr = vr + x_iv_flat = x_flat[ivl:ivr] + y_iv_flat = y_flat[ivl:ivr] + + # reshape to 1d for graphics rendering + y_iv = y_iv_flat.reshape(-1) + x_iv = x_iv_flat.reshape(-1) + + return x_iv, y_iv, 'all' + + def to_step_format( shm: ShmArray, data_field: str, @@ -239,3 +340,74 @@ def to_step_format( # start y at origin level y_out[0, 0] = 0 return x_out, y_out + + +def update_step_xy( + src_shm: ShmArray, + array_key: str, + y_update: np.ndarray, + slc: slice, + ln: int, + first: int, + last: int, + is_append: bool, + +) -> np.ndarray: + + # for a step curve we slice from one datum prior + # to the current "update slice" to get the previous + # "level". + if is_append: + start = max(last - 1, 0) + end = src_shm._last.value + new_y = src_shm._array[start:end][array_key] + slc = slice(start, end) + + else: + new_y = y_update + + return ( + np.broadcast_to( + new_y[:, None], (new_y.size, 2), + ), + slc, + ) + + +def step_to_xy( + r: Renderer, + array: np.ndarray, + array_key: str, + vr: tuple[int, int], + +) -> tuple[ + np.ndarray, + np.nd.array, + str, +]: + + # 2 more datum-indexes to capture zero at end + x_step = r.x_data[r._xy_first:r._xy_last+2] + y_step = r.y_data[r._xy_first:r._xy_last+2] + + lasts = array[['index', array_key]] + last = lasts[array_key][-1] + y_step[-1] = last + + # slice out in-view data + ivl, ivr = vr + ys_iv = y_step[ivl:ivr+1] + xs_iv = x_step[ivl:ivr+1] + + # flatten to 1d + y_iv = ys_iv.reshape(ys_iv.size) + x_iv = xs_iv.reshape(xs_iv.size) + + # print( + # f'ys_iv : {ys_iv[-s:]}\n' + # f'y_iv: {y_iv[-s:]}\n' + # f'xs_iv: {xs_iv[-s:]}\n' + # f'x_iv: {x_iv[-s:]}\n' + # ) + + return x_iv, y_iv, 'all'