diff --git a/.gitignore b/.gitignore index d378559a..d8163665 100644 --- a/.gitignore +++ b/.gitignore @@ -49,6 +49,7 @@ pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ +test_images .tox/ .nox/ .coverage diff --git a/pyproject.toml b/pyproject.toml index 8e4f2c74..adadd43b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -91,6 +91,9 @@ doctest_optionflags = "NORMALIZE_WHITESPACE ELLIPSIS NUMBER" filterwarnings = [ "error", "ignore:numpy.ndarray size changed:RuntimeWarning", + "ignore:Unable to remesh 1 cell:UserWarning", + "ignore:pyvista test cache image dir:UserWarning", + "ignore:pyvista test generated image dir:UserWarning", ] image_cache_dir = "tests/plotting/image_cache" markers = [ diff --git a/src/geovista/__init__.py b/src/geovista/__init__.py index f1623766..9f541ffa 100644 --- a/src/geovista/__init__.py +++ b/src/geovista/__init__.py @@ -18,6 +18,7 @@ from __future__ import annotations import logging +import os from .bridge import Transform # noqa: F401 from .cache import ( # noqa: F401 @@ -49,3 +50,8 @@ logger = logging.getLogger(__name__) logger.addHandler(logging.StreamHandler()) logger.setLevel("WARNING") + +#: flag when performing image testing +GEOVISTA_IMAGE_TESTING: bool = ( + os.environ.get("GEOVISTA_IMAGE_TESTING", "false").lower() == "true" +) diff --git a/src/geovista/cache.py b/src/geovista/cache.py index 4c744cdc..b70048bb 100644 --- a/src/geovista/cache.py +++ b/src/geovista/cache.py @@ -35,7 +35,7 @@ BASE_URL: str = "https://github.com/bjlittle/geovista-data/raw/{version}/data/" #: Pin to use the specific geovista-data repository version for geovista resources. -DATA_VERSION: str = "2023.08.0" +DATA_VERSION: str = "2023.10.1" #: Environment variable to override pooch cache manager path. ENV: str = "GEOVISTA_CACHEDIR" diff --git a/src/geovista/examples/clouds.py b/src/geovista/examples/clouds.py index bd9a6df4..345a3457 100755 --- a/src/geovista/examples/clouds.py +++ b/src/geovista/examples/clouds.py @@ -92,7 +92,6 @@ def main() -> None: font_size=10, shadow=True, ) - plotter.camera.zoom(1.5) plotter.show() diff --git a/src/geovista/examples/clouds_robin.py b/src/geovista/examples/clouds_robin.py index cc308669..8686db08 100755 --- a/src/geovista/examples/clouds_robin.py +++ b/src/geovista/examples/clouds_robin.py @@ -94,7 +94,6 @@ def main() -> None: shadow=True, ) plotter.view_xy() - plotter.camera.zoom(1.5) plotter.show() diff --git a/src/geovista/examples/earthquakes.py b/src/geovista/examples/earthquakes.py index c309d9b9..3df29e36 100755 --- a/src/geovista/examples/earthquakes.py +++ b/src/geovista/examples/earthquakes.py @@ -79,7 +79,6 @@ def main() -> None: shadow=True, ) plotter.view_xz(negative=True) - plotter.camera.zoom(1.5) plotter.show() diff --git a/src/geovista/examples/earthquakes_wink1.py b/src/geovista/examples/earthquakes_wink1.py index aaaa0734..ae065883 100755 --- a/src/geovista/examples/earthquakes_wink1.py +++ b/src/geovista/examples/earthquakes_wink1.py @@ -81,7 +81,6 @@ def main() -> None: shadow=True, ) plotter.view_xy() - plotter.camera.zoom(1.5) plotter.show() diff --git a/src/geovista/examples/from_1d__oisst_eqc.py b/src/geovista/examples/from_1d__oisst_eqc.py index bbf6c931..7e5d50b9 100755 --- a/src/geovista/examples/from_1d__oisst_eqc.py +++ b/src/geovista/examples/from_1d__oisst_eqc.py @@ -55,7 +55,6 @@ def main() -> None: shadow=True, ) plotter.view_xy() - plotter.camera.zoom(1.5) plotter.show() diff --git a/src/geovista/examples/from_1d__synthetic_face_m1_n1.py b/src/geovista/examples/from_1d__synthetic_face_m1_n1.py index 387741fd..c571ebf0 100755 --- a/src/geovista/examples/from_1d__synthetic_face_m1_n1.py +++ b/src/geovista/examples/from_1d__synthetic_face_m1_n1.py @@ -28,7 +28,8 @@ def main() -> None: M, N = 45, 90 lats = np.linspace(-90, 90, M + 1) lons = np.linspace(-180, 180, N + 1) - data = np.random.random(M * N) + clim = (0, 1) + data = np.linspace(*clim, num=M * N) # create the mesh from the synthetic data name = "Synthetic Cells" @@ -41,7 +42,7 @@ def main() -> None: plotter = gv.GeoPlotter() sargs = {"title": f"{name} / 1", "shadow": True} plotter.add_mesh( - mesh, clim=(0, 1), cmap="ice", scalar_bar_args=sargs, show_edges=True + mesh, clim=clim, cmap="ice", scalar_bar_args=sargs, show_edges=True ) plotter.add_coastlines() plotter.add_axes() diff --git a/src/geovista/examples/from_1d__synthetic_face_m1_n1_robin.py b/src/geovista/examples/from_1d__synthetic_face_m1_n1_robin.py index a000c57a..1dba41aa 100755 --- a/src/geovista/examples/from_1d__synthetic_face_m1_n1_robin.py +++ b/src/geovista/examples/from_1d__synthetic_face_m1_n1_robin.py @@ -29,7 +29,8 @@ def main() -> None: M, N = 45, 90 lats = np.linspace(-90, 90, M + 1) lons = np.linspace(-180, 180, N + 1) - data = np.random.random(M * N) + clim = (0, 1) + data = np.linspace(*clim, num=M * N) # create the mesh from the synthetic data name = "Synthetic Cells" @@ -43,7 +44,7 @@ def main() -> None: plotter = gv.GeoPlotter(crs=crs) sargs = {"title": f"{name} / 1", "shadow": True} plotter.add_mesh( - mesh, clim=(0, 1), cmap="ice", scalar_bar_args=sargs, show_edges=True + mesh, clim=clim, cmap="ice", scalar_bar_args=sargs, show_edges=True ) plotter.add_coastlines() plotter.add_axes() @@ -54,7 +55,6 @@ def main() -> None: shadow=True, ) plotter.view_xy() - plotter.camera.zoom(1.5) plotter.show() diff --git a/src/geovista/examples/from_1d__synthetic_node_m1_n1.py b/src/geovista/examples/from_1d__synthetic_node_m1_n1.py index c1be015e..a654a8c3 100755 --- a/src/geovista/examples/from_1d__synthetic_node_m1_n1.py +++ b/src/geovista/examples/from_1d__synthetic_node_m1_n1.py @@ -28,7 +28,8 @@ def main() -> None: M, N = 45, 90 lats = np.linspace(-90, 90, M + 1) lons = np.linspace(-180, 180, N + 1) - data = np.random.random((M + 1) * (N + 1)) + clim = (0, 1) + data = np.linspace(*clim, num=(M + 1) * (N + 1)) # create the mesh from the synthetic data name = "Synthetic Points" @@ -41,7 +42,7 @@ def main() -> None: plotter = gv.GeoPlotter() sargs = {"title": f"{name} / 1", "shadow": True} plotter.add_mesh( - mesh, clim=(0, 1), cmap="ice", scalar_bar_args=sargs, show_edges=True + mesh, clim=clim, cmap="ice", scalar_bar_args=sargs, show_edges=True ) plotter.add_coastlines() plotter.add_axes() diff --git a/src/geovista/examples/from_1d__synthetic_node_m1_n1_moll.py b/src/geovista/examples/from_1d__synthetic_node_m1_n1_moll.py index 9c01dc6c..ab25a493 100755 --- a/src/geovista/examples/from_1d__synthetic_node_m1_n1_moll.py +++ b/src/geovista/examples/from_1d__synthetic_node_m1_n1_moll.py @@ -29,7 +29,8 @@ def main() -> None: M, N = 45, 90 lats = np.linspace(-90, 90, M + 1) lons = np.linspace(-180, 180, N + 1) - data = np.random.random((M + 1) * (N + 1)) + clim = (0, 1) + data = np.linspace(*clim, num=(M + 1) * (N + 1)) # create the mesh from the synthetic data name = "Synthetic Points" @@ -43,11 +44,7 @@ def main() -> None: plotter = gv.GeoPlotter(crs=crs) sargs = {"title": f"{name} / 1", "shadow": True} plotter.add_mesh( - mesh, - clim=(0, 1), - cmap="ice", - scalar_bar_args=sargs, - show_edges=True, + mesh, clim=clim, cmap="ice", scalar_bar_args=sargs, show_edges=True ) plotter.add_coastlines() plotter.add_axes() @@ -58,7 +55,6 @@ def main() -> None: shadow=True, ) plotter.view_xy() - plotter.camera.zoom(1.5) plotter.show() diff --git a/src/geovista/examples/from_2d__orca.py b/src/geovista/examples/from_2d__orca.py index dc55bd34..55656df4 100755 --- a/src/geovista/examples/from_2d__orca.py +++ b/src/geovista/examples/from_2d__orca.py @@ -40,7 +40,7 @@ def main() -> None: # plot the mesh plotter = gv.GeoPlotter() sargs = {"title": f"{sample.name} / {sample.units}", "shadow": True} - plotter.add_mesh(mesh, show_edges=True, scalar_bar_args=sargs) + plotter.add_mesh(mesh, scalar_bar_args=sargs) plotter.add_base_layer(texture=gv.natural_earth_1()) plotter.add_coastlines() plotter.add_axes() diff --git a/src/geovista/examples/from_2d__orca_moll.py b/src/geovista/examples/from_2d__orca_moll.py index 2415fb21..61e88acf 100755 --- a/src/geovista/examples/from_2d__orca_moll.py +++ b/src/geovista/examples/from_2d__orca_moll.py @@ -63,7 +63,6 @@ def main() -> None: shadow=True, ) plotter.view_xy() - plotter.camera.zoom(1.5) plotter.show() diff --git a/src/geovista/examples/from_2d__synthetic_face_m1n1.py b/src/geovista/examples/from_2d__synthetic_face_m1n1.py index bbf7a755..4cfe4e09 100755 --- a/src/geovista/examples/from_2d__synthetic_face_m1n1.py +++ b/src/geovista/examples/from_2d__synthetic_face_m1n1.py @@ -29,7 +29,8 @@ def main() -> None: lats = np.linspace(-90, 90, M + 1) lons = np.linspace(-180, 180, N + 1) mlons, mlats = np.meshgrid(lons, lats, indexing="xy") - data = np.random.random(M * N) + clim = (0, 1) + data = np.linspace(*clim, num=M * N) # create the mesh from the synthetic data name = "Synthetic Cells" @@ -40,9 +41,9 @@ def main() -> None: # plot the data plotter = gv.GeoPlotter() - sargs = sargs = {"title": f"{name} / 1", "shadow": True} + sargs = {"title": f"{name} / 1", "shadow": True} plotter.add_mesh( - mesh, clim=(0, 1), cmap="tempo", scalar_bar_args=sargs, show_edges=True + mesh, clim=clim, cmap="tempo", scalar_bar_args=sargs, show_edges=True ) plotter.add_coastlines() plotter.add_axes() diff --git a/src/geovista/examples/from_2d__synthetic_face_m1n1_robin.py b/src/geovista/examples/from_2d__synthetic_face_m1n1_robin.py index 893923c6..755313b2 100755 --- a/src/geovista/examples/from_2d__synthetic_face_m1n1_robin.py +++ b/src/geovista/examples/from_2d__synthetic_face_m1n1_robin.py @@ -30,7 +30,8 @@ def main() -> None: lats = np.linspace(-90, 90, M + 1) lons = np.linspace(-180, 180, N + 1) mlons, mlats = np.meshgrid(lons, lats, indexing="xy") - data = np.random.random(M * N) + clim = (0, 1) + data = np.linspace(*clim, num=M * N) # create the mesh from the synthetic data name = "Synthetic Cells" @@ -42,9 +43,9 @@ def main() -> None: # plot the data crs = "+proj=robin" plotter = gv.GeoPlotter(crs=crs) - sargs = sargs = {"title": f"{name} / 1", "shadow": True} + sargs = {"title": f"{name} / 1", "shadow": True} plotter.add_mesh( - mesh, clim=(0, 1), cmap="tempo", scalar_bar_args=sargs, show_edges=True + mesh, clim=clim, cmap="tempo", scalar_bar_args=sargs, show_edges=True ) plotter.add_coastlines() plotter.add_axes() @@ -55,7 +56,6 @@ def main() -> None: shadow=True, ) plotter.view_xy() - plotter.camera.zoom(1.5) plotter.show() diff --git a/src/geovista/examples/from_2d__synthetic_node_m1n1.py b/src/geovista/examples/from_2d__synthetic_node_m1n1.py index 5666eb27..5fbd6b70 100755 --- a/src/geovista/examples/from_2d__synthetic_node_m1n1.py +++ b/src/geovista/examples/from_2d__synthetic_node_m1n1.py @@ -29,7 +29,8 @@ def main() -> None: lats = np.linspace(-90, 90, M + 1) lons = np.linspace(-180, 180, N + 1) mlons, mlats = np.meshgrid(lons, lats, indexing="xy") - data = np.random.random((M + 1) * (N + 1)) + clim = (0, 1) + data = np.linspace(*clim, num=(M + 1) * (N + 1)) # create the mesh from the synthetic data name = "Synthetic Points" @@ -40,7 +41,10 @@ def main() -> None: # plot the mesh plotter = gv.GeoPlotter() - plotter.add_mesh(mesh, clim=(0, 1), cmap="tempo", show_edges=True) + sargs = {"title": f"{name} / 1", "shadow": True} + plotter.add_mesh( + mesh, clim=clim, cmap="tempo", scalar_bar_args=sargs, show_edges=True + ) plotter.add_coastlines() plotter.add_axes() plotter.add_text( diff --git a/src/geovista/examples/from_2d__synthetic_node_m1n1_moll.py b/src/geovista/examples/from_2d__synthetic_node_m1n1_moll.py index 1e3feadc..0a7680c3 100755 --- a/src/geovista/examples/from_2d__synthetic_node_m1n1_moll.py +++ b/src/geovista/examples/from_2d__synthetic_node_m1n1_moll.py @@ -30,7 +30,8 @@ def main() -> None: lats = np.linspace(-90, 90, M + 1) lons = np.linspace(-180, 180, N + 1) mlons, mlats = np.meshgrid(lons, lats, indexing="xy") - data = np.random.random((M + 1) * (N + 1)) + clim = (0, 1) + data = np.linspace(*clim, num=(M + 1) * (N + 1)) # create the mesh from the synthetic data name = "Synthetic Points" @@ -44,11 +45,7 @@ def main() -> None: plotter = gv.GeoPlotter(crs=crs) sargs = {"title": f"{name} / 1", "shadow": True} plotter.add_mesh( - mesh, - clim=(0, 1), - cmap="tempo", - scalar_bar_args=sargs, - show_edges=True, + mesh, clim=clim, cmap="tempo", scalar_bar_args=sargs, show_edges=True ) plotter.add_coastlines() plotter.add_axes() @@ -59,7 +56,6 @@ def main() -> None: shadow=True, ) plotter.view_xy() - plotter.camera.zoom(1.5) plotter.show() diff --git a/src/geovista/examples/from_points__orca_cloud_eqc.py b/src/geovista/examples/from_points__orca_cloud_eqc.py index cbc78461..111ecb61 100755 --- a/src/geovista/examples/from_points__orca_cloud_eqc.py +++ b/src/geovista/examples/from_points__orca_cloud_eqc.py @@ -60,7 +60,6 @@ def main() -> None: plotter.add_base_layer(texture=gv.natural_earth_1(), opacity=0.5, zlevel=0) plotter.add_axes() plotter.view_xy() - plotter.camera.zoom(1.5) plotter.add_text( f"ORCA Point-Cloud ({crs})", position="upper_left", diff --git a/src/geovista/examples/from_unstructured__fesom_fouc.py b/src/geovista/examples/from_unstructured__fesom_fouc.py index 7517a251..c14dcb13 100755 --- a/src/geovista/examples/from_unstructured__fesom_fouc.py +++ b/src/geovista/examples/from_unstructured__fesom_fouc.py @@ -60,7 +60,6 @@ def main() -> None: shadow=True, ) plotter.view_xy() - plotter.camera.zoom(1.5) plotter.show() diff --git a/src/geovista/examples/from_unstructured__fvcom.py b/src/geovista/examples/from_unstructured__fvcom.py index c0653e92..8588bfb3 100755 --- a/src/geovista/examples/from_unstructured__fvcom.py +++ b/src/geovista/examples/from_unstructured__fvcom.py @@ -50,9 +50,7 @@ def main() -> None: # plot the mesh plotter = gv.GeoPlotter() sargs = {"title": f"{sample.name} / {sample.units}", "shadow": True} - plotter.add_mesh( - mesh, cmap="deep", scalars="face", show_edges=True, scalar_bar_args=sargs - ) + plotter.add_mesh(mesh, cmap="deep", scalars="face", scalar_bar_args=sargs) plotter.add_axes() plotter.add_text( "PML FVCOM Tamar", diff --git a/src/geovista/examples/from_unstructured__icon.py b/src/geovista/examples/from_unstructured__icon.py index 64cba346..76fd662f 100755 --- a/src/geovista/examples/from_unstructured__icon.py +++ b/src/geovista/examples/from_unstructured__icon.py @@ -41,7 +41,7 @@ def main() -> None: plotter = gv.GeoPlotter() sargs = {"title": f"{sample.name} / {sample.units}", "shadow": True} cmap = mpl.colormaps.get_cmap("cet_CET_L17").resampled(lutsize=9) - plotter.add_mesh(mesh, cmap=cmap, show_edges=True, scalar_bar_args=sargs) + plotter.add_mesh(mesh, cmap=cmap, scalar_bar_args=sargs) plotter.add_coastlines() plotter.add_axes() plotter.add_text( diff --git a/src/geovista/examples/from_unstructured__icon_eqc.py b/src/geovista/examples/from_unstructured__icon_eqc.py index 5f8d144d..3eac77d1 100755 --- a/src/geovista/examples/from_unstructured__icon_eqc.py +++ b/src/geovista/examples/from_unstructured__icon_eqc.py @@ -43,7 +43,7 @@ def main() -> None: plotter = gv.GeoPlotter(crs=crs) sargs = {"title": f"{sample.name} / {sample.units}", "shadow": True} cmap = mpl.colormaps.get_cmap("cet_CET_L17").resampled(lutsize=9) - plotter.add_mesh(mesh, cmap=cmap, show_edges=True, scalar_bar_args=sargs) + plotter.add_mesh(mesh, cmap=cmap, scalar_bar_args=sargs) plotter.add_coastlines() plotter.add_axes() plotter.add_text( @@ -53,7 +53,6 @@ def main() -> None: shadow=True, ) plotter.view_xy() - plotter.camera.zoom(1.5) plotter.show() diff --git a/src/geovista/examples/from_unstructured__icosahedral_poly.py b/src/geovista/examples/from_unstructured__icosahedral_poly.py index 63f1c851..1e3f33ec 100755 --- a/src/geovista/examples/from_unstructured__icosahedral_poly.py +++ b/src/geovista/examples/from_unstructured__icosahedral_poly.py @@ -51,7 +51,6 @@ def main() -> None: shadow=True, ) plotter.view_xy() - plotter.camera.zoom(1.5) plotter.show() diff --git a/src/geovista/examples/from_unstructured__lam_pacific_moll.py b/src/geovista/examples/from_unstructured__lam_pacific_moll.py index 773c68c7..3a9cfd7e 100755 --- a/src/geovista/examples/from_unstructured__lam_pacific_moll.py +++ b/src/geovista/examples/from_unstructured__lam_pacific_moll.py @@ -56,7 +56,6 @@ def main() -> None: shadow=True, ) plotter.view_xy() - plotter.camera.zoom(1.5) plotter.show() diff --git a/src/geovista/examples/from_unstructured__lfric_orog_warp.py b/src/geovista/examples/from_unstructured__lfric_orog_warp.py index fa495dd3..a4a9264a 100755 --- a/src/geovista/examples/from_unstructured__lfric_orog_warp.py +++ b/src/geovista/examples/from_unstructured__lfric_orog_warp.py @@ -50,7 +50,7 @@ def main() -> None: # plot the mesh plotter = gv.GeoPlotter() sargs = {"title": f"{sample.name} / {sample.units}", "shadow": True} - plotter.add_mesh(mesh, show_edges=True, scalar_bar_args=sargs) + plotter.add_mesh(mesh, scalar_bar_args=sargs) plotter.add_axes() plotter.add_text( "LFRic C48 Unstructured Cube-Sphere", diff --git a/src/geovista/examples/from_unstructured__lfric_sst.py b/src/geovista/examples/from_unstructured__lfric_sst.py index 022e08a9..71a2abea 100755 --- a/src/geovista/examples/from_unstructured__lfric_sst.py +++ b/src/geovista/examples/from_unstructured__lfric_sst.py @@ -46,7 +46,7 @@ def main() -> None: # plot the mesh plotter = gv.GeoPlotter() sargs = {"title": f"{sample.name} / {sample.units}", "shadow": True} - plotter.add_mesh(mesh, show_edges=True, scalar_bar_args=sargs) + plotter.add_mesh(mesh, scalar_bar_args=sargs) plotter.add_base_layer(texture=gv.natural_earth_1()) plotter.add_coastlines() plotter.add_graticule() diff --git a/src/geovista/examples/from_unstructured__lfric_sst_bonne.py b/src/geovista/examples/from_unstructured__lfric_sst_bonne.py index 816488e9..a7da21f6 100755 --- a/src/geovista/examples/from_unstructured__lfric_sst_bonne.py +++ b/src/geovista/examples/from_unstructured__lfric_sst_bonne.py @@ -48,7 +48,7 @@ def main() -> None: crs = "+proj=bonne +lat_1=10 +lon_0=180" plotter = gv.GeoPlotter(crs=crs) sargs = {"title": f"{sample.name} / {sample.units}", "shadow": True} - plotter.add_mesh(mesh, show_edges=True, scalar_bar_args=sargs) + plotter.add_mesh(mesh, scalar_bar_args=sargs) plotter.add_base_layer(texture=gv.natural_earth_1()) plotter.add_coastlines() plotter.add_graticule() @@ -60,7 +60,6 @@ def main() -> None: shadow=True, ) plotter.view_xy() - plotter.camera.zoom(1.5) plotter.show() diff --git a/src/geovista/examples/from_unstructured__smc_sinu.py b/src/geovista/examples/from_unstructured__smc_sinu.py index e32e4f08..a87fdca8 100755 --- a/src/geovista/examples/from_unstructured__smc_sinu.py +++ b/src/geovista/examples/from_unstructured__smc_sinu.py @@ -54,7 +54,6 @@ def main() -> None: shadow=True, ) plotter.view_xy() - plotter.camera.zoom(1.5) plotter.show() diff --git a/src/geovista/examples/from_unstructured__tri.py b/src/geovista/examples/from_unstructured__tri.py index ec265844..052b7ab1 100755 --- a/src/geovista/examples/from_unstructured__tri.py +++ b/src/geovista/examples/from_unstructured__tri.py @@ -42,7 +42,7 @@ def main() -> None: # plot the mesh plotter = gv.GeoPlotter() sargs = {"title": f"{sample.name} / {sample.units}", "shadow": True} - plotter.add_mesh(mesh, show_edges=True, scalar_bar_args=sargs) + plotter.add_mesh(mesh, scalar_bar_args=sargs) plotter.add_base_layer(texture=gv.natural_earth_hypsometric()) plotter.add_coastlines() plotter.add_axes() diff --git a/src/geovista/examples/from_unstructured__tri_hammer.py b/src/geovista/examples/from_unstructured__tri_hammer.py index 2ab16b21..d0ebaa2f 100755 --- a/src/geovista/examples/from_unstructured__tri_hammer.py +++ b/src/geovista/examples/from_unstructured__tri_hammer.py @@ -48,7 +48,7 @@ def main() -> None: crs = "+proj=hammer" plotter = gv.GeoPlotter(crs=crs) sargs = {"title": f"{sample.name} / {sample.units}", "shadow": True} - plotter.add_mesh(mesh, show_edges=True, scalar_bar_args=sargs, scalars=sample.name) + plotter.add_mesh(mesh, scalar_bar_args=sargs, scalars=sample.name) plotter.add_base_layer(texture=gv.natural_earth_hypsometric()) plotter.add_coastlines() plotter.add_axes() @@ -59,7 +59,6 @@ def main() -> None: shadow=True, ) plotter.view_xy() - plotter.camera.zoom(1.5) plotter.show() diff --git a/src/geovista/examples/vectors.py b/src/geovista/examples/vectors.py index 157d6298..d57db9b5 100755 --- a/src/geovista/examples/vectors.py +++ b/src/geovista/examples/vectors.py @@ -9,16 +9,15 @@ from __future__ import annotations import numpy as np -import pyvista as pv import geovista as gv +from geovista.samples import regular_grid import geovista.theme # noqa: F401 def main() -> None: """Create vectors plotting inspired by cartopy.""" - # make cool swirly pattern - sphere = pv.Sphere(gv.common.RADIUS) + sphere = regular_grid(resolution="r25") vectors = np.vstack( ( -sphere.points[:, 1], @@ -33,7 +32,6 @@ def main() -> None: plotter = gv.GeoPlotter() plotter.add_base_layer(texture=gv.natural_earth_1(), zlevel=0, lighting=False) plotter.add_mesh(sphere.arrows, lighting=False) - plotter.camera.zoom(1.5) plotter.add_axes() plotter.show() diff --git a/src/geovista/geoplotter.py b/src/geovista/geoplotter.py index 545175a4..1059c6ad 100644 --- a/src/geovista/geoplotter.py +++ b/src/geovista/geoplotter.py @@ -745,8 +745,10 @@ def add_meridians( .. versionadded:: 0.3.0 """ + from . import GEOVISTA_IMAGE_TESTING + if show_labels is None: - show_labels = GRATICULE_SHOW_LABELS + show_labels = False if GEOVISTA_IMAGE_TESTING else GRATICULE_SHOW_LABELS if zlevel is None: zlevel = ZTRANSFORM_FACTOR if self.crs.is_projected else GRATICULE_ZLEVEL @@ -924,8 +926,10 @@ def add_parallels( .. versionadded:: 0.3.0 """ + from . import GEOVISTA_IMAGE_TESTING + if show_labels is None: - show_labels = GRATICULE_SHOW_LABELS + show_labels = False if GEOVISTA_IMAGE_TESTING else GRATICULE_SHOW_LABELS if zlevel is None: zlevel = ZTRANSFORM_FACTOR if self.crs.is_projected else GRATICULE_ZLEVEL diff --git a/src/geovista/registry.txt b/src/geovista/registry.txt index 7ffdd97c..a2d23643 100644 --- a/src/geovista/registry.txt +++ b/src/geovista/registry.txt @@ -4,10 +4,6 @@ mesh/lfric_c96.vtk.bz2 ba62e4c9fa4831e7617a36e763cd596e0b9eb54fbdedc76bf9fa527c7 natural_earth/physical/ne_coastlines_10m.vtk.bz2 404e2c5908f5dba71bd3bfe21e6e0295e5a5f316344e1b8c4f94774edd00d15b natural_earth/physical/ne_coastlines_110m.vtk.bz2 c7094c14661563e6dc80bcf22556eeef31539a52c6c655bf01264527c5993f12 natural_earth/physical/ne_coastlines_50m.vtk.bz2 12f393aac2d4861770a3e584f1fbabd96e3dda3a100ab2ff6318223bdb24d90d -raster/HYP_50M_SR_W.jpg 837755a2871305b3f61d5980aee3b4b795e715bb6e7e7aa40e36b2e64aa511bd -raster/NE1_50M_SR_W.jpg 1a39003fe5fabb82a6816e79bb6bbe8215a8060d2e437b110f265ced5efdbbe7 -raster/uv-checker-map-4k.png b9ad63b29f85aed2ef2a6d630028d50a63f3a9050a3f9f0b291dcb5878adf820 -raster/world.topo.bathy.200412.3x5400x2700.jpg a9f0088972dee0254610af851c4d6838ca3f2cf79176987e0a5713e2c15ec042 pantry/c768/cloud_amount_high.nc.bz2 64363097a93616e29bdd98074ff02827a8404a27f1e5ebac8a6ea6a7d423f88f pantry/c768/cloud_amount_low.nc.bz2 303e02792fa8e11e384bc373f351a2edc39c650b0dbc81f2a1d84b6b4a176329 pantry/c768/cloud_amount_medium.nc.bz2 0a869e322e5a2b2bf3a13b36044ce79fcb84e401e4e520f8b2af8e8c8871490b @@ -31,4 +27,44 @@ pantry/votemper.nc.bz2 1ff98424be74840434a42d955ffae12d277e34014f0356fba2dd7a0eb pantry/votemper-gradient.nc.bz2 75fe715ff45b0112375867dddbf61a01df6d0cd5e212eaea0bf05bcb1f55d1b6 pantry/ww3/ww3_gbl_tri_hs.nc.bz2 3bdd71d799bb4532bb4c2868ac64c3062dceb7c43393602f5f66664822ef97ac pantry/ww3/ww3_gbl_smc_hs.nc.bz2 d5764f8b2461f8587d1a5946f6c650b6a59e5325ef9ce015a323f7498e8dc3d0 +raster/HYP_50M_SR_W.jpg 837755a2871305b3f61d5980aee3b4b795e715bb6e7e7aa40e36b2e64aa511bd +raster/NE1_50M_SR_W.jpg 1a39003fe5fabb82a6816e79bb6bbe8215a8060d2e437b110f265ced5efdbbe7 +raster/uv-checker-map-4k.png b9ad63b29f85aed2ef2a6d630028d50a63f3a9050a3f9f0b291dcb5878adf820 +raster/world.topo.bathy.200412.3x5400x2700.jpg a9f0088972dee0254610af851c4d6838ca3f2cf79176987e0a5713e2c15ec042 +tests/images/clouds.png 227b833d01d82b30cb9c016e3652c4b3ef4196f9368befbc2f118eeae835a714 +tests/images/clouds_robin.png 9bb1883bb3899f1c7a474a1ef14eca527263d8d1a4c988d845c96be297b0117a +tests/images/earthquakes.png 995b3777e18cdaf1677a96d5a5dc0918a75e679167b9074c2841d1ffd88295b0 +tests/images/earthquakes_wink1.png bb929dc99d2ea390a217929a9217fc7b1e7828639cd9a0fcc51484b342b88d6f +tests/images/from_1d__oisst_eqc.png 5e8c62933238d08baaa09eb6f12f7d5e47e731023bbe8600b75dc96c1ef21971 +tests/images/from_1d__oisst.png 92da94eb490eaa72acca8938e22edacf530b43a15bd215633bb6111d5380bfba +tests/images/from_1d__synthetic_face_m1_n1.png 917dca4452ab0c2fbe1fccfe8439855f3cd1becb8b73c2424086a622d3867c62 +tests/images/from_1d__synthetic_face_m1_n1_robin.png 0f5e20354da5776735b91ab0889d3beae99e5578426a1383a67408cbc103b54e +tests/images/from_1d__synthetic_node_m1_n1_moll.png e6bb80546c4131f6431736746967b155240e0ef741767aa2a8dbd88adbd0bfaa +tests/images/from_1d__synthetic_node_m1_n1.png 8ba6bf5afceb10375df4d95acf7391e5d979ac4945806091ee99928771139b2f +tests/images/from_2d__orca_moll.png 576dd061b242aa2fd55166381aed91f9068aed508ed6b832186a326523855072 +tests/images/from_2d__orca.png 8552e2010bb058f190694cc7da3d096988edb30406b2d9ad1d64165eaa2f836a +tests/images/from_2d__synthetic_face_m1n1.png 9ef84bc7a41d237063f819b56a122b943e856f62f402bf16ca0d53f17284bfb9 +tests/images/from_2d__synthetic_face_m1n1_robin.png 9c837e028c1ad33cc867d42f0d8e129bb932fd076476249bccb8e4abf6156225 +tests/images/from_2d__synthetic_node_m1n1_moll.png d93e9907bd306b95c2e67b5ed99466f08edb3cdd35de85bb4026076422f9b5e3 +tests/images/from_2d__synthetic_node_m1n1.png 9d00923e48dad39ba03d903b18a5151ffb085b98e8eba4ad0601ad8630aef5b8 +tests/images/from_points__orca_cloud_eqc.png 0da4c929670685e5cdbf363ca402df9d13f7d577b0a7c9eb9ed60f53d7c2206e +tests/images/from_points__orca_cloud.png 2698626824f4a40f99f98e8bf636e4e883c31c53f3a4851d87bf31a807ab3bcf +tests/images/from_unstructured__fesom_fouc.png 8a8de06cd8f18e9758c05baf3bad77b570bca883cb7f5f2ef4f3c3164e7cfe9d +tests/images/from_unstructured__fesom.png 3a5463d807a8951072a33e0b8f9f83eedfea8ab9b41cd1a13ae4689478013e39 +tests/images/from_unstructured__fvcom.png 969a121c2e2af298f5bac742634f72dddf916d2dd929a4572d6e6f89c22eacbc +tests/images/from_unstructured__icon_eqc.png 442ec422eb1069041212acee9133efa5bfd5f2f9e2b15612eb2818c93887be56 +tests/images/from_unstructured__icon.png a505dafa655d08fa2ce38343a0ae860089b1733aaf818a7397dcfaebe187b917 +tests/images/from_unstructured__icosahedral.png b36ac3c2150ec34fc287ec1ec49370cf31f6f3a0bc62801fb29723194e2d198a +tests/images/from_unstructured__icosahedral_poly.png 13afe9e5cd9de728c445b1e089f166353a18ce1d0e16e1ab7f71c5f13b3a3ad6 +tests/images/from_unstructured__lam_pacific_moll.png aaf8a550bf37c4cb32d43c61fd6bf2aa427a5292f7e7c599392e771fac762a42 +tests/images/from_unstructured__lam_pacific.png 25558c7d0ecc3333661c85aff89fea74e2420f0b5e8296b8108237cdf62f0492 +tests/images/from_unstructured__lfric_orog.png 0a144ea930023718003b2e511f109111f244fbbc71e58dc5f8528477b240099a +tests/images/from_unstructured__lfric_orog_warp.png 1de1341d896af0c57f2419d76fe5e27b6c14c69b9320f2395c367e2e030aaf71 +tests/images/from_unstructured__lfric_sst_bonne.png 273a3b4eedad61b690f7251eba788c092bd0d1df9ac665739637a24689c4c763 +tests/images/from_unstructured__lfric_sst.png 29e1556282260a3a27faec5dd4bbe477c21bac40520e73b931dc4b0b84b299a0 +tests/images/from_unstructured__smc.png 976987bcfdc25610b085efa2c538e2ff9125f0e5e3f0fa0dffe7214189e53a6a +tests/images/from_unstructured__smc_sinu.png c4e05cf1d4a8bea4da32aecb21a053a929198387c7722049bb8356e6b353da85 +tests/images/from_unstructured__tri_hammer.png 5105b32b3d52fe35e39f41e41645120b3b8021db3d144825f2214b909d71026e +tests/images/from_unstructured__tri.png 327027e93d74e202d0fd31f03b47de4f4c9e7870dbed8ac84b5aecd5760ddf95 +tests/images/vectors.png fdfea84491039fa742b73e610b834272d73eac7a70ab6b8c6e9b51019c0e1a95 tos_Omon_AWI-ESM-1-1-LR_historical_r1i1p1f1_gn_185001-185012.nc 28a8f7b8f3d8edded39a7b9c93afe54aeefbf271086927c7f684207327acc882 diff --git a/src/geovista/theme.py b/src/geovista/theme.py index d0e2f525..f74ab628 100644 --- a/src/geovista/theme.py +++ b/src/geovista/theme.py @@ -9,6 +9,8 @@ import pyvista as pv +from . import GEOVISTA_IMAGE_TESTING + theme = pv.themes.Theme() theme.name = "geovista" theme.background = (1.0, 1.0, 1.0) @@ -18,4 +20,8 @@ theme.font.color = (0.0, 0.0, 0.0) theme.outline_color = (0.0, 0.0, 0.0) theme.title = "GeoVista" -pv.global_theme.load_theme(theme) + +if not GEOVISTA_IMAGE_TESTING: + # only load the geovista theme if we're not performing image testing, + # as the default pyvista testing theme is adopted instead + pv.global_theme.load_theme(theme) diff --git a/tests/plotting/image_cache/earthquakes.png b/tests/plotting/image_cache/earthquakes.png deleted file mode 100644 index a205abaa..00000000 Binary files a/tests/plotting/image_cache/earthquakes.png and /dev/null differ diff --git a/tests/plotting/test_examples.py b/tests/plotting/test_examples.py index 9625908c..4730b108 100644 --- a/tests/plotting/test_examples.py +++ b/tests/plotting/test_examples.py @@ -1,17 +1,64 @@ """Unit-tests for :mod:`geovista.examples`.""" +import importlib +import os +from pathlib import Path +import pkgutil +import shutil import pytest import pyvista as pv -from geovista.examples import earthquakes +import geovista as gv +from geovista.cache import CACHE +import geovista.examples +# determine whether executing on a GHA runner +# https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables +CI: bool = os.environ.get("CI", "false").lower() == "true" + +# construct list of example script names +SCRIPTS = sorted( + [submodule.name for submodule in pkgutil.iter_modules(gv.examples.__path__)] +) + +# prepare geovista/pyvista for off-screen image testing +pv.global_theme.load_theme(pv.plotting.themes._TestingTheme()) pv.OFF_SCREEN = True +gv.GEOVISTA_IMAGE_TESTING = True + +# prepare to download image cache for each image test +# also see reference in pyproject.toml +cache_dir = Path(__file__).parent.resolve() / "image_cache" +if cache_dir.is_dir() and not cache_dir.is_symlink(): + # remove directory which will have been created by pytest-pyvista + # when plugin is bootstrapped by pytest + shutil.rmtree(str(cache_dir)) +if not cache_dir.exists(): + # create the symbolic link to the pooch cache + cache_dir.symlink_to(CACHE.abspath / "tests" / "images") + +# individual GHA CI test case exceptions to the default image tolerances +thresholds = { + "from_points__orca_cloud": {"warning_value": 202.0}, + "from_points__orca_cloud_eqc": {"warning_value": 250.0}, +} @pytest.mark.image -def test_earthquakes(verify_image_cache): - """Image test of earthquakes example.""" - verify_image_cache.high_variance_test = True - verify_image_cache.var_error_value = (value := 4470) - verify_image_cache.var_warning_value = value - earthquakes.main() +@pytest.mark.parametrize("script", SCRIPTS) +def test(script, verify_image_cache): + """Image test the example scripts.""" + # apply individual test case image tolerance exceptions only when + # executing within a remote GHA runner environment + if CI and script in thresholds: + for attr, value in thresholds[script].items(): + setattr(verify_image_cache, attr, value) + + verify_image_cache.test_name = f"test_{script}" + # import the example script + module = importlib.import_module(f"geovista.examples.{script}") + # if necessary, download and cache missing script base image (expected) to + # compare with the actual test image generated via pytest-pyvista plugin + _ = CACHE.fetch(f"tests/images/{script}.png") + # execute the example script for image testing + module.main() diff --git a/tox.ini b/tox.ini index 158efe42..c7d5f96f 100644 --- a/tox.ini +++ b/tox.ini @@ -61,6 +61,7 @@ description = platform = linux passenv = + CI CODECOV_TOKEN POST_COMMAND setenv = @@ -68,7 +69,6 @@ setenv = usedevelop = true commands = - mkdir --parents {toxinidir}/test_images pytest {posargs} --fail_extra_image_cache --generated_image_dir {toxinidir}{/}test_images {env:POST_COMMAND:}