Example #1
0
def mask_regions(imagepath, regionpath):
    """Adds the ds9 regions from a specified region file to the bad pixel mask
    of an image.

    The region file should be of the kind output by ds9 (region->save regions)
    with the following modes selected:
    format: Anything other than 'XML' or 'X Y'
    coordinate system: 'image'

    Currently this code only supports the following region types:
    box, ellipse, circle

    Inputs:
    imagepath -> String, path to an image
    regionpath -> String, path to a region file

    """

    shapes, par = parse_region_file(regionpath)

    # Open the input image and run it through the various region masks
    im = FPImage(imagepath, update=True)
    for i in range(len(shapes)):
        if shapes[i] == "circle":
            im.badp[mask_circle(im.badp, par[i])] = 1
            im.badp = mask_circle(im.badp, par[i])
        elif shapes[i] == "box":
            im.badp[mask_box(im.badp, par[i])] = 1
        elif shapes[i] == "ellipse":
            im.badp[mask_ellipse(im.badp, par[i])] = 1

    # Close files
    im.close()

    return
Example #2
0
def flatten(fnlist, flatfile):
    """Flattens a series of images by dividing them by a flatfield image.
    This differs from traditional flatfielding only in that in maintains the
    RSS aperture mask.

    Inputs:
    fnlist -> A list of strings, each the path to an image to flatten
    flatfile -> The location of a master flat image

    """

    flatimage = fits.open(flatfile)
    for i in range(len(fnlist)):
        print("Flattening image " + str(i + 1) + " of " + str(len(fnlist)) + ": " + fnlist[i])
        image = FPImage(fnlist[i], update=True)
        image.flattog = "True"
        image.inty /= flatimage[0].data  # Flatten data
        image.vari /= flatimage[0].data ** 2  # Flatten variance
        image.badp[np.isnan(image.inty)] = 1  # Fix bad pixels:
        image.inty[np.isnan(image.inty)] = 0
        image.badp[np.isinf(image.inty)] = 1
        image.inty[np.isinf(image.inty)] = 0
        image.close()
    flatimage.close()

    return
Example #3
0
def aperture_mask(fnlist, axcen, aycen, arad):
    """Opens a series of images and masks pixels outside a circular aperture.
    The images are overwritten with the new masked image. The values of axcen,
    aycen, and arad are added to the FITS headers as keywords 'fpaxcen',
    'fpaycen', and 'fparad' respectively.

    Inputs:
    fnlist -> A list containing strings, each the path to a fits image.
    axcen -> The aperture x center
    aycen -> The aperture y center
    arad -> The aperture radius

    """

    for i in range(len(fnlist)):
        print ("Masking pixels outside aperture for image " +
               str(i+1)+" of "+str(len(fnlist))+": "+fnlist[i])
        image = FPImage(fnlist[i], update=True)
        rgrid = image.rarray(axcen, aycen)
        image.inty[rgrid > arad] = 0
        image.badp[rgrid > arad] = 1
        image.axcen = axcen
        image.aycen = aycen
        image.arad = arad
        image.close()
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
Example #5
0
def get_aperture(path):
    """Opens a fits image and has the user click on points near the image
    aperture to fit a circle to those points. Once at least three points
    on the aperture boundary have been clicked, begins displaying a best-fit
    circle to those points overlayed on the image.

    Inputs:
    path -> Path to the fits file to open

    Outputs:
    axcen -> The aperture x center
    aycen -> The aperture y center
    arad -> The aperture radius

    """

    image = FPImage(path)
    xs, ys = [], []
    axcen, aycen, arad = image.xsize / 2, image.ysize / 2, 0
    apguess = np.array([image.xsize / 2, image.ysize / 2, min(image.xsize, image.ysize) / 2], dtype="float64")
    while True:
        click = Click_Near_Aperture_Plot(image.inty, xs, ys, axcen, aycen, arad)
        if (
            click.xcoo is not None
            and click.xcoo > 26
            and click.ycoo > 26
            and click.xcoo < image.xsize - 26
            and click.ycoo < image.ysize - 26
        ):
            # If the click was in a not-stupid place
            zoomdata = image.inty[click.ycoo - 25 : click.ycoo + 25, click.xcoo - 25 : click.xcoo + 25]
            newclick = Zoom_Aperture_Plot(zoomdata)
            if not (newclick.xcoo is None):
                xs.append(newclick.xcoo + click.xcoo - 25)
                ys.append(newclick.ycoo + click.ycoo - 25)
                if len(xs) > 2:
                    # Fit a circle to the points
                    def fun(x):
                        return np.sum(((np.array(xs) - x[0]) ** 2 + (np.array(ys) - x[1]) ** 2 - x[2] ** 2) ** 2)

                    apparams = minimize(fun, apguess).x
                    axcen, aycen, arad = apparams[0], apparams[1], apparams[2]
        elif len(xs) > 2 and click.xcoo is None:
            break

    image.close()

    return axcen, aycen, arad
Example #6
0
def fit_wave_soln(fnlist, doprint=False):
    """Fits a wavelength solution to rings in a set of images. Appends this
    wavelength solution to the image headers as the keywords:
    'fpwave0' and 'fpcalf'

    Each object in fnlist must have a corresponding "median.fits" in its
    image directory, or this routine will not work.

    ARC ring images are fitted by adjusting the center, while the center is
    held fixed for night sky rings. A combination of fits to both sets of
    rings is used to determine a wavelength solution for the whole set of
    images.

    If the ARC rings disagree substantially with the night sky rings, it is
    recommended that users delete the ARC rings from the fit and use only the
    night sky rings.

    It is also known that the wavelength solution can sometimes be piecewise in
    time when a large jump in 'z' happens between two images; i.e. different
    wavelength solutions exist before and after the jump. The routine allows
    the user to make a piecewise solution for this reason, but this ability
    should be used sparingly.

    This routine contains one of the few hard-coded numbers in the pipeline,
    Fguess=5600. Currently F values are not written to the fits image headers,
    and this is a reasonable guess.

    Inputs:
    fnlist -> List of strings, each the path to a fits image. These images
    should all have been taken with the same order filter. If not, the routine
    will crash.

    """

    # This bit takes care of the 's' to save shortcut in matplotlib.
    oldsavekey = plt.rcParams["keymap.save"]
    plt.rcParams["keymap.save"] = ""

    # Open all of the images
    arclist = []
    objlist = []
    images = [FPImage(fn) for fn in fnlist]

    # Separate ARCs and Object images and median-subtract Object images
    isarclist = [image.object == "ARC" for image in images]
    for i in range(len(isarclist)):
        if isarclist[i]:
            arclist.append(images[i])
        else:
            if not isfile(join(split(fnlist[i])[0], "median.fits")):
                exit("Error! No 'median.fits' file found.")
            medimage = fits.open(join(split(fnlist[i])[0], "median.fits"))
            images[i].inty -= medimage[0].data
            images[i].inty -= np.median(images[i].inty[images[i].badp != 1])
            medimage.close()
            objlist.append(images[i])

    filts = [image.filter for image in images]
    arclibs = []
    nightlibs = []
    for i in range(len(fnlist)):
        if isarclist[i]:
            arclibs.append(get_libraries(filts[i])[0])
        else:
            nightlibs.append(get_libraries(filts[i])[1])
        if get_libraries(filts[i])[0] is None:
            exit("Error! Filter "+filts[i]+" not in the wavelength library!")

    # This next bit fits all of the rings that the user marks

    # Fit rings in the object images
    radlists = []
    for i in range(len(objlist)):
        radlists.append([])
    i = 0
    while True:
        xcen = objlist[i].xcen
        ycen = objlist[i].ycen
        axcen = objlist[i].axcen
        aycen = objlist[i].aycen
        arad = objlist[i].arad
        rgrid = objlist[i].rarray(axcen, aycen)
        # Create radius bins
        rbins = np.arange(arad-np.int(max(abs(axcen-xcen), abs(aycen-ycen))))+1
        intbins = np.empty_like(rbins)
        # Get the median intensity within each radius bin
        for j in range(len(rbins)):
            binmask = np.logical_and(rgrid > rbins[j]-1,
                                     rgrid < rbins[j])
            goodbinmask = np.logical_and(binmask,
                                         objlist[i].badp == 0)
            if np.sum(goodbinmask) != 0:
                intbins[j] = np.median(objlist[i].inty[goodbinmask])
            else:
                intbins[j] = 0
        # Shift/scale the radius and intensity for the purposes of plotting
        plotxcen = xcen-axcen+arad
        plotycen = ycen-aycen+arad
        plotrbins = rbins+plotxcen
        plotintbins = intbins*arad/np.percentile(np.abs(intbins), 98)+plotycen

        # Plot the data interactively
        ringplot = PlotRingProfile(objlist[i].inty[aycen-arad:aycen+arad,
                                                   axcen-arad:axcen+arad],
                                   plotrbins, plotintbins,
                                   plotxcen, plotycen,
                                   radlists[i],
                                   repr(i+1)+"/"+repr(len(objlist)))

        # Changing images and loop breakout conditions
        if ringplot.key == "d":
            i += 1
        if ringplot.key == "a":
            i += -1
        if i == -1 or i == len(objlist):
            while True:
                yn = raw_input("Finished marking sky rings? (y/n) ")
                if "n" in yn or "N" in yn:
                    if i == -1:
                        i = 0
                    if i == len(objlist):
                        i = len(objlist)-1
                    break
                elif "y" in yn or "Y" in yn:
                    break
        if i == -1 or i == len(objlist):
            break

        # Force-marking a ring
        if ringplot.key == "e" and ringplot.xcoo is not None:
            radlists[i].append(ringplot.xcoo-arad-(xcen-axcen))

        # Deleting a ring
        if (ringplot.key == "s" and ringplot.xcoo is not None and
                len(radlists[i]) > 0):
            distarray = np.abs((np.array(radlists[i]) -
                                np.sqrt((ringplot.xcoo-arad-(xcen-axcen))**2 +
                                        (ringplot.ycoo-arad-(ycen-aycen))**2)))
            radlists[i].pop(np.argmin(distarray))

        # Fitting a ring profile
        if ringplot.key == "w" and ringplot.xcoo is not None:
            lower_index = max(ringplot.xcoo-plotxcen-50, 0)
            upper_index = min(ringplot.xcoo-plotxcen+50, len(rbins))
            x = rbins[lower_index:upper_index]**2
            y = intbins[lower_index:upper_index]
            fit = GaussFit(x, y)
            fitplot = PlotRingFit(x, y, fit)
            if fitplot.key == "w":
                radlists[i].append(np.sqrt(fit[2]))

    zo = []
    to = []
    ro = []
    lib_o = []
    for i in range(len(objlist)):
        for j in range(len(radlists[i])):
            zo.append(objlist[i].z)
            to.append(objlist[i].jd)
            ro.append(radlists[i][j])
            lib_o.append(nightlibs[i])
            if doprint:
                print objlist[i].z, objlist[i].jd, radlists[i][j]

    # Fit rings in the ARC images if there are any
    xcen = objlist[0].xcen
    ycen = objlist[0].ycen
    radlists = []
    for i in range(len(arclist)):
        radlists.append([])
    i = 0
    while len(arclist) > 0:
        axcen = arclist[i].axcen
        aycen = arclist[i].aycen
        arad = arclist[i].arad
        rgrid = arclist[i].rarray(axcen, aycen)
        # Create radius bins
        rbins = np.arange(arad-np.int(max(abs(axcen-xcen), abs(aycen-ycen))))+1
        intbins = np.empty_like(rbins)
        # Get the median intensity in each radius bin
        for j in range(len(rbins)):
            binmask = np.logical_and(rgrid > rbins[j]-1,
                                     rgrid < rbins[j])
            goodbinmask = np.logical_and(binmask,
                                         arclist[i].badp == 0)
            intbins[j] = np.median(arclist[i].inty[goodbinmask])
        # Shift/scale the radius and intensity for the purposes of plotting
        plotxcen = xcen-axcen+arad
        plotycen = ycen-aycen+arad
        plotrbins = rbins+plotxcen
        plotintbins = intbins*arad/np.percentile(np.abs(intbins), 98)+plotycen

        # Plot the data interactively
        ringplot = PlotRingProfile(arclist[i].inty[aycen-arad:aycen+arad,
                                                   axcen-arad:axcen+arad],
                                   plotrbins, plotintbins,
                                   plotxcen, plotycen,
                                   radlists[i],
                                   repr(i+1)+"/"+repr(len(arclist)))

        # Changing images and loop breakout conditions
        if ringplot.key == "d":
            i += 1
        if ringplot.key == "a":
            i += -1
        if i == -1 or i == len(arclist):
            while True:
                yn = raw_input("Finished marking ARC rings? (y/n) ")
                if "n" in yn or "N" in yn:
                    if i == -1:
                        i = 0
                    if i == len(arclist):
                        i = len(arclist)-1
                    break
                elif "y" in yn or "Y" in yn:
                    break
        if i == -1 or i == len(arclist):
            break

        # Force-marking a ring
        if ringplot.key == "e" and ringplot.xcoo is not None:
            radlists[i].append(ringplot.xcoo-arad-(xcen-axcen))

        # Deleting a ring
        if (ringplot.key == "s" and ringplot.xcoo is not None and
                len(radlists[i]) > 0):
            distarray = np.abs((np.array(radlists[i]) -
                                np.sqrt((ringplot.xcoo-arad-(xcen-axcen))**2 +
                                        (ringplot.ycoo-arad-(ycen-aycen))**2)))
            radlists[i].pop(np.argmin(distarray))
        # Fitting a ring profile
        if ringplot.key == "w" and ringplot.xcoo is not None:
            lower_index = max(ringplot.xcoo-plotxcen-50, 0)
            upper_index = min(ringplot.xcoo-plotxcen+50, len(rbins))
            x = rbins[lower_index:upper_index]**2
            y = intbins[lower_index:upper_index]
            fit = GaussFit(x, y)
            fitplot = PlotRingFit(x, y, fit)
            if fitplot.key == "w":
                radlists[i].append(np.sqrt(fit[2]))

    za = []
    ta = []
    ra = []
    lib_a = []
    for i in range(len(arclist)):
        for j in range(len(radlists[i])):
            za.append(arclist[i].z)
            ta.append(arclist[i].jd)
            ra.append(radlists[i][j])
            lib_a.append(arclibs[i])
            if doprint:
                print arclist[i].z, arclist[i].jd, radlists[i][j]

    # Now we try to get a good guess at the wavelengths

    # Get a good guess at which wavelengths are which
    Bguess = objlist[0].b
    Fguess = objlist[0].f
    if Fguess is None:
        Fguess = 5600

    # Figure out A by matching rings to the wavelength libraries
    master_r = np.array(ro+ra)
    master_z = np.array(zo+za)
    wavematch = np.zeros_like(master_r)
    oldrms = 10000  # Really high initial RMS for comparisons
    master_lib = lib_o+lib_a
    for i in range(len(master_r)):
        lib = master_lib[i]
        for j in range(len(lib)):
            # Assume the i'th ring is the j'th line
            Aguess = (lib[j]*np.sqrt(1+master_r[i]**2/Fguess**2) -
                      Bguess*master_z[i])
            # What are all of the other rings, given this A?
            waveguess = ((Aguess+Bguess*master_z) /
                         np.sqrt(1+master_r**2/Fguess**2))
            for k in range(len(master_r)):
                wherematch = np.argmin(np.abs(master_lib[k]-waveguess[k]))
                wavematch[k] = master_lib[k][wherematch]
            rms = np.sqrt(np.average((waveguess-wavematch)**2))
            if rms < oldrms:
                # This is the new best solution. Keep it!
                oldrms = rms
                bestA = Aguess
                master_wave = wavematch.copy()

    # Make more master arrays for the plotting
    master_t = np.array(to+ta)
    t0 = np.min(master_t)
    master_t += -t0
    master_t *= 24*60  # Convert to minutes
    master_color = np.array(len(ro)*["blue"]+len(ra)*["red"])
    toggle = np.ones(len(master_r), dtype="bool")
    dotime = False
    time_dividers = []

    # Do the interactive plotting
    while True:
        rplot = master_r[toggle]
        zplot = master_z[toggle]
        tplot = master_t[toggle]
        colorplot = master_color[toggle]
        wplot = master_wave[toggle]
        fitplot = np.zeros(len(wplot))
        xs = np.zeros((3, len(rplot)))
        xs[0] = rplot
        xs[1] = zplot
        xs[2] = tplot
        fit = [0]*(len(time_dividers)+1)
        time_dividers = sorted(time_dividers)
        if len(time_dividers) > 1:
            print ("Warning: Too many time divisions is likely unphysical." +
                   "Be careful!")
        for i in range(len(time_dividers)+1):
            # Create a slice for all of the wavelengths before this time
            # divider but after the one before it
            if len(time_dividers) == 0:
                tslice = tplot == tplot
            elif i == 0:
                tslice = tplot < time_dividers[i]
            elif i == len(time_dividers):
                tslice = tplot > time_dividers[i-1]
            else:
                tslice = np.logical_and(tplot < time_dividers[i],
                                        tplot > time_dividers[i-1])
            # Case for fitting time dependence
            if dotime:
                fit[i] = curve_fit(fpfunc_for_curve_fit_with_t,
                                   xs[:, tslice], wplot[tslice],
                                   p0=(bestA, Bguess, 0, Fguess))[0]
                fitplot[tslice] = fpfunc_for_curve_fit_with_t(xs[:, tslice],
                                                              fit[i][0],
                                                              fit[i][1],
                                                              fit[i][2],
                                                              fit[i][3])
            # Case without time dependence
            else:
                fit[i] = curve_fit(fpfunc_for_curve_fit,
                                   xs[:, tslice], wplot[tslice],
                                   p0=(bestA, Bguess, Fguess))[0]
                fitplot[tslice] = fpfunc_for_curve_fit(xs[:, tslice],
                                                       fit[i][0],
                                                       fit[i][1],
                                                       fit[i][2])
        # Calculate residuals to the fit
        resid = wplot - fitplot

        # Interactively plot the residuals
        solnplot = WaveSolnPlot(rplot, zplot, tplot, wplot,
                                resid, colorplot, time_dividers)
        # Breakout case
        if solnplot.key == "a":
            while True:
                for i in range(len(time_dividers)+1):
                    if dotime:
                        solnstring = ("Solution "+repr(i+1) +
                                      ": A = "+str(fit[i][0]) +
                                      ", B = "+str(fit[i][1]) +
                                      ", E = "+str(fit[i][2]) +
                                      ", F = "+str(fit[i][3]))
                    else:
                        solnstring = ("Solution "+repr(i+1) +
                                      ": A = "+str(fit[i][0]) +
                                      ", B = "+str(fit[i][1]) +
                                      ", F = "+str(fit[i][2]))
                    print solnstring
                rms = np.sqrt(np.average(resid**2))
                print ("Residual rms="+str(rms) +
                       " for "+repr(len(time_dividers)+1) +
                       " independent "+repr(3+dotime) +
                       "-parameter fits to "+repr(len(rplot))+" rings.")
                yn = raw_input("Accept wavelength solution? (y/n) ")
                if "n" in yn or "N" in yn:
                    break
                elif "y" in yn or "Y" in yn:
                    solnplot.key = "QUIT"
                    break
        if solnplot.key == "QUIT":
            break

        # Restore all points case
        if solnplot.key == "r":
            toggle = np.ones(len(master_r), dtype="bool")

        # Delete nearest point case
        if solnplot.key == "d" and solnplot.axis is not None:
            # Figure out which plot was clicked in
            if solnplot.axis == 1:
                # Resid vs. z plot
                z_loc = solnplot.xcoo
                resid_loc = solnplot.ycoo
                dist2 = (((zplot-z_loc)/(np.max(zplot)-np.min(zplot)))**2 +
                         ((resid-resid_loc)/(np.max(resid)-np.min(resid)))**2)
            elif solnplot.axis == 2:
                # Resid vs. R plot
                r_loc = solnplot.xcoo
                resid_loc = solnplot.ycoo
                dist2 = (((rplot-r_loc)/(np.max(rplot)-np.min(rplot)))**2 +
                         ((resid-resid_loc)/(np.max(resid)-np.min(resid)))**2)
            elif solnplot.axis == 3:
                # Resit vs. T plot
                t_loc = solnplot.xcoo
                resid_loc = solnplot.ycoo
                dist2 = (((tplot-t_loc)/(np.max(tplot)-np.min(tplot)))**2 +
                         ((resid-resid_loc)/(np.max(resid)-np.min(resid)))**2)
            elif solnplot.axis == 4:
                # Resid vs. Wave plot
                wave_loc = solnplot.xcoo
                resid_loc = solnplot.ycoo
                dist2 = (((wplot-wave_loc)/(np.max(wplot)-np.min(wplot)))**2 +
                         ((resid-resid_loc)/(np.max(resid)-np.min(resid)))**2)
            # Get the radius and time of the worst ring
            r_mask = rplot[dist2 == np.min(dist2)][0]
            t_mask = tplot[dist2 == np.min(dist2)][0]
            toggle[np.logical_and(master_r == r_mask,
                                  master_t == t_mask)] = False

        # Toggle time fit case
        if solnplot.key == "t":
            dotime = not dotime

        # Add time break case
        if solnplot.key == "w":
            timeplot = TimePlot(tplot, resid, colorplot, time_dividers)
            if timeplot.xcoo is not None:
                time_dividers.append(timeplot.xcoo)

        # Remove time breaks case
        if solnplot.key == "q":
            time_dividers = []

    # Close all images
    for image in images:
        image.close()

    # For each image, write the central wavelength and F to the image header
    for i in range(len(fnlist)):
        image = FPImage(fnlist[i], update=True)
        image_t = (image.jd-t0)*24*60
        # Figure out which time division it's in
        div_index = np.where(np.array(time_dividers) > image_t)[0]
        if len(div_index > 0):
            div_index = div_index[0]
        else:
            div_index = len(time_dividers)
        image_fit = fit[div_index]
        if dotime:
            image_wave0 = (image_fit[0] +
                           image_fit[1]*image.z +
                           image_fit[2]*image_t)
            image_F = image_fit[3]
        else:
            image_wave0 = (image_fit[0] +
                           image_fit[1]*image.z)
            image_F = image_fit[2]
        image.wave0 = image_wave0
        image.calf = image_F
        image.calrms = rms
        image.calnring = repr(len(rplot))
        image.calnfits = repr(len(time_dividers)+1)
        image.calnpars = repr(3+dotime)
        image.close()

    # Restore the old keyword shortcut
    plt.rcParams["keymap.save"] = oldsavekey

    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
Example #8
0
def pipeline(rawdir="raw", mode="halpha"):
    """Runs successive steps of the saltfp data reduction, checking along the
    way to see if each step was successful. This is the main driver program of
    the SALT Fabry-Perot pipeline.

    Inputs:
    rawdir -> String, containing the path to the 'raw' directory. By
                  default, this is 'raw'
    mode -> Mode for velocity fitting. Currently the only option is H-Alpha
                line fitting.

    """

    # Set rest wave based on the mode called
    if mode == "halpha":
        rest_wave = 6562.81

    # Create product directory
    if isdir("product"):
        while True:
            yn = raw_input("Product directory already exists. " + "Recreate it? (y/n) ")
            if "n" in yn or "N" in yn:
                break
            elif "y" in yn or "Y" in yn:
                # Confirmation
                yn = raw_input("Are you sure? This takes a while. (y/n) ")
                if ("y" in yn or "Y" in yn) and not ("n" in yn or "N" in yn):
                    rmtree("product")
                    break

    if not isdir("product"):
        # Acquire the list of filenames from the raw directory
        fnlist = sorted(listdir(rawdir))
        for i in range(len(fnlist)):
            fnlist[i] = join(rawdir, fnlist[i])
        # Run the first two steps of imred on the first image
        iraf.pysalt(_doprint=0)
        iraf.saltred(_doprint=0)
        iraf.saltprepare(
            fnlist[0],
            "temp.fits",
            "",
            createvar=False,
            badpixelimage="",
            clobber=True,
            logfile="temp.log",
            verbose=True,
        )
        iraf.saltbias(
            "temp.fits",
            "temp.fits",
            "",
            subover=True,
            trim=True,
            subbias=False,
            masterbias="",
            median=False,
            function="polynomial",
            order=5,
            rej_lo=3.0,
            rej_hi=5.0,
            niter=10,
            plotover=False,
            turbo=False,
            clobber=True,
            logfile="temp.log",
            verbose=True,
        )
        # Create the bad pixel mask
        image = fits.open("temp.fits")
        for i in range(1, len(image)):
            mask = image[i].data != image[i].data
            image[i].data = 1 * mask
        image.writeto("badpixmask.fits", clobber="True")
        image.close()
        # Remove temporary files
        remove("temp.fits")
        remove("temp.log")
        # Run the raw images through the first few data reduction pipeline
        # steps
        imred(fnlist, "product", bpmfile="badpixmask.fits")
        # Delete the temporary bad pixel mask
        remove("badpixmask.fits")
        # Move these raw images into the product directory
        mkdir("product")
        fnlist = sorted(listdir("."))
        for i in range(len(fnlist)):
            if "mfxgbpP" in fnlist[i] and ".fits" in fnlist[i]:
                move(fnlist[i], join("product", fnlist[i]))
    # List of files in the product directory
    fnlist = sorted(listdir("product"))
    for i in range(len(fnlist)):
        fnlist[i] = join("product", fnlist[i])

    # Manual verification of fits images and headers
    firstimage = FPImage(fnlist[0])
    verify = firstimage.verifytog
    firstimage.close()
    if verify is None:
        while True:
            prompt = "Manually verify image contents? (Recommended) (y/n) "
            yn = raw_input(prompt)
            if "n" in yn or "N" in yn:
                print ("Skipping manual verification of image contents " + "(Not recommended)")
                break
            if "y" in yn or "Y" in yn:
                fnlist = verify_images(fnlist)
                break

    # Make separate lists of the different fits files
    (flatlist, list_of_objs, objlists, list_of_filts, filtlists) = separate_lists(fnlist)

    # Masking of pixels outside the aperture
    firstimage = FPImage(objlists[0][0])
    axcen = firstimage.axcen
    firstimage.close()
    if axcen is None:
        print "Masking pixels outside the RSS aperture..."
        axcen, aycen, arad = get_aperture(objlists[0][0])
        aperture_mask(fnlist, axcen, aycen, arad)
    else:
        print "Images have already been aperture-masked."

    # Masking bad pixels from external region file
    for objlist in objlists:
        for i in range(len(objlist)):
            if isfile(splitext(split(objlist[i])[1])[0] + ".reg"):
                print ("Adding regions from file " + splitext(split(objlist[i])[1])[0] + ".reg to the bad pixel mask.")
                mask_regions(objlist[i], splitext(split(objlist[i])[1])[0] + ".reg")

    # Measure stellar FWHMs
    firstimage = FPImage(objlists[0][0])
    fwhm = firstimage.fwhm
    firstimage.close()
    if fwhm is None:
        dofwhm = True
    else:
        while True:
            yn = raw_input("Seeing FWHM has already been measured. " + "Redo this? (y/n) ")
            if "n" in yn or "N" in yn:
                dofwhm = False
                break
            elif "y" in yn or "Y" in yn:
                dofwhm = True
                break
    if dofwhm:
        print "Measuring seeing FWHMs..."
        for objlist in objlists:
            measure_fwhm(objlist)

    # Find image centers using ghost pairs
    for i in range(len(objlists)):
        firstimage = FPImage(objlists[i][0])
        xcen = firstimage.xcen
        deghosted = firstimage.ghosttog
        firstimage.close()
        if deghosted is None:
            if xcen is None:
                ghosttog = True
            else:
                while True:
                    yn = raw_input(
                        "Optical centers already measured for " + "object " + list_of_objs[i] + ". Redo this? (y/n) "
                    )
                    if "n" in yn or "N" in yn:
                        ghosttog = False
                        break
                    elif "y" in yn or "Y" in yn:
                        ghosttog = True
                        break
            if ghosttog:
                print (
                    "Identifying optical centers for object "
                    + list_of_objs[i]
                    + ". This may take a while for crowded fields..."
                )
                find_ghost_centers(objlists[i])

    # Deghost images
    for i in range(len(objlists)):
        firstimage = FPImage(objlists[i][0])
        deghosted = firstimage.ghosttog
        firstimage.close()
        if deghosted is None:
            print "Deghosting images for object " + list_of_objs[i] + "..."
            for j in range(len(objlists[i])):
                deghost(objlists[i][j])
        else:
            print ("Images for object " + list_of_objs[i] + " have already been deghosted.")

    # Image Flattening
    firstimage = FPImage(objlists[0][0])
    flattog = firstimage.flattog
    firstimage.close()
    if flattog is None:
        print "Flattening images..."
        if len(flatlist) == 0:
            while True:
                print "Uh oh! No flatfield exposure found!"
                flatpath = raw_input("Enter path to external flat image: " + "(leave blank to skip flattening) ")
                if flatpath == "" or isfile(flatpath):
                    break
        else:
            combine_flat(flatlist, "flat.fits")
            flatpath = "flat.fits"
        if flatpath != "":
            notflatlist = []
            for objlist in objlists:
                notflatlist += objlist
            flatten(notflatlist, flatpath)
        else:
            print "Skipping image flattening. (Not recommended!)"
    else:
        print "Images have already been flattened."

    # Make separate directories for each object.
    # This is the first bit since 'singext' to create a new directory, because
    # this is the first point where it's really necessary to start treating the
    # images from different tracks very differently.
    for i in range(len(objlists)):
        if isdir(list_of_objs[i].replace(" ", "")):
            while True:
                yn = raw_input("A directory for object " + list_of_objs[i] + " already exists. Recreate? (y/n) ")
                if "n" in yn or "N" in yn:
                    do_copy = False
                    break
                elif "y" in yn or "Y" in yn:
                    do_copy = True
                    rmtree(list_of_objs[i].replace(" ", ""))
                    break
        else:
            do_copy = True
        if do_copy:
            mkdir(list_of_objs[i].replace(" ", ""))
            for j in range(len(objlists[i])):
                copyfile(objlists[i][j], join(list_of_objs[i].replace(" ", ""), split(objlists[i][j])[1]))
        for j in range(len(objlists[i])):
            objlists[i][j] = join(list_of_objs[i].replace(" ", ""), split(objlists[i][j])[1])
    # Update the filter lists
    for i in range(len(filtlists)):
        for j in range(len(filtlists[i])):
            for k in range(len(objlists)):
                for l in range(len(objlists[k])):
                    if split(filtlists[i][j])[1] == split(objlists[k][l])[1]:
                        filtlists[i][j] = objlists[k][l]

    # Image alignment and normalization
    for i in range(len(objlists)):
        firstimage = FPImage(objlists[i][0])
        aligned = firstimage.phottog
        firstimage.close()
        if aligned is None:
            print ("Aligning and normalizing images for object " + list_of_objs[i] + "...")
            align_norm(objlists[i])
        else:
            print ("Images for object " + list_of_objs[i] + " have already been aligned and normalized.")

    # Make a median image for each object
    for i in range(len(objlists)):
        if isfile(join(list_of_objs[i].replace(" ", ""), "median.fits")):
            while True:
                yn = raw_input("Median image for object " + list_of_objs[i] + " already exists. Replace it? (y/n) ")
                if "n" in yn or "N" in yn:
                    break
                elif "y" in yn or "Y" in yn:
                    make_median(objlists[i], join(list_of_objs[i].replace(" ", ""), "median.fits"))
                    break
        else:
            make_median(objlists[i], join(list_of_objs[i].replace(" ", ""), "median.fits"))

    # Wavelength calibrations
    all_rings_list = []
    for i in range(len(list_of_filts)):
        all_rings_list = all_rings_list + filtlists[i]
    firstimage = FPImage(all_rings_list[0])
    calf = firstimage.calf
    firstimage.close()
    if not (calf is None):
        while True:
            yn = raw_input("Wavelength solution already found. " + "Redo it? (y/n) ")
            if "n" in yn or "N" in yn:
                break
            elif "y" in yn or "Y" in yn:
                fit_wave_soln(all_rings_list)
                break
    else:
        fit_wave_soln(all_rings_list)

    # Sky ring removal
    for i in range(len(objlists)):
        for j in range(len(objlists[i])):
            # Check to see if sky rings have already been removed
            image = FPImage(objlists[i][j])
            deringed = image.ringtog
            image.close()
            if deringed is None:
                print "Subtracting sky rings for image " + objlists[i][j]
                sub_sky_rings([objlists[i][j]], [join(list_of_objs[i].replace(" ", ""), "median.fits")])
            else:
                print ("Sky ring subtraction already done for image " + objlists[i][j])

    # Creation of data cube and convolution to uniform PSF
    for i in range(len(objlists)):
        if isdir(list_of_objs[i].replace(" ", "") + "_cube"):
            while True:
                yn = raw_input("A data cube for object " + list_of_objs[i] + " already exists. Recreate? (y/n) ")
                if "n" in yn or "N" in yn:
                    do_create = False
                    break
                elif "y" in yn or "Y" in yn:
                    # Confirmation
                    yn = raw_input("Are you sure? This takes a while. (y/n) ")
                    if ("y" in yn or "Y" in yn) and not ("n" in yn or "N" in yn):
                        do_create = True
                        rmtree(list_of_objs[i].replace(" ", "") + "_cube")
                        break
        else:
            do_create = True
        if do_create:
            mkdir(list_of_objs[i].replace(" ", "") + "_cube")
            for j in range(len(objlists[i])):
                image = FPImage(objlists[i][j])
                fwhm = image.fwhm
                if j == 0:
                    largestfwhm = fwhm
                if fwhm > largestfwhm:
                    largestfwhm = fwhm
                image.close()
            while True:
                prompt = "Enter desired final fwhm or leave blank to use" + " default (" + str(largestfwhm) + " pix) "
                user_fwhm = raw_input(prompt)
                if user_fwhm == "":
                    user_fwhm = largestfwhm
                    break
                else:
                    try:
                        user_fwhm = float(user_fwhm)
                    except ValueError:
                        print "That wasn't a valid number..."
                    else:
                        if user_fwhm < largestfwhm:
                            print ("Final fwhm must exceed " + str(largestfwhm) + " pixels.")
                        else:
                            break
            desired_fwhm = user_fwhm * 1.01
            for j in range(len(objlists[i])):
                make_final_image(
                    objlists[i][j],
                    join(list_of_objs[i].replace(" ", "") + "_cube", split(objlists[i][j])[1]),
                    desired_fwhm,
                    clobber=True,
                )

    # Get final lists for the velocity map fitting for each object
    final_lists = []
    for i in range(len(list_of_objs)):
        final_lists.append([])
        for j in range(len(objlists[i])):
            final_lists[i].append(join(list_of_objs[i].replace(" ", "") + "_cube", split(objlists[i][j])[1]))

    # Shift to solar velocity frame
    for i in range(len(list_of_objs)):
        firstimage = FPImage(final_lists[i][0])
        velshift = firstimage.solarvel
        firstimage.close()
        if velshift is None:
            print ("Performing solar velocity shift for object " + list_of_objs[i] + "...")
            solar_velocity_shift(final_lists[i], rest_wave)
        else:
            print ("Solar velocity shift for object " + list_of_objs[i] + " already done.")

    if not do_velmap:
        sys.exit("Velocity map not made - Voigt-fitting software not found.")

    # Velocity map fitting
    for i in range(len(list_of_objs)):
        if isfile(join(list_of_objs[i].replace(" ", "") + "_cube", "velocity.fits")):
            while True:
                yn = raw_input("Velocity map already fitted for object " + list_of_objs[i] + ". Redo this? (y/n) ")
                if "n" in yn or "N" in yn:
                    domap = False
                    break
                elif "y" in yn or "Y" in yn:
                    # Confirmation
                    yn = raw_input("Are you sure? This takes a while. (y/n) ")
                    if ("y" in yn or "Y" in yn) and not ("n" in yn or "N" in yn):
                        domap = True
                        break
        else:
            domap = True
    if domap:
        print "Fitting velocity map for object " + list_of_objs[i] + "..."
        if mode == "halpha":
            fit_velmap_ha_n2_mode(final_lists[i], list_of_objs[i].replace(" ", "") + "_cube", clobber=True)

    # Clean velocity map
    for i in range(len(list_of_objs)):
        make_clean_map(list_of_objs[i].replace(" ", "") + "_cube", clobber=True)
Example #9
0
def separate_lists(fnlist):
    """Separates a list of images into separate lists based on their header
    keywords 'OBJECT' and 'FILTER'

    Inputs:
    fnlist -> A list containing strings, each the location of a fits image

    Outputs:
    flatlist -> List of paths to flatfield images
    list_of_objects -> List of distinct objects
    objectlists -> List of lists, each one all of the files for an object
    list_of_filters -> List of distinct filters
    filterlists -> List of lists, each one all the files for a filter

    """

    # Open each file and look for flats and new filters/objects
    list_of_objects = []
    list_of_filters = []
    flatlist = []
    for i in range(len(fnlist)):
        image = FPImage(fnlist[i])
        # Skip the rest if image was previously flagged as bad
        if image.goodtog is None:
            # Case for flats
            if image.object == "FLAT":
                flatlist.append(fnlist[i])
            # Case for ARCs
            elif image.object == "ARC":
                if not (image.filter in list_of_filters):
                    list_of_filters.append(image.filter)
            # Case for Objects
            else:
                if not (image.filter in list_of_filters):
                    list_of_filters.append(image.filter)
                if not (image.object in list_of_objects):
                    list_of_objects.append(image.object)
        image.close()
    list_of_filters = np.array(list_of_filters)
    list_of_objects = np.array(list_of_objects)

    # Reopen the files and populate the object lists
    objectlists = []
    filterlists = []
    for i in range(len(list_of_objects)):
        objectlists.append([])
    for i in range(len(list_of_filters)):
        filterlists.append([])
    for i in range(len(fnlist)):
        image = FPImage(fnlist[i])
        # Skip the rest if image was previously flagged as bad
        if image.goodtog is None:
            if image.object != "FLAT":
                if image.object != "ARC":
                    object_index = np.where(image.object == list_of_objects)[0][0]
                    objectlists[object_index].append(fnlist[i])
                filter_index = np.where(image.filter == list_of_filters)[0][0]
                filterlists[filter_index].append(fnlist[i])
        image.close()

    return flatlist, list_of_objects, objectlists, list_of_filters, filterlists
Example #10
0
def verify_images(fnlist):
    """Interactively plots all of the images in 'fnlist' to verify their
    contents are correct. Allows user to mark bad images and change image
    header keywords to correct issues.

    Inputs:
    fnlist -> List of filenames to be plotted

    Outputs:
    newfnlist -> New list of filenames with bad files removed from the list

    """

    # Change pyplot shortcut keys
    oldsavekey = plt.rcParams["keymap.save"]
    plt.rcParams["keymap.save"] = ""

    # How many total images are there?
    num_images = len(fnlist)
    flagged = np.zeros(len(fnlist), dtype="bool")

    # Interactively look at the images
    current_num = 0
    while True:
        image = FPImage(fnlist[current_num], update=True)
        image.header["fpverify"] = "True"
        verify = Verify_Images_Plot(image.inty, image.object, flagged[current_num], current_num + 1, num_images)
        image.close()
        if verify.key == "a":
            current_num = current_num - 1
        if verify.key == "d":
            current_num = current_num + 1
        if verify.key == "w":
            flagged[current_num] = True
        if verify.key == "s":
            flagged[current_num] = False

        # Breakout condition
        if current_num == -1:
            while True:
                yn = raw_input("Finished flagging images? (y/n) ")
                if "n" in yn or "N" in yn:
                    current_num = 0
                    break
                elif "y" in yn or "Y" in yn:
                    break
        elif current_num == num_images:
            while True:
                yn = raw_input("Finished flagging images? (y/n) ")
                if "n" in yn or "N" in yn:
                    current_num = current_num - 1
                    break
                elif "y" in yn or "Y" in yn:
                    break
        if current_num == -1 or current_num == num_images or verify.key == "q":
            break

    # Review flagged images
    print "Reviewing " + str(np.sum(flagged)) + " flagged images..."
    newfnlist = []
    for i in range(len(fnlist)):
        if flagged[i]:
            image = FPImage(fnlist[i], update=True)
            review = Review_Images_Plot(image.inty, image.object, fnlist[i])
            if review.key == "h":
                # Correct header
                while True:
                    prompt = "New object type for header (ARC, FLAT, etc.): "
                    newhead = raw_input(prompt)
                    prompt = "New object header: '" + newhead + "'. Is this okay? (y/n) "
                    yn = raw_input(prompt)
                    if ("y" in yn or "Y" in yn) and not ("n" in yn or "N" in yn):
                        image.object = newhead
                        break
                newfnlist.append(fnlist[i])
            if review.key == "a":
                newfnlist.append(fnlist[i])
            if review.key == "d":
                print fnlist[i] + " removed from file list."
                image.goodtog = "False"
            image.close()
        else:
            newfnlist.append(fnlist[i])

    plt.rcParams["keymap.save"] = oldsavekey

    return newfnlist
Example #11
0
def deghost(fn):
    """Routine to deghost an image by rotating it about a central point and
    subtracting a constant multiple of this rotated image from the original.
    My experience has shown that about 0.04 is the right value, but your
    mileage may vary.

    The fits header must contain values in "fpxcen" and "fpycen" for the center
    about which to be rotated.

    Optionally also modifies an uncertainty image to account for the effects
    of deghosting on the error propagation.

    Creates the fits header keyword "fpghost" = "True".

    Inputs:
    fn -> String, the path to the fits image to be deghosted.

    """

    # Open the image and check for center coordinates
    image = FPImage(fn, update=True)
    if image.xcen is None:
        exit("Error! Image "+fn+" doesn't have center coordinates in header!")
    xcen = image.xcen+1
    ycen = image.ycen+1
    bins = int(image.bins.split()[0])
    reflection_xcen = image.xcen + reflection_center_x_shift/bins
    reflection_ycen = image.ycen + reflection_center_y_shift/bins
    r_array = image.rarray(reflection_xcen, reflection_ycen)
    g_array = reflection_intercept + reflection_slope*bins*r_array

    # Deghost the intensity image
    print "Deghosting image "+fn

    # Determine image size
    xsize, ysize = image.xsize, image.ysize

    # Make a mask for the chip gaps and outside-aperture stuff
    mask = image.inty == 0

    # Make an array of the flipped data
    flipinty = image.inty[::-1, ::-1].copy()*1.0
    flipvari = image.vari[::-1, ::-1].copy()*1.0
    flip_g = g_array[::-1, ::-1].copy()*1.0

    # Calculate the difference between the image's geometric center (midpoint)
    # and the axis of rotation
    xshift = 2*xcen-xsize-1
    yshift = 2*ycen-ysize-1

    # A given pixel's position, when rotated, will overlap its four
    # neighboring pixels. All pixels will have the same four overlapping
    # regions because the rotation is 180 degrees. Here we take a weighted
    # sum of these four pixels, where the weights are equal to the areas
    # of overlap. This weighted sum is subtracted from the original pixel.
    for i in range(4):
        # This branching is for the four overlapping pixel regions
        if i == 0:
            miny = max(np.floor(yshift), 0)
            maxy = min(ysize+np.floor(yshift), ysize)
            minx = max(np.floor(xshift), 0)
            maxx = min(xsize+np.floor(xshift), xsize)
            flipminy = max(-np.floor(yshift), 0)
            flipmaxy = min(ysize, ysize-np.floor(yshift))
            flipminx = max(-np.floor(xshift), 0)
            flipmaxx = min(xsize, xsize-np.floor(xshift))
            frac = abs((np.ceil(yshift)-yshift)*(np.ceil(xshift)-xshift))
        if i == 1:
            miny = max(np.ceil(yshift), 0)
            maxy = min(ysize+np.ceil(yshift), ysize)
            minx = max(np.floor(xshift), 0)
            maxx = min(xsize+np.floor(xshift), xsize)
            flipminy = max(-np.ceil(yshift), 0)
            flipmaxy = min(ysize, ysize-np.ceil(yshift))
            flipminx = max(-np.floor(xshift), 0)
            flipmaxx = min(xsize, xsize-np.floor(xshift))
            frac = abs((np.floor(yshift)-yshift)*(np.ceil(xshift)-xshift))
        if i == 2:
            miny = max(np.floor(yshift), 0)
            maxy = min(ysize+np.floor(yshift), ysize)
            minx = max(np.ceil(xshift), 0)
            maxx = min(xsize+np.ceil(xshift), xsize)
            flipminy = max(-np.floor(yshift), 0)
            flipmaxy = min(ysize, ysize-np.floor(yshift))
            flipminx = max(-np.ceil(xshift), 0)
            flipmaxx = min(xsize, xsize-np.ceil(xshift))
            frac = abs((np.ceil(yshift)-yshift)*(np.floor(xshift)-xshift))
        if i == 3:
            miny = max(np.ceil(yshift), 0)
            maxy = min(ysize+np.ceil(yshift), ysize)
            minx = max(np.ceil(xshift), 0)
            maxx = min(xsize+np.ceil(xshift), xsize)
            flipminy = max(-np.ceil(yshift), 0)
            flipmaxy = min(ysize, ysize-np.ceil(yshift))
            flipminx = max(-np.ceil(xshift), 0)
            flipmaxx = min(xsize, xsize-np.ceil(xshift))
            frac = abs((np.floor(yshift)-yshift)*(np.floor(xshift)-xshift))
        g = flip_g[flipminy:flipmaxy,
                   flipminx:flipmaxx]

        # Rotate and subtract the intensity array
        image.inty[miny:maxy,
                   minx:maxx] -= g*frac*flipinty[flipminy:flipmaxy,
                                                 flipminx:flipmaxx]
        image.vari[miny:maxy,
                   minx:maxx] += (g*frac)**2*flipvari[flipminy:flipmaxy,
                                                      flipminx:flipmaxx]

    # Remask the intensity data using the mask we created
    image.inty[mask] = 0

    # Update the header
    image.ghosttog = "True"

    # Close the image
    image.close()

    return
Example #12
0
def sub_sky_rings(fnlist, medfilelist):
    """Fits for night sky rings in a series of images, then subtracts them off.
    If uncertainty images exist, updates them with better uncertainties.

    The rings are fitted in an interactive way by the user.

    After the ring subtraction, the header keyword "fpdering" is created and
    set to "True."

    Note: A 'median.fits' image must exist for the object or this routine will
    not work.

    Inputs:
    fnlist -> List of strings, each the path to a fits image.
    medfilelist -> List of paths to median images for each image to subtract
                   off (probably all the same, but could be different)

    """

    # This bit takes care of the 's' to save shortcut in matplotlib.
    oldsavekey = plt.rcParams["keymap.save"]
    plt.rcParams["keymap.save"] = ""
    oldfullscreenkey = plt.rcParams['keymap.fullscreen']
    plt.rcParams['keymap.fullscreen'] = ""

    # Open all of the images, median-subtract them, make wavelength arrays
    # make skywavelibs for each, make spectra for each, trim images
    images = [FPImage(fn) for fn in fnlist]
    wavearraylist = []
    skywavelibs = []
    skywavelibs_extended = []
    spectrum_wave = []
    spectrum_inty = []
    minwavelist = []
    maxwavelist = []
    xcenlist = []
    ycenlist = []
    Flist = []
    wave0list = []
    has_been_saved = np.zeros(len(fnlist), dtype="bool")
    for i in range(len(fnlist)):
        # Get a bunch of relevant header values
        medimage = fits.open(medfilelist[i])
        Flist.append(images[i].calf)
        wave0list.append(images[i].wave0)
        xcen = images[i].xcen
        ycen = images[i].ycen
        arad = images[i].arad
        axcen = images[i].axcen
        aycen = images[i].aycen
        # Median subtract it
        images[i].inty -= medimage[0].data
        images[i].inty -= np.median(images[i].inty[images[i].badp != 1])
        # Make wavelength array
        r2grid = images[i].rarray(xcen, ycen)**2
        wavearraylist.append(wave0list[i]/np.sqrt(1+r2grid/Flist[i]**2))
        # Get the sky wave library
        minwavelist.append(np.min(wavearraylist[i][r2grid < arad**2]))
        maxwavelist.append(np.max(wavearraylist[i][r2grid < arad**2]))
        skywaves = get_libraries(images[i].filter)[1]
        skywavelibs.append(skywaves[np.logical_and(skywaves > minwavelist[i],
                                                   skywaves < maxwavelist[i])])
        # Make an "extended" sky wave library
        lowermask = np.logical_and(skywaves < minwavelist[i],
                                   skywaves > minwavelist[i]-5)
        uppermask = np.logical_and(skywaves > maxwavelist[i],
                                   skywaves < maxwavelist[i]+5)
        skywavelibs_extended.append(skywaves[np.logical_or(uppermask,
                                                           lowermask)])
        # Make the spectrum
        minwav = np.min(wavearraylist[i][r2grid < arad**2])
        maxwav = np.max(wavearraylist[i][r2grid < arad**2])
        wavstep = 0.25  # Quarter-angstrom spacing
        spectrum_wave.append(np.linspace(minwav, maxwav,
                                         np.int(((maxwav-minwav)/wavstep))))
        spectrum_inty.append(np.zeros_like(spectrum_wave[i][1:]))
        for j in range(len(spectrum_wave[i])-1):
            uppermask = wavearraylist[i] < spectrum_wave[i][j+1]
            lowermask = wavearraylist[i] > spectrum_wave[i][j]
            mask = np.logical_and(uppermask, lowermask)
            goodmask = np.logical_and(mask, images[i].badp == 0)
            spectrum_inty[i][j] = np.median(images[i].inty[goodmask])
        spectrum_wave[i] = 0.5*(spectrum_wave[i][:-1]+spectrum_wave[i][1:])
        # Trim the images and arrays to the aperture size
        images[i].inty = images[i].inty[aycen-arad:aycen+arad,
                                        axcen-arad:axcen+arad]
        wavearraylist[i] = wavearraylist[i][aycen-arad:aycen+arad,
                                            axcen-arad:axcen+arad]
        xcenlist.append(arad+xcen-axcen)
        ycenlist.append(arad+ycen-aycen)
        # Convert skywavelibs to lists
        skywavelibs[i] = list(skywavelibs[i])
        skywavelibs_extended[i] = list(skywavelibs_extended[i])
        # Close median image
        medimage.close()
        # Try to fix the NaNs
        mask = np.logical_not(np.isnan(spectrum_inty[i]))
        spectrum_wave[i] = spectrum_wave[i][mask]
        spectrum_inty[i] = spectrum_inty[i][mask]

    # Interactive plotting of ring profiles
    addedwaves = []
    final_fitted_waves = []
    final_fitted_intys = []
    final_fitted_sigs = []
    for i in range(len(fnlist)):
        addedwaves.append([])
        final_fitted_waves.append([])
        final_fitted_intys.append([])
        final_fitted_sigs.append([])
    i = 0
    while True:
        # Determine which wavelengths we want to fit
        waves_to_fit = skywavelibs[i]+addedwaves[i]

        # Generate a fitting function from those wavelengths
        func_to_fit = make_sum_gaussians(waves_to_fit)

        # Come up with reasonable guesses for the fitting function parameters
        contguess = [0]
        intyguesses = []
        sigguesses = []
        for j in range(len(waves_to_fit)):
            uppermask = spectrum_wave[i] < waves_to_fit[j]+4
            lowermask = spectrum_wave[i] > waves_to_fit[j]-4
            mask = np.logical_and(uppermask, lowermask)
            # Guess at line intensity via integral
            intyguesses.append(np.sum(spectrum_inty[i][mask] *
                                      (spectrum_wave[i][1] -
                                       spectrum_wave[i][0])))
            # Guess sigma is 2 angstroms
            sigguesses.append(2)
        guess = contguess+intyguesses+sigguesses

        # Fit the spectrum
        fitsuccess = True
        try:
            fit = curve_fit(func_to_fit, spectrum_wave[i],
                            spectrum_inty[i], p0=guess)[0]
        except RuntimeError:
            print ("Warning: Fit did not converge. " +
                   "Maybe remove some erroneous lines from the fit?")
            fit = guess
            fitsuccess = False
        fitcont = fit[0]
        fitintys = np.array(fit[1:len(waves_to_fit)+1])
        fitsigs = np.array(fit[len(waves_to_fit)+1:2*len(waves_to_fit)+1])
        # Flip signs of negative sigma fits (sign degeneracy)
        fitintys[fitsigs < 0] = -1*fitintys[fitsigs < 0]
        fitsigs[fitsigs < 0] = -1*fitsigs[fitsigs < 0]

        # Make the subtracted plot array
        subarray = images[i].inty.copy()
        for j in range(len(waves_to_fit)):
            subarray -= fitintys[j]*nGauss(wavearraylist[i]-waves_to_fit[j],
                                           fitsigs[j])

        # Figure out which radius each wavelength is at
        radiilist = []
        for j in range(len(waves_to_fit)):
            radiilist.append(Flist[i] *
                             np.sqrt((wave0list[i]/waves_to_fit[j])**2-1))

        # Create the subtracted spectrum
        sub_spec_inty = spectrum_inty[i] - fitcont
        for j in range(len(waves_to_fit)):
            sub_spec_inty -= (fitintys[j] *
                              nGauss(spectrum_wave[i]-waves_to_fit[j],
                                     fitsigs[j]))

        # Create the spectrum fit plot
        fit_X = np.linspace(minwavelist[i], maxwavelist[i], 500)
        fit_Y = np.ones_like(fit_X)*fitcont
        for j in range(len(waves_to_fit)):
            fit_Y += fitintys[j]*nGauss(fit_X-waves_to_fit[j], fitsigs[j])

        # Plot the image, spectrum, and fit
        profile_plot = SkyRingPlot(images[i].inty, subarray,
                                   xcenlist[i], ycenlist[i], radiilist,
                                   spectrum_wave[i], spectrum_inty[i],
                                   sub_spec_inty, fit_X, fit_Y,
                                   skywavelibs[i], addedwaves[i],
                                   skywavelibs_extended[i],
                                   repr(i+1)+"/"+repr(len(fnlist)),
                                   has_been_saved[i], fitsuccess)

        # Shifting images and breakout condition
        if profile_plot.key == "a":
            i += -1
        if profile_plot.key == "d":
            i += 1
        quitloop = False
        if (i == -1 or i == len(fnlist) or profile_plot.key == "q"):
            if (np.sum(np.logical_not(has_been_saved)) == 0):
#                 while True:
#                     yn = raw_input("Finished fitting ring profiles? (y/n) ")
#                     if "n" in yn or "N" in yn:
#                         break
#                     if "y" in yn or "Y" in yn:
#                         quitloop = True
#                         break
                quitloop = True
            else:
                print ("Error: Ring fits have not yet been " +
                       "saved for the following images:")
                imagenumbers = np.arange(len(fnlist))+1
                print imagenumbers[np.logical_not(has_been_saved)]
        if quitloop:
            break
        if i == -1:
            i = 0
        if i == len(fnlist):
            i = len(fnlist)-1

        # Delete ring option
        if profile_plot.key == "s":
            nearest_wave = None
            if profile_plot.axis in [1, 3] and len(waves_to_fit) != 0:
                # The keypress was made in an image plot
                clicked_radius = np.sqrt((profile_plot.xcoo - xcenlist[i])**2 +
                                         (profile_plot.ycoo - ycenlist[i])**2)
                near_index = np.argmin(np.abs((np.array(radiilist) -
                                               clicked_radius)))
                nearest_wave = waves_to_fit[near_index]
            elif profile_plot.axis in [2, 4] and len(waves_to_fit) != 0:
                # The keypress was in a spectrum plot
                clicked_wave = profile_plot.xcoo
                near_index = np.argmin(np.abs(np.array(waves_to_fit) -
                                              clicked_wave))
                nearest_wave = waves_to_fit[near_index]
            if nearest_wave is not None:
                # Remove the nearest wavelength from the sky_wave_lib or
                # added waves, add it to extra waves
                if nearest_wave in skywavelibs[i]:
                    skywavelibs[i].remove(nearest_wave)
                    skywavelibs_extended[i].append(nearest_wave)
                if nearest_wave in addedwaves[i]:
                    addedwaves[i].remove(nearest_wave)

        # Add ring option
        if profile_plot.key == "w":
            clicked_wave = None
            if profile_plot.axis in [1, 3]:
                # The keypress was made in an image plot
                clicked_radius = np.sqrt((profile_plot.xcoo - xcenlist[i])**2 +
                                         (profile_plot.ycoo - ycenlist[i])**2)
                # Convert radius to wavelength
                clicked_wave = (wave0list[i] /
                                (1+clicked_radius**2/Flist[i]**2)**0.5)
            elif profile_plot.axis in [2, 4]:
                # The keypress was in a spectrum plot
                clicked_wave = profile_plot.xcoo
            if clicked_wave is not None:
                # Find the nearest wavelength in the extended list
                if len(np.array(skywavelibs_extended[i])-clicked_wave) > 0:
                    near = np.argmin(np.abs(np.array(skywavelibs_extended[i]) -
                                            clicked_wave))
                    wave_to_add = skywavelibs_extended[i][near]
                    # Add that wave to skywavelibs, remove it from extended
                    skywavelibs[i].append(wave_to_add)
                    skywavelibs_extended[i].remove(wave_to_add)

        # Force add ring option
        if profile_plot.key == "e":
            clicked_wave = None
            if profile_plot.axis in [1, 3]:
                # The click was made in an image plot
                clicked_radius = np.sqrt((profile_plot.xcoo - xcenlist[i])**2 +
                                         (profile_plot.ycoo - ycenlist[i])**2)
                # Convert radius to wavelength
                clicked_wave = (wave0list[i] /
                                (1+clicked_radius**2/Flist[i]**2)**0.5)
            elif profile_plot.axis in [2, 4]:
                # The click was in a spectrum plot
                clicked_wave = profile_plot.xcoo
            if clicked_wave is not None:
                # Add this wavelength to the added array
                addedwaves[i].append(clicked_wave)

        # Save option
        if profile_plot.key == "r":
            final_fitted_waves[i] = waves_to_fit[:]
            final_fitted_intys[i] = fitintys[:]
            final_fitted_sigs[i] = fitsigs[:]
            has_been_saved[i] = True

        # Manual Mode Option
        if profile_plot.key == "x":

            # Initialize the manual mode lists
            man_waves = list(waves_to_fit)
            man_intys = list(fitintys)
            man_sigs = list(fitsigs)
            man_cont = fitcont
            man_real = []
            for wave in man_waves:
                if wave in skywavelibs[i] or wave in skywavelibs_extended[i]:
                    man_real.append(True)
                else:
                    man_real.append(False)

            # Initialize refinement level and selected line
            selected_line = 0
            refine = 1
            inty_refine = 0.25*(np.max(spectrum_inty[i]) -
                                np.min(spectrum_inty[i]))
            sig_refine = 0.5
            cont_refine = inty_refine

            # Loop until completion
            while True:

                # Make the subtracted plot array
                subarray = images[i].inty.copy()
                for j in range(len(man_waves)):
                    subarray -= man_intys[j]*nGauss((wavearraylist[i] -
                                                     man_waves[j]),
                                                    man_sigs[j])

                # Figure out which radius each wavelength is at
                radiilist = []
                for j in range(len(man_waves)):
                    radiilist.append(Flist[i] *
                                     np.sqrt((wave0list[i]/man_waves[j])**2-1))
                for skywav in skywavelibs_extended[i]:
                    radiilist.append(Flist[i] *
                                     np.sqrt((wave0list[i] / skywav)**2-1))

                # Create the subtracted spectrum
                sub_spec_inty = spectrum_inty[i] - man_cont
                for j in range(len(man_waves)):
                    sub_spec_inty -= (man_intys[j] *
                                      nGauss(spectrum_wave[i]-man_waves[j],
                                             man_sigs[j]))

                # Create the spectrum fit plot
                fit_X = np.linspace(minwavelist[i], maxwavelist[i], 500)
                fit_Y = np.ones_like(fit_X)*man_cont
                for j in range(len(man_waves)):
                    fit_Y += man_intys[j]*nGauss(fit_X-man_waves[j],
                                                 man_sigs[j])

                # What colors are we using?
                colors = []
                for k in range(len(man_waves)):
                    if selected_line == k:
                        colors.append('purple')
                    elif not man_real[k]:
                        colors.append('green')
                    elif man_real[k]:
                        colors.append('blue')
                for _w in skywavelibs_extended[i]:
                    colors.append('red')

                # Plot the spectrum and fit, get user input
                man_plot = ManualPlot(images[i].inty, subarray,
                                      xcenlist[i], ycenlist[i],
                                      spectrum_wave[i], spectrum_inty[i],
                                      sub_spec_inty,
                                      fit_X, fit_Y,
                                      has_been_saved[i], (repr(i+1)+"/" +
                                                          repr(len(fnlist))),
                                      radiilist,
                                      man_waves+skywavelibs_extended[i],
                                      colors)

                # Cycle selected line
                if man_plot.key == "z":
                    selected_line += 1
                    if selected_line >= len(man_waves):
                        selected_line = 0
                    refine = 1

                # Save fit
                if man_plot.key == "r":
                    final_fitted_waves[i] = man_waves[:]
                    final_fitted_intys[i] = man_intys[:]
                    final_fitted_sigs[i] = man_sigs[:]
                    has_been_saved[i] = True
                    break

                # Refinement levels
                if man_plot.key == "c":
                    refine *= 0.5
                if man_plot.key == "v":
                    refine *= 2

                # Increase/decrease inty
                if len(man_waves) != 0:
                    if man_plot.key == "w":
                        man_intys[selected_line] += refine*inty_refine
                    if man_plot.key == "s":
                        man_intys[selected_line] -= refine*inty_refine

                # Increase/decrease line width
                if len(man_waves) != 0:
                    if man_plot.key == "q":
                        man_sigs[selected_line] += refine*sig_refine
                    if man_plot.key == "a":
                        man_sigs[selected_line] -= refine*sig_refine

                # Increase/decrease continuum
                if man_plot.key == "t":
                    man_cont += refine*cont_refine
                if man_plot.key == "g":
                    man_cont -= refine*cont_refine

                # Add the known line nearest the keypress
                if man_plot.key == "e":
                    clicked_wave = None
                    if man_plot.axis in [1, 3]:
                        # The keypress was made in an image plot
                        clicked_radius = np.sqrt((man_plot.xcoo -
                                                  xcenlist[i])**2 +
                                                 (man_plot.ycoo -
                                                  ycenlist[i])**2)
                        # Convert radius to wavelength
                        clicked_wave = (wave0list[i] /
                                        (1+clicked_radius**2/Flist[i]**2)**0.5)
                    elif man_plot.axis in [2, 4]:
                        # The keypress was in a spectrum plot
                        clicked_wave = man_plot.xcoo
                    if clicked_wave is not None:
                        # Find the nearest wavelength in the extended list
                        if len(np.array(skywavelibs_extended[i]) -
                               clicked_wave) > 0:
                            sky = np.array(skywavelibs_extended[i])
                            near = np.argmin(np.abs(sky-clicked_wave))
                            wave_to_add = skywavelibs_extended[i][near]
                            # Add that wave to manwaves,
                            # remove from extended list
                            man_waves.append(wave_to_add)
                            man_intys.append(0)
                            man_sigs.append(1)
                            man_real.append(True)
                            skywavelibs_extended[i].remove(wave_to_add)
                            selected_line = len(man_waves)-1

                # Remove line nearest the keypress
                if man_plot.key == "d":
                    nearest_wave = None
                    if man_plot.axis in [1, 3] and len(man_waves) != 0:
                        # The keypress was made in an image plot
                        clicked_radius = np.sqrt((man_plot.xcoo -
                                                  xcenlist[i])**2 +
                                                 (man_plot.ycoo -
                                                  ycenlist[i])**2)
                        near_index = np.argmin(np.abs((np.array(radiilist) -
                                                       clicked_radius)))
                        nearest_wave = man_waves[near_index]
                    elif man_plot.axis in [2, 4] and len(man_waves) != 0:
                        # The keypress was in a spectrum plot
                        clicked_wave = man_plot.xcoo
                        near_index = np.argmin(np.abs(np.array(man_waves) -
                                                      clicked_wave))
                        nearest_wave = man_waves[near_index]
                    if nearest_wave is not None:
                        # Remove the nearest wavelength
                        # re-add to extended list
                        # if it was real
                        wave_index = np.where(np.array(man_waves) ==
                                              nearest_wave)[0][0]
                        man_waves.pop(wave_index)
                        man_intys.pop(wave_index)
                        man_sigs.pop(wave_index)
                        if man_real[wave_index]:
                            skywavelibs_extended[i].append(nearest_wave)
                        man_real.pop(wave_index)
                        if wave_index == selected_line:
                            selected_line = 0

                # Force-add
                if man_plot.key == "f":
                    clicked_wave = None
                    if profile_plot.axis in [1, 3]:
                        # The click was made in an image plot
                        clicked_radius = np.sqrt((man_plot.xcoo -
                                                  xcenlist[i])**2 +
                                                 (man_plot.ycoo -
                                                  ycenlist[i])**2)
                        # Convert radius to wavelength
                        clicked_wave = (wave0list[i] /
                                        (1+clicked_radius**2 /
                                         Flist[i]**2)**0.5)
                    elif profile_plot.axis in [2, 4]:
                        # The click was in a spectrum plot
                        clicked_wave = man_plot.xcoo
                    if clicked_wave is not None:
                        # Add this wavelength to the added array
                        man_waves.append(clicked_wave)
                        man_intys.append(0)
                        man_sigs.append(1)
                        man_real.append(False)
                        selected_line = len(man_waves)-1

                # Switch back to automatic
                if man_plot.key == "x":
                    break

    # Close all of the images
    for image in images:
        image.close()

    # Subtract the ring profiles and update headers
    for i in range(len(fnlist)):
        image = FPImage(fnlist[i], update=True)
        arad = image.arad
        axcen = image.axcen
        aycen = image.aycen
        mask = image.inty == 0  # For re-correcting chip gaps
        for j in range(len(final_fitted_waves[i])):
            image.header['subwave'+repr(j)] = final_fitted_waves[i][j]
            image.header['subinty'+repr(j)] = final_fitted_intys[i][j]
            image.header['subsig'+repr(j)] = final_fitted_sigs[i][j]
            ringimg = (final_fitted_intys[i][j] *
                       nGauss(wavearraylist[i]-final_fitted_waves[i][j],
                              final_fitted_sigs[i][j]))
            image.inty[aycen-arad:aycen+arad, axcen-arad:axcen+arad] -= ringimg
            image.ringtog = "True"
        image.inty[mask] = 0  # For re-correcting chip gaps
        image.close()

    # Restore the old keyword shortcut
    plt.rcParams["keymap.save"] = oldsavekey
    plt.rcParams['keymap.fullscreen'] = oldfullscreenkey

    return
def find_ghost_centers(fnlist, tolerance=3, thresh=4, guess=None):
    """This routine finds the most likely optical center for a series of images
    by attempting to match ghost reflections of stars with their stellar
    counterparts. It can be rather slow if the fields are very dense with
    stars, but is also much more likely to succeed in locating the correct
    center in dense fields.

    Images should have already been aperture masked at this point. If they
    haven't, run 'aperture_mask' on them first.

    It is useful if the images have had their seeing FWHMs measured. If they
    have, these values (in pixels) should be in the fits headers as "FPFWHM".
    If not, the FWHM is assumed to be 5 pixels (and this is not ideal).

    The routine creates a number of temporary files while running, which are
    deleted at the end of the routine. Interrupting the routine while running
    is probably not a great idea.

    The routine returns a list of image centers as well as appending these to
    the fits headers as "fpxcen" and "fpycen"

    Inputs:
    fnlist -> List of strings, each one containing the path to a fits image.
              The images should be roughly aligned to one another or the
              routine will not work.
    tolerance -> Optional. How close two pixels can be and be "close enough"
                 Default is 3 pixels.
    thresh -> Optional. Level above sky background variation to look for objs.
              Default is 4.5 (times SkySigma). Decrease if center positions
              aren't being found accurately. Increase for crowded fields to
              decrease computation time.
    guess -> Optional. If you already have an idea of where the center should
             be, you'd put it here. Should be a 2-long iterable, with guess[0]
             being the X center guess, and guess[1] being the y center guess.

    Outputs:
    xcenlist -> List of image center X coordinates
    ycenlist -> List of image center Y coordinates

    """

    # Get image FWHMs
    fwhm = np.empty(len(fnlist))
    firstimage = FPImage(fnlist[0])
    toggle = firstimage.fwhm
    axcen = firstimage.axcen
    aycen = firstimage.aycen
    arad = firstimage.arad
    firstimage.close()
    if axcen is None:
        exit("Error! Images have not yet been aperture-masked! Do this first!")
    if toggle is None:
        print "Warning: FWHMs have not been measured!"
        print "Assuming 5 pixel FWHM for all images."
        fwhm = 5.*np.ones(len(fnlist))
    else:
        for i in range(len(fnlist)):
            image = FPImage(fnlist[i])
            fwhm[i] = image.fwhm
            image.close()

    # Get sky background levels
    skyavg = np.empty(len(fnlist))
    skysig = np.empty(len(fnlist))
    for i in range(len(fnlist)):
        image = FPImage(fnlist[i])
        skyavg[i], skysig[i], _skyvar = image.skybackground()
        image.close()

    # Identify the stars in each image
    xlists = []
    ylists = []
    maglists = []
    print "Identifying stars and ghosts in each image..."
    for i in range(len(fnlist)):
        xlists.append([])
        ylists.append([])
        maglists.append([])
        image = FPImage(fnlist[i])
        axcen = image.axcen
        aycen = image.aycen
        arad = image.arad
        sources = daofind(image.inty-skyavg[i],
                          fwhm=fwhm[i],
                          threshold=thresh*skysig[i]).as_array()
        for j in range(len(sources)):
            # Masks for center and edge of image
            cenmask = ((sources[j][1]-axcen)**2 +
                       (sources[j][2]-aycen)**2 > (0.05*arad)**2)
            edgemask = ((sources[j][1]-axcen)**2 +
                        (sources[j][2]-aycen)**2 < (0.95*arad)**2)
            if np.logical_and(cenmask, edgemask):
                xlists[i].append(sources[j][1])
                ylists[i].append(sources[j][2])
                maglists[i].append(sources[j][-1])
        image.close()

    if guess is None:
        # Use the found stars to come up with a center guess for each image
        xcen = np.zeros(len(fnlist))
        ycen = np.zeros(len(fnlist))
        goodcen = np.zeros(len(fnlist))
        for i in range(len(fnlist)):
            N = len(xlists[i])
            xcenarray = np.zeros(((N*N-N)/2))
            ycenarray = np.zeros(((N*N-N)/2))
            dist2array = np.zeros(((N*N-N)/2))
            sub1array = np.zeros(((N*N-N)/2))
            index = 0
            # All "possible" centers for all possible pairs of stars
            for j in range(N):
                for k in range(j+1, N):
                    xcenarray[index] = xlists[i][j]+xlists[i][k]
                    ycenarray[index] = ylists[i][j]+ylists[i][k]
                    index = index+1
            xcenarray, ycenarray = 0.5*xcenarray, 0.5*ycenarray
            # Cross check the various possible centers against each other
            for j in range(len(xcenarray)):
                dist2array = ((xcenarray-xcenarray[j])**2 +
                              (ycenarray-ycenarray[j])**2)
                sub1array[j] = np.sum(dist2array < tolerance**2)
            # Determine the locations of the "best" centers.
            bestcenloc = np.where(sub1array == max(sub1array))[0]
            # Now cross check JUST the best ones against each other
            sub1array = np.zeros(len(bestcenloc))
            xcenarray = xcenarray[bestcenloc]
            ycenarray = ycenarray[bestcenloc]
            for j in range(len(bestcenloc)):
                dist2array = ((xcenarray-xcenarray[j])**2 +
                              (ycenarray-ycenarray[j])**2)
                sub1array[j] = np.sum(dist2array < tolerance**2)
            # Again, determine the locations of the "best" centers.
            bestcenloc = np.where(sub1array == max(sub1array))[0]
            xcen[i] = np.average(xcenarray[bestcenloc])
            ycen[i] = np.average(ycenarray[bestcenloc])

        # Cross-check the various image's centers against each other
        for i in range(len(fnlist)):
            dist2array = (xcen-xcen[i])**2 + (ycen-ycen[i])**2
            goodcen[i] = np.sum(dist2array < tolerance**2)

        # Determine where in the arrays the best fitting centers are
        bestcenloc = np.where(goodcen == max(goodcen))[0]
        bestxcen = np.average(xcen[bestcenloc])
        bestycen = np.average(ycen[bestcenloc])

    else:
        # Forced guess:
        bestxcen, bestycen = guess[0], guess[1]

    # Now we want to improve the center for each image using the best guesses
    xcenlist = np.zeros(len(fnlist))
    ycenlist = np.zeros(len(fnlist))
    objxs = []
    objys = []
    ghoxs = []
    ghoys = []
    for i in range(len(fnlist)):
        # Where would the reflected objects be if they exist
        refxlist = 2.*bestxcen - np.array(xlists[i])
        refylist = 2.*bestycen - np.array(ylists[i])
        # Populate lists of objects and ghosts based on this
        objxs.append([])
        objys.append([])
        ghoxs.append([])
        ghoys.append([])
        for j in range(len(xlists[i])):
            dist2list = ((xlists[i] - refxlist[j])**2 +
                         (ylists[i] - refylist[j])**2)
            matchlist = dist2list < tolerance**2
            if np.sum(matchlist) >= 1:
                # We found a match! Now we need to know where the match is
                matchloc = np.where(matchlist == 1)[0][0]
                # Cool, now, is this thing brighter than the ghost?
                if maglists[i][matchloc] < maglists[i][j]:
                    # It is! Record it:
                    objxs[i].append(xlists[i][matchloc])
                    objys[i].append(ylists[i][matchloc])
                    ghoxs[i].append(xlists[i][j])
                    ghoys[i].append(ylists[i][j])
        # Calculate the centers based on the object / ghost coords
        if len(objxs[i]) == 0:
            xcenlist[i] = 0
            ycenlist[i] = 0
        else:
            xcenlist[i] = 0.5*(np.average(objxs[i])+np.average(ghoxs[i]))
            ycenlist[i] = 0.5*(np.average(objys[i])+np.average(ghoys[i]))

    # Fill in the blanks with a "best guess"
    xcenlist[xcenlist == 0] = np.average(xcenlist[xcenlist != 0])
    ycenlist[ycenlist == 0] = np.average(ycenlist[ycenlist != 0])
    xcenlist[np.isnan(xcenlist)] = bestxcen
    ycenlist[np.isnan(ycenlist)] = bestycen

    # Append the values to the image headers
    for i in range(len(fnlist)):
        image = FPImage(fnlist[i], update=True)
        image.xcen = xcenlist[i]
        image.ycen = ycenlist[i]
        image.close()

    # Manually verify ghost centers
    while True:
        yn = raw_input("Manually verify ghost centers? (Recommended) (y/n) ")
        if "n" in yn or "N" in yn:
            break
        elif "y" in yn or "Y" in yn:
            goodtog = verify_center(fnlist,
                                    objxs, objys,
                                    ghoxs, ghoys,
                                    xcenlist, ycenlist)
            if goodtog:
                break
            else:
                exit("Centers not approved!")

    return xcenlist, ycenlist
Example #14
0
def align_norm(fnlist, tolerance=5, thresh=3.5):
    """Aligns a set of images to each other, as well as normalizing the images
    to the same average brightness.

    Both the alignment and normalization are accomplished through stellar
    photometry using the IRAF routine 'daophot'. The centroids of a handful
    of stars are found and used to run the IRAF routine 'imalign'. The
    instrumental magnitudes of the stars are used to determine by how much
    each image must be scaled for the photometry to match across images.

    The images are simply updated with their rescaled, shifted selves. This
    overwrites the previous images and adds the header keyword 'fpphot' to
    the images.

    A handful of temporary files are created during this process, which should
    all be deleted by the routine at the end. But if it is interrupted, they
    might not be.

    If the uncertainty images exist, this routine also shifts them by the same
    amounts as the intensity images, as well as updating the uncertainty values
    for both the new normalization and the uncertainties in normalizing the
    images.

    Inputs:
    fnlist -> List of strings, each the path to a fits image.
    tolerance -> How close two objects can be and still be considered the same
                 object. Default is 3 pixels.
    thresh -> Optional. Level above sky background variation to look for objs.
              Default is 3.5 (times SkySigma). Decrease if center positions
              aren't being found accurately. Increase for crowded fields to
              decrease computation time.

    """

    # Get image FWHMs
    fwhm = np.empty(len(fnlist))
    firstimage = FPImage(fnlist[0])
    toggle = firstimage.fwhm
    axcen = firstimage.axcen
    aycen = firstimage.aycen
    arad = firstimage.arad
    firstimage.close()
    if axcen is None:
        print "Error! Images have not yet been aperture-masked! Do this first!"
        crash()
    if toggle is None:
        print "Warning! FWHMs have not been measured!"
        print "Assuming 5 pixel FWHM for all images."
        for i in range(len(fnlist)):
            fwhm[i] = 5
    else:
        for i in range(len(fnlist)):
            image = FPImage(fnlist[i])
            fwhm[i] = image.fwhm
            image.close()

    # Get sky background levels
    skyavg = np.empty(len(fnlist))
    skysig = np.empty(len(fnlist))
    for i in range(len(fnlist)):
        image = FPImage(fnlist[i])
        skyavg[i], skysig[i], _skyvar = image.skybackground()
        image.close()

    # Identify the stars in each image
    xlists = []
    ylists = []
    print "Identifying stars in each image..."
    for i in range(len(fnlist)):
        xlists.append([])
        ylists.append([])
        image = FPImage(fnlist[i])
        axcen = image.axcen
        aycen = image.aycen
        arad = image.arad
        sources = daofind(image.inty-skyavg[i],
                          fwhm=fwhm[i],
                          threshold=thresh*skysig[i]).as_array()
        for j in range(len(sources)):
            # If the source is not near the center or edge
            centermask = ((sources[j][1]-axcen)**2 +
                          (sources[j][2]-aycen)**2 > (0.05*arad)**2)
            edgemask = ((sources[j][1]-axcen)**2 +
                        (sources[j][2]-aycen)**2 < (0.95*arad)**2)
            if np.logical_and(centermask, edgemask):
                xlists[i].append(sources[j][1])
                ylists[i].append(sources[j][2])
        image.close()

    # Match objects between fields
    print "Matching objects between images..."
    xcoo = []
    ycoo = []
    for i in range(len(xlists[0])):
        # For each object in the first image
        accept = True
        for j in range(1, len(fnlist)):
            # For each other image
            dist2 = ((np.array(xlists[j])-xlists[0][i])**2 +
                     (np.array(ylists[j])-ylists[0][i])**2)
            if (min(dist2) > tolerance**2):
                accept = False
                break
        if accept:
            # We found an object at that position in every image
            xcoo.append(xlists[0][i])
            ycoo.append(ylists[0][i])

    # Create coordinate arrays for the photometry and shifting
    x = np.zeros((len(fnlist), len(xcoo)))
    y = np.zeros_like(x)
    for i in range(len(xcoo)):
        # For every object found in the first image
        for j in range(len(fnlist)):
            # Find that object in every image
            dist2 = ((np.array(xlists[j])-xcoo[i])**2 +
                     (np.array(ylists[j])-ycoo[i])**2)
            index = np.argmin(dist2)
            x[j, i] = xlists[j][index]
            y[j, i] = ylists[j][index]

    # Do aperture photometry on the matched objects
    print "Performing photometry on matched stars..."
    counts = np.zeros_like(x)
    dcounts = np.zeros_like(x)
    for i in range(len(fnlist)):
        image = FPImage(fnlist[i])
        apertures = CircularAperture((x[i], y[i]), r=2*fwhm[i])
        annuli = CircularAnnulus((x[i], y[i]), r_in=3*fwhm[i], r_out=4*fwhm[i])
        phot_table = aperture_photometry(image.inty,
                                         apertures, error=np.sqrt(image.vari))
        sky_phot_table = aperture_photometry(image.inty, annuli,
                                             error=np.sqrt(image.vari))
        counts[i] = phot_table["aperture_sum"] / apertures.area()
        counts[i] -= sky_phot_table["aperture_sum"] / annuli.area()
        counts[i] *= apertures.area()
        dcounts[i] = phot_table["aperture_sum_err"] / apertures.area()
        image.close()

    # Calculate the shifts and normalizations
    norm, dnorm = calc_norm(counts, dcounts)
    for i in range(x.shape[1]):
        x[:, i] = -(x[:, i] - x[0, i])
        y[:, i] = -(y[:, i] - y[0, i])
    xshifts = np.average(x, axis=1)
    yshifts = np.average(y, axis=1)

    # Normalize the images and put shifts in the image headers
    for i in range(len(fnlist)):
        image = FPImage(fnlist[i], update=True)
        image.phottog = "True"
        image.dnorm = dnorm[i]
        image.inty /= norm[i]
        image.vari = image.vari/norm[i]**2
        image.xshift = xshifts[i]
        image.yshift = yshifts[i]
        image.close()

    return