Skip to content

Commit

Permalink
Fix a whole bunch of bugs todo with -TAB in wcsapi/fitswcs
Browse files Browse the repository at this point in the history
  • Loading branch information
Cadair committed Apr 19, 2023
1 parent 0407c10 commit bf1b98b
Show file tree
Hide file tree
Showing 6 changed files with 53 additions and 21 deletions.
12 changes: 8 additions & 4 deletions astropy/wcs/wcsapi/fitswcs.py
Expand Up @@ -416,7 +416,11 @@ def _get_components_and_classes(self):
else:
kwargs = {}
kwargs["frame"] = celestial_frame
kwargs["unit"] = u.deg
# Very occasionally (i.e. with TAB) wcs does not convert the units to degrees
kwargs["unit"] = (
u.Unit(self.wcs.cunit[self.wcs.lng]),
u.Unit(self.wcs.cunit[self.wcs.lat]),
)

classes["celestial"] = (SkyCoord, (), kwargs)

Expand Down Expand Up @@ -445,7 +449,7 @@ def _get_components_and_classes(self):
earth_location = EarthLocation(*self.wcs.obsgeo[:3], unit=u.m)

# Get the time scale from TIMESYS or fall back to 'utc'
tscale = self.wcs.timesys or "utc"
tscale = self.wcs.timesys.lower() or "utc"

Check warning on line 452 in astropy/wcs/wcsapi/fitswcs.py

View check run for this annotation

Codecov / codecov/patch

astropy/wcs/wcsapi/fitswcs.py#L452

Added line #L452 was not covered by tests

if np.isnan(self.wcs.mjdavg):
obstime = Time(
Expand Down Expand Up @@ -695,8 +699,8 @@ def value_from_spectralcoord(spectralcoord):
# Initialize delta
reference_time_delta = None

# Extract time scale
scale = self.wcs.ctype[i].lower()
# Extract time scale, and remove any algorithm code
scale = self.wcs.ctype[i].split("-")[0].lower()

Check warning on line 703 in astropy/wcs/wcsapi/fitswcs.py

View check run for this annotation

Codecov / codecov/patch

astropy/wcs/wcsapi/fitswcs.py#L703

Added line #L703 was not covered by tests

if scale == "time":
if self.wcs.timesys:
Expand Down
1 change: 1 addition & 0 deletions astropy/wcs/wcsapi/tests/example_4d_tab.fits
@@ -0,0 +1 @@
SIMPLE = T / conforms to FITS standard BITPIX = 8 / array data type NAXIS = 0 / number of array dimensions EXTEND = T WCSAXES = 4 WCSNAME = 'CompositeFrame' CTYPE1 = 'GLON-TAB' CUNIT1 = 'arcsec ' PS1_0 = 'WCS-TABLE' PV1_1 = 1 PS1_1 = 'coordinates' PV1_3 = 1 CRVAL1 = 1 CRPIX1 = 1.0 PC1_1 = 1.0 CDELT1 = 1.0 CTYPE2 = 'GLAT-TAB' CUNIT2 = 'arcsec ' PS2_0 = 'WCS-TABLE' PV2_1 = 1 PS2_1 = 'coordinates' PV2_3 = 2 CRVAL2 = 1 CRPIX2 = 1.0 PC2_2 = 1.0 CDELT2 = 1.0 CTYPE3 = 'FREQ-TAB' CUNIT3 = 'Hz ' PS3_0 = 'WCS-TABLE' PV3_1 = 1 PS3_1 = 'coordinates' PV3_3 = 3 CRVAL3 = 1 CRPIX3 = 1.0 PC3_3 = 1.0 CDELT3 = 1.0 CTYPE4 = 'TIME-TAB' CUNIT4 = '' PS4_0 = 'WCS-TABLE' PV4_1 = 1 PS4_1 = 'coordinates' PV4_3 = 4 CRVAL4 = 1 CRPIX4 = 1.0 PC4_4 = 1.0 CDELT4 = 1.0 TIMESYS = 'UTC ' END XTENSION= 'BINTABLE' / binary table extension BITPIX = 8 / array data type NAXIS = 2 / number of array dimensions NAXIS1 = 3840 / length of dimension 1 NAXIS2 = 1 / length of dimension 2 PCOUNT = 0 / number of group parameters GCOUNT = 1 / number of groups TFIELDS = 1 / number of table fields TTYPE1 = 'coordinates' TFORM1 = '480D ' TDIM1 = '(4,5,4,3,2)' EXTNAME = 'WCS-TABLE' / extension name EXTVER = 1 / extension value END @m#Ȏ�,�U���]@8�\(�@<�@j�؉�=��V �\��@8�\(�@<�@f������V @8�\(�@<�@bJ'vG�H�V �\��@8�\(�@<�@_�n�Y��U���]@8�\(�@<�@nm���]��U����"@8�\(�@<�@l�@��+�V%~�ކ@8�\(�@<�@f������V@@8�\(�@<�@`�#�2��V%~�ޅ@8�\(�@<�@]$�2WD!�U����"@8�\(�@<�@o������U��Խ@8�\(�@<�@nm��qmh�V8rQ/��@8�\(�@<�@f������V`@8�\(�@<�@]$Fe%$�V8rQ/��@8�\(�@<�@Z�HW���U��Լ@8�\(�@<�@p��V@8�\(�@<�@p��V@@8�\(�@<�@F��V�@8�\(�@<�@V������V@@8�\(�@<�@V������V@8�\(�@<�@m#Ȏ�,�U���]@8�Q��@<�@j�؉�=��V �\��@8�Q��@<�@f������V @8�Q��@<�@bJ'vG�H�V �\��@8�Q��@<�@_�n�Y��U���]@8�Q��@<�@nm���]��U����"@8�Q��@<�@l�@��+�V%~�ކ@8�Q��@<�@f������V@@8�Q��@<�@`�#�2��V%~�ޅ@8�Q��@<�@]$�2WD!�U����"@8�Q��@<�@o������U��Խ@8�Q��@<�@nm��qmh�V8rQ/��@8�Q��@<�@f������V`@8�Q��@<�@]$Fe%$�V8rQ/��@8�Q��@<�@Z�HW���U��Լ@8�Q��@<�@p��V@8�Q��@<�@p��V@@8�Q��@<�@F��V�@8�Q��@<�@V������V@@8�Q��@<�@V������V@8�Q��@<�@m#Ȏ�,�U���]@8��G�{@<�@j�؉�=��V �\��@8��G�{@<�@f������V @8��G�{@<�@bJ'vG�H�V �\��@8��G�{@<�@_�n�Y��U���]@8��G�{@<�@nm���]��U����"@8��G�{@<�@l�@��+�V%~�ކ@8��G�{@<�@f������V@@8��G�{@<�@`�#�2��V%~�ޅ@8��G�{@<�@]$�2WD!�U����"@8��G�{@<�@o������U��Խ@8��G�{@<�@nm��qmh�V8rQ/��@8��G�{@<�@f������V`@8��G�{@<�@]$Fe%$�V8rQ/��@8��G�{@<�@Z�HW���U��Լ@8��G�{@<�@p��V@8��G�{@<�@p��V@@8��G�{@<�@F��V�@8��G�{@<�@V������V@@8��G�{@<�@V������V@8��G�{@<�@m#Ȏ�,�U���]@8�\(�@<������@j�؉�=��V �\��@8�\(�@<������@f������V @8�\(�@<������@bJ'vG�H�V �\��@8�\(�@<������@_�n�Y��U���]@8�\(�@<������@nm���]��U����"@8�\(�@<������@l�@��+�V%~�ކ@8�\(�@<������@f������V@@8�\(�@<������@`�#�2��V%~�ޅ@8�\(�@<������@]$�2WD!�U����"@8�\(�@<������@o������U��Խ@8�\(�@<������@nm��qmh�V8rQ/��@8�\(�@<������@f������V`@8�\(�@<������@]$Fe%$�V8rQ/��@8�\(�@<������@Z�HW���U��Լ@8�\(�@<������@p��V@8�\(�@<������@p��V@@8�\(�@<������@F��V�@8�\(�@<������@V������V@@8�\(�@<������@V������V@8�\(�@<������@m#Ȏ�,�U���]@8�Q��@<������@j�؉�=��V �\��@8�Q��@<������@f������V @8�Q��@<������@bJ'vG�H�V �\��@8�Q��@<������@_�n�Y��U���]@8�Q��@<������@nm���]��U����"@8�Q��@<������@l�@��+�V%~�ކ@8�Q��@<������@f������V@@8�Q��@<������@`�#�2��V%~�ޅ@8�Q��@<������@]$�2WD!�U����"@8�Q��@<������@o������U��Խ@8�Q��@<������@nm��qmh�V8rQ/��@8�Q��@<������@f������V`@8�Q��@<������@]$Fe%$�V8rQ/��@8�Q��@<������@Z�HW���U��Լ@8�Q��@<������@p��V@8�Q��@<������@p��V@@8�Q��@<������@F��V�@8�Q��@<������@V������V@@8�Q��@<������@V������V@8�Q��@<������@m#Ȏ�,�U���]@8��G�{@<������@j�؉�=��V �\��@8��G�{@<������@f������V @8��G�{@<������@bJ'vG�H�V �\��@8��G�{@<������@_�n�Y��U���]@8��G�{@<������@nm���]��U����"@8��G�{@<������@l�@��+�V%~�ކ@8��G�{@<������@f������V@@8��G�{@<������@`�#�2��V%~�ޅ@8��G�{@<������@]$�2WD!�U����"@8��G�{@<������@o������U��Խ@8��G�{@<������@nm��qmh�V8rQ/��@8��G�{@<������@f������V`@8��G�{@<������@]$Fe%$�V8rQ/��@8��G�{@<������@Z�HW���U��Լ@8��G�{@<������@p��V@8��G�{@<������@p��V@@8��G�{@<������@F��V�@8��G�{@<������@V������V@@8��G�{@<������@V������V@8��G�{@<������
Expand Down
35 changes: 30 additions & 5 deletions astropy/wcs/wcsapi/tests/test_fitswcs.py
Expand Up @@ -4,6 +4,7 @@

import warnings
from itertools import product
from pathlib import Path

import numpy as np
import pytest
Expand All @@ -20,6 +21,7 @@
SkyCoord,
SpectralCoord,
)
from astropy.io import fits
from astropy.io.fits import Header
from astropy.io.fits.verify import VerifyWarning
from astropy.tests.helper import assert_quantity_allclose
Expand Down Expand Up @@ -145,7 +147,7 @@ def test_simple_celestial():
assert wcs.world_axis_object_classes["celestial"][0] is SkyCoord
assert wcs.world_axis_object_classes["celestial"][1] == ()
assert isinstance(wcs.world_axis_object_classes["celestial"][2]["frame"], ICRS)
assert wcs.world_axis_object_classes["celestial"][2]["unit"] is u.deg
assert wcs.world_axis_object_classes["celestial"][2]["unit"] == (u.deg, u.deg)

Check warning on line 150 in astropy/wcs/wcsapi/tests/test_fitswcs.py

View check run for this annotation

Codecov / codecov/patch

astropy/wcs/wcsapi/tests/test_fitswcs.py#L150

Added line #L150 was not covered by tests

assert_allclose(wcs.pixel_to_world_values(29, 39), (10, 20))
assert_allclose(wcs.array_index_to_world_values(39, 29), (10, 20))
Expand Down Expand Up @@ -275,7 +277,7 @@ def test_spectral_cube():
assert wcs.world_axis_object_classes["celestial"][0] is SkyCoord
assert wcs.world_axis_object_classes["celestial"][1] == ()
assert isinstance(wcs.world_axis_object_classes["celestial"][2]["frame"], Galactic)
assert wcs.world_axis_object_classes["celestial"][2]["unit"] is u.deg
assert wcs.world_axis_object_classes["celestial"][2]["unit"] == (u.deg, u.deg)

Check warning on line 280 in astropy/wcs/wcsapi/tests/test_fitswcs.py

View check run for this annotation

Codecov / codecov/patch

astropy/wcs/wcsapi/tests/test_fitswcs.py#L280

Added line #L280 was not covered by tests

assert wcs.world_axis_object_classes["spectral"][0] is Quantity
assert wcs.world_axis_object_classes["spectral"][1] == ()
Expand Down Expand Up @@ -395,7 +397,7 @@ def test_spectral_cube_nonaligned():
assert wcs.world_axis_object_classes["celestial"][0] is SkyCoord
assert wcs.world_axis_object_classes["celestial"][1] == ()
assert isinstance(wcs.world_axis_object_classes["celestial"][2]["frame"], Galactic)
assert wcs.world_axis_object_classes["celestial"][2]["unit"] is u.deg
assert wcs.world_axis_object_classes["celestial"][2]["unit"] == (u.deg, u.deg)

Check warning on line 400 in astropy/wcs/wcsapi/tests/test_fitswcs.py

View check run for this annotation

Codecov / codecov/patch

astropy/wcs/wcsapi/tests/test_fitswcs.py#L400

Added line #L400 was not covered by tests

assert wcs.world_axis_object_classes["spectral"][0] is Quantity
assert wcs.world_axis_object_classes["spectral"][1] == ()
Expand Down Expand Up @@ -491,7 +493,7 @@ def test_time_cube():
assert wcs.world_axis_object_classes["celestial"][0] is SkyCoord
assert wcs.world_axis_object_classes["celestial"][1] == ()
assert isinstance(wcs.world_axis_object_classes["celestial"][2]["frame"], ICRS)
assert wcs.world_axis_object_classes["celestial"][2]["unit"] is u.deg
assert wcs.world_axis_object_classes["celestial"][2]["unit"] == (u.deg, u.deg)

Check warning on line 496 in astropy/wcs/wcsapi/tests/test_fitswcs.py

View check run for this annotation

Codecov / codecov/patch

astropy/wcs/wcsapi/tests/test_fitswcs.py#L496

Added line #L496 was not covered by tests

assert wcs.world_axis_object_classes["time"][0] is Time
assert wcs.world_axis_object_classes["time"][1] == ()
Expand Down Expand Up @@ -905,7 +907,7 @@ def test_caching_components_and_classes():
assert wcs.world_axis_object_classes["celestial"][0] is SkyCoord
assert wcs.world_axis_object_classes["celestial"][1] == ()
assert isinstance(wcs.world_axis_object_classes["celestial"][2]["frame"], ICRS)
assert wcs.world_axis_object_classes["celestial"][2]["unit"] is u.deg
assert wcs.world_axis_object_classes["celestial"][2]["unit"] == (u.deg, u.deg)

Check warning on line 910 in astropy/wcs/wcsapi/tests/test_fitswcs.py

View check run for this annotation

Codecov / codecov/patch

astropy/wcs/wcsapi/tests/test_fitswcs.py#L910

Added line #L910 was not covered by tests

wcs.wcs.radesys = "FK5"

Expand Down Expand Up @@ -1348,3 +1350,26 @@ def check_wcs(header):
# Check fall back to scale='utc'
del hdr["TIMESYS"]
check_wcs(hdr)


def test_fits_tab():
"""
This test is a regression test for https://github.com/astropy/astropy/issues/12095
It checks the following:
- If a spatial WCS isn't converted to units of deg by wcslib it still works.
- If TIMESYS is upper case we parse it correctly
- If a TIME CTYPE key has an algorithm code (in this case -TAB) it still works.
The file used here was generated by gWCS and then edited to add the TIMESYS key.
"""
with fits.open(Path(__file__).parent / "example_4d_tab.fits") as hdul:
with pytest.warns(FITSFixedWarning):
w = WCS(header=hdul[0].header, fobj=hdul)
world = w.pixel_to_world(0, 0, 0, 0)
assert isinstance(world[0], SkyCoord)
assert world[0].data.lat.unit is u.arcsec
assert world[0].data.lon.unit is u.arcsec
assert isinstance(world[1], u.Quantity)
assert isinstance(world[2], Time)
assert world[2].scale == "utc"

Check warning on line 1375 in astropy/wcs/wcsapi/tests/test_fitswcs.py

View check run for this annotation

Codecov / codecov/patch

astropy/wcs/wcsapi/tests/test_fitswcs.py#L1366-L1375

Added lines #L1366 - L1375 were not covered by tests

0 comments on commit bf1b98b

Please sign in to comment.