Source code for species.util.spec_util

"""
Utility functions for manipulating spectra.
"""

import warnings

from math import ceil
from typing import Tuple, Union

import numpy as np

from scipy.ndimage import gaussian_filter
from typeguard import typechecked


[docs] @typechecked def create_wavelengths( wavel_range: Tuple[Union[float, np.float32], Union[float, np.float32]], wavel_sampling: float, ) -> np.ndarray: """ Function for creating logarithmically-spaced wavelengths, so with a constant :math:`\\lambda/\\Delta\\lambda`. Parameters ---------- wavel_range : tuple(float, float) Wavelength range (:math:`\\mu\\mathrm{m}`). Tuple with the minimum and maximum wavelength. wavel_sampling : float Wavelength sampling :math:`\\lambda/\\Delta\\lambda`. Returns ------- np.ndarray Array with the wavelengths (:math:`\\mu\\mathrm{m}`). Since the wavelength boundaries are fixed, the output sampling is slightly different from the value provided as argument of ``wavel_sampling``. """ n_test = 100 wavel_test = np.logspace(np.log10(wavel_range[0]), np.log10(wavel_range[1]), n_test) sampling_test = 0.5 * (wavel_test[1:] + wavel_test[:-1]) / np.diff(wavel_test) # math.ceil returns int, but np.ceil returns float wavel_array = np.logspace( np.log10(wavel_range[0]), np.log10(wavel_range[1]), ceil(n_test * wavel_sampling / np.mean(sampling_test)) + 1, ) # res_out = np.mean(0.5*(wavel_array[1:]+wavel_array[:-1])/np.diff(wavel_array)) return wavel_array
[docs] @typechecked def smooth_spectrum( wavelength: np.ndarray, flux: np.ndarray, spec_res: float, size: int = 11, force_smooth: bool = False, ) -> np.ndarray: """ Function for smoothing a spectrum with a Gaussian kernel to a fixed spectral resolution. The kernel size is set to 5 times the FWHM of the Gaussian. The FWHM of the Gaussian is equal to the ratio of the wavelength and the spectral resolution. If the kernel does not fit within the available wavelength grid (i.e. at the edge of the array) then the flux values are set to NaN. Parameters ---------- wavelength : np.ndarray Wavelength points (um). Should be sampled with a uniform spectral resolution or a uniform wavelength spacing (slow). flux : np.ndarray Flux (W m-2 um-1). spec_res : float Spectral resolution. size : int Kernel size (odd integer). force_smooth : bool Force smoothing for constant spectral resolution Returns ------- np.ndarray Smoothed spectrum (W m-2 um-1). """ def _gaussian(size, sigma): pos = range(-(size - 1) // 2, (size - 1) // 2 + 1) kernel = [ np.exp(-float(x) ** 2 / (2.0 * sigma**2)) / (sigma * np.sqrt(2.0 * np.pi)) for x in pos ] return np.asarray(kernel / sum(kernel)) spacing = np.mean(2.0 * np.diff(wavelength) / (wavelength[1:] + wavelength[:-1])) spacing_std = np.std(2.0 * np.diff(wavelength) / (wavelength[1:] + wavelength[:-1])) if spacing_std / spacing < 1e-2 or force_smooth: # delta_lambda of resolution element is # FWHM of the LSF's standard deviation sigma_lsf = 1.0 / spec_res / (2.0 * np.sqrt(2.0 * np.log(2.0))) # Calculate the sigma to be used with the Gaussian filter # in units of the input wavelength bins sigma_filter = sigma_lsf / spacing flux_smooth = gaussian_filter(flux, sigma=sigma_filter, mode="nearest") else: if size % 2 == 0: raise ValueError("The kernel size should be an odd number.") flux_smooth = np.zeros(flux.shape) # (W m-2 um-1) spacing = np.mean(np.diff(wavelength)) # (um) spacing_std = np.std(np.diff(wavelength)) # (um) if spacing_std / spacing > 1e-2: warnings.warn( "The wavelength spacing is not uniform " f"(lambda/d_lambda = {spacing} +/- {spacing_std}). " "The smoothing with the Gaussian kernel requires " "either the spectral resolution or the wavelength " "spacing to be uniformly sampled. This warning " "should not have occurred with any of the model " "grids provided by species. Please open an issue " "on the Github page if help is needed." ) for i, item in enumerate(wavelength): fwhm = item / spec_res # (um) sigma = fwhm / (2.0 * np.sqrt(2.0 * np.log(2.0))) # (um) size = int( 5.0 * sigma / spacing ) # Kernel size 5 times the width of the LSF if size % 2 == 0: size += 1 gaussian = _gaussian(size, sigma / spacing) try: flux_smooth[i] = np.sum( gaussian * flux[i - (size - 1) // 2 : i + (size - 1) // 2 + 1] ) except ValueError: flux_smooth[i] = np.nan return flux_smooth