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
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
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
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
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
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)
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
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
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
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
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