From 8074ce875b3a277bfab620a5b8f902bff91434da Mon Sep 17 00:00:00 2001 From: max Date: Wed, 18 Jun 2025 14:04:41 +1000 Subject: [PATCH 1/7] adding testing --- src/accessvis/earth.py | 8 +- src/accessvis/widgets/calendar_widget.py | 9 +- src/accessvis/widgets/season_widget.py | 11 +- src/accessvis/widgets/widget_base.py | 11 +- tests/test_earth_no_gpu.py | 479 +++++++++++++++++++++++ tests/test_import_accessvis.py | 2 +- tests/test_widgets.py | 160 ++++++++ 7 files changed, 669 insertions(+), 11 deletions(-) create mode 100644 tests/test_earth_no_gpu.py create mode 100644 tests/test_widgets.py diff --git a/src/accessvis/earth.py b/src/accessvis/earth.py index 36c2834..65e3a6a 100644 --- a/src/accessvis/earth.py +++ b/src/accessvis/earth.py @@ -15,7 +15,7 @@ import py360convert import quaternion as quat import xarray as xr -from PIL import Image, ImageFile +from PIL import Image from .utils import download, is_notebook, pushd @@ -426,7 +426,7 @@ def crop_img_uv(img, cropbox): return arr - elif isinstance(img, ImageFile.ImageFile): + elif isinstance(img, Image.Image): crop_regions = [] if u0 < 0: # wraps around the left side crop_regions.append( @@ -1419,7 +1419,7 @@ def vec_rotate(v, theta, axis): v : list/numpy.ndarray The 3 component vector theta : float - Angle in degrees + Angle in radians axis : list/numpy.ndarray The 3 component axis of rotation @@ -2418,7 +2418,7 @@ def plot_vectors_xr( alt = max_alt # Basis vector directions, on the 3d model. - normal = latlon_normal_vector(lat, lon) + normal = latlon_normal_vector(lat=lat, lon=lon) east = latlon_vector_to_east(lat=lat, lon=lon) north = latlon_vector_to_north(lat=lat, lon=lon) diff --git a/src/accessvis/widgets/calendar_widget.py b/src/accessvis/widgets/calendar_widget.py index bb6fdec..89ba2b6 100644 --- a/src/accessvis/widgets/calendar_widget.py +++ b/src/accessvis/widgets/calendar_widget.py @@ -54,6 +54,8 @@ def _make_mpl(self): ax.set_xticklabels(MONTH, size=20) ax.spines["polar"].set_color(self.text_colour) + ax.set_ylim([0, 10]) + # Make Colours: ax.bar(x=0, height=10, width=np.pi * 2, color="black") for i in range(12): @@ -90,7 +92,7 @@ def _update_mpl(self, fig, ax, date: datetime.datetime = None, show_year=True): position, 0, 0, - 8.5, + 7.5, facecolor="#fff", width=0.1, head_length=2, @@ -103,4 +105,7 @@ def _reset_mpl(self, fig, ax, **kwargs): """ fig.suptitle("") if self.arrow is not None: - self.arrow.remove() + try: + self.arrow.remove() + except ValueError: + pass # already removed diff --git a/src/accessvis/widgets/season_widget.py b/src/accessvis/widgets/season_widget.py index 7b1e6f4..e1cf321 100644 --- a/src/accessvis/widgets/season_widget.py +++ b/src/accessvis/widgets/season_widget.py @@ -68,6 +68,8 @@ def _make_mpl(self): ax.set_xticklabels(MONTH, size=20) ax.spines["polar"].set_color(self.text_colour) + ax.set_ylim([0, 10]) + # Colour background based on time of year: dec22_doy = datetime.date(2001, 12, 22).timetuple().tm_yday - 1 dec22 = np.pi * 2.0 * dec22_doy / 365 # summer solstice @@ -76,6 +78,8 @@ def _make_mpl(self): R, T = np.meshgrid(r, theta) ax.pcolormesh(T, R, T, cmap=cmap, shading="gouraud") + self._update_mpl(fig=fig, ax=ax) + return fig, ax def _update_mpl(self, fig, ax, date: datetime.datetime = None, show_year=True): @@ -106,7 +110,7 @@ def _update_mpl(self, fig, ax, date: datetime.datetime = None, show_year=True): position, 0, 0, - 8.5, + 7.5, # length of arrow facecolor="#fff", width=0.1, head_length=2, @@ -119,4 +123,7 @@ def _reset_mpl(self, fig, ax, **kwargs): """ fig.suptitle("") if self.arrow is not None: - self.arrow.remove() + try: + self.arrow.remove() + except ValueError: + pass # already removed diff --git a/src/accessvis/widgets/widget_base.py b/src/accessvis/widgets/widget_base.py index 4e58e32..b0a2615 100644 --- a/src/accessvis/widgets/widget_base.py +++ b/src/accessvis/widgets/widget_base.py @@ -194,7 +194,7 @@ def _make_pixels(self, **kwargs): canvas = self.fig.canvas canvas.draw() - pixels = np.asarray(canvas.buffer_rgba()) + pixels = np.asarray(canvas.buffer_rgba(), copy=True) self._reset_mpl(fig=self.fig, ax=self.ax, **kwargs) @@ -218,4 +218,11 @@ def list_widgets(): ------- List[String]: The name of the classes. """ - return [cls.__name__ for cls in Widget.__subclasses__()] + + def get_subclasses(cls): + # From https://stackoverflow.com/questions/3862310/how-to-find-all-the-subclasses-of-a-class-given-its-name + for subclass in cls.__subclasses__(): + yield from get_subclasses(subclass) + yield subclass.__name__ + + return [Widget.__name__] + list(get_subclasses(Widget)) diff --git a/tests/test_earth_no_gpu.py b/tests/test_earth_no_gpu.py new file mode 100644 index 0000000..c8a82cd --- /dev/null +++ b/tests/test_earth_no_gpu.py @@ -0,0 +1,479 @@ +""" +These tests cover functions in earth.py which do not require a GPU/lavavu. +""" + + +def test_array_to_rgba(): + import accessvis + import numpy as np + + data1 = np.array([[i + j for i in range(20)] for j in range(10)]) + + # Grays colourmap is monotonic, data1 is monotonic, out1 should be monotonic. + out1 = accessvis.array_to_rgba(values=data1, colourmap="Greys") + assert out1.shape == (10, 20, 4) + assert np.all(np.diff(out1[:, :, 0], axis=0) > 0) + assert np.all(np.diff(out1[:, :, 1], axis=0) > 0) + assert np.all(np.diff(out1[:, :, 2], axis=0) > 0) + assert np.all(np.diff(out1[:, :, 0], axis=1) > 0) + assert np.all(np.diff(out1[:, :, 1], axis=1) > 0) + assert np.all(np.diff(out1[:, :, 2], axis=1) > 0) + + assert np.all(out1[:, :, 3] == 255), "Should not be transparent" + + # opacity + out2 = accessvis.array_to_rgba(values=data1, colourmap="Greys", opacity=0.5) + assert np.all(out2[:, :, 3] == 127), "Should be transparent" + + # opacity map + out3 = accessvis.array_to_rgba(values=data1, colourmap="Greys", opacitymap=True) + assert np.all(np.diff(out3[:, :, 3], axis=0) > 0) + assert np.all(np.diff(out3[:, :, 3], axis=1) > 0) + + om1 = np.zeros_like(data1) + om1[:5] = 1 + out4 = accessvis.array_to_rgba(values=data1, colourmap="Greys", opacitymap=om1) + assert np.all(out4[:5, :, 3] == 255) + assert np.all(out4[5:, :, 3] == 0) + + # min/max + data2 = np.ones((10, 10)) + data2[:5] = 10 + out5 = accessvis.array_to_rgba( + values=data2, colourmap="Greys", minimum=2, maximum=8 + ) + assert np.all(out5[:5, :, :3] == 0) # all values should be equal + assert np.all(out5[5:, :, :3] == 255) # all values should be equal + + # Test colourmap changes output + out6 = accessvis.array_to_rgba(values=data1, colourmap="viridis") + assert np.any(out6 != out1), "Does not change with different colourmaps" + + +def test_normalise_array(): + import accessvis + import numpy as np + + arr1 = accessvis.normalise_array(np.array([1, 2, 3])) + exp1 = [0, 0.5, 1] + assert np.allclose(arr1, exp1) + + arr2 = accessvis.normalise_array(np.array([2, 3, 4]), minimum=1, maximum=5) + exp2 = [0.25, 0.5, 0.75] + assert np.allclose(arr2, exp2) + + arr3 = accessvis.normalise_array(np.array([1, 3, 5]), minimum=2, maximum=4) + exp3 = [0, 0.5, 1] + assert np.allclose(arr3, exp3) + + +def test_latlon_vector_to_north(): + """ + At the equator, all point directly upwards. + Should have length 1. + """ + import accessvis + import numpy as np + + vec1 = accessvis.latlon_vector_to_north(lat=0.0, lon=0.0) + vec2 = accessvis.latlon_vector_to_north(lat=0, lon=90) + vec3 = [0, 1, 0] + + assert np.allclose(vec1, vec2) + assert np.allclose(vec1, vec3) + + vec4 = accessvis.latlon_vector_to_north(lat=47.3, lon=88.1) + assert np.allclose(np.linalg.norm(vec4), 1), "doesn't have length 1." + + +def test_latlon_vector_to_east(): + import accessvis + import numpy as np + + vec1 = accessvis.latlon_vector_to_east(lat=0.0, lon=0.0) + vec2 = [1, 0, 0] + assert np.allclose(vec1, vec2) + + vec3 = accessvis.latlon_vector_to_east(lat=0, lon=-90) + vec4 = [0, 0, 1] + assert np.allclose(vec3, vec4) + + norm_vec = accessvis.latlon_vector_to_east(lat=87.3, lon=-25.1) + assert np.allclose(np.linalg.norm(norm_vec), 1), "doesn't have length 1." + + +def test_latlon_normal_vector(): + import accessvis + import numpy as np + + vec1 = accessvis.latlon_normal_vector(lat=0.0, lon=0.0) + vec2 = [0, 0, 1] + assert np.allclose(vec1, vec2) + + vec3 = accessvis.latlon_normal_vector(lat=0, lon=90) + vec4 = [1, 0, 0] + assert np.allclose(vec3, vec4) + + vec5 = accessvis.latlon_normal_vector(lat=90, lon=47.2) + vec6 = [0, 1, 0] + assert np.allclose(vec5, vec6) + + vec7 = accessvis.latlon_normal_vector(lat=-90, lon=47.2) + vec8 = [0, -1, 0] + assert np.allclose(vec7, vec8) + + norm_vec = accessvis.latlon_normal_vector(lat=-65.1, lon=-48.1) + assert np.allclose(np.linalg.norm(norm_vec), 1), "doesn't have length 1." + + +def test_normalise(): + import accessvis + import numpy as np + + vec1 = accessvis.normalise([1, -2, 3]) + mag = 14**0.5 + vec2 = [1 / mag, -2 / mag, 3 / mag] + assert np.allclose(vec1, vec2) + + +def test_magnitude(): + import accessvis + import numpy as np + + mag = accessvis.magnitude([1, -2, 3]) + assert np.allclose(mag, 14**0.5) + + +def test_vec_rotate(): + import accessvis + import numpy as np + + vec1 = np.array([1, 0, 0]) + vec2 = accessvis.vec_rotate(vec1, np.pi / 2, np.array([0, 0, 1])) + assert np.allclose(vec2, [0, 1, 0]) + + vec3 = np.array([0, 1, 0]) + vec4 = accessvis.vec_rotate(vec3, np.pi / 2, np.array([1, 0, 0])) + assert np.allclose(vec4, [0, 0, 1]) + + vec5 = np.array([0, 0, 1]) + vec6 = accessvis.vec_rotate(vec5, np.pi / 2, np.array([1, 0, 0])) + assert np.allclose(vec6, [0, -1, 0]) + + vec7 = np.array([0, 0, 1]) + vec8 = accessvis.vec_rotate(vec7, 7982.4, np.array([35, 0.878, 10.7])) + assert np.allclose(np.linalg.norm(vec8), 1), "Magnutude changes" + + +def test_crop_img_uv_numpy(): + """ + I make an array with half 1, half 2. + Various crops will have different 1s and 2s. + """ + import accessvis + import numpy as np + + arr = np.zeros((10, 20)) + arr[:, :10] = 1 + arr[:, 10:] = 2 + + cropbox1 = (0, 0), (0.5, 1) # left side + out1 = accessvis.crop_img_uv(img=arr, cropbox=cropbox1) + assert out1.shape == (10, 10), f"Left shape {out1.shape}" + assert np.all(out1 == 1), "Left sum" + + cropbox2 = (0.25, 0.25), (0.75, 0.75) # Middle + out2 = accessvis.crop_img_uv(img=arr, cropbox=cropbox2) + assert out2.shape == (5, 10), f"Middle shape {out2.shape}" + assert np.all(out2[:, :5] == 1), "middle 1" + assert np.all(out2[:, 5:] == 2), "middle 2" + + cropbox3 = (-0.25, 0), (0.25, 1) # overflow left + out3 = accessvis.crop_img_uv(img=arr, cropbox=cropbox3) + assert out3.shape == (10, 10), "overflow left shape" + assert np.all(out3[:, :5] == 2), "overflow left 1" + assert np.all(out3[:, 5:] == 1), "overflow left 2" + + cropbox4 = (0.75, 0), (1.25, 1) # overflow right + out4 = accessvis.crop_img_uv(img=arr, cropbox=cropbox4) + assert out4.shape == (10, 10), "overflow right shape" + assert np.all(out4[:, :5] == 2), "overflow right 1" + assert np.all(out4[:, 5:] == 1), "overflow right 2" + + +def test_crop_img_uv_pil(): + """ + I make a PIL image with half black, half white. + Various crops will have different black/white. + """ + import accessvis + import numpy as np + from PIL import Image + + arr = [ + [[0, 0, 0] if j < 10 else [255, 255, 255] for j in range(20)] for i in range(10) + ] + arr = np.uint8(arr) + image = Image.fromarray(arr) + + cropbox1 = (0, 0), (0.5, 1) # left side + out1 = np.array(accessvis.crop_img_uv(img=image, cropbox=cropbox1)) + assert out1.shape == (10, 10, 3), f"Left shape {out1.shape}" + assert np.all(out1 == 0), "Left sum" + + cropbox2 = (0.25, 0.25), (0.75, 0.75) # Middle + out2 = np.array(accessvis.crop_img_uv(img=image, cropbox=cropbox2)) + assert out2.shape == (5, 10, 3), f"Middle shape {out2.shape}" + assert np.all(out2[:, :5] == 0), "middle 1" + assert np.all(out2[:, 5:] == 255), "middle 2" + + cropbox3 = (-0.25, 0), (0.25, 1) # overflow left + out3 = np.array(accessvis.crop_img_uv(img=image, cropbox=cropbox3)) + assert out3.shape == (10, 10, 3), "overflow left shape" + assert np.all(out3[:, :5] == 255), "overflow left 1" + assert np.all(out3[:, 5:] == 0), "overflow left 2" + + cropbox4 = (0.75, 0), (1.25, 1) # overflow right + out4 = np.array(accessvis.crop_img_uv(img=image, cropbox=cropbox4)) + assert out4.shape == (10, 10, 3), "overflow right shape" + assert np.all(out4[:, :5] == 255), "overflow right 1" + assert np.all(out4[:, 5:] == 0), "overflow right 2" + + +def test_crop_img_lat_lon(): + """ + I make an array with half 1, half 2. + Various crops will have different 1s and 2s. + """ + import accessvis + import numpy as np + + arr = np.zeros((10, 20)) + arr[:, :10] = 1 + arr[:, 10:] = 2 + + cropbox1 = (90, -180), (-90, 0) # left side + out1 = accessvis.crop_img_lat_lon(img=arr, cropbox=cropbox1) + assert out1.shape == (10, 10), f"Left shape {out1.shape}" + assert np.all(out1 == 1), "Left sum" + + cropbox2 = (45, -90), (-45, 90) # Middle + out2 = accessvis.crop_img_lat_lon(img=arr, cropbox=cropbox2) + assert out2.shape == (5, 10), f"Middle shape {out2.shape}" + assert np.all(out2[:, :5] == 1), "middle 1" + assert np.all(out2[:, 5:] == 2), "middle 2" + + cropbox3 = (90, -270), (-90, -90) # overflow left + out3 = accessvis.crop_img_lat_lon(img=arr, cropbox=cropbox3) + assert out3.shape == (10, 10), "overflow left shape" + assert np.all(out3[:, :5] == 2), "overflow left 1" + assert np.all(out3[:, 5:] == 1), "overflow left 2" + + cropbox4 = (90, 90), (-90, 270) # overflow right + out4 = accessvis.crop_img_lat_lon(img=arr, cropbox=cropbox4) + assert out4.shape == (10, 10), "overflow right shape" + assert np.all(out4[:, :5] == 2), "overflow right 1" + assert np.all(out4[:, 5:] == 1), "overflow right 2" + + +def test_latlon_to_uv(): + import accessvis + + assert accessvis.latlon_to_uv(0, 0) == (0.5, 0.5) + assert accessvis.latlon_to_uv(90, -180) == (0, 0) + assert accessvis.latlon_to_uv(-90, 180) == (1, 1) + + +def test_uv_to_pixel(): + import accessvis + + assert accessvis.uv_to_pixel(u=0.1, v=0.8, width=1017, height=8256) == (101, 6604) + + +def test_latlon_to_pixel(): + import accessvis + + lon = 0.1 * 360 - 180 + lat = (1 - 0.8) * 180 - 90 # same as test_uv_to_pixel + assert accessvis.latlon_to_pixel(lon=lon, lat=lat, width=1017, height=8256) == ( + 101, + 6604, + ) + + +def test_lonlat_to_3D_true(): + import accessvis + import numpy as np + + # With Flattening + R = 6.371 + assert np.allclose( + accessvis.lonlat_to_3D_true(lon=0, lat=90, flattening=0), [0, R, 0] + ), "North" + assert np.allclose( + accessvis.lonlat_to_3D_true(lon=0, lat=-90, flattening=0), [0, -R, 0] + ), "South" + assert np.allclose( + accessvis.lonlat_to_3D_true(lon=0, lat=0, flattening=0), [0, 0, R] + ), "Equator1" + assert np.allclose( + accessvis.lonlat_to_3D_true(lon=180, lat=0, flattening=0), [0, 0, -R] + ), "Equator2" + assert np.allclose( + accessvis.lonlat_to_3D_true(lon=90, lat=0, flattening=0), [R, 0, 0] + ), "Equator3" + assert np.allclose( + accessvis.lonlat_to_3D_true(lon=270, lat=0, flattening=0), [-R, 0, 0] + ), "Equator4" + + # Altitude + assert np.allclose( + accessvis.lonlat_to_3D_true(lon=270, lat=0, alt=1, flattening=0), [-R - 1, 0, 0] + ), "Equator4" + assert np.allclose( + accessvis.lonlat_to_3D_true(lon=0, lat=90, alt=1, flattening=0), [0, R + 1, 0] + ), "North" + assert np.allclose( + accessvis.lonlat_to_3D_true(lon=0, lat=-90, alt=1, flattening=0), [0, -R - 1, 0] + ), "South" + assert np.allclose( + accessvis.lonlat_to_3D_true(lon=0, lat=0, alt=1, flattening=0), [0, 0, R + 1] + ), "Equator1" + assert np.allclose( + accessvis.lonlat_to_3D_true(lon=180, lat=0, alt=1, flattening=0), [0, 0, -R - 1] + ), "Equator2" + assert np.allclose( + accessvis.lonlat_to_3D_true(lon=90, lat=0, alt=1, flattening=0), [R + 1, 0, 0] + ), "Equator3" + assert np.allclose( + accessvis.lonlat_to_3D_true(lon=270, lat=0, alt=1, flattening=0), [-R - 1, 0, 0] + ), "Equator4" + + v1 = accessvis.lonlat_to_3D_true(lon=85.1, lat=63.48) + v2 = accessvis.lonlat_to_3D_true(lon=85.1, lat=63.48, alt=1) + v3 = accessvis.lonlat_to_3D_true(lon=85.1, lat=63.48, alt=2.5) + + assert np.allclose(np.linalg.norm(v2 - v1), 1), "Altitude==1 is incorrect" + assert np.allclose(np.linalg.norm(v3 - v1), 2.5), "Altitude==2.5 is incorrect" + + # Testing it works with array inputs + # Note: there is some code which depends on this being shaped this way. E.g. plot_vectors_xr + # alt 1 unit increments + arr1 = accessvis.lonlat_to_3D_true( + lon=23.7, lat=84.3, alt=np.array(range(10)), flattening=0 + ) + norm1 = np.linalg.norm(arr1[:, 1:] - arr1[:, :-1], axis=0) + assert arr1.shape == (3, 10) + assert norm1.shape == (9,) + assert np.allclose(norm1, 1) + + # lon 1 deg increments + arr2 = accessvis.lonlat_to_3D_true( + lon=np.array(range(-10, 10)), lat=84.3, alt=0, flattening=0 + ) + norm2 = np.linalg.norm(arr2[:, 1:] - arr2[:, :-1], axis=0) + assert arr2.shape == (3, 20) + assert norm2.shape == (19,) + assert np.allclose(norm2, norm2[0]) + + # lat 1 deg increments + arr3 = accessvis.lonlat_to_3D_true( + lon=46.4, lat=0.67 + np.array(range(-10, 10)), alt=0, flattening=0 + ) + norm3 = np.linalg.norm(arr3[:, 1:] - arr3[:, :-1], axis=0) + assert arr3.shape == (3, 20) + assert norm3.shape == (19,) + assert np.allclose(norm3, norm3[0]) + + # All are arrays + arr3 = accessvis.lonlat_to_3D_true( + lon=np.array(range(-10, 10)), + lat=0.67 + np.array(range(-10, 10)), + alt=np.array(range(20)), + ) + assert arr3.shape == (3, 20) + + +def test_latlon_to_3D(): + import accessvis + import numpy as np + + R = 6.371 + assert np.allclose(accessvis.latlon_to_3D(lon=0, lat=90), [0, R, 0]), "North" + assert np.allclose(accessvis.latlon_to_3D(lon=0, lat=-90), [0, -R, 0]), "South" + assert np.allclose(accessvis.latlon_to_3D(lon=0, lat=0), [0, 0, R]), "Equator1" + assert np.allclose(accessvis.latlon_to_3D(lon=180, lat=0), [0, 0, -R]), "Equator2" + assert np.allclose(accessvis.latlon_to_3D(lon=90, lat=0), [R, 0, 0]), "Equator3" + assert np.allclose(accessvis.latlon_to_3D(lon=270, lat=0), [-R, 0, 0]), "Equator4" + + +def test_lonlat_to_3D(): + import accessvis + import numpy as np + + R = 6.371 + assert np.allclose(accessvis.lonlat_to_3D(lon=0, lat=90), [0, R, 0]), "North" + assert np.allclose(accessvis.lonlat_to_3D(lon=0, lat=-90), [0, -R, 0]), "South" + assert np.allclose(accessvis.lonlat_to_3D(lon=0, lat=0), [0, 0, R]), "Equator1" + assert np.allclose(accessvis.lonlat_to_3D(lon=180, lat=0), [0, 0, -R]), "Equator2" + assert np.allclose(accessvis.lonlat_to_3D(lon=90, lat=0), [R, 0, 0]), "Equator3" + assert np.allclose(accessvis.lonlat_to_3D(lon=270, lat=0), [-R, 0, 0]), "Equator4" + + +def test_earth_vertices_to_3D(): + import accessvis + import numpy as np + + R = 6.371 + vertices = np.array( + [[0, 90, 0], [0, -90, 0], [0, 0, 0], [180, 0, 0], [90, 0, 0], [270, 0, 0]] + ) + + out = accessvis.earth_vertices_to_3D(vertices=vertices) + expected = np.array( + [[0, R, 0], [0, -R, 0], [0, 0, R], [0, 0, -R], [R, 0, 0], [-R, 0, 0]] + ) + assert np.allclose(out, expected) + + +def test_lonlat_grid_3D(): + import accessvis + import numpy as np + + out1 = accessvis.lonlat_grid_3D( + latitudes=(-30, 30), longitudes=(-75, 130), resolution=(20, 10) + ) + assert out1.shape == (20, 10, 3) + assert np.all(np.diff(out1[:, :, 1], axis=1) > 0), "Z direction does not increase" + + out2 = accessvis.lonlat_grid_3D( + latitudes=np.linspace(-30, 30, 10), longitudes=np.linspace(-75, 130, 20) + ) + assert np.allclose(out1, out2) + + out3 = accessvis.lonlat_grid_3D( + latitudes=np.array([-90, 0, 90]), + longitudes=np.array([0, 90, 180, 270]), + altitude=0, + ) + print(out3.shape) + print(out3) + R = 6.371 + exp3 = np.array( + [ + [[0, -R, 0], [0, 0, R], [0, R, 0]], + [[0, -R, 0], [R, 0, 0], [0, R, 0]], + [[0, -R, 0], [0, 0, -R], [0, R, 0]], + [[0, -R, 0], [-R, 0, 0], [0, R, 0]], + ] + ) + assert np.allclose(out3, exp3) + + +# def test_read_image():# TODO +# import accessvis +# accessvis.read_image() + +# def test_paste_image():# TODO +# import accessvis +# accessvis.paste_image() diff --git a/tests/test_import_accessvis.py b/tests/test_import_accessvis.py index 0e75d02..c1ee028 100644 --- a/tests/test_import_accessvis.py +++ b/tests/test_import_accessvis.py @@ -1,5 +1,5 @@ def test_import_accessvis(): try: - assert True + pass except ImportError: assert False, "Failed to import the 'accessvis' package" diff --git a/tests/test_widgets.py b/tests/test_widgets.py new file mode 100644 index 0000000..080ed1e --- /dev/null +++ b/tests/test_widgets.py @@ -0,0 +1,160 @@ +""" +These tests check the widgets change when they should, reset correctly, etc. +It does not check that the output looks the same (you may wish to change the appearance of the widgets). +""" + + +def test_import_all_widgets(): + import accessvis + + _ = accessvis.Widget + _ = accessvis.WidgetMPL + _ = accessvis.list_widgets + + _ = accessvis.CalendarWidget + _ = accessvis.ClockWidget + _ = accessvis.ImageWidget + _ = accessvis.SeasonWidget + _ = accessvis.TextWidget + + +def test_list_widgets(): + import accessvis + + assert set(accessvis.list_widgets()).issuperset( + {"CalendarWidget", "ClockWidget", "ImageWidget", "SeasonWidget", "TextWidget"} + ), accessvis.list_widgets() + + +def test_CalendarWidget(): + """ + Testing that CalendarWidget changes with different kwargs, and resets correctly. + """ + + from datetime import datetime + + import accessvis + import numpy as np + + widget = accessvis.CalendarWidget(None) + pixels1 = widget._make_pixels() + + time2 = datetime(2025, 6, 13) + pixels2 = widget._make_pixels(date=time2, show_year=True) + + pixels3 = widget._make_pixels(date=time2, show_year=False) + + time4 = datetime(2025, 1, 13) + pixels4 = widget._make_pixels(date=time4, show_year=False) + + pixels5 = widget._make_pixels() + + pixels6 = accessvis.CalendarWidget(None, text_colour="blue")._make_pixels() + + assert np.array_equal(pixels1, pixels5) # testing it resets correctly + assert not np.array_equal(pixels1, pixels2) # everything else should be different + assert not np.array_equal(pixels2, pixels3) # show year different + assert not np.array_equal(pixels3, pixels4) # different date + assert not np.array_equal(pixels1, pixels6) # text colour + + +def test_ClockWidget(): + """ + Testing that ClockWidget changes with different kwargs, and resets correctly. + """ + + from datetime import datetime + + import accessvis + import numpy as np + + widget = accessvis.ClockWidget(None) + pixels1 = widget._make_pixels() + + time2 = datetime(2025, 6, 13, 12, 30, 45) + pixels2 = widget._make_pixels(time=time2) + + pixels3 = widget._make_pixels() + + pixels4 = accessvis.ClockWidget(None, text_colour="blue")._make_pixels(time=time2) + pixels5 = accessvis.ClockWidget(None, background="blue")._make_pixels(time=time2) + pixels6 = accessvis.ClockWidget(None, show_seconds=True)._make_pixels(time=time2) + pixels7 = accessvis.ClockWidget(None, show_hours=False)._make_pixels(time=time2) + pixels8 = accessvis.ClockWidget(None, show_minutes=False)._make_pixels(time=time2) + + time9 = datetime(2025, 6, 13, 15, 30) + pixels9 = accessvis.ClockWidget(None)._make_pixels(time=time9) + + time10 = datetime(2025, 6, 13, 12, 45) + pixels10 = accessvis.ClockWidget(None)._make_pixels(time=time10) + + assert np.array_equal(pixels1, pixels3), "Did not reset correctly" + assert not np.array_equal(pixels1, pixels2), "Not showing hands" + assert not np.array_equal(pixels2, pixels4), "Text colour not changing" + assert not np.array_equal(pixels2, pixels5), "Background colour not changing" + assert not np.array_equal(pixels2, pixels6), "Second hand not showing" + assert not np.array_equal(pixels2, pixels7), "Hour hand not hidden" + assert not np.array_equal(pixels2, pixels8), "Minute hand not hidden" + assert not np.array_equal(pixels2, pixels9), "Hour not different" + assert not np.array_equal(pixels2, pixels10), "Minute not different" + + +def test_SeasonWidget(): + """ + Testing that SeasonWidget changes with different kwargs, and resets correctly. + """ + + from datetime import datetime + + import accessvis + import numpy as np + + widget = accessvis.SeasonWidget(None) + pixels1 = widget._make_pixels() + + time2 = datetime(2025, 6, 13) + pixels2 = widget._make_pixels(date=time2, show_year=True) + + pixels3 = widget._make_pixels(date=time2, show_year=False) + + time4 = datetime(2025, 1, 13) + pixels4 = widget._make_pixels(date=time4, show_year=False) + + pixels5 = widget._make_pixels() + + pixels6 = accessvis.SeasonWidget(None, text_colour="blue")._make_pixels() + pixels7 = accessvis.SeasonWidget(None, hemisphere="north")._make_pixels() + + assert np.array_equal(pixels1, pixels5), "Not resetting correctly" + assert not np.array_equal(pixels1, pixels2), "Not showing arrow" + assert not np.array_equal(pixels2, pixels3), "Not updating year" + assert not np.array_equal(pixels3, pixels4), "Not updating date" + assert not np.array_equal(pixels1, pixels6), "Not changing text colour" + assert not np.array_equal(pixels1, pixels7), "Not changing hemisphere colours" + + +def test_TextWidget(): + """ + Testing that TextWidget changes with different kwargs, and resets correctly. + """ + + import accessvis + import numpy as np + + widget = accessvis.TextWidget(None) + pixels1 = widget._make_pixels() + + pixels2 = widget._make_pixels(text="Hello") + + pixels3 = widget._make_pixels() + + pixels4 = accessvis.TextWidget(None, text_colour="blue")._make_pixels(text="Hello") + pixels5 = accessvis.TextWidget(None, background="blue")._make_pixels() + + assert np.array_equal(pixels1, pixels3), "fails to reset" + assert not np.array_equal(pixels1, pixels2), "not showing text" + assert not np.array_equal(pixels2, pixels4), "not changing text colour" + assert not np.array_equal(pixels1, pixels5), "not changing background colour" + + +# def test_ImageWidget(): # TODO From 313c41b3b0d114afa16bbb02415fefe387721a28 Mon Sep 17 00:00:00 2001 From: max Date: Wed, 18 Jun 2025 14:20:42 +1000 Subject: [PATCH 2/7] fixing import accessvis test --- tests/test_import_accessvis.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_import_accessvis.py b/tests/test_import_accessvis.py index c1ee028..250c117 100644 --- a/tests/test_import_accessvis.py +++ b/tests/test_import_accessvis.py @@ -1,5 +1,5 @@ def test_import_accessvis(): try: - pass + import accessvis # noqa: F401 except ImportError: assert False, "Failed to import the 'accessvis' package" From 5d20e5a6d38955fcd48016ef893b0bf5ed08ea1f Mon Sep 17 00:00:00 2001 From: max Date: Wed, 18 Jun 2025 15:01:03 +1000 Subject: [PATCH 3/7] changing testing to us lavavu-osmesa --- ci/environment-3.11.yml | 2 +- ci/environment-3.12.yml | 2 +- ci/environment-3.13.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ci/environment-3.11.yml b/ci/environment-3.11.yml index 13b38f8..5c4df1d 100644 --- a/ci/environment-3.11.yml +++ b/ci/environment-3.11.yml @@ -16,6 +16,6 @@ dependencies: - xarray - pip - pip: - - lavavu + - lavavu-osmesa - codecov - pytest-cov diff --git a/ci/environment-3.12.yml b/ci/environment-3.12.yml index 69c2a96..027f9e6 100644 --- a/ci/environment-3.12.yml +++ b/ci/environment-3.12.yml @@ -16,6 +16,6 @@ dependencies: - xarray - pip - pip: - - lavavu + - lavavu-osmesa - codecov - pytest-cov diff --git a/ci/environment-3.13.yml b/ci/environment-3.13.yml index 2a8c10a..b169179 100644 --- a/ci/environment-3.13.yml +++ b/ci/environment-3.13.yml @@ -16,6 +16,6 @@ dependencies: - xarray - pip - pip: - - lavavu + - lavavu-osmesa - codecov - pytest-cov From 39f90b5024617f711aecc09df951165881081c4a Mon Sep 17 00:00:00 2001 From: Owen Kaluza Date: Wed, 18 Jun 2025 17:14:22 +1000 Subject: [PATCH 4/7] Try moderngl for testing --- ci/environment-3.11.yml | 3 ++- ci/environment-3.12.yml | 3 ++- ci/environment-3.13.yml | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/ci/environment-3.11.yml b/ci/environment-3.11.yml index 5c4df1d..f542499 100644 --- a/ci/environment-3.11.yml +++ b/ci/environment-3.11.yml @@ -16,6 +16,7 @@ dependencies: - xarray - pip - pip: - - lavavu-osmesa + - lavavu + - moderngl - codecov - pytest-cov diff --git a/ci/environment-3.12.yml b/ci/environment-3.12.yml index 027f9e6..29db441 100644 --- a/ci/environment-3.12.yml +++ b/ci/environment-3.12.yml @@ -16,6 +16,7 @@ dependencies: - xarray - pip - pip: - - lavavu-osmesa + - lavavu + - moderngl - codecov - pytest-cov diff --git a/ci/environment-3.13.yml b/ci/environment-3.13.yml index b169179..53429c7 100644 --- a/ci/environment-3.13.yml +++ b/ci/environment-3.13.yml @@ -16,6 +16,7 @@ dependencies: - xarray - pip - pip: - - lavavu-osmesa + - lavavu + - moderngl - codecov - pytest-cov From b045313869c38d2ec696c9197f28170d3b049c32 Mon Sep 17 00:00:00 2001 From: Owen Kaluza Date: Wed, 18 Jun 2025 18:10:48 +1000 Subject: [PATCH 5/7] Use lavavu-osmesa for all conda envs/tests --- .conda/meta.yaml | 2 +- .github/workflows/CI.yml | 6 +++++- ci/environment-3.11.yml | 2 +- ci/environment-3.12.yml | 2 +- ci/environment-3.13.yml | 2 +- 5 files changed, 9 insertions(+), 5 deletions(-) diff --git a/.conda/meta.yaml b/.conda/meta.yaml index e919160..c2d2093 100644 --- a/.conda/meta.yaml +++ b/.conda/meta.yaml @@ -26,7 +26,7 @@ requirements: - xarray - tqdm - netcdf4 - - lavavu + - lavavu-osmesa - moderngl diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 413d896..7b14b01 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -40,10 +40,14 @@ jobs: - name: Run tests shell: bash -l {0} + env: + LV_CONTEXT: moderngl + LV_ECHO_FAIL: 1 + LV_ARGS: -v run: python -m pytest -s . - name: Upload code coverage uses: codecov/codecov-action@v5 with: token: ${{ secrets.codecov_token }} - files: ./coverage.xml \ No newline at end of file + files: ./coverage.xml diff --git a/ci/environment-3.11.yml b/ci/environment-3.11.yml index f542499..de82a3c 100644 --- a/ci/environment-3.11.yml +++ b/ci/environment-3.11.yml @@ -16,7 +16,7 @@ dependencies: - xarray - pip - pip: - - lavavu + - lavavu-osmesa - moderngl - codecov - pytest-cov diff --git a/ci/environment-3.12.yml b/ci/environment-3.12.yml index 29db441..84bee62 100644 --- a/ci/environment-3.12.yml +++ b/ci/environment-3.12.yml @@ -16,7 +16,7 @@ dependencies: - xarray - pip - pip: - - lavavu + - lavavu-osmesa - moderngl - codecov - pytest-cov diff --git a/ci/environment-3.13.yml b/ci/environment-3.13.yml index 53429c7..e784c41 100644 --- a/ci/environment-3.13.yml +++ b/ci/environment-3.13.yml @@ -16,7 +16,7 @@ dependencies: - xarray - pip - pip: - - lavavu + - lavavu-osmesa - moderngl - codecov - pytest-cov From 5d8c1da4e3dabe4d077b2772957812548735eccb Mon Sep 17 00:00:00 2001 From: Owen Kaluza Date: Wed, 18 Jun 2025 18:21:45 +1000 Subject: [PATCH 6/7] Use existing data dir on self hosted test runner --- .github/workflows/testing.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/testing.yaml b/.github/workflows/testing.yaml index 31d5f2a..9c0f312 100644 --- a/.github/workflows/testing.yaml +++ b/.github/workflows/testing.yaml @@ -43,6 +43,7 @@ jobs: LV_CONTEXT: moderngl LV_ECHO_FAIL: 1 LV_ARGS: -v + ACCESSVIS_DATA_DIR: /media/data/accessvis run: | python -m pip install moderngl freezegun git clone -b tests --depth 1 https://github.com/ACCESS-NRI/ACCESS-Visualisation-Recipes.git From 6c0bd003212c931ad1feb5ede46bda1a90ffc519 Mon Sep 17 00:00:00 2001 From: Owen Kaluza Date: Wed, 18 Jun 2025 20:10:11 +1000 Subject: [PATCH 7/7] Don't use the tests branch! It's in main now --- .github/workflows/testing.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/testing.yaml b/.github/workflows/testing.yaml index 9c0f312..e659e19 100644 --- a/.github/workflows/testing.yaml +++ b/.github/workflows/testing.yaml @@ -46,7 +46,7 @@ jobs: ACCESSVIS_DATA_DIR: /media/data/accessvis run: | python -m pip install moderngl freezegun - git clone -b tests --depth 1 https://github.com/ACCESS-NRI/ACCESS-Visualisation-Recipes.git + git clone --depth 1 https://github.com/ACCESS-NRI/ACCESS-Visualisation-Recipes.git cd ACCESS-Visualisation-Recipes/tests python tests.py thumbs