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:

>>> from glue.core import Data
>>> data = Data(x=[1, 2, 3])
>>> data.coords
<glue.core.coordinates.Coordinates object at 0x7fa52f5547b8>

This attribute can be used to convert pixel to so-called ‘world’ coordinates and vice-versa:

>>> data.coords.pixel2world(2)
(2,)
>>> data.coords.world2pixel(3)
(3,)

By default the coords object for Data objects created manually is an instance of Coordinates which is an identity transform, as can be seen above. However, it is possible to use other coordinate systems or define your own.

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 a 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:

>>> data_double = Data(x=[1, 2, 3], coords=affine_coords)
>>> data_double.coords.pixel2world(2, 1)
[array(4.), array(2.)]
>>> data_double.coords.world2pixel(4.0, 2.0)
[array(2.), array(1.)]

Custom coordinates

If you want to define a fully customized coordinate transformation, you will need to either define a Coordinates subclass with the following methods:

from glue.core.coordinates import Coordinates


class MyCoordinates(Coordinates):

    def pixel2world(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 world2pixel(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_axis_unit(self, axis):
        # For a given axis (0-based) return the units of the world
        # coordinate as a string. This is optional and will return '' by
        # default if not defined.

    def axis_label(self, axis):
        # For a given axis (0-based) return the name of the world
        # coordinate as a string. This is optional and will return
        # 'World {axis}' by default if not defined.

    def dependent_axes(self, axis):
        # This should return a tuple of all the world dimensions that are
        # correlated with the specified pixel axis. As an example, for a
        # 2-d coordinate system rotated compared to the pixel coordinates,
        # both world coordinates depend on both pixel coordinates, so this
        # should return (0, 1). If all axes are independent, then this
        # should return (axis,) (the default implementation)

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 pixel2world(self, *args):
...        return tuple([2.0 * x for x in args])
...
...     def world2pixel(self, *args):
...        return ([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())
>>> data_double.coords.pixel2world(2)
(4.0,)
>>> data_double.coords.world2pixel(4.0)
[2.0]