From c9954005e3f85568d3910531f6d11c4d681691fb Mon Sep 17 00:00:00 2001 From: ViciousEagle03 Date: Wed, 24 Jul 2024 21:15:31 +0530 Subject: [PATCH 01/30] Add converter and schema for basic astropy.wcs.WCS objects --- asdf_astropy/converters/__init__.py | 3 +- asdf_astropy/converters/fits/__init__.py | 2 + asdf_astropy/converters/fits/fitswcs.py | 29 +++++++++++++ .../converters/fits/tests/test_fitswcs.py | 41 +++++++++++++++++++ asdf_astropy/extensions.py | 2 + .../resources/manifests/astropy-1.0.0.yaml | 5 +++ .../resources/schemas/fits/fitswcs-1.0.0.yaml | 17 ++++++++ 7 files changed, 98 insertions(+), 1 deletion(-) create mode 100644 asdf_astropy/converters/fits/fitswcs.py create mode 100644 asdf_astropy/converters/fits/tests/test_fitswcs.py create mode 100644 asdf_astropy/resources/schemas/fits/fitswcs-1.0.0.yaml diff --git a/asdf_astropy/converters/__init__.py b/asdf_astropy/converters/__init__.py index 92d7730a..5ecf3773 100644 --- a/asdf_astropy/converters/__init__.py +++ b/asdf_astropy/converters/__init__.py @@ -15,6 +15,7 @@ "FitsConverter", "AsdfFitsConverter", "AstropyFitsConverter", + "FitsWCSConverter", "ColumnConverter", "AstropyTableConverter", "AsdfTableConverter", @@ -52,7 +53,7 @@ SkyCoordConverter, SpectralCoordConverter, ) -from .fits import AsdfFitsConverter, AstropyFitsConverter, FitsConverter +from .fits import AsdfFitsConverter, AstropyFitsConverter, FitsConverter, FitsWCSConverter from .nddata import UncertaintyConverter from .table import AsdfTableConverter, AstropyTableConverter, ColumnConverter, NdarrayMixinConverter from .time import TimeConverter, TimeDeltaConverter diff --git a/asdf_astropy/converters/fits/__init__.py b/asdf_astropy/converters/fits/__init__.py index 4647e59c..4f915659 100644 --- a/asdf_astropy/converters/fits/__init__.py +++ b/asdf_astropy/converters/fits/__init__.py @@ -2,6 +2,8 @@ "FitsConverter", "AsdfFitsConverter", "AstropyFitsConverter", + "FitsWCSConverter", ] from .fits import AsdfFitsConverter, AstropyFitsConverter, FitsConverter +from .fitswcs import FitsWCSConverter diff --git a/asdf_astropy/converters/fits/fitswcs.py b/asdf_astropy/converters/fits/fitswcs.py new file mode 100644 index 00000000..a01fde2c --- /dev/null +++ b/asdf_astropy/converters/fits/fitswcs.py @@ -0,0 +1,29 @@ +from asdf.extension import Converter + + +class FitsWCSConverter(Converter): + """ + 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 + such as tabular or distortion WCSes. + + Future work: + - Until the support for tabular and distortion WCSes is added, throw error for such WCSes when passed through in the converter + - Implement mechanisms to detect tabular and distortion WCSes and support their serialization + """ + 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 + + header = node["header"] + wcs = WCS(header) + return wcs + + def to_yaml_tree(self, wcs, tag, ctx): + node = {} + node["header"] = dict(wcs.to_header()) + return node diff --git a/asdf_astropy/converters/fits/tests/test_fitswcs.py b/asdf_astropy/converters/fits/tests/test_fitswcs.py new file mode 100644 index 00000000..3cedc02e --- /dev/null +++ b/asdf_astropy/converters/fits/tests/test_fitswcs.py @@ -0,0 +1,41 @@ +import asdf +import pytest +from astropy.wcs import WCS + + +def create_wcs(): + header = { + 'CTYPE1': 'TIME', + 'CUNIT1': 'min', + 'CDELT1': 0.4, + 'CRPIX1': 0, + 'CRVAL1': 0, + 'CTYPE2': 'WAVE', + 'CUNIT2': 'Angstrom', + 'CDELT2': 0.2, + 'CRPIX2': 0, + 'CRVAL2': 0, + 'CTYPE3': 'HPLT-TAN', + 'CUNIT3': 'arcsec', + 'CDELT3': 20, + 'CRPIX3': 0, + 'CRVAL3': 0, + 'CTYPE4': 'HPLN-TAN', + 'CUNIT4': 'arcsec', + 'CDELT4': 5, + 'CRPIX4': 5, + 'CRVAL4': 0, + } + return WCS(header) + +@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() diff --git a/asdf_astropy/extensions.py b/asdf_astropy/extensions.py index ffaf0904..2b4318c5 100644 --- a/asdf_astropy/extensions.py +++ b/asdf_astropy/extensions.py @@ -12,6 +12,7 @@ from .converters.coordinates.sky_coord import SkyCoordConverter from .converters.coordinates.spectral_coord import SpectralCoordConverter from .converters.fits.fits import AsdfFitsConverter, AstropyFitsConverter +from .converters.fits.fitswcs import FitsWCSConverter from .converters.nddata.uncertainty import UncertaintyConverter from .converters.table.table import AsdfTableConverter, AstropyTableConverter, ColumnConverter, NdarrayMixinConverter from .converters.time.time import TimeConverter @@ -478,6 +479,7 @@ AstropyFitsConverter(), NdarrayMixinConverter(), UncertaintyConverter(), + FitsWCSConverter(), ] _COORDINATES_MANIFEST_URIS = [ diff --git a/asdf_astropy/resources/manifests/astropy-1.0.0.yaml b/asdf_astropy/resources/manifests/astropy-1.0.0.yaml index 4f6d25b7..c7eba75d 100644 --- a/asdf_astropy/resources/manifests/astropy-1.0.0.yaml +++ b/asdf_astropy/resources/manifests/astropy-1.0.0.yaml @@ -28,6 +28,11 @@ 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 + schema_uri: http://astropy.org/schemas/astropy/fits/fitswcs-1.0.0 + title: Represents an astropy.wcs.WCS object + description: |- + Supports serialization of the simple wcs objects header - tag_uri: tag:astropy.org:astropy/table/table-1.0.0 schema_uri: http://astropy.org/schemas/astropy/table/table-1.0.0 title: A table. diff --git a/asdf_astropy/resources/schemas/fits/fitswcs-1.0.0.yaml b/asdf_astropy/resources/schemas/fits/fitswcs-1.0.0.yaml new file mode 100644 index 00000000..86a15ad2 --- /dev/null +++ b/asdf_astropy/resources/schemas/fits/fitswcs-1.0.0.yaml @@ -0,0 +1,17 @@ +%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 + +description: + Represents the fits object + +allOf: + - tag: "tag:astropy.org:astropy/fits/fitswcs-1.0.0" + - type: object + properties: + header: + type: object \ No newline at end of file From 1272c5d7491203a65400cfd8bf4943ce0d2fb76b Mon Sep 17 00:00:00 2001 From: ViciousEagle03 Date: Wed, 24 Jul 2024 22:15:06 +0530 Subject: [PATCH 02/30] Style Fix --- asdf_astropy/converters/fits/fitswcs.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/asdf_astropy/converters/fits/fitswcs.py b/asdf_astropy/converters/fits/fitswcs.py index a01fde2c..63a50d7c 100644 --- a/asdf_astropy/converters/fits/fitswcs.py +++ b/asdf_astropy/converters/fits/fitswcs.py @@ -10,18 +10,19 @@ class FitsWCSConverter(Converter): such as tabular or distortion WCSes. Future work: - - Until the support for tabular and distortion WCSes is added, throw error for such WCSes when passed through in the converter + - Until the support for tabular and distortion WCSes is added, + throw error for such WCSes when passed through in the converter - Implement mechanisms to detect tabular and distortion WCSes and support their serialization """ + tags = ("tag:astropy.org:astropy/fits/fitswcs-*",) - types = ("astropy.wcs.wcs.WCS", ) + types = ("astropy.wcs.wcs.WCS",) def from_yaml_tree(self, node, tag, ctx): from astropy.wcs import WCS header = node["header"] - wcs = WCS(header) - return wcs + return WCS(header) def to_yaml_tree(self, wcs, tag, ctx): node = {} From 880ff6d9fcc9b9032f4b94ec74f6cf04e3457c09 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 4 Aug 2024 19:46:39 +0000 Subject: [PATCH 03/30] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- .../converters/fits/tests/test_fitswcs.py | 41 ++++++++++--------- .../resources/schemas/fits/fitswcs-1.0.0.yaml | 2 +- 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/asdf_astropy/converters/fits/tests/test_fitswcs.py b/asdf_astropy/converters/fits/tests/test_fitswcs.py index 3cedc02e..7d08fbee 100644 --- a/asdf_astropy/converters/fits/tests/test_fitswcs.py +++ b/asdf_astropy/converters/fits/tests/test_fitswcs.py @@ -5,29 +5,30 @@ def create_wcs(): header = { - 'CTYPE1': 'TIME', - 'CUNIT1': 'min', - 'CDELT1': 0.4, - 'CRPIX1': 0, - 'CRVAL1': 0, - 'CTYPE2': 'WAVE', - 'CUNIT2': 'Angstrom', - 'CDELT2': 0.2, - 'CRPIX2': 0, - 'CRVAL2': 0, - 'CTYPE3': 'HPLT-TAN', - 'CUNIT3': 'arcsec', - 'CDELT3': 20, - 'CRPIX3': 0, - 'CRVAL3': 0, - 'CTYPE4': 'HPLN-TAN', - 'CUNIT4': 'arcsec', - 'CDELT4': 5, - 'CRPIX4': 5, - 'CRVAL4': 0, + "CTYPE1": "TIME", + "CUNIT1": "min", + "CDELT1": 0.4, + "CRPIX1": 0, + "CRVAL1": 0, + "CTYPE2": "WAVE", + "CUNIT2": "Angstrom", + "CDELT2": 0.2, + "CRPIX2": 0, + "CRVAL2": 0, + "CTYPE3": "HPLT-TAN", + "CUNIT3": "arcsec", + "CDELT3": 20, + "CRPIX3": 0, + "CRVAL3": 0, + "CTYPE4": "HPLN-TAN", + "CUNIT4": "arcsec", + "CDELT4": 5, + "CRPIX4": 5, + "CRVAL4": 0, } return WCS(header) + @pytest.mark.parametrize("wcs", [create_wcs()]) def test_wcs_serialization(wcs, tmp_path): file_path = tmp_path / "test_wcs.asdf" diff --git a/asdf_astropy/resources/schemas/fits/fitswcs-1.0.0.yaml b/asdf_astropy/resources/schemas/fits/fitswcs-1.0.0.yaml index 86a15ad2..4f020b08 100644 --- a/asdf_astropy/resources/schemas/fits/fitswcs-1.0.0.yaml +++ b/asdf_astropy/resources/schemas/fits/fitswcs-1.0.0.yaml @@ -14,4 +14,4 @@ allOf: - type: object properties: header: - type: object \ No newline at end of file + type: object From a77c22915bd0a370527465b84e4fd82afe3abc54 Mon Sep 17 00:00:00 2001 From: ViciousEagle03 Date: Wed, 7 Aug 2024 21:54:13 +0530 Subject: [PATCH 04/30] Add SlicedLowLevelWCS serialization logic --- asdf_astropy/converters/slicedwcs/__init__.py | 5 +++ .../converters/slicedwcs/slicedwcs.py | 36 +++++++++++++++++++ .../converters/slicedwcs/test/__init__.py | 0 .../slicedwcs/test/test_slicedwcs.py | 34 ++++++++++++++++++ asdf_astropy/extensions.py | 2 ++ .../resources/manifests/astropy-1.0.0.yaml | 5 +++ .../schemas/slicedwcs/slicedwcs-1.0.0.yaml | 19 ++++++++++ 7 files changed, 101 insertions(+) create mode 100644 asdf_astropy/converters/slicedwcs/__init__.py create mode 100644 asdf_astropy/converters/slicedwcs/slicedwcs.py create mode 100644 asdf_astropy/converters/slicedwcs/test/__init__.py create mode 100644 asdf_astropy/converters/slicedwcs/test/test_slicedwcs.py create mode 100644 asdf_astropy/resources/schemas/slicedwcs/slicedwcs-1.0.0.yaml diff --git a/asdf_astropy/converters/slicedwcs/__init__.py b/asdf_astropy/converters/slicedwcs/__init__.py new file mode 100644 index 00000000..8473b67e --- /dev/null +++ b/asdf_astropy/converters/slicedwcs/__init__.py @@ -0,0 +1,5 @@ +__all__ = [ + "SlicedWCSConverter", +] + +from .slicedwcs import SlicedWCSConverter diff --git a/asdf_astropy/converters/slicedwcs/slicedwcs.py b/asdf_astropy/converters/slicedwcs/slicedwcs.py new file mode 100644 index 00000000..e4745211 --- /dev/null +++ b/asdf_astropy/converters/slicedwcs/slicedwcs.py @@ -0,0 +1,36 @@ +import ast + +from asdf.extension import Converter + + +class SlicedWCSConverter(Converter): + tags = ("tag:astropy.org:astropy/slicedwcs/slicedwcs-*",) + types = ("astropy.wcs.wcsapi.wrappers.sliced_wcs.SlicedLowLevelWCS",) + + def parse_slice_string(self, slice_str): + if slice_str.isdigit(): + return int(ast.literal_eval(slice_str)) + slice_str = slice_str[len("slice(") : -1] + parts = slice_str.split(",") + start = ast.literal_eval(parts[0].strip()) + stop = ast.literal_eval(parts[1].strip()) + step = ast.literal_eval(parts[2].strip()) + return slice(start, stop, step) + + def from_yaml_tree(self, node, tag, ctx): + from astropy.wcs.wcsapi.wrappers.sliced_wcs import SlicedLowLevelWCS + + wcs = node["wcs"] + slice_array = node["slices_array"] + slice_array = [self.parse_slice_string(s) for s in slice_array] + + return SlicedLowLevelWCS(wcs, slice_array) + + def to_yaml_tree(self, sl, tag, ctx): + import astropy + + node = {} + if isinstance(sl._wcs, astropy.wcs.WCS): + node["wcs"] = sl._wcs + node["slices_array"] = [str(item) for item in sl._slices_array] + return node diff --git a/asdf_astropy/converters/slicedwcs/test/__init__.py b/asdf_astropy/converters/slicedwcs/test/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/asdf_astropy/converters/slicedwcs/test/test_slicedwcs.py b/asdf_astropy/converters/slicedwcs/test/test_slicedwcs.py new file mode 100644 index 00000000..3106c622 --- /dev/null +++ b/asdf_astropy/converters/slicedwcs/test/test_slicedwcs.py @@ -0,0 +1,34 @@ +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.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() + assert sl_wcs._slices_array == loaded_sl_wcs._slices_array diff --git a/asdf_astropy/extensions.py b/asdf_astropy/extensions.py index 2b4318c5..f528e34d 100644 --- a/asdf_astropy/extensions.py +++ b/asdf_astropy/extensions.py @@ -14,6 +14,7 @@ from .converters.fits.fits import AsdfFitsConverter, AstropyFitsConverter from .converters.fits.fitswcs import FitsWCSConverter from .converters.nddata.uncertainty import UncertaintyConverter +from .converters.slicedwcs.slicedwcs import SlicedWCSConverter from .converters.table.table import AsdfTableConverter, AstropyTableConverter, ColumnConverter, NdarrayMixinConverter from .converters.time.time import TimeConverter from .converters.time.time_delta import TimeDeltaConverter @@ -480,6 +481,7 @@ NdarrayMixinConverter(), UncertaintyConverter(), FitsWCSConverter(), + SlicedWCSConverter(), ] _COORDINATES_MANIFEST_URIS = [ diff --git a/asdf_astropy/resources/manifests/astropy-1.0.0.yaml b/asdf_astropy/resources/manifests/astropy-1.0.0.yaml index c7eba75d..2c37a76d 100644 --- a/asdf_astropy/resources/manifests/astropy-1.0.0.yaml +++ b/asdf_astropy/resources/manifests/astropy-1.0.0.yaml @@ -33,6 +33,11 @@ tags: title: Represents an astropy.wcs.WCS object description: |- Supports serialization of the simple wcs objects header +- tag_uri: tag:astropy.org:astropy/slicedwcs/slicedwcs-1.0.0 + schema_uri: http://astropy.org/schemas/astropy/slicedwcs/slicedwcs-1.0.0 + title: Represents a SlicedLowLevelWCS object + description: |- + Supports serialization of the sliced_wcs objects - tag_uri: tag:astropy.org:astropy/table/table-1.0.0 schema_uri: http://astropy.org/schemas/astropy/table/table-1.0.0 title: A table. diff --git a/asdf_astropy/resources/schemas/slicedwcs/slicedwcs-1.0.0.yaml b/asdf_astropy/resources/schemas/slicedwcs/slicedwcs-1.0.0.yaml new file mode 100644 index 00000000..622f9a0d --- /dev/null +++ b/asdf_astropy/resources/schemas/slicedwcs/slicedwcs-1.0.0.yaml @@ -0,0 +1,19 @@ +%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: + - tag: "tag:astropy.org:astropy/slicedwcs/slicedwcs-1.0.0" + - type: object + properties: + wcs: + tag: "tag:astropy.org:astropy/fits/fitswcs-1.0.0" + slices_array: + type: array From 45fd10a9259a3a96b0738b5d786f7a7ee4bb9d29 Mon Sep 17 00:00:00 2001 From: ViciousEagle03 Date: Wed, 7 Aug 2024 22:04:53 +0530 Subject: [PATCH 05/30] Remove unnecessary check --- asdf_astropy/converters/slicedwcs/slicedwcs.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/asdf_astropy/converters/slicedwcs/slicedwcs.py b/asdf_astropy/converters/slicedwcs/slicedwcs.py index e4745211..d3ee5e6c 100644 --- a/asdf_astropy/converters/slicedwcs/slicedwcs.py +++ b/asdf_astropy/converters/slicedwcs/slicedwcs.py @@ -27,10 +27,7 @@ def from_yaml_tree(self, node, tag, ctx): return SlicedLowLevelWCS(wcs, slice_array) def to_yaml_tree(self, sl, tag, ctx): - import astropy - node = {} - if isinstance(sl._wcs, astropy.wcs.WCS): - node["wcs"] = sl._wcs + node["wcs"] = sl._wcs node["slices_array"] = [str(item) for item in sl._slices_array] return node From 4d04f5bced670559dc0947072906db0b776c1638 Mon Sep 17 00:00:00 2001 From: ViciousEagle03 Date: Sat, 24 Aug 2024 04:56:30 +0530 Subject: [PATCH 06/30] Apply suggestions from code review --- asdf_astropy/converters/fits/fitswcs.py | 19 ++----- .../converters/fits/tests/test_fitswcs.py | 51 ++++++++----------- .../converters/slicedwcs/slicedwcs.py | 33 ++++++------ .../slicedwcs/test/test_slicedwcs.py | 17 ++++--- .../resources/manifests/astropy-1.0.0.yaml | 10 ---- .../resources/manifests/astropy-1.1.0.yaml | 17 +++++++ .../resources/schemas/fits/fitswcs-1.0.0.yaml | 5 +- .../schemas/slicedwcs/slicedwcs-1.0.0.yaml | 6 +++ 8 files changed, 77 insertions(+), 81 deletions(-) diff --git a/asdf_astropy/converters/fits/fitswcs.py b/asdf_astropy/converters/fits/fitswcs.py index 63a50d7c..2db29661 100644 --- a/asdf_astropy/converters/fits/fitswcs.py +++ b/asdf_astropy/converters/fits/fitswcs.py @@ -2,29 +2,16 @@ class FitsWCSConverter(Converter): - """ - 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 - such as tabular or distortion WCSes. - - Future work: - - Until the support for tabular and distortion WCSes is added, - throw error for such WCSes when passed through in the converter - - Implement mechanisms to detect tabular and distortion WCSes and support their serialization - """ - 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 - header = node["header"] - return WCS(header) + primary_hdu = node["Hdu"][0] + return WCS(primary_hdu.header) def to_yaml_tree(self, wcs, tag, ctx): node = {} - node["header"] = dict(wcs.to_header()) + node["Hdu"] = wcs.to_fits() return node diff --git a/asdf_astropy/converters/fits/tests/test_fitswcs.py b/asdf_astropy/converters/fits/tests/test_fitswcs.py index 7d08fbee..69dd9624 100644 --- a/asdf_astropy/converters/fits/tests/test_fitswcs.py +++ b/asdf_astropy/converters/fits/tests/test_fitswcs.py @@ -1,42 +1,33 @@ +import warnings + import asdf import pytest -from astropy.wcs import WCS +from astropy.io import fits +from astropy.utils.data import download_file +from astropy.wcs import WCS, FITSFixedWarning def create_wcs(): - header = { - "CTYPE1": "TIME", - "CUNIT1": "min", - "CDELT1": 0.4, - "CRPIX1": 0, - "CRVAL1": 0, - "CTYPE2": "WAVE", - "CUNIT2": "Angstrom", - "CDELT2": 0.2, - "CRPIX2": 0, - "CRVAL2": 0, - "CTYPE3": "HPLT-TAN", - "CUNIT3": "arcsec", - "CDELT3": 20, - "CRPIX3": 0, - "CRVAL3": 0, - "CTYPE4": "HPLN-TAN", - "CUNIT4": "arcsec", - "CDELT4": 5, - "CRPIX4": 5, - "CRVAL4": 0, - } - return WCS(header) + urls = [ + "http://data.astropy.org/tutorials/FITS-cubes/reduced_TAN_C14.fits", + "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.parametrize("wcs", [create_wcs()]) +@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: + with asdf.AsdfFile() as af, warnings.catch_warnings(): + warnings.simplefilter("ignore", FITSFixedWarning) 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() + with warnings.catch_warnings(): + warnings.simplefilter("ignore", FITSFixedWarning) + with asdf.open(file_path) as af: + loaded_wcs = af["wcs"] + assert wcs.to_header() == loaded_wcs.to_header() diff --git a/asdf_astropy/converters/slicedwcs/slicedwcs.py b/asdf_astropy/converters/slicedwcs/slicedwcs.py index d3ee5e6c..404a6d3d 100644 --- a/asdf_astropy/converters/slicedwcs/slicedwcs.py +++ b/asdf_astropy/converters/slicedwcs/slicedwcs.py @@ -1,5 +1,3 @@ -import ast - from asdf.extension import Converter @@ -7,27 +5,30 @@ class SlicedWCSConverter(Converter): tags = ("tag:astropy.org:astropy/slicedwcs/slicedwcs-*",) types = ("astropy.wcs.wcsapi.wrappers.sliced_wcs.SlicedLowLevelWCS",) - def parse_slice_string(self, slice_str): - if slice_str.isdigit(): - return int(ast.literal_eval(slice_str)) - slice_str = slice_str[len("slice(") : -1] - parts = slice_str.split(",") - start = ast.literal_eval(parts[0].strip()) - stop = ast.literal_eval(parts[1].strip()) - step = ast.literal_eval(parts[2].strip()) - return slice(start, stop, step) - def from_yaml_tree(self, node, tag, ctx): from astropy.wcs.wcsapi.wrappers.sliced_wcs import SlicedLowLevelWCS wcs = node["wcs"] - slice_array = node["slices_array"] - slice_array = [self.parse_slice_string(s) for s in slice_array] - + 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"] = [str(item) for item in sl._slices_array] + node["slices_array"] = [] + + for s in sl._slices_array: + if isinstance(s, slice): + 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, + }, + ) + else: + node["slices_array"].append(s) return node diff --git a/asdf_astropy/converters/slicedwcs/test/test_slicedwcs.py b/asdf_astropy/converters/slicedwcs/test/test_slicedwcs.py index 3106c622..f5d5d046 100644 --- a/asdf_astropy/converters/slicedwcs/test/test_slicedwcs.py +++ b/asdf_astropy/converters/slicedwcs/test/test_slicedwcs.py @@ -1,7 +1,9 @@ +import warnings + import asdf import numpy as np import pytest -from astropy.wcs import WCS +from astropy.wcs import WCS, FITSFixedWarning from astropy.wcs.wcsapi.wrappers.sliced_wcs import SlicedLowLevelWCS @@ -24,11 +26,14 @@ def create_wcs(): @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: + with asdf.AsdfFile() as af, warnings.catch_warnings(): + warnings.simplefilter("ignore", FITSFixedWarning) 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() - assert sl_wcs._slices_array == loaded_sl_wcs._slices_array + with warnings.catch_warnings(): + warnings.simplefilter("ignore", FITSFixedWarning) + 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() + assert sl_wcs._slices_array == loaded_sl_wcs._slices_array diff --git a/asdf_astropy/resources/manifests/astropy-1.0.0.yaml b/asdf_astropy/resources/manifests/astropy-1.0.0.yaml index 2c37a76d..4f6d25b7 100644 --- a/asdf_astropy/resources/manifests/astropy-1.0.0.yaml +++ b/asdf_astropy/resources/manifests/astropy-1.0.0.yaml @@ -28,16 +28,6 @@ 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 - schema_uri: http://astropy.org/schemas/astropy/fits/fitswcs-1.0.0 - title: Represents an astropy.wcs.WCS object - description: |- - Supports serialization of the simple wcs objects header -- tag_uri: tag:astropy.org:astropy/slicedwcs/slicedwcs-1.0.0 - schema_uri: http://astropy.org/schemas/astropy/slicedwcs/slicedwcs-1.0.0 - title: Represents a SlicedLowLevelWCS object - description: |- - Supports serialization of the sliced_wcs objects - tag_uri: tag:astropy.org:astropy/table/table-1.0.0 schema_uri: http://astropy.org/schemas/astropy/table/table-1.0.0 title: A table. diff --git a/asdf_astropy/resources/manifests/astropy-1.1.0.yaml b/asdf_astropy/resources/manifests/astropy-1.1.0.yaml index 9e8094a9..49e26253 100644 --- a/asdf_astropy/resources/manifests/astropy-1.1.0.yaml +++ b/asdf_astropy/resources/manifests/astropy-1.1.0.yaml @@ -54,3 +54,20 @@ tags: title: NdarrayMixin column. description: |- Represents an astropy.table.NdarrayMixin instance. +- tag_uri: tag:astropy.org:astropy/slicedwcs/slicedwcs-1.0.0 + schema_uri: http://astropy.org/schemas/astropy/slicedwcs/slicedwcs-1.0.0 + title: Represents an instance of SlicedLowLevelWCS + 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. +- tag_uri: tag:astropy.org:astropy/fits/fitswcs-1.0.0 + schema_uri: http://astropy.org/schemas/astropy/fits/fitswcs-1.0.0 + title: FITS WCS (World Coordinate System) Converter + 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. diff --git a/asdf_astropy/resources/schemas/fits/fitswcs-1.0.0.yaml b/asdf_astropy/resources/schemas/fits/fitswcs-1.0.0.yaml index 4f020b08..32617c40 100644 --- a/asdf_astropy/resources/schemas/fits/fitswcs-1.0.0.yaml +++ b/asdf_astropy/resources/schemas/fits/fitswcs-1.0.0.yaml @@ -10,8 +10,7 @@ description: Represents the fits object allOf: - - tag: "tag:astropy.org:astropy/fits/fitswcs-1.0.0" - type: object properties: - header: - type: object + hdu: + tag: "tag:stsci.edu:asdf/fits/fits-*" diff --git a/asdf_astropy/resources/schemas/slicedwcs/slicedwcs-1.0.0.yaml b/asdf_astropy/resources/schemas/slicedwcs/slicedwcs-1.0.0.yaml index 622f9a0d..8db22487 100644 --- a/asdf_astropy/resources/schemas/slicedwcs/slicedwcs-1.0.0.yaml +++ b/asdf_astropy/resources/schemas/slicedwcs/slicedwcs-1.0.0.yaml @@ -17,3 +17,9 @@ allOf: tag: "tag:astropy.org:astropy/fits/fitswcs-1.0.0" slices_array: type: array + items: + - oneOf: + - type: integer + - type: object + + required: ["wcs", "slices_array"] From 2e37f2a31361f5097ded1c5fc709465a1692f6a3 Mon Sep 17 00:00:00 2001 From: ViciousEagle03 Date: Sat, 24 Aug 2024 05:17:18 +0530 Subject: [PATCH 07/30] Update schema for fitswcs and slicedwcs instance --- asdf_astropy/resources/schemas/fits/fitswcs-1.0.0.yaml | 2 ++ .../resources/schemas/slicedwcs/slicedwcs-1.0.0.yaml | 5 ++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/asdf_astropy/resources/schemas/fits/fitswcs-1.0.0.yaml b/asdf_astropy/resources/schemas/fits/fitswcs-1.0.0.yaml index 32617c40..08827d9f 100644 --- a/asdf_astropy/resources/schemas/fits/fitswcs-1.0.0.yaml +++ b/asdf_astropy/resources/schemas/fits/fitswcs-1.0.0.yaml @@ -14,3 +14,5 @@ allOf: properties: hdu: tag: "tag:stsci.edu:asdf/fits/fits-*" + + required: ["hdu"] diff --git a/asdf_astropy/resources/schemas/slicedwcs/slicedwcs-1.0.0.yaml b/asdf_astropy/resources/schemas/slicedwcs/slicedwcs-1.0.0.yaml index 8db22487..8116c865 100644 --- a/asdf_astropy/resources/schemas/slicedwcs/slicedwcs-1.0.0.yaml +++ b/asdf_astropy/resources/schemas/slicedwcs/slicedwcs-1.0.0.yaml @@ -10,11 +10,10 @@ description: Represents the SlicedLowLevelWCS object allOf: - - tag: "tag:astropy.org:astropy/slicedwcs/slicedwcs-1.0.0" - type: object properties: wcs: - tag: "tag:astropy.org:astropy/fits/fitswcs-1.0.0" + tag: "tag:astropy.org:astropy/fits/fitswcs-1*" slices_array: type: array items: @@ -22,4 +21,4 @@ allOf: - type: integer - type: object - required: ["wcs", "slices_array"] + required: ["wcs", "slices_array"] From 5afd469317026e2dae8a928bc23d57f9723d9b41 Mon Sep 17 00:00:00 2001 From: ViciousEagle03 Date: Sun, 25 Aug 2024 15:12:34 +0530 Subject: [PATCH 08/30] Fix tests and schema --- asdf_astropy/converters/fits/fitswcs.py | 4 ++-- .../converters/fits/tests/test_fitswcs.py | 12 +++++------- .../slicedwcs/test/test_slicedwcs.py | 18 +++++++----------- .../resources/schemas/fits/fitswcs-1.0.0.yaml | 4 ++-- .../schemas/slicedwcs/slicedwcs-1.0.0.yaml | 2 +- 5 files changed, 17 insertions(+), 23 deletions(-) diff --git a/asdf_astropy/converters/fits/fitswcs.py b/asdf_astropy/converters/fits/fitswcs.py index 2db29661..403af7bd 100644 --- a/asdf_astropy/converters/fits/fitswcs.py +++ b/asdf_astropy/converters/fits/fitswcs.py @@ -8,10 +8,10 @@ class FitsWCSConverter(Converter): def from_yaml_tree(self, node, tag, ctx): from astropy.wcs import WCS - primary_hdu = node["Hdu"][0] + primary_hdu = node["hdu"][0] return WCS(primary_hdu.header) def to_yaml_tree(self, wcs, tag, ctx): node = {} - node["Hdu"] = wcs.to_fits() + node["hdu"] = wcs.to_fits() return node diff --git a/asdf_astropy/converters/fits/tests/test_fitswcs.py b/asdf_astropy/converters/fits/tests/test_fitswcs.py index 69dd9624..70c183ef 100644 --- a/asdf_astropy/converters/fits/tests/test_fitswcs.py +++ b/asdf_astropy/converters/fits/tests/test_fitswcs.py @@ -18,16 +18,14 @@ def create_wcs(): 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, warnings.catch_warnings(): - warnings.simplefilter("ignore", FITSFixedWarning) + with asdf.AsdfFile() as af: af["wcs"] = wcs af.write_to(file_path) - with warnings.catch_warnings(): - warnings.simplefilter("ignore", FITSFixedWarning) - with asdf.open(file_path) as af: - loaded_wcs = af["wcs"] - assert wcs.to_header() == loaded_wcs.to_header() + with asdf.open(file_path) as af: + loaded_wcs = af["wcs"] + assert wcs.to_header() == loaded_wcs.to_header() diff --git a/asdf_astropy/converters/slicedwcs/test/test_slicedwcs.py b/asdf_astropy/converters/slicedwcs/test/test_slicedwcs.py index f5d5d046..5b09c375 100644 --- a/asdf_astropy/converters/slicedwcs/test/test_slicedwcs.py +++ b/asdf_astropy/converters/slicedwcs/test/test_slicedwcs.py @@ -1,9 +1,7 @@ -import warnings - import asdf import numpy as np import pytest -from astropy.wcs import WCS, FITSFixedWarning +from astropy.wcs import WCS from astropy.wcs.wcsapi.wrappers.sliced_wcs import SlicedLowLevelWCS @@ -23,17 +21,15 @@ def create_wcs(): return [wcs0, wcs1, wcs2, wcs_ellipsis, wcs3] +@pytest.mark.filterwarnings("ignore::astropy.wcs.wcs.FITSFixedWarning") @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, warnings.catch_warnings(): - warnings.simplefilter("ignore", FITSFixedWarning) + with asdf.AsdfFile() as af: af["sl_wcs"] = sl_wcs af.write_to(file_path) - with warnings.catch_warnings(): - warnings.simplefilter("ignore", FITSFixedWarning) - 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() - assert sl_wcs._slices_array == loaded_sl_wcs._slices_array + 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() + assert sl_wcs._slices_array == loaded_sl_wcs._slices_array diff --git a/asdf_astropy/resources/schemas/fits/fitswcs-1.0.0.yaml b/asdf_astropy/resources/schemas/fits/fitswcs-1.0.0.yaml index 08827d9f..8147aea0 100644 --- a/asdf_astropy/resources/schemas/fits/fitswcs-1.0.0.yaml +++ b/asdf_astropy/resources/schemas/fits/fitswcs-1.0.0.yaml @@ -13,6 +13,6 @@ allOf: - type: object properties: hdu: - tag: "tag:stsci.edu:asdf/fits/fits-*" + tag: "tag:astropy.org:astropy/fits/fits-*" - required: ["hdu"] + required: ["hdu"] diff --git a/asdf_astropy/resources/schemas/slicedwcs/slicedwcs-1.0.0.yaml b/asdf_astropy/resources/schemas/slicedwcs/slicedwcs-1.0.0.yaml index 8116c865..4215b6a5 100644 --- a/asdf_astropy/resources/schemas/slicedwcs/slicedwcs-1.0.0.yaml +++ b/asdf_astropy/resources/schemas/slicedwcs/slicedwcs-1.0.0.yaml @@ -21,4 +21,4 @@ allOf: - type: integer - type: object - required: ["wcs", "slices_array"] + required: ["wcs", "slices_array"] From 54a33dac6390093ecc4c1404d62ce56724f6a1d6 Mon Sep 17 00:00:00 2001 From: ViciousEagle03 Date: Sun, 25 Aug 2024 15:25:15 +0530 Subject: [PATCH 09/30] Add changelog --- CHANGES.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 4027b550..58178013 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -12,6 +12,8 @@ - Add support for astropy.nddata.uncertainty classes [#239] +- Add support for astropy.wcs.WCS and astropy.wcs.wcsapi.SlicedLowLevelWCS [##235] + 0.6.1 (2024-04-05) ------------------ From 7262e0db2e3658b2110a5e7283387aead040a2df Mon Sep 17 00:00:00 2001 From: ViciousEagle03 Date: Sun, 25 Aug 2024 15:38:19 +0530 Subject: [PATCH 10/30] Revert small change --- CHANGES.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 58178013..ba11c5a1 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -12,7 +12,7 @@ - Add support for astropy.nddata.uncertainty classes [#239] -- Add support for astropy.wcs.WCS and astropy.wcs.wcsapi.SlicedLowLevelWCS [##235] +- Add support for astropy.wcs.WCS and astropy.wcs.wcsapi.SlicedLowLevelWCS [#235] 0.6.1 (2024-04-05) ------------------ From 3e2cb9d069a515b3a56421099b54c41bdff62553 Mon Sep 17 00:00:00 2001 From: ViciousEagle03 Date: Wed, 28 Aug 2024 19:37:03 +0530 Subject: [PATCH 11/30] Apply review suggestion --- asdf_astropy/converters/fits/fitswcs.py | 8 +- .../converters/fits/tests/test_fitswcs.py | 77 +++++++++++++++---- .../slicedwcs/test/test_slicedwcs.py | 1 + .../resources/schemas/fits/fitswcs-1.0.0.yaml | 8 +- .../schemas/slicedwcs/slicedwcs-1.0.0.yaml | 10 ++- 5 files changed, 79 insertions(+), 25 deletions(-) diff --git a/asdf_astropy/converters/fits/fitswcs.py b/asdf_astropy/converters/fits/fitswcs.py index 403af7bd..aecec738 100644 --- a/asdf_astropy/converters/fits/fitswcs.py +++ b/asdf_astropy/converters/fits/fitswcs.py @@ -8,10 +8,12 @@ class FitsWCSConverter(Converter): def from_yaml_tree(self, node, tag, ctx): from astropy.wcs import WCS - primary_hdu = node["hdu"][0] - return WCS(primary_hdu.header) + return WCS(node["hdu"][0].header, fobj=node["hdu"]) def to_yaml_tree(self, wcs, tag, ctx): node = {} - node["hdu"] = wcs.to_fits() + if wcs.sip is not None: + node["hdu"] = wcs.to_fits(relax=True) + else: + node["hdu"] = wcs.to_fits() return node diff --git a/asdf_astropy/converters/fits/tests/test_fitswcs.py b/asdf_astropy/converters/fits/tests/test_fitswcs.py index 70c183ef..af9fddf5 100644 --- a/asdf_astropy/converters/fits/tests/test_fitswcs.py +++ b/asdf_astropy/converters/fits/tests/test_fitswcs.py @@ -1,25 +1,62 @@ -import warnings - import asdf +import numpy as np import pytest -from astropy.io import fits -from astropy.utils.data import download_file -from astropy.wcs import WCS, FITSFixedWarning +from astropy import wcs + + +def create_sip_wcs(): + rng = np.random.default_rng() + twcs = wcs.WCS(naxis=2) + twcs.wcs.crval = [251.29, 57.58] + twcs.wcs.cdelt = [1, 1] + twcs.wcs.crpix = [507, 507] + twcs.wcs.pc = np.array([[7.7e-6, 3.3e-5], [3.7e-5, -6.8e-6]]) + twcs._naxis = [1014, 1014] + twcs.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 + twcs.sip = wcs.Sip(a, b, None, None, twcs.wcs.crpix) + twcs.wcs.set() -def create_wcs(): - urls = [ - "http://data.astropy.org/tutorials/FITS-cubes/reduced_TAN_C14.fits", - "http://data.astropy.org/tutorials/FITS-images/HorseHead.fits", - ] + # Creates a WCS object with distortion lookup tables + img_world_wcs = 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 - with warnings.catch_warnings(): - warnings.simplefilter("ignore", FITSFixedWarning) - return [WCS(fits.open(download_file(url, cache=True))[0].header) for url in urls] + # 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 = wcs.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 = wcs.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 (twcs, img_world_wcs) + + +@pytest.mark.parametrize("wcs", create_sip_wcs()) @pytest.mark.filterwarnings("ignore::astropy.wcs.wcs.FITSFixedWarning") -@pytest.mark.parametrize("wcs", create_wcs()) +@pytest.mark.filterwarnings( + "ignore:Some non-standard WCS keywords were excluded:astropy.utils.exceptions.AstropyWarning", +) def test_wcs_serialization(wcs, tmp_path): file_path = tmp_path / "test_wcs.asdf" with asdf.AsdfFile() as af: @@ -29,3 +66,15 @@ def test_wcs_serialization(wcs, tmp_path): with asdf.open(file_path) as af: loaded_wcs = af["wcs"] assert wcs.to_header() == loaded_wcs.to_header() + + +@pytest.mark.xfail(reason="Tabular WCS serialization is currently not supported") +def test_tabular_wcs_serialization(tab_wcs_2di, tmp_path): + file_path = tmp_path / "test_wcs.asdf" + with asdf.AsdfFile() as af: + af["wcs"] = tab_wcs_2di + af.write_to(file_path) + + with asdf.open(file_path) as af: + loaded_wcs = af["wcs"] + assert tab_wcs_2di.to_header() == loaded_wcs.to_header() diff --git a/asdf_astropy/converters/slicedwcs/test/test_slicedwcs.py b/asdf_astropy/converters/slicedwcs/test/test_slicedwcs.py index 5b09c375..8d661377 100644 --- a/asdf_astropy/converters/slicedwcs/test/test_slicedwcs.py +++ b/asdf_astropy/converters/slicedwcs/test/test_slicedwcs.py @@ -13,6 +13,7 @@ def create_wcs(): 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]) diff --git a/asdf_astropy/resources/schemas/fits/fitswcs-1.0.0.yaml b/asdf_astropy/resources/schemas/fits/fitswcs-1.0.0.yaml index 8147aea0..220ac616 100644 --- a/asdf_astropy/resources/schemas/fits/fitswcs-1.0.0.yaml +++ b/asdf_astropy/resources/schemas/fits/fitswcs-1.0.0.yaml @@ -3,11 +3,11 @@ $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 +title: Represents the fits object -description: - 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 diff --git a/asdf_astropy/resources/schemas/slicedwcs/slicedwcs-1.0.0.yaml b/asdf_astropy/resources/schemas/slicedwcs/slicedwcs-1.0.0.yaml index 4215b6a5..942f6343 100644 --- a/asdf_astropy/resources/schemas/slicedwcs/slicedwcs-1.0.0.yaml +++ b/asdf_astropy/resources/schemas/slicedwcs/slicedwcs-1.0.0.yaml @@ -3,11 +3,13 @@ $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 +title: Represents the SlicedLowLevelWCS object -description: - 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 From c0872c2d116a2aff26ccd2e91c46317af63a3568 Mon Sep 17 00:00:00 2001 From: ViciousEagle03 Date: Wed, 28 Aug 2024 21:22:00 +0530 Subject: [PATCH 12/30] Add to a manifest and register it as a new extension --- .../resources/manifests/astropy-1.1.0.yaml | 17 ---------------- .../resources/manifests/astropy-1.3.0.yaml | 20 +++++++++++++++---- 2 files changed, 16 insertions(+), 21 deletions(-) diff --git a/asdf_astropy/resources/manifests/astropy-1.1.0.yaml b/asdf_astropy/resources/manifests/astropy-1.1.0.yaml index 49e26253..9e8094a9 100644 --- a/asdf_astropy/resources/manifests/astropy-1.1.0.yaml +++ b/asdf_astropy/resources/manifests/astropy-1.1.0.yaml @@ -54,20 +54,3 @@ tags: title: NdarrayMixin column. description: |- Represents an astropy.table.NdarrayMixin instance. -- tag_uri: tag:astropy.org:astropy/slicedwcs/slicedwcs-1.0.0 - schema_uri: http://astropy.org/schemas/astropy/slicedwcs/slicedwcs-1.0.0 - title: Represents an instance of SlicedLowLevelWCS - 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. -- tag_uri: tag:astropy.org:astropy/fits/fitswcs-1.0.0 - schema_uri: http://astropy.org/schemas/astropy/fits/fitswcs-1.0.0 - title: FITS WCS (World Coordinate System) Converter - 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. diff --git a/asdf_astropy/resources/manifests/astropy-1.3.0.yaml b/asdf_astropy/resources/manifests/astropy-1.3.0.yaml index 53c3720a..231d84b0 100644 --- a/asdf_astropy/resources/manifests/astropy-1.3.0.yaml +++ b/asdf_astropy/resources/manifests/astropy-1.3.0.yaml @@ -54,8 +54,20 @@ tags: title: NdarrayMixin column. description: |- Represents an astropy.table.NdarrayMixin instance. -- tag_uri: tag:astropy.org:astropy/nddata/uncertainty-1.0.0 - schema_uri: http://astropy.org/schemas/astropy/nddata/uncertainty-1.0.0 - title: Represent an astropy.nddata uncertainty +- tag_uri: tag:astropy.org:astropy/slicedwcs/slicedwcs-1.0.0 + schema_uri: http://astropy.org/schemas/astropy/slicedwcs/slicedwcs-1.0.0 + title: Represents an instance of SlicedLowLevelWCS description: |- - Represents an instance of an astropy.nddata uncertainty + 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. +- tag_uri: tag:astropy.org:astropy/fits/fitswcs-1.0.0 + schema_uri: http://astropy.org/schemas/astropy/fits/fitswcs-1.0.0 + title: FITS WCS (World Coordinate System) Converter + 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. From 83ff33f67c5ecec5258732970947304f9ab80954 Mon Sep 17 00:00:00 2001 From: ViciousEagle03 Date: Wed, 28 Aug 2024 21:25:33 +0530 Subject: [PATCH 13/30] Small change --- asdf_astropy/converters/fits/tests/test_fitswcs.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/asdf_astropy/converters/fits/tests/test_fitswcs.py b/asdf_astropy/converters/fits/tests/test_fitswcs.py index af9fddf5..fb885171 100644 --- a/asdf_astropy/converters/fits/tests/test_fitswcs.py +++ b/asdf_astropy/converters/fits/tests/test_fitswcs.py @@ -4,7 +4,7 @@ from astropy import wcs -def create_sip_wcs(): +def create_sip_distortion_wcs(): rng = np.random.default_rng() twcs = wcs.WCS(naxis=2) twcs.wcs.crval = [251.29, 57.58] @@ -52,7 +52,7 @@ def create_sip_wcs(): return (twcs, img_world_wcs) -@pytest.mark.parametrize("wcs", create_sip_wcs()) +@pytest.mark.parametrize("wcs", create_sip_distortion_wcs()) @pytest.mark.filterwarnings("ignore::astropy.wcs.wcs.FITSFixedWarning") @pytest.mark.filterwarnings( "ignore:Some non-standard WCS keywords were excluded:astropy.utils.exceptions.AstropyWarning", From 706f870d21e2b923aaaf1a829c4af521a930f065 Mon Sep 17 00:00:00 2001 From: ViciousEagle03 Date: Sun, 1 Sep 2024 23:28:36 +0530 Subject: [PATCH 14/30] Correct the test suite of table lookup distortion wcs --- .../converters/fits/tests/test_fitswcs.py | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/asdf_astropy/converters/fits/tests/test_fitswcs.py b/asdf_astropy/converters/fits/tests/test_fitswcs.py index fb885171..19cd1835 100644 --- a/asdf_astropy/converters/fits/tests/test_fitswcs.py +++ b/asdf_astropy/converters/fits/tests/test_fitswcs.py @@ -4,7 +4,7 @@ from astropy import wcs -def create_sip_distortion_wcs(): +def create_sip_distortion_and_tabular_wcs(): rng = np.random.default_rng() twcs = wcs.WCS(naxis=2) twcs.wcs.crval = [251.29, 57.58] @@ -52,7 +52,7 @@ def create_sip_distortion_wcs(): return (twcs, img_world_wcs) -@pytest.mark.parametrize("wcs", create_sip_distortion_wcs()) +@pytest.mark.parametrize("wcs", create_sip_distortion_and_tabular_wcs()) @pytest.mark.filterwarnings("ignore::astropy.wcs.wcs.FITSFixedWarning") @pytest.mark.filterwarnings( "ignore:Some non-standard WCS keywords were excluded:astropy.utils.exceptions.AstropyWarning", @@ -66,15 +66,3 @@ def test_wcs_serialization(wcs, tmp_path): with asdf.open(file_path) as af: loaded_wcs = af["wcs"] assert wcs.to_header() == loaded_wcs.to_header() - - -@pytest.mark.xfail(reason="Tabular WCS serialization is currently not supported") -def test_tabular_wcs_serialization(tab_wcs_2di, tmp_path): - file_path = tmp_path / "test_wcs.asdf" - with asdf.AsdfFile() as af: - af["wcs"] = tab_wcs_2di - af.write_to(file_path) - - with asdf.open(file_path) as af: - loaded_wcs = af["wcs"] - assert tab_wcs_2di.to_header() == loaded_wcs.to_header() From aab79f9e1131ee94034845a8dff8b71e322c9264 Mon Sep 17 00:00:00 2001 From: ViciousEagle03 Date: Thu, 19 Sep 2024 03:22:48 +0530 Subject: [PATCH 15/30] Apply code suggestions --- .../converters/fits/tests/test_fitswcs.py | 34 ++++++++++++++++--- .../converters/slicedwcs/slicedwcs.py | 6 ++-- .../slicedwcs/test/test_slicedwcs.py | 4 ++- .../schemas/slicedwcs/slicedwcs-1.0.0.yaml | 14 ++++++++ 4 files changed, 49 insertions(+), 9 deletions(-) diff --git a/asdf_astropy/converters/fits/tests/test_fitswcs.py b/asdf_astropy/converters/fits/tests/test_fitswcs.py index 19cd1835..d36f3552 100644 --- a/asdf_astropy/converters/fits/tests/test_fitswcs.py +++ b/asdf_astropy/converters/fits/tests/test_fitswcs.py @@ -3,9 +3,11 @@ import pytest from astropy import wcs +from asdf_astropy.testing.helpers import assert_hdu_list_equal -def create_sip_distortion_and_tabular_wcs(): - rng = np.random.default_rng() + +def create_sip_distortion_wcs(): + rng = np.random.default_rng(42) twcs = wcs.WCS(naxis=2) twcs.wcs.crval = [251.29, 57.58] twcs.wcs.cdelt = [1, 1] @@ -22,6 +24,27 @@ def create_sip_distortion_and_tabular_wcs(): twcs.sip = wcs.Sip(a, b, None, None, twcs.wcs.crpix) twcs.wcs.set() + return (twcs,) + + +@pytest.mark.xfail(reason="Fails due to normalization differences when using wcs.to_fits().") +@pytest.mark.parametrize("wcs", create_sip_distortion_wcs()) +@pytest.mark.filterwarnings("ignore::astropy.wcs.wcs.FITSFixedWarning") +@pytest.mark.filterwarnings( + "ignore:Some non-standard WCS keywords were excluded:astropy.utils.exceptions.AstropyWarning", +) +def test_sip_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_hdu_list_equal(wcs.to_fits(relax=True), loaded_wcs.to_fits(relax=True)) + + +def create_tabular_wcs(): # Creates a WCS object with distortion lookup tables img_world_wcs = wcs.WCS(naxis=2) img_world_wcs.wcs.crpix = 1, 1 @@ -49,15 +72,15 @@ def create_sip_distortion_and_tabular_wcs(): img_world_wcs.cpdis1 = map_x img_world_wcs.cpdis2 = map_y - return (twcs, img_world_wcs) + return (img_world_wcs,) -@pytest.mark.parametrize("wcs", create_sip_distortion_and_tabular_wcs()) +@pytest.mark.parametrize("wcs", create_tabular_wcs()) @pytest.mark.filterwarnings("ignore::astropy.wcs.wcs.FITSFixedWarning") @pytest.mark.filterwarnings( "ignore:Some non-standard WCS keywords were excluded:astropy.utils.exceptions.AstropyWarning", ) -def test_wcs_serialization(wcs, tmp_path): +def test_twcs_serialization(wcs, tmp_path): file_path = tmp_path / "test_wcs.asdf" with asdf.AsdfFile() as af: af["wcs"] = wcs @@ -66,3 +89,4 @@ def test_wcs_serialization(wcs, tmp_path): with asdf.open(file_path) as af: loaded_wcs = af["wcs"] assert wcs.to_header() == loaded_wcs.to_header() + assert_hdu_list_equal(wcs.to_fits(), loaded_wcs.to_fits()) diff --git a/asdf_astropy/converters/slicedwcs/slicedwcs.py b/asdf_astropy/converters/slicedwcs/slicedwcs.py index 404a6d3d..d45a1d77 100644 --- a/asdf_astropy/converters/slicedwcs/slicedwcs.py +++ b/asdf_astropy/converters/slicedwcs/slicedwcs.py @@ -24,9 +24,9 @@ def to_yaml_tree(self, sl, tag, ctx): if isinstance(s, slice): 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, + "start": s.start, + "stop": s.stop, + "step": s.step, }, ) else: diff --git a/asdf_astropy/converters/slicedwcs/test/test_slicedwcs.py b/asdf_astropy/converters/slicedwcs/test/test_slicedwcs.py index 8d661377..6d3f1cef 100644 --- a/asdf_astropy/converters/slicedwcs/test/test_slicedwcs.py +++ b/asdf_astropy/converters/slicedwcs/test/test_slicedwcs.py @@ -4,6 +4,8 @@ from astropy.wcs import WCS from astropy.wcs.wcsapi.wrappers.sliced_wcs import SlicedLowLevelWCS +from asdf_astropy.testing.helpers import assert_hdu_list_equal + def create_wcs(): wcs = WCS(naxis=4) @@ -32,5 +34,5 @@ def test_sliced_wcs_serialization(sl_wcs, tmp_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() + assert_hdu_list_equal(sl_wcs._wcs.to_fits(), loaded_sl_wcs._wcs.to_fits()) assert sl_wcs._slices_array == loaded_sl_wcs._slices_array diff --git a/asdf_astropy/resources/schemas/slicedwcs/slicedwcs-1.0.0.yaml b/asdf_astropy/resources/schemas/slicedwcs/slicedwcs-1.0.0.yaml index 942f6343..f4f08ff4 100644 --- a/asdf_astropy/resources/schemas/slicedwcs/slicedwcs-1.0.0.yaml +++ b/asdf_astropy/resources/schemas/slicedwcs/slicedwcs-1.0.0.yaml @@ -22,5 +22,19 @@ allOf: - oneOf: - type: integer - type: object + properties: + start: + anyOf: + - type: integer + - type: "null" + stop: + anyOf: + - type: integer + - type: "null" + step: + anyOf: + - type: integer + - type: "null" + required: ["wcs", "slices_array"] From 160532f2c563036268e12be72272f29c003837f4 Mon Sep 17 00:00:00 2001 From: ViciousEagle03 Date: Thu, 19 Sep 2024 03:55:43 +0530 Subject: [PATCH 16/30] correct the standard version --- asdf_astropy/resources/manifests/astropy-1.3.0.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/asdf_astropy/resources/manifests/astropy-1.3.0.yaml b/asdf_astropy/resources/manifests/astropy-1.3.0.yaml index 231d84b0..764726c4 100644 --- a/asdf_astropy/resources/manifests/astropy-1.3.0.yaml +++ b/asdf_astropy/resources/manifests/astropy-1.3.0.yaml @@ -6,7 +6,7 @@ description: |- model classes, which are handled by an implementation of the ASDF transform extension. asdf_standard_requirement: - gte: 1.6.0 + gte: 1.5.0 tags: - tag_uri: tag:astropy.org:astropy/time/timedelta-1.1.0 schema_uri: http://astropy.org/schemas/astropy/time/timedelta-1.1.0 From 5213d4ef394b3bfb2981513b9d3b4a532de0b891 Mon Sep 17 00:00:00 2001 From: ViciousEagle03 Date: Fri, 20 Sep 2024 02:10:02 +0530 Subject: [PATCH 17/30] move fitswcs and sliced_wcs converters to wcs submodule --- asdf_astropy/converters/__init__.py | 2 ++ asdf_astropy/converters/fits/__init__.py | 2 -- asdf_astropy/converters/{slicedwcs => wcs}/__init__.py | 3 ++- asdf_astropy/converters/{fits => wcs}/fitswcs.py | 0 asdf_astropy/converters/{slicedwcs => wcs}/slicedwcs.py | 0 .../converters/{slicedwcs/test => wcs/tests}/__init__.py | 0 asdf_astropy/converters/{fits => wcs}/tests/test_fitswcs.py | 3 --- .../converters/{slicedwcs/test => wcs/tests}/test_slicedwcs.py | 0 asdf_astropy/extensions.py | 2 ++ 9 files changed, 6 insertions(+), 6 deletions(-) rename asdf_astropy/converters/{slicedwcs => wcs}/__init__.py (56%) rename asdf_astropy/converters/{fits => wcs}/fitswcs.py (100%) rename asdf_astropy/converters/{slicedwcs => wcs}/slicedwcs.py (100%) rename asdf_astropy/converters/{slicedwcs/test => wcs/tests}/__init__.py (100%) rename asdf_astropy/converters/{fits => wcs}/tests/test_fitswcs.py (95%) rename asdf_astropy/converters/{slicedwcs/test => wcs/tests}/test_slicedwcs.py (100%) diff --git a/asdf_astropy/converters/__init__.py b/asdf_astropy/converters/__init__.py index 5ecf3773..003eda5d 100644 --- a/asdf_astropy/converters/__init__.py +++ b/asdf_astropy/converters/__init__.py @@ -16,6 +16,7 @@ "AsdfFitsConverter", "AstropyFitsConverter", "FitsWCSConverter", + "SlicedWCSConverter", "ColumnConverter", "AstropyTableConverter", "AsdfTableConverter", @@ -77,3 +78,4 @@ UnitsMappingConverter, ) from .unit import EquivalencyConverter, MagUnitConverter, QuantityConverter, UnitConverter +from .wcs import FitsWCSConverter, SlicedWCSConverter diff --git a/asdf_astropy/converters/fits/__init__.py b/asdf_astropy/converters/fits/__init__.py index 4f915659..4647e59c 100644 --- a/asdf_astropy/converters/fits/__init__.py +++ b/asdf_astropy/converters/fits/__init__.py @@ -2,8 +2,6 @@ "FitsConverter", "AsdfFitsConverter", "AstropyFitsConverter", - "FitsWCSConverter", ] from .fits import AsdfFitsConverter, AstropyFitsConverter, FitsConverter -from .fitswcs import FitsWCSConverter diff --git a/asdf_astropy/converters/slicedwcs/__init__.py b/asdf_astropy/converters/wcs/__init__.py similarity index 56% rename from asdf_astropy/converters/slicedwcs/__init__.py rename to asdf_astropy/converters/wcs/__init__.py index 8473b67e..e8034fcc 100644 --- a/asdf_astropy/converters/slicedwcs/__init__.py +++ b/asdf_astropy/converters/wcs/__init__.py @@ -1,5 +1,6 @@ __all__ = [ + "FitsWCSConverter", "SlicedWCSConverter", ] - +from .fitswcs import FitsWCSConverter from .slicedwcs import SlicedWCSConverter diff --git a/asdf_astropy/converters/fits/fitswcs.py b/asdf_astropy/converters/wcs/fitswcs.py similarity index 100% rename from asdf_astropy/converters/fits/fitswcs.py rename to asdf_astropy/converters/wcs/fitswcs.py diff --git a/asdf_astropy/converters/slicedwcs/slicedwcs.py b/asdf_astropy/converters/wcs/slicedwcs.py similarity index 100% rename from asdf_astropy/converters/slicedwcs/slicedwcs.py rename to asdf_astropy/converters/wcs/slicedwcs.py diff --git a/asdf_astropy/converters/slicedwcs/test/__init__.py b/asdf_astropy/converters/wcs/tests/__init__.py similarity index 100% rename from asdf_astropy/converters/slicedwcs/test/__init__.py rename to asdf_astropy/converters/wcs/tests/__init__.py diff --git a/asdf_astropy/converters/fits/tests/test_fitswcs.py b/asdf_astropy/converters/wcs/tests/test_fitswcs.py similarity index 95% rename from asdf_astropy/converters/fits/tests/test_fitswcs.py rename to asdf_astropy/converters/wcs/tests/test_fitswcs.py index d36f3552..52d9c2e4 100644 --- a/asdf_astropy/converters/fits/tests/test_fitswcs.py +++ b/asdf_astropy/converters/wcs/tests/test_fitswcs.py @@ -77,9 +77,6 @@ def create_tabular_wcs(): @pytest.mark.parametrize("wcs", create_tabular_wcs()) @pytest.mark.filterwarnings("ignore::astropy.wcs.wcs.FITSFixedWarning") -@pytest.mark.filterwarnings( - "ignore:Some non-standard WCS keywords were excluded:astropy.utils.exceptions.AstropyWarning", -) def test_twcs_serialization(wcs, tmp_path): file_path = tmp_path / "test_wcs.asdf" with asdf.AsdfFile() as af: diff --git a/asdf_astropy/converters/slicedwcs/test/test_slicedwcs.py b/asdf_astropy/converters/wcs/tests/test_slicedwcs.py similarity index 100% rename from asdf_astropy/converters/slicedwcs/test/test_slicedwcs.py rename to asdf_astropy/converters/wcs/tests/test_slicedwcs.py diff --git a/asdf_astropy/extensions.py b/asdf_astropy/extensions.py index f528e34d..ca426cc8 100644 --- a/asdf_astropy/extensions.py +++ b/asdf_astropy/extensions.py @@ -33,6 +33,8 @@ from .converters.unit.magunit import MagUnitConverter from .converters.unit.quantity import QuantityConverter from .converters.unit.unit import UnitConverter +from .converters.wcs.fitswcs import FitsWCSConverter +from .converters.wcs.slicedwcs import SlicedWCSConverter __all__ = [ "TRANSFORM_CONVERTERS", From c324e00ecb992d2fc38c1d3eb9f197713fd5a3da Mon Sep 17 00:00:00 2001 From: Brett Date: Tue, 5 Nov 2024 10:39:22 -0500 Subject: [PATCH 18/30] clean up warnings, fix sip --- asdf_astropy/converters/wcs/fitswcs.py | 28 ++++++++++++++----- .../converters/wcs/tests/test_fitswcs.py | 9 +----- .../converters/wcs/tests/test_slicedwcs.py | 1 - 3 files changed, 22 insertions(+), 16 deletions(-) diff --git a/asdf_astropy/converters/wcs/fitswcs.py b/asdf_astropy/converters/wcs/fitswcs.py index aecec738..6ac8df34 100644 --- a/asdf_astropy/converters/wcs/fitswcs.py +++ b/asdf_astropy/converters/wcs/fitswcs.py @@ -1,5 +1,7 @@ from asdf.extension import Converter +_WCS_ATTRS = ("naxis", "colsel", "keysel", "key", "pixel_shape", "pixel_bounds") + class FitsWCSConverter(Converter): tags = ("tag:astropy.org:astropy/fits/fitswcs-*",) @@ -8,12 +10,24 @@ class FitsWCSConverter(Converter): def from_yaml_tree(self, node, tag, ctx): from astropy.wcs import WCS - return WCS(node["hdu"][0].header, fobj=node["hdu"]) + if naxis := node["attrs"].pop("naxis"): + node["hdu"][0].header["naxis"] = naxis + + pixel_shape = node["attrs"].pop("pixel_shape") + pixel_bounds = node["attrs"].pop("pixel_bounds") + + wcs = WCS(node["hdu"][0].header, fobj=node["hdu"], **node["attrs"]) - def to_yaml_tree(self, wcs, tag, ctx): - node = {} if wcs.sip is not None: - node["hdu"] = wcs.to_fits(relax=True) - else: - node["hdu"] = wcs.to_fits() - return node + # work around a bug in astropy where sip headers lose precision + wcs.sip = wcs._read_sip_kw(node["hdu"][0].header, node["attrs"].get("key", " ")) + wcs.wcs.set() + + wcs.pixel_shape = pixel_shape + 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 {"hdu": hdulist, "attrs": attrs} diff --git a/asdf_astropy/converters/wcs/tests/test_fitswcs.py b/asdf_astropy/converters/wcs/tests/test_fitswcs.py index 52d9c2e4..f0a3c887 100644 --- a/asdf_astropy/converters/wcs/tests/test_fitswcs.py +++ b/asdf_astropy/converters/wcs/tests/test_fitswcs.py @@ -27,12 +27,7 @@ def create_sip_distortion_wcs(): return (twcs,) -@pytest.mark.xfail(reason="Fails due to normalization differences when using wcs.to_fits().") @pytest.mark.parametrize("wcs", create_sip_distortion_wcs()) -@pytest.mark.filterwarnings("ignore::astropy.wcs.wcs.FITSFixedWarning") -@pytest.mark.filterwarnings( - "ignore:Some non-standard WCS keywords were excluded:astropy.utils.exceptions.AstropyWarning", -) def test_sip_wcs_serialization(wcs, tmp_path): file_path = tmp_path / "test_wcs.asdf" with asdf.AsdfFile() as af: @@ -76,7 +71,6 @@ def create_tabular_wcs(): @pytest.mark.parametrize("wcs", create_tabular_wcs()) -@pytest.mark.filterwarnings("ignore::astropy.wcs.wcs.FITSFixedWarning") def test_twcs_serialization(wcs, tmp_path): file_path = tmp_path / "test_wcs.asdf" with asdf.AsdfFile() as af: @@ -85,5 +79,4 @@ def test_twcs_serialization(wcs, tmp_path): with asdf.open(file_path) as af: loaded_wcs = af["wcs"] - assert wcs.to_header() == loaded_wcs.to_header() - assert_hdu_list_equal(wcs.to_fits(), loaded_wcs.to_fits()) + assert_hdu_list_equal(wcs.to_fits(relax=True), loaded_wcs.to_fits(relax=True)) diff --git a/asdf_astropy/converters/wcs/tests/test_slicedwcs.py b/asdf_astropy/converters/wcs/tests/test_slicedwcs.py index 6d3f1cef..52d0af20 100644 --- a/asdf_astropy/converters/wcs/tests/test_slicedwcs.py +++ b/asdf_astropy/converters/wcs/tests/test_slicedwcs.py @@ -24,7 +24,6 @@ def create_wcs(): return [wcs0, wcs1, wcs2, wcs_ellipsis, wcs3] -@pytest.mark.filterwarnings("ignore::astropy.wcs.wcs.FITSFixedWarning") @pytest.mark.parametrize("sl_wcs", create_wcs()) def test_sliced_wcs_serialization(sl_wcs, tmp_path): file_path = tmp_path / "test_slicedwcs.asdf" From cb69b81965547b1149cf1cfcd64b5c0d1fa8e3d6 Mon Sep 17 00:00:00 2001 From: Brett Date: Tue, 5 Nov 2024 13:14:25 -0500 Subject: [PATCH 19/30] rename hdu to hdulist --- asdf_astropy/converters/wcs/fitswcs.py | 18 +++++++++++------- .../resources/schemas/fits/fitswcs-1.0.0.yaml | 6 ++++-- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/asdf_astropy/converters/wcs/fitswcs.py b/asdf_astropy/converters/wcs/fitswcs.py index 6ac8df34..cae19c4e 100644 --- a/asdf_astropy/converters/wcs/fitswcs.py +++ b/asdf_astropy/converters/wcs/fitswcs.py @@ -10,17 +10,21 @@ class FitsWCSConverter(Converter): def from_yaml_tree(self, node, tag, ctx): from astropy.wcs import WCS - if naxis := node["attrs"].pop("naxis"): - node["hdu"][0].header["naxis"] = naxis + hdulist = node["hdulist"] + attrs = node["attrs"] - pixel_shape = node["attrs"].pop("pixel_shape") - pixel_bounds = node["attrs"].pop("pixel_bounds") + if naxis := attrs.pop("naxis"): + hdulist[0].header["naxis"] = naxis - wcs = WCS(node["hdu"][0].header, fobj=node["hdu"], **node["attrs"]) + pixel_shape = attrs.pop("pixel_shape") + 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 - wcs.sip = wcs._read_sip_kw(node["hdu"][0].header, node["attrs"].get("key", " ")) + # 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_shape = pixel_shape @@ -30,4 +34,4 @@ def from_yaml_tree(self, node, tag, ctx): 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 {"hdu": hdulist, "attrs": attrs} + return {"hdulist": hdulist, "attrs": attrs} diff --git a/asdf_astropy/resources/schemas/fits/fitswcs-1.0.0.yaml b/asdf_astropy/resources/schemas/fits/fitswcs-1.0.0.yaml index 220ac616..1be9f56b 100644 --- a/asdf_astropy/resources/schemas/fits/fitswcs-1.0.0.yaml +++ b/asdf_astropy/resources/schemas/fits/fitswcs-1.0.0.yaml @@ -12,7 +12,9 @@ description: Represents the FITS WCS object, the HDUlist of the FITS header is p allOf: - type: object properties: - hdu: + hdulist: tag: "tag:astropy.org:astropy/fits/fits-*" + attrs: + type: object - required: ["hdu"] + required: ["hdulist", "attrs"] From dc200c774f49b6ccc064d11ed4211d2ab1b1c3de Mon Sep 17 00:00:00 2001 From: Brett Date: Tue, 5 Nov 2024 14:04:06 -0500 Subject: [PATCH 20/30] reorganize manifests --- .../resources/manifests/astropy-1.2.0.yaml | 17 +++++++++++++++++ .../resources/manifests/astropy-1.3.0.yaml | 2 +- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/asdf_astropy/resources/manifests/astropy-1.2.0.yaml b/asdf_astropy/resources/manifests/astropy-1.2.0.yaml index 80f63bb1..e09469f4 100644 --- a/asdf_astropy/resources/manifests/astropy-1.2.0.yaml +++ b/asdf_astropy/resources/manifests/astropy-1.2.0.yaml @@ -59,3 +59,20 @@ tags: title: Represent an astropy.nddata uncertainty description: |- Represents an instance of an astropy.nddata uncertainty +- tag_uri: tag:astropy.org:astropy/slicedwcs/slicedwcs-1.0.0 + schema_uri: http://astropy.org/schemas/astropy/slicedwcs/slicedwcs-1.0.0 + title: Represents an instance of SlicedLowLevelWCS + 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. +- tag_uri: tag:astropy.org:astropy/fits/fitswcs-1.0.0 + schema_uri: http://astropy.org/schemas/astropy/fits/fitswcs-1.0.0 + title: FITS WCS (World Coordinate System) Converter + 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. diff --git a/asdf_astropy/resources/manifests/astropy-1.3.0.yaml b/asdf_astropy/resources/manifests/astropy-1.3.0.yaml index 764726c4..231d84b0 100644 --- a/asdf_astropy/resources/manifests/astropy-1.3.0.yaml +++ b/asdf_astropy/resources/manifests/astropy-1.3.0.yaml @@ -6,7 +6,7 @@ description: |- model classes, which are handled by an implementation of the ASDF transform extension. asdf_standard_requirement: - gte: 1.5.0 + gte: 1.6.0 tags: - tag_uri: tag:astropy.org:astropy/time/timedelta-1.1.0 schema_uri: http://astropy.org/schemas/astropy/time/timedelta-1.1.0 From c9d441e47d9acb1911fa4c58f13aeb54b345d01d Mon Sep 17 00:00:00 2001 From: Brett Date: Tue, 5 Nov 2024 14:15:12 -0500 Subject: [PATCH 21/30] update PR number --- CHANGES.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index ba11c5a1..a380fbb0 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -12,7 +12,7 @@ - Add support for astropy.nddata.uncertainty classes [#239] -- Add support for astropy.wcs.WCS and astropy.wcs.wcsapi.SlicedLowLevelWCS [#235] +- Add support for astropy.wcs.WCS and astropy.wcs.wcsapi.SlicedLowLevelWCS [#246] 0.6.1 (2024-04-05) ------------------ From b1391084b5fa29f72a578b5527c24536b00d5c03 Mon Sep 17 00:00:00 2001 From: Brett Date: Tue, 5 Nov 2024 14:20:11 -0500 Subject: [PATCH 22/30] slicedwcs converter cleanup --- asdf_astropy/converters/wcs/slicedwcs.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/asdf_astropy/converters/wcs/slicedwcs.py b/asdf_astropy/converters/wcs/slicedwcs.py index d45a1d77..577622c3 100644 --- a/asdf_astropy/converters/wcs/slicedwcs.py +++ b/asdf_astropy/converters/wcs/slicedwcs.py @@ -9,20 +9,17 @@ 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"] = [] + slices_array = [] for s in sl._slices_array: if isinstance(s, slice): - node["slices_array"].append( + slices_array.append( { "start": s.start, "stop": s.stop, @@ -30,5 +27,8 @@ def to_yaml_tree(self, sl, tag, ctx): }, ) else: - node["slices_array"].append(s) - return node + slices_array.append(s) + return { + "wcs": sl._wcs, + "slices_array": slices_array, + } From 4bd4c2c0a7b3726d35768c70a511f7c34cb440ba Mon Sep 17 00:00:00 2001 From: Brett Date: Tue, 5 Nov 2024 14:34:11 -0500 Subject: [PATCH 23/30] rename fitswcs to wcs and put in wcs with slicedwcs --- asdf_astropy/converters/__init__.py | 4 ++-- asdf_astropy/converters/wcs/__init__.py | 4 ++-- asdf_astropy/converters/wcs/slicedwcs.py | 2 +- asdf_astropy/converters/wcs/{fitswcs.py => wcs.py} | 4 ++-- asdf_astropy/extensions.py | 4 ++-- .../resources/manifests/astropy-1.2.0.yaml | 8 ++++---- .../resources/manifests/astropy-1.3.0.yaml | 8 ++++---- .../{slicedwcs => wcs}/slicedwcs-1.0.0.yaml | 4 ++-- .../fitswcs-1.0.0.yaml => wcs/wcs-1.0.0.yaml} | 2 +- docs/asdf-astropy/schemas.rst | 14 ++++++++++++++ 10 files changed, 34 insertions(+), 20 deletions(-) rename asdf_astropy/converters/wcs/{fitswcs.py => wcs.py} (92%) rename asdf_astropy/resources/schemas/{slicedwcs => wcs}/slicedwcs-1.0.0.yaml (89%) rename asdf_astropy/resources/schemas/{fits/fitswcs-1.0.0.yaml => wcs/wcs-1.0.0.yaml} (88%) diff --git a/asdf_astropy/converters/__init__.py b/asdf_astropy/converters/__init__.py index 003eda5d..21c5d797 100644 --- a/asdf_astropy/converters/__init__.py +++ b/asdf_astropy/converters/__init__.py @@ -15,7 +15,7 @@ "FitsConverter", "AsdfFitsConverter", "AstropyFitsConverter", - "FitsWCSConverter", + "WCSConverter", "SlicedWCSConverter", "ColumnConverter", "AstropyTableConverter", @@ -78,4 +78,4 @@ UnitsMappingConverter, ) from .unit import EquivalencyConverter, MagUnitConverter, QuantityConverter, UnitConverter -from .wcs import FitsWCSConverter, SlicedWCSConverter +from .wcs import SlicedWCSConverter, WCSConverter diff --git a/asdf_astropy/converters/wcs/__init__.py b/asdf_astropy/converters/wcs/__init__.py index e8034fcc..73e65a8f 100644 --- a/asdf_astropy/converters/wcs/__init__.py +++ b/asdf_astropy/converters/wcs/__init__.py @@ -1,6 +1,6 @@ __all__ = [ - "FitsWCSConverter", + "WCSConverter", "SlicedWCSConverter", ] -from .fitswcs import FitsWCSConverter from .slicedwcs import SlicedWCSConverter +from .wcs import WCSConverter diff --git a/asdf_astropy/converters/wcs/slicedwcs.py b/asdf_astropy/converters/wcs/slicedwcs.py index 577622c3..34eea6d6 100644 --- a/asdf_astropy/converters/wcs/slicedwcs.py +++ b/asdf_astropy/converters/wcs/slicedwcs.py @@ -2,7 +2,7 @@ class SlicedWCSConverter(Converter): - tags = ("tag:astropy.org:astropy/slicedwcs/slicedwcs-*",) + tags = ("tag:astropy.org:astropy/wcs/slicedwcs-*",) types = ("astropy.wcs.wcsapi.wrappers.sliced_wcs.SlicedLowLevelWCS",) def from_yaml_tree(self, node, tag, ctx): diff --git a/asdf_astropy/converters/wcs/fitswcs.py b/asdf_astropy/converters/wcs/wcs.py similarity index 92% rename from asdf_astropy/converters/wcs/fitswcs.py rename to asdf_astropy/converters/wcs/wcs.py index cae19c4e..b3a46ac7 100644 --- a/asdf_astropy/converters/wcs/fitswcs.py +++ b/asdf_astropy/converters/wcs/wcs.py @@ -3,8 +3,8 @@ _WCS_ATTRS = ("naxis", "colsel", "keysel", "key", "pixel_shape", "pixel_bounds") -class FitsWCSConverter(Converter): - tags = ("tag:astropy.org:astropy/fits/fitswcs-*",) +class WCSConverter(Converter): + tags = ("tag:astropy.org:astropy/wcs/wcs-*",) types = ("astropy.wcs.wcs.WCS",) def from_yaml_tree(self, node, tag, ctx): diff --git a/asdf_astropy/extensions.py b/asdf_astropy/extensions.py index ca426cc8..e84884a4 100644 --- a/asdf_astropy/extensions.py +++ b/asdf_astropy/extensions.py @@ -33,8 +33,8 @@ from .converters.unit.magunit import MagUnitConverter from .converters.unit.quantity import QuantityConverter from .converters.unit.unit import UnitConverter -from .converters.wcs.fitswcs import FitsWCSConverter from .converters.wcs.slicedwcs import SlicedWCSConverter +from .converters.wcs.wcs import WCSConverter __all__ = [ "TRANSFORM_CONVERTERS", @@ -482,7 +482,7 @@ AstropyFitsConverter(), NdarrayMixinConverter(), UncertaintyConverter(), - FitsWCSConverter(), + WCSConverter(), SlicedWCSConverter(), ] diff --git a/asdf_astropy/resources/manifests/astropy-1.2.0.yaml b/asdf_astropy/resources/manifests/astropy-1.2.0.yaml index e09469f4..e6d8839d 100644 --- a/asdf_astropy/resources/manifests/astropy-1.2.0.yaml +++ b/asdf_astropy/resources/manifests/astropy-1.2.0.yaml @@ -59,8 +59,8 @@ tags: title: Represent an astropy.nddata uncertainty description: |- Represents an instance of an astropy.nddata uncertainty -- tag_uri: tag:astropy.org:astropy/slicedwcs/slicedwcs-1.0.0 - schema_uri: http://astropy.org/schemas/astropy/slicedwcs/slicedwcs-1.0.0 +- tag_uri: tag:astropy.org:astropy/wcs/slicedwcs-1.0.0 + schema_uri: http://astropy.org/schemas/astropy/wcs/slicedwcs-1.0.0 title: Represents an instance of SlicedLowLevelWCS description: |- The SlicedLowLevelWCS class is a wrapper class for WCS that applies slices @@ -69,8 +69,8 @@ tags: It manages the slicing and coordinate transformations while preserving the underlying WCS object. -- tag_uri: tag:astropy.org:astropy/fits/fitswcs-1.0.0 - schema_uri: http://astropy.org/schemas/astropy/fits/fitswcs-1.0.0 +- tag_uri: tag:astropy.org:astropy/wcs/wcs-1.0.0 + schema_uri: http://astropy.org/schemas/astropy/wcs/wcs-1.0.0 title: FITS WCS (World Coordinate System) Converter description: |- Represents the FITS WCS object, the HDUlist of the FITS header is preserved diff --git a/asdf_astropy/resources/manifests/astropy-1.3.0.yaml b/asdf_astropy/resources/manifests/astropy-1.3.0.yaml index 231d84b0..964412fe 100644 --- a/asdf_astropy/resources/manifests/astropy-1.3.0.yaml +++ b/asdf_astropy/resources/manifests/astropy-1.3.0.yaml @@ -54,8 +54,8 @@ tags: title: NdarrayMixin column. description: |- Represents an astropy.table.NdarrayMixin instance. -- tag_uri: tag:astropy.org:astropy/slicedwcs/slicedwcs-1.0.0 - schema_uri: http://astropy.org/schemas/astropy/slicedwcs/slicedwcs-1.0.0 +- tag_uri: tag:astropy.org:astropy/wcs/slicedwcs-1.0.0 + schema_uri: http://astropy.org/schemas/astropy/wcs/slicedwcs-1.0.0 title: Represents an instance of SlicedLowLevelWCS description: |- The SlicedLowLevelWCS class is a wrapper class for WCS that applies slices @@ -64,8 +64,8 @@ tags: It manages the slicing and coordinate transformations while preserving the underlying WCS object. -- tag_uri: tag:astropy.org:astropy/fits/fitswcs-1.0.0 - schema_uri: http://astropy.org/schemas/astropy/fits/fitswcs-1.0.0 +- tag_uri: tag:astropy.org:astropy/wcs/wcs-1.0.0 + schema_uri: http://astropy.org/schemas/astropy/wcs/wcs-1.0.0 title: FITS WCS (World Coordinate System) Converter description: |- Represents the FITS WCS object, the HDUlist of the FITS header is preserved diff --git a/asdf_astropy/resources/schemas/slicedwcs/slicedwcs-1.0.0.yaml b/asdf_astropy/resources/schemas/wcs/slicedwcs-1.0.0.yaml similarity index 89% rename from asdf_astropy/resources/schemas/slicedwcs/slicedwcs-1.0.0.yaml rename to asdf_astropy/resources/schemas/wcs/slicedwcs-1.0.0.yaml index f4f08ff4..4f82f8c4 100644 --- a/asdf_astropy/resources/schemas/slicedwcs/slicedwcs-1.0.0.yaml +++ b/asdf_astropy/resources/schemas/wcs/slicedwcs-1.0.0.yaml @@ -1,7 +1,7 @@ %YAML 1.1 --- $schema: "http://stsci.edu/schemas/yaml-schema/draft-01" -id: "http://astropy.org/schemas/astropy/slicedwcs/slicedwcs-1.0.0" +id: "http://astropy.org/schemas/astropy/wcs/slicedwcs-1.0.0" title: Represents the SlicedLowLevelWCS object @@ -15,7 +15,7 @@ allOf: - type: object properties: wcs: - tag: "tag:astropy.org:astropy/fits/fitswcs-1*" + tag: "tag:astropy.org:astropy/wcs/wcs-1*" slices_array: type: array items: diff --git a/asdf_astropy/resources/schemas/fits/fitswcs-1.0.0.yaml b/asdf_astropy/resources/schemas/wcs/wcs-1.0.0.yaml similarity index 88% rename from asdf_astropy/resources/schemas/fits/fitswcs-1.0.0.yaml rename to asdf_astropy/resources/schemas/wcs/wcs-1.0.0.yaml index 1be9f56b..bfec3db9 100644 --- a/asdf_astropy/resources/schemas/fits/fitswcs-1.0.0.yaml +++ b/asdf_astropy/resources/schemas/wcs/wcs-1.0.0.yaml @@ -1,7 +1,7 @@ %YAML 1.1 --- $schema: "http://stsci.edu/schemas/yaml-schema/draft-01" -id: "http://astropy.org/schemas/astropy/fits/fitswcs-1.0.0" +id: "http://astropy.org/schemas/astropy/wcs/wcs-1.0.0" title: Represents the fits object diff --git a/docs/asdf-astropy/schemas.rst b/docs/asdf-astropy/schemas.rst index 30d5ea4c..2f670a9d 100644 --- a/docs/asdf-astropy/schemas.rst +++ b/docs/asdf-astropy/schemas.rst @@ -81,3 +81,17 @@ units/equivalency-1.0.0 .. asdf-autoschemas:: units/equivalency-1.0.0 + +WCS +--- + +The following schemas are associated with **astropy** objects from the +:ref:`astropy-wcs` submodule: + +wcs/wcs-1.0.0 +^^^^^^^^^^^^^^^ + +.. asdf-autoschemas:: + + wcs/wcs-1.0.0 + wcs/slicedwcs-1.0.0 From 2f2cb9d24cebc0b9f0c7ca08bcc76cff78e72f6c Mon Sep 17 00:00:00 2001 From: Brett Date: Tue, 5 Nov 2024 14:44:56 -0500 Subject: [PATCH 24/30] minor schema docs --- asdf_astropy/resources/schemas/wcs/slicedwcs-1.0.0.yaml | 3 ++- asdf_astropy/resources/schemas/wcs/wcs-1.0.0.yaml | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/asdf_astropy/resources/schemas/wcs/slicedwcs-1.0.0.yaml b/asdf_astropy/resources/schemas/wcs/slicedwcs-1.0.0.yaml index 4f82f8c4..b3464da2 100644 --- a/asdf_astropy/resources/schemas/wcs/slicedwcs-1.0.0.yaml +++ b/asdf_astropy/resources/schemas/wcs/slicedwcs-1.0.0.yaml @@ -5,7 +5,8 @@ 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 +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 diff --git a/asdf_astropy/resources/schemas/wcs/wcs-1.0.0.yaml b/asdf_astropy/resources/schemas/wcs/wcs-1.0.0.yaml index bfec3db9..3ab83217 100644 --- a/asdf_astropy/resources/schemas/wcs/wcs-1.0.0.yaml +++ b/asdf_astropy/resources/schemas/wcs/wcs-1.0.0.yaml @@ -5,7 +5,8 @@ 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 +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. @@ -13,8 +14,10 @@ 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"] From 2e5973b1e901ce9cb5592aca70b876b2d4b6c1d8 Mon Sep 17 00:00:00 2001 From: Brett Date: Tue, 5 Nov 2024 15:14:48 -0500 Subject: [PATCH 25/30] add assert_wcs_equal, reorg tests --- .../converters/wcs/tests/test_fitswcs.py | 60 +++++++------------ asdf_astropy/testing/helpers.py | 33 ++++++++++ 2 files changed, 54 insertions(+), 39 deletions(-) diff --git a/asdf_astropy/converters/wcs/tests/test_fitswcs.py b/asdf_astropy/converters/wcs/tests/test_fitswcs.py index f0a3c887..c770e3fe 100644 --- a/asdf_astropy/converters/wcs/tests/test_fitswcs.py +++ b/asdf_astropy/converters/wcs/tests/test_fitswcs.py @@ -1,47 +1,34 @@ -import asdf import numpy as np import pytest -from astropy import wcs +from astropy.wcs import WCS, DistortionLookupTable, Sip -from asdf_astropy.testing.helpers import assert_hdu_list_equal +from asdf_astropy.testing.helpers import assert_wcs_roundtrip def create_sip_distortion_wcs(): rng = np.random.default_rng(42) - twcs = wcs.WCS(naxis=2) - twcs.wcs.crval = [251.29, 57.58] - twcs.wcs.cdelt = [1, 1] - twcs.wcs.crpix = [507, 507] - twcs.wcs.pc = np.array([[7.7e-6, 3.3e-5], [3.7e-5, -6.8e-6]]) - twcs._naxis = [1014, 1014] - twcs.wcs.ctype = ["RA---TAN-SIP", "DEC--TAN-SIP"] + 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 - twcs.sip = wcs.Sip(a, b, None, None, twcs.wcs.crpix) - twcs.wcs.set() + wcs.sip = Sip(a, b, None, None, wcs.wcs.crpix) + wcs.wcs.set() - return (twcs,) - - -@pytest.mark.parametrize("wcs", create_sip_distortion_wcs()) -def test_sip_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_hdu_list_equal(wcs.to_fits(relax=True), loaded_wcs.to_fits(relax=True)) + return wcs def create_tabular_wcs(): # Creates a WCS object with distortion lookup tables - img_world_wcs = wcs.WCS(naxis=2) + 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 @@ -49,7 +36,7 @@ def create_tabular_wcs(): # 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 = wcs.DistortionLookupTable( + map_x = DistortionLookupTable( x_dist_array.astype(np.float32), (5, 10), (10, 20), @@ -57,7 +44,7 @@ def create_tabular_wcs(): ) y_dist_array = np.zeros((25, 25)) y_dist_array[10, 5] = 0.7 - map_y = wcs.DistortionLookupTable( + map_y = DistortionLookupTable( y_dist_array.astype(np.float32), (5, 10), (10, 20), @@ -67,16 +54,11 @@ def create_tabular_wcs(): img_world_wcs.cpdis1 = map_x img_world_wcs.cpdis2 = map_y - return (img_world_wcs,) - + return img_world_wcs -@pytest.mark.parametrize("wcs", create_tabular_wcs()) -def test_twcs_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_hdu_list_equal(wcs.to_fits(relax=True), loaded_wcs.to_fits(relax=True)) +@pytest.mark.parametrize("version", ["1.5.0", "1.6.0"]) +@pytest.mark.parametrize("wcs_gen", [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) diff --git a/asdf_astropy/testing/helpers.py b/asdf_astropy/testing/helpers.py index c0cd064c..f4d18921 100644 --- a/asdf_astropy/testing/helpers.py +++ b/asdf_astropy/testing/helpers.py @@ -152,6 +152,27 @@ def assert_table_equal(a, b): ) +def assert_wcs_equal(a, b): + from asdf.tags.core.ndarray import NDArrayType + + from asdf_astropy.converters.wcs.wcs import _WCS_ATTRS + + assert type(a) == type(b) + assert_hdu_list_equal(a.to_fits(relax=True), b.to_fits(relax=True)) + for attr in _WCS_ATTRS: + in_a = hasattr(a, attr) + in_b = hasattr(b, attr) + assert in_a == in_b + if not in_a: + continue + a_val = getattr(a, attr) + b_val = getattr(b, attr) + if isinstance(a_val, (np.ndarray, NDArrayType)) or isinstance(b_val, (np.ndarray, NDArrayType)): + np.testing.assert_array_equal(a_val, b_val) + else: + assert a_val == b_val + + def assert_table_roundtrip(table, tmp_path): """ Assert that a table can be written to an ASDF file and read back @@ -237,3 +258,15 @@ def assert_model_roundtrip(model, tmp_path, version=None): with asdf.open(path) as af: assert_model_equal(model, af["model"]) return af["model"] + + +def assert_wcs_roundtrip(wcs, tmp_path, version=None): + import asdf + + path = str(tmp_path / "test.asdf") + + with asdf.AsdfFile({"wcs": wcs}, version=version) as af: + af.write_to(path) + + with asdf.open(path) as af: + assert_wcs_equal(wcs, af["wcs"]) From b20650fa0dcc0749f74305698ec785508d5cd4a6 Mon Sep 17 00:00:00 2001 From: Brett Date: Tue, 5 Nov 2024 15:15:02 -0500 Subject: [PATCH 26/30] rename --- .../converters/wcs/tests/{test_fitswcs.py => test_wcs.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename asdf_astropy/converters/wcs/tests/{test_fitswcs.py => test_wcs.py} (100%) diff --git a/asdf_astropy/converters/wcs/tests/test_fitswcs.py b/asdf_astropy/converters/wcs/tests/test_wcs.py similarity index 100% rename from asdf_astropy/converters/wcs/tests/test_fitswcs.py rename to asdf_astropy/converters/wcs/tests/test_wcs.py From 160adcc5aa61afaaa156911b586e2788f55f303d Mon Sep 17 00:00:00 2001 From: Brett Date: Tue, 5 Nov 2024 15:17:20 -0500 Subject: [PATCH 27/30] use assert_wcs_equal for sliced tests --- asdf_astropy/converters/wcs/tests/test_slicedwcs.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/asdf_astropy/converters/wcs/tests/test_slicedwcs.py b/asdf_astropy/converters/wcs/tests/test_slicedwcs.py index 52d0af20..f845c2b2 100644 --- a/asdf_astropy/converters/wcs/tests/test_slicedwcs.py +++ b/asdf_astropy/converters/wcs/tests/test_slicedwcs.py @@ -4,7 +4,7 @@ from astropy.wcs import WCS from astropy.wcs.wcsapi.wrappers.sliced_wcs import SlicedLowLevelWCS -from asdf_astropy.testing.helpers import assert_hdu_list_equal +from asdf_astropy.testing.helpers import assert_wcs_equal def create_wcs(): @@ -33,5 +33,5 @@ def test_sliced_wcs_serialization(sl_wcs, tmp_path): with asdf.open(file_path) as af: loaded_sl_wcs = af["sl_wcs"] - assert_hdu_list_equal(sl_wcs._wcs.to_fits(), loaded_sl_wcs._wcs.to_fits()) + assert_wcs_equal(sl_wcs._wcs, loaded_sl_wcs._wcs) assert sl_wcs._slices_array == loaded_sl_wcs._slices_array From 93b946ec240e4d5985f86138f155a8efed8b8b0b Mon Sep 17 00:00:00 2001 From: Brett Date: Tue, 5 Nov 2024 15:43:47 -0500 Subject: [PATCH 28/30] add attrs and blank wcs test --- asdf_astropy/converters/wcs/tests/test_wcs.py | 16 +++++++++++++++- asdf_astropy/converters/wcs/wcs.py | 2 ++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/asdf_astropy/converters/wcs/tests/test_wcs.py b/asdf_astropy/converters/wcs/tests/test_wcs.py index c770e3fe..728c10b0 100644 --- a/asdf_astropy/converters/wcs/tests/test_wcs.py +++ b/asdf_astropy/converters/wcs/tests/test_wcs.py @@ -5,6 +5,17 @@ from asdf_astropy.testing.helpers import assert_wcs_roundtrip +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) @@ -58,7 +69,10 @@ def create_tabular_wcs(): @pytest.mark.parametrize("version", ["1.5.0", "1.6.0"]) -@pytest.mark.parametrize("wcs_gen", [create_tabular_wcs, create_sip_distortion_wcs]) +@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) diff --git a/asdf_astropy/converters/wcs/wcs.py b/asdf_astropy/converters/wcs/wcs.py index b3a46ac7..24cf623a 100644 --- a/asdf_astropy/converters/wcs/wcs.py +++ b/asdf_astropy/converters/wcs/wcs.py @@ -1,5 +1,7 @@ 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_shape", "pixel_bounds") From 8c6231be349e0a88de46b063fb5aff91ae7d1a77 Mon Sep 17 00:00:00 2001 From: Brett Date: Thu, 7 Nov 2024 12:22:05 -0500 Subject: [PATCH 29/30] fix pixel_shape roundtrip bug, test against astropy test data --- asdf_astropy/converters/wcs/tests/test_wcs.py | 44 ++++++++++++++++++- asdf_astropy/converters/wcs/wcs.py | 4 +- pyproject.toml | 1 + 3 files changed, 45 insertions(+), 4 deletions(-) diff --git a/asdf_astropy/converters/wcs/tests/test_wcs.py b/asdf_astropy/converters/wcs/tests/test_wcs.py index 728c10b0..70206fd5 100644 --- a/asdf_astropy/converters/wcs/tests/test_wcs.py +++ b/asdf_astropy/converters/wcs/tests/test_wcs.py @@ -1,9 +1,27 @@ +import warnings + import numpy as np import pytest -from astropy.wcs import WCS, DistortionLookupTable, Sip +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() @@ -76,3 +94,27 @@ def create_tabular_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) diff --git a/asdf_astropy/converters/wcs/wcs.py b/asdf_astropy/converters/wcs/wcs.py index 24cf623a..3ed9f7ad 100644 --- a/asdf_astropy/converters/wcs/wcs.py +++ b/asdf_astropy/converters/wcs/wcs.py @@ -2,7 +2,7 @@ # These attributes don't end up in the hdulist and # instead will be stored in "attrs" -_WCS_ATTRS = ("naxis", "colsel", "keysel", "key", "pixel_shape", "pixel_bounds") +_WCS_ATTRS = ("naxis", "colsel", "keysel", "key", "pixel_bounds") class WCSConverter(Converter): @@ -18,7 +18,6 @@ def from_yaml_tree(self, node, tag, ctx): if naxis := attrs.pop("naxis"): hdulist[0].header["naxis"] = naxis - pixel_shape = attrs.pop("pixel_shape") pixel_bounds = attrs.pop("pixel_bounds") wcs = WCS(hdulist[0].header, fobj=hdulist, **attrs) @@ -29,7 +28,6 @@ def from_yaml_tree(self, node, tag, ctx): wcs.sip = wcs._read_sip_kw(hdulist[0].header, attrs.get("key", " ")) wcs.wcs.set() - wcs.pixel_shape = pixel_shape wcs.pixel_bounds = pixel_bounds return wcs diff --git a/pyproject.toml b/pyproject.toml index 5cfadc13..d817bebe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -97,6 +97,7 @@ extend-ignore = [ "SLF001", # private-member-access "FBT001", # boolean positional arguments in function definition "RUF012", # mutable class attributes should be annotated with typing.ClassVar + "PTH123", # replace open with Path.open ] extend-exclude = ["docs/*"] From 1edecd171ead337cc7fd6cf5be519bae27716742 Mon Sep 17 00:00:00 2001 From: Brett Date: Wed, 13 Nov 2024 08:25:15 -0500 Subject: [PATCH 30/30] cleanup after rebase --- asdf_astropy/converters/__init__.py | 2 +- asdf_astropy/extensions.py | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/asdf_astropy/converters/__init__.py b/asdf_astropy/converters/__init__.py index 21c5d797..3334fa0b 100644 --- a/asdf_astropy/converters/__init__.py +++ b/asdf_astropy/converters/__init__.py @@ -54,7 +54,7 @@ SkyCoordConverter, SpectralCoordConverter, ) -from .fits import AsdfFitsConverter, AstropyFitsConverter, FitsConverter, FitsWCSConverter +from .fits import AsdfFitsConverter, AstropyFitsConverter, FitsConverter from .nddata import UncertaintyConverter from .table import AsdfTableConverter, AstropyTableConverter, ColumnConverter, NdarrayMixinConverter from .time import TimeConverter, TimeDeltaConverter diff --git a/asdf_astropy/extensions.py b/asdf_astropy/extensions.py index e84884a4..0bda0584 100644 --- a/asdf_astropy/extensions.py +++ b/asdf_astropy/extensions.py @@ -12,9 +12,7 @@ from .converters.coordinates.sky_coord import SkyCoordConverter from .converters.coordinates.spectral_coord import SpectralCoordConverter from .converters.fits.fits import AsdfFitsConverter, AstropyFitsConverter -from .converters.fits.fitswcs import FitsWCSConverter from .converters.nddata.uncertainty import UncertaintyConverter -from .converters.slicedwcs.slicedwcs import SlicedWCSConverter from .converters.table.table import AsdfTableConverter, AstropyTableConverter, ColumnConverter, NdarrayMixinConverter from .converters.time.time import TimeConverter from .converters.time.time_delta import TimeDeltaConverter