-
-
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
Support serialization of astropy.wcs.WCS
objects to ASDF.
#235
Changes from 12 commits
d7bbee8
11f1b27
110fc4c
6673946
faa8680
5e830f1
9d27051
66904fc
a6efc4f
1f492ac
4f78680
de41f51
21062b5
0d31b03
0919142
6b24fe1
5b1d4cf
53df278
a016e2b
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,17 @@ | ||
from asdf.extension import Converter | ||
|
||
|
||
class FitsWCSConverter(Converter): | ||
tags = ("tag:astropy.org:astropy/fits/fitswcs-*",) | ||
types = ("astropy.wcs.wcs.WCS",) | ||
|
||
def from_yaml_tree(self, node, tag, ctx): | ||
from astropy.wcs import WCS | ||
|
||
primary_hdu = node["hdu"][0] | ||
return WCS(primary_hdu.header) | ||
|
||
def to_yaml_tree(self, wcs, tag, ctx): | ||
node = {} | ||
node["hdu"] = wcs.to_fits() | ||
return node |
Original file line number | Diff line number | Diff line change | ||||||
---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,31 @@ | ||||||||
import warnings | ||||||||
|
||||||||
import asdf | ||||||||
import pytest | ||||||||
from astropy.io import fits | ||||||||
from astropy.utils.data import download_file | ||||||||
from astropy.wcs import WCS, FITSFixedWarning | ||||||||
|
||||||||
|
||||||||
def create_wcs(): | ||||||||
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. I wonder if there's a set of WCSes we can use to test in astropy somewhere? There has to already be one or many fixtures in astropy we can import riiighghtttt? 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. I have extracted the |
||||||||
urls = [ | ||||||||
"http://data.astropy.org/tutorials/FITS-cubes/reduced_TAN_C14.fits", | ||||||||
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. This would introduce downloading data files as part of the test runs. This is not something done by other tests and I don't think this belongs in unit tests. Would you add a fixture that creates suitable wcs objects? 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. Please see this |
||||||||
"http://data.astropy.org/tutorials/FITS-images/HorseHead.fits", | ||||||||
] | ||||||||
|
||||||||
with warnings.catch_warnings(): | ||||||||
warnings.simplefilter("ignore", FITSFixedWarning) | ||||||||
return [WCS(fits.open(download_file(url, cache=True))[0].header) for url in urls] | ||||||||
|
||||||||
|
||||||||
@pytest.mark.filterwarnings("ignore::astropy.wcs.wcs.FITSFixedWarning") | ||||||||
@pytest.mark.parametrize("wcs", create_wcs()) | ||||||||
def test_wcs_serialization(wcs, tmp_path): | ||||||||
file_path = tmp_path / "test_wcs.asdf" | ||||||||
with asdf.AsdfFile() as af: | ||||||||
af["wcs"] = wcs | ||||||||
af.write_to(file_path) | ||||||||
|
||||||||
with asdf.open(file_path) as af: | ||||||||
loaded_wcs = af["wcs"] | ||||||||
assert wcs.to_header() == loaded_wcs.to_header() | ||||||||
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.
Suggested change
This isn't testing the distortion information for the wcses. 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. The asdf-astropy/asdf_astropy/testing/helpers.py Line 105 in fa5f1f4
and reports a failure for this test since the cards don't match:
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. Since there isn't a helper function available to check if two WCS objects are equivalent, I thought of checking the round-tripping of the WCS through its |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
__all__ = [ | ||
"SlicedWCSConverter", | ||
] | ||
|
||
from .slicedwcs import SlicedWCSConverter |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
from asdf.extension import Converter | ||
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. I believe the general layout for this package is to put converters in submodules similar to the layout in astropy. Would you move this and 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. Agreed, moving them to a |
||
|
||
|
||
class SlicedWCSConverter(Converter): | ||
tags = ("tag:astropy.org:astropy/slicedwcs/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 = [] | ||
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): | ||
node = {} | ||
node["wcs"] = sl._wcs | ||
node["slices_array"] = [] | ||
|
||
for s in sl._slices_array: | ||
if isinstance(s, slice): | ||
braingram marked this conversation as resolved.
Show resolved
Hide resolved
|
||
node["slices_array"].append( | ||
{ | ||
"start": s.start if s.start is not None else None, | ||
"stop": s.stop if s.stop is not None else None, | ||
"step": s.step if s.step is not None else None, | ||
ViciousEagle03 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
}, | ||
) | ||
else: | ||
node["slices_array"].append(s) | ||
return node |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -0,0 +1,35 @@ | ||||||
import asdf | ||||||
import numpy as np | ||||||
import pytest | ||||||
from astropy.wcs import WCS | ||||||
from astropy.wcs.wcsapi.wrappers.sliced_wcs import SlicedLowLevelWCS | ||||||
|
||||||
|
||||||
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.filterwarnings("ignore::astropy.wcs.wcs.FITSFixedWarning") | ||||||
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. What's triggering this warning? 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. Same as the above reason |
||||||
@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 sl_wcs._wcs.to_header() == loaded_sl_wcs._wcs.to_header() | ||||||
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.
Suggested change
If I try to use |
||||||
assert sl_wcs._slices_array == loaded_sl_wcs._slices_array |
Original file line number | Diff line number | Diff line change | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,18 @@ | ||||||||||
%YAML 1.1 | ||||||||||
--- | ||||||||||
$schema: "http://stsci.edu/schemas/yaml-schema/draft-01" | ||||||||||
id: "http://astropy.org/schemas/astropy/fits/fitswcs-1.0.0" | ||||||||||
|
||||||||||
title: | ||||||||||
Represents the fits object | ||||||||||
ViciousEagle03 marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||
|
||||||||||
description: | ||||||||||
Represents the fits object | ||||||||||
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.
Suggested change
Same as |
||||||||||
|
||||||||||
allOf: | ||||||||||
- type: object | ||||||||||
properties: | ||||||||||
hdu: | ||||||||||
tag: "tag:astropy.org:astropy/fits/fits-*" | ||||||||||
|
||||||||||
required: ["hdu"] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
%YAML 1.1 | ||
--- | ||
$schema: "http://stsci.edu/schemas/yaml-schema/draft-01" | ||
id: "http://astropy.org/schemas/astropy/slicedwcs/slicedwcs-1.0.0" | ||
|
||
title: | ||
Represents the SlicedLowLevelWCS object | ||
|
||
description: | ||
Represents the SlicedLowLevelWCS object | ||
|
||
allOf: | ||
- type: object | ||
properties: | ||
wcs: | ||
tag: "tag:astropy.org:astropy/fits/fitswcs-1*" | ||
slices_array: | ||
type: array | ||
items: | ||
- oneOf: | ||
- type: integer | ||
- type: object | ||
braingram marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
required: ["wcs", "slices_array"] |
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.
Doesn't this throw out the distortion (and other non-primary) data that is generated with
to_fits
? Would you add a test with a wcs that generates more than 1 hdu forto_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.
@braingram, Do you have any suggestions for effectively preserving distortion data?
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.
I believe that's already done with
to_fits
on write (since it puts the table into additional hdus that are stored in the asdf file with this PR).This is all new to me so I could be getting this wrong but I think:
would work for reading. Do any of the example files have distortion tables?
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.
I am currently experimenting and trying to determine the appropriate serialization logic for this WCS. If you have any suggestions, please let me know.