def main():
    
    files=[]

    if (len(sys.argv)==1):
        usage()
        sys.exit(1)

    #defaults
    nosolve = 0
    overwrite = 0
    outfile = ''

    print 'sys.argv'
    
    #Sets up option flag parser with help files.  If unsure how to run run program with -h flag
    
    parser = OptionParser()
    
    parser.add_option("-l",	"--sat", help="saturation level (do not use stars exceeding)", 
    					default=-1, action="store", type="float", dest="sat")

    parser.add_option("-x", "--pix", help="pixel scale in arcsec/pixel (within 1%)", 
    					default=-1, action="store", type="float", dest="pix")    

    parser.add_option("-b", "--box", help="half-width of box for reference catalog query (arcsec)", 
    					default=-1, action="store", type="float", dest="box") 

    parser.add_option("-s", "--see", help="approximate seeing in pixels for CR/star/galaxy ID'ing", 
    					default=-1, action="store", type="float", dest="see") 
    					
    parser.add_option("-u", "--upa", help="Uncertainty of the position angle (degrees)", 
    					default=-1, action="store", type="float", dest="upa") 
    					    					
    parser.add_option("-c", "--catalog", help="Catalog to use (tmpsc, ub2, tmc, sdss, or file: see --catalog)", 
    					default='', action="store", dest="cat")  					    					

    parser.add_option("-t", "--toler", help="Amount of slack allowed in match determination", 
    					default=defaulttolerance, action="store", type="float", dest="tol") 
    					    					
    parser.add_option("-e", help="Maximum ellipticity", 
    					default=-1, action="store", type="float", dest="mel") 
   					    					    					    					
    parser.add_option("-p", "--pa", help="The position angle in degrees.  Not usually needed.", 
    					default=-999, action="store", type="float", dest="pa") 

    parser.add_option("-i", "--inv", help="Reverse(=positive) parity.", 
    					default=False, action="store_true", dest="inv") 
    					
    parser.add_option("-q", "--quiet", help="Quiet", 
    					default=False, action="store_true", dest="quiet")     					

    parser.add_option("-m", help="Maximum distance to look for star pairs (in radius).", 
    					default=-1, action="store", type="float", dest="maxrad") 
    					
    parser.add_option("-r", "--ra", help="Right ascension", 
    					default='', action="store", dest="userra")     					

    parser.add_option("-d", "--dec", help="Declination", 
    					default='', action="store", dest="userdec")   
    					
    parser.add_option("-o", "--output", help="Specify output file (no argument overwrites input file)", 
    					default='', action="store", dest="outfile")       					

    parser.add_option("-n", "--nosolve", help="Do not attempt to solve astrometry; just write catalog.", 
    					default='', action="store", dest="nosolvecat")
    					
    					
    (options, args) = parser.parse_args()
    
    #If nosolve set, then set nosolve=1
    if options.nosolvecat != '': 
    	options.cat = options.nosolvecat
    	nosolve = 1
    if options.inv:
    	inv = 1
    else:
    	inv = 0
    
    #Turns userra and userdec into degrees or sets number default (parser stored as strings because it is in sexagesimal)
    if options.userra == '':
    	userra = -999
    else:
    	userra = astrometrystats.rasex2deg(options.userra)
    	
    if options.userdec == '':
    	userdec = -999
    else:
    	userdec = astrometrystats.decsex2deg(options.userdec)
    
    #Finds input files and stores in variable files.  Assumes input files are argv that come before flagged keywords
    #If no files present prints warning and quits
    isys=0
    for value in sys.argv[1:]:
    	if value.find('-') != -1:
    		break
    	isys += 1

    files = sys.argv[1:isys+1]
    
    if len(files) == 0:
    	print 'No files selected!'
    	return
    
    if (options.see == -1):
    	minfwhm = defaultminfwhm
    	maxfwhm = defaultmaxfwhm
    else:
    	minfwhm = 0.7 * options.see
    	maxfwhm = 2.0 * options.see
    
    #Find directory where this python code is located
    call = sys.argv[0]
    place = call.find('vlt_autoastrometry.py')
    propath = call[:place]
    

    #Copies parameter file and configuration file from default files located in program folder   
    shutil.copyfile(propath+'temp.param', 'temp.param')
    
    if not os.path.exists('sex.config'): 
    	shutil.copyfile(propath+'sex.config', 'sex.config')
    
    if not os.path.exists('sex.conv'):    
    	shutil.copyfile(propath+'sex.conv', 'sex.conv')    
    	
    if not os.path.exists('default.nnw'):    
    	shutil.copyfile(propath+'default.nnw', 'default.nnw') 

    nimage = len(files)
    failures = []
    questionable = []
    multiinfo = []    

    for file in files:
        print 'Processing', file
        print 'userra and dec'
        print userra, userdec
        
        fitinfo = autoastrometry(file,pixelscale=options.pix,pa=options.pa,inv=inv,
        						uncpa=options.upa,minfwhm=minfwhm,maxfwhm=maxfwhm,
        						maxellip=options.mel,boxsize=options.box, maxrad=options.maxrad, 
        						userra=userra, userdec=userdec, tolerance=options.tol, 
        						catalog=options.cat, nosolve=nosolve, overwrite=overwrite, 
        						outfile=options.outfile, saturation=options.sat, quiet=options.quiet)
		
        if nosolve: continue
        
        #Save output of autoastrometry to multiinfo
        if type(fitinfo)==int: 
           fitinfo = (0,0,0,0,0,0)
        
        multiinfo.append(fitinfo)

		#If number of matches (fitsinfo[0]) from autoastrometry is 0, list in failures
		#If std is greater than 2 then list in questionable
        if (fitinfo[0] == 0):   #number of matches
            failures.append(file)
        if (fitinfo[5] > 2):    #stdev of offset
            questionable.append(file)
    
    #Returns failed and questionable images.  Also prints all values out from multiinfo       
    if nimage > 1 and nosolve==0:

        if len(failures) == 0 and len(questionable) == 0:
            print 'Successfully processed all images!'
        else:
            print 'Finished processing all images.'
        
        if len(questionable) > 0:
            print 'The following images solved but have questionable astrometry: '
            print '    ',
            for f in questionable: print f
            
        if len(failures) > 0:
            print 'The following images failed to solve: '
            print '    ',
            for f in failures: print f

        print "%25s " %'Filename', 
        print "%6s %8s (%6s)  %7s %7s (%6s)" % ('#match', 'dPA ', 'stdev', 'dRA', 'dDec', 'stdev')
        for i in range(len(files)):
            info = multiinfo[i]
            print "%25s " % files[i], 
            if info[0] > 0:
               print "%6d %8.3f (%6.3f)  %7.3f %7.3f (%6.3f)" % info
            else:
               print "failed to solve"
               
	#Removes temp.param file               
    try:
       os.remove('temp.param')
    except:
       print 'Could not remove temp.param for some reason'
Beispiel #2
0
def getcatalog(catalog,
               ra,
               dec,
               boxsize,
               rawidth,
               decwidth,
               minmag=8.0,
               maxmag=-1,
               maxpm=60.):
    # Get catalog from USNO

    #If maximum magnitude is not set, assign max magnitude based on catalog max or set default
    if maxmag == -1:
        maxmag = 999  #default (custom catalog)
        if catalog == 'tmpsc': maxmag = 20.0
        if catalog == 'ub2': maxmag = 21.0
        if catalog == 'sdss': maxmag = 22.0
        if catalog == 'tmc': maxmag = 20.0

#If catalog is one of the standard 4, then set values for indices for catalogs
#Search catalog with given box size (if no box size selected, will be fieldwidth)
#Read in file to variable catlines
    if (catalog == 'tmpsc' or catalog == 'ub2' or catalog == 'sdss'
            or catalog == 'tmc'):
        usercat = 0
        racolumn = 1
        deccolumn = 2
        magcolumn = 6
        if (catalog == 'tmpsc' or catalog == 'tmc'): magcolumn = 3
        pmracolumn = 10
        pmdeccolumn = 11

        #SDSS direct query has different format than other scat queries
        if catalog == 'sdss':

            #queryurl = "http://cas.sdss.org/dr7/en/tools/search/x_radial.asp?ra="+str(ra)+"&dec="+str(dec)+"&radius="+str(boxsize/60.0)+"&entries=top&topnum=6400&format=csv"
            #queryurl = "http://cas.sdss.org/dr7/en/tools/search/x_rect.asp?min_ra="+str(ra-boxsize/3600.)+"&max_ra="+str(ra+boxsize/3600.)+"&min_dec="+str(dec-2.*boxsize/3600.)+"&max_dec="+str(dec+2.*boxsize/3600.)+"&entries=top&topnum=6400&format=csv"
            if (((ra + rawidth / 3600.) - (ra - rawidth / 3600.) < 0.2) and
                ((dec + rawidth / 3600.) - (dec - rawidth / 3600.) < 0.2)):
                queryurl = "http://cas.sdss.org/dr7/en/tools/search/x_rect.asp?min_ra=" + str(
                    ra - rawidth / 3600.) + "&max_ra=" + str(
                        ra + rawidth / 3600.) + "&min_dec=" + str(
                            dec - decwidth / 3600.) + "&max_dec=" + str(
                                dec + decwidth /
                                3600.) + "&entries=top&topnum=6400&format=csv"
            else:
                queryurl = "http://cas.sdss.org/dr7/en/tools/search/x_rect.asp?min_ra=" + str(
                    ra -
                    0.095) + "&max_ra=" + str(ra + 0.095) + "&min_dec=" + str(
                        dec - 0.095) + "&max_dec=" + str(
                            dec +
                            0.095) + "&entries=top&topnum=6400&format=csv"

            racolumn = 7
            deccolumn = 8
            magcolumn = 12
            pmracolumn = 99
            pmdeccolumn = 99
            print queryurl
        else:
            queryurl = "http://tdc-www.harvard.edu/cgi-bin/scat?catalog=" + catalog + "&ra=" + str(
                ra) + "&dec=" + str(dec) + "&system=J2000&dra=" + str(
                    rawidth) + '&ddec=' + str(
                        decwidth) + "&sort=mag&epoch=2000.00000&nstar=6400"
        cat = urllib.urlopen(queryurl)
        catlines = cat.readlines()
        cat.close()

        if len(catlines) > 6400 - 20:
            print 'WARNING: Reached maximum catalog query size.'
            print '         Gaps may be present in the catalog, leading to a poor solution or no solution.'
            print '         Decrease the search radius.'
    else:
        usercat = 1
        try:
            cat = open(catalog, 'r')
            print 'Reading user catalog ', catalog
        except:
            print 'Failed to open user catalog ', catalog
            print 'File not found or invalid online catalog.  Specify tmpsc, ub2, sdss, or tmc.'
            return []
        racolumn = 0
        deccolumn = 1  # defaults
        magcolumn = -1  #  (to override, specify in first line using format #:0,1,2)
        catlines = cat.readlines()
        cat.close()

    #Finds which lines are not comments (anything before a line starting with '-----' only for regular catalogs (user catalog has no comments)
    #Then gathers ra, dec, mag (takes first given magnitude) from specified columns set for each catalog
    #If magnitude not given skip entries, also skip entries that have too faint of too bright of magnitudes
    #as well as values not within proper motion parameters
    if usercat == 1:
        comment = False
    else:
        comment = True
    catlist = []

    #SDSS direct query has different format than other scat queries
    if catalog == 'sdss':
        comment = False
        catlines = catlines[1:]

    for line in catlines:

        if not comment:
            if catalog == 'sdss':
                cline = line.split(',')
            else:
                cline = line.split()

            narg = len(cline)

            if line[0:2] == '#:':
                inlinearg = line[2:].split(',')
                racolumn = int(inlinearg[0]) - 1
                deccolumn = int(inlinearg[1]) - 1
                if len(inlinearg) > 2: magcolumn = int(inlinearg[2]) - 1
                continue

            if cline[racolumn].find(':') == -1:
                ra = float(cline[racolumn])
            else:
                ra = astrometrystats.rasex2deg(cline[racolumn])

            if cline[deccolumn].find(':') == -1:
                dec = float(cline[deccolumn])
            else:
                dec = astrometrystats.decsex2deg(cline[deccolumn])

            if magcolumn >= 0 and narg > magcolumn:
                try:
                    mag = float(cline[magcolumn])
                except:
                    mag = float(cline[magcolumn][0:-2])
            else:
                mag = maxmag  #Lets all magnitudes pass for user set catalog

            if usercat == 0 and narg > pmracolumn and narg > pmdeccolumn:
                pmra = float(cline[pmracolumn])
                pmdec = float(cline[pmdeccolumn])
            else:
                pmra = pmdec = 0

            if mag > maxmag: continue  #don't believe anything this faint
            if mag < minmag: continue  #ignore anything this bright
            if abs(pmra) > maxpm or abs(pmdec) > maxpm: continue

            iobj = Obj(ra, dec, mag)  #process the line into an object
            catlist.append(iobj)
#Add to catalog array in vertical stack

        if line.find('---') != -1:
            comment = False

    #Sort by magnitude
    catlist.sort(astrometrystats.magcomp)

    return catlist
def autoastrometry(filename,pixelscale=-1,pa=-999,inv=0,uncpa=-1,userra=-999, userdec=-999, minfwhm=1.5,maxfwhm=20,maxellip=0.5,boxsize=-1,maxrad=-1,tolerance=0.010,catalog='',nosolve=0,overwrite=False, outfile='', saturation=-1, quiet=False):
  
    # Get some basic info from the header  
    try: 
       fits = pyfits.open(filename)
       fits.verify('silentfix')
    except:
       print 'Error opening', filename
       if os.path.isfile(filename)==False: print 'File does not exist.'
       return -1
    h = fits[0].header  #ideally check for primary extension, or even iterate
    sfilename = filename
    
    #Position angle set to 0 if pixel scale set
    if pixelscale > 0 and pa == -999: pa = 0

	#If pixel scale set and position angle set (default is -999), 
	#calculate CD*, CR* header keywords and write as temp.fits
    if pixelscale > 0 and pa > -360:
 
       parad = pa * numpy.pi / 180.
       pxscaledeg = pixelscale / 3600.
       
       if inv > 0: 
          parity = -1
       else:
          parity = 1
          
       if  360. > userra >= 0.:
          ra = userra
       else:
          try:
             ra = astrometrystats.rasex2deg(h['RA'])
          except:
             ra = astrometrystats.rasex2deg(h['CRVAL1'])
             
       if 360. > userdec >= 0.:
          dec = userdec
       else:
          try:
             dec = astrometrystats.decsex2deg(h['DEC'])
          except:
             dec = astrometrystats.decsex2deg(h['CRVAL2'])
       
       epoch = float(h.get('EPOCH', 2000))
       equinox = float(h.get('EQUINOX', epoch)) #If RA and DEC are not J2000 then convert

       if abs(equinox-2000) > 0.5:
           print 'Converting equinox from', equinox, 'to J2000'
           try:
              j2000 = ephem.Equatorial(ephem.Equatorial(str(ra/15), str(dec), epoch=str(equinox)),epoch=ephem.J2000)
              [ra, dec] = [astrometrystats.rasex2deg(j2000.ra), astrometrystats.decsex2deg(j2000.dec)]
           except:
              print 'PyEphem is not installed but is required to precess this image.'
              return -1
       h.update("CD1_1",  pxscaledeg * numpy.cos(parad)*parity)
       h.update("CD1_2",  pxscaledeg * numpy.sin(parad))
       h.update("CD2_1", -pxscaledeg * numpy.sin(parad)*parity)
       h.update("CD2_2",  pxscaledeg * numpy.cos(parad))
       h.update("CRPIX1", h['NAXIS1']/2)
       h.update("CRPIX2", h['NAXIS2']/2)
       h.update("CRVAL1", ra)
       h.update("CRVAL2", dec)
       h.update("CTYPE1","RA---TAN")
       h.update("CTYPE2","DEC--TAN")
       h.update("EQUINOX",2000.0)
       
       if os.path.isfile('temp.fits'): os.remove('temp.fits')
       fits[0].header = h
       fits.writeto('temp.fits',output_verify='silentfix') #,clobber=True
       fits.close()
       fits = pyfits.open('temp.fits')
       h = fits[0].header
       sfilename = 'temp.fits'

    #Read the WCS values from file header (even if we put it there in the first place)
    try:
        # no longer drawing RA and DEC from here.
        nxpix = h['NAXIS1']
        nypix = h['NAXIS2']
    except:
        print 'Cannot find necessary WCS header keyword NAXIS*'
        sys.exit(1)
    try: 
        cra  =  float(h['CRVAL1'])
        cdec = float(h['CRVAL2'])
        crpix1 = float(h['CRPIX1'])  
        crpix2 = float(h['CRPIX2'])
        cd11 = float(h['CD1_1'])
        cd22 = float(h['CD2_2'])
        cd12 = float(h['CD1_2']) # deg / pix
        cd21 = float(h['CD2_1'])
    except:
        print 'Cannot find necessary WCS header keyword CRVAL*, CRPIX*, or CD*_*'
        print 'Must specify pixel scale (-px VAL) or provide provisional basic WCS info via CD matrix.'
        sys.exit(1)

	#This section deals with manipulating WCS coordinates, for thorough description see:
	#iraf.noao.edu/iraf/ftp/misc/fitswcs_draft.ps

	#Determine parity from sign of determinant of CD matrix   
    if cd11 * cd22 < 0 or cd12 * cd21 > 0:
       parity = -1
    else:
       parity = 1
        
    #Calculates CDELTA1 (xscale) and CDELTA2 (yscale) which is how much RA or DEC 
    #changes when you move along a column or row
    xscale = numpy.sqrt(cd11**2 + cd21**2)
    yscale = numpy.sqrt(cd12**2 + cd22**2)
    
    #Calculates CROTA2 based from transformations between CD matrix and CDELT values
    initpa = -parity * numpy.arctan2(cd21 * yscale, cd22 * xscale) * 180 / numpy.pi
    
    #Find field width based on largest dimension and calculates the area of the field
    #as well as the pixel scale in arcseconds
    xscale = abs(xscale)
    yscale = abs(yscale)
    fieldwidth = max(xscale * nxpix, yscale * nypix) * 3600.    
    area_sqdeg = xscale * nxpix * yscale * nypix
    area_sqmin = area_sqdeg * 3600. 
    area_sqsec = area_sqmin * 3600. 
    pixscale = numpy.sqrt(xscale*yscale) * 3600.

	#Finds center pixel in each dimension
    centerx = nxpix/2
    centery = nypix/2
    
    #Calculate how the center pixel relates the to the header value's crpix1/2.  
    #Theoretically, centerx and crpix1 should be the same.  But most of the time, they are not. 
    #This is due to a number of reasons, ranging from the algorithm used to calculate the 
    #astrometry to simply cropping the image at some point in the reduction.  
    #So, the crpix header value may be hundreds of pixels off from the actual center of the actual field.
    centerdx = centerx - crpix1
    centerdy = centery - crpix2
    
    #Calculate the RA and DEC at center of field to correct initial guess
    centerra  = cra  - centerdx*xscale*numpy.cos(initpa*numpy.pi/180.) + centerdy*yscale*numpy.sin(initpa*numpy.pi/180.)
    centerdec = cdec + parity*centerdx*xscale*numpy.sin(-initpa*numpy.pi/180.) + centerdy*yscale*numpy.cos(initpa*numpy.pi/180.)
    print 'cra=%10.6f, centerdx=%10.6f, xscale=%10.6f, centerdy=%10.6f, yscale=%10.6f' % (cra,centerdx,xscale,centerdy,yscale)
    # this has only been checked for a PA of zero.
  
    if quiet == False:
       print 'Initial WCS info:'
       print '   pixel scale:     x=%.4f"/pix,   y=%.4f"/pix' % (xscale*3600, yscale*3600)
       print '   position angle: PA=%.2f' % initpa
       if parity ==  1: print '   normal parity'
       if parity == -1: print '   inverse parity'
       print '   center:        RA=%10.6f, dec=%9.6f' % (centerra, centerdec)
       print '   field width: %10.6f' % (fieldwidth)

	#Run sextract (runs sextractor) to produce image star catalog
    goodsexlist = astrometrysources.sextract(sfilename, nxpix, nypix, 3, 12, minfwhm=minfwhm, maxfwhm=maxfwhm, maxellip=maxellip, saturation=saturation, sexpath=sexpath)
    
    #If there are less than 4 good objects, ends program and writes images to txt and region files
    ngood = len(goodsexlist)
    if ngood < 4:
       print 'Only', ngood, 'good stars were found in the image.  The image is too small or shallow, the detection'
       print 'threshold is set too high, or stars and cosmic rays are being confused.'      
       #Saves text file that contains RA, DEC, and mag of sextractor list
       writetextfile('det.init.txt', goodsexlist)
       writeregionfile('det.im.reg', goodsexlist, 'red', 'img')
       return -1

	#Finds source number density
    density = len(goodsexlist) / area_sqmin
    print 'Source density of %f4 /arcmin^2' % density
    
    #If set to only solve for catalog and not astrometry, save good list
    if nosolve == 1: 
       if catalog == '': catalog = 'det.ref.txt'
       #Saves text file that contains RA, DEC, and mag of sextractor list
       writetextfile(catalog, goodsexlist)
       return

    #If no catalog specified, find catalog with > 15 entries for center RA and DEC encircled by radius of 90 arcseconds
    #If no catalog found after that, end program
    if catalog == '':
    
        trycats = ['tmpsc','sdss', 'ub2', 'tmc']
        for trycat in trycats:
            testqueryurl = "http://tdc-www.harvard.edu/cgi-bin/scat?catalog=" + trycat +  "&ra=" + str(centerra) + "&dec=" + str(centerdec) + "&system=J2000&rad=" + str(-90)
            check = urllib.urlopen(testqueryurl)
            checklines = check.readlines()
            check.close()
            if len(checklines) > 15:
                catalog = trycat
                print 'Using catalog', catalog
                break
        if (catalog == ''):
            print 'No catalog is available.  Check your internet connection.'
            return -1
	
    #Load in reference star catalog with boxsize
    if (boxsize == -1):
        boxsize = fieldwidth

    catlist = astrometrysources.getcatalog(catalog, centerra, centerdec, boxsize)
    
    ncat = len(catlist)
    catdensity = ncat / (2*boxsize/60.)**2
    print ncat, 'good catalog objects.'
    print 'Source density of %f4 /arcmin^2' % catdensity
    
    #Throws up warning if very few catalog objects, stops program if no catalog objects found
    if 0 < ncat < 5:
       print 'Only ', ncat, ' catalog objects in the search zone.  Increase the magnitude threshold or box size.'

    if ncat == 0 :
       print
       print 'No objects found in catalog.'
       print 'The web query failed, all stars were excluded by the FHWM clip, or the image'
       print 'is too small.  Check input parameters or your internet connection.'
       return -1   
  
    #If this image is actually shallower than reference catalog, trim the reference catalog down
    if ncat > 16 and catdensity > 3 * density:
        print 'Image is shallow.  Trimming reference catalog...'
        while catdensity > 3 * density:
            catlist = catlist[0:len(catlist)*4/5]
            ncat = len(catlist)
            catdensity = ncat / (2*boxsize/60.)**2
        
    #If the image is way deeper than USNO, trim the image catalog down
    if ngood > 8 and density > 4 * catdensity:
        print 'Image is deep.  Trimming image catalog...'
        while density > 4 * catdensity and ngood > 8:
            goodsexlist = goodsexlist[0:len(goodsexlist)*4/5]
            ngood = len(goodsexlist)
            density = ngood / area_sqmin

    #If too many objects, do some more trimming
    if ngood*ncat > 120*120*4:
        print 'Image and/or catalog still too deep.  Trimming...'
        while ngood*ncat > 120*120*4:
            if density > catdensity: 
                goodsexlist = goodsexlist[0:len(goodsexlist)*4/5]
                ngood = len(goodsexlist)
                density = ngood / area_sqmin
            else:
                catlist = catlist[0:len(catlist)*4/5]
                ncat = len(catlist)
                catdensity = ncat / (2*boxsize/60.)**2   
    
    #Remove fainter object in close pairs for both lists
    goodsexlist = astrometrydist.tooclose(goodsexlist, minsep=3)
    catlist = astrometrydist.tooclose(catlist, minsep=3)
    
    #Saves text file that contains RA, DEC, and mag of sextractor list
    writetextfile('det.init.txt', goodsexlist)
    writeregionfile('det.im.reg', goodsexlist, 'red', 'img')
    writetextfile('cat.txt', catlist)
    writeregionfile('cat.wcs.reg', catlist, 'green', 'wcs')   

    ##### The catalogs have now been completed. Now start getting into the actual astrometry. #####
    
    #Maximum radius (in arcseconds) calculated by looking at the radius of 15 object in the sparsest dataset
    #Must at least 60 arcseconds or 75% of the field width (whichever is smaller)
    minrad = 5.0
    if (maxrad == -1):  
    	#60 arcsec/arcmin *sqrt(15 stars/(stars/arcsec^2)) / 2 <-- density is for a box, so half the length of the box is the radius                                          
        maxrad = 30.0*(15.0 /min(density,catdensity))**0.5  
        maxrad = max(maxrad, 60.0)
        
        if maxrad == 60.0:                           
             minrad = 10.0   # in theory could scale this up further to reduce #comparisons i.e. instead of 15 stars, x stars
        maxrad = min(maxrad, fieldwidth*3./4)       
        
    #Finds the number of objects expected within circular area (chooses smaller of the entire field or of area 
    #in between min and max radius). NOTE: density is per arcmin^2, while the radii are in arcsec, hence the conversion factor. 
    circlearea     = (numpy.pi*(maxrad/60.)**2 - numpy.pi*(minrad/60)**2) #in arcmin^2
    circdensity    = density * min([area_sqmin, circlearea])
    circcatdensity = catdensity * circlearea	#Finds number of catalog objects expected within circular area
    catperimage    = catdensity * area_sqmin	#Finds number of catalog objects expected within field

    print 'After trimming: '
    print '   ', len(goodsexlist), 'detected objects (%.2f/arcmin^2, %.1f/searchzone)' % (density, circdensity)
    print '   ', len(catlist),     'catalog objects (%.2f/arcmin^2, %.1f/searchzone)' % (catdensity, circcatdensity)
	
	#Sets position angle tolerance and calculates the expected number of false multiples
    patolerance = defaultpatolerance
    expectfalsepairs = ngood * ncat * circdensity**1 * circcatdensity**1 * tolerance**1 * (patolerance/360.)**0
    expectfalsetrios = ngood * ncat * circdensity**2 * circcatdensity**2 * tolerance**2 * (patolerance/360.)**1
    expectfalsequads = ngood * ncat * circdensity**3 * circcatdensity**3 * tolerance**3 * (patolerance/360.)**2
    expectfalsequint = ngood * ncat * circdensity**4 * circcatdensity**4 * tolerance**4 * (patolerance/360.)**3

	#Guess that 30% of the sextractor sources overlap with stars, finds estimate of how many real matches we expect
    overlap1 = 0.3 * min(1,catdensity/density) # fraction of stars in image that are also in catalog - a guess
    truematchesperstar = (circdensity * overlap1) # but how many matches >3 and >4?  some annoying binomial thing
    
    #Default required match is 3 (triangle), but can require more if we expect a log of false triples or less if not many sources in either image
    reqmatch = 3
    if expectfalsetrios > 30 and truematchesperstar >= 4: reqmatch = 4   
       #should check that this will actually work for the catalog, too.
    if catperimage <= 6 or ngood <= 6: reqmatch = 2 
    if catperimage <= 3 or ngood <= 3: reqmatch = 1
        #for an extremely small or shallow image

	#Calculates the matched stars between sextractor and catalog
    print 'Pair comparison search radius: %.2f"'%maxrad
    print 'Using reqmatch =', reqmatch
    (primarymatchs, primarymatchc, mpa) = astrometrydist.distmatch(goodsexlist, catlist, maxrad, minrad, reqmatch, patolerance, uncpa, showmatches=showmatches, fastmatch=fastmatch)
    
    #Quits program if no matches or too few matches found (gives different error readouts)
    nmatch = len(primarymatchs)
    if nmatch == 0:
        print ' No valid matches found!'
        if quiet == False:
           print ' Possible issues:'
           print '  - The specified pixel scale (or PA or parity) is incorrect.  Double-check the input value.'
           print '  - The field is outside the catalog search region.  Check header RA/DEC or increase search radius.'
           print '  - The routine is flooded by bad sources.  Specify or check the input seeing.'
           print '  - The routine is flagging many real stars.  Check the input seeing.'
           print ' You can display a list of detected/catalog sources using det.im.reg and cat.wcs.reg.'
        return -1
    if nmatch <= 2:
        print 'Warning: only', nmatch, 'match(es).  Astrometry may be unreliable.'
        if quiet == False:
           print '   Check the pixel scale and parity and consider re-running.'
        return -1
        warning = 1

    #We now have the PA and a list of stars that are almost certain matches.
    offpa = astrometrystats.median(mpa)  #get average PA from the excellent values
    stdevpa = astrometrystats.stdev(mpa)
    
    skyoffpa = -parity*offpa # This appears to be necessary for the printed value to agree with our normal definition.

    print 'PA offset:'
    print '  dPA = %.3f  (unc. %.3f)' % (skyoffpa, stdevpa)

    # Rotate the image to the new, correct PA
    #  NOTE: when CRPIX don't match CRVAL this shifts the center and screws things up.  
    #  I don't understand why they don't always match.  [[I think this was an equinox issue.
    #  should be solved now, but be alert for further problems.]]
    
    #Greisen et al.:
    #WCS_i = SUM[j] (CD_ij)(p_j - CRPIX_j)      i.e.
    # RA - CRVAL1 = CD1_1 (x - CRPIX1) + CD1_2 (y - CRPIX2)
    #dec - CRVAL2 = CD2_1 (x - CRPIX1) + CD2_2 (y - CRPIX2)   [times a projection scale...]

    #Rotate CD matrix to account for additional rotation calculated from catalog star matching
    rot = offpa * numpy.pi/180
      #...the image itself
    h.update("CD1_1", numpy.cos(rot)*cd11 - numpy.sin(rot)*cd21 )
    h.update("CD1_2", numpy.cos(rot)*cd12 - numpy.sin(rot)*cd22 )  # a parity issue may be involved here?
    h.update("CD2_1", numpy.sin(rot)*cd11 + numpy.cos(rot)*cd21 )
    h.update("CD2_2", numpy.sin(rot)*cd12 + numpy.cos(rot)*cd22 )
      #...the coordinates (so we don't have to resex) 
    for i in range(len(goodsexlist)):  #do all of them, though this is not necessary
        goodsexlist[i].rotate(offpa,cra,cdec)

    #Saves text file that contains RA, DEC, and mag of sextractor list
    writetextfile('det.wcs.txt', goodsexlist)
    
    #Calculate shift in RA and DEC for each object compared to its catalog match
    imraoffset = []
    imdecoffset = []
    for i in range(len(primarymatchs)):
        imraoffset.append(goodsexlist[primarymatchs[i]].ra - catlist[primarymatchc[i]].ra)
        imdecoffset.append(goodsexlist[primarymatchs[i]].dec - catlist[primarymatchc[i]].dec)
    
    #Find the median and standard deviation of offsets    
    raoffset = -astrometrystats.median(imraoffset)
    decoffset = -astrometrystats.median(imdecoffset)
    rastd = astrometrystats.stdev(imraoffset)*numpy.cos(cdec*numpy.pi/180)  # all of these are in degrees
    decstd = astrometrystats.stdev(imdecoffset)
    stdoffset = numpy.sqrt(rastd**2 + decstd**2)
    
    #Change from degrees to arcseconds
    raoffsetarcsec = raoffset*3600*numpy.cos(cdec*numpy.pi/180)
    decoffsetarcsec = decoffset*3600
    totoffsetarcsec = (raoffsetarcsec**2 + decoffset**2)**0.5
    stdoffsetarcsec = stdoffset*3600
    
    print 'Spatial offset:'
    print '  dra = %.2f",  ddec = %.2f"  (unc. %.3f")' % (raoffsetarcsec, decoffsetarcsec, stdoffsetarcsec)
    
    #If standard deviation of total offset is larger than 10 arcseconds end program
    warning = 0
    if (stdoffset*3600 > 10.0):
        print 'WARNING: poor solution - some matches may be bad.  Check pixel scale?'
        return -1
        warning = 1
    
    #Shift center pixel to match catalog values
    h.update("CRVAL1", cra + raoffset)
    h.update("CRVAL2", cdec + decoffset)
    
    #Add keywords to header to detail changes made to WCS coordinates
    try:
       oldcat = h['ASTR_CAT']
       h.update("OLD_CAT",oldcat, "Earlier reference catalog")
    except:
       pass
    h.update("ASTR_CAT", catalog, "Reference catalog for vlt_autoastrometry")
    h.update("ASTR_UNC", stdoffsetarcsec, "Astrometric scatter vs. catalog (arcsec)")
    h.update("ASTR_SPA", stdevpa, "Measured uncertainty in PA (degrees)")
    h.update("ASTR_DPA", skyoffpa, "Change in PA (degrees)")
    h.update("ASTR_OFF", totoffsetarcsec, "Change in center position (arcsec)")
    h.update("ASTR_NUM", len(primarymatchs), "Number of matches")

    #Write out a match list to allow doing a formal fit with WCStools.
    outmatch = open('match.list','w')
    for i in range(len(primarymatchs)):
        si = primarymatchs[i]
        ci = primarymatchc[i]
        outmatch.write("%s %s  %s %s\n" % (goodsexlist[si].x, goodsexlist[si].y, catlist[ci].ra, catlist[ci].dec))
    outmatch.close()                                                     
    
    # Could repeat with scale adjustment
    # Could then go back to full good catalog and match all sources
    
    #Create new file with new header
    if overwrite: outfile = filename
    if outfile == '': 
        slashpos = filename.rfind('/')
        dir = filename[0:slashpos+1]
        fil = filename[slashpos+1:]
        outfile = dir+'a'+fil # alternate behavior would always output to current directory
    try:
        os.remove(outfile)
    except:
        pass
    fits[0].header = h
    fits.writeto(outfile,output_verify='silentfix') #,clobber=True
    print 'Written to '+outfile

    fits.close()
    
    #Return relevant offsets
    return (nmatch, skyoffpa, stdevpa, raoffsetarcsec, decoffsetarcsec, stdoffsetarcsec)
Beispiel #4
0
def getcatalog(catalog, ra, dec, boxsize, minmag=8.0, maxmag=-1, maxpm=60.0):
    # Get catalog from USNO

    # If maximum magnitude is not set, assign max magnitude based on catalog max or set default
    if maxmag == -1:
        maxmag = 999  # default (custom catalog)
        if catalog == "tmpsc":
            maxmag = 20.0
        if catalog == "ub2":
            maxmag = 21.0
        if catalog == "sdss":
            maxmag = 22.0
        if catalog == "tmc":
            maxmag = 20.0

    # If catalog is one of the standard 4, then set values for indices for catalogs
    # Search catalog with given box size (if no box size selected, will be fieldwidth)
    # Read in file to variable catlines
    if catalog == "tmpsc" or catalog == "ub2" or catalog == "sdss" or catalog == "tmc":
        usercat = 0
        racolumn = 1
        deccolumn = 2
        magcolumn = 6
        if catalog == "tmpsc" or catalog == "tmc":
            magcolumn = 3
        pmracolumn = 10
        pmdeccolumn = 11
        queryurl = (
            "http://tdc-www.harvard.edu/cgi-bin/scat?catalog="
            + catalog
            + "&ra="
            + str(ra)
            + "&dec="
            + str(dec)
            + "&system=J2000&rad="
            + str(-boxsize)
            + "&sort=mag&epoch=2000.00000&nstar=6400"
        )
        print queryurl
        cat = urllib.urlopen(queryurl)
        catlines = cat.readlines()
        cat.close()
        if len(catlines) > 6400 - 20:
            print "WARNING: Reached maximum catalog query size."
            print "         Gaps may be present in the catalog, leading to a poor solution or no solution."
            print "         Decrease the search radius."
    else:
        usercat = 1
        try:
            cat = open(catalog, "r")
            print "Reading user catalog ", catalog
        except:
            print "Failed to open user catalog ", catalog
            print "File not found or invalid online catalog.  Specify tmpsc, ub2, sdss, or tmc."
            return []
        racolumn = 0
        deccolumn = 1  # defaults
        magcolumn = -1  #  (to override, specify in first line using format #:0,1,2)
        catlines = cat.readlines()
        cat.close()

    # Finds which lines are not comments (anything before a line starting with '-----' only for regular catalogs (user catalog has no comments)
    # Then gathers ra, dec, mag (takes first given magnitude) from specified columns set for each catalog
    # If magnitude not given skip entries, also skip entries that have too faint of too bright of magnitudes
    # as well as values not within proper motion parameters
    if usercat == 1:
        comment = False
    else:
        comment = True
    catlist = []

    for line in catlines:

        if not comment:
            cline = line.split()
            narg = len(cline)

            if line[0:2] == "#:":
                inlinearg = line[2:].split(",")
                racolumn = int(inlinearg[0]) - 1
                deccolumn = int(inlinearg[1]) - 1
                if len(inlinearg) > 2:
                    magcolumn = int(inlinearg[2]) - 1
                continue

            if cline[racolumn].find(":") == -1:
                ra = float(cline[racolumn])
            else:
                ra = astrometrystats.rasex2deg(cline[racolumn])

            if cline[deccolumn].find(":") == -1:
                dec = float(cline[deccolumn])
            else:
                dec = astrometrystats.decsex2deg(cline[deccolumn])

            if magcolumn >= 0 and narg > magcolumn:
                try:
                    mag = float(cline[magcolumn])
                except:
                    mag = float(cline[magcolumn][0:-2])
            else:
                mag = maxmag  # Lets all magnitudes pass for user set catalog

            if usercat == 0 and narg > pmracolumn and narg > pmdeccolumn:
                pmra = float(cline[pmracolumn])
                pmdec = float(cline[pmdeccolumn])
            else:
                pmra = pmdec = 0

            if mag > maxmag:
                continue  # don't believe anything this faint
            if mag < minmag:
                continue  # ignore anything this bright
            if abs(pmra) > maxpm or abs(pmdec) > maxpm:
                continue

            iobj = Obj(ra, dec, mag)  # process the line into an object
            catlist.append(iobj)
            # Add to catalog array in vertical stack

        if line.find("---") != -1:
            comment = False

    # Sort by magnitude
    catlist.sort(astrometrystats.magcomp)

    return catlist