Skip to content

Commit

Permalink
Revert "Spring cleaning coordinate frames (spacetelescope#457)"
Browse files Browse the repository at this point in the history
This reverts commit 4a5fad4.
  • Loading branch information
WilliamJamieson committed Jan 13, 2025
1 parent 7043544 commit 1b81836
Show file tree
Hide file tree
Showing 19 changed files with 811 additions and 1,161 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name: test
on:
push:
branches:
- '*'
- 'master'
tags:
- '*'
pull_request:
Expand Down
10 changes: 0 additions & 10 deletions CHANGES.rst
Original file line number Diff line number Diff line change
@@ -1,16 +1,6 @@
0.22.0 (2024-12-19)
-------------------

- Coordinate frames now have a "native" order and then are sorted based on ``axes_order``. [#457]

- ``WCS.numerical_inverse`` no longer accepts high level objects (``with_units=`` is not supported) use ``WCS.inverse``. [#457]

- ``CoordinateFrame.coordinates`` has been replaced by ``CoordinateFrame.to_high_level_coordinates`` [#457]

- ``CoordinateFrame.to_quantity`` has been replaced by ``CoordinateFrame.from_high_level_coordinates``. [#457]

- Inputs to ``CelestialFrame``, such as ``axes_names`` are now explicitly in lon, lat order and will re sorted based on ``axes_order=``. [#457]

- Replace usages of ``copy_arrays`` with ``memmap`` [#503]

- Fix an issue with units in ``wcs_from_points``. [#507]
Expand Down
10 changes: 8 additions & 2 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ To install the latest release::

pip install gwcs

The latest release of GWCS is also available as a conda package via `conda-forge <https://github.com/conda-forge/gwcs-feedstock>`__.
The latest release of GWCS is also available as part of `astroconda <https://github.com/astroconda/astroconda>`__.


.. _getting-started:
Expand Down Expand Up @@ -240,7 +240,13 @@ To convert a pixel (x, y) = (1, 2) to sky coordinates, call the WCS object as a
The :meth:`~gwcs.wcs.WCS.invert` method evaluates the :meth:`~gwcs.wcs.WCS.backward_transform`
if available, otherwise applies an iterative method to calculate the reverse coordinates.

GWCS supports the :ref:`wcsapi` which defines several methods to work with high level Astropy objects:
.. doctest-skip::

>>> wcsobj.invert(*sky)
(0.9999999996185807, 1.999999999186798)

GWCS supports the common WCS interface which defines several methods
to work with high level Astropy objects:

.. doctest-skip::

Expand Down
110 changes: 99 additions & 11 deletions gwcs/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,17 @@
"""

from astropy.wcs.wcsapi import BaseLowLevelWCS, HighLevelWCSMixin
from astropy.wcs.wcsapi import BaseHighLevelWCS, BaseLowLevelWCS
from astropy.modeling import separable
import astropy.units as u

from gwcs import utils
from . import utils
from . import coordinate_frames as cf

__all__ = ["GWCSAPIMixin"]


class GWCSAPIMixin(BaseLowLevelWCS, HighLevelWCSMixin):
class GWCSAPIMixin(BaseHighLevelWCS, BaseLowLevelWCS):
"""
A mix-in class that is intended to be inherited by the
:class:`~gwcs.wcs.WCS` class and provides the low- and high-level
Expand Down Expand Up @@ -51,7 +52,14 @@ def world_axis_physical_types(self):
arbitrary string. Alternatively, if the physical type is
unknown/undefined, an element can be `None`.
"""
return self.output_frame.axis_physical_types
# A CompositeFrame orders the output correctly based on axes_order.
if isinstance(self.output_frame, cf.CompositeFrame):
return self.output_frame.axis_physical_types

# If we don't have a CompositeFrame, where this is taken care of for us,
# we need to make sure we re-order the output to match the transform.
# The underlying frames don't reorder themselves because axes_order is global.
return tuple(self.output_frame.axis_physical_types[i] for i in self.output_frame.axes_order)

@property
def world_axis_units(self):
Expand All @@ -67,17 +75,22 @@ def world_axis_units(self):

def _remove_quantity_output(self, result, frame):
if self.forward_transform.uses_quantity:
if frame.naxes == 1:
if self.output_frame.naxes == 1:
result = [result]

result = tuple(r.to_value(unit) if isinstance(r, u.Quantity) else r
for r, unit in zip(result, frame.unit))
result = tuple(r.to_value(unit) for r, unit in zip(result, frame.unit))

# If we only have one output axes, we shouldn't return a tuple.
if self.output_frame.naxes == 1 and isinstance(result, tuple):
return result[0]
return result

def _add_units_input(self, arrays, transform, frame):
if transform.uses_quantity:
return tuple(u.Quantity(array, unit) for array, unit in zip(arrays, frame.unit))

return arrays

def pixel_to_world_values(self, *pixel_arrays):
"""
Convert pixel coordinates to world coordinates.
Expand All @@ -91,7 +104,8 @@ def pixel_to_world_values(self, *pixel_arrays):
order, where for an image, ``x`` is the horizontal coordinate and ``y``
is the vertical coordinate.
"""
result = self._call_forward(*pixel_arrays)
pixel_arrays = self._add_units_input(pixel_arrays, self.forward_transform, self.input_frame)
result = self(*pixel_arrays, with_units=False)

return self._remove_quantity_output(result, self.output_frame)

Expand All @@ -118,7 +132,15 @@ def world_to_pixel_values(self, *world_arrays):
be returned in the ``(x, y)`` order, where for an image, ``x`` is the
horizontal coordinate and ``y`` is the vertical coordinate.
"""
result = self._call_backward(*world_arrays)
try:
backward_transform = self.backward_transform
world_arrays = self._add_units_input(world_arrays,
backward_transform,
self.output_frame)
except NotImplementedError:
pass

result = self.invert(*world_arrays, with_units=False)

return self._remove_quantity_output(result, self.input_frame)

Expand Down Expand Up @@ -247,11 +269,77 @@ def serialized_classes(self):

@property
def world_axis_object_classes(self):
return self.output_frame.world_axis_object_classes
return self.output_frame._world_axis_object_classes

@property
def world_axis_object_components(self):
return self.output_frame.world_axis_object_components
return self.output_frame._world_axis_object_components

# High level APE 14 API

@property
def low_level_wcs(self):
"""
Returns a reference to the underlying low-level WCS object.
"""
return self

def _sanitize_pixel_inputs(self, *pixel_arrays):
pixels = []
if self.forward_transform.uses_quantity:
for i, pixel in enumerate(pixel_arrays):
if not isinstance(pixel, u.Quantity):
pixel = u.Quantity(value=pixel, unit=self.input_frame.unit[i])
pixels.append(pixel)
else:
for i, pixel in enumerate(pixel_arrays):
if isinstance(pixel, u.Quantity):
if pixel.unit != self.input_frame.unit[i]:
raise ValueError('Quantity input does not match the '
'input_frame unit.')
pixel = pixel.value
pixels.append(pixel)

return pixels

def pixel_to_world(self, *pixel_arrays):
"""
Convert pixel values to world coordinates.
"""
pixels = self._sanitize_pixel_inputs(*pixel_arrays)
return self(*pixels, with_units=True)

def array_index_to_world(self, *index_arrays):
"""
Convert array indices to world coordinates (represented by Astropy
objects).
"""
pixel_arrays = index_arrays[::-1]
pixels = self._sanitize_pixel_inputs(*pixel_arrays)
return self(*pixels, with_units=True)

def world_to_pixel(self, *world_objects):
"""
Convert world coordinates to pixel values.
"""
result = self.invert(*world_objects, with_units=True)
if self.input_frame.naxes > 1:
first_res = result[0]
if not utils.isnumerical(first_res):
result = [i.value for i in result]
else:
if not utils.isnumerical(result):
result = result.value

return result

def world_to_array_index(self, *world_objects):
"""
Convert world coordinates (represented by Astropy objects) to array
indices.
"""
result = self.invert(*world_objects, with_units=True)[::-1]
return tuple([utils._toindex(r) for r in result])

@property
def pixel_axis_names(self):
Expand Down
11 changes: 11 additions & 0 deletions gwcs/converters/wcs.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,8 +147,19 @@ def from_yaml_tree(self, node, tag, ctx):
from ..coordinate_frames import SpectralFrame
node = self._from_yaml_tree(node, tag, ctx)

if 'reference_position' in node:
node['reference_position'] = node['reference_position'].upper()

return SpectralFrame(**node)

def to_yaml_tree(self, frame, tag, ctx):
node = self._to_yaml_tree(frame, tag, ctx)

if frame.reference_position is not None:
node['reference_position'] = frame.reference_position.lower()

return node


class CompositeFrameConverter(FrameConverter):
tags = ["tag:stsci.edu:gwcs/composite_frame-*"]
Expand Down
Loading

0 comments on commit 1b81836

Please sign in to comment.