-
-
Notifications
You must be signed in to change notification settings - Fork 16
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fits wcs #246
Fits wcs #246
Changes from all commits
c995400
1272c5d
880ff6d
a77c229
45fd10a
4d04f5b
2e37f2a
5afd469
54a33da
7262e0d
3e2cb9d
c0872c2
83ff33f
706f870
aab79f9
160532f
5213d4e
c324e00
cb69b81
dc200c7
c9d441e
b139108
4bd4c2c
2f2cb9d
2e5973b
b20650f
160adcc
93b946e
8c6231b
1edecd1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
__all__ = [ | ||
"WCSConverter", | ||
"SlicedWCSConverter", | ||
] | ||
from .slicedwcs import SlicedWCSConverter | ||
from .wcs import WCSConverter |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
from asdf.extension import Converter | ||
|
||
|
||
class SlicedWCSConverter(Converter): | ||
tags = ("tag:astropy.org:astropy/wcs/slicedwcs-*",) | ||
types = ("astropy.wcs.wcsapi.wrappers.sliced_wcs.SlicedLowLevelWCS",) | ||
|
||
def from_yaml_tree(self, node, tag, ctx): | ||
from astropy.wcs.wcsapi.wrappers.sliced_wcs import SlicedLowLevelWCS | ||
|
||
wcs = node["wcs"] | ||
slice_array = [ | ||
s if isinstance(s, int) else slice(s["start"], s["stop"], s["step"]) for s in node["slices_array"] | ||
] | ||
return SlicedLowLevelWCS(wcs, slice_array) | ||
|
||
def to_yaml_tree(self, sl, tag, ctx): | ||
slices_array = [] | ||
|
||
for s in sl._slices_array: | ||
if isinstance(s, slice): | ||
slices_array.append( | ||
{ | ||
"start": s.start, | ||
"stop": s.stop, | ||
"step": s.step, | ||
}, | ||
) | ||
else: | ||
slices_array.append(s) | ||
return { | ||
"wcs": sl._wcs, | ||
"slices_array": slices_array, | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import asdf | ||
import numpy as np | ||
import pytest | ||
from astropy.wcs import WCS | ||
from astropy.wcs.wcsapi.wrappers.sliced_wcs import SlicedLowLevelWCS | ||
|
||
from asdf_astropy.testing.helpers import assert_wcs_equal | ||
|
||
|
||
def create_wcs(): | ||
wcs = WCS(naxis=4) | ||
wcs.wcs.ctype = "RA---CAR", "DEC--CAR", "FREQ", "TIME" | ||
wcs.wcs.cunit = "deg", "deg", "Hz", "s" | ||
wcs.wcs.cdelt = -2.0, 2.0, 3.0e9, 1 | ||
wcs.wcs.crval = 4.0, 0.0, 4.0e9, 3 | ||
wcs.wcs.crpix = 6.0, 7.0, 11.0, 11.0 | ||
wcs.wcs.cname = "Right Ascension", "Declination", "Frequency", "Time" | ||
|
||
wcs0 = SlicedLowLevelWCS(wcs, 1) | ||
wcs1 = SlicedLowLevelWCS(wcs, [slice(None), slice(None), slice(None), 10]) | ||
wcs3 = SlicedLowLevelWCS(SlicedLowLevelWCS(wcs, slice(None)), [slice(3), slice(None), slice(None), 10]) | ||
wcs_ellipsis = SlicedLowLevelWCS(wcs, [Ellipsis, slice(5, 10)]) | ||
wcs2 = SlicedLowLevelWCS(wcs, np.s_[:, 2, 3, :]) | ||
return [wcs0, wcs1, wcs2, wcs_ellipsis, wcs3] | ||
|
||
|
||
@pytest.mark.parametrize("sl_wcs", create_wcs()) | ||
def test_sliced_wcs_serialization(sl_wcs, tmp_path): | ||
file_path = tmp_path / "test_slicedwcs.asdf" | ||
with asdf.AsdfFile() as af: | ||
af["sl_wcs"] = sl_wcs | ||
af.write_to(file_path) | ||
|
||
with asdf.open(file_path) as af: | ||
loaded_sl_wcs = af["sl_wcs"] | ||
assert_wcs_equal(sl_wcs._wcs, loaded_sl_wcs._wcs) | ||
assert sl_wcs._slices_array == loaded_sl_wcs._slices_array |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
import warnings | ||
|
||
import numpy as np | ||
import pytest | ||
from astropy.io import fits | ||
from astropy.utils.data import get_pkg_data_filename, get_pkg_data_filenames | ||
from astropy.wcs import WCS, DistortionLookupTable, FITSFixedWarning, Sip | ||
|
||
from asdf_astropy.testing.helpers import assert_wcs_roundtrip | ||
|
||
_astropy_test_header_filenames = list(get_pkg_data_filenames("tests/data/maps", "astropy.wcs", "*.hdr")) + list( | ||
get_pkg_data_filenames("tests/data/spectra", "astropy.wcs", "*.hdr"), | ||
) | ||
|
||
_astropy_test_fits_filenames = [ | ||
get_pkg_data_filename(f"tests/data/{fn}", "astropy.wcs") | ||
for fn in [ | ||
"ie6d07ujq_wcs.fits", | ||
"j94f05bgq_flt.fits", | ||
"sip.fits", | ||
"sip2.fits", | ||
] | ||
] | ||
|
||
|
||
def create_empty_wcs(): | ||
return WCS() | ||
|
||
|
||
def create_wcs_with_attrs(): | ||
wcs = WCS(naxis=3) | ||
wcs.pixel_shape = [100, 200, 300] | ||
wcs.pixel_bounds = [[11, 22], [33, 45], [55, 67]] | ||
return wcs | ||
|
||
|
||
def create_sip_distortion_wcs(): | ||
rng = np.random.default_rng(42) | ||
wcs = WCS(naxis=2) | ||
wcs.wcs.crval = [251.29, 57.58] | ||
wcs.wcs.cdelt = [1, 1] | ||
wcs.wcs.crpix = [507, 507] | ||
wcs.wcs.pc = np.array([[7.7e-6, 3.3e-5], [3.7e-5, -6.8e-6]]) | ||
wcs._naxis = [1014, 1014] | ||
wcs.wcs.ctype = ["RA---TAN-SIP", "DEC--TAN-SIP"] | ||
|
||
# Generate random SIP coefficients | ||
a = rng.uniform(low=-1e-5, high=1e-5, size=(5, 5)) | ||
b = rng.uniform(low=-1e-5, high=1e-5, size=(5, 5)) | ||
|
||
# Assign SIP coefficients | ||
wcs.sip = Sip(a, b, None, None, wcs.wcs.crpix) | ||
wcs.wcs.set() | ||
|
||
return wcs | ||
|
||
|
||
def create_tabular_wcs(): | ||
# Creates a WCS object with distortion lookup tables | ||
img_world_wcs = WCS(naxis=2) | ||
img_world_wcs.wcs.crpix = 1, 1 | ||
img_world_wcs.wcs.crval = 0, 0 | ||
img_world_wcs.wcs.cdelt = 1, 1 | ||
|
||
# Create maps with zero distortion except at one particular pixel | ||
x_dist_array = np.zeros((25, 25)) | ||
x_dist_array[10, 20] = 0.5 | ||
map_x = DistortionLookupTable( | ||
x_dist_array.astype(np.float32), | ||
(5, 10), | ||
(10, 20), | ||
(2, 2), | ||
) | ||
y_dist_array = np.zeros((25, 25)) | ||
y_dist_array[10, 5] = 0.7 | ||
map_y = DistortionLookupTable( | ||
y_dist_array.astype(np.float32), | ||
(5, 10), | ||
(10, 20), | ||
(3, 3), | ||
) | ||
|
||
img_world_wcs.cpdis1 = map_x | ||
img_world_wcs.cpdis2 = map_y | ||
|
||
return img_world_wcs | ||
|
||
|
||
@pytest.mark.parametrize("version", ["1.5.0", "1.6.0"]) | ||
@pytest.mark.parametrize( | ||
"wcs_gen", | ||
[create_empty_wcs, create_wcs_with_attrs, create_tabular_wcs, create_sip_distortion_wcs], | ||
) | ||
def test_roundtrip(wcs_gen, tmp_path, version): | ||
wcs = wcs_gen() | ||
assert_wcs_roundtrip(wcs, tmp_path, version) | ||
|
||
|
||
@pytest.mark.parametrize("fn", _astropy_test_header_filenames) | ||
@pytest.mark.parametrize("version", ["1.5.0", "1.6.0"]) | ||
def test_astropy_data_header_roundtrip(fn, tmp_path, version): | ||
with open(fn) as f: | ||
header = f.read() | ||
|
||
with warnings.catch_warnings(): | ||
warnings.filterwarnings("ignore", category=FITSFixedWarning) | ||
wcs = WCS(header) | ||
|
||
assert_wcs_roundtrip(wcs, tmp_path, version) | ||
|
||
|
||
@pytest.mark.parametrize("fn", _astropy_test_fits_filenames) | ||
@pytest.mark.parametrize("version", ["1.5.0", "1.6.0"]) | ||
def test_astropy_data_fits_roundtrip(fn, tmp_path, version): | ||
with fits.open(fn) as ff: | ||
with warnings.catch_warnings(): | ||
warnings.filterwarnings("ignore", category=FITSFixedWarning) | ||
wcs = WCS(ff[0].header, ff) | ||
|
||
assert_wcs_roundtrip(wcs, tmp_path, version) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
from asdf.extension import Converter | ||
|
||
# These attributes don't end up in the hdulist and | ||
# instead will be stored in "attrs" | ||
_WCS_ATTRS = ("naxis", "colsel", "keysel", "key", "pixel_bounds") | ||
|
||
|
||
class WCSConverter(Converter): | ||
tags = ("tag:astropy.org:astropy/wcs/wcs-*",) | ||
types = ("astropy.wcs.wcs.WCS",) | ||
|
||
def from_yaml_tree(self, node, tag, ctx): | ||
from astropy.wcs import WCS | ||
|
||
hdulist = node["hdulist"] | ||
attrs = node["attrs"] | ||
|
||
if naxis := attrs.pop("naxis"): | ||
hdulist[0].header["naxis"] = naxis | ||
|
||
pixel_bounds = attrs.pop("pixel_bounds") | ||
|
||
wcs = WCS(hdulist[0].header, fobj=hdulist, **attrs) | ||
|
||
if wcs.sip is not None: | ||
# work around a bug in astropy where sip headers lose precision | ||
# see https://github.com/astropy/astropy/issues/17334 | ||
wcs.sip = wcs._read_sip_kw(hdulist[0].header, attrs.get("key", " ")) | ||
wcs.wcs.set() | ||
|
||
wcs.pixel_bounds = pixel_bounds | ||
return wcs | ||
|
||
def to_yaml_tree(self, wcs, tag, ctx): | ||
hdulist = wcs.to_fits(relax=True) | ||
attrs = {a: getattr(wcs, a) for a in _WCS_ATTRS if hasattr(wcs, a)} | ||
return {"hdulist": hdulist, "attrs": attrs} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
%YAML 1.1 | ||
--- | ||
$schema: "http://stsci.edu/schemas/yaml-schema/draft-01" | ||
id: "http://astropy.org/schemas/astropy/wcs/slicedwcs-1.0.0" | ||
|
||
title: Represents the SlicedLowLevelWCS object | ||
|
||
description: >- | ||
The SlicedLowLevelWCS class is a wrapper class for WCS that applies slices | ||
to the WCS, allowing certain pixel and world dimensions to be retained or | ||
dropped. | ||
It manages the slicing and coordinate transformations while preserving | ||
the underlying WCS object. | ||
|
||
allOf: | ||
- type: object | ||
properties: | ||
wcs: | ||
tag: "tag:astropy.org:astropy/wcs/wcs-1*" | ||
slices_array: | ||
type: array | ||
items: | ||
- oneOf: | ||
- type: integer | ||
- type: object | ||
Comment on lines
+24
to
+25
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would it be simpler if we always stored a slice object with stop = start + 1? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Possibly. Although I think this should retain the format of the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's fair, let's leave it as is. |
||
properties: | ||
start: | ||
anyOf: | ||
- type: integer | ||
- type: "null" | ||
stop: | ||
anyOf: | ||
- type: integer | ||
- type: "null" | ||
step: | ||
anyOf: | ||
- type: integer | ||
- type: "null" | ||
|
||
|
||
required: ["wcs", "slices_array"] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
%YAML 1.1 | ||
--- | ||
$schema: "http://stsci.edu/schemas/yaml-schema/draft-01" | ||
id: "http://astropy.org/schemas/astropy/wcs/wcs-1.0.0" | ||
|
||
title: Represents the fits object | ||
|
||
description: >- | ||
Represents the FITS WCS object, the HDUlist of the FITS header is preserved | ||
during serialization and during deserialization the WCS object is recreated | ||
from the HDUlist. | ||
|
||
allOf: | ||
- type: object | ||
properties: | ||
hdulist: | ||
title: "HDUList produced by WCS.to_fits" | ||
tag: "tag:astropy.org:astropy/fits/fits-*" | ||
attrs: | ||
title: "extra WCS attributes not contained in hdulist" | ||
type: object | ||
|
||
required: ["hdulist", "attrs"] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a distortion table, but do we have a test for a
-TAB
wcs?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any suggestions for an example to try?
If I use the one here:
https://github.com/astropy/astropy/blob/0f80be718107ef797a58ba42025e70b21c454c61/astropy/wcs/tests/conftest.py#L11
it doesn't roundtrip through pickle (or
to_fits
)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The file in the astropy package data
get_pkg_data_filename("data/tab-time-last-axis.fits", package="astropy.wcs.tests")
also fails to round-trip through pickle (orto_fits
).There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh astropy/astropy#9998 astropy can't produce a
-TAB
wcs so we can't support it here.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well I guess we should write a test which errors then 😆