def solar_velocity_shift(imagelist, restwave):
    """Opens the images of a data cube and shifts the wavelength planes
    so they are shifted to the solar frame. This is accomplished using the
    IRAF task 'rvcorrect'.

    It does it in a really really stupid way, which is to temporarily redirect
    IRAF's output to a string, because IRAF doesn't actually save the output
    anywhere (that I know of), it only prints it to the terminal. Then this
    routine searches through that string for the necessary value. It's a bit
    backwards, but it works!

    Note that the boost here is a Lorentz boost, not a Galilean boost, though
    values for either will be similar (i.e. the relativistic formula is used)

    Inputs:
    imagelist -> List of strings, the paths to wavelength images to be
                     updated.
    restwave -> The rest wavelength of the line you're interested in.

    """

    c = 299792.458  # in km/s

    for i in range(len(imagelist)):
        sys.stdout = iraf_output = StringIO()
        image = FPImage(imagelist[i], update=True)
        iraf.rvcorrect(header="N",
                       input="N",
                       imupdate="N",
                       observatory="SAAO",
                       year=float(image.datestring.split("-")[0]),
                       month=float(image.datestring.split("-")[1]),
                       day=float(image.datestring.split("-")[2]),
                       ut=image.ut,
                       ra=image.ra,
                       dec=image.dec,
                       vobs=0,
                       mode="a")
        image.solarvel = float(iraf_output.getvalue().split()[-6])
        iraf_output.close()
        beta_earth = (((image.wave/restwave)**2-1) /
                      ((image.wave/restwave)**2+1))
        beta_shift = image.solarvel/c
        beta_helio = (beta_earth+beta_shift)/(1+beta_earth*beta_shift)
        image.wave = restwave*(1+beta_helio)/np.sqrt(1-beta_helio**2)
        image.wave[np.isnan(image.wave)] = 0
        image.close()

    sys.stdout = sys.__stdout__

    return
def make_final_image(input_image, output_image,
                     desired_fwhm, clobber=False):
    """This routine makes the 'final' images for a data cube. At least the
    paths to the input image, output image, and output wavelength image are
    necessary for this. Beyond that, the user may also have the routine create
    uncertainty images as well.

    Images are convolved to the resolution 'desired_fwhm'. If the current fwhm
    is already higher than that, the routine will throw an error.

    A number of fits header keywords are necessary for this program to function
    properly. Any of these missing will throw an error.

    The output images are intensity-weighted, i.e. the wavelength image will be
    created such that the wavelengths at each pixel are the 'most likely'
    wavelength for the intensity at that pixel, etc.

    Inputs:
    input_image -> Path to the input image.
    output_image -> Path to the output image.
    desired_fwhm -> Desired FWHM for the resultant image to have.

    Optional Inputs:
    clobber -> Overwrite output images if they already exist. Default is False.

    """

    print "Making final data cube images for image "+input_image

    # Open the image
    image = FPImage(input_image)

    # Measure the sky background level in the input image
    skyavg, _truesig, skysig = image.skybackground()

    # Get various header keywords, crash if necessary
    intygrid = image.inty
    fwhm = image.fwhm
    wave0 = image.wave0
    calf = image.calf
    xcen = image.xcen
    ycen = image.ycen
    if fwhm is None:
        exit("Error! FWHM not measured for image "+input_image+".")
    if wave0 is None or calf is None:
        exit("Error! Wavelength solution does " +
             "not exist for image "+input_image+".")
    if xcen is None or ycen is None:
        exit("Error! Center values not measured " +
             "image "+input_image+".")
    if fwhm > desired_fwhm:
        exit("Error! Desired FWHM too low for image " +
             input_image+".")

    # Calculate the necessary FWHM for convolution
    fwhm_conv = np.sqrt(desired_fwhm**2-fwhm**2)
    # Magic number converts sigma to fwhm
    sig = fwhm_conv/2.3548
    # Extremely conservative "safe" kernel size
    ksize = np.ceil(4*sig)
    # Generate the gaussian kernel, shifted to shift the image
    kxgrid, kygrid = np.meshgrid(np.linspace(-ksize, ksize, 2*ksize+1),
                                 np.linspace(-ksize, ksize, 2*ksize+1))
    xshift, yshift = image.xshift, image.yshift
    rad2grid = (kxgrid + xshift)**2 + (kygrid + yshift)**2
    kern = np.exp(-rad2grid/(2*sig**2))

    # Normalize the kernel
    kern = kern/np.sum(kern)

    # Extract relevant arrays
    vargrid = image.vari
    badpixgrid = image.badp

    # Convolve the variances appropriately
    new_vargrid = convolve_variance(vargrid, badpixgrid, kern)

    # Add the sky background uncertainty to the uncertainty grid
    vargrid = vargrid+skysig**2

    # Create and convolve the wavelength array
    if image.wave is None:
        rgrid = image.rarray(xcen, ycen)
        wavegrid = wave0 / np.sqrt(1+rgrid**2/calf**2)
    else:
        wavegrid = image.wave
    new_wavegrid = convolve_wave(wavegrid, intygrid, badpixgrid, kern)

    # Convolve the intensity image
    new_intygrid = convolve_inty(intygrid, badpixgrid, kern)
    new_intygrid[new_intygrid == 0] = intygrid[new_intygrid == 0]
    new_intygrid[np.isnan(new_intygrid)] = intygrid[np.isnan(new_intygrid)]
    image.fwhm = desired_fwhm  # Update header FWHM keyword

    # Subtract the sky background from the image
    new_intygrid[new_intygrid != 0] -= skyavg

    # Create a new fits extension for the wavelength array
    if image.wave is None:
        waveheader = image.openimage[2].header.copy()
        waveheader['EXTVER'] = 4
        wavehdu = ImageHDU(data=new_wavegrid,
                           header=waveheader,
                           name="WAVE")
        image.openimage[1].header.set('WAVEEXT', 4,
                                      comment='Extension for Wavelength Frame')
        image.openimage.append(wavehdu)

    # Put all of the convolved images in the right extensions
    image.inty = new_intygrid
    image.vari = new_vargrid
    image.wave = new_wavegrid

    # Adjust header keywords (even though they're kinda irrelevant now)
    image.xcen += image.xshift
    image.ycen += image.yshift
    image.axcen += image.xshift
    image.aycen += image.yshift

    # Write the output file
    image.writeto(output_image, clobber=clobber)

    # Close images
    image.close()

    return