Source code for species.util.spec_util
"""
Utility functions for manipulating spectra.
"""
import warnings
from math import ceil
import numpy as np
from beartype import beartype
from beartype.typing import Tuple, Union
from scipy.ndimage import gaussian_filter
[docs]
@beartype
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,
)
# Check wavelength sampling, lambda/D_lambda, of the created array
# res_out = np.mean(0.5*(wavel_array[1:]+wavel_array[:-1])/np.diff(wavel_array))
return wavel_array
[docs]
@beartype
def smooth_spectrum(
wavelength: np.ndarray,
flux: np.ndarray,
spec_res: float,
kernel_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 constant
logarithmic steps (i.e. fixed :math:`\\lambda/\\Delta\\lambda`)
or sampled with a uniform linear spacing. The latter
implementation is slow so the first is preferred.
flux : np.ndarray
Flux (W m-2 um-1).
spec_res : float
Spectral resolution.
kernel_size : int
Kernel size (odd integer). Only used when the wavelengths
are linearly sampled. Not used by the function.
force_smooth : bool
Force the smoothing for logarithmically spaced wavelengths.
Returns
-------
np.ndarray
Smoothed spectrum (W m-2 um-1).
"""
def _gaussian(kernel_size, sigma):
pos = range(-(kernel_size - 1) // 2, (kernel_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:
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)
# Kernel size 5 times the width of the LSF
kernel_size = int(5.0 * sigma / spacing)
if kernel_size % 2 == 0:
kernel_size += 1
gaussian = _gaussian(kernel_size, sigma / spacing)
try:
flux_smooth[i] = np.sum(
gaussian
* flux[i - (kernel_size - 1) // 2 : i + (kernel_size - 1) // 2 + 1]
)
except ValueError:
flux_smooth[i] = np.nan
return flux_smooth