def addlines2spec(wavelength, wl_line, fl_line, resolution, scale_spec=1., debug=False): """ Create a spectrum with a set of (gaussian) emission lines. Parameters ---------- wavelength : np.array wavelength vector of the input spectrum wl_line, fl_line : np.arrays wavelength and flux of each individual line resolution : np.float resolution of the spectrograph. In other words, the lines will have a FWHM equal to: fwhm_line = wl_line / resolution scale_spec : np.float rescale all the normalization of the final spectrum. Default scale_spec=1. debug : boolean If True will show debug plots Returns ------- line_spec : np.array Spectrum with lines """ line_spec = np.zeros_like(wavelength) wl_line_min, wl_line_max = np.min(wavelength), np.max(wavelength) good_lines = (wl_line>wl_line_min) & (wl_line<wl_line_max) wl_line_good = wl_line[good_lines] fl_line_good = fl_line[good_lines] # define sigma of the gaussians sigma = wl_line_good / resolution / 2.355 msgs.info("Creating line spectrum") for ii in np.arange(len(wl_line_good)): line_spec += scale_spec*fl_line_good[ii]*\ np.exp(-np.power((wl_line_good[ii]-wavelength),2.)/(2.*np.power(sigma[ii],2.))) if debug: utils.pyplot_rcparams() msgs.info("Plot of the line spectrum.") plt.figure() plt.plot(wavelength, line_spec, color='navy', linestyle='-', alpha=0.8, label=r'Spectrum with lines included') plt.legend() plt.xlabel(r'Wavelength') plt.ylabel(r'Flux') msgs.info("Close the Figure to continue.") plt.show(block=True) plt.close() utils.pyplot_rcparams_default() return line_spec
def blackbody(wavelength, T_BB=250., debug=False): """ Given wavelength [in microns] and Temperature in Kelvin it returns the black body emission. Parameters ---------- wavelength : np.array wavelength vector in microns T_BB : float black body temperature in Kelvin. Default is set to: T_BB = 250. Returns ------- blackbody : np.array spectral radiance of the black body in cgs units: B_lambda = 2.*h*c^2/lambda^5.*(1./(exp(h*c/(lambda*k_b*T_BB))-1.) blackbody_counts : np.array Same as above but in flux density """ # Define constants in cgs PLANCK = astropy.constants.h.cgs.value # erg*s C_LIGHT = astropy.constants.c.cgs.value # cm/s K_BOLTZ = astropy.constants.k_B.cgs.value # erg/K RADIAN_PER_ARCSEC = 1. / 3600. * np.pi / 180. msgs.info("Creating BB spectrum at T={}K".format(T_BB)) lam = wavelength / 1e4 # convert wave in cm. blackbody_pol = 2. * PLANCK * np.power(C_LIGHT, 2) / np.power(lam, 5) blackbody_exp = np.exp(PLANCK * C_LIGHT / (lam * K_BOLTZ * T_BB)) - 1. blackbody = blackbody_pol / blackbody_exp blackbody_counts = blackbody / (PLANCK * C_LIGHT / lam) * 1e-4 \ * np.power(RADIAN_PER_ARCSEC, 2.) if debug: utils.pyplot_rcparams() msgs.info("Plot of the blackbody spectrum.") plt.figure() plt.plot(wavelength, blackbody, color='navy', linestyle='-', alpha=0.8, label=r'T_BB={}'.format(T_BB)) plt.legend() plt.xlabel(r"Wavelength [micron]") plt.ylabel(r"Spectral Radiance") plt.title(r"Planck's law") msgs.info("Close the Figure to continue.") plt.show(block=True) plt.close() utils.pyplot_rcparams_default() return blackbody, blackbody_counts
def conv2res(wavelength, flux, resolution, central_wl='midpt', debug=False): """Convolve an imput spectrum to a specific resolution. This is only approximate. It takes a fix FWHM for the entire spectrum given by: fwhm = wl_cent / resolution Parameters ---------- wavelength : np.array wavelength flux : np.array flux resolution : np.float resolution of the spectrograph central_wl if 'midpt' the central pixel of wavelength is used, otherwise the central_wl will be used. debug : boolean If True will show debug plots Returns ------- flux_convolved :np.array Resulting flux after convolution px_sigma : float Size of the sigma in pixels at central_wl px_bin : float Size of one pixel at central_wl """ if central_wl == 'midpt': wl_cent = np.median(wavelength) else: wl_cent = np.float(central_wl) wl_sigma = wl_cent / resolution / 2.355 wl_bin = np.abs((wavelength - np.roll(wavelength, 1))[np.where( np.abs(wavelength - wl_cent) == np.min(np.abs(wavelength - wl_cent)))]) msgs.info("The binning of the wavelength array at {} is: {}".format( wl_cent, wl_bin[0])) px_bin = wl_bin[0] px_sigma = wl_sigma / px_bin msgs.info("Covolving with a Gaussian kernel with sigma = {} pixels".format( px_sigma)) gauss_kernel = Gaussian1DKernel(px_sigma) flux_convolved = convolve(flux, gauss_kernel) if debug: utils.pyplot_rcparams() msgs.info("Spectrum Convolved at R = {}".format(resolution)) plt.figure() plt.plot(wavelength, flux, color='navy', linestyle='-', alpha=0.8, label=r'Original') plt.plot(wavelength, flux_convolved, color='crimson', linestyle='-', alpha=0.8, label=r'Convolved') plt.legend() plt.xlabel(r'Wavelength') plt.ylabel(r'Flux') plt.title(r'Spectrum Convolved at R = {}'.format(resolution)) msgs.info("Close the Figure to continue.") plt.show(block=True) plt.close() utils.pyplot_rcparams_default() return flux_convolved, px_sigma, px_bin
def optical_modelThAr(resolution, waveminmax=(3000., 10500.), dlam=40.0, flgd=True, thar_outfile=None, debug=False): """ Generate a model of a ThAr lamp in the uvb/optical. This is based on the Murphy et al. ThAr spectrum. Detailed information are here: http://astronomy.swin.edu.au/~mmurphy/thar/index.html Everythins is smoothed at the given resolution. Parameters ---------- resolution : np.float resolution of the spectrograph. The ThAr lines will have a FWHM equal to: fwhm_line = wl_line / resolution waveminmax : tuple wavelength range in angstrom to be covered by the model. Default is: (3000.,10500.) dlam : bin to be used to create the wavelength grid of the model. If flgd='True' it is a bin in velocity (km/s). If flgd='False' it is a bin in linear space (microns). Default is: 40.0 (with flgd='True') flgd : boolean if flgd='True' (default) wavelengths are created with equal steps in log space. If 'False', wavelengths will be created wit equal steps in linear space. thar_outfile : str name of the fits file where the model sky spectrum will be stored. default is 'None' (i.e., no file will be written). debug : boolean If True will show debug plots Returns ------- wave, thar_model : np.arrays wavelength (in Ang.) and flux of the final model of the ThAr lamp emission. """ # Create the wavelength array: wv_min = waveminmax[0] wv_max = waveminmax[1] if flgd: msgs.info("Creating wavelength vector in velocity space.") velpix = dlam # km/s loglam = np.log10(1.0 + velpix / 299792.458) wave = np.power(10., np.arange(np.log10(wv_min), np.log10(wv_max), loglam)) else: msgs.info("Creating wavelength vector in linear space.") wave = np.arange(wv_min, wv_max, dlam) msgs.info("Add in ThAr lines") th_wv, th_fx = thar_lines() # select spectral region filt_wl = (th_wv >= wv_min) & (th_wv <= wv_max) # calculate sigma at the mean wavelenght of the ThAr spectrum mn_wv = np.mean(th_wv[filt_wl]) # Convolve to the instrument resolution. This is only # approximate. smooth_fx, dwv, thar_dwv = conv2res(th_wv, th_fx, resolution, central_wl=mn_wv, debug=debug) # Interpolate over input wavelengths interp_thar = scipy.interpolate.interp1d(th_wv, smooth_fx, kind='cubic', fill_value='extrapolate') thar_spec = interp_thar(wave) # remove negative artifacts thar_spec[thar_spec < 0.] = 0. # Remove regions of the spectrum outside the wavelength covered by the ThAr model if wv_min < np.min(th_wv): msgs.warn("Model of the ThAr spectrum outside the template coverage.") thar_spec[wave < np.min(th_wv)] = 0. if wv_max < np.max(th_wv): msgs.warn("Model of the ThAr spectrum outside the template coverage.") thar_spec[wave > np.max(th_wv)] = 0. if thar_outfile is not None: msgs.info("Saving the ThAr model in: {}".format(thar_outfile)) hdu = fits.PrimaryHDU(np.array(thar_spec)) header = hdu.header if flgd: header['CRVAL1'] = np.log10(wv_min) header['CDELT1'] = loglam header['DC-FLAG'] = 1 else: header['CRVAL1'] = wv_min header['CDELT1'] = dlam header['DC-FLAG'] = 0 hdu.writeto(thar_outfile, overwrite=True) if debug: utils.pyplot_rcparams() msgs.info( "Plot of the Murphy et al. template at R={}".format(resolution)) plt.figure() plt.plot(th_wv, th_fx, color='navy', linestyle='-', alpha=0.3, label=r'Original') plt.plot(th_wv, smooth_fx, color='crimson', linestyle='-', alpha=0.6, label=r'Convolved at R={}'.format(resolution)) plt.plot(wave, thar_spec, color='maroon', linestyle='-', alpha=1.0, label=r'Convolved at R={} and resampled'.format(resolution)) plt.legend() plt.xlabel(r'Wavelength [Ang.]') plt.ylabel(r'Emission') plt.title(r'Murphy et al. ThAr spectrum at R={}'.format(resolution)) msgs.info("Close the Figure to continue.") plt.show(block=True) plt.close() utils.pyplot_rcparams_default() return np.array(wave), np.array(thar_spec)
def nearIR_modelsky(resolution, waveminmax=(0.8, 2.6), dlam=40.0, flgd=True, nirsky_outfile=None, T_BB=250., SCL_BB=1., SCL_OH=1., SCL_H2O=10., WAVE_WATER=2.3, debug=False): """ Generate a model sky in the near-IR. This includes a continuum model to match to gemini broadband level, a black body at T_BB, OH lines, and H2O lines (but only at lambda>WAVE_WATER). Everythins is smoothed at the given resolution. Parameters ---------- resolution : np.float resolution of the spectrograph. The OH and H2O lines will have a FWHM equal to: fwhm_line = wl_line / resolution waveminmax : tuple wavelength range in microns to be covered by the model. Default is: (0.8, 2.6) dlam : bin to be used to create the wavelength grid of the model. If flgd='True' it is a bin in velocity (km/s). If flgd='False' it is a bin in linear space (microns). Default is: 40.0 (with flgd='True') flgd : boolean if flgd='True' (default) wavelengths are created with equal steps in log space. If 'False', wavelengths will be created wit equal steps in linear space. nirsky_outfile : str name of the fits file where the model sky spectrum will be stored. default is 'None' (i.e., no file will be written). T_BB : float black body temperature in Kelvin. Default is set to: T_BB = 250. SCL_BB : float scale factor for modelling the sky black body emssion. Default: SCL_BB=1. SCL_OH : float scale factor for modelling the OH emssion. Default: SCL_OH=1. SCL_H2O : float scale factor for modelling the H2O emssion. Default: SCL_H2O=10. WAVE_WATER : float wavelength (in microns) at which the H2O are inclued. Default: WAVE_WATER = 2.3 debug : boolean If True will show debug plots Returns ------- wave, sky_model : np.arrays wavelength (in Ang.) and flux of the final model of the sky. """ # Create the wavelength array: wv_min = waveminmax[0] wv_max = waveminmax[1] if flgd: msgs.info("Creating wavelength vector in velocity space.") velpix = dlam # km/s loglam = np.log10(1.0 + velpix / 299792.458) wave = np.power(10., np.arange(np.log10(wv_min), np.log10(wv_max), loglam)) else: msgs.info("Creating wavelength vector in linear space.") wave = np.arange(wv_min, wv_max, dlam) # Calculate transparency # trans = transparency(wave, debug=False) # Empirical match to gemini broadband continuum level logy = -0.55 - 0.55 * (wave - 1.0) y = np.power(10., logy) msgs.info("Add in a blackbody for the atmosphere.") bb, bb_counts = blackbody(wave, T_BB=T_BB, debug=debug) bb_counts = bb_counts msgs.info("Add in OH lines") oh_wv, oh_fx = oh_lines() # produces better wavelength solutions with 1.0 threshold msgs.info("Selecting stronger OH lines") filt_oh = oh_fx > 1. oh_wv, oh_fx = oh_wv[filt_oh], oh_fx[filt_oh] # scale_spec was added to match the XIDL code ohspec = addlines2spec(wave, oh_wv, oh_fx, resolution=resolution, scale_spec=((resolution / 1000.) / 40.), debug=debug) if wv_max > WAVE_WATER: msgs.info("Add in H2O lines") h2o_wv, h2o_rad = h2o_lines() filt_h2o = (h2o_wv > wv_min - 0.1) & (h2o_wv < wv_max + 0.1) h2o_wv = h2o_wv[filt_h2o] h2o_rad = h2o_rad[filt_h2o] # calculate sigma at the mean wavelenght of the H2O spectrum filt_h2o_med = h2o_wv > WAVE_WATER mn_wv = np.mean(h2o_wv[filt_h2o_med]) # Convolve to the instrument resolution. This is only # approximate. smooth_fx, dwv, h2o_dwv = conv2res(h2o_wv, h2o_rad, resolution, central_wl=mn_wv, debug=debug) # Interpolate over input wavelengths interp_h2o = scipy.interpolate.interp1d(h2o_wv, smooth_fx, kind='cubic', fill_value='extrapolate') h2ospec = interp_h2o(wave) # Zero out below WAVE_WATER microns (reconsider) h2ospec[wave < WAVE_WATER] = 0. h2ospec[wave > np.max(h2o_wv)] = 0. else: h2ospec = np.zeros(len(wave), dtype='float') sky_model = y + bb_counts * SCL_BB + ohspec * SCL_OH + h2ospec * SCL_H2O if nirsky_outfile is not None: msgs.info("Saving the sky model in: {}".format(nirsky_outfile)) hdu = fits.PrimaryHDU(np.array(sky_model)) header = hdu.header if flgd: header['CRVAL1'] = np.log10(wv_min) header['CDELT1'] = loglam header['DC-FLAG'] = 1 else: header['CRVAL1'] = wv_min header['CDELT1'] = dlam header['DC-FLAG'] = 0 hdu.writeto(nirsky_outfile, overwrite=True) if debug: utils.pyplot_rcparams() msgs.info("Plot of the sky emission at R={}".format(resolution)) plt.figure() plt.plot(wave, sky_model, color='black', linestyle='-', alpha=0.8, label=r'Sky Model') plt.plot(wave, y, color='darkorange', linestyle='-', alpha=0.6, label=r'Continuum') plt.plot(wave, bb_counts * SCL_BB, color='green', linestyle='-', alpha=0.6, label=r'Black Body at T={}K'.format(T_BB)) plt.plot(wave, ohspec * SCL_OH, color='darkviolet', linestyle='-', alpha=0.6, label=r'OH') plt.plot(wave, h2ospec * SCL_H2O, color='dodgerblue', linestyle='-', alpha=0.6, label=r'H2O') plt.legend() plt.xlabel(r'Wavelength [microns]') plt.ylabel(r'Emission') plt.title(r'Sky Emission Spectrum at R={}'.format(resolution)) msgs.info("Close the Figure to continue.") plt.show(block=True) plt.close() utils.pyplot_rcparams_default() return np.array(wave * 10000.), np.array(sky_model)
def transparency(wavelength, debug=False): """ Interpolate the atmospheric transmission model in the IR over a given wavelength (in microns) range. Parameters ---------- wavelength : np.array wavelength vector in microns debug : boolean If True will show debug plots Returns ------- transparency : np.array Transmission of the sky over the considered wavelength rage. 1. means fully transparent and 0. fully opaque """ msgs.info("Reading in the atmospheric transmission model") transparency = np.loadtxt( data.get_skisim_filepath('atm_transmission_secz1.5_1.6mm.dat')) wave_mod = transparency[:, 0] tran_mod = transparency[:, 1] # Limit model between 0.8 and np.max(wavelength) microns filt_wave_mod = (wave_mod > 0.8) & (wave_mod < np.max(wavelength)) wave_mod = wave_mod[filt_wave_mod] tran_mod = tran_mod[filt_wave_mod] # Interpolate over input wavelengths interp_tran = scipy.interpolate.interp1d(wave_mod, tran_mod, kind='cubic', fill_value='extrapolate') transmission = interp_tran(wavelength) transmission[wavelength < 0.9] = 1. # Clean for spourious values due to interpolation transmission[transmission < 0.] = 0. transmission[transmission > 1.] = 1. if debug: utils.pyplot_rcparams() msgs.info("Plot of the sky transmission template") plt.figure() plt.plot(wave_mod, tran_mod, color='navy', linestyle='-', alpha=0.8, label=r'Original') plt.plot(wavelength, transmission, color='crimson', linestyle='-', alpha=0.8, label=r'Resampled') plt.legend() plt.xlabel(r'Wavelength [microns]') plt.ylabel(r'Transmission') plt.title(r' IR Transmission Spectra ') msgs.info("Close the Figure to continue.") plt.show(block=True) plt.close() utils.pyplot_rcparams_default() # Returns return transmission
def fit2darc_old(all_wv, all_pix, all_orders, nspec, nspec_coeff=4, norder_coeff=4, sigrej=3.0, debug=False): """Routine to obtain the 2D wavelength solution for an echelle spectrograph. This is calculated from the spec direction pixelcentroid and the order number of identified arc lines. The fit is a simple least-squares with rejections. This is a port of the XIDL code: x_fit2darc.pro Parameters ---------- all_wv: np.array wavelength of the identified lines all_pix: np.array y-centroid position of the identified lines all_orders: np.array order number of the identified lines nspec: int Size of the image in the spectral direction nspec_coeff : np.int order of the fitting along the spectral (pixel) direction for each order norder_coeff : np.int order of the fitting in the order direction sigrej: np.float sigma level for the rejection debug: boolean Extra plots to check the status of the procedure Returns: ------- """ # To use the legendre polynomial pixels and orders # need to be normalized in the -1,+1 range # Normalize pixels mnx = 0 # np.min(all_pix) mxx = float(nspec - 1) # np.max(all_pix) norm_pixel = np.array([0.5 * (mnx + mxx), mxx - mnx]) pix_nrm = 2. * (all_pix - norm_pixel[0]) / norm_pixel[1] # Normalize orders mnx, mxx = np.min(all_orders), np.max(all_orders) norm_order = np.array([0.5 * (mnx + mxx), mxx - mnx]) orders_nrm = 2. * (all_orders - norm_order[0]) / norm_order[1] if debug: # set some plotting parameters utils.pyplot_rcparams() plt.figure(figsize=(7, 5)) msgs.info("Plot identified lines") cm = plt.cm.get_cmap('RdYlBu_r') sc = plt.scatter(orders_nrm, pix_nrm, c=all_wv / 10000., cmap=cm) cbar = plt.colorbar(sc) cbar.set_label(r'Wavelength [$\mu$m]', rotation=270, labelpad=20) plt.xlabel(r'Normalized Orders') plt.ylabel(r'Normalized Pixels') plt.title(r'Location of the identified lines') plt.show() # Setup some things for the fits all_wv_order = all_wv * all_orders work2d = np.zeros((nspec_coeff * norder_coeff, len(all_wv)), dtype=np.float64) worky = pydl.flegendre(pix_nrm, nspec_coeff) workt = pydl.flegendre(orders_nrm, norder_coeff) for i in range(norder_coeff): for j in range(nspec_coeff): work2d[j * norder_coeff + i, :] = worky[j, :] * workt[i, :] # ToDO add upper lower to inputs lower = np.abs(sigrej) upper = np.abs(sigrej) maxiter = 25 iIter = 0 qdone = False thismask = np.ones_like(all_wv, dtype=bool) while (not qdone) and (iIter < maxiter): coeffs, wv_order_mod = fit_double_poly(all_wv_order, work2d, thismask.astype(float), nspec_coeff, norder_coeff) thismask, qdone = pydl.djs_reject(all_wv_order, wv_order_mod, outmask=thismask, lower=np.float64(lower), upper=np.float64(upper), use_mad=True, sticky=True) iIter += 1 if debug: utils.pyplot_rcparams() plt.figure(figsize=(7, 5)) plt.axhline(y=np.average(wv_order_mod[thismask] / all_orders[thismask] - all_wv[thismask]), color='r', linestyle='--') plt.axhline(y=+np.std(wv_order_mod[thismask] / all_orders[thismask] - all_wv[thismask]), color='r', linestyle=':') plt.axhline(y=-np.std(wv_order_mod[thismask] / all_orders[thismask] - all_wv[thismask]), color='r', linestyle=':') plt.scatter(all_wv[~thismask] / 10000., wv_order_mod[~thismask] / all_orders[~thismask] - all_wv[~thismask], marker="v", label=r'Rejected values') plt.scatter(all_wv[thismask] / 10000., wv_order_mod[thismask] / all_orders[thismask] - all_wv[thismask], marker="v", label=r'Good values') plt.text(np.min(all_wv / 10000), np.average(wv_order_mod[thismask] / all_orders[thismask] - all_wv[thismask]), r'Average={0:.1f}$\AA$'.format( np.average(wv_order_mod[thismask] / all_orders[thismask] - all_wv[thismask])), ha="left", va="bottom", bbox=dict(boxstyle="square", ec=(1., 0.5, 0.5), fc=(1., 0.8, 0.8), alpha=0.7, )) plt.text(np.max(all_wv / 10000), np.std(wv_order_mod[thismask] / all_orders[thismask] - all_wv[thismask]), r'Sigma={0:.1f}$\AA$'.format( np.std(wv_order_mod[thismask] / all_orders[thismask] - all_wv[thismask])), ha="right", va="bottom", bbox=dict(boxstyle="square", ec=(1., 0.5, 0.5), fc=(1., 0.8, 0.8), alpha=0.7, )) plt.legend() plt.title(r'Residuals after rejection iteration #{:d}'.format(iIter)) plt.xlabel(r'Wavelength [$\mu$m]') plt.ylabel(r'Residuals [$\AA$]') plt.show() if iIter == maxiter: msgs.warn('Maximum number of iterations maxiter={:}'.format(maxiter) + ' reached in robust_polyfit_djs') # Final fit coeffs, wv_order_mod = fit_double_poly(all_wv_order, work2d, thismask.astype(float), nspec_coeff, norder_coeff) # Check quality resid = (wv_order_mod[thismask] - all_wv_order[thismask]) fin_rms = np.sqrt(np.mean(resid ** 2)) msgs.info("RMS: {0:.5f} Ang*Order#".format(fin_rms)) orders = np.unique(all_orders) fit_dict = dict(coeffs=coeffs, orders=orders, nspec_coeff=nspec_coeff, norder_coeff=norder_coeff, pixel_cen=norm_pixel[0], pixel_norm=norm_pixel[1], order_cen=norm_order[0], order_norm=norm_order[1], nspec=nspec, all_pix=all_pix, all_wv=all_wv, all_orders=all_orders, all_mask=thismask) if debug: fit2darc_global_qa(fit_dict) fit2darc_orders_qa(fit_dict) return fit_dict
def fit2darc_orders_qa_old(fit_dict, outfile=None): """ QA on 2D fit of the wavelength solution of an Echelle spectrograph. Each panel contains a single order with the global fit and the residuals. Parameters ---------- fit_dict: dict dict of the 2D arc solution outfile: parameter for QA Returns ------- """ msgs.info("Creating QA for 2D wavelength solution") utils.pyplot_rcparams() # Extract info from fit_dict nspec = fit_dict['nspec'] orders = fit_dict['orders'] pixel_norm = fit_dict['pixel_norm'] pixel_cen = fit_dict['pixel_cen'] nspec_coeff = fit_dict['nspec_coeff'] norder_coeff = fit_dict['norder_coeff'] all_wv = fit_dict['all_wv'] all_pix = fit_dict['all_pix'] all_orders = fit_dict['all_orders'] thismask = fit_dict['all_mask'] resid_wl_global = [] # Define pixels array all_pixels = np.arange(nspec) # set the size of the plot nrow = np.int(2) ncol = np.int(np.ceil(len(orders ) /2.)) fig = plt.figure(figsize=( 5 *ncol , 6 *nrow)) outer = gridspec.GridSpec(nrow, ncol, wspace=0.3, hspace=0.2) for ii_row in range(nrow): for ii_col in range(ncol): if (ii_ro w *ncol + ii_col) < len(orders): inner = gridspec.GridSpecFromSubplotSpec(2, 1, height_ratios=[2 ,1], width_ratios=[1], subplot_spec=outer[ii_ro w *ncol + ii_col], wspace=0.1, hspace=0.0) ax0 = plt.Subplot(fig, inner[0]) ax1 = plt.Subplot(fig, inner[1], sharex=ax0) plt.setp(ax0.get_xticklabels(), visible=False) ii = orders[ii_ro w *ncol + ii_col] # define the color rr = (i i -np.max(orders) ) /(np.min(orders ) -np.max(orders)) gg = 0.0 bb = (i i -np.min(orders) ) /(np.max(orders ) -np.min(orders)) # Evaluate function wv_order_mod = eval2dfit(fit_dict, all_pixels, ii) # Evaluate delta lambda dw l =(wv_order_mod[-1 ] -wv_order_mod[0] ) /i i /(all_pixels[-1 ] -all_pixels[0]) # Estimate the residuals this_pix = all_pix[all_orders == ii] this_wv = all_wv[all_orders == ii] this_msk = thismask[all_orders == ii] wv_order_mod_resid = eval2dfit(fit_dict, this_pix, ii) resid_wl = (wv_order_mod_resi d /i i -this_wv) resid_wl_global = np.append(resid_wl_global ,resid_wl[this_msk]) # Plot the fit ax0.set_title('Order = {0:0.0f}'.format(ii)) ax0.plot(all_pixels, wv_order_mo d /i i /10000. ,color=(rr ,gg ,bb), linestyle='-', linewidth=2.5) ax0.scatter(this_pix[~this_msk], (wv_order_mod_resid[~this_msk ] /i i /10000. )+ \ 100 . *resid_wl[~this_msk ] /10000., marker='x', color='black', \ linewidth=2.5, s=16.) ax0.scatter(this_pix[this_msk], (wv_order_mod_resid[this_msk ] /i i /10000. )+ \ 100 . *resid_wl[this_msk ] /10000., color=(rr ,gg ,bb), \ linewidth=2.5, s=16.) ax0.set_ylabel(r'Wavelength [$\mu$m]') # Plot the residuals ax1.scatter(this_pix[~this_msk] ,(resid_wl[~this_msk ] /dwl) ,marker='x', color='black', \ linewidth=2.5, s=16.) ax1.scatter(this_pix[this_msk], (resid_wl[this_msk ] /dwl), color=(rr ,gg ,bb), \ linewidth=2.5, s=16.) ax1.axhline(y=0., color=(rr ,gg ,bb), linestyle=':', linewidth=2.5) ax1.get_yaxis().set_label_coords(-0.15 ,0.5) rms_order = np.sqrt(np.mean((resid_wl[this_msk] )* *2)) ax1.set_ylabel(r'Res. [pix]') ax0.text(0.1 ,0.9 ,r'RMS={0:.3f} Pixel'.format(rms_orde r /np.abs(dwl)) ,ha="left", va="top", transform = ax0.transAxes) ax0.text(0.1 ,0.8 ,r'$\Delta\lambda$={0:.3f} Pixel/$\AA$'.format(np.abs(dwl)) ,ha="left", va="top", transform = ax0.transAxes) ax0.get_yaxis().set_label_coords(-0.15 ,0.5) fig.add_subplot(ax0) fig.add_subplot(ax1)
def fit2darc_global_qa_old(fit_dict, outfile=None): """ QA on 2D fit of the wavelength solution. Parameters ---------- fit_dict: dict dict of the 2D arc solution outfile: parameter for QA Returns ------- """ msgs.info("Creating QA for 2D wavelength solution") utils.pyplot_rcparams() # Extract info from fit_dict nspec = fit_dict['nspec'] orders = fit_dict['orders'] pixel_norm = fit_dict['pixel_norm'] pixel_cen = fit_dict['pixel_cen'] nspec_coeff = fit_dict['nspec_coeff'] norder_coeff = fit_dict['norder_coeff'] all_wv = fit_dict['all_wv'] all_pix = fit_dict['all_pix'] all_orders = fit_dict['all_orders'] thismask = fit_dict['all_mask'] resid_wl_global = [] # Define pixels array all_pixels = np.arange(nspec) # Define figure properties plt.figure(figsize=(8 ,5)) # Variable where to store the max wavelength covered by the # spectrum mx = 0. # Loop over orders for ii in orders: # define the color rr = (i i -np.max(orders) ) /(np.min(orders ) -np.max(orders)) gg = 0.0 bb = (i i -np.min(orders) ) /(np.max(orders ) -np.min(orders)) # evaluate solution wv_order_mod = eval2dfit(fit_dict, all_pixels, ii) # Plot solution plt.plot(wv_order_mo d /ii, all_pixels ,color=(rr ,gg ,bb), linestyle='-', linewidth=2.5) # Evaluate residuals at each order this_pix = all_pix[all_orders == ii] this_wv = all_wv[all_orders == ii] this_msk = thismask[all_orders == ii] wv_order_mod_resid = eval2dfit(fit_dict, this_pix, ii) resid_wl = (wv_order_mod_resid /i i -this_wv) resid_wl_global = np.append(resid_wl_global ,resid_wl[this_msk]) plt.scatter((wv_order_mod_resid[~this_msk ] /ii )+ \ 100 . *resid_wl[~this_msk], this_pix[~this_msk], \ marker='x', color='black', linewidths=2.5, s=16.) plt.scatter((wv_order_mod_resid[this_msk ] /ii )+ \ 100 . *resid_wl[this_msk], this_pix[this_msk], \ color=(rr ,gg ,bb), linewidth=2.5, s=16.) if np.max(wv_order_mod_resi d /ii) > mx : mx = np.max(wv_order_mod_resi d /ii)
def write_QA(self): """ Write out zeropoint QA files """ utils.pyplot_rcparams() # Plot QA for zeropoint if 'Echelle' in self.spectrograph.pypeline: order_or_det = self.spectrograph.orders[np.arange(self.norderdet)] order_or_det_str = 'order' else: order_or_det = np.arange(self.norderdet) + 1 order_or_det_str = 'det' spec_str = f' {self.spectrograph.name} {self.spectrograph.pypeline} ' \ f'{self.spectrograph.dispname} ' zp_title = [ 'PypeIt Zeropoint QA for' + spec_str + order_or_det_str + f'={order_or_det[idet]}' for idet in range(self.norderdet) ] thru_title = [ order_or_det_str + f'={order_or_det[idet]}' for idet in range(self.norderdet) ] is_odd = self.norderdet % 2 != 0 npages = int(np.ceil(self.norderdet / 2)) if is_odd else self.norderdet // 2 + 1 # TODO: PDF page logic is a bit complicated becauase we want to plot two # plots per page, but the number of pages depends on the number of # order/det. Consider just dumping out a set of plots or revamp once we # have a dashboard. with PdfPages(self.qafile) as pdf: for ipage in range(npages): figure, (ax1, ax2) = plt.subplots(2, figsize=(8.27, 11.69)) if (2 * ipage) < self.norderdet: flux_calib.zeropoint_qa_plot( self.sens['SENS_WAVE'][2 * ipage], self.sens['SENS_ZEROPOINT'][2 * ipage], self.sens['SENS_ZEROPOINT_GPM'][2 * ipage], self.sens['SENS_ZEROPOINT_FIT'][2 * ipage], self.sens['SENS_ZEROPOINT_FIT_GPM'][2 * ipage], title=zp_title[2 * ipage], axis=ax1) if (2 * ipage + 1) < self.norderdet: flux_calib.zeropoint_qa_plot( self.sens['SENS_WAVE'][2 * ipage + 1], self.sens['SENS_ZEROPOINT'][2 * ipage + 1], self.sens['SENS_ZEROPOINT_GPM'][2 * ipage + 1], self.sens['SENS_ZEROPOINT_FIT'][2 * ipage + 1], self.sens['SENS_ZEROPOINT_FIT_GPM'][2 * ipage + 1], title=zp_title[2 * ipage + 1], axis=ax2) if self.norderdet == 1: # For single order/det just finish up after the first page ax2.remove() pdf.savefig() plt.close('all') elif (self.norderdet > 1) & (ipage < npages - 1): # For multi order/det but not on the last page, finish up. # No need to remove ax2 since there are always 2 plots per # page except on the last page pdf.savefig() plt.close('all') else: # For multi order/det but on the last page, add order/det # summary plot to the final page Deal with even/odd page # logic for axes if is_odd: # add order/det summary plot to axis 2 of current page axis = ax2 else: axis = ax1 ax2.remove() for idet in range(self.norderdet): # define the color rr = (np.max(order_or_det) - order_or_det[idet]) \ / np.maximum(np.max(order_or_det) - np.min(order_or_det), 1) gg = 0.0 bb = (order_or_det[idet] - np.min(order_or_det)) \ / np.maximum(np.max(order_or_det) - np.min(order_or_det), 1) wave_gpm = self.sens['SENS_WAVE'][idet] > 1.0 axis.plot(self.sens['SENS_WAVE'][idet, wave_gpm], self.sens['SENS_ZEROPOINT_FIT'][idet, wave_gpm], color=(rr, gg, bb), linestyle='-', linewidth=2.5, label=thru_title[idet], zorder=5 * idet) _wave_min = np.amin(self.sens['WAVE_MIN']) _wave_max = np.amax(self.sens['WAVE_MAX']) # If we are splicing, overplot the spliced zeropoint if self.splice_multi_det: wave_slice_gpm = (self.wave_splice >= _wave_min) \ & (self.wave_splice <= _wave_max) \ & (self.wave_splice > 1.0) axis.plot( self.wave_splice[wave_slice_gpm].flatten(), self.zeropoint_splice[wave_slice_gpm].flatten(), color='black', linestyle='-', linewidth=2.5, label='Spliced Zeropoint', zorder=30, alpha=0.3) wave_gpm = self.sens['SENS_WAVE'] > 1.0 axis.set_xlim((0.98 * _wave_min, 1.02 * _wave_max)) axis.set_ylim( (0.95 * np.amin(self.sens['SENS_ZEROPOINT_FIT'][wave_gpm]), 1.05 * np.amax(self.sens['SENS_ZEROPOINT_FIT'][wave_gpm]))) axis.legend(fontsize=14) axis.set_xlabel('Wavelength (Angstroms)') axis.set_ylabel('Zeropoint (AB mag)') axis.set_title('PypeIt Zeropoints for' + spec_str, fontsize=12) pdf.savefig() plt.close('all') # Plot throughput curve(s) for all orders/det fig = plt.figure(figsize=(12, 8)) axis = fig.add_axes([0.1, 0.1, 0.8, 0.8]) for idet in range(self.wave.shape[1]): # define the color rr = (np.max(order_or_det) - order_or_det[idet]) \ / np.maximum(np.max(order_or_det) - np.min(order_or_det), 1) gg = 0.0 bb = (order_or_det[idet] - np.min(order_or_det)) \ / np.maximum(np.max(order_or_det) - np.min(order_or_det), 1) gpm = (self.throughput[:, idet] >= 0.0) axis.plot(self.wave[gpm, idet], self.throughput[gpm, idet], color=(rr, gg, bb), linestyle='-', linewidth=2.5, label=thru_title[idet], zorder=5 * idet) if self.splice_multi_det: axis.plot(self.wave_splice[wave_slice_gpm].flatten(), self.throughput_splice[wave_slice_gpm].flatten(), color='black', linestyle='-', linewidth=2.5, label='Spliced Throughput', zorder=30, alpha=0.3) axis.set_xlim((0.98 * self.wave[self.throughput >= 0.0].min(), 1.02 * self.wave[self.throughput >= 0.0].max())) axis.set_ylim( (0.0, 1.05 * self.throughput[self.throughput >= 0.0].max())) axis.legend() axis.set_xlabel('Wavelength (Angstroms)') axis.set_ylabel('Throughput') axis.set_title('PypeIt Throughput for' + spec_str) fig.savefig(self.thrufile)