Source code for fastplotlib.widgets.nd_widget._ndw_subplot

import warnings
from collections.abc import Callable
from typing import Any, Literal, Sequence, Hashable

import numpy as np
from numpy.typing import ArrayLike

from ... import (
    ScatterCollection,
    ScatterStack,
    LineCollection,
    LineStack,
    ImageGraphic,
)
from ...layouts import Subplot
from ...utils import ArrayProtocol, enums
from . import NDImageSlicer, NDImage, NDPositions, NDTimeseries, NDVectors
from ._nd_positions._nd_positions import (
    NDPositionsSlicer,
    ColorsType,
    FeatureCallable,
    MarkersType,
    SizesType,
)
from ._index import AutoRangeContinuous
from ._video import VideoSlicer
from ._base import NDGraphic, WindowFuncCallable


[docs] class NDWSubplot: """ Entry point for adding ``NDGraphic`` objects to a subplot of an ``NDWidget``. Accessed via ``ndw[row, col]`` or ``ndw["subplot_name"]``. Each ``add_nd_<...>`` method constructs the appropriate ``NDGraphic``, registers it with the parent ``ReferenceIndex``, appends it to this subplot and returns the ``NDGraphic`` instance to the user. Note: ``NDWSubplot`` is not meant to be constructed directly, it only exists as part of an ``NDWidget`` """ def __init__(self, ndw, subplot: Subplot): self.ndw = ndw self._subplot = subplot self._nd_graphics = list() @property def subplot(self) -> Subplot: """The ``Subplot`` of the ``NDWidget`` figure that this ``NDWSubplot`` adds graphics to""" return self._subplot @property def nd_graphics(self) -> tuple[NDGraphic]: """all the NDGraphic instance in this subplot""" return tuple(self._nd_graphics) def __getitem__(self, key) -> NDGraphic: # get a specific NDGraphic by index or name if isinstance(key, (int, np.integer)): return self.nd_graphics[key] for g in self.nd_graphics: if g.name == key: return g else: raise KeyError(f"NDGraphc with given key not found: {key}")
[docs] def delete_nd_graphic(self, ndg: NDGraphic): """ Delete an NDGraphic from the subplot .. important:: Not implemented yet. """ raise NotImplementedError("Not yet implemented")
# TODO: verify that this actually garbage collects, and shut down the # slicer's thread pool with `ndg.slicer.close()` # del ndg.data # self.subplot.delete_graphic(ndg.graphic) # self._nd_graphics.remove(ndg) # # del ndg def _check_slider_dims( self, dims: Sequence[Hashable], display_dims: Sequence[Hashable], data: ArrayProtocol | None, positions: bool = False, ): """ Make sure every slider (non-spatial) dim of a graphic being added has a reference range. A dim without one gets an ``AutoRangeContinuous`` sized to the data, an existing ``AutoRangeContinuous`` is grown to fit, and an explicit range is left untouched. """ if data is None: # size is unknown, an explicit range is still required return if not isinstance(data, ArrayProtocol): return dims = tuple(dims) slider_dims = set(dims) - set(display_dims) if positions: # the datapoints `p` axis is a spatial dim that also needs a reference range slider_dims.add(display_dims[1]) for dim in slider_dims: size = data.shape[dims.index(dim)] if dim not in self.ndw.indices.dims: warnings.warn( f"No reference range specified for non-spatial dim '{dim}', " f"auto-generating an `AutoRangeContinuous(0, {size}, 1)`." ) self.ndw.indices.push_dims({dim: AutoRangeContinuous(0, size, 1)}) elif isinstance(self.ndw.indices.ref_ranges[dim], AutoRangeContinuous): # grow the existing auto range to fit this array self.ndw.indices.ref_ranges[dim].stop = max( self.ndw.indices.ref_ranges[dim].stop, size )
[docs] def add_nd_image( self, data: ArrayProtocol | None, dims: Sequence[str], display_dims: ( tuple[str, str] | tuple[str, str, str] ), # must be in order! [rows, cols] | [z, rows, cols] rgb_dim: str | None = None, window_funcs: dict[ str, tuple[WindowFuncCallable | None, int | float | None] ] = None, window_order: tuple[str, ...] = None, spatial_func: Callable[[ArrayProtocol], ArrayProtocol] = None, compute_histogram: bool = True, clim_quantiles: tuple[float, float] | None = None, slider_maps: dict[str, Callable[[Any], int] | ArrayLike] = None, slicer_type: type[NDImageSlicer] = NDImageSlicer, colorspace: Literal[ "srgb", "tex-srgb", "physical", "yuv420p", "yuv444p" ] = "srgb", colorrange: Literal["full", "limited"] = "full", name: str = None, graphic_kwargs: dict = None, ) -> NDImage: """ Add an n-dimensional image or volume to this subplot. Every dim that is not listed in ``display_dims`` becomes a slider dim. Parameters ---------- data: ArrayProtocol or None n-dimensional image data, must have 2 or more dims. Pass ``None`` to create the ``NDImage`` without a graphic and set the data later using ``nd_image.data``, the slider dims then require an explicit reference range in the ``NDWidget``. dims: Sequence[str] name for every dim of ``data``, in order. They do not need to be in display order, ex: an array whose dims are ``("col", "depth", "row", "time")`` with ``display_dims`` of ``("row", "col")``. display_dims: tuple[str, str] | tuple[str, str, str] The 2 or 3 spatial dims **in display order**, which also determines the graphic used for rendering: * ``(rows, cols)``, a 2D grayscale ``ImageGraphic`` * ``(rows, cols, rgb_dim)``, a 2D RGB(A) ``ImageGraphic`` * ``(z, rows, cols)``, a 3D ``ImageVolumeGraphic`` rgb_dim: str, optional Name of the RGB(A) dim, if present. It must be listed in ``display_dims`` and be of size 3 or 4. window_funcs: dict[str, tuple[WindowFuncCallable | None, int | float | None]], optional Per-slider-dim window functions applied around the current slider position, ex: ``{"time": (np.mean, 2.5)}``. Each value is a ``(func, window_size)`` pair where: * *func* must accept ``axis: int`` and ``keepdims: bool`` kwargs (ex: ``np.mean``, ``np.max``). It **must** return an array that has the same dims as the input, therefore the size of any dim along which it was applied should reduce to ``1``. These dims must not be removed by the window func. * *window_size* is in reference-space units (ex: 2.5 seconds). window_order: tuple[str, ...], optional Order in which the window functions are applied across dims. Only dims listed here have their window function applied, ``window_funcs`` are ignored for any dim not specified in ``window_order``. spatial_func: Callable[[ArrayProtocol], ArrayProtocol], optional A function applied to the spatial slice *after* the window funcs, right before rendering. It is given the slice in ``display_dims`` order, i.e. the array as it is rendered, and must return an array with those same dims. compute_histogram: bool, default ``True`` Estimate a histogram of the data and display an ``ImguiColorbar`` on the right edge of the subplot, which is used to interactively set vmin, vmax. Disable if random access of the data is not blazing-fast (ex: data that uses video codecs), or if a histogram is not useful for this data. clim_quantiles: (float, float), optional ``(low, high)`` quantiles of the histogram, within ``[0, 1]``, used as vmin, vmax. Requires ``compute_histogram=True``, overrides any passed vmin, vmax in ``graphic_kwargs``. The limits are recomputed whenever the histogram is, so they follow the data. slider_maps: dict mapping dim_name -> Callable, an ArrayLike, or None, optional Per-slider-dim mapping from reference-space values to local array indices. An array of reference values may be given instead of a callable, ``searchsorted`` is then used as the transform (ex: a timestamps array). Any dim without a transform uses the identity mapping, i.e. the current reference value is rounded to the nearest integer and used as the array index. slicer_type: type[NDImageSlicer], default ``NDImageSlicer`` ``NDImageSlicer`` subclass that manages the data and produces the data slices. colorspace: "srgb" | "tex-srgb" | "physical" | "yuv420p" | "yuv444p", default "srgb" Colorspace in which to interpret the data. The RGB colorspaces are rendered using an ``ImageGraphic`` or ``ImageVolumeGraphic``, see :class:`.ImageGraphic` for their meaning. The YUV colorspaces are rendered using an ``ImageYUVGraphic``, see :class:`.ImageYUVGraphic`. colorrange: "full" | "limited", default "full" Used only for the YUV colorspaces, see :class:`.ImageYUVGraphic`. name: str, optional Name for this ``NDGraphic``, used to retrieve it with ``nd_subplot[name]``. graphic_kwargs: dict, optional passed to the underlying image graphic, ex: ``{"cmap": "viridis", "interpolation": "linear"}`` Returns ------- NDImage """ self._check_slider_dims(dims, display_dims, data) nd = NDImage( self.ndw.indices, nd_subplot=self, data=data, dims=dims, display_dims=display_dims, rgb_dim=rgb_dim, window_funcs=window_funcs, window_order=window_order, spatial_func=spatial_func, compute_histogram=compute_histogram, clim_quantiles=clim_quantiles, slider_maps=slider_maps, slicer_type=slicer_type, colorspace=colorspace, colorrange=colorrange, name=name, graphic_kwargs=graphic_kwargs, ) self._nd_graphics.append(nd) return nd
[docs] def add_video( self, data: ArrayProtocol | None, dims: Sequence[str], display_dims: tuple[str, str] | tuple[str, str, str], rgb_dim: str | None = None, colorspace: enums.ColorspacesYUV | enums.ColorspacesRGB = "yuv420p", colorrange: enums.ColorRange = "limited", slicer_type: NDImageSlicer = VideoSlicer, window_funcs: dict[ str, tuple[WindowFuncCallable | None, int | float | None] ] = None, window_order: tuple[str, ...] = None, spatial_func: Callable[[ArrayProtocol], ArrayProtocol] = None, slider_maps: dict[str, Callable[[Any], int] | ArrayLike] = None, name: str = None, graphic_kwargs: dict = None, ) -> NDImage: """ Add a video to this subplot. This is usually what you want for video data. Videos are usually stored in a YUV colorspace, and sending the YUV planes to the GPU is much faster than converting each frame to RGB and copying it into an sRGB texture. We strongly recommend using ``asyncvideo`` for the ``data`` object, it is the most efficient async video reader that we know of for visualization purposes: https://pypi.org/project/asyncvideo/ Same as :meth:`add_nd_image` but uses a :class:`VideoSlicer` and YUV defaults. The ``VideoSlicer`` reads the frame at the current index directly, it does not apply ``window_funcs``. Parameters ---------- data: ArrayProtocol or None video data, an object that decodes frames on demand, ex: an ``asyncvideo`` reader. Pass ``None`` to create the ``NDImage`` without a graphic and set the data later using ``nd_image.data``, the slider dims then require an explicit reference range in the ``NDWidget``. dims: Sequence[str] name for every dim of ``data``, in order. They do not need to be in display order. display_dims: tuple[str, str] | tuple[str, str, str] The 2 or 3 spatial dims **in display order**, see :meth:`add_nd_image`. rgb_dim: str, optional Name of the RGB(A) dim, if present. It must be listed in ``display_dims`` and be of size 3 or 4. colorspace: "yuv420p" | "yuv444p" | "srgb" | "tex-srgb" | "physical", default "yuv420p" Colorspace in which to interpret the data. The YUV colorspaces are rendered using an ``ImageYUVGraphic``, see :class:`.ImageYUVGraphic`. The RGB colorspaces are rendered using an ``ImageGraphic`` or ``ImageVolumeGraphic``, see :class:`.ImageGraphic`. colorrange: "full" | "limited", default "limited" Used only for the YUV colorspaces, see :class:`.ImageYUVGraphic`. Most videos use "limited". slicer_type: type[NDImageSlicer], default ``VideoSlicer`` ``NDImageSlicer`` subclass that manages the data and produces the data slices. window_funcs: dict[str, tuple[WindowFuncCallable | None, int | float | None]], optional Per-slider-dim window functions, see :meth:`add_nd_image`. Ignored by the default ``VideoSlicer``. window_order: tuple[str, ...], optional Order in which the window functions are applied across dims. Ignored by the default ``VideoSlicer``. spatial_func: Callable[[ArrayProtocol], ArrayProtocol], optional A function applied to the spatial slice right before rendering. It is given the frame in ``display_dims`` order, i.e. the array as it is rendered, and must return an array with those same dims. slider_maps: dict mapping dim_name -> Callable, an ArrayLike, or None, optional Per-slider-dim mapping from reference-space values to local array indices, ex: an array of frame timestamps to map seconds onto frame indices. See :meth:`add_nd_image`. name: str, optional Name for this ``NDGraphic``, used to retrieve it with ``nd_subplot[name]``. graphic_kwargs: dict, optional passed to the underlying image graphic Returns ------- NDImage """ return self.add_nd_image( data=data, dims=dims, display_dims=display_dims, rgb_dim=rgb_dim, colorspace=colorspace, colorrange=colorrange, slicer_type=slicer_type, window_funcs=window_funcs, window_order=window_order, spatial_func=spatial_func, compute_histogram=False, slider_maps=slider_maps, name=name, graphic_kwargs=graphic_kwargs, )
[docs] def add_nd_vectors( self, data: ArrayProtocol | None, dims: Sequence[str], display_dims: tuple[str, str, str], window_funcs: dict[ str, tuple[WindowFuncCallable | None, int | float | None] ] = None, window_order: tuple[str, ...] = None, spatial_func: Callable[[ArrayProtocol], ArrayProtocol] = None, slider_maps: dict[str, Callable[[Any], int] | ArrayLike] = None, name: str = None, graphic_kwargs: dict = None, ) -> NDVectors: """ Add n-dimensional vectors to this subplot, similar to matplotlib quiver. Every dim that is not listed in ``display_dims`` becomes a slider dim. Parameters ---------- data: ArrayProtocol or None n-dimensional vector data of shape ``[..., n_vectors, 2, 2]`` or ``[..., n_vectors, 2, 3]``, where ``data[..., 0, :]`` are the vector positions and ``data[..., 1, :]`` are the vector directions. Pass ``None`` to create the ``NDVectors`` without a graphic and set the data later using ``nd_vectors.data``, the slider dims then require an explicit reference range in the ``NDWidget``. dims: Sequence[str] name for every dim of ``data``, in order. They do not need to be in display order. display_dims: tuple[str, str, str] The 3 spatial dims **in order**: ``(n_vectors, positions_and_directions, xy(z))``. The positions/directions dim must be of size 2 and the coordinate dim of size 2 or 3. window_funcs: dict[str, tuple[WindowFuncCallable | None, int | float | None]], optional Per-slider-dim window functions applied around the current slider position, ex: ``{"time": (np.mean, 2.5)}``. Each value is a ``(func, window_size)`` pair where: * *func* must accept ``axis: int`` and ``keepdims: bool`` kwargs (ex: ``np.mean``, ``np.max``). It **must** return an array that has the same dims as the input, therefore the size of any dim along which it was applied should reduce to ``1``. These dims must not be removed by the window func. * *window_size* is in reference-space units (ex: 2.5 seconds). window_order: tuple[str, ...], optional Order in which the window functions are applied across dims. Only dims listed here have their window function applied, ``window_funcs`` are ignored for any dim not specified in ``window_order``. spatial_func: Callable[[ArrayProtocol], ArrayProtocol], optional A function applied to the spatial slice *after* the window funcs, right before rendering. It is given the slice in ``display_dims`` order, i.e. the array as it is rendered, and must return an array with those same dims. slider_maps: dict mapping dim_name -> Callable, an ArrayLike, or None, optional Per-slider-dim mapping from reference-space values to local array indices. An array of reference values may be given instead of a callable, ``searchsorted`` is then used as the transform (ex: a timestamps array). Any dim without a transform uses the identity mapping, i.e. the current reference value is rounded to the nearest integer and used as the array index. name: str, optional Name for this ``NDGraphic``, used to retrieve it with ``nd_subplot[name]``. graphic_kwargs: dict, optional passed to the underlying :class:`.VectorsGraphic`, ex: ``{"color": "cyan", "size": 0.5}`` Returns ------- NDVectors """ self._check_slider_dims(dims, display_dims, data) nd = NDVectors( self.ndw.indices, nd_subplot=self, data=data, dims=dims, display_dims=display_dims, window_funcs=window_funcs, window_order=window_order, spatial_func=spatial_func, slider_maps=slider_maps, name=name, graphic_kwargs=graphic_kwargs, ) self._nd_graphics.append(nd) return nd
[docs] def add_nd_scatter( self, data: ArrayProtocol | None, dims: Sequence[str], display_dims: tuple[str, str, str], *args, slicer: type[NDPositionsSlicer] = NDPositionsSlicer, display_window: int | float | None = 10, window_funcs: dict[ str, tuple[WindowFuncCallable | None, int | float | None] ] = None, window_order: tuple[str, ...] = None, spatial_func: Callable[[ArrayProtocol], ArrayProtocol] = None, slider_maps: dict[str, Callable[[Any], int] | ArrayLike] = None, max_display_datapoints: int | None = 1_000, datapoints_window_func: tuple[Callable, str, int | float] | None = None, colors: ColorsType = None, cmap: str | Sequence[str] = None, cmap_transform: np.ndarray | FeatureCallable = None, cmap_range: tuple[float, float] = None, sizes: SizesType = None, markers: MarkersType = None, name: str = None, graphic_kwargs: dict = None, slicer_kwargs: dict = None, ) -> NDPositions: """ Add n-dimensional positional data to this subplot, rendered as a ``ScatterCollection``. Every dim that is not listed in ``display_dims`` becomes a slider dim. The datapoints dim, ``p``, is both a spatial dim and a slider dim, it is windowed by ``display_window`` and ``datapoints_window_func`` rather than by ``window_funcs``. Parameters ---------- data: ArrayProtocol or None n-dimensional positional data. Ex: an array of shape ``[n_trials, n_scatters, n_points, 2]`` with ``dims`` of ``("trial", "scatter", "point", "xy")`` and ``display_dims`` of ``("scatter", "point", "xy")``. Pass ``None`` to create the ``NDPositions`` without a graphic and set the data later using ``nd_positions.data``, the slider dims then require an explicit reference range in the ``NDWidget``. dims: Sequence[str] name for every dim of ``data``, in order. display_dims: tuple[str, str, str] The 3 spatial dims **in display order**: ``(n_graphics, p, <value dim>)``, i.e. the number of scatters in the collection, the number of datapoints ``p`` in each of them, and the value dim which holds the xy or xyz coordinate and must be of size 2 or 3. The dims do not need to be in this order in the array, the data slice is transposed into display order. args extra positional arguments passed to the ``slicer`` constructor. slicer: type[NDPositionsSlicer], default ``NDPositionsSlicer`` ``NDPositionsSlicer`` subclass that manages the data and produces the data slices. display_window: int, float or None, default 10 Size of the window of the ``p`` dim to render, in the reference units of that dim, centered on its current index. Use ``None`` to render every datapoint, or ``0`` to render only the datapoint at the current index. This is what makes out-of-core rendering possible, i.e. rendering a window of a dataset that is larger than GPU VRAM. window_funcs: dict[str, tuple[WindowFuncCallable | None, int | float | None]], optional Per-slider-dim window functions applied around the current slider position, ex: ``{"trial": (np.mean, 5)}``. Each value is a ``(func, window_size)`` pair where: * *func* must accept ``axis: int`` and ``keepdims: bool`` kwargs (ex: ``np.mean``, ``np.max``). It **must** return an array that has the same dims as the input, therefore the size of any dim along which it was applied should reduce to ``1``. These dims must not be removed by the window func. * *window_size* is in reference-space units. Not used for the ``p`` dim, see ``datapoints_window_func``. window_order: tuple[str, ...], optional Order in which the window functions are applied across dims. Only dims listed here have their window function applied, ``window_funcs`` are ignored for any dim not specified in ``window_order``. spatial_func: Callable[[ArrayProtocol], ArrayProtocol], optional A function applied to the spatial slice *after* the window funcs, right before rendering. It is given the slice in ``display_dims`` order, i.e. the array as it is rendered, and must return an array with those same dims. slider_maps: dict[str, Callable[[Any], int] | ArrayLike], optional Per-slider-dim mapping from reference-space values to local array indices. An array of reference values may be given instead of a Callable, ``searchsorted`` is then used as the transform (ex: a timestamps array). Any dim without a transform uses the identity mapping, i.e. the current reference value is rounded to the nearest integer and used as the array index. max_display_datapoints: int | None, default 1_000 Maximum number of datapoints to render per graphic. The step size of the display window slice is set from this using floor division. ``None`` renders every datapoint in the window, with no decimation. Neither ``None`` nor a very large value is recommended: the entire window is then read into RAM and uploaded, which is slow for a large window over a large array. datapoints_window_func: tuple[Callable, str, int | float], optional Window function applied along the ``p`` dim after the display window has been taken, as ``(func, apply_dims, window_size)`` where: * *func* must accept an ``axis: int`` kwarg (ex: ``np.mean``, ``np.max``). It is given a sliding window view of the data and is reduced along the window axis. * *apply_dims* names the coordinates of the value dim to apply it to, one of ``"all", "x", "y", "z", "xy", "xz", "yz", "xyz"``. Coordinates that are not named are passed through unchanged. * *window_size* is in the reference units of the ``p`` dim. It is mapped to array indices, clamped to a minimum of 3, and rounded up to an odd size. If used, ``display_window`` is approximate and not exact due to padding from the window size. colors: str | Sequence[str] | np.ndarray | FeatureCallable, optional Colors of the scatters. Mutually exclusive with ``cmap``, setting one clears the other. * static, a single color for every graphic, ex: ``"cyan"`` or an RGBA sequence of 4 floats * static, one color per graphic, ``[n_graphics]`` of str or ``[n_graphics, 4]`` RGBA * windowed, one color per datapoint, ``[n_graphics, p, 4]`` RGBA * windowed, a ``FeatureCallable`` cmap: str | Sequence[str], optional Colormap applied to the scatters, always static. A single name for every graphic, or an iterable of ``[n_graphics]`` names for a colormap per graphic. Mutually exclusive with ``colors``. cmap_transform: np.ndarray | FeatureCallable, optional Values that the colormap colors are mapped from. * static, one value per graphic, ``[n_graphics]``, so each graphic gets a single color * windowed, one value per datapoint, ``[n_graphics, p]`` * windowed, a ``FeatureCallable`` cmap_range: (float, float) | np.ndarray, optional The (min, max) of ``cmap_transform`` mapped onto the colormap, or ``[n_graphics, 2]`` for a range per graphic. A windowed array ``cmap_transform`` defaults to its own (min, max) over the full ``p`` dim, so the display window keeps its position within the colormap. A ``FeatureCallable`` transform requires an explicit range, its full range is not knowable without evaluating it everywhere. sizes: float | Sequence[float] | np.ndarray | FeatureCallable, optional Size of the scatter points. * static, a single size for every graphic, or ``[n_graphics]`` sizes for one size per graphic * windowed, one size per datapoint, ``[n_graphics, p]`` * windowed, a ``FeatureCallable`` markers: str | Sequence[str] | np.ndarray | FeatureCallable, optional Marker shape of the scatter points. * static, a single marker for every graphic, or ``[n_graphics]`` markers for one per graphic * windowed, one marker per datapoint, ``[n_graphics, p]`` * windowed, a ``FeatureCallable`` name: str, optional Name for this ``NDGraphic``, used to retrieve it with ``nd_subplot[name]``. graphic_kwargs: dict, optional passed to the underlying ``ScatterCollection`` slicer_kwargs: dict, optional passed to the ``slicer`` constructor. Returns ------- NDPositions Notes ----- Each of the other graphic features is either *windowed* or *static*, decided from the value itself: * **windowed**: a ``FeatureCallable``, or an array whose axis 1 spans the ``p`` dim. It is re-sliced with the same display window slice as the data on every update, so the feature carries a value per displayed datapoint. An array **must** span the **full** ``p`` dim of the data, i.e. ``[n_graphics, p, <value dim>]``, since it is indexed with an index into the full ``p`` dim. A ``FeatureCallable`` is passed the data slice and that display window slice, and returns the feature values for the displayed datapoints. * **static**: anything else. It is set once on the collection, ex: a single value for every graphic, ``[n_graphics]`` values for one per graphic, or an iterator of per-graphic values such as ``itertools.cycle(["jet", "viridis"])``. """ self._check_slider_dims(dims, display_dims, data, positions=True) nd = NDPositions( self.ndw.indices, self, data, dims, display_dims, *args, graphic_type=ScatterCollection, slicer=slicer, display_window=display_window, window_funcs=window_funcs, window_order=window_order, spatial_func=spatial_func, slider_maps=slider_maps, max_display_datapoints=max_display_datapoints, datapoints_window_func=datapoints_window_func, colors=colors, cmap=cmap, cmap_transform=cmap_transform, cmap_range=cmap_range, sizes=sizes, markers=markers, name=name, graphic_kwargs=graphic_kwargs, slicer_kwargs=slicer_kwargs, ) self._nd_graphics.append(nd) return nd
[docs] def add_nd_timeseries( self, data: ArrayProtocol | None, dims: Sequence[str], display_dims: tuple[str, str, str], *args, graphic_type: type[ LineCollection | LineStack | ScatterCollection | ScatterStack | ImageGraphic ] = LineStack, x_range_mode: Literal["fixed", "auto"] | None = "auto", slicer: type[NDPositionsSlicer] = NDPositionsSlicer, display_window: int | float | None = 10, window_funcs: dict[ str, tuple[WindowFuncCallable | None, int | float | None] ] = None, window_order: tuple[str, ...] = None, spatial_func: Callable[[ArrayProtocol], ArrayProtocol] = None, slider_maps: dict[str, Callable[[Any], int] | ArrayLike] = None, max_display_datapoints: int | None = 1_000, datapoints_window_func: tuple[Callable, str, int | float] | None = None, colors: ColorsType = None, cmap: str | Sequence[str] = None, cmap_transform: np.ndarray | FeatureCallable = None, cmap_range: tuple[float, float] = None, thickness: float | Sequence[float] = None, sizes: SizesType = None, markers: MarkersType = None, name: str = None, graphic_kwargs: dict = None, slicer_kwargs: dict = None, ) -> NDTimeseries: """ Add n-dimensional timeseries data to this subplot, where the ``p`` dim is a time-like x-axis. Every dim that is not listed in ``display_dims`` becomes a slider dim. The datapoints dim, ``p``, is both a spatial dim and a slider dim, it is windowed by ``display_window`` and ``datapoints_window_func`` rather than by ``window_funcs``. A ``LinearSelector`` that marks the current index of the ``p`` dim is added to the subplot. Dragging it sets that index in the ``ReferenceIndex``, so it drives every other graphic that uses this dim. Only one is created per subplot. Parameters ---------- data: ArrayProtocol or None n-dimensional timeseries data. The value dim holds the (x, y) of each datapoint, where x is the time-like coordinate. Ex: an array of shape ``[n_trials, n_traces, n_timepoints, 2]`` with ``dims`` of ``("trial", "trace", "time", "xy")`` and ``display_dims`` of ``("trace", "time", "xy")``. Pass ``None`` to create the ``NDTimeseries`` without a graphic and set the data later using ``nd_timeseries.data``, the slider dims then require an explicit reference range in the ``NDWidget``. dims: Sequence[str] name for every dim of ``data``, in order. display_dims: tuple[str, str, str] The 3 spatial dims **in display order**: ``(n_graphics, p, <value dim>)``, i.e. the number of traces in the collection, the number of datapoints ``p`` in each of them, and the value dim which holds the xy or xyz coordinate. A heatmap requires a value dim of size exactly 2. The dims do not need to be in this order in the array, the data slice is transposed into display order. args extra positional arguments passed to the ``slicer`` constructor. graphic_type: type[LineCollection | LineStack | ScatterCollection | ScatterStack | ImageGraphic], default ``LineStack`` The graphical representation used to display the data slice. ``ImageGraphic`` renders the traces as a heatmap, one row per trace, where the color represents the y coordinate. The x coordinates are applied as the offset and scale of the image, and the y values are interpolated onto a uniform x grid if the x sampling is not uniform. x_range_mode: "fixed" | "auto" | None, default "auto" How the camera x-range is coupled to the ``p`` dim. * ``None``: the camera is left alone. * ``"fixed"``: the x-range is set from ``display_window``, centered on the current ``p`` index, on every update. * ``"auto"``: as ``"fixed"``, and the camera x-range is also polled on every render. Panning or zooming then sets ``display_window`` to the new width and the ``p`` index to the new center, with a lower bound of 3 datapoints on the width. Forced to ``None`` when ``display_window`` is ``None``. slicer: type[NDPositionsSlicer], default ``NDPositionsSlicer`` ``NDPositionsSlicer`` subclass that manages the data and produces the data slices. display_window: int, float or None, default 10 Size of the window of the ``p`` dim to render, in the reference units of that dim, centered on its current index. Use ``None`` to render every datapoint, which also forces ``x_range_mode`` to ``None``. This is what makes out-of-core rendering possible, i.e. rendering a window of a dataset that is larger than GPU VRAM. window_funcs: dict[str, tuple[WindowFuncCallable | None, int | float | None]], optional Per-slider-dim window functions applied around the current slider position, ex: ``{"trial": (np.mean, 5)}``. Each value is a ``(func, window_size)`` pair where: * *func* must accept ``axis: int`` and ``keepdims: bool`` kwargs (ex: ``np.mean``, ``np.max``). It **must** return an array that has the same dims as the input, therefore the size of any dim along which it was applied should reduce to ``1``. These dims must not be removed by the window func. * *window_size* is in reference-space units. Not used for the ``p`` dim, see ``datapoints_window_func``. window_order: tuple[str, ...], optional Order in which the window functions are applied across dims. Only dims listed here have their window function applied, ``window_funcs`` are ignored for any dim not specified in ``window_order``. spatial_func: Callable[[ArrayProtocol], ArrayProtocol], optional A function applied to the spatial slice *after* the window funcs, right before rendering. It is given the slice in ``display_dims`` order, i.e. the array as it is rendered, and must return an array with those same dims. slider_maps: dict[str, Callable[[Any], int] | ArrayLike], optional Per-slider-dim mapping from reference-space values to local array indices. An array of reference values may be given instead of a Callable, ``searchsorted`` is then used as the transform. The transform for the ``p`` dim is typically the array of x values, ex: a timestamps array, so the slider is in seconds rather than sample indices. Any dim without a transform uses the identity mapping, i.e. the current reference value is rounded to the nearest integer and used as the array index. max_display_datapoints: int | None, default 1_000 Maximum number of datapoints to render per graphic. The step size of the display window slice is set from this using floor division. ``None`` renders every datapoint in the window, with no decimation. Neither ``None`` nor a very large value is recommended: the entire window is then read into RAM and uploaded, which is slow for a large window over a large array. datapoints_window_func: tuple[Callable, str, int | float], optional Window function applied along the ``p`` dim after the display window has been taken, as ``(func, apply_dims, window_size)`` where: * *func* must accept an ``axis: int`` kwarg (ex: ``np.mean``, ``np.max``). It is given a sliding window view of the data and is reduced along the window axis. * *apply_dims* names the coordinates of the value dim to apply it to, one of ``"all", "x", "y", "z", "xy", "xz", "yz", "xyz"``. Coordinates that are not named are passed through unchanged. * *window_size* is in the reference units of the ``p`` dim. It is mapped to array indices, clamped to a minimum of 3, and rounded up to an odd size. If used, ``display_window`` is approximate and not exact due to padding from the window size. colors: str | Sequence[str] | np.ndarray | FeatureCallable, optional Colors of the traces. Mutually exclusive with ``cmap``, setting one clears the other. * static, a single color for every graphic, ex: ``"cyan"`` or an RGBA sequence of 4 floats * static, one color per graphic, ``[n_graphics]`` of str or ``[n_graphics, 4]`` RGBA * windowed, one color per datapoint, ``[n_graphics, p, 4]`` RGBA * windowed, a ``FeatureCallable`` cmap: str | Sequence[str], optional Colormap applied to the traces, always static. A single name for every graphic, or an iterable of ``[n_graphics]`` names for a colormap per graphic. Mutually exclusive with ``colors``. It is the only feature that is carried over to the heatmap representation. cmap_transform: np.ndarray | FeatureCallable, optional Values that the colormap colors are mapped from. * static, one value per graphic, ``[n_graphics]``, so each graphic gets a single color * windowed, one value per datapoint, ``[n_graphics, p]`` * windowed, a ``FeatureCallable`` cmap_range: (float, float) | np.ndarray, optional The (min, max) of ``cmap_transform`` mapped onto the colormap, or ``[n_graphics, 2]`` for a range per graphic. A windowed array ``cmap_transform`` defaults to its own (min, max) over the full ``p`` dim, so the display window keeps its position within the colormap. A ``FeatureCallable`` transform requires an explicit range, its full range is not knowable without evaluating it everywhere. thickness: float | Sequence[float], optional Thickness of the lines, always static. A single value for every graphic, or ``[n_graphics]`` values for a thickness per graphic. sizes: float | Sequence[float] | np.ndarray | FeatureCallable, optional Size of the scatter points. * static, a single size for every graphic, or ``[n_graphics]`` sizes for one size per graphic * windowed, one size per datapoint, ``[n_graphics, p]`` * windowed, a ``FeatureCallable`` markers: str | Sequence[str] | np.ndarray | FeatureCallable, optional Marker shape of the scatter points. * static, a single marker for every graphic, or ``[n_graphics]`` markers for one per graphic * windowed, one marker per datapoint, ``[n_graphics, p]`` * windowed, a ``FeatureCallable`` name: str, optional Name for this ``NDGraphic``, used to retrieve it with ``nd_subplot[name]``. graphic_kwargs: dict, optional passed to the ``graphic_type`` constructor. slicer_kwargs: dict, optional passed to the ``slicer`` constructor. Returns ------- NDTimeseries Notes ----- Each of the other graphic features is either *windowed* or *static*, decided from the value itself: * **windowed**: a ``FeatureCallable``, or an array whose axis 1 spans the ``p`` dim. It is re-sliced with the same display window slice as the data on every update, so the feature carries a value per displayed datapoint. An array **must** span the **full** ``p`` dim of the data, i.e. ``[n_graphics, p, <value dim>]``, since it is indexed with an index into the full ``p`` dim. A ``FeatureCallable`` is passed the data slice and that display window slice, and returns the feature values for the displayed datapoints. * **static**: anything else. It is set once on the collection, ex: a single value for every graphic, ``[n_graphics]`` values for one per graphic, or an iterator of per-graphic values such as ``itertools.cycle(["jet", "viridis"])``. A feature the graphic type does not have is ignored, ex: ``thickness`` for scatters, ``markers`` for lines. The heatmap representation uses only ``cmap``. """ self._check_slider_dims(dims, display_dims, data, positions=True) nd = NDTimeseries( self.ndw.indices, self, data, dims, display_dims, *args, graphic_type=graphic_type, linear_selector=True, x_range_mode=x_range_mode, slicer=slicer, display_window=display_window, window_funcs=window_funcs, window_order=window_order, spatial_func=spatial_func, slider_maps=slider_maps, max_display_datapoints=max_display_datapoints, datapoints_window_func=datapoints_window_func, colors=colors, cmap=cmap, cmap_transform=cmap_transform, cmap_range=cmap_range, thickness=thickness, sizes=sizes, markers=markers, name=name, graphic_kwargs=graphic_kwargs, slicer_kwargs=slicer_kwargs, ) self._nd_graphics.append(nd) return nd
[docs] def add_nd_lines( self, data: ArrayProtocol | None, dims: Sequence[str], display_dims: tuple[str, str, str], *args, slicer: type[NDPositionsSlicer] = NDPositionsSlicer, display_window: int | float | None = 10, window_funcs: dict[ str, tuple[WindowFuncCallable | None, int | float | None] ] = None, window_order: tuple[str, ...] = None, spatial_func: Callable[[ArrayProtocol], ArrayProtocol] = None, slider_maps: dict[str, Callable[[Any], int] | ArrayLike] = None, max_display_datapoints: int | None = 1_000, datapoints_window_func: tuple[Callable, str, int | float] | None = None, colors: ColorsType = None, cmap: str | Sequence[str] = None, cmap_transform: np.ndarray | FeatureCallable = None, cmap_range: tuple[float, float] = None, thickness: float | Sequence[float] = None, name: str = None, graphic_kwargs: dict = None, slicer_kwargs: dict = None, ) -> NDPositions: """ Add n-dimensional positional data to this subplot, rendered as a ``LineCollection``. Every dim that is not listed in ``display_dims`` becomes a slider dim. The datapoints dim, ``p``, is both a spatial dim and a slider dim, it is windowed by ``display_window`` and ``datapoints_window_func`` rather than by ``window_funcs``. Parameters ---------- data: ArrayProtocol or None n-dimensional positional data. Ex: an array of shape ``[n_trials, n_keypoints, n_timepoints, 2]`` with ``dims`` of ``("trial", "keypoint", "time", "xy")`` and ``display_dims`` of ``("keypoint", "time", "xy")``. Pass ``None`` to create the ``NDPositions`` without a graphic and set the data later using ``nd_positions.data``, the slider dims then require an explicit reference range in the ``NDWidget``. dims: Sequence[str] name for every dim of ``data``, in order. display_dims: tuple[str, str, str] The 3 spatial dims **in display order**: ``(n_graphics, p, <value dim>)``, i.e. the number of lines in the collection, the number of datapoints ``p`` in each of them, and the value dim which holds the xy or xyz coordinate and must be of size 2 or 3. The dims do not need to be in this order in the array, the data slice is transposed into display order. args extra positional arguments passed to the ``slicer`` constructor. slicer: type[NDPositionsSlicer], default ``NDPositionsSlicer`` ``NDPositionsSlicer`` subclass that manages the data and produces the data slices. display_window: int, float or None, default 10 Size of the window of the ``p`` dim to render, in the reference units of that dim, centered on its current index. Use ``None`` to render every datapoint, or ``0`` to render only the datapoint at the current index. This is what makes out-of-core rendering possible, i.e. rendering a window of a dataset that is larger than GPU VRAM. window_funcs: dict[str, tuple[WindowFuncCallable | None, int | float | None]], optional Per-slider-dim window functions applied around the current slider position, ex: ``{"trial": (np.mean, 5)}``. Each value is a ``(func, window_size)`` pair where: * *func* must accept ``axis: int`` and ``keepdims: bool`` kwargs (ex: ``np.mean``, ``np.max``). It **must** return an array that has the same dims as the input, therefore the size of any dim along which it was applied should reduce to ``1``. These dims must not be removed by the window func. * *window_size* is in reference-space units. Not used for the ``p`` dim, see ``datapoints_window_func``. window_order: tuple[str, ...], optional Order in which the window functions are applied across dims. Only dims listed here have their window function applied, ``window_funcs`` are ignored for any dim not specified in ``window_order``. spatial_func: Callable[[ArrayProtocol], ArrayProtocol], optional A function applied to the spatial slice *after* the window funcs, right before rendering. It is given the slice in ``display_dims`` order, i.e. the array as it is rendered, and must return an array with those same dims. slider_maps: dict[str, Callable[[Any], int] | ArrayLike], optional Per-slider-dim mapping from reference-space values to local array indices. An array of reference values may be given instead of a Callable, ``searchsorted`` is then used as the transform (ex: a timestamps array). Any dim without a transform uses the identity mapping, i.e. the current reference value is rounded to the nearest integer and used as the array index. max_display_datapoints: int | None, default 1_000 Maximum number of datapoints to render per graphic. The step size of the display window slice is set from this using floor division. ``None`` renders every datapoint in the window, with no decimation. Neither ``None`` nor a very large value is recommended: the entire window is then read into RAM and uploaded, which is slow for a large window over a large array. datapoints_window_func: tuple[Callable, str, int | float], optional Window function applied along the ``p`` dim after the display window has been taken, as ``(func, apply_dims, window_size)`` where: * *func* must accept an ``axis: int`` kwarg (ex: ``np.mean``, ``np.max``). It is given a sliding window view of the data and is reduced along the window axis. * *apply_dims* names the coordinates of the value dim to apply it to, one of ``"all", "x", "y", "z", "xy", "xz", "yz", "xyz"``. Coordinates that are not named are passed through unchanged. * *window_size* is in the reference units of the ``p`` dim. It is mapped to array indices, clamped to a minimum of 3, and rounded up to an odd size. If used, ``display_window`` is approximate and not exact due to padding from the window size. colors: str | Sequence[str] | np.ndarray | FeatureCallable, optional Colors of the lines. Mutually exclusive with ``cmap``, setting one clears the other. * static, a single color for every graphic, ex: ``"cyan"`` or an RGBA sequence of 4 floats * static, one color per graphic, ``[n_graphics]`` of str or ``[n_graphics, 4]`` RGBA * windowed, one color per datapoint, ``[n_graphics, p, 4]`` RGBA * windowed, a ``FeatureCallable`` cmap: str | Sequence[str], optional Colormap applied to the lines, always static. A single name for every graphic, or an iterable of ``[n_graphics]`` names for a colormap per graphic. Mutually exclusive with ``colors``. cmap_transform: np.ndarray | FeatureCallable, optional Values that the colormap colors are mapped from. * static, one value per graphic, ``[n_graphics]``, so each graphic gets a single color * windowed, one value per datapoint, ``[n_graphics, p]`` * windowed, a ``FeatureCallable`` cmap_range: (float, float) | np.ndarray, optional The (min, max) of ``cmap_transform`` mapped onto the colormap, or ``[n_graphics, 2]`` for a range per graphic. A windowed array ``cmap_transform`` defaults to its own (min, max) over the full ``p`` dim, so the display window keeps its position within the colormap. A ``FeatureCallable`` transform requires an explicit range, its full range is not knowable without evaluating it everywhere. thickness: float | Sequence[float], optional Thickness of the lines, always static. A single value for every graphic, or ``[n_graphics]`` values for a thickness per graphic. name: str, optional Name for this ``NDGraphic``, used to retrieve it with ``nd_subplot[name]``. graphic_kwargs: dict, optional passed to the underlying ``LineCollection`` slicer_kwargs: dict, optional passed to the ``slicer`` constructor. Returns ------- NDPositions Notes ----- Each of the other graphic features is either *windowed* or *static*, decided from the value itself: * **windowed**: a ``FeatureCallable``, or an array whose axis 1 spans the ``p`` dim. It is re-sliced with the same display window slice as the data on every update, so the feature carries a value per displayed datapoint. An array **must** span the **full** ``p`` dim of the data, i.e. ``[n_graphics, p, <value dim>]``, since it is indexed with an index into the full ``p`` dim. A ``FeatureCallable`` is passed the data slice and that display window slice, and returns the feature values for the displayed datapoints. * **static**: anything else. It is set once on the collection, ex: a single value for every graphic, ``[n_graphics]`` values for one per graphic, or an iterator of per-graphic values such as ``itertools.cycle(["jet", "viridis"])``. """ self._check_slider_dims(dims, display_dims, data, positions=True) nd = NDPositions( self.ndw.indices, self, data, dims, display_dims, *args, graphic_type=LineCollection, slicer=slicer, display_window=display_window, window_funcs=window_funcs, window_order=window_order, spatial_func=spatial_func, slider_maps=slider_maps, max_display_datapoints=max_display_datapoints, datapoints_window_func=datapoints_window_func, colors=colors, cmap=cmap, cmap_transform=cmap_transform, cmap_range=cmap_range, thickness=thickness, name=name, graphic_kwargs=graphic_kwargs, slicer_kwargs=slicer_kwargs, ) self._nd_graphics.append(nd) return nd