Skip to content

NDWidget#971

Draft
kushalkolar wants to merge 113 commits intomainfrom
ndwidget
Draft

NDWidget#971
kushalkolar wants to merge 113 commits intomainfrom
ndwidget

Conversation

@kushalkolar
Copy link
Copy Markdown
Member

@kushalkolar kushalkolar commented Dec 25, 2025

Internals

RangeContinuous

Just a dataclass that stores the start, stop, and step of a reference range. A reference range is usually in scientific units, such as seconds/ms for time. Depth is another example of a reference range. A reference range just specifies the min and max of a dimension that will be used as a slider dimension. The imgui sliders use the reference range to determine the min, max, and step size of the UI elements (sliders, step-one button, etc.).

RangeDiscrete is mostly a placeholder for now, not fully implemented yet, waiting for a proper usecase. Genes might be an example? Session index for multi-session data could be another potential usecase.

Users must define a reference range for every dimension they want to use as a slider dimension.

TODO: Would be useful to have an automatic reference range that's generated for a slider dimension when it's not specified. For example, sometimes you just want to dump a bunch of calcium arrays and look at them, and they're already in the same time-space. Could have a simple auto-reference range mode that just sets the min and max for eacher slider dim based on the current arrays in the NDWidget.

ReferenceIndex

This manages the reference index for an NDWidget. It's the only place where indices should be set.

TODO: pushing/popping reference ranges.

NDProcessor

This manages n-dimensional data of any type. Subclasses must implement a get() method which takes a dict mapping dim names -> index for that dim. If specified, window functions are applied on slider dimensions. _get_slider_dims_indexer takes the indices dict and outputs a dict of dim_names -> slice. _apply_window_functions() uses these slices and applies the window functions. The final output after window functions has the same number of dims as the data, but with 1 element in any dimensions that were sliced (i.e. all slider dims, spatial dims remain untouched). In the end, the get() method must return an array that can be directly mapped to graphic data.

Key properties in an NDProcessor:

data - usually an xarray, but subclasses can manage data that is of any type. The NDProcessor NDPP_Pandas for positional data stored in a pandas dataframe is an example of this.

shape, ndim, dims, spatial_dims, slider_dims - self explanatory, dims are always named

window_funcs - window functions, dict {dim_name: (func, window_size)}. A window function must take axis and keep_dims as kwargs.
slider_dim_transforms - dict of functions for each slider dim that maps a reference range value to the local array index value. If a user doesn't provide a transform for a dim (for example, they just want to look at many arrays which are already in the same time-space, such as calcium movies) the it just uses an identify mapping.

NDPositionsProcessor and NDImageProcessor have logic specific to those types of data.

NDPositionsProcessor has an additional display_window properties which acts on the datapoints dimension. The datapoints dim is also both a slider dim and spatial dim, but it is excluded for window_funcs. datapoints_window_func is a separate property on NDPositionsProcessor that applies window funcs for that dimension since it's a bit different from other slider dims.

NDGraphic

A n-dimensional graphical representation. It has an NDProcessor and fpl.Graphic that it manages. ReferenceIndex sets the indices on an NDGraphic, which then call get() on its NDProcessor to retrieve the new graphic data, and then sets it on the graphic. It has property aliases for the various processor properties, so that users can set window funcs, slider dim transforms, etc.

NDPositions and NDImage are two NDGraphic subclasses that have specific code for positional and image specific representations.

NDPositions also manages a linear selector for timeseries representations, and allows swapping between scatter, line stack, line collection, and heatmap representations. It also manages an "auto x range" mode for timeseries representations

NDImage also manages the HistogramLUTTool and swapping between ImageGraphic and ImageVolumeGraphic. It also sets the camera and controller when these are swapped.

NDWSubplot

The main entry point for users to add NDGraphics to a subplot in an NDWidget. It mainly has add_nd<...> methods for users to provide data and kwargs to add NDGraphics.

Need to cleanup this subclass so the args and kwargs for each add method are properly specified and documented.

NDWidget

Just a very simple class that has a fpl.Figure, an instance of ReferenceIndex, and the NDWSubsplots.

Very simplified diagram:

s1, ... sn are slider dims, d1, d2, ... are spatial dims.

image

User API

The user mainly interacts via the NDW subplots and NDGraphics. For example with images:

data = np.random.rand(1000, 30, 64, 64)

# must define a reference range for each dim
ref = {
    "time": (0, 1000, 1),
    "depth": (0, 30, 1),
}


ndw = fpl.NDWidget(ref_ranges=ref, size=(700, 560))
ndw.show()

ndi = ndw[0, 0].add_nd_image(
    data,
    ("time", "depth", "m", "n"),  # specify all dim names
    ("m", "n"),  # specify spatial dims IN ORDER, rest are auto slider dims
    name="4d-image",
)

# change spatial dims on the fly
# ndi.spatial_dims = ("depth", "m", "n")

Positional data:

# generate some toy timeseries data
n_datapoints = 100_000  # number of datapoints per line
n_freqs = 20  # number of frequencies
n_ampls = 15  # number of amplitudes
n_lines = 8

xs = np.linspace(0, 1000 * np.pi, n_datapoints)

data = np.zeros(shape=(n_freqs, n_ampls, n_lines, n_datapoints, 2), dtype=np.float32)

for freq in range(data.shape[0]):
    for ampl in range(data.shape[1]):
        ys = np.sin(xs * (freq + 1)) * (ampl + 1) + np.random.normal(
            0, 0.1, size=n_datapoints
        )
        line = np.column_stack([xs, ys])
        data[freq, ampl] = np.stack([line] * n_lines)


# must define a reference range, this would often be your time dimension and corresponds to your x-dimension
ref = {
    "freq": (1, n_freqs + 1, 1),
    "ampl": (1, n_ampls + 1, 1),
    "angle": (0, xs[-1], 0.1),
}

ndw = fpl.NDWidget(ref_ranges=ref, size=(700, 560))

nd_lines = ndw[0, 0].add_nd_timeseries(
    data,
    ("freq", "ampl", "n_lines", "angle", "d"),
    ("n_lines", "angle", "d"),
    slider_dim_transforms={
        "angle": xs,
        "ampl": lambda x: int(x + 1),
        "freq": lambda x: int(x + 1),
    },
    x_range_mode="view-range",
    name="nd-sine"
)

nd_lines.graphic.cmap = "tab10"

subplot = ndw.figure[0, 0]
subplot.controller.add_camera(subplot.camera, include_state={"x", "width"})

ndw.show(maintain_aspect=False)

Main things left todo

  • docs, docstrings
  • tests, and test edge cases such as when NDProcessor.get() returns empty arrays
  • lots of examples
  • user input validation, see what Edo did recently with nemos
  • auto reference ranges
  • push/pop reference range on ReferenceIndex
  • other graphical features such as colors, cmap, sizes, etc. Also maintain things like cmap across lines/scatters when switching graphical representations.
  • if data is set to None, delete the graphic, indices setter should just ignore. Might need to update ReferenceIndex to also ignore it.
  • clear install instructions since this will be a new toggle due to xarray dep.
  • the pandas NDProcessor should just be a default one that's available since pandas is an xarray dependency.
  • Maybe reorganize subpackage so it's just fastplotlib.ndwidget?
  • optimize pandas processor get()
  • nice repr, this seems like a good tutorial https://dev.to/dcodeyt/creating-beautiful-html-tables-with-css-428l
  • renaming things for consistency, clarity
    • ReferenceIndex -> ReferenceIndices, it manages multiple indices
    • indices -> ref_indices, keep constructor and properties symmetric like the rest of fpl

implements #951

@kushalkolar kushalkolar changed the title NWidget NDWidget Dec 25, 2025
@kushalkolar
Copy link
Copy Markdown
Member Author

Got basic timeseries with linestack working. I've also got some code snippets for interpolating to display heatmap with non-uniformly sampled timeseries data. I should be able to have this fully working with time series very soon :D

Kooha-2025-12-27-03-50-45.mp4

@kushalkolar
Copy link
Copy Markdown
Member Author

Got heatmap to display timeseries working. It should also work with non-uniformly sampled data by interpolating, need to test.

Also need to implementing switching between heatmap and line representations, need to delete the graphic when switching.

Kooha-2025-12-27-17-47-16.mp4

@kushalkolar
Copy link
Copy Markdown
Member Author

kushalkolar commented Dec 29, 2025

So timeseries can be represented with arrays of one of the following shapes (let's ignore x-axis values for now).

If we have:

l: number of timeseries
p: number of datapoints in a timeseries

We can have the following shapes:

p: only y-values
p, 2: yz vals
l, p: l-timeseries with y values
l, p 2: l-timeseries with yz values

Extended to n-dimensional arrays (for example, trajectories projected onto principal components?). If each non-timeseries dim is $d_1, d_2, ... d_n$, then the above becomes:

$$\begin{align*} d_1, ... d_n, p\\\ d_1, ... d_n, p, 2\\\ d_1, ... d_n, l, p\\\ d_1, ... d_n, l, p, 2\\\ \end{align*}$$

I don't think we can auto-detect if l is present or not and the user should specify, something like:

multi_timeseries: bool = True

Scatters can be similar to some cases of nd-lines 🤔 , but x values would be directly specified and the current index is parametric (example with time indicating position in a low dim space). This would actually be true for lines as well if representing trajectories.

So for nd-line maybe we have two versions, parametric (y and z are not functions of x, but x, y, z are a function of some other dim) and non-parametric (simple timeseries lines where y and z are functions of x).

@kushalkolar
Copy link
Copy Markdown
Member Author

kushalkolar commented Dec 29, 2025

I made a more generalist NDPositions which can map data that is:

[s1, s2, ... sn, l, p, 2 | 3]

where:
s1, s2, .... sn are slider dims
l is number of lines or number of scatters, this dimension is optional and the user must specify whether or not it exists
2 | 3 is the last dim, indicating xy or xyz positions.

It can map arrays of these dims to a line, line collection, line stack, scatter, or list of scatters (similar to multi-line).

I think this is a much more elegant way to deal with things, and NDTimeSeries is not necessary. The user can provide a slider mapping (to map from reference units to array index) for the p dimension which is the same as the "x-axis" for time series data!

Example if we have data that is [n_timepoints, 2], and the x-positions here (in the last dim) are in seconds. The NDWidget reference units for the slider can also be in seconds, and we can provide a mapping function that goes from the slider reference units to the n_timepoints index.

I think we can also use this for heatmaps and interpolation. Use the reference units to determine a uniform x-range for the current display window, and we can interpolate using [n_timepoints, 2] data.

EDIT: I think that the NDPositions will also work for PolygonGraphic ! Can think about meshes in general later.

@kushalkolar
Copy link
Copy Markdown
Member Author

For positions graphics, I should actually do [n_datapoints, n_lines, 2 | 3] so everything before the last 1 or 2 dims is always sliders.

@kushalkolar
Copy link
Copy Markdown
Member Author

kushalkolar commented Jan 27, 2026

Some more ideas:

Allow any 2-3 dims to be used as the graphic dimensions and specify the slider dims. This would also allow using named dims (such as those used in xarray).
API like:

add_nd_<scatter|lines|heatmap>(
  data=<array>, # array like, defaults to last 2-3 dims are graphical, first (n - 2 | 3) dims are sliders and in order
  x=<int | str | None>  # optionally specify x graphical dim using the integer dim index or dim name
  y = ... # same for other graphical dims
  slider_dims=<None | tuple[int | str]> # specify slider dims, None is auto first (n - 2 | 3), or specify a tuple of slider dims as int or str, examples: (0, 1, 2, 3), ("time", "plane")
  ... # other args
)

We interpret the given order of the slider_dims passed as $$s_1, s_2, ... s_n$$, regardless of their order within the actual array. This will make it clear to users which dims they are syncing when they use the sliders.

EDIT: A limitation of the above is that a user can't collapse multiple "graphic/display dimensions" into "final graphic/display dimensions" if they're hard-coded this like. So something like:

add_nd_<...>(
  data=<array>,
  display_dims=<tuple[int | str]>, # specify display dims in [x, y, z] order, OR display dims that collapse to xyz after the finalizer function
...
)

An example for images would be collapsing [z, m, n] to display a projection over z as slider dims are moved. But we could also have > 3 dims that are used, and then collapsed to 3 or fewer dims for the "final graphic/display dims".

@kushalkolar
Copy link
Copy Markdown
Member Author

We can use LineCollections to display multiple lines, like behavior tracks of keypoints, with shape [n_keypoints, n_timepoints, 2]. This works well with NDPositions and display windows.

I was thinking of what's the best way to show a scatter for each keypoint, and I think I should make a ScatterCollection that behaves like a LineCollection so the same array with the same shape can be given, the only difference is the graphical representation would be a scatter instead of lines. For typical behavior keypoint viz, the display_window would usually be just 1, but it can be greater than 1 for any viz that needs to show a window of scatter points.

@kushalkolar
Copy link
Copy Markdown
Member Author

ok I think stuff is working

ndpositions-2026-01-29_22.55.54.mp4

@kushalkolar
Copy link
Copy Markdown
Member Author

I think I need to make a PolygonCollection too 🤔 . Would be very similar to the ScatterCollection.

@kushalkolar
Copy link
Copy Markdown
Member Author

kushalkolar commented Jan 30, 2026

so the basics are all working 🥳

LineStack and heatmap representations are swappable:

nd_positions_swap_linestack_heatmap-2026-01-30_00.19.27.mp4

Scatter and line collection to show behavior trajectories:

nd_positions_behavior-2026-01-30_00.29.15.mp4
image

@kushalkolar
Copy link
Copy Markdown
Member Author

A set of imgui UIs that allow controlling some aspects of the "nd graphics" could be useful, such as:

  • graphical representation, dropdown menu to choose scatter, line, heatmap
  • display_window
  • multi bool

@kushalkolar
Copy link
Copy Markdown
Member Author

kushalkolar commented Jan 30, 2026

Stuff I should finish before implementing the orchestrator:

  • merge auto-replace buffers #974 , so I can implement changing the display_window
  • implement logic to allow the display_window to centered, on the left or on the right
  • adding a linear selector, it's "default" positions and its position when the main slider for the n_datapoints dimension changes should correspond whether the display_window is in "center", "start" or "end" mode. Moving the linear selector should probably change the index in all other NDobjects but not its "parent" object. So that the visual representation of the parent object doesn't change, ex: if the linear selector is moved we don't want a linestack to also "move"
  • spatial function
  • implement mapping from a slider reference index with units (such as time) to array index.
  • figure out how to implement stuff like colors, cmaps given that we're essentially performing out of core rendering with these graphics
  • auto display_window? Zoom in/out and set graphic data at different subsample levels

Things that make me "uncomfortable" that I need to settle:

dim shapes

Dim shape for nd-positions is [s1, s2, ... sn, n, p, 2 | 3] where:

n: number of lines, scatters, or heatmap rows (optional, can be 1)
p: number of datapoints, often corresponds to a time index.

p would be a slider dim, but there's this n dim that's in between p and all the other slider dims. We could instead use a shape of [s1, ... sn, p, n, 2 | 3] but that feels weird and we'd have to do a transpose to get the array for the graphical representation, i.e. [p, n, 2 | 3] -> [n, p, 2 | 3] is required for the graphical representation.

Do we just document this well, that the n dimension is not a slider dim but p is?

When using nd-positions data in conjunction with nd-image data, we'd have something like this:

# nd-positions
[s1, ... sn, n, p, 2 | 3]

# nd-image
[s1, ... sn, p, r, c, 1 | 3 | 4]

Where r: rows, cols: columns. 1 | 3 | 4 denotes grayscale or RGB(A).

The p dimension in the nd-image array above would correspond to the p in the nd-positions. nd-images don't have any n dimension, it doesn't make sense there.

@kushalkolar
Copy link
Copy Markdown
Member Author

Working on "implement mapping from a slider reference index with units (such as time) to array index.", which requires proper implementation of slider_dims and n_slider_dims properties. Need to figure out how to properly separate the p n_datapoints dimension from other slider dims, and also apply the window funcs on the p dim. Will do tomorrow.

@kushalkolar
Copy link
Copy Markdown
Member Author

Window funcs working for p (n_datapoints) dim and all graphical representations

ndp_windows-2026-02-01_03.39.52.mp4

@kushalkolar
Copy link
Copy Markdown
Member Author

kushalkolar commented Mar 15, 2026

weird graphics artifacts with markers, will figure out later, but basics work

Edit: Fixed

There is a weird thing going on with the markers buffer O_O

Need to separate cmap and cmap_transform as two distinct features to implement that on NDGraphics, it also needs access to the parent graphic for stuff like checking the data shape.

@kushalkolar
Copy link
Copy Markdown
Member Author

kushalkolar commented Mar 17, 2026

Idea! I think we can allow global indices to be shared between NDWidgets so then we can have multiple windows.

Edit: this works!

@kushalkolar
Copy link
Copy Markdown
Member Author

kushalkolar commented Mar 19, 2026

@clewis7

This also contains the replaceable buffers PR #974 which also needs a review but those bit of code are simpler and they're in the other parts of the codebase outside the ndwidget subpackage

the following modules/classes are ready for review:

base classes NDProcessor and NDGraphic: https://github.com/fastplotlib/fastplotlib/pull/971/changes#diff-38237d8750a6678e1e5ffe318e6ccd6144f1aeab91bfad9c1b803963da62b666

NDImageProcessor and NDImage: https://github.com/fastplotlib/fastplotlib/pull/971/changes#diff-af2caa1af4b5c04b6b406797c16c77699c807894e99c402ced57c3166f35a0a2

ReferenceRange stuff and ReferenceIndex (was previously called GlobalIndex, I renamed it because I thought it'd be intuitive for "reference index" to be in "reference space", and because people might think global means global variable especially when you could have multiple ndwidgets with different reference spaces they each mange if someone is going crazy): https://github.com/fastplotlib/fastplotlib/pull/971/changes#diff-ff78d59b530fa54e6290bf9472a461c955e2fdc8d8c78ac265f0563b3fc0532c

General thoughts on NDWSubplot, I think we should autogen this: https://github.com/fastplotlib/fastplotlib/pull/971/changes#diff-41e35095bf2376587f8c124262ab9c63abe820980b3832446d4c918e4de3fa8e

NDPositionsProcessor and NDPositions aren't ready for review, but I had a few comments on those and would like your thoughts (should I do ND Timeseries subclass and the "_each" arts)

random questions:

ndw["bah"].add_ndimage() or ndw["bah"].add_nd_image(), and likewise for the rest 😆

What do you think about the name index_mappings vs slider_dim_transforms or slider_dim_mappings? I was thinking slider_dim_ prefix makes it explicit what it's about, and xarray uses the name transform for a similar concept (but they use it for ref-space -> array inedx mappings in spatial dimensions unlike us). I'm evenly split

I think maybe slider_maps, explicit and short 🤔

any other naming things

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I now realize we can probably auto-gen NDWSubplot.add_nd_graphic methods similar to Subplot.add_graphic methods

Comment on lines +689 to +710
self._timeseries = timeseries
# TODO: I think this is messy af, NDTimeseriesSubclass???
if self._timeseries:
# makes some assumptions about positional data that apply only to timeseries representations
# probably don't want to maintain aspect
self._subplot.camera.maintain_aspect = False

# auto x range modes make no sense for non-timeseries data
self.x_range_mode = x_range_mode

if linear_selector:
self._linear_selector = LinearSelector(
0, limits=(-np.inf, np.inf), edge_color="cyan"
)
self._linear_selector.add_event_handler(
self._linear_selector_handler, "selection"
)
self._subplot.add_graphic(self._linear_selector)
else:
self._linear_selector = None
else:
self._linear_selector = None
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should have an NDTimeseries subclass of NDPositions which has the timeseries specific stuff, i.e. x_range_mode linear_selector, etc., instead of messy if-else logic. 🤔

Comment on lines +822 to +839
# TODO: I think this is messy af, NDTimeseriesSubclass???
# x range of the data
xr = data_slice[0, 0, 0], data_slice[0, -1, 0]
if self.x_range_mode is not None:
self.graphic._plot_area.x_range = xr

# if the update_from_view is polling, this prevents it from being called by setting the new last xrange
# in theory, but this doesn't seem to fully work yet, not a big deal right now can check later
self._last_x_range[:] = self.graphic._plot_area.x_range

if self._linear_selector is not None:
with pause_events(self._linear_selector): # we don't want the linear selector change to update the indices
self._linear_selector.limits = xr
# linear selector acts on `p` dim
self._linear_selector.selection = indices[
self.processor.spatial_dims[1]
]

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

similar to other comment, I think we should have an NDTimeseries subclass of NDPositions 🤔

Comment on lines +981 to +1020
@property
def x_range_mode(self) -> Literal["fixed", "auto"] | None:
"""x-range using a fixed window from the display window, or by polling the camera (auto)"""
return self._x_range_mode

@x_range_mode.setter
def x_range_mode(self, mode: Literal[None, "fixed", "auto"]):
if self._x_range_mode == "auto":
# old mode was auto
self._subplot.remove_animation(self._update_from_view_range)

if mode == "auto":
self._subplot.add_animations(self._update_from_view_range)

self._x_range_mode = mode

def _update_from_view_range(self):
if self._graphic is None:
return

xr = self._subplot.x_range

# the floating point error near zero gets nasty here
if np.allclose(xr, self._last_x_range, atol=1e-14):
return

last_width = abs(self._last_x_range[1] - self._last_x_range[0])
self._last_x_range[:] = xr

new_width = abs(xr[1] - xr[0])
new_index = (xr[0] + xr[1]) / 2

if (new_index == self._ref_index[self.processor.spatial_dims[1]]) and (
last_width == new_width
):
return

self.processor.display_window = new_width
# set the `p` dim on the global index vector
self._ref_index[self.processor.spatial_dims[1]] = new_index
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

similar to other comment, I think we should have an NDTimeseries subclass of NDPositions 🤔

slider_dim_transforms=None,
name: str = None,
):
"""
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about having the detailed docstring of the arguments only in the corresponding NDGraphic, ex: in NDImage, and not in the NDImageProcessor since people will usually look at the NDImage arguments (via the auto subplot.add_nd_<...> described in the PR comment)

Comment on lines +591 to +603
colors: (
Sequence[str] | np.ndarray | Callable[[slice, np.ndarray], np.ndarray]
) = None,
# TODO: cleanup how this cmap stuff works, require a cmap to be set per-graphic
# before allowing cmaps_transform, validate that stuff makes sense etc.
cmap: str = None, # across the line/scatter collection
cmap_each: Sequence[str] = None, # for each individual line/scatter
cmap_transform_each: np.ndarray = None, # for each individual line/scatter
markers: np.ndarray = None, # across the scatter collection, shape [l,]
markers_each: Sequence[str] = None, # for each individual scatter, shape [l, p]
sizes: np.ndarray = None, # across the scatter collection, shape [l,]
sizes_each: Sequence[float] = None, # for each individual scatter, shape [l, p]
thickness: np.ndarray = None, # for each line, shape [l,]
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think of these args for the other graphic features. l is the "stack" dimension (i.e. number of lines or scatters in a collection or stack), p is the datapoints dim.

Also I think I will change colors to this:

colors: np.ndarray, # of shape [l,], specify color for each line/scatter
colors_each: np.ndarray, # of shape [l, p], specify color for every datapoint of every line/scatter

# decorator to block re-entrant set_value methods
# useful when creating complex, circular, bidirectional event graphs
def set_value_wrapper(self: GraphicFeature, graphic_or_key, value):
def set_value_wrapper(self: GraphicFeature, graphic_or_key, value, **kwargs):
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the kwarg is used by the LinearRegionSelectionFeature


@block_reentrance
def set_value(self, selector, value: Sequence[float]):
def set_value(self, selector, value: Sequence[float], *, change: str = "full"):
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the new HistogramLUTTool, even logic was much easier if we know whether both vmin, vmax changed or only one of them.

]

def __init__(self, data, isolated_buffer: bool = True):
def __init__(self, data):
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

an isolated buffer is always created now. There really isn't a usecase for non-isolated buffer IMO, user should use OOC rendering if the data is huge. In NDWidget the final array copy is also very fast anyways because we're always only ever looking at a slice of large data.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a complete rewrite, the git diff viewer is useless lol

@kushalkolar
Copy link
Copy Markdown
Member Author

After speaking with Edoardo I now see a clear use case for a slider dim inverse transforms and the RangeDiscrete 😆

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants