The starting point are the images created by PyEmir, as explained in the tutorial:
https://guaix-ucm.github.io/pyemir-tutorials/tutorial_mos/ngc7798.html
The final results are response cuves, which are employed in that tutorial to proceed with the absolute flux calibration of NGC7798.
from astropy.io import fits
import matplotlib.pyplot as plt
import numpy as np
from numina.array.display.ximshow import ximshow
from numina.array.display.ximplotxy import ximplotxy
with fits.open('grismJ_hd209290_A-B.fits', mode='readonly') as hdulist:
image_headerJ = hdulist[0].header
image_dataJ = hdulist[0].data
# image dimensions
naxis2, naxis1 = image_dataJ.shape
# read wavelength calibration from header
crpix1 = image_headerJ['crpix1']
crval1 = image_headerJ['crval1']
cdelt1 = image_headerJ['cdelt1']
exptime = image_headerJ['exptime']
print('crpix1, crval1, cdelt1:', crpix1, crval1, cdelt1)
print('exptime:', exptime)
crpix1, crval1, cdelt1: 1.0 11200.0 0.77 exptime: 9.999289
ximshow(image_dataJ, image_bbox=(600,2750,1050,1110),
cbar_orientation='vertical',
title='HD209290 (grism J)')
ximshow(image_dataJ, image_bbox=(600,2750,850,930),
cbar_orientation='vertical',
title='HD209290 (grism J)')
# extract and coadd individual spectra by coadding rows
sp1 = np.sum(image_dataJ[1060:1100,], axis=0)
sp2 = -np.sum(image_dataJ[870:910,], axis=0)
# sum of two spectra
spectrumJ = sp1 + sp2
# compute counts/second
spectrumJ = spectrumJ / (2 * exptime)
# plot spectrum
fig, ax = plt.subplots()
waveJ = crval1 + (np.arange(1, naxis1 + 1) - crpix1) * cdelt1
ax.plot(waveJ, spectrumJ)
wvminJ = 11710
wvmaxJ = 13270
ax.set_xlim([wvminJ, wvmaxJ])
ax.set_xlabel('wavelength (Angstrom)')
ax.set_ylabel('count rate (ADU/s)')
ax.set_title('HD209290 (grism J)')
Text(0.5, 1.0, 'HD209290 (grism J)')
# save spectrum into FITS file
hdu_sp = fits.PrimaryHDU(data=spectrumJ, header=image_headerJ)
hdu_sp.writeto('grismJ_hd209290.fits', overwrite=True)
with fits.open('grismH_hd209290_A-B.fits', mode='readonly') as hdulist:
image_headerH = hdulist[0].header
image_dataH = hdulist[0].data
# image dimensions
naxis2, naxis1 = image_dataH.shape
# read wavelength calibration from header
crpix1 = image_headerH['crpix1']
crval1 = image_headerH['crval1']
cdelt1 = image_headerH['cdelt1']
exptime = image_headerH['exptime']
print('crpix1, crval1, cdelt1:', crpix1, crval1, cdelt1)
print('exptime:', exptime)
crpix1, crval1, cdelt1: 1.0 14500.0 1.22 exptime: 9.999289
ximshow(image_dataH, image_bbox=(580,2730,1050,1110),
cbar_orientation='vertical',
title='HD209290 (grism H)')
ximshow(image_dataH, image_bbox=(580,2730,850,930),
cbar_orientation='vertical',
title='HD209290 (grism H)')
# extract and coadd individual spectra by coadding rows
sp1 = np.sum(image_dataH[1060:1100,], axis=0)
sp2 = -np.sum(image_dataH[870:910,], axis=0)
# sum of two spectra
spectrumH = sp1 + sp2
# compute counts/second
spectrumH = spectrumH / (2 * exptime)
# plot spectrum
fig, ax = plt.subplots()
waveH = crval1 + (np.arange(1, naxis1 + 1) - crpix1) * cdelt1
ax.plot(waveH, spectrumH)
wvminH = 15280
wvmaxH = 17750
ax.set_xlim([wvminH, wvmaxH])
ax.set_xlabel('wavelength (Angstrom)')
ax.set_ylabel('count rate (ADU/s)')
ax.set_title('HD209290 (grism H)')
Text(0.5, 1.0, 'HD209290 (grism H)')
# save spectrum into FITS file
hdu_sp = fits.PrimaryHDU(data=spectrumH, header=image_headerH)
hdu_sp.writeto('grismH_hd209290.fits', overwrite=True)
with fits.open('grismK_hd209290_A-B.fits', mode='readonly') as hdulist:
image_headerK = hdulist[0].header
image_dataK = hdulist[0].data
# image dimensions
naxis2, naxis1 = image_dataK.shape
# read wavelength calibration from header
crpix1 = image_headerK['crpix1']
crval1 = image_headerK['crval1']
cdelt1 = image_headerK['cdelt1']
exptime = image_headerK['exptime']
print('crpix1, crval1, cdelt1:', crpix1, crval1, cdelt1)
print('exptime:', exptime)
crpix1, crval1, cdelt1: 1.0 19100.0 1.73 exptime: 9.999289
ximshow(image_dataK, image_bbox=(630,2780,1050,1110),
cbar_orientation='vertical',
title='HD209290 (grism K)')
ximshow(image_dataK, image_bbox=(630,2780,850,930),
cbar_orientation='vertical',
title='HD209290 (grism K)')
# extract and coadd individual spectra by coadding rows
sp1 = np.sum(image_dataK[1060:1100,], axis=0)
sp2 = -np.sum(image_dataK[870:910,], axis=0)
# sum of two spectra
spectrumK = sp1 + sp2
# compute counts/second
spectrumK = spectrumK / (2 * exptime)
# plot spectrum
fig, ax = plt.subplots()
waveK = crval1 + (np.arange(1, naxis1 + 1) - crpix1) * cdelt1
ax.plot(waveK, spectrumK)
wvminK = 20280
wvmaxK = 23810
ax.set_xlim([wvminK, wvmaxK])
ax.set_xlabel('wavelength (Angstrom)')
ax.set_ylabel('count rate (ADU/s)')
ax.set_title('HD209290 grism K')
Text(0.5, 1.0, 'HD209290 grism K')
# save spectrum into FITS file
hdu_sp = fits.PrimaryHDU(data=spectrumK, header=image_headerK)
hdu_sp.writeto('grismK_hd209290.fits', overwrite=True)
The flux calibrated spectrum of this star can be found in the web page of the NASA Infrared Telescope Facility (IRTF), in particular see the file: http://irtfweb.ifa.hawaii.edu/~spex/IRTF_Spectral_Library/Data/M0.5V_HD209290.txt
# read absolute flux calibration for the star
table_flux_tabulated = np.genfromtxt(
"http://irtfweb.ifa.hawaii.edu/~spex/IRTF_Spectral_Library/Data/M0.5V_HD209290.txt"
)
print(table_flux_tabulated)
[[ 8.074530e-01 1.484350e-11 3.487900e-14] [ 8.076550e-01 1.492060e-11 3.509390e-14] [ 8.078580e-01 1.494630e-11 3.503310e-14] ... [ 5.422893e+00 -9.990000e+02 -9.990000e+02] [ 5.423898e+00 -9.990000e+02 -9.990000e+02] [ 5.424903e+00 -9.990000e+02 -9.990000e+02]]
# Wavelength (microns): convert to Angstrom
xtab = table_flux_tabulated[:,0] * 10000
# Flux Density (W m-2 um-1)
ytab = table_flux_tabulated[:,1]
# resample previous spectrum to sampling of observed spectrum
from scipy.interpolate import interp1d
funinterp = interp1d(xtab, ytab, kind="linear")
# convolve to match spectral resolution
from scipy.ndimage import gaussian_filter
yfluxJ = funinterp(waveJ)
sigma_tab_J = 0 # by 'eye'
if sigma_tab_J > 0:
yfluxJ = gaussian_filter(yfluxJ, sigma_tab_J)
yfluxH = funinterp(waveH)
sigma_tab_H = 3 # by 'eye'
if sigma_tab_H > 0:
yfluxH = gaussian_filter(yfluxH, sigma_tab_H)
yfluxK = funinterp(waveK)
sigma_tab_K = 3 # by 'eye'
if sigma_tab_K > 0:
yfluxK = gaussian_filter(yfluxK, sigma_tab_K)
# plot observed spectrum and tabulated data
for grism, wvmin, wvmax, wave, spectrum, yflux in zip(['J', 'H', 'K'],
[wvminJ, wvminH, wvminK],
[wvmaxJ, wvmaxH, wvmaxK],
[waveJ, waveH, waveK],
[spectrumJ, spectrumH, spectrumK],
[yfluxJ, yfluxH, yfluxK]):
fig, ax = plt.subplots()
ax.plot(wave, spectrum, 'C0-', label='observed spectrum')
ax.set_xlabel('Wavelength (Angstrom)')
ax.set_ylim([0, spectrum.max()*1.05])
ax.set_xlim([wvmin, wvmax])
ax.set_ylabel('count rate (ADU/s)', color='C0')
ax.legend(loc=4)
ax2 = ax.twinx()
ax2.plot(wave, yflux, 'C1-', label='tabulated spectrum')
ax2.set_ylim([0, yflux.max()*1.05])
ax2.set_xlabel('wavelength (Angstrom)')
ax2.set_ylabel(r'tabulated $F_\lambda$ (W m$^{-2} \mu\rm{m}^{-1}$)', color='C1')
ax2.set_title('HD209290 (grism ' + grism + ')')
ax2.legend(loc=3)
Since the spectral resolution of the tabulated spectrum is worse than the one in the observed spectrum, we convolve the observed spectrum (using gaussian kernel).
sigma_obs_J = 3 # by 'eye'
if sigma_obs_J > 0:
yfluxJ = gaussian_filter(yfluxJ, sigma=sigma_obs_J)
sigma_obs_H = 0 # by 'eye'
if sigma_obs_H > 0:
yfluxH = gaussian_filter(yfluxH, sigma=sigma_obs_H)
sigma_obs_K = 3 # by 'eye'
if sigma_obs_K > 0:
yfluxK = gaussian_filter(yfluxK, sigma=sigma_obs_K)
# repeat previous plot
for grism, wvmin, wvmax, wave, spectrum, yflux in zip(['J', 'H', 'K'],
[wvminJ, wvminH, wvminK],
[wvmaxJ, wvmaxH, wvmaxK],
[waveJ, waveH, waveK],
[spectrumJ, spectrumH, spectrumK],
[yfluxJ, yfluxH, yfluxK]):
fig, ax = plt.subplots()
ax.plot(wave, spectrum, 'C0-', label='observed spectrum')
ax.set_xlabel('wavelength (Angstrom)')
ax.set_ylim([0, spectrum.max()*1.05])
ax.set_xlim([wvmin, wvmax])
ax.set_ylabel('count rate (ADU/s)', color='C0')
ax.legend(loc=4)
ax2 = ax.twinx()
ax2.plot(wave, yflux, 'C1-', label='tabulated spectrum')
ax2.set_ylim([0, yflux.max()*1.05])
ax2.set_xlabel('wavelength (Angstrom)')
ax2.set_ylabel(r'tabulated $F_\lambda$ (W m$^{-2} \mu\rm{m}^{-1}$)', color='C1')
ax2.set_title('HD209290 (grism ' + grism + ')')
ax2.legend(loc=3)
Download the following atmospheric transmission file: http://nartex.fis.ucm.es/~ncl/emir/skycalc_transmission_R20000.txt
# preliminary response curve
yratioJ = spectrumJ / yfluxJ
yratioH = spectrumH / yfluxH
yratioK = spectrumK / yfluxK
# read telluric transmission
telluric_tabulated = np.genfromtxt("skycalc_transmission_R20000.txt")
xtab = telluric_tabulated[:,0] * 10 # convert from nm to Angstrom
ytab = telluric_tabulated[:,1]
# resample previous spectrum to sampling of observed spectrum
funinterp = interp1d(xtab, ytab, kind="linear")
ytelluricJ = funinterp(waveJ)
ytelluricH = funinterp(waveH)
ytelluricK = funinterp(waveK)
# broaden resolution
sigma_tel_J = 3 # by 'eye'
if sigma_tel_J > 0:
ytelluricJ = gaussian_filter(ytelluricJ, sigma=sigma_tel_J)
sigma_tel_H = 0 # by 'eye'
if sigma_tel_H > 0:
ytelluricH = gaussian_filter(ytelluricH, sigma=sigma_tel_H)
sigma_tel_K = 3 # by 'eye'
if sigma_tel_K > 0:
ytelluricK = gaussian_filter(ytelluricK, sigma=sigma_tel_K)
# plot
for grism, wvmin, wvmax, wave, yratio, ytelluric in zip(['J', 'H', 'K'],
[wvminJ, wvminH, wvminK],
[wvmaxJ, wvmaxH, wvmaxK],
[waveJ, waveH, waveK],
[yratioJ, yratioH, yratioK],
[ytelluricJ, ytelluricH, ytelluricK]):
fig, ax = plt.subplots()
ax.plot(wave, yratio, 'C2', label='preliminary response curve')
ax.set_xlim([wvmin, wvmax])
ax.set_xlabel('wavelength (Angstrom)')
ax.set_ylabel('preliminary response curve\n' + r'(ADU s$^{-1}$) / (W m$^{-2} \mu\rm{m}^{-1})$', color='g')
ax.set_title('HD209290 (grism ' + grism +')')
ax.legend(loc=3)
ax2 = ax.twinx()
ax2.plot(wave, ytelluric, 'C3', label='skycal_transmission')
ax2.set_xlim([wvmin, wvmax])
ax2.set_xlabel('wavelength (Angstrom)')
ax2.set_ylabel('telluric transmission', color='C3')
ax2.legend(loc=4)
# save preliminary response curve
hdu_sp = fits.PrimaryHDU(data=yratioJ, header=image_headerJ)
hdu_sp.writeto('grismJ_preliminary_response_curve_hd209290.fits', overwrite=True)
hdu_sp = fits.PrimaryHDU(data=yratioH, header=image_headerH)
hdu_sp.writeto('grismH_preliminary_response_curve_hd209290.fits', overwrite=True)
hdu_sp = fits.PrimaryHDU(data=yratioK, header=image_headerK)
hdu_sp.writeto('grismK_preliminary_response_curve_hd209290.fits', overwrite=True)
# ratio between response curve and atmosphere transmission
yratioplusJ = yratioJ / ytelluricJ
yratioplusH = yratioH / ytelluricH
yratioplusK = yratioK / ytelluricK
# smoothed version of previous curve
from scipy.signal import savgol_filter
yratiosmoothJ = savgol_filter(yratioplusJ, window_length=101, polyorder=2)
yratiosmoothH = savgol_filter(yratioplusH, window_length=101, polyorder=2)
yratiosmoothK = savgol_filter(yratioplusK, window_length=101, polyorder=2)
# plot
for grism, wvmin, wvmax, wave, yratioplus, yratiosmooth in zip(['J', 'H', 'K'],
[wvminJ, wvminH, wvminK],
[wvmaxJ, wvmaxH, wvmaxK],
[waveJ, waveH, waveK],
[yratioplusJ, yratioplusH, yratioplusK],
[yratiosmoothJ, yratiosmoothH, yratiosmoothK]):
fig, ax = plt.subplots()
ax.plot(wave, yratioplus, 'C4', label='smoothed curve')
ax.set_xlim([wvmin, wvmax])
ax.set_xlabel('wavelength (Angstrom)')
ax.set_ylabel('response curve\n' + r'(ADU s$^{-1}$) / (W m$^{-2} \mu\rm{m}^{-1})$', color='k')
ax.set_title('HD209290 (grism ' + grism + ')')
ax.plot(wave, yratiosmooth, 'k', label='skycal_transmission')
ax.legend(loc=3)
# save smoothed response curve
hdu_sp = fits.PrimaryHDU(data=yratiosmoothJ, header=image_headerJ)
hdu_sp.writeto('grismJ_response_curve_hd209290.fits', overwrite=True)
hdu_sp = fits.PrimaryHDU(data=yratiosmoothH, header=image_headerH)
hdu_sp.writeto('grismH_response_curve_hd209290.fits', overwrite=True)
hdu_sp = fits.PrimaryHDU(data=yratiosmoothK, header=image_headerK)
hdu_sp.writeto('grismK_response_curve_hd209290.fits', overwrite=True)