CCFCenter
This script is used to find the center an meaure the width of a CCF peak.
ccfcenter.py
Go to the documentation of this file.
00001 #!/usr/bin/python
00002 ##
00003 # 
00004 # This module finds the center and width of a MARVELS ccf
00005 # 
00006 # This module uses a non-linear least-squares optimizer from scipy to 
00007 #     fit a Gaussian to the central pixels of a ccf file.
00008 # 
00009 # @package ccfcenter
00010 # @author Nathan De Lee
00011 # @version $Revision: 1.2 $
00012 # @date $Date: 2012-01-13 20:12:24 $
00013 # 
00014 
00015 # -----------------------------
00016 # Standard library dependencies
00017 # -----------------------------
00018 import os
00019 import sys
00020 import collections
00021 
00022 # -------------------
00023 # Third-party imports
00024 # -------------------
00025 from scipy import optimize
00026 import numpy as np
00027 import matplotlib.pyplot as pl
00028 from matplotlib.backends.backend_pdf import PdfPages
00029 
00030 ##
00031 # 
00032 #     Choose which section of the ccf to fit based on width parameter 
00033 #         or automatically using percent
00034 #     
00035 #     @param pixel   Numpy array of pixel positions
00036 #     @param ccf     Numpy array of ccf values
00037 #     @param width   Integer number of pixels to include, if None: 
00038 #                     then automatically determine.
00039 #     @param percent Floating point percentage of the singal amplitude 
00040 #                     above the noise that a point must be to be included in 
00041 #                     the fit.
00042 #     
00043 #     @return This function returns a tuple (pixel,ccf,lmargin,rmargin
00044 #             ,noiselevel).
00045 #     
00046 #     @retval pixel      Numpy array of pixel positions
00047 #     @retval ccf        Numpy array of ccf values
00048 #     @retval lmargin    Integer index of the left margin of the input arrays
00049 #     @retval rmargin    Integer index of the right margin of the input arrays
00050 #     @retval noiselevel Floating point values of the CCF plot noise level
00051 #     
00052 def cutccf(pixel,ccf,width=None,percent=0.30): 
00053     cent_index= ccf.argmax()
00054     if width == None:
00055         noiselevel = np.median(ccf)
00056         cutoff = (percent * (max(ccf) - noiselevel))+noiselevel
00057         indice = np.array((ccf < cutoff).nonzero()).flatten()
00058         lmargin = max(indice[np.array((indice < cent_index).nonzero()).flatten()])
00059         rmargin = min(indice[np.array((indice > cent_index).nonzero()).flatten()])    
00060     else:
00061         lmargin = cent_index-width
00062         rmargin = cent_index+width+1
00063          
00064     return(pixel[lmargin:rmargin],ccf[lmargin:rmargin],lmargin,rmargin,noiselevel)
00065 
00066 ##
00067 # 
00068 #     Takes x points and returns a Gaussian function
00069 #     
00070 #     @param P A 3 element array (Amplitude,Offset,Sigma)
00071 #     @param x Numpy array of x positions
00072 #     
00073 #     @returns y values for the x positions and parameters given
00074 #     
00075 #     @retval y Numpy array of y values 
00076 #     
00077 def gaussfunc(P,x):
00078         
00079     ##  @page Formula Mathematical Formula
00080     #   @section gauss Gaussian 
00081     #    @{
00082     #   I use this formulation for my Gaussian Equation
00083     #   @f$ y = Amp \exp(-\frac{(x - Offset)^{2}}{2\sigma^{2}})@f$
00084     ##   @}
00085 
00086     return(P[0]*np.exp(-(x-P[1])**2/(2*P[2]**2)))
00087   
00088 ##
00089 # 
00090 #     Creates a Gaussian signal and then fits it to see if I can recover the input parameters.
00091 #     
00092 #     @return None
00093 #     
00094 #     
00095 def gausstest():
00096     ntrials = 10000
00097     #Open output pdf
00098     pp = PdfPages('test.pdf')  
00099     print "Progress: ",
00100     for i in range(ntrials) :
00101         if ntrials >= 10 and i % (ntrials/10) == 0 :
00102             print str("."),
00103         (x,y) = make_gauss(6,amp=0.91,sigma=10.4,offset=5.1,error=0.015)
00104         #Fit a gaussian to the data cutout     
00105         #(params,errors,chi)=fitgauss(x,y,inguess=[1,1,20],inerror=(np.zeros(len(x))+.015))
00106         (params,errors,chi)=fitgauss(x,y,inguess=[1,1,20])
00107 
00108         #print i,params,errors,chi
00109         if i == 0:
00110             allparams = params
00111             allerrors = errors
00112             allchi = chi
00113         else:
00114             allparams = np.vstack([allparams,params])
00115             allerrors = np.vstack([allerrors,errors])
00116             allchi = np.vstack([allchi,chi])
00117 
00118     ##@fn def gausstest():
00119     #@todo Make a histogram of all the parameters
00120     
00121     #Print out how well I did.
00122     print "\nActual:  ",np.std(allparams[:,0]),np.std(allparams[:,1]),np.std(allparams[:,2]) 
00123     print "First:   ",allerrors[0,0],allerrors[0,1],allerrors[0,2]
00124     print "Average: ",np.mean(allerrors[:,0]),np.mean(allerrors[:,1]),np.mean(allerrors[:,2])
00125     pp.close()
00126     return(0)
00127 
00128 ##
00129 # 
00130 #     Use a Levenberg-Marquadt non-linear least-squares fitter 
00131 #     to fit a Gaussian to Xin and Yin
00132 #     
00133 #     The scipy module optimize is used to find the smallest residual
00134 #     to @ref error which is compared to the @ref gauss function.
00135 #     
00136 #     @param xin Numpy array of x-values
00137 #     @param yin Numpy array of y-values
00138 #     @param inerror Numpy array of error values
00139 #     @param inguess A 3 element list (Amplitude,Offset,Sigma) of 
00140 #             starting values for Gaussian parameters 
00141 #     
00142 #     @return This function returns a tuple (Params, perror , red_chi)
00143 #     
00144 #     @retval Params A 3 element Numpy array of Gaussian 
00145 #              Parameters (Amplitude,Offset,Sigma)
00146 #     @retval perror A 3 element Numpy array of parameter errors 
00147 #              calculated from the covariance matrix
00148 #     @retval red_chi A float giving the reduced @f$\chi^{2}@f$ 
00149 #              of the fit.
00150 #    
00151 def fitgauss(xin,yin,inerror=[],inguess=[1,1,1]):
00152     
00153     #Don't want my np.arrays messed up.
00154     x = np.array(xin,dtype='float64')
00155     y = np.array(yin,dtype='float64')
00156     guess = np.array(inguess[:])
00157     if len(inerror) == len(xin):
00158         error = np.array(inerror,dtype='float64')
00159     else:
00160         error = np.ones(len(xin)) 
00161 
00162     if(len(inerror) == len(xin)):
00163     ##  @page Formula Mathematical Formula
00164     #   @section error Error Function 
00165     #    @{
00166     #   I use this formulation for optimizing my least-squares fitter
00167     #    - With Errors:    @f$\frac{y - Gaussian(P,x)}{errror}@f$
00168     #    - Without Errors: @f$y - Gaussian(P,x)@f$
00169     #   @}
00170         errfunc = lambda P,x,y,error: (y - gaussfunc(P, x))/error 
00171         out = optimize.leastsq(errfunc, guess, args=(x, y, error),full_output=1)
00172     else:
00173         errfunc = lambda P,x,y: y - gaussfunc(P, x) 
00174         out = optimize.leastsq(errfunc, guess, args=(x, y),full_output=1)
00175  
00176     Params = out[0]
00177     #Sigma should be positive
00178     Params[2] = np.abs(Params[2])
00179     
00180     if out[1] != None :
00181         cov = out[1] 
00182         perror = np.array([np.sqrt(cov[0][0]),np.sqrt(cov[1][1]),np.sqrt(cov[2][2])])
00183     else:
00184         perror = np.array([1e11,1e11,1e11])
00185     if(len(inerror) == len(xin)):  
00186         red_chi = sum((errfunc(Params,x,y,error))**2)/(len(x)-3)
00187     else:
00188         red_chi = sum((errfunc(Params,x,y))**2)/(len(x)-3)
00189     
00190     ##@fn def fitgauss(xin,yin,inerror=[],inguess=[1,1,1]):
00191     #@remarks
00192     #If there are no measurement errors then the parameter errors must be scaled by the reduced chisq
00193     #Section 15.2 in <a href="http://adsabs.harvard.edu/abs/1992nrca.book.....P/">Numerical 
00194     #Recipes </a> discusses this.
00195     
00196     if(len(inerror) == 0):    
00197         perror = perror * np.sqrt(red_chi)
00198      
00199     return(Params,perror,red_chi)
00200 
00201 ##
00202 # 
00203 #     Generate Gaussian data using @ref gauss
00204 # 
00205 #     @param npts          Integer number of points to create
00206 #     @param amp           Floating point amplitude of the Gaussian
00207 #     @param offset        Floating point offset of the Gaussian
00208 #     @param sigma         Floating point sigma of the Gaussian
00209 #     @param error         Floating point number or a Numpy array of sigmas for the error in each point
00210 #     @param inflate_error Floating point factor to scale the real as versus reported error
00211 #     @param randpts       Bool True: Select x points from a uniform distribution;
00212 #                          False Take from a regular grid.
00213 #     
00214 #     @return This function returns a tuple (x,y,y_err)
00215 #     
00216 #     @retval x     Numpy Array of x positions
00217 #     @retval y     Numpy Array of y positions
00218 #     @retval y_err Numpy Array of y errors
00219 #    
00220 def make_gauss(npts,amp=1,offset=0,sigma=1,error=0,inflate_error=1.0,randpts=False):
00221       
00222     if randpts == True:
00223         x = (np.random.uniform(size=npts) - 0.5)* 6.0 * sigma + offset
00224     else:
00225         x = (np.arange(npts,dtype='float') -npts/2.0) * 6 * sigma /npts + offset
00226     #Get Gaussian value
00227     y = gaussfunc([amp,offset,sigma],x)
00228     
00229     #Add Error to Gaussian
00230     if error > 0:
00231         y = y + (np.random.normal(0,error,npts)*inflate_error) 
00232 
00233     return(x,y)
00234 
00235 ##
00236 # 
00237 #     This function is the main() program for this module
00238 #     
00239 #     @param filename String name of MARVELS ccf file to process
00240 #     @param width    Integer number of pixels to fit around peak; 
00241 #                     None: Automatically choose
00242 #     
00243 #     @return The function returns 0 for success or an error message.
00244 #     
00245 #     @retval ret 0 for success or an error message 
00246 #     
00247 def ccfcenter(filename,width=None):
00248     #Numpy arrays are tricky.  To make an empty shapeless numpy array np.ndarray((0,))
00249     #I want a dictionary of dictionary of numpy arrays.
00250     #The collections defaultdict command allows one to do this.  It makes a dictionary typed as a callable
00251     #function.  For instance if you use list() it will allow you to use a .append on it.
00252     #You must define a factory which is callable, so I use the lambda to make np.ndarray((0,)) into a 
00253     #callable function like list()
00254     ccfdict = collections.defaultdict(lambda : collections.defaultdict(lambda : np.ndarray((0,))))
00255 
00256     #Read in data file
00257     try:
00258         inputf = open(filename)
00259     except IOError:
00260         print "File %s could not be opened!" % filename
00261         return(1)
00262     
00263     try:
00264         for line in inputf:
00265             #Ignore comments and blank lines
00266             if line.startswith("#") or line.isspace():
00267                 continue
00268             line.strip()    
00269             aline = line.split()
00270             ccfdict[aline[0]]['pixel'] = np.append(ccfdict[aline[0]]['pixel'],float(aline[8]))
00271             ccfdict[aline[0]]['ccf'] = np.append(ccfdict[aline[0]]['ccf'],float(aline[9]))
00272             ccfdict[aline[0]]['rv'] = float(aline[1])
00273             ccfdict[aline[0]]['rverr'] = float(aline[2])
00274             ccfdict[aline[0]]['ccfrv'] = float(aline[3])
00275             ccfdict[aline[0]]['ccfrverr'] = float(aline[4])
00276     finally:
00277         
00278         inputf.close()
00279     
00280     #Get base filename
00281     basename = os.path.splitext(filename)[0]    
00282     #Open output pdf
00283     pp = PdfPages(basename+'.pdf')  
00284     #Open output file
00285     outputf = open(basename+'.out','w')
00286     #Loop through each epoch 
00287     i=0
00288     for key in sorted(ccfdict.keys()):
00289         i += 1
00290         #Make data cutout
00291         (pixcut,ccfcut,lmargin,rmargin,noiselvl) = cutccf(ccfdict[key]['pixel'],np.array(ccfdict[key]['ccf']),width)        
00292         print lmargin,rmargin,noiselvl
00293         #Fit a gaussian to the data cutout     
00294         (params,errors,chi)=fitgauss(pixcut,ccfcut,inguess=[1,1,4])
00295         #Print the results
00296         print i,key,params,errors,chi
00297         rv = ccfdict[key]['rv']
00298         err = ccfdict[key]['rverr']
00299         rvccf = ccfdict[key]['ccfrv']
00300         ccferr = ccfdict[key]['ccfrverr']
00301         #Write out to file
00302         out = map(str,(i,key,rv,err,rvccf,ccferr,' '.join(map(str,params)),' '.join(map(str,errors)),chi))
00303         outputf.write(' '.join(out))
00304         outputf.write('\n')
00305         #Plot the results
00306         pl.xlabel("Pixel")
00307         pl.ylabel("CCF")
00308         pl.ylim(-.2,1.1)
00309         pl.title("Date: %s" % key)
00310         pl.plot(ccfdict[key]['pixel'][lmargin-20:rmargin+20],ccfdict[key]['ccf'][lmargin-20:rmargin+20],'go')
00311         pl.plot(pixcut,ccfcut,'ro')
00312         pixcut = np.array(pixcut,dtype='float64')
00313         modelx = np.arange(100) * ((pixcut.max() - pixcut.min()+20)/ 100.0) + pixcut.min()-10
00314         pl.plot(modelx,gaussfunc(params,modelx))
00315         pp.savefig()
00316         pl.clf()      
00317     pp.close()
00318     outputf.close()
00319     return(0)
00320 if __name__ == '__main__':
00321     #Check to make sure we have at least one argument and no more than 2 arguments
00322     if len(sys.argv) < 2 or len(sys.argv) > 3:
00323         print 'Usage: ccf ccfilename {width}\n'
00324         print 'If test is given for the filename, then will run a montecarlo simulation'
00325         print 'ccfilename              Output filename from ccfsearch.pro'
00326         print 'width                   Optional width parameter (in pixels)\n'
00327         sys.exit(1)
00328     filename = sys.argv[1]
00329     if filename == 'test':
00330         gausstest()
00331         sys.exit(0)
00332     else:
00333         width = None
00334         ret = ccfcenter(filename,width)
00335         sys.exit(ret)
00336 
00337 ##
00338 #@mainpage A Guassian CCF Fitter 
00339 #This module is designed to fit a gaussian to the peak of a cross-correlation function.  
00340 #In it's current form, it uses a non-linear least squares fitter from the scipy optimize module.
00341 #@verbatim
00342 #Usage: ccf ccfilename {width}
00343 #If test is given for the filename, then will run a montecarlo simulation
00344 #ccfilename              Output filename from ccfsearch.pro
00345 #width                   Optional width parameter (in pixels)
00346 #@endverbatim
00347 
 All Namespaces Files Functions Variables