Source code for sofia_redux.instruments.exes.submean

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

from astropy import log
import numpy as np

from sofia_redux.instruments.exes import utils

__all__ = ['submean']


[docs] def submean(data, header, flat, illum, order_mask): """ Subtract residual sky background from nod-on-slit data by removing the mean value at each wavelength. For each input frame, the mean value at each spectral column is calculated, using the flat to weight the values, then is subtracted from the data in the column. Input data is assumed to be distortion corrected and rotated to align the spectral axis with the x-axis. Parameters ---------- data : numpy.ndarray 3D data cube [nframe, nspec, nspat] header : fits.Header FITS header associated with the data. flat : numpy.ndarray 2D processed flat [nspec, nspat], as produced by `exes.makeflat`. illum : numpy.ndarray 2D array [nspec, nspat] indicating illuminated regions of the frame. 1=illuminated, 0=unilluminated, -1=pixel that does not correspond to any region in the raw frame. order_mask : numpy.ndarray 2D array [nspat,nspec] indicating the order number for every pixel in the image. For pixels outsize illuminated orders, the value in the order_mask is NaN. Returns ------- corrected: numpy.ndarray Returns the corrected 3D data cube [nspat,nspec,nframe]. """ nx = header.get('NSPAT') ny = header.get('NSPEC') try: nz = utils.check_data_dimensions(data=data, nx=nx, ny=ny) except RuntimeError: log.error(f'Data has wrong dimensions {data.shape}. ' f'Not applying flat') return data if illum is None: illum = np.ones((ny, nx)) else: if illum.ndim != 2 or illum.shape != (ny, nx): log.error(f'Illum array has wrong dimensions ' f'{illum.shape}. Not correcting background.') return data # Get good data from flat/illumination mask good = (illum == 1) & (flat > 0) # Loop over frames log.info('Subtracting mean from each spectral point') corrected = data.copy() for i in range(nz): d = data[i] frame_good = good & ~np.isnan(d) avg = _multi_order_avg(d, flat, frame_good, order_mask) # Note: # We are not propagating variance, assumption is that there # is no error in the average value corrected[i] = data[i] - avg return corrected
def _multi_order_avg(data, flat, good, order_mask): # make flat weights without dividing by zero # note: flat is inverted, so high value = low illumination # and should be low weight weight_frame = flat.copy() weight_frame[~good] = 1. weight_frame = 1 / weight_frame ** 2 weight_frame[~good] = 0. # do a weighted average over y in each order avg = np.zeros_like(data) n_order = np.nanmax(order_mask) for j in range(n_order): order_idx = (order_mask == j + 1) idx = good & order_idx if idx.sum() == 0: continue masked_data = np.ma.MaskedArray(data, mask=~idx) row = np.ma.average(masked_data, weights=weight_frame, axis=0) row = np.ma.filled(row, fill_value=0) avg_order = np.zeros_like(data) avg_order[:] = row avg[order_idx] = avg_order[order_idx] return avg