diff --git a/src/modelskill/plotting/_temporal_coverage.py b/src/modelskill/plotting/_temporal_coverage.py index a861728a5..5052ac8cc 100644 --- a/src/modelskill/plotting/_temporal_coverage.py +++ b/src/modelskill/plotting/_temporal_coverage.py @@ -96,7 +96,7 @@ def temporal_coverage( if len(mod) > 0 and limit_to_model_period: mr = mod[0] # take first model - plt.xlim([mr.time[0], mr.time[-1]]) + plt.xlim((mr.time[0], mr.time[-1])) plt.yticks(np.arange(n_lines) + 1, labels) if len(mod) > 0: diff --git a/src/modelskill/plotting/_wind_rose.py b/src/modelskill/plotting/_wind_rose.py index 730f197ed..76180bd88 100644 --- a/src/modelskill/plotting/_wind_rose.py +++ b/src/modelskill/plotting/_wind_rose.py @@ -570,7 +570,7 @@ def _add_legend_to_ax( frameon=True, title=label, bbox_to_anchor=bbox_to_anchor, - loc=loc, + loc=loc, # type: ignore[arg-type] ) box_width = 0.32 diff --git a/tests/regression/baseline/wind_rose_defaults.png b/tests/regression/baseline/wind_rose_defaults.png index 17671ff8c..d6c7d14fd 100644 Binary files a/tests/regression/baseline/wind_rose_defaults.png and b/tests/regression/baseline/wind_rose_defaults.png differ diff --git a/tests/regression/test_regression_rose.py b/tests/regression/test_regression_rose.py index efbf5ab40..95d726730 100644 --- a/tests/regression/test_regression_rose.py +++ b/tests/regression/test_regression_rose.py @@ -1,12 +1,19 @@ import sys import matplotlib.pyplot as plt -from PIL import Image -import numpy as np +from matplotlib.testing.compare import compare_images import pytest import mikeio from modelskill.plotting import wind_rose +# Max allowed RMS pixel difference vs. the baseline. The test is a refactoring +# tripwire for the *default* wind rose, not a pixel-exact lock: this tolerance +# absorbs minor rendering drift (antialiasing, font/patch-level matplotlib +# changes) while still catching real layout/data regressions, which measure +# ~30 RMS. Regenerate the baseline (see below) on a major matplotlib bump that +# legitimately changes rendering. +IMAGE_TOLERANCE = 10 + @pytest.fixture def wave_data_model_obs(): @@ -24,26 +31,25 @@ def wave_data_model_obs(): @pytest.mark.skipif(sys.platform == "win32", reason="does not run on windows") -def test_wind_rose_image_identical(wave_data_model_obs, tmp_path): - # TODO this test seems fragile, since it relies pixel by pixel comparison of images +def test_wind_rose_matches_baseline(wave_data_model_obs, tmp_path): data = wave_data_model_obs.to_numpy() wind_rose(data) baseline_path = "tests/regression/baseline/wind_rose_defaults.png" - img_path = tmp_path / "temp.png" + img_path = tmp_path / "wind_rose_defaults.png" fig = plt.gcf() fig.set_size_inches( 10, 6 ) # TODO without setting the size, the legends are outside the image plt.tight_layout() - # plt.savefig(baseline_path) # uncomment to generate new baseline + # To regenerate the baseline (e.g. after a major matplotlib bump that + # legitimately changes rendering), save to baseline_path instead: + # plt.savefig(baseline_path) plt.savefig(img_path) - # compare images to ensure that the plot is identical to the baseline pixel by pixel - - baseline_arr = np.array(Image.open(baseline_path)) - img_arr = np.array(Image.open(img_path)) - - # these two Numpy arrays should be the same - assert np.all(baseline_arr == img_arr) + # Compare against the baseline within a tolerance. compare_images returns + # None on success and an explanatory message on failure, writing a + # *-failed-diff.png next to img_path for inspection. + result = compare_images(baseline_path, str(img_path), tol=IMAGE_TOLERANCE) + assert result is None, result