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

Ravel model for the illusion of 2D tables #227

Merged
merged 32 commits into from
Mar 28, 2023
Merged
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
3c19477
Add Ravel and Unravel models
SolarDrew Feb 16, 2023
36b4643
Add very basic tests for ravel and unravel models
SolarDrew Feb 16, 2023
b2f1ccf
Combine ravel and unravel tests into one
SolarDrew Feb 17, 2023
a764bc4
Init Ravel/Unravel classes directly in inverse methods
SolarDrew Feb 17, 2023
2d7278e
Input in first dimension should be rounded to the nearest integer
SolarDrew Feb 23, 2023
15da828
Expand ravel/unravel test and add test for compound with Tabular1D
SolarDrew Feb 23, 2023
5f284f7
Add schema for Ravel model
SolarDrew Feb 28, 2023
82b1861
Add new wcs manifest
SolarDrew Mar 1, 2023
80d8633
Add ASDF converter for Ravel model
SolarDrew Mar 1, 2023
df85cfa
Tests don't like tuple in the schema apparently.
SolarDrew Mar 6, 2023
7b1ebdf
Doesn't like required either. Try unindenting it
SolarDrew Mar 6, 2023
97c23f8
Need to use the properties of the actual model
SolarDrew Mar 7, 2023
5204861
Required needs to be a list apparently
SolarDrew Mar 7, 2023
90b83b2
isort
SolarDrew Mar 7, 2023
60d45d2
Add missing blank line
SolarDrew Mar 7, 2023
b574a7b
Merge branch 'ravel-model' of github.com:SolarDrew/dkist into ravel-m…
SolarDrew Mar 7, 2023
100258f
Correct manifest version numbers
SolarDrew Mar 7, 2023
f1f72a8
Don't need .yaml in these apparently, plus remove debug print
SolarDrew Mar 9, 2023
4afd2fe
Fix various bits of importing
SolarDrew Mar 9, 2023
0e7a397
Add test for ravel converter
SolarDrew Mar 9, 2023
b03b593
Couple of typos
SolarDrew Mar 10, 2023
9355136
Cast Ravel array shape to tuple to make the converter test pass
SolarDrew Mar 10, 2023
c6e7a91
Add changelog
SolarDrew Mar 10, 2023
505328d
isort
SolarDrew Mar 10, 2023
dbb7d26
isort hates me
SolarDrew Mar 10, 2023
5755ef7
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Mar 10, 2023
00392d8
Add separable
Cadair Mar 20, 2023
8e398dc
Add order selection to Ravel Unravel
Cadair Mar 21, 2023
486fec2
more sep
Cadair Mar 21, 2023
269579e
Add test for C and Fortran ordering
SolarDrew Mar 24, 2023
8e351f4
Add tests for repr functions
SolarDrew Mar 28, 2023
feed4a9
Apply suggestions from code review
Cadair Mar 28, 2023
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
3 changes: 3 additions & 0 deletions changelog/227.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Add a new model to take a 2D index and return the corresponding correct index for a 1D array, and the inverse model for the reverse operation.
To be used as a compound with Tabular1D so that it looks like a Tabular2D but the compound model can still be inverted.
This will be relevant and useful in the dkist-inventory package but users are unlikely to need to use it.
2 changes: 1 addition & 1 deletion dkist/io/asdf/converters/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from .dataset import DatasetConverter
from .file_manager import FileManagerConverter
from .models import CoupledCompoundConverter, VaryingCelestialConverter
from .models import CoupledCompoundConverter, RavelConverter, VaryingCelestialConverter
from .tiled_dataset import TiledDatasetConverter
20 changes: 20 additions & 0 deletions dkist/io/asdf/converters/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,3 +115,23 @@ def from_yaml_tree_transform(self, node, tag, ctx):
shared_inputs=node["shared_inputs"])

return model


class RavelConverter(TransformConverterBase):
"""
ASDF serialization support for Ravel
"""

tags = [
"asdf://dkist.nso.edu/tags/ravel_model-1.0.0"
]

types = ["dkist.wcs.models.Ravel"]

def to_yaml_tree_transform(self, model, tag, ctx):
return {"array_shape": model.array_shape, "order": model.order}

def from_yaml_tree_transform(self, node, tag, ctx):
from dkist.wcs.models import Ravel

return Ravel(node["array_shape"], order=node["order"])
11 changes: 7 additions & 4 deletions dkist/io/asdf/entry_points.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@
from asdf.extension import ManifestExtension
from asdf.resource import DirectoryResourceMapping

from dkist.io.asdf.converters import (CoupledCompoundConverter, DatasetConverter,
FileManagerConverter, RavelConverter, TiledDatasetConverter,
VaryingCelestialConverter)

if sys.version_info < (3, 9):
import importlib_resources
else:
import importlib.resources as importlib_resources

from dkist.io.asdf.converters import (CoupledCompoundConverter, DatasetConverter,
FileManagerConverter, TiledDatasetConverter,
VaryingCelestialConverter)


def get_resource_mappings():
Expand Down Expand Up @@ -42,12 +43,14 @@ def get_extensions():
Get the list of extensions.
"""
dkist_converters = [FileManagerConverter(), DatasetConverter(), TiledDatasetConverter()]
wcs_converters = [VaryingCelestialConverter(), CoupledCompoundConverter()]
wcs_converters = [VaryingCelestialConverter(), CoupledCompoundConverter(), RavelConverter()]
return [
ManifestExtension.from_uri("asdf://dkist.nso.edu/manifests/dkist-1.1.0",
converters=dkist_converters),
ManifestExtension.from_uri("asdf://dkist.nso.edu/manifests/dkist-1.0.0",
converters=dkist_converters),
ManifestExtension.from_uri("asdf://dkist.nso.edu/manifests/dkist-wcs-1.1.0",
converters=wcs_converters),
ManifestExtension.from_uri("asdf://dkist.nso.edu/manifests/dkist-wcs-1.0.0",
converters=wcs_converters),
# This manifest handles all pre-refactor tags
Expand Down
20 changes: 20 additions & 0 deletions dkist/io/asdf/resources/manifests/dkist-wcs-1.1.0.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
%YAML 1.1
---
id: asdf://dkist.nso.edu/dkist/manifests/dkist-wcs-1.1.0
extension_uri: asdf://dkist.nso.edu/dkist/extensions/dkist-wcs-1.1.0
title: DKIST WCS extension
description: ASDF schemas and tags for models and WCS related classes.

tags:
- schema_uri: "asdf://dkist.nso.edu/schemas/varying_celestial_transform-1.0.0"
tag_uri: "asdf://dkist.nso.edu/tags/varying_celestial_transform-1.0.0"
- schema_uri: "asdf://dkist.nso.edu/schemas/varying_celestial_transform-1.0.0"
tag_uri: "asdf://dkist.nso.edu/tags/inverse_varying_celestial_transform-1.0.0"
- schema_uri: "asdf://dkist.nso.edu/schemas/coupled_compound_model-1.0.0"
tag_uri: "asdf://dkist.nso.edu/tags/coupled_compound_model-1.0.0"
- schema_uri: "asdf://dkist.nso.edu/schemas/varying_celestial_transform-1.0.0"
tag_uri: "asdf://dkist.nso.edu/tags/varying_celestial_transform_slit-1.0.0"
- schema_uri: "asdf://dkist.nso.edu/schemas/varying_celestial_transform-1.0.0"
tag_uri: "asdf://dkist.nso.edu/tags/inverse_varying_celestial_transform_slit-1.0.0"
- schema_uri: "asdf://dkist.nso.edu/schemas/ravel_model-1.0.0"
tag_uri: "asdf://dkist.nso.edu/tags/ravel_model-1.0.0"
16 changes: 16 additions & 0 deletions dkist/io/asdf/resources/schemas/ravel_model-1.0.0.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
%YAML 1.1
---
$schema: "http://stsci.edu/schemas/yaml-schema/draft-01"
id: "asdf://dkist.nso.edu/schemas/ravel_model-1.0.0"

title: A model to flatten 2D indices into 1D
description:
A model which takes a pair of indices and flattens them into the corresponding index for a 1D array. This can be used as a compound with Tabular1D to enable it to be indexed as if it were Tabular2D.

type: object
properties:
array_shape:
type: array
order:
type: string
required: [array_shape, order]
16 changes: 13 additions & 3 deletions dkist/io/asdf/tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@
from dkist.wcs.models import (CoupledCompoundModel, InverseVaryingCelestialTransform,
InverseVaryingCelestialTransform2D,
InverseVaryingCelestialTransformSlit,
InverseVaryingCelestialTransformSlit2D, VaryingCelestialTransform,
VaryingCelestialTransform2D, VaryingCelestialTransformSlit,
VaryingCelestialTransformSlit2D)
InverseVaryingCelestialTransformSlit2D, Ravel, Unravel,
VaryingCelestialTransform, VaryingCelestialTransform2D,
VaryingCelestialTransformSlit, VaryingCelestialTransformSlit2D)


def test_roundtrip_vct():
Expand Down Expand Up @@ -122,3 +122,13 @@ def test_coupled_compound_model_nested():

assert ccm.n_inputs == new.n_inputs
assert ccm.inputs == new.inputs


def test_ravel_model():
ravel = Ravel((10, 10))
new = roundtrip_object(ravel)

assert isinstance(new, Ravel)
assert isinstance(new.inverse, Unravel)

assert new.array_shape == ravel.array_shape
54 changes: 53 additions & 1 deletion dkist/wcs/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
__all__ = ['CoupledCompoundModel',
'InverseVaryingCelestialTransform',
'VaryingCelestialTransform',
'BaseVaryingCelestialTransform']
'BaseVaryingCelestialTransform',
'Ravel']


def generate_celestial_transform(crpix: Union[Iterable[float], u.Quantity],
Expand Down Expand Up @@ -733,3 +734,54 @@ def varying_celestial_transform_from_tables(crpix: Union[Iterable[float], u.Quan
lon_pole=lon_pole,
projection=projection
)


class Ravel(Model):
array_shape = (0, 0)
n_inputs = 2
n_outputs = 1
_separable = False

def __init__(self, array_shape, order='C', **kwargs):
super().__init__(**kwargs)

self.array_shape = tuple(array_shape)
self.order = order # TODO: Validate order is either C or F

def evaluate(self, *inputs):
ravel_shape = self.array_shape[1]
if self.order == 'F':
inputs = inputs[::-1]
ravel_shape = self.array_shape[0]
return (np.round(inputs[0]) * ravel_shape) + inputs[1]

@property
def inverse(self):
return Unravel(self.array_shape, order=self.order)

def __repr__(self):
return f"<Ravel(array_shape={self.array_shape}, order={self.order})>"


class Unravel(Model):
array_shape = (0, 0)
n_inputs = 1
n_outputs = 2
_separable = False

def __init__(self, array_shape, order='C', **kwargs):
super().__init__(**kwargs)

self.array_shape = array_shape
self.order = order # TODO: Validate order is either C or F

def evaluate(self, input_):
i = 1 if self.order == 'C' else 0
return (input_ // self.array_shape[i], input_ % self.array_shape[i])

@property
def inverse(self):
return Ravel(self.array_shape)

def __repr__(self):
return f"<Unravel(array_shape={self.array_shape}, order={self.order})>"
67 changes: 66 additions & 1 deletion dkist/wcs/tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@
import astropy.units as u
from astropy.coordinates.matrix_utilities import rotation_matrix
from astropy.modeling import CompoundModel
from astropy.modeling.models import Tabular1D

from dkist.wcs.models import (VaryingCelestialTransform, VaryingCelestialTransform2D,
from dkist.wcs.models import (Ravel, VaryingCelestialTransform, VaryingCelestialTransform2D,
VaryingCelestialTransformSlit, VaryingCelestialTransformSlit2D,
generate_celestial_transform,
varying_celestial_transform_from_tables)
Expand Down Expand Up @@ -371,3 +372,67 @@ def test_vct_slit2d_unitless():
world = vct_slit(*pixel)
ipixel = vct_slit.inverse(*world, 0, 0)
assert u.allclose(ipixel, pixel[0], atol=1e-5)


@pytest.mark.parametrize("array_shape",
[(i, 100 // i) for i in range(2, 21)])
def test_ravel_model(array_shape):
ravel = Ravel(array_shape)

# Make 10 attempts with random numbers
for _ in range(10):
x, y = np.random.random() * (array_shape[0]-1), np.random.random() * array_shape[1]
expected_val = ((round(x) * array_shape[1]) + y)
assert ravel(x, y) == expected_val
assert np.allclose(ravel.inverse(expected_val), (round(x), y))
assert ravel.inverse.inverse(x, y) == expected_val


@pytest.mark.parametrize("array_shape",
[(i, 100 // i) for i in range(2, 21)])
def test_raveled_tabular1d(array_shape):
values = np.arange(100)

ravel = Ravel(array_shape)
tabular = Tabular1D(values,
values*u.nm,
bounds_error=False,
fill_value=np.nan,
method="linear")

raveled_tab = ravel | tabular

# Make 10 attempts with random numbers
for _ in range(10):
x, y = (np.random.random() * (array_shape[0]-1),
np.random.random() * (array_shape[1]-1))
expected_val = ((round(x) * array_shape[1]) + y) * u.nm
assert raveled_tab(x, y) == expected_val
assert np.allclose(raveled_tab.inverse(expected_val), (round(x), y))
assert raveled_tab.inverse.inverse(x, y) == expected_val


@pytest.mark.parametrize("array_shape",
[(i, 100 // i) for i in range(2, 21)])
@pytest.mark.parametrize("order", ["C", "F"])
def test_ravel_ordering(array_shape, order):
ravel = Ravel(array_shape, order=order)

values = np.arange(array_shape[0]*array_shape[1]).reshape(array_shape, order=order)

# Make 10 attempts with random numbers
for _ in range(10):
x, y = (np.random.randint((array_shape[0]-1)),
np.random.randint((array_shape[1]-1)))
assert ravel(x, y) == values[x, y]


@pytest.mark.parametrize("array_shape",
[(i, 100 // i) for i in range(2, 21)])
@pytest.mark.parametrize("order", ["C", "F"])
def test_ravel_repr(array_shape, order):
ravel = Ravel(array_shape, order=order)
unravel = ravel.inverse

assert str(array_shape) in repr(ravel) and order in repr(ravel)
assert str(array_shape) in repr(unravel) and order in repr(unravel)