diff --git a/holoviews/operation/downsample.py b/holoviews/operation/downsample.py index 50cdd7ff65..77259b5812 100644 --- a/holoviews/operation/downsample.py +++ b/holoviews/operation/downsample.py @@ -100,6 +100,13 @@ def _lttb_inner(x, y, n_out, sampled_x, offset): ) +def _ensure_contiguous(x, y): + """ + Ensures the arrays are contiguous in memory (required by tsdownsample). + """ + return np.ascontiguousarray(x), np.ascontiguousarray(y) + + def _lttb(x, y, n_out, **kwargs): """ Downsample the data using the LTTB algorithm. @@ -115,6 +122,7 @@ def _lttb(x, y, n_out, **kwargs): """ try: from tsdownsample import LTTBDownsampler + x, y = _ensure_contiguous(x, y) return LTTBDownsampler().downsample(x, y, n_out=n_out, **kwargs) except ModuleNotFoundError: pass @@ -168,6 +176,7 @@ def _min_max(x, y, n_out, **kwargs): 'The min-max downsampling algorithm requires the tsdownsample ' 'library to be installed.' ) from None + x, y = _ensure_contiguous(x, y) return MinMaxDownsampler().downsample(x, y, n_out=n_out, **kwargs) def _min_max_lttb(x, y, n_out, **kwargs): @@ -178,6 +187,7 @@ def _min_max_lttb(x, y, n_out, **kwargs): 'The minmax-lttb downsampling algorithm requires the tsdownsample ' 'library to be installed.' ) from None + x, y = _ensure_contiguous(x, y) return MinMaxLTTBDownsampler().downsample(x, y, n_out=n_out, **kwargs) def _m4(x, y, n_out, **kwargs): @@ -188,6 +198,7 @@ def _m4(x, y, n_out, **kwargs): 'The m4 downsampling algorithm requires the tsdownsample ' 'library to be installed.' ) from None + x, y = _ensure_contiguous(x, y) n_out = n_out - (n_out % 4) # n_out must be a multiple of 4 return M4Downsampler().downsample(x, y, n_out=n_out, **kwargs) diff --git a/holoviews/tests/operation/test_downsample.py b/holoviews/tests/operation/test_downsample.py index 3ca6a4c929..e030696ff3 100644 --- a/holoviews/tests/operation/test_downsample.py +++ b/holoviews/tests/operation/test_downsample.py @@ -2,7 +2,8 @@ import pandas as pd import pytest -import holoviews as hv +from holoviews.core.overlay import NdOverlay, Overlay +from holoviews.element import Curve from holoviews.operation.downsample import _ALGORITHMS, downsample1d try: @@ -20,9 +21,9 @@ def test_downsample1d_multi(plottype): assert N > downsample1d.width if plottype == "overlay": - figure = hv.Overlay([hv.Curve(range(N)), hv.Curve(range(N))]) + figure = Overlay([Curve(range(N)), Curve(range(N))]) elif plottype == "ndoverlay": - figure = hv.NdOverlay({"A": hv.Curve(range(N)), "B": hv.Curve(range(N))}) + figure = NdOverlay({"A": Curve(range(N)), "B": Curve(range(N))}) figure_values = downsample1d(figure, dynamic=False).data.values() for n in figure_values: @@ -30,6 +31,16 @@ def test_downsample1d_multi(plottype): assert value.size == downsample1d.width +@pytest.mark.skipif(not tsdownsample, reason="tsdownsample not installed") +@pytest.mark.parametrize("algorithm", algorithms) +def test_downsample1d_non_contiguous(algorithm): + x = np.arange(20) + y = np.arange(40).reshape(1, 40)[0, ::2] + + downsampled = downsample1d(Curve((x, y), datatype=['array']), dynamic=False, width=10, algorithm=algorithm) + assert len(downsampled) + + def test_downsample1d_shared_data(): runs = [0] @@ -42,7 +53,7 @@ def _compute_mask(self, element): N = 1000 df = pd.DataFrame({c: range(N) for c in "xyz"}) - figure = hv.Overlay([hv.Curve(df, kdims="x", vdims=c) for c in "yz"]) + figure = Overlay([Curve(df, kdims="x", vdims=c) for c in "yz"]) # We set x_range to trigger _compute_mask mocksample(figure, dynamic=False, x_range=(0, 500)) @@ -61,7 +72,7 @@ def _compute_mask(self, element): N = 1000 df = pd.DataFrame({c: range(N) for c in "xyz"}) - figure = hv.Overlay([hv.Curve(df, kdims="index", vdims=c) for c in "xyz"]) + figure = Overlay([Curve(df, kdims="index", vdims=c) for c in "xyz"]) # We set x_range to trigger _compute_mask mocksample(figure, dynamic=False, x_range=(0, 500))