Exemple #1
0
def distmatch(sexlist,
              catlist,
              maxrad=180,
              minrad=10,
              reqmatch=3,
              patolerance=1.2,
              uncpa=-1,
              showmatches=0,
              fastmatch=1):

    if reqmatch < 2:
        print 'Warning: reqmatch >=3 suggested'
    if patolerance <= 0:
        print 'PA tolerance cannot be negative!!!'
        patolerance = abs(patolerance)
    if uncpa < 0: uncpa = 720

    declist = []
    for s in sexlist:
        declist.append(s.dec_rad)
    avdec_rad = astrometrystats.median(declist)  # faster distance computation
    rascale = numpy.cos(avdec_rad)  # will mess up meridian crossings, however

    #Calculates distances between objects in same list for both image catalog and reference catalog
    (sexdists, sexmatchids) = calcdist(sexlist, maxrad, minrad, rascale)
    (catdists, catmatchids) = calcdist(catlist, maxrad, minrad, rascale)

    # Now look for matches in the reference catalog to distances in the image catalog.
    countgreatmatches = 0

    smatch = []
    cmatch = []
    mpa = []
    offset = []
    offpa = []
    nmatch = []
    primarymatchs = []
    primarymatchc = []

    #For each sextractor source A
    for si in range(len(sexdists)):
        sexdistarr = sexdists[si]
        sexidarr = sexmatchids[si]
        if len(sexdistarr) < 2: continue

        #For each catalog source B
        for ci in range(len(catdists)):
            catdistarr = catdists[ci]
            catidarr = catmatchids[ci]
            if len(catdistarr) < 2: continue
            match = 0
            smatchin = []
            cmatchin = []

            #For each sextractor source A, use the distances to other sextractor sources
            #that are within the radius range
            for sj in range(len(sexdistarr)):
                sexdist = sexdistarr[sj]
                newmatch = 1

                #For each catalog source B, use the distances to other catalog sources
                #that are within the radius range and compare the ratio to
                #(sextractor source A - sextractor sources) to (catalog source B - catalog sources)
                #If within 3% then save the match (only count 1 match per catalog row)
                for cj in range(len(catdistarr)):
                    catdist = catdistarr[cj]
                    if abs((sexdist / catdist) - 1.0) < 0.03:

                        match += newmatch
                        newmatch = 0  #further matches before the next sj loop indicate degeneracies
                        smatchin.append(sexmatchids[si][sj])
                        cmatchin.append(catmatchids[ci][cj])

#If number of matches is above or equal to required number of matches for sextractor source A
#(i.e. sextractor source A's distance to other sources makes it a likely candidate to be a catalog source)
#Find difference in position angle between matched angle between sextractor source A and matched source with
#catalog source B and matched catalog source.
#Remove values that are larger than user specified value or if above position angle tolerance.
            if match >= reqmatch:

                dpa = []

                # Here, dpa[n] is the mean rotation of the PA from the primary star of this match
                #  to the stars in its match RELATIVE TO those same angles for those same stars
                #  in the catalog.  Therefore it is a robust measurement of the rotation.
                for i in range(len(smatchin)):
                    ddpa = posangle(sexlist[si],
                                    sexlist[smatchin[i]]) - posangle(
                                        catlist[ci], catlist[cmatchin[i]])
                    while ddpa > 200:
                        ddpa -= 360.
                    while ddpa < -160:
                        ddpa += 360.
                    dpa.append(ddpa)

                #If user was confident the initial PA was right, remove bad PA's right away
                for i in range(len(smatchin) - 1, -1, -1):
                    if abs(dpa[i]) > uncpa:
                        del smatchin[i]
                        del cmatchin[i]
                        del dpa[i]

                if len(smatchin) < 2: continue

                #Finds mode of difference position angle but allowing values to be +/- patolerance
                dpamode = astrometrystats.most(dpa,
                                               vmin=patolerance * 3,
                                               vmax=patolerance * 3)

                #Remove deviant matches by PA
                for i in range(len(smatchin) - 1, -1, -1):
                    if abs(dpa[i] - dpamode) > patolerance:
                        del smatchin[i]
                        del cmatchin[i]
                        del dpa[i]

                if len(smatchin) < 2: continue

                #Finds the number of degenerate matches
                ndegeneracies = len(smatchin) - len(
                    astrometrystats.unique(smatchin)) + len(cmatchin) - len(
                        astrometrystats.unique(cmatchin))
                # this isn't quite accurate (overestimates if degeneracies are mixed up)

                #Save values
                mpa.append(dpamode)
                primarymatchs.append(si)
                primarymatchc.append(ci)
                smatch.append(smatchin)
                cmatch.append(cmatchin)
                nmatch.append(len(smatchin) - ndegeneracies)

                #If the number of unique sextractor sources is greater than 6, count as a great match
                if (len(smatchin) - ndegeneracies > 6): countgreatmatches += 1

        #Breaks loop if over 16 great matches are found and fastmatch
        if countgreatmatches > 16 and fastmatch == 1:
            break  #save processing time

    #If no matches found end program and return empty lists
    nmatches = len(smatch)
    if (nmatches == 0):
        print 'Found no potential matches of any sort (including pairs).'
        print 'The algorithm is probably not finding enough real stars to solve the field.  Check seeing.'
        return [], [], []

    #Get rid of matches that don't pass the reqmatch cut (2nd cut after removing bad position angles)
    for i in range(len(primarymatchs) - 1, -1, -1):
        if nmatch[i] < reqmatch:
            del mpa[i]
            del primarymatchs[i]
            del primarymatchc[i]
            del smatch[i]
            del cmatch[i]
            del nmatch[i]

#If no remaining matches then exit program
    if len(smatch) < 1:
        print 'Found no matching clusters of reqmatch =', reqmatch
        return [], [], []

    #If we still have lots of matches, get rid of those with the minimum number of submatches
    #(that is, increase reqmatch by 1)
    minmatch = min(nmatch)
    countnotmin = 0

    #Finds how many matches have more than the minimum require matches
    for n in nmatch:
        if n > minmatch: countnotmin += 1

    #If the number of matches is above 16 and there are more than 3 sources with more than the
    #required number of matches, then delete any source with the bare minimum number of matches
    if len(nmatch) > 16 and countnotmin > 3:
        print 'Too many matches: increasing reqmatch to', reqmatch + 1
        for i in range(len(primarymatchs) - 1, -1, -1):
            if nmatch[i] == minmatch:
                del mpa[i]
                del primarymatchs[i]
                del primarymatchc[i]
                del smatch[i]
                del cmatch[i]
                del nmatch[i]

    nmatches = len(
        smatch
    )  # recalculate with the new reqmatch and with prunes supposedly removed
    print 'Found', nmatches, 'candidate matches.'

    # Kill the bad matches
    rejects = 0

    # Use only matches with a consistent PA (finds mode counting those within 3 patolerance)
    offpa = astrometrystats.most(mpa,
                                 vmin=3 * patolerance,
                                 vmax=3 * patolerance)

    #Removes values with rotational positional offsets that are above tolerance, then removes
    #values that are above 2 sigma of those values
    if len(smatch) > 2:

        #Coarse iteration for anything away from the mode
        for i in range(len(primarymatchs) - 1, -1, -1):
            if abs(mpa[i] - offpa) > patolerance:
                del mpa[i]
                del primarymatchs[i]
                del primarymatchc[i]
                del smatch[i]
                del cmatch[i]
                del nmatch[i]
                rejects += 1

        medpa = astrometrystats.median(mpa)
        stdevpa = astrometrystats.stdev(mpa)
        refinedtolerance = (
            2.0 * stdevpa
        )  #VLT Changed from arbitrary value of 2.2 from original script

        #Fine iteration to flag outliers now that we know most are reliable
        for i in range(len(primarymatchs) - 1, -1, -1):
            if abs(mpa[i] - offpa) > refinedtolerance:
                del mpa[i]
                del primarymatchs[i]
                del primarymatchc[i]
                del smatch[i]
                del cmatch[i]
                del nmatch[i]
                rejects += 1  #these aren't necessarily bad, just making more manageable.

    # New verification step: calculate distances and PAs between central stars of matches
    ndistflags = [0] * len(primarymatchs)
    for v in range(2):  #two iterations

        if len(primarymatchs) == 0: break

        #Find distances between central stars of matches and compare sextractor source distances to catalog sources
        for i in range(len(primarymatchs)):
            for j in range(len(primarymatchs)):
                if i == j: continue
                si = primarymatchs[i]
                ci = primarymatchc[i]
                sj = primarymatchs[j]
                cj = primarymatchc[j]

                sexdistij = distance(sexlist[si], sexlist[sj])
                catdistij = distance(catlist[ci], catlist[cj])

                try:
                    if abs((sexdistij / catdistij) - 1.0) > 0.03:
                        ndistflags[i] += 1
                except:  # (occasionally will get divide by zero)
                    pass

        #Delete bad clusters that were flagged for every match
        ntestmatches = len(primarymatchs)
        for i in range(ntestmatches - 1, -1, -1):
            if ndistflags[
                    i] == ntestmatches - 1:  #if every comparison is bad, this is a bad match
                del mpa[i]
                del primarymatchs[i]
                del primarymatchc[i]
                del smatch[i]
                del cmatch[i]
                del nmatch[i]
                rejects += 1

    print 'Rejected', rejects, 'bad matches.'
    nmatches = len(primarymatchs)
    print 'Found', nmatches, 'good matches.'

    #If no remaining matches, return empty lists
    if nmatches == 0:
        return [], [], []

    #Returns pixel scale (great circle distance in catalog [ra, dec]/cartesian distance in sextractor source [x,y])
    pixscalelist = []
    if len(primarymatchs) >= 2:
        for i in range(len(primarymatchs) - 1):
            for j in range(i + 1, len(primarymatchs)):
                si = primarymatchs[i]
                ci = primarymatchc[i]
                sj = primarymatchs[j]
                cj = primarymatchc[j]
                try:
                    pixscalelist.append(
                        distance(catlist[ci], catlist[cj]) /
                        imdistance(sexlist[si], sexlist[sj]))
                except:
                    pass
        pixelscale = astrometrystats.median(pixscalelist)
        pixelscalestd = astrometrystats.stdev(pixscalelist)

        if len(primarymatchs) >= 3:
            print 'Refined pixel scale measurement: %.4f"/pix (+/- %.4f)' % (
                pixelscale, pixelscalestd)
        else:
            print 'Refined pixel scale measurement: %.4f"/pix' % pixelscale

#If showmatches keyword set then print which objects match
    for i in range(len(primarymatchs)):
        si = primarymatchs[i]
        ci = primarymatchc[i]
        print '%3i' % si, 'matches', '%3i' % ci, ' (dPA =%7.3f)' % mpa[i],

        #Keyword set in main program
        if showmatches:
            print
            if len(smatch[i]) < 16:
                print '  ', si, '-->', smatch[i],
                if len(smatch[i]) >= 7: print
                print '  ', ci, '-->', cmatch[i]
            else:
                print '  ', si, '-->', smatch[i][0:10], '+', len(
                    smatch[i]) - 10, 'more'
                print '  ', ci, '-->', cmatch[i][
                    0:10], '+'  #, len(cmatch[i])-10, ' more'
            if i + 1 >= 10 and len(primarymatchs) - 10 > 0:
                print(len(primarymatchs) - 10), 'additional matches not shown.'
                break
        else:
            print ':', str(len(smatch[i])).strip(), 'rays'

#Create region files for DS9 with the sextractor sources (matchlines.im.reg) and catalog (matchlines.wcs.reg)
    out = open('matchlines.im.reg', 'w')
    i = -1
    color = 'red'
    out.write(
        '# Region file format: DS9 version 4.0\nglobal color=' + color +
        ' font="helvetica 10 normal" select=1 highlite=1 edit=1 move=1 delete=1 include=1 fixed=0 source\n'
    )
    out.write('image\n')
    for i in range(len(primarymatchs)):
        si = primarymatchs[i]
        for j in range(len(smatch[i])):
            sj = smatch[i][j]
            out.write(
                "line(%.3f,%.3f,%.3f,%.3f) # line=0 0\n" %
                (sexlist[si].x, sexlist[si].y, sexlist[sj].x, sexlist[sj].y))
    out.close()

    out = open('matchlines.wcs.reg', 'w')
    i = -1
    color = 'green'
    out.write(
        '# Region file format: DS9 version 4.0\nglobal color=' + color +
        ' font="helvetica 10 normal" select=1 highlite=1 edit=1 move=1 delete=1 include=1 fixed=0 source\n'
    )
    out.write('fk5\n')
    for i in range(len(primarymatchs)):
        ci = primarymatchc[i]
        for j in range(len(smatch[i])):
            cj = cmatch[i][j]
            out.write("line(%.5f,%.5f,%.5f,%.5f) # line=0 0\n" %
                      (catlist[ci].ra, catlist[ci].dec, catlist[cj].ra,
                       catlist[cj].dec))
    out.close()

    #future project: if not enough, go to the secondary offsets

    #Returns sextractor sources and catalog sources that appear to have matches along with the mode of the position angle
    return (primarymatchs, primarymatchc, mpa)
Exemple #2
0
def sextract(
    sexfilename, nxpix, nypix, border=3, corner=12, minfwhm=1.5, maxfwhm=25, maxellip=0.5, saturation=-1, sexpath=""
):

    if maxellip == -1:
        maxellip = 0.5

    if saturation > 0:
        sexsaturation = saturation
    else:
        sexsaturation = 9e5

    # Runs sextractor and reads in sextractor output file (temp.cat)
    try:
        # Sextract the image !
        print sexpath + "sex " + sexfilename + " -c sex.config -SATUR_LEVEL " + str(sexsaturation)
        os.system(sexpath + "sex " + sexfilename + " -c sex.config -SATUR_LEVEL " + str(sexsaturation))
    except:
        print " Error: Problem running sextractor"
        print " Check that program is installed and runs at command line using " + sexpath + "sex"
        sys.exit(1)

    # Read in the sextractor catalog
    try:
        cat = numpy.loadtxt("temp.cat", dtype="float", comments="#")
    except:
        print "Cannot load sextractor output file!"
        sys.exit(1)

    if len(cat) == 0:
        print "Sextractor catalog is empty: try a different catalog?"
        sys.exit(1)

    minx = border
    miny = border
    maxx = nxpix - border  # This should be generalized
    maxy = nypix - border

    # Separate columns from sextractor output
    x = cat[:, 0]
    y = cat[:, 1]
    ra = cat[:, 2]
    dec = cat[:, 3]
    mag = cat[:, 4]
    magerr = cat[:, 5]
    ellip = cat[:, 6]
    fwhm = cat[:, 7]
    flag = cat[:, 8]

    # Initial filtering creates mask that will remove borders, corners,
    # values above max ellipticity (galaxies), and within fwhm constraints
    mask = (
        (ellip <= maxellip)
        & (fwhm >= minfwhm)
        & (fwhm <= maxfwhm)
        & (x >= minx)
        & (x <= maxx)
        & (y >= miny)
        & (y <= maxy)
        & (x + y >= corner)
        & (x + nypix - y >= corner)
        & (nxpix - x >= corner)
        & (nxpix - x + nypix - y >= corner)
    )

    # Removes flagged values if saturation level set
    if saturation > 0:
        mask = mask & (flag == 0)

    # VLT Removed code from autoastrometry.py line 387-432 that rules out false detections
    # If too many false positives need to rewrite section in more coherent way
    # OC: Might also be good to screen for false detections created by bad columns/rows

    fwhmlist = fwhm[mask]

    # Calculates the 20% value of the sextractor masked FWHM and the mode
    # to distinguish stars from galaxies (removed by ellipticity) and cosmic rays
    if len(fwhmlist) > 5:
        sfwhmlist = sorted(fwhmlist)
        fwhm20 = sfwhmlist[len(fwhmlist) / 5]  # percentile values
        fwhmmode = astrometrystats.most(sfwhmlist, vmax=0, vmin=0)
    else:
        fwhmmode = minfwhm
        fwhm20 = minfwhm

    # Creates new FWHM cutoff to distinguish stars from cosmic rays
    # This method done from trial and error from original program to calculate typical cutoffs between stars and cosmic
    # OC: if CR's are bigger and more common than stars, this is dangerous...
    refinedminfwhm = numpy.median([0.75 * fwhmmode, 0.9 * fwhm20, minfwhm])
    print "Refined min FWHM:", refinedminfwhm, "pix"

    # Masks out fwhm with more stringent fwhm condition and creates newmask
    # that combines the new masked out fwhm values
    refwhmmask = fwhm > refinedminfwhm
    newmask = refwhmmask & mask

    # Create array with bad values removed and then sort by magnitude (column 4)
    goodsext = cat[newmask]
    sortedgoodsext = goodsext[goodsext[:, 4].argsort()]

    print len(fwhmlist), "objects detected in image (" + str(len(fwhmlist) - len(sortedgoodsext)) + " discarded)"

    # Save RA, DEC, and mag into class and save class objects into list
    goodsexlist = []
    for value in sortedgoodsext:
        # RA = column 2, DEC = column 3, mag = column 4 with columns starting at 0
        indObj = SexObj(value)
        goodsexlist.append(indObj)

    return goodsexlist
Exemple #3
0
def sextract(sexfilename,
             nxpix,
             nypix,
             border=3,
             corner=12,
             minfwhm=1.5,
             maxfwhm=25,
             maxellip=0.5,
             saturation=-1,
             sexpath='',
             quiet=False):

    if maxellip == -1: maxellip = 0.5

    if saturation > 0:
        sexsaturation = saturation
    else:
        sexsaturation = 9e5

#Runs sextractor and reads in sextractor output file (temp.cat)
    try:
        # Sextract the image !
        if quiet == False:
            print sexpath + "sex " + sexfilename + " -c sex.config -SATUR_LEVEL " + str(
                sexsaturation)
        os.system(sexpath + "sex " + sexfilename +
                  " -c sex.config -SATUR_LEVEL " + str(sexsaturation))
    except:
        print ' Error: Problem running sextractor'
        print ' Check that program is installed and runs at command line using ' + sexpath + 'sex'
        sys.exit(1)

# Read in the sextractor catalog
    try:
        cat = numpy.loadtxt('temp.cat', dtype='float', comments='#')
    except:
        print 'Cannot load sextractor output file!'
        sys.exit(1)

    if len(cat) == 0:
        print 'Sextractor catalog is empty: try a different catalog?'
        sys.exit(1)

    minx = border
    miny = border
    maxx = nxpix - border  # This should be generalized
    maxy = nypix - border

    #Separate columns from sextractor output
    x = cat[:, 0]
    y = cat[:, 1]
    ra = cat[:, 2]
    dec = cat[:, 3]
    mag = cat[:, 4]
    magerr = cat[:, 5]
    ellip = cat[:, 6]
    fwhm = cat[:, 7]
    flag = cat[:, 8]
    a_imag = cat[:, 9]
    b_imag = cat[:, 10]

    #Initial filtering creates mask that will remove borders, corners,
    #values above max ellipticity (galaxies), and within fwhm constraints
    mask = (ellip <= maxellip) & (fwhm >= minfwhm) & (fwhm <= maxfwhm) \
      & (x >= minx) & (x <= maxx) & (y >= miny) & (y <= maxy) \
      & (x+y >= corner) & (x+nypix-y >= corner) \
  & (nxpix-x >= corner) & (nxpix-x+nypix-y >= corner) \
  & (a_imag > 1) & (b_imag > 1)

    #Removes flagged values if saturation level set
    if saturation > 0:
        mask = mask & (flag == 0)

    #VLT Removed code from autoastrometry.py line 387-432 that rules out false detections
    #If too many false positives need to rewrite section in more coherent way
    #OC: Might also be good to screen for false detections created by bad columns/rows

    fwhmlist = fwhm[mask]

    #Calculates the 20% value of the sextractor masked FWHM and the mode
    #to distinguish stars from galaxies (removed by ellipticity) and cosmic rays
    if len(fwhmlist) > 5:
        sfwhmlist = sorted(fwhmlist)
        fwhm20 = sfwhmlist[len(fwhmlist) / 5]  #percentile values
        fwhmmode = astrometrystats.most(sfwhmlist, vmax=0, vmin=0)
    else:
        fwhmmode = minfwhm
        fwhm20 = minfwhm

#Creates new FWHM cutoff to distinguish stars from cosmic rays
#This method done from trial and error from original program to calculate typical cutoffs between stars and cosmic
#OC: if CR's are bigger and more common than stars, this is dangerous...
    refinedminfwhm = numpy.median([0.75 * fwhmmode, 0.9 * fwhm20, minfwhm])
    if quiet == False:
        print 'Refined min FWHM:', refinedminfwhm, 'pix'

    #Masks out fwhm with more stringent fwhm condition and creates newmask
    #that combines the new masked out fwhm values
    refwhmmask = fwhm > refinedminfwhm
    newmask = refwhmmask & mask

    #Create array with bad values removed and then sort by magnitude (column 4)
    goodsext = cat[newmask]
    sortedgoodsext = goodsext[goodsext[:, 4].argsort()]

    if quiet == False:
        print len(fwhmlist), 'objects detected in image (' + str(
            len(fwhmlist) - len(sortedgoodsext)) + ' discarded)'

    #Save RA, DEC, and mag into class and save class objects into list
    goodsexlist = []
    for value in sortedgoodsext:
        #RA = column 2, DEC = column 3, mag = column 4 with columns starting at 0
        indObj = SexObj(value)
        goodsexlist.append(indObj)

    return goodsexlist
def distmatch(sexlist, catlist, maxrad=180, minrad=10, reqmatch=3, patolerance=1.2,uncpa=-1, showmatches=0, fastmatch=1):
    
    if reqmatch < 2:
       print 'Warning: reqmatch >=3 suggested'
    if patolerance <= 0: 
       print 'PA tolerance cannot be negative!!!'
       patolerance = abs(patolerance)
    if uncpa < 0: uncpa = 720

    declist = []
    for s in sexlist:
       declist.append(s.dec_rad)
    avdec_rad = astrometrystats.median(declist)       		# faster distance computation
    rascale = numpy.cos(avdec_rad)          # will mess up meridian crossings, however

 	#Calculates distances between objects in same list for both image catalog and reference catalog  
    (sexdists, sexmatchids) = calcdist(sexlist, maxrad, minrad, rascale)
    (catdists, catmatchids) = calcdist(catlist, maxrad, minrad, rascale)
    
    # Now look for matches in the reference catalog to distances in the image catalog.    
    countgreatmatches = 0

    smatch = []
    cmatch = []
    mpa = []
    offset = []
    offpa = []
    nmatch = []   
    primarymatchs = []
    primarymatchc = []
    
    #For each sextractor source A
    for si in range(len(sexdists)):
        sexdistarr = sexdists[si]
        sexidarr = sexmatchids[si]
        if len(sexdistarr) < 2: continue
        
        #For each catalog source B
        for ci in range(len(catdists)):
            catdistarr = catdists[ci]
            catidarr = catmatchids[ci]
            if len(catdistarr) < 2: continue
            match = 0
            smatchin = []
            cmatchin = []
            
            #For each sextractor source A, use the distances to other sextractor sources
            #that are within the radius range
            for sj in range(len(sexdistarr)):
                sexdist = sexdistarr[sj]
                newmatch = 1
                
                #For each catalog source B, use the distances to other catalog sources
                #that are within the radius range and compare the ratio to 
                #(sextractor source A - sextractor sources) to (catalog source B - catalog sources)
                #If within 3% then save the match (only count 1 match per catalog row)
                for cj in range(len(catdistarr)):
                    catdist = catdistarr[cj]
                    if abs((sexdist/catdist)-1.0) < 0.03:

                        match += newmatch
                        newmatch = 0 #further matches before the next sj loop indicate degeneracies
                        smatchin.append(sexmatchids[si][sj])
                        cmatchin.append(catmatchids[ci][cj])                 

			#If number of matches is above or equal to required number of matches for sextractor source A
			#(i.e. sextractor source A's distance to other sources makes it a likely candidate to be a catalog source)
			#Find difference in position angle between matched angle between sextractor source A and matched source with
			#catalog source B and matched catalog source.
			#Remove values that are larger than user specified value or if above position angle tolerance.
            if match >= reqmatch: 
                
                dpa = []
                
                # Here, dpa[n] is the mean rotation of the PA from the primary star of this match
                #  to the stars in its match RELATIVE TO those same angles for those same stars
                #  in the catalog.  Therefore it is a robust measurement of the rotation.
                for i in range(len(smatchin)):
                    ddpa = posangle(sexlist[si],sexlist[smatchin[i]]) - posangle(catlist[ci],catlist[cmatchin[i]])
                    while ddpa > 200: ddpa  -= 360.
                    while ddpa < -160: ddpa += 360.
                    dpa.append(ddpa)

                #If user was confident the initial PA was right, remove bad PA's right away
                for i in range(len(smatchin)-1,-1,-1):
                    if abs(dpa[i]) > uncpa: 
                        del smatchin[i]
                        del cmatchin[i]
                        del dpa[i]
                    
                if len(smatchin) < 2: continue
                
                #Finds mode of difference position angle but allowing values to be +/- patolerance    
                dpamode = astrometrystats.most(dpa, vmin=patolerance*3, vmax=patolerance*3)
                
                #Remove deviant matches by PA
                for i in range(len(smatchin)-1,-1,-1):
                    if abs(dpa[i] - dpamode) > patolerance:
                        del smatchin[i]
                        del cmatchin[i]
                        del dpa[i]
                				
                if len(smatchin) < 2: continue
                
                #Finds the number of degenerate matches
                ndegeneracies = len(smatchin)-len(astrometrystats.unique(smatchin)) + len(cmatchin)-len(astrometrystats.unique(cmatchin))
                    # this isn't quite accurate (overestimates if degeneracies are mixed up)

				#Save values
                mpa.append(dpamode)
                primarymatchs.append(si)
                primarymatchc.append(ci)
                smatch.append(smatchin)
                cmatch.append(cmatchin)
                nmatch.append(len(smatchin)-ndegeneracies)
                
                #If the number of unique sextractor sources is greater than 6, count as a great match
                if (len(smatchin)-ndegeneracies > 6): countgreatmatches += 1
        
        #Breaks loop if over 16 great matches are found and fastmatch
        if countgreatmatches > 16 and fastmatch == 1: break #save processing time
    
    #If no matches found end program and return empty lists
    nmatches = len(smatch)
    if (nmatches == 0):
        print 'Found no potential matches of any sort (including pairs).'
        print 'The algorithm is probably not finding enough real stars to solve the field.  Check seeing.'
        return [], [], []   
    
    #Get rid of matches that don't pass the reqmatch cut (2nd cut after removing bad position angles)
    for i in range(len(primarymatchs)-1,-1,-1):
        if nmatch[i] < reqmatch:
            del mpa[i]
            del primarymatchs[i]
            del primarymatchc[i]
            del smatch[i]
            del cmatch[i]
            del nmatch[i]

	#If no remaining matches then exit program
    if len(smatch) < 1:
        print 'Found no matching clusters of reqmatch =', reqmatch
        return [], [], []

    #If we still have lots of matches, get rid of those with the minimum number of submatches
    #(that is, increase reqmatch by 1)
    minmatch = min(nmatch)
    countnotmin = 0
    
    #Finds how many matches have more than the minimum require matches
    for n in nmatch:
       if n > minmatch: countnotmin += 1
    
    #If the number of matches is above 16 and there are more than 3 sources with more than the 
    #required number of matches, then delete any source with the bare minimum number of matches
    if len(nmatch) > 16 and countnotmin > 3:
        print 'Too many matches: increasing reqmatch to', reqmatch+1
        for i in range(len(primarymatchs)-1,-1,-1):
            if nmatch[i] == minmatch:
                del mpa[i]
                del primarymatchs[i]
                del primarymatchc[i]
                del smatch[i]
                del cmatch[i]
                del nmatch[i]
          
    nmatches = len(smatch) # recalculate with the new reqmatch and with prunes supposedly removed
    print 'Found',nmatches,'candidate matches.'

    # Kill the bad matches
    rejects = 0
    
    # Use only matches with a consistent PA (finds mode counting those within 3 patolerance)
    offpa = astrometrystats.most(mpa, vmin=3*patolerance, vmax=3*patolerance)
    
    #Removes values with rotational positional offsets that are above tolerance, then removes
    #values that are above 2 sigma of those values
    if len(smatch) > 2:    

        #Coarse iteration for anything away from the mode
        for i in range(len(primarymatchs)-1,-1,-1):
            if abs(mpa[i] - offpa) > patolerance:
                del mpa[i]
                del primarymatchs[i]
                del primarymatchc[i]
                del smatch[i]
                del cmatch[i]
                del nmatch[i]
                rejects += 1
    
        medpa = astrometrystats.median(mpa)
        stdevpa = astrometrystats.stdev(mpa)
        refinedtolerance = (2.0 * stdevpa) #VLT Changed from arbitrary value of 2.2 from original script
        
        #Fine iteration to flag outliers now that we know most are reliable
        for i in range(len(primarymatchs)-1,-1,-1):
            if abs(mpa[i] - offpa) > refinedtolerance:
                del mpa[i]
                del primarymatchs[i]
                del primarymatchc[i]
                del smatch[i]
                del cmatch[i]
                del nmatch[i]
                rejects += 1  #these aren't necessarily bad, just making more manageable.
    	
    # New verification step: calculate distances and PAs between central stars of matches
    ndistflags = [0]*len(primarymatchs)
    for v in range(2):  #two iterations

        if len(primarymatchs) == 0: break

        #Find distances between central stars of matches and compare sextractor source distances to catalog sources
        for i in range(len(primarymatchs)):
            for j in range(len(primarymatchs)):
                if i == j: continue
                si = primarymatchs[i]
                ci = primarymatchc[i]
                sj = primarymatchs[j]
                cj = primarymatchc[j]
    
                sexdistij = distance(sexlist[si], sexlist[sj])
                catdistij = distance(catlist[ci], catlist[cj])
                
                try:
                   if abs((sexdistij/catdistij)-1.0) > 0.03:
                      ndistflags[i] += 1
                except:  # (occasionally will get divide by zero)
                   pass

        #Delete bad clusters that were flagged for every match
        ntestmatches = len(primarymatchs)
        for i in range(ntestmatches-1,-1,-1):
            if ndistflags[i] == ntestmatches-1:   #if every comparison is bad, this is a bad match
                del mpa[i]
                del primarymatchs[i]
                del primarymatchc[i]
                del smatch[i]
                del cmatch[i]
                del nmatch[i]
                rejects += 1

    print 'Rejected', rejects, 'bad matches.'
    nmatches = len(primarymatchs)
    print 'Found', nmatches, 'good matches.'

	#If no remaining matches, return empty lists
    if nmatches == 0:
       return [], [], []

    #Returns pixel scale (great circle distance in catalog [ra, dec]/cartesian distance in sextractor source [x,y])
    pixscalelist = []
    if len(primarymatchs) >= 2:
        for i in range(len(primarymatchs)-1):
            for j in range(i+1,len(primarymatchs)):
                si = primarymatchs[i]
                ci = primarymatchc[i]
                sj = primarymatchs[j]
                cj = primarymatchc[j]
                try:
                    pixscalelist.append(distance(catlist[ci],catlist[cj])/imdistance(sexlist[si],sexlist[sj]))
                except:
                    pass
        pixelscale = astrometrystats.median(pixscalelist)
        pixelscalestd = astrometrystats.stdev(pixscalelist)

        if len(primarymatchs) >= 3:
           print 'Refined pixel scale measurement: %.4f"/pix (+/- %.4f)' % (pixelscale, pixelscalestd)
        else:
           print 'Refined pixel scale measurement: %.4f"/pix' % pixelscale

	#If showmatches keyword set then print which objects match
    for i in range(len(primarymatchs)):
        si = primarymatchs[i]
        ci = primarymatchc[i]
        print  '%3i' % si, 'matches', '%3i' % ci, ' (dPA =%7.3f)' % mpa[i],
        
        #Keyword set in main program
        if showmatches:
           print
           if len(smatch[i]) < 16:
              print '  ', si, '-->', smatch[i], 
              if len(smatch[i]) >= 7: print
              print '  ', ci, '-->', cmatch[i]
           else:
              print '  ', si, '-->', smatch[i][0:10], '+', len(smatch[i])-10, 'more'
              print '  ', ci, '-->', cmatch[i][0:10], '+'#, len(cmatch[i])-10, ' more'
           if i+1 >= 10 and len(primarymatchs)-10 > 0: 
              print (len(primarymatchs)-10), 'additional matches not shown.'
              break
        else:
           print ':', str(len(smatch[i])).strip(), 'rays'
      
	#Create region files for DS9 with the sextractor sources (matchlines.im.reg) and catalog (matchlines.wcs.reg)
    out = open('matchlines.im.reg','w')
    i = -1
    color='red'
    out.write('# Region file format: DS9 version 4.0\nglobal color='+color+' font="helvetica 10 normal" select=1 highlite=1 edit=1 move=1 delete=1 include=1 fixed=0 source\n')
    out.write('image\n')
    for i in range(len(primarymatchs)):
        si = primarymatchs[i]
        for j in range(len(smatch[i])):
           sj = smatch[i][j] 
           out.write("line(%.3f,%.3f,%.3f,%.3f) # line=0 0\n" % (sexlist[si].x, sexlist[si].y, sexlist[sj].x, sexlist[sj].y))
    out.close()

    out = open('matchlines.wcs.reg','w')
    i = -1
    color='green'
    out.write('# Region file format: DS9 version 4.0\nglobal color='+color+' font="helvetica 10 normal" select=1 highlite=1 edit=1 move=1 delete=1 include=1 fixed=0 source\n')
    out.write('fk5\n')
    for i in range(len(primarymatchs)):
        ci = primarymatchc[i]
        for j in range(len(smatch[i])):
           cj = cmatch[i][j]
           out.write("line(%.5f,%.5f,%.5f,%.5f) # line=0 0\n" % (catlist[ci].ra, catlist[ci].dec, catlist[cj].ra, catlist[cj].dec))
    out.close()

    #future project: if not enough, go to the secondary offsets   
    
    #Returns sextractor sources and catalog sources that appear to have matches along with the mode of the position angle
    return (primarymatchs, primarymatchc, mpa)