Customizing the coordinate system of a data object¶
Background¶
Data objects represented by the Data
class can have
a coordinate system defined, for display and/or linking purposes. This
coordinate system is defined in the .coords
attribute of data objects.
By default the coords
object for Data
objects
created manually is None
unless you explicitly specify coords=
when
creating the data object. For data objects returned by data loaders, whether
coords
is set or not will depend on the particular file format. For example
if we use the glue loaders to read in an example file:
>>> from glue.core.data_factories import load_data
>>> from glue.utils import require_data
>>> require_data('Astronomy/W5/w5.fits')
Successfully downloaded data file to w5.fits
>>> data = load_data('w5.fits')
the resulting coords
object has methods to convert between pixel coordinates
and so-called ‘world’ or ‘physical’ coordinates:
>>> data.coords.pixel_to_world_values(2, 3)
(array(46.34527244), array(58.85867558))
>>> data.coords.world_to_pixel_values(46.3, 58.9)
(array(10.39880029), array(16.44193896))
If not None
, the coords
attribute will be an object exposing the two
above methods as well as other useful methods and properties related to
coordinate transformations. The programmatic interface we have adopted for
coords
objects is described in A shared Python interface for World Coordinate Systems (while originally
defined by the Astropy project, this is very general and not astronomy-specific).
Any object implementing that API can be used as a coordinate object and will
integrate with the rest of glue.
A number of convenience coordinate classes are available in glue for common cases, and it is also possible to define your own (both options are described in the next sections).
Affine coordinates¶
The most common cases of transformation between pixel and world coordinates are affine transformations, which can represent combinations of e.g. reflections, scaling, translations, rotations, and shear. A common way of representing an affine transformations is through an augmented matrix, which has shape N+1 x N+1, where N is the number of pixel and world dimensions.
Glue provides an AffineCoordinates
class for
representing arbitrary affine transformations:
>>> from glue.core.coordinates import AffineCoordinates
To initialize it, you will need to provide an augmented matrix, and optionally lists of units and axis names (as strings). For example, to construct an affine transformation where the x and y coordinates are each doubled, you would do:
>>> import numpy as np
>>> matrix = np.array([[2, 0, 0], [0, 2, 0], [0, 0, 1]])
>>> affine_coords = AffineCoordinates(matrix, units=['m', 'm'], labels=['xw', 'yw'])
To use a custom coordinate system, when creating a data object you should specify
the coordinates object via the coords=
keyword argument:
>>> from glue.core import Data
>>> data_double = Data(x=[[1, 2], [3, 4]], coords=affine_coords)
>>> data_double.coords.pixel_to_world_values(2, 1)
(4.0, 2.0)
>>> data_double.coords.world_to_pixel_values(4.0, 2.0)
(2.0, 1.0)
Identity coordinates¶
A special/simple case of coordinate transformation is the identity transform,
where world coordinates are the same as pixel coordinates. Glue provides an
IdentityCoordinates
class for representing
this transformation:
>>> from glue.core.coordinates import IdentityCoordinates
To initialize it, you will need to specify the number of dimensions in the data:
>>> data_ident = Data(x=[1, 2, 3], coords=IdentityCoordinates(n_dim=1))
>>> data_ident.coords.pixel_to_world_values(2, 1)
(2, 1)
>>> data_ident.coords.world_to_pixel_values(4.0, 2.0)
(4.0, 2.0)
Custom coordinates¶
If you want to define a fully customized coordinate transformation, we
provide a Coordinates
class that you can
start from to make things easier. The only required methods in this case
are the following:
from glue.core.coordinates import Coordinates
class MyCoordinates(Coordinates):
def pixel_to_world_values(self, *args):
# This should take N arguments (where N is the number of dimensions
# in your dataset) and assume these are 0-based pixel coordinates,
# then return N world coordinates with the same shape as the input.
def world_to_pixel_values(self, *args):
# This should take N arguments (where N is the number of dimensions
# in your dataset) and assume these are 0-based pixel coordinates,
# then return N world coordinates with the same shape as the input.
In addition, you can also optionally specify units and names for all world coordinates with the two following properties:
@property
def world_axis_units(self):
# Returns an iterable of strings given the units of the world
# coordinates for each axis.
@property
def world_axis_names(self):
# Returns an iterable of strings given the names of the world
# coordinates for each axis.
For example, let’s consider a coordinate system where the world coordinates are simply scaled by a factor of two compared to the pixel coordinates. The minimal class implementing this would look like:
>>> from glue.core.coordinates import Coordinates
>>> class DoubleCoordinates(Coordinates):
...
... def pixel_to_world_values(self, *args):
... return tuple([2.0 * x for x in args])
...
... def world_to_pixel_values(self, *args):
... return tuple([0.5 * x for x in args])
To use a custom coordinate system, when creating a data object you should specify
the coordinates object via the coords=
keyword argument:
>>> data_double = Data(x=[1, 2, 3], coords=DoubleCoordinates(n_dim=1))
>>> data_double.coords.pixel_to_world_values(2)
(4.0,)
>>> data_double.coords.world_to_pixel_values(4.0)
(2.0,)
Note that the n_dim=
argument needs to be passed to give the number of
dimensions in the data.
In fact you do not need to start from our Coordinates
class - any class that conforms to the API described in
A shared Python interface for World Coordinate Systems is valid. If
you want full control over your coordinate transformations, we recomment you
take a look at that document.