Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for the serialization of the ndcube WCS wrappers. #751

Open
wants to merge 6 commits into
base: asdf-support
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions ndcube/asdf/converters/compoundwcs_converter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from asdf.extension import Converter


class CompoundConverter(Converter):
tags = ["tag:sunpy.org:ndcube/compoundwcs-0.1.0"]
types = ["ndcube.wcs.wrappers.compound_wcs.CompoundLowLevelWCS"]

def from_yaml_tree(self, node, tag, ctx):
from ndcube.wcs.wrappers import CompoundLowLevelWCS

return(CompoundLowLevelWCS(*node["wcs"], mapping = node.get("mapping"), pixel_atol = node.get("atol")))

def to_yaml_tree(self, compoundwcs, tag, ctx):
node={}
node["wcs"] = compoundwcs._wcs
node["mapping"] = compoundwcs.mapping.mapping
node["atol"] = compoundwcs.atol
return node
7 changes: 6 additions & 1 deletion ndcube/asdf/converters/ndcube_converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,14 @@ def to_yaml_tree(self, ndcube, tag, ctx):
This ensures that users are aware of potentially important information
that is not included in the serialized output.
"""
from astropy.wcs.wcsapi import HighLevelWCSWrapper

node = {}
node["data"] = ndcube.data
node["wcs"] = ndcube.wcs
if isinstance(ndcube.wcs, HighLevelWCSWrapper):
node["wcs"] = ndcube.wcs._low_level_wcs
else:
node["wcs"] = ndcube.wcs
node["extra_coords"] = ndcube.extra_coords
node["global_coords"] = ndcube.global_coords
node["meta"] = ndcube.meta
Expand Down
21 changes: 21 additions & 0 deletions ndcube/asdf/converters/reorderedwcs_converter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from asdf.extension import Converter


class ReorderedConverter(Converter):
tags = ["tag:sunpy.org:ndcube/reorderedwcs-0.1.0"]
types = ["ndcube.wcs.wrappers.reordered_wcs.ReorderedLowLevelWCS"]

def from_yaml_tree(self, node, tag, ctx):
from ndcube.wcs.wrappers import ReorderedLowLevelWCS

reorderedwcs = ReorderedLowLevelWCS(wcs=node["wcs"],
pixel_order = node.get("pixel_order"),
world_order = node.get("world_order")
)
return reorderedwcs
def to_yaml_tree(self, reorderedwcs, tag, ctx):
node={}
node["wcs"] = reorderedwcs._wcs
node["pixel_order"] = reorderedwcs._pixel_order
node["world_order"] = reorderedwcs._world_order
return node
22 changes: 22 additions & 0 deletions ndcube/asdf/converters/resampled_converter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from asdf.extension import Converter


class ResampledConverter(Converter):
tags = ["tag:sunpy.org:ndcube/resampledwcs-0.1.0"]
types = ["ndcube.wcs.wrappers.resampled_wcs.ResampledLowLevelWCS"]

def from_yaml_tree(self, node, tag, ctx):
from ndcube.wcs.wrappers import ResampledLowLevelWCS

resampledwcs = ResampledLowLevelWCS(wcs=node["wcs"],
offset = node.get("offset"),
factor = node.get("factor"),
)
return resampledwcs
def to_yaml_tree(self, resampledwcs, tag, ctx):
node={}
node["wcs"] = resampledwcs._wcs
node["factor"] = resampledwcs._factor
node["offset"] = resampledwcs._offset

return node
92 changes: 92 additions & 0 deletions ndcube/asdf/converters/tests/test_ndcube_wcs_wrappers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
"""
Tests for roundtrip serialization of NDCube with various GWCS types.

TODO: Add tests for the roundtrip serialization of NDCube with ResampledLowLevelWCS, ReorderedLowLevelWCS, and CompoundLowLevelWCS when using astropy.wcs.WCS.
"""

import pytest
from gwcs import __version__ as gwcs_version
from packaging.version import Version

import asdf

from ndcube import NDCube
from ndcube.conftest import data_nd
from ndcube.tests.helpers import assert_cubes_equal
from ndcube.wcs.wrappers import CompoundLowLevelWCS, ReorderedLowLevelWCS, ResampledLowLevelWCS


@pytest.fixture
def create_ndcube_resampledwcs(gwcs_3d_lt_ln_l):
shape = (2, 3, 4)
new_wcs = ResampledLowLevelWCS(wcs = gwcs_3d_lt_ln_l, factor=2 ,offset = 1)
data = data_nd(shape)
return NDCube(data = data, wcs =new_wcs)


@pytest.mark.skipif(Version(gwcs_version) < Version("0.20"), reason="Requires gwcs>=0.20")
def test_serialization_resampled(create_ndcube_resampledwcs, tmp_path):
ndc = create_ndcube_resampledwcs
file_path = tmp_path / "test.asdf"
with asdf.AsdfFile() as af:
af["ndcube"] = ndc
af.write_to(file_path)

with asdf.open(file_path) as af:
loaded_ndcube = af["ndcube"]

loaded_resampledwcs = loaded_ndcube.wcs.low_level_wcs
resampledwcs = ndc.wcs.low_level_wcs
assert (loaded_resampledwcs._factor == resampledwcs._factor).all()
assert (loaded_resampledwcs._offset == resampledwcs._offset).all()

assert_cubes_equal(loaded_ndcube, ndc)

@pytest.fixture
def create_ndcube_reorderedwcs(gwcs_3d_lt_ln_l):
shape = (2, 3, 4)
new_wcs = ReorderedLowLevelWCS(wcs = gwcs_3d_lt_ln_l, pixel_order=[1, 2, 0] ,world_order=[2, 0, 1])
data = data_nd(shape)
return NDCube(data = data, wcs =new_wcs)



@pytest.mark.skipif(Version(gwcs_version) < Version("0.20"), reason="Requires gwcs>=0.20")
def test_serialization_reordered(create_ndcube_reorderedwcs, tmp_path):
ndc = create_ndcube_reorderedwcs
file_path = tmp_path / "test.asdf"
with asdf.AsdfFile() as af:
af["ndcube"] = ndc
af.write_to(file_path)

with asdf.open(file_path) as af:
loaded_ndcube = af["ndcube"]

loaded_reorderedwcs = loaded_ndcube.wcs.low_level_wcs
reorderedwcs = ndc.wcs.low_level_wcs
assert (loaded_reorderedwcs._pixel_order == reorderedwcs._pixel_order)
assert (loaded_reorderedwcs._world_order == reorderedwcs._world_order)

assert_cubes_equal(loaded_ndcube, ndc)

@pytest.fixture
def create_ndcube_compoundwcs(gwcs_2d_lt_ln, time_and_simple_extra_coords_2d):

shape = (1, 2, 3, 4)
new_wcs = CompoundLowLevelWCS(gwcs_2d_lt_ln, time_and_simple_extra_coords_2d.wcs, mapping = [0, 1, 2, 3])
data = data_nd(shape)
return NDCube(data = data, wcs = new_wcs)

@pytest.mark.skipif(Version(gwcs_version) < Version("0.20"), reason="Requires gwcs>=0.20")
def test_serialization_compoundwcs(create_ndcube_compoundwcs, tmp_path):
ndc = create_ndcube_compoundwcs
file_path = tmp_path / "test.asdf"
with asdf.AsdfFile() as af:
af["ndcube"] = ndc
af.write_to(file_path)

with asdf.open(file_path) as af:
loaded_ndcube = af["ndcube"]
assert_cubes_equal(loaded_ndcube, ndc)
assert (loaded_ndcube.wcs.low_level_wcs.mapping.mapping == ndc.wcs.low_level_wcs.mapping.mapping)
assert (loaded_ndcube.wcs.low_level_wcs.atol == ndc.wcs.low_level_wcs.atol)
6 changes: 6 additions & 0 deletions ndcube/asdf/entry_points.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,12 @@
"""
Get the list of extensions.
"""
from ndcube.asdf.converters.compoundwcs_converter import CompoundConverter

Check warning on line 34 in ndcube/asdf/entry_points.py

View check run for this annotation

Codecov / codecov/patch

ndcube/asdf/entry_points.py#L34

Added line #L34 was not covered by tests
from ndcube.asdf.converters.extracoords_converter import ExtraCoordsConverter
from ndcube.asdf.converters.globalcoords_converter import GlobalCoordsConverter
from ndcube.asdf.converters.ndcube_converter import NDCubeConverter
from ndcube.asdf.converters.reorderedwcs_converter import ReorderedConverter
from ndcube.asdf.converters.resampled_converter import ResampledConverter

Check warning on line 39 in ndcube/asdf/entry_points.py

View check run for this annotation

Codecov / codecov/patch

ndcube/asdf/entry_points.py#L38-L39

Added lines #L38 - L39 were not covered by tests
from ndcube.asdf.converters.tablecoord_converter import (
QuantityTableCoordinateConverter,
SkyCoordTableCoordinateConverter,
Expand All @@ -47,6 +50,9 @@
QuantityTableCoordinateConverter(),
SkyCoordTableCoordinateConverter(),
GlobalCoordsConverter(),
ResampledConverter(),
ReorderedConverter(),
CompoundConverter(),
]
_manifest_uri = "asdf://sunpy.org/ndcube/manifests/ndcube-0.1.0"

Expand Down
9 changes: 9 additions & 0 deletions ndcube/asdf/resources/manifests/ndcube-0.1.0.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,12 @@ tags:

- tag_uri: "tag:sunpy.org:ndcube/global_coords/globalcoords-0.1.0"
schema_uri: "asdf://sunpy.org/ndcube/schemas/global_coords-0.1.0"

- tag_uri: "tag:sunpy.org:ndcube/resampledwcs-0.1.0"
schema_uri: "asdf://sunpy.org/ndcube/schemas/resampledwcs-0.1.0"

- tag_uri: "tag:sunpy.org:ndcube/reorderedwcs-0.1.0"
schema_uri: "asdf://sunpy.org/ndcube/schemas/reorderedwcs-0.1.0"

- tag_uri: "tag:sunpy.org:ndcube/compoundwcs-0.1.0"
schema_uri: "asdf://sunpy.org/ndcube/schemas/compoundwcs-0.1.0"
25 changes: 25 additions & 0 deletions ndcube/asdf/resources/schemas/compoundwcs-0.1.0.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
%YAML 1.1
---
$schema: "http://stsci.edu/schemas/yaml-schema/draft-01"
id: "asdf://sunpy.org/ndcube/schemas/Compoundwcs-0.1.0"

title:
Represents the ndcube CompoundLowLevelWCS object

description:
Represents the ndcube CompoundLowLevelWCS object

type: object
properties:
wcs:
type: array
items:
tag: "tag:stsci.edu:gwcs/wcs-1.*"
mapping:
type: array
atol:
type: number

required: [wcs]
additionalProperties: true
...
6 changes: 5 additions & 1 deletion ndcube/asdf/resources/schemas/ndcube-0.1.0.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@ properties:
data:
description: "Must be compatible with ASDF serialization/deserialization and supported by NDCube."
wcs:
tag: "tag:stsci.edu:gwcs/wcs-1.*"
anyOf:
- tag: "tag:stsci.edu:gwcs/wcs-1.*"
- tag: "tag:sunpy.org:ndcube/resampledwcs-0.1.0"
- tag: "tag:sunpy.org:ndcube/reorderedwcs-0.1.0"
- tag: "tag:sunpy.org:ndcube/compoundwcs-0.1.0"
extra_coords:
tag: "tag:sunpy.org:ndcube/extra_coords/extra_coords/extracoords-0.*"
global_coords:
Expand Down
23 changes: 23 additions & 0 deletions ndcube/asdf/resources/schemas/reorderedwcs-0.1.0.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
%YAML 1.1
---
$schema: "http://stsci.edu/schemas/yaml-schema/draft-01"
id: "asdf://sunpy.org/ndcube/schemas/resampledwcs-0.1.0"

title:
Represents the ndcube ReorderedLowLevelWCS object

description:
Represents the ndcube ReorderedLowLevelWCS object

type: object
properties:
wcs:
tag: "tag:stsci.edu:gwcs/wcs-1.*"
pixel_order:
type: array
world_order:
type: array

required: [wcs]
additionalProperties: true
...
23 changes: 23 additions & 0 deletions ndcube/asdf/resources/schemas/resampledwcs-0.1.0.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
%YAML 1.1
---
$schema: "http://stsci.edu/schemas/yaml-schema/draft-01"
id: "asdf://sunpy.org/ndcube/schemas/resampledwcs-0.1.0"

title:
Represents the ndcube ResampledLowLevelWCS object

description:
Represents the ndcube ResampledLowLevelWCS object

type: object
properties:
wcs:
tag: "tag:stsci.edu:gwcs/wcs-1.*"
factor:
tag: "tag:stsci.edu:asdf/core/ndarray-1.0.0"
offset:
tag: "tag:stsci.edu:asdf/core/ndarray-1.0.0"

required: [wcs]
additionalProperties: true
...
8 changes: 5 additions & 3 deletions ndcube/extra_coords/table_coord.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@
import astropy.units as u
from astropy.coordinates import SkyCoord
from astropy.modeling import models
from astropy.modeling.models import tabular_model
from astropy.modeling.tabular import _Tabular
from astropy.modeling.tabular import Tabular1D, Tabular2D, _Tabular
from astropy.time import Time
from astropy.wcs.wcsapi.wrappers.sliced_wcs import combine_slices, sanitize_slices

Expand Down Expand Up @@ -136,7 +135,10 @@
raise TypeError("lookup_table must be a Quantity.") # pragma: no cover

ndim = lookup_table.ndim
TabularND = tabular_model(ndim, name=f"Tabular{ndim}D")
if ndim == 1:
TabularND = Tabular1D
elif ndim == 2:
TabularND = Tabular2D

Check warning on line 141 in ndcube/extra_coords/table_coord.py

View check run for this annotation

Codecov / codecov/patch

ndcube/extra_coords/table_coord.py#L138-L141

Added lines #L138 - L141 were not covered by tests
Copy link
Member

Choose a reason for hiding this comment

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

I think this should have an else for >2D. Also a comment saying that this is for asdf would be good for future reference.

Copy link
Member Author

Choose a reason for hiding this comment

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

Sure


# The integer location is at the centre of the pixel.
points = [(np.arange(size) - 0) * points_unit for size in lookup_table.shape]
Expand Down
Loading