# Licensed under a 3-clause BSD style license - see LICENSE.rst
from .coordinate_helpers import CoordinateHelper
from .transforms import WCSPixel2WorldTransform
from .utils import coord_type_from_ctype
from .frame import RectangularFrame
from .coordinate_range import find_coordinate_range
[docs]class CoordinatesMap:
"""
A container for coordinate helpers that represents a coordinate system.
This object can be used to access coordinate helpers by index (like a list)
or by name (like a dictionary).
Parameters
----------
axes : :class:`~astropy.visualization.wcsaxes.WCSAxes`
The axes the coordinate map belongs to.
wcs : :class:`~astropy.wcs.WCS`, optional
The WCS for the data. If this is specified, ``transform`` cannot be
specified.
transform : `~matplotlib.transforms.Transform`, optional
The transform for the data. If this is specified, ``wcs`` cannot be
specified.
coord_meta : dict, optional
A dictionary providing additional metadata when ``transform`` is
specified. This should include the keys ``type``, ``wrap``, and
``unit``. Each of these should be a list with as many items as the
dimension of the WCS. The ``type`` entries should be one of
``longitude``, ``latitude``, or ``scalar``, the ``wrap`` entries should
give, for the longitude, the angle at which the coordinate wraps (and
`None` otherwise), and the ``unit`` should give the unit of the
coordinates as :class:`~astropy.units.Unit` instances. This can
optionally also include a ``format_unit`` entry giving the units to use
for the tick labels (if not specified, this defaults to ``unit``).
slice : tuple, optional
For WCS transformations with more than two dimensions, we need to
choose which dimensions are being shown in the 2D image. The slice
should contain one ``x`` entry, one ``y`` entry, and the rest of the
values should be integers indicating the slice through the data. The
order of the items in the slice should be the same as the order of the
dimensions in the :class:`~astropy.wcs.WCS`, and the opposite of the
order of the dimensions in Numpy. For example, ``(50, 'x', 'y')`` means
that the first WCS dimension (last Numpy dimension) will be sliced at
an index of 50, the second WCS and Numpy dimension will be shown on the
x axis, and the final WCS dimension (first Numpy dimension) will be
shown on the y-axis (and therefore the data will be plotted using
``data[:, :, 50].transpose()``)
frame_class : type, optional
The class for the frame, which should be a subclass of
:class:`~astropy.visualization.wcsaxes.frame.BaseFrame`. The default is to use a
:class:`~astropy.visualization.wcsaxes.frame.RectangularFrame`
previous_frame_path : `~matplotlib.path.Path`, optional
When changing the WCS of the axes, the frame instance will change but
we might want to keep re-using the same underlying matplotlib
`~matplotlib.path.Path` - in that case, this can be passed to this
keyword argument.
"""
def __init__(self, axes, wcs=None, transform=None, coord_meta=None,
slice=None, frame_class=RectangularFrame,
previous_frame_path=None):
# Keep track of parent axes and WCS
self._axes = axes
if wcs is None:
if transform is None:
raise ValueError("Either `wcs` or `transform` are required")
if coord_meta is None:
raise ValueError("`coord_meta` is required when "
"`transform` is passed")
self._transform = transform
naxis = 2
else:
if transform is not None:
raise ValueError("Cannot specify both `wcs` and `transform`")
if coord_meta is not None:
raise ValueError("Cannot pass `coord_meta` if passing `wcs`")
self._transform = WCSPixel2WorldTransform(wcs, slice=slice)
naxis = wcs.wcs.naxis
self.frame = frame_class(axes, self._transform, path=previous_frame_path)
# Set up coordinates
self._coords = []
self._aliases = {}
for coord_index in range(naxis):
# Extract coordinate metadata from WCS object or transform
if wcs is not None:
coord_unit = wcs.wcs.cunit[coord_index]
coord_type, format_unit, coord_wrap = coord_type_from_ctype(wcs.wcs.ctype[coord_index])
name = wcs.wcs.ctype[coord_index][:4].replace('-', '')
else:
try:
coord_type = coord_meta['type'][coord_index]
coord_wrap = coord_meta['wrap'][coord_index]
coord_unit = coord_meta['unit'][coord_index]
name = coord_meta['name'][coord_index]
if 'format_unit' in coord_meta:
format_unit = coord_meta['format_unit'][coord_index]
else:
format_unit = None
except IndexError:
raise ValueError("coord_meta items should have a length of {0}".format(len(wcs.wcs.naxis)))
self._coords.append(CoordinateHelper(parent_axes=axes,
parent_map=self,
transform=self._transform,
coord_index=coord_index,
coord_type=coord_type,
coord_wrap=coord_wrap,
coord_unit=coord_unit,
format_unit=format_unit,
frame=self.frame))
# Set up aliases for coordinates
self._aliases[name.lower()] = coord_index
def __getitem__(self, item):
if isinstance(item, str):
return self._coords[self._aliases[item.lower()]]
else:
return self._coords[item]
def __contains__(self, item):
if isinstance(item, str):
return item.lower() in self._aliases
else:
return 0 <= item < len(self._coords)
[docs] def set_visible(self, visibility):
raise NotImplementedError()
def __iter__(self):
for coord in self._coords:
yield coord
[docs] def grid(self, draw_grid=True, grid_type=None, **kwargs):
"""
Plot gridlines for both coordinates.
Standard matplotlib appearance options (color, alpha, etc.) can be
passed as keyword arguments.
Parameters
----------
draw_grid : bool
Whether to show the gridlines
grid_type : { 'lines' | 'contours' }
Whether to plot the contours by determining the grid lines in
world coordinates and then plotting them in world coordinates
(``'lines'``) or by determining the world coordinates at many
positions in the image and then drawing contours
(``'contours'``). The first is recommended for 2-d images, while
for 3-d (or higher dimensional) cubes, the ``'contours'`` option
is recommended. By default, 'lines' is used if the transform has
an inverse, otherwise 'contours' is used.
"""
for coord in self:
coord.grid(draw_grid=draw_grid, grid_type=grid_type, **kwargs)
[docs] def get_coord_range(self):
xmin, xmax = self._axes.get_xlim()
ymin, ymax = self._axes.get_ylim()
return find_coordinate_range(self._transform,
[xmin, xmax, ymin, ymax],
[coord.coord_type for coord in self],
[coord.coord_unit for coord in self])