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

Asdf-Support #708

Open
wants to merge 30 commits into
base: asdf-support
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
39faa60
initial commit
ViciousEagle03 May 20, 2024
9644bbb
version alignment
ViciousEagle03 May 20, 2024
af7dccb
Remove method to remove any delay and
ViciousEagle03 May 22, 2024
67b8805
Update tag and
ViciousEagle03 May 22, 2024
b62ac94
enable schema test
ViciousEagle03 May 22, 2024
e2928ea
revert small change
ViciousEagle03 May 22, 2024
c2b240f
Add the converters for ExtraCoords and TimeTableCoordinate class
ViciousEagle03 May 30, 2024
94f2065
Add ExtraCoords and TimeTableCoord Schema
ViciousEagle03 May 30, 2024
cc65cc3
Update manifest and entry_point.py
ViciousEagle03 May 30, 2024
5ffb493
Add the QuantityTableCoordinate and SkyCoordTableCoordinate converters
ViciousEagle03 May 31, 2024
695f8f6
Add the schema for QuantityTableCoordinate and SkyCoordTableCoordinate
ViciousEagle03 May 31, 2024
9e38352
Update manifest and entry_point.py
ViciousEagle03 May 31, 2024
a9c5868
Add validation for schema and manifests
ViciousEagle03 Jun 4, 2024
835c5c7
Merge branch 'asdf-support' into asdf_basic_support
Cadair Jun 5, 2024
3bb70e4
pre-commit
Cadair Jun 5, 2024
04409b5
Update the tox.ini and CI workflow
ViciousEagle03 Jun 5, 2024
76cc27e
Update schemas
ViciousEagle03 Jun 5, 2024
3ea5e65
Add the converter and schema for GlobalCoords
ViciousEagle03 Jun 17, 2024
a31f5fc
Update converters
ViciousEagle03 Jun 17, 2024
f313395
Update entry_points,schemas and manifest
ViciousEagle03 Jun 17, 2024
3d9d9e8
minor change
ViciousEagle03 Jun 19, 2024
d031c35
lowercase schema URIs and use wildcards
ViciousEagle03 Jun 21, 2024
171dcd6
Add tests and GWCS objects
ViciousEagle03 Jul 1, 2024
0c7135f
merge from upstream/asdf-support
ViciousEagle03 Jul 4, 2024
0a84f8b
revert small change
ViciousEagle03 Jul 4, 2024
cd31d95
apply suggestions from code review
ViciousEagle03 Jul 6, 2024
7c04d35
Remove mesh as a property validator
ViciousEagle03 Jul 11, 2024
266f88e
Update the dependencies version
ViciousEagle03 Jul 11, 2024
67358b4
Style changes and add warnings to NDCube converter
ViciousEagle03 Jul 19, 2024
6e7a338
env name update
ViciousEagle03 Jul 19, 2024
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
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ jobs:
- windows: py311
- macos: py310
- linux: py310-oldestdeps
- linux: schema
secrets:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}

Expand Down
Empty file added ndcube/asdf/__init__.py
Empty file.
28 changes: 28 additions & 0 deletions ndcube/asdf/converters/extracoords_converter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from asdf.extension import Converter


class ExtraCoordsConverter(Converter):
tags = ["tag:sunpy.org:ndcube/extra_coords/extra_coords/extracoords-*"]
types = ["ndcube.extra_coords.extra_coords.ExtraCoords"]

def from_yaml_tree(self, node, tag, ctx):
from ndcube.extra_coords.extra_coords import ExtraCoords
extra_coords = ExtraCoords()
extra_coords._wcs = node.get("wcs")
Cadair marked this conversation as resolved.
Show resolved Hide resolved
extra_coords._mapping = node.get("mapping")
extra_coords._lookup_tables = node.get("lookup_tables", [])
extra_coords._dropped_tables = node.get("dropped_tables")
extra_coords._ndcube = node.get("ndcube")
Copy link
Member

Choose a reason for hiding this comment

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

@braingram @ViciousEagle03 is asdf smart enough to handle this circular reference and not save out the ndcube object twice? i.e. I save an NDCube which has an ExtraCoords which references the same NDCube?

Choose a reason for hiding this comment

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

Theoretically yes but I believe the converter will need to be updated.
https://asdf.readthedocs.io/en/latest/asdf/extending/converters.html#reference-cycles
That seems like a good test case.

Copy link
Member Author

@ViciousEagle03 ViciousEagle03 Jul 19, 2024

Choose a reason for hiding this comment

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

@braingram, for the current implementation I believe it is still not storing the NDCube object twice, right?
And also in the current from_yaml_tree method, it doesn't have yield but it can still deserialize the extra_coords object, that is, when the extra_coords converter is called the extra_coords.ndcube shows _PendingValue and when it gets picked up in the NDCube converter and the ndcube.extra_coords is initialized the reference to the ndcube is properly mapped in the ndcube.extra_coords._ndcube.

We can see the deserialized NDCube object and the NDCube reference address for the ExtraCoords associated is the same for the below.

(Pdb) ndcube.extra_coords._ndcube
<ndcube.ndcube.NDCube object at 0x7c84fe60c2d0>
NDCube
------
Shape: (10, 10)
Physical Types of Axes: [('em.energy', 'time', 'pos.eq.ra', 'pos.eq.dec'), ('time', 'custom:CUSTOM')]
Unit: None
Data Type: float64
(Pdb) ndcube
<ndcube.ndcube.NDCube object at 0x7c84fe60c2d0>
NDCube
------
Shape: (10, 10)
Physical Types of Axes: [('em.energy', 'time', 'pos.eq.ra', 'pos.eq.dec'), ('time', 'custom:CUSTOM')]
Unit: None
Data Type: float64
(Pdb) 

Choose a reason for hiding this comment

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

Thanks for testing this out. This looks to be working because __set__ overwrites _ndcube when _extra_coords is assigned:

value._ndcube = obj

I am not sure what's going on with __set__. @Cadair is there ever an instance where:

  • the extra_coords attached to a NDCube will reference a different NDCube?
  • extra_coords will exist without a NDCube?

return extra_coords

def to_yaml_tree(self, extracoords, tag, ctx):
node = {}
if extracoords._wcs is not None:
node["wcs"] = extracoords._wcs
if extracoords._mapping is not None:
node["mapping"] = extracoords._mapping
if extracoords._lookup_tables:
node["lookup_tables"] = extracoords._lookup_tables
node["dropped_tables"] = extracoords._dropped_tables
node["ndcube"] = extracoords._ndcube
return node
24 changes: 24 additions & 0 deletions ndcube/asdf/converters/globalcoords_converter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from asdf.extension import Converter


class GlobalCoordsConverter(Converter):
tags = ["tag:sunpy.org:ndcube/global_coords/globalcoords-*"]
types = ["ndcube.global_coords.GlobalCoords"]

def from_yaml_tree(self, node, tag, ctx):
from ndcube.global_coords import GlobalCoords

globalcoords = GlobalCoords()
if "internal_coords" in node:
globalcoords._internal_coords = node["internal_coords"]
globalcoords._ndcube = node["ndcube"]

return globalcoords

def to_yaml_tree(self, globalcoords, tag, ctx):
node = {}
node["ndcube"] = globalcoords._ndcube
if globalcoords._internal_coords:
node["internal_coords"] = globalcoords._internal_coords

return node
24 changes: 24 additions & 0 deletions ndcube/asdf/converters/ndcube_converter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from asdf.extension import Converter


class NDCubeConverter(Converter):
tags = ["tag:sunpy.org:ndcube/ndcube/ndcube-*"]
types = ["ndcube.ndcube.NDCube"]

def from_yaml_tree(self, node, tag, ctx):
from ndcube.ndcube import NDCube

ndcube = NDCube(node["data"], node["wcs"])
ndcube._extra_coords = node["extra_coords"]
ndcube._global_coords = node["global_coords"]

return ndcube

def to_yaml_tree(self, ndcube, tag, ctx):
node = {}
node["data"] = ndcube.data
node["wcs"] = ndcube.wcs
node["extra_coords"] = ndcube.extra_coords
node["global_coords"] = ndcube.global_coords
Copy link
Member

Choose a reason for hiding this comment

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

I wonder if we want to be safe here and throw a warning if any of the other parameters are present and not being saved? That would mean if we don't "finish" this converter then it's still safe to release and use.

Copy link
Member Author

Choose a reason for hiding this comment

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

I have added warnings for the NDCube parameters that wouldn't be serialized to ASDF.


return node
81 changes: 81 additions & 0 deletions ndcube/asdf/converters/tablecoord_converter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
from asdf.extension import Converter


class TimeTableCoordConverter(Converter):
tags = ["tag:sunpy.org:ndcube/extra_coords/table_coord/timetablecoordinate-*"]
types = ["ndcube.extra_coords.table_coord.TimeTableCoordinate"]

def from_yaml_tree(self, node, tag, ctx):
from ndcube.extra_coords.table_coord import TimeTableCoordinate

names = node.get("names")
physical_types = node.get("physical_types")
reference_time = node.get("reference_time")
timetablecoordinate = TimeTableCoordinate(
node["table"], names=names, physical_types=physical_types, reference_time=reference_time)

return timetablecoordinate

def to_yaml_tree(self, timetablecoordinate, tag, ctx):
node = {}
node["table"] = timetablecoordinate.table
node["names"] = timetablecoordinate.names
node["mesh"] = timetablecoordinate.mesh
ViciousEagle03 marked this conversation as resolved.
Show resolved Hide resolved
if timetablecoordinate.physical_types is not None:
node["physical_types"] = timetablecoordinate.physical_types
node["reference_time"] = timetablecoordinate.reference_time

return node


class QuantityTableCoordinateConverter(Converter):
tags = ["tag:sunpy.org:ndcube/extra_coords/table_coord/quantitytablecoordinate-*"]
types = ["ndcube.extra_coords.table_coord.QuantityTableCoordinate"]

def from_yaml_tree(self, node, tag, ctx):
from ndcube.extra_coords.table_coord import QuantityTableCoordinate

names = node.get("names")
mesh = node.get("mesh")
physical_types = node.get("physical_types")
quantitytablecoordinate = QuantityTableCoordinate(*node["table"],
names=names, physical_types=physical_types)
quantitytablecoordinate.unit = node["unit"]
quantitytablecoordinate.mesh = mesh
return quantitytablecoordinate

def to_yaml_tree(self, quantitytablecoordinate, tag, ctx):
node = {}
node["unit"] = quantitytablecoordinate.unit
node["table"] = quantitytablecoordinate.table
node["names"] = quantitytablecoordinate.names
node["mesh"] = quantitytablecoordinate.mesh
if quantitytablecoordinate.physical_types is not None:
node["physical_types"] = quantitytablecoordinate.physical_types

return node


class SkyCoordTableCoordinateConverter(Converter):
tags = ["tag:sunpy.org:ndcube/extra_coords/table_coord/skycoordtablecoordinate-*"]
types = ["ndcube.extra_coords.table_coord.SkyCoordTableCoordinate"]

def from_yaml_tree(self, node, tag, ctx):
from ndcube.extra_coords.table_coord import SkyCoordTableCoordinate

names = node.get("names")
mesh = node.get("mesh")
physical_types = node.get("physical_types")
skycoordinatetablecoordinate = SkyCoordTableCoordinate(node["table"], mesh=mesh,
names=names, physical_types=physical_types)
return skycoordinatetablecoordinate

def to_yaml_tree(self, skycoordinatetablecoordinate, tag, ctx):
node = {}
node["table"] = skycoordinatetablecoordinate.table
node["names"] = skycoordinatetablecoordinate.names
node["mesh"] = skycoordinatetablecoordinate.mesh
if skycoordinatetablecoordinate.physical_types is not None:
node["physical_types"] = skycoordinatetablecoordinate.physical_types

return node
44 changes: 44 additions & 0 deletions ndcube/asdf/converters/tests/test_ndcube_converter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import numpy as np
import pytest

import asdf

from ndcube.tests.helpers import assert_cubes_equal


@pytest.mark.parametrize("ndc",[("ndcube_gwcs_2d_ln_lt"),
("ndcube_gwcs_3d_ln_lt_l"),
("ndcube_gwcs_3d_ln_lt_l_ec_dropped_dim"),
("ndcube_gwcs_3d_ln_lt_l_ec_q_t_gc"),
("ndcube_gwcs_3d_rotated"),
("ndcube_gwcs_4d_ln_lt_l_t")
], indirect=("ndc",))
def test_serialization(ndc, tmp_path):
file_path = tmp_path / "test.asdf"
with asdf.AsdfFile() as af:
af["ndcube_gwcs"] = ndc
af.write_to(file_path)

with asdf.open(file_path) as af:
assert_cubes_equal(af["ndcube_gwcs"], ndc)

@pytest.mark.xfail(reason="Serialization of sliced ndcube not supported")
ViciousEagle03 marked this conversation as resolved.
Show resolved Hide resolved
ViciousEagle03 marked this conversation as resolved.
Show resolved Hide resolved
def test_serialization_sliced_ndcube(ndcube_gwcs_3d_ln_lt_l, tmp_path):
sndc = ndcube_gwcs_3d_ln_lt_l[np.s_[0, :, :]]
file_path = tmp_path / "test.asdf"
with asdf.AsdfFile() as af:
af["ndcube_gwcs"] = sndc
af.write_to(file_path)

with asdf.open(file_path) as af:
assert_cubes_equal(af["ndcube_gwcs"], sndc)

@pytest.mark.xfail(reason="Serialization of ndcube with .wcs attribute as astropy.wcs.wcs.WCS not supported")
ViciousEagle03 marked this conversation as resolved.
Show resolved Hide resolved
def test_serialization_ndcube_wcs(ndcube_3d_ln_lt_l, tmp_path):
file_path = tmp_path / "test.asdf"
with asdf.AsdfFile() as af:
af["ndcube"] = ndcube_3d_ln_lt_l
af.write_to(file_path)

with asdf.open(file_path) as af:
assert_cubes_equal(af["ndcube"], ndcube_3d_ln_lt_l)
55 changes: 55 additions & 0 deletions ndcube/asdf/entry_points.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
"""
This file contains the entry points for asdf.
"""
import importlib.resources as importlib_resources

from asdf.extension import ManifestExtension
from asdf.resource import DirectoryResourceMapping


def get_resource_mappings():
"""
Get the resource mapping instances for myschemas
and manifests. This method is registered with the
asdf.resource_mappings entry point.

Returns
-------
list of collections.abc.Mapping
"""
from ndcube.asdf import resources
resources_root = importlib_resources.files(resources)
return [

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

View check run for this annotation

Codecov / codecov/patch

ndcube/asdf/entry_points.py#L20-L22

Added lines #L20 - L22 were not covered by tests
DirectoryResourceMapping(
resources_root / "schemas", "asdf://sunpy.org/ndcube/schemas/"),
DirectoryResourceMapping(
resources_root / "manifests", "asdf://sunpy.org/ndcube/manifests/"),
]


def get_extensions():
"""
Get the list of extensions.
"""
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.tablecoord_converter import (

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

View check run for this annotation

Codecov / codecov/patch

ndcube/asdf/entry_points.py#L34-L37

Added lines #L34 - L37 were not covered by tests
QuantityTableCoordinateConverter,
SkyCoordTableCoordinateConverter,
TimeTableCoordConverter,
)

ndcube_converters = [

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

View check run for this annotation

Codecov / codecov/patch

ndcube/asdf/entry_points.py#L43

Added line #L43 was not covered by tests
NDCubeConverter(),
ExtraCoordsConverter(),
TimeTableCoordConverter(),
QuantityTableCoordinateConverter(),
SkyCoordTableCoordinateConverter(),
GlobalCoordsConverter(),
]
_manifest_uri = "asdf://sunpy.org/ndcube/manifests/ndcube-0.1.0"

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

View check run for this annotation

Codecov / codecov/patch

ndcube/asdf/entry_points.py#L51

Added line #L51 was not covered by tests

return [

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

View check run for this annotation

Codecov / codecov/patch

ndcube/asdf/entry_points.py#L53

Added line #L53 was not covered by tests
ManifestExtension.from_uri(_manifest_uri, converters=ndcube_converters)
]
25 changes: 25 additions & 0 deletions ndcube/asdf/resources/manifests/ndcube-0.1.0.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
%YAML 1.1
---
id: asdf://sunpy.org/ndcube/manifests/ndcube-0.1.0
extension_uri: asdf://sunpy.org/extensions/ndcube-0.1.0
title: NDCube ASDF Manifest
description: ASDF schemas and tags for NDCube classes.

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

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

- tag_uri: "tag:sunpy.org:ndcube/extra_coords/table_coord/timetablecoordinate-0.1.0"
schema_uri: "asdf://sunpy.org/ndcube/schemas/timetablecoordinate-0.1.0"

- tag_uri: "tag:sunpy.org:ndcube/extra_coords/table_coord/quantitytablecoordinate-0.1.0"
schema_uri: "asdf://sunpy.org/ndcube/schemas/quantitytablecoordinate-0.1.0"

- tag_uri: "tag:sunpy.org:ndcube/extra_coords/table_coord/skycoordtablecoordinate-0.1.0"
schema_uri: "asdf://sunpy.org/ndcube/schemas/skycoordtablecoordinate-0.1.0"

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

title:
Represents the ndcube ExtraCoords object

description:
Represents the ndcube ExtraCoords object

type: object
properties:
wcs:
tag: "tag:stsci.edu:gwcs/wcs-1.*"
mapping:
type: array
lookup_tables:
type: array
items:
type: array
items:
- oneOf:
- type: number
- type: array
- oneOf:
- tag: "tag:sunpy.org:ndcube/extra_coords/table_coord/quantitytablecoordinate-0.*"
- tag: "tag:sunpy.org:ndcube/extra_coords/table_coord/skycoordtablecoordinate-0.*"
- tag: "tag:sunpy.org:ndcube/extra_coords/table_coord/timetablecoordinate-0.*"
dropped_tables:
type: array
ndcube:
tag: "tag:sunpy.org:ndcube/ndcube/ndcube-0.*"

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

title:
Represents the ndcube GlobalCoords object

description:
Represents the ndcube GlobalCoords object

type: object
properties:
internal_coords:
type: object
additionalProperties:
type: array
items:
- type: string
- type: object
oneOf:
- tag: "tag:stsci.edu:asdf/unit/quantity-*"
- tag: "tag:astropy.org:astropy/coordinates/skycoord-*"
ndcube:
tag: "tag:sunpy.org:ndcube/ndcube/ndcube-0.*"

required: [ndcube]
additionalProperties: false
...
25 changes: 25 additions & 0 deletions ndcube/asdf/resources/schemas/ndcube-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/NDCube-0.1.0"

title:
Represents the ndcube NDCube object

description:
Represents the ndcube NDCube object

type: object
properties:
data:
description: "Must be compatible with ASDF serialization/deserialization and supported by NDCube."
wcs:
tag: "tag:stsci.edu:gwcs/wcs-1.*"
extra_coords:
tag: "tag:sunpy.org:ndcube/extra_coords/extra_coords/extracoords-0.*"
global_coords:
tag: "tag:sunpy.org:ndcube/global_coords/globalcoords-0.*"

required: [data, wcs]
additionalProperties: true
ViciousEagle03 marked this conversation as resolved.
Show resolved Hide resolved
ViciousEagle03 marked this conversation as resolved.
Show resolved Hide resolved
...
Loading
Loading