def convolveTelluric(lsf, airmass, pwv, telluric_data): """ Return a convolved, normalized telluric transmission model given a telluric data and lsf. """ # get a telluric standard model wavelow = telluric_data.wave[0] - 50 wavehigh = telluric_data.wave[-1] + 50 modelwave, modelflux = InterpTelluricModel(wavelow=wavelow, wavehigh=wavehigh, airmass=airmass, pwv=pwv) #modelflux **= alpha # lsf modelflux = smart.broaden(wave=modelwave, flux=modelflux, vbroad=lsf, rotate=False, gaussian=True) # resample modelflux = np.array( smart.integralResample(xh=modelwave, yh=modelflux, xl=telluric_data.wave)) modelwave = telluric_data.wave telluric_model = smart.Model() telluric_model.flux = modelflux telluric_model.wave = modelwave return telluric_model
def convolveTelluric(lsf, telluric_data, alpha=1.0, airmass='1.0', pwv='1.5'): """ Return a convolved telluric transmission model given a telluric data and lsf. """ # get a telluric standard model wavelow = telluric_data.wave[0] - 50 wavehigh = telluric_data.wave[-1] + 50 telluric_model = smart.getTelluric(wavelow=wavelow, wavehigh=wavehigh, airmass=airmass, pwv=pwv) telluric_model.flux **= alpha # lsf telluric_model.flux = smart.broaden(wave=telluric_model.wave, flux=telluric_model.flux, vbroad=lsf, rotate=False, gaussian=True) # resample telluric_model.flux = np.array( smart.integralResample(xh=telluric_model.wave, yh=telluric_model.flux, xl=telluric_data.wave)) telluric_model.wave = telluric_data.wave return telluric_model
def applyTelluric(model, tell_alpha=1.0, airmass=1.5, pwv=0.5): """ Apply the telluric model on the science model. Parameters ---------- model : model object BT Settl model alpha : float telluric scaling factor (the power on the flux) Returns ------- model : model object BT Settl model times the corresponding model """ # read in a telluric model wavelow = model.wave[0] - 10 wavehigh = model.wave[-1] + 10 #telluric_model = smart.getTelluric(wavelow=wavelow, wavehigh=wavehigh, alpha=alpha, airmass=airmass) telluric_model = smart.Model() telluric_model.wave, telluric_model.flux = smart.InterpTelluricModel( wavelow=wavelow, wavehigh=wavehigh, airmass=airmass, pwv=pwv) # apply the telluric alpha parameter telluric_model.flux = telluric_model.flux**(tell_alpha) #if len(model.wave) > len(telluric_model.wave): # print("The model has a higher resolution ({}) than the telluric model ({})."\ # .format(len(model.wave),len(telluric_model.wave))) # model.flux = np.array(smart.integralResample(xh=model.wave, # yh=model.flux, xl=telluric_model.wave)) # model.wave = telluric_model.wave # model.flux *= telluric_model.flux #elif len(model.wave) < len(telluric_model.wave): ## This should be always true telluric_model.flux = np.array( smart.integralResample(xh=telluric_model.wave, yh=telluric_model.flux, xl=model.wave)) telluric_model.wave = model.wave model.flux *= telluric_model.flux #elif len(model.wave) == len(telluric_model.wave): # model.flux *= telluric_model.flux return model
def telluric_mask(data, sigma=2.5, lsf=4.8, pwv=None, pixel_start=10, pixel_end=-30, outlier_rejection=2.5, diagnostic=True, save_to_path='./'): """ Routine to generate a mask for tellurics as the MCMC initialization. Parameters ---------- sigma : float; default 2.5 The sigma-clipping method to reject the outliers. lsf : float; default 4.8 The value of line spread function. The default is the normal value of LSF for Keck/NIRSPEC. pwv : float; default None precitable water vapor. The default is to run the chi2 grids of pwv to obtain the best pwv. pixel_start : int; default 10 The starting pixel to compute the mask. pixel_end : int; default -30 The ending pixel to compute the mask. outlier_rejection : float; default 2.5 The value for number * sigma to reject outliers diagnostic : bool; default True Generate the diagnostic plots for pwv-ch2 and model-data before/after masks save_to_path : str; default the current working directory Returns ------- mask : numpy array pwv : float airmass : float Example ------- >>> from smart.forward_model import mask >>> tell_mask, pwv, airmass = mask.telluric_mask(data, diagnostic=False) """ data.flux = data.flux[pixel_start:pixel_end] data.wave = data.wave[pixel_start:pixel_end] data.noise = data.noise[pixel_start:pixel_end] data0 = copy.deepcopy(data) # take the closest airmass from the header airmass = float(round(data.header['AIRMASS']*2)/2) if airmass > 3.0: airmass = 3.0 # simple chi2 comparison with different pwv if pwv is None: pwvs = [0.5, 1.0, 1.5, 2.5, 3.5, 5.0, 7.5, 10.0, 20.0] pwv_chi2 = [] for pwv in pwvs: data_tmp = copy.deepcopy(data) #data_tmp = smart.continuumTelluric(data=data_tmp, model=model_tmp) model_tmp = tellurics.makeTelluricModel(lsf=lsf, airmass=airmass, pwv=pwv, flux_offset=0, wave_offset=0, data=data_tmp, deg=10) model_tmp.flux = np.array(smart.integralResample(xh=model_tmp.wave, yh=model_tmp.flux, xl=data_tmp.wave)) model_tmp.wave = data_tmp.wave #plt.plot(data_tmp.wave, data_tmp.flux, 'k-') #plt.plot(model_tmp.wave, model_tmp.flux, 'r-') #plt.show() #plt.close() pwv_chi2.append(smart.chisquare(data_tmp, model_tmp)) # find the pwv with minimum chisquare pwv_chi2_array = np.array(pwv_chi2) if diagnostic: plt.plot(pwvs, pwv_chi2) plt.xlabel('pwv (mm)', fontsize=15) plt.ylabel('$\chi^2$', fontsize=15) plt.tight_layout() plt.savefig(save_to_path+'pwv_chi2.png') #plt.show() plt.close() pwv_min_index = np.where(pwv_chi2_array == np.min(pwv_chi2_array))[0][0] pwv = pwvs[pwv_min_index] data_tmp = copy.deepcopy(data) model = tellurics.makeTelluricModel(lsf=lsf, airmass=airmass, pwv=pwv, flux_offset=0, wave_offset=0, data=data_tmp) model_0 = copy.deepcopy(model) # generate the mask based on sigma clipping pixel = np.delete(np.arange(len(data.oriWave)), data.mask)[pixel_start: pixel_end] #pixel = np.delete(np.arange(len(data_tmp.oriWave)),data_tmp.mask) mask = pixel[np.where(np.abs(data_tmp.flux-model.flux) > outlier_rejection*np.std(data_tmp.flux-model.flux))] #plt.plot(data_tmp.wave, data_tmp.flux, 'k-') #plt.plot(model.wave, model.flux, 'r-') #plt.show() #plt.close() data_tmp.mask_custom(mask) data_tmp.flux = data_tmp.flux[pixel_start:pixel_end] data_tmp.wave = data_tmp.wave[pixel_start:pixel_end] data_tmp.noise = data_tmp.noise[pixel_start:pixel_end] #plt.plot(data_tmp.wave, data_tmp.flux, 'k-') #plt.plot(model.wave, model.flux, 'r-') #plt.show() #plt.close() # use curve_fit def tell_model_fit(wave, airmass, pwv, flux_offset, wave_offset): model = tellurics.makeTelluricModel(lsf=lsf, airmass=airmass, pwv=pwv, flux_offset=flux_offset, wave_offset=wave_offset, data=data_tmp) return model.flux print('initial airmass and pwv', airmass, pwv) flux_med = np.median(data_tmp.flux) p0 = [airmass, pwv, 0, 0] bounds = ([airmass-0.5, pwv-0.5, -flux_med*0.05, -0.05], [airmass+0.5, pwv+0.5, flux_med*0.05, 0.05]) popt, pcov = curve_fit(tell_model_fit, data_tmp.wave, data_tmp.flux, p0=p0, bounds=bounds) airmass, pwv, flux_offset, wave_offset = popt[0], popt[1], popt[2], popt[3] print('best-fit airmass, pwv, flux_offset, wave_offset', airmass, pwv, flux_offset, wave_offset) model = tellurics.makeTelluricModel(lsf=lsf, airmass=airmass, pwv=pwv, flux_offset=0, wave_offset=0, data=data_tmp) print('old telluric mask', mask) pixel = np.delete(np.arange(len(data_tmp.oriWave)), mask)[pixel_start: pixel_end] #print('len pixel, data, model', len(pixel), len(data_tmp.wave), len(model.wave)) mask = pixel[np.where(np.abs(data_tmp.flux-model.flux) > outlier_rejection*np.std(data_tmp.flux-model.flux))] # combine the masks mask = np.union1d(mask,np.array(data_tmp.mask)) print('new telluric mask', mask) data.mask_custom(mask) data.flux = data.flux[pixel_start:pixel_end] data.wave = data.wave[pixel_start:pixel_end] data.noise = data.noise[pixel_start:pixel_end] print(data.mask) #plt.plot(data.wave, data.flux, 'k-') #plt.plot(model.wave, model.flux, 'r-') #plt.plot(model_0.wave, model_0.flux, 'b-', alpha=0.5) #plt.show() #plt.close() if diagnostic: data.flux = data.flux[pixel_start:pixel_end] data.wave = data.wave[pixel_start:pixel_end] data.noise = data.noise[pixel_start:pixel_end] model = tellurics.makeTelluricModel(lsf=lsf, airmass=airmass, pwv=pwv, flux_offset=0, wave_offset=0, data=data) plt.plot(data0.wave, data0.flux, 'k-', label='original data', alpha=0.5) plt.plot(data.wave, data.flux, 'k-', label='masked data') plt.plot(model.wave, model.flux, 'r-', alpha=0.7) plt.plot(data.wave, data.flux-model.flux, 'r-') plt.xlabel('$\lambda (\AA)$') plt.ylabel('$F_{\lambda}$') plt.savefig(save_to_path+'telluric_data_model_mask.png') #plt.show() plt.close() return mask.tolist(), pwv, airmass
def continuumTelluric(data, model=None): """ Return a continnum telluric standard data. Default: return a telluric flux of mean 1. Parameters ---------- data: spectrum object The input telluric data to be continuum corrected model: (optional) model object The telluric model to obtain the mean flux Instead of 1 as in default, it returns a constant shift by the difference between the mean flux of the telluric data and that of the telluric model Returns ------- data: spectrum object continuum corrected telluric data Examples -------- >>> import smart >>> smart.continuumTelluric(data) >>> smart.continuumTelluric(data,model) """ if model is None: wavelow = data.wave[0] - 20 wavehigh = data.wave[-1] + 20 model = smart.getTelluric(wavelow, wavehigh) if not data.applymask: data2 = copy.deepcopy(data) data.maskBySigmas(sigma=1.5) else: data2 = copy.deepcopy(data) data.wave = data.wave[10:-30] data.flux = data.flux[10:-30] data.noise = data.noise[10:-30] # plt.plot(data.wave,data.flux, alpha=0.5) # plt.plot(data2.wave,data2.flux, alpha=0.5) # plt.show() # plt.close() if data.order == 35: # O35 has a voigt absorption profile popt, pcov = curve_fit( voigt_profile, data.wave[20:-20], data.flux[20:-20], p0=[21660, 2000, 0.1, 0.1, 0.01, 0.1, 10000, 1000], maxfev=10000) #plt.plot(data.wave,data.flux,'k-',alpha=0.5) #plt.plot(data.wave,voigt_profile(data.wave,*popt),'r-',alpha=0.5) #plt.show() #plt.close() const = np.mean(data.flux/voigt_profile(data.wave, *popt))\ -np.mean(model.flux) data.flux = data.flux / voigt_profile(data.wave, *popt) - const data.noise = data.noise / voigt_profile(data.wave, *popt) #if not data.applymask: data2.flux = data2.flux / voigt_profile(data2.wave, *popt) - const data2.noise = data2.noise / voigt_profile(data2.wave, *popt) data = data2 elif data.order == 38 or data.order == 30: # O38 has rich absorption features def fit_continuum_O38(x, a, b, **kwargs): flux = kwargs.get('flux', data.flux) linear = a * x + b return flux / linear model2 = copy.deepcopy(model) model2.flux = smart.broaden(wave=model2.wave, flux=model2.flux, vbroad=4.8, rotate=False, gaussian=True) model2.flux = np.array( smart.integralResample(xh=model2.wave, yh=model2.flux, xl=data.wave)) model2.wave = data.wave popt, pcov = curve_fit(fit_continuum_O38, data.wave, model2.flux, p0=[8.54253062e+00, -166000]) #const = np.mean(data.flux/linear_fit(data.wave, *popt))-np.mean(model.flux) #data.flux = data.flux/linear_fit(data.wave, *popt) - const data.flux = data.flux / linear_fit(data.wave, *popt) data.noise = data.noise / linear_fit(data.wave, *popt) #if not data.applymask: data2.flux /= linear_fit(data2.wave, *popt) data2.noise /= linear_fit(data2.wave, *popt) data = data2 elif data.order == 55: popt, pcov = curve_fit(_continuumFit, data.wave, data.flux) #if data.applymask: # data.flux = data.flux/_continuumFit(data.wave, *popt) # data.noise = data.noise/_continuumFit(data.wave, *popt) # # factor = np.max(data.flux) # data.flux /= factor # data.noise /= factor # data.flux /= 0.93 # data.noise /= 0.93 #elif not data.applymask: data2.flux = data2.flux / _continuumFit(data2.wave, *popt) data2.noise = data2.noise / _continuumFit(data2.wave, *popt) factor = np.max(data2.flux) data2.flux = data2.flux / factor data2.noise = data2.noise / factor data2.flux /= 0.93 data2.noise /= 0.93 data = data2 elif data.order == 56: # select the highest points to fit a polynomial x1 = np.max(data.flux[0:100]) x2 = np.max(data.flux[100:150]) x3 = np.max(data.flux[200:300]) x4 = np.max(data.flux[300:400]) x5 = np.max(data.flux[600:700]) x6 = np.max(data.flux[700:800]) a = [ float(data.wave[np.where(data.flux == x1)]), float(data.wave[np.where(data.flux == x2)]), float(data.wave[np.where(data.flux == x3)]), float(data.wave[np.where(data.flux == x4)]), float(data.wave[np.where(data.flux == x5)]), float(data.wave[np.where(data.flux == x6)]) ] b = [x1, x2, x3, x4, x5, x6] popt, pcov = curve_fit(_continuumFit, a, b) data.flux = data.flux / _continuumFit(data.wave, *popt) * 0.85 data.noise = data.noise / _continuumFit(data.wave, *popt) * 0.85 #if not data.applymask: data2.flux = data2.flux / _continuumFit(data2.wave, *popt) * 0.85 data2.noise = data2.noise / _continuumFit(data2.wave, *popt) * 0.85 data = data2 elif data.order == 59: #wave0 = int(data.wave[np.where(data.flux==np.min(data.flux))]) popt, pcov = curve_fit( voigt_profile, data.wave, data.flux, p0=[12820, 2000, 0.1, 0.1, 0.01, 0.1, 10000, 1000], maxfev=100000) #p0=[wave0,2000,0.1,0.1,0.01,0.1,10000,1000], #maxfev=10000) data.flux /= voigt_profile(data.wave, *popt) data.noise /= voigt_profile(data.wave, *popt) #if not data.applymask: data2.flux /= voigt_profile(data2.wave, *popt) data2.noise /= voigt_profile(data2.wave, *popt) data = data2 #plt.plot(data.wave,data.flux) #plt.show() #plt.close() ## this is not true in general!! elif data.order == 65: # O65 is best mateched by a gaussian absorption feature popt, pcov = curve_fit(gaus_absorption_only, data.wave, data.flux, p0=[11660, 50, 2000, 2000], maxfev=100000) const = np.mean( data.flux / gaus_absorption_only(data.wave, *popt)) - np.mean( model.flux) data.flux = data.flux / gaus_absorption_only(data.wave, *popt) - const data.noise /= gaus_absorption_only(data.wave, *popt) #if not data.applymask: data2.flux = data2.flux / gaus_absorption_only(data2.wave, * popt) - const data2.noise /= gaus_absorption_only(data2.wave, *popt) data = data2 else: # this second order polynomial continnum correction # works for the O33, O34, O36, and O37 popt, pcov = curve_fit(_continuumFit, data.wave, data.flux) const = np.mean(data.flux / _continuumFit(data.wave, *popt)) - np.mean( model.flux) if data.order == 57: const = 0 data.flux = data.flux / _continuumFit(data.wave, *popt) - const data.noise = data.noise / _continuumFit(data.wave, *popt) #if not data.applymask: data2.flux = data2.flux / _continuumFit(data2.wave, *popt) - const data2.noise = data2.noise / _continuumFit(data2.wave, *popt) data = data2 return data
def makeModel(teff, logg=5, metal=0, vsini=1, rv=0, tell_alpha=1.0, airmass=1.0, pwv=0.5, wave_offset=0, flux_offset=0, **kwargs): """ Return a forward model. Parameters ---------- teff : effective temperature data : an input science data used for continuum correction Optional Parameters ------------------- Returns ------- model: a synthesized model """ # read in the parameters order = kwargs.get('order', '33') modelset = kwargs.get('modelset', 'btsettl08') instrument = kwargs.get('instrument', 'nirspec') veiling = kwargs.get('veiling', 0) # flux veiling parameter lsf = kwargs.get('lsf', 4.5) # instrumental LSF if instrument == 'apogee': try: import apogee_tools as ap except ImportError: print( 'Need to install the package "apogee_tools" (https://github.com/jbirky/apogee_tools) \n' ) xlsf = kwargs.get('xlsf', np.linspace(-7., 7., 43)) # APOGEE instrumental LSF sampling wave_off1 = kwargs.get('wave_off1') # wavelength offset for chip a wave_off2 = kwargs.get('wave_off2') # wavelength offset for chip b wave_off3 = kwargs.get('wave_off3') # wavelength offset for chip c c0_1 = kwargs.get('c0_1') # constant flux offset for chip a c0_2 = kwargs.get('c0_2') # linear flux offset for chip a c1_1 = kwargs.get('c1_1') # constant flux offset for chip b c1_2 = kwargs.get('c1_2') # linear flux offset for chip b c2_1 = kwargs.get('c2_1') # constant flux offset for chip c c2_2 = kwargs.get('c2_2') # linear flux offset for chip c tell = kwargs.get('tell', True) # apply telluric #tell_alpha = kwargs.get('tell_alpha', 1.0) # Telluric alpha power binary = kwargs.get('binary', False) # make a binary model # assume the secondary has the same metallicity if binary: teff2 = kwargs.get('teff2') logg2 = kwargs.get('logg2') rv2 = kwargs.get('rv2') vsini2 = kwargs.get('vsini2') flux_scale = kwargs.get('flux_scale', 0.8) data = kwargs.get('data', None) # for continuum correction and resampling output_stellar_model = kwargs.get('output_stellar_model', False) if data is not None and instrument == 'nirspec': order = data.order # read in a model #print('teff ',teff,'logg ',logg, 'z', z, 'order', order, 'modelset', modelset) #print('teff ',type(teff),'logg ',type(logg), 'z', type(z), 'order', type(order), 'modelset', type(modelset)) model = smart.Model(teff=teff, logg=logg, metal=metal, order=str(order), modelset=modelset, instrument=instrument) #elif data is not None and instrument == 'apogee': elif instrument == 'apogee': model = smart.Model(teff=teff, logg=logg, metal=metal, modelset=modelset, instrument=instrument) # Dirty fix here model.wave = model.wave[np.where(model.flux != 0)] model.flux = model.flux[np.where(model.flux != 0)] # apply vmicro vmicro = 2.478 - 0.325 * logg model.flux = smart.broaden(wave=model.wave, flux=model.flux, vbroad=vmicro, rotate=False, gaussian=True) elif data is None and instrument == 'nirspec': model = smart.Model(teff=teff, logg=logg, metal=metal, order=str(order), modelset=modelset, instrument=instrument) # wavelength offset #model.wave += wave_offset # apply vsini model.flux = smart.broaden(wave=model.wave, flux=model.flux, vbroad=vsini, rotate=True, gaussian=False) # apply rv (including the barycentric correction) model.wave = rvShift(model.wave, rv=rv) # flux veiling model.flux += veiling ## if binary is True: make a binary model if binary: model2 = smart.Model(teff=teff2, logg=logg2, metal=metal, order=str(order), modelset=modelset, instrument=instrument) # apply vsini model2.flux = smart.broaden(wave=model2.wave, flux=model2.flux, vbroad=vsini2, rotate=True, gaussian=False) # apply rv (including the barycentric correction) model2.wave = rvShift(model2.wave, rv=rv2) # linearly interpolate the model2 onto the model1 grid fit = interp1d(model2.wave, model2.flux) select_wavelength = np.where((model.wave < model2.wave[-1]) & (model.wave > model2.wave[0])) model.flux = model.flux[select_wavelength] model.wave = model.wave[select_wavelength] # combine the models together and scale the secondary flux model.flux += flux_scale * fit(model.wave) if output_stellar_model: stellar_model = copy.deepcopy(model) if binary: model2.flux = flux_scale * fit(model.wave) # apply telluric if tell is True: model = smart.applyTelluric(model=model, tell_alpha=tell_alpha, airmass=airmass, pwv=pwv) # instrumental LSF if instrument == 'nirspec': model.flux = smart.broaden(wave=model.wave, flux=model.flux, vbroad=lsf, rotate=False, gaussian=True) elif instrument == 'apogee': model.flux = ap.apogee_hack.spec.lsf.convolve(model.wave, model.flux, lsf=lsf, xlsf=xlsf).flatten() model.wave = ap.apogee_hack.spec.lsf.apStarWavegrid() # Remove the NANs model.wave = model.wave[~np.isnan(model.flux)] model.flux = model.flux[~np.isnan(model.flux)] if output_stellar_model: stellar_model.flux = smart.broaden(wave=stellar_model.wave, flux=stellar_model.flux, vbroad=lsf, rotate=False, gaussian=True) if binary: model2.flux = smart.broaden(wave=model2.wave, flux=model2.flux, vbroad=lsf, rotate=False, gaussian=True) # add a fringe pattern to the model #model.flux *= (1+amp*np.sin(freq*(model.wave-phase))) # wavelength offset model.wave += wave_offset if output_stellar_model: stellar_model.wave += wave_offset if binary: model2.wave = stellar_model.wave # integral resampling if data is not None: if instrument == 'nirspec': model.flux = np.array( smart.integralResample(xh=model.wave, yh=model.flux, xl=data.wave)) model.wave = data.wave if output_stellar_model: stellar_model.flux = np.array( smart.integralResample(xh=stellar_model.wave, yh=stellar_model.flux, xl=data.wave)) stellar_model.wave = data.wave if binary: model2.flux = np.array( smart.integralResample(xh=model2.wave, yh=model2.flux, xl=data.wave)) model2.wave = data.wave # contunuum correction if data.instrument == 'nirspec': niter = 5 # continuum iteration if output_stellar_model: model, cont_factor = smart.continuum(data=data, mdl=model, prop=True) for i in range(niter): model, cont_factor2 = smart.continuum(data=data, mdl=model, prop=True) cont_factor *= cont_factor2 stellar_model.flux *= cont_factor if binary: model2.flux *= cont_factor else: model = smart.continuum(data=data, mdl=model) for i in range(niter): model = smart.continuum(data=data, mdl=model) elif data.instrument == 'apogee': ## set the order in the continuum fit deg = 5 ## because of the APOGEE bands, continuum is corrected from three pieces of the spectra data0 = copy.deepcopy(data) model0 = copy.deepcopy(model) # wavelength offset model0.wave += wave_off1 range0 = np.where((data0.wave >= data.oriWave0[0][-1]) & (data0.wave <= data.oriWave0[0][0])) data0.wave = data0.wave[range0] data0.flux = data0.flux[range0] if data0.wave[0] > data0.wave[-1]: data0.wave = data0.wave[::-1] data0.flux = data0.flux[::-1] model0.flux = np.array( smart.integralResample(xh=model0.wave, yh=model0.flux, xl=data0.wave)) model0.wave = data0.wave model0 = smart.continuum(data=data0, mdl=model0, deg=deg) # flux corrections model0.flux = (model0.flux + c0_1) * np.e**(-c0_2) data1 = copy.deepcopy(data) model1 = copy.deepcopy(model) # wavelength offset model1.wave += wave_off2 range1 = np.where((data1.wave >= data.oriWave0[1][-1]) & (data1.wave <= data.oriWave0[1][0])) data1.wave = data1.wave[range1] data1.flux = data1.flux[range1] if data1.wave[0] > data1.wave[-1]: data1.wave = data1.wave[::-1] data1.flux = data1.flux[::-1] model1.flux = np.array( smart.integralResample(xh=model1.wave, yh=model1.flux, xl=data1.wave)) model1.wave = data1.wave model1 = smart.continuum(data=data1, mdl=model1, deg=deg) # flux corrections model1.flux = (model1.flux + c1_1) * np.e**(-c1_2) data2 = copy.deepcopy(data) model2 = copy.deepcopy(model) # wavelength offset model2.wave += wave_off3 range2 = np.where((data2.wave >= data.oriWave0[2][-1]) & (data2.wave <= data.oriWave0[2][0])) data2.wave = data2.wave[range2] data2.flux = data2.flux[range2] if data2.wave[0] > data2.wave[-1]: data2.wave = data2.wave[::-1] data2.flux = data2.flux[::-1] model2.flux = np.array( smart.integralResample(xh=model2.wave, yh=model2.flux, xl=data2.wave)) model2.wave = data2.wave model2 = smart.continuum(data=data2, mdl=model2, deg=deg) # flux corrections model2.flux = (model2.flux + c2_1) * np.e**(-c2_2) ## scale the flux to be the same as the data #model0.flux *= (np.std(data0.flux)/np.std(model0.flux)) #model0.flux -= np.median(model0.flux) - np.median(data0.flux) #model1.flux *= (np.std(data1.flux)/np.std(model1.flux)) #model1.flux -= np.median(model1.flux) - np.median(data1.flux) #model2.flux *= (np.std(data2.flux)/np.std(model2.flux)) #model2.flux -= np.median(model2.flux) - np.median(data2.flux) model.flux = np.array( list(model2.flux) + list(model1.flux) + list(model0.flux)) model.wave = np.array( list(model2.wave) + list(model1.wave) + list(model0.wave)) if instrument == 'nirspec': # flux offset model.flux += flux_offset if output_stellar_model: stellar_model.flux += flux_offset if binary: model2.flux += flux_offset #model.flux **= (1 + flux_exponent_offset) if output_stellar_model: if not binary: return model, stellar_model else: return model, stellar_model, model2 else: return model
def coadd(self, sp, method='pixel'): """ Coadd individual extractions, either in pixel space or wavelength space. Parameters ---------- sp : Spectrum object spectrum to be coadded method : 'pixel' or 'wavelength' coadd based on adding pixels or wavelength If 'wavelength', the second spectrum would be 10x supersample and then cross correlated to be optimally shifted and coadded Returns ------- self : Spectrum object coadded spectra """ if method == 'pixel': w1 = 1 / self.oriNoise**2 w2 = 1 / sp.oriNoise**2 self.oriFlux = (self.oriFlux * w1 + sp.oriFlux * w2) / (w1 + w2) self.oriNoise = np.sqrt(1 / (w1 + w2)) ## set up masking criteria self.avgFlux = np.mean(self.oriFlux) self.stdFlux = np.std(self.oriFlux) self.smoothFlux = self.oriFlux ## set the outliers as the flux below if self.applymask: self.smoothFlux[self.smoothFlux <= self.avgFlux - 2 * self.stdFlux] = 0 self.mask = np.where(self.smoothFlux <= 0) else: self.mask = [] self.wave = np.delete(self.oriWave, list(self.mask)) self.flux = np.delete(self.oriFlux, list(self.mask)) self.noise = np.delete(self.oriNoise, list(self.mask)) elif method == 'wavelength': self_supers = copy.deepcopy(self) g = interpolate.interp1d(self.wave, self.flux) sp_supers = copy.deepcopy(sp) f = interpolate.interp1d(sp.wave, sp.flux) ## 10x supersample the average difference of ## the wavelength #step0 = np.mean(np.diff(self.wave))/10 #self_supers.wave = np.arange(self.wave[0], # self.wave[-1],step0) self_supers.flux = g(self_supers.wave) self_supers.oriWave = np.arange( self.oriWave[0], self.oriWave[-1], (self.oriWave[-1] - self.oriWave[0]) / 10240) g1 = interpolate.interp1d(self.oriWave, self.oriFlux) self_supers.oriFlux = g1(self_supers.oriWave) #step = np.mean(np.diff(sp.wave))/10 #sp_supers.wave = np.arange(sp.wave[0],sp.wave[-1],step) #sp_supers.flux = f(sp_supers.wave) sp_supers.oriWave = np.arange(sp.oriWave[0], sp.oriWave[-1], (sp.oriWave[-1] - sp.oriWave[0]) / 10240) f1 = interpolate.interp1d(sp.oriWave, sp.oriFlux) sp_supers.oriFlux = f1(sp_supers.oriWave) ## calculate the max cross correlation value def xcorr(a0, b0, shift): """ Shift is the index number after supersampling both of the spectra. """ a = copy.deepcopy(a0) b = copy.deepcopy(b0) ## shift the wavelength of b length = b.oriFlux.shape[0] if shift >= 0: mask_a = np.arange(0, shift, 1) a.oriFlux = np.delete(a.oriFlux, mask_a) mask_b = np.arange(length - 1, length - shift - 1, -1) b.oriFlux = np.delete(b.oriFlux, mask_b) elif shift < 0: mask_a = np.arange(length - 1, length + shift - 1, -1) a.oriFlux = np.delete(a.oriFlux, mask_a) mask_b = np.arange(0, -shift, 1) b.oriFlux = np.delete(b.oriFlux, mask_b) ## shift the wavelength of b #b.wave += shift * step ## discard the points where the wavelength values ## are larger #condition = (a.wave > b.wave[0]) & (a.wave < b.wave[-1]) #a.flux = a.flux[np.where(condition)] #a.wave = a.wave[np.where(condition)] ## resampling the telluric model #b.flux = np.array(smart.integralResample(xh=b.wave, # yh=b.flux, xl=a.wave)) return np.inner(a.oriFlux, b.oriFlux)/\ (np.average(a.oriFlux)*np.average(b.oriFlux))/a.oriFlux.shape[0] xcorr_list = [] ## mask the ending pixels self_supers2 = copy.deepcopy(self_supers) sp_supers2 = copy.deepcopy(sp_supers) self_supers2.wave = self_supers2.wave[1000:-1000] self_supers2.flux = self_supers2.flux[1000:-1000] sp_supers2.wave = sp_supers2.wave[1000:-1000] sp_supers2.flux = sp_supers2.flux[1000:-1000] for shift in np.arange(-10, 10, 1): xcorr_list.append(xcorr(self_supers2, sp_supers2, shift)) ## dignostic plot for cc result fig, ax = plt.subplots() ax.plot(np.arange(-10, 10, 1), np.array(xcorr_list), 'k-') plt.show() plt.close() step = np.absolute(np.mean(np.diff(sp_supers.wave))) bestshift = np.arange(-10 * step, 10 * step, step)[np.argmax(xcorr_list)] sp_supers.oriWave += bestshift ## discard the points where the wavelength values ## are larger condition = (self.oriWave > sp_supers.oriWave[0])\ & (self.oriWave < sp_supers.oriWave[-1]) self.oriFlux = self.oriFlux[np.where(condition)] self.oriWave = self.oriWave[np.where(condition)] self.oriNoise = self.oriNoise[np.where(condition)] sp_supers.oriNoise = sp_supers.oriNoise[np.where(condition)] sp_supers.oriFlux = np.array( smart.integralResample(xh=sp_supers.oriWave, yh=sp_supers.oriFlux, xl=self.oriWave)) w1 = 1 / self.oriNoise**2 w2 = 1 / sp_supers.oriNoise**2 self.oriFlux = (self.oriFlux * w1 + sp_supers.oriFlux * w2) / (w1 + w2) self.oriNoise = np.sqrt(1 / (w1 + w2)) ## set up masking criteria self.avgFlux = np.mean(self.oriFlux) self.stdFlux = np.std(self.oriFlux) self.smoothFlux = self.oriFlux ## set the outliers as the flux below self.smoothFlux[self.smoothFlux <= self.avgFlux - 2 * self.stdFlux] = 0 self.mask = np.where(self.smoothFlux <= 0) self.wave = np.delete(self.oriWave, list(self.mask)) self.flux = np.delete(self.oriFlux, list(self.mask)) self.noise = np.delete(self.oriNoise, list(self.mask)) return self