diff --git a/changelog/772.bugfix.rst b/changelog/772.bugfix.rst new file mode 100644 index 000000000..278bb0d32 --- /dev/null +++ b/changelog/772.bugfix.rst @@ -0,0 +1 @@ +Fix support for astropy 7.0, this involved a change to ``CompoundLowLevelWCS`` so that in handles ``pixel_bounds`` if only one component WCS sets a pixel bound. diff --git a/ndcube/extra_coords/tests/test_lookup_table_coord.py b/ndcube/extra_coords/tests/test_lookup_table_coord.py index 7443691b0..e810d5b5f 100644 --- a/ndcube/extra_coords/tests/test_lookup_table_coord.py +++ b/ndcube/extra_coords/tests/test_lookup_table_coord.py @@ -526,7 +526,7 @@ def test_mtc_dropped_table_skycoord_join(lut_1d_time, lut_2d_skycoord_mesh): assert all(isinstance(u, str) for u in dwd["world_axis_units"]) assert dwd["world_axis_units"] == ["deg", "deg"] assert dwd["world_axis_physical_types"] == ["pos.eq.ra", "pos.eq.dec"] - assert dwd["world_axis_object_components"] == [("celestial", 0, "spherical.lon"), ("celestial", 1, "spherical.lat")] + assert [c[:2] for c in dwd["world_axis_object_components"]] == [("celestial", 0), ("celestial", 1)] assert wao_classes["celestial"][0] is SkyCoord assert dwd["value"] == [0*u.deg, 0*u.deg] diff --git a/ndcube/wcs/wrappers/compound_wcs.py b/ndcube/wcs/wrappers/compound_wcs.py index 4fb26d5a0..85aa49e22 100644 --- a/ndcube/wcs/wrappers/compound_wcs.py +++ b/ndcube/wcs/wrappers/compound_wcs.py @@ -111,12 +111,12 @@ def pixel_to_world_values(self, *pixel_arrays): world_arrays = [] for w in self._wcs: pixel_arrays_sub = pixel_arrays[:w.pixel_n_dim] - pixel_arrays = pixel_arrays[w.pixel_n_dim:] world_arrays_sub = w.pixel_to_world_values(*pixel_arrays_sub) if w.world_n_dim > 1: world_arrays.extend(world_arrays_sub) else: world_arrays.append(world_arrays_sub) + pixel_arrays = pixel_arrays[w.pixel_n_dim:] return tuple(world_arrays) def world_to_pixel_values(self, *world_arrays): @@ -174,14 +174,15 @@ def pixel_shape(self): @property def pixel_bounds(self): - if not any(w.pixel_bounds is None for w in self._wcs): - pixel_bounds = tuplesum(w.pixel_bounds for w in self._wcs) + if any(w.pixel_bounds is not None for w in self._wcs): + pixel_bounds = tuplesum(w.pixel_bounds or [tuple() for _ in range(w.pixel_n_dim)] for w in self._wcs) out_bounds = self.mapping.inverse(*pixel_bounds) for i, ix in enumerate(self.mapping.mapping): - if out_bounds[ix] != pixel_bounds[i]: + if pixel_bounds[i] and (out_bounds[ix] != pixel_bounds[i]): raise ValueError( "The pixel bounds of the supplied WCSes do not match for the dimensions shared by the supplied mapping.") - return out_bounds + iint = np.iinfo(int) + return tuple(o or (iint.min, iint.max) for o in out_bounds) @property def pixel_axis_names(self): diff --git a/ndcube/wcs/wrappers/tests/conftest.py b/ndcube/wcs/wrappers/tests/conftest.py index d7e98078b..329d9a1e3 100644 --- a/ndcube/wcs/wrappers/tests/conftest.py +++ b/ndcube/wcs/wrappers/tests/conftest.py @@ -1 +1,22 @@ +import pytest + +from astropy.wcs.wcsapi.conftest import Celestial2DLowLevelWCS as ApyCelestial2DLowLevelWCS from astropy.wcs.wcsapi.conftest import * # NOQA + + +class Celestial2DLowLevelWCS(ApyCelestial2DLowLevelWCS): + def __init__(self): + self._pixel_bounds = (-1, 5), (1, 7) + + @property + def pixel_bounds(self): + return self._pixel_bounds + + @pixel_bounds.setter + def pixel_bounds(self, val): + self._pixel_bounds = val + + +@pytest.fixture +def celestial_2d_ape14_wcs(): + return Celestial2DLowLevelWCS() diff --git a/ndcube/wcs/wrappers/tests/test_compound_wcs.py b/ndcube/wcs/wrappers/tests/test_compound_wcs.py index 5d38554d9..e3a4ce0bf 100644 --- a/ndcube/wcs/wrappers/tests/test_compound_wcs.py +++ b/ndcube/wcs/wrappers/tests/test_compound_wcs.py @@ -4,7 +4,8 @@ import pytest from numpy.testing import assert_allclose, assert_equal -from astropy import units as u +import astropy +import astropy.units as u from astropy.coordinates import SkyCoord from astropy.tests.helper import assert_quantity_allclose from astropy.units import Quantity @@ -31,7 +32,7 @@ def celestial_wcs(request): Array shape (Numpy order): (7, 6, 3) Pixel Dim Axis Name Data size Bounds - 0 None 3 (1, 2) + 0 None 3 (1, 3) 1 None 6 (-1, 5) 2 None 7 (1, 7) @@ -73,20 +74,28 @@ def test_celestial_spectral_ape14(spectral_wcs, celestial_wcs): # If any of the individual shapes are None, return None overall assert wcs.pixel_shape is None assert wcs.array_shape is None - assert wcs.pixel_bounds is None + assert wcs.pixel_bounds == ((np.iinfo(int).min, np.iinfo(int).max), (-1, 5), (1, 7)) # Set the shape and bounds on the spectrum and test again spectral_wcs.pixel_shape = (3,) - spectral_wcs.pixel_bounds = [(1, 2)] + spectral_wcs.pixel_bounds = [(1, 3)] assert wcs.pixel_shape == (3, 6, 7) assert wcs.array_shape == (7, 6, 3) - assert wcs.pixel_bounds == ((1, 2), (-1, 5), (1, 7)) + assert wcs.pixel_bounds == ((1, 3), (-1, 5), (1, 7)) pixel_scalar = (2.3, 4.3, 1.3) world_scalar = (-1.91e10, 5.4, -9.4) assert_allclose(wcs.pixel_to_world_values(*pixel_scalar), world_scalar) assert_allclose(wcs.array_index_to_world_values(*pixel_scalar[::-1]), world_scalar) - assert_allclose(wcs.world_to_pixel_values(*world_scalar), pixel_scalar) + assert_allclose(wcs.world_to_pixel_values(*world_scalar), pixel_scalar, equal_nan=True) + + assert str(wcs) == EXPECTED_CELESTIAL_SPECTRAL_APE14_REPR + assert EXPECTED_CELESTIAL_SPECTRAL_APE14_REPR in repr(wcs) + + # Not going to test too many things with out of bounds inputs + spectral_wcs.pixel_bounds = None + celestial_wcs.pixel_bounds = None + assert wcs.pixel_bounds is None assert_allclose(wcs.world_to_array_index_values(*world_scalar), [1, 4, 2]) pixel_array = (np.array([2.3, 2.4]), @@ -117,9 +126,6 @@ def test_celestial_spectral_ape14(spectral_wcs, celestial_wcs): assert_quantity_allclose(celestial.ra, world_array[1] * u.deg) assert_quantity_allclose(celestial.dec, world_array[2] * u.deg) - assert str(wcs) == EXPECTED_CELESTIAL_SPECTRAL_APE14_REPR - assert EXPECTED_CELESTIAL_SPECTRAL_APE14_REPR in repr(wcs) - def test_shared_pixel_axis_compound_1d(spectral_1d_fitswcs, time_1d_fitswcs): @@ -158,7 +164,7 @@ def test_shared_pixel_axis_compound_3d(spectral_cube_3d_fitswcs, time_1d_fitswcs assert wcs.pixel_n_dim == 3 np.testing.assert_allclose(wcs.pixel_shape, (10, 20, 30)) assert wcs.pixel_axis_names == ('', '', '') - assert wcs.pixel_bounds is None + assert wcs.pixel_bounds == ((-1, 5), (1, 7), (1, 2.5)) np.testing.assert_allclose(wcs.axis_correlation_matrix, [[True, True, False], [True, True, False], @@ -166,8 +172,12 @@ def test_shared_pixel_axis_compound_3d(spectral_cube_3d_fitswcs, time_1d_fitswcs [False, True, False]]) world = wcs.pixel_to_world_values(0, 0, 0) - np.testing.assert_allclose(world, (14, -12, -2.6e+10, -7.0)) - np.testing.assert_allclose(wcs.world_to_pixel_values(*world), (0, 0, 0)) + if astropy.__version__ >= "7.0.0.dev": + np.testing.assert_allclose(world, (14, np.nan, np.nan, -7.0)) + np.testing.assert_allclose(wcs.world_to_pixel_values(*world), (0, np.nan, np.nan)) + else: + np.testing.assert_allclose(world, (14, -12, -2.6e+10, -7.0)) + np.testing.assert_allclose(wcs.world_to_pixel_values(*world), (0, 0, 0)) with pytest.raises(ValueError): wcs.world_to_pixel_values((14, -12, -2.6e+10, -6.0)) diff --git a/ndcube/wcs/wrappers/tests/test_resampled_wcs.py b/ndcube/wcs/wrappers/tests/test_resampled_wcs.py index 295c8c161..7b7b945e4 100644 --- a/ndcube/wcs/wrappers/tests/test_resampled_wcs.py +++ b/ndcube/wcs/wrappers/tests/test_resampled_wcs.py @@ -86,13 +86,18 @@ def test_2d(celestial_wcs): assert_allclose(wcs.array_shape, (7/3, 15)) assert_allclose(wcs.pixel_bounds, ((-2.5, 12.5), (1/3, 7/3))) - pixel_scalar = (2.3, 4.3) - world_scalar = (12.16, 13.8) + pixel_scalar = (2.3, 1.2) + world_scalar = (12.16, -4.8) assert_allclose(wcs.pixel_to_world_values(*pixel_scalar), world_scalar) assert_allclose(wcs.array_index_to_world_values(*pixel_scalar[::-1]), world_scalar) assert_allclose(wcs.world_to_pixel_values(*world_scalar), pixel_scalar) - assert_allclose(wcs.world_to_array_index_values(*world_scalar), [4, 2]) + assert_allclose(wcs.world_to_array_index_values(*world_scalar), [1, 2]) + + EXPECTED_2D_REPR = EXPECTED_2D_REPR_NUMPY2 if np.__version__ >= '2.0.0' else EXPECTED_2D_REPR_NUMPY1 + assert str(wcs) == EXPECTED_2D_REPR + assert EXPECTED_2D_REPR in repr(wcs) + celestial_wcs.pixel_bounds = None pixel_array = (np.array([2.3, 2.4]), np.array([4.3, 4.4])) world_array = (np.array([12.16, 12.08]), @@ -115,16 +120,13 @@ def test_2d(celestial_wcs): assert_quantity_allclose(celestial.ra, world_array[0] * u.deg) assert_quantity_allclose(celestial.dec, world_array[1] * u.deg) - EXPECTED_2D_REPR = EXPECTED_2D_REPR_NUMPY2 if np.__version__ >= '2.0.0' else EXPECTED_2D_REPR_NUMPY1 - assert str(wcs) == EXPECTED_2D_REPR - assert EXPECTED_2D_REPR in repr(wcs) - @pytest.mark.parametrize('celestial_wcs', ['celestial_2d_ape14_wcs', 'celestial_2d_fitswcs'], indirect=True) def test_scalar_factor(celestial_wcs): + celestial_wcs.pixel_bounds = None wcs = ResampledLowLevelWCS(celestial_wcs, 2) pixel_scalar = (2.3, 4.3) @@ -139,6 +141,7 @@ def test_scalar_factor(celestial_wcs): ['celestial_2d_ape14_wcs', 'celestial_2d_fitswcs'], indirect=True) def test_offset(celestial_wcs): + celestial_wcs.pixel_bounds = None offset = 1 factor = 2 wcs = ResampledLowLevelWCS(celestial_wcs, factor, offset=offset)