Skip to content
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

Open
wants to merge 19 commits into
base: main
Choose a base branch
from

Conversation

ViciousEagle03
Copy link
Contributor

@ViciousEagle03 ViciousEagle03 commented Jul 24, 2024

This PR is a [WIP] and adds the support for serialization of astropy.wcs.WCS to ASDF.
fixes: #234
Checklist:

  • Add support for the basic WCSes by preserving the wcs.to_fits().
  • Test

@ViciousEagle03 ViciousEagle03 marked this pull request as draft July 24, 2024 15:57
@braingram
Copy link
Contributor

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!

Copy link

codecov bot commented Jul 24, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 97.97%. Comparing base (d63bc9a) to head (a016e2b).
Report is 1 commits behind head on main.

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.
📢 Have feedback on the report? Share it here.

@ViciousEagle03
Copy link
Contributor Author

Thanks for approving the CI run.

@ViciousEagle03 ViciousEagle03 marked this pull request as ready for review July 24, 2024 16:51
@ViciousEagle03 ViciousEagle03 marked this pull request as draft July 24, 2024 16:51
@ViciousEagle03
Copy link
Contributor Author

The CI still needs to be approved to run.

@braingram
Copy link
Contributor

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:
https://github.com/orgs/community/discussions/14334

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:
"astropy>=5.2.0",

@ViciousEagle03
Copy link
Contributor Author

Sure I am on it

@ViciousEagle03
Copy link
Contributor Author

pre-commit.ci autofix

@ViciousEagle03
Copy link
Contributor Author

ViciousEagle03 commented Aug 4, 2024

Hey @braingram, to enable serialization for SlicedLowLevelWCS, we need the schema and converter for fitswcs. Should I push the serialization code for SlicedLowLevelWCS here, or should I add the changes into a new branch after this branch is merged?

@braingram
Copy link
Contributor

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@@ -28,6 +28,16 @@ tags:
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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would you add this to a new manifest astropy-1.1.0.yaml and register it as a new extension?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

astropy-1.1.0.yaml manifest already exists. Do I need to add it to this manifest?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines 9 to 10
description:
Represents the fits object
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
description:
Represents the fits object
description: >-
Represents the fits object

Same as title would you add a more descriptive description?

@ViciousEagle03
Copy link
Contributor Author

ViciousEagle03 commented Aug 21, 2024

Can this use WCS.to_fits instead of to_header and use the existing FitsConverter?

@Cadair and I had discussed using to_header for the simpler WCS cases.

@Cadair
Copy link
Member

Cadair commented Aug 21, 2024

I did not know the FITSConverter existed, so maybe that is easier. I shall have a look in more detail tomorrow.

@Cadair
Copy link
Member

Cadair commented Aug 22, 2024

Thinking about this a bit more +1 to just using to_fits, it's a much better solution.

@Cadair
Copy link
Member

Cadair commented Aug 22, 2024

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:

FTR, the best solution is to change the settings in the repo settings to only require approval for new github accounts.

from astropy.wcs import WCS


def create_wcs():
Copy link
Member

Choose a reason for hiding this comment

The 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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have extracted the WCS from the FITS file. I thought it would be better to test directly using a FITS file.

asdf_astropy/converters/slicedwcs/slicedwcs.py Outdated Show resolved Hide resolved
@ViciousEagle03
Copy link
Contributor Author

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]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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?

Copy link
Contributor Author

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?

Copy link
Contributor

@braingram braingram Aug 26, 2024

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:

astropy.wcs.WCS(node["hdu"][0].header, fobj=node["hdu"])

would work for reading. Do any of the example files have distortion tables?

Copy link
Contributor Author

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.


def create_wcs():
urls = [
"http://data.astropy.org/tutorials/FITS-cubes/reduced_TAN_C14.fits",
Copy link
Contributor

Choose a reason for hiding this comment

The 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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please see this

@braingram
Copy link
Contributor

I left a few comments. Any clue why oldest deps is failing?

@ViciousEagle03
Copy link
Contributor Author

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 fitswcs converter.

from astropy import wcs


def create_sip_distortion_wcs():
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should I create a separate fixture for the WCS test cases, or will the existing ones work?

Copy link
Contributor

@braingram braingram left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

def assert_hdu_list_equal(a, b):

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')

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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 header.

Comment on lines 56 to 58
@pytest.mark.filterwarnings("ignore::astropy.wcs.wcs.FITSFixedWarning")
@pytest.mark.filterwarnings(
"ignore:Some non-standard WCS keywords were excluded:astropy.utils.exceptions.AstropyWarning",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's triggering these warnings?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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?

Copy link
Contributor Author

@ViciousEagle03 ViciousEagle03 Sep 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I believe so.
Is there a case we are not considering? If so, could you please explain how we can account for it?

asdf_astropy/converters/slicedwcs/slicedwcs.py Outdated Show resolved Hide resolved
return [wcs0, wcs1, wcs2, wcs_ellipsis, wcs3]


@pytest.mark.filterwarnings("ignore::astropy.wcs.wcs.FITSFixedWarning")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's triggering this warning?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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?

Comment on lines +8 to +9
asdf_standard_requirement:
gte: 1.5.0
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why restrict the standard here?

Copy link
Contributor Author

@ViciousEagle03 ViciousEagle03 Sep 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tox tests seem to fail with the 1.6.0 version.

items:
- oneOf:
- type: integer
- type: object
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- type: object
- type: object

This needs more details. It appears to be an object with start stop step all either int or None.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, I will include it in the schema

@@ -0,0 +1,34 @@
from asdf.extension import Converter
Copy link
Contributor

Choose a reason for hiding this comment

The 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 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).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, moving them to a wcs submodule makes sense and will keep things organized.

@ViciousEagle03
Copy link
Contributor Author

@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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add support for serialization of astropy.wcs.WCS objects to ASDF.
3 participants