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'
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)
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