########################################## ### ### Echelle Reduction script for use with APO 3.5-m telescope. ### ### Directory structure required: ### WD/: ### echelleReduction.py ### filter_redflat.fits ### badcols ### dcr ### dcr.par ### database/: ### apechtrace130522 ### ecarcnewref.ec ### rraw/: ### DATE/ ### unprocessed data ### reduced/: ### (stuff will go here) ### ######### ### ### Edited by Jean McKeever 11/22/13 ### ######### ### Import PyRAF packages import sys,subprocess,os,datetime as dt from subprocess import call from pyraf import iraf from pyraf.iraf import noao,imred,ccdred,proto,twodspec,apextract,echelle,images,imgeom,generic,astutil,onedspec,crutil ### A few simple definitions yes='yes' no='no' INDEF='INDEF' STDOUT=sys.stdout STDERR=sys.stderr ### Set up necessary files/directories date=sys.argv[1] call(['mkdir','reduced/'+date]) logfile=open('reduced/'+date+'/reductionlog.txt','w') sys.stdout=logfile sys.stderr=logfile try: grating=sys.argv[2] except: grating='new' print 'Copying data files and other necessary files' if grating=='old': call(['mkdir','reduced/'+date+'/database/']) params='cp database_old/* reduced/'+date+'/database/' call(params,shell=True) #sample='3350:3731,3740:3746,3752:3787,3800:3837,3843:3856,3862:3877,3882:3926,3938:3955,3966:3968,3970:4379,4382.8:4477.5,4482:4492,4503:4645,4650:4685,4690:4880,4890:6555,6570:6865,6880:7590,7615:10650' else: call(['cp','-r','database/','reduced/'+date]) #sample='3350:3561,3574:3578,3590:3731,3740:3746,3752:3756,3762:3818,3822:3837,3843:3856,3862:3877,3882:3926,3940:3961,3975:4000,4007:4027,4035:4140,4150:4170,4174:4219,4230:4265,4273:4279,4282.5:4297.5,4305:4321,4325:4379,4382.8:4425,4435:4477.5,4482:4492,4503:4645,4650:4685,4690:5138,5140:5165,5185:5323,5330:5368,5372:6555,6570:7593,7615:10900' #call(['cp','badcols','reduced/'+date]) call(['cp','dcr.par','reduced/'+date]) call(['cp','echmask.pl','reduced/'+date]) call(['cp','filter_redflat.fit','reduced/'+date]) params='cp raw/'+date+'/*.fits reduced/'+date+'/' call(params,shell=True) print 'Changing to reduced data directory' os.chdir('reduced/'+date) # SET DATA TYPE # Set the type of data being extracted to 'Echelle'. Forced review of the # parameters being set is suppressed. iraf.setinstrument('echelle',review=no) # MAKE IMAGE LISTS # note there are blue flats and 'no filter' (red) flats for ARCES. # this is because the instrument is undersensitive in the blue. # lists include: allflats, flat_blue, flat_red, biases, arcs (Th-Ar), targets # also makes the list objflat (contains both targets and flats) # (darks are also an option, but are omitted by default) print 'Generating file lists...' iraf.hselect('*.fits','$I','IMAGETYP == "flat"',Stdout='allflats') iraf.hselect('@allflats','$I','FILTER == "Blue"',Stdout='flat_blue') iraf.hselect('@allflats','$I','FILTER == "Open"',Stdout='flat_red') iraf.hselect('*.fits','$I','IMAGETYP == "zero"',Stdout='biases') #iraf.hselect('*.fits','$I','IMAGETYP == "dark"',Stdout='darks') iraf.hselect('*.fits','$I','IMAGETYP == "comp"',Stdout='arcs') iraf.hselect('*.fits','$I','IMAGETYP == "object"',Stdout='targets') outfile=open('objflat','w') iraf.hselect('*.fits','$I','IMAGETYP == "flat"',Stdout=outfile) iraf.hselect('*.fits','$I','IMAGETYP == "object"',Stdout=outfile) outfile.close() # SET DISPAXIS (dispersion axis) # you can set Dispaxis = 1 (horizontal) or 2 (vertical) print 'Setting dispaxis...' iraf.hedit('*.fits','dispaxis','1',add=yes,verify=no,show=yes,update=yes) # CREATE ECHELLE BAD PIXEL MASK # Use the task TEXT2MASK to create the mask from the ASCII 'badcols' file #print 'Creating bad pixel mask from file "badcols"...' #iraf.text2mask('badcols','echmask',ncols=2128,nlines=2068,linterp=1,cinterp=1, # square=1,pixel=1) # Note: This ^ doesn't work currently on 64-bit systems. Does not produce # a working mask. Use 32-bit IRAF to produce mask. # Forces interpolation of bad pixel masks along the iraf.fixpix('@objflat','echmask.pl',linterp=1,verbose=yes) iraf.fixpix('@arcs','echmask.pl',linterp=1,verbose=yes) # MAKE AN AVERAGE (FIDUCIAL) BIAS AND DARK (if applicable) # by default, we skip the darks, but this can be changed if desired iraf.imdelete('bias_fid',verify=no) iraf.zerocombine('@biases',output='bias_fid',combine='median', reject='none',ccdtype='',process=no,delete=no, scale='none') #iraf.imdelete('dark_fid',verify=no) #iraf.darkcombine('@darks',output='dark_fid',combine='average', # reject='avsigclip',ccdtype='',process=no,delete=no, # scale='none') # CALIBRATE ARCS, OBJECT AND FLAT FIELD SPECTRA # Calibrate object images and flat field images with the task ccdproc. The # calibrations applied at this step are the bias level subtraction # and trimming. The trim section is hardcoded here. trimsec='[200:1850,1:2048]' print 'Calibrating object images and flats (bias, and trimming)...' iraf.ccdproc('@objflat',output='',ccdtype='object',trimsec=trimsec, zerocor=yes,darkcor=no,flatcor=no,trim=yes, fixpix=no,overscan=no,zero='bias_fid',interactive=no) print 'Calibrating object images and flats (bias, and trimming)...' iraf.ccdproc('@flat_red',output='',ccdtype='flat',trimsec=trimsec, zerocor=yes,darkcor=no,flatcor=no,trim=yes, fixpix=no,overscan=no,zero='bias_fid',interactive=no) print 'Calibrating object images and flats (bias, and trimming)...' iraf.ccdproc('@flat_blue',output='',ccdtype='flat',trimsec=trimsec, zerocor=yes,darkcor=no,flatcor=no,trim=yes, fixpix=no,overscan=no,zero='bias_fid',interactive=no) print 'Calibrating ThAr arcs...' iraf.ccdproc('@arcs',output='',ccdtype='comp',trimsec=trimsec, zerocor=yes,darkcor=no,flatcor=no,trim=yes, fixpix=no,overscan=no,zero='bias_fid',interactive=no) ### Cosmic ray removal using DCR. Histogram method. astro-ph/0311290 flist=open('objflat') for ifile in flist: ifile=ifile.strip() params='../../dcr '+ifile+' '+ifile+' crmask >> reductionlog.txt' call(params,shell=True) flist.close() # COMBINE 'RED' AND 'BLUE' FLATS SEPARATELY # Separately combine 'red' and 'blue' flat field frames to produce mean flats print 'Combining red and blue flats into separate fiducial flats...' iraf.flatcombine('@flat_blue',output='flat_blue_mean.fits',combine='median', ccdtype='',reject='avsigclip',process=no,delete=no, scale='none',mclip=yes,lsigma=3.0,hsigma=3.0) iraf.flatcombine('@flat_red',output='flat_red_mean.fits',combine='median', ccdtype='',reject='avsigclip',process=no,delete=no, scale='none',mclip=yes,lsigma=3.0,hsigma=3.0) # Combine the 'red' and 'blue' mean flats to result in a 'superflat' or # fiducial flat by which object images will eventually be divided print 'Combining average red and blue flats into a superflat...' iraf.imarith('flat_red_mean.fits','*','filter_redflat.fit', 'flat_red_mean_filt.fits',verbose=yes) iraf.imarith('flat_blue_mean.fits','+','flat_red_mean_filt.fits','junk', verbose=yes) iraf.imarith('junk','/',2.0,'flat_fid',verbose=yes) iraf.imdelete('junk',verify=no) # EXTRACT AND NORMALIZE SUPERFLAT # Magnify superflat by 4 in the cross-dispersion direction. # Then apply model apertures, recenter, resize, trace, and extract. # Scattered light subtraction isn't necessary because superflat will be # normalized anyway (we only want pixel-to-pixel variation). print 'Resample the superflat in the y direction...' iraf.magnify(input='flat_fid.fits',output='flat_fid_mag.fits',xmag=1,ymag=4) iraf.hedit('flat_fid_mag.fits',fields='CCDSEC',value='[200:1850,1:8189]', add=no,addonly=no,delete=no,verify=no,show=yes,update=yes) print 'Modeling and extracting the superflat...' iraf.apall(input='flat_fid_mag.fits',ref='echtrace130522',format='echelle', interactive=no,find=no,recenter=yes,resize=yes,edit=no,trace=yes, fittrace=no,extract=yes,extras=no,review=no,line=825,nsum=10, lower=-14.0,upper=14.0,b_function='chebyshev',b_order=2, b_niterate=3,b_naverage=-3,b_sample='-22:-15,15:22',width=18.0, radius=18.0,npeaks=INDEF,shift=no,ylevel=.05,t_nsum=5,t_step=1, t_function='legendre',t_order=5,t_naverage=3,t_niterate=3, t_low_reject=2.5,t_high_reject=2.5,t_nlost=3,t_sample='*', background='fit',weights='none',clean=no,saturation=40000.0) print 'Normalizing the superflat...' iraf.imcopy('flat_fid_mag.ec.fits','nonorm_flat_fid_mag.ec.fits') iraf.imdelete('flat_fid_mag.ec.fits',verify=no) # The extraction of the flat ends up with some negative values, which # skews the fit in the following steps, so I added a small bias level # equal to the minimum value in the minval=iraf.imstat('nonorm_flat_fid_mag.ec.fits',fields='min',Stdout=1)[-1] minval=abs(float(minval)) iraf.imarith('nonorm_flat_fid_mag.ec.fits','+',minval, 'nonorm_flat_fid_mag_bias.fits',verbose=yes) iraf.sfit(input='nonorm_flat_fid_mag_bias.fits',output='flat_fid_mag.ec.fits', type='ratio',replace=no,wavesca=no,logscal=no,override=yes, listonly=no,interac=no,sample='*',naverage=-5,function='spline3', order=12,low_rej=2,high_rej=3.5,niterate=10,grow=0) # APPLY MODEL APERTURES TO WAVECALS # Apply model apertures. No resizing, recentering or tracing. # Also extract spectra here if doing 1D flatfielding, else extract # after apflatten call(['rm','all.list']) call(['cp','arcs','all.list']) flist=open('all.list') for ifile in flist: ifile=ifile.strip() print 'Resampling the arc by a factor of 4 in the y direction...' iraf.magnify(input=ifile,output=ifile,xmag=1,ymag=4) iraf.hedit(images=ifile,fields='CCDSEC',value='[200:1850,1:8189]',add=no, addonly=no,delete=no,verify=no,show=yes,update=yes) print 'Applying model apertures to the arc and extracting spectra...' iraf.apall(input=ifile,reference='flat_fid_mag',format='echelle', interactive=no,find=no,recenter=no,resize=no,edit=no, trace=no,fittrace=no,extract=yes,extras=no,review=no, shift=no,background='none',weights='none') flist.close() iraf.hselect('*.ec.fits','$I','IMAGETYP == "comp"',Stdout='arcs_extracted') # APPLY MODEL APERTURES TO OBJECTS # # To properly account for and deal with the spatial aliasing of ARCES spectra, # the spectra must be resampled in the order direction. (Uses the # images and imgeom packages to do this with the task magnify) # # Magnify objects by 4 in the cross-dispersion direction. # Then apply model apertures, recenter, resize, and trace. # Manual interactive scattered light subtraction because interactive apscat # in automated script doesn't work. If doing automatic apscat, turn off # interaction and check apscat1 and apscat2 for params. # Then recenter and resize apertures again for better fit. # Then extract the spectra (for 1D flatfielding). #Values from old script. Probably should refit interactively to confirm iraf.apscat1.function='spline3' iraf.apscat1.order=25 iraf.apscat1.low_reject=10 iraf.apscat1.high_reject=0.5 iraf.apscat1.niterate=5 iraf.apscat2.function='spline3' iraf.apscat2.order=6 iraf.apscat2.low_reject=3 iraf.apscat2.high_reject=0.5 iraf.apscat2.niterate=5 call(['rm','all.list']) call(['cp','targets', 'all.list']) flist=open('all.list') for ifile in flist: print 'Resampling the object by a factor of 4 in the y direction...' s1=ifile.strip()[:-5] iraf.magnify(input=s1,output=s1,xmag=1,ymag=4) iraf.hedit(images=s1,fields='CCDSEC',value='[200:1850,1:8189]',add=no, addonly=no,delete=no,verify=no,show=yes,update=yes) print 'Applying model apertures to the object...' iraf.apall(input=s1,ref='flat_fid_mag',format='echelle',interactive=no, find=no,recenter=yes,resize=yes,edit=no,trace=no,fittrace=no, extract=no,extras=no,shift=no,width=18.0,radius=18.0, ylevel=0.05,background='fit',b_function='chebyshev',b_order=2, b_sample='-22:-15,15:22',b_naverage=-3,weights='none') print "Removing scattered inter-order light from the spectrum..." s2='noscat'+s1 iraf.imcopy(s1,s2) iraf.imdel(s1,verify=no) print "Removing scattered light with apscatter..." iraf.apscatter(input=s2,output=s1,ref=s1,interac=no,find=no,recent=no, resize=no,edit=no,trace=no,fittrace=no,subtrac=yes, smooth=yes,fitscat=no,fitsmoo=no,nsum=-10) s3=s1+'.ec.fits' print "Fine-tuning apertures and extracting spectra..." iraf.apall(input=s1,output=s3,ref=s1,format="echelle",interactive=no, find=no,recenter=no,resize=no,edit=no,trace=no,fittrace=no, extract=yes,extras=no,review=no,shift=no,background='fit', b_function='chebyshev',b_order=1,b_sample='-22:-15,15:22', b_naverage=-3,weights='variance',clean=yes, saturation=40000.0,readnoise='RDNOISE',gain='gain') flist.close() iraf.hselect('*.ec.fits','$I','IMAGETYP == "object"',Stdout='targets_extracted') # ADD 'REFSPEC1' PARAMETER TO OBJECT SPECTRA HEADERS # Use UNIX awk command to add image names to comparison spectra under the # "REFSPEC1" header parameter flist=open('arcs_extracted') for ifile in flist: ifile=ifile.strip() iraf.hedit(ifile,'REFSPEC1',ifile,add=yes,verify=no) flist.close() # DIVIDE OBJECT SPECTRA AND WAVECALS BY THE SUPERFLAT (1D FLATFIELDING) # # Divide by the normalized, extracted fiducial flat prior to dispersion # correcting object images. The reasoning is as follows: # # "Flatfielding should be done prior to dispersion correction, i.e. # strictly in pixel space. Dispcor resamples the spectra when it # linearizes the dispersion and thus loses some FPN information." # (J. Thorburn) # # Added the provision for flat-fielding the wavecals in hopes of recovering # response particularly in the blue, leading to the ID'ing of more lines. # # PL: 2D flatfielding is bad in our case due to narrow apertures. # So we use this 1D flatfielding, assuming response is same in the # cross-dispersion direction. print "Flatfielding extracted arcs and object spectra..." call(['rm','all.list']) params='cat arcs_extracted targets_extracted > all.list' call(params,shell=True) flist=open('all.list') for ifile in flist: ifile=ifile.strip() iraf.imarith(ifile,'/','flat_fid_mag.ec.fits',result=ifile) flist.close() ## ## Set UTMIDDLE Header keyword. = Dateobs +exptime/2, and jd keywords ## Set airmass sets utmiddle too!!! flist=open('all.list') for ifile in flist: ifile=ifile.strip() iraf.hedit(ifile,'UTMIDDLE',value='0',addonly=yes,verify=no) datobs=iraf.hsel(ifile,'date-obs','yes',Stdout=1)[0] exptime=iraf.hsel(ifile,'exptime','yes',Stdout=1)[0] dateobs=dt.datetime.strptime(datobs,'%Y-%m-%dT%H:%M:%S.%f') utmiddle=dateobs+dt.timedelta(0,float(exptime)/2) iraf.hedit(ifile,'UTMIDDLE',value=utmiddle.strftime('%H:%M:%S.%f'), addonly=no,verify=no) flist.close() print 'Setting Julian Date Keywords' iraf.setjd('@targets_extracted',date='date-obs',exposure='exptime',ra='ra', dec='dec',epoch='equinox',utdate=yes,uttime=yes,listonly=no) iraf.setairmass('@targets_extracted',date='date-obs',exposure='exptime',ra='ra', dec='dec',equinox='equinox',utmiddle='utmiddle',st='lst', airmass='airmass',show=yes,update=yes,override=yes, intype='beginning',outtype='middle',ut='date-obs') # DISPERSION CALIBRATE WAVECALS #print "Median combining arcs and dispersion calibrating the fiducial arc..." #call(['rm','arc_fid.ec.fits']) #iraf.scombine('@arcs_extracted',output='arc_fid.ec.fits',group="apertures", # combine="median",first=no,w1=INDEF,w2=INDEF,dw=INDEF,nw=INDEF, # scale="none",log=no) #iraf.ecreidentify(images='arc_fid.ec.fits',reference='arcnewref.ec',cradius=2, # threshold=50,shift=INDEF) iraf.ecreidentify(images='@arcs_extracted',reference='arcnewref.ec', shift=INDEF,cradius=2,threshold=45,refit=yes) # ASSIGN WAVECALS TO OBJECT SPECTRA # Assign comparison spectra to object images based on times the images were # made, with weights for later dispersion solution assignment iraf.refspectra('@targets_extracted',references='@arcs_extracted', select='interp',sort='utmiddle',group='observat',time=yes, confirm=no,override=yes,assign=yes) iraf.refspectra('@arcs_extracted',references='@arcs_extracted', select='match',confirm=no,override=yes,assign=yes) # APPLY DISPERSION SOLUTION TO OBJECT SPECTRA # Apply the dispersion solution to object spectra based on the previously- # assigned comparison spectra iraf.dispcor(input='@targets_extracted',output='@targets_extracted',flux=no, linearize=no,log=no,verbose=yes,samedisp=no) # Just as a check to the dispersion solution. iraf.dispcor(input='@arcs_extracted',output='z//@arcs_extracted',flux=no, linearize=no,log=no,verbose=yes,samedisp=no) ### ### Continuum fitting and removal different orders for different parts... ### Trial and error to get it working. ### print 'Continuum fitting the spectra...' iraf.continuum(input='@targets_extracted',output='fitcont//@targets_extracted', lines='*',type='fit',replace=no,wavescale=yes,logscale=no, listonly=no,interactive=no,sample='*',naverage=-3, function='spline3',order=5,niterate=10,low_reject=1.5, high_reject=5.0,markrej=no,grow=0,override=no) # Good fits for old 90-92 Ca H and K if grating == 'old': iraf.continuum(input='@targets_extracted', output='fitcont//@targets_extracted',lines='90,91,92', type='fit',replace=no,wavescale=yes,logscale=no,listonly=no, interactive=no,sample='3850:3920,3945:3960,3980:4050', naverage=-3,function='spline3',order=5,niterate=10, low_reject=2.5,high_reject=5.0,markrej=no,grow=0, override=yes) # Order 92 of the new grating was the only one that gave issues. CA H or K. if grating == 'new': iraf.continuum(input='@targets_extracted', output='fitcont//@targets_extracted',lines='92',type='fit', replace=no,wavescale=yes,logscale=no,listonly=no, interactive=no,sample='3860:3920,3945:3960',naverage=-3, function='spline3',order=5,niterate=10,low_reject=1.5, high_reject=5.0,markrej=no,grow=0,override=yes) print 'Combining spectra...' iraf.scombine('@targets_extracted','sum//@targets_extracted',group='images', combine='sum',reject='none',first=no,w1=INDEF,w2=INDEF,dw=INDEF, nw=INDEF,log=no,scale='none',weight='none',zero='none') iraf.scombine('fitcont//@targets_extracted','sumfitcont//@targets_extracted', group='images',combine='sum',reject='none',first=no,w1=INDEF, w2=INDEF,dw=INDEF,nw=INDEF,log=no,scale='none',weight='none', zero='none') iraf.imarith('sum//@targets_extracted','/','sumfitcont//@targets_extracted','fullspec//@targets_extracted') iraf.imarith('@targets_extracted','/','fitcont//@targets_extracted','nocontinuum//@targets_extracted') ### Shouldnt be necessary, but incase there are huge spikes cut them off. iraf.imreplace(images='fullspec*',value=50,lower=50,upper=INDEF) iraf.imreplace(images='fullspec*',value=-10,lower=INDEF,upper=-10) ### Fix exposure times. They are off by the number of orders in the trace ### from the summing of orders part if grating == 'old': norders=107.0 else: norders=115.0 flist=open('targets_extracted') for ifile in flist: ifile='fullspec'+ifile.strip() exptime=iraf.hsel(ifile,'exptime','yes',Stdout=1)[0] print ifile,exptime iraf.hedit(ifile,'exptime',value=float(exptime)/norders,add=no, verify=no,show=yes,update=yes,addonly=no,delete=no) flist.close() #Clean up some middle steps.... params='rm sum* fitcont* noscat*' call(params,shell=True) print 'ALL DONE! Yay! (=^.^=) ' os.chdir('../../') logfile.close() sys.stdout=STDOUT sys.stderr=STDERR