Source code for pdrtpy.utils.wcs
"""
WCS and image array utilities for PDR Toolbox.
"""
import numpy as np
from pdrtpy.utils.units import draine_unit, get_rad, habing_unit, is_rad
[docs]
def mask_union(arrays):
"""Return the union mask (logical OR) of the input masked arrays.
This is useful when doing arithmetic on images that don't have identical
masks and you want the most restrictive mask.
Parameters
----------
arrays : :class:`numpy.ma.masked_array`
Masked arrays to unionize.
Returns
-------
mask
"""
z = list()
for m in arrays:
z.append(m.mask)
return np.any(z, axis=0)
[docs]
def dropaxis(w):
"""Drop the first single dimension axis from a World Coordinate System.
Returns the modified WCS if it had a single dimension axis or the
original WCS if not.
Parameters
----------
w : :class:`astropy.wcs.WCS`
A WCS.
Returns
-------
:class:`astropy.wcs.WCS`
"""
for i in range(len(w._naxis)):
if w._naxis[i] == 1:
return w.dropaxis(i)
return w
[docs]
def has_single_axis(w):
"""Check if the input WCS has any single dimension axes.
Parameters
----------
w : :class:`astropy.wcs.WCS`
A WCS.
Returns
-------
bool
True if the input WCS has any single dimension axes, False otherwise.
"""
for i in range(len(w._naxis)):
if w._naxis[i] == 1:
return True
return False
[docs]
def squeeze(image):
"""Remove single-dimensional entries from image data and WCS.
Parameters
----------
image : :class:`astropy.nddata.CCDData` or :class:`~pdrtpy.measurement.Measurement`
The image to convert. It must have a :class:`numpy.ndarray` data member
and :class:`astropy.units.Unit` unit member.
Returns
-------
:class:`astropy.nddata.CCDData` or :class:`~pdrtpy.measurement.Measurement`
An image with single axes removed (same type as input).
"""
while has_single_axis(image.wcs):
image.wcs = dropaxis(image.wcs)
# np.squeeze is a no-op if there are no dimensions to squeeze
if image.data is not None:
image.data = np.squeeze(image.data)
if image.uncertainty is not None:
image.uncertainty.array = np.squeeze(image.uncertainty.array)
if image.mask is not None:
image.mask = np.squeeze(image.mask)
# update the header which can be independent of WCS
image.header["NAXIS"] = image.wcs.wcs.naxis
i = image.wcs.wcs.naxis + 1
nax = "NAXIS" + str(i)
while image.header.pop(nax, None) is not None:
i = i + 1
nax = "NAXIS" + str(i)
return image
[docs]
def fliplabel(label):
"""Given a label with a numerator and denominator separated by ``'/'``, return the reciprocal label.
For example, if the input label is ``'(x+y)/z'`` return ``'z/(x+y)'``. This
method simply looks for the ``'/'`` and swaps the substrings before and after it.
Parameters
----------
label : str
The label to flip.
Returns
-------
str
The reciprocal label.
Raises
------
ValueError
If the input label has no ``'/'``.
"""
ii = label.index("/")
return label[ii + 1 :] + "/" + label[0:ii]
[docs]
def get_xy_from_wcs(data, quantity=False, linear=False):
"""Get the x,y axis vectors from the WCS of the input image.
Parameters
----------
data : :class:`astropy.io.fits.ImageHDU`, :class:`astropy.nddata.CCDData`, or :class:`~pdrtpy.measurement.Measurement`
The input image.
quantity : bool, optional
If True, return the arrays as :class:`astropy.units.Quantity`.
If False, return :class:`numpy.ndarray`. Default: False.
linear : bool, optional
If True, returned arrays are in linear space; if False, in log space. Default: False.
Returns
-------
:class:`numpy.ndarray` or :class:`astropy.units.Quantity`
The axis values as arrays. Values are center of pixel.
"""
w = data.wcs
if w is None:
raise Exception("No WCS in the input image")
xind = np.arange(w._naxis[0])
yind = np.arange(w._naxis[1])
x_pixel_arrays = [xind, xind]
y_pixel_arrays = [yind, yind]
for _i in range(2, w.pixel_n_dim):
x_pixel_arrays.append([0])
y_pixel_arrays.append([0])
if quantity:
x = w.array_index_to_world(*x_pixel_arrays)[0]
y = w.array_index_to_world(*y_pixel_arrays)[1]
# Need to handle Habing or Draine units which are non-standard FITS.
# Can't apply them to a WCS because it will raise an Exception.
# See ModelSet.get_model
cunit = data.header.get("CUNIT2", None)
if cunit == "Habing":
y._unit = habing_unit
if cunit == "Draine":
y._unit = draine_unit
if linear:
j = 10 * np.ones(len(x.value))
k = 10 * np.ones(len(y.value))
# ugh we are depending on CTYPE being properly indicated as log(whatever)
if "log" in w.wcs.ctype[0].lower():
x = np.power(j, x.value) * x.unit
if "log" in w.wcs.ctype[1].lower():
y = np.power(k, y.value) * y.unit
else:
x = w.array_index_to_world_values(*x_pixel_arrays)[0]
y = w.array_index_to_world_values(*y_pixel_arrays)[1]
if linear:
j = 10 * np.ones(len(x))
k = 10 * np.ones(len(y))
if "log" in w.wcs.ctype[0].lower():
x = np.power(j, x)
if "log" in w.wcs.ctype[1].lower():
y = np.power(k, y)
return (x, y)
[docs]
def rescale_axis_units(x, from_unit, from_ctype, to_unit, loglabel=True):
"""Rescale axis units and return updated axis values and label.
Parameters
----------
x : :class:`astropy.units.Quantity`
Axis values.
from_unit : str
Original unit string.
from_ctype : str
Original CTYPE string.
to_unit : str or None
Target unit string, or None to keep original.
loglabel : bool, optional
If True, prefix label with ``'log(...)'``. Default: True.
Returns
-------
tuple
``(x, xlabel)`` with rescaled axis values and axis label string.
"""
import astropy.units as u
xax_unit = u.Unit(from_unit)
# cover the case where we had to erase the wcs unit to avoid FITS error
if x._unit is None or x._unit is u.dimensionless_unscaled:
x._unit = xax_unit
if is_rad(xax_unit):
if loglabel:
xtype = f"log({get_rad(xax_unit)})"
else:
xtype = f"{get_rad(xax_unit)}"
elif loglabel and "log" in from_ctype:
if "_" in from_ctype:
xtype = r"${\rm " + from_ctype + "}$"
else:
xtype = from_ctype
else:
xtype = from_ctype.replace("log(", "").replace(")", "")
if "_" in xtype:
xtype = r"${\rm " + xtype + "}$"
if to_unit is not None:
xax_unit = u.Unit(to_unit)
if is_rad(to_unit):
if loglabel:
xtype = f"log({get_rad(xax_unit)})"
else:
xtype = f"{get_rad(xax_unit)}"
elif loglabel and "log" in from_ctype:
xtype = from_ctype
else:
xtype = from_ctype.replace("log(", "").replace(")", "")
x = x.to(xax_unit)
xlabel = rf"{xtype} [{xax_unit:latex_inline}]"
return (x, xlabel)