Personal tools
You are here: Home / CarnegiePython / Burns' Python Scripts / Modules /

A library of custom-made iraf-related routines. This will, of course, require you install pyaf. You also need and

Python Source icon — Python Source, 34 KB (35247 bytes)

File contents

#!/usr/bin/env python

# A collection of tasks I find useful for reducing echelle spectra.  These
# can all be called within pyraf after an 'from myiraf import *'.  

from pyraf import iraf
import string
import re
import math
import os
import numarray as num
from ReadSex import readsex
import stats
import pygplot

# a file patter, omitting the trailing [#]...
file_pat = re.compile(r'(^.*?)\[.*')

def display_triplet(root):
   # Display the original, template and difference images in 3 frames
   SN = root+"SN.fits"
   diff = root+"diff.fits"
   temp = root+"temp.fits"
   iraf.display(SN, 1)

def get_image_size(images):
   result = iraf.imheader(images=images, longheader=0, Stdout=1)
   if result[0] == '':
      return None
   array = {}
   for item in result:
      res ='(.*)\[([0-9]+),([0-9]+)\]', item)
      array[] = [int(,int(]

   if len(array.keys()) == 1:
      return array[array.keys()[0]]
      return array

def get_mask(image):
   nmasks = get_header(image, 'NMASKS', float)
   im_masks = []
   if nmasks is not None:
      for i in range(nmasks):
               get_header(image, "MASK%d" % i,str))))
      nmasks = 0
   return (im_masks)

def sex(image, output, thresh=2.0, fwhm=4, gain=None, zmag=None,
        sexdir='/Users/burns/CSP/template_subtraction/sex', scale=0.125,
   '''Construct a sextractor command and run it.'''
   if sexdir[-1] != '/':  sexdir += '/'
   com = ["sex", image, "-c "+sexdir+"",
          "-PARAMETERS_NAME "+sexdir+"default.param",
          "-DETECT_MINAREA 5", "-DETECT_THRESH " + str(thresh),
          "-ANALYSIS_THRESH " + str(thresh), "-PIXEL_SCALE %f" % (scale),
          "-SEEING_FWHM "+str(fwhm), "-CATALOG_NAME "+output,
          "-FILTER_NAME "+sexdir+"gauss_3.0_5x5.conv",
          "-STARNNW_NAME "+sexdir+"default.nnw",
          "-SATUR_LEVEL "+str(30000), "-VERBOSE_TYPE QUIET"]
   if gain is not None:
      com += ["-GAIN",str(gain)]
   if zmag is not None:
      com += ["-MAG_ZEROPOINT", str(zmag)]
   if sigma_map is not None:
      com += ["-WEIGHT_IMAGE",sigma_map,"-WEIGHT_TYPE MAP_WEIGHT"]
   com = string.join(com)
   print com
   res = os.system(com)
   return res

def rectify_image(image, reference, output, refout=None, fitgeometry='general', 
                   xxorder=3, yyorder=3, xyorder=3, yxorder=3, clobber=1,
                   objects=None, interactive=1, maxiter=5, reject=3, xrange=None,
                   yrange=None, thresh=2.0, scale=0.125, fwhm=4, image_sigma=None,
                   reference_sigma=None, debug=0, aoffset=0):
   '''This task will take an image and a reference image and apply geomap/
   geotran to aligned them.  image is registered and output to output.
   You can specify the fit geometry (shift, xyscale, rotate, rscale,
   rxyscale, general).  You can also choose the geenral function
   (legendre, chebyshev, polynomial).  You can specfy the order of all
   the terms (xxorder, yyorder, xyorder, yxorder).  If you choose
   clobber=1 (the default), new images overwrite older ones.  Lastly, if
   you want to use a pre-existing list of objects, specify them as the
   objects keyword.'''
   # First, if we already have objects to use, save time by not finding them
   #  again!

   if objects is None:
      sex(image, output='', thresh=thresh, scale=scale, fwhm=fwhm,
      sex(reference, output='', thresh=thresh, scale=scale,
            fwhm=fwhm, sigma_map=reference_sigma)

      im_masks1 = get_mask(image)
      im_masks2 = get_mask(reference)
      if aoffset == 0:
         # Check the ROTANG header field to see if we need to give a rotation
         # offset to the object matcher.
         rotang1 = get_header(reference, "ROTANG", float)
         rotang2 = get_header(image, "ROTANG", float)
         if rotang1 is not None and rotang2 is not None:
            if abs(rotang1-rotang2) > 0.1:
               aoffset = rotang1-rotang2
      # Run the matchum routine to get corresponding objects:
      (x2,y2,x1,y1,o1,o2) = matchum('', '', 
            im_masks1=im_masks1, im_masks2=im_masks2, xrange=xrange,
            yrange=yrange, debug=debug, aoffset=aoffset)

      f = open('geomap.objects', 'w')
      for i in range(len(x1)):
         f.write('%.2f %.2f %.2f %.2f\n' % (x1[i],y1[i],x2[i],y2[i]))
      objects = "geomap.objects"
      data = pygplot.columns(objects)
      x1 = data[0]
      y1 = data[1]
      x2 = data[2]
      y2 = data[3]

   xshift = int(stats.bwt(x2-x1)[0])
   yshift = int(stats.bwt(y2-y1)[0])

   # We now have some selected objects to use with geomap.  Let's figure out
   #  the area of validity for the data using ths shifts computed above.
   (r_xmax,r_ymax) = get_image_size(reference)
   if xshift < 0:
      xmin = -xshift
      xmax = r_xmax
      xmin = 1
      xmax = r_xmax - xshift
   if yshift < 0:
      ymin = -yshift
      ymax = r_ymax
      ymin = 1
      ymax = r_ymax - yshift
   #xmin = min(x1);  xmax = max(x1)
   #ymin = min(y1);  ymax = max(y1)
   print xshift,yshift,xmin,xmax,ymin,ymax
   if os.path.isfile('geomap.db'):  os.unlink('geomap.db')

   # We may not have enough points to do a general fit, so we do them in order of
   #   complexity.  Hopefully, we have at least ONE point (for the shift)
   fitgeometries = [fitgeometry, 'rxyscale', 'rotate', 'shift']

   success = 0
   for fitgeo in fitgeometries:
      print "using fitgeometry = ", fitgeo
      res = iraf.geomap(input=objects,database='geomap.db', xmin=1,
               ymin=1, xmax=r_xmax, ymax=r_ymax, fitgeometry=fitgeo,
               function=function, xxorder=xxorder, yyorder=yyorder, 
               xyorder=xyorder, yxorder=yxorder, results='', 
               transforms=objects, interactive=interactive, 
               maxiter=maxiter, reject=reject, Stdout=1)
      # assume all went well
      success = 1
      for line in res:
         print line
         if'Too few points',line):
            success = 0
      if success:

   if clobber and os.path.isfile(output):  iraf.imdel(output)
   iraf.geotran(input=image, output='temp.rectify', database='geomap.db', 
         transforms=objects, geometry='geometric', interpolant="linear")
   section = "[%d:%d,%d:%d]" % (xmin, xmax, ymin, ymax)
   print 'Valid section of data:', section

def mygeotran(input, output, database, transforms, geometry='geometric', 

   # First, we get the shifts from the database
   f = open(database, 'r')
   for line in f.readlines():
      if'xshift', line):
         xshift = float(string.split(line)[1])
      elif'yshift', line):
         yshift = float(string.split(line)[1])
   f = open('offsets.dat', 'w')
   print >>f, 0,0
   print >>f, xshift+1,yshift+1

   # Now we make a blank holder for the mapped image
   iraf.imcombine(input+','+input, 'placeholder.fits', offsets='offsets.dat')
   iraf.imarith('placeholder.fits', '*', 0.0, 'placeholder.fits')

   xsize,ysize = get_image_size(input)
   # next, copy the input into this place holder
   iraf.imcopy(input+'[*,*]', 'placeholder.fits[1:%d,1:%d]' % (xsize,ysize))

   # now run geogran on this padded image
   iraf.geotran(input='placeholder.fits', output=output, database=database,
         transforms=transforms, geometry=geometry, interpolant=interpolant)
#   nuke('placeholder.fits')

def combine_images(images, output, reference=None, method='average', zscale=1,
      fitgeometry='general', function='polynomial', xxorder=3, yyorder=3, 
      xyorder=3, yxorder=3, objects=None, trim=0, interactive=1, maxiter=5,
      reject=3, sigma=None, xrange=None, yrange=None, rectify=1, thresh=2.0, scale=0.125,
      fwhm=4.0, debug=0, aoffset=0, creject=None):
   '''This Routine will take a list of images and use rectify_image to rectify
   each with respect to referece.  It will then use imcombine to combine the
   images into one and output to "output".  Default method is 'average', but
   you can use any available to imcombine.  The rest of the arguments are sent
   to rectify_images.  If zscale=1, all the images are scaled to a common
   zmag (30).  Also, if a file with _sigma.fits is found for all input files,
   we combine them too.  If you specify a sigma, that file will hold the
   standard deviations from the files.'''
   names = get_files(images)
   if reference is None:
      reference = names[0]
      names = names[1:]

   pclip = -0.5
   lsigma = 3.
   hsigma = 3.
   nkeep = -2
   if creject:
      creject = "pclip"
      creject = "none"

   # Check to see if we have _sigma files for each input file
   if os.path.isfile(reference.replace('.fits','_sigma.fits')):
      do_sigmas = 1
      do_sigmas = 0

   sigmas = []
   for name in names:
      if os.path.isfile(name.replace('.fits','_sigma.fits')):
         do_sigmas = 0

   # Put the reference image first, to ensure that all positional header keywords are
   #  valid.
   if do_sigmas:
      ref_sig = reference.replace('.fits','_sigma.fits')
   update = 0
   if zscale:
      zmag = get_header(reference, "ZMAG", float)
      if zmag is not None and zmag != 26:
         update = 1
         zmagfac = num.power(10, (zmag-30)/2.5)
         if zmagfac != 1:
             print 'zmagfac = ',zmagfac
             iraf.imarith(reference, '/', zmagfac, reference)
             iraf.hedit(reference, "ZMAG", 30.0, add=1, verify=0)
             if do_sigmas:
                iraf.imarith(ref_sig, '/', zmagfac, ref_sig)
                iraf.hedit(ref_sig, "ZMAG", 30.0, add=1, verify=0)

   print "Using %s as reference" % (reference)

   if rectify:
      iraf.imcopy(reference, reference.replace('.fits','_rec.fits'))
      if do_sigmas:
      temps = [reference.replace('.fits','_rec.fits')]
      if do_sigmas:  sig_temps = [reference.replace('.fits','_rec_sigma.fits')]
      temps = [reference]
      if do_sigmas:  sig_temps = [ref_sig]
   i = 0
   for name in names:
      print name
      if rectify:
         temp = name.replace('.fits','_rec.fits')
         if do_sigmas:
            (x0,x1,y0,y1) = rectify_image(name, reference, output=temp, 
                 function=function, xxorder=xxorder, yyorder=yyorder,
                 xyorder=xyorder, yxorder=yxorder, objects=objects,
                 interactive=interactive, maxiter=maxiter, reject=reject, 
                 yrange=yrange, thresh=thresh, scale=scale, fwhm=fwhm,
                 image_sigma=sigmas[i], reference_sigma=ref_sig, debug=debug,
            (x0,x1,y0,y1) = rectify_image(name, reference, output=temp, 
                 function=function, xxorder=xxorder, yyorder=yyorder,
                 xyorder=xyorder, yxorder=yxorder, objects=objects,
                 interactive=interactive, maxiter=maxiter, reject=reject, 
                 yrange=yrange, thresh=thresh, scale=scale, fwhm=fwhm, debug=debug,
         temp = name
      bpm = get_header(name, 'BPM', str)
      if bpm and rectify:
         bpm_fits = bpm.replace('.pl','.fits')
         temp_bpm = bpm.replace('.pl','_rec.fits')
         temp_bpm_pl = bpm.replace('.pl','')
         nuke(bpm_fits);  nuke(temp_bpm);  nuke(temp_bpm_pl)
         iraf.imcopy(bpm, bpm_fits)
         iraf.imarith(bpm_fits, "*", 10.0, bpm_fits)
         if objects is None:  sigmaobjects = 'geomap.objects'
         iraf.geotran(input=bpm_fits, output=temp_bpm, database='geomap.db', 
            transforms=sigmaobjects, geometry='geometric', 
         iraf.imreplace(temp_bpm, lower=0.02, upper="INDEF", value=1.)
         iraf.imcopy(temp_bpm, temp_bpm_pl)
         iraf.hedit(temp, 'BPM', temp_bpm_pl, update=1, verify=0)
      if do_sigmas:
         if rectify:
            temp_sig = sigmas[i].replace('_sigma.fits','_rec_sigma.fits')
            if objects is None:  
               sigmaobjects = 'geomap.objects'
               sigmaobjects = objects

            iraf.geotran(input=sigmas[i], output=temp_sig, database='geomap.db', 
               transforms=sigmaobjects, geometry='geometric', 
            temp_sig = sigmas[i]
      if rectify:
         if i == 0:
            xmin = x0;  ymin = y0;   xmax = x1;  ymax = y1
            xmin = max(xmin,x0);  ymin = max(ymin,y0)
            xmax = min(xmax,x1);  ymax = min(ymax,y1)
      if zscale:
         zmag = get_header(name, "ZMAG", float)
         if zmag is not None and zmag != 26:
            zmagfac = num.power(10, (zmag-30)/2.5)
            if zmagfac != 1:
               print 'zmagfac = ',zmagfac
               iraf.imarith(temp, '/', zmagfac, temp)
               iraf.hedit(temp, 'ZMAG', 30.0, add=1, verify=0)
               if do_sigmas:
                  iraf.imarith(temp_sig, '/', zmagfac, temp_sig)
                  iraf.hedit(temp_sig, 'ZMAG', 30.0, add=1, verify=0)

      if do_sigmas:  sig_temps.append(temp_sig)
      i = i + 1
   Nimages = len(temps)
   temps = string.join(temps, ',')
   print "Combining ", temps
   if sigma is not None:
      iraf.imcombine(temps, output, combine=method, sigma=sigma, 
            masktyp='badvalue', maskval=1., reject=creject,
            lsigma=lsigma, hsigma=hsigma, nkeep=nkeep)
      iraf.imcombine(temps, output, combine=method, masktyp='badvalue', 
            maskval=1., reject=creject, lsigma=lsigma, hsigma=hsigma, nkeep=nkeep)

   if do_sigmas:
      Ns = []
      for this in sig_temps:
         N = this.replace('.fits','_N.fits')
         iraf.imcopy(this, N)
         iraf.imreplace(N, lower=0.01, upper="INDEF", value=1)
      sig_temps = string.join(sig_temps, ',')
      Ns = string.join(Ns,',')
      iraf.imfunction(sig_temps, sig_temps, 'square')
      file = output.replace('.fits','_sigma.fits')
      iraf.imcombine(sig_temps, file, combine='sum')
      if method == 'average':
         iraf.imcombine(Ns, 'N.fits', combine='sum')
         iraf.imarith(file, '/', 'N.fits', file)
      iraf.imfunction(file, file, 'sqrt')
      iraf.imfunction(sig_temps, sig_temps, 'sqrt')
   if update:
      iraf.hedit(output, "ZMAG", 30.0, verify=0, add=1)

   # Now, let's clean up the gain and rdnoise headers, as they don't seem to
   #  be handled in the PANIC pipeline.  NOTE:  we really need a noise map, but
   #  for now, we'll deal with just global values.
   gain = get_header(output, "gain", float)
   rdnoise = get_header(output, "rdnoise", float)
   if gain is None:
      # have to compute it from scratch
      egain = get_header(output, "egain", float)
      enoise = get_header(output, "enoise", float)
      nloop = get_header(output, "nloops", int)
      i = 1
      key = ""
      # This seems to be the only safe way to compute this.  Can't trust
      #   NDITHERS
      while key is not None:
         key = get_header(output, "imcmb%03d" % (i), str)
         i = i + 1
      ndither = i - 1
      print "N, ndither, nloop, egain, enoise = ", Nimages, ndither, nloop, egain, enoise
      gain = egain*Nimages*nloop*ndither
      rdnoise = enoise*num.sqrt(Nimages*nloop*ndither)
      gain = gain*Nimages
      rdnoise = rdnoise*num.sqrt(Nimages)
   iraf.hedit(output, "gain", gain, verify=0, add=1)
   iraf.hedit(output, "rdnoise", rdnoise, verify=0, add=1)

   # If requested, trim to the common regions of the combined images
   if rectify and trim and Nimages > 1:
      iraf.imcopy(output+"[%d:%d,%d:%d]" % (xmin,xmax,ymin,ymax), 

def matchum(file1, file2, tol=10, perr=4, aerr=1.0, nmax = 40, 
      im_masks1=[], im_masks2=[], debug=0, domags=0, xrange=None,
      yrange=None, sigma=4, aoffset=0):
   '''Take the output of two sextractor runs and match up the objects with
   each other (find out which objects in the first file match up with
   objects in the second file.  The routine considers a 'match' to be any 
   two objects that are closer than tol pixels (after applying the shift).  
   Returns a 6-tuple:  (x1,y1,x2,y2,o1,o2).  o1 and o2 are the ojbects
   numbers such that o1[i] in file 1 corresponds to o2[i] in file 2.'''
   NA = num.NewAxis

   sexdata1 = readsex(file1)
   sexdata2 = readsex(file2)

   # Use the readsex data to get arrays of the (x,y) positions
   x1 = num.asarray(sexdata1[0]['X_IMAGE'])
   y1 = num.asarray(sexdata1[0]['Y_IMAGE'])
   x2 = num.asarray(sexdata2[0]['X_IMAGE'])
   y2 = num.asarray(sexdata2[0]['Y_IMAGE'])
   m1 = num.asarray(sexdata1[0]['MAG_BEST'])
   m2 = num.asarray(sexdata2[0]['MAG_BEST'])
   o1 = num.asarray(sexdata1[0]['NUMBER'])
   o2 = num.asarray(sexdata2[0]['NUMBER'])
   f1 = num.asarray(sexdata1[0]['FLAGS'])
   f2 = num.asarray(sexdata2[0]['FLAGS'])

   # First, make a cut on the flags:
   gids = num.where(f1 < 4)
   x1 = x1[gids];  y1 = y1[gids];  m1 = m1[gids]; o1 = o1[gids]
   gids = num.where(f2 < 4)
   x2 = x2[gids];  y2 = y2[gids];  m2 = m2[gids]; o2 = o2[gids]

   # next, if there is a range to use:
   if xrange is not None and yrange is not None:
      cond = num.greater(x1, xrange[0])*num.less(x1,xrange[1])*\
            num.greater(y1, yrange[0])*num.less(y1,yrange[1])
      gids = num.where(cond)
      x1 = x1[gids];  y1 = y1[gids];  m1 = m1[gids]; o1 = o1[gids]
      cond = num.greater(x2, xrange[0])*num.less(x2,xrange[1])*\
            num.greater(y2, yrange[0])*num.less(y2,yrange[1])
      gids = num.where(cond)
      x2 = x2[gids];  y2 = y2[gids];  m2 = m2[gids]; o2 = o2[gids]

   # Use the user masks
   for m in im_masks1:
      print "applying mask (%d,%d,%d,%d)" % tuple(m)
      condx = num.less(x1, m[0]) + num.greater(x1, m[1])
      condy = num.less(y1, m[2]) + num.greater(y1, m[3])
      gids = num.where(condx + condy)
      x1 = x1[gids];  y1 = y1[gids];  m1 = m1[gids]; o1 = o1[gids]

   for m in im_masks2:
      print "applying mask (%d,%d,%d,%d)" % tuple(m)
      condx = num.less(x2, m[0]) + num.greater(x2, m[1])
      condy = num.less(y2, m[2]) + num.greater(y2, m[3])
      gids = num.where(condx + condy)
      x2 = x2[gids];  y2 = y2[gids];  m2 = m2[gids];  o2 = o2[gids]

   if nmax:
      if len(x1) > nmax:
         ids = num.argsort(m1)[0:nmax]
         x1 = x1[ids]
         y1 = y1[ids]
         m1 = m1[ids]
         o1 = o1[ids]
      if len(x2) > nmax:
         ids = num.argsort(m2)[0:nmax]
         x2 = x2[ids]
         y2 = y2[ids]
         m2 = m2[ids]
         o2 = o2[ids]
   if debug:
      print "objects in frame 1:"
      print o1
      print "objects in frame 2:"
      print o2
      mp = pygplot.MPlot(2,1,device='/XWIN')
      p = pygplot.Plot()
      [p.label(x1[i],y1[i],"%d" % o1[i]) for i in range(len(x1))]
      p = pygplot.Plot()
      [p.label(x2[i],y2[i],"%d" % o2[i]) for i in range(len(x2))]

   # Now, we make 2-D arrays of all the differences in x and y between each pair
   #  of objects.  e.g., dx1[n,m] is the delta-x between object n and m in file 1 and
   #  dy2[n,m] is the y-distance between object n and m in file 2.
   dx1 = x1[NA,:] - x1[:,NA];   dx2 = x2[NA,:] - x2[:,NA]
   dy1 = y1[NA,:] - y1[:,NA];   dy2 = y2[NA,:] - y2[:,NA]
   # Same, but with angles
   da1 = num.arctan2(dy1,dx1)*180/num.pi
   da2 = num.arctan2(dy2,dx2)*180/num.pi
   # Same, but with absolute distances
   ds1 = num.sqrt(num.power(dx1,2) + num.power(dy1,2))
   ds2 = num.sqrt(num.power(dx2,2) + num.power(dy2,2))

   # Here's the real magic:  this is a matrix of matrices (4-D).  Consider 4 objects:
   #  objects i and j in file 1 and objects m and n in file 2.  dx[i,j,m,n] is the
   #  difference between delta-xs for objects i,j in file 1 and m,n in file 2.  If object
   #  i corresponds to object m and object j corresponds to object n, this should be a small
   #  number, irregardless of an overall shift in coordinate systems between file 1 and 2.
   dx = dx1[::,::,NA,NA] - dx2[NA,NA,::,::]
   dy = dy1[::,::,NA,NA] - dy2[NA,NA,::,::]
   da = da1[::,::,NA,NA] - da2[NA,NA,::,::] + aoffset
   ds = ds1[::,::,NA,NA] - ds2[NA,NA,::,::]
   # pick out close pairs.
   #use = num.less(dy,perr)*num.less(dx,perr)*num.less(num.abs(da),aerr)
   use = num.less(ds,perr)*num.less(num.abs(da), aerr)
   use = use.astype(num.Int32)

   #use = num.less(num.abs(da),perr)
   suse = num.add.reduce(num.add.reduce(use,3),1)
   print suse[0]

   guse = num.greater(suse,suse.flat.max()/2)
   i = [j for j in range(x1.shape[0]) if num.sum(guse[j])]
   m = [num.argmax(guse[j]) for j in range(x1.shape[0]) if num.sum(guse[j])]
   xx0,yy0,oo0,mm0 = num.take([x1,y1,o1,m1],i,1)
   xx1,yy1,oo1,mm1 = num.take([x2,y2,o2,m2],m,1)
   if debug:
      mp = pygplot.MPlot(2,1,device='/XWIN')
      p = pygplot.Plot()
      [p.label(xx0[i],yy0[i],"%d" % oo0[i]) for i in range(len(xx0))]
      p = pygplot.Plot()
      [p.label(xx1[i],yy1[i],"%d" % oo1[i]) for i in range(len(xx1))]
   xshift,xscat = stats.bwt(xx0-xx1)
   xscat = max([1.0,xscat])
   yshift,yscat = stats.bwt(yy0-yy1)
   yscat = max([1.0,yscat])
   mshift,mscat = stats.bwt(mm0 - mm1)
   print "xscat = ", xscat
   print "yscat = ", yscat
   print "xshift = ", xshift
   print "yshift = ", yshift
   print "mshift = ", mshift
   print "mscat = ", mscat
   keep = num.less(num.abs(xx0-xx1-xshift),sigma*xscat)*\
   # This is a list of x,y,object# in each file.
   xx0,yy0,oo0,xx1,yy1,oo1 = num.compress( keep, [xx0,yy0,oo0,xx1,yy1,oo1], 1)

   if debug:
      print file1, oo0
      print file2, oo1
      mp = pygplot.MPlot(2,1, device='')
      p1 = pygplot.Plot()
      p1.point(xx0,yy0, symbol=25, color='red')
      for i in range(len(xx0)):
         p1.label(xx0[i],yy0[i], " %d" % oo0[i], color='red')
      p2 = pygplot.Plot()
      p2.point(xx1,yy1, symbol=25, color='green')
      for i in range(len(xx1)):
         p2.label(xx1[i],yy1[i], " %d" % oo1[i], color='green')

   if domags:
def rename_files(files, key, keypattern, oldpattern, newpattern, exact=0):
   '''Quick little function that lets you search a list of files for
      a particular key (e.g., OBJECT) and if it matches the keypattern
      (e.g., 'flat'), it applies imrename using the provided oldpattern
      and newpattern to do the renaming (e.g. 'a' and 'comp').  If you 
      want an exact match for the key pattern, specify exact=1'''
   list = get_header(files, key, str)
   if exact:  keypattern = r"\A" + keypattern + r"\Z"
   for name in list:
      if, list[name]):
         iraf.imrename(oldnames=name, newnames=re.sub(oldpattern,newpattern,
def nuke(fileparm):
   '''Delete a list of images/files, but only try it if they exist. USE WITH CAUTION.
   With FITS files, this is a bit over-cautious, but with imh files, you reallly need
   to use imdel to get rid of the .pix files properly.'''
   files = get_files(fileparm)
   if files is None: return
   for file in files:
      if os.path.isfile(file):
         if'(fits|imh)$', file):

def get_files(fileparm):
   '''In iraf cl, sometimes a pattern or a file list is given instead of a 
   proper name.  Python doesn't handle that very well, so here's a cludge to
   get it to work using imhead.'''

   result = iraf.imhead(images=fileparm, longheader=0, Stdout=1)
   if len(result) == 0:
      return None
   if result[0] == '' or result[0] == 'no images found':
      return None

   out = []
   for res in result:

   return out

def label_objects(images, xfield, yfield):
   '''Given a list of images, allow the user to pick an object in the
   display and have the x and y coordinates saved as header keywords
   xfield and yfield.'''
   files = get_files(images)
   print "Please use the 'a' key to select objects"
   for file in files:
      res = iraf.imexamine(file, Stdout=1)
      #res = iraf.rimcursor(file, Stdout=1)
      x,y = map(float,(string.split(res[2])[0:2]))

      iraf.hedit(images=file, fields=xfield, value=x, add=1, verify=0)
      iraf.hedit(images=file, fields=yfield, value=y, add=1, verify=0)

def get_header(images,field,type):
   '''Get a header keyword (field) from the images (images) and apply
      the given type conversion (type).  Returns an associated array
      with the image name as key, unless there's only one image, in which
      case, just the value of the key is returned.'''
   result = iraf.hedit(images=images, fields=field, value='.', Stdout=1)
   if len(result) == 0:  return None
   if result[0] == '':  return None
   array = {}
   for res in result:
      res = re.sub('"', '', res)
      value = type(string.split(res, '=')[1])
      name = string.split(res, ',')[0]
      array[name] = value
   if len(array.keys()) == 1:
      return array[array.keys()[0]]
      return array

def closest_time(images, reference, keyword='JD', printit=0):
   '''This function takes a list of images (in the iraf sense) and determines
   which one is closest in time to the reference (good to figure out 
   which comparison spectra to use.'''

   todo = get_files(reference)
   if todo is None:  
      raise IOError, "Could not access any images:  %s" % images

   to_return = {}
   for this_ref in todo:
      result = get_header(images, keyword, float)
      list = result.keys()
      ref = get_header(this_ref, keyword, float)
      ref = float(ref[this_ref])
      diff = abs(ref - result[list[0]])
      best = list[0]
      for obj in list[1:]:
         if abs(ref - result[obj]) < diff:
            diff = abs(ref - result[obj])
            best = obj
      to_return[this_ref] = best

   if printit:
      list = to_return.keys()
      for item in list:
         print item,': ',to_return[item]

   if len(to_return.keys()) == 1:
      return to_return[to_return.keys()[0]]
      return to_return

def sort_dict(dict, cmp=lambda x, y: x > y):
   '''A function that takes a dictionary and sorts the values using the 
      proviced cmp function.  It then returns the associated sorted keys.
   keys = dict.keys()
   indices = range(len(keys))
   for j in indices:
      for i in range(j):
         if cmp(dict[keys[i]], dict[keys[i+1]]): 
            (keys[i], keys[i+1]) = (keys[i+1], keys[i])
   return keys
def closest_time_pos(images, reference, time_key='JD', ra_key='RA', 
      dec_key='DEC', printit=0):
   '''This function takes a list of images (in the iraf sense) and determines
   which one is closest in time and position to each reference (good to 
   figure out which comparison spectra to use.  It returns a dictionary
   where the key is the reference image and the value is the image closest
   in time/position to it.  This can be fed directly into extractComps.'''

   # This is a bias multiplied to the coordinate difference in order to
   #  make it "worse" to be off target rather than off in time
   pos_bias = 100.0
   #  Get a list of all references we need to do.
   todo = get_files(reference)
   if todo is None:  
      raise IOError, "Could not access any images:  %s" % images

   # The the times, RA's and DEC's of all the comparison spectra:
   obs_times = get_header(images, time_key, iraf.real)
   obs_ras = get_header(images, ra_key, iraf.real)
   obs_decs = get_header(images, dec_key, iraf.real)

   # Just in case the reference files are included in the images list,
   #  get rid of them (otherwise, this is just the identity)
   for this_ref in todo:
      if this_ref in obs_times.keys():
         del obs_times[this_ref]
         del obs_ras[this_ref]
         del obs_decs[this_ref]


   # Now go through each file and find the closest in time and position.
   for this_ref in todo:
      this_time = get_header(this_ref, time_key, iraf.real)[this_ref]
      this_ra = get_header(this_ref, ra_key, iraf.real)[this_ref]
      this_dec = get_header(this_ref, dec_key, iraf.real)[this_ref]

      # Get a list of differences in observing times:
      deltas = {}
      for obj in obs_times:
         deltas[obj] = abs(this_time - obs_times[obj])

      # Get a list of differences in observing positions
      dists = {}
      for obj in obs_ras:
         deltas[obj] = deltas[obj] + pos_bias*math.sqrt((this_ra - \
               obs_ras[obj])**2 + ((this_dec - obs_decs[obj])/15.0)**2)

      # deltas is now a dictionary to which each objects is associated
      # the difference in position plus the difference in position (all
      # in units of hours).  We now sort on this value and the smallest
      # will be the comparison we want.
      sorted_objs = sort_dict(deltas)
      # Now find the closest position
      to_return[this_ref] = sorted_objs[0]
      if printit:
         obj = to_return[this_ref]
         print '%s: %s  delta_t=%.4f sec, delta_position=%.2f sec' % \
            (this_ref,obj, 3600.0*(this_time - obs_times[obj]),
             3600.0*math.sqrt((this_ra - obs_ras[obj])**2 + \
                  ((this_dec - obs_decs[obj])/15.0)**2))

   if len(to_return.keys()) == 1:
      return to_return[to_return.keys()[0]]
      return to_return

def doDarks(dark, readnoise, images):
   '''This function will take the provided name of the dark frame (dark),
      determine the dark signal (median of the frame) and compute
      the dark current expected for each file name in images.  This is then
      compared to the readnoise.  A list of all images in which the dark 
      current is expected to be greater than the readnoise is printed,
      separated by commas, so you can cut-n-paste into ccdproc's epar.'''

   if not iraf.imaccess(dark):
      print "Error:  can't open dark frame %s" % (dark)

   # First off, get the dark current and exposure time
   dc = float(iraf.imstat(images=dark, fields='mean', Stdout=1)[1])
   dt = get_header(dark, 'darktime', float)[dark]
   print 'dark:  count=%f   exptime=%f   dark current rate=%f' % (dc,dt,dc/dt)

   # Next, get the exposure times for each image
   res = get_header(images=images, field='exptime', type=float)

   # a list to be printed out at the end (good for cut-n-paste)
   names = []
   print "expected dark currents:"
   for name in res.keys():
      print name,"%.2f" % (res[name]*dc/dt)
      if res[name]*dc/dt > readnoise:
   print "expected dark current greater than read noise:"
   print string.join(names, ',')

def extractComps(assignments, dorefspec=1):
   '''This task takes a dictionary 'assignments' which has keywords equal
      to the objects spectrum and value equal to the comparison spectrum
      which should be used for the object spectrum.  You could use 
      closest_time_pos() to figure this out, for example.  The apertures
      of each comp are then extracted using apall and the object's spectrum
      as a reference.  Finally, the object's headers are updated using
      the REFSPEC1 keyword (unless dorefspec=0).'''
   iraf.noao();  iraf.imred();  iraf.echelle()
   # First things first, we save the old paramter list.

   # iraf.apall.unlearn()
   iraf.apall.format = 'echelle'

   # now, get the associated list of references and comps:
   stuff = assignments

   for ref in stuff.keys():
      print 'extracting %s using %s as reference' % (stuff[ref], ref)
      comp = stuff[ref]
      if iraf.imaccess(re.sub('\.fits','.ec.fits',comp)):
         print 'Extracted comparison exists, skipping...'

      # Now, since we've used them to extract the spectra, we can assign
      # them using refspec:
      # print "now assigning reference spectrum"
      if dorefspec:  iraf.hedit(images=ref, fields='REFSPEC1', 
                 value=re.sub('\.fits','.ec',comp), add=1, addonly=1,
                 verify=0, show=1)


def precess_coords(file, epoch=2000.0):
   '''This function takes a file, accesses its header keywords (RA,DEC and
      EPOCH), precesses the coordinates to the proviced epoch and returns
      the RA and DEC as a tuple of string representations.'''

   iraf.noao();  iraf.astutil()
   if not iraf.imaccess(file):
      print "Error:  can't open image frame %s" % (file)
      return None

   ra = get_header(file, 'RA', str)[file]
   dec = get_header(file, 'DEC', str)[file]
   in_epoch = get_header(file, 'EPOCH', str)[file]

   f = open('iraf.tempfile', 'w')
   f.write('%s %s %s %.1f\n' % (ra,dec,in_epoch,epoch))
   result = iraf.precess(input='iraf.tempfile', startyear=1950, endyear=2000, 
   return string.split(result[0])[0:2]

def closer_than(ra1,dec1,ra2,dec2, tol=5):
   '''Returns 1 if ra2,dec2 is closer than tol arc-sec from ra1,dec1 and
   0 otherwise.  ra?  is assumed to be in hours, dec? in degrees.'''
   ra1 = iraf.real(ra1)
   dec1 = iraf.real(dec1)
   ra2 = iraf.real(ra2)
   dec2 = iraf.real(dec2)

   dist = math.sqrt(((ra1 - ra2)*15.0)**2 + (dec1 - dec2)**2)*3600.0

   if dist < tol:
      return 1
      return 0