Support serialization of astropy.wcs.WCS objects to ASDF. #235
Support serialization of astropy.wcs.WCS objects to ASDF. #235ViciousEagle03 wants to merge 19 commits intoastropy:mainfrom
astropy.wcs.WCS objects to ASDF. #235Conversation
|
FYI I had to "approve and run" the CI for this (likely because this is your first contribution). Let me know if the CI stops running and I can look into fixing it. Thanks! |
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## main #235 +/- ##
==========================================
+ Coverage 97.90% 97.97% +0.07%
==========================================
Files 54 59 +5
Lines 2053 2178 +125
==========================================
+ Hits 2010 2134 +124
- Misses 43 44 +1 ☔ View full report in Codecov by Sentry. |
|
Thanks for approving the CI run. |
|
The CI still needs to be approved to run. |
|
Hmmm, I don't know of a way to "approve all runs for this PR from a first time contributor" and this suggests it might not be an option: Would it be possible to open a PR with a pretty minimal change that I can approve (which will then make you a contributor)? A PR that just removes the if check here: and unindents the code in the if block would work. The minimum required version of astropy is now 5.2 so the if will always be True:Line 24 in 9348f89 |
|
Sure I am on it |
|
pre-commit.ci autofix |
|
Hey @braingram, to enable serialization for |
|
I'm ok having both of those features in this PR since they seem very related. |
| Converter for serializing and deserializing `astropy.wcs.WCS` objects. | ||
|
|
||
| This converter currently supports the serialization of simple WCS objects | ||
| by preserving the `wcs.to_header()` data. It does not support complex WCS objects |
There was a problem hiding this comment.
| by preserving the `wcs.to_header()` data. It does not support complex WCS objects | |
| by preserving the ``wcs.to_header()`` data. It does not support complex WCS objects |
This should hopefully fix the docs.
There was a problem hiding this comment.
Since we are now using to_fits instead of to_header, we no longer need to specify that the converter supports only certain types of WCSes.
| be accessible in its proper form in the ASDF file. | ||
|
|
||
| Only image and binary table extensions are supported. | ||
| - tag_uri: tag:astropy.org:astropy/fits/fitswcs-1.0.0 |
There was a problem hiding this comment.
Would you add this to a new manifest astropy-1.1.0.yaml and register it as a new extension?
There was a problem hiding this comment.
astropy-1.1.0.yaml manifest already exists. Do I need to add it to this manifest?
There was a problem hiding this comment.
Yeah a new manifest is needed (I forgot this already has an 1.1.0).
Things are a bit more complicated as 1.2.0 exists but is only ASDF standard 1.6.0 compliant.
Would you add a new one (1.3.0) based off of the 1.1.0 one (which uses the current ASDF standard 1.5.0). I'll sort out the 1.6.0 issues after this PR is in.
| description: | ||
| Represents the fits object |
There was a problem hiding this comment.
| description: | |
| Represents the fits object | |
| description: >- | |
| Represents the fits object |
Same as title would you add a more descriptive description?
|
Hello, @braingram could you please review the recent changes. |
| def from_yaml_tree(self, node, tag, ctx): | ||
| from astropy.wcs import WCS | ||
|
|
||
| primary_hdu = node["hdu"][0] |
There was a problem hiding this comment.
| primary_hdu = node["hdu"][0] | |
| primary_hdu = node["hdu"][0] |
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 for to_fits?
There was a problem hiding this comment.
@braingram, Do you have any suggestions for effectively preserving distortion data?
There was a problem hiding this comment.
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:
astropy.wcs.WCS(node["hdu"][0].header, fobj=node["hdu"])would work for reading. Do any of the example files have distortion tables?
There was a problem hiding this comment.
I am currently experimenting and trying to determine the appropriate serialization logic for this WCS. If you have any suggestions, please let me know.
|
|
||
| def create_wcs(): | ||
| urls = [ | ||
| "http://data.astropy.org/tutorials/FITS-cubes/reduced_TAN_C14.fits", |
There was a problem hiding this comment.
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?
|
I left a few comments. Any clue why oldest deps is failing? |
I suspect the first error is most likely due to the distortion data not being properly handled by the |
| from astropy import wcs | ||
|
|
||
|
|
||
| def create_sip_distortion_wcs(): |
There was a problem hiding this comment.
Should I create a separate fixture for the WCS test cases, or will the existing ones work?
braingram
left a comment
There was a problem hiding this comment.
The wcses do not appear to be round-tripping.
|
|
||
| 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.
| assert wcs.to_header() == loaded_wcs.to_header() | |
| assert wcs.to_header() == loaded_wcs.to_header() |
This isn't testing the distortion information for the wcses.
I tried changing this to wcs == loaded_wcs and this is failing in part due to the sip attribute not round-tripping.
There was a problem hiding this comment.
The wcs == loaded_wcs appears to be defaulting to object.__eq__ (so will fail since id(wcs) != id(loaded_wcs)). This helper function could be used instead:
asdf-astropy/asdf_astropy/testing/helpers.py
Line 105 in fa5f1f4
and reports a failure for this test since the cards don't match:
-> assert tuple(card_a) == tuple(card_b)
(Pdb) p card_a
('A_0_0', -2.0413459666638e-06, 'SIP distortion coefficient')
(Pdb) p card_b
('A_0_0', -2.0413459666638133e-06, 'SIP distortion coefficient')
There was a problem hiding this comment.
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 header.
| @pytest.mark.filterwarnings("ignore::astropy.wcs.wcs.FITSFixedWarning") | ||
| @pytest.mark.filterwarnings( | ||
| "ignore:Some non-standard WCS keywords were excluded:astropy.utils.exceptions.AstropyWarning", |
There was a problem hiding this comment.
What's triggering these warnings?
There was a problem hiding this comment.
I filtered the FITSFixedWarning because it was triggered due to the test setup where the image has 0 axes but the WCS expects NAXIS to be 4 as no image is associated with it.
The exact warning is as follows:
astropy.wcs.wcs.FITSFixedWarning: The WCS transformation has more axes (4) than the image it is associated with (0)
Since the warning is not relevant to the test so I thought of filtering it out.
The second warning was filtered as non-standard WCS keywords (here the SIP distortion coefficients) were excluded when converting the WCS object to a FITS header using to_header.
| def to_yaml_tree(self, wcs, tag, ctx): | ||
| node = {} | ||
| if wcs.sip is not None: | ||
| node["hdu"] = wcs.to_fits(relax=True) |
There was a problem hiding this comment.
| node["hdu"] = wcs.to_fits(relax=True) | |
| node["hdu"] = wcs.to_fits(relax=True) |
Presumably the relax is here for the "non-standard" wcs keywords. Will these only be present if sip is not None?
There was a problem hiding this comment.
Yes, I believe so.
Is there a case we are not considering? If so, could you please explain how we can account for it?
| return [wcs0, wcs1, wcs2, wcs_ellipsis, wcs3] | ||
|
|
||
|
|
||
| @pytest.mark.filterwarnings("ignore::astropy.wcs.wcs.FITSFixedWarning") |
There was a problem hiding this comment.
What's triggering this warning?
There was a problem hiding this comment.
Same as the above reason
|
|
||
| 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.
| assert sl_wcs._wcs.to_header() == loaded_sl_wcs._wcs.to_header() | |
| assert sl_wcs._wcs.to_header() == loaded_sl_wcs._wcs.to_header() |
If I try to use sl_wcs == loaded_sl_wcs it fails. Any idea why the wcs is not round-tripping?
| asdf_standard_requirement: | ||
| gte: 1.5.0 |
There was a problem hiding this comment.
Why restrict the standard here?
There was a problem hiding this comment.
The tox tests seem to fail with the 1.6.0 version.
| @@ -0,0 +1,34 @@ | |||
| from asdf.extension import Converter | |||
There was a problem hiding this comment.
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 fitswcs.py (and the corresponding tests) into a wcs submodule? Having them both contained in the new wcs submodule is sufficient (so the SlicedWCSConverter doesn't have to be further nested in a wcsapi.wrappers.sliced_wcs submodule).
There was a problem hiding this comment.
Agreed, moving them to a wcs submodule makes sense and will keep things organized.
|
@braingram, @Cadair I'm sorry for being unavailable for the past two weeks because of my exams and practicals. Now that they are completed, I can fully dedicate my time to completing the project. |
| if wcs.sip is not None: | ||
| node["hdu"] = wcs.to_fits(relax=True) | ||
| else: | ||
| node["hdu"] = wcs.to_fits() |
There was a problem hiding this comment.
Is there a harm always running with relax=True?
There was a problem hiding this comment.
|
|
||
| def to_yaml_tree(self, wcs, tag, ctx): | ||
| node = {} | ||
| if wcs.sip is not None: |
There was a problem hiding this comment.
astropy.wcs supports 2 other type of distortions. Are these ignored on purpose? In truth I don't know of anyone else but HST using them but the call to_fits is only truly needed if the other two distortions are present because SIP distortion is only in the headers.
I suggest removing the condition that sip is present and always call wcs.to_fits(relax=True)
There was a problem hiding this comment.
Are you referring to the "table" and "TAB" distortions or is there another type?
- "TAB" is a non-starter since astropy can't write them: Create a FITS file from a WCS-TAB object astropy#9998
- "table" is tested: https://github.com/astropy/asdf-astropy/pull/246/files#diff-4d12199a58487bbc9c9a729bcb6f8a3d6297a6547eb1510c29c6a8721986b43eR40
|
For the tests in this PR, would it be useful to use some of the data in the astropy.wcs.tests.data directory. There is a variety of WCSs there, including some with non-sip distortion. Also would be be helpful to add a |
|
Closing in favor of #246 |
This PR is a [WIP] and adds the support for serialization of
astropy.wcs.WCSto ASDF.fixes: #234
Checklist:
wcs.to_fits().