diff --git a/holoviews/core/util/__init__.py b/holoviews/core/util/__init__.py index 906548f80a..ca9779a73f 100644 --- a/holoviews/core/util/__init__.py +++ b/holoviews/core/util/__init__.py @@ -2255,7 +2255,7 @@ def validate_regular_sampling(values, rtol=10e-6): if len(diffs) < 1: return True min_diff = diffs.min() - return abs(min_diff - diffs.max()) < abs(min_diff * rtol) + return abs(min_diff - diffs.max()) <= abs(min_diff * rtol) def compute_density(start, end, length, time_unit="us"): diff --git a/holoviews/tests/element/test_image.py b/holoviews/tests/element/test_image.py index 74aaca1463..506660fb25 100644 --- a/holoviews/tests/element/test_image.py +++ b/holoviews/tests/element/test_image.py @@ -4,6 +4,8 @@ from __future__ import annotations +import datetime as dt + import numpy as np import pytest @@ -74,6 +76,55 @@ def test_image_rtol_config(self): hv.Image({"vals": vals, "xs": xs, "ys": ys}, ["xs", "ys"], "vals") hv.config.image_rtol = image_rtol + @pytest.mark.parametrize( + "x", + [ + # max(|step|) / min(|step|) - 1 = 0.0015 + [dt.datetime(2017, 1, 1, 0, 0, 0, ms) for ms in [0, 10000, 20015]], + *( + np.array([0, 10000, 20015]).astype(f"datetime64[{unit}]") + for unit in ["D", "h", "m", "s", "ms", "us"] + ), + ], + ) + def test_image_datetime_rtol_failure(self, x): + """ + Constructing an Image with a date-time coordinate warns about irregular sampling + if, approximately speaking, one less than the ratio of the max and min steps + exceeds the default `image_rtol`. + """ + y = np.arange(2) + hv.Image((x, y, np.zeros((len(y), len(x))))) + self.log_handler.assert_endswith( + "WARNING", + "set a higher tolerance on hv.config.image_rtol or the rtol parameter in " + "the Image constructor.", + ) + + @pytest.mark.parametrize( + "x", + [ + # |granularity / step| = 1/400 = 0.0025 + [dt.datetime(2017, 1, 1, 0, 0, 0, ms) for ms in [0, 400]], + # |granularity / step| = 1/800 = 0.00125 + *( + np.array([10000, 10800]).astype(f"datetime64[{unit}]") + for unit in ["D", "h", "m", "s", "ms", "us"] + ), + ], + ) + def test_image_datetime_rtol_granularity(self, x): + """ + Constructing an Image with an evenly spaced date-time coordinate does not warn + about irregular sampling, even when the ratio of the date-time granularity to + the coordinate step is greater than the default `image_rtol` (or twice + `image_rtol` for `dt.datetime` types because of how `dt.timedelta` instances + round when multiplied by a `float`). + """ + y = np.arange(2) + hv.Image((x, y, np.zeros((len(y), len(x))))) + assert len(self.log_handler.tail("WARNING", n=1)) == 0 + def test_image_clone(self): vals = np.random.rand(20, 20) xs = np.linspace(0, 10, 20)