Source code for sofia_redux.spectroscopy.findapertures

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

from astropy.stats import gaussian_fwhm_to_sigma, gaussian_sigma_to_fwhm
import numpy as np

from sofia_redux.toolkit.fitting.fitpeaks1d import fitpeaks1d
from sofia_redux.toolkit.interpolate import tabinv

__all__ = ['find_apertures']


[docs] def find_apertures(profiles, npeaks=1, orders=None, positions=None, fwhm=1.0, fix=False, **kwargs): """ Determine the position of the aperture(s) in a spatial profile. Profiles expected are the median profiles produced by `sofia_redux.spectroscopy.mkspatprof`. Peaks are fit using a Gaussian model, with initial estimates optionally provided by the user. Any additional keyword arguments provided are passed to the `sofia_redux.toolkit.fitting.fitpeaks1d` algorithm, used to fit the Gaussian model to the profile. Parameters ---------- profiles : dict order (int) -> profile (numpy.ndarray) (n_spatial, 2) spatial profile where profile[:, 0] = spatial coordinate and profile[:, 1] = median spatial profile. npeaks : int, optional Number of peaks to find in each profile. orders : list of int, optional Orders to extract. If not present, all orders in `profiles` will be extracted. positions : dict, optional order (int) -> list of float Up to (npeaks) positions to use as the starting point(s) for the fit. fwhm : float, optional Starting estimate for Gaussian FWHM of the spatial peak, in arcsec up the slit. fix : bool, optional If set, apertures will be fixed to the input positions. If input positions are not specified, they will be fixed to the center of the slit. Returns ------- apertures : dict order (int) -> list of dict Keys and values are as follows: position : float fwhm : float sign : {1, -1} """ apertures = {} if orders is None: orders = np.unique(list(profiles.keys())).astype(int) else: orders = np.unique(orders).astype(int) # Gaussian width from FHWM sigma = gaussian_fwhm_to_sigma * fwhm for order in orders: # x and y values: slit position vs. normalized profile value x = profiles[order][0] y = profiles[order][1] dx = np.mean(x[1:] - x[:-1]) # guess positions if provided if positions is None or order not in positions: guess = None else: guess = positions[order] if fix: # if positions not specified, divide up the slit by npeaks if guess is None: guess = [] dx = len(x) / (npeaks + 1) for i in range(1, npeaks + 1): idx = int(np.round(i * dx)) guess.append(x[idx]) # set the guess positions as the apertures # with the given FWHM and a sign derived from the profile aplist = [] for pos in guess: sign = 1 if y[int(tabinv(x, pos))] > 0 else -1 aplist.append({'position': pos, 'fwhm': fwhm, 'sign': sign}) else: # find highest peaks in the profile # bounds on fit position and FHWM: keep inside the slit bounds = {} for i in range(npeaks): bounds['mean_{}'.format(i)] = (x[0], x[-1]) bounds['stddev_{}'.format(i)] = (dx, x[-1]) fit_peaks = fitpeaks1d(x, y, npeaks=npeaks, guess=guess, stddev=sigma, bounds=bounds, **kwargs) # assuming the npeaks are the first n models in the output; # background fit is last aplist = [] for i in range(npeaks): # fit position fit_pos = fit_peaks[i].mean.value # fit FHWM fit_fwhm = fit_peaks[i].stddev.value * gaussian_sigma_to_fwhm # sign at fit position sign = 1 if y[int(tabinv(x, fit_pos))] > 0 else -1 aplist.append({'position': fit_pos, 'fwhm': fit_fwhm, 'sign': sign}) apertures[order] = sorted(aplist, key=lambda j: j['position']) return apertures