Source code for sofia_redux.spectroscopy.mkapmask

# Licensed under a 3-clause BSD style license - see LICENSE.rst

from astropy import log
import numpy as np
from sofia_redux.toolkit.interpolate import tabinv

__all__ = ['mkapmask']


[docs] def mkapmask(slit, wave, apertures, background=None): """ Constructs a 2D aperture mask. First, the mask is set to zero for all values. Then, the backgrounds are set to NaN without regard to the apertures. Then, for each aperture, the 'trace' value +/- the 'psf_radius' value is set to the aperture number. The full PSF apertures are indexed to apnum, inner aperture radii to -apnum. PSF radius is required, aperture radius is optional. It is assumed that the PSF radius is larger than the aperture radius if present. The edge pixels for the PSF aperture are set to reflect their fractional pixel values. To determine where the full aperture n is: z = (abs(mask) > n-1) & (abs(mask) <= n) Inner aperture radii are set to whole pixels only. To determine the central region of the aperture n: z = (mask == -1 * n) Parameters ---------- slit : array_like of float (n_values,) 1D array of slit position values (e.g. pixels, arcseconds). wave : array_like of float (n_values,) 1D array of wavelength coordinate values (e.g. um). apertures : list of dict Required keys and values for the dictionaries are: trace : float aperture_radius : float psf_radius : float apertures : list of dict Required keys and values for the dictionaries are: trace : float aperture_radius : float psf_radius : float background : list of list, optional Each element should be a [start, stop] pair of spatial coordinates indicating a background region. Returns ------- numpy.ndarray (ns, nw) array of int """ if not hasattr(slit, '__len__'): slit = [slit] slit = np.array(slit).astype(float) # mask starts as zero -- unused pixels will remain zero mask = np.zeros((slit.size, wave.size)) # set background to nan if background is not None: regions = np.round(tabinv(slit, background)).astype(int) for region in regions: if len(region) == 2: mask[region[0]: region[1] + 1, :] = np.nan for api, aperture in enumerate(apertures): pos = aperture['trace'] # define PSF aperture rad = aperture['psf_radius'] ap = np.array([pos - rad, pos + rad]) # this gets the effective index for aperture position # in the slit array, clipping to 0 at the lower edge, # and len(slit)-1 at the upper apidxs = tabinv(slit, ap) # define aperture radius, if available if 'aperture_radius' in aperture: aprad = aperture['aperture_radius'] apradpos = np.array([pos - aprad, pos + aprad]) aprad_idxs = tabinv(slit, apradpos) else: aprad_idxs = None for wavei, apidx in enumerate(apidxs.T): # this takes the floor of the identified indices, # so may include a fractional pixel on the lower edge, # and will miss any fractional pixels on the upper edge apint = apidx.astype(int) # check for overlap with previous aperture: # identified pixels must be either nan or 0 maxap = apint[1] + 2 if apint[1] < len(slit) - 2 else len(slit) test = mask[apint[0]:maxap, wavei] if not np.all(np.isnan(test) | (test == 0)): msg = "The extraction apertures overlap. " \ "Please lower the aperture radii." log.error(msg) raise ValueError(msg) # set values to aperture number mask[apint[0]:apint[1] + 1, wavei] = api + 1 # fix endpoints to reflect fractional pixels # Note that this assumes aperture widths are greater than # pixel widths. dap = apidx - apint if dap[0] > 0: # correct the first point to a fractional weight # (weight for extraction is mask - api, so full # pixels have weight 1) mask[apint[0], wavei] = api + dap[0] if dap[1] > 0: # add the next point up the slit to the aperture, # with a fractional weight mask[apint[1] + 1, wavei] = api + dap[1] # define aperture radius, if available if aprad_idxs is not None: # for the central aperture region, use only whole pixels apidx = aprad_idxs.T[wavei] apint[0] = int(np.ceil(apidx[0])) apint[1] = int(np.floor(apidx[1])) # set value to -1 * aperture number mask[apint[0]:apint[1] + 1, wavei] *= -1 return mask