Exemple #1
0
def bin(datafile, errfile, SNR, outputfile, waverange=None, exclude=[], 
        logfile=None, logfits=None):
    """Bin GradPak fibers along a row until the desired SNR is achieved.

    The way fibers are grouped together is somewhat simplistic, but the
    underlying mathematics is correct. 

    The first bin starts at the smallest non-sky, non-excluded fiber (usually
    fiber #3) and just keeps adding the next fiber until the desire SNR is
    reached. Fibers are not binned across rows, which ensures that each bin is
    made up only of fibers of one size, but also limits the amount of binning
    that can be done. The major limitation of this method is that the ends of
    rows can often be left with a single, low SNR fiber. For example, if a bin
    is constructed containing fibers 14-16 and fiber 17 (at the end of the
    row) is below the threshold it will not be added to the previous bin and
    instead will make up a single bin with low a low SNR. This should be easy
    to fix but in practice it doesn't cause that much of an issue so I just
    haven't done it.

    The spectrum for each bin is an average of all the contributing fibers,
    weighted by each individual's SNR^2. The corresponding error bin is a sum
    in quadrature, weighted in the same way.

    The history of each bin is recorded in the output FITS header using two keywords:

     **BINXXXF** The fibers that went into bin XXX

     **BINXXXP** The position (in arcsec) of the center of bin XXX. This is
       the *unweighted* average of the centers of the contributing fibers.

    Parameters
    ----------

    datafile : str
        The name of a multispec FITS file that you want to bin

    errfile : str
        The name of a multispec FITS file containing error vectors corresponding to the datafile

    SNR : float
        The minimum SNR in each bin

    outputfile : str 
        The base name of the binned spectral and error arrays. The results
        will be called OUTPUTFILE.ms.fits and OUTPUTFILE.me.fits.

    waverange : list (default: None)
        A 2 element list containing the minimum and maximum wavelengths
        to consider *for the SNR calculations*. Regardless of this parameter
        the final output will span the same wavelength range as the input
        files.
    
    exclude : list (default: [])
        A list containing fibers to exlcude from binning. Sky fibers are
        automatically excluded and fiber numbers start at 1.

    logfile : str
        Name of a log file that will contain, for each aperture, a
        list of the individual fibers and the total S/N.

    logfits : str
        Name of a FITS file that will contain a separate HDU for each
        aperture. Each HDU will contain an array with two dimensions
        that records the fiber numbers and associated weights for that
        aperture.
        
    Returns
    -------
    
    finalf : numpy.ndarray
        Array with shape NBINS x NWAVE containing the binned data

    finatle : numpy.ndarray
        Array with same shape as finalf containing the errors on the binned data

    fibdict : dict
        Dictionary of questionable usefulness that contains information about
        which fibers went into each bin. The keys are ROW_BIN and the entries
        are a list of the fibers that went into that bin (e.g., '1_2' for the
        second bin in the first row).

    """
    hdu = pyfits.open(datafile)[0]
    data = hdu.data
    err = pyfits.open(errfile)[0].data

    if waverange is not None:
        wave = (np.arange(data.shape[1]) + hdu.header['CRPIX1'] - 1)\
               *hdu.header['CDELT1'] + hdu.header['CRVAL1']
        waveidx = np.where((wave >= waverange[0]) & (wave <= waverange[1]))[0]
    else:
        waveidx = None

    data *= 1e17
    err *= 1e17

    y_values = np.array([c.center[1] for c in GPP.GradPak_patches()[:,1]])
    x_values = np.array([c.center[0] for c in GPP.GradPak_patches()[:,1]])
    fibnums = np.arange(109) + 1
    row_pos = np.unique(y_values)

    finalf = np.zeros(data.shape[1])
    finale = np.zeros(data.shape[1])

    fibdict = {}
    binnum = 1

    if logfile is not None:
        lf = open(logfile,'w')
        
    if logfits is not None:
        log_HDUs = [] #This will hold [fiber_list, weight_list] for each aperture
        
    for i in range(row_pos.size):

        if row_pos[i] > 80:
            continue

        idx = np.where(y_values == row_pos[i])[0]
        b = 0
        n = 0

        while fibnums[idx[n]] in exclude:
            print 'Skipping fiber {}'.format(fibnums[idx[n]])
            n += 1

        while n < len(idx):
            try:
                while fibnums[idx[n]] in exclude:
                    print 'Skipping fiber {}'.format(fibnums[idx[n]])
                    n += 1
            except IndexError: #The rest of the fibers in the row are excluded
                break

            fstack = data[idx[n]][None,:]
            estack = err[idx[n]][None,:]
            tmp = compute_SN(data[idx[n]], err[idx[n]], waveidx)
            snstack = np.array([[tmp]])
            fibers = [fibnums[idx[n]]]
            xpos = [x_values[idx[n]]]
            ypos = [y_values[idx[n]]]

            while tmp < SNR:
                n += 1
                print 'fibers: {}, SNR: {}'.format(fibers, tmp)
                if n > len(idx) - 1:
                    print "WARNING, SN threshold not met in row {}, bin {}".\
                        format(i,b)
                    break

                if fibnums[idx[n]] in exclude:
                    print 'Skipping fiber {}'.format(fibnums[idx[n]])
                    continue
                    
                fstack = np.vstack((fstack, data[idx[n]]))
                estack = np.vstack((estack, err[idx[n]]))
                snstack = np.vstack((snstack, compute_SN(data[idx[n]], 
                                                         err[idx[n]], 
                                                         waveidx)))
                tmpf, tmpe = create_bin(fstack, estack, snstack)
                tmp = compute_SN(tmpf, tmpe, waveidx)
                fibers.append(fibnums[idx[n]])
                xpos.append(x_values[idx[n]])
                ypos.append(y_values[idx[n]])

            binf, bine = create_bin(fstack, estack, snstack)
            binsn = compute_SN(binf, bine, waveidx)
            print 'binned aperture {}: {}, SNR: {}'.format(binnum,fibers, binsn)
            if logfile is not None:
                lf.write('binned aperture {}: {}, SNR: {}\n'.format(binnum,fibers, binsn))
            if logfits is not None:
                log_HDUs.append(pyfits.ImageHDU(np.vstack([fibers, snstack.T[0]])))

            bin_x_pos = np.mean(xpos)
            bin_y_pos = np.mean(ypos)
            fibstr = [str(i) for i in fibers]
            hdu.header.update('BIN{:03}F'.format(binnum),' '.join(fibstr))
            hdu.header.update('BIN{:03}P'.format(binnum),' '.\
                              join([str(bin_x_pos),str(bin_y_pos)]))

            finalf = np.vstack((finalf,binf))
            finale = np.vstack((finale,bine))
            fibdict['{}_{}'.format(i,b)] = fibers
            b += 1
            n += 1
            binnum += 1

    finalf = finalf[1:]/1e17
    finale = finale[1:]/1e17
    pyfits.PrimaryHDU(finalf, hdu.header).\
        writeto('{}.ms.fits'.format(outputfile),clobber=True)
    pyfits.PrimaryHDU(finale, hdu.header).\
        writeto('{}.me.fits'.format(outputfile),clobber=True)

    if logfits is not None:
        lP = pyfits.PrimaryHDU()
        lP.header.update('HDUDIM','APERTURE')
        lP.header.update('AXIS1','FIBERS')
        lP.header.update('AXIS2','0: FIBER NUMBER 1: WEIGHT')
        pyfits.HDUList([lP] + log_HDUs).writeto(logfits, clobber=True)
        
    return finalf, finale, fibdict
Exemple #2
0
def create_locations(binfile, galcenter=[35.637962,42.347629], 
                     ifucenter=[35.637962,42.347629], reffiber=105,
                     galpa=293.3, ifupa=295.787, kpc_scale=0.0485):
    """Given a binned data file, creat a list of bin locations, relative to a galactic center.

    The function is designed to give physical meaning to the location of each
    bin created by :func:`bin`. For each bin found in the FITS header it
    computes physical coordinates in both arcsec and kpc. This method was
    written with a 2D projection of cylindrical coordinates in mind (r,z), but
    the user is free to fully define the location and meaning of the
    coordinate system through keyword options.

    The output is a text file containig the location of each bin in the
    desired coordinate system.

    The current defaults are for NGC 891 and WIYN proposal 14B-0456. You
    should probably change them.

    Parameters
    ----------
    
    binfile : str
        The name of a FITS file produced by :func:`bin`
    
    galcenter : list, optional
        A list containing the [RA,DEC] coordinates of the center of the
        coordinate system. The units are decimal degrees.

    ifucenter : list, optional 
        A list containing the center of the fiber specified in
        **reffiber**. Units are decimal degrees. This is usually the
        coordinates of your GradPak pointing.

    reffiber : int, optional
        The *fiber* number whoes coordinates are given in **ifucenter**. This
        is generally the fiber you used to align GradPak during observations.

    galpa : float, optional
        The position angle of the desired coordinate system. This angle
        defines rotation of the horizontal axis relative to the sky.
    
    ifupa : float, optional 
        The position angle of GradPak. This is defined as an absolute PA,
        relative to North, *not* to the defined coordinate system. This is
        generally the PA you entered during GradPak observations.
    
    kpc_scale : float, optional
        kpc/arcsec of the object in question

    Returns
    -------

    None :
        The result is file with the suffix "locations.dat" that contains
        information about the location in the user defined coordinates of each
        aperture (bin). Coordinates are given in arcsec and kpc. Note that the
        reported "size" is the size of the fibers that went into each bin, not
        the size of the bin itself. This is admittedly confusing.

    """

    hdu = pyfits.open(binfile)[0]
    numaps = hdu.data.shape[0]
    binhead = hdu.header
    
    patches = GPP.get_binned_patches(binhead)
    refpatches = GPP.GradPak_patches()
    
    patches, refpatches = GPP.transform_patches(patches,refpatches=refpatches,
                                                pa=ifupa, center=ifucenter, 
                                                reffiber=reffiber)

    decrad = galcenter[1]*2*np.pi/360.
    parad = galpa*2*np.pi/360.
    
    f = open('{}_locations.dat'.format(binfile.split('.ms.fits')[0]),'w')
    f.write("""# Generated on {}
# Inpute file: {}
#
""".format(time.asctime(),binfile))
    f.write('# {:4}{:>10}{:>10}{:>10}{:>10}{:>10}\n#\n'.format('Apnum',
                                                              'size (")',
                                                              'r (")',
                                                              'z (")',
                                                              'r (kpc)',
                                                              'z (kpc)'))

    for i, p in enumerate(patches[:,1]):
    
        fibers = binhead['BIN{:03}F'.format(i+1)]
        radius = refpatches[int(fibers.split(' ')[0]) - 1][1].get_radius()

        ra_diff = 3600*(galcenter[0] - p.center[0])*np.cos(decrad)
        dec_diff = 3600*(galcenter[1] - p.center[1])

        r_diff = ra_diff*np.cos(parad) - dec_diff*np.sin(parad)
        z_diff = -1*(ra_diff*np.sin(parad) + dec_diff*np.cos(parad))

        print i+1, p.center, ra_diff, dec_diff, r_diff, z_diff

        f.write(str('{:7n}'+5*'{:10.3f}'+'\n').format(i,
                                                             radius,
                                                             r_diff,
                                                             z_diff,
                                                             r_diff*kpc_scale,
                                                             z_diff*kpc_scale))
    f.close()
    return