def prepare_cannon_model(model, spec, dointerp=False): """ This prepares a Cannon model or list of models for an observed spectrum. The models are trimmed, rebinned, convolved to the spectrum LSF's and finally interpolated onto the spectrum's wavelength scale. The final interpolation can be omitted by setting dointerp=False (the default). This can be useful if the model is to interpolated multiple times on differen wavelength scales (e.g. for multiple velocities or logarithmic wavelength scales for cross-correlation). Parameters ---------- model : Cannon model or list The Cannon model(s) to prepare for "spec". spec : Spec1D object The observed spectrum for which to prepare the Cannon model(s). dointerp : bool, optional Do the interpolation onto the observed wavelength scale. The default is False. Returns ------- omodel : Cannon model or list The Cannon model or list of models prepared for "spec". Examples -------- omodel = prepare_cannon_model(model,spec) """ if spec.wave is None: raise Exception('No wavelength in observed spectrum') if spec.lsf is None: raise Exception('No LSF in observed spectrum') if type(model) is not list: if model.dispersion is None: raise Exception('No model wavelength information') if type(model) is list: outmodel = [] for i in range(len(model)): model1 = model[i] omodel1 = prepare_cannon_model(model1, spec, dointerp=dointerp) outmodel.append(omodel1) else: # Convert wavelength from air->vacuum or vice versa if model.wavevac != spec.wavevac: # Air -> Vacuum if spec.wavevac is True: model.dispersion = astro.airtovac(model.dispersion) model.wavevac = True # Vacuum -> Air else: model.dispersion = astro.vactoair(model.dispersion) model.wavevac = False # Observed spectrum values wave = spec.wave ndim = wave.ndim if ndim == 2: npix, norder = wave.shape if ndim == 1: norder = 1 npix = len(wave) wave = wave.reshape(npix, norder) # Loop over the orders outmodel = [] for o in range(norder): w = wave[:, o] w0 = np.min(w) w1 = np.max(w) dw = dln.slope(w) dw = np.hstack((dw, dw[-1])) dw = np.abs(dw) meddw = np.median(dw) npix = len(w) if (np.min(model.dispersion) > w0) | (np.max(model.dispersion) < w1): raise Exception( 'Model does not cover the observed wavelength range') # Trim nextend = int(np.ceil(len(w) * 0.25)) # extend 25% on each end nextend = np.maximum(nextend, 200) # or 200 pixels if (np.min(model.dispersion) < (w0 - nextend * meddw)) | (np.max(model.dispersion) > (w1 + nextend * meddw)): tmodel = trim_cannon_model(model, w0=w0 - nextend * meddw, w1=w1 + nextend * meddw) #if (np.min(model.dispersion)<(w0-50*meddw)) | (np.max(model.dispersion)>(w1+50*meddw)): # tmodel = trim_cannon_model(model,w0=w0-20*meddw,w1=w1+20*meddw) #if (np.min(model.dispersion)<(w0-1000.0*w0/cspeed)) | (np.max(model.dispersion)>(w1+1000.0*w1/cspeed)): # # allow for up to 1000 km/s offset on each end # tmodel = trim_cannon_model(model,w0=w0-1000.0*w0/cspeed,w1=w1+1000.0*w1/cspeed) tmodel.trim = True else: tmodel = cannon_copy(model) tmodel.trim = False # Rebin # get LSF FWHM (A) for a handful of positions across the spectrum xp = np.arange(npix // 20) * 20 fwhm = spec.lsf.fwhm(w[xp], xtype='Wave', order=o) # FWHM is in units of lsf.xtype, convert to wavelength/angstroms, if necessary if spec.lsf.xtype.lower().find('pix') > -1: dw1 = dln.interp(w, dw, w[xp], assume_sorted=False) fwhm *= dw1 # convert FWHM (A) in number of model pixels at those positions dwmod = dln.slope(tmodel.dispersion) dwmod = np.hstack((dwmod, dwmod[-1])) xpmod = interp1d(tmodel.dispersion, np.arange(len(tmodel.dispersion)), kind='cubic', bounds_error=False, fill_value=(np.nan, np.nan), assume_sorted=False)(w[xp]) xpmod = np.round(xpmod).astype(int) fwhmpix = fwhm / dwmod[xpmod] # need at least ~4 pixels per LSF FWHM across the spectrum # using 3 affects the final profile shape nbin = np.round(np.min(fwhmpix) // 4).astype(int) if nbin == 0: import pdb nbin = 1 print(spec.filename, 'Model has lower resolution than the observed spectrum', fwhmpix.min()) #raise Exception('Model has lower resolution than the observed spectrum',spec.filename,fwhmpix.min()) if nbin > 1: rmodel = rebin_cannon_model(tmodel, nbin) rmodel.rebin = True else: rmodel = cannon_copy(tmodel) rmodel.rebin = False # Convolve lsf = spec.lsf.anyarray(rmodel.dispersion, xtype='Wave', order=o) #import pdb; pdb.set_trace() cmodel = convolve_cannon_model(rmodel, lsf) cmodel.convolve = True # Interpolate if dointerp is True: omodel = interp_cannon_model(cmodel, wout=spec.wave) omodel.interp = True else: omodel = cannon_copy(cmodel) omodel.interp = False # Order information omodel.norder = norder omodel.order = o # # Copy continuum information # if hasattr(model,'continuum'): # omodel.continuum = cannon_copy(model.continuum) # Append to final output outmodel.append(omodel) # Single-element list if (type(outmodel) is list) & (len(outmodel) == 1): outmodel = outmodel[0] return outmodel
def anyarray(self,x,xtype='pixels',order=0,original=True): """ Return the LSF of the spectrum at specific locations or on a new wavelength array. Parameters ---------- x : array The array of x-values for which to return the LSF. xtype : string, optional The type of x-values input, either 'wave' or 'pixels'. The default is 'pixels'. order : int, optional The order for which to retrn the LSF array. The default is 0. original : bool, optional If original=True, then the LSFs are returned on the original wavelength scale but at the centers given in "x". If the LSF is desired on a completely new wavelength scale (given by "x"), then orignal=False should be used instead. Returns ------- lsf : array The 2D array of LSF values. Examples -------- lsf = lsf.anyarray([100,200]) """ nx = len(x) # returns xsigma in units of self.xtype not necessarily xtype xsigma = self.sigma(x,xtype=xtype,order=order) # Get wavelength and pixel arrays if xtype.lower().find('pix') > -1: w = self.pix2wave(x,order=order) else: w = x.copy() x = self.wave2pix(w,order=order) # Convert sigma from wavelength to pixels, if necessary if self.xtype.lower().find('wave') > -1: wsigma = xsigma.copy() # On original wavelength scale if original is True: w1 = self.pix2wave(np.array(x)+1,order=order) dw = w1-w # New wavelength/pixel scale else: dw = dln.slope(w) dw = np.hstack((dw,dw[-1])) xsigma = wsigma / dw # Figure out nLSF pixels needed, +/-3 sigma nlsf = np.int(np.round(np.max(xsigma)*6)) if nlsf % 2 == 0: nlsf+=1 # must be odd # Make LSF array lsf = np.zeros((nx,nlsf)) xlsf = np.arange(nlsf)-nlsf//2 xlsf2 = np.repeat(xlsf,nx).reshape((nlsf,nx)).T xsigma2 = np.repeat(xsigma,nlsf).reshape((nx,nlsf)) lsf = np.exp(-0.5*xlsf2**2 / xsigma2**2) / (np.sqrt(2*np.pi)*xsigma2) lsf[lsf<0.] = 0. lsf /= np.tile(np.sum(lsf,axis=1),(nlsf,1)).T # should I use gaussbin???? return lsf
def array(self,order=None): """ Return the full LSF for the spectrum. Parameters ---------- order : int, optional The order for which to return the full LSF array, if there are multiple orders. The default is None which means the LSF array for all orders is returned. Returns ------- lsf : array The full LSF array for the spectrum (or just one order). Examples -------- lsf = lsf.array() """ # Return what we already have if self._array is not None: if (self.ndim==2) & (order is not None): # [Npix,Nlsf,Norder] return self._array[:,:,order] else: return self._array # Loop over orders and figure out how many Nlsf pixels we need # must be same across all orders wave = self.wave.reshape(self.npix,self.norder) nlsfarr = np.zeros(self.norder,dtype=int) xsigma = np.zeros((self.npix,self.norder),dtype=np.float64) for o in range(self.norder): x = np.arange(self.npix) xsig = self.sigma(order=o) w = wave[:,o] # Convert sigma from wavelength to pixels, if necessary if self.xtype.lower().find('wave') > -1: wsig = xsig.copy() dw = dln.slope(w) dw = np.hstack((dw,dw[-1])) xsig = wsig / dw xsigma[:,o] = xsig # Figure out nLSF pixels needed, +/-3 sigma nlsf = np.int(np.round(np.max(xsigma)*6)) if nlsf % 2 == 0: nlsf+=1 # must be odd nlsfarr[o] = nlsf nlsf = np.max(np.array(nlsfarr)) # Make LSF array lsf = np.zeros((self.npix,nlsf,self.norder)) # Loop over orders for o in range(self.norder): xlsf = np.arange(nlsf)-nlsf//2 xlsf2 = np.repeat(xlsf,self.npix).reshape((nlsf,self.npix)).T lsf1 = np.zeros((self.npix,nlsf)) xsig = xsigma[:,o] xsigma2 = np.repeat(xsig,nlsf).reshape((self.npix,nlsf)) lsf1 = np.exp(-0.5*xlsf2**2 / xsigma2**2) / (np.sqrt(2*np.pi)*xsigma2) # should I use gaussbin???? lsf1[lsf1<0.] = 0. lsf1 /= np.tile(np.sum(lsf1,axis=1),(nlsf,1)).T lsf[:,:,o] = lsf1 # if only one order then reshape if self.ndim==1: lsf=lsf.reshape(self.npix,nlsf) self._array = lsf # save for next time return lsf
def make_logwave_scale(wave, vel=1000.0): """ Create a logarithmic wavelength scale for a given wavelength array. This is used by rv.fit() to create a logarithmic wavelength scale for cross-correlation. If the input wavelength array is 2D with multiple orders (trailing dimension), then the output wavelength array will extend beyond the boundaries of some of the orders. These pixels will have to be "padded" with dummy/masked-out pixels. Parameters ---------- wave : array Input wavelength array, 1D or 2D with multiple orders. vel : float, optional Maximum velocity shift to allow for. Default is 1000. Returns ------- fwave : array New logarithmic wavelength array. Example ------- .. code-block:: python fwave = make_logwave_scale(wave) """ # If the existing wavelength scale is logarithmic then use it, just extend # on either side vel = np.abs(vel) wave = np.float64(wave) nw = len(wave) wr = dln.minmax(wave) # Multi-order wavelength array if wave.ndim == 2: norder = wave.shape[1] # Ranges wr = np.empty((2, norder), np.float64) wlo = np.empty(norder, np.float64) whi = np.empty(norder, np.float64) for i in range(norder): wr[:, i] = dln.minmax(wave[:, i]) wlo[i] = wr[0, i] - vel / cspeed * wr[0, i] whi[i] = wr[1, i] + vel / cspeed * wr[1, i] # For multi-order wavelengths, the final wavelength solution will extend # beyond the boundary of the original wavelength solution (at the end). # The extra pixels will have to be "padded" with masked out values. # We want the same logarithmic step for all order dw = np.log10(np.float64(wave[1:, :])) - np.log10( np.float64(wave[0:-1, :])) dwlog = np.median(np.abs(dw)) # positive # Extend at the ends if vel > 0: nlo = np.empty(norder, int) nhi = np.empty(norder, int) for i in range(norder): nlo[i] = np.int( np.ceil((np.log10(np.float64(wr[0, i])) - np.log10(np.float64(wlo[i]))) / dwlog)) nhi[i] = np.int( np.ceil((np.log10(np.float64(whi[i])) - np.log10(np.float64(wr[1, i]))) / dwlog)) # Use the maximum over all orders nlo = np.max(nlo) nhi = np.max(nhi) else: nlo = 0 nhi = 0 # Number of pixels for the input wavelength range n = np.empty(norder, int) for i in range(norder): if vel == 0: n[i] = np.int((np.log10(np.float64(wr[1, i])) - np.log10(np.float64(wr[0, i]))) / dwlog) else: n[i] = np.int( np.ceil((np.log10(np.float64(wr[1, i])) - np.log10(np.float64(wr[0, i]))) / dwlog)) # maximum over all orders n = np.max(n) # Final number of pixels nf = n + nlo + nhi # Final wavelength array fwave = np.empty((nf, norder), np.float64) for i in range(norder): fwave[:, i] = 10**((np.arange(nf) - nlo) * dwlog + np.log10(np.float64(wr[0, i]))) # Make sure the element that's associated with the first input wavelength is identical for i in range(norder): # Increasing input wavelength arrays if np.median(dw[:, i]) > 0: fwave[:, i] -= fwave[nlo, i] - wave[0, i] # Decreasing input wavelength arrays else: fwave[:, i] -= fwave[nlo, i] - wave[-1, i] # Single-order wavelength array else: # extend wavelength range by +/-vel km/s wlo = wr[0] - vel / cspeed * wr[0] whi = wr[1] + vel / cspeed * wr[1] # logarithmic step dwlog = np.median(dln.slope(np.log10(np.float64(wave)))) # extend at the ends if vel > 0: nlo = np.int( np.ceil( (np.log10(np.float64(wr[0])) - np.log10(np.float64(wlo))) / dwlog)) nhi = np.int( np.ceil( (np.log10(np.float64(whi)) - np.log10(np.float64(wr[1]))) / dwlog)) else: nlo = 0 nhi = 0 # Number of pixels if vel == 0.0: n = np.int( (np.log10(np.float64(wr[1])) - np.log10(np.float64(wr[0]))) / dwlog) else: n = np.int( np.ceil( (np.log10(np.float64(wr[1])) - np.log10(np.float64(wr[0]))) / dwlog)) nf = n + nlo + nhi fwave = 10**((np.arange(nf) - nlo) * dwlog + np.log10(np.float64(wr[0]))) # Make sure the element that's associated with the first input wavelength is identical fwave -= fwave[nlo] - wave[0] # w=10**(w0log+i*dwlog) return fwave